AX-BND-001 — Schema-Derived Runtime Validation at the DB Service Boundary
Kernel Milestone: 4 of 6 (Part 5 of 5) Previous: Milestone 4 (Part 4) — Ordering Determinism — AX-QRY-001 (v5.22.0) Next: Milestone 4 Complete — Temporal Determinism, DB Boundary Validation & Unified Verification Harness (v5.24.0)
System Impact
-------------
Ledger integrity: strengthened
Ledger mutation: unchanged
Tenant isolation: unchanged
Financial immutability: unchanged
State machine: unchanged
Breaking changes: none
Replay determinism: strengthened — boundary failures now fail loud
Invariant Change
----------------
DB query results at the invoice service boundary are now validated against
strict Zod schemas before any mapper executes. An unregistered column, a
type mismatch, or a missing required field causes immediate parse failure.
The boundary can no longer pass corrupted financial state silently into the
domain layer.
Why This Matters
The database driver returns database values as raw JavaScript primitives: integer columns as strings to preserve precision, decimal columns as strings for scale preservation, date columns as ISO strings. TypeScript generics on query calls impose no constraint on the actual runtime values returned. A schema migration changing a column type, a driver version update altering coercion behaviour, or a wildcard column selection after a schema addition would all compile cleanly, pass tests, and process incorrect data until a financial discrepancy surfaced downstream. There would be no error. The wrong values would simply flow through the mapper into domain objects and eventually into financial calculations.
This is the DB boundary problem. The mapper assumes the query result has the right shape. Nothing checks that assumption at runtime. The only defence was the TypeScript generic — and that generic is a lie, because the driver does not enforce it.
AX-BND-001 closes this gap. Every invoice query result now passes through a Zod schema before the mapper can access it. The schemas are declared .strict() — an unregistered column causes an immediate parse failure rather than passing through silently. A ZodError at this boundary is not caught and not handled: it propagates as a 500 and is treated as a P1 incident. The boundary fails loud rather than passing corrupted state to the domain layer.
The unsafe type cast on the invoice mapper — the clearest possible signal of an unvalidated boundary — has been replaced with a satisfies check. This requires the TypeScript compiler to verify mapper completeness at every build. If the domain type gains a required field and the mapper does not provide it, the build fails. The escape hatch is gone.
What Was Delivered
Boundary schemas define strict Zod schemas for each invoice query result shape. Each schema enforces the actual driver output types: integer-as-string monetary values are validated by format, date-as-string values are validated against the ISO date pattern in accordance with AX-TMP-001. Schemas are .strict() — an unregistered column in the result causes an immediate parse failure. Legacy computed columns from before the M3 pence migration are declared optional before .strict() to maintain compatibility with existing queries.
The CI boundary gate is a new static analysis script that scans all service and route files for query result accesses that lack a nearby schema parse call, and exits non-zero if any are found. Route handlers are included in the scan to close the bypass vector where a handler could access query results directly without going through a service. This gate runs on every build and rejects any new unvalidated boundary crossing before it can reach production.
The row type definitions complete the AX-TMP-001 temporal purge. All Date types across every row interface are now typed as string. The deprecated sequence columns that were superseded by the AX-CON-001 document sequencing migration are marked accordingly. The invoice line row interface has been corrected to include the canonical monetary pence columns introduced in M3 that were missing from the original definition.
Boundary Schema Design Decisions
.strict() on all schemas. A schema without .strict() silently accepts extra fields. This means a migration adding a column would pass through unnoticed — exactly the failure class AX-BND-001 is designed to prevent.
ZodError must not be caught at the boundary. A boundary parse failure means the production database schema has diverged from the application’s expectations. This is a P1 incident. Catching the error would mask the incident and allow subsequent requests to continue processing corrupted data. ZodError propagates directly to the error handler.
satisfies replaces the unsafe cast. The previous cast hid the gap between what the mapper produced and what the domain type required. The satisfies check inverts this: the compiler verifies the mapper output covers every required field. If the type changes, the build fails at the mapper, not silently at runtime.
Reference implementation scope. AX-BND-001 is closed at the standard and enforcement machinery level in M4. The invoice service is the reference implementation. M5 propagates the pattern across the remaining services. The CI gate is already active — any new unvalidated boundary crossing will be caught before it reaches production.
Threat Closure
| Threat | Status Before | Status After | Enforcement Layer |
|---|---|---|---|
| Driver coercion producing wrong runtime types | Open | Closed (Class M) | Zod schema parse at boundary |
| Schema migration silently corrupting mapped objects | Open | Closed (Class M) | .strict() — unexpected columns fail immediately |
| Mapper incompleteness hidden by unsafe cast | Open | Closed (Class M) | satisfies check — compiler gate |
| Unvalidated boundary in route handlers | Open | Closed (Class M) | CI gate scans routes as well as services |
Date objects crossing the DB/domain boundary | Open | Closed (Class M) | Row type temporal purge — AX-TMP-001 |
Proof Anchors
Runtime layer — AX-BND-001:
Boundary schema file: three strict Zod schemas for invoice queries.
ZodError propagates as 500 — not caught.
Compiler layer — AX-BND-001:
Invoice service mapper: `satisfies` domain type check replaces unsafe cast.
Compiler verifies mapper completeness at every build.
CI layer — AX-BND-001:
Boundary CI gate: scans service and route files for query result accesses
without a nearby schema parse call. Exits non-zero, blocking build.
Runs on every commit.
Compiler layer — AX-TMP-001 (completed):
Row type definitions: all Date types replaced with string.
Compiler rejects any mapper accepting driver-coerced Date objects.
Security Posture Change
Prior to this release, DB query result shapes were a Class O guarantee — dependent on developer discipline and TypeScript generics that the runtime did not enforce. A driver update, a schema migration, or a misconfigured query could silently produce wrong financial data with no observable error. This release moves the DB service boundary to Class M: Zod schemas enforce the expected shape at runtime, the compiler enforces mapper completeness, and the CI gate prevents any new unvalidated boundary from reaching production.
Verification Record
Pre-flight:
Invoice service identified as primary reference implementation
Row type audit: all temporal fields confirmed as string candidates
Invoice line row gap identified: M3 canonical monetary columns missing
from original row type definition
Patch application:
Boundary schemas — 3 schemas, 4 primitive types, .strict() on all
Invoice service — schema parse at boundary; satisfies check applied;
unsafe cast removed; enum column cast narrowed
Row type definitions — Date purge complete; deprecated columns marked;
invoice line row corrected
Domain types — inline contact summary type introduced; invoice number
and created-by nullability corrected; all Date fields replaced with string
Post-commit:
V1 pnpm build — clean, 0 TypeScript errors
V2 Boundary CI gate — PASS, 0 violations
V3 Axiom coverage verifier — PASS, 21 delivered axioms, 0 violations
V4 Full harness (run-harness.sh) — 8/8 gates passed
Operational Impact
Boundary failures are loud. A schema mismatch produces an immediate 500 identifying the exact field and value that failed. There is no silent corruption path.
Migration safety. Any future schema migration changing a column type or renaming a column will be caught at the first request to hit the affected query — not discovered later through financial discrepancy investigation.
Compiler as enforcer. Adding a required field to the domain type without updating the mapper produces a build error, not a runtime gap.
CI as enforcer. The boundary gate runs on every build. No new unvalidated boundary crossing can reach production without a deliberate exemption. Zero ongoing maintenance cost.
Kernel Status
| Milestone | Description | Status |
|---|---|---|
| M1 | Tenant Isolation | Complete |
| M2 | Financial Immutability and State Machines | Complete |
| M3 | Monetary Domain Migration | Complete |
| M4 — AX-CON-001 | Sequential Identifier Correctness | Complete |
| M4 — AX-TMP-001 | Temporal Determinism | Complete |
| M4 — AX-CON-002 | Idempotency Contract | Complete |
| M4 — AX-QRY-001 | Ordering Determinism | Complete |
| M4 — AX-BND-001 | Schema-Derived Runtime Validation | Complete |
| M5 | Boundary Propagation | Pending |
| M6 | Append-Only Cryptographic Ledger | Pending |
Files Changed
New:
- Boundary schema definitions — strict Zod schemas for invoice query results
- CI boundary gate — static analysis script for unvalidated boundary crossings
Modified:
- Row type definitions — AX-TMP-001 temporal purge complete; invoice line row corrected; deprecated columns marked
- Invoice service — schema parse at boundary; satisfies check; unsafe cast removed
- Domain types — inline contact summary introduced; invoice number and created-by nullability corrected; all Date fields replaced with string
The reference implementation pattern established here — strict boundary schema, satisfies mapper check, CI gate — is the template for service boundary validation across the remaining services in M5.