fabro/docs/internal/run-finalization.md
Scott Werner e9c35911bb Require finalization for every Fabro run
FabroHooks always declares required run finalization instead of only
when the run publishes or checkpoints. Every run takes one path, the
best-effort diff branch in run_finished goes away, and a fork always
declares what its worker's hooks will declare on resume, so the fork no
longer carries its source's flag across.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:08:28 -04:00

3.7 KiB

Required run finalization

Petri owns the durable run result. Fabro declares required finalization for every run and implements it in FabroHooks::finalize_run, so a resume or a fork always matches the stored declaration. A failed checkpoint rejects with checkpoint_failed and skips publication. The checkpoint failure cancelled the run, so Petri may commit cancelled; Fabro reports that finish as a workflow failure with the checkpoint's message. For successful workflow execution this prepares the final diff, then uses the existing publisher to retry the push and reconcile or create the pull request. A preparation or publication error rejects with publish_failed and a rendered message. Missing branch, checkpoint or workspace evidence is a rejection. Failed or cancelled execution skips publication; its diff remains best effort. Local workflow run-end hooks remain observational.

The coordinator awaits finalization and cleanup before committing run.finished. Until that record, the run stays active without a conclusion, even when every stage has succeeded. The run stream, API projection, managed server status, worker return and CLI verdict use the committed overall status and structured finalization failure. Stage outcomes, metrics and invocation results keep their execution meaning. Successful stages stay successful when publication fails.

Both HTTP and in-process worker transports store the finish before settling managed status. A rejected append cannot settle the run. The later platform terminal lifecycle record acknowledges the same outcome; it cannot replace a terminal result or conclusion. Early worker failures without a Petri finish still end through their committed platform lifecycle record. Run scope, authorization and lease ownership checks remain required for every worker write.

Recovery and stored history

This integration requires a Petri with required run finalization (coordinator format 9) and event contract 6. Petri retains its strict stored-format policy: version-8 coordinator logs cannot resume, inspect or replay with this engine. Fabro does not rewrite source history or relax that policy. Existing materialized views and stream rows remain stored; a projector replay failure holds Petri positions and reports incomplete record health. A historical run without a materialized view cannot recover its old Petri stage evidence through the new engine. Operators requiring inspection or recovery of old execution logs must keep the prior compatible binary and its storage backup.

Existing incorrect historical publication-success projections are not repaired by this change. Consumed cursors and immutable conclusions remain unchanged; historical-view repair requires a separate, bounded process over preserved source records. No publication is invoked by projection or replay.

A committed resume returns the same overall result without publishing again. A fork declares required finalization while seeding its records, as every Fabro run does; its worker restores the actual publication hooks before resume. Unfinished recovery must restore the same finalization requirement. Petri may call the restored finalizer again after interruption, including a crash after the callback returns but before the terminal record commits. Existing GitHub reconciliation is retained; this contract does not provide independent retry orchestration or guarantee external-effect deduplication after every crash. Lost terminal-execution workspaces are not recreated for finalization. Fabro rejects publication when it cannot establish the required evidence. Push attempts retain their existing five-minute timeout; this integration adds no overall finalizer deadline. Cancellation arriving after execution ends does not interrupt Petri's awaited finalizer.