Repository navigation
RQ-59-GLOBALINIT (#1052): ARM relocatable path REFUSES global initializers it does not materialize — the fourth silent drop, plus teeth for the harness that was green over it - #1058
Conversation
…dropped global initializers Six refusal tests FAIL on current main (exit 0 where a loud refusal is required): nonzero i32/i64 const inits, non-const integer init, cortex-m4 + cortex-r5, import-forced ET_REL, and --native-pointer-abi with no linear memory (region never emitted). Four green controls pass: zero inits, the self-contained materialization path (#649), --native-pointer-abi with memory (#237), and an untouched float global (GI-FPU-001 lane). The assertion shape is deliberate: NON-ZERO EXIT + a reason naming the global initializers — NOT "the init value is absent from the object", which was already true on the broken behaviour and is exactly how the bug survived the #643 harness's zeroed-globals fixture. Refs #1052 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…tializers it does not materialize The plain --relocatable path compiled (global (mut i32) (i32.const 42)) + a global.get export to an object whose entire text was push; ldr.w r0,[r9]; pop — 0x2A nowhere in the ELF, exit 0. OUTSIDE the documented embedder contract: R9 is documented as the globals-table BASE only (select_with_stack.rs "R9 = globals base"); no sentence assigns initializer EVALUATION to the embedder, in contrast to the data-segment sentence behind #1041's refusal. Every other path ships inits (#237, #851), materializes them at reset (#649), or loud-skips (#643). The guard's predicate is "does the initial VALUE reach the object", not "which flag was passed": it refuses nonzero i32/i64 const inits and non-const integer init exprs on the plain path, AND --native-pointer-abi without a linear memory (no __synth_globals region is emitted there — the object shipped an undefined symbol and no init image). All-zero const inits never refuse (a zeroed embedder table is their correct initial state — the #643 fixture and every zeroed-scratch harness stay green). Float/v128 globals stay the GI-FPU-001 (#369)/#680 loud-skip lane; a new WasmGlobal::float_or_v128 field lets the guard tell an integer non-const init (silent-drop class) from a float None (access already loud-skips). --embedder-global-init is the explicit escape hatch (the #952/#1041 shape): it declares the embedder evaluates the module's global initializers and seeds the R9 table before any export runs. Emitted bytes are identical either way — the refusal-test suite pins the flagged object byte-identical to a zero-init twin's object. Fallout measured, not assumed: 2 tests in cabi_arena_bind_418.rs (the two ET_REL seam tests, which model exactly the host-instantiates-the- module contract and already passed --embedder-data-init) gain the new flag; 0 python harnesses (every nonzero-init repro fixture compiles --native-pointer-abi or self-contained). Materializing the inits on this path is documented capability follow-on work (v0.60), not this fix. All 11 refusal tests green (6 were red on the parent commit). Refs #1052 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… could notice
The sole relocatable-path globals harness mapped a ZEROED region
("# zeroed globals table (inits are 0)") and its fixture's initializers
ARE zero — so it was green over the #1052 silent drop for its entire
existence: it structurally could not distinguish "the initializers were
written" from "the initializers were all zero anyway" (the #1055/#1054
weak-checker family).
New section check_nonzero_init_contract_1052 with the NONZERO-init
fixture globals_init_1052.wat, three legs:
1. refusal — plain --relocatable must exit non-zero naming the global
initializers (NOT "the value is absent from the object", which was
true on the broken behaviour too);
2. contract — with --embedder-global-init the harness acts as the
acknowledged embedder: it seeds the R9 table from the module's OWN
initial values (read back via wasmtime exported globals — the
VCR-VER-003 served-vs-runtime shape, no second hardcoded copy) and
the execution differential must agree with wasmtime;
3. non-vacuity — the SAME object on a zeroed table must DIVERGE from
wasmtime; if it ever stops diverging the fixture has gone vacuous
and the harness fails itself.
TEETH PROVEN by execution: against the pre-fix compiler the harness reds
("[BUG] refusal: exit=0 ... SILENT-DROP: compiled exit 0"); against the
fixed compiler all legs pass (seeded get_a=0x2a, get_b=0x1122334455667788
both words, zeroed-table divergence confirmed). ci-checks floor
(emulations >= 28) untouched — the new legs only add emulations.
Refs #1052
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…s truncated by fail-fast The initial fallout count (2 tests) was WRONG: cargo test stops at the first failing binary, so the first suite run's failure list was cut off. Re-measured with --no-fail-fast plus executing all 37 unique ci.yml 'synth compile' invocations against the fixed binary: - 9 test files (~28 tests) compile frozen SP-global fixtures as harness-embedders and gain --embedder-global-init (the #1049 shape): cmp_select_fusion_census, cmp_select_two_move_coverage, dwarf_debug_line_emit_394, flag_flip_wave_242, frozen_codegen_bytes, recovery_stats_242, shift_mask_elide_686, spill_baseline_pressure_390 (+ cabi_arena_bind_418 from the previous commit). frozen_codegen_bytes green with the flag IS the frozen-anchor proof: the bit-identical oracle passes, so no emitted byte moved. - 2 python harnesses: arm_corpus_sweep_973 (corpus compile coverage restored to 150/162 over its 144 floor) and gpio_thin_846_differential (loom $__stack_pointer; runner maps R9 itself). Same acknowledgment comment shape as #1041's. - 3 ci.yml compile steps: flight-seam (x2), control-step, call_6_7args. Re-probe after: 0 refusals, 0 other failures. - Guard ORDER fix: the #1052 guard now runs AFTER the #739 shadow-stack-size flag-honesty guard so that more specific refusal keeps its message (shadow_stack_size_without_region_refuses_739). - Over-refusal refined away: native-pointer-abi fixtures whose globals are declared but never accessed (no __synth_globals relocation, no region emitted — the call_5args shape) are INERT: the object has no globals surface, nothing can read the dropped value, so they compile. A module that DOES access globals with no region still refuses (pinned by native_pointer_abi_without_memory_refuses_loudly). On the plain path R9 accesses carry no relocation, so presence-based refusal stands, matching #1041. Gates: cargo test --workspace --no-fail-fast TRUE_EXIT=0 (149 green suites, 0 failing binaries); clippy -D warnings clean; fmt clean; claim_check 50/50; model_coverage_audit ok; corpus sweep PASS; gpio/643/call_5args harnesses PASS. Refs #1052 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
eab2c6f to
63e9299
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
[coordinator] Holding this PR — it reddens All 9 required contexts are SUCCESS, so this would have merged under a count-based rule. It is held because the failing check is not on the advisory allowlist (only DiagnosisThe failing job is titled All four trap oracles reproduce GREEN locally on this branch, each at its non-vacuity floor exactly: 8/8, 8/8, 28/28, 49/49 emulations. Baseline comparison — this is a regression, not pre-existingSame script, same command, same host, main vs this branch: Both met the floor — the branch ran the work and the assertions failed. What actually breaksSix fixtures now refuse to compile, on this PR's own new refusal: And the consequence is worse than six failures. The oracle's own output: The refused fixtures were exactly the ones exercising the join colouring. So the refusal does not merely break compiles — it hollows out an unrelated allocator oracle, which then correctly refuses to pass rather than reporting a green vacuous run. That is the #910 non-vacuity design doing its job, and the reason this must not be merged and patched afterwards. The refusal is right; the collision is what needs decidingNothing here argues against the refusal. Silently dropping Options, in the order I'd rank them:
Option 2 needs an explicit "still non-vacuous" demonstration, not an assertion. Reproduce(Trap for anyone reproducing: do not redirect Everything else on this PR looks good — the refusal test and the extended |
… embedder-global-init contract — non-vacuously The #1052 refusal reddened vcr_dec_001_graph_alloc_differential.py: six fixtures (control_step, flight_seam, flight_seam_flat, gust_kernel, sret_decide, msgq_put_359) carry nonzero global initializers, and they were precisely the fixtures exercising the join colouring — the oracle correctly reported itself VACUOUS (diverging=0) rather than passing hollow. Fix is the established escape-hatch shape (#1041 --embedder-data-init, one line above): the harness owns the R9 globals table, so it acknowledges initializer evaluation explicitly with --embedder-global-init. Evidence: * bytes unmoved: section (1) still pins every fixture's flag-off text against the frozen goldens (control_step 8b3f1f6fe3a4 len=288 OK, ...), and sha256(with flag) == sha256(without) on a module that compiles either way (signed_div_const, 9d85e7b4...c57 both). * teeth restored, not silenced: applying fixtures 9 (>= 2), diverging fixtures 4 (>= 2), exit=0 — the join colouring reaches the corpus again. * arm_reloc_globalinit_refusal_1052 (11 tests) and i64_globals_643_differential.py stay green (refusal remains the default; the flag is opt-in acknowledgment only). Refs #1052 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
#1058 merged as e1a7b57. Verified the fix is ON MAIN rather than inferred from the merge list: `embedder_global_init` appears 9x in crates/synth-cli/src/main.rs and crates/synth-cli/tests/arm_reloc_globalinit_refusal_1052.rs exists there. Second flip this release that NO PR owned (RQ-59-TIERCENSUS was the first). That is the shape v0.58 warned about — a status left `proposed` over shipped code makes the release-readiness query under-report scope at the cut — and it is worth noting that it went unnoticed twice in one release even with people watching for it. Folded HERE rather than into a new PR for two reasons: GitHub-hosted CI capacity is the binding constraint (#1062), and this PR already rewrites every line of release-v0.59.yaml. The PR that makes v0.59 QUERYABLE should also make it ACCURATE. Not duplicated: RQ-59-TIERCENSUS and RQ-59-I64SHIFT are already carried by #1051, so flipping them here too would put competing edits to the same file in two open PRs. Verified the way #1064 taught — BY ID, not by absence of an error: RQ-59-GLOBALINIT -> (system-req) [implemented] artifacts measured 464 = ARTIFACT_FLOOR 464 (a status flip moves no count) CI-filter OURS=0, claim_check exit 0 v0.59 now reads 14/17 on this branch; PARTIALCENSUS, TIERCENSUS and I64SHIFT land with #1051. Refs #1052, Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
#1058 merged as e1a7b57. Verified the fix is ON MAIN rather than inferred from the merge list: `embedder_global_init` appears 9x in crates/synth-cli/src/main.rs and crates/synth-cli/tests/arm_reloc_globalinit_refusal_1052.rs exists there. Second flip this release that NO PR owned (RQ-59-TIERCENSUS was the first). That is the shape v0.58 warned about — a status left `proposed` over shipped code makes the release-readiness query under-report scope at the cut — and it is worth noting that it went unnoticed twice in one release even with people watching for it. Folded HERE rather than into a new PR for two reasons: GitHub-hosted CI capacity is the binding constraint (#1062), and this PR already rewrites every line of release-v0.59.yaml. The PR that makes v0.59 QUERYABLE should also make it ACCURATE. Not duplicated: RQ-59-TIERCENSUS and RQ-59-I64SHIFT are already carried by #1051, so flipping them here too would put competing edits to the same file in two open PRs. Verified the way #1064 taught — BY ID, not by absence of an error: RQ-59-GLOBALINIT -> (system-req) [implemented] artifacts measured 464 = ARTIFACT_FLOOR 464 (a status flip moves no count) CI-filter OURS=0, claim_check exit 0 v0.59 now reads 14/17 on this branch; PARTIALCENSUS, TIERCENSUS and I64SHIFT land with #1051. Refs #1052, Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…pervised release Measured on v0.59, in a release where I was EXPLICITLY watching for this class because v0.58 had been burned by it: RQ-59-TIERCENSUS #1047 merged; no open PR flipped it RQ-59-GLOBALINIT #1058 merged; no open PR flipped it RQ-59-PARTIALCENSUS #1051 merged and did not flip its OWN status All three caught by hand and folded into unrelated PRs. Three misses in one supervised release is a coupling problem, not an attention problem: nothing mechanically ties "the code landed" to "the artifact says so". It fails SILENTLY and in the direction that looks like LESS work — a stale `proposed` makes v0.58's release-readiness query under-report scope at the cut, so a shipped artifact can be omitted from the notes and the evidence package. The compounding factor is why this is not just "add a checklist": #1064 showed the v0.59 file was never LOADED by rivet, so for most of the release the readiness query could not have caught a stale status even if run. The two defects hid each other — the file was invisible, so the statuses inside it were unfalsifiable. Three candidate fixes recorded with their tradeoffs rather than one asserted; deriving status from evidence is most in keeping with this release's theme, a CI check that a MERGED-PR artifact is not still `proposed` is the cheapest thing that would have caught all three. Kill-criterion: replay v0.59's three misses — a fix that would not have caught all three has not addressed the measurement. Validated BY ID: RQ-60-FLIPCOUPLE resolves; CI-filter OURS=0. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…he 8 links it hid, and a load floor (#1064) (#1065) * fix(rivet): release-v0.59.yaml was never in the graph — convert to the generic-yaml schema rivet's generic-yaml source denies unknown top-level fields and accepts only 'artifacts:'. release-v0.59.yaml carried 'metadata:' + 'requirements:', so the whole file was skipped and all 16 RQ-59-* artifacts were invisible — the release-readiness query answered about artifacts rivet could not see. Fold the metadata block into comments (the v0.56-58 shape) and rename requirements: -> artifacts:. This EXPOSES 8 pre-existing wrong-type derives-from links (fixed in the next commit — this commit alone would turn Rivet Validation red). Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * fix(rivet): repair the 8 wrong-type derives-from links Part 1 exposed A system-req must derive-from a stakeholder-req. The newly-visible v0.59 artifacts carried 8 derives-from links at sw-req / system-req / sys-verification targets. Per-artifact disposition (reason inline in the file next to each link): RQ-59-REACH -> BR-001, VCR-REACH-001 kept as refines RQ-59-MEASURE -> BR-002 (flip verdict = performance dominance), VCR-DEC-001 kept as refines RQ-59-WCETI64 -> BR-001 (DAL-A timing artifact), VCR-WCET-001 kept as traces-to (it is a sys-verification; refines would invert the V) RQ-59-CRSWEEP -> BR-001, NFR-002 DROPPED (board hygiene is evidence integrity, not product reliability) RQ-59-MINORHOLD -> BR-001 (qualified toolchain / deterministic builds), NFR-002 DROPPED RQ-59-FRESHNESS -> BR-001 (stale-read-as-fresh forges evidence), NFR-002 DROPPED RQ-59-PARTIALCENSUS -> BR-001 (the policy fork is a safety question), NFR-002 KEPT as refines (it measures NFR-002's real-world shortfall) RQ-59-TIERCENSUS -> BR-001 (where formal verification does not reach), NFR-002 KEPT as refines rivet validate: exit 0, Result: PASS, 0 errors; CI-filter OURS = 0. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * ci(rivet): non-vacuity floor on artifacts loaded — a skipped source file can no longer pass Part 3 of #1064. The Rivet Validation gate greps validate output for ERROR: lines, so a release file rivet SKIPS entirely (non-schema top-level keys) contributes zero errors and the gate stays green precisely because the file is invisible — validation did LESS work and nothing noticed (same shape as #1012 one level up, and the sixth consecutive release where the defect was in a checker). Pin a floor on artifacts loaded (the scripts/oracle_run.py discipline: floor=N measured=N, the number may only rise). Measured under the CI-pinned rivet v0.23.0 with the externals block stripped — this job's own conditions: 463 local artifacts. Fails closed if the count line ever disappears from 'rivet list' output. RED-FIRST, replayed against the step's exact logic under rivet 0.23.0 (and cross-checked under 0.32.0, identical): break: sed 's/^artifacts:/requirements:/' release-v0.59.yaml red: artifact-load floor=463 measured=447 -> exit 1 (the OURS error-grep stayed at 0 — the old gate is structurally blind to this class) restore: artifact-load floor=463 measured=463 -> exit 0 When a PR adds artifacts, raise the floor in the same PR (visible-diff rule); a fall means a source went invisible — find it, never lower the floor. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * ci(#1064): tighten ARTIFACT_FLOOR 463 -> 464 after #1056 landed GLOBALINIT The floor was measured before #1056 merged RQ-59-GLOBALINIT to main. A floor only fails BELOW itself, so 464-measured against 463-pinned would have passed — but loose. The guard's own rule is that a PR adding artifacts raises the floor in the same PR, so leaving it slack by one would be the first crack in exactly the discipline this gate exists to establish. Re-measured on this branch rebased onto main (17 v0.59 artifacts, GLOBALINIT included): 464. Re-verified the guard is still potent at the new value by reintroducing the original #1064 defect: GREEN: measured=464 floor=464 RED : metadata:/requirements: restored -> measured=447 -> FIRES CI-filter OURS=0, claim_check exit 0. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * chore(rivet): RQ-59-GLOBALINIT is implemented — the second orphaned flip #1058 merged as e1a7b57. Verified the fix is ON MAIN rather than inferred from the merge list: `embedder_global_init` appears 9x in crates/synth-cli/src/main.rs and crates/synth-cli/tests/arm_reloc_globalinit_refusal_1052.rs exists there. Second flip this release that NO PR owned (RQ-59-TIERCENSUS was the first). That is the shape v0.58 warned about — a status left `proposed` over shipped code makes the release-readiness query under-report scope at the cut — and it is worth noting that it went unnoticed twice in one release even with people watching for it. Folded HERE rather than into a new PR for two reasons: GitHub-hosted CI capacity is the binding constraint (#1062), and this PR already rewrites every line of release-v0.59.yaml. The PR that makes v0.59 QUERYABLE should also make it ACCURATE. Not duplicated: RQ-59-TIERCENSUS and RQ-59-I64SHIFT are already carried by #1051, so flipping them here too would put competing edits to the same file in two open PRs. Verified the way #1064 taught — BY ID, not by absence of an error: RQ-59-GLOBALINIT -> (system-req) [implemented] artifacts measured 464 = ARTIFACT_FLOOR 464 (a status flip moves no count) CI-filter OURS=0, claim_check exit 0 v0.59 now reads 14/17 on this branch; PARTIALCENSUS, TIERCENSUS and I64SHIFT land with #1051. Refs #1052, Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * chore(rivet): RQ-59-PARTIALCENSUS is implemented — v0.59 reads 17/17 #1051 merged as f1e2e7b but did not flip its OWN status. THIRD orphaned flip this release (RQ-59-TIERCENSUS, RQ-59-GLOBALINIT, now this one), all in a release where I was explicitly watching for the class. Three in one release says the coupling between "the fix merges" and "the status flips" is too manual to be reliable — filed as a v0.60 concern alongside RQ-60-ARTIFACTSPLIT rather than noted and forgotten again. Verified the work is ON MAIN, not inferred: scripts/repro/partial_census_1017.py exists there and carries its `# ci-status: manual (measurement)` declaration. WITH THIS, v0.59 IS 17/17 — and for the first time that number is a QUERY rather than a YAML read. Per-artifact, via `rivet validate --explain <id>`: resolved AND implemented: 17 / 17 (failures: 0) That distinction is the whole of #1064: before this PR's schema fix, the file was skipped entirely and `rivet validate --explain RQ-59-POPCNT` answered "artifact not found" while a YAML read happily reported statuses. A count read out of a file rivet never loaded is not a readiness metric. artifacts measured 464 = ARTIFACT_FLOOR 464 (status flips move no count) CI-filter OURS=0, claim_check exit 0 Refs #1017, Refs #1064 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>
…pervised release Measured on v0.59, in a release where I was EXPLICITLY watching for this class because v0.58 had been burned by it: RQ-59-TIERCENSUS #1047 merged; no open PR flipped it RQ-59-GLOBALINIT #1058 merged; no open PR flipped it RQ-59-PARTIALCENSUS #1051 merged and did not flip its OWN status All three caught by hand and folded into unrelated PRs. Three misses in one supervised release is a coupling problem, not an attention problem: nothing mechanically ties "the code landed" to "the artifact says so". It fails SILENTLY and in the direction that looks like LESS work — a stale `proposed` makes v0.58's release-readiness query under-report scope at the cut, so a shipped artifact can be omitted from the notes and the evidence package. The compounding factor is why this is not just "add a checklist": #1064 showed the v0.59 file was never LOADED by rivet, so for most of the release the readiness query could not have caught a stale status even if run. The two defects hid each other — the file was invisible, so the statuses inside it were unfalsifiable. Three candidate fixes recorded with their tradeoffs rather than one asserted; deriving status from evidence is most in keeping with this release's theme, a CI check that a MERGED-PR artifact is not still `proposed` is the cheapest thing that would have caught all three. Kill-criterion: replay v0.59's three misses — a fix that would not have caught all three has not addressed the measurement. Validated BY ID: RQ-60-FLIPCOUPLE resolves; CI-filter OURS=0. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…rtifacts (#1070) * plan(v0.60): scope the release — "Derive what you check against" Five artifacts. Both halves of the theme were EARNED during v0.59 rather than asserted, which is why this file leads with the evidence: RQ-60-CANARY VCR-TIER-001 increment 1 (delivered, PR #1061). Its immediate finding is the theme's best argument: the census's OWN hand-maintained `declared_temps` column declared the #1048 miscompile LEGAL — `&[R5]` (rm_hi) for the i64 shifts, `&[R3]` (rnhi) for the bit-counts, exactly the registers the defect clobbered. A checker that mirrors the defect it exists to catch. RQ-60-CFOBLIG #1057 (gale). 48% of a real object is covered by NEITHER proof half, and the cause is one level deeper than the issue could see: `Inductive wasm_instr` has NO control-flow constructor at all. There is no obligation for `BrIf` because the model has no `BrIf`. Verified before scoping — gale asked to be corrected and was right to. RQ-60-A64IMPORT VCR-REACH-002. AArch64 accepts 13/805 real modules (1.6%). Import dispatch (~121 modules) is synth's OWN ARM `--relocatable` undefined-symbol design ported, so it is a port of a shipped pattern, not a design question. RQ-60-RACOST VCR-RA-011. Cost model + tied operands, NOT a better search — with the ruled-out alternatives recorded so they are not re-litigated. RQ-60-ARTIFACTSPLIT #1059. The single-file artifact write surface that silently dropped a trace link during the v0.59 wave. Validated with the TYPED oracle, not a permissive one — the lesson from the v0.59 corruption this file's last artifact is about: rivet validate: OURS=0 (main baseline 0; rivet FAILs on main by construction) duplicate-key-STRICT loader: 5 artifacts, no duplicate ids structural: every artifact carries its own links: and a non-empty issue: release field consistent: {v0.60} The structural check is the mitigation RQ-60-ARTIFACTSPLIT proposes, run here by hand against this file. Refs #1021, Refs #1057, Refs #1017, Refs #242, Refs #1059 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * plan(v0.60): fix the schema — this file had the #1064 defect I filed I wrote release-v0.60.yaml by copying v0.59's shape, so it carried the exact defect #1064 is about: top-level `metadata:` + `requirements:` instead of `artifacts:`. rivet's generic-yaml DENIES unknown top-level fields, so the whole file was skipped with a WARN carrying no `ERROR:` prefix. Which means my own validation of this file was VACUOUS. I ran `rivet validate`, got OURS=0, and reported it clean — but rivet had skipped the file entirely. Zero errors from a file that never loaded is not zero errors. Same shape as #1012 one level up, and the reason #1064's fix pins a FLOOR on artifacts loaded rather than only grepping for errors: a validator doing LESS work must fail, not pass quietly. Converted metadata: -> comments (v0.58's shape), requirements: -> artifacts:. Verified BY ID rather than by absence of an error — the distinction that hid this in the first place: RQ-60-CANARY -> RQ-60-CANARY (system-req) [proposed] RQ-60-CFOBLIG -> RQ-60-CFOBLIG (system-req) [proposed] RQ-60-A64IMPORT -> RQ-60-A64IMPORT (system-req) [proposed] RQ-60-RACOST -> RQ-60-RACOST (system-req) [proposed] RQ-60-ARTIFACTSPLIT -> RQ-60-ARTIFACTSPLIT (system-req) [proposed] CI-filter OURS = 0. Artifact count on this branch 447 -> 452 (+5); once #1065 lands the v0.59 fix the same 5 lift 463 -> 468, so this PR must raise ARTIFACT_FLOOR to 468 when it rebases onto #1065. All 5 already derive-from BR-001 (the correct stakeholder-req type), so unlike v0.59 no link retargeting is needed. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * plan(v0.60): triage RQ-60-WCETKEY (#1063) — the hints seam has no key for internal functions gale (fathom) measured it on the E2 dissolved gust:os composite: synth-wcet-v1 takes its `name` from the EXPORT SECTION and falls back to `func_<index>` for every internal function, so 7 of 13 `loop` declines cannot be hinted at all — and `loop` is 13 of 28 declines with 11 `callee-unbounded` cascades behind it. WORSE THAN "NOT HINTABLE": a hint keyed on `func_22` SILENTLY RETARGETS when an edit shifts the index space. It still parses and still keys onto a function — just not the one the author verified. synth always emits its own DERIVED ceiling rather than the raw hint, so the bound stays synth-derived; what a mis-keyed hint corrupts is the OPT-IN, converting a decline for a function whose shape nobody looked at. An index is not an identity. gale pre-empted the obvious dismissal: rebuilding with `meld fuse --preserve-names` yields a BYTE-IDENTICAL .wcet.json while populating the name section 0 -> 59 of 68. synth carries the names and ignores them. Recorded with the caveat they volunteered, because it must shape the design: the names are v2 Rust mangling carrying a NON-content-derived crate disambiguator, and scry measured 43-45% of their function identities churning per build for exactly that reason (scry#123/#137). Better than an index, still not stable — fixing the blocker with a key that churns every build would trade an unaddressable decline for an unreliable one. Their kill-criterion adopted verbatim: a hints file keyed on the name-section name of one of those seven converts its `loop` decline to `hint-verified`, or is rejected with a NAMED reason. Being ignored because the key never matches is the failure. Validated the way #1064 taught — BY ID, not by absence of an error: all 6 v0.60 artifacts resolve via `rivet validate --explain`; CI-filter OURS=0; duplicate-key-strict loader clean; every artifact carries its own links: and issue:. Refs #1063 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * plan(v0.60): RQ-60-FLIPCOUPLE — three orphaned status flips in one supervised release Measured on v0.59, in a release where I was EXPLICITLY watching for this class because v0.58 had been burned by it: RQ-59-TIERCENSUS #1047 merged; no open PR flipped it RQ-59-GLOBALINIT #1058 merged; no open PR flipped it RQ-59-PARTIALCENSUS #1051 merged and did not flip its OWN status All three caught by hand and folded into unrelated PRs. Three misses in one supervised release is a coupling problem, not an attention problem: nothing mechanically ties "the code landed" to "the artifact says so". It fails SILENTLY and in the direction that looks like LESS work — a stale `proposed` makes v0.58's release-readiness query under-report scope at the cut, so a shipped artifact can be omitted from the notes and the evidence package. The compounding factor is why this is not just "add a checklist": #1064 showed the v0.59 file was never LOADED by rivet, so for most of the release the readiness query could not have caught a stale status even if run. The two defects hid each other — the file was invisible, so the statuses inside it were unfalsifiable. Three candidate fixes recorded with their tradeoffs rather than one asserted; deriving status from evidence is most in keeping with this release's theme, a CI check that a MERGED-PR artifact is not still `proposed` is the cheapest thing that would have caught all three. Kill-criterion: replay v0.59's three misses — a fix that would not have caught all three has not addressed the measurement. Validated BY ID: RQ-60-FLIPCOUPLE resolves; CI-filter OURS=0. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * plan(v0.60): RQ-60-VFPPRESSURE (#1069) — three named functions block a complete falcon M7 cascade jess filed it with a fully public repro and measured it on a real RT1176 Renode model (memory byte-exact, 214,696 instructions retired). 2 of 5 cascade stages export; the loop cannot close on target without the other three. VERIFIED ALL THREE CLAIMS against current code before scoping — they measured 0.55.0, we shipped 0.59.0: * the S pool IS `[bool; 16]` = S0..S15; callee-saved S16..S31 / D8..D15 are absent and would roughly double it * NO __aeabi_ul2f/l2f/ul2d/l2d routing exists anywhere; #869's acceptance comment described exactly that route and inline-f64 shipped instead * has_double_fpu() gates f64 on FPUPrecision::Double, i.e. m7dp only REPRODUCED ON 0.59.0, minimal case sharper than the cascade: (func (param i64) (result f32) (f32.convert_i64_u (local.get 0))) m4f -> DECLINED "scalar f64 requires a double-precision FPU target" m7dp -> compiles A function with NO f64 in its signature is refused for needing f64, because our own lowering introduces it. A reach failure of our own making. HONEST RESIDUAL IN MY OWN VERIFICATION, recorded so a lane does not inherit a false premise: sub-problem 1 (S-exhaustion) is confirmed by CODE INSPECTION ONLY. My deep-f32 fixture compiled fine at 60 bytes — not deep enough to exhaust S0..S15. A lane must build a fixture that actually reddens phase 1 before claiming to fix it. Refs #1069, Refs #869 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * ci(#1064): raise ARTIFACT_FLOOR 464 -> 472 for v0.60's 8 artifacts The gate's own rule: a PR that ADDS artifacts raises the floor in the SAME PR, so every movement is a visible diff. 464 (main) + 8 (release-v0.60.yaml) = 472, measured under the job's own conditions rather than assumed. Re-verified the guard is still POTENT at the new value rather than assuming potency carried over — a raised gate that no longer fires is worse than the omission it was raised for: GREEN: measured=472 floor=472 RED : reintroduce the #1064 metadata:/requirements: schema defect -> measured drops below 472 -> FIRES 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>
Closes the fourth silent drop of the #1041 class: the ARM plain
--relocatablepath silently discarded nonzero global initializers — compiled exit 0,0x2Anowhere in the ELF, wrong value at runtime. The fix is the house refusal (#851 / #1049 / #1053 shape). Materializing the initializers on this path is capability work (v0.60, alongside VCR-REACH-002) and is explicitly NOT this artifact — this PR is the honest-frontier minimum plus the harness that should have caught it.Rivet:
RQ-59-GLOBALINIT(artifact entry on PR #1056,plan/v59-globalinit; this branch is off main and does not duplicate it).Red-first transcript
Before (origin/main) — the filed repro, executed:
wasmtime:
get() == 42. The object returns whatever the embedder left at R9.After (this branch):
The assertion shape is the point (commit 1, red on its parent): the tests assert a non-zero exit + a reason string naming the global initializers. They deliberately do NOT assert "the initializer value is absent from the object" — that was already true on the broken behaviour and would have been vacuously green, which is exactly how this bug survived. 6 refusal tests red on main (nonzero i32, nonzero i64, non-const init expr, cortex-m4 + cortex-r5, import-forced ET_REL,
--native-pointer-abiwithout memory); 4 green controls passed throughout; all 11 pass now.The contract language, re-verified (not inherited from the finding lane)
crates/synth-synthesis/src/instruction_selector/select_with_stack.rs:5545: "Load global value from globals table (R9 = globals base)." — the BASE register, nothing more. Same at :5629 and incontracts.rs("R9: globals base pointer").scripts/repro/multi_segment_static_data_differential.py:20: "and the embedder populates its init segments (the object carries no ROM image on this path)" — the data-segment sentence that made ARM --relocatable SILENTLY DROPS active data segments — exit 0, no bytes, no warning (explains gale#278 state(0)=255) #1041 a refusal-with-flag exists for data segments only. A repo-wide grep for any sentence assigning global-initializer evaluation to the embedder finds none.So: outside the contract, confirmed. Every other path ships the inits (
--native-pointer-abi#237, aarch64 #851), materializes them at reset (self-contained — #649 fixed exactly this class there as a BUG), or loud-skips (RV32 #643). The plain relocatable path alone did none of the three.The guard
Predicate is "does the initial VALUE reach the object", not "which flag was passed" (in
build_relocatable_elf, afteremit_wasm_datais known):i32.const/i64.constinit on the plain (R9-table) path → refuse;global.getof an imported global) → refuse on every ET_REL path — no path materializes an unknown value (the native.dataslot would ship a silent0);--native-pointer-abiwithout a linear memory, when code carries a__synth_globalsrelocation → refuse: no region is emitted, so pre-fix the object shipped an undefined__synth_globalsand no init image (loud at host link by default, so not filed as a fifth silent drop — but the same guard closes it at compile time). With NO such relocation the declared global is inert (no globals surface in the object) and the compile is allowed;WasmGlobal::float_or_v128field lets the guard tell integer-None(silent-drop class) from float-None(covered lane) —slot_bytesalone cannot (4 = i32 OR f32).--embedder-global-initis the explicit escape hatch (the #952/#1049 shape). Bytes identical either way, pinned by test: the flagged nonzero-init object is asserted byte-identical to a zero-init twin's unflagged object — the init value reaches no emitted byte, which is precisely the contract the flag acknowledges. Frozen anchors: unmoved (refusal only; fullfrozen_codegen_bytessuite green).The strengthened harness, and proof it has teeth
Why the bug survived: the sole relocatable-path globals harness (
i64_globals_643_differential.py) maps a zeroed region annotated# zeroed globals table (inits are 0), and its fixture's inits ARE 0 — it structurally could not distinguish "initializers written" from "initializers were all zero anyway". Same weak-checker family as #1055 ("before FIRST read") and #1054 ("may clobber it freely").Strengthened (
check_nonzero_init_contract_1052, new fixtureglobals_init_1052.watwith inits42and0x1122334455667788), three legs:--relocatablemust exit non-zero naming the global initializers;--embedder-global-initthe harness acts as the acknowledged embedder: it seeds the R9 table from the module's own initial values, read back through wasmtime's exported globals (the VCR-VER-003 served-vs-runtime shape — no second hardcoded copy), then the execution differential must agree with wasmtime;Teeth proven by execution against the pre-fix compiler (origin/main sources, rebuilt):
Against the fixed compiler: all legs pass (seeded
get_a=0x2a,get_b_lo=0x55667788,get_b_hi=0x11223344; zeroed-tableget_a -> 0x0 DIVERGES). The original #643 sections and itsci-checks: emulations >= 28floor are untouched (the new legs only add emulations).Affected-harness count (measured, not assumed — and re-measured)
First measurement said "2 tests in 1 file" and was wrong:
cargo teststops at the first failing binary, so the first run's failure list was truncated. The honest number comes from--no-fail-fastplus executing every CIsynth compileinvocation extracted fromci.ymlagainst the fixed binary. Measured fallout, all converted per the #1049 shape (default refuses; the flag acknowledges; bytes identical):cabi_arena_bind_418,cmp_select_fusion_census,cmp_select_two_move_coverage,dwarf_debug_line_emit_394,flag_flip_wave_242,frozen_codegen_bytes,recovery_stats_242,shift_mask_elide_686,spill_baseline_pressure_390— all compile frozen SP-global fixtures as harness-embedders and gain--embedder-global-init.frozen_codegen_bytespassing again with the flag IS the frozen-anchor proof: the bit-identical oracle is green, so no emitted byte moved.arm_corpus_sweep_973.py(globs the whole corpus; ~25 nonzero-init fixtures; compile count restored to 150/162 over its 144 floor) andgpio_thin_846_differential.py(loom-emitted$__stack_pointer; its runner maps R9 itself). Each carries the same acknowledgment commentmulti_segment_static_datagot for ARM --relocatable SILENTLY DROPS active data segments — exit 0, no bytes, no warning (explains gale#278 state(0)=255) #1041.ci.yml(flight-seam ×2, control-step, call_6_7args) — verified by re-running all 37 unique CI compile invocations: 0 refusals, 0 other failures after.shadow_stack_size_without_region_refuses_739expects the more specific --shadow-stack-size: static ABOVE sp_init is baked at its 1MB linmem offset, not re-based → OOB read in shrunk layout (post-link oracle wrongly passes) #739 flag-honesty message; the ARM plain-relocatable path silently drops nonzero GLOBAL INITIALIZERS — no init image, no decline, no documented embedder obligation (4th silent drop of the #1041 class) #1052 guard now runs after it.call_5args.wat-shape fixtures (--native-pointer-abi, no linear memory, globals declared but never accessed — no__synth_globalsrelocation, no region) are INERT: the object has no globals surface at all, so nothing can ever read the dropped value. The guard allows them; a module that DOES access globals with no region still refuses (pinned by test). On the plain path this refinement is impossible (R9 accesses carry no relocation) and presence-based refusal stands, matching ARM --relocatable SILENTLY DROPS active data segments — exit 0, no bytes, no warning (explains gale#278 state(0)=255) #1041.(#1049 found ~30 affected, #1053 found 0 — this one measured ~30 across 9 test files + 2 harnesses + 3 CI steps.)
Gates
cargo fmt --allclean;cargo clippy --workspace --all-targets -- -D warningsexit 0cargo test --workspaceexit 0 (true exit code, unpiped)python3 scripts/claim_check.py claims.yaml: 50/50 hold;python3 scripts/model_coverage_audit.py --check: okrivet validate: FAIL (50 errors) — identical count to main (rivet externals are declared against a volume that does not exist — the federated half of the trace graph is never validated #1012, pre-existing)EXPECTED_DECLINES(fix(#973): ARM select on an i64-comparison returns the then-arm — and an ARM leg for the corpus CI never compiled #992) untouchedRefs #1052
🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L