Files
CleanMM/Docs/plans/2026-03-13-recovery-retention-enforcement.md
zhukang 1cb9a42c7b fix: enforce recovery retention and fail-closed restore semantics
- prune expired recovery items on load/save and reject expired restores at worker boundary
- add restoreExpired and restoreConflict protocol/application error mapping
- disable expired restore actions in History and reload persisted state after restore failures
- add recovery expiry/conflict coverage plus sync protocol, architecture, state-machine, and recovery contract docs
- wire AtlasAppTests into the shared Xcode scheme and add app-layer regression coverage for expired restore reload behavior

Refs: ATL-221 ATL-222 ATL-223 ATL-224 ATL-225, vibe-kanban SID-9
2026-03-13 14:38:50 +08:00

2.6 KiB

Recovery Retention Enforcement Plan

Goal

Align shipped recovery behavior with the existing Atlas retention contract so expired recovery items are no longer restorable, no longer linger as active recovery entries, and return stable restore-specific protocol errors.

Problem

The current worker restores any RecoveryItem still present in state, even when expiresAt is already in the past. The app also keeps the restore action available as long as the item remains selected. This breaks the retention-window claim already present in the protocol, task-state, and recovery docs.

Options

Option A: Narrow docs to match current code

  • Remove the retention-window restore claim from docs and gate reviews.
  • Keep restore behavior unchanged.

Why not:

  • It weakens an existing product promise instead of fixing the trust gap.
  • It leaves expired recovery items actionable in UI and worker flows.

Option B: Enforce expiry only inside restoreItems

  • Reject restore requests when any selected RecoveryItem.expiresAt is in the past.
  • Leave repository state unchanged.

Why not:

  • Expired entries would still linger in active recovery state across launches.
  • The app could still display stale recovery items until the user attempts restore.

Option C: Enforce expiry centrally and prune expired recovery items

  • Normalize persisted workspace state so expired recovery items are removed on load/save.
  • Recheck expiry in the worker restore path to fail closed for items that expire while the app is open.
  • Return stable restore-specific error codes for expiry and restore conflicts.
  • Disable restore UI when the selected entry is already expired.

Decision

Choose Option C.

Implementation Outline

  1. Extend AtlasProtocolErrorCode with restore-specific cases used by this flow.
  2. Normalize workspace state in AtlasWorkspaceRepository by pruning expired RecoveryItems.
  3. Recheck expiry in AtlasScaffoldWorkerService.restoreItems before side effects.
  4. Map restore conflicts such as an already-existing destination to a stable restore-specific rejection.
  5. Disable restore actions for expired entries in History UI.
  6. Add tests for:
    • expired recovery rejection
    • repository pruning of expired recovery items
    • restore conflict rejection
    • controller localization for restore-specific rejections
  7. Update protocol, architecture, task-state, recovery contract, and gate review docs to match the shipped behavior.

Validation

  • swift test --package-path Packages --filter AtlasInfrastructureTests
  • swift test --package-path Packages --filter AtlasApplicationTests
  • swift test --package-path Packages