- loadConfig: pure over env object, accumulates all problems, names var names only - DATABASE_URL now required at startup; PORT/HOST/LOG_LEVEL/NODE_ENV/REDIS_URL defaulted - flags module behind FeatureFlagProvider; unknown flags OFF; runtime setEnabled (no redeploy) - buildApp decorates app.flags; server.ts fail-fast before app boot - tests caught and fixed flag-store case-normalization bug before gates - zero new dependencies; all gates approved; verify.sh green
1.7 KiB
1.7 KiB
Architect — F-004 Typed config and feature flags
done -> work/artifacts/F-004/architect.md
Deliverables
- specs/F-004-config-flags/SPEC.md
- specs/F-004-config-flags/DESIGN.md
- specs/F-004-config-flags/TASKS.md
- specs/F-004-config-flags/TESTS.md
Key decisions
- Config loader lives in
infrastructure/configand is a pure function over an env object. No reads ofprocess.envinside the loader → deterministic unit tests. It accumulates every problem and throws ONEConfigError(fail fast AND clear). It names var NAMES only — never values — so secrets cannot leak into error messages. - Feature flags are a real module (
src/modules/flags) behind aFeatureFlagProviderinterface with an in-memory store. Unknown flags default to OFF (fail-safe).setEnabledmutates live state → activation ≠ deployment, no redeploy. - Inversion to keep the module clean:
infrastructure/configparsesFLAG_*into a plain record and the composition root hands it to the flags store. The flags module never imports config. The boundary checker enforces this. - buildApp gains an optional
flagsdep, decorated onto the instance (app.flags), establishing the wiring point future risky routes will consume. Default: empty store → everything off. - No new dependencies. Hand-rolled validation is small and boring.
Boundaries
flagsmodule imports nothing outside its subtree (no shared needed). Config → flags only via composition root. server.ts pulls config + passes flags into buildApp.
Risks
- server now requires DATABASE_URL to start (intentional fail-fast). Smoke/QA must pass it.
- ConfigError must not echo values. Covered by name-only messages + test.