Fiks rundedeling: minst én kategori er nok, ikke alle (ADR-036 E)

Bruker rapporterte at en runde delt med kategorien "Make" ikke ble
synlig for vedkommendes ektefelle. Diagnostisert direkte mot ekte
teecup_db (skrivebeskyttede spørringer): eieren hadde kategorisert
ektefellen under tre kategorier, mens runden kun delte to av dem.
_can_view_round krevde at ALLE en venns kategorier måtte være i
rundens synlige sett -- en presisering fra 2026-07-29 som kun ble
dokumentert i kode-kommentarer, aldri i denne ADR-en, og som strammet
inn utover ADR-036 Beslutning B sin opprinnelige "minst én
kategori"-regel fra 2026-07-25.

Reversert til den opprinnelige regelen i alle fire duplikate SQL-steder
(_can_view_round, _friends_who_can_see_round, list_friends_on_course i
rounds.py; feed-listingen i round_messages.py) -- fra "NOT EXISTS en
kategori utenfor synlig sett" til "EXISTS en kategori innenfor synlig
sett". Scratch-verifisert i begge retninger: venn med overlappende
kategori slipper nå inn, venn med ingen overlappende kategori fortsatt
korrekt avvist.

Se CHANGELOG.md punkt 44 for full verifiseringsdetalj og bekreftelse
mot de ekte, virkelige dataene etter utrulling.
This commit is contained in:
Erol Haagenrud 2026-08-08 12:37:09 +02:00
parent b0c65d983b
commit 8b1c73cf4f
3 changed files with 47 additions and 31 deletions

View file

@ -3207,6 +3207,37 @@ søk-UI i `round-detail.tsx`, "Gjest" omdøpt til "Medspiller" gjennomgående,
samme viewer-relative "Deg"-fiks portert til `round-stats.tsx`/
`round-scorecard.tsx`, som hadde samme latente bug).
### Beslutning E — Kategori-match ved rundevisibilitet: MINST ÉN, ikke ALLE
Beslutning B punkt 5 (over) spesifiserte fra start "minst én
`friend_categorization`-rad ... hvor kategorien finnes i
`round_visible_category`". En senere presisering 2026-07-29 (kun
dokumentert i kode-kommentarer, aldri i denne ADR-en — selve
dokumentasjonshullet som gjorde denne avviket vanskelig å spore) strammet
dette til at ALLE en venns kategorier måtte være i rundens synlige sett,
ikke bare én.
**Reversert til original Beslutning B-regel 2026-08-08**, etter at
brukeren rapporterte at en runde delt med kategorien "Make" ikke ble
synlig for vedkommendes ektefelle. Årsak: ektefellen var i tillegg
kategorisert "Storfamilie" (Beslutning A tillater flere kategorier per
venn), og siden "Storfamilie" ikke var huket av på DENNE runden, blokkerte
den strenge ALLE-regelen hele synligheten -- til tross for at den
relevante kategorien ("Make") faktisk var valgt. I praksis gjorde dette
det nærmest umulig å dele pålitelig med noen som var tagget i mer enn én
kategori, uten å huke av samtlige av dem hver gang.
**Ny/gjeninnført regel:** en venn ser runden hvis MINST ÉN av kategoriene
eieren har satt dem i, er i rundens synlige sett -- flere kategorier på
samme venn er bare flere sjanser til å matche, aldri en ekstra
begrensning. En venn med INGEN kategorier vises fortsatt aldri (uendret).
Rettet i fire duplikate SQL-steder (`app/routers/rounds.py`:
`_can_view_round`, `_friends_who_can_see_round`,
`list_friends_on_course`; `app/routers/round_messages.py`: feed-
listingen) -- samme sted-for-sted-duplisering som opprinnelig omtalt i
Beslutning B, nå alle konsistent på MINST ÉN-varianten.
---
## ADR-037: Individuelle turneringer, flerrunde-turneringer — grunnstruktur

View file

@ -511,11 +511,7 @@ async def get_feed(
AND EXISTS (
SELECT 1 FROM friend_categorization fc
WHERE fc.owner_user_id = r.owner_user_id AND fc.friend_user_id = $1
)
AND NOT EXISTS (
SELECT 1 FROM friend_categorization fc
WHERE fc.owner_user_id = r.owner_user_id AND fc.friend_user_id = $1
AND fc.category NOT IN (
AND fc.category IN (
SELECT category FROM round_visible_category WHERE round_id = r.id
)
)

View file

@ -2031,13 +2031,15 @@ async def _can_view_round(conn, round_id: str, viewer_user_id: str | None) -> bo
# kategorisering av viewer (ikke omvendt -- det er eieren som
# begrenser, basert på egen gruppering, ADR-036 Beslutning B).
#
# Presisert av bruker 2026-07-29: "ikke vise" overstyrer "vise" -- er
# vennen satt i FLERE kategorier, holder det IKKE at én av dem er
# synlig for runden; ALLE vennens kategorier må være i rundens synlige
# sett. En venn med INGEN kategorier i det hele tatt vises ALDRI (samme
# runde: kategorisering er nå obligatorisk fra vennskapet inngås, se
# friends.py -- en ukategorisert venn er derfor alltid en igjenværende
# unntakstilstand fra FØR den regelen, ikke en normal, forventet sti).
# Regel: MINST ÉN av vennens kategorier må være i rundens synlige sett
# (ADR-036 Beslutning E, 2026-08-08). En strengere "ALLE kategorier må
# være synlige"-variant gjaldt 2026-07-29--2026-08-08, men ga et reelt
# forvirrende utfall i praksis (bruker delte en runde med "Make"
# huket av, men vedkommendes ektefelle -- ALSO tagget "Storfamilie" --
# fikk ikke se runden fordi den kategorien ikke var huket av). Reversert
# til den opprinnelige ADR-036 Beslutning B-regelen (som alltid sa
# "minst én kategori", se punkt 5 der) etter brukerrapport. En venn med
# INGEN kategorier i det hele tatt vises fortsatt ALDRI.
if viewer_user_id is None:
return False
owner_id = row["owner_user_id"]
@ -2060,11 +2062,7 @@ async def _can_view_round(conn, round_id: str, viewer_user_id: str | None) -> bo
SELECT EXISTS(
SELECT 1 FROM friend_categorization
WHERE owner_user_id = $1 AND friend_user_id = $2
)
AND NOT EXISTS(
SELECT 1 FROM friend_categorization fc
WHERE fc.owner_user_id = $1 AND fc.friend_user_id = $2
AND fc.category NOT IN (SELECT category FROM round_visible_category WHERE round_id = $3)
AND category IN (SELECT category FROM round_visible_category WHERE round_id = $3)
)
""",
owner_id,
@ -2104,19 +2102,14 @@ async def _friends_who_can_see_round(conn, round_id: str, owner_user_id: str) ->
return []
if row["visibility_mode"] == "public":
return friend_ids
# Samme "alle kategorier må være synlige, ikke bare én"-regel som
# _can_view_round -- en venn med minst én ikke-synlig kategori
# ekskluderes fra fan-out-varselet, selv om en annen av kategoriene
# deres er valgt.
# Samme "minst én kategori er nok"-regel som _can_view_round (ADR-036
# Beslutning E).
matched = await conn.fetch(
"""
SELECT fc.friend_user_id::text AS friend_id
SELECT DISTINCT fc.friend_user_id::text AS friend_id
FROM friend_categorization fc
WHERE fc.owner_user_id = $1 AND fc.friend_user_id = ANY($2::uuid[])
GROUP BY fc.friend_user_id
HAVING COUNT(*) FILTER (
WHERE fc.category NOT IN (SELECT category FROM round_visible_category WHERE round_id = $3)
) = 0
AND fc.category IN (SELECT category FROM round_visible_category WHERE round_id = $3)
""",
owner_user_id,
friend_ids,
@ -2183,11 +2176,7 @@ async def list_friends_on_course(user: CurrentUser = Depends(get_current_user))
AND EXISTS(
SELECT 1 FROM friend_categorization fc
WHERE fc.owner_user_id = r.owner_user_id AND fc.friend_user_id = $1
)
AND NOT EXISTS(
SELECT 1 FROM friend_categorization fc
WHERE fc.owner_user_id = r.owner_user_id AND fc.friend_user_id = $1
AND fc.category NOT IN (
AND fc.category IN (
SELECT category FROM round_visible_category WHERE round_id = r.id
)
)