# Implementer — F-002 Database foundation with module-owned schemas

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

## Files created (all under project/ unless noted)
- docker-compose.yml (postgres:16-alpine + redis:7-alpine, named volumes, healthchecks, dev-only creds)
- .env.example
- migrations/001_baseline.js (node-pg-migrate; citext + pgcrypto extensions, app_meta table, with down)
- src/infrastructure/db/pool.ts (createPoolFromEnv fail-fast, typed query helper)
- src/infrastructure/db/tests/db-test-support.ts (recreateDatabase, runMigrations, tableExists helpers; strict identifier validation for DDL-interpolated db name)
- src/infrastructure/db/tests/db-test-support.test.ts (pure helper tests incl. identifier rejection)
- src/infrastructure/db/tests/migrations.itest.ts
- src/infrastructure/db/tests/pool.itest.ts
- scripts/db-status.mjs

## Files modified
- package.json: deps (pg, node-pg-migrate; @types/pg dev) + scripts (test:integration, docker:up/down, db:up/down/status)
- vitest.config.ts: include *.itest.ts; fileParallelism false (integration files share one test DB)
- .gitignore: .env
- README.md: Database section + table naming convention
- spec/tech.md: dependency justification rows for pg / node-pg-migrate / @types/pg
- specs/F-002-database-foundation/DESIGN.md + TASKS.md: migration filename corrected to .js (node-pg-migrate native format)

## Database migrations
- 001_baseline: extensions citext + pgcrypto, table app_meta(key, value, updated_at). Down fully reverts.
- Tracking table: pgmigrations (owned by node-pg-migrate).

## API changes
- None.

## Tests added
- migrations.itest.ts: fresh up creates schema / second up is no-op / down rolls back cleanly
- pool.itest.ts: SELECT 1 roundtrip / app_meta insert-read-delete via query helper / fail fast without DATABASE_URL

## Tests passed (evidence)
```
npm run docker:up        -> mdv-dev-postgres Healthy, mdv-dev-redis Healthy
npm run db:up (1st)      -> MIGRATION 001_baseline (UP), Migrations complete!
npm run db:up (2nd)      -> No migrations to run! (no-op)
npm run db:status        -> 001_baseline listed as applied
npm run db:down          -> MIGRATION 001_baseline (DOWN); psql to_regclass('public.app_meta') -> empty
npm run db:up (again)    -> re-applies cleanly
npm run lint             -> OK
npm run lint:boundaries  -> Boundary check OK: 11 file(s) checked
npm run typecheck        -> exit 0
npm run build            -> exit 0
npm test                 -> 4 files passed, 2 integration files skipped explicitly without TEST_DATABASE_URL (11 passed | 6 skipped)
npm run test:integration -> Test Files 2 passed (2), Tests 6 passed (6) against real PostgreSQL 16
docker exec mdv-dev-redis redis-cli ping -> PONG
```

## Known limitations
- node-pg-migrate programmatic runner requires explicit migrationsTable (passed 'pgmigrations' in the test helper to match CLI default).
- Integration tests drop/create the shared test database; vitest fileParallelism disabled to avoid the race (documented in vitest.config.ts).
- No connection retry/backoff yet; not needed while only dev scripts connect.

## Follow-up work
- F-004 will centralize env/config (current fail-fast reader stays until then).
- First real module tables arrive with F-005+ following <module>_<table> convention.

## Security hardening round (requested by security gate)
- db-test-support.ts now validates the database name from TEST_DATABASE_URL against /^[a-zA-Z_][a-zA-Z0-9_]*$/ before interpolating it into DROP/CREATE DATABASE DDL; rejection covered by db-test-support.test.ts.
- Re-verified: lint, typecheck, npm test (11 passed), test:integration (6 passed).
