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

## Affected modules
- `src/infrastructure/db` (new): pool factory + migration runner entry.

## Modules touched
- `src/infrastructure/db`
- `project/migrations/` (new folder, SQL files)
- `project/docker-compose.yml` (new)

## Modules NOT touched
- All business modules. No HTTP API change. No shared/ change.

## New interfaces
- `src/infrastructure/db/pool.ts`: `createPoolFromEnv(): Pool` (fail fast if `DATABASE_URL` missing), `query(text, params)` helper typed over pg.
- npm scripts: `db:up`, `db:down`, `db:status`, `docker:up`, `docker:down`, `test:integration`.

## API changes
- None.

## Database changes
- Baseline migration `001_baseline.js` (node-pg-migrate, pgm.sql):
  - `CREATE EXTENSION IF NOT EXISTS citext;`
  - `CREATE EXTENSION IF NOT EXISTS pgcrypto;`
  - `CREATE TABLE app_meta (key text PRIMARY KEY, value text NOT NULL, updated_at timestamptz NOT NULL DEFAULT now());`
  - Down: drop table, drop extensions.
- Migration version tracking table owned by the migration tool.

## Naming convention (documented rule)
```text
<module>_<table>        e.g. catalog_products, inventory_stock, orders_orders
<module>_<table>_id_seq sequences owned by their table
```
- A module never queries tables without its own prefix.
- Migrations are immutable once merged; fixes ship as new migrations.

## Events
- None.

## External integrations
- Docker Compose: postgres:16-alpine (port 5432), redis:7-alpine (port 6379), named volumes.

## Cache changes
- None (Redis present but unused until cache ticket).

## Security considerations
- Compose credentials are dev-only and public by design; documented as such.
- `.env.example` committed, real `.env` gitignored.
- No secrets in migration files.

## Toolchain decisions
- Migration tool: **node-pg-migrate** (pure npm dependency, battle-tested, SQL-first via `pgm.sql`, supports up/down and dry-run). Rejected: hand-rolled runner (reinvention), Flyway/golang-migrate (foreign toolchains for a Node monolith).
- Driver: **pg** (standard).
- Env loading: Node 22 `--env-file` flag; no dotenv dependency.

## Test strategy
- Integration tests (`*.itest.ts`) run only when `TEST_DATABASE_URL` is set: `describe.skipIf` — explicit, no magic.
- They verify: fresh up creates schema; second up is no-op; down reverts cleanly; `app_meta` usable via pool helper.

## Migration strategy
- Greenfield; compose starts empty volume, migrations run from zero.

## Rollback strategy
- `db:down` reverts last migration; compose volumes can be removed with `docker compose down -v`.
