Skip to content

fix(determinism): replace HashMap.iter().find with sorted-key iteration (LS-A-15)#154

Merged
avrabe merged 1 commit into
mainfrom
fix/hashmap-iteration-determinism
May 15, 2026
Merged

fix(determinism): replace HashMap.iter().find with sorted-key iteration (LS-A-15)#154
avrabe merged 1 commit into
mainfrom
fix/hashmap-iteration-determinism

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 14, 2026

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

Site Effect
`merger.rs::component_realloc_index` fallback (2907-2919) Multi-module component with reallocs in modules other than 0 picks an arbitrary realloc — may be bound to wrong memory (H-4.3)
`merger.rs` handle-table fallbacks (~1040, 1090-1100) Ties between candidate handle tables broken by iteration order
`resolver.rs::resolve_resource_positions` last-resort (1295-1308) Resource-rep / -new / -drop prefix collisions return arbitrary import → cross-resource confusion (H-3)

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:

  • `component_realloc_index` picks the lowest module index
  • Handle-table fallbacks pick lexicographically smallest `(owner, iface, rn)`
  • Resource type-id fallback picks the smallest matching type-id

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` —
    rebuilds the MergedModule across 64 iterations (each with fresh HashMap
    hash seed) and asserts a single observed value.
  • `ls_a_15_component_realloc_index_prefers_module_0` — regression
    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

  • `cargo test -p meld-core --lib` — 210 pass (208 prior + 2 new)
  • `cargo clippy --all-targets -- -D warnings` — clean
  • `cargo fmt --check` — clean
  • YAML lint
  • CI green on smithy
  • `mythos-pass-done` label applied

Refs

  • LS-A-15 (UCA-M-10, H-7, H-3, H-4.3)
  • Discovered by post-v0.8.0 Mythos delta-pass on merger.rs and resolver.rs

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

Mythos delta-pass required

This PR modifies one or more Tier-5 source files (per
scripts/mythos/rank.md):

meld-core/src/merger.rs
meld-core/src/resolver.rs

Before merge, run the Mythos discover protocol on the
modified Tier-5 files:

  1. Follow scripts/mythos/discover.md
    — one fresh agent session per touched Tier-5 file.
  2. For each finding, the agent must produce both a Kani
    harness and a failing PoC test (per the protocol's
    "if you cannot produce both, do not report" rule).
  3. Attach a comment on this PR with either the findings
    (formatted per discover.md's output schema) or
    NO FINDINGS.
  4. Add the mythos-pass-done label to this PR.

Why this gate exists: LS-A-10
(CABI alignment padding in async-lift retptr writeback) was
found by the v0.8.0 pre-release Mythos pass — but it had
lived in the callback emitter since #128, across six
releases. A PR-time gate would have caught it at review
time instead of at the release boundary.

The gate check on this PR will pass once the label is
applied.

@avrabe avrabe added the mythos-pass-done Mythos delta-pass completed on Tier-5 file changes; findings (or NO FINDINGS) attached to PR label May 14, 2026
@avrabe
Copy link
Copy Markdown
Contributor Author

avrabe commented May 14, 2026

Mythos delta-pass evidence

Tier-5 files touched: meld-core/src/merger.rs and meld-core/src/resolver.rs.

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 (ls_a_15_component_realloc_index_picks_lowest_module_deterministically).

Other findings from those file scans not in this PR (will land in follow-up PRs):

  • merger.rs: ends_with(rn) suffix matching for resource imports → cluster C3
  • resolver.rs F2/F3 (min_by_key heuristic + single-pass alias propagation) — speculative PoCs not concrete, will validate before triaging

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>
@avrabe avrabe force-pushed the fix/hashmap-iteration-determinism branch from afb7687 to 9583011 Compare May 15, 2026 10:10
@avrabe avrabe merged commit ff6c317 into main May 15, 2026
10 checks passed
@avrabe avrabe deleted the fix/hashmap-iteration-determinism branch May 15, 2026 17:17
@avrabe avrabe mentioned this pull request May 16, 2026
3 tasks
avrabe added a commit that referenced this pull request May 16, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mythos-pass-done Mythos delta-pass completed on Tier-5 file changes; findings (or NO FINDINGS) attached to PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant