You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Producer suite: after-outcome hold, grow-archive lookup and halted event across restart #384
Split out of #383. Three of the ten producer failures there have their own root causes, and none of them depend on the handoff now design question that #383 keeps.
Reproduced on main at f9855e8 (after #382) with the --require-all variant in Docker. The result matches #383: 31 passed and 10 failed.
Scenario
Failure
sealed-after-outcome
archive must stay unsealed while held
export-torn-read
harness grow-archive failed: ... no unsealed archive to grow
Category: bug Summary: Make sealed-after-outcome, export-torn-read and halt-records-last-halt pass in the producer suite. There are three independent defects: two in the conformance harness and one in the engine.
Current behavior (verified by reading the code and reproducing the suite run):
finish-attempt drops the after-outcome hold. The harness verb finish-attempt deletes the hold flag (hold.json) unconditionally, so that a blocked executor can run on. The GatedPublisher gate is where an after-outcome hold is supposed to pause finalization, and it never sees the hold. So the attempt finalizes and seals straight away, and attempts show reports sealed: true while the suite still expects the hold to apply. finish-attempt then waits for the active attempt to clear, which could never happen under a live after-outcome hold.
grow-archive cannot find the attempt's archive. The contract attempt_id is the attempt's raw started_at timestamp, which contains :. AttemptArchive names its directory with : replaced by -. The harness's archive-candidate lookup filters run directories by attempt_id in run_dir.name, which never matches. So grow-archive answers failed: no unsealed archive to grow even while the attempt is active.
The halted Contract Event does not survive the restart. On halt, the harness daemon records the halt in HaltStore and emits the native halted event before it exits. The Contract Event buffer is in memory only and starts empty after a restart. So after start-container + release, instance.last_halt is correct, but recent_events has no halted entry.
Desired behavior:
Under hold {point: after-outcome}, finish-attempt records the scripted outcome and leaves the after-outcome hold in place. It returns once the outcome is recorded and finalization is paused at the gate (attempt still unsealed, outcome present). The next release lets finalization continue, seal the archive, and emit sealed for that attempt. Holds that block the executor (after-claim, native-phase) are still cleared by finish-attempt, as today.
grow-archive with a contract attempt_id (or null, meaning the active attempt) finds that attempt's unsealed archive files, whatever the directory-name sanitising. The second export then shows a torn read.
After an engine-decided halt and the next start, recent_events holds exactly one halted Contract Event whose reason matches the recorded last_halt. Seeding it at startup from the durable halt record is the expected approach, the same pattern Seed last_finished post-provenance and keep restart contract events valid #382 used to seed last_finished post-provenance. A start with no recorded halt must not invent one, and the event must stay a valid Contract Event (the suite's contract-events scenario checks this).
Key interfaces:
Harness verb finish-attempt: how it handles holds, and when it decides it has finished (outcome recorded vs. attempt cleared) when an after-outcome hold is armed.
Harness verb grow-archive: how it matches a contract attempt_id to an AttemptArchive directory. Preferably derive it from the same sanitising rule AttemptArchive uses, not a second copy of it.
HaltStore / instance.last_halt, and the in-memory Contract Event buffer (CONTRACT_EVENT_NAMES includes halted with a reason extra).
Acceptance criteria:
The producer suite reports PASS for sealed-after-outcome, export-torn-read and halt-records-last-halt, run in Docker as the CI producer job does, in both variants (--require-all with full capabilities, and --capabilities "").
Every scenario that passes today still passes, contract-events and publication-pending-recovery included.
Unit or integration tests cover: finish-attempt under an after-outcome hold leaves the attempt unsealed with its outcome recorded; grow-archive resolving a colon-bearing attempt_id; a restart after a recorded halt shows one halted event in recent_events, and a restart without one shows none.
Activity
lbacik commentedon Nov 10, 2026
Agent Brief
Category: bug
Summary: Make
sealed-after-outcome,export-torn-readandhalt-records-last-haltpass in the producer suite. There are three independent defects: two in the conformance harness and one in the engine.Current behavior (verified by reading the code and reproducing the suite run):
finish-attemptdrops theafter-outcomehold. The harness verbfinish-attemptdeletes the hold flag (hold.json) unconditionally, so that a blocked executor can run on. TheGatedPublishergate is where anafter-outcomehold is supposed to pause finalization, and it never sees the hold. So the attempt finalizes and seals straight away, andattempts showreportssealed: truewhile the suite still expects the hold to apply.finish-attemptthen waits for the active attempt to clear, which could never happen under a live after-outcome hold.grow-archivecannot find the attempt's archive. The contractattempt_idis the attempt's rawstarted_attimestamp, which contains:.AttemptArchivenames its directory with:replaced by-. The harness's archive-candidate lookup filters run directories byattempt_id in run_dir.name, which never matches. Sogrow-archiveanswersfailed: no unsealed archive to groweven while the attempt is active.haltedContract Event does not survive the restart. Onhalt, the harness daemon records the halt inHaltStoreand emits the nativehaltedevent before it exits. The Contract Event buffer is in memory only and starts empty after a restart. So afterstart-container+release,instance.last_haltis correct, butrecent_eventshas nohaltedentry.Desired behavior:
hold {point: after-outcome},finish-attemptrecords the scripted outcome and leaves the after-outcome hold in place. It returns once the outcome is recorded and finalization is paused at the gate (attempt still unsealed, outcome present). The nextreleaselets finalization continue, seal the archive, and emitsealedfor that attempt. Holds that block the executor (after-claim,native-phase) are still cleared byfinish-attempt, as today.grow-archivewith a contractattempt_id(ornull, meaning the active attempt) finds that attempt's unsealed archive files, whatever the directory-name sanitising. The second export then shows a torn read.recent_eventsholds exactly onehaltedContract Event whosereasonmatches the recordedlast_halt. Seeding it at startup from the durable halt record is the expected approach, the same pattern Seed last_finished post-provenance and keep restart contract events valid #382 used to seedlast_finishedpost-provenance. A start with no recorded halt must not invent one, and the event must stay a valid Contract Event (the suite'scontract-eventsscenario checks this).Key interfaces:
finish-attempt: how it handles holds, and when it decides it has finished (outcome recorded vs. attempt cleared) when an after-outcome hold is armed.grow-archive: how it matches a contractattempt_idto anAttemptArchivedirectory. Preferably derive it from the same sanitising ruleAttemptArchiveuses, not a second copy of it.HaltStore/instance.last_halt, and the in-memory Contract Event buffer (CONTRACT_EVENT_NAMESincludeshaltedwith areasonextra).Acceptance criteria:
PASSforsealed-after-outcome,export-torn-readandhalt-records-last-halt, run in Docker as the CIproducerjob does, in both variants (--require-allwith full capabilities, and--capabilities "").contract-eventsandpublication-pending-recoveryincluded.finish-attemptunder an after-outcome hold leaves the attempt unsealed with its outcome recorded;grow-archiveresolving a colon-bearingattempt_id; a restart after a recorded halt shows onehaltedevent inrecent_events, and a restart without one shows none.uv run pytestis green.Out of scope:
handoff,handoff-crash, and theagent_unreachablecascade (recovery-retry,recovery-release,errors-reset,offline-reads,offline-while-running). Those stay in Producer suite: sealing under hold, halted event, handoff acknowledgement and agent_unreachable cascade #383.agent_operator_contractpackage or its suite.Merge pull request #387 from lbacik/fix/issue-384-harness-holds-archi…