eaa3399ec0
- CLAUDE.md: add Code Standards section with backend and frontend shared module maps, component architecture rules, duplication checklist, and no-dead-code enforcement rule - SECURITY.md: Phase 02 + 03 security audit results (all threats CLOSED) - .planning: update milestone audit, config, and add plan/UAT files for phases 01, 02-06, and 06.2-05 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
414 lines
26 KiB
Markdown
414 lines
26 KiB
Markdown
---
|
|
milestone: v1.0
|
|
audited: "2026-05-30T00:00:00Z"
|
|
status: gaps_found
|
|
scores:
|
|
requirements: 44/54
|
|
phases_verified: 2/6
|
|
integration_blockers: 4
|
|
integration_warnings: 7
|
|
flows_complete: 3/6
|
|
gaps:
|
|
requirements:
|
|
- id: "SHARE-02"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-04-PLAN.md", "06.1-01-PLAN.md"]
|
|
completed_by_plans: ["06.1-01-PLAN.md"]
|
|
verification_status: "gaps_found"
|
|
evidence: "Two distinct bugs: (1) backend/api/documents.py line 542 checks doc.user_id != current_user.id and raises 404 — share recipients get 404 on GET /api/documents/{id} despite having a valid Share record; (2) SharedView.vue accesses share.document?.original_name, share.shared_by, share.document?.created_at but /api/shares/received returns a flat object with filename/owner_handle/created_at — all metadata fields render blank."
|
|
|
|
- id: "DOC-01"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-01-PLAN.md", "04-09-PLAN.md"]
|
|
completed_by_plans: ["04-09-PLAN.md"]
|
|
verification_status: "missing"
|
|
evidence: "Owners can view document metadata and extracted text. Share recipients cannot — documents.py:542 enforces ownership-only check, returning 404 for recipients who navigate to /document/{id}. No share-grant lookup performed before the 404."
|
|
|
|
- id: "SHARE-03"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-04-PLAN.md", "06.1-01-PLAN.md"]
|
|
completed_by_plans: ["06.1-01-PLAN.md"]
|
|
verification_status: "gaps_found"
|
|
evidence: "ShareCreate model has no permission field. grant_share hardcodes permission='view'. No PATCH /api/shares/{id} endpoint exists to change permission after creation. SHARE-03 requires 'owner controls permission level' — only the 'view-only default' half is satisfied. The stored permission field in the shares table cannot be changed through any API endpoint."
|
|
|
|
- id: "SHARE-05"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-04-PLAN.md", "06.1-01-PLAN.md"]
|
|
completed_by_plans: ["06.1-01-PLAN.md"]
|
|
verification_status: "gaps_found"
|
|
evidence: "is_shared computed per document in documents.py lines 433-445 and 498-510 (two separate DB subqueries per list request). Zero occurrences of is_shared in any .vue or .js file. DocumentCard.vue has no visual indicator for shared documents. 06.1-VALIDATION added test_share_indicator_in_owner_list which confirms is_shared=True in the API response — but no frontend component reads or renders it."
|
|
|
|
- id: "STORE-06"
|
|
status: "partial"
|
|
phase: "3"
|
|
claimed_by_plans: ["03-02-PLAN.md"]
|
|
completed_by_plans: ["03-02-SUMMARY.md (STORE-06)"]
|
|
verification_status: "missing"
|
|
evidence: "MinIO path: services/storage.py:168-175 implements atomic CASE WHEN quota decrement — correct for MinIO documents. Cloud path: delete_document() calls self._backend().delete_object(doc.object_key) where _backend() always returns MinIOBackend regardless of doc.storage_backend. Cloud-stored documents: (1) MinIO delete_object gets NoSuchKey (silently swallowed); (2) MinIO quota decremented even though no quota was charged at cloud upload; (3) actual file in Google Drive / OneDrive / Nextcloud / WebDAV is never deleted. Additionally, test_delete_decrements_quota is @pytest.mark.xfail(strict=False) — ROADMAP phase gate requires INTEGRATION=1 confirmation against live PostgreSQL."
|
|
|
|
- id: "SEC-09"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-07-PLAN.md", "05-05-PLAN.md"]
|
|
completed_by_plans: ["04-07-SUMMARY.md", "05-05-SUMMARY.md"]
|
|
verification_status: "partial"
|
|
evidence: "Admin-initiated account deletion (admin.py lines 518-565) correctly purges all CloudConnection rows and calls delete_user_files() before MinIO+DB cleanup — SEC-09 satisfied for the account deletion path. However, user-initiated document deletion (services/storage.delete_document) does not call get_storage_backend_for_document — cloud provider files are orphaned when a user deletes a cloud-stored document."
|
|
|
|
- id: "ADMIN-06"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-06-PLAN.md", "06.1-02-PLAN.md"]
|
|
completed_by_plans: ["06.1-02-PLAN.md"]
|
|
verification_status: "gaps_found"
|
|
evidence: "GET /api/admin/audit-log JSON viewer works end-to-end. Filter behavioral tests pass (test_audit_log_filter_by_event_type added in commit 451fff1). GET /api/admin/audit-log/export: AuditLogTab.vue:191 uses window.location.href which sends no Authorization: Bearer header. get_current_admin requires HTTPBearer — CSV export always returns 403."
|
|
|
|
- id: "CLOUD-03"
|
|
status: "partial"
|
|
phase: "5"
|
|
claimed_by_plans: ["05-06-PLAN.md"]
|
|
completed_by_plans: ["05-06-SUMMARY.md"]
|
|
verification_status: "human_needed"
|
|
evidence: "PATCH /api/users/me/default-storage fully implemented (cloud.py:927, registered in main.py). updateDefaultStorage() exported from client.js:448. However, updateDefaultStorage() is never imported or called by any Vue component. SettingsCloudTab.vue renders cloud connections but has no radio/select to change the default storage backend. Users cannot change their default backend through the UI."
|
|
|
|
- id: "FOLD-01"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-03-PLAN.md"]
|
|
completed_by_plans: ["04-03-PLAN.md"]
|
|
verification_status: "missing"
|
|
evidence: "_folder_to_dict() in folders.py:65 returns {id, name, parent_id, user_id, created_at} — no doc_count field. FolderDeleteModal.vue:31 and FolderRow.vue:32 display folder.doc_count ?? 0. FOLD-01 requires 'delete confirms content count before proceeding' — confirmation always shows '0 documents' regardless of actual folder content."
|
|
|
|
- id: "FOLD-05"
|
|
status: "partial"
|
|
phase: "4"
|
|
claimed_by_plans: ["04-03-PLAN.md"]
|
|
completed_by_plans: ["04-03-PLAN.md"]
|
|
verification_status: "missing"
|
|
evidence: "SearchBar rendered with v-if='currentFolderId' in FileManagerView.vue — hidden at the root level (no folder selected). Users browsing the root document library have no search input. FOLD-05 requires 'Full-text search across user's documents' — search is only available when inside a folder, not at root."
|
|
|
|
integration:
|
|
- blocker: "SHARE-02 / DOC-01 — Share recipient blocked from document detail"
|
|
description: "documents.py:542 checks doc.user_id != current_user.id → HTTPException(404). No share-grant lookup performed. Recipients with valid Share records cannot access GET /api/documents/{id}, breaking the shared document detail view. SharedView.vue also accesses wrong field paths (share.document?.original_name instead of share.filename etc.) — all metadata renders blank even on the list."
|
|
|
|
- blocker: "STORE-06 / SEC-09 — Cloud document delete corrupts quota and orphans files"
|
|
description: "services/storage.delete_document() calls self._backend().delete_object() where _backend() always returns MinIOBackend. Cloud-stored docs: MinIO delete_object silently fails (NoSuchKey), MinIO quota decremented unconditionally, actual cloud provider file never deleted."
|
|
|
|
- blocker: "ADMIN-06 — Audit log CSV export always returns 403"
|
|
description: "AuditLogTab.vue:191 uses window.location.href for CSV export. Browser navigation strips Authorization: Bearer header. Backend endpoint requires HTTPBearer. All CSV export clicks result in 403."
|
|
|
|
- blocker: "CLOUD-03 — Default storage UI orphaned"
|
|
description: "updateDefaultStorage() exported from client.js but never called by any component. No frontend UI exists to change the default storage backend."
|
|
|
|
flows:
|
|
- name: "Recipient views shared document detail"
|
|
breaks_at: "documents.py:542 ownership-only check"
|
|
affected_requirements: ["SHARE-02", "DOC-01"]
|
|
|
|
- name: "User deletes cloud-stored document"
|
|
breaks_at: "services/storage.delete_document() — MinIO backend hardcoded"
|
|
affected_requirements: ["STORE-06", "SEC-09"]
|
|
|
|
- name: "Admin exports audit log as CSV"
|
|
breaks_at: "AuditLogTab.vue:191 window.location.href drops Bearer token"
|
|
affected_requirements: ["ADMIN-06"]
|
|
|
|
tech_debt:
|
|
- phase: "01-infrastructure-foundation"
|
|
items:
|
|
- "No VERIFICATION.md exists (phase not formally verified by gsd-verifier)"
|
|
- "VALIDATION.md: nyquist_compliant: true, audited 2026-05-30"
|
|
|
|
- phase: "02-users-authentication"
|
|
items:
|
|
- "VERIFICATION.md exists (gaps_found 4/5) — SC5 gap closed by Phase 3, no re-verification run"
|
|
- "No VALIDATION.md — Nyquist compliance MISSING for Phase 2"
|
|
- "4 human verification items pending: TOTP enrollment e2e, password reset email, sign out all devices, admin panel visuals"
|
|
|
|
- phase: "03-document-migration-multi-user-isolation"
|
|
items:
|
|
- "No VERIFICATION.md exists (phase not formally verified)"
|
|
- "VALIDATION.md: nyquist_compliant: false, status: draft — Nyquist PARTIAL"
|
|
- "Document.user_id ORM column has nullable=True but DB has NOT NULL constraint (migration 0003 alters it) — ORM/schema drift"
|
|
- "test_delete_decrements_quota is xfail(strict=False) on SQLite — INTEGRATION=1 gate requires live PostgreSQL to confirm"
|
|
|
|
- phase: "04-folders-sharing-quotas-document-ux"
|
|
items:
|
|
- "No VERIFICATION.md exists (phase not formally verified)"
|
|
- "VALIDATION.md: nyquist_compliant: false, status: draft — Nyquist PARTIAL"
|
|
- "AdminView.vue has no frontend role guard — unauthenticated-role users who navigate to /admin see full UI (all backend calls return 403 but no redirect occurs)"
|
|
- "FOLD-01: _folder_to_dict() omits doc_count; delete confirmation always shows 0 documents"
|
|
- "FOLD-05: SearchBar hidden at root level (v-if='currentFolderId')"
|
|
- "SHARE-05: is_shared computed per document (2 DB subqueries) but never rendered in any Vue component"
|
|
- "SHARE-03: permission hardcoded 'view', no PATCH endpoint to change it"
|
|
|
|
- phase: "05-cloud-storage-backends"
|
|
items:
|
|
- "VERIFICATION.md: human_needed — 6 items require live cloud credentials (Google OAuth, OneDrive OAuth, live Nextcloud/WebDAV server)"
|
|
- "VALIDATION.md: nyquist_compliant: true, audited 2026-05-30"
|
|
- "CLOUD-05 REQUIRES_REAUTH transition implemented for OAuth providers only (Google Drive, OneDrive). Nextcloud/WebDAV credential failures produce generic 502 — no REQUIRES_REAUTH state for non-OAuth backends. Spec-compliant but UX gap."
|
|
- "_doc_to_dict() omits storage_backend and folder_id — document list response cannot distinguish cloud vs local documents"
|
|
- "CLOUD-03: updateDefaultStorage() exported but no UI element calls it"
|
|
|
|
- phase: "06.1-close-v1-audit-gaps"
|
|
items:
|
|
- "VERIFICATION.md: stale (written before commit 451fff1 which added audit filter test). 06.1-VALIDATION.md supersedes."
|
|
- "VALIDATION.md: nyquist_compliant: true, gaps_found: 3, gaps_resolved: 2, gaps_manual: 1"
|
|
- "STORE-06 INTEGRATION=1 gate: manual-only — requires live PostgreSQL Docker stack to confirm"
|
|
- "conftest.py WR-03: dependency_overrides not cleared on exception in async_client fixture (low-probability correctness gap)"
|
|
|
|
- phase: "all"
|
|
items:
|
|
- "REQUIREMENTS.md checkboxes are stale — 22 satisfied requirements still show [ ]. Not maintained during execution."
|
|
- "CLAUDE.md specifies ES256 JWT algorithm, email_hmac deterministic index, fgp token fingerprint claim — none implemented (HS256, plaintext email, no fingerprint). Outside 54 v1 REQ-IDs; v2 hardening scope."
|
|
|
|
nyquist:
|
|
compliant_phases: [1, 5, "6.1"]
|
|
partial_phases: [3, 4]
|
|
missing_phases: [2]
|
|
overall: partial
|
|
---
|
|
|
|
# DocuVault v1.0 — Milestone Audit Report
|
|
|
|
**Milestone:** v1.0
|
|
**Audited:** 2026-05-30
|
|
**Phases audited:** 1, 2, 3, 4, 5, 6.1 (Phase 6 not started — excluded)
|
|
**Status:** ⚠ GAPS FOUND
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
All 6 planned v1 phases executed and marked complete. Phase 5 formally verified (7/7 truths, human_needed). Phase 2 VERIFICATION.md status=gaps_found (4/5); gap confirmed closed by Phase 3. Phases 1, 3, 4 have no VERIFICATION.md. Phase 6.1 VALIDATION.md supersedes its stale VERIFICATION.md.
|
|
|
|
The integration check found **4 blockers** and **7 warnings**. Ten requirements are partially satisfied, primarily due to frontend wiring gaps and a cloud-delete path defect.
|
|
|
|
| Metric | Score |
|
|
|--------|-------|
|
|
| Requirements satisfied | 44/54 (81%) |
|
|
| Requirements partial | 10/54 (19%) |
|
|
| Requirements unsatisfied | 0/54 |
|
|
| Phases formally verified | 2/6 (Phases 2, 5) |
|
|
| Nyquist compliant | 3/6 phases |
|
|
| Test gate | 309 passed, 1 pre-existing failure (test_extract_docx — missing python-docx module; unrelated to milestone scope) |
|
|
|
|
---
|
|
|
|
## Requirements Coverage
|
|
|
|
### Satisfied (44/54)
|
|
|
|
| Phase | REQ-IDs | Count |
|
|
|-------|---------|-------|
|
|
| 1 — Infrastructure | STORE-01, STORE-02, STORE-07 | 3 |
|
|
| 2 — Auth | AUTH-01, AUTH-02, AUTH-03, AUTH-04, AUTH-05, AUTH-06, AUTH-07, AUTH-08, SEC-01, SEC-02, SEC-03, SEC-05, SEC-06, SEC-07, ADMIN-01, ADMIN-02, ADMIN-03, ADMIN-04, ADMIN-05, ADMIN-07 | 20 |
|
|
| 3 — Documents | STORE-03, STORE-04, STORE-05, STORE-08, SEC-04, DOC-03, DOC-04, DOC-05 | 8 |
|
|
| 4 — Folders/Sharing | FOLD-02, FOLD-03, FOLD-04, SHARE-01, SHARE-04, SEC-08, DOC-02 | 7 |
|
|
| 5 — Cloud | CLOUD-01, CLOUD-02, CLOUD-04, CLOUD-05, CLOUD-06, CLOUD-07 | 6 |
|
|
|
|
**Total satisfied: 44**
|
|
|
|
Notable confirmations from integration check:
|
|
- **STORE-08**: Zero `BackgroundTasks` usages remain; all async work runs through Celery (`document_tasks.py`, `email_tasks.py`).
|
|
- **DOC-02**: PDF proxy chain complete — `fetchDocumentContent()` → Bearer-authenticated `GET /api/documents/{id}/content` → `get_storage_backend_for_document()` → byte stream → blob URL.
|
|
- **SEC-07**: `get_regular_user` raises 403 for admin role on all `/api/documents/*` endpoints — Phase 2 gap confirmed closed by Phase 3.
|
|
- **SEC-08**: `CloudConnectionOut` whitelist (provider, display_name, connected_at, status only) used at `cloud.py:637,661` — `credentials_enc` excluded from all responses.
|
|
|
|
---
|
|
|
|
### Partial (10/54) — Blockers and Warnings
|
|
|
|
| REQ-ID | Phase | Severity | Root Cause |
|
|
|--------|-------|----------|------------|
|
|
| **SHARE-02** | 4 | BLOCKER | Recipients get 404 on `GET /api/documents/{id}` (ownership check only). SharedView.vue field names wrong (blank metadata display). |
|
|
| **DOC-01** | 4 | BLOCKER | Owners: ✅. Share recipients: 404 at `documents.py:542` (`doc.user_id != current_user.id`, no share-grant check). |
|
|
| **STORE-06** | 3 | BLOCKER | MinIO delete-path correct. Cloud delete-path: MinIO backend called unconditionally → quota corrupted, cloud file orphaned. |
|
|
| **SEC-09** | 4 | BLOCKER | Admin account deletion: ✅. User-initiated document delete: cloud provider file not deleted (only MinIO attempted). |
|
|
| **ADMIN-06** | 4 | BLOCKER | JSON audit viewer: ✅. CSV export: `window.location.href` drops Bearer header → 403. |
|
|
| **SHARE-03** | 4 | WARNING | `permission="view"` hardcoded. No `PATCH /api/shares/{id}` endpoint. Owner cannot change permission level. |
|
|
| **SHARE-05** | 4 | WARNING | `is_shared` computed per document (2 DB subqueries / request) but never rendered by any Vue component. |
|
|
| **CLOUD-03** | 5 | WARNING | `PATCH /api/users/me/default-storage` implemented. `updateDefaultStorage()` exported but no UI calls it. |
|
|
| **FOLD-01** | 4 | WARNING | `_folder_to_dict()` omits `doc_count`. Delete confirmation modal always shows "0 documents". |
|
|
| **FOLD-05** | 4 | WARNING | `SearchBar` hidden at root (`v-if="currentFolderId"`). Full-text search unavailable in root document library. |
|
|
|
|
---
|
|
|
|
## Phase Verification Status
|
|
|
|
| Phase | VERIFICATION.md | Status | Notes |
|
|
|-------|----------------|--------|-------|
|
|
| 01 — Infrastructure Foundation | ❌ MISSING | Unverified | Phase marked complete; no formal verification run |
|
|
| 02 — Users & Authentication | ✅ exists | gaps_found (4/5) | SC5 gap (admin JWT → 403 on docs) closed by Ph3; confirmed by integration check |
|
|
| 03 — Document Migration | ❌ MISSING | Unverified | Phase marked complete; no formal verification run |
|
|
| 04 — Folders, Sharing, Quotas | ❌ MISSING | Unverified | Phase marked complete; no formal verification run |
|
|
| 05 — Cloud Storage Backends | ✅ exists | human_needed (7/7) | All must-haves verified; 6 human UAT items require live cloud credentials |
|
|
| 6.1 — Gap Closure | ✅ exists (stale) | Superseded by VALIDATION.md | 06.1-VALIDATION.md: gaps_found 3, resolved 2, manual 1 |
|
|
|
|
---
|
|
|
|
## Nyquist Coverage
|
|
|
|
| Phase | VALIDATION.md | nyquist_compliant | Action |
|
|
|-------|---------------|-------------------|--------|
|
|
| 01 — Infrastructure Foundation | ✅ audited 2026-05-30 | `true` | None |
|
|
| 02 — Users & Authentication | ❌ MISSING | — | `/gsd:validate-phase 2` |
|
|
| 03 — Document Migration | ✅ exists, status: draft | `false` | `/gsd:validate-phase 3` |
|
|
| 04 — Folders, Sharing, Quotas | ✅ exists, status: draft | `false` | `/gsd:validate-phase 4` |
|
|
| 05 — Cloud Storage Backends | ✅ audited 2026-05-30 | `true` | None |
|
|
| 6.1 — Gap Closure | ✅ audited 2026-05-30 | `true` | None |
|
|
|
|
---
|
|
|
|
## Critical Blockers (5)
|
|
|
|
### BLOCKER-1 — Share Recipient Cannot View Document Metadata (SHARE-02, DOC-01)
|
|
|
|
**File:** `backend/api/documents.py` line 542
|
|
**Root cause:** `if doc is None or doc.user_id != current_user.id: raise HTTPException(404)` — no share-grant check.
|
|
**Broken flow:** SharedView.vue → click shared item → DocumentView.vue → `getDocument(id)` → 404 for recipient despite valid Share record.
|
|
**Secondary bug:** SharedView.vue accesses `share.document?.original_name`, `share.shared_by`, `share.document?.created_at` but `/api/shares/received` returns a flat object (`filename`, `owner_handle`, `created_at`). All metadata renders blank even on the list.
|
|
**Fix (backend):** In `get_document()`, after the ownership 404, add: check `Share` table for `(document_id=doc_id, recipient_id=current_user.id)` and allow if found.
|
|
**Fix (frontend):** In `SharedView.vue`, update field access to match flat response shape.
|
|
|
|
### BLOCKER-2 — Cloud Document Delete Corrupts Quota and Orphans Files (STORE-06, SEC-09)
|
|
|
|
**File:** `backend/services/storage.py` (delete_document function)
|
|
**Root cause:** `self._backend().delete_object(doc.object_key)` always uses MinIOBackend regardless of `doc.storage_backend`. Then decrements MinIO quota unconditionally.
|
|
**Impact:** Cloud-stored documents: (1) MinIO `delete_object` gets NoSuchKey (silently swallowed), (2) MinIO quota decremented below actual usage, (3) actual cloud provider file never deleted → GDPR Article 17 obligation not met for cloud storage.
|
|
**Fix:** Use `get_storage_backend_for_document(doc, session)` in `delete_document()`. Gate quota decrement on `doc.storage_backend == "minio"`.
|
|
|
|
### BLOCKER-3 — Admin Audit Log CSV Export Always Returns 403 (ADMIN-06)
|
|
|
|
**File:** `frontend/src/components/admin/AuditLogTab.vue` line 191
|
|
**Root cause:** `window.location.href = '/api/admin/audit-log/export?${params}'` — browser navigation strips the `Authorization: Bearer` header. `get_current_admin` requires `HTTPBearer`.
|
|
**Fix:** Replace `window.location.href` with `fetch()` using `Authorization: Bearer ${accessToken}`, then create a Blob URL for download. The `fetchDocumentContent()` pattern in `client.js` is the correct model.
|
|
|
|
### BLOCKER-4 — Default Storage Backend Has No Frontend UI (CLOUD-03)
|
|
|
|
**File:** `frontend/src/components/settings/SettingsCloudTab.vue`
|
|
**Root cause:** `updateDefaultStorage()` is exported from `client.js:448` but never imported or called by any component. `SettingsCloudTab.vue` has no UI control to select a default backend.
|
|
**Fix:** Add a "Set as default" button or radio to each connected provider row in `SettingsCloudTab.vue`; wire it to `updateDefaultStorage(provider)`.
|
|
|
|
---
|
|
|
|
## Warnings (7)
|
|
|
|
| # | Description | Requirement |
|
|
|---|-------------|-------------|
|
|
| W-1 | `is_shared` computed per document (2 subqueries per list request) but no Vue component renders it | SHARE-05 |
|
|
| W-2 | SHARE-03: `permission` hardcoded to `"view"`; no `PATCH /api/shares/{id}` endpoint | SHARE-03 |
|
|
| W-3 | `_folder_to_dict()` omits `doc_count` — delete confirmation modal always shows "0 documents" | FOLD-01 |
|
|
| W-4 | `SearchBar` hidden at root level (`v-if="currentFolderId"`) — search unavailable in root library | FOLD-05 |
|
|
| W-5 | `Document.user_id` ORM column `nullable=True`; DB has `NOT NULL` constraint (migration 0003) — ORM/schema drift | STORE-03 |
|
|
| W-6 | `AdminView.vue` has no frontend role guard — regular users who navigate to `/admin` see full UI; backend returns 403 but no redirect | — |
|
|
| W-7 | CLAUDE.md specifies ES256 JWT, `email_hmac` index, `fgp` fingerprint claim — none implemented (HS256, plaintext email, no fingerprint). v2 hardening scope, outside 54 v1 REQ-IDs. | v2 |
|
|
|
|
---
|
|
|
|
## E2E Flow Results
|
|
|
|
| Flow | Status | Break Point |
|
|
|------|--------|-------------|
|
|
| MinIO upload → quota updated → Celery AI classification | ✅ COMPLETE | — |
|
|
| Password reset → TOTP gate on next login | ✅ COMPLETE | — |
|
|
| Cloud upload → authenticated content proxy (blob URL) | ✅ COMPLETE | — |
|
|
| Share document → "Shared with me" list → recipient views detail | ❌ BROKEN | `documents.py:542` ownership-only check + SharedView field mismatch |
|
|
| User deletes cloud-stored document → files purged | ❌ BROKEN | `delete_document()` hardcodes MinIOBackend |
|
|
| Admin views audit log → exports CSV | ⚠️ PARTIAL | JSON viewer works; CSV export → 403 (no Bearer in `window.location.href`) |
|
|
|
|
---
|
|
|
|
## Integration Wiring Summary (47 connections)
|
|
|
|
| Connection | Status |
|
|
|------------|--------|
|
|
| Auth deps (`get_regular_user` / `get_current_admin`) on all Phase 3-5 endpoints | ✅ All wired (verified across documents.py, folders.py, shares.py, cloud.py, audit.py) |
|
|
| Phase 2 admin gap (admin JWT → 403 on `/api/documents/*`) | ✅ Closed in Phase 3 by `get_regular_user` |
|
|
| Atomic quota at upload (MinIO path) | ✅ Wired (`documents.py:342-346`) |
|
|
| Atomic quota decrement at delete (MinIO path) | ✅ Wired (`services/storage.py:168-175`) |
|
|
| Atomic quota decrement at delete (cloud path) | ❌ Not wired — MinIOBackend hardcoded |
|
|
| Cloud document content proxy (authenticated fetch → blob URL) | ✅ Wired |
|
|
| Admin delete: cloud cleanup → MinIO cleanup → DB delete | ✅ Wired (`admin.py:518-565`) |
|
|
| User-initiated doc delete: cloud provider cleanup | ❌ Not wired |
|
|
| Share recipient access to `/api/documents/{id}/content` | ✅ Wired (content proxy uses `get_storage_backend_for_document`) |
|
|
| Share recipient access to `GET /api/documents/{id}` metadata | ❌ Ownership check blocks recipients |
|
|
| Admin audit log JSON viewer | ✅ Wired end-to-end |
|
|
| Admin audit log CSV export | ❌ Bearer header dropped by `window.location.href` |
|
|
| Default storage backend selection UI | ❌ `updateDefaultStorage()` orphaned — no UI calls it |
|
|
| HKDF credential encryption through all cloud flows | ✅ Wired |
|
|
| `write_audit_log()` called from documents (3), shares (2), folders (3), cloud (4) | ✅ All wired |
|
|
| All API routers registered in `main.py` | ✅ Confirmed |
|
|
| `get_storage_backend_for_document()` factory in content proxy + Celery task | ✅ Wired |
|
|
| `SHARE-05 is_shared` computed in API → frontend | ❌ Computed, never rendered |
|
|
|
|
---
|
|
|
|
## Tech Debt Summary
|
|
|
|
**Phase 01:** No VERIFICATION.md. VALIDATION.md compliant.
|
|
|
|
**Phase 02:** VERIFICATION.md gaps_found (Phase 2 SC5 closed by Phase 3). No VALIDATION.md (Nyquist MISSING for Phase 2). 4 human verification items pending.
|
|
|
|
**Phase 03:** No VERIFICATION.md. VALIDATION.md draft (Nyquist PARTIAL). `Document.user_id` ORM nullable vs DB NOT NULL drift. `test_delete_decrements_quota` xfail — INTEGRATION=1 required.
|
|
|
|
**Phase 04:** No VERIFICATION.md. VALIDATION.md draft (Nyquist PARTIAL). AdminView.vue missing frontend role guard. Multiple UI gaps (FOLD-01, FOLD-05, SHARE-05, SHARE-03, SHARE-02 field names).
|
|
|
|
**Phase 05:** VERIFICATION.md human_needed (must-haves all confirmed). VALIDATION.md compliant. CLOUD-03 UI orphaned. CLOUD-05 REQUIRES_REAUTH only for OAuth providers.
|
|
|
|
**Phase 06.1:** VERIFICATION.md stale (superseded). VALIDATION.md compliant. STORE-06 manual gate pending. conftest.py WR-03 teardown gap.
|
|
|
|
**Cross-cutting:** REQUIREMENTS.md checkboxes not maintained (22 satisfied reqs still show `[ ]`). CLAUDE.md v2 hardening items (ES256, email_hmac, fgp fingerprint) not yet implemented.
|
|
|
|
---
|
|
|
|
## Remediation Guide
|
|
|
|
**Run Nyquist validation first (may close some verification gaps retroactively):**
|
|
```
|
|
/gsd:validate-phase 2
|
|
/gsd:validate-phase 3
|
|
/gsd:validate-phase 4
|
|
```
|
|
|
|
**Then insert closure phases for remaining blockers:**
|
|
|
|
```
|
|
/clear then:
|
|
/gsd:phase --insert 6.2 "Close v1 sharing + cloud-delete + CSV export gaps"
|
|
/gsd:discuss-phase 6.2
|
|
/gsd:plan-phase 6.2
|
|
/gsd:execute-phase 6.2
|
|
```
|
|
|
|
Suggested scope for Phase 6.2 (all small fixes, could ship as one phase):
|
|
|
|
| Fix | Files | Effort | REQ-IDs |
|
|
|-----|-------|--------|---------|
|
|
| Share recipient access: add share-grant check to `get_document()` | `documents.py:542` | ~15 lines | SHARE-02, DOC-01 |
|
|
| Fix SharedView.vue field names | `SharedView.vue` | ~10 lines | SHARE-02 |
|
|
| Cloud delete: use `get_storage_backend_for_document()` + gate quota decrement | `services/storage.py` | ~25 lines | STORE-06, SEC-09 |
|
|
| Audit CSV export: fetch() + Bearer + blob download | `AuditLogTab.vue:191` | ~20 lines | ADMIN-06 |
|
|
| Default storage UI: "Set as default" button in SettingsCloudTab | `SettingsCloudTab.vue` | ~30 lines | CLOUD-03 |
|
|
| Add `is_shared` indicator to DocumentCard.vue | `DocumentCard.vue` | ~15 lines | SHARE-05 |
|
|
| Add `doc_count` to `_folder_to_dict()` | `folders.py:65` | ~10 lines | FOLD-01 |
|
|
| Remove `v-if="currentFolderId"` gate from SearchBar | `FileManagerView.vue` | ~5 lines | FOLD-05 |
|
|
| Add `PATCH /api/shares/{id}` permission endpoint | `shares.py` | ~30 lines | SHARE-03 |
|
|
| Add frontend role guard to AdminView route or component | `router/index.js` or `AdminView.vue` | ~10 lines | — |
|
|
| Confirm `test_delete_decrements_quota` under INTEGRATION=1 | `test_quota.py:196` | manual | STORE-06 |
|
|
|
|
---
|
|
|
|
**Also available:**
|
|
- `cat .planning/v1.0-MILESTONE-AUDIT.md` — this report
|
|
- `/gsd:complete-milestone v1.0` — proceed with gaps noted (accept as tech debt)
|
|
|
|
---
|
|
|
|
_Audited: 2026-05-30_
|
|
_Auditor: Claude (gsd-audit-milestone)_
|
|
_Integration checker: gsd-integration-checker (155 tool calls, 47 connections verified)_
|