Skip to content

scry#208: the gate-coverage guard stops counting text that merely mentions a gate - #234

Merged
avrabe merged 1 commit into
mainfrom
feat208-gate-coverage-comments
Nov 8, 2026
Merged

avrabe merged 1 commit into
mainfrom
feat208-gate-coverage-comments

Conversation

@avrabe

@avrabe avrabe commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Closes #208.

tools/check-gate-coverage.py exists so #141 cannot recur — CI once ran cargo test for 4 of 12 crates and 222 of 273 tests never executed. It matched with if tool in line, a bare substring test, so text that mentions a gate counted as running one.

Verified by execution before the fix

Three shapes each credited {a, b} as fully covered:

commented-out          credited=True  test=['a','b'] clippy=['a','b']
continue-on-error      credited=True  test=['a','b'] clippy=['a','b']
echoed in a string     credited=True  test=['a','b'] clippy=[]

The third is not in the issue — it surfaced while writing the red case for the other two. A job-level continue-on-error was equally uncounted-for.

The script's own docstring already listed "a predicate that counted a comment as a gate" among the failures its YAML rewrite escaped. The rewrite fixed the awk-range bug (#157) and reintroduced that one. Its --self-test never fed it a comment.

The fix

Matching now:

  1. strips shell comments
  2. requires the tool at the start of a command segment — splitting on &&, ||, ; and |, skipping leading VAR=value assignments and a cargo +toolchain selector
  3. ignores any step or job with continue-on-error: true — a step that cannot fail is not a gate, which is The Rocq and Verus proof jobs cannot fail anything — the v1.0 'flips to required' never happened #207 one level up

Two measurements during the mutation check changed the tests

This is the part worth reading, because both are ways a test can look right and check nothing.

(1) Deleting the comment-stripping line SURVIVED the first four cases. Requiring the tool at the start of a segment already rejects a line beginning with #, so the two defences overlap and nothing exercised the gap: a comment carrying a shell separator, which splits into a segment that does start with the tool.

(2) The replacement case still did not kill it. In a plain YAML scalar, run: cmd # note, YAML eats the comment before this script ever sees it — measured directly:

plain scalar   run = 'cargo build --workspace'
block scalar   run = 'cargo build --workspace  # or: cargo test -p a || cargo test -p b\n'

A shell comment only reaches the matcher inside run: |. Both new cases were rewritten as block scalars, and only then did the mutation die. A test that cannot reach the code it covers is not a weaker test; it is not a test.

Mutation-checked: 7 mutations, 7 killed, unmutated control green

mutation verdict
M1 stop stripping comments KILLED
M2 invokes() back to substring KILLED
M3 credit continue-on-error STEPS KILLED
M4 credit continue-on-error JOBS KILLED
M5 over-strict: invokes() always False KILLED
M6 drop the +toolchain skip KILLED
M7 drop the env-assignment skip KILLED

M5 is the one that keeps the fix honest in the other direction — a matcher that rejects everything would satisfy every negative case. So six real invocation forms are asserted to still count: plain, VAR=1 cargo test, cargo +1.99.0 test, chained after &&, piped into tee, and a trailing comment that must not leak its -p into the package set.

Positive control: against the real ci.yml the guard still reports OK — 13 publishable crates in BOTH gates, exit 0. The fix is not over-strict.

Re-run on the rebased SHA (eac78b8): 7/7 still killed, control green, real ci.yml still accepted. Neither tools/check-gate-coverage.py nor ci.yml changed on main in the interim (git log a2567b9..main -- on both paths is empty).

Residual

The guard credits an invocation in any job that can fail, not only in a job whose name is a required status context. A cargo test -p x in a non-required job still counts as coverage for x. Closing that needs the required-check list, which lives in tools/check-required-checks.py — itself #217. Not merged here.

Comment stripping is textual: a literal # inside a quoted shell string would truncate the line. No current workflow line has one, and the alternative is a shell parser.

This repairs the guard, not the gate it guards. What ran green while the guard was blind? Nothing — the guard was correct in practice on the real ci.yml throughout; all 13 crates were genuinely in both gates.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

…tions a gate

tools/check-gate-coverage.py exists so scry#141 cannot recur — CI once ran
`cargo test` for 4 of 12 crates and 222 of 273 tests never executed. It
matched with `if tool in line`, a bare substring test, so text that
MENTIONS a gate counted as RUNNING one.

VERIFIED BY EXECUTION BEFORE THE FIX. Three shapes each credited {a, b}
as fully covered:

  commented-out          credited=True  test=['a','b'] clippy=['a','b']
  continue-on-error      credited=True  test=['a','b'] clippy=['a','b']
  echoed in a string     credited=True  test=['a','b'] clippy=[]

The third is not in scry#208; it surfaced while writing the red case for
the other two. A job-level continue-on-error was equally uncounted-for.

THE SCRIPT'S OWN DOCSTRING already listed "a predicate that counted a
comment as a gate" among the failures its YAML rewrite escaped. The
rewrite fixed the awk-range bug (scry#157) and reintroduced that one.

Matching now (a) strips shell comments, (b) requires the tool at the
START of a command segment — splitting on &&, ||, ; and |, skipping
leading VAR=value assignments and a `cargo +toolchain` selector — and
(c) ignores any step or job with continue-on-error: true.

TWO MEASUREMENTS DURING THE MUTATION CHECK CHANGED THE TESTS, and both
are the reason this took more than one pass.

(1) Deleting the comment-stripping line SURVIVED the first four cases.
Requiring the tool at the start of a segment already rejects a line
beginning with `#`, so the two defences overlap and nothing exercised the
gap: a comment carrying a shell SEPARATOR, which splits into a segment
that DOES start with the tool.

(2) The replacement case still did not kill it. In a PLAIN YAML scalar,
`run: cmd  # note`, YAML eats the comment before this script ever sees
it — measured directly:

  plain scalar   run = 'cargo build --workspace'
  block scalar   run = 'cargo build --workspace  # or: cargo test -p a ...'

A shell comment only reaches the matcher inside `run: |`. Both new cases
were rewritten as block scalars, and only then did the mutation die. A
test that cannot reach the code it covers is not a weaker test; it is
not a test.

MUTATION-CHECKED: 7 mutations, 7 killed, unmutated control green.

  M1 stop stripping comments                KILLED (2 cases)
  M2 invokes() back to substring            KILLED (2 cases)
  M3 credit continue-on-error STEPS         KILLED (1 case)
  M4 credit continue-on-error JOBS          KILLED (1 case)
  M5 over-strict: invokes() always False    KILLED (10 cases)
  M6 drop the +toolchain skip               KILLED (1 case)
  M7 drop the env-assignment skip           KILLED (1 case)

M5 is the one that keeps the fix honest in the other direction: a
matcher that rejects everything would satisfy every negative case, so
six real invocation forms are asserted to still COUNT — plain,
`VAR=1 cargo test`, `cargo +1.99.0 test`, chained after &&, piped into
tee, and a trailing comment that must not leak its -p into the package
set.

POSITIVE CONTROL: against the real ci.yml the guard still reports
`OK — 13 publishable crates in BOTH gates`, exit 0. The fix is not
over-strict.

Refs: FEAT-101

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc
@avrabe
avrabe force-pushed the feat208-gate-coverage-comments branch from eac78b8 to c39a7ee Compare November 8, 2026 12:50
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown

📐 rivet artifact delta

PR: #234 Base SHA: 7ca4e05e

Validation

head — `rivet validate` result
  SR-11 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-12 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-13 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-2 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-3 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-4 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-5 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-6 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-7 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-8 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-9 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SYS-1 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-2 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-3 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-4 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-5 (system-req, status: accepted) — missing: sys-integration-verification
  → run `rivet validate --explain SR-1` to see which link type and source types satisfy a gap

Result: PASS (196 warnings)
Schemas: common@0.4.0 (embedded), dev@0.6.0 (embedded), research@0.1.0 (embedded), research-ext@0.1.0 (on-disk), dev-ext@0.1.0 (on-disk), safety-case@0.1.0 (embedded), aspice@0.3.0 (embedded)
base — `rivet validate` result (for comparison)
  SR-11 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-12 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-13 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-2 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-3 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-4 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-5 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-6 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-7 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-8 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-9 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SYS-1 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-2 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-3 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-4 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-5 (system-req, status: accepted) — missing: sys-integration-verification
  → run `rivet validate --explain SR-1` to see which link type and source types satisfy a gap

Result: PASS (193 warnings)
Schemas: common@0.4.0 (embedded), dev@0.6.0 (embedded), research@0.1.0 (embedded), research-ext@0.1.0 (on-disk), dev-ext@0.1.0 (on-disk), safety-case@0.1.0 (embedded), aspice@0.3.0 (embedded)

Artifact stats

base head
Total artifacts 270 271
full stats — head
Artifact summary:
  academic-reference               24
  competitive-analysis             11
  design-decision                  23
  feature                         100
  market-finding                    7
  requirement                      21
  safety-context                    3
  safety-goal                       5
  safety-justification              4
  safety-solution                   6
  safety-strategy                   1
  stakeholder-req                   3
  sw-req                           13
  sw-verification                  13
  sys-verification                  5
  system-req                        5
  technology-evaluation            12
  verification                     15
  TOTAL                           271

Orphan artifacts (no links): 12
  CA-001
  CA-002
  CA-003
  CA-004
  CA-005
  CA-006
  CA-007
  CA-008
  CA-009
  CA-010
  CA-011
  FEAT-078

Diagnostics: 0 error(s), 196 warning(s), 11 info(s)

Diff (base → head)

+ FEAT-101  v3.4 — The gate-coverage guard stops counting text that merely mentions a gate (scry#208)

1 added, 0 removed, 0 modified, 270 unchanged

~ NEW    WARN: [FEAT-101] prose mentions 'REQ-010' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-101] prose mentions 'FEAT-091' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-101] prose mentions 'FEAT-093' but no typed link to it; add a link in `links:` or remove the mention
0 new errors, 0 resolved errors, 3 new warnings, 0 resolved warnings

AADL model — head

spar/scry.aadl: OK

Posted by the rivet-delta workflow. Informational only — does not gate the PR.

@avrabe
avrabe merged commit dc1e603 into main Nov 8, 2026
13 checks passed
@avrabe
avrabe deleted the feat208-gate-coverage-comments branch November 8, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check-gate-coverage.py counts a COMMENTED-OUT cargo test as coverage — the exact class its rewrite escaped

1 participant