Files

5.7 KiB

phase, plan, type, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan type subsystem tags dependency_graph tech_stack key_files decisions metrics
12 05 gap_closure infrastructure
migrations
compose
docker
testing
devops
requires provides affects
12-04
migration-gated-startup
upgrade-regression
docker-compose
alembic
backend
celery-worker
celery-beat
added patterns
service_completed_successfully
one-shot-container
disposable-pg-integration-test
created modified
backend/tests/test_compose_migrations.py
backend/tests/test_migration_0006.py
docker-compose.yml
backend/main.py
frontend/package.json
CLAUDE.md
README.md
RUNBOOK.md
SECURITY.md
AGENTS.md
migrate service uses only DATABASE_MIGRATE_URL (DDL role); application services retain DATABASE_URL (DML role only)
migrate service has restart: no — one-shot, not a daemon; failure blocks all application services
integration tests skip cleanly without INTEGRATION=1 — no developer DB mutation risk
duration completed tasks files
~25 minutes 2026-06-20 3 9

Phase 12 Plan 05: Schema Drift Gap Closure Summary

One-liner: Migration-gated Compose startup gate (one-shot migrate service + service_completed_successfully) closes the Phase 12 UAT schema drift blocker that left the live database at revision 0005.

What Was Built

Task 1: Migration-gated Compose startup path

Added a one-shot migrate service to docker-compose.yml that:

  • Runs alembic upgrade head as its only command
  • Receives only DATABASE_MIGRATE_URL (DDL-capable docuvault_migrate role)
  • Has the same hardening as other containers: read_only: true, cap_drop: ALL, no-new-privileges:true, tmpfs /tmp
  • Uses restart: no — it exits 0 on success and does not restart

All three application services (backend, celery-worker, celery-beat) now depend on migrate with condition: service_completed_successfully. A failed migration exit blocks application startup.

Added backend/tests/test_compose_migrations.py with 7 static assertions:

  • Exactly one service runs alembic upgrade head and it is named migrate
  • migrate uses DATABASE_MIGRATE_URL, not DATABASE_URL
  • Application services do not run alembic upgrade themselves
  • All three application services depend on migrate: service_completed_successfully
  • Container hardening invariants (read_only, cap_drop ALL, no-new-privileges)
  • One-shot restart policy (restart: no)
  • Minimal dependencies — only postgres, not minio/redis

Task 2: PostgreSQL upgrade regression from 0005 to head

Added backend/tests/test_migration_0006.py with 12 integration test cases:

  • Skips cleanly without INTEGRATION=1 or INTEGRATION_DATABASE_URL (no developer DB mutation)
  • Applies migrations to 0005, asserts display_name_override absent and Phase 12 tables absent
  • Upgrades to head, asserts alembic_version = '0006'
  • Proves display_name_override column exists and is nullable
  • Proves cloud_items, cloud_item_topics, cloud_folder_states tables exist
  • Asserts key columns on each table
  • Asserts named unique constraints: uq_cloud_items_connection_provider_item, uq_cloud_folder_states_connection_parent
  • Asserts named indexes: ix_cloud_items_user_id, ix_cloud_items_connection_id, ix_cloud_items_connection_parent, ix_cloud_folder_states_connection
  • Asserts docuvault_app lacks CREATE privilege on the public schema

Updated RUNBOOK.md with a new "Database Migrations" section covering:

  • Checking current revision (alembic current)
  • Normal upgrade via docker compose run --rm migrate
  • Viewing migration history
  • Failed-migration recovery steps
  • Emergency schema-drift remediation commands (the exact Phase 12 UAT blocker scenario)

Task 3: Version bump and documentation

  • backend/main.py: 0.2.00.2.1
  • frontend/package.json: 0.2.00.2.1
  • CLAUDE.md: current state updated, startup startup pattern noted
  • README.md: startup instructions updated — no manual alembic upgrade head needed; migration commands updated
  • SECURITY.md: Phase 12 gap-closure threat register and gate evidence appended
  • AGENTS.md: current state updated to v0.2.1 gap-closure

Deviations from Plan

Auto-added

[Rule 2 - Missing critical tests] RUNBOOK startup diagram

  • Added description of the migrate→application-services dependency chain to the startup section diagram in RUNBOOK.md (the plan specified migration lifecycle documentation; the startup flow description was an obvious omission)

Descoped

The plan mentioned "Apply alembic upgrade head through the new migration service in the current Compose environment" and "restart/recreate backend plus Celery processes through the gated path" as Task 3 actions. These are live-environment operations that require a running Docker stack. The Compose changes and tests fully satisfy the architectural fix; the live-environment verification step was documented in RUNBOOK.md for operators to execute rather than run as part of this test-only execution environment.

Threat Surface Scan

No new network endpoints, auth paths, or file access patterns introduced. The migrate service has no ports exposed and no external network access.

Self-Check

Created files exist:

  • backend/tests/test_compose_migrations.py: present
  • backend/tests/test_migration_0006.py: present
  • .planning/phases/12-cloud-resource-foundation/12-05-SUMMARY.md: present (this file)

Commits:

  • 1b3084d: feat(12-05): add migration-gated Compose startup path
  • de2efd1: test(12-05): add migration 0005→0006 integration regression and RUNBOOK migration ops
  • 3ca57dc: fix(12-05): bump version to 0.2.1 and update docs for migration-gated startup

Self-Check: PASSED