- Add named route 'cloud-file-detail' at /cloud/:connectionId/item/:itemId(.*)
to frontend/src/router/index.js; placed before cloud-folder to prevent wildcard
capture; itemId is opaque (T-14.1-08)
- Add getCloudItemDetail(connectionId, itemId) to frontend/src/api/cloud.js
calling GET /api/cloud/connections/{id}/items/{encodeURIComponent(itemId)}/detail
(T-14.1-03/T-14.1-06: zero bytes, credential-free schema)
- Create frontend/src/views/CloudDetailView.vue as thin data-provider feeding
DocumentDetailSurface with CloudItem detail data + event handlers
- Wire force=true for re-analyze confirmation (D-11, Copywriting Contract modal)
- Unsupported preview shows reason + keeps Download active, no auto-download (D-14)
- Add fetchCloudItemDetail action + force param to enqueueAnalysis in store
(cloudConnections.js imports cloud.js barrel for getCloudItemDetail)
- translateAnalysisStatus remains single source; CloudDetailView calls store
translator, never defines its own
- All 8 CloudDetailParity.test.js tests now pass (route, params, Re-classify gone)
- Create frontend/src/components/storage/DocumentDetailSurface.vue as shared detail
surface for both local and cloud files (section order: Back → Header → Status →
Topics → Extracted Text → secondary controls)
- Props: title, metadataLine, source, topics, analysisStatus, extractedText,
previewState, downloadState, analysisAction
- Emits: back, preview, download, analyze, reanalyze, retry-analysis
- Single analysis action slot swaps Analyze/Re-analyze/Retry by analysisAction.kind
(no simultaneous buttons per UI-SPEC Re-Analyze contract)
- Imports formatDate, formatSize, providerColor, providerBg, providerLabel from
utils/formatters.js (no local redefinitions)
- Refactor DocumentView.vue to thin data-provider using DocumentDetailSurface
- Visible label changed from "Re-classify" to "Re-analyze" (internal classifyDocument
API call preserved per D-09 / Codex discretion)
- No rendered Re-classify remains in either file
- CloudDetailParity Re-classify regression test now passes
- Add force: bool = False to AnalysisEnqueueRequest in cloud/schemas.py (D-11)
- Extend enqueue_analysis_job to accept force param; already_current check
becomes (already_current and not force) — bypass for supported items (D-11, ANALYZE-06)
- Add retry_or_create_single_item_job service helper in cloud_analysis.py that
creates a single-item force enqueue job when no active job exists (D-12)
- Add POST /analysis/connections/{id}/items/{cloud_item_id}/retry route
in analysis.py returning AnalysisEnqueueOut with queued_count >= 1
- Wire force= through enqueue_job route handler (body.force)
- All 8 test_cloud_reanalyze_force tests pass; 26 test_cloud_analysis_contract
tests pass (no idempotency regression); 872/873 backend tests pass
- backend/tests/test_cloud_detail_parity.py: RED tests asserting GET
/api/cloud/connections/{id}/items/{item_id}/detail returns extracted_text,
analysis_status, semantic_index_status, topics, provider metadata, and
excludes credentials_enc/object_key; owner gets 200, foreign user gets 404,
admin gets 403 via get_regular_user; stale items retain prior data; detail
endpoint must not call hydrate_and_cache_bytes (D-05, D-07, D-18, T-14.1-01/02)
- backend/tests/test_cloud_reanalyze_force.py: RED tests asserting force=True
on AnalysisEnqueueRequest bypasses already_current for explicit re-analyze;
default enqueue still skips unchanged indexed items; single-item retry with
no active job creates a new one; force flag is owner-scoped and admin-blocked
(D-11, D-12, ANALYZE-05, ANALYZE-06, ANALYZE-07, T-14.1-02)
- frontend/src/views/__tests__/CloudDetailParity.test.js: RED tests asserting
cloud-file-detail named route exists in router; paired local+cloud route
parity; DocumentView does not contain Re-classify (D-09 regression); route
prerequisite for navigation (D-01, D-19)
- frontend/src/components/storage/__tests__/StorageBrowser.parity.test.js: RED
tests asserting cloud rows render TopicBadge and analysis-status indicator in
same slot as local rows; Analyze/Re-analyze/Retry action slot by state; no
Re-classify copy in cloud rows (D-06, D-08, D-09, D-10)
All four files collect cleanly. 11 backend failures and 7 frontend failures are
tied exclusively to the missing detail endpoint, force flag, route, and Re-analyze
copy — not to fixture or infrastructure errors.
Plans 01–11, CONTEXT, PATTERNS, RESEARCH, REVIEW-FIX, and updated
SUMMARY and CONTEXT for the virtual-local-cloud-operations phase.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plans 01–04, CONTEXT, PATTERNS, RESEARCH, UAT, and .gitkeep for
the fix-nextcloud-root-listing-and-sync-visibility sub-phase.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add cache limit number input with tier cap validation and usage display
- Add segmented control for simplified vs detailed analysis progress (D-06)
- Add segmented control for pause-batch vs continue-item failure behavior (D-11)
- Load cache settings on mount via loadCacheSettings; surface API errors
- Keep existing connection health/reconnect/disconnect UX intact
- All 471 frontend tests pass; no regressions
- Fix getCacheSettings/updateCacheSettings URLs to use /api/cloud/analysis/cache
and /api/cloud/analysis/cache/settings (Rule 1: wrong URLs per Plan 14-03 backend)
- Add loadCacheSettings action: hydrates tierCapBytes, cacheUsage, cacheLimit,
detailedAnalysisProgress, failureBehavior from server without clearing on error
- Add tierCapBytes, cacheUsage, cacheSettingsLoading, cacheSettingsError state
- updateCacheSettings merges full CacheStatusOut response into local state
- Add cache_backed_response_has_no_object_key describe block to CloudFolderOpenPreview.test.js
- Verifies openCloudFile response processed by CloudFolderView never exposes object_key, MinIO paths, or credentials_enc in rendered HTML or API call arguments (T-14-02)
- All 9 tests pass (8 pre-existing + 1 new)
- Add hydrate_and_cache_bytes to services/cloud_cache.py (Plan 06 cache lifecycle)
- Cache-hit path: pin existing entry, read from MinIO, release pin in finally
- Cache-miss path: fetch from provider, store in MinIO, create/reactivate entry, pin during response, release pin in finally
- CacheQuotaExceeded and MinIO failures are non-fatal (bytes returned from provider path)
- evict_lru_entries run after pin release to stay within user cache limit
- Update preview_cloud_file and download_cloud_file in operations.py to call hydrate_and_cache_bytes
- Version key computed from CloudItem metadata (no provider call required for key resolution)
- Shared _make_minio_cache_wrapper helper eliminates duplicate adapter code
- Falls through to direct provider fetch when no CloudItem row found
- Add _make_minio_cache_wrapper helper to operations.py (DRY, shared by preview and download)
- Add 5 new tests to test_cloud_cache.py: cache-miss fetches provider, cache-hit skips provider, pin released on hit, pin released on miss, user isolation
- Add 3 new security tests to test_cloud_security.py: preview response excludes object_key, download response excludes object_key, foreign-user IDOR via download blocked
- Implement services/cloud_analysis_processing.py with full pipeline:
queued -> downloading -> extracting -> classifying -> indexed
- Stale guard: version key comparison fires before provider byte fetch (T-14-13)
- Cache pin lifecycle: pin acquired before bytes used, released in finally block (T-14-12)
- No provider mutation methods called: adapter only used via get_object (T-14-03)
- No local Document row created: classification targets CloudItem directly
- Cooperative cancellation check at job/item level before each stage
- ProcessingResult dataclass with status, cache_entry_id, error_code fields
- Add 4 contract tests: status_transitions, stale_detection, pin_release, no_mutations
- 14-02-SUMMARY.md: documents migration 0007, ORM models, version-key
precedence helper, settings service, and Phase 14 cache functions
- STATE.md: advance to Plan 3 of 9, record P02 metrics and decisions
- cloud_analysis_versioning.py: compute_version_key with version>etag>fingerprint
precedence (D-19); compute_fingerprint helper; content_hash is optional post-
hydration only (D-20); raises ValueError only, no HTTPException
- cloud_analysis_settings.py: get_or_create_user_analysis_settings (upsert on
first access), update_user_analysis_settings with enum validation and tier-capped
cache limit, build_default_settings for API responses; raises ValueError only
- cloud_cache.py: Phase 14 durable cache functions added alongside existing Phase 12
in-memory TTL cache — create_cache_entry, list_cache_entries (owner-scoped),
evict_lru_entries (LRU, skips pin_count>0 and active_job_count>0), increment/
decrement_quota_for_cache (atomic UPDATE pattern); re-exports compute_version_key
from cloud_analysis_versioning so tests have one import site
Implemented write_audit_log calls for open_cloud_file, create_cloud_folder,
and rename_cloud_item in api/cloud/operations.py. Removed xfail markers from
test_cloud_audit.py. All 11 audit tests pass; 360 cloud tests pass total.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>