PER-7

Money persistence as bigint integer cents

Origin

Commit 4d07fe2 (money-column migration to bigint). The prior integer type silently overflowed on asset values above $21,474,836.47 — a threshold crossed routinely by luxury residential, commercial, and institutional real-estate records. The migration aligned the Zod input bound to JavaScript’s Number.MAX_SAFE_INTEGER to prevent precision loss at the API boundary.

Rule Text

Money columns MUST be PostgreSQL bigint with Zod input validator z.number().int().min(0).max(Number.MAX_SAFE_INTEGER). The types integer, numeric, and real — and any floating-point representation — are FORBIDDEN. Currency values are stored as integer cents.

Testable Assertion

expect(result.valueCents).toBe(9_007_199_254_740_991); // round-trips through the DB
expect(() => create({ valueCents: Number.MAX_SAFE_INTEGER + 1 })).rejects.toThrow();

Enforcement

  • Gate-time — Static-analysis rule forbids integer on money columns; forbids parseFloat and toFixed on money values; forbids floating-point arithmetic on money variables.
  • Runtime — Zod bound rejects over-range input at the procedure entry, before any database interaction.

Violation Closed

Silent overflow on any asset valued above the int32 ceiling. The failure mode is invisible at small scales — most records fit — and catastrophic at production scale: a single corrupted valuation destroys a derived report, a tax filing, or a portfolio summary. The bound is aligned to JavaScript’s safe-integer ceiling rather than PostgreSQL’s bigint ceiling, to prevent downstream precision loss when values cross the API boundary.