---
phase: 01-infrastructure-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - docker-compose.yml
  - docker/postgres/initdb.d/01-init-users.sql
  - .env.example
  - .gitignore
  - backend/requirements.txt
  - backend/config.py
autonomous: true
requirements:
  - STORE-01
  - STORE-07
user_setup: []
tags:
  - infrastructure
  - docker-compose
  - postgresql
  - minio
  - redis
  - celery
  - pydantic-settings

must_haves:
  truths:
    - "Running `docker compose config` against the new compose file succeeds with no validation errors"
    - "`.env.example` lists every variable referenced by `docker-compose.yml` via `${VAR}` interpolation"
    - "`backend/config.py` exposes a Pydantic `Settings` instance reading `database_url`, `database_migrate_url`, `minio_endpoint`, `minio_access_key`, `minio_secret_key`, `minio_bucket`, `redis_url`, `secret_key` from environment"
    - "`backend/requirements.txt` declares SQLAlchemy async, psycopg v3, Alembic, MinIO SDK, Celery+Redis, redis, aiosqlite for tests; `filelock` is removed"
    - "`docker/postgres/initdb.d/01-init-users.sql` creates both `docuvault_app` and `docuvault_migrate` users with correct CONNECT grant"
  artifacts:
    - path: "docker-compose.yml"
      provides: "postgres, minio, redis, celery-worker services with healthchecks and depends_on conditions"
      contains: "postgres:17-alpine"
    - path: "docker/postgres/initdb.d/01-init-users.sql"
      provides: "docuvault_app and docuvault_migrate role provisioning"
      contains: "CREATE USER docuvault_migrate"
    - path: ".env.example"
      provides: "Documented placeholders for DATABASE_URL, DATABASE_MIGRATE_URL, MINIO_*, REDIS_URL, SECRET_KEY"
      contains: "DATABASE_MIGRATE_URL=postgresql+psycopg"
    - path: "backend/config.py"
      provides: "Pydantic Settings instance with all Phase 1 env vars"
      contains: "class Settings(BaseSettings)"
    - path: "backend/requirements.txt"
      provides: "Updated dependency manifest"
      contains: "sqlalchemy[asyncio]>=2.0"
  key_links:
    - from: "docker-compose.yml"
      to: ".env.example variables"
      via: "${VAR_NAME} interpolation"
      pattern: "\\$\\{DATABASE_URL\\}|\\$\\{REDIS_URL\\}|\\$\\{MINIO_ENDPOINT\\}"
    - from: "backend/config.py"
      to: ".env.example variables"
      via: "Pydantic Settings env_file"
      pattern: "database_url|minio_endpoint|redis_url"
    - from: "docker-compose.yml postgres service"
      to: "docker/postgres/initdb.d/01-init-users.sql"
      via: "bind-mount into /docker-entrypoint-initdb.d"
      pattern: "initdb.d:/docker-entrypoint-initdb.d"
---

<objective>
Wire the Phase 1 service topology (PostgreSQL + MinIO + Redis + celery-worker) into Docker Compose, introduce the two-user PostgreSQL init script, replace the legacy `config.py` constants with a Pydantic `Settings` class that reads every Phase 1 env var, extend `.env.example` with all new variables grouped by service, and update `backend/requirements.txt` to swap `filelock` for the SQLAlchemy / Alembic / MinIO / Celery stack.

Purpose: This is the foundation layer of the walking skeleton. Until `docker compose up` boots all five services cleanly and `Settings()` can read every Phase 1 variable, no subsequent plan can run.

Output: A new `docker-compose.yml`, a new `docker/postgres/initdb.d/01-init-users.sql`, an extended `.env.example`, an updated `backend/requirements.txt`, and a rewritten `backend/config.py`.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@CLAUDE.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-infrastructure-foundation/01-CONTEXT.md
@.planning/phases/01-infrastructure-foundation/01-RESEARCH.md
@.planning/phases/01-infrastructure-foundation/01-PATTERNS.md
@.planning/phases/01-infrastructure-foundation/SKELETON.md

<interfaces>
Key existing structures the executor must preserve:

From the current `docker-compose.yml` (only `backend` and `frontend` services exist today; backend volumes include `./backend/data:/app/data` which MUST be removed per D-04). The Compose file uses no top-level `volumes:` block yet.

From the current `backend/config.py`: module-level constants `DATA_DIR`, `UPLOADS_DIR`, `METADATA_DIR`, `TOPICS_FILE`, `SETTINGS_FILE`, `DEFAULT_SYSTEM_PROMPT`, `DEFAULT_SETTINGS`, and a function `ensure_data_dirs()`. `DEFAULT_SYSTEM_PROMPT` and `DEFAULT_SETTINGS` must be preserved verbatim because `services/storage.py`, `services/classifier.py`, and `api/settings.py` still consume them; `DATA_DIR`/`UPLOADS_DIR`/`METADATA_DIR`/`TOPICS_FILE`/`SETTINGS_FILE`/`ensure_data_dirs` will be removed by Plan 05 once `services/storage.py` is rewritten — leave them in place for now to keep the rest of the app booting between waves.

From `backend/requirements.txt`: `pydantic-settings>=2.2` is already declared (line 4) — no new install needed for that package.

Env var canon (sourced from RESEARCH.md Code Examples lines 914-937 and PATTERNS.md `.env.example` section):
- `DATABASE_URL` — `postgresql+psycopg://docuvault_app:<pw>@postgres:5432/docuvault`
- `DATABASE_MIGRATE_URL` — `postgresql+psycopg://docuvault_migrate:<pw>@postgres:5432/docuvault`
- `POSTGRES_PASSWORD` — superuser password for the init container (not used by app)
- `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` — MinIO root, init-time only
- `MINIO_ENDPOINT` — `minio:9000`
- `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` — app-level access key pair
- `MINIO_BUCKET` — value `docuvault`
- `REDIS_PASSWORD` — used by Redis `--requirepass` and inside `REDIS_URL`
- `REDIS_URL` — `redis://:<pw>@redis:6379/0`
- `SECRET_KEY` — Phase 2 JWT/HKDF placeholder; documented now, not read by Phase 1 code paths
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Replace docker-compose.yml with five-service stack and add PostgreSQL init script</name>
  <files>docker-compose.yml, docker/postgres/initdb.d/01-init-users.sql, .gitignore</files>
  <read_first>
    - docker-compose.yml (current 2-service file; remove `./backend/data:/app/data` volume per D-04)
    - .planning/phases/01-infrastructure-foundation/01-RESEARCH.md (Pattern 6: Docker Compose Health Checks; Pitfall 5: Redis healthcheck auth; Pitfall 6: MinIO mc; Pattern 7: PostgreSQL Two-User Init Script)
    - .planning/phases/01-infrastructure-foundation/01-PATTERNS.md (docker-compose.yml section — full service block source-of-truth; initdb.d section)
    - .planning/phases/01-infrastructure-foundation/01-CONTEXT.md (D-06 bucket name `docuvault`; D-09 Redis in Phase 1; D-10 celery-worker service; D-14 init script provisions both users)
  </read_first>
  <action>
    Rewrite `docker-compose.yml` to declare exactly five services — `postgres`, `minio`, `redis`, `backend`, `celery-worker` — plus the existing `frontend` service. Use image tags `postgres:17-alpine`, `minio/minio:latest`, `redis:7-alpine`. Each infrastructure service has a `healthcheck` block: postgres uses `pg_isready -U postgres -d docuvault` (interval 10s, timeout 5s, retries 5, start_period 10s); minio uses `["CMD", "mc", "ready", "local"]` (interval 10s, timeout 5s, retries 5, start_period 15s) per D-07 (NOT `curl` — removed from image); redis uses `["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]` (interval 10s, timeout 3s, retries 5) per Pitfall 5 — bare `redis-cli ping` returns NOAUTH. The `backend` service `depends_on` block uses `condition: service_healthy` for all three of postgres, minio, redis. The `celery-worker` service uses the same `build: ./backend`, has identical `depends_on`, and its `command:` is `celery -A celery_app worker --loglevel=info -Q documents` per D-10. Wire all environment variables on `backend` and `celery-worker` via `${VAR}` interpolation: `DATABASE_URL`, `DATABASE_MIGRATE_URL` (backend only — workers do not need DDL access), `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET`, `REDIS_URL`, plus `PYTHONDONTWRITEBYTECODE=1`. The postgres service has `POSTGRES_DB: docuvault`, `POSTGRES_USER: postgres`, `POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`. The minio service exposes ports `9000:9000` and `9001:9001` and runs `command: server /data --console-address ":9001"`. Add a top-level `volumes:` block declaring `postgres_data:` and `minio_data:`. Mount `./docker/postgres/initdb.d:/docker-entrypoint-initdb.d:ro` on the postgres service. **Delete** the `./backend/data:/app/data` volume from the `backend` service (D-04). Keep the existing `./backend:/app` bind mount for hot reload. Keep the existing `frontend` service unchanged. Then create the directory `docker/postgres/initdb.d/` and the file `docker/postgres/initdb.d/01-init-users.sql` per Pattern 7: it must `CREATE USER docuvault_migrate WITH PASSWORD 'changeme_migrate'`, `GRANT ALL PRIVILEGES ON DATABASE docuvault TO docuvault_migrate`, `CREATE USER docuvault_app WITH PASSWORD 'changeme_app'`, and `GRANT CONNECT ON DATABASE docuvault TO docuvault_app`. Do NOT include schema-level GRANTs or `ALTER DEFAULT PRIVILEGES` here — Plan 03 handles those inside the Alembic initial migration (Pitfall 4). Add a comment to the SQL file noting that table-level grants are issued by the Alembic migration. Finally, ensure `.env` is gitignored: read the existing `.gitignore` (create one if missing), and append a `.env` line if not present.
  </action>
  <verify>
    <automated>cd /Users/nik/Documents/Progamming/document_scanner && docker compose --env-file .env.example config -q 2>&1 | grep -v "warning" | (! grep -q "error\|invalid"); echo "compose-config-exit=$?"</automated>
  </verify>
  <acceptance_criteria>
    - `docker-compose.yml` contains exactly these top-level service keys: `postgres`, `minio`, `redis`, `backend`, `celery-worker`, `frontend` (verifiable via `grep -E "^  (postgres|minio|redis|backend|celery-worker|frontend):" docker-compose.yml | wc -l` equals 6)
    - `docker-compose.yml` contains the string `postgres:17-alpine`
    - `docker-compose.yml` contains the string `minio/minio:latest`
    - `docker-compose.yml` contains the string `redis:7-alpine`
    - `docker-compose.yml` contains the substring `mc", "ready", "local"` in the minio healthcheck (NOT `curl`)
    - `docker-compose.yml` contains the substring `redis-cli", "-a"` in the redis healthcheck
    - `docker-compose.yml` contains `pg_isready -U postgres -d docuvault` in the postgres healthcheck
    - `docker-compose.yml` contains the substring `celery -A celery_app worker` in the celery-worker service command
    - `docker-compose.yml` contains `condition: service_healthy` at least three times (one per infrastructure dependency on backend)
    - `docker-compose.yml` contains a top-level `volumes:` block with both `postgres_data:` and `minio_data:`
    - `docker-compose.yml` does NOT contain `./backend/data:/app/data` (D-04 removal verifiable via `grep -v "^#" docker-compose.yml | grep -c "backend/data" | grep -q "^0$"`)
    - `docker-compose.yml` references `${DATABASE_URL}`, `${DATABASE_MIGRATE_URL}`, `${MINIO_ENDPOINT}`, `${MINIO_ACCESS_KEY}`, `${MINIO_SECRET_KEY}`, `${MINIO_BUCKET}`, `${REDIS_URL}`, `${REDIS_PASSWORD}`, `${POSTGRES_PASSWORD}`, `${MINIO_ROOT_USER}`, `${MINIO_ROOT_PASSWORD}` (each verifiable via `grep -c "\${VAR}" docker-compose.yml >= 1`)
    - `docker compose --env-file .env.example config -q` exits 0 (no parsing errors)
    - File `docker/postgres/initdb.d/01-init-users.sql` exists
    - The SQL script contains `CREATE USER docuvault_migrate` and `CREATE USER docuvault_app`
    - The SQL script contains `GRANT ALL PRIVILEGES ON DATABASE docuvault TO docuvault_migrate`
    - The SQL script contains `GRANT CONNECT ON DATABASE docuvault TO docuvault_app`
    - The SQL script does NOT contain `GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES` (those go in the Alembic migration per Pitfall 4)
    - `.gitignore` contains a `.env` line (`grep -Fx ".env" .gitignore` exits 0)
  </acceptance_criteria>
  <done>The Compose file declares the full five-service Phase 1 stack with all health checks and dependency conditions; the PostgreSQL init script provisions both DB users on first container start; `.env` is gitignored.</done>
</task>

<task type="auto">
  <name>Task 2: Extend .env.example with all Phase 1 variables (D-11, D-13, D-15, D-16)</name>
  <files>.env.example</files>
  <read_first>
    - .env.example (current 6-line file with only ANTHROPIC_API_KEY and OPENAI_API_KEY)
    - .planning/phases/01-infrastructure-foundation/01-PATTERNS.md (`.env.example` section — full block source-of-truth)
    - .planning/phases/01-infrastructure-foundation/01-CONTEXT.md (D-11 .env.example committed; D-13 two DSNs; D-15 MinIO vars including separate access key; D-16 REDIS_URL + SECRET_KEY documented now)
    - docker/postgres/initdb.d/01-init-users.sql (Task 1 output — verify the placeholder passwords in this file match the DATABASE_URL / DATABASE_MIGRATE_URL passwords)
  </read_first>
  <action>
    Replace `.env.example` keeping the existing `ANTHROPIC_API_KEY=` and `OPENAI_API_KEY=` lines at the top, then append four new sections grouped by `# ── <Service> ───` comment headers:

    1. **PostgreSQL section**: `DATABASE_URL=postgresql+psycopg://docuvault_app:changeme_app@postgres:5432/docuvault` (with explanatory comment "App user — SELECT/INSERT/UPDATE/DELETE only, used by FastAPI + Celery"), `DATABASE_MIGRATE_URL=postgresql+psycopg://docuvault_migrate:changeme_migrate@postgres:5432/docuvault` (with comment "Migration user — DDL privileges, used ONLY by Alembic, never by the app at runtime"), `POSTGRES_PASSWORD=changeme_super` (with comment "Superuser password for the postgres init container — used only by initdb.d scripts").

    2. **MinIO section**: `MINIO_ROOT_USER=minioadmin`, `MINIO_ROOT_PASSWORD=changeme_minio_root`, `MINIO_ENDPOINT=minio:9000`, `MINIO_ACCESS_KEY=docuvault_app` (comment: "App-level access key — minimal permissions on docuvault bucket only"), `MINIO_SECRET_KEY=changeme_minio_app`, `MINIO_BUCKET=docuvault`.

    3. **Redis section**: `REDIS_PASSWORD=changeme_redis`, `REDIS_URL=redis://:changeme_redis@redis:6379/0` (comment noting it must match `REDIS_PASSWORD` and that the leading `:` is the no-username form for `requirepass`).

    4. **Security (Phase 2) section**: `SECRET_KEY=CHANGEME-replace-with-64-char-random-hex` (comment: "Not read by the app in Phase 1 — documented here for Phase 2 JWT + HKDF use"). Each value uses `changeme_*` style placeholders to make obvious which fields require replacement. The DATABASE_URL password (`changeme_app`) and DATABASE_MIGRATE_URL password (`changeme_migrate`) MUST match the hardcoded passwords in `docker/postgres/initdb.d/01-init-users.sql` from Task 1 — re-read that file to confirm. End the file with a final newline.
  </action>
  <verify>
    <automated>grep -E '^(DATABASE_URL|DATABASE_MIGRATE_URL|POSTGRES_PASSWORD|MINIO_ROOT_USER|MINIO_ROOT_PASSWORD|MINIO_ENDPOINT|MINIO_ACCESS_KEY|MINIO_SECRET_KEY|MINIO_BUCKET|REDIS_PASSWORD|REDIS_URL|SECRET_KEY|ANTHROPIC_API_KEY|OPENAI_API_KEY)=' /Users/nik/Documents/Progamming/document_scanner/.env.example | sort -u | wc -l | awk '{exit ($1 == 14) ? 0 : 1}'</automated>
  </verify>
  <acceptance_criteria>
    - `.env.example` defines exactly 14 named variables: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `DATABASE_URL`, `DATABASE_MIGRATE_URL`, `POSTGRES_PASSWORD`, `MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD`, `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET`, `REDIS_PASSWORD`, `REDIS_URL`, `SECRET_KEY` (verifiable via the Verify command)
    - `DATABASE_URL` value starts with `postgresql+psycopg://docuvault_app:` (verifiable via `grep -c "^DATABASE_URL=postgresql+psycopg://docuvault_app:" .env.example` >= 1)
    - `DATABASE_MIGRATE_URL` value starts with `postgresql+psycopg://docuvault_migrate:`
    - `MINIO_BUCKET=docuvault` exactly (D-06)
    - `REDIS_URL` value matches the form `redis://:<password>@redis:6379/0` (verifiable via `grep -E "^REDIS_URL=redis://:[^@]+@redis:6379/0$" .env.example` exits 0)
    - `MINIO_ENDPOINT=minio:9000` exactly
    - Section headers are present: `grep -c "── PostgreSQL ──" .env.example` >= 1, similarly for MinIO, Redis, Security
    - The password embedded in `DATABASE_URL` matches the password literal used in `docker/postgres/initdb.d/01-init-users.sql` for the `docuvault_app` user (re-read both files and confirm string equality of the password substring)
    - The password embedded in `DATABASE_MIGRATE_URL` matches the password literal used in `docker/postgres/initdb.d/01-init-users.sql` for the `docuvault_migrate` user
    - The password embedded in `REDIS_URL` matches `REDIS_PASSWORD` (verifiable: extract value of REDIS_PASSWORD and grep `redis://:${value}@` in REDIS_URL line)
  </acceptance_criteria>
  <done>`.env.example` documents every Phase 1 environment variable with safe placeholder values, grouped and commented by service; the DB and Redis password placeholders are consistent between `.env.example` and the Postgres init script.</done>
</task>

<task type="auto">
  <name>Task 3: Replace backend/config.py with Pydantic Settings + extend backend/requirements.txt</name>
  <files>backend/config.py, backend/requirements.txt</files>
  <read_first>
    - backend/config.py (current — preserve `DEFAULT_SYSTEM_PROMPT`, `DEFAULT_SETTINGS`, and `ensure_data_dirs()` verbatim because they are still consumed by `services/storage.py`, `services/classifier.py`, `api/settings.py` until Plan 05; rewrite the rest as a Pydantic `Settings` class)
    - backend/requirements.txt (current — `pydantic-settings>=2.2` and `httpx>=0.27` and `pytest-asyncio>=0.23` already present; add new deps, remove `filelock`)
    - .planning/phases/01-infrastructure-foundation/01-RESEARCH.md (Standard Stack section: exact pinned versions; Config Extension code example lines 914-937)
    - .planning/phases/01-infrastructure-foundation/01-PATTERNS.md (backend/config.py section — module-level Settings instance is the established consumer interface)
    - .env.example (Task 2 output — variable names must match the Settings field names lower-cased)
  </read_first>
  <action>
    Rewrite `backend/config.py` so it imports `from pydantic_settings import BaseSettings`, defines a `Settings(BaseSettings)` class with these fields (with the listed defaults — defaults are used only if env vars are unset, which keeps tests workable): `data_dir: str = "/app/data"`, `database_url: str = "postgresql+psycopg://docuvault_app:changeme_app@postgres:5432/docuvault"`, `database_migrate_url: str = "postgresql+psycopg://docuvault_migrate:changeme_migrate@postgres:5432/docuvault"`, `minio_endpoint: str = "minio:9000"`, `minio_access_key: str = "docuvault_app"`, `minio_secret_key: str = "changeme_minio_app"`, `minio_bucket: str = "docuvault"`, `redis_url: str = "redis://:changeme_redis@redis:6379/0"`, `secret_key: str = "CHANGEME"`. Inside the class, add `model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")` (Pydantic Settings v2 API — `class Config:` is deprecated). After the class, instantiate `settings = Settings()` at module level so all callers `from config import settings`. **Preserve and keep at module level for backward compatibility through Wave 4:** the existing `DATA_DIR = Path(...)`, `UPLOADS_DIR`, `METADATA_DIR`, `TOPICS_FILE`, `SETTINGS_FILE` constants; the `DEFAULT_SYSTEM_PROMPT` string; the `DEFAULT_SETTINGS` dict; and the `ensure_data_dirs()` function. Plan 05 deletes these once `services/storage.py` is rewritten. The rewrite is additive in this plan: the existing app must still boot after this change. Then update `backend/requirements.txt`: REMOVE the line `filelock>=3.14` (RESEARCH.md "State of the Art" — replaced by PostgreSQL transactions). APPEND these new lines: `sqlalchemy[asyncio]>=2.0.49`, `psycopg[binary]>=3.3.4`, `alembic>=1.18.4`, `minio>=7.2.20`, `celery[redis]>=5.6.3`, `redis>=7.4.0`, `aiosqlite>=0.20.0` (needed by Plan 02's in-memory test engine). Bump `pytest-asyncio>=1.3.0` (existing `>=0.23` no longer supports `asyncio_mode = auto` reliably with current pytest). Keep all other existing lines.
  </action>
  <verify>
    <automated>cd /Users/nik/Documents/Progamming/document_scanner/backend && python3 -c "import ast; tree = ast.parse(open('config.py').read()); names = {n.name for node in ast.walk(tree) for n in ([node] if isinstance(node, ast.ClassDef) else [])}; assert 'Settings' in names, 'Settings class missing'; print('settings-class-ok')"</automated>
  </verify>
  <acceptance_criteria>
    - `backend/config.py` contains `class Settings(BaseSettings):` (verifiable via `grep -c "class Settings(BaseSettings)" backend/config.py` >= 1)
    - `backend/config.py` contains the literal `settings = Settings()` at module level
    - `backend/config.py` contains every field name `database_url`, `database_migrate_url`, `minio_endpoint`, `minio_access_key`, `minio_secret_key`, `minio_bucket`, `redis_url`, `secret_key` (each verifiable via grep)
    - `backend/config.py` uses `SettingsConfigDict(env_file=".env"` (not the deprecated `class Config:` form) — verifiable via `grep -c "SettingsConfigDict" backend/config.py` >= 1
    - `backend/config.py` still exports the `DEFAULT_SYSTEM_PROMPT` and `DEFAULT_SETTINGS` constants and the `ensure_data_dirs` function (verifiable: `grep -c "DEFAULT_SYSTEM_PROMPT\|DEFAULT_SETTINGS\|def ensure_data_dirs" backend/config.py` >= 3)
    - `python3 -c "import sys; sys.path.insert(0, 'backend'); from config import settings; assert settings.minio_bucket == 'docuvault'; assert settings.database_url.startswith('postgresql+psycopg://'); print('config-import-ok')"` exits 0 (executed from the project root)
    - `backend/requirements.txt` no longer contains `filelock` (verifiable: `grep -c "^filelock" backend/requirements.txt | grep -q "^0$"`)
    - `backend/requirements.txt` contains each of: `sqlalchemy[asyncio]>=2.0`, `psycopg[binary]>=3.3`, `alembic>=1.18`, `minio>=7.2`, `celery[redis]>=5.6`, `redis>=7.4`, `aiosqlite>=0.20`, `pytest-asyncio>=1.3` (each verifiable via `grep -F` on the line prefix)
    - Existing lines preserved: `fastapi>=0.111`, `uvicorn[standard]>=0.29`, `python-multipart`, `pydantic-settings>=2.2`, `anthropic>=0.26`, `openai>=1.30`, `PyMuPDF>=1.24`, `python-docx>=1.1`, `pytesseract>=0.3`, `Pillow>=10.3`, `aiofiles>=23.2`, `httpx>=0.27`, `pytest>=8.2` (each verifiable via `grep -F`)
  </acceptance_criteria>
  <done>`config.settings` is a Pydantic Settings instance reading every Phase 1 env var; the legacy data-dir constants remain available for the still-running flat-file code path; `requirements.txt` declares the full Phase 1 dependency set and removes `filelock`.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Docker host → containers | Compose orchestrates services; environment variables flow from `.env`/`/etc/docuvault/env` into containers |
| Backend container → PostgreSQL | App connects with restricted `docuvault_app` role; Alembic connects with privileged `docuvault_migrate` role |
| Backend container → MinIO | App connects with app-level access key (separate from MinIO root credentials) |
| Backend container → Redis | App + Celery worker connect with `requirepass`-protected URL |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-01-01 | Elevation of Privilege | PostgreSQL connection from app | mitigate | Two-DSN pattern (D-13): `DATABASE_URL` uses `docuvault_app` (DML only, no DDL); `DATABASE_MIGRATE_URL` uses `docuvault_migrate` (DDL only, used by Alembic only). Init script in Task 1 hard-codes both grants; Plan 03 issues `ALTER DEFAULT PRIVILEGES` inside the migration. |
| T-01-01-02 | Elevation of Privilege | MinIO root credentials | mitigate | `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` used only for MinIO container init; app uses separate `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` (D-15); the app never connects with root credentials. |
| T-01-01-03 | Information Disclosure | Redis unauthenticated access on Docker network | mitigate | Redis runs with `--requirepass ${REDIS_PASSWORD}` (Pattern 6); both app and worker connect via `REDIS_URL` containing the password; healthcheck passes `-a $REDIS_PASSWORD` per Pitfall 5. |
| T-01-01-04 | Information Disclosure | Secret leakage via committed `.env` file | mitigate | `.env` added to `.gitignore` in Task 1; only `.env.example` with placeholder `changeme_*` values is committed (D-11); production secrets stored outside the project at `/etc/docuvault/env` `chmod 600` (D-12, documented in SKELETON.md). |
| T-01-01-05 | Tampering | Compose service starts before its dependencies are ready | mitigate | `depends_on: condition: service_healthy` on backend and celery-worker for postgres + minio + redis (Pattern 6); replaces race-prone `sleep` patterns. |
| T-01-01-SC | Tampering | npm/pip supply chain on dependency install | mitigate | RESEARCH.md Package Legitimacy Audit verified all 6 new packages on PyPI via `pip3 index versions` + slopcheck OK; no `[ASSUMED]` or `[SUS]` packages — no checkpoint required this plan. |
</threat_model>

<verification>
- After all three tasks complete, run `cd /Users/nik/Documents/Progamming/document_scanner && docker compose --env-file .env.example config -q` — must exit 0.
- Confirm `cd backend && python3 -c "from config import settings; print(settings.minio_bucket)"` prints `docuvault`.
- `grep -c "filelock" backend/requirements.txt` must equal 0.
</verification>

<success_criteria>
- `docker-compose.yml`, `docker/postgres/initdb.d/01-init-users.sql`, `.env.example`, `backend/config.py`, and `backend/requirements.txt` are all updated according to the acceptance criteria above.
- `docker compose --env-file .env.example config -q` exits 0.
- `from config import settings` works in Python without raising; `settings.minio_bucket == "docuvault"`.
- `.env` is gitignored and `.env.example` documents every env var referenced by `docker-compose.yml`.
- No existing flat-file constants are deleted yet — the app must still boot after this plan in isolation (Plan 05 completes the cutover).
</success_criteria>

<output>
Create `.planning/phases/01-infrastructure-foundation/01-01-SUMMARY.md` when done summarizing: services declared, env vars introduced, dependencies added/removed, and any deviations from the plan.
</output>
