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 <noreply@anthropic.com>
43 KiB
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.
CloudFolderViewremains 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, orProjects (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.vueremains the single file browser;CloudFolderView.vueandFileManagerView.vuestay 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, neverHTTPException; 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_listingremains the only metadata reconciliation entry point; provider backends must not updatecloud_itemsrows directly. [VERIFIED: AGENTS.md; VERIFIED:backend/services/cloud_items.py]- JWT access token stays in Pinia memory only; refresh token stays in
httpOnlyStrict 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:
# 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
flowchart TD
U["User action in shared StorageBrowser"] --> V["CloudFolderView / SettingsCloudTab<br/>(thin data providers)"]
V --> API["FastAPI cloud endpoints<br/>connection-id scoped"]
API --> S["Cloud operations service<br/>validate ownership, stale state, conflict policy"]
S --> A["Mutable cloud adapter<br/>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<br/>items, capabilities, queue, health"]
B --> U
S -. refresh after reconnect / stale mismatch .-> C["Celery refresh_cloud_folder"]
C --> A
Recommended Project Structure
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:
# 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:
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:
// 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
# 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
# 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
# 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: <target>
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)
- 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] - OAuth reconnect model — RESOLVED: Use a connection-ID reconnect intent whose OAuth state identifies and patches the existing owned
cloud_connectionsrow, preserving stable metadata identity while invalidating provider/listing/capability caches. [AGENT DISCRETION; VERIFIED: D-14 and current callback behavior] - Upload queue payload — RESOLVED: Use typed JSON conflict/error bodies with stable
kindandreasoncodes; 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] - 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
pytestis unavailable; usedocker 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.pyandcd 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 shapesbackend/tests/test_cloud_reconnect.pyor equivalent expansion intest_cloud.py— connection-ID reconnect semantics, token persistence, cache invalidation, metadata retentionbackend/tests/test_cloud_audit.pyor equivalent mutation assertions intest_cloud.py— metadata-only audit rows for each successful mutationfrontend/src/components/storage/__tests__/StorageBrowser.cloud-queue.test.js— sequential cloud upload queue, conflict pause, error pause, resume/cancel-allfrontend/src/views/__tests__/CloudFolderOpenPreview.test.js— cloud open/preview/download action behavior through shared browserfrontend/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.mdbackend/storage/cloud_base.pybackend/api/cloud/browse.pybackend/api/cloud/connections.pybackend/services/cloud_items.pybackend/services/audit.pybackend/storage/google_drive_backend.pybackend/storage/onedrive_backend.pybackend/storage/webdav_backend.pybackend/storage/nextcloud_backend.pyfrontend/src/components/storage/StorageBrowser.vuefrontend/src/views/CloudFolderView.vuefrontend/src/components/settings/SettingsCloudTab.vuebackend/tests/test_cloud.pybackend/tests/test_cloud_provider_contract.pybackend/tests/test_cloud_security.pybackend/tests/test_cloud_capabilities.pyfrontend/src/views/__tests__/CloudFolderView.test.jsfrontend/src/views/__tests__/CloudFolderRenderedFlow.test.jsfrontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.jsfrontend/src/components/settings/__tests__/SettingsCloudTab.test.js
- Google Drive API scopes: developers.google.com/workspace/drive/api/guides/api-specific-auth
- Google Drive
filesresource: 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 - Google Drive
files.delete: 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 - Google Drive
files.download: developers.google.com/workspace/drive/api/reference/rest/v3/files/download - Microsoft Graph
driveItemresource: 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
- Microsoft Graph rename/move update: 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
- Microsoft Graph delete: 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
- Nextcloud WebDAV basic ops: docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html
- RFC 4918 WebDAV: 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