Skip to content

FEAT-092 (scry#126): one operator, one name - #168

Closed
avrabe wants to merge 2 commits into
sync-required-checks-filefrom
feat-092-op-names
Closed

FEAT-092 (scry#126): one operator, one name#168
avrabe wants to merge 2 commits into
sync-required-checks-filefrom
feat-092-op-names

Conversation

@avrabe

@avrabe avrabe commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

The defect

TrapCheck::op is documented as "the operator name (e.g. i32.div_s)" — wasm text
format. Measured on scry's own scry_mcdc.wasm, 2,800 of 10,520 advisories carried
a Rust enum identifier instead: I64Store, I32Load8U, I32Store8, MemoryCopy,
F64Load

Counts are identical on the stripped and unstripped module, so this isn't an
artifact of how the module was built.

Mechanism

op_report_name falls back to format!("{op:?}") for any operator op_name has no arm
for — and among memory operators op_name covered exactly two: I32Load and
I32Store. Every other load/store rendered as its Rust variant name.

Why this is #126's operator ranking, not cosmetics

The same operator carried two names at once.

path name sites code
non-degraded (literal label) i64.store 287 out-of-bounds
degraded (op_report_name) I64Store 1,008 out-of-bounds

A consumer ranking operators by frequency sees one operator as two rows and
under-counts the top entry by ~4.5× — i64.store is really 1,295 sites. Same for
i64.load (261 + 669).

Naming only — measured, not argued

After the change, on the real module:

before after
leaked Rust identifiers 2,800 71
out-of-bounds 8,162 8,162
proven-safe OOB 55 55
every advisory code equal yes

Every code count is identical across all 10,520 advisories. The test also pins the
verdicts, so a later edit can't move what scry proves while claiming to rename.

Oracles

Red first, and the red was the contract rather than an empty fixture — the
non-vacuity assertion (>= 3 trap checks) passed before the failure:

TrapCheck::op must be the wasm text-format name, never a Rust enum identifier;
leaked: ["I32Load8U", "I32Store8"]

Mutation-checked: deleting the i32.load8_u arm — asserted to match exactly one
site before applying — turns the test red naming exactly I32Load8U; restoring is green.

Not covered, stated rather than implied

71 advisories still render a Rust identifier. All 71 are unsupported-op on i64/i32
arithmetic and comparison operators (I64GtS, I64Add, I32Rotl, …) that scry
genuinely does not model — and each carries a single name, so the ranking-splitting
defect doesn't apply to them. Naming that family is a separate mechanical slice that
would also serve #126's fallback ranking.

And this fixes the name, not the coverage. The reason i64.store appeared 1,008
times via the degraded path is that those functions were already scrubbed by an
unsupported operator upstream. The corrected ranking now shows honestly how much of the
out-of-bounds population comes from degraded functions.

tests=0 clippy=0 fmt=0 rivet=0 claim-check=0 gate-coverage=0

⚠️ Per #130 this repo has no required status checks; verify gh pr checks by hand.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

📐 rivet artifact delta

PR: #168 Base SHA: b877f455

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 (159 warnings)
Schemas: common@0.3.0 (embedded), dev@0.3.0 (embedded), research@0.1.0 (embedded), research-ext@0.1.0 (on-disk), safety-case@0.1.0 (embedded), aspice@0.2.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 (158 warnings)
Schemas: common@0.3.0 (embedded), dev@0.3.0 (embedded), research@0.1.0 (embedded), research-ext@0.1.0 (on-disk), safety-case@0.1.0 (embedded), aspice@0.2.0 (embedded)

Artifact stats

base head
Total artifacts 262 263
full stats — head
Artifact summary:
  academic-reference               24
  competitive-analysis             11
  design-decision                  22
  feature                          93
  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                           263

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), 159 warning(s), 26 info(s)

Diff (base → head)

+ FEAT-092  v3.4 — One operator, one name: stop leaking Rust identifiers into the operator ranking (scry#126)
~ FEAT-069
  description: changed

1 added, 0 removed, 1 modified, 261 unchanged

~ NEW    WARN: [FEAT-069] prose mentions 'FEAT-089' but no typed link to it; add a link in `links:` or remove the mention
0 new errors, 0 resolved errors, 1 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 force-pushed the feat-092-op-names branch from eca0e77 to 58890f7 Compare August 27, 2026 05:34
@avrabe
avrabe force-pushed the feat-092-op-names branch from 58890f7 to fa6e7d7 Compare August 27, 2026 05:47
avrabe added a commit that referenced this pull request Aug 27, 2026
ci.yml recorded the rule: require a job only once its name exists on every PR,
i.e. after it merges to main. Correct, and incomplete -- a PR opened BEFORE the
job existed still does not have it, so it can never report and is blocked
forever.

Observed rather than theorised. When #167 added two jobs and both were required,
PRs #168 and #169 each showed 11 checks with 0 of the 2 new ones. Both would have
deadlocked on a check that could never run. Both were rebased and now register 13.

The complete procedure is now in ci.yml:
  1. merge the PR that adds the job
  2. add the context to the ruleset AND to required-checks.txt, ruleset FIRST
     (CI gates on the file, so a file ahead of the ruleset means CI is gating on
      a fiction -- the live-mode cross-check says exactly that)
  3. REBASE EVERY OPEN PR

drift-gate=0 gate-coverage=0 claim-check=0 rivet=0.

Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
avrabe and others added 2 commits August 27, 2026 08:50
`TrapCheck::op` is documented as "the operator name (e.g. `i32.div_s`)" -- wasm
text format. Measured on scry's own scry_mcdc.wasm, 2,800 of 10,520 advisories
carried a RUST ENUM IDENTIFIER instead: I64Store, I32Load8U, I32Store8,
MemoryCopy, F64Load. Identical counts on the stripped and unstripped module, so
not an artifact of the build.

MECHANISM: op_report_name falls back to format!("{op:?}") for any operator
op_name has no arm for, and among memory operators op_name covered exactly two
-- I32Load and I32Store. Every other load/store rendered as its Rust variant.

WHY THIS IS #126's OPERATOR RANKING AND NOT COSMETICS: the same operator carried
TWO names at once. The non-degraded path labels the access from a literal
(`i64.store`, 287 sites); the degraded path calls op_report_name (`I64Store`,
1,008 sites). Both are out-of-bounds advisories. A consumer ranking operators by
frequency sees ONE operator as TWO rows and under-counts the top entry ~4.5x --
i64.store is really 1,295 sites.

NAMING ONLY, and measured rather than argued: after the change EVERY
advisory-code count on the real module is identical -- out-of-bounds 8,162 ->
8,162, proven-safe OOB 55 -> 55, every code equal across all 10,520 -- while
leaked identifiers drop 2,800 -> 71. The test pins the verdicts too, so a later
edit cannot move what scry proves while claiming to rename.

Red-first: failed with leaked: ["I32Load8U", "I32Store8"], AFTER the
non-vacuity assertion (>= 3 trap checks) passed -- the red was the contract, not
an empty fixture. Mutation-checked: deleting the i32.load8_u arm (asserted to
match exactly one site before applying) turns it red naming exactly I32Load8U.

NOT COVERED, stated rather than implied: 71 advisories still render a Rust
identifier. All 71 are `unsupported-op` on i64/i32 arithmetic and comparison
operators scry genuinely does not model, and each carries a SINGLE name -- the
ranking-splitting defect does not apply to them. Naming that family is a
separate mechanical slice.

tests=0 clippy=0 fmt=0 rivet=0 claim-check=0 gate-coverage=0.

Refs: FEAT-092

Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
FEAT-069 (safe-accesses.json, a v3.3.0 blocker) proposes exporting scry's
PROVEN-SAFE out-of-bounds verdicts so synth can elide software bounds checks.
Measured before anyone builds it, on scry's own scry_mcdc.wasm and identical
stripped vs unstripped: scry proves 55 memory accesses safe against 8,162 it
cannot -- a 0.67% proven rate. The export would carry 55 sites out of 8,217.
synth measures bounds checks at ~25-40% overhead, so eliding 0.67% of them
recovers on the order of 0.2% of runtime.

That does not kill the feature; it relocates the work. The blocker is not the
export FORMAT, it is PRECISION -- the same module shows 1,639
unmodeled-control-flow and 623 unsupported-op advisories, an if/else havocs its
region by design, and FEAT-089 measured that a disequality guard cannot refine
an interval at all. Shipping the channel before the payload exists would hand
synth a correct and nearly empty file.

Recorded on the artifact rather than only on the tracker, so a future tick does
not build it expecting a full payload.

(Also repairs three terms this edit itself blanked: the first attempt used an
unquoted heredoc, so the shell ran the backticked operator names as command
substitutions. `rivet validate` passed over the gaps -- structural validation
cannot see missing prose, which is worth remembering before trusting it as a
review.)

rivet=0 claim-check=0 fmt=0.

Refs: FEAT-069

Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@avrabe
avrabe force-pushed the feat-092-op-names branch from fa6e7d7 to b3a8dbe Compare August 27, 2026 06:50
@avrabe
avrabe changed the base branch from main to sync-required-checks-file August 27, 2026 06:50
avrabe added a commit that referenced this pull request Aug 27, 2026
* Sync required-checks.txt to the ruleset: 10 -> 12 (scry#130)

#167 landed FEAT-091 and FEAT-093, so `Commit traceability (rivet)` and
`Required checks track CI jobs (scry#130)` now exist on main and run on every
PR. The drift gate immediately moved them from PENDING to FAIL -- which is the
transition it was built to make: a requirable job that is not required is
scry#130 recurring one job at a time.

Both were added to ruleset 16891064 (10 -> 12 contexts), and this syncs the
checked-in file so the two agree. Order matters: the RULESET was updated first
and the file second, because CI gates on the file -- a file listing checks the
ruleset does not require would mean CI gating on a fiction, which the live-mode
cross-check reports in exactly those words. Briefly red on main is the honest
state during that window.

Verified: file mode PASS, live mode PASS with `file agrees: True`.

rivet=0 claim-check=0 drift-self-test=0.

Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* "Require it after merge" was necessary but not sufficient

ci.yml recorded the rule: require a job only once its name exists on every PR,
i.e. after it merges to main. Correct, and incomplete -- a PR opened BEFORE the
job existed still does not have it, so it can never report and is blocked
forever.

Observed rather than theorised. When #167 added two jobs and both were required,
PRs #168 and #169 each showed 11 checks with 0 of the 2 new ones. Both would have
deadlocked on a check that could never run. Both were rebased and now register 13.

The complete procedure is now in ci.yml:
  1. merge the PR that adds the job
  2. add the context to the ruleset AND to required-checks.txt, ruleset FIRST
     (CI gates on the file, so a file ahead of the ruleset means CI is gating on
      a fiction -- the live-mode cross-check says exactly that)
  3. REBASE EVERY OPEN PR

drift-gate=0 gate-coverage=0 claim-check=0 rivet=0.

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>
@avrabe
avrabe deleted the branch sync-required-checks-file August 27, 2026 07:03
@avrabe avrabe closed this Aug 27, 2026
@avrabe

avrabe commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #172 — same branch, rebased onto main.

Auto-closed by GitHub when I deleted sync-required-checks-file while merging #170. That branch was the base of three stacked PRs, so deleting it closed all three. My error, not a problem with the work — the review discussion here remains the record.

avrabe added a commit that referenced this pull request Aug 27, 2026
…175)

Operating the gate exposed a hole in the procedure it enforces. Landing a job
while its required-checks.txt entry arrives in a SEPARATE PR leaves main AND
every open PR red until that follow-up merges. #167 did exactly that: the moment
it merged, main failed its own drift gate and #168/#169 went red on a file they
could not fix.

The fix is structural, not another comment. The gate now FAILS a PR that adds a
requirable job which is not in required-checks.txt, so the job and its entry
ship together and the window does not exist.

VERIFIED ON THE REAL REPO, both directions:
  job added, no file entry -> exit 1, naming it and quoting the instruction
  same job WITH its entry   -> exit 0 (pending the ruleset update only)
  restored                  -> exit 0
Plus two new self-test cases covering exactly those, 7 in total.

The procedure in ci.yml is now three steps with no red window:
  1. in the SAME PR: add the job AND its required-checks.txt entry
  2. after merge: add the context to the ruleset (instant, via the API)
  3. rebase every open PR

Steps 2 and 3 were each learned by getting them wrong. A required context whose
job does not exist deadlocks everything; a job whose context does not exist
deadlocks nothing. The asymmetry is why the ordering matters.

drift-self-test=0 drift-file=0 gate-coverage=0 trailer-self-test=0
claim-check=0 rivet=0 fmt=0.

Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant