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

217 lines
17 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"
plan: "06"
type: gap_closure
wave: 1
depends_on:
- "12-05"
files_modified:
- backend/api/cloud/connections.py
- backend/tests/test_cloud.py
- backend/tests/test_cloud_security.py
- frontend/src/api/cloud.js
- frontend/src/api/utils.js
- frontend/src/components/cloud/CloudCredentialModal.vue
- frontend/src/components/cloud/CloudProviderTreeItem.vue
- frontend/src/components/cloud/CloudFolderTreeItem.vue
- frontend/src/components/layout/AppSidebar.vue
- frontend/src/components/settings/SettingsCloudTab.vue
- frontend/src/components/storage/StorageBrowser.vue
- frontend/src/views/CloudFolderView.vue
- frontend/src/views/CloudStorageView.vue
- frontend/src/components/cloud/__tests__/CloudCredentialModal.test.js
- frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js
- frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js
- frontend/src/components/layout/__tests__/AppSidebar.test.js
- frontend/src/components/settings/__tests__/SettingsCloudTab.test.js
- frontend/src/views/__tests__/CloudFolderView.test.js
- frontend/src/views/__tests__/CloudStorageView.test.js
- backend/main.py
- frontend/package.json
- frontend/package-lock.json
- AGENTS.md
- CLAUDE.md
- README.md
- SECURITY.md
autonomous: true
requirements:
- CONN-04
- CLOUD-01
- CLOUD-08
must_haves:
truths:
- "A successful Nextcloud connection opens through its UUID and never calls the UUID browse endpoint with a provider slug"
- "A user can create, rename, edit, browse, and remove two independent connections for the same provider"
- "Cloud folder breadcrumbs read Cloud > connection display name > folder hierarchy and every non-current segment navigates correctly even when synchronization fails"
- "Only the deepest applicable cloud sidebar node is highlighted; the generic Cloud Storage link is active only on the provider overview"
- "Validation failures show a useful field/provider message rather than bare HTTP 422"
artifacts:
- path: "backend/api/cloud/connections.py"
provides: "Independent connection creation and connection-ID credential update semantics"
contains: "connection_id"
- path: "frontend/src/components/cloud/CloudProviderTreeItem.vue"
provides: "UUID-based provider root browsing and route-aware selection"
contains: "connection.id"
- path: "frontend/src/views/CloudFolderView.vue"
provides: "Cloud > connection > folder breadcrumb mapping"
contains: "connectionRoot"
- path: "frontend/src/components/settings/SettingsCloudTab.vue"
provides: "Per-provider connection lists with Add account action"
contains: "connectionsFor"
- path: "backend/tests/test_cloud.py"
provides: "Two same-provider connections and exact successful-connect-to-browse regression"
contains: "same_provider"
key_links:
- from: "frontend/src/components/cloud/CloudProviderTreeItem.vue"
to: "GET /api/cloud/connections/{connection_id}/items"
via: "connection UUID passed to API and /cloud/:connectionId route"
pattern: "getCloudFoldersByConnectionId.*connection.id"
- from: "frontend/src/components/settings/SettingsCloudTab.vue"
to: "backend/api/cloud/connections.py"
via: "POST creates; connection-ID update edits exactly one account"
pattern: "connectionsFor|connectionId"
- from: "frontend/src/views/CloudFolderView.vue"
to: "frontend/src/components/ui/BreadcrumbBar.vue"
via: "Cloud root plus connection and folder segment IDs"
pattern: "Cloud.*connectionRoot"
---
<objective>
Close the second Phase 12 UAT gap set: remove the remaining provider-keyed cloud navigation, implement genuinely independent same-provider connections, and make breadcrumb/sidebar state accurately represent the active cloud location.
The observed Nextcloud POST succeeded with HTTP 201. The subsequent sidebar navigation used `/cloud/nextcloud/root`, causing `CloudFolderView` to send `nextcloud` to the UUID endpoint and receive HTTP 422. Same-provider creation is separately blocked by backend provider upsert semantics and Settings' `connectionFor(provider)` first-match rendering.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/phases/12-cloud-resource-foundation/12-UAT.md
@.planning/phases/12-cloud-resource-foundation/12-UI-SPEC.md
@.planning/phases/12-cloud-resource-foundation/12-VALIDATION.md
@backend/api/cloud/connections.py
@backend/api/cloud/browse.py
@frontend/src/views/CloudFolderView.vue
@frontend/src/components/layout/AppSidebar.vue
@frontend/src/components/settings/SettingsCloudTab.vue
</context>
<tasks>
<task type="auto">
<name>Task 1: Make cloud connection lifecycle connection-ID native</name>
<files>backend/api/cloud/connections.py, backend/tests/test_cloud.py, backend/tests/test_cloud_security.py, frontend/src/api/cloud.js, frontend/src/api/utils.js, frontend/src/components/cloud/CloudCredentialModal.vue, frontend/src/components/settings/SettingsCloudTab.vue, frontend/src/components/settings/__tests__/SettingsCloudTab.test.js, frontend/src/components/cloud/__tests__/CloudCredentialModal.test.js</files>
<read_first>
- backend/api/cloud/connections.py — provider upsert, OAuth state, WebDAV create/config/delete paths
- backend/api/cloud/schemas.py — whitelisted credential-free output schemas
- backend/tests/test_cloud.py — existing Nextcloud create and connection-ID regressions
- backend/tests/test_cloud_security.py — owner scoping and credential-exclusion assertions
- frontend/src/components/settings/SettingsCloudTab.vue — first-match connectionFor rendering
- frontend/src/components/cloud/CloudCredentialModal.vue — create/edit currently share provider POST
- frontend/src/api/utils.js — structured 422 detail is currently discarded
</read_first>
<action>
Replace `_upsert_cloud_connection(user_id, provider)` semantics with explicit operations: creating a connection always inserts a new UUID row; editing/reconnecting credentials targets an owner-scoped connection ID and never changes another same-provider row. Add a credential-update endpoint for WebDAV/Nextcloud keyed by connection UUID; preserve the existing password when an edit omits a replacement, and validate/health-check the resulting credentials before commit. Keep OAuth creation independent as well: initiation state must distinguish a new account from a connection-ID reconnect/edit, and callback updates only the explicitly owner-scoped connection when reconnecting. Never put credentials in OAuth state, broker payloads, responses, logs, or audit metadata.
Refactor SettingsCloudTab from `connectionFor(provider)` to `connectionsFor(provider)`: render every connection as its own named row with Rename/Edit/Remove actions and retain an always-visible Add account control for each provider. CloudCredentialModal receives an existing connection ID and calls the ID update endpoint for edits; new connections call POST. Use effective custom/default names consistently and disambiguate duplicate defaults without exposing UUIDs beyond a short visual suffix where required by the UI contract.
Improve `request()` error normalization so FastAPI validation arrays become concise field messages and controlled API detail strings remain intact. This is diagnostic hardening; do not leak response internals or credentials.
</action>
<acceptance_criteria>
- two POSTs for Nextcloud by one user create two distinct connection UUIDs and retain independent encrypted credentials
- editing/reconnecting one connection changes only that connection; wrong-owner IDs return 404 and disclose nothing
- Settings shows both same-provider accounts plus Add Nextcloud, and actions address the selected UUID
- bare HTTP 422 is replaced by a controlled actionable message
- create/update/list responses exclude `credentials_enc`, passwords, tokens, and raw provider objects
</acceptance_criteria>
<verify><automated>docker compose exec -T backend pytest -q tests/test_cloud.py tests/test_cloud_security.py &amp;&amp; cd frontend &amp;&amp; npm test -- --run src/components/settings/__tests__/SettingsCloudTab.test.js src/components/cloud/__tests__/CloudCredentialModal.test.js</automated></verify>
<done>Cloud accounts are independent UUID resources across create/edit/reconnect/settings, with owner-scoped security and useful validation errors.</done>
</task>
<task type="auto">
<name>Task 2: Remove provider-keyed navigation and repair cloud location state</name>
<files>frontend/src/components/cloud/CloudProviderTreeItem.vue, frontend/src/components/cloud/CloudFolderTreeItem.vue, frontend/src/components/layout/AppSidebar.vue, frontend/src/components/storage/StorageBrowser.vue, frontend/src/views/CloudFolderView.vue, frontend/src/views/CloudStorageView.vue, frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js, frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js, frontend/src/components/layout/__tests__/AppSidebar.test.js, frontend/src/views/__tests__/CloudFolderView.test.js, frontend/src/views/__tests__/CloudStorageView.test.js</files>
<read_first>
- frontend/src/components/ui/TreeItem.vue — shared expansion and isActive contract
- frontend/src/components/ui/BreadcrumbBar.vue — root/segment emission behavior
- frontend/src/components/storage/StorageBrowser.vue — single shared browser and current root-label substitution
- frontend/src/components/cloud/CloudProviderTreeItem.vue — stale provider API/route
- frontend/src/components/cloud/CloudFolderTreeItem.vue — stale provider propagation
- frontend/src/views/CloudFolderView.vue — current connection lookup and folder-only breadcrumb
- frontend/src/components/layout/AppSidebar.vue — broad startsWith('/cloud') active state
</read_first>
<action>
Make every sidebar cloud request and route connection-ID based. CloudProviderTreeItem passes `connection.id` to `getCloudFoldersByConnectionId`, routes to `/cloud/{connection.id}/root`, and passes the same ID through nested CloudFolderTreeItem instances. Nested folders browse and route by connection ID and provider item/path reference; remove all active frontend use of the deprecated provider-keyed folder API.
Build the cloud breadcrumb as `Cloud > {effective connection name} > {provider folder segments}`. The Cloud root emits navigation to `/cloud`; the connection segment emits navigation to `/cloud/{id}/root`; folder segments navigate within that connection. Preserve the breadcrumb when browse refresh fails so users can always escape a stale/error view. Do this through StorageBrowser/BreadcrumbBar props and emitted events without creating a parallel breadcrumb component or mutating child props.
Make selection route-aware using TreeItem's existing `isActive` prop: generic Cloud Storage is active only when route path is exactly `/cloud`; the connection node is active at its root and while no deeper loaded node is selected; the deepest matching folder node is active for its provider reference. Use route params, not display labels or provider slugs. Keep connection nodes distinct for duplicate providers.
</action>
<acceptance_criteria>
- no frontend cloud component calls `getCloudFolders(provider, ...)` or constructs `/cloud/{provider}`
- the exact sequence POST Nextcloud 201 → click sidebar connection calls the UUID endpoint and never returns UUID-validation 422
- failed refresh still shows a clickable Cloud crumb leading to `/cloud`
- root and nested views show Cloud > custom/default connection name > folders
- generic, connection, and folder sidebar highlighting are mutually consistent with the current route
</acceptance_criteria>
<verify><automated>cd frontend &amp;&amp; npm test -- --run src/components/cloud/__tests__/CloudProviderTreeItem.test.js src/components/cloud/__tests__/CloudFolderTreeItem.test.js src/components/layout/__tests__/AppSidebar.test.js src/views/__tests__/CloudFolderView.test.js src/views/__tests__/CloudStorageView.test.js &amp;&amp; ! rg "getCloudFolders\(|/cloud/\$\{[^}]*provider" src/components/cloud src/views src/components/layout</automated></verify>
<done>Cloud navigation uses connection UUIDs end-to-end, breadcrumbs expose the full hierarchy, and the sidebar highlights the actual active resource.</done>
</task>
<task type="auto">
<name>Task 3: Run cross-layer regressions, security gate, documentation, and release protocol</name>
<files>backend/main.py, frontend/package.json, frontend/package-lock.json, AGENTS.md, CLAUDE.md, README.md, SECURITY.md</files>
<read_first>
- .planning/phases/12-cloud-resource-foundation/12-UAT.md — exact five user-reported symptoms and diagnosed artifacts
- AGENTS.md — tests, documentation, version, security, commit, and push requirements
- CLAUDE.md — mirrored development-agent contract
- SECURITY.md — Phase 12 threat register and accepted risks
</read_first>
<action>
Add/complete regression coverage for all UAT failures: successful Nextcloud creation followed by UUID browse; two same-provider connections; owner-scoped credential update; credential exclusion; Cloud overview/connection/folder breadcrumb navigation during provider failure; and exact active sidebar node. Run focused backend/frontend tests, full suites, production build, Bandit, npm audit, available pip-audit, and secret checks. Resolve introduced failures and high-severity findings without skips or suppressions. Bump patch version from 0.2.1 to 0.2.2 in backend/main.py and frontend package files. Update AGENTS.md and CLAUDE.md current state and shared architecture rules, README user-facing multi-account/navigation behavior, and SECURITY.md evidence. Commit and push atomically under `fix(12-uat): repair cloud connection navigation and multi-account support`.
</action>
<acceptance_criteria>
- all focused regressions and full backend/frontend suites pass with zero new failures
- frontend production build and mandatory security gates pass
- no provider-keyed frontend browse path remains active
- versions are 0.2.2 and documentation describes true multi-account behavior
- Phase 12 UAT is ready to resume at Test 2, with Tests 46 still awaiting user verification
</acceptance_criteria>
<verify><automated>docker compose exec -T backend pytest -q &amp;&amp; cd frontend &amp;&amp; npm test -- --run &amp;&amp; npm run build &amp;&amp; npm audit --audit-level=high</automated></verify>
<done>The five reported cloud defects have regression coverage, security and full-suite gates pass, release/docs are current, and UAT can resume.</done>
</task>
</tasks>
<threat_model>
| Threat ID | Threat | Mitigation and evidence |
|-----------|--------|-------------------------|
| T-12-06-01 | IDOR while editing/browsing one of several connections | Resolve every connection ID with owner scope before decrypting, updating, browsing, or deleting; negative tests use another user's UUID |
| T-12-06-02 | Credential disclosure through multi-account list/edit/errors | Whitelisted responses only; password omission means preserve existing secret; logs/audit/errors contain no credentials |
| T-12-06-03 | OAuth state confusion updates another connection | Bind reconnect state to authenticated user plus explicit connection ID in expiring Redis state and re-check ownership at callback |
| T-12-06-04 | SSRF bypass during credential edit | Re-run canonical `validate_cloud_url` and provider health check before storing updated WebDAV/Nextcloud credentials |
| T-12-06-05 | Cross-connection metadata collision | Continue scoping durable cloud items and folder states by connection UUID; same-provider regression asserts isolation |
| T-12-06-06 | Route/provider identifier confusion | UUID route parsing plus frontend tests prove provider slugs never enter connection-ID endpoints |
| T-12-06-SC | Dependency/supply-chain risk | No new runtime dependencies; rerun Bandit, pip/npm audits, and secret scan |
</threat_model>
<verification>
1. Plan structure contains three executable tasks, explicit files, acceptance criteria, and automated checks.
2. Backend tests prove independent same-provider lifecycle and owner isolation.
3. Frontend tests prove UUID-only sidebar navigation, full breadcrumb hierarchy, and deepest-node selection.
4. A cross-layer regression captures the observed POST 201 followed by sidebar browse without 422.
5. Full tests, build, security gates, version/docs, commit, and push complete before UAT resumes.
</verification>
<success_criteria>
- The connected Nextcloud account opens from every UI entry point without a provider-slug/UUID mismatch.
- Users can manage multiple accounts from any supported provider as independent connection resources.
- Cloud breadcrumbs and sidebar selection truthfully represent overview, connection, and nested folder state.
- Failures remain navigable and actionable instead of trapping the user or displaying bare HTTP status text.
</success_criteria>
<output>Create `.planning/phases/12-cloud-resource-foundation/12-06-SUMMARY.md` when complete.</output>