Skip to content

fix(GATE-ANCHOR-PER-JOB): each diff gate anchors on its own last STEP verdict, so a red main can return to green (#1773) - #1776

Open
localai-bot wants to merge 14 commits into
mainfrom
row/GATE-ANCHOR-PER-JOB
Open

fix(GATE-ANCHOR-PER-JOB): each diff gate anchors on its own last STEP verdict, so a red main can return to green (#1773)#1776
localai-bot wants to merge 14 commits into
mainfrom
row/GATE-ANCHOR-PER-JOB

Conversation

@localai-bot

@localai-bot localai-bot commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

A diff-scoped gate on the push lane used to ask the runs endpoint for
status=success. That is the RUN's own conclusion, the one thing
scripts/main-baseline.py has said since #274 must never be read, and it is
wrong in both directions: it reads success over a red continue-on-error job,
and cancelled over a job that ran to completion. Because a successful run was
exactly what a red gate blocked, the anchor froze while the range widened on
every push, re-flagging the commits blocking it. Measured at 21abaf169: 484
commits, anchored eleven days back.

Worse, a shared base resolved in a SEPARATE job is a race. The consumers carry
if: always(), so they outlive that job's cancellation. On run 32625264281 the
resolver was cancelled, the shared string rendered empty, and
documentation-checkpoint fell back to PUSH_BASE and passed over one push,
the same gate that fails over 484 commits whenever the resolver survives. The
verdict on main was decided by which job won a cancellation race.

Each diff-scoped job now resolves its OWN anchor, in its own first step.

The unit is the STEP, not the job

The first round of this pull request anchored on the job, and the fresh review
failed it on that. It re-created #863's hole one level down. GitHub concludes a
job failure the moment any step fails and marks every remaining step
skipped, so "the job concluded" reads failure over a gate that refused the
range and over a gate that never executed, and cannot tell the two apart. Both
agent-record and commit-protocol-tag place their diff-scoped gate after
other steps.

Measured on commit-protocol-tag, where the strict trailer walk runs nowhere
else on the push lane:

run head job strict-trailer step
32599040638 038ff61e5 success success
32601353990 1a1d17e53 failure skipped
32608320394 6354755ba failure skipped
32613454280 b508cbce6 failure skipped
32616777372 66d1b0a90 failure skipped
32623377380 a4f2a9585 failure skipped
32625264281 1fdd3e26d success success

A job-level anchor walks 038ff61e5..a4f2a9585, six commits, straight past.
agent-record is twelve commits on the same shape, rescued only because
documentation-checkpoint runs the same checker, which is luck rather than
construction.

Three things enforce the step as the unit. --gate-step is required and the
flag-less form exits 2, so the hole cannot be re-created by omitting a flag.
steps_concluded reads steps[].conclusion and treats an absent step as no
verdict. Every gate step carries
if: !cancelled() && steps.checkout.outcome == 'success' && steps.anchor.outcome == 'success' && env.GATE_ANCHOR_DEGRADED != 'true',
so an earlier failure cannot silence it and the three cases where a conclusion
would be a lie produce a skip instead. documentation-checkpoint ran two
diff-scoped checkers in ONE step under set -eu, where the first one's failure
aborted before the second ran while the step still concluded, and that step is
now split in two.

The floor, and a degraded query

The floor read one run short, so the oldest candidate's own head fell outside
the range the floor exists to guarantee. It now reads window + 1 runs, which
costs nothing. Past the window commits still roll off permanently, and that is
stated with its bound and asserted by a test rather than denied: it happens only
after a gate has returned no verdict on 20 consecutive pushes, and only after
the pull request lane already gated those commits at merge.

The floor could also land on the run being pushed. With one push run in the
window runs[-1] IS runs[0], so the anchor resolved to the head being pushed
and base..head was EMPTY: a gate reporting success over no commits at all, and
CONCLUDING on it, which advanced its own anchor. Latent on this repository, whose
window is always full, and reachable on a fork's first push. There is nothing to
floor on with a single run, so the answer is the clean absence. Below
window + 1 runs the off-by-one is not repairable at all -- the oldest available
run is itself a candidate, and a workflow_run object carries head_sha and no
parent -- so that residual is asserted by a test rather than implied away.

A degraded query was swallowed by || true, leaving an empty anchor that the
gate silently replaced with PUSH_BASE. Under a step-level anchor that is not
merely dishonest but a hole, because the narrowed pass advances the anchor past
everything the narrowing dropped. --gate-anchor now exits 3 on
REMOTE_UNVERIFIED and 1 on a clean absence, the split AGENTS.md already
documents for scripts/agent-pr-body.py, and a degraded read SKIPS the gate.

push_runs broke that split from underneath. It returned an empty window --
rc 1, and therefore a NARROWED range -- for any payload that was not a dict
carrying workflow_runs, where jobs_for has always answered
REMOTE_UNVERIFIED for the same case. Driving the real main through a fake
gh_api, a list payload, a null payload and GitHub's {"message": "Not Found"}
error object all exited 1 where they had to exit 3. All four shapes exit 3 now,
and a genuinely empty window still exits 1, because collapsing the two in either
direction is its own defect.

The five commits, and the landing rule

The external-fork hypothesis is refuted. check-role-discipline.py reads no
ref: for a single-parent commit arrives_via_row_pr is commit-message text and
nothing else, and the one NON-fork commit of the five fails identically. What
the five share is a subject with no (#N).

The mechanism is labelled INFERRED, because the forge records no merge-input
payload and the review was right to say so. Four of the five pull requests carry
a "Maintainer change on top" section, not three as first written, and all five
were merged by the same account that appends (#N) correctly on every other
squash. check-role-discipline.py is not touched and no exception is recorded.
AGENTS.md gains a landing rule instead, which names the flag and the endpoint
field, binds whoever performs the MERGE rather than the author, and says plainly
that no gate can catch it. The third review found AGENTS.md still asserting the
inferred mechanism as fact and naming a specific account as the cause, after
this spec and the index row had both retracted it. It is the one file every
agent loads automatically, so it carried the strongest version of a claim the
rest of this pull request hedges, and it would have been quoted forward as
measured. The RULE is unchanged and still one flag; the causal and the account
attribution now match the evidence, and the reader is pointed at the spec for
the two facts that weaken the hypothesis.

.agents/issue-index.md's row is corrected to match, because that row is ADDED
by this pull request and stated both retracted claims as fact: the inferred
commit_title mechanism, and the job-level anchor the review refuted. The spec's
first reason for leaving it was wrong -- the row has not landed, and
scripts/check-issue-index-append-only.py diffs the merge base against the head,
so amending a row this branch also added stays a pure addition. With
squash_merge_commit_message = PR_BODY exactly one version of that row will ever
exist on main, so before the merge is the last moment it can be right.

The guards are RESOLVED now, not grepped

The second review passed the design and failed the guards around it. The test
holding the five gate-step if: conditions asserted four SUBSTRINGS. Appending
|| true to all five leaves every one of them byte-for-byte intact, turns the
whole conjunction into the constant true, and restores the degraded-read
narrowing in full -- 99 tests, OK, All gates green., no skip. It now resolves
the guard to a boolean over all sixteen states it has to decide, reusing the
expression evaluator this suite already carried for concurrency keys; the same
mutation reports 75 failures across all five steps.

The one-gate-per-step test had the same shape. It read the first python3 <arg>,
so a git fetch planted before check-now-current.py was invisible, and
if not checkers: continue exempted the one gate step whose whole body is an
inline shell gate. Both mutations reported 99 tests, OK. Under set -eu that
command aborts the step, the step concludes failure, and steps_concluded
reads the conclusion as a verdict about a gate that never ran. The property is
held by resolving the body now: the range prelude may bind variables and call
set, [, echo and :, the first command that is not one of those IS the
gate, and it must consume the range. Both mutations red, and the inline-shell
step is in the population.

The prelude refusal covered the wrong statements

The third review confirmed the TREE as correct, and failed the pull request
because a test and a record assert a completeness they do not hold. That is the
same class as the two findings above, one layer further in.

The repair described in the previous section stated one residual and had three
escapes past it. The parser returned on any name=... head BEFORE the
$(/backtick refusal ran, so a binding carrying a substitution was never
examined; and [ was allowlisted on the reasoning that its if/|| consumes
the failure, while the parser splits on || and discards it, so nothing checked
that a given [ HAS one. Four injections immediately before
check-now-current.py, each executed under bash with python3 shimmed:

injected before the gate step body under set -eu gate ran suite
base="$(git rev-parse --verify "${base}^{commit}")" rc 128 when the anchor is absent, rc 0 when it resolves 0x Ran 109, OK
_p="$(git fetch -q origin nope)" rc 128 0x Ran 109, OK
_p=`git fetch -q origin nope` rc 128 0x Ran 109, OK
[ -f .git/NO_SUCH ] rc 1 0x Ran 109, OK

The first is the one to worry about, and its two rcs are the point. Normalising
the anchor to a full commit id right before the gate is the natural next edit in
that step; it is harmless while the anchor resolves and aborts at 128 once it
does not, which is the shallow fetch, the rewritten branch, and the floor SHA
aged out of the window. The step then concludes failure over a gate that never
executed, which is the defect this test exists to close.

commands_in becomes statements_in. It returns bindings as statements instead
of dropping them, and it keeps the operator that follows each piece, because
whether a failure aborts the step depends on what comes after it. The refusal
covers every statement before the gate, and a [ is admitted only where an
if/while header or an ||/&& reads its status. It discriminates rather
than banning: [ -f .git/NO_SUCH ] || echo missing, an if header form and a
plain binding all stay green, and the shell agrees with the test on each. The
nine shapes the previous round caught still red. Two residuals are now stated on
the test instead of overstated.

Three more repairs ride with it. baseline_runs, the advisory lane's reader,
had never been executed by any test and reported an unreadable forge to a human
as a clean absence -- a list, a null and {"message": "Not Found"} each printed
"No completed baseline run found on main." with an instruction to trigger the
first run. The rc was never wrong, so nothing read it as a pass; what was wrong
is that unknown was reported as absence, and jobs_for beside it already
refused those shapes. test_the_checker_resolves_no_ref_to_decide_arrival
forbade origin/, ls-remote and for-each-ref in the RAW source, so a future
comment explaining that this checker resolves no ref would red it with a message
about a lookup that is not there; it now reads the source with comments and
docstrings blanked and string literals kept, which is where a real lookup lives.
And the spec's own count of the gate-step guards said four in one line and five
in another; measured from ci.yml it is five.

Evidence

test_no_commit_is_ever_skipped is re-expressed at the granularity that can
actually fail. The union of the ranges was never the property: a job-level
anchor keeps that union whole while the gate is skipped on every push. Against
the unmodified implementation it reports commits with no verdict from any run that ran the gate: ['p2', 'p3']. tests/scripts/test_main_baseline.py reports
Ran 111 tests, OK, up from 82, then 99, then 109.
tests/scripts/test_check_role_discipline.py is Ran 22 tests, OK.
scripts/agent-preflight.sh reports All gates green., rc 0.

Sixteen mutations across the first two review rounds and twenty-six probes in
the third, each printed with
git diff --stat, parsed -- PyYAML for ci.yml, ast.parse for the scripts --
to prove it was not a syntax error wearing a pass, and restored against a
recorded sha256 with an empty git status --porcelain, an empty git diff, and
__pycache__ cleared under PYTHONDONTWRITEBYTECODE=1 on both sides. That last
step is not ceremony: a same-length mutation and a same-second restore both
satisfy Python's (mtime, size) cache validation, and one restored file in the
previous round kept running the mutant's bytecode from a provably clean tree.

Live anchors on origin/main at c98ffd4d0: commit-protocol-tag resolves a
verdict anchor at 21abaf169, while agent-record and
documentation-checkpoint fall back to the window floor 08c81a892. ci.yml
parses under PyYAML with an explicit duplicate-key scan reporting 0, and a
job-by-job comparison against origin/main reports 17 jobs on both sides, none
added, none dropped, no job-level if: and no needs: changed.

git diff --numstat origin/main -- .agents/issue-index.md is 2 0, and
scripts/check-issue-index-append-only.py returns OK.

One gap is filed rather than repaired here. scripts/check-symbol-anchors.py
resolves a path::Symbol citation, so a document that cites a symbol as a bare
backticked name with no path is outside its population -- which is why this pull
request's own rename in 26def4c8f left .agents/specs/ci-concurrency.md
naming a test that does not exist, with every gate green. The stale citation is
repaired here. Teaching the checker to resolve a bare identifier changes checker
semantics and needs its own row, spec and red-before evidence, and it carries a
real design question about which backticked tokens are citations at all. Tracked
as #1787, owned by this row's spec under ## Owed.

Refs #1764 (the live reds; its documentation-checkpoint item is what this
addresses, and its two other items stay open), #274, #822, #863, #873, #1262
(the landed exception whose argument this row invalidates, rewritten in place),
#1787.

Closes #1773

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

mudler added 4 commits August 23, 2026 09:22
…rdict, not on a run-level success (#1773)

`main` cannot return to green by itself. `last-gated-commit` resolves the base of
every diff-scoped gate with a RUN-level `status=success` query, and a successful
run is what those gates are blocking, so a red `main` widens its own range and
keeps re-flagging the commits blocking it.

Measured at this base. The query returns `fafa16f0f` from 2026-08-12 -- a
484-commit range -- while `documentation-checkpoint` itself concluded `success`
at `1fdd3e26d` eleven days later, inside run `32625264281` whose own conclusion
is `cancelled`. That is the per-job/run-level distinction
`scripts/main-baseline.py` has documented since #274, in the module the job
asking the question never calls.

The spec also records a second defect, sharper than the cycle: `last-gated-commit`
is itself cancellable and its consumers carry `if: always()`, so when the
resolver loses the race `LAST_GREEN` renders empty, the step falls back to
`PUSH_BASE`, and the gate passes over a single push. `main`'s diff gates are
therefore green when the resolver dies and red when it lives, and neither
reading is about the tree.

It refutes the external-fork hypothesis carried by #1764 and #1773.
`check-role-discipline.py` reads no ref: for a single-parent commit
`arrives_via_row_pr` is commit-message text only. The one non-fork commit of the
five being re-flagged fails identically. The real discriminator is the squash
subject, which lacks `(#N)` because the merger supplied an explicit
`commit_title`.

Spec only. The implementation follows in this branch.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ict, so a red main can return to green (#1773)

`last-gated-commit` resolved one shared base for every diff-scoped gate from
`repos/.../runs?branch=main&event=push&status=success&per_page=1`. `status=`
filters on the RUN's conclusion, which `scripts/main-baseline.py` has said since
#274 must never be read: `sanitize-cpu` is `continue-on-error`, so that field
reads success over a red job, and a cancelled run reads `cancelled` over a job
that ran to completion. And because a successful run was exactly what a red gate
blocked, the anchor froze while every later push widened `LAST_GREEN..HEAD`,
re-including the commits being flagged. The exit condition required the thing it
blocked.

Measured at `21abaf169`. The query returns `fafa16f0f` from 2026-08-12, a
484-commit range, and `check-role-discipline.py` over it reports five commits
from 2026-08-18. Meanwhile `documentation-checkpoint` itself concluded `success`
at `1fdd3e26d` on 2026-08-23, inside run `32625264281` whose own conclusion is
`cancelled`.

That run also shows the second defect, which is a race rather than a cycle. The
gates carry `if: always()`, so they outlive their own resolver: in `32625264281`
`last-gated-commit` was cancelled, the shared string rendered empty, the step
fell back to `PUSH_BASE` and PASSED over a single push. `main`'s diff gates were
green when the resolver died and red when it lived, and neither reading was
about the tree.

`scripts/main-baseline.py` gains `--gate-anchor JOB`, in the module that already
owns the per-job doctrine. It walks the newest 20 push runs and returns the head
of the first one in which every payload entry for that job carries a conclusion.
`failure` counts: the range decides which commits are inspected, never what is
demanded of them, and a commit on `main` is immutable, so anchoring on `success`
turns one violation into a permanent block while the gate loses the ability to
report on new commits. One alarm per violation is a complete alarm. The pull
request lane still blocks, over `base..head`, unchanged.

Each of `agent-record`, `documentation-checkpoint` and `commit-protocol-tag` now
resolves an anchor naming ITSELF, in its own first step. One anchor could not
have been right for three jobs anyway: measured live, `documentation-checkpoint`
resolves `ff8f72807` (2 commits) while `agent-record` resolves `66d1b0a90` (13),
because they have different cancellation profiles. `last-gated-commit` keeps
only the closed-pull-request guard `agent-record` inherits through `needs:`
(#873). When no run in the window qualifies the anchor is the window's OLDEST
head, so a range can never widen past 20 pushes and degrades toward more
coverage rather than less; a failed query keeps the `PUSH_BASE` fallback.

`check-role-discipline.py` is NOT touched. The external-fork hypothesis carried
by #1764 and #1773 is refuted: the checker resolves no ref, and the one non-fork
commit of the five fails identically. What the five share is a squash subject
with no `(#N)`, because the merger supplied an explicit `commit_title` and
suppressed the append GitHub makes even under `squash_merge_commit_title =
PR_TITLE`. AGENTS.md gains that landing rule, which is where the defect was
introduced. Widening the match to the whole commit body was rejected and is now
held shut by a test: AGENTS.md requires every change to name an issue, so a
body-wide match would pass every direct-to-main push ever made.

Two existing assertions are re-pinned rather than relaxed.
`test_every_diff_scoped_step_bases_on_the_last_gated_commit` and
`test_the_diff_scoped_base_is_the_last_gated_commit` named the shared string;
they now demand that each step consume an anchor its OWN job resolved, which is
strictly stricter, and a new test refuses a shared value in the guard job.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
Keeps the branch fast-forwardable onto main so the diff-gate anchor change is
reviewed against the tree it will land on.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ation that turns the 484-commit red GREEN (#1773)

Fills the spec's evidence section with what was measured rather than what was
planned. The load-bearing rows: the anchor moves from `fafa16f0f` (2026-08-12,
484 commits, five ERRORs) to `ff8f72807` (2 commits, OK); `agent-record`
resolves `66d1b0a90` and `documentation-checkpoint` `ff8f72807` on the same
tree, eleven commits apart, which is the per-job argument stated as a number.

Five mutations, each hashed before, printed with `git diff --stat`, parsed, and
restored against the hash. M2 makes the cycle test print the widening range it
constructs. M4 is the rejected widening: matching `#N` anywhere in the message
turns the whole 484-commit red into `OK`, which is what deleting the obligation
looks like from the inside. M5 is the control -- a real direct push inside the
NARROWED range is still reported by SHA.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
mudler added 3 commits August 23, 2026 10:39
… concludes over a gate that never ran (#1773)

The fresh review on #1776 failed the first round on a critical finding, and it
was right: anchoring on the JOB re-created #863's hole one level down. GitHub
concludes a job `failure` the moment any step fails and marks every remaining
step `skipped`, so "the job concluded" reads `failure` over a gate that refused
the range and over a gate that never executed, and cannot tell them apart. Both
`agent-record` and `commit-protocol-tag` place their diff-scoped gate after
other steps. Measured on `commit-protocol-tag`, runs 32601353990 through
32623377380: job `failure` five pushes running, strict-trailer step `skipped`
every time, and a job-level anchor walks `038ff61e5..a4f2a95`, six commits,
straight past. `agent-record` is twelve commits, rescued only by
`documentation-checkpoint` running the same checker, which is luck.

The unit is now the step. `--gate-step` is required and the flag-less form exits
2, `steps_concluded` reads `steps[].conclusion` and treats an absent step as no
verdict, every gate step carries `if: !cancelled() && ...` so an earlier failure
cannot silence it, and `documentation-checkpoint`'s two-checker step is split so
one gate's abort cannot conclude for the other.

Three smaller findings go with it. The floor read one run short, so the oldest
candidate's own head fell outside the range it was meant to guarantee; it now
reads `window + 1`, and the residual loss past the window is stated with its
bound instead of being denied. A degraded query was swallowed by `|| true` and
silently narrowed the gate to `PUSH_BASE`, which under a step anchor is not just
dishonest but a hole, because the narrowed pass advances the anchor; rc 3 now
means REMOTE_UNVERIFIED and the gate skips. The AGENTS.md landing rule loses the
file's only em dash, names the flag and the endpoint field, binds the merging
account rather than the author, and says plainly that no gate can catch it.

`test_no_commit_is_ever_skipped` is re-expressed at step granularity, where it
can actually fail, and its red against the unmodified tree is recorded in the
spec. The union of the ranges was never the property: a job-level anchor keeps
that union whole while the gate is skipped on every push.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
Keeps the branch fast-forwardable and lets the diff-scoped trailer gates
actually run: preflight SKIPS them while the branch is behind, and exits 0
while doing it.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ust softened, and says so

`.agents/issue-index.md` is append-only, so the row filed for #1773 cannot be
edited to match §3.5's softened wording about `commit_title`. Naming that in the
spec keeps the two readable together instead of looking like a contradiction
nobody noticed.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-bot localai-bot changed the title fix(GATE-ANCHOR-PER-JOB): each diff gate anchors on its own last verdict, so a red main can return to green (#1773) fix(GATE-ANCHOR-PER-JOB): each diff gate anchors on its own last STEP verdict, so a red main can return to green (#1773) Aug 23, 2026
…use the bytecode cache survived a same-length mutation

M4 swaps `subject` for `message`. The two words are the same length, so the
mutant and the original have identical file sizes, and the restore landed in the
same second as the mutation. Python validates a cached `.pyc` on source mtime and
size alone, so it served the mutant's bytecode from a tree whose sha256 and
`git status` both said clean. Hash the source and discard the cache; neither
check catches this on its own.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
mudler added 6 commits August 23, 2026 11:52
…adable runs payload (#1773)

The second fresh review on #1776 confirmed the step-granularity design by
mutation and failed the pull request on the guards around it. This commit
carries the three executable repairs.

`test_every_gate_step_SKIPS_rather_than_narrows` asserted four SUBSTRINGS and
never resolved the expression. Appending `|| true` to all five gate-step guards
leaves every asserted substring byte-for-byte intact and turns the whole
conjunction into the constant `true`: the gate then runs on a degraded read,
`GATE_ANCHOR` is empty, `base` falls back to `$PUSH_BASE`, a one-push range
passes, the step CONCLUDES, and the anchor advances past the entire span the
narrowing dropped. That mutation reported `Ran 99 / OK`. The test now RESOLVES
the guard over all sixteen states it has to decide, using the evaluator this
file already carried for concurrency keys; `_Expression` gained unary `!` and
the status-function call form so `!cancelled()` evaluates rather than being
read. The same mutation now reports 75 failures across all five steps.

`push_runs` reported an unreadable forge as a clean absence. It returned `[]`
for anything that was not a dict carrying `workflow_runs`, where `jobs_for` has
always answered `REMOTE_UNVERIFIED` for the same case, and the difference is not
cosmetic: rc 1 makes `ci.yml` fall back to `$PUSH_BASE` and narrow, where rc 3
makes it SKIP. Driving the real `main` through a fake `gh_api` measured a list
payload, a null payload and `{"message": "Not Found"}` all exiting 1. And
nothing executed `push_runs` at all -- every test replaced it with a stand-in
and `gh_api` appeared in no test, which is the stand-in shape one layer down
from the shell shim. `PushRunsPayloadTests` drives the whole chain over the four
shapes, plus a genuinely empty window that must stay rc 1 and a readable window
that must stay rc 0.

`test_one_diff_scoped_checker_per_gate_step` did not hold the property the spec
claimed for it. It read the first `python3 <arg>`, so a fallible non-`python3`
command inserted before the gate was invisible, and `if not checkers: continue`
exempted the one gate step whose whole body is an inline shell gate. A `git
fetch` planted in either place reported `Ran 99 / OK`. Under `set -eu` that
command aborts the step, GitHub concludes it `failure`, and `steps_concluded`
reads the conclusion as a verdict about a gate that never ran.
`test_nothing_fallible_PRECEDES_the_gate_in_its_own_body` resolves the body
instead: the range prelude may use `set`, `[`, `echo` and nothing else, the
first command that is not one of those is the GATE, and it must consume the
range. Both mutations now red, and the inline-shell step is in the population.

`resolve_gate_anchor` could also floor onto the run being pushed. With one run
in the window `runs[-1]` is `runs[0]`, so the anchor was the head being pushed
and `base..head` was EMPTY -- a gate reporting success over no commits and
concluding on it, which advanced its own anchor. Latent here and reachable on a
fork's first push. There is no floor to name with a single run, so the answer is
the clean absence. The residual short-history bound is asserted rather than
denied: below `window + 1` runs the oldest available run is itself a candidate
and its own commit falls outside the range it bases, and no payload field can
repair that.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…corrected before they land (#1773)

The second review found four records stating things this row retracted or
invalidated. Three are on `main` already and one would have landed with this
pull request; that last one is why the correction cannot wait.

`.agents/issue-index.md`'s row is ADDED by this pull request and stated as fact
both claims the pull request had softened: that all five squashes lack `(#N)`
BECAUSE the merger supplied an explicit `commit_title`, and that each job
anchors on the last push run in which THAT JOB concluded. The first is inferred
and not measured -- the pulls endpoint keeps no merge-input payload, and all
five were merged by the same account that appends `(#N)` correctly everywhere
else. The second is the job-level rule the review refuted and this row replaced
with step granularity. The spec's reason for leaving the row alone was wrong:
the row has not landed, and `scripts/check-issue-index-append-only.py` diffs the
merge base against the head, so amending a row this branch also added stays a
pure addition -- `git diff --numstat` reads `2 0` and the checker returns `OK`.
`squash_merge_commit_message = PR_BODY` means exactly one version of that row
will ever exist on `main`, so this is the last moment it can be right.

`scripts/check-commit-trailers.py` argued its landed exceptions from a property
this row removes: "`LAST_GREEN` advances only on a GREEN run, so a range
containing an unrepairable red is re-walked ... forever." The anchor now
advances on a CONCLUDED run, failure included. The exceptions are still
necessary and the old argument is quoted in place so the next reader does not
re-derive a property the tree no longer has. #1262 is the live instance.

`.github/workflows/ci.yml`'s concurrency prose still said the gates walk from
the last SUCCESSFULLY gated commit via `last-gated-commit`. Both halves are now
false: the base advances past a concluded step, and `last-gated-commit` resolves
nothing and keeps only the closed-pull-request guard.
`.agents/specs/ci-concurrency.md` carried the superseded rule as live design and
cited `test_every_diff_scoped_step_bases_on_the_last_gated_commit`, a symbol
`26def4c8f` renamed away inside this same pull request.

That last one is a gap in the tooling, filed as #1787 rather than fixed here.
`scripts/check-symbol-anchors.py` resolves a `path::Symbol` citation; a bare
backticked symbol name has no path and is outside its population, so the rename
left a document naming a symbol that does not exist and every gate stayed green.
Teaching it to resolve a bare identifier changes checker semantics and needs its
own row, spec and red-before evidence.

The spec also gains the two numbers the review corrected: §4.2's pre-fix
narration was off by one, because an exclusive base puts runs 1 and 2 outside
the range at 21 pushes rather than run 1 alone, and §4.3 item 3 claimed a test
held a property it did not.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…g reproduced before it was repaired (#1773)

The measurements the two commits before this one rest on, written down where a
reviewer can re-run them.

Each of the five reproductions is the mutation or construction that made the
finding real on the tree as the review left it: `|| true` on all five guards
reporting `Ran 99 / OK`, three unreadable payloads exiting 1 through the real
`main`, a `git fetch` planted before the gate in each of the two step shapes,
and a test asserting the empty range its own docstring warned about. Beside each
is what the same input does now.

The earlier round's mutations are re-run on the repaired tree rather than
assumed to still hold: M7 gives 5 failures, M9 gives 5, M10 gives 1.

Every mutation was hashed first, printed with `git diff --stat`, parsed with
PyYAML or `ast.parse` so a syntax error could not read as a passing test, and
restored against the hash with `sha256sum -c`, an empty `git status
--porcelain`, an empty `git diff` and a cleared `__pycache__` under
`PYTHONDONTWRITEBYTECODE=1`. The cache step is here because the round before this
one measured a restored tree still running the mutant's bytecode.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…tray blank line

`.agents/style/prose.md:136` refuses em dashes and binds new prose. This round's
additions to the spec used ten, where the rest of that file uses `--`. Only the
lines this round added are rewritten; the file's existing prose is left alone,
because rewriting a file to satisfy a style rule is out of scope unless a row
asks for it.

`34af7b221` also left three consecutive blank lines in the test file's `job`
payload builder where PEP 8 wants two.

No behaviour and no assertion changes. `tests/scripts/test_main_baseline.py`
reports `Ran 109 tests`, `OK`.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…whatever binds it (#1773)

The third fresh review on #1776 confirmed the TREE as correct: none of the five
gate bodies carries a hazardous shape. It failed the pull request because a test
and a record assert a completeness they do not hold. This commit carries the
executable half.

`test_nothing_fallible_PRECEDES_the_gate_in_its_own_body` stated one residual
and had three escapes past it. `commands_in` returned on any `name=...` head
BEFORE the `$(`/backtick refusal ran, so a binding that carried a substitution
was never examined; and `[` was allowlisted on the reasoning that "its failure
the surrounding `if`/`||` consumes", while the parser splits on `||` and
discards it, so nothing checked that a given `[` has one. Four injections
immediately before `check-now-current.py` each reported `Ran 109 / OK`:
`base="$(git rev-parse --verify "${base}^{commit}")"`,
`_p="$(git fetch -q origin nope)"`, its backtick spelling, and
`[ -f .git/NO_SUCH ]`. Executed under `bash` with `python3` shimmed, the first
three abort the step at rc 128 and the last at rc 1, with the gate invoked zero
times. GitHub then concludes the step `failure`, `steps_concluded` reads that as
a verdict, and the anchor advances over a range the gate never walked.

The first of those is the one to worry about. Normalising the anchor to a full
commit id right before the gate is the natural next edit in that step, and it is
harmless while the anchor resolves and aborts at 128 once it does not, which is
the shallow fetch, the rewritten branch, and the floor SHA aged out of the
window.

`commands_in` becomes `statements_in`. It returns bindings as statements instead
of dropping them, and it keeps the operator that follows each piece, because
whether a failure aborts the step depends on what comes after it. The
substitution refusal now covers every statement before the gate, and a `[` is
admitted only where an `if`/`while` header or an `||`/`&&` reads its status. The
repair discriminates rather than banning: `[ -f .git/NO_SUCH ] || echo missing`,
an `if` header form, and a plain binding all stay green, and the shell agrees
with the test on each. The nine shapes the previous round caught still red. Two
residuals are now stated on the test instead of overstated: a statement that
itself references `$base` is not distinguishable from the gate, and the
allowlist remains a judgement about `set`, `echo` and `:` rather than a proof.

`baseline_runs` reported an unreadable forge to a human as a clean absence.
Nothing had ever executed it, because the two tests that name it replace
`collect` one layer above. It skipped any payload that was not a dict and took
`workflow_runs` from any dict that was one, so a list, a null and GitHub's
`{"message": "Not Found"}` each returned `([], None)`, which `render` prints as
"No completed baseline run found on main." with an instruction to trigger the
first run. The rc was never wrong and this is the advisory lane rather than a
gate, so no consumer read it as a pass; what was wrong is that unknown was
reported as absence. `jobs_for`, one function above, already refuses exactly
these shapes. `BaselineRunsPayloadTests` executes the reader for the first time,
through real `main` and real `render`, and holds the other half too: a genuinely
empty window still reads as an absence.

`test_the_checker_resolves_no_ref_to_decide_arrival` forbade `origin/`,
`ls-remote` and `for-each-ref` in the raw source, which forbids them in English
as well. A comment explaining that this checker does not resolve `origin/main`
reddened it with a message about a ref lookup that was not there, and the
message was the entire file. `executable_source` blanks the two prose surfaces,
comments and docstrings, and deliberately keeps string literals, because a real
lookup is spelled as one. Verified both ways at module level: the two comment
shapes pass, and three planted lookups still fail. The failure now names the
line.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…sm this spec retracts (#1773)

Round 2 softened the `.agents/issue-index.md` row and this spec to say that the
`commit_title` mechanism behind the five unnumbered squashes is INFERRED and
cannot be measured, because the pulls endpoint keeps no merge-input payload.
`AGENTS.md` kept the unhedged version, and it is the one file every agent loads
automatically. It asserted that an explicit `commit_title` suppressed the
append, that "Five commits from 2026-08-18 are that mistake", and that because
"All five of those merges were performed by the automation `localai-bot`" the
place to comply is that automation. A reader takes the strongest of two
disagreeing records, so this would be quoted forward as measured, with a named
account attached to an unproven cause.

The RULE is untouched and still one flag: do not pass `--subject` to
`gh pr merge --squash`, do not send `commit_title` to the merge endpoint, and
the default title wins. It is worth writing whatever suppressed the append,
which is now said in place of the causal claim. What is hedged is the mechanism
and the account: `localai-bot` appends the number correctly on every other
squash it lands, so that account is not an explanation on its own, and its
automation is one place to check rather than a known cause. The rule still binds
whoever performs the merge, because the merging side chooses the title.

Four record corrections ride with it. §8 said "four gate-step guards" where the
same section says five; measured from `ci.yml` it is five, one in `agent-record`
and two each in `documentation-checkpoint` and `commit-protocol-tag`. The
records paragraph still carried the first round's `1 0` and 637 index rows,
which round 2's #1787 row made stale; the branch stands at `2 0` and 638, both
rows additions and both last in the file. §4.3 item 3 restated the completeness
that the third review then broke, and now describes what is actually held. And
24 added lines in this file carried em dashes, which `.agents/style/prose.md`
forbids and which bind new prose; the file is now at 0, as `AGENTS.md` already
was.

§8 gains the third round's evidence and §11 the review itself, including one
judgement recorded rather than left silent: F-D was fixed in flow instead of
being owed, because it is the same class as F2, in the same file, and it changes
no checker semantics.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants