Main's PR #287 added a workflow-level defaults shell, and the refusal made every
suite read as unrun. The two cases that pinned it are deleted; one shows a step
under workflow and job defaults is counted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
The gate split on \n alone while YAML also breaks at U+0085, U+2028 and
U+2029, so an if: after one was invisible and the gate exited 0. Every line,
comments and block-scalar bodies included, is now checked for C0 (but tab),
DEL, C1 and U+2028/2029. CR is folded by the one read path, read_workflow, and
file-based controls pin that. Argument: context/code/tooling.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
A multi-line flow value (- name: [a, "b ... run: ... c: x"]) is one string to
go-yaml, PyYAML and Psych, but the gate counted the inner run:. Rather than
refuse one more construct, values are now an allowlist: empty, a block header,
a closed quote, a one-line flow with no quotes or nesting, or a plain scalar
not starting with an indicator. The real ci.yml needs nothing else.
Argument: context/code/tooling.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
The tracker closed a multi-line quote on an escaped \" or a doubled '', so a
later quote reopened it over a job's real if:. Both styles exited 0 on the real
ci.yml. The opening line is refused instead; _opens_quote's escape handling
is now load-bearing and has a case per style. Argument: context/code/tooling.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
A job header with a trailing comment was accepted by the readability check
(comment-stripped line) and never opened (raw line), so its if: went to the
previous job and read_graph never listed it. The audit found the stripper also
cut a quoted "x # y" open, hiding a later job-level if:. Both exited 0 on the
real ci.yml. Refusing the comment makes stripped == raw on every accepted
line, so the stripper is deleted. Argument: context/code/tooling.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
w0j4 read any line deeper than 4 outside steps:/needs: as content, so a
needed job whose own keys sat at indent 5 or 6 (valid YAML) had its if:
and needs: missed and the gate exited 0. A deeper line is now content
only once its job has a 4-space key. tooling.md names the three valid
styles the gate refuses loudly.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
Quoted, anchored and multi-line needs: values, and quoted, spaced or
explicit ? keys, all skipped kb on a PR while the gate reported every
suite run. Rather than parse more YAML, every structural line inside
jobs: must take one plain form or the gate names it and fails. Also a
1be2 case at the one-column boundary, which the owner+1 rival passed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
A plain scalar folds a deeper-indented next line into run:, so
`|| true` there made the counted call fail-open (improvise-1be2).
A job that needs a job under if: is skipped with it, directly or
transitively (improvise-bbwa). The unrun message now points at the
ci_suites row. tooling.md names the checkout-with-ref hole.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
improvise-st8q, improvise-tvte, from the #228 re-review at c1417359. The
maax parser still failed open. A bare checkers.sh inside a heredoc or a
folded run: value counted as every suite, and step and job if: were
ignored. Every round of parsing met another shell or YAML form, so the
parsing is gone.
checkers.sh holds a job-to-suites table (ci_suites), and each CI job
makes one call, `checkers.sh --ci <job>`. check-ci-graph.py counts a step
only when all of these hold: it sits inside steps: at step-key
indentation, its whole run: value is that call, the call names its own
job, and no if:, continue-on-error:, shell: or defaults: covers it. Every
other mention of scripts/checkers.sh is reported. A key with an inline
value owns deeper lines, and an open quote owns lines until it closes, so
heredocs, folds and quoted values are content, never structure.
There are 19 suite self-test cases and 7 rivals, each failing a case only
it can. The reviewer's heredoc, fold and if: attacks on the real ci.yml
now exit 1. checkers-test.sh gains five --ci cases. context/code/tooling.md
corrects two overstated claims ("--would-run anywhere is a usage error",
"fails closed").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
The #228 review and Codex's P1 found the suite check failing open. A bare
scripts/checkers.sh after an inline comment, in a step name or as echo's
argument read as "every suite runs". --list alone read as every suite, and
--self-test's discarded names, and --list's, counted as run.
checkers.sh gains --would-run, honoured only as the first argument. It
reports what the rest of the arguments would run, from the real argument
loop and name resolution. check-ci-graph.py only finds invocations: in run:
text, comments stripped by bash's rule, command position, every command on
the line, quotes removed. It then asks --would-run about each. An
unparseable line contributes nothing and fails closed.
16 suite self-test cases, 8 of which the old parser gets wrong. Six
rivals each fail a case only they can. checkers-test.sh gains four
--would-run cases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
improvise-dak5: ci.yml names its checker suites by hand, so a new
scripts/X-test.sh ran locally and never in CI. check-ci-graph.py now
asks `checkers.sh --list` for the suites, so the naming rule keeps one
home, and reads every checkers.sh invocation in ci.yml. A bare run
covers every suite, --self-test covers `checkers`, and a YAML comment is
never a run.
A suite planted in the real tree fails the check, naming it, and adding
it to the step clears it. Seven new self-test cases. Rivals: counting
comments fails the comment case; answering "everything runs" fails five.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
Both blockers from the adversarial review of #83, both in the mode this PR
rewrote, both producing a pass while the gate did not do what its prose said.
1. cargo_test_names reduced every listed path to its last segment. Six leaf
names collide in this workspace and two are cited in evidence, so a heading was
satisfiable by a test in another module answering another question -- renaming
ui::project::tests::a_load_that_fails_is_not_a_rebase, the one its entry
describes, left the checker at exit 0 with the ui::host namesake answering for
it. Paths are kept whole; a bare leaf resolves only when unique, and an
ambiguous one is REFUSED with both candidates named. _RUST_NAME accepts `::`, so
a qualified heading is a claim rather than prose, and a citation is matched on
the leaf so qualifying a heading cannot orphan its own test. The two colliding
entries are qualified. Neither half suffices alone: rename one of a colliding
pair and the leaf becomes unique again, which is why qualification is the fix
and the refusal is what forces it.
2. --rust-tests called cargo_test_names with one argument, so the --ignored
subtraction never ran, and it printed a byte-identical success line. It now
requires --rust-tests-ignored (one alone exits 2) and the line names its source.
Also, from the same review: a missing context/test-evidence/ exited 0 while 170
citations pointed at nothing (early return, now gone); the doc-site "runs last"
check accepted a trailing `echo` that merely NAMED the generator, so it asks for
an invocation; bundle_freedom gains an arm for a transitive `import "../app.js"`,
which reaches the bundle while naming nothing forbidden; and two prose overclaims
are corrected -- the prose-heading count is a convenience rather than a guard
(git diff is the mechanism), and the ratio rule says "above the run MEDIAN",
which is defensible where "above the others" is false against second place.
scripts/check-ci-graph.py makes claim 6's rule a gate: pages-deploy must `needs`
every other job in ci.yml. The rule was stated in the last commit and a
zz-new-check: job added during review was still ungated with nothing noticing.
README now states the rule and lists no jobs, the second copy being what went
stale twice.
Every fault re-injected and observed; figures in context/test-evidence/tooling.md
rather than only here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH