pub async fn record_permanent_refusal(
ctx: &mut RunCtx,
error: &EngineError,
) -> Result<bool, RuntimeError>Expand description
Records the terminal RunFailed a PERMANENT run_graph refusal deserves,
so a dead run stops masquerading as a running one.
run_graph itself never writes a terminal for a refusal, and that is
deliberate: refuse-before-record is what keeps the log free of events past a
refusal, and the engine cannot know whether its caller intends to re-drive.
The DRIVER does know, and it is the driver that owns the run’s disposition.
So the drivers (the CLI’s graph run, resume, and graph fork paths, and
the server’s drive_graph task) call this with the error they just received
and THE SAME ctx that produced it. The same one matters: the append claims
the position that ctx’s cursor stands on, which is the position after
everything the refused drive replayed or wrote. A fresh RunCtx over the
same log stands at position zero and would diverge rather than append, which
is the machinery refusing to write a terminal onto a run it has not read.
Only a permanent error is recorded: one that
EngineError::is_permanent calls a pure function of the frozen document
and the recorded log, so re-driving reproduces it forever. A transient error
is left exactly as it was, and the run stays recoverable. Returns whether a
terminal was recorded, so a caller can say so in its own voice.
§Ordering, and the kill between the refusal and this append
The append goes through RunCtx::fail_run,
which is the same persist discipline every other event uses: the cursor
claims the next sequence and the envelope is durable before this returns.
That is also the “only when the log holds no terminal” guard, and it needs no
second read of the store. A log whose recorded next event is the identical
RunFailed (a driver that already did this) REPLAYS it and appends nothing;
a log holding a different terminal is a divergence the caller surfaces
rather than overwrites.
A kill -9 landing between the refusal and this append therefore leaves a
log with no terminal at all, ending at whatever the refusal was recorded
past (for a fold, its last FoldIterationJoined). That log is not corrupt
and not stuck: the next drive replays it, re-derives the SAME permanent
refusal from the same recorded values, and this appends the RunFailed
then. Nothing re-executes, because everything before the refusal is history.
The window is a delay in the status an operator reads, never a divergence.
§Errors
RuntimeError when the append does not
persist, or when the log already holds a different terminal. Callers report
the ORIGINAL engine refusal in that case: it is the real news, and losing
the terminal only means the run reads as recoverable when it is not.