fix(maestro): roll back phantom approval row on log append failure

request_approval() appended the Approval row to the live projection
before the daemon tried to persist it. On append failure the pending
nonce was cancelled but the row stayed in the in-memory transcript, so
a WebSocket snapshot taken afterward showed a row durable replay could
never reproduce.

Add Projection::rollback_row to undo the append and call it alongside
the existing cancel_pending_approvals on failure. Extract the pump's
per-request handling into record_permission_request so the failure
path is directly testable without a live ACP connection.

Fault-injection regression tests use a read-only file handle
(EventLog::for_test, cfg(test)) to force append() to fail
deterministically, then assert the live snapshot and durable replay
agree (both empty) and a subsequent request still persists and
replays correctly.

Fixes #184.
This commit is contained in:
Miguel Palhas
2026-09-05 04:34:47 +01:00
parent 51f96cfb98
commit c067f48a24
4 changed files with 270 additions and 38 deletions
+5 -3
View File
@@ -314,9 +314,11 @@ persistence consequences:
1. **The adapter exits, or a notification append to the event log fails,
while the daemon keeps running.** (An approval-row append failure is
narrower: it cancels the pending approval and lets the session continue,
without marking it dead.) `mark_session_dead` applies the same
natural-exit worktree rule
narrower: it cancels the pending approval, rolls the unpersisted
`Approval` row back out of the live transcript, and lets the session
continue, without marking it dead — a snapshot taken after the failure
must never show a row durable replay could not reproduce (#184).)
`mark_session_dead` applies the same natural-exit worktree rule
as a terminal session's own pane exiting (`docs/WORKTREES.md`): a pristine
worktree is removed, one holding unsaved work is left on disk. No recovery
marker is written. This death is permanent — a later daemon restart's