What
ci.yml's changes job classifies a diff as code-vs-artifacts with this allow-list:
allow='^(artifacts/.*\.ya?ml|safety/.*|docs/.*|[^/]*\.md|.*/.*\.md)$'
Root-level rivet.yaml does not match any alternative — [^/]*\.md covers only root markdown. Since "any path outside the allow-list makes the WHOLE diff code", a PR that edits nothing but rivet artifacts plus rivet.yaml is classified code=true and runs Clippy, Test, Miri, Proptest, Fuzz smoke, Code Coverage and Mutation Testing.
Evidence
PR #374 (fix(artifacts): move 276 statuses onto rivet's lifecycle vocabulary) changes 8 files, all artifact-plane:
artifacts/architecture.yaml <- allow-listed
artifacts/requirements.yaml <- allow-listed
artifacts/verification.yaml <- allow-listed
rivet.yaml <- NOT allow-listed <<<
safety/stpa/rendering-analysis.yaml <- allow-listed
safety/stpa/requirements.yaml <- allow-listed
safety/stpa/solver-requirements.yaml <- allow-listed
safety/stpa/validation.yaml <- allow-listed
One unmatched path ⇒ full suite. Measured cost of the single worst job: Mutation Testing over the last 40 CI runs, successful completions only — n=15, min 35.2 min, median 68.9 min, max 118.4 min. It also requires a lean-mem runner, and only pulseengine-ci-01-{1,2,3,4} ever serve it — so a pure-manifest PR occupies 1 of 4 scarce runners for ~an hour.
The workflow already contradicts itself. ci.yml:382 describes the artifact plane as:
# in artifacts/, safety/stpa/, and rivet.yaml.
so rivet.yaml is understood to be artifact-plane by the rivet-validate job, while the allow-list 300 lines earlier treats it as code.
Why the fix is sound
The allow-list must only admit paths that cannot affect a mutant, a fuzz target, or a Miri run. For rivet.yaml:
- Zero Rust sources reference it —
grep -rn "rivet\.yaml" --include="*.rs" . ⇒ 0 files.
- Repo-wide, it is referenced only by
AGENTS.md, two docs/plans/*.md, and ci.yml itself.
- It is consumed by the external
rivet binary in Rivet validate (artifacts), which deliberately has no changes gate and therefore still runs on every PR. So the check that actually validates rivet.yaml keeps running; only the jobs that cannot observe it get skipped.
Suggested change
Add rivet.yaml to the allow-list, e.g.
allow='^(artifacts/.*\.ya?ml|safety/.*|docs/.*|rivet\.yaml|[^/]*\.md|.*/.*\.md)$'
and add a fixture asserting a diff of rivet.yaml alone classifies as code=false, so the constant is tested directly rather than via a glob the test rebuilds.
Related
Same failure shape as the *.yml-glob defect fixed in #372's PR: a pattern that omits a real case, where the omission is silent and biases toward "do the expensive/unsafe thing". Here the bias is fail-safe (over-runs rather than under-runs), so it costs runner-hours rather than correctness — which is why it survived unnoticed.
Not fixed inline right now deliberately: the fix touches ci.yml (hence itself needs a full suite), and main has required_status_checks.strict = true with 7 PRs already queued in a serial merge chain — landing an 8th mid-chain costs more than it saves today.
What
ci.yml'schangesjob classifies a diff as code-vs-artifacts with this allow-list:Root-level
rivet.yamldoes not match any alternative —[^/]*\.mdcovers only root markdown. Since "any path outside the allow-list makes the WHOLE diff code", a PR that edits nothing but rivet artifacts plusrivet.yamlis classifiedcode=trueand runs Clippy, Test, Miri, Proptest, Fuzz smoke, Code Coverage and Mutation Testing.Evidence
PR #374 (
fix(artifacts): move 276 statuses onto rivet's lifecycle vocabulary) changes 8 files, all artifact-plane:One unmatched path ⇒ full suite. Measured cost of the single worst job:
Mutation Testingover the last 40 CI runs, successful completions only — n=15, min 35.2 min, median 68.9 min, max 118.4 min. It also requires alean-memrunner, and onlypulseengine-ci-01-{1,2,3,4}ever serve it — so a pure-manifest PR occupies 1 of 4 scarce runners for ~an hour.The workflow already contradicts itself.
ci.yml:382describes the artifact plane as:so
rivet.yamlis understood to be artifact-plane by the rivet-validate job, while the allow-list 300 lines earlier treats it as code.Why the fix is sound
The allow-list must only admit paths that cannot affect a mutant, a fuzz target, or a Miri run. For
rivet.yaml:grep -rn "rivet\.yaml" --include="*.rs" .⇒ 0 files.AGENTS.md, twodocs/plans/*.md, andci.ymlitself.rivetbinary inRivet validate (artifacts), which deliberately has nochangesgate and therefore still runs on every PR. So the check that actually validatesrivet.yamlkeeps running; only the jobs that cannot observe it get skipped.Suggested change
Add
rivet.yamlto the allow-list, e.g.and add a fixture asserting a diff of
rivet.yamlalone classifies ascode=false, so the constant is tested directly rather than via a glob the test rebuilds.Related
Same failure shape as the
*.yml-glob defect fixed in #372's PR: a pattern that omits a real case, where the omission is silent and biases toward "do the expensive/unsafe thing". Here the bias is fail-safe (over-runs rather than under-runs), so it costs runner-hours rather than correctness — which is why it survived unnoticed.Not fixed inline right now deliberately: the fix touches
ci.yml(hence itself needs a full suite), andmainhasrequired_status_checks.strict = truewith 7 PRs already queued in a serial merge chain — landing an 8th mid-chain costs more than it saves today.