v5.17.0 6 March 2026 Improvement Fix

Milestone 3 Complete — Monetary Domain Migration

Kernel Milestone: 3 of 6 Previous: Milestone 2 — Financial Immutability and State Machines (v5.15.0) Next: Milestone 4 — Provenance

System Impact
-------------
Ledger integrity:         strengthened
Ledger mutation:          unchanged — no posted records modified
Tenant isolation:         unchanged
Financial immutability:   unchanged
State machine:            unchanged
Breaking changes:         none
Replay determinism:       preserved
Invariant Change
----------------
VAT polarity is now enforced at database level: a non-zero VAT component
must share the sign of its net amount. All monetary arithmetic across
financial routes operates exclusively in integer pence, eliminating
floating-point intermediates and unit-conversion errors introduced by
the M3 schema migration.

Why This Matters

The M3 schema migration moved monetary storage from decimal pounds to integer pence across the financial kernel. That migration was correct at the database layer, but several application-layer arithmetic paths were authored before M3 landed and continued treating pence values as if they were decimal pounds, applying conversions that were no longer appropriate. The result was monetary values inflated by a factor of one hundred in ledger endpoints, dividend calculations, and PDF generation — silent errors that produced no exceptions but delivered incorrect figures to any user who saw them.

This release corrects all identified sites, extracts VAT arithmetic into the canonical monetary module so no route layer can implement its own calculation path, and closes the final M3 enforcement gap by adding a database constraint that makes sign-inconsistent VAT states structurally impossible. SpeyBooks now enforces the pence domain end-to-end: from database storage through arithmetic to API response, with no floating-point intermediates and no alternate calculation paths.


Monetary Correctness Fixes

Dividend profit sufficiency (P1)

  • Retained earnings calculated 100x too large The profit sufficiency check underpinning dividend approvals read a pence aggregate from the database and multiplied it by one hundred, treating an already-converted value as if it still required conversion. Every dividend approval or rejection produced since the M3 migration was arithmetically incorrect. Corrected to integer parsing with no scaling. The legal significance of this path — dividend approvals are a director obligation — justified P1 classification.

PDF invoice rendering (P1)

  • All monetary values rendered 100x too large The PDF generation service applied a formatting function that divided by one hundred to all monetary values, but the function was receiving values that had not yet been divided. The net effect was that every PDF invoice showed figures one hundred times their correct value. Resolved by separating the formatting path for pence-domain values from the path for rate and unit-price values, which remain in their original representation.

Transaction ledger endpoint (P2)

  • Ledger balances, debits, credits, and running totals inflated 100x The account ledger endpoint applied a decimal-to-pence conversion to values that were already integer pence following the M3 migration. Opening balance, all per-entry debit and credit columns, and the closing balance were all affected. Corrected to integer arithmetic throughout, removing the decimal library dependency from this path entirely.

  • Transaction list totals inflated 100x Aggregate debit and credit totals on the transaction list endpoint were subject to the same conversion error. Corrected to integer parsing.

  • Transaction line detail inflated 100x Individual line amounts and VAT amounts on the transaction detail endpoint were affected by the same pattern. Corrected.

VAT arithmetic centralisation (architectural)

  • VAT calculation moved out of the route layer VAT was computed inline inside the transaction creation route using ad hoc arithmetic. This violated the architectural rule that all monetary calculation must occur in the canonical monetary module. Two functions have been added to that module covering exclusive and inclusive VAT treatment, both implemented in integer arithmetic with half-up rounding and no floating-point intermediates. The route now calls these functions directly. Enforced by: architectural boundary rule — no monetary arithmetic in routes. Proof: monetary module addition, migration_075 session.

Suspense line parsing

  • Float coercion on integer pence column The category suggestion endpoint parsed a pence-domain value using floating-point coercion. Corrected to integer parsing. No financial output was persisted from this path, but the domain assertion was incorrect.

VAT Sign Constraint

The final M3 enforcement gap: VAT polarity was previously guaranteed only by application logic. A non-zero VAT amount must share the sign of its net amount — positive on invoice lines, negative on credit note lines. Without a database constraint, nothing prevented a code path from writing a structurally impossible combination.

A CHECK constraint now enforces this at the database layer. The valid states are: positive VAT with positive net (standard invoice line), negative VAT with negative net (credit note line), or zero VAT for any net (zero-rated or exempt supply). Any write that violates this invariant is rejected by the database regardless of application behaviour.

Pre-flight verification confirmed zero pre-existing violations across all tenants before the constraint was installed. Validation was performed against the application role to ensure RLS-scoped visibility was clean, not only the superuser view.

Enforced by: database CHECK constraint — Class M. Proof: migration_075.


Threat Closure

ThreatStatus BeforeStatus AfterEnforcement Layer
VAT polarity inconsistency (positive VAT on credit note lines)Open — application-level onlyClosed (Class M)Database CHECK constraint
Floating-point intermediates in VAT calculationOpen — inline route arithmeticClosed (Class M)Canonical monetary module, integer arithmetic only
Pence-domain values re-converted through decimal transformation layerOpen — post-M3 regressionClosedInteger parsing at all affected sites

Security Posture Change

VAT sign consistency moves from Class O (application-level enforcement — bypassable by any write path that bypasses the service layer) to Class M (database constraint — enforced unconditionally on every insert and update regardless of origin). This closes a bypass vector that existed for any bulk import, migration, or direct write that did not route through the invoice service.


Verification Record

Pre-flight:
  2/2 checks clean
  migration 074 present and confirmed as latest applied
  zero sign-violating rows across all tenants (verified as application role)

Migration:
  COMMIT — clean
  All 3 guard blocks passed (baseline, data cleanliness, idempotency)

Post-commit:
  V1  constraint present, convalidated = true, definition matches invariant
  V2  zero violations visible to application role under RLS
  V3  migration recorded at 2026-03-06 20:56:26 UTC

Adversarial review:
  Migration 075 reviewed independently on pre-flight query, guard logic,
  constraint semantics, lock strategy, and role separation

Architectural Context

M3 established the integer pence domain at the database layer. This release closes the application-layer gap: every route that reads monetary data from the database now interprets it correctly as integer pence. M4 (Provenance) can now build on a foundation where monetary values have a single, unambiguous representation end-to-end. Without M3 completion, provenance chains anchoring financial events would be computed against inflated values and produce incorrect audit trails.


Operational Impact

  • Dividend profit sufficiency checks now produce arithmetically correct results
  • PDF invoices render correct monetary values
  • Account ledger balances, running totals, and transaction summaries are correct
  • VAT calculation is centralised — no route implements its own arithmetic path
  • Sign-inconsistent VAT states are structurally impossible to write
  • Floating-point intermediates eliminated from all affected monetary paths

Kernel Status

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

Files Changed

Backend:

  • api/db/migrations/075-vat-sign-constraint.sql — VAT sign CHECK constraint with two-phase locking, pre-flight guard, and post-commit verification queries
  • api/src/routes/transactions.ts — corrected monetary interpretation at six sites; removed decimal library dependency from ledger path; VAT calculation replaced with canonical helpers; float coercion on pence column corrected
  • api/src/routes/dividends.ts — corrected retained earnings aggregate interpretation; float-with-scaling replaced with integer parsing at both calculation sites
  • api/src/services/pdf-service.ts — separated pence-domain formatting from rate-domain formatting; corrected monetary rendering for all invoice value fields
  • api/src/lib/api-amounts.ts — added canonical VAT helpers for exclusive and inclusive treatment, both in integer arithmetic

M4 (Provenance) begins with a complete and arithmetically correct monetary foundation.