- formatError() now emits `code` (the field the client reads) and hides
internals on any 5xx (missing status treated as 500). Single body shape
everywhere: { error, message, code, field? }.
- The terminal error handler now delegates to formatError, so a thrown
ApiError produces its real status + message + code (previously every
error collapsed to a generic "Internal server error"). Only 5xx are
logged/recorded; 4xx client errors no longer spam the error tracker.
This unblocks the Express-5 `throw ApiError` route pattern.
- Resilience: added `uncaughtException` (record + exit for clean restart),
enriched `unhandledRejection` (record, don't crash), and a SIGTERM/SIGINT
graceful shutdown that drains connections, checkpoints the WAL, and
closes the DB — verified: kill -TERM → "database closed cleanly" + exit.
check:server + typecheck:server clean; 232/232 tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch of low-blast-radius modules: utils/apiError, and services statusRuntime,
loginFingerprint, auditService, transactionMatchState, updateCheckService,
advisoryFilterService, bankSyncConfigService.
- types/http.d.ts: shared permissive Express-ish Req/Res/Next/Router types
(@types/express isn't installed; handlers are dynamic) — for apiError's
errorHandler now and the routes/middleware batches next.
- Every require of a migrated module updated to the explicit .cts extension.
- Confirmed + documented the Node rule: a .cts needs at least one `import`
statement or type-stripping never activates (node --check fails on the first
type token); import-less modules lead with an `import type`.
Behavior-preserving. Verified: typecheck:server 0, check:server 0, suite 226/226.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dates.mts (ESM, typed) joins money.mts — the two most-required server
leaves are now TypeScript. Added .cts to the server tsconfig for the
CJS-module migration path. typecheck:server clean; suite 225 + probe 17/17.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Establishes the server-TypeScript foundation and migrates utils/money →
utils/money.mts with branded Cents/Dollars (mirroring the client), so the
cents↔dollars boundary — the origin of the reconciliation bug class — is a
compile error for any typed server caller.
Approach (no build step): Node 25 runs .ts natively (type-stripping).
Migrated modules use the explicit-ESM .mts extension (the project is
"type":"commonjs", which disables .ts ESM auto-detection) and are required
from the CJS callers via Node's require(esm) — verified working. Infra:
tsconfig.server.json (allowJs + checkJs:false → incremental like the
client), npm run typecheck:server, check:server extended to .mts, wired
into ci. 28 require sites repointed to money.mts.
typecheck:server clean; full server suite 225 + e2e probe 17/17 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- utils/money: toCents rounds off the shortest decimal string instead of
Math.round(n*100), so 1.005 -> 101 (not 100). Output is identical for all
integer / <=2-decimal / "$1,234.56" inputs, so no downstream change (QA-B7-01)
- add tests/money.test.js (9 tests; the money core previously had none)
- docs: archive QA-B7-01 to HISTORY v0.41.0; QA cycle 1 now 0 open findings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>