8.4 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12 | 02 | cloud-browse |
|
|
|
|
|
|
Phase 12 Plan 02: Cloud Resource Foundation Browse API Summary
Connection-ID browse endpoint with all four providers implementing CloudResourceAdapter, stale-while-revalidate caching, and durable refresh_cloud_folder Celery task.
What Was Built
Task 1: Provider normalization (committed ff33439)
All four cloud providers (Google Drive, OneDrive, WebDAV, Nextcloud) now implement CloudResourceAdapter as a mixin. Each provider adds list_folder() and get_capabilities() returning normalized CloudListing and capability dicts. Factory adds build_cloud_resource_adapter(). 22 new tests in test_cloud_backends.py verify pagination, no-byte-download, and capability evidence contracts.
Task 2: api/cloud/ package decomposition (committed e186019)
Replaced flat api/cloud.py with a package:
connections.py: all connection management routes (OAuth, WebDAV, list, delete, rename)browse.py: canonicalGET /api/cloud/connections/{connection_id}/itemsschemas.py: credential-freeCloudBrowseResponse,CloudItemOut,FolderFreshnessOut__init__.py: aggregates sub-routers under/api/cloudprefix
Fixed UUID type incompatibility: test_cloud_items.py now uses UUID(as_uuid=True) matching the production ORM instead of String(36) patch. Service layer coerces all inputs to uuid.UUID objects for WHERE clauses.
Task 3: Celery task + version bump (committed c6237ca)
refresh_cloud_folderCelery task: idempotent, bounded 3-retry (30s/90s/270s ± 10s jitter), credential decryption inside worker- Stale-while-revalidate: 5-minute threshold in browse endpoint
- Version bumped 0.1.4 → 0.1.5 in backend/main.py and frontend/package.json
- AGENTS.md created/updated with Phase 12 state and new shared module map entries
- README.md updated with connection-ID browse API table
Commits
| Hash | Message |
|---|---|
ff33439 |
feat(12-02): normalize all four providers into CloudResourceAdapter contract |
e186019 |
feat(12-02): decompose api/cloud.py into package with connection-ID browse endpoint |
c6237ca |
feat(12-02): add refresh_cloud_folder Celery task, staleness trigger, version 0.1.5 |
Test Results
493 passing, 1 pre-existing failure (test_extract_docx — missing python-docx module, unrelated to this plan), 6 skipped, 7 xfailed.
New tests added:
- 22 in
test_cloud_backends.py(Task 1) - 11 in
test_cloud.py(Task 2: IDOR, admin block, credential exclusion, rename, duplicate providers, malformed UUID) - 5 in
test_cloud_items.py(Task 3: idempotency, cached-row retention, task structure, no-byte-download, scheduling)
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] UUID type incompatibility between test fixtures
- Found during: Task 2 debugging
- Issue:
test_cloud_items.pypatched UUID columns toString(36)while conftest usedUUID(as_uuid=True). Service functions could not work with both simultaneously — UUID objects caused InterfaceError on String(36) columns; strings caused AttributeError (.hex) on UUID(as_uuid=True) columns. - Fix: Updated
test_cloud_items.pyfixture to useUUID(as_uuid=True)approach (matching production ORM). Updated all helpers to useuuid.UUIDobjects. Updatedcloud_items.pyservice to always coerce touuid.UUIDobjects in WHERE clauses. - Files modified:
backend/tests/test_cloud_items.py,backend/services/cloud_items.py,backend/tests/conftest.py - Commits:
e186019,c6237ca
2. [Rule 2 - Missing functionality] display_name_override missing from ORM model
- Found during: Task 2
- Issue: Migration 0006 added
display_name_overridecolumn tocloud_connectionstable but ORM model didn't declare it, causing AttributeError when PATCH endpoint tried to set it. - Fix: Added
display_name_override: Mapped[Optional[str]] = mapped_column(Text, nullable=True)toCloudConnectionmodel. - Files modified:
backend/db/models.py - Commit:
e186019
3. [Rule 1 - Bug] Celery patch target wrong in refresh scheduling test
- Found during: Task 3 test authoring
- Issue: Test patched
api.cloud.browse.refresh_cloud_folderbut browse.py uses a local import inside the function body, so the module attribute doesn't exist at test time. - Fix: Changed patch target to
tasks.cloud_tasks.refresh_cloud_folder. - Files modified:
backend/tests/test_cloud.py - Commit:
c6237ca
4. [Rule 1 - Bug] should_refresh condition never triggered for stale cached items
- Found during: Task 3 scheduling test
- Issue: Browse endpoint's
should_refreshonly checked for no items, "refreshing" state, orlast_refreshed_at is None. Cached items withrefresh_state="fresh"and a recentlast_refreshed_atwould never schedule background refresh. - Fix: Added 5-minute staleness check — if
last_refreshed_atis older than 5 minutes, refresh is scheduled. - Files modified:
backend/api/cloud/browse.py - Commit:
c6237ca
Known Stubs
None — all data sources wired. Browse endpoint returns real durable cached rows from cloud_items table and schedules real Celery task.
Threat Flags
| Flag | File | Description |
|---|---|---|
| threat_flag: credential_in_worker | backend/tasks/cloud_tasks.py | Credentials decrypted inside Celery worker — master key must be in CLOUD_CREDS_KEY env var on all worker nodes. Never log credentials. Already mitigated by existing key-from-env pattern. |
Self-Check: PASSED
Files confirmed created:
- backend/api/cloud/__init__.py ✓
- backend/api/cloud/browse.py ✓
- backend/api/cloud/connections.py ✓
- backend/api/cloud/schemas.py ✓
- backend/tasks/cloud_tasks.py ✓
Commits confirmed:
Test result: 493 passed, 1 pre-existing failure ✓