Skip to content

GI-FPU-002 phase 1: scalar f32 hard-float reachable on thumb-2 (#619, #369) - #705

Merged
avrabe merged 2 commits into
mainfrom
feat/619-vfp-f32-hardfloat
Jul 10, 2026
Merged

GI-FPU-002 phase 1: scalar f32 hard-float reachable on thumb-2 (#619, #369)#705
avrabe merged 2 commits into
mainfrom
feat/619-vfp-f32-hardfloat

Conversation

@avrabe

@avrabe avrabe commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes scalar f32 reachable end-to-end on the thumb-2 CLI path for FPU targets (cortex-m4f/m7/m7dp), routing through the existing VFP encoder + selector, with honest reject on non-FPU (m0/m3/r5). Closes the "M4F float frontier" gap from #619 and the hard-float half of #369.

Infra found (premise correction)

The issue's premise was half-true: the VFP encoder (f32_vfp_encoding_test.rs) and ArmOp VFP variants exist, and select_default has VFP arms — but the direct select_with_stack selector had no float arms; it fell through to select_default, whose alloc_vfp_reg is a blind %16 round-robin with no operand-stack integration. So f32.const;f32.const;f32.add emitted VADD S2,S3,S4 over garbage. Building the real VFP value stack is phase 1.

f32 ops wired (phase-1 honest subset)

f32.add/sub/mul/div, the six comparisons, i32.trunc_f32_s/u, f32.convert_i32_s/u, f32.const, and f32-param local.get. Decoder un-drops exactly these; the selector lowers them onto a real S0–S15 value stack (StackVal::Float, no VFP spilling — loud-bail on exhaustion). Integer modules never construct a Float entry → frozen path byte-identical.

AAPCS-VFP handling

f32 params homed in S0..S15 (a params_f32 mask threaded decoder → CompileConfig → selector), f32 result returned in S0. Mixed f32/integer param lists and f32-in-functions-with-calls loud-decline (independent core/VFP arg pools; S0–S15 caller-saved). .ARM.attributes gains Tag_FP_arch=VFPv4-D16 + Tag_ABI_VFP_args on FPU targets, and the reset handler now enables CP10/CP11 in SCB->CPACR (the M4F FPU is disabled at reset).

f64 verdict

Held for phase 2 (M7DP double-FPU) — loud-skipped at decode with a phase-2 reason. StackVal::Float holds only an S-register, so f64 never touches the value-stack model. Also held for 1b: f32 load/store and f32 local.set/tee.

Two latent encoder bugs fixed (surfaced by making float reachable)

  • encode_{arm,thumb}_f32_convert_i32: signed/unsigned VCVT constants were swapped (0xEEB80A40 is U32), silently making convert_i32_s an unsigned conversion. objdump-confirmed.
  • CPACR ORR mask (main.rs / cortex_m.rs) encoded #0x07800000 instead of #0x00F00000 (did not actually enable CP10/CP11).

Red→green evidence

scripts/repro/f32_vfp_619_differential.py — compiles the fixture for cortex-m4f, reads each symbol from the ELF SYMTAB (#489), runs it under unicorn (ARM/Thumb, FPU enabled via CPACR + FPEXC) with f32 args in S0/S1 per hard-float, and asserts bit-exact (to_bits) vs wasmtime on boundary values (0.0, 1.5, -2.25, 1e30, subnormal).

  • RED on origin/main: compile rejects → exit 1.
  • GREEN here: 48/48 bit-exact → exit 0.

The f32 comparisons compile and are byte-pinned in f32_vfp_encoding_test.rs but are not execution-differentiated — unicorn does not model the VMRS APSR_nzcv, FPSCR flag transfer (emulator gap, not a synth defect; documented in the harness).

Honest-reject confirmation

The same harness asserts cortex-m3 rejects f32. crates/synth-backend/tests/f32_hardfloat_619.rs locks the AAPCS-VFP S0/S1 homing, the honest reject, and the convert-signedness fix in CI without unicorn.

Gates

Frozen anchors 10/10, the #511 estimator↔encoder oracle, and the claim gate 17/17 all pass untouched (float declines the optimized path → the estimator never sees VFP ops). cargo test --workspace green; fmt + chunked clippy -D warnings clean.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

avrabe and others added 2 commits July 10, 2026 22:26
…umb-2 (#619, #369)

The VFP encoder + selector arms existed but the decoder dropped every scalar
float op, so `synth compile -t cortex-m4f` honest-rejected f32 on every CLI
path. Worse, the "existing" VFP lowering was a non-functional prototype:
select_with_stack fell through to select_default's blind %16 round-robin
`alloc_vfp_reg`, so f32.const;f32.const;f32.add emitted VADD S2,S3,S4 over
garbage. This wires the real bridge for the phase-1 honest subset.

Delivered:
- Decoder un-drops in-scope f32 ops (add/sub/mul/div, 6 compares,
  i32.trunc_f32_s/u, f32.convert_i32_s/u, f32.const); f64 + rest stay dropped.
- Real VFP value stack in select_with_stack: StackVal::Float, S0..S15 allocator
  (no spilling — loud-bail on exhaustion), f32 op arms. Integer path untouched.
- AAPCS-VFP: f32 params homed in S0..S15 (params_f32 threaded decoder →
  CompileConfig → selector), f32 result in S0.
- FPU gate: m4f/m7/m7dp lower f32; m0/m3/r5 honest-reject with a clear message.
- .ARM.attributes Tag_FP_arch=VFPv4-D16 + Tag_ABI_VFP_args on FPU targets; reset
  handler enables CP10/CP11 in SCB->CPACR (M4F FPU is off at reset).
- Fix two latent encoder bugs surfaced by making float reachable: the
  signed/unsigned VCVT constants were swapped (convert_i32_s was unsigned), and
  the CPACR ORR mask encoded #0x07800000 instead of #0x00F00000.

Held (honest-subset, chosen): f64 (phase 2), f32 load/store + local.set/tee
(phase 1b), mixed f32/int param lists + f32-with-calls (loud-decline).

Oracle: scripts/repro/f32_vfp_619_differential.py — unicorn (ARM/Thumb, FPU
enabled) vs wasmtime, bit-exact on boundary values. RED on origin/main (compile
rejects, exit 1) → GREEN after (48/48, exit 0); m3 honest-reject asserted.
crates/synth-backend/tests/f32_hardfloat_619.rs locks the wiring in CI without
unicorn. Frozen anchors 10/10, #511 estimator oracle, claim gate 17/17 all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…te (#619)

FPU-target startup now writes SCB->CPACR (0xE000ED88) to enable CP10/CP11
(GI-FPU-002). The #649 i64-global-init + #687 stack-layout differentials
replay the real reset path under unicorn and faulted UC_ERR_READ_UNMAPPED on
that access — the System Control Space page was never mapped (real silicon
has the register; the codegen is correct). Map 0xE000E000 (4 KB) in both
harnesses. #649 + #687 + f32 differentials all green; frozen anchors 10/10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@avrabe
avrabe force-pushed the feat/619-vfp-f32-hardfloat branch from 687a249 to b9674ef Compare July 10, 2026 20:30
@avrabe
avrabe merged commit 6f3c2a7 into main Jul 10, 2026
37 checks passed
@avrabe
avrabe deleted the feat/619-vfp-f32-hardfloat branch July 10, 2026 20:42
avrabe added a commit that referenced this pull request Aug 5, 2026
…tly won

The second genuinely-ours rivet error, which the lane brief did not know
about because the local grep for it (`^  ERROR:`) misses filename-prefixed
diagnostics:

    gale-integration.yaml: ERROR: [GI-FPU-002] artifact id 'GI-FPU-002' is
    declared more than once: ./artifacts/verified-codegen-roadmap.yaml and
    ./artifacts/gale-integration.yaml — the second definition silently
    overwrites the first

This is the #893 class one level worse than a stale description: the
requirement was declared in gale-integration.yaml (`status: proposed`, the
original #369 ask) and AGAIN in verified-codegen-roadmap.yaml (`status:
implemented`, the phase-1 delivery record added by PR #705). rivet loaded
the `proposed` copy over the `implemented` one, so the traceability graph
reported GI-FPU-002 as NOT STARTED while README/CHANGELOG report #369
CLOSED, f32 complete v0.41, f64 complete v0.43, and VFP register-file
spilling shipped v0.53.

Resolved by MERGING, not deleting — the two copies carried disjoint edges
and disjoint evidence:

* Survivor: gale-integration.yaml. That is the id's namespace home (GI-002
  -> GI-FPU-001 -> GI-FPU-002 -> GI-FPU-VER-001 are one chain in that file;
  GI-FPU-002 was the ONLY GI-* artifact in the roadmap). It also already
  carried `derives-from GI-002`, `traces-to gale:369`, and the jess
  REQ-PIX-001 / AFD-024 Pixhawk linkage — all of which a straight delete of
  that side would have dropped. README names the roadmap the single source
  of truth for the VCR-* program's roadmap status, which GI-* is not.
* Folded in: the roadmap copy's six-point phase-1 DELIVERED list and its
  full verification-criteria (the f32_vfp_619_differential RED->GREEN
  evidence, the m3 honest-reject direction, the f32_hardfloat_619.rs unit
  lock, and the recorded unicorn VMRS FPSCR->APSR emulator gap).
* De-staled, since the merge had to pick one status anyway: `proposed` ->
  `implemented`, with the post-phase-1 evidence the roadmap copy predated —
  f64 complete v0.43 (#369 closed), v0.52 #869 inline i64<->float, v0.53
  #881 VFP spilling (109 rows bit-identical to wasmtime) — and the two
  residuals stated as loud declines rather than implied away
  (`f32.{ceil,floor,trunc,nearest}` pending a real VRINT.F32 after v0.54
  removed the unsound saturating-VCVT pseudo-op, and `i64.trunc_sat_f32_*`
  on single-precision FPUs).
* Where the duplicate was, the roadmap now carries a pointer comment
  explaining why the id is not defined there.

rivet: 52 -> 50 errors; NON-EXTERNAL errors 2 -> 0. Warning/info diagnostic
sets are byte-identical before/after (no new class introduced).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
avrabe added a commit that referenced this pull request Aug 6, 2026
… de-staled, VCR-VER-004 filed (#893) (#913)

* fix(rivet): VCR-DEC-003 traces-to an ARTIFACT, not a GitHub issue number

`traces-to: synth:396` was the one genuinely-ours rivet broken-link error:
`synth:396` reads as "artifact 396 in repo synth", and no such artifact
exists — an issue number used where an artifact id belongs.

The traceability intent is preserved rather than deleted: synth#396's own
body says "Tracked in rivet as VCR-COV-001, sibling to VCR-DBG-001", and
VCR-COV-001's title carries "(synth #396)". So the link retargets to
VCR-COV-001 (in-repo `traces-to` targets are already idiomatic in this
file — VCR-SEL-001, VCR-RA-001, VCR-MEM-001, …), and `synth-396` joins the
tags so the issue number stays discoverable as a reference instead of a
resolvable target.

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

* fix(rivet): GI-FPU-002 was declared TWICE — the `proposed` copy silently won

The second genuinely-ours rivet error, which the lane brief did not know
about because the local grep for it (`^  ERROR:`) misses filename-prefixed
diagnostics:

    gale-integration.yaml: ERROR: [GI-FPU-002] artifact id 'GI-FPU-002' is
    declared more than once: ./artifacts/verified-codegen-roadmap.yaml and
    ./artifacts/gale-integration.yaml — the second definition silently
    overwrites the first

This is the #893 class one level worse than a stale description: the
requirement was declared in gale-integration.yaml (`status: proposed`, the
original #369 ask) and AGAIN in verified-codegen-roadmap.yaml (`status:
implemented`, the phase-1 delivery record added by PR #705). rivet loaded
the `proposed` copy over the `implemented` one, so the traceability graph
reported GI-FPU-002 as NOT STARTED while README/CHANGELOG report #369
CLOSED, f32 complete v0.41, f64 complete v0.43, and VFP register-file
spilling shipped v0.53.

Resolved by MERGING, not deleting — the two copies carried disjoint edges
and disjoint evidence:

* Survivor: gale-integration.yaml. That is the id's namespace home (GI-002
  -> GI-FPU-001 -> GI-FPU-002 -> GI-FPU-VER-001 are one chain in that file;
  GI-FPU-002 was the ONLY GI-* artifact in the roadmap). It also already
  carried `derives-from GI-002`, `traces-to gale:369`, and the jess
  REQ-PIX-001 / AFD-024 Pixhawk linkage — all of which a straight delete of
  that side would have dropped. README names the roadmap the single source
  of truth for the VCR-* program's roadmap status, which GI-* is not.
* Folded in: the roadmap copy's six-point phase-1 DELIVERED list and its
  full verification-criteria (the f32_vfp_619_differential RED->GREEN
  evidence, the m3 honest-reject direction, the f32_hardfloat_619.rs unit
  lock, and the recorded unicorn VMRS FPSCR->APSR emulator gap).
* De-staled, since the merge had to pick one status anyway: `proposed` ->
  `implemented`, with the post-phase-1 evidence the roadmap copy predated —
  f64 complete v0.43 (#369 closed), v0.52 #869 inline i64<->float, v0.53
  #881 VFP spilling (109 rows bit-identical to wasmtime) — and the two
  residuals stated as loud declines rather than implied away
  (`f32.{ceil,floor,trunc,nearest}` pending a real VRINT.F32 after v0.54
  removed the unsound saturating-VCVT pseudo-op, and `i64.trunc_sat_f32_*`
  on single-precision FPUs).
* Where the duplicate was, the roadmap now carries a pointer comment
  explaining why the id is not defined there.

rivet: 52 -> 50 errors; NON-EXTERNAL errors 2 -> 0. Warning/info diagnostic
sets are byte-identical before/after (no new class introduced).

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

* fix(#893): VCR-RA-004 was `proposed` for a resolver that shipped in v0.11.38

Half of #893: v0.53's VFP-spilling lane tagged its work `VCR-RA-004`, while
artifacts/verified-codegen-roadmap.yaml still carried that id as `status:
proposed`. The brief offered two resolutions — mint a new id for the v0.53
work, or flip VCR-RA-004 to implemented. The evidence decides it, and it is
neither of the two things the ID-collision framing suggested: the resolver
VCR-RA-004 describes shipped in **v0.11.38**, three years of releases before
the lane that got blamed for overloading the id.

`synth_synthesis::parallel_move` is verbatim what the artifact asks for — a
pure, testable component that sequentializes a parallel move set with cycle
detection, scratch selection from dead registers, and a guaranteed-progress
fallback. The artifact's own tags already said `release-v0.11.38`; the
CHANGELOG names it twice (v0.11.38 "Cycle-safe parallel-move resolver
(`synth_synthesis::parallel_move`, VCR-RA-004)" and v0.11.39 "#327 —
VCR-RA-004's resolver (v0.11.38) breaks cycles via a stack-scratch cell").
Only the status field was never flipped. Minting a second id would have
created the collision the issue was trying to remove.

So: `proposed` -> `implemented`, with the evidence written down instead of
left in changelog prose —

* the algorithm and its progress discipline (the size bound and the
  strictly-shrinking pending set are `assert!`s in the resolver, so an
  unbounded path aborts rather than emitting);
* both consumers, each of which removed a real defect rather than only
  adding a component: v0.11.39 #327 arg-move marshalling (the old
  cycle-breaker demanded a callee-saved register AND miscompiled genuine
  2-swaps by duplicating a value), and v0.53 #881 VFP register-file spilling
  (the falcon `S0..S15 all live` wall) — which is precisely the work the
  v0.53 notes tagged VCR-RA-004;
* SWVER-022, a new sw-verification artifact linking `verifies` ->
  VCR-RA-004, so the right side of the V is closed by a typed link rather
  than by a paragraph. It records the run recipe and what each of the three
  criteria clauses is actually met by.

`implemented`, NOT `verified`, deliberately. The property test the criteria
demand does exist and does exactly what they specify — 2000 iterations over
R0..R8 alternating full random permutations with partial move sets, each
re-checked at scratch-set sizes 0/1/2 (6000 sequentializations) against a
reference parallel semantics, plus 12 directed shape tests — verified
locally, `cargo test -p synth-synthesis parallel_move` 13/13, real exit 0.
But the second pitfall the artifact names, split points landing inside hot
loops, is still bounded by ASSUMPTION (synth's straight-line segment scope)
rather than by a check that fails when segments widen. That residual is now
stated in both the requirement and SWVER-022 rather than implied away.

rivet: non-external errors still 0; warnings 104 -> 103 (VCR-RA-004's
"should be verified by at least one verification measure" WARN closed, no
new warning introduced). claim_check 37/37.

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

* fix(#893): VCR-SEL-005 spans THREE backends, and both ledger counts had drifted

Second half of #893. VCR-SEL-005's description still said the gate lowers
probes on "BOTH the ARM (Thumb-2) and RISC-V (RV32IMAC) selectors" — it has
covered a third, aarch64, since v0.53 (#883). SWVER-017, the verification
artifact that is supposed to be the right-side evidence for exactly this
requirement, carried the same claim in its title ("ARM vs RISC-V") and
described a two-selector ledger.

While correcting the backend count I checked the numbers the same documents
assert, and both were stale in the same direction — they described gaps that
have since CLOSED, which is the flattering direction and therefore the one
worth checking:

* The roadmap said "the KNOWN_DIVERGENCES ledger is now 5 Zbb + 16 new = 21
  entries". The array holds 18: `memory.size`/`memory.grow` closed in v0.50
  and `br_table` in v0.53 (#882).
* `known_divergences`'s own doc comment said 19 (it had accounted for v0.50
  but not #882).
* `aarch64_known_divergences`'s doc comment said "leaving the SEVEN below"
  over an array of 5 — v0.54 (#899) closed `global.get`/`global.set` and
  removed the entries without updating the prose above them.

All four now state what the arrays hold, with the counts' derivation written
out so the next drift is visible, and a note at each site that the count must
move with the array. The stale-entry check already forces a CLOSED gap to
retire its ledger line; nothing forced the PROSE ABOUT the ledger to move
with it, which is the #893 defect one layer up.

Also recorded, because it is the part of the third-backend leg that is not
just "one more backend": aarch64 gets a probed FLOAT/SIMD surface
(`a64_extended_surface`, floor `probed >= 100`) that ARM and RV32
structurally cannot have — float is `StructurallyExcluded` from their leg
because ARM float lowering is TARGET-parameterized (f32.add declines at
fpu=None, lowers at Single/Double) and RV32 has no FPU, whereas the aarch64
backend has one fixed host profile, so both directions are assertable and a
stale gap-claim is caught the same way a stale divergence is.

Changes are prose and doc-comment only — no test logic touched.
`cargo test -p synth-backend-riscv --test cross_backend_op_parity` 8/8, real
exit 0. rivet non-external errors still 0; claim_check 37/37.

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

* docs(roadmap): file VCR-VER-004 — a shipped North-Star component had NO roadmap entry

README calls artifacts/verified-codegen-roadmap.yaml "the single source of
truth for roadmap status". VCR-VER-004 shipped in v0.54 and appeared in the
CHANGELOG, the FEATURE_MATRIX template and the CI job list — but the roadmap
had no entry for it at all, so the one document the README points at for
"what is the state of the VCR-* program" was missing the release's headline
validator.

The entry records what it is and, more importantly, why it exists: v0.53
showed by mutation that emptying `cfg_exit_observable` makes the compiler
leave a return value in the WRONG REGISTER and that BOTH per-compilation
validators accept it (`validate_cfg_rewrite` -> Ok, VCR-RA-003 ->
Consistent). Only execution caught it. `abi_contract::validate_abi_contract`
is not a third file on the same axis — it differs on four axes (an obligation
that cannot be emptied because it is `RETURN_CONTRACT_REGS = [R0, R1]`
hard-named in its own source; forward rather than backward, so there is no
seed whose empty set is a vacuous fixpoint; evidence that is a VALUE compared
by greatest-fixpoint bisimulation rather than a name-pair; and a `(orig,
rewritten)` signature that takes nothing from the pass).

Its honest limit is in the entry, not implied away — all three residuals:

  (a) it GATES only the flag-off colouring allocator; on the default path it
      is a report-only audit held to a `Violated 0` CI floor, because gating
      a user's compile on a checker whose false-positive rate is measured
      rather than proven is a flip we have deliberately not taken;
  (b) memory is NOT in its obligation (complementary to
      `validate_cfg_rewrite`, not redundant with it);
  (c) THE OP MODEL IS STILL SHARED — def/use extraction runs through
      `liveness::reg_effect`, so a mismodeled op is a blind spot common to
      all three instruments. VCR-VER-004 closes the shared-CONTRACT hole, not
      the shared-OP-MODEL hole, and until `synth-verify`'s
      `ArmSemantics::encode_op` is pinned against it (VCR-ISA-001's
      Sail-derived semantics being the eventual anchor, now a typed
      `traces-to` link rather than a prose aside) "three independent
      validators" WOULD BE AN OVERCLAIM.

Shaped to match its two siblings VCR-VER-003 / VCR-VER-761 exactly:
`sys-verification`, `verifies -> VCR-001`, `method: translation-validation`,
`preconditions`/`steps`/`pass-criteria`. That inherits two diagnostics those
siblings already carry (the schema's `method` allowed-values does not list
`translation-validation`, and `pass-criteria` is not a declared
sys-verification field) — kept deliberately, because the fix for those is a
rivet schema decision about the whole family, not a divergent shape for one
member.

rivet: 50 errors, non-external 0 (unchanged). Warnings 103 -> 105; the delta
is exactly the three new-artifact diagnostics above, and the diagnostic-class
diff against the lane's baseline shows no new KIND.  claim_check 37/37.

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

* docs(README): the aarch64 crate row still said "integer subset"

`synth-backend-aarch64` has not been an integer subset since v0.54. The row
now names what actually ships and points at the generated feature matrix for
the exact surface rather than restating it (a second copy of that list is how
v0.54's cold review found a doc-honesty defect):

* the complete scalar f32/f64 surface (v0.54 #898 — rounding, FP memory, i64
  converts, guarded i64 truncations);
* bounds-checked linear memory (default `--safety-bounds software`, #865);
* WASM globals and `call_indirect` with all three §4.4.8 trap guards
  (v0.54 #899);
* direct calls and full control flow.

The row is the LAST place in README that described the backend by what it
could not do; the intro paragraph and the feature matrix were already current.
Note for whoever picks this up next: CLAUDE.md carries a byte-identical stale
copy of this row. It is deliberately NOT touched here — that file is agent
configuration and is not mine to edit on a lane brief.

claim_check 37/37 (the aarch64 rows in the generated matrix are template-
driven and unaffected — no generated doc was hand-edited).

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

* ci(#893): the Rivet Validation gate could not see either error this lane fixed

Both defects this lane repaired were sitting in a tree with `Rivet
Validation` green. That is not a coincidence — the job's filter had two holes,
and each one swallowed exactly one of them.

  (1) `grep "^  ERROR:"` anchored on two-space-indented lines. rivet prefixes
      SOME diagnostics with the source file instead
      (`gale-integration.yaml: ERROR: …`), so that entire class was invisible
      to the gate — including "artifact id X is declared more than once … the
      second definition silently overwrites the first". That is how
      GI-FPU-002 could be `implemented` in one file and `proposed` in another,
      with rivet resolving it to `proposed`, and nothing complained.

  (2) The cross-repo exemption `targets '.*:.*' which does not exist`
      exempted any target containing a COLON. `synth:396` contains a colon.
      So a broken link in our OWN graph — an issue number written where an
      artifact id belongs — was classified as an unresolvable link into an
      un-rivet'd sibling repo and waved through.

Fixed structurally rather than by allowlist: every line containing `ERROR:`
is now considered, and the exemption is "a target carrying a FOREIGN prefix",
with our own `synth:` prefix explicitly added back to the ours-count. A new
sibling repo therefore needs no edit here, and our own prefix can never slip
back into the exempt set. Failures now PRINT the offending lines instead of
only a count, so the next one is self-diagnosing.

RED-FIRST, end to end, by replaying this exact step (extracted from the YAML,
not paraphrased) against both trees:
  * pre-fix artifacts (65417c0): exit 1, "Found 2 rivet validation errors that
    are OURS (not cross-repo)", both named;
  * fixed artifacts (this branch): exit 0, with the expected cross-repo
    warning still emitted.

CAVEAT, stated rather than assumed: this was verified against local rivet
0.28.0, while the job pins 0.23.0. The duplicate-id diagnostic may not exist
in 0.23.0 at all, in which case hole (1)'s fix is latent protection rather
than an active check today; hole (2)'s fix is version-independent. If 0.23.0
emits some other filename-prefixed error we cannot see locally, this job is
where it will surface — and surfacing it is the point.

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

* docs(CHANGELOG): v0.55 L7 — traceability repair + the honest-N/A backlog

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

* fix(#893): GI-FPU-VER-002 — close the gap the GI-FPU-002 status flip revealed

Flipping GI-FPU-002 from `proposed` to `implemented` made rivet START checking
its lifecycle coverage, which correctly reported that the requirement had NO
verification artifact at all. The gap was not created by the flip — the wrong
status was HIDING it, which is the same failure mode as the duplicate id
itself, one rule further down.

The evidence already existed and was already named in the requirement's own
criteria; it simply had no typed `verifies` link. GI-FPU-VER-002 is that link,
and it records what the verification actually is rather than asserting that
some exists:

* the f32 execution differential (48/48 bit-exact vs wasmtime on cortex-m4f,
  symbols read from the ELF SYMTAB per #489 rather than from host-dependent
  `synth disasm` text, FPU genuinely enabled via CPACR + FPEXC.EN);
* the HONEST-REJECT direction in the same harness (cortex-m3 must still
  refuse) — a one-directional differential would pass equally well on a
  compiler that had quietly widened the FPU gate;
* the unit-level pins that need no emulator (AAPCS-VFP S0/S1 homing, the
  swapped-VCVT signedness fix);
* the v0.53 #881 spilled-VFP differential (109 rows, NaN-aware per WASM
  §4.3.3, internal `bl` resolved by a REAL link so an unresolved relocation
  cannot be silently skipped as a pass);
* and the one part of the surface whose evidence is encoding-level ONLY —
  the f32 comparisons, because unicorn does not model the VMRS FPSCR→APSR
  flag transfer. Recorded, not omitted.

Deliberately shaped `method: automated-test` + `steps.run`/`steps.coverage`
rather than mirroring GI-FPU-VER-001's `method: test` + `pass-criteria`, which
produce a WARN and an INFO against the schema. This adds ZERO new diagnostics.

MEASURED, prompted by review asking whether `rivet coverage` — the SECOND step
of the same CI job, which I had not exercised — moved:

  rivet coverage, real exit 0 both sides
  swe1-has-verification (sw-req)   31/60 (51.7%)  ->  33/60 (55.0%)
  swe6-verifies-swe1               32/32          ->  34/34
  sys5-verifies-sys2               49/49          ->  50/50
  Overall (weighted)               90.3%          ->  90.7%
  VCR-RA-004 and GI-FPU-002 both drop off the "lacking verification" list.

Full diagnostic diff for the whole branch vs main is now exactly:
  −2 ERROR (both ours: synth:396, the duplicate id)
  −2 WARN  (GI-FPU-002 and VCR-RA-004 "should be verified by", both closed)
  +2 WARN, +1 INFO (all three VCR-VER-004's, all of kinds its sibling
                    sys-verification artifacts already carry)
So: errors 52 -> 50 with ours 2 -> 0, and warnings net UNCHANGED at 104.

Lifecycle coverage gaps 54 -> 56 — honest, not a regression: GI-FPU-002 and
VCR-RA-004 are newly CHECKED because they are no longer `proposed`. Both were
absent from the baseline list only because a wrong status exempted them.

cargo fmt 0 / clippy 0 / test --workspace 0 (2675 passed) / claim_check 37/37.

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

* docs(CHANGELOG): record the two verification artifacts + the rivet coverage delta

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

* docs(roadmap): disambiguate the aarch64 ledger count (5 entries, not 4 named)

The prose grouped `local.set`+get and `local.tee` on a param local as one
phrase over two separate ledger entries, so the sentence read as four items
beside the count 5 — a small instance of exactly the prose-vs-array drift this
paragraph exists to prevent.

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

* docs(CLAUDE.md): de-stale the three aarch64 claims

L7 correctly declined to touch this on a lane brief (agent configuration, not
lane scope) and flagged it instead. Coordinator picking it up.

CLAUDE.md carried a byte-identical copy of the stale README row the v0.54 cold
review found, plus a third instance nobody had spotted:

1. header: "AArch64 (host-native, integer subset)" — the scalar float surface
   is complete as of v0.54.
2. crate map: "integer subset" — now i32/i64 core, complete scalar f32/f64,
   globals, call_indirect, bounds-checked linear memory.
3. VCR-VER-003 note: "AArch64 is N/A (no linear-memory ops in the integer
   subset)". The VERDICT is still right, the REASON is false — aarch64 has had
   bounds-checked linear-memory load/store since v0.52 (#865). It is N/A because
   it emits no data section and REFUSES data-carrying modules loudly (v0.53), so
   there is no served-vs-runtime image to compare. A correct conclusion resting
   on a false premise is the harder version of this defect: the sentence reads
   fine and the reasoning has rotted.

Fourth copy of a list this project keeps duplicating (oracle, matrix row,
CHANGELOG, CLAUDE.md). Generating the prose from the executable decline list is
the standing fix; #911 is the nearest tracked version of it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L

* docs(CHANGELOG): record the CLAUDE.md aarch64 de-staling, incl. the rotted premise

0f0c232 landed the CLAUDE.md half of the aarch64 doc fix but no release note.
Adding one — and specifically calling out its third finding, which is the only
one of the four that is not a plain stale string:

VCR-VER-003's aarch64 N/A note gave a FALSE REASON for a TRUE verdict ("no
linear-memory ops in the integer subset" — aarch64 has had bounds-checked
linear-memory load/store since v0.52 #865). It is N/A because it emits no data
section and refuses data-carrying modules loudly, so there is no
served-vs-runtime image to compare.

That failure mode deserves the note more than the two string copies do: a stale
"integer subset" reads wrong and invites a check, whereas a correct conclusion
resting on a rotted premise still reads fine, so nothing prompts one. Both
underlying facts re-verified against the generated feature matrix before writing
this.

claim_check 37/37 (CLAUDE.md is pinned by three ledger entries; unaffected).

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

---------

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