Files
curo1305andClaude Sonnet 4.6 b67e77dd69 docs(13): add phase 13 planning artifacts — virtual local cloud operations
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>
2026-06-23 20:34:57 +02:00

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)