# 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 1. **Config loader lives in `infrastructure/config`** and is a pure function over an env object. No reads of `process.env` inside the loader → deterministic unit tests. It accumulates every problem and throws ONE `ConfigError` (fail fast AND clear). It names var NAMES only — never values — so secrets cannot leak into error messages. 2. **Feature flags are a real module (`src/modules/flags`)** behind a `FeatureFlagProvider` interface with an in-memory store. Unknown flags default to OFF (fail-safe). `setEnabled` mutates live state → activation ≠ deployment, no redeploy. 3. **Inversion to keep the module clean:** `infrastructure/config` parses `FLAG_*` 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. 4. **buildApp gains an optional `flags` dep**, decorated onto the instance (`app.flags`), establishing the wiring point future risky routes will consume. Default: empty store → everything off. 5. **No new dependencies.** Hand-rolled validation is small and boring. ## Boundaries - `flags` module 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.