Files
kite/.planning/phases/13-virtual-local-cloud-operations/13-RESEARCH.md
T
curo1305andClaude Sonnet 4.6 b67e77dd69 docs(13): add phase 13 planning artifacts — virtual local cloud operations
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>
2026-06-23 20:34:57 +02:00

43 KiB
Raw Blame History

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 12s 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 projects 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 projects 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 repositorys 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
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 users 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 / Dont 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)

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