v5.16.2 6 March 2026 Fix

Kernel Defect Closure — Authentication Restored, Invoice Preview Corrected

Why This Matters

A hardening migration that applied row-level security to the wrong table produced a silent deny-all on authentication — every login attempt returned INVALID_CREDENTIALS on service restart, with no error in application logs. This is the class of defect that a mechanical pre-flight system exists to catch: an invariant applied correctly in principle but incorrectly scoped. The fix is a single DDL statement; the forensic trail is a formal migration with pre-flight, post-flight, and a schema record. A second defect — invoice preview arithmetic diverging from the canonical calculation path — was identified and corrected in the same session.

Both fixes strengthen the audit posture: the authentication defect is now formally documented in the migration chain rather than silently remediated, and the invoice preview endpoint now produces figures that are guaranteed to match the values stored when an invoice is created.


Defect A — Authentication Failure on Service Restart (Migration 074)

Root Cause

Migration 071 (Milestone 3, PHASE 0) applied ENABLE ROW LEVEL SECURITY and FORCE ROW LEVEL SECURITY to the users table as a gap closure for tenant isolation. This was architecturally incorrect. The users table is a cross-tenant identity store — users are resolved by the authentication layer before any organisation context is established, and no tenant session context is available at that point. Row-level security with no policies denies all rows for application roles. Every authentication query returned zero rows.

The defect remained latent while existing pooled connections were active. It became fully visible on service restart when new connections executed the authentication query under the application role.

Fix

Row-level security disabled on the users table — migration 074. Sessions and password reset tokens, which are also cross-tenant by the same architectural argument, were verified clean as part of the same migration.

The correct architectural boundary: tenant-scoped tables (all financial data, audit records, import state) carry RLS enforced by app.current_org_id. Cross-tenant identity tables (users, sessions, password reset tokens) sit outside the RLS envelope because authentication occurs before tenant context is established.

Enforcement: Migration 074 — pre-flight confirmed defect present, post-flight confirmed RLS disabled, self-recorded in schema_migrations.

Connection Search Path Hardening

The database role’s default search_path was hardened at the PostgreSQL level. This ensures the correct schema is active for every new connection even if the application connection pool hook is bypassed or fails to execute. The pool hook remains in place, but is no longer a load-bearing control.


Defect B — Invoice Preview Arithmetic Divergence

Root Cause

The invoice preview endpoint — which calculates line totals, VAT, and invoice totals for real-time UI display without persisting anything — used a different arithmetic path than the invoice creation service. Specifically:

  • Preview used native JavaScript integer multiplication and Math.round for VAT
  • The invoice service uses Decimal.js with global ROUND_HALF_UP configuration, operating in pounds-space after converting from pence

The two paths produce the same result for most inputs. They diverge at rounding boundaries: any line where VAT at the applied rate produces a half-penny value would display one figure in the preview and store a different (correct) figure on save. A user creating an invoice would see a total that didn’t match the saved record.

Fix

The preview calculation now uses the identical arithmetic path as the invoice creation service, which implements the ADR-003 monetary kernel (Decimal arithmetic, ROUND_HALF_UP, and pence-domain conversion). The preview is now guaranteed to produce the same figures that will be stored.

This also closes an AX-RND-006 violation — inline monetary arithmetic outside the canonical service computation path.

Axiom: AX-RND-001 (canonical rounding mode), AX-RND-006 (no alternate arithmetic path).


Threat Closure

ThreatStatus BeforeStatus AfterEnforcement Layer
Authentication failure on service restart due to RLS on cross-tenant tableActive defect — complete auth denialClosedusers table excluded from RLS envelope — migration 074
Invoice preview figures diverging from stored values at rounding boundariesOpen — divergence possible at 0.5p VAT valuesClosedCanonical Decimal arithmetic path — AX-RND-001, AX-RND-006
Connection pool search path load-bearing on startupClass O — race condition on fresh connectionsMitigatedRole-level search_path default — defence in depth

Verification Record

Migration 074 pre-flight:
  PF-1  Baseline confirmed: 073-schema-migrations-bootstrap.sql
  PF-2  RLS confirmed enabled on users table (defect present)
  PF-3  No policies on users table (deny-all confirmed)

Migration 074 post-flight:
  RLS confirmed disabled on users table
  sessions: RLS already disabled — no action required
  password_reset_tokens: RLS already disabled — no action required
  Self-recorded in migration tracking table

Invoice preview fix:
  Decimal import added to invoices route
  Arithmetic path verified to match invoice-service.ts exactly
  Boundary case confirmed: 0.5p VAT values now round correctly under ROUND_HALF_UP

Files Changed

Database migrations:

  • api/db/migrations/074-users-rls-defect.sql — disable RLS on cross-tenant identity tables, formal pre/post-flight verification

Backend:

  • api/src/routes/invoices.ts — invoice preview endpoint rewritten to use canonical Decimal arithmetic path matching invoice-service.ts