fix(maestro): make EventLog::append transactional under partial write/flush failure
A write_all that errors mid-line, or a flush that errors after a full write, could leave last_seq and the log's on-disk line count out of sync: a well-formed but uncounted line, or a partial one, would corrupt every later cursor once the next append landed beside it. append now captures the pre-append length and truncates back to it on either failure, so the same EventLog can be appended to again immediately. If the rollback itself can't restore that length, append returns a Fatal variant so callers kill the session instead of promising consistency they can no longer keep — record_permission_request and its pump now route this through the session-death path used elsewhere. Adds deterministic fault injection (a seekable/truncatable LogSink double) to prove the same EventLog recovers with valid JSONL and live/replay agreement, rather than swapping in a healthy handle. Fixes the row-index rollback test to actually shift a later row, and tightens the persistence-failure wording in STRUCTURED-VIEW.md.
This commit is contained in:
+16
-5
@@ -302,7 +302,14 @@ directory: the sidecar itself (`<id>.json`), plus two next to it:
|
||||
daemon-generated row. Both share the same 1-based line-position cursor. A
|
||||
line a hard kill left half-written is terminated before the next append, so
|
||||
it keeps its position and is skipped as malformed rather than corrupting
|
||||
what follows.
|
||||
what follows. An append that fails *while the daemon is running* — a
|
||||
`write_all` that errors after only a prefix reached disk, or a `flush` that
|
||||
errors after a complete write — is rolled back to the exact byte length the
|
||||
file was at before the call, so `last_seq` and the file's line count never
|
||||
disagree and the same log handle can be appended to again immediately (#184).
|
||||
If that rollback itself cannot restore the prior length, the append is
|
||||
fatal: the log's consistency can no longer be promised, so the caller kills
|
||||
the session instead of appending to it again.
|
||||
- **`<id>.recover`** — an empty marker file. Its only meaning is "the daemon
|
||||
that wrote this shut down gracefully while this session was running, and
|
||||
the next daemon may attempt to resume it." It is consumed (deleted) the
|
||||
@@ -314,10 +321,14 @@ 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, 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).)
|
||||
narrower: as long as the log rolled back cleanly, it only cancels the
|
||||
pending approval and rolls the unpersisted `Approval` row back out of the
|
||||
live transcript, without marking the session dead — a snapshot taken after
|
||||
the failure must never show a row durable replay could not reproduce
|
||||
(#184). If the rollback itself did not restore the log's prior length,
|
||||
this narrower path does not apply either: the session is marked dead the
|
||||
same as any other append failure, since the log can no longer promise
|
||||
`last_seq` matches its file.)
|
||||
`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
|
||||
|
||||
Reference in New Issue
Block a user