diff --git a/AGENTS.md b/AGENTS.md index ece9e29..7c28a1c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ DocuVault is a multi-user SaaS document management platform built on FastAPI (Python) + Vue 3. It handles document upload, text extraction (PDF/DOCX/image/text), AI-based topic classification, per-user isolated storage, folder organization, document sharing, and pluggable cloud storage backends (OneDrive, Google Drive, Nextcloud, WebDAV). -**Current state:** v0.2.0 — Phase 12 complete (2026-06-19). Cloud resource foundation: connection-ID browse API, CloudResourceAdapter contract, durable cloud item metadata, capability-aware action rendering, breadcrumb freshness indicators. Cloud and local files share one StorageBrowser implementation. Security-negative suite in test_cloud_security.py. Not cleared for public internet deployment. +**Current state:** v0.2.1 — Phase 12 gap-closure complete (2026-06-20). Migration-gated startup: one-shot migrate service runs alembic upgrade head before backend/worker/beat start (service_completed_successfully gate). 0005→0006 upgrade regression in test_migration_0006.py. RUNBOOK migration ops section. Cloud resource foundation: connection-ID browse API, CloudResourceAdapter contract, durable cloud item metadata, capability-aware action rendering. Not cleared for public internet deployment. ## Stack @@ -123,9 +123,9 @@ This project uses the GSD (Get Shit Done) planning workflow. Planning artifacts /gsd:progress — check status and advance workflow ``` -### Current state: v0.2.0 — Phase 12 complete (2026-06-19) +### Current state: v0.2.1 — Phase 12 gap-closure complete (2026-06-20) -Phase 12 (cloud-resource-foundation): full cloud browse foundation — CloudResourceAdapter contract, connection-ID browse API (`GET /api/cloud/connections/{id}/items`), durable cloud item metadata (CloudItem ORM), connection-ID routing (`/cloud/:connectionId`), capability-aware action rendering (CapabilityButton / aria-disabled), breadcrumb freshness indicators (refreshing/fresh/stale), session-only folder memory, display-name rename for cloud connections, and a dedicated security-negative integration suite (`test_cloud_security.py`). Cloud and local files share one StorageBrowser row/action implementation. The browse foundation is complete; Phase 13 adds cloud mutations (upload/move/delete) and Phase 14 adds byte caching. The app is functional but alpha-quality — not cleared for public internet deployment. +Phase 12 gap-closure: migration-gated Compose startup (migrate service, service_completed_successfully dependency gate on backend/worker/beat), 0005→0006 upgrade regression tests (test_migration_0006.py), RUNBOOK migration ops section. Full Phase 12 cloud browse foundation — CloudResourceAdapter contract, connection-ID browse API (`GET /api/cloud/connections/{id}/items`), durable cloud item metadata (CloudItem ORM), connection-ID routing (`/cloud/:connectionId`), capability-aware action rendering (CapabilityButton / aria-disabled), breadcrumb freshness indicators (refreshing/fresh/stale), session-only folder memory, display-name rename for cloud connections, and a dedicated security-negative integration suite (`test_cloud_security.py`). Cloud and local files share one StorageBrowser row/action implementation. The browse foundation is complete; Phase 13 adds cloud mutations (upload/move/delete) and Phase 14 adds byte caching. The app is functional but alpha-quality — not cleared for public internet deployment. ## Development Setup diff --git a/CLAUDE.md b/CLAUDE.md index 13f9bcd..779ba2c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ DocuVault is a multi-user SaaS document management platform built on FastAPI (Python) + Vue 3. It handles document upload, text extraction (PDF/DOCX/image/text), AI-based topic classification, per-user isolated storage, folder organization, document sharing, and pluggable cloud storage backends (OneDrive, Google Drive, Nextcloud, WebDAV). -**Current state:** v0.2.0 — Phase 12 complete 2026-06-19. Provider-neutral cloud resource contract (CloudResourceAdapter, CloudResource, CloudCapability), durable CloudItem/CloudFolderState metadata layer (Alembic migration 0006), connection-ID browse API with IDOR protection and stale-while-revalidate, refresh_cloud_folder Celery task, and capability-aware StorageBrowser frontend. 12 of 12 phases complete. Not cleared for public internet deployment. +**Current state:** v0.2.1 — Phase 12 gap-closure complete 2026-06-20. Migration-gated Compose startup (migrate service, service_completed_successfully dependency gate), 0005→0006 upgrade regression tests, RUNBOOK migration ops section. Provider-neutral cloud resource contract (CloudResourceAdapter, CloudResource, CloudCapability), durable CloudItem/CloudFolderState metadata layer (Alembic migration 0006), connection-ID browse API with IDOR protection and stale-while-revalidate, refresh_cloud_folder Celery task, and capability-aware StorageBrowser frontend. 12 of 12 phases complete. Not cleared for public internet deployment. ## Stack @@ -119,7 +119,7 @@ This project uses the GSD (Get Shit Done) planning workflow. Planning artifacts /gsd:progress — check status and advance workflow ``` -### Current state: v0.2.0 — Phase 12 (cloud-resource-foundation) complete (2026-06-19) +### Current state: v0.2.1 — Phase 12 gap-closure complete (2026-06-20) All phases are marked complete in `.planning/ROADMAP.md`. The app is functional but alpha-quality — not cleared for public internet deployment. The next milestone has not been started. diff --git a/README.md b/README.md index f818d0c..d09da19 100644 --- a/README.md +++ b/README.md @@ -117,16 +117,15 @@ print('JWT_PUBLIC_KEY=' + base64.b64encode(k.public_key().public_bytes(serializ # (everything else has usable defaults for local dev) nano .env -# 5. Start all services +# 5. Start all services (migrations run automatically before backend starts) docker compose up -d --build -# 6. Run database migrations -docker compose exec backend alembic upgrade head - -# 7. Open the app +# 6. Open the app open http://localhost:5173 ``` +The `migrate` service runs `alembic upgrade head` automatically before the backend, Celery worker, and Celery beat start. No manual migration step is needed on a normal `docker compose up`. + The bootstrap admin account (email + password from `.env`) is created automatically on first startup if no users exist. --- @@ -222,8 +221,12 @@ npm run dev # http://localhost:5173 ### Database migrations ```bash -# Apply all pending migrations -docker compose exec backend alembic upgrade head +# Migrations run automatically on docker compose up via the migrate service. +# To run them manually (e.g., after a git pull without restarting): +docker compose run --rm migrate + +# Check current revision +docker compose run --rm migrate alembic current # Create a new migration after changing db/models.py docker compose exec backend alembic revision --autogenerate -m "describe change" diff --git a/SECURITY.md b/SECURITY.md index f9cd6d2..0a8c3db 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -385,3 +385,35 @@ None. Phase 12 adds a read-only cloud browse surface (no MinIO writes, no quota |---------|-----------|---------------|-----------| | T-12-SC | pip-audit tooling | pip-audit not installed in local Python 3.9 environment | No new Python packages added in Phase 12; existing packages audited in Phase 8. Operator should run pip-audit in CI or with Python 3.12 target environment. | | T-12-disabled | DISABLED connection returns 200 with unavailable capabilities | Browse endpoint is accessible but all operations are unavailable | This is correct behavior: ownership is still asserted (user owns the connection), and capabilities convey the unavailability. No cloud bytes or credentials are served. | + +--- + +## Phase 12 Gap Closure — Schema Drift Fix (2026-06-20) + +### Gap Closure Threat Register + +| Threat ID | Threat | Disposition | Status | Evidence | +|-----------|--------|-------------|--------|----------| +| T-12-05-01 | App starts against stale schema (UndefinedColumn) | mitigate | CLOSED | `docker-compose.yml` migrate service + `service_completed_successfully` dependency gate; `backend/tests/test_compose_migrations.py` — 7 static assertions | +| T-12-05-02 | DDL privilege escalation (app uses migrate URL) | mitigate | CLOSED | migrate service sets only `DATABASE_MIGRATE_URL`; backend/worker/beat receive only `DATABASE_URL`; test assertions in `test_compose_migrations.py:test_migrate_service_uses_migrate_url` | +| T-12-05-03 | Concurrent migrations (multiple replicas upgrading) | mitigate | CLOSED | Exactly one migrate service; replicas only wait on `service_completed_successfully` — never run Alembic themselves | +| T-12-05-04 | Secrets in Compose config or test output | mitigate | CLOSED | Environment references via `${VAR}` only; no rendered values in source or committed output | +| T-12-05-05 | Integration test destroys developer database | mitigate | CLOSED | `test_migration_0006.py` skips without `INTEGRATION=1`/`INTEGRATION_DATABASE_URL`; explicit skip message; never touches `DATABASE_URL` | + +### Gap Closure Security Gate Evidence + +**Gate 1 — Compose configuration invariants:** +``` +pytest -q backend/tests/test_compose_migrations.py +7 passed +``` + +**Gate 2 — Migration regression tests:** +``` +pytest -q backend/tests/test_migration_0006.py +12 skipped (INTEGRATION not set — correct behavior without disposable DB) +``` +Integration tests activate with `INTEGRATION=1` against a disposable PostgreSQL database. + +**Gate 3 — No new Python or frontend packages introduced.** +**Gate 4 — No hardcoded credentials in Compose or test files.** diff --git a/backend/main.py b/backend/main.py index 8208120..b8afa88 100644 --- a/backend/main.py +++ b/backend/main.py @@ -244,7 +244,7 @@ async def lifespan(app: FastAPI): # ── Application factory ─────────────────────────────────────────────────────── -app = FastAPI(title="Document Scanner API", version="0.2.0", lifespan=lifespan) +app = FastAPI(title="Document Scanner API", version="0.2.1", lifespan=lifespan) # Rate limiter state (slowapi) app.state.limiter = auth_limiter diff --git a/frontend/package.json b/frontend/package.json index 63c2f44..0287603 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "document-scanner-frontend", - "version": "0.2.0", + "version": "0.2.1", "type": "module", "scripts": { "dev": "vite",