# Phase 12.1: Fix Nextcloud Root Listing and Sync Visibility — Validation Matrix **Status:** Plan 04 complete. Phase 12.1 closed. All gates pass, docs updated, v0.2.6 committed. **Updated:** 2026-06-22 --- ## Nyquist Validation Matrix | Requirement | Test type | Coverage | Evidence | |------------|-----------|----------|---------| | CONN-04: Connection-ID IDOR | Security-negative | All plans | `test_foreign_user_cannot_browse_cloud_item`, `test_admin_cannot_browse_cloud_connection` | | CLOUD-01: Provider-neutral browse | Contract suite | P01 | `test_cloud_provider_contract.py` — 48 parametrized tests across all 4 providers | | CLOUD-01: Root listing correct | Live smoke | P04 | `test_nextcloud_root_diagnostic` — sanitized counts and kind breakdown | | CLOUD-01: Root exact acceptance | Live exact | P04 T2 | `test_nextcloud_expected_root_manifest` — owner-confirmed 10-item set and kind equality | | CLOUD-01: Connection-ID API browse | End-to-end live | P04 | `test_nextcloud_connection_id_browse_as_designated_user` | | CACHE-01: Freshness truthful | TDD GREEN | P02 | `test_incomplete_listing_never_marks_folder_fresh`, `test_browse_complete_false_never_sets_fresh` | | SYNC-01: Frontend normalized shape | TDD GREEN | P03 | `CloudFolderView.test.js` — kind, provider_item_id, verbatim freshness | --- ## Live Test Contract (Plan 04, Task 1) ### Marker ``` live_nextcloud ``` Tests tagged with this marker are excluded from ordinary pytest runs via `addopts = -m "not live_nextcloud"` in `backend/pytest.ini`. Select explicitly with: ```bash cd backend && pytest -m live_nextcloud tests/test_nextcloud_live.py ``` ### Required environment variables (values in ignored `.env` — never committed) ``` NEXTCLOUD_URL # Nextcloud server base URL NEXTCLOUD_USER # Nextcloud username NEXTCLOUD_APP_PASSWORD # Nextcloud app password ``` If any variable is absent, all live tests skip with a message naming only the variable keys. ### Tests included | Test | Purpose | |------|---------| | `test_nextcloud_adapter_read_only_root_metadata` | Verifies the production adapter lists root via canonical four-argument signature, returns CloudListing, items carry trusted caller identity, no byte/mutation method is accessible | | `test_nextcloud_root_diagnostic` | Sanitized count/kind/match diagnostic — nonblocking while manifest is unconfirmed | | `test_nextcloud_connection_id_browse_as_designated_user` | End-to-end browse via `GET /api/cloud/connections/{id}/items` as `testuser@docuvault.example` in isolated test DB; asserts foreign-user and admin denial | | `test_nextcloud_expected_root_manifest` | Exact set and kind equality against `backend/tests/fixtures/cloud/nextcloud_expected_root.json` (owner_confirmed: true); enabled after Task 2 reconciliation | ### Read-only / no-mutation contract The network guard integrated in the test design blocks these HTTP methods before dispatch: `GET` (byte content), `PUT`, `POST`, `PATCH`, `DELETE`, `MKCOL`, `MOVE`, `COPY`. Only `PROPFIND`, `OPTIONS`, and `HEAD` are permitted. Mutation methods are asserted absent on the adapter object. No MinIO, quota, or object-storage change is made. ### Threat coverage | Threat ID | Mitigation | Test | |-----------|-----------|------| | T-12.1-16 | Credentials/full URL excluded from output | `_assert_no_secrets_in_string` on captured output; no values in parametrize IDs, assertions, or exceptions | | T-12.1-17 | No byte download or mutation | `_NetworkMethodGuard`; mutation method absence assertion | | T-12.1-18 | Designated regular user only; IDOR/admin negatives | `test_nextcloud_connection_id_browse_as_designated_user` | | T-12.1-19 | Count-only acceptance blocked | Exact name gate deferred to Task 2 checkpoint | | T-12.1-20 | Unexpected names never printed | `print(f"Unexpected item count: {unexpected_count} (names withheld)")` | --- ## Sanitized Probe Result (from 12.1-RESEARCH.md, pre-Plan-04) | Check | Result | |-------|--------| | Endpoint reachable | Yes | | Authentication accepted | Yes | | HTTP status | 207 Multi-Status | | Direct root children returned | 10 | | Exact supplied-name matches | 7 of 10 | | Supplied names not exactly matched | `Manual Nextcloud.png`, `Nextcloud Intro.mp4`, `Template credits.md` | | Additional returned-name count | 3; names withheld | | Byte download or mutation | None | --- ## Manifest Status: OWNER CONFIRMED — Task 2 Complete The project owner reconciled the three fixture discrepancies (2026-06-22). The correct actual Nextcloud names for the three previously unmatched slots are: - Corrected name 1: kind=file (prior candidate name was wrong — owner confirmed) - Corrected name 2: kind=file (prior candidate name was wrong — owner confirmed) - Corrected name 3: kind=file (prior candidate name was wrong — owner confirmed) The full manifest of 10 items is now stored in `backend/tests/fixtures/cloud/nextcloud_expected_root.json` with `owner_confirmed: true`. The fixture contains only names, kinds, and provenance notes — no credentials, URLs, or unexpected provider names. Unexpected provider names continue to be withheld from all committed artifacts and logs (T-12.1-20). `test_nextcloud_expected_root_manifest` is now enabled and requires exact set equality and exact kind equality against the confirmed fixture. --- ## Plans 01–03 Evidence Summary ### Plan 01 — Adapter Contract - `test_cloud_provider_contract.py` — 48 tests: all pass - `test_cloud_backends.py` — 67 tests: all pass - `test_webdav_backend.py` — 29 tests: all pass - `test_cloud_security.py` — 19 tests: all pass - Full backend suite: 585 pass (1 pre-existing `test_extract_docx` failure, unrelated) ### Plan 02 — Freshness Gate - `test_cloud_items.py` — 45 tests: all pass - `test_cloud.py` — 25 tests: all pass - `test_cloud_security.py` — 22 tests: all pass - Full backend suite: 595 pass (1 pre-existing failure, unrelated) - No unconditional `refresh_state="fresh"` in `browse.py` or `cloud_tasks.py` ### Plan 03 — Frontend Contract - `CloudFolderView.test.js` — 23 tests: all pass - `CloudProviderTreeItem.test.js` — 8 tests: all pass - `CloudFolderTreeItem.test.js` — 16 tests: all pass - `StorageBrowser.skeleton.test.js` — 22 tests: all pass - `cloudConnections.test.js` — 23 tests: all pass - `CloudBreadcrumbNavigation.test.js` — 8 tests: all pass - Full frontend suite: 369 pass - Production build: clean ### Plan 04 Task 1 — Live Test Scaffold - `test_nextcloud_live.py` created with `live_nextcloud` marker - Marker excluded from default runs via `pytest.ini` - Three live tests: adapter metadata, sanitized root diagnostic, connection-ID end-to-end - Task 2 (exact-name acceptance) deferred to checkpoint — fixture not yet created ### Plan 04 Task 2 — Fixture Reconciliation - Owner confirmed corrected names for 3 previously unmatched items (2026-06-22) - `backend/tests/fixtures/cloud/nextcloud_expected_root.json` created with `owner_confirmed: true` - `test_nextcloud_expected_root_manifest` enabled with exact set and kind equality (T-12.1-19) - Unexpected provider names remain withheld (T-12.1-20) ### Plan 04 Task 3 — Full Regression, Security, and Rendered-Flow Gates | Gate | Command | Result | |------|---------|--------| | Bandit HIGH findings | `bandit -r backend/ -q` | 0 HIGH (10 LOW, pre-existing) | | npm audit high/critical | `npm audit --audit-level=high` | 0 vulnerabilities | | Git tracked .env files | `git ls-files '.env' '.env.*'` | None (only .env.example) | | Secret scan new findings | `gitleaks detect --redact` | 3 findings, all pre-existing in old commits (May 2026), none from Phase 12.1 | | Docker Compose config | `docker compose config --quiet` | Clean | | Focused cloud tests | `pytest test_cloud_*.py test_webdav_backend.py` | 236 pass | | Full backend suite | `pytest -v` | 595 pass, 1 pre-existing failure (test_extract_docx, unrelated) | | Full frontend suite | `npm test` | 376 pass (7 new from CloudFolderRenderedFlow.test.js) | | Frontend build | `npm run build` | Clean | | Rendered flow test | `npm test -- --run CloudFolderRenderedFlow.test.js` | 7 pass |