7.1 KiB
phase: 05-cloud-storage-backends plan: "09" subsystem: cloud-documents tags: [cloud, documents, patch, celery, frontend, blob-url, authentication] dependency_graph: requires: [05-06] provides: [PATCH /api/documents/{id}, cloud-aware re-analyze, authenticated-preview] affects: [backend/api/documents.py, backend/tasks/document_tasks.py, frontend/src/api/client.js, frontend/src/components/documents/DocumentPreviewModal.vue, frontend/src/views/DocumentView.vue] tech_stack: added: [] patterns: [fetch-blob-url, model_fields_set, cloud-aware-task-routing, tdd-red-green] key_files: created: [] modified: - backend/api/documents.py - backend/tasks/document_tasks.py - backend/tests/test_cloud.py - frontend/src/api/client.js - frontend/src/components/documents/DocumentPreviewModal.vue - frontend/src/views/DocumentView.vue decisions:
- "Test 3 patches storage module (not tasks module): deferred import pattern means get_storage_backend_for_document is not a module-level attribute of document_tasks — patching at storage module level is correct"
- "Test 3 is a pure unit test (no db_session): _run() opens its own AsyncSessionLocal which requires PostgreSQL; mocking the session manager keeps the test fast and infrastructure-free"
- "node_modules symlinked into worktree frontend for build verification: worktree does not have its own node_modules; symlink to main repo preserves isolation while enabling build check" metrics: duration: "~25 minutes" completed: "2026-05-30" tasks_completed: 2 files_modified: 6
Phase 5 Plan 09: Cloud Document Access Fixes Summary
Fixed three independent root causes that blocked cloud document use: unauthenticated iframe preview, hardcoded MinIO in Celery re-analyze, and missing PATCH endpoint for document metadata.
Tasks Completed
| Task | Name | Commit | Files |
|---|---|---|---|
| 1 | PATCH /documents/{id} + cloud-aware Celery re-analyze | 6d094d1 |
backend/api/documents.py, backend/tasks/document_tasks.py, backend/tests/test_cloud.py |
| TDD RED | Failing tests for Task 1 | 9bc0561 |
backend/tests/test_cloud.py |
| 2 | Authenticated document preview — fetch + Blob URL | 4a42cce |
frontend/src/api/client.js, frontend/src/components/documents/DocumentPreviewModal.vue, frontend/src/views/DocumentView.vue |
What Was Built
Task 1: Backend fixes
DocumentPatchPydantic model withOptional[str] filenameandOptional[uuid.UUID] folder_id; usesmodel_fields_setto distinguish "not provided" from "explicitly set to null"PATCH /api/documents/{doc_id}endpoint with ownership guard (non-owner → 404), admin guard (get_regular_user → 403), empty body guard (422), andstorage.get_metadata()whitelist response_run()indocument_tasks.pyupdated: MinIO path unchanged; non-MinIO path callsget_storage_backend_for_document(doc, user, session); missing user returnsmissing_userstatus;CloudConnectionErrorreturnsextract_failedwith generic "cloud backend error" message (no provider details)
Task 2: Frontend fixes
fetchDocumentContent(docId)inclient.js: authenticated fetch returning rawResponse(not parsed JSON); single 401 → refresh → retry pattern; mirrorsrequest()auth logic withoutres.json()DocumentPreviewModal.vue: replaced unauthenticatediframe :src="proxyUrl"with authenticated fetch → blob →URL.createObjectURL(); loading spinner, error state,URL.revokeObjectURLon unmountDocumentView.vueopenPdf(): replacedwindow.open(rawUrl)with fetch → blob → objectUrl →window.open(objectUrl); 60-second auto-revoke
Test Results
- 3 new tests in
test_cloud.py: all passtest_patch_document_filename— PATCH 200 with updated filenametest_patch_document_wrong_owner— PATCH 404 for non-owner (IDOR guard)test_reanalyze_cloud_document_routes_to_cloud_backend— cloud backend called, MinIO not called
- Full
test_cloud.pysuite: 23 passed, 0 failed (no regressions) - Frontend build: zero errors (
vite buildexits 0)
Deviations from Plan
Auto-adjusted Issues
1. [Rule 1 - Bug] Test patching at storage module level, not tasks module level
- Found during: Task 1 GREEN phase
- Issue: Plan specified patching
tasks.document_tasks.get_storage_backend_for_document, but that attribute does not exist at module level — the import is inside_run()(deferred import pattern).unittest.mock.patchraisesAttributeErroron absent attributes. - Fix: Test patches
storage.get_storage_backend_for_documentandstorage.get_storage_backend— the canonical source of both functions. Behavior under test is identical. - Files modified:
backend/tests/test_cloud.py
2. [Rule 1 - Bug] Test 3 changed to pure unit test (no db_session fixture)
- Found during: Task 1 GREEN phase — second attempt
- Issue:
_run()opens its ownAsyncSessionLocal()internally which requires a live PostgreSQL connection. Usingdb_sessionfixture in the test doesn't affect_run()'s internal session. Tests run without Docker → connection refused. - Fix: Test mocks
db.session.AsyncSessionLocalwith a fake async context manager that returns a mock session withsession.get()returning pre-builtMagicMockDocument and User objects. Removeddb_sessionfrom test signature. - Files modified:
backend/tests/test_cloud.py
3. [Rule 3 - Blocking] node_modules symlink needed for worktree build
- Found during: Task 2 verification
- Issue: Worktree's
frontend/has nonode_modules(npm install runs in main repo only).vite buildfailed withERR_MODULE_NOT_FOUND. - Fix: Created a symlink
worktree/frontend/node_modules → main/frontend/node_modules. Build succeeded. - Files modified:
frontend/node_modules(symlink, not tracked in git)
Security Analysis (T-05-09 Threat Register)
| Threat ID | Status | Notes |
|---|---|---|
| T-05-09-01 | Mitigated | get_regular_user enforced on PATCH; admin → 403; wrong owner → 404 |
| T-05-09-02 | Mitigated | storage.get_metadata() response whitelist via _doc_to_dict() — no credentials_enc |
| T-05-09-03 | Mitigated | Credentials loaded inside Celery task's own DB session via get_storage_backend_for_document |
| T-05-09-04 | Accepted | Blob URL same-origin, revoked on unmount — no persistent exposure |
| T-05-09-SC | N/A | No new packages installed |
Known Stubs
None — all plan features fully implemented and wired.
Self-Check: PASSED
All created/modified files confirmed present on disk. All task commits verified in git log.
| Item | Status |
|---|---|
| backend/api/documents.py | FOUND |
| backend/tasks/document_tasks.py | FOUND |
| backend/tests/test_cloud.py | FOUND |
| frontend/src/api/client.js | FOUND |
| frontend/src/components/documents/DocumentPreviewModal.vue | FOUND |
| frontend/src/views/DocumentView.vue | FOUND |
| .planning/phases/05-cloud-storage-backends/05-09-SUMMARY.md | FOUND |
Commit 9bc0561 (RED tests) |
FOUND |
Commit 6d094d1 (GREEN implementation) |
FOUND |
Commit 4a42cce (frontend auth preview) |
FOUND |