Skip to content

docs(ops): showcase backup with a read-only backup role and a rehearsed restore (#108) - #125

Open
Fluory wants to merge 2 commits into
mainfrom
claude/ops-db-backup-restore-108
Open

Fluory wants to merge 2 commits into
mainfrom
claude/ops-db-backup-restore-108

Conversation

@Fluory

@Fluory Fluory commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Warum

Refs #108 (nicht „Fixes“: die Probe gegen den Showcase selbst und das Anlegen der Backup-Rolle sind Kontoaktionen des Orchestrators) · Lücke aus dem Add-on-Audit #65 (Datenbank-Checkliste #12 „Backup eingerichtet und Restore einmal real geprobt“). Start: Orchestrator „mach weiter“ am 2026-10-16.

Arbeitsstand

  • Ziel: Festgelegt und geprobt, wie die Showcase-Datenbank gesichert und in eine getrennte Datenbank wiederhergestellt wird – mit Mandantentrennung danach.
  • Nicht-Ziele: PITR für Produktion, Backup des lokalen Docker-Volumes (laut Issue); ein Backup-Skript oder Workflow im Repo (Sicherungen enthalten personenbezogene Daten und gehören nicht in CI).
  • Erledigt: Abschnitt „Backup and restore (showcase)“ in operations.md; lokale Restore-Probe mit echtem Migrationslauf, Queues, zwei Firmen, RLS-Guard.
  • Offen (Orchestrator): Rolle backup_reader auf dem Showcase anlegen (SQL steht in der Doku), erste Sicherung, eine Probe gegen den Showcase in ein getrenntes Projekt; klären, ob der Showcase Free oder Pro ist.
  • Annahmen: Supabase-Angaben laut Doku vom 2026-10-16 (Free: kein automatisches Backup, Pro: 7 Tage; Storage nie enthalten).
  • Nächster kleinster Schritt: Review (inkl. Freigabe der RLS-Ausnahme für backup_reader) und Merge durch den Orchestrator; danach Kontoaktion backup_reader + erste Sicherung.

Was ist passiert (Klartext)

Für die Showcase-Datenbank gab es bisher keine Sicherung und nie eine Probe, ob man sie zurückspielen kann. Jetzt ist festgelegt: wöchentlich und vor jeder Datenbankänderung eine Sicherung, die letzten vier bleiben, verschlüsselt beim Orchestrator – nie im Repository, weil darin auch Namen und E-Mail-Adressen der Konten stehen. Dabei kam heraus: Mit der normalen Eigentümer-Rolle der App geht das gar nicht, weil die Mandantentrennung auch für sie gilt – das ist gewollt. Deshalb braucht es eine eigene, nur lesende Sicherungsrolle, wie Supabase sie empfiehlt. Die hochgeladenen Dokumente selbst werden nicht gesichert; nach einer Wiederherstellung fehlen sie, die Musterfälle lassen sich neu anlegen. Die Wiederherstellung ist lokal einmal vollständig durchgespielt: alle Daten, der Stand der Datenbankänderungen und die Mandantentrennung stimmen danach.

Plan-Pflicht (SYSTEM.md §4)

  • Kein Auslöser – nur Doku; keine Modulgrenze, API, Migration, Auth-Änderung im Code, kein neuer Infrapfad im Repo, umkehrbar
  • Auslöser zutreffend – Impact Manifest ausgefüllt (Plan vor Code)

Hinweis: Die vorgeschlagene Rolle backup_reader umgeht RLS (nur lesend) – eine Sicherheitsentscheidung für den Orchestrator; nichts davon ist im Repo automatisiert.

Geändert

  • docs/technical/operations.md: neuer Abschnitt „Backup and restore (showcase)“ (vor „Frequent failures“)
  • docs/technical/architecture.md: backup_reader in der Rollentabelle und im Ausnahmeregister

Nachweis (SYSTEM.md §11)

  • Restore-Probe 2026-10-16, lokal (PostgreSQL 16, nicht der Showcase): Quelle mit runMigrations (20 Migrationen) und installJobQueues, synthetisch zwei Firmen (11 Anfragen, 3 Nutzer, 1 wartender Job) → pg_dump -Fc -n app -n identity -n drizzle -n pgboss → pg_restore --exit-on-error in eine leere Datenbank → Migrations- und Queue-Schritte von setup:deploy erneut (Migrationen bleiben 20). Vergleich Quelle/Ziel identisch: Migrationen 20/20, Anfragen 11/11, Nutzer 3/3, Jobs 1/1, Tabellen nicht im Besitz von app_owner 0/0, RLS-Guard (TABLE_SECURITY_QUERY + tenantIsolationViolations) ohne Verstoß, app_owner/app_rw weder Superuser noch BYPASSRLS, app_rw ohne/mit Firmenkontext 0/7. Dauer ~1 s, 96 KB.
  • pg_dump als app_owner: schlägt fehl („query would be affected by row-level security policy for table audit_events“) – erwartet.
  • pg_dump als backup_reader (LOGIN BYPASSRLS, pg_read_all_data, default_transaction_read_only): 27/27 Datenabschnitte wie beim postgres-Dump; Schreiben abgewiesen („cannot execute INSERT in a read-only transaction“); Restore daraus vollständig.
  • scripts/doku-check.sh: grün (0 WARN); scripts/pr-check.sh lokal gegen diese Beschreibung: grün
  • Restore als Nicht-Superuser (wie postgres auf Supabase): ohne Mitgliedschaft in app_owner/app_rw bricht pg_restore bei ALTER SCHEMA app OWNER TO app_owner ab („must be able to SET ROLE“); mit Mitgliedschaft (ohne BYPASSRLS) vollständig – 20 Migrationen, 11/3/1 Zeilen, Besitzer korrekt, RLS-Guard ohne Verstoß, app_rw 0/7 → neuer Schritt 2 in der Doku
  • Frischer Review (unabhängiger Subagent): 3 wichtige Punkte + 3 Nits – alle behoben in b041322; Details im PR-Kommentar
  • verify: grün – CI check Run 38879467954 am PR-Kopf b041322 (nur Doku: Doku- und PR-Guard; kein Code geändert)
  • Probe gegen den Showcase: offen (Kontoaktion)

Doku-Entscheidung (genau eine)

  • Keine langlebige Doku betroffen – Begründung: –
  • Doku betroffen und im selben PR aktualisiert:
    • Produktdoku: –
    • Technische Doku: operations.md (Backup and restore), architecture.md (Rollen, Ausnahmeregister)
    • Architekturkarte: –
    • ADR: –
    • CHANGELOG [Unreleased]: kein Produktverhalten geändert

Entferntes oder Umbenanntes: nichts

Dateigrößen und neue Bausteine (SYSTEM.md §7)

Dateien über 500 Zeilen im Diff (Ausnahmen: generierter Code, Lockfiles, Fixtures, Migrationen, Schemas, Ressourcen, Doku, Konfiguration):

  • keine
  • bewusst belassen – Begründung: –
  • im selben PR nach fachlicher Verantwortung geteilt
  • Folge-Issue –

Über 800 Zeilen mit neuer Fachlogik oder über 1000 Zeilen (P1/P2): nicht betroffen

Neue Shared-Komponente, Utility-Datei, Adapter oder fachlicher Service:

  • nein
  • ja – gesucht nach: –

Subagent-Einsätze

  • 1 Review-Subagent (nur lesend, frischer Kontext): Abgleich mit Bootstrap-Skript, Setup, Migrationen und ADR; Ergebnis im PR-Kommentar

Risiken / offene Punkte

  • backup_reader kann alle Daten aller Firmen lesen – Passwort so geschützt wie das postgres-Passwort, Rolle für nichts anderes benutzen.
  • Lokal mit PostgreSQL 16 geprobt, der Showcase läuft 17; pg_dump/pg_restore müssen mindestens die Server-Version haben.
  • Die Rollen existierten in der Probe bereits im Cluster; Schritt 1 (Bootstrap-Skript auf einem leeren Ziel) ist noch nicht geprobt.
  • Das Issue nennt „pg_dump als app_owner“ – unter erzwungener RLS unmöglich; die Ersatzlösung ist eine bewusste Ausnahme von D7 und braucht die Freigabe des Orchestrators.

🤖 Generated with Claude Code

https://claude.ai/code/session_0117LRiQKt8ycJEHW7vxLWG3

…ed restore (#108)

operations.md gets "Backup and restore (showcase)": what Supabase covers per
plan, a dedicated read-only BYPASSRLS export role (app_owner cannot dump under
forced RLS, by design), schedule, retention, encrypted storage outside the
repo, the documents bucket as not backed up with its consequence, the restore
steps into a separate database, and a local rehearsal protocol (counts,
migration state, RLS guard, roles) – the showcase rehearsal stays open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117LRiQKt8ycJEHW7vxLWG3
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
requestflow Ready Ready Preview Oct 16, 2026 9:18am UTC

…ole table (#125 review)

Restore on Supabase needs postgres to be a member of app_owner/app_rw
(tested: without it pg_restore stops at OWNER TO; with it the restore is
complete and passes the RLS guard). setup:deploy must point at the target's
database and storage. backup_reader is listed in the role table and the
exceptions register; read-only rests on missing write grants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117LRiQKt8ycJEHW7vxLWG3

Fluory commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

Fresh review (independent subagent, read-only) – result and how it was handled

Verdict: changes needed (no single blocker, three important points, three nits). Checked as correct: exports of the RLS guard, seed:samples/setup:deploy, ADR references, no extensions needed, default privileges are IN SCHEMA (kept by a -n dump), the bootstrap restores statement_timeout and GRANT CREATE ON DATABASE, bootstrap-before-restore order.

  • Important – backup_reader missing from the role table / exceptions register → fixed in b041322: row in architecture.md → Database roles and an entry in the exceptions register (the one deliberate RLS bypass, review at M3).
  • Important – restore as a non-superuser postgres (Supabase) → tested locally with a non-superuser restoring role: without membership pg_restore stops at ALTER SCHEMA app OWNER TO app_owner ("must be able to SET ROLE"); with GRANT app_owner, app_rw TO … (and without BYPASSRLS) the restore is complete – 20 migrations, 11/3/1 rows, owners correct, RLS guard clean, app_rw 0/7. New step 2 (grant, revoke afterwards).
  • Important – setup:deploy could hit the showcase → step 4 now says MIGRATION_DATABASE_URL and S3_* must point at the target.
  • Nit – read-only rests on missing write grants, not on default_transaction_read_only → stated.
  • Nit – link to Supabase's recommendation → added.
  • Nit – bootstrap on an empty target not exercised → named under "Still open".

Needs the orchestrator: the issue's criterion says "pg_dump as app_owner"; that cannot work under forced RLS, so this PR replaces it with a read-only BYPASSRLS role – a deliberate exception to D7 that only you can approve.


Generated by Claude Code

This branch was successfully deployed

1 active deployment
Preview – requestflow — b041322f Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants