---
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 4–6 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>
