From 079f096def643900489ee0db2bedd1969bb23ee1 Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 18 Oct 2026 23:25:59 +0000 Subject: [PATCH 1/5] Make the gate bypass test act on its input (test audit gap 1) The old input left the prepared gate open with the full hold, so the output was dry whatever the bypass word said. The rewrite closes the gate on a quiet plateau, asserts the unbypassed render is not dry, and asserts the bypassed render is bit-exact dry at every launch rate. Co-Authored-By: Claude Opus 5.5 --- crates/gate-expander/tests/contract.rs | 57 ++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/crates/gate-expander/tests/contract.rs b/crates/gate-expander/tests/contract.rs index 4b7873919..b6c70f91e 100644 --- a/crates/gate-expander/tests/contract.rs +++ b/crates/gate-expander/tests/contract.rs @@ -217,19 +217,62 @@ fn missing_and_misordered_initial_records_return_exact_errors() { assert_eq!(error.code, "effect.parameter.initial"); } +/// Bypass is exact dry identity on an input that the gate acts on. +/// +/// At the active values (`T = -20`, `rho = 20`, range 48 dB, hold 0 ms) a -60 dBFS plateau that +/// lasts 4,800 frames closes the gate onto the range floor, so the unbypassed output is not dry. +/// The same input, bypassed, must come back bit-exact dry (signed zero included) at every launch +/// rate. The unbypassed render is asserted not dry, so the bypass check cannot pass vacuously: +/// at default values the prepared lane is open with the full hold and every sample is dry +/// whatever the bypass word says. +/// +/// Red mutations: the kernel ignores the bypass word (`kernel.rs`, the identity mask), or the +/// prepared gate drops the bypass flag (`lib.rs`, `bypass: L::splat(0.0)`). #[test] fn bypass_is_exact_identity_at_all_rates() { + const FRAMES: usize = 4_800; + const QUIET: f32 = 1.0 / 1_024.0; + let mut left_input: Vec = (0..FRAMES) + .map(|frame| if (frame / 8) % 2 == 0 { QUIET } else { -QUIET }) + .collect(); + left_input[0] = f32::from_bits(0x8000_0000); + let right_input: Vec = left_input.iter().map(|value| -value).collect(); + let bits = |samples: &[f32]| { + samples + .iter() + .map(|value| value.to_bits()) + .collect::>() + }; for rate in [44_100, 48_000, 88_200, 96_000] { - let values = initial_values(); + let values = support::active_values(); + + let mut effect = prepare(request_at_rate(&values, rate)); + let mut left = left_input.clone(); + let mut right = right_input.clone(); + render_scalar(&mut effect, &mut left, &mut right, 128); + assert_ne!( + bits(&left), + bits(&left_input), + "unbypassed left must act at {rate} Hz" + ); + assert_ne!( + bits(&right), + bits(&right_input), + "unbypassed right must act at {rate} Hz" + ); + let mut request = request_at_rate(&values, rate); request.bypass = true; let mut effect = GateExpanderFactory.prepare(request).expect("prepare"); - let mut left = [f32::from_bits(0x8000_0000), 0.25, -0.5]; - let mut right = [-0.0, -0.25, 0.5]; - let expected = (left, right); - render_scalar(&mut effect, &mut left, &mut right, 3); - assert_eq!(left, expected.0); - assert_eq!(right, expected.1); + let mut left = left_input.clone(); + let mut right = right_input.clone(); + render_scalar(&mut effect, &mut left, &mut right, 128); + assert_eq!(bits(&left), bits(&left_input), "bypassed left at {rate} Hz"); + assert_eq!( + bits(&right), + bits(&right_input), + "bypassed right at {rate} Hz" + ); assert_eq!(effect.metadata.latency, LatencySamples(0)); } } From bc9b6b73beeb93c6edb26c720de585bfe09df593 Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 18 Oct 2026 23:29:15 +0000 Subject: [PATCH 2/5] Check the browser plain session seek's successor bookkeeping (test audit gap 2) A plain session seek declares the discontinuity, so the host must keep, count and name the successor it publishes, and admit a submission on it before the swap. Audio alone cannot show this; the new test asserts it from #1285's floors, swapped and still waiting. Co-Authored-By: Claude Opus 5.5 --- hosts/host-web/src/tests.rs | 131 ++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/hosts/host-web/src/tests.rs b/hosts/host-web/src/tests.rs index 4f235b294..f721fbf1a 100644 --- a/hosts/host-web/src/tests.rs +++ b/hosts/host-web/src/tests.rs @@ -7602,6 +7602,137 @@ mod structural_apply { assert_eq!(rig.host.service(None), RESULT_OK); } + /// Root ruling B16-R9's bookkeeping on the browser: a plain session seek declares the + /// discontinuity in the same call, so the host keeps and counts the successor it publishes as + /// it keeps a declaration's. From #1285's floors (a muted track behind a true-peak limiter + /// removed), swapped or still waiting for its block, the plain seek publishes one successor; + /// the host holds that plan's tables for its adoption block, counts one blocking rebuild, + /// names the successor's meter header from the rendering plan, and admits a command + /// submission against it before the swap. The next block adopts it and the host stays ready. + /// Mirrors the C ABI's `an_accepted_plain_session_seek_publishes_one_successor_and_applies`. + /// + /// Test value: red if the browser's plain seek reaches the control plane without the host's + /// plan bookkeeping (`run_control`): the audio and the latency are the same, but the + /// successor has no tables, is not counted and is not named, and the first submission before + /// its swap fails the host (`web.internal.pending_shape`). + #[test] + fn a_plain_session_seek_keeps_and_counts_its_successor_on_the_browser() { + for swap in [true, false] { + let mut model = gate_model(); + let limited = &mut model.tracks[0]; + limited.fader.left_mute = true; + limited.fader.right_mute = true; + limited.inserts.effects.push(session::Effect { + id: StableId::parse("lim").expect("insert ID"), + identity: session::EffectIdentity::Native { + effect_id: StableId::parse("miso.true-peak-limiter").expect("limiter id"), + }, + quality: session::EffectQuality::Normal, + bypass: false, + link_mode: session::LinkMode::Maximum, + params: Vec::new(), + sidechain: session::SidechainDeclaration::None, + }); + let edits = [ + SessionEdit::RemoveRoute { + route_id: model.routes[0].id.clone(), + }, + SessionEdit::RemoveTrack { + track_id: model.tracks[0].id.clone(), + }, + ]; + let mut rig = Rig::boot(&model, options(), None); + for _ in 0..APPLY_BEFORE { + rig.step(); + } + assert!(matches!( + rig.apply(&edits, 1), + Outcome::Applied { + path: EditPath::Rebuild, + .. + } + )); + if swap { + for _ in 0..4 { + rig.step(); + } + assert!(!rig.pending(), "swap {swap}: the removal was adopted"); + } else { + assert!( + rig.pending(), + "swap {swap}: the removal waits for its block" + ); + } + let counted = rig.host.blocking_rebuilds(); + let newest = |rig: &Rig| { + rig.host + .ready + .as_ref() + .expect("ready") + .control + .newest_plan_epoch() + }; + let before = newest(&rig); + + const FRAME: u64 = 1_280; + assert_eq!(rig.host.session_seek(None, 2, FRAME, None), RESULT_OK); + let successor = newest(&rig); + assert_eq!(successor, before + 1, "swap {swap}: one successor"); + assert_eq!( + rig.host.blocking_rebuilds(), + counted + 1, + "swap {swap}: the successor is counted" + ); + { + let ready = rig.host.ready.as_ref().expect("ready"); + let waiting: Vec<_> = ready + .pending_shapes + .iter() + .filter(|entry| !entry.adopted) + .collect(); + assert_eq!( + waiting.len(), + 1, + "swap {swap}: the successor's tables, and no superseded candidate's" + ); + assert_eq!(waiting[0].epoch, successor, "swap {swap}"); + assert_eq!( + rig.host.meter_successor_address(), + core::ptr::from_ref(&waiting[0].shape.meter_header).addr(), + "swap {swap}: the rendering plan names the successor's header" + ); + } + assert_eq!( + rig.host.submit_commands(0), + RESULT_OK, + "swap {swap}: a submission before the swap is admitted on the successor" + ); + assert_eq!(rig.host.status().state, STATE_READY, "swap {swap}"); + + let quantum = u64::from(QUANTUM); + let lane = |lane: u64| { + (FRAME..FRAME + quantum) + .map(|frame| strip_sample(0, lane, frame)) + .collect::>() + }; + let planes = [lane(0), lane(1)]; + let views: [&[f32]; 2] = [&planes[0], &planes[1]]; + assert_eq!( + rig.host + .submit_source(SOURCE, 2, FRAME, 48_000, &views, QUANTUM, false), + RESULT_OK + ); + assert_eq!(rig.host.render_next(), RESULT_OK, "swap {swap}"); + assert_eq!( + rig.epoch(), + successor, + "swap {swap}: the next block adopts it" + ); + assert_eq!(rig.host.status().state, STATE_READY, "swap {swap}"); + assert_eq!(rig.host.service(None), RESULT_OK); + } + } + /// #1310 on the browser path, which runs the same control plane: a structural edit while a /// candidate waits unadopted commits and supersedes it. The first candidate never renders; the /// replacement carries both transactions and is adopted at the next block. From bf27558fc13f446418a2b38eacda0f16e45a77f6 Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 18 Oct 2026 23:31:22 +0000 Subject: [PATCH 3/5] Draft TBD-TA2: close the gate bypass and browser plain-seek test gaps Owner-approved, relayed by root on 2026-10-18, from the test audit. Records the red/green evidence for both rewritten and new tests. Co-Authored-By: Claude Opus 5.5 --- ...bypass-and-browser-plain-seek-test-gaps.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md diff --git a/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md b/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md new file mode 100644 index 000000000..b0f432bb2 --- /dev/null +++ b/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md @@ -0,0 +1,93 @@ +# Close the gate bypass and browser plain-seek test gaps + +Owner-approved, relayed by root on 2026-10-18, from the test audit (mutation pilot, stage 1). +Filed by agent G. Test-only: no product code changes. Code anchors at `81a3074fc`. + +## Product outcome + +Two tests that the mutation pilot found weak now assert what their names claim: + +1. The gate/expander bypass test renders an input the gate acts on, so a bypass that does not + reach the kernel turns it red. +2. A host-web test checks the host's bookkeeping of the successor a plain session seek publishes + (root ruling B16-R9), which audio alone cannot show. + +## Context + +- Gap 1 (pilot rows s1o-m1, s1o-m2). `contract::bypass_is_exact_identity_at_all_rates` + (`crates/gate-expander/tests/contract.rs`) rendered 3 samples at default values. Preparation + seeds every lane open with the full hold (100 ms at defaults), so the gain stays 0 dB and the + output is dry through the `gain_db == 0` branch whatever the bypass word says. Mutants: the + kernel ignores the bypass word (`kernel.rs:255`, s1o-m1) and the prepared gate drops the bypass + flag (`lib.rs:473`, s1o-m2) left it green. s1o-m3 (bypass mutes) was already red. +- Gap 2 (pilot row s1e-m2). `hosts/host-web/src/lib.rs:1721`: the plain form of + `ReadyHost::session_seek` runs through `run_control`. A mutant that sends it straight to the + control plane leaves the successor with no `PendingShape`, does not count it in + `blocking_rebuilds`, skips the table-room check and does not name the successor's meter header. + The first command submission before the swap then fails the host + (`web.internal.pending_shape`). Audio and latency are unchanged, so no host-web test saw it + (`control_parity::a_plain_session_seek_declares_and_an_anchored_one_does_not_alike_on_both_hosts` + checks bits and latency only). + +## Decisions + +- D1. Gap 1 rewrites the old test in place (it supersedes it): the active values (`T = -20`, + ratio 20, range 48 dB, hold 0 ms) over a 4,800-frame -60 dBFS plateau with a signed zero at + frame 0, both channels. It asserts the unbypassed render is not dry (no vacuous pass) and the + bypassed render is bit-exact dry, at 44.1, 48, 88.2 and 96 kHz. +- D2. Gap 2 adds `tests::structural_apply::a_plain_session_seek_keeps_and_counts_its_successor_on_the_browser` + beside `a_declared_discontinuity_resets_the_floors_on_the_browser`, from the same #1285 floors. + It mirrors the C ABI's `runtime::session_seek_declares::an_accepted_plain_session_seek_publishes_one_successor_and_applies` + (`crates/capi/src/runtime/tests.rs`): both the swapped and the still-waiting removal. + Assertions: one successor epoch; `blocking_rebuilds` up by one; exactly one unadopted + `PendingShape`, at the successor's epoch (no superseded candidate's left); the rendering plan + names that entry's meter header; `submit_commands(0)` is `RESULT_OK` and the host stays ready; + the next block adopts the successor. +- D3. The table-room backpressure branch is not tested. `PENDING_SHAPES` is 4, `run_control` + reclaims adopted entries first and drops superseded ones, and the core holds at most one + waiting candidate, so no public call sequence fills the table before a plain seek. The branch + is defensive; no test is added for it. +- D4. host-core has no host-side table bookkeeping to mirror. The C ABI has the equivalent + publication test (D2); it keeps no tables, so it needs nothing more. + +## Authorized paths + +- `crates/gate-expander/tests/contract.rs` +- `hosts/host-web/src/tests.rs` +- this spec + +## Objective gates + +- `cargo fmt --all --check` +- `cargo clippy --locked -p gate-expander --all-targets --all-features -- -D warnings`, and the same + for `-p host-web` +- `cargo test --locked --all-features -p gate-expander`, and the same for `-p host-web` +- `scripts/check-workspace-policy.sh` + +## Evidence (agent G, 2026-10-18) + +Mutants are the pilot's exact old/new texts, applied one at a time with a revert-on-exit driver; +the tree was clean after each. + +| Test | Branch | s1o-m1 | s1o-m2 | s1o-m3 | s1e-m2 | +|---|---|---|---|---|---| +| gate-expander `contract::bypass_is_exact_identity_at_all_rates` | green | red | red | red | - | +| host-web `tests::structural_apply::a_plain_session_seek_keeps_and_counts_its_successor_on_the_browser` | green | - | - | - | red | + +- Gap 1: all three mutants fail at the bypassed-left bit comparison. +- Gap 2: the mutant fails first at the `blocking_rebuilds` assertion. With that assertion and the + table checks removed in a scratch probe, the mutant fails at the `submit_commands(0)` + assertion, so the submission check is red on its own. + +## Test value + +- Gap 1: red if bypass does not reach the gate kernel (the kernel ignores the bypass word, or + preparation drops the flag) while the gate is acting, which no other gate-expander test caught + in the pilot (only rack's `bypass_shunt_identity` tests caught the dropped flag). +- Gap 2: red if the browser's plain session seek publishes its successor without the host's plan + bookkeeping, so the successor is uncounted, unnamed and has no tables and the next submission + before its swap fails the host, which no host-web test catches because the audio is unchanged. + +## Dependencies + +None. From 9145731fab8b5981255c4452b3396d4e5dc37234 Mon Sep 17 00:00:00 2001 From: BL Date: Sun, 18 Oct 2026 23:38:48 +0000 Subject: [PATCH 4/5] Fold the TA2 review nits: -0.0 on both channels, parity sentence The right channel's sample zero is -0.0 again, as in the old test. The spec says control_parity also compares result verdicts. Co-Authored-By: Claude Opus 5.5 --- ...2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md | 2 +- crates/gate-expander/tests/contract.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md b/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md index b0f432bb2..362e9212c 100644 --- a/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md +++ b/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md @@ -27,7 +27,7 @@ Two tests that the mutation pilot found weak now assert what their names claim: The first command submission before the swap then fails the host (`web.internal.pending_shape`). Audio and latency are unchanged, so no host-web test saw it (`control_parity::a_plain_session_seek_declares_and_an_anchored_one_does_not_alike_on_both_hosts` - checks bits and latency only). + checks result verdicts, bits and latency, not the host's table bookkeeping). ## Decisions diff --git a/crates/gate-expander/tests/contract.rs b/crates/gate-expander/tests/contract.rs index b6c70f91e..eb5d17ca8 100644 --- a/crates/gate-expander/tests/contract.rs +++ b/crates/gate-expander/tests/contract.rs @@ -236,7 +236,8 @@ fn bypass_is_exact_identity_at_all_rates() { .map(|frame| if (frame / 8) % 2 == 0 { QUIET } else { -QUIET }) .collect(); left_input[0] = f32::from_bits(0x8000_0000); - let right_input: Vec = left_input.iter().map(|value| -value).collect(); + let mut right_input: Vec = left_input.iter().map(|value| -value).collect(); + right_input[0] = f32::from_bits(0x8000_0000); let bits = |samples: &[f32]| { samples .iter() From b1502e67f7a8737d3488222c25926bc8385e9496 Mon Sep 17 00:00:00 2001 From: BL Date: Mon, 19 Oct 2026 13:27:22 +0000 Subject: [PATCH 5/5] Number the test gap issue: #1724 Co-Authored-By: Claude Opus 5.5 --- ...724-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_SPECS/{TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md => 1724-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md} (100%) diff --git a/.github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md b/.github/ISSUE_SPECS/1724-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md similarity index 100% rename from .github/ISSUE_SPECS/TBD-TA2-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md rename to .github/ISSUE_SPECS/1724-close-the-gate-bypass-and-browser-plain-seek-test-gaps.md