feat(p3): static resource-lifetime validation for async streams (#142 iv, v0.21.0)#210
Conversation
…iv) meld fuse now rejects a fusion where a component declares a stream<T>/future<T> whose element type is a resource handle. A borrow<R> cannot outlive its lending call across the async boundary (use-after-scope); an own<R> is the drop-while-referenced hazard #142 (iv) names. New StreamValidationIssue::ResourceLifetime → Error::StreamValidation. Detection: resource handles surface in the parsed stream descriptor as `stream<Type(N)>` (handle hidden behind a Type(idx) ref), so the check parses the index out and reuses ParsedComponent::resolve_to_resource — the same Type(idx)→Own/Borrow chase meld applies to function params — rather than trusting Debug text for the handle kind. The wasmparser ComponentValType::Type(N) Debug form is pinned by a regression test so a wasmparser upgrade that changes it fails loudly. New LS-R-14 (approved) + 3 tests (borrow flagged, own flagged-as-owned, primitive not flagged) + the format pin. resolve_to_resource made pub(crate). #142 (ii) bounded-channel capacity documented NOT APPLICABLE: the canonical ABI has no capacity concept (stream.new takes none; streams are unbounded). This closes the #142 (i)-(iv) checklist (i/iii shipped v0.13/v0.15, iv here, ii N/A). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 |
LS-N verification gate
Approved Failed LS entries(none) Missing regression tests
Updated automatically by |
Mythos delta-pass (auto)✅ NO FINDINGS across 3 Tier-5 file(s)
Auto-run via |
Brings the rivet traceability requirement statuses in line with what has actually shipped on main. 24 draft + 2 planned (SR-33, SR-35) → implemented; SR-34 and SR-36 deliberately held at `planned`. Basis (status `implemented` = code exists and works, full test suite green; NOT `verified` = per-requirement formal verification, which is a separate pass): - SR-35 DWARF address remap → shipped v0.16–v0.20 (DwarfHandling::Remap, dwarf.rs); in-tree witness oracle passes. NB its stated verification is a cross-repo witness smoke that is NOT yet done — hence implemented, not verified. - SR-33 cross-component stream fusion → v0.9.0 (#141). - SR-31 multiply-instantiated detection → LS-M-5, ls_m_5_* regression test. - SR-19 deterministic output → LS-A-15. - SR-1..SR-25 (parser / canonical-ABI / merger / adapter / wrapping) → foundational subsystems shipped across v0.1–v0.20; exercised end-to-end by the wasmtime runtime tests (real components fused + executed). Held at `planned` (honesty — not on main): - SR-34 static stream validation: (i)/(iii) merged, (iv) is in the unmerged PR #210, (ii) bounded-capacity is N/A. Flip to implemented when #210 merges. - SR-36 synthesised DWARF DIEs for adapters: DWARF Phase 3 (#144), not started. `rivet validate` error count is unchanged (164 pre-existing schema-drift / broken-link errors, none introduced by this change). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Implements #142 (iv) — resource lifetime across async boundaries, the last open item in the static stream-validation checklist, and closes (ii) as not-applicable with rationale.
meld fusenow rejects a fusion where a component declares astream<T>/future<T>whose element type is a resource handle:borrow<R>— definite violation. A borrow is valid only within its lending call, but a stream/future is read by the consumer after that call returns across the async boundary → use-after-scope.own<R>— flagged as the drop-while-referenced hazard P3 async — static stream validation at build time (#94 sub-B) #142 names: if the producer drops the handle while the consumer still holds it via the stream, the representation is freed under the consumer.Surfaced as
StreamValidationIssue::ResourceLifetime→Error::StreamValidation.How detection works (and why it's grounded)
Resource handles don't appear literally in the stream descriptor — they hide behind a type reference, so a handle stream parses as
stream<Type(N)>where component type indexNresolves to aDefined(Own/Borrow). The check parses theType(N)index out and reusesParsedComponent::resolve_to_resource— the exactType(idx) → Own/Borrowchase meld already applies to function params — rather than trusting the Debug text for the handle kind. The wasmparserComponentValType::Type(N)Debug form is pinned by a regression test (wasmparser_type_debug_form_is_stable), so a wasmparser upgrade that changes it fails loudly instead of silently disabling the check.Verification
ls_r_14_borrow_handle_in_stream_flagged,ls_r_14_own_handle_in_future_flagged_as_owned, and negative testls_r_14_primitive_element_stream_not_flagged, plus the format pin.run_ls_verification.py:[ OK ] LS-R-14 (3 pass).resolve_to_resourcemadepub(crate).(ii) bounded-channel capacity — not applicable
The Component-Model canonical ABI has no bounded-channel / capacity concept:
stream.new(CanonicalEntry::StreamNew { ty }) takes no capacity argument and streams are unbounded by construction. There is nothing in the component binary to validate — "declare a capacity" presupposes an annotation mechanism the ABI does not provide. Documented inp3_stream.rs+ LS-R-14. This closes the #142 (i)–(iv) checklist: i/iii shipped in v0.13.0/v0.15.0, iv here, ii N/A.Limitation (acknowledged)
A handle nested inside a composite element (
stream<list<own<R>>>) is not flagged — the same boundary asstream_elements_in_valtype.p3_stream.rs/parser.rs/resolver.rsare Tier-5, so this PR gets the Mythos delta scan.🤖 Generated with Claude Code