Files
finance-app/prisma
siddharthd 2251060ac6
ci / lint-test (push) Successful in 57s
db: append-only change log for the ledger (migration 0037)
`transaction_audit` records every INSERT/UPDATE/DELETE on transactions,
transaction_splits, transaction_tags, transaction_overrides and
expense_metadata, via AFTER row triggers.

WHY: on 2026-08-23 the n8n CC Statement Processor stamped `statement_id` onto
seven statementless wallet/pantry rows without setting `reconciled_with_id`,
double-counting $245.90 and hiding those rows from the reconcile queue
(`WHERE statement_id IS NULL`). Answering "was anything lost?" required
restoring the 02:30 pg_dump into a scratch database and diffing it column by
column, because nothing in the estate recorded what changed.

Trigger rather than application code because the ledger has at least five
independent writers -- this app (Prisma), n8n (raw SQL), pantry-app, the
ingestion runner (psycopg) and ad-hoc `docker exec psql`. That bug was an n8n
raw-SQL UPDATE; an app-level audit would not have seen it.

Not Postgres' own logs: wal_level=replica, wal_keep_size=0, archive_mode=off,
log_statement=none, track_commit_timestamp=off, no pgaudit -- verified live.
Postgres was recording nothing usable, and has no built-in audit tables.

UPDATEs store only the columns that changed (a whole-row copy buries a
one-column edit, and line_items can be kilobytes); no-op UPDATEs write nothing;
txid groups the rows of one logical edit, which matters because
applyReconcileMatch moves four children in a single transaction. No FK to
transactions -- the log must outlive the row. TRUNCATE does not fire row
triggers, so the test suite's wipes are silent.

Rehearsed against a scratch restore first: changed-column-only diffing, no-op
suppression, composite PK (transaction_tags), and the
matched_transaction_id fallback (a card-settled order has only that) all
verified before applying. Applied to personal and personal_test. Board #259.
2026-08-23 15:57:44 +10:00
..