Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,155 @@ Done under B15-R8: #1386 and #1643 each carry an amendment of 2026-10-15.
`seek_at` on both hosts at 48 and 96 kHz; after each block, the browser record and the C ABI
struct are equal field by field, from the seek call to the block that applies it and after.
Red if either host reports at the call, names a different block, or drops the lateness.

## Amendment (controls coordinator, batch 16 check, 2026-10-16)

Checked against `35ac17545`.

**Landed dependencies.** #1309, #1316, #1319, #1323, #1562 and #1688 are closed and on main.
#1332 and #1387 stay open and ordering-only (B15-R8). The scope is D5, D6 and D7 (three exports).

**Moved anchors.**
- `hosts/host-web/src/ffi.rs`: `miso_engine_web_v1_source_seek` `:3251-3276`; `service`
`:3336`; `declare_discontinuity` `:3354`; `control_request_ptr` `:3365`; `control_command`
`:3384`; `control_response_ptr` `:3417`; `control_response_bytes` `:3430`;
`plan_latency_read` `:3474`; `plan_latency_ptr` `:3482`.
- `hosts/host-web/src/lib.rs`: `seek_source` `:3533-3565`. It still checks
`plan.can_prepare_source_seek` (`:3548`), calls `ready.control.seek_unserviced` (`:3551-3553`),
then `plan.prepare_source_seek` (`:3556`). `source_result` `:5181`, `source_failure_result`
`:5197`. `RESULT_REFUSED_LIFECYCLE` equals `RESULT_WRONG_STATE` (`:146`, `:117`).
- `PcmSourceConsumer::prepare_seek` is `crates/source/src/lib.rs:1241` (doc `:1233-1240`). The D4
trap still holds: it returns `false` while an anchored seek is held.
- `SessionState` (`crates/control-plane/src/control.rs`): `seek_at` `:4073` and `seek_report`
`:4090` both run the service step first, so both are message-handler calls, never `process()`.
`SourceControlSet::seek_report` is `crates/host-core/src/source.rs:258`.
- Mirrors: `expected_exports` `scripts/check-web-audioworklet.sh:274` (its call of
`check-abi-layout-v1.py` `:638`); `EXPORTS` `tools/parameter-metadata/src/abi_layout.rs:126`
(105 names; 108 after this slice) and `scripts/check-abi-layout-v1.py:120`; the self-test
fixture `scripts/fixtures/abi-layout-v1-self-test.json`; `sdk/assets/miso-engine-v1-abi-layout.json`;
`sdk/src/generated/abi.ts`. `scripts/check-sdk-generated.sh` holds no list; it needs no edit.
- The hermetic mock in `scripts/test-web-audioworklet.mjs` (`:2741`) lists only the exports the
worklet script calls (68). No worklet script calls the new exports, so it needs no edit.
- #1688 is the model for D6: `WebPlanLatency` (`lib.rs:1229-1246`), its read and pointer exports,
`plan_latency_fields` (`abi_layout.rs:564`) and its parity rows (commit `6c1f8c2c1`).

**Stale text and the code's truth.**
- D5 "with the plain seek's argument checks": the plain seek's `can_prepare_source_seek` check
and its own source lookup (`lib.rs:3540-3550`) belong to the `prepare_source_seek` path. D5
takes the staged-ID checks only and lets the core report an unknown source
(`source.id.unknown`, `RESULT_INVALID_ARGUMENT`).
- D6's record is 48 bytes (`struct_size`, `reserved0`, five `u64`). The C struct
`miso_engine_v1_seek_report` is 64 bytes, with `reserved[2]` after the five words
(`crates/capi/include/miso_engine_v1.h:601-610`; `crates/capi/src/abi.rs:585`). The field names
there are `cumulative_underrun_frames` and `cumulative_held_frames`. #1688's browser record
copied the C size and reserved tail. Root decides whether D6 does too (see the coordinator's
report).
- Gate 3 "renders silence until the anchor": while a seek is held, queued PCM of the playing
generation plays first (header "Held", `:312-316`). The gate holds only with no PCM of the old
generation queued. Set the gate up that way.
- D6's error mapping is already the core's: `ReportBusy` is backpressure and `UnknownSource` is
invalid in `source_result`. No new mapping is needed.

**Paths.** The parity row (root ruling, batch 15c) is in `hosts/host-web/tests/control_parity.rs`,
which is not in the authorized paths. That ruling authorizes it.

**Hazard.** The browser's plain seek prepares the consumer at the call (`prepare_source_seek`);
the C ABI's applies at the next block's start. The parity row's plain-seek case compares the
reports. If the browser publishes a different `first_sample` there, the gate is red and the fix is
in the plain seek, outside this slice. Stop and report it; do not change the plain seek here.

**Decision 17.** D5 and D6 call the same core functions as the C ABI (`SessionState::seek_at`,
`SessionState::seek_report`); host-web keeps staging, pointer checks and the record copy. The
widened C13 list names the C seek report only; B15-R8 and the batch 15c ruling approved D5 and D6.
Root may add the three browser names to the list.

## Decision record: #1293 (batch 16)

Root ruling R7 (2026-10-16): the record is 64 bytes, the C struct's layout, reserved words zero.

**Shipped (D5, D6, D7; gates 3 to 6 and the batch 15c parity row).**
- `miso_engine_web_v1_source_seek_at(handle, source_id_bytes, generation, source_frame,
anchor_sample)`: the staged-ID checks (count inside the staging) only, then
`AudioWorkletEngineHost::seek_source_at`, which calls `SessionState::seek_at` and maps a refusal
through `source_failure_result`. It never calls `plan.prepare_source_seek` (the D4 trap) and is not
`render_locked`: it runs the service step, so the page calls it from the message handler only.
- `miso_engine_web_v1_source_seek_report(handle, source_id_bytes)` and
`miso_engine_web_v1_source_seek_report_ptr(handle)`: `read_seek_report` calls
`SessionState::seek_report` (the C ABI's call) and copies its five words into `WebSeekReport`, 64
bytes: `struct_size u32`, `reserved0 u32`, `generation`, `first_sample`, `source_frame`,
`cumulative_underrun_frames`, `cumulative_held_frames` (`u64` each), `reserved [u64; 2]`. Busy is
`RESULT_BACKPRESSURE`, an unknown source `RESULT_INVALID_ARGUMENT`. A refusal leaves the record
alone. host-web holds no seek or report logic.
- A host that is not ready (no control half today) refuses both with `RESULT_REFUSED_LIFECYCLE` and
changes nothing; the pointer export returns 0.
- Mirrors: exports 105 -> 108 in `check-web-audioworklet.sh`, `abi_layout.rs` `EXPORTS`,
`check-abi-layout-v1.py` and its self-test fixture; the `seekReport` structure (64 bytes, eight
fields) in `abi_layout.rs`, the checker's `STRUCTURES`/`SEEK_REPORT_FIELDS` and the fixture;
`sdk/assets/miso-engine-v1-abi-layout.json` and `sdk/src/generated/abi.ts` regenerated with
`cargo run -p parameter-metadata -- --print-abi-layout`, `node codegen/assets.mjs` and
`node codegen/generate.mjs`.

**Departures from the text.**
- `scripts/check-session-map-shape.py` (not in the authorized paths) treated every
`source_*(handle) -> u32` export as a source-map query, so it flagged `source_seek_report_ptr`.
Mechanical fix: it skips record-pointer exports (a name ending in `_ptr`; the fold made the
one-name skip this rule). No other behavior changed.
- The plain seek is unchanged. The amendment's hazard did not materialize: the parity row shows the
browser's report after a plain seek equals the C ABI's at the call (the old generation) and at
every later block (`first_sample` = the first block that begins after the call), at 48 and 96 kHz.
`prepare_source_seek` at the call changes what the consumer reads, not the published report.
- Gate 5 for the pointer export: `pointer_u32` truncates a 64-bit native address to 0, so the
export's value cannot be read natively. The fold split out `seek_report_pointer`, which the
export casts, and the test reads its address before the cast (`test_seek_report_addresses`, the
`test_buffer_address` pattern): the record's address while ready, null once not ready.
- Gate 3's "silence until the anchor" is set up with no PCM of the old generation queued.

**Gates -> tests.**
- Gate 3 and 4: `tests::the_anchored_seek_exports_play_at_the_anchor_and_report_where_it_landed`
(raw exports: an unaligned anchor and an unknown source are `RESULT_INVALID_ARGUMENT`; an anchor two
quanta ahead is `RESULT_OK`; two silent blocks then the sought frame; the record reads
`(2, 256, 10000)`, 256 held frames, 0 underrun frames, reserved zero).
- Gate 5: `tests::the_seek_exports_refuse_a_host_with_no_control_half_and_change_nothing`.
- Parity row (`hosts/host-web/tests/control_parity.rs`):
`the_seek_report_is_alike_on_both_hosts_for_plain_late_and_future_seeks` (plain seek, late
`seek_at` whose anchor is behind the render clock, and `seek_at` two quanta ahead; 48 and 96 kHz;
record equals the C struct field by field at the call and after every block; output bits equal).
- Gate 6: `python3 -B scripts/check-abi-layout-v1.py --self-test` (36 mutations caught, including
dropping each of the three exports and a record field), `parameter-metadata --check sdk/assets`,
`check-web-audioworklet.sh` (compares the wasm's exports with `expected_exports`),
`check-sdk-generated.sh target/ci/h1293/a`.

**Commands run.** `cargo test --locked -p host-web --features host-web/test-support` (all pass);
`cargo clippy --locked -p host-web -p parameter-metadata --all-targets --all-features -- -D
warnings` (two packages only; the workspace-wide clippy runs at the batch gates); `cargo fmt --all`; `build-web-audioworklet.sh --named-twin target/ci/h1293/n
target/ci/h1293/a`; `check-web-audioworklet.sh`; `check-sdk-generated.sh target/ci/h1293/a`;
`test-web-audioworklet.sh`; `check-workspace-policy.sh`; `cargo run -p realtime-policy`.

**Artifact.** `miso-engine-v1-audio-worklet.simd128.wasm`: 4,198,551 bytes, sha256
`20b08c9049f20b898676f17ea1fda74ea18eda4eabe8a94526c1e80eeb07b5be` (named twin 4,818,838 bytes,
`965b71c5265c0fa8cfa10579856e3f0274b1fba634423edf8e6fd69aba8ec5d7`).

**Successors.** The SDK binding for the three exports and the message-handler-only rule for
`source_seek_at` (the feed worklet's render-side seek must keep the plain seek) are #1294 and #1297.
Correction (batch 16a fold): at `454d65688` neither spec said so. #1297 D3 had `applySharedSeek`, an
audio-thread caller, call `source_seek_at`, and #1294 D1 still named the dropped `edit_ptr`/`apply`
exports; the fold amends both (M4, outside this record).
A `seek_at_unserviced` for a render-side caller stays unbuilt until a caller needs it.

**Fold (batch 16a review).**

- **MINOR 8.** `source_seek_report_ptr` has a behavioural test: in
`tests::the_seek_exports_refuse_a_host_with_no_control_half_and_change_nothing` the pointer
(before its `u32` cast) is the record's address while ready and null once the host is failed.
Red if the pointer names anything else while ready, or anything at all once not ready (both run).
- **MINOR 9.** The ABI self-test swaps the `firstSample` and `sourceFrame` names of `seekReport`,
offsets kept, which only the record's name rule catches (red with that rule deleted, run). It
replaces the record-field drop (caught by the generic layout rules) and two of the three seek
export drops; the `source_seek_at` drop stays. 34 mutations caught.
- **MINOR 10.** `check-session-map-shape.py` states the `_ptr` rule in its docstring and code (16
self-test mutations caught, unchanged). The gate 3/4 test no longer claims to catch another
source's ring (its session has one source). The staged-ID refusal now follows its operation's
bookkeeping: `source_seek_at` records it as `seek_source_at` records its results, and
`source_seek_report` records none, as `read_seek_report` records none (`with_staged_source_id`
returns the refusal to its caller). The gate 5 test checks both; red if the report export
records its refusal (run).
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,55 @@ worklet only renders and swaps.
- *Supersede an unadopted candidate plan by compare-and-swap* (#1310).

**Decision 17 amendment (2026-10-13).** D1's `miso.document.v1` message and D6's `committedDocument()` wrapper method are withdrawn: #1571 reads the committed document with `SESSION_SNAPSHOT_GET` frames through #1562's `control` message. Every event the control plane stages, `SESSION_COMMITTED` included, reaches the page through #1570's `control-event` message. D2-D5 stay.

## Amendment (controls coordinator, batch 16a review, 2026-10-16)

Anchors checked against `90b76cfdf` (batch 16a with its first fold). Earlier text is kept.

**Finding (review M4).** D1 names exports that #1293 dropped.
- `miso_engine_web_v1_edit_ptr` and `miso_engine_web_v1_apply`, and the "outcome record", do not exist.
Root ruling B15-R8 dropped D1's staging, and decision 17 dropped D2's `apply` and D3's outcome record
(see #1293, "Decision 17 amendment (2026-10-13)" and "B15-R8" in its batch 15 check).
- `miso_engine_web_v1_replace` (#1386 D3) is not built either. #1386 is re-scoped under C1
(`docs/rulings/one-control-abstraction-2026-10-13.md`, C1 and C13): the replace is a protocol frame
through the same generic entry. Its browser transport is #1697. See #1386's amendments.

**Rule, replacing D1's call path.**
- A transaction is staged in `miso_engine_web_v1_control_request_ptr` (`hosts/host-web/src/ffi.rs:3464`)
and applied with `miso_engine_web_v1_control_command` (`:3483`), on the control half only. The worklet
already has the staging helper: `readControlCommand`
(`hosts/host-web/web/miso-engine-v1-audio-worklet.js:1727`) and the message `miso.control.v1`
(`CONTROL_FIELDS = ["tag", "requestId", "frame"]`, `:93`; dispatched at `:1116`).
- The outcome is the response frame, read with `miso_engine_web_v1_control_response_ptr` (`ffi.rs:3516`)
and `_control_response_bytes` (`:3529`): `revision`, `edit_path` and the diagnostics. `miso.edit.v1`
`{ result, revision, path, diagnostic }` is built from that frame and from the call's result code.
`path` is the frame's `edit_path`; `diagnostic` is the frame's diagnostic text, empty on success.
- `miso.apply.v1` and `miso.replace.v1` stay as the SDK's internal messages only if the root re-brief
keeps them. They may be dropped for `miso.control.v1` frames. Root decides in the #1386 re-brief; this
issue does not add a message that has no export behind it.

**Other text that names the dropped exports.**
- Context, the exports bullet (`:47-53`): "the exports come from #1293" is now only `source_seek_at` and
`source_seek_report`. Apply and replace come from #1562's generic entry and #1386/#1697. The service and
watermark exports stay with #1381.
- D2 (`:71-75`): `source_seek_at` is real (`ffi.rs:3313`) and is control half only: it runs the service
step (`crates/control-plane/src/control.rs:4073-4081`) and must never run in `process()`. Its sentence
"Feeds that use the shared PCM ring do not use it" is amended: a ring's anchored seek is applied by this
handler between blocks (#1297 amendment of 2026-10-16). The writer that publishes an anchored seek on
the ring also posts the same seek (generation, frame, anchor) to the control half as
`miso.seek-at.v1`; that message is what wakes this handler. The feed worklet's render path keeps the plain
seek. No render-side caller is added.
- D6 (`:118-120`): `apply(transaction)` and `replace(document)` call the generic entry; they need no
`edit_ptr`.
- Non-goals (`:140`, "every export used here comes from #1293, #1381 or #1386"): read it as #1293
(`source_seek_at`, `source_seek_report`), #1381, #1562 and #1697.
- Gate 1 (`:145-147`, "calls `edit_ptr` then `apply`"): replace with "stages the frame through
`control_request_ptr`, calls `control_command` once, and replies from the response frame". A malformed
message still calls no export.
- Gate 5 (`:162-163`, "typed result and diagnostic"): read both from the response frame.
- Test value, gate 1 (`:173-174`, "replies before the outcome record is written"): read it as "replies
before the response frame is read, or from a stale response of the previous command". The worklet
has this hazard today (`readControlCommand`, `:1727-1747`, returns an empty response when staging
refuses).
- Dependencies (`:187`, `:193-194`): #1293 supplies only the seek exports. Add #1562 (the generic entry)
and #1697 (document-replace transport). #1386 is a dependency only through its re-brief.
Loading
Loading