# Implementer — F-004 Typed config and feature flags

done -> work/artifacts/F-004/implementer.md

## Files created
- src/infrastructure/config/config.ts (loadConfig pure over env object; ConfigError accumulates all problems; names var names only)
- src/infrastructure/config/tests/config.test.ts (8 tests)
- src/modules/flags/domain/feature-flag-store.ts (FeatureFlagProvider + InMemoryFeatureFlagStore + createFlagStore)
- src/modules/flags/index.ts (module public API)
- src/modules/flags/tests/feature-flag-store.test.ts (5 tests)
- src/app/tests/config-flags.test.ts (2 composition tests: default OFF, runtime flip without redeploy)

## Files modified
- src/app/build-app.ts: optional flags dep; `app.flags` decorated on the instance (typed via module augmentation)
- src/infrastructure/http/server.ts: loadConfig fail-fast before anything starts; typed port/host/logLevel; flags seeded from FLAG_* env
- .env.example: REDIS_URL + FLAG_EXAMPLE_FEATURE example
- README.md: Configuration section

## API changes
- Server now REQUIRES DATABASE_URL at startup (fail-fast; intentional per spec).

## Bug found by tests during build (fixed before gates)
- Flag store constructor did not lowercase keys while isEnabled lowercased lookups -> case-insensitive seed test failed. Fixed by normalizing keys in the constructor. Regression test retained.

## Tests passed (evidence)
```
npm run lint            -> OK
npm run lint:boundaries -> Boundary check OK: 22 file(s) checked (flags module imports nothing outside its subtree)
npm run typecheck       -> exit 0
npm run build           -> exit 0
npm test                -> 9 files passed, 2 skipped (integration without DB); 38 passed | 6 skipped
npm run test:integration-> 2 files, 6 passed (no regression)
live smoke:
  env -u DATABASE_URL node dist/.../server.js -> "Invalid configuration:\n- DATABASE_URL is required", exit 1 (AC1)
  with DATABASE_URL + FLAG_EXAMPLE_FEATURE=true -> health=200, "HTTP server listening"
./scripts/verify.sh     -> exit 0
```

## Known limitations
- No admin endpoint to flip flags yet (scope out); store API is runtime-mutable so the first consumer only adds transport.
- Flags live in memory: restart reseeds from env (documented as expected; persistence is a later ticket if needed).

## Follow-up work
- F-005 identity consumes config.databaseUrl through the pool and can guard risky paths via app.flags.
