Match each accounting table's foreign keys to its lifetime¶
Context and Problem Statement¶
The accounting tables reference users, workers, and teams, and those three are deleted in different ways. User accounts are soft-deleted and wiped. Workers and teams can be hard-deleted by their owners, and that deletion is a supported operation rather than an exceptional one.
The four tables also differ in what their rows are for:
kudos_ledgerrows are authoritative currency history and the input to reconciliation.kudos_stat_eventsrows are display and counter history behind worker, team, and user aggregates.kudos_reservationsrows are temporary holds against one payer.kudos_balance_snapshotsrows are a per-user reconciliation baseline.
Decision Drivers¶
- Users, workers, and teams are deleted in different ways, and worker and team deletion is a supported operation that must stay unblocked.
- The four tables hold rows with different lifetimes: authoritative currency history, display and counter history, temporary holds, and a per-user reconciliation baseline.
Considered Options¶
- Match each accounting table's foreign keys to its lifetime
- Ownership foreign keys everywhere, cascading
- Ownership foreign keys everywhere, restricting
- No foreign keys on any accounting table
Decision Outcome¶
Chosen option: "Match each accounting table's foreign keys to its lifetime".
Each table gets the policy its contents' lifetime requires.
kudos_ledger.user_idis a required foreign key withON DELETE RESTRICT. Authoritative currency history must not become orphaned, and users are removed by soft delete and wipe rather than row deletion, so the restriction does not block a supported operation.kudos_reservations.user_idandkudos_balance_snapshots.user_idare ownership foreign keys withON DELETE CASCADE. A hold and a per-user reconciliation baseline only have meaning while the user exists.kudos_stat_eventscarriesuser_id,worker_id,worker_user_id, andteam_idas immutable audit references with no foreign key. Workers and teams are hard-deletable while their counter history must survive them, and neither cascade nor restrict can deliver both. The exception is documented on the mapped model so it reads as a decision rather than an omission.
Consequences¶
- Good: A
kudos_ledgerrow always names a real user, so reconciliation never has to reason about a posting whose account is gone. - Good: Deleting a worker or a team stays a supported, unblocked operation, and the totals its work
contributed to keep their explanation in
kudos_stat_events. -
Good: Holds and snapshots clean themselves up with the account they belong to.
-
Bad:
kudos_stat_eventsrows can reference a worker or team that no longer exists, so a consumer joining against live rows must tolerate a missing target rather than assume one. - Bad: Every future accounting table must be assigned a policy deliberately, answering the same question: is this row authoritative history, an audit reference, or operational state?
- Bad: The split reinforces why currency and statistic postings live in separate tables (ADR 2). Merging them would reopen a conflict with no schema-level resolution.
Pros and Cons of the Options¶
Ownership foreign keys everywhere, cascading¶
- Bad: Deleting a worker would erase the postings that explain its team's totals, and reconciliation would lose the currency history for a wiped user.
Ownership foreign keys everywhere, restricting¶
- Bad: Worker and team deletion would fail for any worker that had ever earned anything, which is every worker that has run.
No foreign keys on any accounting table¶
- Bad: It gives up the guarantee that a currency posting always names a real user, which is the one referential guarantee reconciliation depends on.
More Information¶
ADR 14 amends the hard-deletion behavior with the worker deletion/projector lock protocol and the deterministic statistic-event quarantine policy.