Severity: medium — public API door into a spec-unreachable state.
HostFuture.drop (exec/host_streams.ts:799-803) and embedder
Future.drop/Symbol.dispose (embedder/streams.ts:546-549, 562-564) have
no wrote-a-value guard. The spec's writable future end may not drop before
delivering its value (definitions.py:1183-1184); the host wrapper plays
the writable role once lowered.
Crash shapes today: (a) guest reader parked -> "a readable future end
cannot observe DROPPED" assert (stream_builtins.ts:264-268) fires
synchronously inside the host's drop() call; (b) no reader parked ->
guest's later future.read hits the shape assert at task/streams.ts:411.
The layer knows — the producer-failure path (embedder/streams.ts:714-749)
documents the invariant and dodges it — but the public door has no guard.
Ruling (fix shape): with the teardown-trap mechanism from the
poisoning-walk issue in place, host drop-before-write on a lowered
future becomes total and honest: arm the reader-side trap ("the host
dropped the writable end without writing a value") instead of DROPPED;
drop()/dispose never throw. On an unlowered future (guest never saw
it), plain state cleanup. Write-then-drop stays the normal path. The
producer-failure path keeps its store-level reporting (better attribution
for in-flight calls); its stale comment about the invariant gets updated.
embedder-api.md gains the drop semantics (orchestrator authors the
contract text).
Found in the 2026-08-10 adversarial conformance review (runtime + translator vs component-model @ 73b7ad5 definitions.py; wasmtime-environ 47.0.3 treated as trusted).
Severity: medium — public API door into a spec-unreachable state.
HostFuture.drop(exec/host_streams.ts:799-803) and embedderFuture.drop/Symbol.dispose(embedder/streams.ts:546-549, 562-564) haveno wrote-a-value guard. The spec's writable future end may not drop before
delivering its value (definitions.py:1183-1184); the host wrapper plays
the writable role once lowered.
Crash shapes today: (a) guest reader parked -> "a readable future end
cannot observe DROPPED" assert (stream_builtins.ts:264-268) fires
synchronously inside the host's
drop()call; (b) no reader parked ->guest's later
future.readhits the shape assert at task/streams.ts:411.The layer knows — the producer-failure path (embedder/streams.ts:714-749)
documents the invariant and dodges it — but the public door has no guard.
Ruling (fix shape): with the teardown-trap mechanism from the
poisoning-walk issue in place, host drop-before-write on a lowered
future becomes total and honest: arm the reader-side trap ("the host
dropped the writable end without writing a value") instead of DROPPED;
drop()/dispose never throw. On an unlowered future (guest never sawit), plain state cleanup. Write-then-drop stays the normal path. The
producer-failure path keeps its store-level reporting (better attribution
for in-flight calls); its stale comment about the invariant gets updated.
embedder-api.md gains the drop semantics (orchestrator authors the
contract text).
Found in the 2026-08-10 adversarial conformance review (runtime + translator vs component-model @ 73b7ad5 definitions.py; wasmtime-environ 47.0.3 treated as trusted).