Skip to content

Immediate exits: end the sequence where a gate says to leave - #586

Merged
m2ux merged 2 commits into
workflowsfrom
workflow/585-decisionless-checkpoints
Sep 3, 2026
Merged

Immediate exits: end the sequence where a gate says to leave#586
m2ux merged 2 commits into
workflowsfrom
workflow/585-decisionless-checkpoints

Conversation

@m2ux

@m2ux m2ux commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

When a person answers a checkpoint by choosing to leave — return to drafting, fix the findings first, remediate before attesting — the option records an exit, and the activity takes that exit when it finishes. Taking it at the end is the default, and the schema says so plainly: without immediate, "an exit is recorded when chosen and taken when the sequence ends." The flag exists, in the schema's own words, "for the aborts, where the tail would otherwise run against the user's decision."

Eight exits needed it and did not have it. On six of them the tail commits, pushes, opens a pull request, marks it ready, or removes the worktree — after the reader declined.

Found while starting #585. It is a different defect from the 22 gates that issue covers, and it blocked that work: #585's Stage 1 would have attached two of these very exits to five more options, adding sites to this class rather than closing it.

What happens today

workflow-design's validate-and-commit activity ends with a commit-approval gate. Its options include "Return to drafting — the changes are not ready" and "Correct open judgements". Both record an exit. Neither exit is immediate, so after the reader declines, the activity runs commit-regular-files, commit-verification, push-branch, publish-workflow-pr, create-pr and mark-ready, and only then routes back to drafting. The change is committed, pushed and published against the decision not to publish it.

workflow-authoring's validate-and-commit is the same shape three times over — the review disposition, the audit disposition, and its own commit gate — and its tail additionally writes the completion document and removes the worktree. That activity already states the intended behaviour in its own outcome:

A remediation round leaves the worktree, the branch and the register intact, because nothing past the disposition runs until the round that resolves it

Nothing past the disposition is exactly what ran. The definition contradicted the outcome it declared, which is what settles this as a defect rather than a design choice.

Three more, smaller in blast radius and the same in kind. In work-package's submit-for-review, answering the PR-body gate with "Provide missing input" still marked the pull request ready and entered the review loop. In midnight-system-review, sending a review back for re-investigation still asked whether to publish it. In workflow-design's scope-and-draft, choosing to return to drafting after a Critical pre-attestation finding still asked the reader to attest the content they had just rejected.

The fix

Eight exits are now declared immediate, so the sequence ends where the answer is given.

Every one of those exits has exactly one selecting option, so no gate that reads correctly today changes behaviour. That was the open question when this started, and the per-exit survey answered it: the only exit with two selectors is workflow-design's quality-review fix-issues, and that one is deliberately left alone.

One needed more than a flag. workflow-authoring's scope gate takes its id from scope_round, and the action advancing that counter sat after the gate, inside the tail. Cutting the tail would have frozen the counter, so a re-entered activity would present the same gate id and the self-loop could not tell its rounds apart. The counter now advances on the way into the gate, on the condition it always carried. Two contract lines follow from the move: the bump reads scope_manifest_confirmed ahead of the gate that writes it, so the activity declares that read, and scope_round is now written before it is read on every path the gate exists on, so it is no longer an inherited read.

Deliberately unchanged

workflow-design/08 :: fix-issues — the only exit with two selecting options. Its tail is four remaining audit passes and the fix cycle. Finishing the audit sweep before routing out to fix what it found is coherent, so the tail is the behaviour rather than a defect.

prism-audit/00 :: adjust trails a single create-output-folder, and meta/04 :: return trails a single completion log line. Neither is worth a behaviour change.

Sixteen further exits need nothing. Their gate is already the activity's last step, so immediate would change nothing: all of work-packages (eleven exits over six activities), both prism-evaluate scope gates, and plain-language's evaluation gate.

Verification

  • Guards: 30 pass, 0 fail, 0 unmeasured against this worktree.
  • Option-coverage walk: run (21 minutes). The coverage assertion passed, at 156/278 declared options (56%) over 14 workflows, 89 activities entered, 55 checkpoints short — identical in every figure to the run against merged #582. That is the load-bearing check here: cutting eight tails could have stranded an option in a step that no longer runs, and none did.
  • The run's stamp-freshness assertion fails, as it does for any corpus checkout at or after the current corpus head: tests/e2e/__snapshots__/corpus-sha.json on main still records c56b51ca671c, while this branch's base is 7d53c853. That stamp moves on the code branch's adoption commit, alongside the submodule pointer and the walk baseline. Nothing in this branch causes it or can clear it.
  • The eight sites were found by a scan written for this shape — every checkpoint option selecting a non-immediate exit with steps still ahead of it — after the original audit's scan, which looked only for gates where no option carried an effect, structurally could not see them.

Follow-up

#585 is updated: its Stage 1 no longer claims to be one line per gate, and its acceptance criteria now say what checkpoint-requires-decision actually requires — that a gate's answers stop being indistinguishable, not that every option carries an effect. With redraft and return-to-draft now immediate, that stage can proceed as written.


🤖 Generated with Claude Code

m2ux added 2 commits September 3, 2026 05:59
Seven exits were selected by a checkpoint option and declared without
immediate, so the answer was recorded and every remaining step ran
before the activity took it. The schema states the contract: without
that flag an exit is recorded when chosen and taken when the sequence
ends, and it is declared for the aborts, where the tail would
otherwise run against the user's decision.

Six of the tails mutate the deliverable. Declining the commit gate in
workflow-design and workflow-authoring ran commit, push, create-pr
and mark-ready, and in workflow-authoring also wrote the completion
document and removed the worktree. Choosing to remediate before
attesting ran the same tail. Supplying missing PR-body input in
work-package marked the pull request ready and entered the review
loop. Sending a midnight review back for re-investigation still asked
whether to publish it, and returning to drafting after a Critical
pre-attestation finding still asked for attestation of the content
just rejected.

workflow-authoring's validate-and-commit already declared the
intended behaviour in its own outcome: a remediation round leaves the
worktree, the branch and the register intact, because nothing past
the disposition runs until the round that resolves it. The definition
now does what the outcome says.

Every exit changed here has exactly one selecting option, so no gate
that reads correctly today changes.
workflow-authoring's scope gate takes its id from scope_round, and
the action advancing that counter sat after the gate. Marking the
revise exit immediate would cut the counter along with the rest of
the tail, so a re-entered activity would present the same gate id
and the self-loop would never distinguish its rounds.

The counter now advances on the way into the gate, on the same
condition it always carried, so the id is distinct on every round and
the exit can end the sequence where it is chosen. Choosing to revise
the manifest no longer drafts every file the unrevised manifest
names, nor authors the README over them.

The bump reads scope_manifest_confirmed ahead of the gate that writes
it, so the activity declares that read; scope_round is now written
before it is read on every path the gate exists on, so it is no
longer an inherited read.
@m2ux
m2ux merged commit 6eba1b8 into workflows Sep 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant