Migrated from Method backlog
This issue was created from a legacy filesystem backlog card. GitHub Issues are now the live work tracker; repository docs remain Method evidence.
Source backlog: docs/method/backlog/up-next/PLATFORM_wasm-runtime.md
Original lane: up-next
Original legend: PLATFORM
Original backlog card
WASM Runtime Integration
Milestone: First Light | Priority: P1 | Repo: Echo
Historical note: This roadmap item predates the observation-first ABI v2
and the intent-shaped ABI v3 control-plane rewrite. It is retained as
planning archaeology, not as the current WASM API contract. The live public
boundary is documented in SPEC-0009 WASM ABI.
Historical First Light plan for wiring warp-wasm exports to real engine
logic. It reflects an earlier step(...)-driven browser boundary that has
since been replaced.
T-4-1-1: Wire Engine lifecycle behind wasm-bindgen exports
User Story: As a web developer, I want the WASM module to expose a real Engine instance so that I can drive tick execution from JavaScript.
Requirements:
- R1: Instantiate an
Engine (via EngineBuilder) in a module-scoped RefCell or OnceCell inside warp-wasm, gated behind an init() export.
- R2:
dispatch_intent(bytes) must call engine.ingest_intent() and return the IngestDisposition encoded as CBOR via echo-wasm-abi.
- R3:
step(budget) must call engine.tick() up to budget times, returning a CBOR-encoded StepResult containing tick count and commit status.
- R4:
get_head() must return the latest Snapshot fields (tick, state_root, commit_id) as CBOR bytes.
Acceptance Criteria:
Definition of Done:
Scope: Engine instantiation, intent ingestion, tick execution, head query. All through existing wasm-bindgen exports.
Out of Scope: Snapshot-at (time travel), render_snapshot, query execution. Multi-warp Engine configurations.
Test Plan:
- Goldens: CBOR golden vectors for
get_head() after genesis init (zero-tick snapshot).
- Failures:
step before init returns structured error. Malformed intent bytes rejected gracefully (no WASM trap).
- Edges:
step(0) is a no-op returning current head. step(u32::MAX) runs until no pending rewrites remain.
- Fuzz/Stress: Proptest with random intent payloads; engine must not panic.
Blocked By: none
Blocking: T-4-1-2, T-4-2-1
Est. Hours: 6h
Expected Complexity: ~250 LoC
T-4-1-2: Snapshot and ViewOp drain exports
User Story: As a web developer, I want to drain ViewOps and request snapshots at specific ticks so that I can render simulation state in the browser.
Requirements:
- R1:
drain_view_ops() must return all materialized ViewOp frames accumulated since the last drain, CBOR-encoded.
- R2:
snapshot_at(tick) must replay from the provenance store and return the full Snapshot at the requested tick as CBOR bytes.
- R3:
render_snapshot(bytes) must accept a Snapshot and produce ViewOps suitable for visualization.
Acceptance Criteria:
Definition of Done:
Scope: ViewOp drain, snapshot-at replay, render_snapshot projection.
Out of Scope: Streaming ViewOps over WebSocket. Incremental snapshot diffs.
Test Plan:
- Goldens: ViewOp CBOR output for a single-motion-rule tick golden scenario.
- Failures:
snapshot_at for non-existent tick returns structured CBOR error. render_snapshot with garbage bytes returns error.
- Edges:
drain_view_ops on fresh engine (no ticks) returns empty array. snapshot_at(current_tick) matches get_head.
- Fuzz/Stress: Rapid alternation of
step and drain_view_ops with random budgets.
Blocked By: T-4-1-1
Blocking: T-4-2-2
Est. Hours: 5h
Expected Complexity: ~200 LoC
T-4-1-3: JS/WASM memory bridge and error protocol
User Story: As a web developer, I want a clean TypeScript API wrapper around the raw WASM exports so that I do not deal with raw Uint8Array encoding/decoding.
Requirements:
- R1: Publish a
@echo/wasm-bridge TypeScript package (or web/ directory) that wraps each wasm-bindgen export with typed async functions.
- R2: CBOR decode all return payloads into typed TypeScript objects.
- R3: Errors from the WASM side (structured CBOR with
{ error: string, code: number }) are surfaced as typed EchoWasmError exceptions.
- R4: Memory cleanup: large Uint8Array returns are freed on the WASM linear memory side after JS copies them.
Acceptance Criteria:
Definition of Done:
Scope: TypeScript wrapper, CBOR decode, error mapping, basic memory lifecycle.
Out of Scope: React/Svelte bindings. Worker thread offloading. Streaming APIs.
Test Plan:
- Goldens: Snapshot: bridge.getHead() output matches known JSON fixture after genesis init.
- Failures: Every WASM error code has a corresponding
EchoWasmError subclass or code. Unrecognized CBOR returns a generic decode error.
- Edges: Empty intent bytes. Zero-length ViewOp drain. Concurrent calls (JS is single-threaded but test re-entrant safety).
- Fuzz/Stress: 1000 rapid step/drain cycles; monitor WASM memory (must not monotonically grow beyond blob cache).
Blocked By: T-4-1-1
Blocking: T-4-2-1, T-4-4-3
Est. Hours: 5h
Expected Complexity: ~300 LoC (TypeScript)
Migrated from Method backlog
This issue was created from a legacy filesystem backlog card. GitHub Issues are now the live work tracker; repository docs remain Method evidence.
Source backlog:
docs/method/backlog/up-next/PLATFORM_wasm-runtime.mdOriginal lane:
up-nextOriginal legend:
PLATFORMOriginal backlog card
WASM Runtime Integration
T-4-1-1: Wire Engine lifecycle behind wasm-bindgen exports
User Story: As a web developer, I want the WASM module to expose a real Engine instance so that I can drive tick execution from JavaScript.
Requirements:
Engine(viaEngineBuilder) in a module-scopedRefCellorOnceCellinside warp-wasm, gated behind aninit()export.dispatch_intent(bytes)must callengine.ingest_intent()and return theIngestDispositionencoded as CBOR viaecho-wasm-abi.step(budget)must callengine.tick()up tobudgettimes, returning a CBOR-encodedStepResultcontaining tick count and commit status.get_head()must return the latestSnapshotfields (tick, state_root, commit_id) as CBOR bytes.Acceptance Criteria:
init()export constructs an Engine with at least one demo rewrite rule (motion rule).dispatch_intentthenstep(1)and receives a non-emptyStepResult.get_head()returns a valid CBOR payload whosestate_rootchanges after a step that applies a rewrite.stepbeforeinitreturns an error CBOR (not a panic).Definition of Done:
Scope: Engine instantiation, intent ingestion, tick execution, head query. All through existing wasm-bindgen exports.
Out of Scope: Snapshot-at (time travel), render_snapshot, query execution. Multi-warp Engine configurations.
Test Plan:
get_head()after genesis init (zero-tick snapshot).stepbeforeinitreturns structured error. Malformed intent bytes rejected gracefully (no WASM trap).step(0)is a no-op returning current head.step(u32::MAX)runs until no pending rewrites remain.Blocked By: none
Blocking: T-4-1-2, T-4-2-1
Est. Hours: 6h
Expected Complexity: ~250 LoC
T-4-1-2: Snapshot and ViewOp drain exports
User Story: As a web developer, I want to drain ViewOps and request snapshots at specific ticks so that I can render simulation state in the browser.
Requirements:
drain_view_ops()must return all materializedViewOpframes accumulated since the last drain, CBOR-encoded.snapshot_at(tick)must replay from the provenance store and return the fullSnapshotat the requested tick as CBOR bytes.render_snapshot(bytes)must accept a Snapshot and produce ViewOps suitable for visualization.Acceptance Criteria:
step(1)that fires a motion rule,drain_view_ops()returns at least one ViewOp frame.drain_view_ops()twice in succession returns an empty array on the second call.snapshot_at(0)returns the genesis snapshot matching the initialget_head().snapshot_at(tick)for a future tick returns an error payload.Definition of Done:
Scope: ViewOp drain, snapshot-at replay, render_snapshot projection.
Out of Scope: Streaming ViewOps over WebSocket. Incremental snapshot diffs.
Test Plan:
snapshot_atfor non-existent tick returns structured CBOR error.render_snapshotwith garbage bytes returns error.drain_view_opson fresh engine (no ticks) returns empty array.snapshot_at(current_tick)matchesget_head.stepanddrain_view_opswith random budgets.Blocked By: T-4-1-1
Blocking: T-4-2-2
Est. Hours: 5h
Expected Complexity: ~200 LoC
T-4-1-3: JS/WASM memory bridge and error protocol
User Story: As a web developer, I want a clean TypeScript API wrapper around the raw WASM exports so that I do not deal with raw Uint8Array encoding/decoding.
Requirements:
@echo/wasm-bridgeTypeScript package (orweb/directory) that wraps each wasm-bindgen export with typed async functions.{ error: string, code: number }) are surfaced as typedEchoWasmErrorexceptions.Acceptance Criteria:
tsc --strict --noEmit.bridge.init()/bridge.step(n)/bridge.getHead()type signatures match the CBOR schema.EchoWasmErrorwith a.codeproperty.Definition of Done:
Scope: TypeScript wrapper, CBOR decode, error mapping, basic memory lifecycle.
Out of Scope: React/Svelte bindings. Worker thread offloading. Streaming APIs.
Test Plan:
EchoWasmErrorsubclass or code. Unrecognized CBOR returns a generic decode error.Blocked By: T-4-1-1
Blocking: T-4-2-1, T-4-4-3
Est. Hours: 5h
Expected Complexity: ~300 LoC (TypeScript)