FEAT-086 (#157): clippy covers every crate, and the guard covers both gates - #159
Merged
Conversation
…both gates
CI's Clippy job ran FOUR packages — the same four the Test job ran before
scry#141 widened it. Nine publishable crates were never linted, and TWO had live
errors on main that CI could not see:
scry-sai-bits a doc line beginning `> 2^63`, read as an unterminated
Markdown blockquote
scry-sai-pentagon two needless borrows, in its OWN meet/leq soundness tests
Both fixed. Clippy now covers all twelve publishable crates; verified locally
across all nine previously-unlinted packages, exit 0.
THE GUARD IS THE REAL DELIVERABLE, and it took four attempts to make one that
works. The version merged in scry#142 asserted coverage for `cargo test` only,
so when the clippy list stayed narrow it said nothing. Widening one gate did not
fix the pattern: the two per-package lists sit three lines apart in the same
file and drifted independently.
Three text-scraping attempts at the extended guard were VACUOUS in ways that
looked like they worked:
1. a per-crate dynamic regex — survived BSD grep locally, failed on GNU grep
in CI, and failed CLOSED naming all twelve crates;
2. an `awk '/cargo test/,/^ - name:/'` range — the ranges chained across
the whole file, so `tested` and `linted` came out IDENTICAL. That guard
could not detect the exact drift it exists for. It printed "FIRES" under
mutation, which nearly passed review, but with the WRONG attribution:
removing a crate from clippy reported it missing from BOTH;
3. before that, in a different check, a predicate that counted a COMMENT as a
gate (scry#147).
Replaced with tools/check-gate-coverage.py, which parses the workflow as YAML
and attributes packages per step's `run` block. It carries `--self-test`,
asserting it distinguishes a good workflow from a bad one — including the
explicit property the awk version failed: that the two gates are not identical.
CI runs the self-test before the real check, so a checker that stops
discriminating fails the build rather than silently passing.
Mutation-checked against the real workflow in both directions:
drop scry-sai-float from CLIPPY only -> scry-sai-float(clippy), exit 1
drop scry-sai-poly from TEST only -> scry-sai-poly(test), exit 1
restored -> 12 crates in BOTH, exit 0
Correct attribution, which the awk version got wrong.
tests=0 clippy=0 fmt=0 rivet=0 claim-check=0 gate-coverage=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc
📐 rivet artifact deltaPR: #159 Base SHA: Validationhead — `rivet validate` resultbase — `rivet validate` result (for comparison)Artifact stats
full stats — headDiff (base → head)AADL model — headPosted by the |
This was referenced Aug 26, 2026
Closed
avrabe
added a commit
that referenced
this pull request
Aug 27, 2026
… cuttable (#160) FEAT-086 did not exist. PR #159 merged with a commit and PR both titled "FEAT-086 (scry#157)" referencing an artifact I never created. That is untracked work — the anti-pattern the issue-hunt skill names explicitly, because it is invisible to the release plan and resurfaces as a gate surprise. `rivet validate` cannot catch it. It validates artifacts that EXIST; nothing checks that an id named in a commit message or PR title resolves to one. I found it by grepping for the id while promoting something else. Recorded as the feature's own residual, and worth a rivet-side check. FEAT-086 now carries what actually shipped, including the part worth keeping: the guard took FOUR attempts, and three of them were vacuous in ways that looked like they worked. The awk-range version is the instructive one — its two lists came out IDENTICAL, so it could not detect the drift it existed for, and it printed "FIRES" under mutation with the WRONG attribution, which nearly passed review. FEAT-083 promoted to accepted: merged, CI-green, and its end-to-end AC discharged on the BUILT artifact (the CI-built scry.wasm contains 3.2.6 and not the rules_rust 0.0.0 default, against the shipped v3.2.6 component which is the exact inverse). rivet release status v3.2.7: Cuttable, 2/2. NOT CUTTING — tagging publishes to crates.io and is irreversible. tests=0 fmt=0 rivet=0 claim-check=0 gate-coverage=0. Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
Aug 27, 2026
…eck drift gate (FEAT-093, #130) (#167) * FEAT-091 (scry#161): connect the code side to the artifact side rivet ships commit-to-artifact traceability and it shipped here unconfigured, so `Broken refs` read 0 for want of any reference to check. That counter is what would have caught PR #159 merging with a commit naming FEAT-086 before the artifact existed (#160) -- found by grepping, not by a gate. TWO MEASUREMENTS CORRECT #161's PREMISE, and both changed the design. (1) #161 says "zero of 125 commits carry an artifact trailer". Configure a `Verifies` mapping and rivet instead reports linked:4, broken_refs:4 AND malformed_refs:4 -- the SAME four commits inflating three counters. None carry a trailer. All four are the prose line Verified: bazel build //:scry green + wasm-tools validate ok. which rivet's FUZZY trailer-key matcher reads as a malformed `Verifies:`. Five commits in this history carry such a line. A/B measured: with `Verifies` mapped the counters read 4/4/4; unmapped they read 0/0/0 and orphans rises 81 -> 85. So `linked: 4` was never real, and the honest baseline is linked:0 orphans:85 broken_refs:0. The mapping is kept to `Refs: traces-to` ALONE -- a finding, not laziness: a gate that fails on a normal English sentence gets switched off, and "Verified:" is an entirely natural thing to write. Reported upstream. (2) `rivet commits --strict` cannot BE the gate. It also promotes the repo-wide unimplemented-artifacts warning -- `Artifact coverage: 1/257 (0.4%)` -- which no pull request can satisfy. Measured: a range with one correctly-linked commit, zero orphans and zero broken refs STILL exits 1 under --strict. A gate no PR can pass is not a gate. So tools/check-commit-trailers.py reads the JSON summary and fails on only the three counters a PR owns -- orphans, broken_refs, malformed_refs -- ignoring unimplemented and artifact_coverage. Forward-only by construction: scoped to origin/main..HEAD, so the 85 pre-existing orphans never enter it and no history rewrite is needed. Its own CI job, so a traceability failure is legible in the checks list rather than buried behind artifact validation. VERIFIED END-TO-END on the real repo, three directions: crates/ commit, no trailer -> orphans=1, exit 1 same commit, `Refs: REQ-005` -> linked=1, exit 0 `Refs: FEAT-99999` (the #160 bug) -> broken_refs=1, exit 1 Plus: parse the JSON even when rivet exits non-zero (bailing on the exit code alone degraded an informative failure to "rivet exited 1"), and fail CLOSED when rivet is absent. Self-test of 6 cases runs BEFORE the real check. tests=0 fmt=0 rivet=0 claim-check=0 gate-coverage=0 trailers-self-test=0. Refs: FEAT-091 Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * scry#130: the required-check comment was claiming a gate that did not exist ci.yml said "Required-status-check names that temper's branch protection expects: Format, Clippy, Test". The ruleset required NOTHING -- so the file documented a gate the repo did not have, and every "merged green" rested on whoever merged remembering to check by hand. Now accurate: all ten CI jobs are required as of 2026-08-27, verified by every open PR flipping to mergeStateStatus=BLOCKED. Also records the constraint that decides which checks may be required at all: `Rivet artifact delta` lives in the PATH-FILTERED rivet-delta.yml, so it never reports on a code-only PR. Requiring it would block such PRs forever. Same trap applies to any job added to this file -- require it only after its name exists on every PR, i.e. after it has merged to main. Refs: FEAT-091 Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * FEAT-093 (scry#130): the required-check set cannot silently rot back scry#130 was enforced on 2026-08-27 -- all ten ci.yml jobs are now required. Verified in BOTH directions, which is what separates a gate from a setting: every open PR flipped to mergeStateStatus=BLOCKED on apply, and #164 merged CLEAN once its ten checks passed. A setting that was wrong for months can be wrong again, and it would be invisible -- CI still runs, PRs still go green, nothing announces that a job stopped being enforced. Three ways it rots: 1. a job is ADDED and never required -- scry#130 one job at a time, no symptom 2. a job is RENAMED -- the stale context never reports, every PR deadlocks 3. the ruleset is RESET -- it is named `temper-`, so something may regenerate it, and required_status_checks would simply vanish THE DEADLOCK RULE is why this is not set-equality. A check may be required only if it reports on EVERY PR. rivet-delta.yml is PATH-FILTERED, so `Rivet artifact delta` never runs on a code-only PR and requiring it would block such PRs forever. The gate fails if a path-filtered or `if:`-conditional job is ever required, and carries that exclusion WITH its reason so it is auditable rather than folklore. NEW JOBS ARE NOT FAILURES. A job added in the current PR does not exist on main and cannot be required yet. The gate reads ci.yml from BOTH origin/main and the PR and reports such jobs as PENDING -- without which it would fail on the very PR that introduces it. This PR introduces two and still exits 0. MUTATION-CHECKED AGAINST THE LIVE RULESET, not a fixture: `Format` was removed from the real required set (asserted 10 -> 9 before applying), the gate failed naming exactly that job, and restoring returned 10 contexts and exit 0. Also corrects ci.yml, which claimed "temper's branch protection expects Format, Clippy, Test" while the ruleset required NOTHING -- the repo was documenting a gate it did not have. rivet=0 claim-check=0 fmt=0 drift-self-test=0 trailer-self-test=0. Refs: FEAT-093 Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * Fix the broken CI workflow: GITHUB_TOKEN cannot read rulesets I broke CI on this branch. The required-checks drift gate ran the LIVE-ruleset check in CI under `permissions: administration: read`. That is not a grantable Actions scope, and a workflow requesting it is REJECTED WHOLESALE -- the run completed as `failure` with ZERO jobs and no log, which reads like a queue outage rather than a syntax error. It was caught only by noticing the PR showed 1 registered check instead of 12. A workflow's GITHUB_TOKEN cannot read rulesets at all, so the original design could never have worked in CI. Two modes now: --against-file compares jobs to .github/required-checks.txt. No API, so CI can run it. Catches the rot WE cause in our own PRs -- a job added or renamed without updating the file. (default) compares jobs to the LIVE ruleset AND cross-checks the file against it, because CI gates on the file and a drifted file means CI is gating on a fiction. Needs admin credentials. MUTATION-CHECKED, and the two modes fail DIFFERENTLY, which is the point: deleting `Clippy` from the file gives file mode: "job 'Clippy' exists on main and runs on every PR, but is NOT required" live mode: "'Clippy' is required LIVE but missing from .github/required-checks.txt" Both exit 1; both restore to 0. HONEST LIMIT, now in the artifact residual: a ruleset RESET is still not caught by CI and cannot be, since no workflow can see the live setting. It is caught the next time the live mode is run by hand. Closing that needs a PAT secret, which is a repo-owner decision. rivet=0 claim-check=0 gate-coverage=0 drift-self-test=0 drift-file=0 trailer-self-test=0 fmt=0. Refs: FEAT-093 Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
CI's Clippy job ran four packages — the same four the Test job ran before #141 widened it. Nine publishable crates were never linted, and two had live errors on
mainthat CI could not see:scry-sai-bits> 2^63, read as an unterminated Markdown blockquotescry-sai-pentagonBoth fixed; clippy now covers all twelve, verified locally across the nine previously-unlinted packages.
The guard is the real deliverable, and it took four attempts
The version merged in #142 asserted coverage for
cargo testonly. When the clippy list stayed narrow, it said nothing. Widening one gate did not fix the pattern — the two per-package lists sit three lines apart in the same file and drifted independently.Three text-scraping attempts at the extended guard were vacuous in ways that looked like they worked:
awk '/cargo test/,/^ - name:/'range — the ranges chained across the whole file, sotestedandlintedcame out identical. That guard could not detect the exact drift it exists for. Under mutation it printedFIRES, which nearly passed my review — but with the wrong attribution: removing a crate from clippy reported it missing from both;What replaced it
tools/check-gate-coverage.pyparses the workflow as YAML and attributes packages per step'srunblock.It carries a
--self-testasserting it distinguishes a good workflow from a bad one — including the explicit property the awk version failed: that the two gates are not identical. CI runs the self-test before the real check, so a checker that stops discriminating fails the build instead of silently passing.Mutation-checked against the real workflow, both directions:
Correct attribution — which the awk version got wrong.
tests 0 · clippy 0 (all 9) · fmt 0 ·
rivet validate0 · claim-check 0 · gate-coverage 0.