Files
accounted/lib/invariants
Jakob Wennberg f266c386f3 chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers (#2150)
* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers

Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n
namespaces and 4 unused dependencies; fold byte-identical helper copies
into one canonical home each (lib/utils chunk/sleep/utcDateStamp,
lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format,
lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body +
v1ValidationError rolled out to ~55 v1 routes, booking-template schemas).

No behaviour change: v1 bodies and status codes, MCP tool schemas, DB
writes and money math are untouched. Naive ore rounding was deliberately
not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list
of things left alone on purpose.

tsc, lint, 19588 unit tests and check:guards green; antipattern baseline
ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(transactions): import RawTransaction from @/types after the ingest re-export removal

CI's type ratchet (check:types, full tsconfig) caught the one test file
that still imported the type through lib/transactions/ingest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 11:51:16 +02:00
..

lib/invariants

Shared product contracts: the formats and bounds that more than one consumer must agree on, with the reason for each rule recorded next to it.

What belongs here

A rule belongs here only when consumers outside the owning module need the identical invariant to validate input or to generate compatible output.

Our consumers are the web app, the public /api/v1 surface, the MCP server, the SIE importer and exporter, and the Skatteverket-bound generators (AGI, KU10, SRU, iXBRL). When two of those disagree about what a valid value looks like, the disagreement is invisible until a customer's filing fails.

What does not belong here

  • Rules specific to one module. They stay in that module.
  • General helpers. lib/utils.ts, lib/money.ts.
  • Database questions. "Is this account in the company's chart?" is lib/bookkeeping/account-validation.ts, not a format rule.
  • Business rules. "Is this fiscal year open?" is a period question.

Naming

The name must make the owning concept explicit. isAccountNumber, not isValidNumber. Two rules in here share a byte-identical regex (account-number and fiscal-year are both /^\d{4}$/) and mean entirely different things: the names are the only thing keeping a call site honest, so they carry the weight.

Layout

File Owns
org-number.ts Swedish organisationsnummer / personnummer: canonical 10-digit form, Luhn, Skatteverket 12-digit conversion
account-number.ts BAS account number format (4 digits, always a string)
iso-date.ts YYYY-MM-DD shape, plus a real-calendar-date check
fiscal-year.ts Four-digit räkenskapsår key
zod.ts Zod primitives built from the rules above, for API schemas

zod.ts is separate so that consumers which do not use Zod (the MCP server, report generators) can import a rule without pulling the dependency in.

Adding a rule

  1. Write the rule and the reason in the docblock. A rule without a recorded reason gets re-litigated or worked around within a quarter.
  2. Export a pure validator (primitives in, boolean or null out).
  3. Add the Zod primitive to zod.ts if an API surface needs it.
  4. Replace the hand-rolled copies. npm run check:guards tracks how many remain and fails if the count goes up.