From b67e77dd6972a674de40d00b46f6f702335fbbda Mon Sep 17 00:00:00 2001 From: curo1305 Date: Tue, 23 Jun 2026 20:34:57 +0200 Subject: [PATCH] =?UTF-8?q?docs(13):=20add=20phase=2013=20planning=20artif?= =?UTF-8?q?acts=20=E2=80=94=20virtual=20local=20cloud=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plans 01–11, CONTEXT, PATTERNS, RESEARCH, REVIEW-FIX, and updated SUMMARY and CONTEXT for the virtual-local-cloud-operations phase. Co-Authored-By: Claude Sonnet 4.6 --- .../13-01-PLAN.md | 186 ++++++ .../13-02-PLAN.md | 176 ++++++ .../13-03-PLAN.md | 178 ++++++ .../13-04-PLAN.md | 178 ++++++ .../13-05-PLAN.md | 174 ++++++ .../13-06-PLAN.md | 164 +++++ .../13-07-PLAN.md | 163 +++++ .../13-08-PLAN.md | 172 ++++++ .../13-08-SUMMARY.md | 9 + .../13-09-PLAN.md | 170 ++++++ .../13-10-PLAN.md | 174 ++++++ .../13-11-PLAN.md | 175 ++++++ .../13-CONTEXT.md | 5 + .../13-PATTERNS.md | 559 ++++++++++++++++++ .../13-RESEARCH.md | 524 ++++++++++++++++ .../13-REVIEW-FIX.md | 107 ++++ 16 files changed, 3114 insertions(+) create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-01-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-02-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-03-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-04-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-05-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-06-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-07-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-08-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-09-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-10-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-11-PLAN.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md create mode 100644 .planning/phases/13-virtual-local-cloud-operations/13-REVIEW-FIX.md diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-01-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-01-PLAN.md new file mode 100644 index 0000000..56e7bfb --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-01-PLAN.md @@ -0,0 +1,186 @@ +--- +phase: "13" +plan: "01" +type: tdd +wave: 0 +depends_on: [] +files_modified: + - backend/tests/test_cloud_mutations.py + - backend/tests/test_cloud_reconnect.py + - backend/tests/test_cloud_audit.py + - backend/tests/test_cloud_backends.py + - backend/tests/test_cloud_provider_contract.py +autonomous: true +requirements: + - CONN-01 + - CONN-02 + - CONN-03 + - CLOUD-02 + - CLOUD-03 + - CLOUD-04 + - CLOUD-05 + - CLOUD-06 + - CLOUD-07 + - CLOUD-09 +must_haves: + truths: + - "Phase 13 backend work starts from failing contracts instead of inferred behavior." + - "All four providers are held to one mutation, reconnect, health, and secrecy contract before implementation." + - "Reconnect, refreshed-credential persistence, conflict typing, unsupported-operation disclosure, and metadata-only auditing are blocked by tests, not left implicit." + artifacts: + - path: "backend/tests/test_cloud_mutations.py" + provides: "Red endpoint and mutation-result coverage for open, preview, upload, create, rename, move, and delete." + - path: "backend/tests/test_cloud_reconnect.py" + provides: "Red reconnect, health, cache invalidation, and credential-refresh persistence coverage." + - path: "backend/tests/test_cloud_audit.py" + provides: "Red metadata-only audit assertions for successful cloud operations." + - path: "backend/tests/test_cloud_backends.py" + provides: "Provider-specific mutation and conflict/error normalization coverage." + - path: "backend/tests/test_cloud_provider_contract.py" + provides: "Canonical mutable-adapter contract coverage across Google Drive, OneDrive, Nextcloud, and WebDAV." + key_links: + - from: "mutable adapter methods" + to: "provider contract suites" + via: "normalized kind/reason result assertions" + pattern: "kind" + - from: "OneDrive token refresh success" + to: "cloud_connections.credentials_enc persistence" + via: "reconnect and content-operation regressions" + pattern: "refresh_token" +--- + + +Create the missing red backend and provider-contract suites for Phase 13 so reconnect, content access, uploads, folder mutations, audit secrecy, and provider normalization are fully specified before implementation. + +Purpose: Turn the resolved Phase 13 decisions into executable backend contracts. +Output: Failing backend test files and extensions that cover all ten Phase 13 requirements. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/ROADMAP.md +@.planning/REQUIREMENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md +@.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md +@backend/tests/test_cloud.py +@backend/tests/test_cloud_security.py +@backend/tests/test_cloud_backends.py +@backend/tests/test_cloud_provider_contract.py +@backend/tests/test_cloud_items.py + + +## Artifacts this phase produces + +- `backend/tests/test_cloud_mutations.py` — red endpoint contracts for typed `{kind, reason}` content and mutation results. +- `backend/tests/test_cloud_reconnect.py` — red reconnect, health, cache invalidation, and refreshed-credential persistence coverage. +- `backend/tests/test_cloud_audit.py` — red metadata-only audit coverage for successful cloud operations. +- Extended `backend/tests/test_cloud_backends.py` and `backend/tests/test_cloud_provider_contract.py` for four-provider mutable-operation coverage. + +## Pattern analogs + +- `backend/tests/test_cloud.py` — connection-ID API integration style and DB side-effect assertions. +- `backend/tests/test_cloud_security.py` — owner/admin/credential/SSRF negative coverage style. +- `backend/tests/test_cloud_backends.py` — provider-specific fixture and normalization style. +- `backend/tests/test_cloud_provider_contract.py` — canonical contract verification style for all providers. +- `backend/tests/test_cloud_items.py` — reconciliation and freshness truth assertions. + + + + + Task 1: Add red API and audit contracts for reconnect, content, and mutation flows + backend/tests/test_cloud_mutations.py, backend/tests/test_cloud_reconnect.py, backend/tests/test_cloud_audit.py + + - backend/tests/test_cloud.py + - backend/tests/test_cloud_security.py + - backend/tests/test_cloud_items.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + Create failing integration and security tests for D-02 through D-18 and CONN-01 through CONN-03. Require connection-ID reconnect patching of the existing row, explicit connection test behavior, automatic health re-evaluation after credential failures, cached metadata preservation during transient outages, and typed `kind` plus `reason` bodies for conflict, stale, offline, reauth-required, invalid-destination, unsupported-preview, and unsupported-operation outcomes. + + Cover binary-only preview per D-18, authorized download fallback for unsupported preview formats per D-02, metadata-only audit rows for successful reconnect/open/preview/upload/create/rename/move/delete, and disconnect semantics that remove credentials and connection-scoped metadata without touching provider files. Add negative cases proving raw provider URLs, access tokens, refresh tokens, `credentials_enc`, and provider-owned bytes never leak through responses, logs, or audit payloads. + + + - the new suites fail against the current codebase because the Phase 13 routes and semantics do not yet exist + - reconnect tests require patch-in-place row preservation and persisted refreshed credentials + - mutation tests require typed `kind` and `reason` bodies instead of Vue-side inference + - audit tests reject provider URLs, tokens, bytes, and document content in successful-operation metadata + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py tests/test_cloud_reconnect.py tests/test_cloud_audit.py -x + + Backend red suites now define the Phase 13 reconnect, content, mutation, and audit truth. + + + + Task 2: Extend provider contract suites for four-provider mutable-operation parity + backend/tests/test_cloud_backends.py, backend/tests/test_cloud_provider_contract.py + + - backend/tests/test_cloud_backends.py + - backend/tests/test_cloud_provider_contract.py + - backend/storage/cloud_base.py + - backend/storage/google_drive_backend.py + - backend/storage/onedrive_backend.py + - backend/storage/nextcloud_backend.py + - backend/storage/webdav_backend.py + - .planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md + + + Add red provider-level coverage for Google Drive, OneDrive, Nextcloud, and WebDAV so the mutable contract explicitly proves normalized create-folder, rename, move, delete, upload, preview-support, and explicit unsupported-operation disclosure behavior. Include D-17 broader Google Drive access expectations, OneDrive refreshed-credential handoff to the service layer, Nextcloud and WebDAV SSRF or redirect protections, collision normalization, trash-versus-permanent delete disclosure, and explicit unsupported preview or replace semantics where providers cannot honor a requested operation. + + Keep the contract provider-neutral: signature, caller identity, no direct `cloud_items` writes, no browse-time byte transfer, and no hidden overwrite path. Use the existing contract-test idiom so later implementation must satisfy one canonical interface instead of per-provider router branching. + + + - the provider suites fail until mutable contract methods and normalized result types exist + - every supported provider has explicit assertions for conflict/error normalization, refreshed-credential persistence handoff, SSRF defense, and unsupported-operation disclosure + - no new test encodes provider-specific API payloads in router-visible shapes + + + docker compose run --rm backend pytest -v tests/test_cloud_backends.py tests/test_cloud_provider_contract.py -x + + Four-provider mutable-operation parity is now blocked by red backend contract coverage. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| client → cloud API | Untrusted user input can trigger reconnect, content, and mutation operations. | +| cloud API → provider SDK/HTTP | Provider failures and URLs must be normalized before reaching API responses. | +| request transaction → audit log | Successful operations must log metadata only, with no secret or byte leakage. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-01 | E | reconnect/content/mutation routes | mitigate | Red owner-scope tests cover wrong-user, admin, and cross-connection access paths. | +| T-13-02 | I | content and preview responses | mitigate | Red tests inspect bodies, headers, and audit rows for provider URLs, tokens, and `credentials_enc`. | +| T-13-03 | T | provider conflict handling | mitigate | Red tests require typed `kind`/`reason` conflict bodies and no silent overwrite path. | +| T-13-04 | T | Nextcloud/WebDAV outbound calls | mitigate | Provider contract tests require SSRF validation and controlled redirect handling. | +| T-13-05 | R | audit trail accuracy | mitigate | Audit tests require metadata-only rows for successful operations and no false overwrite events. | + + + +- Confirm the new backend suites fail in the expected places. +- Confirm every backend verification command uses `docker compose run --rm backend pytest ...`. +- Confirm provider-level mutable-operation coverage exists for Google Drive, OneDrive, Nextcloud, and WebDAV. + + + +- Phase 13 backend implementation cannot start without failing tests for reconnect, content, upload, folder mutations, and audit secrecy. +- Provider-level contract coverage now includes lifecycle, content, upload, token persistence, unsupported-capability disclosure, conflict normalization, and SSRF protections. +- No host `pytest` command remains in this plan. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-01-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-02-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-02-PLAN.md new file mode 100644 index 0000000..fe8d621 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-02-PLAN.md @@ -0,0 +1,176 @@ +--- +phase: "13" +plan: "02" +type: tdd +wave: 0 +depends_on: [] +files_modified: + - frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js + - frontend/src/views/__tests__/CloudFolderOpenPreview.test.js + - frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js + - frontend/src/stores/__tests__/cloudConnections.test.js + - frontend/src/views/__tests__/CloudFolderView.test.js + - frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js +autonomous: true +requirements: + - CONN-01 + - CONN-02 + - CONN-03 + - CLOUD-02 + - CLOUD-03 + - CLOUD-04 + - CLOUD-05 + - CLOUD-06 + - CLOUD-07 + - CLOUD-09 +must_haves: + truths: + - "Phase 13 frontend work starts from failing shared-browser and health-state regressions instead of ad hoc UI behavior." + - "Queue pause or resume, binary-only preview fallback, reconnect health, and no-probe-on-navigation are all specified before implementation." + - "Browser-adjacent health and Settings diagnostics stay aligned because the same red tests cover both surfaces." + artifacts: + - path: "frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js" + provides: "Red sequential queue and mutation-dialog coverage in the shared browser." + - path: "frontend/src/views/__tests__/CloudFolderOpenPreview.test.js" + provides: "Red authorized preview and download fallback coverage." + - path: "frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js" + provides: "Red Test/Reconnect/Disconnect/consent-copy coverage in Settings." + - path: "frontend/src/stores/__tests__/cloudConnections.test.js" + provides: "Red health-state translation, auto-test, and no-probe-on-navigation coverage." + key_links: + - from: "queue conflict and error bodies" + to: "StorageBrowser pause and resume UI" + via: "shared-browser red tests" + pattern: "paused_conflict" + - from: "credential failure responses" + to: "browser and Settings health state" + via: "store and rendered-flow red tests" + pattern: "requires_reauth" +--- + + +Create the missing red frontend and store suites for shared cloud queue behavior, authorized preview, actionable health UX, broader Google Drive consent copy, and the no-probe-on-navigation invariant. + +Purpose: Lock the shared browser and health UX before backend behavior is wired through it. +Output: Failing frontend tests that define the only acceptable Phase 13 UI and store behavior. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/ROADMAP.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md +@.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md +@frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js +@frontend/src/views/__tests__/CloudFolderView.test.js +@frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js +@frontend/src/components/settings/__tests__/SettingsCloudTab.test.js +@frontend/src/stores/__tests__/cloudConnections.test.js + + +## Artifacts this phase produces + +- `frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js` — red shared queue and mutation-dialog coverage. +- `frontend/src/views/__tests__/CloudFolderOpenPreview.test.js` — red authorized preview and download fallback coverage. +- `frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js` — red health, reconnect, disconnect, and broader-scope consent coverage. +- Extended `frontend/src/stores/__tests__/cloudConnections.test.js`, `frontend/src/views/__tests__/CloudFolderView.test.js`, and `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` for no-probe and health-state regressions. + +## Pattern analogs + +- `frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js` — emitted-event and capability UI assertions. +- `frontend/src/views/__tests__/CloudFolderView.test.js` — thin-view orchestration style. +- `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` — rendered shared-browser flow style. +- `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` — settings action and confirmation style. +- `frontend/src/stores/__tests__/cloudConnections.test.js` — store mapping and reset-behavior style. + + + + + Task 1: Add red shared-browser tests for queue, preview, and fallback download behavior + frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js, frontend/src/views/__tests__/CloudFolderOpenPreview.test.js, frontend/src/views/__tests__/CloudFolderView.test.js + + - frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js + - frontend/src/views/__tests__/CloudFolderView.test.js + - frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + Create failing tests that require D-01 through D-04 and D-18 exactly: cloud upload runs as a sequential queue, pauses the whole queue on a typed conflict or error body, preserves remaining items, and resumes only after Keep both, Replace, Skip, Retry, or Cancel all. Add open and preview tests that require binary-only in-app preview, ownership-checked download fallback for unsupported formats, and zero `window.open()` or raw provider URL usage. Extend the thin-view suite so CloudFolderView must remain a data provider over `StorageBrowser`, not a parallel cloud grid. + + + - the new queue and preview tests fail against the current placeholder cloud handlers + - red tests require backend-authored conflict/error typing instead of Vue-side guessing + - unsupported preview formats are required to fall back to authorized download rather than Office-native or Google Workspace preview + + + cd frontend && npm run test -- --run src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js src/views/__tests__/CloudFolderOpenPreview.test.js src/views/__tests__/CloudFolderView.test.js + + Frontend red tests now define the only acceptable shared queue and preview behavior. + + + + Task 2: Add red store and health-flow tests for reconnect, broader Google consent, and no navigation probe + frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js, frontend/src/stores/__tests__/cloudConnections.test.js, frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js + + - frontend/src/components/settings/__tests__/SettingsCloudTab.test.js + - frontend/src/stores/__tests__/cloudConnections.test.js + - frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js + - .planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + Add failing tests for D-12 through D-17: compact actionable health beside the cloud browser, fuller diagnostics plus explicit Test and Reconnect controls in Settings, automatic health retest after connect, reconnect, and credential-related failures, preserved stale metadata during transient outages, and an explicit invariant that ordinary folder navigation never triggers a provider health probe. Make the Google Drive consent copy explicit about broader storage access so the expanded Phase 13 scope cannot ship silently. + + Keep the behavior store-led: the store must translate server health states once for both browser and Settings, record when reconnect should refresh the current folder, and distinguish transient offline state from reauthentication. The rendered-flow test should confirm the browser keeps cached items visible while warning state and reconnect actions are shown. + + + - health-flow tests fail unless automatic post-failure retest and no-probe-on-navigation behavior are implemented + - the settings suite fails unless broader Google Drive access is explained in the user-facing consent/reconnect copy + - rendered-flow tests fail unless stale metadata remains visible during warning and reconnect states + + + cd frontend && npm run test -- --run src/components/settings/__tests__/SettingsCloudTab.health.test.js src/stores/__tests__/cloudConnections.test.js src/views/__tests__/CloudFolderRenderedFlow.test.js + + Frontend red tests now lock health-state, reconnect, consent, and no-probe behavior before implementation. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| server health state → store/UI | Trusted backend status must not be replaced by client-side guesswork. | +| user interaction → shared browser dialogs | Conflict and delete choices must remain explicit and auditable. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-06 | T | cloud health UI | mitigate | Red tests require no background probe on ordinary navigation and explicit server-driven health mapping. | +| T-13-07 | I | preview and download UI | mitigate | Red tests forbid raw provider URLs and require authorized preview/download helpers only. | +| T-13-08 | R | queue conflict decisions | mitigate | Red tests require explicit pause/resume choices and no silent replace or hidden cancel path. | +| T-13-09 | S | reconnect/consent UX | mitigate | Red settings tests require explicit broader Google scope copy and reconnect affordances. | + + + +- Confirm the queue, preview, health, store, and rendered-flow suites fail in the expected places. +- Confirm no frontend test assumes cloud-only layout or browser-direct provider URLs. +- Confirm the no-probe-on-navigation invariant is explicitly asserted. + + + +- Phase 13 frontend implementation is blocked by failing shared-browser, store, and Settings regressions. +- Broader Google Drive access, binary-only preview, and health re-evaluation rules are concretely encoded. +- No backend host-venv assumption appears anywhere in this plan. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-02-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-03-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-03-PLAN.md new file mode 100644 index 0000000..c6a8be2 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-03-PLAN.md @@ -0,0 +1,178 @@ +--- +phase: "13" +plan: "03" +type: execute +wave: 1 +depends_on: + - "13-01" +files_modified: + - backend/storage/cloud_base.py + - backend/storage/cloud_backend_factory.py + - backend/services/cloud_operations.py + - backend/services/cloud_items.py + - backend/tests/test_cloud_backends.py + - backend/tests/test_cloud_provider_contract.py + - backend/tests/test_cloud_reconnect.py +autonomous: true +requirements: + - CONN-01 + - CONN-02 + - CONN-03 + - CLOUD-02 + - CLOUD-03 + - CLOUD-04 + - CLOUD-05 + - CLOUD-06 + - CLOUD-07 + - CLOUD-09 +must_haves: + truths: + - "Provider-neutral mutable-operation contracts exist before routes or UI are wired." + - "Providers can hand refreshed credentials upward without writing the database themselves." + - "Reconciliation and freshness remain centralized in `cloud_items.py` even for mutable work." + artifacts: + - path: "backend/storage/cloud_base.py" + provides: "Normalized mutable-operation result types, health states, and provider error vocabulary." + - path: "backend/services/cloud_operations.py" + provides: "Owner-scoped orchestration for reconnect, content, upload, and folder mutation work." + key_links: + - from: "provider refresh outcomes" + to: "encrypted credential persistence" + via: "cloud_operations orchestration" + pattern: "credentials" + - from: "mutable provider results" + to: "cloud_items reconciliation" + via: "single service-layer integration point" + pattern: "reconcile" +--- + + +Build the Phase 13 backend foundation: the mutable cloud contract, provider-factory assertions, and the service-layer orchestration seam that owns credential refresh handoff, stale classification, and centralized reconciliation. + +Purpose: Create the backend contract layer every later Phase 13 route and provider change depends on. +Output: A provider-neutral mutable adapter interface and a single orchestration module. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md +@.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md +@backend/storage/cloud_base.py +@backend/storage/cloud_backend_factory.py +@backend/services/cloud_items.py +@backend/tests/test_cloud_backends.py +@backend/tests/test_cloud_provider_contract.py +@backend/tests/test_cloud_reconnect.py + + +## Artifacts this phase produces + +- `backend/storage/cloud_base.py` — mutable-operation dataclasses, health/result enums, and domain exceptions. +- `backend/services/cloud_operations.py` — owner-scoped orchestration for reconnect, content, upload, and folder mutations. +- `backend/storage/cloud_backend_factory.py` — factory assertions for the mutable contract. + +## Pattern analogs + +- `backend/services/cloud_items.py` — centralized reconciliation and freshness ownership. +- `backend/storage/cloud_backend_factory.py` — provider construction and interface assertion boundary. +- `backend/tests/test_cloud_provider_contract.py` — canonical contract-verification style. + + + + + Task 1: Extend the shared cloud contract for mutable operations and normalized outcomes + backend/storage/cloud_base.py, backend/storage/cloud_backend_factory.py, backend/tests/test_cloud_backends.py, backend/tests/test_cloud_provider_contract.py + + - backend/storage/cloud_base.py + - backend/storage/cloud_backend_factory.py + - backend/tests/test_cloud_backends.py + - backend/tests/test_cloud_provider_contract.py + - .planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md + + + - Test 1: providers expose one mutable-operation contract with normalized success, conflict, stale, offline, and unsupported results + - Test 2: providers can hand refreshed credentials upward without persisting them directly + - Test 3: preview support is explicit and binary-only rather than inferred from provider MIME quirks + + + Extend `backend/storage/cloud_base.py` with the mutable-operation interface and normalized result dataclasses for health, reconnect, preview support, upload conflict, create/rename collision, move validation, delete disclosure, and unsupported-operation reporting. Keep `kind` and `reason` vocabulary centralized here so every provider and route shares the same typed contract. Update `backend/storage/cloud_backend_factory.py` to assert the new interface without introducing provider-name switches in routers or services. + + + - the provider contract suites from 13-01 pass for normalized signatures and result shapes + - binary-only preview support is explicit in the shared contract + - providers are not given permission to write audit rows or `cloud_items` directly + + + docker compose run --rm backend pytest -v tests/test_cloud_backends.py tests/test_cloud_provider_contract.py -x + + The codebase now has one canonical mutable cloud contract for all later Phase 13 backend work. + + + + Task 2: Create the cloud operations orchestration seam without breaking freshness ownership + backend/services/cloud_operations.py, backend/services/cloud_items.py, backend/tests/test_cloud_reconnect.py + + - backend/services/cloud_items.py + - backend/tests/test_cloud_reconnect.py + - backend/services/audit.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: the orchestration layer resolves owned connections and classifies reauth versus transient offline states + - Test 2: refreshed credentials persist only above the provider boundary + - Test 3: reconnect and mutation work invalidate caches and route back through centralized reconciliation rather than direct ORM writes + + + Create `backend/services/cloud_operations.py` as the single service-layer seam for D-13 through D-16. It must resolve the owned connection, decrypt credentials only at the provider boundary, accept refreshed credentials back from providers for encrypted persistence, classify credential failures versus transient outages, and funnel listing invalidation or follow-up refresh through `cloud_items.py`. Keep service exceptions domain-specific or `ValueError` only; routers remain responsible for translating them to `HTTPException`. + + + - reconnect red tests pass at the orchestration layer + - persisted refreshed credentials are handled only in the service layer, never inside providers + - `cloud_items.py` remains the sole reconciliation and freshness authority + + + docker compose run --rm backend pytest -v tests/test_cloud_reconnect.py tests/test_cloud_provider_contract.py -x + + The backend now has a single orchestration seam that later routes and providers can safely build on. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| router/service → provider | Provider exceptions and refreshed credentials must be normalized before leaving the backend boundary. | +| service → database | Only orchestration code may persist refreshed credentials or trigger reconciliation. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-10 | T | mutable contract layer | mitigate | Centralize `kind`/`reason` result vocabulary in `cloud_base.py` and verify with provider contract tests. | +| T-13-11 | I | refreshed credentials | mitigate | Persist only encrypted credentials above the provider boundary; verify in reconnect tests. | +| T-13-12 | T | reconciliation path | mitigate | Route all mutable follow-up work through `cloud_items.py`; forbid direct provider ORM writes. | + + + +- Pass the provider contract and reconnect suites introduced in Wave 0. +- Confirm every backend verify command remains containerized. +- Confirm the orchestration seam is the only place where refreshed credentials can be persisted. + + + +- Mutable-operation contracts and the orchestration seam exist before route work begins. +- Provider-level contract coverage now has a real shared interface to validate. +- Freshness and reconciliation ownership remain centralized instead of fragmenting across routers or adapters. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-03-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-04-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-04-PLAN.md new file mode 100644 index 0000000..d3a737f --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-04-PLAN.md @@ -0,0 +1,178 @@ +--- +phase: "13" +plan: "04" +type: execute +wave: 2 +depends_on: + - "13-01" + - "13-03" +files_modified: + - backend/api/cloud/operations.py + - backend/api/cloud/connections.py + - backend/api/cloud/schemas.py + - frontend/src/api/cloud.js + - backend/tests/test_cloud_mutations.py + - backend/tests/test_cloud_reconnect.py + - backend/tests/test_cloud_security.py +autonomous: true +requirements: + - CONN-01 + - CONN-02 + - CONN-03 + - CLOUD-02 +must_haves: + truths: + - "Reconnect is a connection-ID patch flow, not a second-row account insertion." + - "Google Drive reconnect and connect flows explicitly request broader Phase 13 access." + - "Open, preview, and fallback download use DocuVault-controlled endpoints with typed result bodies and binary-only preview rules." + artifacts: + - path: "backend/api/cloud/operations.py" + provides: "Owner-scoped test, open, preview, and fallback download endpoints." + - path: "backend/api/cloud/connections.py" + provides: "Connection-ID reconnect patching and broader Drive OAuth scope handling." + - path: "backend/api/cloud/schemas.py" + provides: "Whitelisted typed health, reconnect, and content result schemas." + key_links: + - from: "OAuth reconnect state" + to: "existing cloud_connections row" + via: "connection-ID patching" + pattern: "connection_id" + - from: "preview result typing" + to: "frontend shared browser handlers" + via: "whitelisted kind/reason response bodies" + pattern: "reason" +--- + + +Implement the connection-ID reconnect, explicit health-test, and authorized content-route slice of Phase 13, including broader Google Drive scope handling and binary-only preview fallback rules. + +Purpose: Put the owner-scoped route layer in place before upload and folder mutations build on it. +Output: Typed reconnect and content endpoints plus centralized frontend API helpers. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md +@.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md +@backend/api/cloud/connections.py +@backend/api/cloud/schemas.py +@frontend/src/api/cloud.js +@backend/tests/test_cloud_mutations.py +@backend/tests/test_cloud_reconnect.py +@backend/tests/test_cloud_security.py + + +## Artifacts this phase produces + +- `backend/api/cloud/operations.py` — owner-scoped explicit test and content-access routes. +- `backend/api/cloud/connections.py` — connection-ID reconnect patching and scope-aware OAuth handling. +- `backend/api/cloud/schemas.py` — typed health, reconnect, preview, and download fallback bodies. +- `frontend/src/api/cloud.js` — centralized client helpers for the new route family. + +## Pattern analogs + +- `backend/api/cloud/browse.py` — owner-scoped connection-ID route structure. +- `backend/api/documents/content.py` — authorized streaming and controlled error translation. +- `backend/api/cloud/connections.py` — existing connect/update/disconnect patterns. +- `frontend/src/api/cloud.js` — centralized client boundary for cloud routes. + + + + + Task 1: Patch reconnect and explicit test flows onto the existing connection row + backend/api/cloud/connections.py, backend/api/cloud/schemas.py, frontend/src/api/cloud.js, backend/tests/test_cloud_reconnect.py, backend/tests/test_cloud_security.py + + - backend/api/cloud/connections.py + - backend/api/cloud/schemas.py + - frontend/src/api/cloud.js + - backend/tests/test_cloud_reconnect.py + - .planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md + + + - Test 1: reconnect patches the existing owned connection row by ID and invalidates caches without deleting metadata + - Test 2: explicit test and credential-failure recovery classify reauth versus transient offline states + - Test 3: Google Drive OAuth uses the broader Phase 13 access scope and exposes that state through controlled responses + + + Update the OAuth initiation and callback flow so reconnect is an existing-connection repair path keyed by connection ID rather than a new-row insertion. Persist refreshed encrypted credentials in place, invalidate connection-scoped capability and listing caches, and mark cached metadata stale while a refresh follows D-14. Preserve D-15 by keeping transient outages non-destructive. Make the Google Drive path request the broader Phase 13 scope explicitly and keep the response schema typed so the frontend can render honest consent and health states without parsing provider payloads. + + + - reconnect and security tests pass without creating a second connection row + - broader Google Drive access is explicit in the backend flow rather than implicit or undocumented + - transient offline and reauth-required states are distinguishable and credential-safe + + + docker compose run --rm backend pytest -v tests/test_cloud_reconnect.py tests/test_cloud_security.py -k "reconnect or health or credential or scope" -x + + Reconnect and health routes now satisfy the connection-ID, scope, and cache-invalidating Phase 13 contract. + + + + Task 2: Add authorized open, preview, and fallback download routes with typed bodies + backend/api/cloud/operations.py, backend/api/cloud/schemas.py, frontend/src/api/cloud.js, backend/tests/test_cloud_mutations.py, backend/tests/test_cloud_security.py + + - backend/api/documents/content.py + - backend/api/cloud/schemas.py + - backend/tests/test_cloud_mutations.py + - backend/tests/test_cloud_security.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: authorized content routes expose binary-only preview and typed unsupported-preview fallback + - Test 2: preview and download responses never expose raw provider URLs or credentials + - Test 3: ordinary folder navigation still does not trigger a provider health probe + + + Add `backend/api/cloud/operations.py` and expose owner-scoped open, preview, and authorized download fallback routes that call the service layer only. Use typed response and error bodies with stable `kind` and `reason` codes, limit preview support to the Phase 13 binary matrix, and keep unsupported formats on the download fallback path. Extend `frontend/src/api/cloud.js` with centralized helpers for the new routes so later UI work does not call raw URLs or construct provider-specific content paths. + + + - content and security suites pass with typed route outputs + - preview support remains explicitly binary-only and does not introduce Office-native or Google Workspace rendering + - no content route leaks provider URLs, tokens, or decrypted credentials + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py tests/test_cloud_security.py -k "open or preview or download or content" -x + + The route layer now exposes safe reconnect and content endpoints that later upload and UI plans can reuse. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| browser → reconnect/content routes | Untrusted requests must stay owner-scoped and CSRF-protected. | +| cloud route → provider bytes | Provider content must stay behind DocuVault authorization and typed error shaping. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-13 | E | reconnect flow | mitigate | Connection-ID patching and owner-scoped lookup are verified in reconnect and security tests. | +| T-13-14 | I | preview/download routes | mitigate | Typed route outputs plus content/security tests forbid provider URL or token leakage. | +| T-13-15 | T | navigation health behavior | mitigate | Reconnect tests and later UI tests assert no health probe on ordinary folder navigation. | + + + +- Pass the reconnect, content, and security suites introduced in Wave 0. +- Confirm every backend verification command is containerized. +- Confirm binary-only preview and broader Drive scope handling are explicitly encoded in the route layer. + + + +- Reconnect now patches the existing connection row and exposes honest health states. +- The backend has safe open, preview, and fallback download routes with typed result bodies. +- Phase 13 now encodes broader Google Drive access, binary-only preview, and no-probe-on-navigation rules concretely. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-04-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-05-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-05-PLAN.md new file mode 100644 index 0000000..7169548 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-05-PLAN.md @@ -0,0 +1,174 @@ +--- +phase: "13" +plan: "05" +type: execute +wave: 3 +depends_on: + - "13-01" + - "13-03" + - "13-04" +files_modified: + - backend/api/cloud/operations.py + - backend/api/cloud/schemas.py + - backend/services/cloud_operations.py + - backend/storage/google_drive_backend.py + - backend/storage/onedrive_backend.py + - backend/storage/webdav_backend.py + - backend/tests/test_cloud_mutations.py + - backend/tests/test_cloud_backends.py + - backend/tests/test_cloud_provider_contract.py +autonomous: true +requirements: + - CLOUD-03 +must_haves: + truths: + - "Cloud upload uses typed conflict and error bodies instead of hidden overwrite semantics." + - "Provider upload differences stay behind the mutable adapter and service contract." + - "Refreshed provider credentials can be handed upward during upload work without provider-owned database writes." + artifacts: + - path: "backend/api/cloud/operations.py" + provides: "Connection-ID upload endpoint with typed conflict and retry result bodies." + - path: "backend/services/cloud_operations.py" + provides: "Upload dispatch that normalizes keep-both, replace, retryable error, and unsupported-replace outcomes." + key_links: + - from: "provider upload result" + to: "shared queue resume decisions" + via: "typed kind/reason response bodies" + pattern: "conflict" + - from: "refreshed provider credentials" + to: "service-layer persistence handoff" + via: "upload success and retry paths" + pattern: "credentials" +--- + + +Implement the bounded backend upload mechanics slice of Phase 13: connection-ID upload routing, typed conflict and retry bodies, provider-normalized keep-both or replace handling, and refreshed-credential handoff without yet folding in reconcile or audit follow-through. + +Purpose: Finish the provider and route mechanics the shared upload queue needs before the authoritative success path is layered on top. +Output: Upload-capable provider adapters, route and service dispatch, and passing backend upload mechanics suites. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md +@.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md +@backend/api/cloud/operations.py +@backend/services/cloud_operations.py +@backend/storage/google_drive_backend.py +@backend/storage/onedrive_backend.py +@backend/storage/webdav_backend.py + + +## Artifacts this phase produces + +- Upload support in `backend/api/cloud/operations.py` and `backend/services/cloud_operations.py`. +- Provider-normalized keep-both, replace, retry, skip, and unsupported-replace semantics across Google Drive, OneDrive, Nextcloud-via-WebDAV, and generic WebDAV. +- Passing upload and provider-contract suites for the mechanics half of `CLOUD-03`. + +## Pattern analogs + +- `backend/api/documents/upload.py` — multipart upload proxy pattern. +- `backend/tests/test_cloud_backends.py` and `backend/tests/test_cloud_provider_contract.py` — four-provider normalization assertions. +- `backend/services/cloud_operations.py` from 13-03 — refreshed-credential handoff and typed domain-result pattern. + + + + + Task 1: Implement provider-normalized upload conflict semantics across the adapter boundary + backend/storage/google_drive_backend.py, backend/storage/onedrive_backend.py, backend/storage/webdav_backend.py, backend/tests/test_cloud_backends.py, backend/tests/test_cloud_provider_contract.py + + - backend/storage/google_drive_backend.py + - backend/storage/onedrive_backend.py + - backend/storage/webdav_backend.py + - backend/tests/test_cloud_backends.py + - backend/tests/test_cloud_provider_contract.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: upload can return success, typed conflict, typed retryable error, or explicit unsupported-replace results + - Test 2: keep-both naming is authoritative and inserts the counter before the file extension + - Test 3: provider-specific conflict semantics normalize into one shared contract across all four providers + + + Implement D-03 and D-04 inside the provider layer so Google Drive, OneDrive, and the shared WebDAV path normalize upload conflicts, replace support, keep-both suffixing, and retryable transient failures into one mutable-operation contract. Preserve broader Google Drive access assumptions, OneDrive refreshed-credential handoff, and SSRF validation on every WebDAV upload request. Keep Nextcloud on the shared WebDAV mutation path unless a narrow override is strictly required. + + + - upload provider suites pass for all four supported providers + - keep-both naming is authoritative and consistent across providers + - provider code returns typed results and never writes `cloud_items`, audit rows, or router-visible provider payloads directly + + + docker compose run --rm backend pytest -v tests/test_cloud_backends.py tests/test_cloud_provider_contract.py -k "upload or replace or conflict" -x + + The adapter layer now exposes typed, provider-normalized upload mechanics for the shared queue. + + + + Task 2: Expose connection-ID upload dispatch with typed route and service results + backend/api/cloud/operations.py, backend/api/cloud/schemas.py, backend/services/cloud_operations.py, backend/tests/test_cloud_mutations.py + + - backend/api/cloud/operations.py + - backend/api/cloud/schemas.py + - backend/services/cloud_operations.py + - backend/tests/test_cloud_mutations.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: the upload route returns typed success, conflict, retryable error, and unsupported-replace bodies + - Test 2: the service layer owns refreshed-credential persistence handoff above the provider boundary + - Test 3: queue-control semantics come from backend-authored results instead of Vue-side inference + + + Wire the connection-ID upload endpoint through `backend/services/cloud_operations.py` and `backend/api/cloud/schemas.py` so the route returns stable `kind` and `reason` bodies for Keep both, Replace, Skip, Retry, Cancel all, or unsupported-replace outcomes. Keep this plan bounded to mechanics: normalize provider results, surface refreshed credentials upward for encrypted persistence, and leave centralized reconciliation plus metadata-only audit follow-through to the next plan. + + + - upload route and mutation suites pass for typed mechanics and credential-safe results + - queue-control semantics are backend-authored and credential-safe + - this plan does not yet add upload-specific reconciliation or success-audit behavior outside the existing foundations + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py tests/test_cloud_backends.py tests/test_cloud_provider_contract.py -k "upload or replace or conflict" -x + + The backend now exposes the bounded upload mechanics the next follow-through plan can finish. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| browser upload → provider write | User content enters a provider-owned mutation path through DocuVault authorization only. | +| provider SDK/HTTP → route result | Provider conflict, retry, and replace behavior must be normalized before Vue consumes it. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-16 | T | upload conflict handling | mitigate | Typed upload result bodies and provider-contract tests forbid silent overwrite behavior. | +| T-13-17 | I | route result typing | mitigate | Upload route tests verify no provider URLs, tokens, or raw payloads escape typed responses. | +| T-13-18 | T | provider boundary | mitigate | WebDAV SSRF and OneDrive refresh handoff remain covered by provider suites. | + + + +- Pass the bounded backend upload and provider-contract suites. +- Confirm all backend pytest invocations are direct containerized commands. +- Confirm upload mechanics stop at typed route and service results, leaving reconcile and audit follow-through to the next plan. + + + +- The backend can upload into the current cloud folder with typed conflict and retry semantics. +- Provider-specific differences remain behind the shared contract and service layer. +- The upload mechanics plan stays within 9 files and leaves reconcile plus audit follow-through for the next bounded plan. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-05-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-06-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-06-PLAN.md new file mode 100644 index 0000000..5d42a33 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-06-PLAN.md @@ -0,0 +1,164 @@ +--- +phase: "13" +plan: "06" +type: execute +wave: 4 +depends_on: + - "13-05" +files_modified: + - backend/api/cloud/operations.py + - backend/services/cloud_operations.py + - backend/services/cloud_items.py + - backend/tests/test_cloud_mutations.py + - backend/tests/test_cloud_audit.py +autonomous: true +requirements: + - CLOUD-03 + - CLOUD-09 +must_haves: + truths: + - "Successful cloud upload does not return before authoritative metadata reconciliation completes." + - "Upload success writes metadata-only audit rows, while skipped, canceled, or failed queue decisions stay silent." + - "Upload freshness and navigation updates still route through `cloud_items.py` rather than a second reconcile path." + artifacts: + - path: "backend/services/cloud_operations.py" + provides: "Authoritative upload success handling that routes back through centralized reconciliation and audit helpers." + - path: "backend/services/cloud_items.py" + provides: "Stable folder freshness and item identity updates after upload success." + key_links: + - from: "authoritative upload success" + to: "cloud_items reconciliation" + via: "service-layer follow-through before response return" + pattern: "upload" + - from: "authoritative upload success" + to: "metadata-only audit rows" + via: "same-transaction audit helper call" + pattern: "cloud.item" +--- + + +Finish the bounded upload follow-through slice of Phase 13: reconcile successful uploads immediately, refresh affected folder state truthfully, and emit metadata-only audit rows only on authoritative success. + +Purpose: Complete `CLOUD-09` for uploads before the shared frontend queue is wired to the backend. +Output: Passing upload reconciliation and audit suites with no duplicate reconcile path. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@backend/api/cloud/operations.py +@backend/services/cloud_operations.py +@backend/services/cloud_items.py +@backend/services/audit.py +@backend/tests/test_cloud_mutations.py +@backend/tests/test_cloud_audit.py + + +## Artifacts this phase produces + +- Upload success follow-through in `backend/services/cloud_operations.py`. +- Authoritative upload reconciliation and folder freshness updates through `backend/services/cloud_items.py`. +- Passing upload mutation and audit suites for `CLOUD-03` and `CLOUD-09`. + +## Pattern analogs + +- `backend/services/cloud_items.py` — single reconciliation and freshness authority. +- `backend/services/audit.py` — metadata-only audit helper used inside the caller transaction. + + + + + Task 1: Route authoritative upload success through centralized reconciliation before returning + backend/api/cloud/operations.py, backend/services/cloud_operations.py, backend/services/cloud_items.py, backend/tests/test_cloud_mutations.py + + - backend/services/cloud_operations.py + - backend/services/cloud_items.py + - backend/tests/test_cloud_mutations.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: successful upload updates current-folder navigation through centralized reconciliation before success returns + - Test 2: upload freshness state stays truthful and does not bypass `cloud_items.py` + - Test 3: failed, skipped, and canceled queue decisions do not mutate listing freshness as success + + + Feed authoritative upload success back through `backend/services/cloud_items.py` immediately, update affected folder freshness truthfully, and keep stable row identity for the newly visible item before the route returns success. Do not bypass centralized reconciliation, do not mutate quota, and do not let skipped or canceled queue decisions look like successful overwrites. + + + - upload mutation suites pass with reconcile-before-return behavior + - navigation refresh is tied to authoritative success only + - upload follow-through still uses the single shared reconciliation path + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py -k "upload" -x + + Successful uploads now refresh authoritative metadata and freshness before the API reports success. + + + + Task 2: Emit metadata-only audit rows only for authoritative upload success + backend/services/cloud_operations.py, backend/tests/test_cloud_mutations.py, backend/tests/test_cloud_audit.py + + - backend/services/cloud_operations.py + - backend/services/audit.py + - backend/tests/test_cloud_audit.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: successful upload writes metadata-only audit rows in the caller transaction + - Test 2: failed, skipped, canceled, and retry-needed queue outcomes do not produce false success audit events + - Test 3: audit rows never include provider URLs, tokens, bytes, or document text + + + Write metadata-only audit rows in the same request transaction only after authoritative upload success has reconciled metadata. Preserve the Phase 13 secrecy rules by keeping provider URLs, tokens, decrypted credentials, bytes, and document content out of the audit payload. Treat Skip, Cancel all, retryable error, and conflict-pause outcomes as non-success paths that must not emit a success audit event. + + + - upload audit suites pass + - audit behavior is tied to authoritative success only + - no upload audit row includes provider URLs, tokens, bytes, or document text + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py tests/test_cloud_audit.py -k "upload or audit" -x + + Upload success now satisfies the metadata refresh and metadata-only audit half of `CLOUD-09`. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| upload success → metadata state | Only authoritative success may update cloud metadata or folder freshness. | +| upload success → audit log | Only authoritative success may write an audit row, and it must stay metadata-only. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-19 | T | upload reconciliation | mitigate | Mutation tests require reconcile-before-return and forbid success-state bypasses. | +| T-13-20 | I | upload audit payloads | mitigate | Audit suites verify metadata-only rows for successful uploads only. | +| T-13-21 | R | queue decision logging | mitigate | Tests require Skip, Cancel all, conflict-pause, and retry-needed outcomes to avoid false success events. | + + + +- Pass the upload mutation and audit suites. +- Confirm every backend pytest invocation is a direct `docker compose run --rm backend pytest ...` command. +- Confirm upload success routes through centralized reconciliation before audit and before response return. + + + +- Successful uploads refresh navigation and folder freshness through the shared reconciliation layer. +- Successful uploads produce metadata-only audit rows promptly enough to satisfy `CLOUD-09`. +- The upload follow-through plan stays bounded to 5 files and does not absorb the frontend queue work. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-06-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-07-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-07-PLAN.md new file mode 100644 index 0000000..9a368e0 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-07-PLAN.md @@ -0,0 +1,163 @@ +--- +phase: "13" +plan: "07" +type: execute +wave: 5 +depends_on: + - "13-02" + - "13-04" + - "13-06" +files_modified: + - frontend/src/api/cloud.js + - frontend/src/views/CloudFolderView.vue + - frontend/src/components/storage/StorageBrowser.vue + - frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js + - frontend/src/views/__tests__/CloudFolderOpenPreview.test.js + - frontend/src/views/__tests__/CloudFolderView.test.js +autonomous: true +requirements: + - CLOUD-02 + - CLOUD-03 +must_haves: + truths: + - "Cloud upload stays on the shared browser path and never forks a cloud-only queue UI." + - "The queue is sequential and pauses on typed conflict or error bodies until the user explicitly resumes or cancels all." + - "Binary-only preview and authorized download fallback are driven through centralized client helpers, not provider URLs." + artifacts: + - path: "frontend/src/views/CloudFolderView.vue" + provides: "Thin queue and preview orchestration over the shared browser." + - path: "frontend/src/components/storage/StorageBrowser.vue" + provides: "Shared queue dialogs and authorized content action surfaces." + key_links: + - from: "upload and content client helpers" + to: "shared browser events" + via: "CloudFolderView thin handlers" + pattern: "upload" +--- + + +Wire the shared browser to the completed backend upload and content endpoints so cloud queue handling, binary-only preview, and authorized fallback download behave exactly like the new Phase 13 contracts require. + +Purpose: Complete the frontend half of `CLOUD-02` and `CLOUD-03` without violating the shared-browser architecture. +Output: Passing queue, preview, and CloudFolderView frontend suites. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md +@frontend/src/api/cloud.js +@frontend/src/views/CloudFolderView.vue +@frontend/src/components/storage/StorageBrowser.vue +@frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js +@frontend/src/views/__tests__/CloudFolderOpenPreview.test.js +@frontend/src/views/__tests__/CloudFolderView.test.js + + +## Artifacts this phase produces + +- Shared upload queue and conflict-dialog behavior in `StorageBrowser.vue`. +- Thin queue and preview orchestration in `CloudFolderView.vue`. +- Passing queue, preview, and thin-view tests for `CLOUD-02` and `CLOUD-03`. + +## Pattern analogs + +- `frontend/src/views/FileManagerView.vue` — local thin-view orchestration style. +- `frontend/src/components/storage/StorageBrowser.vue` — existing shared action and dialog surface. +- `frontend/src/api/cloud.js` — centralized client boundary pattern. + + + + + Task 1: Implement the sequential shared upload queue with typed pause and resume behavior + frontend/src/api/cloud.js, frontend/src/views/CloudFolderView.vue, frontend/src/components/storage/StorageBrowser.vue, frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js, frontend/src/views/__tests__/CloudFolderView.test.js + + - frontend/src/views/FileManagerView.vue + - frontend/src/components/storage/StorageBrowser.vue + - frontend/src/views/CloudFolderView.vue + - frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js + + + - Test 1: the queue runs one file at a time and pauses on typed conflict or error results + - Test 2: Keep both, Replace, Skip, Retry, and Cancel all resume or stop the queue exactly where required + - Test 3: CloudFolderView remains thin and delegates queue UI to the shared browser + + + Replace the placeholder cloud upload flow with sequential queue orchestration that calls the new upload helper in `frontend/src/api/cloud.js`, feeds typed conflict and error bodies into shared browser dialogs, and preserves remaining items while paused. Keep queue state in the thin-view plus shared-browser boundary only; do not create a cloud-only queue component and do not infer replace semantics client-side. + + + - queue and view tests pass using the shared browser rather than a parallel cloud UI + - typed backend conflict/error bodies drive pause and resume behavior directly + - Cancel all stops remaining uploads without recording a false overwrite success + + + cd frontend && npm run test -- --run src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js src/views/__tests__/CloudFolderView.test.js + + The shared browser now owns the sequential cloud upload experience without architectural drift. + + + + Task 2: Wire binary-only preview and authorized download fallback through shared actions + frontend/src/api/cloud.js, frontend/src/views/CloudFolderView.vue, frontend/src/components/storage/StorageBrowser.vue, frontend/src/views/__tests__/CloudFolderOpenPreview.test.js + + - frontend/src/api/cloud.js + - frontend/src/views/__tests__/CloudFolderOpenPreview.test.js + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: preview stays in-app for supported binary files + - Test 2: unsupported formats use authorized download fallback rather than Office-native or Google Workspace preview + - Test 3: no provider URL or credential is stored or opened directly in Vue + + + Implement the cloud open and preview handlers through centralized client helpers only. Keep the preview path limited to the backend-declared binary matrix and route unsupported formats to the authorized download fallback. Reuse the shared browser’s action surface for all user interactions so the cloud path feels local while still respecting the stricter backend content contract. + + + - preview suite passes with binary-only in-app preview and authorized fallback download + - CloudFolderView remains a thin data provider + - no frontend code opens raw provider URLs or stores provider credentials + + + cd frontend && npm run test -- --run src/views/__tests__/CloudFolderOpenPreview.test.js + + The cloud browser now consumes the authorized preview and download contract through the same shared actions users already know. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| typed backend result → shared queue UI | The client must consume authoritative conflict/error typing instead of inventing semantics. | +| content helper → preview surface | Preview must stay behind DocuVault authorization. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-22 | T | queue resume flow | mitigate | Shared-browser tests require explicit resume and cancel semantics for every pause reason. | +| T-13-23 | I | preview/download UI | mitigate | Preview tests forbid raw provider URLs and require binary-only in-app preview plus authorized fallback. | + + + +- Pass the new queue and preview frontend suites. +- Confirm the cloud path still uses `StorageBrowser.vue` as the single browser. +- Confirm preview remains binary-only and credential-safe. + + + +- Users can upload to cloud storage through the same shared browser interaction path as local files. +- Cloud preview and download fallback stay within the authorized backend contract. +- The frontend now satisfies `CLOUD-02` and `CLOUD-03` without architectural duplication. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-07-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-08-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-08-PLAN.md new file mode 100644 index 0000000..d83caf6 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-08-PLAN.md @@ -0,0 +1,172 @@ +--- +phase: "13" +plan: "08" +type: execute +wave: 5 +depends_on: + - "13-03" + - "13-04" + - "13-06" +files_modified: + - backend/api/cloud/operations.py + - backend/api/cloud/schemas.py + - backend/services/cloud_operations.py + - backend/services/cloud_items.py + - backend/storage/google_drive_backend.py + - backend/storage/onedrive_backend.py + - backend/storage/webdav_backend.py + - backend/tests/test_cloud_mutations.py + - backend/tests/test_cloud_backends.py + - backend/tests/test_cloud_provider_contract.py +autonomous: true +requirements: + - CLOUD-04 + - CLOUD-05 + - CLOUD-09 +must_haves: + truths: + - "Create-folder and rename collisions auto-suffix human-readable counters with bounded retry." + - "Stale create or rename targets stop, refresh the affected folder, and return typed retry guidance instead of forcing the mutation." + - "Successful create and rename operations preserve stable cloud item identity through centralized reconciliation." + artifacts: + - path: "backend/api/cloud/operations.py" + provides: "Create-folder and rename endpoints with typed collision and stale-result bodies." + - path: "backend/services/cloud_operations.py" + provides: "Create-folder and rename orchestration with bounded retry and reconcile-on-success behavior." + key_links: + - from: "create-folder and rename provider result" + to: "cloud_items stable row identity" + via: "centralized post-mutation reconciliation" + pattern: "parent_ref" +--- + + +Implement the bounded backend create-folder and rename slice of Phase 13: collision-safe naming, stale guards, bounded retry, and stable-identity reconciliation for successful results. + +Purpose: Deliver `CLOUD-04` and `CLOUD-05` without mixing move or delete semantics into the same plan. +Output: Passing backend create-folder and rename mutation suites with centralized reconciliation intact. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md +@backend/api/cloud/operations.py +@backend/services/cloud_operations.py +@backend/services/cloud_items.py +@backend/storage/google_drive_backend.py +@backend/storage/onedrive_backend.py +@backend/storage/webdav_backend.py + + +## Artifacts this phase produces + +- Create-folder and rename support in the operations router and cloud operations service. +- Four-provider collision, bounded-retry, and stale-result normalization through Google Drive, OneDrive, Nextcloud-via-WebDAV, and generic WebDAV. +- Passing create-folder and rename backend, provider-contract, and mutation suites. + +## Pattern analogs + +- `backend/api/folders.py` — local create and rename handler structure. +- `backend/services/cloud_items.py` — stable identity and freshness reconciliation behavior. +- `backend/tests/test_cloud_backends.py` and `backend/tests/test_cloud_provider_contract.py` — provider normalization assertions. + + + + + Task 1: Implement create-folder and rename semantics with bounded collision retries and stale guards + backend/api/cloud/operations.py, backend/api/cloud/schemas.py, backend/services/cloud_operations.py, backend/storage/google_drive_backend.py, backend/storage/onedrive_backend.py, backend/storage/webdav_backend.py, backend/tests/test_cloud_mutations.py, backend/tests/test_cloud_backends.py, backend/tests/test_cloud_provider_contract.py + + - backend/api/folders.py + - backend/storage/google_drive_backend.py + - backend/storage/onedrive_backend.py + - backend/storage/webdav_backend.py + - backend/tests/test_cloud_mutations.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: create-folder and rename choose `Name (n)` counters and retry within a bounded window after concurrent collisions + - Test 2: stale version or etag mismatches stop the mutation, refresh the folder, and return a typed retry-needed result + - Test 3: provider differences normalize behind one shared contract + + + Implement D-05 through D-07 for create-folder and rename. Use the shared mutable-operation contract to normalize collision detection, bounded retry, and stale precondition failure behavior across Google Drive, OneDrive, and the shared WebDAV path. Keep counter insertion human-readable and before the file extension for files, and keep Nextcloud on the shared WebDAV mutation path unless a narrow override is unavoidable. + + + - create-folder and rename suites pass across provider-contract and backend mutation coverage + - stale mismatches return typed retry-needed results after refresh rather than forcing the mutation + - no provider writes `cloud_items` directly or bypasses the shared contract + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py tests/test_cloud_backends.py tests/test_cloud_provider_contract.py -k "create or rename or stale" -x + + Create-folder and rename now satisfy the bounded collision and stale-safety rules. + + + + Task 2: Reconcile successful create-folder and rename results through stable item identity + backend/services/cloud_operations.py, backend/services/cloud_items.py, backend/tests/test_cloud_mutations.py + + - backend/services/cloud_operations.py + - backend/services/cloud_items.py + - backend/tests/test_cloud_mutations.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: successful create-folder and rename update navigation through centralized reconciliation before success returns + - Test 2: stable cloud item identity is preserved instead of creating duplicate rows + - Test 3: refresh guidance remains typed and tied to the service layer rather than provider-specific router branches + + + Route successful create-folder and rename results back through `backend/services/cloud_items.py` so stable row identity, parent relationships, and folder freshness stay authoritative before the API returns success. Keep the route layer thin, keep typed stale-retry guidance service-owned, and do not introduce a second reconciliation path for create or rename. + + + - create-folder and rename mutation suites pass with reconcile-before-return behavior + - stable row identity is preserved across successful create-folder and rename operations + - centralized reconciliation remains the only metadata write path + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py -k "create or rename" -x + + Create-folder and rename success now satisfy the stable-identity half of `CLOUD-09`. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| proposed new name → provider mutation | User-supplied names must be collision-safe, stale-safe, and provider-neutral before mutation. | +| mutation success → metadata state | Only authoritative success may update cloud metadata and folder freshness. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-24 | T | collision handling | mitigate | Mutation and provider-contract suites require bounded retry and forbid silent overwrite behavior. | +| T-13-25 | T | stale mutation safety | mitigate | Stale guards refresh and require retry instead of forcing create-folder or rename. | +| T-13-26 | T | identity reconciliation | mitigate | Successful create-folder and rename must flow back through `cloud_items.py` to preserve stable row identity. | + + + +- Pass the create-folder and rename backend and provider-contract suites. +- Confirm every backend pytest invocation is a direct `docker compose run --rm backend pytest ...` command. +- Confirm create-folder and rename remain separated from move and delete work in both files touched and task scope. + + + +- The backend fully supports `CLOUD-04` and `CLOUD-05` with bounded collision and stale-safety semantics. +- Successful create-folder and rename results refresh navigation through centralized reconciliation. +- This bounded plan stays at 10 files and does not absorb move or delete behavior. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md b/.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md index 3d5d504..0bdc09d 100644 --- a/.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md +++ b/.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md @@ -172,3 +172,12 @@ No new security surfaces introduced. T-13-24, T-13-25, T-13-26 are mitigated: - FOUND commit: `9ad9946` (Task 1 RED) - FOUND commit: `aaa63c1` (Task 1/2 GREEN) - Full suite: 755 passed, 17 skipped, 4 deselected, 12 xfailed + +## Self-Check: PASSED (verified) + +- FOUND: `backend/api/cloud/operations.py` +- FOUND: `backend/tests/test_cloud_mutations.py` +- FOUND: `.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md` +- FOUND commit: `9ad9946` (RED tests) +- FOUND commit: `aaa63c1` (GREEN implementation) +- FOUND commit: `3e9355f` (docs/state) diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-09-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-09-PLAN.md new file mode 100644 index 0000000..6071131 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-09-PLAN.md @@ -0,0 +1,170 @@ +--- +phase: "13" +plan: "09" +type: execute +wave: 6 +depends_on: + - "13-08" +files_modified: + - backend/api/cloud/operations.py + - backend/api/cloud/schemas.py + - backend/services/cloud_operations.py + - backend/services/cloud_items.py + - backend/storage/google_drive_backend.py + - backend/storage/onedrive_backend.py + - backend/storage/webdav_backend.py + - backend/tests/test_cloud_mutations.py + - backend/tests/test_cloud_security.py + - backend/tests/test_cloud_audit.py +autonomous: true +requirements: + - CLOUD-06 + - CLOUD-07 + - CLOUD-09 +must_haves: + truths: + - "Move is restricted to one connection and rejects self or descendant destinations before and after provider submission." + - "Delete returns typed trash-versus-permanent disclosure and stronger folder warnings without leaking provider internals." + - "Successful move and delete operations reconcile metadata and emit metadata-only audit rows before success returns." + artifacts: + - path: "backend/api/cloud/operations.py" + provides: "Move and delete endpoints with destination, stale, and delete-disclosure safeguards." + - path: "backend/services/cloud_operations.py" + provides: "Move and delete orchestration tied to centralized reconciliation and metadata-only auditing." + key_links: + - from: "move and delete provider result" + to: "cloud_items stable row identity" + via: "centralized post-mutation reconciliation" + pattern: "parent_ref" +--- + + +Implement the bounded backend move and delete slice of Phase 13: same-connection validation, stale guards, delete disclosure, metadata reconciliation, and metadata-only audit behavior. + +Purpose: Deliver `CLOUD-06`, `CLOUD-07`, and the remaining mutation half of `CLOUD-09` without dragging create-folder or rename work back into scope. +Output: Passing backend move, delete, security, and audit suites. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@backend/api/cloud/operations.py +@backend/services/cloud_operations.py +@backend/services/cloud_items.py +@backend/storage/google_drive_backend.py +@backend/storage/onedrive_backend.py +@backend/storage/webdav_backend.py +@backend/tests/test_cloud_security.py +@backend/tests/test_cloud_audit.py + + +## Artifacts this phase produces + +- Move and delete support in the operations router and cloud operations service. +- Four-provider destination, stale, trash-versus-permanent delete, and audit-safe success behavior. +- Passing move, delete, security, and audit suites. + +## Pattern analogs + +- `backend/api/folders.py` — local move and delete handler structure. +- `backend/services/cloud_items.py` — stable identity and freshness reconciliation behavior. +- `backend/tests/test_cloud_security.py` — wrong-owner and cross-connection negative tests. + + + + + Task 1: Implement move with same-connection validation, stale guards, and centralized reconciliation + backend/api/cloud/operations.py, backend/api/cloud/schemas.py, backend/services/cloud_operations.py, backend/services/cloud_items.py, backend/storage/google_drive_backend.py, backend/storage/onedrive_backend.py, backend/storage/webdav_backend.py, backend/tests/test_cloud_mutations.py, backend/tests/test_cloud_security.py + + - backend/api/folders.py + - backend/services/cloud_items.py + - backend/tests/test_cloud_security.py + - backend/tests/test_cloud_mutations.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: move rejects cross-connection, self, and descendant destinations before and after provider submission + - Test 2: stale version or etag mismatches stop the move, refresh the folder, and return typed retry-needed guidance + - Test 3: successful move reconciles metadata before success returns + + + Implement D-07 through D-09 for move. Enforce same-connection-only movement, disable or reject self and descendant destinations, normalize stale precondition failures, and keep provider differences behind the shared contract for Google Drive, OneDrive, and the shared WebDAV path. Route successful move results back through `backend/services/cloud_items.py` so identity, parent linkage, and freshness remain centralized. + + + - move and security suites pass + - invalid destinations are rejected consistently in service and provider paths + - successful move uses centralized reconciliation before success returns + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py tests/test_cloud_security.py -k "move or destination" -x + + Move now satisfies the single-connection, descendant-safety, and stale-refresh rules. + + + + Task 2: Implement delete disclosure and metadata-only audit-safe success paths + backend/api/cloud/operations.py, backend/api/cloud/schemas.py, backend/services/cloud_operations.py, backend/storage/google_drive_backend.py, backend/storage/onedrive_backend.py, backend/storage/webdav_backend.py, backend/tests/test_cloud_mutations.py, backend/tests/test_cloud_security.py, backend/tests/test_cloud_audit.py + + - backend/services/cloud_operations.py + - backend/tests/test_cloud_security.py + - backend/tests/test_cloud_audit.py + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: delete returns typed trash-versus-permanent disclosure for files and folders + - Test 2: folder delete messaging is stronger than file delete messaging + - Test 3: successful delete emits metadata-only audit rows before success returns + + + Implement D-10 and D-11 for delete. Normalize provider results so the frontend can distinguish trash versus permanent delete without leaking provider internals, keep folder delete semantics explicit, and write metadata-only audit rows only after authoritative success. Preserve all owner-scope, secret-secrecy, and no-byte guarantees from the Wave 0 security suites. + + + - delete, security, and audit suites pass + - delete success discloses trash versus permanent behavior without provider leakage + - no audit row includes provider URLs, tokens, bytes, or document text + + + docker compose run --rm backend pytest -v tests/test_cloud_mutations.py tests/test_cloud_security.py tests/test_cloud_audit.py -k "delete or audit" -x + + Delete now satisfies the disclosure, security, and metadata-only audit rules. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| user-selected destination → provider mutation | Invalid folder destinations must be rejected before and after provider submission. | +| delete success → audit trail | Only authoritative success may write metadata-only delete events. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-27 | T | move destinations | mitigate | Mutation and security suites enforce same-connection, self, and descendant rejection. | +| T-13-28 | T | stale move safety | mitigate | Stale guards refresh and require retry instead of forcing move. | +| T-13-29 | I | delete disclosure and audit | mitigate | Typed delete results and audit tests keep provider behavior transparent and metadata-only. | + + + +- Pass the move, delete, security, and audit suites. +- Confirm every backend pytest invocation is a direct `docker compose run --rm backend pytest ...` command. +- Confirm move and delete remain separated from create-folder and rename work in both files touched and task scope. + + + +- The backend fully supports `CLOUD-06` and `CLOUD-07` plus the remaining move/delete portion of `CLOUD-09`. +- Stale safety, destination validation, and delete disclosure are explicit and tested across all supported provider classes. +- This bounded plan stays at 10 files and does not absorb create-folder or rename behavior. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-09-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-10-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-10-PLAN.md new file mode 100644 index 0000000..8eaf2d1 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-10-PLAN.md @@ -0,0 +1,174 @@ +--- +phase: "13" +plan: "10" +type: execute +wave: 7 +depends_on: + - "13-02" + - "13-04" + - "13-07" + - "13-09" +files_modified: + - frontend/src/stores/cloudConnections.js + - frontend/src/views/CloudFolderView.vue + - frontend/src/components/storage/StorageBrowser.vue + - frontend/src/components/settings/SettingsCloudTab.vue + - frontend/src/components/cloud/CloudCredentialModal.vue + - frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js + - frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js + - frontend/src/stores/__tests__/cloudConnections.test.js + - frontend/src/views/__tests__/CloudFolderView.test.js +autonomous: true +requirements: + - CONN-01 + - CONN-02 + - CONN-03 + - CLOUD-04 + - CLOUD-05 + - CLOUD-06 + - CLOUD-07 + - CLOUD-09 +must_haves: + truths: + - "Connection health is visible near the browser and in Settings from one shared store mapping." + - "Post-connect, post-reconnect, and credential-failure flows re-evaluate health automatically, while ordinary navigation never probes health." + - "The shared browser surfaces cloud create, rename, move, and delete results without introducing a second browser or folder picker." + artifacts: + - path: "frontend/src/stores/cloudConnections.js" + provides: "Server health mapping, reconnect refresh coordination, and no-probe-on-navigation behavior." + - path: "frontend/src/components/settings/SettingsCloudTab.vue" + provides: "Test/Reconnect/Disconnect controls and broader Google Drive consent copy." + key_links: + - from: "reconnect and credential-failure responses" + to: "browser-adjacent and Settings health state" + via: "cloudConnections store" + pattern: "health" +--- + + +Finish the frontend side of Phase 13 by wiring shared-browser folder mutations, actionable health UX, broader Drive consent copy, automatic post-failure health rechecks, and the explicit no-probe-on-navigation rule. + +Purpose: Close the loop between the completed backend contracts and the two frontend surfaces users rely on. +Output: Passing health, rendered-flow, store, and mutation UX suites. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +@.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md +@frontend/src/stores/cloudConnections.js +@frontend/src/views/CloudFolderView.vue +@frontend/src/components/storage/StorageBrowser.vue +@frontend/src/components/settings/SettingsCloudTab.vue +@frontend/src/components/cloud/CloudCredentialModal.vue + + +## Artifacts this phase produces + +- Shared browser support for cloud create, rename, move, and delete UX. +- Store-backed browser and Settings health presentation with auto-test and no-probe behavior. +- Explicit broader Google Drive consent or reconnect copy in the cloud credential and settings surfaces. + +## Pattern analogs + +- `frontend/src/stores/__tests__/cloudConnections.test.js` — centralized state mapping pattern. +- `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` — rendered browser-health flow pattern. +- `frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js` — Settings control and copy assertions. + + + + + Task 1: Implement store-backed health and reconnect UX with explicit no-probe-on-navigation behavior + frontend/src/stores/cloudConnections.js, frontend/src/views/CloudFolderView.vue, frontend/src/components/settings/SettingsCloudTab.vue, frontend/src/components/cloud/CloudCredentialModal.vue, frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js, frontend/src/stores/__tests__/cloudConnections.test.js, frontend/src/views/__tests__/CloudFolderView.test.js + + - frontend/src/stores/cloudConnections.js + - frontend/src/components/settings/SettingsCloudTab.vue + - frontend/src/components/cloud/CloudCredentialModal.vue + - frontend/src/stores/__tests__/cloudConnections.test.js + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: browser-adjacent and Settings health views derive from one store mapping + - Test 2: connect, reconnect, and credential-failure flows auto-test health, but ordinary folder navigation never triggers a probe + - Test 3: broader Google Drive access is explicit in consent or reconnect copy + + + Extend the cloud connection store and thin view handlers so server health states are mapped once and rendered in both the cloud browser context and Settings. Trigger automatic health re-evaluation after connect, reconnect, and credential-related failures, keep cached metadata visible while warning or reauth states are shown, and explicitly prevent ordinary folder navigation from performing a provider health probe. Update the credential and Settings surfaces so Google Drive’s broader Phase 13 access request is visible in user-facing copy rather than hidden in backend-only behavior. + + + - health and store suites pass with explicit no-probe-on-navigation coverage + - browser and Settings show actionable, consistent health state from the same store mapping + - broader Drive access copy is visible wherever users connect or reconnect that provider + + + cd frontend && npm run test -- --run src/components/settings/__tests__/SettingsCloudTab.health.test.js src/stores/__tests__/cloudConnections.test.js src/views/__tests__/CloudFolderView.test.js + + Connection health, reconnect, and consent UX now follow one store-backed truth without background probe drift. + + + + Task 2: Surface folder mutation results through the shared browser without forking layout + frontend/src/views/CloudFolderView.vue, frontend/src/components/storage/StorageBrowser.vue, frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js + + - frontend/src/components/storage/StorageBrowser.vue + - frontend/src/views/CloudFolderRenderedFlow.test.js + - .planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md + + + - Test 1: cloud create, rename, move, and delete reuse the shared browser surface and picker behavior + - Test 2: invalid destinations are disabled before submission and backend rejections are rendered clearly + - Test 3: trash-versus-permanent delete messaging and stale-refresh retry guidance are shown without duplicating layout + + + Wire the shared browser to consume the folder-mutation contract from the backend. Reuse the existing create, rename, drag-move, picker-move, and delete surfaces, but feed them cloud-specific capability, invalid-destination, stale-refresh, and trash-versus-permanent delete messages through props and emitted events only. Keep `CloudFolderView.vue` thin and avoid introducing a second folder picker, second drag state, or parallel mutation layout. + + + - rendered-flow suite passes with the real shared browser + - invalid destinations are prevented in the UI before submission and still handled cleanly after backend rejection + - cloud create/rename/move/delete behavior stays on the shared browser surface + + + cd frontend && npm run test -- --run src/views/__tests__/CloudFolderRenderedFlow.test.js + + The frontend now presents full cloud mutation and health behavior through the shared browser and Settings surfaces only. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| server health/mutation results → store/UI | The UI must present backend truth without inventing probe or mutation semantics. | +| shared browser interactions → destructive actions | Delete and move UX must stay explicit and capability-aware. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-30 | T | health UX | mitigate | Store and Settings tests require auto-test on connect/reconnect/failure and forbid navigation-triggered probes. | +| T-13-31 | R | destructive cloud actions | mitigate | Rendered-flow tests require explicit delete disclosure, stale-retry messaging, and invalid-destination prevention. | +| T-13-32 | I | broader-scope consent | mitigate | Settings and credential-modal tests require explicit broader Google Drive access copy. | + + + +- Pass the health, store, view, and rendered-flow frontend suites. +- Confirm the shared browser remains the only browser surface. +- Confirm no-probe-on-navigation is explicitly asserted and implemented. + + + +- Users can see, test, reconnect, and recover cloud connections through one consistent frontend state model. +- Shared-browser folder mutations now satisfy the backend contracts for `CLOUD-04` through `CLOUD-07`. +- Phase 13 now concretely encodes broader Drive access and the D-13 health-testing invariant on the frontend. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-10-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-11-PLAN.md b/.planning/phases/13-virtual-local-cloud-operations/13-11-PLAN.md new file mode 100644 index 0000000..bc8ffd1 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-11-PLAN.md @@ -0,0 +1,175 @@ +--- +phase: "13" +plan: "11" +type: execute +wave: 8 +depends_on: + - "13-03" + - "13-04" + - "13-05" + - "13-06" + - "13-07" + - "13-08" + - "13-09" + - "13-10" +files_modified: + - .planning/ROADMAP.md + - AGENTS.md + - README.md + - RUNBOOK.md + - SECURITY.md + - backend/main.py + - frontend/package.json +autonomous: true +requirements: + - CONN-01 + - CONN-02 + - CONN-03 + - CLOUD-02 + - CLOUD-03 + - CLOUD-04 + - CLOUD-05 + - CLOUD-06 + - CLOUD-07 + - CLOUD-09 +must_haves: + truths: + - "Phase 13 closeout is isolated from implementation work." + - "Documentation, security evidence, version bumps, and ship gates happen only after all scoped behavior exists." + - "Full backend verification uses direct containerized pytest commands only, and the hardcoded-secret scan is non-skippable." + artifacts: + - path: "AGENTS.md" + provides: "Updated current-state line and shared-module guidance for shipped Phase 13 behavior." + - path: "SECURITY.md" + provides: "Phase 13 gate evidence for IDOR, CSRF, SSRF, typed conflict handling, audit secrecy, no-probe behavior, and the passing secret scan." + - path: ".planning/ROADMAP.md" + provides: "Phase 13 plan list and closeout status updates." + key_links: + - from: "completed Phase 13 behavior" + to: "docs, versions, and security evidence" + via: "final closeout-only plan" + pattern: "Phase 13" +--- + + +Run the Phase 13 closeout only after all implementation plans are complete: update roadmap and docs, bump versions, run the full suites and security gates, execute a non-skippable hardcoded-secret scan, and prepare the phase for atomic commit and push. + +Purpose: Keep documentation, versioning, security review, and ship gates isolated in one bounded final plan. +Output: Ready-to-ship Phase 13 documentation and validation state. + + + +@$HOME/.codex/gsd-core/workflows/execute-plan.md +@$HOME/.codex/gsd-core/templates/summary.md + + + +@AGENTS.md +@.planning/ROADMAP.md +@.planning/REQUIREMENTS.md +@README.md +@RUNBOOK.md +@SECURITY.md +@backend/main.py +@frontend/package.json + + +## Artifacts this phase produces + +- Updated roadmap and phase-close documentation. +- Version bumps for the shipped Phase 13 user-facing behavior. +- Full Phase 13 validation, dependency-audit, and security-gate evidence collected in one place, including a passing hardcoded-secret scan. + +## Pattern analogs + +- `.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-04-SUMMARY.md` — bounded closeout-only phase finish. +- `AGENTS.md` documentation protocol — required current-state, shared-module, and version-bump updates. + + + + + Task 1: Update roadmap, docs, versions, and closeout evidence to match the shipped Phase 13 behavior + .planning/ROADMAP.md, AGENTS.md, README.md, RUNBOOK.md, SECURITY.md, backend/main.py, frontend/package.json + + - AGENTS.md + - .planning/ROADMAP.md + - README.md + - RUNBOOK.md + - SECURITY.md + + + Update Phase 13 planning and closeout documentation only after Plans 03 through 10 are complete. Record the finished health, reconnect, content, upload, create-folder, rename, move, delete, broader Drive scope, binary-only preview, and no-probe-on-navigation behavior accurately. Update the AGENTS current-state line and shared module map if the cloud operations layer became a new canonical shared module. Bump backend and frontend patch versions together for the shipped user-visible behavior, and record the final gate evidence in `SECURITY.md`, including the required hardcoded-secret scan result. + + + - roadmap and docs describe the actual shipped Phase 13 scope without claiming Phase 14 cache lifecycle or deferred Collabora work + - AGENTS and README reflect the final user-visible behavior and version numbers accurately + - SECURITY captures the Phase 13 threat evidence, gate outcomes, and the passing hardcoded-secret scan + + + docker compose config --quiet + + Phase 13 documentation, version metadata, and closeout evidence are ready for final validation. + + + + Task 2: Run the full test, dependency, security, and hardcoded-secret gates before commit and push + .planning/ROADMAP.md, AGENTS.md, README.md, RUNBOOK.md, SECURITY.md, backend/main.py, frontend/package.json + + - AGENTS.md + - SECURITY.md + - .planning/phases/13-virtual-local-cloud-operations/13-VALIDATION.md + + + Run the full backend and frontend suites, then the focused security and dependency gates required by AGENTS.md. Every backend pytest invocation must be a direct `docker compose run --rm backend pytest ...` command. Run `docker compose run --rm backend bandit -r .`, `docker compose run --rm backend pip audit`, `cd frontend && npm audit --audit-level=high`, and the non-skippable hardcoded-secret scan `trufflehog filesystem --no-update --fail .`. Confirm the wrong-owner, admin-negative, credential-secrecy, SSRF, typed conflict, audit secrecy, and no-probe-on-navigation invariants remain green. Review the final diff for unrelated files or secret exposure, then stage, commit, and push atomically as the closeout action for the completed phase. + + + - full backend and frontend suites pass + - backend security and dependency gates pass with direct containerized commands + - `trufflehog filesystem --no-update --fail .` passes and is recorded as closeout evidence + - the phase can be committed and pushed without unrelated files or secret exposure + + + docker compose run --rm backend pytest -v + cd frontend && npm run test + docker compose run --rm backend bandit -r . + docker compose run --rm backend pip audit + cd frontend && npm audit --audit-level=high + trufflehog filesystem --no-update --fail . + git diff --check + + Phase 13 is fully validated, secret-scanned, and ready for atomic commit and push. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| shipped code → documentation/security evidence | Closeout must describe only what actually shipped. | +| final diff → git history | Secret exposure and unrelated changes must be caught before commit. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-13-33 | R | closeout docs | mitigate | Final-plan-only doc update ensures documentation follows completed implementation. | +| T-13-34 | I | release validation | mitigate | Full suites, dependency audits, and the non-skippable `trufflehog filesystem --no-update --fail .` gate run before commit and push. | + + + +- Confirm all backend pytest commands are direct containerized invocations. +- Confirm full backend, frontend, security, dependency, and hardcoded-secret gates are isolated to this plan. +- Confirm docs and versions match the implemented Phase 13 scope and nothing deferred is claimed as shipped. + + + +- Docs, versions, security evidence, full gates, and the explicit hardcoded-secret scan are isolated to one bounded plan. +- Phase 13 can be closed without mixing implementation work into the same plan. +- The final plan preserves the project’s commit, push, documentation, and closeout protocol exactly. + + + +Create `.planning/phases/13-virtual-local-cloud-operations/13-11-SUMMARY.md` when done + diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md b/.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md index 48b5375..17928c9 100644 --- a/.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md +++ b/.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md @@ -37,6 +37,10 @@ Deliver owner-authorized connection maintenance and the main cloud file-manageme - **D-15:** A timeout or temporarily unreachable provider is an unhealthy connection state only. Preserve credentials and cached metadata, show an actionable warning, and allow retry/reconnect; never delete data because of transient failure. - **D-16:** Explicit user-initiated Disconnect requires confirmation, removes credentials and connection-scoped cloud metadata, and leaves all provider files untouched. Phase 13 has no byte cache to migrate. +### Provider access and preview scope +- **D-17:** Phase 13 requests broader Google Drive access rather than retaining the restricted `drive.file` scope, so authorized users can operate on existing Drive items throughout their connected storage. Consent copy and tests must make the expanded scope explicit. +- **D-18:** Phase 13 preview support is limited to supported binary file formats. Google Workspace and Microsoft Office document rendering/editing are excluded; unsupported formats use the ownership-checked authorized download fallback from D-02. + ### Codex's Discretion - Choose exact accessible dialog wording, progress labels, icons, and controlled provider-error translations while preserving the decisions above. - Choose the bounded retry count for automatic collision suffixing and safe provider-specific mechanics for trash versus permanent delete. @@ -119,6 +123,7 @@ Deliver owner-authorized connection maintenance and the main cloud file-manageme ## Deferred Ideas - Phase 14 must place bytes used for in-app cloud preview into DocuVault's temporary cache and remove them through normal cache eviction/clearing; preview must not become a browser-to-device download. +- A future phase will add Office document rendering/editing through Collabora running in a separate internally accessible container; Phase 13 must not introduce Collabora, Google Workspace export preview, or Office-native preview plumbing. - Future `IMPORT-01`: on explicit disconnect, convert cached/downloaded cloud files into local DocuVault documents under a folder named after the connection's current display name, preserve the cloud hierarchy, continue counting those bytes toward quota, and leave provider files untouched. This is a permanent import capability and remains outside Phase 13 and the current v0.3 scope. diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md b/.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md new file mode 100644 index 0000000..ff6cbbf --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-PATTERNS.md @@ -0,0 +1,559 @@ +# Phase 13: Virtual-Local Cloud Operations - Pattern Map + +**Mapped:** 2026-06-22 +**Files analyzed:** 33 likely new/modified files +**Analogs found:** 33 / 33 (6 are composite/no-exact) + +## Inherited phase rules that stay locked + +- Keep `StorageBrowser.vue` as the only file browser. Cloud behavior extends emitted events and props; it does not fork layout. See [AGENTS.md], Phase 12 patterns, and [frontend/src/components/storage/StorageBrowser.vue]. +- Keep cloud routing owner-scoped by connection UUID plus opaque `provider_item_id`; never parse provider refs on the client. Primary analogs: [backend/api/cloud/browse.py:183], [frontend/src/views/CloudFolderView.vue:42], [frontend/src/components/cloud/CloudFolderTreeItem.vue:42]. +- Keep metadata reconciliation centralized in `reconcile_cloud_listing` / `apply_listing_and_finalize`; provider adapters never write `cloud_items` rows directly. Primary analogs: [backend/services/cloud_items.py:163], [backend/services/cloud_items.py:271]. +- Keep service-layer exception discipline: services raise domain errors / `ValueError`, routers translate to `HTTPException`. Primary analogs: [AGENTS.md], [backend/services/cloud_items.py:1]. +- Keep audit writes metadata-only and in the caller’s transaction. Primary analog: [backend/services/audit.py:27]. + +## File Classification + +| Likely file | Role | Data flow | Closest analog | Match | +|---|---|---|---|---| +| `backend/api/cloud/operations.py` | route | request-response + file-I/O + CRUD | `backend/api/cloud/browse.py`, `backend/api/documents/content.py`, `backend/api/documents/upload.py`, `backend/api/folders.py` | composite | +| `backend/api/cloud/connections.py` | route | request-response | itself + `backend/tasks/cloud_tasks.py` | exact | +| `backend/api/cloud/schemas.py` | schema | transform | itself | exact | +| `backend/services/cloud_operations.py` | service | CRUD + transform | `backend/services/cloud_items.py` + `backend/services/audit.py` | composite | +| `backend/services/cloud_items.py` | service | CRUD + batch reconcile | itself | exact | +| `backend/storage/cloud_base.py` | contract | transform | itself | exact | +| `backend/storage/cloud_backend_factory.py` | factory | request-response | itself | exact | +| `backend/storage/google_drive_backend.py` | provider | file-I/O + CRUD | itself | exact | +| `backend/storage/onedrive_backend.py` | provider | file-I/O + CRUD | itself | exact | +| `backend/storage/webdav_backend.py` | provider | file-I/O + CRUD | itself | exact | +| `backend/storage/nextcloud_backend.py` | provider | file-I/O | itself + `webdav_backend.py` | exact | +| `backend/tasks/cloud_tasks.py` | task | event-driven + batch | itself | exact | +| `frontend/src/api/cloud.js` | API client | request-response | itself + `frontend/src/api/documents.js` | exact | +| `frontend/src/stores/cloudConnections.js` | store | state transform | itself | exact | +| `frontend/src/views/CloudFolderView.vue` | thin view | request-response + queue orchestration | itself + `frontend/src/views/FileManagerView.vue` | exact | +| `frontend/src/components/storage/StorageBrowser.vue` | smart component | CRUD UI + queue UI | itself | exact | +| `frontend/src/components/settings/SettingsCloudTab.vue` | smart component | request-response | itself | exact | +| `frontend/src/components/cloud/CloudCredentialModal.vue` | modal/form | request-response | itself | exact | +| `backend/tests/test_cloud.py` | integration test | request-response | itself | exact | +| `backend/tests/test_cloud_security.py` | security-negative test | request-response | itself | exact | +| `backend/tests/test_cloud_backends.py` | provider unit/contract test | file-I/O | itself | exact | +| `backend/tests/test_cloud_provider_contract.py` | provider contract test | transform | itself | exact | +| `backend/tests/test_cloud_items.py` | service/task test | CRUD + batch | itself | exact | +| `backend/tests/test_cloud_mutations.py` | integration/contract test | CRUD + file-I/O | `test_cloud.py` + `test_cloud_security.py` + `test_cloud_items.py` | composite | +| `backend/tests/test_cloud_reconnect.py` | integration/security test | request-response + task | `test_cloud.py` + `test_cloud_items.py` | composite | +| `backend/tests/test_cloud_audit.py` | integration test | transform | `backend/tests/test_audit.py` + `backend/tests/test_cloud.py` | partial | +| `frontend/src/views/__tests__/CloudFolderView.test.js` | view unit test | request-response | itself | exact | +| `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` | rendered-flow test | request-response | itself | exact | +| `frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js` | component unit test | CRUD UI | itself | exact | +| `frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js` | component unit test | queue UI | `StorageBrowser.capabilities.test.js` + `FileManagerView.vue` upload flow | composite | +| `frontend/src/views/__tests__/CloudFolderOpenPreview.test.js` | rendered-flow test | request-response + preview | `CloudFolderRenderedFlow.test.js` + document content helpers | composite | +| `frontend/src/stores/__tests__/cloudConnections.test.js` | store unit test | state transform | itself | exact | +| `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` | component unit test | request-response | itself | exact | +| `frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js` | component unit test | health/reconnect UI | `SettingsCloudTab.test.js` + store freshness tests | partial | + +## Pattern Assignments + +### `backend/api/cloud/operations.py` + +**Primary analogs** + +- Route/dependency skeleton: [backend/api/cloud/browse.py:19-42], [backend/api/cloud/browse.py:183-214] +- Connection settings/test/update/disconnect patterns: [backend/api/cloud/connections.py:368-434], [backend/api/cloud/connections.py:492-579], [backend/api/cloud/connections.py:604-635] +- Authorized byte streaming and cloud-error translation: [backend/api/documents/content.py:50-103] +- Multipart upload proxy pattern: [backend/api/documents/upload.py:122-198] +- Local create/rename/delete/move handler shape: [backend/api/folders.py:122-160], [backend/api/folders.py:232-265], [backend/api/folders.py:268-351], [backend/api/folders.py:357-375] + +**Copy these conventions** + +- Use `get_regular_user`, `get_db`, `account_limiter`, and `request.state.current_user = current_user` exactly like cloud browse/connections. +- Resolve ownership through `resolve_owned_connection(...)` before any provider action. +- Translate provider/service failures to controlled `HTTPException` messages like document content/upload already do; do not surface raw provider text. +- For open/preview/download, copy the `StreamingResponse` + `CloudConnectionError` mapping from `content.py`, but key authorization off cloud connection/item ownership instead of `Document`. +- For upload/create/rename/move/delete, keep router bodies thin: validate request shape, call service, return whitelisted schema, write no inline reconciliation logic. + +**No exact analog** + +- There is no existing single cloud route file that combines owner-scoped connection actions with content proxying and normalized mutation results. Keep the file shaped like `browse.py`, but borrow handler bodies from the document/folder routers above. + +### `backend/api/cloud/connections.py` + +**Primary analogs** + +- WebDAV connect and health-check flow: [backend/api/cloud/connections.py:368-434] +- Credential update flow: [backend/api/cloud/connections.py:492-579] +- Display-name rename: [backend/api/cloud/connections.py:582-601] +- Disconnect confirmation target: [backend/api/cloud/connections.py:604-635] + +**Copy these conventions** + +- Keep `_get_owned_connection(...)` for owner checks. +- Keep URL validation before backend construction. +- Keep audit writes adjacent to the successful state change. +- If reconnect/test routes are added, model them after `connect_webdav` + `update_webdav_credentials`: validate, health-check, persist, audit, commit. + +**Phase 13-specific caution** + +- The current OAuth callback always inserts a new row (`_upsert_cloud_connection = _insert_cloud_connection`) at [backend/api/cloud/connections.py:128-129]. Do not copy that behavior for reconnect; Phase 13 needs connection-row preservation. + +### `backend/api/cloud/schemas.py` + +**Primary analog** + +- Current cloud whitelist schemas: [backend/api/cloud/schemas.py:17-86] + +**Copy these conventions** + +- Add only explicit allowlist response/request models. +- Keep validators at schema level for user-submitted fields, as in `ConnectionRenameRequest`. +- Keep credentials, provider URLs, and raw provider payloads out of every schema. + +**No exact analog** + +- There is no current normalized mutation-result schema. Add new result types beside `CloudBrowseResponse`, not in routers. + +### `backend/services/cloud_operations.py` + +**Primary analogs** + +- Owner resolution / stable identity / folder freshness: [backend/services/cloud_items.py:38-62], [backend/services/cloud_items.py:97-159], [backend/services/cloud_items.py:271-342] +- Metadata-only audit helper: [backend/services/audit.py:27-58] +- Background refresh orchestration: [backend/tasks/cloud_tasks.py:50-163] + +**Copy these conventions** + +- Accept UUIDs/strings the same way `cloud_items.py` does. +- Raise domain errors or `ValueError`, never `HTTPException`. +- Reconcile and finalize folder state before returning success. +- Write audit rows through `write_audit_log(...)`, letting the caller own the transaction. + +**No exact analog** + +- There is no existing service that turns provider mutation outcomes into normalized API results. Treat this as the new orchestration seam; do not push that logic into routers or providers. + +### `backend/services/cloud_items.py` + +**Primary analog** + +- Keep using the current service itself. + +**Copy these conventions** + +- Owner resolution: [backend/services/cloud_items.py:38-62] +- Stable upsert-by-`(connection_id, provider_item_id)`: [backend/services/cloud_items.py:97-159] +- Complete-vs-incomplete reconciliation: [backend/services/cloud_items.py:163-213] +- Single freshness gate: [backend/services/cloud_items.py:271-342] +- Controlled folder-state updates: [backend/services/cloud_items.py:345-384] + +**Planner note** + +- Any mutation service should feed its authoritative provider result back through these primitives instead of adding direct ORM writes elsewhere. + +### `backend/storage/cloud_base.py` + +**Primary analog** + +- Keep extending the current contract file itself: [backend/storage/cloud_base.py:27-245] + +**Copy these conventions** + +- Stable action vocabulary and reason codes live here, not in routers or Vue. +- Immutable normalized dataclasses (`CloudCapability`, `CloudResource`, `CloudListing`) are the pattern to follow for new mutation result types. +- Keep provider-specific branching out of the shared contract. + +**No exact analog** + +- There is no Phase-13 mutation result/value type yet. New normalized result dataclasses belong here. + +### `backend/storage/cloud_backend_factory.py` + +**Primary analog** + +- Current factory and adapter assertion: [backend/storage/cloud_backend_factory.py:7-66] + +**Copy these conventions** + +- Keep lazy imports. +- Keep Nextcloud URL normalization in the factory boundary. +- If a mutable-adapter subclass is introduced, assert the interface here instead of switching on provider names in routers. + +### `backend/storage/google_drive_backend.py` + +**Primary analog** + +- Existing upload/download/delete/list/capability patterns: [backend/storage/google_drive_backend.py:140-172], [backend/storage/google_drive_backend.py:174-207], [backend/storage/google_drive_backend.py:257-272], [backend/storage/google_drive_backend.py:276-417] + +**Copy these conventions** + +- Wrap SDK calls in `asyncio.to_thread()`. +- Centralize provider error classification in helper methods like `_handle_http_error(...)`. +- Normalize provider metadata into `CloudResource` objects. +- Keep the backend stateless: it may return refreshed credentials or typed errors, but must not write DB state directly. + +### `backend/storage/onedrive_backend.py` + +**Primary analog** + +- Existing token lifecycle + upload/download/delete/list/capabilities: [backend/storage/onedrive_backend.py:83-154], [backend/storage/onedrive_backend.py:158-218], [backend/storage/onedrive_backend.py:220-245], [backend/storage/onedrive_backend.py:280-409] + +**Copy these conventions** + +- Refresh token on demand inside the backend boundary. +- Keep Graph HTTP async with `httpx`. +- Keep `CloudConnectionError` unified across providers. + +**Gap to flag** + +- There is no persistence pattern yet for refreshed OneDrive credentials; Phase 13 must add one above the backend, not inside it. + +### `backend/storage/webdav_backend.py` + +**Primary analog** + +- Existing SSRF, PUT/GET/DELETE, list-folder, and capability patterns: [backend/storage/webdav_backend.py:66-137], [backend/storage/webdav_backend.py:139-174], [backend/storage/webdav_backend.py:222-237], [backend/storage/webdav_backend.py:241-370] + +**Copy these conventions** + +- Re-run `validate_cloud_url(...)` before every outbound request. +- Keep basename/path-traversal protection when building destination names. +- Keep WebDAV-specific overwrite/conflict semantics inside this provider file. + +### `backend/storage/nextcloud_backend.py` + +**Primary analog** + +- Minimal specialization over WebDAV: [backend/storage/nextcloud_backend.py:38-90] + +**Copy these conventions** + +- Keep Nextcloud as a tiny specialization, not a parallel backend. +- If Phase 13 needs Nextcloud-specific mutation quirks, implement them as narrow overrides that preserve the public contract. + +### `backend/tasks/cloud_tasks.py` + +**Primary analog** + +- Current refresh worker: [backend/tasks/cloud_tasks.py:50-163], [backend/tasks/cloud_tasks.py:175-216] + +**Copy these conventions** + +- Revalidate ownership inside the worker. +- Decrypt credentials inside the worker, never in broker payload. +- Use sentinel exceptions to separate retryable provider failures from terminal auth failures. +- Reuse this task model if reconnect or stale-metadata recovery needs post-success refresh. + +### `frontend/src/api/cloud.js` + +**Primary analogs** + +- Existing connection browse/config/update methods: [frontend/src/api/cloud.js:11-18], [frontend/src/api/cloud.js:36-50], [frontend/src/api/cloud.js:73-88] +- Cloud upload helper shape: [frontend/src/api/documents.js:48-54] + +**Copy these conventions** + +- Keep all Phase 13 cloud endpoints behind this domain module. +- Keep connection UUID and encoded `parent_ref` handling here. +- Add new `open/preview/upload/create/rename/move/delete/test/reconnect` helpers here instead of calling raw URLs from views/components. + +### `frontend/src/stores/cloudConnections.js` + +**Primary analog** + +- Current store state and reset/mapping helpers: [frontend/src/stores/cloudConnections.js:33-138] + +**Copy these conventions** + +- Keep server state translation centralized in the store. +- Keep sessionStorage limited to folder refs only. +- Reset browse state on connection switch. + +**No exact analog** + +- There is no queue/conflict/resume state machine yet. Add it here only if it is shared across cloud views/settings; otherwise keep queue orchestration in `CloudFolderView`. + +### `frontend/src/views/CloudFolderView.vue` + +**Primary analogs** + +- Existing cloud thin-view contract: [frontend/src/views/CloudFolderView.vue:1-20], [frontend/src/views/CloudFolderView.vue:125-170] +- Local upload / create / rename / move / delete orchestration: [frontend/src/views/FileManagerView.vue:1-30], [frontend/src/views/FileManagerView.vue:103-182] + +**Copy these conventions** + +- Keep the view thin: store/router/API orchestration only. +- Pass everything into `StorageBrowser`; do not add layout or grid logic here. +- Reuse the local upload queue orchestration style from `FileManagerView`, but Phase 13 needs sequential pause/resume instead of the current all-at-once `Promise.allSettled(...)` placeholder at [frontend/src/views/CloudFolderView.vue:172-181]. +- Use toasts and success/error handling the same way `FileManagerView` does. + +### `frontend/src/components/storage/StorageBrowser.vue` + +**Primary analog** + +- Extend the current shared component itself: [frontend/src/components/storage/StorageBrowser.vue:140-275], [frontend/src/components/storage/StorageBrowser.vue:424-681] + +**Copy these conventions** + +- Props/events are the contract; keep new behavior behind emitted events and prop-driven state. +- Capability-aware buttons and notices stay in this component: [frontend/src/components/storage/StorageBrowser.vue:360-418], [frontend/src/components/storage/StorageBrowser.vue:495-530]. +- New folder / rename inline patterns already exist here: [frontend/src/components/storage/StorageBrowser.vue:118-135], [frontend/src/components/storage/StorageBrowser.vue:157-166], [frontend/src/components/storage/StorageBrowser.vue:543-593]. +- File move picker and drag-to-move patterns already exist here: [frontend/src/components/storage/StorageBrowser.vue:253-344], [frontend/src/components/storage/StorageBrowser.vue:595-621]. + +**Gap to flag** + +- There is no existing conflict dialog / paused upload queue UI. That is a real no-analog area inside the shared browser and should be added here rather than in a cloud-only component. + +### `frontend/src/components/settings/SettingsCloudTab.vue` + +**Primary analog** + +- Existing settings actions and confirm flows: [frontend/src/components/settings/SettingsCloudTab.vue:21-102], [frontend/src/components/settings/SettingsCloudTab.vue:104-170], [frontend/src/components/settings/SettingsCloudTab.vue:293-359] + +**Copy these conventions** + +- Keep provider rows, inline status badges, and destructive confirms in one shared settings tab. +- Add Test/Reconnect controls beside existing Connect/Edit/Remove actions; do not create a parallel health screen. +- Keep OAuth initiation in the component and store mutation calls behind the store/API layer. + +### `frontend/src/components/cloud/CloudCredentialModal.vue` + +**Primary analog** + +- Existing submit branching for create vs edit: [frontend/src/components/cloud/CloudCredentialModal.vue:301-323] + +**Copy these conventions** + +- Keep credential edits inside the modal for WebDAV/Nextcloud. +- If Phase 13 adds explicit “Test connection” before save, hang it off the same API module and modal state machine instead of duplicating form state elsewhere. + +### `backend/tests/test_cloud.py` + +**Primary analog** + +- Current integration coverage for connect/upload/status/browse/rename/refresh: [backend/tests/test_cloud.py:30-65], [backend/tests/test_cloud.py:473-527], [backend/tests/test_cloud.py:532-655], [backend/tests/test_cloud.py:998-1315] + +**Copy these conventions** + +- Use the shared auth helper pattern and `async_client`. +- Patch provider/network boundaries, not route internals, where possible. +- Assert both HTTP contract and DB side effects. + +### `backend/tests/test_cloud_security.py` + +**Primary analog** + +- Current security-negative suite: [backend/tests/test_cloud_security.py:37-117], [backend/tests/test_cloud_security.py:122-340] + +**Copy these conventions** + +- Add Phase 13 wrong-owner/admin/credential/no-byte/cross-connection negatives here. +- Keep raw-provider-error sanitization assertions here, not only in happy-path integration tests. + +### `backend/tests/test_cloud_backends.py` + +**Primary analog** + +- Provider backend behavior tests: [backend/tests/test_cloud_backends.py:334-420] and the rest of the file’s provider sections + +**Copy these conventions** + +- Provider-specific fixtures, normalized assertions. +- Explicit “never download/mutate while browsing” checks. +- Keep backend tests provider-neutral where possible and provider-specific only where semantics differ. + +### `backend/tests/test_cloud_provider_contract.py` + +**Primary analog** + +- Canonical signature/identity/pagination/no-byte contract: [backend/tests/test_cloud_provider_contract.py:201-276], [backend/tests/test_cloud_provider_contract.py:376-536], [backend/tests/test_cloud_provider_contract.py:659-727] + +**Copy these conventions** + +- Any new mutable adapter methods should get the same style of canonical contract tests: signature, normalized result, trusted caller identity, no forbidden side effects. + +### `backend/tests/test_cloud_items.py` + +**Primary analog** + +- Owner-scoped service and folder-state tests: [backend/tests/test_cloud_items.py:368-529], [backend/tests/test_cloud_items.py:545-859] + +**Copy these conventions** + +- Mutation reconciliation tests belong here: stable UUID across rename/move, complete/incomplete behavior, freshness truth, no quota mutation. + +### `backend/tests/test_cloud_mutations.py` + +**Closest analogs** + +- API behavior: [backend/tests/test_cloud.py:473-527], [backend/tests/test_cloud.py:1112-1249] +- Security negatives: [backend/tests/test_cloud_security.py:122-340] +- Reconciliation truth: [backend/tests/test_cloud_items.py:411-499], [backend/tests/test_cloud_items.py:743-859] + +**No exact analog** + +- There is no existing end-to-end mutation suite for cloud items. Build it as a new file, but follow the helper/fixture style of `test_cloud.py`. + +### `backend/tests/test_cloud_reconnect.py` + +**Closest analogs** + +- Connection lifecycle/status: [backend/tests/test_cloud.py:532-655] +- Worker/freshness behavior: [backend/tests/test_cloud.py:1251-1315], [backend/tests/test_cloud_items.py:545-609] + +**No exact analog** + +- There is no dedicated reconnect/token-persistence suite today. + +### `backend/tests/test_cloud_audit.py` + +**Closest analogs** + +- Audit helper behavior: [backend/services/audit.py:27-58] +- Inline audit assertions in cloud routes: [backend/api/cloud/connections.py:351-359], [backend/api/cloud/connections.py:422-430], [backend/api/cloud/connections.py:567-575], [backend/api/cloud/connections.py:624-632] + +**No exact analog** + +- There is no cloud-mutation audit suite today; create one that asserts metadata-only payloads and same-transaction persistence. + +### `frontend/src/views/__tests__/CloudFolderView.test.js` + +**Primary analog** + +- Existing thin-view cloud tests: [frontend/src/views/__tests__/CloudFolderView.test.js:73-294] + +**Copy these conventions** + +- Stub `StorageBrowser` when the test is about view orchestration only. +- Assert route params, API calls, and props passed to the browser. + +### `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` + +**Primary analog** + +- Existing real-browser rendered flow: [frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js:1-260] + +**Copy these conventions** + +- Render the real `StorageBrowser` when testing end-to-end view/browser interactions. +- Keep only router/API boundaries mocked. + +### `frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js` + +**Primary analog** + +- Current accessibility and unsupported-capability suite: [frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js:47-257] + +**Copy these conventions** + +- Assert emitted events, `aria-disabled`, notices, and touch-target classes from the real component. +- Extend this file for new capability buttons only; do not hide cloud actions based on mode. + +### `frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js` + +**Closest analogs** + +- Capability interaction assertions: [frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js:89-190] +- Local upload queue behavior source: [frontend/src/views/FileManagerView.vue:103-128] + +**No exact analog** + +- There is no existing paused sequential queue test file. Create it as a dedicated component suite around the shared browser’s new queue/conflict UI. + +### `frontend/src/views/__tests__/CloudFolderOpenPreview.test.js` + +**Closest analogs** + +- Rendered cloud flow: [frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js:170-260] +- Authorized document content fetch helper: [frontend/src/api/documents.js:60-81] + +**No exact analog** + +- There is no cloud open/preview rendered-flow test today. + +### `frontend/src/stores/__tests__/cloudConnections.test.js` + +**Primary analog** + +- Existing store tests for selection, freshness, session state: [frontend/src/stores/__tests__/cloudConnections.test.js:28-237] + +**Copy these conventions** + +- Keep pure store tests focused on mapping and reset behavior. +- If queue state or health-state translation lives in the store, test it here once. + +### `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` + +**Primary analog** + +- Existing settings-tab tests: [frontend/src/components/settings/__tests__/SettingsCloudTab.test.js:1-132] + +**Copy these conventions** + +- Mock the store and API module, not network. +- Keep component tests structural and action-trigger oriented. + +### `frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js` + +**Closest analogs** + +- Existing settings tab structure: [frontend/src/components/settings/__tests__/SettingsCloudTab.test.js:51-132] +- Store freshness/health state assertions: [frontend/src/stores/__tests__/cloudConnections.test.js:160-237] + +**No exact analog** + +- There is no dedicated health/test/reconnect settings suite yet. + +## Shared Patterns to Reuse Everywhere + +### Authentication / ownership + +- Regular-user-only dependency: [backend/api/cloud/browse.py:29-31], [backend/api/cloud/connections.py:25-27], [backend/api/documents/content.py:24-26] +- Owner resolution before resource work: [backend/api/cloud/browse.py:205-214], [backend/api/cloud/connections.py:132-141], [backend/services/cloud_items.py:38-62] + +### Router shape + +- Thin routers with typed bodies + `account_limiter`: [backend/api/cloud/connections.py:281-304], [backend/api/cloud/browse.py:183-200], [backend/api/folders.py:122-149] +- Response shaping through explicit Pydantic schemas or dict helpers, never raw ORM/provider payloads: [backend/api/cloud/browse.py:64-97], [backend/api/cloud/schemas.py:17-67] + +### Error translation + +- `CloudConnectionError` becomes controlled reconnect guidance: [backend/api/documents/upload.py:160-175], [backend/api/documents/content.py:61-76] +- Incomplete/stale provider state becomes controlled warning, not fake success: [backend/services/cloud_items.py:319-342], [backend/api/cloud/browse.py:296-307] + +### Audit + +- Use `write_audit_log(...)` after successful state change, inside caller-owned transaction: [backend/services/audit.py:27-58] + +### Provider boundary + +- Providers normalize data and keep SDK/HTTP details private: [backend/storage/google_drive_backend.py:276-362], [backend/storage/onedrive_backend.py:300-386], [backend/storage/webdav_backend.py:241-332] +- Factories own provider construction and URL normalization: [backend/storage/cloud_backend_factory.py:7-66] + +### Frontend architecture + +- Thin view -> shared browser: [frontend/src/views/CloudFolderView.vue:1-20], [frontend/src/views/FileManagerView.vue:1-30] +- Shared browser owns interaction/layout/state: [frontend/src/components/storage/StorageBrowser.vue:424-681] +- Settings tab owns connection controls, not provider-specific subviews: [frontend/src/components/settings/SettingsCloudTab.vue:21-214] + +### Testing + +- Integration tests assert API contract plus DB side effect: [backend/tests/test_cloud.py:532-655], [backend/tests/test_cloud.py:1112-1249] +- Security tests assert absence of leaks and forbidden side effects: [backend/tests/test_cloud_security.py:197-301] +- View tests stub the browser for orchestration-only checks; rendered-flow tests use the real browser: [frontend/src/views/__tests__/CloudFolderView.test.js:51-101], [frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js:170-205] + +## No-Analog / planner watchlist + +1. Provider-neutral mutation result dataclasses do not exist yet. Add them in `backend/storage/cloud_base.py`; do not leak provider payloads. +2. OAuth reconnect that patches an existing connection row does not exist yet. Current callback flow inserts a new row. +3. Shared paused upload queue UI for cloud conflicts/errors does not exist yet. Add it in `StorageBrowser.vue`, not a cloud-only component. +4. Authorized cloud-item preview/open endpoints do not exist yet. Reuse document content streaming patterns, but this is a new endpoint family. +5. Dedicated reconnect/audit/open-preview test suites do not exist yet; create them instead of overloading unrelated files. + +## Recommended implementation order from pattern proximity + +1. Extend `cloud_base.py` + provider backends + factory. +2. Add `cloud_operations.py` and keep all reconciliation through `cloud_items.py`. +3. Add `api/cloud/operations.py`, then minimally extend `connections.py` / `schemas.py`. +4. Extend `api/cloud.js` + `cloudConnections.js` + `CloudFolderView.vue`. +5. Extend `StorageBrowser.vue` and `SettingsCloudTab.vue`. +6. Add the missing dedicated backend/frontend test files before broad polish. + +## PATTERN MAPPING COMPLETE diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md b/.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md new file mode 100644 index 0000000..96b0581 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md @@ -0,0 +1,524 @@ +# Phase 13: Virtual-Local Cloud Operations - Research + +**Researched:** 2026-06-22 +**Domain:** Provider-neutral cloud mutations, authorized cloud open/preview, connection health recovery, and shared browser UX +**Confidence:** HIGH + +## User Constraints + +Deliver owner-authorized connection maintenance and the main cloud file-management operations through the same shared browser interactions used for local files: test/reconnect/disconnect, open/preview, upload, create folder, rename, move within one connection, and delete. Mutations must respect normalized provider capabilities, promptly reconcile metadata, refresh affected listings, and emit metadata-only audit events. Phase 14 owns temporary byte-cache lifecycle and analysis; permanent cloud-to-local import remains future requirement `IMPORT-01`. + +- **D-01:** Cloud open, preview, and upload must feel the same as local storage and reuse the same shared components, progress presentation, and interaction paths. `CloudFolderView` remains a thin data provider; cloud-specific transport does not justify parallel UI. +- **D-02:** Preview stays inside DocuVault and must not trigger a browser-to-device download. Unsupported preview formats fall back to an ownership-checked authorized download; provider credentials and raw provider URLs are never exposed. +- **D-03:** A same-name upload opens a conflict dialog and never overwrites silently. The available decisions are Keep both with a renamed item, Replace where supported, Skip, and Cancel all. +- **D-04:** Multi-file uploads run as a sequential queue. A conflict or error pauses the whole queue for user input without losing remaining items. Conflict actions are Keep both, Replace, Skip, and Cancel all; error actions are Retry, Skip, and Cancel all. Retry/Keep both/Replace/Skip resume the queue, while Cancel all stops it. +- **D-05:** Create-folder and rename collisions automatically choose a non-conflicting human-readable counter name: `Report (1).pdf`, `Report (2).pdf`, or `Projects (1)` for folders. +- **D-06:** If a concurrent client takes the candidate name between checking and mutation, retry with the next counter within a small bounded number of attempts. +- **D-07:** Rename, move, or delete must not operate on stale metadata when the item changed externally. Stop the mutation, refresh the affected folder, explain what changed, and ask the user to retry. +- **D-08:** Moving cloud items matches local storage: support both drag-to-folder and the shared folder picker. Destinations are restricted to the same cloud connection; cross-provider transfer remains out of scope. +- **D-09:** Invalid folder destinations, including the folder itself and its descendants, are disabled before submission and independently rejected by the backend. +- **D-10:** Delete confirmation is context-sensitive. Files receive a standard confirmation; folders clearly warn that nested contents are included. +- **D-11:** Prefer the provider's trash/recycle-bin operation when supported. When only permanent deletion is available, the confirmation must say so explicitly. +- **D-12:** Connection health appears in both places users need it: compact status plus Reconnect in the cloud browser, and full diagnostics plus Test/Reconnect controls in Settings. +- **D-13:** Test automatically after connect/reconnect and after credential-related failures, and allow an explicit Test action. Do not probe the provider on every folder navigation. +- **D-14:** A successful reconnect keeps cached metadata visible as stale, invalidates provider/listing/capability caches, and immediately refreshes the current folder. +- **D-15:** A timeout or temporarily unreachable provider is an unhealthy connection state only. Preserve credentials and cached metadata, show an actionable warning, and allow retry/reconnect; never delete data because of transient failure. +- **D-16:** Explicit user-initiated Disconnect requires confirmation, removes credentials and connection-scoped cloud metadata, and leaves all provider files untouched. Phase 13 has no byte cache to migrate. + +Out of scope for this phase: + +- Phase 14 temporary preview-byte cache and eviction behavior. +- `IMPORT-01`: preserve cached/downloaded files as quota-counted local documents on explicit disconnect, under a connection-named folder with the provider hierarchy retained. + +## Phase Requirements + +| Req ID | Requirement | Locked interpretation for Phase 13 | +|---|---|---| +| CONN-01 | User can connect, reconnect, test, and disconnect each supported cloud provider. | Add explicit test and reconnect flows without creating a parallel cloud UX. | +| CONN-02 | User can see connection health and actionable errors for expired, revoked, or invalid credentials. | Surface compact browser status and fuller Settings diagnostics with controlled provider-error mapping. | +| CONN-03 | Reconnecting or refreshing credentials invalidates stale provider caches without exposing credentials. | Reconnect must preserve metadata rows as stale, invalidate cache layers, and refresh current browse state. | +| CLOUD-02 | User can open and preview supported cloud documents through DocuVault authorization. | Preview stays in-app; fallback download is still DocuVault-authorized and never a raw provider URL. | +| CLOUD-03 | User can upload files into the currently viewed cloud folder. | Use the shared upload queue and conflict-resolution flow already implied by local UX. | +| CLOUD-04 | User can create folders in connected cloud storage where the provider supports it. | Automatic collision suffixing plus bounded retry on concurrent races. | +| CLOUD-05 | User can rename cloud files and folders where the provider supports it. | Same counter-suffix policy as create; stale metadata must stop-and-refresh, not force. | +| CLOUD-06 | User can move files and folders within the same cloud connection where the provider supports it. | Shared picker and drag-move UX; reject self/descendant and cross-connection moves in UI and backend. | +| CLOUD-07 | User can delete cloud files and folders after explicit confirmation. | Prefer trash/recycle-bin where supported; confirmation must disclose permanent-delete providers. | +| CLOUD-09 | Successful cloud mutations update navigation promptly and produce metadata-only audit events. | Mutations must reconcile `cloud_items`, refresh folder state, and write same-transaction metadata-only audit rows. | + +## Project Constraints (from AGENTS.md) + +- `StorageBrowser.vue` remains the single file browser; `CloudFolderView.vue` and `FileManagerView.vue` stay thin data providers. [VERIFIED: AGENTS.md] +- No router-local duplicate helpers. Shared helpers belong in the existing module map: `backend/deps/utils.py`, `backend/storage/exceptions.py`, `backend/services/auth.py`, `backend/storage/cloud_base.py`, `backend/services/cloud_items.py`, `backend/api/cloud/schemas.py`. [VERIFIED: AGENTS.md] +- Service layer raises domain errors or `ValueError`, never `HTTPException`; routers translate them. [VERIFIED: AGENTS.md] +- Cloud browse and refresh must remain metadata-only and must not download provider bytes or mutate quota. [VERIFIED: AGENTS.md; VERIFIED: `backend/tests/test_cloud_security.py`] +- `reconcile_cloud_listing` remains the only metadata reconciliation entry point; provider backends must not update `cloud_items` rows directly. [VERIFIED: AGENTS.md; VERIFIED: `backend/services/cloud_items.py`] +- JWT access token stays in Pinia memory only; refresh token stays in `httpOnly` Strict cookie only. Any new cloud endpoints must preserve the existing auth model. [VERIFIED: AGENTS.md] +- Every new endpoint, store path, service function, and shared component behavior must ship with tests, and backend/frontend suites must pass before the phase is complete. [VERIFIED: AGENTS.md] +- Security gates remain mandatory: ownership checks on every resource path, CSRF protection on all state-changing endpoints, no credential leakage, SSRF allowlisting for WebDAV/Nextcloud, metadata-only audit logs, and no admin access to document content. [VERIFIED: AGENTS.md] + +## Summary + +Phase 13 should extend the existing cloud browse foundation rather than branch around it. The codebase already has the right structural spine: a normalized `CloudResourceAdapter` vocabulary, owner-scoped connection-ID browse routes, centralized `cloud_items` reconciliation, a shared `StorageBrowser`, connection health statuses in Settings, and provider SDK wrappers that already know how to upload/download/delete bytes at the backend boundary. The missing work is the orchestration layer that turns those primitives into safe, provider-neutral cloud mutations and authorized open/preview flows. [VERIFIED: `backend/storage/cloud_base.py`; VERIFIED: `backend/api/cloud/browse.py`; VERIFIED: `backend/services/cloud_items.py`; VERIFIED: `frontend/src/components/storage/StorageBrowser.vue`; VERIFIED: `frontend/src/views/CloudFolderView.vue`] + +The highest-leverage implementation is to add a Phase 13 mutation/content contract adjacent to `CloudResourceAdapter`, keep provider-specific behavior in the backend adapters, and route all open/preview/mutate actions through owner-scoped connection-ID endpoints that reconcile metadata and emit audit rows in the same transaction. That preserves Phase 12’s normalized model, avoids any provider-specific Vue branches, and keeps raw provider IDs, URLs, tokens, and download links off the client. [VERIFIED: `backend/services/audit.py`; VERIFIED: `backend/db/models.py`; CITED: Google Drive and Microsoft Graph content/download docs] + +Two execution risks need explicit planning attention. First, OAuth reconnect currently creates a new connection row rather than reauthorizing an existing one, which conflicts with D-14 and CONN-03. Second, OneDrive token refresh is currently in-memory only, so successful mutation/open flows can silently depend on credentials that are never persisted back to `cloud_connections.credentials_enc`. Both issues are Phase 13 blockers for trustworthy reconnect and mutation semantics. [VERIFIED: `backend/api/cloud/connections.py`; VERIFIED: `backend/storage/onedrive_backend.py`] + +**Primary recommendation:** Implement Phase 13 as one provider-neutral cloud operations layer: `connection-id API -> service orchestration -> mutable cloud adapter -> reconcile/audit/freshness update -> shared StorageBrowser`, with reconnect and token-refresh persistence treated as Wave 1 platform work before UI polish. + +## Architectural Responsibility Map + +| Capability | Primary Tier | Secondary Tier | Rationale | +|---|---|---|---| +| Connection test/reconnect/disconnect | Backend API + service orchestration | SettingsCloudTab / CloudFolderView | Health truth and credential mutation are server-owned; UI only presents state and user intent. | +| Open / preview / authorized download | Backend content endpoint | StorageBrowser action surface | Browser must never receive raw provider URLs or credentials. | +| Upload queue + conflict UI | StorageBrowser + CloudFolderView | Backend mutation service | Queue pause/resume is shared UX state; actual upload and conflict truth come from backend/provider. | +| Create / rename / move / delete semantics | Mutable cloud adapter + cloud operations service | StorageBrowser | Provider differences belong in adapters; shared UI should consume normalized outcomes. | +| Metadata reconciliation after mutation | `backend/services/cloud_items.py` | Celery refresh task | Stable IDs and freshness semantics already live here; do not duplicate in routers or adapters. | +| Connection capability / health refresh | Provider adapter | cloudConnections Pinia store | Adapter knows scope/reauth/offline truth; store caches the server result for browser/settings reuse. | +| Metadata-only audit events | Backend API/service transaction | Admin audit UI | Existing `write_audit_log()` helper already fits the phase requirement. | +| Security enforcement | FastAPI deps + provider validators | Tests | Ownership, CSRF, SSRF, and secrecy are backend invariants, not UI conventions. | + +## Standard Stack + +### Core + +| Library | Version | Purpose | Why Standard | +|---|---|---|---| +| FastAPI | 0.128.8 | Owner-scoped cloud API routes and response schemas | Already the project’s canonical API framework; adding Phase 13 endpoints here avoids split auth behavior. [VERIFIED: `backend/requirements.txt`] | +| SQLAlchemy async | 2.0.49 | Atomic metadata reconciliation and audit writes | Existing ORM layer already owns `cloud_items`, `cloud_folder_states`, `cloud_connections`, and `audit_log`. [VERIFIED: `backend/requirements.txt`] | +| google-api-python-client | 2.197.0 | Google Drive move/rename/delete/content operations | Already installed and used by the Drive backend; no new SDK needed. [VERIFIED: `backend/requirements.txt`] | +| msal | 1.37.0 | OneDrive/Graph token handling | Already installed and wrapped by the OneDrive backend. [VERIFIED: `backend/requirements.txt`] | +| webdavclient3 | 3.14.7 | WebDAV/Nextcloud PUT/MKCOL/MOVE/DELETE primitives | Already installed and used by both DAV adapters. [VERIFIED: `backend/requirements.txt`] | +| Vue | 3.5.38 | Shared browser flows in the existing frontend | Existing thin-view + smart-component architecture already matches the phase rules. [VERIFIED: `frontend/package.json`] | +| Vitest | 4.1.7 | Frontend interaction and rendered-flow regression tests | Already pinned and used across cloud/browser tests. [VERIFIED: `frontend/package.json`] | +| pytest | 9.0.3 | Backend provider/API/security contract tests | Already pinned and used across cloud suites. [VERIFIED: `backend/requirements.txt`] | + +### Supporting + +| Library | Version | Purpose | When to Use | +|---|---|---|---| +| httpx | 0.28.1 | Async integration/API tests and provider HTTP boundaries | Keep for endpoint tests and mocked provider transports. [VERIFIED: `backend/requirements.txt`] | +| Celery | 5.6.3 | Folder refresh after reconnect or stale-metadata recovery | Reuse for background refresh only; Phase 13 should not introduce separate async machinery. [VERIFIED: `backend/requirements.txt`] | +| Pinia | 2.1.0 | Cloud browse/health state and upload-queue coordination | Keep queue state and server freshness centralized, but keep provider semantics in backend APIs. [VERIFIED: `frontend/package.json`] | + +### Alternatives Considered + +| Instead of | Could Use | Tradeoff | +|---|---|---| +| Existing provider SDKs | New abstraction package or sync client library | Adds risk and duplicates code the repo already carries. | +| Authorized backend preview/download | Browser-direct provider links | Violates D-02 and the project’s credential/privacy boundary. | +| Shared StorageBrowser extension | Cloud-only grid or modal stack | Violates the “looks the same to the user => same code” rule. | + +**Installation:** + +```bash +# None — Phase 13 should reuse the repository's existing pinned stack. +``` + +**Version verification:** No new external packages are recommended in this research. The versions above are the repository’s pinned execution versions from `backend/requirements.txt` and `frontend/package.json`, which is sufficient for Phase 13 planning because the recommendation is to stay within the existing stack. [VERIFIED: repository pins] + +## Package Legitimacy Audit + +No external package install is recommended for Phase 13. + +| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition | +|---|---|---:|---:|---|---|---| +| none | — | — | — | — | OK | Reuse existing pinned dependencies only | + +**Packages removed due to [SLOP] verdict:** none +**Packages flagged as suspicious [SUS]:** none + +## Architecture Patterns + +### System Architecture Diagram + +```mermaid +flowchart TD + U["User action in shared StorageBrowser"] --> V["CloudFolderView / SettingsCloudTab
(thin data providers)"] + V --> API["FastAPI cloud endpoints
connection-id scoped"] + API --> S["Cloud operations service
validate ownership, stale state, conflict policy"] + S --> A["Mutable cloud adapter
Google / OneDrive / Nextcloud / WebDAV"] + A --> P["Provider API / WebDAV server"] + S --> R["reconcile_cloud_listing / folder freshness"] + S --> L["write_audit_log(metadata only)"] + R --> API + API --> V + V --> B["StorageBrowser props
items, capabilities, queue, health"] + B --> U + S -. refresh after reconnect / stale mismatch .-> C["Celery refresh_cloud_folder"] + C --> A +``` + +### Recommended Project Structure + +```text +backend/ +├── api/cloud/ +│ ├── browse.py # existing read path +│ ├── connections.py # existing connect/rename/disconnect path +│ ├── operations.py # Phase 13 mutate/open/preview/test endpoints +│ └── schemas.py # extend with mutation/result payloads only +├── services/ +│ ├── cloud_items.py # existing reconciliation/freshness source of truth +│ ├── cloud_operations.py # Phase 13 orchestration / stale checks / audit wiring +│ └── audit.py # existing metadata-only audit helper +├── storage/ +│ ├── cloud_base.py # extend with mutable adapter contract +│ ├── google_drive_backend.py +│ ├── onedrive_backend.py +│ ├── nextcloud_backend.py +│ └── webdav_backend.py +frontend/src/ +├── views/CloudFolderView.vue # keep thin; swap placeholders for API/store handlers +├── components/storage/StorageBrowser.vue +├── stores/cloudConnections.js # add health + queue state, not provider logic +└── api/cloud.js # add Phase 13 client methods +``` + +### Pattern 1: Provider-neutral mutation results + +**What:** Add a mutable cloud adapter contract that returns normalized outcomes such as `updated_item`, `affected_parent_refs`, `conflict`, `stale`, `reauth_required`, and `used_trash`, rather than leaking provider response shapes into routers or Vue. + +**When to use:** Every create/rename/move/delete/upload/open/preview/test operation. + +**Example:** + +```python +# Pattern adapted from official provider docs and current DocuVault contracts. +result = await adapter.rename_item( + connection_id=conn.id, + user_id=user.id, + provider_item_id=item.provider_item_id, + target_name=candidate_name, + if_match=item.etag, +) +``` + +**Why:** Google Drive, Graph, and WebDAV all expose different verbs and conflict signals, but the UI only needs normalized outcomes. [CITED: Google Drive `files.update`; CITED: Microsoft Graph `driveItem-update`; CITED: RFC 4918 MOVE/Overwrite] + +### Pattern 2: Reconcile after mutate, not before response only + +**What:** Every successful mutation should update the provider first, then reconcile local metadata and folder freshness in the same request transaction before returning. + +**When to use:** Upload, create folder, rename, move, delete, reconnect refresh. + +**Example:** + +```python +provider_result = await adapter.delete_item(...) +await apply_mutation_reconciliation(session, provider_result) +await write_audit_log(session, event_type="cloud.item.deleted", ...) +``` + +**Why:** `cloud_items` owns stable row identity and browse correctness. Returning success before reconcile creates stale navigation and violates CLOUD-09. [VERIFIED: `backend/services/cloud_items.py`; VERIFIED: `backend/services/audit.py`] + +### Pattern 3: Sequential shared upload queue with pause reasons + +**What:** Keep queue state in the cloud view/store, but treat each conflict or provider error as a paused queue state that requires an explicit next action. + +**When to use:** Multi-file upload from the shared StorageBrowser. + +**Example:** + +```javascript +// Queue state belongs in shared UI flow, not provider code. +queue = [{ file, state: 'running' | 'paused_conflict' | 'paused_error' | 'done' }] +``` + +**Why:** D-03 and D-04 are user-experience rules, not provider rules. The backend should return normalized conflict/error responses; the shared browser should decide whether to resume, skip, retry, or cancel all. [VERIFIED: `frontend/src/components/storage/StorageBrowser.vue`; VERIFIED: `frontend/src/views/FileManagerView.vue`] + +### Anti-Patterns to Avoid + +- **Cloud-only browser layout:** violates the locked single-browser rule and will drift from local behavior. +- **Provider-specific route parameters in Vue:** keep using connection UUID + opaque `provider_item_id`; never split or derive paths client-side. +- **Raw provider download URLs in responses:** violates D-02 and leaks provider internals. +- **Blind overwrite on rename/upload/create:** violates D-03, D-05, D-06, and provider conditional-write semantics. +- **Reconnect by creating a new connection row:** breaks CONN-03 and D-14 because cached metadata and stable navigation become orphaned. + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---|---|---|---| +| Provider auth / token dance | Custom OAuth or refresh logic | Existing Google/MSAL + current backend wrappers | The repo already carries these SDKs and their edge cases. | +| WebDAV mutation semantics | Ad hoc HTTP verbs assembled in routers | Existing WebDAV backend methods plus RFC-compliant headers | MOVE/MKCOL/DELETE/PUT conflict behavior is subtle. | +| Audit pipeline | New cloud-only audit table | Existing `services.audit.write_audit_log()` | Same-transaction metadata logging already exists. | +| Shared file UI | New cloud grid/dialog system | `StorageBrowser.vue` + thin provider views | Project rule forbids parallel code for same-looking UX. | +| Client-side stale detection | Heuristics in Vue | Backend etag/version preconditions + refresh results | Only the backend has trustworthy provider state. | +| Permanent preview cache | New Phase-13 cache subsystem | Minimal authorized open/preview hydration now; Phase 14 owns lifecycle | Prevents scope bleed into CACHE-03/04/05. | + +**Key insight:** Phase 13 is not a package-selection problem; it is a contract-extension problem. The codebase already has the right libraries, so hand-rolled divergence is a bigger risk than missing dependencies. + +## Common Pitfalls + +### Pitfall 1: Google Drive scope looks writable but is still visibility-limited + +**What goes wrong:** The app can mutate only files within the `drive.file` visibility boundary, so “browse all of My Drive and mutate anything” fails even though the SDK calls are correct. + +**Why it happens:** `drive.file` is least-privilege and only covers files the user has opened with or created via the app. [CITED: Google Drive API scopes] + +**How to avoid:** Treat scope limitations as a first-class capability/health outcome, and decide explicitly whether Phase 13 should preserve `drive.file` or require a broader scope upgrade with user consent. + +**Warning signs:** Items appear in browse flows inconsistently, capability state flips to reauth/scope warnings, or open/mutate actions fail only on pre-existing files. + +### Pitfall 2: OneDrive refresh succeeds once but future requests regress + +**What goes wrong:** A request refreshes the access token in memory, but the persisted encrypted credentials remain stale, so later requests or workers fail again. + +**Why it happens:** The current backend refresh helper updates runtime state but does not persist the new credential set. [VERIFIED: `backend/storage/onedrive_backend.py`] + +**How to avoid:** Return refreshed credentials from the adapter/service boundary and persist them atomically when a request or reconnect succeeds. + +**Warning signs:** Health check passes immediately after reconnect, then later background refresh or a second request returns `REQUIRES_REAUTH`. + +### Pitfall 3: WebDAV overwrite rules differ from local expectations + +**What goes wrong:** MOVE/rename/create behavior overwrites or conflicts differently across servers. + +**Why it happens:** WebDAV uses protocol-level overwrite semantics, not local filesystem UX defaults. `Overwrite: F` must return `412 Precondition Failed` when the destination exists. [CITED: RFC 4918] + +**How to avoid:** Normalize create/rename/move through explicit collision probing or conditional requests and convert provider responses into Keep-both / Replace / Skip / Retry UI outcomes. + +**Warning signs:** Same-name moves unexpectedly replace files, or rename conflicts surface as generic 500/409 errors without a resumable queue state. + +### Pitfall 4: Preview leaks provider internals + +**What goes wrong:** The browser receives a raw Drive/Graph/WebDAV URL or provider download token. + +**Why it happens:** Provider SDKs often expose “downloadUrl” conveniences that are tempting to forward. [CITED: Microsoft Graph `driveItem` resource] + +**How to avoid:** Keep preview/open/download as backend-authorized proxy or streaming endpoints and redact provider-only details from all responses. + +**Warning signs:** Frontend code stores provider URLs, `window.open()` targets third-party hosts directly, or logs include download URLs. + +### Pitfall 5: Shared browser queue and provider mutation truth get split + +**What goes wrong:** The UI invents local queue conflict decisions that the backend/provider never confirmed. + +**Why it happens:** Local UX seems simple, but cloud conflicts can depend on provider state, scope, etag, and stale metadata. + +**How to avoid:** Make the backend authoritative for conflict/stale/offline classification and let the shared browser only orchestrate the user’s next action. + +**Warning signs:** Keep-both names diverge from what the provider actually created, or retry resumes without a fresh backend decision. + +## Code Examples + +Verified patterns from official sources: + +### Google Drive move within one parent graph + +```python +# Source pattern: https://developers.google.com/workspace/drive/api/reference/rest/v3/files/update +# Drive files have a single parent; moves are addParents/removeParents, not path rewrites. +await drive.files().update( + fileId=file_id, + addParents=new_parent_id, + removeParents=old_parent_id, + body={}, +).execute() +``` + +### Microsoft Graph safe rename / move with precondition + +```python +# Source pattern: https://learn.microsoft.com/en-us/graph/api/driveitem-update?view=graph-rest-1.0 +# Use PATCH and send If-Match when etag is known so stale items fail safely. +await graph.patch( + f"/me/drive/items/{item_id}", + headers={"If-Match": etag}, + json={"name": new_name, "parentReference": {"id": dest_id}}, +) +``` + +### WebDAV conflict-aware move + +```python +# Source pattern: RFC 4918 MOVE with Overwrite: F +# Existing destination should yield 412, which maps cleanly to a Keep-both/Replace prompt. +MOVE source -> destination +Headers: + Destination: + Overwrite: F +``` + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|---|---|---|---| +| Separate local/cloud browser logic | One shared browser with normalized item shape and capabilities | Phase 12 / 12.1 | Phase 13 should extend shared events, not create cloud-only UI. [VERIFIED: Phase 12/12.1 artifacts] | +| “Health” inferred from a successful browse response | Explicit freshness/health state from backend plus connection status | Phase 12.1 | Reconnect/test flows should preserve stale metadata instead of clearing state. [VERIFIED: `backend/services/cloud_items.py`; VERIFIED: `backend/api/cloud/browse.py`] | +| Provider-specific direct content links | Authorized backend-mediated open/preview/download | Modern cloud SaaS security norm | Keeps provider credentials and raw URLs off the client. [CITED: Graph content/download model; CITED: Drive export/download model] | +| N+1 WebDAV-style metadata fetches | Prefer one authoritative browse/mutate contract and conditional operations | Current provider reliability direction | Reduces stale/conflict ambiguity and makes provider differences testable. [VERIFIED: current code; CITED: Nextcloud WebDAV basic ops; RFC 4918] | + +**Deprecated/outdated:** + +- Treating OAuth reconnect as “add another account” when the user intends to repair an existing connection. This no longer matches the locked D-14/D-16 behavior. +- Treating frontend timestamps or HTTP 200 alone as proof of provider freshness. Phase 12.1 explicitly moved freshness truth to the backend. + +## Assumptions Log + +| # | Claim | Section | Risk if Wrong | +|---|---|---|---| +| A1 | Phase 13 should preserve the current Google Drive `drive.file` scope unless the planner/user explicitly chooses a broader consent surface. | Common Pitfalls / Security Domain | Medium — some user-visible mutations may be impossible on previously existing Drive items. | +| A2 | Reconnect for OAuth providers should update an existing `cloud_connections` row rather than create a replacement row. | Summary / Architecture Patterns | High — wrong choice breaks stable navigation, cache invalidation, and metadata continuity. | +| A3 | Preview/open can be implemented with authorized backend hydration now without introducing the full persistent cache lifecycle reserved for Phase 14. | Summary / Don’t Hand-Roll | Medium — if the implementation implicitly requires durable cache semantics, scope bleeds into Phase 14. | + +## Open Questions (RESOLVED) + +1. **Google Drive scope — RESOLVED:** Request broader Google Drive access for Phase 13 UX parity rather than retaining `drive.file`. Consent copy and security tests must explicitly cover the expanded scope. [USER DECISION: 2026-06-22; CITED: Google Drive API scopes] +2. **OAuth reconnect model — RESOLVED:** Use a connection-ID reconnect intent whose OAuth state identifies and patches the existing owned `cloud_connections` row, preserving stable metadata identity while invalidating provider/listing/capability caches. [AGENT DISCRETION; VERIFIED: D-14 and current callback behavior] +3. **Upload queue payload — RESOLVED:** Use typed JSON conflict/error bodies with stable `kind` and `reason` codes; keep pause/resume queue state in the shared frontend flow and do not introduce resumable operation tokens in Phase 13. [AGENT DISCRETION; VERIFIED: D-03/D-04] +4. **Preview matrix — RESOLVED:** Phase 13 supports only supported binary file preview. Google Workspace export preview and Microsoft Office-native rendering/editing are excluded; unsupported formats use the authorized download fallback. A future phase will integrate Collabora in a separate internally accessible container. [USER DECISION: 2026-06-22] + +## Environment Availability + +| Dependency | Required By | Available | Version | Fallback | +|---|---|---|---|---| +| `docker` / `docker compose` | Backend integration/security runs and service-backed validation | ✓ | Docker 29.5.3 | — | +| `node` | Frontend Vitest runs | ✓ | v26.3.1 | — | +| `npm` | Frontend scripts | ✓ | 11.16.0 | — | +| `python3` (host) | Ad hoc local scripts only | ✓ | 3.9.6 | Use containerized backend for project Python 3.12 behavior | +| `pytest` (host) | Direct host backend test execution | ✗ | — | Run backend tests in the backend container or a project venv | + +**Missing dependencies with no fallback:** +- none + +**Missing dependencies with fallback:** +- Host `pytest` is unavailable; use `docker compose run --rm backend pytest ...` or a project-local venv. +- Host Python is 3.9.6 while the project target is Python 3.12; use the backend container for execution-fidelity checks. + +## Validation Architecture + +### Test Framework + +| Property | Value | +|---|---| +| Framework | Backend: `pytest 9.0.3` + `pytest-asyncio 1.4.0`; Frontend: `vitest 4.1.7` | +| Config file | Backend: none explicit in repo root; Frontend: Vite/Vitest defaults via `frontend/package.json` | +| Quick run command | Backend: `docker compose run --rm backend pytest -v tests/test_cloud.py tests/test_cloud_security.py -x` ; Frontend: `cd frontend && npm run test -- src/views/__tests__/CloudFolderView.test.js src/views/__tests__/CloudFolderRenderedFlow.test.js src/components/storage/__tests__/StorageBrowser.capabilities.test.js` | +| Full suite command | `docker compose run --rm backend pytest -v` and `cd frontend && npm run test` | + +### Phase Requirements → Test Map + +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|---|---|---|---|---| +| CONN-01 | connect, reconnect, explicit test, disconnect for each provider | backend integration + frontend component/store | `docker compose run --rm backend pytest -v tests/test_cloud.py -k "connect or disconnect or reconnect or test"` | ✅ extend `backend/tests/test_cloud.py`; ✅ extend `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` | +| CONN-02 | actionable connection health for expired/revoked/invalid creds | backend integration + frontend component | `docker compose run --rm backend pytest -v tests/test_cloud.py tests/test_cloud_security.py -k "reauth or invalid or health"` | ✅ extend existing suites | +| CONN-03 | reconnect invalidates caches without exposing creds | backend integration + security + store | `docker compose run --rm backend pytest -v tests/test_cloud.py tests/test_cloud_security.py -k "cache or credential"` | ✅ extend `backend/tests/test_cloud.py`; ✅ extend `frontend/src/stores/__tests__/cloudConnections.test.js` | +| CLOUD-02 | authorized open/preview/download with no raw provider URLs | backend API/security + rendered-flow | `docker compose run --rm backend pytest -v tests/test_cloud.py tests/test_cloud_security.py -k "open or preview or content"` | ❌ Wave 0 add dedicated backend content tests; ✅ extend rendered-flow suites | +| CLOUD-03 | upload into current cloud folder with sequential queue + conflict handling | backend integration + frontend view/component | `docker compose run --rm backend pytest -v tests/test_cloud.py -k "upload"` and `cd frontend && npm run test -- src/views/__tests__/CloudFolderView.test.js` | ✅ existing files to extend; ❌ Wave 0 add queue/conflict suite | +| CLOUD-04 | create folder with keep-both suffix + bounded retry | backend provider contract + integration | `docker compose run --rm backend pytest -v tests/test_cloud_backends.py tests/test_cloud.py -k "create_folder"` | ❌ Wave 0 add mutation contract cases | +| CLOUD-05 | rename file/folder with stale protection and suffixing | backend provider contract + integration + rendered-flow | `docker compose run --rm backend pytest -v tests/test_cloud_backends.py tests/test_cloud.py -k "rename"` | ❌ Wave 0 add rename mutation suite | +| CLOUD-06 | move within same connection, reject self/descendant/cross-connection | backend provider contract + security + frontend interaction | `docker compose run --rm backend pytest -v tests/test_cloud.py tests/test_cloud_security.py -k "move"` | ❌ Wave 0 add move suite; ✅ extend `StorageBrowser.dragmove` coverage if needed | +| CLOUD-07 | delete with explicit confirmation and trash/permanent semantics | backend provider contract + integration + frontend component | `docker compose run --rm backend pytest -v tests/test_cloud.py tests/test_cloud_security.py -k "delete"` | ❌ Wave 0 add delete mutation suite | +| CLOUD-09 | prompt navigation refresh and metadata-only audit log on success | backend integration + audit assertion + rendered-flow | `docker compose run --rm backend pytest -v tests/test_cloud.py -k "audit or refresh"` | ❌ Wave 0 add audit-specific cloud mutation assertions | + +### Sampling Rate + +- **Per task commit:** backend targeted cloud suite + frontend targeted cloud suite for the touched behavior +- **Per wave merge:** `docker compose run --rm backend pytest -v tests/test_cloud.py tests/test_cloud_backends.py tests/test_cloud_provider_contract.py tests/test_cloud_security.py tests/test_cloud_items.py` and `cd frontend && npm run test` +- **Phase gate:** Full backend suite green, full frontend suite green, then security/dependency gates before `$gsd-verify-work` + +### Wave 0 Gaps + +- [ ] `backend/tests/test_cloud_mutations.py` — provider-neutral mutation contract for create/rename/move/delete/upload/open/preview result shapes +- [ ] `backend/tests/test_cloud_reconnect.py` or equivalent expansion in `test_cloud.py` — connection-ID reconnect semantics, token persistence, cache invalidation, metadata retention +- [ ] `backend/tests/test_cloud_audit.py` or equivalent mutation assertions in `test_cloud.py` — metadata-only audit rows for each successful mutation +- [ ] `frontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js` — sequential cloud upload queue, conflict pause, error pause, resume/cancel-all +- [ ] `frontend/src/views/__tests__/CloudFolderOpenPreview.test.js` — cloud open/preview/download action behavior through shared browser +- [ ] `frontend/src/components/settings/__tests__/SettingsCloudTab.health.test.js` — explicit Test and Reconnect controls, transient outage vs reauth UI +- [ ] Host backend test runner gap: use containerized pytest until a project-local Python 3.12 venv is provisioned + +## Security Domain + +### Applicable ASVS Categories + +| ASVS Category | Applies | Standard Control | +|---|---|---| +| V2 Authentication | yes | Existing JWT + httpOnly refresh-cookie auth; no provider credentials exposed to client | +| V3 Session Management | yes | Existing token rotation/revocation; reconnect/open endpoints must preserve same auth boundary | +| V4 Access Control | yes | `resolve_owned_connection`, resource ownership checks, admin-negative tests | +| V5 Input Validation | yes | Pydantic/FastAPI request schemas plus opaque provider-ref handling | +| V6 Cryptography | yes | Existing encrypted `credentials_enc` via `cryptography`; no custom crypto | + +### Known Threat Patterns for this stack + +| Pattern | STRIDE | Standard Mitigation | +|---|---|---| +| IDOR on connection or cloud item mutation | Elevation of Privilege | Resolve by connection UUID under current user; reject foreign rows with indistinguishable not-found behavior | +| Raw provider URL / token leakage in preview/download | Information Disclosure | Backend-authorized proxy/stream only; never return `downloadUrl`, access tokens, or `credentials_enc` | +| SSRF through Nextcloud/WebDAV server URL or redirects | Tampering | Reuse `validate_cloud_url`, normalize Nextcloud URLs centrally, and revalidate redirect/host boundaries | +| CSRF on state-changing cloud endpoints | Tampering | Existing SameSite Strict cookie + Origin/Referer validation on every mutate/reconnect/disconnect route | +| Stale-etag mutation or concurrent overwrite | Tampering | Conditional provider writes when supported; on mismatch return controlled stale result and refresh folder | +| Cross-connection move | Tampering | UI restrict destination tree to one connection and backend enforces same-connection invariant | +| Audit log leakage of provider secrets or paths | Information Disclosure | Metadata-only `write_audit_log()` payloads with stable IDs/names/status only | +| Queue confusion causing silent overwrite | Repudiation / Tampering | Conflict responses must be explicit and resumable; no silent replace path | +| Temporary outage treated as destructive disconnect | Denial of Service | Preserve credentials and cached metadata on transient failure; only explicit disconnect purges state | + +## Sources + +### Primary (HIGH confidence) + +- Internal code and tests reviewed directly: + - `AGENTS.md` + - `.planning/ROADMAP.md` + - `.planning/REQUIREMENTS.md` + - `.planning/PROJECT.md` + - `.planning/STATE.md` + - `.planning/phases/13-virtual-local-cloud-operations/13-CONTEXT.md` + - `.planning/phases/12-cloud-resource-foundation/12-RESEARCH.md` + - `.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-RESEARCH.md` + - `backend/storage/cloud_base.py` + - `backend/api/cloud/browse.py` + - `backend/api/cloud/connections.py` + - `backend/services/cloud_items.py` + - `backend/services/audit.py` + - `backend/storage/google_drive_backend.py` + - `backend/storage/onedrive_backend.py` + - `backend/storage/webdav_backend.py` + - `backend/storage/nextcloud_backend.py` + - `frontend/src/components/storage/StorageBrowser.vue` + - `frontend/src/views/CloudFolderView.vue` + - `frontend/src/components/settings/SettingsCloudTab.vue` + - `backend/tests/test_cloud.py` + - `backend/tests/test_cloud_provider_contract.py` + - `backend/tests/test_cloud_security.py` + - `backend/tests/test_cloud_capabilities.py` + - `frontend/src/views/__tests__/CloudFolderView.test.js` + - `frontend/src/views/__tests__/CloudFolderRenderedFlow.test.js` + - `frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js` + - `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` +- Google Drive API scopes: [developers.google.com/workspace/drive/api/guides/api-specific-auth](https://developers.google.com/workspace/drive/api/guides/api-specific-auth) +- Google Drive `files` resource: [developers.google.com/workspace/drive/api/reference/rest/v3/files](https://developers.google.com/workspace/drive/api/reference/rest/v3/files) +- Google Drive `files.update`: [developers.google.com/workspace/drive/api/reference/rest/v3/files/update](https://developers.google.com/workspace/drive/api/reference/rest/v3/files/update) +- Google Drive `files.delete`: [developers.google.com/workspace/drive/api/reference/rest/v3/files/delete](https://developers.google.com/workspace/drive/api/reference/rest/v3/files/delete) +- Google Drive `files.export`: [developers.google.com/workspace/drive/api/reference/rest/v3/files/export](https://developers.google.com/workspace/drive/api/reference/rest/v3/files/export) +- Google Drive `files.download`: [developers.google.com/workspace/drive/api/reference/rest/v3/files/download](https://developers.google.com/workspace/drive/api/reference/rest/v3/files/download) +- Microsoft Graph `driveItem` resource: [learn.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0](https://learn.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0) +- Microsoft Graph create folder: [learn.microsoft.com/en-us/graph/api/driveitem-post-children?view=graph-rest-1.0](https://learn.microsoft.com/en-us/graph/api/driveitem-post-children?view=graph-rest-1.0) +- Microsoft Graph rename/move update: [learn.microsoft.com/en-us/graph/api/driveitem-update?view=graph-rest-1.0](https://learn.microsoft.com/en-us/graph/api/driveitem-update?view=graph-rest-1.0) +- Microsoft Graph move: [learn.microsoft.com/en-us/graph/api/driveitem-move?view=graph-rest-1.0](https://learn.microsoft.com/en-us/graph/api/driveitem-move?view=graph-rest-1.0) +- Microsoft Graph delete: [learn.microsoft.com/en-us/graph/api/driveitem-delete?view=graph-rest-1.0](https://learn.microsoft.com/en-us/graph/api/driveitem-delete?view=graph-rest-1.0) +- Microsoft Graph get content: [learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0) +- Nextcloud WebDAV basic ops: [docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html) +- RFC 4918 WebDAV: [rfc-editor.org/rfc/rfc4918](https://www.rfc-editor.org/rfc/rfc4918) + +### Secondary (MEDIUM confidence) + +- README and Docker Compose runtime contracts for local execution and service availability. + +### Tertiary (LOW confidence) + +- none + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH - no new dependencies are recommended; all proposed tooling is already pinned in-repo. +- Architecture: HIGH - recommendations align with existing Phase 12/12.1 contracts and current code seams. +- Pitfalls: HIGH - most are verified directly in current code or official provider docs, with assumptions explicitly logged. + +**Research date:** 2026-06-22 +**Valid until:** 2026-07-06 + +## RESEARCH COMPLETE diff --git a/.planning/phases/13-virtual-local-cloud-operations/13-REVIEW-FIX.md b/.planning/phases/13-virtual-local-cloud-operations/13-REVIEW-FIX.md new file mode 100644 index 0000000..51267a8 --- /dev/null +++ b/.planning/phases/13-virtual-local-cloud-operations/13-REVIEW-FIX.md @@ -0,0 +1,107 @@ +--- +phase: 13-virtual-local-cloud-operations +fixed_at: 2026-06-23T00:31:15Z +review_path: .planning/phases/13-virtual-local-cloud-operations/13-REVIEW.md +iteration: 1 +findings_in_scope: 7 +fixed: 7 +skipped: 0 +status: all_fixed +--- + +# Phase 13: Code Review Fix Report + +**Fixed at:** 2026-06-23T00:31:15Z +**Source review:** `.planning/phases/13-virtual-local-cloud-operations/13-REVIEW.md` +**Iteration:** 1 + +**Summary:** +- Findings in scope: 7 (CR-01 through CR-06, WR-06) +- Fixed: 7 +- Skipped: 0 + +--- + +## Fixed Issues + +### CR-06: preview_cloud_file swallows HTTPException + +**Files modified:** `backend/api/cloud/operations.py` +**Commit:** ebc74f4 +**Applied fix:** Changed `except HTTPException: return _unsupported_preview_response(...)` to `except HTTPException: raise` so 401 (credential failure) and 404 (ownership race) are re-raised rather than masked as a 200 unsupported-preview response. The `except Exception` fallthrough for genuine provider errors is unchanged. + +--- + +### CR-01: Content-Disposition header injection via unescaped filename + +**Files modified:** `backend/api/cloud/operations.py` +**Commit:** d4b2697 +**Applied fix:** Added `import urllib.parse` at the top of the module and replaced `filename.replace('"', "'")` + bare `filename=` header with `urllib.parse.quote(filename, safe=...)` producing an RFC 6266 `filename*=UTF-8''` header. This prevents injection of newlines, semicolons, and other HTTP header-special characters that the previous single-quote substitution did not cover. + +--- + +### CR-02: Path traversal in WebDAV upload_file and rename + +**Files modified:** `backend/storage/webdav_backend.py` +**Commit:** a1d1c3b +**Applied fix:** +- Added `PurePosixPath` to the existing `from pathlib import Path` import. +- In `upload_file`: applied `PurePosixPath(filename).name` before constructing `object_path`, with a fallback to `"upload"` for empty or dot-only results. Updated the returned `"name"` field to use the sanitized name. +- In `rename`: applied the same `PurePosixPath(new_name).name` guard before computing `new_path`, with a fallback to the original `new_name` (caller-validated). Updated the returned `"name"` field to use the sanitized name. + +--- + +### CR-03: Audit log/DB failure after provider upload leaves orphaned upload + +**Files modified:** `backend/api/cloud/operations.py` +**Commit:** 405c7a6 +**Applied fix:** Wrapped the entire post-upload DB block (`upsert_cloud_item` + `update_folder_state` + `write_audit_log` + `session.commit()`) in a `try/except Exception` block. On failure, the session is rolled back and a `JSONResponse(207)` with `kind: "provider_success_db_error"` is returned, documenting that the file is on the provider but DocuVault has no metadata record. This makes the failure observable and actionable rather than an unhandled 500. + +Note: this finding is classified as a logic/correctness concern — requires human verification that the 207 response is appropriate for the frontend conflict-action flow. + +--- + +### CR-04: Successful cloud delete does not soft-delete the CloudItem row + +**Files modified:** `backend/api/cloud/operations.py` +**Commit:** af0de30 +**Applied fix:** After `kind == MUT_KIND_DELETED`, added a SQLAlchemy `sa_update(CloudItem).where(...).values(deleted_at=datetime.now(timezone.utc))` targeting `connection_id + provider_item_id + user_id + deleted_at.is_(None)`. This runs before `update_folder_state` and `write_audit_log` in the same transaction, so the soft-delete is committed atomically with the audit row and folder-state invalidation. Queries filtering on `deleted_at.is_(None)` (upload conflict check, preview, download) will no longer see the deleted file. + +Note: this is a logic/correctness fix — requires human verification that the soft-delete target columns match the CloudItem model. + +--- + +### CR-05: reconnect_connection audit log never committed + +**Files modified:** `backend/api/cloud/connections.py` +**Commit:** 60df855 +**Applied fix:** Added `await session.commit()` immediately after `write_audit_log(...)` in the reconnect endpoint, with an explanatory comment. The service's earlier `session.commit()` (persisting credentials) is a separate unit of work; this commit persists the audit row that was written to the session after that earlier commit. + +--- + +### WR-06: testConnection reads wrong field name (state vs status) + +**Files modified:** `frontend/src/stores/cloudConnections.js` +**Commit:** b1a9f43 +**Applied fix:** Changed `result?.state ?? 'unknown'` to `result?.status ?? 'unknown'` in `testConnection`. The server's health/test endpoints return `{ status: ... }` — the internal store vocabulary uses `state` but the translation must happen at the store boundary. Added a comment explaining the naming mismatch. + +--- + +## Test Results + +**Backend cloud tests** (test_cloud_mutations, test_cloud_reconnect, test_cloud_backends, test_cloud_audit): +183 passed, 3 xfailed, 4 warnings — 0 failures. + +**Frontend store tests** (cloudConnections.test.js): +31 passed — 0 failures. + +**Full backend suite** (excluding pre-existing `test_extract_docx` missing-module failure): +766 passed, 18 skipped, 4 deselected, 10 xfailed, 65 warnings — 0 failures. + +The `test_extract_docx` failure is pre-existing (missing `python-docx` module in the local environment) and was failing on the main branch before any of these fixes were applied. + +--- + +_Fixed: 2026-06-23_ +_Fixer: Claude (gsd-code-fixer)_ +_Iteration: 1_