Skip to content

check-shard-attestation.mjs misclassifies a shard job as an aggregate gate on a bare --verify substring anywhere in its run: text (false red, measured) #6589

Description

@os-project-manager

Found while implementing #6195 (PR against .github/workflows/ci.yml). Out of that issue's scope, so filed separately and unassigned per Prime Directive #10.

The defect

scripts/check-shard-attestation.mjs, the static drift guard, decides whether a job is an aggregate gate with a two-substring test over the job's joined run: text:

const text = runTextOf(job);
if (!text.includes(SCRIPT_BASENAME) || !text.includes('--verify')) continue;

SCRIPT_BASENAME is check-shard-attestation.mjs. The problem is that every attesting shard job already contains that string — it is the job's own --emit step at the bottom (node scripts/check-shard-attestation.mjs --emit ...). So for a shard job the sole discriminator is the bare substring for the script's own verify flag, appearing anywhere in any run: step of that job — including inside a shell comment, since comments are part of run:.

How it fires (measured, not hypothesised)

Adding an ordinary git rev-parse strict-existence check to the Compute this shard's package set step of ci.yml — the idiomatic spelling for "does this ref resolve" — flipped job test from attesting shard job to aggregate gate, and the guard went red with two complaints that name nothing to do with the edit:

✗ check-shard-attestation — 2 problem(s) in .github/workflows/ci.yml

  • job 'test' is an aggregate gate but its job-level if: is "${{ !cancelled() && needs.filter.outputs.core != 'false' }}", not always() — a gate that skips publishes no required context (#3622).
  • gate 'test' never downloads the shard attestations it claims to count.

Baseline check, both directions, same tree: origin/main's ci.yml exits 0; the same file plus that one flag exits 1. Removing only the flag restores green.

The diagnostic is actively misleading. Neither message mentions the classifier, the flag, or the step that was actually edited, and both describe the shard job as something it is not. The natural response to "job 'test' is an aggregate gate but its if: is not always()" is to go change the shard job's if: — which would be wrong, and would break #3622/#4928.

There is a second-order trap on top: documenting the collision in a comment re-triggers it, because the comment lives in run:. That cost a second lap here — the same shape as #4890, where a raw NUL landed in SKILL.md while writing the no-raw-NUL rule.

Why this is worth fixing rather than routing around

PR for #6195 routes around it (git cat-file -e, with the reason written next to it), so nothing is on fire. But the trap stays armed for the whole --verify-shaped family: git rev-parse --verify, git show-ref --verify, gpg --verify, npm audit --verify, or any prose mentioning the flag. The next person to write one in a shard job gets a red naming the wrong job and the wrong property, on a workflow file where the gate's whole purpose is to be trustworthy.

Suggested direction (not a ruling — triage's call)

Require adjacency instead of co-occurrence, so the classifier keys on the actual invocation rather than on two independently-occurring substrings, e.g. matching check-shard-attestation.mjs followed by the verify flag in the same command (a regex over SCRIPT_BASENAME\s+.*--verify on a per-step basis, or per-line). The --emit side already works this way in spirit — emit.run.includes(SCRIPT_BASENAME) && emit.run.includes('--emit') is scoped to a single step's run, not to the job's joined text, which is exactly why the emit side does not have this bug.

Whatever shape is chosen, the self-test should gain a fixture with a shard job whose steps contain the verify flag for an unrelated reason, asserting it is still classified as an attester. Without that fixture the fix can regress silently.

Scope note

Not fixed in the #6195 PR: that PR's dispatch ruling scopes it to the Compute this shard's package set step of ci.yml, and this is a defect in scripts/check-shard-attestation.mjs.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions