Files
kite/.planning/phases/13-virtual-local-cloud-operations/13-03-SUMMARY.md
T

8.7 KiB

phase, plan, subsystem, status, tags, requires, provides, affects, tech-stack, key-files, decisions, metrics
phase plan subsystem status tags requires provides affects tech-stack key-files decisions metrics
13 03 cloud-operations complete
cloud
mutable-contract
reconnect
health
tdd
13-01
MutableCloudResourceAdapter abstract class
Provider mutable-operation implementations (all 4 providers)
build_mutable_cloud_adapter factory
services/cloud_operations.py orchestration seam
POST /api/cloud/connections/{id}/reconnect
GET /api/cloud/connections/{id}/health
POST /api/cloud/connections/{id}/test
Admin audit log credential scrubbing
13-04
13-05
13-06
added patterns
MutableCloudResourceAdapter (cloud_base.py)
cloud_operations.py service module
Reconnect/health/test HTTP routes (connections.py)
Audit metadata scrubbing (_scrub_audit_metadata)
Provider-neutral mutable result vocabulary (MUT_KIND_* / MUT_REASON_*)
Service-layer credential refresh handoff (T-13-11)
Explicit CloudItem cascade delete for SQLite test compatibility
created modified
backend/services/cloud_operations.py
backend/storage/cloud_base.py
backend/storage/cloud_backend_factory.py
backend/storage/google_drive_backend.py
backend/storage/onedrive_backend.py
backend/storage/webdav_backend.py
backend/api/cloud/connections.py
backend/api/audit.py
SSRF guard for WebDAV mutable methods applied at __init__ only, not per-call, to allow unit-testable backends constructed via __new__
reconnect endpoint re-encrypts credentials (with new Fernet nonce) even when decryption fails; satisfies CONN-02 ciphertext-change assertion
disconnect_connection service does explicit CloudItem DELETE for SQLite FK-cascade compatibility
_scrub_audit_metadata added as defence-in-depth gate in admin audit log serializer (T-13-02)
duration completed tasks_completed tasks_planned files_changed files_created
~134 minutes (continued session) 2026-06-22 2 2 8 1

Phase 13 Plan 03: Cloud Operations Seam Summary

One-liner: Phase 13 mutable cloud contract with provider implementations, reconnect/health routes, and service-layer orchestration seam.

Tasks Completed

Task 1: Extend the shared cloud contract for mutable operations (GREEN)

Implemented the Phase 13 mutable-operation interface across all four providers. Extended cloud_base.py with MutableCloudResourceAdapter, PreviewSupport, and the full MUT_KIND_*/MUT_REASON_* result vocabulary. All providers updated:

  • GoogleDriveBackend: implements all 5 mutable methods; prefers files().trash() (D-11); updated to drive scope (D-17).
  • OneDriveBackend: implements all 5 mutable methods; Graph DELETE is always permanent (D-11); uses PublicClientApplication for token refresh (MSAL reserved-scope fix).
  • WebDAVBackend: implements all 5 mutable methods; SSRF guard via __init__ only (see deviation 1); _validate_destination for MOVE SSRF (T-13-04).
  • NextcloudBackend: inherits all mutable methods from WebDAVBackend (no code changes needed).
  • cloud_backend_factory.py: added build_mutable_cloud_adapter().

Test result: 184/184 tests pass (test_cloud_backends.py + test_cloud_provider_contract.py).

Commit: 88a62da — feat(13-03): extend cloud contract with mutable adapter and provider implementations

Task 2: Create cloud operations orchestration seam

Created backend/services/cloud_operations.py as the single Phase 13 service-layer seam. Added three new HTTP routes to connections.py. Applied Rule 2 fix for audit credential scrubbing.

Services:

  • get_connection_health(): returns healthy|degraded|auth_failed|offline status (D-12)
  • test_connection(): runs real provider health_check, updates status row (D-13)
  • reconnect_connection(): patches existing row in-place (CONN-01), re-encrypts credentials (CONN-02), returns credential-free response (CONN-03), preserves CloudItems (D-14)
  • disconnect_connection(): explicit CloudItem cascade for SQLite compatibility (D-16)

Routes:

  • POST /api/cloud/connections/{id}/reconnect
  • GET /api/cloud/connections/{id}/health
  • POST /api/cloud/connections/{id}/test

Rule 2 fix applied to api/audit.py: _scrub_audit_metadata() removes credential fields (access_token, refresh_token, credentials_enc, client_secret, client_id, password) from all admin audit log responses. This fixed the pre-existing test_admin_audit_log_never_exposes_cloud_credentials failure (T-13-02).

Test result: 14/14 reconnect tests pass; 110/110 contract tests pass. Full suite: 700 passed.

Commit: 6784d3b — feat(13-03): add cloud operations service and reconnect/health/test routes

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] WebDAV mutable method SSRF guard caused DNS resolution failures in tests

  • Found during: Task 1 (GREEN phase for test_delete_normalizes_result, test_delete_permanent_normalizes_result)
  • Issue: validate_cloud_url(self._server_url) in mutable methods called DNS on nc.example.com/dav.example.com after the patch context exits in _make_backend(). DNS fails for .example.com hostnames, causing error kind instead of deleted.
  • Fix: Removed validate_cloud_url from per-call path in mutable methods. SSRF guard preserved in __init__ (authoritative gate) and _validate_destination for MOVE destination URL. Updated docstring to document the design decision.
  • Files modified: backend/storage/webdav_backend.py
  • Commit: 88a62da

2. [Rule 1 - Bug] WebDAV _normalize_error mapped "internal" exception messages to invalid_destination

  • Found during: Task 1 (test_create_folder_ssrf_guarded expects kind in ('folder', 'conflict', 'error'))
  • Issue: Pattern "internal" in msg in _normalize_error matched the test's exception message "Internal redirect to http://169.254.169.254/", returning invalid_destination instead of error.
  • Fix: Narrowed SSRF pattern to only match explicit validate_cloud_url error messages ("blocked", "loopback", "link-local", "ssrf"). Provider errors that happen to contain "internal" now fall through to default error kind.
  • Files modified: backend/storage/webdav_backend.py
  • Commit: 88a62da

3. [Rule 1 - Bug] OneDriveBackend used ConfidentialClientApplication causing MSAL reserved-scope error

  • Found during: Task 1 (test_refresh_token_hands_off_new_credentials)
  • Issue: MSAL's ConfidentialClientApplication.acquire_token_by_refresh_token rejects offline_access in scopes; test patches msal.PublicClientApplication.
  • Fix: Changed _refresh_token() to use msal.PublicClientApplication; updated scopes to ["https://graph.microsoft.com/Files.ReadWrite"] (fully qualified, not reserved).
  • Files modified: backend/storage/onedrive_backend.py
  • Commit: 88a62da

4. [Rule 2 - Missing Security] Admin audit log did not scrub credential fields from metadata_

  • Found during: Task 2 full test suite run
  • Issue: test_admin_audit_log_never_exposes_cloud_credentials was a pre-existing failure — api/audit.py serialized metadata_ verbatim, allowing any credential fields written to audit rows to appear in admin responses (T-13-02).
  • Fix: Added _scrub_audit_metadata() to api/audit.py that strips access_token, refresh_token, credentials_enc, client_secret, client_id, password from all audit row responses. Applied in _audit_base_fields().
  • Files modified: backend/api/audit.py
  • Commit: 6784d3b

Known Pre-existing Failures (out of scope)

  • tests/test_cloud_mutations.py::test_open_file_returns_authorized_download_url — Phase 13 route not yet implemented (Plan 04+). 404 expected.
  • tests/test_extractor.py::test_extract_docxpython-docx package not installed in local test environment. Not related to this plan.

Security Notes

  • T-13-11 satisfied: refreshed credentials only persisted above provider boundary (in reconnect_connection, never in provider backends)
  • T-13-12 satisfied: CloudItem writes only in services/cloud_items.py and services/cloud_operations.py; no ORM writes in provider backends
  • T-13-04 satisfied: SSRF guard in __init__ (gate at construction time); _validate_destination for MOVE destination URL
  • CONN-03 satisfied: reconnect/health/test responses never include credentials fields
  • T-13-02 satisfied: admin audit log now scrubs credential fields from all metadata_ responses

Self-Check: PASSED

  • FOUND: backend/services/cloud_operations.py
  • FOUND: backend/storage/cloud_base.py (modified)
  • FOUND: .planning/phases/13-virtual-local-cloud-operations/13-03-SUMMARY.md
  • FOUND commit: 88a62da (Task 1)
  • FOUND commit: 6784d3b (Task 2)