v5.25.0 9 March 2026 Improvement

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 RuleRow interface is retained for TMADD matching logic; CatRuleEngineRow is 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 from null to undefined is 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 undefined at the construction site to satisfy the voucher organisation contract.


Threat Closure

ThreatStatus BeforeStatus AfterEnforcement Layer
Driver coercion bug in director routesOpenClosed (Class M)Runtime schema validation
Migration drift in contact queriesOpenClosed (Class M)Runtime schema validation
Unregistered column propagation in account routesOpenClosed (Class M).strict() schema parse
BIGINT string coercion invisible in dividend routesOpenClosed (Class M)Runtime schema + Compiler
Untyped voucher construction in dividend routesOpenClosed (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

MilestoneDescriptionStatus
M1Tenant IsolationComplete
M2Financial Immutability and State MachinesComplete
M3Monetary Domain MigrationComplete
M4ProvenanceComplete
M5Schema-Derived Categorical BoundaryComplete
M6Append-Only Cryptographic LedgerPending

Files Changed

Backend:

  • api/src/routes/directors.ts — 4 Zod schemas, 5 parse sites, accounting. prefix corrected throughout
  • api/src/routes/contacts.ts — 6 Zod schemas, 8 parse sites, accounting. prefix corrected throughout
  • api/src/routes/accounts.ts — 6 Zod schemas, 11 parse sites, accounting. prefix corrected throughout
  • api/src/routes/categorisation-rules.ts — 14 Zod schemas, 25 parse sites, ORDER BY determinism annotated
  • api/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.