# 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 _ 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).