Skip to content

Controls batch 16b: seek the session in one step on both hosts, behind a publish fence - #1708

Merged
tamashi095 merged 16 commits into
mainfrom
codex/controls-batch-16b
Oct 16, 2026
Merged

tamashi095 merged 16 commits into
mainfrom
codex/controls-batch-16b

Conversation

@tamashi095

@tamashi095 tamashi095 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Controls batch 16b: the session seek. One call moves the session's timeline and every source to one timeline sample, under one new generation, all or nothing, on both hosts. A publish fence makes every consumer take the seek in the same render block even while the session thread pushes during a render. The C ABI exports miso_engine_v1_session_seek and _session_seek_at. The browser engine module exports the same two calls. The headless SDK gains OfflineEngine.seek. This PR sits on batch 16a and merges after it.

Closes #1642
Closes #1643

What changed

  • Seek the timeline and every source in one C ABI call #1642 tranche 1 (51ec4fd): the publish fence and the session seek in the core.

    • The publish fence (root ruling B16-R2). There is one PublishedGeneration word per session (an Arc<AtomicU64>). PlanStateInventory keeps it across every plan of the session: successors, discontinuity successors and withdrawn-candidate successors. Every source producer, the timeline producer and each plan's source-set driver share it.
    • The driver loads the word once per block entry (Acquire). SeekState::take_published, the one peek/pop path, pops a command only when its generation is at or below the word.
    • Every seek push publishes max(word, g) with a Release fetch_max. The session seek publishes once, after its last push.
    • The PCM acquire peeks each block before it pops it, so a block whose seek is held back stays queued. Nothing acked is dropped.
    • Behaviour note: a per-source seek pushed while a block renders now applies at the next block, not possibly the current one. Only a concurrent host can see this.
    • SourceControlSet::seek_session:
      • It refuses generation 0, and a generation at or below the word (source.generation.stale).
      • It checks every producer with check_seek before it pushes to any.
      • It pushes the timeline first, then the sources in ID order. Each source goes to min(timeline_sample, its frames); the timeline frame is not clamped (limit 2^53).
      • A push that fails after the checks is InternalInvariant.
    • SessionState::session_seek runs the service step, then seeks the newest committed session (the candidate's producers while a swap waits).
    • The word costs 24 B, charged in PlanStateInventory.
  • Seek the timeline and every source in one C ABI call #1642 tranche 2 (a5beb8b): the C ABI.

    • miso_engine_v1_session_seek and _session_seek_at share one body.
    • MISO_ENGINE_V1_FEATURE_SESSION_SEEK is 1 << 10, so the mask is 2047. The frozen symbol list goes 19 -> 21.
    • Header: the session seek paragraph after "Anchored seek.", the thread table and the "Control service" list. The text says a host may pick one above the largest generation it has used.
    • The qualification record carries the same text.
    • Four private intra-doc links became plain code spans.
  • Root ruling B16-R8 (5567f74): RealtimePlanOwner::active_plan_mut, the capi gates' reader of the active plan's timeline, is behind a new engine/test-support feature. capi's dev-dependency turns it on. The workspace debug-test and doctest steps of qualification.yml name it, so check-test-support-ci.py passes (13 packages). The release build does not see it. The edit under crates/engine/src/realtime/ was accepted on its one attempt.

  • Seek the timeline and every source from the browser module export and the headless SDK #1643 (e9a203c): the browser and headless SDK.

    • miso_engine_web_v1_session_seek and _session_seek_at call SessionState::session_seek only: no seek logic in host-web, and no prepare_source_seek. A host with no control half refuses with RESULT_REFUSED_LIFECYCLE.
    • OfflineEngine.seek(frame, { generation? }) calls WasmBoundary.sessionSeek once (after B16-R9 the core declares the discontinuity itself). The default generation is one above the largest this engine object has used through seek, seekSource or submitSource (B16-R5).
    • Mirrors: EXPORTS goes 108 -> 110, and the SDK mirrors are regenerated.
    • host-web's test-support also names control-plane/test-support and engine/test-support, for the timeline reader.
  • No production caller yet. miso_engine_web_v1_session_seek_at waits for Seek the timeline and every source from the browser SDK and the PCM feed #1644, the browser SDK seek. The headless seek() is the first SDK caller of miso_engine_web_v1_session_seek.

  • Root ruling B16-R9, folded in 07b8084.

    • A plain session seek is a declared discontinuity. SessionState::session_seek checks every producer, then declares the discontinuity through the core's one declaration path, then pushes, in one call on both hosts. A refused seek declares nothing. A refused declaration pushes nothing and returns the declaration's own error. A push that fails after the check is InternalInvariant.
    • An anchored session seek keeps continuity and declares nothing. It is the loop and exact-start form; a declaration would reset floors and effect tails before the anchor while the old pass still plays.
    • No host and no SDK calls declare_discontinuity before a session seek. The SDK and the audit lost their separate calls, and B15-R7's order is retired (Reset latency floors at a host-declared discontinuity #1323, Seek the timeline and every source from the browser module export and the headless SDK #1643, Seek the timeline and every source from the browser SDK and the PCM feed #1644).
    • declare_discontinuity keeps its duty for a stop, and before a seek of every source made with per-source seeks.
    • Gates, each red when its rule is removed: a refused plain seek publishes nothing; an accepted plain seek publishes one discontinuity successor and its seek applies; an anchored seek publishes no successor. They run on the C ABI (session_seek_declares, also while a candidate waits), in a parity row at 48 and 96 kHz, and in the headless SDK eval. The floor is raised on the Keep every node's latency from dropping during playback #1285 path, through a new structural frame remove_t3_limiter (structural-frames.json regenerated).
  • Review fold M2 (07b8084).

    • The unfenced mode is gone. The session's word is a required argument of every ring, timeline and set preparation, and prepare_graph_source_set is removed.
    • The only unpublished push is the source crate's SessionSeek (PublishedGeneration::session_seek). It owns the zero and stale rules, refuses a producer of another session, and publishes once, only if it pushed.
    • A set, carry or swap across two words is refused (SourceGraphSourceSetError::SessionWord; a pointer compare on the render path).
    • PcmSourceRing::prepare(config) stays for standalone tests. It gives the ring a private word, so the ring is fenced as a session of one.
  • Review fold M1. Stale tests where the word is above every live producer (host-core), and where a removed source keeps a session seek stale (capi). Each is red without the stale rule.

  • Current host difference (root ruling B16-R10, Ask for a control queue slot only when a declared discontinuity publishes #1707). On the browser, a plain session seek or a standalone declare_discontinuity refuses with RESULT_BACKPRESSURE when the control table queue is full, even when nothing would publish. The C ABI has no such queue and returns OK. Ask for a control queue slot only when a declared discontinuity publishes #1707 fixes it in the core for both calls, with a parity row.

Evidence

  • Gate 1b (crates/host-core/tests/session_seek.rs):

    • A session thread pushes 10,000 plain and anchored session seeks with rising generations, at random moments up to 40 µs into a block, while a render thread renders.
    • Every seek lands in one block, on the timeline and on both sources, at Simd8 and Simd4.
    • Render-thread allocations, reallocations and frees are 0.
    • With the fence removed, it went red in both runs (the short source one block ahead).
  • C ABI gates 1-7 (runtime::tests::session_seek): both forms move everything at one block; a stale generation changes nothing; one full slot refuses every consumer; a seek before the swap reaches the carried source, the added source and the timeline; a per-source seek leaves the timeline alone; the ABI checks pass.

  • Browser gates 1 and 3a (hosts/host-web/src/tests.rs, mod session_seek). Gate 2 headless eval (sdk/test/capability-evals.mjs): the export order, default generations and stale refusal. SDK suite: 565 of 565.

  • Parity row (B16-R6, control_parity.rs::the_session_seek_is_alike_on_both_hosts_for_plain_and_late_seeks): a plain and a late anchored session seek at 48 and 96 kHz, with three sources (one shorter). The browser and C ABI seek reports are equal field by field, and the output bits are equal, at the call and after every block. The row reads the timeline on the browser only, because capi has no public test hook for it; capi's own gates pin the C ABI timeline.

  • audit capi: pcm_digest 4f52089e4a8cf83c -> bffc21e857b5e134. Reason: the new audit step, miso_engine_v1_session_seek(session, 3, 8 * QUANTUM_FRAMES) and then one generation-3 quantum, each RESULT_OK, plays that quantum in call 5, where the run gave underrun silence before. The B16-R9 fold removed the audit's separate declare_discontinuity call. The digest did not move with it, because the audit session has no raised floor and no source-read offset, so neither declaration publishes. 0 violations; replacements == 1 holds.

  • The full local CI-equivalent set (batch-gates.sh) ran on the fold tree d05a35d and passes, with one exception below. It covers:

    • fmt, the bench policy, and the realtime policy (153 marked regions in 33 files);
    • the host-core feature matrix and loom;
    • workspace clippy and the workspace tests;
    • all 15 shell gates, the web build, check-web-audioworklet (callgraph pins included), check-sdk-headless (566 of 566), the 7 Python gates and the fuzz check.
  • The exception was the doc job's test-support CI self-test (scripts/test-test-support-ci.py). It pins the debug-test step's feature string and host-web's manifest text, and B16-R8 (engine/test-support) and Seek the timeline and every source from the browser module export and the headless SDK #1643 (host-web forwarding control-plane and engine) made those pins stale. 0282d0b updates them. Its doctest-parity mutation now drops protocol/test-support, which nothing forwards. At the 16b head 0282d0b, the full doc job (every step, the self-test included), the capi and host-web test suites, check-capi-abi.sh and fmt pass again.

  • Browser qualification passes on the fold tree on Chromium 151.0.7922.34, Firefox 153.0 and WebKit 26.5 (--check-matrix --self-test-mutations). Render-locked allocations are 0. Later commits change no web code: fold 3 changes capi tests, a header wrap, a host-web test name and a CI self-test script.

  • Artifact miso-engine-v1-audio-worklet.simd128.wasm at the 16b head: 4,212,349 bytes, sha256 4c7c5c7af31d759d2744a5ed079effeebbf2b5fa0e4d37152b04e4d3e95c660d (the committed release pin is not re-pinned).

Re-pins

  • None. The 24-byte fence word is charged in PlanStateInventory::retained_bytes. No source pin or ceiling moved. capi_retained_bytes stays inside its 175,296 ceiling (resource_lifecycle passes; 168,378 at batch 16a, plus the 24-byte word). Browser boot high-water growth: 19,857,408 bytes, unchanged, within the 20 MiB pin (20,971,520).

Review

  • Batch review (opus-high): FAIL, no blocker. The fence design is confirmed sound, and gate 1b was red 3 of 3 with the fence off. Three majors, all folded in 07b8084:
    • M1: the stale-against-the-word rule had no test that separated it.
    • M2: the unfenced second mode was removed.
    • M3: a refused headless seek still declared a discontinuity. This is root ruling B16-R9, folded as above.
      Minors folded: a complete header refusal list, one qualification-doc wording fix, Seek the timeline and every source from the browser SDK and the PCM feed #1644 names the web session seek exports, a test comment, and the 3a lifecycle test now checks that no seek waits.
  • Fold verification (opus-high): PASS, no blocker or major. Every fold rule is red under its mutation: the stale rule, declare-before-check, no declaration, an anchored declaration, the SDK's separate declaration, and the lifecycle order.
    • The verifier confirmed that the producers after the declaration are the very ones checked, so a valid seek cannot become INTERNAL.
    • PcmSourceRing::prepare(config) has no host caller; only the source crate's integration tests call it. So it stays ungated, which meets root ruling B16-R10's condition.
    • Its one rule with no gate, "a refused declaration pushes nothing", now has one: session_seek_declares::a_refused_declaration_pushes_nothing_and_leaves_the_generation_free (c2e6328). It is red when the declaration's error is ignored. The same commit rewraps one header line, renames one test and adds two record lines.

Issues to file from this batch's specs

Spec bodies to sync after merge

#1642 (tranche 1 and 2 records, B16-R8, B16-R9), #1643 (decision record, B16-R9), #1644 (caller of the web session seek exports; B15-R7 retired), #1323 (B15-R7 retired), #1706, #1707.

CI fold

  • The first CI run failed in "release shape and panic-clobber policy" (cargo check --release --workspace --all-targets, no features): four host-web lib tests and four control_parity helpers called test-support-only functions without the feature. e2a782d gates them like structural_apply; the batch gate script now runs both no-feature checks.

🤖 Generated with Claude Code

tamashi095 and others added 16 commits October 16, 2026 13:46
#1642 tranche 1)

B16-R2: one PublishedGeneration word per session (Arc<AtomicU64>), kept
by PlanStateInventory across every plan of the session and shared by
every producer, the timeline producer and each plan's source-set driver.
The driver loads it once per block entry; SeekState::take_published
peeks a command and pops it only when its generation is at or below the
word, for the PCM consumer and the timeline alike. Every seek push
publishes max(word, g); the session seek publishes once after its last
push. The PCM acquire peeks each block before popping, so a block whose
seek is held back stays queued: nothing acked is dropped.
D1: SourceControlSet::seek_session (generation zero and a generation at
or below the word refused; every producer checked before any push;
timeline first, then sources in ID order). SessionState::session_seek
runs the service step and seeks the newest committed session.
Gate 1b (crates/host-core/tests/session_seek.rs): 10,000 concurrent
plain and anchored session seeks never split across blocks, at Simd8
and Simd4, with zero render allocations; red without the fence.
audit capi pcm_digest unchanged (4f52089e4a8cf83c).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D4: miso_engine_v1_session_seek and miso_engine_v1_session_seek_at share
one body over SessionState::session_seek, with the SourceFailure::report
mapping. FEATURE_SESSION_SEEK = 1 << 10, mask 2047; the frozen symbol
list goes 19 -> 21. D5: the header's session seek paragraph after
"Anchored seek.", the thread table and "Control service" lists, and the
qualification record. D6: audit capi declares a discontinuity, session
seeks to generation 3 at 8 quanta and submits one generation-3 quantum;
pcm_digest 4f52089e4a8cf83c -> bffc21e857b5e134 (call 5 plays that
quantum instead of underrun silence); 0 violations; replacements == 1.
Gates 1-7 in runtime::tests::session_seek. RealtimePlanOwner gains
active_plan_mut (one realtime edit, accepted) so a test reads the active
plan's timeline. Four private intra-doc links became code spans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oot ruling B16-R8)

Its only caller is capi's #1642 test reader. The new engine test-support
feature is enabled by capi's dev-dependency, and the workspace debug-test
and doctest steps name it, so check-test-support-ci.py covers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…adless SDK (#1643)

D1: miso_engine_web_v1_session_seek and _session_seek_at call
SessionState::session_seek only (no prepare_source_seek, no host-web seek
logic); a host with no control half refuses with RESULT_REFUSED_LIFECYCLE.
D2: OfflineEngine.seek(frame, { generation? }) declares the discontinuity
first (B15-R7), then calls WasmBoundary.sessionSeek; the default
generation is one above the largest this engine object has used
(B16-R5). D3 mirrors: EXPORTS 108 -> 110, regenerated SDK mirrors, two
self-test mutations. Gates 1 and 3a in host-web tests; gate 2 eval;
gate 6 parity row (B16-R6): browser and C ABI seek reports of three
sources equal per block for plain and late session seeks at 48 and
96 kHz. host-web test-support also names control-plane/test-support and
engine/test-support for the timeline reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s on the C ABI

The #1293 review's MINOR 5, the C13 one-host difference "source submit
diagnostics": host-web drops the core's diagnostic and both hosts copy
the result-code rule. No issue existed for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R9: a plain session seek checks every producer, declares the
discontinuity through the core's one declaration path, then pushes, in
one SessionState call on both hosts; a refused seek declares nothing,
a refused declaration pushes nothing, and a push that fails after the
check is InternalInvariant. The anchored session seek keeps continuity
and declares nothing. The SDK and the audit no longer declare before a
session seek (B15-R7's order retired in #1323, #1643, #1644). Gates on
the C ABI, the parity row and the SDK eval, each red when its rule is
removed; floors raised on the #1285 path (a new structural frame removes
a limiter).
M2: the unfenced mode is gone. The word is a required argument of every
ring, timeline and set preparation; prepare_graph_source_set and the
sentinel are removed; the only unpublished push is the source crate's
SessionSeek, which owns the zero and stale rules and refuses a producer
of another session; a set or carry across words is refused.
M1: tests where the word is above every live producer (host-core) and
a removed source keeps a session seek stale (capi), red without the
stale rule. Minors: full header refusal list, qualification wording,
#1644 names the web session seek exports, a test comment, the 3a
lifecycle test asserts no seek waits. pcm_digest stays bffc21e857b5e134.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…discontinuity publishes

Root ruling B16-R10: the browser refuses a plain session seek or a
standalone declaration with backpressure on a full table queue even when
nothing would publish; the C ABI returns OK. Covers both calls, with a
parity row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A capi gate for B16-R9's "a refused declaration pushes nothing": a
plain session seek whose declaration is refused returns the refusal,
publishes nothing, leaves the generation free and is accepted after the
fault clears (red when the declaration's error is ignored). Header line
rewrapped; a host-web test renamed to match its doc; the #1642 record
points the tranche-1 text to the part A fold and states why
PcmSourceRing::prepare(config) stays ungated (no host caller, B16-R10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-web's forwards

B16-R8 added engine/test-support to the debug-test step's features and
#1643 made host-web's test-support forward control-plane and engine, so
the self-test's pinned feature string, its every-feature-removed package
set and its host-web manifest anchor were stale. The doctest-parity
mutation now drops protocol/test-support, which nothing forwards
(control-plane's is forwarded through host-web since #1643).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's no-feature release check (CARGO_PROFILE_RELEASE_PANIC=unwind cargo
check --release --workspace --all-targets) failed with E0425: host-web's
mod session_seek calls native_staging::timeline_at,
session_seek_slots_empty and active_plan_epoch, which exist only under
test-support. The module and the parity row's helpers (CAbi::session_seek,
session_seek_code, session_block, same_session_reports) now carry the
same cfg as their callers and callees. Two older helpers from #1677
(observe_key, armed_mask), whose callers are all test-support modules,
are gated too, so the no-feature check prints no warning. Both the
release and debug no-feature checks are clean; host-web tests with
test-support pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tamashi095
tamashi095 merged commit 31fa33f into main Oct 16, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Seek the timeline and every source from the browser module export and the headless SDK Seek the timeline and every source in one C ABI call

1 participant