docs(catalog): Spec für KAT-05 Katalogdaten (freigegeben) #51

Merged
cschlaefke merged 6 commits from docs/2-kat05-spec into main 2026-11-03 14:15:11 +00:00
Showing only changes of commit b77146eabf - Show all commits

View file

@ -3,7 +3,7 @@
| | |
|---|---|
| Issue | #2 (KAT-05) |
| Status | freigegeben (Chris, 2026-11-03); Spec-F1–Spec-F9 entschieden wie empfohlen (§ 9.2); Review und Re-Review PR #51 eingearbeitet (§ 10); Designwahlen in § 9.3 warten auf Einzelbestätigung durch Chris |
| Status | freigegeben (Chris, 2026-11-03); Spec-F1–Spec-F9 entschieden wie empfohlen (§ 9.2); Review und Re-Review PR #51 eingearbeitet (§ 10); Designwahlen in § 9.3 einzeln bestätigt (Chris, 2026-11-03, Issue #2) |
| Autor | architect |
| Stand | 2026-11-03 |
| Etappe | R1.1 (Teil 2a: Datenmodell, Rechte, Ausblenden, Startbestand), R1.2 (Teil 2b: Zusammenführen; Teil 2c: Meldungen bearbeiten); Import (Krit. 1, 3, 6, Quellenteil von 2) erst mit Lizenz |
@ -30,13 +30,13 @@ Ein Import-Job (BGG oder eine andere Quelle) ist **nicht** Teil dieser Spec. Es
| E1 | **Ein Issue #2, drei PRs:** 2a (R1.1) Datenmodell, Rechte, Ausblenden, Startbestand-Werkzeug, Platzhalter-Cover; 2b (R1.2) Zusammenführen; 2c (R1.2) Meldungstabelle und Bearbeiten von Meldungen. **Alle drei PRs referenzieren nur `Refs #2`**, keiner schließt das Issue. Chris schließt #2 nach der Go-live-Abnahme des Startbestands (C1) und nach KAT-02 (#4). | Entspricht dem Etappenschnitt im Issue. Krit. 7 und 10 lassen sich erst mit den echten Daten bzw. der Detailseite abnehmen. 2a blockiert KAT-01 (#3), 2c muss vor der Umsetzung von KAT-04 (#13) gemergt sein. |
| E2 | **Größe neu bewertet: L statt XL.** 2a ≈ 2–3 Tage (davon der Großteil Migration und pgTAP), 2b ≈ 1–2 Tage, 2c ≈ 1 Tag. Das Einpflegen der ~200 Spiele durch Chris ist nicht eingerechnet. | Import, Edge Function und Admin-UI entfallen (Klärungen 24, 44). Das Label `size/xl` ändert nur Chris. |
| E3 | **Ausblenden über `games.hidden_at`, Zusammenführen über `games.merged_into_id`.** Die Lese-Policy von `games` bleibt unverändert. „Nicht mehr auffindbar“ (Krit. 4) wird über die neue View `catalog_games` umgesetzt, die ausgeblendete und zusammengeführte Einträge nicht enthält. Suche und Entdecken (KAT-01) lesen nur diese View. | Würde RLS ausgeblendete Spiele verbergen, liefen bestehende Sammlungs-, Favoriten- und Partie-Zeilen ins Leere. Entschieden (Spec-F2): Ausgeblendete Einträge bleiben per ID lesbar; ein Status „gesperrt“ kommt erst bei Bedarf. |
| E4 | **Manuell gepflegte Felder als Liste `games.manual_fields text[]`**, gepflegt von einem Trigger: Jedes Feld eines Katalogspiels, das ein Mensch (Studio/SQL) setzt oder ändert, landet automatisch in der Liste. Ausgenommen ist nur ein späterer Import, und nur wenn **beides** gilt: Er läuft als eigene Datenbankrolle `catalog_importer` **und** setzt in seiner Transaktion `set local shuffle.catalog_import = 'on'`. Zuordnungen zählen als Pseudo-Felder (`genres`, `mechanics`, `categories`, `publishers`). | Der Admin muss in Studio nichts zusätzlich pflegen, die Kennzeichnung kann nicht vergessen werden (Krit. 4). Die Bindung an eine eigene Rolle verhindert, dass ein versehentliches `set` im Studio die Kennzeichnung abschaltet. Entschieden (Spec-F4): Im Startbestand gelten alle gesetzten Felder als manuell gepflegt. |
| E4 | **Manuell gepflegte Felder als Liste `games.manual_fields text[]`**, gepflegt von einem Trigger: Jedes Feld eines Katalogspiels, das ein Mensch (Studio/SQL) setzt oder ändert, landet automatisch in der Liste. Ausgenommen ist nur ein späterer Import, und nur wenn **beides** gilt: Er läuft als eigene Datenbankrolle `catalog_importer` **und** setzt in seiner Transaktion `set local shuffle.catalog_import = 'on'`. Zuordnungen zählen als Pseudo-Felder (`genres`, `mechanics`, `categories`, `publishers`). | Der Admin muss in Studio nichts zusätzlich pflegen, die Kennzeichnung kann nicht vergessen werden (Krit. 4). Die Bindung an eine eigene Rolle verhindert, dass ein versehentliches `set` im Studio die Kennzeichnung abschaltet (W7, bestätigt). Entschieden (Spec-F4): Im Startbestand gelten alle gesetzten Felder als manuell gepflegt. |
| E5 | **Quellen als eigene Tabelle `catalog_sources`** (Schlüssel, Name, Quellenangabe, Anzeige-Erlaubnis für Bilder und Texte je Quelle) und je Spiel `catalog_source_id`, `source_ref` (Kennung in der Quelle), `source_url`, `source_synced_at`. Die Migration legt die Quelle `bgg` mit `images_allowed = false`, `texts_allowed = false` an. | Klärung 7 und Krit. 2, 6: Felder und Erlaubnis je Quelle ab R1 vorhanden. `games.bgg_id` bleibt unverändert der Abgleichsschlüssel für SAM-08 (auch bei privaten Spielen); `source_*` beschreibt, woher die Katalogdaten stammen. Die Doppelung bei BGG ist bewusst; die Konsistenz stellt der Import sicher. Der Fremdschlüssel heißt nach Konvention `<Zieltabelle>_id`. |
| E6 | **Anzeige-Erlaubnis je Quelle (Krit. 6): Daten ohne Erlaubnis werden gar nicht gespeichert.** Die View `catalog_games` blendet `description` und `image_path` zusätzlich aus, wenn die Quelle die Anzeige nicht erlaubt (zweite Sicherung). **Bitte bestätigen (§ 9.3, W1).** | Die View allein schützt nicht: `games` ist für `anon` tabellenweise lesbar, die Rohspalten wären über die API abrufbar. Spaltenweises `select` für `anon` würde auch eigene private Spiele und bestehende Abfragen treffen und wäre invasiv. Deshalb wird die Pflicht „nicht speichern“ der Import-Spec mitgegeben (9.5); im Startbestand gibt es ohnehin keine Quelle. |
| E6 | **Anzeige-Erlaubnis je Quelle (Krit. 6): Daten ohne Erlaubnis werden gar nicht gespeichert.** Die View `catalog_games` blendet `description` und `image_path` zusätzlich aus, wenn die Quelle die Anzeige nicht erlaubt (zweite Sicherung). Bestätigt (Chris, 2026-11-03, W1). | Die View allein schützt nicht: `games` ist für `anon` tabellenweise lesbar, die Rohspalten wären über die API abrufbar. Spaltenweises `select` für `anon` würde auch eigene private Spiele und bestehende Abfragen treffen und wäre invasiv. Deshalb wird die Pflicht „nicht speichern“ der Import-Spec mitgegeben (9.5); im Startbestand gibt es ohnehin keine Quelle. |
| E7 | **Verlage (Klärung 4) entstehen in dieser Spec** (`publishers`, `game_publishers`), nicht erst in KAT-01. Alternative Titel (Klärung 3, #3) und Basisspiel-Verweis (Klärung 5, #12) bleiben in ihren Stories; das Pflegeformat des Startbestands hat dafür aber schon Spalten (4.3), damit Chris die ~200 Spiele nicht zweimal anfassen muss. | Krit. 10 verlangt den Verlag im Startbestand. |
| E8 | **Sprache des Kurztexts als Spalte `games.description_locale`** (`de`/`en`, nur zusammen mit `description`), schon in 2a. | Der Startbestand füllt sie (immer `de`, Spec-F5); KAT-02 Krit. 7 (Klärung 48) zeigt damit den Sprachhinweis. |
| E9 | **Startbestand ist keine Migration und nicht Teil von `seed.sql`**, sondern eine erzeugte, idempotente SQL-Datei `supabase/catalog/startbestand.sql` mit festen IDs. Lokal und in CI lädt sie `supabase db reset` über `[db.seed] sql_paths`; auf Staging und Produktion spielt Chris sie nach den Migrationen per Studio-SQL-Editor oder `psql` ein. Entschieden (Spec-F6): Chris pflegt eine CSV, ein Skript prüft sie und erzeugt die SQL-Datei; nach dem Go-live ist die Datei eingefroren. | Migrationen sind unveränderlich und nur additiv, Katalogdaten werden bis zum Start aber laufend korrigiert. `seed.sql` ist ausdrücklich nur für die lokale Entwicklung. |
| E10 | **In R1 gibt es kein App-Konto mit Admin-Rolle.** Chris pflegt ausschließlich über Studio bzw. `psql` als Datenbankrolle `postgres` bzw. `supabase_admin` (umgeht RLS). Die bestehenden `is_admin()`-Policies (JWT `app_metadata.role = 'admin'`) bleiben für QUE-08 (R4) stehen. Die neuen Admin-Funktionen prüfen **nur** die Datenbankrolle (kein `is_admin()`-Pfad) und sind für `anon`, `authenticated` und `service_role` nicht ausführbar. | Klärung 44: Admin-Rolle nur als Datenbankrolle. Kein Admin-JWT und kein Funktionsaufruf über die API heißt weniger Angriffsfläche. QUE-08 (R4) ergänzt bei Bedarf einen eigenen Pfad. |
| E10 | **In R1 gibt es kein App-Konto mit Admin-Rolle.** Chris pflegt ausschließlich über Studio bzw. `psql` als Datenbankrolle `postgres` bzw. `supabase_admin` (umgeht RLS). Die bestehenden `is_admin()`-Policies (JWT `app_metadata.role = 'admin'`) bleiben für QUE-08 (R4) stehen. Die neuen Admin-Funktionen prüfen **nur** die Datenbankrolle (kein `is_admin()`-Pfad) und sind für `anon`, `authenticated` und `service_role` nicht ausführbar (H1/H2, bestätigt). | Klärung 44: Admin-Rolle nur als Datenbankrolle. Kein Admin-JWT und kein Funktionsaufruf über die API heißt weniger Angriffsfläche. QUE-08 (R4) ergänzt bei Bedarf einen eigenen Pfad. |
| E11 | **Umhängen, Zusammenführen und Meldungsergebnis laufen über SQL-Funktionen**, nicht über Hand-SQL: `admin_merge_games(source, target)` (zwei Katalogspiele) und `admin_resolve_report(report, status, target)` (vorläufiges privates Spiel → Katalogspiel), beide nutzen die interne Funktion `repoint_game_references(from, to)`. **Ob Varianten kopiert werden, ergibt sich aus der Sichtbarkeit:** nur wenn Quelle und Ziel Katalogspiele sind. Beim Umhängen eines privaten Eintrags wird nie kopiert (Entscheidung Chris, RB1); die Edition wird dort auf eine gleichnamige Variante des Ziels abgebildet, sonst geleert (Klärung N15). | Zusammenführen ist kein einzelnes UPDATE: Varianten mit zusammengesetzten Fremdschlüsseln, `unique (owner_id, game_id)` in `collection_items`, `unique (profile_id, game_id)` in `favorite_games` und künftig ein Wertungsbogen je Spiel (Klärung 39). Private Varianten eines Nutzers dürfen nicht in den öffentlichen Katalog wandern. |
| E12 | **Die Meldungstabelle `game_reports` entsteht in 2c (diese Spec), vollständig mit den Spalten, die KAT-04 und SAM-08 brauchen** (Herkunft Nutzer/Import, BGG-Kennung, vorläufiger Eintrag). Anlegen durch Nutzer (Insert-Policy, Tageslimit 10, Ähnlichkeitsprüfung) liefert #13. | #13 hängt in Forgejo von #2 ab. Läge die Tabelle in #13, wäre 2c von #13 blockiert. Die Spalten ergeben sich aus den Klärungen 6, 26, 27, N12 und N12a. |
| E13 | **Keine neuen Pakete, keine Edge Function, keine Supabase-Cloud-Funktionen.** Alles ist reines PostgreSQL 17 (Trigger, Views mit `security_invoker`, Spaltenrechte, Custom-GUC), PostgREST und Studio, die es im Self-Hosting gibt (ADR-0002). `[db.seed] sql_paths` ist eine Funktion der lokalen CLI. | Vorgabe aus CLAUDE.md und ADR-0002. |
@ -259,7 +259,7 @@ Rückgabe: `jsonb` mit Zählern je Tabelle, z. B. `{"collection_items": 3, "coll
Ablauf:
1. `repoint_game_references(p_source, p_target)` (beide Katalog → Varianten werden kopiert).
2. **BGG-Kennung und Quelle übernehmen** (W6, **bitte bestätigen**): Hat das Ziel kein `bgg_id`, aber die Quelle, wird der Wert übertragen; ebenso `catalog_source_id`/`source_ref`/`source_url`/`source_synced_at` als Gruppe. Reihenfolge wegen der eindeutigen Indizes: Werte merken → an der Quelle leeren → am Ziel setzen. Haben beide unterschiedliche Werte, bleibt das Ziel unverändert, an der Quelle werden die Werte trotzdem geleert, und das Ergebnis-JSON meldet den Konflikt **mit den alten Werten**, z. B. `"bgg_id_conflict": {"source": 13, "target": 42}` bzw. `"source_conflict": {"source": {"catalog_source_id": …, "source_ref": …, "source_url": …}, "target": {…}}`, damit der Admin den geleerten Wert nicht verliert. Begründung: Sonst würde der BGG-Abgleich (SAM-08) später den ausgeblendeten Quell-Eintrag treffen. `bgg_id` und `source_*` stehen nicht in der `manual_fields`-Schlüsselliste.
2. **BGG-Kennung und Quelle übernehmen** (W6, bestätigt von Chris am 2026-11-03): Hat das Ziel kein `bgg_id`, aber die Quelle, wird der Wert übertragen; ebenso `catalog_source_id`/`source_ref`/`source_url`/`source_synced_at` als Gruppe. Reihenfolge wegen der eindeutigen Indizes: Werte merken → an der Quelle leeren → am Ziel setzen. Haben beide unterschiedliche Werte, bleibt das Ziel unverändert, an der Quelle werden die Werte trotzdem geleert, und das Ergebnis-JSON meldet den Konflikt **mit den alten Werten**, z. B. `"bgg_id_conflict": {"source": 13, "target": 42}` bzw. `"source_conflict": {"source": {"catalog_source_id": …, "source_ref": …, "source_url": …}, "target": {…}}`, damit der Admin den geleerten Wert nicht verliert. Begründung: Sonst würde der BGG-Abgleich (SAM-08) später den ausgeblendeten Quell-Eintrag treffen. `bgg_id` und `source_*` stehen nicht in der `manual_fields`-Schlüsselliste.
3. `p_source.merged_into_id := p_target`, `hidden_at := coalesce(hidden_at, now())`; Ketten glätten (`update games set merged_into_id = p_target where merged_into_id = p_source`).
4. Ab 2c zusätzlich: `game_reports.target_game_id` von `p_source` auf `p_target`.
@ -613,22 +613,22 @@ Weitere Entscheidungen von Chris aus den Reviews zu PR #51 (2026-11-03):
| W-a | Privaten Eintrag öffentlich schalten | **Trigger plus Runbook-Regel:** Der Trigger `games_visibility_immutable` lehnt jede Änderung von `visibility` und `owner_id` ab (auch als `postgres`), das Runbook verbietet es ausdrücklich. | E14, 3.4, 4.4, 7.1 Krit. 9, B8, B9, B10 |
| N15 | KAT-04 Krit. 4 (Edition) | Abweichung als neue Klärung N15 in `requirements/r1-klaerungen.md` festgehalten. | § 2, 3.3, R10, 9.5 |
### 9.3 Designwahlen aus dem Review – bitte einzeln bestätigen
### 9.3 Designwahlen aus dem Review – bestätigt (Chris, 2026-11-03)
Chris bestätigt diese Punkte einzeln; bis dahin bleiben sie unverändert.
Chris hat jede Wahl einzeln und ohne Änderung bestätigt (Kommentar im Issue #2 vom 2026-11-03).
| # | Wahl | Begründung | Alternative |
|---|---|---|---|
| W1 | Krit. 6: Daten ohne Anzeige-Erlaubnis werden gar nicht gespeichert (Pflicht für die Import-Spec); die View ist nur zweite Sicherung (E6). | Am wenigsten invasiv: keine Änderung an den Leserechten von `games`, keine Auswirkung auf private Spiele und bestehende Abfragen. | Spaltenweises `select` für `anon` auf `games` |
| W6 | `admin_merge_games` überträgt `bgg_id` und `source_*` auf das Ziel, wenn es keine hat, leert sie an der Quelle und meldet Konflikte mit den alten Werten (3.3). | Sonst träfe der BGG-Abgleich (SAM-08) den ausgeblendeten Quell-Eintrag; die Unique-Indizes bleiben gewahrt. | Nichts übertragen, SAM-08 muss ausgeblendete Treffer selbst auflösen |
| W7 | Import-Ausnahme beim Kennzeichnen nur für die Rolle `catalog_importer` **und** `set local` (E4). | Ein versehentliches `set` im Studio schaltet die Kennzeichnung nicht ab. | nur die Sitzungseinstellung |
| W4 | Verlage ohne abgeleitete ID, Konfliktziel `lower(name)`; Schlüssel unveränderlich, Entfernen über `hidden`; UUID-v5-Namespace `6f1d9a52-3c4e-4b8a-9f27-5d0e8c71a3b4` (4.3). | Studio-Verlage und Startbestand vertragen sich; keine verwaisten Spiele. | feste Verlags-IDs aus dem Skript |
| H1/H2 | Admin-Funktionen ohne `is_admin()`-Pfad, `execute` auch `service_role` entzogen (E10). | Klärung 44; in R1 gibt es keinen API-Aufrufer. | `is_admin()` als R4-Vorbereitung belassen |
| H7 | `merged_into_id`, `target_game_id`, `catalog_source_id`: `on delete restrict`; `merged_into_id` zeigt per Trigger nur auf Katalogspiele (3.2). | Katalogspiele und Quellen werden ausgeblendet, nicht gelöscht; versehentliches Löschen fällt sofort auf; das Konto-Löschen bleibt möglich, weil private Spiele nie Ziel eines Verweises sind. | `on delete set null` |
| # | Status | Wahl | Begründung | Verworfene Alternative |
|---|---|---|---|---|
| W1 | bestätigt (Chris, 2026-11-03) | Krit. 6: Daten ohne Anzeige-Erlaubnis werden gar nicht gespeichert (Pflicht für die Import-Spec); die View ist nur zweite Sicherung (E6). | Am wenigsten invasiv: keine Änderung an den Leserechten von `games`, keine Auswirkung auf private Spiele und bestehende Abfragen. | Spaltenweises `select` für `anon` auf `games` |
| W6 | bestätigt (Chris, 2026-11-03) | `admin_merge_games` überträgt `bgg_id` und `source_*` auf das Ziel, wenn es keine hat, leert sie an der Quelle und meldet Konflikte mit den alten Werten (3.3). | Sonst träfe der BGG-Abgleich (SAM-08) den ausgeblendeten Quell-Eintrag; die Unique-Indizes bleiben gewahrt. | Nichts übertragen, SAM-08 muss ausgeblendete Treffer selbst auflösen |
| W7 | bestätigt (Chris, 2026-11-03) | Import-Ausnahme beim Kennzeichnen nur für die Rolle `catalog_importer` **und** `set local` (E4). | Ein versehentliches `set` im Studio schaltet die Kennzeichnung nicht ab. | nur die Sitzungseinstellung |
| W4 | bestätigt (Chris, 2026-11-03) | Verlage ohne abgeleitete ID, Konfliktziel `lower(name)`; Schlüssel unveränderlich, Entfernen über `hidden`; UUID-v5-Namespace `6f1d9a52-3c4e-4b8a-9f27-5d0e8c71a3b4` (4.3). | Studio-Verlage und Startbestand vertragen sich; keine verwaisten Spiele. | feste Verlags-IDs aus dem Skript |
| H1/H2 | bestätigt (Chris, 2026-11-03) | Admin-Funktionen ohne `is_admin()`-Pfad, `execute` auch `service_role` entzogen (E10). | Klärung 44; in R1 gibt es keinen API-Aufrufer. | `is_admin()` als R4-Vorbereitung belassen |
| H7 | bestätigt (Chris, 2026-11-03) | `merged_into_id`, `target_game_id`, `catalog_source_id`: `on delete restrict`; `merged_into_id` zeigt per Trigger nur auf Katalogspiele (3.2). | Katalogspiele und Quellen werden ausgeblendet, nicht gelöscht; versehentliches Löschen fällt sofort auf; das Konto-Löschen bleibt möglich, weil private Spiele nie Ziel eines Verweises sind. | `on delete set null` |
### 9.4 Ausstehende Prozessschritte
- Spec freigegeben am 2026-11-03, Spec-F1–Spec-F9 sowie Review und Re-Review PR #51 eingearbeitet. Chris hält die Freigabe und die Einzelbestätigungen aus 9.3 im Issue #2 fest (Koordination über den Orchestrator).
- Spec freigegeben am 2026-11-03; Spec-F1–Spec-F9, Review und Re-Review PR #51 eingearbeitet; Designwahlen aus 9.3 einzeln bestätigt. Freigabe und Bestätigungen stehen als Kommentare im Issue #2.
- Nächste Schritte nach Merge der Spec: B1 (backend-dev), parallel U1 (ui-designer); B3a liefert den Vokabular-Vorschlag zur Freigabe durch Chris (C0).
- Label `size/xl` → `size/l` (E2) ändert nur Chris.
- Spec #13 (KAT-04) baut auf 3.4 auf (siehe 9.5).
@ -662,11 +662,11 @@ Chris hat die Spec freigegeben und alle Fragen wie empfohlen entschieden. Eingea
- **RB1 (Chris):** Beim Umhängen eines privaten Eintrags werden keine Varianten kopiert; Abbildung auf gleichnamige Zielvariante, sonst `null`; Spec-F1-Füllregel über `variant_map`; Auswirkung auf KAT-04 Krit. 4 (R10, 9.5).
- **W3 (Chris):** Alle PRs `Refs #2`; Abschluss von #2 nach C1 und #4 (E1, § 2, § 5, 8).
- **W1:** E6 eingeschränkt, Pflicht „nicht speichern“ für die Import-Spec (bitte bestätigen).
- **W1:** E6 eingeschränkt, Pflicht „nicht speichern“ für die Import-Spec.
- **W2:** Grant-Tabelle je Objekt in 3.2.
- **W4:** Idempotenz von `startbestand.sql`, unveränderliche Schlüssel mit Schlüsselliste, Entfernen über `hidden`, UUID-v5-Namespace.
- **W5:** `checks.sql` erzeugt, mit eingebetteten Startbestands-IDs, alle SB2-Felder, SB6 inkl. `bgg_id`, Beispielspiel ausgenommen.
- **W6:** Übertragung von `bgg_id`/`source_*` beim Zusammenführen (bitte bestätigen).
- **W6:** Übertragung von `bgg_id`/`source_*` beim Zusammenführen.
- **W7:** Begründung zum Import-Schalter korrigiert; Ausnahme an Rolle `catalog_importer` gebunden; Runbook „nie `set` ohne `local`“.
- **H1–H14:** `is_admin()`-Pfad aus den Funktionen gestrichen, `service_role` entzogen; Spaltenrechte ohne `id`; Trigger auch für Update, R8 ergänzt; Zuordnung der Kriterien zu PRs; Fremdschlüssel-Wächter mit `game_reports`; `on delete restrict`; Umbenennung in SB1–SB8 und Spec-F1–Spec-F9; Abhängigkeit #4; Pooler und `/pg/`; CLI-Annahmen als unverifiziert markiert (R11); KI-Entwürfe nur aus Fakten (SB4); Kleinigkeiten (H14).
@ -681,4 +681,7 @@ Chris hat die Spec freigegeben und alle Fragen wie empfohlen entschieden. Eingea
- 2c: `revoke` für `admin_merge_games` nach `create or replace` ausdrücklich wiederholt, Prüfung in `08`; Konflikt-JSON mit alten Werten.
- **H7:** Trigger `games_merged_into_catalog` (2a) begründet; Test in `08`: Konto des Meldenden nach Freigabe löschen gelingt, Katalogspiel und Daten anderer Nutzer bleiben.
- Verweise auf die Hinweisliste auf 9.5 korrigiert; Review-Blocker B1 in RB1 umbenannt; Hinweis zu `security definer` und `current_user` für die Import-Spec (9.5).
- Designwahlen in 9.3 unverändert, warten auf Einzelbestätigung durch Chris.
### 2026-11-03 – Bestätigung der Designwahlen
Chris hat W1, W4, W6, W7, H1/H2 und H7 (§ 9.3) einzeln und ohne Änderung bestätigt (Kommentar im Issue #2). § 9.3 trägt jetzt je Zeile den Status „bestätigt (Chris, 2026-11-03)“; die Hinweise „bitte bestätigen“ in Status, E6 und 3.3 sind entfernt; 9.4 entsprechend angepasst.