Skip to content

Decisions

Forty architecture decision records. Each one exists because a choice was made that looks arbitrary from the outside, and the reasoning would otherwise be lost the moment the person who made it stopped being reachable.

These are the authority. Where a page elsewhere in this site summarises one, the ADR wins.

Note

The work order that commissioned this site said thirty-eight. There are forty: 0039 (schema v0.3) and 0040 (MinIO) landed after it was written. 0040 supersedes the object-store choice in 0037; 0029 supersedes the consent stub in 0009.


Storage and the append-only invariant

0001 Append-only is enforced by the database role Not by application code, because application code can be bypassed and a grant cannot
0002 superseded_by and stale are derived, not stored A stored derived field requires an UPDATE, which the role forbids
0005 Record storage layout One table per namespace, partitioned monthly, payload as JSONB
0022 Lineage: staleness, cycles, and who may correct Who is entitled to supersede, and what happens when a chain loops

Ingest

0006 Idempotency is on the id, and a reused id is refused Retry safety without silently accepting a different record under an old id
0007 Delegation is resolved at ingest, and forks refuse authority Authority is checked against the moment of the event, not the moment of the write
0015 Observation.subject_ref is checked, not enforced A foreign key would make the referenced record undeletable and the reference untyped
0019 Every record states the ground it was collected under s.7, made structural rather than procedural

Quantity, conversion and quality

0011 A declared loss is an Observation, not a field A loss is somebody's assertion at a time, with its own provenance
0014 Mass balance is reconciled across the custody sequence Flag the discrepancy; never reject the record that revealed it
0018 A measured conversion has to show what it measured Sample size and method, or the factor is an assumption wearing a number

Custody, fulfilment and settlement

0012 Lot.custodian is computed from the transfer chain A stored custodian and a transfer chain will eventually disagree
0013 Fulfilment is summed on read, never stored Same reason; the sum is a view of the deliveries, not a fact beside them
0016 Settlements are summarised per obligation, and never per party A per-party balance is a wallet, and a wallet makes this a financial institution
0021 A forked record is counted in nothing Ambiguous provenance must not silently contribute to a total

Datasets and the inference quarantine

0017 Fabricated records are separated at the row, not by refusing to mix them Separate databases drift; a row-level discriminator cannot be forgotten
0023 The adversarial seed Seed data that contains the problems, so the flags are exercised before real data arrives
0036 P4: the inference module closed Fetch by name, no listing, no application write path

Registry and reference data

0010 Reference data lives in its own append-only schema A conversion factor that changes retroactively changes history
0024 The registry is public, and the sample is stored The only unauthenticated read surface, and the one that must be auditable
0027 The season calendar is a registry table, not a schema change Defers D5
0028 A ceiling on the observation vocabulary Defers D8; an unbounded vocabulary is an unbounded schema

Sync and devices

0008 Sync is two endpoints and no server-held state Server-held per-device state is the part of sync that breaks

Identity and parties

0026 Party links: same_as as a reversible assertion Defers D2; merging identities is irreversible and often wrong
0009 A consent stub that fails closed Superseded by 0029, but the fail-closed default survives
0025 The audit log is a statutory record s.36 and s.37, not a debugging convenience
0029 Consent Grantee, purpose, record types, expiry โ€” all five, always
0030 Objection under s.7(3) And why it is not withdrawal
0031 Subject access under s.24 Including why the route takes no subject parameter
0032 Notifying the parties who received the old version s.16(4)
0035 J5 retention notice, J6 naming discipline s.13(1)(i), and the words this system refuses to use

Lender-facing output

0034 The lender view is read as the lender Rendered by the kernel, because a rule in one renderer can be reviewed

Media, anchoring and integrity

0037 Media: bytes that survive a bad link and disclose nothing Object-store choice superseded by 0040
0038 P6: anchoring The mechanism, and why verification must be external
0040 MinIO, reversing 0037 Including the anonymous-policy requirement nothing yet tests

Toolchain and operations

0003 TypeScript 7 for compilation, TypeScript 6 for the linter Two versions, deliberately, until the linter catches up
0004 PostGIS image for local development Parity with production over convenience
0020 Verified backups, config guards, statutory runbook The minimum before real data could be accepted

Retrospectives

0033 The six fixes: what they were, and what two of them turned out to be Two were not bugs; the record of that is more useful than the fixes
0039 Schema v0.3 Including ยง7 on two-sided confirmation