Shuffle/docs/specs/13-umsetzung.md
cschlaefke ec42421312
All checks were successful
CI / flutter (pull_request) Successful in 2m9s
CI / web-build (pull_request) Successful in 1m33s
CI / supabase (pull_request) Successful in 10s
CI / supabase-tests (pull_request) Successful in 37s
CI / e2e (pull_request) Successful in 4m14s
docs(db): Review-Nachbesserungen PR #67 (Spec, Nachtrag, Release-Checkliste, Testkorrekturen) Refs #13
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 20:03:50 +01:00

6.7 KiB
Raw Blame History

Spec #13 – KAT-04 Fehlendes Spiel melden: Umsetzungs-Nachtrag PR 13a (Backend)

Ergänzt docs/specs/13-fehlendes-spiel-melden.md (die Spec selbst wird hier nicht geändert). Stand: 2026-11-04, Branch feature/13a-fehlendes-spiel-backend (die Spec nennt feature/13a-meldungen-backend), Migration 20261104100000_game_reports_user.sql, Tests supabase/tests/database/10_game_reports_user.test.sql.

0. Entscheidung von Chris (2026-11-04, Option B, Spec-F10): kein „abgelehnt mit Verweis“

Dies ist eine Entscheidung von Chris, sie ändert Spec #2 Spec-F3. adopt_report_target (Funktion, Grants, Tests) wurde vollständig entfernt; ebenso entfallen die App-Zustände rejectedWithTarget, rejectedReference und rejectedAdopted. Wer auf einen vorhandenen Eintrag verweisen will, setzt merged (direktes Umhängen, ohne Nutzeraktion). rejected gibt es nur ohne Ziel.

  • admin_resolve_report ist per create or replace ersetzt (Rumpf aus 2c unverändert, einzige Ergänzung: rejected mit p_target → report_reject_has_target, P0001; Kommentar zu rejected angepasst). Rechte-Revoke ausdrücklich wiederholt.
  • Neuer Check game_reports_rejected_no_target (status <> 'rejected' or target_game_id is null). Verträglich mit admin_merge_games (hängt nur Ziele von approved/merged um) und repoint_game_references; keine Altdaten betroffen (2c ist nicht ausgerollt).
  • Tests: 08 (Fixtures „rejected mit Ziel“ → merged, neuer Test für den Check, rejected mit Ziel → report_reject_has_target, Runbook-Baustein 3c ohne Ziel), 10 (Abschnitt „adopt“ ersetzt durch merged/rejected-Tests). result_seen_at (Spalte, Recht, Policy „Verstanden“) bleibt.
  • Für 13b: nur noch Zustände pending / approved / merged / rejected (ohne Ziel); der Edition-Hinweis gilt bei approved und merged.
  • Spec-F10 statt F9: Die Nummer F9 ist in der Spec schon durch die vorausgewählte Liste belegt.

1. Abweichungen und Präzisierungen zum Vertrag

  • Spec-F6 ist entschieden und umgesetzt (2c): game_reports.editions_cleared und die Zuweisung in admin_resolve_report stehen schon in der Migration game_reports (PR 2c); 13a legt die Spalte nicht an.

  • Tageslimit gilt auch für postgres: Der Trigger game_reports_daily_limit unterscheidet nicht nach Rolle (Spec 3.2). Fixtures, die als postgres mehr als 10 Nutzer-Meldungen „von heute“ für einen Nutzer anlegen, scheitern deshalb. Folge für die Tests: 08_game_reports.test.sql schaltet den Trigger am Anfang ab (alter table … disable trigger, mit dem Rollback am Ende zurückgenommen); in 10 bekommen Fixtures für gestern/älter feste Zeitstempel. Außerdem ist created_at nach dem Anlegen unveränderlich (Trigger set_updated_at), alte Zeitstempel müssen also beim Einfügen gesetzt werden.

  • Limit-Zählung über report_day(created_at) = report_day(now()) (nicht die optionale Bereichsform aus Spec 3.2); bei höchstens 10 Zeilen je Nutzer und Tag spielt der Index (owner_id, created_at) keine Rolle.

  • Trimmen: report_missing_game trimmt Leerzeichen, Tab, CR und LF (nicht nur Leerzeichen). Titel, Verlag und Link werden vor allen Prüfungen getrimmt, Längen gelten für den getrimmten Wert.

  • find_similar_catalog_games: Der Titel wird vor der Normalisierung getrimmt und die Länge (1 bis 200) am getrimmten Wert geprüft. Ist der normalisierte Titel leer (kann nach normalize_game_title nur bei leerem Rest vorkommen), gibt es keine Treffer. Je Spiel wird der beste Kandidatentitel gewählt: gleicher Schlüssel vor Ähnlichkeit, dann Haupttitel vor alternativen Titeln, dann Schreibweise. Verlage sind nach publishers.name_search, dann name sortiert (wie in search_catalog_games). Bei gleicher Ähnlichkeit sortiert das Ergebnis nach title, dann game_id. Die Ähnlichkeit bei same_title ist immer 1.0.

  • report_provisional_in_use: Der Handler um den Insert in game_reports wertet den Namen des verletzten Index (game_reports_provisional_game_id_key) aus; andere Unique-Verletzungen laufen unverändert weiter. Über die RPC ist der Fall nicht erreichbar (jeder Aufruf legt ein frisches Spiel an); der Test lenkt ihn mit einem temporären Trigger herbei.

  • Testplan: 00_schema unverändert (22 Tabellen), 07/08-Fremdschlüssel-Wächter unberührt (kein neuer Fremdschlüssel), 08 um das Abschalten des Limit-Triggers ergänzt und nach Spec-F10 angepasst (Fixtures „rejected mit Ziel“ → merged, Tests zu report_reject_has_target und zum Check game_reports_rejected_no_target, Runbook-Baustein 3c ohne Ziel); die Spaltenrechte-Tests aus 08 waren schon auf result_seen_at und die Insert-Spalten ausgelegt.

  • Review-Nachbesserungen: find_similar_catalog_games behandelt ein Jahr außerhalb 1800-2100 wie null (kein Überlauf, kein 22003). Zusätzliche Tests: Tagesgrenze Berlin (10 Meldungen kurz nach Mitternacht Berlin, Trigger und Vorprüfung), Trimmen von Tab/Zeilenumbruch.

  • 08-Fixtures (N3) bleiben bei disable trigger: Ben und weitere Nutzer haben dort über 20 Fixture-Inserts mit Standard-created_at; sie alle auf feste alte Zeitstempel umzustellen wäre ein großer, fehleranfälliger Eingriff in 2c-Tests. Das Limit prüft 10 ausführlich.

  • Zielsperre (N1) unverändert for update: nicht umgestellt, weil admin_resolve_report und admin_merge_games ebenfalls for update sperren und eine gemischte Sperrstufe die Verklemmungsanalyse (Spec R2) ändern würde.

  • Check ohne NOT VALID (Einspielen auf Staging/Prod): game_reports_rejected_no_target prüft beim Anlegen alle vorhandenen Zeilen. Vor dem Einspielen von 13a prüfen: select count(*) from public.game_reports where status = 'rejected' and target_game_id is not null; Bei Treffern vorher bereinigen (Ziel auf null setzen oder Status auf merged umstellen). Der Punkt steht in docs/release-checkliste.md.

  • Uhrzeitabhängigkeit: Die Mitternachtstests (Tagesgrenze) hängen an der Uhrzeit: Läuft der Test zwischen Mitternacht Berlin und Mitternacht UTC, hat created_at dasselbe UTC-Datum wie now() und die Mutante created_at::date = now()::date überlebt. Das Verhalten der Funktion ist davon nicht betroffen.

2. Mutationsprüfung

Die Mutanten sind veränderte Kopien der Migration, jeweils in einer Transaktion zusammen mit dem Testlauf angewendet und mit dem Rollback des Tests zurückgenommen (am echten Schema bleibt nichts). Die Skripte liegen nicht im Repo. Ergebnis siehe Bericht an Chris; die überlebenden Mutanten sind entweder Doppelabsicherungen (zum Beispiel origin = 'user' in der Insert-Policy, die schon durch die Spaltenrechte gesichert ist) oder nur mit Parallelität testbar (Advisory-Lock).

3. Offene Punkte

  • Paralleltest des Limits (zwei psql-Sitzungen unter READ COMMITTED) ist nicht automatisiert (Spec 7.1) und gehört zu T1.