Fix quarantine non-atomic operations with two-phase commit

Implements atomic quarantine/restore operations using two-phase commit
pattern to prevent orphaned files when manifest updates fail.

Changes to models.py:
- Add status field to QuarantineEntry ("pending" | "committed")
- Default to "committed" for backward compatibility

Changes to quarantine.py:
- Rewrite quarantine_file() with three phases:
  1. Write pending manifest entry BEFORE moving file
  2. Move file to quarantine
  3. Mark manifest entry as committed
- Rewrite restore_from_quarantine() with same pattern
- Add _recover_pending_entries() for auto-recovery on manifest load
- Update _load_manifest() and _save_manifest() to handle status field

Changes to executor.py:
- Add special handling for quarantine rollback using QuarantineManager
- Fix bug where executor didn't preserve quarantine destination_path
- Return QuarantineManager result directly (includes actual quarantine path)

Testing:
- Fixed pre-existing test_rollback_quarantine_operation
- All 439 tests now pass (was 438 with 1 failure)

Atomicity guarantees:
- If manifest write fails → operation fails, no file moved
- If file move fails → rollback removes pending manifest entry
- If commit fails → auto-recovery fixes on next load
- No orphaned files possible

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
windyboy
2026-02-13 09:50:29 +08:00
co-authored by Claude Sonnet 4.5
parent 065195b83b
commit d6c8852e1e
4 changed files with 1848 additions and 101 deletions
File diff suppressed because it is too large Load Diff