Bygg DELETE .../matches/{id}/participants/{id}
Scratch-verifisere DELETE-endepunktet
Integrere session-blind-draw.tsx mot ekte API
Finn+fiks synlighetshull: org-admin så ikke egne tilføyde deltakere
Ekte typesjekket frontend-build
Fikse+verifisere 500-krasj på null handicap_index
Rulle ut live
Oppdatere CLAUDE.md/FEATURE_BACKLOG.md
Slette zip (6) etter fullført integrering
Alt levert og live:
Blind draw-skjermen — /tournaments/[id]/sessions/[sessionId], koblet fra Program-siden
Ny DELETE-endepunkt for å angre et deltakervalg før låsing
Synlighetshull tettet — org-admin så ikke lenger sine egne tilføyelser før reveal (own_team_ids() manglet samme owner/admin-utvidelse som skrive-siden fikk forrige runde)
500-krasjen fikset — manglende handicap-indeks gir nå en ren 400 i stedet for å krasje, med use_handicap=false fortsatt upåvirket
Alt scratch-verifisert i flere runder, test_isolation.sql 12/12, ekte typesjekket build, begge containere redeployet, teeoff.no upåvirket. Zip (6) slettet. .md-filene er oppdatert med hele runden.
59 KiB
CLAUDE.md — arbeidsinstruks for TeeCup
Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber.
Autoritative kilder (les før du gjør noe)
ARCHITECTURE_DECISIONS.md— hva som er bestemt og hvorfor (ADR-001…018). Fasit.FEATURE_BACKLOG.md— hva som gjenstår, hva som er utsatt, hva som mangler.- Endres en beslutning: legg til en ny ADR, ikke slett historikk. Hold begge filene oppdatert når noe avgjøres.
Sikkerhetsregler (ufravikelige)
- Rør ALDRI
teeoff-databasen eller den ekteteecup_dbuten at brukeren eksplisitt har bekreftet det i samme økt. Test alltid migrasjoner mot en egen scratch-database først, og rydd opp etterpå. - Vis planen (hvilke kommandoer, mot hvilken database) FØR du kjører noe som skriver, migrerer eller sletter. Vent på bekreftelse.
- Hemmeligheter (passord, secrets) bor i
.env(filrettigheter 600), dekkes av.gitignore, committes aldri, og skrives aldri i klartekst i chatten eller i SQL-filer. Generer dem på serveren (openssl rand -base64 32). - Kjør appen som databaserollen
teecup_app(NOSUPERUSER, NOBYPASSRLS) — aldri somteeoff_admin/superuser i runtime.
Arkitektur-invarianter (ikke bryt uten en ny ADR)
- Tenant = organisasjon.
organization_idpå alle domenetabeller, håndhevet av RLS. App-koden setterapp.current_orgmedSET LOCALper transaksjon. - Verifiser at brukeren er medlem av organisasjonen FØR org-konteksten settes.
RLS stoler blindt på
app.current_org. - Egen innlogging (uavhengig av teeoff). Banedata hentes fra teeoff via lesende API, ikke delt database.
- v1 = nøyaktig to lag (Ryder Cup-format), håndhevet i app-laget. Match-modellen holdes generell (to sider) så knockout/flere lag kan komme senere.
- Handicap-/matchlogikk skal ligge i
handicap_engine.py(rent, testet, uten db/API-avhengigheter). Allowances er konfig, ikke hardkodet. - Media (bilder/video) skal i objektlagring (MinIO), ikke i Postgres. Postgres holder bare metadata + nøkkel.
Arbeidsmåte
- Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før du går videre.
- Bruk git (remote: brukerens Forgejo). Commit i logiske steg med tydelige meldinger.
- Er du usikker på omfang eller en beslutning: spør heller enn å gjette.
Status (oppdater denne når ting endres)
Ferdig og verifisert:
-
Handicap-motor + tester (24/24, R&A-verifisert).
-
Skjema
001+ roller002+ scoring/blind draw003. Isolasjon bevist medtest_isolation.sql(RLS-oppførsel, ikke bare at skjemaet kjører). -
002 hadde en reell bug (psql interpolerer ikke
:'var'inne iDO $$...$$) — permanent fikset, verifisert mot scratch to ganger. -
API-et kjørt for ekte (ikke bare syntaks-sjekket) i en engangs Docker- container mot en scratch-database, RLS bevist gjennom hele asyncpg-pool-stacken (ikke bare i rå SQL).
-
Oppsett-endepunktene er bygget og verifisert:
app/routers/players.py(spillerpool),tournaments.py(turnering/lag/roster/økter, ADR-011 to-lags-grense håndhevet medFOR UPDATE-lås),matches.py(matcher/ deltakere/blind draw-lås, ADR-013-synlighet push-down i SQL). Delt feiloversettelse iapp/errors.py, delte synlighetsspørringer iapp/blind_draw.py.main.pyer nå bare app-factory +include_router. -
Scoring-runden er bygget og verifisert for ekte mot scratch-db (18-hulls bane med
tee_rating, 4 spillere for fourball-testing):app/handicap.py(ADR-014 fire brytere viaparse_allowance_config, handicap beregnes icompute_and_store_side_handicapsrett etter deltaker-innsetting — singles/fourball per spiller umiddelbart, foursome/greensome/scramble kun når siden er komplett),app/routers/scoring.py(hole-scores/hole-results-upsert,scorecard-GET, matchstatus-recompute medFOR UPDATE-lås mot race og SAMMENHENGENDE-prefiks-regel for uferdige hull).app/team_authz.pyskilt ut framatches.py(delt medscoring.py). Alle 10 planlagte tester bestått, inkl. fourball better-ball-aggregering (MIN av to nettoer, venter til begge partnere har registrert), poeng-caching ved tidlig avgjort match, og ADR-014-bryterenuse_handicap=false. Fant og fikset underveis:tournaments.pysinSessionCreatemangletscoring_modehelt (økter kunne aldri opprettes ihole_result-modus via API-et) — lagt til. Bevisst utelatt/kjente begrensninger: en side som aldri når forventet deltakerantall (no-show) får aldri beregnet handicap og matchen kan da aldri avgjøres — ingen manuell overstyring bygget. Score-skriving er upsert (ingen avvisning ved duplikat) — ingen audit-trail på rettelser. Kapteins-only autorisasjon fortsatt ikke bygget (FEATURE_BACKLOG ❓); bar er «rostret på laget». -
Match-lås ved avgjørelse (2026-07-16):
submit_hole_score/submit_hole_resultavviser nå 409 hvismatch.points_side_a IS NOT NULL(matchen er avgjort) — FØR upserten kjøres, både for nye hull og korrigering av allerede talte hull. Tetter en reell bug: uten dette kunne «spøkelses-hull» lagt inn etter avgjørelse endre en allerede cachet margin ved neste omregning. Automatisk, ingen ny autorisasjon involvert. -
Ekte autentisering bygget og verifisert (2026-07-16):
X-Debug-User-Id- stubben er HELT fjernet (ingen fallback). Magic-link + JWT-sesjon iapp/routers/auth.py+app/auth.py(request-link/verify-link/logout/me), ny migrasjon004_auth.sql(magic_link_token-tabell + unik e-post-indeks påapp_user). Token =secrets.token_urlsafe(32), kun SHA-256-hash lagres, atomisk forbruk (UPDATE ... RETURNING, ikke les-sjekk-skriv), generisk respons uansett om e-posten finnes (unngår enumerering), gamle uforbrukte lenker ugyldiggjøres når en ny utstedes,app_useropprettes FØRST ved vellykket verifisering (ikke ved forespørsel). Sesjons-JWT (PyJWT,algorithms=["HS256"]eksplisitt) i HttpOnly/SameSite=Lax/dynamisk-Secure-cookie, 30 dager, med et ekte eksistens-oppslag motapp_userpå hver forespørsel (faktisk tilbakekalling — en slettet bruker kan ikke ri ut sesjonen). Alle 12 planlagte tester bestått. Fant og fikset underveis:ON CONFLICT (email)matchet ikke den nye PARTIELLE unike indeksen uten eksplisittWHERE email IS NOT NULL(samme klasse feil somhole_scores partielle indekser i scoring-runden). Fant, IKKE fikset i denne runden (egen runde rett etterpå — se under):organization-tabellens RLS-policy kastet en 500 i stedet for skjemaets lovede "trygg standard: se ingenting" ved tomstreng-GUC. -
RLS-tomstreng-bug FIKSET (2026-07-16): ny migrasjon
005_rls_null_guard.sql— deltSTABLESQL-funksjonapp_current_org()gjørNULLIF(current_setting('app.current_org', true), '')::uuidi stedet for det rå uttrykket, brukt av alle 15 RLS-policyer (ALTER POLICY, 14org_isolation+org_self). Verifisert med 3 nye regresjonstester itest_isolation.sql(Test 10-12) OG ved faktisk å gjenskape original- buggen mot en ekte container (pool-størrelse 1, varm opp medorg_connection(), deretter/auth/mepå samme gjenbrukte tilkobling — gikk fra 500 til 200). Viktig presisering fra denne runden: fiksen gjør IKKE at/auth/mekan joineorganizationdirekte viaplain_connection()— det var en feilaktig antakelse i forrige runde.org_selfkrever fortsatt en MATCHENDEapp.current_orgfor å vise en rad (riktig RLS-design, ikke noe fiksen skulle endre), og en bruker kan tilhøre flere organisasjoner samtidig, så det finnes ingen ÉN kontekst å sette for en tverr-org-spørring./auth/meslår derfor opp hvert org-navn ett om gangen viaorg_connection()(N+1, N = antall org-er brukeren tilhører) — dette er riktig løsning, ikke en omvei. -
Organisasjon-bootstrap bygget og verifisert (2026-07-16): nytt
POST /orgs(app/routers/organizations.py) — det ENESTE stedet i API-et som setter inn enorganization-rad. Fant under statusgjennomgang at dette manglet helt (alle tidligere org-er var seedet med superbruker-SQL). Ingen ny migrasjon. Selvrefererende RLS-bootstrap bekreftet å fungere: generer org-ens uuid i Python, settapp.current_orgtil nøyaktig den via eksisterendeorg_connection(), sett innorganization-raden med samme id —org_selfs implisitteWITH CHECKblir da trivielt sann, ingen privilegert tilkobling nødvendig (i motsetning til hva 002s kommentar antydet). Verifisert med 5 tester inkl. en negativ kontroll (mismatchende id avvist medinsufficient_privilege) og full kryss-org-isolasjon mellom to uavhengig opprettede organisasjoner. -
Ekte SMTP-utsending bygget og verifisert (2026-07-16): ny
app/email.py(send_magic_link_email,smtplibviaasyncio.to_thread, håndterer både implisitt TLS/port 465 og STARTTLS dynamisk). Brukeren la egne SMTP-credentials i.env(TEECUP_SMTP_*,TEECUP_FROM_EMAIL— ADR-009, ikke delt med teeoff); jeg leste kun nøkkelnavnene for å bekrefte de fantes, aldri verdiene.app/config.pysinSMTP_CONFIGUREDer valgfri (ikke_required) — dev-only logging (TEECUP_DEV_LOG_MAGIC_LINKS) fortsatt fungerer uendret når SMTP ikke er satt opp. Driftsfeil i utsendingen lekker aldri til klientresponsen (bevarer anti-enumerering). Verifisert med faktisk levering: sendte én ekte test-e-post til en adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i prosjektet er bevist ved ekte levering, ikke bare curl/scratch. -
Containerisert og LIVE på
teecup.teeoff.no(2026-07-16): ekteteecup_dbopprettet (migrasjoner 001→005 kjørt permanent,test_isolation.sqlbestår),Dockerfile+docker-compose.yml(tjenesteteecup_api, joiner det eksisterendeteeoff_default-nettverket), Caddy-blokk lagt til i/opt/teeoff/deploy/Caddyfile. Ekte innlogging (magic-link → e-post → JWT-sesjon medSecure-cookie) verifisert ende-til-ende mot den live stacken.teeoff.noupåvirket gjennom hele prosessen. To reelle hendelser underveis, begge løst:- Caddy plukket ikke opp filendringen —
teeoff_caddysinCaddyfile-mount er en ENKELTFIL-bind-mount, låst til inoden som fantes da containeren sist startet. Min fil-redigering (atomisk rename) laget en ny inode på samme sti, så containeren fortsatte å lese den GAMLE filen uansett hvor mange gangercaddy validate/caddy reload/admin-API/loadble kjørt (alle validerte/lastet den uendrede gamle filen, derav ingen feilmelding). Løst med en fulldocker restart teeoff_caddy(brukeren bekreftet — avvek fra planens "kun graceful reload, ingen omstart"-løfte, noen sekunders nedetid forteeoff.no). - Alvorlig nettverksalias-kollisjon (funnet RETT ETTER omstarten, da
ekte teeoff-trafikk som
/api/facilities?...med ekte klubb-slugs dukket opp iteecup_apisin logg):docker-compose.ymlsin service-nøkkel varapi:— SAMME nøkkel som teeoffs egetapi-servicenavn (docker-compose.prod.yml). Docker Compose registrerer nettverksalias basert på service-NAVNET (ikke barecontainer_name) på delte nettverk, så BEGGE containerne fikk aliasapipåteeoff_default— Caddysreverse_proxy api:8000i teeoff sin egen config kunne da tilfeldig treffe enten ekteteeoff_apiellerteecup_api. Rettet umiddelbart (stoppetteecup_apiførst for å hindre videre feilruting av ekte teeoff-trafikk, ga service-nøkkelen navnetteecup_apii stedet, gjenopprettet — bekreftet meddocker network inspectat aliasapinå KUN peker på ekteteeoff_api). Mindre driftslærdom: (a) jeg eksponerte ved et uhellTEECUP_SMTP_PASS/TEECUP_FROM_EMAILi eget debug-output mens jeg feilsøkte en.env-korrupsjon (manglende linjeskift fra min egen>>-tilføyelse) — brukeren roterte passordet som forsiktighetsregel; (b) Docker leser IKKE.envpå nytt for en allerede kjørende container —docker compose up -d --force-recreatekreves etter enhver.env-endring som skal tas i bruk; (c) et#-tegn i et upassordet.env-passord kuttes som en kommentar av Compose sin parser — anførselstegn (fortrinnsvis enkle) løser dette.
- Caddy plukket ikke opp filendringen —
-
Dato/klokkeslett + feilkode-kontrakt + i18n-forberedelse (2026-07-17, ADR-015): reist av brukeren rett før frontend-arbeidet. Ny migrasjon
006_scheduling_and_locale.sql(alt additivt):tournament.end_date,session.scheduled_at/tee_interval_minutes/start_hole,match.tee_time_override,app_user.preferred_locale,magic_link_token.locale.match.tee_timeer UTLEDET i Python (scheduled_at + (sequence-1)*tee_interval_minutes, override vinner hvis satt) — aldri lagret per match. Full retrofit av ALLE 39 daværendeHTTPException(..., detail="norsk streng")-steder på tvers av 6 filer til en deltapp_error(status_code, code, message)-factory (app/errors.py) — responsformen er nå konsekvent{"detail":{"code":...,"message":...}}i hele API-et, verifisert med et sistegrep -rn 'detail="' app/som ga NULL treff. i18n:locale(nb/en) sendes av klienten vedrequest-link, styrer e-postmalen (ekte engelsk mal lagt inn iapp/email.py, ikke bare rørlegging) OG settes som en HELT NY brukerspreferred_locale— en eksisterende bruker som logger inn på et annet språk får IKKE sin lagrede preferanse overskrevet. Fant og fikset underveis:tournament.start_datehar ligget i skjemaet siden migrasjon 001, men var ALDRI koblet tilTournamentCreate/Tournament-modellene — funnet som en naturlig bivirkning av å legge tilend_date. Verifisert grundig mot ferskteecup_scratch(001→006,test_isolation.sqlfortsatt 12/12): feilkode-form bekreftet på tvers av 5 filer (NOT_FOUND/LIMIT_REACHEDtournaments.py,DUPLICATEroster,NOT_AUTHENTICATEDuten cookie,NOT_ROSTERED_ON_TEAMmatches.py), tee_time-beregning bekreftet (10 min intervall → 08:00/08:10/08:20, override vinner,scheduled_at=nullgirtee_time:nullikke feil), full i18n-runde bekreftet (ny brukerlocale:"en"→preferred_locale:"en"; påfølgenderequest-linkfor samme bruker medlocale:"nb"skiftet e-postmalen men IKKE den lagrede preferansen, bekreftet via/auth/me). Kjørt mot ekteteecup_db2026-07-17 (bruker bekreftet eksplisitt i samme økt): migrasjonen kjørte rent,test_isolation.sqlfortsatt 12/12 (ruller alltid tilbake, ingen domenerader berørt). Mindre driftslærdom, funnet OG rettet samme runde: et.env-filter (grep -v -i 'pass|secret|key') jeg brukte for å lese ikke-sensitive nøkler fanget ikke oppTEECUP_DATABASE_URL, som barteecup_app- passordet innebygd i selve URL-en (i tillegg til at det SAMME passordet allerede lå rent iTEECUP_APP_PASSWORD— duplisert, ikke bare skjult) — passordet ble dermed synlig i et verktøyresultat. Flagget til brukeren umiddelbart (samme mønster som SMTP-passord-hendelsen over). -
.env/tilkobling ryddet opp (2026-07-17), samme runde: roten til hendelsen over var atTEECUP_DATABASE_URLvar én sammensatt DSN-streng med passordet URL-kodet inni — usynlig for navnebaserte secret-filtre. Erstattet med fem separate, rent navngitte felt:TEECUP_DB_HOST/TEECUP_DB_PORT/TEECUP_DB_NAME/TEECUP_DB_USER/TEECUP_DB_PASS(sistnevnte omdøpt fraTEECUP_APP_PASSWORD, kun nøkkelnavnet — verdien aldri lest eller skrevet av meg).app/config.pyogapp/db.pybygger nåasyncpg-poolen fra disse fem separate feltene (host=/port=/user=/password=/database=) i stedet for én DSN — fjerner også URL-prosentkoding-problemet fra SMTP-passord-hendelsen sin klasse av feil.docker-compose.ymlsinenvironment:-liste oppdatert tilsvarende. Krevde et fullt image-rebuild, ikke bare--force-recreate:DockerfilesinCOPY app/ app/bakes inn i imaget ved build-tid (ingen bind-mount i prod, i motsetning til scratch-verifiseringens engangscontainere) — en ren--force-recreategjenbrukte det GAMLE imaget og krasjet umiddelbart på den nå fjernedeTEECUP_DATABASE_URL. Rettet meddocker compose up -d --build --force-recreate. Verifisert ende-til-ende mot den live stacken: containeren boot-et rent (Application startup completei loggen, som krever en vellykketinit_pool()— asyncpg ville kastet og forhindret akkurat den logglinjen ved feil tilkoblingsparametre),/auth/meover ekte https ga et rent401 NOT_AUTHENTICATED(ikke 500/502),teeoff.noupåvirket (200gjennom hele omstarten, kunteecup_apirestartet — ikke delt Caddy-instans, ikke samme risikoklasse som Caddy-hendelsen fra containeriseringsrunden). -
Frontend startet, innlogging LIVE (2026-07-17, ADR-016): første frontend-skjerm i prosjektet. Designet i V0 (Next.js + Tailwind + shadcn/ui), hentet inn som
frontend/— merkevare-form/farge fra Teeoff-logoen, IKKE navn/logo (egne, separate produkter, se ADR-009). Kvalitetsrunde før bruk: V0s fargetokens var OKLCH-TILNÆRMINGER, ikke eksakte — regnet ut presise verdier fra#8bc24a/#ff5722og rettet alle 6 forekomster iglobals.css. Fjernet@vercel/analytics(unødvendig på egen-hostet infra), fjernettypescript: { ignoreBuildErrors: true }(ekte typesjekk kjører nå), fjernet dødtpnpm.overrides-felt.frontend/.gitignoremanglet.pnpm-store/— årsaken til at brukerens VSCode viste 10 000 "ukjente" filer ved første commit-forsøk; rettet. Kablet mot ekte API:next.config.mjssinrewrites()proxyer/auth/*//orgs/*//healthserver-side tilteecup_api— same-origin, ingen CORS, cookie uendret (full begrunnelse i ADR-016). Login-skjermet sender ektePOST /auth/request-link; ny/verify-side mottar?token=...fra e-postlenken (auto-verifiserer) eller viser et manuelt "lim inn koden"-felt.app/email.pyfikk en nyPUBLIC_BASE_URL- innstilling og sender nå en EKTE klikkbar lenke (koden beholdes som fallback). Reell fallgruve funnet og fikset ved containerisering: Next.js sinrewrites()løses ved BUILD-tid foroutput: "standalone", ikke ved container-oppstart — en runtime-e TEECUP_API_ORIGIN=...ble stille ignorert (proxy-kall feilet medECONNREFUSEDmotlocalhost:8000). Løst med en Docker build-timeARG TEECUP_API_ORIGINifrontend/Dockerfile, satt viadocker-compose.ymlsinbuild.args. Rullet ut live: nyteecup_frontend-tjeneste idocker-compose.yml. Caddy (teecup.teeoff.no, i det SEPARATEteeoff-repoet,/opt/teeoff/deploy/Caddyfile) endret fra å peke direkte påteecup_apitil å peke påteecup_frontend— samme stale-inode-oppførsel som containeriseringsrunden (gracefulreloadplukket IKKE opp endringen,/fortsatte å giteecup_apisin egen 404 i stedet for innloggingssiden til reload faktisk skjedde). Løst likt: fulldocker restart teeoff_caddy, brukeren bekreftet eksplisitt på forhånd.teeoff.noupåvirket gjennom hele omstarten. Verifisert med FAKTISK e-postlevering: ekte magic-link sendt til brukerens egen adresse overhttps://teecup.teeoff.no, ekte e-post mottatt med en ekte klikkbar lenke, åpnet i nettleser, landet på en fungerende/verify-side, sesjon opprettet — brukeren bekreftet innlogget status. Første gang en hel bruker-vendt flyt er bevist ende-til-ende i produksjon, ikke bare API-et isolert. Merk for neste økt:deploy/Caddyfile-endringen ligger uncommitted i det SEPARATE/opt/teeoff-repoet, ikke iteecup-repoet — lett å glemme siden denne økten ellers kun har jobbet i/opt/teecup. -
Dashboard-skjerm LIVE (2026-07-18): andre V0-skjerm — organisasjon- bytter/-opprettelse + turneringsliste (
/dashboard), samme mønster som login-runden. Reell integrasjonsfelle unngått: V0s eksport denne gangen var en FULL re-eksport av hele prosjektet (inkl.login-form.tsx,next.config.mjs,package.json), ikke bare de nye filene — en naiv utpakking ville stille reversertrewrites()-proxyen,output: "standalone", den ekte fetch-kablingen i login-skjemaet, og alle V0-uavhengige opprydninger fra forrige runde. Løst ved å pakke ut til et scratch-område FØRST, diffe mot live-treet fil for fil, og kun ta inn det som faktisk var nytt (dashboard.tsx,tournament-card.tsx,tournament-status-badge.tsx,wordmark.tsx,badge.tsx,dropdown-menu.tsx,app/dashboard/page.tsx) —next.config.mjs,package.json,globals.css, Docker-filene ble bevisst IKKE overskrevet.login-form.tsxfikk en kirurgisk patch (kun Wordmark flyttet til egen fil, som V0 selv hadde gjort — all egen fetch-/feilhåndteringslogikk urørt).dashboard.tsxsitt datalag skrevet om fra bunnen (V0 leverte kun mockuseState): henter/auth/mefor organisasjonsmedlemskap,/orgs/{id}/tournamentsper valgt org,POST /orgs/POST /orgs/{id}/tournamentsfor opprettelse,POST /auth/logoutfor utlogging — presentasjonskomponentene (kort, bytter, tomme tilstander) beholdt uendret fra V0./verify-siden oppdatert til å sende brukeren videre til/dashboardetter vellykket innlogging (fantes ingen dit å gå før nå). Verifisert: ekte typesjekket build, redeploy av kunteecup_frontend(ingen Caddy-endring nødvendig denne gangen — ADR-016s mønster holder),teecup.teeoff.no/dashboard→ 200,teeoff.noupåvirket. Skrive-flyten bekreftet med EKTE data samme dag (brukeren testet selv, ikke meg): organisasjon "Tjøme Gents" og turnering "De Gamle er Eldst" opprettet via UI-et mot den ekteteecup_db— statusmerket viste riktig "Utkast", "Ingen datoer satt" håndtert korrekt (ingen krasj på manglende dato), ett-org-visningen viste riktig uten unødvendig bytter-UI. Første gang en HEL skrive-flyt (ikke bare lesing) er bevist ende-til-ende fra frontend mot ekte produksjonsdata. -
Lag/roster-skjerm LIVE (2026-07-18): tredje V0-skjerm (
/tournaments/[id]), samme re-eksport-mønster som dashboard-runden — diffet mot live-treet, tok kun inntournament-detail.tsxog etLink-baserttournament-card.tsx(navigasjon fra dashbordet). URL-design bevisst avvikende fra V0s forslag: V0s genererte side leste aldriparams.idog hadde ingen organization_id i det hele tatt — holdt derfor V0s flate/tournaments/[id]-struktur (i stedet for en nøstet/orgs/[orgId]/tournaments/[id], som ville krevd manuell ombygging ved HVER fremtidig V0-reeksport) og laorg+nametil som søkeparametre itournament-card.tsxsin lenke — API-et krever organization_id på alle team-/roster-kall (RLS). Reelt hull funnet FØR integrering, ikke etter: V0-skjermen bygger inn "fjern spiller"/"gjør til kaptein"-handlinger, men backend hadde KUN GET/POST påteam_roster— ingen DELETE eller PATCH. Spurte bruker eksplisitt (samme mønster som andre scope-avklaringer denne økten) — svar: bygg de to endepunktene nå. Lagt til iapp/routers/tournaments.py:PATCH .../roster/{roster_id}(bevisst enkel — setter/fjerneris_captainpå NØYAKTIG denne raden, håndhever IKKE "kun én kaptein per lag", siden kaptein fortsatt bare er et merke, ikke en egen autorisasjonsrolle) ogDELETE .../roster/{roster_id}(204, idempotentNOT_FOUNDved dobbel sletting — ikke krasj).tournament-detail.tsxsitt datalag skrevet om fra V0s mock: henter lag + roster (roster-radene bærer allerededisplay_name/handicap_index_snapshotfra APIet, så V0s separatepoolById-oppslag ble fjernet som overflødig) og organisasjonens spillerpool (GET /orgs/{id}/players, brukt til type-ahead ved "legg til spiller"). Alle fem mutasjonene (opprett lag, legg til eksisterende spiller, opprett ny spiller inline + rostre, endre kaptein, fjern fra roster) kablet mot ekte endepunkter — presentasjonskomponentene (kort, type-ahead, fargevelger, bekreft-fjerning) beholdt uendret fra V0. Verifisert: de to nye endepunktene testet mot en ferskteecup_scratch(PATCH setter kaptein + riktigNOT_FOUNDpå ugyldig id, DELETE gir 204 + idempotentNOT_FOUNDved gjentak,test_isolation.sqlfortsatt 12/12), ekte typesjekket frontend-build (5 ruter), redeploy av BEGGE containere (backend-endepunktene er nye),teecup.teeoff.no/dashboard→ 200,teeoff.noupåvirket. Selve skrive-flyten på/tournaments/[id](opprett lag/roster) ikke testet med ekte data i denne runden — venter på brukeren, samme mønster som dashboard-rundens skrive-test. -
ADR-017 + migrasjon 007 (2026-07-18): brukeren reiste selvregistrering rett etter at roster-skrive-flyten var bekreftet. Full ADR skrevet (5 beslutninger: offentlig påmelding uten innlogging, e-post som sammenkoblingsnøkkel mot forhåndsopprettede spillere, egen
tournament_registration-tabell atskilt frateam_rostermed konfigurerbar godkjenning/kapasitet/venteliste, utvidet spillerprofil + obligatorisk samtykke, ogpublic_tournament_org()). Migrasjon007_registration_and_player_fields.sqlskrevet og scratch-verifisert (001→007 kjører rent,test_isolation.sqlfortsatt 12/12). Reelt arkitekturproblem løst underveis, ikke bare skjema: et offentlig (uautentisert) påmeldingskall kjenner en turnering-id, men ingen org-kontekst — og utenapp.current_orgslipper RLS ingen rader gjennom, heller ikke oppslaget for å FINNE riktig org. Løst med en sneverSECURITY DEFINER-funksjon (public_tournament_org) som KUN eksponerer uuid→uuid-koblingen. Verifisert presist, ikke bare "kjørte uten feil": kalt funksjonen somteecup_app-rollen med ingenapp.current_orgsatt — ga korrekt org-id for en kjent turnering,NULL(ikke feil) for en ukjent — OG et RÅTTSELECTpåtournamentpå SAMME tilkobling/rolle ga fortsatt 0 rader, som beviser RLS ikke er brutt generelt, bare dette ene smale unntaket eksisterer. Kjørt mot ekteteecup_db2026-07-18 (bruker bekreftet eksplisitt i samme økt): migrasjonen kjørte rent,test_isolation.sqlfortsatt 12/12. -
Registrerings-API LIVE (2026-07-18), samme dag: ny
app/routers/registration.py—GET /public/tournaments/{id}ogPOST /public/tournaments/{id}/register, begge UTENget_current_userellerget_authorized_org(helt uautentisert, egen/public-prefiks, bevisst atskilt fra/orgs/...i koden). Brukerpublic_tournament_org()(migrasjon 007) til å slå opp org-kontekst FØR RLS kan håndheve noe.app/routers/players.pyutvidet med alle sju nye ADR-017-feltene (mobile/email/birth_date/nickname/country/club/club_member_number).frontend/next.config.mjssinrewrites()utvidet med/public/*(ADR-016s konsekvens: enhver ny API-prefiks MÅ inn her). Ny brukers-oppdaget notat fanget FØR bygging, ikke etter: brukeren krevde eksplisitt at synlighet (offentlig/kun org/kun turnering- deltakere) må være et VALG for fremtidige landingssider — notert grundig iFEATURE_BACKLOG.md(koblet til samme åpne spørsmål for "Banter Board"-feeden) FØR dette registrerings-API-et ble bygget, akkurat for at det ikke skal gå i glemmeboken til landingsside-runden. Verifisert grundig mot ferskteecup_scratch(001→007,test_isolation.sql12/12): hele registreringsløpet testet reelt — samtykke-avvisning (400), duplikat-avvisning (409DUPLICATE), e-post-matching mot en organisator-forhåndsopprettet spiller (BEKREFTET: ingen duplikatrad, mobil fylt inn viaCOALESCE,display_nameIKKE overskrevet), kapasitet+waitlist-policy (→waitlisted), kapasitet+closed-policy (→ 409LIMIT_REACHED),registration_ requires_approval(→pending), utløpt frist (→ 409REGISTRATION_CLOSED), ogconfirmed_countiGET-responsen talt riktig (kunconfirmed+pending, ikkewaitlisted/avviste). Rullet ut live: begge containere redeployet,teecup.teeoff.no/ dashboardog/healthfortsatt 200,teeoff.noupåvirket, det offentlige endepunktet bekreftet nåbart over ekte https (ukjent turnering-id ga korrekt404/NOT_FOUND, ikke-destruktiv sjekk — selve påmeldingsflyten med ekte data ikke testet mot prod i denne runden). E-post-basert kontosammenkobling LIVE, samme dag: ny migrasjon008_link_player_by_email.sql—link_player_by_email(user_id, email), sammeSECURITY DEFINER-mønster sompublic_tournament_org()(007), denne gangen for en tverr-org UPDATE i stedet for et lese-oppslag.verify_magic_link(app/routers/auth.py) kaller den på HVER innlogging (idempotent — funksjonensWHERE user_id IS NULLgjør gjentatte kall til en no-op), ikke bare ved førstegangsopprettelse. Verifisert presist: to separate org-er, hver med sin egen organisator-opprettede "Kari"-rad (samme e-post, ulik store/små bokstaver for å teste case-insensitivitet også) — ved Karis FØRSTE innlogging ble BEGGE radene koblet til kontoen hennes, i to org-er hun aldri har vært medlem av. Eksplisitt bekreftet:organization_membershiphar NULL rader for henne etterpå — ren identitetskobling, ingen privilegie-eskalering (å haplayer.user_idsatt gir ingen ny tilgang gjennomget_authorized_org, som fortsatt krever ekte org-medlemskap uavhengig av dette). Andre innlogging idempotent, ingen feil.test_isolation.sqlfortsatt 12/12. Kjørt mot ekteteecup_db2026-07-18, bruker bekreftet eksplisitt, backend redeployet, live sjekker OK,teeoff.noupåvirket. ADR-017s backend er dermed komplett (registrering + kontokobling). Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere ADR-runde (seFEATURE_BACKLOG.md). -
ADR-018 + migrasjon 009: landingssider, backend LIVE (2026-07-18), samme dag: trenivås synlighet (
tournament.visibility:public/org/participants, defaultorg— trygg standard) +organization.public_profile. Reell presisering funnet underveis, ikke antatt på forhånd: RLS (org_isolation) beskytter kun TENANT-grenser (org A ser aldri org B), IKKE innholds-synlighet innenfor riktig org-kontekst — det eksisterendeGET /public/tournaments/{id}(ADR-017) leste allerede fullt innhold uten synlighetssjekk, fordi RLS er fornøyd så snart org-konteksten er satt, uansett hvem som spør.visibilityhåndheves derfor eksplisitt iapp/routers/registration.py, på BÅDE lesing og registrering (ADR-018 Beslutning D: kan du ikke se turneringen, kan du heller ikke melde deg på den — bekreftet med bruker FØR bygging). Nyget_current_user_optionaliapp/auth.py(somget_current_user, men returnererNonei stedet for 401 — offentlige endepunkter skal fungere for anonyme lesere også). Ny "deltaker"-autorisasjonsvei: en innlogget bruker medplayer.user_idkoblet (ADR-017) OG entournament_registration- ellerteam_roster-rad for NØYAKTIG den turneringen får separticipants-synlige turneringer, uansett org-medlemskap. Ekte migrasjonsfeil funnet OG rettet UNDER scratch-verifisering, ikke antatt riktig: migrasjonen feilet først ("column slug already exists") —organization.slughar ligget i skjemaet siden migrasjon 001 ("f.eks. subdomene/URL-vennlig", allerede med en plainUNIQUE), noe jeg hadde oversett fullstendig og forsøkt å legge til på nytt. Rettet ved å fjerne den dobleADD COLUMN+ den overflødige partielle unik-indeksen (001 sin plainUNIQUEdekker "unik når satt" allerede, siden Postgres behandler NULL som distinkt), beholde kun de nyeCHECK-constraintene. Kjørte rent på ny etter fiksen. Lærdom: grep alltid eksisterende skjema for feltnavn FØR en ny migrasjon skrives, ikke bare stol på hukommelsen om hva som "sikkert" ikke finnes fra før. Ny tabelltournament_sponsor(navn+lenke aktivt,logo_keyinert til MinIO-runden — samme medtournament.hero_image_key). TredjeSECURITY DEFINER-bro i prosjektet:public_org_by_slug()(etterpublic_tournament_org007,link_player_by_email008) — returnererNULLfor BÅDE "finnes ikke" og "finnes, men er privat", samme anti-enumerering som magic-link. Fylte også et implisitt hull oppdaget underveis: ADR-en beskrev hvordan synlighet skulle håndheves, men ingen tidligere runde hadde bygget noen vei for organisator til faktisk å SETTE disse feltene. Lagt til:PATCH /orgs/{id}/tournaments/{id}(visibility/description/ registrerings-innstillinger, ekte PATCH-semantikk via Pydantic sinexclude_unset— et utelatt felt nullstilles IKKE),PATCH /orgs/{id}(slug/public_profile), full sponsor-CRUD._fetch_sessions()trukket ut som delt hjelpefunksjon itournaments.py(delt mellom den innloggede og den nye offentligeGET /public/tournaments/{id}/sessions— blind draw-hemmelighold, ADR-013, arves automatisk, ikke reimplementert). Verifisert grundig mot ferskteecup_scratch(001→009,test_isolation.sql12/12): hele synlighetsmatrisen testet med ekte HTTP-kall — anonym avvist påorg-synlig turnering (både lesing OG registrering),PATCHtilpublic+ beskrivelse + sponsor fungerte, anonym lesing fungerte deretter,participants-synlighet bekreftet reell chicken-and-egg-konsekvens av Beslutning D (ingen kan selv- registrere seg til enparticipants-synlig turnering, kun organisator kan legge til direkte — korrekt, ikke en bug), en organisator-rostret spiller som logget inn fikk tilgang, en tilfeldig innlogget FREMMED (ikke deltaker) ble fortsatt avvist, org-landingsside viste KUNpublic-synlige turneringer,CHECK-constraint (public_profilekreverslug) avvist korrekt, ugyldig slug-format avvist av Pydantic, sponsor-sletting fungerte. Kjørt mot ekteteecup_db2026-07-18, bruker bekreftet eksplisitt, backend redeployet, live sjekker OK,teeoff.noupåvirket. Ingen frontend-endring nødvendig for selve API-tilgangen (det brede/public/:path*-mønsteret fra ADR-016 dekker allerede/public/orgs/*). Gjenstår: selve landingsside-SKJERMENE i frontend (V0), og MinIO/bildeopplasting — bevisst utsatt, egen runde. -
Offentlig turnering-landingsside LIVE (2026-07-18), samme dag: fjerde V0-skjerm (
components/public-tournament.tsx, ny rute/t/[id]) — banner (bevisst permanent farge-/gradient-utseende, ikke en "bilde kommer"-plassholder), presenterende tekst, status (datoer + "X av Y plasser"), program, sponsorer (navn+lenke), og et påmeldingsskjema med navn+e-post synlig først og resten bak en "flere detaljer"-utvidelse — tre distinkte bekreftelsestilstander (bekreftet/venteliste/godkjenning venter), ikke én generisk "takk". Ingen ny reeksport-kollisjon denne gangen — kun étt genuint nytt filnavn (public-tournament.tsx), resten var kjent V0-revert av allerede-tilpassede filer, samme mønster som før. V0 opprettet komponenten, men ingen rute — la selv tilapp/t/[id]/ page.tsx(bevisst en FLAT/t/[id]-sti, ikke nøstet under/orgs/...som den innloggede turnering-detalj-siden, siden det offentlige API-et kun trenger turnering-id, ikke org-id). Datalaget skrevet om fra V0s mock: ektefetchmotGET /public/tournaments/{id}+/sessions, ektePOST .../register. Håndterer 403 (NOT_VISIBLE, ADR-018) og 404 med en egen tilgang-avvist- tilstand V0 ikke hadde bedt om (fantes ikke i prompten, men trengs for at siden faktisk skal fungere fororg/participants-synlige turneringer). Mappetstatus:"waitlisted"fra API-et til komponentens"waitlist", og skjemaets norske kjønnsvalg ("kvinne"/"mann"/"annet") til API-ets^[mfx]$-mønster — to reelle navnekollisjoner mellom V0s UI-språk og API-kontrakten, ikke bare et rett-frem felt-for-felt-uttrekk. Reell driftsfeil funnet OG rettet under scratch-test, ikke i produksjon:pnpm install(uten--ignore-scripts) i dev-server- testoppsettet feilet stille med tom logg og exit 1 — corepack hadde hentet en NY pnpm-versjon (11.13.1 → 11.14.0) siden sist, som gjør "ignored builds"-varselet til en hard feil i stedet for bare en advarsel. Rettet ved å bruke samme--ignore-scripts-flagg somfrontend/Dockerfileallerede bruker (upåvirket av denne — bekreftet ved at selve prod-buildet fortsatt gikk rent). Lærdom:Dockerfilesin pinning av kode er ikke det samme som å pinne verktøyene rundt (corepack henter alltid siste pnpm) — verdt å huske neste gang et scratch-dev-server-oppsett plutselig feiler uten åpenbar grunn. Verifisert grundig mot ferskteecup_scratch+ en ekte kjørende frontend-dev-server (ikke barenext build): ekte turnering med beskrivelse, kapasitet, sponsor og økt opprettet via API-et, hentet gjennom frontend-proxyen (ikke direkte mot backend) og bekreftet byte-for-byte riktig — inkludert en ektePOST-registrering som økteconfirmed_countfra 0 til 1. Rullet ut live, kunteecup_frontend(ingen backend-endring denne runden),teeoff.noupåvirket. Gjenstår: org-landingssiden (egen V0-prompt), Open Graph-metadata for deling, MinIO/bilder. -
Offentlig klubb-landingsside LIVE (2026-07-18), samme dag: femte og siste V0-skjerm i ADR-018 (
components/public-club.tsx, ny rute/clubs/[slug]). Samme banner-språk som turnering-siden, liste over klubbens turneringer (gjenbrukte eksisterendeTournamentCard), egen tom- tilstand. Reell delt-komponent-kollisjon løst, ikke duplisert bort:TournamentCardvar bygget for KUN den innloggede konteksten (krevdeorgId, lenket til/tournaments/{id}?org=...). I stedet for en egen kopi av kortet for den offentlige siden, gjortorgIdvalgfri — satt (dashbordet) gir innlogget lenke, utelatt (klubbsiden) gir/t/{id}i stedet. Samme kort, to kontekster, ingen duplisering. Bekreftet dashbordets egen bruk uendret/upåvirket etterpå. V0 opprettet denne gangen selv en rute (app/clubs/[id]/page.tsx, uten noen props sendt inn i det hele tatt) — men navnga parameteren[id]selv om den faktisk er en SLUG (public_org_by_slug(), ADR-018). Skrev selv en ny, riktigapp/clubs/[slug]/page.tsxi stedet for å bruke V0s (feilnavngitte og prop-løse) versjon. Verifisert mot ferskteecup_scratch+ ekte kjørende frontend-dev- server: org med slug+public_profile, én turnering sattpublic, én latt stå på defaultorg— klubbsiden viste GJENNOM frontend-proxyen kun den ene offentlige turneringen, den org-private var korrekt usynlig (samme filtermønster som API-et selv, bekreftet fra klientsiden også). Ukjent slug ga korrekt 404. Rullet ut live, kunteecup_frontend,teeoff.noupåvirket. ADR-018s planlagte skjermer er dermed komplette. Gjenstår: Open Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere runder. -
Open Graph-metadata LIVE (2026-07-18), samme dag — ADR-018 dermed helt ferdig:
generateMetadata()lagt til på/t/[id]og/clubs/[slug](ekte tittel/beskrivelse fra API-et,og:site_name, trygg fallback-tittel ved ukjent id/slug — feil her skal ALDRI hindre selve siden i å laste). Reell driftsfeil funnet FØR den nådde produksjon, ikke etter:generateMetadata()kjører server-side ved REQUEST-tid, ikke i nettleseren — går derfor IKKE gjennomnext.config.mjssinrewrites()(som kun gjelder nettleser-trafikk inn til Next.js- serveren). Måtte derfor leseTEECUP_API_ORIGINdirekte, men den variabelen fantes KUN iDockerfilesitt builder-steg —ENVsatt i ettFROM-steg arves ikke til et senere. Rettet ved å sette sammeARG/ENVpå nytt i runner-steget også. Verifisert presist at fiksen faktisk virker, ikke bare at bygget gikk gjennom: bygget det EKTE produksjonsimaget (ikke dev-server) pekt mot en scratch-backend med en ekte offentlig turnering+org, hentet den faktiske server-rendrede HTML-en og bekreftet ekte<title>/og:title/og:description— ikke bare at TypeScript kompilerte. Ukjent turnering-id ga korrekt trygg fallback-tittel. Bevisst utenfor omfang:og:image— ingen ekte bilde finnes ennå (MinIO-runden). La tilmetadataBaseiapp/layout.tsxnå likevel, som forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en egen fiks da. Rullet ut live, kunteecup_frontend,teeoff.noupåvirket. -
MinIO-runden LIVE (2026-07-18), samme dag — ADR-018 dermed HELT ferdig, ingenting utsatt igjen: ny
teecup-minio-tjeneste (persistent volum, genererte credentials i.env), backend-endepunkter for ekte bildeopplasting tiltournament.hero_image_key/tournament_sponsor. logo_key(som lå inerte siden migrasjon 009), offentlige API-svar bygger nå fulle URL-er,og:imagekoblet på i/t/[id]singenerateMetadata. Sent, men viktig presisert krav underveis: brukeren avbrøt en verktøyskall midtveis for å presisere at ALLE bilder skal konverteres til AVIF for plassbesparelse. Snudde HELE opplastingsarkitekturen på dette: droppet den opprinnelige planen om presignerte URL-er (nettleser laster opp DIREKTE til MinIO) til fordel for ekte multipart-opplasting GJENNOM API-et, som konverterer til AVIF (Pillow + pillow-avif-plugin) FØR lagring. Dette forenklet arkitekturen betydelig: kun ÉN MinIO-klient trengs nå (før: to separate klient-oppsett for henholdsvis internt admin-arbeid og offentlig presignering), og Caddy sin nye rute trenger ikke lenger bevare Host-headeren presist (relevant kun for SigV4- signaturverifisering av presignerte URL-er, ikke for anonym public-read). To reelle feil funnet UNDER scratch-verifisering, aldri i produksjon: (1)pillow-avif-pluginkrever ingen ekstra systempakker ipython:3.12-slim-- verifisert med et frittstående encode/decode- rundtrip FØR det ble tatt i bruk i selve API-et. (2) MinIO validererHost-headeren STRENGT og avviser understrek som ugyldig vertsnavn -- et tjenestenavn med understrek (teecup_minio, konsistent medteecup_api/teecup_frontend) feilet umiddelbart ved oppstart ("Invalid Request (invalid hostname)"). Bekreftet presist ved å teste samme oppsett med bindestrek i stedet (teecup-minio) -- fungerte umiddelbart. Alle referanser rettet til bindestrek FØR noe ble forsøkt mot ekte infrastruktur. Caddy: ny/teecup-media/*-rute på det EKSISTERENDEteecup.teeoff.no-blokket (IKKE et nytt subdomene -- en tidlig sjekk avdekket atmedia.teecup.teeoff.nofantes som en wildcard DNS-post, men pekte til en IPv6-adresse denne serveren ikke har i det hele tatt; path-prefiks på et allerede fungerende domene unngikk hele den DNS- avhengigheten). Samme stale-inode-oppførsel som alle tidligere Caddy- runder -- fulldocker restart teeoff_caddy, brukeren bekreftet eksplisitt.teeoff.noupåvirket. Verifisert grundig, i flere lag: frittstående AVIF-encode/decode- test, full scratch-kjede (fersk Postgres + en ISOLERT scratch-MinIO- container) med et ekte opplastet bilde -- bekreftet konvertert til gyldig AVIF (800×400, 406 bytes for et helfarget testbilde), bekreftet lagret med riktig nøkkel, bekreftet lesbart ANONYMT direkte mot MinIO (uten Caddy, isolerer bucket-policyen), bekreftethero_image_url/logo_urlbygget riktig i det offentlige API-svaret. Alle tre valideringsveier testet (ugyldig content-type, korrupt bildeinnhold, for stor fil >8MB) -- alle ga korrektVALIDATION_FAILED. Etter Caddy-omstart: et ekte anonymt kall mot/teecup-media/...på produksjonsdomenet ga en ekte MinIO S3-XML-feilrespons (NoSuchKeyfor en scratch-nøkkel som naturligvis ikke finnes i prod) -- beviser ruten treffer MinIO selv, ikke frontend sin egen 404-side.test_isolation.sqlfortsatt 12/12 gjennom hele runden. Bevisst utenfor omfang: ingen faktisk opplasting av et EKTE bilde til en EKTE, live turnering i denne runden (ville krevd å skrive test-data i brukerens ekte konto uten å bli spurt) -- tilbudt, ikke utført. Selve dra-og-slipp-opplastingsskjermen i frontend (V0) er fortsatt ikke bygget, som avtalt fra starten av runden. -
Program-skjerm (økter/tidsplan), bygget og SCRATCH-verifisert, IKKE live ennå (2026-07-18): sjette V0-skjerm, første i "bygg i rekkefølgen ting brukes"-serien (etter lag/roster: program → blind draw → scorekort → leaderboard).
components/tournament-program.tsx, ny rute/tournaments/[id]/program. Tidslinje over økter + opprett-skjema (format, hullomfang, scoringsmodus, poeng, klokkeslett+intervall, starthull, kollapsbar "avansert"-seksjon med ADR-014s fire brytere). Reelt blokkerende hull funnet FØR integrering:SessionCreate. course_ider påkrevd, men INGEN endepunkt kunne noensinne produsere en — ADR-004s teeoff-integrasjon er fortsatt bare vedtatt (ingen utgående HTTP-klient finnes). Spurte bruker eksplisitt — svar: bygg enkel course-CRUD nå. Nyapp/routers/courses.py(GET/POST /orgs/{id}/ courses, kunsource='custom'). Program-skjemaet fikk et type-ahead-felt for bane (samme mønster som spiller-type-ahead i roster-skjermen). Reell korrekthetsfeil rettet FØR integrering: V0-promptet mitt ba om ett generisk "Scramble"-valg, men skjemaetsCHECK-constraint oghandicap_engine.pysinFormat-enum kreverscramble_2/scramble_4som distinkte verdier -- ren"scramble"avvises med 400. Rettet i frontend-mappingen til to segment-knapper.allowance_override-JSON-formen verifisert eksakt motapp/handicap.pysinparse_allowance_config/_strategy_from_json({type: "combined"|"per_player", percentage: 0..1}, ikke en flat prosent) -- frontend velger riktigtypeut fra om formatet er side-enhet eller spiller-enhet, konverterer 0–100-skjemafelt til 0–1. Scratch-infrastruktur denne runden, bevisst forskjellig fra tidligere: brukte en ISOLERTteecup_app_scratch-rolle (GRANT teecup_app TO teecup_app_scratch) i stedet for den ekteteecup_app-rollen -- den er nå cluster-global og produksjonskritisk (delt Postgres-instans medteecup_db), så et eldre plandokuments "drop teecup_app-rolle"- opprydning (skrevet FØR go-live) er utdatert og ble bevisst IKKE fulgt. Egen isolert scratch-MinIO-container også (app-oppstart krever en nåbar MinIO forensure_bucket()). Verifisert: courses opprettet+listet, kryss-org-isolasjon bekreftet, økt med klokkeslett, økt medscramble_4+fullallowance_override- rundtur, gammel"scramble"-verdi avvist (400),test_isolation.sql12/12, ekte typesjekket PRODUKSJONSBUILD (sammeDockerfilesom faktisk deployes) kjørt og bekreftet. Diffet V0-eksporten mot live-treet FØR noe ble tatt inn (samme mønster som alle tidligere runder): kun tre reelt nye filer, resten forventede full-reverts, ikke rørt. Fjernet V0s dev-only forhåndsvisnings-toggle; lagt til fanerad ("Lag og spillere" / "Program") i BEGGE skjermene siden V0 ikke visste om den andre når den ble generert i egen prompt. Rullet ut live 2026-07-18, bruker bekreftet eksplisitt:docker compose up -d --build teecup_api teecup_frontend(kun disse to,teecup- miniourørt). Verifisert: begge containere boot-et rent (Application startup complete, Next.jsReady),teecup.teeoff.no/healthog/dashboard→ 200,teeoff.noupåvirket (200). -
ADR-004 (teeoff-banedata) kartlagt, IKKE bygget (2026-07-18): brukeren påpekte rett etter program-skjerm-rullingen at ADR-004s teeoff- integrasjon fortsatt bare er vedtatt, ikke bygget (kun
source='custom'finnes). Kartlagt/opt/teeoff/backend/main.py(annet repo, kun lest):GET /api/facilities/{slug}(offentlig, INGEN auth/API-nøkkel) returnerer allerede alt teecup trenger —courses[].holes[](par,hcp_index= stroke index),courses[].tees[](name,cr_men/slope_men,cr_women/slope_women— kjønnsdelt WHS-rating). CORS-lista på teeoff- siden ekskluderer teecups origin, men er IRRELEVANT for et server-til- server-kall (kun nettleser-fetch rammes av CORS). Ingen dokumentasjon (README/OpenAPI) finnes for dette API-et i teeoff-repoet — kartleggingen over er basert på å lese koden direkte. Stabil identifikator:facilities. slug(f.eks.borregaard-golfklubb) — selve banen har kun en intern serial-id, ingen egen slug, såcourse.external_course_refmå bære facility-slug + bane-id sammen. Oppdatering samme dag: brukeren ba meg ta fatt på dette NÅ (bevisst sidesprang fra "bygg i rekkefølgen ting brukes"-planen — blind draw- skjermen er fortsatt neste steg i den planen ETTERPÅ, ikke droppet). Design besluttet og skrevet som ADR-019 (se ARCHITECTURE_DECISIONS.md): import (kopi) ved organisators eksplisitte valg, ikke live oppslag ved hver bruk — fryser data på importtidspunktet, samme reproduserbarhets- prinsipp som handicap-snapshotten (ADR-007). Server-til-server-kall (teecup_api→http://teeoff_api:8000, internt Docker-nettverk, ingen auth trengs). Ny migrasjon010(unikexternal_course_refper org, hindrer dupliserte importer). Se ADR-019 for alle fem delbeslutningene. Bygget, scratch-verifisert MOT EKTEteeoff_api(ikke en simulert respons — ekte HTTP-kall til den kjørende produksjonscontaineren, kun lesing): søkte opp "Borregaard" i teeoff sine 174 publiserte anlegg, hentet Borregaard Golfklubb sin hovedbane (18 hull, 4 tee-farger × kjønn), importerte den til en scratch-org — alle 18 hull med riktig par/stroke-index (1-18, unik), alle 8 tee/tee_rating-rader med riktig full_18 course/slope-rating verifisert direkte i databasen, opprettet deretter en ekte økt med den importerte banen somcourse_id(beviser hele veien til handicap-motoren fungerer, ikke bare selve importen). Reimport av samme bane korrekt avvist (409 DUPLICATE, migrasjon 010 sin indeks). Kryss-org-isolasjon bekreftet, ukjent teeoff-slug ga korrekt 404,test_isolation.sql12/12. Ekte typesjekket produksjonsbuild av frontend-utvidelsen (bane-søk i program-skjemaet: søk anlegg → velg bane → importer, samme UI-mønster som spiller-/bane-type-ahead ellers i appen). Rullet ut live 2026-07-18, bruker bekreftet eksplisitt: migrasjon 010 kjørt mot ekteteecup_db(kun én ny partiell unik-indeks, ingen eksisterende rader rørt), begge containere bygget+redeployet, live sjekker OK,teeoff.noupåvirket. "Bygg i rekkefølgen ting brukes"- planen gjenopptas nå — blind draw-skjermen er neste steg. Reell produksjonsbug funnet OG fikset samme dag, rapportert av bruker som faktisk brukte funksjonen: brukeren klikket seg korrekt via dashbord → turnering → Program-fane (bekreftet med skjermbilde + full klikk-sti, ikke gjettet), åpnet "Hent bane fra teeoff", søkte "Tjøme", og fikk feilmeldingen "Mangler organisasjon i lenken" — URL-en hadde da MISTET?org=...&name=.... Root-cause:OfficialCourseSearchsitt eget søke-<form onSubmit={runSearch}>var rendret INNICreateSessionCardsitt eksisterende<form onSubmit={handleSubmit}>-- nestede<form>-elementer er ugyldig HTML. Nettleseren slår sammen de to skjemaene i den faktiske DOM-en, så "Søk"-knappen submittet i praksis det YTRE økt-skjemaet som en ekte native side-navigasjon (GET til gjeldende sti, ingen navngitte felt => tom spørrestreng) -- dette vasket bort org-parameteren og landet brukeren på siden sin egen org-guard. Fikset ved å fjerne det indre<form>-elementet helt (vanlig<div>+ Enter-tast-håndtering på inputet +type="button"i stedet fortype="submit"på søkeknappen) -- gjør nestede skjemaer strukturelt umulig fremover for denne komponenten. Ekte typesjekket produksjonsbuild kjørt på nytt, kunteecup_frontendredeployet (ingen backend-endring). Lærdom for fremtidige skjermer: en ny søk-/underskjema-widget som skal plasseres INNI et eksisterende skjema (slik som denne bane-søk-widgeten ligger inni økt-opprett-skjemaet) må ALDRI være et eget<form>-- bruk<div>+ eksplisitt klikk-/Enter-håndtering. -
Organisator-overstyring i
team_authz.py, LIVE (2026-07-18): reist av brukeren rett før blind draw-skjermen skulle designes:app/team_authz.pysinuser_may_act_for_teamkrevde tidligere enteam_roster-rad med lenket bruker-konto — ingen vei for organisatoren til å låse/legge til deltakere/føre score hvis ingen spiller hadde logget inn ennå (vanlig tidlig i en turnering). Utvidet til også å godta org-eier/admin (organization_membership.role IN ('owner','admin')), på ETHVERT lag, ingen unntak for at organisatoren selv er rostret på motstanderlaget. Det unntaket ble bevisst vurdert og avvist (brukeren spurte selv om det, jeg anbefalte det opprinnelig, men vi kom sammen frem til at det ville skapt en verre låsning: er organisatoren spillende på Lag A og Lag B heller ikke har noen innlogget spiller, ville Lag B blitt helt låst ute) — TeeCup er et tillitsbasert klubb-/vennegjeng-verktøy, ikke en sikkerhetsgrense mot en fiendtlig organisator som uansett allerede ser begge lags fulle troppe-liste (blind draw skjuler kun selve kamp-paringen). Alle 5 kallsteder oppdatert (matches.py sin add_participant/lock_lineup, scoring.py sin submit_hole_score/ submit_hole_result). Verifisert grundig i scratch: org-eier uten roster kan nå låse BEGGE lag + føre score, en vanlig 'member'-rolle fortsatt blokkert (uendret), en rostret spiller fungerer uendret uavhengig av org-rolle.test_isolation.sql12/12. Ingen migrasjon (ren Python-endring) — kunteecup_apiredeployet,teeoff.noupåvirket. -
Tee-endepunkter bygget og LIVE (2026-07-18), rett før blind draw-skjermen: fant et hull som blokkerte selve blind draw-flyten:
match_participant. tee_ider påkrevd, men det fantes INGENGET-vei for å liste en banes tee-er (selv offisielt importerte baner har tee-rader, men ingenting eksponerte dem) OG egendefinerte («custom») baner har ALDRI hatt noen vei til å FÅ tee-er i det hele tatt — dette er ikke noe ADR-019 innførte, det var et hull som fantes fra før custom-baner ble lagt til i første omgang, bare usynlig til nå. Konsekvens før fiksen: en turnering satt opp på en manuelt navngitt bane kunne ALDRI få en ekte deltaker lagt til på noen match. Presisering (brukeren spurte eksplisitt): dette er IKKE noe som må løses i teeoff.no sin kode — egendefinerte baner er per definisjon baner teeoff ikke kjenner til, så dette er en ren teecup-intern funksjon, uavhengig av ADR-019-integrasjonen. NyGET/POST /orgs/{id}/courses/{id}/teesiapp/routers/courses.py.POSTavviser eksplisitt forsøk på offisielle baner (400VALIDATION_FAILED— de får tee-ene sine fra teeoff-importen, ikke manuelt). Kun full_18-rating dekket (samme begrunnelse som ADR-019 Beslutning D). Bevisst UTENFOR omfang denne runden, egen senere sak: hull-/stroke-index-data for egendefinerte baner (hole-tabellen forblir tom for custom-baner) — trengs for korrekt slagfordeling i SCORING-fasen (ADR-008), ikke i blind draw, så det løses naturlig når scorekort- skjermen bygges (samme "bygg i rekkefølgen ting brukes"-logikk). Verifisert grundig i scratch: tom tee-liste på fersk custom-bane, opprett+list tee på custom-bane, offisiell bane viser alle 8 importerte tee-er, manuell tee-opprettelse avvist på offisiell bane, og — den faktiske payoff-en — en deltaker lagt til en match på en custom-bane-økt for FØRSTE gang noensinne (POST .../matches/{id}/participantslykkes nå med en tee_id fra en nyopprettet custom-tee).test_isolation.sql12/12. Ingen migrasjon, kunteecup_apiredeployet,teeoff.noupåvirket. -
Blind draw-skjermen LIVE (2026-07-18): syvende V0-skjerm,
components/session-blind-draw.tsx, ny rute/tournaments/[id]/sessions/[sessionId]. To lag-kolonner, hver med sine matcher ("flights"), legg til/fjern spiller+tee per plass (antall plasser avhenger av format), lås-knapp med bekreftelse, avslørings-visning når begge lag har låst. Program-skjermens økt-kort er nå klikkbare inn hit. Datamodell tilpasset fra V0s mock til API-ets faktiske sett-modell: V0 designet faste, indekserte "Slot"-arrays (kontrollert skjema-state); API-et har verken PATCH påmatch_participanteller noen slot-indeks — kun opprett/slett av en løs deltaker-mengde per side. Løst med en "legg til spiller"-inline-form (samme mønster som spiller-/bane-type-ahead ellers i appen) i stedet for faste dropdown-rader; funksjonelt likeverdig, strukturelt riktigere for API-ets faktiske form. To reelle hull funnet og fikset FØR/UNDER integrering:- Ingen
DELETEfantes formatch_participant— en kaptein kunne aldri angre et valg før låsing uten å etterlate en foreldreløs rad. NyDELETE /orgs/{id}/matches/{match_id}/participants/{participant_id}(samme ALREADY_LOCKED/NOT_ROSTERED_ON_TEAM-sjekker som opprett). - "Skriv blindt"-hull, funnet under selve scratch-testingen (ikke bare
tenkt ut på forhånd): forrige rundes org-admin-overstyring i
team_authz.pylot en organisator LEGGE TIL deltakere på et lag de ikke selv er rostret på, menapp/blind_draw.pysinown_team_ids()sjekket KUN rostret spiller for SYNLIGHET — så organisatoren så aldri sine egne tilføyelser igjen før begge lag hadde låst. Fikset ved å giown_team_ids()samme owner/admin-utvidelse somuser_may_act_for_ team()— en org-admin ser nå BEGGE lag umiddelbart (konsistent med at de uansett allerede har full tilgang, se forrige rundes resonnement), mens en faktisk rostret kaptein fortsatt kun ser sitt eget lag før reveal. Kun ett reelt kallsted (matches.pysinlist_matches). Tredje, urelatert bug fanget under samme scratch-økt og fikset på brukerens eksplisitte forespørsel:POST .../matches/{id}/participantskrasjet med en rå 500 (TypeErrorihandicap_engine.pysincourse_handicap_raw) hvis spilleren manglethandicap_indexOGuse_handicapvar på (standard) — preeksisterende, ikke noe denne runden introduserte. Fikset iapp/routers/matches.pysinadd_participant: sjekker nåhandicap_index_snapshot IS NULLEKSPLISITT FØR innsetting nåruse_handicaper sann, avviser med en klarVALIDATION_FAILED(400) i stedet for å krasje. Bekreftet atuse_handicap=falsefortsatt tillater en spiller uten handicap (scratch-spill), og at en spiller MED handicap fortsatt fungerer uendret. Verifisert grundig i scratch, flere runder: DELETE-endepunktet (fjern+idempotent 404 ved gjentak+409 etter lås), synlighetsfikset (org- admin ser begge sider, rostret kaptein ser fortsatt kun eget lag), null-handicap-fikset (avvist rent, scratch-modus upåvirket, normal spiller upåvirket), full opprett-match→legg-til-deltaker→lås→avslør- syklus ende-til-ende.test_isolation.sql12/12 etter hver runde. Ekte typesjekket produksjonsbuild. Rullet ut live, bruker bekreftet eksplisitt: begge containere bygget+redeployet (ingen migrasjon),teeoff.noupåvirket.
- Ingen
Neste steg:
- Scorekort-skjermen (neste i "bygg i rekkefølgen ting brukes") — her må også hull-/stroke-index-data for egendefinerte baner løses (bevisst utsatt fra tee-rundens tidligere status-notat). Deretter leaderboard. Samme mønster: design i V0 (fortsett i samme prosjekt), FORVENT en full re-eksport hver gang — diff mot live-treet i et scratch-område før noe pakkes ut over eksisterende filer, og sjekk om V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR integrering.
- PWA-egenskaper (manifest, service worker, offline-cache) — ikke startet.
- Kommunikasjon (chat/feed) — ikke startet.