Files
2026-06-18 22:30:07 +02:00

13 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
12 02 execute 2
12-01
backend/storage/cloud_backend_factory.py
backend/storage/google_drive_backend.py
backend/storage/onedrive_backend.py
backend/storage/nextcloud_backend.py
backend/storage/webdav_backend.py
backend/api/cloud/__init__.py
backend/api/cloud/connections.py
backend/api/cloud/browse.py
backend/api/cloud/schemas.py
backend/api/cloud.py
backend/tasks/cloud_tasks.py
backend/celery_app.py
backend/tests/test_cloud_backends.py
backend/tests/test_cloud.py
backend/tests/test_cloud_items.py
backend/main.py
frontend/package.json
frontend/package-lock.json
AGENTS.md
README.md
true
CONN-04
CLOUD-01
CLOUD-08
CACHE-01
CACHE-02
SYNC-01
truths artifacts key_links
D-01/D-02/D-03: API lists distinct connection roots and browses by owned connection UUID, including duplicate provider types
D-08: capability reasons come from provider metadata, granted permissions, and safe read-only checks only
D-11/D-12: previously indexed folders return durable rows promptly and refresh in the background
D-13/D-14: successful refresh reconciles silently; failures retain cached rows, warning state, last-success time, and bounded retries
D-18/CACHE-01: browse and refresh never download file bytes, write MinIO, or mutate quota
path provides contains
backend/api/cloud/browse.py Owner-scoped connection-ID browse response and refresh scheduling connection_id
path provides contains
backend/tasks/cloud_tasks.py Idempotent cloud metadata refresh with bounded retries refresh_cloud_folder
path provides contains
backend/api/cloud/schemas.py Whitelisted capability, item, freshness, and connection schemas CloudBrowseResponse
path provides contains
backend/tests/test_cloud.py Multiple account, IDOR/admin, credential exclusion, and no-byte browse tests test_browse_connection_rejects_foreign_owner
from to via pattern
backend/api/cloud/browse.py backend/services/cloud_items.py owned connection resolution and durable cached listing list_cloud_children
from to via pattern
backend/tasks/cloud_tasks.py backend/storage/cloud_backend_factory.py connection provider selects CloudResourceAdapter after ownership lookup build_cloud_resource_adapter
Normalize all four providers into the Phase 12 resource contract and expose durable, owner-scoped, connection-ID browsing with safe stale-while-revalidate behavior. Decompose the cloud router without changing its public connection-management behavior.

Scope fence: capability reporting is active, but create/rename/move/delete/open/preview implementations remain Phase 13; file byte hydration and cache accounting remain Phase 14.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/phases/12-cloud-resource-foundation/12-01-SUMMARY.md @.planning/phases/12-cloud-resource-foundation/12-CONTEXT.md @.planning/phases/12-cloud-resource-foundation/12-RESEARCH.md @.planning/phases/12-cloud-resource-foundation/12-PATTERNS.md @.planning/phases/12-cloud-resource-foundation/12-VALIDATION.md Task 1: Normalize provider listings and capability evidence backend/storage/cloud_backend_factory.py, backend/storage/google_drive_backend.py, backend/storage/onedrive_backend.py, backend/storage/nextcloud_backend.py, backend/storage/webdav_backend.py, backend/tests/test_cloud_backends.py - backend/storage/cloud_base.py — contract created by Plan 12-01 - backend/storage/cloud_backend_factory.py — existing lazy provider construction - backend/api/cloud.py — current inline Google/OneDrive listing implementations to relocate - backend/storage/nextcloud_backend.py — current N+1 listing implementation and SSRF guard - backend/tests/test_cloud_backends.py — provider contract test conventions Make each provider expose `CloudResourceAdapter` behavior and normalize all pages into `CloudListing`. Google requests explicit files fields for id, parents, name, mimeType, size, modifiedTime, version/etag-equivalent fields, and item capabilities; follows `nextPageToken`; represents native Google documents with nullable/zero size without treating them as folders; and reports `insufficient_scope` when current `drive.file` credentials cannot browse intended content. OneDrive follows `@odata.nextLink` and normalizes id, drive/parent reference, file/folder facet, size, modified time, eTag/cTag, and effective permission state. Nextcloud/WebDAV use SSRF-validated non-mutating PROPFIND/OPTIONS metadata, normalize getetag/getlastmodified/getcontentlength/getcontenttype/resourcetype, and treat absent properties as unknown rather than unsupported. Factory adds `build_cloud_resource_adapter`. Fixture tests assert equivalent normalized results, complete pagination, safe capability evidence, and zero byte-download/mutation calls. - all supported providers return the same normalized resource and capability schema - Google and OneDrive pagination is followed before `complete=true` - every WebDAV/Nextcloud outbound request retains `validate_cloud_url()` protection - provider tests verify list/capability flows never call get_object/put/delete/move/rename - `pytest -q tests/test_cloud_backends.py` exits 0 cd backend && pytest -q tests/test_cloud_backends.py All four providers pass the normalized metadata/capability and complete-pagination contract without byte or mutation calls. Task 2: Decompose cloud API and add connection-ID browse schemas backend/api/cloud/__init__.py, backend/api/cloud/connections.py, backend/api/cloud/browse.py, backend/api/cloud/schemas.py, backend/api/cloud.py, backend/main.py, backend/tests/test_cloud.py - backend/api/cloud.py — preserve all connection/OAuth/WebDAV/default-storage behavior while splitting - backend/api/documents/__init__.py — no-prefix sub-router aggregator pattern and FastAPI empty-path warning - backend/api/schemas.py — explicit response whitelist pattern - backend/deps/utils.py — canonical `parse_uuid` and `get_client_ip` - backend/services/cloud_items.py — owner-scoped service API Convert `api/cloud.py` into an `api/cloud/` package with one parent router owning `/api/cloud`; sub-routers carry no prefix. Preserve existing endpoint URLs as compatibility routes where unambiguous, but introduce canonical `GET /api/cloud/connections/{connection_id}/items` with optional encoded parent reference and owner-scoped `PATCH /api/cloud/connections/{connection_id}` accepting only a validated nonblank `display_name` field. Parse UUIDs with `deps.utils.parse_uuid`, resolve the connection by current owner, and return explicit schemas for connection root, items, merged capabilities, freshness state, and last refresh timestamp. List connections keeps every same-provider account and never returns credentials. For an indexed folder, return durable rows and set/schedule refresh; for an empty first visit, perform one bounded metadata fetch so the user receives content. Map service/provider errors to controlled router errors. Admin remains blocked by `get_regular_user`. Add tests for renaming one connection without affecting another, duplicate providers, foreign connection/item IDs, admin access, credentials/token/password exclusion, malformed UUID, mass-assignment rejection, and compatibility endpoint behavior. - cloud package aggregator is the only router with `/api/cloud` prefix - canonical browse endpoint uses connection UUID and cannot select by provider alone - response schemas cannot serialize `credentials_enc` or decrypted credential fields - two active Google Drive connections for one user are independently browsable and independently renameable - focused cloud API tests pass cd backend && pytest -q tests/test_cloud.py -k "connection or browse or credential or admin" Canonical connection-ID browse and display-name APIs preserve existing connection flows and pass ownership/credential negatives. Task 3: Add durable metadata refresh task, retry behavior, and plan documentation backend/tasks/cloud_tasks.py, backend/celery_app.py, backend/tests/test_cloud_items.py, backend/tests/test_cloud.py, backend/main.py, frontend/package.json, frontend/package-lock.json, AGENTS.md, README.md - backend/tasks/document_tasks.py — sync Celery entry point bridging async service with bounded retry - backend/celery_app.py — JSON-only task registration and queue routing - backend/services/cloud_items.py — idempotent reconciliation/folder-state functions - AGENTS.md — documentation, version, testing, and security protocol - README.md — cloud feature and API documentation sections Add `refresh_cloud_folder(user_id, connection_id, parent_ref)` as a JSON-only Celery task on the documents queue. The task opens its own async DB session, revalidates owner/connection, decrypts credentials inside the worker (never broker payload), marks refreshing, obtains a complete normalized listing, reconciles idempotently, and marks fresh. Retry only transient provider/network failures using bounded increasing countdowns with jitter or the established explicit retry harness; terminal auth/scope errors become warning state with safe reason/remedy. Update task registration. Add tests for idempotency, retry countdown bounds, failed refresh retaining rows/last-success time, and no provider byte calls/quota changes. Apply project protocol for this user-visible/API plan: bump backend/frontend/package-lock patch version from 0.1.4 to 0.1.5, update AGENTS current state/shared module map if new canonical modules were added, and document the connection-ID browse/capability foundation in README. - task payload contains IDs/references only and no credential or provider byte content - transient retries are bounded; auth/scope failures do not retry forever - warning state preserves cached rows and last successful refresh timestamp - browse/refresh tests assert no `get_object`, MinIO put, or quota update - versions are exactly 0.1.5 in backend main, package.json, and package-lock root/package entry - focused backend tests pass cd backend && pytest -q tests/test_cloud.py tests/test_cloud_backends.py tests/test_cloud_items.py && cd ../frontend && npm run build Durable refresh retries safely, retains cached metadata on failure, performs no byte/quota work, and plan documentation/version 0.1.5 is complete.

<threat_model>

Threat ID Threat Mitigation and evidence
T-12-01 Connection/item IDOR get_regular_user plus owner-scoped connection UUID lookup; foreign/admin negative tests
T-12-03 Credential disclosure Explicit schemas and broker payload tests exclude credentials/tokens/passwords
T-12-04 WebDAV SSRF Preserve URL validation before every request and redirect; regression tests
T-12-05 Cache collision Connection UUID replaces provider in route/task/durable identity
T-12-07 Data loss from partial pagination complete=true only after all pages; reconciliation test
T-12-08 Provider error XSS/leak Stable reason codes and controlled messages; no raw credential-bearing exception response
T-12-09 Byte/quota side effects Negative mocks assert metadata browse never invokes byte or quota APIs
</threat_model>
1. `cd backend && pytest -q tests/test_cloud.py tests/test_cloud_backends.py tests/test_cloud_items.py` 2. `cd backend && pytest -v` 3. `cd frontend && npm run build` 4. Inspect API response fixtures for zero credential fields. 5. Confirm Phase 13 mutation and Phase 14 byte-cache methods are absent.

<success_criteria>

  • Every provider supplies normalized metadata and capabilities.
  • Browsing is keyed by owned connection UUID and supports duplicate provider accounts.
  • Durable cached rows return without provider-byte downloads and refresh safely in background.
  • Failure states preserve navigation, retry boundedly, and expose safe actionable reasons. </success_criteria>

Create .planning/phases/12-cloud-resource-foundation/12-02-SUMMARY.md when complete.