Files
kite/.planning/phases/12-cloud-resource-foundation/12-RESEARCH.md
T

238 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 12: Cloud Resource Foundation - Research
**Researched:** 2026-06-18
**Status:** Complete
## Research Question
What must be understood to plan a provider-neutral cloud capability contract, durable owner-scoped cloud item index, and capability-aware shared browser without copying provider-owned files into DocuVault storage?
## Executive Summary
Phase 12 should introduce a cloud-resource layer beside the existing object-storage `StorageBackend`, not expand that byte-oriented contract until it becomes a file manager. The durable identity is `CloudConnection.id`; provider type is only an adapter selector. This is required for multiple accounts from one provider and means current provider-keyed routes, cache keys, and `_get_active_connection()` queries must change.
Browsing should use stale-while-revalidate semantics backed by PostgreSQL: return normalized metadata immediately, refresh from the provider, then upsert owner/connection-scoped rows. Provider-owned bytes remain absent. Capability responses need both operation support and a reason state so `StorageBrowser.vue` can distinguish unsupported actions from temporarily blocked actions without provider-specific branches.
Two cross-phase constraints must be made explicit. First, Google currently requests the `drive.file` scope, which is limited to files created by or opened with the app and cannot satisfy whole-existing-drive discovery; Phase 13 reconnect/credential work must request and communicate appropriate broader access. Second, retained byte-cache quota accounting belongs to Phase 14, but Phase 12 schemas should not conflate metadata size with stored byte usage.
## Current-State Findings
### Connection Identity Is Provider-Keyed
- `CloudConnection` already has a UUID primary key and `display_name`, but `_upsert_cloud_connection()` and `_get_active_connection()` select by `(user_id, provider)`.
- Browse routes are `/api/cloud/folders/{provider}/{folder_id}` and frontend routes are `/cloud/:provider/:folderId`; both make a second account of the same provider ambiguous.
- `cloud_cache.py` keys listings as `{user_id}:{provider}:{folder_id}`, so same-provider accounts would collide.
- `CloudStorageView.vue` correctly iterates connection IDs, but navigates with `conn.provider` and discards the connection identity.
**Planning consequence:** migrate browse identity to connection UUID throughout API, route, cache, store, and view boundaries. Do not expose encrypted credentials or infer ownership from a client-supplied provider.
### Existing Storage Contract Is Byte-Oriented
`StorageBackend` defines put/get/delete, presigned URLs, health, and stat. It does not model folders, normalized resource metadata, rename/move, provider capabilities, or change cursors. Nextcloud adds `list_folder()` outside the abstract contract, while Google and OneDrive listing functions live directly in `api/cloud.py`.
**Planning consequence:** add a separate cloud resource adapter contract. Keep `StorageBackend` for DocuVault-owned/local object storage and existing document byte operations. A cloud adapter can later support:
- `list_folder(parent_ref)` returning normalized resources and a listing result
- `capabilities()` for connection-level support
- item-level capability overrides from provider metadata
- future `open`, `preview`, `upload`, `create_folder`, `rename`, `move`, `delete`, and change-cursor operations
Phase 12 implements browse and capability reporting; Phase 13 implements mutations.
### Listings Are Too Sparse and Ephemeral
Current Google/OneDrive/WebDAV listing dictionaries expose only `id`, `name`, `is_dir`, and `size`. The in-process TTL cache lasts 60 seconds and disappears on restart or another application instance. There is no persisted parent, MIME type, modified time, version/etag, last-seen time, or analysis/index placeholder.
**Planning consequence:** a `cloud_items` table should persist normalized metadata with a uniqueness boundary at `(connection_id, provider_item_id)` and explicit `user_id` for defense-in-depth ownership queries. Recommended fields:
- UUID `id`, UUID `user_id`, UUID `connection_id`
- provider-native `provider_item_id`, optional canonical path snapshot
- optional `parent_item_id` plus provider parent reference/path
- `name`, `item_type` (`file`/`folder`), optional `content_type`, `size_bytes`
- optional `etag`, `version`, provider `modified_at`
- `last_seen_at`, optional `deleted_at`, timestamps
- analysis/index placeholders that do not imply byte ownership (`analysis_status`, optional extracted-text/index relation or future-compatible status fields)
Use foreign keys with cascade from user/connection, indexes for owner+connection+parent browsing, and a unique connection+provider-item constraint. Never make path the stable primary identity: rename and move change paths, while provider IDs are generally more stable.
### Capability Data Has Two Levels
Provider APIs expose capability information differently:
- Google Drive `files.capabilities` includes item-level booleans such as `canRename`, `canTrash`, and `canMoveItemWithinDrive`.
- Microsoft Graph `driveItem` supplies stable IDs, parent references, eTags/cTags, facets, and permissions; some effective capability decisions require granted scopes and item context rather than one universal provider matrix.
- WebDAV capability discovery can use standards-visible methods/properties and server responses, but arbitrary servers vary. Safe discovery must use `OPTIONS`, `PROPFIND`, configured permission information, and observed non-mutating responses, never test mutations.
Model each action as structured data rather than booleans alone:
- state: `supported`, `unsupported`, or `temporarily_unavailable`
- reason code: stable machine identifier such as `provider_unsupported`, `insufficient_scope`, `read_only`, `reauth_required`, `offline`, `item_restricted`
- message: short user-facing explanation
- optional remedy code for frontend action routing in later phases
Connection-level defaults should be merged with item-level provider capabilities. Keep the action vocabulary fixed across providers: browse, open, preview, upload, create-folder, rename, move, delete, and change-tracking.
### Google OAuth Scope Is a Functional Blocker for the Milestone Vision
The current OAuth flow requests `https://www.googleapis.com/auth/drive.file`. Google documents this as access to files the app created or that users explicitly opened with the app. It is insufficient for discovering and analyzing all documents already organized in a users Drive.
**Planning consequence:** Phase 12 should expose an `insufficient_scope` capability/health reason when credentials cannot browse the intended corpus. Phase 13 owns reconnect and credential lifecycle, including a deliberate broader-scope consent flow. Do not silently widen permissions in Phase 12.
### Metadata and Byte Quota Must Stay Separate
The context locks these semantics:
- provider bytes are authoritative;
- metadata/search/index records do not consume document-storage quota;
- actual file bytes retained in DocuVault storage consume quota while present;
- transient streaming that is immediately discarded does not consume storage quota.
**Planning consequence:** Phase 12 must not create MinIO objects or increment quota during browse. A future byte-cache table/service should reference cloud items and account for actual retained bytes atomically in Phase 14. `cloud_items.size_bytes` is provider metadata, not DocuVault quota usage.
## Recommended Architecture
### Backend Modules
Keep routers thin and split the growing cloud monolith along current project conventions:
- `backend/storage/cloud_base.py`: normalized enums/dataclasses or Pydantic-neutral domain types and the abstract cloud resource adapter.
- `backend/storage/cloud_backend_factory.py`: return resource-capable provider adapters by connection/provider.
- Provider adapters: normalize metadata and capabilities close to provider-specific SDK/API responses.
- `backend/services/cloud_items.py`: owner-scoped upsert/list/reconciliation service; raises domain errors, never `HTTPException`.
- `backend/api/cloud/connections.py` and `backend/api/cloud/browse.py` or an equivalent sub-router split using the projects no-prefix sub-router rule.
- `backend/api/schemas.py` or `backend/api/cloud/schemas.py`: explicit whitelisted response models; never include `credentials_enc`.
Avoid a generic repository abstraction unless it removes repeated owner-scoped queries. The important abstraction is the provider resource contract and normalized response.
### Browse/Reconciliation Flow
1. Authenticate regular user and resolve `connection_id` with `(connection.id, connection.user_id)`.
2. Read durable child metadata for `(user_id, connection_id, parent_ref)`.
3. Return cached rows promptly with freshness fields and normalized capabilities.
4. Refresh provider metadata in a bounded request/background path.
5. Upsert observed resources by `(connection_id, provider_item_id)`; update parent/name/type/size/mtime/etag/version and `last_seen_at`.
6. Reconcile disappeared children conservatively. Phase 12 may mark listing membership stale/deleted only after a successful complete listing; never delete rows after a failed or partial provider response.
7. Preserve stable DocuVault cloud item UUIDs so the frontend can reconcile silently without losing selection/scroll.
The API should distinguish `fresh`, `refreshing`, and `warning` folder states and include `last_refreshed_at`/safe error metadata. Exact asynchronous transport (request-triggered task, Celery refresh, or immediate refresh after cached response) is planner discretion, but it must work across multiple backend instances and not rely solely on process memory.
### Frontend Contract
`StorageBrowser.vue` stays the only grid. Prefer data-driven props:
- normalized rows with stable DocuVault IDs plus provider IDs hidden from presentation
- connection root/breadcrumb data
- action capability map with state/message/remedy metadata
- folder freshness state and last-successful timestamp
- byte-availability state (`cloud_only` now; cache states become active in Phase 14)
Emit generic actions. `CloudStorageView` and `CloudFolderView` remain thin route/store data providers. Replace `mode === 'local'` action hiding with capability rendering; local mode can supply a local capability set so one rendering path serves both sources.
Disabled native HTML buttons do not receive click events, so the touch requirement needs an accessible wrapper/trigger or `aria-disabled` control that blocks execution while still accepting focus/tap for explanation. The plan must test keyboard focus and touch/click explanation behavior.
## Security Threat Model Inputs
| Threat | Phase 12 mitigation to plan |
|--------|-----------------------------|
| IDOR across cloud connections | Resolve every connection by both UUID and current user; return 404 for foreign IDs |
| IDOR across cloud items | Query item by user and connection; never trust provider ID alone |
| Credential disclosure | Explicit response schemas; negative tests for `credentials_enc`, access tokens, refresh tokens, passwords |
| SSRF regression | Preserve `validate_cloud_url()` before all WebDAV/Nextcloud outbound requests and redirects |
| Cache/data collision | Include user and connection UUID in every durable/in-memory key |
| Destructive capability probing | Only provider declarations and non-mutating calls; tests assert no mutation methods invoked |
| Partial-list data loss | Reconcile removals only after a successful complete listing/page traversal |
| Admin content access | Continue `get_regular_user`; admin-negative tests for browse and metadata endpoints |
| Quota inflation/escape | Browse writes metadata only; assert quota and MinIO remain unchanged |
| Stored XSS through filenames/messages | Vue text rendering only; no `v-html`; provider errors mapped to controlled messages |
## Provider-Specific Planning Notes
### Google Drive
- Request fields explicitly: IDs, parents, name, mime type, size, modified time, version/md5 where applicable, capabilities, and pagination token.
- Handle all pages and shared-drive flags where intended; a partial first page must not trigger removals.
- Native Google Docs may have no byte size and require export later; metadata normalization must allow nullable/zero size without treating the item as a folder.
- Scope remediation belongs to Phase 13.
### OneDrive
- Normalize `id`, `parentReference`, `file`/`folder` facets, `size`, `lastModifiedDateTime`, `eTag`, and `cTag` when present.
- Follow `@odata.nextLink`; incomplete pagination cannot be considered a complete reconciliation.
- Preserve drive identity where IDs are only meaningful with a drive/account context.
### Nextcloud/WebDAV
- Replace N+1 `list()` + `info()` where practical with a Depth-1 PROPFIND that requests needed properties in one response.
- Normalize `getetag`, `getlastmodified`, `getcontentlength`, `getcontenttype`, and `resourcetype` where available.
- Paths can change on rename/move and require careful percent encoding; retain provider path as a reference/snapshot, not global identity.
- Treat missing properties as unknown, not proof that an action is unsupported.
## Validation Architecture
### Test Layers
| Layer | What to prove | Suggested location |
|-------|---------------|--------------------|
| Domain/unit | Capability merge/state/reason behavior; provider normalization; no destructive probes | `backend/tests/test_cloud_capabilities.py` |
| Service/unit | Owner-scoped upsert, stable identity, parent move/rename metadata update, complete-vs-partial reconciliation | `backend/tests/test_cloud_items.py` |
| DB integration | Constraints/indexes/cascades and same provider item IDs isolated across connections/users using PostgreSQL | integration-marked cloud item tests |
| API integration | Connection-ID routes, multiple same-provider accounts, foreign-owner/admin 404/403, credential exclusion, cached-first response schema | `backend/tests/test_cloud.py` or split cloud API tests |
| Provider contract | Google/OneDrive/WebDAV fixtures normalize to the same resource and capability schema; pagination is complete | `backend/tests/test_cloud_backends.py` plus focused adapter tests |
| Frontend unit | Connection-ID navigation, shared browser capabilities, disabled tooltip on focus/hover/tap, warning states, breadcrumb freshness | component/store/view Vitest files |
| Regression | Existing local browser actions and cloud connect/disconnect remain intact | current backend/frontend suites |
### Required Commands
- Fast backend loop: `cd backend && pytest -q tests/test_cloud.py tests/test_cloud_backends.py tests/test_cloud_capabilities.py tests/test_cloud_items.py`
- Full backend gate: `cd backend && pytest -v`
- Frontend focused loop: `cd frontend && npm test -- --run StorageBrowser CloudFolderView CloudStorageView cloudConnections`
- Full frontend gate: `cd frontend && npm test`
- Production build: `cd frontend && npm run build`
- PostgreSQL integration gate for schema/ownership constraints using the project integration environment.
### Requirement-to-Evidence Map
| Requirement | Primary evidence |
|-------------|------------------|
| CONN-04 | Provider contract tests plus shared-browser capability state tests |
| CLOUD-01 | Connection-ID browse API and `StorageBrowser` integration tests |
| CLOUD-08 | Unsupported/warning explanation tests across pointer, keyboard, and touch behavior |
| CACHE-01 | Browse tests asserting no byte fetch, MinIO object, or quota mutation |
| CACHE-02 | Cloud item schema/service tests proving metadata/index placeholders persist independently |
| SYNC-01 | Provider normalization and durable metadata persistence tests for IDs, parent, size, mtime, etag/version |
### Nyquist Rule
Every implementation task that creates a contract, service, endpoint, or component behavior should create or update its focused tests in the same plan. Do not defer all tests to a final plan. The final integration plan may add cross-layer and negative-security tests, but it must not be the first coverage for earlier functionality.
## Planning Risks and Mitigations
1. **Schema overreach into Phase 14:** Persist metadata and analysis/index placeholders, not byte-cache implementation or analysis jobs.
2. **Provider-specific branches leaking into UI:** Normalize capabilities and items in backend/provider adapters.
3. **Same-provider account ambiguity:** Connection UUID is mandatory in route, cache, and uniqueness boundaries.
4. **Silent OAuth mismatch:** Surface insufficient scope; leave consent/reconnect implementation to Phase 13.
5. **False deletion from pagination/failure:** Track complete successful listing before marking missing children absent.
6. **SQLite-only confidence:** Run PostgreSQL integration tests for UUID/FK/index/uniqueness and ownership-sensitive queries.
7. **Tooltip inaccessible on touch:** Use focusable/tappable `aria-disabled` controls, not an inert `disabled` button.
## Recommended Plan Shape
1. **Cloud resource contract and schema:** normalized capability/item types, migrations, owner-scoped cloud item service, tests.
2. **Provider normalization and durable browsing API:** connection-ID routes, pagination/completeness, metadata reconciliation, security tests.
3. **Shared browser integration:** connection roots, breadcrumbs/freshness, capability-aware actions and accessible explanations, frontend tests.
4. **Cross-layer verification and documentation:** PostgreSQL integration, admin/owner negatives, no-byte/no-quota assertions, version/docs updates required by project protocol.
Plans 1 and frontend scaffolding can begin in parallel only if their shared response schema is locked first. Provider/API work depends on the domain schema. Final integration depends on all prior plans.
## Primary References
- Microsoft Graph driveItem: https://learn.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0
- Microsoft Graph OneDrive resource model: https://learn.microsoft.com/en-us/graph/api/resources/onedrive?view=graph-rest-1.0
- Google Drive files resource and item capabilities: https://developers.google.com/workspace/drive/api/reference/rest/v3/files
- Google Drive OAuth scopes: https://developers.google.com/workspace/drive/api/guides/api-specific-auth
- Nextcloud WebDAV API: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html
- RFC 4918 WebDAV: https://www.rfc-editor.org/rfc/rfc4918
- Celery retry behavior: https://docs.celeryq.dev/en/stable/userguide/tasks.html#retrying
## RESEARCH COMPLETE