Skip to content

Type the heal push race: a branch that moved under heal leaves by SupersededByAuthorHead - #11109

Merged
briansrls merged 3 commits into
mainfrom
heal-superseded-by-author-arm
Sep 12, 2026
Merged

briansrls merged 3 commits into
mainfrom
heal-superseded-by-author-arm

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What was actually failing

heal-generated-artifacts regenerated correctly, committed, and then died on git's own non-fast-forward exit status because the author pushed while it was building. Under the step's default shell the bare push ends the job, so the outcome reaching every reader is a raw rejection hint — true about git, silent about which state it is, and indistinguishable at the grain people read (a job name and a conclusion) from a drift the author really did cause. That is why this was reported upstream as the opposite defect.

Log verification of the three reported lanes

Read per job id, not per run (gh run view --log --job <id>; --allow-escape-sequences is rejected by that subcommand and its usage text greps as zero matches).

lane run heal job verdict
#11083 head eb94bf95 34646493695 failure lost race — committed c1f349c20e, ! [rejected] ... (fetch first)
#11083 head 8cbaa834 34644122365 failure lost race — committed bd81aa80d9, same rejection at 20:55:03
#10994 heads b4ec82a3, 0c987b11 34644904585, 34633098011 success neither — heal was green in both; the reds are required-witnesses-build and required-witnesses-floor

So it is the same race twice on one branch, and #10994 is not a heal failure at all — that lane's premise does not survive the log.

No genuine drift refusal exists in this population (item 4). Both #11083 heal runs regenerated and staged cleanly with no AUTHOR_COMMIT_DRIFT; docs/design-rung-drops.md was read and derived normally. Nothing here supports a rung-drop refusal finding, so none is filed.

The fix

The two states are decidable in the job by one question it can answer: is the remote branch still at the head this run judged?

  • gunbc.heal_revalidation gains HealSupersededByAuthorHead and its PriorRunDisposition mirror SupersededByAuthorHead — the author-produced counterpart to the heal-produced SupersededByHealedHead that already stood beside it.
  • gunbc.ci_spec classifies the push instead of assuming it. Branch moved → the named arm, exit 0. Push refused with the branch still where this run left it → a hard red, HealPushRefused.

No force, no retry. A second attempt against a still-moving branch either loses again or lands a repair derived from a tree nobody checked.

Why this arm exits zero where its twin exits nonzero

Both arms say the same thing about standing — the head this run judged is no longer the tip. They differ in what they leave behind. SupersededByHealedHead follows a published head: heal pushed H1, nothing has judged it, and the nonzero exit is what stops an unjudged tree standing as green. This arm publishes nothing — the healed commit stays on the runner and the branch carries an author head with its own run, which regenerates from the only tree that matters. Exiting nonzero would render a red square beside a PR for a scheduling outcome about a revision that no longer exists: exactly arm one of gunbc.recurring_failure_mode repair_job_red_is_attributed_to_the_content_it_repaired. The outcome stays loud and countable as a typed ::notice:: naming all three revisions plus the branch and both path sets; it does not stay loud by borrowing the channel that means "the content under review is wrong". heal-generated-artifacts is not a required lane, so this opens no gate.

This retires arm one of that row and not the row. Arm two still reports red on every heal that lands, and the row's trigger is explicit that a fix covering one arm does not retire it. I did not append a receipt to the row: it is a hot per-lane append surface and its text stays accurate as written.

Evidence (executed, both directions)

heal_outcome_token gives the name the log speaks one authority — the emitter cannot apply a coproduct arm to revisions that exist only as shell variables, so the token crosses into the script as text, and the witness derives its pattern from that fold rather than typing it twice.

  • Positive: all three new/extended witnesses return true.
  • RED 1 — rename the arm's token in the model → the_heal_push_race_leaves_by_its_own_named_arm returns false. The join is real, not a self-satisfying grep.
  • RED 2 — flip the arm's exit 0 to exit 1 in the emitter → same witness returns false. The exit status is asserted as ordered adjacency, so membership alone cannot green it.

Both reverted; the emitted witnesses.yml diff is the single expected hunk.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HKvv6ctMMcjbtFUiNV64dF

gunbc-ci-auto-heal and others added 2 commits September 11, 2026 22:39
…ersededByAuthorHead

heal-generated-artifacts regenerated correctly and then died on git's own
non-fast-forward exit status when the author pushed while it was building. The
outcome reaching every reader was a raw rejection hint -- true about git, silent
about which of two states it was, and indistinguishable at the grain people read
from a drift the author really did cause. gunbc#11083 lost this race on two
consecutive heads in half an hour.

The two states are decidable in the job, by one question it can answer: is the
remote branch still at the head this run judged? gunbc.heal_revalidation gains
HealSupersededByAuthorHead and its PriorRunDisposition mirror
SupersededByAuthorHead, the author-produced counterpart to the heal-produced
SupersededByHealedHead that already stood beside it. The emitter classifies the
push instead of assuming it: moved branch -> the named arm, exit 0; refused with
the branch still where this run left it -> a hard red, HealPushRefused.

It exits zero where its twin exits nonzero because the twin PUBLISHED a head
nothing had judged and this arm publishes nothing -- the healed commit stays on
the runner and the branch carries an author head with its own run. A nonzero
exit would render a red square beside a pull request for a scheduling outcome
about a revision that no longer exists, which is arm one of
gunbc.recurring_failure_mode
repair_job_red_is_attributed_to_the_content_it_repaired. The outcome stays loud
and countable as a typed line naming all three revisions; it does not stay loud
by borrowing the channel that means "the content under review is wrong". No
force, no retry: a second attempt against a still-moving branch either loses
again or lands a repair derived from a tree nobody checked.

heal_outcome_token gives the name the log speaks one authority, and
test.claim.heal_revalidation_binding_witness derives the pattern from that fold
rather than typing it twice. Executed both directions: renaming the arm and
flipping the arm's exit status each turn the witness false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKvv6ctMMcjbtFUiNV64dF
…wn status

Review 64120 (request_changes), two findings, both verified and both mine.

ONE: the emitter still hard-coded "::notice::SupersededByAuthorHead", so the
token fold was a check beside a construction that was available in the very
same function -- gunbc.ci_spec already splices
live_heal_push_refusal_cause_token into an emitted line. My annotation had
asserted the opposite, that an arm cannot be applied where its revisions are
shell variables, which was wrong four lines from the counterexample.

gunbc.heal_revalidation now derives the name from the arm and gunbc.ci_spec
splices it, for every HealOutcome and PriorRunDisposition arm the job prints --
not only the new one. That included HealPushRefused, which this PR had itself
introduced as a hand-spelled literal: it is now a real arm, folding to
PriorRunStillCurrent, since a push refused at a standing head supersedes
nothing. A second name for an arm is no longer writable in the emitter.

The control is that the emitted witnesses.yml is BYTE-IDENTICAL across the
conversion (81469 bytes, empty diff): the derivation reproduces all six names
exactly, so the change is a change of authority and not of output.

The annotation's claim is narrowed to what is true rather than left standing:
HealRepairDeclarationMissing is a step-integrity refusal of the emitter's own
protocol with no arm here, and is deliberately not dragged into the coproduct
to make a tidier sentence. The string-contains witness is re-scoped to what it
still discriminates -- the SHAPE around the name, which construction does not
reach -- because the token half can no longer go red.

TWO: the status of the failure-mode row was asserted in an annotation in a
different module, where nothing can read it and it rots away from the row it
describes. The claim is removed from the annotation and the receipt is written
on the row itself, which is its single authority: arm one now leaves by a named
arm, the row stays open on arm two, and the trigger's own words say why a fix
sufficient for one arm is not sufficient for the row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKvv6ctMMcjbtFUiNV64dF
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 64120 verified and fixed in 4201833. Both were mine.

1 — validation where construction was available. Correct, and sharper than stated: my own annotation asserted that an arm cannot be applied where its revisions are shell variables, four lines from live_heal_push_refusal_cause_token doing exactly that. gunbc.heal_revalidation now derives the name from the arm and gunbc.ci_spec splices it.

Done for every HealOutcome / PriorRunDisposition arm the job prints, not just the new one — so the three sibling literals you named (HealNoChange, HealProduced, SupersededByHealedHead) are derived too, plus HealAuthorCommitRequired at both of its spellings. That included HealPushRefused, which this PR had itself introduced as a hand-spelled literal — the same defect, in the code written to fix it. It is now a real arm folding to PriorRunStillCurrent, since a push refused at a standing head supersedes nothing.

The control: the emitted witnesses.yml is byte-identical across the conversion (81469 bytes, empty diff). The derivation reproduces all six names exactly, so this changes authority and not output.

The annotation is narrowed regardless, as you asked, because "every token the job prints" would still overstate it: HealRepairDeclarationMissing is a step-integrity refusal belonging to the emitter's own protocol with no arm here, and it is left alone deliberately rather than pulled into the coproduct to make the sentence tidier. The claim now reads "every HealOutcome and PriorRunDisposition arm the job prints" and says what it excludes.

The string-contains witness is re-scoped to what it still discriminates. Its token half can no longer go red — that is §4b(4) dissolution on a climb — so it is annotated as a regression control that the construction stays in place, and the wall it still carries is the shape around the name: ordered adjacency of the exit 0, the hard-red twin, and the absence of any force/lease flag. That half still reddens (verified by flipping the arm's exit status).

2 — a row's status asserted in a foreign annotation. Correct. Nothing can read an annotation, and a status parked in another module rots away from the row that owns it. The claim is deleted from the annotation; the receipt is now written on gunbc.recurring_failure_mode.repair_job_red_is_attributed_to_the_content_it_repaired itself, recording that arm one leaves by a named arm, that the row stays open on arm two, and that the trigger's own words say why a one-arm fix is not sufficient. The combined markdown projection is not a committed merge surface (DESIGN), so there is no artifact to regenerate; the row renders correctly under tools.docs_projection_gate regen.

Witnesses re-run after the change: all four return true.

— sent from vivid-fox-409

…uced it

Review 64162 (request_changes), two findings, both verified.

ONE: heal_outcome_token mapped HealSupersededByAuthorHead to
"SupersededByAuthorHead", dropping a prefix every sibling arm keeps and
colliding exactly with the PriorRunDisposition arm of that name -- one log word
naming two arms in two coproducts, with only one recoverable from it. A §3 fork
minted inside the function whose annotation claims a second name is not
writable.

The suggested repair was to spell the arm out in heal_outcome_token. That ends
the fork but leaves the log asymmetric: the job would print SupersededByHealedHead
beside HealSupersededByAuthorHead for the same kind of statement. The collision
names its own cause. A supersede line answers what STANDING the prior head now
has, which is PriorRunDisposition, and SupersededByHealedHead was already
emitted from that fold; sourcing its author-side twin from HealOutcome instead
is what bought the short name and created the clash. So both halves: each fold
now maps every arm to its own spelling character for character, and the notice
line draws superseded_by_author_head_token from the disposition fold where its
sibling already lives. The emitted witnesses.yml is unchanged -- the log word
and its symmetry survive, the fork does not.

TWO: the binding witness hand-typed "::error::HealPushRefused" three lines under
a comment saying the name is constructed. It consumes heal_push_refused_token.

AND THE WALL THE REVIEW IMPLIES. Nothing in a total match refuses two arms
returning one string: both typecheck perfectly. no_heal_token_names_two_arms
asserts all eight tokens across both folds are pairwise distinct, which is a
property OF THE PAIR that neither fold can see alone. Executed both ways --
green as written, and false when the shipped nickname is restored. The rung is
mechanically preventable and not structural: a colliding spelling is still
writable, and this witness is what refuses it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKvv6ctMMcjbtFUiNV64dF
@gunbai-bot

gunbai-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 64162 verified and fixed in 26e3c78.

1 — the nickname and its collision. Correct. I took a different repair than the one suggested, because the collision names its own cause.

Spelling the arm out in heal_outcome_token ends the fork but leaves the log asymmetric: the job would print SupersededByHealedHead beside HealSupersededByAuthorHead for the same kind of statement. A supersede line answers what standing the prior head now has — that is PriorRunDisposition, and SupersededByHealedHead was already emitted from that fold. Sourcing its author-side twin from HealOutcome instead is what bought the short name and created the clash in the first place.

So both halves:

  • each fold now maps every arm to its own spelling character for character (HealSupersededByAuthorHead in HealOutcome, SupersededByAuthorHead in PriorRunDisposition) — no token is recoverable to two arms;
  • the notice line draws superseded_by_author_head_token from the disposition fold, where its sibling already lives.

The emitted witnesses.yml is unchanged (81469 bytes, empty diff): the log word and its symmetry survive, the fork does not.

2 — the hand-typed literal. Correct, and the sharper reading of it is that it was the copy that survives a rename and greens anyway. It consumes heal_push_refused_token now.

The wall your first finding implies, added. Nothing in a total match refuses two arms returning one string — both typecheck perfectly, which is exactly why this shipped past a fold that is otherwise closed. no_heal_token_names_two_arms asserts all eight tokens across both folds are pairwise distinct. It lives in the witness rather than in either fold because it is a property of the pair; each fold alone is blind to it.

Executed both directions: green as written, and false when the shipped nickname is restored (reverted). Rung stated honestly — mechanically preventable, not structural: a colliding spelling is still writable, and this witness is what refuses it.

Full sweep after the change: emission unchanged, all five witnesses return true.

— sent from vivid-fox-409

@gunbai-bot

gunbai-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — taking the approval and not pushing over it, but one of your two non-findings is worth recording rather than leaving implicit, because it is a case of a test reading as more coverage than it carries.

You are right that count(all_tokens) == 8 is not an oracle violation (the universe is the test's own list, not a live population) and right that the exhaustive match is what catches a new arm with no token. The part worth stating plainly: all_tokens is a hand-built roster, so the disjointness wall covers collisions among the tokens listed there and is not a coverage guarantee over the coproducts. A new arm added with a token but not added to the roster passes silently — and the == 8 conjunct does not catch that either, since it only reds once someone has already extended the list. So that conjunct is a "you changed the roster deliberately" ratchet, not a completeness check, and I would rather it be read that way than as the wall.

The real wall against a missing token stays the exhaustive match, as you say. Deriving the roster from the arms is what would close the gap properly, and there is no reflection to do it with today, so it is a manual roster by construction rather than by oversight.

Not pushing a comment-only change over a green approval to say this — it is recorded here, and it belongs in the witness's annotation whenever that file is next opened for a substantive reason.

— sent from vivid-fox-409

@briansrls
briansrls merged commit 71eaa62 into main Sep 12, 2026
4 checks passed
@briansrls
briansrls deleted the heal-superseded-by-author-arm branch September 12, 2026 03:16
@briansrls
briansrls restored the heal-superseded-by-author-arm branch September 12, 2026 03:16
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