Kernel Milestone 5 — Schema-Derived Categorical Boundary
Kernel Milestone: 5 of 6 Previous: Milestone 4 — Provenance (v5.22.0) Next: Milestone 6 — Append-Only Cryptographic Ledger
System Impact
-------------
Ledger integrity: strengthened — unvalidated row access eliminated
Ledger mutation: unchanged
Tenant isolation: unchanged
Financial immutability: unchanged
State machine: unchanged
Breaking changes: none
Replay determinism: preserved
Invariant Change
----------------
Every database query result in every route file now passes through a
strict Zod schema before entering the domain layer. The raw untyped
boundary is closed system-wide.
Why This Matters
The database boundary is the most dangerous trust boundary in any financial system. When raw query results flow directly into domain logic without validation, two classes of failure become invisible: driver coercion bugs (where the database returns data in a form different from what the application assumes) and migration drift (where a column is added, renamed, or removed without the application noticing).
Milestone 4 established the boundary pattern in the invoice service and proved it mechanically. Milestone 5 propagates that pattern across every remaining route file in the system. The result is that no query result can reach the domain layer without passing through a declared schema with known shape, known types, and known nullability.
The secondary value of this milestone is diagnostic. Strict schemas make the application’s assumptions explicit. When those assumptions were wrong, as they were in three places in the dividend voucher endpoint, the compiler refused to build until they were corrected. Three pre-existing type defects that had been silently masked by untyped row access were surfaced and fixed as a direct consequence of this work. None of them were new bugs introduced by the milestone. All of them were real.
Boundary Propagation
Route File Coverage
AX-BND-001 boundary validation has been applied to all remaining route files. Each file received a full schema sweep: every distinct query shape was assigned a dedicated Zod schema declared .strict(), and every result set passes through .parse() before any field access.
Directors 4 schemas covering all query shapes. 5 parse sites. Zero bare row accesses remain.
Contacts 6 schemas including contact metadata, invoice summary, and count aggregates. 8 parse sites. Zero bare row accesses remain.
Accounts 6 schemas covering account detail, balance aggregates, and type lookups. 11 parse sites. Zero bare row accesses remain.
Categorisation Rules 14 schemas covering the full TMADD engine query surface: rule list, engine evaluation, account lookups, insert/update/delete signals, match log, and next-priority computation. 25 parse sites. Zero bare row accesses remain. The categorisation engine’s internal
RuleRowinterface is retained for TMADD matching logic;CatRuleEngineRowis the boundary gate that all DB results pass through before entering the engine.Dividends 14 schemas covering board minutes, declared dividends, director queries, retained earnings, voucher data, and date/sequence lookups. 37 parse sites. Zero bare row accesses remain.
BIGINT Discipline
The dividend route handles amount_pence as a PostgreSQL BIGINT, which the database driver returns as a string rather than a number. This driver behaviour is now explicitly encoded in the schema boundary: amount_pence carries type z.string() at the schema layer, and parseInt(..., 10) conversion happens at each consumption site. Ordinary integer fields retain z.number().int() with no conversion. The boundary is now truthful to what the driver actually produces.
Pre-Existing Defects Surfaced
Three type defects in the dividend voucher endpoint were uncovered and fixed as a direct consequence of the boundary schema work. All three had been silently masked by untyped row access. None were introduced by this milestone.
Director name nullability The voucher data structure requires a non-null director name. The underlying join guarantees this at runtime, but the schema correctly declares the column nullable to reflect the actual database type. A null-coalescing default is applied at the consumption site, making the intent explicit where previously it was invisible.
Company number null-to-undefined coercion The voucher organisation type declares company number as
string | undefined. The database column is nullable and the schema reflects this. The coercion fromnulltoundefinedis now explicit at the construction site.Address field null-to-undefined coercion All six address fields carry the same pattern. Each is nullable in the schema, correctly reflecting the column definition, and each is coerced to
undefinedat the construction site to satisfy the voucher organisation contract.
Threat Closure
| Threat | Status Before | Status After | Enforcement Layer |
|---|---|---|---|
| Driver coercion bug in director routes | Open | Closed (Class M) | Runtime schema validation |
| Migration drift in contact queries | Open | Closed (Class M) | Runtime schema validation |
| Unregistered column propagation in account routes | Open | Closed (Class M) | .strict() schema parse |
| BIGINT string coercion invisible in dividend routes | Open | Closed (Class M) | Runtime schema + Compiler |
| Untyped voucher construction in dividend routes | Open | Closed (Class M) | Compiler type check |
Kernel Closure Statement
Axiom: AX-BND-001 — Schema-Derived Runtime Validation
Status: CLOSED system-wide as of SpeyBooks v5.25.0
Enforcement layers verified:
Runtime layer — Zod .strict() parse on every query result set
across all route and service files
Compiler layer — Typed mapper functions reject shape mismatches
at build time; three pre-existing defects fixed
CI layer — check-db-boundary.ts scans all route and service
files; exits 1 on any unvalidated row access
CI verification:
verify-axiom-coverage.sh — PASS (21 delivered axioms, 0 violations)
Residual risk:
None within the runtime boundary domain. Egress shape invariance
(AX-BND-002) remains planned for post-launch.
Next dependent axiom:
AX-LED-001 — Canonical Time Axis (Milestone 6)
Security Posture Change
The database boundary was previously a Class O boundary in the route layer: correct behaviour depended on developer discipline in accessing the right fields with the right types. That is now Class M. Every query result is validated by a declared schema before any field is accessed. A query returning an unexpected shape causes an immediate 500 rather than silent propagation. This eliminates an entire class of potential migration-related data corruption from the threat model.
Verification Record
Pre-flight:
0 lint violations across all five route files
tsc: clean build after three type defects corrected
Post-commit:
V1 directors.ts — 4 schemas, 5 parse sites, 0 bare row accesses
V2 contacts.ts — 6 schemas, 8 parse sites, 0 bare row accesses
V3 accounts.ts — 6 schemas, 11 parse sites, 0 bare row accesses
V4 categorisation-rules.ts — 14 schemas, 25 parse sites, 0 bare row accesses
V5 dividends.ts — 14 schemas, 37 parse sites, 0 bare row accesses
Axiom coverage gate:
verify-axiom-coverage.sh — PASS
Delivered axioms: 21
Violations: 0
Execution time: 9s
Architectural Context
The six kernel milestones form a dependency chain. Milestones 1 and 2 established that the ledger cannot be corrupted from outside the system (tenant isolation, immutability, state machines). Milestones 3 and 4 established that the ledger cannot be corrupted from inside the system (monetary arithmetic, temporal determinism, identifier correctness, query ordering, typed error handling). Milestone 5 closes the final trust boundary between the database and the domain layer, ensuring that data entering the domain is always of known shape and type.
Milestone 6 completes the chain by making the ledger append-only and cryptographically verifiable, so that the integrity guarantees established in Milestones 1 through 5 can be independently audited over time.
Operational Impact
- Zero unvalidated query results enter the domain layer across any route in the system
- Three pre-existing type defects in the dividend voucher endpoint are resolved
- Build-time type errors now surface schema mismatches that were previously silent
- BIGINT columns are handled with explicit documented coercion, not silent assumption
- CI boundary gate prevents any future unvalidated row access from reaching production
Kernel Status
| Milestone | Description | Status |
|---|---|---|
| M1 | Tenant Isolation | Complete |
| M2 | Financial Immutability and State Machines | Complete |
| M3 | Monetary Domain Migration | Complete |
| M4 | Provenance | Complete |
| M5 | Schema-Derived Categorical Boundary | Complete |
| M6 | Append-Only Cryptographic Ledger | Pending |
Files Changed
Backend:
api/src/routes/directors.ts— 4 Zod schemas, 5 parse sites,accounting.prefix corrected throughoutapi/src/routes/contacts.ts— 6 Zod schemas, 8 parse sites,accounting.prefix corrected throughoutapi/src/routes/accounts.ts— 6 Zod schemas, 11 parse sites,accounting.prefix corrected throughoutapi/src/routes/categorisation-rules.ts— 14 Zod schemas, 25 parse sites, ORDER BY determinism annotatedapi/src/routes/dividends.ts— 14 Zod schemas, 37 parse sites, BIGINT discipline applied, 3 type defects corrected
Milestone 6 will close the final architectural gap: append-only cryptographic ledger integrity, making every guarantee established across Milestones 1 through 5 independently auditable over time.