v5.20.0 7 March 2026 Improvement

M4 Provenance — Temporal Determinism (AX-TMP-001)

Kernel Milestone: M4 of 6 (second delivery) Previous: M4 — Sequential Identifier Correctness (v5.19.0) Next: AX-CON-002 — Idempotency Contract

System Impact
-------------
Ledger integrity:         strengthened
Ledger mutation:          DB-authoritative timestamps enforced at all write paths
Tenant isolation:         unchanged
Financial immutability:   unchanged
State machine:            unchanged
Breaking changes:         none
Replay determinism:       strengthened — no application-clock source in financial paths
Invariant Change
----------------
All temporal values at the API egress boundary are now normalised through a
canonical module and typed as strings at compile time, eliminating silent Date
object coercion and application-clock contamination of financial records.

Why This Matters

Date handling is one of the most common sources of silent non-determinism in financial software. When a JavaScript Date object is constructed from a PostgreSQL date column, the result depends on the local timezone of the Node.js process, the database driver version, and the column type. Two environments that are byte-identical at the source can produce different outputs. In an accounting system, that means different ledger representations of the same record, depending on where and when the code runs.

This release closes that gap. A canonical temporal normalisation module now governs all date and timestamp values at the point they cross from the database into the domain layer. PostgreSQL is declared the sole time authority for all financial write paths: no new Date() call may source a value written to a ledger record. Every date column arrives at the API boundary as an ISO 8601 string. Every timestamp column arrives as a UTC ISO 8601 string. The type system enforces this at compile time.

This is the foundation on which deterministic replay and future Merkle-chained audit trails depend. A ledger that cannot guarantee the same timestamp representation under replay is not a ledger.


Proof Anchors

Compiler layer:
  db-types.ts — all date and timestamp fields in raw DB row types typed as
  string; Date objects rejected at compile time before reaching domain logic

Runtime layer:
  canonical normalisation module — three helper functions covering DATE,
  TIMESTAMPTZ, and nullable timestamp column types; sole permitted coercion
  path at the DB-to-domain boundary
  mapper sweep — all route mappers and invoice mapper updated to call canonical
  helpers; bespoke local formatDate/formatDateTime functions removed
  DB-authoritative write paths — now() expressions in SQL for reconciliation
  timestamps, dividend payment/void dates, S455 reference dates, and MWCE
  cutover defaults; application-clock sources eliminated from all financial paths

Database layer:
  constraint coverage CI gate — 75/75 constraints mapped in error codec; three
  document sequence constraints added as kernel invariant faults; gate confirms
  no raw SQL error strings reachable by clients

Temporal Determinism

Canonical Normalisation Boundary

A dedicated normalisation module now provides three functions covering all temporal column types in the schema: one for DATE columns producing YYYY-MM-DD strings, one for TIMESTAMPTZ columns producing ISO UTC strings, and one for nullable timestamp columns. These are the only permitted temporal coercion functions in the codebase. No other date formatting is allowed at the DB-to-domain boundary.

  • Database row types All date and timestamp fields in the raw database row types are typed as string, not Date. This is enforced at compile time: the driver’s native coercion produces a Date object, but the type contract rejects it before it reaches domain logic.

  • Mapper boundary Every mapper function that converts a raw database row to an API response now calls the canonical normaliser for each temporal field. Bespoke formatting functions that previously existed in individual mappers have been removed.

  • DB-authoritative write paths Any financial record that requires the current date or timestamp uses a database-side now() expression rather than the application clock. This applies to reconciliation timestamps, payment dates, tax calculation reference dates, and migration cutover defaults.

  • Error boundary coverage Three database constraints protecting the document sequence table were not yet registered in the typed error boundary codec. These have been added as kernel invariant faults. The CI gate that enforces total constraint coverage detected the gap automatically.


Threat Closure

ThreatStatus BeforeStatus AfterEnforcement Layer
Application-clock contamination of financial timestampsOpenClosed (Class M)Runtime — DB now() enforced at all financial write paths
Silent Date object coercion at DB boundaryOpenClosed (Class M)Compiler — DB row temporal fields typed as string
Bespoke date formatting divergence across mappersOpenClosed (Class M)Runtime — single canonical normalisation module
Unmapped database constraints reaching API error boundaryOpenClosed (Class M)Runtime — constraint coverage CI gate

Kernel Closure Statement

Axiom: AX-TMP-001 — Temporal Determinism Standard

Status: CLOSED as of SpeyBooks v5.20.0

Enforcement layers verified:
  Compiler layer   — DB row temporal fields typed as string in db-types.ts;
                     Date objects rejected before domain layer
  Runtime layer    — canonical normalisation module (three helpers);
                     mapper sweep across all route and invoice mappers;
                     DB now() expressions on all financial write paths
  Database layer   — constraint coverage CI gate at 75/75; no raw SQL
                     error strings reachable by clients

CI verification:
  verify-axiom-coverage.sh      — PASS (18 delivered axioms, 0 violations)
  constraint-coverage-check.sh  — PASS (75/75 constraints mapped)

Residual risk:
  None within the temporal determinism domain.

Next dependent axiom:
  AX-CON-002 — Idempotency Contract

Security Posture Change

No invariants moved from Class O to Class M in the security domain. The threat closure above is within the determinism and error boundary domains. The constraint coverage CI gate (AX-ERR-001) is elevated to fully closed: every database constraint in the schema now has a typed mapping in the error codec, removing the possibility of a raw database error string reaching a client response.


Verification Record

Pre-flight:
  Axiom registry v1.7 — 18 delivered axioms, 0 violations
  AX-TMP-001 proof anchors verified against implementation
  Milestone M4 deliverables narrowed to delivered axioms only

Mapper sweep:
  Routes: all temporal fields normalised via canonical helpers
  Invoice mapper: local formatDate/formatDateTime helpers removed
  OBCE mapper: clean (no temporal coercion)
  MWCE proof: cutover_date normalised via canonical helper

DB-authoritative write paths:
  Reconciliation timestamp: DB now() in SQL
  Dividend payment/void dates: DB now()::date::text via client
  Director loan S455 reference date: DB now()::date::text via client
  Opening balance cutover default: date_trunc expression via client

Constraint coverage:
  75/75 constraints mapped — CI gate green
  3 document sequence constraints added as kernel invariant faults

Adversarial review:
  PASS / Production Gold — axiom registry and mapper files
  Two corrections required and applied:
    Milestone M4 deliverables narrowed to delivered axioms only
    AX-TMP-001 proof anchor layer classification corrected
      (compiler: db-types.ts, runtime: normalisation module + mapper sweep)
  Proof anchor ref widened to include lib/mappers/ after invoice mapper sweep

Architectural Context

M4 Provenance is a three-axiom sequence. AX-CON-001 (Sequential Identifier Correctness, v5.19.0) closed the concurrency gap in document number generation. AX-TMP-001 (this release) closes the temporal determinism gap at the DB-to-API boundary. The remaining M4 work covers AX-CON-002 (Idempotency Contract) and the runtime boundary axioms, both of which depend on the stable temporal representation established here.

M5 (Schema-Derived Categorical Boundary) requires M4 to be complete before its import subsystem proofs can be stated with precision. A schema whose timestamps are non-deterministic cannot make closure claims about import conservation.


Operational Impact

  • Zero application-clock contamination in any financial write path
  • Deterministic timestamp representation across all environments and timezones
  • Compile-time rejection of Date objects at the database row boundary
  • Single normalisation function for each temporal column type — no mapper divergence
  • Full database constraint coverage in the error codec — no raw SQL errors reachable by clients
  • Axiom registry CI gate passes clean: 18 delivered axioms, 0 violations

Kernel Status

MilestoneDescriptionStatus
M1Tenant IsolationComplete
M2Financial Immutability and State MachinesComplete
M3Monetary Domain MigrationComplete
M4ProvenanceActive — AX-CON-001, AX-TMP-001 delivered
M5Schema-Derived Categorical BoundaryPending
M6Append-Only Cryptographic LedgerPending

Files Changed

Backend:

  • Canonical temporal normalisation module — three normaliser helpers covering all temporal column types
  • Database row type definitions — all date and timestamp fields typed as string
  • Route mapper sweep — all temporal mapper calls updated to canonical helpers
  • Invoice mapper — local date formatting helpers removed, canonical helpers adopted
  • Financial write paths — application-clock sources replaced with DB-side now() expressions
  • MWCE proof module — cutover date normalised via canonical helper
  • Error boundary codec — three document sequence constraint entries added as kernel invariant faults
  • Axiom registry v1.7 — AX-TMP-001 delivered, M4 milestone deliverables updated, proof anchors corrected

M4 Provenance continues with AX-CON-002 Idempotency Contract, establishing single-winner claim semantics for all financial mutation endpoints.