Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
51ec4fd
Seek the timeline and every source in one step, behind a publish fenc…
tamashi095 Oct 16, 2026
a5beb8b
Export the session seek from the C ABI (#1642 tranche 2)
tamashi095 Oct 16, 2026
547da41
Merge branch 'codex/controls-batch-16a' into codex/controls-batch-16b
tamashi095 Oct 16, 2026
5567f74
Gate RealtimePlanOwner::active_plan_mut behind engine/test-support (r…
tamashi095 Oct 16, 2026
e9a203c
Seek the timeline and every source from the browser module and the he…
tamashi095 Oct 16, 2026
fa4cf01
Merge branch 'codex/controls-batch-16a' into codex/controls-batch-16b
tamashi095 Oct 16, 2026
5dbc72a
Placeholder TBD46: report source refusal diagnostics on the browser a…
tamashi095 Oct 16, 2026
fdb1631
Merge remote-tracking branch 'origin/codex/controls-batch-16a' into c…
tamashi095 Oct 16, 2026
07b8084
Fold the batch 16b review and root ruling B16-R9 (#1642, #1643)
tamashi095 Oct 16, 2026
d05a35d
Merge branch 'codex/controls-b16b-fold' into codex/controls-batch-16b
tamashi095 Oct 16, 2026
cebbf0d
Merge remote-tracking branch 'origin/main' into codex/controls-batch-16b
tamashi095 Oct 16, 2026
f7459fc
Placeholder TBD47: ask for a control queue slot only when a declared …
tamashi095 Oct 16, 2026
c2e6328
Fold the batch 16b fold verification (#1642)
tamashi095 Oct 16, 2026
0282d0b
Update the test-support CI self-test for engine/test-support and host…
tamashi095 Oct 16, 2026
09f4723
Number the two batch 16b placeholders: #1706, #1707
tamashi095 Oct 16, 2026
e2a782d
Gate the session seek tests that call test-support helpers (#1643)
tamashi095 Oct 16, 2026
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 @@ -417,3 +417,14 @@ adoption, `EXACT`, with `SUPERSEDED` for those A folded in.
**Measured.** `audit capi`: allocations 0, deallocations 0, syscalls 0, pcm_digest
`4f52089e4a8cf83c` (unchanged; `tools/audit` not touched). C ABI gate 2: latency 486 -> 0 at
48 kHz. No resource ceiling was re-pinned.

## Amendment (root ruling B16-R9, batch 16b review, 2026-10-16)

B15-R7's order ("#1643 calls it before its seek") is retired. A plain session seek (#1642,
`SessionState::session_seek`) is a declared discontinuity: it runs this slice's declaration in the
same call, through the same core path (`declare_discontinuity_serviced`), after every check and
before any push; a refused seek declares nothing, and a refused declaration pushes nothing and
returns its own error. An anchored session seek declares nothing (it keeps continuity: D2 publishes
`Next`, before the anchor). No host and no SDK calls `declare_discontinuity` before a session seek.
D1's duty is now: a host calls it at a stop, and before a seek of every source made with
per-source seeks. B15-R7's browser export stays as it is.
242 changes: 242 additions & 0 deletions .github/ISSUE_SPECS/1642-seek-the-session-from-the-c-abi.md

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions .github/ISSUE_SPECS/1643-seek-the-session-from-the-headless-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,136 @@ exports and the C13 list, the SDK-chosen generation (D2), and a parity row in
and the timeline (#1641's host-core test reader) reads the same value on both hosts. Red if
either host splits the seek across blocks, clamps differently or drops the lateness. It needs
#1642's C export.

## Decision record: #1643 (batch 16)

Built on `5567f7487` (worktree `codex/controls-batch-16b`).

**What shipped.**
- **D1.** `miso_engine_web_v1_session_seek(handle, generation, frame)` and
`miso_engine_web_v1_session_seek_at(handle, generation, frame, anchor)` in
`hosts/host-web/src/ffi.rs`, one shared body `AudioWorkletEngineHost::session_seek(generation,
frame, Option<anchor>)` in `lib.rs`: it calls `SessionState::session_seek` (the C ABI's call,
after its service step) and maps a refusal through `source_failure_result`. host-web holds no seek
logic and never calls `prepare_source_seek`. A host that is not ready refuses with
`RESULT_REFUSED_LIFECYCLE` and changes nothing; both exports run the service step, so the page
calls them from the message handler only (neither is `render_locked`).
- **D2.** `OfflineEngine.seek(frame, options?) -> { generation } & EngineCallResult`
(`sdk/src/headless/engine.ts`) and `WasmBoundary.declareDiscontinuity()` / `sessionSeek()`
(`sdk/src/core/boundary.ts`). `sessionSeek` calls `miso_engine_web_v1_declare_discontinuity`
first (B15-R7), stops at its refusal, then calls the session seek export. The generation is
`options.generation`, else one above the largest this object has passed (starts at 1). The
counter (`#highestGeneration`) is the only state added; `seek`, `seekSource` and `submitSource`
raise it on an `ok` result only, so a refusal (backpressure, stale) never moves it.
- **D3.** Mirrors: `check-web-audioworklet.sh` `expected_exports`, `abi_layout.rs` `EXPORTS` (108 ->
110), `check-abi-layout-v1.py` `EXPORTS` and the fixture, `sdk/assets/miso-engine-v1-abi-layout.json`
and `sdk/src/generated/abi.ts` (regenerated with `node codegen/assets.mjs` and `node
codegen/generate.mjs`). The ABI self-test gains two mutations (each new export dropped): 36
caught, from 34. The test-worklet mock needed no edit.

**Departures.**
- **Test-support plumbing outside the listed paths** (mechanical): `hosts/host-web/Cargo.toml`
`test-support` now also names `control-plane/test-support` and `engine/test-support`, so a native
host-web test reaches the active plan's timeline through `PlanState::test_owner_mut` and
`RealtimePlanOwner::active_plan_mut` (B16-R8) and `host_core::test_only_timeline_at`:
`AudioWorkletEngineHost::timeline_at_for_test` and `native_staging::timeline_at`, both
`cfg(feature = "test-support")`. The workspace test steps already name both features
(`check-test-support-ci.py` passes, 13 packages). No file under `crates/engine/src/realtime/`
changed.
- **Gate 6: the C ABI's timeline is not read.** `capi::Plan` and `Session` expose no accessor
outside the crate (`test_plan_timeline_at` is `cfg(test)` and crate-private), and a new public
capi test hook is not mechanical. The parity row therefore compares the browser's and the C
ABI's seek reports of all three sources field by field (at the call, after every block through
the applying one and after), the output bits, and reads the browser's timeline: it moves in the
applying block, runs on one quantum per block, and in the late seek lands on the frame the long
sources' reports (equal on both hosts) name. The C ABI's timeline is pinned by capi's own gate
(`runtime::tests::session_seek`). The row is `cfg(feature = "test-support")`.
- **Gate 1/6: the short source is not fed at its clamped frame.** A source clamped to its end has
no frame to submit there (a chunk from the end is `RESULT_INVALID_ARGUMENT`), so the long source
is fed and the short source underruns; its report still names the clamped frame.
- **Gate 1's full-slot case** holds the slot with an anchored `source_seek_at` (which, unlike the
plain `source_seek`, does not prepare the consumer and so does not drain the slot).
- **A refused session seek still declares the discontinuity first** (the SDK holds no
generation state beyond its counter, so it cannot know the core would refuse). The declaration
resets latency floors and publishes nothing else; it is the C ABI host's order.
- **Gate 4** (no rendered bit moves) is the unchanged `browser` job digests; this slice adds no
render code and no worklet call. Not run here (CI job).

**Gates -> tests.**
- Gate 1 (`hosts/host-web/src/tests.rs`, `tests::session_seek`):
`both_forms_move_the_timeline_and_every_source_at_one_block` (plain seek at 2/6,000: long at
6,000, short at 4,000, timeline 6,000 in one block; anchored seek held two blocks then landing
at the anchor), `a_stale_generation_changes_nothing` (next block bits and timeline equal a run
without the call; a per-source seek to 5 makes a session seek at 4 stale),
`one_full_slot_refuses_every_consumer` (`RESULT_BACKPRESSURE`; the short source's slot and the
timeline stay untouched). All call the `extern "C"` exports.
- Gate 2 (`sdk/test/capability-evals.mjs`): "session seek declares the discontinuity first and moves
the sources in the next block" (export call order `declare_discontinuity`, `session_seek`; output
equals a `seekSource` twin and is not silent; default generations 2, 3, 10, 13, 14; a stale
explicit generation is `invalidArgument`; usage errors reach no export).
- Gate 3: `check-web-audioworklet.sh` (compares the wasm's exports with `expected_exports`),
`check-abi-layout-v1.py` and `--self-test` (36 mutations), `check-sdk-generated.sh`.
- Gate 3a (`tests::session_seek`):
`the_session_seek_exports_refuse_a_host_with_no_control_half_and_change_nothing` and
`a_seek_before_the_swap_reaches_the_carried_and_the_added_source_and_the_timeline` (the
transaction goes through `native_staging::control_command`, the `control_command` export; the
seek before the swap reaches the carried source, the added source and the timeline in the swap
block, active epoch 0 -> 1).
- Gate 6 (`hosts/host-web/tests/control_parity.rs`):
`the_session_seek_is_alike_on_both_hosts_for_plain_and_late_seeks` (48 and 96 kHz).

**Commands run (all pass).** `cargo test --locked -p host-web --features host-web/test-support`
(lib 145 passed, control_parity 20 passed, every other binary green); `cargo clippy --locked -p
host-web -p parameter-metadata --all-targets --all-features -- -D warnings`; `cargo fmt --all --
--check`; `RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps -p host-web --all-features`;
`build-web-audioworklet.sh --named-twin`; `check-web-audioworklet.sh`; `test-web-audioworklet.sh`;
`check-sdk-generated.sh`, `check-sdk-types.sh`, `check-sdk-headless.sh` (565 SDK tests pass);
`check-abi-layout-v1.py` (document and `--self-test`); `check-session-map-shape.py` (and
`--self-test`); `check-cross-targets.sh` (PASS, no new `memset_pattern16` call);
`check-workspace-policy.sh`; `check-test-support-ci.py`; `cargo run -p realtime-policy` (153
regions, unchanged). The workspace-wide clippy runs at the batch gates.

**Artifact.** `miso-engine-v1-audio-worklet.simd128.wasm`: 4,207,840 bytes, sha256
`2fc4d8aaa1b9fb49f18c71cc8f2e0fc8fd1f096b96324e86bc6312e64bf92a74` (named twin 4,830,433 bytes,
`f6cb1e2c5d2a5d1ea88327d6331c2473ff7eeaa5d5f75eff550a8b19a2521b01`). The committed pin is not
re-pinned.

**Successors.** (1) A capi test hook that reads the active plan's timeline would let the parity row
compare both timelines directly. (2) #1644 (browser SDK `seek` and the PCM feed's ring rule) is the
next caller of the exports.

## Amendment (root ruling B16-R9, batch 16b review, 2026-10-16)

- **B15-R7's order is retired.** The headless `seek` (D2) and `WasmBoundary.sessionSeek` no longer
call `miso_engine_web_v1_declare_discontinuity`: a plain session seek is a declared discontinuity,
and the engine declares it in the same call, after every check, so a refused seek declares
nothing. An anchored session seek keeps continuity and declares nothing (the loop and exact-start
form; README A1.2). `WasmBoundary.declareDiscontinuity()` stays for a stop.
- **Gate 2 amended.** It asserts the SDK does not call the declaration before a session seek (the
call log is `["session_seek"]`), and a new eval holds the B16-R9 rules on the headless path.

## Fold (batch 16b review), part B / B16-R9

- **SDK.** `sdk/src/core/boundary.ts` `sessionSeek` and `sdk/src/headless/engine.ts` `seek`: no
declaration call; the doc comments say the engine declares in the same call. The browser export
(`miso_engine_web_v1_session_seek`) runs the plain form through the host's `run_control`, so a
plan the declaration publishes keeps its tables and counts as a blocking rebuild; the anchored
export declares nothing.
- **Evals (`sdk/test/capability-evals.mjs`).** `session seek calls the session seek export alone and
moves the sources in the next block` (gate 2, the call log is `["session_seek"]`; red with the
SDK's declaration restored) and `a plain session seek declares the discontinuity itself, and a
refused one declares nothing` (B16-R9 gate 4 on the parity session: t3's limiter removed through
the new `remove_t3_limiter` structural frame, so #1285 floors hold the latency; a stale plain seek
keeps the floors, an accepted one resets them, and no separate declaration is called; red with
the SDK's declaration restored, and red against an artifact built with the core's declaration
removed). The headless SDK has no anchored session seek, so rule 3 is held on the C ABI and the
parity row only.
- **Fixture.** `hosts/host-web/tests/structural_frames_fixture.rs` writes one more frame,
`remove_t3_limiter` (`RemoveTrackEffect` of t3's limiter), into
`hosts/host-web/tests/fixtures/structural-frames.json` (blessed; the byte gate passes).
- **Browser tests.** `tests::session_seek::the_session_seek_exports_refuse_a_host_with_no_control_half_and_change_nothing`
now asserts no seek waits in a slot after the refusals (`native_staging::session_seek_slots_empty`,
test-support; MINOR f; red when the export pushes before its lifecycle refusal); its doc says the
failed host keeps its control state. The `one_full_slot_refuses_every_consumer` comment is
corrected (MINOR e: the slot frees at the next block's pop; it is full only because no block
renders between the calls).
13 changes: 13 additions & 0 deletions .github/ISSUE_SPECS/1644-seek-the-session-from-the-browser-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,16 @@ Checked against `12d4a4f9d`.
refused declaration calls no seek export.
- **Dependencies** gain #1323 (the export and its mirrors). Authorized paths do not change: the
call is in D1's handler.

## Amendment (batch 16b review fold, root ruling B16-R9, 2026-10-16)

- **This slice is the caller of `miso_engine_web_v1_session_seek` and
`miso_engine_web_v1_session_seek_at`** (#1643 D1; MINOR d): D1's handler calls the plain export
for a seek and the anchored export for a seek with an anchor (the loop and exact-start form).
No other SDK file calls the anchored export today.
- **"Discontinuity first (B15-R7 reach)" (batch 15a fold) is retired.** A plain session seek
declares the discontinuity in the same call, and a refused seek declares nothing; an anchored
session seek keeps continuity. D1's handler calls the session seek export alone, never
`miso_engine_web_v1_declare_discontinuity` before it. Gate 2's added rows become: the handler
calls no declaration export for a session seek message, once per message.
- **Dependencies.** #1323 is no longer one for this slice.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Report source refusal diagnostics on the browser as on the C ABI

Filed by root from the batch 16 #1293 review (MINOR 5). It is the "source submit
diagnostics" one-host difference that decision 17 C13 lists
(`docs/rulings/one-control-abstraction-2026-10-13.md`, C13).

## Product outcome

A browser host that is refused a source call (submit, seek, anchored seek, session seek or seek
report) reads the same typed diagnostic as a C host: for example `source.id.unknown`,
`source.seek.anchor_unaligned`, `source.generation.stale`, `source.seek.backpressure`,
`source.report.busy` or `capi.source.epoch`'s browser twin. One core function owns the mapping from
a `SourceFailure` to its result kind and diagnostic; both hosts call it.

## Context (anchors on `5567f7487`)

- The C ABI maps `SourceFailure` to `(result code, diagnostic bytes)` in
`crates/capi/src/runtime/error.rs:121-143` and writes the diagnostic to
`miso_engine_v1_last_error`.
- The browser maps the same failure in `hosts/host-web/src/lib.rs:5338-5356`
(`source_result`, `source_failure_result`) to a result code only, and drops the core's
diagnostic. The result-code rule is written twice, once per host: logic in two adapters.
- The core's text is `SourceControlError::diagnostic` (`crates/host-core/src/source.rs`).

## Decisions to make at filing

- D1. One core mapping, in `crates/control-plane` beside `SourceFailure` (or in host-core):
`SourceFailure::report() -> (SourceResultKind, &'static str)` (kinds: ok-refusal invalid
argument, backpressure, internal). capi and host-web map the kind to their result codes and
write the text to their diagnostic buffers; neither holds the rule.
- D2. The browser writes the diagnostic where `AudioWorkletEngineHost::diagnostic` is read today,
for every source export, and the SDK surfaces it as it surfaces other refusals.
- D3. Parity row in `hosts/host-web/tests/control_parity.rs`: each refusal kind of each source call
gives the same result kind and the same diagnostic on both hosts.

## Dependencies

- None blocking. #1644 (browser SDK `seek`) benefits from it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Ask for a control queue slot only when a declared discontinuity publishes

Filed by root from ruling B16-R10 (batch 16b, 2026-10-16).

## Product outcome

A declared discontinuity that publishes nothing never refuses with backpressure on either host.
That covers the standalone declaration (`miso_engine_v1_declare_discontinuity`,
`miso_engine_web_v1_declare_discontinuity`) and the plain session seek, which declares one in the
same call (B16-R9). Only a declaration that publishes a successor asks for room in a host's
control queue, and the core decides whether it publishes before any host asks for that room.

## Context (anchors on `cebbf0df4`)

- The core decides whether a discontinuity successor is needed with #1323 D2's two terms: a floor
above a node's natural arrival, or a source-read offset above 0
(`PlanStateInventory::runs_behind_natural_timing`, `crates/host-core/src/prepare.rs:1022`).
When neither holds, `SessionState::declare_discontinuity` (`crates/control-plane/src/control.rs:2154`,
serviced body `:2161`) returns `Ok` and publishes nothing.
- The browser runs both calls through `AudioWorkletEngineHost::run_control`
(`hosts/host-web/src/lib.rs:1737`). It refuses with its `full` error when the pending-shapes table
queue is full (`:1745-1747`), before the core runs, so before the core knows whether anything
would publish. The declaration (`:1695`) and the plain session seek (`:1713`, `full` =
`SessionSeekFailure::Declaration(CommandError::Backpressure)`) both inherit that refusal.
- The C ABI has no such queue, so the same call returns `OK` there. That is a one-host difference
(decision 17 C1: browser and C ABI behave the same).

## Decisions to make at filing

- D1. The core exposes the decision before the publish: for example a `SessionState` query
"would a declaration publish now?" (the two #1323 D2 terms on the newest plan), or a
declaration API that takes the host's room as a parameter and refuses with backpressure only when
it would publish. Pick one; one core rule, both hosts.
- D2. `run_control` asks for a slot only when the core says the call publishes; a declaration or
plain session seek that publishes nothing returns `OK` on a full queue, as on the C ABI.
- D3. Parity row in `hosts/host-web/tests/control_parity.rs`: with the browser's table queue full,
(a) a standalone declaration and (b) a plain session seek that publish nothing return `OK` on
both hosts and the seek applies; (c) ones that would publish (floor raised on the #1285 path)
refuse with backpressure on the browser and nothing changes. Red if the browser asks for the slot
first again.

## Dependencies

- #1642 and #1643 (B16-R9's plain session seek).
4 changes: 2 additions & 2 deletions .github/workflows/qualification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ jobs:
--exclude conformance --exclude audit --exclude bench --exclude console-workload \
--exclude wasm-gates --exclude wasm-gate-guest \
--exclude wasm-gate-corpus \
--features builtins-compiler/test-support,graph/test-support,host-web/test-support,host-core/test-support,effect-compiler/test-support,protocol/test-support,control-plane/test-support,engine/realtime-audit
--features builtins-compiler/test-support,graph/test-support,host-web/test-support,host-core/test-support,effect-compiler/test-support,protocol/test-support,control-plane/test-support,engine/test-support,engine/realtime-audit
# --all-targets runs no doctest, and most doctests here are compile_fail type-level
# guarantees, each beside a plain twin (#1422). Same packages and features as the step above.
- name: Workspace doctests (same packages and features as the debug tests)
Expand All @@ -647,7 +647,7 @@ jobs:
--exclude conformance --exclude audit --exclude bench --exclude console-workload \
--exclude wasm-gates --exclude wasm-gate-guest \
--exclude wasm-gate-corpus \
--features builtins-compiler/test-support,graph/test-support,host-web/test-support,host-core/test-support,effect-compiler/test-support,protocol/test-support,control-plane/test-support,engine/realtime-audit
--features builtins-compiler/test-support,graph/test-support,host-web/test-support,host-core/test-support,effect-compiler/test-support,protocol/test-support,control-plane/test-support,engine/test-support,engine/realtime-audit

test-debug-b:
name: DSP crates debug tests
Expand Down
3 changes: 3 additions & 0 deletions crates/capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ builtins = { workspace = true, features = ["test-support"] }
# #1309 D8: the control plane's observers and fault hooks, for capi's tests only. A dev-dependency
# feature never reaches the release `capi` build (`scripts/check-capi-abi.sh` builds `-p capi`).
control-plane = { workspace = true, features = ["test-support"] }
# B16-R8: `RealtimePlanOwner::active_plan_mut`, so the #1642 gates read the active plan's
# timeline. A dev-dependency feature never reaches the release `capi` build.
engine = { workspace = true, features = ["test-support"] }
# #1639: the block's held jump lengths (`graph::test_only_block_jump_lengths`), for capi's tests only.
graph = { workspace = true, features = ["test-support"] }
# The tests write control values by row name (`control_core::{row_address, fader_edits, ...}`).
Expand Down
Loading
Loading