utils/logger.cts — console-compatible log.{debug,info,warn,error} with:
- level gating (LOG_LEVEL; debug in dev / info in prod), and
- REDACTION of secrets/PII (sensitive keys + bearer/JWT) so tokens,
passwords, and encrypted values never reach the logs (financial app).
Backend is abstracted (console now, swappable to pino later).
Swept all 317 server-runtime console.* across 35 files → log.* + inserted
the import at correct scope. tests/logger.test.js pins the redaction
contract (4 tests). CODE_STANDARDS updated; raw console.* is now banned in
server code.
Verified: check:server + typecheck:server + lint clean; 240/240 server
tests; client 50/50; build; e2e probe 17/17; boot smoke logs flow through
the logger and graceful shutdown intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
utils/env.cts `validateEnv()` runs first in main(): exits 1 on clearly
invalid config (non-integer PORT / SESSION_CLEANUP_INTERVAL_MS, too-short
TOKEN_ENCRYPTION_KEY), and warns loudly in production when
TOKEN_ENCRYPTION_KEY is unset (secrets fall back to a DB-stored key).
Resolves the standing prod-hardening TODO. Verified: bad PORT → exit 1;
prod without key → warns but boots (health 200).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Establishes the canonical route error pattern (see docs/CODE_STANDARDS.md):
handlers `throw ValidationError/NotFoundError/ConflictError(...)` and let
Express 5 forward to the single terminal handler — no per-handler
try/catch swallows, no ad-hoc `res.status().json({error})`. Only the
UNIQUE-constraint → 409 mapping keeps a tight try/catch (`asConflict`).
- apiError factories NotFoundError/ConflictError now take an optional
`field` (parity with ValidationError) so form-field info is preserved.
- routes/categories.cts fully converted; identical status/messages, now
uniform `{ error, message, code, field }` bodies.
- Test harnesses (categoryGroups, categoryReorder) updated to simulate the
terminal handler (thrown ApiError → formatted response).
Verified: 236/236 server tests; e2e probe 17/17; live smoke confirms
400 VALIDATION_ERROR / 404 NOT_FOUND with correct field + status.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>