fix(devx): classify shard-attestation jobs by invocation, not by co-occurring substrings (#6589) - #6707
Merged
os-project-manager merged 1 commit intoAug 8, 2026
Conversation
…ccurring substrings (#6589) `scanWorkflow` decided a job was an aggregate gate from `text.includes(SCRIPT_BASENAME) && text.includes('--verify')` over the job's JOINED `run:` text. Every attesting shard job already contains the basename (its own `--emit` step), so the sole discriminator was a bare `--verify` substring anywhere in any step — a `git rev-parse --verify` reclassified job `test` as a gate and produced two complaints about gate properties a shard job does not owe, pointing the reader at the #3622/#4928 contracts to break. Documenting the collision in a `run:` comment re-armed it (#4890's shape). The classification now asks whether the flag is an ARGUMENT of a command that runs this script (`invokesScript`), over a small quote-aware lexer that joins backslash-newline continuations, drops shell comments and splits at `; & | && ||`. The same predicate replaces the two other substring tests in the same function (`emitsHere`, "publishes an attestation that no gate counts"). Self-test: +3 workflow fixtures asserting a shard job that spells the flag for an unrelated reason stays an attester (including one whose `run:` comment spells the whole invocation), the `--emit` twin, and the positive limb — a gate that no longer invokes the script stops being a gate. `scanWorkflow` now reports `gateIds`/`attesterIds` so the classification is asserted directly, and `fixture()` fails by name when its ci.yml anchor goes stale instead of silently judging the pristine workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-project-manager
marked this pull request as ready for review
August 8, 2026 13:13
os-project-manager
enabled auto-merge
August 8, 2026 13:13
os-project-manager
deleted the
claude/issue-6589-shard-attestation-classifier
branch
August 8, 2026 13:49
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.
Fixes #6589
The defect
The static drift guard in
scripts/check-shard-attestation.mjsdecided whether a job was an aggregate gate from two substrings CO-OCCURRING in the job's JOINEDrun:text:The problem is that every attesting shard job already contains
check-shard-attestation.mjs— its own--emitstep at the bottom. So the sole discriminator was the bare substring--verifyappearing anywhere in anyrun:step of that job, including inside a shell comment, since comments are part ofrun:.The same function carried a second instance of the family: "publishes an attestation that no gate counts" tested
runTextOf(job).includes('--emit'), which does not even require the script's name.Reproduction (both directions, same tree, only the script swapped)
Adding the idiomatic strict ref-existence check
git rev-parse --verifyto thetestshard job, then running the same input against each version of the script.origin/main's script:This PR's script, same input:
The misleading diagnostic is the heart of the issue: neither message names the classifier or the flag. They say the shard job lacks two properties that only an aggregate gate owes. Acting on them means editing the shard job's
if:— which breaks #3622 / #4928.The fix: adjacency, not co-occurrence
New
invokesScript(runText, flag): the flag must be an ARGUMENT of a command that runs this script. Underneath it is a small quote-aware lexer,shellCommands(), which#or a|inside an argument stays an argument instead of truncating the command — a false negative in that direction would quietly disable the gate, which is worse than the false positive this issue is about;;&&&|||and newline.The same predicate replaces the two other substring tests in that function (
emitsHere, and the--emitsite above), so all three now ask one question. The--emitside was already scoped per STEP, which is why it never had this issue's bug; it is now scoped per INVOCATION, one notch tighter.scanWorkflow()additionally returnsgateIds/attesterIds, so the classification itself can be asserted rather than inferred from the absence of a problem — #6589's misclassification produced problems that named neither the classifier nor the flag, which is exactly why inferring is unreliable here.Self-test (
--self-test)21 new assertions (92, up from 71), all named, all with guarded bodies — nothing stack-traces under ablation.
The mandatory regression fixtures — a shard job that spells the flag for an UNRELATED reason must still be classified as an attester:
git rev-parse --verify "$GITHUB_SHA^{commit}"(the exact edit that was measured red)git show-ref \plus a continuation line--verify refs/heads/main(the flag on ANOTHER program's continuation line)run:comment spelling the whole invocation — the second-order trap goes straight into a fixture, proving it is safe by constructionThe positive limb — a real aggregate gate must still read as a gate: changing
test-gate'scheck-shard-attestation.mjs --verifyto--verify-nothingstops that job being a gate, and the required-context check goes red.The
--emittwin — another program's--emitin a job no gate counts (esbuild src/index.ts --emit) leaves the guard green.A no-op guard on the fixtures themselves — every fixture anchor is a literal lifted out of ci.yml, and a
String.replacethat matches nothing returns the input unchanged. The green-expecting assertions would then pass permanently and silently for having judged an unmodified workflow. A no-op mutation is now a named failure of its own, and the label says which anchor died.Reverse verification
Putting the deleted co-occurrence test back (
text.includes(SCRIPT_BASENAME) && text.includes(…)) turns the self-test red with 8 named failures, whose text reproduces the two misleading diagnostics verbatim:The two positive-limb assertions go red under the same ablation, and the reason is worth stating on its own: the old substring test could not even stop
--verify-nothing— that string CONTAINS the substring, sotest-gatestill read as a gate in the ablated version. The old test could not tell a flag from another flag's prefix.Ablating only the
--emitsite reds exactly one fixture, the twin:Scope
scripts/check-shard-attestation.mjsonly (implementation plus self-test fixtures)..github/workflows/ci.ymlis byte-for-byte untouched: #6195'sgit cat-file -eworkaround, and the comment next to it explaining why, both stay exactly as they are. Whether that step should return to the idiomatic spelling now that the classifier is fixed is a separate question and is not part of this PR.No changeset: this is a repo-internal CI guard script that releases nothing, hence
skip-changeset.Generated by Claude Code