Files
kite/.planning/phases/01-infrastructure-foundation/01-01-PLAN.md
T
curo1305 6fed5ba531 docs(01): create phase 1 plan — 5 plans in 4 waves
Research, pattern mapping, and verification complete.
Walking Skeleton mode active (MVP Phase 1).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:49:36 +02:00

26 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, tags, must_haves
phase plan type wave depends_on files_modified autonomous requirements user_setup tags must_haves
01-infrastructure-foundation 01 execute 1
docker-compose.yml
docker/postgres/initdb.d/01-init-users.sql
.env.example
.gitignore
backend/requirements.txt
backend/config.py
true
STORE-01
STORE-07
infrastructure
docker-compose
postgresql
minio
redis
celery
pydantic-settings
truths artifacts key_links
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
path provides contains
docker-compose.yml postgres, minio, redis, celery-worker services with healthchecks and depends_on conditions postgres:17-alpine
path provides contains
docker/postgres/initdb.d/01-init-users.sql docuvault_app and docuvault_migrate role provisioning CREATE USER docuvault_migrate
path provides contains
.env.example Documented placeholders for DATABASE_URL, DATABASE_MIGRATE_URL, MINIO_*, REDIS_URL, SECRET_KEY DATABASE_MIGRATE_URL=postgresql+psycopg
path provides contains
backend/config.py Pydantic Settings instance with all Phase 1 env vars class Settings(BaseSettings)
path provides contains
backend/requirements.txt Updated dependency manifest sqlalchemy[asyncio]>=2.0
from to via pattern
docker-compose.yml .env.example variables ${VAR_NAME} interpolation ${DATABASE_URL}|${REDIS_URL}|${MINIO_ENDPOINT}
from to via pattern
backend/config.py .env.example variables Pydantic Settings env_file database_url|minio_endpoint|redis_url
from to via pattern
docker-compose.yml postgres service docker/postgres/initdb.d/01-init-users.sql bind-mount into /docker-entrypoint-initdb.d initdb.d:/docker-entrypoint-initdb.d
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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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 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_URLpostgresql+psycopg://docuvault_app:<pw>@postgres:5432/docuvault
  • DATABASE_MIGRATE_URLpostgresql+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_ENDPOINTminio: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_URLredis://:<pw>@redis:6379/0
  • SECRET_KEY — Phase 2 JWT/HKDF placeholder; documented now, not read by Phase 1 code paths
Task 1: Replace docker-compose.yml with five-service stack and add PostgreSQL init script docker-compose.yml, docker/postgres/initdb.d/01-init-users.sql, .gitignore - 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) 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. 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=$?" - `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) 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. Task 2: Extend .env.example with all Phase 1 variables (D-11, D-13, D-15, D-16) .env.example - .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) Replace `.env.example` keeping the existing `ANTHROPIC_API_KEY=` and `OPENAI_API_KEY=` lines at the top, then append four new sections grouped by `# ── ───` 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.
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}' - `.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://:@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) `.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. Task 3: Replace backend/config.py with Pydantic Settings + extend backend/requirements.txt backend/config.py, backend/requirements.txt - 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) 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. 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')" - `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`) `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`.

<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>
- 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.

<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>
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.