- 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
- 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
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>
Move endpoint (D-07, D-08, D-09, T-13-27, T-13-28, T-13-29):
- Descendant destination detection via cloud_items ancestor-chain walk (D-09)
- Stale provider result marks source folder non-fresh, returns typed stale body (D-07)
- Success: upsert_cloud_item with new parent_ref before returning
- Success: update_folder_state for both source and destination folders
- Success: write_audit_log 'cloud.item_moved' with metadata-only payload
Delete endpoint (D-10, D-11, T-13-29):
- Pre-fetch item kind for D-10 folder-vs-file disclosure
- Success: update_folder_state for parent folder before returning
- Success: write_audit_log 'cloud.item_deleted' with delete_kind metadata
- Response carries is_folder/item_kind for frontend disclosure
- Failed deletes never emit audit rows
Promote xfail markers in test_cloud_audit.py for move and delete audit rows
- create_cloud_folder: bounded collision retry (up to 5 attempts) with keep_both_name counter suffix (D-05, D-06)
- create_cloud_folder: stale precondition marks parent folder non-fresh and returns typed stale result (D-07)
- rename_cloud_item: stale precondition marks parent folder non-fresh via folder state update (D-07)
- create_cloud_folder: reconcile-before-return upserts new folder in cloud_items and invalidates parent (T-13-26)
- rename_cloud_item: reconcile-before-return upserts renamed item and invalidates parent folder (T-13-26)
- Import upsert_cloud_item, update_folder_state, keep_both_name, CloudResource from service modules
- Write cloud.file_uploaded audit row in same transaction as reconciliation
- Audit payload is metadata-only: filename, size_bytes, connection_id, provider_item_id, parent_ref
- No provider URLs, tokens, bytes, or document text in audit metadata (T-13-02)
- Non-success paths (conflict, offline, reauth_required) bypass audit write (T-13-21)
- Import get_client_ip from deps.utils and write_audit_log from services.audit
- Upsert uploaded item into cloud_items for stable row identity and navigation
- Invalidate parent folder state (warning/upload_mutated) so next browse re-fetches
- Reconcile-before-return: session committed before response returned
- Failed/conflict uploads bypass reconciliation path (T-13-19)
- connections.py: Update Google Drive OAuth to broader `drive` scope (D-17)
instead of `drive.file` so authorized users can operate on all existing Drive
items, not just files created by this app. Both initiation and callback flows
updated to keep scopes consistent.
- schemas.py: Add Phase 13 typed response schemas — ConnectionHealthOut, ReconnectOut,
ContentResultOut, MutationResultOut, and request schemas CreateFolderRequest,
RenameItemRequest, MoveItemRequest. All credential-free (T-13-14, CONN-03).
- api/cloud.js: Add centralized frontend helpers for getConnectionHealth,
reconnectCloudConnection, testCloudConnection (D-12, D-13), plus authorized
cloud content and mutation helpers: openCloudFile, previewCloudFile,
createCloudFolder, renameCloudItem, moveCloudItem, deleteCloudItem, uploadCloudFile.
All reconnect/health/test tests pass (14/14). Security suite passes (19/19).