--- 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