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>
184 lines
8.3 KiB
Markdown
184 lines
8.3 KiB
Markdown
---
|
|
phase: "13"
|
|
plan: "08"
|
|
subsystem: cloud-operations
|
|
status: complete
|
|
tags:
|
|
- cloud
|
|
- create-folder
|
|
- rename
|
|
- collision-retry
|
|
- stale-guard
|
|
- reconciliation
|
|
- tdd
|
|
|
|
dependency_graph:
|
|
requires:
|
|
- "13-03 (MutableCloudResourceAdapter + mutable provider implementations)"
|
|
- "13-04 (operations.py route layer + typed result vocabulary)"
|
|
- "13-06 (upload reconcile-before-return pattern)"
|
|
provides:
|
|
- "Bounded collision retry for create-folder (D-05, D-06)"
|
|
- "Stale guard with folder state refresh for create-folder and rename (D-07)"
|
|
- "Reconcile-before-return for create-folder and rename success (T-13-26)"
|
|
- "10 new behavioral tests in test_cloud_mutations.py"
|
|
affects:
|
|
- "13-09 through 13-11 (move, delete, audit plans)"
|
|
|
|
tech_stack:
|
|
added:
|
|
- "Bounded collision retry loop in create_cloud_folder route (D-06)"
|
|
- "_CREATE_FOLDER_MAX_RETRIES = 5 constant"
|
|
- "upsert_cloud_item called from create_cloud_folder success path"
|
|
- "upsert_cloud_item called from rename_cloud_item success path"
|
|
- "update_folder_state called with warning/create_folder_mutated on create-folder success"
|
|
- "update_folder_state called with warning/rename_mutated on rename success"
|
|
- "update_folder_state called with warning/stale_listing on stale create-folder or rename"
|
|
- "keep_both_name imported from services.cloud_operations for counter suffix generation"
|
|
patterns:
|
|
- "Reconcile-before-return: upsert + folder invalidation before success response"
|
|
- "Bounded retry: up to 5 collision suffix attempts before returning conflict"
|
|
- "Stale-guard: folder state invalidation on stale result before returning typed stale body"
|
|
- "Non-success bypass: conflict/offline/reauth/stale never reach reconciliation upsert"
|
|
|
|
key_files:
|
|
modified:
|
|
- path: "backend/api/cloud/operations.py"
|
|
change: "create_cloud_folder: bounded retry + stale guard + reconcile-before-return; rename_cloud_item: stale guard + reconcile-before-return; new imports"
|
|
- path: "backend/tests/test_cloud_mutations.py"
|
|
change: "Added 10 behavioral tests (RED then GREEN) for Plan 08"
|
|
|
|
decisions:
|
|
- "Rename collision surfaced to user (they chose the name explicitly) — no auto-retry for rename"
|
|
- "Create-folder retry uses keep_both_name counter suffix: 'Projects (1)', 'Projects (2)', etc."
|
|
- "Stale guard for both operations uses update_folder_state with warning/stale_listing error_code"
|
|
- "Reconciliation pattern follows Plan 06 upload pattern: upsert + folder state + session.commit in one atomic transaction"
|
|
- "rename reconcile resolves parent_ref from existing CloudItem (if present) to invalidate correct folder"
|
|
|
|
metrics:
|
|
duration: "~20 minutes"
|
|
completed: "2026-06-22"
|
|
tasks_completed: 2
|
|
tasks_planned: 2
|
|
files_changed: 2
|
|
files_created: 0
|
|
tests_added: 10
|
|
tests_passing: 755
|
|
---
|
|
|
|
# Phase 13 Plan 08: Create-Folder and Rename Collision, Stale Guard, and Reconciliation Summary
|
|
|
|
**One-liner:** Bounded collision retry (D-05/D-06), stale-guard with folder refresh (D-07), and reconcile-before-return reconciliation (T-13-26) for create-folder and rename mutations.
|
|
|
|
## Tasks Completed
|
|
|
|
| Task | Name | Commit | Key Files |
|
|
|------|------|--------|-----------|
|
|
| 1 (RED) | Add failing collision retry, stale guard, and provider normalization tests | 9ad9946 | test_cloud_mutations.py |
|
|
| 1/2 (GREEN) | Implement bounded retry, stale guard, and reconcile-before-return | aaa63c1 | api/cloud/operations.py |
|
|
|
|
## What Was Built
|
|
|
|
### Task 1: Collision Retry, Stale Guard, Provider Normalization (GREEN)
|
|
|
|
**`create_cloud_folder` in `backend/api/cloud/operations.py`:**
|
|
|
|
Added a bounded retry loop (up to `_CREATE_FOLDER_MAX_RETRIES = 5` attempts) around the adapter call:
|
|
|
|
```python
|
|
for attempt in range(_CREATE_FOLDER_MAX_RETRIES + 1):
|
|
result = await adapter.create_folder(parent_ref=..., name=candidate_name, ...)
|
|
if kind == MUT_KIND_FOLDER:
|
|
# reconcile and return
|
|
if kind == MUT_KIND_CONFLICT and attempt < _CREATE_FOLDER_MAX_RETRIES:
|
|
candidate_name = keep_both_name(body.name, attempt + 1) # 'Projects (1)', etc.
|
|
continue
|
|
if kind == MUT_KIND_STALE:
|
|
# update_folder_state with warning/stale_listing and return stale body
|
|
break
|
|
```
|
|
|
|
- D-05/D-06: Collision auto-suffixes `Projects (1)`, `Projects (2)`, etc. via `keep_both_name`; retries up to 5 times before surfacing conflict to client
|
|
- D-07: Stale precondition calls `update_folder_state(warning, stale_listing)` before returning typed `{kind: 'stale'}`
|
|
|
|
**`rename_cloud_item` in `backend/api/cloud/operations.py`:**
|
|
|
|
- D-07: Stale result triggers `update_folder_state(warning, stale_listing)` using the existing CloudItem's `parent_ref` before returning typed `{kind: 'stale'}`
|
|
- Rename collision is surfaced directly (user chose the name — no auto-retry)
|
|
|
|
### Task 2: Reconcile Create-Folder and Rename Success (GREEN)
|
|
|
|
Both `create_cloud_folder` and `rename_cloud_item` now follow the reconcile-before-return pattern established by Plan 06 (upload):
|
|
|
|
**create_cloud_folder success path:**
|
|
```python
|
|
resource = CloudResource(provider_item_id=..., kind="folder", ...)
|
|
await upsert_cloud_item(session, user_id=..., resource=resource)
|
|
await update_folder_state(session, ..., refresh_state="warning", error_code="create_folder_mutated")
|
|
await session.commit()
|
|
```
|
|
|
|
**rename_cloud_item success path:**
|
|
```python
|
|
# Resolve existing item for parent_ref, kind, content_type, size
|
|
existing_item = await session.execute(select(CloudItem).where(...))
|
|
resource = CloudResource(provider_item_id=..., name=resolved_name, ...)
|
|
await upsert_cloud_item(session, user_id=..., resource=resource)
|
|
await update_folder_state(session, ..., refresh_state="warning", error_code="rename_mutated")
|
|
await session.commit()
|
|
```
|
|
|
|
Key design decisions:
|
|
- `upsert_cloud_item` uses `provider_item_id` as stable identity key — existing rows are updated in place, preserving the DocuVault UUID
|
|
- `update_folder_state` invalidates the parent folder (not the item itself) so the next browse triggers a provider re-list
|
|
- Non-success paths (conflict, offline, reauth, stale) never reach the upsert/folder-state calls — preserving T-13-26 (no phantom metadata mutations on failure)
|
|
|
|
## Deviations from Plan
|
|
|
|
### Auto-fixed Issues
|
|
|
|
None. The plan was executed exactly as written.
|
|
|
|
**Scope note:** The plan listed `backend/services/cloud_operations.py` and `backend/services/cloud_items.py` as files modified. In practice, neither was modified — the existing `upsert_cloud_item`, `update_folder_state`, and `keep_both_name` helpers were sufficient. The route layer in `operations.py` consumed them directly via imports, consistent with the CLAUDE.md shared module map.
|
|
|
|
## Test Results
|
|
|
|
```
|
|
755 passed, 17 skipped, 4 deselected, 12 xfailed
|
|
```
|
|
|
|
- `test_cloud_mutations.py`: 10 new tests, all pass
|
|
- `test_cloud_backends.py`: unchanged, all pass
|
|
- `test_cloud_provider_contract.py`: unchanged, all pass
|
|
- Full backend suite: no regressions
|
|
|
|
## Known Stubs
|
|
|
|
None. Create-folder and rename reconciliation are fully functional. The reconcile-before-return pattern is complete for create-folder and rename (analogous to Plan 06 for upload).
|
|
|
|
## Threat Flags
|
|
|
|
No new security surfaces introduced. T-13-24, T-13-25, T-13-26 are mitigated:
|
|
|
|
- **T-13-24 (collision handling):** Bounded retry loop enforces D-06; tests verify retry behavior and collision exhaustion.
|
|
- **T-13-25 (stale mutation safety):** Stale guards stop mutations, refresh affected folder state, require retry — never forcing a stale create-folder or rename.
|
|
- **T-13-26 (identity reconciliation):** Successful create-folder and rename route through `upsert_cloud_item` before success is returned.
|
|
|
|
## Self-Check: PASSED
|
|
|
|
- FOUND: `backend/api/cloud/operations.py` with bounded retry, stale guards, and reconcile-before-return for both create_cloud_folder and rename_cloud_item
|
|
- FOUND: `backend/tests/test_cloud_mutations.py` with 10 new behavioral tests
|
|
- FOUND: `.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md`
|
|
- FOUND commit: `9ad9946` (Task 1 RED)
|
|
- FOUND commit: `aaa63c1` (Task 1/2 GREEN)
|
|
- Full suite: 755 passed, 17 skipped, 4 deselected, 12 xfailed
|
|
|
|
## Self-Check: PASSED (verified)
|
|
|
|
- FOUND: `backend/api/cloud/operations.py`
|
|
- FOUND: `backend/tests/test_cloud_mutations.py`
|
|
- FOUND: `.planning/phases/13-virtual-local-cloud-operations/13-08-SUMMARY.md`
|
|
- FOUND commit: `9ad9946` (RED tests)
|
|
- FOUND commit: `aaa63c1` (GREEN implementation)
|
|
- FOUND commit: `3e9355f` (docs/state)
|