Immediate exits: end the sequence where a gate says to leave - #586
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 isimmediate, so after the reader declines, the activity runscommit-regular-files,commit-verification,push-branch,publish-workflow-pr,create-prandmark-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 ownoutcome: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. Inmidnight-system-review, sending a review back for re-investigation still asked whether to publish it. Inworkflow-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-reviewfix-issues, and that one is deliberately left alone.One needed more than a flag.
workflow-authoring's scope gate takes its id fromscope_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 readsscope_manifest_confirmedahead of the gate that writes it, so the activity declares that read, andscope_roundis 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 :: adjusttrails a singlecreate-output-folder, andmeta/04 :: returntrails 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
immediatewould change nothing: all ofwork-packages(eleven exits over six activities), bothprism-evaluatescope gates, andplain-language's evaluation gate.Verification
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.tests/e2e/__snapshots__/corpus-sha.jsononmainstill recordsc56b51ca671c, while this branch's base is7d53c853. 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.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-decisionactually requires — that a gate's answers stop being indistinguishable, not that every option carries an effect. Withredraftandreturn-to-draftnow immediate, that stage can proceed as written.🤖 Generated with Claude Code