Skip to content

FEAT-098: file the guard scry declines to read, which is what made slice 2a inert - #202

Merged
avrabe merged 1 commit into
mainfrom
feat098-unsigned-guard
Sep 1, 2026
Merged

FEAT-098: file the guard scry declines to read, which is what made slice 2a inert#202
avrabe merged 1 commit into
mainfrom
feat098-unsigned-guard

Conversation

@avrabe

@avrabe avrabe commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

FEAT-057 slice 2a (#201) shipped polyhedra into the fixpoint and measured itself inert for the class it exists to serve: 5,138 linear constraints across 130 functions on scry_mcdc.wasm, and zero involving three or more locals — nothing the octagon could not already express.

Instrumented, not guessed: of 94 z := x + y sites reaching the transfer, zero have a provable no-wrap, because the operands are ⊤.

The operands are ⊤ because the guard that would bound them is declined

guard_op maps only signed comparisons to a GuardOp. Every *U variant returns None, and the code says why:

"unsigned comparisons wrap, so refining their bounds with a signed constant is not sound"

That refusal is correct. It is also the most consequential precision decision in the analyzer, because LLVM emits array bounds checks as i32.lt_u index, len — so the one idiom that would bound an index is the one shape scry declines to read.

The refinement is available under a side condition — and it is exactly the bounds-check shape

For x <u c with a constant 0 ≤ c ≤ 2^31, the signed interval [0, c-1] is sound: unsigned x < c ≤ 2^31 puts x in [0, 2^31-1], representable as a non-negative signed i32, so no wrap into the negative half is possible.

Verified exhaustively at w=8 over every (c, x) pair before filing, in both directions:

counterexamples for 0 ≤ c ≤ 2^(w-1) 0
constants above it where the signed reading fails 127

The negative control matters as much as the positive one: the side condition is load-bearing, not decorative, and a version that dropped it would be unsound. A rule that held for every c would have meant I'd mis-stated it.

Not a polyhedra feature

Bounding locals feeds the interval domain, the octagon, the no-wrap gate, the trap checks, and the OOB proven rate — FEAT-069 measured 0.67% proven and diagnosed precision, not format. Filed as its own artifact rather than buried in FEAT-057's residual because it blocks several things and, like FEAT-095, nothing tracked it.

The dependency is typed, and I confirmed the gate actually reads it

FEAT-057 --depends-on--> FEAT-098, both v3.4.0, so the ordering gate stays green. Rather than assume the link is machine-visible, I mutated FEAT-098 into v3.5.0:

FAIL: FEAT-057 (v3.4.0) depends-on FEAT-098 (v3.5.0) — a release cannot be cut
      before one it depends on. Move one of them, or add it to KNOWN with a reason.

Green again on revert. This is the first inversion FEAT-096's gate has caught that I did not seed at build time.

The acceptance criteria demand measured deltas, not fixtures

AC#4 and AC#5 require reporting, after implementation: the ≥3-local linear count (0 today), the no-wrap-provable share of the 94 sites (0 today), and FEAT-069's 0.67%. Written so that "the number did not move" is a reportable outcome rather than a silent one — because this feature is filed on a causal claim, and that claim is checkable.

Honest risk, in the residual

Scope is the constant-operand shape only. x <u y between two locals needs a bound on y first — the same problem one level up. The motivating LLVM idiom is index <u len where len is often a local, not a constant, so a real corpus may see less benefit than constant-form fixtures suggest. That is why AC#4 demands the measured delta rather than a passing fixture.

Refs: FEAT-098 · FEAT-057

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

…ice 2a inert

FEAT-057 slice 2a (#201) shipped polyhedra into the fixpoint and
MEASURED ITSELF INERT for the class it exists to serve: 5,138 linear
constraints across 130 functions on scry_mcdc.wasm, and ZERO involving
three or more locals -- nothing the octagon could not already express.
Instrumented, not guessed: of 94 `z := x + y` sites reaching the
transfer, ZERO have a provable no-wrap, because the operands are ⊤.

The operands are ⊤ because the guard that would bound them is declined.
`guard_op` maps only SIGNED comparisons; every `*U` variant returns
`None`, and the code says why -- unsigned comparisons wrap, so refining
with a signed constant is not sound. That refusal is CORRECT. It is also
the most consequential precision decision in the analyzer, because LLVM
emits array bounds checks as `i32.lt_u index, len`: the one idiom that
would bound an index is the one shape scry declines to read.

THE REFINEMENT IS AVAILABLE UNDER A SIDE CONDITION, and the side
condition is exactly the bounds-check shape. For `x <u c` with
`0 <= c <= 2^31`, the SIGNED interval `[0, c-1]` is sound -- unsigned
`x < c <= 2^31` puts x in `[0, 2^31-1]`, representable as non-negative
signed, so no wrap into the negative half.

VERIFIED EXHAUSTIVELY at w=8 over all (c, x) pairs BEFORE filing, in both
directions. Zero counterexamples for `0 <= c <= 2^(w-1)`. And 127
constants ABOVE it where the signed reading FAILS -- so the side
condition is load-bearing, not decorative, and a version that dropped it
would be unsound. A rule that held for every c would have meant I had
mis-stated it.

NOT A POLYHEDRA FEATURE. Bounding locals feeds the interval domain, the
octagon, the no-wrap gate, the trap checks, and the OOB proven rate
(FEAT-069 measured 0.67% and diagnosed PRECISION, not format). Filed as
its own artifact rather than buried in FEAT-057's residual because it
blocks several things and, like FEAT-095, nothing tracked it.

Typed `depends-on FEAT-057 -> FEAT-098` so the dependency is machine-
visible rather than prose (the FEAT-096 lesson). Both are v3.4.0, so the
ordering gate stays green -- CONFIRMED IT IS ACTUALLY READ by mutating
FEAT-098 into v3.5.0: the gate fired naming the new pair, then went green
on revert. First inversion the gate has caught that I did not seed at
build time.

AC#4 and AC#5 demand the MEASURED delta after implementation -- the
>=3-local count (0 today), the no-wrap-provable share of 94 sites (0
today), and FEAT-069's 0.67%. "The number did not move" is written to be
a reportable outcome rather than a silent one.

Refs: FEAT-098

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

📐 rivet artifact delta

PR: #202 Base SHA: 3444b752

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 (190 warnings)
Schemas: common@0.3.0 (embedded), dev@0.3.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.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 (188 warnings)
Schemas: common@0.3.0 (embedded), dev@0.3.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.2.0 (embedded)

Artifact stats

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

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), 190 warning(s), 14 info(s)

Diff (base → head)

+ FEAT-098  v3.4 — Refine unsigned bounds-check guards, the measured blocker on every relational domain
~ FEAT-057
  link: + depends-on -> FEAT-098

1 added, 0 removed, 1 modified, 266 unchanged

~ NEW    WARN: [FEAT-098] prose mentions 'FEAT-069' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-098] prose mentions 'FEAT-095' but no typed link to it; add a link in `links:` or remove the mention
0 new errors, 0 resolved errors, 2 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 bc9523c into main Sep 1, 2026
13 checks passed
@avrabe
avrabe deleted the feat098-unsigned-guard branch September 1, 2026 22:14
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