Skip to content

ACC-71: Ownergebundenes PostgreSQL-Schema und FinanceDataV1-Reader - #21

Merged
hernstev97 merged 1 commit into
developfrom
codex/acc-71-postgres-schema-reader
Aug 14, 2026
Merged

hernstev97 merged 1 commit into
developfrom
codex/acc-71-postgres-schema-reader

Conversation

@hernstev97

@hernstev97 hernstev97 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Zusammenfassung

Diese PR implementiert ACC-71 und schafft den vollständigen PostgreSQL-Lesepfad für FinanceDataV1, ohne den produktiven Datenfluss bereits von Google Sheets auf PostgreSQL umzustellen.

Enthalten sind:

  • ein portables, ownergebundenes PostgreSQL-Schema für alle Quellenfelder aus FinanceDataV1;
  • ein gemeinsamer Lazy-Pool für die bestehenden und neuen PostgreSQL-Repositories;
  • ein Finance-Repository, das über die verifizierte Google-Subjekt-ID einen vollständigen FinanceDataV1-Stand rekonstruiert;
  • Laufzeit- und Integritätsvalidierung des gespeicherten Datenstands;
  • echte Integrationstests gegen PostgreSQL 17;
  • ein separater PostgreSQL-CI-Job;
  • aktualisierte Architektur-, Datenbank-, Betriebs- und Testdokumentation.

Der bisherige Sheets-Pfad bleibt vollständig produktiv. Diese PR importiert keine bestehenden Daten und verändert /api/finance nicht.

Motivation

Google Sheets ist aktuell gleichzeitig Bearbeitungsoberfläche und produktive Finanzquelle. ACC-71 bereitet den kontrollierten Wechsel zu PostgreSQL vor, ohne dabei den bewährten Domänenvertrag oder die bestehende Finanzlogik zu verändern.

Die Persistenz soll dabei:

  • Finance-Daten eindeutig einem internen Owner zuordnen;
  • ownerübergreifende Beziehungen technisch verhindern;
  • alle v1-Quellenwerte und historischen Snapshots verlustfrei erhalten;
  • PostgreSQL statt Neon-spezifischer Funktionen als portablen Vertrag verwenden;
  • keine Berechnungen oder Snapshot-Auswahl in SQL verschieben;
  • weiterhin exakt FinanceDataV1 an Selektoren und View-Models liefern.

Datenbankschema

Die neue Migration 002_finance_data_v1.sql wird nach 001_google_connections.sql ausgeführt und läuft vollständig in einer Transaktion.

Sie legt folgende Tabellen an:

  • owners
  • finance_meta
  • accounts
  • account_snapshots
  • pockets
  • pocket_snapshots
  • budget_items
  • debts
  • debt_snapshots
  • debt_milestones
  • relief_milestones

Owner-Modell

owners verwendet eine interne UUID und ordnet sie eindeutig einem Google sub zu.

Zwischen owners und google_connections besteht bewusst kein Foreign Key. Dadurch kann ein späterer Google-Disconnect nicht versehentlich Finanzdaten entfernen.

Jede Finance-Tabelle besitzt ein nicht-nullbares owner_id. Fachliche Primär- und Fremdschlüssel enthalten den Owner, beispielsweise:

  • Account: (owner_id, id)
  • Account-Snapshot: (owner_id, account_id, as_of)
  • Pocket: (owner_id, id)
  • Pocket → Account: (owner_id, account_id)
  • Debt-Snapshot: (owner_id, debt_id, as_of)

Damit können unterschiedliche Owner dieselben fachlichen IDs verwenden, während ownerübergreifende Referenzen von PostgreSQL abgewiesen werden.

Es wurden keine automatischen Lösch-Cascades zwischen Finance-Entitäten eingeführt.

Constraints und Typen

Das Schema erzwingt unter anderem:

  • Geldwerte als BIGINT in Cents;
  • Beschränkung aller JavaScript-relevanten Integer auf den sicheren Zahlenbereich;
  • nicht negative und sichere Ratenanzahlen;
  • lowercase-kebab-case für fachliche IDs;
  • nicht leere Namen, Labels und Pflichttexte;
  • NULL oder nicht leere optionale Notizen;
  • salary_day und due_day zwischen 1 und 31 oder NULL;
  • exakt Schema-Version 1;
  • exakt Währung EUR;
  • Text-Enums mit CHECK-Constraints;
  • zusammengesetzte Owner-Fremdschlüssel;
  • eindeutige Snapshot- und Meilensteinschlüssel.

Negative Finanzbeträge bleiben erlaubt, da der bestehende v1-Vertrag sie zulässt.

Abgeleitete Werte wie aktuelle Summen, safeToSpend, Budgetstatus oder ausgewählte Snapshots werden nicht gespeichert.

Meilensteinpräzision

Debt- und Relief-Meilensteine speichern:

  • milestone_date DATE
  • date_precision als month oder day

Bei Monatspräzision muss der gespeicherte Tag der Monatserste sein. Der Reader rekonstruiert daraus wieder exakt:

  • YYYY-MM für Monatspräzision;
  • YYYY-MM-DD für Tagespräzision.

Relief-Meilensteine verwenden eine interne UUID, weil der v1-Vertrag dort keine eindeutige fachliche ID verlangt und doppelte Ereignisse erlaubt.

Gemeinsamer PostgreSQL-Zugang

Mit api/_lib/database.ts gibt es jetzt einen zentralen, lazy erzeugten PostgreSQL-Pool je DATABASE_URL.

Die bestehende Konfiguration bleibt erhalten:

  • max: 1
  • idle_timeout: 20
  • connect_timeout: 10
  • prepare: false

Das bestehende Google-Connection-Repository und das neue Finance-Repository verwenden denselben Pool. Dadurch entstehen in einer Vercel-Function-Instanz keine unabhängigen Pools für dieselbe Datenbankverbindung.

Finance-Repository

Das neue Repository implementiert folgenden Vertrag:

interface FinanceRepository {
  readForGoogleSub(googleSub: string): Promise<FinanceDataV1 | null>;
}

Identitäts- und Sicherheitsgrenze

Das Repository nimmt ausschließlich Google sub entgegen. Die Owner-UUID bleibt intern und wird nicht vom Browser geliefert.

Der Ablauf ist:

  1. Google sub zu owners.id auflösen.
  2. Alle weiteren Abfragen ausschließlich mit dieser Owner-ID ausführen.
  3. Keine Owner-ID oder Persistenzmetadaten in FinanceDataV1 zurückgeben.

Fehlt der Owner oder besitzt er kein finance_meta, liefert das Repository null. Es wird kein künstlicher leerer Finanzstand erzeugt.

Konsistenter Read

Der vollständige Lesevorgang läuft in einer:

READ ONLY
REPEATABLE READ

Transaktion.

Dadurch stammen Meta-Daten, Entitäten, Snapshots und Meilensteine aus demselben konsistenten Datenbankstand.

Das Repository liest sämtliche gespeicherten Snapshots – einschließlich alter und zukünftiger Einträge. Die fachliche Auswahl des gültigen Snapshots bleibt weiterhin Aufgabe der bestehenden Selektoren.

Deterministische Reihenfolge

Die Ausgabe wird stabil sortiert:

  • Accounts, Pockets, Budgetpositionen und Debts nach display_order und ID;
  • Snapshots nach Elternreihenfolge beziehungsweise Eltern-ID und Datum;
  • Debt-Meilensteine nach Datum, Debt-Reihenfolge und Präzision;
  • Relief-Meilensteine nach Datum, Ereignis, Detail und interner UUID.

Die Reihenfolge ist deterministisch, bildet aber bewusst keine ursprüngliche Sheet-Zeilenreihenfolge nach.

Sichere Typabbildung

PostgreSQL-BIGINT-Werte werden explizit aus ihrer Stringdarstellung gelesen.

Die Mappingfunktion:

  1. akzeptiert nur eine gültige Integerdarstellung;
  2. konvertiert sie zu number;
  3. prüft Number.isSafeInteger;
  4. erzeugt andernfalls einen internen Integritätsfehler.

DATE-Werte werden bereits in SQL als ISO-Text formatiert. Dadurch laufen reine Kalendertage nicht durch lokale JavaScript-Zeitzonen.

Abschlussvalidierung

Das rekonstruierte Objekt wird vollständig mit financeDataV1Schema validiert.

Zusätzlich prüft das Repository, dass jedes aktive Konto, Pocket und jede aktive Schuld mindestens einen Snapshot mit Datum kleiner oder gleich finance_meta.as_of besitzt. Dies entspricht der bestehenden Parserregel, ohne einen komplexen SQL-Trigger einzuführen.

Integritätsfehler verwenden einen eigenen internen Fehlertyp und enthalten keine:

  • vollständigen Datenbankzeilen;
  • Entity-IDs;
  • Finanzwerte;
  • internen Datenbankdetails.

Eine neue HTTP-Fehlerabbildung wurde bewusst nicht ergänzt, da der produktive Endpoint in ACC-71 unverändert bleibt.

PostgreSQL-Integrationstests

Die neue dedizierte Suite läuft gegen eine echte PostgreSQL-Instanz und verwendet keine SQL-Mocks.

Pro Lauf wird ein isoliertes, zufällig benanntes Testschema angelegt. Die Suite führt Migration 001 und anschließend Migration 002 aus und entfernt das Schema nach dem Test wieder.

Abgedeckt sind:

  1. erfolgreiche Ausführung beider Migrationen;
  2. nicht-nullbare owner_id-Spalten in allen Finance-Tabellen;
  3. vollständiges Einfügen des normalisierten anonymen Fixtures;
  4. Rekonstruktion eines laufzeitgültigen FinanceDataV1;
  5. Erhalt von Cents, negativen Beträgen, NULL, Aktivstatus und historischen Snapshots;
  6. Unterscheidung von Monats- und Tagesmeilensteinen;
  7. null bei fehlendem Owner oder fehlendem finance_meta;
  8. gleiche fachliche IDs bei zwei Ownern;
  9. vollständige Isolation der Owner-Daten;
  10. Ablehnung ownerübergreifender Foreign Keys;
  11. Ablehnung doppelter fachlicher Schlüssel;
  12. Ablehnung ungültiger Fälligkeitstage, Enums, Leertexte und unsicherer Integer;
  13. vollständige Rückgabe alter und zukünftiger Snapshots;
  14. weiterhin fachliche Snapshot-Auswahl durch die bestehenden Selektoren;
  15. sanitierter Integritätsfehler bei aktiven Entitäten ohne gültigen Snapshot.

npm test bleibt datenbankunabhängig. Die PostgreSQL-Suite wird separat gestartet:

npm run test:postgres

Ohne POSTGRES_TEST_URL bricht sie absichtlich ab und wird nicht still übersprungen.

CI

Die GitHub-CI enthält einen neuen separaten Job mit PostgreSQL 17 als temporärem Service.

Der Job:

  • startet eine synthetische Testdatenbank;
  • wartet über pg_isready auf deren Bereitschaft;
  • installiert die bestehenden Abhängigkeiten;
  • führt npm run test:postgres aus.

Normale Unit-, Build-, Lint- und Browser-Jobs bleiben unverändert getrennt.

Betriebs- und Neon-Dokumentation

Die Dokumentation unterscheidet jetzt ausdrücklich:

  • gepoolte Neon-URL als Runtime-DATABASE_URL;
  • direkten Neon-Endpoint für Migrationen, Rollenverwaltung und Restore;
  • administrative Credentials gegenüber eingeschränkten Runtime-Credentials;
  • Development und Production als getrennte Datenbanken oder Branches;
  • synthetische Preview- und Testdaten gegenüber Produktionsdaten.

Für die spätere Runtime-Rolle ist dokumentiert:

  • bestehende notwendige Rechte auf google_connections;
  • nur SELECT auf Owner- und Finance-Tabellen für ACC-71;
  • keine DDL- oder Rollenverwaltung;
  • keine pauschalen Finance-Schreibrechte vor dem Editor.

Vor ACC-66 bleiben folgende Betriebsaufgaben ausdrücklich offen:

  • reale Neon-Region prüfen;
  • tatsächliche Vercel-Functions-Region prüfen;
  • gemeinsame sinnvolle EU-Region bewerten;
  • ausreichendes Restore-Fenster sicherstellen;
  • Restore mit synthetischen Daten praktisch testen;
  • Migrationen und vollständigen Reader nach Restore erneut prüfen.

Es wurde bewusst keine vermutete Region in vercel.json eingetragen und keine externe Datenbank migriert.

ADR 0013 dokumentiert zusätzlich Convex als geprüfte, aber wegen des abweichenden Backend-Modells und der fehlenden unveränderten Abbildung zusammengesetzter relationaler Owner-Fremdschlüssel verworfene Alternative.

Zusätzliche Konfigurationsbereinigung

Die zuvor dokumentierte, aber fehlende .env.example wurde mit ausschließlich synthetischen Platzhaltern ergänzt.

Die widersprüchliche .gitignore-Regel wurde bereinigt, sodass lokale .env-Dateien weiterhin ignoriert werden, während .env.example und weitere explizite Beispielvorlagen eingecheckt werden können.

Bewusst nicht enthalten

Diese PR enthält ausdrücklich nicht:

  • Import bestehender Google-Sheets-Daten;
  • produktive Migration einer Neon-Datenbank;
  • Umschaltung von /api/finance;
  • PostgreSQL-zu-Sheets-Fallback;
  • Feature-Flag-Dualbetrieb;
  • Schreibrepository für den Editor;
  • Änderungen an OAuth, Picker oder Refresh-Tokens;
  • neue Finanzberechnungen;
  • Schema v2;
  • Supabase oder Convex;
  • vollständige Multi-User-/RLS-Architektur.

Der aktuelle produktive Pfad bleibt:

Google Sheets
→ Parser
→ FinanceDataV1
→ Selektoren
→ View-Model

Der neue, noch nicht produktiv angeschlossene Pfad ist:

verifizierte Google sub
→ interner Owner
→ PostgreSQL
→ FinanceRepository
→ FinanceDataV1
→ bestehende Selektoren

Verifikation

Folgende Prüfungen wurden erfolgreich ausgeführt:

  • npm run docs:check
  • npm test
    • 237 reguläre Vitest-Tests
    • 11 Node-Tests
  • npm run test:postgres
    • 10 Testfälle gegen eine echte temporäre PostgreSQL-17-Instanz
  • npm run lint
  • npm run licenses:check
  • npm run build
  • git diff --check
  • Scope-Prüfung auf unbeabsichtigte OAuth-, UI-, Parser-, Selektor- oder /api/finance-Änderungen

Auswirkungen und nächste Schritte

Für Nutzer ändert sich mit dieser PR noch nichts. Der produktive Finanzstand wird weiterhin aus Google Sheets gelesen.

Die vorgesehene weitere Reihenfolge bleibt:

ACC-71 Schema und Reader
→ ACC-29 Paritätsnachweis
→ ACC-66 Import und Cutover
→ ACC-72 Editor

ACC-29 kann auf dem neuen Reader und den Integrationstests aufbauen, um die vollständige Parität zwischen Sheet-Parser, PostgreSQL-Persistenz und bestehenden Selektoren nachzuweisen.

Greptile Summary

Der PR ergänzt das ownergebundene PostgreSQL-Schema und einen noch nicht produktiv angeschlossenen Reader für vollständige FinanceDataV1-Daten.

  • Führt zusammengesetzte Owner-Schlüssel und Integritäts-Constraints für Finanzentitäten, Snapshots und Meilensteine ein.
  • Rekonstruiert und validiert Finance-Daten innerhalb einer read-only Repeatable-Read-Transaktion.
  • Zentralisiert den lazy erzeugten PostgreSQL-Pool für Connection- und Finance-Repositories.
  • Ergänzt PostgreSQL-17-Integrationstests, einen separaten CI-Job sowie Betriebs- und Architektur-Dokumentation.

Confidence Score: 5/5

Der PR erscheint sicher zum Mergen; es wurden keine konkreten geänderten Codepfade mit einem veröffentlichungswürdigen Fehler gefunden.

Das Schema erzwingt die vorgesehene Owner-Isolation, der Reader filtert sämtliche Abfragen über die intern aufgelöste Owner-ID und validiert den konsistent gelesenen Datenstand vor der Rückgabe.

Important Files Changed

Filename Overview
migrations/002_finance_data_v1.sql Definiert das transaktionale, ownergebundene Finance-v1-Schema mit zusammengesetzten Fremdschlüsseln und fachlichen Integritäts-Constraints.
api/_lib/financeRepository.ts Liest einen konsistenten Owner-Datenstand, bildet PostgreSQL-Typen sicher ab und validiert das rekonstruierte FinanceDataV1.
api/_lib/database.ts Stellt je DATABASE_URL einen gemeinsam genutzten, lazy erzeugten PostgreSQL-Pool bereit.
api/_lib/repository.ts Stellt das bestehende Google-Connection-Repository auf den gemeinsamen, URL-gebundenen Pool um.
tests/postgres/financeRepository.postgres.test.ts Prüft Migration, Owner-Isolation, Constraints, Präzision, vollständige Rekonstruktion und Integritätsfehler gegen echtes PostgreSQL.
.github/workflows/ci.yml Ergänzt einen isolierten PostgreSQL-17-Job für die neue Integrationstestsuite.

Sequence Diagram

sequenceDiagram
    participant Caller as Interner Server-Caller
    participant Repo as FinanceRepository
    participant DB as PostgreSQL
    participant Schema as financeDataV1Schema

    Caller->>Repo: readForGoogleSub(verifiedGoogleSub)
    Repo->>DB: BEGIN READ ONLY, REPEATABLE READ
    Repo->>DB: Google sub → owners.id
    alt Owner oder finance_meta fehlt
        DB-->>Repo: Kein vollständiger Datenstand
        Repo-->>Caller: null
    else Datenstand vorhanden
        Repo->>DB: Owner-gefilterte Entitäten, Snapshots und Meilensteine
        DB-->>Repo: Konsistenter Snapshot
        Repo->>Schema: BIGINT-Mapping und Laufzeitvalidierung
        Repo->>Repo: Aktuelle Snapshots aktiver Entitäten prüfen
        Repo-->>Caller: FinanceDataV1
    end
Loading

Reviews (1): Last reviewed commit: "Implement ACC-71 PostgreSQL finance read..." | Re-trigger Greptile

@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
finance-overview Ready Ready Preview Aug 14, 2026 8:06am

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0413d51-c6e4-49b5-a794-fa4ac5e9e83c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hernstev97
hernstev97 merged commit 4f5f3bf into develop Aug 14, 2026
9 checks passed
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.

1 participant