fix(determinism): replace HashMap.iter().find with sorted-key iteration (LS-A-15)#154
Conversation
Mythos delta-pass requiredThis PR modifies one or more Tier-5 source files (per Before merge, run the Mythos discover protocol on the
Why this gate exists: LS-A-10 The gate check on this PR will pass once the label is |
|
Mythos delta-pass evidence Tier-5 files touched: The post-v0.8.0 sweep ran fresh agents on both files. Both surfaced the HashMap-iteration non-determinism finding (resolver F1 + merger B) with concrete PoCs. The PoCs are folded into the regression test ( Other findings from those file scans not in this PR (will land in follow-up PRs):
|
Three sites resolved cross-component routing via HashMap::iter().find() over keyed maps. HashMap iteration order is hash-seed-randomised per process, so when more than one entry matched the lookup predicate the chosen entry varied across runs — producing non-reproducible fused output bytes and, in some cases, routing a call to the wrong target. Affected sites: - merger.rs::component_realloc_index fallback (2907-2919) - merger.rs handle-table fallbacks (~1040 and 1090-1100) - resolver.rs::resolve_resource_positions last-resort (1295-1308) Wasm validator does not catch any of these because structural types match across candidates (all reallocs share (i32,i32,i32,i32) -> i32; all resource-rep imports share (i32) -> i32). Fix: collect candidate keys, sort, pick .first(). Deterministic tie-breaking by lowest module index / smallest type-id / lexicographic key. Picks may still be semantically wrong if multiple distinct resources share a prefix (Step 6 alias propagation is the right structural mitigation), but the output is now reproducible across runs. Tests (2 new): - ls_a_15_component_realloc_index_picks_lowest_module_deterministically - ls_a_15_component_realloc_index_prefers_module_0 LS-A-15 added to safety/stpa/loss-scenarios.yaml. Discovered by the post-v0.8.0 Mythos delta-pass sweep on merger.rs and resolver.rs. Refs: LS-A-15 (UCA-M-10, H-7, H-3, H-4.3) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
afb7687 to
9583011
Compare
Cuts v0.8.1 carrying the post-v0.8.0 Mythos delta-pass sweep on the 8 Tier-5 files unscanned at v0.8.0 cut time (per #151 gate scope). Ten loss scenarios closed across 6 fix PRs, all of them silent (no host trap, no validator rejection — wrong-by-construction outputs): - LS-A-11 (#152) — extended-const init/offset truncation in segments and global initializers. Folds (i32.const x)(i32.const y) i32.add instead of dropping operands after the first. - LS-A-12/13/14 (#153) — p3-async detection bugs: mixed-mode stackful mis-classification, stream-write over-count silently classified as Complete, future<stream<...>> mis-routed to stream_types via substring match. - LS-A-15 (#154) — HashMap.iter().find non-determinism at three sites; realloc and resource-rep/-new fallbacks now sort candidate keys. - LS-A-16 (#159) — wrapper dropped source canonical options for lifts; every export silently received the first lift's string encoding. - LS-A-17/18/19 (#156) — resource_graph definer purge and terminal- exporter pass ignored the iface dimension; merger dedup used ends_with(rn) where exact match was required (float / bigfloat suffix collision). - LS-A-20 (#157) — flags<N> canonical ABI silently modeled as Record<N x Bool>; new ComponentValType::Flags variant with explicit arms in every canonical-ABI helper. Also under Added: the Mythos delta-pass CI gate workflow (#151) that made this sweep observable at PR time, and the stackful async-lift cross-memory (ptr, len) return path that v0.8.0 had errored out on. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Third fix from the post-v0.8.0 Mythos delta-pass sweep. Three sites in
meld-core resolved cross-component routing via `HashMap::iter().find(...)`,
which picks a hash-seed-randomised entry. When multiple entries
matched, the chosen entry varied per run.
The bugs
The wasm validator does not catch any of these because structural types match across candidates: all reallocs share `(i32,i32,i32,i32) -> i32`; all resource-rep imports share `(i32) -> i32`.
Fix
Collect candidate keys → sort → pick `.first()`. Deterministic tie-breaking:
Picks may still be semantically wrong if multiple distinct resources share a prefix (Step 6 alias propagation is the right structural mitigation), but the output is now reproducible across runs.
Tests (2 new)
rebuilds the MergedModule across 64 iterations (each with fresh HashMap
hash seed) and asserts a single observed value.
for the primary path.
Tier-5 gate
Touches `merger.rs` and `resolver.rs` (both Tier-5). Mythos pass evidence
in a comment below.
Test plan
Refs
🤖 Generated with Claude Code