diff --git a/backend/api/cloud/operations.py b/backend/api/cloud/operations.py index 3c6bfa9..5a82287 100644 --- a/backend/api/cloud/operations.py +++ b/backend/api/cloud/operations.py @@ -637,12 +637,60 @@ async def upload_cloud_file( kind = result.get("kind") if kind == MUT_KIND_UPLOADED: + # Route authoritative upload success through centralized reconciliation + # before returning (Plan 06, Task 1: T-13-19). + # upsert_cloud_item gives the new item stable row identity in cloud_items, + # so navigation can show it immediately without a full provider re-list. + # update_folder_state invalidates the parent folder so the next browse + # triggers a provider re-list and reflects the new content truthfully. + # These calls are intentionally in-request — the response is not returned + # until reconciliation completes (reconcile-before-return contract). + provider_item_id = result.get("provider_item_id") + resolved_name = result.get("name", upload_filename) + resolved_parent_ref = result.get("parent_ref", parent_ref) + resolved_size = result.get("size", len(content)) + + if provider_item_id: + from storage.cloud_base import CloudResource + from services.cloud_items import upsert_cloud_item, update_folder_state + + resource = CloudResource( + id=uuid.uuid4(), + provider_item_id=provider_item_id, + connection_id=connection_id, + user_id=current_user.id, + name=resolved_name, + kind="file", + parent_ref=resolved_parent_ref, + content_type=content_type, + size=resolved_size, + ) + # Upsert item — establishes stable row identity for navigation + await upsert_cloud_item(session, user_id=str(current_user.id), resource=resource) + + # Invalidate parent folder so next browse triggers a provider re-list. + # We do NOT call apply_listing_and_finalize — that requires a full provider + # listing which we don't have. Instead we mark the folder stale with a + # controlled reason so the browse endpoint knows to re-fetch. + invalidate_parent = resolved_parent_ref if resolved_parent_ref is not None else "" + await update_folder_state( + session, + user_id=str(current_user.id), + connection_id=str(connection_id), + parent_ref=invalidate_parent, + refresh_state="warning", + error_code="upload_mutated", + error_message="Folder contents changed by upload — re-listing required.", + ) + + await session.commit() + return { "kind": "uploaded", - "provider_item_id": result.get("provider_item_id"), - "name": result.get("name", upload_filename), - "parent_ref": result.get("parent_ref", parent_ref), - "size": result.get("size", len(content)), + "provider_item_id": provider_item_id, + "name": resolved_name, + "parent_ref": resolved_parent_ref, + "size": resolved_size, } if kind == MUT_KIND_CONFLICT: