Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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 @@ -264,3 +264,21 @@ Done under B15-R8: #1386 and #1643 each carry an amendment of 2026-10-15.
**Gates and commands.** Every named script and file exists. `build-web-audioworklet.sh
--named-twin`, `check-sdk-generated.sh [DIR]`, the parameter-metadata `--check`
(`tools/parameter-metadata/src/lib.rs:1143`) and `check-abi-layout-v1.py --self-test` are valid.

## Amendment (root ruling, batch 15c, 2026-10-16): D6 owns the browser seek report

- #1316 (batch 15c) shipped the core seek report and its C ABI export,
`miso_engine_v1_source_seek_report` (struct `miso_engine_v1_seek_report`, 64 bytes; the core
reads it through `SessionState::seek_report`, which runs the service step first). The browser
export is this issue's D6, in batch 16. It does not wait for #1332 or #1387, because #1332 waits
on the owner's iOS test and a parity gap there could wait indefinitely. In `single` mode D6
ships on its own (B15-R8).
- D6 reads the same core call as the C ABI (thick core, thin clients): no seek-report logic in
host-web. Its record carries the same fields as the C struct, in the same order and with the
same meanings (generation in effect, source-read sample and source frame of its first block,
cumulative underrun and held frames). A busy read is `RESULT_BACKPRESSURE` and an unknown source
`RESULT_INVALID_ARGUMENT`, as the C ABI's `source.report.busy` and `source.id.unknown`.
- **New gate (parity row).** In `hosts/host-web/tests/control_parity.rs`: a plain seek and a late
`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.
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,82 @@ Re-pin from the measurement, with the reason.
**Realtime-path edits.** D2 edits `crates/engine/src/realtime/watermark.rs` and `mod.rs` and adds
the new `seqlock.rs` (B15-R1). D3 adds a render-side publication in the source consumer. That slice gets
one attempt.

## Decision record: #1316 (batch 15c)

**Shipped.** D1-D8 and B15-R1/R2 on `460fca68b`.

- `crates/engine/src/realtime/seqlock.rs` (new, B15-R1): `PublishedWords<const N: usize>`, a
non-`Clone` `PublishedWordsWriter::publish(&mut self, &[u64; N])` (marked realtime region,
wait-free) and a `Clone` `PublishedWordsReader::read` bounded at `MAXIMUM_READ_ATTEMPTS` (64),
then `PublishedWordsBusy`. No `unsafe`. `watermark.rs` now stores its six words through it; its
record size is unchanged (72 bytes). #1314's torn-read test moved to `seqlock.rs` with the
protocol and the watermark copy was deleted.
- `crates/source`: `begin_block_at(source_read_sample)` and `SeekClock::At { source_read_sample }`
with D1's text; `SourceCommand::SeekAt` and `HeldSeek` anchors documented on the source-read
clock. `SourceSeekReport` (five words) and `SourceSeekReportBusy`; one record per ring made in
`prepare_at_source_frame`, writer in the consumer, reader in the producer
(`PcmSourceProducer::seek_report`, `HostChunkProvider::seek_report`). D3's publication is
`publish_seek_report` at the end of `begin_block_play`, inside the existing marked region.
`SourceResourceReport::seek_report_bytes` is in overhead, total and the largest-allocation list.
- host-core: `SourceControlSet::seek_report`, the `seek_at` doc (D7), and a new
`SourceControlError::ReportBusy` (`source.report.busy`, backpressure).
- control-plane: `SessionState::seek_report` runs `service()` first (amendment), reads the newest
providers; `pub use source::SourceSeekReport`.
- capi: `miso_engine_v1_source_seek_report`, `SOURCE_SEEK_REPORT_SIZE` 64,
`FEATURE_SOURCE_SEEK_REPORT` = `1 << 9` (mask 1023), header thread table, service
paragraph and a two-sentence note, both smoke programs, the frozen list (19 symbols) and
`docs/C_ABI_V1_QUALIFICATION.md`.

**Departures.**

- The C struct is `miso_engine_v1_seek_report`, not D6's `miso_engine_v1_source_seek_report`: C
and C++ give a typedef and a function one name space, so D6's two names do not compile
(`redeclared as different kind of entity`). The size macro and feature bit keep D6's names.
- D3 read: a block that changes only a counter rewrites the two counters and keeps the
generation's `first_sample` and `source_frame`, so `(g, s, f)` always names the generation's
first block (the host workflow of gate 3).
- A clockless `begin_block` (tests and `read_block` only) continues its own clock: the block after
a block at `t` is at `t + quantum`, from 0.
- No re-export in `crates/capi/src/runtime/mod.rs`: the export reads the report's fields without
naming the type.
- Mechanical edits outside Authorized paths: `crates/host-core/tests/source_diagnostics.rs` gains
the `ReportBusy` row (its exhaustive table does not compile without it), and
`tools/realtime-policy/src/policy.rs` raises the floors to the tree's counts (33 files, 151
regions), as its comment requires.

**Gates.**

1. `capi` `runtime::tests::a_plain_seek_is_reported_at_the_block_that_applied_it`: `(2, 768,
4096, 0, 0)` after block 6, and `(1, 0, 0, 0, 0)` between the seek call and block 6.
2. `a_late_anchored_seek_reports_its_frame_plus_the_lateness`: `(2, 1280, 768, 0, 0)`, and block
10 is bit-identical to block 6 (frames 768 on).
3. and 4. `a_scrubbed_stem_and_a_stem_started_from_its_report_stay_aligned`: Y anchored at
`f + (A - s)`; blocks 10-15 bit-identical to a fresh session of the committed document fed both
stems from the aligned frames; X unchanged across the carrying swap and its underrun counter
moves after it; Y `(0, ..)` before the swap block, `(1, 896, 0, 0, held)` while held, then
`(2, 1280, 4608, 0, 384)`. Source-level: `a_carried_consumer_continues_the_predecessor_audio_gap_free`
now also checks the report across the swap.
5. `source` `the_seek_report_carries_the_cumulative_held_and_underrun_frames`; also
`the_seek_report_names_the_block_and_frame_each_generation_started_at` and
`a_clockless_consumer_reports_consecutive_quanta`.
6. `report_separates_session_pcm_from_source_overhead` adds the row. `./target/release/audit capi`:
0 allocations, 0 deallocations, 0 syscalls, `"total_violations":0`, `pcm_digest`
`4f52089e4a8cf83c` (unchanged).
7. `ffi::tests::seek_report_refusals_leave_out_untouched_and_name_their_rule`; `cargo test -p
source`, `-p engine --features engine/realtime-audit`, `-p host-core --features
host-core/test-support`, `-p capi`; `check-capi-abi.sh` and `--self-test`; `realtime-policy`;
`check-cross-targets.sh`; `check-workspace-policy.sh`; clippy of the touched crates; fmt; doc.

**Measured.** `seek_report_bytes` is 64 per ring on 64-bit targets, 56 on wasm32 (`Arc`'s two
counters plus six `u64` words). host-web's `retained_ceilings` stays inside its budgets.

**Successors.** The browser export is #1332 and #1293 (Non-goals). `observe.rs` onto
`seqlock.rs` is #1482 D3. `source.id.invalid` on this entry point is #1350.

**Successor owner (root ruling, 2026-10-16).** The browser export of the seek report is #1293 D6
(batch 16) alone, with a parity row against the C ABI struct, independent of #1332 (which waits
on the owner's iOS test). Until #1293 lands, the browser cannot seek through the core, so it has
no seek to report; the C ABI reads the report today. #1293's amendment of 2026-10-16 records it.

Bit move: bit 9, because #1323 took bit 8 first; merge of 15b into 15c.
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,64 @@ the lines moved.
exact-mask tests.

**Realtime-path edits.** None. This slice is header text, one doc and one C test.

## Decision record: #1317 (batch 15c)

**Shipped.** D1-D4 and B15-R3, comment text and one C test; no declaration, symbol or Rust change.

- Header (`crates/capi/include/miso_engine_v1.h`): "Starting an added stem in time" and the two-line
seek-report note are replaced by ten paragraphs in D1's order: the source-read clock, seek,
anchored seek, choosing the anchor, held, replacement, one seek in flight, seeks across a
structural transaction, far-past anchors, rate, and where a seek landed. A new comment block
before `MISO_ENGINE_V1_ABI_VERSION` states the growth rule (D2) with B15-R3's two sentences. The
cross reference in the live-edit paragraph now points at the new paragraphs, and the #1310
paragraph is rewrapped to 100 columns (one line was 135 wide); "placeholder #1699" became "issue
1699 is the successor issue for one".
- `docs/C_ABI_V1_QUALIFICATION.md` (D3): the `seek_at` paragraph no longer implies the bit protects
a directly linked host and names the source-read clock; a paragraph records the seek contract and
one "ABI growth" paragraph states D2 once.
- `crates/capi/tests/c/abi_smoke.c` (D4): `reports_every_defined_feature` requires one `&` per
`MISO_ENGINE_V1_FEATURE_*` symbol (ten); the `!= MISO_ENGINE_V1_FEATURE_MASK` comparison is gone.
The compile-time pins are kept.

**Statements checked against the code** (file, function): clock origin 0 and `render.time.discontinuity`
(`plan.rs` `render_contiguous`, `plan_exchange.rs` `adopt_absolute_sample`, `ffi.rs` render test);
quantum-multiple anchor and the check order (`source/src/lib.rs` `try_seek`); producer switches to
`g` at the call and refuses other generations (`validate_submission_metadata`); held, replacement,
late apply and region end (`observe_seek_at_block_boundary`, `begin_block_play`, `settle_block`,
tests `a_late_anchored_seek_past_the_region_end_is_the_end_of_region`); one command slot
(`command_capacity = 1`, `source.seek.backpressure` in `host-core/src/source.rs`); consumer carry
including `held_seek` (`adopt_sources`); report words and 64-attempt bound (`publish_seek_report`,
`seqlock.rs`); source rate equals the session rate (`prepare.rs` pushes `compiled.sample_rate()`).

**Departures.**

- The text says a seek takes effect "no later than the first block that begins after the call
returns", not "at the next block render begins": a producer that pushes a seek and its PCM
between a block's command pop and its data pop is observed by the acquire's re-observe in that
same block (#1274 MINOR-3).
- Far-past text says the seek applies in the block that pops it (not "the next block after it is
popped"), as `observe_seek_at_block_boundary` pops and applies in one block.
- The end-of-region sentence is conditional on a region the host closed with `end_of_region`; with
no end known the block is an underrun.
- Feature mask is 1023 (bit 8 landed with #1323, bit 9 with #1316), not the amendment's 255.
- #1320's prime has no host-visible API, so the header says nothing about it. #1396 has not landed,
so "equal to the render clock in this version" stands and #1396 is not named in the header.

**Gates.**

1. `bash scripts/check-capi-abi.sh`: ok (C11 and C++17 at `-Wall -Wextra -Werror -pedantic`, symbol
set, smoke run, shared and static); `--self-test`: ok.
2. Smoke is a bit test, by a local patch of `FEATURE_MASK` in `crates/capi/src/abi.rs`, reverted
after each run: clearing `FEATURE_SERVICE` makes `check-capi-abi.sh` exit 1; OR-ing in unknown bit
`1 << 9` passes.
3. `cargo test --locked -p capi`: 158 lib tests and every integration test pass (the Rust exact-mask
tests are unchanged).
4. Review gate: every D1 statement is checked above against its code anchor.
5. `bash scripts/check-workspace-policy.sh`, `cargo fmt --all -- --check`,
`RUSTDOCFLAGS='-D warnings' cargo doc --locked --workspace --no-deps`: pass.

**Successor work.** #1355 must amend the "equal to the render clock in this version" sentence (the
header's first seek paragraph, the qualification record and `SourceControlSet::seek_at`'s doc) when
a warm successor makes the source-read offset nonzero; the amendment asks for this to be recorded in
#1355, whose spec does not yet carry it. #1699 owns the counter the header says does not exist yet.
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,60 @@ is not a control.
the timeline consumer is for #1355 or #1641 to decide.

**Dependencies.** #1316, #1318 and #1319 are open and in the batch. Nothing outside the batch.

## Decision record: #1320 (batch 15c)

**What shipped.**
- D1: `Consumer::peek(&self, i) -> Option<&T>` in `crates/engine/src/realtime/spsc.rs`, inside the
consumer's existing realtime-policy region. One `Acquire` load of the producer cursor (through
`available_at_entry`), no cursor, cache or counter change. A shared-access `sync::with` helper
(loom: `UnsafeCell::with`) sits beside `with_mut` in both `sync` shims; it is peek's read path.
- D2: `PcmSourceConsumer::prime_ready(&self, first_sample, lead_blocks)`. One command-queue load
and at most `lead_blocks + 1` peeks; the walk takes `current` first, then the queue; it returns
`true` as soon as the region's end is reached, because later blocks move no frame.
- D3: `PcmSourceConsumer::prime_block_at(&mut self, sample)`: `end_block`,
`flush_deferred_recycle`, `generation_changed = false`, then `begin_block_play(SeekClock::Hold)`.
Debug assertions check no underrun, no held frames and no command pop.
- D4: `prime_required` is one `Arc<AtomicBool>` per ring, made at preparation.
`PcmSourceProducer::prime_flag()` and `HostChunkProvider::prime_flag()` return a
`PcmSourcePrimeFlag` with `set(bool)` (`Relaxed`). `PcmSourceConsumer::prime_required()` loads it
(`Relaxed`). New resource row `SourceResourceReport::prime_required_bytes`. It is in
`overhead_bytes` and in the largest-allocation list.

**Departures.**
- The prime's acquire stops before a block of an unobserved generation
(`CommandObservation::Withheld`, one `peek(0)` per pop in a prime only). D3 says the acquire
never pops past block `j`. That is not true at the region's end: there `begin_block_play` still
runs the acquire, and the acquire pops everything queued. A `Seek` pushed after a true
readiness, with its PCM behind the end block, would then make the prime observe it at once
(`:1408-1431`), and a plain seek would apply inside the window. The guard leaves that block
and its command queued. Ordinary blocks and `prepare_seek` pass `Allowed` and do not change.
Gate 4's end-at-`j = 1` case is red without the guard.
- The prime sets `next_source_read_sample = sample` before `begin_block_play(Hold)`, because
`Hold` reports on the consumer's own clock. Without this, a prime after a source-read clock
jump (#1639) would leave a later clockless block, and its seek report, on the old clock.
`the_prime_continues_the_callers_clock_across_a_jump` defends this.
- No new marked region. `peek`, `prime_ready` and `prime_block_at` are in existing regions, so
the realtime-policy floors do not change (151 regions in 33 files).
- No browser or C ABI surface: #1355 owns the callers. The browser's `source_overhead_bytes`
grows by the flag's bytes per ring. The host-web ceilings test still passes, so no ceiling moves.

**Gates.**
1. `realtime::spsc::tests::peek_returns_each_queued_item_in_order_across_wraps_and_changes_nothing`
and the loom test `realtime::spsc::loom_tests::spsc_loom_peek_returns_the_item_later_popped`
(`spsc_loom` CI command: 15 passed).
2. `crates/source/src/lib.rs` `tests::prime_readiness::*` (11 tests). Each readiness call
asserts that telemetry, `next_frame`, `end_frame`, the data, recycle and command queue counts,
the pop counts, the held seek, `current`, the clock and the seek report did not change. Held
anchors inside or late in the window are set directly, because `try_seek` refuses unaligned
anchors. Gaps and stale blocks are pushed raw, because the host path refuses them.
3. `crates/source/tests/prime.rs::the_prime_equals_the_blocks_the_consumer_would_have_played`
(steady, region end at `j = 1`, a seek 2 blocks earlier).
4. `the_prime_observes_no_command` (steady and end at `j = 1`, with the seek's PCM queued).
5. `the_prime_flag_is_shared_between_the_handle_and_the_consumer`.
6. `readiness_and_sixteen_primes_allocate_nothing`: `k = 16`, 16 primes, 0 allocations and
0 frees (current-thread counters).

**Measured.** `prime_required_bytes` = 24 B per ring on 64-bit targets and 12 B on wasm32
(three words). `audit capi` reports allocations 0 and pcm_digest `4f52089e4a8cf83c`, which is
unchanged.
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,10 @@ widths.
- *Carry meter and effect observation state across a plan swap* (#1327).
- *Carry spectrum capture state across a plan swap* (#1395).
- *Reset latency floors at a host-declared discontinuity* (#1323), D2: the rule D6 cites.

**Note (controls coordinator, batch 15c, 2026-10-16).** #1317 documents the seek contract in the
C header with "equal to the render clock in this version" for the source-read clock. When a warm
successor makes the source-read offset nonzero, this slice amends that sentence in the header's
first seek paragraph, in `docs/C_ABI_V1_QUALIFICATION.md` and in the `SourceControlSet::seek_at`
doc. It also calls #1320's `prime_ready` with the source-read sample (S + O), not the render
sample, and wires the prime on both hosts.
Loading
Loading