Skip to content

fix(355): --component lifts bare world exports with real signatures - #357

Merged
avrabe merged 2 commits into
mainfrom
fix/355-component-bare-export-types
Jul 16, 2026
Merged

fix(355): --component lifts bare world exports with real signatures#357
avrabe merged 2 commits into
mainfrom
fix/355-component-bare-export-types

Conversation

@avrabe

@avrabe avrabe commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What

Fixes #355fuse --component emitted an invalid component for any export
with a non-empty signature. Reproduced on avrabe's exact fixtures.

Root cause (three latent defects)

Bare world exports (world root { export get-b: func(i: s32) -> u32 }) took a
path that hardcoded an empty (func) type. Digging in surfaced three bugs:

  1. Parser (ComponentFuncDef): exporting a Func binds a new
    component-function index (an export alias); wit-component interleaves these
    with canon lifts. The parser recorded them nowhere, so the func index space
    was silently compacted and component_func_defs[export.index] mis-resolved
    (get-b ✓, set-b/ptr-b shifted). Fixed by recording
    ComponentFuncDef::ExportAlias.
  2. Wrapper no-result func: func_enc.result(None) was never called for a
    void-returning export (set-b), leaving the type unterminated → decoder
    EOF. (Latent until a no-result export routed through this helper.)
  3. Wrapper export-alias skip: the bare-export emitter advanced the
    component-func index by 1 per export, but each export also binds an alias
    index — so every export referenced the previous export's alias. Fixed to
    advance by 2.

Result

get-b: func(i:s32)->u32, set-b: func(i:s32,v:u32), ptr-b: func()->u32 — all
correct, output validates. The standard {iface}#{func} interface path is
untouched.

Tests / falsification

component_bare_export_355.rs (single + two-component) on tests/reloc351/{a,b}.wasm:
asserts a valid component (wasmparser::Validator, all features) and the
correct per-export arity (get-b (1,1), set-b (2,0), ptr-b (0,1)) — the first
catches the empty-type + no-result bugs, the second catches the index shift.

Full meld-core suite green (no regression from the parser index-space change);
fmt + clippy clean. SR-54 (verified); CHANGELOG. Tier-5 (parser/component_wrap/
p3_stream) → Mythos discover pass to follow on this PR.

Refs #355

fuse --component emitted an INVALID component for any non-empty export
signature: bare world exports (world root { export get-b: func(i:s32)->u32 })
were all lifted with an empty (func) type, so wasm-tools validate rejected the
output — silently, after 'Fusion complete!'.

Three latent defects, all fixed:
- parser.rs: record func export-aliases (ComponentFuncDef::ExportAlias) so the
  component-function index space stays aligned with export.index (wit-component
  interleaves canon lifts and export-aliases; the space was silently compacted,
  so component_func_defs[export.index] mis-resolved).
- component_wrap.rs: carry the source export's lift type + options instead of
  hardcoding func(); terminate a no-result func type (func_enc.result(None) was
  never called → truncated type → decoder EOF); advance the bare-export
  component-func index by 2 (lift + export-alias) so each export references its
  own lift, not the previous export's alias.
- p3_stream.rs: handle the new ExportAlias variant (follow one hop).

Tests: component_bare_export_355.rs on avrabe's exact fixtures — valid component
+ correct per-export arities (get-b (1,1), set-b (2,0), ptr-b (0,1)). Full
meld-core suite green. SR-54; CHANGELOG.

Refs #355

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Mythos delta-pass required

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

meld-core/src/component_wrap.rs
meld-core/src/p3_stream.rs
meld-core/src/parser.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.

@github-actions

Copy link
Copy Markdown

LS-N verification gate

59/59 approved LS entries verified

count
Passed (≥1 test, all green) 59
Failed (≥1 test failure) 0
Missing (no ls_*_NN_* test found) 0

Approved loss-scenarios.yaml entries are expected to have a
regression test named ls_<letter>_<num>_* (e.g. LS-A-11
ls_a_11_*). The gate runs each prefix via cargo test --lib --no-fail-fast and aggregates pass/fail/missing.

Failed LS entries

(none)

Missing regression tests

(none)

Updated automatically by tools/post_verification_comment.py.
Source of truth: safety/stpa/loss-scenarios.yaml.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Mythos delta-pass (auto)

NO FINDINGS across 3 Tier-5 file(s)

File Verdict Hypothesis
`` ✅ NO FINDINGS
`` ✅ NO FINDINGS
`` ✅ NO FINDINGS

Auto-run via anthropics/claude-code-action@v1
(SHA-pinned) on the touched Tier-5 files, using the
maintainer's Max-plan OAuth token. See
.github/workflows/mythos-auto.yml and
scripts/mythos/discover.md.

…thos)

The #355 Mythos discover pass on component_wrap.rs (clean on the #355 change
itself) surfaced the same index-desync class in the INTERFACE-export loop: it
advanced component_instance_idx by 1 per exported interface, but exporting an
instance binds an export-alias index too, so a component exporting >=2 interfaces
silently bound the 2nd+ export to the previous interface's alias — exporting the
wrong functions (output still validated; UCA-CP-1). Fixed (+= 2).

- component_wrap.rs: component_instance_idx += 2.
- tests/component_multi_interface_instance_idx.rs: two-interface component, each
  export must resolve to its own funcs (Mythos PoC).
- SR-54 verification extended; CHANGELOG.

Full meld-core suite green. Mythos passes (parser/component_wrap/p3_stream) all
clean on the #355 change; parser change also improves resolver index alignment.

Refs #355

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@avrabe

avrabe commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Mythos delta-pass — findings

Three clean-room adversarial discover passes (fresh-context agents), one per
changed Tier-5 file. Verdict: no reportable bug on the #355 change itself; one
real sibling bug found and fixed in the same PR.

parser.rs (ComponentFuncDef::ExportAlias) — CLEAN

Every consumer of component_func_defs uses variant-specific match/if let;
none assume the (previously compacted) length or iterate expecting only the old
three variants. The change is order-preserving by construction (entries
pushed in binary section order), so alignment holds for any section ordering, not
just the fixture. Notably it also improves resolver.rs correctness — its
func-index→name maps were keyed by the compacted space pre-fix and could
mis-resolve interleaved cases.

component_wrap.rs — CLEAN on the #355 change; found a sibling bug (fixed)

The += 2, post-return, result(None), and type-carry changes all verified
correct (mixed interface+bare, string-returning, void, fallback cases).
But the same index-desync exists in the interface-export loop: it advanced
component_instance_idx by 1 while exporting an instance also binds an
export-alias index — so a component exporting ≥2 interfaces silently bound
the 2nd+ export to the previous interface's alias, exporting the wrong functions
(the output still validated — silent; UCA-CP-1). Confirmed with a Kani harness +
a differential PoC. Fixed in commit e115719 (+= 2) with a regression test
(component_multi_interface_instance_idx.rs).
Noted (not reported — no reachable PoC): type_remap is keyed only by
source_type_idx; a defensive key of (component, source_type_idx) would harden
against a future cross-component collision.

p3_stream.rs (new ExportAlias match arm) — CLEAN

Unreachable for the #355 fixtures (they hit Lift directly); when hit it follows
one hop to the real Lift/Import (no cycle/panic/OOB — single .get()).
Empty-result only feeds the #142 stream-mismatch diagnostic, which has a
role-list fallback, so no fused-byte divergence is producible.

Full meld-core suite green after both commits. Adding mythos-pass-done.

@avrabe avrabe added the mythos-pass-done Mythos delta-pass completed on Tier-5 file changes; findings (or NO FINDINGS) attached to PR label Jul 16, 2026
@avrabe
avrabe merged commit a73b350 into main Jul 16, 2026
19 of 20 checks passed
@avrabe
avrabe deleted the fix/355-component-bare-export-types branch July 16, 2026 21:08
avrabe added a commit that referenced this pull request Jul 17, 2026
Ships the #355 fixes (merged #357): fuse --component now lifts bare world
exports with their real signatures instead of an empty (func) (was an INVALID
component for any non-empty signature), and a component exporting >=2 interfaces
binds each export to its own instance (sibling index-desync found by the Mythos
pass). SR-54 verified.

Co-authored-by: Claude Opus 4.8 (1M context) <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.

fuse --component emits an invalid component: every export lifted with an empty (func) type, so non-empty signatures fail wasm-tools validate

1 participant