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
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)#1776localai-bot wants to merge 14 commits into
localai-bot wants to merge 14 commits into
Conversation
…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]
This was referenced Aug 23, 2026
… 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]
…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]
…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]
This was referenced Aug 23, 2026
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.
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 thingscripts/main-baseline.pyhas said since #274 must never be read, and it iswrong in both directions: it reads success over a red
continue-on-errorjob,and
cancelledover a job that ran to completion. Because a successful run wasexactly what a red gate blocked, the anchor froze while the range widened on
every push, re-flagging the commits blocking it. Measured at
21abaf169: 484commits, 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 theresolver was cancelled, the shared string rendered empty, and
documentation-checkpointfell back toPUSH_BASEand passed over one push,the same gate that fails over 484 commits whenever the resolver survives. The
verdict on
mainwas 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
failurethe moment any step fails and marks every remaining stepskipped, so "the job concluded" readsfailureover a gate that refused therange and over a gate that never executed, and cannot tell the two apart. Both
agent-recordandcommit-protocol-tagplace their diff-scoped gate afterother steps.
Measured on
commit-protocol-tag, where the strict trailer walk runs nowhereelse on the push lane:
038ff61e51a1d17e536354755bab508cbce666d1b0a90a4f2a95851fdd3e26dA job-level anchor walks
038ff61e5..a4f2a9585, six commits, straight past.agent-recordis twelve commits on the same shape, rescued only becausedocumentation-checkpointruns the same checker, which is luck rather thanconstruction.
Three things enforce the step as the unit.
--gate-stepis required and theflag-less form exits 2, so the hole cannot be re-created by omitting a flag.
steps_concludedreadssteps[].conclusionand treats an absent step as noverdict. 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-checkpointran twodiff-scoped checkers in ONE step under
set -eu, where the first one's failureaborted 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 + 1runs, whichcosts 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]ISruns[0], so the anchor resolved to the head being pushedand
base..headwas EMPTY: a gate reporting success over no commits at all, andCONCLUDING 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 + 1runs the off-by-one is not repairable at all -- the oldest availablerun is itself a candidate, and a
workflow_runobject carrieshead_shaand noparent -- so that residual is asserted by a test rather than implied away.
A degraded query was swallowed by
|| true, leaving an empty anchor that thegate silently replaced with
PUSH_BASE. Under a step-level anchor that is notmerely dishonest but a hole, because the narrowed pass advances the anchor past
everything the narrowing dropped.
--gate-anchornow exits 3 onREMOTE_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_runsbroke 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, wherejobs_forhas always answeredREMOTE_UNVERIFIED for the same case. Driving the real
mainthrough a fakegh_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.pyreads noref: for a single-parent commit
arrives_via_row_pris commit-message text andnothing 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 othersquash.
check-role-discipline.pyis 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 ADDEDby this pull request and stated both retracted claims as fact: the inferred
commit_titlemechanism, and the job-level anchor the review refuted. The spec'sfirst reason for leaving it was wrong -- the row has not landed, and
scripts/check-issue-index-append-only.pydiffs the merge base against the head,so amending a row this branch also added stays a pure addition. With
squash_merge_commit_message = PR_BODYexactly one version of that row will everexist 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|| trueto all five leaves every one of them byte-for-byte intact, turns thewhole conjunction into the constant
true, and restores the degraded-readnarrowing in full -- 99 tests, OK,
All gates green., no skip. It now resolvesthe 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 fetchplanted beforecheck-now-current.pywas invisible, andif not checkers: continueexempted the one gate step whose whole body is aninline shell gate. Both mutations reported 99 tests, OK. Under
set -euthatcommand aborts the step, the step concludes
failure, andsteps_concludedreads 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,[,echoand:, the first command that is not one of those IS thegate, 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 neverexamined; and
[was allowlisted on the reasoning that itsif/||consumesthe failure, while the parser splits on
||and discards it, so nothing checkedthat a given
[HAS one. Four injections immediately beforecheck-now-current.py, each executed underbashwithpython3shimmed:set -eubase="$(git rev-parse --verify "${base}^{commit}")"Ran 109,OK_p="$(git fetch -q origin nope)"Ran 109,OK_p=`git fetch -q origin nope`Ran 109,OK[ -f .git/NO_SUCH ]Ran 109,OKThe 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
failureover a gate that neverexecuted, which is the defect this test exists to close.
commands_inbecomesstatements_in. It returns bindings as statements insteadof 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 anif/whileheader or an||/&&reads its status. It discriminates ratherthan banning:
[ -f .git/NO_SUCH ] || echo missing, anifheader form and aplain 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_forbeside it alreadyrefused those shapes.
test_the_checker_resolves_no_ref_to_decide_arrivalforbade
origin/,ls-remoteandfor-each-refin the RAW source, so a futurecomment 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.ymlit is five.Evidence
test_no_commit_is_ever_skippedis re-expressed at the granularity that canactually 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.pyreportsRan 111 tests,OK, up from 82, then 99, then 109.tests/scripts/test_check_role_discipline.pyisRan 22 tests,OK.scripts/agent-preflight.shreportsAll 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 forci.yml,ast.parsefor 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 emptygit diff, and__pycache__cleared underPYTHONDONTWRITEBYTECODE=1on both sides. That laststep 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 theprevious round kept running the mutant's bytecode from a provably clean tree.
Live anchors on
origin/mainatc98ffd4d0:commit-protocol-tagresolves averdict anchor at
21abaf169, whileagent-recordanddocumentation-checkpointfall back to the window floor08c81a892.ci.ymlparses under PyYAML with an explicit duplicate-key scan reporting 0, and a
job-by-job comparison against
origin/mainreports 17 jobs on both sides, noneadded, none dropped, no job-level
if:and noneeds:changed.git diff --numstat origin/main -- .agents/issue-index.mdis2 0, andscripts/check-issue-index-append-only.pyreturnsOK.One gap is filed rather than repaired here.
scripts/check-symbol-anchors.pyresolves a
path::Symbolcitation, so a document that cites a symbol as a barebackticked name with no path is outside its population -- which is why this pull
request's own rename in
26def4c8fleft.agents/specs/ci-concurrency.mdnaming 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-checkpointitem is what thisaddresses, 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]