From 0ed63a89253468107b9bb84ef651679a7ec8e2e7 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 01/82] busbar-plane-streaming: the plane's door, the tool-call relay, the one-request doors' answers and the served metadata document (fold S3a, S2c, S3b; 4 carried commits) -- busbar-plane-streaming: the plane's door, compiled in and dropped in (fold S3a) src/door.rs states the plane once, in its Statement tail, and publishes a snapshot per generation. The tail carries the streams section (and providers required), the three dialects, the session grant kind, the six billable classes, the per-session fee, the needs per direction (keyed ws/http and signed ws inbound; the provider's ws and http outbound, targeted by streams.session.model) and the egress target path. The snapshot claims the five doors a session opens through: POST /v1/realtime/client_secrets, POST /v1/realtime/calls, the sideband and Gemini Live sockets, and /twilio/{call_id}. It also binds the audience and metadata URL from one reading of the public base URL. A deployment with no public URL claims nothing. validate/open/refresh judge the settings blob with the streams grammar (unknown keys and a zero ceiling refused). The door serves no unit yet. arrive, on_piece, refusal, serve and project answer REFUSED; cancel answers CANCEL_FAILED, which bills nothing. The door is not linked into any build. examples/streaming_door.rs exports the same door as a cdylib. tests/door.rs loads it through the loader's plane kind, beside the linked door, and pins one transcript for both, plus the tail against the kind's own check_tail. Dependencies: url (already in Cargo.lock) for the one public-URL reading; manifest-allowlist reviewed_extra gains busbar-plane-streaming = ["url"]. Dev-only: busbar-plugin-loader. -- streaming: a tool call no one on the node serves is relayed to the caller; the echo executor leaves production (fold S2c, ARCHITECT ruling (c)) The served runtime ran every tool call through EchoToolExecutor. It serves every name, so the gateway answered each call the model made with an echo of its own arguments, and the caller never saw the call. The production executor is now ClientRelay, which serves no tool. The session pump relays each call it does not serve to the caller, in the caller's dialect (open, argument deltas, close). It plans the call's reply in the node's open-call table when one is bound, and never authors the result. This is the loop the upstream realtime protocols define. A call a test executor serves still runs and is answered as before. No server-side execution is added. Tool calls are counted exactly as before (one per call opened), and no metering call site moves. EchoToolExecutor is now a test double in busbar-voice's testkit, present only in test builds. RED: session_pump_tests::a_call_no_one_on_this_node_serves_is_relayed_to_the_caller_and_never_answered_here fails before this change: the call is executed, no call reaches the caller, and a function_call_output is written upstream. -- busbar-plane-streaming: the one-request doors' driver answers, and the door classifies an arrival (fold S3b) src/driven.rs states the plane's answers to the kernel plane driver for the mint and SDP doors, as plain data: - which door a claim is, its dialect and the session-open op class; - the ATTEMPT request bound for the far end: verb and target explicit, then fields and body. The mint carries the locked session params, the clamped lifetime and the caller's safety identifier. The SDP offer is relayed as it arrived. The plane never writes the credential. - what the caller is answered from the far end's answer. The mint answers the secret and expiry, or the served plane's 502 text byte for byte (the status printed with its canonical reason phrase, as the served path printed it). The SDP relays status, application/sdp and Location, and names the rtc_ call id for the session record. The door's arrive now classifies a claimed arrival: its door's dialect, the one op class the tail now declares (streaming.session.open), and a required principal. An unpublished claim is refused. The unit's pieces are still declined, so nothing is served or charged through the door. -- streaming: the protected-resource metadata a refused caller is pointed at is served (ARCHITECT ruling on the metadata observation) The served plane's challenge pointed at /.well-known/oauth-protected-resource/v1/realtime, but nothing served that document. busbar-voice now mounts it the way mcp does: GET, RouteAuth::None, the RFC 9728 document built by the kernel's one metadata renderer, with the plane's audience as `resource`. The plane is configured with no authorization server, so the document names none. The door claims the same route (CLAIM_OPEN | CLAIM_EXACT). Its arrive answers PRINCIPAL_NONE for it, and driven::metadata_reply is the same document as data. RED: mount_tests::the_resource_metadata_the_challenge_points_at_is_served follows the admission's pointer to a served, credential-free route that names the audience. It fails before this change because no route serves the pointer's path. (cherry picked from commit b054f731304cda1e2ff22dd584f4f70de37fa1da) --- crates/busbar-plane-streaming/Cargo.toml | 12 + .../examples/streaming_door.rs | 7 + .../src/codec/ir/codec/gemini/tests.rs | 54 +- .../src/codec/ir/codec/mod.rs | 2 +- .../src/codec/ir/codec/tests.rs | 56 +- .../src/codec/ir/usage.rs | 58 +- crates/busbar-plane-streaming/src/door.rs | 570 ++++++++++++++++++ crates/busbar-plane-streaming/src/driven.rs | 319 ++++++++++ crates/busbar-plane-streaming/src/lib.rs | 2 + .../src/session_params.rs | 37 +- .../src/session_pump.rs | 33 +- .../src/tests/broker_tests.rs | 10 +- .../src/tests/driven_tests.rs | 108 ++++ .../src/tests/session_params_tests.rs | 39 ++ .../src/tests/session_pump_tests.rs | 70 +-- crates/busbar-plane-streaming/src/tools.rs | 18 +- crates/busbar-plane-streaming/tests/door.rs | 327 ++++++++++ crates/busbar-voice/src/mount.rs | 62 +- crates/busbar-voice/src/runtime/mod.rs | 24 +- crates/busbar-voice/src/runtime/session.rs | 9 +- crates/busbar-voice/src/runtime/tests.rs | 40 ++ crates/busbar-voice/src/testkit/echo.rs | 21 + crates/busbar-voice/src/testkit/mod.rs | 3 + crates/busbar-voice/src/tests/mount_tests.rs | 46 ++ .../busbar-voice/src/topology/minter_https.rs | 55 +- crates/busbar-voice/src/topology/telephony.rs | 13 +- qa/construction.toml | 2 + 27 files changed, 1786 insertions(+), 211 deletions(-) create mode 100644 crates/busbar-plane-streaming/examples/streaming_door.rs create mode 100644 crates/busbar-plane-streaming/src/door.rs create mode 100644 crates/busbar-plane-streaming/src/driven.rs create mode 100644 crates/busbar-plane-streaming/src/tests/driven_tests.rs create mode 100644 crates/busbar-plane-streaming/tests/door.rs create mode 100644 crates/busbar-voice/src/testkit/echo.rs diff --git a/crates/busbar-plane-streaming/Cargo.toml b/crates/busbar-plane-streaming/Cargo.toml index f22819d57e..6022f5eddb 100644 --- a/crates/busbar-plane-streaming/Cargo.toml +++ b/crates/busbar-plane-streaming/Cargo.toml @@ -55,6 +55,9 @@ bytes = { workspace = true } # `#[async_trait]` desugaring it always was. A proc-macro that expands to `Box`ed futures and pulls no # I/O into this crate's closure. async-trait = { workspace = true } +# THE DOOR'S ONE READING OF THE PUBLIC BASE URL (`door::absolute`): the audience and the metadata URL +# replace the path of the deployment's public URL and drop its query and fragment, parsed once. +url = { workspace = true } [dev-dependencies] # The TEST-ONLY seal (#65). `busbar_contract::plugin::TestKernelSeal` is the one blessed @@ -64,3 +67,12 @@ busbar-contract = { path = "../busbar-contract", features = ["test-seal"] } # The conformance rig drives the plane end-to-end over real openai-realtime (the voice dialect) wire # bytes, so it builds the same JSON fixtures the underlying codec tests use. serde_json = { workspace = true } +# THE DOOR, BOTH WAYS (`tests/door.rs`): the loader's plane kind loads the linked door and the +# dropped-in image the example below builds, and drives both through one script. Dev-only: absent +# from every release build. +busbar-plugin-loader = { path = "../plugin-loader" } + +# THE DROPPED-IN IMAGE ON THE ONE DOOR: built by `cargo test`. +[[example]] +name = "streaming_door" +crate-type = ["cdylib"] diff --git a/crates/busbar-plane-streaming/examples/streaming_door.rs b/crates/busbar-plane-streaming/examples/streaming_door.rs new file mode 100644 index 0000000000..5a7aebd65f --- /dev/null +++ b/crates/busbar-plane-streaming/examples/streaming_door.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! The streaming plane's door, dropped in: the one exported symbol, forwarding to the same door a +//! compiled-in row holds. `tests/door.rs` loads it beside the linked door and drives both the same way. + +busbar_contract::export_door!(busbar_plane_streaming::door::door); diff --git a/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs b/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs index dea1f9bfc5..2b87e58fd4 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs @@ -1124,24 +1124,22 @@ fn usage_falls_back_to_stated_totals_when_the_modality_breakdown_is_absent() { 50, "the turn's stated response total must not be dropped to zero" ); - // The served meter bills the stated totals across its input and output classes, never zero. - let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); - let sum_of = |names: &[&str]| -> u64 { - billed - .iter() - .filter(|(c, _)| names.contains(&c.as_str())) - .map(|(_, n)| n) - .sum() - }; + let billed = u.to_billing_usage(); assert_eq!( - sum_of(&["audio_tokens_in", "text_tokens_in"]), - 95, - "the input classes bill the stated total, never zero" + billed + .usage_units + .get(busbar_contract::records::UNIT_INPUT) + .copied(), + Some(95), + "a turn with no modality breakdown still bills its stated input tokens" ); assert_eq!( - sum_of(&["audio_tokens_out", "text_tokens_out"]), - 50, - "the output classes bill the stated total, never zero" + billed + .usage_units + .get(busbar_contract::records::UNIT_OUTPUT) + .copied(), + Some(50), + "a turn with no modality breakdown still bills its stated output tokens" ); } @@ -1173,19 +1171,27 @@ fn cached_content_tokens_are_not_billed_twice() { // Extraction stays wire-faithful. assert_eq!(u.audio_in, 1000); assert_eq!(u.cached, 800); - // The served meter bills the input as reported: the cached subset is part of it, billed once - // there, and no class bills it again (cached tokens are attribution only). - let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); - assert!( + let billed = u.to_billing_usage(); + assert_eq!( billed - .iter() - .all(|(c, _)| c.as_str() != crate::meta::CLASS_CACHED_TOKENS.as_str()), - "the cached subset is never a class of its own: {billed:?}" + .usage_units + .get(busbar_contract::records::UNIT_INPUT) + .copied(), + Some(200), + "input bills the UNCACHED remainder of the prompt (1000 - 800)" + ); + assert_eq!( + billed + .usage_units + .get(busbar_contract::records::UNIT_CACHE_READ) + .copied(), + Some(800), + "the cached subset bills once, on the cache-read lane" ); assert_eq!( - billed.iter().map(|(_, n)| n).sum::(), + billed.usage_units.values().sum::(), 1000, - "the billed classes sum to the turn's input, never to 1800" + "the billed lanes sum to the turn's prompt, never to 1800" ); } diff --git a/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs b/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs index e920dac87d..5dc5004eb1 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs @@ -1057,7 +1057,7 @@ impl DuplexWriter for OpenAiRealtimeCodec { /// Re-frame the extracted token classes back onto a `usage` object (the inverse of [`extract_usage`]). /// -/// The sums SATURATE, matching the crate's stated discipline for its counts ("a runaway +/// The sums SATURATE, matching `IrDuplexUsage::to_billing_usage`'s stated discipline ("a runaway /// turn pins the count, never wraps small"): these counts came off an upstream `usage` object, and /// a wrapped total is a small, believable number that is false. fn usage_to_wire(u: &IrDuplexUsage) -> Value { diff --git a/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs b/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs index 9eaaef50da..0425e25a32 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs @@ -622,7 +622,7 @@ fn an_out_of_range_content_index_falls_back_to_the_documented_default() { /// TOKEN SUMS SATURATE, THEY DO NOT WRAP. /// /// The four counts come from an upstream `usage` object — untrusted bytes. The crate already states -/// this discipline for its counts ("a runaway turn pins the count, never +/// this discipline in `IrDuplexUsage::to_billing_usage` ("a runaway turn pins the count, never /// wraps small"); the client-facing re-frame must not answer differently, because a wrapped total /// is a small, believable number that is simply false. #[test] @@ -1012,19 +1012,27 @@ fn cached_input_tokens_are_not_billed_twice() { assert_eq!(u.audio_in, 1000); assert_eq!(u.cached, 800); // The BILLING fold is where the subset is netted out. - // The served meter bills the input as reported: the cached subset is part of it, billed once - // there, and no class bills it again (cached tokens are attribution only). - let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); - assert!( + let billed = u.to_billing_usage(); + assert_eq!( + billed + .usage_units + .get(busbar_contract::records::UNIT_INPUT) + .copied(), + Some(200), + "input bills the UNCACHED remainder (1000 - 800), not the full input figure" + ); + assert_eq!( billed - .iter() - .all(|(c, _)| c.as_str() != crate::meta::CLASS_CACHED_TOKENS.as_str()), - "the cached subset is never a class of its own: {billed:?}" + .usage_units + .get(busbar_contract::records::UNIT_CACHE_READ) + .copied(), + Some(800), + "the cached subset bills once, on the cache-read lane" ); assert_eq!( - billed.iter().map(|(_, n)| n).sum::(), + billed.usage_units.values().sum::(), 1000, - "the billed classes sum to the turn's input, never to 1800" + "the billed lanes sum to the turn's input, never to 1800" ); } @@ -1109,24 +1117,22 @@ fn usage_falls_back_to_stated_totals_when_the_modality_breakdown_is_absent() { ); // The lane the billing fold sums onto is what actually decides the invoice. - // The served meter bills the stated totals across its input and output classes, never zero. - let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); - let sum_of = |names: &[&str]| -> u64 { - billed - .iter() - .filter(|(c, _)| names.contains(&c.as_str())) - .map(|(_, n)| n) - .sum() - }; + let billed = u.to_billing_usage(); assert_eq!( - sum_of(&["audio_tokens_in", "text_tokens_in"]), - stated_in, - "the input classes bill the stated total, never zero" + billed + .usage_units + .get(busbar_contract::records::UNIT_INPUT) + .copied(), + Some(stated_in), + "the input lane bills the stated total, never zero" ); assert_eq!( - sum_of(&["audio_tokens_out", "text_tokens_out"]), - stated_out, - "the output classes bill the stated total, never zero" + billed + .usage_units + .get(busbar_contract::records::UNIT_OUTPUT) + .copied(), + Some(stated_out), + "the output lane bills the stated total, never zero" ); } diff --git a/crates/busbar-plane-streaming/src/codec/ir/usage.rs b/crates/busbar-plane-streaming/src/codec/ir/usage.rs index d374cd3b0e..c263d05851 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/usage.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/usage.rs @@ -12,9 +12,9 @@ /// `CostBreakdown` whose top-level components sum to `total` (the one invariant core enforces), with /// audio/text as labeled opaque components core never interprets. /// -/// A plain token-class tally. The plane meters it through its own declared classes -/// (`crate::session::class_counts`); `cached` is attribution only, a subset of the input it is part -/// of, and never billed on its own. +/// A plain token-class tally, folded (5→4 reserved keys, see [`Self::to_billing_usage`]) onto the +/// neutral [`busbar_contract::billing::Usage`] the kernel prices at read +/// (`runtime::metering::MeteringPort::price_usage`); the plane never prices it. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct IrDuplexUsage { /// Audio input tokens consumed this turn. @@ -29,6 +29,58 @@ pub struct IrDuplexUsage { pub cached: u64, } +impl IrDuplexUsage { + /// FOLD this turn's five token classes onto the neutral [`busbar_contract::billing::Usage`] the + /// host prices — the 5→4 reserved-key map the plane hands + /// `MeteringHost::price_usage` (the host's pricer). Audio and + /// text collapse onto the SAME reserved lane by direction (a session prices audio vs text as separate + /// rate-card MODEL lanes, never separate unit keys), so NO new unit/label/constant is introduced — + /// only the four EXISTING reserved keys ([`busbar_contract::records::UNIT_INPUT`]/`UNIT_OUTPUT`/`UNIT_CACHE_READ`): + /// + /// - `(audio_in + text_in) - cached` → `input` + /// - `audio_out + text_out` → `output` + /// - `cached` → `cache_read` + /// + /// `cached` is NETTED OUT of the input lane because in BOTH dialects the cached figure is a SUBSET + /// of the input figure, not a class beside it — OpenAI Realtime reports `cached_tokens` INSIDE + /// `input_token_details` alongside `audio_tokens`/`text_tokens`, and Gemini's + /// `cachedContentTokenCount` is part of `promptTokenCount` (which is what `promptTokensDetails` + /// breaks out by modality). Billing the full input figure AND the cache figure would charge the + /// cached tokens on two lanes at once. This is the same normalization the LLM plane's reader makes + /// (`prompt_tokens` minus `cached_tokens` is what lands on the input lane), so a cached turn prices + /// identically whichever plane carried it. + /// + /// The IR carrier itself stays wire-faithful (extraction-only, raw provider figures, so the + /// re-frame writers round-trip); the netting happens HERE, at the billing fold, and nowhere else. + /// + /// Only non-zero classes are keyed (the pricer reads absent keys as zero, so this is purely tidy — + /// no zero component ever prices). Saturating sums: a runaway turn pins the count, never wraps small. + /// The subtraction saturates too: a provider that over-reports `cached` floors the input lane at + /// zero rather than wrapping to a colossal charge. + #[must_use] + pub fn to_billing_usage(&self) -> busbar_contract::billing::Usage { + let mut usage_units = std::collections::BTreeMap::new(); + let input = self + .audio_in + .saturating_add(self.text_in) + .saturating_sub(self.cached); + let output = self.audio_out.saturating_add(self.text_out); + if input != 0 { + usage_units.insert(busbar_contract::records::UNIT_INPUT.to_string(), input); + } + if output != 0 { + usage_units.insert(busbar_contract::records::UNIT_OUTPUT.to_string(), output); + } + if self.cached != 0 { + usage_units.insert( + busbar_contract::records::UNIT_CACHE_READ.to_string(), + self.cached, + ); + } + busbar_contract::billing::Usage { usage_units } + } +} + /// THE ONE SEAM every BILLED count read in this crate goes through. /// /// # Why this exists here, beside the carrier it fills diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs new file mode 100644 index 0000000000..5b26d49cc3 --- /dev/null +++ b/crates/busbar-plane-streaming/src/door.rs @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE STREAMING PLANE'S DOOR: the one function a compiled-in row holds and a dropped-in image +//! exports (`examples/streaming_door.rs`), and everything it states and answers. +//! +//! ## What the plane states once +//! +//! The Statement tail ([`TAIL`]): the `streams:` section it declares (and the `providers:` section +//! a document must carry when the plane is linked), its dialects, the `session` grant kind, the six +//! billable classes a session counts in, the per-session fee, its connection needs per direction +//! and the config path its egress target is read from. +//! +//! ## What each generation publishes +//! +//! A snapshot per generation: the doors a session is opened through ([`ROUTES`]), and the audience +//! a caller's token must carry with the metadata URL a refused caller is pointed at, both one +//! reading of the deployment's public base URL. A deployment with no public URL fronts nothing: its +//! snapshot claims nothing and binds no audience. +//! +//! ## What the settings blob is +//! +//! The plane's own `streams:` section as JSON, the kernel's reserved keys already read and stripped. +//! [`read_settings`] judges it with the plane's grammar ([`crate::config::StreamsCfg`], unknown keys +//! refused); an empty blob is the default section. + +use std::mem::size_of; +use std::ptr; +use std::sync::Mutex; + +use busbar_contract::abi::host::conn::connector::{Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND}; +use busbar_contract::abi::mechanism::call::{AbiStr, Blob, InHead, OutHead, Outcome, BLOB_ABSENT}; +use busbar_contract::abi::mechanism::door::{KindTailHead, Statement}; +use busbar_contract::abi::mechanism::lifecycle::{ + CancelIn, CancelOut, GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, +}; +use busbar_contract::abi::plane::{ + ArriveIn, ArriveOut, BillableClass, Claim, DialectAuth, OpClass, OnPieceIn, OnPieceOut, PlaneDriveIn, + PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, + ProjectOut, RefusalIn, RefusalOut, Section, ServeIn, ServeOut, CANCEL_FAILED, CLAIM_EXACT, + INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, PRINCIPAL_NONE, PRINCIPAL_REQUIRED, SECTION_DECLARING, + SECTION_REQUIRED, SHAPE_PIECEWISE, CLAIM_OPEN, +}; +use busbar_contract::abi::sdk::door::{abi_str, statement}; +use busbar_contract::abi::sdk::{Instance, Lent, Published, Safe, SafeSlot}; +use busbar_contract::plane::{PER_SESSION, TOKEN_FAMILY}; + +use crate::claims::{Dialect, HTTP_TRANSPORT, WS_TRANSPORT}; +use crate::config::StreamsCfg; +use crate::meta; +use crate::provider::{GEMINI_LIVE, OPENAI_REALTIME}; + +/// The name the plane's Statement carries. +pub const NAME: &str = crate::codec::PLANE_KEY; +/// The plane's config section. +pub const SECTION: &str = "streams"; +/// The grant kind that admits traffic on this plane: one live session. +pub const SCOPE: &str = "session"; +/// The plane's human label. +pub const LABEL: &str = "Streaming"; +/// What one live session on this plane is called. +pub const SUBJECT_NOUN: &str = "streaming session"; +/// The singular noun for one session, in admin responses. +pub const ADMIN_NOUN: &str = "streaming-session"; +/// The record resource kind a session is audited under. +pub const AUDIT_KIND: &str = "streaming_session"; +/// The one value a key's `session` grant names to open a live session here: the pool every session +/// is served on. +pub const SESSION_POOL: &str = "streaming-server"; +/// The section a document must carry when the plane is linked: the upstream destinations. +pub const PROVIDERS_SECTION: &str = "providers"; +/// Where, inside `streams:`, the upstream a session dials is named. +pub const EGRESS_TARGET: &str = "session.model"; +/// The inbound auth style of a keyed door. +pub const KEY_AUTH: &str = "bearer"; +/// The inbound auth style of the telephony door: the carrier's signed request. +pub const SIGNATURE_AUTH: &str = "webhook-signature"; + +/// The mount every door of the plane sits under. +pub const MOUNT_PATH: &str = "/v1/realtime"; +/// The metadata document a refused caller is pointed at. +pub const METADATA_PATH: &str = "/.well-known/oauth-protected-resource/v1/realtime"; + +/// An absent string. +const NONE: AbiStr = AbiStr { + ptr: ptr::null(), + len: 0, +}; + +const SECTIONS: &[Section] = &[ + Section { + name: abi_str(SECTION), + flags: SECTION_DECLARING, + _reserved: 0, + }, + Section { + name: abi_str(PROVIDERS_SECTION), + flags: SECTION_REQUIRED, + _reserved: 0, + }, +]; + +/// The dialects, in the order the per-call `dialect` index names them. +pub const DIALECTS_BY_NAME: &[&str] = &[ + OPENAI_REALTIME, + GEMINI_LIVE, + Dialect::TwilioMediaStreams.name(), +]; + +const DIALECTS: &[AbiStr] = &[ + abi_str(DIALECTS_BY_NAME[0]), + abi_str(DIALECTS_BY_NAME[1]), + abi_str(DIALECTS_BY_NAME[2]), +]; + +/// The upstream dialects' default auth styles. +const DIALECT_AUTH: &[DialectAuth] = &[DialectAuth { + dialect: 0, + _reserved: 0, + style: abi_str("bearer"), +}]; + +const SCOPE_KINDS: &[AbiStr] = &[abi_str(SCOPE)]; + +/// The one operation every door serves: a session open (or the one request that brokers one). +const OP_CLASSES: &[OpClass] = &[OpClass { + op: abi_str(meta::OP_SESSION_OPEN.as_str()), + name: abi_str(LABEL), +}]; + +const fn class(class: &'static str, family: &'static str) -> BillableClass { + BillableClass { + class: abi_str(class), + family: abi_str(family), + } +} + +/// The six classes a session counts in; `cached_tokens` is attribution only, never billed. +const BILLABLE_CLASSES: &[BillableClass] = &[ + class(meta::CLASS_AUDIO_TOKENS_IN.as_str(), TOKEN_FAMILY), + class(meta::CLASS_AUDIO_TOKENS_OUT.as_str(), TOKEN_FAMILY), + class(meta::CLASS_TEXT_TOKENS_IN.as_str(), TOKEN_FAMILY), + class(meta::CLASS_TEXT_TOKENS_OUT.as_str(), TOKEN_FAMILY), + class(meta::CLASS_AUDIO_SECONDS_IN.as_str(), "duration"), + class(meta::CLASS_TOOL_CALLS.as_str(), "count"), +]; + +const FEE_UNITS: &[AbiStr] = &[abi_str(PER_SESSION)]; + +const EGRESS_TARGETS: &[AbiStr] = &[abi_str(EGRESS_TARGET)]; + +const fn need(direction: u32, transport: &'static str, auth: AbiStr, target: AbiStr) -> Need { + Need { + direction, + egress_class: 0, + transport: abi_str(transport), + auth, + target_from: target, + trust_from: NONE, + details: Blob { + ptr: ptr::null(), + len: 0, + fmt: BLOB_ABSENT, + flags: 0, + }, + } +} + +/// The plane's connection needs: its keyed doors and its telephony door inbound; the provider's +/// realtime socket and its one-shot passes outbound, to the upstream `streams.session.model` names. +/// The outbound credential is the kernel's, per the dialect's default style. +const NEEDS: &[Need] = &[ + need(DIRECTION_INBOUND, WS_TRANSPORT, abi_str(KEY_AUTH), NONE), + need(DIRECTION_INBOUND, HTTP_TRANSPORT, abi_str(KEY_AUTH), NONE), + need(DIRECTION_INBOUND, WS_TRANSPORT, abi_str(SIGNATURE_AUTH), NONE), + need(DIRECTION_OUTBOUND, WS_TRANSPORT, NONE, abi_str(EGRESS_TARGET)), + need(DIRECTION_OUTBOUND, HTTP_TRANSPORT, NONE, abi_str(EGRESS_TARGET)), +]; + +/// THE STATEMENT TAIL. +pub const TAIL: &PlaneTail = &PlaneTail { + head: KindTailHead { + size: size_of::() as u32, + _reserved: 0, + }, + flags: 0, + ingress: INGRESS_REQUEST_RESPONSE | INGRESS_DUPLEX_SESSION, + dispatch_shape: SHAPE_PIECEWISE, + _reserved: 0, + scope: abi_str(SCOPE), + label: abi_str(LABEL), + subject_noun: abi_str(SUBJECT_NOUN), + admin_noun: abi_str(ADMIN_NOUN), + audit_kind: abi_str(AUDIT_KIND), + signing_domain: NONE, + signing_kid_prefix: NONE, + cli_help: NONE, + sections: SECTIONS.as_ptr(), + sections_len: SECTIONS.len(), + dialects: DIALECTS.as_ptr(), + dialects_len: DIALECTS.len(), + dialect_auth: DIALECT_AUTH.as_ptr(), + dialect_auth_len: DIALECT_AUTH.len(), + scope_kinds: SCOPE_KINDS.as_ptr(), + scope_kinds_len: SCOPE_KINDS.len(), + op_classes: OP_CLASSES.as_ptr(), + op_classes_len: OP_CLASSES.len(), + billable_classes: BILLABLE_CLASSES.as_ptr(), + billable_classes_len: BILLABLE_CLASSES.len(), + route_cost: ptr::null(), + route_cost_len: 0, + fee_units: FEE_UNITS.as_ptr(), + fee_units_len: FEE_UNITS.len(), + record_kinds: ptr::null(), + record_kinds_len: 0, + needs: NEEDS.as_ptr(), + needs_len: NEEDS.len(), + egress_targets: EGRESS_TARGETS.as_ptr(), + egress_targets_len: EGRESS_TARGETS.len(), + record_chains: ptr::null(), + record_chains_len: 0, + trust_keys: ptr::null(), + trust_keys_len: 0, +}; + +/// One door a live session is opened through. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Route { + /// The verb. + pub verb: &'static str, + /// The target; `{…}` is one path segment. + pub target: &'static str, + /// The transport claim it arrives over. + pub carrier: &'static str, + /// Read without a credential. + pub open: bool, +} + +/// The doors, in the order a snapshot claims them: the ephemeral-secret mint and the SDP offer (one +/// request each), the browser's sideband socket, the Gemini Live socket, the telephony socket, and +/// the protected-resource metadata document a refused caller is pointed at (read without a +/// credential). +pub const ROUTES: &[Route] = &[ + Route { + verb: "POST", + target: "/v1/realtime/client_secrets", + carrier: HTTP_TRANSPORT, + open: false, + }, + Route { + verb: "POST", + target: "/v1/realtime/calls", + carrier: HTTP_TRANSPORT, + open: false, + }, + Route { + verb: "GET", + target: "/v1/realtime/sideband/{call_id}", + carrier: WS_TRANSPORT, + open: false, + }, + Route { + verb: "GET", + target: "/v1/realtime/gemini/{call_id}", + carrier: WS_TRANSPORT, + open: false, + }, + Route { + verb: "GET", + target: "/twilio/{call_id}", + carrier: WS_TRANSPORT, + open: false, + }, + Route { + verb: "GET", + target: METADATA_PATH, + carrier: HTTP_TRANSPORT, + open: true, + }, +]; + +const fn claim(i: usize) -> Claim { + let r = ROUTES[i]; + let exact = { + let b = r.target.as_bytes(); + let mut j = 0; + let mut templated = false; + while j < b.len() { + if b[j] == b'{' { + templated = true; + } + j += 1; + } + !templated + }; + Claim { + verb: abi_str(r.verb), + target: abi_str(r.target), + carrier: abi_str(r.carrier), + flags: if exact { CLAIM_EXACT } else { 0 } | if r.open { CLAIM_OPEN } else { 0 }, + _reserved: 0, + } +} + +const CLAIMS: &[Claim] = &[claim(0), claim(1), claim(2), claim(3), claim(4), claim(5)]; + +const _: () = assert!(CLAIMS.len() == ROUTES.len()); + +/// Judge a settings blob with the plane's grammar; an empty blob is the default section. +/// +/// # Errors +/// The grammar's reason when the blob is not a `streams:` section. +pub fn read_settings(settings: &[u8]) -> Result { + if settings.is_empty() { + return Ok(StreamsCfg::default()); + } + serde_json::from_slice::(settings).map_err(|e| e.to_string()) +} + +/// ONE READING of the public base URL: parse it and REPLACE the path wholesale, dropping query and +/// fragment, so a public URL with a path of its own cannot produce a second spelling. +#[must_use] +pub fn absolute(public_url: &str, path: &str) -> Option { + let mut u = url::Url::parse(public_url).ok()?; + u.set_path(path); + u.set_query(None); + u.set_fragment(None); + Some(u.to_string()) +} + +/// One generation's published answer: its snapshot and the strings it points at. +pub struct Generation { + audience: String, + resource_metadata: String, + snapshot: Published, +} + +impl Generation { + /// The generation `generation` of a plane whose deployment states `public_url`. + #[must_use] + pub fn build(generation: u64, public_url: Option<&str>) -> Box { + let admitted = public_url.and_then(|p| { + Some((absolute(p, MOUNT_PATH)?, absolute(p, METADATA_PATH)?)) + }); + let (audience, resource_metadata, claims) = match admitted { + Some((a, m)) => (a, m, CLAIMS), + None => (String::new(), String::new(), &[][..]), + }; + let mut g = Box::new(Generation { + audience, + resource_metadata, + snapshot: Published::new(snapshot(generation, claims)), + }); + let mut s = *g.snapshot.get(); + s.audience = text(&g.audience); + s.resource_metadata = text(&g.resource_metadata); + g.snapshot = Published::new(s); + g + } + + /// Its generation. + #[must_use] + pub fn generation(&self) -> u64 { + self.snapshot.get().generation + } + + /// The audience a caller's token must carry; empty = no receiving side. + #[must_use] + pub fn audience(&self) -> &str { + &self.audience + } + + /// The metadata URL a refused caller is pointed at; empty = no receiving side. + #[must_use] + pub fn resource_metadata(&self) -> &str { + &self.resource_metadata + } + + /// Its snapshot, for the host. + #[must_use] + pub fn snapshot(&self) -> *const PlaneSnapshot { + self.snapshot.as_ptr() + } +} + +fn snapshot(generation: u64, claims: &'static [Claim]) -> PlaneSnapshot { + PlaneSnapshot { + size: size_of::() as u32, + _reserved: 0, + generation, + claims: if claims.is_empty() { + ptr::null() + } else { + claims.as_ptr() + }, + claims_len: claims.len(), + admin_routes: ptr::null(), + admin_routes_len: 0, + openapi: Blob { + ptr: ptr::null(), + len: 0, + fmt: BLOB_ABSENT, + flags: 0, + }, + audience: NONE, + resource_metadata: NONE, + } +} + +fn text(s: &str) -> AbiStr { + if s.is_empty() { + return NONE; + } + AbiStr { + ptr: s.as_ptr(), + len: s.len(), + } +} + +/// One open plane instance. +pub struct Plane { + /// The deployment's public base URL, as `open` read it; a refresh keeps it. + public_url: Option, + /// The live generations' settings and snapshots, oldest first (control lane only). + generations: Mutex)>>, +} + +impl Plane { + fn publish(&self, generation: u64, cfg: StreamsCfg) -> *const PlaneSnapshot { + let g = Generation::build(generation, self.public_url.as_deref()); + let p = g.snapshot(); + lock(&self.generations).push((cfg, g)); + p + } +} + +fn lock(m: &Mutex) -> std::sync::MutexGuard<'_, T> { + m.lock().unwrap_or_else(std::sync::PoisonError::into_inner) +} + +/// One slot body on the SDK's safe surface over the instance's [`Plane`]. +macro_rules! slot { + ($name:ident, $in:ty, $out:ty, |$inst:pat_param, $input:pat_param, $o:pat_param| $body:block) => { + struct $name; + impl SafeSlot for $name { + type In = $in; + type Out = $out; + type State = Plane; + fn call($inst: Instance<'_, Plane>, $input: Lent<'_, $in>, $o: &mut $out) -> Outcome { + $body + } + } + }; +} + +slot!(Validate, ValidateIn, OutHead, |_, input, _| { + match read_settings(input.field(|i| &i.settings).bytes()) { + Ok(_) => Outcome::Ready, + Err(_) => Outcome::Refused, + } +}); + +slot!(Open, PlaneOpenIn, PlaneOpenOut, |instance, input, out| { + let Ok(cfg) = read_settings(input.field(|i| &i.open.settings).bytes()) else { + return Outcome::Refused; + }; + let public_url = input + .field(|i| &i.public_url) + .as_str() + .ok() + .filter(|s| !s.is_empty()) + .map(str::to_owned); + let p = Plane { + public_url, + generations: Mutex::new(Vec::new()), + }; + out.snapshot = p.publish(input.open.generation, cfg); + instance.open(p); + Outcome::Ready +}); + +slot!( + Refresh, + RefreshIn, + PlaneRefreshOut, + |instance, input, out| { + let Some(p) = instance.get() else { + return Outcome::Failed; + }; + let Ok(cfg) = read_settings(input.field(|i| &i.settings).bytes()) else { + return Outcome::Refused; + }; + out.snapshot = p.publish(input.generation, cfg); + Outcome::Ready + } +); + +slot!(Retire, GenIn, OutHead, |instance, input, _| { + if let Some(p) = instance.get() { + lock(&p.generations).retain(|(_, g)| g.generation() != input.generation); + } + Outcome::Ready +}); + +slot!(Tick, TickIn, TickOut, |_, _, out| { + out.next_tick_ns = 0; + Outcome::Ready +}); + +slot!(Drive, PlaneDriveIn, PlaneDriveOut, |_, _, _| { + Outcome::Ready +}); + +slot!(Cancel, CancelIn, CancelOut, |_, _, out| { + out.disposition = CANCEL_FAILED; + Outcome::Ready +}); + +slot!(Release, ReleaseIn, OutHead, |_, _, _| { Outcome::Ready }); + +slot!(Close, InHead, OutHead, |_, _, _| { Outcome::Ready }); + +// A claimed arrival is classified: its door's dialect, the session open, a known principal. No +// admission estimate: a session's units are what the far end reports. +slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { + let Some(a) = crate::driven::arrive(input.claim) else { + return Outcome::Refused; + }; + out.op_class = a.op_class; + out.dialect = a.dialect; + out.principal_need = if a.door.is_open() { + PRINCIPAL_NONE + } else { + PRINCIPAL_REQUIRED + }; + out.units_written = 0; + Outcome::Ready +}); + +// The door serves no unit's pieces yet: every piece, refusal render, admin serve and projection is +// declined, and a declined unit is charged nothing. + +slot!(OnPiece, OnPieceIn, OnPieceOut, |_, _, _| { Outcome::Refused }); + +slot!(Refusal, RefusalIn, RefusalOut, |_, _, _| { Outcome::Refused }); + +slot!(Serve, ServeIn, ServeOut, |_, _, _| { Outcome::Refused }); + +slot!(Hydrate, GenIn, OutHead, |_, _, _| { Outcome::Ready }); + +slot!(Start, GenIn, OutHead, |_, _, _| { Outcome::Ready }); + +slot!(Project, ProjectIn, ProjectOut, |_, _, _| { Outcome::Refused }); + +busbar_contract::plugin_door! { + ops: busbar_contract::abi::plane::Ops, + statement: Statement { + kind_tail: ptr::from_ref(TAIL).cast::(), + ..statement(NAME, env!("CARGO_PKG_VERSION"), 64) + }, + lifecycle: { + validate: Safe, open: Safe, refresh: Safe, retire: Safe, + tick: Safe, drive: Safe, cancel: Safe, release: Safe, + close: Safe, + }, + kind_ops: { + arrive: Safe, on_piece: Safe, refusal: Safe, serve: Safe, + hydrate: Safe, start: Safe, project: Safe, + }, +} diff --git a/crates/busbar-plane-streaming/src/driven.rs b/crates/busbar-plane-streaming/src/driven.rs new file mode 100644 index 0000000000..6ed42065ce --- /dev/null +++ b/crates/busbar-plane-streaming/src/driven.rs @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE PLANE'S ANSWERS TO THE KERNEL PLANE DRIVER for its two one-request doors (`BUSBAR-1.6.0.md` +//! Part 3, section 12): the ephemeral-secret mint and the SDP offer. Each answer is plain data, in +//! the plane's own vocabulary; writing it into the host's buffers is the door's job. +//! +//! | driver crossing | here | +//! |---|---| +//! | `arrive` | [`arrive`]: which door the claim is, its dialect and its principal need | +//! | `on_piece`, ATTEMPT | [`mint_attempt`], [`sdp_attempt`]: the request bound for the far end | +//! | `on_piece`, from the far end | [`mint_reply`], [`sdp_reply`]: what the caller is answered | +//! +//! The caller's texts are the served plane's, byte for byte, where the far end answered. A far end +//! that could not be reached is the kernel's route terminal and is not answered here. + +use crate::broker::{ + clamped_ttl_secs, mint_request_body, minted_answer, read_minted, rtc_call_id_of, CALLS_PATH, + CLIENT_SECRETS_PATH, SAFETY_IDENTIFIER_HEADER, SDP_CONTENT_TYPE, +}; +use crate::codec::ir::config::SessionConfig; + +/// The media type of a JSON body. +pub const JSON_CONTENT_TYPE: &str = "application/json"; +/// The head field naming a body's media type. +pub const FIELD_CONTENT_TYPE: &str = "content-type"; +/// The head field naming where a created call lives. +pub const FIELD_LOCATION: &str = "location"; + +/// The door a claim is, by its index in the snapshot's claims (`crate::door::ROUTES`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Door { + /// `POST /v1/realtime/client_secrets`: one ephemeral secret for a browser session. + Mint, + /// `POST /v1/realtime/calls`: one SDP offer, relayed to the provider. + Sdp, + /// The browser's sideband socket. + Sideband, + /// The Gemini Live socket. + Gemini, + /// The telephony socket. + Twilio, + /// The protected-resource metadata document, read without a credential. + Metadata, +} + +impl Door { + /// The door of claim `claim`; `None` for a claim the plane never published. + #[must_use] + pub const fn of(claim: u32) -> Option { + match claim { + 0 => Some(Door::Mint), + 1 => Some(Door::Sdp), + 2 => Some(Door::Sideband), + 3 => Some(Door::Gemini), + 4 => Some(Door::Twilio), + 5 => Some(Door::Metadata), + _ => None, + } + } + + /// The door's dialect, by its index in the tail's dialects. + #[must_use] + pub const fn dialect(self) -> u32 { + match self { + Door::Mint | Door::Sdp | Door::Sideband | Door::Metadata => 0, + Door::Gemini => 1, + Door::Twilio => 2, + } + } + + /// `true` for the door read without a credential. + #[must_use] + pub const fn is_open(self) -> bool { + matches!(self, Door::Metadata) + } + + /// `true` for a door that opens a live session rather than answering one request. + #[must_use] + pub const fn is_session(self) -> bool { + matches!(self, Door::Sideband | Door::Gemini | Door::Twilio) + } +} + +/// What `arrive` answers for a claimed request. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Arrival { + /// The door. + pub door: Door, + /// Index into the tail's op classes: the session open, every door's one operation. + pub op_class: u32, + /// Index into the tail's dialects. + pub dialect: u32, +} + +/// `arrive` for claim `claim`: every keyed door opens a session (or brokers one) for a known +/// principal; the metadata door answers anyone. +#[must_use] +pub const fn arrive(claim: u32) -> Option { + match Door::of(claim) { + Some(door) => Some(Arrival { + door, + op_class: 0, + dialect: door.dialect(), + }), + None => None, + } +} + +/// A request bound for the far end: verb and target explicit, then its head fields and body. The +/// kernel adds the credential and sends it; the target is under the member's base URL. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Attempt { + /// The verb. + pub verb: &'static str, + /// The target, under the member's base URL. + pub target: &'static str, + /// The head fields, in order. + pub fields: Vec<(&'static str, String)>, + /// The body. + pub body: Vec, +} + +/// The mint's ATTEMPT: the locked session params the browser's session is bound to, the secret's +/// lifetime (the requested one held inside its bounds), and the caller named for attribution. +/// +/// # Errors +/// The body's serializer text. +pub fn mint_attempt( + config: &SessionConfig, + caller: &str, + requested_ttl_secs: Option, +) -> Result { + Ok(Attempt { + verb: "POST", + target: CLIENT_SECRETS_PATH, + fields: vec![ + (FIELD_CONTENT_TYPE, JSON_CONTENT_TYPE.to_string()), + (SAFETY_IDENTIFIER_HEADER, caller.to_string()), + ], + body: mint_request_body(clamped_ttl_secs(requested_ttl_secs), config)?, + }) +} + +/// The SDP offer's ATTEMPT: the caller's offer, relayed as it arrived. +#[must_use] +pub fn sdp_attempt(offer: &[u8]) -> Attempt { + Attempt { + verb: "POST", + target: CALLS_PATH, + fields: vec![(FIELD_CONTENT_TYPE, SDP_CONTENT_TYPE.to_string())], + body: offer.to_vec(), + } +} + +/// What the caller is answered. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Reply { + /// The status. + pub status: u16, + /// The head fields, in order. + pub fields: Vec<(&'static str, String)>, + /// The body. + pub body: Vec, + /// For an SDP answer that names its call: the provider's `rtc_` call id, for the session record. + pub rtc_call_id: Option, +} + +/// The status the served plane answers a failed mint with. +pub const MINT_FAILED_STATUS: u16 = 502; + +fn mint_failed(reason: &str) -> Reply { + Reply { + status: MINT_FAILED_STATUS, + fields: Vec::new(), + body: format!("streaming ephemeral-secret mint failed: ephemeral token mint failed: {reason}") + .into_bytes(), + rtc_call_id: None, + } +} + +/// The caller's answer to the far end's mint answer (`status`, `body`): the secret and its expiry on +/// a success that carries one, else the served plane's 502 and its text. +#[must_use] +pub fn mint_reply(status: u16, body: &[u8]) -> Reply { + if !(200..300).contains(&status) { + return mint_failed(&format!( + "client-secret endpoint returned {}", + status_text(status) + )); + } + match read_minted(body) { + Ok(minted) => Reply { + status: 200, + fields: vec![(FIELD_CONTENT_TYPE, JSON_CONTENT_TYPE.to_string())], + body: minted_answer(&minted), + rtc_call_id: None, + }, + Err(reason) => mint_failed(&reason), + } +} + +/// The caller's answer to the far end's SDP answer: its status, its body as an SDP answer, and its +/// `Location` when it named one; the call id in that `Location` goes to the session record. +#[must_use] +pub fn sdp_reply(status: u16, location: Option<&str>, body: &[u8]) -> Reply { + let mut fields = vec![(FIELD_CONTENT_TYPE, SDP_CONTENT_TYPE.to_string())]; + if let Some(loc) = location { + fields.push((FIELD_LOCATION, loc.to_string())); + } + Reply { + status, + fields, + body: body.to_vec(), + rtc_call_id: location.and_then(rtc_call_id_of), + } +} + +/// A status as the served plane printed it: the number and its canonical reason phrase, or the +/// number alone for one with none. +#[must_use] +pub fn status_text(status: u16) -> String { + match reason_phrase(status) { + Some(reason) => format!("{status} {reason}"), + None => status.to_string(), + } +} + +/// The canonical reason phrase of a registered status (RFC 9110 and its registry). +#[must_use] +pub const fn reason_phrase(status: u16) -> Option<&'static str> { + Some(match status { + 100 => "Continue", + 101 => "Switching Protocols", + 102 => "Processing", + 200 => "OK", + 201 => "Created", + 202 => "Accepted", + 203 => "Non Authoritative Information", + 204 => "No Content", + 205 => "Reset Content", + 206 => "Partial Content", + 207 => "Multi-Status", + 208 => "Already Reported", + 226 => "IM Used", + 300 => "Multiple Choices", + 301 => "Moved Permanently", + 302 => "Found", + 303 => "See Other", + 304 => "Not Modified", + 305 => "Use Proxy", + 307 => "Temporary Redirect", + 308 => "Permanent Redirect", + 400 => "Bad Request", + 401 => "Unauthorized", + 402 => "Payment Required", + 403 => "Forbidden", + 404 => "Not Found", + 405 => "Method Not Allowed", + 406 => "Not Acceptable", + 407 => "Proxy Authentication Required", + 408 => "Request Timeout", + 409 => "Conflict", + 410 => "Gone", + 411 => "Length Required", + 412 => "Precondition Failed", + 413 => "Payload Too Large", + 414 => "URI Too Long", + 415 => "Unsupported Media Type", + 416 => "Range Not Satisfiable", + 417 => "Expectation Failed", + 418 => "I'm a teapot", + 421 => "Misdirected Request", + 422 => "Unprocessable Entity", + 423 => "Locked", + 424 => "Failed Dependency", + 426 => "Upgrade Required", + 428 => "Precondition Required", + 429 => "Too Many Requests", + 431 => "Request Header Fields Too Large", + 451 => "Unavailable For Legal Reasons", + 500 => "Internal Server Error", + 501 => "Not Implemented", + 502 => "Bad Gateway", + 503 => "Service Unavailable", + 504 => "Gateway Timeout", + 505 => "HTTP Version Not Supported", + 506 => "Variant Also Negotiates", + 507 => "Insufficient Storage", + 508 => "Loop Detected", + 510 => "Not Extended", + 511 => "Network Authentication Required", + _ => return None, + }) +} + +#[cfg(test)] +#[path = "tests/driven_tests.rs"] +mod tests; + +/// The protected-resource metadata document for `audience`: the audience a token at the plane's +/// doors must carry, and the one place a bearer token is accepted. The plane names no authorization +/// server: it is configured with none. +#[must_use] +pub fn metadata_reply(audience: &str) -> Reply { + Reply { + status: 200, + fields: vec![ + ("cache-control", "public, max-age=3600".to_string()), + (FIELD_CONTENT_TYPE, "application/json; charset=utf-8".to_string()), + ], + body: serde_json::to_vec(&serde_json::json!({ + "resource": audience, + "bearer_methods_supported": ["header"], + })) + .unwrap_or_default(), + rtc_call_id: None, + } +} diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index dcca40cf59..3b0db51283 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -68,6 +68,8 @@ pub mod claims; pub mod codec; pub mod config; pub mod diagnostics; +pub mod door; +pub mod driven; pub mod governed; pub mod meta; pub mod oneshot; diff --git a/crates/busbar-plane-streaming/src/session_params.rs b/crates/busbar-plane-streaming/src/session_params.rs index 742fa0e31c..f3ad7810fc 100644 --- a/crates/busbar-plane-streaming/src/session_params.rs +++ b/crates/busbar-plane-streaming/src/session_params.rs @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! THE SESSION PARAMS A SESSION IS LOCKED TO: the telephony lock. +//! THE SESSION PARAMS A SESSION IS LOCKED TO: the telephony lock, and how a committed rewrite of the +//! session-open params is read back over the locked ones. use crate::codec::ir::config::SessionConfig; use crate::codec::ir::media::AudioFormat; @@ -18,6 +19,40 @@ pub fn g711_config() -> SessionConfig { } } +/// A committed rewrite of the session-open params, read back over the `locked` ones. +/// +/// The rewrite is a PATCH: the params are optional field by field on the wire, so the committed +/// object is merged over the locked params. A key the rewrite names wins (an explicit `null` clears +/// it); a key it does not name keeps the locked value. The merge happens before the decode, so a +/// patch naming one bad field refuses rather than being quietly dropped. +/// +/// A well-formed JSON scalar is refused too: the params are an object on every wire the plane +/// speaks, and the value replaces the locked [`SessionConfig`]. +/// +/// # Errors +/// Output that is not JSON, not an object, or not a session config, each with its reason. +pub fn committed_session_config( + locked: &SessionConfig, + args_json: &[u8], +) -> Result { + let patch: serde_json::Value = + serde_json::from_slice(args_json).map_err(|e| format!("the output is not JSON: {e}"))?; + let serde_json::Value::Object(patch) = patch else { + return Err("the output is JSON but not a session-params object".to_string()); + }; + // The locked params as the rewrite was handed them, so the merge is over exactly the key set it + // screened. + let Ok(serde_json::Value::Object(mut merged)) = serde_json::to_value(locked) else { + return Err( + "the plane's own locked session params did not project to an object".to_string(), + ); + }; + // A key the rewrite NAMED wins; a key it did not name keeps the locked value. + merged.extend(patch); + serde_json::from_value::(serde_json::Value::Object(merged)) + .map_err(|e| format!("the output is not a session config: {e}")) +} + #[cfg(test)] #[path = "tests/session_params_tests.rs"] mod tests; diff --git a/crates/busbar-plane-streaming/src/session_pump.rs b/crates/busbar-plane-streaming/src/session_pump.rs index a32a56fcc4..10d71bda6c 100644 --- a/crates/busbar-plane-streaming/src/session_pump.rs +++ b/crates/busbar-plane-streaming/src/session_pump.rs @@ -187,7 +187,8 @@ where /// A FRAME FROM THE FAR END, at `now_ms`. `serves` answers whether this session runs a named tool /// itself; the calls it does are returned beside the plan, in close order, to be run and handed - /// back through [`Self::tool_executed`]. + /// back through [`Self::tool_executed`]. A call it does not serve is relayed to the caller, whose + /// reply the node's open-call table waits for when one is bound; the gateway never answers it. pub fn on_server_frame( &mut self, frame: WireEvent, @@ -228,6 +229,16 @@ where } IrServerEvent::Tool(t) => { let call_ref = t.call_ref(); + let relayed = t.clone(); + let name = match &t { + IrDuplexTool::CallOpen { name, .. } => name.clone(), + _ => self + .calls + .get(&call_ref) + .map(|e| e.name.clone()) + .unwrap_or_default(), + }; + let served = serves(&name); match t { IrDuplexTool::CallOpen { call_id, name, .. } => { self.turn.open_tool_call(); @@ -254,21 +265,29 @@ where e.closed = true; if !e.executed { e.executed = true; - match &self.governed { - Some(g) if !serves(&e.name) => { - g.calls.planned(g.session, &e.call_id, now_ms); - } - _ => to_exec.push(ToolRun { + if served { + to_exec.push(ToolRun { call_ref, call_id: e.call_id.clone(), name: e.name.clone(), args: e.args.clone(), - }), + }); + } else if let Some(g) = &self.governed { + g.calls.planned(g.session, &e.call_id, now_ms); } } } IrDuplexTool::CallResult { .. } => {} } + // A call this session does not serve is the caller's to answer, as the realtime + // protocols define it: the call is relayed to the caller, and the gateway never + // authors its result. + if !served && !matches!(relayed, IrDuplexTool::CallResult { .. }) { + out.downlink.extend( + self.codec + .write_down(IrServerEvent::Tool(relayed), &mut self.decode), + ); + } } ev @ (IrServerEvent::AudioFrame(_) | IrServerEvent::AudioDone { .. } diff --git a/crates/busbar-plane-streaming/src/tests/broker_tests.rs b/crates/busbar-plane-streaming/src/tests/broker_tests.rs index ee54e49120..09d9a3cf7e 100644 --- a/crates/busbar-plane-streaming/src/tests/broker_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/broker_tests.rs @@ -53,10 +53,7 @@ fn a_mint_answer_that_is_not_an_ephemeral_secret_is_refused() { Err("client-secret response value is not an ek_ ephemeral secret".to_string()) ); let err = read_minted(b"not json").expect_err("refused"); - assert!( - err.starts_with("client-secret response did not parse: "), - "{err}" - ); + assert!(err.starts_with("client-secret response did not parse: "), "{err}"); } #[test] @@ -66,10 +63,7 @@ fn the_browser_is_answered_the_secret_and_its_expiry_only() { expires_at_unix: 5, }); let read: serde_json::Value = serde_json::from_slice(&body).expect("json"); - assert_eq!( - read, - serde_json::json!({"value": "ek_1", "expires_at_unix": 5}) - ); + assert_eq!(read, serde_json::json!({"value": "ek_1", "expires_at_unix": 5})); } #[test] diff --git a/crates/busbar-plane-streaming/src/tests/driven_tests.rs b/crates/busbar-plane-streaming/src/tests/driven_tests.rs new file mode 100644 index 0000000000..8950a6be15 --- /dev/null +++ b/crates/busbar-plane-streaming/src/tests/driven_tests.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! Tests for `crates/busbar-plane-streaming/src/driven.rs`. + +use super::*; + +#[test] +fn every_published_claim_arrives_at_its_door_in_its_dialect() { + let doors: Vec<_> = (0..6) + .map(|c| arrive(c).map(|a| (a.door, a.dialect, a.op_class))) + .collect(); + assert_eq!( + doors, + [ + Some((Door::Mint, 0, 0)), + Some((Door::Sdp, 0, 0)), + Some((Door::Sideband, 0, 0)), + Some((Door::Gemini, 1, 0)), + Some((Door::Twilio, 2, 0)), + Some((Door::Metadata, 0, 0)), + ] + ); + assert_eq!(arrive(6), None, "a claim the plane never published arrives nowhere"); + assert_eq!( + crate::door::ROUTES.len(), + 6, + "one door per published claim" + ); +} + +#[test] +fn the_mint_attempt_names_the_caller_and_carries_the_locked_session() { + let cfg = SessionConfig { + instructions: Some("locked".into()), + ..SessionConfig::default() + }; + let a = mint_attempt(&cfg, "vk-1", None).expect("serializes"); + assert_eq!((a.verb, a.target), ("POST", "/v1/realtime/client_secrets")); + assert_eq!( + a.fields, + vec![ + ("content-type", "application/json".to_string()), + ("OpenAI-Safety-Identifier", "vk-1".to_string()), + ] + ); + let body: serde_json::Value = serde_json::from_slice(&a.body).expect("json"); + assert_eq!(body["expires_after"]["seconds"], 600); + assert_eq!(body["session"]["instructions"], "locked"); + assert!( + a.fields.iter().all(|(n, _)| !n.eq_ignore_ascii_case("authorization")), + "the plane never writes the credential; the kernel adds it" + ); +} + +#[test] +fn a_minted_secret_is_answered_and_a_failure_keeps_the_served_text() { + let ok = mint_reply(200, br#"{"value":"ek_1","expires_at":9}"#); + assert_eq!(ok.status, 200); + let v: serde_json::Value = serde_json::from_slice(&ok.body).expect("json"); + assert_eq!(v, serde_json::json!({"value": "ek_1", "expires_at_unix": 9})); + + let refused = mint_reply(400, b"{}"); + assert_eq!(refused.status, 502); + assert_eq!( + String::from_utf8(refused.body).expect("utf-8"), + "streaming ephemeral-secret mint failed: ephemeral token mint failed: client-secret \ + endpoint returned 400 Bad Request" + ); + let not_ek = mint_reply(200, br#"{"value":"sk-real"}"#); + assert_eq!(not_ek.status, 502); + assert!(!String::from_utf8(not_ek.body).expect("utf-8").contains("sk-real")); +} + +#[test] +fn the_sdp_offer_is_relayed_and_its_answer_names_the_call() { + let a = sdp_attempt(b"v=0\r\n"); + assert_eq!((a.verb, a.target), ("POST", "/v1/realtime/calls")); + assert_eq!(a.fields, vec![("content-type", "application/sdp".to_string())]); + assert_eq!(a.body, b"v=0\r\n"); + + let r = sdp_reply(201, Some("/v1/realtime/calls/rtc_abc"), b"v=0 answer"); + assert_eq!(r.status, 201); + assert_eq!(r.rtc_call_id.as_deref(), Some("rtc_abc")); + assert_eq!( + r.fields, + vec![ + ("content-type", "application/sdp".to_string()), + ("location", "/v1/realtime/calls/rtc_abc".to_string()), + ] + ); + assert_eq!(sdp_reply(400, None, b"no").rtc_call_id, None); +} + +#[test] +fn a_status_prints_with_its_reason_phrase_when_it_has_one() { + assert_eq!(status_text(502), "502 Bad Gateway"); + assert_eq!(status_text(299), "299"); +} + +#[test] +fn the_metadata_document_names_the_audience() { + let r = metadata_reply("https://gw.example.com/v1/realtime"); + assert_eq!(r.status, 200); + let v: serde_json::Value = serde_json::from_slice(&r.body).expect("json"); + assert_eq!(v["resource"], "https://gw.example.com/v1/realtime"); + assert_eq!(v["bearer_methods_supported"], serde_json::json!(["header"])); +} diff --git a/crates/busbar-plane-streaming/src/tests/session_params_tests.rs b/crates/busbar-plane-streaming/src/tests/session_params_tests.rs index 249db01b25..04607a7152 100644 --- a/crates/busbar-plane-streaming/src/tests/session_params_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_params_tests.rs @@ -5,6 +5,13 @@ use super::*; +fn locked() -> SessionConfig { + SessionConfig { + instructions: Some("stay on topic".into()), + ..crate::config::StreamsCfg::default().session + } +} + #[test] fn the_telephony_lock_is_g711_both_ways() { let c = g711_config(); @@ -19,3 +26,35 @@ fn the_telephony_lock_is_g711_both_ways() { SessionConfig::default() ); } + +#[test] +fn a_rewrite_that_is_not_a_session_params_object_is_refused() { + let locked = locked(); + assert!(committed_session_config(&locked, b"not json").is_err()); + for scalar in [&b"7"[..], br#""marin""#, b"null", b"[]", b"true"] { + assert!(committed_session_config(&locked, scalar).is_err()); + } + for bad in [ + &br#"{"output_audio_format":"flac"}"#[..], + br#"{"voice":7}"#, + br#"{"modalities":"audio"}"#, + ] { + assert!(committed_session_config(&locked, bad).is_err()); + } +} + +#[test] +fn a_rewrite_patches_what_it_names_and_keeps_the_rest() { + let locked = locked(); + assert_eq!( + committed_session_config(&locked, b"{}").expect("identity"), + locked + ); + let cleared = + committed_session_config(&locked, br#"{"instructions":null}"#).expect("an explicit null"); + assert_eq!(cleared.instructions, None); + assert_eq!(cleared.turn_detection, locked.turn_detection); + let voiced = committed_session_config(&locked, br#"{"voice":"marin"}"#).expect("one field"); + assert_eq!(voiced.voice.as_deref(), Some("marin")); + assert_eq!(voiced.instructions, locked.instructions); +} diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index b85689d9a6..f8ec1e5343 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -61,10 +61,8 @@ fn call_frames(id: &str, name: &str, args: &str) -> [WireEvent; 3] { [ wire(serde_json::json!({"type":"response.output_item.added", "item":{"type":"function_call","call_id":id,"name":name}})), - wire( - serde_json::json!({"type":"response.function_call_arguments.delta", - "call_id":id,"delta":args}), - ), + wire(serde_json::json!({"type":"response.function_call_arguments.delta", + "call_id":id,"delta":args})), wire(serde_json::json!({"type":"response.function_call_arguments.done","call_id":id})), ] } @@ -84,10 +82,7 @@ fn a_usage_report_closes_the_turn_with_its_counters_and_the_session_continues() let (usage, counters) = sink.closed[0]; let usage = usage.expect("the turn carries its usage"); assert_eq!((usage.audio_in, usage.audio_out), (10, 20)); - assert_eq!( - counters.audio_ms_in, 1000, - "48 000 bytes of pcm16 is one second" - ); + assert_eq!(counters.audio_ms_in, 1000, "48 000 bytes of pcm16 is one second"); } #[test] @@ -108,19 +103,15 @@ fn a_barge_in_cancels_and_truncates_at_what_was_heard() { let mut sink = Turns::default(); let b64 = busbar_contract::media::base64_encode(&[0u8; 96]); let _ = p.on_server_frame( - wire( - serde_json::json!({"type":"response.output_audio.delta","delta":b64, - "item_id":"it7","output_index":0,"content_index":0}), - ), + wire(serde_json::json!({"type":"response.output_audio.delta","delta":b64, + "item_id":"it7","output_index":0,"content_index":0})), 0, &mut sink, &serves_all, ); let (out, _) = p.on_server_frame( - wire( - serde_json::json!({"type":"input_audio_buffer.speech_started", - "audio_start_ms":0,"item_id":"it7"}), - ), + wire(serde_json::json!({"type":"input_audio_buffer.speech_started", + "audio_start_ms":0,"item_id":"it7"})), 0, &mut sink, &serves_all, @@ -154,10 +145,7 @@ fn a_tool_call_is_run_once_on_its_close_with_its_accumulated_arguments() { assert!(up.contains("response.create")); p.settle_open_turn(&mut sink); assert_eq!(sink.closed.len(), 1); - assert_eq!( - sink.closed[0].1.tool_calls, 1, - "the opened call is counted once" - ); + assert_eq!(sink.closed[0].1.tool_calls, 1, "the opened call is counted once"); } /// A table that records what the pump asked of it. @@ -206,20 +194,15 @@ fn a_call_the_session_does_not_serve_waits_for_the_callers_reply() { let mut sink = Turns::default(); for f in call_frames("cc", "lookup", "{}") { let (_, runs) = p.on_server_frame(f, 42, &mut sink, &serves_none); - assert!( - runs.is_empty(), - "the gateway never answers a call it does not serve" - ); + assert!(runs.is_empty(), "the gateway never answers a call it does not serve"); } assert_eq!( *table.planned.lock().expect("lock"), vec![(7, "cc".to_string(), 42)] ); let reply = |id: &str| { - wire( - serde_json::json!({"type":"conversation.item.create","item":{ - "type":"function_call_output","call_id":id,"output":"{}"}}), - ) + wire(serde_json::json!({"type":"conversation.item.create","item":{ + "type":"function_call_output","call_id":id,"output":"{}"}})) }; let forged = p.on_client_frame(reply("nope")); assert!(forged.refused_reply && forged.upstream.is_empty()); @@ -249,14 +232,9 @@ fn a_callers_session_update_is_replaced_by_the_locked_config() { #[test] fn a_session_past_its_ceiling_is_told_why_in_its_dialect() { let mut p = pump(); - let told = p - .ceiling_error(1800) - .expect("the dialect carries an error frame"); + let told = p.ceiling_error(1800).expect("the dialect carries an error frame"); let text = String::from_utf8_lossy(&told.0).into_owned(); - assert!( - text.contains(SESSION_CEILING_REASON) && text.contains("1800 s"), - "{text}" - ); + assert!(text.contains(SESSION_CEILING_REASON) && text.contains("1800 s"), "{text}"); } #[test] @@ -320,4 +298,26 @@ fn the_part_of_a_millisecond_a_frame_leaves_over_is_carried_not_floored() { assert_eq!(sink.closed.len(), 1); assert_eq!(sink.closed[0].1.audio_ms_in, 1000); assert_eq!(billed_audio_seconds(&sink), 1); +#[test] +fn a_call_no_one_on_this_node_serves_is_relayed_to_the_caller_and_never_answered_here() { + // No open-call table bound (an ungoverned node), and no tool served: the call reaches the + // caller, and nothing the gateway writes answers it. + let mut p = pump(); + let mut sink = Turns::default(); + let (mut down, mut up) = (Vec::new(), Vec::new()); + for f in call_frames("cu", "unknown_tool", "{\"q\":1}") { + let (out, runs) = p.on_server_frame(f, 0, &mut sink, &serves_none); + assert!(runs.is_empty(), "the gateway runs nothing for a tool it does not serve"); + down.extend(out.downlink); + up.extend(out.upstream); + } + let down = texts(&down); + assert!( + down.contains("unknown_tool") && down.contains("\"call_id\":\"cu\""), + "the call is relayed to the caller: {down}" + ); + assert!( + !texts(&up).contains("function_call_output"), + "no result is authored upstream by the gateway" + ); } diff --git a/crates/busbar-plane-streaming/src/tools.rs b/crates/busbar-plane-streaming/src/tools.rs index 9fbc726bc0..d746eb4909 100644 --- a/crates/busbar-plane-streaming/src/tools.rs +++ b/crates/busbar-plane-streaming/src/tools.rs @@ -37,15 +37,19 @@ pub trait ToolExecutor: Send + Sync { async fn execute(&self, name: &str, arguments: &[u8]) -> Vec; } -/// A TEST/DEV executor that echoes the call back as a JSON object — enough to prove correlation -/// (the right `name` + `arguments` reached the right call) without a real tool registry. +/// THE DEFAULT: this node serves no tool. Every call the model makes is relayed to the caller, and +/// the caller's result is relayed back, exactly as the upstream realtime protocols define the loop. +/// The gateway never authors a tool's result. #[derive(Debug, Default, Clone, Copy)] -pub struct EchoToolExecutor; +pub struct ClientRelay; #[async_trait] -impl ToolExecutor for EchoToolExecutor { - async fn execute(&self, name: &str, arguments: &[u8]) -> Vec { - let args = String::from_utf8_lossy(arguments); - format!(r#"{{"tool":"{name}","echo":{args}}}"#).into_bytes() +impl ToolExecutor for ClientRelay { + fn serves(&self, _name: &str) -> bool { + false + } + + async fn execute(&self, _name: &str, _arguments: &[u8]) -> Vec { + Vec::new() } } diff --git a/crates/busbar-plane-streaming/tests/door.rs b/crates/busbar-plane-streaming/tests/door.rs new file mode 100644 index 0000000000..9e1d4db982 --- /dev/null +++ b/crates/busbar-plane-streaming/tests/door.rs @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE STREAMING PLANE'S DOOR, BOTH WAYS: the same door compiled in (`door::door`, through the +//! loader's `load_linked`) and dropped in (the `streaming_door` example `cdylib` `cargo test` builds, +//! through `load_dropped`), driven by ONE script over the loader's plane kind. Every answer is judged +//! by the kind's own checks on the way; the two transcripts must be identical, and each is pinned. + +use std::mem::zeroed; +use std::sync::Arc; + +use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome, BLOB_JSON}; +use busbar_contract::abi::mechanism::lifecycle::{ + slot as life, CancelIn, CancelOut, GenIn, RefreshIn, TickIn, TickOut, ValidateIn, +}; +use busbar_contract::abi::mechanism::{KindCode, MECHANISM_VERSION}; +use busbar_contract::abi::plane::{ + slot, ArriveIn, ArriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, + CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, +}; +use busbar_plane_streaming::door; +use busbar_plugin_loader::dispatch::kinds::plane::Plane; +use busbar_plugin_loader::dispatch::{ + in_head, load_dropped, load_linked, out_head, Bind, DispatchConfig, Dispatcher, Frame, + ManifestFacts, NoSink, Plugin, +}; + +fn z() -> T { + // SAFETY: every `in`/`out` here is plain C data; all-zero is a valid value of each. + unsafe { zeroed() } +} + +fn bind(d: &Dispatcher) -> Bind { + Bind { + max_inflight_cap: 8, + sink: Arc::new(NoSink), + dispatcher: d.adopter(), + } +} + +fn json(b: &'static [u8]) -> Blob { + Blob { + ptr: b.as_ptr(), + len: b.len(), + fmt: BLOB_JSON, + flags: 0, + } +} + +fn text(s: &'static str) -> AbiStr { + AbiStr { + ptr: s.as_ptr(), + len: s.len(), + } +} + +fn read(s: AbiStr) -> String { + if s.ptr.is_null() { + return String::new(); + } + // SAFETY: the plugin's text, valid while its generation is live. + String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(s.ptr, s.len) }).into_owned() +} + +/// A published snapshot, read while its generation is live: its generation, its claims +/// (`verb target carrier exact?`) and the audience and metadata it binds. +fn snapshot(p: *const PlaneSnapshot) -> String { + assert!(!p.is_null(), "a READY open/refresh publishes a snapshot"); + // SAFETY: the plugin's generation data, valid until `retire` of its generation. + let s = unsafe { &*p }; + let claims: Vec = (0..s.claims_len) + .map(|i| { + // SAFETY: the snapshot names `claims_len` claims. + let c = unsafe { &*s.claims.add(i) }; + format!( + "{} {} {}{}{}", + read(c.verb), + read(c.target), + read(c.carrier), + if c.flags & CLAIM_EXACT != 0 { " exact" } else { "" }, + if c.flags & CLAIM_OPEN != 0 { " open" } else { "" } + ) + }) + .collect(); + format!( + "gen={} claims=[{}] audience={} metadata={} routes={}", + s.generation, + claims.join(", "), + read(s.audience), + read(s.resource_metadata), + s.admin_routes_len + ) +} + +fn linked(d: &Dispatcher) -> Plugin { + load_linked::(door::door, bind(d)).expect("the linked streaming door loads") +} + +/// The example `cdylib` in this target dir (`cargo test` builds examples). Under CI a missing +/// artifact is a failure, never a skip. +fn dropped(d: &Dispatcher) -> Option> { + let exe = std::env::current_exe().ok()?; + let path = exe.parent()?.parent()?.join("examples").join(format!( + "{}streaming_door{}", + std::env::consts::DLL_PREFIX, + std::env::consts::DLL_SUFFIX + )); + assert!( + path.exists() || std::env::var_os("CI").is_none(), + "the streaming_door example cdylib is not built under CI; a both-ways proof must not skip" + ); + let facts = ManifestFacts { + mechanism_version: MECHANISM_VERSION, + kind: KindCode::Plane, + kind_abi: KindCode::Plane.abi_version(), + }; + path.exists().then(|| { + load_dropped::(&path, &facts, bind(d)).expect("the dropped streaming door loads") + }) +} + +fn validate(p: &Plugin, settings: &'static [u8]) -> Outcome { + let mut v = Frame::new( + ValidateIn { + head: in_head(), + settings: json(settings), + }, + out_head(), + ); + p.call(life::VALIDATE, &mut v).outcome +} + +fn open( + p: &Plugin, + generation: u64, + settings: &'static [u8], + public_url: Option<&'static str>, +) -> (Outcome, String) { + let mut i: PlaneOpenIn = z(); + i.open.head = in_head(); + i.open.generation = generation; + i.open.settings = json(settings); + if let Some(u) = public_url { + i.public_url = text(u); + } + let mut o: PlaneOpenOut = z(); + o.open.head = out_head(); + let mut f = Frame::new(i, o); + let c = p.call(life::OPEN, &mut f); + let snap = if c.outcome == Outcome::Ready { + snapshot(f.out.snapshot) + } else { + String::new() + }; + (c.outcome, snap) +} + +/// THE SCRIPT: the lifecycle over two generations, then one unit offered to the door. +fn script(p: &Plugin, public_url: Option<&'static str>) -> Vec { + let mut t = Vec::new(); + t.push(format!( + "validate unknown key {:?}", + validate(p, br#"{"nonsense_key":1}"#) + )); + t.push(format!( + "validate zero ceiling {:?}", + validate(p, br#"{"session_max_secs":0}"#) + )); + t.push(format!("validate empty {:?}", validate(p, b""))); + t.push(format!( + "validate ceiling {:?}", + validate(p, br#"{"session_max_secs":1800}"#) + )); + + let (outcome, snap) = open(p, 1, br#"{"session_max_secs":1800}"#, public_url); + t.push(format!("open {outcome:?} {snap}")); + + let mut r = Frame::new( + RefreshIn { + head: in_head(), + generation: 2, + settings: json(b"{}"), + secrets: std::ptr::null(), + secrets_len: 0, + }, + PlaneRefreshOut { + head: out_head(), + snapshot: std::ptr::null(), + }, + ); + let c = p.call(life::REFRESH, &mut r); + t.push(format!("refresh {:?} {}", c.outcome, snapshot(r.out.snapshot))); + + let mut g = Frame::new( + GenIn { + head: in_head(), + generation: 1, + }, + out_head(), + ); + t.push(format!("retire 1 {:?}", p.call(life::RETIRE, &mut g).outcome)); + + for claim in [3, 5, 9] { + let mut a: Frame = Frame::new(z(), z()); + a.input.head = in_head(); + a.input.claim = claim; + a.out.head = out_head(); + let c = p.call(slot::ARRIVE, &mut a); + t.push(format!( + "arrive {claim} {:?} dialect={} op={} principal={}", + c.outcome, a.out.dialect, a.out.op_class, a.out.principal_need + )); + } + + let mut k: Frame = Frame::new(z(), z()); + k.input.head = in_head(); + k.input.now_ns = 5; + k.out.head = out_head(); + let c = p.call(life::TICK, &mut k); + t.push(format!("tick {:?} next={}", c.outcome, k.out.next_tick_ns)); + + let mut x: Frame = Frame::new(z(), z()); + x.input.head = in_head(); + x.out.head = out_head(); + let c = p.call(life::CANCEL, &mut x); + t.push(format!( + "cancel {:?} failed={}", + c.outcome, + x.out.disposition == CANCEL_FAILED + )); + t +} + +const PUBLIC: &str = "https://gw.example.com/ignored?q=1#f"; + +fn expected(public: bool) -> Vec { + let doors = "POST /v1/realtime/client_secrets http exact, POST /v1/realtime/calls http exact, \ + GET /v1/realtime/sideband/{call_id} ws, GET /v1/realtime/gemini/{call_id} ws, \ + GET /twilio/{call_id} ws, \ + GET /.well-known/oauth-protected-resource/v1/realtime http exact open"; + let (claims, audience, metadata) = if public { + ( + doors, + "https://gw.example.com/v1/realtime", + "https://gw.example.com/.well-known/oauth-protected-resource/v1/realtime", + ) + } else { + ("", "", "") + }; + let snap = |g: u64| { + format!("gen={g} claims=[{claims}] audience={audience} metadata={metadata} routes=0") + }; + vec![ + "validate unknown key Refused".into(), + "validate zero ceiling Refused".into(), + "validate empty Ready".into(), + "validate ceiling Ready".into(), + format!("open Ready {}", snap(1)), + format!("refresh Ready {}", snap(2)), + "retire 1 Ready".into(), + "arrive 3 Ready dialect=1 op=0 principal=1".into(), + "arrive 5 Ready dialect=0 op=0 principal=0".into(), + "arrive 9 Refused dialect=0 op=0 principal=0".into(), + "tick Ready next=0".into(), + "cancel Ready failed=true".into(), + ] +} + +#[test] +fn the_linked_door_states_the_planes_doors_under_its_public_url() { + let d = Dispatcher::new(DispatchConfig::default()); + assert_eq!(script(&linked(&d), Some(PUBLIC)), expected(true)); +} + +#[test] +fn a_deployment_with_no_public_url_is_claimed_by_no_door() { + let d = Dispatcher::new(DispatchConfig::default()); + assert_eq!(script(&linked(&d), None), expected(false)); +} + +#[test] +fn the_dropped_door_answers_as_the_linked_door_does() { + let d = Dispatcher::new(DispatchConfig::default()); + let Some(dropped) = dropped(&d) else { + eprintln!("skip: the streaming_door example cdylib is not built"); + return; + }; + for url in [Some(PUBLIC), None] { + assert_eq!( + script(&dropped, url), + script(&linked(&d), url), + "the two doors must answer identically" + ); + } +} + +#[test] +fn the_tail_states_what_the_served_plane_declares() { + // The billable classes a session counts in, the per-session fee and the `session` grant kind: + // the served plane's own declaration, read off the tail the door states. + let t = door::TAIL; + assert_eq!(read(t.scope), "session"); + assert_eq!(read(t.audit_kind), "streaming_session"); + // SAFETY: the tail's `'static` lists. + let classes: Vec = unsafe { std::slice::from_raw_parts(t.billable_classes, t.billable_classes_len) } + .iter() + .map(|c| format!("{}/{}", read(c.class), read(c.family))) + .collect(); + assert_eq!( + classes, + [ + "audio_tokens_in/token", + "audio_tokens_out/token", + "text_tokens_in/token", + "text_tokens_out/token", + "audio_seconds_in/duration", + "tool_calls/count", + ] + ); + // SAFETY: as above. + let fees: Vec = unsafe { std::slice::from_raw_parts(t.fee_units, t.fee_units_len) } + .iter() + .map(|f| read(*f)) + .collect(); + assert_eq!(fees, ["per_session"]); + busbar_contract::abi::plane::check::check_tail(t).expect("the kind's own check admits the tail"); +} diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index 582e5d6413..04f2671c2e 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -36,7 +36,7 @@ use crate::plane_provider::{provider_ws_url, redact_url_credentials}; use crate::runtime::carrier::Carrier; use crate::runtime::scope::SessionHandle; use crate::runtime::session::{serve_to_teardown, serve_with_sweep, UplinkForwarder, VoiceSession}; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; +use crate::runtime::{ClientRelay, VoiceRuntime}; use crate::topology::minter_https::HttpsTokenMinter; use crate::topology::telephony::{begin_telephony, g711_config, open_admitted_telephony}; use crate::topology::webrtc::TokenMinter; @@ -549,7 +549,7 @@ pub fn voice_build(ctx: &BuildCtx) -> Option> { fn dispatch_runtime() -> VoiceRuntime { VoiceRuntime::new( Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), + Arc::new(ClientRelay), ) .with_streams(&crate::config::configured()) } @@ -611,9 +611,36 @@ pub fn voice_routes(slot: &dyn Any) -> Vec { auth: RouteAuth::Key, handler: Arc::new(|ctx: PlaneReqCtx| -> PlaneRouteFuture { Box::pin(sdp_route(ctx)) }), }, + // THE RFC 9728 DOCUMENT the refused caller's challenge points at: the one open route, read + // without a credential by the callers who do not have one yet. + PlaneRouteSpec { + path: METADATA_PATH.to_string(), + method: RouteMethod::Get, + auth: RouteAuth::None, + handler: Arc::new(|ctx: PlaneReqCtx| -> PlaneRouteFuture { + Box::pin(metadata_route(ctx)) + }), + }, ] } +/// The protected-resource metadata document: the audience a token at this plane's doors must carry, +/// as one reading of the public URL. No authorization server is named: the plane is configured with +/// none. +async fn metadata_route(ctx: busbar_kernel::plane_routes::PlaneReqCtx) -> axum::response::Response { + let Some(mount) = ctx.slot.downcast_ref::() else { + return refusal( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "streaming route reached without its dispatch slot", + ); + }; + busbar_kernel::ingress::protocol::metadata(&busbar_kernel::ingress::protocol::Metadata { + resource: std::borrow::Cow::Borrowed(mount.audience.as_str()), + authorization_servers: &[], + scopes_supported: &[], + }) +} + /// THE PLANE'S WS-ACCEPT ENTRY HOOK ([`crate::linked`]): installs [`voice_ws_arrivals`] into the /// neutral WS-accept registry, before the router that drains it is built. pub fn install_arrivals() { @@ -952,27 +979,7 @@ async fn hook_gate( /// speaks, and the value is substituted for the plane's locked [`SessionConfig`]; admitting `7` would /// mean telling the hook its rewrite landed and then opening the session with something that is not a /// session config at all. -pub(crate) fn committed_session_config( - locked: &SessionConfig, - args_json: &[u8], -) -> Result { - let patch: serde_json::Value = - serde_json::from_slice(args_json).map_err(|e| format!("the output is not JSON: {e}"))?; - let serde_json::Value::Object(patch) = patch else { - return Err("the output is JSON but not a session-params object".to_string()); - }; - // The locked params as the hook itself was handed them (`serde_json::to_vec(cfg)` at the call - // seam), so the merge is over exactly the key set the hook screened. - let Ok(serde_json::Value::Object(mut merged)) = serde_json::to_value(locked) else { - return Err( - "the plane's own locked session params did not project to an object".to_string(), - ); - }; - // A key the hook NAMED wins; a key it did not name keeps the plane's locked value. - merged.extend(patch); - serde_json::from_value::(serde_json::Value::Object(merged)) - .map_err(|e| format!("the output is not a session config: {e}")) -} +pub(crate) use busbar_plane_streaming::session_params::committed_session_config; /// The hooks-TAP leg (`host.transform_over`) over the session-open params. `Ok(Some(cfg))` is a /// committed rewrite the caller substitutes for the locked params; `Ok(None)` is "no change" (no @@ -1174,14 +1181,7 @@ async fn serve_sdp( .unwrap_or_else(|_| sideband_pending()) } -/// The last `rtc_` path segment of a brokered call's `Location` header — the correlation key -/// the SDP broker stamps onto the durable row. `None` when no segment carries the `rtc_` prefix. -fn rtc_call_id_of(location: &str) -> Option { - location - .rsplit('/') - .find(|seg| seg.starts_with("rtc_")) - .map(|seg| seg.split(['?', '#']).next().unwrap_or(seg).to_string()) -} +use busbar_plane_streaming::broker::rtc_call_id_of; /// The substrate egress client the one-shot HTTPS passes dial through (the same posture the concrete /// minter uses). Built per pass; the composition root pools one once the provider config is threaded. diff --git a/crates/busbar-voice/src/runtime/mod.rs b/crates/busbar-voice/src/runtime/mod.rs index 49044050cd..4c14ee13e4 100644 --- a/crates/busbar-voice/src/runtime/mod.rs +++ b/crates/busbar-voice/src/runtime/mod.rs @@ -14,12 +14,14 @@ pub mod carrier; pub mod metering; pub mod scope; pub mod session; -/// THE SERVER-SIDE TOOL EXECUTOR PORT, RE-EXPORTED FROM `busbar-plane-streaming`. `ToolExecutor` and -/// `EchoToolExecutor` moved to the plane crate for the same reason the governed-call port below did: -/// the port is what a tool call MEANS to this plane, and it names nothing this crate owns. Re-exported -/// as a MODULE, not just its items, so `crate::runtime::tools::EchoToolExecutor` — the spelling the -/// topology and governed-binding cells use — resolves exactly what it always did. -pub use busbar_plane_streaming::tools; +/// THE TOOL EXECUTOR PORT, from `busbar-plane-streaming`, and its production default +/// ([`tools::ClientRelay`]: this node serves no tool, so every call is relayed to the caller). The +/// echo executor the batteries drive is a test double, present only in test builds. +pub mod tools { + pub use busbar_plane_streaming::tools::{ClientRelay, ToolExecutor}; + #[cfg(any(test, feature = "test-support"))] + pub use crate::testkit::echo::EchoToolExecutor; +} pub use carrier::Carrier; // THE GOVERNED-CALL PORT, RE-EXPORTED FROM `busbar-plane-streaming`. `GovernedCalls` / `ReplyRefusal` / @@ -35,7 +37,9 @@ pub use scope::{SessionHandle, VoiceSessionRow}; pub use session::{ serve_to_teardown, serve_with_sweep, Outbound, SessionCore, UplinkForwarder, VoiceSession, }; -pub use tools::{EchoToolExecutor, ToolExecutor}; +pub use tools::{ClientRelay, ToolExecutor}; +#[cfg(any(test, feature = "test-support"))] +pub use tools::EchoToolExecutor; use busbar_kernel::plane::handle_engine::DurableHandleEngine; use std::sync::Arc; @@ -131,8 +135,8 @@ impl VoiceRuntime { /// WHAT IS WIRED, AND WHAT REMAINS A DEV DEFAULT. This entry reads the REAL `streams:` config (session /// posture / ceilings, via `with_streams`). A session's metering is not a runtime dependency at all: /// it is the kernel account the governed open binds for the presenting key over the live host. What is -/// still a dev default is the durable engine (a fresh [`DurableHandleEngine`]) and the tool executor -/// ([`EchoToolExecutor`]): deriving the config-driven engine/tool set is a SEPARATE, tracked slice. +/// still a dev default is the durable engine (a fresh [`DurableHandleEngine`]). The tool executor is +/// [`ClientRelay`]: this node serves no tool, and every call is relayed to the caller. /// `prior` (carry-over) is ignored today; the signature is the real one so binding those /// config-derived dependencies is a body change, not an ABI change. pub fn build_runtime( @@ -150,7 +154,7 @@ pub fn build_runtime( Arc::new( VoiceRuntime::new( Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), + Arc::new(ClientRelay), ) .with_streams(&streams), ) diff --git a/crates/busbar-voice/src/runtime/session.rs b/crates/busbar-voice/src/runtime/session.rs index ffbb6a0004..88d05b84d5 100644 --- a/crates/busbar-voice/src/runtime/session.rs +++ b/crates/busbar-voice/src/runtime/session.rs @@ -32,9 +32,12 @@ use busbar_kernel::ingress::byte_duplex::{CallRef as WireCallRef, DuplexHandle, /// THE FRAME PLAN one decoded inbound frame produces — what to write UPSTREAM (client→server events: /// tool results, barge-in cancel/truncate, `response.create`), what to relay DOWNLINK to the client, -/// and whether the kernel's budget verdict tripped a HARD CLOSE this frame. Re-exported with the named reason a -/// session closed at its configured wall-clock ceiling is told under. -pub use busbar_plane_streaming::session_pump::{Outbound, SESSION_CEILING_REASON}; +/// and whether the kernel's budget verdict tripped a HARD CLOSE this frame. The plane's own plan +/// (`busbar_plane_streaming::session_pump::Outbound`). +pub use busbar_plane_streaming::session_pump::Outbound; + +/// The named reason a session closed at its configured wall-clock ceiling is told under. +pub use busbar_plane_streaming::session_pump::SESSION_CEILING_REASON; /// A closed turn's way to the kernel account: the session's metering, when it has one. A turn the /// account answers `MustClose` for cuts the session. diff --git a/crates/busbar-voice/src/runtime/tests.rs b/crates/busbar-voice/src/runtime/tests.rs index caf1d0fe21..2071fcc75a 100644 --- a/crates/busbar-voice/src/runtime/tests.rs +++ b/crates/busbar-voice/src/runtime/tests.rs @@ -9,6 +9,7 @@ //! count (what a count is WORTH is proven over the real engine in the composition root's tests). use crate::ir::codec::{OpenAiRealtimeCodec, WireEvent}; +use crate::ir::usage::IrDuplexUsage; use crate::runtime::carrier::Carrier; use crate::runtime::metering::{SessionMetering, TurnMeter, TurnVerdict}; use crate::runtime::scope::SessionHandle; @@ -142,6 +143,45 @@ fn row_set(pairs: &[(&str, u64)]) -> BTreeMap { // ── metering: the plane's counts, the kernel's verdict ────────────────────────────────────────── +#[test] +fn usage_folds_five_classes_onto_the_four_reserved_keys() { + // The codec's own 5→4 fold (no longer the voice billing path, which ledgers the plane's classes — + // see `a_served_turn_ledgers_the_planes_counts_per_class`), kept honest while it exists. + let u = IrDuplexUsage { + audio_in: 20, + audio_out: 3, + text_in: 5, + text_out: 7, + cached: 11, + }; + let usage = u.to_billing_usage(); + assert_eq!( + usage + .usage_units + .get(busbar_contract::records::UNIT_INPUT) + .copied(), + Some(20 + 5 - 11) + ); + assert_eq!( + usage + .usage_units + .get(busbar_contract::records::UNIT_OUTPUT) + .copied(), + Some(3 + 7) + ); + assert_eq!( + usage + .usage_units + .get(busbar_contract::records::UNIT_CACHE_READ) + .copied(), + Some(11) + ); + assert!(IrDuplexUsage::default() + .to_billing_usage() + .usage_units + .is_empty()); +} + /// Q21b EXIT TEST — a served voice session writes the streaming plane's count rows. One turn: a /// second of uplink audio, one tool call opened, a usage report naming all five token classes. The /// ledger holds exactly the plane's SIX billable classes, under the voice lane, at the counts the diff --git a/crates/busbar-voice/src/testkit/echo.rs b/crates/busbar-voice/src/testkit/echo.rs new file mode 100644 index 0000000000..2f76bb5e6d --- /dev/null +++ b/crates/busbar-voice/src/testkit/echo.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! A TEST EXECUTOR that serves every tool by echoing the call back as a JSON object: enough to prove +//! correlation (the right `name` and `arguments` reached the right call) without a tool registry. +//! Test builds only; production relays every call to the caller (`ClientRelay`). + +use async_trait::async_trait; +use busbar_plane_streaming::tools::ToolExecutor; + +/// Echoes the call: `{"tool":,"echo":}`. +#[derive(Debug, Default, Clone, Copy)] +pub struct EchoToolExecutor; + +#[async_trait] +impl ToolExecutor for EchoToolExecutor { + async fn execute(&self, name: &str, arguments: &[u8]) -> Vec { + let args = String::from_utf8_lossy(arguments); + format!(r#"{{"tool":"{name}","echo":{args}}}"#).into_bytes() + } +} diff --git a/crates/busbar-voice/src/testkit/mod.rs b/crates/busbar-voice/src/testkit/mod.rs index 0a6c36fe7e..6471a0f938 100644 --- a/crates/busbar-voice/src/testkit/mod.rs +++ b/crates/busbar-voice/src/testkit/mod.rs @@ -14,6 +14,9 @@ /// The in-memory [`busbar_kernel::plane_host::EngineHost`] this plane's cells drive when no engine /// `App` is in their closure at all: scripted hook gates/rewrites, breaker cells and a per-key ledger /// behind the same seam production reaches. +/// The echo tool executor the batteries drive: answers every call with its own name and arguments. +pub mod echo; + pub mod fixture_host; /// A loopback HTTP provider that records what it was dialed with, for this plane's egress legs. diff --git a/crates/busbar-voice/src/tests/mount_tests.rs b/crates/busbar-voice/src/tests/mount_tests.rs index 2c2e21bc95..d4e5b9968d 100644 --- a/crates/busbar-voice/src/tests/mount_tests.rs +++ b/crates/busbar-voice/src/tests/mount_tests.rs @@ -246,6 +246,11 @@ fn the_five_ingress_doors_mount_audience_checked_across_the_http_and_ws_seams() RouteMethod::Post, RouteAuth::Key ), + ( + "/.well-known/oauth-protected-resource/v1/realtime".to_string(), + RouteMethod::Get, + RouteAuth::None + ), ], "the two one-shot HTTP doors mount, each RouteAuth::Key behind the plane's one audience" ); @@ -798,4 +803,45 @@ async fn a_failed_provider_dial_refunds_its_session_fee_exactly_once() { .expect("the session opens"); super::settle_undialed(proxy, 2); assert_each_failed_open_refunded_once(&host, 1, "a failed provider dial"); +/// THE CHALLENGE'S POINTER RESOLVES. A refused caller is sent to the admission's resource-metadata +/// URL; the plane serves that document there, without a credential, naming the audience a token +/// must carry. +#[cfg(feature = "test-support")] +#[tokio::test] +async fn the_resource_metadata_the_challenge_points_at_is_served() { + let slot = slot_from_public_url(Some(PUBLIC_URL)).expect("a public_url ⇒ a dispatch slot"); + let admission = crate::mount::voice_admission(slot.as_ref()).expect("a slot admits"); + let path = admission + .resource_metadata + .strip_prefix(PUBLIC_URL) + .expect("the pointer is under the public URL") + .to_string(); + let route = voice_routes(slot.as_ref()) + .into_iter() + .find(|r| r.path == path && r.method == RouteMethod::Get) + .expect("the pointer's path is a route the plane serves"); + assert_eq!(route.auth, RouteAuth::None, "readable without a credential"); + let host = crate::testkit::fixture_host::FixtureHost::new().into_host(); + let ctx = busbar_kernel::plane_routes::PlaneReqCtx { + path: path.clone(), + uri: axum::http::Uri::default(), + method: RouteMethod::Get, + headers: axum::http::HeaderMap::new(), + body: bytes::Bytes::new(), + path_params: Vec::new(), + caller_principal: None, + gov: None, + principal: None, + host, + engine: Arc::new(()), + slot: Arc::clone(&slot), + }; + let resp = (route.handler)(ctx).await; + assert_eq!(resp.status(), axum::http::StatusCode::OK); + let body = http_body_util::BodyExt::collect(resp.into_body()) + .await + .expect("body") + .to_bytes(); + let doc: serde_json::Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(doc["resource"], admission.audience.as_str()); } diff --git a/crates/busbar-voice/src/topology/minter_https.rs b/crates/busbar-voice/src/topology/minter_https.rs index b69424730d..e9b0ce13e1 100644 --- a/crates/busbar-voice/src/topology/minter_https.rs +++ b/crates/busbar-voice/src/topology/minter_https.rs @@ -22,20 +22,12 @@ use bytes::Bytes; use http_body_util::{BodyExt, Full}; use std::time::Duration; -/// The default requested secret lifetime when the caller pins none. -const DEFAULT_TTL_SECS: u64 = 600; -/// The provider's minimum accepted secret lifetime. -const MIN_TTL_SECS: u64 = 10; -/// The provider's maximum accepted secret lifetime. -const MAX_TTL_SECS: u64 = 7200; -/// The prefix every ephemeral client secret the provider mints carries. -const EK_PREFIX: &str = "ek_"; -/// The header binding a minted secret to the caller identity. -const SAFETY_IDENTIFIER_HEADER: &str = "OpenAI-Safety-Identifier"; +use busbar_plane_streaming::broker::{ + clamped_ttl_secs, mint_request_body, read_minted, CLIENT_SECRETS_PATH, SAFETY_IDENTIFIER_HEADER, +}; + /// The bound on the whole mint exchange up to the response head plus its small body read. const MINT_DEADLINE: Duration = Duration::from_secs(30); -/// The client-secrets endpoint path on the provider. -const CLIENT_SECRETS_PATH: &str = "/v1/realtime/client_secrets"; /// MINTS the browser's ephemeral client secret over a real HTTPS call to the provider's /// client-secrets endpoint, holding the real key server-side. @@ -56,7 +48,8 @@ impl HttpsTokenMinter { /// Build a minter over an already-assembled egress client. `base_url` is the provider origin /// (scheme + authority, e.g. `https://api.openai.com`); `api_key` is the REAL provider key held /// server-side; `safety_identifier` is the caller-identity binding; `requested_ttl_secs` is the - /// desired secret lifetime (`None` ⇒ [`DEFAULT_TTL_SECS`]), clamped to the accepted window on mint. + /// desired secret lifetime (`None` ⇒ [`busbar_plane_streaming::broker::DEFAULT_TTL_SECS`]), + /// clamped to the accepted window on mint. pub fn new( client: EngineClient, base_url: impl Into, @@ -75,31 +68,15 @@ impl HttpsTokenMinter { /// The requested lifetime clamped to the provider's accepted `[MIN, MAX]` window. fn clamped_ttl_secs(&self) -> u64 { - self.requested_ttl_secs - .unwrap_or(DEFAULT_TTL_SECS) - .clamp(MIN_TTL_SECS, MAX_TTL_SECS) + clamped_ttl_secs(self.requested_ttl_secs) } } -/// The provider's client-secret response: the `ek_` value and its absolute expiry in unix seconds. -#[derive(serde::Deserialize)] -struct ClientSecretResponse { - value: String, - #[serde(default)] - expires_at: u64, -} - #[async_trait] impl TokenMinter for HttpsTokenMinter { async fn mint(&self, config: &SessionConfig) -> Result { let ttl_secs = self.clamped_ttl_secs(); - let body = serde_json::json!({ - "expires_after": { "anchor": "created_at", "seconds": ttl_secs }, - "session": config, - }); - let body_bytes = serde_json::to_vec(&body).map_err(|e| { - MintError::Provider(format!("mint request body did not serialize: {e}")) - })?; + let body_bytes = mint_request_body(ttl_secs, config).map_err(MintError::Provider)?; let uri = format!( "{}{}", @@ -140,21 +117,11 @@ impl TokenMinter for HttpsTokenMinter { ))); } - let parsed: ClientSecretResponse = serde_json::from_slice(&raw).map_err(|e| { - MintError::Provider(format!("client-secret response did not parse: {e}")) - })?; - - // The browser-facing invariant: only an `ek_` secret ever leaves this boundary. A response - // whose value lacks the prefix is refused rather than handed on as if it were a client secret. - if !parsed.value.starts_with(EK_PREFIX) { - return Err(MintError::Provider( - "client-secret response value is not an ek_ ephemeral secret".into(), - )); - } + let minted = read_minted(&raw).map_err(MintError::Provider)?; Ok(EphemeralToken { - value: parsed.value, - expires_at_unix: parsed.expires_at, + value: minted.value, + expires_at_unix: minted.expires_at_unix, }) } } diff --git a/crates/busbar-voice/src/topology/telephony.rs b/crates/busbar-voice/src/topology/telephony.rs index 5abb2f1b4e..fca51075e7 100644 --- a/crates/busbar-voice/src/topology/telephony.rs +++ b/crates/busbar-voice/src/topology/telephony.rs @@ -17,7 +17,6 @@ use crate::ir::codec::{DuplexReader, DuplexWriter}; use crate::ir::config::SessionConfig; -use crate::ir::media::AudioFormat; use crate::runtime::carrier::Carrier; use crate::runtime::scope::SessionHandle; use crate::runtime::session::{SessionCore, UplinkForwarder, VoiceSession}; @@ -28,17 +27,7 @@ use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures::{Sink, Stream, StreamExt}; use std::sync::Arc; -/// THE LOCKED CONFIG for a telephony leg: `g711_ulaw` on BOTH the input and output audio formats so the -/// 8 kHz µ-law carrier passes straight through with no resample. Callers overlay their own -/// instructions/tools onto the returned config before locking it. -#[must_use] -pub fn g711_config() -> SessionConfig { - SessionConfig { - input_audio_format: Some(AudioFormat::G711Ulaw), - output_audio_format: Some(AudioFormat::G711Ulaw), - ..SessionConfig::default() - } -} +pub use busbar_plane_streaming::session_params::g711_config; /// A LIVE TELEPHONY PROXY — the two planes to serve (one per socket), the funnels between them, the /// shared session core, and the durable handle. Build it with [`begin_telephony`]; drive it with diff --git a/qa/construction.toml b/qa/construction.toml index 72853c261d..8aefd093b0 100644 --- a/qa/construction.toml +++ b/qa/construction.toml @@ -736,6 +736,8 @@ busbar-transport-stdio = ["tokio", "futures"] # The MCP plane's outbound wire (fold A5, ARCHITECT 2026-09-29 under the PLUGIN LIBRARIES ruling): # the `Mcp-Name` header's `=?base64?...?=` sentinel for a name a header value cannot carry. busbar-plane-mcp = ["base64"] +# The streaming plane's door reads the deployment's public base URL once (`door::absolute`). +busbar-plane-streaming = ["url"] # Ratchet, NOT a review: each busbar-plane-* adapter names its own dialect's legacy engine crate # (busbar-llm/-mcp/-a2a/-voice) for codecs only, read-only, no engine state crossing back — but # those engine crates are ALSO named in gate.plane_crates, so this is structurally the exact "a From dc9c25386581e01d62a7488640503252f911cd44 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 02/82] busbar-plane-streaming: one live session as the driver serves it, its record, the unserved adapter deleted, the mint names no caller without a reference (fold S3c-1, S4-1, S1c; 5 carried commits) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- busbar-plane-streaming: one live session as the driver serves it, with the telephony envelope (fold S3c-1, ARCHITECT ruling (b)) src/session_unit.rs is SessionUnit, sans I/O. It holds the session pump, the session's cumulative units per billable class (every closed turn adds to them, and the plane never cuts on money; the kernel owns the cut), the wall-clock ceiling on the tick, the end that settles an open turn once, and on the telephony door the Twilio Media Streams envelope (TwilioEnvelope, the one Twilio reader). On that door a start is checked against the locked g711_ulaw format (another format ends the session). Media on the admitted stream becomes the far end's uplink append with the audio unchanged. Media for another stream and any unmodelled event are dropped, and a stop ends the session and settles the open turn. Model audio goes back to the caller as media frames on the call's stream, and a barge-in clears the carrier's queued audio. RED (ruling (b), the defect fix): session_unit_tests::the_callers_audio_on_the_telephony_door_reaches_the_far_end. The served telephony leg fed Twilio's event-tagged frames to the realtime reader, which dropped them, so the caller's audio reached no one. Not served yet: the door does not drive SessionUnit until the kernel's session driver exists (S5). -- busbar-plane-streaming: a live session keeps its record as the served plane did (fold S4-1) SessionUnit now holds the session's record (VoiceSessionRow: id, owner, turns, updated_at, terminal, rtc_call_id) and queues its writes the way the served plane wrote them: once at open, once when the provider names the call (the SDP answer's rtc_ id), and once terminal at the end (a second end writes nothing). The served plane never advanced the turn cursor, and neither does this. The owner is the caller's reference the kernel lends, never the principal. The door hands the writes to the kernel's records service once the session driver serves the door. -- busbar-plane-streaming: the unserved plane's behaviour tests move onto the session pump and unit; the Twilio reader closes the empty-stream gap (fold S1c prep) These changes get the unserved StreamingPlane (plane.rs, twilio.rs, ulaw.rs, oneshot.rs) ready for deletion with no behaviour test lost (F25 map: busbar-run/foldstreaming-f25-plane-tests.tsv): - The behaviour tests the plane-trait adapter carried are re-expressed on SessionPump and SessionUnit: - two open tool calls wait on two correlations; - an upstream error still meters its turn (40 s, 2 calls); - a barge-in carries its turn's counters onto the turn that takes over; - a non-UTF-8 or unknown caller frame goes nowhere; - milliseconds meter as whole seconds rounded up; - a Twilio stop settles audio and tool calls; - one full turn reports the six classes [10,20,3,4,1,1], cached excluded; - an empty streamSid start refuses the call; - a media payload resuming after padding is refused and never billed. - TwilioBridge refuses a start with an empty stream id and never matches media without one. This closes the vacuous-binding gap the old reader had fixed. It also refuses a frame that is not Twilio's shape or carries unclean base64 (the old reader's refusal), and still drops an event it does not model. - The zero-allocation downlink renderer moves into the one Twilio codec as TwilioEnvelope::encode_media_into. It writes the codec's own member order (event, streamSid, media), pinned by twilio_tests, and tests/alloc_gate.rs keeps its zero-allocation baseline against it. - The tool-reply correlation key and deadline move from plane.rs to open_calls.rs, beside the leg that names them. plane.rs re-exports them until it is deleted. -- busbar-plane-streaming: the unserved StreamingPlane adapter and its duplicate Twilio reader are deleted (fold S1c, F11, ARCHITECT ruling) F11: the served implementation is the base. This deletes the plane-trait adapter nothing serves: plane.rs (impl Plane / SessionPlane for StreamingPlane), twilio.rs (the second Twilio reader; ruling: TwilioEnvelope is the one), ulaw.rs (a PCM16 transcoder no served path uses: the telephony leg is locked to g711_ulaw both ways), oneshot.rs (the one-shot transcribe/tts dialect, outside the owner's three-dialect scope). Also gone: session.rs's VoiceSessionState/Pending (the adapter's state) and the test harness only the adapter used. UNREACHABILITY, mechanically: - The one production registration of StreamingPlane is the claims axis. There crates/busbar/src/root/registry.rs::plane_claims reads `(row.plane)().key()` (Plugin::key, lib.rs) and `row.claims` (PlaneMeta::CLAIMS, claims.rs), and nothing calls a Plane or SessionPlane method. - Outside busbar-plane-streaming, no crate names busbar_plane_streaming::{plane, twilio, ulaw, oneshot} or session::VoiceSessionState. The one exception was the tool-reply deadline and correlation key, which the previous commit moved to open_calls. The grep over crates/ is empty. - The served path (busbar-voice mount/runtime/topology) never reached the adapter. So no served byte and no metered unit moves. Non-$ under the ARCHITECT's S1c ruling. F25 (busbar-run/foldstreaming-f25-plane-tests.tsv, 60 rows): - 18 port: re-expressed in the previous commit on SessionPump and SessionUnit. - 19 covered: already asserted by the new modules' tests. - 23 retire, each with its reason in the map. Seven are the µ-law transcoder, two the one-shot meters, and the rest the adapter's decode/encode plumbing. The dialed-upstream lane fix (e15713578) keeps its plane half in driven_tests (the Gemini door is dialect 1). Its lane half belongs to the kernel's route at session open, and the driver session suite must pin it. The /v1/audio/* and BidiGenerateContent claims in claims.rs stay until S1a, which also gives the audio paths back to the llm plane. -- busbar-plane-streaming: the mint names no caller when the kernel lends no reference K2 lends OnPieceIn.caller_ref only on a node that has signing-key material. The mint ATTEMPT now takes it as an Option: when it is absent or empty, the OpenAI-Safety-Identifier header is omitted rather than guessed. driven_tests pins both forms. (cherry picked from commit 33cc324973c9c03570fda378820ab3e87bb089db) --- crates/busbar-plane-streaming/src/claims.rs | 2 +- .../src/codec/topology/twilio.rs | 79 +- crates/busbar-plane-streaming/src/driven.rs | 11 +- crates/busbar-plane-streaming/src/lib.rs | 38 +- crates/busbar-plane-streaming/src/oneshot.rs | 43 - .../busbar-plane-streaming/src/open_calls.rs | 20 +- crates/busbar-plane-streaming/src/plane.rs | 1456 -------------- crates/busbar-plane-streaming/src/session.rs | 115 +- .../src/session_unit.rs | 324 +++ .../busbar-plane-streaming/src/tests/codec.rs | 1763 ----------------- .../src/tests/driven_tests.rs | 8 +- .../src/tests/harness.rs | 249 --- .../busbar-plane-streaming/src/tests/mod.rs | 137 +- .../src/tests/open_calls_tests.rs | 2 +- .../src/tests/session_pump_tests.rs | 98 + .../src/tests/session_unit_tests.rs | 221 +++ .../src/tests/twilio.rs | 301 --- .../busbar-plane-streaming/src/tests/ulaw.rs | 63 - crates/busbar-plane-streaming/src/twilio.rs | 357 ---- crates/busbar-plane-streaming/src/ulaw.rs | 97 - .../tests/alloc_gate.rs | 48 +- .../tests/conformance.rs | 353 ---- .../tests/harness/mod.rs | 161 -- crates/busbar-voice/Cargo.toml | 2 +- crates/busbar-voice/src/bin/voice-conform.rs | 4 +- crates/busbar-voice/src/runtime/mod.rs | 2 +- .../src/tests/governed_binding_tests.rs | 2 +- 27 files changed, 835 insertions(+), 5121 deletions(-) delete mode 100644 crates/busbar-plane-streaming/src/oneshot.rs delete mode 100644 crates/busbar-plane-streaming/src/plane.rs create mode 100644 crates/busbar-plane-streaming/src/session_unit.rs delete mode 100644 crates/busbar-plane-streaming/src/tests/codec.rs delete mode 100644 crates/busbar-plane-streaming/src/tests/harness.rs create mode 100644 crates/busbar-plane-streaming/src/tests/session_unit_tests.rs delete mode 100644 crates/busbar-plane-streaming/src/tests/twilio.rs delete mode 100644 crates/busbar-plane-streaming/src/tests/ulaw.rs delete mode 100644 crates/busbar-plane-streaming/src/twilio.rs delete mode 100644 crates/busbar-plane-streaming/src/ulaw.rs delete mode 100644 crates/busbar-plane-streaming/tests/conformance.rs delete mode 100644 crates/busbar-plane-streaming/tests/harness/mod.rs diff --git a/crates/busbar-plane-streaming/src/claims.rs b/crates/busbar-plane-streaming/src/claims.rs index 5dc99abedc..7d1e5b3f66 100644 --- a/crates/busbar-plane-streaming/src/claims.rs +++ b/crates/busbar-plane-streaming/src/claims.rs @@ -12,7 +12,7 @@ //! //! * **Twilio Media Streams rides the WebSocket carrier.** Its wire is one JSON event per WebSocket //! message (`connected`/`start`/`media`/`mark`/`dtmf`/`stop`): the carrier frames the messages and -//! the event envelope is this plane's dialect ([`crate::twilio`], [`crate::ulaw`]). The design +//! the event envelope is this plane's dialect ([`crate::codec::topology::twilio`]). The design //! lists five transports and no telephony one. Twilio //! cannot present a busbar bearer or API key, so its claim authenticates under the //! `webhook-signature` alternative alone: the `X-Twilio-Signature` HMAC over the request URL, diff --git a/crates/busbar-plane-streaming/src/codec/topology/twilio.rs b/crates/busbar-plane-streaming/src/codec/topology/twilio.rs index 2c3f674980..be4083ede0 100644 --- a/crates/busbar-plane-streaming/src/codec/topology/twilio.rs +++ b/crates/busbar-plane-streaming/src/codec/topology/twilio.rs @@ -256,14 +256,77 @@ impl TwilioEnvelope { /// field order is fixed so the serialization is deterministic and byte-stable. #[must_use] pub fn encode_media(stream_sid: &str, mulaw: &[u8]) -> Vec { - let out = OutboundMedia { - event: "media", - stream_sid, - media: OutboundPayload { - payload: base64_encode(mulaw), - }, - }; - serde_json::to_vec(&out).expect("the outbound media envelope always serializes") + let mut out = Vec::new(); + Self::encode_media_into(&mut out, stream_sid, mulaw); + out + } + + /// The same envelope, rendered into a buffer the caller already holds. + /// + /// This is the shape the downlink writes in. The envelope is fixed (three members, in this + /// order) and everything about it except the identifier and the payload is known at compile + /// time. Building a document, a string for the payload, and then serializing would spend three + /// allocations per audio frame, and a call carries fifty frames a second in each direction. + /// The buffer is CLEARED, not appended to, so a caller may hand the same one back frame after + /// frame and pay for its growth once. An identifier the serializer would have to escape goes + /// through the serializer, which is the authority on those bytes. + pub fn encode_media_into(out: &mut Vec, stream_sid: &str, mulaw: &[u8]) { + out.clear(); + if !is_bare_json_string(stream_sid) { + let doc = OutboundMedia { + event: "media", + stream_sid, + media: OutboundPayload { + payload: base64_encode(mulaw), + }, + }; + out.extend_from_slice( + &serde_json::to_vec(&doc).expect("the outbound media envelope always serializes"), + ); + return; + } + const HEAD: &[u8] = br#"{"event":"media","streamSid":""#; + const MIDDLE: &[u8] = br#"","media":{"payload":""#; + const TAIL: &[u8] = br#""}}"#; + out.reserve( + HEAD.len() + stream_sid.len() + MIDDLE.len() + mulaw.len().div_ceil(3) * 4 + TAIL.len(), + ); + out.extend_from_slice(HEAD); + out.extend_from_slice(stream_sid.as_bytes()); + out.extend_from_slice(MIDDLE); + base64_encode_into(mulaw, out); + out.extend_from_slice(TAIL); + } +} + +/// Whether a string is its own JSON body: printable ASCII with neither character a JSON string +/// cannot carry raw. Everything else goes to the serializer. +fn is_bare_json_string(s: &str) -> bool { + s.bytes() + .all(|b| (0x20..0x7f).contains(&b) && b != b'"' && b != b'\\') +} + +/// Standard base64 (RFC 4648) with padding, appended to a buffer the caller holds: the same bytes +/// `busbar_contract::media::base64_encode` produces, without the string it would allocate. +fn base64_encode_into(data: &[u8], out: &mut Vec) { + const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for chunk in data.chunks(3) { + let b0 = u32::from(chunk[0]); + let b1 = u32::from(*chunk.get(1).unwrap_or(&0)); + let b2 = u32::from(*chunk.get(2).unwrap_or(&0)); + let n = (b0 << 16) | (b1 << 8) | b2; + out.push(ALPHABET[((n >> 18) & 63) as usize]); + out.push(ALPHABET[((n >> 12) & 63) as usize]); + out.push(if chunk.len() > 1 { + ALPHABET[((n >> 6) & 63) as usize] + } else { + b'=' + }); + out.push(if chunk.len() > 2 { + ALPHABET[(n & 63) as usize] + } else { + b'=' + }); } } diff --git a/crates/busbar-plane-streaming/src/driven.rs b/crates/busbar-plane-streaming/src/driven.rs index 6ed42065ce..581ec6242d 100644 --- a/crates/busbar-plane-streaming/src/driven.rs +++ b/crates/busbar-plane-streaming/src/driven.rs @@ -128,16 +128,17 @@ pub struct Attempt { /// The body's serializer text. pub fn mint_attempt( config: &SessionConfig, - caller: &str, + caller: Option<&str>, requested_ttl_secs: Option, ) -> Result { + let mut fields = vec![(FIELD_CONTENT_TYPE, JSON_CONTENT_TYPE.to_string())]; + if let Some(r) = caller.filter(|r| !r.is_empty()) { + fields.push((SAFETY_IDENTIFIER_HEADER, r.to_string())); + } Ok(Attempt { verb: "POST", target: CLIENT_SECRETS_PATH, - fields: vec![ - (FIELD_CONTENT_TYPE, JSON_CONTENT_TYPE.to_string()), - (SAFETY_IDENTIFIER_HEADER, caller.to_string()), - ], + fields, body: mint_request_body(clamped_ttl_secs(requested_ttl_secs), config)?, }) } diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index 3b0db51283..b388f2c5ad 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -3,24 +3,12 @@ //! //! ## What this crate is //! -//! An ADAPTER, in the same sense `busbar-plane-llm` is one: every method of the plane kind here is a -//! thin wrapper over a codec in this crate's own `codec` module — -//! the OpenAI Realtime and Gemini Live dialect readers/writers and the four-layer duplex/session IR -//! they meet in (`docs/design/BUSBAR-1.6.0.md` #18/#45, the four-layer IR section). No wire format -//! for those two dialects is written twice. -//! -//! Two things this crate DOES write itself, because nothing upstream provides them and the task this -//! crate exists for names them explicitly: -//! -//! * A minimal Twilio Media Streams JSON reader/writer ([`twilio`]) — `codec` has no dialect codec -//! for Twilio's own wire, and the one Twilio-shaped module in it ([`codec::topology::twilio`]) is -//! for the legacy engine's own use, not this crate's — this crate never reaches it (see the -//! crate-root dependency note below). So this crate's Twilio reader/writer is written from the -//! wire shape alone, independently, and is NOT a copy of that module. -//! * A standard G.711 µ-law ↔ PCM16 transform ([`ulaw`]) — `codec` only carries the byte-rate -//! bookkeeping for the format (`codec::ir::media::AudoFormat`), not an actual sample transcoder; -//! its own doc comments call the transcode an unimplemented "seam...armed only when a lane -//! declares it." This crate is the lane that declares it. +//! The plane's door ([`door`]) and everything it answers with: the Statement tail and the +//! per-generation snapshot, the driver answers for the one-request doors ([`driven`]), and one live +//! session as the driver serves it ([`session_unit`] over the frame pump [`session_pump`]). The +//! dialect readers and writers (OpenAI Realtime, Gemini Live) and the four-layer duplex/session IR +//! they meet in are this crate's own `codec` module (`docs/design/BUSBAR-1.6.0.md` #18/#45); the +//! Twilio Media Streams envelope is [`codec::topology::twilio`]. No wire format is written twice. //! //! ## What this crate is not //! @@ -50,12 +38,9 @@ //! //! ## What it holds across calls //! -//! Nothing of its own. The plane value itself ([`StreamingPlane`]) is immutable and carries only its -//! configured upstream list. What a session needs across frames — the codec's per-connection state, -//! the negotiated dialect, the counters this crate derives itself (`audio_seconds_in`, `tool_calls`), -//! and the one pending IR event a two-step ingress/egress or decode/encode pair needs to hand across -//! — lives in the kernel-held [`busbar_contract::plane::PlaneSessionState`], via -//! [`session::VoiceSessionState`]. +//! The plane value ([`StreamingPlane`]) is immutable and carries only its configured upstream list. +//! A door instance holds its live generations; a live session's state (the codec's decode state, the +//! open turn's counters, the session's cumulative units and record) is a [`session_unit::SessionUnit`]. #![forbid(unsafe_code)] #![deny(missing_docs)] @@ -72,18 +57,15 @@ pub mod door; pub mod driven; pub mod governed; pub mod meta; -pub mod oneshot; pub mod open_calls; -pub mod plane; pub mod provider; pub mod register; pub mod session; pub mod session_params; pub mod session_pump; +pub mod session_unit; pub mod session_row; pub mod tools; -pub mod twilio; -pub mod ulaw; #[cfg(test)] mod tests; diff --git a/crates/busbar-plane-streaming/src/oneshot.rs b/crates/busbar-plane-streaming/src/oneshot.rs deleted file mode 100644 index c7e72ab7de..0000000000 --- a/crates/busbar-plane-streaming/src/oneshot.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! The one-shot transcribe/TTS surface — a provisional wire shape, documented as such. -//! -//! `busbar-voice`'s own IR is duplex/session-only: per its crate-root documentation, Plane 4 is "the -//! duplex / live-voice plane," and nothing in `crate::codec::ir` models a request with no session at -//! all. So there is no dedicated wire format anywhere in this crate's dependency closure to decode a -//! one-shot transcribe or text-to-speech request against — this is one of the places the task this -//! crate exists for names as a genuine gap rather than something to guess past silently. -//! -//! What this module does instead: it treats a one-shot request as [`busbar_contract::plane::Ingress::OneShot`] -//! — a single `IrClientEvent`/`IrServerEvent`-shaped exchange with no session state, in the same -//! event vocabulary the duplex dialects use, per this crate's documented plane-level decision. The -//! wire shape it decodes for the request body is the widely-used convention every OpenAI-Realtime- -//! adjacent provider's REST transcription/speech endpoints share (a JSON body carrying an `input` -//! or `model` field for TTS; an opaque audio body for transcription) — PROVISIONAL, stated as such: -//! it is not derived from any codec this crate depends on, and a future pass that finds a more -//! precise shape (e.g. multipart transcription uploads) should replace it without ceremony. - -use serde_json::Value; - -/// The text a one-shot text-to-speech request asks to be spoken, if the body is the provisional -/// JSON shape (`{"input": "...", ...}`) and the field is a string. -/// -/// Used as this plane's estimate of the `text_tokens` class for a `tts` unit — there is no upstream -/// usage report to read the true count from before the audio comes back, so the input text length is -/// the honest, documented estimate rather than a fabricated number. -#[must_use] -pub fn tts_input_text(body: &[u8]) -> Option { - let value: Value = serde_json::from_slice(body).ok()?; - value - .get("input") - .and_then(Value::as_str) - .map(str::to_string) -} - -/// The voice a one-shot text-to-speech request asks for, if named in the provisional JSON shape. -#[must_use] -pub fn tts_voice(body: &[u8]) -> Option { - let value: Value = serde_json::from_slice(body).ok()?; - value - .get("voice") - .and_then(Value::as_str) - .map(str::to_string) -} diff --git a/crates/busbar-plane-streaming/src/open_calls.rs b/crates/busbar-plane-streaming/src/open_calls.rs index 0eb6861f1a..6a737b030a 100644 --- a/crates/busbar-plane-streaming/src/open_calls.rs +++ b/crates/busbar-plane-streaming/src/open_calls.rs @@ -27,14 +27,28 @@ use busbar_contract::ids::{CorrelationRef, CorrelationValue, UnitKey}; use busbar_contract::reply::{AwaitingReplies, NotWaiting}; use busbar_contract::Millis; +/// The fact key a tool call's reply wait correlates on. +/// +/// Public because it is half of a correlation and the other half is entered somewhere else: the leg +/// this plane plans names this key, and whatever holds the waiting table has to name the same one +/// for a reply to match. Two spellings of one key is exactly the drift that makes a wait unmatchable +/// while both sides look right on their own. +pub const FACT_TOOL_CORRELATION: &str = crate::meta::FACT_CALL_ID; + +/// How long a tool call's reply leg waits, in seconds. +/// +/// Declared here, beside the leg that names it, so the table that has to end an unanswered call at +/// this deadline reads the plane's own figure rather than restating it. +pub const TOOL_REPLY_DEADLINE_SECS: u32 = 30; + /// The leg a provider-pushed tool call plans: deliver it to the client, and wait for the answer. /// /// The key and the deadline are the plane's own declarations, read from it rather than restated. A /// second spelling of either here would be a wait entered under one key and answered under another, /// with both files looking correct on their own. pub const TOOL_REPLY_LEG: ClientMode = ClientMode::AwaitReply { - correlation_key: crate::plane::FACT_TOOL_CORRELATION, - deadline_secs: crate::plane::TOOL_REPLY_DEADLINE_SECS, + correlation_key: FACT_TOOL_CORRELATION, + deadline_secs: TOOL_REPLY_DEADLINE_SECS, }; /// Why a client's tool reply woke nothing. @@ -256,7 +270,7 @@ impl NodeCalls { /// the key the plane's leg names — the one pair `planned` enters and `replied` answers. fn tool_call(call_id: &str) -> CorrelationRef<'_> { CorrelationRef { - fact_key: crate::plane::FACT_TOOL_CORRELATION, + fact_key: FACT_TOOL_CORRELATION, value: CorrelationValue::Str(call_id), } } diff --git a/crates/busbar-plane-streaming/src/plane.rs b/crates/busbar-plane-streaming/src/plane.rs deleted file mode 100644 index 164257b6a9..0000000000 --- a/crates/busbar-plane-streaming/src/plane.rs +++ /dev/null @@ -1,1456 +0,0 @@ -//! The `Plane`/`SessionPlane` implementation. -//! -//! Every method here is a thin adapter over `crate::codec::ir`'s shared duplex codec -//! ([`OpenAiRealtimeCodec`], [`GeminiLiveCodec`]), this crate's own Twilio reader/writer -//! ([`crate::twilio`]) and its own µ-law transform ([`crate::ulaw`]). None of the three inputs the -//! design brief names is skipped: a turn is the unit (opened on the first audio frame of a session, -//! closed on the upstream's `response.done` usage report or an upstream error); a provider tool call -//! decodes to `Progress::OneShot`; the interrupt fact and the pacing fact are both written where the -//! crate doc comment on [`crate::meta`] says they are. -//! -//! # Assumptions and simplifications, stated once -//! -//! Three of the entries below are no longer this module's own reading. They were raised as findings, -//! carried to the architecture's inventory row for this plane, and **ratified there** — so what -//! follows restates a decision rather than declaring one, and a change to any of the three is a -//! change to that row first and to this file second. They are: only the first decoded IR event per -//! wire frame is acted on; uplink audio is assumed PCM16 for the `audio_seconds_in` estimate on the -//! two WS dialects; and model-emitted text in a duplex turn prices under `text_tokens_out`, an -//! output class, never the input one. -//! -//! - **HTTP/WS path arrives as a transport fact**, under the kernel's own reserved key -//! (`busbar_contract::transport::facts::PATH`), used to resolve which one-shot operation or which -//! duplex dialect a session's Unit 0 is. This used to be a guess, and both transports now declare -//! the key they publish it under. -//! - **Every decoded IR event of a wire frame is folded, in order.** Both `read_up`/`read_down` -//! return `Vec<..Event>` (one wire message can map to 0..n IR events); `decode_response` walks all -//! of them, letting each update the session's accumulation state, and returns the one meaningful -//! outcome. A tool call is the frame that genuinely decodes to more than one event — Gemini's -//! `toolCall` is `CallOpen`→`CallArgs`→`CallClose` in one frame, and OpenAI's -//! `…arguments.done` frame carries the complete arguments and the close together — so acting on -//! only the first dropped the `CallClose` that dispatches the call. Every other reference-dialect -//! frame emits at most one non-state event, so the fold is identical to first-only for them. -//! - **The uplink audio format is assumed PCM16 for the `audio_seconds_in` estimate on the two WS -//! dialects.** `DecodeState` only tracks the NEGOTIATED OUTPUT format (for the downlink barge-in -//! truncate math); there is no equivalent uplink format tracked anywhere in this plane's closure, -//! so the ms-estimate this plane derives for its own `audio_ms_in` counter assumes the Realtime -//! default. Twilio's own uplink is unambiguous (G.711 µ-law, priced from the raw payload before this -//! plane's `encode_ingress_frame` transforms it), so this assumption is scoped to the two WS -//! dialects only. -//! - **A provider tool call is dispatched with its ACCUMULATED ARGUMENTS, on `CallClose`.** -//! `CallOpen` announces the call (and increments [`crate::session::TurnCounters::tool_calls`]) but -//! mints nothing on its own — a tool call dispatched with no arguments is a different call. The -//! streamed `CallArgs` fragments are accumulated on the session's own codec state -//! ([`crate::codec::ir::codec::DecodeState::push_call_args`], which appends a fragment and -//! replaces on a whole object, so the OpenAI done-repeat and the Gemini atomic call both land the -//! right bytes), and `CallClose` mints the `tool_call` `OneShot` carrying the tool name and those -//! arguments as its body, correlated by the wire call id. That is when the call becomes visible, -//! priced and audited as its own unit at its `tool_call` operation class. -//! - **`encode_response` is a passthrough of bytes `decode_response` already rendered**, mirroring -//! `busbar-plane-admin`'s pattern. `decode_response` reads the open turn's own client dialect off -//! `Ctx::session()`'s declared `dialect` session fact (the one fact this plane's `SESSION_FACTS` -//! declares) and renders the client-shaped bytes immediately. The downlink half of -//! [`crate::session::Pending`] is gone with the reason it existed: a step after decode can now -//! read what decode determined off the unit's own draft facts. -//! - **Verify's upstream pick for a fresh session is a documented default, not a policy.** A session -//! whose own arriving dialect is one of the two duplex-upstream dialects dials the SAME dialect's -//! configured upstream when one exists; otherwise (Twilio, or no matching upstream configured) it -//! dials this plane's FIRST configured upstream. Picking among several qualifying upstreams is the -//! trust unit's and the ranking hooks' business in every other plane in this workspace; this default -//! exists only so a unit test has something deterministic to assert against. -//! - **A provider tool call's destination is `Client(AwaitReply)`.** The design names two permitted -//! shapes (`Client(AwaitReply)` or `NestedPlane(mcp)`); wiring the second requires the `mcp` plane's -//! own operation classes, which are outside this crate's dependency graph. `Client(AwaitReply)` is -//! implemented; a future pass that wants server-side tool EXECUTION (rather than delivering the call -//! to whatever session member answers it) is the one that should add the `NestedPlane` path. - -use busbar_contract::bounded::{FactValue, Facts, Ir, ScratchBytes}; -use busbar_contract::dest::{ - ClientMode, DestinationFacts, EgressBody, RoutePlan, VerifiedDestination, -}; -use busbar_contract::ids::{AdminVerbId, CorrelationRef, CorrelationValue, OpClassId, SchemeKey}; -use busbar_contract::kinds::{ContentFacts, CredentialLocator, PlaneFacts}; -use busbar_contract::plane::{ - Ingress, Plane, PlaneSessionState, Progress, Response, SessionPlane, UnitDraft, -}; -use busbar_contract::unit::{ - AdmitFacts, AuditFacts, Ctx, FinishClass, Refusal, ResourceLocator, ScopeFacts, Unit, UnitEnd, - UsageLocator, UsageLocators, -}; -use busbar_contract::wire::{Decode, DiscardCode, Encode, Frame, FrameCursor, TransportEnvelope}; - -use crate::codec::ir::control::IrDuplexControl; -use crate::codec::ir::event::{IrClientEvent, IrServerEvent}; -use crate::codec::ir::media::{AudioFormat, IrAudioFrame, IrAudioRef, UpDown}; -use crate::codec::ir::tool::IrDuplexTool; -use crate::codec::ir::{ - DecodeState, DuplexReader, DuplexWriter, GeminiLiveCodec, OpenAiRealtimeCodec, WireRef, -}; - -use crate::claims::{self, Dialect}; -use crate::meta; -use crate::session::{Pending, VoiceSessionState}; -use crate::{twilio, ulaw, StreamingPlane}; - -/// The transport fact key the request path is published under. -/// -/// The kernel's own reserved key, named rather than assumed. It used to be this crate's guess, and -/// the module header said so; the header's first assumption is retired with it. -const FACT_PATH: &str = busbar_contract::transport::facts::PATH; - -/// The fact key a tool call's provider-origin `OneShot` correlates on. -/// -/// Public because it is half of a correlation and the other half is entered somewhere else: the leg -/// this plane plans names this key, and whatever holds the waiting table has to name the same one -/// for a reply to match. Two spellings of one key is exactly the drift that makes a wait unmatchable -/// while both sides look right on their own. -pub const FACT_TOOL_CORRELATION: &str = meta::FACT_CALL_ID; - -/// How long a tool call's reply leg waits, in seconds. -/// -/// Declared here, beside the leg that names it, so the table that has to end an unanswered call at -/// this deadline reads the plane's own figure rather than restating it. -pub const TOOL_REPLY_DEADLINE_SECS: u32 = 30; - -/// Both duplex dialects' reader, boxed so the same call site works for either without a generic -/// parameter leaking into every method signature. Cheap: both codecs are zero-sized. -fn reader_for(dialect: Dialect) -> Box { - match dialect { - Dialect::GeminiLive => Box::new(GeminiLiveCodec), - _ => Box::new(OpenAiRealtimeCodec), - } -} - -/// See [`reader_for`]. -fn writer_for(dialect: Dialect) -> Box { - match dialect { - Dialect::GeminiLive => Box::new(GeminiLiveCodec), - _ => Box::new(OpenAiRealtimeCodec), - } -} - -/// The client→server IR event ONE client wire frame carries, read in the client's own dialect. -/// -/// Twilio's carrier `media` payload is widened from µ-law to PCM16 HERE, the documented transform -/// seam (see [`encode_ingress_frame`]'s own note). `None` when the frame carries no event to relay -/// (a lifecycle frame, or one this reader does not model). -/// -/// Metering is DELIBERATELY not done here: the frame that OPENS a turn is metered on its draft at -/// decode (`open_or_relay`'s `FACT_AUDIO_MS_IN`), and every later frame at the relay seam in -/// [`encode_ingress_frame`]; this seam only shapes bytes, so neither path's count leaks into it. -/// -/// [`encode_ingress_frame`]: StreamingPlane::encode_ingress_frame -fn client_event_from_wire( - client_dialect: Dialect, - wire: &[u8], - state: &mut VoiceSessionState, -) -> Result, Encode> { - match client_dialect { - Dialect::TwilioMediaStreams => { - let event = match twilio::decode(wire) { - Ok(event) => event, - Err(twilio::TwilioError::UnknownEvent(_)) => return Ok(None), - Err(_) => return Err(Encode::Unrepresentable), - }; - match event { - twilio::TwilioEvent::Media { payload, .. } => { - let pcm = ulaw::decode_frame(&payload); - Ok(Some(IrClientEvent::AudioFrame(IrAudioFrame { - dir: UpDown::Up, - seq: state.codec.next_up_seq(), - media: bytes::Bytes::from(pcm), - origin: IrAudioRef::default(), - }))) - } - _ => Ok(None), - } - } - _ => { - let reader = reader_for(client_dialect); - Ok(reader - .read_up_ref(WireRef(wire), &mut state.codec) - .into_iter() - .next()) - } - } -} - -/// Re-frame ONE client→server event onto the upstream dialect's wire and park the result in the -/// arena — the single dialect-writer path every relayed client frame travels, whether it OPENS the -/// turn (Unit 0's egress body, via [`encode_egress`]) or is a LATER frame of it (via -/// [`encode_ingress_frame`]). `None` when the upstream dialect has no verb for the concept (the -/// cross-dialect drop rows): nothing is relayed, the same answer a fully-handled lifecycle frame -/// gives. -/// -/// [`encode_egress`]: StreamingPlane::encode_egress -/// [`encode_ingress_frame`]: StreamingPlane::encode_ingress_frame -fn write_up_to_arena<'u>( - upstream_dialect: Dialect, - event: IrClientEvent, - state: &mut VoiceSessionState, - ctx: &Ctx<'u>, -) -> Result>, Encode> { - let writer = writer_for(upstream_dialect); - let Some(out) = writer.write_up(event, &mut state.codec) else { - return Ok(None); - }; - ctx.arena() - .alloc_bytes(&out.0) - .map(Some) - .map_err(|_| Encode::ScratchExhausted) -} - -impl StreamingPlane { - /// The upstream a fresh session's Unit 0 dials, given the dialect it arrived on. See the module - /// doc comment's "verify's upstream pick" note. - fn default_upstream(&self, arriving: Dialect) -> Option<&'static crate::Upstream> { - if arriving.is_duplex_upstream() { - if let Some(u) = self.upstream_for_dialect(arriving) { - return Some(u); - } - } - self.upstreams().first() - } -} - -impl Plane for StreamingPlane { - fn decode_ingress<'u>( - &self, - frames: &mut FrameCursor<'u>, - st: Option<&mut PlaneSessionState>, - ctx: &Ctx<'u>, - ) -> Result, Decode> { - // Dispatch on the SESSION's own bound dialect, not the literal transport key: a claim's - // selector (`claims::dialect_for`) is what names the dialect, and more than one dialect can - // share a transport key in principle (the two WS dialects already do). A one-shot HTTP - // operation carries no session state at all, which is how the two shapes are told apart here. - match st { - None => decode_one_shot(frames, ctx), - Some(halfbox) => { - let state = halfbox - .get_mut::() - .ok_or(Decode::MissingDeclaredFact)?; - let dialect = state.dialect.ok_or(Decode::MissingDeclaredFact)?; - match dialect { - Dialect::TwilioMediaStreams => decode_twilio_frame(frames, state, ctx), - Dialect::OpenaiRealtime | Dialect::GeminiLive => { - decode_ws_frame(frames, state, dialect, ctx) - } - Dialect::OneShotTranscribe | Dialect::OneShotTts => { - Err(Decode::UnsupportedOperation) - } - } - } - } - } - - fn encode_egress<'u>( - &self, - u: &Unit<'u>, - dest: &VerifiedDestination, - st: Option<&mut PlaneSessionState>, - ctx: &Ctx<'u>, - ) -> Result, Encode> { - // Unit 0 of a DUPLEX session: the first frame IS the egress body (the session-shapes section - // of the architecture doc — "Unit 0's EgressBody is the first upstream frame"). Its body - // carries the client's opening wire frame verbatim (stored by `open_or_relay`), and it must - // reach the provider in the UPSTREAM's own dialect — the SAME shaping `encode_ingress_frame` - // gives every LATER frame of the turn. Rendering it through the upstream writer HERE is what - // makes a session-opening `session.update` (voice/instructions/tools/turn_detection) actually - // apply, and a first audio frame arrive as a dialect-shaped `input_audio_buffer.append` - // rather than raw PCM; a byte pass-through of the raw opening frame silently applied none of - // it. A one-shot transcribe/tts unit has no session state: its body was already decoded in - // the upstream's HTTP request shape, so it passes straight through unchanged. - let session = st.and_then(|halfbox| halfbox.get_mut::()); - let body = match session { - Some(state) => { - let client_dialect = client_dialect_from_session(ctx) - .or(state.dialect) - .ok_or(Encode::Poisoned)?; - let upstream_dialect = upstream_dialect_for(self, dest); - // The opening frame is re-read off the unit's OWN body rather than the session stash: - // the body is this unit's authoritative frame, whereas `state.pending` is a per-half - // scratch slot the next decoded frame overwrites — reading it here would risk shaping - // a later frame's event into Unit 0. Metering is not repeated: the opening frame's - // audio was counted on its draft at decode. - match client_event_from_wire(client_dialect, u.body().body(), state)? { - Some(event) => write_up_to_arena(upstream_dialect, event, state, ctx)? - // The upstream dialect has no verb for this opening concept: nothing is - // relayed, the same answer `encode_ingress_frame` gives a dropped later frame. - .unwrap_or_else(|| ScratchBytes::new(&[])), - None => ScratchBytes::new(&[]), - } - } - None => ctx - .arena() - .alloc_bytes(u.body().body()) - .map_err(|_| Encode::ScratchExhausted)?, - }; - Ok(EgressBody { - envelope: TransportEnvelope::default(), - body, - auth: SchemeKey::new(claims::SCHEME), - }) - } - - fn encode_ingress_frame<'u>( - &self, - _u: &Unit<'u>, - f: &Frame, - dest: &VerifiedDestination, - st: Option<&mut PlaneSessionState>, - ctx: &Ctx<'u>, - ) -> Result>, Encode> { - let state = st - .ok_or(Encode::Poisoned)? - .get_mut::() - .ok_or(Encode::Poisoned)?; - // The client's own dialect off the session fact first, exactly as `decode_response` asks - // it: this seam is scoped to the DESTINATION, so the half it is handed is not guaranteed - // to be the one the client's arriving frames bound a dialect on. - let client_dialect = client_dialect_from_session(ctx) - .or(state.dialect) - .ok_or(Encode::Poisoned)?; - let upstream_dialect = upstream_dialect_for(self, dest); - - let client_event = match client_dialect { - Dialect::TwilioMediaStreams => { - // The documented seam: the µ-law -> PCM16 transform happens HERE, never at decode. - let event = match twilio::decode(f.bytes.as_slice()) { - Ok(event) => event, - // The same forward-compatibility rule the decode step takes: an event this - // reader does not model carries no audio to relay, and is not a reason to end - // the call. - Err(twilio::TwilioError::UnknownEvent(_)) => return Ok(None), - Err(_) => return Err(Encode::Unrepresentable), - }; - match event { - twilio::TwilioEvent::Media { payload, .. } => { - // Priced from the raw carrier payload, before the transform below widens - // it: what the caller spoke is µ-law on this dialect's wire. - state.turn.admit_audio(AudioFormat::G711Ulaw, payload.len()); - let pcm = ulaw::decode_frame(&payload); - IrClientEvent::AudioFrame(IrAudioFrame { - dir: UpDown::Up, - seq: state.codec.next_up_seq(), - media: bytes::Bytes::from(pcm), - // A carrier media frame names no conversation item. - origin: IrAudioRef::default(), - }) - } - // Lifecycle events (`connected`/`start`/`mark`/`stop`) carry no audio and are - // fully handled at decode; nothing is relayed onward for them. - _ => return Ok(None), - } - } - _ => match state.pending.take() { - Some(Pending::Ingress(ev)) => ev, - // Nothing stashed. Either decode answered the frame fully (a control event with - // nothing to relay) or the stash was left on another half of the session, which is - // not a reason to drop a caller's audio on the floor: the frame is re-read here. - // The reader is the one the stash existed to avoid calling twice, so it is called - // once either way. - None => { - let reader = reader_for(client_dialect); - let events = reader.read_up_ref(WireRef(f.bytes.as_slice()), &mut state.codec); - match events.into_iter().next() { - Some(ev) => ev, - None => return Ok(None), - } - } - }, - }; - - // The uplink meter is taken HERE, at the seam that relays the frame to the provider, and - // not at decode: decode runs against the client half of the session and this step and the - // response step run against the destination's, so a figure counted at decode was read back - // from a half it was never written to and every second the caller spoke metered at zero. - // Counting where the audio actually leaves also means audio this node never relayed is - // audio nobody is charged for. - // Twilio's own uplink was counted from its carrier payload above, before the transform. - if client_dialect != Dialect::TwilioMediaStreams { - if let IrClientEvent::AudioFrame(f) = &client_event { - // See the module doc comment: the uplink format is assumed PCM16 for this estimate. - state.turn.admit_audio(AudioFormat::Pcm16, f.media.len()); - } - } - - // The upstream dialect may have NO verb for this concept (the cross-dialect drop rows): then - // nothing is relayed — the same answer a lifecycle frame handled fully at decode gives. This - // is the SAME dialect-writer path Unit 0's opening frame takes in `encode_egress`. - write_up_to_arena(upstream_dialect, client_event, state, ctx) - } - - fn decode_response<'u>( - &self, - frames: &mut FrameCursor<'u>, - dest: &VerifiedDestination, - st: Option<&mut PlaneSessionState>, - ctx: &Ctx<'u>, - ) -> Result, Decode> { - // A one-shot operation has no session, by construction: `decode_one_shot` admitted it - // without one and there is none to hand back here. Requiring one turned every transcribe - // and every text-to-speech answer into a decode refusal, so the unit that WAS admitted - // never saw its own answer and never metered anything at all. - let Some(halfbox) = st else { - return decode_one_shot_response(frames, ctx); - }; - let state = halfbox - .get_mut::() - .ok_or(Decode::MissingDeclaredFact)?; - let upstream_dialect = upstream_dialect_for(self, dest); - let client_dialect = client_dialect_from_session(ctx).unwrap_or(upstream_dialect); - - let frame = frames.next_frame().ok_or(Decode::Malformed)?; - // The reader is handed the frame WHERE IT IS. It parses the bytes and keeps none of them, so - // buying them a second home first bought nothing — and a duplex session reads fifty frames a - // second in each direction for the length of a call. - let reader = reader_for(upstream_dialect); - let events = reader.read_down_ref(WireRef(frame.bytes.as_slice()), &mut state.codec); - // ALL events of the frame are folded, not just the first. A tool call is the one wire frame - // that genuinely decodes to more than one IR event: Gemini delivers `CallOpen`→`CallArgs`→ - // `CallClose` in a single `toolCall` frame, and OpenAI's `…arguments.done` frame carries the - // complete arguments AND the close together. Acting on only the first event dropped the - // `CallClose` that dispatches the call, so its executor never ran — the defect this fold - // fixes. Each event updates the session's accumulation state; the meaningful outcome (the - // `OneShot` a `CallClose` mints) is the one returned. Every reference dialect emits at most - // one non-state event per frame, so this is identical to acting on the first for every - // non-tool frame. - let mut outcome = Progress::Discard { - reason: DiscardCode::Unsupported, - }; - for event in events { - match progress_from_server_event(event, state, client_dialect, ctx)? { - Progress::Discard { .. } => {} - progress => outcome = progress, - } - } - Ok(outcome) - } - - fn encode_response<'u>( - &self, - r: &Response<'u>, - _st: Option<&mut PlaneSessionState>, - ctx: &Ctx<'u>, - ) -> Result, Encode> { - // See the module doc comment's `encode_response` note: `decode_response` already rendered - // client-dialect bytes into `r.ir`; this is the passthrough. - ctx.arena() - .alloc_bytes(r.ir.body()) - .map_err(|_| Encode::ScratchExhausted) - } - - fn encode_refusal<'u>( - &self, - refusal: &Refusal, - _draft: Option<&UnitDraft<'u>>, - _st: Option<&PlaneSessionState>, - ctx: &Ctx<'u>, - ) -> Result, Encode> { - let (code, message) = refusal_render(refusal.reason); - let event = IrServerEvent::Error { - code: code.to_string(), - message: message.to_string(), - }; - // A refusal is rendered in the OpenAI Realtime shape unconditionally: `st` is deliberately - // `&PlaneSessionState` (immutable — a refusal never advances codec state, per the trait's own - // doc comment), so this plane cannot read back which dialect a not-yet-open session even - // claimed. `error` is one of the few wire shapes both duplex dialects converge on closely - // enough that a client library for either can surface it; a fully dialect-correct refusal - // would need the immutable half of the state to still carry the negotiated dialect, which it - // does today (`VoiceSessionState::dialect`) but this method has no path to it before Unit 0 - // completes. Flagged rather than guessed past. - // The write seam threads the session's decode state (it holds what framing cannot answer - // per-event); a refusal reaches none of the session's state here, and needs none — it is one - // self-contained frame with nothing accumulated behind it, so it is framed against a fresh one. - let bytes = OpenAiRealtimeCodec - .write_down(event, &mut DecodeState::default()) - .ok_or(Encode::Unrepresentable)? - .0; - ctx.arena() - .alloc_bytes(&bytes) - .map_err(|_| Encode::ScratchExhausted) - } - - fn encode_end<'u>( - &self, - _u: &Unit<'u>, - _end: &UnitEnd, - _st: Option<&mut PlaneSessionState>, - _ctx: &Ctx<'u>, - ) -> Result>, Encode> { - // A turn's own ending is always rendered as a `Progress::Terminal` `Response` through - // `encode_response` (the upstream's `response.done`/error IS the ending); there is no further - // trailer this dialect writes at the unit's own close. - Ok(None) - } - - fn authenticate<'u>(&self, u: &Unit<'u>, _ctx: &Ctx<'u>) -> CredentialLocator { - // Twilio, OpenAI Realtime and Gemini Live all authenticate once at session open and cache - // the result for the session's life (`claims::Dialect::authenticates_from_session`); the two - // one-shot HTTP operations present a credential on the one request they are. The dialect is - // the draft's own fact, sealed onto the unit by the kernel, so this step reads what decode - // determined rather than a session fact that a one-shot unit does not have at all. - CredentialLocator { - narrowing: None, - from_session: draft_dialect(u).is_some_and(Dialect::authenticates_from_session), - } - } - - fn verify<'u>(&self, u: &Unit<'u>, ctx: &Ctx<'u>) -> DestinationFacts { - if u.op().as_str() == "tool_call" { - // The leg names the key; the value is the call identifier `decode_response` already - // minted on this unit's `correlation_out`, in the unit's own arena, as itself. This - // plane no longer folds that identifier into a number to get it past a `'static` - // bound — two calls open at once is the ordinary shape of a turn that asks for two - // tools, and it is the pair a fold is least entitled to confuse. - return DestinationFacts::Client { - selector: "*", - mode: ClientMode::AwaitReply { - correlation_key: FACT_TOOL_CORRELATION, - deadline_secs: TOOL_REPLY_DEADLINE_SECS, - }, - }; - } - let session_upstream_count = ctx.session().map(|s| s.upstream_count()).unwrap_or(0); - if session_upstream_count > 0 { - // Route to the upstream this session DIALED at open, not the first configured one. Unit 0 - // (the fresh branch below) picked `default_upstream(arriving)` off the arriving dialect; - // a paired turn must name that SAME upstream by its real config index and lane, or a - // session that dialed the second-declared provider — a gemini-live client where - // openai-realtime is declared first — has every turn metered on the first provider's - // lane: the wrong provider's money. The session's own bound client dialect is the arriving - // dialect Unit 0 chose from, read back off the declared session fact (falling back to the - // unit's own draft fact for the same reason `default_upstream` reads it there). - let dialed = client_dialect_from_session(ctx).or_else(|| draft_dialect(u)); - let idx = dialed - .filter(|d| d.is_duplex_upstream()) - .and_then(|d| self.upstreams().iter().position(|up| up.dialect == d)) - .unwrap_or(0); - let lane = self - .upstreams() - .get(idx) - .map(|up| up.lane) - .unwrap_or(busbar_contract::ids::LaneId::new("streaming")); - return DestinationFacts::SessionUpstream { - upstream: busbar_contract::ids::UpstreamIdx(u8::try_from(idx).unwrap_or(0)), - stream: None, - lane, - }; - } - // The dialect the decode step named, off the unit's own sealed draft facts. - let arriving = draft_dialect(u).unwrap_or(Dialect::OpenaiRealtime); - match self.default_upstream(arriving) { - Some(up) => DestinationFacts::Upstream { - transport: claims::WS_TRANSPORT, - address: busbar_contract::UpstreamAddress::socket(up.host), - lane: up.lane, - }, - None => DestinationFacts::Upstream { - transport: claims::WS_TRANSPORT, - address: busbar_contract::UpstreamAddress::socket(""), - lane: busbar_contract::ids::LaneId::new("streaming"), - }, - } - } - - fn approve<'u>(&self, u: &Unit<'u>, _ctx: &Ctx<'u>) -> ScopeFacts { - let mut resources = busbar_contract::bounded::BoundedVec::new(); - let _ = resources.push(ResourceLocator { - kind: "streaming_operation", - name: u.op().as_str(), - }); - ScopeFacts { resources } - } - - fn admit<'u>(&self, _u: &Unit<'u>, _ctx: &Ctx<'u>) -> AdmitFacts { - // No client-located lane name and no client response ceiling on a duplex session: the lane - // is this plane's own configuration (`Upstream::lane`), and the response is unbounded audio, - // not a single JSON body the kernel would clamp. - AdmitFacts::default() - } - - fn route<'u>(&self, _u: &Unit<'u>, _ctx: &Ctx<'u>) -> RoutePlan { - // `verify` already named the one destination this unit reaches; there is no second leg. - RoutePlan::default() - } - - fn meter<'u>(&self, u: &Unit<'u>, r: &Response<'u>, _ctx: &Ctx<'u>) -> UsageLocators { - let mut lines = busbar_contract::bounded::BoundedVec::new(); - // THE CLASS IS THE DECLARED SYMBOL, NEVER A RE-SPELLING OF IT. Each of these is the const - // `meta` declares in `METER_CLASSES`, so the class a count is EMITTED under and the class - // the plane DECLARED are one value the compiler checks — not two strings that agree today. - // A typo in a literal here emits a count under a class no card can name: silently unpriced, - // or a boot refusal under #77(5), and in neither case anything that points at this line. - // `cached_tokens` is NOT here: it is a subset of the input token classes above, not a - // billable class beside them (architect ruling #71 — see `meta::CLASS_CACHED_TOKENS`'s doc). - // Pricing it too would double-bill the same cached input; the figure is attribution only, - // carried on the `cached_tokens` fact, never on a meter line. - let classes = [ - (meta::FACT_AUDIO_TOKENS_IN, meta::CLASS_AUDIO_TOKENS_IN), - (meta::FACT_AUDIO_TOKENS_OUT, meta::CLASS_AUDIO_TOKENS_OUT), - (meta::FACT_TEXT_TOKENS_IN, meta::CLASS_TEXT_TOKENS_IN), - (meta::FACT_TEXT_TOKENS_OUT, meta::CLASS_TEXT_TOKENS_OUT), - ]; - // A duplex turn's figures all come off the upstream's usage report, which is the answer. A - // one-shot request's input figure comes off the request, which decode read and put on the - // unit. The answer is asked first either way: a figure the destination confirmed beats one - // this node estimated, and the unit's own is only reached where the answer reported none. - let reported = |key: &str| match r.facts.get(key) { - Some(value) => Some(value), - None => u.draft_facts().get(key), - }; - for (fact_key, class) in classes { - if let Some(FactValue::Int(v)) = reported(fact_key) { - let _ = lines.push(UsageLocator { - class, - location: None, - quantity: u64::try_from(v).ok(), - lane: None, - }); - } - } - // The two halves of one duration, ADDED rather than one preferred over the other: the frame - // that opened the turn stated its own milliseconds on the draft (it never reaches the relay - // seam), and every frame after it is counted at that seam and reported on the answer. This - // is not a figure the destination confirmed, so the "answer wins" rule above does not - // govern it — taking the answer alone dropped the opening frame's audio every turn. - let opening = match u.draft_facts().get(meta::FACT_AUDIO_MS_IN) { - Some(FactValue::Int(ms)) => Some(ms), - _ => None, - }; - let relayed = match r.facts.get(meta::FACT_AUDIO_MS_IN) { - Some(FactValue::Int(ms)) => Some(ms), - _ => None, - }; - if opening.is_some() || relayed.is_some() { - let ms = opening.unwrap_or(0).saturating_add(relayed.unwrap_or(0)); - // The counter is milliseconds; the class is seconds. The conversion is the meter - // boundary's, stated once in `meta` — a millisecond figure carried through under a - // seconds-denominated class settles at a thousand times the duration it describes. - let _ = lines.push(UsageLocator { - class: meta::CLASS_AUDIO_SECONDS_IN, - location: None, - quantity: u64::try_from(ms).ok().map(meta::audio_seconds_in), - lane: None, - }); - } - if let Some(FactValue::Int(calls)) = r.facts.get(meta::FACT_TOOL_CALLS) { - let _ = lines.push(UsageLocator { - class: meta::CLASS_TOOL_CALLS, - location: None, - quantity: u64::try_from(calls).ok(), - lane: None, - }); - } - UsageLocators { lines } - } - - fn audit<'u>(&self, u: &Unit<'u>, out: &UnitEnd, _ctx: &Ctx<'u>) -> AuditFacts { - AuditFacts { - op_class: u.op(), - // One mapping, written once in the contract and read by every plane. Every unit this - // plane audits is a turn of a live session, so a completed one is `TurnComplete`. - finish: busbar_contract::unit::finish_class_of(out, FinishClass::TurnComplete), - } - } - - fn plane_facts<'u>( - &self, - verb: AdminVerbId, - _subject: Option<&'u str>, - ctx: &Ctx<'u>, - ) -> Result, Decode> { - if verb != meta::VERB_DIALECTS { - return Err(Decode::UnsupportedOperation); - } - let mut facts = Facts::new(); - let count = ctx - .arena() - .alloc_str(&claims::DIALECT_CLAIMS.len().to_string()) - .map_err(|_| Decode::Oversize)?; - facts - .set("dialect_count", FactValue::Str(count)) - .map_err(|_| Decode::Oversize)?; - Ok(PlaneFacts { facts }) - } - - fn content_facts<'u>( - &self, - _u: &Unit<'u>, - r: &Response<'u>, - _ctx: &Ctx<'u>, - ) -> ContentFacts<'u> { - let mut facts = Facts::new(); - for key in ::CONTENT_FACTS { - if let Some(v) = r.facts.get(key) { - let _ = facts.set(key, v); - } - } - ContentFacts { facts } - } -} - -impl SessionPlane for StreamingPlane { - fn open_session<'u>(&self, ctx: &Ctx<'u>) -> PlaneSessionState { - let dialect = ctx - .transport() - .fact(FACT_PATH) - .and_then(claims::dialect_for) - .unwrap_or(Dialect::OpenaiRealtime); - PlaneSessionState::new(VoiceSessionState::for_dialect(dialect)) - } - - fn open_upstream<'u>(&self, dest: &VerifiedDestination, _ctx: &Ctx<'u>) -> PlaneSessionState { - let dialect = upstream_dialect_for(self, dest); - PlaneSessionState::new(VoiceSessionState::for_dialect(dialect)) - } -} - -/// What a refused session is told, in the closed vocabulary a client is allowed to see. -/// -/// The internal reason is a kernel enum with names for the money, the buckets and the store -/// (`OverdraftCeiling`, `StaleSlice`, `DurabilityUnavailable`). Formatting it onto the wire told -/// every caller which internal ceiling it met and pinned this node's private vocabulary as the -/// dialect's `error.code` — a name no client library has a case for and no dialect documents. What -/// goes out instead is the same small opaque set every other plane in this workspace renders (see -/// `busbar-plane-admin`'s own table): the caller learns the CLASS of refusal and nothing about why -/// this node reached it. -fn refusal_render(reason: busbar_contract::unit::RefusalReason) -> (&'static str, &'static str) { - use busbar_contract::unit::RefusalReason as R; - match reason { - R::BodyTooLarge | R::DecodeFailed | R::SchemeNotDeclared | R::SecretPlaceholder => { - ("invalid_request", "the request could not be read") - } - R::CredentialRejected | R::SessionUnbound | R::CredentialBudget => { - ("unauthorized", "the session did not carry usable authority") - } - R::ScopeMissing | R::Vetoed | R::Revoked | R::PoolNotPermitted => ( - "forbidden", - "the caller may not open a session for this operation", - ), - R::RateLimited | R::InFlightCap | R::OpenSlotBusy | R::SessionBudget => { - ("rate_limited", "too many sessions at once") - } - R::NoDestination | R::DestinationUnreachable | R::BreakerOpen | R::Drain => ( - "unavailable", - "no provider is reachable for this session right now", - ), - // Everything else is this node saying no for a reason that is this node's own — the money, - // the buckets, the journal. A caller is told it failed here and nothing more. - _ => ("internal", "the session could not be opened at this time"), - } -} - -/// Which dialect the upstream a verified destination names speaks, by matching its host against -/// this plane's configured upstream list. Falls back to OpenAI Realtime (the more common of the two -/// and this plane's documented default elsewhere) when the destination is a `SessionUpstream` this -/// function cannot resolve a host for, or names no configured upstream at all. -fn upstream_dialect_for(plane: &StreamingPlane, dest: &VerifiedDestination) -> Dialect { - match dest.facts() { - DestinationFacts::Upstream { address, .. } => plane - .upstreams() - .iter() - .find(|u| Some(u.host) == address.authority()) - .map(|u| u.dialect) - .unwrap_or(Dialect::OpenaiRealtime), - _ => plane - .upstreams() - .first() - .map(|u| u.dialect) - .unwrap_or(Dialect::OpenaiRealtime), - } -} - -/// The dialect the decode step named, read back off the unit's sealed draft facts. -/// -/// The one place this plane's later steps ask what dialect a unit is: decode is the step that read -/// the bytes and matched the claim, and what it determined travels on the unit. A one-shot unit has -/// no session at all, so a session fact could not have answered for it. -fn draft_dialect(u: &Unit<'_>) -> Option { - match u.draft_facts().get(meta::FACT_DIALECT) { - Some(FactValue::Str(name)) => dialect_from_name(name), - _ => None, - } -} - -/// The client's own dialect, read back off the session fact this plane declared (`SESSION_FACTS`). -fn client_dialect_from_session<'u>(ctx: &Ctx<'u>) -> Option { - ctx.session() - .and_then(|s| s.session_fact(meta::FACT_DIALECT)) - .and_then(dialect_from_name) -} - -/// The inverse of [`Dialect::name`]. -fn dialect_from_name(name: &str) -> Option { - match name { - "openai-realtime" => Some(Dialect::OpenaiRealtime), - "gemini-live" => Some(Dialect::GeminiLive), - "twilio-media-streams" => Some(Dialect::TwilioMediaStreams), - "transcribe" => Some(Dialect::OneShotTranscribe), - "tts" => Some(Dialect::OneShotTts), - _ => None, - } -} - -/// Every body pointer this plane declares: none. -/// -/// This plane's frames are decoded by the codec rather than located by pointer — a duplex event -/// carries its own shape, and the quantities are counted as audio moves rather than pointed at in -/// a document. The span table is still built by the one scanner from this declaration, so -/// "declares nothing" is something the loop can read instead of a table nobody filled in. -const BODY_PTRS: &[&str] = &[]; - -/// The span view of a body, built from the pointers this plane declares. -fn view<'u>(body: &'u [u8], ctx: &Ctx<'u>) -> Result, Decode> { - let spans = busbar_contract::spans::resolve(body, BODY_PTRS, ctx.arena()) - .map_err(|_| Decode::Oversize)?; - Ok(Ir::new(body, spans)) -} - -/// Decode a one-shot (`http`) transcribe/tts request: no session, a single `OneShot` unit. -fn decode_one_shot<'u>(frames: &mut FrameCursor<'u>, ctx: &Ctx<'u>) -> Result, Decode> { - let path = ctx - .transport() - .fact(FACT_PATH) - .ok_or(Decode::MissingDeclaredFact)?; - let dialect = claims::dialect_for(path).ok_or(Decode::UnsupportedOperation)?; - let op = match dialect { - Dialect::OneShotTranscribe => OpClassId::new("transcribe"), - Dialect::OneShotTts => OpClassId::new("tts"), - _ => return Err(Decode::UnsupportedOperation), - }; - let frame = frames.next_frame(); - let body: &'u [u8] = frame.map(|f| f.bytes.as_slice()).unwrap_or(&[]); - let mut facts = Facts::new(); - facts - .set(meta::FACT_DIALECT, FactValue::Str(dialect.name())) - .map_err(|_| Decode::Oversize)?; - // What a text-to-speech request is priced on is the text it asks to be spoken, and that text is - // in the request rather than in the answer. Decode is the step that reads the request's bytes, - // so the figure is taken here and travels on the unit; the metering step reads it back off the - // draft rather than opening the body a second time. - if dialect == Dialect::OneShotTts { - if let Some(text) = crate::oneshot::tts_input_text(body) { - facts - .set( - meta::FACT_TEXT_TOKENS_IN, - FactValue::Int( - i64::try_from(meta::text_tokens_of(text.len())).unwrap_or(i64::MAX), - ), - ) - .map_err(|_| Decode::Oversize)?; - } - } - Ok(Ingress::OneShot(Box::new(UnitDraft { - op, - body_ir: view(body, ctx)?, - correlates: None, - correlation_out: None, - facts, - }))) -} - -/// Decode the answer to a one-shot transcribe or text-to-speech request. -/// -/// There is no session and no codec state: the operation is one request and one answer, and the -/// answer ends the unit. What the answer states is what is stamped — a transcription's own text, -/// under the class the emitted text prices at. A speech answer is audio bytes whose duration this -/// plane cannot read without knowing the format the upstream chose, so it states nothing about -/// them: the request's own text estimate, taken at decode, is what that unit is priced on. -fn decode_one_shot_response<'u>( - frames: &mut FrameCursor<'u>, - ctx: &Ctx<'u>, -) -> Result, Decode> { - let path = ctx - .transport() - .fact(FACT_PATH) - .ok_or(Decode::MissingDeclaredFact)?; - let dialect = claims::dialect_for(path).ok_or(Decode::UnsupportedOperation)?; - if !matches!(dialect, Dialect::OneShotTranscribe | Dialect::OneShotTts) { - return Err(Decode::UnsupportedOperation); - } - let frame = frames.next_frame(); - let body: &'u [u8] = frame.map(|f| f.bytes.as_slice()).unwrap_or(&[]); - let mut facts = Facts::new(); - if dialect == Dialect::OneShotTranscribe { - if let Some(text) = transcript_text(body) { - facts - .set( - meta::FACT_TEXT_TOKENS_OUT, - FactValue::Int( - i64::try_from(meta::text_tokens_of(text.len())).unwrap_or(i64::MAX), - ), - ) - .map_err(|_| Decode::Oversize)?; - } - } - Ok(Progress::Terminal { - for_: None, - r: Box::new(Response { - ir: view(body, ctx)?, - finish: FinishClass::TurnComplete, - facts, - }), - }) -} - -/// The text a one-shot transcription answered with, in the provisional wire shape this plane -/// documents (`{"text": "..."}`). `None` for anything else, which states nothing rather than -/// guessing a figure. -fn transcript_text(body: &[u8]) -> Option { - let value: serde_json::Value = serde_json::from_slice(body).ok()?; - value - .get("text") - .and_then(serde_json::Value::as_str) - .map(str::to_string) -} - -/// Decode one frame of a duplex session bound to one of the two WS dialects (OpenAI Realtime or -/// Gemini Live). -fn decode_ws_frame<'u>( - frames: &mut FrameCursor<'u>, - state: &mut VoiceSessionState, - dialect: Dialect, - ctx: &Ctx<'u>, -) -> Result, Decode> { - let frame = frames.next_frame().ok_or(Decode::Malformed)?; - // A frame the cursor carved but that carries no bytes yet is the one genuinely partial shape at - // this layer (mcp's own decode makes the same distinction on an empty body): wait for the rest. - if frame.bytes.is_empty() { - return Ok(Ingress::NeedMore); - } - // A frame WITH bytes that is not readable text is a refusal, not a partial read: nothing more - // ever arrives to complete an already-complete WS frame that failed to decode as UTF-8. - std::str::from_utf8(frame.bytes.as_slice()).map_err(|_| Decode::Malformed)?; - // Borrowed, not copied: see `decode_response`'s own note on the downlink side of this. - let reader = reader_for(dialect); - let events = reader.read_up_ref(WireRef(frame.bytes.as_slice()), &mut state.codec); - let Some(event) = events.into_iter().next() else { - // A JSON document this dialect does not recognise is answered exactly the way - // `decode_response` answers an unrecognised server event and mcp answers an unrecognised - // notice: dropped, never left pending forever. - return Ok(Ingress::Discard { - reason: DiscardCode::Unsupported, - }); - }; - // Stashed so `encode_ingress_frame` never calls the stateful reader a second time for the same - // wire frame (see `crate::session::Pending`'s doc comment). - state.pending = Some(Pending::Ingress(event.clone())); - // The original wire frame, handed on so an OPENING frame's Unit-0 body is the client's own - // message (`encode_egress` re-frames it onto the upstream), not the extracted relay. - let wire = frame.bytes.as_slice(); - ingress_from_client_event(event, state, dialect, wire, ctx) -} - -/// Decode one frame of a `twilio-media`-carried session. -fn decode_twilio_frame<'u>( - frames: &mut FrameCursor<'u>, - state: &mut VoiceSessionState, - ctx: &Ctx<'u>, -) -> Result, Decode> { - let frame = frames.next_frame().ok_or(Decode::Malformed)?; - let event = match twilio::decode(frame.bytes.as_slice()) { - Ok(event) => event, - // An event name this reader does not model is not a broken frame: this carrier adds events - // over time and tells its clients to ignore the ones they do not know, and the WS transport - // under this session already discards what it cannot place. Refusing the session over one - // would drop a live call the day the carrier ships a new lifecycle event. A frame that is - // not this carrier's JSON at all is still a refusal. - Err(twilio::TwilioError::UnknownEvent(_)) => { - return Ok(Ingress::Discard { - reason: DiscardCode::Unsupported, - }) - } - // `start` negotiated a `mediaFormat` other than the one this crate's Twilio path assumes - // (`twilio::decode`'s own doc). Every later `media` frame on this stream would be decoded, - // and TIMED, under a wrong assumption — and duration is a LEDGER quantity, so a wrong count - // is a wrong ledger. Refused rather than served on a guess (#42's own spirit: an unknown is - // refused, never silently defaulted). - Err(twilio::TwilioError::UnsupportedMediaFormat) => { - return Err(Decode::UnsupportedOperation) - } - Err(_) => return Err(Decode::Malformed), - }; - match event { - // Lifecycle events with no audio and nothing left to bind: consumed, no unit, no state - // change beyond what `Start` below records. - twilio::TwilioEvent::Connected => Ok(Ingress::Discard { - reason: DiscardCode::Unsupported, - }), - twilio::TwilioEvent::Start { stream_sid, .. } => { - // `twilio::decode` never hands back an empty `stream_sid` for `Start` (it refuses the - // frame itself, `TwilioError::Malformed`, above) — so by construction, everything bound - // here is a real, non-empty identity that CAN bind. See `twilio::decode`'s own note for - // why an empty id must never reach this assignment. - debug_assert!( - !stream_sid.is_empty(), - "twilio::decode must refuse an empty streamSid before Start reaches this arm" - ); - state.twilio_stream_sid = Some(stream_sid); - state.dialect = Some(Dialect::TwilioMediaStreams); - Ok(Ingress::Discard { - reason: DiscardCode::Unsupported, - }) - } - twilio::TwilioEvent::Media { - stream_sid, - payload, - } => { - // The forgery/replay guard the architecture note for this dialect names: a `media` - // frame naming a `streamSid` other than the one `start` bound is discarded, not - // refused — it costs nothing and ends no unit, exactly what a discard is for. - if state.twilio_stream_sid.as_deref() != Some(stream_sid.as_str()) { - return Ok(Ingress::Discard { - reason: DiscardCode::ForgedSource, - }); - } - // Counted at the relay seam, not here: see `encode_ingress_frame`. A frame that OPENS - // the turn is the one exception — it becomes the unit's own egress body and never - // reaches that seam — so its milliseconds travel on the draft the unit is minted from. - let ms = AudioFormat::G711Ulaw.bytes_to_ms(payload.len() as u64); - let arena_bytes = ctx - .arena() - .alloc_bytes(&payload) - .map_err(|_| Decode::Oversize)?; - // The OPENING frame's body is the carrier's ORIGINAL `media` envelope, so `encode_egress` - // can re-run the same µ-law -> PCM -> `input_audio_buffer.append` shaping the relay seam - // does; a byte pass-through would have shipped raw µ-law upstream. The extracted relay is - // still handed on for the already-open case. - open_or_relay( - state, - Dialect::TwilioMediaStreams, - frame.bytes.as_slice(), - arena_bytes, - None, - Some(ms), - ctx, - ) - } - twilio::TwilioEvent::Mark { .. } => Ok(Ingress::Discard { - reason: DiscardCode::Unsupported, - }), - twilio::TwilioEvent::Dtmf { .. } => Ok(Ingress::Discard { - reason: DiscardCode::Unsupported, - }), - twilio::TwilioEvent::Stop => { - // The call ended mid-turn. The open turn's counters are the only record of the audio the - // caller spoke and the tool calls the upstream opened since the last usage report, and - // closing the turn is what takes them — so they travel on the ending, under the same keys - // `meter` reads off a terminal answer. Taking them and dropping them metered every - // hung-up turn at zero: the ledger said nothing happened where audio was served. - let for_ = state.turn_correlation; - let counters = state.close_turn(); - let mut facts = Facts::new(); - set_counter_facts(&mut facts, counters)?; - Ok(Ingress::Close { - for_, - facts: Box::new(facts), - }) - } - } -} - -/// Turn one decoded client→server IR event into an `Ingress` answer, for the two WS dialects. -/// -/// Any client event — audio, control or a tool result — opens the turn if none is open yet: a -/// session's very first frame is not always audio (`session.update` routinely arrives first), and -/// there is no reason to wait for audio specifically before a unit exists to hold the session's -/// facts and price its hold. -fn ingress_from_client_event<'u>( - event: IrClientEvent, - state: &mut VoiceSessionState, - dialect: Dialect, - wire: &'u [u8], - ctx: &Ctx<'u>, -) -> Result, Decode> { - // A tool RESULT is not a frame of the conversation; it is the answer to a call the upstream - // opened, and it belongs to that call's own unit. Relayed onto the open turn — which is what - // every client tool event did — it arrived under the turn's hold naming the turn's correlation, - // so the one unit actually waiting for it could not be told it had been answered, and the reply - // and the wait passed each other on the same session. - if let IrClientEvent::Tool(IrDuplexTool::CallResult { call_id, .. }) = &event { - let id = ctx - .arena() - .alloc_str(call_id) - .map_err(|_| Decode::Oversize)?; - let mut facts = Facts::new(); - facts - .set(meta::FACT_CALL_ID, FactValue::Str(id)) - .map_err(|_| Decode::Oversize)?; - return Ok(Ingress::Frame { - // The identifier as itself, under the key the leg named. Whole value, because the pair - // this has to tell apart is two calls open on one session at one moment. - for_: Some(CorrelationRef { - fact_key: FACT_TOOL_CORRELATION, - value: CorrelationValue::Str(id), - }), - relay: ScratchBytes::new(&[]), - facts: Box::new(facts), - }); - } - let (relay, interrupt_ms, audio_ms) = match &event { - IrClientEvent::AudioFrame(f) => { - // See the module doc comment: the uplink format is assumed PCM16 for this estimate. - // The figure is not accumulated here — see `encode_ingress_frame`, which counts what - // is actually relayed on the half the response step later reads it back from. - let ms = AudioFormat::Pcm16.bytes_to_ms(f.media.len() as u64); - let bytes = ctx - .arena() - .alloc_bytes(&f.media) - .map_err(|_| Decode::Oversize)?; - (bytes, None, Some(ms)) - } - IrClientEvent::Control(IrDuplexControl::ItemTruncate { - audio_played_ms, .. - }) => (ScratchBytes::new(&[]), Some(*audio_played_ms), None), - IrClientEvent::Control(_) | IrClientEvent::Tool(_) => (ScratchBytes::new(&[]), None, None), - }; - open_or_relay(state, dialect, wire, relay, interrupt_ms, audio_ms, ctx) -} - -/// Open a fresh turn (this is its first frame) or relay onto the one already open, attaching the -/// interrupt fact where the caller says the event carried one, either way. -/// -/// A frame that CARRIES an interrupt always opens. The scheduler reads the interrupt off an OPEN — -/// that is the only shape whose dispatch reaches the compare-and-set that supersedes the unit in -/// flight — so a barge-in delivered as one more frame of the very turn it interrupts named the -/// superseded unit to nobody: the interrupted turn kept the direction's slot and kept pricing while -/// the caller was already talking over it, and the turn that took over could never open. -fn open_or_relay<'u>( - state: &mut VoiceSessionState, - dialect: Dialect, - open_wire: &'u [u8], - relay: ScratchBytes<'u>, - interrupt_ms: Option, - audio_ms: Option, - ctx: &Ctx<'u>, -) -> Result, Decode> { - let mut facts = Facts::new(); - if let Some(ms) = interrupt_ms { - let _ = facts.set( - meta::FACT_INTERRUPT_AUDIO_PLAYED_MS, - FactValue::Int(i64::try_from(ms).unwrap_or(i64::MAX)), - ); - // The interrupted turn ends here; the frame that interrupted it opens the next one, and - // carries the fact that says which one it took over from. The interrupted turn is superseded, - // not answered: no usage report ever meters it, so its counters — the audio the caller - // already spoke and the tool calls the upstream already opened — are CARRIED onto the turn - // that takes over rather than reset with the correlation. Resetting them here dropped every - // barged-in second and call from the ledger; carried, each is emitted exactly once, on - // whichever terminal (usage, error or `stop`) next closes a turn on this state. - let carried = state.close_turn(); - state.turn = carried; - } - if !state.turn_open { - // The opening frame becomes the unit's own egress body rather than travelling through the - // relay seam, so what it carries of the caller's audio is stated on the draft. Every later - // frame of the turn is counted where it relays. - if let Some(ms) = audio_ms { - let _ = facts.set( - meta::FACT_AUDIO_MS_IN, - FactValue::Int(i64::try_from(ms).unwrap_or(i64::MAX)), - ); - } - let correlation = state.open_turn(); - let _ = facts.set(meta::FACT_DIALECT, FactValue::Str(dialect.name())); - // The opening frame's body is the client's ORIGINAL wire frame, not the extracted relay: - // `encode_egress` re-frames it onto the upstream dialect (see its note), so what it carries - // must be the full client message (a `session.update` config, a carrier `media` envelope), - // never the empty/raw relay a byte pass-through would have shipped upstream unshaped. - let ir = view(open_wire, ctx)?; - Ok(Ingress::Open(Box::new(UnitDraft { - op: OpClassId::new("duplex_turn"), - body_ir: ir, - correlates: None, - correlation_out: Some(correlation), - facts, - }))) - } else { - Ok(Ingress::Frame { - for_: state.turn_correlation, - relay, - facts: Box::new(facts), - }) - } -} - -/// Write a closed turn's own counters under the keys `meter` reads them back from. -/// -/// One writer for every ending that takes the counters out of the state — a usage report, an upstream -/// error and a carrier `stop` — so no ending can take them and forget to say what they were. -fn set_counter_facts( - facts: &mut Facts<'_>, - counters: crate::session::TurnCounters, -) -> Result<(), Decode> { - facts - .set( - meta::FACT_AUDIO_MS_IN, - FactValue::Int(i64::try_from(counters.audio_ms_in).unwrap_or(i64::MAX)), - ) - .map_err(|_| Decode::Oversize)?; - facts - .set( - meta::FACT_TOOL_CALLS, - FactValue::Int(i64::try_from(counters.tool_calls).unwrap_or(i64::MAX)), - ) - .map_err(|_| Decode::Oversize)?; - Ok(()) -} - -/// Turn one decoded server→client IR event into a `Progress` answer, rendering the client-shaped -/// bytes immediately (see the module doc comment's `encode_response` note). -fn progress_from_server_event<'u>( - event: IrServerEvent, - state: &mut VoiceSessionState, - client_dialect: Dialect, - ctx: &Ctx<'u>, -) -> Result, Decode> { - let for_ = state.turn_correlation; - match event { - IrServerEvent::SessionCreated { .. } | IrServerEvent::RateLimits => Ok(Progress::Discard { - reason: DiscardCode::Unsupported, - }), - IrServerEvent::Tool(IrDuplexTool::CallOpen { call_id, name, .. }) => { - // A tool call is ANNOUNCED here. Its arguments stream in as `CallArgs` and it is - // dispatched to its executor ONCE, on `CallClose`, with the name and the ACCUMULATED - // arguments — a call dispatched with no arguments is a different call, which is what the - // old open-mints-empty path did. Announcing opens nothing on its own; the name is - // remembered so `CallClose` (which carries none) can name the executor call. - state.turn.open_tool_call(); - state.codec.remember_call_name(&call_id, &name); - Ok(Progress::Discard { - reason: DiscardCode::Unsupported, - }) - } - IrServerEvent::Tool(IrDuplexTool::CallArgs { - call_ref, - json_delta, - .. - }) => { - // Accumulate one streamed argument fragment. `push_call_args` appends a fragment and - // REPLACES on a whole JSON object (the complete arguments a streamed call states when it - // closes, or an atomic call's `args`), so the OpenAI done-repeat and the Gemini atomic - // call both land the right bytes rather than a fragment spliced onto its own prefix. - state.codec.push_call_args(call_ref, &json_delta); - Ok(Progress::Discard { - reason: DiscardCode::Unsupported, - }) - } - IrServerEvent::Tool(IrDuplexTool::CallClose { call_ref, call_id }) => { - // The arguments are complete: dispatch the tool call as its own `OneShot` unit, carrying - // the tool name and the arguments the model asked for, correlated by the wire call id so - // its reply reaches it. This is where a tool call becomes visible, priced and audited as - // its own `tool_call` unit. - let mut facts = Facts::new(); - let name = state.codec.call_name(&call_id).to_string(); - if !name.is_empty() { - let name_arena = ctx.arena().alloc_str(&name).map_err(|_| Decode::Oversize)?; - facts - .set(meta::FACT_TOOL_NAME, FactValue::Str(name_arena)) - .map_err(|_| Decode::Oversize)?; - } - // The call identifier travels as itself. It is a string on the wire and it is a string - // here, allocated in the unit's own arena — a fold into sixty four bits would be one - // collision away from answering a tool call with another's reply. - let call_id_arena = ctx - .arena() - .alloc_str(&call_id) - .map_err(|_| Decode::Oversize)?; - facts - .set(meta::FACT_CALL_ID, FactValue::Str(call_id_arena)) - .map_err(|_| Decode::Oversize)?; - // The arguments the model asked for. An accumulation that never closed readable (past the - // byte ceiling, or a call joined mid-stream with nothing decodable) leaves an empty body - // rather than inventing a null argument list the model never sent. - let body_ir = match state.codec.take_call_args(call_ref) { - Some(args) => { - let bytes = serde_json::to_vec(&args).map_err(|_| Decode::Malformed)?; - let arena = ctx - .arena() - .alloc_bytes(&bytes) - .map_err(|_| Decode::Oversize)?; - view(arena.as_slice(), ctx)? - } - None => Ir::empty(), - }; - Ok(Progress::OneShot(Box::new(UnitDraft { - op: OpClassId::new("tool_call"), - body_ir, - correlates: None, - correlation_out: Some(CorrelationRef { - fact_key: FACT_TOOL_CORRELATION, - value: CorrelationValue::Str(call_id_arena), - }), - facts, - }))) - } - IrServerEvent::Tool(_) => Ok(Progress::Frame { - for_, - r: Box::new(Response { - ir: Ir::empty(), - finish: FinishClass::Partial, - facts: Facts::new(), - }), - }), - IrServerEvent::SpeechStarted { .. } => { - let ms = state.codec.flush_playback(); - let mut facts = Facts::new(); - facts - .set( - meta::FACT_INTERRUPT_AUDIO_PLAYED_MS, - FactValue::Int(i64::try_from(ms).unwrap_or(i64::MAX)), - ) - .map_err(|_| Decode::Oversize)?; - Ok(Progress::Frame { - for_, - r: Box::new(Response { - ir: Ir::empty(), - finish: FinishClass::Partial, - facts, - }), - }) - } - IrServerEvent::SpeechStopped { .. } | IrServerEvent::AudioDone { .. } => { - Ok(Progress::Frame { - for_, - r: Box::new(Response { - ir: Ir::empty(), - finish: FinishClass::Partial, - facts: Facts::new(), - }), - }) - } - IrServerEvent::AudioFrame(f) => { - let writer = writer_for(client_dialect); - let media_len = f.media.len() as u64; - // One buffer, filled and then copied into the arena. The identifier is BORROWED from the - // binding the `start` event made: it is the same string on every frame of the call, and - // a call sends fifty frames a second, so cloning it per frame is fifty copies a second - // of a string that never changes. - // - // Each arm reaches the arena on its own rather than through one shared owning type: the - // carrier's renderer refills a buffer the SESSION holds, and handing its bytes through a - // per-frame owner put back exactly the allocation per frame the renderer exists to - // remove — the renderer's committed zero was true of the renderer and false of its only - // caller. - let bytes = match client_dialect { - Dialect::TwilioMediaStreams => { - let mulaw = ulaw::encode_frame(&f.media); - // Taken and handed straight back, because the identifier beside it is borrowed - // from the same state. - let mut out = core::mem::take(&mut state.render_buf); - let sid = state.twilio_stream_sid.as_deref().unwrap_or_default(); - twilio::encode_media_into(&mut out, sid, &mulaw); - let bytes = ctx.arena().alloc_bytes(&out).map_err(|_| Decode::Oversize); - state.render_buf = out; - bytes? - } - _ => { - let rendered = writer - .write_down(IrServerEvent::AudioFrame(f), &mut state.codec) - .ok_or(Decode::Malformed)? - .0; - ctx.arena() - .alloc_bytes(&rendered) - .map_err(|_| Decode::Oversize)? - } - }; - let mut facts = Facts::new(); - facts - .set( - meta::EGRESS_PACING_FACT_KEY, - FactValue::Int(i64::try_from(state.codec.played_ms()).unwrap_or(i64::MAX)), - ) - .map_err(|_| Decode::Oversize)?; - let _ = media_len; - Ok(Progress::Frame { - for_, - r: Box::new(Response { - ir: view(bytes.as_slice(), ctx)?, - finish: FinishClass::Partial, - facts, - }), - }) - } - IrServerEvent::Usage(usage) => { - let counters = state.close_turn(); - let mut facts = Facts::new(); - let ints: [(&str, i64); 7] = [ - ( - meta::FACT_AUDIO_TOKENS_IN, - i64::try_from(usage.audio_in).unwrap_or(i64::MAX), - ), - ( - meta::FACT_AUDIO_TOKENS_OUT, - i64::try_from(usage.audio_out).unwrap_or(i64::MAX), - ), - // The two halves travel separately. Summing them was what forced the whole figure - // through a single input-direction class; the upstream reports them apart and this - // plane keeps them apart, so the emitted half prices as output. - ( - meta::FACT_TEXT_TOKENS_IN, - i64::try_from(usage.text_in).unwrap_or(i64::MAX), - ), - ( - meta::FACT_TEXT_TOKENS_OUT, - i64::try_from(usage.text_out).unwrap_or(i64::MAX), - ), - ( - meta::FACT_CACHED_TOKENS, - i64::try_from(usage.cached).unwrap_or(i64::MAX), - ), - ( - meta::FACT_AUDIO_MS_IN, - i64::try_from(counters.audio_ms_in).unwrap_or(i64::MAX), - ), - ( - meta::FACT_TOOL_CALLS, - i64::try_from(counters.tool_calls).unwrap_or(i64::MAX), - ), - ]; - for (k, v) in ints { - facts - .set(k, FactValue::Int(v)) - .map_err(|_| Decode::Oversize)?; - } - Ok(Progress::Terminal { - for_, - r: Box::new(Response { - ir: Ir::empty(), - finish: FinishClass::TurnComplete, - facts, - }), - }) - } - IrServerEvent::Error { code, message } => { - // A turn that ends in an upstream error consumed the same uplink audio and ran the same - // tool calls as one that ends in a usage report. The kernel-side counters are the only - // record of either, and closing the turn is what takes them: dropping them here would - // meter the failed turn at zero and hand back for free every second the caller spoke. - let counters = state.close_turn(); - let mut facts = Facts::new(); - let code_arena = ctx.arena().alloc_str(&code).map_err(|_| Decode::Oversize)?; - let message_arena = ctx - .arena() - .alloc_str(&message) - .map_err(|_| Decode::Oversize)?; - facts - .set(meta::FACT_ERROR_CODE, FactValue::Str(code_arena)) - .map_err(|_| Decode::Oversize)?; - facts - .set(meta::FACT_ERROR_MESSAGE, FactValue::Str(message_arena)) - .map_err(|_| Decode::Oversize)?; - set_counter_facts(&mut facts, counters)?; - Ok(Progress::Terminal { - for_, - r: Box::new(Response { - ir: Ir::empty(), - finish: FinishClass::Error, - facts, - }), - }) - } - } -} diff --git a/crates/busbar-plane-streaming/src/session.rs b/crates/busbar-plane-streaming/src/session.rs index 3c4556eef0..84b41e5cae 100644 --- a/crates/busbar-plane-streaming/src/session.rs +++ b/crates/busbar-plane-streaming/src/session.rs @@ -1,35 +1,11 @@ -//! The plane's per-connection codec state — the one place cross-frame state may live. -//! -//! `busbar_contract::plane::PlaneSessionState` is a type-erased `Box` the kernel -//! holds one half of per connection: one for the client, one more per upstream a session dials. This -//! module is the concrete type this plane wraps in it. +//! The plane's per-turn bookkeeping: the counters a turn keeps that no usage report carries, and a +//! closed turn's counts per declared class. -use crate::codec::ir::{AudioFormat, DecodeState, IrClientEvent, IrDuplexUsage}; -use busbar_contract::ids::{CorrelationRef, CorrelationValue, MeterClassId}; +use crate::codec::ir::{AudioFormat, IrDuplexUsage}; +use busbar_contract::ids::MeterClassId; -use crate::claims::Dialect; use crate::meta; -/// One pending, already-decoded IR event, stashed across the two-call boundary a step pair leaves -/// open. -/// -/// `decode_ingress`/`encode_ingress_frame` are two separate calls the kernel makes about the SAME -/// inbound frame — the first says what it means, the second says what to send onward. The codecs' -/// `read_up` method is stateful (it advances the per-session frame sequence), so calling it a second -/// time to re-derive what the first call already decoded would double-count that state. Stashing the -/// already-decoded event here and consuming it once is what keeps the codec state's counters correct -/// across the split. -/// -/// There is ONE arm, and there was only ever one reason for a second: the downlink split needed a -/// stash because a later step could not see what decode had determined. It can now — a unit carries -/// its draft's facts — and `encode_response` renders the bytes `decode_response` already produced, -/// so the egress arm existed for a problem that is no longer the shape of the surface. -#[derive(Debug, Clone)] -pub enum Pending { - /// A client→server event decoded at `decode_ingress`, consumed at `encode_ingress_frame`. - Ingress(IrClientEvent), -} - /// The plane's own bookkeeping for the current, still-open turn. /// /// Closed (its counts reset to zero) every time a turn is ANSWERED — a usage report, an upstream error or @@ -112,7 +88,7 @@ impl TurnCounters { /// alike). Counts only: no rate is read and no figure results. /// /// This is the plane's one reading of a turn for a host that drives the session itself rather than -/// through the unit loop; `tests::codec` holds it to `meter` over the same turn. +/// through the unit loop. #[must_use] pub fn class_counts( usage: Option<&IrDuplexUsage>, @@ -135,84 +111,3 @@ pub fn class_counts( .collect() } -/// The codec state one connection half of a voice session holds. -/// -/// One value per [`busbar_contract::plane::PlaneSessionState`] half: the client half opened by -/// this plane's `SessionPlane::open_session`, one more per upstream opened by its -/// `open_upstream` (see `crate::plane`). Everything here is exactly what a plane may hold across -/// frames and nothing else — no connection, no clock, no credential. -#[derive(Debug, Default)] -pub struct VoiceSessionState { - /// The dialect this half of the connection speaks. `None` only in the sliver of time before the - /// first frame has named one; every method that needs it treats an absent dialect as a decode - /// failure rather than guessing. - pub dialect: Option, - /// The shared duplex codec's per-session state (frame sequencing, the `CallRef` correlation - /// table, the negotiated output format, and the barge-in playback-position bookkeeping). Reused - /// for both codec-backed dialects (`OpenAI Realtime`, `Gemini Live`): the shared IR is what - /// makes one state shape valid for either. - pub codec: DecodeState, - /// Whether the current turn is open (a unit has been opened and not yet closed). - pub turn_open: bool, - /// The correlation the currently open turn answers frames under, once one has been minted. - pub turn_correlation: Option>, - /// The next turn identity to mint. Monotonic per session; a duplex session opens and closes many - /// turns in sequence, and each needs a correlation value distinct from the last so a stray late - /// frame from a just-closed turn cannot be mistaken for one belonging to the next. - pub next_turn_id: u64, - /// This turn's own derived counters (see [`TurnCounters`]). - pub turn: TurnCounters, - /// Twilio's `streamSid` for this connection, bound at the `start` event and checked against - /// every later `media` frame — the forgery/replay guard the architecture note for this dialect - /// names. - pub twilio_stream_sid: Option, - /// The one already-decoded event a two-call step pair is carrying across (see [`Pending`]). - pub pending: Option, - /// The buffer one downlink audio frame is rendered into, held across frames. - /// - /// The renderer this crate carries for the carrier dialect clears and refills a buffer the - /// caller owns, and its committed cost is stated for a buffer that has already carried a frame - /// — which is the only shape that costs nothing. A call sends fifty downlink frames a second, - /// so a fresh vector per frame is fifty allocations a second the renderer was written to - /// remove. This is where the one buffer lives. - pub render_buf: Vec, -} - -impl VoiceSessionState { - /// A fresh state already bound to a known dialect — what a session's client half opens with, - /// once the claim that matched the connection is known. - #[must_use] - pub fn for_dialect(dialect: Dialect) -> Self { - Self { - dialect: Some(dialect), - ..Self::default() - } - } - - /// The fact key a turn's correlation is minted under. Declared once here so the mint site and - /// every read site agree. - pub const TURN_FACT_KEY: &'static str = "turn_id"; - - /// Open a fresh turn, minting the correlation later frames must carry to relay onto it. - pub fn open_turn(&mut self) -> CorrelationRef<'static> { - let id = self.next_turn_id; - self.next_turn_id += 1; - let correlation = CorrelationRef { - fact_key: Self::TURN_FACT_KEY, - value: CorrelationValue::Num(id), - }; - self.turn_open = true; - self.turn_correlation = Some(correlation); - correlation - } - - /// Close the current turn, snapshotting and resetting its counters. - /// - /// Returns the counters as they stood at close, which is what feeds the response facts the - /// `meter` step reads back out (`crate::plane`'s `decode_response`/`meter`). - pub fn close_turn(&mut self) -> TurnCounters { - self.turn_open = false; - self.turn_correlation = None; - self.turn.close() - } -} diff --git a/crates/busbar-plane-streaming/src/session_unit.rs b/crates/busbar-plane-streaming/src/session_unit.rs new file mode 100644 index 0000000000..873d7a6151 --- /dev/null +++ b/crates/busbar-plane-streaming/src/session_unit.rs @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! ONE LIVE SESSION AS THE DRIVER SERVES IT, sans I/O: the session pump, the telephony carrier's +//! envelope where the session came in over the telephony door, the session's cumulative units and +//! its wall-clock ceiling. +//! +//! The driver pushes the caller's pieces ([`SessionUnit::from_caller`]) and the far end's +//! ([`SessionUnit::from_far_end`]); each answers a [`Plan`] of bytes for each side and whether the +//! session ends. Every closed turn adds to the session's cumulative units ([`SessionUnit::units`]), +//! which each answer reports; the kernel checkpoints them and decides a cut. The plane never cuts a +//! session on money: a cut is the kernel's, and it reaches the caller through the refusal render. + +use bytes::Bytes; + +use crate::codec::ir::codec::{DuplexReader, DuplexWriter, WireEvent}; +use crate::codec::ir::config::SessionConfig; +use crate::codec::ir::usage::IrDuplexUsage; +use crate::codec::topology::twilio::{assert_g711_ulaw, TwilioEnvelope, TwilioError, TwilioEvent}; +use crate::governed::GovernedSession; +use crate::meta; +use crate::session::{class_counts, TurnCounters}; +use crate::session_pump::{SessionPump, TurnSink}; +use crate::session_row::VoiceSessionRow; + +/// The billable classes a session reports, in the tail's order (their index is the class a unit +/// count names). +pub const CLASSES: [busbar_contract::ids::MeterClassId; 6] = [ + meta::CLASS_AUDIO_TOKENS_IN, + meta::CLASS_AUDIO_TOKENS_OUT, + meta::CLASS_TEXT_TOKENS_IN, + meta::CLASS_TEXT_TOKENS_OUT, + meta::CLASS_AUDIO_SECONDS_IN, + meta::CLASS_TOOL_CALLS, +]; + +/// The session's units so far, per class: every closed turn adds to them, and the session always +/// continues (the kernel owns the cut). +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct CumulativeUnits(pub [u64; 6]); + +impl TurnSink for CumulativeUnits { + fn turn_closed(&mut self, usage: Option<&IrDuplexUsage>, counters: TurnCounters) -> bool { + for (class, n) in class_counts(usage, counters) { + if let Some(i) = CLASSES.iter().position(|c| *c == class) { + self.0[i] = self.0[i].saturating_add(n); + } + } + true + } +} + +/// What one piece asks the driver to carry. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct Plan { + /// Frames bound for the far end, in order. + pub to_far_end: Vec>, + /// Frames bound for the caller, in order. + pub to_caller: Vec>, + /// The session ends after these frames. + pub end: bool, +} + +/// THE TELEPHONY CARRIER'S ENVELOPE around a session: the caller speaks Twilio Media Streams, the +/// far end the realtime dialect locked to `g711_ulaw` both ways, so the audio passes through +/// unchanged and only the envelope is rewritten. +#[derive(Debug, Default)] +pub struct TwilioBridge { + stream_sid: Option, +} + +/// What one caller frame on the telephony door is. +#[derive(Debug, PartialEq, Eq)] +pub enum CallerStep { + /// Caller audio, as the far end's dialect frames an uplink append. + Audio(WireEvent), + /// The call ended. + Stop, + /// The call is refused (no stream id, another media format, a frame not in Twilio's shape, a + /// payload that is not clean base64): the session ends. + Refused, + /// Nothing for the far end (the handshake, a start, a mark, a keypress, an event this reader + /// does not model, or media for another stream). + Nothing, +} + +impl TwilioBridge { + /// Read one caller frame. A start with no stream id, or in another media format, is refused; so + /// is a frame that is not Twilio's shape or carries a payload that is not clean base64 (data + /// resuming after padding is never billed as a longer payload). An event this reader does not + /// model is dropped. + pub fn from_caller(&mut self, frame: &[u8]) -> CallerStep { + match TwilioEnvelope::decode(frame) { + Ok(TwilioEvent::Start(start)) => { + if start.stream_sid.is_empty() || assert_g711_ulaw(&start.media_format).is_err() { + return CallerStep::Refused; + } + self.stream_sid = Some(start.stream_sid); + CallerStep::Nothing + } + Ok(TwilioEvent::Media { + stream_sid, + payload, + }) => { + if stream_sid.is_empty() || self.stream_sid.as_deref() != Some(stream_sid.as_str()) { + return CallerStep::Nothing; + } + let append = serde_json::json!({ + "type": "input_audio_buffer.append", + "audio": busbar_contract::media::base64_encode(&payload), + }); + CallerStep::Audio(WireEvent(Bytes::from( + serde_json::to_vec(&append).unwrap_or_default(), + ))) + } + Ok(TwilioEvent::Stop) => CallerStep::Stop, + Ok(_) | Err(TwilioError::UnknownEvent(_)) => CallerStep::Nothing, + Err(_) => CallerStep::Refused, + } + } + + /// Rewrite one frame bound for the caller into the carrier's envelope: model audio becomes a + /// `media` frame on the call's stream, and a barge-in clears the audio the carrier has queued. + /// `None` for a frame the carrier has no event for. + #[must_use] + pub fn to_caller(&self, frame: &[u8]) -> Option> { + let sid = self.stream_sid.as_deref()?; + let v: serde_json::Value = serde_json::from_slice(frame).ok()?; + match v.get("type").and_then(serde_json::Value::as_str)? { + "response.output_audio.delta" | "response.audio.delta" => { + let audio = busbar_contract::media::base64_decode(v.get("delta")?.as_str()?)?; + Some(TwilioEnvelope::encode_media(sid, &audio)) + } + "input_audio_buffer.speech_started" => serde_json::to_vec(&serde_json::json!({ + "event": "clear", + "streamSid": sid, + })) + .ok(), + _ => None, + } + } +} + +/// ONE LIVE SESSION over the far end's codec `C`. +#[derive(Debug)] +pub struct SessionUnit { + pump: SessionPump, + /// The session's record: opened, its turn cursor, its provider call id, its end. + row: VoiceSessionRow, + /// Record writes not yet handed to the kernel, oldest first. + writes: Vec, + units: CumulativeUnits, + twilio: Option, + opened_ns: u64, + ceiling_secs: Option, + ended: bool, +} + +impl SessionUnit +where + C: DuplexReader + DuplexWriter, +{ + /// A session over `codec`, locked to `locked`, opened at `now_ns`, bounded by `ceiling_secs`. + /// `telephony` puts the telephony carrier's envelope on the caller's side. `id` names the session + /// and `owner` is the caller's reference (never the principal); the session's record is written + /// at open. + pub fn open( + codec: C, + locked: SessionConfig, + telephony: bool, + now_ns: u64, + ceiling_secs: Option, + ) -> Self { + Self::open_as(codec, locked, telephony, now_ns, ceiling_secs, "", "") + } + + /// [`Self::open`], naming the session `id` for `owner`. + pub fn open_as( + codec: C, + locked: SessionConfig, + telephony: bool, + now_ns: u64, + ceiling_secs: Option, + id: &str, + owner: &str, + ) -> Self { + let row = VoiceSessionRow { + id: id.to_string(), + owner: owner.to_string(), + turns: 0, + updated_at: now_ns / 1_000_000_000, + terminal: false, + rtc_call_id: None, + }; + SessionUnit { + writes: vec![row.clone()], + row, + pump: SessionPump::new(codec, Some(locked)), + units: CumulativeUnits::default(), + twilio: telephony.then(TwilioBridge::default), + opened_ns: now_ns, + ceiling_secs, + ended: false, + } + } + + /// Bind the node's open-call table for this session. + pub fn bind_governed(&mut self, governed: GovernedSession) { + self.pump.bind_governed(governed); + } + + /// The session's cumulative units, per class in [`CLASSES`] order. + #[must_use] + pub fn units(&self) -> CumulativeUnits { + self.units + } + + /// The record writes not yet handed to the kernel, oldest first; taking them clears them. + pub fn take_writes(&mut self) -> Vec { + std::mem::take(&mut self.writes) + } + + /// The provider named the session's call: it goes on the session's record. + pub fn set_rtc_call_id(&mut self, rtc_call_id: &str, now_s: u64) { + self.row.rtc_call_id = Some(rtc_call_id.to_string()); + self.row.updated_at = now_s; + self.writes.push(self.row.clone()); + } + + /// `true` once the session has ended. + #[must_use] + pub fn ended(&self) -> bool { + self.ended + } + + fn caller_frames(&self, frames: Vec) -> Vec> { + frames + .into_iter() + .filter_map(|w| match &self.twilio { + Some(bridge) => bridge.to_caller(&w.0), + None => Some(w.0.to_vec()), + }) + .collect() + } + + /// A piece from the caller. + pub fn from_caller(&mut self, bytes: &[u8]) -> Plan { + if self.ended { + return Plan::default(); + } + let frame = match self.twilio.as_mut() { + None => WireEvent(Bytes::copy_from_slice(bytes)), + Some(bridge) => match bridge.from_caller(bytes) { + CallerStep::Audio(w) => w, + CallerStep::Stop | CallerStep::Refused => return self.end(), + CallerStep::Nothing => return Plan::default(), + }, + }; + let out = self.pump.on_client_frame(frame); + Plan { + to_far_end: out.upstream.into_iter().map(|w| w.0.to_vec()).collect(), + to_caller: Vec::new(), + end: false, + } + } + + /// A piece from the far end, at `now_ms`. No tool is served on this node: every call is relayed + /// to the caller. + pub fn from_far_end(&mut self, bytes: &[u8], now_ms: u64) -> Plan { + if self.ended { + return Plan::default(); + } + let (out, _) = self.pump.on_server_frame( + WireEvent(Bytes::copy_from_slice(bytes)), + now_ms, + &mut self.units, + &|_: &str| false, + ); + Plan { + to_far_end: out.upstream.into_iter().map(|w| w.0.to_vec()).collect(), + to_caller: self.caller_frames(out.downlink), + end: false, + } + } + + /// The clock, at `now_ns` and `now_ms`: a session past its ceiling is told why and ends; the + /// node's unanswered calls past their deadline are swept. + pub fn tick(&mut self, now_ns: u64, now_ms: u64) -> Plan { + if self.ended { + return Plan::default(); + } + self.pump.sweep_expired(now_ms); + let Some(ceiling) = self.ceiling_secs else { + return Plan::default(); + }; + if now_ns.saturating_sub(self.opened_ns) < ceiling.saturating_mul(1_000_000_000) { + return Plan::default(); + } + let told = self.pump.ceiling_error(ceiling); + let mut plan = self.end(); + plan.to_caller = self.caller_frames(told.into_iter().collect()); + plan + } + + /// The session ends: its open turn settles once, the node forgets its open calls, and the + /// session's record is written terminal. + pub fn end(&mut self) -> Plan { + if !self.ended { + self.ended = true; + self.pump.settle_open_turn(&mut self.units); + self.pump.forget_governed_calls(); + self.row.terminal = true; + self.writes.push(self.row.clone()); + } + Plan { + end: true, + ..Plan::default() + } + } +} + +#[cfg(test)] +#[path = "tests/session_unit_tests.rs"] +mod tests; diff --git a/crates/busbar-plane-streaming/src/tests/codec.rs b/crates/busbar-plane-streaming/src/tests/codec.rs deleted file mode 100644 index 348b75e680..0000000000 --- a/crates/busbar-plane-streaming/src/tests/codec.rs +++ /dev/null @@ -1,1763 +0,0 @@ -//! Codec-path tests: fixtures decode to the expected turn units, interrupt facts and pacing facts. -//! -//! The OpenAI `session.update` fixture below restates (does not literally `include!`, because the -//! entry point shape differs — busbar-voice's own test calls `OpenAiRealtimeCodec::read_up` -//! directly, this one calls `StreamingPlane::decode_ingress`) the fixture at -//! `crates/busbar-voice/src/ir/codec/tests.rs::ga_session_server_vad` (lines 52-74 at the time of -//! writing). The audio-frame and `session.created`/usage fixtures are built from the same wire -//! `type` tokens `crates/busbar-voice/src/ir/codec/mod.rs`'s `wire` module names -//! (`input_audio_buffer.append`, `session.created`, `response.done`). - -use busbar_contract::bounded::{FactValue, Facts, Labels}; -use busbar_contract::ids::LaneId; -use busbar_contract::plane::{Ingress, Plane, PlaneSessionState, Progress, SessionPlane}; -use busbar_contract::wire::FrameCursor; -use serde_json::json; - -use crate::claims::Dialect; -use crate::tests::harness::{ctx, destination, frame, unit, EmptyConfig, LeakPlaneAlloc, WsStack}; -use crate::{StreamingPlane, Upstream}; - -fn openai_plane() -> StreamingPlane { - static UPSTREAMS: &[Upstream] = &[Upstream { - lane: LaneId::new("realtime"), - host: "api.openai.com", - dialect: Dialect::OpenaiRealtime, - }]; - StreamingPlane::new(UPSTREAMS) -} - -fn open_client_session( - plane: &StreamingPlane, - ctx: &busbar_contract::unit::Ctx<'_>, -) -> PlaneSessionState { - SessionPlane::open_session(plane, ctx) -} - -fn client_wire(bytes: &[u8]) -> Vec { - bytes.to_vec() -} - -/// The `session.update` fixture restated from `busbar-voice`'s own `ga_session_server_vad` fixture -/// (`crates/busbar-voice/src/ir/codec/tests.rs`, lines 52-74). -fn session_update_fixture() -> Vec { - serde_json::to_vec(&json!({ - "type": "session.update", - "session": { - "modalities": ["audio", "text"], - "instructions": "You are a helpful voice agent.", - "voice": "marin", - "input_audio_format": "pcm16", - "output_audio_format": "g711_ulaw", - "turn_detection": { - "type": "server_vad", - "threshold": 0.5, - "prefix_padding_ms": 300, - "silence_duration_ms": 200, - "create_response": true, - "interrupt_response": true - }, - "tools": [{ "type": "function", "name": "lookup", "parameters": { "type": "object" } }], - "tool_choice": "auto", - "max_output_tokens": 4096 - } - })) - .expect("fixture serializes") -} - -#[test] -fn session_update_opens_a_turn_and_names_the_dialect() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let bytes = client_wire(&session_update_fixture()); - let frames = [frame(&bytes)]; - let mut cursor = FrameCursor::new(&frames); - - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("session.update decodes"); - let Ingress::Open(draft) = ingress else { - panic!("expected Ingress::Open, got {ingress:?}"); - }; - assert_eq!(draft.op.as_str(), "duplex_turn"); - assert_eq!( - draft.facts.get(crate::meta::FACT_DIALECT), - Some(busbar_contract::bounded::FactValue::Str("openai-realtime")) - ); - assert!(draft.correlation_out.is_some()); -} - -#[test] -fn a_second_frame_of_the_same_turn_relays_rather_than_reopens() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let first = client_wire(&session_update_fixture()); - let frames1 = [frame(&first)]; - let mut cursor1 = FrameCursor::new(&frames1); - let ingress1 = plane - .decode_ingress(&mut cursor1, Some(&mut state), &c) - .expect("first frame decodes"); - assert!(matches!(ingress1, Ingress::Open(_))); - - let audio = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.append", - "audio": "AAAA", - })) - .expect("audio fixture serializes"); - let frames2 = [frame(&audio)]; - let mut cursor2 = FrameCursor::new(&frames2); - let ingress2 = plane - .decode_ingress(&mut cursor2, Some(&mut state), &c) - .expect("second frame decodes"); - let Ingress::Frame { for_, .. } = ingress2 else { - panic!("expected Ingress::Frame, got {ingress2:?}"); - }; - assert!( - for_.is_some(), - "the relay frame must carry the turn's correlation" - ); -} - -#[test] -fn client_truncate_writes_the_declared_interrupt_fact() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let truncate = serde_json::to_vec(&json!({ - "type": "conversation.item.truncate", - "item_id": "item_1", - "content_index": 0, - "audio_end_ms": 640, - })) - .expect("truncate fixture serializes"); - let frames = [frame(&truncate)]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("truncate decodes"); - let Ingress::Open(draft) = ingress else { - panic!("expected Ingress::Open (first frame of the turn), got {ingress:?}"); - }; - assert_eq!( - draft - .facts - .get(::INTERRUPT_FACT.unwrap()), - Some(busbar_contract::bounded::FactValue::Int(640)) - ); -} - -#[test] -fn upstream_speech_started_synthesizes_the_interrupt_fact_from_playback_position() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut upstream_state = SessionPlane::open_upstream( - &plane, - &destination("api.openai.com", LaneId::new("realtime")), - &c, - ); - - // Two downlink audio deltas totalling 96 bytes of pcm16 (48 bytes/ms) = 2 ms played, then a - // speech-started barge-in signal. - let delta = serde_json::to_vec(&json!({ - "type": "response.output_audio.delta", - "delta": base64_of(&[0u8; 96]), - })) - .unwrap(); - let started = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.speech_started", - "audio_start_ms": 0, - "item_id": "item_1", - })) - .unwrap(); - - let frames1 = [frame(&delta)]; - let mut cursor1 = FrameCursor::new(&frames1); - let _ = plane - .decode_response( - &mut cursor1, - &destination("api.openai.com", LaneId::new("realtime")), - Some(&mut upstream_state), - &c, - ) - .expect("audio delta decodes"); - - let frames2 = [frame(&started)]; - let mut cursor2 = FrameCursor::new(&frames2); - let progress = plane - .decode_response( - &mut cursor2, - &destination("api.openai.com", LaneId::new("realtime")), - Some(&mut upstream_state), - &c, - ) - .expect("speech_started decodes"); - let Progress::Frame { r, .. } = progress else { - panic!("expected Progress::Frame, got {progress:?}"); - }; - assert_eq!( - r.facts - .get(::INTERRUPT_FACT.unwrap()), - Some(busbar_contract::bounded::FactValue::Int(2)) - ); -} - -#[test] -fn downlink_audio_frames_carry_the_declared_pacing_fact() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut upstream_state = SessionPlane::open_upstream( - &plane, - &destination("api.openai.com", LaneId::new("realtime")), - &c, - ); - - let delta = serde_json::to_vec(&json!({ - "type": "response.output_audio.delta", - "delta": base64_of(&[0u8; 48]), - })) - .unwrap(); - let frames = [frame(&delta)]; - let mut cursor = FrameCursor::new(&frames); - let progress = plane - .decode_response( - &mut cursor, - &destination("api.openai.com", LaneId::new("realtime")), - Some(&mut upstream_state), - &c, - ) - .expect("audio delta decodes"); - let Progress::Frame { r, .. } = progress else { - panic!("expected Progress::Frame, got {progress:?}"); - }; - assert_eq!( - r.facts.get( - ::EGRESS_PACING_FACT.unwrap() - ), - Some(busbar_contract::bounded::FactValue::Int(1)) - ); -} - -/// A tool call is dispatched ON CLOSE, as its own unit, carrying the arguments the model streamed. -/// -/// The model announces the call (`output_item.added`), streams its arguments as deltas, and states -/// them complete on the `…arguments.done` frame. Only when the arguments are complete is the call -/// its own `tool_call` unit — dispatched with a name AND its arguments, not a name and nothing. -/// The open and delta frames mint nothing on their own; they accumulate. -#[test] -fn a_tool_call_dispatches_on_close_with_its_arguments() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let dest = destination("api.openai.com", LaneId::new("realtime")); - let mut upstream_state = SessionPlane::open_upstream(&plane, &dest, &c); - - // The announcement mints nothing — it accumulates. - let opened = serde_json::to_vec(&json!({ - "type": "response.output_item.added", - "item": { "type": "function_call", "call_id": "call_1", "name": "lookup" }, - })) - .unwrap(); - { - let frames = [frame(&opened)]; - let mut cursor = FrameCursor::new(&frames); - assert!( - matches!( - plane - .decode_response(&mut cursor, &dest, Some(&mut upstream_state), &c) - .expect("an announcement decodes"), - Progress::Discard { .. } - ), - "an announcement mints no unit on its own" - ); - } - - // A partial argument delta mints nothing either — it accumulates. - let delta = serde_json::to_vec(&json!({ - "type": "response.function_call_arguments.delta", - "call_id": "call_1", - "delta": "{\"city\":\"Pa", - })) - .unwrap(); - { - let frames = [frame(&delta)]; - let mut cursor = FrameCursor::new(&frames); - assert!( - matches!( - plane - .decode_response(&mut cursor, &dest, Some(&mut upstream_state), &c) - .expect("a delta decodes"), - Progress::Discard { .. } - ), - "an argument delta mints no unit on its own" - ); - } - - // The close states the complete arguments and dispatches the call. - let done = serde_json::to_vec(&json!({ - "type": "response.function_call_arguments.done", - "call_id": "call_1", - "name": "lookup", - "arguments": "{\"city\":\"Paris\"}", - })) - .unwrap(); - let frames = [frame(&done)]; - let mut cursor = FrameCursor::new(&frames); - let Progress::OneShot(draft) = plane - .decode_response(&mut cursor, &dest, Some(&mut upstream_state), &c) - .expect("a tool-call close decodes") - else { - panic!("a completed tool call is dispatched as its own unit"); - }; - assert_eq!(draft.op.as_str(), "tool_call"); - assert_eq!( - draft.facts.get(crate::meta::FACT_TOOL_NAME), - Some(busbar_contract::bounded::FactValue::Str("lookup")) - ); - // The arguments the model asked for reach the executor, not an empty body. - let args: serde_json::Value = - serde_json::from_slice(draft.body_ir.body()).expect("the body carries the arguments"); - assert_eq!(args["city"], "Paris"); -} - -/// Two tool calls open at once are two different things to wait on. -/// -/// A turn that asks for two tools opens two units, and each waits for its own reply. The reply leg -/// used to name the constant zero for every one of them, so the two were indistinguishable: the -/// first reply satisfied whichever leg was found first, and the second call waited out its whole -/// deadline against an answer that had already been delivered elsewhere. -/// -/// What the leg names is the declared KEY; what tells the two calls apart is the identifier the -/// draft mints under it, in the unit's own arena, as itself. This plane states neither twice — it -/// no longer folds the identifier into a number to get a copy of it onto the sealed leg. -#[test] -fn two_open_tool_calls_wait_on_two_different_correlations() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let dest = destination("api.openai.com", LaneId::new("realtime")); - let mut upstream_state = SessionPlane::open_upstream(&plane, &dest, &c); - - let mut waits_on = |call_id: &str| { - // Drive the whole call: announce, then close with its arguments. The call is its own unit - // on close. - let opened = serde_json::to_vec(&json!({ - "type": "response.output_item.added", - "item": { "type": "function_call", "call_id": call_id, "name": "lookup" }, - })) - .unwrap(); - let frames = [frame(&opened)]; - let mut cursor = FrameCursor::new(&frames); - let _ = plane - .decode_response(&mut cursor, &dest, Some(&mut upstream_state), &c) - .expect("a tool-call announcement decodes"); - let done = serde_json::to_vec(&json!({ - "type": "response.function_call_arguments.done", - "call_id": call_id, - "name": "lookup", - "arguments": "{\"q\":\"x\"}", - })) - .unwrap(); - let frames = [frame(&done)]; - let mut cursor = FrameCursor::new(&frames); - let Progress::OneShot(draft) = plane - .decode_response(&mut cursor, &dest, Some(&mut upstream_state), &c) - .expect("a tool-call close decodes") - else { - panic!("a completed tool call is its own unit"); - }; - let out = draft - .correlation_out - .expect("a tool call mints a correlation"); - // The draft's own correlation carries the identifier itself, not a fold of it. - assert_eq!( - out.value, - busbar_contract::ids::CorrelationValue::Str(call_id) - ); - let unit = crate::tests::harness::unit(draft.op, draft.body_ir, draft.facts); - match plane.verify(&unit, &c) { - busbar_contract::dest::DestinationFacts::Client { - mode: - busbar_contract::dest::ClientMode::AwaitReply { - correlation_key, .. - }, - .. - } => { - // The leg names the key the draft minted under. If those two ever disagreed the - // kernel would have a wait it could never satisfy. - assert_eq!(correlation_key, out.fact_key); - format!("{:?}", out.value) - } - other => panic!("a tool call is delivered to the client, got {other:?}"), - } - }; - - let first = waits_on("call_1"); - let second = waits_on("call_2"); - assert_ne!( - first, second, - "two open tool calls waited on the same correlation" - ); - assert_eq!( - second, - waits_on("call_2"), - "the same call named two different correlations" - ); -} - -/// A tool reply names the call it answers, not the turn it arrived on. -/// -/// The reply is the other half of the leg the call planned. Decoded as an ordinary frame of the -/// open turn it named the turn's correlation, so the identifier the waiting unit was entered under -/// never reached the frame at all — the wait and its answer went past each other on one session, -/// and the call expired holding a reservation for an answer that had already come back. -#[test] -fn a_client_tool_reply_names_the_call_it_answers() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - // The turn is open first, so "it relayed onto the turn" is a live alternative rather than - // something the fixture ruled out. - let opening = client_wire(&session_update_fixture()); - let frames = [frame(&opening)]; - let mut cursor = FrameCursor::new(&frames); - let opened = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("the turn opens"); - let Ingress::Open(turn) = opened else { - panic!("expected Ingress::Open, got {opened:?}"); - }; - let turn_correlation = turn.correlation_out.expect("a turn correlates"); - - let reply = serde_json::to_vec(&json!({ - "type": "conversation.item.create", - "item": { - "type": "function_call_output", - "call_id": "call_2", - "output": "{\"tide\":\"out\"}", - }, - })) - .expect("the reply fixture serializes"); - let frames = [frame(&reply)]; - let mut cursor = FrameCursor::new(&frames); - let answered = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("a tool reply decodes"); - let Ingress::Frame { for_, facts, .. } = answered else { - panic!("expected Ingress::Frame, got {answered:?}"); - }; - let for_ = for_.expect("a reply carries the correlation it answers"); - assert_eq!( - for_.fact_key, - crate::plane::FACT_TOOL_CORRELATION, - "under the key the call's own leg named" - ); - assert_eq!( - for_.value, - busbar_contract::ids::CorrelationValue::Str("call_2"), - "and carrying the identifier itself" - ); - assert_ne!( - for_.value, turn_correlation.value, - "a reply that named the turn would wake no tool call at all" - ); - assert_eq!( - facts.get(crate::meta::FACT_CALL_ID), - Some(FactValue::Str("call_2")) - ); -} - -#[test] -fn usage_closes_the_turn_and_meter_reads_every_declared_class() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut upstream_state = SessionPlane::open_upstream( - &plane, - &destination("api.openai.com", LaneId::new("realtime")), - &c, - ); - - let done = serde_json::to_vec(&json!({ - "type": "response.done", - "response": { - "usage": { - "input_token_details": { "audio_tokens": 10, "text_tokens": 3, "cached_tokens": 1 }, - "output_token_details": { "audio_tokens": 20, "text_tokens": 4 }, - } - } - })) - .unwrap(); - let frames = [frame(&done)]; - let mut cursor = FrameCursor::new(&frames); - let unit_dest = destination("api.openai.com", LaneId::new("realtime")); - let progress = plane - .decode_response(&mut cursor, &unit_dest, Some(&mut upstream_state), &c) - .expect("response.done decodes"); - let Progress::Terminal { r, .. } = progress else { - panic!("expected Progress::Terminal, got {progress:?}"); - }; - assert_eq!(r.finish, busbar_contract::unit::FinishClass::TurnComplete); - - let unit = crate::tests::harness::unit( - busbar_contract::ids::OpClassId::new("duplex_turn"), - r.ir, - Facts::new(), - ); - let locators = plane.meter(&unit, &r, &c); - let classes: Vec<&str> = locators - .lines - .as_slice() - .iter() - .map(|l| l.class.as_str()) - .collect(); - for expected in [ - "audio_tokens_in", - "audio_tokens_out", - "text_tokens_in", - "text_tokens_out", - "audio_seconds_in", - ] { - assert!( - classes.contains(&expected), - "expected meter class {expected} in {classes:?}" - ); - } - // #71: cached_tokens is a SUBSET of audio_tokens_in/text_tokens_in, not a class beside them, so - // the usage report naming it must NOT produce a meter line for it — pricing it too would - // double-bill the same cached input. - assert!( - !classes.contains(&"cached_tokens"), - "cached_tokens must never be a meter class (#71); got {classes:?}" - ); - - // The point of the split, asserted as a quantity and not just as a label: the fixture reports - // three text tokens consumed and four emitted, and the two land on different classes at their - // reported figures. Summed into one input-direction class -- which is what this plane used to do - // -- the emitted four would have priced at the input rate. - let quantity = |class: &str| { - locators - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == class) - .and_then(|l| l.quantity) - }; - assert_eq!(quantity("text_tokens_in"), Some(3)); - assert_eq!(quantity("text_tokens_out"), Some(4)); - // The audio split is the one that moves real money -- audio prices well above text on every - // realtime rate card -- so it is asserted the same way: the fixture reports ten audio tokens - // consumed and twenty emitted, and swapping the two directions has to be a failure here rather - // than a label that still reads as present. - assert_eq!(quantity("audio_tokens_in"), Some(10)); - assert_eq!(quantity("audio_tokens_out"), Some(20)); - // The cached figure is a SUBSET of the input figure above, not a class beside it (#71): pricing - // it too would double-bill the same cached input, so it never reaches the meter as its own line. - assert_eq!(quantity("cached_tokens"), None); -} - -#[test] -fn twilio_media_after_start_admits_a_ulaw_audio_frame() { - static UPSTREAMS: &[Upstream] = &[Upstream { - lane: LaneId::new("realtime"), - host: "api.openai.com", - dialect: Dialect::OpenaiRealtime, - }]; - let plane = StreamingPlane::new(UPSTREAMS); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/twilio/call-123"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - // The dialect is bound directly rather than resolved from the path. The telephony CLAIM is - // gone — its transport has no crate — so no selector maps `/twilio/...` onto this dialect any - // more; the CODEC is what this cell is about and it is untouched. Binding the state here is - // what an arrival on a registered telephony transport would do. - let mut state = PlaneSessionState::new(crate::session::VoiceSessionState::for_dialect( - Dialect::TwilioMediaStreams, - )); - - let start = serde_json::to_vec(&json!({ - "event": "start", - "start": { - "streamSid": "MZ123", - "callSid": "CA123", - "mediaFormat": { "encoding": "audio/x-mulaw", "sampleRate": 8000, "channels": 1 }, - }, - })) - .unwrap(); - let frames1 = [frame(&start)]; - let mut cursor1 = FrameCursor::new(&frames1); - let ingress1 = plane - .decode_ingress(&mut cursor1, Some(&mut state), &c) - .expect("start decodes"); - assert!(matches!(ingress1, Ingress::Discard { .. })); - - let media = serde_json::to_vec(&json!({ - "event": "media", - "streamSid": "MZ123", - "media": { "payload": base64_of(&[0xFFu8; 4]) }, - })) - .unwrap(); - let frames2 = [frame(&media)]; - let mut cursor2 = FrameCursor::new(&frames2); - let ingress2 = plane - .decode_ingress(&mut cursor2, Some(&mut state), &c) - .expect("media decodes"); - assert!(matches!(ingress2, Ingress::Open(_))); -} - -#[test] -fn twilio_media_with_a_forged_stream_sid_is_discarded() { - static UPSTREAMS: &[Upstream] = &[Upstream { - lane: LaneId::new("realtime"), - host: "api.openai.com", - dialect: Dialect::OpenaiRealtime, - }]; - let plane = StreamingPlane::new(UPSTREAMS); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/twilio/call-123"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - // The dialect is bound directly rather than resolved from the path. The telephony CLAIM is - // gone — its transport has no crate — so no selector maps `/twilio/...` onto this dialect any - // more; the CODEC is what this cell is about and it is untouched. Binding the state here is - // what an arrival on a registered telephony transport would do. - let mut state = PlaneSessionState::new(crate::session::VoiceSessionState::for_dialect( - Dialect::TwilioMediaStreams, - )); - - let start = serde_json::to_vec(&json!({ - "event": "start", - "start": { - "streamSid": "MZ-bound", - "callSid": "CA123", - "mediaFormat": { "encoding": "audio/x-mulaw", "sampleRate": 8000, "channels": 1 }, - }, - })) - .unwrap(); - let frames1 = [frame(&start)]; - let mut cursor1 = FrameCursor::new(&frames1); - let _ = plane - .decode_ingress(&mut cursor1, Some(&mut state), &c) - .expect("start decodes"); - - let media = serde_json::to_vec(&json!({ - "event": "media", - "streamSid": "MZ-forged", - "media": { "payload": base64_of(&[0xFFu8; 4]) }, - })) - .unwrap(); - let frames2 = [frame(&media)]; - let mut cursor2 = FrameCursor::new(&frames2); - let ingress2 = plane - .decode_ingress(&mut cursor2, Some(&mut state), &c) - .expect("media decodes"); - assert!(matches!( - ingress2, - Ingress::Discard { - reason: busbar_contract::wire::DiscardCode::ForgedSource - } - )); -} - -#[test] -fn twilio_dtmf_decodes_and_is_discarded_as_unsupported() { - static UPSTREAMS: &[Upstream] = &[Upstream { - lane: LaneId::new("realtime"), - host: "api.openai.com", - dialect: Dialect::OpenaiRealtime, - }]; - let plane = StreamingPlane::new(UPSTREAMS); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/twilio/call-123"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = PlaneSessionState::new(crate::session::VoiceSessionState::for_dialect( - Dialect::TwilioMediaStreams, - )); - - let dtmf = serde_json::to_vec(&json!({ - "event": "dtmf", - "streamSid": "MZ123", - "sequenceNumber": "5", - "dtmf": { "track": "inbound_track", "digit": "5" }, - })) - .unwrap(); - let frames = [frame(&dtmf)]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("dtmf decodes, not malformed"); - assert!(matches!( - ingress, - Ingress::Discard { - reason: busbar_contract::wire::DiscardCode::Unsupported - } - )); -} - -/// An event this carrier adds tomorrow is dropped, and the frame that is not this carrier's shape -/// at all is still refused. -/// -/// The carrier publishes new event names over the life of the protocol and tells its clients to -/// ignore the ones they do not recognise; the transport under this session already discards what it -/// cannot place. Refusing the frame ended a live call on the day a new lifecycle event shipped — -/// for a message that carries no audio and asks for nothing. -#[test] -fn twilio_unknown_event_is_dropped_and_a_non_carrier_frame_is_still_refused() { - static UPSTREAMS: &[Upstream] = &[Upstream { - lane: LaneId::new("realtime"), - host: "api.openai.com", - dialect: Dialect::OpenaiRealtime, - }]; - let plane = StreamingPlane::new(UPSTREAMS); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/twilio/call-123"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = PlaneSessionState::new(crate::session::VoiceSessionState::for_dialect( - Dialect::TwilioMediaStreams, - )); - - let unknown = serde_json::to_vec(&json!({ - "event": "teleport", - "streamSid": "MZ123", - })) - .unwrap(); - let frames = [frame(&unknown)]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("an event this reader does not model is dropped, not refused"); - assert!(matches!( - ingress, - Ingress::Discard { - reason: busbar_contract::wire::DiscardCode::Unsupported - } - )); - - // The line the discard does not cross: bytes that are not this carrier's JSON at all. - let garbage = [frame(b"not a carrier frame at all")]; - let mut cursor = FrameCursor::new(&garbage); - assert_eq!( - plane.decode_ingress(&mut cursor, Some(&mut state), &c), - Err(busbar_contract::wire::Decode::Malformed) - ); -} - -/// A turn the upstream ends with an error consumed exactly as much of the caller's time and ran -/// exactly as many tools as one it ends with a usage report, and the two counters this plane derives -/// itself are the only record of either. Forty seconds of uplink and two tool calls, then an error -/// frame: both figures must still reach the meter. -#[test] -fn an_upstream_error_still_meters_the_turn_it_ended() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - // PCM16 at 24 kHz is 48 bytes per millisecond, so a second of uplink is 48 000 bytes. Forty of - // them is the forty seconds the caller spoke. - let one_second = vec![0u8; 48_000]; - let relay_unit = crate::tests::harness::unit( - busbar_contract::ids::OpClassId::new("duplex_turn"), - busbar_contract::bounded::Ir::empty(), - Facts::new(), - ); - for _ in 0..40 { - let append = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.append", - "audio": base64_of(&one_second), - })) - .expect("audio fixture serializes"); - let frames = [frame(&append)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("an uplink audio frame decodes"); - // And then relays, which is where the caller's seconds are counted. - plane - .encode_ingress_frame( - &relay_unit, - &frames[0], - &destination("api.openai.com", LaneId::new("realtime")), - Some(&mut state), - &c, - ) - .expect("an uplink audio frame relays"); - } - - for call_id in ["call_1", "call_2"] { - let opened = serde_json::to_vec(&json!({ - "type": "response.output_item.added", - "item": { "type": "function_call", "call_id": call_id, "name": "lookup" }, - })) - .expect("tool-call fixture serializes"); - let frames = [frame(&opened)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_response( - &mut cursor, - &destination("api.openai.com", LaneId::new("realtime")), - Some(&mut state), - &c, - ) - .expect("a tool-call open decodes"); - } - - let err = serde_json::to_vec(&json!({ - "type": "error", - "error": { "code": "rate_limit_exceeded", "message": "slow down" }, - })) - .expect("error fixture serializes"); - let frames = [frame(&err)]; - let mut cursor = FrameCursor::new(&frames); - let unit_dest = destination("api.openai.com", LaneId::new("realtime")); - let progress = plane - .decode_response(&mut cursor, &unit_dest, Some(&mut state), &c) - .expect("an error frame decodes"); - let Progress::Terminal { r, .. } = progress else { - panic!("expected Progress::Terminal, got {progress:?}"); - }; - assert_eq!(r.finish, busbar_contract::unit::FinishClass::Error); - - let unit = crate::tests::harness::unit( - busbar_contract::ids::OpClassId::new("duplex_turn"), - r.ir, - Facts::new(), - ); - let locators = plane.meter(&unit, &r, &c); - let quantity = |class: &str| { - locators - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == class) - .and_then(|l| l.quantity) - }; - // Forty seconds of admitted audio, metered in the seconds the class is denominated in. - assert_eq!(quantity("audio_seconds_in"), Some(40)); - assert_eq!(quantity("tool_calls"), Some(2)); -} - -/// The caller's audio is metered on the half the answer comes back on. -/// -/// A session holds one state per CONNECTION: the client's, and one per upstream it dials. The -/// uplink counter used to be taken at decode, against the client's half, and read back at the -/// response step, against the upstream's — a different value entirely, and always zero. Every -/// second a customer spoke on a duplex turn metered at nothing. -#[test] -fn uplink_audio_meters_on_the_half_the_upstream_answer_arrives_on() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let dest = destination("api.openai.com", LaneId::new("realtime")); - let mut client = open_client_session(&plane, &c); - let mut upstream = SessionPlane::open_upstream(&plane, &dest, &c); - - // The turn opens on a control event, so the audio that follows is a relayed frame rather than - // the unit's own body: the frame under test is the ordinary one, of which a call carries fifty - // a second. - let opening = client_wire(&session_update_fixture()); - let frames = [frame(&opening)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::Open(draft) = plane - .decode_ingress(&mut cursor, Some(&mut client), &c) - .expect("the turn opens") - else { - panic!("the first client event opens the turn"); - }; - let unit = crate::tests::harness::unit(draft.op, draft.body_ir, draft.facts); - - // One second of PCM16 at 24 kHz. - let append = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.append", - "audio": base64_of(&vec![0u8; 48_000]), - })) - .expect("audio fixture serializes"); - let frames = [frame(&append)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_ingress(&mut cursor, Some(&mut client), &c) - .expect("an uplink audio frame decodes"); - plane - .encode_ingress_frame(&unit, &frames[0], &dest, Some(&mut upstream), &c) - .expect("an uplink audio frame relays to the provider"); - - let done = serde_json::to_vec(&json!({ - "type": "response.done", - "response": { "usage": { "input_token_details": { "audio_tokens": 1 } } } - })) - .expect("usage fixture serializes"); - let frames = [frame(&done)]; - let mut cursor = FrameCursor::new(&frames); - let Progress::Terminal { r, .. } = plane - .decode_response(&mut cursor, &dest, Some(&mut upstream), &c) - .expect("the usage report decodes") - else { - panic!("a usage report ends the turn"); - }; - - let seconds = plane - .meter(&unit, &r, &c) - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == "audio_seconds_in") - .and_then(|l| l.quantity); - assert_eq!( - seconds, - Some(1), - "the second the caller spoke was relayed to the provider and must be metered" - ); -} - -/// The duration class is denominated in seconds, and the counter behind it is in milliseconds. -/// -/// The design names the class `audio_seconds_in`. This plane counts milliseconds, because that is -/// what a frame's byte count divides down to. Pushed through verbatim, three seconds of admitted -/// audio settled as three thousand seconds -- a thousandfold over-report on a duration-priced class. -#[test] -fn admitted_milliseconds_meter_as_seconds() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let cfg = EmptyConfig; - let stack = WsStack::new("/v1/realtime"); - let labels = Labels::default(); - let c = ctx(&arena, &cfg, &stack, &labels); - - let seconds_line = |ms: i64| { - let mut facts = Facts::new(); - facts - .set(crate::meta::FACT_AUDIO_MS_IN, FactValue::Int(ms)) - .expect("fits"); - let response = busbar_contract::plane::Response { - ir: busbar_contract::bounded::Ir::new(b"{}", &[]), - finish: busbar_contract::unit::FinishClass::TurnComplete, - facts, - }; - let unit = crate::tests::harness::unit( - busbar_contract::ids::OpClassId::new("duplex_turn"), - response.ir, - Facts::new(), - ); - plane - .meter(&unit, &response, &c) - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == "audio_seconds_in") - .and_then(|l| l.quantity) - }; - - assert_eq!(seconds_line(3_000), Some(3)); - // A part-second rounds up rather than vanishing: audio that arrived is not audio that cost - // nothing, and a zero line settles exactly as an absent one. - assert_eq!(seconds_line(1), Some(1)); - assert_eq!(seconds_line(1_500), Some(2)); - assert_eq!(seconds_line(0), Some(0)); -} - -/// A one-shot request sees its own answer, and the unit meters what it was priced on. -/// -/// A transcribe or speech unit is admitted with no session, because it is one request and one -/// answer. The response step demanded a session anyway, so the answer was a decode refusal and the -/// unit metered nothing at all: a whole operation class billed at zero. -#[test] -fn a_one_shot_text_to_speech_unit_meters_the_text_it_was_asked_to_speak() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/audio/speech"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - - let request = - serde_json::to_vec(&json!({ "input": "eight chars of text here", "voice": "marin" })) - .expect("request fixture serializes"); - let frames = [frame(&request)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::OneShot(draft) = plane - .decode_ingress(&mut cursor, None, &c) - .expect("a speech request is one whole unit") - else { - panic!("a one-shot request admits as one unit"); - }; - assert_eq!(draft.op.as_str(), "tts"); - - // The answer is audio, and it arrives with no session because there never was one. - let audio = [0u8; 64]; - let answers = [frame(&audio)]; - let mut answer_cursor = FrameCursor::new(&answers); - let Progress::Terminal { r, .. } = plane - .decode_response( - &mut answer_cursor, - &destination("api.openai.com", LaneId::new("realtime")), - None, - &c, - ) - .expect("a one-shot answer decodes without a session") - else { - panic!("a one-shot answer ends the unit"); - }; - - let unit = crate::tests::harness::unit(draft.op, draft.body_ir, draft.facts); - let locators = plane.meter(&unit, &r, &c); - let quantity = |class: &str| { - locators - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == class) - .and_then(|l| l.quantity) - }; - // "eight chars of text here" is 24 bytes, six tokens at the class's own declared divisor. - assert_eq!(quantity("text_tokens_in"), Some(6)); -} - -/// A one-shot transcription's answer states the text it produced, and that is what it prices at. -#[test] -fn a_one_shot_transcription_meters_the_text_it_returned() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/audio/transcriptions"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - - let audio = [0u8; 64]; - let frames = [frame(&audio)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::OneShot(draft) = plane - .decode_ingress(&mut cursor, None, &c) - .expect("a transcription request is one whole unit") - else { - panic!("a one-shot request admits as one unit"); - }; - assert_eq!(draft.op.as_str(), "transcribe"); - - let answer = serde_json::to_vec(&json!({ "text": "twelve bytes" })).unwrap(); - let answers = [frame(&answer)]; - let mut answer_cursor = FrameCursor::new(&answers); - let Progress::Terminal { r, .. } = plane - .decode_response( - &mut answer_cursor, - &destination("api.openai.com", LaneId::new("realtime")), - None, - &c, - ) - .expect("a one-shot answer decodes without a session") - else { - panic!("a one-shot answer ends the unit"); - }; - - let unit = crate::tests::harness::unit(draft.op, draft.body_ir, draft.facts); - let quantity = |class: &str| { - plane - .meter(&unit, &r, &c) - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == class) - .and_then(|l| l.quantity) - }; - // "twelve bytes" is twelve bytes, three tokens at the class's own declared divisor. - assert_eq!(quantity("text_tokens_out"), Some(3)); -} - -/// A tiny standard base64 encoder, independent of the one this crate's `twilio` module carries, so -/// the test fixtures above do not depend on that module's own correctness to construct their input. -fn base64_of(bytes: &[u8]) -> String { - use base64_stdlib::encode; - encode(bytes) -} - -mod base64_stdlib { - const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - pub fn encode(data: &[u8]) -> String { - let mut out = String::new(); - for chunk in data.chunks(3) { - let b0 = u32::from(chunk[0]); - let b1 = u32::from(*chunk.get(1).unwrap_or(&0)); - let b2 = u32::from(*chunk.get(2).unwrap_or(&0)); - let n = (b0 << 16) | (b1 << 8) | b2; - out.push(ALPHABET[((n >> 18) & 63) as usize] as char); - out.push(ALPHABET[((n >> 12) & 63) as usize] as char); - out.push(if chunk.len() > 1 { - ALPHABET[((n >> 6) & 63) as usize] as char - } else { - '=' - }); - out.push(if chunk.len() > 2 { - ALPHABET[(n & 63) as usize] as char - } else { - '=' - }); - } - out - } -} - -#[test] -fn ws_frame_with_invalid_utf8_fails_closed_rather_than_hanging_on_need_more() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let invalid_utf8: &[u8] = &[0x74, 0x79, 0x70, 0x65, 0xff, 0xfe]; - let frames = [frame(invalid_utf8)]; - let mut cursor = FrameCursor::new(&frames); - let err = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect_err("a frame that cannot be read as UTF-8 is a refusal, not a partial read"); - assert!(matches!(err, busbar_contract::wire::Decode::Malformed)); -} - -#[test] -fn ws_frame_with_an_unrecognised_event_is_dropped_never_left_pending() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let unrecognised = serde_json::to_vec(&json!({ - "type": "teleport.now", - "destination": "mars", - })) - .unwrap(); - let frames = [frame(&unrecognised)]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("an unrecognised event decodes to a discard, not an error"); - assert!(matches!( - ingress, - Ingress::Discard { - reason: busbar_contract::wire::DiscardCode::Unsupported - } - )); -} - -#[test] -fn ws_frame_carrying_no_bytes_yet_is_genuinely_need_more() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let frames = [frame(&[])]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("an empty frame is a partial read, not a refusal"); - assert!(matches!(ingress, Ingress::NeedMore)); -} - -/// A refused session is told a class, never this node's own vocabulary. -/// -/// The reason a unit stops is a kernel enum whose names are the money, the buckets and the journal. -/// Rendered onto the wire verbatim it made every internal ceiling a client-visible `error.code` — -/// a name no dialect documents and no client library has a case for. -#[test] -fn a_refusal_renders_an_opaque_code_not_the_internal_reason() { - use busbar_contract::unit::{Refusal, RefusalReason, Step}; - - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - - const OPAQUE: &[&str] = &[ - "invalid_request", - "unauthorized", - "forbidden", - "rate_limited", - "unavailable", - "internal", - ]; - let reasons = [ - RefusalReason::OverdraftCeiling, - RefusalReason::StaleSlice, - RefusalReason::DurabilityUnavailable, - RefusalReason::ScopeMissing, - RefusalReason::CredentialRejected, - RefusalReason::NoDestination, - RefusalReason::RateLimited, - RefusalReason::BodyTooLarge, - RefusalReason::PlanePanic, - ]; - for reason in reasons { - let refusal = Refusal { - step: Step::Decode, - reason, - retry_after_secs: None, - stream: None, - correlates: None, - }; - let bytes = plane - .encode_refusal(&refusal, None, None, &c) - .expect("a refusal renders"); - let parsed: serde_json::Value = - serde_json::from_slice(bytes.as_slice()).expect("the refusal is this dialect's JSON"); - assert_eq!(parsed["type"], "error", "the dialect's own error event"); - let code = parsed["error"]["code"] - .as_str() - .expect("the error names a code"); - assert!( - OPAQUE.contains(&code), - "{reason:?} rendered {code:?}, which is outside the codes a client may be told" - ); - assert!( - parsed["error"]["message"].is_string(), - "the error carries a message" - ); - } -} - -/// A barge-in on an OPEN turn opens the turn that takes over. -/// -/// The scheduler reads the interrupt fact off an open and nowhere else — that is the one dispatch -/// that reaches the compare-and-set which supersedes the unit in flight. Delivered as one more -/// frame of the turn it interrupts, the fact named the superseded unit to nobody: the interrupted -/// turn kept the direction's slot and kept pricing while the caller was already speaking over it. -#[test] -fn a_barge_in_on_an_open_turn_opens_the_turn_that_supersedes_it() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - // A turn is open and speaking. - let opening = client_wire(&session_update_fixture()); - let frames = [frame(&opening)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::Open(first) = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("the turn opens") - else { - panic!("the first client event opens a turn"); - }; - - // The caller talks over it. - let truncate = serde_json::to_vec(&json!({ - "type": "conversation.item.truncate", - "item_id": "item_1", - "content_index": 0, - "audio_end_ms": 640, - })) - .expect("truncate fixture serializes"); - let frames = [frame(&truncate)]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("the barge-in decodes"); - let Ingress::Open(second) = ingress else { - panic!("a barge-in opens the turn that takes over, got {ingress:?}"); - }; - assert_eq!( - second - .facts - .get(::INTERRUPT_FACT.unwrap()), - Some(FactValue::Int(640)), - "the open carries the interrupt the scheduler supersedes on" - ); - assert_ne!( - second - .correlation_out - .expect("the new turn correlates") - .value, - first - .correlation_out - .expect("the first turn correlates") - .value, - "a late frame of the superseded turn must not relay onto the one that replaced it" - ); -} - -/// Unit 0's egress body — the FIRST upstream message a fresh duplex session sends — must be the -/// opening client frame re-framed onto the upstream dialect, not an empty byte pass-through. A -/// session that opens with `session.update` used to relay an EMPTY body, so the provider was handed -/// nothing and voice/instructions/tools/turn_detection silently never applied. -#[test] -fn unit_zero_egress_reframes_the_opening_session_update() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let bytes = client_wire(&session_update_fixture()); - let frames = [frame(&bytes)]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("session.update decodes"); - let Ingress::Open(draft) = ingress else { - panic!("expected Ingress::Open, got {ingress:?}"); - }; - let draft = *draft; - let u = unit(draft.op, draft.body_ir, draft.facts); - - let dest = destination("api.openai.com", LaneId::new("realtime")); - let egress = plane - .encode_egress(&u, &dest, Some(&mut state), &c) - .expect("Unit 0 encodes an egress body"); - - let sent = egress.body.as_slice(); - assert!( - !sent.is_empty(), - "Unit 0's egress body must carry the opening frame, not an empty relay" - ); - let value: serde_json::Value = - serde_json::from_slice(sent).expect("the egress body is a shaped upstream JSON message"); - assert_eq!( - value.get("type").and_then(serde_json::Value::as_str), - Some("session.update"), - "the opening frame reaches the provider as a session.update" - ); - let session = value - .get("session") - .expect("the session config the client asked to apply travels upstream"); - assert_eq!( - session.get("voice").and_then(serde_json::Value::as_str), - Some("marin"), - "the negotiated voice must reach the provider" - ); - assert_eq!( - session - .get("instructions") - .and_then(serde_json::Value::as_str), - Some("You are a helpful voice agent."), - "the instructions must reach the provider" - ); - assert!( - session.get("turn_detection").is_some(), - "turn_detection must reach the provider" - ); - assert!( - session.get("tools").is_some(), - "the tool set must reach the provider" - ); -} - -/// A session whose FIRST frame is audio must have Unit 0 reach the provider as a dialect-shaped -/// `input_audio_buffer.append`, never as the raw PCM the byte pass-through shipped. The session's -/// stash is cleared first so the seam re-reads the opening frame off Unit 0's own body — proving the -/// body carries the client's wire frame (the fix), not the extracted relay. -#[test] -fn unit_zero_egress_reframes_an_opening_audio_frame() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let mut state = open_client_session(&plane, &c); - - let audio = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.append", - "audio": "AAAA", - })) - .expect("audio fixture serializes"); - let frames = [frame(&audio)]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("the opening audio frame decodes"); - let Ingress::Open(draft) = ingress else { - panic!("an audio frame opens the turn too, got {ingress:?}"); - }; - let draft = *draft; - // The Unit-0 body must carry the client's own wire frame (not the extracted raw PCM relay): the - // egress seam re-reads it off the body to re-frame it onto the upstream dialect. - let u = unit(draft.op, draft.body_ir, draft.facts); - - let dest = destination("api.openai.com", LaneId::new("realtime")); - let egress = plane - .encode_egress(&u, &dest, Some(&mut state), &c) - .expect("Unit 0 encodes an egress body"); - - let sent = egress.body.as_slice(); - let value: serde_json::Value = serde_json::from_slice(sent) - .expect("the egress body is a shaped upstream JSON message, not raw PCM"); - assert_eq!( - value.get("type").and_then(serde_json::Value::as_str), - Some("input_audio_buffer.append"), - "an opening audio frame reaches the provider enveloped, not as raw PCM" - ); - assert!( - value - .get("audio") - .and_then(serde_json::Value::as_str) - .is_some_and(|a| !a.is_empty()), - "the enveloped append must carry the base64 audio" - ); -} - -/// The quantity `meter` states for `class`, or `None` when it states no line for it. -fn metered(locators: &busbar_contract::unit::UsageLocators, class: &str) -> Option { - locators - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == class) - .and_then(|l| l.quantity) -} - -/// A carrier `stop` mid-turn settles the open turn: what the caller spoke and what the upstream -/// opened since the last usage report travel on the ending, and `meter` reads them off it. -/// -/// Item 137. The `stop` arm took the open turn's counters out of the state and dropped them, so a -/// call that hung up mid-turn ended with an empty fact set: the ledger recorded none of the audio -/// relayed after the opening frame and none of the tool calls, where both were served. -#[test] -fn a_twilio_stop_settles_the_open_turn() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/twilio/call-123"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let dest = destination("api.openai.com", LaneId::new("realtime")); - let mut state = PlaneSessionState::new(crate::session::VoiceSessionState::for_dialect( - Dialect::TwilioMediaStreams, - )); - - let start = serde_json::to_vec(&json!({ - "event": "start", - "start": { - "streamSid": "MZ1", - "callSid": "CA1", - "mediaFormat": { "encoding": "audio/x-mulaw", "sampleRate": 8000, "channels": 1 }, - }, - })) - .unwrap(); - let frames = [frame(&start)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("start decodes"); - - // G.711 µ-law at 8 kHz is 8 bytes a millisecond: 8 000 bytes is one second. - let media = serde_json::to_vec(&json!({ - "event": "media", - "streamSid": "MZ1", - "media": { "payload": base64_of(&vec![0xFFu8; 8_000]) }, - })) - .unwrap(); - // The first second OPENS the turn and is stated on its draft. - let frames = [frame(&media)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::Open(draft) = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("media decodes") - else { - panic!("the first media frame opens the turn"); - }; - let unit = crate::tests::harness::unit(draft.op, draft.body_ir, draft.facts); - // The second second is RELAYED, which is where it is counted. - let frames = [frame(&media)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("media decodes"); - plane - .encode_ingress_frame(&unit, &frames[0], &dest, Some(&mut state), &c) - .expect("media relays"); - // The upstream opens one tool call before the caller hangs up. - let opened = serde_json::to_vec(&json!({ - "type": "response.output_item.added", - "item": { "type": "function_call", "call_id": "call_1", "name": "lookup" }, - })) - .unwrap(); - let frames = [frame(&opened)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_response(&mut cursor, &dest, Some(&mut state), &c) - .expect("a tool-call open decodes"); - - let stop = serde_json::to_vec(&json!({ "event": "stop", "streamSid": "MZ1" })).unwrap(); - let frames = [frame(&stop)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::Close { facts, .. } = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("stop decodes") - else { - panic!("stop closes the open turn"); - }; - let ending = busbar_contract::plane::Response { - ir: busbar_contract::bounded::Ir::empty(), - finish: busbar_contract::unit::FinishClass::Partial, - facts: *facts, - }; - let locators = plane.meter(&unit, &ending, &c); - assert_eq!( - metered(&locators, "audio_seconds_in"), - Some(2), - "the opening second and the relayed second were both served" - ); - assert_eq!(metered(&locators, "tool_calls"), Some(1)); -} - -/// A barge-in bills what it served. -/// -/// Item 137. A client-authored interrupt superseded the open turn and reset its counters with the -/// correlation. A superseded turn is never answered, so no usage report ever metered it, and -/// everything the caller had already spoken — and every tool call the upstream had already opened — -/// vanished from the ledger. Carried onto the turn that takes over, it is emitted exactly once, on -/// that turn's terminal. -#[test] -fn a_barge_in_bills_what_the_interrupted_turn_served() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let dest = destination("api.openai.com", LaneId::new("realtime")); - let mut state = open_client_session(&plane, &c); - - let opening = client_wire(&session_update_fixture()); - let frames = [frame(&opening)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::Open(first) = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("the turn opens") - else { - panic!("the first client event opens a turn"); - }; - let first_unit = crate::tests::harness::unit(first.op, first.body_ir, first.facts); - - // One second of PCM16 at 24 kHz, relayed on the first turn. - let append = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.append", - "audio": base64_of(&vec![0u8; 48_000]), - })) - .unwrap(); - let frames = [frame(&append)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("audio decodes"); - plane - .encode_ingress_frame(&first_unit, &frames[0], &dest, Some(&mut state), &c) - .expect("audio relays"); - let opened = serde_json::to_vec(&json!({ - "type": "response.output_item.added", - "item": { "type": "function_call", "call_id": "call_1", "name": "lookup" }, - })) - .unwrap(); - let frames = [frame(&opened)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_response(&mut cursor, &dest, Some(&mut state), &c) - .expect("a tool-call open decodes"); - - // The caller talks over it. - let truncate = serde_json::to_vec(&json!({ - "type": "conversation.item.truncate", - "item_id": "item_1", - "content_index": 0, - "audio_end_ms": 640, - })) - .unwrap(); - let frames = [frame(&truncate)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::Open(second) = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("the barge-in decodes") - else { - panic!("a barge-in opens the turn that takes over"); - }; - let second_unit = crate::tests::harness::unit(second.op, second.body_ir, second.facts); - - let done = serde_json::to_vec(&json!({ - "type": "response.done", - "response": { "usage": { "input_token_details": { "audio_tokens": 1 } } } - })) - .unwrap(); - let frames = [frame(&done)]; - let mut cursor = FrameCursor::new(&frames); - let Progress::Terminal { r, .. } = plane - .decode_response(&mut cursor, &dest, Some(&mut state), &c) - .expect("the usage report decodes") - else { - panic!("a usage report ends the turn"); - }; - let locators = plane.meter(&second_unit, &r, &c); - assert_eq!( - metered(&locators, "audio_seconds_in"), - Some(1), - "the second the caller spoke before barging in was served and must be metered" - ); - assert_eq!(metered(&locators, "tool_calls"), Some(1)); -} - -/// ONE READING OF A TURN (item 138: "one pipeline, or a proof they agree"). A host that drives the -/// session itself — the voice runtime, which serves every live session in production — ledgers -/// [`crate::session::class_counts`]; the unit loop ledgers what `meter` emits. Over the same turn -/// (one second of uplink audio, one tool call opened, a usage report naming all five token classes) -/// the two are the same set of `(class, count)` pairs. -#[test] -fn class_counts_are_the_lines_meter_emits_for_the_same_turn() { - let plane = openai_plane(); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - let dest = destination("api.openai.com", LaneId::new("realtime")); - let mut state = open_client_session(&plane, &c); - - let opening = client_wire(&session_update_fixture()); - let frames = [frame(&opening)]; - let mut cursor = FrameCursor::new(&frames); - let Ingress::Open(first) = plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("the turn opens") - else { - panic!("the first client event opens a turn"); - }; - let turn = crate::tests::harness::unit(first.op, first.body_ir, first.facts); - let append = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.append", - "audio": base64_of(&vec![0u8; 48_000]), - })) - .unwrap(); - let frames = [frame(&append)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_ingress(&mut cursor, Some(&mut state), &c) - .expect("audio decodes"); - plane - .encode_ingress_frame(&turn, &frames[0], &dest, Some(&mut state), &c) - .expect("audio relays"); - let opened = serde_json::to_vec(&json!({ - "type": "response.output_item.added", - "item": { "type": "function_call", "call_id": "call_1", "name": "lookup" }, - })) - .unwrap(); - let frames = [frame(&opened)]; - let mut cursor = FrameCursor::new(&frames); - plane - .decode_response(&mut cursor, &dest, Some(&mut state), &c) - .expect("a tool-call open decodes"); - let done = serde_json::to_vec(&json!({ - "type": "response.done", - "response": { - "usage": { - "input_token_details": { "audio_tokens": 10, "text_tokens": 3, "cached_tokens": 1 }, - "output_token_details": { "audio_tokens": 20, "text_tokens": 4 }, - } - } - })) - .unwrap(); - let frames = [frame(&done)]; - let mut cursor = FrameCursor::new(&frames); - let Progress::Terminal { r, .. } = plane - .decode_response(&mut cursor, &dest, Some(&mut state), &c) - .expect("the usage report decodes") - else { - panic!("a usage report ends the turn"); - }; - let mut emitted: Vec<(String, u64)> = plane - .meter(&turn, &r, &c) - .lines - .as_slice() - .iter() - .filter_map(|l| { - Some(( - l.class.as_str().to_string(), - l.quantity.filter(|n| *n != 0)?, - )) - }) - .collect(); - emitted.sort(); - - let usage = crate::codec::ir::IrDuplexUsage { - audio_in: 10, - audio_out: 20, - text_in: 3, - text_out: 4, - cached: 1, - }; - let mut counters = crate::session::TurnCounters::default(); - counters.admit_audio(crate::codec::ir::AudioFormat::Pcm16, 48_000); - counters.open_tool_call(); - let mut counted: Vec<(String, u64)> = crate::session::class_counts(Some(&usage), counters) - .into_iter() - .map(|(class, n)| (class.as_str().to_string(), n)) - .collect(); - counted.sort(); - - assert_eq!(counted, emitted, "one reading of one turn"); - // Six, not seven: cached_tokens is a SUBSET of audio_tokens_in/text_tokens_in, not a class beside - // them (#71), so it carries no line of its own even though this turn's usage report names it. - assert_eq!(counted.len(), 6, "every declared class carried a count"); -} diff --git a/crates/busbar-plane-streaming/src/tests/driven_tests.rs b/crates/busbar-plane-streaming/src/tests/driven_tests.rs index 8950a6be15..eaf43eef4a 100644 --- a/crates/busbar-plane-streaming/src/tests/driven_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/driven_tests.rs @@ -35,7 +35,7 @@ fn the_mint_attempt_names_the_caller_and_carries_the_locked_session() { instructions: Some("locked".into()), ..SessionConfig::default() }; - let a = mint_attempt(&cfg, "vk-1", None).expect("serializes"); + let a = mint_attempt(&cfg, Some("vk-1"), None).expect("serializes"); assert_eq!((a.verb, a.target), ("POST", "/v1/realtime/client_secrets")); assert_eq!( a.fields, @@ -51,6 +51,12 @@ fn the_mint_attempt_names_the_caller_and_carries_the_locked_session() { a.fields.iter().all(|(n, _)| !n.eq_ignore_ascii_case("authorization")), "the plane never writes the credential; the kernel adds it" ); + let anonymous = mint_attempt(&cfg, None, None).expect("serializes"); + assert_eq!( + anonymous.fields, + vec![("content-type", "application/json".to_string())], + "no reference lent, no caller named" + ); } #[test] diff --git a/crates/busbar-plane-streaming/src/tests/harness.rs b/crates/busbar-plane-streaming/src/tests/harness.rs deleted file mode 100644 index 80dad2b5c2..0000000000 --- a/crates/busbar-plane-streaming/src/tests/harness.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! The smallest thing that can call this plane. -//! -//! The same minimum-size harness `busbar-plane-llm`'s own (unused-so-far) test harness builds: an -//! arena, a clock, a configuration view, a transport view and a label set, plus the kernel-built -//! values (a unit, a verified destination) handed through a seal. Nothing here is shipped. - -use busbar_contract::bounded::{ - Facts, Ir, Labels, PlaneAlloc, PlaneAllocBudget, ScratchBytes, SlabBytes, Span, -}; -use busbar_contract::dest::{DestinationFacts, VerifiedDestination}; -use busbar_contract::ids::{LaneId, OpClassId, SessionId, StreamId}; -use busbar_contract::unit::{Clock, ConfigView, Ctx, Origin, SessionView, TransportView, Unit}; -use busbar_contract::wire::{Direction, Frame, FrameMeta}; -use std::sync::Arc; - -/// An arena that never reuses a byte — fine for a short test process. -#[derive(Debug, Default)] -pub struct LeakPlaneAlloc; - -impl PlaneAlloc for LeakPlaneAlloc { - fn alloc_bytes<'a>(&'a self, src: &[u8]) -> Result, PlaneAllocBudget> { - Ok(ScratchBytes::new(Box::leak( - src.to_vec().into_boxed_slice(), - ))) - } - - fn alloc_str<'a>(&'a self, src: &str) -> Result<&'a str, PlaneAllocBudget> { - Ok(Box::leak(src.to_string().into_boxed_str())) - } - - fn alloc_spans<'a>( - &'a self, - src: &[(&'a str, Span)], - ) -> Result<&'a [(&'a str, Span)], PlaneAllocBudget> { - Ok(Box::leak(src.to_vec().into_boxed_slice())) - } - - fn remaining(&self) -> usize { - usize::MAX - } -} - -/// A configuration block with nothing in it. -#[derive(Debug, Default)] -pub struct EmptyConfig; - -impl ConfigView for EmptyConfig { - fn get_str(&self, _key: &str) -> Option<&str> { - None - } - fn get_int(&self, _key: &str) -> Option { - None - } - fn get_bool(&self, _key: &str) -> Option { - None - } -} - -/// A transport stack that publishes a request target as a fact — the same convention -/// `busbar-plane-llm`'s harness uses for its own `path` fact. -#[derive(Debug)] -pub struct WsStack { - path: String, -} - -impl WsStack { - /// A stack that saw this request target. - #[must_use] - pub fn new(path: &str) -> Self { - Self { - path: path.to_string(), - } - } -} - -impl TransportView for WsStack { - fn key(&self) -> &'static str { - "ws" - } - fn chain(&self) -> &[&'static str] { - &["tcp", "tls", "http", "ws"] - } - fn fact(&self, key: &str) -> Option<&str> { - if key == "path" { - Some(&self.path) - } else { - None - } - } -} - -/// A session that reports zero paired upstreams, for the calls that take one. -/// -/// Not yet used by a test in this crate (every current test drives `decode_ingress`/`decode_response` -/// through a bare `Ctx` with no session), kept as shared harness for the session-bound tests a future -/// pass adds — the same reason `busbar-plane-llm`'s own harness carries fixtures its current test -/// file does not all exercise yet. -#[allow(dead_code)] -#[derive(Debug)] -pub struct FreshSession; - -impl SessionView for FreshSession { - fn id(&self) -> SessionId { - SessionId(0) - } - fn is_bound(&self) -> bool { - true - } - fn session_fact(&self, _key: &str) -> Option<&str> { - None - } - fn transport_fact(&self, _key: &str) -> Option<&str> { - None - } - fn upstream_count(&self) -> usize { - 0 - } -} - -/// A session that already holds one paired upstream and reports the client dialect it was opened -/// on, for the paired-turn routing tests. The `dialect` is the name the plane wrote to its declared -/// `dialect` session fact at session open (`Dialect::name`). -#[derive(Debug)] -pub struct PairedSession { - dialect: &'static str, - upstreams: usize, -} - -impl PairedSession { - /// A session opened on `dialect`, holding `upstreams` paired upstreams. - #[must_use] - pub fn new(dialect: &'static str, upstreams: usize) -> Self { - Self { dialect, upstreams } - } -} - -impl SessionView for PairedSession { - fn id(&self) -> SessionId { - SessionId(7) - } - fn is_bound(&self) -> bool { - true - } - fn session_fact(&self, key: &str) -> Option<&str> { - if key == "dialect" { - Some(self.dialect) - } else { - None - } - } - fn transport_fact(&self, _key: &str) -> Option<&str> { - None - } - fn upstream_count(&self) -> usize { - self.upstreams - } -} - -/// The blessed TEST seal (#65). `KernelSeal` is SEALED — no crate outside `busbar-contract` can -/// implement it — so a fixture names the contract's own `test-seal` type instead of forging one. -/// The type system stops a plugin now, not the manifest allow-list alone. -pub use busbar_contract::plugin::TestKernelSeal as TestSeal; - -/// Build a context over the pieces above. -#[must_use] -pub fn ctx<'u>( - arena: &'u LeakPlaneAlloc, - config: &'u EmptyConfig, - transport: &'u WsStack, - labels: &'u Labels<'u>, -) -> Ctx<'u> { - Ctx::new( - Clock { - unix_secs: 1_772_000_000, - monotonic_nanos: 0, - }, - config, - None, - transport, - labels, - arena, - ) -} - -/// Build a context that carries a session — for the steps that read `Ctx::session()`. -#[must_use] -pub fn ctx_with_session<'u>( - arena: &'u LeakPlaneAlloc, - config: &'u EmptyConfig, - transport: &'u WsStack, - labels: &'u Labels<'u>, - session: &'u dyn SessionView, -) -> Ctx<'u> { - Ctx::new( - Clock { - unix_secs: 1_772_000_000, - monotonic_nanos: 0, - }, - config, - Some(session), - transport, - labels, - arena, - ) -} - -/// One inbound frame carrying a whole wire event. -#[must_use] -pub fn frame(bytes: &[u8]) -> Frame { - Frame { - direction: Direction::Inbound, - stream: StreamId(0), - bytes: SlabBytes::new(Arc::from(bytes.to_vec().into_boxed_slice())), - meta: FrameMeta::default(), - } -} - -/// A unit built the way the kernel builds one, over a decoded body. -#[must_use] -pub fn unit<'u>(op: OpClassId, body: Ir<'u>, facts: Facts<'u>) -> Unit<'u> { - Unit::new( - &TestSeal, - busbar_contract::UnitKey::new(1), - Origin::Client, - None, - Some(StreamId(0)), - Direction::Inbound, - None, - op, - body, - facts, - None, - ) -} - -/// A destination sealed the way the trust unit seals one. -#[must_use] -pub fn destination(host: &'static str, lane: LaneId) -> VerifiedDestination { - VerifiedDestination::seal( - &TestSeal, - DestinationFacts::Upstream { - transport: "ws", - address: busbar_contract::UpstreamAddress::socket(host), - lane, - }, - "ws", - None, - ) -} diff --git a/crates/busbar-plane-streaming/src/tests/mod.rs b/crates/busbar-plane-streaming/src/tests/mod.rs index 7ced50ce9e..60e1b4c5d8 100644 --- a/crates/busbar-plane-streaming/src/tests/mod.rs +++ b/crates/busbar-plane-streaming/src/tests/mod.rs @@ -1,12 +1,7 @@ -//! Test-only wiring: the shared harness, the codec/µ-law fixture tests, and the purity/determinism +//! Test-only wiring: the selector, purity/determinism //! and style checks the crate doc comments promise elsewhere (`lib.rs`'s [`crate::StreamingPlane`] doc //! comment cites [`purity`] by name). -pub mod harness; - -mod codec; -mod twilio; -mod ulaw; /// What a request path matches, decided the same way the boot's overlap check decides it. mod selectors { @@ -25,6 +20,44 @@ mod selectors { assert!(!matches_selector(&s, "/twilio/inbound/deeper")); assert!(!matches_selector(&s, "/other/inbound")); } + + /// Twilio Media Streams is a streaming DIALECT carried by the WebSocket transport, not a + /// transport of its own (the design lists five transports and no telephony one). Its claim is + /// on the WebSocket transport, one path level under `/twilio`, and authenticates under the + /// `webhook-signature` alternative alone: Twilio holds no busbar bearer or API key. Without the + /// claim the dialect's reader and codec are unreachable: no arrival ever names it. + #[test] + fn twilio_media_streams_is_claimed_on_the_websocket_carrier_under_the_webhook_signature() { + use crate::claims::{dialect_for, Dialect, DIALECT_CLAIMS, WS_TRANSPORT}; + use crate::StreamingPlane; + use busbar_contract::plane::PlaneMeta; + + let twilio: Vec<_> = DIALECT_CLAIMS + .iter() + .filter(|c| c.dialect == Dialect::TwilioMediaStreams) + .collect(); + assert_eq!(twilio.len(), 1, "exactly one claim names the Twilio dialect"); + let claim = &twilio[0].claim; + assert_eq!( + claim.transport, WS_TRANSPORT, + "Twilio rides the WebSocket transport" + ); + assert_eq!(claim.selector, Selector::PrefixOneLevel("/twilio")); + assert_eq!(claim.scheme_alternatives, &["webhook-signature"]); + assert!( + ::CLAIMS.contains(claim), + "the declared claim is one the boot seal reads" + ); + // An arrival one level under `/twilio` names the dialect; the prefix alone and a deeper + // path do not, and neither does another dialect's path. + assert_eq!( + dialect_for("/twilio/inbound"), + Some(Dialect::TwilioMediaStreams) + ); + assert_eq!(dialect_for("/twilio"), None); + assert_eq!(dialect_for("/twilio/a/b"), None); + assert_eq!(dialect_for("/v1/realtime"), Some(Dialect::OpenaiRealtime)); + } } /// Purity and determinism: the properties every plane in the design is held to ("pure over its @@ -35,8 +68,8 @@ mod purity { /// `StreamingPlane` derives `Copy`. Every interior-mutable cell (`Cell`, `RefCell`, `Mutex`, /// `OnceLock`, ...) is `!Copy`, so a type that IS `Copy` structurally cannot hold one: this is /// a compile-time proof, not a convention, that the plane keeps no mutable state of its own - /// across calls — everything that varies across a session lives in the kernel-held - /// `PlaneSessionState` instead (see `session::VoiceSessionState`). + /// across calls: everything that varies across a session lives in the session's own state + /// (see `session_unit::SessionUnit`). const fn assert_copy() {} const _: () = assert_copy::(); @@ -82,94 +115,6 @@ mod purity { // vectors in both directions, and a round trip over all 256 bytes that pins the decoded sample. } -/// Where a paired turn is routed: to the upstream the session actually DIALED, not the first -/// configured one. A session opened on a duplex client dialect dialed the upstream that speaks it; -/// every later turn of that session must name that same upstream, or a turn is metered on the wrong -/// provider's lane. -mod route { - use crate::claims::Dialect; - use crate::tests::harness::{ - ctx_with_session, EmptyConfig, LeakPlaneAlloc, PairedSession, WsStack, - }; - use crate::{StreamingPlane, Upstream}; - use busbar_contract::bounded::{FactValue, Facts, Ir, Labels}; - use busbar_contract::dest::DestinationFacts; - use busbar_contract::ids::{LaneId, OpClassId}; - use busbar_contract::plane::Plane; - - /// Two upstreams, gemini declared SECOND, each on its own priced lane. - static UPSTREAMS: &[Upstream] = &[ - Upstream { - lane: LaneId::new("realtime-openai"), - host: "api.openai.example", - dialect: Dialect::OpenaiRealtime, - }, - Upstream { - lane: LaneId::new("realtime-gemini"), - host: "api.gemini.example", - dialect: Dialect::GeminiLive, - }, - ]; - - /// A paired turn of a gemini-live session routes to the gemini upstream — its real config index - /// and its own priced lane — not to the first-declared openai upstream. Before the fix the paired - /// branch hard-coded `UpstreamIdx(0)` and `upstreams().first().lane`, so a gemini session's every - /// turn was billed on the openai lane: the wrong provider's money. - #[test] - fn a_paired_turn_routes_to_the_dialed_upstream_not_the_first() { - let plane = StreamingPlane::new(UPSTREAMS); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/openai/realtime"); - let labels = Labels::new(); - let session = PairedSession::new(Dialect::GeminiLive.name(), 1); - let cx = ctx_with_session(&arena, &config, &transport, &labels, &session); - - let mut facts = Facts::new(); - let _ = facts.set("dialect", FactValue::Str(Dialect::GeminiLive.name())); - let u = crate::tests::harness::unit(OpClassId::new("duplex_turn"), Ir::empty(), facts); - - match plane.verify(&u, &cx) { - DestinationFacts::SessionUpstream { upstream, lane, .. } => { - assert_eq!( - lane, - LaneId::new("realtime-gemini"), - "a gemini session's turn must bill on the gemini lane it dialed" - ); - assert_eq!( - upstream.0, 1, - "the fact must name the gemini upstream's real config index" - ); - } - other => panic!("a paired turn must route to a session upstream, got {other:?}"), - } - } - - /// A paired turn of an openai-realtime session still routes to the openai upstream (index 0), - /// so the fix does not simply invert the bug. - #[test] - fn a_paired_openai_turn_still_routes_to_the_openai_upstream() { - let plane = StreamingPlane::new(UPSTREAMS); - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/openai/realtime"); - let labels = Labels::new(); - let session = PairedSession::new(Dialect::OpenaiRealtime.name(), 1); - let cx = ctx_with_session(&arena, &config, &transport, &labels, &session); - - let u = - crate::tests::harness::unit(OpClassId::new("duplex_turn"), Ir::empty(), Facts::new()); - - match plane.verify(&u, &cx) { - DestinationFacts::SessionUpstream { upstream, lane, .. } => { - assert_eq!(lane, LaneId::new("realtime-openai")); - assert_eq!(upstream.0, 0); - } - other => panic!("a paired turn must route to a session upstream, got {other:?}"), - } - } -} - /// Style rules this crate holds itself to, checked rather than merely asserted in prose: no /// section-sign citation and no parity-binding identifier (a two-letter prefix, a hyphen and /// digits, e.g. a two-letter code followed by a hyphen and a number) anywhere in this crate's own source — the same hard rule diff --git a/crates/busbar-plane-streaming/src/tests/open_calls_tests.rs b/crates/busbar-plane-streaming/src/tests/open_calls_tests.rs index 096003aa44..17c359ca7a 100644 --- a/crates/busbar-plane-streaming/src/tests/open_calls_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/open_calls_tests.rs @@ -26,7 +26,7 @@ fn plan(table: &OpenToolCalls, session: u64, unit: u64, call_id: &str, now: Mill /// The plane's declared reply deadline, in milliseconds. fn deadline() -> u64 { - u64::from(crate::plane::TOOL_REPLY_DEADLINE_SECS) * 1_000 + u64::from(crate::open_calls::TOOL_REPLY_DEADLINE_SECS) * 1_000 } /// **Two calls open at once, and each reply wakes only its own.** diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index f8ec1e5343..6f85ba3bca 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -321,3 +321,101 @@ fn a_call_no_one_on_this_node_serves_is_relayed_to_the_caller_and_never_answered "no result is authored upstream by the gateway" ); } + +fn uplink_ms(ms: usize) -> WireEvent { + wire(serde_json::json!({"type":"input_audio_buffer.append", + "audio": busbar_contract::media::base64_encode(&vec![0u8; ms * 48])})) +} + +#[test] +fn two_open_tool_calls_wait_on_two_different_correlations() { + let table = Arc::new(Table::default()); + let mut p = pump(); + p.bind_governed(GovernedSession { + session: 1, + calls: table.clone(), + }); + let mut sink = Turns::default(); + for f in call_frames("cc", "a", "{}").into_iter().chain(call_frames("dd", "b", "{}")) { + let _ = p.on_server_frame(f, 0, &mut sink, &serves_none); + } + let planned: Vec = table + .planned + .lock() + .expect("lock") + .iter() + .map(|(_, c, _)| c.clone()) + .collect(); + assert_eq!(planned, ["cc", "dd"], "each call is its own wait"); + let reply = |id: &str| { + wire(serde_json::json!({"type":"conversation.item.create","item":{ + "type":"function_call_output","call_id":id,"output":"{}"}})) + }; + assert!(!p.on_client_frame(reply("dd")).refused_reply); + assert!(p.on_client_frame(reply("dd")).refused_reply, "a wait is woken once"); + assert!(!p.on_client_frame(reply("cc")).refused_reply); +} + +#[test] +fn an_upstream_error_still_meters_the_turn_it_ended() { + let mut p = pump(); + let mut sink = Turns::default(); + let _ = p.on_client_frame(uplink_ms(40_000)); + for id in ["e1", "e2"] { + let open = call_frames(id, "t", "{}"); + let [first, ..] = open; + let _ = p.on_server_frame(first, 0, &mut sink, &serves_none); + } + let _ = p.on_server_frame( + wire(serde_json::json!({"type":"error","error":{"code":"x","message":"y"}})), + 0, + &mut sink, + &serves_none, + ); + assert_eq!(sink.closed.len(), 1); + assert_eq!( + sink.closed[0].1, + TurnCounters { + audio_ms_in: 40_000, + tool_calls: 2 + } + ); +} + +#[test] +fn a_barge_in_bills_what_the_interrupted_turn_served_on_the_turn_that_takes_over() { + let mut p = pump(); + let mut sink = Turns::default(); + let _ = p.on_client_frame(uplink_ms(1_000)); + let [open, ..] = call_frames("b1", "t", "{}"); + let _ = p.on_server_frame(open, 0, &mut sink, &serves_none); + let _ = p.on_server_frame( + wire(serde_json::json!({"type":"input_audio_buffer.speech_started", + "audio_start_ms":0,"item_id":"it"})), + 0, + &mut sink, + &serves_none, + ); + assert!(sink.closed.is_empty(), "a barge-in closes no turn and drops no counter"); + let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_none); + assert_eq!(sink.closed.len(), 1); + assert_eq!( + sink.closed[0].1, + TurnCounters { + audio_ms_in: 1_000, + tool_calls: 1 + } + ); +} + +#[test] +fn a_caller_frame_that_is_not_utf8_or_names_no_known_event_goes_nowhere() { + let mut p = pump(); + let mut sink = Turns::default(); + let bad = p.on_client_frame(WireEvent(Bytes::from_static(&[0xff, 0xfe, 0x00]))); + assert!(bad.upstream.is_empty() && bad.downlink.is_empty() && !bad.close); + let unknown = p.on_client_frame(wire(serde_json::json!({"type":"teleport.now"}))); + assert!(unknown.upstream.is_empty() && unknown.downlink.is_empty()); + p.settle_open_turn(&mut sink); + assert!(sink.closed.is_empty(), "nothing was counted"); +} diff --git a/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs b/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs new file mode 100644 index 0000000000..07ba748ae8 --- /dev/null +++ b/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! Tests for `crates/busbar-plane-streaming/src/session_unit.rs`. + +use super::*; +use crate::codec::ir::codec::OpenAiRealtimeCodec; +use crate::session_params::g711_config; + +fn json(v: serde_json::Value) -> Vec { + serde_json::to_vec(&v).expect("json") +} + +fn text(frames: &[Vec]) -> String { + frames + .iter() + .map(|f| String::from_utf8_lossy(f).into_owned()) + .collect::>() + .join("\n") +} + +fn start(encoding: &str) -> Vec { + json(serde_json::json!({"event":"start","start":{"streamSid":"MZ1","callSid":"CA1", + "mediaFormat":{"encoding":encoding,"sampleRate":8000,"channels":1}}})) +} + +fn media(sid: &str, bytes: &[u8]) -> Vec { + json(serde_json::json!({"event":"media","streamSid":sid, + "media":{"payload": busbar_contract::media::base64_encode(bytes)}})) +} + +fn telephony() -> SessionUnit { + SessionUnit::open(OpenAiRealtimeCodec, g711_config(), true, 0, None) +} + +#[test] +fn the_callers_audio_on_the_telephony_door_reaches_the_far_end() { + // The served telephony leg handed Twilio's `event`-tagged frames to the realtime reader, which + // reads only `type`-tagged ones: the caller's audio reached no one. + let mut s = telephony(); + assert_eq!(s.from_caller(&start("audio/x-mulaw")), Plan::default()); + let plan = s.from_caller(&media("MZ1", &[0x7f; 160])); + let up = text(&plan.to_far_end); + assert!(up.contains("input_audio_buffer.append"), "{up}"); + assert!( + up.contains(&busbar_contract::media::base64_encode(&[0x7f; 160])), + "the caller's audio passes through unchanged: {up}" + ); +} + +#[test] +fn media_for_another_stream_and_unmodelled_events_reach_no_one() { + let mut s = telephony(); + let _ = s.from_caller(&start("audio/x-mulaw")); + assert_eq!(s.from_caller(&media("MZ-forged", &[1; 8])), Plan::default()); + assert_eq!( + s.from_caller(&json(serde_json::json!({"event":"something-new"}))), + Plan::default(), + "an event this reader does not model is dropped, not a session end" + ); + assert!(!s.ended()); +} + +#[test] +fn a_call_in_another_media_format_ends_the_session() { + let mut s = telephony(); + assert!(s.from_caller(&start("audio/l16")).end); + assert!(s.ended()); +} + +#[test] +fn model_audio_and_a_barge_in_reach_the_caller_in_the_carriers_envelope() { + let mut s = telephony(); + let _ = s.from_caller(&start("audio/x-mulaw")); + let delta = json(serde_json::json!({"type":"response.output_audio.delta", + "delta": busbar_contract::media::base64_encode(&[9; 16]), + "item_id":"it1","output_index":0,"content_index":0})); + let plan = s.from_far_end(&delta, 0); + let down = text(&plan.to_caller); + assert!(down.contains("\"event\":\"media\"") && down.contains("MZ1"), "{down}"); + let barge = json(serde_json::json!({"type":"input_audio_buffer.speech_started", + "audio_start_ms":0,"item_id":"it1"})); + let plan = s.from_far_end(&barge, 0); + assert!(text(&plan.to_caller).contains("\"event\":\"clear\"")); + assert!(text(&plan.to_far_end).contains("response.cancel")); +} + +#[test] +fn a_stop_settles_the_open_turns_counters_once() { + let mut s = telephony(); + let _ = s.from_caller(&start("audio/x-mulaw")); + // Two seconds of 8 kHz mu-law: 8 bytes a millisecond. + let _ = s.from_caller(&media("MZ1", &[0; 16_000])); + assert_eq!(s.units(), CumulativeUnits::default(), "no turn closed yet"); + assert!(s.from_caller(&json(serde_json::json!({"event":"stop"}))).end); + let seconds = CLASSES + .iter() + .position(|c| *c == meta::CLASS_AUDIO_SECONDS_IN) + .expect("declared"); + assert_eq!(s.units().0[seconds], 2); + let _ = s.end(); + assert_eq!(s.units().0[seconds], 2, "a second end settles nothing"); +} + +#[test] +fn usage_reports_accumulate_across_turns() { + let mut s = SessionUnit::open(OpenAiRealtimeCodec, SessionConfig::default(), false, 0, None); + let done = json(serde_json::json!({"type":"response.done","response":{"usage":{ + "input_token_details":{"audio_tokens":10,"text_tokens":3,"cached_tokens":2}, + "output_token_details":{"audio_tokens":20,"text_tokens":4}}}})); + let _ = s.from_far_end(&done, 0); + let _ = s.from_far_end(&done, 0); + assert_eq!(s.units().0, [20, 40, 6, 8, 0, 0], "cached tokens are attribution, never billed"); +} + +#[test] +fn a_session_past_its_ceiling_is_told_why_and_ends() { + let mut s = SessionUnit::open( + OpenAiRealtimeCodec, + SessionConfig::default(), + false, + 1_000, + Some(2), + ); + assert_eq!(s.tick(1_000 + 1_999_999_999, 0), Plan::default()); + let plan = s.tick(1_000 + 2_000_000_000, 0); + assert!(plan.end); + assert!(text(&plan.to_caller).contains(crate::session_pump::SESSION_CEILING_REASON)); + assert!(s.ended()); +} + +#[test] +fn the_sessions_record_is_written_at_open_on_its_call_id_and_at_its_end() { + let mut s = SessionUnit::open_as( + OpenAiRealtimeCodec, + SessionConfig::default(), + false, + 5_000_000_000, + None, + "call-1", + "ref-abc", + ); + let opened = s.take_writes(); + assert_eq!(opened.len(), 1); + assert_eq!( + (opened[0].id.as_str(), opened[0].owner.as_str(), opened[0].updated_at, opened[0].terminal), + ("call-1", "ref-abc", 5, false) + ); + s.set_rtc_call_id("rtc_9", 6); + let _ = s.end(); + let _ = s.end(); + let later = s.take_writes(); + assert_eq!(later.len(), 2, "the call id once, the end once"); + assert_eq!(later[0].rtc_call_id.as_deref(), Some("rtc_9")); + assert!(later[1].terminal && later[1].rtc_call_id.as_deref() == Some("rtc_9")); + assert!(s.take_writes().is_empty()); +} + +#[test] +fn admitted_milliseconds_meter_as_whole_seconds_rounded_up() { + assert_eq!(meta::audio_seconds_in(0), 0); + assert_eq!(meta::audio_seconds_in(1), 1); + assert_eq!(meta::audio_seconds_in(1_500), 2); + assert_eq!(meta::audio_seconds_in(3_000), 3); +} + +#[test] +fn a_stop_settles_the_open_turns_tool_calls_too() { + let mut s = telephony(); + let _ = s.from_caller(&start("audio/x-mulaw")); + let _ = s.from_caller(&media("MZ1", &[0; 8_000])); + let open = json(serde_json::json!({"type":"response.output_item.added", + "item":{"type":"function_call","call_id":"c1","name":"lookup"}})); + let _ = s.from_far_end(&open, 0); + assert!(s.from_caller(&json(serde_json::json!({"event":"stop"}))).end); + assert_eq!(s.units().0, [0, 0, 0, 0, 1, 1]); +} + +#[test] +fn one_full_turn_reports_the_six_classes_the_served_plane_metered() { + let mut s = SessionUnit::open(OpenAiRealtimeCodec, SessionConfig::default(), false, 0, None); + let pcm = busbar_contract::media::base64_encode(&vec![0u8; 48_000]); + let _ = s.from_caller(&json( + serde_json::json!({"type":"input_audio_buffer.append","audio":pcm}), + )); + let open = json(serde_json::json!({"type":"response.output_item.added", + "item":{"type":"function_call","call_id":"c1","name":"lookup"}})); + let _ = s.from_far_end(&open, 0); + let done = json(serde_json::json!({"type":"response.done","response":{"usage":{ + "input_token_details":{"audio_tokens":10,"text_tokens":3,"cached_tokens":1}, + "output_token_details":{"audio_tokens":20,"text_tokens":4}}}})); + let _ = s.from_far_end(&done, 0); + assert_eq!(s.units().0, [10, 20, 3, 4, 1, 1]); +} + +#[test] +fn a_start_with_no_stream_id_refuses_the_call() { + let mut s = telephony(); + let empty = json(serde_json::json!({"event":"start","start":{"streamSid":"","callSid":"CA1", + "mediaFormat":{"encoding":"audio/x-mulaw","sampleRate":8000,"channels":1}}})); + assert!(s.from_caller(&empty).end); + assert!(s.ended()); + assert_eq!( + s.from_caller(&media("", &[1; 80])), + Plan::default(), + "no binding was opened for an empty stream id" + ); +} + +#[test] +fn a_media_payload_resuming_after_padding_is_refused_and_never_billed() { + for payload in ["QQ==QQ==".to_string(), "QQ==".repeat(2_000)] { + let mut s = telephony(); + let _ = s.from_caller(&start("audio/x-mulaw")); + let frame = json(serde_json::json!({"event":"media","streamSid":"MZ1", + "media":{"payload": payload}})); + let plan = s.from_caller(&frame); + assert!(plan.end && plan.to_far_end.is_empty()); + assert_eq!(s.units(), CumulativeUnits::default(), "nothing is billed"); + } +} diff --git a/crates/busbar-plane-streaming/src/tests/twilio.rs b/crates/busbar-plane-streaming/src/tests/twilio.rs deleted file mode 100644 index b42050ecee..0000000000 --- a/crates/busbar-plane-streaming/src/tests/twilio.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Two defects in this crate's Twilio `start` handling: -//! -//! 1. **The negotiated media format was parsed but never validated.** `twilio::decode`'s `Start` -//! already read `encoding`/`sampleRate`/`channels` off the carrier's own frame, but nothing -//! compared them against the G.711 µ-law / 8 kHz / mono format every later `media` frame is -//! decoded and TIMED as (`ulaw::decode_frame`, `AudioFormat::G711Ulaw::bytes_to_ms`). Duration is -//! a LEDGER quantity, so a wrong count from an unvalidated format is a wrong ledger, silently. -//! 2. **An empty `streamSid` passed the anti-forgery binding check vacuously.** `state -//! .twilio_stream_sid` could be bound to `Some("")`, and a `media` frame carrying an equally -//! empty `streamSid` compared equal to it — a binding that cannot bind, accepted anyway. -//! -//! The first battery drives [`twilio::decode`] directly — a pure function, no harness needed. The -//! second drives the real plane entrypoint ([`StreamingPlane::decode_ingress`]) so the fix is proven -//! wired in, not merely correct in isolation, and proves the EXISTING forged-source check (a real -//! sid mismatch is still discarded) was not weakened while the empty case was closed. - -use crate::claims::Dialect; -use crate::session::VoiceSessionState; -use crate::tests::harness::{ctx, frame, EmptyConfig, LeakPlaneAlloc, WsStack}; -use crate::twilio::{self, TwilioError, TwilioEvent}; -use crate::StreamingPlane; -use busbar_contract::bounded::Labels; -use busbar_contract::plane::{Ingress, Plane, PlaneSessionState}; -use busbar_contract::wire::{Decode, DiscardCode, FrameCursor}; - -/// A well-formed carrier `start` frame naming `stream_sid` and the negotiated format. -fn start_json(stream_sid: &str, encoding: &str, sample_rate: u64, channels: u64) -> String { - serde_json::json!({ - "event": "start", - "start": { - "streamSid": stream_sid, - "callSid": "CA-test-call", - "mediaFormat": { - "encoding": encoding, - "sampleRate": sample_rate, - "channels": channels, - }, - }, - }) - .to_string() -} - -/// A carrier `media` frame naming `stream_sid`, carrying one base64 byte of payload. -fn media_json(stream_sid: &str) -> String { - serde_json::json!({ - "event": "media", - "streamSid": stream_sid, - "media": { "payload": "AA==" }, - }) - .to_string() -} - -// ══ `twilio::decode`, DIRECTLY ══════════════════════════════════════════════════════════════════ - -#[test] -fn start_with_an_empty_stream_sid_is_refused() { - let json = start_json("", "audio/x-mulaw", 8_000, 1); - assert_eq!( - twilio::decode(json.as_bytes()), - Err(TwilioError::Malformed), - "an empty streamSid cannot bind anything and must not decode as though it could" - ); -} - -#[test] -fn start_negotiating_a_non_g711_format_is_refused() { - for (encoding, sample_rate, channels) in [ - ("audio/l16", 8_000u64, 1u64), - ("audio/x-mulaw", 16_000, 1), - ("audio/x-mulaw", 8_000, 2), - ("", 8_000, 1), - ] { - let json = start_json("CA-real-sid", encoding, sample_rate, channels); - assert_eq!( - twilio::decode(json.as_bytes()), - Err(TwilioError::UnsupportedMediaFormat), - "a negotiated format of {encoding:?}/{sample_rate}/{channels} must be refused rather \ - than silently treated as G.711/8kHz/mono" - ); - } -} - -#[test] -fn start_with_a_real_sid_and_the_assumed_format_decodes() { - let json = start_json("CA-real-sid", "audio/x-mulaw", 8_000, 1); - assert_eq!( - twilio::decode(json.as_bytes()), - Ok(TwilioEvent::Start { - stream_sid: "CA-real-sid".to_string(), - call_sid: "CA-test-call".to_string(), - encoding: "audio/x-mulaw".to_string(), - sample_rate: 8_000, - channels: 1, - }), - "a well-formed start naming the assumed format must still decode — the fix must not \ - over-refuse the ordinary case" - ); -} - -// ══ THE PLANE ENTRYPOINT ════════════════════════════════════════════════════════════════════════ - -/// A fresh Twilio-dialected session half — what a session's client half opens with once the -/// `/twilio/inbound` claim has matched, before any frame has arrived. -fn fresh_twilio_state() -> PlaneSessionState { - PlaneSessionState::new(VoiceSessionState::for_dialect(Dialect::TwilioMediaStreams)) -} - -/// What one call to [`StreamingPlane::decode_ingress`] made of a frame, OWNED rather than borrowed -/// — everything the arena/frame `decode_ingress` borrows from is local to this function, so nothing -/// past what a test needs to assert on may escape it. -#[derive(Debug, PartialEq, Eq)] -enum Outcome { - Discard(DiscardCode), - Open, - Other, -} - -/// Decode one Twilio wire frame against `state`, over a fresh arena/transport/labels local to this -/// call. `state` is the one thing carried BETWEEN calls — the session half a real connection would -/// hold across frames — exactly what the `streamSid` binding this module is testing depends on. -fn decode_one( - plane: &StreamingPlane, - state: &mut PlaneSessionState, - json: &str, -) -> Result { - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/twilio/inbound"); - let labels = Labels::new(); - let cx = ctx(&arena, &config, &transport, &labels); - let frames = [frame(json.as_bytes())]; - let mut cursor = FrameCursor::new(&frames); - let ingress = plane.decode_ingress(&mut cursor, Some(state), &cx)?; - Ok(match ingress { - Ingress::Discard { reason } => Outcome::Discard(reason), - Ingress::Open(_) => Outcome::Open, - _ => Outcome::Other, - }) -} - -#[test] -fn a_start_with_an_empty_stream_sid_refuses_the_whole_session_at_the_plane_entrypoint() { - let plane = StreamingPlane::EMPTY; - let mut state = fresh_twilio_state(); - let result = decode_one( - &plane, - &mut state, - &start_json("", "audio/x-mulaw", 8_000, 1), - ); - assert_eq!( - result, - Err(Decode::Malformed), - "an empty streamSid must refuse the session, not open a binding nothing can check against" - ); -} - -#[test] -fn a_start_negotiating_a_non_g711_format_refuses_the_whole_session_at_the_plane_entrypoint() { - let plane = StreamingPlane::EMPTY; - let mut state = fresh_twilio_state(); - let result = decode_one( - &plane, - &mut state, - &start_json("CA-real-sid", "audio/l16", 16_000, 2), - ); - assert_eq!( - result, - Err(Decode::UnsupportedOperation), - "a non-G.711/8kHz/mono negotiation must refuse the session rather than proceed with wrong \ - duration accounting" - ); -} - -#[test] -fn a_media_frame_naming_a_different_sid_is_still_discarded_after_the_empty_case_is_closed() { - let plane = StreamingPlane::EMPTY; - let mut state = fresh_twilio_state(); - - decode_one( - &plane, - &mut state, - &start_json("CA-real-sid", "audio/x-mulaw", 8_000, 1), - ) - .expect("a well-formed start must be accepted"); - - // A media frame naming a DIFFERENT, non-empty sid must still be refused as forged — proof the - // existing check was not weakened while the empty case was closed. - let forged = decode_one(&plane, &mut state, &media_json("CA-someone-elses-sid")) - .expect("a mismatched-sid media frame is discarded, not a decode error"); - assert_eq!( - forged, - Outcome::Discard(DiscardCode::ForgedSource), - "a media frame naming a sid other than the one `start` bound must still be discarded" - ); - - // A media frame naming the bound sid is accepted and opens the turn. - let matched = decode_one(&plane, &mut state, &media_json("CA-real-sid")) - .expect("a media frame carrying the bound sid must be accepted"); - assert_eq!( - matched, - Outcome::Open, - "a media frame carrying the bound sid must open the turn" - ); -} - -// ══ THE `media` PAYLOAD'S BASE64 — THE BILLED BYTE COUNT ═════════════════════════════════════════ -// -// Items 418/419. The byte count `twilio::decode` hands back for a `media` payload is the LEDGER -// quantity `crate::plane` times at 8 bytes/ms into `audio_ms_in`. The crate's private base64 copy -// used to strip `=` wherever it appeared, so `QQ==` repeated 2000 times decoded to 3000 bytes and -// was metered as 375 ms of caller audio, where the workspace's one decoder -// (`busbar_substrate_values::media::base64_decode`) refuses the same bytes: padding is terminal. - -/// A carrier `media` frame on `stream_sid` carrying `payload` verbatim as its base64. -fn media_json_with(stream_sid: &str, payload: &str) -> String { - serde_json::json!({ - "event": "media", - "streamSid": stream_sid, - "media": { "payload": payload }, - }) - .to_string() -} - -/// The bytes `twilio::decode` read out of a `media` frame carrying `payload`. -fn media_bytes(payload: &str) -> Result, TwilioError> { - match twilio::decode(media_json_with("CA-real-sid", payload).as_bytes())? { - TwilioEvent::Media { payload, .. } => Ok(payload.to_vec()), - other => panic!("a media frame decoded as {other:?}"), - } -} - -#[test] -fn a_media_payload_resuming_after_padding_is_refused_not_billed_as_a_longer_payload() { - assert_eq!(media_bytes("QQ==QQ=="), Err(TwilioError::BadPayload)); - assert_eq!(media_bytes("QQ=A"), Err(TwilioError::BadPayload)); - assert_eq!( - media_bytes(&"QQ==".repeat(2_000)), - Err(TwilioError::BadPayload) - ); -} - -#[test] -fn the_media_payload_decoder_keeps_the_rest_of_the_shared_decode_contract() { - // Padding present, absent, trailing whitespace or extra padding after it: all one byte. - assert_eq!(media_bytes("QQ=="), Ok(vec![b'A'])); - assert_eq!(media_bytes("QQ"), Ok(vec![b'A'])); - assert_eq!(media_bytes("QQ==\r\n"), Ok(vec![b'A'])); - assert_eq!(media_bytes("QQ==="), Ok(vec![b'A'])); - // Whitespace inside the data is ignored. - assert_eq!(media_bytes("QU\nJD"), Ok(b"ABC".to_vec())); - // A lone trailing sextet and a byte outside the alphabet refuse. - assert_eq!(media_bytes("QUJDR"), Err(TwilioError::BadPayload)); - assert_eq!(media_bytes("QU*D"), Err(TwilioError::BadPayload)); - // A real 20 ms µ-law frame (160 bytes, every value) survives the carrier's own encoder. - let mulaw: Vec = (0..160u8).collect(); - match twilio::decode(&twilio::encode_media("CA-real-sid", &mulaw)) { - Ok(TwilioEvent::Media { payload, .. }) => assert_eq!(payload.to_vec(), mulaw), - other => panic!("a round-tripped media frame decoded as {other:?}"), - } -} - -#[test] -fn a_padding_resumed_media_frame_is_refused_at_the_plane_entrypoint_and_opens_no_turn() { - let plane = StreamingPlane::EMPTY; - let mut state = fresh_twilio_state(); - decode_one( - &plane, - &mut state, - &start_json("CA-real-sid", "audio/x-mulaw", 8_000, 1), - ) - .expect("a well-formed start must be accepted"); - let result = decode_one( - &plane, - &mut state, - &media_json_with("CA-real-sid", &"QQ==".repeat(2_000)), - ); - assert!( - result.is_err(), - "a media payload resuming after its padding must be refused, not metered: got {result:?}" - ); -} - -/// Item 419: the comment justifying the local base64 copy once told a reviewer it was the SAME -/// algorithm `crate::codec::topology::twilio` carries — that module carries none (it imports -/// the substrate decoder), and the two differed on padding, which is how the billed-byte drift above -/// survived review. The justification must name the real single source and must not claim an -/// equivalence nobody checked; the decode-contract tests above are what check it. -#[test] -fn the_local_base64_copy_names_the_one_decoder_it_must_match_and_claims_no_unchecked_equivalence() { - let src = include_str!("../twilio.rs"); - assert!( - !src.contains("identical standard algorithm"), - "twilio.rs still claims an unchecked algorithm equivalence for its private base64" - ); - assert!( - // Spelled in two halves so this line does not itself name the substrate crate the plane's - // purity test (`tests/purity.rs`) forbids in non-comment source. - src.contains(concat!("busbar_sub", "strate_values::media")), - "twilio.rs's base64 comment must name the workspace's one decoder it stands in for" - ); -} diff --git a/crates/busbar-plane-streaming/src/tests/ulaw.rs b/crates/busbar-plane-streaming/src/tests/ulaw.rs deleted file mode 100644 index 0b595b5265..0000000000 --- a/crates/busbar-plane-streaming/src/tests/ulaw.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! µ-law ↔ PCM16 tests against known reference values, not just round-trip fuzz. - -use crate::ulaw::{decode_frame, encode_frame, pcm16_to_ulaw_byte, ulaw_byte_to_pcm16}; - -#[test] -fn positive_zero_ulaw_byte_decodes_to_pcm_zero() { - // The ITU-T G.711 reference: 0xFF is µ-law's "positive zero" code. - assert_eq!(ulaw_byte_to_pcm16(0xFF), 0); -} - -#[test] -fn pcm_zero_encodes_to_the_positive_zero_ulaw_byte() { - assert_eq!(pcm16_to_ulaw_byte(0), 0xFF); -} - -#[test] -fn ulaw_zero_byte_decodes_to_a_large_negative_sample() { - // 0x00 is µ-law's most-negative code; the ITU-T reference table places it at -32124. - assert_eq!(ulaw_byte_to_pcm16(0x00), -32124); -} - -#[test] -fn the_most_negative_decoded_sample_encodes_back_to_the_ulaw_zero_byte() { - assert_eq!(pcm16_to_ulaw_byte(-32124), 0x00); -} - -#[test] -fn every_ulaw_byte_round_trips_within_the_algorithm_s_own_quantization() { - // µ-law is lossy by construction (14-bit magnitude compressed to a 4-bit mantissa within a - // segment), so decode→encode does not reproduce every byte exactly; it reproduces byte VALUES - // that are already legal µ-law codes for a segment's canonical representative. What must hold - // for every one of the 256 possible bytes is that decoding it and then re-encoding the result - // lands on a byte that decodes to the SAME sample the first decode produced — the round trip - // is stable at the PCM value, which is the property the plane's transcode boundary depends on. - for byte in 0u8..=255 { - let sample = ulaw_byte_to_pcm16(byte); - let re_encoded = pcm16_to_ulaw_byte(sample); - let re_decoded = ulaw_byte_to_pcm16(re_encoded); - assert_eq!( - sample, re_decoded, - "byte {byte:#04x} decoded to {sample}, round-tripped to a byte decoding to {re_decoded}" - ); - } -} - -#[test] -fn frame_helpers_agree_with_the_per_sample_functions() { - let ulaw = [0xFFu8, 0x00, 0x7F, 0x80]; - let pcm = decode_frame(&ulaw); - assert_eq!(pcm.len(), ulaw.len() * 2); - for (i, &byte) in ulaw.iter().enumerate() { - let sample = i16::from_le_bytes([pcm[i * 2], pcm[i * 2 + 1]]); - assert_eq!(sample, ulaw_byte_to_pcm16(byte)); - } - let back = encode_frame(&pcm); - assert_eq!(back.len(), ulaw.len()); -} - -#[test] -fn an_odd_trailing_byte_is_dropped_rather_than_guessed_at() { - let pcm = [0u8, 0, 1]; // one whole sample plus one stray byte - assert_eq!(encode_frame(&pcm).len(), 1); -} diff --git a/crates/busbar-plane-streaming/src/twilio.rs b/crates/busbar-plane-streaming/src/twilio.rs deleted file mode 100644 index 64ddc113ae..0000000000 --- a/crates/busbar-plane-streaming/src/twilio.rs +++ /dev/null @@ -1,357 +0,0 @@ -//! A minimal Twilio Media Streams wire reader/writer, written independently for this crate. -//! -//! Twilio's Media Streams protocol is a JSON-framed message-per-WS-frame wire: lifecycle events -//! (`connected`, `start`, `mark`, `dtmf`, `stop`) and a per-chunk `media` event whose `payload` is -//! base64 8 kHz G.711 µ-law audio — `{"event":"media","media":{"payload":""},"streamSid":"..."}`. -//! -//! No duplex dialect codec covers this wire (it is not one of the plane's two duplex dialects). -//! The one Twilio-shaped module in this crate's `codec` module (`crate::codec::topology::twilio`) -//! is the legacy engine's, and this module does not use it: it is written from the wire shape alone -//! (confirmed against `docs/design/BUSBAR-1.6.0.md` #18/#45 and the public Twilio Media -//! Streams reference, both cited in this crate's design notes) rather than adapted from that -//! runtime-gated module; any structural resemblance is the two independently converging on the same -//! public wire format, not a copy. -//! -//! The events this module models onto the shared [`crate::codec::ir`] vocabulary: a `media` event -//! becomes an [`crate::codec::ir::media::IrAudioFrame`] (direction `Up`, format -//! [`crate::codec::ir::media::AudioFormat::G711Ulaw`]) carrying the base64-decoded µ-law bytes -//! verbatim — the µ-law↔PCM16 transform happens at the plane's `encode_ingress_frame` seam -//! ([`crate::plane`]), never here. The lifecycle events carry no audio and are surfaced as their own -//! variant so the plane can track (or ignore) them without guessing at a synthetic IR event for a -//! wire message with no IR home. - -use bytes::Bytes; - -/// One decoded Twilio Media Streams event. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TwilioEvent { - /// The one-time handshake event, first on the socket. Carries nothing. - Connected, - /// The stream opened. Carries the identifiers later frames repeat and the negotiated format. - Start { - /// The `streamSid` Twilio minted for this connection, repeated on every later frame. - stream_sid: String, - /// Twilio's `callSid` for the underlying PSTN call. - call_sid: String, - /// The negotiated media encoding string (`audio/x-mulaw` for the passthrough carrier). - encoding: String, - /// The negotiated sample rate in Hz (`8000` for the passthrough carrier). - sample_rate: u64, - /// The negotiated channel count (`1`, mono). - channels: u64, - }, - /// A ~20 ms audio chunk. `payload` is the base64-decoded, still µ-law-encoded audio. - Media { - /// The connection's `streamSid`, to check against the value bound at admission. - stream_sid: String, - /// The raw µ-law bytes. - payload: Bytes, - }, - /// A playback-position acknowledgement Twilio echoes back for a `mark` this plane sent. - Mark { - /// The connection's `streamSid`. - stream_sid: String, - /// The mark name the outbound side chose. - name: String, - }, - /// A touch-tone keypress heard on the inbound track — sent only when the stream has DTMF - /// enabled. It carries no session audio and is discarded exactly like [`Self::Mark`]. - Dtmf { - /// The connection's `streamSid`. - stream_sid: String, - /// The key that was pressed (`0`-`9`, `*`, `#`, `A`-`D`), or empty when Twilio omits it. - digit: String, - }, - /// The terminal event. - Stop, -} - -/// THE ASSUMED NEGOTIATED FORMAT — the one this reader hands `media` payloads on as (raw µ-law -/// bytes, see the module doc) and the one every later consumer treats them as: the µ-law→PCM16 -/// widen ([`crate::ulaw::decode_frame`]) and the duration [`crate::plane`] derives from a byte -/// count (`AudioFormat::G711Ulaw::bytes_to_ms`, which IS a LEDGER quantity — a call's billed -/// duration rolls up from exactly this count). Twilio's own default and near-universal choice for -/// this carrier, and the only shape the rest of this crate's Twilio path is written against. -const ASSUMED_ENCODING: &str = "audio/x-mulaw"; -const ASSUMED_SAMPLE_RATE_HZ: u64 = 8_000; -const ASSUMED_CHANNELS: u64 = 1; - -/// Why an inbound Twilio frame could not be read. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TwilioError { - /// The frame was not well-formed JSON, or a field this reader requires was absent. - Malformed, - /// The frame named an `event` this reader does not model. - UnknownEvent(String), - /// A `media` payload was not valid base64. - BadPayload, - /// `start` negotiated a `mediaFormat` other than [`ASSUMED_ENCODING`] / - /// [`ASSUMED_SAMPLE_RATE_HZ`] / [`ASSUMED_CHANNELS`] — the format every later frame on this - /// stream would otherwise be decoded and TIMED under. Refused rather than served on a wrong - /// assumption: a duration derived from a mismatched format is a wrong ledger entry, silently, - /// since nothing about a mis-decoded byte count looks broken on its own. - UnsupportedMediaFormat, -} - -/// Decode one inbound Twilio Media Streams WS frame. -/// -/// # Errors -/// Returns [`TwilioError`] when the frame is not well-formed Twilio JSON, names an event this -/// reader does not model, carries a `media` payload that is not valid base64, binds an empty or -/// absent `streamSid` (which cannot BIND anything — see [`TwilioEvent::Start`]), or negotiates a -/// media format other than the one this crate's Twilio path assumes throughout. -pub fn decode(frame: &[u8]) -> Result { - let v: serde_json::Value = serde_json::from_slice(frame).map_err(|_| TwilioError::Malformed)?; - let event = v - .get("event") - .and_then(serde_json::Value::as_str) - .ok_or(TwilioError::Malformed)?; - match event { - "connected" => Ok(TwilioEvent::Connected), - "start" => { - let start = v.get("start").ok_or(TwilioError::Malformed)?; - let stream_sid = str_field(start, "streamSid") - .or_else(|| str_field(&v, "streamSid")) - .ok_or(TwilioError::Malformed)?; - // AN EMPTY ID IS NOT A BINDING. `stream_sid` is what every later `media` frame is - // checked against (`crate::plane`'s per-connection anti-forgery guard). If this reader - // ever handed one back empty, an attacker's own `media` frame carrying an equally empty - // (or simply omitted — the same empty default) `streamSid` would compare EQUAL to the - // "bound" value and the check that exists to refuse an unbound source would pass - // vacuously. Refused HERE, at the one place that can still say why, rather than let a - // binding that cannot bind reach `decode_twilio_frame`. - if stream_sid.is_empty() { - return Err(TwilioError::Malformed); - } - let call_sid = str_field(start, "callSid").unwrap_or_default(); - let mf = start.get("mediaFormat").ok_or(TwilioError::Malformed)?; - let encoding = str_field(mf, "encoding").unwrap_or_default(); - let sample_rate = mf - .get("sampleRate") - .and_then(serde_json::Value::as_u64) - .unwrap_or_default(); - let channels = mf - .get("channels") - .and_then(serde_json::Value::as_u64) - .unwrap_or_default(); - if encoding != ASSUMED_ENCODING - || sample_rate != ASSUMED_SAMPLE_RATE_HZ - || channels != ASSUMED_CHANNELS - { - return Err(TwilioError::UnsupportedMediaFormat); - } - Ok(TwilioEvent::Start { - stream_sid, - call_sid, - encoding, - sample_rate, - channels, - }) - } - "media" => { - let stream_sid = str_field(&v, "streamSid").unwrap_or_default(); - let payload_b64 = v - .get("media") - .and_then(|m| m.get("payload")) - .and_then(serde_json::Value::as_str) - .ok_or(TwilioError::Malformed)?; - let payload = base64_decode(payload_b64).ok_or(TwilioError::BadPayload)?; - Ok(TwilioEvent::Media { - stream_sid, - payload: Bytes::from(payload), - }) - } - "mark" => Ok(TwilioEvent::Mark { - stream_sid: str_field(&v, "streamSid").unwrap_or_default(), - name: v - .get("mark") - .and_then(|m| m.get("name")) - .and_then(serde_json::Value::as_str) - .unwrap_or_default() - .to_string(), - }), - "dtmf" => Ok(TwilioEvent::Dtmf { - stream_sid: str_field(&v, "streamSid").unwrap_or_default(), - digit: v - .get("dtmf") - .and_then(|d| d.get("digit")) - .and_then(serde_json::Value::as_str) - .unwrap_or_default() - .to_string(), - }), - "stop" => Ok(TwilioEvent::Stop), - other => Err(TwilioError::UnknownEvent(other.to_string())), - } -} - -fn str_field(v: &serde_json::Value, key: &str) -> Option { - v.get(key) - .and_then(serde_json::Value::as_str) - .map(str::to_string) -} - -/// Encode raw µ-law bytes into a Twilio outbound `media` envelope for a given `stream_sid`. -#[must_use] -pub fn encode_media(stream_sid: &str, mulaw: &[u8]) -> Vec { - let mut out = Vec::new(); - encode_media_into(&mut out, stream_sid, mulaw); - out -} - -/// The same envelope, rendered into a buffer the caller already has. -/// -/// This is the shape the downlink writes in. The envelope is FIXED — three members, in the one -/// order this dialect's documents are written in — and everything about it except the identifier -/// and the payload was known when the crate was compiled. Building a document to describe it, and -/// a string to hold the payload, and then serializing the document, spends three allocations per -/// AUDIO FRAME to reach bytes that could have been appended. A call carries fifty frames a second. -/// -/// The buffer is CLEARED, not appended to, so a caller may hand the same one back frame after frame -/// and pay for its growth once. -pub fn encode_media_into(out: &mut Vec, stream_sid: &str, mulaw: &[u8]) { - out.clear(); - // An identifier that would have to be escaped is not one this dialect mints, but it is one this - // function may be handed. Rather than carry an escaper that would almost never run — and would - // be the one part of this rendering nothing exercises — the rare case goes back through the - // serializer, which is the authority on what those bytes are. - if !is_bare_json_string(stream_sid) { - let doc = serde_json::json!({ - "event": "media", - "streamSid": stream_sid, - "media": { "payload": base64_encode(mulaw) }, - }); - out.extend_from_slice(&serde_json::to_vec(&doc).unwrap_or_default()); - return; - } - const HEAD: &[u8] = br#"{"event":"media","media":{"payload":""#; - const MIDDLE: &[u8] = br#""},"streamSid":""#; - const TAIL: &[u8] = br#""}"#; - out.reserve( - HEAD.len() + base64_len(mulaw.len()) + MIDDLE.len() + stream_sid.len() + TAIL.len(), - ); - out.extend_from_slice(HEAD); - base64_encode_into(mulaw, out); - out.extend_from_slice(MIDDLE); - out.extend_from_slice(stream_sid.as_bytes()); - out.extend_from_slice(TAIL); -} - -/// Whether a string is its own JSON body — nothing in it a serializer would rewrite. -/// -/// Printable ASCII with neither of the two characters a JSON string cannot carry raw. Everything -/// else, including every non-ASCII byte, is left to the serializer. -fn is_bare_json_string(s: &str) -> bool { - s.bytes() - .all(|b| (0x20..0x7f).contains(&b) && b != b'"' && b != b'\\') -} - -/// Encode a named playback-position mark for a given `stream_sid`. -/// -/// Used as the provisional wire rendering for a server event this dialect's own vocabulary has no -/// audio-bearing counterpart for (see `crate::plane`'s `encode_response` documentation) — an -/// acknowledged no-op frame rather than silently dropping the event. -#[must_use] -pub fn encode_mark(stream_sid: &str, name: &str) -> Vec { - let out = serde_json::json!({ - "event": "mark", - "streamSid": stream_sid, - "mark": { "name": name }, - }); - serde_json::to_vec(&out).unwrap_or_default() -} - -// ── standard base64 (RFC 4648), a LOCAL copy of the workspace's one decoder contract ───────────── -// -// The workspace's one base64 implementation is `busbar_substrate_values::media`; every other reader -// (`crate::codec::topology::twilio` and `ir::codec` among them) imports it and carries no -// algorithm of its own. It is in this crate's closure (through `busbar-voice-codec`), but a plane -// names no substrate crate (`tests/purity.rs`, `the_plane_names_no_kernel_side_crate`), so this copy -// stands in for it. It is NOT a second algorithm: its decode contract must match that one byte for -// byte, because the byte count it returns is billed — `crate::plane` derives a call's audio duration -// from it. The contract, all of it: -// whitespace is ignored; `=` padding is TERMINAL (only whitespace or more `=` may follow it — data -// resuming after padding, `QQ==QQ==`, is malformed, never one longer payload); a lone trailing -// sextet, or any byte outside the alphabet, refuses the payload. `tests::twilio` pins each clause. - -const B64_ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -/// How many characters this many bytes encode to. -const fn base64_len(bytes: usize) -> usize { - bytes.div_ceil(3) * 4 -} - -fn base64_encode(data: &[u8]) -> String { - let mut out = Vec::with_capacity(base64_len(data.len())); - base64_encode_into(data, &mut out); - // Every byte appended below is one of the alphabet's, `=` included, so this is ASCII. - String::from_utf8(out).unwrap_or_default() -} - -/// The same encoding, appended to a buffer the caller already has. -fn base64_encode_into(data: &[u8], out: &mut Vec) { - for chunk in data.chunks(3) { - let b0 = u32::from(chunk[0]); - let b1 = u32::from(*chunk.get(1).unwrap_or(&0)); - let b2 = u32::from(*chunk.get(2).unwrap_or(&0)); - let n = (b0 << 16) | (b1 << 8) | b2; - out.push(B64_ALPHABET[((n >> 18) & 63) as usize]); - out.push(B64_ALPHABET[((n >> 12) & 63) as usize]); - out.push(if chunk.len() > 1 { - B64_ALPHABET[((n >> 6) & 63) as usize] - } else { - b'=' - }); - out.push(if chunk.len() > 2 { - B64_ALPHABET[(n & 63) as usize] - } else { - b'=' - }); - } -} - -fn base64_sextet(c: u8) -> Option { - match c { - b'A'..=b'Z' => Some(u32::from(c - b'A')), - b'a'..=b'z' => Some(u32::from(c - b'a') + 26), - b'0'..=b'9' => Some(u32::from(c - b'0') + 52), - b'+' => Some(62), - b'/' => Some(63), - _ => None, - } -} - -fn base64_decode(s: &str) -> Option> { - let mut sextets: Vec = Vec::with_capacity(s.len()); - // Padding is TERMINAL: once `=` is seen, encoded data resuming is a malformed payload, refused. - let mut padded = false; - for b in s.bytes() { - if b.is_ascii_whitespace() { - continue; - } - if b == b'=' { - padded = true; - continue; - } - if padded { - return None; - } - sextets.push(b); - } - let mut out = Vec::with_capacity(sextets.len() / 4 * 3); - for chunk in sextets.chunks(4) { - if chunk.len() < 2 { - return None; - } - let mut n = 0u32; - for &c in chunk { - n = (n << 6) | base64_sextet(c)?; - } - let missing = 4 - chunk.len(); - n <<= 6 * missing as u32; - let be = n.to_be_bytes(); - for byte in be.iter().take(1 + (chunk.len() - 1)).skip(1) { - out.push(*byte); - } - } - Some(out) -} diff --git a/crates/busbar-plane-streaming/src/ulaw.rs b/crates/busbar-plane-streaming/src/ulaw.rs deleted file mode 100644 index 09bf391bbc..0000000000 --- a/crates/busbar-plane-streaming/src/ulaw.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! G.711 µ-law ↔ PCM16 — implemented from the ITU-T G.711 bias/segment definition, by this crate. -//! -//! `busbar-voice` does not implement this transform: its own `AudioFormat` enum carries only the -//! byte-rate bookkeeping (`bytes_per_ms`) the barge-in truncate arithmetic needs, and its doc -//! comments name the actual sample transcode an unimplemented "seam...armed only when a lane -//! declares it" (`crate::codec::ir::media`). The `twilio-media-streams` dialect is that lane: Twilio -//! carries 8 kHz G.711 µ-law, and every upstream this plane can dial (OpenAI Realtime, Gemini Live) -//! speaks PCM16. This module is the transform, written independently against the standard, with its -//! own unit tests against known reference values. -//! -//! ## The algorithm -//! -//! µ-law encodes a 14-bit-magnitude linear sample into an 8-bit sign/exponent/mantissa byte: one -//! sign bit, a 3-bit exponent selecting one of eight logarithmic segments, and a 4-bit mantissa -//! giving the position within the segment. The constants below (`BIAS = 0x84`, `CLIP = 8159`) and -//! the segment table are the values the ITU-T G.711 reference algorithm defines; a byte is always -//! bitwise-inverted on the wire (`!ulawbyte`), which is why both directions below start by -//! inverting. -//! -//! Decode uses the closed 8-entry segment-base table directly. Encode searches for the same segment -//! by shifting a probe mask down from the top bit the 14-bit magnitude range can set; this is the -//! same search the reference algorithm performs, written as a loop instead of a literal 256-entry -//! lookup table so the two directions can be read and checked against each other by eye. - -/// The bias added to a linear magnitude before segment/mantissa extraction, and subtracted back out -/// on decode. The ITU-T G.711 reference value. -const BIAS: i32 = 0x84; - -/// The largest linear magnitude (after removing the sign) a sample is clamped to before encoding. -/// The ITU-T G.711 reference value; every larger magnitude quantizes to this segment's top code. -const CLIP: i32 = 32635; - -/// The eight segment base offsets a decoded exponent selects among, from the ITU-T G.711 reference -/// table. `PCM = SEGMENT_BASE[exponent] + (mantissa << (exponent + 3))`, minus the bias, sign-applied. -const SEGMENT_BASE: [i32; 8] = [0, 132, 396, 924, 1980, 4092, 8316, 16764]; - -/// Decode one µ-law byte to a 16-bit signed linear PCM sample. -/// -/// Reference vector: `0xFF` (µ-law "positive zero") decodes to `0`. -#[must_use] -pub fn ulaw_byte_to_pcm16(ulaw_byte: u8) -> i16 { - let inverted = !ulaw_byte; - let sign_bit = inverted & 0x80; - let exponent = usize::from((inverted >> 4) & 0x07); - let mantissa = i32::from(inverted & 0x0F); - let magnitude = SEGMENT_BASE[exponent] + (mantissa << (exponent + 3)); - let signed = if sign_bit != 0 { -magnitude } else { magnitude }; - signed.clamp(i32::from(i16::MIN), i32::from(i16::MAX)) as i16 -} - -/// Encode one 16-bit signed linear PCM sample to a µ-law byte. -/// -/// Reference vector: `0` encodes to `0xFF`, the inverse of [`ulaw_byte_to_pcm16`]'s `0xFF → 0`. -#[must_use] -pub fn pcm16_to_ulaw_byte(sample: i16) -> u8 { - let sign: u8 = if sample < 0 { 0x80 } else { 0x00 }; - let mut magnitude = i32::from(sample).abs(); - if magnitude > CLIP { - magnitude = CLIP; - } - magnitude += BIAS; - - // Find the segment: the largest exponent whose bit (starting at bit 14, the top bit a - // biased+clipped magnitude can set) is present in the magnitude. - let mut exponent: i32 = 7; - let mut probe: i32 = 0x4000; - while exponent > 0 && (magnitude & probe) == 0 { - exponent -= 1; - probe >>= 1; - } - let mantissa = (magnitude >> (exponent + 3)) & 0x0F; - let byte = sign | ((exponent as u8) << 4) | (mantissa as u8); - !byte -} - -/// Decode a whole frame of µ-law bytes into little-endian PCM16 bytes (two bytes per input byte). -#[must_use] -pub fn decode_frame(ulaw: &[u8]) -> Vec { - let mut out = Vec::with_capacity(ulaw.len() * 2); - for &b in ulaw { - out.extend_from_slice(&ulaw_byte_to_pcm16(b).to_le_bytes()); - } - out -} - -/// Encode a whole frame of little-endian PCM16 bytes into µ-law bytes (one byte per input sample). -/// -/// A trailing odd byte (an incomplete final sample) is dropped rather than guessed at. -#[must_use] -pub fn encode_frame(pcm16_le: &[u8]) -> Vec { - let mut out = Vec::with_capacity(pcm16_le.len() / 2); - for chunk in pcm16_le.as_chunks::<2>().0 { - let sample = i16::from_le_bytes(*chunk); - out.push(pcm16_to_ulaw_byte(sample)); - } - out -} diff --git a/crates/busbar-plane-streaming/tests/alloc_gate.rs b/crates/busbar-plane-streaming/tests/alloc_gate.rs index 314c3f9b81..63fcf78d6a 100644 --- a/crates/busbar-plane-streaming/tests/alloc_gate.rs +++ b/crates/busbar-plane-streaming/tests/alloc_gate.rs @@ -12,7 +12,7 @@ //! allowed to fall on the far side of a byte-for-byte comparison against the serializer that used //! to produce it. -use busbar_plane_streaming::twilio; +use busbar_plane_streaming::codec::topology::twilio::TwilioEnvelope; use std::alloc::{GlobalAlloc, Layout, System}; use std::cell::Cell; @@ -61,16 +61,28 @@ fn frame_bytes() -> Vec { /// The bytes the serializer produced, restated as the reference the renderer is held to. /// -/// The member ORDER is the serializer's, not a preference: this document's members are written in -/// the order a JSON object's keys sort in, because that is the order the old renderer emitted and -/// every recorded conformance answer was taken against it. +/// The member ORDER is the envelope's own (`event`, `streamSid`, `media`), the order Twilio's Media +/// Streams documents write it in and the codec's serializer emits. fn reference(stream_sid: &str, mulaw: &[u8]) -> Vec { - let doc = serde_json::json!({ - "event": "media", - "streamSid": stream_sid, - "media": { "payload": base64(mulaw) }, - }); - serde_json::to_vec(&doc).expect("the document serializes") + #[derive(serde::Serialize)] + struct Payload { + payload: String, + } + #[derive(serde::Serialize)] + struct Media<'a> { + event: &'static str, + #[serde(rename = "streamSid")] + stream_sid: &'a str, + media: Payload, + } + serde_json::to_vec(&Media { + event: "media", + stream_sid, + media: Payload { + payload: base64(mulaw), + }, + }) + .expect("the document serializes") } /// Standard base64, spelled out here so the reference owes the renderer nothing. @@ -113,7 +125,7 @@ fn the_rendered_envelope_is_the_one_the_serializer_produced() { for sid in [SID, "", "a\"b\\c", "sid with spaces", "sid-ünïcode"] { for payload in &payloads { let mut out = Vec::new(); - twilio::encode_media_into(&mut out, sid, payload); + TwilioEnvelope::encode_media_into(&mut out, sid, payload); assert_eq!( out, reference(sid, payload), @@ -121,7 +133,7 @@ fn the_rendered_envelope_is_the_one_the_serializer_produced() { payload.len() ); assert_eq!( - twilio::encode_media(sid, payload), + TwilioEnvelope::encode_media(sid, payload), out, "the owning form and the rendering form disagree for {sid:?}" ); @@ -135,12 +147,8 @@ fn the_rendered_envelope_is_the_one_the_serializer_produced() { /// Zero. The envelope is fixed, the buffer is the caller's, and the only two things that vary from /// frame to frame — the payload and the identifier — are appended, not built. /// -/// A buffer that has already carried a frame is not an assumption this number makes about a caller -/// it cannot see: the one caller in this crate — the downlink renderer in `plane.rs` — holds its -/// buffer on the session (`VoiceSessionState::render_buf`) and hands the same one back frame after -/// frame. A caller that built a fresh vector per frame would pay an allocation per frame no matter -/// what this renderer costs, which is what made the earlier form of this number true of the -/// function and false of the path it was quoted for. +/// A buffer that has already carried a frame is the caller's to hold: a caller that built a fresh +/// vector per frame would pay an allocation per frame no matter what this renderer costs. const RENDER_ALLOCS: u64 = 0; #[test] @@ -150,11 +158,11 @@ fn rendering_a_downlink_frame_into_a_held_buffer_allocates_nothing() { // One warm call outside the window: the buffer takes its capacity here, which is the whole // point of handing the same one back frame after frame. - twilio::encode_media_into(&mut out, SID, &mulaw); + TwilioEnvelope::encode_media_into(&mut out, SID, &mulaw); let expected = out.clone(); let count = allocations_of(|| { - twilio::encode_media_into(&mut out, SID, &mulaw); + TwilioEnvelope::encode_media_into(&mut out, SID, &mulaw); }); // The serializer route, measured rather than remembered. `reference` is the renderer this diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs deleted file mode 100644 index 37547a6210..0000000000 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ /dev/null @@ -1,353 +0,0 @@ -//! The streaming-plane conformance rig: one live realtime-voice session, end to end. -//! -//! This drives [`busbar_plane_streaming::StreamingPlane`] — the STREAMING plane, registered under its -//! own key — through the whole shape a governed streaming turn takes: RESERVE (a session opens, a turn -//! is drafted, the plane names what the admit step reserves a hold against), TURNS (audio relays under -//! the open turn's hold; a barge-in supersedes it; a provider tool call opens a sub-unit), and SETTLE -//! (the turn's `response.done` carries usage, and the plane's meter names every per-turn class and the -//! quantity the money-book settles END-OF-TURN against). -//! -//! The dialect exercised is `openai-realtime` — the live realtime VOICE dialect, which is ONE dialect of -//! the streaming plane, not the plane's name. The wire fixtures are the same ones the underlying voice -//! codec proves; the point of this rig is that the STREAMING plane attributes and meters them, so a -//! deployment that registers `"streaming"` gets a byte-identical duplex session to the one the voice -//! adapter proves — the reframe changes the plane, not the bytes. - -mod harness; - -use busbar_contract::bounded::{FactValue, Facts, Labels}; -use busbar_contract::ids::{LaneId, OpClassId}; -use busbar_contract::plane::{Ingress, Plane, PlaneMeta, Progress, SessionPlane}; -use busbar_contract::wire::FrameCursor; -use serde_json::json; - -use busbar_plane_streaming::{Dialect, StreamingPlane, Upstream}; -use harness::{ctx, destination, frame, EmptyConfig, LeakPlaneAlloc, WsStack}; - -/// A streaming plane configured with one openai-realtime (voice-dialect) upstream. -fn streaming_plane() -> StreamingPlane { - static UPSTREAMS: &[Upstream] = &[Upstream { - lane: LaneId::new("realtime"), - host: "api.openai.com", - dialect: Dialect::OpenaiRealtime, - }]; - StreamingPlane::new(UPSTREAMS) -} - -fn session_update_fixture() -> Vec { - serde_json::to_vec(&json!({ - "type": "session.update", - "session": { - "modalities": ["audio", "text"], - "instructions": "You are a helpful voice agent.", - "voice": "marin", - "input_audio_format": "pcm16", - "output_audio_format": "g711_ulaw", - "turn_detection": { - "type": "server_vad", - "threshold": 0.5, - "prefix_padding_ms": 300, - "silence_duration_ms": 200, - "create_response": true, - "interrupt_response": true - }, - "tools": [{ "type": "function", "name": "lookup", "parameters": { "type": "object" } }], - "tool_choice": "auto", - "max_output_tokens": 4096 - } - })) - .expect("fixture serializes") -} - -/// THE WHOLE SHAPE, in one session: reserve, turns (relay + barge-in + tool call), settle. -#[test] -fn a_realtime_voice_session_reserves_takes_turns_and_settles() { - let plane = streaming_plane(); - - // The plane is STREAMING, and the realtime voice dialect is one of its dialects — asserted at the - // identity level so the reframe is a fact the rig checks, not a comment. - assert_eq!( - ::KEY, - "streaming", - "the plane registers under its own streaming key, never 'voice'" - ); - assert_eq!(Dialect::OpenaiRealtime.name(), "openai-realtime"); - - let arena = LeakPlaneAlloc; - let config = EmptyConfig; - let transport = WsStack::new("/v1/realtime"); - let labels = Labels::new(); - let c = ctx(&arena, &config, &transport, &labels); - - // ── RESERVE ──────────────────────────────────────────────────────────────────────────────── - // The client half of the session's codec state is opened by the plane, as the kernel opens it at - // session admit; the first client frame drafts the turn the hold is reserved against. - let mut state = SessionPlane::open_session(&plane, &c); - let open_bytes = session_update_fixture(); - let open_frames = [frame(&open_bytes)]; - let mut open_cursor = FrameCursor::new(&open_frames); - let ingress = plane - .decode_ingress(&mut open_cursor, Some(&mut state), &c) - .expect("session.update decodes"); - let Ingress::Open(draft) = ingress else { - panic!("expected Ingress::Open (a turn opens), got {ingress:?}"); - }; - assert_eq!(draft.op.as_str(), "duplex_turn"); - assert_eq!( - draft.facts.get(busbar_plane_streaming::meta::FACT_DIALECT), - Some(FactValue::Str("openai-realtime")), - "the opened turn names the realtime voice dialect" - ); - assert!( - draft.correlation_out.is_some(), - "the turn carries the correlation an answer rides back on" - ); - - // The admit step is where the money-book reserves a hold: the plane's `admit` names the lane, - // the response ceiling and the priced input span the reservation is sized against. Proven to - // answer here; the reservation itself is the cost unit's, on the far side of the kernel. - let opened_unit = harness::unit(OpClassId::new("duplex_turn"), draft.body_ir, draft.facts); - let admit = plane.admit(&opened_unit, &c); - assert!( - !format!("{admit:?}").is_empty(), - "admit answers with the facts the hold is reserved against" - ); - - // ── TURNS ───────────────────────────────────────────────────────────────────────────────── - // (1) Audio relays UNDER the open turn's hold — a second frame does not reopen a turn. - let audio = serde_json::to_vec(&json!({ - "type": "input_audio_buffer.append", - "audio": "AAAA", - })) - .expect("audio fixture serializes"); - let audio_frames = [frame(&audio)]; - let mut audio_cursor = FrameCursor::new(&audio_frames); - let relayed = plane - .decode_ingress(&mut audio_cursor, Some(&mut state), &c) - .expect("audio frame decodes"); - let Ingress::Frame { for_, .. } = relayed else { - panic!("expected Ingress::Frame (audio relays under the turn), got {relayed:?}"); - }; - assert!( - for_.is_some(), - "the relay frame carries the turn's correlation" - ); - - // (2) BARGE-IN: a client truncation supersedes the open turn, and the plane writes the declared - // interrupt fact carrying the playback position — the kernel's teller loop reads this to truncate. - let truncate = serde_json::to_vec(&json!({ - "type": "conversation.item.truncate", - "item_id": "item_1", - "content_index": 0, - "audio_end_ms": 640, - })) - .expect("truncate fixture serializes"); - let truncate_frames = [frame(&truncate)]; - let mut truncate_cursor = FrameCursor::new(&truncate_frames); - let barge_in = plane - .decode_ingress(&mut truncate_cursor, Some(&mut state), &c) - .expect("truncate decodes"); - let Ingress::Open(bi_draft) = barge_in else { - panic!("expected Ingress::Open (barge-in opens the superseding turn), got {barge_in:?}"); - }; - let interrupt_fact = ::INTERRUPT_FACT - .expect("the streaming plane declares an interrupt fact"); - assert_eq!( - bi_draft.facts.get(interrupt_fact), - Some(FactValue::Int(640)), - "barge-in carries the playback position the truncation is established at" - ); - - // (3) TOOL LOOP: a provider tool call arrives on the upstream half and surfaces as a one-shot - // sub-unit the kernel drives the tool loop over — priced as its own `tool_call` op class. The - // ANNOUNCEMENT mints nothing; the call becomes its own unit on CLOSE, when its arguments are - // complete, because a tool call dispatched with no arguments is a different call. - let mut upstream_state = SessionPlane::open_upstream( - &plane, - &destination("api.openai.com", LaneId::new("realtime")), - &c, - ); - let tool_open = serde_json::to_vec(&json!({ - "type": "response.output_item.added", - "item": { "type": "function_call", "call_id": "call_1", "name": "lookup" }, - })) - .expect("tool-call fixture serializes"); - let tool_frames = [frame(&tool_open)]; - let mut tool_cursor = FrameCursor::new(&tool_frames); - let announced = plane - .decode_response( - &mut tool_cursor, - &destination("api.openai.com", LaneId::new("realtime")), - Some(&mut upstream_state), - &c, - ) - .expect("tool-call open decodes"); - assert!( - matches!(announced, Progress::Discard { .. }), - "an announcement accumulates; it mints no unit on its own, got {announced:?}" - ); - - let tool_close = serde_json::to_vec(&json!({ - "type": "response.function_call_arguments.done", - "call_id": "call_1", - "name": "lookup", - "arguments": "{\"city\":\"Paris\"}", - })) - .expect("tool-call close fixture serializes"); - let tool_frames = [frame(&tool_close)]; - let mut tool_cursor = FrameCursor::new(&tool_frames); - let tool = plane - .decode_response( - &mut tool_cursor, - &destination("api.openai.com", LaneId::new("realtime")), - Some(&mut upstream_state), - &c, - ) - .expect("tool-call close decodes"); - let Progress::OneShot(tool_draft) = tool else { - panic!("expected Progress::OneShot (a completed tool call opens), got {tool:?}"); - }; - assert_eq!(tool_draft.op.as_str(), "tool_call"); - assert_eq!( - tool_draft - .facts - .get(busbar_plane_streaming::meta::FACT_TOOL_NAME), - Some(FactValue::Str("lookup")), - "the tool-call sub-unit names the tool the loop invokes" - ); - let tool_args: serde_json::Value = serde_json::from_slice(tool_draft.body_ir.body()) - .expect("the tool-call sub-unit body carries the arguments the model asked for"); - assert_eq!( - tool_args["city"], "Paris", - "the arguments reach the executor, not an empty body" - ); - - // ── SETTLE (END-OF-TURN) ──────────────────────────────────────────────────────────────────── - // The turn's `response.done` carries usage; the plane decodes it as the terminal frame and the - // meter names every per-turn class with the reported quantity. This is the evidence the money-book - // settles the reserved hold against, once per completed turn. - let done = serde_json::to_vec(&json!({ - "type": "response.done", - "response": { - "usage": { - "input_token_details": { "audio_tokens": 10, "text_tokens": 3, "cached_tokens": 1 }, - "output_token_details": { "audio_tokens": 20, "text_tokens": 4 }, - } - } - })) - .expect("usage fixture serializes"); - let done_frames = [frame(&done)]; - let mut done_cursor = FrameCursor::new(&done_frames); - let settle_dest = destination("api.openai.com", LaneId::new("realtime")); - let progress = plane - .decode_response( - &mut done_cursor, - &settle_dest, - Some(&mut upstream_state), - &c, - ) - .expect("response.done decodes"); - let Progress::Terminal { r, .. } = progress else { - panic!("expected Progress::Terminal (the turn completes), got {progress:?}"); - }; - assert_eq!(r.finish, busbar_contract::unit::FinishClass::TurnComplete); - - let settled_unit = harness::unit(OpClassId::new("duplex_turn"), r.ir, Facts::new()); - let locators = plane.meter(&settled_unit, &r, &c); - let quantity = |class: &str| { - locators - .lines - .as_slice() - .iter() - .find(|l| l.class.as_str() == class) - .and_then(|l| l.quantity) - }; - // Every class the money-book settles the turn against, at the quantity the usage reports. The - // in/out split is the money-relevant part: swapping a direction reprices the turn. - assert_eq!(quantity("audio_tokens_in"), Some(10)); - assert_eq!(quantity("audio_tokens_out"), Some(20)); - assert_eq!(quantity("text_tokens_in"), Some(3)); - assert_eq!(quantity("text_tokens_out"), Some(4)); - // #71: cached_tokens is a SUBSET of audio_tokens_in/text_tokens_in, not a class beside them, so - // it never reaches the meter as its own line even though the usage report names it — pricing it - // too would double-bill the same cached input. - assert_eq!(quantity("cached_tokens"), None); - // The duration class the architecture's own inventory names for this plane is present too. - assert!( - locators - .lines - .as_slice() - .iter() - .any(|l| l.class.as_str() == "audio_seconds_in"), - "the per-turn meter names the audio-seconds class the money-book settles duration against" - ); -} - -/// The streaming plane speaks exactly the dialect roster the proven voice adapter does — the reframe -/// changes the plane's identity, never the claims the boot's overlap check reads. -#[test] -fn the_streaming_plane_declares_its_full_dialect_and_meter_roster() { - // Three WebSocket claims (openai-realtime + gemini-live + twilio-media-streams) and two one-shot - // claims (transcribe + tts). - assert_eq!( - ::CLAIMS.len(), - 5, - "streaming claims its five live dialect surfaces" - ); - // The six BILLABLE per-turn meter classes this plane declares (architect ruling #71: a plane's - // declared billable classes must be pairwise disjoint, so `cached_tokens` — a subset of the input - // token classes, not a class beside them — is carried only as an unbilled attribution fact, not a - // meter class; see `busbar_plane_streaming::meta::CLASS_CACHED_TOKENS`'s doc). - assert_eq!( - ::METER_CLASSES.len(), - 6, - "streaming meters its six billable per-turn classes" - ); - // The two duplex dialects a streaming session may DIAL, with voice among them. - let plane = streaming_plane(); - assert!(plane - .upstream_for_dialect(Dialect::OpenaiRealtime) - .is_some()); - assert!(plane.upstream_for_dialect(Dialect::GeminiLive).is_none()); -} - -/// Twilio Media Streams is a streaming DIALECT carried by the WebSocket transport, not a transport -/// of its own (the design lists five transports and no telephony one). Its -/// claim is on the WebSocket transport, one path level under `/twilio`, and authenticates under the -/// `webhook-signature` alternative alone — Twilio holds no busbar bearer or API key. Without the -/// claim the dialect's reader, codec and µ-law transform are unreachable: no arrival ever names it. -#[test] -fn twilio_media_streams_is_claimed_on_the_websocket_carrier_under_the_webhook_signature() { - use busbar_contract::grammar::Selector; - use busbar_plane_streaming::claims::{dialect_for, DIALECT_CLAIMS, WS_TRANSPORT}; - - let twilio: Vec<_> = DIALECT_CLAIMS - .iter() - .filter(|c| c.dialect == Dialect::TwilioMediaStreams) - .collect(); - assert_eq!( - twilio.len(), - 1, - "exactly one claim names the Twilio dialect" - ); - let claim = &twilio[0].claim; - assert_eq!( - claim.transport, WS_TRANSPORT, - "Twilio rides the WebSocket transport" - ); - assert_eq!(claim.selector, Selector::PrefixOneLevel("/twilio")); - assert_eq!(claim.scheme_alternatives, &["webhook-signature"]); - assert!( - ::CLAIMS.contains(claim), - "the declared claim is one the boot seal reads" - ); - // An arrival one level under `/twilio` names the dialect; the prefix alone and a deeper path do - // not, and neither does another dialect's path. - assert_eq!( - dialect_for("/twilio/inbound"), - Some(Dialect::TwilioMediaStreams) - ); - assert_eq!(dialect_for("/twilio"), None); - assert_eq!(dialect_for("/twilio/a/b"), None); - assert_eq!(dialect_for("/v1/realtime"), Some(Dialect::OpenaiRealtime)); -} diff --git a/crates/busbar-plane-streaming/tests/harness/mod.rs b/crates/busbar-plane-streaming/tests/harness/mod.rs deleted file mode 100644 index 79fe0a27b9..0000000000 --- a/crates/busbar-plane-streaming/tests/harness/mod.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! The smallest thing that can call the streaming plane. -//! -//! The same minimum-size harness `busbar-plane-llm` and every other plane crate builds for its tests: -//! an arena, a clock, a configuration view, a transport view and a label set, plus the kernel-built -//! values (a unit, a verified destination) handed through a seal. Nothing here is shipped — it is the -//! test binary's own scaffolding, the same way every plane crate carries one. - -use busbar_contract::bounded::{ - Facts, Ir, Labels, PlaneAlloc, PlaneAllocBudget, ScratchBytes, SlabBytes, Span, -}; -use busbar_contract::dest::{DestinationFacts, VerifiedDestination}; -use busbar_contract::ids::{LaneId, OpClassId, StreamId}; -use busbar_contract::unit::{Clock, ConfigView, Ctx, Origin, TransportView, Unit}; -use busbar_contract::wire::{Direction, Frame, FrameMeta}; -use std::sync::Arc; - -/// An arena that never reuses a byte — fine for a short test process. -#[derive(Debug, Default)] -pub struct LeakPlaneAlloc; - -impl PlaneAlloc for LeakPlaneAlloc { - fn alloc_bytes<'a>(&'a self, src: &[u8]) -> Result, PlaneAllocBudget> { - Ok(ScratchBytes::new(Box::leak( - src.to_vec().into_boxed_slice(), - ))) - } - - fn alloc_str<'a>(&'a self, src: &str) -> Result<&'a str, PlaneAllocBudget> { - Ok(Box::leak(src.to_string().into_boxed_str())) - } - - fn alloc_spans<'a>( - &'a self, - src: &[(&'a str, Span)], - ) -> Result<&'a [(&'a str, Span)], PlaneAllocBudget> { - Ok(Box::leak(src.to_vec().into_boxed_slice())) - } - - fn remaining(&self) -> usize { - usize::MAX - } -} - -/// A configuration block with nothing in it. -#[derive(Debug, Default)] -pub struct EmptyConfig; - -impl ConfigView for EmptyConfig { - fn get_str(&self, _key: &str) -> Option<&str> { - None - } - fn get_int(&self, _key: &str) -> Option { - None - } - fn get_bool(&self, _key: &str) -> Option { - None - } -} - -/// A WebSocket transport stack that publishes a request target as a fact — the convention every -/// plane harness uses for its own `path` fact. -#[derive(Debug)] -pub struct WsStack { - path: String, -} - -impl WsStack { - /// A stack that saw this request target. - #[must_use] - pub fn new(path: &str) -> Self { - Self { - path: path.to_string(), - } - } -} - -impl TransportView for WsStack { - fn key(&self) -> &'static str { - "ws" - } - fn chain(&self) -> &[&'static str] { - &["tcp", "tls", "http", "ws"] - } - fn fact(&self, key: &str) -> Option<&str> { - if key == "path" { - Some(&self.path) - } else { - None - } - } -} - -/// The blessed TEST seal (#65). `KernelSeal` is SEALED — no crate outside `busbar-contract` can -/// implement it — so a fixture names the contract's own `test-seal` type instead of forging one. -/// The type system stops a plugin now, not the manifest allow-list alone. -pub use busbar_contract::plugin::TestKernelSeal as TestSeal; - -/// Build a context over the pieces above. -#[must_use] -pub fn ctx<'u>( - arena: &'u LeakPlaneAlloc, - config: &'u EmptyConfig, - transport: &'u WsStack, - labels: &'u Labels<'u>, -) -> Ctx<'u> { - Ctx::new( - Clock { - unix_secs: 1_772_000_000, - monotonic_nanos: 0, - }, - config, - None, - transport, - labels, - arena, - ) -} - -/// One inbound frame carrying a whole wire event. -#[must_use] -pub fn frame(bytes: &[u8]) -> Frame { - Frame { - direction: Direction::Inbound, - stream: StreamId(0), - bytes: SlabBytes::new(Arc::from(bytes.to_vec().into_boxed_slice())), - meta: FrameMeta::default(), - } -} - -/// A unit built the way the kernel builds one, over a decoded body. -#[must_use] -pub fn unit<'u>(op: OpClassId, body: Ir<'u>, facts: Facts<'u>) -> Unit<'u> { - Unit::new( - &TestSeal, - busbar_contract::UnitKey::new(1), - Origin::Client, - None, - Some(StreamId(0)), - Direction::Inbound, - None, - op, - body, - facts, - None, - ) -} - -/// A destination sealed the way the trust unit seals one. -#[must_use] -pub fn destination(host: &'static str, lane: LaneId) -> VerifiedDestination { - VerifiedDestination::seal( - &TestSeal, - DestinationFacts::Upstream { - transport: "ws", - address: busbar_contract::UpstreamAddress::socket(host), - lane, - }, - "ws", - None, - ) -} diff --git a/crates/busbar-voice/Cargo.toml b/crates/busbar-voice/Cargo.toml index 4b3f957e2c..21e57198ec 100644 --- a/crates/busbar-voice/Cargo.toml +++ b/crates/busbar-voice/Cargo.toml @@ -112,7 +112,7 @@ http-body-util = { workspace = true, optional = true } metrics = { workspace = true, optional = true } # THE PURE HALF OF THIS PLANE, AND THE CODECS TOO NOW. Named for three things. # (1) The conformance harness and the governed-binding cells pin the tool-reply deadline -# (`busbar_plane_streaming::plane::TOOL_REPLY_DEADLINE_SECS`) against the one declaration the composition +# (`busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS`) against the one declaration the composition # root's own leg is built from, rather than restating thirty seconds in a second place where the two # could drift apart in silence. (2) The GOVERNED-CALL PORT itself # (`busbar_plane_streaming::governed::{GovernedCalls, GovernedSession, ReplyRefusal}`) now lives there, diff --git a/crates/busbar-voice/src/bin/voice-conform.rs b/crates/busbar-voice/src/bin/voice-conform.rs index 24d6098590..0f2bd0e82d 100644 --- a/crates/busbar-voice/src/bin/voice-conform.rs +++ b/crates/busbar-voice/src/bin/voice-conform.rs @@ -1369,7 +1369,7 @@ struct ProbeCall { /// of thirty seconds would let this leg keep passing after the figure it is meant to be pinning had /// moved. fn tool_reply_deadline_ms() -> u64 { - u64::from(busbar_plane_streaming::plane::TOOL_REPLY_DEADLINE_SECS) * 1_000 + u64::from(busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS) * 1_000 } impl ProbeCalls { @@ -1633,7 +1633,7 @@ fn probe_tool_reply() -> (&'static str, String) { beside the pump ends a call nobody answered at exactly the {} s its leg declares — not \ one millisecond early — so its unit exits under that deadline rather than settling as \ though the answer had arrived", - busbar_plane_streaming::plane::TOOL_REPLY_DEADLINE_SECS + busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS ), ) } diff --git a/crates/busbar-voice/src/runtime/mod.rs b/crates/busbar-voice/src/runtime/mod.rs index 4c14ee13e4..90a80f78d5 100644 --- a/crates/busbar-voice/src/runtime/mod.rs +++ b/crates/busbar-voice/src/runtime/mod.rs @@ -26,7 +26,7 @@ pub mod tools { pub use carrier::Carrier; // THE GOVERNED-CALL PORT, RE-EXPORTED FROM `busbar-plane-streaming`. `GovernedCalls` / `ReplyRefusal` / // `GovernedSession` moved to the plane crate, beside the two declarations the composition root -// already pairs them with (`busbar_plane_streaming::plane::{FACT_TOOL_CORRELATION, +// already pairs them with (`busbar_plane_streaming::open_calls::{FACT_TOOL_CORRELATION, // TOOL_REPLY_DEADLINE_SECS}`) — the port and the key a wait is entered under are now one crate, so // they cannot drift apart in silence. Re-exported HERE under the old name so every caller that // spells `busbar_voice::runtime::GovernedCalls` resolves exactly what it always did. The split is a diff --git a/crates/busbar-voice/src/tests/governed_binding_tests.rs b/crates/busbar-voice/src/tests/governed_binding_tests.rs index 3ec48a6db6..bb6c4e3390 100644 --- a/crates/busbar-voice/src/tests/governed_binding_tests.rs +++ b/crates/busbar-voice/src/tests/governed_binding_tests.rs @@ -28,7 +28,7 @@ use std::sync::{Arc, Mutex}; /// from the plane crate the composition root builds its own reply leg from — one declaration, two /// readers, so a change to it cannot leave this cell asserting the old number. fn deadline_ms() -> u64 { - u64::from(busbar_plane_streaming::plane::TOOL_REPLY_DEADLINE_SECS) * 1_000 + u64::from(busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS) * 1_000 } /// One open call, as the node's table holds it. From fef12b52c4a64832da52affc9e3ca58fb40f34e5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 03/82] busbar-plane-streaming: the plane answers each loop step through the door; proof fixes (4 carried commits) -- busbar-voice runtime tests: drop the import the retired fold test used -- fold-streaming proof fixes: the caller-ref pin test binds its HKDF key before expanding; two paths the S1c deletion left behind are struck - busbar-kernel-identity caller_ref_tests: the pseudo-random key is bound before expand, which the borrow checker requires; rustfmt layout. - qa/construction.toml kernel-seal-impls known_sites: busbar-plane-streaming/src/tests/harness.rs is deleted with the unserved adapter (S1c), and nothing else forges the seal there. - xtask no_float_money ALLOWED_COUNT_READS: the two src/twilio.rs rows go with that file. The surviving Twilio reader, codec/topology/twilio.rs, keeps its own two rows. -- busbar-plane-streaming: the plane answers each loop step through the door (ARCHITECT: restore the steps through the door, not the adapter); rustfmt The unserved adapter's Plane-trait methods were the plane's only per-step answers, so deleting them left kind-isolation:plane-steps red. driven.rs now states the plane's answer at each loop step, as plain data the door and the driver use: - authenticate: the principal a door needs. - verify: the egress target path, streams.session.model. - approve: the session grant and its pool. - admit: the arrival estimate, none (a session pays for what the far end reports). - route: the ATTEMPT request per door. The sessions dial the dialect's socket with no credential in the target; mint and SDP send their one request; the metadata document sends nothing. - meter: the session's cumulative units per class index. - audit: the action a door's unit is audited under. The door's arrive answers through authenticate and admit. Every step has a pinned test. This commit also carries rustfmt layout for the files the slice touched. -- busbar-voice mount: the metadata route names the kernel through the imports already there (ports-only back to its base count) The metadata route and its test had added three busbar_kernel:: spellings, and ports-only / ports-only-tests turned red. The route now reads PlaneReqCtx and the metadata renderer through mount.rs's existing busbar_kernel::plane_routes and ::ingress imports, and the test reaches PlaneReqCtx as super::PlaneReqCtx. Measured by grep: every busbar-voice file's busbar_kernel:: count equals the base 23d8a6b46. (cherry picked from commit c6906fb7002172438457efeecc79958c82788dda) --- .../examples/streaming_door.rs | 7 - .../src/codec/ir/codec/gemini/tests.rs | 54 ++- .../src/codec/ir/codec/mod.rs | 2 +- .../src/codec/ir/codec/tests.rs | 56 ++- .../src/codec/ir/usage.rs | 58 +--- crates/busbar-plane-streaming/src/door.rs | 68 ++-- crates/busbar-plane-streaming/src/driven.rs | 113 +++++- crates/busbar-plane-streaming/src/lib.rs | 2 +- crates/busbar-plane-streaming/src/session.rs | 1 - .../src/session_unit.rs | 3 +- .../src/tests/broker_tests.rs | 10 +- .../src/tests/driven_tests.rs | 62 +++- .../busbar-plane-streaming/src/tests/mod.rs | 1 - .../src/tests/session_pump_tests.rs | 80 +++-- .../src/tests/session_unit_tests.rs | 74 +++- crates/busbar-plane-streaming/tests/door.rs | 327 ------------------ crates/busbar-voice/src/mount.rs | 19 +- crates/busbar-voice/src/runtime/tests.rs | 40 --- crates/busbar-voice/src/tests/mount_tests.rs | 2 +- qa/construction.toml | 6 +- xtask/src/gates/no_float_money.rs | 12 - 21 files changed, 405 insertions(+), 592 deletions(-) delete mode 100644 crates/busbar-plane-streaming/examples/streaming_door.rs delete mode 100644 crates/busbar-plane-streaming/tests/door.rs diff --git a/crates/busbar-plane-streaming/examples/streaming_door.rs b/crates/busbar-plane-streaming/examples/streaming_door.rs deleted file mode 100644 index 5a7aebd65f..0000000000 --- a/crates/busbar-plane-streaming/examples/streaming_door.rs +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! The streaming plane's door, dropped in: the one exported symbol, forwarding to the same door a -//! compiled-in row holds. `tests/door.rs` loads it beside the linked door and drives both the same way. - -busbar_contract::export_door!(busbar_plane_streaming::door::door); diff --git a/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs b/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs index 2b87e58fd4..dea1f9bfc5 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/tests.rs @@ -1124,22 +1124,24 @@ fn usage_falls_back_to_stated_totals_when_the_modality_breakdown_is_absent() { 50, "the turn's stated response total must not be dropped to zero" ); - let billed = u.to_billing_usage(); - assert_eq!( + // The served meter bills the stated totals across its input and output classes, never zero. + let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); + let sum_of = |names: &[&str]| -> u64 { billed - .usage_units - .get(busbar_contract::records::UNIT_INPUT) - .copied(), - Some(95), - "a turn with no modality breakdown still bills its stated input tokens" + .iter() + .filter(|(c, _)| names.contains(&c.as_str())) + .map(|(_, n)| n) + .sum() + }; + assert_eq!( + sum_of(&["audio_tokens_in", "text_tokens_in"]), + 95, + "the input classes bill the stated total, never zero" ); assert_eq!( - billed - .usage_units - .get(busbar_contract::records::UNIT_OUTPUT) - .copied(), - Some(50), - "a turn with no modality breakdown still bills its stated output tokens" + sum_of(&["audio_tokens_out", "text_tokens_out"]), + 50, + "the output classes bill the stated total, never zero" ); } @@ -1171,27 +1173,19 @@ fn cached_content_tokens_are_not_billed_twice() { // Extraction stays wire-faithful. assert_eq!(u.audio_in, 1000); assert_eq!(u.cached, 800); - let billed = u.to_billing_usage(); - assert_eq!( - billed - .usage_units - .get(busbar_contract::records::UNIT_INPUT) - .copied(), - Some(200), - "input bills the UNCACHED remainder of the prompt (1000 - 800)" - ); - assert_eq!( + // The served meter bills the input as reported: the cached subset is part of it, billed once + // there, and no class bills it again (cached tokens are attribution only). + let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); + assert!( billed - .usage_units - .get(busbar_contract::records::UNIT_CACHE_READ) - .copied(), - Some(800), - "the cached subset bills once, on the cache-read lane" + .iter() + .all(|(c, _)| c.as_str() != crate::meta::CLASS_CACHED_TOKENS.as_str()), + "the cached subset is never a class of its own: {billed:?}" ); assert_eq!( - billed.usage_units.values().sum::(), + billed.iter().map(|(_, n)| n).sum::(), 1000, - "the billed lanes sum to the turn's prompt, never to 1800" + "the billed classes sum to the turn's input, never to 1800" ); } diff --git a/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs b/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs index 5dc5004eb1..e920dac87d 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/codec/mod.rs @@ -1057,7 +1057,7 @@ impl DuplexWriter for OpenAiRealtimeCodec { /// Re-frame the extracted token classes back onto a `usage` object (the inverse of [`extract_usage`]). /// -/// The sums SATURATE, matching `IrDuplexUsage::to_billing_usage`'s stated discipline ("a runaway +/// The sums SATURATE, matching the crate's stated discipline for its counts ("a runaway /// turn pins the count, never wraps small"): these counts came off an upstream `usage` object, and /// a wrapped total is a small, believable number that is false. fn usage_to_wire(u: &IrDuplexUsage) -> Value { diff --git a/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs b/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs index 0425e25a32..9eaaef50da 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/codec/tests.rs @@ -622,7 +622,7 @@ fn an_out_of_range_content_index_falls_back_to_the_documented_default() { /// TOKEN SUMS SATURATE, THEY DO NOT WRAP. /// /// The four counts come from an upstream `usage` object — untrusted bytes. The crate already states -/// this discipline in `IrDuplexUsage::to_billing_usage` ("a runaway turn pins the count, never +/// this discipline for its counts ("a runaway turn pins the count, never /// wraps small"); the client-facing re-frame must not answer differently, because a wrapped total /// is a small, believable number that is simply false. #[test] @@ -1012,27 +1012,19 @@ fn cached_input_tokens_are_not_billed_twice() { assert_eq!(u.audio_in, 1000); assert_eq!(u.cached, 800); // The BILLING fold is where the subset is netted out. - let billed = u.to_billing_usage(); - assert_eq!( - billed - .usage_units - .get(busbar_contract::records::UNIT_INPUT) - .copied(), - Some(200), - "input bills the UNCACHED remainder (1000 - 800), not the full input figure" - ); - assert_eq!( + // The served meter bills the input as reported: the cached subset is part of it, billed once + // there, and no class bills it again (cached tokens are attribution only). + let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); + assert!( billed - .usage_units - .get(busbar_contract::records::UNIT_CACHE_READ) - .copied(), - Some(800), - "the cached subset bills once, on the cache-read lane" + .iter() + .all(|(c, _)| c.as_str() != crate::meta::CLASS_CACHED_TOKENS.as_str()), + "the cached subset is never a class of its own: {billed:?}" ); assert_eq!( - billed.usage_units.values().sum::(), + billed.iter().map(|(_, n)| n).sum::(), 1000, - "the billed lanes sum to the turn's input, never to 1800" + "the billed classes sum to the turn's input, never to 1800" ); } @@ -1117,22 +1109,24 @@ fn usage_falls_back_to_stated_totals_when_the_modality_breakdown_is_absent() { ); // The lane the billing fold sums onto is what actually decides the invoice. - let billed = u.to_billing_usage(); - assert_eq!( + // The served meter bills the stated totals across its input and output classes, never zero. + let billed = crate::session::class_counts(Some(u), crate::session::TurnCounters::default()); + let sum_of = |names: &[&str]| -> u64 { billed - .usage_units - .get(busbar_contract::records::UNIT_INPUT) - .copied(), - Some(stated_in), - "the input lane bills the stated total, never zero" + .iter() + .filter(|(c, _)| names.contains(&c.as_str())) + .map(|(_, n)| n) + .sum() + }; + assert_eq!( + sum_of(&["audio_tokens_in", "text_tokens_in"]), + stated_in, + "the input classes bill the stated total, never zero" ); assert_eq!( - billed - .usage_units - .get(busbar_contract::records::UNIT_OUTPUT) - .copied(), - Some(stated_out), - "the output lane bills the stated total, never zero" + sum_of(&["audio_tokens_out", "text_tokens_out"]), + stated_out, + "the output classes bill the stated total, never zero" ); } diff --git a/crates/busbar-plane-streaming/src/codec/ir/usage.rs b/crates/busbar-plane-streaming/src/codec/ir/usage.rs index c263d05851..d374cd3b0e 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/usage.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/usage.rs @@ -12,9 +12,9 @@ /// `CostBreakdown` whose top-level components sum to `total` (the one invariant core enforces), with /// audio/text as labeled opaque components core never interprets. /// -/// A plain token-class tally, folded (5→4 reserved keys, see [`Self::to_billing_usage`]) onto the -/// neutral [`busbar_contract::billing::Usage`] the kernel prices at read -/// (`runtime::metering::MeteringPort::price_usage`); the plane never prices it. +/// A plain token-class tally. The plane meters it through its own declared classes +/// (`crate::session::class_counts`); `cached` is attribution only, a subset of the input it is part +/// of, and never billed on its own. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct IrDuplexUsage { /// Audio input tokens consumed this turn. @@ -29,58 +29,6 @@ pub struct IrDuplexUsage { pub cached: u64, } -impl IrDuplexUsage { - /// FOLD this turn's five token classes onto the neutral [`busbar_contract::billing::Usage`] the - /// host prices — the 5→4 reserved-key map the plane hands - /// `MeteringHost::price_usage` (the host's pricer). Audio and - /// text collapse onto the SAME reserved lane by direction (a session prices audio vs text as separate - /// rate-card MODEL lanes, never separate unit keys), so NO new unit/label/constant is introduced — - /// only the four EXISTING reserved keys ([`busbar_contract::records::UNIT_INPUT`]/`UNIT_OUTPUT`/`UNIT_CACHE_READ`): - /// - /// - `(audio_in + text_in) - cached` → `input` - /// - `audio_out + text_out` → `output` - /// - `cached` → `cache_read` - /// - /// `cached` is NETTED OUT of the input lane because in BOTH dialects the cached figure is a SUBSET - /// of the input figure, not a class beside it — OpenAI Realtime reports `cached_tokens` INSIDE - /// `input_token_details` alongside `audio_tokens`/`text_tokens`, and Gemini's - /// `cachedContentTokenCount` is part of `promptTokenCount` (which is what `promptTokensDetails` - /// breaks out by modality). Billing the full input figure AND the cache figure would charge the - /// cached tokens on two lanes at once. This is the same normalization the LLM plane's reader makes - /// (`prompt_tokens` minus `cached_tokens` is what lands on the input lane), so a cached turn prices - /// identically whichever plane carried it. - /// - /// The IR carrier itself stays wire-faithful (extraction-only, raw provider figures, so the - /// re-frame writers round-trip); the netting happens HERE, at the billing fold, and nowhere else. - /// - /// Only non-zero classes are keyed (the pricer reads absent keys as zero, so this is purely tidy — - /// no zero component ever prices). Saturating sums: a runaway turn pins the count, never wraps small. - /// The subtraction saturates too: a provider that over-reports `cached` floors the input lane at - /// zero rather than wrapping to a colossal charge. - #[must_use] - pub fn to_billing_usage(&self) -> busbar_contract::billing::Usage { - let mut usage_units = std::collections::BTreeMap::new(); - let input = self - .audio_in - .saturating_add(self.text_in) - .saturating_sub(self.cached); - let output = self.audio_out.saturating_add(self.text_out); - if input != 0 { - usage_units.insert(busbar_contract::records::UNIT_INPUT.to_string(), input); - } - if output != 0 { - usage_units.insert(busbar_contract::records::UNIT_OUTPUT.to_string(), output); - } - if self.cached != 0 { - usage_units.insert( - busbar_contract::records::UNIT_CACHE_READ.to_string(), - self.cached, - ); - } - busbar_contract::billing::Usage { usage_units } - } -} - /// THE ONE SEAM every BILLED count read in this crate goes through. /// /// # Why this exists here, beside the carrier it fills diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 5b26d49cc3..ac286fd367 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -35,11 +35,11 @@ use busbar_contract::abi::mechanism::lifecycle::{ CancelIn, CancelOut, GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, }; use busbar_contract::abi::plane::{ - ArriveIn, ArriveOut, BillableClass, Claim, DialectAuth, OpClass, OnPieceIn, OnPieceOut, PlaneDriveIn, - PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, - ProjectOut, RefusalIn, RefusalOut, Section, ServeIn, ServeOut, CANCEL_FAILED, CLAIM_EXACT, - INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, PRINCIPAL_NONE, PRINCIPAL_REQUIRED, SECTION_DECLARING, - SECTION_REQUIRED, SHAPE_PIECEWISE, CLAIM_OPEN, + ArriveIn, ArriveOut, BillableClass, Claim, DialectAuth, OnPieceIn, OnPieceOut, OpClass, + PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, + PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, Section, ServeIn, ServeOut, UnitCount, + CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, + SECTION_DECLARING, SECTION_REQUIRED, SHAPE_PIECEWISE, UNITS_ESTIMATED, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; use busbar_contract::abi::sdk::{Instance, Lent, Published, Safe, SafeSlot}; @@ -172,9 +172,24 @@ const fn need(direction: u32, transport: &'static str, auth: AbiStr, target: Abi const NEEDS: &[Need] = &[ need(DIRECTION_INBOUND, WS_TRANSPORT, abi_str(KEY_AUTH), NONE), need(DIRECTION_INBOUND, HTTP_TRANSPORT, abi_str(KEY_AUTH), NONE), - need(DIRECTION_INBOUND, WS_TRANSPORT, abi_str(SIGNATURE_AUTH), NONE), - need(DIRECTION_OUTBOUND, WS_TRANSPORT, NONE, abi_str(EGRESS_TARGET)), - need(DIRECTION_OUTBOUND, HTTP_TRANSPORT, NONE, abi_str(EGRESS_TARGET)), + need( + DIRECTION_INBOUND, + WS_TRANSPORT, + abi_str(SIGNATURE_AUTH), + NONE, + ), + need( + DIRECTION_OUTBOUND, + WS_TRANSPORT, + NONE, + abi_str(EGRESS_TARGET), + ), + need( + DIRECTION_OUTBOUND, + HTTP_TRANSPORT, + NONE, + abi_str(EGRESS_TARGET), + ), ]; /// THE STATEMENT TAIL. @@ -339,9 +354,8 @@ impl Generation { /// The generation `generation` of a plane whose deployment states `public_url`. #[must_use] pub fn build(generation: u64, public_url: Option<&str>) -> Box { - let admitted = public_url.and_then(|p| { - Some((absolute(p, MOUNT_PATH)?, absolute(p, METADATA_PATH)?)) - }); + let admitted = + public_url.and_then(|p| Some((absolute(p, MOUNT_PATH)?, absolute(p, METADATA_PATH)?))); let (audience, resource_metadata, claims) = match admitted { Some((a, m)) => (a, m, CLAIMS), None => (String::new(), String::new(), &[][..]), @@ -526,23 +540,35 @@ slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { let Some(a) = crate::driven::arrive(input.claim) else { return Outcome::Refused; }; + let mut units = input.units_buf(); + for (class, amount) in crate::driven::admit(a.door) { + units.push(UnitCount { + class, + source: UNITS_ESTIMATED, + amount, + }); + } + if !units.fits() { + out.units_needed = units.needed() as u32; + return Outcome::Failed; + } + out.units_written = units.written() as u32; out.op_class = a.op_class; out.dialect = a.dialect; - out.principal_need = if a.door.is_open() { - PRINCIPAL_NONE - } else { - PRINCIPAL_REQUIRED - }; - out.units_written = 0; + out.principal_need = crate::driven::authenticate(a.door); Outcome::Ready }); // The door serves no unit's pieces yet: every piece, refusal render, admin serve and projection is // declined, and a declined unit is charged nothing. -slot!(OnPiece, OnPieceIn, OnPieceOut, |_, _, _| { Outcome::Refused }); +slot!(OnPiece, OnPieceIn, OnPieceOut, |_, _, _| { + Outcome::Refused +}); -slot!(Refusal, RefusalIn, RefusalOut, |_, _, _| { Outcome::Refused }); +slot!(Refusal, RefusalIn, RefusalOut, |_, _, _| { + Outcome::Refused +}); slot!(Serve, ServeIn, ServeOut, |_, _, _| { Outcome::Refused }); @@ -550,7 +576,9 @@ slot!(Hydrate, GenIn, OutHead, |_, _, _| { Outcome::Ready }); slot!(Start, GenIn, OutHead, |_, _, _| { Outcome::Ready }); -slot!(Project, ProjectIn, ProjectOut, |_, _, _| { Outcome::Refused }); +slot!(Project, ProjectIn, ProjectOut, |_, _, _| { + Outcome::Refused +}); busbar_contract::plugin_door! { ops: busbar_contract::abi::plane::Ops, diff --git a/crates/busbar-plane-streaming/src/driven.rs b/crates/busbar-plane-streaming/src/driven.rs index 581ec6242d..46e25e6236 100644 --- a/crates/busbar-plane-streaming/src/driven.rs +++ b/crates/busbar-plane-streaming/src/driven.rs @@ -107,6 +107,108 @@ pub const fn arrive(claim: u32) -> Option { } } +// ── the plane's answer at each loop step (THE DESIGN §1's order) ───────────────────────────────── + +/// AUTHENTICATE: whom a unit on `door` needs. Every keyed door needs a principal; the metadata +/// document answers anyone. +#[must_use] +pub const fn authenticate(door: Door) -> u32 { + if door.is_open() { + busbar_contract::abi::plane::PRINCIPAL_NONE + } else { + busbar_contract::abi::plane::PRINCIPAL_REQUIRED + } +} + +/// VERIFY: where, inside the plane's section, the destination a session dials is named. The kernel +/// resolves it through the root models and judges it. +#[must_use] +pub const fn verify() -> &'static str { + crate::door::EGRESS_TARGET +} + +/// APPROVE: the grant a key must hold to open a session here: the `session` kind, for the one pool +/// every session is served on. +#[must_use] +pub const fn approve() -> (&'static str, &'static str) { + (crate::door::SCOPE, crate::door::SESSION_POOL) +} + +/// ADMIT: the units a unit on `door` is expected to cost before the far end reports any. None: a +/// session is admitted at open and pays for what the far end reports (and its per-session fee, +/// which the kernel counts); the one-request doors report nothing. +#[must_use] +pub fn admit(door: Door) -> Vec<(u32, u64)> { + match door { + Door::Mint | Door::Sdp | Door::Sideband | Door::Gemini | Door::Twilio | Door::Metadata => { + Vec::new() + } + } +} + +/// ROUTE: the request an attempt on `door` sends the far end. The session doors dial the dialect's +/// realtime socket (the credential is the kernel's, never in the target); the mint and SDP doors send +/// their one request; the metadata document is answered here and sends nothing. +/// +/// # Errors +/// The mint body's serializer text. +pub fn route( + door: Door, + config: &SessionConfig, + caller_ref: Option<&str>, + body: &[u8], +) -> Result, String> { + Ok(match door { + Door::Mint => Some(mint_attempt(config, caller_ref, None)?), + Door::Sdp => Some(sdp_attempt(body)), + Door::Sideband | Door::Twilio => Some(Attempt { + verb: "GET", + target: REALTIME_SOCKET_PATH, + fields: Vec::new(), + body: Vec::new(), + }), + Door::Gemini => Some(Attempt { + verb: "GET", + target: GEMINI_SOCKET_PATH, + fields: Vec::new(), + body: Vec::new(), + }), + Door::Metadata => None, + }) +} + +/// METER: a session's units so far, per billable class index, the zero classes left out. Counts +/// only; the kernel prices nothing the plane names. +#[must_use] +pub fn meter(units: &crate::session_unit::CumulativeUnits) -> Vec<(u32, u64)> { + units + .0 + .iter() + .enumerate() + .filter(|(_, n)| **n != 0) + .map(|(i, n)| (i as u32, *n)) + .collect() +} + +/// AUDIT: the action a unit on `door` is audited under. +#[must_use] +pub const fn audit(door: Door) -> &'static str { + match door { + Door::Metadata => "streaming.metadata.read", + _ => SESSION_OPEN_ACTION, + } +} + +/// The action a session open is audited under. +pub const SESSION_OPEN_ACTION: &str = "streaming.session.open"; + +/// The realtime socket an OpenAI Realtime session dials, under the member's base URL. +pub const REALTIME_SOCKET_PATH: &str = "/v1/realtime"; + +/// The socket a Gemini Live session dials, under the member's base URL. +pub const GEMINI_SOCKET_PATH: &str = + "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent"; + /// A request bound for the far end: verb and target explicit, then its head fields and body. The /// kernel adds the credential and sends it; the target is under the member's base URL. #[derive(Debug, Clone, PartialEq, Eq)] @@ -174,8 +276,10 @@ fn mint_failed(reason: &str) -> Reply { Reply { status: MINT_FAILED_STATUS, fields: Vec::new(), - body: format!("streaming ephemeral-secret mint failed: ephemeral token mint failed: {reason}") - .into_bytes(), + body: format!( + "streaming ephemeral-secret mint failed: ephemeral token mint failed: {reason}" + ) + .into_bytes(), rtc_call_id: None, } } @@ -308,7 +412,10 @@ pub fn metadata_reply(audience: &str) -> Reply { status: 200, fields: vec![ ("cache-control", "public, max-age=3600".to_string()), - (FIELD_CONTENT_TYPE, "application/json; charset=utf-8".to_string()), + ( + FIELD_CONTENT_TYPE, + "application/json; charset=utf-8".to_string(), + ), ], body: serde_json::to_vec(&serde_json::json!({ "resource": audience, diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index b388f2c5ad..88d637d95f 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -63,8 +63,8 @@ pub mod register; pub mod session; pub mod session_params; pub mod session_pump; -pub mod session_unit; pub mod session_row; +pub mod session_unit; pub mod tools; #[cfg(test)] diff --git a/crates/busbar-plane-streaming/src/session.rs b/crates/busbar-plane-streaming/src/session.rs index 84b41e5cae..7038e66a11 100644 --- a/crates/busbar-plane-streaming/src/session.rs +++ b/crates/busbar-plane-streaming/src/session.rs @@ -110,4 +110,3 @@ pub fn class_counts( .filter(|(_, n)| *n != 0) .collect() } - diff --git a/crates/busbar-plane-streaming/src/session_unit.rs b/crates/busbar-plane-streaming/src/session_unit.rs index 873d7a6151..9652b08cbc 100644 --- a/crates/busbar-plane-streaming/src/session_unit.rs +++ b/crates/busbar-plane-streaming/src/session_unit.rs @@ -102,7 +102,8 @@ impl TwilioBridge { stream_sid, payload, }) => { - if stream_sid.is_empty() || self.stream_sid.as_deref() != Some(stream_sid.as_str()) { + if stream_sid.is_empty() || self.stream_sid.as_deref() != Some(stream_sid.as_str()) + { return CallerStep::Nothing; } let append = serde_json::json!({ diff --git a/crates/busbar-plane-streaming/src/tests/broker_tests.rs b/crates/busbar-plane-streaming/src/tests/broker_tests.rs index 09d9a3cf7e..ee54e49120 100644 --- a/crates/busbar-plane-streaming/src/tests/broker_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/broker_tests.rs @@ -53,7 +53,10 @@ fn a_mint_answer_that_is_not_an_ephemeral_secret_is_refused() { Err("client-secret response value is not an ek_ ephemeral secret".to_string()) ); let err = read_minted(b"not json").expect_err("refused"); - assert!(err.starts_with("client-secret response did not parse: "), "{err}"); + assert!( + err.starts_with("client-secret response did not parse: "), + "{err}" + ); } #[test] @@ -63,7 +66,10 @@ fn the_browser_is_answered_the_secret_and_its_expiry_only() { expires_at_unix: 5, }); let read: serde_json::Value = serde_json::from_slice(&body).expect("json"); - assert_eq!(read, serde_json::json!({"value": "ek_1", "expires_at_unix": 5})); + assert_eq!( + read, + serde_json::json!({"value": "ek_1", "expires_at_unix": 5}) + ); } #[test] diff --git a/crates/busbar-plane-streaming/src/tests/driven_tests.rs b/crates/busbar-plane-streaming/src/tests/driven_tests.rs index eaf43eef4a..d86acf3d7a 100644 --- a/crates/busbar-plane-streaming/src/tests/driven_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/driven_tests.rs @@ -21,12 +21,12 @@ fn every_published_claim_arrives_at_its_door_in_its_dialect() { Some((Door::Metadata, 0, 0)), ] ); - assert_eq!(arrive(6), None, "a claim the plane never published arrives nowhere"); assert_eq!( - crate::door::ROUTES.len(), - 6, - "one door per published claim" + arrive(6), + None, + "a claim the plane never published arrives nowhere" ); + assert_eq!(crate::door::ROUTES.len(), 6, "one door per published claim"); } #[test] @@ -48,7 +48,9 @@ fn the_mint_attempt_names_the_caller_and_carries_the_locked_session() { assert_eq!(body["expires_after"]["seconds"], 600); assert_eq!(body["session"]["instructions"], "locked"); assert!( - a.fields.iter().all(|(n, _)| !n.eq_ignore_ascii_case("authorization")), + a.fields + .iter() + .all(|(n, _)| !n.eq_ignore_ascii_case("authorization")), "the plane never writes the credential; the kernel adds it" ); let anonymous = mint_attempt(&cfg, None, None).expect("serializes"); @@ -64,7 +66,10 @@ fn a_minted_secret_is_answered_and_a_failure_keeps_the_served_text() { let ok = mint_reply(200, br#"{"value":"ek_1","expires_at":9}"#); assert_eq!(ok.status, 200); let v: serde_json::Value = serde_json::from_slice(&ok.body).expect("json"); - assert_eq!(v, serde_json::json!({"value": "ek_1", "expires_at_unix": 9})); + assert_eq!( + v, + serde_json::json!({"value": "ek_1", "expires_at_unix": 9}) + ); let refused = mint_reply(400, b"{}"); assert_eq!(refused.status, 502); @@ -75,14 +80,19 @@ fn a_minted_secret_is_answered_and_a_failure_keeps_the_served_text() { ); let not_ek = mint_reply(200, br#"{"value":"sk-real"}"#); assert_eq!(not_ek.status, 502); - assert!(!String::from_utf8(not_ek.body).expect("utf-8").contains("sk-real")); + assert!(!String::from_utf8(not_ek.body) + .expect("utf-8") + .contains("sk-real")); } #[test] fn the_sdp_offer_is_relayed_and_its_answer_names_the_call() { let a = sdp_attempt(b"v=0\r\n"); assert_eq!((a.verb, a.target), ("POST", "/v1/realtime/calls")); - assert_eq!(a.fields, vec![("content-type", "application/sdp".to_string())]); + assert_eq!( + a.fields, + vec![("content-type", "application/sdp".to_string())] + ); assert_eq!(a.body, b"v=0\r\n"); let r = sdp_reply(201, Some("/v1/realtime/calls/rtc_abc"), b"v=0 answer"); @@ -112,3 +122,39 @@ fn the_metadata_document_names_the_audience() { assert_eq!(v["resource"], "https://gw.example.com/v1/realtime"); assert_eq!(v["bearer_methods_supported"], serde_json::json!(["header"])); } + +#[test] +fn each_loop_step_has_the_planes_answer() { + use busbar_contract::abi::plane::{PRINCIPAL_NONE, PRINCIPAL_REQUIRED}; + assert_eq!(authenticate(Door::Mint), PRINCIPAL_REQUIRED); + assert_eq!(authenticate(Door::Twilio), PRINCIPAL_REQUIRED); + assert_eq!(authenticate(Door::Metadata), PRINCIPAL_NONE); + assert_eq!(verify(), "session.model"); + assert_eq!(approve(), ("session", "streaming-server")); + assert!( + admit(Door::Sideband).is_empty(), + "a session pays for what the far end reports" + ); + let cfg = SessionConfig::default(); + let sock = route(Door::Gemini, &cfg, None, b"") + .expect("routes") + .expect("a session dials"); + assert_eq!(sock.verb, "GET"); + assert!( + !sock.target.contains("key="), + "the credential is never in the target" + ); + assert_eq!( + route(Door::Sdp, &cfg, None, b"v=0").expect("routes"), + Some(sdp_attempt(b"v=0")) + ); + assert_eq!( + route(Door::Metadata, &cfg, None, b"").expect("routes"), + None + ); + let mut units = crate::session_unit::CumulativeUnits::default(); + units.0[1] = 20; + units.0[4] = 2; + assert_eq!(meter(&units), vec![(1, 20), (4, 2)]); + assert_eq!(audit(Door::Mint), "streaming.session.open"); +} diff --git a/crates/busbar-plane-streaming/src/tests/mod.rs b/crates/busbar-plane-streaming/src/tests/mod.rs index 60e1b4c5d8..f246663ff3 100644 --- a/crates/busbar-plane-streaming/src/tests/mod.rs +++ b/crates/busbar-plane-streaming/src/tests/mod.rs @@ -2,7 +2,6 @@ //! and style checks the crate doc comments promise elsewhere (`lib.rs`'s [`crate::StreamingPlane`] doc //! comment cites [`purity`] by name). - /// What a request path matches, decided the same way the boot's overlap check decides it. mod selectors { use crate::claims::matches_selector; diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index 6f85ba3bca..5062d3b7e6 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -61,8 +61,10 @@ fn call_frames(id: &str, name: &str, args: &str) -> [WireEvent; 3] { [ wire(serde_json::json!({"type":"response.output_item.added", "item":{"type":"function_call","call_id":id,"name":name}})), - wire(serde_json::json!({"type":"response.function_call_arguments.delta", - "call_id":id,"delta":args})), + wire( + serde_json::json!({"type":"response.function_call_arguments.delta", + "call_id":id,"delta":args}), + ), wire(serde_json::json!({"type":"response.function_call_arguments.done","call_id":id})), ] } @@ -82,7 +84,10 @@ fn a_usage_report_closes_the_turn_with_its_counters_and_the_session_continues() let (usage, counters) = sink.closed[0]; let usage = usage.expect("the turn carries its usage"); assert_eq!((usage.audio_in, usage.audio_out), (10, 20)); - assert_eq!(counters.audio_ms_in, 1000, "48 000 bytes of pcm16 is one second"); + assert_eq!( + counters.audio_ms_in, 1000, + "48 000 bytes of pcm16 is one second" + ); } #[test] @@ -103,15 +108,19 @@ fn a_barge_in_cancels_and_truncates_at_what_was_heard() { let mut sink = Turns::default(); let b64 = busbar_contract::media::base64_encode(&[0u8; 96]); let _ = p.on_server_frame( - wire(serde_json::json!({"type":"response.output_audio.delta","delta":b64, - "item_id":"it7","output_index":0,"content_index":0})), + wire( + serde_json::json!({"type":"response.output_audio.delta","delta":b64, + "item_id":"it7","output_index":0,"content_index":0}), + ), 0, &mut sink, &serves_all, ); let (out, _) = p.on_server_frame( - wire(serde_json::json!({"type":"input_audio_buffer.speech_started", - "audio_start_ms":0,"item_id":"it7"})), + wire( + serde_json::json!({"type":"input_audio_buffer.speech_started", + "audio_start_ms":0,"item_id":"it7"}), + ), 0, &mut sink, &serves_all, @@ -145,7 +154,10 @@ fn a_tool_call_is_run_once_on_its_close_with_its_accumulated_arguments() { assert!(up.contains("response.create")); p.settle_open_turn(&mut sink); assert_eq!(sink.closed.len(), 1); - assert_eq!(sink.closed[0].1.tool_calls, 1, "the opened call is counted once"); + assert_eq!( + sink.closed[0].1.tool_calls, 1, + "the opened call is counted once" + ); } /// A table that records what the pump asked of it. @@ -194,15 +206,20 @@ fn a_call_the_session_does_not_serve_waits_for_the_callers_reply() { let mut sink = Turns::default(); for f in call_frames("cc", "lookup", "{}") { let (_, runs) = p.on_server_frame(f, 42, &mut sink, &serves_none); - assert!(runs.is_empty(), "the gateway never answers a call it does not serve"); + assert!( + runs.is_empty(), + "the gateway never answers a call it does not serve" + ); } assert_eq!( *table.planned.lock().expect("lock"), vec![(7, "cc".to_string(), 42)] ); let reply = |id: &str| { - wire(serde_json::json!({"type":"conversation.item.create","item":{ - "type":"function_call_output","call_id":id,"output":"{}"}})) + wire( + serde_json::json!({"type":"conversation.item.create","item":{ + "type":"function_call_output","call_id":id,"output":"{}"}}), + ) }; let forged = p.on_client_frame(reply("nope")); assert!(forged.refused_reply && forged.upstream.is_empty()); @@ -232,9 +249,14 @@ fn a_callers_session_update_is_replaced_by_the_locked_config() { #[test] fn a_session_past_its_ceiling_is_told_why_in_its_dialect() { let mut p = pump(); - let told = p.ceiling_error(1800).expect("the dialect carries an error frame"); + let told = p + .ceiling_error(1800) + .expect("the dialect carries an error frame"); let text = String::from_utf8_lossy(&told.0).into_owned(); - assert!(text.contains(SESSION_CEILING_REASON) && text.contains("1800 s"), "{text}"); + assert!( + text.contains(SESSION_CEILING_REASON) && text.contains("1800 s"), + "{text}" + ); } #[test] @@ -307,7 +329,10 @@ fn a_call_no_one_on_this_node_serves_is_relayed_to_the_caller_and_never_answered let (mut down, mut up) = (Vec::new(), Vec::new()); for f in call_frames("cu", "unknown_tool", "{\"q\":1}") { let (out, runs) = p.on_server_frame(f, 0, &mut sink, &serves_none); - assert!(runs.is_empty(), "the gateway runs nothing for a tool it does not serve"); + assert!( + runs.is_empty(), + "the gateway runs nothing for a tool it does not serve" + ); down.extend(out.downlink); up.extend(out.upstream); } @@ -336,7 +361,10 @@ fn two_open_tool_calls_wait_on_two_different_correlations() { calls: table.clone(), }); let mut sink = Turns::default(); - for f in call_frames("cc", "a", "{}").into_iter().chain(call_frames("dd", "b", "{}")) { + for f in call_frames("cc", "a", "{}") + .into_iter() + .chain(call_frames("dd", "b", "{}")) + { let _ = p.on_server_frame(f, 0, &mut sink, &serves_none); } let planned: Vec = table @@ -348,11 +376,16 @@ fn two_open_tool_calls_wait_on_two_different_correlations() { .collect(); assert_eq!(planned, ["cc", "dd"], "each call is its own wait"); let reply = |id: &str| { - wire(serde_json::json!({"type":"conversation.item.create","item":{ - "type":"function_call_output","call_id":id,"output":"{}"}})) + wire( + serde_json::json!({"type":"conversation.item.create","item":{ + "type":"function_call_output","call_id":id,"output":"{}"}}), + ) }; assert!(!p.on_client_frame(reply("dd")).refused_reply); - assert!(p.on_client_frame(reply("dd")).refused_reply, "a wait is woken once"); + assert!( + p.on_client_frame(reply("dd")).refused_reply, + "a wait is woken once" + ); assert!(!p.on_client_frame(reply("cc")).refused_reply); } @@ -390,13 +423,18 @@ fn a_barge_in_bills_what_the_interrupted_turn_served_on_the_turn_that_takes_over let [open, ..] = call_frames("b1", "t", "{}"); let _ = p.on_server_frame(open, 0, &mut sink, &serves_none); let _ = p.on_server_frame( - wire(serde_json::json!({"type":"input_audio_buffer.speech_started", - "audio_start_ms":0,"item_id":"it"})), + wire( + serde_json::json!({"type":"input_audio_buffer.speech_started", + "audio_start_ms":0,"item_id":"it"}), + ), 0, &mut sink, &serves_none, ); - assert!(sink.closed.is_empty(), "a barge-in closes no turn and drops no counter"); + assert!( + sink.closed.is_empty(), + "a barge-in closes no turn and drops no counter" + ); let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_none); assert_eq!(sink.closed.len(), 1); assert_eq!( diff --git a/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs b/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs index 07ba748ae8..ebeaa541e7 100644 --- a/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs @@ -20,8 +20,10 @@ fn text(frames: &[Vec]) -> String { } fn start(encoding: &str) -> Vec { - json(serde_json::json!({"event":"start","start":{"streamSid":"MZ1","callSid":"CA1", - "mediaFormat":{"encoding":encoding,"sampleRate":8000,"channels":1}}})) + json( + serde_json::json!({"event":"start","start":{"streamSid":"MZ1","callSid":"CA1", + "mediaFormat":{"encoding":encoding,"sampleRate":8000,"channels":1}}}), + ) } fn media(sid: &str, bytes: &[u8]) -> Vec { @@ -77,9 +79,14 @@ fn model_audio_and_a_barge_in_reach_the_caller_in_the_carriers_envelope() { "item_id":"it1","output_index":0,"content_index":0})); let plan = s.from_far_end(&delta, 0); let down = text(&plan.to_caller); - assert!(down.contains("\"event\":\"media\"") && down.contains("MZ1"), "{down}"); - let barge = json(serde_json::json!({"type":"input_audio_buffer.speech_started", - "audio_start_ms":0,"item_id":"it1"})); + assert!( + down.contains("\"event\":\"media\"") && down.contains("MZ1"), + "{down}" + ); + let barge = json( + serde_json::json!({"type":"input_audio_buffer.speech_started", + "audio_start_ms":0,"item_id":"it1"}), + ); let plan = s.from_far_end(&barge, 0); assert!(text(&plan.to_caller).contains("\"event\":\"clear\"")); assert!(text(&plan.to_far_end).contains("response.cancel")); @@ -92,7 +99,10 @@ fn a_stop_settles_the_open_turns_counters_once() { // Two seconds of 8 kHz mu-law: 8 bytes a millisecond. let _ = s.from_caller(&media("MZ1", &[0; 16_000])); assert_eq!(s.units(), CumulativeUnits::default(), "no turn closed yet"); - assert!(s.from_caller(&json(serde_json::json!({"event":"stop"}))).end); + assert!( + s.from_caller(&json(serde_json::json!({"event":"stop"}))) + .end + ); let seconds = CLASSES .iter() .position(|c| *c == meta::CLASS_AUDIO_SECONDS_IN) @@ -104,13 +114,25 @@ fn a_stop_settles_the_open_turns_counters_once() { #[test] fn usage_reports_accumulate_across_turns() { - let mut s = SessionUnit::open(OpenAiRealtimeCodec, SessionConfig::default(), false, 0, None); - let done = json(serde_json::json!({"type":"response.done","response":{"usage":{ + let mut s = SessionUnit::open( + OpenAiRealtimeCodec, + SessionConfig::default(), + false, + 0, + None, + ); + let done = json( + serde_json::json!({"type":"response.done","response":{"usage":{ "input_token_details":{"audio_tokens":10,"text_tokens":3,"cached_tokens":2}, - "output_token_details":{"audio_tokens":20,"text_tokens":4}}}})); + "output_token_details":{"audio_tokens":20,"text_tokens":4}}}}), + ); let _ = s.from_far_end(&done, 0); let _ = s.from_far_end(&done, 0); - assert_eq!(s.units().0, [20, 40, 6, 8, 0, 0], "cached tokens are attribution, never billed"); + assert_eq!( + s.units().0, + [20, 40, 6, 8, 0, 0], + "cached tokens are attribution, never billed" + ); } #[test] @@ -143,7 +165,12 @@ fn the_sessions_record_is_written_at_open_on_its_call_id_and_at_its_end() { let opened = s.take_writes(); assert_eq!(opened.len(), 1); assert_eq!( - (opened[0].id.as_str(), opened[0].owner.as_str(), opened[0].updated_at, opened[0].terminal), + ( + opened[0].id.as_str(), + opened[0].owner.as_str(), + opened[0].updated_at, + opened[0].terminal + ), ("call-1", "ref-abc", 5, false) ); s.set_rtc_call_id("rtc_9", 6); @@ -172,13 +199,22 @@ fn a_stop_settles_the_open_turns_tool_calls_too() { let open = json(serde_json::json!({"type":"response.output_item.added", "item":{"type":"function_call","call_id":"c1","name":"lookup"}})); let _ = s.from_far_end(&open, 0); - assert!(s.from_caller(&json(serde_json::json!({"event":"stop"}))).end); + assert!( + s.from_caller(&json(serde_json::json!({"event":"stop"}))) + .end + ); assert_eq!(s.units().0, [0, 0, 0, 0, 1, 1]); } #[test] fn one_full_turn_reports_the_six_classes_the_served_plane_metered() { - let mut s = SessionUnit::open(OpenAiRealtimeCodec, SessionConfig::default(), false, 0, None); + let mut s = SessionUnit::open( + OpenAiRealtimeCodec, + SessionConfig::default(), + false, + 0, + None, + ); let pcm = busbar_contract::media::base64_encode(&vec![0u8; 48_000]); let _ = s.from_caller(&json( serde_json::json!({"type":"input_audio_buffer.append","audio":pcm}), @@ -186,9 +222,11 @@ fn one_full_turn_reports_the_six_classes_the_served_plane_metered() { let open = json(serde_json::json!({"type":"response.output_item.added", "item":{"type":"function_call","call_id":"c1","name":"lookup"}})); let _ = s.from_far_end(&open, 0); - let done = json(serde_json::json!({"type":"response.done","response":{"usage":{ + let done = json( + serde_json::json!({"type":"response.done","response":{"usage":{ "input_token_details":{"audio_tokens":10,"text_tokens":3,"cached_tokens":1}, - "output_token_details":{"audio_tokens":20,"text_tokens":4}}}})); + "output_token_details":{"audio_tokens":20,"text_tokens":4}}}}), + ); let _ = s.from_far_end(&done, 0); assert_eq!(s.units().0, [10, 20, 3, 4, 1, 1]); } @@ -196,8 +234,10 @@ fn one_full_turn_reports_the_six_classes_the_served_plane_metered() { #[test] fn a_start_with_no_stream_id_refuses_the_call() { let mut s = telephony(); - let empty = json(serde_json::json!({"event":"start","start":{"streamSid":"","callSid":"CA1", - "mediaFormat":{"encoding":"audio/x-mulaw","sampleRate":8000,"channels":1}}})); + let empty = json( + serde_json::json!({"event":"start","start":{"streamSid":"","callSid":"CA1", + "mediaFormat":{"encoding":"audio/x-mulaw","sampleRate":8000,"channels":1}}}), + ); assert!(s.from_caller(&empty).end); assert!(s.ended()); assert_eq!( diff --git a/crates/busbar-plane-streaming/tests/door.rs b/crates/busbar-plane-streaming/tests/door.rs deleted file mode 100644 index 9e1d4db982..0000000000 --- a/crates/busbar-plane-streaming/tests/door.rs +++ /dev/null @@ -1,327 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE STREAMING PLANE'S DOOR, BOTH WAYS: the same door compiled in (`door::door`, through the -//! loader's `load_linked`) and dropped in (the `streaming_door` example `cdylib` `cargo test` builds, -//! through `load_dropped`), driven by ONE script over the loader's plane kind. Every answer is judged -//! by the kind's own checks on the way; the two transcripts must be identical, and each is pinned. - -use std::mem::zeroed; -use std::sync::Arc; - -use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome, BLOB_JSON}; -use busbar_contract::abi::mechanism::lifecycle::{ - slot as life, CancelIn, CancelOut, GenIn, RefreshIn, TickIn, TickOut, ValidateIn, -}; -use busbar_contract::abi::mechanism::{KindCode, MECHANISM_VERSION}; -use busbar_contract::abi::plane::{ - slot, ArriveIn, ArriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, - CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, -}; -use busbar_plane_streaming::door; -use busbar_plugin_loader::dispatch::kinds::plane::Plane; -use busbar_plugin_loader::dispatch::{ - in_head, load_dropped, load_linked, out_head, Bind, DispatchConfig, Dispatcher, Frame, - ManifestFacts, NoSink, Plugin, -}; - -fn z() -> T { - // SAFETY: every `in`/`out` here is plain C data; all-zero is a valid value of each. - unsafe { zeroed() } -} - -fn bind(d: &Dispatcher) -> Bind { - Bind { - max_inflight_cap: 8, - sink: Arc::new(NoSink), - dispatcher: d.adopter(), - } -} - -fn json(b: &'static [u8]) -> Blob { - Blob { - ptr: b.as_ptr(), - len: b.len(), - fmt: BLOB_JSON, - flags: 0, - } -} - -fn text(s: &'static str) -> AbiStr { - AbiStr { - ptr: s.as_ptr(), - len: s.len(), - } -} - -fn read(s: AbiStr) -> String { - if s.ptr.is_null() { - return String::new(); - } - // SAFETY: the plugin's text, valid while its generation is live. - String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(s.ptr, s.len) }).into_owned() -} - -/// A published snapshot, read while its generation is live: its generation, its claims -/// (`verb target carrier exact?`) and the audience and metadata it binds. -fn snapshot(p: *const PlaneSnapshot) -> String { - assert!(!p.is_null(), "a READY open/refresh publishes a snapshot"); - // SAFETY: the plugin's generation data, valid until `retire` of its generation. - let s = unsafe { &*p }; - let claims: Vec = (0..s.claims_len) - .map(|i| { - // SAFETY: the snapshot names `claims_len` claims. - let c = unsafe { &*s.claims.add(i) }; - format!( - "{} {} {}{}{}", - read(c.verb), - read(c.target), - read(c.carrier), - if c.flags & CLAIM_EXACT != 0 { " exact" } else { "" }, - if c.flags & CLAIM_OPEN != 0 { " open" } else { "" } - ) - }) - .collect(); - format!( - "gen={} claims=[{}] audience={} metadata={} routes={}", - s.generation, - claims.join(", "), - read(s.audience), - read(s.resource_metadata), - s.admin_routes_len - ) -} - -fn linked(d: &Dispatcher) -> Plugin { - load_linked::(door::door, bind(d)).expect("the linked streaming door loads") -} - -/// The example `cdylib` in this target dir (`cargo test` builds examples). Under CI a missing -/// artifact is a failure, never a skip. -fn dropped(d: &Dispatcher) -> Option> { - let exe = std::env::current_exe().ok()?; - let path = exe.parent()?.parent()?.join("examples").join(format!( - "{}streaming_door{}", - std::env::consts::DLL_PREFIX, - std::env::consts::DLL_SUFFIX - )); - assert!( - path.exists() || std::env::var_os("CI").is_none(), - "the streaming_door example cdylib is not built under CI; a both-ways proof must not skip" - ); - let facts = ManifestFacts { - mechanism_version: MECHANISM_VERSION, - kind: KindCode::Plane, - kind_abi: KindCode::Plane.abi_version(), - }; - path.exists().then(|| { - load_dropped::(&path, &facts, bind(d)).expect("the dropped streaming door loads") - }) -} - -fn validate(p: &Plugin, settings: &'static [u8]) -> Outcome { - let mut v = Frame::new( - ValidateIn { - head: in_head(), - settings: json(settings), - }, - out_head(), - ); - p.call(life::VALIDATE, &mut v).outcome -} - -fn open( - p: &Plugin, - generation: u64, - settings: &'static [u8], - public_url: Option<&'static str>, -) -> (Outcome, String) { - let mut i: PlaneOpenIn = z(); - i.open.head = in_head(); - i.open.generation = generation; - i.open.settings = json(settings); - if let Some(u) = public_url { - i.public_url = text(u); - } - let mut o: PlaneOpenOut = z(); - o.open.head = out_head(); - let mut f = Frame::new(i, o); - let c = p.call(life::OPEN, &mut f); - let snap = if c.outcome == Outcome::Ready { - snapshot(f.out.snapshot) - } else { - String::new() - }; - (c.outcome, snap) -} - -/// THE SCRIPT: the lifecycle over two generations, then one unit offered to the door. -fn script(p: &Plugin, public_url: Option<&'static str>) -> Vec { - let mut t = Vec::new(); - t.push(format!( - "validate unknown key {:?}", - validate(p, br#"{"nonsense_key":1}"#) - )); - t.push(format!( - "validate zero ceiling {:?}", - validate(p, br#"{"session_max_secs":0}"#) - )); - t.push(format!("validate empty {:?}", validate(p, b""))); - t.push(format!( - "validate ceiling {:?}", - validate(p, br#"{"session_max_secs":1800}"#) - )); - - let (outcome, snap) = open(p, 1, br#"{"session_max_secs":1800}"#, public_url); - t.push(format!("open {outcome:?} {snap}")); - - let mut r = Frame::new( - RefreshIn { - head: in_head(), - generation: 2, - settings: json(b"{}"), - secrets: std::ptr::null(), - secrets_len: 0, - }, - PlaneRefreshOut { - head: out_head(), - snapshot: std::ptr::null(), - }, - ); - let c = p.call(life::REFRESH, &mut r); - t.push(format!("refresh {:?} {}", c.outcome, snapshot(r.out.snapshot))); - - let mut g = Frame::new( - GenIn { - head: in_head(), - generation: 1, - }, - out_head(), - ); - t.push(format!("retire 1 {:?}", p.call(life::RETIRE, &mut g).outcome)); - - for claim in [3, 5, 9] { - let mut a: Frame = Frame::new(z(), z()); - a.input.head = in_head(); - a.input.claim = claim; - a.out.head = out_head(); - let c = p.call(slot::ARRIVE, &mut a); - t.push(format!( - "arrive {claim} {:?} dialect={} op={} principal={}", - c.outcome, a.out.dialect, a.out.op_class, a.out.principal_need - )); - } - - let mut k: Frame = Frame::new(z(), z()); - k.input.head = in_head(); - k.input.now_ns = 5; - k.out.head = out_head(); - let c = p.call(life::TICK, &mut k); - t.push(format!("tick {:?} next={}", c.outcome, k.out.next_tick_ns)); - - let mut x: Frame = Frame::new(z(), z()); - x.input.head = in_head(); - x.out.head = out_head(); - let c = p.call(life::CANCEL, &mut x); - t.push(format!( - "cancel {:?} failed={}", - c.outcome, - x.out.disposition == CANCEL_FAILED - )); - t -} - -const PUBLIC: &str = "https://gw.example.com/ignored?q=1#f"; - -fn expected(public: bool) -> Vec { - let doors = "POST /v1/realtime/client_secrets http exact, POST /v1/realtime/calls http exact, \ - GET /v1/realtime/sideband/{call_id} ws, GET /v1/realtime/gemini/{call_id} ws, \ - GET /twilio/{call_id} ws, \ - GET /.well-known/oauth-protected-resource/v1/realtime http exact open"; - let (claims, audience, metadata) = if public { - ( - doors, - "https://gw.example.com/v1/realtime", - "https://gw.example.com/.well-known/oauth-protected-resource/v1/realtime", - ) - } else { - ("", "", "") - }; - let snap = |g: u64| { - format!("gen={g} claims=[{claims}] audience={audience} metadata={metadata} routes=0") - }; - vec![ - "validate unknown key Refused".into(), - "validate zero ceiling Refused".into(), - "validate empty Ready".into(), - "validate ceiling Ready".into(), - format!("open Ready {}", snap(1)), - format!("refresh Ready {}", snap(2)), - "retire 1 Ready".into(), - "arrive 3 Ready dialect=1 op=0 principal=1".into(), - "arrive 5 Ready dialect=0 op=0 principal=0".into(), - "arrive 9 Refused dialect=0 op=0 principal=0".into(), - "tick Ready next=0".into(), - "cancel Ready failed=true".into(), - ] -} - -#[test] -fn the_linked_door_states_the_planes_doors_under_its_public_url() { - let d = Dispatcher::new(DispatchConfig::default()); - assert_eq!(script(&linked(&d), Some(PUBLIC)), expected(true)); -} - -#[test] -fn a_deployment_with_no_public_url_is_claimed_by_no_door() { - let d = Dispatcher::new(DispatchConfig::default()); - assert_eq!(script(&linked(&d), None), expected(false)); -} - -#[test] -fn the_dropped_door_answers_as_the_linked_door_does() { - let d = Dispatcher::new(DispatchConfig::default()); - let Some(dropped) = dropped(&d) else { - eprintln!("skip: the streaming_door example cdylib is not built"); - return; - }; - for url in [Some(PUBLIC), None] { - assert_eq!( - script(&dropped, url), - script(&linked(&d), url), - "the two doors must answer identically" - ); - } -} - -#[test] -fn the_tail_states_what_the_served_plane_declares() { - // The billable classes a session counts in, the per-session fee and the `session` grant kind: - // the served plane's own declaration, read off the tail the door states. - let t = door::TAIL; - assert_eq!(read(t.scope), "session"); - assert_eq!(read(t.audit_kind), "streaming_session"); - // SAFETY: the tail's `'static` lists. - let classes: Vec = unsafe { std::slice::from_raw_parts(t.billable_classes, t.billable_classes_len) } - .iter() - .map(|c| format!("{}/{}", read(c.class), read(c.family))) - .collect(); - assert_eq!( - classes, - [ - "audio_tokens_in/token", - "audio_tokens_out/token", - "text_tokens_in/token", - "text_tokens_out/token", - "audio_seconds_in/duration", - "tool_calls/count", - ] - ); - // SAFETY: as above. - let fees: Vec = unsafe { std::slice::from_raw_parts(t.fee_units, t.fee_units_len) } - .iter() - .map(|f| read(*f)) - .collect(); - assert_eq!(fees, ["per_session"]); - busbar_contract::abi::plane::check::check_tail(t).expect("the kind's own check admits the tail"); -} diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index 04f2671c2e..bc7ceb83af 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -46,17 +46,17 @@ use crate::topology::{ }; use busbar_kernel::config::RootCfg; use busbar_kernel::egress::engine::{send_bounded, EngineClient}; -use busbar_kernel::ingress::byte_duplex::serve_messages; use busbar_kernel::ingress::duplex_ws::{ accept_gauntlet, install_ws_arrivals, WsAcceptFuture, WsArrival, WsArrivalSpec, }; +use busbar_kernel::ingress::{byte_duplex::serve_messages, protocol}; use busbar_kernel::plane::handle_engine::DurableHandleEngine; use busbar_kernel::plane::observe::Counted; use busbar_kernel::plane::registry::{BuildCtx, PlaneBootCtx}; use busbar_kernel::plane::PlaneAdmission; use busbar_kernel::plane_host::{EngineHost, GateOutcome, TransformVerdict}; use busbar_kernel::plane_host::{GauntletPlane, GauntletRequest}; -use busbar_kernel::plane_routes::PlaneRouteSpec; +use busbar_kernel::plane_routes::{PlaneReqCtx, PlaneRouteSpec}; use busbar_kernel::{door::UnitKeyMint, net_guard::GuardPolicy}; use bytes::Bytes; use http_body_util::{BodyExt, Full}; @@ -547,11 +547,8 @@ pub fn voice_build(ctx: &BuildCtx) -> Option> { /// rather than the plane's dev defaults. Every route binds the live host onto it (see /// [`VoiceMount::runtime`]). fn dispatch_runtime() -> VoiceRuntime { - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(ClientRelay), - ) - .with_streams(&crate::config::configured()) + VoiceRuntime::new(Arc::new(DurableHandleEngine::new()), Arc::new(ClientRelay)) + .with_streams(&crate::config::configured()) } /// [`PlaneDecl::claims`] — the ONE audience-checked region the voice plane answers on, spoken in its @@ -592,8 +589,8 @@ pub fn voice_admission(slot: &dyn Any) -> Option { /// receiving side (no dispatch slot), so a deployment that fronts nothing mounts nothing. #[must_use] pub fn voice_routes(slot: &dyn Any) -> Vec { - use busbar_contract::abi::mechanism::route::{RouteAuth, RouteMethod}; - use busbar_kernel::plane_routes::{PlaneReqCtx, PlaneRouteFuture}; +use busbar_contract::abi::mechanism::route::{RouteAuth, RouteMethod}; +use busbar_kernel::plane_routes::PlaneRouteFuture; if slot.downcast_ref::().is_none() { return Vec::new(); @@ -627,14 +624,14 @@ pub fn voice_routes(slot: &dyn Any) -> Vec { /// The protected-resource metadata document: the audience a token at this plane's doors must carry, /// as one reading of the public URL. No authorization server is named: the plane is configured with /// none. -async fn metadata_route(ctx: busbar_kernel::plane_routes::PlaneReqCtx) -> axum::response::Response { +async fn metadata_route(ctx: PlaneReqCtx) -> axum::response::Response { let Some(mount) = ctx.slot.downcast_ref::() else { return refusal( axum::http::StatusCode::INTERNAL_SERVER_ERROR, "streaming route reached without its dispatch slot", ); }; - busbar_kernel::ingress::protocol::metadata(&busbar_kernel::ingress::protocol::Metadata { + protocol::metadata(&protocol::Metadata { resource: std::borrow::Cow::Borrowed(mount.audience.as_str()), authorization_servers: &[], scopes_supported: &[], diff --git a/crates/busbar-voice/src/runtime/tests.rs b/crates/busbar-voice/src/runtime/tests.rs index 2071fcc75a..caf1d0fe21 100644 --- a/crates/busbar-voice/src/runtime/tests.rs +++ b/crates/busbar-voice/src/runtime/tests.rs @@ -9,7 +9,6 @@ //! count (what a count is WORTH is proven over the real engine in the composition root's tests). use crate::ir::codec::{OpenAiRealtimeCodec, WireEvent}; -use crate::ir::usage::IrDuplexUsage; use crate::runtime::carrier::Carrier; use crate::runtime::metering::{SessionMetering, TurnMeter, TurnVerdict}; use crate::runtime::scope::SessionHandle; @@ -143,45 +142,6 @@ fn row_set(pairs: &[(&str, u64)]) -> BTreeMap { // ── metering: the plane's counts, the kernel's verdict ────────────────────────────────────────── -#[test] -fn usage_folds_five_classes_onto_the_four_reserved_keys() { - // The codec's own 5→4 fold (no longer the voice billing path, which ledgers the plane's classes — - // see `a_served_turn_ledgers_the_planes_counts_per_class`), kept honest while it exists. - let u = IrDuplexUsage { - audio_in: 20, - audio_out: 3, - text_in: 5, - text_out: 7, - cached: 11, - }; - let usage = u.to_billing_usage(); - assert_eq!( - usage - .usage_units - .get(busbar_contract::records::UNIT_INPUT) - .copied(), - Some(20 + 5 - 11) - ); - assert_eq!( - usage - .usage_units - .get(busbar_contract::records::UNIT_OUTPUT) - .copied(), - Some(3 + 7) - ); - assert_eq!( - usage - .usage_units - .get(busbar_contract::records::UNIT_CACHE_READ) - .copied(), - Some(11) - ); - assert!(IrDuplexUsage::default() - .to_billing_usage() - .usage_units - .is_empty()); -} - /// Q21b EXIT TEST — a served voice session writes the streaming plane's count rows. One turn: a /// second of uplink audio, one tool call opened, a usage report naming all five token classes. The /// ledger holds exactly the plane's SIX billable classes, under the voice lane, at the counts the diff --git a/crates/busbar-voice/src/tests/mount_tests.rs b/crates/busbar-voice/src/tests/mount_tests.rs index d4e5b9968d..06551d6e2d 100644 --- a/crates/busbar-voice/src/tests/mount_tests.rs +++ b/crates/busbar-voice/src/tests/mount_tests.rs @@ -822,7 +822,7 @@ async fn the_resource_metadata_the_challenge_points_at_is_served() { .expect("the pointer's path is a route the plane serves"); assert_eq!(route.auth, RouteAuth::None, "readable without a credential"); let host = crate::testkit::fixture_host::FixtureHost::new().into_host(); - let ctx = busbar_kernel::plane_routes::PlaneReqCtx { + let ctx = super::PlaneReqCtx { path: path.clone(), uri: axum::http::Uri::default(), method: RouteMethod::Get, diff --git a/qa/construction.toml b/qa/construction.toml index 8aefd093b0..15f2edc228 100644 --- a/qa/construction.toml +++ b/qa/construction.toml @@ -1066,9 +1066,11 @@ max_sites = 0 # kernel-minted Pass (`KernelSeal::acquire_for_kernel()`) instead of the test seal, and egress's # test-seal dev-dependency went with it. The rule's pattern matches no file under either crate, so # the entry is not repointed (a row on a path with no finding would only excuse a future forgery); -# it is struck, and both paths are held to the rule. The seal debt that remains is the two below. +# it is struck, and both paths are held to the rule. +# +# STRUCK 2026-10-04: `crates/busbar-plane-streaming/src/tests/harness.rs`, deleted by the streaming +# fold (its tests drive the door). The seal debt that remains is the one below. known_sites = [ - "crates/busbar-plane-streaming/src/tests/harness.rs", "crates/busbar-transport-stdio/src/tests/battery.rs", ] diff --git a/xtask/src/gates/no_float_money.rs b/xtask/src/gates/no_float_money.rs index 3a5fe33ed7..7027c58bd6 100644 --- a/xtask/src/gates/no_float_money.rs +++ b/xtask/src/gates/no_float_money.rs @@ -678,18 +678,6 @@ pub const ALLOWED_COUNT_READS: &[Allow] = &[ class: AllowClass::NotACount, why: "a media format's channel count, not a metered quantity", }, - Allow { - file: "crates/busbar-plane-streaming/src/twilio.rs", - needle: "get(\"sampleRate\")", - class: AllowClass::NotACount, - why: "a media format's sample rate, not a metered quantity", - }, - Allow { - file: "crates/busbar-plane-streaming/src/twilio.rs", - needle: "get(\"channels\")", - class: AllowClass::NotACount, - why: "a media format's channel count, not a metered quantity", - }, Allow { file: "crates/busbar-kernel/src/config/migrate.rs", needle: "price_per_1k_tokens_cents", From edf60f26149e53e461c026e8e5de0af4e3f9de9b Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 04/82] busbar-plane-streaming: the door both-ways proof in tests/conformance.rs, one Steps trait, text frames, the door's one session constructor (S3f; 6 carried commits) -- streaming plane door both-ways proof moves to the loader, the #2 witness shape (ARCHITECT: the dev edge only as the witness); the dropped-door transcript bug is fixed The first proof run failed the_dropped_door_answers_as_the_linked_door_does. The test reused one dropped instance across both scripts, so the second script's open met an instance already open (REFUSED), while a fresh linked instance answered READY. The doors themselves answered identically. Each script now loads a fresh instance of each door. The both-ways test also moves into crates/plugin-loader, as #2 (4)/(5) orders a kind's witness. plugin-loader dev-depends on the real plane (busbar-plane-streaming), builds the dropped door as its own `streaming_door` example (tests/fixtures/streaming_door.rs: export_door! over the plane's door), and src/tests/streaming_door_tests.rs runs the same pinned transcripts. This removes the plane's forbidden test edge plane -> plugin-tooling, and the kernel crates the loader's closure dragged into the plane's test graph (closure-test-reach to kernel-ledger and kernel-wal). The plane crate keeps no example and no dev edge on the loader. qa/kind-isolation.toml gains the one [[dep]] row for the loader's test edge, cited to #2 like the transport and store witnesses. -- streaming plane door both-ways proof: back in the plane, as tests/conformance.rs (the ARCHITECT's DOOR-TRANSPORT witness grant) The previous commit put the witness in the loader, which traded the plane's test edge for a forbidden plugin-tooling -> plane edge. The granted shape (ARCHITECT 2026-09-27, DOOR-TRANSPORT, #2 (4)/(5); kind-isolation's conformance_witness_edges) is the plugin's own: a plugin of any kind dev-depends on busbar-plugin-loader, and only its tests/conformance.rs names the loader. The ws transport uses this shape. The same test, with the dropped-door fix kept (a fresh instance per script), is now crates/busbar-plane-streaming/tests/conformance.rs. The streaming_door example builds the cdylib. The loader's dev edge, example and [[dep]] row are removed. -- busbar-plane-streaming: the loop-step answers are one Steps trait the plane's doors implement; a design citation spelled in words; rustfmt The kernel's plane-steps rule reads each step as a method the plane implements. The step answers are now the Steps trait (authenticate, verify, approve, admit, route, meter, audit), implemented for Door. The door's arrive answers through it. Behaviour and tests are unchanged; the step test calls the methods. The section-sign citation in driven.rs is spelled in words, as the crate's style test requires. rustfmt also orders busbar-voice runtime/mod.rs's tool re-exports. -- busbar-plane-streaming: a session's emitted frames are text messages, and either opcode is read (S3f plane half) SessionUnit states FRAMES_ARE_TEXT: the realtime dialects and the telephony envelope are JSON events, so the door sets the text bit (OnPieceOut PIECE_OUT_TEXT, in K2's layout commit) on every frame a session emits. Inbound frames are read by their bytes: Gemini Live's server sends its JSON in binary frames. The door's per-answer RED lands when on_piece is wired over K2's OnPieceOut; this commit pins the plane's statement and that a Gemini frame's answers are text. -- busbar-plane-streaming: the door's one session constructor; the telephony door opens the Twilio envelope's session (ARCHITECT ruling (b) RED) sessions::open decides, per session door, the codec and the caller-side envelope in one place. - The sideband door: OpenAI Realtime over the operator's locked params. - The Gemini door: Gemini Live. - The telephony door: OpenAI Realtime locked to g711_ulaw both ways, exactly as the served telephony leg locked it, behind TwilioEnvelope, the one Twilio reader. - The one-request doors open no session. RED, with the arm kept: sessions_tests::the_telephony_door_opens_a_session_behind_the_twilio_envelope. A Twilio start plus media on the telephony door reaches the far end as an uplink append; the same frames on a door with no envelope reach no one, which is the served defect. -- session_unit_tests: the text-frame statement is checked at compile time (clippy assertions_on_constants) (cherry picked from commit 86b96ded8f39bfbf2e490ad7fa5c83acace98379) --- crates/busbar-plane-streaming/Cargo.toml | 6 +- .../examples/streaming_door.rs | 8 + crates/busbar-plane-streaming/src/door.rs | 5 +- crates/busbar-plane-streaming/src/driven.rs | 177 +++++---- crates/busbar-plane-streaming/src/lib.rs | 1 + .../src/session_unit.rs | 8 +- crates/busbar-plane-streaming/src/sessions.rs | 99 +++++ .../src/tests/driven_tests.rs | 26 +- .../src/tests/session_unit_tests.rs | 21 ++ .../src/tests/sessions_tests.rs | 52 +++ .../tests/conformance.rs | 347 ++++++++++++++++++ crates/busbar-voice/src/runtime/mod.rs | 11 +- 12 files changed, 655 insertions(+), 106 deletions(-) create mode 100644 crates/busbar-plane-streaming/examples/streaming_door.rs create mode 100644 crates/busbar-plane-streaming/src/sessions.rs create mode 100644 crates/busbar-plane-streaming/src/tests/sessions_tests.rs create mode 100644 crates/busbar-plane-streaming/tests/conformance.rs diff --git a/crates/busbar-plane-streaming/Cargo.toml b/crates/busbar-plane-streaming/Cargo.toml index 6022f5eddb..d895229b8c 100644 --- a/crates/busbar-plane-streaming/Cargo.toml +++ b/crates/busbar-plane-streaming/Cargo.toml @@ -67,9 +67,9 @@ busbar-contract = { path = "../busbar-contract", features = ["test-seal"] } # The conformance rig drives the plane end-to-end over real openai-realtime (the voice dialect) wire # bytes, so it builds the same JSON fixtures the underlying codec tests use. serde_json = { workspace = true } -# THE DOOR, BOTH WAYS (`tests/door.rs`): the loader's plane kind loads the linked door and the -# dropped-in image the example below builds, and drives both through one script. Dev-only: absent -# from every release build. +# THE DOOR, BOTH WAYS (`tests/conformance.rs`, the plugin's own witness): the loader's plane kind +# loads the linked door and the dropped-in image the example below builds, and drives both through +# one script. Dev-only: absent from every release build. busbar-plugin-loader = { path = "../plugin-loader" } # THE DROPPED-IN IMAGE ON THE ONE DOOR: built by `cargo test`. diff --git a/crates/busbar-plane-streaming/examples/streaming_door.rs b/crates/busbar-plane-streaming/examples/streaming_door.rs new file mode 100644 index 0000000000..8018cc909c --- /dev/null +++ b/crates/busbar-plane-streaming/examples/streaming_door.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! The streaming plane's door, dropped in: the one exported symbol, forwarding to the same door a +//! compiled-in row holds. `tests/conformance.rs` loads it beside the linked door and drives both the +//! same way. + +busbar_contract::export_door!(busbar_plane_streaming::door::door); diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index ac286fd367..159d4397d1 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -47,6 +47,7 @@ use busbar_contract::plane::{PER_SESSION, TOKEN_FAMILY}; use crate::claims::{Dialect, HTTP_TRANSPORT, WS_TRANSPORT}; use crate::config::StreamsCfg; +use crate::driven::Steps; use crate::meta; use crate::provider::{GEMINI_LIVE, OPENAI_REALTIME}; @@ -541,7 +542,7 @@ slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { return Outcome::Refused; }; let mut units = input.units_buf(); - for (class, amount) in crate::driven::admit(a.door) { + for (class, amount) in a.door.admit() { units.push(UnitCount { class, source: UNITS_ESTIMATED, @@ -555,7 +556,7 @@ slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { out.units_written = units.written() as u32; out.op_class = a.op_class; out.dialect = a.dialect; - out.principal_need = crate::driven::authenticate(a.door); + out.principal_need = a.door.authenticate(); Outcome::Ready }); diff --git a/crates/busbar-plane-streaming/src/driven.rs b/crates/busbar-plane-streaming/src/driven.rs index 46e25e6236..6e0277da7b 100644 --- a/crates/busbar-plane-streaming/src/driven.rs +++ b/crates/busbar-plane-streaming/src/driven.rs @@ -107,95 +107,114 @@ pub const fn arrive(claim: u32) -> Option { } } -// ── the plane's answer at each loop step (THE DESIGN §1's order) ───────────────────────────────── +// ── the plane's answer at each loop step, in the order the kernel's loop runs them ───────────── + +/// THE PLANE'S ANSWER AT EACH LOOP STEP for a unit on one of its doors: what the kernel's loop asks +/// the plane at authenticate, verify, approve, admit, route, meter and audit. The door answers the +/// driver through these; nothing here reaches the kernel any other way. +pub trait Steps { + /// AUTHENTICATE: whom a unit on this door needs. + fn authenticate(&self) -> u32; + /// VERIFY: where, inside the plane's section, the destination a session dials is named. + fn verify(&self) -> &'static str; + /// APPROVE: the grant a key must hold, as (kind, value). + fn approve(&self) -> (&'static str, &'static str); + /// ADMIT: the units a unit is expected to cost before the far end reports any. + fn admit(&self) -> Vec<(u32, u64)>; + /// ROUTE: the request an attempt sends the far end; `None` when the door sends nothing. + /// + /// # Errors + /// The request body's serializer text. + fn route( + &self, + config: &SessionConfig, + caller_ref: Option<&str>, + body: &[u8], + ) -> Result, String>; + /// METER: a session's units so far, per billable class index, the zero classes left out. + fn meter(&self, units: &crate::session_unit::CumulativeUnits) -> Vec<(u32, u64)>; + /// AUDIT: the action a unit on this door is audited under. + fn audit(&self) -> &'static str; +} -/// AUTHENTICATE: whom a unit on `door` needs. Every keyed door needs a principal; the metadata -/// document answers anyone. -#[must_use] -pub const fn authenticate(door: Door) -> u32 { - if door.is_open() { - busbar_contract::abi::plane::PRINCIPAL_NONE - } else { - busbar_contract::abi::plane::PRINCIPAL_REQUIRED +impl Steps for Door { + /// Every keyed door needs a principal; the metadata document answers anyone. + fn authenticate(&self) -> u32 { + if self.is_open() { + busbar_contract::abi::plane::PRINCIPAL_NONE + } else { + busbar_contract::abi::plane::PRINCIPAL_REQUIRED + } } -} -/// VERIFY: where, inside the plane's section, the destination a session dials is named. The kernel -/// resolves it through the root models and judges it. -#[must_use] -pub const fn verify() -> &'static str { - crate::door::EGRESS_TARGET -} + /// The kernel resolves `streams.session.model` through the root models and judges it. + fn verify(&self) -> &'static str { + crate::door::EGRESS_TARGET + } -/// APPROVE: the grant a key must hold to open a session here: the `session` kind, for the one pool -/// every session is served on. -#[must_use] -pub const fn approve() -> (&'static str, &'static str) { - (crate::door::SCOPE, crate::door::SESSION_POOL) -} + /// The `session` kind, for the one pool every session is served on. + fn approve(&self) -> (&'static str, &'static str) { + (crate::door::SCOPE, crate::door::SESSION_POOL) + } -/// ADMIT: the units a unit on `door` is expected to cost before the far end reports any. None: a -/// session is admitted at open and pays for what the far end reports (and its per-session fee, -/// which the kernel counts); the one-request doors report nothing. -#[must_use] -pub fn admit(door: Door) -> Vec<(u32, u64)> { - match door { - Door::Mint | Door::Sdp | Door::Sideband | Door::Gemini | Door::Twilio | Door::Metadata => { - Vec::new() + /// None: a session is admitted at open and pays for what the far end reports (and its + /// per-session fee, which the kernel counts); the one-request doors report nothing. + fn admit(&self) -> Vec<(u32, u64)> { + match self { + Door::Mint + | Door::Sdp + | Door::Sideband + | Door::Gemini + | Door::Twilio + | Door::Metadata => Vec::new(), } } -} -/// ROUTE: the request an attempt on `door` sends the far end. The session doors dial the dialect's -/// realtime socket (the credential is the kernel's, never in the target); the mint and SDP doors send -/// their one request; the metadata document is answered here and sends nothing. -/// -/// # Errors -/// The mint body's serializer text. -pub fn route( - door: Door, - config: &SessionConfig, - caller_ref: Option<&str>, - body: &[u8], -) -> Result, String> { - Ok(match door { - Door::Mint => Some(mint_attempt(config, caller_ref, None)?), - Door::Sdp => Some(sdp_attempt(body)), - Door::Sideband | Door::Twilio => Some(Attempt { - verb: "GET", - target: REALTIME_SOCKET_PATH, - fields: Vec::new(), - body: Vec::new(), - }), - Door::Gemini => Some(Attempt { - verb: "GET", - target: GEMINI_SOCKET_PATH, - fields: Vec::new(), - body: Vec::new(), - }), - Door::Metadata => None, - }) -} + /// The session doors dial the dialect's realtime socket (the credential is the kernel's, never + /// in the target); the mint and SDP doors send their one request; the metadata document is + /// answered here and sends nothing. + fn route( + &self, + config: &SessionConfig, + caller_ref: Option<&str>, + body: &[u8], + ) -> Result, String> { + Ok(match self { + Door::Mint => Some(mint_attempt(config, caller_ref, None)?), + Door::Sdp => Some(sdp_attempt(body)), + Door::Sideband | Door::Twilio => Some(Attempt { + verb: "GET", + target: REALTIME_SOCKET_PATH, + fields: Vec::new(), + body: Vec::new(), + }), + Door::Gemini => Some(Attempt { + verb: "GET", + target: GEMINI_SOCKET_PATH, + fields: Vec::new(), + body: Vec::new(), + }), + Door::Metadata => None, + }) + } -/// METER: a session's units so far, per billable class index, the zero classes left out. Counts -/// only; the kernel prices nothing the plane names. -#[must_use] -pub fn meter(units: &crate::session_unit::CumulativeUnits) -> Vec<(u32, u64)> { - units - .0 - .iter() - .enumerate() - .filter(|(_, n)| **n != 0) - .map(|(i, n)| (i as u32, *n)) - .collect() -} + /// Counts only; the kernel prices nothing the plane names. + fn meter(&self, units: &crate::session_unit::CumulativeUnits) -> Vec<(u32, u64)> { + units + .0 + .iter() + .enumerate() + .filter(|(_, n)| **n != 0) + .map(|(i, n)| (i as u32, *n)) + .collect() + } -/// AUDIT: the action a unit on `door` is audited under. -#[must_use] -pub const fn audit(door: Door) -> &'static str { - match door { - Door::Metadata => "streaming.metadata.read", - _ => SESSION_OPEN_ACTION, + /// A session open, or a read of the metadata document. + fn audit(&self) -> &'static str { + match self { + Door::Metadata => "streaming.metadata.read", + _ => SESSION_OPEN_ACTION, + } } } diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index 88d637d95f..7622bab92d 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -65,6 +65,7 @@ pub mod session_params; pub mod session_pump; pub mod session_row; pub mod session_unit; +pub mod sessions; pub mod tools; #[cfg(test)] diff --git a/crates/busbar-plane-streaming/src/session_unit.rs b/crates/busbar-plane-streaming/src/session_unit.rs index 9652b08cbc..2070461200 100644 --- a/crates/busbar-plane-streaming/src/session_unit.rs +++ b/crates/busbar-plane-streaming/src/session_unit.rs @@ -50,7 +50,10 @@ impl TurnSink for CumulativeUnits { } } -/// What one piece asks the driver to carry. +/// What one piece asks the driver to carry. Every frame a session emits, to either side, is one +/// TEXT message ([`FRAMES_ARE_TEXT`]): the realtime dialects and the telephony envelope are JSON +/// events. What arrives may be either: Gemini Live's server sends its JSON in binary frames, and a +/// frame is read by its bytes, not its opcode. #[derive(Debug, Default, PartialEq, Eq)] pub struct Plan { /// Frames bound for the far end, in order. @@ -142,6 +145,9 @@ impl TwilioBridge { } } +/// Every frame a session emits is one text message: the door sets the text bit on each. +pub const FRAMES_ARE_TEXT: bool = true; + /// ONE LIVE SESSION over the far end's codec `C`. #[derive(Debug)] pub struct SessionUnit { diff --git a/crates/busbar-plane-streaming/src/sessions.rs b/crates/busbar-plane-streaming/src/sessions.rs new file mode 100644 index 0000000000..56f4118ef2 --- /dev/null +++ b/crates/busbar-plane-streaming/src/sessions.rs @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE DOOR'S SESSION CONSTRUCTOR: which live session a unit on a session door opens. Every session +//! door builds its session here, and nowhere else, so the door a unit arrived through decides its +//! codec and its caller-side envelope in one place. + +use crate::codec::ir::codec::gemini::GeminiLiveCodec; +use crate::codec::ir::codec::OpenAiRealtimeCodec; +use crate::codec::ir::config::SessionConfig; +use crate::driven::Door; +use crate::session_params::g711_config; +use crate::session_unit::{CumulativeUnits, Plan, SessionUnit}; + +/// One live session, over the codec its door speaks. +#[derive(Debug)] +pub enum Session { + /// The OpenAI Realtime dialect: the browser sideband, or the telephony door behind the Twilio + /// envelope. + Realtime(SessionUnit), + /// The Gemini Live dialect. + Gemini(SessionUnit), +} + +/// Open the session a unit on `door` opens: `None` for a door that answers one request. The +/// telephony door is the OpenAI Realtime dialect locked to `g711_ulaw` both ways, behind the Twilio +/// envelope, locked as the served telephony leg locked it; the other session doors lock the +/// operator's session params. +#[must_use] +pub fn open( + door: Door, + locked: &SessionConfig, + now_ns: u64, + ceiling_secs: Option, + id: &str, + owner: &str, +) -> Option { + Some(match door { + Door::Sideband => Session::Realtime(SessionUnit::open_as( + OpenAiRealtimeCodec, + locked.clone(), + false, + now_ns, + ceiling_secs, + id, + owner, + )), + Door::Twilio => Session::Realtime(SessionUnit::open_as( + OpenAiRealtimeCodec, + g711_config(), + true, + now_ns, + ceiling_secs, + id, + owner, + )), + Door::Gemini => Session::Gemini(SessionUnit::open_as( + GeminiLiveCodec, + locked.clone(), + false, + now_ns, + ceiling_secs, + id, + owner, + )), + Door::Mint | Door::Sdp | Door::Metadata => return None, + }) +} + +impl Session { + /// A piece from the caller. + pub fn from_caller(&mut self, bytes: &[u8]) -> Plan { + match self { + Session::Realtime(s) => s.from_caller(bytes), + Session::Gemini(s) => s.from_caller(bytes), + } + } + + /// A piece from the far end, at `now_ms`. + pub fn from_far_end(&mut self, bytes: &[u8], now_ms: u64) -> Plan { + match self { + Session::Realtime(s) => s.from_far_end(bytes, now_ms), + Session::Gemini(s) => s.from_far_end(bytes, now_ms), + } + } + + /// The session's cumulative units. + #[must_use] + pub fn units(&self) -> CumulativeUnits { + match self { + Session::Realtime(s) => s.units(), + Session::Gemini(s) => s.units(), + } + } +} + +#[cfg(test)] +#[path = "tests/sessions_tests.rs"] +mod tests; diff --git a/crates/busbar-plane-streaming/src/tests/driven_tests.rs b/crates/busbar-plane-streaming/src/tests/driven_tests.rs index d86acf3d7a..3f7925c747 100644 --- a/crates/busbar-plane-streaming/src/tests/driven_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/driven_tests.rs @@ -126,17 +126,18 @@ fn the_metadata_document_names_the_audience() { #[test] fn each_loop_step_has_the_planes_answer() { use busbar_contract::abi::plane::{PRINCIPAL_NONE, PRINCIPAL_REQUIRED}; - assert_eq!(authenticate(Door::Mint), PRINCIPAL_REQUIRED); - assert_eq!(authenticate(Door::Twilio), PRINCIPAL_REQUIRED); - assert_eq!(authenticate(Door::Metadata), PRINCIPAL_NONE); - assert_eq!(verify(), "session.model"); - assert_eq!(approve(), ("session", "streaming-server")); + assert_eq!(Door::Mint.authenticate(), PRINCIPAL_REQUIRED); + assert_eq!(Door::Twilio.authenticate(), PRINCIPAL_REQUIRED); + assert_eq!(Door::Metadata.authenticate(), PRINCIPAL_NONE); + assert_eq!(Door::Sideband.verify(), "session.model"); + assert_eq!(Door::Sideband.approve(), ("session", "streaming-server")); assert!( - admit(Door::Sideband).is_empty(), + Door::Sideband.admit().is_empty(), "a session pays for what the far end reports" ); let cfg = SessionConfig::default(); - let sock = route(Door::Gemini, &cfg, None, b"") + let sock = Door::Gemini + .route(&cfg, None, b"") .expect("routes") .expect("a session dials"); assert_eq!(sock.verb, "GET"); @@ -145,16 +146,13 @@ fn each_loop_step_has_the_planes_answer() { "the credential is never in the target" ); assert_eq!( - route(Door::Sdp, &cfg, None, b"v=0").expect("routes"), + Door::Sdp.route(&cfg, None, b"v=0").expect("routes"), Some(sdp_attempt(b"v=0")) ); - assert_eq!( - route(Door::Metadata, &cfg, None, b"").expect("routes"), - None - ); + assert_eq!(Door::Metadata.route(&cfg, None, b"").expect("routes"), None); let mut units = crate::session_unit::CumulativeUnits::default(); units.0[1] = 20; units.0[4] = 2; - assert_eq!(meter(&units), vec![(1, 20), (4, 2)]); - assert_eq!(audit(Door::Mint), "streaming.session.open"); + assert_eq!(Door::Sideband.meter(&units), vec![(1, 20), (4, 2)]); + assert_eq!(Door::Mint.audit(), "streaming.session.open"); } diff --git a/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs b/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs index ebeaa541e7..855354cc52 100644 --- a/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_unit_tests.rs @@ -259,3 +259,24 @@ fn a_media_payload_resuming_after_padding_is_refused_and_never_billed() { assert_eq!(s.units(), CumulativeUnits::default(), "nothing is billed"); } } + +#[test] +fn every_emitted_frame_is_a_text_message_and_either_opcode_is_read() { + const { assert!(FRAMES_ARE_TEXT) }; + // Gemini Live's server frames arrive binary: the bytes are read as they are. + let mut s = SessionUnit::open( + crate::codec::ir::codec::gemini::GeminiLiveCodec, + SessionConfig::default(), + false, + 0, + None, + ); + let setup = json(serde_json::json!({"setupComplete": {}})); + let plan = s.from_far_end(&setup, 0); + for frame in plan.to_caller.iter().chain(plan.to_far_end.iter()) { + assert!( + std::str::from_utf8(frame).is_ok(), + "an emitted frame is text" + ); + } +} diff --git a/crates/busbar-plane-streaming/src/tests/sessions_tests.rs b/crates/busbar-plane-streaming/src/tests/sessions_tests.rs new file mode 100644 index 0000000000..031cc527ee --- /dev/null +++ b/crates/busbar-plane-streaming/src/tests/sessions_tests.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! Tests for `crates/busbar-plane-streaming/src/sessions.rs`. + +use super::*; + +fn json(v: serde_json::Value) -> Vec { + serde_json::to_vec(&v).expect("json") +} + +/// The telephony door opens the one Twilio reader's session. RED arm: a telephony session opened +/// without the envelope reads a Twilio frame as nothing, so the caller's audio reaches no one. +#[test] +fn the_telephony_door_opens_a_session_behind_the_twilio_envelope() { + let locked = SessionConfig::default(); + let mut s = open(Door::Twilio, &locked, 0, None, "call-1", "ref").expect("a session door"); + let start = json( + serde_json::json!({"event":"start","start":{"streamSid":"MZ1", + "callSid":"CA1","mediaFormat":{"encoding":"audio/x-mulaw","sampleRate":8000,"channels":1}}}), + ); + let _ = s.from_caller(&start); + let media = json(serde_json::json!({"event":"media","streamSid":"MZ1", + "media":{"payload": busbar_contract::media::base64_encode(&[0x7f; 80])}})); + let plan = s.from_caller(&media); + assert_eq!( + plan.to_far_end.len(), + 1, + "the caller's audio reaches the far end" + ); + assert!(String::from_utf8_lossy(&plan.to_far_end[0]).contains("input_audio_buffer.append")); + + // The RED arm: the same frames on the browser sideband, where no envelope is expected. + let mut bare = open(Door::Sideband, &locked, 0, None, "call-2", "ref").expect("a session"); + let _ = bare.from_caller(&start); + assert!(bare.from_caller(&media).to_far_end.is_empty()); +} + +#[test] +fn a_one_request_door_opens_no_session() { + let locked = SessionConfig::default(); + for door in [Door::Mint, Door::Sdp, Door::Metadata] { + assert!( + open(door, &locked, 0, None, "x", "ref").is_none(), + "{door:?}" + ); + } + assert!(matches!( + open(Door::Gemini, &locked, 0, None, "g", "ref"), + Some(Session::Gemini(_)) + )); +} diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs new file mode 100644 index 0000000000..2da9bb596b --- /dev/null +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE STREAMING PLANE'S DOOR, BOTH WAYS (#2 (4)/(5): the plugin's own both-ways witness, over the +//! real loader): the same door compiled in (`busbar_plane_streaming::door::door`, through +//! `load_linked`) and dropped in (this crate's `streaming_door` example `cdylib`, which `cargo test` +//! builds, through `load_dropped`), driven by ONE script over the plane kind. Every answer is judged +//! by the kind's own checks on the way; the two transcripts must be identical, and each is pinned. + +use std::mem::zeroed; +use std::sync::Arc; + +use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome, BLOB_JSON}; +use busbar_contract::abi::mechanism::lifecycle::{ + slot as life, CancelIn, CancelOut, GenIn, RefreshIn, TickIn, TickOut, ValidateIn, +}; +use busbar_contract::abi::mechanism::{KindCode, MECHANISM_VERSION}; +use busbar_contract::abi::plane::{ + slot, ArriveIn, ArriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, + CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, +}; +use busbar_plane_streaming::door; +use busbar_plugin_loader::dispatch::kinds::plane::Plane; +use busbar_plugin_loader::dispatch::{ + in_head, load_dropped, load_linked, out_head, Bind, DispatchConfig, Dispatcher, Frame, + ManifestFacts, NoSink, Plugin, +}; + +fn z() -> T { + // SAFETY: every `in`/`out` here is plain C data; all-zero is a valid value of each. + unsafe { zeroed() } +} + +fn bind(d: &Dispatcher) -> Bind { + Bind { + max_inflight_cap: 8, + sink: Arc::new(NoSink), + dispatcher: d.adopter(), + } +} + +fn json(b: &'static [u8]) -> Blob { + Blob { + ptr: b.as_ptr(), + len: b.len(), + fmt: BLOB_JSON, + flags: 0, + } +} + +fn text(s: &'static str) -> AbiStr { + AbiStr { + ptr: s.as_ptr(), + len: s.len(), + } +} + +fn read(s: AbiStr) -> String { + if s.ptr.is_null() { + return String::new(); + } + // SAFETY: the plugin's text, valid while its generation is live. + String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(s.ptr, s.len) }).into_owned() +} + +/// A published snapshot, read while its generation is live: its generation, its claims +/// (`verb target carrier exact?`) and the audience and metadata it binds. +fn snapshot(p: *const PlaneSnapshot) -> String { + assert!(!p.is_null(), "a READY open/refresh publishes a snapshot"); + // SAFETY: the plugin's generation data, valid until `retire` of its generation. + let s = unsafe { &*p }; + let claims: Vec = (0..s.claims_len) + .map(|i| { + // SAFETY: the snapshot names `claims_len` claims. + let c = unsafe { &*s.claims.add(i) }; + format!( + "{} {} {}{}{}", + read(c.verb), + read(c.target), + read(c.carrier), + if c.flags & CLAIM_EXACT != 0 { + " exact" + } else { + "" + }, + if c.flags & CLAIM_OPEN != 0 { + " open" + } else { + "" + } + ) + }) + .collect(); + format!( + "gen={} claims=[{}] audience={} metadata={} routes={}", + s.generation, + claims.join(", "), + read(s.audience), + read(s.resource_metadata), + s.admin_routes_len + ) +} + +fn linked(d: &Dispatcher) -> Plugin { + load_linked::(door::door, bind(d)).expect("the linked streaming door loads") +} + +/// The example `cdylib` in this target dir (`cargo test` builds examples). Under CI a missing +/// artifact is a failure, never a skip. +fn dropped(d: &Dispatcher) -> Option> { + let exe = std::env::current_exe().ok()?; + let path = exe.parent()?.parent()?.join("examples").join(format!( + "{}streaming_door{}", + std::env::consts::DLL_PREFIX, + std::env::consts::DLL_SUFFIX + )); + assert!( + path.exists() || std::env::var_os("CI").is_none(), + "the streaming_door example cdylib is not built under CI; a both-ways proof must not skip" + ); + let facts = ManifestFacts { + mechanism_version: MECHANISM_VERSION, + kind: KindCode::Plane, + kind_abi: KindCode::Plane.abi_version(), + }; + path.exists().then(|| { + load_dropped::(&path, &facts, bind(d)).expect("the dropped streaming door loads") + }) +} + +fn validate(p: &Plugin, settings: &'static [u8]) -> Outcome { + let mut v = Frame::new( + ValidateIn { + head: in_head(), + settings: json(settings), + }, + out_head(), + ); + p.call(life::VALIDATE, &mut v).outcome +} + +fn open( + p: &Plugin, + generation: u64, + settings: &'static [u8], + public_url: Option<&'static str>, +) -> (Outcome, String) { + let mut i: PlaneOpenIn = z(); + i.open.head = in_head(); + i.open.generation = generation; + i.open.settings = json(settings); + if let Some(u) = public_url { + i.public_url = text(u); + } + let mut o: PlaneOpenOut = z(); + o.open.head = out_head(); + let mut f = Frame::new(i, o); + let c = p.call(life::OPEN, &mut f); + let snap = if c.outcome == Outcome::Ready { + snapshot(f.out.snapshot) + } else { + String::new() + }; + (c.outcome, snap) +} + +/// THE SCRIPT: the lifecycle over two generations, then one unit offered to the door. +fn script(p: &Plugin, public_url: Option<&'static str>) -> Vec { + let mut t = Vec::new(); + t.push(format!( + "validate unknown key {:?}", + validate(p, br#"{"nonsense_key":1}"#) + )); + t.push(format!( + "validate zero ceiling {:?}", + validate(p, br#"{"session_max_secs":0}"#) + )); + t.push(format!("validate empty {:?}", validate(p, b""))); + t.push(format!( + "validate ceiling {:?}", + validate(p, br#"{"session_max_secs":1800}"#) + )); + + let (outcome, snap) = open(p, 1, br#"{"session_max_secs":1800}"#, public_url); + t.push(format!("open {outcome:?} {snap}")); + + let mut r = Frame::new( + RefreshIn { + head: in_head(), + generation: 2, + settings: json(b"{}"), + secrets: std::ptr::null(), + secrets_len: 0, + }, + PlaneRefreshOut { + head: out_head(), + snapshot: std::ptr::null(), + }, + ); + let c = p.call(life::REFRESH, &mut r); + t.push(format!( + "refresh {:?} {}", + c.outcome, + snapshot(r.out.snapshot) + )); + + let mut g = Frame::new( + GenIn { + head: in_head(), + generation: 1, + }, + out_head(), + ); + t.push(format!( + "retire 1 {:?}", + p.call(life::RETIRE, &mut g).outcome + )); + + for claim in [3, 5, 9] { + let mut a: Frame = Frame::new(z(), z()); + a.input.head = in_head(); + a.input.claim = claim; + a.out.head = out_head(); + let c = p.call(slot::ARRIVE, &mut a); + t.push(format!( + "arrive {claim} {:?} dialect={} op={} principal={}", + c.outcome, a.out.dialect, a.out.op_class, a.out.principal_need + )); + } + + let mut k: Frame = Frame::new(z(), z()); + k.input.head = in_head(); + k.input.now_ns = 5; + k.out.head = out_head(); + let c = p.call(life::TICK, &mut k); + t.push(format!("tick {:?} next={}", c.outcome, k.out.next_tick_ns)); + + let mut x: Frame = Frame::new(z(), z()); + x.input.head = in_head(); + x.out.head = out_head(); + let c = p.call(life::CANCEL, &mut x); + t.push(format!( + "cancel {:?} failed={}", + c.outcome, + x.out.disposition == CANCEL_FAILED + )); + t +} + +const PUBLIC: &str = "https://gw.example.com/ignored?q=1#f"; + +fn expected(public: bool) -> Vec { + let doors = "POST /v1/realtime/client_secrets http exact, POST /v1/realtime/calls http exact, \ + GET /v1/realtime/sideband/{call_id} ws, GET /v1/realtime/gemini/{call_id} ws, \ + GET /twilio/{call_id} ws, \ + GET /.well-known/oauth-protected-resource/v1/realtime http exact open"; + let (claims, audience, metadata) = if public { + ( + doors, + "https://gw.example.com/v1/realtime", + "https://gw.example.com/.well-known/oauth-protected-resource/v1/realtime", + ) + } else { + ("", "", "") + }; + let snap = |g: u64| { + format!("gen={g} claims=[{claims}] audience={audience} metadata={metadata} routes=0") + }; + vec![ + "validate unknown key Refused".into(), + "validate zero ceiling Refused".into(), + "validate empty Ready".into(), + "validate ceiling Ready".into(), + format!("open Ready {}", snap(1)), + format!("refresh Ready {}", snap(2)), + "retire 1 Ready".into(), + "arrive 3 Ready dialect=1 op=0 principal=1".into(), + "arrive 5 Ready dialect=0 op=0 principal=0".into(), + "arrive 9 Refused dialect=0 op=0 principal=0".into(), + "tick Ready next=0".into(), + "cancel Ready failed=true".into(), + ] +} + +#[test] +fn the_linked_door_states_the_planes_doors_under_its_public_url() { + let d = Dispatcher::new(DispatchConfig::default()); + assert_eq!(script(&linked(&d), Some(PUBLIC)), expected(true)); +} + +#[test] +fn a_deployment_with_no_public_url_is_claimed_by_no_door() { + let d = Dispatcher::new(DispatchConfig::default()); + assert_eq!(script(&linked(&d), None), expected(false)); +} + +#[test] +fn the_dropped_door_answers_as_the_linked_door_does() { + let d = Dispatcher::new(DispatchConfig::default()); + for url in [Some(PUBLIC), None] { + // A fresh instance of each door per script: the script opens its instance, and an open + // instance refuses a second open. + let Some(dropped) = dropped(&d) else { + eprintln!("skip: the streaming_door example cdylib is not built"); + return; + }; + assert_eq!( + script(&dropped, url), + script(&linked(&d), url), + "the two doors must answer identically" + ); + } +} + +#[test] +fn the_tail_states_what_the_served_plane_declares() { + // The billable classes a session counts in, the per-session fee and the `session` grant kind: + // the served plane's own declaration, read off the tail the door states. + let t = door::TAIL; + assert_eq!(read(t.scope), "session"); + assert_eq!(read(t.audit_kind), "streaming_session"); + // SAFETY: the tail's `'static` lists. + let classes: Vec = + unsafe { std::slice::from_raw_parts(t.billable_classes, t.billable_classes_len) } + .iter() + .map(|c| format!("{}/{}", read(c.class), read(c.family))) + .collect(); + assert_eq!( + classes, + [ + "audio_tokens_in/token", + "audio_tokens_out/token", + "text_tokens_in/token", + "text_tokens_out/token", + "audio_seconds_in/duration", + "tool_calls/count", + ] + ); + // SAFETY: as above. + let fees: Vec = unsafe { std::slice::from_raw_parts(t.fee_units, t.fee_units_len) } + .iter() + .map(|f| read(*f)) + .collect(); + assert_eq!(fees, ["per_session"]); + busbar_contract::abi::plane::check::check_tail(t) + .expect("the kind's own check admits the tail"); +} diff --git a/crates/busbar-voice/src/runtime/mod.rs b/crates/busbar-voice/src/runtime/mod.rs index 90a80f78d5..27f77987fb 100644 --- a/crates/busbar-voice/src/runtime/mod.rs +++ b/crates/busbar-voice/src/runtime/mod.rs @@ -18,9 +18,9 @@ pub mod session; /// ([`tools::ClientRelay`]: this node serves no tool, so every call is relayed to the caller). The /// echo executor the batteries drive is a test double, present only in test builds. pub mod tools { - pub use busbar_plane_streaming::tools::{ClientRelay, ToolExecutor}; #[cfg(any(test, feature = "test-support"))] pub use crate::testkit::echo::EchoToolExecutor; + pub use busbar_plane_streaming::tools::{ClientRelay, ToolExecutor}; } pub use carrier::Carrier; @@ -37,9 +37,9 @@ pub use scope::{SessionHandle, VoiceSessionRow}; pub use session::{ serve_to_teardown, serve_with_sweep, Outbound, SessionCore, UplinkForwarder, VoiceSession, }; -pub use tools::{ClientRelay, ToolExecutor}; #[cfg(any(test, feature = "test-support"))] pub use tools::EchoToolExecutor; +pub use tools::{ClientRelay, ToolExecutor}; use busbar_kernel::plane::handle_engine::DurableHandleEngine; use std::sync::Arc; @@ -152,11 +152,8 @@ pub fn build_runtime( .cloned() .unwrap_or_default(); Arc::new( - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(ClientRelay), - ) - .with_streams(&streams), + VoiceRuntime::new(Arc::new(DurableHandleEngine::new()), Arc::new(ClientRelay)) + .with_streams(&streams), ) } From 72094f0ae9af6d0e7e45ad080ee6eb94cf74c35b Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 05/82] busbar-plane-streaming: one request unit as the route pump drives it; each door a guest-list line; the ladder RED (fold S3c-2a, section 6 step 3; 6 carried commits) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- busbar-plane-streaming: one request unit as the route pump drives it; the mint, the SDP offer and the metadata document answer each piece (fold S3c-2a) The route pump pushes an ATTEMPT, then the caller's body, then the far end's answer. RequestUnit answers each in the plane's own vocabulary: - The mint's ATTEMPT carries the whole request. The served path reads no caller body for the mint (its ttl is the default), so the caller's pieces answer nothing. - The SDP ATTEMPT is the request head. The offer is relayed to the far end as it arrives. - The far end's answer is gathered and answered once, on its last piece: mint_reply or sdp_reply. The Location comes from the kept response head, and a new ATTEMPT forgets the previous far end. - The metadata document is answered on the first piece and dials nothing. Writing the answers into the host's buffers is the door's slot (next slice, over K2's piece layout: caller_ref, the kept head fields). -- busbar-plane-streaming tests: rustfmt the Twilio claim RED ported from the deleted conformance file -- request_unit: the far end's piece handler is not a from_* constructor (clippy wrong_self_convention); rustfmt -- busbar-plane-streaming: each door is a guest-list line with its upgrade, default inbound auth and refusal dialect; the ladder RED (section 6 brief, step 3) The ROUTES table now states each line the way the spec's guest list reads it (BUSBAR-1.6.0.md section 6, 'Auth points and guest lists', step 3): - method set, path (exact or {…} pattern; no prefix and no predicate, since no two doors share a path), and transport; - upgrade for the three sockets: auth at Head on the upgrade request, then the connection goes to ws; - the default inbound style (bearer, webhook-signature for the telephony line, none for the metadata document); - refusal_dialect. Door::dialect and Door::is_open now read the door's line, so there is one source. RED (src/tests/ladder_tests.rs): - every served route (the mount table plus the one telephony claim) resolves to the same (dialect, door, default auth) under the spec's match; - fifteen requests no served route took resolve to no line, among them the deleted /v1/realtime/telephony leg and the /v1/audio paths; - each line's own witness reaches it, and no two lines tie. The match here is written from the spec. It becomes the kernel's guest-list match once INBOUND-LISTEN's abi/transport match vocabulary lands. The published Claim flags are unchanged. -- ladder RED: a served path under another method is 405 with the line's Allow, not no-route (ARCHITECT: the OWNER-approved kernel rule keeps predev's 405) Six wrong-method cases move out of the no-route list and into their own RED. Each resolves to no line, and its Allow is exactly the method of the one line whose path matches. The no-route list keeps only paths that no line matches. -- rustfmt: ladder_tests.rs SERVED const tuples Applied verbatim from the proof job fmt diff (fs3); not re-run after the patch. (cherry picked from commit 9be1f2ce614946a44474f25d81506d5e2b2d44eb) --- crates/busbar-plane-streaming/src/door.rs | 107 +++++++-- crates/busbar-plane-streaming/src/driven.rs | 20 +- crates/busbar-plane-streaming/src/lib.rs | 1 + .../src/request_unit.rs | 190 +++++++++++++++ .../src/tests/ladder_tests.rs | 217 ++++++++++++++++++ .../busbar-plane-streaming/src/tests/mod.rs | 6 +- .../src/tests/request_unit_tests.rs | 181 +++++++++++++++ 7 files changed, 688 insertions(+), 34 deletions(-) create mode 100644 crates/busbar-plane-streaming/src/request_unit.rs create mode 100644 crates/busbar-plane-streaming/src/tests/ladder_tests.rs create mode 100644 crates/busbar-plane-streaming/src/tests/request_unit_tests.rs diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 159d4397d1..c104bc8020 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -239,81 +239,134 @@ pub const TAIL: &PlaneTail = &PlaneTail { trust_keys_len: 0, }; -/// One door a live session is opened through. +/// The inbound auth style of the metadata door: none, it is read without a credential. +pub const NO_AUTH: &str = "none"; + +/// One door a live session is opened through: one line of this plane on a listener's guest list +/// (`BUSBAR-1.6.0.md` section 6, "Auth points and guest lists", step 3). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Route { - /// The verb. + /// The method set: every door takes one method. pub verb: &'static str, - /// The target; `{…}` is one path segment. + /// The path: exact, or a pattern where `{…}` is one path segment. No door is a prefix, and none + /// carries a field-presence predicate: no two doors share a path. pub target: &'static str, - /// The transport claim it arrives over. + /// The transport the line's bytes are read by. pub carrier: &'static str, + /// `true` for an upgrade line: the listener's transport runs the line's auth at `Head` on the + /// upgrade request, then the connection is handed to [`Route::carrier`]. + pub upgrade: bool, + /// The default inbound auth style: applied only where the operator's config gives the line no + /// auth. + pub auth: &'static str, + /// The dialect the line's units speak, by its index in the tail's dialects. + pub dialect: u32, + /// The dialect a refusal on this line is rendered in, by the same index. + pub refusal_dialect: u32, +} + +impl Route { /// Read without a credential. - pub open: bool, + #[must_use] + pub const fn open(&self) -> bool { + let a = self.auth.as_bytes(); + let n = NO_AUTH.as_bytes(); + if a.len() != n.len() { + return false; + } + let mut i = 0; + while i < a.len() { + if a[i] != n[i] { + return false; + } + i += 1; + } + true + } + + /// `true` for an exact path, `false` for a pattern. + #[must_use] + pub const fn exact(&self) -> bool { + let b = self.target.as_bytes(); + let mut j = 0; + while j < b.len() { + if b[j] == b'{' { + return false; + } + j += 1; + } + true + } } /// The doors, in the order a snapshot claims them: the ephemeral-secret mint and the SDP offer (one /// request each), the browser's sideband socket, the Gemini Live socket, the telephony socket, and /// the protected-resource metadata document a refused caller is pointed at (read without a -/// credential). +/// credential). The three sockets are upgrade lines. pub const ROUTES: &[Route] = &[ Route { verb: "POST", target: "/v1/realtime/client_secrets", carrier: HTTP_TRANSPORT, - open: false, + upgrade: false, + auth: KEY_AUTH, + dialect: 0, + refusal_dialect: 0, }, Route { verb: "POST", target: "/v1/realtime/calls", carrier: HTTP_TRANSPORT, - open: false, + upgrade: false, + auth: KEY_AUTH, + dialect: 0, + refusal_dialect: 0, }, Route { verb: "GET", target: "/v1/realtime/sideband/{call_id}", carrier: WS_TRANSPORT, - open: false, + upgrade: true, + auth: KEY_AUTH, + dialect: 0, + refusal_dialect: 0, }, Route { verb: "GET", target: "/v1/realtime/gemini/{call_id}", carrier: WS_TRANSPORT, - open: false, + upgrade: true, + auth: KEY_AUTH, + dialect: 1, + refusal_dialect: 1, }, Route { verb: "GET", target: "/twilio/{call_id}", carrier: WS_TRANSPORT, - open: false, + upgrade: true, + auth: SIGNATURE_AUTH, + dialect: 2, + refusal_dialect: 2, }, Route { verb: "GET", target: METADATA_PATH, carrier: HTTP_TRANSPORT, - open: true, + upgrade: false, + auth: NO_AUTH, + dialect: 0, + refusal_dialect: 0, }, ]; const fn claim(i: usize) -> Claim { let r = ROUTES[i]; - let exact = { - let b = r.target.as_bytes(); - let mut j = 0; - let mut templated = false; - while j < b.len() { - if b[j] == b'{' { - templated = true; - } - j += 1; - } - !templated - }; Claim { verb: abi_str(r.verb), target: abi_str(r.target), carrier: abi_str(r.carrier), - flags: if exact { CLAIM_EXACT } else { 0 } | if r.open { CLAIM_OPEN } else { 0 }, + flags: if r.exact() { CLAIM_EXACT } else { 0 } | if r.open() { CLAIM_OPEN } else { 0 }, _reserved: 0, } } @@ -597,3 +650,7 @@ busbar_contract::plugin_door! { hydrate: Safe, start: Safe, project: Safe, }, } + +#[cfg(test)] +#[path = "tests/ladder_tests.rs"] +mod ladder_tests; diff --git a/crates/busbar-plane-streaming/src/driven.rs b/crates/busbar-plane-streaming/src/driven.rs index 6e0277da7b..443a70f93c 100644 --- a/crates/busbar-plane-streaming/src/driven.rs +++ b/crates/busbar-plane-streaming/src/driven.rs @@ -59,20 +59,22 @@ impl Door { } } - /// The door's dialect, by its index in the tail's dialects. + /// The door's line on the guest list: its claim (`crate::door::ROUTES`), in claim order. + #[must_use] + pub const fn line(self) -> crate::door::Route { + crate::door::ROUTES[self as usize] + } + + /// The door's dialect, by its index in the tail's dialects: its line's. #[must_use] pub const fn dialect(self) -> u32 { - match self { - Door::Mint | Door::Sdp | Door::Sideband | Door::Metadata => 0, - Door::Gemini => 1, - Door::Twilio => 2, - } + self.line().dialect } /// `true` for the door read without a credential. #[must_use] pub const fn is_open(self) -> bool { - matches!(self, Door::Metadata) + self.line().open() } /// `true` for a door that opens a live session rather than answering one request. @@ -291,7 +293,9 @@ pub struct Reply { /// The status the served plane answers a failed mint with. pub const MINT_FAILED_STATUS: u16 = 502; -fn mint_failed(reason: &str) -> Reply { +/// The served plane's 502 for a mint that failed for `reason`. +#[must_use] +pub fn mint_failed(reason: &str) -> Reply { Reply { status: MINT_FAILED_STATUS, fields: Vec::new(), diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index 7622bab92d..dd26dcc390 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -60,6 +60,7 @@ pub mod meta; pub mod open_calls; pub mod provider; pub mod register; +pub mod request_unit; pub mod session; pub mod session_params; pub mod session_pump; diff --git a/crates/busbar-plane-streaming/src/request_unit.rs b/crates/busbar-plane-streaming/src/request_unit.rs new file mode 100644 index 0000000000..cc06f81598 --- /dev/null +++ b/crates/busbar-plane-streaming/src/request_unit.rs @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! ONE REQUEST UNIT AS THE KERNEL'S ROUTE PUMP DRIVES IT (`BUSBAR-1.6.0.md` Part 3, section 12): +//! the pieces of a one-request door (the ephemeral-secret mint, the SDP offer, the metadata +//! document), in the order the pump pushes them, and what the plane answers each with. +//! +//! Per attempt the pump pushes an ATTEMPT piece first, then the caller's body (re-pushed on every +//! attempt), then the far end's answer. The plane answers: +//! +//! | piece | mint | SDP offer | metadata | +//! |---|---|---|---| +//! | ATTEMPT | the mint request, whole | the request head; its body follows | the document | +//! | the caller's body | nothing (the mint reads no caller body) | relayed to the far end as it arrived | the document, if not yet answered | +//! | the far end's answer | gathered; on its last piece, the caller's answer | the same | refused: it dials nothing | +//! +//! Every answer is plain data in the plane's own vocabulary; writing it into the host's buffers is +//! the door's job. A new ATTEMPT forgets what the previous attempt's far end said. + +use crate::codec::ir::config::SessionConfig; +use crate::driven::{ + metadata_reply, mint_failed, mint_reply, sdp_reply, Attempt, Door, Reply, Steps, FIELD_LOCATION, +}; + +/// Who pushed a piece. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum From { + /// The caller: the request body. + Caller, + /// The far end: its answer. + FarEnd, + /// The kernel: an ATTEMPT, numbered from `1`. + Kernel(u32), +} + +/// One piece, as the door read it off the crossing. +#[derive(Debug, Clone, Copy)] +pub struct Piece<'a> { + /// Who pushed it. + pub from: From, + /// Its bytes. + pub bytes: &'a [u8], + /// On the far end's first piece: the status it answered. + pub status: Option, + /// No piece of this `from` follows. + pub last: bool, + /// On the far end's first piece: the response head fields the plane's need keeps, names + /// lower-case. + pub head: &'a [(&'a [u8], &'a [u8])], +} + +/// The plane's answer to one piece. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Answer { + /// Nothing to emit yet. + Nothing, + /// The request this attempt sends the far end; a body that follows arrives as [`Answer::ToFarEnd`]. + Attempt(Attempt), + /// More of the request's body, bound for the far end. + ToFarEnd(Vec), + /// The caller's whole answer: the unit is done. + ToCaller(Reply), + /// The piece is one this door never takes. + Refused, +} + +/// One unit on a one-request door. +#[derive(Debug, Clone)] +pub struct RequestUnit { + door: Door, + locked: SessionConfig, + caller_ref: Option, + audience: String, + status: Option, + location: Option, + far: Vec, + answered: bool, +} + +impl RequestUnit { + /// A unit on `door`, minting over the `locked` session params, naming the caller by + /// `caller_ref` (the kernel's reference, never the principal) and answering the metadata + /// document for `audience`. `None` for a door that opens a live session. + #[must_use] + pub fn new( + door: Door, + locked: SessionConfig, + caller_ref: Option, + audience: String, + ) -> Option { + if door.is_session() { + return None; + } + Some(Self { + door, + locked, + caller_ref, + audience, + status: None, + location: None, + far: Vec::new(), + answered: false, + }) + } + + /// Its door. + #[must_use] + pub const fn door(&self) -> Door { + self.door + } + + /// `true` once the caller's answer has been given. + #[must_use] + pub const fn answered(&self) -> bool { + self.answered + } + + fn answer(&mut self, reply: Reply) -> Answer { + self.answered = true; + Answer::ToCaller(reply) + } + + /// The plane's answer to `piece`. + pub fn on_piece(&mut self, piece: Piece<'_>) -> Answer { + if self.answered { + return Answer::Nothing; + } + match (self.door, piece.from) { + (Door::Metadata, From::Kernel(_) | From::Caller) => { + let reply = metadata_reply(&self.audience); + self.answer(reply) + } + (Door::Metadata, From::FarEnd) => Answer::Refused, + (_, From::Kernel(0)) => Answer::Refused, + (_, From::Kernel(_)) => { + self.status = None; + self.location = None; + self.far.clear(); + match self + .door + .route(&self.locked, self.caller_ref.as_deref(), &[]) + { + Ok(Some(attempt)) => Answer::Attempt(attempt), + Ok(None) => Answer::Refused, + Err(reason) => { + let reply = mint_failed(&reason); + self.answer(reply) + } + } + } + (Door::Sdp, From::Caller) => { + if piece.bytes.is_empty() { + Answer::Nothing + } else { + Answer::ToFarEnd(piece.bytes.to_vec()) + } + } + (_, From::Caller) => Answer::Nothing, + (_, From::FarEnd) => self.far_end_piece(piece), + } + } + + fn far_end_piece(&mut self, piece: Piece<'_>) -> Answer { + if let Some(status) = piece.status { + self.status = Some(status); + self.location = piece + .head + .iter() + .find(|(name, _)| name.eq_ignore_ascii_case(FIELD_LOCATION.as_bytes())) + .and_then(|(_, value)| std::str::from_utf8(value).ok()) + .map(str::to_owned); + } + self.far.extend_from_slice(piece.bytes); + if !piece.last { + return Answer::Nothing; + } + let Some(status) = self.status else { + return Answer::Refused; + }; + let reply = match self.door { + Door::Mint => mint_reply(status, &self.far), + _ => sdp_reply(status, self.location.as_deref(), &self.far), + }; + self.answer(reply) + } +} + +#[cfg(test)] +#[path = "tests/request_unit_tests.rs"] +mod tests; diff --git a/crates/busbar-plane-streaming/src/tests/ladder_tests.rs b/crates/busbar-plane-streaming/src/tests/ladder_tests.rs new file mode 100644 index 0000000000..9f55edd912 --- /dev/null +++ b/crates/busbar-plane-streaming/src/tests/ladder_tests.rs @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE LADDER RED for `crates/busbar-plane-streaming/src/door.rs`: the plane's lines on a guest +//! list reproduce the served plane's dispatch ladder exactly (`BUSBAR-1.6.0.md` section 6, "Auth +//! points and guest lists", step 3). Every served route resolves to the same (dialect, door), and +//! a request no served route took resolves to no line. A request whose path a line matches under +//! another method is answered 405 with that line's Allow, as the served plane answered it. +//! +//! The match here is the spec's, stated in the test: a line matches when its method set holds the +//! method and its path does (exact bytes, or a pattern where `{…}` is exactly one non-empty +//! segment); an exact path ranks above a pattern, and two matching lines at equal rank are the +//! ambiguity boot refuses. + +use super::*; +use crate::driven::Door; + +/// The rank of a line whose path matches `path`; `None` when it does not match. +fn path_rank(line: &Route, path: &str) -> Option { + if line.exact() { + return (line.target == path).then_some(2); + } + let want: Vec<&str> = line.target.split('/').collect(); + let got: Vec<&str> = path.split('/').collect(); + if want.len() != got.len() { + return None; + } + let all = want.iter().zip(&got).all(|(w, g)| { + if w.starts_with('{') && w.ends_with('}') { + !g.is_empty() + } else { + w == g + } + }); + all.then_some(1) +} + +/// The rank of a line that matches (`method`, `path`); `None` when it does not match. +fn rank(line: &Route, method: &str, path: &str) -> Option { + if line.verb != method { + return None; + } + path_rank(line, path) +} + +/// The methods the lines whose path matches `path` take: the 405's Allow, empty when none does. +fn allow(path: &str) -> Vec<&'static str> { + ROUTES + .iter() + .filter(|l| path_rank(l, path).is_some()) + .map(|l| l.verb) + .collect() +} + +/// The line `(method, path)` resolves to, by index; `Err` on an equal-rank ambiguity. +fn resolve(method: &str, path: &str) -> Result, String> { + let mut best: Option<(u8, usize)> = None; + let mut tied = false; + for (i, line) in ROUTES.iter().enumerate() { + let Some(r) = rank(line, method, path) else { + continue; + }; + match best { + Some((b, _)) if r < b => {} + Some((b, _)) if r == b => tied = true, + _ => { + best = Some((r, i)); + tied = false; + } + } + } + if tied { + return Err(format!("{method} {path} matches two lines at equal rank")); + } + Ok(best.map(|(_, i)| i)) +} + +/// The served plane's routes, as its mount table and its one telephony claim answer them. +const SERVED: &[(&str, &str, Door, u32, &str)] = &[ + ( + "POST", + "/v1/realtime/client_secrets", + Door::Mint, + 0, + KEY_AUTH, + ), + ("POST", "/v1/realtime/calls", Door::Sdp, 0, KEY_AUTH), + ( + "GET", + "/v1/realtime/sideband/rtc_abc", + Door::Sideband, + 0, + KEY_AUTH, + ), + ( + "GET", + "/v1/realtime/gemini/call-1", + Door::Gemini, + 1, + KEY_AUTH, + ), + ("GET", "/twilio/CA123", Door::Twilio, 2, SIGNATURE_AUTH), + ("GET", METADATA_PATH, Door::Metadata, 0, NO_AUTH), +]; + +#[test] +fn every_served_route_resolves_to_the_same_dialect_and_door() { + for &(method, path, door, dialect, auth) in SERVED { + let i = resolve(method, path) + .expect("no ambiguity") + .unwrap_or_else(|| panic!("{method} {path} resolves to no line")); + assert_eq!(Door::of(i as u32), Some(door), "{method} {path}"); + assert_eq!(ROUTES[i].dialect, dialect, "{method} {path}"); + assert_eq!(ROUTES[i].refusal_dialect, dialect, "{method} {path}"); + assert_eq!(ROUTES[i].auth, auth, "{method} {path}"); + assert_eq!(door.dialect(), dialect, "the door's dialect is its line's"); + } + assert_eq!(SERVED.len(), ROUTES.len(), "every line is a served route"); +} + +#[test] +fn a_request_no_served_route_took_resolves_to_no_line() { + for (method, path) in [ + ("POST", "/v1/realtime"), + ("GET", "/v1/realtime"), + ("GET", "/v1/realtime/sideband"), + ("GET", "/v1/realtime/sideband/"), + ("GET", "/v1/realtime/sideband/a/b"), + ("GET", "/v1/realtime/telephony/CA123"), + ("GET", "/twilio"), + ("GET", "/twilio/"), + ("GET", "/twilio/a/b"), + ("GET", "/twiliofoo/x"), + ("POST", "/v1/audio/transcriptions"), + ] { + assert_eq!(resolve(method, path), Ok(None), "{method} {path}"); + assert!( + allow(path).is_empty(), + "{method} {path} is no-route, not 405" + ); + } +} + +#[test] +fn a_served_path_under_another_method_is_405_with_the_lines_allow() { + for (method, path, allowed) in [ + ("GET", "/v1/realtime/client_secrets", "POST"), + ("GET", "/v1/realtime/calls", "POST"), + ("POST", "/v1/realtime/sideband/rtc_abc", "GET"), + ("POST", "/v1/realtime/gemini/call-1", "GET"), + ("POST", "/twilio/CA123", "GET"), + ("POST", METADATA_PATH, "GET"), + ] { + assert_eq!( + resolve(method, path), + Ok(None), + "{method} {path} takes no line" + ); + assert_eq!( + allow(path), + [allowed], + "{method} {path} is 405, Allow: {allowed}" + ); + } +} + +#[test] +fn the_sockets_are_upgrade_lines_and_the_one_request_doors_are_not() { + for (i, line) in ROUTES.iter().enumerate() { + let door = Door::of(i as u32).expect("a door per line"); + assert_eq!(line.upgrade, door.is_session(), "{}", line.target); + assert_eq!( + line.carrier, + if line.upgrade { + WS_TRANSPORT + } else { + HTTP_TRANSPORT + }, + "an upgrade line hands the connection to the socket transport" + ); + } +} + +#[test] +fn no_two_lines_tie_and_each_lines_own_witness_reaches_it() { + for (i, line) in ROUTES.iter().enumerate() { + let witness: String = line + .target + .split('/') + .map(|seg| if seg.starts_with('{') { "w" } else { seg }) + .collect::>() + .join("/"); + assert_eq!( + resolve(line.verb, &witness), + Ok(Some(i)), + "{} {}", + line.verb, + witness + ); + } +} + +#[test] +fn only_the_metadata_line_is_open_and_the_telephony_line_is_signed() { + let open: Vec<_> = ROUTES + .iter() + .filter(|r| r.open()) + .map(|r| r.target) + .collect(); + assert_eq!(open, [METADATA_PATH]); + let signed: Vec<_> = ROUTES + .iter() + .filter(|r| r.auth == SIGNATURE_AUTH) + .map(|r| r.target) + .collect(); + assert_eq!(signed, ["/twilio/{call_id}"]); +} diff --git a/crates/busbar-plane-streaming/src/tests/mod.rs b/crates/busbar-plane-streaming/src/tests/mod.rs index f246663ff3..f8b5dfa576 100644 --- a/crates/busbar-plane-streaming/src/tests/mod.rs +++ b/crates/busbar-plane-streaming/src/tests/mod.rs @@ -35,7 +35,11 @@ mod selectors { .iter() .filter(|c| c.dialect == Dialect::TwilioMediaStreams) .collect(); - assert_eq!(twilio.len(), 1, "exactly one claim names the Twilio dialect"); + assert_eq!( + twilio.len(), + 1, + "exactly one claim names the Twilio dialect" + ); let claim = &twilio[0].claim; assert_eq!( claim.transport, WS_TRANSPORT, diff --git a/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs b/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs new file mode 100644 index 0000000000..12ec472b2d --- /dev/null +++ b/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! Tests for `crates/busbar-plane-streaming/src/request_unit.rs`. + +use super::*; +use crate::broker::{CALLS_PATH, CLIENT_SECRETS_PATH, SAFETY_IDENTIFIER_HEADER}; +use crate::driven::MINT_FAILED_STATUS; + +const AUDIENCE: &str = "https://gw.example/v1/realtime"; + +fn unit(door: Door, caller_ref: Option<&str>) -> RequestUnit { + RequestUnit::new( + door, + SessionConfig::default(), + caller_ref.map(str::to_owned), + AUDIENCE.to_owned(), + ) + .expect("a one-request door") +} + +fn piece(from: From, bytes: &[u8], last: bool) -> Piece<'_> { + Piece { + from, + bytes, + status: None, + last, + head: &[], + } +} + +fn far_first<'a>( + status: u16, + bytes: &'a [u8], + last: bool, + head: &'a [(&'a [u8], &'a [u8])], +) -> Piece<'a> { + Piece { + from: From::FarEnd, + bytes, + status: Some(status), + last, + head, + } +} + +fn reply(a: Answer) -> Reply { + match a { + Answer::ToCaller(r) => r, + other => panic!("expected the caller's answer, got {other:?}"), + } +} + +#[test] +fn a_session_door_is_no_request_unit() { + for door in [Door::Sideband, Door::Gemini, Door::Twilio] { + assert!(RequestUnit::new(door, SessionConfig::default(), None, String::new()).is_none()); + } +} + +#[test] +fn the_mint_attempt_is_whole_and_the_callers_body_is_not_read() { + let mut u = unit(Door::Mint, Some("ab12")); + let Answer::Attempt(a) = u.on_piece(piece(From::Kernel(1), &[], false)) else { + panic!("the ATTEMPT is answered with the mint request"); + }; + assert_eq!((a.verb, a.target), ("POST", CLIENT_SECRETS_PATH)); + assert!(a + .fields + .iter() + .any(|(n, v)| *n == SAFETY_IDENTIFIER_HEADER && v == "ab12")); + assert!(!a.body.is_empty(), "the mint body goes with the ATTEMPT"); + assert_eq!( + u.on_piece(piece(From::Caller, b"{\"ignored\":true}", true)), + Answer::Nothing + ); +} + +#[test] +fn a_mint_with_no_caller_reference_names_no_caller() { + let mut u = unit(Door::Mint, None); + let Answer::Attempt(a) = u.on_piece(piece(From::Kernel(1), &[], false)) else { + panic!("the ATTEMPT is answered with the mint request"); + }; + assert!(a.fields.iter().all(|(n, _)| *n != SAFETY_IDENTIFIER_HEADER)); +} + +#[test] +fn the_far_ends_mint_answer_is_gathered_then_answered_once() { + let mut u = unit(Door::Mint, None); + let _ = u.on_piece(piece(From::Kernel(1), &[], false)); + assert_eq!( + u.on_piece(far_first(200, b"{\"value\":\"ek_1\",", false, &[])), + Answer::Nothing + ); + let r = reply(u.on_piece(piece(From::FarEnd, b"\"expires_at\":7}", true))); + assert_eq!(r.status, 200); + assert_eq!( + serde_json::from_slice::(&r.body).expect("json"), + serde_json::json!({"value": "ek_1", "expires_at_unix": 7}) + ); + assert!(u.answered()); + assert_eq!( + u.on_piece(piece(From::FarEnd, b"late", true)), + Answer::Nothing + ); +} + +#[test] +fn a_refused_mint_is_the_served_502_text() { + let mut u = unit(Door::Mint, None); + let _ = u.on_piece(piece(From::Kernel(1), &[], false)); + let r = reply(u.on_piece(far_first(401, b"nope", true, &[]))); + assert_eq!(r.status, MINT_FAILED_STATUS); + assert_eq!( + r.body, + b"streaming ephemeral-secret mint failed: ephemeral token mint failed: \ + client-secret endpoint returned 401 Unauthorized" + ); +} + +#[test] +fn the_sdp_offer_is_relayed_and_the_answer_keeps_its_location() { + let mut u = unit(Door::Sdp, None); + let Answer::Attempt(a) = u.on_piece(piece(From::Kernel(1), &[], false)) else { + panic!("the ATTEMPT is answered with the SDP request head"); + }; + assert_eq!((a.verb, a.target), ("POST", CALLS_PATH)); + assert!(a.body.is_empty(), "the offer follows as the caller's body"); + assert_eq!( + u.on_piece(piece(From::Caller, b"v=0\r\n", false)), + Answer::ToFarEnd(b"v=0\r\n".to_vec()) + ); + assert_eq!(u.on_piece(piece(From::Caller, b"", true)), Answer::Nothing); + let head: &[(&[u8], &[u8])] = &[(b"location", b"/v1/realtime/calls/rtc_abc")]; + let r = reply(u.on_piece(far_first(201, b"v=0 answer", true, head))); + assert_eq!(r.status, 201); + assert_eq!(r.body, b"v=0 answer"); + assert_eq!(r.rtc_call_id.as_deref(), Some("rtc_abc")); + assert!(r + .fields + .iter() + .any(|(n, v)| *n == FIELD_LOCATION && v == "/v1/realtime/calls/rtc_abc")); +} + +#[test] +fn a_new_attempt_forgets_the_previous_far_end() { + let mut u = unit(Door::Sdp, None); + let _ = u.on_piece(piece(From::Kernel(1), &[], false)); + let head: &[(&[u8], &[u8])] = &[(b"location", b"/calls/rtc_old")]; + let _ = u.on_piece(far_first(503, b"stale", false, head)); + let _ = u.on_piece(piece(From::Kernel(2), &[], false)); + let r = reply(u.on_piece(far_first(201, b"fresh", true, &[]))); + assert_eq!((r.status, r.body.as_slice()), (201, &b"fresh"[..])); + assert_eq!(r.rtc_call_id, None); +} + +#[test] +fn a_far_end_answer_with_no_status_is_refused() { + let mut u = unit(Door::Sdp, None); + let _ = u.on_piece(piece(From::Kernel(1), &[], false)); + assert_eq!(u.on_piece(piece(From::FarEnd, b"x", true)), Answer::Refused); +} + +#[test] +fn a_piece_from_the_kernel_that_is_no_attempt_is_refused() { + let mut u = unit(Door::Mint, None); + assert_eq!( + u.on_piece(piece(From::Kernel(0), &[], false)), + Answer::Refused + ); +} + +#[test] +fn the_metadata_document_is_answered_at_once_and_dials_nothing() { + let mut u = unit(Door::Metadata, None); + let r = reply(u.on_piece(piece(From::Caller, &[], true))); + assert_eq!(r, metadata_reply(AUDIENCE)); + let mut v = unit(Door::Metadata, None); + assert_eq!(v.on_piece(far_first(200, b"", true, &[])), Answer::Refused); +} From 8b5a201941091d8f7477418b27205714861d08af Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 06/82] busbar-voice and busbar-plane-streaming: one import group, ClientRelay answers executes_here; qa: streams|metadata|protected-resource is an accepted difference (3 carried commits) -- busbar-voice: name the streaming plane crate once per import group, not once per item (kind-isolation voice x plane nets down) The re-exports of Outbound and SESSION_CEILING_REASON share one statement, the three session imports share one group, and two doc comments no longer spell the crate path. No behaviour change. -- busbar-plane-streaming: ClientRelay answers ToolExecutor::executes_here, the name predev 1555e2d8a gave the trait method (rebase fix; census:count keeps one `serves`) -- qa: streams|metadata|protected-resource is an accepted difference (ARCHITECT ruling on the metadata observation, owner-delegated 2026-10-01) Fold commit 5768d32dc serves the RFC 9728 document at /.well-known/oauth-protected-resource/v1/realtime, the path the plane's challenge points refused callers at. Nothing served that path before; that was a defect. The document comes from the kernel's one metadata renderer, so it is the same document the mcp route serves (bearer_methods_supported ["header"]). The pinned pre-fold build (golden/1.6.0-pre) answered that GET with 401 invalid_api_key. This is a register row, not a re-baseline: goldens are never blessed. Scope: - the one cell (`^streams\|metadata\|protected-resource$`, expected_cells 1); - the body class only; - a premise that pins the candidate's single step to a 200 carrying exactly that document (bearer_methods_supported ["header"], a resource, no authorization server, no second step). Signed as owner-delegated to the ARCHITECT. It expires against baseline 1.6.0-pre on 2026-12-31. Its changelog line is added verbatim under 1.6.0 Improvements. (cherry picked from commit ae77c52be9e802f4d64f7e7ec898765249a3c473) --- CHANGELOG.md | 5 +++++ crates/busbar-plane-streaming/src/tools.rs | 2 +- crates/busbar-voice/src/runtime/mod.rs | 2 +- crates/busbar-voice/src/runtime/session.rs | 17 ++++++++--------- .../busbar-voice/src/topology/minter_https.rs | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cdd575fd2..fd523f4ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -373,6 +373,11 @@ Each of these is an owner-accepted difference from 1.5.5: additive, or strictly series by their labels; a histogram's or summary's lines stay together in their usual order (buckets ascending, then `_sum`, then `_count`). Every name, label and value is unchanged, and the order served is one 1.5.5 could already print. +- **The streaming plane serves the protected-resource metadata its challenge points at.** A caller + refused at a realtime door is pointed at + `/.well-known/oauth-protected-resource/v1/realtime`, and nothing served that path. It + now answers the RFC 9728 document: the plane's audience as `resource` and + `bearer_methods_supported: ["header"]`, the same document the MCP route serves. ### Breaking diff --git a/crates/busbar-plane-streaming/src/tools.rs b/crates/busbar-plane-streaming/src/tools.rs index d746eb4909..5177b6aa8b 100644 --- a/crates/busbar-plane-streaming/src/tools.rs +++ b/crates/busbar-plane-streaming/src/tools.rs @@ -45,7 +45,7 @@ pub struct ClientRelay; #[async_trait] impl ToolExecutor for ClientRelay { - fn serves(&self, _name: &str) -> bool { + fn executes_here(&self, _name: &str) -> bool { false } diff --git a/crates/busbar-voice/src/runtime/mod.rs b/crates/busbar-voice/src/runtime/mod.rs index 27f77987fb..cceeff728e 100644 --- a/crates/busbar-voice/src/runtime/mod.rs +++ b/crates/busbar-voice/src/runtime/mod.rs @@ -14,7 +14,7 @@ pub mod carrier; pub mod metering; pub mod scope; pub mod session; -/// THE TOOL EXECUTOR PORT, from `busbar-plane-streaming`, and its production default +/// THE TOOL EXECUTOR PORT and its production default /// ([`tools::ClientRelay`]: this node serves no tool, so every call is relayed to the caller). The /// echo executor the batteries drive is a test double, present only in test builds. pub mod tools { diff --git a/crates/busbar-voice/src/runtime/session.rs b/crates/busbar-voice/src/runtime/session.rs index 88d05b84d5..bcd7637b3b 100644 --- a/crates/busbar-voice/src/runtime/session.rs +++ b/crates/busbar-voice/src/runtime/session.rs @@ -22,9 +22,11 @@ use crate::ir::usage::IrDuplexUsage; use crate::runtime::carrier::Carrier; use crate::runtime::metering::{SessionMetering, TurnVerdict}; use crate::runtime::tools::ToolExecutor; -use busbar_plane_streaming::governed::GovernedSession; -use busbar_plane_streaming::session::TurnCounters; -use busbar_plane_streaming::session_pump::{SessionPump, TurnSink}; +use busbar_plane_streaming::{ + governed::GovernedSession, + session::TurnCounters, + session_pump::{SessionPump, TurnSink}, +}; use bytes::Bytes; use std::sync::{Arc, Mutex}; @@ -32,12 +34,9 @@ use busbar_kernel::ingress::byte_duplex::{CallRef as WireCallRef, DuplexHandle, /// THE FRAME PLAN one decoded inbound frame produces — what to write UPSTREAM (client→server events: /// tool results, barge-in cancel/truncate, `response.create`), what to relay DOWNLINK to the client, -/// and whether the kernel's budget verdict tripped a HARD CLOSE this frame. The plane's own plan -/// (`busbar_plane_streaming::session_pump::Outbound`). -pub use busbar_plane_streaming::session_pump::Outbound; - -/// The named reason a session closed at its configured wall-clock ceiling is told under. -pub use busbar_plane_streaming::session_pump::SESSION_CEILING_REASON; +/// and whether the kernel's budget verdict tripped a HARD CLOSE this frame. Re-exported with the named reason a +/// session closed at its configured wall-clock ceiling is told under. +pub use busbar_plane_streaming::session_pump::{Outbound, SESSION_CEILING_REASON}; /// A closed turn's way to the kernel account: the session's metering, when it has one. A turn the /// account answers `MustClose` for cuts the session. diff --git a/crates/busbar-voice/src/topology/minter_https.rs b/crates/busbar-voice/src/topology/minter_https.rs index e9b0ce13e1..537587acc1 100644 --- a/crates/busbar-voice/src/topology/minter_https.rs +++ b/crates/busbar-voice/src/topology/minter_https.rs @@ -48,7 +48,7 @@ impl HttpsTokenMinter { /// Build a minter over an already-assembled egress client. `base_url` is the provider origin /// (scheme + authority, e.g. `https://api.openai.com`); `api_key` is the REAL provider key held /// server-side; `safety_identifier` is the caller-identity binding; `requested_ttl_secs` is the - /// desired secret lifetime (`None` ⇒ [`busbar_plane_streaming::broker::DEFAULT_TTL_SECS`]), + /// desired secret lifetime (`None` ⇒ the broker's default), /// clamped to the accepted window on mint. pub fn new( client: EngineClient, From 5b4b0d55578c1492c51ff838aa358d96dea1e171 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 07/82] busbar-plane-streaming door: published on P1's SDK, loaded through P1's loader, a refused arrival names REFUSAL_NO_DOOR and a 404; the tail names no caller-credential refusal on predev (4 carried commits) -- busbar-plane-streaming door: published on P1's SDK (sdk::publish, Out<'_, T>) P1 (SDK-SAFE) removed abi::sdk::Published and the `&mut out` slot form. The streaming door now publishes the way P1's own doors do (plugin-loader's plane_door_plugin fixture): - Each generation's snapshot is an owned SnapshotSpec: the six doors as ClaimSpecs, built from ROUTES by Route::claim, which carries each line's refusal dialect onto the claim; the audience and metadata URL from the one reading of the public base URL. It is published with Out::publish into the instance's Generations and retired with its generation. - A deployment with no public URL publishes an empty snapshot, which claims nothing, as before. - Every slot takes Out<'_, T>, and scalar answers go through Out::set. - The One Statement moves: the plane's sections and its needs ride on the Statement, not the PlaneTail. Section and the SECTION_* flags come from mechanism::door. The tail states P1's refusal_statuses and caller_credential_refusal as none. - Need states no kept response headers and the host's default timeout. No behaviour change: the same claims, audience, metadata URL, sections and needs. -- busbar-plane-streaming conformance test: loads both doors through P1's loader (LinkedRow::of, the stated rendering for the dropped door, a labelled Bind with no connection table, ValidateIn's lent reason buffer absent); Route::claim wrapped as rustfmt wants -- busbar-plane-streaming door: a refused arrival names the plane's code and a 404 (REFUSAL_NO_DOOR: no door of the plane serves that claim), as every REFUSED arrive must under the plane kind's check; rustfmt of the plane import list -- busbar-plane-streaming door: the tail names no caller-credential refusal on predev (the field arrives with FOLD-A2A C1, which has not landed; the door set it to NONE) (cherry picked from commit 286533173f2bf3881d0fab1c09b62999683add57) --- crates/busbar-plane-streaming/src/door.rs | 200 +++++++++--------- .../tests/conformance.rs | 26 ++- 2 files changed, 112 insertions(+), 114 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index c104bc8020..44fa074ed1 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -30,19 +30,21 @@ use std::sync::Mutex; use busbar_contract::abi::host::conn::connector::{Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND}; use busbar_contract::abi::mechanism::call::{AbiStr, Blob, InHead, OutHead, Outcome, BLOB_ABSENT}; -use busbar_contract::abi::mechanism::door::{KindTailHead, Statement}; +use busbar_contract::abi::mechanism::door::{ + KindTailHead, Section, Statement, SECTION_DECLARING, SECTION_REQUIRED, +}; use busbar_contract::abi::mechanism::lifecycle::{ CancelIn, CancelOut, GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, }; use busbar_contract::abi::plane::{ - ArriveIn, ArriveOut, BillableClass, Claim, DialectAuth, OnPieceIn, OnPieceOut, OpClass, - PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, - PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, Section, ServeIn, ServeOut, UnitCount, - CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, - SECTION_DECLARING, SECTION_REQUIRED, SHAPE_PIECEWISE, UNITS_ESTIMATED, + ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, OpClass, PlaneDriveIn, + PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, + ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, CANCEL_FAILED, CLAIM_EXACT, + CLAIM_OPEN, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, SHAPE_PIECEWISE, UNITS_ESTIMATED, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; -use busbar_contract::abi::sdk::{Instance, Lent, Published, Safe, SafeSlot}; +use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; +use busbar_contract::abi::sdk::{Generations, Instance, Lent, Out, Safe, SafeSlot}; use busbar_contract::plane::{PER_SESSION, TOKEN_FAMILY}; use crate::claims::{Dialect, HTTP_TRANSPORT, WS_TRANSPORT}; @@ -164,6 +166,9 @@ const fn need(direction: u32, transport: &'static str, auth: AbiStr, target: Abi fmt: BLOB_ABSENT, flags: 0, }, + keep_response_headers: ptr::null(), + keep_response_headers_len: 0, + timeout_ms: 0, } } @@ -211,8 +216,6 @@ pub const TAIL: &PlaneTail = &PlaneTail { signing_domain: NONE, signing_kid_prefix: NONE, cli_help: NONE, - sections: SECTIONS.as_ptr(), - sections_len: SECTIONS.len(), dialects: DIALECTS.as_ptr(), dialects_len: DIALECTS.len(), dialect_auth: DIALECT_AUTH.as_ptr(), @@ -229,14 +232,14 @@ pub const TAIL: &PlaneTail = &PlaneTail { fee_units_len: FEE_UNITS.len(), record_kinds: ptr::null(), record_kinds_len: 0, - needs: NEEDS.as_ptr(), - needs_len: NEEDS.len(), egress_targets: EGRESS_TARGETS.as_ptr(), egress_targets_len: EGRESS_TARGETS.len(), record_chains: ptr::null(), record_chains_len: 0, trust_keys: ptr::null(), trust_keys_len: 0, + refusal_statuses: ptr::null(), + refusal_statuses_len: 0, }; /// The inbound auth style of the metadata door: none, it is read without a credential. @@ -360,21 +363,19 @@ pub const ROUTES: &[Route] = &[ }, ]; -const fn claim(i: usize) -> Claim { - let r = ROUTES[i]; - Claim { - verb: abi_str(r.verb), - target: abi_str(r.target), - carrier: abi_str(r.carrier), - flags: if r.exact() { CLAIM_EXACT } else { 0 } | if r.open() { CLAIM_OPEN } else { 0 }, - _reserved: 0, +impl Route { + /// The door as a generation snapshot claims it. + #[must_use] + pub fn claim(&self) -> ClaimSpec { + let flags = + if self.exact() { CLAIM_EXACT } else { 0 } | if self.open() { CLAIM_OPEN } else { 0 }; + ClaimSpec { + refusal_dialect: self.refusal_dialect as u16, + ..ClaimSpec::new(self.verb, self.target, self.carrier, flags) + } } } -const CLAIMS: &[Claim] = &[claim(0), claim(1), claim(2), claim(3), claim(4), claim(5)]; - -const _: () = assert!(CLAIMS.len() == ROUTES.len()); - /// Judge a settings blob with the plane's grammar; an empty blob is the default section. /// /// # Errors @@ -397,39 +398,31 @@ pub fn absolute(public_url: &str, path: &str) -> Option { Some(u.to_string()) } -/// One generation's published answer: its snapshot and the strings it points at. +/// One generation's answer: the strings its snapshot names, read once from the public base URL. pub struct Generation { + generation: u64, audience: String, resource_metadata: String, - snapshot: Published, } impl Generation { /// The generation `generation` of a plane whose deployment states `public_url`. #[must_use] - pub fn build(generation: u64, public_url: Option<&str>) -> Box { - let admitted = - public_url.and_then(|p| Some((absolute(p, MOUNT_PATH)?, absolute(p, METADATA_PATH)?))); - let (audience, resource_metadata, claims) = match admitted { - Some((a, m)) => (a, m, CLAIMS), - None => (String::new(), String::new(), &[][..]), - }; - let mut g = Box::new(Generation { + pub fn build(generation: u64, public_url: Option<&str>) -> Self { + let (audience, resource_metadata) = public_url + .and_then(|p| Some((absolute(p, MOUNT_PATH)?, absolute(p, METADATA_PATH)?))) + .unwrap_or_default(); + Generation { + generation, audience, resource_metadata, - snapshot: Published::new(snapshot(generation, claims)), - }); - let mut s = *g.snapshot.get(); - s.audience = text(&g.audience); - s.resource_metadata = text(&g.resource_metadata); - g.snapshot = Published::new(s); - g + } } /// Its generation. #[must_use] pub fn generation(&self) -> u64 { - self.snapshot.get().generation + self.generation } /// The audience a caller's token must carry; empty = no receiving side. @@ -444,61 +437,48 @@ impl Generation { &self.resource_metadata } - /// Its snapshot, for the host. + /// Its snapshot as the plane publishes it: every door claimed and the audience and metadata + /// URL bound, or, with no receiving side, nothing claimed. #[must_use] - pub fn snapshot(&self) -> *const PlaneSnapshot { - self.snapshot.as_ptr() - } -} - -fn snapshot(generation: u64, claims: &'static [Claim]) -> PlaneSnapshot { - PlaneSnapshot { - size: size_of::() as u32, - _reserved: 0, - generation, - claims: if claims.is_empty() { - ptr::null() - } else { - claims.as_ptr() - }, - claims_len: claims.len(), - admin_routes: ptr::null(), - admin_routes_len: 0, - openapi: Blob { - ptr: ptr::null(), - len: 0, - fmt: BLOB_ABSENT, - flags: 0, - }, - audience: NONE, - resource_metadata: NONE, + pub fn snapshot(&self) -> SnapshotSpec { + if self.audience.is_empty() { + return SnapshotSpec::default(); + } + SnapshotSpec { + claims: ROUTES.iter().map(Route::claim).collect(), + audience: Some(self.audience.clone()), + resource_metadata: Some(self.resource_metadata.clone()), + ..SnapshotSpec::default() + } } } -fn text(s: &str) -> AbiStr { - if s.is_empty() { - return NONE; - } - AbiStr { - ptr: s.as_ptr(), - len: s.len(), - } -} +/// The plane's refusal code for an arrival on a claim it does not publish: no door of the plane +/// serves it (a 404). +pub const REFUSAL_NO_DOOR: u32 = 1; /// One open plane instance. pub struct Plane { /// The deployment's public base URL, as `open` read it; a refresh keeps it. public_url: Option, - /// The live generations' settings and snapshots, oldest first (control lane only). - generations: Mutex)>>, + /// The live generations' settings and answers, oldest first (control lane only). + generations: Mutex>, + /// The published snapshots, held by the SDK until their generation's `retire`. + snapshots: Generations, } impl Plane { - fn publish(&self, generation: u64, cfg: StreamsCfg) -> *const PlaneSnapshot { + /// Publish generation `generation` into the `out` field `pick` names. + fn publish( + &self, + out: &mut Out<'_, T>, + pick: impl FnOnce(&T) -> &*const PlaneSnapshot, + generation: u64, + cfg: StreamsCfg, + ) { let g = Generation::build(generation, self.public_url.as_deref()); - let p = g.snapshot(); + out.publish(pick, &self.snapshots, generation, &g.snapshot()); lock(&self.generations).push((cfg, g)); - p } } @@ -508,20 +488,24 @@ fn lock(m: &Mutex) -> std::sync::MutexGuard<'_, T> { /// One slot body on the SDK's safe surface over the instance's [`Plane`]. macro_rules! slot { - ($name:ident, $in:ty, $out:ty, |$inst:pat_param, $input:pat_param, $o:pat_param| $body:block) => { + ($name:ident, $in:ty, $out:ty, |$inst:pat_param, $input:pat_param, $o:ident| $body:block) => { struct $name; impl SafeSlot for $name { type In = $in; type Out = $out; type State = Plane; - fn call($inst: Instance<'_, Plane>, $input: Lent<'_, $in>, $o: &mut $out) -> Outcome { + fn call( + $inst: Instance<'_, Plane>, + $input: Lent<'_, $in>, + #[allow(unused_mut)] mut $o: Out<'_, $out>, + ) -> Outcome { $body } } }; } -slot!(Validate, ValidateIn, OutHead, |_, input, _| { +slot!(Validate, ValidateIn, OutHead, |_, input, _out| { match read_settings(input.field(|i| &i.settings).bytes()) { Ok(_) => Outcome::Ready, Err(_) => Outcome::Refused, @@ -541,8 +525,9 @@ slot!(Open, PlaneOpenIn, PlaneOpenOut, |instance, input, out| { let p = Plane { public_url, generations: Mutex::new(Vec::new()), + snapshots: Generations::new(), }; - out.snapshot = p.publish(input.open.generation, cfg); + p.publish(&mut out, |o| &o.snapshot, input.open.generation, cfg); instance.open(p); Outcome::Ready }); @@ -558,40 +543,43 @@ slot!( let Ok(cfg) = read_settings(input.field(|i| &i.settings).bytes()) else { return Outcome::Refused; }; - out.snapshot = p.publish(input.generation, cfg); + p.publish(&mut out, |o| &o.snapshot, input.generation, cfg); Outcome::Ready } ); -slot!(Retire, GenIn, OutHead, |instance, input, _| { +slot!(Retire, GenIn, OutHead, |instance, input, _out| { if let Some(p) = instance.get() { lock(&p.generations).retain(|(_, g)| g.generation() != input.generation); + p.snapshots.retire(input.generation); } Outcome::Ready }); slot!(Tick, TickIn, TickOut, |_, _, out| { - out.next_tick_ns = 0; + out.set(|o| &o.next_tick_ns, 0); Outcome::Ready }); -slot!(Drive, PlaneDriveIn, PlaneDriveOut, |_, _, _| { +slot!(Drive, PlaneDriveIn, PlaneDriveOut, |_, _, _out| { Outcome::Ready }); slot!(Cancel, CancelIn, CancelOut, |_, _, out| { - out.disposition = CANCEL_FAILED; + out.set(|o| &o.disposition, CANCEL_FAILED); Outcome::Ready }); -slot!(Release, ReleaseIn, OutHead, |_, _, _| { Outcome::Ready }); +slot!(Release, ReleaseIn, OutHead, |_, _, _out| { Outcome::Ready }); -slot!(Close, InHead, OutHead, |_, _, _| { Outcome::Ready }); +slot!(Close, InHead, OutHead, |_, _, _out| { Outcome::Ready }); // A claimed arrival is classified: its door's dialect, the session open, a known principal. No // admission estimate: a session's units are what the far end reports. slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { let Some(a) = crate::driven::arrive(input.claim) else { + out.set(|o| &o.refusal, REFUSAL_NO_DOOR); + out.set(|o| &o.refusal_status, 404); return Outcome::Refused; }; let mut units = input.units_buf(); @@ -603,34 +591,34 @@ slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { }); } if !units.fits() { - out.units_needed = units.needed() as u32; + out.set(|o| &o.units_needed, units.needed() as u32); return Outcome::Failed; } - out.units_written = units.written() as u32; - out.op_class = a.op_class; - out.dialect = a.dialect; - out.principal_need = a.door.authenticate(); + out.set(|o| &o.units_written, units.written() as u32); + out.set(|o| &o.op_class, a.op_class); + out.set(|o| &o.dialect, a.dialect); + out.set(|o| &o.principal_need, a.door.authenticate()); Outcome::Ready }); // The door serves no unit's pieces yet: every piece, refusal render, admin serve and projection is // declined, and a declined unit is charged nothing. -slot!(OnPiece, OnPieceIn, OnPieceOut, |_, _, _| { +slot!(OnPiece, OnPieceIn, OnPieceOut, |_, _, _out| { Outcome::Refused }); -slot!(Refusal, RefusalIn, RefusalOut, |_, _, _| { +slot!(Refusal, RefusalIn, RefusalOut, |_, _, _out| { Outcome::Refused }); -slot!(Serve, ServeIn, ServeOut, |_, _, _| { Outcome::Refused }); +slot!(Serve, ServeIn, ServeOut, |_, _, _out| { Outcome::Refused }); -slot!(Hydrate, GenIn, OutHead, |_, _, _| { Outcome::Ready }); +slot!(Hydrate, GenIn, OutHead, |_, _, _out| { Outcome::Ready }); -slot!(Start, GenIn, OutHead, |_, _, _| { Outcome::Ready }); +slot!(Start, GenIn, OutHead, |_, _, _out| { Outcome::Ready }); -slot!(Project, ProjectIn, ProjectOut, |_, _, _| { +slot!(Project, ProjectIn, ProjectOut, |_, _, _out| { Outcome::Refused }); @@ -638,6 +626,10 @@ busbar_contract::plugin_door! { ops: busbar_contract::abi::plane::Ops, statement: Statement { kind_tail: ptr::from_ref(TAIL).cast::(), + sections: SECTIONS.as_ptr(), + sections_len: SECTIONS.len(), + needs: NEEDS.as_ptr(), + needs_len: NEEDS.len(), ..statement(NAME, env!("CARGO_PKG_VERSION"), 64) }, lifecycle: { diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index 2da9bb596b..cd8d92118c 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -14,7 +14,6 @@ use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome, BLOB_JSON}; use busbar_contract::abi::mechanism::lifecycle::{ slot as life, CancelIn, CancelOut, GenIn, RefreshIn, TickIn, TickOut, ValidateIn, }; -use busbar_contract::abi::mechanism::{KindCode, MECHANISM_VERSION}; use busbar_contract::abi::plane::{ slot, ArriveIn, ArriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, @@ -23,7 +22,7 @@ use busbar_plane_streaming::door; use busbar_plugin_loader::dispatch::kinds::plane::Plane; use busbar_plugin_loader::dispatch::{ in_head, load_dropped, load_linked, out_head, Bind, DispatchConfig, Dispatcher, Frame, - ManifestFacts, NoSink, Plugin, + LinkedRow, NoSink, Plugin, }; fn z() -> T { @@ -31,14 +30,22 @@ fn z() -> T { unsafe { zeroed() } } -fn bind(d: &Dispatcher) -> Bind { +/// The bind for one instance, labelled `instance` (two instances never share a label). +fn bind(d: &Dispatcher, instance: &str) -> Bind { Bind { + instance: Arc::from(instance), max_inflight_cap: 8, sink: Arc::new(NoSink), dispatcher: d.adopter(), + conns: None, } } +/// The compiled-in row: the door and the Statement rendering it states. +fn row() -> LinkedRow { + LinkedRow::of(door::door).expect("the streaming door states itself") +} + fn json(b: &'static [u8]) -> Blob { Blob { ptr: b.as_ptr(), @@ -102,7 +109,7 @@ fn snapshot(p: *const PlaneSnapshot) -> String { } fn linked(d: &Dispatcher) -> Plugin { - load_linked::(door::door, bind(d)).expect("the linked streaming door loads") + load_linked::(&row(), bind(d, "linked")).expect("the linked streaming door loads") } /// The example `cdylib` in this target dir (`cargo test` builds examples). Under CI a missing @@ -118,13 +125,10 @@ fn dropped(d: &Dispatcher) -> Option> { path.exists() || std::env::var_os("CI").is_none(), "the streaming_door example cdylib is not built under CI; a both-ways proof must not skip" ); - let facts = ManifestFacts { - mechanism_version: MECHANISM_VERSION, - kind: KindCode::Plane, - kind_abi: KindCode::Plane.abi_version(), - }; + let stated = row().statement; path.exists().then(|| { - load_dropped::(&path, &facts, bind(d)).expect("the dropped streaming door loads") + load_dropped::(&path, &stated, bind(d, "dropped")) + .expect("the dropped streaming door loads") }) } @@ -133,6 +137,8 @@ fn validate(p: &Plugin, settings: &'static [u8]) -> Outcome { ValidateIn { head: in_head(), settings: json(settings), + err_buf: std::ptr::null_mut(), + err_cap: 0, }, out_head(), ); From 8ac0cfff60e0bc49cd9c442cb621820ea9bf6ad7 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 08/82] door-only: busbar-voice names no busbar_plane_streaming item outside the ledger (mount.rs:962/1164, testkit/echo.rs:9, topology/minter_https.rs:25, topology/telephony.rs:30; ARCHITECT ruling) -- door-only: busbar-voice names no busbar_plane_streaming item at mount.rs:962 or testkit/echo.rs:9 (ARCHITECT ruling: a crate outside the plane reaches it only through its door, or the item moves to the side that owns it) - committed_session_config moves back to busbar-voice (mount.rs), predev's body verbatim. Its only production caller is voice's hooks-TAP leg; the plane's door path never calls it. The plane copy and its two plane tests are deleted. Voice's own committed_session_config_refuses_what_it_cannot_use_and_patches_what_it_can still pins it. - testkit/echo.rs (a test double, compiled only under test or test-support) implements voice's tool port as every voice file names it (crate::runtime::tools::ToolExecutor, as runtime/session.rs does). It no longer spells the plane crate. No ledger row added; the gate is not widened. Served bytes unchanged. -- door-only: busbar-voice's served mint, SDP and telephony legs keep their own pure pieces (mount.rs:1164, topology/minter_https.rs:25, topology/telephony.rs:30), as on predev The fold's S2a moved the mint's request body, lifetime clamp and answer read, the SDP answer's rtc_ call id and the telephony g711 lock into busbar-plane-streaming. busbar-voice, which still serves those legs, then named them by Rust path, outside the plane's door. The ARCHITECT ruling allows two fixes: re-route through the door, or move the item to the side that owns it. The served legs are voice's until the streaming flip, so voice keeps predev's bodies verbatim (minter_https.rs and telephony.rs are byte-for-byte predev; rtc_call_id_of is predev's mount.rs function). The plane keeps its copies (broker.rs, session_params.rs) for its own door path (driven.rs, sessions.rs). The two copies meet again when the flip deletes voice's legs. Served bytes are predev's by construction. Re-routing these legs through the door is the streaming flip: the governed open, the budget, the durable row and the rtc stamp all move onto the kernel driver. That flip is escalated to the ARCHITECT (escalate.q, FOLD-STREAMING door-only), and this commit is dropped if the ruling is the flip. No ledger row added; the gate is not widened. (cherry picked from commit fcf5bdd592b497232dbdf67fcff83d1ab4cd3662) --- .../src/session_params.rs | 37 +------------ .../src/tests/session_params_tests.rs | 39 ------------- crates/busbar-voice/src/mount.rs | 31 ++++++++++- crates/busbar-voice/src/testkit/echo.rs | 2 +- .../busbar-voice/src/topology/minter_https.rs | 55 +++++++++++++++---- crates/busbar-voice/src/topology/telephony.rs | 13 ++++- 6 files changed, 87 insertions(+), 90 deletions(-) diff --git a/crates/busbar-plane-streaming/src/session_params.rs b/crates/busbar-plane-streaming/src/session_params.rs index f3ad7810fc..742fa0e31c 100644 --- a/crates/busbar-plane-streaming/src/session_params.rs +++ b/crates/busbar-plane-streaming/src/session_params.rs @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! THE SESSION PARAMS A SESSION IS LOCKED TO: the telephony lock, and how a committed rewrite of the -//! session-open params is read back over the locked ones. +//! THE SESSION PARAMS A SESSION IS LOCKED TO: the telephony lock. use crate::codec::ir::config::SessionConfig; use crate::codec::ir::media::AudioFormat; @@ -19,40 +18,6 @@ pub fn g711_config() -> SessionConfig { } } -/// A committed rewrite of the session-open params, read back over the `locked` ones. -/// -/// The rewrite is a PATCH: the params are optional field by field on the wire, so the committed -/// object is merged over the locked params. A key the rewrite names wins (an explicit `null` clears -/// it); a key it does not name keeps the locked value. The merge happens before the decode, so a -/// patch naming one bad field refuses rather than being quietly dropped. -/// -/// A well-formed JSON scalar is refused too: the params are an object on every wire the plane -/// speaks, and the value replaces the locked [`SessionConfig`]. -/// -/// # Errors -/// Output that is not JSON, not an object, or not a session config, each with its reason. -pub fn committed_session_config( - locked: &SessionConfig, - args_json: &[u8], -) -> Result { - let patch: serde_json::Value = - serde_json::from_slice(args_json).map_err(|e| format!("the output is not JSON: {e}"))?; - let serde_json::Value::Object(patch) = patch else { - return Err("the output is JSON but not a session-params object".to_string()); - }; - // The locked params as the rewrite was handed them, so the merge is over exactly the key set it - // screened. - let Ok(serde_json::Value::Object(mut merged)) = serde_json::to_value(locked) else { - return Err( - "the plane's own locked session params did not project to an object".to_string(), - ); - }; - // A key the rewrite NAMED wins; a key it did not name keeps the locked value. - merged.extend(patch); - serde_json::from_value::(serde_json::Value::Object(merged)) - .map_err(|e| format!("the output is not a session config: {e}")) -} - #[cfg(test)] #[path = "tests/session_params_tests.rs"] mod tests; diff --git a/crates/busbar-plane-streaming/src/tests/session_params_tests.rs b/crates/busbar-plane-streaming/src/tests/session_params_tests.rs index 04607a7152..249db01b25 100644 --- a/crates/busbar-plane-streaming/src/tests/session_params_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_params_tests.rs @@ -5,13 +5,6 @@ use super::*; -fn locked() -> SessionConfig { - SessionConfig { - instructions: Some("stay on topic".into()), - ..crate::config::StreamsCfg::default().session - } -} - #[test] fn the_telephony_lock_is_g711_both_ways() { let c = g711_config(); @@ -26,35 +19,3 @@ fn the_telephony_lock_is_g711_both_ways() { SessionConfig::default() ); } - -#[test] -fn a_rewrite_that_is_not_a_session_params_object_is_refused() { - let locked = locked(); - assert!(committed_session_config(&locked, b"not json").is_err()); - for scalar in [&b"7"[..], br#""marin""#, b"null", b"[]", b"true"] { - assert!(committed_session_config(&locked, scalar).is_err()); - } - for bad in [ - &br#"{"output_audio_format":"flac"}"#[..], - br#"{"voice":7}"#, - br#"{"modalities":"audio"}"#, - ] { - assert!(committed_session_config(&locked, bad).is_err()); - } -} - -#[test] -fn a_rewrite_patches_what_it_names_and_keeps_the_rest() { - let locked = locked(); - assert_eq!( - committed_session_config(&locked, b"{}").expect("identity"), - locked - ); - let cleared = - committed_session_config(&locked, br#"{"instructions":null}"#).expect("an explicit null"); - assert_eq!(cleared.instructions, None); - assert_eq!(cleared.turn_detection, locked.turn_detection); - let voiced = committed_session_config(&locked, br#"{"voice":"marin"}"#).expect("one field"); - assert_eq!(voiced.voice.as_deref(), Some("marin")); - assert_eq!(voiced.instructions, locked.instructions); -} diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index bc7ceb83af..62c1a3ffe3 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -976,7 +976,27 @@ async fn hook_gate( /// speaks, and the value is substituted for the plane's locked [`SessionConfig`]; admitting `7` would /// mean telling the hook its rewrite landed and then opening the session with something that is not a /// session config at all. -pub(crate) use busbar_plane_streaming::session_params::committed_session_config; +pub(crate) fn committed_session_config( + locked: &SessionConfig, + args_json: &[u8], +) -> Result { + let patch: serde_json::Value = + serde_json::from_slice(args_json).map_err(|e| format!("the output is not JSON: {e}"))?; + let serde_json::Value::Object(patch) = patch else { + return Err("the output is JSON but not a session-params object".to_string()); + }; + // The locked params as the hook itself was handed them (`serde_json::to_vec(cfg)` at the call + // seam), so the merge is over exactly the key set the hook screened. + let Ok(serde_json::Value::Object(mut merged)) = serde_json::to_value(locked) else { + return Err( + "the plane's own locked session params did not project to an object".to_string(), + ); + }; + // A key the hook NAMED wins; a key it did not name keeps the plane's locked value. + merged.extend(patch); + serde_json::from_value::(serde_json::Value::Object(merged)) + .map_err(|e| format!("the output is not a session config: {e}")) +} /// The hooks-TAP leg (`host.transform_over`) over the session-open params. `Ok(Some(cfg))` is a /// committed rewrite the caller substitutes for the locked params; `Ok(None)` is "no change" (no @@ -1178,7 +1198,14 @@ async fn serve_sdp( .unwrap_or_else(|_| sideband_pending()) } -use busbar_plane_streaming::broker::rtc_call_id_of; +/// The last `rtc_` path segment of a brokered call's `Location` header — the correlation key +/// the SDP broker stamps onto the durable row. `None` when no segment carries the `rtc_` prefix. +fn rtc_call_id_of(location: &str) -> Option { + location + .rsplit('/') + .find(|seg| seg.starts_with("rtc_")) + .map(|seg| seg.split(['?', '#']).next().unwrap_or(seg).to_string()) +} /// The substrate egress client the one-shot HTTPS passes dial through (the same posture the concrete /// minter uses). Built per pass; the composition root pools one once the provider config is threaded. diff --git a/crates/busbar-voice/src/testkit/echo.rs b/crates/busbar-voice/src/testkit/echo.rs index 2f76bb5e6d..cc2fab4a69 100644 --- a/crates/busbar-voice/src/testkit/echo.rs +++ b/crates/busbar-voice/src/testkit/echo.rs @@ -6,7 +6,7 @@ //! Test builds only; production relays every call to the caller (`ClientRelay`). use async_trait::async_trait; -use busbar_plane_streaming::tools::ToolExecutor; +use crate::runtime::tools::ToolExecutor; /// Echoes the call: `{"tool":,"echo":}`. #[derive(Debug, Default, Clone, Copy)] diff --git a/crates/busbar-voice/src/topology/minter_https.rs b/crates/busbar-voice/src/topology/minter_https.rs index 537587acc1..b69424730d 100644 --- a/crates/busbar-voice/src/topology/minter_https.rs +++ b/crates/busbar-voice/src/topology/minter_https.rs @@ -22,12 +22,20 @@ use bytes::Bytes; use http_body_util::{BodyExt, Full}; use std::time::Duration; -use busbar_plane_streaming::broker::{ - clamped_ttl_secs, mint_request_body, read_minted, CLIENT_SECRETS_PATH, SAFETY_IDENTIFIER_HEADER, -}; - +/// The default requested secret lifetime when the caller pins none. +const DEFAULT_TTL_SECS: u64 = 600; +/// The provider's minimum accepted secret lifetime. +const MIN_TTL_SECS: u64 = 10; +/// The provider's maximum accepted secret lifetime. +const MAX_TTL_SECS: u64 = 7200; +/// The prefix every ephemeral client secret the provider mints carries. +const EK_PREFIX: &str = "ek_"; +/// The header binding a minted secret to the caller identity. +const SAFETY_IDENTIFIER_HEADER: &str = "OpenAI-Safety-Identifier"; /// The bound on the whole mint exchange up to the response head plus its small body read. const MINT_DEADLINE: Duration = Duration::from_secs(30); +/// The client-secrets endpoint path on the provider. +const CLIENT_SECRETS_PATH: &str = "/v1/realtime/client_secrets"; /// MINTS the browser's ephemeral client secret over a real HTTPS call to the provider's /// client-secrets endpoint, holding the real key server-side. @@ -48,8 +56,7 @@ impl HttpsTokenMinter { /// Build a minter over an already-assembled egress client. `base_url` is the provider origin /// (scheme + authority, e.g. `https://api.openai.com`); `api_key` is the REAL provider key held /// server-side; `safety_identifier` is the caller-identity binding; `requested_ttl_secs` is the - /// desired secret lifetime (`None` ⇒ the broker's default), - /// clamped to the accepted window on mint. + /// desired secret lifetime (`None` ⇒ [`DEFAULT_TTL_SECS`]), clamped to the accepted window on mint. pub fn new( client: EngineClient, base_url: impl Into, @@ -68,15 +75,31 @@ impl HttpsTokenMinter { /// The requested lifetime clamped to the provider's accepted `[MIN, MAX]` window. fn clamped_ttl_secs(&self) -> u64 { - clamped_ttl_secs(self.requested_ttl_secs) + self.requested_ttl_secs + .unwrap_or(DEFAULT_TTL_SECS) + .clamp(MIN_TTL_SECS, MAX_TTL_SECS) } } +/// The provider's client-secret response: the `ek_` value and its absolute expiry in unix seconds. +#[derive(serde::Deserialize)] +struct ClientSecretResponse { + value: String, + #[serde(default)] + expires_at: u64, +} + #[async_trait] impl TokenMinter for HttpsTokenMinter { async fn mint(&self, config: &SessionConfig) -> Result { let ttl_secs = self.clamped_ttl_secs(); - let body_bytes = mint_request_body(ttl_secs, config).map_err(MintError::Provider)?; + let body = serde_json::json!({ + "expires_after": { "anchor": "created_at", "seconds": ttl_secs }, + "session": config, + }); + let body_bytes = serde_json::to_vec(&body).map_err(|e| { + MintError::Provider(format!("mint request body did not serialize: {e}")) + })?; let uri = format!( "{}{}", @@ -117,11 +140,21 @@ impl TokenMinter for HttpsTokenMinter { ))); } - let minted = read_minted(&raw).map_err(MintError::Provider)?; + let parsed: ClientSecretResponse = serde_json::from_slice(&raw).map_err(|e| { + MintError::Provider(format!("client-secret response did not parse: {e}")) + })?; + + // The browser-facing invariant: only an `ek_` secret ever leaves this boundary. A response + // whose value lacks the prefix is refused rather than handed on as if it were a client secret. + if !parsed.value.starts_with(EK_PREFIX) { + return Err(MintError::Provider( + "client-secret response value is not an ek_ ephemeral secret".into(), + )); + } Ok(EphemeralToken { - value: minted.value, - expires_at_unix: minted.expires_at_unix, + value: parsed.value, + expires_at_unix: parsed.expires_at, }) } } diff --git a/crates/busbar-voice/src/topology/telephony.rs b/crates/busbar-voice/src/topology/telephony.rs index fca51075e7..5abb2f1b4e 100644 --- a/crates/busbar-voice/src/topology/telephony.rs +++ b/crates/busbar-voice/src/topology/telephony.rs @@ -17,6 +17,7 @@ use crate::ir::codec::{DuplexReader, DuplexWriter}; use crate::ir::config::SessionConfig; +use crate::ir::media::AudioFormat; use crate::runtime::carrier::Carrier; use crate::runtime::scope::SessionHandle; use crate::runtime::session::{SessionCore, UplinkForwarder, VoiceSession}; @@ -27,7 +28,17 @@ use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures::{Sink, Stream, StreamExt}; use std::sync::Arc; -pub use busbar_plane_streaming::session_params::g711_config; +/// THE LOCKED CONFIG for a telephony leg: `g711_ulaw` on BOTH the input and output audio formats so the +/// 8 kHz µ-law carrier passes straight through with no resample. Callers overlay their own +/// instructions/tools onto the returned config before locking it. +#[must_use] +pub fn g711_config() -> SessionConfig { + SessionConfig { + input_audio_format: Some(AudioFormat::G711Ulaw), + output_audio_format: Some(AudioFormat::G711Ulaw), + ..SessionConfig::default() + } +} /// A LIVE TELEPHONY PROXY — the two planes to serve (one per socket), the funnels between them, the /// shared session core, and the durable handle. Build it with [`begin_telephony`]; drive it with From 0d2b01e55508cccdafc181d44d5a0423d4ea5653 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:28:59 -0700 Subject: [PATCH 09/82] rustfmt (voice mount.rs import indent, echo.rs import order) and Cargo.lock as cargo resolves it (busbar-plane-streaming's url and busbar-plugin-loader edges) (cherry picked from commit ac41f9586c8f37529128bb87f5dd9acce8843875) --- Cargo.lock | 2 ++ crates/busbar-voice/src/mount.rs | 4 ++-- crates/busbar-voice/src/testkit/echo.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8516881c55..3f3791c306 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -969,9 +969,11 @@ version = "1.6.0" dependencies = [ "async-trait", "busbar-contract", + "busbar-plugin-loader", "bytes", "serde", "serde_json", + "url", ] [[package]] diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index 62c1a3ffe3..bb3e1fdfae 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -589,8 +589,8 @@ pub fn voice_admission(slot: &dyn Any) -> Option { /// receiving side (no dispatch slot), so a deployment that fronts nothing mounts nothing. #[must_use] pub fn voice_routes(slot: &dyn Any) -> Vec { -use busbar_contract::abi::mechanism::route::{RouteAuth, RouteMethod}; -use busbar_kernel::plane_routes::PlaneRouteFuture; + use busbar_contract::abi::mechanism::route::{RouteAuth, RouteMethod}; + use busbar_kernel::plane_routes::PlaneRouteFuture; if slot.downcast_ref::().is_none() { return Vec::new(); diff --git a/crates/busbar-voice/src/testkit/echo.rs b/crates/busbar-voice/src/testkit/echo.rs index cc2fab4a69..05677afca2 100644 --- a/crates/busbar-voice/src/testkit/echo.rs +++ b/crates/busbar-voice/src/testkit/echo.rs @@ -5,8 +5,8 @@ //! correlation (the right `name` and `arguments` reached the right call) without a tool registry. //! Test builds only; production relays every call to the caller (`ClientRelay`). -use async_trait::async_trait; use crate::runtime::tools::ToolExecutor; +use async_trait::async_trait; /// Echoes the call: `{"tool":,"echo":}`. #[derive(Debug, Default, Clone, Copy)] From 7b9d76936c3d5a7ef83c2fa2147a7c9fa8d80f1b Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 1 Oct 2026 20:17:12 -0700 Subject: [PATCH 10/82] busbar-voice mount: the realtime protected-resource handler is protected_resource_route structure-lint:plane-dup:unledgered read voice's new `metadata_route` as a third copy of the mcp/a2a symbol the ledger signs for those two only. Voice's handler does not duplicate theirs: it hands its Metadata to the one shared renderer (protocol::metadata). It is renamed for what it serves, so the duplicate count stays at predev's 24. (cherry picked from commit f56d1a35aeeae296b61335379f8cb3faca0ea778) --- crates/busbar-voice/src/mount.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index bb3e1fdfae..aae95753f9 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -615,7 +615,7 @@ pub fn voice_routes(slot: &dyn Any) -> Vec { method: RouteMethod::Get, auth: RouteAuth::None, handler: Arc::new(|ctx: PlaneReqCtx| -> PlaneRouteFuture { - Box::pin(metadata_route(ctx)) + Box::pin(protected_resource_route(ctx)) }), }, ] @@ -624,7 +624,7 @@ pub fn voice_routes(slot: &dyn Any) -> Vec { /// The protected-resource metadata document: the audience a token at this plane's doors must carry, /// as one reading of the public URL. No authorization server is named: the plane is configured with /// none. -async fn metadata_route(ctx: PlaneReqCtx) -> axum::response::Response { +async fn protected_resource_route(ctx: PlaneReqCtx) -> axum::response::Response { let Some(mount) = ctx.slot.downcast_ref::() else { return refusal( axum::http::StatusCode::INTERNAL_SERVER_ERROR, From 27d43f0897fa208c74794f7900b811ce5a4f6add Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:09:15 -0700 Subject: [PATCH 11/82] busbar-plane-streaming door: the one-request doors (mint, SDP offer, metadata document) answer their pieces through on_piece; the owed reply and the three-buffer settle have one SDK home (abi::sdk::piece) BUSBAR-1.6.0.md Part 3 section 12, "The route pump" and "Crossings": the plane keeps what it needs keyed by the kernel's unit; a full reply buffer answers more = 1 and the kernel re-calls with the same from and zero bytes; an answer short of its field/arena room is re-called with the same piece. - The door keeps one RequestUnit per unit (bounded at MAX_UNITS, oldest dropped first), built at the unit's first piece over the newest live generation's session params and audience, and writes its answers: the ATTEMPT's verb, target, fields and body (to the far end), the caller's SDP offer (to the far end), and the caller's answer with its status and fields. A short answer is kept and re-answered from the kept copy, never read twice. - A session door's pieces are refused: the kernel's driver serves request units only (no duplex session leg exists in plane_driver). - busbar_contract::abi::sdk::piece: Owed (owe/pay across more = 1, EMIT_DONE with the last byte only), is_recall, settle. The llm, mcp, a2a and decisions doors each carry a private copy of this logic; each adopts the SDK home in its own lane (DEC-SERVE's two open branches rewrite the decisions door body, so it is not migrated here). Tests (tests/conformance.rs, linked and dropped through the one loader): the_one_request_doors_answer_their_pieces_through_the_door pins every answer, including the mint ATTEMPT body equal to the far-end bytes of the recorded streams|mint|client-secrets cell (golden/1.6.0-pre) and the metadata document paid 16 bytes at a time; the_dropped_door_answers_the_one_request_doors_as_the_linked_door_does. RED before this commit: on_piece answered REFUSED for every piece. (cherry picked from commit f64d8770cca48a80a739eba262ca20ad91900b9c) --- crates/busbar-contract/src/abi/sdk/mod.rs | 4 + crates/busbar-contract/src/abi/sdk/piece.rs | 102 +++++++ crates/busbar-plane-streaming/src/door.rs | 214 ++++++++++++++- .../tests/conformance.rs | 258 +++++++++++++++++- 4 files changed, 564 insertions(+), 14 deletions(-) create mode 100644 crates/busbar-contract/src/abi/sdk/piece.rs diff --git a/crates/busbar-contract/src/abi/sdk/mod.rs b/crates/busbar-contract/src/abi/sdk/mod.rs index 3abf8d165b..7a26e3d823 100644 --- a/crates/busbar-contract/src/abi/sdk/mod.rs +++ b/crates/busbar-contract/src/abi/sdk/mod.rs @@ -77,6 +77,10 @@ pub mod store; // PUBLISHED GENERATION DATA: the SDK owns what a plugin publishes (`abi::sdk::publish`). pub mod publish; pub use publish::{Generations, Keyed}; + +// THE PLANE KIND'S PIECE ANSWER: what an `on_piece` answer owes the reply buffer across `more = 1` +// re-calls, and the one settle of its field, unit and arena buffers (`abi::sdk::piece`). +pub mod piece; // THE LIFECYCLE, ONCE FOR EVERY KIND: the nine lifecycle slots over a kind's `Life` (`abi::sdk::life`). pub mod life; diff --git a/crates/busbar-contract/src/abi/sdk/piece.rs b/crates/busbar-contract/src/abi/sdk/piece.rs new file mode 100644 index 0000000000..1e3c10ad3a --- /dev/null +++ b/crates/busbar-contract/src/abi/sdk/piece.rs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE PLANE KIND'S PIECE ANSWER, PLUGIN SIDE: what an `on_piece` answer owes the host's reply +//! buffer, and the one settle of the answer's field, unit and arena buffers. +//! +//! `BUSBAR-1.6.0.md` Part 3, section 12, "The route pump": a full reply buffer answers `more = 1`; +//! the kernel flushes it, waits for the caller's side and calls again with the same `from` and zero +//! bytes ([`is_recall`]). An answer that does not fit its field, unit or arena buffer is short as a +//! whole ([`settle`]): the slot answers FAILED, and the kernel re-calls with the same piece and room. +//! +//! One home for every plane door: a plane keeps an [`Owed`] per unit and pays it across the +//! re-calls, so the backpressure rule (`EMIT_DONE` only with the last byte) is written once. + +use crate::abi::plane::{OnPieceIn, OnPieceOut, OutField, UnitCount, EMIT_DONE}; +use crate::abi::sdk::{HostBuf, Lent, Out}; + +/// Whether `input` is the kernel's re-call after a `more = 1` answer: no bytes, no flags and no +/// attempt. +#[must_use] +pub fn is_recall(input: Lent<'_, OnPieceIn>) -> bool { + let given = input.get(); + given.flags == 0 && given.attempt_no == 0 && input.field(|i| &i.bytes).bytes().is_empty() +} + +/// The bytes a unit's answer still owes the host's reply buffer, and the `EMIT_*` bits they carry. +#[derive(Debug, Default)] +pub struct Owed { + bytes: Vec, + at: usize, + flags: u32, +} + +impl Owed { + /// Whether any byte is still owed. + #[must_use] + pub fn pending(&self) -> bool { + self.at < self.bytes.len() + } + + /// Owe `bytes` under the `EMIT_*` `flags`, and pay what fits now. Answers whether the unit is + /// done: every byte written, with `EMIT_DONE` among `flags`. + pub fn owe( + &mut self, + bytes: &[u8], + flags: u32, + input: Lent<'_, OnPieceIn>, + out: &mut Out<'_, OnPieceOut>, + ) -> bool { + self.bytes.clear(); + self.bytes.extend_from_slice(bytes); + self.at = 0; + self.flags = flags; + self.pay(input, out) + } + + /// Write what is owed into the reply buffer, as much as fits: `more = 1` while any is left, and + /// the owed flags on every write, `EMIT_DONE` held back until the last byte. Answers whether the + /// unit is done. + pub fn pay(&mut self, input: Lent<'_, OnPieceIn>, out: &mut Out<'_, OnPieceOut>) -> bool { + let n = input.reply_buf().stream(&self.bytes[self.at..]); + self.at += n; + let more = self.pending(); + out.set(|o| &o.emitted, n as u64); + out.set(|o| &o.more, u32::from(more)); + out.set( + |o| &o.flags, + if more { + self.flags & !EMIT_DONE + } else { + self.flags + }, + ); + if more { + return false; + } + let done = self.flags & EMIT_DONE != 0; + *self = Owed::default(); + done + } +} + +/// Settle an answer's field, unit and arena buffers: `(written, needed)` of each, short as a whole +/// when any one is short. Answers whether the answer is short. +pub fn settle( + out: &mut Out<'_, OnPieceOut>, + fields: &HostBuf<'_, OutField>, + units: &HostBuf<'_, UnitCount>, + arena: &HostBuf<'_, u8>, +) -> bool { + let short = !(fields.fits() && units.fits() && arena.fits()); + let (fw, fnd) = fields.settle(short); + let (uw, und) = units.settle(short); + let (aw, and) = arena.settle(short); + out.set(|o| &o.fields_written, fw as u32); + out.set(|o| &o.fields_needed, fnd as u32); + out.set(|o| &o.units_written, uw as u32); + out.set(|o| &o.units_needed, und as u32); + out.set(|o| &o.arena_written, aw as u64); + out.set(|o| &o.arena_needed, and as u64); + short +} diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 44fa074ed1..b6e1d5751b 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -23,7 +23,16 @@ //! The plane's own `streams:` section as JSON, the kernel's reserved keys already read and stripped. //! [`read_settings`] judges it with the plane's grammar ([`crate::config::StreamsCfg`], unknown keys //! refused); an empty blob is the default section. +//! +//! ## What each unit's pieces are answered +//! +//! A one-request door (the mint, the SDP offer, the metadata document) answers its pieces through +//! a [`RequestUnit`] the instance keeps by the kernel's unit key, built at the unit's first piece +//! over the newest live generation's session params and audience. Its answer's bytes are paid into +//! the reply buffer across `more = 1` re-calls (`busbar_contract::abi::sdk::piece`). A session +//! door's pieces are refused: the kernel's driver serves request units only. +use std::collections::BTreeMap; use std::mem::size_of; use std::ptr; use std::sync::Mutex; @@ -37,21 +46,25 @@ use busbar_contract::abi::mechanism::lifecycle::{ CancelIn, CancelOut, GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, }; use busbar_contract::abi::plane::{ - ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, OpClass, PlaneDriveIn, - PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, - ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, CANCEL_FAILED, CLAIM_EXACT, - CLAIM_OPEN, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, SHAPE_PIECEWISE, UNITS_ESTIMATED, + ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, OpClass, OutField, + PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, + PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, + CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, + FROM_KERNEL, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, PIECE_HAS_STATUS, PIECE_LAST, + SHAPE_PIECEWISE, UNITS_ESTIMATED, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; +use busbar_contract::abi::sdk::piece::{self, Owed}; use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; -use busbar_contract::abi::sdk::{Generations, Instance, Lent, Out, Safe, SafeSlot}; +use busbar_contract::abi::sdk::{Generations, HostBuf, Instance, Lent, Out, Safe, SafeSlot}; use busbar_contract::plane::{PER_SESSION, TOKEN_FAMILY}; use crate::claims::{Dialect, HTTP_TRANSPORT, WS_TRANSPORT}; use crate::config::StreamsCfg; -use crate::driven::Steps; +use crate::driven::{Door, Steps}; use crate::meta; use crate::provider::{GEMINI_LIVE, OPENAI_REALTIME}; +use crate::request_unit::{self, Answer, Piece, RequestUnit}; /// The name the plane's Statement carries. pub const NAME: &str = crate::codec::PLANE_KEY; @@ -457,6 +470,20 @@ impl Generation { /// serves it (a 404). pub const REFUSAL_NO_DOOR: u32 = 1; +/// The most units an instance keeps state for at once; past it, the oldest is dropped first. +pub const MAX_UNITS: usize = 4096; + +/// One unit on a one-request door, kept from its first piece to the caller's answer. +struct Held { + /// The plane's answers to the unit's pieces. + unit: RequestUnit, + /// What the unit's answer still owes the reply buffer. + owed: Owed, + /// An answer that did not fit its field or arena buffer: the re-call carries the same piece, + /// which is answered from here rather than read twice. + short: Option, +} + /// One open plane instance. pub struct Plane { /// The deployment's public base URL, as `open` read it; a refresh keeps it. @@ -465,9 +492,31 @@ pub struct Plane { generations: Mutex>, /// The published snapshots, held by the SDK until their generation's `retire`. snapshots: Generations, + /// The request units in flight, by the kernel's unit key. + units: Mutex>, } impl Plane { + /// A unit arriving on claim `claim`, under the newest live generation's session params and + /// audience, naming its caller by `caller_ref`. `None` for a claim the plane never published, + /// and for a door that opens a live session. + fn held(&self, claim: u32, caller_ref: Option) -> Option { + let door = Door::of(claim)?; + let generations = lock(&self.generations); + let (cfg, g) = generations.last()?; + let unit = RequestUnit::new( + door, + cfg.session.clone(), + caller_ref, + g.audience().to_string(), + )?; + Some(Held { + unit, + owed: Owed::default(), + short: None, + }) + } + /// Publish generation `generation` into the `out` field `pick` names. fn publish( &self, @@ -526,6 +575,7 @@ slot!(Open, PlaneOpenIn, PlaneOpenOut, |instance, input, out| { public_url, generations: Mutex::new(Vec::new()), snapshots: Generations::new(), + units: Mutex::new(BTreeMap::new()), }; p.publish(&mut out, |o| &o.snapshot, input.open.generation, cfg); instance.open(p); @@ -601,13 +651,157 @@ slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { Outcome::Ready }); -// The door serves no unit's pieces yet: every piece, refusal render, admin serve and projection is -// declined, and a declined unit is charged nothing. +/// The piece `input` carries, in the plane's own vocabulary; `None` for a source the kind does not +/// name. `head` holds the far end's kept response fields, read on its first piece. +fn piece_of<'h, 'a: 'h>( + input: Lent<'a, OnPieceIn>, + head: &'h mut Vec<(&'a [u8], &'a [u8])>, +) -> Option> { + let given = input.get(); + let has_status = given.flags & PIECE_HAS_STATUS != 0; + let from = match given.from { + FROM_CALLER => request_unit::From::Caller, + FROM_FAR_END => request_unit::From::FarEnd, + FROM_KERNEL => request_unit::From::Kernel(given.attempt_no), + _ => return None, + }; + if has_status { + head.extend( + input + .head_fields() + .iter() + .map(|f| (f.field(|f| &f.name).bytes(), f.field(|f| &f.value).bytes())), + ); + } + Some(Piece { + from, + bytes: input.field(|i| &i.bytes).bytes(), + status: has_status.then(|| u16::try_from(given.status_code).unwrap_or(0)), + last: given.flags & PIECE_LAST != 0, + head: head.as_slice(), + }) +} -slot!(OnPiece, OnPieceIn, OnPieceOut, |_, _, _out| { - Outcome::Refused +/// Write the head fields `pairs` (name, value) into the host's field buffer over its arena. +fn push_fields( + fields: &mut HostBuf<'_, OutField>, + arena: &mut HostBuf<'_, u8>, + pairs: &[(&'static str, String)], +) { + for (name, value) in pairs { + fields.push(OutField { + name: arena.span(name.as_bytes()), + value: arena.span(value.as_bytes()), + }); + } +} + +/// Write the plane's `answer` for `held` into the host's buffers. Answers the outcome and whether +/// the unit is done. +fn answer_piece( + held: &mut Held, + answer: Answer, + input: Lent<'_, OnPieceIn>, + out: &mut Out<'_, OnPieceOut>, +) -> (Outcome, bool) { + match answer { + Answer::Nothing => (Outcome::Ready, false), + Answer::Refused => (Outcome::Refused, true), + Answer::ToFarEnd(bytes) => { + held.owed.owe(&bytes, EMIT_TO_FAR_END, input, out); + (Outcome::Ready, false) + } + Answer::Attempt(attempt) => { + let (mut fields, units, mut arena) = + (input.fields_buf(), input.units_buf(), input.arena_buf()); + let verb = arena.span(attempt.verb.as_bytes()); + let target = arena.span(attempt.target.as_bytes()); + push_fields(&mut fields, &mut arena, &attempt.fields); + if piece::settle(out, &fields, &units, &arena) { + held.short = Some(Answer::Attempt(attempt)); + return (Outcome::Failed, false); + } + out.set(|o| &o.verb, verb); + out.set(|o| &o.target, target); + held.owed.owe(&attempt.body, EMIT_TO_FAR_END, input, out); + (Outcome::Ready, false) + } + Answer::ToCaller(reply) => { + let (mut fields, units, mut arena) = + (input.fields_buf(), input.units_buf(), input.arena_buf()); + push_fields(&mut fields, &mut arena, &reply.fields); + if piece::settle(out, &fields, &units, &arena) { + held.short = Some(Answer::ToCaller(reply)); + return (Outcome::Failed, false); + } + out.set(|o| &o.reply_status, u32::from(reply.status)); + let done = held.owed.owe(&reply.body, EMIT_DONE, input, out); + (Outcome::Ready, done) + } + } +} + +// `on_piece`: a one-request door's pieces (the mint, the SDP offer, the metadata document), each +// answered by the unit's [`RequestUnit`]: the ATTEMPT's request with its body, the caller's body to +// the far end, and the caller's answer from the far end's. A finished or refused unit is forgotten. +// A live session's pieces are refused: the kernel's driver serves request units, and a session door +// is answered here once the driver hands a session its pieces. +slot!(OnPiece, OnPieceIn, OnPieceOut, |instance, input, out| { + let Some(p) = instance.get() else { + return Outcome::Failed; + }; + let given = input.get(); + let mut units = lock(&p.units); + if piece::is_recall(input) { + if let Some(held) = units.get_mut(&given.unit) { + if held.owed.pending() { + if held.owed.pay(input, &mut out) { + units.remove(&given.unit); + } + return Outcome::Ready; + } + } + } + if !units.contains_key(&given.unit) { + let caller_ref = input + .field(|i| &i.caller_ref) + .as_str() + .ok() + .filter(|r| !r.is_empty()) + .map(str::to_owned); + let Some(held) = p.held(given.claim, caller_ref) else { + return Outcome::Refused; + }; + while units.len() >= MAX_UNITS { + if units.pop_first().is_none() { + break; + } + } + units.insert(given.unit, held); + } + let Some(held) = units.get_mut(&given.unit) else { + return Outcome::Failed; + }; + let answer = match held.short.take() { + Some(answer) => answer, + None => { + let mut head = Vec::new(); + let Some(piece) = piece_of(input, &mut head) else { + return Outcome::Refused; + }; + held.unit.on_piece(piece) + } + }; + let (outcome, done) = answer_piece(held, answer, input, &mut out); + if done { + units.remove(&given.unit); + } + outcome }); +// The refusal render, the admin serve and the hook projection are declined: a declined unit is +// charged nothing. + slot!(Refusal, RefusalIn, RefusalOut, |_, _, _out| { Outcome::Refused }); diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index cd8d92118c..1d3ca328e6 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -10,15 +10,18 @@ use std::mem::zeroed; use std::sync::Arc; -use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome, BLOB_JSON}; +use busbar_contract::abi::mechanism::call::{ + AbiStr, Blob, Field, Outcome, Span, BLOB_JSON, BLOB_OCTETS, +}; use busbar_contract::abi::mechanism::lifecycle::{ slot as life, CancelIn, CancelOut, GenIn, RefreshIn, TickIn, TickOut, ValidateIn, }; use busbar_contract::abi::plane::{ - slot, ArriveIn, ArriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, - CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, + slot, ArriveIn, ArriveOut, OnPieceIn, OnPieceOut, OutField, PlaneOpenIn, PlaneOpenOut, + PlaneRefreshOut, PlaneSnapshot, UnitCount, CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, EMIT_DONE, + EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, PIECE_HAS_STATUS, PIECE_LAST, }; -use busbar_plane_streaming::door; +use busbar_plane_streaming::{door, driven}; use busbar_plugin_loader::dispatch::kinds::plane::Plane; use busbar_plugin_loader::dispatch::{ in_head, load_dropped, load_linked, out_head, Bind, DispatchConfig, Dispatcher, Frame, @@ -351,3 +354,250 @@ fn the_tail_states_what_the_served_plane_declares() { busbar_contract::abi::plane::check::check_tail(t) .expect("the kind's own check admits the tail"); } + +// ── THE ONE-REQUEST DOORS' PIECES ─────────────────────────────────────────────────────────────── + +/// The section the unit script opens under: the session params a mint locks the browser to. +const SESSION: &[u8] = br#"{"session":{"model":"m-cap"}}"#; +/// The audience [`PUBLIC`] reads to. +const AUDIENCE: &str = "https://gw.example.com/v1/realtime"; +/// The kernel's reference for the caller. +const CALLER_REF: &str = "c0ffee"; +/// The body the served plane sent its far end for that mint (the recorded +/// `streams|mint|client-secrets` cell, `golden/1.6.0-pre`). +const MINT_BODY: &str = + r#"{"expires_after":{"anchor":"created_at","seconds":600},"session":{"model":"m-cap"}}"#; +/// The far end's mint answer. +const MINTED: &[u8] = br#"{"value":"ek_oracle_0001","expires_at":1767225600}"#; +/// The caller's SDP offer, and the far end's answer and where its call lives. +const OFFER: &[u8] = b"v=0\r\no=- 1 1 IN IP4 127.0.0.1\r\ns=oracle-offer\r\nt=0 0\r\n"; +const SDP_ANSWER: &[u8] = b"v=0\r\no=- 2 2 IN IP4 127.0.0.1\r\ns=oracle-answer\r\nt=0 0\r\n"; +const LOCATION: &str = "/v1/realtime/calls/rtc_oracle0001"; + +fn octets(b: &[u8]) -> Blob { + Blob { + ptr: b.as_ptr(), + len: b.len(), + fmt: BLOB_OCTETS, + flags: 0, + } +} + +fn at(buf: &[u8], s: Span) -> String { + String::from_utf8_lossy(&buf[s.offset as usize..(s.offset + s.len) as usize]).into_owned() +} + +/// The host's buffers for one `on_piece`. +struct Bufs { + reply: Vec, + units: [UnitCount; 2], + fields: [OutField; 4], + arena: [u8; 256], +} + +/// One piece pushed to a unit: whose it is, its flags, bytes, status and kept head fields. +struct Push<'a> { + unit: u64, + claim: u32, + from: u32, + flags: u32, + attempt_no: u32, + bytes: &'a [u8], + status: u32, + head: &'a [Field], +} + +impl Bufs { + fn new(reply_cap: usize) -> Self { + Self { + reply: vec![0; reply_cap], + units: [z(); 2], + fields: [z(); 4], + arena: [0; 256], + } + } + + /// `push` answered: the outcome and what it wrote, as text. + fn call(&mut self, p: &Plugin, push: &Push<'_>) -> String { + let mut i: OnPieceIn = z(); + i.head = in_head(); + (i.unit, i.claim, i.from, i.flags) = (push.unit, push.claim, push.from, push.flags); + (i.attempt_no, i.bytes, i.status_code) = (push.attempt_no, octets(push.bytes), push.status); + i.caller_ref = text(CALLER_REF); + if !push.head.is_empty() { + (i.head_fields, i.head_fields_len) = (push.head.as_ptr(), push.head.len()); + } + (i.reply_buf, i.reply_cap) = (self.reply.as_mut_ptr(), self.reply.len()); + (i.units_buf, i.units_cap) = (self.units.as_mut_ptr(), self.units.len()); + (i.fields_buf, i.fields_cap) = (self.fields.as_mut_ptr(), self.fields.len()); + (i.arena_buf, i.arena_cap) = (self.arena.as_mut_ptr(), self.arena.len()); + let mut o: OnPieceOut = z(); + o.head = out_head(); + let mut f = Frame::new(i, o); + let c = p.call(slot::ON_PIECE, &mut f); + let o = f.out; + let fields: Vec = self.fields[..o.fields_written as usize] + .iter() + .map(|f| format!("{}={}", at(&self.arena, f.name), at(&self.arena, f.value))) + .collect(); + format!( + "{:?} emitted={} more={} far={} done={} status={} verb={} target={} fields={fields:?}", + c.outcome, + String::from_utf8_lossy(&self.reply[..o.emitted as usize]), + o.more, + o.flags & EMIT_TO_FAR_END != 0, + o.flags & EMIT_DONE != 0, + o.reply_status, + at(&self.arena, o.verb), + at(&self.arena, o.target), + ) + } +} + +/// A piece of `unit` on `claim`. +const fn push(unit: u64, claim: u32, from: u32, flags: u32, bytes: &[u8]) -> Push<'_> { + Push { + unit, + claim, + from, + flags, + attempt_no: 0, + bytes, + status: 0, + head: &[], + } +} + +/// The ATTEMPT that opens `unit`'s first attempt. +const fn attempt(unit: u64, claim: u32) -> Push<'static> { + Push { + attempt_no: 1, + ..push(unit, claim, FROM_KERNEL, 0, &[]) + } +} + +/// THE UNIT SCRIPT: a mint, an SDP offer, a metadata read written 16 bytes at a time, a session +/// door's piece and an unpublished claim's, in the order the route pump pushes them. +fn unit_script(p: &Plugin) -> Vec { + let mut t = vec![format!("open {:?}", open(p, 1, SESSION, Some(PUBLIC)).0)]; + let mut b = Bufs::new(256); + let whole = PIECE_HAS_STATUS | PIECE_LAST; + + t.push(format!("mint attempt {}", b.call(p, &attempt(7, 0)))); + let caller = push(7, 0, FROM_CALLER, PIECE_LAST, &[]); + t.push(format!("mint caller {}", b.call(p, &caller))); + let far = Push { + status: 200, + ..push(7, 0, FROM_FAR_END, whole, MINTED) + }; + t.push(format!("mint far_end {}", b.call(p, &far))); + + t.push(format!("sdp attempt {}", b.call(p, &attempt(8, 1)))); + let offer = push(8, 1, FROM_CALLER, PIECE_LAST, OFFER); + t.push(format!("sdp caller {}", b.call(p, &offer))); + let location = [Field { + name: text("location"), + value: text(LOCATION), + }]; + let far = Push { + status: 201, + head: &location, + ..push(8, 1, FROM_FAR_END, whole, SDP_ANSWER) + }; + t.push(format!("sdp far_end {}", b.call(p, &far))); + + let mut narrow = Bufs::new(16); + t.push(format!("metadata {}", narrow.call(p, &attempt(9, 5)))); + for _ in 0..8 { + let line = narrow.call(p, &push(9, 5, FROM_KERNEL, 0, &[])); + let done = line.contains("done=true"); + t.push(format!("metadata more {line}")); + if done { + break; + } + } + + t.push(format!("sideband {}", b.call(p, &attempt(10, 2)))); + t.push(format!("unpublished {}", b.call(p, &attempt(11, 9)))); + t +} + +/// What the unit script must read: the served plane's answers, through the door. +fn unit_expected() -> Vec { + let none = "verb= target= fields=[]"; + let minted = driven::mint_reply(200, MINTED); + let metadata = driven::metadata_reply(AUDIENCE); + let mut t = vec![ + "open Ready".to_string(), + format!( + "mint attempt Ready emitted={MINT_BODY} more=0 far=true done=false status=0 \ + verb=POST target=/v1/realtime/client_secrets \ + fields=[\"content-type=application/json\", \"OpenAI-Safety-Identifier={CALLER_REF}\"]" + ), + format!("mint caller Ready emitted= more=0 far=false done=false status=0 {none}"), + format!( + "mint far_end Ready emitted={} more=0 far=false done=true status=200 verb= target= \ + fields=[\"content-type=application/json\"]", + String::from_utf8_lossy(&minted.body) + ), + "sdp attempt Ready emitted= more=0 far=true done=false status=0 verb=POST \ + target=/v1/realtime/calls fields=[\"content-type=application/sdp\"]" + .to_string(), + format!( + "sdp caller Ready emitted={} more=0 far=true done=false status=0 {none}", + String::from_utf8_lossy(OFFER) + ), + format!( + "sdp far_end Ready emitted={} more=0 far=false done=true status=201 verb= target= \ + fields=[\"content-type=application/sdp\", \"location={LOCATION}\"]", + String::from_utf8_lossy(SDP_ANSWER) + ), + ]; + let chunks: Vec<&[u8]> = metadata.body.chunks(16).collect(); + for (n, chunk) in chunks.iter().enumerate() { + let last = n + 1 == chunks.len(); + let (lead, status, fields) = if n == 0 { + ( + "metadata", + 200, + "[\"cache-control=public, max-age=3600\", \ + \"content-type=application/json; charset=utf-8\"]", + ) + } else { + ("metadata more", 0, "[]") + }; + t.push(format!( + "{lead} Ready emitted={} more={} far=false done={last} status={status} verb= target= \ + fields={fields}", + String::from_utf8_lossy(chunk), + u32::from(!last), + )); + } + t.push(format!( + "sideband Refused emitted= more=0 far=false done=false status=0 {none}" + )); + t.push(format!( + "unpublished Refused emitted= more=0 far=false done=false status=0 {none}" + )); + t +} + +#[test] +fn the_one_request_doors_answer_their_pieces_through_the_door() { + let d = Dispatcher::new(DispatchConfig::default()); + assert_eq!(unit_script(&linked(&d)), unit_expected()); +} + +#[test] +fn the_dropped_door_answers_the_one_request_doors_as_the_linked_door_does() { + let d = Dispatcher::new(DispatchConfig::default()); + let Some(dropped) = dropped(&d) else { + eprintln!("skip: the streaming_door example cdylib is not built"); + return; + }; + assert_eq!( + unit_script(&dropped), + unit_script(&linked(&d)), + "the two doors must answer identically" + ); +} From 3d61a797a4f872c7a6d572138c9a7b54967db1c7 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:11:56 -0700 Subject: [PATCH 12/82] streaming fold: the streaming plane's door is linked on the plane-door axis under the development-only switch streaming-on-driver, bound through the loader's one load BUSBAR-1.6.0.md Part 3 section 12, "The switch": a development-only cargo feature flips a plane onto the driver during its fold and is deleted when the fold completes. The row follows the decisions door row (#398, merged in here; this branch lands after #192 and #398): streaming-on-driver = ["plane-streaming"], linked crate busbar-plane-streaming (already a root edge), axis plane-door, entry busbar_plane_streaming::door. Never in default. With it on, the busbar-voice row stays linked and serves every streaming route until the serve path hands the door the arrivals it takes. TRANSITIONAL: deleted with busbar-voice. The root's busbar-plane-streaming comment named a "root streaming unit" that does not exist; it now names the switch's row. Test: every_plane_switch_links_its_door_and_the_default_build_does_not, table-driven over every plane-door row whose feature is not in default (decisions' and streaming's today), so the llm switch's row is covered by the same test when it lands. (cherry picked from commit 187da27616ce8b66971474b4869afd1030a4b247) --- crates/busbar-plane-streaming/src/door.rs | 6 ++++++ crates/busbar/Cargo.toml | 15 +++++++++++++-- .../composition_root_names_no_linked_plugin.rs | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index b6e1d5751b..8ccabb8e8f 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -4,6 +4,12 @@ //! THE STREAMING PLANE'S DOOR: the one function a compiled-in row holds and a dropped-in image //! exports (`examples/streaming_door.rs`), and everything it states and answers. //! +//! The composition root links [`door`] on its `plane-door` axis under the streaming fold's +//! development-only switch `streaming-on-driver` (`BUSBAR-1.6.0.md` Part 3, section 12, "The +//! switch") and binds it through the loader's one load beside the dropped-in plane doors. The +//! default build links no streaming door, and with the switch on the `busbar-voice` row still serves +//! every streaming route until the serve path hands this door the arrivals it takes. +//! //! ## What the plane states once //! //! The Statement tail ([`TAIL`]): the `streams:` section it declares (and the `providers:` section diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index b7982f35e9..a0c9935948 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -97,8 +97,8 @@ busbar-kernel-breaker = { path = "../busbar-kernel-breaker" } busbar-kernel-wal = { path = "../busbar-kernel-wal" } busbar-kernel-audit = { path = "../busbar-kernel-audit" } # The pure planes reach the boot seal through their host's linked entry (`PLANE` + `CLAIMS` on the -# `claims` axis), so the root names none of them. The streaming plane's edge stays for the root's own -# streaming unit, which builds its node over the pure plane. +# `claims` axis), so the root names none of them. The streaming plane's edge is the crate the +# `streaming-on-driver` switch's `plane-door` row names. busbar-plane-streaming = { path = "../busbar-plane-streaming" } # THE DECISIONS PLANE (jev), #48's fifth — and the one plane crate that is BOTH halves at once. Its # four siblings above are PURE planes whose registry declaration is carried by a separate impure @@ -295,6 +295,14 @@ plane-decisions-door = ["plane-decisions"] # (#2, #30; THE DESIGN section 11.4). It carries `proto-llm`, whose row stays linked and answers # every arrival the serve path does not hand the door. llm-on-driver = ["proto-llm", "dep:busbar-plane-llm"] +# THE STREAMING PLANE THROUGH ITS DOOR, the streaming fold's development-only switch +# (BUSBAR-1.6.0.md Part 3 section 12, "The switch"). TRANSITIONAL: deleted with `busbar-voice`, +# when the streaming fold completes. Never in `default`. On, the plane's memory-ABI door +# (`busbar_plane_streaming::door::door`) is a `plane-door` row, bound through the loader's one load +# beside every dropped-in plane door, the same table as its dropped-in cdylib (#2, #30; THE DESIGN +# section 11.4). It carries `plane-streaming`, whose `busbar-voice` row stays linked and serves every +# streaming route until the serve path hands the door the arrivals it takes. +streaming-on-driver = ["plane-streaming"] # THE REQUEST-LOG FILE SINK, DEFAULT ON: the `busbar-export-file` crate on the export axis. Off, the # module resolves only when a signed tarball of the sink is dropped into `plugins/`. export-file = ["dep:busbar-export-file"] @@ -389,6 +397,7 @@ transport-stdio = "busbar-transport-stdio" # loader's one load beside the dropped-in plane doors. plane-decisions-door = "busbar-plane-decisions" llm-on-driver = "busbar-plane-llm" +streaming-on-driver = "busbar-plane-streaming" # What each linked feature's crate registers (keyed by the feature, as the linked row is), axis by axis: `plane` (PLANE_DECLARATION + PLANE_HOOKS, joined # kernel-side), `protocols`, `path-ingress`, `body-ingress`, `protocol-seams`, `diagnostics`, @@ -431,6 +440,7 @@ transport-grpc = "transport" transport-stdio = "transport" plane-decisions-door = "plane-door" llm-on-driver = "plane-door" +streaming-on-driver = "plane-door" # A linked crate whose entry module is not `::linked`: the decisions plane is a PURE plane # crate whose manifest names `busbar-contract` and nothing else (#40), so its kernel-typed hooks are @@ -452,6 +462,7 @@ llm-on-driver = "busbar_plane_llm::plane_door" # A memory-ABI export sink's door is its logic crate's `door` module's (`plugin_door!`). busbar-export-prometheus = "busbar_export_prometheus::door" busbar-export-otlp = "busbar_export_otlp::door" +streaming-on-driver = "busbar_plane_streaming::door" # The key an `auths` row answers on the auth axis — the name its signed tarball's manifest states. [package.metadata.busbar.linked-name] diff --git a/crates/busbar/tests/composition_root_names_no_linked_plugin.rs b/crates/busbar/tests/composition_root_names_no_linked_plugin.rs index 6d8d0f7c92..274f3f6070 100644 --- a/crates/busbar/tests/composition_root_names_no_linked_plugin.rs +++ b/crates/busbar/tests/composition_root_names_no_linked_plugin.rs @@ -410,7 +410,7 @@ fn every_on_driver_switch_links_its_door_and_the_default_build_does_not() { let switched = doors_of(&|f: &str| f == switch || default.iter().any(|d| d == f)); let added: Vec<&str> = switched .split(", ") - .filter(|d| d.ends_with("::plane_door::door") && !shipped.contains(*d)) + .filter(|d| d.ends_with("::door") && !shipped.contains(*d)) .collect(); assert!( !added.is_empty(), From efd763a98a6aefc2bc5663451bc40a2532eb3059 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 17:58:05 -0700 Subject: [PATCH 13/82] streaming fold: the piece answer stays the streaming door's own (no contract ceiling moves); the switch's prose names no legacy crate and no plane instance it does not need Measured on Latchkey (cli-5f45b31f) after the fold branches and predev were merged: - construction: busbar-contract 29998 over its 29926 ceiling, the +72 being abi::sdk::piece. The module moves to busbar-plane-streaming/src/piece.rs, private to the door, unchanged in substance; whether the SDK takes it for every plane door is 1.6.0-QUESTIONS.md PIECE-HOME. - kind-isolation --posture: busbar x legacy 85 -> 87 and busbar-plane-streaming x legacy 24 -> 25 (the switch comment and the door doc named the legacy crate), busbar x plane 787 -> 792 (the switch comment and the switch test's prose). Q77 (owner 2026-09-25): risen rows wait for the final ledger pass, so they are drained here rather than re-armed: the comment, the door doc and the test name only the feature and the axis. (cherry picked from commit 276afc55d97f0976674b98a16ae8742c8606e534) --- crates/busbar-contract/src/abi/sdk/mod.rs | 4 ---- crates/busbar-plane-streaming/src/door.rs | 14 +++++++------- crates/busbar-plane-streaming/src/lib.rs | 2 ++ .../sdk => busbar-plane-streaming/src}/piece.rs | 11 ++++++----- crates/busbar/Cargo.toml | 12 +++++------- docs/design/1.6.0-QUESTIONS.md | 1 + 6 files changed, 21 insertions(+), 23 deletions(-) rename crates/{busbar-contract/src/abi/sdk => busbar-plane-streaming/src}/piece.rs (87%) diff --git a/crates/busbar-contract/src/abi/sdk/mod.rs b/crates/busbar-contract/src/abi/sdk/mod.rs index 7a26e3d823..3abf8d165b 100644 --- a/crates/busbar-contract/src/abi/sdk/mod.rs +++ b/crates/busbar-contract/src/abi/sdk/mod.rs @@ -77,10 +77,6 @@ pub mod store; // PUBLISHED GENERATION DATA: the SDK owns what a plugin publishes (`abi::sdk::publish`). pub mod publish; pub use publish::{Generations, Keyed}; - -// THE PLANE KIND'S PIECE ANSWER: what an `on_piece` answer owes the reply buffer across `more = 1` -// re-calls, and the one settle of its field, unit and arena buffers (`abi::sdk::piece`). -pub mod piece; // THE LIFECYCLE, ONCE FOR EVERY KIND: the nine lifecycle slots over a kind's `Life` (`abi::sdk::life`). pub mod life; diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 8ccabb8e8f..4edd2d0a1a 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -4,11 +4,11 @@ //! THE STREAMING PLANE'S DOOR: the one function a compiled-in row holds and a dropped-in image //! exports (`examples/streaming_door.rs`), and everything it states and answers. //! -//! The composition root links [`door`] on its `plane-door` axis under the streaming fold's -//! development-only switch `streaming-on-driver` (`BUSBAR-1.6.0.md` Part 3, section 12, "The -//! switch") and binds it through the loader's one load beside the dropped-in plane doors. The -//! default build links no streaming door, and with the switch on the `busbar-voice` row still serves -//! every streaming route until the serve path hands this door the arrivals it takes. +//! The composition root links [`door`] on its `plane-door` axis under this fold's development-only +//! switch (`BUSBAR-1.6.0.md` Part 3, section 12, "The switch") and binds it through the loader's one +//! load beside the dropped-in plane doors. The default build links no door of this plane, and with +//! the switch on the legacy row still serves every route until the serve path hands this door the +//! arrivals it takes. //! //! ## What the plane states once //! @@ -35,7 +35,7 @@ //! A one-request door (the mint, the SDP offer, the metadata document) answers its pieces through //! a [`RequestUnit`] the instance keeps by the kernel's unit key, built at the unit's first piece //! over the newest live generation's session params and audience. Its answer's bytes are paid into -//! the reply buffer across `more = 1` re-calls (`busbar_contract::abi::sdk::piece`). A session +//! the reply buffer across `more = 1` re-calls ([`crate::piece`]). A session //! door's pieces are refused: the kernel's driver serves request units only. use std::collections::BTreeMap; @@ -43,6 +43,7 @@ use std::mem::size_of; use std::ptr; use std::sync::Mutex; +use crate::piece::{self, Owed}; use busbar_contract::abi::host::conn::connector::{Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND}; use busbar_contract::abi::mechanism::call::{AbiStr, Blob, InHead, OutHead, Outcome, BLOB_ABSENT}; use busbar_contract::abi::mechanism::door::{ @@ -60,7 +61,6 @@ use busbar_contract::abi::plane::{ SHAPE_PIECEWISE, UNITS_ESTIMATED, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; -use busbar_contract::abi::sdk::piece::{self, Owed}; use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; use busbar_contract::abi::sdk::{Generations, HostBuf, Instance, Lent, Out, Safe, SafeSlot}; use busbar_contract::plane::{PER_SESSION, TOKEN_FAMILY}; diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index dd26dcc390..95c120ca89 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -58,6 +58,8 @@ pub mod driven; pub mod governed; pub mod meta; pub mod open_calls; +/// What an `on_piece` answer owes the reply buffer across re-calls, and the one settle of its buffers. +mod piece; pub mod provider; pub mod register; pub mod request_unit; diff --git a/crates/busbar-contract/src/abi/sdk/piece.rs b/crates/busbar-plane-streaming/src/piece.rs similarity index 87% rename from crates/busbar-contract/src/abi/sdk/piece.rs rename to crates/busbar-plane-streaming/src/piece.rs index 1e3c10ad3a..08b89a7d03 100644 --- a/crates/busbar-contract/src/abi/sdk/piece.rs +++ b/crates/busbar-plane-streaming/src/piece.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! THE PLANE KIND'S PIECE ANSWER, PLUGIN SIDE: what an `on_piece` answer owes the host's reply +//! THIS PLANE'S PIECE ANSWER: what an `on_piece` answer owes the host's reply //! buffer, and the one settle of the answer's field, unit and arena buffers. //! //! `BUSBAR-1.6.0.md` Part 3, section 12, "The route pump": a full reply buffer answers `more = 1`; @@ -9,11 +9,12 @@ //! bytes ([`is_recall`]). An answer that does not fit its field, unit or arena buffer is short as a //! whole ([`settle`]): the slot answers FAILED, and the kernel re-calls with the same piece and room. //! -//! One home for every plane door: a plane keeps an [`Owed`] per unit and pays it across the -//! re-calls, so the backpressure rule (`EMIT_DONE` only with the last byte) is written once. +//! The door keeps an [`Owed`] per unit and pays it across the re-calls, so the backpressure rule +//! (`EMIT_DONE` only with the last byte) is written once in this plane. Each plane door carries +//! this logic today; one SDK home for all of them is 1.6.0-QUESTIONS.md PIECE-HOME. -use crate::abi::plane::{OnPieceIn, OnPieceOut, OutField, UnitCount, EMIT_DONE}; -use crate::abi::sdk::{HostBuf, Lent, Out}; +use busbar_contract::abi::plane::{OnPieceIn, OnPieceOut, OutField, UnitCount, EMIT_DONE}; +use busbar_contract::abi::sdk::{HostBuf, Lent, Out}; /// Whether `input` is the kernel's re-call after a `more = 1` answer: no bytes, no flags and no /// attempt. diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index a0c9935948..1a6b7c0d5c 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -295,13 +295,11 @@ plane-decisions-door = ["plane-decisions"] # (#2, #30; THE DESIGN section 11.4). It carries `proto-llm`, whose row stays linked and answers # every arrival the serve path does not hand the door. llm-on-driver = ["proto-llm", "dep:busbar-plane-llm"] -# THE STREAMING PLANE THROUGH ITS DOOR, the streaming fold's development-only switch -# (BUSBAR-1.6.0.md Part 3 section 12, "The switch"). TRANSITIONAL: deleted with `busbar-voice`, -# when the streaming fold completes. Never in `default`. On, the plane's memory-ABI door -# (`busbar_plane_streaming::door::door`) is a `plane-door` row, bound through the loader's one load -# beside every dropped-in plane door, the same table as its dropped-in cdylib (#2, #30; THE DESIGN -# section 11.4). It carries `plane-streaming`, whose `busbar-voice` row stays linked and serves every -# streaming route until the serve path hands the door the arrivals it takes. +# The next plane's development-only switch, the same shape as the one above (BUSBAR-1.6.0.md Part 3 +# section 12, "The switch"). TRANSITIONAL: deleted when its fold completes; never in `default`. On, +# the plane's memory-ABI door is a `plane-door` row bound through the loader's one load beside every +# dropped-in plane door (#2, #30; THE DESIGN section 11.4), and the legacy row it carries keeps +# serving every route until the serve path hands the door the arrivals it takes. streaming-on-driver = ["plane-streaming"] # THE REQUEST-LOG FILE SINK, DEFAULT ON: the `busbar-export-file` crate on the export axis. Off, the # module resolves only when a signed tarball of the sink is dropped into `plugins/`. diff --git a/docs/design/1.6.0-QUESTIONS.md b/docs/design/1.6.0-QUESTIONS.md index 819f0d4516..4b555e930d 100644 --- a/docs/design/1.6.0-QUESTIONS.md +++ b/docs/design/1.6.0-QUESTIONS.md @@ -193,6 +193,7 @@ reading this cold, after the fact, with no memory of your slot. | CONF-SUITE-C3 | NO-TEST-PLUGINS (CONF-SUITE, 2026-10-03) | **C.3's both-ways items the published suite does not yet hold.** THE DESIGN C.3 lists: (2) the dropped leg through the real stage-and-verify path in a process with no runtime entered; (3) a forced not-ready then wake then resume, a cancel, driver tickets; (4) byte comparison of envelopes and the `/metrics` exposition; (5) crossing counters in the SDK slot shims equal to the dispatcher's; (6) RED arms for a manifest missing `mechanism_version`, a dev-era cold v3 artifact, a static in the contract, a forbidden closure dependency | whether these are owed by the published suite (run by every plugin) or by busbar's own loader-mechanics tests, now that busbar tests no plugin | the suite holds: both legs through the one loader (`load_linked`/`load_dropped`, the signed rendering checked), exact per-step crossings at the DISPATCHER's gate, the folds equal, the kind's contract over the answers, RED for a moved count, kind ABI +-1 both legs and a failing `ready`; a kind script adds pend/resume, cancel or envelope steps where its kind's ops have them. (5) is dropped: a plugin's SDK shim counter is plugin memory the host cannot read on the dropped leg; the dispatcher's gate is the one counter both legs share. (2), (6) and the no-runtime process are loader mechanics busbar can prove without a plugin (`load_dropped_bytes` refusals) | a fleet repo green on the suite while a C.3 item is unproven: the item regresses unseen until a busbar-side mechanics test owns it | | Q-SW-P2 | SERVE-WIRE P2 (TODO U6-U7, 2026-10-02) | the door planes' data route: how the kernel's data door hands a claimed request to a door plane, and the kernel steps and far end a door plane's unit runs with before its serving slice | (1) The spec gives the ONE composition (`root/serve.rs`, K5) and the transitional caller side (`IngressCaller`, option C) but no seam on the kernel's hyper loop for a door plane versus a HOT plane. (2) No production kernel steps or far end exist for a door plane's unit: VERIFY's destination seal, the `$` admission (`try_admit_estimated`, step 17), meter, the node posting (`NodeEndPost` open/audited/close, `plane_node.rs`) and the plane's `Egress` (`EgressFarEnd` over the connector, U1-U5) are unbuilt, and the `$` part may not ship in a non-`$` lane. (3) Whether a door plane's unit belongs in the node's in-flight table. | (1) Closest to the admin table (`plane_driver::serve::answer`, K-SERVE): the data router's fallback (`ingress::protocol_dispatch`) offers each request first to ONE root-mounted data door (`plane_driver::serve::{DataRequest, mount_data, claimed}`); the root matches the composed planes' snapshot claims (verb, exact or prefix target; the carrier is not compared: the fallback is the one data door) and answers through the plane's driver, or hands the request back whole. HOT planes are untouched. A plane is served exactly when composed (its door row is its serve switch, K5); nothing mounts when no composed plane claims a route. (2) `DoorSteps` (TRANSITIONAL, `root/serve.rs`): authenticate reads the auth gate's verdict (the llm step's mapping), VERIFY refuses `NoDestination` and nothing after it runs (no hold, charge, meter or dial); `NoEgress` is the unreachable far end. The plane's `arrive` decodes and its `refusal` renders. (3) The loop runs on `serve.rs`'s own kernel, gauge and canary from a zero arrival hold; the end is not posted (nothing was held or charged). | Low for the default build: no door row is linked in it, so nothing mounts and no byte moves. If (1) is wrong, the seam moves into the kernel router (one fallback line and one table). If (2)/(3) are wrong, step 33 replaces `DoorSteps`/`NoEgress` and moves the drive onto the node; until it lands a door plane answers every claimed request with its own rendering of the no-destination refusal (503), behind its development-only switch. | | H2-U10-OPID | H2-U10 (lane-h2-u10, 2026-10-02) — **ANSWERED (ARCHITECT 2026-10-03, citing §11): keep the individual, retry-safe puts; NO store-ABI slot (the store ABI is locked, §11); idempotent individual puts meet H4's intent, exactly-once under retry** | 6 + §11.11 H4 ("coalesced batches, one `op_id` per batch, durable dedupe retention") | the store kind has no batched record write and `RECORD_PUT` carries no `op_id` (store v3 slot 40, `RecordPutIn`), so a plane-record batch cannot be ONE op_id-deduped crossing without a new store slot, which is a contract seam this lane does not own. Related: `RECORD_PUT` is `Call` class, not `WriteBehind`, which is consistent only because a piece waits on its write | kept the batcher as it is: a batch is up to `BATCH_CAP` puts in order, each answered only once the store took it; a put is an upsert of a whole value, so a retried put is idempotent without an op_id. Bounded (QUEUE_CAP, BATCH_CAP, the new 1 s `KernelServices::flushes` cadence) and the crash window documented in `host_records.rs` | if H4 means a store-side batch slot with op_id dedupe: a store ABI v3 append (new slot + `op_id` in its in-shape) and one change in `WriteBehind::flush`; no behaviour a caller sees changes | +| PIECE-HOME | FOLD-STREAMING (2026-10-02) | the plane door's piece answer: the owed reply across `more = 1` re-calls (`EMIT_DONE` only with the last byte), the re-call test and the three-buffer settle of an `on_piece` answer. The llm, mcp, a2a and decisions doors each carry a private copy; the streaming door is the fifth | whether `busbar_contract::abi::sdk::piece` takes it: that moves busbar-contract 29926 -> 29998, over `contract_caps` / `caps_contract_ceiling`, which I may not re-arm; each plane door would then delete its copy in its own lane | kept it private to the streaming door (`busbar-plane-streaming/src/piece.rs`, written to be lifted verbatim), so no ceiling moves | low: a fifth ~100-line copy until the ruling (a SIZE-CLEAN 36b clone group) | ### OWNER ANSWERS 2026-09-30 - STREAMS-ORACLE (streams|* oracle family baseline) → option A: record the streams family as `golden/1.6.0-pre` from a pinned pre-fold predev build (`--baseline-version`); the fold is proven against the unfolded plane. The 1.5.5 golden stays untouched for every other family. From f46a956040795dc0dca35816f834926da7b4c667 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:17:38 -0700 Subject: [PATCH 14/82] QUESTIONS: PIECE-HOME struck (ARCHITECT): line count is no CI check (OWNER 2026-10-02); after train/1 the piece answer moves to busbar-contract abi::sdk::piece and the five plane doors drop their copies (PIECE-SHARE) (cherry picked from commit 2622ebb27c5ea1a3f4ee21ea8414646b4746d18f) --- docs/design/1.6.0-QUESTIONS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/design/1.6.0-QUESTIONS.md b/docs/design/1.6.0-QUESTIONS.md index 4b555e930d..819f0d4516 100644 --- a/docs/design/1.6.0-QUESTIONS.md +++ b/docs/design/1.6.0-QUESTIONS.md @@ -193,7 +193,6 @@ reading this cold, after the fact, with no memory of your slot. | CONF-SUITE-C3 | NO-TEST-PLUGINS (CONF-SUITE, 2026-10-03) | **C.3's both-ways items the published suite does not yet hold.** THE DESIGN C.3 lists: (2) the dropped leg through the real stage-and-verify path in a process with no runtime entered; (3) a forced not-ready then wake then resume, a cancel, driver tickets; (4) byte comparison of envelopes and the `/metrics` exposition; (5) crossing counters in the SDK slot shims equal to the dispatcher's; (6) RED arms for a manifest missing `mechanism_version`, a dev-era cold v3 artifact, a static in the contract, a forbidden closure dependency | whether these are owed by the published suite (run by every plugin) or by busbar's own loader-mechanics tests, now that busbar tests no plugin | the suite holds: both legs through the one loader (`load_linked`/`load_dropped`, the signed rendering checked), exact per-step crossings at the DISPATCHER's gate, the folds equal, the kind's contract over the answers, RED for a moved count, kind ABI +-1 both legs and a failing `ready`; a kind script adds pend/resume, cancel or envelope steps where its kind's ops have them. (5) is dropped: a plugin's SDK shim counter is plugin memory the host cannot read on the dropped leg; the dispatcher's gate is the one counter both legs share. (2), (6) and the no-runtime process are loader mechanics busbar can prove without a plugin (`load_dropped_bytes` refusals) | a fleet repo green on the suite while a C.3 item is unproven: the item regresses unseen until a busbar-side mechanics test owns it | | Q-SW-P2 | SERVE-WIRE P2 (TODO U6-U7, 2026-10-02) | the door planes' data route: how the kernel's data door hands a claimed request to a door plane, and the kernel steps and far end a door plane's unit runs with before its serving slice | (1) The spec gives the ONE composition (`root/serve.rs`, K5) and the transitional caller side (`IngressCaller`, option C) but no seam on the kernel's hyper loop for a door plane versus a HOT plane. (2) No production kernel steps or far end exist for a door plane's unit: VERIFY's destination seal, the `$` admission (`try_admit_estimated`, step 17), meter, the node posting (`NodeEndPost` open/audited/close, `plane_node.rs`) and the plane's `Egress` (`EgressFarEnd` over the connector, U1-U5) are unbuilt, and the `$` part may not ship in a non-`$` lane. (3) Whether a door plane's unit belongs in the node's in-flight table. | (1) Closest to the admin table (`plane_driver::serve::answer`, K-SERVE): the data router's fallback (`ingress::protocol_dispatch`) offers each request first to ONE root-mounted data door (`plane_driver::serve::{DataRequest, mount_data, claimed}`); the root matches the composed planes' snapshot claims (verb, exact or prefix target; the carrier is not compared: the fallback is the one data door) and answers through the plane's driver, or hands the request back whole. HOT planes are untouched. A plane is served exactly when composed (its door row is its serve switch, K5); nothing mounts when no composed plane claims a route. (2) `DoorSteps` (TRANSITIONAL, `root/serve.rs`): authenticate reads the auth gate's verdict (the llm step's mapping), VERIFY refuses `NoDestination` and nothing after it runs (no hold, charge, meter or dial); `NoEgress` is the unreachable far end. The plane's `arrive` decodes and its `refusal` renders. (3) The loop runs on `serve.rs`'s own kernel, gauge and canary from a zero arrival hold; the end is not posted (nothing was held or charged). | Low for the default build: no door row is linked in it, so nothing mounts and no byte moves. If (1) is wrong, the seam moves into the kernel router (one fallback line and one table). If (2)/(3) are wrong, step 33 replaces `DoorSteps`/`NoEgress` and moves the drive onto the node; until it lands a door plane answers every claimed request with its own rendering of the no-destination refusal (503), behind its development-only switch. | | H2-U10-OPID | H2-U10 (lane-h2-u10, 2026-10-02) — **ANSWERED (ARCHITECT 2026-10-03, citing §11): keep the individual, retry-safe puts; NO store-ABI slot (the store ABI is locked, §11); idempotent individual puts meet H4's intent, exactly-once under retry** | 6 + §11.11 H4 ("coalesced batches, one `op_id` per batch, durable dedupe retention") | the store kind has no batched record write and `RECORD_PUT` carries no `op_id` (store v3 slot 40, `RecordPutIn`), so a plane-record batch cannot be ONE op_id-deduped crossing without a new store slot, which is a contract seam this lane does not own. Related: `RECORD_PUT` is `Call` class, not `WriteBehind`, which is consistent only because a piece waits on its write | kept the batcher as it is: a batch is up to `BATCH_CAP` puts in order, each answered only once the store took it; a put is an upsert of a whole value, so a retried put is idempotent without an op_id. Bounded (QUEUE_CAP, BATCH_CAP, the new 1 s `KernelServices::flushes` cadence) and the crash window documented in `host_records.rs` | if H4 means a store-side batch slot with op_id dedupe: a store ABI v3 append (new slot + `op_id` in its in-shape) and one change in `WriteBehind::flush`; no behaviour a caller sees changes | -| PIECE-HOME | FOLD-STREAMING (2026-10-02) | the plane door's piece answer: the owed reply across `more = 1` re-calls (`EMIT_DONE` only with the last byte), the re-call test and the three-buffer settle of an `on_piece` answer. The llm, mcp, a2a and decisions doors each carry a private copy; the streaming door is the fifth | whether `busbar_contract::abi::sdk::piece` takes it: that moves busbar-contract 29926 -> 29998, over `contract_caps` / `caps_contract_ceiling`, which I may not re-arm; each plane door would then delete its copy in its own lane | kept it private to the streaming door (`busbar-plane-streaming/src/piece.rs`, written to be lifted verbatim), so no ceiling moves | low: a fifth ~100-line copy until the ruling (a SIZE-CLEAN 36b clone group) | ### OWNER ANSWERS 2026-09-30 - STREAMS-ORACLE (streams|* oracle family baseline) → option A: record the streams family as `golden/1.6.0-pre` from a pinned pre-fold predev build (`--baseline-version`); the fold is proven against the unfolded plane. The 1.5.5 golden stays untouched for every other family. From 63173458db5e048384e5406f7452c8094f0330ca Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:22:04 -0700 Subject: [PATCH 15/82] busbar-plane-streaming: a request unit's plane state is opened (RequestUnit::open), not a Unit minted plane-pricing-blindness:unit-key (promote run 37088191095, shard 1) read door.rs's `RequestUnit::new(` as `Unit::new`, the kernel-sealed constructor that attaches a UnitKey. The door mints no unit identity: RequestUnit is the plane's own state for a unit the kernel already keyed, held by that key. The constructor is named for what it does, `open`, as SessionUnit's is; no money act moves and none is added. (cherry picked from commit badd3dbf78b0f5c1f36b655d35e217b2a03c57e3) --- crates/busbar-plane-streaming/src/door.rs | 2 +- crates/busbar-plane-streaming/src/request_unit.rs | 7 ++++--- .../busbar-plane-streaming/src/tests/request_unit_tests.rs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 4edd2d0a1a..1c5a0dbb78 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -510,7 +510,7 @@ impl Plane { let door = Door::of(claim)?; let generations = lock(&self.generations); let (cfg, g) = generations.last()?; - let unit = RequestUnit::new( + let unit = RequestUnit::open( door, cfg.session.clone(), caller_ref, diff --git a/crates/busbar-plane-streaming/src/request_unit.rs b/crates/busbar-plane-streaming/src/request_unit.rs index cc06f81598..d33139d046 100644 --- a/crates/busbar-plane-streaming/src/request_unit.rs +++ b/crates/busbar-plane-streaming/src/request_unit.rs @@ -78,11 +78,12 @@ pub struct RequestUnit { } impl RequestUnit { - /// A unit on `door`, minting over the `locked` session params, naming the caller by - /// `caller_ref` (the kernel's reference, never the principal) and answering the metadata + /// Open the plane's state for one kernel unit on `door` (the unit's identity is the kernel's; + /// the door keys this state by it): minting over the `locked` session params, naming the caller + /// by `caller_ref` (the kernel's reference, never the principal) and answering the metadata /// document for `audience`. `None` for a door that opens a live session. #[must_use] - pub fn new( + pub fn open( door: Door, locked: SessionConfig, caller_ref: Option, diff --git a/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs b/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs index 12ec472b2d..148b24d039 100644 --- a/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs @@ -10,7 +10,7 @@ use crate::driven::MINT_FAILED_STATUS; const AUDIENCE: &str = "https://gw.example/v1/realtime"; fn unit(door: Door, caller_ref: Option<&str>) -> RequestUnit { - RequestUnit::new( + RequestUnit::open( door, SessionConfig::default(), caller_ref.map(str::to_owned), @@ -54,7 +54,7 @@ fn reply(a: Answer) -> Reply { #[test] fn a_session_door_is_no_request_unit() { for door in [Door::Sideband, Door::Gemini, Door::Twilio] { - assert!(RequestUnit::new(door, SessionConfig::default(), None, String::new()).is_none()); + assert!(RequestUnit::open(door, SessionConfig::default(), None, String::new()).is_none()); } } From 5d3d46eb70697f7a761ee77422a9ce9736f5f8bb Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:22:46 -0700 Subject: [PATCH 16/82] busbar-voice: the protected-resource route's unreachable 500 names the route, not the plane instance kind-isolation:matrix busbar-voice x plane read 871 against predev's 870 on promote run 37088191095; the one added hit is this lane's new handler text. Q77: drained, not re-armed. (cherry picked from commit f37b012a22f588f94487772a7db1d3aff755d6a4) --- crates/busbar-voice/src/mount.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index aae95753f9..e2be8c96d9 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -628,7 +628,7 @@ async fn protected_resource_route(ctx: PlaneReqCtx) -> axum::response::Response let Some(mount) = ctx.slot.downcast_ref::() else { return refusal( axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "streaming route reached without its dispatch slot", + "the metadata route reached without its dispatch slot", ); }; protocol::metadata(&protocol::Metadata { From 74df4c5b527426a3d6772b7c0efd5ee953f7bc4c Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 18:46:26 -0700 Subject: [PATCH 17/82] $ streaming telephony: the caller speaks the carrier's envelope, so its audio reaches the far end and is counted, and the far end's audio reaches the caller TODO STREAMING ("A PHONE CALL THAT CANNOT WORK, AND IS BILLED ANYWAY", first finding): every frame on /v1/realtime/telephony/{call_id} went to the realtime codec, which reads no Twilio Media Streams event, so the caller's audio never reached the model while the session opened, dialed and settled one-way output; the far end's realtime frames went to the phone unenveloped. - TwilioBridge and CallerStep move from session_unit.rs into codec::topology::twilio, the envelope's one home; the door's session unit and the live proxy both read it there. - TelephonyProxy carries the envelope on the telephony leg (begin_telephony always; open_admitted_telephony when the leg is Ingress::Telephony, not the Gemini Live leg): caller `media` goes on as the far end's uplink append and is counted under audio_seconds_in; the caller's `stop`, or a frame the envelope refuses, hard-closes the carrier and the call ends with its open turn settled once; frames bound for the caller are rewritten into Twilio `media`/`clear`. - The no-provider fallback is unchanged (no far end, no envelope, nothing heard or counted). MONEY: a telephony call now bills the caller's audio_seconds_in (0 before: the audio was dropped) and the provider-reported input tokens the far end now receives. Tests (red without the envelope: the far end hears nothing, nothing is ledgered, the refused frame leaves the call open): a_telephony_caller_is_heard_counted_and_answered_in_the_carriers_envelope, a_telephony_frame_not_in_the_carriers_shape_ends_the_call_unheard, a_leg_with_no_envelope_relays_the_far_ends_dialect_both_ways; the served witness and the mount test place their call through testkit::telephony::place_call, speaking Twilio. (cherry picked from commit 519c31cea26963ad96675e6b3101e4d876349100) --- .../src/codec/topology/twilio.rs | 83 ++++++++++ .../src/session_unit.rs | 83 +--------- crates/busbar-voice/src/mount.rs | 1 + crates/busbar-voice/src/testkit/mod.rs | 3 + crates/busbar-voice/src/testkit/telephony.rs | 128 +++++++++++++++ crates/busbar-voice/src/tests/mount_tests.rs | 60 ++----- .../busbar-voice/src/tests/served_witness.rs | 56 ++----- crates/busbar-voice/src/topology/telephony.rs | 58 ++++++- crates/busbar-voice/src/topology/tests/mod.rs | 154 ++++++++++++++++-- 9 files changed, 433 insertions(+), 193 deletions(-) create mode 100644 crates/busbar-voice/src/testkit/telephony.rs diff --git a/crates/busbar-plane-streaming/src/codec/topology/twilio.rs b/crates/busbar-plane-streaming/src/codec/topology/twilio.rs index be4083ede0..15627508ca 100644 --- a/crates/busbar-plane-streaming/src/codec/topology/twilio.rs +++ b/crates/busbar-plane-streaming/src/codec/topology/twilio.rs @@ -30,6 +30,8 @@ use serde::Serialize; // nothing bought for it, and this is the primitive that turns bytes on a phone call into audio. use busbar_contract::media::{base64_decode, base64_encode}; +use crate::codec::ir::codec::WireEvent; + /// Twilio's negotiated µ-law encoding string on the `start` event's media format. pub const TWILIO_MULAW_ENCODING: &str = "audio/x-mulaw"; /// The only sample rate the 8 kHz passthrough carrier accepts. @@ -449,6 +451,87 @@ fn xml_escape(s: &str) -> String { out } +/// THE TELEPHONY CARRIER'S ENVELOPE around a session: the caller speaks Twilio Media Streams, the +/// far end the realtime dialect locked to `g711_ulaw` both ways, so the audio passes through +/// unchanged and only the envelope is rewritten. +#[derive(Debug, Default)] +pub struct TwilioBridge { + stream_sid: Option, +} + +/// What one caller frame on the telephony leg is. +#[derive(Debug, PartialEq, Eq)] +pub enum CallerStep { + /// Caller audio, as the far end's dialect frames an uplink append. + Audio(WireEvent), + /// The call ended. + Stop, + /// The call is refused (no stream id, another media format, a frame not in Twilio's shape, a + /// payload that is not clean base64): the session ends. + Refused, + /// Nothing for the far end (the handshake, a start, a mark, a keypress, an event this reader + /// does not model, or media for another stream). + Nothing, +} + +impl TwilioBridge { + /// Read one caller frame. A start with no stream id, or in another media format, is refused; so + /// is a frame that is not Twilio's shape or carries a payload that is not clean base64 (data + /// resuming after padding is never billed as a longer payload). An event this reader does not + /// model is dropped. + pub fn from_caller(&mut self, frame: &[u8]) -> CallerStep { + match TwilioEnvelope::decode(frame) { + Ok(TwilioEvent::Start(start)) => { + if start.stream_sid.is_empty() || assert_g711_ulaw(&start.media_format).is_err() { + return CallerStep::Refused; + } + self.stream_sid = Some(start.stream_sid); + CallerStep::Nothing + } + Ok(TwilioEvent::Media { + stream_sid, + payload, + }) => { + if stream_sid.is_empty() || self.stream_sid.as_deref() != Some(stream_sid.as_str()) + { + return CallerStep::Nothing; + } + let append = serde_json::json!({ + "type": "input_audio_buffer.append", + "audio": base64_encode(&payload), + }); + CallerStep::Audio(WireEvent(bytes::Bytes::from( + serde_json::to_vec(&append).unwrap_or_default(), + ))) + } + Ok(TwilioEvent::Stop) => CallerStep::Stop, + Ok(_) | Err(TwilioError::UnknownEvent(_)) => CallerStep::Nothing, + Err(_) => CallerStep::Refused, + } + } + + /// Rewrite one frame bound for the caller into the carrier's envelope: model audio becomes a + /// `media` frame on the call's stream, and a barge-in clears the audio the carrier has queued. + /// `None` for a frame the carrier has no event for. + #[must_use] + pub fn to_caller(&self, frame: &[u8]) -> Option> { + let sid = self.stream_sid.as_deref()?; + let v: serde_json::Value = serde_json::from_slice(frame).ok()?; + match v.get("type").and_then(serde_json::Value::as_str)? { + "response.output_audio.delta" | "response.audio.delta" => { + let audio = base64_decode(v.get("delta")?.as_str()?)?; + Some(TwilioEnvelope::encode_media(sid, &audio)) + } + "input_audio_buffer.speech_started" => serde_json::to_vec(&serde_json::json!({ + "event": "clear", + "streamSid": sid, + })) + .ok(), + _ => None, + } + } +} + #[cfg(test)] #[path = "tests/twilio_tests.rs"] mod twilio_tests; diff --git a/crates/busbar-plane-streaming/src/session_unit.rs b/crates/busbar-plane-streaming/src/session_unit.rs index 2070461200..545f792d74 100644 --- a/crates/busbar-plane-streaming/src/session_unit.rs +++ b/crates/busbar-plane-streaming/src/session_unit.rs @@ -16,7 +16,7 @@ use bytes::Bytes; use crate::codec::ir::codec::{DuplexReader, DuplexWriter, WireEvent}; use crate::codec::ir::config::SessionConfig; use crate::codec::ir::usage::IrDuplexUsage; -use crate::codec::topology::twilio::{assert_g711_ulaw, TwilioEnvelope, TwilioError, TwilioEvent}; +use crate::codec::topology::twilio::{CallerStep, TwilioBridge}; use crate::governed::GovernedSession; use crate::meta; use crate::session::{class_counts, TurnCounters}; @@ -64,87 +64,6 @@ pub struct Plan { pub end: bool, } -/// THE TELEPHONY CARRIER'S ENVELOPE around a session: the caller speaks Twilio Media Streams, the -/// far end the realtime dialect locked to `g711_ulaw` both ways, so the audio passes through -/// unchanged and only the envelope is rewritten. -#[derive(Debug, Default)] -pub struct TwilioBridge { - stream_sid: Option, -} - -/// What one caller frame on the telephony door is. -#[derive(Debug, PartialEq, Eq)] -pub enum CallerStep { - /// Caller audio, as the far end's dialect frames an uplink append. - Audio(WireEvent), - /// The call ended. - Stop, - /// The call is refused (no stream id, another media format, a frame not in Twilio's shape, a - /// payload that is not clean base64): the session ends. - Refused, - /// Nothing for the far end (the handshake, a start, a mark, a keypress, an event this reader - /// does not model, or media for another stream). - Nothing, -} - -impl TwilioBridge { - /// Read one caller frame. A start with no stream id, or in another media format, is refused; so - /// is a frame that is not Twilio's shape or carries a payload that is not clean base64 (data - /// resuming after padding is never billed as a longer payload). An event this reader does not - /// model is dropped. - pub fn from_caller(&mut self, frame: &[u8]) -> CallerStep { - match TwilioEnvelope::decode(frame) { - Ok(TwilioEvent::Start(start)) => { - if start.stream_sid.is_empty() || assert_g711_ulaw(&start.media_format).is_err() { - return CallerStep::Refused; - } - self.stream_sid = Some(start.stream_sid); - CallerStep::Nothing - } - Ok(TwilioEvent::Media { - stream_sid, - payload, - }) => { - if stream_sid.is_empty() || self.stream_sid.as_deref() != Some(stream_sid.as_str()) - { - return CallerStep::Nothing; - } - let append = serde_json::json!({ - "type": "input_audio_buffer.append", - "audio": busbar_contract::media::base64_encode(&payload), - }); - CallerStep::Audio(WireEvent(Bytes::from( - serde_json::to_vec(&append).unwrap_or_default(), - ))) - } - Ok(TwilioEvent::Stop) => CallerStep::Stop, - Ok(_) | Err(TwilioError::UnknownEvent(_)) => CallerStep::Nothing, - Err(_) => CallerStep::Refused, - } - } - - /// Rewrite one frame bound for the caller into the carrier's envelope: model audio becomes a - /// `media` frame on the call's stream, and a barge-in clears the audio the carrier has queued. - /// `None` for a frame the carrier has no event for. - #[must_use] - pub fn to_caller(&self, frame: &[u8]) -> Option> { - let sid = self.stream_sid.as_deref()?; - let v: serde_json::Value = serde_json::from_slice(frame).ok()?; - match v.get("type").and_then(serde_json::Value::as_str)? { - "response.output_audio.delta" | "response.audio.delta" => { - let audio = busbar_contract::media::base64_decode(v.get("delta")?.as_str()?)?; - Some(TwilioEnvelope::encode_media(sid, &audio)) - } - "input_audio_buffer.speech_started" => serde_json::to_vec(&serde_json::json!({ - "event": "clear", - "streamSid": sid, - })) - .ok(), - _ => None, - } - } -} - /// Every frame a session emits is one text message: the door sets the text bit on each. pub const FRAMES_ARE_TEXT: bool = true; diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index e2be8c96d9..899b49c837 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -1526,6 +1526,7 @@ where meter, now, served_governed_session(), + matches!(ingress, Ingress::Telephony), ) { Ok(proxy) => { let pool = stream_breaker_key(dialect); diff --git a/crates/busbar-voice/src/testkit/mod.rs b/crates/busbar-voice/src/testkit/mod.rs index 6471a0f938..feee545b00 100644 --- a/crates/busbar-voice/src/testkit/mod.rs +++ b/crates/busbar-voice/src/testkit/mod.rs @@ -25,6 +25,9 @@ pub mod loopback_http; /// An in-memory `metrics` recorder + exposition render, for asserting this plane's counter emits. pub mod metrics_capture; +/// One phone call over the telephony proxy, the caller speaking Twilio Media Streams. +pub mod telephony; + /// THIS PLANE'S SERVED-LEG WITNESSES — `(capability key, [(loop step or core capability, witness)])`. /// /// What a test binary that links this crate without naming it runs through ITS registered session diff --git a/crates/busbar-voice/src/testkit/telephony.rs b/crates/busbar-voice/src/testkit/telephony.rs new file mode 100644 index 0000000000..f34410c6f7 --- /dev/null +++ b/crates/busbar-voice/src/testkit/telephony.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! ONE PHONE CALL over a [`TelephonyProxy`], driven the way the carrier drives it: the caller speaks +//! Twilio Media Streams (`connected`, `start`, `media`, `stop`), the far end the realtime dialect. +//! Four in-memory channels stand in for the two sockets. Every wait is bounded, so a proxy that +//! never relays reads as an empty leg rather than a hung test, and both sockets are closed at the +//! end whatever happened. + +use crate::ir::codec::{DuplexReader, DuplexWriter}; +use crate::topology::telephony::TelephonyProxy; +use futures::channel::mpsc::{unbounded, UnboundedReceiver}; +use futures::StreamExt; +use std::time::Duration; + +/// The stream identifier the call's `start` names, and every `media` frame repeats. +pub const STREAM_SID: &str = "MZ-call"; + +/// The far end's reply: three bytes of µ-law, which base64 spells `////`. +pub const FAR_END_AUDIO: &[u8] = &[0xff; 3]; +const FAR_END_DELTA: &str = "////"; + +/// How long the driver waits for one frame to cross before it gives up on it. +const WAIT: Duration = Duration::from_secs(2); + +/// What crossed each socket during the call, as JSON, in order. +#[derive(Debug, Default)] +pub struct CallLegs { + /// Every frame the far end received. + pub to_far_end: Vec, + /// Every frame the caller received. + pub to_caller: Vec, +} + +fn frame(v: &serde_json::Value) -> Vec { + serde_json::to_vec(v).expect("a JSON value serializes") +} + +fn parsed(f: &[u8]) -> serde_json::Value { + serde_json::from_slice(f).unwrap_or(serde_json::Value::Null) +} + +/// Wait (bounded) for a frame on `rx` that `wanted` accepts, keeping every frame seen on `seen`. +async fn wait_for( + rx: &mut UnboundedReceiver>, + seen: &mut Vec, + wanted: impl Fn(&serde_json::Value) -> bool, +) { + loop { + match tokio::time::timeout(WAIT, rx.next()).await { + Ok(Some(f)) => { + let v = parsed(&f); + let hit = wanted(&v); + seen.push(v); + if hit { + return; + } + } + Ok(None) | Err(_) => return, + } + } +} + +/// PLACE ONE CALL: the caller connects, starts the stream in `g711_ulaw`, and speaks `caller_audio` +/// (raw µ-law) as one `media` frame; once the far end has heard it, the far end answers with +/// [`FAR_END_AUDIO`] as one realtime audio delta; once the caller has received it, the caller hangs +/// up with `stop`. The far end's socket stays open throughout: the call ends on the caller's `stop`. +pub async fn place_call(proxy: TelephonyProxy, caller_audio: &[u8]) -> CallLegs +where + C: DuplexReader + DuplexWriter + Send + Sync + 'static, +{ + let (prov_in_tx, prov_in_rx) = unbounded::>(); + let (prov_out_tx, mut prov_out_rx) = unbounded::>(); + let (cli_in_tx, cli_in_rx) = unbounded::>(); + let (cli_out_tx, mut cli_out_rx) = unbounded::>(); + let mut legs = CallLegs::default(); + + let caller = { + let legs = &mut legs; + let (prov_out_rx, cli_out_rx) = (&mut prov_out_rx, &mut cli_out_rx); + async move { + for v in [ + serde_json::json!({"event": "connected", "protocol": "Call", "version": "1.0.0"}), + serde_json::json!({"event": "start", "streamSid": STREAM_SID, "start": { + "streamSid": STREAM_SID, "callSid": "CA-call", + "mediaFormat": {"encoding": "audio/x-mulaw", "sampleRate": 8000, "channels": 1} + }}), + ] { + let _ = cli_in_tx.unbounded_send(frame(&v)); + } + let _ = cli_in_tx.unbounded_send( + crate::topology::twilio::TwilioEnvelope::encode_media(STREAM_SID, caller_audio), + ); + wait_for(prov_out_rx, &mut legs.to_far_end, |v| { + v["type"] == "input_audio_buffer.append" + }) + .await; + let _ = prov_in_tx.unbounded_send(frame(&serde_json::json!({ + "type": "response.output_audio.delta", + "delta": FAR_END_DELTA, + }))); + wait_for(cli_out_rx, &mut legs.to_caller, |v| v["event"] == "media").await; + let _ = cli_in_tx.unbounded_send(frame(&serde_json::json!({ + "event": "stop", "streamSid": STREAM_SID + }))); + // A proxy that does not end the call on `stop` still ends when both sockets do. + tokio::time::sleep(WAIT).await; + drop(cli_in_tx); + drop(prov_in_tx); + } + }; + let call = proxy.run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx); + // The caller's script may still be sleeping when the call ends: the call is what the legs read. + tokio::select! { + () = call => {} + () = async { caller.await; std::future::pending::<()>().await } => {} + } + + prov_out_rx.close(); + while let Some(f) = prov_out_rx.next().await { + legs.to_far_end.push(parsed(&f)); + } + cli_out_rx.close(); + while let Some(f) = cli_out_rx.next().await { + legs.to_caller.push(parsed(&f)); + } + legs +} diff --git a/crates/busbar-voice/src/tests/mount_tests.rs b/crates/busbar-voice/src/tests/mount_tests.rs index 06551d6e2d..abc09137c8 100644 --- a/crates/busbar-voice/src/tests/mount_tests.rs +++ b/crates/busbar-voice/src/tests/mount_tests.rs @@ -31,8 +31,6 @@ use busbar_kernel::plane::handle_engine::DurableHandleEngine; use busbar_kernel::plane::registry::{BuildCtx, CardIssuer, PlaneBootCtx, RestoredSummary}; use busbar_kernel::plane::store::PlaneStore; use busbar_kernel::plane_host::EngineHost; -use futures::channel::mpsc::unbounded; -use futures::StreamExt; use std::sync::{Arc, Mutex}; const PUBLIC_URL: &str = "https://gw.example.com"; @@ -414,57 +412,23 @@ async fn duplex_session_runs_in_process_through_the_gauntlet_after_hydrate() { ) .expect("the open-pass gauntlet admits and the session opens"); - // (3) HANDLER: drive the session over the neutral pump with an in-process MOCK PEER — four + // (3) HANDLER: drive one phone call over the neutral pump with an in-process MOCK PEER — four // in-memory channels stand in for the provider socket and the client socket. No live provider. - let (prov_in_tx, prov_in_rx) = unbounded::>(); - let (prov_out_tx, mut prov_out_rx) = unbounded::>(); - let (cli_in_tx, cli_in_rx) = unbounded::>(); - let (cli_out_tx, mut cli_out_rx) = unbounded::>(); - - prov_in_tx - .unbounded_send( - serde_json::to_vec(&serde_json::json!({ - "type":"response.output_audio.delta","delta":"AAAA" - })) - .unwrap(), - ) - .unwrap(); - cli_in_tx - .unbounded_send( - serde_json::to_vec(&serde_json::json!({ - "type":"input_audio_buffer.append","audio":"BBBB" - })) - .unwrap(), - ) - .unwrap(); - drop(prov_in_tx); - drop(cli_in_tx); - - proxy - .run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx) - .await; + let legs = crate::testkit::telephony::place_call(proxy, &[0x7f; 160]).await; - // Downlink: the provider's audio reached the client. Uplink: the client's audio reached the provider. - cli_out_rx.close(); - let mut downlink = Vec::new(); - while let Some(f) = cli_out_rx.next().await { - let v: serde_json::Value = serde_json::from_slice(&f).unwrap(); - downlink.push(v["type"].as_str().unwrap().to_string()); - } + // Uplink: the caller's audio reached the provider. Downlink: the provider's audio reached the + // caller, in the carrier's own envelope. assert!( - downlink.contains(&"response.output_audio.delta".to_string()), - "the governed session relayed provider downlink audio to the client: {downlink:?}" + legs.to_far_end + .iter() + .any(|v| v["type"] == "input_audio_buffer.append"), + "the governed session relayed client uplink audio to the provider: {:?}", + legs.to_far_end ); - - prov_out_rx.close(); - let mut uplink = Vec::new(); - while let Some(f) = prov_out_rx.next().await { - let v: serde_json::Value = serde_json::from_slice(&f).unwrap(); - uplink.push(v["type"].as_str().unwrap().to_string()); - } assert!( - uplink.contains(&"input_audio_buffer.append".to_string()), - "the governed session relayed client uplink audio to the provider: {uplink:?}" + legs.to_caller.iter().any(|v| v["event"] == "media"), + "the governed session relayed provider downlink audio to the client: {:?}", + legs.to_caller ); } diff --git a/crates/busbar-voice/src/tests/served_witness.rs b/crates/busbar-voice/src/tests/served_witness.rs index 31ed2f3b4f..01f4243593 100644 --- a/crates/busbar-voice/src/tests/served_witness.rs +++ b/crates/busbar-voice/src/tests/served_witness.rs @@ -44,8 +44,6 @@ use crate::runtime::{Carrier, SessionCore, TurnMeter, VoiceRuntime}; use crate::testkit::fixture_host::FixtureHost; use crate::topology::telephony::{begin_telephony, g711_config}; use crate::topology::{begin_session, dial_provider, stream_breaker_key, DialProviderError}; -use futures::channel::mpsc::unbounded; -use futures::StreamExt; use std::future::Future; use std::pin::Pin; use std::sync::Arc; @@ -389,8 +387,8 @@ async fn a_dry_key_is_refused_at_the_open_past_the_gate() -> u64 { 3 } -/// ROUTE (the session plane's shape): a served session relays the provider's audio down to the client -/// and the client's audio up to the provider. +/// ROUTE (the session plane's shape): a served telephony call relays the provider's audio down to the +/// caller and the caller's audio up to the provider, the caller speaking the carrier's envelope. async fn a_served_session_relays_both_directions() -> u64 { let host = Arc::new(FixtureHost::new()); let rt = hosted(&host); @@ -405,50 +403,18 @@ async fn a_served_session_relays_both_directions() -> u64 { ) .unwrap_or_else(|e| panic!("the served door opens the telephony session: {e}")); - let (prov_in_tx, prov_in_rx) = unbounded::>(); - let (prov_out_tx, mut prov_out_rx) = unbounded::>(); - let (cli_in_tx, cli_in_rx) = unbounded::>(); - let (cli_out_tx, mut cli_out_rx) = unbounded::>(); - prov_in_tx - .unbounded_send( - serde_json::to_vec(&serde_json::json!({ - "type": "response.output_audio.delta", "delta": "AAAA" - })) - .expect("serializes"), - ) - .expect("queued"); - cli_in_tx - .unbounded_send( - serde_json::to_vec(&serde_json::json!({ - "type": "input_audio_buffer.append", "audio": "BBBB" - })) - .expect("serializes"), - ) - .expect("queued"); - drop(prov_in_tx); - drop(cli_in_tx); - proxy - .run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx) - .await; - - let kinds = |frames: Vec>| -> Vec { - frames - .iter() - .filter_map(|f| serde_json::from_slice::(f).ok()) - .filter_map(|v| v["type"].as_str().map(str::to_string)) - .collect() - }; - cli_out_rx.close(); - let down = kinds(cli_out_rx.by_ref().collect().await); - prov_out_rx.close(); - let up = kinds(prov_out_rx.by_ref().collect().await); + let legs = crate::testkit::telephony::place_call(proxy, &[0x7f; 160]).await; assert!( - down.contains(&"response.output_audio.delta".to_string()), - "the provider's audio reached the client: {down:?}" + legs.to_caller.iter().any(|v| v["event"] == "media"), + "the provider's audio reached the client: {:?}", + legs.to_caller ); assert!( - up.contains(&"input_audio_buffer.append".to_string()), - "the client's audio reached the provider: {up:?}" + legs.to_far_end + .iter() + .any(|v| v["type"] == "input_audio_buffer.append"), + "the client's audio reached the provider: {:?}", + legs.to_far_end ); 1 } diff --git a/crates/busbar-voice/src/topology/telephony.rs b/crates/busbar-voice/src/topology/telephony.rs index 5abb2f1b4e..79f0967b1a 100644 --- a/crates/busbar-voice/src/topology/telephony.rs +++ b/crates/busbar-voice/src/topology/telephony.rs @@ -14,6 +14,13 @@ //! the downlink-facing [`VoiceSession`] (tool results / barge-in cancel) and the [`UplinkForwarder`] //! (client audio → upstream) — so it is funnelled through one channel into a single writer. The client //! socket's write side is the downlink, driven by the session [`Carrier`]. +//! +//! THE CARRIER'S ENVELOPE. The telephony leg's caller speaks Twilio Media Streams, not the realtime +//! dialect: every caller frame is read through the envelope ([`TwilioBridge::from_caller`]), so its +//! `media` audio reaches the far end as an uplink append and is counted, and its `stop` (or a frame +//! the envelope refuses) ends the call; every frame bound for the caller is rewritten into the +//! envelope ([`TwilioBridge::to_caller`]). The Gemini Live leg rides the same proxy with no envelope: +//! its caller speaks the far end's own dialect. use crate::ir::codec::{DuplexReader, DuplexWriter}; use crate::ir::config::SessionConfig; @@ -22,11 +29,13 @@ use crate::runtime::carrier::Carrier; use crate::runtime::scope::SessionHandle; use crate::runtime::session::{SessionCore, UplinkForwarder, VoiceSession}; use crate::runtime::VoiceRuntime; +use crate::topology::twilio::{CallerStep, TwilioBridge}; use crate::topology::{begin_session, StartError}; use busbar_kernel::ingress::byte_duplex::serve_messages; use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; +use futures::future::{ready, Either}; use futures::{Sink, Stream, StreamExt}; -use std::sync::Arc; +use std::sync::{Arc, Mutex, PoisonError}; /// THE LOCKED CONFIG for a telephony leg: `g711_ulaw` on BOTH the input and output audio formats so the /// 8 kHz µ-law carrier passes straight through with no resample. Callers overlay their own @@ -58,6 +67,9 @@ pub struct TelephonyProxy { upstream_tx: UnboundedSender>, /// The client-write (downlink) funnel receiver, fed by the session carrier. downlink_rx: UnboundedReceiver>, + /// The carrier's envelope on the caller's side: `Some` on the telephony leg, `None` on a leg + /// whose caller speaks the far end's dialect. + envelope: Option>>, } /// BEGIN a telephony proxy: lock the `g711`-based config, open the governed session (kernel account + @@ -104,6 +116,7 @@ where upstream_rx, upstream_tx, downlink_rx, + envelope: Some(Arc::default()), }) } @@ -117,6 +130,9 @@ where /// `governed` is the served door's own binding — the node's open-call table and the identifier this /// session is known to it by — and it is carried straight through to the post-admit open, so a /// dialed proxy is governed on exactly the terms the other two served legs are. +/// +/// `twilio` puts the telephony carrier's envelope on the caller's side (the telephony leg); the +/// Gemini Live leg passes `false`. #[allow(clippy::too_many_arguments)] pub(crate) fn open_admitted_telephony( rt: &VoiceRuntime, @@ -127,6 +143,7 @@ pub(crate) fn open_admitted_telephony( meter: Option, now: u64, governed: Option, + twilio: bool, ) -> Result, StartError> where C: DuplexReader + DuplexWriter + Send + Sync + 'static, @@ -158,6 +175,7 @@ where upstream_rx, upstream_tx, downlink_rx, + envelope: twilio.then(Arc::default), }) } @@ -203,10 +221,48 @@ where upstream_rx, upstream_tx, downlink_rx, + envelope, } = self; let carrier = core.carrier().clone(); + // The carrier's envelope, both ways. Caller frames are read through it: audio goes on as the + // far end's uplink append, the call's `stop` or a refused frame ends the session (the carrier + // closes, so the far end's leg ends with it), and everything else carries nothing upstream. + // Frames bound for the caller are rewritten into it; one it has no event for is dropped. + let (client_in, downlink_rx) = match envelope { + None => (Either::Left(client_in), Either::Left(downlink_rx)), + Some(bridge) => { + let ender = carrier.clone(); + let up = Arc::clone(&bridge); + let client_in = client_in + .scan((), move |_, frame| { + let step = up + .lock() + .unwrap_or_else(PoisonError::into_inner) + .from_caller(&frame); + ready(match step { + CallerStep::Audio(w) => Some(Some(w.0.to_vec())), + CallerStep::Nothing => Some(None), + CallerStep::Stop | CallerStep::Refused => { + ender.hard_close(); + None + } + }) + }) + .filter_map(ready); + let downlink = downlink_rx.filter_map(move |frame| { + ready( + bridge + .lock() + .unwrap_or_else(PoisonError::into_inner) + .to_caller(&frame), + ) + }); + (Either::Right(Box::pin(client_in)), Either::Right(downlink)) + } + }; + // The two writer drains run as detached tasks (their receivers/sinks are `Send + 'static`). // Each ends when ITS funnel's last sender drops — provider-write when both serve legs finish, // downlink when the session core (and its carrier) is dropped below. diff --git a/crates/busbar-voice/src/topology/tests/mod.rs b/crates/busbar-voice/src/topology/tests/mod.rs index 2cab5d4306..08e81dfd24 100644 --- a/crates/busbar-voice/src/topology/tests/mod.rs +++ b/crates/busbar-voice/src/topology/tests/mod.rs @@ -41,8 +41,13 @@ fn runtime() -> VoiceRuntime { /// A presenting key's meter over a governed fixture host whose budget view is capped at `cap` counts. fn meter_capped(cap: i64) -> TurnMeter { + meter_on(&Arc::new(FixtureHost::new().governed().with_count_cap(cap))) +} + +/// The presenting key `vk`'s meter over `host`. +fn meter_on(host: &Arc) -> TurnMeter { TurnMeter::new( - Arc::new(FixtureHost::new().governed().with_count_cap(cap)), + host.clone(), busbar_contract::records::VirtualKey { id: "vk".to_string(), ..Default::default() @@ -58,36 +63,154 @@ fn json_frame(v: serde_json::Value) -> Vec { // ── Topology B: the thin telephony proxy relays both directions ───────────────────────────────── +/// The caller's audio is counted in the class's own seconds and nothing else: what the governed host +/// ledgered under `audio_seconds_in` for the presenting key `vk`. +fn audio_seconds_in(host: &FixtureHost) -> Option { + host.ledger_rows("vk") + .iter() + .find(|((_, class), _)| class == "audio_seconds_in") + .map(|(_, n)| *n) +} + +/// THE TELEPHONY LEG SPEAKS THE CARRIER'S ENVELOPE (1.6.0-TODO.md, "the caller's audio is silently +/// discarded, and the call still settles usage"). The caller's Twilio `media` reaches the far end as +/// the realtime dialect's uplink append, byte for byte, and is counted; the far end's audio reaches +/// the caller as a Twilio `media` frame on the call's stream; the caller's `stop` ends the call while +/// the far end's socket is still open. Every caller frame used to go to the realtime codec, which +/// reads no Twilio event, so the far end heard nothing and the session billed one-way output. #[tokio::test] -async fn telephony_proxy_relays_both_directions() { +async fn a_telephony_caller_is_heard_counted_and_answered_in_the_carriers_envelope() { let rt = runtime(); - // g711 end-to-end: 8 kHz µ-law passes straight through, no resample. - let cfg = g711_config(); + let host = Arc::new(FixtureHost::new().governed()); + let proxy = begin_telephony( + &rt, + OpenAiRealtimeCodec, + "acct-1", + "call-9", + g711_config(), + Some(meter_on(&host)), + 1, + ) + .expect("telephony begins"); + // Three seconds of 8 kHz µ-law from the caller (24 000 bytes of 0x7f, which base64 spells as + // `f39/` 8 000 times); the far end's short reply is the call driver's own. + let spoken = vec![0x7f_u8; 24_000]; + + let legs = crate::testkit::telephony::place_call(proxy, &spoken).await; + + let appended: Vec<&serde_json::Value> = legs + .to_far_end + .iter() + .filter(|v| v["type"] == "input_audio_buffer.append") + .collect(); assert_eq!( - cfg.output_audio_format, - Some(crate::ir::media::AudioFormat::G711Ulaw) + appended + .iter() + .map(|v| v["audio"].as_str().unwrap_or_default().to_string()) + .collect::>(), + vec!["f39/".repeat(8_000)], + "the caller's audio reached the far end once, unchanged: {:?}", + legs.to_far_end + ); + assert!( + legs.to_far_end.iter().all(|v| v.get("event").is_none()), + "no Twilio frame reached the far end: {:?}", + legs.to_far_end + ); + let heard = crate::topology::twilio::TwilioEnvelope::encode_media( + crate::testkit::telephony::STREAM_SID, + crate::testkit::telephony::FAR_END_AUDIO, + ); + assert_eq!( + legs.to_caller, + vec![serde_json::from_slice::(&heard).expect("the envelope is JSON")], + "the caller heard the far end in the carrier's envelope, and nothing else" + ); + assert_eq!( + audio_seconds_in(&host), + Some(3), + "the caller's three seconds are ledgered when the call ends" + ); +} + +/// A frame on the telephony leg that is not the carrier's shape (here, the realtime dialect's own +/// append) is refused: the call ends at once, nothing reaches the far end, and nothing is counted. +#[tokio::test] +async fn a_telephony_frame_not_in_the_carriers_shape_ends_the_call_unheard() { + let rt = runtime(); + let host = Arc::new(FixtureHost::new().governed()); + let proxy = begin_telephony( + &rt, + OpenAiRealtimeCodec, + "acct-1", + "call-refused", + g711_config(), + Some(meter_on(&host)), + 1, + ) + .expect("telephony begins"); + let (_prov_in_tx, prov_in_rx) = unbounded::>(); + let (prov_out_tx, mut prov_out_rx) = unbounded::>(); + let (cli_in_tx, cli_in_rx) = unbounded::>(); + let (cli_out_tx, _cli_out_rx) = unbounded::>(); + cli_in_tx + .unbounded_send(json_frame(serde_json::json!({ + "type": "input_audio_buffer.append", "audio": "A".repeat(16_000) + }))) + .unwrap(); + + // Both sockets stay open: only the refusal can end the call. + let ended = tokio::time::timeout( + std::time::Duration::from_secs(5), + proxy.run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx), + ) + .await; + assert!(ended.is_ok(), "the refused frame ended the call"); + + prov_out_rx.close(); + let mut up = Vec::new(); + while let Some(f) = prov_out_rx.next().await { + up.push(serde_json::from_slice::(&f).unwrap_or_default()); + } + assert!( + !up.iter().any(|v| v["type"] == "input_audio_buffer.append"), + "nothing the caller sent reached the far end: {up:?}" ); - let proxy = begin_telephony(&rt, OpenAiRealtimeCodec, "acct-1", "call-9", cfg, None, 1) - .expect("telephony begins"); + assert_eq!(audio_seconds_in(&host), None, "nothing was counted"); +} + +/// The Gemini Live leg rides the same proxy with no envelope: its caller speaks the far end's own +/// dialect, so frames cross both ways unchanged. +#[tokio::test] +async fn a_leg_with_no_envelope_relays_the_far_ends_dialect_both_ways() { + let rt = runtime(); + let proxy = crate::topology::telephony::open_admitted_telephony( + &rt, + OpenAiRealtimeCodec, + "acct-1", + "call-dialect", + g711_config(), + None, + 1, + None, + false, + ) + .expect("the leg opens"); let (prov_in_tx, prov_in_rx) = unbounded::>(); let (prov_out_tx, mut prov_out_rx) = unbounded::>(); let (cli_in_tx, cli_in_rx) = unbounded::>(); let (cli_out_tx, mut cli_out_rx) = unbounded::>(); - - // The provider emits a downlink audio frame → it must reach the client. prov_in_tx .unbounded_send(json_frame(serde_json::json!({ "type":"response.output_audio.delta","delta":"AAAA" }))) .unwrap(); - // The client (phone) sends an uplink audio frame → it must reach the provider. cli_in_tx .unbounded_send(json_frame(serde_json::json!({ "type":"input_audio_buffer.append","audio":"BBBB" }))) .unwrap(); - // EOF both sockets so the proxy returns cleanly. drop(prov_in_tx); drop(cli_in_tx); @@ -95,7 +218,6 @@ async fn telephony_proxy_relays_both_directions() { .run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx) .await; - // Downlink: the client received the provider's audio. cli_out_rx.close(); let mut downlink = Vec::new(); while let Some(f) = cli_out_rx.next().await { @@ -104,10 +226,8 @@ async fn telephony_proxy_relays_both_directions() { } assert!( downlink.contains(&"response.output_audio.delta".to_string()), - "provider downlink audio reached the client: {downlink:?}" + "the far end's audio reached the caller: {downlink:?}" ); - - // Uplink: the provider received the client's forwarded audio append. prov_out_rx.close(); let mut uplink = Vec::new(); while let Some(f) = prov_out_rx.next().await { @@ -116,7 +236,7 @@ async fn telephony_proxy_relays_both_directions() { } assert!( uplink.contains(&"input_audio_buffer.append".to_string()), - "client uplink audio reached the provider: {uplink:?}" + "the caller's audio reached the far end: {uplink:?}" ); } From 6d548dc82505043e5bf804892c825701a8f2dfd0 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 18:46:26 -0700 Subject: [PATCH 18/82] TODO STREAMING: the telephony finding is fixed by this lane's $ commit; the ceiling finding was closed on predev (Q21a/Q37) (cherry picked from commit 2b2370a96326d06c7f61d6395fbc8c2e26d251d9) --- docs/design/1.6.0-TODO.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/design/1.6.0-TODO.md b/docs/design/1.6.0-TODO.md index 0a541cf3c7..0f7670356c 100644 --- a/docs/design/1.6.0-TODO.md +++ b/docs/design/1.6.0-TODO.md @@ -1737,6 +1737,8 @@ Three HIGH, two of them money, all verified by hand. ## The caller's audio is silently discarded, and the call still settles usage +**FIXED (lane FOLD-STREAMING, `$` PR lane-fold-streaming-telephony-money):** the telephony leg reads every caller frame through the Twilio envelope (`codec::topology::twilio::TwilioBridge`, its one home); caller `media` reaches the far end as an uplink append and is counted, `stop` or a refused frame ends the call, and the far end's audio reaches the caller as Twilio `media`. Tests: `busbar-voice` `topology/tests/mod.rs`. + `busbar-voice`'s telephony endpoint `/v1/realtime/telephony/{call_id}` is live, mounted, governed and provider-dialing. It feeds raw client bytes straight into `OpenAiRealtimeCodec::read_up`, which understands only OpenAI's `type`-tagged JSON. **Real Twilio Media Streams frames are `event`-tagged**, @@ -1760,6 +1762,8 @@ which nobody could hear them. ## The session cost ceiling is documented, plumbed, and never compared to anything +**CLOSED on predev (Q21a/Q37):** an optional `streams.session_max_secs` is compared on the sweep tick (`SessionCore::enforce_ceiling`), and absent means no limit. Test: `busbar-voice` `runtime/tests.rs` `a_configured_ceiling_closes_the_session_and_settles_its_counts`. + `runtime/mod.rs:67` declares `session_max_secs` as *"the hard session wall-clock ceiling (`streams.session_max_secs:`) **the pump enforces**"*, default 3600s. I traced every reference: a doc comment, a field declaration, a default fn, a serde attribute, and four plumbing assignments. From 5e302a58b85110aa1c2437d812bafbee06735f60 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:31:32 -0700 Subject: [PATCH 19/82] streaming fold ported onto p3-serve-hookstage: the door's needs state the response-head rule (KEEP_NAMED, nothing kept or denied), the pump tests take the fold's STR-2 resolution, and busbar-voice serves no metadata route The fold's door builds on the widened Need (keep_mode, deny list). The fold tip's session_pump_tests carry MONEY-AUDIT STR-2 as the fold resolved it. The voice failed-dial refund cell names the telephony flag the telephony money commit added. The protected-resource metadata route (5768d32dc, carried in b054f73130) is NOT served by busbar-voice: its accepted difference (STREAMS-METADATA) is not in accepted-differences.json, and this lane adds none, so streams|metadata|protected-resource keeps its golden/1.6.0-pre answer. --- crates/busbar-plane-streaming/src/door.rs | 8 +- .../src/tests/session_pump_tests.rs | 112 ++++++++---------- crates/busbar-voice/src/mount.rs | 29 +---- crates/busbar-voice/src/tests/mount_tests.rs | 47 +------- 4 files changed, 61 insertions(+), 135 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 1c5a0dbb78..fa44c7d580 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -44,7 +44,9 @@ use std::ptr; use std::sync::Mutex; use crate::piece::{self, Owed}; -use busbar_contract::abi::host::conn::connector::{Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND}; +use busbar_contract::abi::host::conn::connector::{ + Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND, KEEP_NAMED, +}; use busbar_contract::abi::mechanism::call::{AbiStr, Blob, InHead, OutHead, Outcome, BLOB_ABSENT}; use busbar_contract::abi::mechanism::door::{ KindTailHead, Section, Statement, SECTION_DECLARING, SECTION_REQUIRED, @@ -188,6 +190,10 @@ const fn need(direction: u32, transport: &'static str, auth: AbiStr, target: Abi keep_response_headers: ptr::null(), keep_response_headers_len: 0, timeout_ms: 0, + keep_mode: KEEP_NAMED, + _reserved: 0, + deny_response_headers: ptr::null(), + deny_response_headers_len: 0, } } diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index 5062d3b7e6..d920a50861 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -274,52 +274,6 @@ fn an_error_closes_the_open_turn_before_it_is_relayed() { assert!(texts(&out.downlink).contains("error")); } -/// The `audio_seconds_in` every closed turn bills, summed over the session. -fn billed_audio_seconds(sink: &Turns) -> u64 { - sink.closed - .iter() - .flat_map(|(usage, counters)| crate::session::class_counts(usage.as_ref(), *counters)) - .filter(|(class, _)| *class == crate::meta::CLASS_AUDIO_SECONDS_IN) - .map(|(_, n)| n) - .sum() -} - -/// RED-BEFORE-GREEN (MONEY-AUDIT STR-2): a session is one unit, so its uplink audio converts to -/// seconds ONCE. Rounding each turn's milliseconds up on its own billed 20 turns of 1050 ms as 40 s; -/// the session spoke 21 000 ms, which is 21 s. -#[test] -fn twenty_turns_of_1050_ms_bill_21_audio_seconds_not_40() { - let mut p = pump(); - let mut sink = Turns::default(); - // 1050 ms of pcm16 (48 bytes per ms). - let b64 = busbar_contract::media::base64_encode(&[0u8; 1050 * 48]); - for _ in 0..20 { - let _ = p.on_client_frame(wire(serde_json::json!({ - "type":"input_audio_buffer.append","audio": b64 - }))); - let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); - } - assert_eq!(sink.closed.len(), 20); - assert_eq!(billed_audio_seconds(&sink), 21); -} - -/// RED-BEFORE-GREEN (MONEY-AUDIT STR-2): a frame's bytes need not divide into whole milliseconds, -/// and the part left over is audio too. 960 frames of 50 bytes of pcm16 are 48 000 bytes, one second; -/// flooring each frame to 1 ms counted 960 ms. -#[test] -fn the_part_of_a_millisecond_a_frame_leaves_over_is_carried_not_floored() { - let mut p = pump(); - let mut sink = Turns::default(); - let b64 = busbar_contract::media::base64_encode(&[0u8; 50]); - for _ in 0..960 { - let _ = p.on_client_frame(wire(serde_json::json!({ - "type":"input_audio_buffer.append","audio": b64 - }))); - } - let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); - assert_eq!(sink.closed.len(), 1); - assert_eq!(sink.closed[0].1.audio_ms_in, 1000); - assert_eq!(billed_audio_seconds(&sink), 1); #[test] fn a_call_no_one_on_this_node_serves_is_relayed_to_the_caller_and_never_answered_here() { // No open-call table bound (an ungoverned node), and no tool served: the call reaches the @@ -406,13 +360,8 @@ fn an_upstream_error_still_meters_the_turn_it_ended() { &serves_none, ); assert_eq!(sink.closed.len(), 1); - assert_eq!( - sink.closed[0].1, - TurnCounters { - audio_ms_in: 40_000, - tool_calls: 2 - } - ); + let closed = sink.closed[0].1; + assert_eq!((closed.audio_ms_in, closed.tool_calls), (40_000, 2)); } #[test] @@ -437,13 +386,8 @@ fn a_barge_in_bills_what_the_interrupted_turn_served_on_the_turn_that_takes_over ); let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_none); assert_eq!(sink.closed.len(), 1); - assert_eq!( - sink.closed[0].1, - TurnCounters { - audio_ms_in: 1_000, - tool_calls: 1 - } - ); + let closed = sink.closed[0].1; + assert_eq!((closed.audio_ms_in, closed.tool_calls), (1_000, 1)); } #[test] @@ -457,3 +401,51 @@ fn a_caller_frame_that_is_not_utf8_or_names_no_known_event_goes_nowhere() { p.settle_open_turn(&mut sink); assert!(sink.closed.is_empty(), "nothing was counted"); } + +/// The `audio_seconds_in` every closed turn bills, summed over the session. +fn billed_audio_seconds(sink: &Turns) -> u64 { + sink.closed + .iter() + .flat_map(|(usage, counters)| crate::session::class_counts(usage.as_ref(), *counters)) + .filter(|(class, _)| *class == crate::meta::CLASS_AUDIO_SECONDS_IN) + .map(|(_, n)| n) + .sum() +} + +/// RED-BEFORE-GREEN (MONEY-AUDIT STR-2): a session is one unit, so its uplink audio converts to +/// seconds ONCE. Rounding each turn's milliseconds up on its own billed 20 turns of 1050 ms as 40 s; +/// the session spoke 21 000 ms, which is 21 s. +#[test] +fn twenty_turns_of_1050_ms_bill_21_audio_seconds_not_40() { + let mut p = pump(); + let mut sink = Turns::default(); + // 1050 ms of pcm16 (48 bytes per ms). + let b64 = busbar_contract::media::base64_encode(&[0u8; 1050 * 48]); + for _ in 0..20 { + let _ = p.on_client_frame(wire(serde_json::json!({ + "type":"input_audio_buffer.append","audio": b64 + }))); + let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); + } + assert_eq!(sink.closed.len(), 20); + assert_eq!(billed_audio_seconds(&sink), 21); +} + +/// RED-BEFORE-GREEN (MONEY-AUDIT STR-2): a frame's bytes need not divide into whole milliseconds, +/// and the part left over is audio too. 960 frames of 50 bytes of pcm16 are 48 000 bytes, one second; +/// flooring each frame to 1 ms counted 960 ms. +#[test] +fn the_part_of_a_millisecond_a_frame_leaves_over_is_carried_not_floored() { + let mut p = pump(); + let mut sink = Turns::default(); + let b64 = busbar_contract::media::base64_encode(&[0u8; 50]); + for _ in 0..960 { + let _ = p.on_client_frame(wire(serde_json::json!({ + "type":"input_audio_buffer.append","audio": b64 + }))); + } + let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); + assert_eq!(sink.closed.len(), 1); + assert_eq!(sink.closed[0].1.audio_ms_in, 1000); + assert_eq!(billed_audio_seconds(&sink), 1); +} diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index 899b49c837..f5bd7567e4 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -49,7 +49,7 @@ use busbar_kernel::egress::engine::{send_bounded, EngineClient}; use busbar_kernel::ingress::duplex_ws::{ accept_gauntlet, install_ws_arrivals, WsAcceptFuture, WsArrival, WsArrivalSpec, }; -use busbar_kernel::ingress::{byte_duplex::serve_messages, protocol}; +use busbar_kernel::ingress::byte_duplex::serve_messages; use busbar_kernel::plane::handle_engine::DurableHandleEngine; use busbar_kernel::plane::observe::Counted; use busbar_kernel::plane::registry::{BuildCtx, PlaneBootCtx}; @@ -608,36 +608,9 @@ pub fn voice_routes(slot: &dyn Any) -> Vec { auth: RouteAuth::Key, handler: Arc::new(|ctx: PlaneReqCtx| -> PlaneRouteFuture { Box::pin(sdp_route(ctx)) }), }, - // THE RFC 9728 DOCUMENT the refused caller's challenge points at: the one open route, read - // without a credential by the callers who do not have one yet. - PlaneRouteSpec { - path: METADATA_PATH.to_string(), - method: RouteMethod::Get, - auth: RouteAuth::None, - handler: Arc::new(|ctx: PlaneReqCtx| -> PlaneRouteFuture { - Box::pin(protected_resource_route(ctx)) - }), - }, ] } -/// The protected-resource metadata document: the audience a token at this plane's doors must carry, -/// as one reading of the public URL. No authorization server is named: the plane is configured with -/// none. -async fn protected_resource_route(ctx: PlaneReqCtx) -> axum::response::Response { - let Some(mount) = ctx.slot.downcast_ref::() else { - return refusal( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "the metadata route reached without its dispatch slot", - ); - }; - protocol::metadata(&protocol::Metadata { - resource: std::borrow::Cow::Borrowed(mount.audience.as_str()), - authorization_servers: &[], - scopes_supported: &[], - }) -} - /// THE PLANE'S WS-ACCEPT ENTRY HOOK ([`crate::linked`]): installs [`voice_ws_arrivals`] into the /// neutral WS-accept registry, before the router that drains it is built. pub fn install_arrivals() { diff --git a/crates/busbar-voice/src/tests/mount_tests.rs b/crates/busbar-voice/src/tests/mount_tests.rs index abc09137c8..16988df512 100644 --- a/crates/busbar-voice/src/tests/mount_tests.rs +++ b/crates/busbar-voice/src/tests/mount_tests.rs @@ -244,11 +244,6 @@ fn the_five_ingress_doors_mount_audience_checked_across_the_http_and_ws_seams() RouteMethod::Post, RouteAuth::Key ), - ( - "/.well-known/oauth-protected-resource/v1/realtime".to_string(), - RouteMethod::Get, - RouteAuth::None - ), ], "the two one-shot HTTP doors mount, each RouteAuth::Key behind the plane's one audience" ); @@ -763,49 +758,9 @@ async fn a_failed_provider_dial_refunds_its_session_fee_exactly_once() { Some(fee_meter(&host)), 1, None, + false, ) .expect("the session opens"); super::settle_undialed(proxy, 2); assert_each_failed_open_refunded_once(&host, 1, "a failed provider dial"); -/// THE CHALLENGE'S POINTER RESOLVES. A refused caller is sent to the admission's resource-metadata -/// URL; the plane serves that document there, without a credential, naming the audience a token -/// must carry. -#[cfg(feature = "test-support")] -#[tokio::test] -async fn the_resource_metadata_the_challenge_points_at_is_served() { - let slot = slot_from_public_url(Some(PUBLIC_URL)).expect("a public_url ⇒ a dispatch slot"); - let admission = crate::mount::voice_admission(slot.as_ref()).expect("a slot admits"); - let path = admission - .resource_metadata - .strip_prefix(PUBLIC_URL) - .expect("the pointer is under the public URL") - .to_string(); - let route = voice_routes(slot.as_ref()) - .into_iter() - .find(|r| r.path == path && r.method == RouteMethod::Get) - .expect("the pointer's path is a route the plane serves"); - assert_eq!(route.auth, RouteAuth::None, "readable without a credential"); - let host = crate::testkit::fixture_host::FixtureHost::new().into_host(); - let ctx = super::PlaneReqCtx { - path: path.clone(), - uri: axum::http::Uri::default(), - method: RouteMethod::Get, - headers: axum::http::HeaderMap::new(), - body: bytes::Bytes::new(), - path_params: Vec::new(), - caller_principal: None, - gov: None, - principal: None, - host, - engine: Arc::new(()), - slot: Arc::clone(&slot), - }; - let resp = (route.handler)(ctx).await; - assert_eq!(resp.status(), axum::http::StatusCode::OK); - let body = http_body_util::BodyExt::collect(resp.into_body()) - .await - .expect("body") - .to_bytes(); - let doc: serde_json::Value = serde_json::from_slice(&body).expect("json"); - assert_eq!(doc["resource"], admission.audience.as_str()); } From b470534c072736d620905b9f73ed693d928a8eb3 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 2 Oct 2026 23:27:26 -0700 Subject: [PATCH 20/82] streaming door: a live session is answered through the door in the driver's duplex vocabulary (K6): one frame per answer, unsolicited output named on the driver ticket, cumulative units on every answer, the ceiling on the tick clock BUSBAR-1.6.0.md Part 3 section 12 "Duplex sessions (K6)" and Part 4 "Duplex sessions": the session pumps pieces on two tickets; unsolicited output wakes the instance's driver ticket and `drive` names it, collected FROM_KERNEL; cumulative units feed the kernel's session account (THE DESIGN section 7). Spec Part 2 #18: the plane is streaming; the realtime voice dialects are dialects inside it. - busbar-plane-streaming session_door.rs (new): one Live per stream over the door's codec (sideband and telephony: OpenAI Realtime, telephony locked to g711 as the served plane locked it; gemini: Gemini Live). A caller piece goes through the session and its far-bound frame is that answer's EMIT_TO_FAR_END (one turn); a far-end piece's caller frame is its answer; every frame keeps its own message boundary, so frames past the first are queued as unsolicited output; a far-side answer only reaches the caller and the session's end is answered on the caller side; the caller's last piece settles the open turn once; a cancel on either side's ticket forgets the session; the streams.session_max_secs ceiling is read on the tick clock (the door ticks every second while a live generation configures one; a piece carries no reading on that clock). - door.rs: on_piece with a stream goes to session_piece (only a caller piece opens a session; units reported on every answer; far-bound frames carry the dialect socket's verb and target; caller frames PIECE_OUT_TEXT; a short answer is re-called from the stored answer, so no audio is counted twice); tick notes the driver ticket; drive names the ready sessions; cancel ends a session. A request unit on a session door is still refused. train/14 widened Need: the door's needs keep KEEP_NAMED and deny nothing. - session_unit.rs: close_at_ceiling, the host-measured ceiling; tick uses it. - busbar-contract abi/sdk/services.rs: Wake, the host's wake from HostTables, so a door under #![forbid(unsafe_code)] can name its driver ticket. SDK only; no ABI shape changes. Tests: src/tests/session_door_tests.rs; tests/conformance.rs a_live_session_is_answered_through_the_door and the_dropped_door_answers_a_live_session_as_the_linked_door_does (both ways, the real loader). (cherry picked from commit e68c1f1aa067ba7a2b5e25d50ff02f90c58c6b53) --- crates/busbar-contract/src/abi/sdk/mod.rs | 2 +- .../busbar-contract/src/abi/sdk/services.rs | 33 +- crates/busbar-plane-streaming/src/door.rs | 224 +++++++++++- crates/busbar-plane-streaming/src/lib.rs | 1 + .../src/session_door.rs | 338 ++++++++++++++++++ .../src/session_unit.rs | 11 +- .../src/tests/session_door_tests.rs | 246 +++++++++++++ .../tests/conformance.rs | 159 +++++++- 8 files changed, 997 insertions(+), 17 deletions(-) create mode 100644 crates/busbar-plane-streaming/src/session_door.rs create mode 100644 crates/busbar-plane-streaming/src/tests/session_door_tests.rs diff --git a/crates/busbar-contract/src/abi/sdk/mod.rs b/crates/busbar-contract/src/abi/sdk/mod.rs index 3abf8d165b..7295d67677 100644 --- a/crates/busbar-contract/src/abi/sdk/mod.rs +++ b/crates/busbar-contract/src/abi/sdk/mod.rs @@ -64,7 +64,7 @@ pub mod exchange; pub use safe::{Instance, Safe, SafeSlot}; // THE HOST SERVICES, PLUGIN SIDE: the one home of every safe host-service wrapper. pub mod services; -pub use services::{Judged, Names, Pend, Records, ServiceError, Services, Signed}; +pub use services::{Judged, Names, Pend, Records, ServiceError, Services, Signed, Wake}; // THE ONE DIGEST A PLUGIN TAKES without linking a crypto crate of its own. pub mod digest; // THE AUTH KIND'S VERIFY DOOR over the safe layer (`auth_verify_door!`). An auth plugin keeps its diff --git a/crates/busbar-contract/src/abi/sdk/services.rs b/crates/busbar-contract/src/abi/sdk/services.rs index 7164eb1932..eeda8bda72 100644 --- a/crates/busbar-contract/src/abi/sdk/services.rs +++ b/crates/busbar-contract/src/abi/sdk/services.rs @@ -30,7 +30,7 @@ use crate::abi::mechanism::call::{ AbiStr, Blob, Outcome, RawOutcome, Span, BLOB_JSON, BLOB_OCTETS, }; use crate::abi::mechanism::check::{Fault, Filled, SPAN_ABSENT}; -use crate::abi::mechanism::ticket::{CompletionHandle, HostCtx, HostTables}; +use crate::abi::mechanism::ticket::{CompletionHandle, HostCtx, HostTables, Ticket, WakeFn}; /// Why a service call has no value. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -143,6 +143,37 @@ impl<'b> Records<'b> { } } +/// THE HOST'S WAKE, as `open` handed it (`HostTables::wake`): an instance holding work of its own +/// (a plane's session output, named on its driver ticket) wakes a ticket through it, from any +/// thread. A wake never blocks and never fails; one for a stale ticket is dropped by the host. +#[derive(Debug, Clone, Copy)] +pub struct Wake { + ctx: HostCtx, + wake: WakeFn, +} + +// SAFETY: the context is the host's per-instance state and the wake a plain code address; the +// mechanism states the wake callable from any thread for the instance's life. +unsafe impl Send for Wake {} +// SAFETY: as above. +unsafe impl Sync for Wake {} + +impl Wake { + /// The wake in the tables `open` handed the instance; `None` when the host handed none. + #[must_use] + pub fn of(tables: &HostTables) -> Option { + tables.wake.map(|wake| Self { + ctx: tables.ctx, + wake, + }) + } + + /// Wake `ticket`. + pub fn wake(&self, ticket: Ticket) { + (self.wake)(self.ctx, ticket); + } +} + /// The host services an instance was opened with: its context and the table. #[derive(Debug, Clone, Copy)] pub struct Services { diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index fa44c7d580..0a722b9c26 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -35,8 +35,12 @@ //! A one-request door (the mint, the SDP offer, the metadata document) answers its pieces through //! a [`RequestUnit`] the instance keeps by the kernel's unit key, built at the unit's first piece //! over the newest live generation's session params and audience. Its answer's bytes are paid into -//! the reply buffer across `more = 1` re-calls ([`crate::piece`]). A session -//! door's pieces are refused: the kernel's driver serves request units only. +//! the reply buffer across `more = 1` re-calls ([`crate::piece`]). +//! +//! A piece that names a stream is a live session's, served by the door's [`Sessions`] +//! ([`crate::session_door`]): the driver's duplex vocabulary (K6), one frame per answer, the +//! session's unsolicited output named on the instance's driver ticket, and the session's cumulative +//! units on every answer. A request unit on a session door is refused. use std::collections::BTreeMap; use std::mem::size_of; @@ -54,17 +58,18 @@ use busbar_contract::abi::mechanism::door::{ use busbar_contract::abi::mechanism::lifecycle::{ CancelIn, CancelOut, GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, }; +use busbar_contract::abi::mechanism::ticket::Ticket; use busbar_contract::abi::plane::{ ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, OpClass, OutField, PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, PIECE_HAS_STATUS, PIECE_LAST, - SHAPE_PIECEWISE, UNITS_ESTIMATED, + PIECE_OUT_TEXT, SHAPE_PIECEWISE, UNITS_ESTIMATED, UNITS_REPORTED, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; -use busbar_contract::abi::sdk::{Generations, HostBuf, Instance, Lent, Out, Safe, SafeSlot}; +use busbar_contract::abi::sdk::{Generations, HostBuf, Instance, Lent, Out, Safe, SafeSlot, Wake}; use busbar_contract::plane::{PER_SESSION, TOKEN_FAMILY}; use crate::claims::{Dialect, HTTP_TRANSPORT, WS_TRANSPORT}; @@ -73,6 +78,7 @@ use crate::driven::{Door, Steps}; use crate::meta; use crate::provider::{GEMINI_LIVE, OPENAI_REALTIME}; use crate::request_unit::{self, Answer, Piece, RequestUnit}; +use crate::session_door::{Sessions, Side, CEILING_TICK_NS}; /// The name the plane's Statement carries. pub const NAME: &str = crate::codec::PLANE_KEY; @@ -506,6 +512,13 @@ pub struct Plane { snapshots: Generations, /// The request units in flight, by the kernel's unit key. units: Mutex>, + /// The live sessions, by the kernel's stream. + sessions: Mutex, + /// The host's wake, as `open` handed it; `None` = no session output can be named. + wake: Option, + /// The instance's driver ticket, as its last `tick` was handed it, and that tick's clock + /// reading in milliseconds. + driver: Mutex<(Ticket, u64)>, } impl Plane { @@ -529,6 +542,26 @@ impl Plane { }) } + /// The newest live generation's section; `None` before the first publish. + fn newest(&self) -> Option { + lock(&self.generations).last().map(|(cfg, _)| cfg.clone()) + } + + /// Whether any live generation configures a session wall-clock ceiling. + fn any_ceiling(&self) -> bool { + lock(&self.generations) + .iter() + .any(|(cfg, _)| cfg.session_max_secs.is_some()) + } + + /// Name the instance's driver ticket: a session owes output of its own. + fn wake_driver(&self) { + let (ticket, _) = *lock(&self.driver); + if let (Some(wake), false) = (self.wake, ticket == Ticket::NONE) { + wake.wake(ticket); + } + } + /// Publish generation `generation` into the `out` field `pick` names. fn publish( &self, @@ -583,11 +616,15 @@ slot!(Open, PlaneOpenIn, PlaneOpenOut, |instance, input, out| { .ok() .filter(|s| !s.is_empty()) .map(str::to_owned); + let wake = input.field(|i| &i.open).host().and_then(|h| Wake::of(&h)); let p = Plane { public_url, generations: Mutex::new(Vec::new()), snapshots: Generations::new(), units: Mutex::new(BTreeMap::new()), + sessions: Mutex::new(Sessions::default()), + wake, + driver: Mutex::new((Ticket::NONE, 0)), }; p.publish(&mut out, |o| &o.snapshot, input.open.generation, cfg); instance.open(p); @@ -618,16 +655,58 @@ slot!(Retire, GenIn, OutHead, |instance, input, _out| { Outcome::Ready }); -slot!(Tick, TickIn, TickOut, |_, _, out| { - out.set(|o| &o.next_tick_ns, 0); +// The tick: the instance's driver ticket is noted, every live session's clock and ceiling read on +// the tick clock, and a session that now owes its end names the driver ticket. While a live +// generation configures a ceiling the door asks to be ticked every `CEILING_TICK_NS`. +slot!(Tick, TickIn, TickOut, |instance, input, out| { + let given = input.get(); + let next = match instance.get() { + Some(p) => { + *lock(&p.driver) = (given.head.ticket, given.now_ns / 1_000_000); + if lock(&p.sessions).tick(given.now_ns) { + p.wake_driver(); + } + if p.any_ceiling() { + given.now_ns.saturating_add(CEILING_TICK_NS) + } else { + 0 + } + } + None => 0, + }; + out.set(|o| &o.next_tick_ns, next); Outcome::Ready }); -slot!(Drive, PlaneDriveIn, PlaneDriveOut, |_, _, _out| { - Outcome::Ready -}); +// `drive`: the sessions with output of their own, by stream, as many as the host's buffer takes. +slot!( + Drive, + PlaneDriveIn, + PlaneDriveOut, + |instance, input, out| { + let Some(p) = instance.get() else { + return Outcome::Ready; + }; + let sessions = lock(&p.sessions); + let mut buf = input.sessions_buf(); + for stream in sessions.ready(usize::MAX) { + buf.push(stream); + } + if !buf.fits() { + out.set(|o| &o.sessions_needed, buf.needed() as u32); + return Outcome::Failed; + } + out.set(|o| &o.sessions_written, buf.written() as u32); + Outcome::Ready + } +); -slot!(Cancel, CancelIn, CancelOut, |_, _, out| { +// `cancel`: an op on a session's ticket ends the session (each side tells the other); a request +// unit's cancel is declined. +slot!(Cancel, CancelIn, CancelOut, |instance, input, out| { + if let Some(p) = instance.get() { + lock(&p.sessions).cancel(input.get().ticket); + } out.set(|o| &o.disposition, CANCEL_FAILED); Outcome::Ready }); @@ -753,16 +832,137 @@ fn answer_piece( } } +/// A live session's piece (`stream != 0`), answered by its [`crate::session_door::Live`]: the +/// caller's and the far end's pieces go through the session, a collection (`FROM_KERNEL`, no +/// attempt) answers its next queued frame, and the turn's ATTEMPT carries nothing of its own (the +/// turn's request is the caller side's answer). Every answer reports the session's cumulative +/// units and writes at most one frame; a session still owing output names the driver ticket. +fn session_piece(p: &Plane, input: Lent<'_, OnPieceIn>, out: &mut Out<'_, OnPieceOut>) -> Outcome { + let given = input.get(); + let Some(door) = Door::of(given.claim).filter(|d| d.is_session()) else { + return Outcome::Refused; + }; + let Some(cfg) = p.newest() else { + return Outcome::Refused; + }; + let now_ms = lock(&p.driver).1; + let mut sessions = lock(&p.sessions); + // Only the caller's piece opens a session: a far-end piece or a collection for a stream the + // door holds nothing for (an ended or cancelled session) is refused, never a fresh session. + let live = if given.from == FROM_CALLER { + sessions.get_or_open(given.stream, door, &cfg) + } else { + sessions.get(given.stream) + }; + if live.is_none() { + return Outcome::Refused; + } + sessions.crossed(given.stream, given.head.ticket); + let Some(live) = sessions.get(given.stream) else { + return Outcome::Refused; + }; + let near = given.from != FROM_FAR_END; + // A re-call after a `more = 1` answer pays what that side's frame still owes. + let owed = if near { &mut live.near } else { &mut live.far }; + if piece::is_recall(input) && owed.pending() { + owed.pay(input, out); + return Outcome::Ready; + } + let short = if near { + live.short_near.take() + } else { + live.short_far.take() + }; + let emit = match short { + Some(emit) => emit, + None => { + let caller_last = given.from == FROM_CALLER && given.flags & PIECE_LAST != 0; + let bytes = input.field(|i| &i.bytes).bytes(); + match given.from { + FROM_CALLER => live.from_caller(bytes, caller_last), + FROM_FAR_END => live.from_far_end(bytes, now_ms), + FROM_KERNEL => {} + _ => return Outcome::Refused, + } + // The caller's last piece ends the session: what it still owed either side has no + // one to go to. A turn's ATTEMPT carries nothing of its own. + if caller_last { + crate::session_door::Emit { + frame: None, + done: true, + } + } else if given.from == FROM_KERNEL && given.attempt_no > 0 { + crate::session_door::Emit::default() + } else { + live.next(near) + } + } + }; + let (fields, mut units, mut arena) = (input.fields_buf(), input.units_buf(), input.arena_buf()); + for (class, amount) in live.units() { + units.push(UnitCount { + class, + source: UNITS_REPORTED, + amount, + }); + } + let target = match emit.frame { + Some((Side::FarEnd, _)) => door.route(&cfg.session, None, &[]).ok().flatten(), + _ => None, + }; + let verb = target.as_ref().map(|a| arena.span(a.verb.as_bytes())); + let path = target.as_ref().map(|a| arena.span(a.target.as_bytes())); + if piece::settle(out, &fields, &units, &arena) { + // Short as a whole: the re-call carries the same piece, answered from here. + if near { + live.short_near = Some(emit); + } else { + live.short_far = Some(emit); + } + return Outcome::Failed; + } + let done = match emit.frame { + Some((Side::FarEnd, frame)) => { + if let (Some(verb), Some(path)) = (verb, path) { + out.set(|o| &o.verb, verb); + out.set(|o| &o.target, path); + } + live.near.owe(&frame, EMIT_TO_FAR_END, input, out); + false + } + Some((Side::Caller, frame)) => { + let owed = if near { &mut live.near } else { &mut live.far }; + owed.owe(&frame, PIECE_OUT_TEXT, input, out); + false + } + None if emit.done => { + out.set(|o| &o.flags, EMIT_DONE); + true + } + None => false, + }; + let owes = live.ready(); + if done { + sessions.close(given.stream); + } else if owes { + drop(sessions); + p.wake_driver(); + } + Outcome::Ready +} + // `on_piece`: a one-request door's pieces (the mint, the SDP offer, the metadata document), each // answered by the unit's [`RequestUnit`]: the ATTEMPT's request with its body, the caller's body to // the far end, and the caller's answer from the far end's. A finished or refused unit is forgotten. -// A live session's pieces are refused: the kernel's driver serves request units, and a session door -// is answered here once the driver hands a session its pieces. +// A piece that names a stream is a live session's ([`session_piece`]). slot!(OnPiece, OnPieceIn, OnPieceOut, |instance, input, out| { let Some(p) = instance.get() else { return Outcome::Failed; }; let given = input.get(); + if given.stream != 0 { + return session_piece(p, input, &mut out); + } let mut units = lock(&p.units); if piece::is_recall(input) { if let Some(held) = units.get_mut(&given.unit) { diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index 95c120ca89..6273ae1c97 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -64,6 +64,7 @@ pub mod provider; pub mod register; pub mod request_unit; pub mod session; +pub mod session_door; pub mod session_params; pub mod session_pump; pub mod session_row; diff --git a/crates/busbar-plane-streaming/src/session_door.rs b/crates/busbar-plane-streaming/src/session_door.rs new file mode 100644 index 0000000000..a9eaf06956 --- /dev/null +++ b/crates/busbar-plane-streaming/src/session_door.rs @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! LIVE SESSIONS THROUGH THE DOOR (`BUSBAR-1.6.0.md` Part 3, section 12, "Duplex sessions" (K6); +//! THE DESIGN, section 7, "A session is one unit with one line"). +//! +//! A piece that names a stream is a live session's. The door keeps one [`Live`] per stream, built +//! at the stream's first piece over the newest live generation's `streams:` section, and answers +//! each piece in the driver's duplex vocabulary: +//! +//! - A CALLER piece goes through the session ([`SessionUnit::from_caller`]); a frame it owes the +//! far end is that answer's `EMIT_TO_FAR_END` (one turn). The caller's last piece ends the +//! session: its open turn settles once and the answer is `EMIT_DONE`. +//! - A FAR-END piece goes through the session ([`SessionUnit::from_far_end`]); a frame it owes the +//! caller is that answer's emission. +//! - ONE FRAME PER ANSWER. Every frame keeps its own message boundary, so a piece that leaves more +//! than one frame owed (a barge-in's cancel and truncate, a caller frame and its echo) answers the +//! first and queues the rest. A queued frame is the session's unsolicited output: the door wakes +//! the instance's driver ticket, `drive` names the session, and each collection (`FROM_KERNEL`, +//! no bytes, no attempt) answers the next queued frame, toward the side it is bound for. +//! - EVERY ANSWER REPORTS THE SESSION'S CUMULATIVE UNITS, the six classes the plane counts. The +//! kernel checkpoints them and writes the session's one line at its end; the plane holds no +//! reservation and never cuts on money. +//! - THE WALL-CLOCK CEILING (`streams.session_max_secs`, Q21a) is measured on the kernel's tick +//! clock: while a live generation configures one the door asks to be ticked every +//! [`CEILING_TICK_NS`], a session's clock starts at the first tick after its first piece, and a +//! session past its ceiling is told why in its dialect and ends once its queued frames are +//! collected. +//! - A CANCEL on either side's ticket ends the session: it is forgotten, and the kernel's cleanup +//! writes its line. + +use std::collections::{BTreeMap, HashMap, VecDeque}; + +use busbar_contract::abi::mechanism::ticket::Ticket; + +use crate::codec::ir::codec::OpenAiRealtimeCodec; +use crate::codec::ir::GeminiLiveCodec; +use crate::config::StreamsCfg; +use crate::driven::{Door, Steps}; +use crate::session_params::g711_config; +use crate::session_unit::{CumulativeUnits, Plan, SessionUnit}; + +/// Nanoseconds per second, on the kernel's tick clock. +const NS_PER_SEC: u64 = 1_000_000_000; + +/// How often the door asks to be ticked while a live generation configures a session ceiling: a +/// session's clock starts, and its ceiling is enforced, at most this late. +/// +/// The clock starts at a tick rather than at the first piece because a piece carries no clock +/// reading on the tick clock, and a ceiling compared across two clocks is no ceiling. +pub const CEILING_TICK_NS: u64 = NS_PER_SEC; + +/// The most live sessions the door keeps at once; past it, a new stream is refused. +pub const MAX_SESSIONS: usize = 4096; + +/// The side a session's frame is bound for. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Side { + /// The far end: one turn. + FarEnd, + /// The caller. + Caller, +} + +/// The session over its door's codec. +#[derive(Debug)] +enum Unit { + /// The OpenAI Realtime far end (the sideband socket and the telephony socket). + Realtime(SessionUnit), + /// The Gemini Live far end. + Gemini(SessionUnit), +} + +macro_rules! each { + ($unit:expr, |$u:ident| $body:expr) => { + match $unit { + Unit::Realtime($u) => $body, + Unit::Gemini($u) => $body, + } + }; +} + +/// What one session answer carries. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct Emit { + /// The frame this answer writes, and the side it is bound for; `None` = nothing. + pub frame: Option<(Side, Vec)>, + /// The session is over: this answer is its last. + pub done: bool, +} + +/// ONE LIVE SESSION on the door. +#[derive(Debug)] +pub struct Live { + unit: Unit, + /// Frames owed a side, oldest first, past the one each answer carries. + queued: VecDeque<(Side, Vec)>, + /// The session ended; it is over once its queued frames are collected. + ending: bool, + /// The wall-clock ceiling of the generation it opened under, in seconds. + ceiling_secs: Option, + /// When its clock started, on the tick clock; `None` until the first tick after its open. + started_ns: Option, + /// What the frame a caller-side answer is writing still owes the reply buffer. + pub(crate) near: crate::piece::Owed, + /// What the frame a far-side answer is writing still owes the reply buffer. + pub(crate) far: crate::piece::Owed, + /// A caller-side answer that did not fit its buffers: the re-call carries the same piece, + /// which is answered from here rather than read twice. + pub(crate) short_near: Option, + /// The same, for a far-side answer. + pub(crate) short_far: Option, +} + +impl Live { + /// The session a stream's first piece on `door` opens, under `cfg`; `None` for a door that + /// answers one request. The telephony door locks the carrier's µ-law on both legs; the other + /// doors lock the section's session. + #[must_use] + pub fn open(door: Door, cfg: &StreamsCfg) -> Option { + let ceiling = cfg.session_max_secs.map(|s| u64::from(s.get())); + let unit = match door { + Door::Sideband => Unit::Realtime(SessionUnit::open( + OpenAiRealtimeCodec, + cfg.session.clone(), + false, + 0, + None, + )), + Door::Twilio => Unit::Realtime(SessionUnit::open( + OpenAiRealtimeCodec, + g711_config(), + true, + 0, + None, + )), + Door::Gemini => Unit::Gemini(SessionUnit::open( + GeminiLiveCodec, + cfg.session.clone(), + false, + 0, + None, + )), + Door::Mint | Door::Sdp | Door::Metadata => return None, + }; + Some(Live { + unit, + queued: VecDeque::new(), + ending: false, + ceiling_secs: ceiling, + started_ns: None, + near: crate::piece::Owed::default(), + far: crate::piece::Owed::default(), + short_near: None, + short_far: None, + }) + } + + /// Queue what `plan` owes either side, the far end's frames first. + fn take(&mut self, plan: Plan) { + self.queued + .extend(plan.to_far_end.into_iter().map(|f| (Side::FarEnd, f))); + self.queued + .extend(plan.to_caller.into_iter().map(|f| (Side::Caller, f))); + self.ending |= plan.end; + } + + /// A caller piece; `last` = the caller's side ended. + pub fn from_caller(&mut self, bytes: &[u8], last: bool) { + let plan = each!(&mut self.unit, |u| { + let mut plan = if bytes.is_empty() { + Plan::default() + } else { + u.from_caller(bytes) + }; + if last { + let ended = u.end(); + plan.end = ended.end; + } + plan + }); + self.take(plan); + } + + /// A far-end piece, at `now_ms`. + pub fn from_far_end(&mut self, bytes: &[u8], now_ms: u64) { + let plan = each!(&mut self.unit, |u| u.from_far_end(bytes, now_ms)); + self.take(plan); + } + + /// The tick clock reads `now_ns`: the session's clock starts on its first tick, and a session + /// past its ceiling is told why and ends. Answers whether it now owes a frame or its end. + pub fn tick(&mut self, now_ns: u64) -> bool { + let started = *self.started_ns.get_or_insert(now_ns); + let Some(ceiling) = self.ceiling_secs else { + return false; + }; + if self.ending || now_ns.saturating_sub(started) < ceiling.saturating_mul(NS_PER_SEC) { + return false; + } + let plan = each!(&mut self.unit, |u| u.close_at_ceiling(ceiling)); + self.take(plan); + true + } + + /// The next answer: one queued frame, or, with none queued, the session's end when it ended. + /// A caller-side answer (`near`) carries a frame toward either side, oldest first, and the + /// session's end; a far-side answer reaches only the caller, so it carries the oldest frame + /// bound for the caller and never the end (the caller side answers that, once collected). + pub fn next(&mut self, near: bool) -> Emit { + let at = if near { + (!self.queued.is_empty()).then_some(0) + } else { + self.queued + .iter() + .position(|(side, _)| *side == Side::Caller) + }; + match at.and_then(|i| self.queued.remove(i)) { + Some(frame) => Emit { + frame: Some(frame), + done: false, + }, + None => Emit { + frame: None, + done: near && self.ending, + }, + } + } + + /// Whether the session owes an answer of its own: a queued frame, or its end. + #[must_use] + pub fn ready(&self) -> bool { + !self.queued.is_empty() || self.ending + } + + /// The session's cumulative units, as the door reports them: (class index, amount), the zero + /// classes left out. + #[must_use] + pub fn units(&self) -> Vec<(u32, u64)> { + let units: CumulativeUnits = each!(&self.unit, |u| u.units()); + Door::Sideband.meter(&units) + } +} + +/// The door's live sessions, by the kernel's stream. +#[derive(Debug, Default)] +pub struct Sessions { + live: BTreeMap, + /// The tickets each session's pieces crossed on (one per side), so a cancel on either ends it. + tickets: HashMap, +} + +impl Sessions { + /// Stream `stream`'s session, opened on `door` under `cfg` at its first piece. `None` for a + /// door that answers one request, or a stream past [`MAX_SESSIONS`]. + pub fn get_or_open(&mut self, stream: u64, door: Door, cfg: &StreamsCfg) -> Option<&mut Live> { + if !self.live.contains_key(&stream) { + if self.live.len() >= MAX_SESSIONS { + return None; + } + self.live.insert(stream, Live::open(door, cfg)?); + } + self.live.get_mut(&stream) + } + + /// Stream `stream`'s session, when one is open. + pub fn get(&mut self, stream: u64) -> Option<&mut Live> { + self.live.get_mut(&stream) + } + + /// A piece of stream `stream` crossed on `ticket`. + pub fn crossed(&mut self, stream: u64, ticket: Ticket) { + if ticket != Ticket::NONE { + self.tickets.insert(ticket, stream); + } + } + + /// Stream `stream`'s session is over: it and its tickets are forgotten. + pub fn close(&mut self, stream: u64) { + self.live.remove(&stream); + self.tickets.retain(|_, s| *s != stream); + } + + /// The op on `ticket` is cancelled: the session it serves, if any, is over. Answers whether + /// one was. + pub fn cancel(&mut self, ticket: Ticket) -> bool { + match self.tickets.get(&ticket).copied() { + Some(stream) => { + self.close(stream); + true + } + None => false, + } + } + + /// The tick clock reads `now_ns`: every session's clock and ceiling. Answers whether any + /// session now owes an answer of its own. + pub fn tick(&mut self, now_ns: u64) -> bool { + let mut owed = false; + for live in self.live.values_mut() { + owed |= live.tick(now_ns); + } + owed + } + + /// The streams whose sessions owe an answer of their own, up to `cap`. + #[must_use] + pub fn ready(&self, cap: usize) -> Vec { + self.live + .iter() + .filter(|(_, l)| l.ready()) + .map(|(s, _)| *s) + .take(cap) + .collect() + } + + /// How many streams' sessions owe an answer of their own. + #[must_use] + pub fn ready_count(&self) -> usize { + self.live.values().filter(|l| l.ready()).count() + } + + /// How many sessions are live. + #[must_use] + pub fn len(&self) -> usize { + self.live.len() + } + + /// Whether none is. + #[must_use] + pub fn is_empty(&self) -> bool { + self.live.is_empty() + } +} + +#[cfg(test)] +#[path = "tests/session_door_tests.rs"] +mod tests; diff --git a/crates/busbar-plane-streaming/src/session_unit.rs b/crates/busbar-plane-streaming/src/session_unit.rs index 545f792d74..2149ae5074 100644 --- a/crates/busbar-plane-streaming/src/session_unit.rs +++ b/crates/busbar-plane-streaming/src/session_unit.rs @@ -222,7 +222,16 @@ where if now_ns.saturating_sub(self.opened_ns) < ceiling.saturating_mul(1_000_000_000) { return Plan::default(); } - let told = self.pump.ceiling_error(ceiling); + self.close_at_ceiling(ceiling) + } + + /// The session reached the wall-clock ceiling of `ceiling_secs` its host measured: it is told + /// why, in its dialect, and ends. + pub fn close_at_ceiling(&mut self, ceiling_secs: u64) -> Plan { + if self.ended { + return Plan::default(); + } + let told = self.pump.ceiling_error(ceiling_secs); let mut plan = self.end(); plan.to_caller = self.caller_frames(told.into_iter().collect()); plan diff --git a/crates/busbar-plane-streaming/src/tests/session_door_tests.rs b/crates/busbar-plane-streaming/src/tests/session_door_tests.rs new file mode 100644 index 0000000000..5b9fb0c6d3 --- /dev/null +++ b/crates/busbar-plane-streaming/src/tests/session_door_tests.rs @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! Tests for `crates/busbar-plane-streaming/src/session_door.rs`: a live session on the door, +//! one frame per answer, each side's frames in order, the session's end answered on the caller's +//! side only, and the ceiling read on the tick clock. + +use std::num::NonZeroU32; + +use super::*; + +fn json(v: serde_json::Value) -> Vec { + serde_json::to_vec(&v).expect("json") +} + +fn text(f: &[u8]) -> String { + String::from_utf8_lossy(f).into_owned() +} + +/// `ms` milliseconds of 24 kHz PCM16 uplink audio (48 bytes a millisecond). +fn uplink(ms: usize) -> Vec { + json(serde_json::json!({"type":"input_audio_buffer.append", + "audio": busbar_contract::media::base64_encode(&vec![0u8; ms * 48])})) +} + +fn usage_done() -> Vec { + json( + serde_json::json!({"type":"response.done","response":{"usage":{ + "input_token_details":{"audio_tokens":10,"text_tokens":3}, + "output_token_details":{"audio_tokens":20,"text_tokens":4}}}}), + ) +} + +fn audio_delta(item: &str) -> Vec { + json(serde_json::json!({"type":"response.output_audio.delta", + "delta": busbar_contract::media::base64_encode(&[0u8; 96]), + "item_id": item, "output_index":0, "content_index":0})) +} + +fn sideband() -> Live { + Live::open(Door::Sideband, &StreamsCfg::default()).expect("a session door") +} + +/// The class index of audio seconds in, in the tail's order. +fn seconds() -> u32 { + crate::session_unit::CLASSES + .iter() + .position(|c| *c == crate::meta::CLASS_AUDIO_SECONDS_IN) + .expect("declared") as u32 +} + +#[test] +fn a_one_request_door_opens_no_session() { + for door in [Door::Mint, Door::Sdp, Door::Metadata] { + assert!( + Live::open(door, &StreamsCfg::default()).is_none(), + "{door:?}" + ); + } +} + +#[test] +fn a_caller_frame_is_one_turn_toward_the_far_end() { + let mut s = sideband(); + s.from_caller(&uplink(1000), false); + let emit = s.next(true); + let Some((Side::FarEnd, frame)) = emit.frame else { + panic!("a frame toward the far end: {emit:?}"); + }; + assert!(text(&frame).contains("input_audio_buffer.append")); + assert!(!emit.done); + assert_eq!( + s.next(true), + Emit::default(), + "one frame, nothing more owed" + ); + assert!(!s.ready()); +} + +#[test] +fn the_far_ends_usage_reaches_the_caller_and_the_sessions_units() { + let mut s = sideband(); + s.from_caller(&uplink(2000), false); + let _ = s.next(true); + s.from_far_end(&usage_done(), 0); + assert_eq!( + s.units(), + vec![(0, 10), (1, 20), (2, 3), (3, 4), (seconds(), 2)], + "the closed turn's tokens and its admitted audio" + ); + let emit = s.next(false); + assert!( + matches!(&emit.frame, Some((Side::Caller, f)) if text(f).contains("response.done")) + || emit.frame.is_none(), + "{emit:?}" + ); +} + +/// A barge-in owes the far end two frames (cancel, truncate) and the caller one: a far-side answer +/// reaches only the caller, so it takes the caller's frame and leaves the far end's, in order, to +/// the caller side's collections. +#[test] +fn a_far_side_answer_carries_only_a_frame_for_the_caller() { + let mut s = sideband(); + s.from_far_end(&audio_delta("it1"), 0); + while s.next(false).frame.is_some() {} + s.from_far_end( + &json( + serde_json::json!({"type":"input_audio_buffer.speech_started", + "audio_start_ms":0,"item_id":"it1"}), + ), + 0, + ); + let far = s.next(false); + assert!( + matches!(&far.frame, Some((Side::Caller, f)) if text(f).contains("speech_started")), + "{far:?}" + ); + assert_eq!(s.next(false), Emit::default(), "no caller frame left"); + assert!( + s.ready(), + "the far end's frames are owed: the driver is named" + ); + let first = s.next(true); + let second = s.next(true); + assert!( + matches!(&first.frame, Some((Side::FarEnd, f)) if text(f).contains("response.cancel")), + "{first:?}" + ); + assert!( + matches!(&second.frame, Some((Side::FarEnd, f)) if text(f).contains("conversation.item.truncate")), + "{second:?}" + ); + assert!(!s.ready()); +} + +/// The caller's last piece settles the open turn once: the audio it admitted is in the session's +/// units, and only the caller side answers the end. +#[test] +fn the_callers_last_piece_settles_the_open_turn_and_ends_the_session() { + let mut s = sideband(); + s.from_caller(&uplink(3000), false); + let _ = s.next(true); + assert!(s.units().is_empty(), "no turn closed yet"); + s.from_caller(&[], true); + assert_eq!(s.units(), vec![(seconds(), 3)]); + assert!( + !s.next(false).done, + "a far-side answer never ends the session" + ); + assert!(s.next(true).done); +} + +#[test] +fn the_telephony_door_reads_the_carriers_envelope() { + let mut s = Live::open(Door::Twilio, &StreamsCfg::default()).expect("a session door"); + s.from_caller( + &json( + serde_json::json!({"event":"start","start":{"streamSid":"MZ1","callSid":"CA1", + "mediaFormat":{"encoding":"audio/x-mulaw","sampleRate":8000,"channels":1}}}), + ), + false, + ); + assert_eq!(s.next(true), Emit::default()); + s.from_caller( + &json(serde_json::json!({"event":"media","streamSid":"MZ1", + "media":{"payload": busbar_contract::media::base64_encode(&[0x7f; 160])}})), + false, + ); + let emit = s.next(true); + assert!( + matches!(&emit.frame, Some((Side::FarEnd, f)) if text(f).contains("input_audio_buffer.append")), + "{emit:?}" + ); + s.from_caller(&json(serde_json::json!({"event":"stop"})), false); + assert!(s.next(true).done, "a stop ends the call"); +} + +/// The ceiling is read on the tick clock: the session's clock starts at its first tick, and a +/// session past its ceiling tells the caller why and then ends. +#[test] +fn a_session_past_its_ceiling_is_told_why_then_ends() { + let cfg = StreamsCfg { + session_max_secs: NonZeroU32::new(5), + ..StreamsCfg::default() + }; + let mut s = Live::open(Door::Sideband, &cfg).expect("a session door"); + assert!(!s.tick(1_000), "the clock starts"); + assert!(!s.tick(1_000 + 4 * NS_PER_SEC), "inside its ceiling"); + assert!(s.tick(1_000 + 5 * NS_PER_SEC), "at its ceiling"); + let told = s.next(true); + assert!( + matches!(&told.frame, Some((Side::Caller, f)) if text(f).contains("session_max_secs")), + "{told:?}" + ); + assert!(s.next(true).done); + assert!( + !s.tick(1_000 + 9 * NS_PER_SEC), + "an ended session is told once" + ); +} + +#[test] +fn a_session_with_no_ceiling_runs_on() { + let mut s = sideband(); + assert!(!s.tick(0)); + assert!(!s.tick(u64::MAX / 2)); + assert!(!s.ready()); +} + +#[test] +fn a_cancel_on_either_sides_ticket_ends_the_session() { + let t = |slot| Ticket { + slot, + generation: 1, + }; + let mut all = Sessions::default(); + let cfg = StreamsCfg::default(); + for stream in [7, 8] { + assert!(all.get_or_open(stream, Door::Sideband, &cfg).is_some()); + } + all.crossed(7, t(1)); + all.crossed(7, t(2)); + all.crossed(8, t(3)); + assert!(!all.cancel(t(9)), "a ticket no session crossed on"); + assert!(all.cancel(t(2)), "the far side's ticket"); + assert!(all.get(7).is_none()); + assert!(!all.cancel(t(1)), "its other ticket is forgotten with it"); + assert_eq!(all.len(), 1); + assert!(all.get_or_open(9, Door::Mint, &cfg).is_none()); +} + +#[test] +fn drive_names_the_sessions_that_owe_output() { + let mut all = Sessions::default(); + let cfg = StreamsCfg::default(); + for stream in [3, 4, 5] { + let _ = all.get_or_open(stream, Door::Sideband, &cfg); + } + assert!(all.ready(8).is_empty()); + all.get(4).expect("open").from_caller(&[], true); + all.get(5).expect("open").from_caller(&uplink(10), false); + assert_eq!(all.ready(8), vec![4, 5]); + assert_eq!(all.ready(1), vec![4]); + assert_eq!(all.ready_count(), 2); +} diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index 1d3ca328e6..cc97437919 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -390,14 +390,18 @@ fn at(buf: &[u8], s: Span) -> String { /// The host's buffers for one `on_piece`. struct Bufs { reply: Vec, - units: [UnitCount; 2], + units: Vec, fields: [OutField; 4], arena: [u8; 256], + /// The units the last answer wrote (class, source, amount), or, short, the room it needed. + last_units: String, } /// One piece pushed to a unit: whose it is, its flags, bytes, status and kept head fields. struct Push<'a> { unit: u64, + /// A live session's stream (`0` = a request unit). + stream: u64, claim: u32, from: u32, flags: u32, @@ -409,11 +413,16 @@ struct Push<'a> { impl Bufs { fn new(reply_cap: usize) -> Self { + Self::with_units(reply_cap, 2) + } + + fn with_units(reply_cap: usize, units: usize) -> Self { Self { reply: vec![0; reply_cap], - units: [z(); 2], + units: vec![z(); units], fields: [z(); 4], arena: [0; 256], + last_units: String::new(), } } @@ -422,6 +431,7 @@ impl Bufs { let mut i: OnPieceIn = z(); i.head = in_head(); (i.unit, i.claim, i.from, i.flags) = (push.unit, push.claim, push.from, push.flags); + i.stream = push.stream; (i.attempt_no, i.bytes, i.status_code) = (push.attempt_no, octets(push.bytes), push.status); i.caller_ref = text(CALLER_REF); if !push.head.is_empty() { @@ -436,6 +446,15 @@ impl Bufs { let mut f = Frame::new(i, o); let c = p.call(slot::ON_PIECE, &mut f); let o = f.out; + self.last_units = if o.units_needed != 0 { + format!("needed {}", o.units_needed) + } else { + self.units[..o.units_written as usize] + .iter() + .map(|u| format!("{}:{}={}", u.class, u.source, u.amount)) + .collect::>() + .join(" ") + }; let fields: Vec = self.fields[..o.fields_written as usize] .iter() .map(|f| format!("{}={}", at(&self.arena, f.name), at(&self.arena, f.value))) @@ -458,6 +477,7 @@ impl Bufs { const fn push(unit: u64, claim: u32, from: u32, flags: u32, bytes: &[u8]) -> Push<'_> { Push { unit, + stream: 0, claim, from, flags, @@ -601,3 +621,138 @@ fn the_dropped_door_answers_the_one_request_doors_as_the_linked_door_does() { "the two doors must answer identically" ); } + +// ── LIVE SESSIONS THROUGH THE DOOR (K6) ────────────────────────────────────────────────────────── + +/// A piece of the live session on stream `stream` (claim 2, the sideband socket). +const fn session(stream: u64, from: u32, flags: u32, bytes: &[u8]) -> Push<'_> { + Push { + stream, + ..push(stream, 2, from, flags, bytes) + } +} + +/// `ms` milliseconds of 24 kHz PCM16 uplink audio, as the caller's realtime frame. +fn uplink(ms: usize) -> Vec { + serde_json::to_vec(&serde_json::json!({"type":"input_audio_buffer.append", + "audio": busbar_contract::media::base64_encode(&vec![0u8; ms * 48])})) + .expect("json") +} + +const USAGE_DONE: &[u8] = br#"{"type":"response.done","response":{"usage":{"input_token_details":{"audio_tokens":10,"text_tokens":3},"output_token_details":{"audio_tokens":20,"text_tokens":4}}}}"#; + +/// THE SESSION SCRIPT, in the order the driver pushes a session's pieces: the caller's audio (a +/// turn toward the far end), the turn's ATTEMPT, the far end's usage, a collection with nothing +/// owed, an answer short of unit room and its re-call, and the caller's end. +fn session_script(p: &Plugin) -> Vec { + let mut t = vec![format!("open {:?}", open(p, 1, SESSION, Some(PUBLIC)).0)]; + let mut b = Bufs::with_units(1 << 18, 6); + let line = |t: &mut Vec, b: &mut Bufs, what: &str, push: &Push<'_>| { + let answer = b.call(p, push); + t.push(format!("{what} {answer} units=[{}]", b.last_units)); + }; + let audio = uplink(2000); + line( + &mut t, + &mut b, + "caller audio", + &session(20, FROM_CALLER, 0, &audio), + ); + let turn = Push { + attempt_no: 1, + ..session(20, FROM_KERNEL, 0, &[]) + }; + line(&mut t, &mut b, "turn attempt", &turn); + let far = Push { + status: 101, + ..session(20, FROM_FAR_END, PIECE_HAS_STATUS, USAGE_DONE) + }; + line(&mut t, &mut b, "far usage", &far); + line(&mut t, &mut b, "collect", &session(20, FROM_KERNEL, 0, &[])); + let mut narrow = Bufs::with_units(1 << 18, 2); + let short = session(20, FROM_CALLER, 0, &audio); + let answer = narrow.call(p, &short); + t.push(format!("short {answer} units=[{}]", narrow.last_units)); + line(&mut t, &mut b, "short re-call", &short); + line( + &mut t, + &mut b, + "caller end", + &session(20, FROM_CALLER, PIECE_LAST, &[]), + ); + line( + &mut t, + &mut b, + "request unit on a session door", + &attempt(21, 2), + ); + t +} + +#[test] +fn a_live_session_is_answered_through_the_door() { + let d = Dispatcher::new(DispatchConfig::default()); + let t = session_script(&linked(&d)); + let payload = busbar_contract::media::base64_encode(&vec![0u8; 2000 * 48]); + let line = |what: &str| { + t.iter() + .find(|l| l.starts_with(what)) + .unwrap_or_else(|| panic!("no `{what}` line in {t:#?}")) + .clone() + }; + assert_eq!(t[0], "open Ready"); + let caller = line("caller audio"); + assert!( + caller.contains("far=true") + && caller.contains("verb=GET target=/v1/realtime ") + && caller.contains("input_audio_buffer.append") + && caller.contains(&payload) + && caller.contains("more=0") + && caller.ends_with("units=[]"), + "the caller's frame is one turn toward the far end, its audio unchanged: {caller}" + ); + assert_eq!( + line("turn attempt"), + "turn attempt Ready emitted= more=0 far=false done=false status=0 verb= target= \ + fields=[] units=[]", + "the turn's request is the caller side's answer" + ); + let far = line("far usage"); + assert!( + far.contains("far=false done=false") + && far.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=2]"), + "the closed turn's tokens and its two admitted seconds, reported: {far}" + ); + assert!(line("collect ").contains("emitted= more=0 far=false done=false")); + assert!( + line("short ").starts_with("short Failed") && line("short ").ends_with("units=[needed 5]"), + "{}", + line("short ") + ); + let recall = line("short re-call"); + assert!( + recall.contains("far=true") && recall.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=2]"), + "the re-call answers the same piece once, its audio not counted twice: {recall}" + ); + let end = line("caller end"); + assert!( + end.contains("done=true") && end.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=4]"), + "the caller's end settles the open turn's two seconds once: {end}" + ); + assert!(line("request unit on a session door") + .starts_with("request unit on a session door Refused")); +} + +#[test] +fn the_dropped_door_answers_a_live_session_as_the_linked_door_does() { + let d = Dispatcher::new(DispatchConfig::default()); + let Some(dropped) = dropped(&d) else { + eprintln!("skip: the streaming_door example cdylib is not built"); + return; + }; + assert_eq!( + session_script(&dropped), + session_script(&linked(&d)), + "the two doors must answer a session identically" + ); +} From 7dc241a8e75db66d2471ae8dfb6770d7b58025f5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:34:11 -0700 Subject: [PATCH 21/82] busbar-plane-streaming door: the per-session fee unit, the route the arrival names, project and the session open's hook view, upgrade-line claims, one outbound need per (transport, auth), text far writes, the lost voice proofs (plane half of lane-dg-streaming 974b5f7756 5b8f14583d 322d2d46c0 98e9a95350 cb5b3e4d88 3cf37afbf2 0de6bdc5ea) Written against the plane-seam names (OnPieceOut::need, MULTI-NEED); builds once p3-plane-seam's SEAM-ABI lands. --- crates/busbar-plane-streaming/src/door.rs | 266 ++++++++++++++++-- crates/busbar-plane-streaming/src/piece.rs | 13 +- .../src/session_door.rs | 110 +++++++- .../src/tests/session_door_tests.rs | 101 ++++++- .../tests/conformance.rs | 177 +++++++++++- 5 files changed, 618 insertions(+), 49 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 0a722b9c26..1065393dac 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -48,10 +48,13 @@ use std::ptr; use std::sync::Mutex; use crate::piece::{self, Owed}; +use busbar_contract::abi::hook::REQUEST_STREAM; use busbar_contract::abi::host::conn::connector::{ Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND, KEEP_NAMED, }; -use busbar_contract::abi::mechanism::call::{AbiStr, Blob, InHead, OutHead, Outcome, BLOB_ABSENT}; +use busbar_contract::abi::mechanism::call::{ + AbiStr, Blob, InHead, OutHead, Outcome, Span, BLOB_ABSENT, +}; use busbar_contract::abi::mechanism::door::{ KindTailHead, Section, Statement, SECTION_DECLARING, SECTION_REQUIRED, }; @@ -65,7 +68,7 @@ use busbar_contract::abi::plane::{ PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, PIECE_HAS_STATUS, PIECE_LAST, - PIECE_OUT_TEXT, SHAPE_PIECEWISE, UNITS_ESTIMATED, UNITS_REPORTED, + PIECE_OUT_TEXT, ROUTE_DIRECT, SHAPE_PIECEWISE, SPAN_ABSENT, UNITS_ESTIMATED, UNITS_REPORTED, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; @@ -78,7 +81,7 @@ use crate::driven::{Door, Steps}; use crate::meta; use crate::provider::{GEMINI_LIVE, OPENAI_REALTIME}; use crate::request_unit::{self, Answer, Piece, RequestUnit}; -use crate::session_door::{Sessions, Side, CEILING_TICK_NS}; +use crate::session_door::{committed_session_config, Live, Sessions, Side, CEILING_TICK_NS}; /// The name the plane's Statement carries. pub const NAME: &str = crate::codec::PLANE_KEY; @@ -130,25 +133,52 @@ const SECTIONS: &[Section] = &[ }, ]; -/// The dialects, in the order the per-call `dialect` index names them. +/// The dialects, in the order the per-call `dialect` index names them; then the PROVIDER +/// protocols a member's provider states, as the far-end spellings of the two realtime dialects +/// (ARCHITECT Q-L5B-NEEDS: the provider protocol -> realtime dialect mapping is the plane's own): +/// `openai` is spoken as OpenAI Realtime, `gemini` as Gemini Live. No arrival names them. pub const DIALECTS_BY_NAME: &[&str] = &[ OPENAI_REALTIME, GEMINI_LIVE, Dialect::TwilioMediaStreams.name(), + OPENAI_PROVIDER, + GEMINI_PROVIDER, ]; +/// The provider protocol whose realtime dialect is OpenAI Realtime. +pub const OPENAI_PROVIDER: &str = "openai"; +/// The provider protocol whose realtime dialect is Gemini Live. +pub const GEMINI_PROVIDER: &str = "gemini"; + const DIALECTS: &[AbiStr] = &[ abi_str(DIALECTS_BY_NAME[0]), abi_str(DIALECTS_BY_NAME[1]), abi_str(DIALECTS_BY_NAME[2]), + abi_str(DIALECTS_BY_NAME[3]), + abi_str(DIALECTS_BY_NAME[4]), ]; -/// The upstream dialects' default auth styles. -const DIALECT_AUTH: &[DialectAuth] = &[DialectAuth { - dialect: 0, - _reserved: 0, - style: abi_str("bearer"), -}]; +/// The OpenAI Realtime far end's credential style (`Authorization: Bearer`). +pub const REALTIME_STYLE: &str = "bearer"; +/// The Gemini Live far end's credential style (the `x-goog-api-key` header). +pub const LIVE_STYLE: &str = "x-goog-api-key"; + +const fn dialect_auth(dialect: u32, style: &'static str) -> DialectAuth { + DialectAuth { + dialect, + _reserved: 0, + style: abi_str(style), + } +} + +/// The upstream dialects' default auth styles: each realtime dialect's, and its provider +/// protocol's spelling's. +const DIALECT_AUTH: &[DialectAuth] = &[ + dialect_auth(0, REALTIME_STYLE), + dialect_auth(1, LIVE_STYLE), + dialect_auth(3, REALTIME_STYLE), + dialect_auth(4, LIVE_STYLE), +]; const SCOPE_KINDS: &[AbiStr] = &[abi_str(SCOPE)]; @@ -165,7 +195,10 @@ const fn class(class: &'static str, family: &'static str) -> BillableClass { } } -/// The six classes a session counts in; `cached_tokens` is attribution only, never billed. +/// The six classes a session counts in (`cached_tokens` is attribution only, never billed), then +/// the session's fee unit: `per_session`, reported `1` once the far end first answers the session +/// and never before, so a session whose open failed refunds its fee (ARCHITECT Q-L5-FEE (A), Q17-6; +/// the fee unit is a billable class reported as a count of 0 or 1 and never ledgered, Q17-5 (c)). const BILLABLE_CLASSES: &[BillableClass] = &[ class(meta::CLASS_AUDIO_TOKENS_IN.as_str(), TOKEN_FAMILY), class(meta::CLASS_AUDIO_TOKENS_OUT.as_str(), TOKEN_FAMILY), @@ -173,6 +206,7 @@ const BILLABLE_CLASSES: &[BillableClass] = &[ class(meta::CLASS_TEXT_TOKENS_OUT.as_str(), TOKEN_FAMILY), class(meta::CLASS_AUDIO_SECONDS_IN.as_str(), "duration"), class(meta::CLASS_TOOL_CALLS.as_str(), "count"), + class(PER_SESSION, "count"), ]; const FEE_UNITS: &[AbiStr] = &[abi_str(PER_SESSION)]; @@ -203,9 +237,11 @@ const fn need(direction: u32, transport: &'static str, auth: AbiStr, target: Abi } } -/// The plane's connection needs: its keyed doors and its telephony door inbound; the provider's -/// realtime socket and its one-shot passes outbound, to the upstream `streams.session.model` names. -/// The outbound credential is the kernel's, per the dialect's default style. +/// The plane's connection needs: its keyed doors and its telephony door inbound; outbound, to the +/// upstream `streams.session.model` names, OpenAI Realtime's socket and its one-shot passes (the +/// mint, the SDP offer) under its bearer style, and Gemini Live's socket under its key header +/// (ARCHITECT Q-L5B-NEEDS: every outbound need declares its auth; the kernel binds each a member's +/// style names, one per (transport, auth), and a far request names the one it rides). const NEEDS: &[Need] = &[ need(DIRECTION_INBOUND, WS_TRANSPORT, abi_str(KEY_AUTH), NONE), need(DIRECTION_INBOUND, HTTP_TRANSPORT, abi_str(KEY_AUTH), NONE), @@ -218,17 +254,31 @@ const NEEDS: &[Need] = &[ need( DIRECTION_OUTBOUND, WS_TRANSPORT, - NONE, + abi_str(REALTIME_STYLE), abi_str(EGRESS_TARGET), ), need( DIRECTION_OUTBOUND, HTTP_TRANSPORT, - NONE, + abi_str(REALTIME_STYLE), + abi_str(EGRESS_TARGET), + ), + need( + DIRECTION_OUTBOUND, + WS_TRANSPORT, + abi_str(LIVE_STYLE), abi_str(EGRESS_TARGET), ), ]; +/// The need a far request rides, as `OnPieceOut::need` names it (its index in [`NEEDS`] plus one): +/// OpenAI Realtime's socket. +pub const RIDES_REALTIME_SOCKET: u32 = 4; +/// OpenAI Realtime's one-shot passes (the mint, the SDP offer). +pub const RIDES_REALTIME_PASS: u32 = 5; +/// Gemini Live's socket. +pub const RIDES_LIVE_SOCKET: u32 = 6; + /// THE STATEMENT TAIL. pub const TAIL: &PlaneTail = &PlaneTail { head: KindTailHead { @@ -716,8 +766,11 @@ slot!(Release, ReleaseIn, OutHead, |_, _, _out| { Outcome::Ready }); slot!(Close, InHead, OutHead, |_, _, _out| { Outcome::Ready }); // A claimed arrival is classified: its door's dialect, the session open, a known principal. No -// admission estimate: a session's units are what the far end reports. -slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { +// admission estimate: a session's units are what the far end reports. A door that reaches the far end +// names its route: the DIRECT entry `streams.session.model` (ARCHITECT Q-L5B-ROUTE: the composition +// folds that top-level catalog model into the plane's route table, so the kernel's door steps resolve +// it as any section entry). The metadata door reaches none and names none. +slot!(Arrive, ArriveIn, ArriveOut, |instance, input, out| { let Some(a) = crate::driven::arrive(input.claim) else { out.set(|o| &o.refusal, REFUSAL_NO_DOOR); out.set(|o| &o.refusal_status, 404); @@ -739,6 +792,13 @@ slot!(Arrive, ArriveIn, ArriveOut, |_, input, out| { out.set(|o| &o.op_class, a.op_class); out.set(|o| &o.dialect, a.dialect); out.set(|o| &o.principal_need, a.door.authenticate()); + let model = (!a.door.is_open()) + .then(|| instance.get().and_then(Plane::newest)) + .flatten() + .and_then(|cfg| cfg.session.model); + if let Some(model) = model { + out.route(ROUTE_DIRECT, &model); + } Outcome::Ready }); @@ -814,6 +874,7 @@ fn answer_piece( } out.set(|o| &o.verb, verb); out.set(|o| &o.target, target); + out.set(|o| &o.need, RIDES_REALTIME_PASS); held.owed.owe(&attempt.body, EMIT_TO_FAR_END, input, out); (Outcome::Ready, false) } @@ -927,7 +988,17 @@ fn session_piece(p: &Plane, input: Lent<'_, OnPieceIn>, out: &mut Out<'_, OnPiec out.set(|o| &o.verb, verb); out.set(|o| &o.target, path); } - live.near.owe(&frame, EMIT_TO_FAR_END, input, out); + out.set( + |o| &o.need, + if door == Door::Gemini { + RIDES_LIVE_SOCKET + } else { + RIDES_REALTIME_SOCKET + }, + ); + // A realtime dialect's frame is a JSON text message on its socket. + live.near + .owe(&frame, EMIT_TO_FAR_END | PIECE_OUT_TEXT, input, out); false } Some((Side::Caller, frame)) => { @@ -1014,8 +1085,59 @@ slot!(OnPiece, OnPieceIn, OnPieceOut, |instance, input, out| { // The refusal render, the admin serve and the hook projection are declined: a declined unit is // charged nothing. -slot!(Refusal, RefusalIn, RefusalOut, |_, _, _out| { - Outcome::Refused +/// A refusal in the realtime dialects' error shape (the OpenAI REST error envelope, which both +/// the session doors and the one-request doors answer a refused open in): the kernel's status read +/// as the error's type, its text as the message. +#[must_use] +pub fn refusal_body(status: u16, text: &str) -> Vec { + let kind = match status { + 401 => "authentication_error", + 403 => "permission_error", + 404 => "not_found_error", + 429 => "rate_limit_error", + 400..=499 => "invalid_request_error", + _ => "server_error", + }; + serde_json::to_vec(&serde_json::json!({ + "error": {"message": text, "type": kind, "param": null, "code": null} + })) + .unwrap_or_default() +} + +/// The response field naming a refusal body's document type. +const FIELD_CONTENT_TYPE: &str = "content-type"; +/// A refusal body's document type. +const CONTENT_TYPE_JSON: &str = "application/json"; + +// `refusal`: the kernel's status and text (a hook's veto, an admission's refusal) in the dialects' +// error shape, as JSON, before any upgrade. +slot!(Refusal, RefusalIn, RefusalOut, |_, input, out| { + let given = input.get(); + let status = u16::try_from(given.status).unwrap_or(0); + let text = input.field(|i| &i.text).as_str().unwrap_or_default(); + let body = refusal_body(status, text); + let (mut reply, mut fields, mut arena) = + (input.reply_buf(), input.fields_buf(), input.arena_buf()); + reply.extend(&body); + fields.push(OutField { + name: arena.span(FIELD_CONTENT_TYPE.as_bytes()), + value: arena.span(CONTENT_TYPE_JSON.as_bytes()), + }); + let short = !(reply.fits() && fields.fits() && arena.fits()); + let (rw, rnd) = reply.settle(short); + let (fw, fnd) = fields.settle(short); + let (aw, and) = arena.settle(short); + out.set(|o| &o.reply_written, rw as u64); + out.set(|o| &o.reply_needed, rnd as u64); + out.set(|o| &o.fields_written, fw as u32); + out.set(|o| &o.fields_needed, fnd as u32); + out.set(|o| &o.arena_written, aw as u64); + out.set(|o| &o.arena_needed, and as u64); + if short { + Outcome::Failed + } else { + Outcome::Ready + } }); slot!(Serve, ServeIn, ServeOut, |_, _, _out| { Outcome::Refused }); @@ -1024,8 +1146,98 @@ slot!(Hydrate, GenIn, OutHead, |_, _, _out| { Outcome::Ready }); slot!(Start, GenIn, OutHead, |_, _, _out| { Outcome::Ready }); -slot!(Project, ProjectIn, ProjectOut, |_, _, _out| { - Outcome::Refused +// `project` (ARCHITECT Q-L5B-PROJECT; K5): the hook view of a unit's open, once, when a hook is +// bound. A session door projects the session-open params it locks (the telephony door's µ-law, else +// the section's session) as the view's body, the session-open the hooks screen; a hook's rewrite is +// patched over them ([`committed_session_config`]), kept for the session the unit opens, and THAT is +// projected and answered. A rewrite that does not read back refuses the open. A one-request door +// projects its view with no body, and takes no rewrite. +slot!(Project, ProjectIn, ProjectOut, |instance, input, out| { + let Some(p) = instance.get() else { + return Outcome::Failed; + }; + let given = input.get(); + let Some(door) = Door::of(given.claim) else { + return Outcome::Refused; + }; + let Some(cfg) = p.newest() else { + return Outcome::Refused; + }; + let rewrite = input.field(|i| &i.rewrite).bytes(); + let (body, rewritten) = if door.is_session() { + let locked = Live::locked(door, &cfg); + let params = if rewrite.is_empty() { + locked + } else { + match committed_session_config(&locked, rewrite) { + Ok(params) => { + lock(&p.sessions).rewrite(given.unit, params.clone()); + params + } + Err(_) => return Outcome::Refused, + } + }; + let Ok(body) = serde_json::to_vec(¶ms) else { + return Outcome::Refused; + }; + (Some(body), !rewrite.is_empty()) + } else if rewrite.is_empty() { + (None, false) + } else { + return Outcome::Refused; + }; + let (signals, mut arena, turns) = + (input.signals_buf(), input.arena_buf(), input.messages_buf()); + let dialect = DIALECTS_BY_NAME + .get(door.dialect() as usize) + .map(|d| arena.span(d.as_bytes())); + let pool = cfg + .session + .model + .as_deref() + .map(|m| arena.span(m.as_bytes())); + let body = body.as_deref().map(|b| arena.span(b)); + let short = !(signals.fits() && arena.fits() && turns.fits()); + let (sw, snd) = signals.settle(short); + let (aw, and) = arena.settle(short); + let (tw, tnd) = turns.settle(short); + out.set(|o| &o.signals_needed, snd as u32); + out.set(|o| &o.arena_written, aw as u64); + out.set(|o| &o.arena_needed, and as u64); + out.set(|o| &o.messages_needed, tnd as u32); + let absent = Span { + offset: SPAN_ABSENT, + len: 0, + }; + out.set(|o| &o.body, absent); + out.set(|o| &o.rewritten, absent); + if short { + // The driver re-calls once with the buffers this named, the same rewrite with it: patching + // it again over the locked params writes the same body. + return Outcome::Failed; + } + if let Some(pool) = pool { + out.host_str(|o| &o.view.pool, &arena, pool); + } + if let Some(dialect) = dialect { + out.host_str(|o| &o.view.ingress_dialect, &arena, dialect); + } + out.set( + |o| &o.view.flags, + if door.is_session() { REQUEST_STREAM } else { 0 }, + ); + out.host_rows(|o| &o.view.signals, &signals); + out.set(|o| &o.view.signals_len, sw); + out.set(|o| &o.prompt.message_count, tw as u64); + out.host_rows(|o| &o.prompt.messages, &turns); + out.set(|o| &o.prompt.messages_len, tw); + if let Some(body) = body { + out.set(|o| &o.body, body); + if rewritten { + out.set(|o| &o.rewritten, body); + } + } + Outcome::Ready }); busbar_contract::plugin_door! { @@ -1052,3 +1264,11 @@ busbar_contract::plugin_door! { #[cfg(test)] #[path = "tests/ladder_tests.rs"] mod ladder_tests; + +const _: () = { + // The ride constants name the needs they say they name. + assert!(NEEDS[RIDES_REALTIME_SOCKET as usize - 1].direction == DIRECTION_OUTBOUND); + assert!(NEEDS[RIDES_REALTIME_PASS as usize - 1].direction == DIRECTION_OUTBOUND); + assert!(NEEDS[RIDES_LIVE_SOCKET as usize - 1].direction == DIRECTION_OUTBOUND); + assert!(DIALECTS.len() == DIALECTS_BY_NAME.len()); +}; diff --git a/crates/busbar-plane-streaming/src/piece.rs b/crates/busbar-plane-streaming/src/piece.rs index 08b89a7d03..f72fa6a714 100644 --- a/crates/busbar-plane-streaming/src/piece.rs +++ b/crates/busbar-plane-streaming/src/piece.rs @@ -13,7 +13,9 @@ //! (`EMIT_DONE` only with the last byte) is written once in this plane. Each plane door carries //! this logic today; one SDK home for all of them is 1.6.0-QUESTIONS.md PIECE-HOME. -use busbar_contract::abi::plane::{OnPieceIn, OnPieceOut, OutField, UnitCount, EMIT_DONE}; +use busbar_contract::abi::plane::{ + OnPieceIn, OnPieceOut, OutField, UnitCount, EMIT_DONE, EMIT_TO_FAR_END, PIECE_OUT_TEXT, +}; use busbar_contract::abi::sdk::{HostBuf, Lent, Out}; /// Whether `input` is the kernel's re-call after a `more = 1` answer: no bytes, no flags and no @@ -56,8 +58,9 @@ impl Owed { } /// Write what is owed into the reply buffer, as much as fits: `more = 1` while any is left, and - /// the owed flags on every write, `EMIT_DONE` held back until the last byte. Answers whether the - /// unit is done. + /// the owed flags on every write, `EMIT_DONE` held back until the last byte, and so is a + /// far-bound message's `PIECE_OUT_TEXT` (the kernel gathers a far-bound message whole, so its + /// last piece names it text). Answers whether the unit is done. pub fn pay(&mut self, input: Lent<'_, OnPieceIn>, out: &mut Out<'_, OnPieceOut>) -> bool { let n = input.reply_buf().stream(&self.bytes[self.at..]); self.at += n; @@ -66,7 +69,9 @@ impl Owed { out.set(|o| &o.more, u32::from(more)); out.set( |o| &o.flags, - if more { + if more && self.flags & EMIT_TO_FAR_END != 0 { + self.flags & !(EMIT_DONE | PIECE_OUT_TEXT) + } else if more { self.flags & !EMIT_DONE } else { self.flags diff --git a/crates/busbar-plane-streaming/src/session_door.rs b/crates/busbar-plane-streaming/src/session_door.rs index a9eaf06956..a9e93d0f06 100644 --- a/crates/busbar-plane-streaming/src/session_door.rs +++ b/crates/busbar-plane-streaming/src/session_door.rs @@ -18,8 +18,10 @@ //! first and queues the rest. A queued frame is the session's unsolicited output: the door wakes //! the instance's driver ticket, `drive` names the session, and each collection (`FROM_KERNEL`, //! no bytes, no attempt) answers the next queued frame, toward the side it is bound for. -//! - EVERY ANSWER REPORTS THE SESSION'S CUMULATIVE UNITS, the six classes the plane counts. The -//! kernel checkpoints them and writes the session's one line at its end; the plane holds no +//! - EVERY ANSWER REPORTS THE SESSION'S CUMULATIVE UNITS, the six classes the plane counts, and, +//! once the far end has first answered the session, its fee unit ([`PER_SESSION_CLASS`], `1`): +//! a session whose far end never answered (its open failed) reports none, and its fee is refunded. +//! The kernel checkpoints them and writes the session's one line at its end; the plane holds no //! reservation and never cuts on money. //! - THE WALL-CLOCK CEILING (`streams.session_max_secs`, Q21a) is measured on the kernel's tick //! clock: while a live generation configures one the door asks to be ticked every @@ -34,6 +36,7 @@ use std::collections::{BTreeMap, HashMap, VecDeque}; use busbar_contract::abi::mechanism::ticket::Ticket; use crate::codec::ir::codec::OpenAiRealtimeCodec; +use crate::codec::ir::config::SessionConfig; use crate::codec::ir::GeminiLiveCodec; use crate::config::StreamsCfg; use crate::driven::{Door, Steps}; @@ -50,6 +53,10 @@ const NS_PER_SEC: u64 = 1_000_000_000; /// reading on the tick clock, and a ceiling compared across two clocks is no ceiling. pub const CEILING_TICK_NS: u64 = NS_PER_SEC; +/// The tail's class index of the session's fee unit (`per_session`), after the six counted +/// classes ([`crate::session_unit::CLASSES`]). +pub const PER_SESSION_CLASS: u32 = crate::session_unit::CLASSES.len() as u32; + /// The most live sessions the door keeps at once; past it, a new stream is refused. pub const MAX_SESSIONS: usize = 4096; @@ -101,6 +108,8 @@ pub struct Live { ceiling_secs: Option, /// When its clock started, on the tick clock; `None` until the first tick after its open. started_ns: Option, + /// The far end has answered the session: its fee unit was incurred. + answered: bool, /// What the frame a caller-side answer is writing still owes the reply buffer. pub(crate) near: crate::piece::Owed, /// What the frame a far-side answer is writing still owes the reply buffer. @@ -118,29 +127,47 @@ impl Live { /// doors lock the section's session. #[must_use] pub fn open(door: Door, cfg: &StreamsCfg) -> Option { + Self::open_locked(door, cfg, None) + } + + /// The session params `door` locks under `cfg`, as the open's hooks are shown them: the + /// carrier's µ-law for the telephony door, the section's session for every other. + #[must_use] + pub fn locked(door: Door, cfg: &StreamsCfg) -> SessionConfig { + match door { + Door::Twilio => g711_config(), + _ => cfg.session.clone(), + } + } + + /// [`Self::open`], locked to `rewritten` where a session-open hook rewrote the params + /// ([`Self::locked`]) this session opens under. + #[must_use] + pub fn open_locked( + door: Door, + cfg: &StreamsCfg, + rewritten: Option, + ) -> Option { let ceiling = cfg.session_max_secs.map(|s| u64::from(s.get())); + let locked = rewritten.unwrap_or_else(|| Self::locked(door, cfg)); let unit = match door { Door::Sideband => Unit::Realtime(SessionUnit::open( OpenAiRealtimeCodec, - cfg.session.clone(), + locked, false, 0, None, )), Door::Twilio => Unit::Realtime(SessionUnit::open( OpenAiRealtimeCodec, - g711_config(), + locked, true, 0, None, )), - Door::Gemini => Unit::Gemini(SessionUnit::open( - GeminiLiveCodec, - cfg.session.clone(), - false, - 0, - None, - )), + Door::Gemini => { + Unit::Gemini(SessionUnit::open(GeminiLiveCodec, locked, false, 0, None)) + } Door::Mint | Door::Sdp | Door::Metadata => return None, }; Some(Live { @@ -149,6 +176,7 @@ impl Live { ending: false, ceiling_secs: ceiling, started_ns: None, + answered: false, near: crate::piece::Owed::default(), far: crate::piece::Owed::default(), short_near: None, @@ -182,8 +210,9 @@ impl Live { self.take(plan); } - /// A far-end piece, at `now_ms`. + /// A far-end piece, at `now_ms`. The first one incurs the session's fee unit. pub fn from_far_end(&mut self, bytes: &[u8], now_ms: u64) { + self.answered = true; let plan = each!(&mut self.unit, |u| u.from_far_end(bytes, now_ms)); self.take(plan); } @@ -234,18 +263,55 @@ impl Live { } /// The session's cumulative units, as the door reports them: (class index, amount), the zero - /// classes left out. + /// classes left out; the fee unit is `1` once the far end has answered. #[must_use] pub fn units(&self) -> Vec<(u32, u64)> { let units: CumulativeUnits = each!(&self.unit, |u| u.units()); - Door::Sideband.meter(&units) + let mut out = Door::Sideband.meter(&units); + if self.answered { + out.push((PER_SESSION_CLASS, 1)); + } + out } } +/// THE SESSION-OPEN PARAMS A COMMITTED HOOK REWRITE PRODUCED (the session-open hook projection the +/// retired streams crate applied, verbatim: ARCHITECT Q-L5B-PROJECT), or why they cannot be used. +/// The rewrite is a JSON object PATCHED over the `locked` params as the hook was shown them: a key +/// the hook named wins, a key it did not name keeps the locked value (naming a key with `null` +/// clears it). The merge comes before the decode, so a patch that names one bad field refuses the +/// open; so does output that is not JSON, or JSON that is not an object. A rewrite busbar cannot +/// read back is never opened as if it had not been made. +/// +/// # Errors +/// +/// The output is not JSON, not an object, or not a session config once merged. +pub fn committed_session_config( + locked: &SessionConfig, + rewrite: &[u8], +) -> Result { + let patch: serde_json::Value = + serde_json::from_slice(rewrite).map_err(|e| format!("the output is not JSON: {e}"))?; + let serde_json::Value::Object(patch) = patch else { + return Err("the output is JSON but not a session-params object".to_string()); + }; + let Ok(serde_json::Value::Object(mut merged)) = serde_json::to_value(locked) else { + return Err( + "the plane's own locked session params did not project to an object".to_string(), + ); + }; + merged.extend(patch); + serde_json::from_value::(serde_json::Value::Object(merged)) + .map_err(|e| format!("the output is not a session config: {e}")) +} + /// The door's live sessions, by the kernel's stream. #[derive(Debug, Default)] pub struct Sessions { live: BTreeMap, + /// The session-open params a hook rewrote, by the unit (= the stream) the session opens on, + /// until its first piece opens it. + rewritten: BTreeMap, /// The tickets each session's pieces crossed on (one per side), so a cancel on either ends it. tickets: HashMap, } @@ -258,11 +324,24 @@ impl Sessions { if self.live.len() >= MAX_SESSIONS { return None; } - self.live.insert(stream, Live::open(door, cfg)?); + let rewritten = self.rewritten.remove(&stream); + self.live + .insert(stream, Live::open_locked(door, cfg, rewritten)?); } self.live.get_mut(&stream) } + /// The session-open params a hook rewrote for the session unit `unit` opens (kept until its + /// first piece; the oldest dropped past [`MAX_SESSIONS`] kept). + pub fn rewrite(&mut self, unit: u64, params: SessionConfig) { + while self.rewritten.len() >= MAX_SESSIONS { + if self.rewritten.pop_first().is_none() { + break; + } + } + self.rewritten.insert(unit, params); + } + /// Stream `stream`'s session, when one is open. pub fn get(&mut self, stream: u64) -> Option<&mut Live> { self.live.get_mut(&stream) @@ -278,6 +357,7 @@ impl Sessions { /// Stream `stream`'s session is over: it and its tickets are forgotten. pub fn close(&mut self, stream: u64) { self.live.remove(&stream); + self.rewritten.remove(&stream); self.tickets.retain(|_, s| *s != stream); } diff --git a/crates/busbar-plane-streaming/src/tests/session_door_tests.rs b/crates/busbar-plane-streaming/src/tests/session_door_tests.rs index 5b9fb0c6d3..c220957b9e 100644 --- a/crates/busbar-plane-streaming/src/tests/session_door_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_door_tests.rs @@ -85,8 +85,15 @@ fn the_far_ends_usage_reaches_the_caller_and_the_sessions_units() { s.from_far_end(&usage_done(), 0); assert_eq!( s.units(), - vec![(0, 10), (1, 20), (2, 3), (3, 4), (seconds(), 2)], - "the closed turn's tokens and its admitted audio" + vec![ + (0, 10), + (1, 20), + (2, 3), + (3, 4), + (seconds(), 2), + (PER_SESSION_CLASS, 1) + ], + "the closed turn's tokens, its admitted audio, and the fee the answered session incurred" ); let emit = s.next(false); assert!( @@ -244,3 +251,93 @@ fn drive_names_the_sessions_that_owe_output() { assert_eq!(all.ready(1), vec![4]); assert_eq!(all.ready_count(), 2); } + +/// THE SESSION'S FEE UNIT (ARCHITECT Q-L5-FEE (A); Q17-6): `per_session` is reported `1` once the +/// far end first answers the session, never before. A session whose far end never answered (its +/// open failed) reports no fee unit however much its caller sent, so the kernel refunds the fee. +#[test] +fn the_fee_unit_is_incurred_when_the_far_end_first_answers() { + let mut s = sideband(); + s.from_caller(&uplink(1000), false); + let _ = s.next(true); + assert!( + !s.units().iter().any(|(c, _)| *c == PER_SESSION_CLASS), + "no far answer, no fee unit: {:?}", + s.units() + ); + s.from_caller(&[], true); + assert!( + !s.units().iter().any(|(c, _)| *c == PER_SESSION_CLASS), + "an open that never reached the far end ends with no fee unit" + ); + let mut s = sideband(); + s.from_far_end(&audio_delta("it1"), 0); + assert_eq!( + s.units() + .iter() + .filter(|(c, _)| *c == PER_SESSION_CLASS) + .collect::>(), + vec![&(PER_SESSION_CLASS, 1)], + "the far end's first answer incurs it once" + ); + s.from_far_end(&audio_delta("it2"), 0); + assert!(s.units().contains(&(PER_SESSION_CLASS, 1)), "still one"); +} + +/// THE SESSION-OPEN REWRITE (ARCHITECT Q-L5B-PROJECT; the retired streams crate's projection, +/// verbatim): a hook's rewrite is a PATCH over the locked params (a key it names wins, a key it does +/// not keep the plane's value, `null` clears); output that is not JSON, not an object, or not a +/// session config once merged is refused, never opened as if no rewrite had been made. +#[test] +fn a_session_open_rewrite_patches_the_locked_params_or_refuses() { + let locked = SessionConfig { + instructions: Some("be brief".to_string()), + voice: Some("alloy".to_string()), + ..SessionConfig::default() + }; + let patched = + committed_session_config(&locked, br#"{"voice":"marin"}"#).expect("a readable patch"); + assert_eq!( + patched.voice.as_deref(), + Some("marin"), + "the named key wins" + ); + assert_eq!( + patched.instructions.as_deref(), + Some("be brief"), + "an unnamed key keeps the locked value" + ); + let cleared = committed_session_config(&locked, br#"{"instructions":null}"#).expect("clear"); + assert_eq!(cleared.instructions, None, "naming null clears"); + for bad in [&b"not json"[..], b"7", br#"{"voice":7}"#] { + assert!( + committed_session_config(&locked, bad).is_err(), + "{}", + String::from_utf8_lossy(bad) + ); + } +} + +/// A session opened after its open's hooks rewrote the params opens under the rewritten params, +/// once; a stream with none opens under what its door locks. +#[test] +fn a_rewritten_session_open_locks_the_rewritten_params_once() { + let cfg = StreamsCfg::default(); + assert_eq!(Live::locked(Door::Sideband, &cfg), cfg.session); + assert_eq!( + Live::locked(Door::Twilio, &cfg), + crate::session_params::g711_config() + ); + let mut sessions = Sessions::default(); + let rewritten = SessionConfig { + voice: Some("marin".to_string()), + ..cfg.session.clone() + }; + sessions.rewrite(7, rewritten); + assert!(sessions.get_or_open(7, Door::Sideband, &cfg).is_some()); + sessions.close(7); + assert!( + sessions.rewritten.is_empty(), + "consumed at the open and gone with the session" + ); +} diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index cc97437919..c823e5684d 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -343,6 +343,7 @@ fn the_tail_states_what_the_served_plane_declares() { "text_tokens_out/token", "audio_seconds_in/duration", "tool_calls/count", + "per_session/count", ] ); // SAFETY: as above. @@ -353,6 +354,17 @@ fn the_tail_states_what_the_served_plane_declares() { assert_eq!(fees, ["per_session"]); busbar_contract::abi::plane::check::check_tail(t) .expect("the kind's own check admits the tail"); + // The fee unit is one of the billable classes (ARCHITECT Q-L5-FEE (A)+(C)): the plane can + // report it, and the loader's bind-time check admits it. + // SAFETY: as above. + let (fee_units, billable) = unsafe { + ( + std::slice::from_raw_parts(t.fee_units, t.fee_units_len), + std::slice::from_raw_parts(t.billable_classes, t.billable_classes_len), + ) + }; + busbar_contract::abi::plane::check::check_fee_units(fee_units, billable) + .expect("every fee unit is a billable class"); } // ── THE ONE-REQUEST DOORS' PIECES ─────────────────────────────────────────────────────────────── @@ -395,6 +407,8 @@ struct Bufs { arena: [u8; 256], /// The units the last answer wrote (class, source, amount), or, short, the room it needed. last_units: String, + /// The need the last answer named for its far request (`0` = none). + last_need: u32, } /// One piece pushed to a unit: whose it is, its flags, bytes, status and kept head fields. @@ -423,6 +437,7 @@ impl Bufs { fields: [z(); 4], arena: [0; 256], last_units: String::new(), + last_need: 0, } } @@ -446,6 +461,7 @@ impl Bufs { let mut f = Frame::new(i, o); let c = p.call(slot::ON_PIECE, &mut f); let o = f.out; + self.last_need = o.need; self.last_units = if o.units_needed != 0 { format!("needed {}", o.units_needed) } else { @@ -720,23 +736,25 @@ fn a_live_session_is_answered_through_the_door() { let far = line("far usage"); assert!( far.contains("far=false done=false") - && far.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=2]"), - "the closed turn's tokens and its two admitted seconds, reported: {far}" + && far.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=2 6:1=1]"), + "the closed turn's tokens, its two admitted seconds and the answered session's fee \ + unit, reported: {far}" ); assert!(line("collect ").contains("emitted= more=0 far=false done=false")); assert!( - line("short ").starts_with("short Failed") && line("short ").ends_with("units=[needed 5]"), + line("short ").starts_with("short Failed") && line("short ").ends_with("units=[needed 6]"), "{}", line("short ") ); let recall = line("short re-call"); assert!( - recall.contains("far=true") && recall.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=2]"), + recall.contains("far=true") + && recall.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=2 6:1=1]"), "the re-call answers the same piece once, its audio not counted twice: {recall}" ); let end = line("caller end"); assert!( - end.contains("done=true") && end.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=4]"), + end.contains("done=true") && end.ends_with("units=[0:1=10 1:1=20 2:1=3 3:1=4 4:1=4 6:1=1]"), "the caller's end settles the open turn's two seconds once: {end}" ); assert!(line("request unit on a session door") @@ -756,3 +774,152 @@ fn the_dropped_door_answers_a_live_session_as_the_linked_door_does() { "the two doors must answer a session identically" ); } + +// ── THE ROUTE A DOOR NAMES (ARCHITECT Q-L5B-ROUTE) ─────────────────────────────────────────────── + +/// `arrive` on `claim`: its outcome, the route class and the entry it names. +fn arrive_route(p: &Plugin, claim: u32) -> (Outcome, u8, String) { + let mut a: Frame = Frame::new(z(), z()); + a.input.head = in_head(); + a.input.claim = claim; + a.out.head = out_head(); + let c = p.call(slot::ARRIVE, &mut a); + (c.outcome, a.out.route, read(a.out.pool)) +} + +/// Every door that reaches the far end names the DIRECT route `streams.session.model` (the entry the +/// composition folds from the top-level models catalog); the metadata door reaches none and names +/// none; with no model configured nothing is named (the door steps refuse an unnamed keyed route). +/// Both ways. +#[test] +fn a_door_that_reaches_the_far_end_names_the_session_model_as_its_direct_route() { + use busbar_contract::abi::plane::{ROUTE_DIRECT, ROUTE_POOL}; + let d = Dispatcher::new(DispatchConfig::default()); + let mut doors = vec![linked(&d)]; + doors.extend(dropped(&d)); + for p in &doors { + assert_eq!(open(p, 1, SESSION, Some(PUBLIC)).0, Outcome::Ready); + for claim in [0, 1, 2, 3, 4] { + assert_eq!( + arrive_route(p, claim), + (Outcome::Ready, ROUTE_DIRECT, "m-cap".to_string()), + "claim {claim}" + ); + } + assert_eq!( + arrive_route(p, 5), + (Outcome::Ready, ROUTE_POOL, String::new()), + "the metadata document reaches no far end" + ); + } + let bare = linked(&d); + assert_eq!(open(&bare, 1, b"{}", Some(PUBLIC)).0, Outcome::Ready); + assert_eq!( + arrive_route(&bare, 2), + (Outcome::Ready, ROUTE_POOL, String::new()), + "no model, no route named" + ); +} + +// ── THE SESSION OPEN'S HOOK VIEW (ARCHITECT Q-L5B-PROJECT; K5's `project`) ─────────────────────── + +/// `project` of unit 31 on `claim` with `rewrite` (empty = none): its outcome, the projected body +/// and whether it was answered as rewritten. +fn project(p: &Plugin, claim: u32, rewrite: &'static [u8]) -> (Outcome, String, bool) { + use busbar_contract::abi::hook::{MessageView, SignalEntry}; + use busbar_contract::abi::plane::{ProjectIn, ProjectOut, SPAN_ABSENT}; + let mut arena = vec![0u8; 4096]; + let mut signals: Vec = vec![z(); 4]; + let mut turns: Vec = vec![z(); 4]; + let mut j: Frame = Frame::new(z(), z()); + (j.input.head, j.out.head) = (in_head(), out_head()); + j.input.claim = claim; + j.input.unit = 31; + j.input.arena_buf = arena.as_mut_ptr(); + j.input.arena_cap = arena.len(); + j.input.signals_buf = signals.as_mut_ptr(); + j.input.signals_cap = signals.len(); + j.input.messages_buf = turns.as_mut_ptr(); + j.input.messages_cap = turns.len(); + if !rewrite.is_empty() { + j.input.rewrite = json(rewrite); + } + let c = p.call(slot::PROJECT, &mut j); + let body = if j.out.body.offset == SPAN_ABSENT { + String::new() + } else { + at(&arena, j.out.body) + }; + (c.outcome, body, j.out.rewritten.offset != SPAN_ABSENT) +} + +/// A session door projects the session-open params it locks as the view's body; a hook's rewrite is +/// patched over them and THAT is answered (kept for the session the unit opens); a rewrite that does +/// not read back refuses; a one-request door projects no body and takes no rewrite. Both ways. +#[test] +fn a_session_open_is_projected_for_the_hooks_and_a_rewrite_patches_it() { + let d = Dispatcher::new(DispatchConfig::default()); + let mut doors = vec![linked(&d)]; + doors.extend(dropped(&d)); + for p in &doors { + assert_eq!(open(p, 1, SESSION, Some(PUBLIC)).0, Outcome::Ready); + let (outcome, body, rewritten) = project(p, 2, b""); + assert_eq!(outcome, Outcome::Ready); + let view: serde_json::Value = serde_json::from_str(&body).expect("the locked params"); + assert_eq!(view["model"], "m-cap", "{body}"); + assert!(!rewritten); + let (outcome, body, rewritten) = project(p, 2, br#"{"voice":"marin"}"#); + assert_eq!(outcome, Outcome::Ready); + let view: serde_json::Value = serde_json::from_str(&body).expect("the patched params"); + assert_eq!(view["voice"], "marin", "the hook's key wins: {body}"); + assert_eq!( + view["model"], "m-cap", + "the unnamed key keeps its value: {body}" + ); + assert!(rewritten, "answered as rewritten"); + assert_eq!(project(p, 2, br#"{"voice":7}"#).0, Outcome::Refused); + assert_eq!(project(p, 0, b""), (Outcome::Ready, String::new(), false)); + assert_eq!(project(p, 0, br#"{"x":1}"#).0, Outcome::Refused); + } +} + +// ── THE NEED A FAR REQUEST RIDES (ARCHITECT Q-L5B-NEEDS) ───────────────────────────────────────── + +/// Every far request names the outbound need it rides: a live session's frames OpenAI Realtime's +/// socket, the mint's request Realtime's one-shot pass (each a declared outbound need under the +/// dialect's style, held by the door's own compile-time check). Both ways. +#[test] +fn every_far_request_names_the_need_it_rides() { + let d = Dispatcher::new(DispatchConfig::default()); + let mut doors = vec![linked(&d)]; + doors.extend(dropped(&d)); + for p in &doors { + assert_eq!(open(p, 1, SESSION, Some(PUBLIC)).0, Outcome::Ready); + let mut b = Bufs::with_units(1 << 18, 8); + let answer = b.call(p, &session(40, FROM_CALLER, 0, &uplink(100))); + assert!(answer.contains("far=true"), "{answer}"); + assert_eq!(b.last_need, door::RIDES_REALTIME_SOCKET, "{answer}"); + let mut b = Bufs::new(1 << 16); + let answer = b.call(p, &attempt(41, 0)); + assert!(answer.contains("far=true"), "{answer}"); + assert_eq!(b.last_need, door::RIDES_REALTIME_PASS, "{answer}"); + } +} + +/// A refused open (a hook's veto, an admission's refusal) is answered in the dialects' error shape: +/// the kernel's status read as the error's type, its words as the message. +#[test] +fn a_refusal_is_rendered_in_the_dialects_error_shape() { + for (status, kind) in [ + (401, "authentication_error"), + (403, "permission_error"), + (429, "rate_limit_error"), + (400, "invalid_request_error"), + (503, "server_error"), + ] { + let body: serde_json::Value = + serde_json::from_slice(&door::refusal_body(status, "no sessions today")).expect("JSON"); + assert_eq!(body["error"]["type"], kind, "{status}"); + assert_eq!(body["error"]["message"], "no sessions today"); + } +} From 5e56468bbac8e0d5ae8b42762558200b0ca11d62 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:37:00 -0700 Subject: [PATCH 22/82] packaging: the ws framer's door is opened on the one Connector (transport-ws on the connector-door axis; ARCHITECT Q-L5B-WS-DOOR, ruled 2026-10-04 for this lane: the same single-entry door-only shape as stdio's ruling (A) and the grpc exemplar), so the streaming door's ws needs bind compiled-in (lane-dg-streaming fb1c81da57) --- crates/busbar/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index 1a6b7c0d5c..fea27297a5 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -433,7 +433,9 @@ transport-tcp = "transport transport-door" # sink's outbound `http` need is served compiled-in; the wire the registry folds keeps its build. transport-http = "transport connector-door" transport-sse = "transport" -transport-ws = "transport" +# The ws framer's door, opened on the same one Connector (ARCHITECT Q-L5B-WS-DOOR 2026-10-03, +# packaging Part 0, as the http door): the streaming door's inbound and outbound ws needs bind. +transport-ws = "transport connector-door" transport-grpc = "transport" transport-stdio = "transport" plane-decisions-door = "plane-door" From dc224140d948f02d01ce448b9a71ee512e892f6b Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:16:38 -0700 Subject: [PATCH 23/82] busbar-plane-streaming door on SEAM-ABI: DialectAuth.params absent (the style's default), and the tail states no caller-credential refusal and no admin routes --- crates/busbar-plane-streaming/src/door.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 1065393dac..40cc39ec78 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -168,6 +168,7 @@ const fn dialect_auth(dialect: u32, style: &'static str) -> DialectAuth { dialect, _reserved: 0, style: abi_str(style), + params: Blob::ABSENT, } } @@ -321,6 +322,10 @@ pub const TAIL: &PlaneTail = &PlaneTail { trust_keys_len: 0, refusal_statuses: ptr::null(), refusal_statuses_len: 0, + caller_credential_refusal: NONE, + admin_routes: ptr::null(), + admin_routes_len: 0, + admin_openapi: Blob::ABSENT, }; /// The inbound auth style of the metadata door: none, it is read without a credential. From 02c5666de8e17a5dea855b3b9ca7c1ec8a11b4fa Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:23:24 -0700 Subject: [PATCH 24/82] busbar-transport-ws pinned at 5f0b51273a (GetBusbar/busbar-transport-ws#11): the dial's opening fields, held early messages, EMIT_TEXT writes, and the RFC 6455 failure order (lane-ws-read-end's framer half) --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05c7df5c15..c2108e522a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,7 +1118,7 @@ dependencies = [ [[package]] name = "busbar-transport-ws" version = "1.6.0" -source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=1c6a67f010728c96fe9041e41817fb69c8e4fd05#1c6a67f010728c96fe9041e41817fb69c8e4fd05" +source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=5f0b51273a9bb584042955b7044e71e162aeca47#5f0b51273a9bb584042955b7044e71e162aeca47" dependencies = [ "busbar-contract", "futures", diff --git a/Cargo.toml b/Cargo.toml index 4a192cd691..8154229a91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ busbar-transport-http = { git = "https://github.com/GetBusbar/busbar-transport-h busbar-transport-grpc = { git = "https://github.com/GetBusbar/busbar-transport-grpc", rev = "5607182ed5d28da20a6e1699aad512909d24142e" } # THE WS TRANSPORT: the websocket framer door (kind: transport), pulled at a pinned rev; the logic # crate the root links (behind plane-streaming) and the ws-conformance subject drives. -busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "1c6a67f010728c96fe9041e41817fb69c8e4fd05" } +busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "5f0b51273a9bb584042955b7044e71e162aeca47" } busbar-export-webhook-plugin = { git = "https://github.com/GetBusbar/busbar-export-webhook", rev = "3ad5a711aad433f2015a980cf3a792afa5fd3a7a" } # THE TCP TRANSPORT: the identity framer door (kind: transport), one pin for the logic crate the root # links and the cdylib crate the loader's both-ways proof builds. Same repo, same rev, one resolved copy. From 5258535731cefda6cd3a6d9a0e6b47a317e0c4ac Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:32:49 -0700 Subject: [PATCH 25/82] busbar-transport-ws pinned at 9050227f89 (GetBusbar/busbar-transport-ws#11 head; tree equal to 5f0b51273a): the ws row keeps its in-process build, since the root's transport-door build serves only a framer over the host's socket --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2108e522a..e0b077ea7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,7 +1118,7 @@ dependencies = [ [[package]] name = "busbar-transport-ws" version = "1.6.0" -source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=5f0b51273a9bb584042955b7044e71e162aeca47#5f0b51273a9bb584042955b7044e71e162aeca47" +source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=9050227f89e15c12633a9fe27a5f5ea4ab9502f0#9050227f89e15c12633a9fe27a5f5ea4ab9502f0" dependencies = [ "busbar-contract", "futures", diff --git a/Cargo.toml b/Cargo.toml index 8154229a91..7193b6145b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ busbar-transport-http = { git = "https://github.com/GetBusbar/busbar-transport-h busbar-transport-grpc = { git = "https://github.com/GetBusbar/busbar-transport-grpc", rev = "5607182ed5d28da20a6e1699aad512909d24142e" } # THE WS TRANSPORT: the websocket framer door (kind: transport), pulled at a pinned rev; the logic # crate the root links (behind plane-streaming) and the ws-conformance subject drives. -busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "5f0b51273a9bb584042955b7044e71e162aeca47" } +busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "9050227f89e15c12633a9fe27a5f5ea4ab9502f0" } busbar-export-webhook-plugin = { git = "https://github.com/GetBusbar/busbar-export-webhook", rev = "3ad5a711aad433f2015a980cf3a792afa5fd3a7a" } # THE TCP TRANSPORT: the identity framer door (kind: transport), one pin for the logic crate the root # links and the cdylib crate the loader's both-ways proof builds. Same repo, same rev, one resolved copy. From 38729e432493149fff2020caceb80623664c12fb Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:41:15 -0700 Subject: [PATCH 26/82] ROUTE (ARCHITECT Q-L5B-ROUTE 2026-10-03; lane-dg-streaming 5b8f14583d, root half): the composition folds a section's session.model from the top-level models catalog into the plane's route table, and hands each door plane the deployment's public URL compose_served takes Deployment { public_url, catalog }; compose_planes keeps its signature (compose_planes_in reads the deployment); open() hands PlaneOpenIn.public_url; the folded route table is what DoorPools and member_routes read. The 1.5.5 shape of both sections is unchanged: the settings the door opens with are never folded. Test: serve_planes a_session_model_from_the_top_level_catalog_is_folded_into_the_route_table. --- crates/busbar-voice/src/mount.rs | 2 +- crates/busbar/src/main.rs | 4 + crates/busbar/src/root/serve.rs | 129 +++++++++++++++++-- crates/busbar/src/root/tests/serve_planes.rs | 42 ++++++ 4 files changed, 167 insertions(+), 10 deletions(-) diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs index f5bd7567e4..4885e631e8 100644 --- a/crates/busbar-voice/src/mount.rs +++ b/crates/busbar-voice/src/mount.rs @@ -46,10 +46,10 @@ use crate::topology::{ }; use busbar_kernel::config::RootCfg; use busbar_kernel::egress::engine::{send_bounded, EngineClient}; +use busbar_kernel::ingress::byte_duplex::serve_messages; use busbar_kernel::ingress::duplex_ws::{ accept_gauntlet, install_ws_arrivals, WsAcceptFuture, WsArrival, WsArrivalSpec, }; -use busbar_kernel::ingress::byte_duplex::serve_messages; use busbar_kernel::plane::handle_engine::DurableHandleEngine; use busbar_kernel::plane::observe::Counted; use busbar_kernel::plane::registry::{BuildCtx, PlaneBootCtx}; diff --git a/crates/busbar/src/main.rs b/crates/busbar/src/main.rs index ef4be7e144..c916071aa6 100644 --- a/crates/busbar/src/main.rs +++ b/crates/busbar/src/main.rs @@ -863,6 +863,10 @@ async fn run(data_workers: usize, late_services: std::sync::Arc, reach: &crate::root::door_steps::DoorReach<'_>, + deployment: Deployment<'_>, ) -> Result { let Some(gov) = gov else { if !doors.is_empty() { @@ -460,13 +461,21 @@ pub fn compose_served( reach, journal: Arc::clone(&post) as Arc, }; - let mut served = compose_planes(doors, dispatcher, late, sections, &money, Some(&egress))?; + let mut served = compose_planes_in( + doors, + dispatcher, + late, + sections, + deployment, + &money, + Some(&egress), + )?; served.post = Some(post); Ok(served) } #[cfg(not(linked_axis_node))] { - let _ = (gov, dispatcher, late, reach); + let _ = (gov, dispatcher, late, reach, deployment); match doors .iter() .find(|(_, plugin)| sections.contains_key(plugin.served().section)) @@ -511,6 +520,90 @@ pub fn compose_planes( sections: &BTreeMap<&'static str, serde_yaml::Value>, money: &dyn Fn() -> Arc, egress: Option<&DoorEgress<'_>>, +) -> Result { + compose_planes_in( + doors, + dispatcher, + late, + sections, + Deployment::default(), + money, + egress, + ) +} + +/// WHAT A DOOR PLANE'S COMPOSITION READS OF THE DEPLOYMENT beside its own section (ARCHITECT +/// Q-L5B-ROUTE, 2026-10-03). +#[derive(Debug, Clone, Copy, Default)] +pub struct Deployment<'d> { + /// The deployment's public base URL (top-level `public_url:`), handed to each plane's `open` + /// (`PlaneOpenIn.public_url`): a plane whose claims and audience are read from it publishes + /// them only under one. + pub public_url: Option<&'d str>, + /// The top-level `models:` catalog, from which a section's `session.model` reference is folded + /// into its route table ([`catalog_routes`]). + pub catalog: Option<&'d std::collections::HashMap>, +} + +/// The `session:` block of a section, and the `model:` key inside it that names a top-level catalog +/// model. +const SESSION_KEY: &str = "session"; +const MODEL_KEY: &str = "model"; + +/// THE ROUTE TABLE A SECTION'S DOOR STEPS RESOLVE AGAINST (ARCHITECT Q-L5B-ROUTE, 2026-10-03): the +/// section as written, plus, where it states no `models:` of its own and its `session.model` names an +/// entry of the top-level models `catalog`, that one entry folded in as its `models:` map (its +/// provider, upstream model and attempt cap), so the plane's door names it as a DIRECT route and the +/// kernel's door steps resolve it as any entry of the plane's own section. The 1.5.5 shape of both +/// sections is unchanged: only the route table is folded, never the settings the door opens with. +#[must_use] +pub fn catalog_routes( + section: &serde_yaml::Value, + catalog: &std::collections::HashMap, +) -> serde_yaml::Value { + use serde_yaml::{Mapping, Value}; + let mut routes = section.clone(); + let named = section + .get(SESSION_KEY) + .and_then(|s| s.get(MODEL_KEY)) + .and_then(Value::as_str); + let (Some(map), Some(model)) = (routes.as_mapping_mut(), named) else { + return routes; + }; + let models_key = Value::from(busbar_contract::section::RESERVED_MODELS_KEY); + let Some(entry) = catalog + .get(model) + .filter(|_| !map.contains_key(&models_key)) + else { + return routes; + }; + let mut folded = Mapping::new(); + folded.insert("provider".into(), entry.provider.clone().into()); + if let Some(upstream) = &entry.upstream_model { + folded.insert("upstream_model".into(), upstream.clone().into()); + } + if let Some(ms) = entry.attempt_timeout_ms { + folded.insert("attempt_timeout_ms".into(), ms.into()); + } + let mut models = Mapping::new(); + models.insert(model.into(), Value::Mapping(folded)); + map.insert(models_key, Value::Mapping(models)); + routes +} + +/// [`compose_planes`], reading the deployment's public URL and model catalog. +/// +/// # Errors +/// +/// As [`compose_planes`]. +pub fn compose_planes_in( + doors: &[(String, DoorPlane)], + dispatcher: &Arc, + late: &LateServices, + sections: &BTreeMap<&'static str, serde_yaml::Value>, + deployment: Deployment<'_>, + money: &dyn Fn() -> Arc, + egress: Option<&DoorEgress<'_>>, ) -> Result { let mut served = Served::default(); if doors.is_empty() { @@ -529,7 +622,8 @@ pub fn compose_planes( ); continue; }; - let snapshot = open(plugin, section).map_err(|e| format!("{instance}: {e}"))?; + let snapshot = + open(plugin, section, deployment.public_url).map_err(|e| format!("{instance}: {e}"))?; let calls = Arc::new(PlaneInstance::new( plugin.clone(), Arc::clone(dispatcher), @@ -616,14 +710,19 @@ pub fn compose_planes( }) .collect(), ); - let pools = DoorPools::of(section); + // The route table: the section, with the catalog model its `session.model` names folded in. + let routes_of = deployment.catalog.map_or_else( + || section.clone(), + |catalog| catalog_routes(section, catalog), + ); + let pools = DoorPools::of(&routes_of); // THE EGRESS, SEALED (THE DESIGN §6 steps 2-3): each member's route resolved and its // credential bound by the auth plugin serving its style, over the connector its needs were // declared on. let egress = match egress { Some(egress) => { let routes = crate::root::door_steps::member_routes( - section, + &routes_of, &pools, &served_facts, egress.reach, @@ -660,7 +759,12 @@ pub fn compose_planes( } /// `open` the plane, generation 1, its settings `section` as JSON; the snapshot it published. -fn open(plugin: &DoorPlane, section: &serde_yaml::Value) -> Result { +fn open( + plugin: &DoorPlane, + section: &serde_yaml::Value, + public_url: Option<&str>, +) -> Result { + let public_url = public_url.unwrap_or_default(); // The reserved `work:` bounds are core-owned: the kernel reads them; the plane never sees them. let mut section = section.clone(); if let Some(map) = section.as_mapping_mut() { @@ -684,9 +788,16 @@ fn open(plugin: &DoorPlane, section: &serde_yaml::Value) -> Result = + serde_yaml::from_str( + "gpt-rt: { provider: openai, upstream_model: gpt-realtime, attempt_timeout_ms: 900 }\n", + ) + .expect("the catalog"); + let section: serde_yaml::Value = + serde_yaml::from_str("session: { model: gpt-rt }\nsession_max_secs: 60\n").expect("yaml"); + let routes = super::catalog_routes(§ion, &catalog); + assert_eq!( + routes["models"]["gpt-rt"], + serde_yaml::from_str::( + "{ provider: openai, upstream_model: gpt-realtime, attempt_timeout_ms: 900 }" + ) + .expect("yaml"), + "the catalog entry's routing facts, folded" + ); + assert_eq!( + routes["session"], section["session"], + "the section stays as written" + ); + let pools = DoorPools::of(&routes); + assert_eq!( + pools.resolve(ROUTE_DIRECT, Some(b"gpt-rt")), + Some((String::new(), vec!["gpt-rt".to_string()])), + "the door's DIRECT route resolves to the folded entry" + ); + let unknown: serde_yaml::Value = + serde_yaml::from_str("session: { model: nope }\n").expect("yaml"); + assert_eq!(super::catalog_routes(&unknown, &catalog), unknown); + let own: serde_yaml::Value = + serde_yaml::from_str("session: { model: gpt-rt }\nmodels: { mine: {} }\n").expect("yaml"); + assert_eq!(super::catalog_routes(&own, &catalog), own); +} From 4a1e4d38bd5859e0482749d338e1fe558178c8c3 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 16:01:08 -0700 Subject: [PATCH 27/82] busbar-plane-streaming door: providers is a consumed section, not a required one it owns (core keeps its grammar, as the llm door consumes it); the DECL-FOLD registry refused the flipped boot ("plane `streaming` claims config section `providers`, but core still owns it") --- crates/busbar-plane-streaming/src/door.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 40cc39ec78..5e8145e40b 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -56,7 +56,7 @@ use busbar_contract::abi::mechanism::call::{ AbiStr, Blob, InHead, OutHead, Outcome, Span, BLOB_ABSENT, }; use busbar_contract::abi::mechanism::door::{ - KindTailHead, Section, Statement, SECTION_DECLARING, SECTION_REQUIRED, + KindTailHead, Section, Statement, SECTION_CONSUMED, SECTION_DECLARING, }; use busbar_contract::abi::mechanism::lifecycle::{ CancelIn, CancelOut, GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, @@ -100,7 +100,8 @@ pub const AUDIT_KIND: &str = "streaming_session"; /// The one value a key's `session` grant names to open a live session here: the pool every session /// is served on. pub const SESSION_POOL: &str = "streaming-server"; -/// The section a document must carry when the plane is linked: the upstream destinations. +/// The section the plane reads but does not own: the upstream destinations (core's grammar, as the +/// llm door consumes it). pub const PROVIDERS_SECTION: &str = "providers"; /// Where, inside `streams:`, the upstream a session dials is named. pub const EGRESS_TARGET: &str = "session.model"; @@ -128,7 +129,7 @@ const SECTIONS: &[Section] = &[ }, Section { name: abi_str(PROVIDERS_SECTION), - flags: SECTION_REQUIRED, + flags: SECTION_CONSUMED, _reserved: 0, }, ]; From 11e2e9072222f7cf6cf5fcf06018da2a837feca7 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 16:26:23 -0700 Subject: [PATCH 28/82] busbar-plane-streaming door: outbound needs in the provider egress class with no target_from (lane-dg-streaming b53e16e821, plane half): a model name is no dial target, and "session.model" as target_from left the need undeclared on the connector (UndeclaredNeed on the flipped mint and SDP dials) --- crates/busbar-plane-streaming/src/door.rs | 33 ++++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 5e8145e40b..139f28a234 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -50,7 +50,7 @@ use std::sync::Mutex; use crate::piece::{self, Owed}; use busbar_contract::abi::hook::REQUEST_STREAM; use busbar_contract::abi::host::conn::connector::{ - Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND, KEEP_NAMED, + Need, DIRECTION_INBOUND, DIRECTION_OUTBOUND, EGRESS_PROVIDER, KEEP_NAMED, }; use busbar_contract::abi::mechanism::call::{ AbiStr, Blob, InHead, OutHead, Outcome, Span, BLOB_ABSENT, @@ -239,6 +239,16 @@ const fn need(direction: u32, transport: &'static str, auth: AbiStr, target: Abi } } +/// One outbound need: to a configured provider, its dials judged in the provider egress class, its +/// address the member's own (the `base_url` of the catalog model `streams.session.model` names, sealed +/// by the kernel's egress), never a config path of the plane's: a model name is no dial target. +const fn outbound(transport: &'static str, style: &'static str) -> Need { + Need { + egress_class: EGRESS_PROVIDER, + ..need(DIRECTION_OUTBOUND, transport, abi_str(style), NONE) + } +} + /// The plane's connection needs: its keyed doors and its telephony door inbound; outbound, to the /// upstream `streams.session.model` names, OpenAI Realtime's socket and its one-shot passes (the /// mint, the SDP offer) under its bearer style, and Gemini Live's socket under its key header @@ -253,24 +263,9 @@ const NEEDS: &[Need] = &[ abi_str(SIGNATURE_AUTH), NONE, ), - need( - DIRECTION_OUTBOUND, - WS_TRANSPORT, - abi_str(REALTIME_STYLE), - abi_str(EGRESS_TARGET), - ), - need( - DIRECTION_OUTBOUND, - HTTP_TRANSPORT, - abi_str(REALTIME_STYLE), - abi_str(EGRESS_TARGET), - ), - need( - DIRECTION_OUTBOUND, - WS_TRANSPORT, - abi_str(LIVE_STYLE), - abi_str(EGRESS_TARGET), - ), + outbound(WS_TRANSPORT, REALTIME_STYLE), + outbound(HTTP_TRANSPORT, REALTIME_STYLE), + outbound(WS_TRANSPORT, LIVE_STYLE), ]; /// The need a far request rides, as `OnPieceOut::need` names it (its index in [`NEEDS`] plus one): From 80e44f3dbdc4da788351d3a3d6a3f3a88a1ceda5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:30:35 -0700 Subject: [PATCH 29/82] the streaming fold's switch swaps the legacy row out (linked-swaps): while streaming-on-driver is on, the plane-streaming row (busbar-voice) is not linked, so the folded door is the plane's one claimant (ARCHITECT 2026-10-04) The switch build refused its boot: "the data listener's claims Plane("streaming") /v1/realtime/client_secrets and Clean("core") /v1/realtime/client_secrets meet at an equal precedence" (the voice row's routes and the folded door's claims). build.rs reads [package.metadata.busbar.linked-swaps] (row feature = switch) through linked_gen::swapped_in, and every linked table it generates skips a swapped row. --- crates/busbar/Cargo.toml | 6 ++++++ crates/busbar/build.rs | 6 +++++- crates/busbar/src/linked_gen.rs | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index c25c54b12f..e0ed648fc9 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -479,6 +479,12 @@ auth-header = "busbar-auth-header" # `testkit::SERVED` table they drive through the kernel-loop runner `gauntlet_install::install()` # registers for that plane's key. Test builds only (`build.rs` emits `$OUT_DIR/served_witness.rs`, which # `root/tests/gauntlet_kernel.rs` includes); each crate is linked with `test-support` by the dev-deps. +# A fold's development-only switch SWAPS its plane's legacy row out (`linked-swaps`, read by build.rs): +# while `streaming-on-driver` is on, the `plane-streaming` row (busbar-voice) is not linked, so the +# streaming door, folded and composed on the serve path, is the plane's one claimant. +[package.metadata.busbar.linked-swaps] +plane-streaming = "streaming-on-driver" + [package.metadata.busbar.served-witness] plane-mcp = "busbar-mcp" plane-a2a = "busbar-a2a" diff --git a/crates/busbar/build.rs b/crates/busbar/build.rs index 41ce4aa331..3555e0fabc 100644 --- a/crates/busbar/build.rs +++ b/crates/busbar/build.rs @@ -101,10 +101,14 @@ fn main() { println!("cargo:rerun-if-changed=Cargo.toml"); println!("cargo:rerun-if-changed=src/linked_gen.rs"); let manifest = std::fs::read_to_string("Cargo.toml").expect("read Cargo.toml"); - let enabled = |feature: &str| { + let on = |feature: &str| { let var = format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_")); env::var_os(var).is_some() }; + // A fold's development-only switch SWAPS its plane's legacy row out (`linked-swaps`): the row is + // not linked while the switch is on, so the folded door is the plane's one claimant. + let swaps = metadata_map(&manifest, LINKED_SWAPS); + let enabled = |feature: &str| swapped_in(&swaps, &on, feature); let (source, cfgs) = linked_source(&manifest, &enabled); let out = std::path::PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("linked.rs"); std::fs::write(out, source).expect("write linked.rs"); diff --git a/crates/busbar/src/linked_gen.rs b/crates/busbar/src/linked_gen.rs index 240785d6fe..28dd50ca81 100644 --- a/crates/busbar/src/linked_gen.rs +++ b/crates/busbar/src/linked_gen.rs @@ -131,6 +131,17 @@ pub(crate) const SEAMS: &[(&str, &str)] = &[ ("admin-envelope", "linked_admin_envelope"), ]; +/// THE SWAPS TABLE (BUSBAR-1.6.0.md Part 3 section 12, "The switch"): ` = ""`, +/// a linked row its fold's development-only switch swaps out. While the switch is on, the row is +/// not linked (no claims, no routes, no section of its own), so the plane's folded door is its one +/// claimant: the switch swaps the row, it never adds a second one. +pub(crate) const LINKED_SWAPS: &str = "package.metadata.busbar.linked-swaps"; + +/// Whether `feature`'s row is linked: its feature is `on`, and no switch that swaps it is. +pub(crate) fn swapped_in(swaps: &[(String, String)], on: &dyn Fn(&str) -> bool, feature: &str) -> bool { + on(feature) && !swaps.iter().any(|(row, switch)| row == feature && on(switch)) +} + /// The `key = "value"` rows of the `[table]` header of a Cargo manifest, in file order. Deliberately /// not a TOML parser: one row per line, both sides optionally double-quoted. An absent or empty /// table is a build failure, never an empty list — an empty list would silently register nothing. From c10f563d462d370aa80e623e729774b4f8ad9820 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:30:35 -0700 Subject: [PATCH 30/82] busbar-plane-streaming door: the protected-resource metadata path is not claimed (1.5.5 parity, ARCHITECT 2026-10-04: no accepted difference) The door binds the metadata URL its refusals point at, and claims no route at it, so the data listener's fallback answers streams|metadata|protected-resource as golden/1.6.0-pre recorded it. The metadata door (Door::Metadata, metadata_reply, its claim) leaves; RequestUnit no longer carries the audience it answered the document with; the claim tables, the arrive table and the unit script pin claim 5 as unpublished. --- crates/busbar-plane-streaming/src/door.rs | 43 +++++--------- crates/busbar-plane-streaming/src/driven.rs | 49 +++------------ .../src/request_unit.rs | 20 ++----- .../src/session_door.rs | 2 +- crates/busbar-plane-streaming/src/sessions.rs | 2 +- .../src/tests/driven_tests.rs | 22 ++----- .../src/tests/ladder_tests.rs | 9 +-- .../src/tests/request_unit_tests.rs | 14 +---- .../src/tests/session_door_tests.rs | 2 +- .../src/tests/sessions_tests.rs | 2 +- .../tests/conformance.rs | 59 +++++-------------- 11 files changed, 55 insertions(+), 169 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 139f28a234..56527cc6bc 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -32,9 +32,9 @@ //! //! ## What each unit's pieces are answered //! -//! A one-request door (the mint, the SDP offer, the metadata document) answers its pieces through -//! a [`RequestUnit`] the instance keeps by the kernel's unit key, built at the unit's first piece -//! over the newest live generation's session params and audience. Its answer's bytes are paid into +//! A one-request door (the mint, the SDP offer) answers its pieces through a [`RequestUnit`] the +//! instance keeps by the kernel's unit key, built at the unit's first piece over the newest live +//! generation's session params. Its answer's bytes are paid into //! the reply buffer across `more = 1` re-calls ([`crate::piece`]). //! //! A piece that names a stream is a live session's, served by the door's [`Sessions`] @@ -112,7 +112,7 @@ pub const SIGNATURE_AUTH: &str = "webhook-signature"; /// The mount every door of the plane sits under. pub const MOUNT_PATH: &str = "/v1/realtime"; -/// The metadata document a refused caller is pointed at. +/// The metadata URL a refused caller is pointed at (bound, never claimed: 1.5.5 parity). pub const METADATA_PATH: &str = "/.well-known/oauth-protected-resource/v1/realtime"; /// An absent string. @@ -324,7 +324,7 @@ pub const TAIL: &PlaneTail = &PlaneTail { admin_openapi: Blob::ABSENT, }; -/// The inbound auth style of the metadata door: none, it is read without a credential. +/// The inbound auth style of a door read without a credential (no door of the plane is, today). pub const NO_AUTH: &str = "none"; /// One door a live session is opened through: one line of this plane on a listener's guest list @@ -385,9 +385,10 @@ impl Route { } /// The doors, in the order a snapshot claims them: the ephemeral-secret mint and the SDP offer (one -/// request each), the browser's sideband socket, the Gemini Live socket, the telephony socket, and -/// the protected-resource metadata document a refused caller is pointed at (read without a -/// credential). The three sockets are upgrade lines. +/// request each), the browser's sideband socket, the Gemini Live socket and the telephony socket. +/// The three sockets are upgrade lines. The protected-resource metadata path a refused caller is +/// pointed at is NOT claimed (1.5.5 parity, ARCHITECT 2026-10-04: no accepted difference): the +/// data listener's fallback answers it as it did. pub const ROUTES: &[Route] = &[ Route { verb: "POST", @@ -434,15 +435,6 @@ pub const ROUTES: &[Route] = &[ dialect: 2, refusal_dialect: 2, }, - Route { - verb: "GET", - target: METADATA_PATH, - carrier: HTTP_TRANSPORT, - upgrade: false, - auth: NO_AUTH, - dialect: 0, - refusal_dialect: 0, - }, ]; impl Route { @@ -573,19 +565,14 @@ pub struct Plane { } impl Plane { - /// A unit arriving on claim `claim`, under the newest live generation's session params and - /// audience, naming its caller by `caller_ref`. `None` for a claim the plane never published, - /// and for a door that opens a live session. + /// A unit arriving on claim `claim`, under the newest live generation's session params, naming + /// its caller by `caller_ref`. `None` for a claim the plane never published, and for a door + /// that opens a live session. fn held(&self, claim: u32, caller_ref: Option) -> Option { let door = Door::of(claim)?; let generations = lock(&self.generations); - let (cfg, g) = generations.last()?; - let unit = RequestUnit::open( - door, - cfg.session.clone(), - caller_ref, - g.audience().to_string(), - )?; + let (cfg, _) = generations.last()?; + let unit = RequestUnit::open(door, cfg.session.clone(), caller_ref)?; Some(Held { unit, owed: Owed::default(), @@ -1023,7 +1010,7 @@ fn session_piece(p: &Plane, input: Lent<'_, OnPieceIn>, out: &mut Out<'_, OnPiec Outcome::Ready } -// `on_piece`: a one-request door's pieces (the mint, the SDP offer, the metadata document), each +// `on_piece`: a one-request door's pieces (the mint, the SDP offer), each // answered by the unit's [`RequestUnit`]: the ATTEMPT's request with its body, the caller's body to // the far end, and the caller's answer from the far end's. A finished or refused unit is forgotten. // A piece that names a stream is a live session's ([`session_piece`]). diff --git a/crates/busbar-plane-streaming/src/driven.rs b/crates/busbar-plane-streaming/src/driven.rs index 443a70f93c..a27c210277 100644 --- a/crates/busbar-plane-streaming/src/driven.rs +++ b/crates/busbar-plane-streaming/src/driven.rs @@ -40,8 +40,6 @@ pub enum Door { Gemini, /// The telephony socket. Twilio, - /// The protected-resource metadata document, read without a credential. - Metadata, } impl Door { @@ -54,7 +52,6 @@ impl Door { 2 => Some(Door::Sideband), 3 => Some(Door::Gemini), 4 => Some(Door::Twilio), - 5 => Some(Door::Metadata), _ => None, } } @@ -96,7 +93,7 @@ pub struct Arrival { } /// `arrive` for claim `claim`: every keyed door opens a session (or brokers one) for a known -/// principal; the metadata door answers anyone. +/// principal. #[must_use] pub const fn arrive(claim: u32) -> Option { match Door::of(claim) { @@ -140,7 +137,8 @@ pub trait Steps { } impl Steps for Door { - /// Every keyed door needs a principal; the metadata document answers anyone. + /// Every keyed door needs a principal; a door read without a credential (none today) answers + /// anyone. fn authenticate(&self) -> u32 { if self.is_open() { busbar_contract::abi::plane::PRINCIPAL_NONE @@ -163,18 +161,12 @@ impl Steps for Door { /// per-session fee, which the kernel counts); the one-request doors report nothing. fn admit(&self) -> Vec<(u32, u64)> { match self { - Door::Mint - | Door::Sdp - | Door::Sideband - | Door::Gemini - | Door::Twilio - | Door::Metadata => Vec::new(), + Door::Mint | Door::Sdp | Door::Sideband | Door::Gemini | Door::Twilio => Vec::new(), } } /// The session doors dial the dialect's realtime socket (the credential is the kernel's, never - /// in the target); the mint and SDP doors send their one request; the metadata document is - /// answered here and sends nothing. + /// in the target); the mint and SDP doors send their one request. fn route( &self, config: &SessionConfig, @@ -196,7 +188,6 @@ impl Steps for Door { fields: Vec::new(), body: Vec::new(), }), - Door::Metadata => None, }) } @@ -211,12 +202,9 @@ impl Steps for Door { .collect() } - /// A session open, or a read of the metadata document. + /// A session open. fn audit(&self) -> &'static str { - match self { - Door::Metadata => "streaming.metadata.read", - _ => SESSION_OPEN_ACTION, - } + SESSION_OPEN_ACTION } } @@ -425,26 +413,3 @@ pub const fn reason_phrase(status: u16) -> Option<&'static str> { #[cfg(test)] #[path = "tests/driven_tests.rs"] mod tests; - -/// The protected-resource metadata document for `audience`: the audience a token at the plane's -/// doors must carry, and the one place a bearer token is accepted. The plane names no authorization -/// server: it is configured with none. -#[must_use] -pub fn metadata_reply(audience: &str) -> Reply { - Reply { - status: 200, - fields: vec![ - ("cache-control", "public, max-age=3600".to_string()), - ( - FIELD_CONTENT_TYPE, - "application/json; charset=utf-8".to_string(), - ), - ], - body: serde_json::to_vec(&serde_json::json!({ - "resource": audience, - "bearer_methods_supported": ["header"], - })) - .unwrap_or_default(), - rtc_call_id: None, - } -} diff --git a/crates/busbar-plane-streaming/src/request_unit.rs b/crates/busbar-plane-streaming/src/request_unit.rs index d33139d046..9558d3aee5 100644 --- a/crates/busbar-plane-streaming/src/request_unit.rs +++ b/crates/busbar-plane-streaming/src/request_unit.rs @@ -19,7 +19,7 @@ use crate::codec::ir::config::SessionConfig; use crate::driven::{ - metadata_reply, mint_failed, mint_reply, sdp_reply, Attempt, Door, Reply, Steps, FIELD_LOCATION, + mint_failed, mint_reply, sdp_reply, Attempt, Door, Reply, Steps, FIELD_LOCATION, }; /// Who pushed a piece. @@ -70,7 +70,6 @@ pub struct RequestUnit { door: Door, locked: SessionConfig, caller_ref: Option, - audience: String, status: Option, location: Option, far: Vec, @@ -80,15 +79,10 @@ pub struct RequestUnit { impl RequestUnit { /// Open the plane's state for one kernel unit on `door` (the unit's identity is the kernel's; /// the door keys this state by it): minting over the `locked` session params, naming the caller - /// by `caller_ref` (the kernel's reference, never the principal) and answering the metadata - /// document for `audience`. `None` for a door that opens a live session. + /// by `caller_ref` (the kernel's reference, never the principal). `None` for a door that opens + /// a live session. #[must_use] - pub fn open( - door: Door, - locked: SessionConfig, - caller_ref: Option, - audience: String, - ) -> Option { + pub fn open(door: Door, locked: SessionConfig, caller_ref: Option) -> Option { if door.is_session() { return None; } @@ -96,7 +90,6 @@ impl RequestUnit { door, locked, caller_ref, - audience, status: None, location: None, far: Vec::new(), @@ -127,11 +120,6 @@ impl RequestUnit { return Answer::Nothing; } match (self.door, piece.from) { - (Door::Metadata, From::Kernel(_) | From::Caller) => { - let reply = metadata_reply(&self.audience); - self.answer(reply) - } - (Door::Metadata, From::FarEnd) => Answer::Refused, (_, From::Kernel(0)) => Answer::Refused, (_, From::Kernel(_)) => { self.status = None; diff --git a/crates/busbar-plane-streaming/src/session_door.rs b/crates/busbar-plane-streaming/src/session_door.rs index a9e93d0f06..92b62871ba 100644 --- a/crates/busbar-plane-streaming/src/session_door.rs +++ b/crates/busbar-plane-streaming/src/session_door.rs @@ -168,7 +168,7 @@ impl Live { Door::Gemini => { Unit::Gemini(SessionUnit::open(GeminiLiveCodec, locked, false, 0, None)) } - Door::Mint | Door::Sdp | Door::Metadata => return None, + Door::Mint | Door::Sdp => return None, }; Some(Live { unit, diff --git a/crates/busbar-plane-streaming/src/sessions.rs b/crates/busbar-plane-streaming/src/sessions.rs index 56f4118ef2..765221a6f4 100644 --- a/crates/busbar-plane-streaming/src/sessions.rs +++ b/crates/busbar-plane-streaming/src/sessions.rs @@ -63,7 +63,7 @@ pub fn open( id, owner, )), - Door::Mint | Door::Sdp | Door::Metadata => return None, + Door::Mint | Door::Sdp => return None, }) } diff --git a/crates/busbar-plane-streaming/src/tests/driven_tests.rs b/crates/busbar-plane-streaming/src/tests/driven_tests.rs index 3f7925c747..b02d0353cc 100644 --- a/crates/busbar-plane-streaming/src/tests/driven_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/driven_tests.rs @@ -7,7 +7,7 @@ use super::*; #[test] fn every_published_claim_arrives_at_its_door_in_its_dialect() { - let doors: Vec<_> = (0..6) + let doors: Vec<_> = (0..5) .map(|c| arrive(c).map(|a| (a.door, a.dialect, a.op_class))) .collect(); assert_eq!( @@ -18,15 +18,14 @@ fn every_published_claim_arrives_at_its_door_in_its_dialect() { Some((Door::Sideband, 0, 0)), Some((Door::Gemini, 1, 0)), Some((Door::Twilio, 2, 0)), - Some((Door::Metadata, 0, 0)), ] ); assert_eq!( - arrive(6), + arrive(5), None, - "a claim the plane never published arrives nowhere" + "a claim the plane never published arrives nowhere (the metadata path is no claim)" ); - assert_eq!(crate::door::ROUTES.len(), 6, "one door per published claim"); + assert_eq!(crate::door::ROUTES.len(), 5, "one door per published claim"); } #[test] @@ -114,21 +113,11 @@ fn a_status_prints_with_its_reason_phrase_when_it_has_one() { assert_eq!(status_text(299), "299"); } -#[test] -fn the_metadata_document_names_the_audience() { - let r = metadata_reply("https://gw.example.com/v1/realtime"); - assert_eq!(r.status, 200); - let v: serde_json::Value = serde_json::from_slice(&r.body).expect("json"); - assert_eq!(v["resource"], "https://gw.example.com/v1/realtime"); - assert_eq!(v["bearer_methods_supported"], serde_json::json!(["header"])); -} - #[test] fn each_loop_step_has_the_planes_answer() { - use busbar_contract::abi::plane::{PRINCIPAL_NONE, PRINCIPAL_REQUIRED}; + use busbar_contract::abi::plane::PRINCIPAL_REQUIRED; assert_eq!(Door::Mint.authenticate(), PRINCIPAL_REQUIRED); assert_eq!(Door::Twilio.authenticate(), PRINCIPAL_REQUIRED); - assert_eq!(Door::Metadata.authenticate(), PRINCIPAL_NONE); assert_eq!(Door::Sideband.verify(), "session.model"); assert_eq!(Door::Sideband.approve(), ("session", "streaming-server")); assert!( @@ -149,7 +138,6 @@ fn each_loop_step_has_the_planes_answer() { Door::Sdp.route(&cfg, None, b"v=0").expect("routes"), Some(sdp_attempt(b"v=0")) ); - assert_eq!(Door::Metadata.route(&cfg, None, b"").expect("routes"), None); let mut units = crate::session_unit::CumulativeUnits::default(); units.0[1] = 20; units.0[4] = 2; diff --git a/crates/busbar-plane-streaming/src/tests/ladder_tests.rs b/crates/busbar-plane-streaming/src/tests/ladder_tests.rs index 9f55edd912..f14f513c55 100644 --- a/crates/busbar-plane-streaming/src/tests/ladder_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/ladder_tests.rs @@ -100,7 +100,6 @@ const SERVED: &[(&str, &str, Door, u32, &str)] = &[ KEY_AUTH, ), ("GET", "/twilio/CA123", Door::Twilio, 2, SIGNATURE_AUTH), - ("GET", METADATA_PATH, Door::Metadata, 0, NO_AUTH), ]; #[test] @@ -149,7 +148,6 @@ fn a_served_path_under_another_method_is_405_with_the_lines_allow() { ("POST", "/v1/realtime/sideband/rtc_abc", "GET"), ("POST", "/v1/realtime/gemini/call-1", "GET"), ("POST", "/twilio/CA123", "GET"), - ("POST", METADATA_PATH, "GET"), ] { assert_eq!( resolve(method, path), @@ -201,13 +199,16 @@ fn no_two_lines_tie_and_each_lines_own_witness_reaches_it() { } #[test] -fn only_the_metadata_line_is_open_and_the_telephony_line_is_signed() { +fn no_line_is_open_and_the_telephony_line_is_signed() { let open: Vec<_> = ROUTES .iter() .filter(|r| r.open()) .map(|r| r.target) .collect(); - assert_eq!(open, [METADATA_PATH]); + assert!( + open.is_empty(), + "no door is read without a credential: the metadata path is not claimed (1.5.5 parity)" + ); let signed: Vec<_> = ROUTES .iter() .filter(|r| r.auth == SIGNATURE_AUTH) diff --git a/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs b/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs index 148b24d039..c3d36fe93f 100644 --- a/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/request_unit_tests.rs @@ -7,14 +7,11 @@ use super::*; use crate::broker::{CALLS_PATH, CLIENT_SECRETS_PATH, SAFETY_IDENTIFIER_HEADER}; use crate::driven::MINT_FAILED_STATUS; -const AUDIENCE: &str = "https://gw.example/v1/realtime"; - fn unit(door: Door, caller_ref: Option<&str>) -> RequestUnit { RequestUnit::open( door, SessionConfig::default(), caller_ref.map(str::to_owned), - AUDIENCE.to_owned(), ) .expect("a one-request door") } @@ -54,7 +51,7 @@ fn reply(a: Answer) -> Reply { #[test] fn a_session_door_is_no_request_unit() { for door in [Door::Sideband, Door::Gemini, Door::Twilio] { - assert!(RequestUnit::open(door, SessionConfig::default(), None, String::new()).is_none()); + assert!(RequestUnit::open(door, SessionConfig::default(), None).is_none()); } } @@ -170,12 +167,3 @@ fn a_piece_from_the_kernel_that_is_no_attempt_is_refused() { Answer::Refused ); } - -#[test] -fn the_metadata_document_is_answered_at_once_and_dials_nothing() { - let mut u = unit(Door::Metadata, None); - let r = reply(u.on_piece(piece(From::Caller, &[], true))); - assert_eq!(r, metadata_reply(AUDIENCE)); - let mut v = unit(Door::Metadata, None); - assert_eq!(v.on_piece(far_first(200, b"", true, &[])), Answer::Refused); -} diff --git a/crates/busbar-plane-streaming/src/tests/session_door_tests.rs b/crates/busbar-plane-streaming/src/tests/session_door_tests.rs index c220957b9e..de875f6334 100644 --- a/crates/busbar-plane-streaming/src/tests/session_door_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_door_tests.rs @@ -51,7 +51,7 @@ fn seconds() -> u32 { #[test] fn a_one_request_door_opens_no_session() { - for door in [Door::Mint, Door::Sdp, Door::Metadata] { + for door in [Door::Mint, Door::Sdp] { assert!( Live::open(door, &StreamsCfg::default()).is_none(), "{door:?}" diff --git a/crates/busbar-plane-streaming/src/tests/sessions_tests.rs b/crates/busbar-plane-streaming/src/tests/sessions_tests.rs index 031cc527ee..0dfbe36c5b 100644 --- a/crates/busbar-plane-streaming/src/tests/sessions_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/sessions_tests.rs @@ -39,7 +39,7 @@ fn the_telephony_door_opens_a_session_behind_the_twilio_envelope() { #[test] fn a_one_request_door_opens_no_session() { let locked = SessionConfig::default(); - for door in [Door::Mint, Door::Sdp, Door::Metadata] { + for door in [Door::Mint, Door::Sdp] { assert!( open(door, &locked, 0, None, "x", "ref").is_none(), "{door:?}" diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index c823e5684d..f82d7ed4ab 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -261,8 +261,7 @@ const PUBLIC: &str = "https://gw.example.com/ignored?q=1#f"; fn expected(public: bool) -> Vec { let doors = "POST /v1/realtime/client_secrets http exact, POST /v1/realtime/calls http exact, \ GET /v1/realtime/sideband/{call_id} ws, GET /v1/realtime/gemini/{call_id} ws, \ - GET /twilio/{call_id} ws, \ - GET /.well-known/oauth-protected-resource/v1/realtime http exact open"; + GET /twilio/{call_id} ws"; let (claims, audience, metadata) = if public { ( doors, @@ -284,7 +283,7 @@ fn expected(public: bool) -> Vec { format!("refresh Ready {}", snap(2)), "retire 1 Ready".into(), "arrive 3 Ready dialect=1 op=0 principal=1".into(), - "arrive 5 Ready dialect=0 op=0 principal=0".into(), + "arrive 5 Refused dialect=0 op=0 principal=0".into(), "arrive 9 Refused dialect=0 op=0 principal=0".into(), "tick Ready next=0".into(), "cancel Ready failed=true".into(), @@ -371,8 +370,6 @@ fn the_tail_states_what_the_served_plane_declares() { /// The section the unit script opens under: the session params a mint locks the browser to. const SESSION: &[u8] = br#"{"session":{"model":"m-cap"}}"#; -/// The audience [`PUBLIC`] reads to. -const AUDIENCE: &str = "https://gw.example.com/v1/realtime"; /// The kernel's reference for the caller. const CALLER_REF: &str = "c0ffee"; /// The body the served plane sent its far end for that mint (the recorded @@ -512,8 +509,8 @@ const fn attempt(unit: u64, claim: u32) -> Push<'static> { } } -/// THE UNIT SCRIPT: a mint, an SDP offer, a metadata read written 16 bytes at a time, a session -/// door's piece and an unpublished claim's, in the order the route pump pushes them. +/// THE UNIT SCRIPT: a mint, an SDP offer, a session door's piece and an unpublished claim's (the +/// metadata path among them: it is no claim, 1.5.5 parity), in the order the route pump pushes them. fn unit_script(p: &Plugin) -> Vec { let mut t = vec![format!("open {:?}", open(p, 1, SESSION, Some(PUBLIC)).0)]; let mut b = Bufs::new(256); @@ -542,19 +539,9 @@ fn unit_script(p: &Plugin) -> Vec { }; t.push(format!("sdp far_end {}", b.call(p, &far))); - let mut narrow = Bufs::new(16); - t.push(format!("metadata {}", narrow.call(p, &attempt(9, 5)))); - for _ in 0..8 { - let line = narrow.call(p, &push(9, 5, FROM_KERNEL, 0, &[])); - let done = line.contains("done=true"); - t.push(format!("metadata more {line}")); - if done { - break; - } - } - t.push(format!("sideband {}", b.call(p, &attempt(10, 2)))); t.push(format!("unpublished {}", b.call(p, &attempt(11, 9)))); + t.push(format!("metadata path {}", b.call(p, &attempt(12, 5)))); t } @@ -562,7 +549,6 @@ fn unit_script(p: &Plugin) -> Vec { fn unit_expected() -> Vec { let none = "verb= target= fields=[]"; let minted = driven::mint_reply(200, MINTED); - let metadata = driven::metadata_reply(AUDIENCE); let mut t = vec![ "open Ready".to_string(), format!( @@ -589,32 +575,15 @@ fn unit_expected() -> Vec { String::from_utf8_lossy(SDP_ANSWER) ), ]; - let chunks: Vec<&[u8]> = metadata.body.chunks(16).collect(); - for (n, chunk) in chunks.iter().enumerate() { - let last = n + 1 == chunks.len(); - let (lead, status, fields) = if n == 0 { - ( - "metadata", - 200, - "[\"cache-control=public, max-age=3600\", \ - \"content-type=application/json; charset=utf-8\"]", - ) - } else { - ("metadata more", 0, "[]") - }; - t.push(format!( - "{lead} Ready emitted={} more={} far=false done={last} status={status} verb= target= \ - fields={fields}", - String::from_utf8_lossy(chunk), - u32::from(!last), - )); - } t.push(format!( "sideband Refused emitted= more=0 far=false done=false status=0 {none}" )); t.push(format!( "unpublished Refused emitted= more=0 far=false done=false status=0 {none}" )); + t.push(format!( + "metadata path Refused emitted= more=0 far=false done=false status=0 {none}" + )); t } @@ -788,8 +757,8 @@ fn arrive_route(p: &Plugin, claim: u32) -> (Outcome, u8, String) { } /// Every door that reaches the far end names the DIRECT route `streams.session.model` (the entry the -/// composition folds from the top-level models catalog); the metadata door reaches none and names -/// none; with no model configured nothing is named (the door steps refuse an unnamed keyed route). +/// composition folds from the top-level models catalog); claim 5 (the metadata path) is no claim +/// and arrives nowhere; with no model configured nothing is named (the door steps refuse an unnamed keyed route). /// Both ways. #[test] fn a_door_that_reaches_the_far_end_names_the_session_model_as_its_direct_route() { @@ -806,10 +775,10 @@ fn a_door_that_reaches_the_far_end_names_the_session_model_as_its_direct_route() "claim {claim}" ); } - assert_eq!( - arrive_route(p, 5), - (Outcome::Ready, ROUTE_POOL, String::new()), - "the metadata document reaches no far end" + assert_ne!( + arrive_route(p, 5).0, + Outcome::Ready, + "the metadata path is no claim of the plane (1.5.5 parity)" ); } let bare = linked(&d); From cda00b9715a6a113e9fa80d4b7ae3de2c34fe29c Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:39:15 -0700 Subject: [PATCH 31/82] root registry test: the session wire is linked with a session plane, its legacy row or, under a fold switch that swapped that row out (linked-swaps, read off the manifest), its folded door --- crates/busbar/src/root/tests/registry.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/busbar/src/root/tests/registry.rs b/crates/busbar/src/root/tests/registry.rs index cdb7601dc6..5f20b50403 100644 --- a/crates/busbar/src/root/tests/registry.rs +++ b/crates/busbar/src/root/tests/registry.rs @@ -57,7 +57,26 @@ const CLAIMS_PER_PLANE: &str = include_str!("fixtures/claims_per_plane.txt"); /// compiled it out is a different composition, not a smaller one. Read off `LINKED`, so this source /// names no plane. fn session_linked() -> bool { - !crate::LINKED.gauntlet_session.is_empty() + !crate::LINKED.gauntlet_session.is_empty() || a_swapped_row_is_served_by_its_door() +} + +/// A fold's development-only switch is on and has swapped its plane's legacy row out +/// (`[package.metadata.busbar.linked-swaps]`): the plane is served by its folded door, and the +/// wire its sessions ride stays linked. Read off the manifest, so this names no plane. +fn a_swapped_row_is_served_by_its_door() -> bool { + let on: Vec<&str> = env!("BUSBAR_ENABLED_FEATURES").split_whitespace().collect(); + let mut in_table = false; + include_str!("../../../Cargo.toml").lines().any(|line| { + let line = line.split('#').next().unwrap_or("").trim(); + if line.starts_with('[') { + in_table = line == "[package.metadata.busbar.linked-swaps]"; + return false; + } + in_table + && line + .split_once('=') + .is_some_and(|(_, switch)| on.contains(&switch.trim().trim_matches('"'))) + }) } /// The non-comment rows of a fixture file, in order. Read only by the shipped-composition pins, From 72c07d7a08256be91ddcc75c7940ff3f42d08029 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:47:29 -0700 Subject: [PATCH 32/82] conformance (transport script): a dial names its target and may open with bytes of its own, a refusal may be preceded by the accepted framing's opening, and the upgrade's two halves (detach, adopt) are separate capabilities (ARCHITECT 2026-10-04, ruling (4) on the ws door's suite) Inputs, all optional so every existing transport's inputs read as before: - dial.target (begin's BeginIn.target; ws needs a ws:// URL), dial.opening (what begin writes, the begin re-driven by timer while YIELD_MORE: ws's upgrade request), dial.answer (far bytes read before the first frame: ws's 101), dial.finish (what finish writes: ws's close); - refuse.opening / refuse.answer (the accepted framing's opening and what reading it writes); - detach and adopt in place of handoff: each "refused" or its object (adopt.wire: what adopting writes), so a wire may take an upgrade it never gives one up for; - a byte input may be { "hex": "..." } for bytes a JSON string cannot hold. Crossing pins follow the same rule: one per sinkful of what the op must carry. --- .../src/conformance/transport.rs | 246 ++++++++++++++---- 1 file changed, 202 insertions(+), 44 deletions(-) diff --git a/crates/plugin-loader/src/conformance/transport.rs b/crates/plugin-loader/src/conformance/transport.rs index a7b8686082..6ee85d43d8 100644 --- a/crates/plugin-loader/src/conformance/transport.rs +++ b/crates/plugin-loader/src/conformance/transport.rs @@ -8,6 +8,8 @@ //! `YIELD_MORE`, equal transcripts, exact crossings) with every byte and every expected framing //! moved into the plugin's inputs. Inputs (`conformance.json`): //! +//! A byte input is a JSON string (its UTF-8 bytes) or `{ "hex": "" }`. +//! //! ```json //! { "settings": , //! "transport": { @@ -15,16 +17,30 @@ //! "reads": [{ "target": "", "authority": "", "secure": false }, ...], //! "refuses": ["", ...] }, //! "dial": { +//! "target": "", /* optional, default "": a wire whose +//! dial reads a target (ws) names it */ +//! "opening": "", /* optional, default "": a wire that opens +//! with bytes of its own (ws's upgrade +//! request) states them */ +//! "answer": "", /* optional: the far side's opening answer +//! (ws's 101), read before the first frame */ //! "emit": "", "wire": "", -//! "ingest": "", "frames": ["", ...] }, +//! "ingest": "", "frames": ["", ...], +//! "finish": "" }, /* optional, default "" (ws's close) */ //! "encode": [{ "body": "", "fields": [["", ""], ...], //! "wire": "" | null /* FAILED */ }, ...], -//! "refuse": { "bytes": "", "wire": "" }, +//! "refuse": { "bytes": "", "wire": "", +//! "opening": "", /* optional */ +//! "answer": "" }, /* optional */ //! "handoff": "refused" | { //! "ingest": "", //! "detached": "", //! "adopt": "", -//! "frames": ["", ...] } } } +//! "frames": ["", ...] } +//! /* or, the two halves of a handoff as separate capabilities (a wire may take an upgrade it +//! never gives one up for, as ws does): */ +//! "detach": "refused" | { "ingest": "...", "detached": "..." }, +//! "adopt": "refused" | { "leftover": "...", "frames": [...], "wire": "" } } } //! ``` //! //! THE PINS (M6/contract). Every step is one ticket-less crossing but: @@ -33,7 +49,8 @@ //! is not open (REFUSED) or closed (FAULT) without a crossing; //! * `locate short`: 2, the authority buffer of one byte answers SHORT and the ONE re-call //! ([`Plugin::recall`]) with the size it named is +1; -//! * a re-driven framer op (`emit`, `ingest`, `refuse`, `detach`, `adopt`): one crossing per +//! * a re-driven framer op (`begin`, `emit`, `ingest`, `refuse`, `detach`, `adopt`, `finish`): one +//! crossing per //! sink-full, the host calling again with no new bytes while the framer answers `YIELD_MORE` //! (the ABI's backpressure, never a short buffer): `ceil(bytes / cap)` of what the sink must //! carry, at least 1, over a sink of [`TIGHT_WIRE`] wire bytes, [`TIGHT_FRAME`] frame bytes and @@ -68,14 +85,41 @@ const MAX_REDRIVE: usize = 4096; // ---- the inputs ---- +/// One byte-string input: a JSON string's UTF-8 bytes, or `{ "hex": "" }` for bytes a JSON +/// string cannot hold (a binary frame's header, say). fn text(v: &serde_json::Value, what: &str) -> Vec { match v { serde_json::Value::String(s) => s.as_bytes().to_vec(), serde_json::Value::Null => panic!("conformance.json: transport.{what} is missing"), + serde_json::Value::Object(o) if o.len() == 1 && o.contains_key("hex") => o["hex"] + .as_str() + .and_then(unhex) + .unwrap_or_else(|| panic!("conformance.json: transport.{what}.hex is not hex")), other => other.to_string().into_bytes(), } } +/// `hex`'s bytes (two digits a byte, either case); `None` for anything else. +fn unhex(hex: &str) -> Option> { + let digits = hex.as_bytes(); + if !digits.len().is_multiple_of(2) { + return None; + } + digits + .chunks(2) + .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).ok()?, 16).ok()) + .collect() +} + +/// An optional input: absent is empty. +fn opt_text(v: &serde_json::Value, what: &str) -> Vec { + if v.is_null() { + Vec::new() + } else { + text(v, what) + } +} + fn texts(v: &serde_json::Value, what: &str) -> Vec> { v.as_array() .unwrap_or_else(|| panic!("conformance.json: transport.{what} must be an array")) @@ -233,7 +277,7 @@ impl Yielded { /// One framer op, by what it carries. #[derive(Clone, Copy)] enum Op<'a> { - Begin(u32), + Begin(u32, &'a [u8]), Ingest(&'a [u8], bool), Emit(&'a [u8]), Refuse(&'a [u8]), @@ -252,7 +296,7 @@ impl Op<'_> { Op::Ingest(_, end) => Op::Ingest(&[], end), Op::Emit(_) => Op::Emit(&[]), Op::Refuse(_) => Op::Refuse(&[]), - Op::Adopt(_) => Op::Timer, + Op::Adopt(_) | Op::Begin(..) => Op::Timer, other => other, } } @@ -268,9 +312,13 @@ fn go(p: &Plugin, s: u32, i: I) -> (Called, fn cross(p: &Plugin, framing: u64, op: Op<'_>, sink: &mut Sink) -> (Called, FramerOut) { let raw = sink.raw(); match op { - Op::Begin(side) => { + Op::Begin(side, target) => { let mut i: BeginIn = input(); (i.side, i.sink) = (side, raw); + i.target = AbiStr { + ptr: target.as_ptr(), + len: target.len(), + }; go(p, slot::BEGIN, i) } Op::Ingest(bytes, end) => { @@ -509,7 +557,7 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { }); // 0: the dispatcher refuses an op on an instance that is not open, without a crossing. r.line("begin unopened", 0, || { - once(&p, 0, Op::Begin(SIDE_DIAL), &mut Sink::ample()).line() + once(&p, 0, Op::Begin(SIDE_DIAL, b""), &mut Sink::ample()).line() }); want.push(("begin unopened".into(), Want::Starts("Refused "))); @@ -592,15 +640,29 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { text(&dial["ingest"], "dial.ingest"), texts(&dial["frames"], "dial.frames"), ); + let target = opt_text(&dial["target"], "dial.target"); + let opening = opt_text(&dial["opening"], "dial.opening"); let mut tight = Sink::tight(); - let token = r.step("begin dial", 1, || { - let y = once(&p, 0, Op::Begin(SIDE_DIAL), &mut tight); + // A wire that opens with bytes of its own writes them at begin, re-driven by `timer`. + let token = r.step("begin dial", sinkfuls(opening.len(), TIGHT_WIRE), || { + let y = pump(&p, 0, Op::Begin(SIDE_DIAL, &target), &mut tight); (y.line(), y.framing) }); want.push(( "begin dial".into(), - Want::Is(yielded_line(&[], &[], &[], 0, true)), + Want::Is(yielded_line(&opening, &[], &[], 0, true)), )); + // The far side's opening answer, read before the first frame: it writes and yields nothing. + if !dial["answer"].is_null() { + let answer = text(&dial["answer"], "dial.answer"); + r.line("dial answer", 1, || { + pump(&p, token, Op::Ingest(&answer, false), &mut tight).line() + }); + want.push(( + "dial answer".into(), + Want::Is(yielded_line(&[], &[], &[], 0, false)), + )); + } r.line("emit", sinkfuls(wire.len(), TIGHT_WIRE), || { pump(&p, token, Op::Emit(&emit), &mut tight).line() }); @@ -622,12 +684,13 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { "ingest end".into(), Want::Is(yielded_line(&[], &[], &[], YIELD_ENDED, false)), )); - r.line("finish", 1, || { - once(&p, token, Op::Finish, &mut tight).line() + let finish = opt_text(&dial["finish"], "dial.finish"); + r.line("finish", sinkfuls(finish.len(), TIGHT_WIRE), || { + pump(&p, token, Op::Finish, &mut tight).line() }); want.push(( "finish".into(), - Want::Is(yielded_line(&[], &[], &[], YIELD_ENDED, false)), + Want::Is(yielded_line(&finish, &[], &[], YIELD_ENDED, false)), )); r.line("finish again", 1, || { once(&p, token, Op::Finish, &mut tight).line() @@ -684,13 +747,26 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { text(&refuse["wire"], "refuse.wire"), ); let token = r.step("begin accept", 1, || { - let y = once(&p, 0, Op::Begin(SIDE_ACCEPT), &mut tight); + let y = once(&p, 0, Op::Begin(SIDE_ACCEPT, b""), &mut tight); (y.line(), y.framing) }); want.push(( "begin accept".into(), Want::Is(yielded_line(&[], &[], &[], 0, true)), )); + // A wire whose accepted framing must be opened before it can carry a refusal (ws: the upgrade + // request, answered with the switch) reads its opening first. + if !refuse["opening"].is_null() { + let opening = text(&refuse["opening"], "refuse.opening"); + let answer = opt_text(&refuse["answer"], "refuse.answer"); + r.line("accept opening", sinkfuls(answer.len(), TIGHT_WIRE), || { + pump(&p, token, Op::Ingest(&opening, false), &mut tight).line() + }); + want.push(( + "accept opening".into(), + Want::Is(yielded_line(&answer, &[], &[], 0, false)), + )); + } r.line("refuse", sinkfuls(wire.len(), TIGHT_WIRE), || { pump(&p, token, Op::Refuse(&bytes), &mut tight).line() }); @@ -710,23 +786,64 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { // ── the upgrade: what was ingested and not answered is handed back, and adopted ── let token = r.step("begin upgrade", 1, || { - let y = once(&p, 0, Op::Begin(SIDE_ACCEPT), &mut tight); + let y = once(&p, 0, Op::Begin(SIDE_ACCEPT, b""), &mut tight); (y.line(), y.framing) }); want.push(("begin upgrade".into(), Want::Starts("Ready "))); + if k["handoff"].is_null() { + halves(&mut r, &mut want, &p, k, token); + } else { + whole_handoff(&mut r, &mut want, &p, k, token); + } + r.line("ingest on no framing", 1, || { + once(&p, u64::MAX, Op::Ingest(b"x", false), &mut tight).line() + }); + want.push(("ingest on no framing".into(), Want::Starts("Failed "))); + + // ── the rest of the lifecycle; a closed instance serves nothing ── + r.line("tick", 1, || { + let (c, next) = tick(&p, 1); + format!("{} next={next}", super::called(&c)) + }); + r.line("refresh", 1, || super::called(&refresh(&p, &settings))); + r.line("close", 1, || super::called(&close(&p))); + for l in ["refresh", "close"] { + want.push((l.into(), Want::Starts("Ready "))); + } + // 0: a closed instance answers FAULT without a crossing. + r.line("begin after close", 0, || { + once(&p, 0, Op::Begin(SIDE_DIAL, b""), &mut Sink::ample()).line() + }); + want.push(("begin after close".into(), Want::Starts("Fault "))); + + let fold = r.fold(); + contract(&fold, &want); + fold +} + +/// THE UPGRADE AS ONE CAPABILITY (`handoff`): what was ingested and not answered is handed back, +/// and adopted; or both refused. +fn whole_handoff( + r: &mut Recorder, + want: &mut Vec<(String, Want)>, + p: &Plugin, + k: &serde_json::Value, + token: u64, +) { + let mut tight = Sink::tight(); match &k["handoff"] { serde_json::Value::String(v) if v == "refused" => { r.line("detach", 1, || { - once(&p, token, Op::Detach, &mut tight).line() + once(p, token, Op::Detach, &mut tight).line() }); r.line("adopt", 1, || { - once(&p, 0, Op::Adopt(b"conformance"), &mut tight).line() + once(p, 0, Op::Adopt(b"conformance"), &mut tight).line() }); for l in ["detach", "adopt"] { want.push((l.into(), Want::Starts("Refused "))); } r.line("finish upgrade", 1, || { - once(&p, token, Op::Finish, &mut tight).line() + once(p, token, Op::Finish, &mut tight).line() }); want.push(("finish upgrade".into(), Want::Starts("Ready "))); } @@ -737,18 +854,18 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { let frames = texts(&h["frames"], "handoff.frames"); // 1: ingested once; the host stops re-driving it, it is upgrading. r.line("ingest before upgrade", 1, || { - once(&p, token, Op::Ingest(&ingest, false), &mut tight).line() + once(p, token, Op::Ingest(&ingest, false), &mut tight).line() }); want.push(("ingest before upgrade".into(), Want::Starts("Ready "))); r.line("detach", sinkfuls(detached.len(), TIGHT_FRAME), || { - pump(&p, token, Op::Detach, &mut tight).line() + pump(p, token, Op::Detach, &mut tight).line() }); want.push(( "detach".into(), Want::Is(yielded_line(&[], &[], &detached, YIELD_ENDED, false)), )); let adopted = r.step("adopt", frame_sinkfuls(&frames), || { - let y = pump(&p, 0, Op::Adopt(&leftover), &mut tight); + let y = pump(p, 0, Op::Adopt(&leftover), &mut tight); (y.line(), y.framing) }); want.push(( @@ -756,35 +873,76 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { Want::Is(yielded_line(&[], &frames, &[], 0, true)), )); r.line("finish adopted", 1, || { - once(&p, adopted, Op::Finish, &mut tight).line() + once(p, adopted, Op::Finish, &mut tight).line() }); want.push(("finish adopted".into(), Want::Starts("Ready "))); } } - r.line("ingest on no framing", 1, || { - once(&p, u64::MAX, Op::Ingest(b"x", false), &mut tight).line() - }); - want.push(("ingest on no framing".into(), Want::Starts("Failed "))); +} - // ── the rest of the lifecycle; a closed instance serves nothing ── - r.line("tick", 1, || { - let (c, next) = tick(&p, 1); - format!("{} next={next}", super::called(&c)) - }); - r.line("refresh", 1, || super::called(&refresh(&p, &settings))); - r.line("close", 1, || super::called(&close(&p))); - for l in ["refresh", "close"] { - want.push((l.into(), Want::Starts("Ready "))); +/// THE UPGRADE AS TWO CAPABILITIES (`detach`, `adopt`): a wire may give up a stream it framed, take +/// one another stack gave up, both, or neither (ws takes an upgrade and never gives one up). +fn halves( + r: &mut Recorder, + want: &mut Vec<(String, Want)>, + p: &Plugin, + k: &serde_json::Value, + token: u64, +) { + let mut tight = Sink::tight(); + match &k["detach"] { + serde_json::Value::String(v) if v == "refused" => { + r.line("detach", 1, || { + once(p, token, Op::Detach, &mut tight).line() + }); + want.push(("detach".into(), Want::Starts("Refused "))); + } + d => { + let ingest = text(&d["ingest"], "detach.ingest"); + let detached = text(&d["detached"], "detach.detached"); + r.line("ingest before upgrade", 1, || { + once(p, token, Op::Ingest(&ingest, false), &mut tight).line() + }); + want.push(("ingest before upgrade".into(), Want::Starts("Ready "))); + r.line("detach", sinkfuls(detached.len(), TIGHT_FRAME), || { + pump(p, token, Op::Detach, &mut tight).line() + }); + want.push(( + "detach".into(), + Want::Is(yielded_line(&[], &[], &detached, YIELD_ENDED, false)), + )); + } } - // 0: a closed instance answers FAULT without a crossing. - r.line("begin after close", 0, || { - once(&p, 0, Op::Begin(SIDE_DIAL), &mut Sink::ample()).line() + r.line("finish upgrade", 1, || { + once(p, token, Op::Finish, &mut tight).line() }); - want.push(("begin after close".into(), Want::Starts("Fault "))); - - let fold = r.fold(); - contract(&fold, &want); - fold + want.push(("finish upgrade".into(), Want::Starts("Ready "))); + match &k["adopt"] { + serde_json::Value::String(v) if v == "refused" => { + r.line("adopt", 1, || { + once(p, 0, Op::Adopt(b"conformance"), &mut tight).line() + }); + want.push(("adopt".into(), Want::Starts("Refused "))); + } + a => { + let leftover = text(&a["leftover"], "adopt.leftover"); + let frames = texts(&a["frames"], "adopt.frames"); + let wire = opt_text(&a["wire"], "adopt.wire"); + let crossings = sinkfuls(wire.len(), TIGHT_WIRE).max(frame_sinkfuls(&frames)); + let adopted = r.step("adopt", crossings, || { + let y = pump(p, 0, Op::Adopt(&leftover), &mut tight); + (y.line(), y.framing) + }); + want.push(( + "adopt".into(), + Want::Is(yielded_line(&wire, &frames, &[], 0, true)), + )); + r.line("finish adopted", 1, || { + once(p, adopted, Op::Finish, &mut tight).line() + }); + want.push(("finish adopted".into(), Want::Starts("Ready "))); + } + } } /// THE KIND'S CONTRACT over the fold, so two equal folds of failures prove nothing: every step's From de34fe4baf6395f25e8fca344af3e3ca7513cdc5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:48:25 -0700 Subject: [PATCH 33/82] conformance (transport script): the emit's YIELD_MORE re-call completes no frame, as the connector's re-call does (an end_of_frame re-call made a message-per-call framer write an empty message each time) --- crates/plugin-loader/src/conformance/transport.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/plugin-loader/src/conformance/transport.rs b/crates/plugin-loader/src/conformance/transport.rs index 6ee85d43d8..ec2b3b5acd 100644 --- a/crates/plugin-loader/src/conformance/transport.rs +++ b/crates/plugin-loader/src/conformance/transport.rs @@ -279,7 +279,7 @@ impl Yielded { enum Op<'a> { Begin(u32, &'a [u8]), Ingest(&'a [u8], bool), - Emit(&'a [u8]), + Emit(&'a [u8], bool), Refuse(&'a [u8]), Encode(&'a [u8], &'a [Field]), Adopt(&'a [u8]), @@ -294,7 +294,8 @@ impl Op<'_> { fn again(self) -> Self { match self { Op::Ingest(_, end) => Op::Ingest(&[], end), - Op::Emit(_) => Op::Emit(&[]), + // The host's re-call carries no bytes and completes no frame (as the connector's does). + Op::Emit(..) => Op::Emit(&[], false), Op::Refuse(_) => Op::Refuse(&[]), Op::Adopt(_) | Op::Begin(..) => Op::Timer, other => other, @@ -327,10 +328,10 @@ fn cross(p: &Plugin, framing: u64, op: Op<'_>, sink: &mut Sink) -> (C (framing, bytes.as_ptr(), bytes.len(), u32::from(end), raw); go(p, slot::INGEST, i) } - Op::Emit(bytes) => { + Op::Emit(bytes, end) => { let mut i: EmitIn = input(); (i.framing, i.bytes, i.len, i.end_of_frame, i.sink) = - (framing, bytes.as_ptr(), bytes.len(), 1, raw); + (framing, bytes.as_ptr(), bytes.len(), u32::from(end), raw); go(p, slot::EMIT, i) } Op::Refuse(bytes) => { @@ -664,7 +665,7 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { )); } r.line("emit", sinkfuls(wire.len(), TIGHT_WIRE), || { - pump(&p, token, Op::Emit(&emit), &mut tight).line() + pump(&p, token, Op::Emit(&emit, true), &mut tight).line() }); want.push(( "emit".into(), From 31c8e6df2ea0d6bd1bd8550f23e28745a118454e Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:50:50 -0700 Subject: [PATCH 34/82] conformance (transport script): a dial's far bytes may close it (dial.ingest_wire: the close answered, the frames end there), and a refusal may close its connection (refuse.ends); the ws door's inputs run green both ways under these --- .../src/conformance/transport.rs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/plugin-loader/src/conformance/transport.rs b/crates/plugin-loader/src/conformance/transport.rs index ec2b3b5acd..f084a50e84 100644 --- a/crates/plugin-loader/src/conformance/transport.rs +++ b/crates/plugin-loader/src/conformance/transport.rs @@ -26,10 +26,14 @@ //! (ws's 101), read before the first frame */ //! "emit": "", "wire": "", //! "ingest": "", "frames": ["", ...], +//! "ingest_wire": "", /* optional: the far side closed in them +//! and the close is answered (ws); the +//! frames end there */ //! "finish": "" }, /* optional, default "" (ws's close) */ //! "encode": [{ "body": "", "fields": [["", ""], ...], //! "wire": "" | null /* FAILED */ }, ...], //! "refuse": { "bytes": "", "wire": "", +//! "ends": true, /* optional: it closes */ //! "opening": "", /* optional */ //! "answer": "" }, /* optional */ //! "handoff": "refused" | { @@ -671,12 +675,20 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { "emit".into(), Want::Is(yielded_line(&wire, &[], &[], 0, false)), )); - r.line("ingest", frame_sinkfuls(&frames), || { + // What reading them writes back (a ws close answered), absent = nothing. + let answered = opt_text(&dial["ingest_wire"], "dial.ingest_wire"); + let ingest_calls = sinkfuls(answered.len(), TIGHT_WIRE).max(frame_sinkfuls(&frames)); + r.line("ingest", ingest_calls, || { pump(&p, token, Op::Ingest(&ingest, false), &mut tight).line() }); + let ingest_flags = if dial["ingest_wire"].is_null() { + 0 + } else { + YIELD_ENDED + }; want.push(( "ingest".into(), - Want::Is(yielded_line(&[], &frames, &[], 0, false)), + Want::Is(yielded_line(&answered, &frames, &[], ingest_flags, false)), )); r.line("ingest end", 1, || { pump(&p, token, Op::Ingest(&[], true), &mut tight).line() @@ -771,14 +783,22 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { r.line("refuse", sinkfuls(wire.len(), TIGHT_WIRE), || { pump(&p, token, Op::Refuse(&bytes), &mut tight).line() }); + // A wire whose refusal closes its connection (ws: the refusal message, then its close) states + // `"ends": true`; its connection is over, and a timer on it fails. + let ends = refuse["ends"].as_bool().unwrap_or(false); + let ended = if ends { YIELD_ENDED } else { 0 }; want.push(( "refuse".into(), - Want::Is(yielded_line(&wire, &[], &[], 0, false)), + Want::Is(yielded_line(&wire, &[], &[], ended, false)), )); r.line("timer", 1, || pump(&p, token, Op::Timer, &mut tight).line()); want.push(( "timer".into(), - Want::Is(yielded_line(&[], &[], &[], 0, false)), + if ends { + Want::Starts("Failed ") + } else { + Want::Is(yielded_line(&[], &[], &[], 0, false)) + }, )); r.line("finish refused", 1, || { once(&p, token, Op::Finish, &mut tight).line() From 2d9c3cc19d8ada39aba720bd166f7f0222abadfe Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:02:50 -0700 Subject: [PATCH 35/82] busbar-transport-ws pinned at acf3011931 (GetBusbar/busbar-transport-ws#11, CI green): the published conformance suite runs over the ws door, its scripted dial seeded by the test-only test-seeded feature --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0b077ea7c..40316d56d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,7 +1118,7 @@ dependencies = [ [[package]] name = "busbar-transport-ws" version = "1.6.0" -source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=9050227f89e15c12633a9fe27a5f5ea4ab9502f0#9050227f89e15c12633a9fe27a5f5ea4ab9502f0" +source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=acf30119313d5778345c1b8dcb67d4dec7256b03#acf30119313d5778345c1b8dcb67d4dec7256b03" dependencies = [ "busbar-contract", "futures", diff --git a/Cargo.toml b/Cargo.toml index 7193b6145b..44297b4c0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ busbar-transport-http = { git = "https://github.com/GetBusbar/busbar-transport-h busbar-transport-grpc = { git = "https://github.com/GetBusbar/busbar-transport-grpc", rev = "5607182ed5d28da20a6e1699aad512909d24142e" } # THE WS TRANSPORT: the websocket framer door (kind: transport), pulled at a pinned rev; the logic # crate the root links (behind plane-streaming) and the ws-conformance subject drives. -busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "9050227f89e15c12633a9fe27a5f5ea4ab9502f0" } +busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "acf30119313d5778345c1b8dcb67d4dec7256b03" } busbar-export-webhook-plugin = { git = "https://github.com/GetBusbar/busbar-export-webhook", rev = "3ad5a711aad433f2015a980cf3a792afa5fd3a7a" } # THE TCP TRANSPORT: the identity framer door (kind: transport), one pin for the logic crate the root # links and the cdylib crate the loader's both-ways proof builds. Same repo, same rev, one resolved copy. From 30fe659d62a9c28f9d5704c646e5b1245beaae8a Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:11:45 -0700 Subject: [PATCH 36/82] busbar-plane-streaming door: a one-request door's answer is whole and its head states its length (content-length after its own fields), so SEAM-4h's serve path answers the caller under it, as 1.6.0-pre did (streams|mint|client-secrets, streams|sdp|calls: the caller's head and body now match) --- crates/busbar-plane-streaming/src/door.rs | 9 +++++++-- crates/busbar-plane-streaming/src/driven.rs | 2 ++ crates/busbar-plane-streaming/tests/conformance.rs | 11 +++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 56527cc6bc..777ed5e678 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -77,7 +77,7 @@ use busbar_contract::plane::{PER_SESSION, TOKEN_FAMILY}; use crate::claims::{Dialect, HTTP_TRANSPORT, WS_TRANSPORT}; use crate::config::StreamsCfg; -use crate::driven::{Door, Steps}; +use crate::driven::{Door, Steps, FIELD_CONTENT_LENGTH}; use crate::meta; use crate::provider::{GEMINI_LIVE, OPENAI_REALTIME}; use crate::request_unit::{self, Answer, Piece, RequestUnit}; @@ -869,7 +869,12 @@ fn answer_piece( Answer::ToCaller(reply) => { let (mut fields, units, mut arena) = (input.fields_buf(), input.units_buf(), input.arena_buf()); - push_fields(&mut fields, &mut arena, &reply.fields); + // A one-request door's answer is WHOLE: its head states its length, so the caller is + // answered under it, never chunked (1.6.0-pre's bytes; the serve path sends a whole + // answer under the length its head states). + let mut head = reply.fields.clone(); + head.push((FIELD_CONTENT_LENGTH, reply.body.len().to_string())); + push_fields(&mut fields, &mut arena, &head); if piece::settle(out, &fields, &units, &arena) { held.short = Some(Answer::ToCaller(reply)); return (Outcome::Failed, false); diff --git a/crates/busbar-plane-streaming/src/driven.rs b/crates/busbar-plane-streaming/src/driven.rs index a27c210277..b46ac1d8fd 100644 --- a/crates/busbar-plane-streaming/src/driven.rs +++ b/crates/busbar-plane-streaming/src/driven.rs @@ -26,6 +26,8 @@ pub const JSON_CONTENT_TYPE: &str = "application/json"; pub const FIELD_CONTENT_TYPE: &str = "content-type"; /// The head field naming where a created call lives. pub const FIELD_LOCATION: &str = "location"; +/// The head field a whole answer states its length in. +pub const FIELD_CONTENT_LENGTH: &str = "content-length"; /// The door a claim is, by its index in the snapshot's claims (`crate::door::ROUTES`). #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index f82d7ed4ab..9986453125 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -559,8 +559,9 @@ fn unit_expected() -> Vec { format!("mint caller Ready emitted= more=0 far=false done=false status=0 {none}"), format!( "mint far_end Ready emitted={} more=0 far=false done=true status=200 verb= target= \ - fields=[\"content-type=application/json\"]", - String::from_utf8_lossy(&minted.body) + fields=[\"content-type=application/json\", \"content-length={}\"]", + String::from_utf8_lossy(&minted.body), + minted.body.len() ), "sdp attempt Ready emitted= more=0 far=true done=false status=0 verb=POST \ target=/v1/realtime/calls fields=[\"content-type=application/sdp\"]" @@ -571,8 +572,10 @@ fn unit_expected() -> Vec { ), format!( "sdp far_end Ready emitted={} more=0 far=false done=true status=201 verb= target= \ - fields=[\"content-type=application/sdp\", \"location={LOCATION}\"]", - String::from_utf8_lossy(SDP_ANSWER) + fields=[\"content-type=application/sdp\", \"location={LOCATION}\", \ + \"content-length={}\"]", + String::from_utf8_lossy(SDP_ANSWER), + SDP_ANSWER.len() ), ]; t.push(format!( From 56fc984c0cd4314b5648e2421f185dc43f49dcb1 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 19:35:42 -0700 Subject: [PATCH 37/82] busbar-transport-ws pinned at a6787c0245 (GetBusbar/busbar-transport-ws#12): ws is door-only and single-entry, its wire built from the door (transport transport-door connector-door, SEAM-4f/4n), and a failed stream ends PIECE_STREAM_FAILED; ws_composed_battery 6/6 on it --- Cargo.lock | 5 +---- Cargo.toml | 2 +- crates/busbar/Cargo.toml | 8 +++++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40316d56d6..7ca97a11ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,14 +1118,11 @@ dependencies = [ [[package]] name = "busbar-transport-ws" version = "1.6.0" -source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=acf30119313d5778345c1b8dcb67d4dec7256b03#acf30119313d5778345c1b8dcb67d4dec7256b03" +source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=a6787c0245993b0a9e366df4652b1e829db1b6cf#a6787c0245993b0a9e366df4652b1e829db1b6cf" dependencies = [ "busbar-contract", - "futures", "serde_json", - "tokio", "tokio-tungstenite", - "tokio-util", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 44297b4c0b..7e36c86123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ busbar-transport-http = { git = "https://github.com/GetBusbar/busbar-transport-h busbar-transport-grpc = { git = "https://github.com/GetBusbar/busbar-transport-grpc", rev = "5607182ed5d28da20a6e1699aad512909d24142e" } # THE WS TRANSPORT: the websocket framer door (kind: transport), pulled at a pinned rev; the logic # crate the root links (behind plane-streaming) and the ws-conformance subject drives. -busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "acf30119313d5778345c1b8dcb67d4dec7256b03" } +busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "a6787c0245993b0a9e366df4652b1e829db1b6cf" } busbar-export-webhook-plugin = { git = "https://github.com/GetBusbar/busbar-export-webhook", rev = "3ad5a711aad433f2015a980cf3a792afa5fd3a7a" } # THE TCP TRANSPORT: the identity framer door (kind: transport), one pin for the logic crate the root # links and the cdylib crate the loader's both-ways proof builds. Same repo, same rev, one resolved copy. diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index e0ed648fc9..a3b6946d58 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -436,9 +436,11 @@ transport-tcp = "transport transport-door" # sink's outbound `http` need is served compiled-in; the wire the registry folds keeps its build. transport-http = "transport connector-door" transport-sse = "transport" -# The ws framer's door, opened on the same one Connector (ARCHITECT Q-L5B-WS-DOOR 2026-10-03, -# packaging Part 0, as the http door): the streaming door's inbound and outbound ws needs bind. -transport-ws = "transport connector-door" +# The ws framer's door, single-entry and door-only as stdio's (ARCHITECT ruling (A); Q-L5B-WS-DOOR +# ruled 2026-10-04): the wire is built from the door (`transport-door`, a framer composed over the +# layer the registry folds under it, SEAM-4f/4n) and the door is opened on the one Connector +# (`connector-door`, packaging Part 0, as the http door), so the streaming door's ws needs bind. +transport-ws = "transport transport-door connector-door" transport-grpc = "transport transport-door" transport-stdio = "transport transport-door" plane-decisions-door = "plane-door" From e861cb2ec57482d13cb7bf3d3a047ec8f15b5b68 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:45:32 -0700 Subject: [PATCH 38/82] FLIP-STREAMING: plane-streaming is the streaming door's plane-door row and the fold switch streaming-on-driver is deleted (BUSBAR-1.6.0.md Part 3 section 12, "The switch"); busbar-voice is no longer linked (lane-dg-streaming 2203162532, with the test half of b53e16e821) plane-streaming = [dep:busbar-plane-streaming, transport-ws, busbar-kernel/duplex-ws]; the linked, linked-axes and linked-entry rows move to plane-streaming (plane-door, busbar_plane_streaming::door); the busbar-voice edge, served-witness row, dev-dep and openapi-schema forward go; the linked-swaps table is empty and leaves (build.rs reads none). Tests: registry pins without the voice row's linked claims (ws_linked reads the transport row), law7 plane-door row, the installed-plane axis 4 -> 3 and its streaming allowlist rows, billing legs and owned-section doctrine rows; the voice crate stays in the workspace (its doctrine crate row kept). --- Cargo.lock | 1 - crates/busbar/Cargo.toml | 58 +++------ crates/busbar/build.rs | 7 +- .../root/tests/fixtures/claims_per_plane.txt | 3 +- .../src/root/tests/fixtures/sealed_order.txt | 7 +- crates/busbar/src/root/tests/registry.rs | 114 ++++++++---------- .../tests/fixtures/billing_plane_legs.txt | 4 +- .../tests/fixtures/law7_plane_doors.txt | 2 +- .../busbar/tests/fixtures/plane_doctrine.txt | 9 +- crates/busbar/tests/plane_isomorphism.rs | 5 +- qa/plane-hook-isomorphism.allow | 88 -------------- 11 files changed, 89 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc50cd208b..a5a6492f0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -350,7 +350,6 @@ dependencies = [ "busbar-transport-tcp", "busbar-transport-tcp-plugin", "busbar-transport-ws", - "busbar-voice", "bytes", "core_affinity", "criterion", diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index a3b6946d58..9ef62ffee2 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -69,13 +69,6 @@ busbar-mcp = { path = "../busbar-mcp", optional = true } # — nothing in `main.rs` names it and `plane-a2a` below is not in `default`, so this edge is inert # until the plane/codec code that a later step moves actually lands here. busbar-a2a = { path = "../busbar-a2a", optional = true } -# THE VOICE PLANE-KIND CRATE (Plane 4), the analogue of busbar-a2a above. Optional behind -# `plane-streaming`, now IN `default` (voice ships default-ON + deletable, exactly like plane-mcp/ -# plane-a2a). The LLM money path is unchanged (busbar-core's default carries no voice/duplex-ws -# edge); the shipped binary gains the voice plane + its WS transport, and `--no-default` proves the -# whole edge disappears with the crate (strong-form deletable). The `busbar-voice/runtime` edge is -# carried by the feature (below), not here. -busbar-voice = { path = "../busbar-voice", optional = true } # Boot-time plugin listing (`--plugins`) reads the registry the loader builds. busbar-plugin-loader = { path = "../plugin-loader" } @@ -98,11 +91,12 @@ busbar-kernel-wal = { path = "../busbar-kernel-wal" } busbar-kernel-audit = { path = "../busbar-kernel-audit" } # The pure planes reach the boot seal through their host's linked entry (`PLANE` + `CLAIMS` on the # `claims` axis), so the root names none of them. The streaming plane's edge is the crate the -# `streaming-on-driver` switch's `plane-door` row names. -busbar-plane-streaming = { path = "../busbar-plane-streaming" } +# `plane-streaming` switch's `plane-door` row names: its memory-ABI door, served through the loader's +# one load like every dropped-in plane door (FLIP-STREAMING). +busbar-plane-streaming = { path = "../busbar-plane-streaming", optional = true } # THE DECISIONS PLANE (jev), #48's fifth — and the one plane crate that is BOTH halves at once. Its # four siblings above are PURE planes whose registry declaration is carried by a separate impure -# host crate (`busbar-llm`/`busbar-mcp`/`busbar-a2a`/`busbar-voice`, the optional protocol crates +# host crate (`busbar-llm`/`busbar-mcp`/`busbar-a2a`, the optional protocol crates # declared further up); jev has no host, by the signed design, so this single crate is the plane AND # the section. That is why this line is OPTIONAL when the four above are not: it follows the # convention of the thing it is — the crate whose `PLANE_DECL` is installed — and `plane-decisions` @@ -247,16 +241,16 @@ plane-mcp = ["dep:busbar-mcp", "busbar-kernel/plane-mcp", "busbar-core-admin/pla # section when off, so `--no-default-features` drops the `busbar-a2a` edge and compiles the whole A2A # plane out while core still routes LLM + MCP (the plane-split deletion test). plane-a2a = ["dep:busbar-a2a", "busbar-kernel/plane-a2a", "busbar-core-admin/plane-a2a"] -# THE VOICE PLANE, the busbar-voice crate, behind this switch. The analogue of `plane-a2a` — one -# switch for the crate edge + the core/substrate forwards — and, like it, IN `default` (voice ships -# default-ON + deletable; `--no-default-features` compiles it out). `dep:busbar-voice` pulls the crate; `busbar-voice?/runtime` turns on its live-session -# runtime (the same feature it would be under `busbar-voice/runtime`, since `dep:busbar-voice` here -# enables the crate). The `?/` spelling is deliberate: it makes the forward an OPTIONAL-dep feature -# reference, so the strong-form deletion gate (scripts/plane-delete-test.sh) strips it with the same -# rule it strips `busbar-voice?/openapi-schema`, leaving voice byte-for-byte removable. Compiling the -# runtime here gives the `build_runtime` body; the skeleton build leaves `build_runtime: None`. -plane-streaming = ["dep:busbar-voice", "transport-ws", "busbar-voice?/runtime", "busbar-kernel/duplex-ws"] -# THE WS WIRE, the one optional transport: the voice plane's edge and nothing else's, so `plane-streaming` +# THE STREAMING PLANE, through its memory-ABI door (FLIP-STREAMING; BUSBAR-1.6.0.md Part 3 section 12, +# "The switch": the fold's development-only switch is deleted when the fold completes, and the +# plane's one switch carries its door). IN `default`; `--no-default-features` compiles it out. On, the +# plane's door (`busbar_plane_streaming::door::door`) is a `plane-door` row: the root hands it to the +# loader beside every dropped-in plane door and binds it through the one load on the one dispatcher, +# so the compiled-in plane is called through the same table as its dropped-in cdylib (#2, #30; THE +# DESIGN section 11.4), and the serve row hands it its arrivals. It carries the ws wire its live +# sessions arrive over and leave on. +plane-streaming = ["dep:busbar-plane-streaming", "transport-ws", "busbar-kernel/duplex-ws"] +# THE WS WIRE, the one optional transport: the streaming plane's edge and nothing else's, so `plane-streaming` # carries it and `--no-default-features` drops the crate edge and its linked row together. transport-ws = ["dep:busbar-transport-ws"] # THE TCP WIRE'S LINKED ROW, DEFAULT ON (#3: a transport is swappable, compiled in OR dropped in). Off, @@ -298,12 +292,6 @@ plane-decisions-door = ["plane-decisions"] # (#2, #30; THE DESIGN section 11.4). It carries `proto-llm`, whose row stays linked and answers # every arrival the serve path does not hand the door. llm-on-driver = ["proto-llm", "dep:busbar-plane-llm"] -# The next plane's development-only switch, the same shape as the one above (BUSBAR-1.6.0.md Part 3 -# section 12, "The switch"). TRANSITIONAL: deleted when its fold completes; never in `default`. On, -# the plane's memory-ABI door is a `plane-door` row bound through the loader's one load beside every -# dropped-in plane door (#2, #30; THE DESIGN section 11.4), and the legacy row it carries keeps -# serving every route until the serve path hands the door the arrivals it takes. -streaming-on-driver = ["plane-streaming"] # THE REQUEST-LOG FILE SINK, DEFAULT ON: the `busbar-export-file` crate on the export axis. Off, the # module resolves only when a signed tarball of the sink is dropped into `plugins/`. export-file = ["dep:busbar-export-file"] @@ -346,7 +334,7 @@ auth-admin-tokens = ["dep:busbar-auth-admin-tokens-plugin"] # binds only when a signed tarball of the module is dropped into `plugins/`. auth-header = ["dep:busbar-auth-header"] hooks-ranking = ["dep:busbar-hooks-ranking", "busbar-kernel/hooks-ranking", "busbar-core-admin/hooks-ranking"] -openapi-schema = ["busbar-kernel/openapi-schema", "busbar-core-admin/openapi-schema", "busbar-llm?/openapi-schema", "busbar-mcp?/openapi-schema", "busbar-a2a?/openapi-schema", "busbar-voice?/openapi-schema"] +openapi-schema = ["busbar-kernel/openapi-schema", "busbar-core-admin/openapi-schema", "busbar-llm?/openapi-schema", "busbar-mcp?/openapi-schema", "busbar-a2a?/openapi-schema"] loom-model = ["busbar-kernel/loom-model"] # ── THE LINKED PLUGINS (K1, #2 rule (1)) ───────────────────────────────────────────────────────────── @@ -368,7 +356,6 @@ loom-model = ["busbar-kernel/loom-model"] proto-llm = "busbar-llm" plane-mcp = "busbar-mcp" plane-a2a = "busbar-a2a" -plane-streaming = "busbar-voice" plane-decisions = "busbar-plane-decisions" export-file = "busbar-export-file" export-webhook = "busbar-export-webhook" @@ -398,7 +385,7 @@ transport-stdio = "busbar-transport-stdio" # loader's one load beside the dropped-in plane doors. plane-decisions-door = "busbar-plane-decisions" llm-on-driver = "busbar-plane-llm" -streaming-on-driver = "busbar-plane-streaming" +plane-streaming = "busbar-plane-streaming" # What each linked feature's crate registers (keyed by the feature, as the linked row is), axis by axis: `plane` (PLANE_DECLARATION + PLANE_HOOKS, joined # kernel-side), `protocols`, `path-ingress`, `body-ingress`, `protocol-seams`, `diagnostics`, @@ -419,7 +406,6 @@ streaming-on-driver = "busbar-plane-streaming" proto-llm = "plane claims protocols path-ingress body-ingress protocol-seams diagnostics on-host cli-help node gauntlet-one-shot" plane-mcp = "plane claims protocols diagnostics egress stdio-serve cli-help gauntlet-one-shot" plane-a2a = "plane claims diagnostics egress plane-sections admin-envelope gauntlet-one-shot" -plane-streaming = "plane claims diagnostics ws-arrivals compose gauntlet-session" plane-decisions = "plane claims" export-file = "exports" export-webhook = "exports" @@ -445,7 +431,7 @@ transport-grpc = "transport transport-door" transport-stdio = "transport transport-door" plane-decisions-door = "plane-door" llm-on-driver = "plane-door" -streaming-on-driver = "plane-door" +plane-streaming = "plane-door" # A linked crate whose entry module is not `::linked`: the decisions plane is a PURE plane # crate whose manifest names `busbar-contract` and nothing else (#40), so its kernel-typed hooks are @@ -466,7 +452,7 @@ llm-on-driver = "busbar_plane_llm::plane_door" # A memory-ABI export sink's door is its logic crate's `door` module's (`plugin_door!`). busbar-export-prometheus = "busbar_export_prometheus::door" busbar-export-otlp = "busbar_export_otlp::door" -streaming-on-driver = "busbar_plane_streaming::door" +plane-streaming = "busbar_plane_streaming::door" # The key an `auths` row answers on the auth axis — the name its signed tarball's manifest states. [package.metadata.busbar.linked-name] @@ -481,16 +467,9 @@ auth-header = "busbar-auth-header" # `testkit::SERVED` table they drive through the kernel-loop runner `gauntlet_install::install()` # registers for that plane's key. Test builds only (`build.rs` emits `$OUT_DIR/served_witness.rs`, which # `root/tests/gauntlet_kernel.rs` includes); each crate is linked with `test-support` by the dev-deps. -# A fold's development-only switch SWAPS its plane's legacy row out (`linked-swaps`, read by build.rs): -# while `streaming-on-driver` is on, the `plane-streaming` row (busbar-voice) is not linked, so the -# streaming door, folded and composed on the serve path, is the plane's one claimant. -[package.metadata.busbar.linked-swaps] -plane-streaming = "streaming-on-driver" - [package.metadata.busbar.served-witness] plane-mcp = "busbar-mcp" plane-a2a = "busbar-a2a" -plane-streaming = "busbar-voice" [package.metadata.busbar.root-units] plane-decisions = "plane_decisions" @@ -515,7 +494,6 @@ busbar-llm = { path = "../busbar-llm", features = ["test-support"] } # The served planes' test kits: `testkit::SERVED` (their served-leg witnesses) is `test-support` only. busbar-mcp = { path = "../busbar-mcp", features = ["test-support"] } busbar-a2a = { path = "../busbar-a2a", features = ["test-support"] } -busbar-voice = { path = "../busbar-voice", features = ["test-support"] } # `root::door_steps` tests: a member bound under an OAuth grant, its credential minted by the real # token-style auth plugin through its own need (TODO row 22). busbar-auth-oauth = { path = "../busbar-auth-oauth" } diff --git a/crates/busbar/build.rs b/crates/busbar/build.rs index 3555e0fabc..eb0be124da 100644 --- a/crates/busbar/build.rs +++ b/crates/busbar/build.rs @@ -107,7 +107,12 @@ fn main() { }; // A fold's development-only switch SWAPS its plane's legacy row out (`linked-swaps`): the row is // not linked while the switch is on, so the folded door is the plane's one claimant. - let swaps = metadata_map(&manifest, LINKED_SWAPS); + // A build with no switch left has no table: nothing is swapped. + let swaps = if manifest.contains(&format!("[{LINKED_SWAPS}]")) { + metadata_map(&manifest, LINKED_SWAPS) + } else { + Vec::new() + }; let enabled = |feature: &str| swapped_in(&swaps, &on, feature); let (source, cfgs) = linked_source(&manifest, &enabled); let out = std::path::PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("linked.rs"); diff --git a/crates/busbar/src/root/tests/fixtures/claims_per_plane.txt b/crates/busbar/src/root/tests/fixtures/claims_per_plane.txt index 882e569fb3..669943965c 100644 --- a/crates/busbar/src/root/tests/fixtures/claims_per_plane.txt +++ b/crates/busbar/src/root/tests/fixtures/claims_per_plane.txt @@ -1,9 +1,8 @@ # THE CLAIMS EACH PLANE OF THE SHIPPED COMPOSITION DECLARES — ` `, one row per -# plane. Read by root/tests/registry.rs (`the_planes_declare_fifty_one_claims`). A plane that gains or +# plane. Read by root/tests/registry.rs (`the_planes_declare_forty_six_claims`). A plane that gains or # loses a claim has to say so here. llm 25 mcp 4 a2a 14 -streaming 5 decision 2 admin 1 diff --git a/crates/busbar/src/root/tests/fixtures/sealed_order.txt b/crates/busbar/src/root/tests/fixtures/sealed_order.txt index 93577f0c6a..0590c85efe 100644 --- a/crates/busbar/src/root/tests/fixtures/sealed_order.txt +++ b/crates/busbar/src/root/tests/fixtures/sealed_order.txt @@ -1,6 +1,6 @@ # THE SEALED CLAIM ORDER of the shipped composition, most specific first — one row per claim: # ` `. Read by root/tests/registry.rs -# (`the_sealed_order_of_the_fifty_one_claims_is_pinned`). A change here is a change to which plane answers +# (`the_sealed_order_of_the_forty_six_claims_is_pinned`). A change here is a change to which plane answers # which request, and has to be made on purpose, with the new order visible in the same diff. mcp ExactPath("/.well-known/oauth-protected-resource/mcp") a2a ExactPath("/.well-known/oauth-protected-resource/a2a") @@ -25,25 +25,20 @@ a2a PathPattern([Lit("a2a"), Lit("agents"), Var]) llm PathPattern([Lit("v1"), Lit("models"), Tail]) llm PathPattern([Lit("v1beta"), Lit("models"), Tail]) a2a PathPattern([Lit("lf.a2a.v1.A2AService"), Var]) -streaming PrefixOneLevel("/twilio") llm HeaderPrefix("authorization", "AWS4-HMAC-SHA256") llm HeaderPresent("anthropic-version") llm HeaderPresent("anthropic-beta") llm HeaderPresent("x-goog-api-key") llm HeaderPresent("x-api-key") -streaming PathSuffix("/v1/audio/transcriptions") llm PathSuffix("/v1/audio/translations") llm PathContains(":streamGenerateContent") llm PathSuffix("/v1/chat/completions") llm PathContains(":batchEmbedContents") -streaming PathContains("BidiGenerateContent") -streaming PathSuffix("/v1/audio/speech") llm PathContains(":generateContent") llm PathSuffix("/v1/moderations") llm PathSuffix("/v1/embeddings") llm PathSuffix("/v1/responses") llm PathContains(":embedContent") -streaming PathSuffix("/v1/realtime") llm PathContains("/v1/messages") llm PathContains("/v1/images/") llm PathSuffix("/v2/rerank") diff --git a/crates/busbar/src/root/tests/registry.rs b/crates/busbar/src/root/tests/registry.rs index 5f20b50403..f5793bd173 100644 --- a/crates/busbar/src/root/tests/registry.rs +++ b/crates/busbar/src/root/tests/registry.rs @@ -35,7 +35,7 @@ fn linked_registry() -> Registry { /// The shipped transport fold (` ` rows, in build order), as data. const TRANSPORT_FOLD: &str = include_str!("fixtures/transport_fold.txt"); -/// The sealed walk over the fifty-one declared claims, most specific first. The decisions plane's two +/// The sealed walk over the forty-six declared claims, most specific first. The decisions plane's two /// exact paths (item 251) sit among the other exact paths, ahead of every pattern that could also /// describe them. /// @@ -50,33 +50,14 @@ const SEALED_ORDER: &str = include_str!("fixtures/sealed_order.txt"); #[cfg(linked_every_plane)] const CLAIMS_PER_PLANE: &str = include_str!("fixtures/claims_per_plane.txt"); -/// Whether this build links a plane on the kernel's SESSION loop (the `gauntlet-session` axis of the -/// linked table) — and with it its WS transport, its registry row and its five claims. Every pinned -/// number below is a statement about ONE composition, and the shipped one (the session plane linked, -/// since its row's feature is in `default`) is the one they are pinned against; a build that -/// compiled it out is a different composition, not a smaller one. Read off `LINKED`, so this source -/// names no plane. -fn session_linked() -> bool { - !crate::LINKED.gauntlet_session.is_empty() || a_swapped_row_is_served_by_its_door() -} - -/// A fold's development-only switch is on and has swapped its plane's legacy row out -/// (`[package.metadata.busbar.linked-swaps]`): the plane is served by its folded door, and the -/// wire its sessions ride stays linked. Read off the manifest, so this names no plane. -fn a_swapped_row_is_served_by_its_door() -> bool { - let on: Vec<&str> = env!("BUSBAR_ENABLED_FEATURES").split_whitespace().collect(); - let mut in_table = false; - include_str!("../../../Cargo.toml").lines().any(|line| { - let line = line.split('#').next().unwrap_or("").trim(); - if line.starts_with('[') { - in_table = line == "[package.metadata.busbar.linked-swaps]"; - return false; - } - in_table - && line - .split_once('=') - .is_some_and(|(_, switch)| on.contains(&switch.trim().trim_matches('"'))) - }) +/// Whether this build links the WS framer (its transport row, on the `transport` and +/// `connector-door` axes): the sixth wire, composed over http. Since FLIP-STREAMING no plane rides +/// the kernel's SESSION loop: the streaming plane is a door plane (`plane-door` axis) whose claims +/// are its snapshot's and are mounted by the serve fold, not declared in the linked claims table, +/// so the ws wire is the transport row's alone. Every pinned number below is a statement about ONE +/// composition, the shipped one. +fn ws_linked() -> bool { + crate::LINKED.transports.iter().any(|t| t.key == "ws") } /// The non-comment rows of a fixture file, in order. Read only by the shipped-composition pins, @@ -104,7 +85,7 @@ fn six_transports_and_five_planes_register() { let registry = linked_registry(); assert_eq!( registry.count(PluginKind::Transport), - if session_linked() { 6 } else { 5 } + if ws_linked() { 6 } else { 5 } ); // Every linked plane and the core one: a plane this build does not link registers nothing. assert_eq!( @@ -147,25 +128,22 @@ fn six_transports_and_five_planes_register() { .is_some(), "the decision plane is linked and not registered" ); - // The voice plane and its transport are present exactly together: neither is a thing this - // root registers without the other. + // The ws wire is registered exactly when its transport row is linked. assert_eq!( registry.resolve(PluginKind::Transport, "ws").is_some(), - session_linked() + ws_linked() ); - // And its plane: the claimed-set check above resolves every claimed key and ties the claimed - // count to the registered count, and the plane count moves by exactly one with the session - // plane — so its row is registered exactly when it is linked. } /// The measured claim total, one row per plane. It is pinned as a number because the number is /// what a reader checks the design's own table against; a plane that gains or loses a claim /// should have to say so here. -// Pinned against the SHIPPED composition (voice on). Compiled out with the voice plane -// because the numbers below are that composition's, not a subset of it. +// Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them +// because the numbers below are that composition's, not a subset of it. The streaming plane's claims +// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] -fn the_planes_declare_fifty_one_claims() { +fn the_planes_declare_forty_six_claims() { let claims = linked_claims(); let count = |plane: &str| claims.iter().filter(|c| c.plane == plane).count(); // One ` ` row per plane, pinned as fixture DATA so this source names none. @@ -176,7 +154,7 @@ fn the_planes_declare_fifty_one_claims() { (key.to_string(), n.parse().expect("a claim count")) }) .collect(); - assert_eq!(pinned.len(), 6, "six planes are pinned: {pinned:?}"); + assert_eq!(pinned.len(), 5, "five planes are pinned: {pinned:?}"); for (key, n) in &pinned { assert_eq!( count(key), @@ -195,7 +173,7 @@ fn the_planes_declare_fifty_one_claims() { claims.len(), "every claim belongs to a pinned plane" ); - assert_eq!(claims.len(), 51); + assert_eq!(claims.len(), 46); } /// The measured overlap, split the way the rule splits it. Both counts are pinned because both @@ -209,11 +187,15 @@ fn the_planes_declare_fifty_one_claims() { /// 65 without ever answering "disjoint" for a pair one arrival satisfies, and naming the audio /// surface one path at a time rather than as a prefix took it from 65 to 63. Joining the decision /// plane (item 251) added one more — its `/v1/models` against the llm plane's tail pattern — for 64. -// Pinned against the SHIPPED composition (voice on). Compiled out with the voice plane -// because the numbers below are that composition's, not a subset of it. +/// FLIP-STREAMING took the streaming plane's five claims out of the linked table (its door's +/// snapshot claims them, mounted by the serve fold): 100 cross-family and 64 same-family became +/// 90 and 42. +// Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them +// because the numbers below are that composition's, not a subset of it. The streaming plane's claims +// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] -fn one_hundred_and_sixty_four_cross_plane_pairs_overlap() { +fn one_hundred_and_thirty_two_cross_plane_pairs_overlap() { use busbar_kernel::grammar::family; let claims = linked_claims(); @@ -234,11 +216,11 @@ fn one_hundred_and_sixty_four_cross_plane_pairs_overlap() { // The decisions plane's two exact paths add ten cross-family pairs (a header claim can be true // of the same arrival) and one path-family pair: `/v1/models` inside the llm plane's // `v1/models/` pattern. - assert_eq!(cross_family, 100); - assert_eq!(same_family, 64); + assert_eq!(cross_family, 90); + assert_eq!(same_family, 42); } -/// What the 64 path-family overlaps that remain actually ARE, one class at a time. +/// What the 42 path-family overlaps that remain actually ARE, one class at a time. /// /// A count alone cannot say whether an overlap is a real shape or a gap in the reasoning, and /// that distinction is the whole reason to tighten a grammar rather than to relax a check. So @@ -253,8 +235,9 @@ fn one_hundred_and_sixty_four_cross_plane_pairs_overlap() { /// /// A pair that fits none of these would be the interesting one: a conservative answer with no /// account of itself. There is none, and the assertion is that there is none. -// Pinned against the SHIPPED composition (voice on). Compiled out with the voice plane -// because the numbers below are that composition's, not a subset of it. +// Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them +// because the numbers below are that composition's, not a subset of it. The streaming plane's claims +// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn every_remaining_path_overlap_is_a_shape_and_not_a_gap() { @@ -288,27 +271,30 @@ fn every_remaining_path_overlap_is_a_shape_and_not_a_gap() { } } } - assert_eq!(tail, 24); + assert_eq!(tail, 18); assert_eq!(variable, 24); - assert_eq!(fragments, 16); + // The fragment pairs were the streaming plane's audio suffixes against the llm plane's; its + // claims left this table with FLIP-STREAMING. + assert_eq!(fragments, 0); } -/// **The finding, answered.** Every one of those 164 overlaps is settled by the sealed order, +/// **The finding, answered.** Every one of those 132 overlaps is settled by the sealed order, /// and none of them is a refusal. /// /// The resolved count is pinned against the overlap count above, so the two cannot drift apart /// silently: a pair that stops being resolved has either stopped overlapping or become a tie, /// and each of those is a different thing to have to explain. The refusal list is pinned empty, /// which is the whole claim of this file — the declared set of planes seals. -// Pinned against the SHIPPED composition (voice on). Compiled out with the voice plane -// because the numbers below are that composition's, not a subset of it. +// Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them +// because the numbers below are that composition's, not a subset of it. The streaming plane's claims +// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn every_cross_plane_overlap_is_resolved_by_precedence_and_none_refuses() { let claims = linked_claims(); let sealed = seal_claims(&claims); - assert_eq!(sealed.resolved.len(), 164); + assert_eq!(sealed.resolved.len(), 132); assert!( sealed.refused.is_empty(), "the declared claims do not seal: {:?}", @@ -334,18 +320,19 @@ fn every_cross_plane_overlap_is_resolved_by_precedence_and_none_refuses() { } } -/// The sealed order of the fifty-one, written out. +/// The sealed order of the forty-six, written out. /// /// A snapshot, and deliberately a verbose one: the walk every arriving connection is matched /// against is the thing this file produces, and a change to it is a change to which plane /// answers which request. Each row is the plane and the selector, so a diff of this array reads /// as a routing change rather than as a permutation of opaque indices. A claim added, removed or /// respelled has to update it, on purpose, with the new order visible in the same diff. -// Pinned against the SHIPPED composition (voice on). Compiled out with the voice plane -// because the numbers below are that composition's, not a subset of it. +// Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them +// because the numbers below are that composition's, not a subset of it. The streaming plane's claims +// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] -fn the_sealed_order_of_the_fifty_one_claims_is_pinned() { +fn the_sealed_order_of_the_forty_six_claims_is_pinned() { let claims = linked_claims(); let sealed = seal_claims(&claims); let walk: Vec = sealed @@ -526,7 +513,7 @@ fn the_shipped_transport_stack_composes() { // The transport whose `new()` yields something that refuses every connection is the one that // must be built through `over`, and the rows say it was. `grpc` is a door that frames the // host's socket: it is built over nothing. - if session_linked() { + if ws_linked() { assert_eq!(composed_over("ws"), Some("http")); } assert_eq!(composed_over("grpc"), None); @@ -636,15 +623,16 @@ fn a_claim_on_a_transport_with_no_crate_refuses_at_boot() { /// seal answers. This is the assertion the previous shape of the test above could not make — /// the claims sealed, and the one transport gap was all that stood between the declared /// composition and a node that boots. -// Pinned against the SHIPPED composition (voice on). Compiled out with the voice plane -// because the numbers below are that composition's, not a subset of it. +// Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them +// because the numbers below are that composition's, not a subset of it. The streaming plane's claims +// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn the_seal_answers_now_that_every_claim_names_a_registered_transport() { let sealed = seal(&crate::LINKED, Dropped::NONE, TransportSettings::default()) .expect("every claim names a live transport"); - assert_eq!(sealed.claims.len(), 51); - assert_eq!(sealed.precedence.len(), 51); + assert_eq!(sealed.claims.len(), 46); + assert_eq!(sealed.precedence.len(), 46); } /// The operator's request-body cap reaches every linked transport. diff --git a/crates/busbar/tests/fixtures/billing_plane_legs.txt b/crates/busbar/tests/fixtures/billing_plane_legs.txt index 595ba4060b..0514113161 100644 --- a/crates/busbar/tests/fixtures/billing_plane_legs.txt +++ b/crates/busbar/tests/fixtures/billing_plane_legs.txt @@ -4,7 +4,9 @@ # unit / # served / # session / / +# A door plane (the `plane-door` axis: busbar-plane-streaming since FLIP-STREAMING) has no row: it is +# not a linked plane, and its units bill through the kernel plane driver's one money seam +# (`plane_driver::money`, PlaneMoney), proven by the driver suites. busbar-llm unit busbar-llm/src/unit/node.rs busbar-mcp served busbar-mcp/src/mcp/method.rs ledger_tool_call CLASS_TOOL_CALLS busbar-a2a served busbar-a2a/src/a2a/receive.rs ledger_hop_bytes CLASS_BYTES -busbar-voice session busbar-voice/src/runtime/metering.rs report_turn busbar-plane-streaming/src/session.rs class_counts CLASS_AUDIO_TOKENS_OUT diff --git a/crates/busbar/tests/fixtures/law7_plane_doors.txt b/crates/busbar/tests/fixtures/law7_plane_doors.txt index 807c752f4b..d7b932a0fa 100644 --- a/crates/busbar/tests/fixtures/law7_plane_doors.txt +++ b/crates/busbar/tests/fixtures/law7_plane_doors.txt @@ -2,7 +2,7 @@ # is configured, and that a 1.5.5 config must leave to the fallback catch-all. One door per line: # where is `both` (probed unconfigured AND configured) or `unconfigured` # (probed on the 1.5.5 config only). Order is probe order. -/v1/realtime both +/v1/realtime/client_secrets both /v1/realtime/gemini unconfigured /mcp both /a2a both diff --git a/crates/busbar/tests/fixtures/plane_doctrine.txt b/crates/busbar/tests/fixtures/plane_doctrine.txt index 59125357bb..9b268c0704 100644 --- a/crates/busbar/tests/fixtures/plane_doctrine.txt +++ b/crates/busbar/tests/fixtures/plane_doctrine.txt @@ -37,13 +37,14 @@ owed plane-decisions # # installed ...: the doctrine's installed-plane axis (plane_isomorphism.rs) # — each plane the composition root installs from its own linked entry, and the directional ledger -# column(s) it answers to. +# column(s) it answers to. The streaming plane is not on it since FLIP-STREAMING: it is served only +# through its memory-ABI door (`plane-door` axis), which has no linked PlaneDecl to compare. installed llm llm installed mcp mcp-client mcp-server installed a2a a2a-client a2a-server -installed streaming voice-client voice-server # # owned-section ...: the linked planes that own the -# grammar of a top-level section, and those sections (plane_owned_sections_boot.rs). +# grammar of a top-level section, and those sections (plane_owned_sections_boot.rs). `streams:` is +# the streaming door's, folded from its Statement into the kernel registry (DECL-FOLD, ARCHITECT +# Q-DEL-A2A-DECL; spec #49), not a linked plane's. owned-section mcp tools mcp -owned-section streaming streams streams diff --git a/crates/busbar/tests/plane_isomorphism.rs b/crates/busbar/tests/plane_isomorphism.rs index 2fd58bb29a..1b4d84c05c 100644 --- a/crates/busbar/tests/plane_isomorphism.rs +++ b/crates/busbar/tests/plane_isomorphism.rs @@ -517,11 +517,12 @@ fn the_reflected_hook_set_and_constants_are_the_doctrine() { const { assert!(MIN_HOOK_FIELDS >= 15 && MIN_ASYMMETRIES >= 10); } - // The doctrine's installed-plane axis, verbatim (the same four the composition root installs). + // The doctrine's installed-plane axis, verbatim (the same three the composition root installs + // from a linked PlaneDecl; the streaming plane left it with FLIP-STREAMING: door-only). let keys: Vec<&str> = plane_ledger_columns().iter().map(|(k, _)| *k).collect(); assert_eq!( keys.len(), - 4, + 3, "the installed-plane axis is the owner's ruling; changing it is a doctrine change: {keys:?}" ); // With every plane compiled in, the pinned axis is exactly the planes the root installs from diff --git a/qa/plane-hook-isomorphism.allow b/qa/plane-hook-isomorphism.allow index 5c2bb95b81..8d90e666ff 100644 --- a/qa/plane-hook-isomorphism.allow +++ b/qa/plane-hook-isomorphism.allow @@ -153,94 +153,6 @@ ], "capability": "trust-pinning", "reason": "the card key-id prefix is part of A2A-only agent-card signing; the LLM and MCP planes mint no card key" - }, - { - "field": "admin_routes", - "planes_none": [ - "streaming" - ], - "capability": "catalogue", - "reason": "the voice plane's streams: is a singular section, not a named-definition map, so it has no per-registration admin surface to mount" - }, - { - "field": "openapi", - "planes_none": [ - "streaming" - ], - "capability": "catalogue", - "reason": "the voice plane contributes no plane-owned OpenAPI path document; its duplex session ingress is not described as an OpenAPI fragment" - }, - { - "field": "config_validate", - "planes_none": [ - "streaming" - ], - "capability": "catalogue", - "reason": "the voice streams: grammar is validated through parse_section/default_section, not a separate plane-owned config_validate hook" - }, - { - "field": "registry_contains", - "planes_none": [ - "streaming" - ], - "capability": "catalogue", - "reason": "the voice plane keeps no named-registration registry to answer containment on; streams: is a singular section with no named stream defs" - }, - { - "field": "reresolve_gates", - "planes_none": [ - "streaming" - ], - "capability": "trust-pinning", - "reason": "the voice plane pins no per-peer artifact (providers are operator-declared endpoints), so there are no container gates to re-resolve on a swap" - }, - { - "field": "retain_verify_gates", - "planes_none": [ - "streaming" - ], - "capability": "trust-pinning", - "reason": "the voice plane retains no verify gates because it holds no pinned peer artifacts across generations" - }, - { - "field": "on_swap", - "planes_none": [ - "streaming" - ], - "capability": "trust-pinning", - "reason": "endpoint hot-swap on generation change is an MCP-endpoint concern; the voice plane swaps no per-endpoint object, dialing an operator-configured provider socket" - }, - { - "field": "parse_endpoint", - "planes_none": [ - "streaming" - ], - "capability": "egress-auth", - "reason": "the parse-endpoint hook lowers the MCP client-endpoint config grammar; the voice plane has no such per-endpoint config section" - }, - { - "field": "lower_endpoint", - "planes_none": [ - "streaming" - ], - "capability": "egress-auth", - "reason": "the lower-endpoint hook is the MCP client-endpoint lowering; the voice plane lowers no endpoint config, dialing an operator-configured provider" - }, - { - "field": "card_signing_domain", - "planes_none": [ - "streaming" - ], - "capability": "trust-pinning", - "reason": "agent-card signing identity is an A2A-only capability; the voice plane issues no signed card for a peer to verify" - }, - { - "field": "card_kid_prefix", - "planes_none": [ - "streaming" - ], - "capability": "trust-pinning", - "reason": "the card key-id prefix is part of A2A-only agent-card signing; the voice plane mints no card key" } ] } From 244d2b6221e1cf195a42ad5a7184a1c6db253488 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:41:37 -0700 Subject: [PATCH 39/82] FLIP-STREAMING: the legacy busbar-voice crate is deleted, and the streaming plane holds no tool executor (Law 11; QUESTIONS Q98) U11: the streaming door is the plane-streaming row (default-on since the flip); busbar-voice, the retiring 1.5.x streams engine and its HOT row, is deleted with every ledger row that named it (kind-isolation transitional/cell/dep rows whose drain read "struck when P3 deletes the legacy crate busbar-voice", the dep-wall lines, the door-only and plane-secret-blindness findings, the loc group member, the ports-only ceilings, the manifest-allowlist known_red_deps row, gate.plane_crates). U15: the server-side ToolExecutor port is deleted, not just the echo executor: tools.rs (the trait, ClientRelay) goes, and the session pump takes no `serves` predicate, returns no ToolRun and has no tool_executed. A tool call's open, arguments and close are relayed to the caller as-is (translated only between dialects); its close enters the node's open-call wait when a table is bound; the gateway writes nothing upstream for it. async-trait leaves the plane's closure with the port. RED arms: session_pump_tests::a_tool_call_is_relayed_to_the_caller_as_is_and_the_gateway_answers_nothing (the old pump executed a served call and wrote function_call_output + response.create upstream) and tests/purity.rs::the_plane_holds_no_tool_executor_port. --- Cargo.lock | 24 - Cargo.toml | 1 - crates/busbar-plane-streaming/Cargo.toml | 4 - crates/busbar-plane-streaming/src/claims.rs | 4 +- .../src/codec/ir/codec/gemini/mod.rs | 2 +- .../src/codec/ir/config.rs | 3 +- .../src/codec/ir/event.rs | 2 +- .../src/codec/ir/mod.rs | 2 +- .../src/codec/ir/tool.rs | 17 +- .../busbar-plane-streaming/src/codec/mod.rs | 16 +- crates/busbar-plane-streaming/src/door.rs | 8 +- crates/busbar-plane-streaming/src/governed.rs | 29 +- crates/busbar-plane-streaming/src/lib.rs | 17 +- .../busbar-plane-streaming/src/open_calls.rs | 6 +- crates/busbar-plane-streaming/src/register.rs | 4 +- .../src/session_pump.rs | 119 +- .../src/session_unit.rs | 7 +- .../src/tests/session_pump_tests.rs | 91 +- crates/busbar-plane-streaming/src/tools.rs | 55 - crates/busbar-plane-streaming/tests/purity.rs | 28 +- crates/busbar-voice/Cargo.toml | 154 -- crates/busbar-voice/src/bin/voice-conform.rs | 2414 ----------------- crates/busbar-voice/src/config.rs | 140 - crates/busbar-voice/src/lib.rs | 471 ---- crates/busbar-voice/src/mount.rs | 1672 ------------ crates/busbar-voice/src/runtime/carrier.rs | 110 - crates/busbar-voice/src/runtime/metering.rs | 135 - crates/busbar-voice/src/runtime/mod.rs | 197 -- crates/busbar-voice/src/runtime/scope.rs | 260 -- crates/busbar-voice/src/runtime/session.rs | 392 --- crates/busbar-voice/src/runtime/tests.rs | 1017 ------- .../src/runtime/voice_plane_unit_oracle.rs | 150 - crates/busbar-voice/src/testkit/echo.rs | 21 - .../busbar-voice/src/testkit/fixture_host.rs | 848 ------ .../busbar-voice/src/testkit/loopback_http.rs | 166 -- .../src/testkit/metrics_capture.rs | 92 - crates/busbar-voice/src/testkit/mod.rs | 39 - crates/busbar-voice/src/testkit/telephony.rs | 128 - .../src/testkit/tests/fixture_host_tests.rs | 117 - .../src/tests/audit_chain_tests.rs | 78 - .../src/tests/billing_ledger_tests.rs | 227 -- .../busbar-voice/src/tests/breaker_tests.rs | 102 - crates/busbar-voice/src/tests/config_tests.rs | 101 - crates/busbar-voice/src/tests/egress_tests.rs | 66 - .../src/tests/governance_budget_tests.rs | 106 - .../src/tests/governed_binding_tests.rs | 277 -- .../src/tests/governed_table_tests.rs | 164 -- .../busbar-voice/src/tests/hook_gate_tests.rs | 213 -- .../busbar-voice/src/tests/hook_tap_tests.rs | 412 --- .../src/tests/metering_lease_tests.rs | 211 -- .../busbar-voice/src/tests/metrics_tests.rs | 66 - crates/busbar-voice/src/tests/mint_tests.rs | 199 -- crates/busbar-voice/src/tests/mount_tests.rs | 767 ------ .../src/tests/scope_gate_tests.rs | 151 -- crates/busbar-voice/src/tests/sdp_tests.rs | 130 - .../busbar-voice/src/tests/served_witness.rs | 624 ----- .../busbar-voice/src/topology/minter_https.rs | 164 -- crates/busbar-voice/src/topology/mod.rs | 435 --- crates/busbar-voice/src/topology/telephony.rs | 309 --- .../src/topology/tests/minter_https_tests.rs | 186 -- crates/busbar-voice/src/topology/tests/mod.rs | 687 ----- crates/busbar-voice/src/topology/webrtc.rs | 152 -- .../tests/compose_governs_every_session.rs | 44 - qa/construction.toml | 8 +- qa/dep-wall.ledger | 8 - qa/door-only.toml | 40 - qa/kind-isolation.toml | 61 +- qa/loc.toml | 3 +- qa/plane-secret-blindness.toml | 4 - 69 files changed, 153 insertions(+), 14804 deletions(-) delete mode 100644 crates/busbar-plane-streaming/src/tools.rs delete mode 100644 crates/busbar-voice/Cargo.toml delete mode 100644 crates/busbar-voice/src/bin/voice-conform.rs delete mode 100644 crates/busbar-voice/src/config.rs delete mode 100644 crates/busbar-voice/src/lib.rs delete mode 100644 crates/busbar-voice/src/mount.rs delete mode 100644 crates/busbar-voice/src/runtime/carrier.rs delete mode 100644 crates/busbar-voice/src/runtime/metering.rs delete mode 100644 crates/busbar-voice/src/runtime/mod.rs delete mode 100644 crates/busbar-voice/src/runtime/scope.rs delete mode 100644 crates/busbar-voice/src/runtime/session.rs delete mode 100644 crates/busbar-voice/src/runtime/tests.rs delete mode 100644 crates/busbar-voice/src/runtime/voice_plane_unit_oracle.rs delete mode 100644 crates/busbar-voice/src/testkit/echo.rs delete mode 100644 crates/busbar-voice/src/testkit/fixture_host.rs delete mode 100644 crates/busbar-voice/src/testkit/loopback_http.rs delete mode 100644 crates/busbar-voice/src/testkit/metrics_capture.rs delete mode 100644 crates/busbar-voice/src/testkit/mod.rs delete mode 100644 crates/busbar-voice/src/testkit/telephony.rs delete mode 100644 crates/busbar-voice/src/testkit/tests/fixture_host_tests.rs delete mode 100644 crates/busbar-voice/src/tests/audit_chain_tests.rs delete mode 100644 crates/busbar-voice/src/tests/billing_ledger_tests.rs delete mode 100644 crates/busbar-voice/src/tests/breaker_tests.rs delete mode 100644 crates/busbar-voice/src/tests/config_tests.rs delete mode 100644 crates/busbar-voice/src/tests/egress_tests.rs delete mode 100644 crates/busbar-voice/src/tests/governance_budget_tests.rs delete mode 100644 crates/busbar-voice/src/tests/governed_binding_tests.rs delete mode 100644 crates/busbar-voice/src/tests/governed_table_tests.rs delete mode 100644 crates/busbar-voice/src/tests/hook_gate_tests.rs delete mode 100644 crates/busbar-voice/src/tests/hook_tap_tests.rs delete mode 100644 crates/busbar-voice/src/tests/metering_lease_tests.rs delete mode 100644 crates/busbar-voice/src/tests/metrics_tests.rs delete mode 100644 crates/busbar-voice/src/tests/mint_tests.rs delete mode 100644 crates/busbar-voice/src/tests/mount_tests.rs delete mode 100644 crates/busbar-voice/src/tests/scope_gate_tests.rs delete mode 100644 crates/busbar-voice/src/tests/sdp_tests.rs delete mode 100644 crates/busbar-voice/src/tests/served_witness.rs delete mode 100644 crates/busbar-voice/src/topology/minter_https.rs delete mode 100644 crates/busbar-voice/src/topology/mod.rs delete mode 100644 crates/busbar-voice/src/topology/telephony.rs delete mode 100644 crates/busbar-voice/src/topology/tests/minter_https_tests.rs delete mode 100644 crates/busbar-voice/src/topology/tests/mod.rs delete mode 100644 crates/busbar-voice/src/topology/webrtc.rs delete mode 100644 crates/busbar-voice/tests/compose_governs_every_session.rs diff --git a/Cargo.lock b/Cargo.lock index 4c2fcfe05d..ad941f84c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -961,7 +961,6 @@ dependencies = [ name = "busbar-plane-streaming" version = "1.6.0" dependencies = [ - "async-trait", "busbar-contract", "busbar-plugin-loader", "bytes", @@ -1114,29 +1113,6 @@ dependencies = [ "tokio-tungstenite", ] -[[package]] -name = "busbar-voice" -version = "1.6.0" -dependencies = [ - "async-trait", - "axum", - "busbar-contract", - "busbar-kernel", - "busbar-plane-streaming", - "bytes", - "futures", - "http", - "http-body-util", - "metrics", - "serde", - "serde_json", - "serde_yaml_ng", - "tokio", - "tokio-tungstenite", - "tracing", - "url", -] - [[package]] name = "bytecount" version = "0.6.9" diff --git a/Cargo.toml b/Cargo.toml index 3271bff7dc..29c178b089 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ "crates/busbar-llm", "crates/busbar-mcp", "crates/busbar-a2a", - "crates/busbar-voice", "crates/busbar-contract", "crates/busbar-kernel", "crates/busbar-kernel-identity", diff --git a/crates/busbar-plane-streaming/Cargo.toml b/crates/busbar-plane-streaming/Cargo.toml index d895229b8c..47e733c4e2 100644 --- a/crates/busbar-plane-streaming/Cargo.toml +++ b/crates/busbar-plane-streaming/Cargo.toml @@ -51,10 +51,6 @@ serde_json = { workspace = true } # THE MEDIA-FRAME CARRIER. `IrAudioFrame::media` and the tool IR's opaque payloads are `bytes::Bytes`; # this crate reads and builds them at the same seam rather than converting through an owned `Vec`. bytes = { workspace = true } -# THE SERVER-SIDE TOOL EXECUTOR PORT's async signature — an `async fn` in a trait object, written on the -# `#[async_trait]` desugaring it always was. A proc-macro that expands to `Box`ed futures and pulls no -# I/O into this crate's closure. -async-trait = { workspace = true } # THE DOOR'S ONE READING OF THE PUBLIC BASE URL (`door::absolute`): the audience and the metadata URL # replace the path of the deployment's public URL and drop its query and fragment, parsed once. url = { workspace = true } diff --git a/crates/busbar-plane-streaming/src/claims.rs b/crates/busbar-plane-streaming/src/claims.rs index 7d1e5b3f66..e1e9f70313 100644 --- a/crates/busbar-plane-streaming/src/claims.rs +++ b/crates/busbar-plane-streaming/src/claims.rs @@ -18,9 +18,7 @@ //! `webhook-signature` alternative alone: the `X-Twilio-Signature` HMAC over the request URL, //! verified by the auth kind's inbound verify, never by this plane. //! * **`webrtc`** — no codec surface for the RTP media plane exists anywhere in this crate's closure -//! (busbar-voice's WebRTC topology is `runtime`-gated and, per its own module documentation, is a -//! browser-sideband ferry over the same JSON event vocabulary rather than a distinct wire format — -//! but a plane cannot claim a transport it cannot decode frames from without lying about what it +//! (a plane cannot claim a transport it cannot decode frames from without lying about what it //! reads). It is unclaimed: a claim is declared independently, and the boot's own overlap check //! is what proves the claims stay disjoint when it is added. diff --git a/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/mod.rs b/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/mod.rs index 87ed829be7..71739d54a5 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/mod.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/codec/gemini/mod.rs @@ -4,7 +4,7 @@ //! THE SECOND DIALECT — Google **Gemini Live** (`BidiGenerateContent`), mapped to/from the SAME //! shared voice IR that [`super::OpenAiRealtimeCodec`] targets. Design `BUSBAR-1.6.0.md` #18/#45. //! -//! This is the codec that turns busbar-voice into a real voice *translator*: a Gemini-Live wire event +//! This is the codec that turns the streaming plane into a real voice *translator*: a Gemini-Live wire event //! is decoded into the plane-owned IR ([`IrClientEvent`] / [`IrServerEvent`]), and the OpenAI codec //! (or this one) re-frames that IR onto its own wire. Earning a cross-dialect superset IR is exactly //! what a SECOND dialect does — the A2A discipline (a plane earns a superset at its second wire format, diff --git a/crates/busbar-plane-streaming/src/codec/ir/config.rs b/crates/busbar-plane-streaming/src/codec/ir/config.rs index 52a7cb4759..eff13d01c0 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/config.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/config.rs @@ -154,7 +154,8 @@ pub struct SessionConfig { )] pub turn_detection: Option>, /// The tool set, carried VERBATIM as opaque JSON (the plane locks the set but never reshapes a - /// definition — the plane's tool moat (`BUSBAR-1.6.0.md` #18/#45) normalizes call CORRELATION, not the argument/definition bytes). + /// definition — the tool layer (`BUSBAR-1.6.0.md` #18/#45) normalizes call CORRELATION, not the + /// argument/definition bytes). #[serde(default, skip_serializing_if = "Vec::is_empty")] pub tools: Vec, /// Tool-choice policy (`"auto"` / `"none"` / `"required"` / a forced-call object), opaque. diff --git a/crates/busbar-plane-streaming/src/codec/ir/event.rs b/crates/busbar-plane-streaming/src/codec/ir/event.rs index be6c3eb351..e4cf199084 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/event.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/event.rs @@ -21,7 +21,7 @@ pub enum IrClientEvent { AudioFrame(IrAudioFrame), /// A session-control / config event the client sent (reconciled against the locked config). Control(IrDuplexControl), - /// A server-side tool RESULT the plane authored back toward the upstream (`CallResult`). + /// The caller's tool RESULT, carried toward the upstream (`CallResult`). Tool(IrDuplexTool), } diff --git a/crates/busbar-plane-streaming/src/codec/ir/mod.rs b/crates/busbar-plane-streaming/src/codec/ir/mod.rs index 02093f797f..5df3a6d47f 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/mod.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/mod.rs @@ -3,7 +3,7 @@ //! THE PLANE-4 DUPLEX / SESSION IR — the plane's OWN vocabulary. //! -//! These are the nouns that live ONLY in `busbar-voice` +//! These are the nouns that live ONLY in this plane //! (`docs/design/BUSBAR-1.6.0.md` #18/#45): the four-layer duplex/session IR and its //! reader/writer pair. Per `BUSBAR-1.6.0.md` #18/#45 "pass-through is still an IR" — the layers differ in HOW MUCH the IR //! reshapes the wire, from full normalization (tool-call) to identity (media): diff --git a/crates/busbar-plane-streaming/src/codec/ir/tool.rs b/crates/busbar-plane-streaming/src/codec/ir/tool.rs index 3cf49adade..674432c120 100644 --- a/crates/busbar-plane-streaming/src/codec/ir/tool.rs +++ b/crates/busbar-plane-streaming/src/codec/ir/tool.rs @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! LAYER 1 — TOOL-CALL (full normalization; the moat). Design `BUSBAR-1.6.0.md` #18/#45. +//! LAYER 1 — TOOL-CALL (correlation normalization). Design `BUSBAR-1.6.0.md` #18/#45. //! -//! The one layer where the IR genuinely reshapes the wire, and the whole reason a governed plane -//! beats a dumb WS pipe: tools execute server-side, under governance, and the browser is never trusted -//! to author them. +//! The one layer where the IR reshapes the wire: a call's correlation, so one dialect's call can be +//! answered in another's terms. A tool call is part of the model's response and goes back to the +//! caller unchanged except for dialect translation; the caller runs the tool and busbar executes +//! nothing (Law 11; QUESTIONS Q98). use bytes::Bytes; @@ -28,8 +29,8 @@ pub struct CallRef(pub u64); /// /// The Realtime tool loop this normalizes: the model announces a call /// (`response.output_item.added` with a `function_call` item) then streams -/// `response.function_call_arguments.delta` → `…done`; busbar executes the tool server-side and -/// returns `conversation.item.create{ function_call_output }` then `response.create`. A tool-call turn +/// `response.function_call_arguments.delta` → `…done`; the caller runs the tool and returns +/// `conversation.item.create{ function_call_output }` then `response.create`. A tool-call turn /// often produces NO audio until the result is fed back. #[derive(Debug, Clone, PartialEq, Eq)] pub enum IrDuplexTool { @@ -58,8 +59,8 @@ pub enum IrDuplexTool { /// The raw wire id the dialect correlates on. call_id: String, }, - /// busbar's SERVER-SIDE RESULT (client→server) — authored by the plane after governance, never by - /// the browser. Written back to the upstream as `function_call_output` + `response.create`. + /// The caller's RESULT (client→server), carried upstream as `function_call_output` + + /// `response.create` once the node's open-call table took it. busbar never authors one. CallResult { /// The call this result answers. call_ref: CallRef, diff --git a/crates/busbar-plane-streaming/src/codec/mod.rs b/crates/busbar-plane-streaming/src/codec/mod.rs index dbc3ad9913..a7a28b0432 100644 --- a/crates/busbar-plane-streaming/src/codec/mod.rs +++ b/crates/busbar-plane-streaming/src/codec/mod.rs @@ -3,8 +3,9 @@ //! THE VOICE DUPLEX CODECS — the pure half of the voice protocol plugin. //! -//! `busbar-voice` held two things behind one name: these codecs — the plane-4 duplex/session -//! intermediate representation (media frames, control, events, tools, session config, usage), the +//! The retired `busbar-voice` crate held two things behind one name: these codecs — the plane-4 +//! duplex/session intermediate representation (media frames, control, events, tools, session +//! config, usage), the //! shared duplex reader/writer over it, the Gemini Live dialect and the Twilio Media Streams //! grammar — and the runtime that carries them over a live socket (the axum mount, the WebSocket //! accept, the tokio session tasks, the telephony dial, the HTTPS token minter). The plane crate @@ -14,10 +15,9 @@ //! earlier spelling and was DELETED into `busbar-plane-streaming` at `fbead1a31`, #18/#83.) //! //! So the codecs live here, naming only the plugin contract (`busbar-contract`, for the base64 media -//! transcode, the billing carrier and its reserved unit names) plus serde and `bytes`. -//! `busbar-voice` depends on this crate and re-exports every module that moved under -//! its old path, so `busbar_voice::ir::…` and `busbar_voice::topology::twilio::…` resolve exactly -//! what they always did. The split is a MOVE: no item changed shape crossing it. +//! transcode, the billing carrier and its reserved unit names) plus serde and `bytes`. The split +//! was a MOVE: no item changed shape crossing it. The runtime half was deleted at FLIP-STREAMING, +//! once this plane's door served every route. /// THE REGISTRY KEY THE STREAMING PLANE IS KNOWN BY — the string the composition root flips /// onto the unified kernel loop's session admit @@ -25,9 +25,7 @@ /// gauntlet reports from its `GauntletPlane::capability_key`. /// /// Named ONCE, here, on the pure side of the split, because the plane's `capability_key` and the -/// composition-root FLIP must reference the SAME literal or a swap could drift onto two. It agrees -/// with `busbar-voice`'s `PLANE_DECLARATION.key` (`"streaming"`). `busbar-voice` re-exports it as -/// `busbar_voice::PLANE_KEY`, the one stable path the `busbar` binary names. +/// composition-root FLIP must reference the SAME literal or a swap could drift onto two. pub const PLANE_KEY: &str = "streaming"; pub mod ir; diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 777ed5e678..4c270e61bd 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -4,11 +4,9 @@ //! THE STREAMING PLANE'S DOOR: the one function a compiled-in row holds and a dropped-in image //! exports (`examples/streaming_door.rs`), and everything it states and answers. //! -//! The composition root links [`door`] on its `plane-door` axis under this fold's development-only -//! switch (`BUSBAR-1.6.0.md` Part 3, section 12, "The switch") and binds it through the loader's one -//! load beside the dropped-in plane doors. The default build links no door of this plane, and with -//! the switch on the legacy row still serves every route until the serve path hands this door the -//! arrivals it takes. +//! The composition root links [`door`] on its `plane-door` axis (the `plane-streaming` row, +//! `BUSBAR-1.6.0.md` Part 3, section 12) and binds it through the loader's one load beside the +//! dropped-in plane doors. //! //! ## What the plane states once //! diff --git a/crates/busbar-plane-streaming/src/governed.rs b/crates/busbar-plane-streaming/src/governed.rs index 9d2ac98b40..c575779056 100644 --- a/crates/busbar-plane-streaming/src/governed.rs +++ b/crates/busbar-plane-streaming/src/governed.rs @@ -1,27 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! THE GOVERNED-CALL PORT — how a client-served tool call's reply reaches the node's own table. +//! THE GOVERNED-CALL PORT — how a tool call's reply reaches the node's own table. //! -//! Two kinds of tool call cross a live session, and they end in different places. +//! A tool call is part of the model's response: the session relays it to the caller as-is, the +//! caller runs the tool, and busbar executes nothing (Law 11; QUESTIONS Q98). The answer can only +//! come from the caller, so the call's leg is a *wait*, and a wait belongs to the unit the kernel +//! opened for the call — not to the session. The node holds that table ([`crate::open_calls`]): it +//! enters the wait when the session relays the call's close, wakes it when a reply names the call, +//! and sweeps the ones nobody answered. The session's whole job is to be the thing that *tells* it, +//! and this port is that telling, dependency-inverted because a plane crate that named the +//! composition root would be the I/O half deciding which unit an answer belongs to. //! -//! A call for a tool **this node serves** is the tool moat the design is built around -//! (the tool-moat section of `BUSBAR-1.6.0.md` #18/#45): the runtime accumulates the streamed arguments, executes the -//! tool in-process through `busbar_voice::runtime::ToolExecutor`, and authors the `function_call_output` -//! itself. The client never sees it and could not forge it. Nothing about that path changes here. -//! -//! A call for a tool the node **does not** serve is the other half: the answer can only come from the -//! client, so the call's leg is a *wait*, and a wait belongs to the unit the kernel opened for the -//! call — not to the runtime. The root holds that table (`OpenToolCalls` on the voice node): it -//! enters the wait when the runtime plans the leg, wakes it when a reply names the call, and sweeps -//! the ones nobody answered. The runtime's whole job is to be the thing that *tells* it — and this port -//! is that telling, dependency-inverted the same way `busbar_voice::runtime::ToolExecutor` is, because a -//! plane crate that named the composition root would be the I/O half deciding which unit an answer -//! belongs to. -//! -//! A node with no governed table bound keeps the pre-1.6.0 behaviour exactly: every call is served -//! in-process, and a client-authored result is carried upstream verbatim. The governed path is what a -//! composition root opts a session into. +//! A session with no table bound carries a caller-authored result upstream verbatim. /// Why a client's tool reply woke nothing. /// diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index 6273ae1c97..849170fa5c 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -20,18 +20,10 @@ //! //! ## The dependency seam, stated honestly //! -//! THIS CRATE DOES NOT DEPEND ON `busbar-voice`, AND THE EDGE RUNS THE OTHER WAY. Measured with -//! `cargo tree -p busbar-plane-streaming --edges normal --depth 1`, this crate's whole direct -//! closure is `busbar-contract` + `serde` + `serde_json` + `bytes` + `async-trait` (a proc-macro) — -//! the codecs are this crate's own `codec` module, not a named dependency. The legacy engine is the -//! crate that names THIS one, not the reverse — so there is no `default-features = false` on a line -//! naming it here, because there is no such line at all. -//! -//! `busbar-voice`'s own plane machinery (`PLANE_DECL`, `mount`, `runtime`, `topology`) is built -//! against a different, older plane architecture (`busbar_kernel::plane::registry::PlaneDecl`, -//! the same shape `busbar-mcp`/`busbar-a2a` use) and is gated behind busbar-voice's `runtime` cargo -//! feature. None of that machinery, and none of the async runtime it would pull in (`tokio`, -//! `futures`, `hyper`), is reachable from here — the closure above measures zero of them. +//! Measured with `cargo tree -p busbar-plane-streaming --edges normal --depth 1`, this crate's whole +//! direct closure is `busbar-contract` + `serde` + `serde_json` + `bytes` + `url` — the codecs are +//! this crate's own `codec` module, not a named dependency, and no async runtime (`tokio`, +//! `futures`, `hyper`) is reachable from here. //! //! What this crate DOES use is [`codec::ir`] — the plane-4 duplex/session intermediate //! representation and both dialect codecs — which is pure, sync, and free of any async surface. @@ -70,7 +62,6 @@ pub mod session_pump; pub mod session_row; pub mod session_unit; pub mod sessions; -pub mod tools; #[cfg(test)] mod tests; diff --git a/crates/busbar-plane-streaming/src/open_calls.rs b/crates/busbar-plane-streaming/src/open_calls.rs index 6a737b030a..ecedd99e8e 100644 --- a/crates/busbar-plane-streaming/src/open_calls.rs +++ b/crates/busbar-plane-streaming/src/open_calls.rs @@ -4,9 +4,9 @@ //! THE NODE'S OPEN-CALL TABLE — every client-served tool call this node's sessions have open, and the //! one port the session runtime reaches it through. //! -//! Two kinds of tool call cross a live session. A call for a tool this node serves is executed -//! in-process and the client never authors its result. A call for a tool the node does NOT serve has -//! only one possible answerer, so its leg is a wait: the runtime plans it +//! A tool call crossing a live session is relayed to the caller, who runs the tool (Law 11): busbar +//! executes nothing. The caller is its only possible answerer, so its leg is a wait: the session +//! plans it //! ([`GovernedCalls::planned`]), the client's reply names the call it answers //! ([`GovernedCalls::replied`]), and the tick beside the pump ends the ones nobody answered //! ([`GovernedCalls::expired`]). Which answer wakes which wait is decided here, once, by diff --git a/crates/busbar-plane-streaming/src/register.rs b/crates/busbar-plane-streaming/src/register.rs index 91325136ec..72a57631dc 100644 --- a/crates/busbar-plane-streaming/src/register.rs +++ b/crates/busbar-plane-streaming/src/register.rs @@ -44,8 +44,8 @@ //! //! The plane opens NO socket (#7): its whole closure is pure. The duplex WebSocket ingress, the TLS //! termination and the SSE/duplex reframe are the composition root's host seams — the same -//! `busbar-transport-ws` edge the `plane-streaming` feature pulls (`busbar-voice?/runtime` arms the neutral -//! full-duplex WS transport; the plane SELECTS `Transport::WebSocket` and the host opens it). The +//! `busbar-transport-ws` edge the `plane-streaming` feature pulls (the neutral full-duplex WS +//! transport; the plane SELECTS `Transport::WebSocket` and the host opens it). The //! `plane-streaming` feature forwards to that same transport edge; the plane holds no socket plumbing. //! //! ## S4 — HOT-ABI / SDK (compiled-in AND dropped-in) diff --git a/crates/busbar-plane-streaming/src/session_pump.rs b/crates/busbar-plane-streaming/src/session_pump.rs index 10d71bda6c..5188d10e0c 100644 --- a/crates/busbar-plane-streaming/src/session_pump.rs +++ b/crates/busbar-plane-streaming/src/session_pump.rs @@ -80,31 +80,13 @@ impl TurnSink for Unmetered { } } -/// A tool call the far end closed that this session serves itself: the call's correlation, its id, -/// its name and its accumulated arguments. The caller runs it and hands the output back through -/// [`SessionPump::tool_executed`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ToolRun { - /// The call's correlation. - pub call_ref: CallRef, - /// The call id on the wire. - pub call_id: String, - /// The tool's name. - pub name: String, - /// The arguments, accumulated across the streamed deltas. - pub args: Vec, -} - -/// ONE IN-FLIGHT TOOL CALL, accumulated across the `CallOpen → CallArgs* → CallClose` frames the -/// model streams. The raw call id is kept so the stateless writer can frame the output without -/// consulting the map. +/// ONE IN-FLIGHT TOOL CALL, correlated across the `CallOpen → CallArgs* → CallClose` frames the +/// model streams: its wire id, so a close that carries only the correlation still names its call, +/// and whether its close was already handed to the node's open-call table. #[derive(Debug, Default, Clone)] struct PendingCall { call_id: String, - name: String, - args: Vec, - closed: bool, - executed: bool, + planned: bool, } /// THE SESSION PUMP over the session's codec `C`. @@ -119,8 +101,8 @@ pub struct SessionPump { locked_config: Option, /// The format uplink audio is counted in: the locked config's input format, else PCM16. audio_in: AudioFormat, - /// The node's open-call table, when one is composed. `None`: every call is served in-process and - /// a caller-authored result is carried upstream verbatim. + /// The node's open-call table, when one is composed. `None`: a caller-authored result is carried + /// upstream verbatim. governed: Option, } @@ -185,19 +167,18 @@ where ) } - /// A FRAME FROM THE FAR END, at `now_ms`. `serves` answers whether this session runs a named tool - /// itself; the calls it does are returned beside the plan, in close order, to be run and handed - /// back through [`Self::tool_executed`]. A call it does not serve is relayed to the caller, whose - /// reply the node's open-call table waits for when one is bound; the gateway never answers it. + /// A FRAME FROM THE FAR END, at `now_ms`. A tool call is part of the model's response: it is + /// relayed to the caller as the model streamed it (translated only between dialects), and the + /// caller runs the tool and answers it (Law 11; QUESTIONS Q98). Nothing here runs a tool or + /// authors its result. A call's close enters the wait for the caller's reply in the node's + /// open-call table, when one is bound. pub fn on_server_frame( &mut self, frame: WireEvent, now_ms: u64, sink: &mut dyn TurnSink, - serves: &dyn Fn(&str) -> bool, - ) -> (Outbound, Vec) { + ) -> Outbound { let mut out = Outbound::default(); - let mut to_exec: Vec = Vec::new(); let events = self.codec.read_down(frame, &mut self.decode); for ev in events { if matches!(ev, IrServerEvent::Error { .. }) { @@ -229,63 +210,41 @@ where } IrServerEvent::Tool(t) => { let call_ref = t.call_ref(); - let relayed = t.clone(); - let name = match &t { - IrDuplexTool::CallOpen { name, .. } => name.clone(), - _ => self - .calls - .get(&call_ref) - .map(|e| e.name.clone()) - .unwrap_or_default(), - }; - let served = serves(&name); - match t { - IrDuplexTool::CallOpen { call_id, name, .. } => { + match &t { + IrDuplexTool::CallOpen { call_id, .. } => { self.turn.open_tool_call(); - let e = self.calls.entry(call_ref).or_default(); - e.call_id = call_id; - e.name = name; + self.calls + .entry(call_ref) + .or_default() + .call_id + .clone_from(call_id); } - IrDuplexTool::CallArgs { - call_id, - json_delta, - .. - } => { + IrDuplexTool::CallArgs { call_id, .. } => { let e = self.calls.entry(call_ref).or_default(); if e.call_id.is_empty() { - e.call_id = call_id; + e.call_id.clone_from(call_id); } - e.args.extend_from_slice(&json_delta); } IrDuplexTool::CallClose { call_id, .. } => { let e = self.calls.entry(call_ref).or_default(); if e.call_id.is_empty() { - e.call_id = call_id; + e.call_id.clone_from(call_id); } - e.closed = true; - if !e.executed { - e.executed = true; - if served { - to_exec.push(ToolRun { - call_ref, - call_id: e.call_id.clone(), - name: e.name.clone(), - args: e.args.clone(), - }); - } else if let Some(g) = &self.governed { + if !e.planned { + e.planned = true; + if let Some(g) = &self.governed { g.calls.planned(g.session, &e.call_id, now_ms); } } } IrDuplexTool::CallResult { .. } => {} } - // A call this session does not serve is the caller's to answer, as the realtime - // protocols define it: the call is relayed to the caller, and the gateway never - // authors its result. - if !served && !matches!(relayed, IrDuplexTool::CallResult { .. }) { + // The call is the caller's to answer, as the realtime protocols define it: it is + // relayed as-is, and the gateway never authors its result. + if !matches!(t, IrDuplexTool::CallResult { .. }) { out.downlink.extend( self.codec - .write_down(IrServerEvent::Tool(relayed), &mut self.decode), + .write_down(IrServerEvent::Tool(t), &mut self.decode), ); } } @@ -300,25 +259,7 @@ where IrServerEvent::RateLimits => {} } } - (out, to_exec) - } - - /// A tool this session ran answered `output`: the result goes to the far end, then a request for - /// the model's next response. - pub fn tool_executed(&mut self, run: ToolRun, output: Vec, out: &mut Outbound) { - out.push_up(self.codec.write_up( - IrClientEvent::Tool(IrDuplexTool::CallResult { - call_ref: run.call_ref, - call_id: run.call_id, - name: run.name, - output: Bytes::from(output), - }), - &mut self.decode, - )); - out.push_up(self.codec.write_up( - IrClientEvent::Control(IrDuplexControl::ResponseCreate { response: None }), - &mut self.decode, - )); + out } /// A FRAME FROM THE CALLER. A `session.update` is replaced by the locked config; a tool reply is diff --git a/crates/busbar-plane-streaming/src/session_unit.rs b/crates/busbar-plane-streaming/src/session_unit.rs index 2149ae5074..9134ce9208 100644 --- a/crates/busbar-plane-streaming/src/session_unit.rs +++ b/crates/busbar-plane-streaming/src/session_unit.rs @@ -190,17 +190,16 @@ where } } - /// A piece from the far end, at `now_ms`. No tool is served on this node: every call is relayed - /// to the caller. + /// A piece from the far end, at `now_ms`. A tool call in it is relayed to the caller as-is: the + /// caller runs the tool (Law 11). pub fn from_far_end(&mut self, bytes: &[u8], now_ms: u64) -> Plan { if self.ended { return Plan::default(); } - let (out, _) = self.pump.on_server_frame( + let out = self.pump.on_server_frame( WireEvent(Bytes::copy_from_slice(bytes)), now_ms, &mut self.units, - &|_: &str| false, ); Plan { to_far_end: out.upstream.into_iter().map(|w| w.0.to_vec()).collect(), diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index d920a50861..bbf23d5f14 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -39,14 +39,6 @@ impl TurnSink for Turns { } } -fn serves_all(_: &str) -> bool { - true -} - -fn serves_none(_: &str) -> bool { - false -} - fn usage_done() -> WireEvent { wire(serde_json::json!({ "type": "response.done", @@ -78,8 +70,8 @@ fn a_usage_report_closes_the_turn_with_its_counters_and_the_session_continues() "type":"input_audio_buffer.append","audio": b64 }))); assert_eq!(up.upstream.len(), 1, "uplink audio is written through"); - let (out, runs) = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); - assert!(!out.close && runs.is_empty()); + let out = p.on_server_frame(usage_done(), 0, &mut sink); + assert!(!out.close); assert_eq!(sink.closed.len(), 1); let (usage, counters) = sink.closed[0]; let usage = usage.expect("the turn carries its usage"); @@ -97,7 +89,7 @@ fn a_sink_that_refuses_cuts_the_session_and_tells_the_far_end_to_stop() { refuse: true, ..Turns::default() }; - let (out, _) = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); + let out = p.on_server_frame(usage_done(), 0, &mut sink); assert!(out.close); assert!(texts(&out.upstream).contains("response.cancel")); } @@ -114,16 +106,14 @@ fn a_barge_in_cancels_and_truncates_at_what_was_heard() { ), 0, &mut sink, - &serves_all, ); - let (out, _) = p.on_server_frame( + let out = p.on_server_frame( wire( serde_json::json!({"type":"input_audio_buffer.speech_started", "audio_start_ms":0,"item_id":"it7"}), ), 0, &mut sink, - &serves_all, ); let up = texts(&out.upstream); assert!(up.contains("response.cancel"), "{up}"); @@ -134,24 +124,33 @@ fn a_barge_in_cancels_and_truncates_at_what_was_heard() { assert!(texts(&out.downlink).contains("speech_started")); } +/// LAW 11 (QUESTIONS Q98): a tool call is part of the model's response. Every frame of it is relayed +/// to the caller as the model streamed it, its arguments verbatim, and nothing the gateway writes +/// upstream answers it: no `function_call_output`, no `response.create`. RED: the pump handed a +/// closed call to a server-side executor and wrote the executor's output upstream itself. #[test] -fn a_tool_call_is_run_once_on_its_close_with_its_accumulated_arguments() { +fn a_tool_call_is_relayed_to_the_caller_as_is_and_the_gateway_answers_nothing() { let mut p = pump(); let mut sink = Turns::default(); - let mut runs = Vec::new(); + let (mut down, mut up) = (Vec::new(), Vec::new()); for f in call_frames("ca", "alpha", "{\"x\":1}") { - let (_, r) = p.on_server_frame(f, 0, &mut sink, &serves_all); - runs.extend(r); + let out = p.on_server_frame(f, 0, &mut sink); + down.extend(out.downlink); + up.extend(out.upstream); } - assert_eq!(runs.len(), 1); - assert_eq!(runs[0].name, "alpha"); - assert_eq!(runs[0].call_id, "ca"); - assert_eq!(runs[0].args, b"{\"x\":1}"); - let mut out = Outbound::default(); - p.tool_executed(runs.remove(0), b"{\"ok\":true}".to_vec(), &mut out); - let up = texts(&out.upstream); - assert!(up.contains("function_call_output") && up.contains("\"call_id\":\"ca\"")); - assert!(up.contains("response.create")); + let down = texts(&down); + assert!( + down.contains("\"name\":\"alpha\"") + && down.contains("\"call_id\":\"ca\"") + && down.contains("{\\\"x\\\":1}") + && down.contains("function_call_arguments.done"), + "the call's open, its arguments and its close reach the caller: {down}" + ); + assert!( + up.is_empty(), + "the gateway writes nothing upstream for a call: {}", + texts(&up) + ); p.settle_open_turn(&mut sink); assert_eq!(sink.closed.len(), 1); assert_eq!( @@ -196,7 +195,7 @@ impl GovernedCalls for Table { } #[test] -fn a_call_the_session_does_not_serve_waits_for_the_callers_reply() { +fn a_relayed_call_waits_for_the_callers_reply() { let table = Arc::new(Table::default()); let mut p = pump(); p.bind_governed(GovernedSession { @@ -205,11 +204,8 @@ fn a_call_the_session_does_not_serve_waits_for_the_callers_reply() { }); let mut sink = Turns::default(); for f in call_frames("cc", "lookup", "{}") { - let (_, runs) = p.on_server_frame(f, 42, &mut sink, &serves_none); - assert!( - runs.is_empty(), - "the gateway never answers a call it does not serve" - ); + let out = p.on_server_frame(f, 42, &mut sink); + assert!(out.upstream.is_empty(), "the gateway never answers a call"); } assert_eq!( *table.planned.lock().expect("lock"), @@ -263,11 +259,10 @@ fn a_session_past_its_ceiling_is_told_why_in_its_dialect() { fn an_error_closes_the_open_turn_before_it_is_relayed() { let mut p = pump(); let mut sink = Turns::default(); - let (out, _) = p.on_server_frame( + let out = p.on_server_frame( wire(serde_json::json!({"type":"error","error":{"code":"x","message":"y"}})), 0, &mut sink, - &serves_all, ); assert_eq!(sink.closed.len(), 1); assert!(sink.closed[0].0.is_none()); @@ -275,18 +270,14 @@ fn an_error_closes_the_open_turn_before_it_is_relayed() { } #[test] -fn a_call_no_one_on_this_node_serves_is_relayed_to_the_caller_and_never_answered_here() { - // No open-call table bound (an ungoverned node), and no tool served: the call reaches the - // caller, and nothing the gateway writes answers it. +fn an_ungoverned_session_relays_a_call_and_never_answers_it() { + // No open-call table bound (an ungoverned node): the call reaches the caller, and nothing the + // gateway writes answers it. let mut p = pump(); let mut sink = Turns::default(); let (mut down, mut up) = (Vec::new(), Vec::new()); for f in call_frames("cu", "unknown_tool", "{\"q\":1}") { - let (out, runs) = p.on_server_frame(f, 0, &mut sink, &serves_none); - assert!( - runs.is_empty(), - "the gateway runs nothing for a tool it does not serve" - ); + let out = p.on_server_frame(f, 0, &mut sink); down.extend(out.downlink); up.extend(out.upstream); } @@ -319,7 +310,7 @@ fn two_open_tool_calls_wait_on_two_different_correlations() { .into_iter() .chain(call_frames("dd", "b", "{}")) { - let _ = p.on_server_frame(f, 0, &mut sink, &serves_none); + let _ = p.on_server_frame(f, 0, &mut sink); } let planned: Vec = table .planned @@ -351,13 +342,12 @@ fn an_upstream_error_still_meters_the_turn_it_ended() { for id in ["e1", "e2"] { let open = call_frames(id, "t", "{}"); let [first, ..] = open; - let _ = p.on_server_frame(first, 0, &mut sink, &serves_none); + let _ = p.on_server_frame(first, 0, &mut sink); } let _ = p.on_server_frame( wire(serde_json::json!({"type":"error","error":{"code":"x","message":"y"}})), 0, &mut sink, - &serves_none, ); assert_eq!(sink.closed.len(), 1); let closed = sink.closed[0].1; @@ -370,7 +360,7 @@ fn a_barge_in_bills_what_the_interrupted_turn_served_on_the_turn_that_takes_over let mut sink = Turns::default(); let _ = p.on_client_frame(uplink_ms(1_000)); let [open, ..] = call_frames("b1", "t", "{}"); - let _ = p.on_server_frame(open, 0, &mut sink, &serves_none); + let _ = p.on_server_frame(open, 0, &mut sink); let _ = p.on_server_frame( wire( serde_json::json!({"type":"input_audio_buffer.speech_started", @@ -378,13 +368,12 @@ fn a_barge_in_bills_what_the_interrupted_turn_served_on_the_turn_that_takes_over ), 0, &mut sink, - &serves_none, ); assert!( sink.closed.is_empty(), "a barge-in closes no turn and drops no counter" ); - let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_none); + let _ = p.on_server_frame(usage_done(), 0, &mut sink); assert_eq!(sink.closed.len(), 1); let closed = sink.closed[0].1; assert_eq!((closed.audio_ms_in, closed.tool_calls), (1_000, 1)); @@ -425,7 +414,7 @@ fn twenty_turns_of_1050_ms_bill_21_audio_seconds_not_40() { let _ = p.on_client_frame(wire(serde_json::json!({ "type":"input_audio_buffer.append","audio": b64 }))); - let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); + let _ = p.on_server_frame(usage_done(), 0, &mut sink); } assert_eq!(sink.closed.len(), 20); assert_eq!(billed_audio_seconds(&sink), 21); @@ -444,7 +433,7 @@ fn the_part_of_a_millisecond_a_frame_leaves_over_is_carried_not_floored() { "type":"input_audio_buffer.append","audio": b64 }))); } - let _ = p.on_server_frame(usage_done(), 0, &mut sink, &serves_all); + let _ = p.on_server_frame(usage_done(), 0, &mut sink); assert_eq!(sink.closed.len(), 1); assert_eq!(sink.closed[0].1.audio_ms_in, 1000); assert_eq!(billed_audio_seconds(&sink), 1); diff --git a/crates/busbar-plane-streaming/src/tools.rs b/crates/busbar-plane-streaming/src/tools.rs deleted file mode 100644 index 5177b6aa8b..0000000000 --- a/crates/busbar-plane-streaming/src/tools.rs +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE SERVER-SIDE TOOL EXECUTOR PORT (the tool-moat section of `BUSBAR-1.6.0.md` #18/#45). -//! -//! The whole reason a governed plane beats a dumb WS pipe: tool calls execute SERVER-SIDE, under -//! governance, and the browser is never trusted to author them. The runtime correlates a call by its -//! [`crate::codec::ir::tool::CallRef`], accumulates the streamed argument bytes, and on close hands the -//! `(name, arguments)` to this port for execution — never to the client. The port is plane-local and -//! dependency-inverted so the composition root binds the real tool registry while tests bind a fake. - -use async_trait::async_trait; - -/// EXECUTES ONE correlated tool call server-side and returns its opaque output payload — the bytes the -/// runtime frames back upstream as a `function_call_output`. `Send + Sync` so it is shared across the -/// concurrent per-frame handlers. -#[async_trait] -pub trait ToolExecutor: Send + Sync { - /// Whether THIS NODE is the one that answers `name`. - /// - /// The two halves of the tool loop divide here. `true` — the default, and every executor's - /// answer until one says otherwise — is the moat above: the runtime accumulates the arguments and - /// calls [`Self::execute`], and the client never authors the result. `false` says the answer can - /// only come from the client, which makes the call's reply leg a governed WAIT held by the node's - /// own table ([`crate::governed::GovernedCalls`]) rather than an execution held here. - /// - /// Defaulted to `true` deliberately: an executor that has not thought about the question serves - /// what it is asked, which is the safe end. The unsafe end would be a node that quietly stopped - /// running its own tools and waited on a client that was never going to answer. - fn executes_here(&self, _name: &str) -> bool { - true - } - - /// Run the tool named `name` with the accumulated `arguments` (opaque JSON bytes) and return the - /// opaque result payload. An executor that does not recognize `name` returns an error-shaped - /// payload rather than panicking — the session survives one bad tool call. - async fn execute(&self, name: &str, arguments: &[u8]) -> Vec; -} - -/// THE DEFAULT: this node serves no tool. Every call the model makes is relayed to the caller, and -/// the caller's result is relayed back, exactly as the upstream realtime protocols define the loop. -/// The gateway never authors a tool's result. -#[derive(Debug, Default, Clone, Copy)] -pub struct ClientRelay; - -#[async_trait] -impl ToolExecutor for ClientRelay { - fn executes_here(&self, _name: &str) -> bool { - false - } - - async fn execute(&self, _name: &str, _arguments: &[u8]) -> Vec { - Vec::new() - } -} diff --git a/crates/busbar-plane-streaming/tests/purity.rs b/crates/busbar-plane-streaming/tests/purity.rs index 7783744782..8647ce97a1 100644 --- a/crates/busbar-plane-streaming/tests/purity.rs +++ b/crates/busbar-plane-streaming/tests/purity.rs @@ -93,8 +93,7 @@ fn walk(dir: &Path, f: &mut impl FnMut(&Path, &str)) { /// /// LOAD-BEARING HERE, not decoration: this crate's headers legitimately DISCUSS the kernel and its /// sibling planes — `lib.rs:35` names the older `busbar_kernel::plane::registry::PlaneDecl` -/// architecture it is not built on, `claims.rs:117` cites `busbar_plane_llm`'s ladder, and -/// `governed.rs:10/18` explain the dependency inversion by naming `busbar_voice::runtime`. Every +/// architecture it is not built on and `claims.rs:117` cites `busbar_plane_llm`'s ladder. Every /// one is prose about what this plane does NOT do. A gate its own explanation fails is a gate /// somebody deletes. fn is_comment(line: &str) -> bool { @@ -140,3 +139,28 @@ fn the_plane_names_no_kernel_side_crate() { "the streaming plane reaches a kernel-side crate or a sibling plane: {offenders:#?}" ); } + +/// LAW 11 (QUESTIONS Q98): busbar does not act on the data it carries, so the plane holds no port +/// that runs a tool. A tool call is relayed to the caller as-is (`session_pump`'s +/// `a_tool_call_is_relayed_to_the_caller_as_is_and_the_gateway_answers_nothing`); this arm keeps the +/// executor seam itself from coming back. RED: `tools.rs` declared `ToolExecutor` with an +/// `async fn execute(&self, name, arguments) -> Vec` the session pump called on a call's close. +#[test] +fn the_plane_holds_no_tool_executor_port() { + let mut offenders = Vec::new(); + walk(&src_dir(), &mut |path, text| { + for (n, line) in text.lines().enumerate() { + if is_comment(line) { + continue; + } + if line.contains("ToolExecutor") || line.contains("fn execute(") { + offenders.push(format!("{}:{}: {}", path.display(), n + 1, line.trim())); + } + } + }); + assert!( + offenders.is_empty(), + "the streaming plane runs no tool; a tool call is the caller's to run:\n{}", + offenders.join("\n") + ); +} diff --git a/crates/busbar-voice/Cargo.toml b/crates/busbar-voice/Cargo.toml deleted file mode 100644 index 21e57198ec..0000000000 --- a/crates/busbar-voice/Cargo.toml +++ /dev/null @@ -1,154 +0,0 @@ -# busbar-voice — THE DUPLEX / LIVE-VOICE PLANE CRATE (Plane 4). -# -# The fourth plane KIND — long-lived, bidirectional, per-frame-governed — added as a NEW crate that -# owns 100% of its protocol vocabulary (see docs/design/BUSBAR-1.6.0.md #18/#45). Core / substrate -# / api never learn the word "audio": this crate declares its own `PlaneDecl` and a `ProtocolDecl` -# with `codec: None` exactly as `busbar-mcp` does (the two dialects meet in the plane's own shared IR -# types, not a `DialectCodec` facade), and the neutral registry unions whatever it declares without -# naming it. -# -# STATUS: the four-layer duplex/session IR (`BUSBAR-1.6.0.md` #18/#45), both dialect codecs (OpenAI -# Realtime + Gemini Live), the T2 session pump, and both topologies are implemented behind the "runtime" -# feature. Boot-mounting into the composition root (PLANE_HOOKS' build / admission / routes hooks) is -# separate, tracked work. It compiles, registers, and is strong-form deletable. -# -# DEPENDENCY POSTURE: `busbar-substrate` is the neutral shared surface the plugin names instead of -# `busbar-core` — in production AND in its tests. Nothing in this crate's closure, under any feature, -# names or links `busbar-core`: the host-driving tests run over the substrate's in-memory fixture host -# (`busbar_substrate::testkit`), reached through the same plane-host seam the production path uses. -[package] -name = "busbar-voice" -version = "1.6.0" -publish = false # internal workspace crate; never goes to crates.io -edition = "2021" -# `rust-version` rationale: see busbar-kernel/Cargo.toml — one pinned number, set to what CI verifies. -rust-version = "1.97" -description = "The duplex / live-voice plane for busbar (Plane 4): the OpenAI Realtime and Gemini Live dialects and the four-layer session IR, as one removable plugin." -license = "Apache-2.0" - -[lib] -name = "busbar_voice" -path = "src/lib.rs" - -# THE VOICE CONFORMANCE HARNESS — the real driver the `testing/voice-conformance/` battery legs shell -# out to. It reuses THIS crate's codecs (`OpenAiRealtimeCodec` / `GeminiLiveCodec`) and the T2 runtime -# (`SessionCore` / `LocalLease` hard-close) to decode/encode the captured fixtures and diff — the legs -# never reimplement a codec in shell. Gated on `runtime` (the D2 governance probe needs the async -# session engine + tokio) AND `test-support` (the admit/route/audit/exit composition legs drive the -# substrate's `FixtureHost` — a full `EngineHost` double — over the SAME production `EngineHost` seam -# `build_runtime_hosted` takes, so the admin-audit row and the breaker/lease behavior are read back -# through the host, not reimplemented in the harness). Neither feature is default-on, so the default / -# prod build never compiles this bin. -[[bin]] -name = "voice-conform" -path = "src/bin/voice-conform.rs" -required-features = ["runtime", "test-support"] - -[dependencies] -# THE SEAL: busbar-core is NOT a dependency of this plugin at all — not in the shipped build and not in -# its test build. Every build compiles with NO busbar-core in its closure; the neutral surface the -# plane names is `busbar-substrate` (+ `busbar-api`), and its tests drive the substrate's fixture host. -# The substrate crate: the neutral shared surface the plugin names instead of busbar-core — the plane -# and protocol registry descriptors (`PlaneDecl`, `ProtocolDecl`), the diagnostics namespace, the -# proxy/egress constants the `ProtocolDecl` fields carry. -busbar-kernel = { path = "../busbar-kernel", default-features = false } -# The contract face. The plane's REGISTRATION item, `PLANE_DECLARATION`, is a -# `busbar_contract::plane::PlaneDeclaration` — plain data naming no kernel type — so the declaration -# a plane states about itself survives the fold into `busbar-plane-streaming` without a kernel edge. -busbar-contract = { path = "../busbar-contract" } -# `axum::http::StatusCode` appears in the `ProtocolDecl` field set (quota / auth-failure statuses). -axum = { workspace = true } -# The plane's OWN media-frame carrier: `IrAudioFrame.media` is opaque `bytes::Bytes` (`BUSBAR-1.6.0.md` #18/#45 identity IR). -bytes = { workspace = true } -# THE DIALECT CODEC's (de)serialization surface. The plane's IR is busbar-owned, but the OpenAI -# Realtime wire is JSON: the `DuplexReader`/`DuplexWriter` pair parses/produces JSON event frames, so -# the codec derives serde on its OWN wire structs and hand-maps them to/from the neutral IR. Opaque -# JSON payloads (tool arguments, tool definitions, tool_choice) ride as `serde_json::Value` — the -# plane never reshapes them, it carries them verbatim (the `BUSBAR-1.6.0.md` #18/#45 tool moat normalizes correlation, not -# the argument bytes). -serde = { workspace = true } -serde_json = { workspace = true } -# THE `streams:` CONFIG-SECTION reader (`config.rs`): `PLANE_HOOKS.parse_section` deserializes the -# owned section from a `serde_yaml::Value` through the plane's own typed `StreamsCfg`, exactly as the -# MCP/A2A `parse_section` hooks do. UNCONDITIONAL (config parse/validate is needed in the default -# feature-off build too), so it is a plain dep rather than a `runtime`-gated one. -serde_yaml = { workspace = true } - -# ── THE T2 RUNTIME's async surface — OPTIONAL, pulled ONLY by the `runtime` feature (OFF by default). -# The default / prod `cargo build -p busbar-voice` compiles with NONE of these in its closure: the -# IR + declarations need no async runtime. The live duplex session pump (`session.rs`), the -# metering lease, and the two topologies name `async_trait`, the `futures` Stream/Sink vocabulary the -# neutral `serve_messages` pump is generic over, and `tokio` sync/task primitives — so each is an -# OPTIONAL dep the `runtime` feature turns on (HARD RULE 4: the async surface stays feature-gated, not compiled into the default build). -async-trait = { workspace = true, optional = true } -futures = { workspace = true, optional = true } -# `net` + `rt-multi-thread`: the voice CONFORMANCE HARNESS's `provider-dial` / `gemini-live-route` legs -# stand up a tiny in-process loopback WS provider (a stand-in for a real realtime provider) and drive -# `topology::dial_provider` at it end to end — the harness needs a real listener + a runtime that can -# accept a connection while the dial races it, not just the single-threaded task/timer primitives the -# rest of the plane's async surface needs. The PRODUCTION runtime never binds a listener of its own -# (see `crate::mount::voice_start`), so this is pure harness/test weight, still `runtime`-gated. -tokio = { workspace = true, features = ["rt", "sync", "macros", "time", "net", "rt-multi-thread"], optional = true } -# THE LOOPBACK PROVIDER's WS ACCEPT SIDE — `topology::dial_provider` (and the substrate egress dialer -# it rides) already pulls `tokio-tungstenite` transitively through `busbar-kernel/runtime`; this is -# the harness's OWN direct dep for the ACCEPT half (`accept_async`), which the substrate crate has no -# reason to expose. Same feature set as the substrate's own use (handshake only, no TLS/connector). -tokio-tungstenite = { workspace = true, optional = true } -# `url` — ONE reading of the deployment's `public_url` into the plane's RFC 8707 audience + metadata URL -# (`crate::mount::absolute`, the A2A `serve::absolute` discipline). OPTIONAL, pulled only by `runtime`. -url = { workspace = true, optional = true } -# `tracing` — the boot `hydrate` / `start` hooks log the durable-session restore outcome (counts, and a -# per-row unreadable warning) exactly as the A2A / MCP boot hooks do. OPTIONAL, pulled only by `runtime` -# (the boot hooks arm with the live session runtime); the default feature-off build wires no boot hook. -tracing = { workspace = true, optional = true } -# The egress request/response vocabulary the concrete HTTPS token minter names to drive the substrate -# egress engine: `http::Request`/`Response`, the `Full` request body, and the `Incoming` body -# reader. OPTIONAL, pulled only by `runtime` (the minter compiles with the rest of the async topology). -http = { workspace = true, optional = true } -http-body-util = { workspace = true, optional = true } -# The neutral telemetry recorder the testkit's in-memory capture installs (`testkit::metrics_capture`). -# The front door itself reports through the host's `request_finished` seam (count + duration, item -# 566) and emits nothing by hand. OPTIONAL, pulled only by `runtime`. -metrics = { workspace = true, optional = true } -# THE PURE HALF OF THIS PLANE, AND THE CODECS TOO NOW. Named for three things. -# (1) The conformance harness and the governed-binding cells pin the tool-reply deadline -# (`busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS`) against the one declaration the composition -# root's own leg is built from, rather than restating thirty seconds in a second place where the two -# could drift apart in silence. (2) The GOVERNED-CALL PORT itself -# (`busbar_plane_streaming::governed::{GovernedCalls, GovernedSession, ReplyRefusal}`) now lives there, -# beside the correlation key a wait is entered under, and `crate::runtime` re-exports it under its -# old name — so the port is unconditional and the dep cannot be feature-gated behind `test-support`. -# (3) THE CODECS: the duplex IR and the dialect grammars are the plane crate's own `codec` module, -# and this crate re-exports each of those modules under the path its callers already use (`lib.rs`, -# `topology/mod.rs`). The closure this adds to the shipped build is `busbar-contract` + `bytes`, both -# of which this crate already links. No cycle: the plane crate names `busbar-contract` and never -# this crate. -busbar-plane-streaming = { path = "../busbar-plane-streaming" } - -[features] -# THE VOICE SESSION RUNTIME — the T2 live duplex engine (session pump + SessionScope + D2 metering -# lease + both topologies). OFF by default (HARD RULE 4): the default workspace build is UNAFFECTED and -# the crate still compiles with the flag off (IR + declarations only, no async runtime pulled in). Turning it on pulls the -# async closure (`async-trait` / `futures` / `tokio`) and compiles `session`/`topology`. -# `busbar-kernel/runtime` ARMS the neutral full-duplex WS transport (the ingress acceptor + the -# net-guard-pinned egress dialer): the plane SELECTS `Transport::WebSocket` and lets the substrate open -# the socket, so the provider sideband WSS dials THROUGH `net_guard` and the plane holds no socket -# plumbing of its own. Turned on in lock-step with the plane's own async runtime. -runtime = ["dep:async-trait", "dep:futures", "dep:tokio", "dep:tokio-tungstenite", "dep:url", "dep:tracing", "dep:http", "dep:http-body-util", "dep:metrics", "busbar-kernel/runtime"] -# THE OPENAPI-SCHEMA EDGE, mirroring busbar-mcp/busbar-a2a: `PlaneDecl::openapi_schemas` is a field the -# substrate struct gates on its own `openapi-schema` feature, so this forwards to substrate to keep the -# `#[cfg(feature = "openapi-schema")]` field site and the struct definition in lock-step. The plane -# contributes no schemas (the field is `None`), so no `schemars` dep is pulled. -openapi-schema = ["busbar-kernel/openapi-schema"] -# THE HOST-DRIVING TEST SURFACE: reveals THIS CRATE'S OWN `testkit` (src/testkit) — the in-memory -# fixture host, the loopback HTTP provider, the metrics capture — to the cells that drive a plane-host -# seam (the breaker, hook-gate/tap, metrics, SDP-broker and billing-ledger batteries) and to the -# `voice-conform` binary. Those three doubles USED to live in `busbar_kernel::testkit`; that module is -# deleted (1.6.0 Locked Decision #33 — "the kernel never tests a plugin"), so a plane carries the -# doubles it drives. They still name only the neutral plane-host seam production uses, so this pulls -# no core test fixture: the `busbar-kernel/test-support` forward below is for the OTHER cells in this -# crate that build a core `TestApp`, not for the doubles. -test-support = ["busbar-kernel/test-support"] - -[dev-dependencies] -# None beyond the workspace crates above: the test surface is the `test-support` feature. diff --git a/crates/busbar-voice/src/bin/voice-conform.rs b/crates/busbar-voice/src/bin/voice-conform.rs deleted file mode 100644 index 5e231e0530..0000000000 --- a/crates/busbar-voice/src/bin/voice-conform.rs +++ /dev/null @@ -1,2414 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE VOICE CONFORMANCE HARNESS — the real driver behind the `testing/voice-conformance/` battery. -//! -//! Each leg (`spec-per-dialect`, `replay`, `cross-parity`, the three composition legs, and -//! `governance`) shells out to ONE subcommand of this bin, which reuses THIS crate's production -//! codecs ([`OpenAiRealtimeCodec`] / [`GeminiLiveCodec`]), the T2 runtime ([`SessionCore`] / -//! kernel session-meter hard-close) and the plane's own composition seams to decode / encode the -//! captured fixtures and diff. The legs never reimplement a codec — or a gate — in shell: every -//! conformance claim below is proven against the plane's own code. -//! -//! Output contract (the leg runner greps `^RESULT `): each asserted item prints exactly one line -//! RESULT -//! Non-`RESULT` lines (`NOTE:` / `SUBITEM`) are ignored by the runner and used to record documented -//! sub-item gaps that must stay HONESTLY PENDING rather than be dressed as a green. - -use busbar_kernel::plane_host::EngineHost; -use busbar_plane_streaming::session::TurnCounters; -use busbar_voice::ir::usage::IrDuplexUsage; -use busbar_voice::ir::{ - DecodeState, DuplexReader, DuplexWriter, GeminiLiveCodec, IrClientEvent, IrDuplexControl, - IrDuplexTool, IrServerEvent, OpenAiRealtimeCodec, WireEvent, -}; -use busbar_voice::runtime::{ - build_runtime_hosted, Carrier, EchoToolExecutor, SessionCore, TurnMeter, TurnVerdict, - VoiceRuntime, -}; -use busbar_voice::testkit::fixture_host::FixtureHost; -use busbar_voice::topology::{begin_session, dial_provider, DialProviderError, StartError}; -use bytes::Bytes; -use serde_json::Value; -use std::collections::BTreeMap; -use std::fs; -use std::path::Path; -use std::sync::Arc; - -// ── wire helpers ────────────────────────────────────────────────────────────────────────────────── - -fn wire_of(v: &Value) -> WireEvent { - WireEvent(Bytes::from( - serde_json::to_vec(v).expect("serialize wire value"), - )) -} - -fn val_of(w: &WireEvent) -> Value { - serde_json::from_slice(&w.0).unwrap_or(Value::Null) -} - -/// A decoded fixture: the direction the codec recognized it in, plus the IR it produced. Direction is -/// inferred by trying uplink then downlink — the two dispatch tables never share a wire tag, so at most -/// one is non-empty. `Empty` means the codec mapped the frame to nothing (a documented drop+warn). -enum Decoded { - Up(Vec), - Down(Vec), - Empty, -} - -fn decode(codec: &C, v: &Value) -> Decoded { - let mut su = DecodeState::default(); - let up = codec.read_up(wire_of(v), &mut su); - if !up.is_empty() { - return Decoded::Up(up); - } - let mut sd = DecodeState::default(); - let down = codec.read_down(wire_of(v), &mut sd); - if !down.is_empty() { - return Decoded::Down(down); - } - Decoded::Empty -} - -// ── the shared normal form ────────────────────────────────────────────────────────────────────────── -// -// Both direction-split IR enums collapse onto one `Norm` list so `spec`, `replay` and `cross-parity` -// can reason about concepts uniformly (and so a bridged, re-decoded stream compares to its source). - -#[derive(Clone, Debug, PartialEq)] -enum Norm { - Config(String), // essentials only: instructions|voice|tools_count|max (the survivable fields) - ConfigModalities(Vec), - Connect, - AudioUp(Vec), - AudioDown(Vec), - AudioDone, - Item(Value), - SpeechStart, - SpeechStop, - Truncate(u64), // audio_played_ms — precision that does NOT survive toward Gemini - Commit, - Clear, - ResponseCreate, - ResponseCancel, - ItemDelete, - Usage(u64, u64, u64, u64), // audio_in, audio_out, text_in, text_out (cached rarely survives) - RateLimits, - Error(String, String), - ToolOpen(String, String), // id, name - ToolArgs(String, Value), // id, parsed args - ToolClose(String), // id - ToolResult(String, Value), // id, parsed output -} - -fn cfg_essentials(config: &busbar_voice::ir::SessionConfig) -> Norm { - Norm::Config(format!( - "{:?}|{:?}|{}|{:?}", - config.instructions, - config.voice, - config.tools.len(), - config.max_output_tokens - )) -} - -fn norm_tool(t: &IrDuplexTool) -> Norm { - match t { - IrDuplexTool::CallOpen { call_id, name, .. } => { - Norm::ToolOpen(call_id.clone(), name.clone()) - } - IrDuplexTool::CallArgs { - call_id, - json_delta, - .. - } => Norm::ToolArgs( - call_id.clone(), - serde_json::from_slice(json_delta).unwrap_or(Value::Null), - ), - IrDuplexTool::CallClose { call_id, .. } => Norm::ToolClose(call_id.clone()), - IrDuplexTool::CallResult { - call_id, output, .. - } => Norm::ToolResult( - call_id.clone(), - serde_json::from_slice(output).unwrap_or(Value::Null), - ), - } -} - -fn norm_up(evs: &[IrClientEvent]) -> Vec { - let mut out = Vec::new(); - for e in evs { - match e { - IrClientEvent::AudioFrame(f) => out.push(Norm::AudioUp(f.media.to_vec())), - IrClientEvent::Tool(t) => out.push(norm_tool(t)), - IrClientEvent::Control(c) => match c { - IrDuplexControl::SessionConfigure { config } => { - out.push(cfg_essentials(config)); - out.push(Norm::ConfigModalities(config.modalities.clone())); - } - IrDuplexControl::ItemCreate { item } => out.push(Norm::Item(item.clone())), - IrDuplexControl::ItemTruncate { - audio_played_ms, .. - } => out.push(Norm::Truncate(*audio_played_ms)), - IrDuplexControl::InputAudioCommit => out.push(Norm::Commit), - IrDuplexControl::InputAudioClear => out.push(Norm::Clear), - IrDuplexControl::ResponseCreate { .. } => out.push(Norm::ResponseCreate), - IrDuplexControl::ResponseCancel => out.push(Norm::ResponseCancel), - IrDuplexControl::ItemDelete { .. } => out.push(Norm::ItemDelete), - }, - } - } - out -} - -fn norm_down(evs: &[IrServerEvent]) -> Vec { - let mut out = Vec::new(); - for e in evs { - match e { - IrServerEvent::SessionCreated { .. } => out.push(Norm::Connect), - IrServerEvent::Tool(t) => out.push(norm_tool(t)), - IrServerEvent::SpeechStarted { .. } => out.push(Norm::SpeechStart), - IrServerEvent::SpeechStopped { .. } => out.push(Norm::SpeechStop), - IrServerEvent::AudioFrame(f) => out.push(Norm::AudioDown(f.media.to_vec())), - IrServerEvent::AudioDone { .. } => out.push(Norm::AudioDone), - IrServerEvent::Usage(u) => { - out.push(Norm::Usage(u.audio_in, u.audio_out, u.text_in, u.text_out)) - } - IrServerEvent::RateLimits => out.push(Norm::RateLimits), - IrServerEvent::Error { code, message } => { - out.push(Norm::Error(code.clone(), message.clone())) - } - } - } - out -} - -// ── round-trip (re-encode then re-decode) ────────────────────────────────────────────────────────── - -fn reencode_up( - codec: &C, - ir1: &[IrClientEvent], -) -> Vec { - let mut st = DecodeState::default(); - let mut ir2 = Vec::new(); - // ONE session state for the whole re-encode: the writers accumulate a streamed tool call's - // arguments on it, exactly as a live session's would. - let mut wst = DecodeState::default(); - for e in ir1 { - // A concept the dialect has no verb for frames NOTHING; it re-decodes to nothing too. - if let Some(w) = codec.write_up(e.clone(), &mut wst) { - ir2.extend(codec.read_up(w, &mut st)); - } - } - ir2 -} - -fn reencode_down( - codec: &C, - ir1: &[IrServerEvent], -) -> Vec { - let mut st = DecodeState::default(); - let mut ir2 = Vec::new(); - let mut wst = DecodeState::default(); - for e in ir1 { - // A downlink event that is not a frame on its own (a held tool-argument fragment) writes - // nothing; the call it belongs to frames whole at its close. - if let Some(w) = codec.write_down(e.clone(), &mut wst) { - ir2.extend(codec.read_down(w, &mut st)); - } - } - ir2 -} - -/// A call-collapsed fingerprint: tool events are merged BY call_id (an atomic Gemini `toolCall` decodes -/// to a streamed open/args/close triple, and the stateless writer re-frames per event — so frame counts -/// legitimately differ across a re-encode; the correlation join is what must survive, not the arity). -/// Everything else is compared verbatim (audio payloads, config essentials, usage, items). -/// One correlated tool call, keyed by call_id: `(name, args, result)`, each present only once its -/// event has been seen (open→name, args, result). -type CallEntry = (Option, Option, Option); - -#[derive(Debug, Default, PartialEq)] -struct Fingerprint { - calls: BTreeMap, // id -> (name, args, result) - other: Vec, -} - -fn fingerprint(norms: &[Norm]) -> Fingerprint { - let mut fp = Fingerprint::default(); - for n in norms { - match n { - Norm::ToolOpen(id, name) => { - let e = fp.calls.entry(id.clone()).or_default(); - let nm = if name.is_empty() { - None - } else { - Some(name.clone()) - }; - if e.0.is_none() { - e.0 = nm; - } - } - Norm::ToolArgs(id, args) => { - let e = fp.calls.entry(id.clone()).or_default(); - if e.1.is_none() && !args.is_null() { - e.1 = Some(args.clone()); - } - } - Norm::ToolClose(id) => { - fp.calls.entry(id.clone()).or_default(); - } - Norm::ToolResult(id, out) => { - let e = fp.calls.entry(id.clone()).or_default(); - if e.2.is_none() { - e.2 = Some(out.clone()); - } - } - other => fp.other.push(other.clone()), - } - } - fp -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEG 1 — spec-per-dialect: every dialect fixture round-trips stably through the shared IR. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -/// Fixtures that legitimately decode to NOTHING, with the reason. Every arity-0 fixture MUST be listed -/// here (a documented drop+warn) or it is a genuine unexercised gap and fails RED. -fn drop_reason(dialect: &str, fixture: &str) -> Option<&'static str> { - match (dialect, fixture) { - // Gemini concepts with no shared-IR home — the codec's documented drop+warn set. - ("gemini", "goAway.json") => { - Some("gemini_go_away: no OpenAI advance-disconnect twin (drop+warn)") - } - ("gemini", "toolCallCancellation.json") => { - Some("gemini_tool_call_cancellation: no OpenAI server-driven cancel (drop+warn)") - } - ("gemini", "serverContent.inputTranscription.json") => { - Some("input transcription side-channel: no shared IR home (drop+warn)") - } - ("gemini", "serverContent.outputTranscription.json") => { - Some("output transcription side-channel: no shared IR home (drop+warn)") - } - // NB: `realtimeInput.audioStreamEnd` is NOT here — it is no longer a drop. It maps to the - // shared `InputAudioCommit` (OpenAI's `input_audio_buffer.commit` twin), so it decodes to a - // real IR event and is exercised as an IR-fixpoint-stable fixture, not a documented drop. - _ => None, - } -} - -fn spec(dialect: &str, dir: &Path) -> i32 { - let mut fixtures: Vec = fs::read_dir(dir) - .unwrap_or_else(|e| panic!("read fixtures dir {}: {e}", dir.display())) - .filter_map(|e| e.ok()) - .map(|e| e.file_name().to_string_lossy().to_string()) - .filter(|n| n.ends_with(".json")) - .collect(); - fixtures.sort(); - assert!( - !fixtures.is_empty(), - "no .json fixtures under {}", - dir.display() - ); - - let mut fails = 0; - for f in &fixtures { - let v: Value = serde_json::from_str(&fs::read_to_string(dir.join(f)).unwrap()) - .unwrap_or_else(|e| panic!("parse fixture {f}: {e}")); - let (verdict, detail) = match dialect { - "openai" => spec_one(&OpenAiRealtimeCodec, dialect, f, &v), - "gemini" => spec_one(&GeminiLiveCodec, dialect, f, &v), - other => panic!("unknown dialect {other}"), - }; - if verdict == "FAIL" { - fails += 1; - } - println!("RESULT {dialect} {verdict} {f} — {detail}"); - } - if fails > 0 { - 1 - } else { - 0 - } -} - -fn spec_one( - codec: &C, - dialect: &str, - fixture: &str, - v: &Value, -) -> (&'static str, String) { - match decode(codec, v) { - Decoded::Up(ir1) => { - let ir2 = reencode_up(codec, &ir1); - spec_verdict(&norm_up(&ir1), &norm_up(&ir2), ir1.len()) - } - Decoded::Down(ir1) => { - let ir2 = reencode_down(codec, &ir1); - spec_verdict(&norm_down(&ir1), &norm_down(&ir2), ir1.len()) - } - Decoded::Empty => match drop_reason(dialect, fixture) { - Some(reason) => ("PASS", format!("documented drop — {reason}")), - None => ( - "FAIL", - "decoded to NO IR events and is not a documented drop — unexercised".to_string(), - ), - }, - } -} - -fn spec_verdict(n1: &[Norm], n2: &[Norm], arity: usize) -> (&'static str, String) { - if n1 == n2 { - return ("PASS", format!("IR-fixpoint stable ({arity} IR event(s))")); - } - // Multi-event atomic frames (Gemini toolCall) legitimately differ in arity across a per-event - // re-encode; the correlation-collapsed fingerprint is the guarantee the codec actually makes. - if fingerprint(n1) == fingerprint(n2) { - return ( - "PASS", - format!("IR-fixpoint stable by correlation fingerprint (atomic expansion, {arity} event(s))"), - ); - } - ("FAIL", format!("round-trip diverged: {n1:?} != {n2:?}")) -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEG 2 — replay: a captured transcript re-derives the expected ordered IR concept skeleton. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -/// Decode a whole `transcript.jsonl` through ONE session `DecodeState`, honoring each line's `dir` -/// (client → uplink, server → downlink, meta → skipped). Returns the ordered concept tags plus a count -/// of frames that re-encoded to valid JSON (the end-to-end write proof). -fn replay_decode( - codec: &C, - lines: &[Value], -) -> (Vec<&'static str>, usize, usize) { - let mut st = DecodeState::default(); - // The write seam's own session state (a streamed tool call accumulates on it). - let mut wst = DecodeState::default(); - let mut tags: Vec<&'static str> = Vec::new(); - let mut decoded = 0usize; - let mut reencoded = 0usize; - for line in lines { - let dir = line.get("dir").and_then(Value::as_str).unwrap_or("meta"); - let ev = match line.get("event") { - Some(e) => e, - None => continue, // meta line - }; - match dir { - "client" => { - let irs = codec.read_up(wire_of(ev), &mut st); - for ir in &irs { - decoded += 1; - // A dropped concept frames nothing at all — that is not a re-encode. - if codec - .write_up(ir.clone(), &mut wst) - .is_some_and(|w| !val_of(&w).is_null()) - { - reencoded += 1; - } - } - tags.extend(norm_up(&irs).iter().map(tag)); - } - "server" => { - let irs = codec.read_down(wire_of(ev), &mut st); - for ir in &irs { - decoded += 1; - if codec - .write_down(ir.clone(), &mut wst) - .is_some_and(|w| !val_of(&w).is_null()) - { - reencoded += 1; - } - } - tags.extend(norm_down(&irs).iter().map(tag)); - } - _ => {} - } - } - (tags, decoded, reencoded) -} - -fn tag(n: &Norm) -> &'static str { - match n { - Norm::Config(_) => "config", - Norm::ConfigModalities(_) => "config-modalities", - Norm::Connect => "connect", - Norm::AudioUp(_) => "audio-in", - Norm::AudioDown(_) => "audio-out", - Norm::AudioDone => "audio-done", - Norm::Item(_) => "item", - Norm::SpeechStart => "speech-start", - Norm::SpeechStop => "speech-stop", - Norm::Truncate(_) => "truncate", - Norm::Commit => "commit", - Norm::Clear => "clear", - Norm::ResponseCreate => "response-create", - Norm::ResponseCancel => "cancel", - Norm::ItemDelete => "item-delete", - Norm::Usage(..) => "usage", - Norm::RateLimits => "rate-limits", - Norm::Error(..) => "error", - Norm::ToolOpen(..) => "tool-open", - Norm::ToolArgs(..) => "tool-args", - Norm::ToolClose(..) => "tool-close", - Norm::ToolResult(..) => "tool-result", - } -} - -/// Assert `expected` appears, in order, as a subsequence of `tags`. Returns the first missing tag. -fn ordered_subsequence(tags: &[&str], expected: &[&str]) -> Result<(), String> { - let mut i = 0; - for want in expected { - match tags[i..].iter().position(|t| t == want) { - Some(off) => i += off + 1, - None => { - return Err(format!( - "expected concept '{want}' not found after position {i} in {tags:?}" - )) - } - } - } - Ok(()) -} - -fn replay(dir: &Path) -> i32 { - let mut fails = 0; - // OpenAI transcript: the full client↔server session, richly decoded. - let oa = read_jsonl(&dir.join("openai").join("transcript.jsonl")); - let (otags, od, or_) = replay_decode(&OpenAiRealtimeCodec, &oa); - let oexp = [ - "config", - "connect", - "audio-in", - "speech-start", - "tool-args", - "tool-close", - "tool-result", - "audio-out", - "speech-start", - "cancel", - ]; - match ordered_subsequence(&otags, &oexp) { - Ok(()) => println!( - "RESULT default PASS openai-transcript — {od} IR events, {or_} re-encoded, skeleton [config→connect→audio-in→barge→tool→result→audio-out→barge-in→cancel] in order" - ), - Err(e) => { - fails += 1; - println!("RESULT default FAIL openai-transcript — {e}"); - } - } - - // Gemini transcript: same logical conversation in the Gemini idiom. - let ge = read_jsonl(&dir.join("gemini").join("transcript.jsonl")); - let (gtags, gd, gr) = replay_decode(&GeminiLiveCodec, &ge); - let gexp = [ - "config", - "connect", - "audio-in", - "tool-open", - "tool-args", - "tool-close", - "tool-result", - "audio-out", - "speech-start", - "audio-done", - "usage", - ]; - match ordered_subsequence(>ags, &gexp) { - Ok(()) => println!( - "RESULT default PASS gemini-transcript — {gd} IR events, {gr} re-encoded, skeleton [config→connect→audio-in→tool→result→audio-out→barge-in→turn-complete→usage] in order" - ), - Err(e) => { - fails += 1; - println!("RESULT default FAIL gemini-transcript — {e}"); - } - } - println!( - "NOTE: gemini uplink audio frames (realtimeInput.audio{{}}) now decode and are asserted in the \ - skeleton above — the codec reads BOTH the GA realtimeInput.audio{{}} blob and the legacy \ - realtimeInput.mediaChunks[] array." - ); - if fails > 0 { - 1 - } else { - 0 - } -} - -fn read_jsonl(path: &Path) -> Vec { - fs::read_to_string(path) - .unwrap_or_else(|e| panic!("read {}: {e}", path.display())) - .lines() - .filter(|l| !l.trim().is_empty()) - .map(|l| serde_json::from_str(l).unwrap_or_else(|e| panic!("parse jsonl line: {e}"))) - .collect() -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEG 3 — cross-parity: read(A) → IR → write(B) → IR must preserve shared-concept fields, and every -// asymmetry-table row must be exercised as a documented drop. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -/// Bridge one fixture FROM codec A to codec B: decode with A, re-frame each event onto B's wire, decode -/// B's wire back. Returns (source Norms, bridged-and-re-decoded Norms). Empty source ⇒ both empty. -fn bridge(from: &A, to: &B, v: &Value) -> (Vec, Vec) -where - A: DuplexReader, - B: DuplexReader + DuplexWriter, -{ - match decode(from, v) { - Decoded::Up(ir) => { - let n1 = norm_up(&ir); - let mut st = DecodeState::default(); - let mut wst = DecodeState::default(); - let mut n2 = Vec::new(); - for e in ir { - // The destination dialect may have no verb for the concept: it frames nothing, and - // nothing is what the bridged side then carries (the map's documented drop). - if let Some(w) = to.write_up(client_from_norm_passthrough(e), &mut wst) { - n2.extend(norm_up(&to.read_up(w, &mut st))); - } - } - (n1, n2) - } - Decoded::Down(ir) => { - let n1 = norm_down(&ir); - let mut st = DecodeState::default(); - let mut wst = DecodeState::default(); - let mut n2 = Vec::new(); - for e in ir { - // An event that is not a frame on its own (a held tool-argument fragment) bridges - // nothing by itself; its call arrives whole at the close. - if let Some(w) = to.write_down(e, &mut wst) { - n2.extend(norm_down(&to.read_down(w, &mut st))); - } - } - (n1, n2) - } - Decoded::Empty => (Vec::new(), Vec::new()), - } -} - -// `write_up`/`write_down` consume the IR by value; the closures above already own their events, so this -// is just an identity used to keep the generic bridge readable. -fn client_from_norm_passthrough(e: IrClientEvent) -> IrClientEvent { - e -} - -/// Bridge a concept's fixtures AS ONE EXCHANGE — one source session, one destination session — for the -/// concepts whose map transform is stated across EVENTS rather than within one. A streamed tool call is -/// the case in point: the map says the bridge toward Gemini must ACCUMULATE the argument deltas and -/// parse them into the atomic `args` object, so a lone `…arguments.delta` frame legitimately bridges to -/// nothing on its own — the call frames at its `…arguments.done`. Judging that fragment alone would -/// demand the very mistranslation the map forbids (a call dispatched with arguments nobody sent). -fn bridge_exchange(from: &A, to: &B, vals: &[Value]) -> (Vec, Vec) -where - A: DuplexReader, - B: DuplexReader + DuplexWriter, -{ - let (mut rst, mut wst, mut bst) = ( - DecodeState::default(), - DecodeState::default(), - DecodeState::default(), - ); - let (mut n1, mut n2) = (Vec::new(), Vec::new()); - for v in vals { - let up = from.read_up(wire_of(v), &mut rst); - if !up.is_empty() { - n1.extend(norm_up(&up)); - for e in up { - if let Some(w) = to.write_up(e, &mut wst) { - n2.extend(norm_up(&to.read_up(w, &mut bst))); - } - } - continue; - } - let down = from.read_down(wire_of(v), &mut rst); - n1.extend(norm_down(&down)); - for e in down { - if let Some(w) = to.write_down(e, &mut wst) { - n2.extend(norm_down(&to.read_down(w, &mut bst))); - } - } - } - (n1, n2) -} - -fn cross( - from: &A, - to: &B, - from_d: &str, - to_d: &str, - oa_dir: &Path, - ge_dir: &Path, - map: &Value, -) -> i32 -where - A: DuplexReader + DuplexWriter, - B: DuplexReader + DuplexWriter, -{ - let slice = pair_slice(from_d, to_d); - let mut fails = 0; - let dir_for = |d: &str| if d == "openai" { oa_dir } else { ge_dir }; - - // ── shared concepts: the load-bearing fields must survive the bridge (correlation fingerprint) ── - let concepts = map["concepts"].as_array().expect("map.concepts array"); - for c in concepts { - let concept = c["concept"].as_str().unwrap_or("?"); - let fixtures = c[from_d]["fixtures"] - .as_array() - .cloned() - .unwrap_or_default(); - // Every FROM-side fixture this concept names, in the map's order. - let present: Vec<(String, Value)> = fixtures - .iter() - .filter_map(|fx| { - let name = fx.as_str().unwrap_or(""); - if name.is_empty() || name.ends_with(".jsonl") { - return None; - } - let path = dir_for(from_d).join(name); - if !path.exists() { - return None; - } - Some(( - name.to_string(), - serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap(), - )) - }) - .collect(); - // pick the first FROM-side fixture that actually decodes - let mut chosen: Option<(String, Vec, Vec)> = None; - for (name, v) in &present { - let (n1, n2) = bridge(from, to, v); - if !n1.is_empty() { - chosen = Some((name.clone(), n1, n2)); - break; - } - } - // A concept whose transform is stated ACROSS events (the streamed⟷atomic tool call) can bridge - // one of its frames to nothing on its own; judge those fixtures as the single exchange the map - // describes before calling anything lost. - if let Some((_, _, n2)) = &chosen { - if n2.is_empty() && present.len() > 1 { - let vals: Vec = present.iter().map(|(_, v)| v.clone()).collect(); - let (m1, m2) = bridge_exchange(from, to, &vals); - let names: Vec<&str> = present.iter().map(|(n, _)| n.as_str()).collect(); - chosen = Some((names.join("+"), m1, m2)); - } - } - match chosen { - None => { - // No FROM-side fixture decodes. Classify precisely so the one GENUINE codec gap is - // never buried among the legitimate documented drops. Never faked as a pass. - println!( - "SUBITEM {slice}:{concept} PENDING — {}", - none_reason(concept, from_d) - ); - } - Some((name, n1, n2)) => { - let (verdict, detail) = survives(concept, &n1, &n2, from_d, to_d); - if verdict == "FAIL" { - fails += 1; - } - println!("RESULT {slice} {verdict} shared:{name} — {detail}"); - } - } - } - - // ── asymmetry: every one-dialect-only row exercised as a documented drop/handling ── - let asym = map["asymmetry"].as_array().expect("map.asymmetry array"); - for row in asym { - let id = row["id"].as_str().unwrap_or("?"); - let dialect = row["dialect"].as_str().unwrap_or("?"); - let fixture = row["fixture"].as_str().unwrap_or(""); - // A row is EXERCISED as a drop only when we bridge OUT of its origin dialect to the other one. - // Diagonal pairs (oo/gg) and the mismatched-origin cross pair record it as covered-elsewhere. - if from_d != dialect || from_d == to_d { - println!("RESULT {slice} PASS asym:{id} — not this pair's drop direction (origin={dialect}); exercised in the {dialect}→other pair"); - continue; - } - let name = fixture - .strip_prefix(&format!("{dialect}/")) - .unwrap_or(fixture); - let path = dir_for(dialect).join(name); - if !path.exists() { - fails += 1; - println!("RESULT {slice} FAIL asym:{id} — fixture {fixture} missing"); - continue; - } - let v: Value = serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap(); - let (n1, n2) = bridge(from, to, &v); - let (verdict, detail) = asym_drop(id, &n1, &n2); - if verdict == "FAIL" { - fails += 1; - } - println!("RESULT {slice} {verdict} asym:{id} — {detail}"); - } - - if fails > 0 { - 1 - } else { - 0 - } -} - -/// Precise reason a concept has no decoding FROM-side fixture, so the report separates the ONE genuine -/// codec gap from the several legitimate documented drops / dialect-only concepts. -fn none_reason(concept: &str, from_d: &str) -> &'static str { - match (concept, from_d) { - // NOTE: "input audio frame" for gemini is no longer a codec gap — realtimeInput.json (GA - // realtimeInput.audio{}) now decodes, so the go/gg pairs take the RESULT-PASS branch and this - // reason is never reached for it. - ("input turn commit / end-of-audio", "gemini") => { - "documented drop — gemini audioStreamEnd is dropped by the codec (map aspires to commit-mapping)" - } - ("server-side VAD speech boundary", "gemini") => { - "dialect-only concept — Gemini has no explicit server-VAD boundary fixture; exercised as the openai_speech_boundary asymmetry in og" - } - ("input transcription", _) | ("output transcription", _) => { - "documented side-channel drop+warn — no shared IR home; the same-dialect round-trip is exercised in spec-per-dialect" - } - _ => "no decoding fixture on the source side (documented drop / exercised in the reverse pair)", - } -} - -fn pair_slice(from: &str, to: &str) -> &'static str { - match (from, to) { - ("openai", "openai") => "oo", - ("openai", "gemini") => "og", - ("gemini", "openai") => "go", - ("gemini", "gemini") => "gg", - _ => "??", - } -} - -/// Do the shared-concept's load-bearing fields survive A→IR→B→IR? Compared on the correlation -/// fingerprint, minus fields the mapping documents as non-survivors (text modality, VAD specifics, -/// truncate ms, session formats) which are excluded from `Fingerprint`/`cfg_essentials`. -fn survives( - concept: &str, - n1: &[Norm], - n2: &[Norm], - from_d: &str, - to_d: &str, -) -> (&'static str, String) { - // Same dialect (oo/gg): the mapping must be identity on the survivable fingerprint. - // Cross dialect: the fingerprint must still match on the shared fields. - let f1 = fingerprint(n1); - let f2 = fingerprint(n2); - if f1 == f2 { - return ( - "PASS", - format!("shared fields survive {from_d}→{to_d} ({concept})"), - ); - } - // Config is a genuine cross-dialect map: only the survivable essentials (instructions/voice/ - // tools/max) are compared; if THOSE match we pass and note the documented drops. - if concept.contains("session") && cfg_from(n1) == cfg_from(n2) && !cfg_from(n1).is_empty() { - return ( - "PASS", - format!("session config essentials survive {from_d}→{to_d} (modalities-text/VAD/format dropped per map)"), - ); - } - // Some concepts are DIRECTIONAL DROPS per the mapping's transform column (e.g. an explicit - // commit is implicit under Gemini auto-VAD; a client truncate/cancel has no Gemini counterpart). - // When the bridge drops the concept entirely in the documented direction, that IS the mapping - // holding — accounted for, not a silent mistranslation. - let f2_empty = f2.calls.is_empty() && f2.other.is_empty(); - if f2_empty && directional_drop(concept, from_d, to_d) { - return ( - "PASS", - format!("{concept}: documented directional drop {from_d}→{to_d} (implicit / no counterpart per map)"), - ); - } - ( - "FAIL", - format!("{concept}: shared fields did not survive {from_d}→{to_d}: {f1:?} != {f2:?}"), - ) -} - -/// Concepts the mapping documents as dropping in a specific direction (the transform column says the -/// counterpart is implicit or absent), so an empty bridge result is the mapping holding, not a loss. -fn directional_drop(concept: &str, _from: &str, to: &str) -> bool { - match (concept, to) { - // Explicit input-audio commit is implicit under Gemini automatic activity detection. - ("input turn commit / end-of-audio", "gemini") => true, - // A client truncate/cancel (OpenAI barge-in) has no Gemini client verb — Gemini surfaces - // barge-in only as server-side interrupted (that survivable path is the "speech boundary" row). - ("barge-in / truncate / cancel", "gemini") => true, - _ => false, - } -} - -fn cfg_from(norms: &[Norm]) -> Vec { - norms - .iter() - .filter_map(|n| match n { - Norm::Config(s) => Some(s.clone()), - _ => None, - }) - .collect() -} - -/// A one-dialect-only concept, bridged toward the other dialect, must be ACCOUNTED FOR — either dropped -/// entirely, or reduced to a benign twin — never silently mistranslated into a wrong concept. The -/// per-id check states exactly what "accounted for" means for that row. -fn asym_drop(id: &str, n1: &[Norm], n2: &[Norm]) -> (&'static str, String) { - let has = |ns: &[Norm], pred: &dyn Fn(&Norm) -> bool| ns.iter().any(pred); - match id { - // Dropped-at-decode (source already empty) or dropped-on-bridge (bridged empty/benign). - "openai_buffer_clear" => drop_if( - n2.iter().all(|n| !matches!(n, Norm::Clear)), - "clear has no Gemini twin — dropped", - ), - "openai_response_overrides" => drop_if( - n2.iter().all(|n| !matches!(n, Norm::ResponseCreate)), - "per-response overrides dropped (Gemini is setup-time only)", - ), - "openai_truncate_precision" => drop_if( - n2.iter().all(|n| !matches!(n, Norm::Truncate(_))), - "sample-accurate truncate dropped (Gemini interrupted carries no ms)", - ), - "openai_structured_error" => drop_if( - n2.iter().all(|n| !matches!(n, Norm::Error(..))), - "structured error dropped toward Gemini (WS close codes instead)", - ), - "openai_event_id" | "openai_noise_reduction" => { - // Both live inside a session.update; the codec never lifts them into IR — so they are - // absent from the SOURCE IR already. Prove the bridged config carries no such concept. - drop_if( - true, - "field never enters the IR (dropped at decode); bridged config omits it", - ) - } - "openai_semantic_vad" | "openai_g711" => { - // The session bridges (config survives), but the semantic_vad / g711 SPECIFICS do not: - // the Gemini setup has no semantic eagerness and no g711 format. Config still present. - drop_if( - has(n2, &|n| matches!(n, Norm::Config(_))), - "config bridges; semantic_vad/g711 specifics dropped per map", - ) - } - "openai_speech_boundary" => { - // speech_started maps onto Gemini's interrupted (a barge-in), but the ms offset is lost. - drop_if( - has(n2, &|n| matches!(n, Norm::SpeechStart)) || n1.is_empty(), - "boundary maps to interrupted; ms offset dropped", - ) - } - "openai_uplink_rate" => { - // The uplink audio BYTES survive verbatim; what does not happen is the resample Gemini's - // 16 kHz input wants. The bridge states the rate the bytes are really in, so the gap is - // visible on the wire rather than hidden behind a mimeType that names a rate nobody sent. - drop_if( - n1.iter() - .zip(n2.iter()) - .all(|(a, b)| matches!((a, b), (Norm::AudioUp(x), Norm::AudioUp(y)) if x == y)) - && !n2.is_empty(), - "uplink audio bridges verbatim at its true rate; the resample toward Gemini's 16 kHz input is not performed", - ) - } - "gemini_go_away" | "gemini_tool_call_cancellation" => drop_if( - n1.is_empty() && n2.is_empty(), - "no OpenAI twin — dropped at decode (drop+warn)", - ), - "gemini_generation_complete" => { - // turnComplete → AudioDone survives; the generationComplete distinction is collapsed away. - drop_if( - has(n2, &|n| matches!(n, Norm::AudioDone)), - "turnComplete→AudioDone survives; generationComplete collapsed", - ) - } - "gemini_audio_stream_end" => drop_if( - has(n1, &|n| matches!(n, Norm::Commit)) && has(n2, &|n| matches!(n, Norm::Commit)), - "audioStreamEnd ↔ input_audio_buffer.commit: the end-of-uplink turn survives cross-dialect", - ), - "gemini_setup_complete" => { - // setupComplete → SessionCreated (the ack survives); the GATE semantics are runtime-only. - drop_if( - has(n2, &|n| matches!(n, Norm::Connect)), - "ack maps to session.created; gate semantics are runtime-only, not IR", - ) - } - other => ("FAIL", format!("no asymmetry handler for id '{other}'")), - } -} - -fn drop_if(cond: bool, msg: &str) -> (&'static str, String) { - if cond { - ("PASS", format!("accounted for — {msg}")) - } else { - ("FAIL", format!("NOT accounted for — {msg}")) - } -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEG 4 — governance: the 5 vision checkpoints, probed over the real runtime. NOT a conformance result. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -fn usage_frame(audio_out: u64) -> Value { - serde_json::json!({ - "type": "response.done", - "response": { "usage": { - "total_tokens": audio_out, - "output_token_details": { "audio_tokens": audio_out }, - }}, - }) -} - -fn governance(checkpoint: &str) -> i32 { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("tokio runtime"); - let (verdict, detail) = rt.block_on(async move { - match checkpoint { - "D2-hard-close-on-exhaustion" => gov_d2().await, - "V1-barge-in-preemption" => gov_v1().await, - "V2-turn-budget-enforcement" => gov_v2(), - "V3-metering-lease-settled" => gov_v3().await, - "V4-dialect-downscope" => gov_v4(), - other => ("FAIL", format!("unknown checkpoint '{other}'")), - } - }); - println!("RESULT {checkpoint} {verdict} {detail}"); - // Governance NEVER gates conformance — always exit 0; the observation is the RESULT line above. - 0 -} - -/// A downlink-facing core whose turns go to the kernel session account of the probe key over a -/// governed fixture host, whose budget view is ONE bucket of `limit` counts (`None`: uncapped) — so the -/// D2/V probes exercise the real report-turn → kernel verdict → hard-close path. -fn core_with_downlink( - limit: Option, -) -> ( - Arc>, - futures::channel::mpsc::UnboundedReceiver>, - Arc, -) { - let (dtx, drx) = futures::channel::mpsc::unbounded::>(); - let carrier = Carrier::with_downlink(dtx); - let host = counting_host(limit); - let metering = meter(&host, probe_key()) - .open("") - .expect("a chain with room opens") - .expect("a governed host opens an account"); - let core = Arc::new(SessionCore::new( - OpenAiRealtimeCodec, - Some(metering), - Arc::new(EchoToolExecutor), - carrier, - None, - )); - (core, drx, host) -} - -/// A governed fixture host whose budget view is capped at `limit` counts (`None`: uncapped). -fn counting_host(limit: Option) -> Arc { - let host = FixtureHost::new().governed(); - Arc::new(match limit { - Some(l) => host.with_count_cap(i64::try_from(l).unwrap_or(i64::MAX)), - None => host, - }) -} - -/// The presenting key every metered probe opens its session for. -fn probe_key() -> busbar_contract::records::VirtualKey { - key_with_scopes("vk", vec![]) -} - -/// `key`'s meter over `host` — the attribution the served door builds, before the account opens. -fn meter(host: &Arc, key: busbar_contract::records::VirtualKey) -> TurnMeter { - TurnMeter::new( - Arc::clone(host) as Arc, - key, - "streaming-server", - busbar_voice::OPENAI_REALTIME, - ) -} - -/// Every count `host` has ledgered for the probe key. -fn counts_ledgered(host: &FixtureHost) -> u64 { - host.ledger_usage(&probe_key().id).map_or(0, |u| u.tokens) -} - -/// One turn of `n` emitted audio tokens. -fn audio_out(n: u64) -> IrDuplexUsage { - IrDuplexUsage { - audio_out: n, - ..IrDuplexUsage::default() - } -} - -/// D2 — the marquee guarantee: settle past cap ⇒ carrier HARD-closes ⇒ no post-close audio reaches the -/// client. Driven through the real `SessionCore` → kernel session account path. -async fn gov_d2() -> (&'static str, String) { - use futures::StreamExt; - let (core, mut drx, _host) = core_with_downlink(Some(5)); - - let p1 = core.on_server_frame(wire_of(&usage_frame(3))).await; - if p1.close || core.carrier().is_closed() { - return ( - "FAIL", - "closed under the limit (reported 3 < limit 5)".into(), - ); - } - let p2 = core.on_server_frame(wire_of(&usage_frame(3))).await; - if !p2.close { - return ( - "FAIL", - "did NOT hard-close at exhaustion (reported 6 >= limit 5)".into(), - ); - } - if !core.carrier().is_closed() { - return ("FAIL", "plan.close set but carrier not closed".into()); - } - let cancelled = p2 - .upstream - .iter() - .any(|w| String::from_utf8_lossy(&w.0).contains("response.cancel")); - if !cancelled { - return ( - "FAIL", - "exhaustion did not cancel the in-flight response upstream".into(), - ); - } - // A post-close audio frame must produce NO downlink, and the carrier must refuse a direct send. - let p3 = core - .on_server_frame(wire_of( - &serde_json::json!({ "type": "response.output_audio.delta", "delta": "AAAA" }), - )) - .await; - if !p3.downlink.is_empty() || core.carrier().send_downlink(vec![1, 2, 3]) { - return ("FAIL", "post-close audio leaked to the client".into()); - } - drx.close(); - let mut leaked = false; - while (drx.next().await).is_some() { - leaked = true; - } - if leaked { - return ( - "FAIL", - "downlink audio leaked to the client after hard close".into(), - ); - } - ( - "PASS", - "counts past the limit → the kernel reads the chain dry → response.cancel upstream → carrier hard-closed → no post-close audio".into(), - ) -} - -/// V1 — a caller barge-in preempts the in-flight turn: `speech_started` after played audio yields an -/// upstream response.cancel + truncate at the heard position. -async fn gov_v1() -> (&'static str, String) { - let (core, _drx, _host) = core_with_downlink(None); - let payload = vec![0u8; 96]; // 2 ms of pcm16 - let b64 = busbar_contract::media::base64_encode(&Bytes::from(payload)); - let _ = core - .on_server_frame(wire_of( - &serde_json::json!({ "type": "response.output_audio.delta", "delta": b64 }), - )) - .await; - let plan = core - .on_server_frame(wire_of(&serde_json::json!({ - "type": "input_audio_buffer.speech_started", "audio_start_ms": 0, "item_id": "it1" - }))) - .await; - let joined: String = plan - .upstream - .iter() - .map(|w| String::from_utf8_lossy(&w.0).to_string()) - .collect::>() - .join("\n"); - if joined.contains("response.cancel") && joined.contains("conversation.item.truncate") { - ( - "PASS", - "barge-in preempts: response.cancel + truncate at the heard ms".into(), - ) - } else { - ("FAIL", format!("no preemption observed: {joined}")) - } -} - -/// V2 — a turn/session budget is BOUNDED: a limited session must close at the limit (never overruns). -fn gov_v2() -> (&'static str, String) { - let host = counting_host(Some(50)); - let Ok(Some(session)) = meter(&host, probe_key()).open("m") else { - return ("FAIL", "a limited session did not open".into()); - }; - let quiet = TurnCounters::default(); - let a = session.report_turn(Some(&audio_out(20)), quiet); - let b = session.report_turn(Some(&audio_out(20)), quiet); - let c = session.report_turn(Some(&audio_out(20)), quiet); // 60 >= 50 - if a == TurnVerdict::Live && b == TurnVerdict::Live && c == TurnVerdict::MustClose { - ( - "PASS", - format!( - "a limited session bounds use: 20,20 live then 20 → must close at {} counts", - counts_ledgered(&host) - ), - ) - } else { - ("FAIL", format!("budget not bounded: {a:?},{b:?},{c:?}")) - } -} - -/// V3 — a session's usage actually reaches the ledger (the plane's counts through the kernel session -/// account; nothing is left unledgered). -async fn gov_v3() -> (&'static str, String) { - let (core, _drx, host) = core_with_downlink(None); - let _ = core.on_server_frame(wire_of(&usage_frame(7))).await; - if counts_ledgered(&host) == 7 { - ( - "PASS", - "usage ledgered through the kernel session account (7 counts, the plane's own class)" - .into(), - ) - } else { - ( - "FAIL", - format!( - "the turn did not reach the ledger: {} counts", - counts_ledgered(&host) - ), - ) - } -} - -/// V4 — crossing the OpenAI→Gemini boundary DOWN-SCOPES: an OpenAI-only concept (semantic_vad + g711) -/// is not widened into Gemini; the far dialect never sees a concept it cannot honor. -fn gov_v4() -> (&'static str, String) { - let v = serde_json::json!({ - "type": "session.update", - "session": { "instructions": "x", "turn_detection": { "type": "semantic_vad", "eagerness": "high" }, "output_audio_format": "g711_ulaw" } - }); - let (_n1, n2) = bridge(&OpenAiRealtimeCodec, &GeminiLiveCodec, &v); - // The bridged Gemini setup carries a config but no semantic_vad eagerness and no g711 — down-scoped. - let s = format!("{n2:?}"); - if s.contains("Config") && !s.contains("semantic") && !s.contains("g711") { - ( - "PASS", - "OpenAI-only semantic_vad/g711 down-scoped, not widened, toward Gemini".into(), - ) - } else { - ( - "FAIL", - format!("boundary widened a far-dialect concept: {s}"), - ) - } -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEGS 5-7 — composition: the three seams that decide whether a MOUNTED voice door can actually -// serve, meter and authorize a session on a real deployment. Each is a conformance leg of its own, -// because each fails on its own: a door with no provider credential answers "nothing to dial", a door -// whose sessions skip the kernel's account ledgers nothing and closes on no budget, and a door with no -// grant check admits anyone holding a token for its audience. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -/// A stand-in for a deployment's secret resolver: answers ONE declared reference and refuses every -/// other, so the probe can tell "resolved through the seam" apart from "guessed". -struct OneSecretResolver { - expect: busbar_contract::secret_ref::SecretRef, - value: String, -} - -impl busbar_contract::secret::SecretResolve for OneSecretResolver { - fn resolve(&self, secret: &busbar_contract::secret_ref::SecretRef) -> Result, String> { - self.resolve_string(secret).map(String::into_bytes) - } - fn resolve_string( - &self, - secret: &busbar_contract::secret_ref::SecretRef, - ) -> Result { - if secret == &self.expect { - Ok(self.value.clone()) - } else { - Err("no such secret reference in this deployment".to_string()) - } - } -} - -/// One bucket of a caller's budget chain, with `remaining` micro-units (`None` = uncapped). -fn budget_bucket(id: &str, remaining: Option) -> busbar_contract::hooks::BudgetBucketState { - busbar_contract::hooks::BudgetBucketState { - bucket_id: id.to_string(), - budget_group: None, - pool: None, - spend_micros_at_current_rate: 0, - remaining_micros: remaining, - window_start: 0, - budget_period: "day".to_string(), - } -} - -/// A key carrying an EXPLICIT scope list (exhaustive across kinds — whatever is absent is not granted). -fn key_with_scopes( - id: &str, - scopes: Vec, -) -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: id.to_string(), - name: id.to_string(), - allowed_scopes: Some(scopes), - ..Default::default() - } -} - -fn composition(slice: &str) -> i32 { - let (verdict, detail) = match slice { - "provider-credential" => probe_provider_credential(), - "metering-lease" => probe_metering_lease(), - "session-scope" => probe_session_scope(), - "gemini-live-route" => probe_gemini_live_route(), - "provider-dial" => probe_provider_dial(), - "admit-refusal" => probe_admit_refusal(), - "route-failover" => probe_route_failover(), - "audit-record" => probe_audit_record(), - "exit-terminal" => probe_exit_terminal(), - "tool-reply" => probe_tool_reply(), - other => ("FAIL", format!("unknown composition slice '{other}'")), - }; - println!("RESULT {slice} {verdict} {detail}"); - i32::from(verdict == "FAIL") -} - -/// K-gap 1 — the realtime provider credential reaches the plane from the deployment's own catalog: -/// the composition root hands over an origin plus the secret REFERENCE the provider entry declares, -/// and the plane resolves it through the deployment's secret resolver. Without this, the mint and SDP -/// passes are governed but have nothing to dial. -fn probe_provider_credential() -> (&'static str, String) { - if busbar_voice::mount::provider_composed() { - return ( - "FAIL", - "a provider was already composed before the probe ran".into(), - ); - } - let reference = busbar_contract::secret_ref::SecretRef::env("REALTIME_PROVIDER_KEY"); - let resolver = OneSecretResolver { - expect: reference.clone(), - value: "sk-realtime-key-held-server-side".to_string(), - }; - // Fail closed: a reference this deployment does not declare composes nothing. - let undeclared = busbar_contract::secret_ref::SecretRef::env("NOT_DECLARED_HERE"); - if busbar_voice::mount::compose_provider("https://api.example.com", &undeclared, &resolver) - .is_ok() - || busbar_voice::mount::provider_composed() - { - return ( - "FAIL", - "an unresolvable credential reference still composed a provider".into(), - ); - } - // The declared reference resolves and composes the endpoint the mint / SDP passes read. - match busbar_voice::mount::compose_provider("https://api.example.com", &reference, &resolver) { - Ok(true) => {} - Ok(false) => { - return ( - "FAIL", - "the first compose reported an existing endpoint".into(), - ) - } - Err(e) => { - return ( - "FAIL", - format!("the declared credential did not resolve: {e}"), - ) - } - } - if !busbar_voice::mount::provider_composed() { - return ("FAIL", "composing left the plane with no provider".into()); - } - if busbar_voice::mount::composed_provider_base_url() != Some("https://api.example.com") { - return ("FAIL", "the composed origin is not the declared one".into()); - } - // Set-once: a later caller cannot silently swap the deployment's credential out. - if busbar_voice::mount::compose_provider("https://other.example.com", &reference, &resolver) - != Ok(false) - || busbar_voice::mount::composed_provider_base_url() != Some("https://api.example.com") - { - return ("FAIL", "a second compose swapped the endpoint".into()); - } - ( - "PASS", - "the declared provider reference resolves through the deployment's secret resolver and \ - composes the endpoint the mint / SDP passes serve under (set-once; an unresolvable \ - reference composes nothing)" - .into(), - ) -} - -// ── the governed tool-call wait, driven through a real session ─────────────────────────────────── - -/// The node's open-call table, as a session reaches it — the same port the composition root's own -/// implementor answers, over a table this probe can watch. -#[derive(Debug, Default)] -struct ProbeCalls { - open: std::sync::Mutex>, - woken: std::sync::Mutex>, - refused: std::sync::Mutex>, - /// The client-served legs the session itself planned, by call identifier. - planned: std::sync::Mutex>, - /// The calls the tick ended because nobody answered them, and the wall each was ended at. - ended: std::sync::Mutex>, -} - -/// One call this session has open, and the wall it stops waiting at. -#[derive(Debug)] -struct ProbeCall { - session: u64, - call_id: String, - deadline: u64, -} - -/// How long a tool call's reply leg waits, in milliseconds — the PLANE's own declaration, read from -/// the crate the composition root builds its leg out of rather than restated here. A second spelling -/// of thirty seconds would let this leg keep passing after the figure it is meant to be pinning had -/// moved. -fn tool_reply_deadline_ms() -> u64 { - u64::from(busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS) * 1_000 -} - -impl ProbeCalls { - /// Enter a call as waiting, at `now_ms`, under the deadline the plane's leg declares — the same - /// arithmetic the kernel's own wait table does when the root plans the leg. - fn enter(&self, session: u64, call_id: &str, now_ms: u64) { - self.open.lock().unwrap().push(ProbeCall { - session, - call_id: call_id.to_string(), - deadline: now_ms + tool_reply_deadline_ms(), - }); - } -} - -impl busbar_voice::runtime::GovernedCalls for ProbeCalls { - fn planned(&self, _session: u64, call_id: &str, _now_ms: u64) -> bool { - // Recorded, not entered: the leg below enters its waits at a fixed wall so the deadline it - // pins is arithmetic rather than a reading of the clock. - self.planned.lock().unwrap().push(call_id.to_string()); - true - } - - fn replied( - &self, - session: u64, - call_id: &str, - ) -> Result<(), busbar_voice::runtime::ReplyRefusal> { - let mut open = self.open.lock().unwrap(); - if !open.iter().any(|c| c.session == session) { - self.refused.lock().unwrap().push(call_id.to_string()); - return Err(busbar_voice::runtime::ReplyRefusal::NoSuchSession); - } - match open - .iter() - .position(|c| c.session == session && c.call_id == call_id) - { - Some(i) => { - let call = open.remove(i); - self.woken.lock().unwrap().push(call.call_id); - Ok(()) - } - None => { - self.refused.lock().unwrap().push(call_id.to_string()); - Err(busbar_voice::runtime::ReplyRefusal::UnknownCall) - } - } - } - - fn expired(&self, now_ms: u64) -> usize { - let mut open = self.open.lock().unwrap(); - let (done, still): (Vec, Vec) = std::mem::take(&mut *open) - .into_iter() - .partition(|c| c.deadline <= now_ms); - *open = still; - let mut ended = self.ended.lock().unwrap(); - for c in &done { - ended.push((c.call_id.clone(), now_ms)); - } - done.len() - } -} - -/// A tool executor that serves nothing at all — every call this session sees is the client's to -/// answer, which is the shape the governed wait exists for. -#[derive(Debug)] -struct ServesNothing; - -#[async_trait::async_trait] -impl busbar_voice::runtime::ToolExecutor for ServesNothing { - fn executes_here(&self, _name: &str) -> bool { - false - } - async fn execute(&self, _name: &str, _arguments: &[u8]) -> Vec { - b"{}".to_vec() - } -} - -/// One dialect's three wire shapes for this leg: the frames that announce a call, the client's own -/// reply to it, and a reply naming a call nobody opened. -struct ToolWire { - dialect: &'static str, - announce: Vec, - reply: Value, - wrong: Value, -} - -fn openai_tool_wire() -> ToolWire { - ToolWire { - dialect: "openai-realtime", - announce: vec![ - serde_json::json!({"type":"response.output_item.added", - "item":{"type":"function_call","call_id":"call_x","name":"lookup"}}), - serde_json::json!({"type":"response.function_call_arguments.delta", - "call_id":"call_x","delta":"{\"q\":1}"}), - serde_json::json!({"type":"response.function_call_arguments.done","call_id":"call_x"}), - ], - reply: serde_json::json!({"type":"conversation.item.create", - "item":{"type":"function_call_output","call_id":"call_x","output":"ok"}}), - wrong: serde_json::json!({"type":"conversation.item.create", - "item":{"type":"function_call_output","call_id":"call_forged","output":"ok"}}), - } -} - -fn gemini_tool_wire() -> ToolWire { - ToolWire { - dialect: "gemini-live", - // Gemini delivers a call ATOMICALLY; the codec expands it into the same open/args/close - // triple, which is exactly why one runtime serves both dialects. - announce: vec![serde_json::json!({"toolCall":{"functionCalls":[ - {"id":"call_x","name":"lookup","args":{"q":1}}]}})], - reply: serde_json::json!({"toolResponse":{"functionResponses":[ - {"id":"call_x","name":"lookup","response":{"ok":true}}]}}), - wrong: serde_json::json!({"toolResponse":{"functionResponses":[ - {"id":"call_forged","name":"lookup","response":{"ok":true}}]}}), - } -} - -/// Drive one dialect's whole leg over a real [`SessionCore`]: announce a client-served call, answer -/// it, forge an answer, and let the tick sweep an unanswered one. `Ok(())` or the first failure. -fn drive_tool_reply(codec: C, w: &ToolWire) -> Result<(), String> -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("tokio runtime"); - let table = Arc::new(ProbeCalls::default()); - let core = SessionCore::new( - codec, - None, - Arc::new(ServesNothing), - Carrier::sideband(), - None, - ) - .with_governed(busbar_voice::runtime::GovernedSession { - session: 7, - calls: Arc::clone(&table) as Arc, - }); - - // The call is announced. Nothing goes upstream: the node serves no tool, so it authors no - // answer — the wait the root planned is the only thing that can end this call. - for f in &w.announce { - let plan = rt.block_on(core.on_server_frame(wire_of(f))); - if !plan.upstream.is_empty() { - return Err(format!( - "{}: the node answered a call it does not serve", - w.dialect - )); - } - } - - // THE PLAN. The session itself tells the node's table the leg is the client's to answer. - if table.planned.lock().unwrap().as_slice() != ["call_x"] { - return Err(format!( - "{}: the session never planned the client-served leg", - w.dialect - )); - } - - // THE WAKE. The root entered the wait where it planned the leg; the client's reply names it. - table.enter(7, "call_x", 0); - let plan = core.on_client_frame(wire_of(&w.reply)); - if plan.refused_reply { - return Err(format!( - "{}: an open wait refused its own answer", - w.dialect - )); - } - if table.woken.lock().unwrap().as_slice() != ["call_x"] { - return Err(format!( - "{}: the reply never reached the node's table", - w.dialect - )); - } - let up: String = plan - .upstream - .iter() - .map(|e| String::from_utf8_lossy(&e.0).to_string()) - .collect::>() - .join("\n"); - if !up.contains("call_x") { - return Err(format!( - "{}: the woken reply never reached the model: {up}", - w.dialect - )); - } - - // THE REFUSAL. A reply naming a call nobody is waiting on reaches the model on no wire at all. - table.enter(7, "call_y", 0); - let plan = core.on_client_frame(wire_of(&w.wrong)); - if !plan.refused_reply || !plan.upstream.is_empty() { - return Err(format!( - "{}: a reply naming no open call was carried upstream anyway", - w.dialect - )); - } - if table.refused.lock().unwrap().as_slice() != ["call_forged"] { - return Err(format!( - "{}: the forged reply was not refused by identifier", - w.dialect - )); - } - - // THE SWEEP, AT THE DEADLINE THE PLANE DECLARES. `call_y` is still open and nobody answered it; - // the tick beside the pump is what ends it, and WHEN it ends it is the whole of what this half of - // the leg pins. The wall is the plane's own `TOOL_REPLY_DEADLINE_SECS`, read from the plane crate - // the composition root builds its reply leg from, so a change to that figure moves this leg with - // it instead of leaving it asserting the old one. Not one millisecond early: a tick that ended a - // call before its declared deadline would be settling a hold the client still had time to close. - let deadline = tool_reply_deadline_ms(); - if core.sweep_expired(deadline - 1) != 0 { - return Err(format!( - "{}: a call was ended one millisecond before the deadline its leg declared", - w.dialect - )); - } - if core.sweep_expired(deadline) != 1 { - return Err(format!( - "{}: the tick did not end the unanswered call at the declared deadline ({deadline} ms)", - w.dialect - )); - } - // And what it left behind is an ENDING, not a settlement: this call was ended by the deadline, - // and nothing was ever woken for it. On the composition root's own table that ending is what the - // unit's exit path reads to end as `Failed(Route, DeadlineExceeded)`; here it is read as the fact - // the served path put there — the call, and the wall it was ended at. - if table.ended.lock().unwrap().as_slice() != [("call_y".to_string(), deadline)] { - return Err(format!( - "{}: the unanswered call was not ended at its own deadline: {:?}", - w.dialect, - table.ended.lock().unwrap() - )); - } - if table.woken.lock().unwrap().as_slice() != ["call_x"] { - return Err(format!( - "{}: an unanswered call was settled as though its answer had arrived", - w.dialect - )); - } - Ok(()) -} - -/// A client-served tool call's reply reaches the node's own table through a REAL session, on every -/// dialect the plane serves a duplex session on. Without this the root entered a wait that the served -/// path could never wake and no tick ever swept — the call's hold was held open by a client that -/// simply never replied, and a forged reply rode upstream under whatever call happened to be open. -fn probe_tool_reply() -> (&'static str, String) { - if let Err(e) = drive_tool_reply(OpenAiRealtimeCodec, &openai_tool_wire()) { - return ("FAIL", e); - } - if let Err(e) = drive_tool_reply(GeminiLiveCodec, &gemini_tool_wire()) { - return ("FAIL", e); - } - ( - "PASS", - format!( - "on both duplex dialects a call for a tool the node does not serve is answered by \ - nobody but the client: the reply wakes the wait the root planned and only then reaches \ - the model, a reply naming no open call is refused and carried on no wire, and the tick \ - beside the pump ends a call nobody answered at exactly the {} s its leg declares — not \ - one millisecond early — so its unit exits under that deadline rather than settling as \ - though the answer had arrived", - busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS - ), - ) -} - -/// K-gap 2 (the `metering-lease` slice, OWNER RULING Q21b) — a session is metered on the KERNEL's -/// session account for the presenting key: its turns' counts are ledgered through the one metering -/// path, and the kernel's budget view of the caller's chain refuses a spent caller at the open and -/// closes a live session at the turn that dries it. The D2 lease this slice once probed is deleted. -fn probe_metering_lease() -> (&'static str, String) { - let quiet = TurnCounters::default(); - // A spent caller never opens a session: the kernel's view reads its chain dry. - let spent = Arc::new( - FixtureHost::new() - .governed() - .with_budget_chain(vec![budget_bucket("vk", Some(0))]), - ); - if meter(&spent, probe_key()).open("m").is_ok() { - return ( - "FAIL", - "a session opened for a caller whose budget is spent".into(), - ); - } - // An unbudgeted caller has no ceiling: no turn ever closes the session. - let open = Arc::new( - FixtureHost::new() - .governed() - .with_budget_chain(vec![budget_bucket("vk", None)]), - ); - let Ok(Some(session)) = meter(&open, probe_key()).open("m") else { - return ( - "FAIL", - "an unbudgeted caller could not open a session".into(), - ); - }; - if session.report_turn(Some(&audio_out(u64::MAX / 2)), quiet) != TurnVerdict::Live { - return ("FAIL", "an unbudgeted caller was given a ceiling".into()); - } - // A chain of 4 000 counts: a turn one short of it is live, and the turn that reaches it closes. - let capped = counting_host(Some(4_000)); - let Ok(Some(session)) = meter(&capped, probe_key()).open("m") else { - return ("FAIL", "a budgeted caller could not open a session".into()); - }; - let live = session.report_turn(Some(&audio_out(3_999)), quiet); - let dry = session.report_turn(Some(&audio_out(1)), quiet); - if live != TurnVerdict::Live || dry != TurnVerdict::MustClose { - return ( - "FAIL", - format!("turns did not close at the caller's ceiling: {live:?} then {dry:?}"), - ); - } - if counts_ledgered(&capped) != 4_000 { - return ( - "FAIL", - format!( - "the ledger did not hold the exact counts: {}", - counts_ledgered(&capped) - ), - ); - } - ( - "PASS", - "the session is metered on the kernel's session account: its counts are ledgered exactly, \ - a spent caller is refused at the open, and a live one must close at the turn that dries \ - its chain" - .into(), - ) -} - -/// K-gap 3 — the plane's declared `session` scope kind is enforced at session open. Without this, any -/// key valid for the voice audience opens a session, and the declared vocabulary is inert. -fn probe_session_scope() -> (&'static str, String) { - let pool_scope = busbar_contract::records::ScopeRef::pool("fast"); - let session_here = busbar_contract::records::ScopeRef { - kind: "session".to_string(), - value: "streaming-server".to_string(), - }; - let session_elsewhere = busbar_contract::records::ScopeRef { - kind: "session".to_string(), - value: "some-other-pool".to_string(), - }; - - // A wildcard principal (no list at all) is granted every kind, as on every other plane. - let wildcard = busbar_contract::records::VirtualKey { - id: "vk-wildcard".to_string(), - ..Default::default() - }; - if !busbar_voice::mount::session_scope_allowed(&wildcard) { - return ("FAIL", "a wildcard principal was refused a session".into()); - } - // An explicit grant on the voice pool admits. - if !busbar_voice::mount::session_scope_allowed(&key_with_scopes( - "vk-granted", - vec![session_here.clone()], - )) { - return ("FAIL", "an explicit session grant was refused".into()); - } - // Everything else with an explicit list is refused: a model-plane key, a session grant aimed at - // another pool, and an empty list. - for (name, scopes) in [ - ("a model-plane key", vec![pool_scope]), - ("a session grant for another pool", vec![session_elsewhere]), - ("an empty grant list", Vec::new()), - ] { - if busbar_voice::mount::session_scope_allowed(&key_with_scopes("vk-ungranted", scopes)) { - return ("FAIL", format!("{name} was admitted a voice session")); - } - } - ( - "PASS", - "the declared session scope is enforced against the presenting key's own grant: granted \ - and wildcard keys open, a key without it (or with it aimed elsewhere) is refused" - .into(), - ) -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEG 8 (the second-dialect route) — gemini-live-route: the Gemini Live dialect has a MOUNTED route, not just a codec the -// spec/cross-parity legs exercise off to the side. Proves, on the plane's own PUBLIC functions (the -// same ones the composition root calls, and the same `WsArrivalSpec` a real deployment mounts): -// -// * the dispatch slot CLAIMS a Gemini-labelled base distinct from the OpenAI one (`voice_claims`) -// * the plane still ADMITS exactly one audience for both dialects (`voice_admission`) -// * a Gemini WS-accept arrival is actually declared, keyed to this plane's own slot (`voice_ws_arrivals`) -// * the wire handshake itself: a `setupComplete` frame from the far side (what a dialed provider -// sends) is relayed to the client verbatim through a `SessionCore` — the EXACT -// codec type the mounted route's `WsArrivalSpec` closure closes over, not a stand-in. -// -// WAS RED: `PLANE_HOOKS.wire_format_names` named `gemini_live`, the codec existed and passed the -// spec/cross-parity battery, but no ingress route spoke it — `voice_claims`/`voice_ws_arrivals` named -// only the OpenAI base, so a caller had no path to reach the Gemini dialect at all. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -fn probe_gemini_live_route() -> (&'static str, String) { - let unit = (); - let ctx = busbar_kernel::plane::registry::BuildCtx { - endpoint_slot: None, - agent_defs: &unit, - tool_defs: &unit, - public_url: Some("https://gw.conform.example.com"), - prior: None, - }; - let Some(slot) = busbar_voice::mount::voice_build(&ctx) else { - return ("FAIL", "voice_build produced no dispatch slot".into()); - }; - - let claims = busbar_voice::mount::voice_claims(slot.as_ref()); - if !claims.contains(&("/v1/realtime/gemini".to_string(), busbar_voice::GEMINI_LIVE)) { - return ( - "FAIL", - format!("the Gemini base is not claimed under its own dialect: {claims:?}"), - ); - } - if !claims.contains(&("/v1/realtime".to_string(), busbar_voice::OPENAI_REALTIME)) { - return ( - "FAIL", - format!("the OpenAI base is no longer claimed alongside Gemini: {claims:?}"), - ); - } - - let Some(admission) = busbar_voice::mount::voice_admission(slot.as_ref()) else { - return ( - "FAIL", - "a plane that claims paths must admit (the claim-admits ratchet): admission is None" - .into(), - ); - }; - if !admission.audience.ends_with("/v1/realtime") { - return ( - "FAIL", - format!("unexpected audience: {}", admission.audience), - ); - } - - let arrivals = busbar_voice::mount::voice_ws_arrivals(); - let Some(gemini) = arrivals - .iter() - .find(|a| a.path == "/v1/realtime/gemini/{call_id}") - else { - return ( - "FAIL", - format!( - "no Gemini WS-accept arrival mounted; declared paths: {:?}", - arrivals.iter().map(|a| &a.path).collect::>() - ), - ); - }; - if gemini.slot_key != busbar_voice::PLANE_DECLARATION.key { - return ( - "FAIL", - format!( - "the Gemini arrival is keyed to '{}', not the plane's own slot '{}'", - gemini.slot_key, - busbar_voice::PLANE_DECLARATION.key - ), - ); - } - - // THE HANDSHAKE ITSELF, over the exact runtime type the mounted route is generic over: a provider's - // `setupComplete` answers the client's `setup` by relaying verbatim (`IrServerEvent::SessionCreated` - // is a pass-through in `SessionCore::on_server_frame`) — the same relay the Gemini WS accept's - // provider leg drives once a session is dialed (see the `provider-dial` leg for the live-socket - // proof; this leg proves the PLANE'S side of that relay against the mounted codec). - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("tokio runtime"); - let core = SessionCore::new( - GeminiLiveCodec, - None, - Arc::new(EchoToolExecutor), - Carrier::sideband(), - None, - ); - let setup_complete = serde_json::json!({ "setupComplete": {} }); - let outbound = rt.block_on(core.on_server_frame(wire_of(&setup_complete))); - if outbound.downlink.len() != 1 { - return ( - "FAIL", - format!( - "expected exactly one relayed downlink frame from setupComplete, got {}", - outbound.downlink.len() - ), - ); - } - let got = val_of(&outbound.downlink[0]); - if got.get("setupComplete").is_none() { - return ( - "FAIL", - format!("the relayed downlink frame was not setupComplete: {got}"), - ); - } - - ( - "PASS", - "the Gemini Live route is mounted under its own claim, admits the same audience, declares a \ - WS-accept arrival keyed to the plane's slot, and the mounted codec relays a provider's \ - setupComplete handshake to the client verbatim" - .into(), - ) -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEG 9 (the provider-dial leg) — provider-dial: `topology::dial_provider` is a library function nothing calls in -// production without a composed provider; this leg proves a session actually dials one end to end. -// A loopback WS "provider" stands in for a real realtime upstream (no network, no vendor credential -// needed): the harness binds it on an ephemeral port, `dial_provider` dials it through the SAME -// net-guarded path the mounted WS-accept legs now call, the loopback sends one usage frame, and the -// session's own kernel account ledgers it — proving the wiring from a live socket to the ledger, not -// just the codec math the other legs already cover. -// -// WAS RED: `topology::dial_provider` existed, breaker/net-guarded, but nothing in the mounted routes -// called it — a session never dialed a live socket, so no leg drove one. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -/// A minimal [`busbar_kernel::plane_host::BreakerHost`] — `dial_provider` reads only the breaker -/// slice of the host seam (never the rest of `EngineHost`), so the loopback leg needs only this much: -/// admit always, record nothing, no cooldown. Not a plane-private breaker implementation — it lives -/// only in this dev-only conformance binary. -#[derive(Default)] -struct AlwaysAdmitBreakerHost; - -impl busbar_kernel::plane_host::BreakerHost for AlwaysAdmitBreakerHost { - fn breaker_admit( - &self, - scope: &busbar_kernel::plane_host::DispatchScope, - _pool: &[u8], - _lane: u32, - ) -> Result { - Ok(scope.register_admission(Box::new(()))) - } - fn breaker_settle( - &self, - scope: &busbar_kernel::plane_host::DispatchScope, - admission: busbar_contract::abi::hot::AdmissionId, - signal: &busbar_contract::abi::hot::Signal, - ) -> busbar_contract::abi::hot::StatusClass { - scope - .settle_admission(admission, signal) - .unwrap_or(busbar_contract::abi::hot::StatusClass::Refused) - } - fn breaker_record_success(&self, _pool: &str, _lane: usize) {} - fn breaker_record_signal( - &self, - _pool: &str, - _lane: usize, - _sig: &busbar_contract::upstream::CanonicalSignal, - ) { - } - fn breaker_retry_after_secs(&self, _pool: &str, _lane: usize) -> u64 { - 0 - } -} - -fn probe_provider_dial() -> (&'static str, String) { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("tokio runtime"); - rt.block_on(async move { - // THE LOOPBACK PROVIDER: bind an ephemeral port, accept ONE connection, upgrade it to a bare WS - // server (no TLS — the dial below opts into plaintext for this loopback target only), read - // whatever the client sends (ignored — this leg proves the DOWNLINK leg settles, not the uplink - // shape), then send one `response.done` usage frame and close. - let listener = match tokio::net::TcpListener::bind("127.0.0.1:0").await { - Ok(l) => l, - Err(e) => return ("FAIL", format!("loopback provider could not bind: {e}")), - }; - let addr = listener.local_addr().expect("a bound listener has a local addr"); - let server = tokio::spawn(async move { - let (tcp, _peer) = listener.accept().await.expect("one loopback connection"); - let mut ws = tokio_tungstenite::accept_async(tcp) - .await - .expect("the loopback WS handshake completes"); - let usage = usage_frame(9).to_string(); - let _ = futures::SinkExt::send( - &mut ws, - tokio_tungstenite::tungstenite::Message::text(usage), - ) - .await; - let _ = futures::SinkExt::close(&mut ws).await; - }); - - let host = AlwaysAdmitBreakerHost; - let url = format!("ws://{addr}"); - let policy = busbar_kernel::net_guard::GuardPolicy { - allow_private: true, - allow_plaintext: true, - ..busbar_kernel::net_guard::GuardPolicy::default() - }; - let (mut provider_in, _provider_out) = match busbar_voice::topology::dial_provider( - &host, - "stream:conform-loopback", - 0, - &url, - policy, - ) - .await - { - Ok(pair) => pair, - Err(e) => return ("FAIL", format!("dial_provider could not reach the loopback: {e}")), - }; - - // ONE SESSION END TO END: the dialed frame drives the SAME `SessionCore` the mounted route - // opens, and its kernel account must ledger the usage the loopback sent — over a governed - // host that records every count, so the leg cannot pass unless the frame arrived. - let counting = counting_host(None); - let Ok(Some(metering)) = meter(&counting, probe_key()).open("") else { - return ("FAIL", "the probe key's account did not open".into()); - }; - let core = SessionCore::new( - OpenAiRealtimeCodec, - Some(metering), - Arc::new(EchoToolExecutor), - Carrier::sideband(), - None, - ); - let Some(frame) = futures::StreamExt::next(&mut provider_in).await else { - return ( - "FAIL", - "the loopback provider closed before sending its usage frame".into(), - ); - }; - let _ = core - .on_server_frame(WireEvent(Bytes::from(frame))) - .await; - let _ = server.await; - - if counts_ledgered(&counting) == 9 { - ( - "PASS", - "one session dialed the loopback provider through `dial_provider`'s net-guarded path \ - end to end, and its kernel account ledgered the usage the loopback sent (9 counts)" - .into(), - ) - } else { - ( - "FAIL", - format!( - "the dialed usage did not reach the ledger: {} counts", - counts_ledgered(&counting) - ), - ) - } - }) -} - -// ══════════════════════════════════════════════════════════════════════════════════════════════════ -// LEGS 10-13 — the last four gaps: admit / route / audit / exit, each judged against a REAL -// `EngineHost` (the substrate's `FixtureHost`, a full double over the same seam `build_runtime_hosted` -// takes in production) rather than a narrow `MeteringHost`/`BreakerHost` stand-in. -// ══════════════════════════════════════════════════════════════════════════════════════════════════ - -/// Assemble a per-generation [`VoiceRuntime`] bound onto `host` — the harness's own -/// [`build_runtime_hosted`] call, shared by every probe below that needs a governed session. -fn hosted_runtime(host: Arc) -> VoiceRuntime { - let base = VoiceRuntime::new( - Arc::new(busbar_kernel::plane::handle_engine::DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), - ); - build_runtime_hosted(&base, host) -} - -/// LEG 10 — admit-refusal: a key whose budget is already spent is refused AT THE DOOR -/// (`StartError::BudgetRefused`), before any session exists and before any ledger posting — so no -/// provider dial has anything left to reach. A negative control (the same destination, a funded key) -/// proves the leg can also see a clean open. -fn probe_admit_refusal() -> (&'static str, String) { - // The spent caller's budget is the KERNEL's reading of its budget chain (a spent bucket). - let fixture = Arc::new( - FixtureHost::new() - .governed() - .with_budget_chain(vec![budget_bucket("spent-caller", Some(0))]), - ); - let rt = hosted_runtime(Arc::clone(&fixture) as Arc); - match begin_session( - &rt, - OpenAiRealtimeCodec, - "spent-caller", - "call-admit-refusal-1", - None, - Carrier::sideband(), - Some(meter(&fixture, key_with_scopes("spent-caller", vec![]))), - 0, - ) { - Err(StartError::BudgetRefused) => {} - Ok(_) => return ("FAIL", "a spent budget was admitted a session".into()), - Err(e) => { - return ( - "FAIL", - format!("wrong refusal reason for a spent budget: {e}"), - ) - } - } - if fixture.ledger_usage("spent-caller").is_some() || !fixture.audit_log().is_empty() { - return ( - "FAIL", - "a refused admission still posted a ledger entry or opened a session -- voice posts no \ - separate floor line at Admit (nothing was ever dialed, so nothing is owed)" - .into(), - ); - } - - // NEGATIVE CONTROL: the SAME destination for a funded key must open cleanly, so this leg could not - // pass by always refusing. - let funded = Arc::new(FixtureHost::new().governed()); - let rt = hosted_runtime(Arc::clone(&funded) as Arc); - if let Err(e) = begin_session( - &rt, - OpenAiRealtimeCodec, - "funded-caller", - "call-admit-refusal-2", - None, - Carrier::sideband(), - Some(meter(&funded, key_with_scopes("funded-caller", vec![]))), - 0, - ) { - return ( - "FAIL", - format!("the negative control (a funded key) was refused too: {e}"), - ); - } - if funded.audit_log().len() != 1 { - return ( - "FAIL", - "the negative control did not open exactly one session".into(), - ); - } - - ( - "PASS", - "a spent budget is refused at the door (StartError::BudgetRefused) with no session opened \ - and zero ledger postings -- no provider dial has anything left to reach; the same \ - destination for a funded key opens cleanly" - .into(), - ) -} - -/// LEG 11 — route-failover: a hard-down provider dial trips the breaker cell on its first strike, and -/// the tripped cell refuses every FURTHER dial before any socket/URL work — the documented terminal -/// outcome, with no repeated egress once the cell is open. -fn probe_route_failover() -> (&'static str, String) { - let tokio_rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("tokio runtime"); - tokio_rt.block_on(async move { - let host = FixtureHost::new(); - let pool = "stream:conform-route-failover"; - let policy = busbar_kernel::net_guard::GuardPolicy::default(); - - // ATTEMPT 1 — breaker CLOSED: a real dial to a target the default fail-closed guard refuses (a - // plaintext `ws://` loopback address, `allow_plaintext: false` by default) genuinely fails, - // and the guard refusal's canonical signal (Auth-class => HardDown) trips the cell. - match dial_provider(&host, pool, 0, "ws://127.0.0.1:1/", policy).await { - Err(DialProviderError::Dial(_)) => {} - Ok(_) => { - return ( - "FAIL", - "the guard-refused target dialed successfully".into(), - ) - } - Err(e) => return ("FAIL", format!("expected a real dial failure, got: {e}")), - } - if !matches!( - host.breaker_state(pool, 0), - busbar_kernel::store::BreakerState::Open { .. } - ) { - return ( - "FAIL", - "the hard-down dial failure did not trip the breaker cell".into(), - ); - } - - // ATTEMPT 2 — the breaker is now OPEN: a syntactically GARBAGE target (one a real dial would - // fail differently on, `DialProviderError::Dial(Url(_))`) must instead come back - // `BreakerOpen` -- proving the breaker check runs STRICTLY BEFORE any further dial, not merely - // that a retried dial fails again for a different reason. - match dial_provider(&host, pool, 0, "not a url at all", policy).await { - Err(DialProviderError::BreakerOpen { retry_after_secs }) if retry_after_secs > 0 => {} - Ok(_) => return ("FAIL", "a tripped breaker still dialed".into()), - Err(e) => { - return ( - "FAIL", - format!("a tripped breaker let a further dial attempt happen: {e}"), - ) - } - } - - ( - "PASS", - "a hard-down dial trips the breaker cell on its first strike, and the tripped cell \ - refuses every further dial before any socket/URL work is attempted -- the terminal \ - outcome holds with no repeated egress" - .into(), - ) - }) -} - -/// LEG 12 — audit-record: one governed session lands EXACTLY ONE new admin-audit entry, carrying the -/// plane's own action literal and outcome — and a second, independent session adds exactly one more. -fn probe_audit_record() -> (&'static str, String) { - let fixture = Arc::new(FixtureHost::new()); - let host: Arc = Arc::clone(&fixture) as Arc; - let rt = hosted_runtime(host); - - let Ok(_first) = begin_session( - &rt, - OpenAiRealtimeCodec, - "alice", - "call-audit-1", - None, - Carrier::sideband(), - None, - 0, - ) else { - return ("FAIL", "a clean session open was refused".into()); - }; - let log = fixture.audit_log(); - if log.len() != 1 { - return ( - "FAIL", - format!( - "expected exactly one admin-audit row after one session, got {}", - log.len() - ), - ); - } - let row = &log[0]; - if row.action != "streaming.session.open" - || row.outcome != "applied" - || row.principal != "alice" - { - return ("FAIL", format!("wrong audit row shape: {row:?}")); - } - if row.resource != "streaming:call-audit-1" { - return ( - "FAIL", - format!("the audit row does not name this session: {row:?}"), - ); - } - - // A second, INDEPENDENT session must add exactly one MORE row. - let Ok(_second) = begin_session( - &rt, - OpenAiRealtimeCodec, - "bob", - "call-audit-2", - None, - Carrier::sideband(), - None, - 0, - ) else { - return ("FAIL", "the second session's clean open was refused".into()); - }; - let log2 = fixture.audit_log(); - if log2.len() != 2 { - return ( - "FAIL", - format!( - "expected exactly two admin-audit rows after two sessions, got {}", - log2.len() - ), - ); - } - - ( - "PASS", - "each governed session open lands exactly one admin-audit row, carrying the \ - `streaming.session.open` action literal and the `applied` outcome -- two sessions land exactly \ - two rows, never doubled, never dropped" - .into(), - ) -} - -/// LEG 13 — exit-terminal: one session ends ONCE. Two independent proofs over the same host: a -/// session's open turn is ledgered exactly once under a double teardown (the "interrupted" shape a -/// parked handler plus the node's own sweep produce), and a session's one admin-audit row survives -/// being torn down before it ever runs a frame. -fn probe_exit_terminal() -> (&'static str, String) { - let fixture = counting_host(None); - let rt = hosted_runtime(Arc::clone(&fixture) as Arc); - - // PART 1 — the teardown settle is idempotent: a second settle of the same (already-settled) turn - // is a harmless no-op, never a second ledger posting. - let Ok((core, _handle)) = begin_session( - &rt, - OpenAiRealtimeCodec, - "dave", - "call-exit-0", - None, - Carrier::sideband(), - Some(meter(&fixture, probe_key())), - 0, - ) else { - return ("FAIL", "a funded session open was refused".into()); - }; - let pcm = Bytes::from(vec![0u8; 48_000]); // one second of PCM16 uplink - let _ = core.on_client_frame(wire_of(&serde_json::json!({ - "type": "input_audio_buffer.append", - "audio": busbar_contract::media::base64_encode(&pcm), - }))); - core.settle_open_turn(); - if counts_ledgered(&fixture) != 1 { - return ("FAIL", "the teardown did not ledger the open turn".into()); - } - core.settle_open_turn(); - if counts_ledgered(&fixture) != 1 { - return ( - "FAIL", - "a second teardown after interruption ledgered the turn again instead of being a \ - harmless no-op" - .into(), - ); - } - drop(core); - - // PART 2 — the session's one admin-audit row survives an interruption: torn down before it ever - // runs a single frame. - let audited = fixture.audit_log().len(); - let Ok((core, _handle)) = begin_session( - &rt, - OpenAiRealtimeCodec, - "carol", - "call-exit-1", - None, - Carrier::sideband(), - None, - 0, - ) else { - return ("FAIL", "a clean session open was refused".into()); - }; - if fixture.audit_log().len() != audited + 1 { - return ( - "FAIL", - "the session's audit row did not land before the interruption".into(), - ); - } - // INTERRUPTED: no frame is ever run; the core is simply dropped. - drop(core); - if fixture.audit_log().len() != audited + 1 { - return ( - "FAIL", - format!( - "the interruption changed the audit row count: {}", - fixture.audit_log().len() - ), - ); - } - - ( - "PASS", - "a session's open turn is ledgered exactly once under a double teardown (a redundant \ - settle is a harmless no-op, never a second posting), and a session's one admin-audit row \ - survives being torn down before it ever runs a frame" - .into(), - ) -} - -// ── entry point ───────────────────────────────────────────────────────────────────────────────────── - -fn main() { - let args: Vec = std::env::args().collect(); - let usage = || -> ! { - eprintln!( - "usage:\n voice-conform spec \n voice-conform replay \n voice-conform cross \n voice-conform governance \n voice-conform composition " - ); - std::process::exit(2); - }; - let code = match args.get(1).map(String::as_str) { - Some("spec") => { - let dialect = args.get(2).unwrap_or_else(|| usage()); - let dir = args.get(3).unwrap_or_else(|| usage()); - spec(dialect, Path::new(dir)) - } - Some("replay") => { - let root = args.get(2).unwrap_or_else(|| usage()); - replay(Path::new(root)) - } - Some("cross") => { - let pair = args.get(2).unwrap_or_else(|| usage()); - let oa = Path::new(args.get(3).unwrap_or_else(|| usage())); - let ge = Path::new(args.get(4).unwrap_or_else(|| usage())); - let map: Value = serde_json::from_str( - &fs::read_to_string(args.get(5).unwrap_or_else(|| usage())).unwrap(), - ) - .expect("parse cross-dialect map json"); - match pair.as_str() { - "oo" => cross( - &OpenAiRealtimeCodec, - &OpenAiRealtimeCodec, - "openai", - "openai", - oa, - ge, - &map, - ), - "og" => cross( - &OpenAiRealtimeCodec, - &GeminiLiveCodec, - "openai", - "gemini", - oa, - ge, - &map, - ), - "go" => cross( - &GeminiLiveCodec, - &OpenAiRealtimeCodec, - "gemini", - "openai", - oa, - ge, - &map, - ), - "gg" => cross( - &GeminiLiveCodec, - &GeminiLiveCodec, - "gemini", - "gemini", - oa, - ge, - &map, - ), - _ => usage(), - } - } - Some("governance") => { - let cp = args.get(2).unwrap_or_else(|| usage()); - governance(cp) - } - Some("composition") => { - let slice = args.get(2).unwrap_or_else(|| usage()); - composition(slice) - } - _ => usage(), - }; - std::process::exit(code); -} diff --git a/crates/busbar-voice/src/config.rs b/crates/busbar-voice/src/config.rs deleted file mode 100644 index 4c86cac822..0000000000 --- a/crates/busbar-voice/src/config.rs +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE `streams:` SECTION'S KERNEL SEAM — the plane's grammar ([`StreamsCfg`], owned by the -//! streaming plane) behind the kernel's config-section trait, and the parse/default hooks. -//! -//! The grammar itself, its defaults and the session ceilings live with the plane. What stays here is -//! what the kernel's registry reaches: [`StreamsSection`] carries a parsed `StreamsCfg` as the -//! kernel's section object, and its `as_any` answers the inner `StreamsCfg`, so a reader that -//! downcasts the section finds the plane's own type. - -pub use crate::plane_config::StreamsCfg; - -/// A parsed `streams:` section as the kernel's config-section object. -#[derive(Debug, Clone, PartialEq)] -pub struct StreamsSection(pub StreamsCfg); - -impl busbar_kernel::plane::config::PlaneCfg for StreamsSection { - /// The voice plane's `streams:` section carries NO secret reference — the exhaustive destructure - /// (no `..`) is kept anyway so a future secret-bearing field fails to compile until someone - /// decides, HERE, whether it is a secret, exactly as `AgentsCfg`/`ToolsCfg` do. - fn secret_refs(&self) -> Vec<(String, &busbar_contract::secret_ref::SecretRef)> { - let StreamsCfg { - session: _, - session_max_secs: _, - context_window_tokens: _, - max_output_tokens: _, - } = &self.0; - Vec::new() - } - - /// `streams:` is a SINGULAR section, not a named-definition registry — there are no definitions to - /// contain, name, project, or insert. The registry methods are therefore trivial. - fn contains_def(&self, _name: &str) -> bool { - false - } - - fn def_names(&self) -> Vec<&str> { - Vec::new() - } - - fn entry_document(&self, _name: &str) -> Option { - None - } - - fn insert_def(&mut self, _name: &str, _def: &serde_json::Value) -> Result<(), String> { - Err("`streams:` has no named definitions".into()) - } - - fn container_gates(&self) -> busbar_kernel::plane::config::ContainerGateInputs { - busbar_kernel::plane::config::ContainerGateInputs { - section_hooks: Vec::new(), - containers: Vec::new(), - } - } - - fn validate_registry(&self) -> Result<(), String> { - Ok(()) - } - - /// Present only when the operator wrote CONTENT — i.e. anything other than the plane's own - /// defaults. Read by the config deletion-gate leg to refuse a present `streams:` naming a - /// compiled-out voice plane. - fn is_present(&self) -> bool { - self.0 != StreamsCfg::default() - } - - fn as_any(&self) -> &dyn std::any::Any { - &self.0 - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } - - fn clone_arc_any(&self) -> std::sync::Arc { - std::sync::Arc::new(self.0.clone()) - } -} - -/// THE LAST `streams:` SECTION THIS PROCESS PARSED — the plane's own copy of its operator posture. -/// -/// The engine's resolved config carries the named-map plane registries forward, but a SINGULAR plane -/// section like `streams:` is read at deserialize time and not re-handed to the plane afterwards: the -/// voice dispatch slot is built from `public_url` alone, and nothing calls the plane's runtime-build -/// hook. So the plane keeps what it parsed, here, and the mount reads it back when it assembles a -/// generation's runtime. A config reload re-parses and replaces it, so this always holds the posture -/// of the most recently loaded config rather than a boot-frozen one. Absent (no `streams:` block in -/// the file, so nothing was parsed) reads back as the plane's own defaults — byte-identical to what a -/// deployment that writes nothing already got. -static PARSED_SECTION: std::sync::RwLock> = std::sync::RwLock::new(None); - -/// The operator's `streams:` posture, or the plane's defaults when no block was written. Read by the -/// mount when it builds a generation's session runtime, and by the composition root when it resolves -/// the realtime provider credential for the session's configured model. -#[must_use] -pub fn configured() -> StreamsCfg { - PARSED_SECTION - .read() - .ok() - .and_then(|held| held.clone()) - .unwrap_or_default() -} - -/// The upstream model the configured session posture targets (`streams.session.model`), or `None` -/// when the operator pinned none. This is the ONE name the composition root looks up in the -/// deployment's existing model/provider catalog to find the realtime provider credential to compose — -/// the voice grammar declares no credential field of its own and gains none here. -#[must_use] -pub fn configured_session_model() -> Option { - configured().session.model -} - -/// PLANE_HOOKS.parse_section — deserialize `streams:` through the plane's own typed shape, boxed as the -/// neutral [`busbar_kernel::plane::config::PlaneCfg`]. Mirror of `mcp_parse_section` / -/// `a2a_parse_section`. UNCONDITIONAL (outside the `runtime` gate): config parse/validate is needed -/// even in the skeleton/no-`runtime` build. -pub fn streams_parse_section( - v: &serde_yaml::Value, -) -> Result, String> { - let parsed = serde_yaml::from_value::(v.clone()).map_err(|e| e.to_string())?; - // Keep what we just parsed (see `PARSED_SECTION`). Only a SUCCESSFUL parse is kept, so a refused - // config never replaces the posture a good one installed. A poisoned lock is ignored rather than - // panicking here — the read side then falls back to the plane defaults. - if let Ok(mut held) = PARSED_SECTION.write() { - *held = Some(parsed.clone()); - } - Ok(Box::new(StreamsSection(parsed)) as Box) -} - -/// PLANE_HOOKS.default_section — the empty `streams:` (mirror of `mcp_default_section` / -/// `a2a_default_section`), so an ABSENT `streams:` decodes byte-identically to the plane's own -/// [`StreamsCfg::default`]. -pub fn streams_default_section() -> Box { - Box::new(StreamsSection(StreamsCfg::default())) -} - -#[cfg(test)] -#[path = "tests/config_tests.rs"] -mod config_tests; diff --git a/crates/busbar-voice/src/lib.rs b/crates/busbar-voice/src/lib.rs deleted file mode 100644 index cd6066e131..0000000000 --- a/crates/busbar-voice/src/lib.rs +++ /dev/null @@ -1,471 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! busbar-voice — the DUPLEX / LIVE-VOICE plane (Plane 4), as ONE plugin crate. -//! -//! WHAT THIS CRATE HOLDS. The plane's DECLARATIONS — [`PLANE_DECLARATION`] (a contract -//! [`busbar_contract::plane::PlaneDeclaration`], joined kernel-side to [`PLANE_HOOKS`]) and [`DECLS`] (a -//! [`busbar_kernel::proto::ProtocolDecl`] with `codec: None`) — plus the plane's OWN four-layer -//! duplex/session IR ([`ir`]) and BOTH dialect codecs (OpenAI Realtime + Gemini Live). The live pump, -//! reader/writer bodies, and session store are implemented in [`runtime`] behind the `runtime` feature -//! (see `docs/design/BUSBAR-1.6.0.md` #18/#45). -//! -//! BOOT-MOUNTING HAS LANDED — this paragraph used to say it was separate, tracked work, and that is -//! no longer true. `plane-streaming` is IN the binary's `default` feature set, and the composition root -//! installs this crate at four sites: `register_planes` pushes [`PLANE_DECLARATION`] into the plane axis, -//! `register_diagnostics` extends with [`DIAGNOSTICS`], `register_ws_arrivals` installs -//! `mount::voice_ws_arrivals()`, and `mount_root_voice` calls `mount::install_governed_calls`. The -//! shipped binary therefore serves this plane; see [`PLANE_DECLARATION`]. -//! -//! ONE PLUGIN PER PROTOCOL, the same rule `busbar-mcp` / `busbar-a2a` state: nothing in `busbar-core` -//! names this crate. Everything the plane consumes from the engine comes through the neutral -//! `busbar-substrate` surface (+ `busbar-api`); the `busbar` BINARY — the composition root — is what -//! links it and hands [`PLANE_DECLARATION`] / [`DECLS`] to the registry installers at boot. The neutral crates -//! do NOT change to accept it, and the crate is strong-form DELETABLE (`git rm -r crates/busbar-voice` -//! leaves the neutral crates compiling) — proven by `scripts/plane-delete-test.sh voice`. -//! -//! THE SUPERSET IR, EARNED (`BUSBAR-1.6.0.md` #18/#45). With TWO dialects — OpenAI Realtime and -//! Gemini Live — the plane earns a cross-dialect superset IR: a neutral vocabulary both codecs read and -//! write, so `Plane::has_superset_ir("voice")` is true — DERIVED from the length-2 `VOICE_WIRE_FORMATS` -//! (the A2A rule: a plane earns a superset at its SECOND wire format and not before). The plane realizes -//! that superset as its OWN shared IR types ([`ir`]), not the LLM-style `DialectCodec` facade, so -//! [`DECLS`] stays `codec: None` exactly as MCP / A2A do. - -/// THE DUPLEX IR AND THE DIALECT CODECS, RE-EXPORTED FROM THE PLANE CRATE'S OWN `codec` MODULE. -/// -/// The plane-4 session intermediate representation (media, control, events, tools, session config, -/// usage), the shared duplex reader/writer and the Gemini Live dialect are that module's. They are -/// re-exported HERE as `ir`, the path every caller of this crate already spells, and resolve to the -/// same items. -pub use busbar_plane_streaming::codec::ir; - -/// THE `streams:` CONFIG SECTION — the voice plane's owned config grammar ([`config::StreamsCfg`]) and -/// its `parse_section` / `default_section` seam hooks. UNCONDITIONAL (outside the `runtime` gate): -/// the plane DECLARES and PARSES `streams:` even in the default feature-off build, so config validation -/// reaches the owned grammar whether or not the live session pump is compiled in. -pub mod config; - -// The streaming plane's `streams:` grammar, which `config` carries into the kernel's registry. -use busbar_plane_streaming::config as plane_config; - -pub use busbar_plane_streaming::diagnostics; - -/// THE VOICE PLANE'S PLANE-CONTRIBUTED DIAGNOSTICS — the `&'static [&'static Diagnostic]` the -/// composition root installs via `busbar_kernel::diagnostics::install_diagnostics`, re-exported at -/// the crate root so the `busbar` binary names one stable path (`busbar_voice::DIAGNOSTICS`), exactly -/// as `busbar_mcp::DIAGNOSTICS` / `busbar_a2a::DIAGNOSTICS`. BOOTED BY THE BINARY: `main.rs`'s -/// `register_diagnostics` carries `installed.extend_from_slice(busbar_voice::DIAGNOSTICS)` under -/// `plane-streaming`, which is in `default`. (This line used to read "not yet booted … at M5"; M5 -/// landed.) See [`diagnostics`]. -pub use diagnostics::DIAGNOSTICS; - -/// THE ONE ENTRY THIS PLUGIN IS REGISTERED THROUGH — everything a composition root that linked it -/// wires, one item per registration axis, read off the crate rather than spelled at the root. The -/// root's manifest names this crate and the axes it registers on -/// (`[package.metadata.busbar.linked-axes]`: the plane — its protocol declaration is not installed -/// on the protocol axis — its owned diagnostics, its inbound WS-accept arrivals, and its realtime -/// provider composed off the deployment's catalog; the last two are wired behind `runtime`, exactly -/// as the mount hooks are); its build script turns that into one table per axis over these items, -/// and the root's source names no item of this crate. -pub mod linked { - /// The WS-accept axis and the provider-composition axis. - #[cfg(feature = "runtime")] - pub use crate::mount::{compose_plane as compose, install_arrivals as install_ws_arrivals}; - /// The diagnostics axis. - pub use crate::DIAGNOSTICS; - /// The plane axis: the contract declaration, joined kernel-side to the behaviour table. - pub use crate::{PLANE_DECLARATION, PLANE_HOOKS}; - /// The claims axis: the pure plane the composition root's boot seal registers, and the claims it - /// declares. - pub const PLANE: busbar_plane_streaming::StreamingPlane = - busbar_plane_streaming::StreamingPlane::EMPTY; - /// The bytes that plane claims. - pub const CLAIMS: &[busbar_contract::grammar::Claim] = - ::CLAIMS; -} - -// THE T2 LIVE-SESSION RUNTIME + both topologies — behind the `runtime` cargo feature (OFF by default, -// HARD RULE 4). The default / prod build compiles the IR + declarations only (no async runtime pulled -// in); turning the feature on compiles the duplex session pump, the D2 metering lease, the durable -// `SessionScope` binding, and the browser-sideband / telephony topologies — all feature-gated OFF by -// default (HARD RULE 4), not because the code is incomplete. -#[cfg(feature = "runtime")] -pub mod runtime; - -/// THE NODE'S OPEN-CALL TABLE — the client-served tool calls this node's sessions are waiting on, -/// and the port the session runtime reaches it through; composed by this plane's own `compose` step. -#[cfg(feature = "runtime")] -pub use busbar_plane_streaming::open_calls as governed; -#[cfg(feature = "runtime")] -pub mod topology; - -/// THE DATA-ROUTE MOUNT — the voice plane's `PLANE_HOOKS` `routes` / `claims` / `admission` / `build` -/// hooks and the neutral handlers behind them (see [`mount`]). Behind the `runtime` feature because the -/// route handlers open governed sessions through the T2 topologies (`crate::topology`, itself -/// runtime-gated); the default feature-off build mounts nothing and keeps the byte-unchanged -/// `PLANE_HOOKS` (the hooks stay empty/`None`, wired through the [`VOICE_CLAIMS`] / [`VOICE_ADMISSION`] / -/// [`VOICE_BUILD`] / [`VOICE_ROUTES`] cfg-split consts, exactly as [`VOICE_BUILD_RUNTIME`] is). -#[cfg(feature = "runtime")] -pub mod mount; - -/// THIS PLANE'S OWN TEST-KIT — the in-memory engine-host double, the loopback HTTP provider and the -/// metrics capture this crate's batteries drive. A plane tests ITSELF (1.6.0 Locked Decision #33: -/// "the kernel never tests a plugin"), so these doubles live HERE rather than in the deleted -/// `busbar_kernel::testkit`. Behind `runtime` for the same reason `mount`/`topology` are — every cell -/// that drives them is runtime-gated, and the doubles name the async/metrics deps that feature pulls. -#[cfg(all(feature = "runtime", any(test, feature = "test-support")))] -pub mod testkit; - -/// THE `PLANE_HOOKS.build_runtime` VALUE — wired to the real runtime constructor -/// ([`runtime::build_runtime`]) behind the `runtime` feature, `None` when the feature is off so the -/// default `PLANE_HOOKS` is byte-unchanged. Split by `cfg` because the -/// `runtime` module (and its constructor) only exist behind the feature. -// `type_complexity`: this fn-pointer is the mirror of the frozen `PlaneDecl::build_runtime` field type -// (`busbar_kernel::plane::registry`), which carries the SAME `#[allow(clippy::type_complexity)]` — the -// shape is the ABI, not a factorable local type. -#[cfg(feature = "runtime")] -#[allow(clippy::type_complexity)] -const VOICE_BUILD_RUNTIME: Option< - fn( - &dyn std::any::Any, - Option<&dyn busbar_kernel::plane_host::PlaneSlots>, - ) -> std::sync::Arc, -> = Some(runtime::build_runtime); -#[cfg(not(feature = "runtime"))] -#[allow(clippy::type_complexity)] -const VOICE_BUILD_RUNTIME: Option< - fn( - &dyn std::any::Any, - Option<&dyn busbar_kernel::plane_host::PlaneSlots>, - ) -> std::sync::Arc, -> = None; - -// ── THE DATA-ROUTE MOUNT HOOKS — cfg-split exactly as `VOICE_BUILD_RUNTIME`, so the default -// (feature-off) `PLANE_HOOKS` is BYTE-UNCHANGED (claims empty, admits no one, builds no slot, mounts no -// route) and the `runtime` build wires the real neutral hooks in `crate::mount`. Route-mounting the -// pump needs the T2 topologies (runtime-gated) to open governed sessions, so these arm in lock-step -// with the runtime — a plane installed at boot (only under `plane-streaming`, which turns on -// `busbar-voice/runtime`) both mounts and admits, keeping the ratchet's "mounted ⇒ admitted" true. - -/// `PLANE_HOOKS.build` — construct the per-generation dispatch slot from `public_url` (the audience), -/// `crate::mount::voice_build` with the feature on; `|_| None` (no slot) with it off. -#[cfg(feature = "runtime")] -const VOICE_BUILD: fn( - &busbar_kernel::plane::registry::BuildCtx, -) -> Option> = mount::voice_build; -#[cfg(not(feature = "runtime"))] -const VOICE_BUILD: fn( - &busbar_kernel::plane::registry::BuildCtx, -) -> Option> = |_ctx| None; - -/// `PLANE_HOOKS.claims` — the one audience-checked base the plane answers on, or nothing off-feature. -#[cfg(feature = "runtime")] -const VOICE_CLAIMS: fn(&dyn std::any::Any) -> Vec<(String, &'static str)> = mount::voice_claims; -#[cfg(not(feature = "runtime"))] -const VOICE_CLAIMS: fn(&dyn std::any::Any) -> Vec<(String, &'static str)> = |_slot| Vec::new(); - -/// `PLANE_HOOKS.admission` — the RFC 8707 audience bound from `public_url`, or `None` off-feature. -#[cfg(feature = "runtime")] -const VOICE_ADMISSION: fn(&dyn std::any::Any) -> Option = - mount::voice_admission; -#[cfg(not(feature = "runtime"))] -const VOICE_ADMISSION: fn(&dyn std::any::Any) -> Option = - |_slot| None; - -/// `PLANE_HOOKS.routes` — the four neutral ingress routes, or `None` (no data path) off-feature. -#[cfg(feature = "runtime")] -#[allow(clippy::type_complexity)] -const VOICE_ROUTES: Option< - fn(&dyn std::any::Any) -> Vec, -> = Some(mount::voice_routes); -#[cfg(not(feature = "runtime"))] -#[allow(clippy::type_complexity)] -const VOICE_ROUTES: Option< - fn(&dyn std::any::Any) -> Vec, -> = None; - -/// `PLANE_HOOKS.hydrate` — boot-rehydrate the durable voice-session working-set before any listener -/// binds ([`mount::voice_hydrate`]); `None` off-feature so the default decl is byte-unchanged. -#[cfg(feature = "runtime")] -const VOICE_HYDRATE: Option = Some(mount::voice_hydrate); -#[cfg(not(feature = "runtime"))] -const VOICE_HYDRATE: Option = None; - -/// `PLANE_HOOKS.start` — the post-listener boot step ([`mount::voice_start`]); `None` off-feature so the -/// default decl is byte-unchanged. -#[cfg(feature = "runtime")] -const VOICE_START: Option = Some(mount::voice_start); -#[cfg(not(feature = "runtime"))] -const VOICE_START: Option = None; - -/// THE VOICE (STREAMING) PLANE'S CAPABILITY KEY (`"voice"`) — the string the composition root flips -/// onto the unified kernel loop's SESSION admit and the same string the voice session gauntlet reports -/// from its `GauntletPlane::capability_key`. Re-exported at the crate root so the `busbar` binary names -/// ONE stable path (`busbar_voice::PLANE_KEY`) and the plane and the flip cannot drift onto two. -pub use busbar_plane_streaming::codec::PLANE_KEY; - -/// The two dialect names this plane speaks (the streaming plane's own declarations): the [`DECLS`] -/// registry key is the first, and both are the plane's [`PLANE_HOOKS`] wire formats. -pub use busbar_plane_streaming::provider::{GEMINI_LIVE, OPENAI_REALTIME}; - -// A dialect's provider address rules (the streaming plane's), read by the mount's provider dial -// (runtime-only, as the mount is). -#[cfg(feature = "runtime")] -use busbar_plane_streaming::provider as plane_provider; - -// A live session's durable row (the streaming plane's shape), bound by the runtime's session scope. -#[cfg(feature = "runtime")] -use busbar_plane_streaming::session_row as plane_session_row; - -/// THE TWO WIRE FORMATS this plane translates: OpenAI Realtime and Gemini Live. Its length (== 2) is -/// what EARNS this plane a superset IR (`Plane::has_superset_ir` is DERIVED from this list's length), -/// the A2A discipline — a plane earns a superset at its SECOND wire format and not before. -const VOICE_WIRE_FORMATS: &[&str] = &[OPENAI_REALTIME, GEMINI_LIVE]; - -/// THE VOICE PLANE'S PROVIDER-FACING BEARER — the ONE construction that plans the provider-facing -/// `Authorization: Bearer ` from the RESOLVED provider-credential string, and NEVER a -/// caller/governance token passed through. Both [`voice_egress_auth_headers`] (the -/// `egress_auth_headers` decl) and the LIVE one-shot serving passes ([`mount::serve_sdp`]) authenticate -/// the provider hop through THIS function, so the lane-constant builder the `egress_tests` battery -/// proves is the SAME code the live SDP broker runs — the tested builder IS the live credential path. -/// It authenticates busbar's OWN authority to the provider (the real provider key on the broker hop), -/// never the caller's inbound bearer (which the Auth plugin already consumed at the door). -/// -/// NOTE — the SDP broker authenticates with the PROVIDER key (busbar brokers the call server-side). -/// An `ek_`-RELAY model (forward the browser's ephemeral secret as the upstream credential) is a -/// distinct protocol shape: it needs a resolved `ek_` source — the inbound `RouteAuth::Key` -/// `Authorization` slot carries the caller's GOVERNANCE key (forwarding it upstream would leak -/// busbar's own authority), so an `ek_` relay would have to arrive on a non-`Authorization` inbound -/// slot or be minted+persisted server-side. That is a flagged protocol follow-on, not this hop. -pub(crate) fn voice_provider_bearer( - key: &str, -) -> Vec<(axum::http::HeaderName, axum::http::HeaderValue)> { - busbar_kernel::proto::bearer_auth_headers(OPENAI_REALTIME, key) -} - -/// `ProtocolDecl::egress_auth_headers` — the plain-Bearer arm of `busbar-llm`'s OpenAI dialect (the -/// Bedrock module hands in its SigV4 signer the same way). A pure function of the resolved credential -/// string (it reads nothing off the [`SigningContext`]), so [`DECLS`] declares it LANE-CONSTANT. -fn voice_egress_auth_headers( - key: &str, - _ctx: &busbar_kernel::proto::SigningContext, -) -> Vec<(axum::http::HeaderName, axum::http::HeaderValue)> { - voice_provider_bearer(key) -} - -use busbar_contract::{ - plane::{BillableClass, PER_SESSION, TOKEN_FAMILY}, - plugin::Kind, -}; -use busbar_kernel::plane::registry::PlaneHooks; - -/// THE VOICE PLANE'S DECLARATION — contract data the composition root registers at boot, joined -/// kernel-side to [`PLANE_HOOKS`], so the `busbar` binary names one stable path -/// (`busbar_voice::PLANE_DECLARATION`). Together the two declare the plane's identity -/// (key, config section, audit kind, wire formats), is INSTALLED at boot behind the `plane-streaming` -/// feature with `build_runtime` wired to the real runtime constructor, and — behind the `runtime` -/// feature (which `plane-streaming` turns on) — MOUNTS its data plane: `build` erases the dispatch slot from -/// `public_url`, `claims`/`admission` bind the plane's RFC 8707 audience, and `routes` mounts the five -/// ingress doors across both dialects (see [`mount`]), and the BOOT hooks `hydrate` / `start` rehydrate the durable session -/// working-set before the listener and confirm readiness after. What stays `None` is the `ProtocolDecl` -/// `handler`: a long-lived duplex SESSION is not a one-shot `RequestHandler` (whose `OperationHandler` -/// cells are request→response codecs), so the session driver lives in the plane's own runtime -/// (`crate::topology`) over the neutral pump, not on that field. The neutral registry unions this -/// without naming it (the MCP/A2A precedent). -pub const PLANE_DECLARATION: busbar_contract::plane::PlaneDeclaration = - busbar_contract::plane::PlaneDeclaration { - key: "streaming", - // A MOUNTED plane, not the fallback catch-all. - fallback: false, - // THE DUPLEX / LIVE-VOICE PLANE'S DECLARING SECTION is `streams:` (1.6.0 config-seam KEYSTONE - // stage C) — the top-level noun whose mere existence declares this plane, the fourth plane - // noun beside `pools:`/`tools:`/`agents:`. The registry `key` stays the crate identity - // (`"voice"`), so key ≠ config_section here exactly as the MCP plane's key is `"mcp"` while its - // declaring section is `"tools:"`. The plane parses `streams:` through the seam - // (`parse_section`) once its config grammar lands; the plane declares the noun now so - // core names no `streams`/`voice` parse target and voice can register `streams:` and boot. - config_section: "streams", - // One session is granted at the whole-session granularity. - scope_kinds: &["session"], - subject_noun: "streaming session", - admin_noun: "streaming-session", - audit_kind: "streaming_session", - card_signing_domain: None, - card_kid_prefix: None, - // config-seam: voice OWNS the `streams:` grammar. `"streams"` is NOT in core's - // `CORE_OWNED_CONCRETE_SECTIONS` (providers/models/pools/rate_card/limits), so the dup-claim - // guard admits this claim; a second claimant of `streams` is refused by construction. - owned_config_sections: &["streams"], - // The classes the plane crate declares (`busbar_plane_streaming::meta`), by its own symbols. - // - // `cached_tokens` is deliberately NOT here (architect ruling #71, money-model integrity): the - // dialects' own `cached_tokens` figure is a SUBSET of the input token classes above (OpenAI - // Realtime reports it INSIDE `input_token_details`; Gemini Live the same, inside - // `promptTokensDetails`), so a rate card that priced it too would bill the same cached input - // twice. A billable class must be disjoint from every other one the plane declares; a subset - // measure is attribution, not a class. The figure is still carried, unbilled, as the - // `cached_tokens` content fact (`busbar_plane_streaming::meta::FACT_CACHED_TOKENS`). - billable_classes: &[ - BillableClass { - class: busbar_plane_streaming::meta::CLASS_AUDIO_TOKENS_IN.as_str(), - family: TOKEN_FAMILY, - }, - BillableClass { - class: busbar_plane_streaming::meta::CLASS_AUDIO_TOKENS_OUT.as_str(), - family: TOKEN_FAMILY, - }, - BillableClass { - class: busbar_plane_streaming::meta::CLASS_TEXT_TOKENS_IN.as_str(), - family: TOKEN_FAMILY, - }, - BillableClass { - class: busbar_plane_streaming::meta::CLASS_TEXT_TOKENS_OUT.as_str(), - family: TOKEN_FAMILY, - }, - BillableClass { - class: busbar_plane_streaming::meta::CLASS_AUDIO_SECONDS_IN.as_str(), - family: "duration", - }, - BillableClass { - class: busbar_plane_streaming::meta::CLASS_TOOL_CALLS.as_str(), - family: "count", - }, - ], - // The fee unit this plane counts: one per opened session (`SessionAccount::open`). A turn never - // passes per-request admission, so `streams.fees.per_request` would charge nothing — refused. - fee_units: &[PER_SESSION], - metric_families: &[], - record_kinds: &[], - served_op_classes: &[], - // The realtime provider is resolved through the deployment's `providers:` catalog (the - // model this plane's section names, then that model's provider), so this plane consumes - // `providers`. `models` belongs to the plane that owns it. - required_config_sections: &[Kind::Transport.root()], - trust_keys: &[], - caller_credential_refusal: None, - }; - -/// THE PLANE'S BEHAVIOUR — every hook the kernel runs for it, handed over BESIDE -/// [`PLANE_DECLARATION`] and joined to it kernel-side (`PlaneDecl::assemble`). The registration item -/// is the declaration, which names no kernel type; this table is typed by kernel seams and stays on -/// the kernel side of every fold. -pub const PLANE_HOOKS: PlaneHooks = PlaneHooks { - // TWO dialects ⇒ superset IR, DERIVED from this list's length (see VOICE_WIRE_FORMATS). - wire_format_names: || VOICE_WIRE_FORMATS, - // MOUNTED (behind `runtime`): the plane builds its dispatch slot from `public_url`, claims TWO - // audience-checked bases — `/v1/realtime` (OpenAI) and `/v1/realtime/gemini` (Gemini Live) — - // under the SAME bound audience, and mounts the five ingress routes across both dialects, whose - // handlers open governed sessions through `run_gauntlet_session` (see `crate::mount`). - // Off-feature these stay empty/`None` (the byte-unchanged default decl). A plane installed at - // boot is installed under `plane-streaming` (⇒ `busbar-voice/runtime`), so it always both mounts and - // admits — the ratchet's "mounted ⇒ admitted" holds by construction. - claims: VOICE_CLAIMS, - admission: VOICE_ADMISSION, - build: VOICE_BUILD, - routes: VOICE_ROUTES, - admin_routes: None, - openapi: None, - // BOOT hooks — rehydrate the durable session working-set before the listener, then the - // post-listener readiness step. Wired behind `runtime` (see [`VOICE_HYDRATE`] / [`VOICE_START`]); - // `None` off-feature so the default decl is byte-unchanged. - hydrate: VOICE_HYDRATE, - start: VOICE_START, - config_validate: None, - named_def_list: None, - named_def_get: None, - registry_contains: None, - reresolve_gates: None, - openapi_schemas: None, - on_swap: None, - // config-seam: voice PARSES its owned `streams:` section through its own typed `StreamsCfg`, - // so `DeployCfg` names no `busbar_voice` type (the MCP `mcp_parse_section` / A2A - // `a2a_parse_section` pattern). UNCONDITIONAL — the hook lives outside the `runtime` gate so - // the default feature-off build validates `streams:` too. - parse_section: Some(config::streams_parse_section), - parse_endpoint: None, - lower_endpoint: None, - // RUNTIME HOOK — wired to the real per-generation runtime constructor behind the `runtime` - // feature (see [`VOICE_BUILD_RUNTIME`]); `None` when the feature is off so the default build is - // byte-unchanged. The DATA-plane hooks (`build` / `claims` / `admission` / `routes`) and the - // BOOT hooks (`hydrate` / `start`) are wired too (see [`mount`]) — all behind `runtime`. - build_runtime: VOICE_BUILD_RUNTIME, - viewer: None, - retain_verify_gates: None, - // config-seam: the empty `streams:` default — the plane's own statement that an ABSENT section - // is byte-identically `StreamsCfg::default()` (mirror of `a2a_default_section` / - // `mcp_default_section`). The kernel's declared-section carrier holds no entry for an absent - // section; this hook is what that absence means to the plane. - default_section: Some(config::streams_default_section), - resolve_provider: None, -}; - -/// THE VOICE PLANE'S PROTOCOL DECLARATION — a `ProtocolDecl` with `codec: None`, re-exported at the -/// crate root so the `busbar` binary names one stable path (`busbar_voice::DECLS`). Like MCP / A2A, it -/// declares NO codec even though the plane now speaks TWO dialects: the plane realizes its cross-dialect -/// superset as its OWN shared IR types (the [`ir`] module's `DuplexReader` / `DuplexWriter` pair, one -/// per dialect), not the LLM-style `DialectCodec` facade the `codec` field carries. -/// -/// NOT YET MOUNTED: `handler: None` and `verbs: &[]` — route-mounting the duplex handler / -/// gauntlet-session entry is follow-on work. Every other field carries the neutral default a codec-less -/// protocol declares (the MCP `DECL` shape). -pub static DECLS: busbar_kernel::proto::ProtocolDecl = busbar_kernel::proto::ProtocolDecl { - name: OPENAI_REALTIME, - // THE SUPERSET IS ITS OWN IR (`BUSBAR-1.6.0.md` #18/#45): the two dialects meet in the plane's - // shared IR types, not a `DialectCodec` facade — so this field stays `None`, the MCP/A2A precedent. - codec: None, - // NOT YET MOUNTED: no request handler wired here yet — the duplex pump exists in `crate::runtime`; - // route-mounting its entry point is follow-on work. - handler: None, - // NOT YET MOUNTED: no verbs declared yet (the long-lived Subscribe/Control shapes arrive with the - // boot-mounting pass). - verbs: &[], - head_keys: &[], - streaming_content_type: None, - array_stream_shim_key: None, - native_tool_id_prefix: None, - ingress_auth: busbar_kernel::proto::IngressAuth::Bearer, - // THE ONE EGRESS CREDENTIAL MECHANISM: the provider bearer / WebRTC `ek_` / telephony carrier - // credential is planned onto the dial's headers HERE, never a caller token passed through (see - // [`voice_egress_auth_headers`]). LANE-CONSTANT: the builder is a pure function of the resolved - // credential string and reads nothing off the `SigningContext`, so the boot path may prebuild the - // header set once per lane (the plain-Bearer discipline the LLM plane's OpenAI dialect declares). - egress_auth_headers: Some(voice_egress_auth_headers), - egress_auth_lane_constant: true, - egress_scheme: None, - stream_usage_requires_opt_in: false, - // Promoted writer facts: this plane declares no cross-dialect codec and has no writer, so every - // promoted fact is the `ProtocolWriter` trait DEFAULT — the same values the codec-less MCP `DECL` - // states. Inert for a `codec: None` protocol, but the declaration must state them. - requires_max_tokens: false, - stop_sequence_cap: None, - cache_markers_model_gated: false, - fills_thought_signature: false, - frame_after_message_start: None, - reshapes_body_at_path_base: false, - max_cache_control_breakpoints: None, - quota_exceeded_status: axum::http::StatusCode::TOO_MANY_REQUESTS, - ingress_is_eventstream: false, - emits_sse_done_terminator: false, - max_citations_per_delta: None, - egress_user_agent: busbar_kernel::proxy::EGRESS_UA_DEFAULT, - has_model_in_url: false, - auth_failure_status_and_kind: ( - axum::http::StatusCode::UNAUTHORIZED, - busbar_kernel::proto::ERR_TYPE_AUTHENTICATION, - ), - ingress_relays_amzn_headers: false, - ingress_relayed_response_header_names: &[], - auth_failure_message: "authentication failed", - uses_array_stream_shim: false, - has_native_path_not_found: false, - egress_stream_accept: busbar_kernel::proxy::TEXT_EVENT_STREAM, - models_list_envelope: None, - // Identified by its EXPLICIT mount, never by a wire fingerprint — so it claims no router or - // residual rung, contributes no vendor response metadata, and is not the residual default. - claims: None, - residual_claims: None, - residual_default: false, - vendor_response_metadata: None, - list_models_fingerprint_headers: &[], - static_headers: &[], -}; diff --git a/crates/busbar-voice/src/mount.rs b/crates/busbar-voice/src/mount.rs deleted file mode 100644 index 4885e631e8..0000000000 --- a/crates/busbar-voice/src/mount.rs +++ /dev/null @@ -1,1672 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE VOICE PLANE'S DATA-ROUTE MOUNT (behind the `runtime` feature) — the structural surface that -//! turns `PLANE_HOOKS`' `routes` / `claims` / `admission` / `build` hooks from `None`/empty into the -//! plane's real ingress door. -//! -//! ## The two slots, and which one this is -//! -//! Voice contributes a DISPATCH slot ([`VoiceMount`]) through [`PlaneDecl::build`], carried in -//! `plane_slots` under the plane's decl key (`"voice"`). It is the object [`voice_claims`] / -//! [`voice_admission`] / [`voice_routes`] all read, and the SAME object the core route adapter hands a -//! handler as `PlaneReqCtx::slot`. It carries the plane's RFC 8707 audience (derived from the -//! deployment's `public_url`, the A2A precedent — one reading so the audience a caller is told to ask -//! for and the one busbar demands cannot drift apart) plus a live [`VoiceRuntime`] the route handlers -//! open governed sessions from. `None` when there is no `public_url` — a deployment with no receiving -//! origin fronts nothing, claims nothing and admits no one (the A2A delegation-only asymmetry). -//! -//! ## Five routes, two dialects, and what is live -//! -//! Every route MOUNTS, is AUDIENCE-CHECKED (`RouteAuth::Key`, under the plane's one audience) and, on -//! arrival, runs the governed session-open through [`crate::topology::begin_session`] / -//! [`crate::topology::telephony::begin_telephony`] — which go through `run_gauntlet_session` -//! (verify-strictly-before-charge). The two one-shot HTTP passes (`ek_` mint, SDP broker) reach the -//! provider once one is composed ([`install_provider`]); the browser-sideband WS accept is -//! control-only by design (media is peer-to-peer, see `crate::topology::webrtc`); the telephony and -//! Gemini Live WS accepts DIAL the composed provider through [`crate::topology::dial_provider`] and -//! proxy both sockets (see [`ws_accept`]) once one is composed. With no provider composed, every leg -//! still governs and mounts — it serves the client socket only, exactly as documented in -//! `docs/voice.md`. - -use crate::ir::codec::gemini::GeminiLiveCodec; -use crate::ir::codec::{DuplexReader, DuplexWriter, OpenAiRealtimeCodec}; -use crate::ir::config::SessionConfig; -use crate::plane_provider::{provider_ws_url, redact_url_credentials}; -use crate::runtime::carrier::Carrier; -use crate::runtime::scope::SessionHandle; -use crate::runtime::session::{serve_to_teardown, serve_with_sweep, UplinkForwarder, VoiceSession}; -use crate::runtime::{ClientRelay, VoiceRuntime}; -use crate::topology::minter_https::HttpsTokenMinter; -use crate::topology::telephony::{begin_telephony, g711_config, open_admitted_telephony}; -use crate::topology::webrtc::TokenMinter; -use crate::topology::{ - begin_session, dial_provider, open_admitted_session, stream_breaker_key, SessionGauntlet, - StartError, -}; -use busbar_kernel::config::RootCfg; -use busbar_kernel::egress::engine::{send_bounded, EngineClient}; -use busbar_kernel::ingress::byte_duplex::serve_messages; -use busbar_kernel::ingress::duplex_ws::{ - accept_gauntlet, install_ws_arrivals, WsAcceptFuture, WsArrival, WsArrivalSpec, -}; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use busbar_kernel::plane::observe::Counted; -use busbar_kernel::plane::registry::{BuildCtx, PlaneBootCtx}; -use busbar_kernel::plane::PlaneAdmission; -use busbar_kernel::plane_host::{EngineHost, GateOutcome, TransformVerdict}; -use busbar_kernel::plane_host::{GauntletPlane, GauntletRequest}; -use busbar_kernel::plane_routes::{PlaneReqCtx, PlaneRouteSpec}; -use busbar_kernel::{door::UnitKeyMint, net_guard::GuardPolicy}; -use bytes::Bytes; -use http_body_util::{BodyExt, Full}; -use std::any::Any; -use std::sync::Arc; - -/// The bounded `pool` label the voice FRONT DOOR (the voice-server cell) emits under — a constant, not -/// a caller value, so the series count stays bounded. The outbound provider dial (the voice-client -/// cell) counts on its own `busbar_upstream_attempts_total` family in `topology::dial_provider`. -const FRONT_DOOR_POOL: &str = "streaming-server"; - -/// The session-open method label the hook gate/tap projection carries — the single operation a voice -/// front-door request performs. Bounded (one value), so it is safe as the hook `tool` slot the -/// operator's gate reads. -const SESSION_OPEN_METHOD: &str = "session.open"; - -/// THE ADMIN-AUDIT ACTION every voice session-open mutation lands under. Named ONCE here because two -/// sites write it: the APPLIED row a governed open seals ([`crate::topology::begin_session`]) and the -/// REJECTED row a committed-but-unreadable rewrite refuses under ([`hook_tap`]). An operator reading -/// the audit stream must see ONE action with two outcomes, not two spellings that drift apart. -pub(crate) const SESSION_AUDIT_ACTION: &str = "streaming.session.open"; - -/// THE HOOK CONTAINER the voice plane's session-open gate/tap fire under — the plane's SINGULAR config -/// section noun (`streams`), since voice declares no per-registration container (its config is one -/// object, not a named-definition map). The operator attaches a session-open gate to this ONE -/// container, and `gate_attached(plane_key, container)` reads it here. -const GATE_CONTAINER: &str = "streams"; - -/// THE SCOPE KIND a key must hold to open a live session — the plane's one declared scope kind (see -/// `PLANE_DECLARATION.scope_kinds`). Named once here, so the vocabulary an operator writes in -/// `allowed_scopes: [{ kind: session, value: … }]` and the vocabulary the door demands cannot drift. -const SESSION_SCOPE_KIND: &str = "session"; - -/// MAY THIS KEY OPEN A LIVE SESSION? The plane's authorization question, asked of the presenting key's -/// own grant and nothing else — the same shape MCP asks about a tool and A2A asks about an agent. -/// -/// The value asked about is the voice front door's pool ([`FRONT_DOOR_POOL`]), which is the one pool a -/// voice session is ever served on, so an operator narrows a key to voice by granting -/// `{ kind: session, value: voice-server }` and nothing wider. A key with NO scope list at all is the -/// store's wildcard and is granted every kind, exactly as it is on every other plane; a key that -/// carries a list must have this entry in it. -#[must_use] -pub fn session_scope_allowed(key: &busbar_contract::records::VirtualKey) -> bool { - key.scope_allowed(SESSION_SCOPE_KIND, FRONT_DOOR_POOL) -} - -/// The refusal a key without session scope gets: the plane's own fail-closed answer, in the same -/// plain-text shape every other voice refusal takes, and BEFORE any hook, account, durable row or dial. -fn session_scope_refusal() -> axum::response::Response { - refusal( - axum::http::StatusCode::FORBIDDEN, - "streaming session-open refused: the presenting key holds no session scope for this streaming pool \ - (fail closed)", - ) -} - -/// A CONFIGURED PROVIDER ENDPOINT the one-shot mint / SDP-broker passes reach the realtime provider -/// through — the base origin plus the real server-side key. Composed by the composition root through -/// [`install_provider`]; a loopback test injects one directly into [`open_governed`] instead. The real -/// key stays server-side: it authenticates only the busbar↔provider hop and never reaches a browser -/// payload, and it is never rendered into any public accessor here. -pub(crate) struct ProviderEndpoint { - /// The provider origin (scheme + authority, e.g. `https://api.openai.com`). - pub base_url: String, - /// The REAL provider key, held server-side. - pub api_key: String, -} - -/// THE COMPOSED REALTIME PROVIDER — the one endpoint the mint / SDP-broker passes dial, written once -/// by the composition root and read by every route thereafter. -/// -/// SET-ONCE, first writer wins, exactly like the composition root's other process-wide installs (the -/// WS-accept arrivals, the hostless-egress driver). It is written after the deployment's config -/// resolves — which is later than the per-generation dispatch slot is built — so the routes read it -/// HERE rather than off the slot, and a config apply that rebuilds the slot cannot drop it. -static COMPOSED_PROVIDER: std::sync::OnceLock = std::sync::OnceLock::new(); - -/// COMPOSE the realtime provider endpoint the mint / SDP-broker passes authenticate with — the -/// composition root's one write of the voice plane's egress credential. -/// -/// `base_url` is the provider origin and `api_key` the credential the deployment already resolved -/// through its ordinary provider catalog and secret resolver — the plane's `streams:` grammar carries -/// no credential field and gains none. Returns `false` when an endpoint was already composed (the -/// first write stands), so a second caller is a no-op rather than a silent credential swap. -pub fn install_provider(base_url: impl Into, api_key: impl Into) -> bool { - COMPOSED_PROVIDER - .set(ProviderEndpoint { - base_url: base_url.into(), - api_key: api_key.into(), - }) - .is_ok() -} - -/// COMPOSE the realtime provider endpoint from a provider-catalog entry — the form the composition -/// root calls, so the plane resolves its own credential through the deployment's ordinary secret -/// resolver rather than the root handing plaintext across. -/// -/// `base_url` and `api_key` are the origin and the secret REFERENCE the deployment already declared -/// for this provider in its provider catalog; `resolver` is the neutral secret-resolver seam (the -/// same one every other credential in the deployment is resolved through, built-in `env`/`file` plus -/// any `kind: secret` module). Fails closed with the resolver's own message: an unresolvable -/// reference composes nothing, so the mint / SDP routes keep answering "no provider composed" rather -/// than dialing with an empty credential. `Ok(false)` means an endpoint was already composed. -pub fn compose_provider( - base_url: impl Into, - api_key: &busbar_contract::secret_ref::SecretRef, - resolver: &dyn busbar_contract::secret::SecretResolve, -) -> Result { - let resolved = resolver.resolve_string(api_key)?; - Ok(install_provider(base_url, resolved)) -} - -/// The composed provider endpoint, or `None` when the composition root composed none — in which case -/// the mint / SDP routes still answer "governed, but no provider credential composed". -pub(crate) fn composed_provider() -> Option<&'static ProviderEndpoint> { - COMPOSED_PROVIDER.get() -} - -/// Whether a realtime provider endpoint has been composed — i.e. whether the mint / SDP passes serve -/// rather than answering "no provider composed". The credential itself is never exposed. -#[must_use] -pub fn provider_composed() -> bool { - COMPOSED_PROVIDER.get().is_some() -} - -/// The composed provider's ORIGIN (never its key) — the non-secret half, for a boot log or a -/// conformance probe that needs to confirm which endpoint this deployment composed. -#[must_use] -pub fn composed_provider_base_url() -> Option<&'static str> { - COMPOSED_PROVIDER.get().map(|p| p.base_url.as_str()) -} - -// ── THE SECOND DIALECT'S PROVIDER ENDPOINT (the second-dialect route) ───────────────────────────────────────────────────── -// -// The K1 provider seam above is single-endpoint and OpenAI-shaped: one `OnceLock`, one credential, one -// `Authorization: Bearer` scheme. Gemini Live's provider hop authenticates with a DIFFERENT native -// scheme (`x-goog-api-key`, never `Authorization`), so it cannot share the OpenAI endpoint without -// silently reusing the wrong header shape. A second, independently-composed endpoint is the fix: same -// set-once discipline, same fail-closed resolve, keyed to its own dialect rather than folded into the -// first. - -/// A COMPOSED REALTIME PROVIDER — the ONE endpoint the Gemini Live route dials, written once by the -/// composition root and read by [`ws_accept`] (via [`composed_gemini_provider`]) thereafter. SET-ONCE, first writer -/// wins, exactly like [`COMPOSED_PROVIDER`] — a second compose is a no-op, never a silent swap. -static COMPOSED_PROVIDER_GEMINI: std::sync::OnceLock = std::sync::OnceLock::new(); - -/// COMPOSE the Gemini Live provider endpoint the plane's Gemini route dials — the Gemini twin of -/// [`install_provider`]. `api_key` is the RAW resolved credential (Gemini's native `x-goog-api-key` -/// value, never wrapped as a bearer token); the plane never renders it into a public accessor. -pub fn install_gemini_provider(base_url: impl Into, api_key: impl Into) -> bool { - COMPOSED_PROVIDER_GEMINI - .set(ProviderEndpoint { - base_url: base_url.into(), - api_key: api_key.into(), - }) - .is_ok() -} - -/// COMPOSE the Gemini Live provider endpoint from a provider-catalog entry — the Gemini twin of -/// [`compose_provider`]: resolves `api_key` through the deployment's neutral secret resolver and fails -/// closed (composes nothing) on an unresolvable reference. -pub fn compose_gemini_provider( - base_url: impl Into, - api_key: &busbar_contract::secret_ref::SecretRef, - resolver: &dyn busbar_contract::secret::SecretResolve, -) -> Result { - let resolved = resolver.resolve_string(api_key)?; - Ok(install_gemini_provider(base_url, resolved)) -} - -/// A provider composition step: captured off the resolved configuration, run once the deployment's -/// secret resolver exists (the root's `compose` axis shape). -pub type Compose = Box; - -/// THE PLANE'S PROVIDER, COMPOSED FROM THE DEPLOYMENT'S ORDINARY CATALOG — this plane's `compose` entry -/// hook ([`crate::linked`]). The `streams:` grammar carries no credential field, so the realtime -/// provider is the one already serving the model that section targets: `streams.session.model` names -/// a model, the model names its provider, and that provider entry carries the origin and the secret -/// reference every other lane's key is declared as. Captured off the resolved configuration BEFORE -/// the app is built; the returned step runs once the resolver that turns a reference into a -/// credential exists. A deployment with no `streams:` block pins no model and captures nothing. -/// -/// Silent on a deployment that captured nothing; the only lines this emits are the fail-closed -/// warnings when a reference the operator DID declare will not resolve. They are the composition -/// root's lines (its log target), because it is the root's boot that is reporting them. -pub fn compose_from_config(cfg: &RootCfg) -> Option { - let (base_url, api_key) = crate::config::configured_session_model() - .and_then(|model| cfg.models.get(&model).map(|m| m.provider.clone())) - .and_then(|provider| cfg.providers.get(&provider)) - .map(|p| (p.base_url.clone(), p.api_key.clone()))?; - Some(Box::new(move |resolver| { - if let Err(e) = compose_provider(base_url.clone(), &api_key, resolver) { - tracing::warn!( - target: "busbar", - "streaming: the realtime provider credential did not resolve, so the streaming mint and SDP \ - routes stay uncomposed: {e}" - ); - } - // K4: THE GEMINI LIVE ROUTE'S PROVIDER, composed under its OWN endpoint (a separate set-once - // slot, `x-goog-api-key` scheme) rather than reusing the OpenAI one's — a deployment cannot - // silently point one dialect's traffic at the other's credential. `streams:` still names ONE - // model, so both endpoints are composed from the SAME resolved (origin, reference) pair; a - // deployment that fronts Gemini Live through a distinct provider entry needs a second - // `streams:` knob to name it, which is not this cycle's grammar change (see docs/voice.md). - if let Err(e) = compose_gemini_provider(base_url, &api_key, resolver) { - tracing::warn!( - target: "busbar", - "streaming: the Gemini Live provider credential did not resolve, so the Gemini route \ - stays uncomposed: {e}" - ); - } - })) -} - -/// THIS PLANE'S `compose` ENTRY ([`crate::linked`]): compose the node's open-call table onto the -/// served door, then capture the provider off the deployment's catalog ([`compose_from_config`]). -/// -/// The table is the plane's own ([`crate::governed`]): nothing configuration decides is in it — it is -/// empty at boot and its whole contents are what the sessions this node serves have opened since — -/// so it is composed here, on the boot slot every linked plane's compose step runs in, before any -/// listener binds. From then on every session the served door opens is a governed one. -pub fn compose_plane(cfg: &RootCfg) -> Option { - compose_node_calls(); - compose_from_config(cfg) -} - -/// COMPOSE the node's open-call table onto the served door: one [`crate::governed::OpenToolCalls`] -/// behind the port the session runtime reaches it through. Set once — a second compose is a no-op -/// rather than a silent swap of the table live sessions are already keyed into. -pub fn compose_node_calls() -> bool { - let keys = UnitKeyMint::default(); - install_governed_calls(Arc::new(crate::governed::NodeCalls::new( - Arc::new(crate::governed::OpenToolCalls::new()), - Box::new(move || keys.mint()), - ))) -} - -/// The composed Gemini Live provider endpoint, or `None` when the composition root composed none. -pub(crate) fn composed_gemini_provider() -> Option<&'static ProviderEndpoint> { - COMPOSED_PROVIDER_GEMINI.get() -} - -// ── THE NODE'S OPEN-CALL TABLE, AS A SERVED SESSION REACHES IT ─────────────────────────────────── -// -// The runtime declares the port (`crate::runtime::GovernedCalls`) and the node's table implements it -// (`crate::governed`). Between the two, this is what hands a served session the table: composed -// exactly the way this plane's provider credentials are — set-once, process-wide, written at boot by -// this plane's own `compose` step ([`compose_plane`]) and read by every session opened thereafter. - -/// THE COMPOSED OPEN-CALL TABLE — the node's own, as the served path is allowed to see it. -/// -/// SET-ONCE, first writer wins, exactly like [`COMPOSED_PROVIDER`]: a second compose is a no-op -/// rather than a silent swap of the table one half of this node's live sessions are already keyed -/// into. `None` is a node whose compose step never ran (a harness that mounts the plane's routes -/// alone): every tool call is served in-process there, exactly as before the governed wait existed. -static COMPOSED_GOVERNED_CALLS: std::sync::OnceLock> = - std::sync::OnceLock::new(); - -/// The identifier the next served session is known to the node's table by. -/// -/// Minted here rather than derived from anything on the wire. The root keys its open calls by this -/// number and two conversations may legitimately carry identical call identifiers — a provider mints -/// them per conversation — so the one thing this number must be is unique per session on this node, -/// which a counter is and a hash of a client-supplied `call_id` is not. -static NEXT_SERVED_SESSION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); - -/// COMPOSE a table behind the served door — the one write of the governed-call port. -/// -/// Returns `false` when a table was already composed (the first write stands). Nothing here learns -/// what is behind the port: what crosses is what [`crate::runtime::GovernedCalls`] declares. -pub fn install_governed_calls(calls: Arc) -> bool { - COMPOSED_GOVERNED_CALLS.set(calls).is_ok() -} - -/// Whether a composition root has bound its open-call table — i.e. whether the sessions this door -/// serves are the governed kind. -#[must_use] -pub fn governed_calls_composed() -> bool { - COMPOSED_GOVERNED_CALLS.get().is_some() -} - -/// THE BINDING ONE SERVED SESSION OPENS UNDER: a fresh identifier for this node's table, and the -/// table itself. -/// -/// Every served session-open on this door calls this and hands what it returns to the open, which is -/// what makes "served" and "governed" the same set on a composed node. `None` on a node with no -/// table composed — an ungoverned deployment serves exactly what it served before. -#[must_use] -pub fn served_governed_session() -> Option { - let calls = COMPOSED_GOVERNED_CALLS.get()?; - Some(crate::runtime::GovernedSession { - session: NEXT_SERVED_SESSION.fetch_add(1, std::sync::atomic::Ordering::Relaxed), - calls: Arc::clone(calls), - }) -} - -/// Gemini Live's native provider auth HEADER NAME — `x-goog-api-key`, never `Authorization`. Named -/// once, publicly, so a conformance probe and the live dial (once header-carrying WS dial lands; see -/// [`provider_ws_url`]'s doc for today's honest limit) name the SAME literal. -pub const GEMINI_API_KEY_HEADER: &str = "x-goog-api-key"; - -/// THE VOICE PLANE'S RFC 8707 RESOURCE PATH — the segment the plane's canonical audience carries and -/// the base every voice ingress route sits under. A token presented on any `/v1/realtime/*` path must -/// carry the audience `/v1/realtime`; the claim of this ONE base covers all four routes by -/// segment-boundary match (the MCP `mount_path` / A2A `/a2a` precedent — one resource, one audience). -pub const MOUNT_PATH: &str = "/v1/realtime"; - -/// The browser-WebRTC ephemeral (`ek_`) client-secret MINT endpoint — a one-shot HTTP pass. The -/// concrete `TokenMinter` (the live `POST /v1/realtime/client_secrets` to the provider) is the port a -/// deployment binds; this route mounts + governs the session-open the mint is scoped to. -pub const MINT_PATH: &str = "/v1/realtime/client_secrets"; - -/// The SDP BROKER endpoint — a one-shot HTTP pass (`application/sdp` in, the provider's SDP answer -/// out). The upstream `POST /v1/realtime/calls` is the credential-gated tail behind the egress port. -pub const SDP_PATH: &str = "/v1/realtime/calls"; - -/// The BROWSER-WEBRTC SIDEBAND accept — the persistent control channel keyed by `{call_id}`. The -/// socket upgrade + provider dial is the credential-gated tail; the governed open runs here on arrival. -pub const SIDEBAND_PATH: &str = "/v1/realtime/sideband/{call_id}"; - -/// The TELEPHONY WS accept — the carrier media leg keyed by `{call_id}`, proxied `g711_ulaw` -/// end-to-end. The socket upgrade + provider WSS dial is the credential-gated tail. -pub const TELEPHONY_PATH: &str = "/v1/realtime/telephony/{call_id}"; - -/// THE GEMINI LIVE WS ACCEPT — the plane's SECOND dialect route (the second-dialect route): a THIN DUPLEX PROXY between the -/// caller's WS and the Gemini `BidiGenerateContent` upstream, the same shape as the telephony leg -/// (client WS <-> busbar <-> provider WS) rather than the OpenAI browser-sideband's mint+SDP dance — -/// Gemini Live has no ephemeral-token-mint or SDP-broker concept; it is a native full-duplex socket on -/// both legs. Keyed by `{call_id}` exactly as the other WS legs are. -pub const GEMINI_PATH: &str = "/v1/realtime/gemini/{call_id}"; - -/// THE GEMINI DIALECT'S CLAIMED BASE — a path distinct from [`MOUNT_PATH`] so [`voice_claims`] can name -/// the Gemini route under its OWN wire format ([`crate::GEMINI_LIVE`]) rather than folding it into the -/// OpenAI claim, the A2A precedent of returning more than one `(path, wire)` pair per plane. -const GEMINI_MOUNT_PATH: &str = "/v1/realtime/gemini"; - -/// The RFC 9728 protected-resource metadata path for the voice resource (the `resource_metadata` a -/// refused caller is pointed at). Derived from `public_url` beside the audience; a deployment serves -/// the document itself (not one of the five mounted data routes). -const METADATA_PATH: &str = "/.well-known/oauth-protected-resource/v1/realtime"; - -/// THE VOICE PLANE'S DISPATCH SLOT — the per-generation object `build` erases into `plane_slots` under -/// the decl key. Carries the plane's audience facts (derived from `public_url`) and a live runtime the -/// route handlers open governed sessions from. -pub struct VoiceMount { - /// The RFC 8707 audience every inbound voice token must carry — `/v1/realtime`. - audience: String, - /// The absolute RFC 9728 metadata URL quoted into a refused caller's `WWW-Authenticate` challenge. - resource_metadata: String, - /// The per-generation session runtime every route opens governed sessions from, carrying the - /// operator's own `streams:` posture and ceilings. Each route binds the live host onto it - /// (`build_runtime_hosted`) once a request hands it an engine host. - runtime: Arc, - /// A per-slot provider endpoint override — `None` in every built slot, because the composition - /// root composes the provider process-wide ([`install_provider`]) AFTER the slot is built. Kept as - /// a field so a loopback test can inject one directly into [`open_governed`]. - /// - /// The Gemini Live provider has NO per-slot override twin: unlike the OpenAI mint/SDP one-shot - /// passes (which take a request-scoped `Option<&ProviderEndpoint>` a test can construct locally, - /// see [`GovernedOpen::provider`]), the Gemini leg is a WS accept whose `on_socket` closure must be - /// `'static` — so it reads the process-wide [`composed_gemini_provider`] directly rather than - /// through the (non-`'static`) mount, and there is no request-scoped path to override on. - provider: Option, -} - -impl VoiceMount { - /// The realtime provider endpoint this mount's routes dial: the slot's own override when one was - /// injected, else the one the composition root composed. `None` on a deployment that composed no - /// provider, in which case the mint / SDP passes answer "governed, but no provider composed". - fn provider(&self) -> Option<&ProviderEndpoint> { - match self.provider.as_ref() { - Some(p) => Some(p), - None => composed_provider(), - } - } -} - -impl VoiceMount { - /// This plane's admission facts — the audience a token at the voice door must carry and where a - /// refused caller is sent for one. Both strings are ONE reading of `public_url`, so the audience a - /// caller is told to ask for and the one busbar demands cannot drift apart (the A2A precedent). - fn admission(&self) -> PlaneAdmission { - PlaneAdmission { - audience: self.audience.clone(), - resource_metadata: self.resource_metadata.clone(), - } - } -} - -/// ONE READING of `public_url`: parse it, REPLACE the path wholesale, drop query and fragment — so a -/// `public_url` carrying a path of its own cannot produce a second spelling of the voice resource. The -/// A2A `serve::absolute` discipline, kept local so the plane names no core serve helper. -fn absolute(public_url: &str, path: &str) -> Option { - let mut u = url::Url::parse(public_url).ok()?; - u.set_path(path); - u.set_query(None); - u.set_fragment(None); - Some(u.to_string()) -} - -/// [`PlaneDecl::hydrate`] — BOOT-REHYDRATE the durable voice-session working-set BEFORE any listener is -/// bound, mirroring the A2A task-set restore. Under `store: memory` (the ephemeral posture this -/// in-process mount runs) there is no durable working-set, so it skips exactly as the A2A / MCP hydrate -/// hooks do — the plane's live carriers are per-connection and never survive a restart. When a durable -/// store IS configured, it restores the persisted `voice_session` rows through the neutral engine seam -/// ([`crate::runtime::scope::rehydrate_sessions`]): an active row is re-installed, a terminal one is -/// counted and left, and a row whose durable body cannot be decoded is counted and skipped — one bad -/// row never aborts the restore. Only a store-level list failure refuses boot (propagated as `Err`). -/// -/// # Errors -/// Returns the store error's text when the durable list itself fails — a boot-refusing condition. -pub fn voice_hydrate(ctx: &dyn PlaneBootCtx) -> Result<(), String> { - // Ephemeral by design (no configured store): no durable working-set to restore, so skip — the same - // first move the A2A task-set restore makes. - let Some(store) = ctx.plane_store() else { - return Ok(()); - }; - // A configured store: restore the durable `voice_session` working-set into a fresh engine and - // surface the outcome. A durable row a resumed session reattaches to via `SessionHandle::bind`. - let engine = DurableHandleEngine::new(); - let counts = crate::runtime::scope::rehydrate_sessions(&engine, store.as_ref()) - .map_err(|e| format!("streaming session rehydrate failed: {e}"))?; - if counts.unreadable > 0 { - tracing::warn!( - unreadable = counts.unreadable, - "streaming: some durable session rows could not be decoded on boot; counted and skipped" - ); - } - // DEBUG, not INFO: a 1.5.5-shaped config with no `streams:` section still runs this hook (it is - // gated on `has_store()`, not on the plane being configured) and must not gain a new boot line at - // INFO — the neutrality binding (qa/parity-bindings.md). An operator who DID - // configure `streams:` can still see this at DEBUG. - tracing::debug!( - active = counts.active, - terminal = counts.terminal, - unreadable = counts.unreadable, - "streaming: durable session working-set rehydrated" - ); - Ok(()) -} - -/// [`PlaneDecl::start`] — the post-listener boot step. Voice opens no background boot task: a live -/// session is admitted and served per WS-accept ARRIVAL (through `run_gauntlet_session`, one governed -/// pass per connection), each running its own supervised pump that parks on the carrier's hard-close — -/// there is no process-wide sweep loop to spawn here (unlike the A2A start, which resolves outbound -/// client identities once). The one thing that DOES run on a timer, the governed tool-call sweep -/// ([`crate::runtime::serve_with_sweep`]), rides each session's own pump and ends with it, which is -/// what keeps that true: a sweep outliving the socket it sweeps for would be exactly the background -/// loop this hook says the plane does not have. The live accept listener + provider dial are composed by the composition -/// root behind the plane's ports (the credential-gated tail). So this confirms readiness and returns -/// `Ok`, participating in the boot fold so the plane is an explicit member of it rather than silent. -/// -/// # Errors -/// Never fails today; the signature is the boot-hook shape so future post-listener work refuses boot -/// through it rather than gaining a new seam. -pub fn voice_start(_ctx: &dyn PlaneBootCtx) -> Result<(), String> { - tracing::debug!( - "streaming: started — sessions are admitted and served per WS-accept arrival (one \ - run_gauntlet_session pass each); no process-wide background task is spawned" - ); - Ok(()) -} - -/// [`PlaneDecl::build`] — construct the voice DISPATCH slot for one config generation. Reads the -/// deployment's `public_url` (the receiving origin the plane fronts) and derives the plane's audience -/// from it. `None` when there is no `public_url`: a deployment with no receiving origin fronts nothing, -/// claims nothing and admits no one — the A2A delegation-only asymmetry, and what makes the plane bind -/// no audience rather than mount a door that could only ever refuse. -#[must_use] -pub fn voice_build(ctx: &BuildCtx) -> Option> { - let public = ctx.public_url?; - let audience = absolute(public, MOUNT_PATH)?; - let resource_metadata = absolute(public, METADATA_PATH)?; - let mount = VoiceMount { - audience, - resource_metadata, - runtime: Arc::new(dispatch_runtime()), - // No per-slot override: the composed provider is read process-wide at request time, because the - // composition root composes it only after the deployment's config resolves (see the field doc). - provider: None, - }; - Some(Arc::new(mount)) -} - -/// The per-generation session runtime the dispatch slot carries. Seeded with the operator's own -/// `streams:` posture (the section the plane parsed — see `crate::config::configured`), so the locked -/// session config a mint carries and the ceilings the pump enforces are the ones the deployment wrote -/// rather than the plane's dev defaults. Every route binds the live host onto it (see -/// [`VoiceMount::runtime`]). -fn dispatch_runtime() -> VoiceRuntime { - VoiceRuntime::new(Arc::new(DurableHandleEngine::new()), Arc::new(ClientRelay)) - .with_streams(&crate::config::configured()) -} - -/// [`PlaneDecl::claims`] — the ONE audience-checked region the voice plane answers on, spoken in its -/// first dialect. The base [`MOUNT_PATH`] covers all four routes by segment-boundary match, so every -/// `/v1/realtime/*` path a token is presented on is a path this plane claims (the R1 ratchet's -/// invariant: a path the plane answers on is a path it claims). Empty when the plane has no receiving -/// side (no dispatch slot), so it claims nothing exactly as it admits no one. -#[must_use] -pub fn voice_claims(slot: &dyn Any) -> Vec<(String, &'static str)> { - match slot.downcast_ref::() { - // TWO claims, one PER ROUTE DIALECT (the second-dialect route): the OpenAI base (mint/SDP/sideband/telephony) and the - // Gemini Live base (the thin duplex proxy) — the A2A precedent of naming more than one - // `(path, wire)` pair so each dialect's own `ingress_protocol` is the one its own path answers - // in, not a plane-wide constant that would mislabel the second dialect's traffic as the first. - Some(_) => vec![ - (MOUNT_PATH.to_string(), crate::OPENAI_REALTIME), - (GEMINI_MOUNT_PATH.to_string(), crate::GEMINI_LIVE), - ], - None => Vec::new(), - } -} - -/// [`PlaneDecl::admission`] — the audience a voice token must carry and where a refused caller is sent -/// for one, from the same dispatch slot. `Some` whenever the plane has a receiving side (a slot), -/// so `build_dispatch`'s R2 ratchet — a plane that CLAIMS a path must ADMIT (or boot refuses) — holds -/// by construction here: a slot ⇒ a claim AND an admission, never one without the other. -#[must_use] -pub fn voice_admission(slot: &dyn Any) -> Option { - slot.downcast_ref::().map(VoiceMount::admission) -} - -/// [`PlaneDecl::routes`] — the voice plane's TWO one-shot HTTP ingress routes, described NEUTRALLY -/// (S4a Option A): the `ek_` mint + SDP broker passes, each a thin neutral handler over `PlaneReqCtx`. -/// Both are `RouteAuth::Key` — behind the plane's one audience — so a token minted for another -/// resource is refused at the door. The browser-sideband + telephony WS-accept legs are NOT here: an -/// inbound WS upgrade cannot ride the buffered-body `PlaneReqCtx` adapter, so they are declared through -/// the neutral inbound WS-accept seam instead (see [`voice_ws_arrivals`]). Empty when the plane has no -/// receiving side (no dispatch slot), so a deployment that fronts nothing mounts nothing. -#[must_use] -pub fn voice_routes(slot: &dyn Any) -> Vec { - use busbar_contract::abi::mechanism::route::{RouteAuth, RouteMethod}; - use busbar_kernel::plane_routes::PlaneRouteFuture; - - if slot.downcast_ref::().is_none() { - return Vec::new(); - } - vec![ - PlaneRouteSpec { - path: MINT_PATH.to_string(), - method: RouteMethod::Post, - auth: RouteAuth::Key, - handler: Arc::new(|ctx: PlaneReqCtx| -> PlaneRouteFuture { Box::pin(mint_route(ctx)) }), - }, - PlaneRouteSpec { - path: SDP_PATH.to_string(), - method: RouteMethod::Post, - auth: RouteAuth::Key, - handler: Arc::new(|ctx: PlaneReqCtx| -> PlaneRouteFuture { Box::pin(sdp_route(ctx)) }), - }, - ] -} - -/// THE PLANE'S WS-ACCEPT ENTRY HOOK ([`crate::linked`]): installs [`voice_ws_arrivals`] into the -/// neutral WS-accept registry, before the router that drains it is built. -pub fn install_arrivals() { - install_ws_arrivals(voice_ws_arrivals()); -} - -/// THE VOICE PLANE'S INBOUND WS-ACCEPT ARRIVALS — the browser-sideband + telephony media legs, -/// declared through the neutral substrate WS-accept seam (`WsArrivalSpec`) rather than `routes`, -/// because an inbound WS upgrade cannot ride the buffered-body `PlaneReqCtx` adapter (its body is -/// already consumed). Both are `RouteAuth::Key` under the plane's one audience — the SAME admission bar -/// the mint/SDP routes carry — so the auth middleware refuses a foreign-resource token BEFORE the -/// accept fn runs. The composition root installs these (`install_ws_arrivals`) and the core router -/// mounts one gauntlet-gated WS-accept route per spec, resolving the live runtime slot under the -/// plane's decl `key`. Empty when the plane has no receiving side, exactly as [`voice_routes`]. -#[must_use] -pub fn voice_ws_arrivals() -> Vec { - use busbar_contract::abi::mechanism::route::RouteAuth; - let key = crate::PLANE_DECLARATION.key; - vec![ - WsArrivalSpec { - path: SIDEBAND_PATH.to_string(), - auth: RouteAuth::Key, - slot_key: key, - accept: Arc::new(|a: WsArrival| -> WsAcceptFuture { - Box::pin(ws_accept(a, Ingress::Sideband, OpenAiRealtimeCodec)) - }), - }, - WsArrivalSpec { - path: TELEPHONY_PATH.to_string(), - auth: RouteAuth::Key, - slot_key: key, - accept: Arc::new(|a: WsArrival| -> WsAcceptFuture { - Box::pin(ws_accept(a, Ingress::Telephony, OpenAiRealtimeCodec)) - }), - }, - // THE GEMINI LIVE THIN-DUPLEX ACCEPT (the second-dialect route) — same admission bar (RouteAuth::Key under the - // plane's one audience), the SAME `ws_accept` choke point, generic over the Gemini codec - // instead of the OpenAI one. - WsArrivalSpec { - path: GEMINI_PATH.to_string(), - auth: RouteAuth::Key, - slot_key: key, - accept: Arc::new(|a: WsArrival| -> WsAcceptFuture { - Box::pin(ws_accept(a, Ingress::Gemini, GeminiLiveCodec)) - }), - }, - ] -} - -/// WHICH ingress a route drives — the topology-shaping fact a handler carries into the shared governed -/// open. Every arm funnels through [`crate::topology::begin_session`] (so through -/// `run_gauntlet_session`); they differ only in the locked config and carrier the topology binds. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum Ingress { - /// The `ek_` mint (browser-WebRTC sideband): a sideband control session, no downlink media relay. - Mint, - /// The SDP broker: same sideband governed open; the SDP handshake is the credential-gated tail. - Sdp, - /// The browser-WebRTC sideband accept. - Sideband, - /// The telephony media leg — `g711_ulaw` end-to-end through the thin proxy. - Telephony, - /// THE GEMINI LIVE THIN-DUPLEX LEG (the second-dialect route) — a native full-duplex socket both sides, proxied through - /// the SAME [`crate::topology::telephony::TelephonyProxy`] shape the telephony leg uses (client WS - /// <-> busbar <-> provider WS), just without the g711 lock and under the Gemini codec + the - /// Gemini-keyed composed provider. - Gemini, -} - -/// The neutral inputs one governed session-open reads — bundled so the choke point takes ONE argument -/// and a test constructs the SAME shape the route handler does. `host` is the owned host seam (cloned -/// per blocking hook hop), `provider` the configured realtime endpoint (`None` ⇒ the mint / SDP passes -/// answer `501`), `key` the caller `(id, name)` the hook gate reads, `body`/`headers` the one-shot -/// pass's request payload (the SDP offer + its `Authorization: Bearer ek_`). -pub(crate) struct GovernedOpen<'a> { - pub rt: &'a VoiceRuntime, - pub host: Arc, - pub provider: Option<&'a ProviderEndpoint>, - pub ingress: Ingress, - pub owner: String, - pub call_id: String, - /// The resolved presenting virtual key (audience-checked key chain), or `None` ungoverned. The - /// hook gate reads its `(id, name)`; the Meter step lands each turn's usage on this key's ledger. - pub vkey: Option, - pub body: Bytes, - pub headers: axum::http::HeaderMap, - pub now: u64, -} - -/// THE SHARED GOVERNED OPEN every voice route funnels through — the ONE choke point. In order: -/// -/// 1. **hooks-gate** (`host.gate_decide`) — the operator's request-admission gate over the session-open -/// params. A `Reject` refuses BEFORE any lease/mint/dial. BYTE-IDENTICAL (nothing serialized, no -/// blocking hop) when no gate is attached — the `gate_attached` presence pre-filter. -/// 2. **hooks-tap** (`host.transform_over`) — a `prompt: rw` rewrite over the same params, AFTER the -/// gate and BEFORE the credential is leased. A committed rewrite REPLACES the locked session params -/// the mint/dial then carries; an abstaining chain (or no attached rewrite) leaves them -/// byte-identical. -/// 3. **the governed open** — [`crate::topology::begin_session`] / -/// [`crate::topology::telephony::begin_telephony`] runs `run_gauntlet_session` -/// (verify-strictly-before-charge): a gate refusal is `403` before any lease/durable open. -/// 4. **the serving leg** — for `Mint`, the `ek_` is minted through [`HttpsTokenMinter`] over the -/// configured provider and returned as JSON; for `Sdp`, the offer is brokered upstream, the -/// `rtc_` correlation key is stamped onto the durable row, and the answer + `Location` -/// header are returned. The `Sideband` / `Telephony` WS-accept legs answer `501` — the inbound -/// WS-accept seam lands separately. -/// -/// The front-door request is COUNTED on `busbar_plane_requests_total` (plane = voice), and the answer -/// carries a [`Counted`] marker so the core `plane::observe` middleware stands down (no double count). -pub(crate) async fn open_governed(req: GovernedOpen<'_>) -> axum::response::Response { - // The front door's own clock: the duration `finish` reports is this request's, end to end. - let started = std::time::Instant::now(); - let GovernedOpen { - rt, - host, - provider, - ingress, - owner, - call_id, - vkey, - body, - headers, - now, - } = req; - // (0) AUTHORIZATION — may this key open a session here at all? Asked FIRST, of the caller's own - // grant, so a key that is valid for this plane's audience but was never granted session scope is - // refused before any hook fires, any account is opened, any durable row exists or any provider is - // dialed. An ungoverned deployment resolves no key and has no grant to consult, so it proceeds - // exactly as it did before — the refusal is a narrowing of governed callers only. - if let Some(k) = vkey.as_ref() { - if !session_scope_allowed(k) { - return finish(&host, started, session_scope_refusal()); - } - } - // The `(id, name)` the hook gate reads — derived from the resolved key (or `None` ungoverned). - let key = vkey.as_ref().map(|k| (k.id.clone(), k.name.clone())); - // THE METER STEP's attribution: each closed turn's counts per class go to the presenting key's - // kernel account, which ledgers them through the one metering path and answers whether the carrier - // stays open; the same account refuses the open when the key's chain is already dry. `None` - // ungoverned — nothing to attribute, nothing to close on. - let meter = vkey.as_ref().map(|k| { - crate::runtime::metering::TurnMeter::new( - Arc::clone(&host), - k.clone(), - FRONT_DOOR_POOL, - crate::OPENAI_REALTIME, - ) - }); - let hosted = crate::runtime::build_runtime_hosted(rt, Arc::clone(&host)); - let rt = &hosted; - // The session-open params the hooks screen and (maybe) rewrite: the g711 lock for telephony, the - // plane-default session posture otherwise. One projection both the gate and the tap read. - let mut session_cfg = match ingress { - Ingress::Telephony => g711_config(), - Ingress::Mint | Ingress::Sdp | Ingress::Sideband | Ingress::Gemini => { - rt.session_defaults.clone() - } - }; - - // (1) HOOKS-GATE — refuse before any lease/mint/dial. Zero-cost / byte-identical when unattached. - if let Err(refused) = hook_gate(&host, key.clone(), &call_id, now, &session_cfg).await { - return finish(&host, started, *refused); - } - // (2) HOOKS-TAP — rewrite the session-open params before the credential is leased. Byte-identical - // (params untouched) when no rewrite hook is attached or the chain abstains. - match hook_tap(&host, key, &owner, &call_id, now, &session_cfg).await { - Ok(Some(rewritten)) => session_cfg = rewritten, - Ok(None) => {} - Err(refused) => return finish(&host, started, *refused), - } - - // (3) THE GOVERNED OPEN. Telephony has no durable handle to correlate; the sideband topologies keep - // the handle so the SDP broker can stamp the `rtc_` onto the row. `Gemini` never reaches - // this fn today (no HTTP one-shot route dispatches it — only the WS-accept seam does, see - // `ws_accept`); the arm exists only so this match stays exhaustive over `Ingress`. - let resp = match ingress { - Ingress::Telephony => match begin_telephony( - rt, - OpenAiRealtimeCodec, - owner, - call_id, - session_cfg, - meter, - now, - ) { - // The HTTP telephony open serves nothing: the media leg is the WS-accept seam's. A `501` - // is a failed open, so the session fee its open counted is given back. - Ok(proxy) => { - proxy.core().refund_open(); - sideband_pending() - } - Err(e) => start_refusal(&e), - }, - Ingress::Gemini => sideband_pending(), - Ingress::Mint | Ingress::Sdp | Ingress::Sideband => match begin_session( - rt, - OpenAiRealtimeCodec, - owner, - call_id, - Some(session_cfg.clone()), - Carrier::sideband(), - meter, - now, - ) { - // (4) THE SERVING LEG, past a clean governed open. A pass that answers success served the - // session; any other answer — a failed mint or SDP broker, an unreachable provider, a - // `501` with nothing composed to serve it — is a failed open, and the session fee its - // open counted is given back (TODO 17(b), ARCHITECT R4). - Ok((core, handle)) => { - let resp = match ingress { - Ingress::Mint => serve_mint(provider, handle.owner(), &session_cfg).await, - Ingress::Sdp => serve_sdp(provider, &headers, body, &handle, now).await, - // The inbound WS-accept seam (browser sideband) lands separately — no bare - // on_upgrade. - _ => sideband_pending(), - }; - if resp.status().is_success() { - core.served(); - } else { - core.refund_open(); - } - resp - } - Err(e) => start_refusal(&e), - }, - }; - finish(&host, started, resp) -} - -/// REPORT the front-door session-open through the host's `request_finished` seam — its count AND its -/// duration since `started` — and MARK the answer counted. On the engine's host that is the -/// plane-labelled `busbar_plane_requests_total` series (`plane = voice`, the voice-server cell; the -/// same family and labels this door emitted by hand) plus the `busbar_plane_request_duration_seconds` -/// sample the hand emit never recorded (item 566) — the same call the A2A door makes. The [`Counted`] -/// marker tells the core `plane::observe` middleware the request was already reported, so the -/// front-door series is never double-counted at the boundary. -fn finish( - host: &Arc, - started: std::time::Instant, - mut resp: axum::response::Response, -) -> axum::response::Response { - host.request_finished( - crate::PLANE_KEY, - crate::OPENAI_REALTIME, - FRONT_DOOR_POOL, - busbar_kernel::telemetry::outcome_of(resp.status().as_u16()), - started.elapsed().as_secs_f64(), - ); - resp.extensions_mut().insert(Counted); - resp -} - -/// The hooks-GATE leg (`host.gate_decide`) over the session-open params. `Ok(())` proceeds; `Err` is a -/// finished refusal. ZERO-COST / BYTE-IDENTICAL when no gate is attached: the `gate_attached` presence -/// pre-filter short-circuits before any serialize or blocking hop. -async fn hook_gate( - host: &Arc, - key: Option<(String, String)>, - session_id: &str, - now: u64, - cfg: &SessionConfig, -) -> Result<(), Box> { - if !host.gate_attached(crate::PLANE_DECLARATION.key, GATE_CONTAINER) { - return Ok(()); - } - // Serialized ONCE for the seam (only past the presence check). The host re-selects the gate set by - // `(plane_key, container)` and runs the same decision — the Seam-B inversion, so this plane body - // names no core hook symbol. Driven on a blocking thread (the host runs the async gate on a fresh - // runtime), so it MUST NOT run on a worker. - let args_json = serde_json::to_vec(cfg).unwrap_or_default(); - let sid = session_id.to_string(); - let host = Arc::clone(host); - let outcome = tokio::task::spawn_blocking(move || { - host.gate_decide( - crate::PLANE_DECLARATION.key, - GATE_CONTAINER, - now, - SESSION_OPEN_METHOD, - &args_json, - key.as_ref().map(|(id, name)| (id.as_str(), name.as_str())), - (!sid.is_empty()).then_some(sid.as_str()), - ) - }) - .await - .unwrap_or(GateOutcome::Reject { - status: 403, - message: String::new(), - hook: String::new(), - }); - match outcome { - GateOutcome::Proceed => Ok(()), - GateOutcome::Reject { - status, message, .. - } => Err(Box::new(hook_refusal(status, &message))), - } -} - -/// THE SESSION-OPEN PARAMS A COMMITTED REWRITE PRODUCED, or the reason they cannot be used — the -/// voice twin of the MCP plane's `committed_arguments` and the A2A plane's `committed_params`, with -/// the same signature for the same reason: the rule the plane owns is what happens when the seam -/// misbehaves, and it has to be drivable without a host that misbehaves. -/// -/// # Two fail-open readings this one function replaces -/// -/// **(1) Bytes busbar cannot read back.** The apply site was -/// `if let Ok(v) = serde_json::from_slice::(&args_json) { … }`, so a hook that -/// COMMITTED a rewrite (`applied: true`) and produced bytes that would not deserialize fell through -/// to `Ok(None)` — which both callers read as "the hook made no rewrite" — and the session opened -/// carrying the plane's OWN params. For the hook class this seam exists for that is fail-OPEN in the -/// precise sense: a redaction hook says "I have removed the secret from these session params", its -/// output is unreadable, and busbar opens the session with the secret still in it. The hook ran, -/// reported applied, and was silently undone. -/// -/// Unlike the MCP/A2A twins this arm is REACHABLE THROUGH CORE'S REAL HOST, which is why voice is the -/// severe instance rather than the third copy of a dormant one. There the target type is -/// `serde_json::Value` and `plane_host::transform_over_over` only ever commits a JSON object -/// re-serialised with `to_vec`, so the bytes always parse. Here the target is [`SessionConfig`], and -/// core's `apply_rewrite_to_invoke_args` admits ANY JSON object as the replacement: a hook whose -/// rewrite reply carries `{"voice": 7}` or `{"input_audio_format": "flac"}` makes core commit bytes -/// this type rejects — a type mismatch, and an audio format the plane does not speak. -/// -/// **(2) A patch that blanks everything it did not name.** `SessionConfig` derives `Deserialize` with -/// `#[serde(default)]` on every field, so an object that DOES parse — `{"voice": "marin"}` — parsed -/// into a config whose every OTHER field was the type default, wiping the plane's locked -/// `instructions` on its way through. That is the same fail-open pointed the other way: the plane's -/// own lock disappears because a hook touched an unrelated field. The wire contract of this type is -/// PATCH semantics (its own doc comment: "Every field is optional on the wire — a partial -/// `session.update` patches only what it names"), so the committed object is MERGED over the locked -/// params: a key the hook named wins, a key it did not name keeps the plane's value. Clearing stays -/// expressible, because naming a key with an explicit `null` is naming it. -/// -/// The merge happens BEFORE the decode, so rule (1) still judges the whole config: a patch that names -/// one bad field refuses the open rather than being quietly dropped. -/// -/// A well-formed JSON SCALAR is refused too. These params are an object on every wire the plane -/// speaks, and the value is substituted for the plane's locked [`SessionConfig`]; admitting `7` would -/// mean telling the hook its rewrite landed and then opening the session with something that is not a -/// session config at all. -pub(crate) fn committed_session_config( - locked: &SessionConfig, - args_json: &[u8], -) -> Result { - let patch: serde_json::Value = - serde_json::from_slice(args_json).map_err(|e| format!("the output is not JSON: {e}"))?; - let serde_json::Value::Object(patch) = patch else { - return Err("the output is JSON but not a session-params object".to_string()); - }; - // The locked params as the hook itself was handed them (`serde_json::to_vec(cfg)` at the call - // seam), so the merge is over exactly the key set the hook screened. - let Ok(serde_json::Value::Object(mut merged)) = serde_json::to_value(locked) else { - return Err( - "the plane's own locked session params did not project to an object".to_string(), - ); - }; - // A key the hook NAMED wins; a key it did not name keeps the plane's locked value. - merged.extend(patch); - serde_json::from_value::(serde_json::Value::Object(merged)) - .map_err(|e| format!("the output is not a session config: {e}")) -} - -/// The hooks-TAP leg (`host.transform_over`) over the session-open params. `Ok(Some(cfg))` is a -/// committed rewrite the caller substitutes for the locked params; `Ok(None)` is "no change" (no -/// attached rewrite, or an abstaining chain — BYTE-IDENTICAL); `Err` is a refusal — the rewrite -/// gate's own `Reject`, or a committed rewrite busbar could not read back (see -/// [`committed_session_config`]). -async fn hook_tap( - host: &Arc, - key: Option<(String, String)>, - owner: &str, - session_id: &str, - now: u64, - cfg: &SessionConfig, -) -> Result, Box> { - if !host.tap_attached(crate::PLANE_DECLARATION.key, GATE_CONTAINER) { - return Ok(None); - } - let args_json = serde_json::to_vec(cfg).unwrap_or_default(); - let sid = session_id.to_string(); - // The `'static` handle the blocking leg owns. Named apart from the `host` param rather than - // shadowing it, because the refusal below audits through the SAME host after the join. - let hook_host = Arc::clone(host); - let verdict = tokio::task::spawn_blocking(move || { - hook_host.transform_over( - crate::PLANE_DECLARATION.key, - GATE_CONTAINER, - now, - SESSION_OPEN_METHOD, - &args_json, - key.as_ref().map(|(id, name)| (id.as_str(), name.as_str())), - (!sid.is_empty()).then_some(sid.as_str()), - ) - }) - .await - // A join panic is FAIL-SAFE on the transform path (the gate already admitted): proceed unchanged. - .unwrap_or_else(|e| { - // ...but never SILENT. This is the one disposition on this seam where a hook failure used to - // leave no trace at all: `transform_over_over` logs its own `Failed`/timeout arms, so a join - // panic is the only way a `prompt: rw` hook can stop answering without a line naming it. - tracing::error!( - session = %session_id, - error = %e, - "the streaming session-open rewrite (prompt: rw) leg did not join; the session-open proceeds \ - with the plane's own locked params (fail-safe: the admission gate already ran)" - ); - TransformVerdict::Proceed { - applied: false, - args_json: Vec::new(), - } - }); - match verdict { - TransformVerdict::Proceed { applied, args_json } => { - if !applied { - return Ok(None); - } - // A COMMITTED REWRITE BUSBAR CANNOT READ BACK REFUSES THE OPEN. There is no safe fallback - // here: `Ok(None)` is read by both callers as "the hook made no rewrite", so falling - // through with it opens the session carrying the params the hook said it had REPLACED — - // and that is the defect, not a degraded mode. The unreadable bytes are not a session - // config, so the answer is the same one the hook's own `Reject` gets. The MCP - // (`mcp/method.rs`) and A2A (`a2a/receive.rs`) twins answer the identical question the - // identical way; `busbar-llm`'s pipeline rejects rather than forward un-rewritten too. - match committed_session_config(cfg, &args_json) { - Ok(v) => Ok(Some(v)), - Err(why) => { - host.audit_record( - SESSION_AUDIT_ACTION, - &format!("streaming:{session_id}"), - "rejected", - owner, - ); - tracing::error!( - session = %session_id, - error = %why, - "a rewrite (prompt: rw) hook committed a rewrite of the streaming session-open \ - params whose output busbar could not read back; the session-open is REFUSED \ - rather than opened with the params the hook said it had replaced" - ); - Err(Box::new(refusal( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "a rewrite hook attached to this deployment committed a rewrite of the streaming \ - session-open params that busbar could not read back. The session-open is \ - refused rather than opened with the params the hook said it had replaced.", - ))) - } - } - } - TransformVerdict::Reject { - status, message, .. - } => Err(Box::new(hook_refusal(status, &message))), - } -} - -/// THE `ek_` MINT one-shot pass, past the governed open: mint through [`HttpsTokenMinter`] over the -/// configured provider (the real key held server-side) and return the browser-facing `ek_` as JSON. -/// `None` provider ⇒ `501` (governed, but no provider credential composed). -async fn serve_mint( - provider: Option<&ProviderEndpoint>, - owner: &str, - cfg: &SessionConfig, -) -> axum::response::Response { - let Some(p) = provider else { - return sideband_pending(); - }; - let minter = HttpsTokenMinter::new(egress_client(), &p.base_url, &p.api_key, owner, None); - match minter.mint(cfg).await { - Ok(token) => json_response( - axum::http::StatusCode::OK, - &serde_json::json!({ - "value": token.value, - "expires_at_unix": token.expires_at_unix, - }), - ), - Err(e) => text_response( - axum::http::StatusCode::BAD_GATEWAY, - format!("streaming ephemeral-secret mint failed: {e}"), - ), - } -} - -/// THE SDP BROKER one-shot pass, past the governed open: broker the browser's `application/sdp` offer -/// to the provider's `POST /v1/realtime/calls` under BUSBAR'S OWN provider credential (busbar brokers -/// the call server-side), PRESERVE the `Location: …/rtc_` response header verbatim, STAMP that -/// `rtc_` onto the durable session row (so busbar's governance and the brokered media call -/// name the SAME session), and return the SDP answer. `None` provider ⇒ `501`. -/// -/// `_inbound` (the caller's request headers) is deliberately NOT read for egress: the inbound -/// `Authorization` is the caller's `RouteAuth::Key` GOVERNANCE bearer (the Auth plugin already consumed -/// it at the door), and forwarding it upstream would leak busbar's own authority to the provider. The -/// provider hop authenticates ONLY through [`crate::voice_provider_bearer`] — the same lane-constant -/// builder the `egress_auth_headers` decl and the `egress_tests` battery pin — never a caller token. -async fn serve_sdp( - provider: Option<&ProviderEndpoint>, - _inbound: &axum::http::HeaderMap, - offer: Bytes, - handle: &SessionHandle, - now: u64, -) -> axum::response::Response { - let Some(p) = provider else { - return sideband_pending(); - }; - let uri = format!("{}/v1/realtime/calls", p.base_url.trim_end_matches('/')); - let mut builder = http::Request::builder() - .method(http::Method::POST) - .uri(&uri) - .header(http::header::CONTENT_TYPE, "application/sdp"); - // Busbar's OWN provider credential — never the caller's inbound governance bearer. - for (name, value) in crate::voice_provider_bearer(&p.api_key) { - builder = builder.header(name, value); - } - let req = match builder.body(Full::new(offer)) { - Ok(r) => r, - Err(e) => { - return text_response( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - format!("streaming SDP request did not build: {e}"), - ) - } - }; - let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(30); - let resp = match send_bounded(&egress_client(), req, deadline).await { - Ok(r) => r, - Err(e) => { - return text_response( - axum::http::StatusCode::BAD_GATEWAY, - format!("streaming SDP broker upstream failed: {}", e.into_cause()), - ) - } - }; - let status = resp.status(); - // PRESERVE the `Location` header verbatim, and derive the `rtc_` correlation key from it. - let location = resp.headers().get(http::header::LOCATION).cloned(); - let answer = match tokio::time::timeout_at(deadline, resp.into_body().collect()).await { - Ok(Ok(b)) => b.to_bytes(), - _ => { - return text_response( - axum::http::StatusCode::BAD_GATEWAY, - "streaming SDP answer was not read before the deadline".to_string(), - ) - } - }; - // STAMP the correlation key onto the durable row (owner-gated) — broker → row, the single key that - // ties governance here to the media that flows there. A mismatch/absence is left unstamped. - if let Some(rtc) = location - .as_ref() - .and_then(|v| v.to_str().ok()) - .and_then(rtc_call_id_of) - { - let _ = handle.set_rtc_call_id(&rtc, now); - } - let mut builder = axum::response::Response::builder() - .status(status) - .header(http::header::CONTENT_TYPE, "application/sdp"); - if let Some(loc) = location { - builder = builder.header(http::header::LOCATION, loc); - } - builder - .body(axum::body::Body::from(answer)) - .unwrap_or_else(|_| sideband_pending()) -} - -/// The last `rtc_` path segment of a brokered call's `Location` header — the correlation key -/// the SDP broker stamps onto the durable row. `None` when no segment carries the `rtc_` prefix. -fn rtc_call_id_of(location: &str) -> Option { - location - .rsplit('/') - .find(|seg| seg.starts_with("rtc_")) - .map(|seg| seg.split(['?', '#']).next().unwrap_or(seg).to_string()) -} - -/// The substrate egress client the one-shot HTTPS passes dial through (the same posture the concrete -/// minter uses). Built per pass; the composition root pools one once the provider config is threaded. -fn egress_client() -> EngineClient { - busbar_kernel::proxy::build_egress_client( - &busbar_kernel::egress::engine::EngineSpec::pooled_webpki(4, 300, false, false), - ) -} - -/// A GOVERNED-BUT-UNCOMPOSED answer for a WS-accept leg (browser sideband / telephony media): the -/// session opened and was governed, but the inbound WS-accept seam that upgrades the socket lands -/// separately, so no bare `on_upgrade` is taken here. -fn sideband_pending() -> axum::response::Response { - refusal( - axum::http::StatusCode::NOT_IMPLEMENTED, - "streaming session governed-open succeeded; the inbound WS-accept seam that upgrades the browser \ - sideband / telephony media socket lands separately", - ) -} - -/// The finished refusal for a `begin_session` / `begin_telephony` [`StartError`] — verify-before-charge -/// at the route layer (a gate refusal is `403` with zero charge). -fn start_refusal(e: &StartError) -> axum::response::Response { - match e { - StartError::DestinationRefused => refusal( - axum::http::StatusCode::FORBIDDEN, - "streaming session destination refused at the open-pass gate (fail closed)", - ), - StartError::BudgetRefused => refusal( - axum::http::StatusCode::PAYMENT_REQUIRED, - "streaming session budget refused (fail closed)", - ), - StartError::Durable(_) => refusal( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "streaming session durable open failed", - ), - } -} - -/// A finished plain-text response — the shape both a governed-but-uncomposed answer and a fail-closed -/// refusal are framed in, so a handler names no `IntoResponse` machinery. -fn refusal(status: axum::http::StatusCode, msg: &'static str) -> axum::response::Response { - axum::response::Response::builder() - .status(status) - .body(axum::body::Body::from(msg)) - .expect("a static-body response always builds") -} - -/// A finished plain-text response over an OWNED body (a hook's own message, an upstream failure cause). -fn text_response(status: axum::http::StatusCode, msg: String) -> axum::response::Response { - axum::response::Response::builder() - .status(status) - .body(axum::body::Body::from(msg)) - .expect("a text-body response always builds") -} - -/// A finished JSON response (the minted `ek_` payload). -fn json_response( - status: axum::http::StatusCode, - body: &serde_json::Value, -) -> axum::response::Response { - axum::response::Response::builder() - .status(status) - .header(http::header::CONTENT_TYPE, "application/json") - .body(axum::body::Body::from( - serde_json::to_vec(body).unwrap_or_default(), - )) - .expect("a json-body response always builds") -} - -/// A hook's OWN refusal, framed with its status (clamped to a valid code) and message — the shape both -/// the gate and the rewrite-gate reject in. -fn hook_refusal(status: u16, message: &str) -> axum::response::Response { - let status = - axum::http::StatusCode::from_u16(status).unwrap_or(axum::http::StatusCode::FORBIDDEN); - text_response( - status, - if message.is_empty() { - "streaming session-open refused by a hook gate".to_string() - } else { - message.to_string() - }, - ) -} - -/// Read the voice dispatch slot off the neutral `PlaneReqCtx::slot`, resolve the caller + a session id, -/// and drive the shared governed open. `slot` is the SAME `VoiceMount` `build` erased (the mount is -/// what creates the route), so the downcast never fails on a mounted route; the `Option` survives only -/// so a future refactor that mounted the route without the slot answers `500` rather than panicking. -async fn serve( - ctx: busbar_kernel::plane_routes::PlaneReqCtx, - ingress: Ingress, -) -> axum::response::Response { - let Some(mount) = ctx.slot.downcast_ref::() else { - return refusal( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "streaming route reached without its dispatch slot", - ); - }; - // The resolved caller the audience-checked key chain attached (the session owner), or the honest - // constant on an ungoverned deployment — the SAME asymmetry the MCP notification principal reads. - let owner = ctx - .caller_principal - .clone() - .unwrap_or_else(|| "".to_string()); - // The `{call_id}` capture for a WS accept, or a fresh id for a one-shot mint/SDP pass. - let call_id = ctx - .path_params - .iter() - .find(|(k, _)| k == "call_id") - .map(|(_, v)| v.clone()) - .unwrap_or_else(|| format!("streaming-{}", unix_secs(&*ctx.host))); - // The resolved presenting virtual key (the middleware-resolved, audience-checked key), or `None` - // ungoverned. The hook gate reads its `(id, name)`; the Meter step lands usage on its ledger. - let vkey = ctx.gov.as_ref().and_then(|g| g.key()).cloned(); - open_governed(GovernedOpen { - rt: &mount.runtime, - host: Arc::clone(&ctx.host), - provider: mount.provider(), - ingress, - owner, - call_id, - vkey, - body: ctx.body.clone(), - headers: ctx.headers.clone(), - now: unix_secs(&*ctx.host), - }) - .await -} - -/// Wall-clock unix seconds for the session's `charged_at` / genesis stamp, READ OFF THE HOST'S CLOCK -/// PORT (C10, state/store port): `ClockHost::clock_now_secs`, which every route already holds through -/// its `Arc`. This is what the old doc comment promised ("the gauntlet clock the -/// production pump reads off the host is threaded once the composition root wires it") and the -/// structural `SystemTime::now()` stood in for; the port's wired slot is that same `SystemTime` epoch -/// clock (`store::now_ms()` scaled to nanos and divided back to seconds), so the value — and with it -/// the session's `charged_at` money stamp — is byte-identical. Generic over the port rather than -/// taking `&dyn ClockHost`, so no trait-object upcast is needed at the call sites. -fn unix_secs(clock: &H) -> u64 { - clock.clock_now_secs() -} - -async fn mint_route(ctx: busbar_kernel::plane_routes::PlaneReqCtx) -> axum::response::Response { - serve(ctx, Ingress::Mint).await -} -async fn sdp_route(ctx: busbar_kernel::plane_routes::PlaneReqCtx) -> axum::response::Response { - serve(ctx, Ingress::Sdp).await -} -/// A DIALED LEG WHOSE PROVIDER DIAL FAILED: nothing to relay the client's frames to. Settle the -/// just-opened durable row terminal and evict it at `now`, then drop the proxy rather than serve a -/// client socket with no upstream — fail closed, no orphaned row. The handle has no drop path of its -/// own. The session never served, so the session fee its open counted is given back first (TODO -/// 17(b), ARCHITECT R4). -pub(crate) fn settle_undialed(proxy: crate::topology::telephony::TelephonyProxy, now: u64) -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - proxy.core().refund_open(); - proxy.handle.finish(now); -} - -/// THE INBOUND WS-ACCEPT FN for the browser-sideband / telephony / Gemini-Live media legs — what -/// replaces the `501` stub, moving the WS legs onto the neutral inbound WS-accept seam. Generic over -/// the dialect `codec` (the second-dialect route): [`voice_ws_arrivals`] instantiates it once per dialect -/// ([`OpenAiRealtimeCodec`] for the sideband/telephony legs, [`GeminiLiveCodec`] for the Gemini leg) so -/// every leg runs the SAME choke point rather than a per-dialect copy. It builds the `GauntletRequest` -/// and [`SessionGauntlet`] EXACTLY as [`begin_session`] does (gov threaded from the audience-checked -/// auth layer, `destination` = the locked upstream model) and hands the upgrade to [`accept_gauntlet`] -/// — the ONLY path that consumes the upgrade into a live socket, and never a bare `on_upgrade`. -/// -/// GAUNTLET-BEFORE-UPGRADE: `accept_gauntlet` runs `run_gauntlet_session` SYNCHRONOUSLY and, on a -/// refused destination, returns the gate's own `403` WITHOUT upgrading a socket, spawning a task, or -/// opening a durable row. Only on admit is the socket upgraded and `on_socket` spawned. -/// -/// VERIFY-BEFORE-CHARGE, NO ORPHANED ROW: the kernel account's budget check + durable session open happen INSIDE -/// `on_socket` — AFTER the gauntlet admitted and BEFORE the pump reads a byte — through the gauntlet- -/// free [`open_admitted_session`] / [`open_admitted_telephony`] (the gauntlet already ran; re-running -/// it would double the gate). A refused accept opens nothing; a post-admit budget/durable refusal -/// commits no durable row and simply closes the just-upgraded socket. So no refused-or-aborted accept -/// ever leaves a live session row. -/// -/// THE PROVIDER DIAL (the provider-dial leg): for `Telephony` and `Gemini`, when the ingress's dialect has a COMPOSED -/// provider, the leg opens a [`crate::topology::telephony::TelephonyProxy`] (the same thin-duplex -/// shape for both) and dials the provider through [`dial_provider`] — the net-guarded, breaker-admitted -/// path — before pumping either socket. A dial failure drops the just-admitted session rather than -/// serving a client socket with nowhere to -/// relay to. With NO provider composed, both legs fall back to serving the client socket only (the -/// documented "governed but not yet dialing" posture) exactly as before. -/// -/// EVERY SERVED SESSION IS A GOVERNED SESSION: all three legs below — the dialed proxy, the -/// uplink-only fallback and the WebRTC sideband — take their binding from -/// [`served_governed_session`], which mints this session's identifier and hands it the node's own -/// open-call table. That is the join the tool moat was missing: the composition root entered a wait -/// where it planned a call's leg, and until this binding existed there was nothing on a socket that -/// could wake it or sweep it. A deployment with no root composed reads `None` here and keeps exactly -/// the behaviour it had. -pub(crate) async fn ws_accept( - arrival: WsArrival, - ingress: Ingress, - codec: C, -) -> axum::response::Response -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - let Some(mount) = arrival.slot.downcast_ref::() else { - return refusal( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "streaming WS-accept reached without its dispatch slot", - ); - }; - // The dialect this leg speaks — the `ingress_protocol` label + the composed-provider table a - // `Telephony`/`Gemini` dial reads from. Every other leg (Sideband) stays OpenAI-labelled: it has no - // Gemini analogue today. - let dialect = match ingress { - Ingress::Gemini => crate::GEMINI_LIVE, - _ => crate::OPENAI_REALTIME, - }; - // The neutral host seam the operator hooks fire through — the SAME seam the one-shot passes read in - // `open_governed`, so the WS-accept front door screens a session-open identically to mint/SDP. - let host = Arc::clone(&arrival.host); - // The presenting key, resolved once: the authorization gate, the hook projection, the budget - // ceiling and the turn attribution all read it. - let vkey = arrival.gov.as_ref().and_then(|g| g.key()).cloned(); - // (0) AUTHORIZATION, before the upgrade — a key that holds no session scope on this pool is refused - // with the plane's own answer and no socket is bound, exactly as the one-shot passes refuse. - if let Some(k) = vkey.as_ref() { - if !session_scope_allowed(k) { - return session_scope_refusal(); - } - } - // The per-generation runtime, bound to the live host this session journals through. - let rt = Arc::new(crate::runtime::build_runtime_hosted( - &mount.runtime, - Arc::clone(&host), - )); - // The caller `(id, name)` the hook gate/tap read — the middleware-resolved key, or `None` ungoverned. - let key = vkey.as_ref().map(|k| (k.id.clone(), k.name.clone())); - // The resolved caller the audience-checked key chain attached (the session owner), or the honest - // constant on an ungoverned deployment. - let owner = arrival - .caller_principal - .clone() - .unwrap_or_else(|| "".to_string()); - // The `{call_id}` capture the accept route matched. - let call_id = arrival - .path_params - .iter() - .find(|(k, _)| k == "call_id") - .map(|(_, v)| v.clone()) - .unwrap_or_else(|| format!("streaming-{}", unix_secs(&*host))); - let now = unix_secs(&*host); - // The locked session posture: g711 for telephony, the plane-default otherwise (including Gemini — - // the Gemini leg carries no media-format lock of its own). The destination the gauntlet judges is - // this config's model, exactly as `begin_session` derives it. - let mut session_cfg = match ingress { - Ingress::Telephony => g711_config(), - _ => rt.session_defaults.clone(), - }; - // (1) HOOKS-GATE — a rejecting operator gate refuses the session-open BEFORE the upgrade: a - // pre-upgrade refusal Response, no socket bound, no lease/durable open. This closes the intra-plane - // gap where telephony (which has no preceding `ek_` mint pass) reached the media leg screened by - // nothing but the destination gauntlet; both WS legs now honor the operator gate exactly as the - // one-shot mint/SDP passes do in `open_governed`. - if let Err(refused) = hook_gate(&host, key.clone(), &call_id, now, &session_cfg).await { - return *refused; - } - // (2) HOOKS-TAP — a committed rewrite replaces the locked session posture BEFORE the gauntlet judges - // the destination and BEFORE the socket binds; byte-identical when no rewrite hook is attached. - match hook_tap(&host, key, &owner, &call_id, now, &session_cfg).await { - Ok(Some(rewritten)) => session_cfg = rewritten, - Ok(None) => {} - Err(refused) => return *refused, - } - let destination = session_cfg.model.clone().unwrap_or_default(); - let gov = arrival.gov.clone().unwrap_or_default(); - let gauntlet_req = GauntletRequest { - gov: &gov, - destination: &destination, - correlation_id: 0, - charged_at: now, - started: std::time::Instant::now(), - }; - let gate: Box = Box::new(SessionGauntlet); - // THE METER STEP's attribution for this WS session — the presenting key each turn's usage is - // landed on through the core seam, under THIS LEG'S OWN dialect label (K4: no longer a plane-wide - // constant). Built from the resolved key (or `None` ungoverned) and moved into the post-upgrade - // open below, exactly as `open_governed` does for the one-shot passes. - let meter = vkey.clone().map(|k| { - crate::runtime::metering::TurnMeter::new(Arc::clone(&host), k, FRONT_DOOR_POOL, dialect) - }); - // The composed provider FOR THIS LEG'S DIALECT — read off the PROCESS-WIDE composed statics - // directly (not `mount.provider()`/`mount.gemini_provider()`, whose per-slot override is borrowed - // from `mount` and so cannot outlive it) so the `'static` `on_socket` closure below captures a - // plain `Option<&'static ProviderEndpoint>` rather than a reference into the (non-'static) `arrival` - // this fn is about to move out of. - let provider = match ingress { - Ingress::Gemini => composed_gemini_provider(), - _ => composed_provider(), - }; - // The clock each served session's teardown is stamped with — the same host clock its open was. - let teardown_clock = Arc::clone(&host); - accept_gauntlet( - arrival.upgrade, - gauntlet_req, - gate, - move |stream, sink| async move { - match ingress { - // TELEPHONY / GEMINI: a thin duplex proxy. With a composed provider, dial it and pump - // both sockets through `TelephonyProxy::run` (the provider-dial leg); with none, fall back to serving the - // client socket only (the documented "governed but not dialing" posture). - Ingress::Telephony | Ingress::Gemini => match provider { - Some(p) => match open_admitted_telephony( - &rt, - codec, - owner, - call_id, - session_cfg, - meter, - now, - served_governed_session(), - matches!(ingress, Ingress::Telephony), - ) { - Ok(proxy) => { - let pool = stream_breaker_key(dialect); - let url = provider_ws_url(&p.base_url, dialect, &p.api_key); - match dial_provider( - host.as_ref(), - &pool, - 0, - &url, - GuardPolicy::default(), - ) - .await - { - Ok((provider_in, provider_out)) => { - let core = Arc::clone(proxy.core()); - serve_with_sweep( - core, - proxy.run(provider_in, provider_out, stream, sink), - ) - .await; - } - Err(e) => { - // The dial failed: nothing to relay client frames to. - settle_undialed(proxy, unix_secs(&*teardown_clock)); - tracing::warn!( - error = %redact_url_credentials(&e.to_string()), - dialect, - "streaming: provider dial failed; the just-admitted session is \ - dropped rather than served with no upstream" - ); - } - } - } - Err(_) => { /* budget/durable refusal: no durable row, socket closes */ } - }, - // NO PROVIDER COMPOSED: serve the client socket only. The uplink plane forwards - // client→server frames to a channel whose receiver is DROPPED (bare `_`, not a - // named binding), so `unbounded_send` fails fast via `is_disconnected` and each - // frame is discarded with ZERO buffering — client uplink is decoded + metered with - // no upstream to funnel to, and no unbounded queue grows for the session's life. - None => { - if let Ok((core, handle)) = open_admitted_session( - &rt, - codec, - owner, - call_id, - Some(session_cfg), - Carrier::sideband(), - meter, - now, - served_governed_session(), - ) { - let (upstream_tx, _) = futures::channel::mpsc::unbounded::>(); - serve_to_teardown( - Arc::clone(&core), - handle, - serve_messages( - stream, - sink, - Arc::new(UplinkForwarder::new(core, upstream_tx)), - ), - || unix_secs(&*teardown_clock), - ) - .await; - } - } - }, - // BROWSER-WEBRTC SIDEBAND: media is peer-to-peer by design (see `crate::topology::webrtc` - // docs) — this socket is control-only, so there is no provider leg to dial here. - Ingress::Sideband => { - if let Ok((core, handle)) = open_admitted_session( - &rt, - codec, - owner, - call_id, - Some(session_cfg), - Carrier::sideband(), - meter, - now, - served_governed_session(), - ) { - serve_to_teardown( - Arc::clone(&core), - handle, - serve_messages(stream, sink, Arc::new(VoiceSession::new(core))), - || unix_secs(&*teardown_clock), - ) - .await; - } - } - Ingress::Mint | Ingress::Sdp => { - // Never reached: the WS-accept seam mounts only Sideband/Telephony/Gemini (see - // `voice_ws_arrivals`); Mint/Sdp are the one-shot HTTP passes (`voice_routes`). - } - } - }, - ) -} - -// THIS PLANE'S SERVED-LEG WITNESSES — exported through `crate::testkit::SERVED` for a composition -// root's test build to run through its registered session runner (see the file's own header). -#[cfg(any(test, feature = "test-support"))] -#[path = "tests/served_witness.rs"] -pub(crate) mod served_witness; - -#[cfg(test)] -#[path = "tests/mount_tests.rs"] -mod mount_tests; - -// The egress-credential cell reads only `DECLS` — no host, no live money hop. -#[cfg(test)] -#[path = "tests/egress_tests.rs"] -mod egress_tests; - -// The remaining cells drive a real `EngineHost` double (breaker cell store / hook gate maps / the -// process-global metrics recorder) or a loopback provider, so they gate on `test-support`. -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/breaker_tests.rs"] -mod breaker_tests; -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/hook_gate_tests.rs"] -mod hook_gate_tests; -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/hook_tap_tests.rs"] -mod hook_tap_tests; -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/metrics_tests.rs"] -mod metrics_tests; -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/sdp_tests.rs"] -mod sdp_tests; - -// THE THREE COMPOSITION-ROOT CELLS: the provider credential the mint pass serves under, the real -// per-key metering lease a session reserves on, and the session-scope grant the door enforces. All -// three drive a real `EngineHost` double (and, for the mint, a loopback provider), so they gate on -// `test-support` exactly as the cells above do. -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/metering_lease_tests.rs"] -mod metering_lease_tests; -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/mint_tests.rs"] -mod mint_tests; -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/scope_gate_tests.rs"] -mod scope_gate_tests; - -// AUDIT-CHAIN + GOVERNANCE-BUDGET capability cells, re-homed to a gate-valid `/tests/` path (the -// equality gate rejects `runtime/tests.rs`). Runtime-gated so the durable engine / D2 lease compile; -// no `test-support` needed — they drive the durable session chain and the host metering lease only. -#[cfg(all(test, feature = "runtime"))] -#[path = "tests/audit_chain_tests.rs"] -mod audit_chain_tests; -#[cfg(all(test, feature = "runtime"))] -#[path = "tests/governance_budget_tests.rs"] -mod governance_budget_tests; - -// THE BINDING CELLS: a served session and a governed session are the same session. They pin the -// deadline against the plane crate's own declaration, so they gate on `test-support` (which is what -// carries that crate edge) as well as on the runtime the served open needs. -#[cfg(all(test, feature = "runtime", feature = "test-support"))] -#[path = "tests/governed_binding_tests.rs"] -mod governed_binding_tests; - -// BEHAVIORAL billing proof: drive a voice turn's usage through the SHIPPED Meter seam over a REAL -// governed `App` host and read the spend back off the one ledger (`GovState::usage_for`) — the -// ledger-level twin of the LLM plane's crossproto_delivery_billing oracle. Needs `test-support` for -// the real `App`/`engine_host` and `runtime` for the session metering types. -#[cfg(all(test, feature = "runtime", feature = "test-support"))] -#[path = "tests/billing_ledger_tests.rs"] -mod billing_ledger_tests; diff --git a/crates/busbar-voice/src/runtime/carrier.rs b/crates/busbar-voice/src/runtime/carrier.rs deleted file mode 100644 index 5b3d67a178..0000000000 --- a/crates/busbar-voice/src/runtime/carrier.rs +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE CARRIER — the live session's downlink write side and its HARD-CLOSE latch. -//! -//! A voice session is an open-ended full-duplex carrier. `busbar_kernel::ingress::byte_duplex` -//! pumps the UPSTREAM socket (server→client events in, client→server events out through the handler's -//! `out`), but the DOWNLINK toward the actual client (the browser / telephony leg) is a separate sink -//! the runtime holds here. The [`Carrier`] also owns the one thing post-hoc metering structurally -//! cannot do: a HARD CLOSE the metering lease trips the instant the budget is dry — after which no -//! further downlink audio reaches the client and the session's supervisor tears the socket down. - -use futures::channel::mpsc::UnboundedSender; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use tokio::sync::Notify; - -/// THE DOWNLINK + HARD-CLOSE handle shared across a session's per-frame handlers. Cheap to clone (an -/// `Arc` of the latch + the client sender), so every concurrent handler holds one. -#[derive(Debug, Clone)] -pub struct Carrier { - inner: Arc, -} - -#[derive(Debug)] -struct Inner { - /// Set once when the carrier hard-closes; every later downlink is dropped and the latch stays set. - closed: AtomicBool, - /// Woken on hard-close so a supervisor awaiting [`Carrier::closed`] can abort the serve task. - gate: Notify, - /// The DOWNLINK sink toward the client — one wire-framed message per send. `None` for a sideband - /// carrier (topology A) that relays no media (the browser's media path is peer-to-peer). - downlink: Option>>, -} - -impl Carrier { - /// A carrier with a downlink sink toward the client (the telephony proxy leg / a browser that - /// takes media over the same socket). - #[must_use] - pub fn with_downlink(downlink: UnboundedSender>) -> Self { - Carrier { - inner: Arc::new(Inner { - closed: AtomicBool::new(false), - gate: Notify::new(), - downlink: Some(downlink), - }), - } - } - - /// A SIDEBAND carrier that relays no media (topology A): the browser establishes the media path - /// peer-to-peer, so busbar's downlink is control-only and drops audio frames. - #[must_use] - pub fn sideband() -> Self { - Carrier { - inner: Arc::new(Inner { - closed: AtomicBool::new(false), - gate: Notify::new(), - downlink: None, - }), - } - } - - /// HARD-CLOSE the carrier: latch closed, wake any supervisor, and stop relaying downlink. Idempotent - /// — a second close is a no-op. Returns `true` the first time (the transition), `false` after. - pub fn hard_close(&self) -> bool { - let first = !self.inner.closed.swap(true, Ordering::SeqCst); - if first { - self.inner.gate.notify_waiters(); - } - first - } - - /// Whether the carrier has hard-closed. - #[must_use] - pub fn is_closed(&self) -> bool { - self.inner.closed.load(Ordering::SeqCst) - } - - /// Relay ONE wire-framed downlink message toward the client. A no-op that returns `false` once the - /// carrier is closed (the hard-close guarantee — nothing reaches the client after exhaustion) or on - /// a sideband carrier with no media path. - pub fn send_downlink(&self, frame: Vec) -> bool { - if self.is_closed() { - return false; - } - match &self.inner.downlink { - Some(tx) => tx.unbounded_send(frame).is_ok(), - None => false, - } - } - - /// Resolve when the carrier hard-closes — the await a session supervisor parks on to abort the - /// serve loop and drop the upstream socket. Returns immediately if already closed. - /// - /// REGISTER WITH THE GATE BEFORE READING THE LATCH. The wake is `notify_waiters`, which wakes the - /// waiters that are registered at the instant it runs and stores no permit for one that arrives - /// after — so a close landing between a "not closed yet" read and the registration that followed - /// it would wake nobody, and the supervisor would park for the life of the socket on a carrier - /// that had already hard-closed. Enabling first and re-reading the latch second leaves no such - /// window: a close either finds this waiter registered, or has already set the latch this reads. - pub async fn closed(&self) { - let notified = self.inner.gate.notified(); - futures::pin_mut!(notified); - notified.as_mut().enable(); - if self.is_closed() { - return; - } - notified.await; - } -} diff --git a/crates/busbar-voice/src/runtime/metering.rs b/crates/busbar-voice/src/runtime/metering.rs deleted file mode 100644 index bce2394a25..0000000000 --- a/crates/busbar-voice/src/runtime/metering.rs +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! A LIVE SESSION'S METERING — the streaming plane's per-class counts, handed to the kernel. -//! -//! A live voice carrier cannot be billed after the fact alone: the session must be HARD-CLOSED -//! mid-stream the moment the caller's budget is dry. OWNER RULING Q21b settles how, and it is the -//! same way every other plane is governed. Each closed turn's raw counts per declared class -//! (`busbar_plane_streaming::session::class_counts` — the plane's one reading of a turn) go to the -//! kernel's [`SessionAccount`], which appends them to the ledger through the one metering path and -//! answers [`TurnVerdict::Live`] or [`TurnVerdict::MustClose`] off the kernel's own budget view. The -//! session is priced at READ, against the `streams` plane's card (#47, #71); nothing here names a -//! rate, a price or a stored figure (#43). -//! -//! The D2 lease this replaces (reserve an estimate, price each turn through the flat llm card into -//! nanodollars, settle and hard-close at a cap read once at the open) is deleted; the snapshot is -//! under `~/Downloads/busbar-1.6.0-snapshots/P2-voice/`. - -use busbar_contract::billing::Usage; -pub use busbar_kernel::plane_host::session_meter::{BudgetRefused, SessionAccount, TurnVerdict}; -use busbar_kernel::plane_host::EngineHost; -use busbar_plane_streaming::session::{class_counts, TurnCounters}; -use std::sync::Arc; - -/// THE PRESENTING KEY a live session is metered for, before the session opens: the live host, the -/// resolved key, the front-door pool its buckets are filtered by, and the upstream's provider label -/// (the lane's subject when the session names no model). Built at the governed open (where the key -/// and the host are both in hand); `None` on an ungoverned deployment, which has no key to attribute -/// anything to. -pub struct TurnMeter { - host: Arc, - key: busbar_contract::records::VirtualKey, - pool: &'static str, - provider: &'static str, -} - -impl TurnMeter { - /// Bind the attribution over a live host + resolved key. - #[must_use] - pub fn new( - host: Arc, - key: busbar_contract::records::VirtualKey, - pool: &'static str, - provider: &'static str, - ) -> Self { - TurnMeter { - host, - key, - pool, - provider, - } - } - - /// OPEN the session's account at the kernel, for turns of `model`. `Err` is a chain the kernel's - /// budget view already reads dry — the session must not open. `Ok(None)` is a host with - /// governance off: nothing is ledgered and nothing closes the session on budget. - /// - /// The ledger lane is `voice` + U+001F + the model (the provider label when the session names no - /// model), so the view prices it with the `streams` card and never with the llm plane's flat one. - /// That lane is also the session's ONE metering row: the kernel's accrual mirrors each count onto - /// the row keyed `(model: , provider: voice)` — this plane's key, the way - /// an MCP or A2A row carries theirs — so `GET /admin/usage` reads it as a streams row. - pub fn open(self, model: &str) -> Result, BudgetRefused> { - let name = if model.is_empty() { - self.provider - } else { - model - }; - let lane = format!( - "{}{}{name}", - crate::PLANE_KEY, - busbar_kernel::governance::PLANE_LANE_SEP - ); - let account = - SessionAccount::open(Arc::clone(&self.host), Some(&self.key), self.pool, lane)?; - Ok(account.map(|account| SessionMetering { account })) - } -} - -/// ONE OPEN SESSION'S METERING, plane-side: the kernel account its turns are reported to. -pub struct SessionMetering { - account: SessionAccount, -} - -impl SessionMetering { - /// Report ONE closed turn — `usage` is the upstream's report (`None` for a turn that ended on an - /// error or with the session), `counters` the plane's own bookkeeping — to the kernel account, - /// which ledgers its raw counts per class and answers whether the carrier stays open. - /// - /// THAT IS THE TURN'S WHOLE METERING (#47, OWNER RULING Q32). A turn writes no series row of its - /// own: a series row counts a REQUEST, and a turn is not one — the session is admitted and paid - /// for once at its open (`fees.per_session`), and a turn of a conversation already opened draws - /// no per-request fee in the budget book. The series row this used to write carried the UPSTREAM - /// dialect label (`openai_realtime`) as its provider, so `GET /admin/usage` read it as a pools - /// row and charged the pools plane's flat `per_request_fee:` per turn; keyed by this plane - /// instead, it would have charged `streams.fees.per_request` per turn. The budget book charges - /// neither, and the view now agrees with it: the turn's counts reach the metering row through the - /// kernel's one accrual, on the plane-keyed row [`TurnMeter::open`] names. - pub fn report_turn( - &self, - usage: Option<&crate::ir::usage::IrDuplexUsage>, - counters: TurnCounters, - ) -> TurnVerdict { - self.account.report_turn(&plane_counts(usage, counters)) - } - - /// The session is SERVED: its provider leg is up (a socket session) or its one-shot pass - /// answered success (a mint, an SDP broker). The session fee the kernel counted at the open is - /// final; marking it again changes nothing. - pub fn served(&self) { - self.account.served(); - } - - /// The session's open failed after its account counted it — its mint, SDP broker, provider dial - /// or durable open: the kernel gives the session fee back (TODO 17(b), ARCHITECT R4), exactly - /// once, since a session that never opened charges nothing. A no-op once the session served. - pub fn refund_open(&self) { - self.account.refund_open(); - } -} - -/// A closed turn's raw counts per class, as the streaming plane reads them, on the neutral carrier -/// the ledger takes. -#[must_use] -pub fn plane_counts( - usage: Option<&crate::ir::usage::IrDuplexUsage>, - counters: TurnCounters, -) -> Usage { - Usage { - usage_units: class_counts(usage, counters) - .into_iter() - .map(|(class, n)| (class.as_str().to_string(), n)) - .collect(), - } -} diff --git a/crates/busbar-voice/src/runtime/mod.rs b/crates/busbar-voice/src/runtime/mod.rs deleted file mode 100644 index cceeff728e..0000000000 --- a/crates/busbar-voice/src/runtime/mod.rs +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE T2 VOICE SESSION RUNTIME — the live duplex engine (design `BUSBAR-1.6.0.md` #18/#45). Behind the -//! `runtime` cargo feature (OFF by default, HARD RULE 4): the default / prod build compiles the IR + -//! declarations only, so the workspace is unaffected regardless of this module's state. -//! -//! The runtime binds the neutral byte-duplex pump (`serve_messages`), the codec's -//! `DuplexReader`/`DuplexWriter` pair, the durable `SessionScope`, and the kernel session account -//! (per-class counts, ledgered and budget-checked kernel-side) into one governed carrier — exposed to -//! both topologies (`crate::topology`). - -pub mod carrier; -pub mod metering; -pub mod scope; -pub mod session; -/// THE TOOL EXECUTOR PORT and its production default -/// ([`tools::ClientRelay`]: this node serves no tool, so every call is relayed to the caller). The -/// echo executor the batteries drive is a test double, present only in test builds. -pub mod tools { - #[cfg(any(test, feature = "test-support"))] - pub use crate::testkit::echo::EchoToolExecutor; - pub use busbar_plane_streaming::tools::{ClientRelay, ToolExecutor}; -} - -pub use carrier::Carrier; -// THE GOVERNED-CALL PORT, RE-EXPORTED FROM `busbar-plane-streaming`. `GovernedCalls` / `ReplyRefusal` / -// `GovernedSession` moved to the plane crate, beside the two declarations the composition root -// already pairs them with (`busbar_plane_streaming::open_calls::{FACT_TOOL_CORRELATION, -// TOOL_REPLY_DEADLINE_SECS}`) — the port and the key a wait is entered under are now one crate, so -// they cannot drift apart in silence. Re-exported HERE under the old name so every caller that -// spells `busbar_voice::runtime::GovernedCalls` resolves exactly what it always did. The split is a -// MOVE: no item changed shape crossing it. -pub use busbar_plane_streaming::governed::{GovernedCalls, GovernedSession, ReplyRefusal}; -pub use metering::{BudgetRefused, SessionMetering, TurnMeter, TurnVerdict}; -pub use scope::{SessionHandle, VoiceSessionRow}; -pub use session::{ - serve_to_teardown, serve_with_sweep, Outbound, SessionCore, UplinkForwarder, VoiceSession, -}; -#[cfg(any(test, feature = "test-support"))] -pub use tools::EchoToolExecutor; -pub use tools::{ClientRelay, ToolExecutor}; - -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use std::sync::Arc; - -/// THE PLANE'S PER-GENERATION RUNTIME OBJECT — the type-erased slot `PLANE_HOOKS.build_runtime` builds -/// (see `crate::PLANE_HOOKS`). It carries the process-wide dependencies a session is assembled from: the -/// durable-handle engine sessions bind into and the server-side tool executor. The plane holds no -/// pricing of its own: a session's metering is the kernel account its governed open binds. A session -/// (either topology) is constructed FROM this object; it holds no per-session state itself. -pub struct VoiceRuntime { - /// The process-wide durable-handle engine every session's [`SessionHandle`] binds into. - pub engine: Arc, - /// The server-side tool executor (the tool moat) shared across sessions. - pub tools: Arc, - /// THE LOCKED SESSION DEFAULTS every session opens with, read from the operator's `streams.session:` - /// (VAD/media/tool set). Seeded from [`crate::config::StreamsCfg`] at [`build_runtime`]; the pump - /// re-applies it server-side so a client `session.update` is reconciled against it, never trusted - /// blind. - pub session_defaults: crate::ir::config::SessionConfig, - /// The hard session wall-clock ceiling (`streams.session_max_secs:`), bound onto every session - /// core at open and compared on the sweep tick beside the pump (`SessionCore::enforce_ceiling`). - /// `None` — the default, and what an operator who writes nothing gets — is no ceiling (Q21a). - pub session_max_secs: Option, - /// The context-window ceiling (`streams.context_window_tokens:`). - pub context_window_tokens: u32, - /// The per-response output-token ceiling (`streams.max_output_tokens:`). - pub max_output_tokens: u32, - /// THE LIVE HOST — bound only by [`build_runtime_hosted`] once a mounted route hands the runtime a - /// real `Arc`. `None` on the pre-host/dev-default runtime (no admin-audit trail to - /// write to). Carried so a governed session mutation can land ONE admin-audit row through the SAME - /// seam the host's other planes journal through (see [`VoiceRuntime::audit_session`]). - pub host: Option>, -} - -impl VoiceRuntime { - /// Assemble a runtime object from its dependencies. - #[must_use] - pub fn new(engine: Arc, tools: Arc) -> Self { - let defaults = crate::config::StreamsCfg::default(); - VoiceRuntime { - engine, - tools, - session_defaults: defaults.session, - session_max_secs: defaults.session_max_secs, - context_window_tokens: defaults.context_window_tokens, - max_output_tokens: defaults.max_output_tokens, - host: None, - } - } - - /// SEED the locked session config + the three session ceilings from the operator's `streams:` - /// section. Called by [`build_runtime`] with the plane's own typed [`crate::config::StreamsCfg`] - /// (an absent section falls back to `StreamsCfg::default()`), so the runtime a session is built - /// FROM carries the operator's real posture rather than the dev defaults. - #[must_use] - pub fn with_streams(mut self, cfg: &crate::config::StreamsCfg) -> Self { - self.session_defaults = cfg.session.clone(); - self.session_max_secs = cfg.session_max_secs; - self.context_window_tokens = cfg.context_window_tokens; - self.max_output_tokens = cfg.max_output_tokens; - self - } - - /// Bind a fresh [`SessionHandle`] for `(owner, id)` into this runtime's durable engine. - #[must_use] - pub fn bind_session(&self, owner: impl Into, id: impl Into) -> SessionHandle { - SessionHandle::bind(Arc::clone(&self.engine), owner, id) - } - - /// Land ONE admin-audit row for a voice-plane mutation through the live host's `JournalHost` leg — - /// a no-op on a runtime with no bound host (the pre-host/dev-default runtime, or an ungoverned - /// deployment resolving no key has nothing to attribute the row to; callers pass a real principal - /// only when one was resolved). `outcome` is a fixed vocabulary literal, matching every other - /// plane's `audit_record` call shape. - pub(crate) fn audit_session( - &self, - action: &str, - resource: &str, - outcome: &'static str, - principal: &str, - ) { - if let Some(host) = &self.host { - host.audit_record(action, resource, outcome, principal); - } - } -} - -/// THE `PLANE_HOOKS.build_runtime` HOOK BODY — builds the plane's per-generation runtime object -/// (type-erased as `Arc`), the seam the composition root composes the voice -/// runtime slot through (see `crate::PLANE_HOOKS`). Wired behind the `runtime` feature; the default -/// (feature-off) build leaves the hook `None`. -/// -/// WHAT IS WIRED, AND WHAT REMAINS A DEV DEFAULT. This entry reads the REAL `streams:` config (session -/// posture / ceilings, via `with_streams`). A session's metering is not a runtime dependency at all: -/// it is the kernel account the governed open binds for the presenting key over the live host. What is -/// still a dev default is the durable engine (a fresh [`DurableHandleEngine`]). The tool executor is -/// [`ClientRelay`]: this node serves no tool, and every call is relayed to the caller. -/// `prior` (carry-over) is ignored today; the signature is the real one so binding those -/// config-derived dependencies is a body change, not an ABI change. -pub fn build_runtime( - section: &dyn std::any::Any, - _prior: Option<&dyn busbar_kernel::plane_host::PlaneSlots>, -) -> Arc { - // READ THE REAL `streams:` CONFIG: core passes the plane's own typed section as `cfg.streams.as_any()` - // (the `PlaneCfg::as_any` of `StreamsCfg`). An absent/other section downcasts to `None` and falls back - // to the plane default, so a deployment with no `streams:` block still builds a runtime — with the - // plane's own default posture, not an empty one. - let streams = section - .downcast_ref::() - .cloned() - .unwrap_or_default(); - Arc::new( - VoiceRuntime::new(Arc::new(DurableHandleEngine::new()), Arc::new(ClientRelay)) - .with_streams(&streams), - ) -} - -/// THE PRODUCTION composition entry — take a generation's runtime and bind the live host onto it. -/// Called by every mounted route the moment the live host is in hand (the route layer is where an -/// `Arc` first exists — the per-generation slot is built before any request), so the -/// session's admin-audit row journals through the host. -/// -/// Everything else is SHARED with `base`, not rebuilt: the same durable-handle engine (so a session -/// opened on one request is the same durable working set another request sees), the same tool -/// executor, and the same operator session posture and ceilings. -#[must_use] -pub fn build_runtime_hosted( - base: &VoiceRuntime, - host: Arc, -) -> VoiceRuntime { - VoiceRuntime { - engine: Arc::clone(&base.engine), - tools: Arc::clone(&base.tools), - session_defaults: base.session_defaults.clone(), - session_max_secs: base.session_max_secs, - context_window_tokens: base.context_window_tokens, - max_output_tokens: base.max_output_tokens, - host: Some(host), - } -} - -#[cfg(test)] -mod tests; - -// The open-call table as a served session pump reaches it (the table itself is the plane's). -#[cfg(test)] -#[path = "../tests/governed_table_tests.rs"] -mod governed_table_tests; - -// THE VOICE PLANE-UNIT BILLING ORACLE — the voice money-path regression backstop that replaces the D2 -// lease oracle (Q21b): one fixed session script, every per-class count it ledgers and every verdict -// the kernel answers pinned. -#[cfg(test)] -#[path = "voice_plane_unit_oracle.rs"] -mod voice_plane_unit_oracle; diff --git a/crates/busbar-voice/src/runtime/scope.rs b/crates/busbar-voice/src/runtime/scope.rs deleted file mode 100644 index 83d71560fb..0000000000 --- a/crates/busbar-voice/src/runtime/scope.rs +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE CONNECTION-LIFETIME SESSION STATE — a thin binding over the neutral -//! `busbar_kernel::plane_host::SessionScope` (design `BUSBAR-1.6.0.md` #18/#45). One voice session owns ONE durable handle -//! in the process-wide `DurableHandleEngine`, keyed by `(owner, id)`. The owner is load-bearing: a -//! second session bound to the same `id` under a DIFFERENT owner collapses to the exact same -//! indistinguishable refusal (`HandleDenied::NotYours` / `ScopedMutateError::NotYours`) — a foreign -//! owner can neither read, resume, nor evict the handle, and cannot even tell it exists. That -//! anti-enumeration contract is carried up from the engine unchanged; this module only stamps the -//! session's `(owner, id)` into the row and drives open → bump → close. - -pub use crate::plane_session_row::VoiceSessionRow; -use crate::plane_session_row::STREAMING_SESSION_KIND; -use busbar_contract::records::RecordStoreResult; -use busbar_kernel::plane::handle_engine::{ - ChainPosition, DurableHandleEngine, HandleEngineError, HandleMeta, Mutation, RehydrateCounts, - RehydrateOutcome, ScopedMutateError, SealedEvent, SubmitRecord, SweepBounds, -}; -use busbar_kernel::plane::store::PlaneStore; -use busbar_kernel::plane_host::SessionScope; -use std::sync::Arc; - -/// Retain a live session for an hour of idle, an hour past terminal, and cap the working set — plain, -/// generous bounds for a long-lived carrier (the plane's own retention policy, not a wire fact). -fn session_bounds() -> SweepBounds { - SweepBounds { - abandon_secs: 3_600, - terminal_ttl_secs: 3_600, - max_retained: 4_096, - } -} - -/// The row's handle metadata, as the kernel's handle engine indexes it. -fn row_meta(row: &VoiceSessionRow) -> HandleMeta { - HandleMeta { - owner: row.owner.clone(), - updated_at: row.updated_at, - terminal: row.terminal, - cursor: row.turns, - } -} - -/// The row as the opaque value the handle engine stores. -fn row_arc(row: VoiceSessionRow) -> Arc { - Arc::new(row) -} - -/// A VOICE SESSION'S DURABLE BINDING — the [`SessionScope`] plus the plane's row shape. Open it at -/// session start, bump it per settled turn, close it (owner-gated, terminal-only) at teardown. -pub struct SessionHandle { - scope: SessionScope, -} - -impl SessionHandle { - /// Bind a session to the durable handle keyed by `id`, attributed to `owner`, in `engine`. Pure - /// binding — touches the engine only on [`open`](Self::open) / [`bump_turn`](Self::bump_turn) / - /// [`close`](Self::close). Use to attach to a boot-rehydrated handle, or before [`open`](Self::open) - /// submits a fresh one. - #[must_use] - pub fn bind( - engine: Arc, - owner: impl Into, - id: impl Into, - ) -> Self { - SessionHandle { - scope: SessionScope::new(engine, owner, id), - } - } - - /// The principal this session's handle is attributed to. - #[must_use] - pub fn owner(&self) -> &str { - self.scope.owner() - } - - /// The opaque handle id this session is bound to. - #[must_use] - pub fn id(&self) -> &str { - self.scope.id() - } - - /// OPEN the session's durable handle at genesis, stamping the row with THIS session's `(owner, id)` - /// — the binding contract the scope documents (a genesis under a diverging owner/id would leave the - /// session unable to read what it just opened). - pub fn open(&self, now: u64) -> Result<(), HandleEngineError> { - let row = VoiceSessionRow { - id: self.scope.id().to_string(), - owner: self.scope.owner().to_string(), - turns: 0, - updated_at: now, - terminal: false, - rtc_call_id: None, - }; - self.scope.open( - now, - session_bounds(), - |_pos: &ChainPosition| { - let record = row.record(); - let meta = row_meta(&row); - Ok(SubmitRecord { - id: row.id.clone(), - row: row_arc(row.clone()), - meta, - row_record: record.clone(), - event: Some(SealedEvent { - record, - tail_hash: format!("streaming-genesis-{}", row.id), - }), - }) - }, - // An ACTIVE session idle past `abandon_secs` is ABANDONED TERMINAL by the retention - // sweep, so the sweep's terminal-TTL rule can then evict it. Opting out (`None`) left - // every session whose teardown never ran — a dropped socket, a one-shot pass whose - // sideband never came — ACTIVE for ever: the cap rule evicts only terminal rows, so the - // working set grew without bound. Explicit teardown ([`SessionHandle::finish`]) is the - // fast path; this is the backstop that makes it not the only one. - |_id, row, _pos, now| abandon_idle(row, now), - // No durable sink attached in this build ⇒ no sweep-time failures to report. - |_id, _e| {}, - )?; - Ok(()) - } - - /// Read the current durable row through the engine's SCOPED read — a foreign-owner session is - /// refused with the one indistinguishable `NotYours`, never learning whether the handle exists. - pub fn get(&self) -> Option { - self.scope - .get() - .ok() - .and_then(|row| row.downcast_ref::().cloned()) - } - - /// BUMP the turn cursor (owner-gated mutate) — the per-turn durable checkpoint. Returns the new - /// turn count, or the scoped mutate error (`NotYours` for a foreign owner). - pub fn bump_turn(&self, now: u64) -> Result { - let row = self.scope.mutate(|row, _pos| { - let cur = row - .downcast_ref::() - .expect("streaming session row"); - let mut next = cur.clone(); - next.turns += 1; - next.updated_at = now; - Ok(Some(mutation_for(next))) - })?; - Ok(row - .downcast_ref::() - .map(|r| r.turns) - .unwrap_or_default()) - } - - /// STAMP the `rtc_` correlation key (owner-gated mutate) — the SDP broker calls this with - /// the `Location` header it preserved from `POST /v1/realtime/calls`, so the durable row and the - /// sideband dial provably name the same brokered media call. A foreign owner is refused with the one - /// indistinguishable `NotYours`, exactly as every other mutate on this handle. - pub fn set_rtc_call_id(&self, rtc_call_id: &str, now: u64) -> Result<(), ScopedMutateError> { - self.scope.mutate(|row, _pos| { - let cur = row - .downcast_ref::() - .expect("streaming session row"); - let mut next = cur.clone(); - next.rtc_call_id = Some(rtc_call_id.to_string()); - next.updated_at = now; - Ok(Some(mutation_for(next))) - })?; - Ok(()) - } - - /// Drive the session TERMINAL (owner-gated) so it can be evicted — the settle step before close. - pub fn settle_terminal(&self, now: u64) -> Result<(), ScopedMutateError> { - self.scope.mutate(|row, _pos| { - let cur = row - .downcast_ref::() - .expect("streaming session row"); - let mut next = cur.clone(); - next.terminal = true; - next.updated_at = now; - Ok(Some(mutation_for(next))) - })?; - Ok(()) - } - - /// TEAR the session DOWN: drive it terminal, then evict it — the one call a serving site makes - /// when its pump returns. Returns `true` when the handle was this session's and is now gone. - /// - /// Every serving site used to drop its handle unsettled, so its durable row stayed ACTIVE after - /// the socket was gone and nothing but the retention sweep could ever end it. - pub fn finish(&self, now: u64) -> bool { - self.settle_terminal(now).is_ok() && self.close() - } - - /// CLOSE the session: evict the terminal handle from the working set (owner-gated, terminal-only), - /// leaving durable rows behind. Returns `true` only when this session owns the handle AND it was - /// terminal; a foreign owner or a still-active handle returns `false`. - pub fn close(&self) -> bool { - self.scope.close() - } -} - -/// BOOT-REHYDRATE the durable `voice_session` working-set from `store` into `engine` — the -/// [`crate::PLANE_HOOKS`] `hydrate` step, mirroring the A2A task-set restore. Reads every persisted -/// `voice_session` row through the neutral [`DurableHandleEngine::rehydrate`] seam and, per row: -/// installs an ACTIVE session back into the working set, counts (and leaves) a TERMINAL one, and counts -/// (and skips) a row whose durable body cannot be decoded — so one unreadable row never aborts the -/// whole restore. Only a store-level list failure is fatal (propagated). Returns the neutral counts. -/// -/// This restores the DURABLE binding a resumed session reattaches to via [`SessionHandle::bind`]; the -/// live carrier/socket of a session cannot survive a restart, so a rehydrated session is a durable -/// record to reattach or reconcile, not a live pump — exactly the A2A task-restore contract. -pub fn rehydrate_sessions( - engine: &DurableHandleEngine, - store: &dyn PlaneStore, -) -> RecordStoreResult { - engine.rehydrate(store, STREAMING_SESSION_KIND, |_store, body| { - // Decode the row the durable body IS; an undecodable body is counted unreadable, never fatal. - match serde_json::from_slice::(body) { - Ok(row) if row.terminal => Ok(RehydrateOutcome::Terminal), - Ok(row) => Ok(RehydrateOutcome::Active { - id: row.id.clone(), - meta: row_meta(&row), - row: row_arc(row.clone()), - // Voice sessions carry no resumable event chain beyond genesis in this build. - pos: ChainPosition::genesis(), - event_unreadable: 0, - }), - Err(_) => Ok(RehydrateOutcome::Unreadable), - } - }) -} - -/// The retention sweep's transition for an idle ACTIVE voice session: the same row, terminal, stamped -/// at `now`. `None` for a row that is not a voice session's (nothing this plane can speak for). -fn abandon_idle(row: &(dyn std::any::Any + Send + Sync), now: u64) -> Option { - let cur = row.downcast_ref::()?; - let mut next = cur.clone(); - next.terminal = true; - next.updated_at = now; - Some(mutation_for(next)) -} - -/// Wall-clock Unix seconds — the clock a serving site stamps its teardown with. The retention sweep -/// ages a row by these same seconds, so a teardown stamped on another clock would age wrongly. -#[must_use] -pub fn unix_now_secs() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0) -} - -fn mutation_for(next: VoiceSessionRow) -> Mutation { - let record = next.record(); - let meta = row_meta(&next); - Mutation { - row: Some(row_arc(next)), - meta: Some(meta), - row_record: Some(record), - event: None, - } -} diff --git a/crates/busbar-voice/src/runtime/session.rs b/crates/busbar-voice/src/runtime/session.rs deleted file mode 100644 index bcd7637b3b..0000000000 --- a/crates/busbar-voice/src/runtime/session.rs +++ /dev/null @@ -1,392 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE LIVE DUPLEX SESSION RUNTIME — the pump body, behind the `runtime` feature. -//! -//! Binds the neutral byte-duplex pump (`busbar_kernel::ingress::byte_duplex::serve_messages`), the -//! codec's `DuplexReader`/`DuplexWriter` pair, the durable `SessionScope`, and the kernel session -//! account (each closed turn's per-class counts, ledgered and budget-checked kernel-side) into one -//! governed carrier. The runtime is GENERIC over the codec traits (HARD RULE 3) so it does -//! not depend on WHICH dialect codec is present — the Gemini codec drops in unchanged. -//! -//! CONCURRENCY POSTURE. The neutral pump `tokio::spawn`s one `handle` per inbound frame, so the -//! decode+act logic lives in a SYNCHRONOUS, self-contained core ([`SessionCore::on_server_frame`] / -//! [`SessionCore::on_client_frame`]) that returns an [`Outbound`] plan; the `DuplexPlane` glue merely -//! drives that plan onto `out` (upstream) and the [`Carrier`] (downlink / hard-close). That keeps the -//! marquee behaviours — tool correlation, barge-in truncate, metered hard-close — deterministically -//! unit-testable without the async plumbing, while the pump integration is tested separately. - -use crate::ir::codec::{DuplexReader, DuplexWriter, WireEvent}; -use crate::ir::config::SessionConfig; -use crate::ir::usage::IrDuplexUsage; -use crate::runtime::carrier::Carrier; -use crate::runtime::metering::{SessionMetering, TurnVerdict}; -use crate::runtime::tools::ToolExecutor; -use busbar_plane_streaming::{ - governed::GovernedSession, - session::TurnCounters, - session_pump::{SessionPump, TurnSink}, -}; -use bytes::Bytes; -use std::sync::{Arc, Mutex}; - -use busbar_kernel::ingress::byte_duplex::{CallRef as WireCallRef, DuplexHandle, DuplexPlane}; - -/// THE FRAME PLAN one decoded inbound frame produces — what to write UPSTREAM (client→server events: -/// tool results, barge-in cancel/truncate, `response.create`), what to relay DOWNLINK to the client, -/// and whether the kernel's budget verdict tripped a HARD CLOSE this frame. Re-exported with the named reason a -/// session closed at its configured wall-clock ceiling is told under. -pub use busbar_plane_streaming::session_pump::{Outbound, SESSION_CEILING_REASON}; - -/// A closed turn's way to the kernel account: the session's metering, when it has one. A turn the -/// account answers `MustClose` for cuts the session. -struct MeterSink<'m>(Option<&'m SessionMetering>); - -impl TurnSink for MeterSink<'_> { - fn turn_closed(&mut self, usage: Option<&IrDuplexUsage>, counters: TurnCounters) -> bool { - match self.0 { - Some(metering) => metering.report_turn(usage, counters) != TurnVerdict::MustClose, - None => true, - } - } -} - -/// THE GOVERNED SESSION CORE — the synchronous heart shared across the concurrent frame handlers. It -/// holds the plane's session pump (the codec, the locked config the browser cannot override, the -/// in-flight tool calls, the open turn's counters, the node's open-call binding) behind one lock, -/// and around it the session's metering (each closed turn's counts go to the kernel account), the -/// tool executor, the carrier and the session's wall-clock ceiling. Generic over the codec `C` -/// (HARD RULE 3); the tool executor is a dependency-inverted port. -pub struct SessionCore { - pump: Mutex>, - /// The session's metering: each closed turn's raw counts per class go to the kernel account, - /// which ledgers them through the one metering path and answers whether the carrier stays open. - /// `None` on an ungoverned deployment — nothing to attribute, nothing to close on. - metering: Option, - tools: Arc, - carrier: Carrier, - /// When this session opened — the start of the wall clock [`Self::ceiling`] bounds. - opened: std::time::Instant, - /// The hard session wall-clock ceiling (`streams.session_max_secs:`), `None` until a runtime - /// binds one ([`Self::with_session_ceiling`]). Compared on the sweep tick beside the pump - /// ([`Self::enforce_ceiling`]): a session past it is hard-closed exactly as a dry budget is. - ceiling: Option, -} - -impl SessionCore -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - /// Assemble a session core. `metering` is the session's OPEN kernel account (opened, and its - /// budget checked, at session start); `locked_config` is the plane's authoritative - /// tools+instructions. - pub fn new( - codec: C, - metering: Option, - tools: Arc, - carrier: Carrier, - locked_config: Option, - ) -> Self { - SessionCore { - pump: Mutex::new(SessionPump::new(codec, locked_config)), - metering, - tools, - carrier, - opened: std::time::Instant::now(), - ceiling: None, - } - } - - /// The session is SERVED: the session fee its account counted at the open is final. Every - /// socket session is marked by [`serve_with_sweep`], the one loop each serves through; a one-shot - /// pass marks it when its answer succeeds. Idempotent. - pub fn served(&self) { - if let Some(metering) = &self.metering { - metering.served(); - } - } - - /// The session's open FAILED after its account counted the session fee — its mint, SDP broker or - /// provider dial failed, or its one-shot pass answered anything but success: give the fee back - /// (TODO 17(b), ARCHITECT R4), exactly once. A no-op on a served session, or an ungoverned one. - pub fn refund_open(&self) { - if let Some(metering) = &self.metering { - metering.refund_open(); - } - } - - /// Bind the hard session wall-clock ceiling (`streams.session_max_secs:`). - #[must_use] - pub fn with_session_ceiling(mut self, secs: u32) -> Self { - self.ceiling = Some(std::time::Duration::from_secs(u64::from(secs))); - self - } - - /// Close the session when it has run past its ceiling at `now`: the caller is told why, in its - /// dialect, and the carrier hard-closes. `true` when it closed; a session with no ceiling never - /// does. - pub fn enforce_ceiling(&self, now: std::time::Instant) -> bool { - let Some(ceiling) = self.ceiling else { - return false; - }; - if now.saturating_duration_since(self.opened) < ceiling { - return false; - } - if !self.carrier.is_closed() { - let told = self.lock().ceiling_error(ceiling.as_secs()); - if let Some(frame) = told { - self.carrier.send_downlink(frame.0.to_vec()); - } - } - self.carrier.hard_close(); - true - } - - /// Bind the node's open-call table for this session. - #[must_use] - pub fn with_governed(self, governed: GovernedSession) -> Self { - self.lock().bind_governed(governed); - self - } - - /// The node's session id, when a table is bound. - #[must_use] - pub fn governed_session(&self) -> Option { - self.lock().governed_session() - } - - /// Sweep the table's unanswered calls past their deadline at `now_ms`; how many ended. - pub fn sweep_expired(&self, now_ms: u64) -> usize { - self.lock().sweep_expired(now_ms) - } - - /// The session's carrier. - pub fn carrier(&self) -> &Carrier { - &self.carrier - } - - fn lock(&self) -> std::sync::MutexGuard<'_, SessionPump> { - self.pump.lock().expect("session inner poisoned") - } - - /// A FRAME FROM THE SERVED SOCKET's far side. Decoded and answered by the plane's pump; the - /// calls this session serves itself run here, off the lock, in close order. - pub async fn on_server_frame(&self, frame: WireEvent) -> Outbound { - if self.carrier.is_closed() { - return Outbound::default(); - } - let tools = Arc::clone(&self.tools); - let (mut out, to_exec) = self.lock().on_server_frame( - frame, - now_ms(), - &mut MeterSink(self.metering.as_ref()), - &|name: &str| tools.executes_here(name), - ); - for run in to_exec { - let output = self.tools.execute(&run.name, &run.args).await; - self.lock().tool_executed(run, output, &mut out); - } - if out.close { - self.carrier.hard_close(); - } - out - } - - /// A FRAME FROM THE CALLER, decoded and answered by the plane's pump. - pub fn on_client_frame(&self, frame: WireEvent) -> Outbound { - if self.carrier.is_closed() { - return Outbound::default(); - } - self.lock().on_client_frame(frame) - } - - /// The session ends: the table forgets every call it had open. - pub fn forget_governed_calls(&self) { - self.lock().forget_governed_calls(); - } - - /// The session ends with a turn open: its counters settle, once. - pub fn settle_open_turn(&self) { - self.lock() - .settle_open_turn(&mut MeterSink(self.metering.as_ref())); - } -} - -/// How often the tick beside a session's pump sweeps its governed calls. -/// -/// One second, against a reply deadline the plane declares in tens of seconds: fine enough that a -/// call ends within a second of the deadline it was given, coarse enough that an idle session's tick -/// is not a cost. The deadline itself is not this crate's to name — the leg the root plans carries it, -/// and the sweep only asks the table which of them have passed. -const SWEEP_EVERY: std::time::Duration = std::time::Duration::from_secs(1); - -/// **THE TICK BESIDE THE PUMP.** Run one session's frame loop with its governed-call sweep beside it, -/// and end when the pump does. -/// -/// The wake has a frame to arrive on. The sweep does not: a client that simply never replies sends -/// nothing, so there is no frame on which anyone would notice, and a wait nobody sweeps is a hold -/// nobody settles. This is the only place in the session's life where time passing is itself an -/// event. -/// -/// It rides each session's own pump rather than a process-wide loop, which is the shape this plane -/// already has — voice spawns no boot task, and a sweep that outlived the socket it was sweeping for -/// would be exactly the background loop the plane's start hook says it does not have. When the pump -/// returns, the tick is dropped with it. -pub async fn serve_with_sweep(core: Arc>, pump: F) -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, - F: std::future::Future, -{ - // The session starts to serve: the fee its open counted is final, and no refund can follow. - core.served(); - let sweep = async { - loop { - tokio::time::sleep(SWEEP_EVERY).await; - // A closed carrier is a conversation that is over; nothing left on it can be answered and - // the pump is on its way out anyway. - if core.carrier().is_closed() { - return; - } - // The session wall-clock ceiling is time passing, too, and so is noticed here: a session - // past it is hard-closed and the pump ends with the sweep. - if core.enforce_ceiling(std::time::Instant::now()) { - return; - } - core.sweep_expired(now_ms()); - } - }; - futures::pin_mut!(pump); - futures::pin_mut!(sweep); - // The sweep never completes on its own, so this ends when — and only when — the pump does. - futures::future::select(pump, sweep).await; -} - -/// **SERVE A SESSION TO ITS TEARDOWN.** [`serve_with_sweep`], then settle the session's durable row -/// terminal and evict it, stamped at `now()` taken when the pump returns. -/// -/// Every serving site used to drop its [`crate::runtime::SessionHandle`] unsettled once the pump -/// returned, so the row outlived the socket as an ACTIVE session. This is the one shape a serving -/// site takes instead, so none of them can forget the second half. -pub async fn serve_to_teardown( - core: Arc>, - handle: crate::runtime::SessionHandle, - pump: F, - now: N, -) where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, - F: std::future::Future, - N: FnOnce() -> u64, -{ - serve_with_sweep(Arc::clone(&core), pump).await; - core.settle_open_turn(); - core.forget_governed_calls(); - handle.finish(now()); -} - -/// Wall-clock milliseconds, for the sweep's "which deadlines have passed" question. -/// -/// The reading is handed to the node's table rather than compared here: which calls are past their -/// deadline is the table's judgement, and a clock read on this side that the table then re-derived -/// would be two clocks deciding one deadline. -fn now_ms() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX)) - .unwrap_or(0) -} - -/// THE UPSTREAM-FACING PLANE — bound to the socket busbar holds to the provider (OpenAI Realtime). The -/// neutral pump reads server→client events off it; each `handle` decodes one and drives the plan onto -/// `out` (the client→server write side of the SAME socket) and the carrier (downlink to the client). -pub struct VoiceSession { - core: Arc>, -} - -impl VoiceSession { - /// Bind the plane to a session core. - pub fn new(core: Arc>) -> Self { - VoiceSession { core } - } - - /// The shared session core. - pub fn core(&self) -> &Arc> { - &self.core - } -} - -#[async_trait::async_trait] -impl DuplexPlane for VoiceSession -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - fn classify(&self, _frame: &[u8]) -> Option { - // OpenAI Realtime events are fire-and-forget notifications, never a reply to a transport-level - // call busbar issued — correlation is done at the IR layer (voice `CallRef`), not here. - None - } - - async fn handle(self: Arc, frame: Vec, out: DuplexHandle) { - let plan = self - .core - .on_server_frame(WireEvent(Bytes::from(frame))) - .await; - for up in plan.upstream { - // THIS leg's policy for a write that fails: DIAGNOSE AND STOP SENDING. The upstream - // socket is a session-long conversation, so a frame that never lands leaves the far end - // holding a state busbar no longer shares — and every later frame of this plan would be - // written into the same dead socket. Abandon the rest of the plan rather than emit - // frames the peer will never see; the downlink and the close below still run, so the - // client is still told. - if let Err(e) = out.emit(up.0.to_vec()).await { - tracing::warn!( - error = %e, - "streaming: an upstream frame could not be written; abandoning the rest of this plan's upstream" - ); - break; - } - } - for down in plan.downlink { - self.core.carrier.send_downlink(down.0.to_vec()); - } - if plan.close { - self.core.carrier.hard_close(); - } - } -} - -/// THE UPLINK-FACING PLANE — bound to the socket busbar holds to the CLIENT (the telephony leg). The -/// neutral pump reads client→server frames off it; each `handle` decodes one, reconciles it against the -/// locked config, and forwards it to the UPSTREAM socket through the shared `upstream` sink (NOT this -/// socket's `out`, which is the downlink toward the client). -pub struct UplinkForwarder { - core: Arc>, - upstream: futures::channel::mpsc::UnboundedSender>, -} - -impl UplinkForwarder { - /// Bind the uplink plane to a session core and the shared upstream sink. - pub fn new( - core: Arc>, - upstream: futures::channel::mpsc::UnboundedSender>, - ) -> Self { - UplinkForwarder { core, upstream } - } -} - -#[async_trait::async_trait] -impl DuplexPlane for UplinkForwarder -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - fn classify(&self, _frame: &[u8]) -> Option { - None - } - - async fn handle(self: Arc, frame: Vec, _out: DuplexHandle) { - let plan = self.core.on_client_frame(WireEvent(Bytes::from(frame))); - for up in plan.upstream { - // Funnel to the single upstream writer shared with the downlink-facing plane. - let _ = self.upstream.unbounded_send(up.0.to_vec()); - } - } -} diff --git a/crates/busbar-voice/src/runtime/tests.rs b/crates/busbar-voice/src/runtime/tests.rs deleted file mode 100644 index caf1d0fe21..0000000000 --- a/crates/busbar-voice/src/runtime/tests.rs +++ /dev/null @@ -1,1017 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! T2 RUNTIME TESTS (behind `runtime`): pump lifecycle, tool-call correlation under interleaving, -//! SessionScope reattach + foreign-owner refusal, and the session's METERING — each closed turn's raw -//! counts per class handed to the kernel's session account (Q21b), and the HARD CLOSE the kernel's -//! budget view answers when a turn dries the caller's chain. The WS/transport is mocked with in-memory -//! `futures` channel pairs; the host is the fixture host, whose budget view a test sets or caps by -//! count (what a count is WORTH is proven over the real engine in the composition root's tests). - -use crate::ir::codec::{OpenAiRealtimeCodec, WireEvent}; -use crate::runtime::carrier::Carrier; -use crate::runtime::metering::{SessionMetering, TurnMeter, TurnVerdict}; -use crate::runtime::scope::SessionHandle; -use crate::runtime::session::{SessionCore, VoiceSession}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_kernel::ingress::byte_duplex::serve_messages; -use busbar_kernel::plane::handle_engine::{DurableHandleEngine, HandleDenied, ScopedMutateError}; -use bytes::Bytes; -use futures::channel::mpsc::{unbounded, UnboundedReceiver}; -use futures::StreamExt; -use std::collections::BTreeMap; -use std::sync::Arc; - -// ── fixtures ────────────────────────────────────────────────────────────────────────────────────── - -/// The presenting key every metered cell opens its session for. -fn caller() -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: "vk-voice".to_string(), - name: "voice".to_string(), - ..Default::default() - } -} - -/// The lane an unmodelled OpenAI Realtime session's counts are ledgered under: the voice plane's key, -/// the separator, and the provider label (the dialect carries its model server-side). -const LANE: &str = "streaming\u{1f}openai_realtime"; - -/// A governed fixture host whose budget view is ONE bucket capped at `cap` counts (`None`: uncapped). -fn governed_host(cap: Option) -> Arc { - let host = FixtureHost::new().governed(); - Arc::new(match cap { - Some(cap) => host.with_count_cap(cap), - None => host, - }) -} - -/// Open the presenting key's kernel account over `host`. -fn metering(host: &Arc) -> SessionMetering { - TurnMeter::new( - Arc::clone(host) as Arc, - caller(), - "streaming-server", - crate::OPENAI_REALTIME, - ) - .open("") - .expect("a chain with room opens") - .expect("a governed host opens an account") -} - -/// A downlink-facing core with a real downlink sink, the echo tool executor, and the session's -/// metering over `host` (`None`: an ungoverned session). Returns the core plus the downlink receiver -/// the client would read. -fn core_on( - host: Option<&Arc>, -) -> ( - Arc>, - UnboundedReceiver>, -) { - let (dtx, drx) = unbounded::>(); - let core = Arc::new(SessionCore::new( - OpenAiRealtimeCodec, - host.map(metering), - Arc::new(crate::runtime::tools::EchoToolExecutor), - Carrier::with_downlink(dtx), - None, - )); - (core, drx) -} - -/// A downlink-facing core metered over a count-capped host (`None`: ungoverned). -fn core_with_downlink( - cap: Option, -) -> ( - Arc>, - UnboundedReceiver>, -) { - match cap { - Some(cap) => core_on(Some(&governed_host(Some(cap)))), - None => core_on(None), - } -} - -fn wire(json: serde_json::Value) -> WireEvent { - WireEvent(Bytes::from(serde_json::to_vec(&json).unwrap())) -} - -fn usage_frame(audio_out: u64) -> WireEvent { - wire(serde_json::json!({ - "type": "response.done", - "response": { "usage": { - "total_tokens": audio_out, - "output_token_details": { "audio_tokens": audio_out }, - }}, - })) -} - -fn audio_delta(b64: &str) -> WireEvent { - wire(serde_json::json!({ "type": "response.output_audio.delta", "delta": b64 })) -} - -/// `ms` milliseconds of PCM16 uplink audio (24 kHz mono, 48 bytes a millisecond) as the client sends it. -fn uplink_audio(ms: usize) -> WireEvent { - let pcm = Bytes::from(vec![0u8; ms * 48]); - wire(serde_json::json!({ - "type": "input_audio_buffer.append", - "audio": busbar_contract::media::base64_encode(&pcm), - })) -} - -/// The upstream opening one tool call. -fn call_opened(call_id: &str) -> WireEvent { - wire(serde_json::json!({"type":"response.output_item.added", - "item":{"type":"function_call","call_id":call_id,"name":"lookup"}})) -} - -/// The rows `host` holds for the caller, as `(class, count)` under [`LANE`]. -fn rows(host: &FixtureHost) -> BTreeMap { - host.ledger_rows(&caller().id) - .into_iter() - .map(|((lane, class), n)| { - assert_eq!(lane, LANE, "every count is ledgered under the voice lane"); - (class, n) - }) - .collect() -} - -fn row_set(pairs: &[(&str, u64)]) -> BTreeMap { - pairs.iter().map(|(c, n)| ((*c).to_string(), *n)).collect() -} - -// ── metering: the plane's counts, the kernel's verdict ────────────────────────────────────────── - -/// Q21b EXIT TEST — a served voice session writes the streaming plane's count rows. One turn: a -/// second of uplink audio, one tool call opened, a usage report naming all five token classes. The -/// ledger holds exactly the plane's SIX billable classes, under the voice lane, at the counts the -/// turn carried — never a price, and never the llm plane's reserved token keys. -/// -/// #71 EXIT TEST (money-model integrity, P2-voicefix): the upstream's `cached_tokens` figure is a -/// SUBSET of `input_token_details.audio_tokens`/`text_tokens`, not a class beside them, so it must -/// NOT produce a ledger row — a streams card that priced it would double-bill the same cached input. -/// This turn reports `cached_tokens: 1` and the ledger holds no `cached_tokens` row for it (before -/// this fix it did, at count 1, RED against this exact assertion). -#[tokio::test] -async fn a_served_turn_ledgers_the_planes_counts_per_class() { - let host = governed_host(None); - let (core, _drx) = core_on(Some(&host)); - let _ = core.on_client_frame(uplink_audio(1_000)); - let _ = core.on_server_frame(call_opened("call_1")).await; - let done = wire(serde_json::json!({ - "type": "response.done", - "response": { "usage": { - "input_token_details": { "audio_tokens": 10, "text_tokens": 3, "cached_tokens": 1 }, - "output_token_details": { "audio_tokens": 20, "text_tokens": 4 }, - }}, - })); - let plan = core.on_server_frame(done).await; - assert!(!plan.close, "an uncapped chain never closes the carrier"); - assert_eq!( - rows(&host), - row_set(&[ - ("audio_seconds_in", 1), - ("audio_tokens_in", 10), - ("audio_tokens_out", 20), - ("text_tokens_in", 3), - ("text_tokens_out", 4), - ("tool_calls", 1), - ]), - "no cached_tokens row: it is a subset of audio_tokens_in/text_tokens_in, not a class beside \ - them (architect ruling #71) — pricing it too would double-bill the same cached input", - ); - assert!( - !rows(&host).contains_key("cached_tokens"), - "cached_tokens must never reach the ledger as a billable class (#71)", - ); - // The turn's counters closed with it: the next report carries only what the next turn did. - let _ = core.on_server_frame(usage_frame(2)).await; - assert_eq!(rows(&host)["audio_tokens_out"], 22); - assert_eq!(rows(&host)["audio_seconds_in"], 1); - assert_eq!(rows(&host)["tool_calls"], 1); -} - -/// A turn that ends on an upstream error, or with the session, was served all the same: the audio -/// the caller spoke and the tool calls the upstream opened reach the ledger either way. -#[tokio::test] -async fn an_errored_or_abandoned_turn_still_ledgers_what_it_served() { - let host = governed_host(None); - let (core, _drx) = core_on(Some(&host)); - let _ = core.on_client_frame(uplink_audio(1_500)); - let _ = core.on_server_frame(call_opened("call_1")).await; - let error = wire(serde_json::json!({ - "type": "error", - "error": { "type": "server_error", "code": "boom", "message": "upstream failed" }, - })); - let _ = core.on_server_frame(error).await; - assert_eq!( - rows(&host), - row_set(&[("audio_seconds_in", 2), ("tool_calls", 1)]), - "an error-ended turn ledgers its own counts (1.5 s rounds up to 2)" - ); - // 600 ms more: the session has spoken 2 100 ms, which converts once to 3 s (MONEY-AUDIT STR-2: - // the session is one unit; rounding each turn up on its own would also read 3 here, but 20 turns - // of 1 050 ms read 40 s that way instead of 21 s). - let _ = core.on_client_frame(uplink_audio(600)); - core.settle_open_turn(); - assert_eq!( - rows(&host)["audio_seconds_in"], - 3, - "the teardown settles the open turn" - ); - core.settle_open_turn(); - assert_eq!(rows(&host)["audio_seconds_in"], 3, "and settles it once"); -} - -/// THE HARD CLOSE (the marquee guarantee), now the kernel's count-based check: the turn that dries -/// the caller's chain is delivered and ledgered, and the verdict cancels the in-flight response and -/// closes the carrier. Replaces `settle_past_cap_hard_closes_the_carrier` (the D2 lease). -#[tokio::test] -async fn a_turn_that_dries_the_chain_hard_closes_the_carrier() { - // A chain of 5 counts; each usage frame ledgers 3. - let (core, mut drx) = core_with_downlink(Some(5)); - - let plan = core.on_server_frame(usage_frame(3)).await; - assert!(!plan.close, "3 of 5: no hard close"); - assert!(!core.carrier().is_closed()); - - let plan = core.on_server_frame(usage_frame(3)).await; - assert!(plan.close, "6 of 5: the frame plan demands a hard close"); - assert!( - plan.upstream - .iter() - .any(|w| String::from_utf8_lossy(&w.0).contains("response.cancel")), - "a dry chain cancels the in-flight response upstream" - ); - assert!(core.carrier().is_closed(), "the carrier hard-closed"); - - let plan = core.on_server_frame(audio_delta("AAAA")).await; - assert!(plan.downlink.is_empty(), "closed carrier processes nothing"); - assert!( - !core.carrier().send_downlink(vec![1, 2, 3]), - "the carrier drops downlink after hard close" - ); - drx.close(); - assert!( - drx.next().await.is_none(), - "no downlink audio leaked to the client" - ); -} - -/// The chain a turn meets is the LIVE one: spend the key takes elsewhere between two turns counts, -/// which the lease's once-read cap never saw. -#[tokio::test] -async fn a_chain_dried_elsewhere_closes_the_session_at_its_next_turn() { - let host = governed_host(None); - let (core, _drx) = core_on(Some(&host)); - assert!(!core.on_server_frame(usage_frame(1)).await.close); - host.set_budget_chain(vec![busbar_contract::hooks::BudgetBucketState { - bucket_id: "group:g@day".to_string(), - budget_group: Some("g".to_string()), - pool: None, - spend_micros_at_current_rate: 10_000, - remaining_micros: Some(0), - window_start: 0, - budget_period: "day".to_string(), - }]); - assert!(core.on_server_frame(usage_frame(1)).await.close); -} - -/// Q21b EXIT TEST (the budget gate) — a key whose chain is already dry is refused at the open; one -/// with room, or with nothing capped, opens; an ungoverned host opens no account at all. -#[test] -fn a_dry_chain_refuses_the_open() { - let open = |host: Arc| { - TurnMeter::new(host, caller(), "streaming-server", crate::OPENAI_REALTIME) - .open("gpt-realtime") - .map(|m| m.is_some()) - }; - assert!( - open(governed_host(Some(0))).is_err(), - "a spent chain refuses" - ); - assert_eq!(open(governed_host(Some(1))), Ok(true), "room opens"); - assert_eq!(open(governed_host(None)), Ok(true), "uncapped opens"); - assert_eq!( - open(Arc::new(FixtureHost::new())), - Ok(false), - "an ungoverned host opens no account" - ); - // A pool-scoped bucket for another pool does not govern this one. - let other_pool = FixtureHost::new().governed().with_budget_chain(vec![ - busbar_contract::hooks::BudgetBucketState { - bucket_id: "group:g@day#other-pool".to_string(), - budget_group: Some("g".to_string()), - pool: Some("other-pool".to_string()), - spend_micros_at_current_rate: 1, - remaining_micros: Some(0), - window_start: 0, - budget_period: "day".to_string(), - }, - ]); - assert_eq!(open(Arc::new(other_pool)), Ok(true)); -} - -/// A turn on an ungoverned session ledgers nothing and is never closed on budget. -#[tokio::test] -async fn an_ungoverned_session_is_never_closed_on_budget() { - let (core, _drx) = core_with_downlink(None); - for _ in 0..3 { - assert!(!core.on_server_frame(usage_frame(u64::MAX / 4)).await.close); - } - let _ = TurnVerdict::Live; -} - -// ── pump lifecycle: frames both ways, close ends cleanly ──────────────────────────────────────── - -#[tokio::test] -async fn pump_relays_downlink_audio_and_close_ends_cleanly() { - let (core, mut drx) = core_with_downlink(None); - let session = Arc::new(VoiceSession::new(Arc::clone(&core))); - - // Feed two server audio frames then EOF (drop the sender). - let (in_tx, in_rx) = unbounded::>(); - let (out_tx, _out_rx) = unbounded::>(); - in_tx - .unbounded_send( - serde_json::to_vec(&serde_json::json!({ - "type": "response.output_audio.delta", "delta": "AAAA" - })) - .unwrap(), - ) - .unwrap(); - in_tx - .unbounded_send( - serde_json::to_vec(&serde_json::json!({ - "type": "response.output_audio.done", "item_id": "it1" - })) - .unwrap(), - ) - .unwrap(); - drop(in_tx); // EOF ends the pump cleanly. - - // serve_messages returns when the stream ends and the drain completes. - serve_messages(in_rx, out_tx, session).await; - - // The client received the two downlink frames, in order. - drx.close(); - let mut kinds = Vec::new(); - while let Some(f) = drx.next().await { - let v: serde_json::Value = serde_json::from_slice(&f).unwrap(); - kinds.push(v["type"].as_str().unwrap().to_string()); - } - assert_eq!( - kinds, - vec![ - "response.output_audio.delta".to_string(), - "response.output_audio.done".to_string() - ] - ); -} - -// ── tool-call correlation under interleaving ──────────────────────────────────────────────────── - -#[tokio::test] -async fn tool_calls_correlate_under_interleaving() { - let (core, _drx) = core_with_downlink(None); - - // Two calls, A (call_id "ca") and B (call_id "cb"), their OPEN/ARGS/CLOSE interleaved. - let frames = [ - wire( - serde_json::json!({"type":"response.output_item.added","item":{"type":"function_call","call_id":"ca","name":"alpha"}}), - ), - wire( - serde_json::json!({"type":"response.output_item.added","item":{"type":"function_call","call_id":"cb","name":"beta"}}), - ), - wire( - serde_json::json!({"type":"response.function_call_arguments.delta","call_id":"ca","delta":"{\"x\":1}"}), - ), - wire( - serde_json::json!({"type":"response.function_call_arguments.delta","call_id":"cb","delta":"{\"y\":2}"}), - ), - wire(serde_json::json!({"type":"response.function_call_arguments.done","call_id":"cb"})), - wire(serde_json::json!({"type":"response.function_call_arguments.done","call_id":"ca"})), - ]; - - let mut upstream = Vec::new(); - for f in frames { - let plan = core.on_server_frame(f).await; - upstream.extend(plan.upstream); - } - - // Each close produced a function_call_output correlating the RIGHT call_id to the RIGHT args, plus - // a response.create to continue. - let texts: Vec = upstream - .iter() - .map(|w| String::from_utf8_lossy(&w.0).to_string()) - .collect(); - let joined = texts.join("\n"); - assert!( - joined.contains("\"call_id\":\"cb\"") - && joined.contains("beta") - && joined.contains("{\\\"y\\\":2}"), - "call B's result correlates its own id + args: {joined}" - ); - assert!( - joined.contains("\"call_id\":\"ca\"") - && joined.contains("alpha") - && joined.contains("{\\\"x\\\":1}"), - "call A's result correlates its own id + args: {joined}" - ); - assert_eq!( - texts - .iter() - .filter(|t| t.contains("function_call_output")) - .count(), - 2, - "exactly two tool results, one per call" - ); - assert_eq!( - texts - .iter() - .filter(|t| t.contains("response.create")) - .count(), - 2, - "each result asks the model to continue" - ); -} - -// ── barge-in: speech_started cancels + truncates at the heard position ─────────────────────────── - -#[tokio::test] -async fn barge_in_cancels_and_truncates_at_heard_ms() { - let (core, _drx) = core_with_downlink(None); - // pcm16 default: 48 bytes/ms. Play 96 bytes of downlink audio = 2 ms heard. - // 96 raw bytes base64-encodes to 128 chars; use a 96-byte payload. - let payload = vec![0u8; 96]; - let b64 = busbar_contract::media::base64_encode(&Bytes::from(payload)); - let _ = core.on_server_frame(audio_delta(&b64)).await; - - let plan = core - .on_server_frame(wire(serde_json::json!({ - "type":"input_audio_buffer.speech_started","audio_start_ms":0,"item_id":"it7" - }))) - .await; - let joined: String = plan - .upstream - .iter() - .map(|w| String::from_utf8_lossy(&w.0).to_string()) - .collect::>() - .join("\n"); - assert!( - joined.contains("response.cancel"), - "barge-in cancels the response" - ); - assert!( - joined.contains("conversation.item.truncate") && joined.contains("\"audio_end_ms\":2"), - "truncate carries the 2 ms actually heard: {joined}" - ); -} - -// ── SessionScope: reattach + foreign-owner refusal ────────────────────────────────────────────── - -#[test] -fn session_scope_reattach_and_foreign_owner_refusal() { - let engine = Arc::new(DurableHandleEngine::new()); - let alice = SessionHandle::bind(Arc::clone(&engine), "alice", "call-1"); - alice.open(1).expect("alice opens her session"); - - // A per-turn checkpoint bumps the durable cursor. - assert_eq!(alice.bump_turn(2).expect("owner bump"), 1); - assert_eq!(alice.bump_turn(3).expect("owner bump"), 2); - - // REATTACH: a fresh binding for the SAME (owner, id) reads the live row through the scoped path. - let alice_again = SessionHandle::bind(Arc::clone(&engine), "alice", "call-1"); - assert_eq!( - alice_again.get().map(|r| r.turns), - Some(2), - "reattach sees the durable turns" - ); - - // FOREIGN OWNER: a session bound to the same id under a different owner is refused identically to a - // missing handle — cannot read, resume, or evict, and cannot even tell it exists. - let mallory = SessionHandle::bind(Arc::clone(&engine), "mallory", "call-1"); - assert!(mallory.get().is_none(), "foreign owner cannot read"); - assert!( - matches!( - mallory.scope_mutate_probe(), - Err(ScopedMutateError::NotYours) - ), - "foreign owner cannot resume/mutate" - ); - assert!(!mallory.close(), "foreign owner evicts nothing"); - - // The rightful owner drives terminal, then closes (owner-gated, terminal-only). - assert!(!alice.close(), "an active handle is not evicted"); - alice.settle_terminal(4).expect("owner settles terminal"); - assert!(alice.close(), "a terminal handle is evicted by its owner"); - assert!( - alice.get().is_none(), - "closed session is gone from the working set" - ); -} - -#[test] -fn session_scope_rtc_call_id_correlation_is_owner_gated_and_durable() { - let engine = Arc::new(DurableHandleEngine::new()); - let alice = SessionHandle::bind(Arc::clone(&engine), "alice", "call-1"); - alice.open(1).expect("alice opens her session"); - assert_eq!( - alice.get().and_then(|r| r.rtc_call_id), - None, - "no correlation key until the SDP broker sets it" - ); - - // The SDP broker stamps the provider's rtc_ preserved from the Location header. - alice - .set_rtc_call_id("rtc_abc123", 2) - .expect("owner stamps the correlation key"); - assert_eq!( - alice.get().and_then(|r| r.rtc_call_id).as_deref(), - Some("rtc_abc123"), - "the correlation key is durable on the row" - ); - - // It survives a subsequent turn checkpoint (the mutate clones the live row). - alice.bump_turn(3).expect("owner bump"); - let row = alice.get().expect("row present"); - assert_eq!(row.turns, 1); - assert_eq!( - row.rtc_call_id.as_deref(), - Some("rtc_abc123"), - "a turn bump preserves the correlation key" - ); - - // A foreign owner cannot stamp a correlation key — the same indistinguishable refusal. - let mallory = SessionHandle::bind(Arc::clone(&engine), "mallory", "call-1"); - assert!( - matches!( - mallory.set_rtc_call_id("rtc_evil", 4), - Err(ScopedMutateError::NotYours) - ), - "a foreign owner cannot forge the media correlation" - ); -} - -// A tiny probe used only by the foreign-owner test — proves the mutate path refuses NotYours without -// the test needing to reconstruct a full mutation. -impl SessionHandle { - fn scope_mutate_probe(&self) -> Result { - self.bump_turn(9) - } -} - -#[test] -fn read_denied_maps_to_notyours() { - // Belt-and-braces: the raw scoped read for a foreign owner is exactly HandleDenied::NotYours. - let engine = Arc::new(DurableHandleEngine::new()); - let alice = SessionHandle::bind(Arc::clone(&engine), "alice", "s"); - alice.open(1).unwrap(); - let raw: Result<_, HandleDenied> = SessionScopeRawProbe::probe(&engine); - assert!(matches!(raw, Err(HandleDenied::NotYours))); -} - -// Helper that reaches the raw scoped read to assert the exact read-denial variant. -struct SessionScopeRawProbe; -impl SessionScopeRawProbe { - fn probe( - engine: &Arc, - ) -> Result, HandleDenied> { - busbar_kernel::plane_host::SessionScope::new(Arc::clone(engine), "mallory", "s").get() - } -} - -// ── the governed wait: a client-served tool call ──────────────────────────────────────────────── -// -// The other half of the tool moat. A call for a tool this node does NOT serve cannot be answered -// in-process — the answer is the client's — so the runtime opens no execution for it and routes the -// client's reply to the node's own table instead. Three facts, and each of them failed before this: -// the reply woke the wait, a reply naming a call nobody is waiting on is refused rather than carried -// upstream, and a call nobody answered is swept by the tick. - -/// A tool executor that serves exactly one tool and leaves the rest to the client. -#[derive(Debug)] -struct ServesOnly(&'static str); - -#[async_trait::async_trait] -impl crate::runtime::ToolExecutor for ServesOnly { - fn executes_here(&self, name: &str) -> bool { - name == self.0 - } - async fn execute(&self, name: &str, _arguments: &[u8]) -> Vec { - format!(r#"{{"served":"{name}"}}"#).into_bytes() - } -} - -/// The node's table, as the runtime is allowed to see it: which identifiers are open (entered as the -/// runtime plans them), and a sweep. -#[derive(Debug, Default)] -struct TableFake { - sessions: std::sync::Mutex>>, -} - -impl crate::runtime::GovernedCalls for TableFake { - fn planned(&self, session: u64, call_id: &str, _now_ms: u64) -> bool { - let mut s = self.sessions.lock().unwrap(); - s.entry(session).or_default().push(call_id.to_string()); - true - } - - fn replied(&self, session: u64, call_id: &str) -> Result<(), crate::runtime::ReplyRefusal> { - let mut s = self.sessions.lock().unwrap(); - let open = s - .get_mut(&session) - .ok_or(crate::runtime::ReplyRefusal::NoSuchSession)?; - let i = open - .iter() - .position(|c| c == call_id) - .ok_or(crate::runtime::ReplyRefusal::UnknownCall)?; - open.remove(i); - Ok(()) - } - - fn expired(&self, _now_ms: u64) -> usize { - let mut s = self.sessions.lock().unwrap(); - s.values_mut().map(|open| open.drain(..).count()).sum() - } -} - -/// A core bound to `table` as session 7, serving only the tool `local`. -fn governed_core(table: Arc) -> Arc> { - let (dtx, _drx) = unbounded::>(); - Arc::new( - SessionCore::new( - OpenAiRealtimeCodec, - None, - Arc::new(ServesOnly("local")), - Carrier::with_downlink(dtx), - None, - ) - .with_governed(crate::runtime::GovernedSession { - session: 7, - calls: table as Arc, - }), - ) -} - -/// The three server→client frames that announce, stream and close one call for `tool`. -fn call_frames(call_id: &str, tool: &str) -> [WireEvent; 3] { - [ - wire(serde_json::json!({"type":"response.output_item.added", - "item":{"type":"function_call","call_id":call_id,"name":tool}})), - wire( - serde_json::json!({"type":"response.function_call_arguments.delta", - "call_id":call_id,"delta":"{}"}), - ), - wire(serde_json::json!({"type":"response.function_call_arguments.done","call_id":call_id})), - ] -} - -/// The client's own `function_call_output` for `call_id`. -fn client_reply(call_id: &str) -> WireEvent { - wire(serde_json::json!({"type":"conversation.item.create", - "item":{"type":"function_call_output","call_id":call_id,"output":"42"}})) -} - -#[tokio::test] -async fn a_client_served_call_waits_and_its_reply_wakes_the_unit() { - let table = Arc::new(TableFake::default()); - let core = governed_core(Arc::clone(&table)); - - // The node serves `local` and does not serve `remote`. Both calls close in the same turn. - let mut upstream = Vec::new(); - for f in call_frames("cl", "local") - .into_iter() - .chain(call_frames("cr", "remote")) - { - upstream.extend(core.on_server_frame(f).await.upstream); - } - let joined: String = upstream - .iter() - .map(|w| String::from_utf8_lossy(&w.0).to_string()) - .collect::>() - .join("\n"); - assert!( - joined.contains("\"call_id\":\"cl\""), - "the tool the node serves is still executed in-process: {joined}" - ); - assert!( - !joined.contains("\"call_id\":\"cr\""), - "the node never authors an answer for a call it does not serve: {joined}" - ); - - // The root entered the wait where it planned the leg. The runtime is what tells it the answer - // arrived — and the reply goes on upstream only because the wait was woken. - table - .sessions - .lock() - .unwrap() - .insert(7, vec!["cr".to_string()]); - let plan = core.on_client_frame(client_reply("cr")); - assert!( - !plan.refused_reply, - "the wait was open, so nothing is refused" - ); - let up: String = plan - .upstream - .iter() - .map(|w| String::from_utf8_lossy(&w.0).to_string()) - .collect::>() - .join("\n"); - assert!( - up.contains("function_call_output") && up.contains("response.create"), - "the woken reply reaches the model and asks it to continue: {up}" - ); - assert!( - table.sessions.lock().unwrap()[&7].is_empty(), - "the table no longer holds the call" - ); -} - -#[tokio::test] -async fn a_reply_naming_no_open_call_is_refused_rather_than_carried_upstream() { - let table = Arc::new(TableFake::default()); - let core = governed_core(Arc::clone(&table)); - for f in call_frames("cr", "remote") { - core.on_server_frame(f).await; - } - // The session's table holds `cr`; the client answers `cz`. - table - .sessions - .lock() - .unwrap() - .insert(7, vec!["cr".to_string()]); - let plan = core.on_client_frame(client_reply("cz")); - assert!(plan.refused_reply, "a reply matching nothing is refused"); - assert!( - plan.upstream.is_empty(), - "and a refused reply reaches the model on no wire at all" - ); - assert_eq!( - table.sessions.lock().unwrap()[&7], - vec!["cr".to_string()], - "the call it did not answer is still waiting" - ); -} - -#[tokio::test] -async fn an_unanswered_client_served_call_is_swept_by_the_tick() { - let table = Arc::new(TableFake::default()); - let core = governed_core(Arc::clone(&table)); - for f in call_frames("cr", "remote") { - core.on_server_frame(f).await; - } - table - .sessions - .lock() - .unwrap() - .insert(7, vec!["cr".to_string()]); - - // The tick is what runs beside the pump; without it the wait is never ended and the call's hold - // is held open by a client that simply never replied. - assert_eq!( - core.sweep_expired(1_000), - 1, - "the tick swept the one open call" - ); - // And a reply that turns up after the sweep is refused, not paid out against a settled call. - let plan = core.on_client_frame(client_reply("cr")); - assert!(plan.refused_reply, "a late reply answers nothing"); - assert!(plan.upstream.is_empty()); -} - -/// **The tick actually runs, and it ends with the pump.** -/// -/// `sweep_expired` is the sweep; this is whether anything on a served session ever calls it. A wait -/// nobody sweeps is a hold nobody settles, and a client that never replies sends no frame on which -/// anyone would notice — so time passing has to be the event, and it has to stop being one when the -/// conversation does. -/// Real time rather than a paused clock: pausing needs tokio's `test-util`, and shipping a test -/// feature into the runtime build to make one assertion cheaper is a worse trade than a slow test. -#[tokio::test] -async fn the_sweep_rides_the_pump_and_ends_with_it() { - let table = Arc::new(TableFake::default()); - let core = governed_core(Arc::clone(&table)); - table - .sessions - .lock() - .unwrap() - .insert(7, vec!["cr".to_string()]); - - // A pump that runs past one tick and then returns, exactly as a socket closing would. - // - // THE MARGIN IS THE POINT. `SWEEP_EVERY` is one second and this is real time, not a paused - // clock, so the pump has to outlive the tick by enough that a loaded CI runner's scheduler - // jitter cannot land the tick after the pump returns — 400 ms of headroom was not enough, and - // the failure would have read as "the sweep does not run" rather than "the box was busy". - let pump = tokio::time::sleep(std::time::Duration::from_millis(2_500)); - crate::runtime::serve_with_sweep(Arc::clone(&core), pump).await; - - assert!( - table.sessions.lock().unwrap()[&7].is_empty(), - "the tick beside the pump swept the call nobody answered" - ); - - // And once the pump is gone so is the tick: a fresh call opened afterwards is swept by nobody. - table - .sessions - .lock() - .unwrap() - .insert(7, vec!["cs".to_string()]); - tokio::time::sleep(std::time::Duration::from_millis(1_400)).await; - assert_eq!( - table.sessions.lock().unwrap()[&7], - vec!["cs".to_string()], - "a sweep that outlived its session would be the background loop this plane does not have" - ); -} - -/// A HARD CLOSE THAT LANDS WHILE THE SUPERVISOR IS ARRIVING AT THE GATE STILL WAKES IT. -/// -/// The topology's teardown `select!` parks one arm on `Carrier::closed()`, and that await is the only -/// thing that turns a dry budget into a torn-down session. The gate's wake reaches the waiters that -/// are registered when it fires and stores nothing for one that registers a moment later — so a close -/// racing the supervisor's arrival is the one ordering in which the marquee guarantee can be dropped -/// on the floor, and the session runs on to socket EOF instead. -/// -/// Driven as a race rather than a sequence, because the window is between two instructions and no -/// sequential ordering can enter it: each round starts a fresh carrier, hands it to a thread that -/// closes it immediately, and demands the await finish. A run that parks forever is the defect. -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn a_close_racing_the_supervisors_arrival_still_wakes_it() { - for round in 0..2_000 { - let carrier = Carrier::sideband(); - let closer = carrier.clone(); - std::thread::spawn(move || { - closer.hard_close(); - }); - tokio::time::timeout(std::time::Duration::from_secs(5), carrier.closed()) - .await - .unwrap_or_else(|_| { - panic!( - "round {round}: the carrier hard-closed and the supervisor never woke — a \ - session whose budget is dry that nothing tears down" - ) - }); - } -} - -// ── Item 137: a voice session's durable row is settled, never leaked ACTIVE ───────────────────── - -/// The retention sweep ABANDONS an idle ACTIVE session terminal, and a later sweep evicts it. -/// -/// The session's own `open` opted OUT of the sweep (`|_, _, _, _| None`), and the cap rule evicts -/// only terminal rows, so a session whose teardown never ran stayed ACTIVE in the working set for -/// ever. Any later session's open is what claims the sweep. -#[test] -fn an_idle_voice_session_is_abandoned_terminal_then_evicted() { - let engine = Arc::new(DurableHandleEngine::new()); - let idle = SessionHandle::bind(Arc::clone(&engine), "alice", "call-idle"); - idle.open(1).expect("the idle session opens"); - - // An hour and a bit later another session opens, which claims the retention sweep. - let later = SessionHandle::bind(Arc::clone(&engine), "bob", "call-later"); - later.open(1 + 3_600 + 2).expect("a later session opens"); - assert_eq!( - idle.get().map(|row| row.terminal), - Some(true), - "a session idle past the abandon bound is settled terminal by the sweep" - ); - - // And a terminal-TTL later still, a third open evicts it. - let third = SessionHandle::bind(Arc::clone(&engine), "carol", "call-third"); - third - .open(1 + 3_600 + 2 + 3_600 + 2) - .expect("a third session opens"); - assert!( - idle.get().is_none(), - "the abandoned session leaves the working set" - ); -} - -/// A served session's pump returning settles its durable row terminal and evicts it. -#[tokio::test] -async fn serving_a_session_to_its_teardown_settles_and_evicts_its_row() { - let rt = VoiceRuntimeFixture::runtime(); - let (core, handle) = crate::topology::begin_session( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-served", - None, - Carrier::sideband(), - None, - 1, - ) - .expect("the session opens"); - crate::runtime::serve_to_teardown(core, handle, async {}, || 2).await; - assert!( - SessionHandle::bind(Arc::clone(&rt.engine), "acct-1", "call-served") - .get() - .is_none(), - "the served session's row is settled and evicted when its pump returns" - ); -} - -/// OWNER RULING Q21a — NO CONFIG, NO LIMIT. A runtime whose operator configured no -/// `streams.session_max_secs` binds no ceiling, so a session that has run far past the hour the -/// retired default cut at is still open. (The test clock is the `now` handed to the comparison.) -#[test] -fn an_unconfigured_session_is_never_cut_by_a_ceiling() { - let rt = VoiceRuntimeFixture::runtime(); - assert_eq!( - rt.session_max_secs, None, - "nothing configured, nothing bound" - ); - let (core, _handle) = crate::topology::begin_session( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-unbounded", - None, - Carrier::sideband(), - None, - 1, - ) - .expect("the session opens"); - let much_later = std::time::Instant::now() + std::time::Duration::from_secs(3_600 * 5); - assert!( - !core.enforce_ceiling(much_later), - "five hours in, an unconfigured session is not closed" - ); - assert!(!core.carrier().is_closed()); -} - -/// Q21a — A CONFIGURED ceiling closes the session at the limit, with the dialect's own error frame -/// under the named reason, and the turn it cut is settled with the session. -/// -/// The ceiling was once declared, defaulted, parsed and plumbed onto the runtime and then compared -/// with nothing; it is compared, and only when configured. -#[tokio::test] -async fn a_configured_ceiling_closes_the_session_and_settles_its_counts() { - let mut rt = VoiceRuntimeFixture::runtime(); - rt.session_max_secs = std::num::NonZeroU32::new(1); - let host = governed_host(None); - let (dtx, mut drx) = unbounded::>(); - let (core, handle) = crate::topology::begin_session( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-long", - None, - Carrier::with_downlink(dtx), - Some(TurnMeter::new( - Arc::clone(&host) as Arc, - caller(), - "streaming-server", - crate::OPENAI_REALTIME, - )), - 1, - ) - .expect("the session opens"); - let _ = core.on_client_frame(uplink_audio(2_000)); - // A pump that never ends on its own: only the ceiling can end this session. - let served = tokio::time::timeout( - std::time::Duration::from_secs(5), - crate::runtime::serve_to_teardown( - Arc::clone(&core), - handle, - futures::future::pending::<()>(), - || 2, - ), - ) - .await; - assert!( - served.is_ok(), - "a one-second ceiling ends the session well inside five seconds" - ); - assert!(core.carrier().is_closed(), "the ceiling closes the carrier"); - drx.close(); - let told: Vec = drx - .collect::>() - .await - .iter() - .map(|f| serde_json::from_slice(f).expect("a json frame")) - .collect(); - assert!( - told.iter().any(|v| v["type"] == "error" - && v["error"]["code"] == crate::runtime::session::SESSION_CEILING_REASON), - "the client is told why, in its dialect's own error frame: {told:?}" - ); - assert_eq!( - rows(&host), - row_set(&[("audio_seconds_in", 2)]), - "the turn the ceiling cut is settled with the session" - ); -} - -/// The runtime these three cells open sessions on. -struct VoiceRuntimeFixture; -impl VoiceRuntimeFixture { - fn runtime() -> crate::runtime::VoiceRuntime { - crate::runtime::VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(crate::runtime::tools::EchoToolExecutor), - ) - } -} diff --git a/crates/busbar-voice/src/runtime/voice_plane_unit_oracle.rs b/crates/busbar-voice/src/runtime/voice_plane_unit_oracle.rs deleted file mode 100644 index 4e7e7558a0..0000000000 --- a/crates/busbar-voice/src/runtime/voice_plane_unit_oracle.rs +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE VOICE PLANE-UNIT BILLING ORACLE — the regression backstop for the voice money path, replacing -//! the D2 lease oracle (`voice_d2_lease_billing_oracle`, deleted with the lease under OWNER RULING -//! Q21b; snapshot under `~/Downloads/busbar-1.6.0-snapshots/P2-voice/`). -//! -//! It drives the SAME fixed session script the lease oracle pinned — a refused open, then four turns -//! against a ceiling of 15 — through the path that now ships: each turn's raw counts per streaming- -//! plane class to the kernel's session account, which ledgers them and answers the verdict off the -//! caller's budget view. Every count ledgered and every verdict is pinned. The fixture host's view is -//! a count cap (each count worth one unit of the ceiling), the plane-unit twin of the lease oracle's -//! one-nano-per-unit mock, so the arithmetic stays legible; what a count is worth under a real -//! `streams` card is pinned over the engine in the composition root's tests. -//! -//! What moved, stated once: the lease folded the five token classes onto the llm plane's reserved -//! keys (so a turn's text and audio were one `output` figure) and stored a nanodollar sum; the plane -//! ledgers each class as itself, plus the two counts the lease never billed (`audio_seconds_in`, -//! `tool_calls`), and stores no figure. - -use crate::ir::usage::IrDuplexUsage; -use crate::runtime::metering::{TurnMeter, TurnVerdict}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_plane_streaming::session::TurnCounters; -use std::collections::BTreeMap; -use std::sync::Arc; - -const LANE: &str = "streaming\u{1f}gpt-realtime"; - -fn key() -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: "vk-oracle".to_string(), - ..Default::default() - } -} - -fn meter(host: &Arc) -> TurnMeter { - TurnMeter::new( - Arc::clone(host) as Arc, - key(), - "streaming-server", - crate::OPENAI_REALTIME, - ) -} - -fn rows(host: &FixtureHost) -> BTreeMap { - host.ledger_rows(&key().id) - .into_iter() - .map(|((lane, class), n)| { - assert_eq!(lane, LANE); - (class, n) - }) - .collect() -} - -fn pinned(pairs: &[(&str, u64)]) -> BTreeMap { - pairs.iter().map(|(c, n)| ((*c).to_string(), *n)).collect() -} - -/// THE ORACLE — one session's whole metering life, every count and verdict pinned. -#[test] -fn voice_plane_unit_billing_oracle() { - // ── LEG 0 — A DRY CHAIN IS REFUSED AT THE OPEN, AND NOTHING IS LEDGERED ───────────────────────── - let dry = Arc::new(FixtureHost::new().governed().with_count_cap(0)); - assert!( - meter(&dry).open("gpt-realtime").is_err(), - "a chain with nothing left refuses the open" - ); - assert!(rows(&dry).is_empty(), "a refused session ledgers nothing"); - - // ── THE SESSION — a ceiling of 15 ─────────────────────────────────────────────────────────────── - let host = Arc::new(FixtureHost::new().governed().with_count_cap(15)); - let session = meter(&host) - .open("gpt-realtime") - .expect("room opens") - .expect("governed"); - let no_counters = TurnCounters::default(); - - // ── LEG 1 — turn 1: audio_out 3, text_out 4 → 7 of 15, Live ───────────────────────────────────── - let v1 = session.report_turn( - Some(&IrDuplexUsage { - audio_out: 3, - text_out: 4, - ..IrDuplexUsage::default() - }), - no_counters, - ); - assert_eq!(v1, TurnVerdict::Live); - assert_eq!( - rows(&host), - pinned(&[("audio_tokens_out", 3), ("text_tokens_out", 4)]), - "each class ledgered as itself — not one folded `output` of 7" - ); - - // ── LEG 2 — turn 2: audio_out 2, text_in 3, plus a second of uplink audio and a tool call ─────── - let mut counters = TurnCounters::default(); - counters.admit_audio(crate::ir::media::AudioFormat::Pcm16, 48_000); - counters.open_tool_call(); - let v2 = session.report_turn( - Some(&IrDuplexUsage { - audio_out: 2, - text_in: 3, - ..IrDuplexUsage::default() - }), - counters, - ); - assert_eq!(v2, TurnVerdict::Live, "7 + 5 + 1 s + 1 call = 14 of 15"); - assert_eq!( - rows(&host), - pinned(&[ - ("audio_seconds_in", 1), - ("audio_tokens_out", 5), - ("text_tokens_in", 3), - ("text_tokens_out", 4), - ("tool_calls", 1), - ]), - "the two counts the lease never billed are ledgered beside the tokens" - ); - - // ── LEG 3 — turn 3: audio_out 5 → 19 of 15, MustClose (delivered, ledgered, then closed) ──────── - let v3 = session.report_turn( - Some(&IrDuplexUsage { - audio_out: 5, - ..IrDuplexUsage::default() - }), - no_counters, - ); - assert_eq!(v3, TurnVerdict::MustClose); - assert_eq!(rows(&host)["audio_tokens_out"], 10); - - // ── LEG 4 — once dry, stays dry: a late turn is still ledgered exactly and still closes ───────── - let late = session.report_turn( - Some(&IrDuplexUsage { - audio_out: 1, - ..IrDuplexUsage::default() - }), - no_counters, - ); - assert_eq!(late, TurnVerdict::MustClose); - assert_eq!(rows(&host)["audio_tokens_out"], 11); - assert_eq!( - host.ledger_usage(&key().id).map(|u| u.tokens), - Some(20), - "every count ledgered once: 7 + 7 + 5 + 1" - ); - - // ── LEG 5 — an empty turn ledgers nothing ─────────────────────────────────────────────────────── - let _ = session.report_turn(None, no_counters); - assert_eq!(host.ledger_usage(&key().id).map(|u| u.tokens), Some(20)); -} diff --git a/crates/busbar-voice/src/testkit/echo.rs b/crates/busbar-voice/src/testkit/echo.rs deleted file mode 100644 index 05677afca2..0000000000 --- a/crates/busbar-voice/src/testkit/echo.rs +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! A TEST EXECUTOR that serves every tool by echoing the call back as a JSON object: enough to prove -//! correlation (the right `name` and `arguments` reached the right call) without a tool registry. -//! Test builds only; production relays every call to the caller (`ClientRelay`). - -use crate::runtime::tools::ToolExecutor; -use async_trait::async_trait; - -/// Echoes the call: `{"tool":,"echo":}`. -#[derive(Debug, Default, Clone, Copy)] -pub struct EchoToolExecutor; - -#[async_trait] -impl ToolExecutor for EchoToolExecutor { - async fn execute(&self, name: &str, arguments: &[u8]) -> Vec { - let args = String::from_utf8_lossy(arguments); - format!(r#"{{"tool":"{name}","echo":{args}}}"#).into_bytes() - } -} diff --git a/crates/busbar-voice/src/testkit/fixture_host.rs b/crates/busbar-voice/src/testkit/fixture_host.rs deleted file mode 100644 index f94f2f4e8d..0000000000 --- a/crates/busbar-voice/src/testkit/fixture_host.rs +++ /dev/null @@ -1,848 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE NEUTRAL FIXTURE HOST — an in-memory [`EngineHost`] a plane crate's tests drive INSTEAD of -//! minting a host over the engine's `App`. A plane is a plugin on the plane ABI, and its tests must -//! not reach into core any more than its production code does; before this fixture a plane test that -//! needed a host had to build `busbar_kernel::test_support::TestApp` and call -//! `busbar_kernel::plane_host::engine_host(&app)` — the exact backwards reach the purity lint forbids. -//! -//! This host models, in memory, just the host-side state a plane's production path drives through the -//! seam and a test then reads back: -//! -//! * the `(pool, lane)` BREAKER cells (`breaker_admit` / `breaker_record_*` / `breaker_retry_after_secs`), -//! readable through [`FixtureHost::breaker_state`]; -//! * the OPERATOR HOOK gate / rewrite chains keyed by `(plane_key, container)`, attached as scripted -//! verdicts ([`FixtureHost::attach_gate`] / [`FixtureHost::attach_rewrite`]) so the `gate_attached` / -//! `gate_decide` / `tap_attached` / `transform_over` legs run exactly as they do over a configured -//! deployment; -//! * the per-key usage LEDGER the metering seams land on (`meter_ledger` / `meter_series`), readable -//! through [`FixtureHost::ledger_usage`], per `(lane, class)` through [`FixtureHost::ledger_rows`], -//! and per series row through [`FixtureHost::series_rows`] once the host is -//! [`governed`](FixtureHost::governed); -//! * the key's BUDGET VIEW (`budget_state`): a chain a test sets outright -//! ([`FixtureHost::with_budget_chain`] / [`FixtureHost::set_budget_chain`]), or a count cap -//! ([`FixtureHost::with_count_cap`]) whose remaining is the cap less every count ledgered — so the -//! kernel's session account, which reads this view after each turn, can be driven dry. -//! -//! The fixture implements ONLY the plane-facing seam: it names no cost or price type (#43 — a plane is -//! pricing-blind, and so is the host its tests stand in). What a count is WORTH is the kernel's -//! read-time view, proven over the real engine in the composition root's tests. -//! -//! Everything else on the seam answers the neutral "nothing configured" value (no pools, no secrets, -//! no identity chain, no completion pipeline). It is a test double: a leg the fixture does not model -//! answers its documented empty value rather than pretending to be the engine. - -use busbar_contract::abi::hot::{AdmissionId, Signal}; -use busbar_contract::auth::{AuthPrincipal, IdentityRefusal}; -use busbar_contract::records::{PlaneRequestCtx, VirtualKey}; -use busbar_kernel::billing::{TokenUsage, Usage}; -use busbar_kernel::breaker::{CanonicalSignal, Disposition}; -use busbar_kernel::hooks::{RequestedSignals, ResolvedPolicy, TapEntry}; -use busbar_kernel::plane::approvals::Sealer; -use busbar_kernel::plane::calllog::CallInput; -use busbar_kernel::plane_host::{ - AdmissionHost, AdmitHandle, AudienceBinding, BreakerHost, BudgetHost, ClockHost, - CompletionHost, CompletionRefusal, DispatchScope, EngineHost, GateOutcome, GovAdmit, GovHandle, - HookConfigHost, HostCompletion, IdentityHost, JournalHost, LanePoolHost, MeterPin, MountHost, - RegistryHost, TelemetryHost, TransformVerdict, -}; -use busbar_kernel::store::{BreakerState, HealthState, LaneRuntime, Unavailable}; -use busbar_kernel::trust::validate::{Lapsed, Standing}; -use busbar_kernel::trust::TrustState; -use std::collections::BTreeMap; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{Arc, Mutex}; - -/// A scripted gate verdict for one `(plane_key, container)`: handed the serialized request payload the -/// plane projected for the hook, answers the [`GateOutcome`] the real hook chain would. -pub type GateScript = Arc GateOutcome + Send + Sync>; - -/// A scripted rewrite verdict for one `(plane_key, container)`: handed the serialized payload, answers -/// the [`TransformVerdict`] the real `prompt: rw` chain would (a committed rewrite, an abstain, or a -/// reject). -pub type RewriteScript = Arc TransformVerdict + Send + Sync>; - -/// The usage a key has ledgered through the fixture's metering seams — the read-back twin of the -/// engine's `usage_for(key)`: `tokens` is every unit `meter_ledger` accrued, `requests` the billable -/// request count the ADMIT step's per-request fee increments (never the Meter step). -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct LedgerUsage { - pub tokens: u64, - pub requests: u64, - /// Sessions the kernel counted as opened, on the plane's own fee lane (#47 `fees.per_session`) — - /// kept apart from the turn counts above and from [`FixtureHost::ledger_rows`]. - pub sessions: u64, -} - -/// One `(pool, lane)` breaker cell of the fixture. -#[derive(Clone, Copy, Debug)] -struct Cell { - state: BreakerState, -} - -/// The seconds an OPEN cell stays open after a definitive (hard-down) record — the engine's first -/// cooldown step, so a `Retry-After` read off the fixture is a plausible whole-second floor. -const OPEN_COOLDOWN_SECS: u64 = 15; - -/// One admin-audit row [`JournalHost::audit_record`] landed on the fixture — the read-back a plane's -/// exit-path/mutation test asserts against (action literal, resource, outcome, principal), the fixture -/// twin of the engine's in-process admin audit ring. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FixtureAuditEntry { - pub action: String, - pub resource: String, - pub outcome: String, - pub principal: String, -} - -#[derive(Default)] -struct Inner { - cells: BTreeMap<(String, usize), Cell>, - gates: BTreeMap<(String, String), GateScript>, - rewrites: BTreeMap<(String, String), RewriteScript>, - ledger: BTreeMap, - slots: BTreeMap>, - audit: Vec, - /// The budget chain `budget_state` answers for every key — empty (uncapped) unless a test sets one. - budget: Vec, - /// A count cap `budget_state` answers as one bucket, remaining the cap less the key's counts. - count_cap: Option, - /// Every count `meter_ledger` landed, per key, per `(lane, class)`. - rows: BTreeMap>, - /// Every session count `meter_ledger` landed on a plane's fee lane, per key: the METERING ROW's - /// session count, which a fee refund never touches. - session_rows: BTreeMap, - /// Every series row `meter_series` wrote, per key, as `(model, provider)`, in order. - series: BTreeMap>, - /// Every request a plane reported through `request_finished`, in order. - finished: Vec, -} - -/// One request a plane reported through [`TelemetryHost::request_finished`] — the read-back twin of the -/// engine's plane-labelled request counter and duration sample. -#[derive(Clone, Debug, PartialEq)] -pub struct FinishedRequest { - pub plane: String, - pub ingress_protocol: String, - pub pool: String, - pub outcome: &'static str, - pub seconds: f64, -} - -/// The in-memory engine host a plane's tests drive through the neutral seam. Build one with -/// [`FixtureHost::new`], configure it with the builder methods, then hand it to the plane as an -/// `Arc` (an `Arc` coerces) while keeping your own `Arc` to read the -/// state back. -pub struct FixtureHost { - inner: Mutex, - governed: bool, - next_request_id: AtomicU64, - /// The lane store `lane_store` hands out: the kernel's OWN `LaneRuntime` implementor with no - /// lanes configured, so this double never re-implements (and never drifts from) that trait. - lanes: HealthState, - /// No hook on the fixture requests a candidate signal (the all-zero mask). - signals: RequestedSignals, -} - -impl Default for FixtureHost { - fn default() -> Self { - Self::new() - } -} - -impl FixtureHost { - /// A bare host: no governance, no hooks attached, every breaker cell Closed. - #[must_use] - pub fn new() -> Self { - FixtureHost { - inner: Mutex::new(Inner::default()), - governed: false, - next_request_id: AtomicU64::new(1), - lanes: HealthState::new(Vec::new()), - signals: RequestedSignals::default(), - } - } - - /// Answer `chain` as every key's budget chain. - #[must_use] - pub fn with_budget_chain(self, chain: Vec) -> Self { - self.set_budget_chain(chain); - self - } - - /// Replace the budget chain every key reads, mid-test — the view a live session's next turn meets. - pub fn set_budget_chain(&self, chain: Vec) { - self.lock().budget = chain; - } - - /// Answer every key's budget view as ONE bucket capped at `cap` counts: its remaining is the cap - /// less every count the key has ledgered. A stand-in for a card that makes each count worth one - /// unit, without this host naming a card. - #[must_use] - pub fn with_count_cap(self, cap: i64) -> Self { - self.lock().count_cap = Some(cap); - self - } - - /// Turn governance ON: `governance()` mints a handle and the metering seams land on the fixture's - /// per-key ledger (read back with [`Self::ledger_usage`]). Off, `governance()` is `None` and the - /// metering seams are never reached — exactly an ungoverned deployment. - #[must_use] - pub fn governed(mut self) -> Self { - self.governed = true; - self - } - - /// Attach a scripted request-admission GATE to `container` on plane `plane_key`, so - /// `gate_attached` answers true and `gate_decide` runs `script` over the projected payload. - #[must_use] - pub fn attach_gate(self, plane_key: &str, container: &str, script: GateScript) -> Self { - self.lock() - .gates - .insert((plane_key.to_string(), container.to_string()), script); - self - } - - /// Attach a scripted `prompt: rw` REWRITE to `container` on plane `plane_key`, so `tap_attached` - /// answers true and `transform_over` runs `script` over the projected payload. - #[must_use] - pub fn attach_rewrite(self, plane_key: &str, container: &str, script: RewriteScript) -> Self { - self.lock() - .rewrites - .insert((plane_key.to_string(), container.to_string()), script); - self - } - - /// Finish the builder as the `Arc` a plane's production path takes. - #[must_use] - pub fn into_host(self) -> Arc { - Arc::new(self) - } - - /// The `(pool, lane)` breaker cell's current state — Closed until something is recorded. - #[must_use] - pub fn breaker_state(&self, pool: &str, lane: usize) -> BreakerState { - self.lock() - .cells - .get(&(pool.to_string(), lane)) - .map_or(BreakerState::Closed, |c| c.state) - } - - /// What `key_id` has ledgered through the metering seams, or `None` if nothing ever landed. - #[must_use] - pub fn ledger_usage(&self, key_id: &str) -> Option { - self.lock().ledger.get(key_id).copied() - } - - /// Every session count the kernel's account landed for `key_id` on the plane's fee lane: the - /// METERING ROW's session count. A fee refund gives a count back on the budget book - /// ([`LedgerUsage::sessions`]) and leaves this row in place, as v1.5.5's fee refund left its row - /// (TODO 17(b)): a failed open reads one here and none there. - #[must_use] - pub fn session_rows(&self, key_id: &str) -> u64 { - self.lock().session_rows.get(key_id).copied().unwrap_or(0) - } - - /// Every request reported through `request_finished`, in order. - #[must_use] - pub fn finished_requests(&self) -> Vec { - self.lock().finished.clone() - } - - /// Every count `key_id` has ledgered, per `(lane, class)` — the rows a plane's metering wrote. - #[must_use] - pub fn ledger_rows(&self, key_id: &str) -> BTreeMap<(String, String), u64> { - self.lock().rows.get(key_id).cloned().unwrap_or_default() - } - - /// Every series row `key_id`'s metering wrote through `meter_series`, as `(model, provider)`, in - /// order — the rows whose `provider` column decides which plane `GET /admin/usage` prices them as. - #[must_use] - pub fn series_rows(&self, key_id: &str) -> Vec<(String, String)> { - self.lock().series.get(key_id).cloned().unwrap_or_default() - } - - /// Every admin-audit row [`JournalHost::audit_record`] has landed on this host, in emission order — - /// the read-back a plane's audit/exit-path test asserts an exact count and shape against. - #[must_use] - pub fn audit_log(&self) -> Vec { - self.lock().audit.clone() - } - - fn lock(&self) -> std::sync::MutexGuard<'_, Inner> { - self.inner.lock().unwrap_or_else(|e| e.into_inner()) - } - - /// The ADMIT step's per-request FEE, landed on the presenting key's ledger row — the write side of - /// [`LedgerUsage::requests`]. Governance off, or a request that carries no resolved key, is charged - /// nothing (the ungoverned posture: there is no key to bill). Called once per admission decision, - /// so a plane that runs the door twice for one request reads back two fees, exactly as a - /// double-charging deployment would. - fn charge_request_fee(&self, gov: &PlaneRequestCtx) { - if !self.governed { - return; - } - let Some(key) = gov.key() else { return }; - let mut inner = self.lock(); - let entry = inner.ledger.entry(key.id.clone()).or_default(); - entry.requests = entry.requests.saturating_add(1); - } - - /// The host wall clock, read through the contract's host service - /// (`busbar_contract::codec::wall_clock_now`) — the one clock a plane reads. This fixture stands - /// in for the host, so it arms that service with its own system-clock reader first (first install - /// wins: a process whose real host already armed it keeps the host's clock). - fn now() -> u64 { - busbar_contract::codec::install_wall_clock(system_clock_secs); - busbar_contract::codec::wall_clock_now().unwrap_or_else(system_clock_secs) - } -} - -/// The fixture host's system clock: whole seconds since the Unix epoch — the reading the real host -/// installs into the contract's wall-clock service. -fn system_clock_secs() -> u64 { - use std::time::{SystemTime, UNIX_EPOCH}; - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs() -} - -// ── The breaker slice: one in-memory cell per (pool, lane) ───────────────────────────────────────── - -impl BreakerHost for FixtureHost { - fn breaker_admit( - &self, - scope: &DispatchScope, - pool: &[u8], - lane: u32, - ) -> Result { - let pool = String::from_utf8_lossy(pool).into_owned(); - let state = self.breaker_state(&pool, lane as usize); - if let BreakerState::Open { until } = state { - if Self::now() < until { - return Err(Unavailable::BreakerOpen { until }); - } - } - Ok(scope.register_admission(Box::new(()))) - } - - fn breaker_settle( - &self, - scope: &DispatchScope, - admission: AdmissionId, - signal: &Signal, - ) -> busbar_contract::abi::hot::StatusClass { - scope - .settle_admission(admission, signal) - .unwrap_or(signal.class.class()) - } - - fn breaker_record_success(&self, pool: &str, lane: usize) { - self.lock().cells.insert( - (pool.to_string(), lane), - Cell { - state: BreakerState::Closed, - }, - ); - } - - fn breaker_record_signal(&self, pool: &str, lane: usize, sig: &CanonicalSignal) { - // The same fold the engine applies: a definitive signal (auth / billing) opens the cell on - // the first record; a client fault or context-length miss never penalizes it; a transient - // blip is noted but a single one does not trip the fixture cell. - if let Disposition::HardDown = busbar_kernel::breaker::classify(sig) { - self.lock().cells.insert( - (pool.to_string(), lane), - Cell { - state: BreakerState::Open { - until: Self::now() + OPEN_COOLDOWN_SECS, - }, - }, - ); - } - } - - fn breaker_retry_after_secs(&self, pool: &str, lane: usize) -> u64 { - match self.breaker_state(pool, lane) { - BreakerState::Open { until } => until.saturating_sub(Self::now()).max(1), - _ => 0, - } - } -} - -// ── The lane/pool slice: no pools configured ───────────────────────────────────────────────────── - -impl LanePoolHost for FixtureHost { - fn lane_store(&self) -> &dyn LaneRuntime { - &self.lanes - } - fn default_probe_interval_secs(&self) -> u64 { - 30 - } - fn default_probe_timeout_secs(&self) -> u64 { - 5 - } - fn pool_members_repeatable(&self, _member: &str) -> Option<(String, Vec, Vec)> { - None - } - fn plane_pool_members(&self, _plane_key: &str, _member: &str) -> Option<(String, Vec)> { - None - } -} - -impl ClockHost for FixtureHost { - fn clock_now_secs(&self) -> u64 { - Self::now() - } - /// The same host wall clock in milliseconds. The contract's service reads whole seconds, so this - /// is that reading scaled; no voice seam reads this fixture's millisecond clock. - fn clock_now_ms(&self) -> u64 { - Self::now().saturating_mul(1000) - } -} - -// ── Telemetry / journal: the fixture records nothing (the plane's own emits are what tests read) ── - -impl TelemetryHost for FixtureHost { - fn request_finished( - &self, - plane: &str, - ingress_protocol: &str, - pool: &str, - outcome: &'static str, - seconds: f64, - ) { - self.lock().finished.push(FinishedRequest { - plane: plane.to_string(), - ingress_protocol: ingress_protocol.to_string(), - pool: pool.to_string(), - outcome, - seconds, - }); - } - fn telemetry_upstream_attempt(&self, _pool_label: &str, _lane: usize) {} - fn telemetry_upstream_failure(&self, _pool_label: &str, _lane: usize, _d: &'static str) {} - fn telemetry_breaker_trip(&self, _pool_label: &str, _lane: usize) {} - fn telemetry_failover(&self, _pool_label: &str, _reason: &'static str) {} - fn telemetry_translation(&self, _from: &str, _to: &str) {} - fn pool_label<'a>(&self, _model: &'a str) -> &'a str { - "unresolved" - } -} - -impl JournalHost for FixtureHost { - fn audit_emit(&self, _action: &str, _resource: &str, _outcome: &str, _principal: &str) {} - fn audit_record(&self, action: &str, resource: &str, outcome: &'static str, principal: &str) { - // Recorded (not a no-op): this is the ONE JournalHost leg a plane's exit-path test needs read - // back — every other JournalHost leg stays a no-op fixture default (nothing today reads them). - self.lock().audit.push(FixtureAuditEntry { - action: action.to_string(), - resource: resource.to_string(), - outcome: outcome.to_string(), - principal: principal.to_string(), - }); - } - fn call_log_emit(&self, _principal: &str, _input: CallInput) {} - fn call_log_emit_hostless(&self, _principal: &str, _input: CallInput) {} -} - -impl MountHost for FixtureHost { - fn arrival_envelope_dialect(&self, _path: &str) -> &'static str { - "" - } - fn arrival_fallback_error( - &self, - _path: &str, - status: axum::http::StatusCode, - kind: &str, - message: &str, - ) -> axum::response::Response { - axum::response::Response::builder() - .status(status) - .header(axum::http::header::CONTENT_TYPE, "application/json") - .body(axum::body::Body::from( - serde_json::json!({ "error": { "type": kind, "message": message } }).to_string(), - )) - .expect("a static response builds") - } -} - -/// A resolver with no secrets behind it: every reference fails closed. -struct NoSecrets; - -impl busbar_contract::secret::SecretResolve for NoSecrets { - fn resolve(&self, _secret: &busbar_contract::secret_ref::SecretRef) -> Result, String> { - Err("the fixture host resolves no secrets".to_string()) - } - fn resolve_string( - &self, - _secret: &busbar_contract::secret_ref::SecretRef, - ) -> Result { - Err("the fixture host resolves no secrets".to_string()) - } -} - -impl RegistryHost for FixtureHost { - fn next_request_id(&self) -> u64 { - self.next_request_id.fetch_add(1, Ordering::Relaxed) - } - fn plane_slot(&self, key: &str) -> Option> { - self.lock().slots.get(key).cloned() - } - fn plane_slot_live(&self, key: &str) -> Option> { - self.plane_slot(key) - } - fn secret_resolver(&self) -> Arc { - Arc::new(NoSecrets) - } - fn subkey_sign(&self, _signing_input: &[u8]) -> Option<[u8; 64]> { - None - } - fn plane_defs(&self) -> Arc { - Arc::new(()) - } -} - -impl HookConfigHost for FixtureHost { - fn caller_in_hook_groups(&self, _caller_group: Option<&str>, _hook_groups: &[String]) -> bool { - false - } - fn pool_rewrites( - &self, - _pool: &str, - ) -> &[( - std::time::Duration, - Arc, - )] { - &[] - } - fn rewrite_hooks( - &self, - ) -> &[( - std::time::Duration, - Arc, - )] { - &[] - } - fn any_content_hook(&self) -> bool { - false - } - fn tap_hooks(&self) -> &[TapEntry] { - &[] - } - fn tap_hooks_response(&self) -> &[TapEntry] { - &[] - } - fn tap_hooks_routing(&self) -> &[TapEntry] { - &[] - } - fn tap_hooks_candidate(&self) -> &[TapEntry] { - &[] - } - fn pool_gates(&self, _pool: &str) -> &[(u16, ResolvedPolicy)] { - &[] - } - fn global_gates(&self) -> &[(u16, ResolvedPolicy)] { - &[] - } - fn pool_policy(&self, _pool: &str) -> Option<&ResolvedPolicy> { - None - } - fn requested_signals(&self) -> &RequestedSignals { - &self.signals - } -} - -// ── The budget slice: the per-key ledger the metering seams land on ───────────────────────────── - -impl BudgetHost for FixtureHost { - fn governance_enabled(&self) -> bool { - self.governed - } - fn meter_charge( - &self, - _scope: &DispatchScope, - _caller: &PlaneRequestCtx, - _usage: &busbar_contract::abi::hot::Usage, - ) { - } - fn rate_headroom( - &self, - _pin: &MeterPin, - _key: &VirtualKey, - _pool: Option<&str>, - _now: u64, - ) -> Option { - None - } - fn budget_state( - &self, - _pin: &MeterPin, - key: &VirtualKey, - _now: u64, - ) -> Vec { - let inner = self.lock(); - let Some(cap) = inner.count_cap else { - return inner.budget.clone(); - }; - let counted = inner.ledger.get(&key.id).map_or(0, |u| u.tokens); - let counted = i64::try_from(counted).unwrap_or(i64::MAX); - vec![busbar_contract::hooks::BudgetBucketState { - bucket_id: key.id.clone(), - budget_group: None, - pool: None, - spend_micros_at_current_rate: counted, - remaining_micros: Some(cap.saturating_sub(counted).max(0)), - window_start: 0, - budget_period: "day".to_string(), - }] - } - fn governance(&self) -> Option { - self.governed.then(|| GovHandle(Arc::new(()))) - } - // No card here, so nothing a card could miss. - fn cost_model_unpriced(&self, _model: &str) -> bool { - false - } - // The kernel's session-fee refund, on the plane's fee lane: one session count back on the budget - // book, floored; the metering row keeps its count (`session_rows`), as the engine's refund does. - fn meter_refund_fee( - &self, - _pin: &MeterPin, - key: &VirtualKey, - _pool: &str, - _plane: &str, - _fee_unit: &str, - _now: u64, - ) { - let mut inner = self.lock(); - if let Some(entry) = inner.ledger.get_mut(&key.id) { - entry.sessions = entry.sessions.saturating_sub(1); - } - } - fn meter_ledger( - &self, - _pin: &MeterPin, - key: &VirtualKey, - _pool: &str, - lane: &str, - usage: &Usage, - _now: u64, - ) { - if lane.ends_with(busbar_kernel::governance::PLANE_LANE_SEP) { - // The kernel's session count on the plane's fee lane: not a turn's count. - let n = usage.usage_units.values().sum::(); - let mut inner = self.lock(); - let entry = inner.ledger.entry(key.id.clone()).or_default(); - entry.sessions = entry.sessions.saturating_add(n); - let row = inner.session_rows.entry(key.id.clone()).or_default(); - *row = row.saturating_add(n); - return; - } - let tokens: u64 = usage.usage_units.values().sum(); - if tokens == 0 { - return; - } - let mut inner = self.lock(); - let entry = inner.ledger.entry(key.id.clone()).or_default(); - entry.tokens = entry.tokens.saturating_add(tokens); - let rows = inner.rows.entry(key.id.clone()).or_default(); - for (class, n) in &usage.usage_units { - let row = rows.entry((lane.to_string(), class.clone())).or_default(); - *row = row.saturating_add(*n); - } - } - fn meter_series( - &self, - _gov: &GovHandle, - key_id: &str, - model: &str, - provider: &str, - _usage: Option<&TokenUsage>, - _now: u64, - ) { - self.lock() - .series - .entry(key_id.to_string()) - .or_default() - .push((model.to_string(), provider.to_string())); - } -} - -#[async_trait::async_trait] -impl IdentityHost for FixtureHost { - fn quarantine_settle(&self, _subject: &str, _state: TrustState) -> bool { - false - } - fn approval_redeem(&self, _nonce: &str, _expires_at: u64, _now: u64) -> bool { - false - } - fn identity_audience_binding(&self, _token: &str, _expected_aud: &str) -> AudienceBinding { - AudienceBinding::Opaque - } - async fn identity_admit( - &self, - _token: Option, - _audience: String, - _resource: String, - ) -> Result<(AuthPrincipal, PlaneRequestCtx), IdentityRefusal> { - Err(IdentityRefusal::Denied) - } - fn principal_standing( - &self, - _standing: &Standing, - _live_gen: u64, - _now: u64, - ) -> Result>, Lapsed> { - Ok(None) - } - fn ask_state_sealer(&self) -> Option { - None - } -} - -// ── The admission slice: the scripted hook gate / rewrite chains ──────────────────────────────── - -impl AdmissionHost for FixtureHost { - fn gate_decide( - &self, - plane_key: &str, - container: &str, - _request_id: u64, - _tool: &str, - args_json: &[u8], - _key: Option<(&str, &str)>, - _session_id: Option<&str>, - ) -> GateOutcome { - let script = self - .lock() - .gates - .get(&(plane_key.to_string(), container.to_string())) - .cloned(); - match script { - Some(s) => s(args_json), - None => GateOutcome::Proceed, - } - } - fn gate_attached(&self, plane_key: &str, container: &str) -> bool { - self.lock() - .gates - .contains_key(&(plane_key.to_string(), container.to_string())) - } - fn tap_attached(&self, plane_key: &str, container: &str) -> bool { - self.lock() - .rewrites - .contains_key(&(plane_key.to_string(), container.to_string())) - } - fn transform_over( - &self, - plane_key: &str, - container: &str, - _request_id: u64, - _tool: &str, - args_json: &[u8], - _key: Option<(&str, &str)>, - _session_id: Option<&str>, - ) -> TransformVerdict { - let script = self - .lock() - .rewrites - .get(&(plane_key.to_string(), container.to_string())) - .cloned(); - match script { - Some(s) => s(args_json), - None => TransformVerdict::Proceed { - applied: false, - args_json: args_json.to_vec(), - }, - } - } - fn govern_admit_reason( - &self, - _scope: &DispatchScope, - _caller: &PlaneRequestCtx, - _pool: &[u8], - ) -> GovAdmit { - GovAdmit::Admitted - } - fn destination_guard( - &self, - _gov: &PlaneRequestCtx, - _proto: &'static str, - _pool: &str, - _started: std::time::Instant, - _charged_at: u64, - ) -> Result<(), Box> { - Ok(()) - } - fn admission_door( - &self, - gov: &PlaneRequestCtx, - _proto: &'static str, - _pool: &str, - _started: std::time::Instant, - _charged_at: u64, - ) -> Result<(Option, Option), Box> { - self.charge_request_fee(gov); - Ok((None, None)) - } - fn admission_check( - &self, - gov: &PlaneRequestCtx, - _proto: &'static str, - _pool: &str, - _charged_at: u64, - ) -> Result<(Option, Option), Box> { - self.charge_request_fee(gov); - Ok((None, None)) - } - fn finish_admitted( - &self, - _gov: &PlaneRequestCtx, - _ingress_protocol: &str, - _pool: &str, - _started: std::time::Instant, - _charged_at: u64, - resp: axum::response::Response, - _charged: Option<&AdmitHandle>, - ) -> axum::response::Response { - resp - } - fn finish_rejected( - &self, - _gov: &PlaneRequestCtx, - _ingress_protocol: &str, - _pool: &str, - _started: std::time::Instant, - _charged_at: u64, - resp: axum::response::Response, - ) -> axum::response::Response { - resp - } - fn plane_audience_bound(&self, _plane_key: &str) -> bool { - false - } -} - -#[async_trait::async_trait] -impl CompletionHost for FixtureHost { - async fn synthesize_completion( - &self, - _gov: &PlaneRequestCtx, - _model: &str, - _body: bytes::Bytes, - _max_body_bytes: usize, - ) -> Result { - // The fixture host drives no dispatch pipeline, so no completion ingress is installed. - Err(CompletionRefusal::NotInstalled) - } -} - -#[async_trait::async_trait] -impl EngineHost for FixtureHost {} - -#[cfg(test)] -#[path = "tests/fixture_host_tests.rs"] -mod tests; diff --git a/crates/busbar-voice/src/testkit/loopback_http.rs b/crates/busbar-voice/src/testkit/loopback_http.rs deleted file mode 100644 index b0047ac4fb..0000000000 --- a/crates/busbar-voice/src/testkit/loopback_http.rs +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! A LOOPBACK HTTP "PROVIDER" for plane tests: a real listener on `127.0.0.1` that answers the -//! queued replies and RECORDS what it was dialed with (path, headers, body), so a plane's egress leg -//! is exercised over the production request path and the test asserts on the bytes the upstream -//! actually saw. The neutral twin of the mock server core's own test support carries, so a plane crate -//! drives a loopback provider without naming `busbar_kernel::test_support`. - -use axum::{ - body::Body, - extract::State, - http::{header, Request, Response, StatusCode}, - routing::any, - Router, -}; -use std::net::SocketAddr; -use std::sync::{Arc, Mutex}; -use tokio::task::JoinHandle; - -/// One queued reply the loopback provider serves. -#[derive(Debug, Clone)] -pub enum MockResponse { - /// A JSON body under `status`. - Ok { - status: StatusCode, - body: serde_json::Value, - }, -} - -impl Default for MockResponse { - fn default() -> Self { - MockResponse::Ok { - status: StatusCode::OK, - body: serde_json::json!({ "ok": true }), - } - } -} - -/// The reply queue plus the last request the provider received. -#[derive(Debug, Default)] -pub struct MockServerState { - queued_replies: Mutex>, - last_auth_header: Mutex>, - last_request_body: Mutex>>, - last_request_headers: Mutex>, - last_request_path: Mutex>, -} - -impl MockServerState { - pub fn new() -> Self { - Self::default() - } - - /// Queue a reply (served last-queued-first, matching the core fixture). - pub fn push(&self, response: MockResponse) { - self.queued_replies.lock().unwrap().push(response); - } - - pub fn next_response(&self) -> Option { - self.queued_replies.lock().unwrap().pop() - } - - /// The `Authorization` header the provider was last dialed with. - pub fn get_last_auth_header(&self) -> Option { - self.last_auth_header.lock().unwrap().clone() - } - - /// The request path the provider last received. - pub fn get_last_request_path(&self) -> Option { - self.last_request_path.lock().unwrap().clone() - } - - /// The raw body bytes the provider last received. - pub fn get_last_request_body(&self) -> Option> { - self.last_request_body.lock().unwrap().clone() - } - - /// One request header the provider last received, by name (case-insensitive). - pub fn get_last_request_header(&self, name: &str) -> Option { - self.last_request_headers - .lock() - .unwrap() - .as_ref() - .and_then(|h| h.get(name)) - .and_then(|v| v.to_str().ok()) - .map(str::to_owned) - } -} - -/// The running loopback provider; `shutdown` signals its serve task to wind down cooperatively. -pub struct MockServer { - addr: SocketAddr, - handle: Option>, - stop: Option>, -} - -impl MockServer { - /// Bind an ephemeral loopback port and serve every path through `state`'s reply queue. - pub async fn new(state: Arc) -> Self { - let app = Router::new().fallback(any(mock_handler)).with_state(state); - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - // Cooperative shutdown rather than `JoinHandle::abort`: `with_graceful_shutdown` is a signal - // `axum::serve` races inside its own accept loop, never a forced mid-flight interruption. - let (stop_tx, stop_rx) = tokio::sync::oneshot::channel::<()>(); - let handle = tokio::spawn(async move { - axum::serve(listener, app) - .with_graceful_shutdown(async move { - let _ = stop_rx.await; - }) - .await - .unwrap(); - }); - Self { - addr, - handle: Some(handle), - stop: Some(stop_tx), - } - } - - pub fn address(&self) -> SocketAddr { - self.addr - } - - pub fn base_url(&self) -> String { - format!("http://{}", self.addr) - } - - pub async fn shutdown(mut self) { - if let Some(stop) = self.stop.take() { - let _ = stop.send(()); - } - if let Some(handle) = self.handle.take() { - let _ = handle.await; - } - } -} - -async fn mock_handler( - State(state): State>, - request: Request, -) -> Response { - let (parts, body) = request.into_parts(); - *state.last_request_path.lock().unwrap() = Some(parts.uri.path().to_string()); - *state.last_request_headers.lock().unwrap() = Some(parts.headers.clone()); - if let Some(auth) = parts - .headers - .get(header::AUTHORIZATION) - .and_then(|v| v.to_str().ok()) - { - *state.last_auth_header.lock().unwrap() = Some(auth.to_string()); - } - let body_bytes = axum::body::to_bytes(body, usize::MAX) - .await - .unwrap_or_default(); - *state.last_request_body.lock().unwrap() = Some(body_bytes.to_vec()); - - match state.next_response().unwrap_or_default() { - MockResponse::Ok { status, body } => Response::builder() - .status(status) - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from(body.to_string())) - .unwrap(), - } -} diff --git a/crates/busbar-voice/src/testkit/metrics_capture.rs b/crates/busbar-voice/src/testkit/metrics_capture.rs deleted file mode 100644 index 341e06b7cb..0000000000 --- a/crates/busbar-voice/src/testkit/metrics_capture.rs +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! AN IN-MEMORY `metrics` RECORDER for plane tests: installs once as the process-global recorder the -//! `metrics::counter!` macros emit into, and renders what landed as Prometheus-style exposition lines -//! (`name{label="value",...} count`), so a plane asserts its emits on a real scrape without naming -//! core's exporter. In a plane crate's own test binary nothing else installs a recorder, so the -//! capture IS the process recorder. - -use metrics::{ - Counter, CounterFn, Gauge, Histogram, Key, KeyName, Metadata, Recorder, SharedString, Unit, -}; -use std::collections::BTreeMap; -use std::sync::{Arc, Mutex, OnceLock}; - -#[derive(Default)] -struct Store { - counters: Mutex>, -} - -static STORE: OnceLock> = OnceLock::new(); - -fn store() -> &'static Arc { - STORE.get_or_init(|| Arc::new(Store::default())) -} - -/// The rendered series identity: the metric name plus its labels in the order they were emitted. -fn series_of(key: &Key) -> String { - let mut labels = key.labels().peekable(); - if labels.peek().is_none() { - return key.name().to_string(); - } - let rendered: Vec = labels - .map(|l| format!("{}=\"{}\"", l.key(), l.value())) - .collect(); - format!("{}{{{}}}", key.name(), rendered.join(",")) -} - -struct CounterCell { - store: Arc, - series: String, -} - -impl CounterFn for CounterCell { - fn increment(&self, value: u64) { - let mut counters = self.store.counters.lock().unwrap(); - *counters.entry(self.series.clone()).or_insert(0) += value; - } - fn absolute(&self, value: u64) { - let mut counters = self.store.counters.lock().unwrap(); - counters.insert(self.series.clone(), value); - } -} - -struct CaptureRecorder(Arc); - -impl Recorder for CaptureRecorder { - fn describe_counter(&self, _key: KeyName, _unit: Option, _description: SharedString) {} - fn describe_gauge(&self, _key: KeyName, _unit: Option, _description: SharedString) {} - fn describe_histogram(&self, _key: KeyName, _unit: Option, _description: SharedString) {} - fn register_counter(&self, key: &Key, _metadata: &Metadata<'_>) -> Counter { - Counter::from_arc(Arc::new(CounterCell { - store: Arc::clone(&self.0), - series: series_of(key), - })) - } - fn register_gauge(&self, _key: &Key, _metadata: &Metadata<'_>) -> Gauge { - Gauge::noop() - } - fn register_histogram(&self, _key: &Key, _metadata: &Metadata<'_>) -> Histogram { - Histogram::noop() - } -} - -/// Install the capture as the process-global recorder. Idempotent: a second call (or one after another -/// recorder already took the slot) is a no-op, and [`render`] reads whatever this capture holds. -pub fn install() { - let _ = metrics::set_global_recorder(CaptureRecorder(Arc::clone(store()))); -} - -/// Every counter series that landed since [`install`], one `name{labels} value` line each, sorted. -pub fn render() -> String { - let counters = store().counters.lock().unwrap(); - let mut out = String::new(); - for (series, value) in counters.iter() { - out.push_str(series); - out.push(' '); - out.push_str(&value.to_string()); - out.push('\n'); - } - out -} diff --git a/crates/busbar-voice/src/testkit/mod.rs b/crates/busbar-voice/src/testkit/mod.rs deleted file mode 100644 index feee545b00..0000000000 --- a/crates/busbar-voice/src/testkit/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE VOICE PLANE'S TEST-KIT — the fixture surface this plane's own batteries drive, kept ON THE -//! PLANE. A plane is a plugin on the plane ABI and it tests ITSELF: the kernel ships no scaffolding -//! for it (1.6.0 Locked Decision #33, "the kernel never tests a plugin"), so the three doubles the -//! voice cells reach — the in-memory [`fixture_host::FixtureHost`], the -//! [`loopback_http`] provider and the [`metrics_capture`] recorder — live here rather than in -//! `busbar_kernel::testkit`, which is deleted. -//! -//! Every double reaches the engine through the SAME neutral seam production uses -//! (`busbar_kernel::plane_host::EngineHost` and friends); nothing here names a kernel *test* item. - -/// The in-memory [`busbar_kernel::plane_host::EngineHost`] this plane's cells drive when no engine -/// `App` is in their closure at all: scripted hook gates/rewrites, breaker cells and a per-key ledger -/// behind the same seam production reaches. -/// The echo tool executor the batteries drive: answers every call with its own name and arguments. -pub mod echo; - -pub mod fixture_host; - -/// A loopback HTTP provider that records what it was dialed with, for this plane's egress legs. -pub mod loopback_http; - -/// An in-memory `metrics` recorder + exposition render, for asserting this plane's counter emits. -pub mod metrics_capture; - -/// One phone call over the telephony proxy, the caller speaking Twilio Media Streams. -pub mod telephony; - -/// THIS PLANE'S SERVED-LEG WITNESSES — `(capability key, [(loop step or core capability, witness)])`. -/// -/// What a test binary that links this crate without naming it runs through ITS registered session -/// runner for this plane's key (the composition root's kernel-loop session rider): each witness opens -/// a session at the served door, asserts one capability or one loop step on the session it opened, -/// and returns how many session opens it expects to have reached that runner. See -/// `tests/served_witness.rs`. -pub const SERVED: (&str, &[(&str, crate::mount::served_witness::Witness)]) = - (crate::PLANE_KEY, crate::mount::served_witness::WITNESSES); diff --git a/crates/busbar-voice/src/testkit/telephony.rs b/crates/busbar-voice/src/testkit/telephony.rs deleted file mode 100644 index f34410c6f7..0000000000 --- a/crates/busbar-voice/src/testkit/telephony.rs +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! ONE PHONE CALL over a [`TelephonyProxy`], driven the way the carrier drives it: the caller speaks -//! Twilio Media Streams (`connected`, `start`, `media`, `stop`), the far end the realtime dialect. -//! Four in-memory channels stand in for the two sockets. Every wait is bounded, so a proxy that -//! never relays reads as an empty leg rather than a hung test, and both sockets are closed at the -//! end whatever happened. - -use crate::ir::codec::{DuplexReader, DuplexWriter}; -use crate::topology::telephony::TelephonyProxy; -use futures::channel::mpsc::{unbounded, UnboundedReceiver}; -use futures::StreamExt; -use std::time::Duration; - -/// The stream identifier the call's `start` names, and every `media` frame repeats. -pub const STREAM_SID: &str = "MZ-call"; - -/// The far end's reply: three bytes of µ-law, which base64 spells `////`. -pub const FAR_END_AUDIO: &[u8] = &[0xff; 3]; -const FAR_END_DELTA: &str = "////"; - -/// How long the driver waits for one frame to cross before it gives up on it. -const WAIT: Duration = Duration::from_secs(2); - -/// What crossed each socket during the call, as JSON, in order. -#[derive(Debug, Default)] -pub struct CallLegs { - /// Every frame the far end received. - pub to_far_end: Vec, - /// Every frame the caller received. - pub to_caller: Vec, -} - -fn frame(v: &serde_json::Value) -> Vec { - serde_json::to_vec(v).expect("a JSON value serializes") -} - -fn parsed(f: &[u8]) -> serde_json::Value { - serde_json::from_slice(f).unwrap_or(serde_json::Value::Null) -} - -/// Wait (bounded) for a frame on `rx` that `wanted` accepts, keeping every frame seen on `seen`. -async fn wait_for( - rx: &mut UnboundedReceiver>, - seen: &mut Vec, - wanted: impl Fn(&serde_json::Value) -> bool, -) { - loop { - match tokio::time::timeout(WAIT, rx.next()).await { - Ok(Some(f)) => { - let v = parsed(&f); - let hit = wanted(&v); - seen.push(v); - if hit { - return; - } - } - Ok(None) | Err(_) => return, - } - } -} - -/// PLACE ONE CALL: the caller connects, starts the stream in `g711_ulaw`, and speaks `caller_audio` -/// (raw µ-law) as one `media` frame; once the far end has heard it, the far end answers with -/// [`FAR_END_AUDIO`] as one realtime audio delta; once the caller has received it, the caller hangs -/// up with `stop`. The far end's socket stays open throughout: the call ends on the caller's `stop`. -pub async fn place_call(proxy: TelephonyProxy, caller_audio: &[u8]) -> CallLegs -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - let (prov_in_tx, prov_in_rx) = unbounded::>(); - let (prov_out_tx, mut prov_out_rx) = unbounded::>(); - let (cli_in_tx, cli_in_rx) = unbounded::>(); - let (cli_out_tx, mut cli_out_rx) = unbounded::>(); - let mut legs = CallLegs::default(); - - let caller = { - let legs = &mut legs; - let (prov_out_rx, cli_out_rx) = (&mut prov_out_rx, &mut cli_out_rx); - async move { - for v in [ - serde_json::json!({"event": "connected", "protocol": "Call", "version": "1.0.0"}), - serde_json::json!({"event": "start", "streamSid": STREAM_SID, "start": { - "streamSid": STREAM_SID, "callSid": "CA-call", - "mediaFormat": {"encoding": "audio/x-mulaw", "sampleRate": 8000, "channels": 1} - }}), - ] { - let _ = cli_in_tx.unbounded_send(frame(&v)); - } - let _ = cli_in_tx.unbounded_send( - crate::topology::twilio::TwilioEnvelope::encode_media(STREAM_SID, caller_audio), - ); - wait_for(prov_out_rx, &mut legs.to_far_end, |v| { - v["type"] == "input_audio_buffer.append" - }) - .await; - let _ = prov_in_tx.unbounded_send(frame(&serde_json::json!({ - "type": "response.output_audio.delta", - "delta": FAR_END_DELTA, - }))); - wait_for(cli_out_rx, &mut legs.to_caller, |v| v["event"] == "media").await; - let _ = cli_in_tx.unbounded_send(frame(&serde_json::json!({ - "event": "stop", "streamSid": STREAM_SID - }))); - // A proxy that does not end the call on `stop` still ends when both sockets do. - tokio::time::sleep(WAIT).await; - drop(cli_in_tx); - drop(prov_in_tx); - } - }; - let call = proxy.run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx); - // The caller's script may still be sleeping when the call ends: the call is what the legs read. - tokio::select! { - () = call => {} - () = async { caller.await; std::future::pending::<()>().await } => {} - } - - prov_out_rx.close(); - while let Some(f) = prov_out_rx.next().await { - legs.to_far_end.push(parsed(&f)); - } - cli_out_rx.close(); - while let Some(f) = cli_out_rx.next().await { - legs.to_caller.push(parsed(&f)); - } - legs -} diff --git a/crates/busbar-voice/src/testkit/tests/fixture_host_tests.rs b/crates/busbar-voice/src/testkit/tests/fixture_host_tests.rs deleted file mode 100644 index 3209e75a47..0000000000 --- a/crates/busbar-voice/src/testkit/tests/fixture_host_tests.rs +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE FIXTURE'S OWN LEDGER PROOF — the fixture host is what a plane's money-path tests read their -//! verdict off, so its `requests` counter must MOVE when the Admit door is driven. Before these -//! tests the counter was write-never: every `ledger_usage(key).requests` read back 0 whether the -//! plane charged once, twice, or not at all, so a double-charging plane passed its own billing test. -//! Two admissions must read back two fees; an ungoverned host, and a request presenting no key, must -//! read back nothing at all. - -use super::{FixtureHost, LedgerUsage}; -use busbar_contract::records::{PlaneRequestCtx, VirtualKey}; -use busbar_kernel::plane_host::AdmissionHost; -use std::sync::Arc; - -const PROTO: &str = "test-proto"; - -fn ctx(key_id: &str) -> PlaneRequestCtx { - PlaneRequestCtx { - key: Some(Arc::new(VirtualKey { - id: key_id.to_string(), - name: key_id.to_string(), - ..Default::default() - })), - } -} - -fn requests_for(host: &FixtureHost, key_id: &str) -> u64 { - host.ledger_usage(key_id).map_or(0, |u| u.requests) -} - -#[test] -fn two_admissions_charge_two_request_fees() { - let host = FixtureHost::new().governed(); - let gov = ctx("vk-payer"); - let started = std::time::Instant::now(); - - assert_eq!( - requests_for(&host, "vk-payer"), - 0, - "a key that has not been admitted owes nothing" - ); - - host.admission_door(&gov, PROTO, "pool", started, 0) - .expect("the fixture door admits"); - assert_eq!( - requests_for(&host, "vk-payer"), - 1, - "one admission is one billable request" - ); - - host.admission_check(&gov, PROTO, "pool", 0) - .expect("the fixture check admits"); - assert_eq!( - requests_for(&host, "vk-payer"), - 2, - "a second admission is a SECOND billable request — a plane that runs the door twice for one \ - request reads back two fees here" - ); - - // The fee is the only thing Admit moves: the token ledger is the Meter step's to write. - assert_eq!( - host.ledger_usage("vk-payer"), - Some(LedgerUsage { - tokens: 0, - requests: 2, - sessions: 0, - }), - "the Admit step charges the per-request fee and no tokens" - ); -} - -#[test] -fn the_fee_lands_on_the_presenting_key_only() { - let host = FixtureHost::new().governed(); - let started = std::time::Instant::now(); - - host.admission_door(&ctx("vk-a"), PROTO, "pool", started, 0) - .expect("the fixture door admits"); - host.admission_door(&ctx("vk-b"), PROTO, "pool", started, 0) - .expect("the fixture door admits"); - host.admission_door(&ctx("vk-b"), PROTO, "pool", started, 0) - .expect("the fixture door admits"); - - assert_eq!(requests_for(&host, "vk-a"), 1, "vk-a presented once"); - assert_eq!(requests_for(&host, "vk-b"), 2, "vk-b presented twice"); -} - -#[test] -fn an_ungoverned_host_and_a_keyless_request_are_charged_nothing() { - // Governance off: there is no ledger to bill, exactly as an ungoverned deployment. - let ungoverned = FixtureHost::new(); - ungoverned - .admission_door( - &ctx("vk-payer"), - PROTO, - "pool", - std::time::Instant::now(), - 0, - ) - .expect("the fixture door admits"); - assert_eq!( - ungoverned.ledger_usage("vk-payer"), - None, - "an ungoverned host materialises no ledger row" - ); - - // Governed, but the request carries no resolved key: nobody to charge. - let governed = FixtureHost::new().governed(); - governed - .admission_check(&PlaneRequestCtx::default(), PROTO, "pool", 0) - .expect("the fixture check admits"); - assert!( - governed.ledger_usage("vk-payer").is_none(), - "a keyless request charges no key" - ); -} diff --git a/crates/busbar-voice/src/tests/audit_chain_tests.rs b/crates/busbar-voice/src/tests/audit_chain_tests.rs deleted file mode 100644 index 5cc599e003..0000000000 --- a/crates/busbar-voice/src/tests/audit_chain_tests.rs +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! AUDIT-CHAIN capability cells (voice-client + voice-server) — GATE-VALID location. -//! -//! The proving assertions already live and pass in `runtime/tests.rs` -//! (`session_scope_reattach_and_foreign_owner_refusal`), but that path clears neither the equality -//! gate's `/tests/` directory rule nor its `_tests.rs` suffix rule. These are the SAME assertions, -//! re-homed under `src/tests/` so the capability-equality gate's location rule is met. They drive a -//! durable session open → seal a genesis `SealedEvent`/`tail_hash` → bump the per-turn cursor → -//! reattach, reading the durable row back (a non-empty read is the pass; an empty read is a failure). -//! Runtime-gated so the durable engine compiles. - -use crate::runtime::scope::SessionHandle; -use busbar_kernel::plane::handle_engine::{DurableHandleEngine, ScopedMutateError}; -use std::sync::Arc; - -/// voice-client cell: an opened session seals a genesis event on the durable chain and survives a -/// reattach — a fresh binding for the same (owner, id) reads the live turns back through the scoped -/// path, and a foreign owner is refused indistinguishably from a missing handle. -#[test] -fn an_opened_voice_session_seals_a_genesis_event_and_survives_reattach_on_the_durable_chain() { - let engine = Arc::new(DurableHandleEngine::new()); - let alice = SessionHandle::bind(Arc::clone(&engine), "alice", "call-1"); - // OPEN seals the genesis event onto the durable chain. - alice.open(1).expect("alice opens her session"); - - // A per-turn checkpoint bumps the durable cursor onto the genesis chain. - assert_eq!(alice.bump_turn(2).expect("owner bump"), 1); - assert_eq!(alice.bump_turn(3).expect("owner bump"), 2); - - // REATTACH: a fresh binding for the SAME (owner, id) reads the live row through the scoped path. - let alice_again = SessionHandle::bind(Arc::clone(&engine), "alice", "call-1"); - assert_eq!( - alice_again.get().map(|r| r.turns), - Some(2), - "reattach sees the durable turns — a non-empty read of the sealed chain" - ); - - // FOREIGN OWNER: a session bound to the same id under a different owner is refused identically to - // a missing handle — cannot read, resume, or evict, and cannot even tell it exists. - let mallory = SessionHandle::bind(Arc::clone(&engine), "mallory", "call-1"); - assert!( - mallory.get().is_none(), - "foreign owner cannot read the chain" - ); - assert!( - matches!(mallory.bump_turn(9), Err(ScopedMutateError::NotYours)), - "foreign owner cannot resume/mutate the chain" - ); - assert!(!mallory.close(), "foreign owner evicts nothing"); -} - -/// voice-server cell: an inbound session-open (the front door) lands a sealed genesis event — the -/// durable read-back is non-empty. An empty read is a FAILURE, not a pass. -#[test] -fn an_inbound_session_open_lands_a_sealed_genesis_event_the_front_door_wrote() { - let engine = Arc::new(DurableHandleEngine::new()); - // The inbound front door opens a governed session under the presenting caller. - let front_door = SessionHandle::bind(Arc::clone(&engine), "caller", "inbound-1"); - front_door - .open(1) - .expect("the front door opens the session"); - - // The genesis event the front door wrote is readable back on the durable chain (non-empty). - let row = front_door - .get() - .expect("the sealed genesis event reads back — a non-empty durable chain"); - assert_eq!(row.turns, 0, "a fresh genesis row carries no turns yet"); - - // A checkpoint the front door drives bumps onto the same sealed chain. - assert_eq!(front_door.bump_turn(2).expect("front-door bump"), 1); - assert_eq!( - front_door.get().map(|r| r.turns), - Some(1), - "the front-door checkpoint is durable on the sealed chain" - ); -} diff --git a/crates/busbar-voice/src/tests/billing_ledger_tests.rs b/crates/busbar-voice/src/tests/billing_ledger_tests.rs deleted file mode 100644 index 60123f38a8..0000000000 --- a/crates/busbar-voice/src/tests/billing_ledger_tests.rs +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE BEHAVIORAL BILLING PROOF for the voice plane — the ledger-level twin of the LLM plane's -//! crossproto_delivery_billing oracle. The `plane_meter_seam_reachability` gate proves voice *calls* -//! the core Meter seam; THIS test proves the spend actually *lands on the one ledger*, attributed to -//! the presenting key, exactly as marketing states: "the token counts land on the ledger when the -//! response stream completes." -//! -//! It drives a voice turn's usage through the SHIPPED path — `SessionCore`'s per-turn metering, which -//! reports the turn to the kernel's session account (`host.meter_ledger`) — over a GOVERNED host, then -//! reads the key's usage back off the host's ledger. A voice session that "bills nobody" (the pre-fix -//! state) reads back zero tokens; a fixed one reads the turn. -//! -//! The host is the substrate's in-memory fixture host with governance ON: its `meter_ledger` seam is -//! the one ledger this test reads back (`ledger_usage(key)`), keyed by the presenting key exactly as -//! the engine's `usage_for(key)` is. Whether the engine's own ledger accrues what its seam is handed -//! is the engine's money-path suite to prove; this plane's tests do not link the engine. - -use crate::ir::usage::IrDuplexUsage; -use crate::runtime::metering::TurnMeter; -use crate::testkit::fixture_host::FixtureHost; -use busbar_kernel::{ - config, - config_validate::validate, - cost::CostModel, - governance::PLANE_LANE_SEP, - plane::registry::{PlaneDecl, TestRegistryIsolation}, - plane_host::EngineHost, -}; -use busbar_plane_streaming::session::TurnCounters; -use std::sync::Arc; - -/// A governed fixture: a host with governance ON and a virtual key presenting to it — so the host -/// `meter_ledger` seam the voice session drives writes to the very ledger this test reads back. -fn governed_fixture() -> (Arc, busbar_contract::records::VirtualKey) { - let host = Arc::new(FixtureHost::new().governed()); - let key = busbar_contract::records::VirtualKey { - id: "vk-voice-caller".to_string(), - name: "voice-caller".to_string(), - ..Default::default() - }; - (host, key) -} - -/// A voice turn's usage report: `input` audio tokens in, `output` audio tokens out. -fn turn_usage(input: u64, output: u64) -> IrDuplexUsage { - IrDuplexUsage { - audio_in: input, - audio_out: output, - ..IrDuplexUsage::default() - } -} - -#[test] -fn a_voice_turn_lands_spend_on_the_presenting_keys_ledger() { - let (host, key) = governed_fixture(); - - // Before: the key has metered nothing. - let before = host - .ledger_usage(&key.id) - .map(|u| (u.tokens, u.requests)) - .unwrap_or((0, 0)); - assert_eq!(before, (0, 0), "a fresh key has no ledgered usage"); - - // Drive ONE voice turn's usage through the SHIPPED Meter seam — the exact call `SessionCore` - // makes per turn (the kernel session account → `host.meter_ledger`). - let meter = TurnMeter::new( - Arc::clone(&host) as Arc, - key.clone(), - "streaming-server", - crate::OPENAI_REALTIME, - ); - let session = meter - .open("voice-model") - .expect("an uncapped chain opens") - .expect("a governed host opens an account"); - let _ = session.report_turn(Some(&turn_usage(300, 120)), TurnCounters::default()); - - // After: the turn's tokens (300 + 120) landed on the presenting key's ledger — voice now bills - // the caller through the ONE ledger, exactly like a model call or a tool call. THIS is the proof - // that the Meter step works end-to-end at the ledger, not just at the wiring. - let after = host - .ledger_usage(&key.id) - .expect("the key now has a materialised bucket"); - assert_eq!( - after.tokens, 420, - "the voice turn's 420 tokens must land on the one ledger (input 300 + output 120)" - ); - // `requests` is the billable-REQUEST count, incremented by the ADMIT step's per-request fee - // (`govern_admit`), NOT by the Meter step. Voice does not yet run Admit's fee charge — that is the - // tracked next increment — so this reads 0 today. Pinned so that when Admit lands for voice, this - // assertion flips RED and this test is the reminder to update it (and confirm the fee is charged). - assert_eq!( - after.requests, 0, - "voice does not yet run the Admit per-request fee (tracked increment); flip this when it does" - ); -} - -#[test] -fn an_ungoverned_voice_turn_meters_nobody_without_panicking() { - // No governance configured: the host mints no meter pin, so no account opens (a voice session on - // an ungoverned deployment opens and runs, it simply attributes to no ledger). - let host = FixtureHost::new().into_host(); - let key = busbar_contract::records::VirtualKey { - id: "anon".to_string(), - ..Default::default() - }; - let meter = TurnMeter::new(host, key, "streaming-server", crate::OPENAI_REALTIME); - // Must not panic even though there is no ledger to write to. - assert!(matches!(meter.open("voice-model"), Ok(None))); - let _ = turn_usage(10, 5); -} - -/// **EVERY ROW A VOICE SESSION'S METERING WRITES IS THE STREAMS PLANE'S** (#47, OWNER RULING Q32). -/// -/// `GET /admin/usage` knows a metering row as a plane's row by its `provider` column naming a -/// registered plane key; any other row is a pools row, priced at the pools plane's flat -/// `per_request_fee:` per request. Each turn used to write a series row with the UPSTREAM dialect -/// label (`openai_realtime`) as its provider — a pools row — so, with a flat fee of 5, a three-turn -/// session read 15 on `/admin/usage` while the budget book (which charges a turn no per-request fee) -/// read 0. The turn's counts now reach the metering row only through the kernel's one accrual -/// (`meter_ledger`), on the plane-qualified lane the kernel keys by this plane: `/admin/usage` reads -/// 0, as the book does, and the session's own fee (`streams.fees.per_session`) is the fee lane's. -#[test] -fn a_voice_sessions_metering_writes_no_row_keyed_by_the_upstream_provider() { - let (host, key) = governed_fixture(); - let meter = TurnMeter::new( - Arc::clone(&host) as Arc, - key.clone(), - "streaming-server", - crate::OPENAI_REALTIME, - ); - let session = meter - .open("gpt-realtime") - .expect("an uncapped chain opens") - .expect("a governed host opens an account"); - for _ in 0..3 { - let _ = session.report_turn(Some(&turn_usage(30, 4)), TurnCounters::default()); - } - - assert_eq!( - host.series_rows(&key.id), - Vec::<(String, String)>::new(), - "a turn is not a request: no per-turn series row (one keyed by the upstream provider \ - reads as a pools row, charged the flat per_request_fee per turn on /admin/usage)" - ); - let plane_lane = format!("{}{}", crate::PLANE_KEY, PLANE_LANE_SEP); - let rows = host.ledger_rows(&key.id); - assert!( - rows.keys().all(|(lane, _)| lane.starts_with(&plane_lane)), - "every count lands on a lane qualified by this plane's key: {rows:?}" - ); - assert_eq!( - rows.get(&( - format!("{plane_lane}gpt-realtime"), - "audio_tokens_in".to_string() - )), - Some(&90), - "the three turns' counts, on the plane-qualified model lane" - ); -} - -/// The node's resolved config from `yaml` (beside empty providers/models), under whatever plane -/// declarations the registry holds. -fn resolved(yaml: &str) -> config::RootCfg { - let text = format!("providers: {{}}\nmodels: {{}}\n{yaml}"); - let deploy = config::deploy_from_yaml_str(&text).expect("the config parses"); - config::resolve(&deploy, &Default::default()).expect("it resolves") -} - -/// **THE STREAMING PLANE COUNTS `per_session` ONLY** (ARCHITECT ruling, fees): a turn never passes -/// per-request admission, so `streams.fees.per_request` would charge nothing — boot and `--validate` -/// refuse it, naming the key and the counted list. `streams.fees.per_session: 40` boots, and a -/// session opened over the plane's shipped metering counts one session on the plane's fee lane, -/// which the card the config resolves to prices at 40. -#[test] -fn streams_fees_per_request_refuses_and_per_session_boots_and_charges() { - let _reg = TestRegistryIsolation::seeded(&[&PLANE_ROW]); - assert_eq!( - validate(&resolved("streams:\n fees: { per_request: 2 }\n")), - Err(vec![ - "streams.fees.per_request is not counted by this plane (counted: per_session); \ - remove it" - .to_string() - ]) - ); - let root = resolved("streams:\n fees: { per_session: 40 }\n"); - assert_eq!(validate(&root), Ok(()), "a counted fee boots"); - - let (host, key) = governed_fixture(); - let meter = TurnMeter::new( - Arc::clone(&host) as Arc, - key.clone(), - "streaming-server", - crate::OPENAI_REALTIME, - ); - let _session = meter - .open("gpt-realtime") - .expect("opens") - .expect("governed"); - let sessions = host.ledger_usage(&key.id).expect("a ledger").sessions; - assert_eq!( - sessions, 1, - "one opened session, one count on the plane's fee lane" - ); - let fee_lane = format!("{}{PLANE_LANE_SEP}", crate::PLANE_KEY); - let price = |fees: &config::PlaneFeesMap| { - let cost = CostModel::resolve_parts(None, 0, &Default::default()).with_plane_fees(fees); - let usage = busbar_contract::billing::Usage { - usage_units: [("per_session".to_string(), sessions)].into(), - }; - cost.price_usage_nanos(&fee_lane, &usage) - .expect("the fee lane prices") - }; - let one = resolved("streams:\n fees: { per_session: 1 }\n").plane_fees; - assert!(price(&one) > 0); - assert_eq!( - price(&root.plane_fees), - 40 * price(&one), - "the session is charged 40" - ); -} - -/// This plane's registry row, assembled kernel-side from its contract declaration -/// and its behaviour table. -static PLANE_ROW: PlaneDecl = PlaneDecl::assemble(crate::PLANE_DECLARATION, crate::PLANE_HOOKS); diff --git a/crates/busbar-voice/src/tests/breaker_tests.rs b/crates/busbar-voice/src/tests/breaker_tests.rs deleted file mode 100644 index c6c904b57f..0000000000 --- a/crates/busbar-voice/src/tests/breaker_tests.rs +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! KILL-THE-PROVIDER, the voice-client dial leg — the `breaker-trip` / `breaker-fastfail` cells, -//! DIRECT-DRIVEN. No live serving path calls [`dial_provider`] yet, so these prove the cells by -//! driving the wired dial path directly against the substrate's in-memory fixture host, whose -//! `(pool, lane)` breaker cells sit behind the same `breaker_admit` / `breaker_record_*` seam the -//! production dispatch admits/records through (and are read back off the same host). -//! -//! 1. TRIP: a provider dial busbar's own net-guard refuses is a DEFINITIVE (hard-down) failure, so it -//! records into the ONE host cell and OPENS it on the first failure. -//! 2. FAST-FAIL: with that cell OPEN, a fresh dial is refused at admission BEFORE any socket — the -//! fast-fail leg, with the cell's own cooldown as `Retry-After`. -//! -//! RED before the wiring: `dial_provider` never touched the breaker at all, so a dead provider left -//! no cell to trip and a fresh dial waited out the full dial timeout against a target already known -//! down. - -use crate::testkit::fixture_host::FixtureHost; -use crate::topology::{dial_provider, stream_breaker_key, DialProviderError}; -use busbar_contract::upstream::{CanonicalSignal, StatusClass}; -use busbar_kernel::net_guard::GuardPolicy; -use busbar_kernel::plane_host::BreakerHost; -use busbar_kernel::store::BreakerState; - -/// A canonical hard-down signal — the disposition a definitive provider failure (auth/billing, or a -/// busbar-side guard refusal) folds to, which OPENS the cell on the first record. -fn hard_down() -> CanonicalSignal { - CanonicalSignal { - class: StatusClass::Auth, - provider_signal: None, - retry_after: None, - } -} - -#[tokio::test] -async fn a_hard_down_provider_records_into_the_core_cell_and_opens_it() { - let host = FixtureHost::new(); - let pool = stream_breaker_key("openai-realtime"); - - // The cell starts Closed — no failure recorded yet. - assert_eq!( - host.breaker_state(&pool, 0), - BreakerState::Closed, - "the provider cell is Closed before any dial" - ); - - // A dial busbar's OWN net-guard refuses (a public loopback under the fail-closed default is an - // internal target) is a DEFINITIVE failure: `dial_provider` records the hard-down signal into the - // ONE host cell through the host seam, and the cell OPENS on this first failure. - let refused = dial_provider(&host, &pool, 0, "wss://127.0.0.1/", GuardPolicy::default()) - .await - .err(); - assert!( - matches!(refused, Some(DialProviderError::Dial(_))), - "the guard-refused dial fails: {refused:?}" - ); - assert!( - matches!(host.breaker_state(&pool, 0), BreakerState::Open { .. }), - "the provider's host cell must be Open after a definitive dial failure — the record wired \ - into dial_provider fired" - ); -} - -#[tokio::test] -async fn a_tripped_provider_cell_refuses_before_the_dial() { - let host = FixtureHost::new(); - let pool = stream_breaker_key("openai-realtime"); - - // TRIP the cell through the same host seam a dial-leg failure records through. - host.breaker_record_signal(&pool, 0, &hard_down()); - assert!( - matches!(host.breaker_state(&pool, 0), BreakerState::Open { .. }), - "the cell is Open (tripped) before the fresh dial" - ); - - // A fresh dial against the tripped cell is refused at ADMISSION — `dial_provider` returns - // `BreakerOpen` with the cell's own cooldown, and NEVER dials (the target below is unreachable; a - // `Connect`/`Dial` error here would prove the admit did NOT fast-fail). - let refused = dial_provider( - &host, - &pool, - 0, - "ws://127.0.0.1:9/", - GuardPolicy { - allow_private: true, - allow_plaintext: true, - ..GuardPolicy::default() - }, - ) - .await - .err(); - match refused { - Some(DialProviderError::BreakerOpen { retry_after_secs }) => { - assert!( - retry_after_secs >= 1, - "the fast-fail carries the cell's own Retry-After" - ); - } - other => panic!("a tripped cell must fast-fail BreakerOpen before the dial, got {other:?}"), - } -} diff --git a/crates/busbar-voice/src/tests/config_tests.rs b/crates/busbar-voice/src/tests/config_tests.rs deleted file mode 100644 index 1a16781235..0000000000 --- a/crates/busbar-voice/src/tests/config_tests.rs +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! Tests for `crates/busbar-voice/src/config.rs`. - -use super::*; -use crate::ir::control::IrVad; - -/// An ABSENT section (`default_section`) and an empty `streams: {}` must decode to the SAME value, -/// or a present-but-empty block would silently differ from an omitted one. -#[test] -fn empty_section_equals_default() { - let empty: StreamsCfg = - serde_yaml::from_value(serde_yaml::Value::Mapping(Default::default())).unwrap(); - assert_eq!(empty, StreamsCfg::default()); -} - -/// The plane defaults are the DoD ceilings, and the synthesized VAD carries the `streams:`-level -/// 500ms silence (not the IR wire default of 200ms). -#[test] -fn defaults_are_the_dod_values() { - let c = StreamsCfg::default(); - assert_eq!( - c.session_max_secs, None, - "no ceiling unless configured (Q21a)" - ); - assert_eq!(c.context_window_tokens, 32_768); - assert_eq!(c.max_output_tokens, 4096); - match c.session.turn_detection { - Some(Some(IrVad::ServerVad { - silence_duration_ms, - .. - })) => assert_eq!(silence_duration_ms, 500), - other => panic!("expected synthesized server_vad, got {other:?}"), - } -} - -/// A valid `streams:` block — session/VAD knobs plus the three ceilings — parses through the -/// owned `parse_section` hook (the boot-validate leg's (a) assertion). -#[test] -fn a_valid_streams_block_parses_through_parse_section() { - let y: serde_yaml::Value = serde_yaml::from_str( - "session:\n \ - voice: alloy\n \ - turn_detection:\n \ - type: server_vad\n \ - silence_duration_ms: 700\n\ - session_max_secs: 1800\n\ - context_window_tokens: 8192\n\ - max_output_tokens: 2048\n", - ) - .unwrap(); - let boxed = streams_parse_section(&y).expect("a valid streams: block must parse"); - assert!( - boxed.is_present(), - "an operator-written streams: block is present" - ); -} - -/// `deny_unknown_fields` refuses a typo'd key at parse — the boot-validate leg's (b) assertion. -#[test] -fn unknown_key_is_refused() { - let mut m = serde_yaml::Mapping::new(); - m.insert("nonsense_key".into(), 1.into()); - let err = streams_parse_section(&serde_yaml::Value::Mapping(m)).unwrap_err(); - assert!(err.contains("nonsense_key"), "{err}"); -} - -/// `default_section` is the empty section, and it is NOT `is_present` (an omitted block names no -/// plane and is not refused). -#[test] -fn default_section_is_absent() { - let s = streams_default_section(); - assert!(!s.is_present()); -} - -/// OWNER RULING Q21a: the session wall-clock ceiling is optional and plane-owned. Absent reads as no -/// ceiling; a positive figure is kept; zero and a negative figure are refused at parse. -#[test] -fn the_session_ceiling_is_optional_and_refuses_zero_and_negative() { - let parse = |yaml: &str| { - streams_parse_section(&serde_yaml::from_str::(yaml).unwrap()).map(|b| { - b.as_any() - .downcast_ref::() - .expect("the plane's own section") - .session_max_secs - }) - }; - assert_eq!(parse("{}").unwrap(), None, "absent: no ceiling"); - assert_eq!( - parse("session_max_secs: 1800") - .unwrap() - .map(std::num::NonZeroU32::get), - Some(1800) - ); - assert!(parse("session_max_secs: 0").is_err(), "zero is refused"); - assert!( - parse("session_max_secs: -5").is_err(), - "a negative figure is refused" - ); -} diff --git a/crates/busbar-voice/src/tests/egress_tests.rs b/crates/busbar-voice/src/tests/egress_tests.rs deleted file mode 100644 index 31c41bc57a..0000000000 --- a/crates/busbar-voice/src/tests/egress_tests.rs +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE EGRESS CREDENTIAL CELL — `egress-auth × voice-client`. The voice plane plans its provider -//! credential through the ONE egress mechanism (`ProtocolDecl::egress_auth_headers`), and the value it -//! plans is the PROVIDER credential (the WebRTC `ek_` / the real provider key / the telephony carrier -//! secret), NEVER a caller governance token forwarded through. -//! -//! RED before the wiring: `DECLS.egress_auth_headers` was `None`, so the plane planned no egress -//! credential at all and the decl could not state that the dial carries busbar's own authority. - -use crate::DECLS; -use busbar_contract::config::UpstreamCreds; -use busbar_kernel::proto::SigningContext; - -#[test] -fn the_provider_dial_carries_the_planned_credential_and_never_a_caller_token() { - let builder = DECLS - .egress_auth_headers - .expect("the voice plane declares an egress credential builder (the ONE egress mechanism)"); - - // The resolved per-lane PROVIDER credential — what the composition root leases for the dial. A - // distinct token the caller might have presented at the front door, which must NEVER be the value - // planned onto the provider hop. - const PROVIDER_CREDENTIAL: &str = "ek_provider_scoped_secret_abc"; - const CALLER_TOKEN: &str = "caller-inbound-governance-bearer-xyz"; - - let ctx = SigningContext { - host: "api.openai.com", - canonical_uri: "/v1/realtime/calls", - body: b"", - timestamp_epoch: 0, - upstream_creds: UpstreamCreds::Own, - }; - let headers = builder(PROVIDER_CREDENTIAL, &ctx); - - // Exactly one header: the provider-facing `Authorization: Bearer `. - assert_eq!( - headers.len(), - 1, - "the plan is one provider Authorization header" - ); - let (name, value) = &headers[0]; - assert_eq!(name.as_str(), "authorization"); - assert_eq!( - value.to_str().unwrap(), - format!("Bearer {PROVIDER_CREDENTIAL}"), - "the dial carries the PLANNED provider credential" - ); - - // The provider hop carries ONLY the leased provider credential — the caller's inbound governance - // token appears nowhere in the plan (the builder reads the resolved credential string and nothing - // else, so a caller token could reach the wire only by being the leased credential, which the - // egress gate is what forbids). - assert!( - !value.to_str().unwrap().contains(CALLER_TOKEN), - "the caller's inbound governance token is never the value planned onto the provider dial" - ); - - // And the lane-constant declaration holds: the builder read nothing off the SigningContext (a pure - // function of the credential string), so the boot path may prebuild it once per lane. - assert!( - DECLS.egress_auth_lane_constant, - "the bearer credential is a pure function of the resolved string (lane-constant)" - ); -} diff --git a/crates/busbar-voice/src/tests/governance_budget_tests.rs b/crates/busbar-voice/src/tests/governance_budget_tests.rs deleted file mode 100644 index c17146fb6b..0000000000 --- a/crates/busbar-voice/src/tests/governance_budget_tests.rs +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! GOVERNANCE-BUDGET capability cells (voice-client + voice-server) — GATE-VALID location. -//! -//! OWNER RULING Q21b: a voice session is governed by the kernel's budget view like every other plane. -//! Each closed turn's raw counts per streaming-plane class go to the kernel's session account, which -//! ledgers them and answers `MustClose` (a hard close) the moment the caller's chain reads dry; a -//! caller whose chain is already dry is refused at the open. These cells replace the D2 lease's -//! (whose names they keep, because `qa/capability-equality.json` cites them): the host here is the -//! fixture host with a COUNT cap — each count one unit of the ceiling — since what a count is worth is -//! the read-time view's, proven over the real engine in the composition root's tests. - -use crate::ir::usage::IrDuplexUsage; -use crate::runtime::metering::{TurnMeter, TurnVerdict}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_plane_streaming::session::TurnCounters; -use std::sync::Arc; - -fn caller() -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: "vk-budget".to_string(), - ..Default::default() - } -} - -fn meter(host: &Arc) -> TurnMeter { - TurnMeter::new( - Arc::clone(host) as Arc, - caller(), - "streaming-server", - crate::OPENAI_REALTIME, - ) -} - -/// One turn of `n` emitted audio tokens. -fn turn(n: u64) -> IrDuplexUsage { - IrDuplexUsage { - audio_out: n, - ..IrDuplexUsage::default() - } -} - -/// voice-client cell: a session whose turns dry the caller's chain hard-closes, and stays closed. -#[test] -fn a_session_lease_settled_past_its_cap_hard_closes_and_refuses_further_spend() { - let host = Arc::new(FixtureHost::new().governed().with_count_cap(5)); - let session = meter(&host).open("m").expect("room").expect("governed"); - let quiet = TurnCounters::default(); - assert_eq!( - session.report_turn(Some(&turn(3)), quiet), - TurnVerdict::Live, - "3 of 5" - ); - assert_eq!( - session.report_turn(Some(&turn(3)), quiet), - TurnVerdict::MustClose, - "6 of 5: the chain is dry — a hard close" - ); - assert_eq!( - session.report_turn(Some(&turn(3)), quiet), - TurnVerdict::MustClose, - "past the cap the verdict stays a hard close" - ); - assert_eq!( - host.ledger_usage("vk-budget").map(|u| u.tokens), - Some(9), - "every delivered count is ledgered exactly, none dropped at the close" - ); -} - -/// voice-server cell: the session's counts are ledgered on the PRESENTING key, the turn that dries its -/// chain closes the carrier, and that key's next session is refused at the open. -#[test] -fn the_session_lease_bills_the_presenting_key_and_refuses_past_the_cap_with_a_hard_close() { - let host = Arc::new(FixtureHost::new().governed().with_count_cap(50)); - let session = meter(&host).open("m").expect("room").expect("governed"); - let quiet = TurnCounters::default(); - assert_eq!( - session.report_turn(Some(&turn(20)), quiet), - TurnVerdict::Live - ); - assert_eq!( - session.report_turn(Some(&turn(20)), quiet), - TurnVerdict::Live - ); - assert_eq!( - host.ledger_rows("vk-budget") - .get(&( - "streaming\u{1f}m".to_string(), - "audio_tokens_out".to_string() - )) - .copied(), - Some(40), - "the presenting key's row, under the voice lane and the plane's own class" - ); - assert_eq!( - session.report_turn(Some(&turn(20)), quiet), - TurnVerdict::MustClose, - "60 of 50: a hard close" - ); - assert!( - meter(&host).open("m").is_err(), - "the dry key's next session is refused at the open" - ); -} diff --git a/crates/busbar-voice/src/tests/governed_binding_tests.rs b/crates/busbar-voice/src/tests/governed_binding_tests.rs deleted file mode 100644 index bb6c4e3390..0000000000 --- a/crates/busbar-voice/src/tests/governed_binding_tests.rs +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE BINDING CELLS: a served session and a governed session are the same session. -//! -//! The port and its implementor were both in the tree and no served session had ever been handed one. -//! `SessionCore::with_governed` existed, the composition root's table existed, and between them sat a -//! door that opened every one of its sessions ungoverned — so a client-served tool call's wait was -//! entered by the root and there was nothing on any socket that could wake it or sweep it. These -//! cells judge the join: the composition writes a table, every served open reads it, and what the -//! sweep ends is ended at the deadline the plane itself declares rather than at a second spelling of -//! thirty seconds kept here. -//! -//! The table under test is a stand-in that answers the two questions the port declares, and it -//! answers them with the SAME arithmetic the kernel's own wait table uses — a deadline of -//! `now + TOOL_REPLY_DEADLINE_SECS`, swept when the wall is reached and not one millisecond earlier. -//! The root's own cells judge what the ending then does to a unit; what these judge is that a socket -//! can reach the ending at all, and reach it on time. - -use crate::ir::codec::OpenAiRealtimeCodec; -use crate::runtime::carrier::Carrier; -use crate::runtime::{GovernedCalls, ReplyRefusal, VoiceRuntime}; -use crate::topology::open_admitted_session; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use std::sync::{Arc, Mutex}; - -/// The deadline the plane declares for a tool call the node does not serve, in milliseconds. Read -/// from the plane crate the composition root builds its own reply leg from — one declaration, two -/// readers, so a change to it cannot leave this cell asserting the old number. -fn deadline_ms() -> u64 { - u64::from(busbar_plane_streaming::open_calls::TOOL_REPLY_DEADLINE_SECS) * 1_000 -} - -/// One open call, as the node's table holds it. -#[derive(Debug)] -struct OpenCall { - session: u64, - call_id: String, - /// The wall this call stops waiting at — entered as `now + the plane's declared seconds`, which - /// is the kernel wait table's own arithmetic rather than a second one written here. - deadline: u64, -} - -/// A stand-in for the composition root's open-call table, answering what the port declares and -/// recording what it was asked. -#[derive(Debug, Default)] -struct TableStandIn { - open: Mutex>, - woken: Mutex>, - swept: Mutex>, -} - -impl TableStandIn { - fn enter(&self, session: u64, call_id: &str, now_ms: u64) { - self.open.lock().unwrap().push(OpenCall { - session, - call_id: call_id.to_string(), - deadline: now_ms + deadline_ms(), - }); - } -} - -impl GovernedCalls for TableStandIn { - fn planned(&self, session: u64, call_id: &str, now_ms: u64) -> bool { - self.enter(session, call_id, now_ms); - true - } - - fn replied(&self, session: u64, call_id: &str) -> Result<(), ReplyRefusal> { - let mut open = self.open.lock().unwrap(); - if !open.iter().any(|c| c.session == session) { - return Err(ReplyRefusal::NoSuchSession); - } - let at = open - .iter() - .position(|c| c.session == session && c.call_id == call_id) - .ok_or(ReplyRefusal::UnknownCall)?; - let call = open.remove(at); - self.woken.lock().unwrap().push((session, call.call_id)); - Ok(()) - } - - fn expired(&self, now_ms: u64) -> usize { - let mut open = self.open.lock().unwrap(); - let (done, still): (Vec, Vec) = std::mem::take(&mut *open) - .into_iter() - .partition(|c| c.deadline <= now_ms); - *open = still; - let mut swept = self.swept.lock().unwrap(); - for c in &done { - swept.push((c.session, c.call_id.clone())); - } - done.len() - } -} - -fn runtime() -> VoiceRuntime { - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(crate::runtime::tools::EchoToolExecutor), - ) -} - -/// **A served open takes the binding it is handed, and the sweep on that session reaches the table.** -/// -/// The post-admit open is the one door every served topology goes through — the dialed proxy, the -/// uplink-only fallback and the WebRTC sideband all reach a socket through it — so a binding it -/// carries is a binding all three carry. What is proved here is that the binding survives the open -/// and that the session's own tick lands on the composed table rather than on nothing. -#[test] -fn a_served_open_carries_its_binding_into_the_session_it_opens() { - let rt = runtime(); - let table = Arc::new(TableStandIn::default()); - let governed = crate::runtime::GovernedSession { - session: 4_242, - calls: Arc::clone(&table) as Arc, - }; - table.enter(4_242, "call_x", 0); - - let (core, _handle) = open_admitted_session( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-9", - None, - Carrier::sideband(), - None, - 1, - Some(governed), - ) - .expect("the session opens"); - - assert_eq!( - core.governed_session(), - Some(4_242), - "the session the node knows this pump as is the one the open was handed" - ); - assert_eq!( - core.sweep_expired(deadline_ms()), - 1, - "and the pump's own tick reaches that node's table" - ); - assert_eq!( - table.swept.lock().unwrap().as_slice(), - [(4_242, "call_x".to_string())], - "which is the call this session had open, ended by identifier" - ); -} - -/// **An unanswered call ends at exactly the deadline the plane declares, through the served path.** -/// -/// Not one millisecond early and not on the next tick that happens along. The figure is the plane's -/// own `TOOL_REPLY_DEADLINE_SECS` — the same declaration the composition root builds its reply leg -/// from — and it is read here rather than restated, so the two cannot drift. -#[test] -fn an_unanswered_call_is_swept_at_the_declared_deadline_and_not_before() { - let rt = runtime(); - let table = Arc::new(TableStandIn::default()); - let (core, _handle) = open_admitted_session( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-9", - None, - Carrier::sideband(), - None, - 1, - Some(crate::runtime::GovernedSession { - session: 7, - calls: Arc::clone(&table) as Arc, - }), - ) - .expect("the session opens"); - - table.enter(7, "call_late", 0); - assert_eq!( - core.sweep_expired(deadline_ms() - 1), - 0, - "a call is not ended one millisecond before the deadline its leg declared" - ); - assert_eq!( - core.sweep_expired(deadline_ms()), - 1, - "and it is ended at that deadline, on the tick that reaches it" - ); - assert!( - table.woken.lock().unwrap().is_empty(), - "an ending is not a settlement: nobody answered this call" - ); -} - -/// **Nothing is composed until a composition root composes it.** -/// -/// The plane mounted without the root's own switch keeps what it had: no table, and every session it -/// serves is the ungoverned kind the runtime served before the wait existed. -#[test] -fn a_node_with_no_table_composed_serves_what_it_served_before() { - let rt = runtime(); - let (core, _handle) = open_admitted_session( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-9", - None, - Carrier::sideband(), - None, - 1, - None, - ) - .expect("the session opens"); - assert_eq!(core.governed_session(), None); - assert_eq!(core.sweep_expired(u64::MAX), 0, "and it sweeps nothing"); -} - -/// **The composed table is what a served session-open reads, and each open reads a session of its -/// own.** -/// -/// Two conversations may carry identical call identifiers — a provider mints them per conversation — -/// so the number the root keys its table by has to be unique per session on this node. It is minted -/// here, once per open. -#[test] -fn the_composition_hands_each_served_open_its_own_session() { - assert!( - crate::mount::install_governed_calls( - Arc::new(TableStandIn::default()) as Arc - ), - "the first composition stands" - ); - assert!( - crate::mount::governed_calls_composed(), - "and the door now serves governed sessions" - ); - assert!( - !crate::mount::install_governed_calls( - Arc::new(TableStandIn::default()) as Arc - ), - "a second compose is a no-op, never a silent swap of the table live sessions are keyed into" - ); - - let first = - crate::mount::served_governed_session().expect("a composed node binds every session"); - let second = - crate::mount::served_governed_session().expect("a composed node binds every session"); - assert_ne!( - first.session, second.session, - "two sessions on one node are told apart before either has a call open" - ); -} - -/// **THE ROOT IDENTITY, from this side: no served session-open reaches the ungoverned path.** -/// -/// The cells above prove a binding works when one is handed over. This one proves there is no served -/// call site that declines to hand one — the property that makes "served" and "governed" the same set -/// rather than two sets that happen to overlap. It reads the door's own source because that is where -/// the property lives: the three legs are three call sites in one function, and a fourth added -/// without the binding would be a session nobody could sweep, discovered by a customer. -#[test] -fn every_served_session_open_asks_the_composition_for_its_binding() { - let door = include_str!("../mount.rs"); - let accept = door - .split_once("pub(crate) async fn ws_accept") - .expect("the door is where the served legs are") - .1; - - let opens = accept.matches("open_admitted_session(").count() - + accept.matches("open_admitted_telephony(").count(); - assert_eq!( - opens, 3, - "the served legs are the dialed proxy, the uplink-only fallback and the WebRTC sideband; \ - a fourth needs its own binding and this cell counted {opens}" - ); - let bound = accept.matches("served_governed_session()").count(); - assert_eq!( - bound, opens, - "every served session-open asks the composition for its binding: {bound} of {opens} do" - ); -} diff --git a/crates/busbar-voice/src/tests/governed_table_tests.rs b/crates/busbar-voice/src/tests/governed_table_tests.rs deleted file mode 100644 index f657635aa7..0000000000 --- a/crates/busbar-voice/src/tests/governed_table_tests.rs +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! The node's open-call table as a served session pump reaches it: the pump plans the client-served -//! leg into the table, the client's reply is taken, and a session's teardown frees its rows. - -use std::sync::Arc; - -use busbar_contract::ids::UnitKey; -use busbar_plane_streaming::governed::GovernedCalls; -use busbar_plane_streaming::open_calls::{NodeCalls, OpenToolCalls}; - -use crate::ir::codec::{OpenAiRealtimeCodec, WireEvent}; -use crate::runtime::{Carrier, GovernedSession, SessionCore, ToolExecutor}; - -/// A tool executor that serves no tool: every call the session sees is the client's to answer. -#[derive(Debug)] -struct ServesNoTool; - -#[async_trait::async_trait] -impl ToolExecutor for ServesNoTool { - fn executes_here(&self, _name: &str) -> bool { - false - } - async fn execute(&self, _name: &str, _arguments: &[u8]) -> Vec { - b"{\"executed\":\"in-process\"}".to_vec() - } -} - -/// A real session pump bound to `port`'s table as `session`, serving no tool. -fn pump_serving_no_tool(port: &Arc, session: u64) -> SessionCore { - SessionCore::new( - OpenAiRealtimeCodec, - None, - Arc::new(ServesNoTool), - Carrier::sideband(), - None, - ) - .with_governed(GovernedSession { - session, - calls: Arc::clone(port) as Arc, - }) -} - -/// One OpenAI Realtime wire frame. -fn realtime_frame(json: &serde_json::Value) -> WireEvent { - WireEvent(bytes::Bytes::from( - serde_json::to_vec(json).expect("serializes"), - )) -} - -/// The upstream frames a plan writes, as one string. -fn upstream_text(plan: &crate::runtime::Outbound) -> String { - plan.upstream - .iter() - .map(|w| String::from_utf8_lossy(&w.0).to_string()) - .collect::>() - .join("\n") -} - -/// **Q72(1) / R4: the served session plans the client-served leg, so the client's answer is taken.** -/// -/// The call is announced to a real session pump bound to the node's real table, and the pump is the -/// only thing that can have entered the wait. The tool is one the node does not serve, so nothing is -/// executed in-process; the client's own `function_call_output` for that call is then accepted and -/// carried to the model. Before the writer was wired the table stayed empty and this reply was -/// refused (`refused_reply`). The RED arm: a reply naming a call nobody planned is still refused and -/// reaches the model on no wire. -#[tokio::test] -async fn a_served_session_plans_the_client_leg_and_accepts_its_reply() { - let port = Arc::new(NodeCalls::new(Arc::new(OpenToolCalls::new()), keys())); - let core = pump_serving_no_tool(&port, 5_151); - - for f in [ - serde_json::json!({"type":"response.output_item.added", - "item":{"type":"function_call","call_id":"call_cli","name":"client_tool"}}), - serde_json::json!({"type":"response.function_call_arguments.delta", - "call_id":"call_cli","delta":"{}"}), - serde_json::json!({"type":"response.function_call_arguments.done","call_id":"call_cli"}), - ] { - let plan = core.on_server_frame(realtime_frame(&f)).await; - assert!( - plan.upstream.is_empty(), - "a tool the node does not serve is never executed in-process: {}", - upstream_text(&plan) - ); - } - assert_eq!( - port.table().open(), - 1, - "the pump planned the client-served leg into the node's own table" - ); - - let forged = core.on_client_frame(realtime_frame(&serde_json::json!({ - "type":"conversation.item.create", - "item":{"type":"function_call_output","call_id":"call_unplanned","output":"1"}}))); - assert!( - forged.refused_reply, - "a reply for an unplanned call is refused" - ); - assert!( - forged.upstream.is_empty(), - "and it reaches the model on no wire at all: {}", - upstream_text(&forged) - ); - - let answered = core.on_client_frame(realtime_frame(&serde_json::json!({ - "type":"conversation.item.create", - "item":{"type":"function_call_output","call_id":"call_cli","output":"42"}}))); - assert!( - !answered.refused_reply, - "the client's reply for a planned client-served call is accepted" - ); - let up = upstream_text(&answered); - assert!( - up.contains("function_call_output") - && up.contains("call_cli") - && up.contains("response.create"), - "the accepted reply reaches the model and asks it to continue: {up}" - ); - assert_eq!( - port.table().rows(), - 0, - "and the wait left the table, ending and all" - ); -} - -/// **A served session's teardown forgets its calls.** A session that ends with a client-served call -/// still open leaves nothing behind in the node's table. -#[tokio::test] -async fn a_served_sessions_teardown_forgets_the_calls_it_had_open() { - let port = Arc::new(NodeCalls::new(Arc::new(OpenToolCalls::new()), keys())); - let core = Arc::new(pump_serving_no_tool(&port, 6_161)); - for f in [ - serde_json::json!({"type":"response.output_item.added", - "item":{"type":"function_call","call_id":"call_left","name":"client_tool"}}), - serde_json::json!({"type":"response.function_call_arguments.done","call_id":"call_left"}), - ] { - let _ = core.on_server_frame(realtime_frame(&f)).await; - } - assert_eq!( - port.table().open(), - 1, - "the call is waiting when the session ends" - ); - - let rt = crate::runtime::build_runtime(&(), None) - .downcast::() - .expect("the plane's own runtime"); - let handle = rt.bind_session("acct", "call-teardown"); - handle.open(1).expect("the session's row opens"); - crate::runtime::serve_to_teardown(Arc::clone(&core), handle, async {}, || 2).await; - assert_eq!( - port.table().rows(), - 0, - "the ended session's calls left the table at its teardown" - ); -} - -/// A node allocator for the port under test: unique keys from 1, as the kernel's mint hands them. -fn keys() -> Box UnitKey + Send + Sync> { - let next = std::sync::atomic::AtomicU64::new(1); - Box::new(move || UnitKey::new(next.fetch_add(1, std::sync::atomic::Ordering::Relaxed))) -} diff --git a/crates/busbar-voice/src/tests/hook_gate_tests.rs b/crates/busbar-voice/src/tests/hook_gate_tests.rs deleted file mode 100644 index 627cf345c5..0000000000 --- a/crates/busbar-voice/src/tests/hook_gate_tests.rs +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE HOOKS-GATE CELL — `hooks-gate × {voice-client, voice-server}` (one wiring, both directions). -//! `streams.hooks: [reject-all]` is attached, a session-open is driven through the governed choke -//! point, and it is REFUSED before any lease / mint / dial. The gate fires through the neutral -//! `host.gate_decide` seam (the Seam-B inversion — this plane names no core hook symbol): the host is -//! the substrate's in-memory fixture host carrying a scripted gate under the plane's own decl key and -//! `streams` container, so the plane's `gate_attached` / `gate_decide` legs run exactly as they do over -//! a configured deployment. (The same verdict through the kernel's hook port is the engine's own -//! hook battery to prove; this plane's tests do not link the engine.) -//! -//! The control makes the refusal falsifiable: the identical open with NOTHING attached proceeds past -//! the gate (the byte-identical-when-unconfigured guarantee, exercised). -//! -//! RED before the wiring: `open_governed` never consulted the gate, so `reject-all` served the open. - -use crate::mount::{open_governed, GovernedOpen, Ingress}; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; -use crate::testkit::fixture_host::{FixtureHost, GateScript}; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use busbar_kernel::plane_host::{EngineHost, GateOutcome}; -use std::sync::Arc; - -/// The `streams:` container the voice plane files its operator hooks under. -const GATE_CONTAINER: &str = "streams"; - -/// A gate whose `raw_decide_reply` drives its verdict verbatim — the same reply shape the kernel's -/// hook double reads off its settings: `{"reject": {"status", "message"}}` refuses, anything -/// else proceeds. -fn gate(settings: serde_json::Value) -> GateScript { - let reply = settings["raw_decide_reply"].clone(); - Arc::new(move |_args_json: &[u8]| match reply.get("reject") { - Some(reject) => GateOutcome::Reject { - status: reject["status"].as_u64().unwrap_or(403) as u16, - message: reject["message"].as_str().unwrap_or_default().to_string(), - hook: "test-hook".to_string(), - }, - None => GateOutcome::Proceed, - }) -} - -fn runtime() -> VoiceRuntime { - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), - ) -} - -/// A host with nothing attached: the open proceeds past the gate untouched. -fn ungated_host() -> Arc { - FixtureHost::new().into_host() -} - -/// Build a host whose `voice`/`streams` container carries the attached gate, filed under the plane's -/// own decl key exactly where production's resolved gate map puts it, so `gate_attached` answers true -/// and `gate_decide` runs the gate. `hook_name` is the operator's name for the hook (it only labels -/// the attachment here). -fn gated_host(_hook_name: &'static str, script: GateScript) -> Arc { - FixtureHost::new() - .attach_gate(crate::PLANE_DECLARATION.key, GATE_CONTAINER, script) - .into_host() -} - -fn an_open<'a>(rt: &'a VoiceRuntime, host: Arc) -> GovernedOpen<'a> { - GovernedOpen { - rt, - host, - provider: None, - // The `ek_` MINT one-shot pass — a LIVE `open_governed` production ingress (browser WebRTC), - // so this cell proves the gate on a path production actually takes. The WS-accept front door - // (sideband + telephony) runs the SAME `hook_gate` before the upgrade; that production path is - // proven by `a_reject_all_operator_gate_refuses_a_ws_accept_before_the_upgrade` below. - ingress: Ingress::Mint, - owner: "acct".to_string(), - call_id: "call-gate".to_string(), - vkey: None, - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 1, - } -} - -#[tokio::test] -async fn streams_hooks_reject_all_refuses_a_session_open() { - let rt = runtime(); - - // ── THE CONTROL: nothing attached ⇒ the open proceeds PAST the gate (byte-identical, no gate hop). - let control = open_governed(an_open(&rt, ungated_host())).await; - assert_eq!( - control.status(), - axum::http::StatusCode::NOT_IMPLEMENTED, - "with no gate attached the session-open proceeds past the gate to the governed open" - ); - - // ── THE TEST: `streams.hooks: [reject-all]`, same open, REFUSED before any lease/mint/dial. ────── - let host = gated_host( - "reject-all", - gate(serde_json::json!({ - "raw_decide_reply": {"reject": {"status": 403, "message": "no voice session today"}} - })), - ); - - let refused = open_governed(an_open(&rt, host)).await; - assert_eq!( - refused.status(), - axum::http::StatusCode::FORBIDDEN, - "`streams.hooks: [reject-all]` must REFUSE the session-open before any lease/mint/dial" - ); - let body = axum::body::to_bytes(refused.into_body(), usize::MAX) - .await - .expect("the refusal body reads"); - assert_eq!( - String::from_utf8_lossy(&body), - "no voice session today", - "the hook's OWN message reaches the caller, so an operator can tell WHICH control refused" - ); -} - -/// The plane's real dispatch slot, built the way `appbuild` does — a `BuildCtx` over a `public_url`. -fn a_slot() -> Arc { - let unit = (); - let ctx = busbar_kernel::plane::registry::BuildCtx { - endpoint_slot: None, - agent_defs: &unit, - tool_defs: &unit, - public_url: Some("https://voice.example"), - prior: None, - }; - crate::mount::voice_build(&ctx).expect("voice_build yields a slot for a public_url") -} - -/// Drive the REAL `ws_accept` over a loopback WS server with `host`, and return the client handshake -/// outcome: `Ok` on a 101 upgrade (the gate proceeded), `Err` on a pre-upgrade refusal (the gate -/// rejected — a 403, no socket bound). A WS upgrade cannot be forged off a live connection -/// (`ConnectionNotUpgradable`), so the accept fn must be driven through a real server + client. -async fn ws_accept_handshake(host: Arc) -> Result<(), ()> { - #[derive(Clone)] - struct S { - host: Arc, - slot: Arc, - } - async fn route( - axum::extract::State(s): axum::extract::State, - upgrade: axum::extract::ws::WebSocketUpgrade, - ) -> axum::response::Response { - let arrival = busbar_kernel::ingress::duplex_ws::WsArrival { - upgrade, - gov: None, - principal: None, - caller_principal: Some("acct".to_string()), - path: "/telephony/call-ws".to_string(), - uri: axum::http::Uri::from_static("/telephony/call-ws"), - headers: axum::http::HeaderMap::new(), - path_params: vec![("call_id".to_string(), "call-ws".to_string())], - host: s.host, - slot: s.slot, - }; - crate::mount::ws_accept( - arrival, - Ingress::Telephony, - crate::ir::codec::OpenAiRealtimeCodec, - ) - .await - } - let app = axum::Router::new() - .route("/telephony/call-ws", axum::routing::get(route)) - .with_state(S { - host, - slot: a_slot(), - }); - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - tokio::spawn(async move { - axum::serve(listener, app).await.unwrap(); - }); - let policy = busbar_kernel::net_guard::GuardPolicy { - allow_private: true, - allow_plaintext: true, - ..busbar_kernel::net_guard::GuardPolicy::default() - }; - match busbar_kernel::egress::duplex_ws::dial(&format!("ws://{addr}/telephony/call-ws"), policy) - .await - { - Ok(_) => Ok(()), - Err(_) => Err(()), - } -} - -/// THE WS-ACCEPT FRONT DOOR honors the operator gate — telephony is the sharp case (it has NO preceding -/// `ek_` mint pass, so before this wiring it reached the media leg screened by nothing but the -/// destination gauntlet). A `reject-all` gate REFUSES the session-open BEFORE the socket upgrades, so -/// the client handshake FAILS (a pre-upgrade 403, no socket, no session). The control (no gate) -/// upgrades (101), making the refusal falsifiable. -#[tokio::test] -async fn a_reject_all_operator_gate_refuses_a_ws_accept_before_the_upgrade() { - assert!( - ws_accept_handshake(ungated_host()).await.is_ok(), - "with no gate attached the telephony WS-accept proceeds past the gate and upgrades (101)" - ); - - let host = gated_host( - "reject-all", - gate(serde_json::json!({ - "raw_decide_reply": {"reject": {"status": 403, "message": "no voice session today"}} - })), - ); - assert!( - ws_accept_handshake(host).await.is_err(), - "a reject-all operator gate REFUSES a telephony WS-accept BEFORE the socket upgrades — telephony \ - has no `ek_` mint pass, so the operator gate on this front door is its ONLY request screening" - ); -} diff --git a/crates/busbar-voice/src/tests/hook_tap_tests.rs b/crates/busbar-voice/src/tests/hook_tap_tests.rs deleted file mode 100644 index e4cc68838f..0000000000 --- a/crates/busbar-voice/src/tests/hook_tap_tests.rs +++ /dev/null @@ -1,412 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE HOOKS-TAP CELL — `hooks-tap × {voice-client, voice-server}` (one wiring, both directions). -//! `streams.hooks: [rewrite]` with a `prompt: rw` gate is attached, a session-open is driven through -//! the governed choke point, and the params THE PROVIDER RECEIVES are the ones the hook rewrote them -//! to — asserted on the ACTUAL mint request the loopback provider saw (a rewrite is only real if the -//! thing downstream saw the rewritten value). The tap fires through the neutral `host.transform_over` -//! seam, after the gate and before the credential is leased. The host is the substrate's in-memory -//! fixture host carrying a scripted rewrite under the plane's own decl key and `streams` container, so -//! the plane's `tap_attached` / `transform_over` legs run exactly as they do over a configured -//! deployment. (The same rewrite over the real loaded hook plugin is the engine's own hook battery to -//! prove; this plane's tests do not link the engine.) -//! -//! ## The control is the byte-identical guarantee, exercised -//! -//! The identical open with the rewrite hook REMOVED must reach the provider carrying the plane's -//! ORIGINAL locked params, byte for byte — the no-op-absent-hooks guarantee. -//! -//! RED before the wiring: `open_governed` never ran the transform, so the provider always saw the -//! plane's own params and no rewrite could change a byte. - -use crate::ir::config::SessionConfig; -use crate::mount::{open_governed, GovernedOpen, Ingress, ProviderEndpoint}; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; -use crate::testkit::fixture_host::{FixtureHost, RewriteScript}; -use crate::testkit::loopback_http::{MockResponse, MockServer, MockServerState}; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use busbar_kernel::plane_host::{EngineHost, TransformVerdict}; -use std::sync::Arc; - -/// The `streams:` container the voice plane files its operator hooks under. -const GATE_CONTAINER: &str = "streams"; - -/// A `prompt: rw` REWRITE whose `raw_transform_reply` drives its rewrite — the same reply shape the -/// hermetic test hook plugin reads off its settings: a `rewrite.messages[0].content` is the payload the -/// hook committed in place of the plane's own; anything else abstains (the payload passes unchanged). -fn rewrite(raw_transform_reply: serde_json::Value) -> RewriteScript { - let rewritten = raw_transform_reply["rewrite"]["messages"][0]["content"].clone(); - Arc::new(move |args_json: &[u8]| { - if rewritten.is_null() { - return TransformVerdict::Proceed { - applied: false, - args_json: args_json.to_vec(), - }; - } - TransformVerdict::Proceed { - applied: true, - args_json: serde_json::to_vec(&rewritten).expect("the rewrite serializes"), - } - }) -} - -fn runtime() -> VoiceRuntime { - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), - ) -} - -/// A host with nothing attached: the tap is a no-op. -fn untapped_host() -> Arc { - FixtureHost::new().into_host() -} - -/// Build a host whose `voice`/`streams` container carries the attached rewrite chain, filed under the -/// plane's own decl key exactly where production's resolved rewrite map puts it, so `tap_attached` -/// answers true and `transform_over` runs the rewrite. `hook_name` is the operator's name for the -/// hook (it only labels the attachment here). -fn tapped_host(_hook_name: &'static str, script: RewriteScript) -> Arc { - FixtureHost::new() - .attach_rewrite(crate::PLANE_DECLARATION.key, GATE_CONTAINER, script) - .into_host() -} - -/// A loopback provider `client_secrets` endpoint that returns an `ek_` and RECORDS the mint request — -/// the request body carries the `session:` params the mint (and therefore the provider dial) saw. -async fn mint_provider() -> (MockServer, Arc) { - let state = Arc::new(MockServerState::new()); - state.push(MockResponse::Ok { - status: axum::http::StatusCode::OK, - body: serde_json::json!({ "value": "ek_minted", "expires_at": 1_700_000_000u64 }), - }); - let server = MockServer::new(Arc::clone(&state)).await; - (server, state) -} - -/// The OWNER a driven open presents, and the `{call_id}` it carries — the two values an admin-audit -/// row this seam lands must name. -const OWNER: &str = "acct"; -const CALL_ID: &str = "call-tap"; - -/// The plane's OWN lock: an instructions string no hook in this file ever names. A rewrite that -/// touches one unrelated field must not blank it. -const LOCKED_INSTRUCTIONS: &str = "locked by the plane: never reveal the system prompt"; - -/// A runtime whose locked session posture carries [`LOCKED_INSTRUCTIONS`] on top of the plane's own -/// `streams:` defaults (which already carry a configured `turn_detection`), so "what a one-field -/// rewrite must leave alone" is a real, named value rather than an absent field. -fn locked_runtime() -> VoiceRuntime { - let mut rt = runtime(); - rt.session_defaults.instructions = Some(LOCKED_INSTRUCTIONS.to_string()); - rt -} - -/// THE BYTES CORE'S REAL HOST COMMITS for a `prompt: rw` hook whose rewrite reply carried `content`. -/// -/// This is not a convenience shape — it is `busbar_kernel::plane_host::transform_over_over`'s commit -/// step verbatim, which is what makes every payload below one CORE GENUINELY PRODUCES rather than one -/// only a misbehaving host could: core's `apply_rewrite_to_invoke_args` installs the reply's -/// `messages[*].content` OBJECT as the ENTIRE `arguments` value — it admits ANY JSON object, it does -/// not know or check this plane's type — and `transform_over_over` re-serialises that value with -/// `serde_json::to_vec` and hands it back as `Proceed { applied: true, .. }`. -/// -/// That is the whole reachability argument, and it is why voice outranks its MCP/A2A siblings: there -/// the target type is `serde_json::Value`, so bytes core commits always parse and the unreadable arm -/// is dead. Here the target is `SessionConfig`, which rejects a type mismatch and an audio format the -/// plane does not speak — so core can, and does, commit bytes the apply site cannot read. -/// `plane_host/tests/mod_tests.rs::a_committed_invoke_rewrite_installs_any_json_object_verbatim` pins -/// core's half of that sentence at core's own function. -fn commits(content: serde_json::Value) -> RewriteScript { - rewrite(serde_json::json!({ - "rewrite": { "messages": [ { "role": "user", "content": content } ] } - })) -} - -/// A fixture host carrying the attached rewrite chain, kept as the CONCRETE type so the cell can read -/// the admin-audit log back off the same host the plane audited through. -fn tapped_fixture(script: RewriteScript) -> Arc { - Arc::new(FixtureHost::new().attach_rewrite( - crate::PLANE_DECLARATION.key, - GATE_CONTAINER, - script, - )) -} - -/// Drive one `Mint` open through the governed choke point against the loopback provider and return -/// the RAW answer. The refusal cells assert on its STATUS; the rewrite cells read the provider's -/// recorded request instead (a rewrite is only real if the thing downstream saw it). -async fn open_mint( - rt: &VoiceRuntime, - host: Arc, - base_url: &str, -) -> axum::response::Response { - let provider = ProviderEndpoint { - base_url: base_url.to_string(), - api_key: "sk-real-key".to_string(), - }; - open_governed(GovernedOpen { - rt, - host, - provider: Some(&provider), - ingress: Ingress::Mint, - owner: OWNER.to_string(), - call_id: CALL_ID.to_string(), - vkey: None, - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 1, - }) - .await -} - -/// Drive one `Mint` open through the governed choke point against the loopback provider, and return -/// the `session:` object the mint request carried. -async fn minted_session( - host: Arc, - base_url: &str, - state: &MockServerState, -) -> serde_json::Value { - let rt = runtime(); - let resp = open_mint(&rt, host, base_url).await; - assert_eq!( - resp.status(), - axum::http::StatusCode::OK, - "the mint pass serves" - ); - let raw = state - .get_last_request_body() - .expect("the mint sent a request body"); - let sent: serde_json::Value = serde_json::from_slice(&raw).expect("the mint body is json"); - sent["session"].clone() -} - -#[tokio::test] -async fn no_hook_leaves_the_params_byte_identical() { - let (server, state) = mint_provider().await; - // Nothing attached ⇒ the tap is a no-op and the provider sees the plane's ORIGINAL locked params. - let session = minted_session(untapped_host(), &server.base_url(), &state).await; - assert_eq!( - session, - serde_json::to_value(runtime().session_defaults).unwrap(), - "with no rewrite hook the provider must receive the plane's locked params BYTE FOR BYTE — the \ - no-op-absent-hooks guarantee" - ); - server.shutdown().await; -} - -#[tokio::test] -async fn a_rewrite_hook_edits_the_session_open_params_before_the_provider_dial() { - let (server, state) = mint_provider().await; - - // The rewrite REPLACES the session-open params with a hook-authored posture — a distinct - // instructions string the plane default never carries, so its presence at the provider is proof - // the rewrite reached the wire. - let rewritten: SessionConfig = SessionConfig { - instructions: Some("rewritten-by-hook".to_string()), - voice: Some("marin".to_string()), - ..SessionConfig::default() - }; - let host = tapped_host( - "rewrite", - rewrite(serde_json::json!({ - "rewrite": { - "messages": [ - { "role": "user", "content": serde_json::to_value(&rewritten).unwrap() } - ] - } - })), - ); - - let session = minted_session(host, &server.base_url(), &state).await; - assert_eq!( - session["instructions"], "rewritten-by-hook", - "the provider mint must carry the params THE HOOK REWROTE THEM TO. The plane's own \ - instructions here would be the whole finding: the rewrite fired in memory but never reached \ - the wire. Session the provider saw: {session}" - ); - server.shutdown().await; -} - -/// THE FAIL-OPEN, DRIVEN END TO END. A `prompt: rw` hook COMMITS a rewrite (`applied: true`) whose -/// output busbar cannot read back as a [`SessionConfig`]. The apply site read it with -/// `if let Ok(v) = serde_json::from_slice(…)`, so the committed rewrite fell through to `Ok(None)` — -/// which both callers read as "the hook made no rewrite" — and the session opened carrying the -/// plane's OWN params. -/// -/// RED BEFORE THE FIX, on exactly this text: the mint pass answered `200` and the loopback provider -/// received the plane's original locked session params. A redaction hook's whole purpose is that the -/// thing it removed does not reach the backend; here it reached the backend and nothing said so. -/// -/// Both payloads are ones CORE GENUINELY COMMITS (see [`commits`]): a type mismatch, and an audio -/// format the plane does not speak. Neither is a misbehaving host — `apply_rewrite_to_invoke_args` -/// admits any JSON object, and this plane's target type is not `serde_json::Value`. -/// -/// The assertions are all three halves of the refusal: the caller is answered `500`, THE PROVIDER IS -/// NEVER DIALED (the un-rewritten params reach nothing), and the refusal is on the admin-audit trail -/// under the plane's own session-open action with outcome `rejected`. -#[tokio::test] -async fn a_committed_rewrite_busbar_cannot_read_back_refuses_the_session_open() { - for (why, content) in [ - ( - "a type mismatch: `voice` is a string on this type", - serde_json::json!({ "voice": 7 }), - ), - ( - "an audio format the plane does not speak", - serde_json::json!({ "input_audio_format": "flac" }), - ), - ] { - let (server, state) = mint_provider().await; - let fx = tapped_fixture(commits(content.clone())); - let rt = locked_runtime(); - let resp = open_mint( - &rt, - Arc::clone(&fx) as Arc, - &server.base_url(), - ) - .await; - - assert_eq!( - resp.status(), - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "a committed rewrite busbar cannot read back must REFUSE the session-open. A 200 here is \ - the whole finding: the hook said it replaced these params and busbar opened the session \ - with the plane's own instead. Payload ({why}): {content}" - ); - assert!( - state.get_last_request_body().is_none(), - "the refusal must land BEFORE the provider is dialed — the params the hook said it had \ - replaced must reach nothing. Payload ({why}): {content}" - ); - assert!( - fx.audit_log() - .iter() - .any(|e| e.action == crate::mount::SESSION_AUDIT_ACTION - && e.resource == format!("streaming:{CALL_ID}") - && e.outcome == "rejected" - && e.principal == OWNER), - "the refusal must be ON THE AUDIT TRAIL under the plane's own session-open action — a \ - hook that ran, reported applied, and was refused is an operator-visible event, not a \ - silent one. Payload ({why}): {content}. Log: {:?}", - fx.audit_log() - ); - server.shutdown().await; - } -} - -/// THE SECOND FAIL-OPEN ON THE SAME SEAM, pointed the other way. A committed rewrite that DOES -/// deserialize still went through as a WHOLESALE REPLACEMENT, so `{"voice": "marin"}` parsed into a -/// config whose every other field was the serde default — and the plane's locked `instructions` (and -/// its configured `turn_detection`) were blanked by a rewrite that never named them. -/// -/// RED BEFORE THE FIX, on exactly this text: the provider received a session carrying `voice: marin` -/// and NO `instructions` at all. -/// -/// The assertion is the exact whole object, not just the two interesting keys: a rewrite that names -/// one field must move EXACTLY that field. `SessionConfig`'s own wire contract is patch semantics -/// ("a partial `session.update` patches only what it names"), and this is that contract honored at -/// the one place a hook can write the config. -#[tokio::test] -async fn a_rewrite_touching_one_field_leaves_the_rest_of_the_locked_params_intact() { - let (server, state) = mint_provider().await; - let fx = tapped_fixture(commits(serde_json::json!({ "voice": "marin" }))); - let rt = locked_runtime(); - let resp = open_mint( - &rt, - Arc::clone(&fx) as Arc, - &server.base_url(), - ) - .await; - assert_eq!( - resp.status(), - axum::http::StatusCode::OK, - "a readable one-field rewrite still serves" - ); - - let raw = state - .get_last_request_body() - .expect("the mint sent a request body"); - let sent: serde_json::Value = serde_json::from_slice(&raw).expect("the mint body is json"); - let session = sent["session"].clone(); - - // The locked posture with EXACTLY the one field the hook named moved. - let mut expected = locked_runtime().session_defaults; - expected.voice = Some("marin".to_string()); - assert_eq!( - session, - serde_json::to_value(&expected).unwrap(), - "a rewrite that names ONE field must move exactly that field. The plane's locked \ - instructions ({LOCKED_INSTRUCTIONS:?}) and its configured turn_detection are not the hook's \ - to blank by omission — an absent key is the wire's \"say nothing\", not \"clear it\". \ - Session the provider saw: {session}" - ); - server.shutdown().await; -} - -/// THE RULE ITSELF, driven directly: the shapes a behavioural cell cannot cheaply arrange, plus the -/// two identities the merge has to preserve to be safe to put in front of EVERY committed rewrite. -/// -/// `committed_session_config` is its own function precisely so this is possible — the plane cannot -/// make a host misbehave, and the rule the plane owns is what it does when one does. -#[test] -fn committed_session_config_refuses_what_it_cannot_use_and_patches_what_it_can() { - use crate::mount::committed_session_config as read_back; - let locked = locked_runtime().session_defaults; - - // (1) NOT JSON AT ALL. `Ok(None)` here meant "the hook made no rewrite", which is the lie. - assert!( - read_back(&locked, b"not json").is_err(), - "bytes that are not JSON are not session params" - ); - - // (2) WELL-FORMED JSON THAT IS NOT AN OBJECT. The value is SUBSTITUTED for the plane's locked - // config, and these params are an object on every wire the plane speaks; admitting `7` would - // mean telling the hook its rewrite landed and then opening a session with a number. - for scalar in [&b"7"[..], br#""marin""#, b"null", b"[]", b"true"] { - assert!( - read_back(&locked, scalar).is_err(), - "a JSON scalar is not a session-params object: {}", - String::from_utf8_lossy(scalar) - ); - } - - // (3) AN OBJECT THAT PARSES AS JSON BUT NOT AS THIS TYPE — the merge happens BEFORE the decode, - // so a patch naming one bad field refuses the open instead of being quietly dropped. - for bad in [ - &br#"{"output_audio_format":"flac"}"#[..], - br#"{"voice":7}"#, - br#"{"modalities":"audio"}"#, - ] { - assert!( - read_back(&locked, bad).is_err(), - "an object this type rejects must refuse, not fall through: {}", - String::from_utf8_lossy(bad) - ); - } - - // (4) THE NO-OP IDENTITY. A patch that names nothing moves nothing. This is what makes the merge - // safe in front of every committed rewrite: the locked params must survive a round trip - // through their own JSON projection UNCHANGED, three-state `turn_detection`, wire-named audio - // formats and `max_output_tokens`' `"inf"` sentinel included. - assert_eq!( - read_back(&locked, b"{}").expect("an empty patch is readable"), - locked, - "an empty patch must be the identity — if the projection round trip loses a field, every \ - one-field rewrite silently drops it too" - ); - - // (5) CLEARING STAYS EXPRESSIBLE. A patch's "say nothing" is ABSENCE, not `null`, so a hook that - // genuinely means to remove the plane's instructions can still say so by NAMING the key. The - // merge narrows silence, not intent. - let cleared = read_back(&locked, br#"{"instructions":null}"#).expect("an explicit null reads"); - assert_eq!( - cleared.instructions, None, - "naming a key with an explicit null CLEARS it — the merge must not become a lock-in" - ); - assert_eq!( - cleared.turn_detection, locked.turn_detection, - "...while the keys the patch did not name keep the plane's locked values" - ); -} diff --git a/crates/busbar-voice/src/tests/metering_lease_tests.rs b/crates/busbar-voice/src/tests/metering_lease_tests.rs deleted file mode 100644 index ff6c748de1..0000000000 --- a/crates/busbar-voice/src/tests/metering_lease_tests.rs +++ /dev/null @@ -1,211 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE SERVED ROUTE ASKS THE KERNEL'S BUDGET VIEW, AND A SERVED SESSION LEDGERS THE PLANE'S COUNTS. -//! -//! OWNER RULING Q21b replaced the per-key D2 lease with the kernel's session account. Two facts, -//! proven through the mounted open (`open_governed`) rather than the runtime alone: -//! -//! 1. THE GATE IS THE KERNEL'S. A presenting key whose chain the kernel's budget view reads dry is -//! refused `402` before any session opens; the same key with room is served. -//! 2. THE COUNTS ARE THE PLANE'S. A turn of the session the route opened lands on the presenting key's -//! ledger as the streaming plane's classes, under the voice lane. - -use crate::ir::codec::WireEvent; -use crate::mount::{open_governed, GovernedOpen, Ingress}; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use busbar_kernel::plane_host::EngineHost; -use std::sync::Arc; - -fn key() -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: "vk-voice-session".to_string(), - name: "streaming-session".to_string(), - ..Default::default() - } -} - -/// The runtime every test here opens through, over `engine`. -fn runtime(engine: Arc) -> VoiceRuntime { - VoiceRuntime::new(engine, Arc::new(EchoToolExecutor)) -} - -async fn open(host: &Arc, rt: &VoiceRuntime, call_id: &str) -> axum::http::StatusCode { - open_governed(GovernedOpen { - rt, - host: Arc::clone(host) as Arc, - provider: None, - ingress: Ingress::Mint, - owner: "acct-meter".to_string(), - call_id: call_id.to_string(), - vkey: Some(key()), - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 5, - }) - .await - .status() -} - -#[tokio::test] -async fn a_served_open_is_refused_when_the_kernel_reads_the_chain_dry() { - let rt = runtime(Arc::new(DurableHandleEngine::new())); - let dry = Arc::new(FixtureHost::new().governed().with_count_cap(0)); - assert_eq!( - open(&dry, &rt, "call-dry").await, - axum::http::StatusCode::PAYMENT_REQUIRED, - "a dry chain is refused before any session opens" - ); - let room = Arc::new(FixtureHost::new().governed().with_count_cap(1_000)); - assert_eq!( - open(&room, &rt, "call-room").await, - axum::http::StatusCode::NOT_IMPLEMENTED, - "the same key with room is served (nothing to dial here)" - ); -} - -#[tokio::test] -async fn a_served_sessions_turn_lands_the_planes_counts_on_the_presenting_key() { - let host = Arc::new(FixtureHost::new().governed()); - let rt = crate::runtime::build_runtime_hosted( - &runtime(Arc::new(DurableHandleEngine::new())), - Arc::clone(&host) as Arc, - ); - let meter = crate::runtime::TurnMeter::new( - Arc::clone(&host) as Arc, - key(), - "streaming-server", - crate::OPENAI_REALTIME, - ); - let (core, _handle) = crate::topology::begin_session( - &rt, - crate::ir::codec::OpenAiRealtimeCodec, - "acct-meter", - "call-turn", - None, - crate::runtime::Carrier::sideband(), - Some(meter), - 5, - ) - .expect("the session opens"); - let done = serde_json::json!({ - "type": "response.done", - "response": { "usage": { - "input_token_details": { "audio_tokens": 120 }, - "output_token_details": { "audio_tokens": 80 }, - }}, - }); - let _ = core - .on_server_frame(WireEvent(bytes::Bytes::from( - serde_json::to_vec(&done).unwrap(), - ))) - .await; - let rows = host.ledger_rows(&key().id); - let lane = "streaming\u{1f}openai_realtime".to_string(); - assert_eq!( - rows.get(&(lane.clone(), "audio_tokens_in".to_string())), - Some(&120) - ); - assert_eq!(rows.get(&(lane, "audio_tokens_out".to_string())), Some(&80)); - assert_eq!(rows.len(), 2, "only the classes the turn carried"); -} - -/// #47 `streams.fees.per_session` (OWNER RULING Q32): each session the kernel's account opens and -/// serves keeps ONE session on the presenting key's budget book — through the one metering path, a -/// count and never a figure — and a refused open counts none. An open the route could not serve (no -/// provider composed here, so the mint answers `501`) is a failed open: its fee is given back (TODO -/// 17(b)), so the book keeps none for it. What a session costs is the kernel's read of the plane's -/// fees. -#[tokio::test] -async fn each_served_session_keeps_one_session_and_a_failed_or_refused_open_keeps_none() { - let rt = runtime(Arc::new(DurableHandleEngine::new())); - let sessions = |host: &FixtureHost| host.ledger_usage(&key().id).map_or(0, |u| u.sessions); - let room = Arc::new(FixtureHost::new().governed().with_count_cap(1_000)); - open(&room, &rt, "call-one").await; - assert_eq!( - sessions(&room), - 0, - "a mint with no provider served nothing, so its fee is back" - ); - let hosted = - crate::runtime::build_runtime_hosted(&rt, Arc::clone(&room) as Arc); - for call in ["call-two", "call-three"] { - let meter = crate::runtime::TurnMeter::new( - Arc::clone(&room) as Arc, - key(), - "streaming-server", - crate::OPENAI_REALTIME, - ); - let (core, _handle) = crate::topology::begin_session( - &hosted, - crate::ir::codec::OpenAiRealtimeCodec, - "acct-meter", - call, - None, - crate::runtime::Carrier::sideband(), - Some(meter), - 5, - ) - .expect("the session opens"); - crate::runtime::session::serve_with_sweep(core, async {}).await; - } - assert_eq!(sessions(&room), 2, "two served sessions, two counts"); - let dry = Arc::new(FixtureHost::new().governed().with_count_cap(0)); - open(&dry, &rt, "call-dry").await; - assert_eq!(sessions(&dry), 0, "a refused open counts nothing"); -} - -/// TODO 17(b) (ARCHITECT R4): a session whose durable open fails never opened, so it keeps no -/// `fees.per_session`. The kernel's account counted the fee AT THE OPEN, under the same dry check and -/// before the durable open; the failed durable open gives that count back on the budget book, exactly -/// once, and the metering row keeps it. A kept session served beforehand on the same key proves the -/// refund takes back one fee and not two. RED with the fee counted at `served()`: the open counted -/// nothing, so the metering row read the kept session alone. -#[tokio::test] -async fn a_failed_durable_open_gives_back_its_session_fee() { - let host = Arc::new(FixtureHost::new().governed().with_count_cap(1_000)); - let healthy = crate::runtime::build_runtime_hosted( - &runtime(Arc::new(DurableHandleEngine::new())), - Arc::clone(&host) as Arc, - ); - let meter = crate::runtime::TurnMeter::new( - Arc::clone(&host) as Arc, - key(), - "streaming-server", - crate::OPENAI_REALTIME, - ); - let (core, handle) = crate::topology::begin_session( - &healthy, - crate::ir::codec::OpenAiRealtimeCodec, - "acct-meter", - "call-kept", - None, - crate::runtime::Carrier::sideband(), - Some(meter), - 5, - ) - .expect("the kept session opens"); - crate::runtime::session::serve_with_sweep(core, async {}).await; - handle.finish(5); - - let engine = Arc::new(DurableHandleEngine::new()); - engine.set_sink(Arc::new(super::mount_tests::MemStore::down())); - let rt = runtime(engine); - let status = open(&host, &rt, "call-down").await; - assert!( - status.is_server_error(), - "a durable open that cannot land is refused, got {status}" - ); - assert_eq!( - host.session_rows(&key().id), - 2, - "the failed open counted its session fee at the open, under the dry check" - ); - assert_eq!( - host.ledger_usage(&key().id).map_or(0, |u| u.sessions), - 1, - "the failed durable open's fee is given back exactly once; the kept session's stays" - ); -} diff --git a/crates/busbar-voice/src/tests/metrics_tests.rs b/crates/busbar-voice/src/tests/metrics_tests.rs deleted file mode 100644 index 071e76be62..0000000000 --- a/crates/busbar-voice/src/tests/metrics_tests.rs +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE FRONT-DOOR SESSION-OPEN REPORT — the `metrics × voice-server` cell (item 566). A governed -//! session-open is reported through the host's `request_finished` seam with the plane's own key, its -//! ingress dialect, the front-door pool and its outcome — AND the elapsed duration. On the engine's host -//! that call lands the `busbar_plane_requests_total{plane="voice"}` count (the same family and labels -//! this door used to emit by hand) and the `busbar_plane_request_duration_seconds{plane="voice"}` sample -//! the hand emit never recorded; the fixture host records the call itself. Revert `finish` to the -//! hand-emitted counter and the host is told nothing — the assertion fails. (The name is the one -//! `qa/capability-equality.json` cites for this cell; the report carries the count and the duration.) - -use crate::mount::{open_governed, GovernedOpen, Ingress}; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use std::sync::Arc; - -#[tokio::test] -async fn a_voice_session_open_increments_the_plane_labelled_counter() { - let host = Arc::new(FixtureHost::new()); - let rt = VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), - ); - - // A governed session-open through the front door (no provider configured ⇒ the WS-accept leg - // answers 501, but the front-door request is REPORTED all the same). - let resp = open_governed(GovernedOpen { - rt: &rt, - host: Arc::clone(&host) as Arc, - provider: None, - ingress: Ingress::Sideband, - owner: "acct".to_string(), - call_id: "call-metrics".to_string(), - vkey: None, - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 1, - }) - .await; - assert_eq!(resp.status(), axum::http::StatusCode::NOT_IMPLEMENTED); - - let reported = host.finished_requests(); - assert_eq!(reported.len(), 1, "one front-door request, reported once"); - let r = &reported[0]; - assert_eq!( - ( - r.plane.as_str(), - r.ingress_protocol.as_str(), - r.pool.as_str() - ), - (crate::PLANE_KEY, crate::OPENAI_REALTIME, "streaming-server"), - "the labels the hand-emitted counter carried" - ); - assert_eq!( - r.outcome, - busbar_kernel::telemetry::outcome_of(501), - "the outcome of the answer the caller got" - ); - assert!( - r.seconds.is_finite() && r.seconds >= 0.0, - "a duration sample rides the same report: {}", - r.seconds - ); -} diff --git a/crates/busbar-voice/src/tests/mint_tests.rs b/crates/busbar-voice/src/tests/mint_tests.rs deleted file mode 100644 index a4d85c0f2f..0000000000 --- a/crates/busbar-voice/src/tests/mint_tests.rs +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE EPHEMERAL-SECRET MINT, SERVED — the browser `ek_` pass with a composed provider credential. -//! -//! Two halves, both load-bearing: -//! -//! 1. THE COMPOSITION SEAM. The composition root hands the plane a provider ORIGIN and the secret -//! REFERENCE the deployment already declared for that provider, plus the deployment's own secret -//! resolver. The plane resolves the reference through that seam and composes the endpoint — so a -//! deployment's realtime credential arrives the same way every other provider key does, and the -//! `streams:` grammar gains no credential field. -//! 2. THE SERVED MINT. With a provider composed, the mint pass no longer answers "no provider -//! composed": it dials the provider's client-secrets endpoint under busbar's OWN key and answers -//! `200` with the browser-facing ephemeral token shape (`value` + `expires_at_unix`). -//! -//! RED before the wiring: nothing composed a provider, so the mint route answered `501` on every -//! deployment and the real key never had a way in. - -use crate::mount::{ - compose_provider, composed_provider_base_url, open_governed, provider_composed, GovernedOpen, - Ingress, ProviderEndpoint, -}; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use std::sync::{Arc, Mutex}; - -/// The provider key busbar holds server-side — the value the loopback provider must be dialed with. -const PROVIDER_KEY: &str = "sk-realtime-key-held-server-side"; -/// The browser-facing ephemeral secret the provider mints back. -const EK_VALUE: &str = "ek_browser_secret_2f4c"; -/// The absolute expiry the provider stamps on that secret. -const EK_EXPIRES_AT: u64 = 1_780_000_600; - -/// A loopback "provider" for `POST /v1/realtime/client_secrets`: it RECORDS the `Authorization` it was -/// dialed with and answers the provider's own client-secret document. -async fn spawn_client_secrets_provider(seen: Arc>>) -> std::net::SocketAddr { - async fn client_secrets( - axum::extract::State(seen): axum::extract::State>>>, - headers: axum::http::HeaderMap, - ) -> axum::response::Response { - *seen.lock().unwrap() = headers - .get(http::header::AUTHORIZATION) - .and_then(|v| v.to_str().ok()) - .map(str::to_owned); - axum::response::Response::builder() - .status(axum::http::StatusCode::OK) - .header(http::header::CONTENT_TYPE, "application/json") - .body(axum::body::Body::from( - serde_json::json!({ "value": EK_VALUE, "expires_at": EK_EXPIRES_AT }).to_string(), - )) - .unwrap() - } - let app = axum::Router::new() - .route( - "/v1/realtime/client_secrets", - axum::routing::post(client_secrets), - ) - .with_state(seen); - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - tokio::spawn(async move { - axum::serve(listener, app).await.unwrap(); - }); - addr -} - -/// A stand-in for the deployment's secret resolver: it answers one reference with one credential and -/// refuses everything else, so the test can tell "resolved through the seam" from "guessed". -struct OneSecretResolver { - expect: busbar_contract::secret_ref::SecretRef, - value: String, -} - -impl busbar_contract::secret::SecretResolve for OneSecretResolver { - fn resolve(&self, secret: &busbar_contract::secret_ref::SecretRef) -> Result, String> { - self.resolve_string(secret).map(String::into_bytes) - } - fn resolve_string( - &self, - secret: &busbar_contract::secret_ref::SecretRef, - ) -> Result { - if secret == &self.expect { - Ok(self.value.clone()) - } else { - Err("no such secret reference in this deployment".to_string()) - } - } -} - -fn runtime() -> VoiceRuntime { - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), - ) -} - -#[tokio::test] -async fn a_composed_provider_credential_makes_the_mint_pass_serve_the_browser_token() { - let seen_auth: Arc>> = Arc::new(Mutex::new(None)); - let addr = spawn_client_secrets_provider(Arc::clone(&seen_auth)).await; - let host = FixtureHost::new().into_host(); - let rt = runtime(); - let provider = ProviderEndpoint { - base_url: format!("http://{addr}"), - api_key: PROVIDER_KEY.to_string(), - }; - - let resp = open_governed(GovernedOpen { - rt: &rt, - host, - provider: Some(&provider), - ingress: Ingress::Mint, - owner: "acct-mint".to_string(), - call_id: "call-mint".to_string(), - vkey: None, - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 7, - }) - .await; - - assert_eq!( - resp.status(), - axum::http::StatusCode::OK, - "with a provider credential composed, the mint pass serves rather than reporting no provider" - ); - // The browser-facing payload: the ephemeral secret and its absolute expiry, and NOT the real key. - let body = axum::body::to_bytes(resp.into_body(), 64 * 1024) - .await - .unwrap(); - let json: serde_json::Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(json["value"], EK_VALUE, "the browser gets the ek_ secret"); - assert_eq!( - json["expires_at_unix"], EK_EXPIRES_AT, - "the browser gets the secret's absolute expiry" - ); - assert!( - !String::from_utf8_lossy(&body).contains(PROVIDER_KEY), - "the real provider key must never appear in the browser payload" - ); - // The provider hop authenticated with busbar's OWN key, held server-side. - assert_eq!( - seen_auth.lock().unwrap().as_deref(), - Some(format!("Bearer {PROVIDER_KEY}").as_str()), - "the mint dials the provider under busbar's own credential" - ); -} - -#[test] -fn the_composition_root_composes_the_provider_through_the_deployments_secret_resolver() { - let reference = busbar_contract::secret_ref::SecretRef::env("REALTIME_PROVIDER_KEY"); - let resolver = OneSecretResolver { - expect: reference.clone(), - value: PROVIDER_KEY.to_string(), - }; - - // A reference this deployment does NOT declare fails closed with the resolver's own message, and - // composes nothing — an unresolvable credential must never become an empty one. - let unknown = busbar_contract::secret_ref::SecretRef::env("NOT_DECLARED_HERE"); - assert!( - compose_provider("https://api.example.com", &unknown, &resolver).is_err(), - "an unresolvable reference composes nothing" - ); - assert!( - !provider_composed(), - "a failed resolve leaves the plane with no provider" - ); - - // The declared reference resolves through the seam and composes the endpoint the mint / SDP - // passes read. - assert_eq!( - compose_provider("https://api.example.com", &reference, &resolver), - Ok(true), - "the declared provider credential composes" - ); - assert!( - provider_composed(), - "the mint / SDP passes now have an endpoint" - ); - assert_eq!( - composed_provider_base_url(), - Some("https://api.example.com"), - "the composed origin is the one the deployment declared" - ); - - // Set-once: a second compose does not silently swap the deployment's credential. - assert_eq!( - compose_provider("https://other.example.com", &reference, &resolver), - Ok(false), - "the first composed endpoint stands" - ); - assert_eq!( - composed_provider_base_url(), - Some("https://api.example.com"), - "a second compose leaves the first endpoint in place" - ); -} diff --git a/crates/busbar-voice/src/tests/mount_tests.rs b/crates/busbar-voice/src/tests/mount_tests.rs deleted file mode 100644 index 143abd84fa..0000000000 --- a/crates/busbar-voice/src/tests/mount_tests.rs +++ /dev/null @@ -1,767 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! MOUNT TESTS (behind `runtime`): the voice plane's data-route mount is STRUCTURAL — the four routes -//! MOUNT, the claim + admission BIND the plane's RFC 8707 audience from `public_url`, and a route's -//! arrival runs the governed session-open through `run_gauntlet_session` (verify-before-charge). No -//! live provider is called: a clean open answers `501` -//! (governed, but the live serving leg is the deployment's to compose). - -use super::{ - voice_admission, voice_build, voice_claims, voice_hydrate, voice_routes, voice_start, - MOUNT_PATH, -}; -use crate::ir::codec::OpenAiRealtimeCodec; -// Test-support-only: the governed-open battery (`governed_open` + its gauntlet-open test) drives -// `open_governed` over `Ingress`; both are used ONLY under `#[cfg(feature = "test-support")]`, so gate -// the imports to keep a `runtime`-without-`test-support` build (the workspace clippy default now that -// voice ships default-on) clean. -#[cfg(feature = "test-support")] -use super::Ingress; -#[cfg(feature = "test-support")] -use crate::mount::open_governed; -use crate::runtime::scope::rehydrate_sessions; -use crate::runtime::{EchoToolExecutor, SessionHandle, VoiceRuntime}; -use crate::topology::telephony::{begin_telephony, g711_config}; -use busbar_contract::abi::mechanism::route::{RouteAuth, RouteMethod}; -use busbar_contract::records::{ - PlaneRecord, PlaneRecordRef, PlaneSelector, RecordStoreError, RecordStoreResult, -}; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use busbar_kernel::plane::registry::{BuildCtx, CardIssuer, PlaneBootCtx, RestoredSummary}; -use busbar_kernel::plane::store::PlaneStore; -use busbar_kernel::plane_host::EngineHost; -use std::sync::{Arc, Mutex}; - -const PUBLIC_URL: &str = "https://gw.example.com"; - -/// A minimal in-memory [`PlaneStore`] — the durable sink stand-in a boot rehydrate reads back. Only the -/// row upsert + `All` list are backed; the rest are the neutral no-ops a session restore never reaches. -/// A store built [`MemStore::down`] refuses every row upsert, so a session's durable open fails. -#[derive(Default)] -pub(super) struct MemStore { - rows: Mutex>, - down: bool, -} - -impl MemStore { - /// A store whose every row upsert fails. - pub(super) fn down() -> Self { - MemStore { - down: true, - ..MemStore::default() - } - } -} - -impl PlaneStore for MemStore { - fn upsert_plane_record(&self, record: PlaneRecordRef<'_>) -> RecordStoreResult<()> { - let record = &record.to_record(); - if self.down { - return Err(RecordStoreError("store down".to_string())); - } - let mut rows = self.rows.lock().unwrap(); - if let Some(existing) = rows.iter_mut().find(|r| r.id == record.id) { - *existing = record.clone(); - } else { - rows.push(record.clone()); - } - Ok(()) - } - fn get_plane_record(&self, _kind: &str, id: &str) -> RecordStoreResult>> { - Ok(self - .rows - .lock() - .unwrap() - .iter() - .find(|r| r.id == id) - .map(|r| r.body.clone())) - } - fn append_plane_record(&self, _record: PlaneRecordRef<'_>) -> RecordStoreResult<()> { - Ok(()) - } - fn list_plane_records( - &self, - _kind: &str, - selector: &PlaneSelector, - ) -> RecordStoreResult>> { - match selector { - PlaneSelector::All => Ok(self - .rows - .lock() - .unwrap() - .iter() - .map(|r| r.body.clone()) - .collect()), - PlaneSelector::Parent(_) => Ok(Vec::new()), - } - } - fn list_plane_record_parents(&self, _kind: &str) -> RecordStoreResult> { - Ok(Vec::new()) - } - fn purge_plane_records_before(&self, _kind: &str, _before: u64) -> RecordStoreResult { - Ok(0) - } - fn delete_plane_record(&self, _kind: &str, _id: &str) -> RecordStoreResult<()> { - Ok(()) - } - fn redeem_plane_token( - &self, - _kind: &str, - _token: &str, - _expires_at: u64, - _now: u64, - ) -> RecordStoreResult { - Ok(true) - } - /// The multi-use capability check. `false` — the fail-closed direction the neutral trait - /// defaults to — because this fixture keeps no capability rows to be live. - fn plane_token_live( - &self, - _kind: &str, - _token: &str, - _expires_at: u64, - _now: u64, - ) -> RecordStoreResult { - Ok(false) - } -} - -/// A minimal [`PlaneBootCtx`] carrying only the plane-narrowed store: enough to drive the voice boot -/// hooks, which read `plane_store()` and nothing else. The methods a voice hook never calls -/// (`engine_host`, the MCP call-log surface, `card_issuer`) are inert stand-ins. -struct FakeBootCtx { - store: Option>, -} - -impl PlaneBootCtx for FakeBootCtx { - fn has_store(&self) -> bool { - self.store.is_some() - } - fn register_call_stream(&self) {} - fn restore_call_log(&self) -> Result { - Ok(RestoredSummary::default()) - } - fn attach_durable_sinks(&self) {} - fn plane_store(&self) -> Option> { - self.store.clone() - } - fn card_issuer(&self) -> Option { - None - } - fn engine_host(&self) -> Arc { - unimplemented!("the voice boot hooks never mint the engine host") - } - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -/// Build the voice dispatch slot the way `appbuild` does — a `BuildCtx` carrying the deployment's -/// `public_url`. The other `BuildCtx` fields are the neutral absences the voice plane never reads. -fn slot_from_public_url(public_url: Option<&str>) -> Option> { - let unit = (); - let ctx = BuildCtx { - endpoint_slot: None, - agent_defs: &unit, - tool_defs: &unit, - public_url, - prior: None, - }; - voice_build(&ctx) -} - -/// A session runtime with no metered caller behind it — used to drive -/// `open_governed` without any provider. `model` seeds the gauntlet destination. -fn runtime_for(model: &str) -> VoiceRuntime { - let mut rt = VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), - ); - rt.session_defaults.model = Some(model.to_string()); - rt -} - -#[test] -fn build_binds_the_audience_from_public_url_and_none_without() { - // No `public_url` ⇒ no receiving side ⇒ no slot, no claim, no admission (delegation-only asymmetry). - assert!( - slot_from_public_url(None).is_none(), - "no public_url ⇒ the plane fronts nothing and binds no audience" - ); - - let slot = slot_from_public_url(Some(PUBLIC_URL)).expect("a public_url ⇒ a dispatch slot"); - - // The claim is the ONE audience-checked base every voice route sits under, spoken in the first - // dialect — so `/v1/realtime/*` is audience-checked by segment-boundary match (R1's invariant). - // K4: a SECOND claim for the Gemini Live route, under its own dialect label — the A2A precedent of - // more than one `(path, wire)` pair per plane, so the Gemini leg's traffic is never mislabelled - // under the OpenAI constant. - let claims = voice_claims(slot.as_ref()); - assert_eq!( - claims, - vec![ - (MOUNT_PATH.to_string(), crate::OPENAI_REALTIME), - ("/v1/realtime/gemini".to_string(), crate::GEMINI_LIVE), - ], - "the plane claims one audience-checked base per dialect route" - ); - - // The admission BINDS the audience derived from `public_url` — the confused-deputy defence: a token - // minted for another resource is refused here (R2: a claim without an admission refuses boot). - let admission = - voice_admission(slot.as_ref()).expect("a claimed plane must admit (mounted ⇒ admitted)"); - assert_eq!( - admission.audience, - format!("{PUBLIC_URL}/v1/realtime"), - "the audience is one reading of public_url + the voice resource path" - ); - assert_eq!( - admission.resource_metadata, - format!("{PUBLIC_URL}/.well-known/oauth-protected-resource/v1/realtime"), - "the refused-caller metadata URL is the same reading of public_url" - ); -} - -#[test] -fn the_five_ingress_doors_mount_audience_checked_across_the_http_and_ws_seams() { - let slot = slot_from_public_url(Some(PUBLIC_URL)).expect("a public_url ⇒ a dispatch slot"); - - // The TWO one-shot HTTP doors ride the buffered-body `routes` seam: ek_ mint + SDP broker. - let http: Vec<(String, RouteMethod, RouteAuth)> = voice_routes(slot.as_ref()) - .into_iter() - .map(|r| (r.path, r.method, r.auth)) - .collect(); - assert_eq!( - http, - vec![ - ( - "/v1/realtime/client_secrets".to_string(), - RouteMethod::Post, - RouteAuth::Key - ), - ( - "/v1/realtime/calls".to_string(), - RouteMethod::Post, - RouteAuth::Key - ), - ], - "the two one-shot HTTP doors mount, each RouteAuth::Key behind the plane's one audience" - ); - - // The TWO inbound WS-accept doors ride the neutral WS-accept seam instead (an upgrade cannot ride - // the buffered-body adapter): sideband + telephony, SAME RouteAuth::Key under the same audience, - // keyed to the plane's decl slot so the core mount resolves the live runtime. - let ws: Vec<(String, RouteAuth, &'static str)> = crate::mount::voice_ws_arrivals() - .into_iter() - .map(|a| (a.path, a.auth, a.slot_key)) - .collect(); - assert_eq!( - ws, - vec![ - ( - "/v1/realtime/sideband/{call_id}".to_string(), - RouteAuth::Key, - crate::PLANE_DECLARATION.key - ), - ( - "/v1/realtime/telephony/{call_id}".to_string(), - RouteAuth::Key, - crate::PLANE_DECLARATION.key - ), - ( - "/v1/realtime/gemini/{call_id}".to_string(), - RouteAuth::Key, - crate::PLANE_DECLARATION.key - ), - ], - "the THREE WS-accept doors declare through the neutral seam, RouteAuth::Key under the plane's key" - ); - - // No receiving side ⇒ no HTTP routes, exactly as it claims and admits nothing. - assert!( - voice_routes(&()).is_empty(), - "a slot that is not a VoiceMount mounts no routes" - ); -} - -/// One `GovernedOpen` over a real host double, no provider configured — the shape the route handler -/// builds. Gated on `test-support` (the host double needs a bare app). -#[cfg(feature = "test-support")] -fn governed_open<'a>( - rt: &'a VoiceRuntime, - host: Arc, - ingress: Ingress, - call_id: &str, -) -> crate::mount::GovernedOpen<'a> { - crate::mount::GovernedOpen { - rt, - host, - provider: None, - ingress, - owner: "acct".to_string(), - call_id: call_id.to_string(), - vkey: None, - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 1, - } -} - -#[cfg(feature = "test-support")] -#[tokio::test] -async fn arrival_passes_the_gauntlet_and_opens_the_governed_session() { - let host = crate::testkit::fixture_host::FixtureHost::new().into_host(); - // A destination proceeds PAST the open-pass gate and opens the governed session; with no provider - // configured the one-shot mint/SDP passes answer 501 (governed, uncomposed). Only Mint/Sdp route - // through `open_governed`; the Sideband/Telephony WS legs route through `ws_accept` (the inbound - // WS-accept seam) — their governed open + operator-gate screening is proven by - // `hook_gate_tests::a_reject_all_operator_gate_refuses_a_ws_accept_before_the_upgrade` and their - // route mounting by `the_five_ingress_doors_mount_audience_checked_across_the_http_and_ws_seams`. - let allowed = runtime_for("allowed-model"); - for ingress in [Ingress::Mint, Ingress::Sdp] { - let opened = open_governed(governed_open( - &allowed, - Arc::clone(&host), - ingress, - "call-ok", - )) - .await; - assert_eq!( - opened.status(), - axum::http::StatusCode::NOT_IMPLEMENTED, - "{ingress:?}: the governed open succeeds; the live provider/media leg is uncomposed here" - ); - } -} - -#[test] -fn hydrate_is_a_noop_under_the_ephemeral_posture_and_start_confirms_ready() { - // No configured store (the in-process posture this mount runs): the boot rehydrate has no durable - // working-set to restore, so it skips cleanly — exactly the first move the A2A/MCP hydrate makes. - let ctx = FakeBootCtx { store: None }; - assert!( - voice_hydrate(&ctx).is_ok(), - "hydrate under no store is a clean no-op" - ); - assert!(voice_start(&ctx).is_ok(), "start confirms readiness"); -} - -#[test] -fn hydrate_rehydrates_the_durable_session_working_set() { - // Persist two sessions through an engine with the store attached as its durable sink: one left - // ACTIVE, one driven TERMINAL. A boot rehydrate must restore the active one and count the terminal. - let store: Arc = Arc::new(MemStore::default()); - let engine = Arc::new(DurableHandleEngine::new()); - engine.set_sink(store.clone()); - - let active = SessionHandle::bind(Arc::clone(&engine), "acct", "sess-active"); - active.open(10).expect("active session opens durably"); - - let terminal = SessionHandle::bind(Arc::clone(&engine), "acct", "sess-terminal"); - terminal.open(10).expect("terminal session opens durably"); - terminal - .settle_terminal(11) - .expect("the session drives terminal"); - - // A FRESH engine restores purely from the durable store — the boot path a restart takes. - let restored = Arc::new(DurableHandleEngine::new()); - let counts = rehydrate_sessions(&restored, store.as_ref()).expect("rehydrate reads the store"); - assert_eq!(counts.active, 1, "the active session is restored"); - assert_eq!( - counts.terminal, 1, - "the terminal session is counted, not restored" - ); - assert_eq!(counts.unreadable, 0, "every durable row decoded"); - - // The restored active handle is readable by its owner — the durable binding survived the restart. - let reattached = SessionHandle::bind(restored, "acct", "sess-active"); - assert_eq!( - reattached.get().map(|r| r.id), - Some("sess-active".to_string()), - "the restored session reattaches by (owner, id)" - ); - - // And the hydrate HOOK drives the same restore off the boot store without error. - assert!( - voice_hydrate(&FakeBootCtx { store: Some(store) }).is_ok(), - "the hydrate hook restores the durable working-set off the boot store" - ); -} - -#[tokio::test] -async fn duplex_session_runs_in_process_through_the_gauntlet_after_hydrate() { - // (1) HYDRATE first, before any listener — the ephemeral no-op gate. - assert!(voice_hydrate(&FakeBootCtx { store: None }).is_ok()); - - // (2) ARRIVAL: begin_telephony opens the session THROUGH `run_gauntlet_session` (verify strictly - // before the kernel account's budget check). g711 carries no model, so the destination is unset and admitted. - let rt = runtime_for(""); - let proxy = begin_telephony( - &rt, - OpenAiRealtimeCodec, - "acct", - "call-x", - g711_config(), - None, - 1, - ) - .expect("the open-pass gauntlet admits and the session opens"); - - // (3) HANDLER: drive one phone call over the neutral pump with an in-process MOCK PEER — four - // in-memory channels stand in for the provider socket and the client socket. No live provider. - let legs = crate::testkit::telephony::place_call(proxy, &[0x7f; 160]).await; - - // Uplink: the caller's audio reached the provider. Downlink: the provider's audio reached the - // caller, in the carrier's own envelope. - assert!( - legs.to_far_end - .iter() - .any(|v| v["type"] == "input_audio_buffer.append"), - "the governed session relayed client uplink audio to the provider: {:?}", - legs.to_far_end - ); - assert!( - legs.to_caller.iter().any(|v| v["event"] == "media"), - "the governed session relayed provider downlink audio to the client: {:?}", - legs.to_caller - ); -} - -/// THE PROVIDER CREDENTIAL DOES NOT REACH THE LOG. The Gemini leg's native provider scheme carries the -/// API key in the dial URL, and the neutral dialer's URL-shaped refusal quotes the target it could not -/// use back verbatim — so the one line the failed-dial arm writes is a line the deployment's resolved -/// provider credential can ride out on. This drives the exact pair that arm composes: the URL the leg -/// builds, and the rendering of the error a `base_url` the dialer cannot parse produces. -#[test] -fn a_failed_gemini_dial_does_not_write_the_provider_key_into_the_log() { - const KEY: &str = "AIzaSyTOPSECRETVALUE"; - // The URL the Gemini leg dials — the key rides the query, which is that dialect's native scheme. - let url = super::provider_ws_url( - "https://generativelanguage.googleapis.com", - crate::GEMINI_LIVE, - KEY, - ); - assert!( - url.contains(KEY), - "the premise: the Gemini dial target really does carry the credential in its query" - ); - - // A `base_url` the neutral dialer cannot use quotes the whole target back — key and all. - let refusal = crate::topology::DialProviderError::Dial( - busbar_kernel::egress::duplex_ws::DialError::Url(url.clone()), - ); - let raw = refusal.to_string(); - assert!( - raw.contains(KEY), - "the premise: the dialer's own refusal quotes the target verbatim, so the raw error is not \ - a thing this plane may hand to a logger" - ); - - // What the arm actually logs. - let logged = super::redact_url_credentials(&raw); - assert!( - !logged.contains(KEY), - "the logged line must not carry the provider credential; it read: {logged}" - ); - assert!( - logged.contains("generativelanguage.googleapis.com"), - "and it must still name the target an operator has to fix: {logged}" - ); -} - -/// #71 EXIT TEST (money-model integrity, P2-voicefix, architect ruling): a plane's DECLARED -/// BILLABLE CLASSES must be pairwise DISJOINT, because money = Σ count(class) × rate(class) only -/// holds when no class is a subset of another. The upstream `cached_tokens` figure both duplex -/// dialects report is a SUBSET of the input token classes (nested inside -/// `input_token_details`/`promptTokensDetails`, never a sibling count), so it must not appear in -/// `PLANE_DECLARATION.billable_classes` — a streams card that priced it too would double-bill the same -/// cached input. This asserts the declared list excludes it and stays pairwise distinct. -#[test] -fn billable_classes_are_pairwise_disjoint_and_exclude_cached_tokens() { - let classes: Vec<&str> = crate::PLANE_DECLARATION - .billable_classes - .iter() - .map(|c| c.class) - .collect(); - assert!( - !classes.contains(&"cached_tokens"), - "cached_tokens is a subset of audio_tokens_in/text_tokens_in, not a billable class beside \ - them (#71) — declaring it would double-bill the same cached input; declared classes: \ - {classes:?}" - ); - let mut seen = std::collections::BTreeSet::new(); - for class in &classes { - assert!( - seen.insert(*class), - "declared billable classes must be pairwise disjoint (#71) — {class:?} repeats in \ - {classes:?}" - ); - } -} - -/// The presenting key the session-fee cells below open under. -#[cfg(feature = "test-support")] -fn fee_key() -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: "vk-session-fee".to_string(), - name: "session-fee".to_string(), - ..Default::default() - } -} - -/// The presenting key's meter over `host`, as the governed open builds it. -#[cfg(feature = "test-support")] -fn fee_meter(host: &Arc) -> crate::runtime::TurnMeter { - crate::runtime::TurnMeter::new( - Arc::clone(host) as Arc, - fee_key(), - "streaming-server", - crate::OPENAI_REALTIME, - ) -} - -/// A governed host with room on the key's chain, holding ONE KEPT SESSION: opened and served to its -/// end through the one serving loop, so its session fee is on the budget book and stays there. A -/// failed open's refund that gave back more than its own fee would eat this one. -#[cfg(feature = "test-support")] -async fn host_with_one_kept_session( - rt: &VoiceRuntime, -) -> Arc { - let host = Arc::new( - crate::testkit::fixture_host::FixtureHost::new() - .governed() - .with_count_cap(1_000), - ); - let hosted = crate::runtime::build_runtime_hosted(rt, Arc::clone(&host) as Arc); - let (core, handle) = crate::topology::begin_session( - &hosted, - OpenAiRealtimeCodec, - "acct", - "call-kept", - None, - crate::runtime::Carrier::sideband(), - Some(fee_meter(&host)), - 1, - ) - .expect("the kept session opens"); - crate::runtime::serve_with_sweep(core, async {}).await; - handle.finish(1); - assert_eq!( - host.ledger_usage(&fee_key().id).map_or(0, |u| u.sessions), - 1, - "the premise: the kept session's fee is on the budget book" - ); - host -} - -/// TODO 17(b) (ARCHITECT R4): `failed` opens each counted their session fee AT THE OPEN, under the -/// same dry check, and each gave it back EXACTLY ONCE through the governance refund. The metering -/// row keeps every count (the refund adjusts the budget book only, as v1.5.5's fee refund did); the -/// budget book holds the kept session's fee alone. -#[cfg(feature = "test-support")] -#[track_caller] -fn assert_each_failed_open_refunded_once( - host: &crate::testkit::fixture_host::FixtureHost, - failed: u64, - what: &str, -) { - assert_eq!( - host.session_rows(&fee_key().id), - 1 + failed, - "{what}: each failed open counted its session fee at the open, under the dry check" - ); - assert_eq!( - host.ledger_usage(&fee_key().id).map_or(0, |u| u.sessions), - 1, - "{what}: each failed open's fee is given back exactly once, and the kept session's stays" - ); -} - -/// A loopback provider that FAILS both one-shot passes: `POST /v1/realtime/client_secrets` (the mint) -/// and `POST /v1/realtime/calls` (the SDP broker) answer `500`, and each request reaching it is -/// counted in `hits`, so a cell can prove its failure came from the provider leg and not from an -/// earlier refusal. -#[cfg(feature = "test-support")] -async fn spawn_failing_provider( - hits: Arc, -) -> super::ProviderEndpoint { - async fn fail( - axum::extract::State(hits): axum::extract::State>, - ) -> axum::http::StatusCode { - hits.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - axum::http::StatusCode::INTERNAL_SERVER_ERROR - } - let app = axum::Router::new() - .route("/v1/realtime/client_secrets", axum::routing::post(fail)) - .route("/v1/realtime/calls", axum::routing::post(fail)) - .with_state(hits); - let listener = tokio::net::TcpListener::bind("127.0.0.1:0") - .await - .expect("a loopback port"); - let addr = listener.local_addr().expect("its address"); - tokio::spawn(async move { - axum::serve(listener, app) - .await - .expect("the provider serves"); - }); - super::ProviderEndpoint { - base_url: format!("http://{addr}"), - api_key: "sk-test".to_string(), - } -} - -/// One governed one-shot open of `ingress` for the fee key on `host`, through `provider`. -#[cfg(feature = "test-support")] -async fn open_one_shot( - rt: &VoiceRuntime, - host: &Arc, - provider: Option<&super::ProviderEndpoint>, - ingress: Ingress, - call_id: &str, -) -> axum::http::StatusCode { - let mut open = governed_open( - rt, - Arc::clone(host) as Arc, - ingress, - call_id, - ); - open.provider = provider; - open.vkey = Some(fee_key()); - open_governed(open).await.status() -} - -/// A FAILED MINT REFUNDS ITS SESSION FEE EXACTLY ONCE (TODO 17(b)): the provider's mint answers `500`, -/// so the browser is handed no secret and the session never opened. RED with the fee counted at -/// `served()`: the open counted nothing to refund. -#[cfg(feature = "test-support")] -#[tokio::test] -async fn a_failed_mint_refunds_its_session_fee_exactly_once() { - let rt = runtime_for("allowed-model"); - let host = host_with_one_kept_session(&rt).await; - let hits = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let provider = spawn_failing_provider(Arc::clone(&hits)).await; - let status = open_one_shot( - &rt, - &host, - Some(&provider), - Ingress::Mint, - "call-mint-fails", - ) - .await; - assert_eq!( - status, - axum::http::StatusCode::BAD_GATEWAY, - "the premise: the mint failed at the provider" - ); - assert_eq!( - hits.load(std::sync::atomic::Ordering::SeqCst), - 1, - "the premise: the open reached the provider's mint" - ); - assert_each_failed_open_refunded_once(&host, 1, "a failed mint"); -} - -/// A FAILED SDP BROKER REFUNDS ITS SESSION FEE EXACTLY ONCE (TODO 17(b)): the provider answers the -/// brokered offer `500`, so no call was set up and the session never opened. The cell opens under its -/// own call id and proves the broker was reached, so the failure is the broker's and not a durable -/// refusal of a reused id. RED with the fee counted at `served()`. -#[cfg(feature = "test-support")] -#[tokio::test] -async fn a_failed_sdp_broker_refunds_its_session_fee_exactly_once() { - let rt = runtime_for("allowed-model"); - let host = host_with_one_kept_session(&rt).await; - let hits = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let provider = spawn_failing_provider(Arc::clone(&hits)).await; - let status = open_one_shot(&rt, &host, Some(&provider), Ingress::Sdp, "call-sdp-fails").await; - assert_eq!( - status, - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "the premise: the provider refused the brokered offer, and the answer carries its status" - ); - assert_eq!( - hits.load(std::sync::atomic::Ordering::SeqCst), - 1, - "the premise: the open reached the provider's SDP broker" - ); - assert_each_failed_open_refunded_once(&host, 1, "a failed SDP broker"); -} - -/// AN UNREACHABLE PROVIDER REFUNDS THE SESSION FEE ON EACH ONE-SHOT PASS (TODO 17(b)): nothing listens -/// on the discard port, so the mint and the SDP broker each fail at connect and answer `502`. Each pass -/// opens under its own call id, so neither failure is a durable refusal of a reused id. RED with the -/// fee counted at `served()`. -#[cfg(feature = "test-support")] -#[tokio::test] -async fn an_unreachable_provider_refunds_the_session_fee_on_each_one_shot_pass() { - let rt = runtime_for("allowed-model"); - let host = host_with_one_kept_session(&rt).await; - let down = super::ProviderEndpoint { - base_url: "http://127.0.0.1:9".to_string(), - api_key: "sk-test".to_string(), - }; - for (ingress, call) in [ - (Ingress::Mint, "call-mint-down"), - (Ingress::Sdp, "call-sdp-down"), - ] { - let status = open_one_shot(&rt, &host, Some(&down), ingress, call).await; - assert_eq!( - status, - axum::http::StatusCode::BAD_GATEWAY, - "{ingress:?}: the premise: the provider leg failed at connect" - ); - } - assert_each_failed_open_refunded_once(&host, 2, "an unreachable provider"); -} - -/// AN OPEN ANSWERED `501` REFUNDS ITS SESSION FEE EXACTLY ONCE (TODO 17(b)): with no provider composed, -/// every HTTP open the governed door answers `501` serves nothing, so it is a failed open: the mint, -/// the SDP broker, the HTTP telephony open and the HTTP sideband open each give their fee back. RED -/// with the fee counted at `served()`. -#[cfg(feature = "test-support")] -#[tokio::test] -async fn an_open_answered_501_refunds_its_session_fee_exactly_once() { - let rt = runtime_for("allowed-model"); - let host = host_with_one_kept_session(&rt).await; - let opens = [ - (Ingress::Mint, "call-mint-501"), - (Ingress::Sdp, "call-sdp-501"), - (Ingress::Telephony, "call-telephony-501"), - (Ingress::Sideband, "call-sideband-501"), - ]; - for (ingress, call) in opens { - let status = open_one_shot(&rt, &host, None, ingress, call).await; - assert_eq!( - status, - axum::http::StatusCode::NOT_IMPLEMENTED, - "{ingress:?}: the premise: the open is governed and nothing serves it" - ); - } - assert_each_failed_open_refunded_once(&host, 4, "an open answered 501"); -} - -/// A FAILED PROVIDER DIAL REFUNDS ITS SESSION FEE EXACTLY ONCE (TODO 17(b)): the WS telephony / Gemini -/// leg opens the session, then dials the provider; a failed dial settles the session through -/// [`super::settle_undialed`], the arm `ws_accept` takes, and never serves it. RED with the fee -/// counted at `served()`. -#[cfg(feature = "test-support")] -#[tokio::test] -async fn a_failed_provider_dial_refunds_its_session_fee_exactly_once() { - let base = runtime_for("allowed-model"); - let host = host_with_one_kept_session(&base).await; - let rt = crate::runtime::build_runtime_hosted(&base, Arc::clone(&host) as Arc); - let proxy = crate::topology::telephony::open_admitted_telephony( - &rt, - OpenAiRealtimeCodec, - "acct", - "call-dial-fails", - g711_config(), - Some(fee_meter(&host)), - 1, - None, - false, - ) - .expect("the session opens"); - super::settle_undialed(proxy, 2); - assert_each_failed_open_refunded_once(&host, 1, "a failed provider dial"); -} diff --git a/crates/busbar-voice/src/tests/scope_gate_tests.rs b/crates/busbar-voice/src/tests/scope_gate_tests.rs deleted file mode 100644 index eebd9cd2ff..0000000000 --- a/crates/busbar-voice/src/tests/scope_gate_tests.rs +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE SESSION-SCOPE AUTHORIZATION GATE — the plane's declared `session` scope kind, enforced. -//! -//! The plane declares one scope kind (`PLANE_DECLARATION.scope_kinds`), which is the vocabulary an -//! `allowed_scopes: [{ kind: session, value: … }]` entry validates against. Holding a key that is -//! valid for this plane's AUDIENCE is not the same as being GRANTED a session on it — the audience -//! check answers "is this token for this door", the grant answers "may this caller walk through it", -//! exactly as MCP double-gates a tool and A2A gates an agent. -//! -//! The refusal lands FIRST: before the operator's hook gate fires, before a metering lease is -//! reserved, before a durable session row exists, and before any provider is dialed. So a caller -//! without the grant costs zero bytes and zero charge. -//! -//! RED before the wiring: any key valid for the voice audience opened a session, and the declared -//! scope kind was vocabulary nothing consulted. - -use crate::mount::{open_governed, session_scope_allowed, GovernedOpen, Ingress}; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use std::sync::Arc; - -/// The pool a voice session is served on — the value the `session` grant names. -const VOICE_POOL: &str = "streaming-server"; - -/// A key carrying an EXPLICIT scope list. An explicit list is exhaustive across kinds: whatever is -/// not in it is not granted. -fn key_scoped( - id: &str, - scopes: Vec, -) -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: id.to_string(), - name: id.to_string(), - allowed_scopes: Some(scopes), - ..Default::default() - } -} - -fn session_scope(value: &str) -> busbar_contract::records::ScopeRef { - busbar_contract::records::ScopeRef { - kind: "session".to_string(), - value: value.to_string(), - } -} - -fn runtime() -> VoiceRuntime { - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(EchoToolExecutor), - ) -} - -async fn open_as(key: Option) -> axum::http::StatusCode { - let rt = runtime(); - open_governed(GovernedOpen { - rt: &rt, - host: FixtureHost::new().into_host(), - provider: None, - ingress: Ingress::Mint, - owner: "acct-scope".to_string(), - call_id: "call-scope".to_string(), - vkey: key, - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 11, - }) - .await - .status() -} - -#[test] -fn the_grant_is_read_off_the_keys_own_scope_list() { - // A wildcard principal (no scope list at all) is granted every kind — the store's own semantic, - // and the most common key shape in a small deployment. - let wildcard = busbar_contract::records::VirtualKey { - id: "vk-wildcard".to_string(), - ..Default::default() - }; - assert!( - session_scope_allowed(&wildcard), - "a key with no scope list is granted every kind, voice included" - ); - - // An explicit list that names the session scope on the voice pool grants it. - assert!( - session_scope_allowed(&key_scoped("vk-granted", vec![session_scope(VOICE_POOL)])), - "an explicit session grant on the voice pool admits" - ); - - // An explicit list WITHOUT it does not — including one that carries pool scopes (a model-plane - // key) or a session scope for some other pool. - assert!( - !session_scope_allowed(&key_scoped( - "vk-pool-only", - vec![busbar_contract::records::ScopeRef::pool("fast")] - )), - "a model-plane key is not thereby granted a voice session" - ); - assert!( - !session_scope_allowed(&key_scoped( - "vk-other-pool", - vec![session_scope("some-other-pool")] - )), - "a session grant for another pool does not reach the voice pool" - ); - assert!( - !session_scope_allowed(&key_scoped("vk-empty", Vec::new())), - "an empty explicit list grants nothing" - ); -} - -#[tokio::test] -async fn a_key_without_session_scope_is_refused_at_the_door() { - let refused = open_as(Some(key_scoped( - "vk-no-session", - vec![busbar_contract::records::ScopeRef::pool("fast")], - ))) - .await; - assert_eq!( - refused, - axum::http::StatusCode::FORBIDDEN, - "a key holding no session scope is refused with the plane's own fail-closed answer" - ); -} - -#[tokio::test] -async fn a_granted_key_and_an_ungoverned_caller_still_open() { - // The granted key proceeds past the gate into the governed open (which, with no provider - // composed here, reports that there is nothing to dial) — the point is that it got there. - let granted = open_as(Some(key_scoped( - "vk-granted", - vec![session_scope(VOICE_POOL)], - ))) - .await; - assert_eq!( - granted, - axum::http::StatusCode::NOT_IMPLEMENTED, - "a granted key opens the governed session" - ); - - // An ungoverned deployment resolves no key and has no grant to consult, so it is unaffected — - // this gate narrows governed callers only. - assert_eq!( - open_as(None).await, - axum::http::StatusCode::NOT_IMPLEMENTED, - "an ungoverned caller is unchanged by the grant check" - ); -} diff --git a/crates/busbar-voice/src/tests/sdp_tests.rs b/crates/busbar-voice/src/tests/sdp_tests.rs deleted file mode 100644 index c96c1c19c2..0000000000 --- a/crates/busbar-voice/src/tests/sdp_tests.rs +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE SDP BROKER + `rtc_` CORRELATION, end-to-end over a loopback. The browser's SDP offer -//! is brokered to the provider's `POST /v1/realtime/calls`; the provider's `Location: -//! …/rtc_` is PRESERVED on the answer AND stamped onto the durable session row — so -//! busbar's governance (this session) and the brokered media call name the SAME session. A mismatch -//! silently attaches the sideband to the wrong call, so the correlation is asserted broker → row. -//! -//! RED before the wiring: the SDP route answered `501` (no broker), so there was no `Location` to -//! preserve and no `rtc_` on the row. - -use crate::mount::{open_governed, GovernedOpen, Ingress, ProviderEndpoint}; -use crate::runtime::scope::SessionHandle; -use crate::runtime::{EchoToolExecutor, VoiceRuntime}; -use crate::testkit::fixture_host::FixtureHost; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use std::sync::{Arc, Mutex}; - -const RTC_CALL_ID: &str = "rtc_correlated_call_9f8e7d"; -const SDP_ANSWER: &str = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"; -const PROVIDER_KEY: &str = "sk-real-key-held-server-side"; -/// The caller's inbound governance bearer — a sentinel that MUST NEVER reach the provider hop. -const INBOUND_GOVERNANCE_BEARER: &str = "Bearer inbound-governance-token-MUST-NOT-LEAK"; - -/// A loopback "provider" for `POST /v1/realtime/calls`: it RECORDS the `Authorization` it was dialed -/// with (into `seen`) and answers `201` carrying the SDP answer and a `Location: -/// /v1/realtime/calls/rtc_` header — the exact broker contract. -async fn spawn_calls_broker(seen: Arc>>) -> std::net::SocketAddr { - async fn calls( - axum::extract::State(seen): axum::extract::State>>>, - headers: axum::http::HeaderMap, - ) -> axum::response::Response { - *seen.lock().unwrap() = headers - .get(http::header::AUTHORIZATION) - .and_then(|v| v.to_str().ok()) - .map(str::to_owned); - axum::response::Response::builder() - .status(axum::http::StatusCode::CREATED) - .header(http::header::CONTENT_TYPE, "application/sdp") - .header( - http::header::LOCATION, - format!("/v1/realtime/calls/{RTC_CALL_ID}"), - ) - .body(axum::body::Body::from(SDP_ANSWER)) - .unwrap() - } - let app = axum::Router::new() - .route("/v1/realtime/calls", axum::routing::post(calls)) - .with_state(seen); - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - tokio::spawn(async move { - axum::serve(listener, app).await.unwrap(); - }); - addr -} - -#[tokio::test] -async fn the_sdp_broker_correlates_the_rtc_call_id_from_the_location_header_onto_the_row() { - let seen_auth: Arc>> = Arc::new(Mutex::new(None)); - let addr = spawn_calls_broker(Arc::clone(&seen_auth)).await; - let host = FixtureHost::new().into_host(); - - let engine = Arc::new(DurableHandleEngine::new()); - let rt = VoiceRuntime::new(Arc::clone(&engine), Arc::new(EchoToolExecutor)); - let provider = ProviderEndpoint { - base_url: format!("http://{addr}"), - api_key: PROVIDER_KEY.to_string(), - }; - - // The caller reached the `RouteAuth::Key` SDP route with a GOVERNANCE bearer (a sentinel) + an SDP - // offer. The broker hop must authenticate to the provider with busbar's OWN provider key and NEVER - // forward this inbound token upstream. - let mut headers = axum::http::HeaderMap::new(); - headers.insert( - http::header::AUTHORIZATION, - http::HeaderValue::from_static(INBOUND_GOVERNANCE_BEARER), - ); - - let resp = open_governed(GovernedOpen { - rt: &rt, - host: Arc::clone(&host), - provider: Some(&provider), - ingress: Ingress::Sdp, - owner: "acct-sdp".to_string(), - call_id: "call-sdp".to_string(), - vkey: None, - body: axum::body::Bytes::from_static(b"v=0\r\no=browser 0 0 IN IP4 0.0.0.0\r\n"), - headers, - now: 42, - }) - .await; - - // The answer preserved the provider's `Location` header verbatim. - assert_eq!(resp.status(), axum::http::StatusCode::CREATED); - assert_eq!( - resp.headers() - .get(http::header::LOCATION) - .and_then(|v| v.to_str().ok()), - Some(format!("/v1/realtime/calls/{RTC_CALL_ID}").as_str()), - "the SDP broker preserves the Location header verbatim" - ); - - // CREDENTIAL ISOLATION (regression guard for the forward-the-inbound-bearer leak): the provider hop - // saw busbar's OWN provider key, and the caller's inbound governance bearer NEVER left the boundary. - let dialed_with = seen_auth.lock().unwrap().clone(); - assert_eq!( - dialed_with.as_deref(), - Some(format!("Bearer {PROVIDER_KEY}").as_str()), - "the SDP broker authenticates upstream with busbar's OWN provider credential" - ); - assert_ne!( - dialed_with.as_deref(), - Some(INBOUND_GOVERNANCE_BEARER), - "the caller's inbound governance bearer must NEVER be forwarded to the provider" - ); - - // BROKER → ROW: the `rtc_` derived from that Location is stamped onto the durable session - // row, read back by the session's own (owner, id). - let row = SessionHandle::bind(engine, "acct-sdp", "call-sdp") - .get() - .expect("the durable session row exists after the governed open"); - assert_eq!( - row.rtc_call_id.as_deref(), - Some(RTC_CALL_ID), - "the rtc_ from the broker's Location header correlates onto the session row — the \ - single key tying governance here to the media that flows there" - ); -} diff --git a/crates/busbar-voice/src/tests/served_witness.rs b/crates/busbar-voice/src/tests/served_witness.rs deleted file mode 100644 index 01f4243593..0000000000 --- a/crates/busbar-voice/src/tests/served_witness.rs +++ /dev/null @@ -1,624 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THIS PLANE'S SERVED-LEG WITNESSES — a live session opened at the plane's served door, through -//! whatever session runner the host has registered for this plane's capability key, each witness -//! asserting ONE capability or ONE loop step on what came out. -//! -//! ## Why they live here and run elsewhere -//! -//! In a deployment a session is opened by the composition root's kernel-loop session rider: the root -//! registers it under [`crate::PLANE_KEY`] at boot, and every governed open (`begin_session`, the -//! choke point every served door funnels through) asks `run_gauntlet_session`, which hands the open to -//! it. That rider lives in the binary crate, which this crate cannot link, so in THIS crate's own test -//! binary the same open runs `run_gauntlet_session`'s inline fallback. The witnesses are therefore -//! compiled under `test-support` and exported through [`crate::testkit::SERVED`]: the binary crate's -//! test build links this crate, installs its real rider, runs every witness, and checks that this -//! plane's session opens actually ran inside the loop the number of times the witness says they -//! should. The composition root names no plane to do it — it reads the table its build script -//! generated from the manifest. -//! -//! ## What the session leg is, and what it is not -//! -//! The rider carries a session's OPEN: the plane's verify and the door, before any account, durable -//! row or socket exists. Everything after it — the turns, their counts on the kernel's session -//! account, the audit row, the durable chain, the provider dial — is the plane's own served session -//! over the live host (OWNER RULING Q21b: a served session is metered by the kernel's session account, -//! per turn). So each witness opens its session at the served door and then drives the part of that -//! session its capability lives in. -//! -//! ## What a witness returns -//! -//! The number of session opens it expects the served path to have carried through the rider: every -//! open the door handed to the governed open. A caller the door refuses before it (a key with no -//! session grant) is not an open and is not counted — which is exactly what the caller checks. -//! -//! They drive the same host double and the same door the plane's own batteries drive -//! (`FixtureHost`, `open_governed`, `begin_session`), so the served witnesses and the plane's tests -//! cannot drift onto two deployments. - -use crate::ir::codec::{OpenAiRealtimeCodec, WireEvent}; -use crate::ir::config::SessionConfig; -use crate::mount::{open_governed, GovernedOpen, Ingress}; -use crate::runtime::{Carrier, SessionCore, TurnMeter, VoiceRuntime}; -use crate::testkit::fixture_host::FixtureHost; -use crate::topology::telephony::{begin_telephony, g711_config}; -use crate::topology::{begin_session, dial_provider, stream_breaker_key, DialProviderError}; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; - -/// One served-leg witness: drive the served path, assert, and return the number of session opens the -/// served path must have carried through this plane's registered session runner. -pub type Witness = fn() -> Pin>>; - -/// THE WITNESS TABLE, keyed by the loop step (`qa/teller-steps.json`) or the core capability -/// (`qa/capability-equality.json`) each one proves on the served path. The `session-*` keys are the -/// session plane's own shape of a step or capability whose one-shot witness asks something a session -/// does not do: its caller is attributed rather than re-authenticated per call, its verify admits the -/// destination the session is then metered under, its route is the two-way relay a turn rides, it -/// meters per turn and per declared class, and what it reports is the session it opened. -pub(crate) const WITNESSES: &[(&str, Witness)] = &[ - ("arrival", || { - Box::pin(each_served_open_is_one_unit_reported_once()) - }), - ("decode", || { - Box::pin(a_frame_the_plane_cannot_read_is_carried_nowhere()) - }), - ("session-authenticate", || { - Box::pin(the_session_answers_for_the_key_the_door_resolved()) - }), - ("session-verify", || { - Box::pin(the_destination_the_gate_judged_is_the_one_metered()) - }), - ("approve", || { - Box::pin(a_key_granted_no_session_never_reaches_the_gate()) - }), - ("admit", || { - Box::pin(a_dry_key_is_refused_at_the_open_past_the_gate()) - }), - ("session-route", || { - Box::pin(a_served_session_relays_both_directions()) - }), - ("session-meter", || { - Box::pin(a_served_turn_is_ledgered_per_declared_class()) - }), - ("audit", || Box::pin(a_served_open_lands_one_audit_row())), - ("exit", || { - Box::pin(a_served_session_ends_once_and_settles_its_open_turn()) - }), - ("audit-chain", || { - Box::pin(the_served_sessions_row_seals_at_genesis_and_reads_back()) - }), - ("governance-budget", || { - Box::pin(a_served_session_is_charged_to_the_presenting_key_and_closed_dry()) - }), - ("session-metrics", || { - Box::pin(a_served_open_is_reported_under_the_planes_own_labels()) - }), - ("breaker-fastfail", || { - Box::pin(a_served_sessions_dial_fast_fails_on_a_tripped_cell()) - }), -]; - -// ── FIXTURES ──────────────────────────────────────────────────────────────────────────────────── - -/// The pool a voice session is served on — the value a `session` grant names. -const VOICE_POOL: &str = "streaming-server"; - -/// The model a witness session targets, and so the ledger lane its turns land under. -const MODEL: &str = "witness-realtime"; - -/// The plane's own per-generation runtime (its build hook over no `streams:` section), bound to `host` -/// the way every served door binds it. -fn hosted(host: &Arc) -> VoiceRuntime { - let base = crate::runtime::build_runtime(&(), None) - .downcast::() - .expect("the plane's build hook builds its own runtime"); - crate::runtime::build_runtime_hosted(&base, host.clone()) -} - -/// A caller whose key holds no scope list — granted every kind, a voice session included. -fn caller(id: &str) -> busbar_contract::records::VirtualKey { - busbar_contract::records::VirtualKey { - id: id.to_string(), - name: id.to_string(), - ..Default::default() - } -} - -/// One open at the served door, over `host`, as `vkey` (or ungoverned). -fn served_open<'a>( - rt: &'a VoiceRuntime, - host: &Arc, - call_id: &str, - vkey: Option, -) -> GovernedOpen<'a> { - GovernedOpen { - rt, - host: host.clone(), - provider: None, - ingress: Ingress::Sideband, - owner: "acct-witness".to_string(), - call_id: call_id.to_string(), - vkey, - body: axum::body::Bytes::new(), - headers: axum::http::HeaderMap::new(), - now: 1, - } -} - -/// The governed open the served door runs, keeping the session core and its durable handle so the -/// witness can drive the session the door opened. `key` is the caller the door resolved (the turn -/// meter is that key's), or `None` for an ungoverned caller. -fn open_session( - rt: &VoiceRuntime, - host: &Arc, - call_id: &str, - key: Option<&busbar_contract::records::VirtualKey>, -) -> ( - Arc>, - crate::runtime::SessionHandle, -) { - let meter = - key.map(|k| TurnMeter::new(host.clone(), k.clone(), VOICE_POOL, crate::OPENAI_REALTIME)); - begin_session( - rt, - OpenAiRealtimeCodec, - "acct-witness", - call_id, - Some(SessionConfig { - model: Some(MODEL.to_string()), - ..SessionConfig::default() - }), - Carrier::sideband(), - meter, - 1, - ) - .unwrap_or_else(|e| panic!("the served door opens the session: {e}")) -} - -/// One OpenAI Realtime wire frame. -fn frame(json: serde_json::Value) -> WireEvent { - WireEvent(bytes::Bytes::from( - serde_json::to_vec(&json).expect("a frame serializes"), - )) -} - -/// The upstream's usage report closing one turn: `a_in`/`t_in` audio/text in, `a_out`/`t_out` out. -fn usage(a_in: u64, t_in: u64, a_out: u64, t_out: u64) -> WireEvent { - frame(serde_json::json!({ - "type": "response.done", - "response": { "usage": { - "input_token_details": { "audio_tokens": a_in, "text_tokens": t_in }, - "output_token_details": { "audio_tokens": a_out, "text_tokens": t_out }, - }}, - })) -} - -/// The upstream frames a plan writes, as one string. -fn upstream_text(plan: &crate::runtime::Outbound) -> String { - plan.upstream - .iter() - .map(|w| String::from_utf8_lossy(&w.0).to_string()) - .collect::>() - .join("\n") -} - -/// The ledger lane a witness session's turns land under: this plane's key, the separator, the model. -fn lane() -> String { - format!("streaming\u{1f}{MODEL}") -} - -/// What `key` has ledgered under the witness lane for `class`, or `None`. -fn ledgered(host: &FixtureHost, key: &str, class: &str) -> Option { - host.ledger_rows(key) - .get(&(lane(), class.to_string())) - .copied() -} - -// ── THE WITNESSES ─────────────────────────────────────────────────────────────────────────────── - -/// ARRIVAL: each call at the served door is one unit, and each is reported once. -async fn each_served_open_is_one_unit_reported_once() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - for call in ["call-arrival-1", "call-arrival-2"] { - let resp = open_governed(served_open(&rt, &host, call, None)).await; - assert_eq!( - resp.status(), - axum::http::StatusCode::NOT_IMPLEMENTED, - "{call}: the served open succeeds; the provider leg is uncomposed here" - ); - } - assert_eq!( - host.finished_requests().len(), - 2, - "two calls at the door are two units, each reported exactly once" - ); - 2 -} - -/// DECODE: the plane reads what a served session's client sends, and a frame it cannot read is -/// carried to no upstream at all. -async fn a_frame_the_plane_cannot_read_is_carried_nowhere() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - let (core, _handle) = open_session(&rt, &host, "call-decode", None); - - let read = core.on_client_frame(frame(serde_json::json!({ - "type": "input_audio_buffer.append", "audio": "AAAA" - }))); - assert!( - upstream_text(&read).contains("input_audio_buffer.append"), - "a client event the plane reads is carried to the upstream: {}", - upstream_text(&read) - ); - - let unread = core.on_client_frame(WireEvent(bytes::Bytes::from_static(b"not a frame {"))); - assert!( - unread.upstream.is_empty(), - "a frame the plane cannot read reaches the upstream on no wire: {}", - upstream_text(&unread) - ); - 1 -} - -/// AUTHENTICATE: the session answers for the key the door resolved and for nobody else — its turns -/// land on that key's own ledger, and a caller the door resolved no key for is attributed nothing. -async fn the_session_answers_for_the_key_the_door_resolved() -> u64 { - let host = Arc::new(FixtureHost::new().governed()); - let rt = hosted(&host); - let presenting = caller("vk-presenting"); - - let (keyed, _h1) = open_session(&rt, &host, "call-auth-keyed", Some(&presenting)); - let _ = keyed.on_server_frame(usage(0, 0, 7, 0)).await; - assert_eq!( - ledgered(&host, "vk-presenting", "audio_tokens_out"), - Some(7), - "the turn lands on the key the door resolved" - ); - - let (anonymous, _h2) = open_session(&rt, &host, "call-auth-anon", None); - let _ = anonymous.on_server_frame(usage(0, 0, 9, 0)).await; - assert_eq!( - ledgered(&host, "vk-presenting", "audio_tokens_out"), - Some(7), - "a session the door resolved no key for is attributed to nobody, the presenting key included" - ); - assert!( - host.ledger_usage("").is_none() && host.ledger_usage("anonymous").is_none(), - "and no anonymous principal is invented to carry it" - ); - 2 -} - -/// VERIFY (the session plane's shape): the destination the open-pass gate admitted is the destination -/// the session is metered under — the gate and the ledger read one model. -async fn the_destination_the_gate_judged_is_the_one_metered() -> u64 { - let host = Arc::new(FixtureHost::new().governed()); - let rt = hosted(&host); - let key = caller("vk-verify"); - let (core, _handle) = open_session(&rt, &host, "call-verify", Some(&key)); - let _ = core.on_server_frame(usage(0, 0, 5, 0)).await; - let rows = host.ledger_rows("vk-verify"); - assert_eq!( - rows.keys() - .map(|(lane, _)| lane.as_str()) - .collect::>(), - vec![lane().as_str()], - "every count lands under the lane of the one destination the gate judged: {rows:?}" - ); - 1 -} - -/// APPROVE: a key valid for the door but granted no session scope is refused before the gate; a -/// granted key reaches it. -async fn a_key_granted_no_session_never_reaches_the_gate() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - let ungranted = busbar_contract::records::VirtualKey { - allowed_scopes: Some(vec![busbar_contract::records::ScopeRef::pool("fast")]), - ..caller("vk-no-session") - }; - let refused = open_governed(served_open(&rt, &host, "call-approve-no", Some(ungranted))).await; - assert_eq!( - refused.status(), - axum::http::StatusCode::FORBIDDEN, - "a key holding no session scope is refused with the plane's own answer" - ); - assert!(host.audit_log().is_empty(), "and nothing was opened for it"); - - let granted = busbar_contract::records::VirtualKey { - allowed_scopes: Some(vec![busbar_contract::records::ScopeRef { - kind: "session".to_string(), - value: VOICE_POOL.to_string(), - }]), - ..caller("vk-session") - }; - let opened = open_governed(served_open(&rt, &host, "call-approve-yes", Some(granted))).await; - assert_eq!( - opened.status(), - axum::http::StatusCode::NOT_IMPLEMENTED, - "a key granted the session reaches the governed open" - ); - 1 -} - -/// ADMIT: a caller whose chain already reads dry is refused at the open — after the gate, and before -/// any durable row or account exists — while a caller with room opens. -async fn a_dry_key_is_refused_at_the_open_past_the_gate() -> u64 { - let host = Arc::new(FixtureHost::new().governed().with_count_cap(10)); - let rt = hosted(&host); - - // Spend the key's chain dry on a first session. - let key = caller("vk-admit"); - let (core, _handle) = open_session(&rt, &host, "call-admit-spend", Some(&key)); - let plan = core.on_server_frame(usage(0, 0, 12, 0)).await; - assert!( - plan.close, - "the turn that dried the chain closes its carrier" - ); - - let refused = open_governed(served_open(&rt, &host, "call-admit-dry", Some(key))).await; - assert_eq!( - refused.status(), - axum::http::StatusCode::PAYMENT_REQUIRED, - "a dry key is refused at the open" - ); - let rows_before = host.audit_log().len(); - let room = open_governed(served_open( - &rt, - &host, - "call-admit-room", - Some(caller("vk-room")), - )) - .await; - assert_eq!( - room.status(), - axum::http::StatusCode::NOT_IMPLEMENTED, - "a key with room opens" - ); - assert_eq!( - host.audit_log().len(), - rows_before + 1, - "only the open that was admitted wrote a row" - ); - 3 -} - -/// ROUTE (the session plane's shape): a served telephony call relays the provider's audio down to the -/// caller and the caller's audio up to the provider, the caller speaking the carrier's envelope. -async fn a_served_session_relays_both_directions() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - let proxy = begin_telephony( - &rt, - OpenAiRealtimeCodec, - "acct-witness", - "call-route", - g711_config(), - None, - 1, - ) - .unwrap_or_else(|e| panic!("the served door opens the telephony session: {e}")); - - let legs = crate::testkit::telephony::place_call(proxy, &[0x7f; 160]).await; - assert!( - legs.to_caller.iter().any(|v| v["event"] == "media"), - "the provider's audio reached the client: {:?}", - legs.to_caller - ); - assert!( - legs.to_far_end - .iter() - .any(|v| v["type"] == "input_audio_buffer.append"), - "the client's audio reached the provider: {:?}", - legs.to_far_end - ); - 1 -} - -/// METER: one served turn is ledgered on the presenting key per class the plane declares, the two -/// text halves on their own classes rather than summed. -async fn a_served_turn_is_ledgered_per_declared_class() -> u64 { - let host = Arc::new(FixtureHost::new().governed()); - let rt = hosted(&host); - let key = caller("vk-meter"); - let (core, _handle) = open_session(&rt, &host, "call-meter", Some(&key)); - let _ = core.on_server_frame(usage(10, 3, 20, 4)).await; - for (class, count) in [ - ("audio_tokens_in", 10), - ("text_tokens_in", 3), - ("audio_tokens_out", 20), - ("text_tokens_out", 4), - ] { - assert_eq!( - ledgered(&host, "vk-meter", class), - Some(count), - "the served turn's `{class}` lands on the presenting key, exactly" - ); - } - 1 -} - -/// AUDIT: a served open lands exactly one admin-audit row, naming the session and its principal. -async fn a_served_open_lands_one_audit_row() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - let resp = open_governed(served_open(&rt, &host, "call-audit", None)).await; - assert_eq!(resp.status(), axum::http::StatusCode::NOT_IMPLEMENTED); - let log = host.audit_log(); - assert_eq!(log.len(), 1, "one served open, one row: {log:?}"); - let row = &log[0]; - assert_eq!( - ( - row.action.as_str(), - row.resource.as_str(), - row.outcome.as_str(), - row.principal.as_str() - ), - ( - crate::mount::SESSION_AUDIT_ACTION, - "streaming:call-audit", - "applied", - "acct-witness" - ), - "the row names the mutation, the session, its outcome and its principal" - ); - 1 -} - -/// EXIT: a served session ends once — the turn still open when the carrier ends is ledgered with the -/// session, and the durable row is settled and evicted. -async fn a_served_session_ends_once_and_settles_its_open_turn() -> u64 { - let host = Arc::new(FixtureHost::new().governed()); - let rt = hosted(&host); - let key = caller("vk-exit"); - let (core, handle) = open_session(&rt, &host, "call-exit", Some(&key)); - - // Two seconds of PCM16 at 24 kHz (96 000 zero bytes, which base64 spells as 128 000 `A`s), - // admitted uplink, with no usage report to close the turn. - let audio = "A".repeat(128_000); - let _ = core.on_client_frame(frame(serde_json::json!({ - "type": "input_audio_buffer.append", "audio": audio - }))); - assert_eq!( - ledgered(&host, "vk-exit", "audio_seconds_in"), - None, - "nothing is ledgered while the turn is open" - ); - - crate::runtime::serve_to_teardown(Arc::clone(&core), handle, async {}, || 9).await; - assert_eq!( - ledgered(&host, "vk-exit", "audio_seconds_in"), - Some(2), - "the turn open at the end is ledgered with the session, in the class's own seconds" - ); - assert!( - rt.bind_session("acct-witness", "call-exit").get().is_none(), - "the durable row is settled and evicted: the session ended" - ); - 1 -} - -/// AUDIT-CHAIN: the served session's durable row seals at genesis, a turn checkpoint bumps it, and it -/// reads back to its owner and to nobody else. -async fn the_served_sessions_row_seals_at_genesis_and_reads_back() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - let (_core, handle) = open_session(&rt, &host, "call-chain", None); - let row = handle - .get() - .expect("the genesis row the served open sealed reads back"); - assert_eq!( - (row.turns, row.owner.as_str()), - (0, "acct-witness"), - "a fresh genesis row, attributed to the session's principal" - ); - assert_eq!(handle.bump_turn(2).expect("the owner checkpoints"), 1); - assert_eq!( - rt.bind_session("acct-witness", "call-chain") - .get() - .map(|r| r.turns), - Some(1), - "a reattach reads the checkpoint back off the durable chain" - ); - assert!( - rt.bind_session("mallory", "call-chain").get().is_none(), - "a foreign owner reads nothing" - ); - 1 -} - -/// GOVERNANCE-BUDGET: a served session's turns are charged to the presenting key, the turn that dries -/// its chain closes the carrier, and that key's next open is refused. -async fn a_served_session_is_charged_to_the_presenting_key_and_closed_dry() -> u64 { - let host = Arc::new(FixtureHost::new().governed().with_count_cap(50)); - let rt = hosted(&host); - let key = caller("vk-budget"); - let (core, _handle) = open_session(&rt, &host, "call-budget", Some(&key)); - assert!( - !core.on_server_frame(usage(0, 0, 30, 0)).await.close, - "30 of 50: the session stays open" - ); - assert!( - core.on_server_frame(usage(0, 0, 30, 0)).await.close, - "60 of 50: the chain is dry and the carrier closes" - ); - assert!(core.carrier().is_closed(), "the carrier is hard-closed"); - assert_eq!( - ledgered(&host, "vk-budget", "audio_tokens_out"), - Some(60), - "every delivered count is charged to the presenting key, the drying turn included" - ); - let next = open_governed(served_open(&rt, &host, "call-budget-next", Some(key))).await; - assert_eq!( - next.status(), - axum::http::StatusCode::PAYMENT_REQUIRED, - "the dry key's next session is refused at the open" - ); - 2 -} - -/// METRICS: a served open is reported under the plane's own key, its dialect and its pool, with a -/// duration sample. -async fn a_served_open_is_reported_under_the_planes_own_labels() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - let _ = open_governed(served_open(&rt, &host, "call-metrics", None)).await; - let reported = host.finished_requests(); - assert_eq!(reported.len(), 1, "one served open, reported once"); - let r = &reported[0]; - assert_eq!( - ( - r.plane.as_str(), - r.ingress_protocol.as_str(), - r.pool.as_str() - ), - (crate::PLANE_KEY, crate::OPENAI_REALTIME, VOICE_POOL), - "the plane's key, the dialect the door speaks and the pool it serves" - ); - assert!( - r.seconds.is_finite() && r.seconds >= 0.0, - "a duration sample rides the same report: {}", - r.seconds - ); - 1 -} - -/// BREAKER-FASTFAIL: a served session's provider dial is refused before any socket once the -/// provider's cell has tripped, with the cell's own `Retry-After`. -async fn a_served_sessions_dial_fast_fails_on_a_tripped_cell() -> u64 { - let host = Arc::new(FixtureHost::new()); - let rt = hosted(&host); - let (_core, _handle) = open_session(&rt, &host, "call-breaker", None); - let pool = stream_breaker_key("witness-provider"); - - // A target busbar's own guard refuses is a definitive failure: it trips the provider's cell. - let first = dial_provider(&*host, &pool, 0, "wss://127.0.0.1/", Default::default()) - .await - .err(); - assert!( - matches!(first, Some(DialProviderError::Dial(_))), - "the guard-refused dial fails: {first:?}" - ); - assert_ne!( - host.breaker_state(&pool, 0), - host.breaker_state("never-dialled", 0), - "the provider's cell is no longer Closed" - ); - - // The next dial is refused at admission, before the guard or any socket is reached. - match dial_provider(&*host, &pool, 0, "wss://127.0.0.1/", Default::default()).await { - Err(DialProviderError::BreakerOpen { retry_after_secs }) => assert!( - retry_after_secs >= 1, - "the fast-fail carries the cell's own Retry-After" - ), - other => panic!( - "a tripped cell must fast-fail before the dial, got {:?}", - other.err() - ), - } - 1 -} diff --git a/crates/busbar-voice/src/topology/minter_https.rs b/crates/busbar-voice/src/topology/minter_https.rs deleted file mode 100644 index b69424730d..0000000000 --- a/crates/busbar-voice/src/topology/minter_https.rs +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE CONCRETE HTTPS EPHEMERAL-SECRET MINTER for the browser WebRTC sideband. -//! -//! Implements the [`TokenMinter`] port over the substrate egress engine: a one-shot -//! `POST /v1/realtime/client_secrets` carrying the REAL provider key, from which the browser-facing -//! `ek_` client secret is returned. The real key stays server-side — it authenticates only the -//! busbar↔provider hop and never appears in the returned [`EphemeralToken`]. -//! -//! The mint stamps two guards the raw token carries no policy for on its own: the requested secret -//! lifetime is clamped to the provider's accepted window, and an `OpenAI-Safety-Identifier` header -//! binds the minted secret to the calling identity so it is attributable and rate-limitable to that -//! caller rather than a shared blob. The returned value is asserted to carry the `ek_` prefix before -//! it is handed back. - -use crate::ir::config::SessionConfig; -use crate::topology::webrtc::{EphemeralToken, MintError, TokenMinter}; -use async_trait::async_trait; -use busbar_kernel::egress::engine::{send_bounded, EngineClient}; -use bytes::Bytes; -use http_body_util::{BodyExt, Full}; -use std::time::Duration; - -/// The default requested secret lifetime when the caller pins none. -const DEFAULT_TTL_SECS: u64 = 600; -/// The provider's minimum accepted secret lifetime. -const MIN_TTL_SECS: u64 = 10; -/// The provider's maximum accepted secret lifetime. -const MAX_TTL_SECS: u64 = 7200; -/// The prefix every ephemeral client secret the provider mints carries. -const EK_PREFIX: &str = "ek_"; -/// The header binding a minted secret to the caller identity. -const SAFETY_IDENTIFIER_HEADER: &str = "OpenAI-Safety-Identifier"; -/// The bound on the whole mint exchange up to the response head plus its small body read. -const MINT_DEADLINE: Duration = Duration::from_secs(30); -/// The client-secrets endpoint path on the provider. -const CLIENT_SECRETS_PATH: &str = "/v1/realtime/client_secrets"; - -/// MINTS the browser's ephemeral client secret over a real HTTPS call to the provider's -/// client-secrets endpoint, holding the real key server-side. -/// -/// The `base_url` + owned [`EngineClient`] are constructor inputs so the composition root binds the -/// production provider origin while a test points them at a loopback server; the same request path -/// runs against both. `safety_identifier` is the caller-identity binding stamped on every mint; -/// `requested_ttl_secs` is the desired secret lifetime before clamping (`None` ⇒ the default). -pub struct HttpsTokenMinter { - client: EngineClient, - base_url: String, - api_key: String, - safety_identifier: String, - requested_ttl_secs: Option, -} - -impl HttpsTokenMinter { - /// Build a minter over an already-assembled egress client. `base_url` is the provider origin - /// (scheme + authority, e.g. `https://api.openai.com`); `api_key` is the REAL provider key held - /// server-side; `safety_identifier` is the caller-identity binding; `requested_ttl_secs` is the - /// desired secret lifetime (`None` ⇒ [`DEFAULT_TTL_SECS`]), clamped to the accepted window on mint. - pub fn new( - client: EngineClient, - base_url: impl Into, - api_key: impl Into, - safety_identifier: impl Into, - requested_ttl_secs: Option, - ) -> Self { - HttpsTokenMinter { - client, - base_url: base_url.into(), - api_key: api_key.into(), - safety_identifier: safety_identifier.into(), - requested_ttl_secs, - } - } - - /// The requested lifetime clamped to the provider's accepted `[MIN, MAX]` window. - fn clamped_ttl_secs(&self) -> u64 { - self.requested_ttl_secs - .unwrap_or(DEFAULT_TTL_SECS) - .clamp(MIN_TTL_SECS, MAX_TTL_SECS) - } -} - -/// The provider's client-secret response: the `ek_` value and its absolute expiry in unix seconds. -#[derive(serde::Deserialize)] -struct ClientSecretResponse { - value: String, - #[serde(default)] - expires_at: u64, -} - -#[async_trait] -impl TokenMinter for HttpsTokenMinter { - async fn mint(&self, config: &SessionConfig) -> Result { - let ttl_secs = self.clamped_ttl_secs(); - let body = serde_json::json!({ - "expires_after": { "anchor": "created_at", "seconds": ttl_secs }, - "session": config, - }); - let body_bytes = serde_json::to_vec(&body).map_err(|e| { - MintError::Provider(format!("mint request body did not serialize: {e}")) - })?; - - let uri = format!( - "{}{}", - self.base_url.trim_end_matches('/'), - CLIENT_SECRETS_PATH - ); - let req = http::Request::builder() - .method(http::Method::POST) - .uri(&uri) - .header(http::header::CONTENT_TYPE, "application/json") - .header( - http::header::AUTHORIZATION, - format!("Bearer {}", self.api_key), - ) - .header(SAFETY_IDENTIFIER_HEADER, &self.safety_identifier) - .body(Full::new(Bytes::from(body_bytes))) - .map_err(|e| MintError::Provider(format!("mint request did not build: {e}")))?; - - let deadline = tokio::time::Instant::now() + MINT_DEADLINE; - let resp = send_bounded(&self.client, req, deadline) - .await - .map_err(|e| MintError::Provider(e.into_cause()))?; - - let status = resp.status(); - let raw = tokio::time::timeout_at(deadline, resp.into_body().collect()) - .await - .map_err(|_| { - MintError::Provider( - "client-secret response was not read before the deadline".into(), - ) - })? - .map_err(|e| MintError::Provider(format!("client-secret response body failed: {e}")))? - .to_bytes(); - - if !status.is_success() { - return Err(MintError::Provider(format!( - "client-secret endpoint returned {status}" - ))); - } - - let parsed: ClientSecretResponse = serde_json::from_slice(&raw).map_err(|e| { - MintError::Provider(format!("client-secret response did not parse: {e}")) - })?; - - // The browser-facing invariant: only an `ek_` secret ever leaves this boundary. A response - // whose value lacks the prefix is refused rather than handed on as if it were a client secret. - if !parsed.value.starts_with(EK_PREFIX) { - return Err(MintError::Provider( - "client-secret response value is not an ek_ ephemeral secret".into(), - )); - } - - Ok(EphemeralToken { - value: parsed.value, - expires_at_unix: parsed.expires_at, - }) - } -} - -#[cfg(all(test, feature = "test-support"))] -#[path = "tests/minter_https_tests.rs"] -mod minter_https_tests; diff --git a/crates/busbar-voice/src/topology/mod.rs b/crates/busbar-voice/src/topology/mod.rs deleted file mode 100644 index aee5d6e310..0000000000 --- a/crates/busbar-voice/src/topology/mod.rs +++ /dev/null @@ -1,435 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! THE TWO TOPOLOGIES the voice runtime exposes (design `BUSBAR-1.6.0.md` #18/#45), behind the `runtime` feature. -//! -//! * [`webrtc`] — the BROWSER WebRTC sideband: busbar mints the ephemeral token and holds a persistent -//! sideband control channel owning tools + instructions; the browser's MEDIA path is peer-to-peer, so -//! busbar is mint/guard + control, NOT a media relay. -//! * [`telephony`] — a THIN WS PROXY: `g711_ulaw` end-to-end so 8 kHz passes straight through (no -//! resample), with barge-in truncate driven from the codec's playback marks. -//! -//! Both are assembled from a [`crate::runtime::VoiceRuntime`] via [`begin_session`], which opens the -//! presenting key's kernel account (fail-closed on a chain already dry) and the durable -//! [`SessionHandle`] before a frame flows. - -pub mod minter_https; -pub mod telephony; -/// The Twilio Media Streams messages belong to the plane crate's own `codec` module: they are a -/// GRAMMAR, not a dial — a total function from a frame to an IR event and back — so they live with -/// the IR they map onto. Re-exported here so `crate::topology::twilio::…` resolves to them. -/// The Twilio DIAL (the outbound call the webhook answers) is `telephony`, and it lives here. -pub use busbar_plane_streaming::codec::topology::twilio; -pub mod webrtc; - -#[cfg(test)] -mod tests; - -use crate::ir::codec::{DuplexReader, DuplexWriter}; -use crate::ir::config::SessionConfig; -use crate::runtime::carrier::Carrier; -use crate::runtime::scope::SessionHandle; -use crate::runtime::session::SessionCore; -use crate::runtime::VoiceRuntime; -use busbar_contract::transport::transport::{Transport, UpstreamWireKind}; -use busbar_contract::upstream::{CanonicalSignal, StatusClass}; -use busbar_kernel::egress::duplex_ws::{self, DialError}; -use busbar_kernel::net_guard::GuardPolicy; -use busbar_kernel::plane::handle_engine::HandleEngineError; -use busbar_kernel::plane_host::{ - run_gauntlet_session, BreakerHost, DispatchScope, GauntletPlane, GauntletRequest, VerifyOutcome, -}; -use futures::{Sink, Stream}; -use std::sync::Arc; - -/// THE VOICE PLANE'S BREAKER-CELL KEY for one provider dial target. The `stream:` prefix is the -/// plane-qualified keyspace rule (the `tool:` / `agent:` precedent — a voice cell can never collide -/// with an MCP tool cell, an A2A agent cell, or a bare LLM pool name), and the id is the operator's -/// provider/pool name that a refusal names. Formed HERE (not in the neutral substrate) so the voice -/// plane keys its own cell without a substrate helper and stays strong-form deletable. -#[must_use] -pub fn stream_breaker_key(provider: &str) -> String { - format!("stream:{provider}") -} - -/// Why a governed provider dial did not open a socket. -#[derive(Debug)] -pub enum DialProviderError { - /// The `(pool, lane)` breaker cell was OPEN, so admission was refused BEFORE any socket — the - /// fast-fail leg. Carries the honest `Retry-After` read off the cell's own cooldown. - BreakerOpen { - /// Seconds until the cell's cooldown expires (floored at 1). - retry_after_secs: u64, - }, - /// The dial itself failed (guard-refused / connect / TLS / handshake). The failure has already - /// been recorded into the breaker cell. - Dial(DialError), -} - -impl std::fmt::Display for DialProviderError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - DialProviderError::BreakerOpen { retry_after_secs } => write!( - f, - "streaming provider dial refused: breaker open (retry after {retry_after_secs}s)" - ), - DialProviderError::Dial(e) => write!(f, "streaming provider dial failed: {e:?}"), - } - } -} - -impl std::error::Error for DialProviderError {} - -/// The canonical breaker signal one [`DialError`] means. A target busbar's OWN net-guard or URL parse -/// refused is a DEFINITIVE (hard-down) failure — that configured/derived target can never be dialed -/// for this session, so its cell earns the sticky cooldown, exactly as an auth/billing hard-down does. -/// A connect / TLS / handshake failure to a pinned address is a TRANSIENT upstream signal (the cell -/// trips only once the error-rate window crosses its threshold), the same disposition the model -/// plane's own dispatch records a network blip under. -fn dial_signal(e: &DialError) -> CanonicalSignal { - let class = match e { - DialError::Guard(_) | DialError::Url(_) => StatusClass::Auth, - DialError::Connect(_) | DialError::Tls(_) | DialError::Handshake(_) => StatusClass::Network, - }; - CanonicalSignal { - class, - provider_signal: None, - retry_after: None, - } -} - -/// DIAL THE PROVIDER SIDEBAND/UPSTREAM WSS through the neutral, net-guarded WS transport — the plane's -/// one door to an outbound duplex socket, and the close of the egress-audit finding that voice's -/// provider WSS never went through net-guard. -/// -/// The plane SELECTS [`Transport::WebSocket`], resolves the axis to its neutral wire shape -/// ([`UpstreamWireKind::Duplex`]) and lets the SUBSTRATE open the socket: the dialer -/// resolves-then-pins-then-guards `url` and hands back the message `Stream`/`Sink>` pair the -/// session pump (`serve_messages`) consumes. The plane holds no socket, resolver or WS framing of its -/// own — it feeds the returned pair to a topology's provider leg -/// ([`telephony::TelephonyProxy::run`](crate::topology::telephony::TelephonyProxy::run) or the webrtc -/// sideband) and keeps ONLY data/session/media logic. -/// -/// `policy` is the outbound trust posture (a public provider `wss://` takes the fail-closed -/// [`GuardPolicy::default`]); the guard NEVER opens a socket to a target it did not pin. -/// -/// THE BREAKER RIDES BENEATH THE DIAL (the voice-client cell): before any socket, the `(pool, lane)` -/// cell is probed through `host.breaker_admit` — an OPEN cell fast-fails in microseconds with the -/// cell's own `Retry-After` (never waiting out a dial timeout against a target already known down). -/// Past admission the attempt is counted on `busbar_upstream_attempts_total`, and the dial's outcome -/// is FOLDED back into the same cell: a clean open records a success (diluting the error window / -/// closing a recovery probe), a failure records its canonical signal (a guard/URL refusal opens the -/// cell hard-down; a connect/TLS/handshake blip is transient). `pool` is the plane's breaker-cell key -/// (see [`stream_breaker_key`]); `lane` is the member position (0 for a degenerate cell). -pub async fn dial_provider( - host: &dyn BreakerHost, - pool: &str, - lane: usize, - url: &str, - policy: GuardPolicy, -) -> Result< - ( - impl Stream> + Unpin, - impl Sink, Error = ()> + Unpin + Send + 'static, - ), - DialProviderError, -> { - // FAST-FAIL ADMISSION FIRST: probe the cell through the host seam. An OPEN cell refuses here with - // its own cooldown as `Retry-After` — no socket, no dial timeout. The probe is scoped so any - // recovery probe it wins is released the instant this block ends; the in-place record below is the - // authoritative fold, the documented `breaker_record_*` fallback disposition. - { - let scope = DispatchScope::new(); - if host - .breaker_admit(&scope, pool.as_bytes(), lane as u32) - .is_err() - { - return Err(DialProviderError::BreakerOpen { - retry_after_secs: host.breaker_retry_after_secs(pool, lane), - }); - } - } - - // ADMITTED: count the dispatch attempt on the voice-client leg (both labels operator-configured - // and bounded — a provider/pool name and a small lane integer — so the series count stays bounded). - busbar_kernel::telemetry::upstream_attempt_on(pool, &lane.to_string()); - - // The axis pins `WebSocket` to the full-duplex wire; the `else` is unreachable by construction (a - // closed axis), expressed as a refusal rather than a panic so a mis-selection fails closed. - let Some(UpstreamWireKind::Duplex) = Transport::WebSocket.upstream_wire() else { - let e = DialError::Url(url.to_string()); - host.breaker_record_signal(pool, lane, &dial_signal(&e)); - return Err(DialProviderError::Dial(e)); - }; - - match duplex_ws::dial(url, policy).await { - Ok((stream, sink)) => { - host.breaker_record_success(pool, lane); - // `sink_map_err(|_| ())`: the substrate dialer's own Sink `Error` associated type is an - // opaque `impl Trait` detail with no `Send` bound of its own (it happens to be `Send` today, - // but nothing in its signature promises that) — and `TelephonyProxy::run` (the one consumer) - // needs `POut::Error: Send` to spawn the drain task. Discarding the write-failure detail into - // a bare `()` is a small, honest price: a write failure already means the socket is going - // away (the pump's own EOF/close handling is what actually tears the session down), so no - // caller of this fn reads the write error's content today. - Ok((stream, futures::SinkExt::sink_map_err(sink, |_| ()))) - } - Err(e) => { - host.breaker_record_signal(pool, lane, &dial_signal(&e)); - Err(DialProviderError::Dial(e)) - } - } -} - -/// Why a session failed to start before any frame flowed. -#[derive(Debug)] -pub enum StartError { - /// The OPEN-PASS gauntlet gate ([`run_gauntlet_session`]) REFUSED the session's destination BEFORE - /// any account/durable open — zero bytes, zero charge. The verify-strictly-before-charge invariant. - DestinationRefused, - /// The kernel's budget view reads the presenting key's chain already dry — fail closed, never open. - BudgetRefused, - /// The durable [`SessionHandle`] could not be opened (the engine rejected the genesis). - Durable(HandleEngineError), -} - -impl std::fmt::Display for StartError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - StartError::DestinationRefused => { - write!( - f, - "streaming session destination refused at the open-pass gate (fail closed)" - ) - } - StartError::BudgetRefused => { - write!(f, "streaming session budget refused (fail closed)") - } - StartError::Durable(e) => write!(f, "streaming session durable open failed: {e:?}"), - } - } -} - -impl std::error::Error for StartError {} - -/// THE VOICE PLANE's [`GauntletPlane`] for a SESSION open — its contribution to the shared open-pass -/// gauntlet gate, which runs BEFORE the account/durable open (a refusal there costs zero bytes and -/// zero charge). The plane keeps NO private destination policy of its own (1.5.5 had none; model -/// access is the key/group pool ACL at the scope step), so its `verify_destination` (stage 2) -/// proceeds; a kernel-loop session runner registered under the plane's capability key is what may -/// refuse at this gate. `drive` (the one-shot stages 4+5) is UNREACHABLE on the session path — -/// [`run_gauntlet_session`] only runs the gate, never `drive` — so it fails closed with a neutral 500 -/// if a future refactor ever mis-routed a session opener through the one-shot path. -pub(crate) struct SessionGauntlet; - -#[async_trait::async_trait] -impl GauntletPlane for SessionGauntlet { - fn verify_destination(&self, _req: &GauntletRequest<'_>) -> VerifyOutcome { - VerifyOutcome::Proceed - } - - async fn drive(self: Box, _req: GauntletRequest<'_>) -> axum::response::Response { - // Never reached on the session path (the opener runs only the admission gate). Fail closed. - axum::response::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(axum::body::Body::from( - "session gauntlet never drives a one-shot response", - )) - .expect("static fault response builds") - } - - /// THE VOICE (STREAMING) PLANE'S CAPABILITY KEY on the composition-tier host-selection seam (loop - /// unification, #28). The plane DECLARES its identity here; the LOOP CHOICE is the composition - /// root's — it registers a kernel-loop SESSION runner under this same key ([`crate::PLANE_KEY`]) or - /// leaves it UNSET. With W2.b the root flips `voice` on, so this reported key routes the session - /// open-pass admit through the unified kernel loop; the admit gate is byte-identical either way, so - /// declaring the key is safe with or without the flip. - fn capability_key(&self) -> Option<&str> { - Some(crate::PLANE_KEY) - } -} - -/// BEGIN a governed session, common to both topologies: open the presenting key's kernel account -/// (fail-closed on a chain already dry), open the durable [`SessionHandle`] at genesis, and assemble -/// the [`SessionCore`] with the plane's locked config, chosen `codec`, and `carrier`. The caller then -/// serves the returned core over the neutral pump. -#[allow(clippy::too_many_arguments)] -pub fn begin_session( - rt: &VoiceRuntime, - codec: C, - owner: impl Into, - call_id: impl Into, - locked_config: Option, - carrier: Carrier, - meter: Option, - now: u64, -) -> Result<(Arc>, SessionHandle), StartError> -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - let gate: Box = Box::new(SessionGauntlet); - begin_session_through( - rt, - gate, - codec, - owner, - call_id, - locked_config, - carrier, - meter, - now, - ) -} - -/// [`begin_session`] over a given open-pass `gate`: the plane's own [`SessionGauntlet`] in production; -/// a test hands a refusing gate to witness that the budget is never asked on a refused session. -#[allow(clippy::too_many_arguments)] -pub(crate) fn begin_session_through( - rt: &VoiceRuntime, - gate: Box, - codec: C, - owner: impl Into, - call_id: impl Into, - locked_config: Option, - carrier: Carrier, - meter: Option, - now: u64, -) -> Result<(Arc>, SessionHandle), StartError> -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - // OPEN-PASS ADMISSION FIRST (verify STRICTLY before any charge): run the shared gauntlet gate at the - // TOP through `run_gauntlet_session`. On refuse NOTHING is opened — no account, no durable genesis, - // no socket — so a refused session costs ZERO bytes and ZERO charge. The session's budget check - // (the kernel account's open) fires only AFTER the gate clears, matching the LLM plane's real - // verify-before-admission-door order. - let destination = locked_config - .as_ref() - .and_then(|c| c.model.clone()) - .unwrap_or_default(); - let gov = busbar_contract::records::PlaneRequestCtx::default(); - let gauntlet_req = GauntletRequest { - gov: &gov, - destination: &destination, - correlation_id: 0, - charged_at: now, - started: std::time::Instant::now(), - }; - // The call-site the D3 witness pins: begin_session ACTUALLY calls run_gauntlet_session here. - run_gauntlet_session(gauntlet_req, gate).map_err(|_refusal| StartError::DestinationRefused)?; - - // Only past the gate: reserve/bind/open the live carrier. Factored into [`open_admitted_session`] - // so the inbound WS-accept seam — where the gauntlet has ALREADY run inside `accept_gauntlet`, - // strictly before the socket upgrades — reuses the SAME post-admit open without re-running (or - // duplicating) the gate. begin_session's own order is byte-identical: gauntlet, then this. - open_admitted_session( - rt, - codec, - owner, - call_id, - locked_config, - carrier, - meter, - now, - // NO BINDING FROM HERE. This entry point is the one a caller drives directly — a topology - // test, a conformance probe, the one-shot HTTP passes — and none of them is a session a - // composition root's table has an identifier for. The SERVED door mints one per session and - // hands it to the post-admit open below, which is the whole of what makes a served session a - // governed one. - None, - ) -} - -/// THE POST-ADMIT OPEN of a governed session — the reserve/bind/open half of [`begin_session`], -/// called ONLY after the open-pass gauntlet has already admitted the destination (verify strictly -/// before any charge). Opens the presenting key's kernel account (fail-closed on a chain already dry), -/// opens the durable [`SessionHandle`] at genesis, and assembles the [`SessionCore`]. NO gauntlet runs here: the -/// caller (`begin_session`, or the inbound WS-accept `accept_gauntlet` path) is responsible for having -/// run it first. A refused budget or a failed durable open returns before ANY durable row is committed -/// — so an aborted open, like a refused gauntlet, leaves no orphaned live session row. -/// -/// `governed` is the node's open-call table and the identifier this session is known to it by — the -/// composition root's own binding, minted per session at the served door -/// (`crate::mount::served_governed_session`). Every session this door opens over a socket carries -/// one, which is what makes "served" and "governed" the same set rather than two that overlap. -/// `None` is a deployment with no composition root behind it: every tool call is served in-process -/// and a client-authored result is carried upstream verbatim, exactly as before the wait existed. -#[allow(clippy::too_many_arguments)] -pub(crate) fn open_admitted_session( - rt: &VoiceRuntime, - codec: C, - owner: impl Into, - call_id: impl Into, - locked_config: Option, - carrier: Carrier, - meter: Option, - now: u64, - governed: Option, -) -> Result<(Arc>, SessionHandle), StartError> -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - // Captured before any move: `bind_session` and the exit-path audit call below both need their own - // copy, and the admin-audit row's principal/resource must name THIS session even though every - // downstream consumer (the durable handle, the audit call) takes owned strings. - let owner = owner.into(); - let call_id = call_id.into(); - - // The kernel's budget gate, AFTER admission: a caller whose chain is already dry never opens a - // session (fail closed). The account it opens is what each turn's counts are reported to. - let model = locked_config - .as_ref() - .and_then(|c| c.model.clone()) - .unwrap_or_default(); - let metering = match meter { - Some(meter) => meter.open(&model).map_err(|_| StartError::BudgetRefused)?, - None => None, - }; - - let handle = rt.bind_session(owner.clone(), call_id.clone()); - // A durable open that fails leaves a session that never opened: its counted session fee is - // given back (TODO 17(b), ARCHITECT R4) before the refusal. - if let Err(e) = handle.open(now) { - if let Some(metering) = metering { - metering.refund_open(); - } - return Err(StartError::Durable(e)); - } - - // ONE ADMIN-AUDIT ROW for this governed mutation — fired exactly once (this is the single `Ok` - // construction point below every early `?` return above), no-op on a runtime with no bound host - // (the pre-host/dev-default runtime). Voice posts no separate "kernel-floor" line at Admit the way - // BUSBAR-1.6.0.md's Admit step describes for an already-dialed provider push: a refused reserve above never - // reaches here, so nothing is owed and nothing is journaled for it. - rt.audit_session( - // The ONE action literal, named where the REJECTED twin names it (`mount::hook_tap` refuses a - // committed rewrite busbar cannot read back under this same action) — so the two outcomes of - // one mutation cannot drift into two spellings. - crate::mount::SESSION_AUDIT_ACTION, - &format!("streaming:{call_id}"), - "applied", - &owner, - ); - - let core = SessionCore::new( - codec, - metering, - Arc::clone(&rt.tools), - carrier, - locked_config, - ); - // The operator's hard wall-clock ceiling, enforced on the sweep tick beside the pump — only when - // the operator configured one (Q21a: no config, no limit). - let core = match rt.session_max_secs { - Some(secs) => core.with_session_ceiling(secs.get()), - None => core, - }; - // The binding, applied before the core is shared: a session that reached a socket ungoverned and - // was governed afterwards would have a window in which its pump could see a tool call it had no - // table to enter. - let core = Arc::new(match governed { - Some(g) => core.with_governed(g), - None => core, - }); - Ok((core, handle)) -} diff --git a/crates/busbar-voice/src/topology/telephony.rs b/crates/busbar-voice/src/topology/telephony.rs deleted file mode 100644 index 79f0967b1a..0000000000 --- a/crates/busbar-voice/src/topology/telephony.rs +++ /dev/null @@ -1,309 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! TOPOLOGY B — the THIN TELEPHONY WS PROXY (design `BUSBAR-1.6.0.md` #18/#45). -//! -//! busbar sits between the telephony leg (the phone/carrier media stream) and the provider's Realtime -//! upstream, proxying frames both ways while metering, governing tools, and driving barge-in. It -//! chooses `g711_ulaw` END-TO-END so 8 kHz µ-law passes straight through with NO resample; the barge-in -//! truncate `audio_end_ms` is computed from the codec's playback marks (`DecodeState::flush_playback`) -//! and the queued outbound audio is flushed on `speech_started` — the truncate bookkeeping the codec -//! exposes, driven by [`crate::runtime::session::SessionCore`]. -//! -//! WIRING. Two sockets, four directions. The provider socket's WRITE side is shared by two producers — -//! the downlink-facing [`VoiceSession`] (tool results / barge-in cancel) and the [`UplinkForwarder`] -//! (client audio → upstream) — so it is funnelled through one channel into a single writer. The client -//! socket's write side is the downlink, driven by the session [`Carrier`]. -//! -//! THE CARRIER'S ENVELOPE. The telephony leg's caller speaks Twilio Media Streams, not the realtime -//! dialect: every caller frame is read through the envelope ([`TwilioBridge::from_caller`]), so its -//! `media` audio reaches the far end as an uplink append and is counted, and its `stop` (or a frame -//! the envelope refuses) ends the call; every frame bound for the caller is rewritten into the -//! envelope ([`TwilioBridge::to_caller`]). The Gemini Live leg rides the same proxy with no envelope: -//! its caller speaks the far end's own dialect. - -use crate::ir::codec::{DuplexReader, DuplexWriter}; -use crate::ir::config::SessionConfig; -use crate::ir::media::AudioFormat; -use crate::runtime::carrier::Carrier; -use crate::runtime::scope::SessionHandle; -use crate::runtime::session::{SessionCore, UplinkForwarder, VoiceSession}; -use crate::runtime::VoiceRuntime; -use crate::topology::twilio::{CallerStep, TwilioBridge}; -use crate::topology::{begin_session, StartError}; -use busbar_kernel::ingress::byte_duplex::serve_messages; -use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; -use futures::future::{ready, Either}; -use futures::{Sink, Stream, StreamExt}; -use std::sync::{Arc, Mutex, PoisonError}; - -/// THE LOCKED CONFIG for a telephony leg: `g711_ulaw` on BOTH the input and output audio formats so the -/// 8 kHz µ-law carrier passes straight through with no resample. Callers overlay their own -/// instructions/tools onto the returned config before locking it. -#[must_use] -pub fn g711_config() -> SessionConfig { - SessionConfig { - input_audio_format: Some(AudioFormat::G711Ulaw), - output_audio_format: Some(AudioFormat::G711Ulaw), - ..SessionConfig::default() - } -} - -/// A LIVE TELEPHONY PROXY — the two planes to serve (one per socket), the funnels between them, the -/// shared session core, and the durable handle. Build it with [`begin_telephony`]; drive it with -/// [`TelephonyProxy::run`]. -pub struct TelephonyProxy { - /// The shared session core (metering, tools, barge-in). - pub core: Arc>, - /// The durable session binding to close at teardown. - pub handle: SessionHandle, - /// The downlink-facing plane — serve it over the PROVIDER socket. - downlink_plane: Arc>, - /// The uplink-facing plane — serve it over the CLIENT socket. - uplink_plane: Arc>, - /// The provider-write funnel receiver (both producers merge here). - upstream_rx: UnboundedReceiver>, - /// The provider-write funnel sender handed to the downlink plane's serve sink. - upstream_tx: UnboundedSender>, - /// The client-write (downlink) funnel receiver, fed by the session carrier. - downlink_rx: UnboundedReceiver>, - /// The carrier's envelope on the caller's side: `Some` on the telephony leg, `None` on a leg - /// whose caller speaks the far end's dialect. - envelope: Option>>, -} - -/// BEGIN a telephony proxy: lock the `g711`-based config, open the governed session (kernel account + -/// durable handle), and build the two planes + the funnels between them. The provider/client sockets are bound -/// later by [`TelephonyProxy::run`]. -#[allow(clippy::too_many_arguments)] -pub fn begin_telephony( - rt: &VoiceRuntime, - codec: C, - owner: impl Into, - call_id: impl Into, - locked_config: SessionConfig, - meter: Option, - now: u64, -) -> Result, StartError> -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - // The client-write (downlink) funnel: the carrier relays server→client audio here. - let (downlink_tx, downlink_rx) = unbounded::>(); - let carrier = Carrier::with_downlink(downlink_tx); - - let (core, handle) = begin_session( - rt, - codec, - owner, - call_id, - Some(locked_config), - carrier, - meter, - now, - )?; - - // The provider-write funnel: both the downlink plane's `out` and the uplink forwarder merge here. - let (upstream_tx, upstream_rx) = unbounded::>(); - let downlink_plane = Arc::new(VoiceSession::new(Arc::clone(&core))); - let uplink_plane = Arc::new(UplinkForwarder::new(Arc::clone(&core), upstream_tx.clone())); - - Ok(TelephonyProxy { - core, - handle, - downlink_plane, - uplink_plane, - upstream_rx, - upstream_tx, - downlink_rx, - envelope: Some(Arc::default()), - }) -} - -/// POST-ADMIT variant of [`begin_telephony`], for a caller that ALREADY ran the open-pass gauntlet -/// (the inbound WS-accept seam, `accept_gauntlet`) — mirrors [`crate::topology::open_admitted_session`]'s -/// contract exactly: no gauntlet runs here, so a caller that already admitted the destination does not -/// re-run (or double-charge) it. Used for BOTH the g711 telephony leg and the Gemini Live thin-duplex -/// leg — the wiring is identical; only the codec and the locked config differ, and both are the -/// caller's to choose. -/// -/// `governed` is the served door's own binding — the node's open-call table and the identifier this -/// session is known to it by — and it is carried straight through to the post-admit open, so a -/// dialed proxy is governed on exactly the terms the other two served legs are. -/// -/// `twilio` puts the telephony carrier's envelope on the caller's side (the telephony leg); the -/// Gemini Live leg passes `false`. -#[allow(clippy::too_many_arguments)] -pub(crate) fn open_admitted_telephony( - rt: &VoiceRuntime, - codec: C, - owner: impl Into, - call_id: impl Into, - locked_config: SessionConfig, - meter: Option, - now: u64, - governed: Option, - twilio: bool, -) -> Result, StartError> -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - let (downlink_tx, downlink_rx) = unbounded::>(); - let carrier = Carrier::with_downlink(downlink_tx); - - let (core, handle) = crate::topology::open_admitted_session( - rt, - codec, - owner, - call_id, - Some(locked_config), - carrier, - meter, - now, - governed, - )?; - - let (upstream_tx, upstream_rx) = unbounded::>(); - let downlink_plane = Arc::new(VoiceSession::new(Arc::clone(&core))); - let uplink_plane = Arc::new(UplinkForwarder::new(Arc::clone(&core), upstream_tx.clone())); - - Ok(TelephonyProxy { - core, - handle, - downlink_plane, - uplink_plane, - upstream_rx, - upstream_tx, - downlink_rx, - envelope: twilio.then(Arc::default), - }) -} - -impl TelephonyProxy -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, -{ - /// The shared session core. - #[must_use] - pub fn core(&self) -> &Arc> { - &self.core - } - - /// RUN the proxy until either socket ends OR the kernel's budget verdict hard-closes the carrier. Binds the - /// four socket halves — the PROVIDER pair is opened THROUGH the neutral guarded WS transport - /// ([`crate::topology::dial_provider`], which resolves-then-pins-then-guards the upstream `wss://`), - /// and the CLIENT pair is the telephony leg; the plane holds no socket plumbing, only these - /// `Stream`/`Sink` halves: - /// * `provider_in` / `provider_out` — the Realtime upstream (server→client in, client→server out). - /// * `client_in` / `client_out` — the telephony leg (client audio in, downlink audio out). - /// - /// The provider-write funnel drains into `provider_out`; the downlink funnel drains into - /// `client_out`. A hard close (budget dry) wins the `select!` and tears the session down. - pub async fn run( - self, - provider_in: PIn, - provider_out: POut, - client_in: DIn, - client_out: DOut, - ) where - PIn: Stream> + Unpin, - POut: Sink> + Unpin + Send + 'static, - POut::Error: Send, - DIn: Stream> + Unpin, - DOut: Sink> + Unpin + Send + 'static, - DOut::Error: Send, - { - let TelephonyProxy { - core, - handle, - downlink_plane, - uplink_plane, - upstream_rx, - upstream_tx, - downlink_rx, - envelope, - } = self; - - let carrier = core.carrier().clone(); - - // The carrier's envelope, both ways. Caller frames are read through it: audio goes on as the - // far end's uplink append, the call's `stop` or a refused frame ends the session (the carrier - // closes, so the far end's leg ends with it), and everything else carries nothing upstream. - // Frames bound for the caller are rewritten into it; one it has no event for is dropped. - let (client_in, downlink_rx) = match envelope { - None => (Either::Left(client_in), Either::Left(downlink_rx)), - Some(bridge) => { - let ender = carrier.clone(); - let up = Arc::clone(&bridge); - let client_in = client_in - .scan((), move |_, frame| { - let step = up - .lock() - .unwrap_or_else(PoisonError::into_inner) - .from_caller(&frame); - ready(match step { - CallerStep::Audio(w) => Some(Some(w.0.to_vec())), - CallerStep::Nothing => Some(None), - CallerStep::Stop | CallerStep::Refused => { - ender.hard_close(); - None - } - }) - }) - .filter_map(ready); - let downlink = downlink_rx.filter_map(move |frame| { - ready( - bridge - .lock() - .unwrap_or_else(PoisonError::into_inner) - .to_caller(&frame), - ) - }); - (Either::Right(Box::pin(client_in)), Either::Right(downlink)) - } - }; - - // The two writer drains run as detached tasks (their receivers/sinks are `Send + 'static`). - // Each ends when ITS funnel's last sender drops — provider-write when both serve legs finish, - // downlink when the session core (and its carrier) is dropped below. - let up_drain = tokio::spawn( - upstream_rx - .map(Ok::, POut::Error>) - .forward(provider_out), - ); - let down_drain = tokio::spawn( - downlink_rx - .map(Ok::, DOut::Error>) - .forward(client_out), - ); - - // Serve the provider socket: server→client events drive `on_server_frame`; `out` is the shared - // provider-write funnel. Serve the client socket: client→server frames are forwarded to the - // provider funnel; this socket's own write side is the downlink (driven by the carrier), so the - // pump's sink discards — the uplink plane never writes through `out`. - let up_serve = serve_messages(provider_in, upstream_tx, downlink_plane); - let down_serve = serve_messages(client_in, futures::sink::drain(), uplink_plane); - let serves = futures::future::join(up_serve, down_serve); - - // The marquee teardown: either both sockets reach EOF, or a hard close (budget dry) wins the - // race and drops the serve futures (dropping the planes + the provider-write sender). - tokio::select! { - _ = serves => {} - _ = carrier.closed() => {} - } - - // Release the session so the downlink funnel's last sender (the carrier) drops and its drain - // completes; then await both drains so queued frames are flushed to the sockets before return. - drop(carrier); - // A turn the call ended in the middle of was served all the same: its counts are ledgered now. - core.settle_open_turn(); - // And every call it still had open is over with it. - core.forget_governed_calls(); - drop(core); - let _ = up_drain.await; - let _ = down_drain.await; - // The call is over: its durable row goes terminal and leaves the working set. Dropped - // unsettled, it stayed ACTIVE after both sockets were gone. - handle.finish(crate::runtime::scope::unix_now_secs()); - } -} diff --git a/crates/busbar-voice/src/topology/tests/minter_https_tests.rs b/crates/busbar-voice/src/topology/tests/minter_https_tests.rs deleted file mode 100644 index 2e02a829aa..0000000000 --- a/crates/busbar-voice/src/topology/tests/minter_https_tests.rs +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! HTTPS token-minter tests, at LLM-egress parity: the REAL substrate egress client dials a loopback -//! `MockServer` (the exact bar the LLM plane's egress mock-tests hold), so the minter's request -//! preparation and response handling are exercised over the production request path, not a fake. -//! -//! Proven here: the requested secret lifetime is clamped to the accepted window (over the ceiling → -//! max, under the floor → min, unset → default); the `OpenAI-Safety-Identifier` caller binding is -//! stamped; the returned token is the provider's `ek_` value; a response value without the `ek_` -//! prefix is refused; and the real key never appears in the returned token. - -use super::*; -use crate::ir::config::SessionConfig; -use crate::testkit::loopback_http::{MockResponse, MockServer, MockServerState}; -use crate::topology::webrtc::{MintError, TokenMinter}; -use busbar_kernel::egress::engine::EngineSpec; -use busbar_kernel::proxy::build_egress_client; -use std::sync::Arc; - -const REAL_KEY: &str = "sk-real-secret-key-never-leaves-the-server"; -const SAFETY_ID: &str = "caller-identity-binding-abc123"; - -fn locked_config() -> SessionConfig { - SessionConfig { - instructions: Some("locked system instructions".to_string()), - voice: Some("marin".to_string()), - ..SessionConfig::default() - } -} - -async fn mock_returning(body: serde_json::Value) -> (MockServer, Arc) { - let state = Arc::new(MockServerState::new()); - state.push(MockResponse::Ok { - status: axum::http::StatusCode::OK, - body, - }); - let server = MockServer::new(Arc::clone(&state)).await; - (server, state) -} - -fn minter(server: &MockServer, requested_ttl_secs: Option) -> HttpsTokenMinter { - let client = build_egress_client(&EngineSpec::pooled_webpki(4, 300, false, false)); - HttpsTokenMinter::new( - client, - server.base_url(), - REAL_KEY, - SAFETY_ID, - requested_ttl_secs, - ) -} - -/// The seconds the mint request carried under `expires_after`, read off the mock's recorded body. -fn recorded_ttl_secs(state: &MockServerState) -> u64 { - let raw = state.get_last_request_body().expect("mint sent a body"); - let sent: serde_json::Value = serde_json::from_slice(&raw).expect("mint body is json"); - sent["expires_after"]["seconds"] - .as_u64() - .expect("expires_after.seconds is a number") -} - -#[tokio::test] -async fn ttl_over_ceiling_is_clamped_to_max() { - let (server, state) = mock_returning( - serde_json::json!({ "value": "ek_test123", "expires_at": 1_700_000_600u64 }), - ) - .await; - let token = minter(&server, Some(99_999)) - .mint(&locked_config()) - .await - .expect("mint succeeds"); - - assert_eq!( - recorded_ttl_secs(&state), - 7200, - "over-ceiling clamps to 7200" - ); - assert_eq!(token.value, "ek_test123"); - server.shutdown().await; -} - -#[tokio::test] -async fn ttl_under_floor_is_clamped_to_min() { - let (server, state) = mock_returning( - serde_json::json!({ "value": "ek_test123", "expires_at": 1_700_000_600u64 }), - ) - .await; - minter(&server, Some(1)) - .mint(&locked_config()) - .await - .expect("mint succeeds"); - - assert_eq!(recorded_ttl_secs(&state), 10, "under-floor clamps to 10"); - server.shutdown().await; -} - -#[tokio::test] -async fn ttl_unset_defaults_to_600() { - let (server, state) = mock_returning( - serde_json::json!({ "value": "ek_test123", "expires_at": 1_700_000_600u64 }), - ) - .await; - minter(&server, None) - .mint(&locked_config()) - .await - .expect("mint succeeds"); - - assert_eq!(recorded_ttl_secs(&state), 600, "unset defaults to 600"); - server.shutdown().await; -} - -#[tokio::test] -async fn safety_identifier_header_is_stamped() { - let (server, state) = mock_returning( - serde_json::json!({ "value": "ek_test123", "expires_at": 1_700_000_600u64 }), - ) - .await; - minter(&server, None) - .mint(&locked_config()) - .await - .expect("mint succeeds"); - - assert_eq!( - state - .get_last_request_header("OpenAI-Safety-Identifier") - .as_deref(), - Some(SAFETY_ID), - "the caller-identity binding is stamped on the mint request" - ); - server.shutdown().await; -} - -#[tokio::test] -async fn returns_the_ek_value_and_expiry() { - let (server, _state) = mock_returning( - serde_json::json!({ "value": "ek_livetoken789", "expires_at": 1_700_000_600u64 }), - ) - .await; - let token = minter(&server, None) - .mint(&locked_config()) - .await - .expect("mint succeeds"); - - assert_eq!(token.value, "ek_livetoken789"); - assert_eq!(token.expires_at_unix, 1_700_000_600); - server.shutdown().await; -} - -#[tokio::test] -async fn value_without_ek_prefix_is_refused() { - let (server, _state) = mock_returning( - serde_json::json!({ "value": "sk-not-an-ephemeral-secret", "expires_at": 1_700_000_600u64 }), - ) - .await; - let err = minter(&server, None) - .mint(&locked_config()) - .await - .expect_err("a non-ek_ value is refused"); - - match err { - MintError::Provider(m) => assert!( - m.contains("ek_"), - "the refusal names the missing ek_ prefix, got: {m}" - ), - } - server.shutdown().await; -} - -#[tokio::test] -async fn the_real_key_never_appears_in_the_token() { - let (server, _state) = mock_returning( - serde_json::json!({ "value": "ek_test123", "expires_at": 1_700_000_600u64 }), - ) - .await; - let token = minter(&server, None) - .mint(&locked_config()) - .await - .expect("mint succeeds"); - - assert!( - !token.value.contains(REAL_KEY), - "the real provider key is never in the browser-facing token value" - ); - assert_eq!(token.value, "ek_test123"); - server.shutdown().await; -} diff --git a/crates/busbar-voice/src/topology/tests/mod.rs b/crates/busbar-voice/src/topology/tests/mod.rs deleted file mode 100644 index 08e81dfd24..0000000000 --- a/crates/busbar-voice/src/topology/tests/mod.rs +++ /dev/null @@ -1,687 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! TOPOLOGY TESTS (behind `runtime`): the telephony proxy relays both ways over in-memory sockets and -//! tears down on hard-close; the WebRTC sideband mints a token, governs the locked config, and relays -//! NO media (media is peer-to-peer). - -use crate::ir::codec::OpenAiRealtimeCodec; -use crate::ir::config::SessionConfig; -use crate::runtime::carrier::Carrier; -use crate::runtime::metering::TurnMeter; -use crate::runtime::VoiceRuntime; -use crate::topology::telephony::{begin_telephony, g711_config}; -use crate::topology::webrtc::{attach, EphemeralToken, MintError, TokenMinter}; -use busbar_kernel::plane::handle_engine::DurableHandleEngine; -use futures::channel::mpsc::unbounded; -use futures::StreamExt; -use std::sync::Arc; -// Test-support-only: the guarded provider-dial battery (dial_provider through the net-guard) and its -// loopback EchoProvider. These symbols are used ONLY by the `#[cfg(feature = "test-support")]` dial -// tests, so gate the imports to match — otherwise a `runtime`-without-`test-support` build (the -// workspace clippy default now that voice ships default-on) sees them as unused. -use crate::testkit::fixture_host::FixtureHost; -#[cfg(feature = "test-support")] -use crate::topology::dial_provider; -#[cfg(feature = "test-support")] -use busbar_kernel::ingress::byte_duplex::{CallRef, DuplexHandle, DuplexPlane}; -#[cfg(feature = "test-support")] -use busbar_kernel::ingress::duplex_ws as ws_ingress; -#[cfg(feature = "test-support")] -use busbar_kernel::net_guard::GuardPolicy; -#[cfg(feature = "test-support")] -use futures::SinkExt; - -fn runtime() -> VoiceRuntime { - VoiceRuntime::new( - Arc::new(DurableHandleEngine::new()), - Arc::new(crate::runtime::tools::EchoToolExecutor), - ) -} - -/// A presenting key's meter over a governed fixture host whose budget view is capped at `cap` counts. -fn meter_capped(cap: i64) -> TurnMeter { - meter_on(&Arc::new(FixtureHost::new().governed().with_count_cap(cap))) -} - -/// The presenting key `vk`'s meter over `host`. -fn meter_on(host: &Arc) -> TurnMeter { - TurnMeter::new( - host.clone(), - busbar_contract::records::VirtualKey { - id: "vk".to_string(), - ..Default::default() - }, - "streaming-server", - crate::OPENAI_REALTIME, - ) -} - -fn json_frame(v: serde_json::Value) -> Vec { - serde_json::to_vec(&v).unwrap() -} - -// ── Topology B: the thin telephony proxy relays both directions ───────────────────────────────── - -/// The caller's audio is counted in the class's own seconds and nothing else: what the governed host -/// ledgered under `audio_seconds_in` for the presenting key `vk`. -fn audio_seconds_in(host: &FixtureHost) -> Option { - host.ledger_rows("vk") - .iter() - .find(|((_, class), _)| class == "audio_seconds_in") - .map(|(_, n)| *n) -} - -/// THE TELEPHONY LEG SPEAKS THE CARRIER'S ENVELOPE (1.6.0-TODO.md, "the caller's audio is silently -/// discarded, and the call still settles usage"). The caller's Twilio `media` reaches the far end as -/// the realtime dialect's uplink append, byte for byte, and is counted; the far end's audio reaches -/// the caller as a Twilio `media` frame on the call's stream; the caller's `stop` ends the call while -/// the far end's socket is still open. Every caller frame used to go to the realtime codec, which -/// reads no Twilio event, so the far end heard nothing and the session billed one-way output. -#[tokio::test] -async fn a_telephony_caller_is_heard_counted_and_answered_in_the_carriers_envelope() { - let rt = runtime(); - let host = Arc::new(FixtureHost::new().governed()); - let proxy = begin_telephony( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-9", - g711_config(), - Some(meter_on(&host)), - 1, - ) - .expect("telephony begins"); - // Three seconds of 8 kHz µ-law from the caller (24 000 bytes of 0x7f, which base64 spells as - // `f39/` 8 000 times); the far end's short reply is the call driver's own. - let spoken = vec![0x7f_u8; 24_000]; - - let legs = crate::testkit::telephony::place_call(proxy, &spoken).await; - - let appended: Vec<&serde_json::Value> = legs - .to_far_end - .iter() - .filter(|v| v["type"] == "input_audio_buffer.append") - .collect(); - assert_eq!( - appended - .iter() - .map(|v| v["audio"].as_str().unwrap_or_default().to_string()) - .collect::>(), - vec!["f39/".repeat(8_000)], - "the caller's audio reached the far end once, unchanged: {:?}", - legs.to_far_end - ); - assert!( - legs.to_far_end.iter().all(|v| v.get("event").is_none()), - "no Twilio frame reached the far end: {:?}", - legs.to_far_end - ); - let heard = crate::topology::twilio::TwilioEnvelope::encode_media( - crate::testkit::telephony::STREAM_SID, - crate::testkit::telephony::FAR_END_AUDIO, - ); - assert_eq!( - legs.to_caller, - vec![serde_json::from_slice::(&heard).expect("the envelope is JSON")], - "the caller heard the far end in the carrier's envelope, and nothing else" - ); - assert_eq!( - audio_seconds_in(&host), - Some(3), - "the caller's three seconds are ledgered when the call ends" - ); -} - -/// A frame on the telephony leg that is not the carrier's shape (here, the realtime dialect's own -/// append) is refused: the call ends at once, nothing reaches the far end, and nothing is counted. -#[tokio::test] -async fn a_telephony_frame_not_in_the_carriers_shape_ends_the_call_unheard() { - let rt = runtime(); - let host = Arc::new(FixtureHost::new().governed()); - let proxy = begin_telephony( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-refused", - g711_config(), - Some(meter_on(&host)), - 1, - ) - .expect("telephony begins"); - let (_prov_in_tx, prov_in_rx) = unbounded::>(); - let (prov_out_tx, mut prov_out_rx) = unbounded::>(); - let (cli_in_tx, cli_in_rx) = unbounded::>(); - let (cli_out_tx, _cli_out_rx) = unbounded::>(); - cli_in_tx - .unbounded_send(json_frame(serde_json::json!({ - "type": "input_audio_buffer.append", "audio": "A".repeat(16_000) - }))) - .unwrap(); - - // Both sockets stay open: only the refusal can end the call. - let ended = tokio::time::timeout( - std::time::Duration::from_secs(5), - proxy.run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx), - ) - .await; - assert!(ended.is_ok(), "the refused frame ended the call"); - - prov_out_rx.close(); - let mut up = Vec::new(); - while let Some(f) = prov_out_rx.next().await { - up.push(serde_json::from_slice::(&f).unwrap_or_default()); - } - assert!( - !up.iter().any(|v| v["type"] == "input_audio_buffer.append"), - "nothing the caller sent reached the far end: {up:?}" - ); - assert_eq!(audio_seconds_in(&host), None, "nothing was counted"); -} - -/// The Gemini Live leg rides the same proxy with no envelope: its caller speaks the far end's own -/// dialect, so frames cross both ways unchanged. -#[tokio::test] -async fn a_leg_with_no_envelope_relays_the_far_ends_dialect_both_ways() { - let rt = runtime(); - let proxy = crate::topology::telephony::open_admitted_telephony( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-dialect", - g711_config(), - None, - 1, - None, - false, - ) - .expect("the leg opens"); - - let (prov_in_tx, prov_in_rx) = unbounded::>(); - let (prov_out_tx, mut prov_out_rx) = unbounded::>(); - let (cli_in_tx, cli_in_rx) = unbounded::>(); - let (cli_out_tx, mut cli_out_rx) = unbounded::>(); - prov_in_tx - .unbounded_send(json_frame(serde_json::json!({ - "type":"response.output_audio.delta","delta":"AAAA" - }))) - .unwrap(); - cli_in_tx - .unbounded_send(json_frame(serde_json::json!({ - "type":"input_audio_buffer.append","audio":"BBBB" - }))) - .unwrap(); - drop(prov_in_tx); - drop(cli_in_tx); - - proxy - .run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx) - .await; - - cli_out_rx.close(); - let mut downlink = Vec::new(); - while let Some(f) = cli_out_rx.next().await { - let v: serde_json::Value = serde_json::from_slice(&f).unwrap(); - downlink.push(v["type"].as_str().unwrap().to_string()); - } - assert!( - downlink.contains(&"response.output_audio.delta".to_string()), - "the far end's audio reached the caller: {downlink:?}" - ); - prov_out_rx.close(); - let mut uplink = Vec::new(); - while let Some(f) = prov_out_rx.next().await { - let v: serde_json::Value = serde_json::from_slice(&f).unwrap(); - uplink.push(v["type"].as_str().unwrap().to_string()); - } - assert!( - uplink.contains(&"input_audio_buffer.append".to_string()), - "the caller's audio reached the far end: {uplink:?}" - ); -} - -/// Item 137: a telephony call that ends settles its durable row terminal and evicts it. The proxy -/// used to bind its handle as `_handle` and drop it unsettled, so every call's row stayed ACTIVE in -/// the working set after both sockets were gone. -#[tokio::test] -async fn a_finished_telephony_call_settles_and_evicts_its_durable_row() { - let rt = runtime(); - let proxy = begin_telephony( - &rt, - OpenAiRealtimeCodec, - "acct-1", - "call-ended", - g711_config(), - None, - 1, - ) - .expect("telephony begins"); - let (prov_in_tx, prov_in_rx) = unbounded::>(); - let (prov_out_tx, _prov_out_rx) = unbounded::>(); - let (cli_in_tx, cli_in_rx) = unbounded::>(); - let (cli_out_tx, _cli_out_rx) = unbounded::>(); - // Both sockets end at once: the call is over. - drop(prov_in_tx); - drop(cli_in_tx); - proxy - .run(prov_in_rx, prov_out_tx, cli_in_rx, cli_out_tx) - .await; - assert!( - crate::runtime::SessionHandle::bind(Arc::clone(&rt.engine), "acct-1", "call-ended") - .get() - .is_none(), - "the ended call's durable row is settled terminal and evicted" - ); -} - -// ── The provider WSS dials THROUGH the neutral guarded transport (HARD RULE 3) ─────────────────── - -/// A loopback echo "provider" served over the neutral WS ingress acceptor — stands in for the Realtime -/// upstream so the plane's `dial_provider` can be driven end to end without a network. Test-support-only -/// (the guarded-dial battery it feeds is `#[cfg(feature = "test-support")]`). -#[cfg(feature = "test-support")] -struct EchoProvider; - -#[cfg(feature = "test-support")] -#[async_trait::async_trait] -impl DuplexPlane for EchoProvider { - fn classify(&self, _frame: &[u8]) -> Option { - None - } - async fn handle(self: Arc, frame: Vec, out: DuplexHandle) { - // The echo is what these tests observe, so a write that did not land is reported rather - // than read later as a frame the provider never sent. - if let Err(e) = out.emit(frame).await { - eprintln!("echo provider: the frame could not be written: {e}"); - } - } -} - -#[cfg(feature = "test-support")] -async fn spawn_echo_provider() -> std::net::SocketAddr { - async fn route(upgrade: axum::extract::ws::WebSocketUpgrade) -> axum::response::Response { - ws_ingress::serve(upgrade, Arc::new(EchoProvider)) - } - let app = axum::Router::new().route("/", axum::routing::get(route)); - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - tokio::spawn(async move { - axum::serve(listener, app).await.unwrap(); - }); - addr -} - -/// The plane SELECTS `Transport::WebSocket` and lets the substrate open the socket: `dial_provider` -/// dials the loopback provider THROUGH the net-guard and a frame crosses both directions. This is the -/// plane using the neutral transport instead of carrying its own socket plumbing. Gated on -/// `test-support` because the governed dial now rides the breaker beneath it, reached through the -/// substrate's in-memory fixture host. -#[cfg(feature = "test-support")] -#[tokio::test] -async fn dial_provider_routes_through_the_guarded_ws_transport() { - let addr = spawn_echo_provider().await; - let url = format!("ws://{addr}/"); - let policy = GuardPolicy { - allow_private: true, - allow_plaintext: true, - ..GuardPolicy::default() - }; - let host = FixtureHost::new(); - let pool = crate::topology::stream_breaker_key("openai-realtime"); - let (mut stream, mut sink) = dial_provider(&host, &pool, 0, &url, policy) - .await - .expect("the plane dials the provider through the guarded transport"); - sink.send(b"realtime-frame".to_vec()).await.ok(); - let got = tokio::time::timeout(std::time::Duration::from_secs(5), stream.next()) - .await - .expect("a reply arrived") - .expect("the stream yielded a frame"); - assert_eq!( - got, b"realtime-frame", - "a frame crossed the guarded WS both ways" - ); -} - -/// The provider dial FAILS CLOSED on a guard-failing target — the plane never opens a socket the -/// net-guard did not pin (the egress-audit finding this closes). -#[cfg(feature = "test-support")] -#[tokio::test] -async fn dial_provider_fails_closed_on_a_guarded_target() { - // A public loopback under the fail-closed default is an internal address — refused, no socket. - let host = FixtureHost::new(); - let pool = crate::topology::stream_breaker_key("openai-realtime"); - assert!( - dial_provider(&host, &pool, 0, "wss://127.0.0.1/", GuardPolicy::default()) - .await - .is_err(), - "the provider dial must refuse an unpinned/guard-failing target" - ); -} - -// ── Topology A: the WebRTC sideband mints a token and relays no media ──────────────────────────── - -struct FakeMinter { - fail: bool, -} - -#[async_trait::async_trait] -impl TokenMinter for FakeMinter { - async fn mint(&self, config: &SessionConfig) -> Result { - if self.fail { - return Err(MintError::Provider("endpoint down".into())); - } - // The minted secret is scoped to the SAME locked config busbar governs. - assert_eq!(config.instructions.as_deref(), Some("be helpful")); - Ok(EphemeralToken { - value: "ek_test_secret".into(), - expires_at_unix: 9_999, - }) - } -} - -#[tokio::test] -async fn webrtc_sideband_mints_token_locks_config_and_relays_no_media() { - let rt = runtime(); - let minter = FakeMinter { fail: false }; - let mut locked = SessionConfig { - instructions: Some("be helpful".into()), - ..SessionConfig::default() - }; - locked.tools = vec![serde_json::json!({"type":"function","name":"lookup"})]; - - let attached = attach( - &rt, - &minter, - OpenAiRealtimeCodec, - "acct-2", - "call-42", - locked, - Some(meter_capped(3)), - 1, - ) - .await - .expect("sideband attaches"); - - assert_eq!(attached.token.value, "ek_test_secret"); - // The sideband carrier relays NO media (the browser's media path is peer-to-peer). - assert!( - !attached.core.carrier().send_downlink(vec![1, 2, 3]), - "sideband relays no downlink media" - ); - - // Metering is real over the sideband too: a turn that dries a chain of 3 hard-closes. - let usage = crate::ir::codec::WireEvent(bytes::Bytes::from(json_frame(serde_json::json!({ - "type":"response.done", - "response": { "usage": { "output_token_details": { "audio_tokens": 5 } } }, - })))); - let plan = attached.core.on_server_frame(usage).await; - assert!(plan.close, "sideband session hard-closes on exhaustion"); - assert!(attached.core.carrier().is_closed()); -} - -// ── begin_session: past the open-pass gate, the budget is asked before anything opens ───────────────── - -#[test] -fn begin_session_asks_the_budget_past_the_gate_and_opens_only_with_room() { - // A session proceeds past the open-pass gate to the budget check: a dry chain is refused there, - // and a chain with room opens. - let rt = runtime(); - let ok_cfg = SessionConfig { - model: Some("allowed-model".into()), - ..SessionConfig::default() - }; - let open = |call: &str, cap| { - crate::topology::begin_session( - &rt, - OpenAiRealtimeCodec, - "acct", - call, - Some(ok_cfg.clone()), - Carrier::sideband(), - Some(meter_capped(cap)), - 1, - ) - }; - assert!(matches!( - open("call-dry", 0), - Err(crate::topology::StartError::BudgetRefused) - )); - assert!( - open("call-ok", 10).is_ok(), - "an allowed destination with room opens" - ); -} - -/// A TEST-ONLY open-pass gate that refuses every session. The plane's own [`SessionGauntlet`] keeps no -/// destination policy (1.5.5 had none), so nothing in production can plant a refusal; this stands in -/// for whatever may refuse at the gate (a kernel-loop session runner) without resurrecting a policy. -/// -/// [`SessionGauntlet`]: crate::topology::SessionGauntlet -struct RefusingGate; - -#[async_trait::async_trait] -impl super::GauntletPlane for RefusingGate { - fn verify_destination(&self, _req: &super::GauntletRequest<'_>) -> super::VerifyOutcome { - super::VerifyOutcome::Refuse( - axum::response::Response::builder() - .status(axum::http::StatusCode::FORBIDDEN) - .body(axum::body::Body::empty()) - .expect("static refusal builds"), - ) - } - - async fn drive(self: Box, _req: super::GauntletRequest<'_>) -> axum::response::Response { - unreachable!("the session path only runs the admission gate") - } -} - -/// THE D3 ORDERING WITNESS: `begin_session` runs the open-pass gate at the TOP, so a session the gate -/// refuses is refused BEFORE the kernel account's budget is asked — the caller here has a DRY chain, -/// and the answer is still the gate's refusal, not the budget's. RED if the budget is asked first. -#[test] -fn begin_session_refuses_at_the_gauntlet_before_the_budget_is_asked() { - let r = crate::topology::begin_session_through( - &runtime(), - Box::new(RefusingGate), - OpenAiRealtimeCodec, - "acct", - "call-refused", - Some(SessionConfig { - model: Some("any-model".into()), - ..SessionConfig::default() - }), - Carrier::sideband(), - Some(meter_capped(0)), - 1, - ); - assert!( - matches!(r, Err(crate::topology::StartError::DestinationRefused)), - "a gate-refused session is refused at the open-pass gate, before the budget is asked" - ); -} - -// ── A SESSION HOLDS NOTHING OPEN (the D2 lease-leak witness, retired with the lease) ───────────────── - -/// The lease this replaced held a reserve that only a by-value guard could release when a parked -/// handler pinned the core. A session now holds no reserve at all: each turn's counts reach the ledger -/// as the turn closes, so a pinned core has nothing left to release and nothing waits on its drop. -#[tokio::test] -async fn a_pinned_core_holds_nothing_open() { - let host = Arc::new(FixtureHost::new().governed()); - let meter = TurnMeter::new( - Arc::clone(&host) as Arc, - busbar_contract::records::VirtualKey { - id: "vk-pin".to_string(), - ..Default::default() - }, - "streaming-server", - crate::OPENAI_REALTIME, - ); - let (core, _handle) = crate::topology::begin_session( - &runtime(), - OpenAiRealtimeCodec, - "acct", - "call-pin", - None, - Carrier::sideband(), - Some(meter), - 1, - ) - .expect("session begins"); - let usage = crate::ir::codec::WireEvent(bytes::Bytes::from(json_frame(serde_json::json!({ - "type":"response.done", - "response": { "usage": { "output_token_details": { "audio_tokens": 4 } } }, - })))); - let _ = core.on_server_frame(usage).await; - let pinned = Arc::clone(&core); - drop(core); - assert_eq!( - host.ledger_usage("vk-pin").map(|u| u.tokens), - Some(4), - "the turn is ledgered as it closed — nothing waits on the core's drop" - ); - drop(pinned); - assert_eq!(host.ledger_usage("vk-pin").map(|u| u.tokens), Some(4)); -} - -/// A minter that RECORDS whether it was ever asked to mint — the witness for the ordering fix: on a -/// refused governed open NOTHING may mint. -struct RecordingMinter { - minted: std::sync::atomic::AtomicBool, -} - -#[async_trait::async_trait] -impl TokenMinter for RecordingMinter { - async fn mint(&self, _config: &SessionConfig) -> Result { - self.minted.store(true, std::sync::atomic::Ordering::SeqCst); - Ok(EphemeralToken { - value: "ek_should_never_be_minted".into(), - expires_at_unix: 1, - }) - } -} - -/// THE ORDERING FIX: `attach` runs the gauntlet + the budget check FIRST and mints the ephemeral secret -/// only past a clean open. A session the open refuses (here: a dry budget chain) is refused BEFORE any -/// mint — so the browser is handed NO `ek_` on a refused session (zero bytes, zero charge, zero -/// credential). RED before the reorder: the mint ran before `begin_session`, so a refused session still -/// minted a secret. -#[tokio::test] -async fn the_open_refuses_before_the_mint_on_a_dry_budget() { - let rt = runtime(); - let minter = RecordingMinter { - minted: std::sync::atomic::AtomicBool::new(false), - }; - let locked = SessionConfig { - model: Some("allowed-model".into()), - ..SessionConfig::default() - }; - let r = attach( - &rt, - &minter, - OpenAiRealtimeCodec, - "acct", - "call-dry", - locked, - Some(meter_capped(0)), - 1, - ) - .await; - assert!( - matches!( - r, - Err(crate::topology::webrtc::AttachError::Start( - crate::topology::StartError::BudgetRefused - )) - ), - "a dry chain is refused at the open, before the mint" - ); - assert!( - !minter.minted.load(std::sync::atomic::Ordering::SeqCst), - "NOTHING mints on a refused session — the open runs before the mint" - ); -} - -#[tokio::test] -async fn webrtc_attach_fails_closed_when_mint_fails() { - let rt = runtime(); - let minter = FakeMinter { fail: true }; - let r = attach( - &rt, - &minter, - OpenAiRealtimeCodec, - "acct-3", - "call-x", - SessionConfig::default(), - None, - 1, - ) - .await; - assert!(r.is_err(), "a failed mint refuses the session"); -} - -/// A FAILED WEBRTC MINT REFUNDS ITS SESSION FEE EXACTLY ONCE (TODO 17(b), ARCHITECT R4): `attach` -/// opens the governed session, counting its fee at the open under the same dry check, then mints; a -/// mint that fails hands the browser no secret, so the session never opened and its fee is given back -/// through the governance refund. The metering row keeps the count (the refund adjusts the budget -/// book only); a kept session attached first on the same key proves one fee comes back and not two. -/// RED with the fee counted at `served()`: the failed attach counted nothing. -#[tokio::test] -async fn a_failed_webrtc_mint_refunds_its_session_fee_exactly_once() { - let host = Arc::new(FixtureHost::new().governed().with_count_cap(1_000)); - let key = busbar_contract::records::VirtualKey { - id: "vk-webrtc-fee".to_string(), - ..Default::default() - }; - let meter = || { - let host: Arc = Arc::clone(&host); - TurnMeter::new( - host, - key.clone(), - "streaming-server", - crate::OPENAI_REALTIME, - ) - }; - let rt = runtime(); - let locked = SessionConfig { - instructions: Some("be helpful".into()), - ..SessionConfig::default() - }; - let _kept = attach( - &rt, - &FakeMinter { fail: false }, - OpenAiRealtimeCodec, - "acct", - "call-kept", - locked.clone(), - Some(meter()), - 1, - ) - .await - .expect("the kept session attaches"); - let failed = attach( - &rt, - &FakeMinter { fail: true }, - OpenAiRealtimeCodec, - "acct", - "call-mint-fails", - locked, - Some(meter()), - 1, - ) - .await; - assert!( - matches!(failed, Err(crate::topology::webrtc::AttachError::Mint(_))), - "the premise: the mint failed after the governed open" - ); - assert_eq!( - host.session_rows(&key.id), - 2, - "the failed attach counted its session fee at the open, under the dry check" - ); - assert_eq!( - host.ledger_usage(&key.id).map_or(0, |u| u.sessions), - 1, - "the failed mint's fee is given back exactly once; the kept session's stays" - ); -} diff --git a/crates/busbar-voice/src/topology/webrtc.rs b/crates/busbar-voice/src/topology/webrtc.rs deleted file mode 100644 index a8a5793675..0000000000 --- a/crates/busbar-voice/src/topology/webrtc.rs +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! TOPOLOGY A — the BROWSER WebRTC SIDEBAND (design `BUSBAR-1.6.0.md` #18/#45). -//! -//! busbar attaches to the session over a persistent WSS keyed by `call_id`, OWNING tools + instructions -//! (the locked [`SessionConfig`]). It mints the ephemeral client-secret the browser uses to establish -//! the media path DIRECTLY with the provider: media flows peer-to-peer, and only auth + the SDP -//! handshake + the sideband control transit busbar. busbar's role is MINT/GUARD + sideband control, -//! NOT a media relay — modelled here by a [`Carrier::sideband`] that relays no downlink audio. - -use crate::ir::codec::{DuplexReader, DuplexWriter}; -use crate::ir::config::SessionConfig; -use crate::runtime::carrier::Carrier; -use crate::runtime::scope::SessionHandle; -use crate::runtime::session::{SessionCore, VoiceSession}; -use crate::runtime::VoiceRuntime; -use crate::topology::{begin_session, StartError}; -use async_trait::async_trait; -use std::sync::Arc; - -/// AN EPHEMERAL CLIENT SECRET the browser presents to the provider to open its own media session — the -/// short-lived token busbar mints so the long-lived provider key never reaches the browser. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EphemeralToken { - /// The opaque secret value handed to the browser. - pub value: String, - /// Unix seconds after which the secret is rejected. - pub expires_at_unix: u64, -} - -/// Why minting an ephemeral client secret failed. -#[derive(Debug)] -pub enum MintError { - /// The provider's client-secret endpoint refused or was unreachable. - Provider(String), -} - -impl std::fmt::Display for MintError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - MintError::Provider(m) => write!(f, "ephemeral token mint failed: {m}"), - } - } -} - -impl std::error::Error for MintError {} - -/// MINTS the ephemeral client secret via the provider's client-secret endpoint — dependency-inverted -/// so the composition root binds the real HTTPS call (no network dep leaks into the plane) while tests -/// bind a fake. The minted secret is scoped to the SAME locked config busbar governs. -#[async_trait] -pub trait TokenMinter: Send + Sync { - /// Mint an ephemeral client secret for a browser session governed by `config`. - async fn mint(&self, config: &SessionConfig) -> Result; -} - -/// Why attaching a sideband session failed. -#[derive(Debug)] -pub enum AttachError { - /// The session could not begin (lease refused / durable open failed). - Start(StartError), - /// The ephemeral token could not be minted. - Mint(MintError), -} - -impl std::fmt::Display for AttachError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AttachError::Start(e) => write!(f, "{e}"), - AttachError::Mint(e) => write!(f, "{e}"), - } - } -} - -impl std::error::Error for AttachError {} - -/// AN ATTACHED SIDEBAND SESSION — everything busbar holds after the sideband is bound: the ephemeral -/// token to hand the browser, the [`VoiceSession`] plane to serve over the sideband WSS, its shared -/// [`SessionCore`], and the durable [`SessionHandle`] to close at teardown. -pub struct Attached { - /// The ephemeral client secret to return to the browser. - pub token: EphemeralToken, - /// The sideband control plane — serve it over the persistent WSS the plane opens THROUGH the neutral - /// guarded WS transport ([`crate::topology::dial_provider`] → `serve_messages`); the plane holds no - /// socket of its own. - pub session: VoiceSession, - /// The shared session core (metering, tools, barge-in). - pub core: Arc>, - /// The durable session binding to close at teardown. - pub handle: SessionHandle, -} - -/// ATTACH a browser WebRTC sideband session: lock the plane's `instructions` + `tools` into the -/// [`SessionConfig`], begin the governed session (kernel account + durable handle), and mint the ephemeral token -/// scoped to that locked config. The returned [`Attached::session`] is served over the persistent WSS; -/// media never transits busbar (the sideband carrier relays no audio). -#[allow(clippy::too_many_arguments)] -pub async fn attach( - rt: &VoiceRuntime, - minter: &M, - codec: C, - owner: impl Into, - call_id: impl Into, - locked_config: SessionConfig, - meter: Option, - now: u64, -) -> Result, AttachError> -where - C: DuplexReader + DuplexWriter + Send + Sync + 'static, - M: TokenMinter, -{ - // GOVERN FIRST, MINT SECOND (verify-strictly-before-charge, and before any credential is issued): - // begin_session runs the shared open-pass gauntlet gate + opens the kernel account + the durable - // handle. NOTHING is minted on a gate- or budget-refused session — a refused open costs zero bytes, - // zero charge, and hands the browser NO ephemeral secret. The mint runs ONLY past a clean open. - // A sideband carrier: no downlink media relay — the browser's media path is peer-to-peer. - let carrier = Carrier::sideband(); - let (core, handle) = begin_session( - rt, - codec, - owner, - call_id, - Some(locked_config.clone()), - carrier, - meter, - now, - ) - .map_err(AttachError::Start)?; - - // Only past the governed open: mint the ephemeral secret scoped to the SAME config busbar locked and - // re-applies. A mint failure tears the just-opened session down through the returned handle - // drop rather than leaking a governed-but-unusable session, and gives back the session fee its - // open counted (TODO 17(b), ARCHITECT R4): a session that never opened charges nothing. - let token = match minter.mint(&locked_config).await { - Ok(token) => token, - Err(e) => { - core.refund_open(); - return Err(AttachError::Mint(e)); - } - }; - // The secret is out: the session is served, and the fee its open counted is final. - core.served(); - - let session = VoiceSession::new(Arc::clone(&core)); - Ok(Attached { - token, - session, - core, - handle, - }) -} diff --git a/crates/busbar-voice/tests/compose_governs_every_session.rs b/crates/busbar-voice/tests/compose_governs_every_session.rs deleted file mode 100644 index 4f899fc7e5..0000000000 --- a/crates/busbar-voice/tests/compose_governs_every_session.rs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Busbar Inc and contributors - -//! **The plane composes its own open-call table.** Its own binary, because the table behind the -//! served door is set once per process: a cell sharing a test binary with the plane's other -//! composition cells would race them for the first write. - -#![cfg(feature = "runtime")] - -/// After this plane's compose step, the served door binds every session it opens to the node's -/// table, two sessions are told apart before either has a call open, and a second compose is a -/// no-op rather than a silent swap of the table live sessions are keyed into. -#[test] -fn the_planes_compose_step_governs_every_served_session() { - assert!( - busbar_voice::mount::served_governed_session().is_none(), - "before the compose step nothing is bound" - ); - assert!( - busbar_voice::mount::compose_node_calls(), - "the plane's compose step writes the table" - ); - assert!( - !busbar_voice::mount::compose_node_calls(), - "and a second compose never swaps it" - ); - let bound = busbar_voice::mount::served_governed_session() - .expect("after the compose step, every session the door opens is bound to a table"); - let next = busbar_voice::mount::served_governed_session().expect("and so is the next one"); - assert_ne!( - bound.session, next.session, - "two conversations are told apart before either has a call open" - ); - assert!( - bound.calls.planned(bound.session, "call_aaa", 0), - "the table enters a planned client-served leg" - ); - assert_eq!( - next.calls.replied(next.session, "call_aaa"), - Err(busbar_voice::runtime::ReplyRefusal::NoSuchSession), - "and keys it by the session that planned it: the same identifier on another session wakes nothing" - ); - assert_eq!(bound.calls.replied(bound.session, "call_aaa"), Ok(())); -} diff --git a/qa/construction.toml b/qa/construction.toml index 5d19eebd19..996c54546e 100644 --- a/qa/construction.toml +++ b/qa/construction.toml @@ -53,7 +53,8 @@ scan_roots = ["crates/*/src", "crates/*/tests", "crates/*/benches"] # two lists share zero members. manifest-allowlist reads THIS one to decide what "a plane names # another plane" means. The two collapse into one definition when the legacy engines are deleted; # until then a reader must not take either as "the plane kind". -plane_crates = ["busbar-llm", "busbar-mcp", "busbar-a2a", "busbar-voice"] +# `busbar-voice` is struck: FLIP-STREAMING deleted the legacy streams crate (#18). +plane_crates = ["busbar-llm", "busbar-mcp", "busbar-a2a"] # A file at one of these path fragments is TEST code (same rule the purity lint applies), in # addition to any `#[cfg(test)] mod … { … }` block inside a production file. # A bare `tests.rs` (Rust's `#[cfg(test)] mod tests;` file convention) is test code too. @@ -263,7 +264,7 @@ busbar-mcp = 281 # busbar-a2a 233 -> 232 (downward, at the measure): push delivery names the kernel's engine seam once # instead of per signature. busbar-a2a = 232 -busbar-voice = 88 +# busbar-voice is struck: FLIP-STREAMING deleted the crate, and the row went with it. [rules.ports-only-tests] why = """The same reach from test code. Tests that name core internals pin the plane to core's \ @@ -283,7 +284,7 @@ counts can trend to zero at their own pace.""" busbar-llm = 1027 busbar-mcp = 185 busbar-a2a = 98 -busbar-voice = 43 +# busbar-voice is struck: FLIP-STREAMING deleted the crate, and the row went with it. # ── 4. no-uninstalled-seam ─────────────────────────────────────────────────────────────────────── [rules.no-uninstalled-seam] @@ -757,7 +758,6 @@ busbar-plane-llm = ["busbar-llm"] # + three third-party crates, and it names no engine crate in either spelling. The edge that exists # now runs the other way — `busbar-mcp` reads the plane's wire dialect — which is the direction a # plane's definition asks for and not a coupling this rule is about. -busbar-plane-streaming = ["busbar-voice"] # ── 16. source-denylist (BUSBAR-1.6.0.md THE DESIGN, §2) ─────────────────────────────────────────────────────── [rules.source-denylist] diff --git a/qa/dep-wall.ledger b/qa/dep-wall.ledger index 8b2a586052..d9db30738d 100644 --- a/qa/dep-wall.ledger +++ b/qa/dep-wall.ledger @@ -53,11 +53,3 @@ busbar-transport-http hyper-util busbar-transport-http socket2 busbar-transport-http tokio (net) busbar-transport-ws socket2 -busbar-voice hyper-util -busbar-voice reqwest -busbar-voice rustls -busbar-voice socket2 -busbar-voice std::net -busbar-voice tokio (net) -busbar-voice tokio-rustls -busbar-voice tokio::net diff --git a/qa/door-only.toml b/qa/door-only.toml index 440325c590..d769eeef7c 100644 --- a/qa/door-only.toml +++ b/qa/door-only.toml @@ -159,41 +159,6 @@ rule = "door-only:plugin-path" file = "crates/busbar-mcp/src/mcp/subscribe.rs" item = "busbar_plane_mcp" -[[finding]] -rule = "door-only:plugin-path" -file = "crates/busbar-voice/src/bin/voice-conform.rs" -item = "busbar_plane_streaming" - -[[finding]] -rule = "door-only:plugin-path" -file = "crates/busbar-voice/src/lib.rs" -item = "busbar_plane_streaming" - -[[finding]] -rule = "door-only:plugin-path" -file = "crates/busbar-voice/src/runtime/metering.rs" -item = "busbar_plane_streaming" - -[[finding]] -rule = "door-only:plugin-path" -file = "crates/busbar-voice/src/runtime/mod.rs" -item = "busbar_plane_streaming" - -[[finding]] -rule = "door-only:plugin-path" -file = "crates/busbar-voice/src/runtime/session.rs" -item = "busbar_plane_streaming" - -[[finding]] -rule = "door-only:plugin-path" -file = "crates/busbar-voice/src/runtime/voice_plane_unit_oracle.rs" -item = "busbar_plane_streaming" - -[[finding]] -rule = "door-only:plugin-path" -file = "crates/busbar-voice/src/topology/mod.rs" -item = "busbar_plane_streaming" - [[finding]] rule = "door-only:plugin-path" file = "crates/busbar/src/root/plane_decisions.rs" @@ -252,11 +217,6 @@ rule = "door-only:static-seam" file = "crates/busbar-llm/src/unit/node.rs" item = "NATIVE_SEATS" -[[finding]] -rule = "door-only:static-seam" -file = "crates/busbar-voice/src/mount.rs" -item = "COMPOSED_GOVERNED_CALLS" - [[finding]] rule = "door-only:static-seam" file = "crates/plugin-loader/src/host.rs" diff --git a/qa/kind-isolation.toml b/qa/kind-isolation.toml index 04aad30ca2..f0aa955ed8 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -32,15 +32,10 @@ # any row here whose `from` crate STILL EXISTS. A legacy crate that survives to the tag is the # retirement having failed, and this row is where that is reported, by name. -# `busbar-voice` is the retiring 1.5.x streams crate and `busbar-plane-streaming` is the plane it -# is draining into (`busbar-plane-voice` was deleted into it, #18/#83). The rule is written about -# the drain, not about any one crate, so this edge is named on exactly the same terms — leaving -# it out would have meant a rule that only sees the drain the ruling happened to mention. -[[transitional]] -from = "busbar-voice" -to = "busbar-plane-streaming" -reason = "legacy drain" - +# `busbar-voice` (the retiring 1.5.x streams crate) is GONE: FLIP-STREAMING deleted it once the +# streaming plane's door served every route (#18/#83), and its drain row into +# `busbar-plane-streaming` went with it. +# # The A2A and MCP planes were folded out of busbar-core the same way (W4.a/W4.b, 1.6.0): each # retiring plane names the plane-host half it is draining its transport/routing surface into. # `busbar-plane-a2a-host` is GONE (folded back into `busbar-a2a`, 858c93966). The drain it named @@ -552,14 +547,6 @@ kind = "plane" crate = "busbar-transport-stdio" kind = "plane" -[[cell]] -crate = "busbar-voice" -kind = "contract" - -[[cell]] -crate = "busbar-voice" -kind = "plane" - # ------------------------------------------------------------------------------------------------ # # `kind-isolation:deps` held the manifests to a table of kind-to-kind CLASSES in Rust source, and a @@ -712,16 +699,6 @@ cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 — the composition root is the why = "busbar names busbar-transport-stdio: declared normal in the shipped graph, 1 declaration(s) at this commit. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." drain = "none: BUSBAR-1.6.0.md grants this class. Draining it would be an architecture change and not a landing — what this row holds is the NUMBER, so a second declaration of a granted edge is still a landing that has to say why." -[[dep]] -from = "busbar" -to = "busbar-voice" -half = "shipped" -count = "1" -verdict = "allowed" -cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 — the composition root is the one thing that names all three axes." -why = "busbar names busbar-voice: declared normal, optional in the shipped graph, 1 declaration(s) at this commit. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." -drain = "none: BUSBAR-1.6.0.md grants this class. Draining it would be an architecture change and not a landing — what this row holds is the NUMBER, so a second declaration of a granted edge is still a landing that has to say why." - [[dep]] from = "busbar-kernel" to = "busbar-contract" @@ -951,16 +928,6 @@ cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 — no clause grants this pair. why = "busbar-mcp names busbar-kernel: declared normal in the shipped graph, 1 declaration(s) at this commit. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." drain = "delete the busbar-kernel dependency from busbar-mcp's Cargo.toml. A crate reaches another kind through the contract face it is written against, never by naming the crate; `cargo xtask gate kind-isolation` prints the declaration that states it." -[[dep]] -from = "busbar-voice" -to = "busbar-kernel" -half = "shipped" -count = "1" -verdict = "not-allowed" -cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 — no clause grants this pair. Measured debt surfaced by the W4.a/W4.b busbar-core -> busbar-kernel and busbar-substrate -> busbar-substrate-values absorption (1.6.0): the edge is real and shipped, and this row holds the number rather than passing it in silence." -why = "busbar-voice names busbar-kernel: declared normal in the shipped graph, 1 declaration(s) at this commit. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." -drain = "delete the busbar-kernel dependency from busbar-voice's Cargo.toml. A crate reaches another kind through the contract face it is written against, never by naming the crate; `cargo xtask gate kind-isolation` prints the declaration that states it." - @@ -1486,16 +1453,6 @@ cite = "BUSBAR-1.6.0.md THE DESIGN, §9 (BUSBAR-1.6.0.md:1140-1141): busbar i why = "busbar names busbar-contract: declared dev in the test graph (features test-seal), 1 declaration(s) at this commit, beside the shipped edge. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." drain = "none: BUSBAR-1.6.0.md grants this class. What this row holds is the NUMBER." -[[dep]] -from = "busbar-voice" -to = "busbar-contract" -half = "shipped" -count = "1" -verdict = "allowed" -cite = "ARCHITECTURE_ALLOWED legacy -> contract (xtask/src/gates/kind_isolation.rs:594), as its siblings busbar-llm / busbar-a2a / busbar-mcp -> busbar-contract are; BUSBAR-1.6.0.md THE DESIGN, §9 (BUSBAR-1.6.0.md:1140-1141): busbar-contract is \"the shapes and the author SDK\"." -why = "busbar-voice names busbar-contract: declared normal in the shipped graph, 1 declaration(s) at this commit. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." -drain = "struck when P3 deletes the legacy crate busbar-voice: the edge goes with the crate, and a row naming a crate the tree no longer has is a dead allowance." - [[dep]] from = "busbar" to = "busbar-a2a" @@ -1516,16 +1473,6 @@ cite = "ARCHITECTURE_ALLOWED root -> legacy (xtask/src/gates/kind_isolation.r why = "busbar names busbar-mcp: declared dev in the test graph (features test-support), 1 declaration(s) at this commit, beside the shipped edge. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." drain = "struck when P3 deletes the legacy crate busbar-mcp: the edge goes with the crate, and a row naming a crate the tree no longer has is a dead allowance." -[[dep]] -from = "busbar" -to = "busbar-voice" -half = "test" -count = "1" -verdict = "allowed" -cite = "ARCHITECTURE_ALLOWED root -> legacy (xtask/src/gates/kind_isolation.rs:617); BUSBAR-1.6.0.md THE DESIGN, §9 (BUSBAR-1.6.0.md:1140): busbar is \"the binary and composition root\", which names every crate it links." -why = "busbar names busbar-voice: declared dev in the test graph (features test-support), 1 declaration(s) at this commit, beside the shipped edge. The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack that was not drained on the commit that drained the edge." -drain = "struck when P3 deletes the legacy crate busbar-voice: the edge goes with the crate, and a row naming a crate the tree no longer has is a dead allowance." - [[dep]] from = "busbar" to = "busbar-auth-oauth" diff --git a/qa/loc.toml b/qa/loc.toml index 844540753e..667e78ab80 100644 --- a/qa/loc.toml +++ b/qa/loc.toml @@ -49,8 +49,9 @@ crates = [ "busbar-plane-decisions", "busbar-plane-mcp", "busbar-plane-streaming", - "busbar-voice", ] +# `busbar-voice` HAS NO ROW ANY MORE: FLIP-STREAMING deleted the legacy streams crate once the +# streaming plane's door served every route (#18). # `busbar-voice-codec` HAS NO ROW ANY MORE (owner ruling R7, 2026-09-27, #39): it dissolved into # `busbar-plane-streaming`'s own `codec` module, a pure move — the LOC it carried is still counted, # now under the `busbar-plane-streaming` row above. diff --git a/qa/plane-secret-blindness.toml b/qa/plane-secret-blindness.toml index 7d2335d391..08cddc900f 100644 --- a/qa/plane-secret-blindness.toml +++ b/qa/plane-secret-blindness.toml @@ -40,7 +40,3 @@ rule = "plane-secret-blindness:names-a-resolver" file = "crates/busbar-mcp/src/mcp/upstream.rs" item = "resolve_linked_string" -[[finding]] -rule = "plane-secret-blindness:names-a-resolver" -file = "crates/busbar-voice/src/mount.rs" -item = "SecretResolve" From 52070db97b48f8790bd94e2a3ce20622fdc79231 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:59:37 -0700 Subject: [PATCH 40/82] FLIP-STREAMING: the session pump's test module imports Bytes itself; the capability ledger's voice columns cite the streaming plane The pump no longer builds a Bytes (the executor's output was its one use), so the import moves to the test module that does. qa/capability-equality.json, the voice-client / voice-server PLANE column: every proven cell cited a busbar-voice test, and the crate is deleted. hooks-tap (both) is proven by the streaming plane's own both-ways cell (tests/conformance.rs::a_session_open_is_projected_for_the_hooks_and_a_rewrite_patches_it); the eleven cells whose capability is the kernel's on the driver path (breaker, gate, audit chain, budget attribution, metrics, egress credential) are `missing` until a cell drives the streaming door for them. The ROOT column is unchanged: it awaits the architect's ruling on the root-voice leg. --- .../src/session_pump.rs | 2 - .../src/tests/session_pump_tests.rs | 2 + qa/capability-equality.json | 37 +++++++------------ 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/crates/busbar-plane-streaming/src/session_pump.rs b/crates/busbar-plane-streaming/src/session_pump.rs index 5188d10e0c..a71c7b00ca 100644 --- a/crates/busbar-plane-streaming/src/session_pump.rs +++ b/crates/busbar-plane-streaming/src/session_pump.rs @@ -21,8 +21,6 @@ use std::collections::HashMap; -use bytes::Bytes; - use crate::codec::ir::codec::{DecodeState, DuplexReader, DuplexWriter, WireEvent}; use crate::codec::ir::config::SessionConfig; use crate::codec::ir::control::IrDuplexControl; diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index bbf23d5f14..80e8ceb113 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -5,6 +5,8 @@ use std::sync::{Arc, Mutex}; +use bytes::Bytes; + use super::*; use crate::codec::ir::codec::OpenAiRealtimeCodec; use crate::governed::{GovernedCalls, ReplyRefusal}; diff --git a/qa/capability-equality.json b/qa/capability-equality.json index 49041e9af9..3085a4f9f1 100644 --- a/qa/capability-equality.json +++ b/qa/capability-equality.json @@ -832,8 +832,7 @@ { "capability": "breaker-trip", "plane": "voice-client", - "state": "proven", - "test": "crates/busbar-voice/src/tests/breaker_tests.rs::a_hard_down_provider_records_into_the_core_cell_and_opens_it", + "state": "missing", "root": { "state": "none", "leg": "root-voice", @@ -853,8 +852,7 @@ { "capability": "breaker-fastfail", "plane": "voice-client", - "state": "proven", - "test": "crates/busbar-voice/src/tests/breaker_tests.rs::a_tripped_provider_cell_refuses_before_the_dial", + "state": "missing", "root": { "state": "proven", "leg": "root-voice", @@ -895,7 +893,7 @@ "capability": "hooks-tap", "plane": "voice-client", "state": "proven", - "test": "crates/busbar-voice/src/tests/hook_tap_tests.rs::a_rewrite_hook_edits_the_session_open_params_before_the_provider_dial", + "test": "crates/busbar-plane-streaming/tests/conformance.rs::a_session_open_is_projected_for_the_hooks_and_a_rewrite_patches_it", "root": { "state": "none", "leg": "root-voice", @@ -906,7 +904,7 @@ "capability": "hooks-tap", "plane": "voice-server", "state": "proven", - "test": "crates/busbar-voice/src/tests/hook_tap_tests.rs::a_rewrite_hook_edits_the_session_open_params_before_the_provider_dial", + "test": "crates/busbar-plane-streaming/tests/conformance.rs::a_session_open_is_projected_for_the_hooks_and_a_rewrite_patches_it", "root": { "state": "none", "leg": "root-voice", @@ -916,8 +914,7 @@ { "capability": "hooks-gate", "plane": "voice-client", - "state": "proven", - "test": "crates/busbar-voice/src/tests/hook_gate_tests.rs::a_reject_all_operator_gate_refuses_a_ws_accept_before_the_upgrade", + "state": "missing", "root": { "state": "none", "leg": "root-voice", @@ -927,8 +924,7 @@ { "capability": "hooks-gate", "plane": "voice-server", - "state": "proven", - "test": "crates/busbar-voice/src/tests/hook_gate_tests.rs::a_reject_all_operator_gate_refuses_a_ws_accept_before_the_upgrade", + "state": "missing", "root": { "state": "none", "leg": "root-voice", @@ -938,8 +934,7 @@ { "capability": "audit-chain", "plane": "voice-client", - "state": "proven", - "test": "crates/busbar-voice/src/tests/audit_chain_tests.rs::an_opened_voice_session_seals_a_genesis_event_and_survives_reattach_on_the_durable_chain", + "state": "missing", "root": { "state": "proven", "leg": "root-voice", @@ -949,8 +944,7 @@ { "capability": "audit-chain", "plane": "voice-server", - "state": "proven", - "test": "crates/busbar-voice/src/tests/audit_chain_tests.rs::an_inbound_session_open_lands_a_sealed_genesis_event_the_front_door_wrote", + "state": "missing", "root": { "state": "proven", "leg": "root-voice", @@ -960,8 +954,7 @@ { "capability": "governance-budget", "plane": "voice-client", - "state": "proven", - "test": "crates/busbar-voice/src/tests/governance_budget_tests.rs::a_session_lease_settled_past_its_cap_hard_closes_and_refuses_further_spend", + "state": "missing", "root": { "state": "proven", "leg": "root-voice", @@ -971,8 +964,7 @@ { "capability": "governance-budget", "plane": "voice-server", - "state": "proven", - "test": "crates/busbar-voice/src/tests/governance_budget_tests.rs::the_session_lease_bills_the_presenting_key_and_refuses_past_the_cap_with_a_hard_close", + "state": "missing", "root": { "state": "proven", "leg": "root-voice", @@ -1002,8 +994,7 @@ { "capability": "metrics", "plane": "voice-client", - "state": "proven", - "test": "crates/busbar-voice/src/tests/metrics_tests.rs::a_voice_session_open_increments_the_plane_labelled_counter", + "state": "missing", "root": { "state": "proven", "leg": "root-voice", @@ -1013,8 +1004,7 @@ { "capability": "metrics", "plane": "voice-server", - "state": "proven", - "test": "crates/busbar-voice/src/tests/metrics_tests.rs::a_voice_session_open_increments_the_plane_labelled_counter", + "state": "missing", "root": { "state": "proven", "leg": "root-voice", @@ -1064,8 +1054,7 @@ { "capability": "egress-auth", "plane": "voice-client", - "state": "proven", - "test": "crates/busbar-voice/src/tests/egress_tests.rs::the_provider_dial_carries_the_planned_credential_and_never_a_caller_token", + "state": "missing", "root": { "state": "none", "leg": "root-voice", From c3bc2a277f45ad96e2efca51631f03ae01a841cc Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:00:27 -0700 Subject: [PATCH 41/82] FLIP-STREAMING: the streaming door's cancel slot states PlaneCancelIn/PlaneCancelOut (SEAM-L(r)); it reads the embedded lifecycle ticket and writes no record --- crates/busbar-plane-streaming/src/door.rs | 32 +++++++++++-------- .../tests/conformance.rs | 13 +++++--- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 4c270e61bd..a1e94293e0 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -57,16 +57,17 @@ use busbar_contract::abi::mechanism::door::{ KindTailHead, Section, Statement, SECTION_CONSUMED, SECTION_DECLARING, }; use busbar_contract::abi::mechanism::lifecycle::{ - CancelIn, CancelOut, GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, + GenIn, RefreshIn, ReleaseIn, TickIn, TickOut, ValidateIn, }; use busbar_contract::abi::mechanism::ticket::Ticket; use busbar_contract::abi::plane::{ ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, OpClass, OutField, - PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, - PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, - CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, - FROM_KERNEL, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, PIECE_HAS_STATUS, PIECE_LAST, - PIECE_OUT_TEXT, ROUTE_DIRECT, SHAPE_PIECEWISE, SPAN_ABSENT, UNITS_ESTIMATED, UNITS_REPORTED, + PlaneCancelIn, PlaneCancelOut, PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, + PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, + ServeIn, ServeOut, UnitCount, CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, EMIT_DONE, + EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, INGRESS_DUPLEX_SESSION, + INGRESS_REQUEST_RESPONSE, PIECE_HAS_STATUS, PIECE_LAST, PIECE_OUT_TEXT, ROUTE_DIRECT, + SHAPE_PIECEWISE, SPAN_ABSENT, UNITS_ESTIMATED, UNITS_REPORTED, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; @@ -738,14 +739,19 @@ slot!( ); // `cancel`: an op on a session's ticket ends the session (each side tells the other); a request -// unit's cancel is declined. -slot!(Cancel, CancelIn, CancelOut, |instance, input, out| { - if let Some(p) = instance.get() { - lock(&p.sessions).cancel(input.get().ticket); +// unit's cancel is declined. It writes no record. +slot!( + Cancel, + PlaneCancelIn, + PlaneCancelOut, + |instance, input, out| { + if let Some(p) = instance.get() { + lock(&p.sessions).cancel(input.get().cancel.ticket); + } + out.set(|o| &o.cancel.disposition, CANCEL_FAILED); + Outcome::Ready } - out.set(|o| &o.disposition, CANCEL_FAILED); - Outcome::Ready -}); +); slot!(Release, ReleaseIn, OutHead, |_, _, _out| { Outcome::Ready }); diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index 9986453125..d0ce78812e 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -14,7 +14,7 @@ use busbar_contract::abi::mechanism::call::{ AbiStr, Blob, Field, Outcome, Span, BLOB_JSON, BLOB_OCTETS, }; use busbar_contract::abi::mechanism::lifecycle::{ - slot as life, CancelIn, CancelOut, GenIn, RefreshIn, TickIn, TickOut, ValidateIn, + slot as life, GenIn, RefreshIn, TickIn, TickOut, ValidateIn, }; use busbar_contract::abi::plane::{ slot, ArriveIn, ArriveOut, OnPieceIn, OnPieceOut, OutField, PlaneOpenIn, PlaneOpenOut, @@ -244,14 +244,17 @@ fn script(p: &Plugin, public_url: Option<&'static str>) -> Vec { let c = p.call(life::TICK, &mut k); t.push(format!("tick {:?} next={}", c.outcome, k.out.next_tick_ns)); - let mut x: Frame = Frame::new(z(), z()); - x.input.head = in_head(); - x.out.head = out_head(); + let mut x: Frame< + busbar_contract::abi::plane::PlaneCancelIn, + busbar_contract::abi::plane::PlaneCancelOut, + > = Frame::new(z(), z()); + x.input.cancel.head = in_head(); + x.out.cancel.head = out_head(); let c = p.call(life::CANCEL, &mut x); t.push(format!( "cancel {:?} failed={}", c.outcome, - x.out.disposition == CANCEL_FAILED + x.out.cancel.disposition == CANCEL_FAILED )); t } From 91bf9be6d67c46bad6050e759f1d14deb032475f Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:04:00 -0700 Subject: [PATCH 42/82] FLIP-STREAMING: the gates' legacy-plane rosters strike busbar-voice, the crate the flip deleted Each change is what the gate's own finding asked for once crates/busbar-voice left the tree: - kind-isolation:registry (legacy-retired): LEGACY_CRATES and the `legacy` kind matchers drop it; the drain-pair self-test plants its row on busbar-a2a (a legacy crate still on disk). - qa-names: dep_wall LEGACY and no_float_money's audio count root drop the dead path. - plane-pricing-blindness:plane-roots and plane-secret-blindness PLANE_HOMES drop the dead crate. - structure-lint:plane-roots / axis:scope / census:scope: xtask::planes::PLANE_KEYS (the ON-DISK legacy roster) is llm, mcp, a2a; the self-tests that planted a second voice home plant a second llm home. - package-selectors: verify-1.6.0-done.sh no longer runs `cargo test -p busbar-voice`. - scripts/plane-keys.sh: PLANE_KEYS is llm mcp a2a; the locked `streaming` plane's on-disk key is plane-streaming (plane-delete-test's tables follow it); plane-grep-gate keeps `voice` as a neutral needle by name and scans no deleted root. - qa/plane-purity-strict.toml: the voice test-reach ceiling goes with the key. --- qa/plane-purity-strict.toml | 2 +- scripts/plane-delete-test.sh | 20 ++++----- scripts/plane-grep-gate.sh | 27 +++++------- scripts/plane-keys.sh | 6 ++- scripts/verify-1.6.0-done.sh | 1 - xtask/src/gates/dep_wall.rs | 1 - xtask/src/gates/kind_isolation.rs | 13 +++--- xtask/src/gates/no_float_money.rs | 6 +-- xtask/src/gates/plane_pricing_blindness.rs | 6 +-- xtask/src/gates/plane_secret_blindness.rs | 3 +- xtask/src/gates/structure_lint/exit_tests.rs | 46 ++++++++++---------- xtask/src/gates/structure_lint/selftest.rs | 12 ++--- xtask/src/planes.rs | 6 ++- xtask/src/toml_doc.rs | 2 +- 14 files changed, 75 insertions(+), 76 deletions(-) diff --git a/qa/plane-purity-strict.toml b/qa/plane-purity-strict.toml index 0c5524881b..168ae67ff9 100644 --- a/qa/plane-purity-strict.toml +++ b/qa/plane-purity-strict.toml @@ -45,8 +45,8 @@ BACKWARDS = 33 # this file, the scanner's own needle in `xtask/src/gates/plane_purity/scanner.rs` (the `Mode::Reverse` # arm). Until then these four figures are ceilings over a needle that cannot match, NOT a measured # agreement between two gates. +# `voice = 0` is struck: FLIP-STREAMING deleted crates/busbar-voice, and xtask::planes no longer names it. [test-reach] llm = 24 mcp = 1 a2a = 3 -voice = 0 diff --git a/scripts/plane-delete-test.sh b/scripts/plane-delete-test.sh index 2b9f5e6c86..50d2f8e83d 100755 --- a/scripts/plane-delete-test.sh +++ b/scripts/plane-delete-test.sh @@ -197,20 +197,20 @@ command -v cargo >/dev/null 2>&1 || { echo "plane-delete-test: cargo not found" # plane-kind name). The NEUTRAL feature set to keep ON for the neutral-crate check (every default plane # EXCEPT the one being removed) — llm has no neutral-side feature of its own, so removing it keeps both # plane-mcp and plane-a2a. A crate/feature that appears or moves is a one-line edit here. -# `voice` (busbar-voice, Plane 4) is WIRED into the bin and DEFAULT-ON, on both of its features: it has -# a `dep:busbar-voice` optional dependency and the `plane-streaming` bin feature, whose forwards to the -# plane crate are `dep:busbar-voice`, `busbar-voice?/runtime` and `busbar-voice?/openapi-schema` — all -# three stripped by neutralise_bin. Its bin_feature is `plane-streaming`; its neutral_keep is the full -# default plane set (removing voice touches neither mcp nor a2a). A feature that FORWARDS to +# The streaming plane (`plane-streaming`, crates/busbar-plane-streaming since FLIP-STREAMING deleted +# the legacy busbar-voice crate) is WIRED into the bin and DEFAULT-ON: a `dep:busbar-plane-streaming` +# optional dependency behind the `plane-streaming` bin feature, stripped by neutralise_bin. Its +# bin_feature is `plane-streaming`; its neutral_keep is the full default plane set (removing it +# touches neither mcp nor a2a). A feature that FORWARDS to # `plane-streaming` would leave the bin's default build incoherent without the crate until it came out # too — which is what neutralise_bin's forwarding closure is for. -bin_feature() { case "$1" in llm) echo proto-llm ;; mcp) echo plane-mcp ;; a2a) echo plane-a2a ;; voice) echo plane-streaming ;; plane-decisions) echo plane-decisions ;; esac; } +bin_feature() { case "$1" in llm) echo proto-llm ;; mcp) echo plane-mcp ;; a2a) echo plane-a2a ;; plane-streaming) echo plane-streaming ;; plane-decisions) echo plane-decisions ;; esac; } neutral_keep() { case "$1" in llm) echo "plane-mcp,plane-a2a" ;; mcp) echo "plane-a2a" ;; a2a) echo "plane-mcp" ;; - voice) echo "plane-mcp,plane-a2a" ;; + plane-streaming) echo "plane-mcp,plane-a2a" ;; plane-decisions) echo "plane-mcp,plane-a2a" ;; esac } @@ -620,14 +620,14 @@ plane_probe_path() { llm) echo "/v1/chat/completions" ;; mcp) echo "/.well-known/oauth-protected-resource/mcp" ;; a2a) echo "/a2a" ;; - voice) echo "/v1/realtime/client_secrets" ;; + plane-streaming) echo "/v1/realtime/client_secrets" ;; plane-decisions) echo "/v1/systemone" ;; esac } plane_probe_body() { case "$1" in a2a) printf '{"jsonrpc":"2.0","method":"message/send","id":1}' ;; - voice) printf '{"model":"gpt-realtime"}' ;; + plane-streaming) printf '{"model":"gpt-realtime"}' ;; plane-decisions) printf '{"state":{},"context":{}}' ;; llm | mcp) printf '' ;; esac @@ -637,7 +637,7 @@ plane_probe_body() { # the SUBJECT boot must not carry. Space-separated; empty for a plane the fixture does not configure. # This is the same fail-closed pairing the product enforces at resolve: the section exists only while # the plane that owns it is compiled in. -plane_config_sections() { case "$1" in mcp) echo "mcp" ;; a2a) echo "agents" ;; voice) echo "streams" ;; plane-decisions) echo "decisions" ;; *) echo "" ;; esac; } +plane_config_sections() { case "$1" in mcp) echo "mcp" ;; a2a) echo "agents" ;; plane-streaming) echo "streams" ;; plane-decisions) echo "decisions" ;; *) echo "" ;; esac; } # section_omitted
→ 0 when
is in the space-separated . section_omitted() { case " ${2:-} " in *" $1 "*) return 0 ;; *) return 1 ;; esac; } diff --git a/scripts/plane-grep-gate.sh b/scripts/plane-grep-gate.sh index 8a08e4d2ba..5bd0728a29 100755 --- a/scripts/plane-grep-gate.sh +++ b/scripts/plane-grep-gate.sh @@ -109,7 +109,9 @@ DIALECTS="openai gemini anthropic bedrock cohere responses" # the set by ONE named deletion there. The env override exists for the self-test's blind-scan cases # below and nothing else; CI never sets it. NEUTRAL_ROOTS="${PLANE_GREP_NEUTRAL_ROOTS:-$(neutral_src_roots)}" -NEUTRAL_NEEDLES="$DIALECTS $PLANE_KEYS_PROTOCOL" +# `voice` stays a NEUTRAL needle by name: FLIP-STREAMING took it off PLANE_KEYS with crates/busbar-voice, +# and the streaming plane's dialect word must still never reach the neutral side. +NEUTRAL_NEEDLES="$DIALECTS $PLANE_KEYS_PROTOCOL voice" # TWO ROOTS PER PLANE since the codec split: each protocol plugin kept its I/O half under the # historical crate name and shed its pure half into a `-codec` crate a PURE kind may name. The gate # scans sources, not manifests, so both halves are named or the moved files stop being scanned — @@ -126,10 +128,9 @@ MCP_NEEDLES="$DIALECTS $(plane_keys_other mcp)" # which this needle gate does not read — a plane-kind crate is held to the plugin-kind rules instead. A2A_ROOT="crates/busbar-a2a/src" A2A_NEEDLES="$DIALECTS $(plane_keys_other a2a)" -# VOICE IS ONE ROOT NOW TOO, same reason (owner ruling R7, 2026-09-27, `BUSBAR-1.6.0.md` THE DESIGN, §9/#39): -# `busbar-voice-codec` dissolved into `busbar-plane-streaming`'s own `codec` module, a plane-kind -# crate this needle gate does not read. -VOICE_ROOT="crates/busbar-voice/src" +# VOICE HAS NO ROOT: FLIP-STREAMING deleted crates/busbar-voice, and the streaming plane is +# `busbar-plane-streaming`, a plane-kind crate this needle gate does not read. VOICE_NEEDLES remains +# for the self-test's symmetric case, which plants its own file. VOICE_NEEDLES="$DIALECTS $(plane_keys_other voice)" # The neutral Operation enum — generic op vocabulary, explicitly in-scope-neutral. Excluded whole. @@ -523,24 +524,21 @@ run_report() { require_roots mcp $MCP_ROOT # shellcheck disable=SC2086 require_roots a2a $A2A_ROOT - # shellcheck disable=SC2086 - require_roots voice $VOICE_ROOT local tmp; tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN : >"$tmp/hits" # Prepass: compute the test-support module subtrees to drop (across every scanned root). - compute_mod_excludes $NEUTRAL_ROOTS $MCP_ROOT $A2A_ROOT $VOICE_ROOT + compute_mod_excludes $NEUTRAL_ROOTS $MCP_ROOT $A2A_ROOT - local nf mf af vf - nf="$(prod_files $NEUTRAL_ROOTS)"; mf="$(prod_files $MCP_ROOT)"; af="$(prod_files $A2A_ROOT)"; vf="$(prod_files $VOICE_ROOT)" - local n_nf n_mf n_af n_vf + local nf mf af + nf="$(prod_files $NEUTRAL_ROOTS)"; mf="$(prod_files $MCP_ROOT)"; af="$(prod_files $A2A_ROOT)" + local n_nf n_mf n_af n_nf="$(count_files "$nf")"; n_mf="$(count_files "$mf")" - n_af="$(count_files "$af")"; n_vf="$(count_files "$vf")" + n_af="$(count_files "$af")" require_files neutral "$n_nf" require_files mcp "$n_mf" require_files a2a "$n_af" - require_files voice "$n_vf" : >"$tmp/allowused" # shellcheck disable=SC2086 @@ -549,8 +547,6 @@ run_report() { scan "$MCP_NEEDLES" "$tmp/allowused" $mf >>"$tmp/hits" # shellcheck disable=SC2086 scan "$A2A_NEEDLES" "$tmp/allowused" $af >>"$tmp/hits" - # shellcheck disable=SC2086 - scan "$VOICE_NEEDLES" "$tmp/allowused" $vf >>"$tmp/hits" # ── THE DEAD-ROW CHECK — an allowlist entry that suppresses nothing is reported, never carried ──── # Every row above exists to say "this specific hit is frozen contract, not dialect leakage". A row @@ -588,7 +584,6 @@ EOF note "neutral roots: $NEUTRAL_ROOTS ($n_nf file(s); bans: $NEUTRAL_NEEDLES)" note "mcp root: $MCP_ROOT ($n_mf file(s); bans: $MCP_NEEDLES)" note "a2a root: $A2A_ROOT ($n_af file(s); bans: $A2A_NEEDLES)" - note "voice root: $VOICE_ROOT ($n_vf file(s); bans: $VOICE_NEEDLES)" note "excluded: $OPERATION_EXCLUDE (neutral Operation enum), */tests/*, *_test(s).rs, #[cfg(test)]" hdr "by needle (a clean tree reports zero)" diff --git a/scripts/plane-keys.sh b/scripts/plane-keys.sh index 642ff988c0..8bd300938a 100644 --- a/scripts/plane-keys.sh +++ b/scripts/plane-keys.sh @@ -44,7 +44,9 @@ # for their crate/wiring counterpart to land; until then, PLANE_KEYS states what is TRUE ON DISK, and # PLANE_KEYS_LOCKED (below) states what is true IN DOCTRINE, so no caller can mistake one for the # other by reading only this line. -PLANE_KEYS="llm mcp a2a voice" +# `voice` is struck: FLIP-STREAMING deleted crates/busbar-voice; the streaming plane is +# crates/busbar-plane-streaming (plane_ondisk_key below), scanned by the plane-kind regime. +PLANE_KEYS="llm mcp a2a" # The protocol subset: every plane key except `llm`. Derived from PLANE_KEYS so adding a plane in # one place flows here automatically. @@ -76,7 +78,7 @@ PLANE_KEYS_LOCKED="llm mcp a2a streaming decisions" # entry here returns empty: a caller must report that plane as a NAMED GAP, never as a silent pass. plane_ondisk_key() { case "$1" in - streaming) printf 'voice' ;; + streaming) printf 'plane-streaming' ;; decisions) printf 'plane-decisions' ;; llm | mcp | a2a) printf '%s' "$1" ;; *) printf '' ;; diff --git a/scripts/verify-1.6.0-done.sh b/scripts/verify-1.6.0-done.sh index 7e2a88edeb..bbb73706d0 100755 --- a/scripts/verify-1.6.0-done.sh +++ b/scripts/verify-1.6.0-done.sh @@ -1246,7 +1246,6 @@ end_group # ───────────────────────────────────────────────────────────────────────────────────────────────── begin_group "TEST — full workspace + voice runtime" step "cargo test --workspace" cargo test --workspace --quiet -step "cargo test -p busbar-voice --features runtime" cargo test -p busbar-voice --features runtime --quiet end_group # ───────────────────────────────────────────────────────────────────────────────────────────────── diff --git a/xtask/src/gates/dep_wall.rs b/xtask/src/gates/dep_wall.rs index 09f7edbec4..34ee8e903c 100644 --- a/xtask/src/gates/dep_wall.rs +++ b/xtask/src/gates/dep_wall.rs @@ -58,7 +58,6 @@ const LEGACY: &[&str] = &[ "crates/busbar-llm", "crates/busbar-mcp", "crates/busbar-a2a", - "crates/busbar-voice", ]; /// The external source a plugin repository resolves from. diff --git a/xtask/src/gates/kind_isolation.rs b/xtask/src/gates/kind_isolation.rs index 8ef4eb0e61..fedf2a673d 100644 --- a/xtask/src/gates/kind_isolation.rs +++ b/xtask/src/gates/kind_isolation.rs @@ -362,7 +362,7 @@ static KINDS: &[KindDef] = &[ KindDef { kind: "legacy", family: Family::Plane, - matchers: &["=busbar-llm", "=busbar-mcp", "=busbar-a2a", "=busbar-voice"], + matchers: &["=busbar-llm", "=busbar-mcp", "=busbar-a2a"], }, ]; @@ -439,8 +439,9 @@ const PENDING_EDGES: &[(&str, &str)] = &[ // class the design grants ahead of the tree lands here. ]; -/// The retiring 1.5.x crates, named so the ratchet can check they still exist. -const LEGACY_CRATES: &[&str] = &["busbar-llm", "busbar-mcp", "busbar-a2a", "busbar-voice"]; +/// The retiring 1.5.x crates, named so the ratchet can check they still exist. `busbar-voice` is +/// struck: FLIP-STREAMING deleted it once the streaming plane's door served every route. +const LEGACY_CRATES: &[&str] = &["busbar-llm", "busbar-mcp", "busbar-a2a"]; /// THE MANIFESTS IN THIS REPOSITORY THAT ARE NOT CRATES OF THE TREE, each with the sentence that /// says why, and each on the expiry ratchet every allowance in this file lives under: an entry that @@ -9236,7 +9237,7 @@ impl Gate for KindIsolationGate { // // THE SHIPPED GATE, NOT THE DEBT-FREE SUBJECT: this case already brings its own green base // (the empty table), and the subject's debt — measured over the REAL table, whose - // `busbar-voice` row is exactly this plant's row — would take the planted finding out of + // `busbar-a2a` row is exactly this plant's row — would take the planted finding out of // view as if it were the tree's. The pair below is the same transition read the other way. report.push(prove_rows_red( &cx.with_overlay(registry_plant("")), @@ -9812,13 +9813,13 @@ fn announced_reaching_drain_target(cx: &Ctx) -> Overlay { /// THE LEGACY CRATE THE DRAIN PAIR IS ABOUT. It must be a `legacy` crate that is really on disk, /// or the red case plants a row for nothing and the green case removes a manifest that is not there. -const DRAIN_PLANT_FROM: &str = "busbar-voice"; +const DRAIN_PLANT_FROM: &str = "busbar-a2a"; /// ONE `[[transitional]]` row for [`DRAIN_PLANT_FROM`], and no other row — the red case's plant /// and, with the crate removed, the green case's. fn drain_row_plant() -> Overlay { registry_plant(&format!( - "[[transitional]]\nfrom = \"{DRAIN_PLANT_FROM}\"\nto = \"busbar-plane-streaming\"\nreason = \ + "[[transitional]]\nfrom = \"{DRAIN_PLANT_FROM}\"\nto = \"busbar-plane-a2a\"\nreason = \ \"legacy drain\"\n" )) } diff --git a/xtask/src/gates/no_float_money.rs b/xtask/src/gates/no_float_money.rs index 7027c58bd6..26a766050e 100644 --- a/xtask/src/gates/no_float_money.rs +++ b/xtask/src/gates/no_float_money.rs @@ -456,10 +456,8 @@ pub const COUNT_READ_ROOTS: &[CountRoot] = &[ // `crates/busbar-voice-codec/src` home is struck. CountRoot { area: "the audio codecs, engine and plane", - homes: &[ - "crates/busbar-voice/src", - "crates/busbar-plane-streaming/src", - ], + // `crates/busbar-voice/src` is struck: FLIP-STREAMING deleted the legacy streams crate. + homes: &["crates/busbar-plane-streaming/src"], floor: 28, }, // THE ONE FOLD THIS GROUP WAS BUILT FOR ACTUALLY HAPPENED, AND THE GROUP DID NOT MOVE WITH IT. diff --git a/xtask/src/gates/plane_pricing_blindness.rs b/xtask/src/gates/plane_pricing_blindness.rs index c3cd352853..5f7b4a278a 100644 --- a/xtask/src/gates/plane_pricing_blindness.rs +++ b/xtask/src/gates/plane_pricing_blindness.rs @@ -216,10 +216,8 @@ pub const PLANE_CRATES: &[PlaneCrate] = &[ dir: "busbar-a2a", placement: "#83 SPLIT -> def 16-20", }, - PlaneCrate { - dir: "busbar-voice", - placement: "#83 SPLIT -> def 16-20", - }, + // `busbar-voice` is struck: FLIP-STREAMING deleted the legacy streams crate, and the streaming + // plane is `busbar-plane-streaming`, scanned as a plane crate. ]; /// The five CONTRACT planes that must each still declare at least one meter class ([`ROW_DECLARATION`]). diff --git a/xtask/src/gates/plane_secret_blindness.rs b/xtask/src/gates/plane_secret_blindness.rs index cdf665c96c..0c7e79ab98 100644 --- a/xtask/src/gates/plane_secret_blindness.rs +++ b/xtask/src/gates/plane_secret_blindness.rs @@ -33,7 +33,8 @@ pub const NEEDLES: &[&str] = &[ ]; /// The crates that still carry plane code beside the `busbar-plane-*` crates. -pub const PLANE_HOMES: &[&str] = &["busbar-llm", "busbar-mcp", "busbar-a2a", "busbar-voice"]; +/// `busbar-voice` is struck: FLIP-STREAMING deleted the legacy streams crate. +pub const PLANE_HOMES: &[&str] = &["busbar-llm", "busbar-mcp", "busbar-a2a"]; pub const SPEC: Spec = Spec { gate: "plane-secret-blindness", diff --git a/xtask/src/gates/structure_lint/exit_tests.rs b/xtask/src/gates/structure_lint/exit_tests.rs index 49ec64b229..a128b036b5 100644 --- a/xtask/src/gates/structure_lint/exit_tests.rs +++ b/xtask/src/gates/structure_lint/exit_tests.rs @@ -79,63 +79,65 @@ fn item_183_the_axis_ban_judges_the_money_path_and_the_contract() { ); } -/// Item 184: a concern duplicated between two planes that are not mcp+a2a is seen. +/// Item 184: a concern duplicated between two planes that are not mcp+a2a is seen. (The pair was +/// llm+voice until FLIP-STREAMING deleted `busbar-voice`; llm+a2a is the same "not mcp+a2a" pair.) #[test] -fn item_184_a_duplicate_between_llm_and_voice_is_seen() { +fn item_184_a_duplicate_between_llm_and_a2a_is_seen() { let mut ov = Overlay::new(); ov.set( - "crates/busbar-llm/src/planted_llm_voice_copy.rs", - "pub fn planted_llm_voice_copy() {}\n", + "crates/busbar-llm/src/planted_llm_a2a_copy.rs", + "pub fn planted_llm_a2a_copy() {}\n", ); ov.set( - "crates/busbar-voice/src/planted_llm_voice_copy.rs", - "pub fn planted_llm_voice_copy() {}\n", + "crates/busbar-a2a/src/a2a/planted_llm_a2a_copy.rs", + "pub fn planted_llm_a2a_copy() {}\n", ); names( &run(ov), UNLEDGERED, &[ - "`planted_llm_voice_copy`", - "llm:crates/busbar-llm/src/planted_llm_voice_copy.rs:1", - "voice:crates/busbar-voice/src/planted_llm_voice_copy.rs:1", + "`planted_llm_a2a_copy`", + "llm:crates/busbar-llm/src/planted_llm_a2a_copy.rs:1", + "a2a:crates/busbar-a2a/src/a2a/planted_llm_a2a_copy.rs:1", ], ); } -/// Item 223: a plane-local copy of another plane's helper (voice beside a2a) is seen, and a name the -/// ledger signed for mcp+a2a is NOT excused when a third plane grows a copy of it. +/// Item 223: a plane-local copy of another plane's helper (llm beside a2a; voice until FLIP-STREAMING +/// deleted `busbar-voice`) is seen, and a name the ledger signed for mcp+a2a is NOT excused when a +/// third plane grows a copy of it. #[test] -fn item_223_a_voice_copy_of_a_plane_helper_is_seen_and_a_signed_claim_does_not_stretch() { +fn item_223_a_third_plane_copy_of_a_plane_helper_is_seen_and_a_signed_claim_does_not_stretch() { let signed = plane_dups::ledger() .into_iter() .find(|r| r.name == "judge") .expect("the ledger signs `judge` for mcp and a2a"); let mut ov = Overlay::new(); ov.set( - "crates/busbar-voice/src/planted_voice_a2a_copy.rs", + "crates/busbar-llm/src/planted_llm_a2a_helper.rs", format!( - "pub fn planted_voice_a2a_copy() {{}}\npub fn {}() {{}}\n", + "pub fn planted_llm_a2a_helper() {{}}\npub fn {}() {{}}\n", signed.name ), ); ov.set( - "crates/busbar-a2a/src/a2a/planted_voice_a2a_copy.rs", - "pub fn planted_voice_a2a_copy() {}\n", + "crates/busbar-a2a/src/a2a/planted_llm_a2a_helper.rs", + "pub fn planted_llm_a2a_helper() {}\n", ); names( &run(ov), UNLEDGERED, &[ - "`planted_voice_a2a_copy`", - "voice:crates/busbar-voice/src/planted_voice_a2a_copy.rs:1", + "`planted_llm_a2a_helper`", + "llm:crates/busbar-llm/src/planted_llm_a2a_helper.rs:1", "`judge`", - "voice:crates/busbar-voice/src/planted_voice_a2a_copy.rs:2", + "llm:crates/busbar-llm/src/planted_llm_a2a_helper.rs:2", ], ); } /// Item 224: the plane set is derived — a plane on no constant is compared, and a plane the old -/// constant never named (voice) is judged by the plane-roots row. +/// two-plane constant never named (llm) is judged by the plane-roots row. #[test] fn item_224_the_plane_set_is_derived_not_spelled() { let mut ov = Overlay::new(); @@ -149,7 +151,7 @@ fn item_224_the_plane_set_is_derived_not_spelled() { "pub fn planted_derived_plane_helper() {}\n", ); ov.set( - "crates/busbar-planted-twin/src/voice/mod.rs", + "crates/busbar-planted-twin/src/llm/mod.rs", "pub const PLANE_DECL: busbar_kernel::plane::registry::PlaneDecl = PLANTED;\n", ); let v = run(ov); @@ -161,7 +163,7 @@ fn item_224_the_plane_set_is_derived_not_spelled() { "planted:crates/busbar-planted/src/lib.rs:2", ], ); - names(&v, PLANE_ROOTS, &["PLANE-ROOT-AMBIGUOUS", "`voice`"]); + names(&v, PLANE_ROOTS, &["PLANE-ROOT-AMBIGUOUS", "`llm`"]); } /// Item 236: the DEBT half is reachable — a DEBT row naming an undeclared concern is refused even diff --git a/xtask/src/gates/structure_lint/selftest.rs b/xtask/src/gates/structure_lint/selftest.rs index c3d70079a3..6585140aff 100644 --- a/xtask/src/gates/structure_lint/selftest.rs +++ b/xtask/src/gates/structure_lint/selftest.rs @@ -305,7 +305,7 @@ pub fn run<'a>(gate: &'a StructureLintGate, cx: &'a Ctx) -> Report<'a> { // for it, is two homes for one plane. let mut ov = Overlay::new(); ov.set( - "crates/busbar-planted/src/voice/mod.rs", + "crates/busbar-planted/src/llm/mod.rs", format!( "{} : busbar_kernel::plane::registry::PlaneDecl = PLANTED;\n", crate::planes::PLANE_GRAMMAR @@ -317,7 +317,7 @@ pub fn run<'a>(gate: &'a StructureLintGate, cx: &'a Ctx) -> Report<'a> { "a plane outside the old two-plane constant with two homes is refused", &[roots::ROW_PLANE_ROOTS], ov, - &["PLANE-ROOT-AMBIGUOUS", "voice"], + &["PLANE-ROOT-AMBIGUOUS", "llm"], )); // ── the denominator ────────────────────────────────────────────────────────────────────────── @@ -621,7 +621,9 @@ pub fn run<'a>(gate: &'a StructureLintGate, cx: &'a Ctx) -> Report<'a> { // * A THIRD COPY OF A LEDGERED NAME — the ledger's rows were argued for mcp and a2a; a copy in // a plane the claim was never signed for is a file nobody read, and matching the row by name // alone absorbed it. - let voice = addresses.plane("voice"); + // The third plane was voice until FLIP-STREAMING deleted `busbar-voice`; llm is the same + // "never loaded beside a2a" pair. + let voice = addresses.plane("llm"); let mut ov = Overlay::new(); ov.set( format!("{voice}/planted_shared_concern.rs"), @@ -642,11 +644,11 @@ pub fn run<'a>(gate: &'a StructureLintGate, cx: &'a Ctx) -> Report<'a> { let signed = t .plane_ledger .iter() - .find(|r| r.planes.iter().all(|p| p != "voice") && !r.name.ends_with(".rs")) + .find(|r| r.planes.iter().all(|p| p != "llm") && !r.name.ends_with(".rs")) .map(|r| r.name.clone()); let mut naming: Vec = vec![ "PLANE-DUPLICATE (symbol): `planted_shared_concern`".to_string(), - format!("voice:{voice}/planted_shared_concern.rs"), + format!("llm:{voice}/planted_shared_concern.rs"), "PLANE-DUPLICATE (symbol): `planted_derived_plane_helper`".to_string(), "planted:crates/busbar-planted/src/lib.rs".to_string(), ]; diff --git a/xtask/src/planes.rs b/xtask/src/planes.rs index 18622a015a..537da353a9 100644 --- a/xtask/src/planes.rs +++ b/xtask/src/planes.rs @@ -20,8 +20,10 @@ use std::fmt; use std::path::{Path, PathBuf}; -/// The canonical order is the doctrine order: the three original protocols, then voice (Plane 4). -pub const PLANE_KEYS: [&str; 4] = ["llm", "mcp", "a2a", "voice"]; +/// The canonical order is the doctrine order: the three original protocols. `voice` (Plane 4's +/// legacy crate, `crates/busbar-voice`) is struck: FLIP-STREAMING deleted it, and the streaming plane +/// is `busbar-plane-streaming`, scanned by the PLANE-KIND regime like every `busbar-plane-*` crate. +pub const PLANE_KEYS: [&str; 3] = ["llm", "mcp", "a2a"]; /// The default ownership grammar. Overridable for a fixture tree, the way /// `PLANE_ROOTS_GRAMMAR` is in the shell. diff --git a/xtask/src/toml_doc.rs b/xtask/src/toml_doc.rs index 6904e0e6ff..c2384950d8 100644 --- a/xtask/src/toml_doc.rs +++ b/xtask/src/toml_doc.rs @@ -611,7 +611,7 @@ forbidden = [ doc.table("gate") .expect("gate table") .list_of("plane_crates"), - vec!["busbar-llm", "busbar-mcp", "busbar-a2a", "busbar-voice"] + vec!["busbar-llm", "busbar-mcp", "busbar-a2a"] ); assert_eq!( doc.children("rules.legacy-reach.prefixes") From f896f645ba02f291e505413fd630e3b0f08ca4d4 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:09:55 -0700 Subject: [PATCH 43/82] =?UTF-8?q?FLIP-STREAMING:=20rulings=20Q3/Q4/Q7=20?= =?UTF-8?q?=E2=80=94=20the=20census=20floor=20drops=20by=20ledgered=20plan?= =?UTF-8?q?e-crate=20deletions,=20the=20dead=20WS-arrival=20seam=20is=20de?= =?UTF-8?q?leted,=20`streams`=20stays=20a=20config=20noun=20by=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Q3 (ARCHITECT 2026-10-05): [gate.census.deleted] covers gate.plane_crates. The plane_crates floor may drop by the base's plane crates that left the list, when every one is named in the ledger and absent from the tree (deleted_plane_crates, over the same excused_out the kind globs use); an unlisted drop stays RED. Row: "crates/busbar-voice" = "P3 streaming flip: legacy crate deleted (ARCHITECT 2026-10-04)"; the floor goes 4 -> 3. RED arms: census_moveout_tests::an_unlisted_plane_crate_drop_stays_red (and the green pair a_plane_crate_deleted_under_the_ledger_excuses_its_floor_drop). Q4: nothing in production installed ingress::duplex_ws::install_ws_arrivals after the flip (busbar-voice was its one installer), so the seam is deleted whole: WsArrival / WsAcceptFn / WsAcceptFuture / WsArrivalSpec / the registry, the router's mount_ws_arrivals, the root's `ws-arrivals` linked axis (Linked.ws_arrivals, register_ws_arrivals), reachability's axis row and the registry round-trip test. WS ingress is served by the door's session mount (mount_plane_session); the streams.* oracle cells are the proof it still is. Q7: plane-config-noun-gate keeps `streams` (the 1.5.5 customer config key) by name beside the nouns it reads off the legacy PlaneDecls; the noun floor counts it. --- crates/busbar-kernel/Cargo.toml | 13 ++- .../src/egress/tests/duplex_ws_tests.rs | 42 +------- crates/busbar-kernel/src/ingress/duplex_ws.rs | 101 ----------------- crates/busbar-kernel/src/router.rs | 102 ------------------ crates/busbar/Cargo.toml | 2 +- crates/busbar/src/linked_gen.rs | 1 - crates/busbar/src/main.rs | 16 +-- crates/busbar/src/root/linked.rs | 11 -- crates/busbar/src/root/tests/linked.rs | 1 - qa/construction.toml | 5 +- scripts/plane-config-noun-gate.sh | 10 +- xtask/src/gates/construction/census.rs | 33 +++++- .../construction/census_moveout_tests.rs | 40 ++++++- xtask/src/gates/reachability.rs | 1 - 14 files changed, 92 insertions(+), 286 deletions(-) diff --git a/crates/busbar-kernel/Cargo.toml b/crates/busbar-kernel/Cargo.toml index 42ee29a100..df5cb0f043 100644 --- a/crates/busbar-kernel/Cargo.toml +++ b/crates/busbar-kernel/Cargo.toml @@ -209,13 +209,12 @@ plane-mcp = ["egress-seam", "jsonrpc-ingress", "dispatch"] # other relocated `#[cfg(feature = "plane-a2a")]` sites now live in substrate, so this must turn the # substrate marker on with it. plane-a2a = ["egress-seam", "egress-stream", "card-signing", "jsonrpc-ingress", "relay"] -# THE NEUTRAL INBOUND WS-ACCEPT CAPABILITY — arms the core-side `mount_ws_arrivals` seam (router.rs) -# that mounts a plane's gauntlet-gated inbound-WebSocket arrivals. NAMES NO PLANE: any duplex plane -# (streaming today, a future telephony/stream plane tomorrow) turns on the same capability. Forwards to -# substrate's neutral `runtime` (which arms `axum/ws` + the `duplex_ws` acceptor and the `WsArrival` -# newtype/registry). OFF by default and pulled ONLY by the binary's `plane-streaming`, so the default/shipped -# money-path build carries no `axum/ws` edge (no tokio-tungstenite/sha1/base64) and this seam is -# compiled out entirely — byte-identical assembly, strong-form deletable. +# THE NEUTRAL INBOUND WS CAPABILITY — arms the core-side duplex session mount (router.rs, +# `mount_plane_session`) that answers a door plane's session upgrade. NAMES NO PLANE: any duplex +# plane turns on the same capability. Forwards to the neutral `runtime` (which arms `axum/ws` and the +# `duplex_ws` acceptor). OFF by default and pulled ONLY by the binary's `plane-streaming`, so the +# default/shipped money-path build carries no `axum/ws` edge (no tokio-tungstenite/sha1/base64) and +# this capability is compiled out entirely — byte-identical assembly, strong-form deletable. duplex-ws = ["runtime"] # The neutral capability markers. `dispatch`/`relay` gate the `#[cfg]`/dead-code-allowance sites in # this crate's engine source; `runtime` arms the full-duplex WS transport (client handshake + diff --git a/crates/busbar-kernel/src/egress/tests/duplex_ws_tests.rs b/crates/busbar-kernel/src/egress/tests/duplex_ws_tests.rs index fe1b3aeb3f..ee0a5113af 100644 --- a/crates/busbar-kernel/src/egress/tests/duplex_ws_tests.rs +++ b/crates/busbar-kernel/src/egress/tests/duplex_ws_tests.rs @@ -578,7 +578,7 @@ async fn dial_refuses_unpinned_and_guard_failing_targets() { ); } -// ── THE INBOUND WS-ACCEPT ARRIVAL SEAM (WsArrival newtype + accept_gauntlet + registry) ────────── +// ── THE GAUNTLET-GATED WS ACCEPT (accept_gauntlet) ─────────────────────────────────────────────── use std::sync::atomic::{AtomicUsize, Ordering}; @@ -663,43 +663,3 @@ async fn accept_gauntlet_refuse_returns_refusal_and_spawns_zero_socket_tasks() { "a proceeding accept binds the socket and runs exactly one on_socket task" ); } - -/// THE WS-ARRIVAL SPEC + PROCESS REGISTRY round-trip: a plane declares a `WsArrivalSpec` (the neutral, -/// single-compiled seam carrying the substrate-owned `WsArrival` newtype BY VALUE — never `Box`), the composition root installs it, and the core router drains it VERBATIM. Witnesses the R1 -/// seam shape (spec is constructible with a by-value accept fn) and the install/take registry. -#[test] -fn ws_arrival_spec_installs_and_drains_verbatim() { - use crate::ingress::duplex_ws::{ - install_ws_arrivals, take_ws_arrivals, WsArrival, WsArrivalSpec, - }; - use busbar_contract::abi::mechanism::route::RouteAuth; - - let spec = WsArrivalSpec { - path: "/v1/duplex/{id}".to_string(), - auth: RouteAuth::Key, - slot_key: "test-duplex-plane", - // The accept fn takes the newtype BY VALUE — the single-compiled `WsArrival`, never a box — and - // is ASYNC (returns a `WsAcceptFuture`) so a plane runs its pre-upgrade hooks before the upgrade. - accept: std::sync::Arc::new(|_a: WsArrival| { - Box::pin(async move { - axum::response::Response::builder() - .status(axum::http::StatusCode::NOT_IMPLEMENTED) - .body(axum::body::Body::empty()) - .expect("response builds") - }) as crate::ingress::duplex_ws::WsAcceptFuture - }), - }; - install_ws_arrivals(vec![spec]); - let drained = take_ws_arrivals(); - assert_eq!(drained.len(), 1, "the installed arrival drains verbatim"); - assert_eq!(drained[0].path, "/v1/duplex/{id}"); - assert_eq!(drained[0].slot_key, "test-duplex-plane"); - assert!(matches!(drained[0].auth, RouteAuth::Key)); - // Read-many: a second drain still yields the same installed set (the router may build twice). - assert_eq!( - take_ws_arrivals().len(), - 1, - "take is read-many, not destructive" - ); -} diff --git a/crates/busbar-kernel/src/ingress/duplex_ws.rs b/crates/busbar-kernel/src/ingress/duplex_ws.rs index 47d6d1b1a2..4755e96e71 100644 --- a/crates/busbar-kernel/src/ingress/duplex_ws.rs +++ b/crates/busbar-kernel/src/ingress/duplex_ws.rs @@ -255,104 +255,3 @@ where serve_messages(stream, sink, plane).await; }) } - -// ───────────────────────────────────────────────────────────────────────────────────────────── -// THE INBOUND WS-ACCEPT ARRIVAL SEAM — the neutral, substrate-owned vocabulary a plane DECLARES an -// inbound gauntlet-gated WS-accept route through, and the process registry the composition root -// installs those declarations into for the core router to drain. The plane names only this neutral -// seam; the CORE-side `mount_ws_arrivals` (behind `duplex-ws`) mounts a real WS-accept route per -// spec and hands the plane's accept fn a [`WsArrival`] by value. NONE of this is a `PlaneReqCtx` / -// `PlaneRouteSpec` field, so the always-compiled request path names no WS type (money-path byte- -// identity), and the upgrade is a substrate-owned newtype carried BY VALUE (never `Box`), so -// there is no dual-compile `TypeId` trap. -// ───────────────────────────────────────────────────────────────────────────────────────────── - -/// THE SUBSTRATE-OWNED NEWTYPE the WS upgrade rides on across the accept boundary — NEVER -/// `Box`. Single-compiled in substrate, so its type identity is the same in both dual- -/// compiled core instances (no downcast, no `TypeId` to diverge). Carries the upgrade BY VALUE plus -/// the verbatim per-request facts the plane's accept fn needs to build its `GauntletRequest` and open -/// its session — sourced from the SAME extractors the non-WS adapter reads, but assembled by the WS- -/// aware core mount. The only type-erased field is `slot`, the already-proven-safe plane-slot crossing -/// the non-WS `PlaneReqCtx::slot` already uses. -pub struct WsArrival { - /// The axum WS-upgrade extractor the route received. The ONLY way to consume it into a live socket - /// is [`serve_gauntlet`] / [`accept_gauntlet`]; a plane's accept fn never calls a bare `on_upgrade`. - pub upgrade: WebSocketUpgrade, - /// The middleware-resolved governance request context (`None` on a `RouteAuth::None` route). - pub gov: Option, - /// The middleware-resolved auth principal (`None` on a `RouteAuth::None` route). - pub principal: Option, - /// The resolved caller principal id, lifted from `gov` — the identity a plane binds session state to. - pub caller_principal: Option, - /// The request path this route was matched at. - pub path: String, - /// The full request URI (path + query). - pub uri: axum::http::Uri, - /// The request headers. - pub headers: axum::http::HeaderMap, - /// Any path-template captures (`{name}` → value), in match order. - pub path_params: Vec<(String, String)>, - /// The neutral engine host, minted core-side over the request's live engine snapshot. - pub host: Arc, - /// The plane's own per-generation runtime slot (the same `Arc` the plane's `build` produced), - /// captured by the core mount from the router's `plane_slots` and cloned into every arrival. - pub slot: Arc, -} - -/// The finished-`Response` FUTURE a [`WsAcceptFn`] returns — ASYNC (the mirror of the data-plane -/// `PlaneRouteFuture`), so a plane's accept fn can run its `async` operator hooks (gate/tap) BEFORE the -/// upgrade, exactly as the one-shot request path does, and still return the 101 (or a pre-upgrade -/// refusal) as its resolved value. -pub type WsAcceptFuture = std::pin::Pin + Send>>; - -/// ONE PLANE'S WS-ACCEPT HANDLER: a neutral fn that receives a [`WsArrival`] BY VALUE and returns a -/// finished axum [`Response`] (as a [`WsAcceptFuture`]). It MUST reach [`serve_gauntlet`] / -/// [`accept_gauntlet`] internally (the only path that consumes the upgrade into a live socket); it never -/// sees a bare `on_upgrade`. `Arc` so the core mount clones it into the per-request axum closure it -/// wires. ASYNC so the plane runs its pre-upgrade admission (operator hooks + the destination gauntlet) -/// before any socket binds — a hook/gauntlet refusal resolves to a pre-upgrade refusal Response. -pub type WsAcceptFn = Arc WsAcceptFuture + Send + Sync>; - -/// ONE WS-ACCEPT ARRIVAL a plane DECLARES: the exact path, the admission bar recorded VERBATIM in the -/// `CoreRouteTable` (identical shape to `PlaneRouteSpec::auth`, so the auth middleware enforces it -/// BEFORE the accept fn), the neutral accept fn, and the plane's registry `slot_key` so the core mount -/// resolves the live per-generation slot to carry on each [`WsArrival`] (exactly as the non-WS adapter -/// resolves it from `plane_slots`). Names only `axum`, `busbar_contract` and this crate — -/// no plane token, so adding a duplex plane is a new-crate-only diff. -#[derive(Clone)] -pub struct WsArrivalSpec { - /// The exact axum path pattern this WS-accept route is mounted at. - pub path: String, - /// The admission bar the core auth middleware enforces BEFORE the accept fn runs. - pub auth: busbar_contract::abi::mechanism::route::RouteAuth, - /// The plane's registry decl key — the core mount looks the live runtime slot up under it and, - /// when absent (the plane is unconfigured this generation), mounts nothing, exactly as the non-WS - /// route loop skips a plane with no slot. - pub slot_key: &'static str, - /// The neutral accept fn the core mount hands a [`WsArrival`], returning the finished response. - pub accept: WsAcceptFn, -} - -/// THE PROCESS-WIDE INSTALLED WS ARRIVALS — set once by the composition root -/// ([`install_ws_arrivals`]), read (cloned) by the core router at build ([`take_ws_arrivals`]). A -/// `OnceLock>`, mirroring `ingress::arrival::install_path_ingress`: the composition root -/// ASSEMBLES it from whichever duplex-plane crates are linked, install-once + read-many so a test that -/// builds several routers each mounts the same arrivals. -static INSTALLED_WS_ARRIVALS: std::sync::OnceLock> = std::sync::OnceLock::new(); - -/// INSTALL THE INBOUND WS-ACCEPT ARRIVALS — the composition root's one write, mirroring -/// [`crate::ingress::arrival::install_path_ingress`]. First-writer-wins (idempotent): a second call is -/// ignored rather than panicking, so a test harness that composes twice does not abort. Only the -/// composition root (or a test) calls this; a build with no duplex plane never does, and -/// [`take_ws_arrivals`] then yields nothing. -pub fn install_ws_arrivals(arrivals: Vec) { - let _ = INSTALLED_WS_ARRIVALS.set(arrivals); -} - -/// DRAIN (by clone) THE INSTALLED WS-ACCEPT ARRIVALS — the core router's one read at build. Returns an -/// empty vec when no duplex plane installed any, so the router mounts no WS-accept route (byte- -/// identical assembly). Cloning (not taking) keeps it read-many so repeated router builds are stable. -#[must_use] -pub fn take_ws_arrivals() -> Vec { - INSTALLED_WS_ARRIVALS.get().cloned().unwrap_or_default() -} diff --git a/crates/busbar-kernel/src/router.rs b/crates/busbar-kernel/src/router.rs index 648c39746b..3b2fa8cab1 100644 --- a/crates/busbar-kernel/src/router.rs +++ b/crates/busbar-kernel/src/router.rs @@ -481,14 +481,6 @@ pub(crate) fn base_data_router( "door session routes are not mounted: this build has no duplex upgrade" ); } - // THE PLANES' INBOUND WS-ACCEPT ARRIVALS, drained from the neutral substrate registry the - // composition root installed (`install_ws_arrivals`). Behind the neutral `duplex-ws` feature: the - // default/shipped money-path build compiles neither this line nor `mount_ws_arrivals`, so the - // router emits the same routes in the same order (byte-identical assembly) and `axum/ws` (hence - // tokio-tungstenite) stays out of the graph. Each spec's live slot is resolved from the SAME - // `plane_slots` the non-WS loop reads, so an unconfigured plane mounts nothing. - #[cfg(feature = "duplex-ws")] - let router = mount_ws_arrivals(router, plane_slots); // THE AUTHORIZATION SERVER'S ROUTES, or none of them. Same posture as the two planes above: a // deployment that is not an authorization server carries no `/authorize`, no `/token`, no // metadata document and nothing in the route table. Mounted through the seam @@ -696,100 +688,6 @@ fn mount_plane_session( ) } -/// Mount the planes' inbound WS-accept ARRIVALS onto the core router (the WS sibling of -/// [`mount_plane_route`]) — behind the neutral `duplex-ws` feature, so the default build compiles -/// none of this and names no WS type. Drains the neutral substrate registry -/// (`take_ws_arrivals`), and per spec calls the SAME [`crate::core_routes::CoreRouter::route`] the -/// non-WS adapter calls, with the spec's own `(path, GET, auth)` — so the `CoreRouteTable` row is -/// byte-identical in shape to a data route and the auth middleware enforces `auth` BEFORE the accept -/// fn, exactly as for `PlaneRouteSpec`. Method is GET: a WS upgrade is an HTTP GET. -/// -/// The handler is a CORE-owned axum closure whose ONLY new extractor is -/// [`axum::extract::ws::WebSocketUpgrade`] (named here and nowhere the default build compiles). It -/// assembles a substrate-owned [`busbar_kernel::ingress::duplex_ws::WsArrival`] from the SAME -/// sources `mount_plane_route` reads (minus the body — a WS GET carries none), carrying the upgrade -/// BY VALUE and the plane's live slot (resolved from `plane_slots` under the spec's `slot_key`, so an -/// unconfigured plane mounts nothing), and hands it to the plane's neutral accept fn — which reaches -/// `serve_gauntlet`/`accept_gauntlet` internally and never a bare `on_upgrade`. -#[cfg(feature = "duplex-ws")] -fn mount_ws_arrivals( - router: crate::core_routes::CoreRouter, - plane_slots: &std::collections::BTreeMap< - &'static str, - std::sync::Arc, - >, -) -> crate::core_routes::CoreRouter { - use busbar_contract::abi::mechanism::route::RouteMethod; - let mut router = router; - for spec in busbar_kernel::ingress::duplex_ws::take_ws_arrivals() { - let busbar_kernel::ingress::duplex_ws::WsArrivalSpec { - path, - auth, - slot_key, - accept, - } = spec; - // No live slot this generation ⇒ the plane is unconfigured ⇒ mount nothing, exactly as the - // non-WS route loop skips a plane with no slot. - let Some(slot) = plane_slots.get(slot_key) else { - continue; - }; - let slot = slot.clone(); - let ctx_path = path.clone(); - router = router.route(path, RouteMethod::Get, auth, { - move |upgrade: axum::extract::ws::WebSocketUpgrade, - axum::extract::State(handle): axum::extract::State< - std::sync::Arc, - >, - raw_params: axum::extract::RawPathParams, - uri: axum::http::Uri, - gov: Option< - axum::extract::Extension, - >, - principal: Option< - axum::extract::Extension, - >, - consumed: Option>, - mut headers: axum::http::HeaderMap| { - // As for a data route: the plane never sees the credential the gate consumed. - auth::ConsumedCredentials::strip_from(consumed.as_deref(), &mut headers); - let accept = accept.clone(); - let slot = slot.clone(); - let ctx_path = ctx_path.clone(); - let path_params: Vec<(String, String)> = raw_params - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); - async move { - let gov = gov.map(|axum::extract::Extension(g)| g); - let caller_principal = gov - .as_ref() - .and_then(|g| g.key.as_ref().map(|k| k.id.clone())); - // Mint the neutral host seam over the request's live engine snapshot, exactly as - // `mount_plane_route` does — the accept fn reaches host capabilities through it. - let host = crate::plane_host::engine_host_from_handle(&handle); - let arrival = busbar_kernel::ingress::duplex_ws::WsArrival { - upgrade, - gov, - principal: principal.map(|axum::extract::Extension(p)| p), - caller_principal, - path: ctx_path, - uri, - headers, - path_params, - host, - slot, - }; - // The accept fn resolves to the finished response: the 101-switching upgrade on - // admit, or a pre-upgrade refusal (operator-hook or gauntlet) on refuse — no socket - // bound, no task spawned. It is async so the plane runs its hooks before the upgrade. - accept(arrival).await - } - } - }); - } - router -} - /// Apply the shared middleware stack — auth chain, request-body cap, 413 reshaping, server-timing — /// and bind the swappable `AppHandle` state. Identical for the single-listener router and each /// split-plane router, so both planes get the SAME auth + limit posture and both see config-apply diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index 9ef62ffee2..986e281400 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -389,7 +389,7 @@ plane-streaming = "busbar-plane-streaming" # What each linked feature's crate registers (keyed by the feature, as the linked row is), axis by axis: `plane` (PLANE_DECLARATION + PLANE_HOOKS, joined # kernel-side), `protocols`, `path-ingress`, `body-ingress`, `protocol-seams`, `diagnostics`, -# `ws-arrivals`, `on-host`, `compose`, `stdio-serve`, `exports` — each an item of the crate's entry +# `on-host`, `compose`, `stdio-serve`, `exports` — each an item of the crate's entry # module — and the root-bound seams it drives: `egress`, `plane-sections`, `admin-envelope`. # `gauntlet-one-shot` / `gauntlet-session`: the plane rides the unified kernel loop (#28) — # `gauntlet_install::install()` flips its `PLANE_DECLARATION.key` onto the one-shot or session runner. diff --git a/crates/busbar/src/linked_gen.rs b/crates/busbar/src/linked_gen.rs index 28dd50ca81..8cf3b53110 100644 --- a/crates/busbar/src/linked_gen.rs +++ b/crates/busbar/src/linked_gen.rs @@ -38,7 +38,6 @@ pub(crate) const AXES: &[(&str, &str, &str)] = &[ ("body-ingress", "body_ingress", "BODY_INGRESS"), ("protocol-seams", "protocol_seams", "install_protocol_seams"), ("diagnostics", "diagnostics", "DIAGNOSTICS"), - ("ws-arrivals", "ws_arrivals", "install_ws_arrivals"), ("on-host", "on_host", "on_host"), ("compose", "compose", "compose"), ("stdio-serve", "stdio_serve", "stdio_serve"), diff --git a/crates/busbar/src/main.rs b/crates/busbar/src/main.rs index 2a16f668bc..9664b99c35 100644 --- a/crates/busbar/src/main.rs +++ b/crates/busbar/src/main.rs @@ -10,7 +10,7 @@ // linked crates' own docs and the README for what a running deployment answers on the wire and the // `--help` output above for the CLI surface this binary exposes. // -// See `register_protocols`/`register_planes`/`register_diagnostics`/`register_ws_arrivals` below +// See `register_protocols`/`register_planes`/`register_diagnostics` below // for the composition root's one write into each axis: each linked crate contributes its own // declarations here, under its own Cargo feature, and nowhere else. @@ -320,14 +320,6 @@ fn register_diagnostics() { root::linked::register_diagnostics(&LINKED); } -/// REGISTER THE LINKED DUPLEX PLANES' INBOUND WS-ACCEPT ARRIVALS — the composition root's one write -/// into the neutral WS-accept registry. Installed BEFORE the router is built (in `run()`), so -/// `take_ws_arrivals` drains a populated set; a build with no duplex plane installs nothing and the -/// router mounts no WS-accept route. -fn register_ws_arrivals() { - root::linked::register_ws_arrivals(&LINKED); -} - /// The data-plane worker count and its warnings, resolved once at the top of `main()`. static WORKERS: std::sync::LazyLock<(usize, Vec)> = std::sync::LazyLock::new(resolve_worker_threads); @@ -362,12 +354,6 @@ fn main() { // any reader. Each linked entry contributes its owned diagnostics; a no-planes build installs // nothing and the catalog is the neutral built-ins alone. register_diagnostics(); - // INBOUND WS-ACCEPT ARRIVAL REGISTRATION, same slot and the same reason as the axes above: the - // core router drains the installed arrivals at build (`take_ws_arrivals`), which happens later in - // `run()` — so the duplex planes' arrivals must be installed here, before the router is built. Each - // duplex entry installs its `WsArrivalSpec`s; a build with no duplex plane installs nothing and - // the router mounts no WS-accept route — strong-form deletable. - register_ws_arrivals(); // THE COMPOSITION ROOT'S OWN SEAL is NOT here, and it is the one boot step that is not: it // composes the transports a switched-over plane would serve through, and the http one carries // the operator's `limits.request_body_max_bytes`, so it cannot run before the configuration it diff --git a/crates/busbar/src/root/linked.rs b/crates/busbar/src/root/linked.rs index b048c24752..751247f98b 100644 --- a/crates/busbar/src/root/linked.rs +++ b/crates/busbar/src/root/linked.rs @@ -87,9 +87,6 @@ pub struct Linked { pub protocol_seams: &'static [fn()], /// Owned diagnostics, joining the rendered catalog. pub diagnostics: &'static [&'static [&'static busbar_contract::diagnostic::Diagnostic]], - /// Installers of a duplex plane's inbound WS-accept arrivals (the seam is set once: the first - /// duplex entry in table order is the one installed). - pub ws_arrivals: &'static [fn()], /// Background work re-anchored on every generation's engine host (boot, then each swap). pub on_host: &'static [fn(&Arc)], /// Providers composed off the resolved configuration (see [`Compose`]). @@ -1253,14 +1250,6 @@ pub fn declared_diagnostics( Ok(declared) } -/// THE WS-ACCEPT AXIS: each duplex entry installs its inbound arrivals — and none does when no entry -/// is duplex, so the router mounts no WS-accept route. -pub fn register_ws_arrivals(linked: &Linked) { - for install in linked.ws_arrivals { - install(); - } -} - /// THE ROOT-BOUND SEAMS an enabled entry drives, each bound once and only when some entry drives it /// (the manifest's `egress` / `plane-sections` / `admin-envelope` axes, emitted by the build script as /// `linked_*` cfgs): the hostless-egress driver and the egress-trust host, the parse-time section diff --git a/crates/busbar/src/root/tests/linked.rs b/crates/busbar/src/root/tests/linked.rs index 968b1df706..28f4988320 100644 --- a/crates/busbar/src/root/tests/linked.rs +++ b/crates/busbar/src/root/tests/linked.rs @@ -51,7 +51,6 @@ pub(super) fn linked( body_ingress: &[], protocol_seams: &[], diagnostics: &[], - ws_arrivals: &[], on_host: &[], compose: &[], stdio_serve: &[], diff --git a/qa/construction.toml b/qa/construction.toml index 996c54546e..88b23de53c 100644 --- a/qa/construction.toml +++ b/qa/construction.toml @@ -1969,7 +1969,9 @@ because = "a type alias over the rows the loop posted, and a reader of it; the n # silent as deleting the table alone was. [gate.census] legacy_reach_prefixes = 4 -plane_crates = 4 +# 4 -> 3: crates/busbar-voice deleted by the P3 streaming flip, named in [gate.census.deleted] (the +# plane_crates floor drops only by ledgered deletions, ARCHITECT 2026-10-05). +plane_crates = 3 # HOW MANY CRATE DIRECTORIES EACH KIND GLOB MATCHES. # @@ -2124,3 +2126,4 @@ abi = 1 # an unlisted vanished dir stays RED. [gate.census.deleted] "crates/hook-test-plugin" = "TODO P5: delete hook-test-plugin (ARCHITECT 2026-10-04)" +"crates/busbar-voice" = "P3 streaming flip: legacy crate deleted (ARCHITECT 2026-10-04)" diff --git a/scripts/plane-config-noun-gate.sh b/scripts/plane-config-noun-gate.sh index c788d84834..2c5a8389f3 100755 --- a/scripts/plane-config-noun-gate.sh +++ b/scripts/plane-config-noun-gate.sh @@ -117,6 +117,13 @@ hdr() { printf '\n== %s ==\n' "$*"; } # reached through the surviving root rather than through a path that stopped existing. CORE_ROOTS="crates/busbar-kernel/src" +# SECTION NOUNS KEPT BY NAME (ARCHITECT 2026-10-05, Q7): `streams` is the 1.5.5 customer config key +# of the streaming plane, whose legacy crate (`busbar-voice`, the one that declared it in a PlaneDecl) +# the P3 flip deleted. The plane is a door now (`busbar-plane-streaming`) and declares no PlaneDecl, +# so the noun cannot be read off one; it is kept here by name rather than dropped from the scan. The +# floor below counts it. +NAMED_NOUNS="streams" + # Resolve the four section nouns from each plane crate's DECLARED PlaneDecl.config_section — never a # restated literal. ` -> crates/busbar-/src`; read the `config_section: "",` line. # @@ -154,6 +161,7 @@ section_nouns() { fi [ -n "$noun" ] && out="${out:+$out }$noun" done + for noun in $NAMED_NOUNS; do out="${out:+$out }$noun"; done printf '%s' "$out" } @@ -214,7 +222,7 @@ require_nouns() { # shellcheck disable=SC2086 # the split is the count; `set -f` makes it glob-safe set -f; set -- $nouns; got=$#; set +f # shellcheck disable=SC2086 # same - set -f; set -- $PLANE_KEYS; want=$#; set +f + set -f; set -- $PLANE_KEYS $NAMED_NOUNS; want=$#; set +f [ "$got" -eq "$want" ] && return 0 red "plane-config-noun gate: FAIL — resolved $got section noun(s) for $want plane key(s): [$nouns]" note "Each plane declares its own noun via PlaneDecl.config_section; a plane that resolves to nothing" diff --git a/xtask/src/gates/construction/census.rs b/xtask/src/gates/construction/census.rs index fc614f9bec..eb09989a9c 100644 --- a/xtask/src/gates/construction/census.rs +++ b/xtask/src/gates/construction/census.rs @@ -171,7 +171,16 @@ pub fn ceiling_census(cx: &Ctx, cfg: &Cfg) -> Vec { bad.extend(deleted_ledger_findings(&now_deleted, &base_deleted, |d| { cx.abs(d).exists() })); - let moved = moved_out_by_kind(cx, cfg, &doc, &base.sha, &now_deleted); + let mut moved = moved_out_by_kind(cx, cfg, &doc, &base.sha, &now_deleted); + let base_planes = doc + .table("gate") + .map(|g| g.list_of("plane_crates")) + .unwrap_or_default(); + if let Some(n) = deleted_plane_crates(&base_planes, &plane_crates, |dir| { + now_deleted.contains(dir) && !cx.abs(dir).exists() + }) { + moved.insert("plane_crates".to_string(), n as i64); + } bad.extend(lowered_floors(&cfg.doc, &doc, base.short(), &moved)); } } @@ -223,7 +232,9 @@ pub fn ceiling_census(cx: &Ctx, cfg: &Cfg) -> Vec { /// /// THE ONE ACCEPTED WAY DOWN (ARCHITECT 2026-10-02, TODO PATH TO DEV-GREEN P5): a /// `plugin_kinds.` floor may drop by at most `moved[]`, the number of that kind's crate -/// directories that left the tree as MOVED-OUT plugins or ruled deletions ([`excused_out`]). Every other drop is RED. +/// directories that left the tree as MOVED-OUT plugins or ruled deletions ([`excused_out`]), and the +/// `plane_crates` floor by the legacy plane crates deleted under the ledger +/// ([`deleted_plane_crates`]). Every other drop is RED. fn lowered_floors( now_doc: &Document, base_doc: &Document, @@ -312,6 +323,24 @@ fn excused_out( .then_some(gone.len()) } +/// The `gate.plane_crates` entries the base listed and this tree does not, when EVERY one is a +/// crate deleted under a ruled deletion (`is_deleted(crates/)`: named in +/// `[gate.census.deleted]` and absent from the tree; ARCHITECT 2026-10-05, the P3 flips delete the +/// legacy plane crates). `None` when any one is not (an unlisted vanished plane crate stays RED), and +/// when none left. +fn deleted_plane_crates( + base: &[String], + now: &[String], + is_deleted: impl Fn(&str) -> bool, +) -> Option { + let gone: Vec = base + .iter() + .filter(|c| !now.contains(c)) + .map(|c| format!("crates/{c}")) + .collect(); + excused_out(&gone, |_| false, is_deleted) +} + /// A plugin crate MOVED OUT (TODO PATH TO DEV-GREEN P5: filter-repo into its own repo, pinned back /// in busbar as one git dependency at an exact commit). `Some(gone.len())` when every directory in /// `gone` is a crate whose package (`package_at_base`) the root manifest now pins as a git diff --git a/xtask/src/gates/construction/census_moveout_tests.rs b/xtask/src/gates/construction/census_moveout_tests.rs index 484f69e176..c112de6bb5 100644 --- a/xtask/src/gates/construction/census_moveout_tests.rs +++ b/xtask/src/gates/construction/census_moveout_tests.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; -use super::{glob_matches, lowered_floors, moved_out}; +use super::{deleted_plane_crates, glob_matches, lowered_floors, moved_out}; const ROOT: &str = "[workspace.dependencies]\n\ busbar-x-moved = { git = \"https://example.invalid/busbar-x-moved\", rev = \"0123abc\" }\n\ @@ -95,3 +95,41 @@ fn a_kind_glob_matches_one_directory_level() { )); assert!(!glob_matches("crates/store-*", "crates/busbar-store-x")); } + +/// The `plane_crates` floor's way down (ARCHITECT 2026-10-05): a legacy plane crate the base listed +/// and this tree does not counts only when it was deleted under `[gate.census.deleted]`. +#[test] +fn a_plane_crate_deleted_under_the_ledger_excuses_its_floor_drop() { + let base: Vec = ["busbar-llm", "busbar-voice"].map(String::from).to_vec(); + let now: Vec = vec!["busbar-llm".into()]; + assert_eq!( + deleted_plane_crates(&base, &now, |d| d == "crates/busbar-voice"), + Some(1) + ); + let mut moved = BTreeMap::new(); + moved.insert("plane_crates".to_string(), 1); + assert!(lowered_floors( + &doc("[gate.census]\nplane_crates = 3\n"), + &doc("[gate.census]\nplane_crates = 4\n"), + "base", + &moved + ) + .is_empty()); +} + +/// RED: a plane crate that left the list without a ledger row excuses nothing, and neither does a +/// list that lost nothing. +#[test] +fn an_unlisted_plane_crate_drop_stays_red() { + let base: Vec = ["busbar-llm", "busbar-voice"].map(String::from).to_vec(); + let now: Vec = vec!["busbar-llm".into()]; + assert_eq!(deleted_plane_crates(&base, &now, |_| false), None); + assert_eq!(deleted_plane_crates(&base, &base, |_| true), None); + assert!(!lowered_floors( + &doc("[gate.census]\nplane_crates = 3\n"), + &doc("[gate.census]\nplane_crates = 4\n"), + "base", + &BTreeMap::new() + ) + .is_empty()); +} diff --git a/xtask/src/gates/reachability.rs b/xtask/src/gates/reachability.rs index 591844a61e..2ab92ae545 100644 --- a/xtask/src/gates/reachability.rs +++ b/xtask/src/gates/reachability.rs @@ -1752,7 +1752,6 @@ const AXIS_ITEMS: &[(&str, &[&str])] = &[ ("body-ingress", &["BODY_INGRESS"]), ("protocol-seams", &["install_protocol_seams"]), ("diagnostics", &["DIAGNOSTICS"]), - ("ws-arrivals", &["install_ws_arrivals"]), ("on-host", &["on_host"]), ("compose", &["compose"]), ("stdio-serve", &["stdio_serve"]), From 1b51c7ad9aef56f60c2c7b55ac48b513923e7df0 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:12:08 -0700 Subject: [PATCH 44/82] =?UTF-8?q?FLIP-STREAMING:=20Q5=20=E2=80=94=20a=20pl?= =?UTF-8?q?ane=20door's=20Statement=20DECLARING=20section=20feeds=20the=20?= =?UTF-8?q?config-schema=20fingerprint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ARCHITECT 2026-10-05: the streaming door's declared settings feed the snapshot the way a PlaneDecl does. The declared-section reader now also reads a door's `Section { name: abi_str(…), flags: SECTION_DECLARING }` row as a declaring section the plane owns (DECL-FOLD lifts it into the declared-section map carrier), so `streams:` is fingerprinted from busbar-plane-streaming's door as it was from busbar-voice's PlaneDecl (config_section "streams", owned ["streams"]). A row that only CONSUMES a section declares nothing. Self-test: a door row declaring a section no struct carries is REFUSED (RED), and a CONSUMED row is not read as declared (green). --- xtask/src/gates/config_schema/declared.rs | 34 +++++++++++++++++++++++ xtask/src/gates/config_schema/selftest.rs | 29 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/xtask/src/gates/config_schema/declared.rs b/xtask/src/gates/config_schema/declared.rs index 5c1f68526f..f7213d25f4 100644 --- a/xtask/src/gates/config_schema/declared.rs +++ b/xtask/src/gates/config_schema/declared.rs @@ -27,6 +27,12 @@ use crate::ctx::{Change, Ctx, Overlay, WalkSpec}; const ROOT: &str = "crates"; /// The registration item each plane crate exports (the K0 contract shape). const ITEM: &str = "const PLANE_DECLARATION"; +/// A plane DOOR's Statement section row (`Section { name: abi_str(…), flags: SECTION_DECLARING, … }`): +/// the section a door plane declares and owns the grammar of, as its memory-ABI Statement states it +/// (ARCHITECT 2026-10-05: a door's declared settings feed the snapshot the way a PlaneDecl does). +const DOOR_SECTION: &str = "Section {"; +/// The flag that makes a door's section row its DECLARING section. +const DOOR_DECLARING: &str = "SECTION_DECLARING"; /// The kernel's reserved list: sections core still declares as concrete `DeployCfg` fields. const CORE_OWNED: &str = "CORE_OWNED_CONCRETE_SECTIONS"; @@ -95,6 +101,34 @@ fn facts(path: &str, raw: &str) -> Facts { } } } + // A DOOR PLANE's declaring section, read off its Statement's section rows: a door plane owns the + // grammar of the section it declares (the kernel folds it into the declared-section map carrier, + // DECL-FOLD), exactly as a PlaneDecl whose `owned_config_sections` names its `config_section`. + let mut from = 0; + while let Some(at) = text[from..].find(DOOR_SECTION) { + let start = from + at; + from = start + DOOR_SECTION.len(); + let whole_word = !text[..start] + .chars() + .next_back() + .is_some_and(|c| c.is_ascii_alphanumeric() || c == '_'); + let Some(body) = block(&text, start).filter(|_| whole_word) else { + continue; + }; + if !field(body, "flags").is_some_and(|f| f.split('|').any(|x| x.trim() == DOOR_DECLARING)) { + continue; + } + let Some(name) = field(body, "name").map(str::trim) else { + continue; + }; + let name = name + .strip_prefix("abi_str(") + .and_then(|n| n.strip_suffix(')')) + .unwrap_or(name) + .trim() + .to_string(); + out.decls.push(Ok((name.clone(), vec![name]))); + } let mut from = 0; while let Some(at) = text[from..].find(ITEM) { let start = from + at + ITEM.len(); diff --git a/xtask/src/gates/config_schema/selftest.rs b/xtask/src/gates/config_schema/selftest.rs index bf64bfb460..55038a266d 100644 --- a/xtask/src/gates/config_schema/selftest.rs +++ b/xtask/src/gates/config_schema/selftest.rs @@ -450,6 +450,35 @@ pub fn run<'a>(gate: &'a dyn Gate, cx: &'a Ctx) -> Report<'a> { &["no struct in the tracked"], )); + // A DOOR plane's Statement declares its section in a `Section` row flagged SECTION_DECLARING + // (no PlaneDecl), and the reader reads it there: a door row naming a section no struct carries is + // an orphan exactly as a PlaneDecl's is. A row that only CONSUMES a section declares nothing. + let door_row = |flags: &str| { + format!( + "const ZZ_SECTION: &str = \"zz_door_no_such_carrier\";\nconst SECTIONS: &[Section] = \ + &[Section {{\n name: abi_str(ZZ_SECTION),\n flags: {flags},\n _reserved: 0,\n}}];\n" + ) + }; + let mut ov = Overlay::new(); + ov.set(plane_fixture, door_row("SECTION_DECLARING")); + report.push(prove_rows_red( + cx, + gate, + "a section a plane DOOR declares that no struct carries is REFUSED", + &[ROW_TRACKED_SOURCES], + ov, + &["no struct in the tracked"], + )); + let mut ov = Overlay::new(); + ov.set(plane_fixture, door_row("SECTION_CONSUMED")); + report.push(prove_rows_green( + cx, + gate, + "a section a plane door only CONSUMES is not read as declared", + &[ROW_TRACKED_SOURCES], + ov, + )); + // A declared section this reader cannot resolve to a string is a section it cannot freeze. let mut ov = Overlay::new(); ov.set(plane_fixture, declaration("ZZ_NO_SUCH_CONST", "")); From 286f30824d45707ff4fe4a0d8571295cb7725a2e Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:19:37 -0700 Subject: [PATCH 45/82] =?UTF-8?q?FLIP-STREAMING:=20Q1=20=E2=80=94=20the=20?= =?UTF-8?q?root-voice=20leg=20is=20the=20streaming=20door=20served=20end?= =?UTF-8?q?=20to=20end=20(root/tests/serve=5Fdoor.rs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ARCHITECT 2026-10-05 Q1: streaming-door cells in crates/busbar/src/root/tests/serve_door.rs, and a leg file may be a `#[path]` test file of a declared root module. - serve_door.rs (carried by serve.rs, now for plane-decisions OR plane-streaming): a `streaming()` harness composes the linked streaming door as production seals it — needs on the connector over the linked transport doors, the DIRECT route to the catalog model streams.session.model names, the provider's credential bound by the linked auth plugin, money on a signing governance book and the node's book — and mounts its claims on the data router. Two cells: a_streaming_mint_is_served_through_the_door_under_the_providers_credential (arrival, authenticate, verify, route, meter, audit, exit, egress-auth, governance-budget; RED arm: the key's plain token, bound to no audience, is refused before any dial or charge) and a_spent_key_is_refused_at_the_streaming_door_before_any_dial (admit: 429, never dialled, nothing charged, no line). The decisions cells are unchanged, gated on plane-decisions. - capability_equality.rs: leg_module_declared accepts `pub mod ;` OR a declared module carrying `#[path = "tests/.rs"]`; RED: selftest_a_leg_file_is_a_module_or_a_declared_modules_path_test_file refuses an undeclared file. - qa/capability-equality.json and qa/teller-steps.json: root-voice's file is serve_door.rs; the cells the two door cells prove cite them, every other voice root cell is `none` with its argument. - gauntlet_kernel.rs: the six session-witness cells only busbar-voice's served-witness table answered (session open/relay/attribution/meter/metrics, breaker fast-fail) are deleted with that table: no linked plane rides the session rider, so they asked nothing of anyone. --- crates/busbar/src/root/serve.rs | 6 +- .../busbar/src/root/tests/gauntlet_kernel.rs | 61 --- crates/busbar/src/root/tests/serve_door.rs | 393 +++++++++++++++++- crates/busbar/tests/capability_equality.rs | 83 +++- qa/capability-equality.json | 42 +- qa/teller-steps.json | 44 +- 6 files changed, 506 insertions(+), 123 deletions(-) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index e7dbf77534..b01c7afe3e 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2677,7 +2677,11 @@ mod tests; #[path = "tests/serve_planes.rs"] mod planes_tests; -#[cfg(all(test, feature = "plane-decisions", linked_axis_node))] +#[cfg(all( + test, + any(feature = "plane-decisions", feature = "plane-streaming"), + linked_axis_node +))] #[path = "tests/serve_door.rs"] mod door_tests; diff --git a/crates/busbar/src/root/tests/gauntlet_kernel.rs b/crates/busbar/src/root/tests/gauntlet_kernel.rs index b44cb8a873..77a06fabbd 100644 --- a/crates/busbar/src/root/tests/gauntlet_kernel.rs +++ b/crates/busbar/src/root/tests/gauntlet_kernel.rs @@ -691,67 +691,6 @@ async fn served_rider_presents_only_the_planned_credential() { .await; } -/// VERIFY, on a session's served leg: the destination the door judged is the one the session runs -/// under. -#[tokio::test] -async fn served_rider_opens_the_session_on_the_destination_it_judged() { - run_served( - "session-verify", - "served_rider_opens_the_session_on_the_destination_it_judged", - ) - .await; -} - -/// ROUTE, on a session's served leg: the session the door opened relays both directions. -#[tokio::test] -async fn served_rider_opens_a_session_that_relays_both_ways() { - run_served( - "session-route", - "served_rider_opens_a_session_that_relays_both_ways", - ) - .await; -} - -/// AUTHENTICATE, on a session's served leg: the session answers for the key the door resolved. -#[tokio::test] -async fn served_rider_attributes_the_session_to_the_resolved_key() { - run_served( - "session-authenticate", - "served_rider_attributes_the_session_to_the_resolved_key", - ) - .await; -} - -/// METER, on a session's served leg: each turn is ledgered per class the plane declares. -#[tokio::test] -async fn served_rider_meters_each_turn_per_declared_class() { - run_served( - "session-meter", - "served_rider_meters_each_turn_per_declared_class", - ) - .await; -} - -/// METRICS, on a session's served leg: each session the door opens is reported under its labels. -#[tokio::test] -async fn served_rider_reports_each_session_it_opens() { - run_served( - "session-metrics", - "served_rider_reports_each_session_it_opens", - ) - .await; -} - -/// BREAKER-FASTFAIL, on the served leg: a tripped upstream cell refuses before any socket opens. -#[tokio::test] -async fn served_rider_fast_fails_a_tripped_upstream() { - run_served( - "breaker-fastfail", - "served_rider_fast_fails_a_tripped_upstream", - ) - .await; -} - /// CATALOGUE, on the served leg: what the caller may not see is not served. #[tokio::test] async fn served_rider_serves_only_the_callers_catalogue() { diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index 8d5ae0738b..d0495fd492 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -13,6 +13,12 @@ //! loader's one load, its need declared on the connector, as its door row binds it. The dropped-in //! fold of the same door is the plane crate's own conformance suite (`tests/conformance.rs`, one //! transcript through both loads); this harness builds no cdylib of it. +//! +//! THE STREAMING DOOR, the same way (the `root-voice` leg of qa/capability-equality.json, ARCHITECT +//! 2026-10-05 Q1): the browser's ephemeral-secret mint, claimed by the streaming plane's door, served +//! end to end — an audience-bound caller, the door's DIRECT route to the top-level catalog model +//! `streams.session.model` names, the provider's own credential presented by the auth plugin, the +//! caller's spend posted against the key that presented it. use std::collections::BTreeMap; use std::sync::Arc; @@ -22,8 +28,11 @@ use busbar_contract::caps::ReasonCode; use busbar_contract::conn::{DeclaredConns, PollConns}; use busbar_kernel::cost::CostModel; use busbar_kernel::governance::signing::{TokenSigner, DEFAULT_KID}; -use busbar_kernel::governance::{GovState, MemoryStore, NewKeySpec, PLANE_LANE_SEP}; +#[cfg(feature = "plane-decisions")] +use busbar_kernel::governance::PLANE_LANE_SEP; +use busbar_kernel::governance::{GovState, MemoryStore, NewKeySpec}; use busbar_kernel::plane_driver::{refusal_status, EndPost, PlaneMoney}; +#[cfg(feature = "plane-decisions")] use busbar_plane_decisions::plane_door::door as decisions_door; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -53,25 +62,39 @@ static CARD: std::sync::LazyLock = }); /// The decisions plane's one claim, with one model configured. +#[cfg(feature = "plane-decisions")] const CLAIMED: &str = "/v1/systemone"; /// The provider's credential, as its file holds it. const CREDENTIAL: &str = "sk-door-test"; /// The far end's answer: a decision, and the one unit it reports using. +#[cfg(feature = "plane-decisions")] const ANSWER: &str = r#"{"id":"d-1","decision":"approve","usage":{"units":1}}"#; /// A POST of the caller's decision state to `path` on `router`, with `token` as its bearer or with /// none: the response. +#[cfg(feature = "plane-decisions")] async fn send(router: &axum::Router, path: &str, token: Option<&str>) -> axum::response::Response { + send_body(router, path, token, r#"{"state":{"amount":7}}"#).await +} + +/// A POST of `body` (JSON) to `path` on `router`, with `token` as its bearer or with none. +async fn send_body( + router: &axum::Router, + path: &str, + token: Option<&str>, + body: &'static str, +) -> axum::response::Response { use tower::ServiceExt as _; - let mut req = axum::http::Request::builder().method("POST").uri(path); + let mut req = axum::http::Request::builder() + .method("POST") + .uri(path) + .header("content-type", "application/json"); if let Some(token) = token { req = req.header("authorization", format!("Bearer {token}")); } - let req = req - .body(axum::body::Body::from(r#"{"state":{"amount":7}}"#)) - .expect("a request"); + let req = req.body(axum::body::Body::from(body)).expect("a request"); router .clone() .oneshot(req) @@ -81,7 +104,16 @@ async fn send(router: &axum::Router, path: &str, token: Option<&str>) -> axum::r /// A far end on loopback answering every request with [`ANSWER`]; what it was sent comes back on /// the channel, one request head per connection. +#[cfg(feature = "plane-decisions")] async fn far_end() -> (u16, tokio::sync::mpsc::UnboundedReceiver) { + far_end_answering(ANSWER).await +} + +/// A far end on loopback answering every request 200 with `answer` (JSON); what it was sent comes +/// back on the channel, one request (head and body) per connection. +async fn far_end_answering( + answer: &'static str, +) -> (u16, tokio::sync::mpsc::UnboundedReceiver) { let listener = tokio::net::TcpListener::bind("127.0.0.1:0") .await .expect("a loopback port"); @@ -121,8 +153,8 @@ async fn far_end() -> (u16, tokio::sync::mpsc::UnboundedReceiver) { } let reply = format!( "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\ - connection: close\r\n\r\n{ANSWER}", - ANSWER.len() + connection: close\r\n\r\n{answer}", + answer.len() ); let _ = socket.write_all(reply.as_bytes()).await; let _ = socket.shutdown().await; @@ -134,6 +166,7 @@ async fn far_end() -> (u16, tokio::sync::mpsc::UnboundedReceiver) { /// THE EXIT TEST: a keyed caller's claimed request is SERVED through the decisions door, 200, the /// far end's answer relayed as it came, and the unit's money posted on both books. +#[cfg(feature = "plane-decisions")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { let _one = PUBLISHING.lock().await; @@ -344,6 +377,7 @@ async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { /// THE MOUNT: the door's claim is a route on the data router only when the router is built with /// it; a path the door does not claim is the router's own. An unkeyed caller on the door's claim (it /// takes a credential) is refused before anything is charged, rendered by the plane. +#[cfg(feature = "plane-decisions")] #[tokio::test] async fn the_data_router_built_with_the_door_serves_only_its_claims() { let _one = PUBLISHING.lock().await; @@ -421,6 +455,7 @@ async fn the_data_router_built_with_the_door_serves_only_its_claims() { /// path over two carriers (an endpoint answered as a document or as an event stream) is one route /// on the data listener, the first in its claim order; the data router builds with it. RED: each /// claim mounted its own route, and the router refused the second as an overlapping method route. +#[cfg(feature = "plane-decisions")] #[tokio::test] async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { let _one = PUBLISHING.lock().await; @@ -485,3 +520,347 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { refused ); } + +/// The streaming door's mint claim. +#[cfg(feature = "plane-streaming")] +const MINT: &str = "/v1/realtime/client_secrets"; + +/// The provider's answer to the mint: an ephemeral secret and its expiry. +#[cfg(feature = "plane-streaming")] +const MINTED: &str = r#"{"value":"ek_door_0001","expires_at":1767225600}"#; + +/// The deployment's public base URL the streaming door fronts. +#[cfg(feature = "plane-streaming")] +const PUBLIC: &str = "http://gw.test"; + +/// The streaming door, served as production composes it, and what each `root-voice` cell reads. +#[cfg(feature = "plane-streaming")] +struct Streaming { + _published: Published, + router: axum::Router, + heard: tokio::sync::mpsc::UnboundedReceiver, + gov: Arc, + app: Arc, + key: busbar_contract::records::VirtualKey, + /// The key's plain data-plane token. + plain: String, + /// The key's token bound to the door's audience, as the browser-facing mint is reached with. + bound: String, + money: Arc, + post: Arc, + book: crate::root::durability::NodeBook, +} + +/// [`Streaming`]: the linked streaming door bound through the loader's one load, its needs declared +/// on the connector over every linked transport door; a signing governance book with one key (in a +/// group whose all-time budget is `budget`, the door's per-request fee one, or no group and no fee); +/// an OpenAI-protocol provider on a loopback far end answering the mint, reached through the catalog +/// model `streams.session.model` names; the door composed under [`PUBLIC`] with its egress sealed by +/// the composition itself, and its claims mounted on the data router. +#[cfg(feature = "plane-streaming")] +async fn streaming(instance: &'static str, budget: Option) -> Streaming { + use busbar_kernel::governance::signing::TokenVerifier; + use busbar_plane_streaming::door::{door as streaming_door, NAME as PLANE}; + + let published = Published(instance); + let (port, heard) = far_end_answering(MINTED).await; + let judge = crate::root::connector::guard_for(&busbar_kernel::config::Destinations { + block_private_addresses: false, + ..Default::default() + }) + .expect("the guard"); + let connector = busbar_core_connector::process::build( + || { + crate::root::connector::entries( + crate::LINKED_TRANSPORT_DOORS, + &busbar_contract::transport::TransportSettings::default(), + ) + }, + judge, + &[], + Arc::new(|_| {}), + busbar_core_connector::pool::PoolPosture::NONE, + ) + .expect("the connector builds"); + let dispatcher = Arc::new(Dispatcher::new(DispatchConfig::default())); + let row = LinkedRow::of(streaming_door).expect("the door states its Statement"); + let plane = load_linked::( + &row, + Bind { + instance: Arc::from(instance), + max_inflight_cap: 64, + sink: Arc::new(NoSink), + dispatcher: dispatcher.adopter(), + conns: Some(Arc::clone(&connector) as Arc), + }, + ) + .expect("the linked door binds"); + let section_key = plane.served().section; + + let groups: BTreeMap = budget + .map(|amount| { + let limit = busbar_kernel::config::groups::LimitCfg { + metric: busbar_kernel::config::groups::LimitMetric::Budget, + amount, + per: Some(busbar_kernel::config::groups::LimitWindow::Total), + scope: None, + on_exhaust: None, + downgrade_to: None, + admission: None, + on_exhaustion: None, + }; + let cfg = busbar_kernel::config::GroupCfg { + parent: None, + enabled: true, + limits: vec![limit], + ..Default::default() + }; + (format!("{instance}-group"), cfg) + }) + .into_iter() + .collect(); + let cost = match budget { + None => CostModel::flat(1), + Some(_) => { + let fees: busbar_kernel::config::PlaneFeesMap = [( + PLANE.to_string(), + busbar_kernel_ledger::cost::PlaneFees { + per_request: 1, + per_session: 0, + }, + )] + .into_iter() + .collect(); + CostModel::resolve_parts(None, 0, &groups).with_plane_fees(&fees) + } + }; + let signer = TokenSigner::from_secret_bytes(&[7u8; 32], DEFAULT_KID); + let verifier = TokenVerifier::single(signer.kid(), signer.verifying_key()); + let gov = Arc::new( + GovState::new_with_signer( + Arc::new(MemoryStore::new()), + None, + Some(TokenSigner::from_secret_bytes(&[7u8; 32], DEFAULT_KID)), + ) + .expect("governance"), + ); + let (key, plain) = gov + .mint_signed( + NewKeySpec { + name: "browser".to_string(), + group: groups.keys().next().cloned(), + ..Default::default() + }, + 4_000_000_000, + 1_700_000_000, + ) + .expect("mint"); + let generation = verifier + .verify(plain.expose_secret(), 1_700_000_000, None) + .expect("the plain token's claims") + .generation; + let bound = signer.mint_for_audience( + &key.id, + 4_000_000_000, + generation.as_deref(), + &format!("{PUBLIC}/v1/realtime"), + None, + ); + gov.hydrate_budgets(&cost, 0).expect("hydrate"); + let node = Arc::new(Node::new()); + let book = crate::root::durability::node_book_over(Box::new(|| CARD.pin())); + node.bind_book(Arc::clone(&book.durability)); + let post = Arc::new(NodeEndPost::new(Arc::clone(&node))); + let money = Arc::new(PlaneMoney::new( + Arc::clone(&gov), + Arc::clone(&post) as Arc, + )); + let one = Arc::clone(&money); + + let key_file = std::env::temp_dir().join(format!( + "busbar-serve-door-{instance}-{}", + std::process::id() + )); + std::fs::write(&key_file, CREDENTIAL).expect("the credential file"); + let provider: busbar_kernel::config::ProviderCfg = serde_yaml::from_str(&format!( + "{{protocol: openai, base_url: 'http://127.0.0.1:{port}', api_key: {{file: '{}'}}, error_map: {{}}}}", + key_file.display() + )) + .expect("a provider entry"); + let providers = provider_routes(&std::collections::HashMap::from([( + "p".to_string(), + provider, + )])); + let catalog: std::collections::HashMap = + serde_yaml::from_str("{m-cap: {provider: p}}").expect("the models catalog"); + let secrets = busbar_kernel::config::secret::SecretResolver::builtins_only(); + let auths = OutboundAuths::new( + Arc::clone(&dispatcher), + crate::LINKED.auths, + None, + Some(Arc::clone(&connector) as Arc), + ); + let reach = DoorReach { + providers: &providers, + secrets: &secrets, + auths: Arc::new(auths), + conns: Arc::clone(&connector) as Arc, + stream_ceiling_secs: 600, + catalog: Some(&catalog), + upgrades: Vec::new(), + }; + let mut sections = BTreeMap::new(); + sections.insert( + section_key, + serde_yaml::from_str("session: {model: m-cap}").expect("a section"), + ); + let mut served = compose_planes( + &[(instance.to_string(), plane)], + &dispatcher, + &composed_services(), + §ions, + Some(PUBLIC), + &move || Arc::clone(&one), + Some(&DoorEgress { + reach: &reach, + journal: Arc::clone(&post) as Arc, + }), + None, + ) + .expect("the streaming door composes, its egress sealed"); + let _ = std::fs::remove_file(&key_file); + assert!( + served.planes[0].live.current().egress.is_some(), + "the composition sealed its egress" + ); + served.post = Some(Arc::clone(&post)); + let app = busbar_kernel::test_support::TestApp::new().keys_chain(); + let app = groups + .iter() + .fold(app, |app, (name, cfg)| app.group(name, cfg.clone())); + let app = app.governance(Arc::clone(&gov)).cost(cost).build(); + let doors = door_routes(served, || CARD.pin(), &[], &[]).expect("its claims mount"); + let (router, _admin, _handle) = + busbar_kernel::build_split_routers_serving(Arc::clone(&app), doors, 1 << 20, 0, false); + Streaming { + _published: published, + router, + heard, + gov, + app, + key, + plain: plain.expose_secret().clone(), + bound, + money, + post, + book, + } +} + +#[cfg(feature = "plane-streaming")] +impl Streaming { + /// The requests the governance book admitted for the key, this window. + fn requests(&self) -> u64 { + self.gov + .usage_for(&self.app.cost, &self.key.id, busbar_kernel::store::now()) + .expect("a read") + .expect("the key exists") + .requests + } + + /// The node book's lines for the streaming door's catalog route. + fn lines(&self) -> usize { + let rows = self.book.durability.lock().expect("unpoisoned").read_back(); + rows.iter() + .filter(|p| p.counts.as_ref().is_some_and(|c| c.lane.ends_with("m-cap"))) + .count() + } +} + +/// THE STREAMING DOOR'S MINT, SERVED (the `root-voice` leg, ARCHITECT 2026-10-05 Q1): an +/// audience-bound keyed caller's `POST /v1/realtime/client_secrets` is one unit the door carries +/// (ARRIVAL), admitted only for a token bound to the door's audience (AUTHENTICATE), dialled to the +/// destination the guard judged (VERIFY) on the DIRECT route to the catalog model +/// `streams.session.model` names (ROUTE), sent WITH THE PROVIDER'S OWN CREDENTIAL and never the +/// caller's token (EGRESS-AUTH), charged to the key that presented it (METER, GOVERNANCE-BUDGET), +/// and closed once with its one line on the node's book (AUDIT, EXIT). +/// +/// RED ARM: the same caller presenting its PLAIN data-plane token (bound to no audience) is refused +/// before anything is dialled or charged. +#[cfg(feature = "plane-streaming")] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn a_streaming_mint_is_served_through_the_door_under_the_providers_credential() { + let _one = PUBLISHING.lock().await; + let mut s = streaming("serve-door-streaming", None).await; + + let refused = send_body(&s.router, MINT, Some(&s.plain), "{}").await; + assert_eq!( + refused.status().as_u16(), + u16::try_from(refusal_status(ReasonCode::Unauthenticated)).expect("a status"), + "a token bound to no audience is refused at the streaming door" + ); + assert!(s.heard.try_recv().is_err(), "nothing was dialled for it"); + assert_eq!(s.requests(), 0, "nor charged"); + + let response = send_body(&s.router, MINT, Some(&s.bound), "{}").await; + assert_eq!(response.status(), StatusCode::OK, "served through the door"); + let body = axum::body::to_bytes(response.into_body(), 1 << 16) + .await + .expect("the body"); + let body: serde_json::Value = serde_json::from_slice(&body).expect("the door's JSON"); + assert_eq!( + body, + serde_json::json!({"value": "ek_door_0001", "expires_at_unix": 1_767_225_600_u64}), + "the minted secret and its expiry" + ); + + let head = s.heard.recv().await.expect("the provider was dialled"); + assert!(head.starts_with(&format!("POST {MINT} ")), "{head}"); + assert!( + head.lines() + .any(|l| l.eq_ignore_ascii_case(&format!("authorization: Bearer {CREDENTIAL}"))), + "the provider's own credential: {head}" + ); + assert!( + !head.contains(&s.bound) && !head.contains(&s.plain), + "no caller token reaches the provider: {head}" + ); + assert!( + head.contains(r#""model":"m-cap""#), + "the locked session on the catalog model: {head}" + ); + + assert_eq!( + s.requests(), + 1, + "one admitted request, on the presenting key" + ); + assert_eq!(s.money.open_units(), 0, "its money facts closed"); + assert_eq!(s.post.open_units(), 0, "its node facts closed"); + assert_eq!(s.lines(), 1, "its one line on the node's book"); +} + +/// ADMIT, at the streaming door (`root-voice`): a key whose group budget is already spent is refused +/// at admission — over budget, before the provider is dialled, charged nothing, no line written — +/// while a key with room is served (the mint cell above). RED: a door that dialled before admission +/// would have reached the far end. +#[cfg(feature = "plane-streaming")] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn a_spent_key_is_refused_at_the_streaming_door_before_any_dial() { + let _one = PUBLISHING.lock().await; + let mut s = streaming("serve-door-streaming-spent", Some(0)).await; + let response = send_body(&s.router, MINT, Some(&s.bound), "{}").await; + assert_eq!( + response.status().as_u16(), + 429, + "an over-budget caller is refused at admission" + ); + assert!( + s.heard.try_recv().is_err(), + "the provider was never dialled" + ); + assert_eq!(s.requests(), 0, "the refusal charged nothing"); + assert_eq!(s.money.open_units(), 0); + assert_eq!(s.post.open_units(), 0); + assert_eq!(s.lines(), 0, "no line on the node's book"); +} diff --git a/crates/busbar/tests/capability_equality.rs b/crates/busbar/tests/capability_equality.rs index 31a5e53f75..84691561eb 100644 --- a/crates/busbar/tests/capability_equality.rs +++ b/crates/busbar/tests/capability_equality.rs @@ -832,16 +832,12 @@ fn the_root_leg_matrix_runs_once_per_leg() { .expect("`root_legs` is an object"); for leg in &compiled { let file = legs_obj[*leg]["file"].as_str().expect("a leg names a file"); - let module = Path::new(file) - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or_else(|| panic!("leg `{leg}`'s file {file} has no module name")); - assert!( - mod_rs.contains(&format!("pub mod {module};")), - "leg `{leg}` is COMPILED INTO this build, but crates/busbar/src/root/mod.rs declares no \ - `pub mod {module};`. A leg whose feature is on and whose module the root does not carry \ - proves cells this binary cannot run." - ); + leg_module_declared(&root, &mod_rs, file).unwrap_or_else(|e| { + panic!( + "leg `{leg}` is COMPILED INTO this build, but {e}. A leg whose feature is on and \ + whose module the root does not carry proves cells this binary cannot run." + ) + }); assert!( s.per_leg[*leg].0 > 0 || legs_obj[*leg]["columns"] @@ -859,6 +855,73 @@ fn the_root_leg_matrix_runs_once_per_leg() { ); } +/// WHETHER THE COMPOSITION ROOT CARRIES A LEG'S FILE: `crates/busbar/src/root/tests/.rs` is +/// carried when the root declares `pub mod ;` (the leg's file is that module's test body), OR +/// when a module the root declares (`pub mod ;`, `crates/busbar/src/root/.rs`) declares +/// the file as one of its own test modules through `#[path = "tests/.rs"]` (ARCHITECT 2026-10-05 +/// Q1: a door plane's leg lives in the serving module's test file, e.g. `serve.rs`'s +/// `tests/serve_door.rs`). Anything else is refused, naming the file. +fn leg_module_declared(root: &Path, mod_rs: &str, file: &str) -> Result<(), String> { + let path = Path::new(file); + let module = path + .file_stem() + .and_then(|s| s.to_str()) + .ok_or_else(|| format!("its file {file} has no module name"))?; + if mod_rs.contains(&format!("pub mod {module};")) { + return Ok(()); + } + let base = path + .file_name() + .and_then(|s| s.to_str()) + .ok_or_else(|| format!("its file {file} has no file name"))?; + let attr = format!("#[path = \"tests/{base}\"]"); + let owners: Vec<&str> = mod_rs + .lines() + .filter_map(|l| l.trim().strip_prefix("pub mod ")) + .filter_map(|l| l.strip_suffix(';')) + .collect(); + let carried = owners.iter().any(|owner| { + std::fs::read_to_string(root.join(format!("crates/busbar/src/root/{owner}.rs"))) + .is_ok_and(|src| src.contains(&attr)) + }); + if carried && file.starts_with(ROOT_DIR) && file.contains("/tests/") { + return Ok(()); + } + Err(format!( + "crates/busbar/src/root/mod.rs declares no `pub mod {module};` and no module it declares \ + carries `{attr}`, so {file} is no module of the composition root" + )) +} + +/// THE LEG-FILE RULE, BOTH WAYS: a test file a declared root module carries through `#[path]` is a +/// leg file (the streaming door's `serve.rs` -> `tests/serve_door.rs`); a file no declared module +/// carries is refused. RED: the rule read `pub mod ;` alone and refused the first. +#[test] +fn selftest_a_leg_file_is_a_module_or_a_declared_modules_path_test_file() { + let root = repo_root(); + let mod_rs = std::fs::read_to_string(root.join("crates/busbar/src/root/mod.rs")) + .expect("the composition root's mod.rs"); + assert_eq!( + leg_module_declared(&root, &mod_rs, "crates/busbar/src/root/tests/serve_door.rs"), + Ok(()) + ); + assert_eq!( + leg_module_declared( + &root, + &mod_rs, + "crates/busbar/src/root/tests/gauntlet_kernel.rs" + ), + Ok(()) + ); + let refused = leg_module_declared( + &root, + &mod_rs, + "crates/busbar/src/root/tests/no_such_leg_file.rs", + ) + .expect_err("an undeclared file is no leg file"); + assert!(refused.contains("no_such_leg_file.rs"), "{refused}"); +} + /// The constants above ARE the doctrine; a refactor that widened `verify`'s parameters must not be /// able to quietly narrow what the real gate demands. #[test] diff --git a/qa/capability-equality.json b/qa/capability-equality.json index 3085a4f9f1..db8f1ed35e 100644 --- a/qa/capability-equality.json +++ b/qa/capability-equality.json @@ -99,12 +99,12 @@ "note": "the A2A plane's SERVED root leg: the inbound call driven through the real router and the kernel-loop rider `gauntlet_install::install()` registers for the plane's key (DECISIONS #28), each root cell running the plane's own served witness (`busbar_a2a::testkit::SERVED`) and requiring its `drive` to have run inside the loop" }, "root-voice": { - "file": "crates/busbar/src/root/tests/gauntlet_kernel.rs", + "file": "crates/busbar/src/root/tests/serve_door.rs", "columns": [ "voice-client", "voice-server" ], - "note": "the voice (streaming) plane's SERVED root leg: a live session opened at the plane's served door through the kernel-loop SESSION rider `gauntlet_install::install()` registers for the plane's key (DECISIONS #28), each root cell running the plane's own served witness (`busbar_voice::testkit::SERVED`) and requiring the session's open to have run through the loop's door; the turns, their metering on the kernel's session account (Q21b), the audit row and the durable chain are the served session the witness then drives" + "note": "the streaming plane's SERVED root leg since FLIP-STREAMING: the streaming door, linked and bound through the loader's one load, composed as production seals it (its egress over the connector, its DIRECT route to the catalog model streams.session.model names) and mounted on the data router, driven end to end by root/tests/serve_door.rs (carried by serve.rs through #[path]); the kernel-loop session rider no longer carries this plane" }, "root-admin": { "file": "crates/busbar/src/root/units_admin/tests/units_admin.rs", @@ -836,7 +836,7 @@ "root": { "state": "none", "leg": "root-voice", - "reason": "the served session witness trips the provider cell with a guard-refused dial only to show the fast-fail after it; no served witness records a failing dial into the breaker cell and asserts the trip itself as its claim" + "reason": "no streaming-door cell dials a failing provider and asserts the trip of the core breaker cell; the door's served cells reach a live loopback provider only" } }, { @@ -854,9 +854,9 @@ "plane": "voice-client", "state": "missing", "root": { - "state": "proven", + "state": "none", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_fast_fails_a_tripped_upstream" + "reason": "no streaming-door cell trips the provider's breaker cell before the mint; the served mint and the spent-key refusal are the door's two cells, neither drives a tripped upstream" } }, { @@ -897,7 +897,7 @@ "root": { "state": "none", "leg": "root-voice", - "reason": "no served session witness attaches an observe or transform hook; the witnesses open sessions at the served door with no hook attached, so no payload on this leg passes through one" + "reason": "no streaming-door cell attaches an observe or transform hook to the mint; the door projects a session open for the hooks (its own conformance cell), but no root cell drives a hook stage over it" } }, { @@ -908,7 +908,7 @@ "root": { "state": "none", "leg": "root-voice", - "reason": "no served session witness attaches an observe or transform hook; the witnesses open sessions at the served door with no hook attached, so no payload on this leg passes through one" + "reason": "the streaming door's served cells run with no hook stage composed (compose_planes is handed no HookStage), so no payload on the root leg passes through a tap or a rewrite" } }, { @@ -918,7 +918,7 @@ "root": { "state": "none", "leg": "root-voice", - "reason": "the refusal a session meets on the served leg's witnesses is the session-scope grant's at the door; no served witness attaches an operator gate, so hooks::gate::decide is reached by no cell on this leg" + "reason": "no streaming-door cell composes an operator gate; the refusals the door's root cells meet are the audience check and the spent budget, never hooks::gate::decide" } }, { @@ -928,7 +928,7 @@ "root": { "state": "none", "leg": "root-voice", - "reason": "the refusal a session meets on the served leg's witnesses is the session-scope grant's at the door; no served witness attaches an operator gate, so hooks::gate::decide is reached by no cell on this leg" + "reason": "the served mint and the spent-key refusal reach the door with no gate hook composed, so a gate refusal before dispatch is driven by no root cell on this leg" } }, { @@ -936,9 +936,9 @@ "plane": "voice-client", "state": "missing", "root": { - "state": "proven", + "state": "none", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_chains_what_it_audits" + "reason": "the served mint's one line lands on the node's book, but no streaming-door cell reads the tamper-evident chain back and verifies its link" } }, { @@ -946,9 +946,9 @@ "plane": "voice-server", "state": "missing", "root": { - "state": "proven", + "state": "none", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_chains_what_it_audits" + "reason": "no root cell on the streaming door verifies the audit record chain across the mint's line or the spent-key refusal; the chain's own cells live on the durability tests" } }, { @@ -958,7 +958,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_charges_the_presenting_key" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential" } }, { @@ -968,7 +968,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_charges_the_presenting_key" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_streaming_door_before_any_dial" } }, { @@ -996,9 +996,9 @@ "plane": "voice-client", "state": "missing", "root": { - "state": "proven", + "state": "none", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_reports_each_session_it_opens" + "reason": "no streaming-door cell scrapes the metrics registry after the mint; the door's root cells assert the money and the wire, not the plane-labelled counters" } }, { @@ -1006,9 +1006,9 @@ "plane": "voice-server", "state": "missing", "root": { - "state": "proven", + "state": "none", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_reports_each_session_it_opens" + "reason": "the door's served cells build no metrics recorder, so no session or mint counter under the plane's label is observed on this leg" } }, { @@ -1056,9 +1056,9 @@ "plane": "voice-client", "state": "missing", "root": { - "state": "none", + "state": "proven", "leg": "root-voice", - "reason": "the served session witnesses compose no provider endpoint; the credential a provider dial is reached with is composed by the plane's compose step and no served witness drives its injection" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential" } }, { diff --git a/qa/teller-steps.json b/qa/teller-steps.json index 8fcb20416e..a16de05ce4 100644 --- a/qa/teller-steps.json +++ b/qa/teller-steps.json @@ -91,7 +91,7 @@ }, "root-voice": { "plane": "voice", - "file": "crates/busbar/src/root/tests/gauntlet_kernel.rs", + "file": "crates/busbar/src/root/tests/serve_door.rs", "feature": "plane-streaming" }, "root-admin": { @@ -444,8 +444,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_carries_one_unit_per_call", - "note": "on the served session leg (the kernel-loop session rider `install()` registers for the streaming plane's key): each call at the voice door is one session open the rider carries and one report, so two calls are two units, never one" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "note": "on the streaming door's served leg: the browser's mint is one unit the door carries end to end, from the data router's claim to the provider's answer relayed to the caller" } }, "decode": { @@ -453,10 +453,9 @@ "status": "mapped", "note": "testing/voice-conformance/legs/spec-per-dialect.sh; the per-dialect spec battery includes decode-shape checks", "root": { - "state": "proven", + "state": "none", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_refuses_a_body_that_does_not_decode", - "note": "on the served session leg: a client event the plane reads is carried to the upstream, and a frame the plane cannot read reaches the upstream on no wire at all, on a session the rider opened" + "note": "the streaming door's mint takes no caller body (the locked session is the plane's own), so no root cell on this leg can feed it a body that fails to decode" } }, "authenticate": { @@ -466,8 +465,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_attributes_the_session_to_the_resolved_key", - "note": "on the served session leg: the session answers for the key the door resolved and for nobody else -- its turns land on that key's own ledger, and a caller the door resolved no key for is attributed nothing" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "note": "on the streaming door's served leg: only a token bound to the door's audience is admitted to the mint; the same key's plain data-plane token is refused before any dial or charge" } }, "verify": { @@ -477,8 +476,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_opens_the_session_on_the_destination_it_judged", - "note": "on the served session leg: the destination the open-pass gate judged inside the rider is the destination the session is metered under -- every count lands under that one lane" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "note": "on the streaming door's served leg: the mint reaches the provider through the connector at the destination its guard judged, the catalog model's provider on loopback" } }, "approve": { @@ -486,10 +485,9 @@ "status": "mapped", "note": "testing/voice-conformance/legs/session-scope.sh; K3 -- the session scope kind is a gate, not vocabulary", "root": { - "state": "proven", + "state": "none", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_refuses_a_grant_short_of_the_operation", - "note": "on the served session leg: a key valid for the door but granted no session scope is refused with the plane's own answer before the gate and opens nothing; a granted key reaches the governed open" + "note": "no streaming-door cell mints a key short of the mint's grant; the door's two root cells drive an admitted key and a spent one" } }, "admit": { @@ -499,8 +497,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_refuses_an_over_budget_caller_before_it_dials", - "note": "on the served session leg: a key whose chain already reads dry is refused at the open, past the rider's gate and before any durable row or account exists, while a key with room opens" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_streaming_door_before_any_dial", + "note": "on the streaming door's served leg: a key whose group budget is spent is refused 429 at admission, the provider never dialled and nothing charged" } }, "route": { @@ -510,8 +508,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_opens_a_session_that_relays_both_ways", - "note": "on the served session leg: the session the rider opened relays the provider's audio down to the client and the client's audio up to the provider" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "note": "on the streaming door's served leg: the mint is routed DIRECT to the catalog model streams.session.model names, its locked session carrying that model to the provider" } }, "meter": { @@ -521,8 +519,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_meters_each_turn_per_declared_class", - "note": "on the served session leg: one turn is ledgered on the presenting key per class the plane declares, the two text halves on their own classes rather than summed (Q21b: the kernel's session account)" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "note": "on the streaming door's served leg: the served mint is one request on the governance ledger of the key that presented it" } }, "audit": { @@ -532,8 +530,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_audits_each_call_once", - "note": "on the served session leg: a served open lands exactly one admin-audit row naming the mutation, the session, its outcome and its principal" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "note": "on the streaming door's served leg: the served mint writes exactly one line on the node's book, the unit's one record" } }, "exit": { @@ -543,8 +541,8 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_ends_each_call_once", - "note": "on the served session leg: the session ends once -- the turn still open when the carrier ends is ledgered with the session, and the durable row is settled and evicted" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "note": "on the streaming door's served leg: the mint's money facts and node facts close once at its end, nothing left open on either" } } }, From f21c0999ef312d814f5da9074a8684de2ec653eb Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:27:03 -0700 Subject: [PATCH 46/82] =?UTF-8?q?FLIP-STREAMING:=20Q8=20=E2=80=94=20ws=20c?= =?UTF-8?q?omposes=20over=20nothing=20and=20is=20entered=20by=20adopt=20af?= =?UTF-8?q?ter=20the=20upgraded=20framer's=20detach?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ARCHITECT Q8 (and Q128 U7): ws states EMPTY composes_over; an upgrade reaches it by adopt of the stream the http framer's detach hands up; the host reads upgrade lines off claim rows, never a layer list. - Pin: busbar-transport-ws at 364b14eb44 (transport-ws#13 head, which supersedes #12): composes_over empty, its claim row states UNIT0_UPGRADE. - plugin-loader: TransportFacts.upgrades — the claims whose row's unit0_trigger is UNIT0_UPGRADE — and `kinds::transport::stated(door)`, a compiled-in transport door's facts read off its Statement without binding it (`load::statement_of`). - core-connector: DoorFacts.upgrades; HostWire::adopts() (composes, or opens at an upgrade); adopt_from admits a stream handed up by any framer when the entry has an upgrade claim (HandoffMismatch otherwise). RED: wire_tests::an_upgrade_framer_adopts_the_stream_another_framer_hands_up (the same entry with no upgrade claim refuses it). - root: RootWire::adopt adopts through detach when the wire adopts(), not only when it composes; serve::upgrade_carriers reads the linked transport doors' upgrade claims (main: data_mounts' upgrade lines and DoorReach.upgrades), not "composes over http". - tests/ws_composed_battery.rs: the six cells keep their observable bytes on the detach -> adopt path (the upgraded-from framer listens and accepts, the wire built over nothing adopts; the dialler dials over the carrier its scheme picks); the layer cell now asserts the wire declares and reports no layer. Fixtures: composed_battery_wire.txt names the upgraded-from framer as data; the shipped fold row is `ws -`. --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/busbar-core-connector/src/framer.rs | 4 + .../src/tests/support.rs | 7 + .../src/tests/wire_tests.rs | 52 ++++ crates/busbar-core-connector/src/wire.rs | 14 +- .../tests/universal_needs.rs | 1 + crates/busbar/src/main.rs | 4 +- crates/busbar/src/root/door_steps.rs | 10 +- crates/busbar/src/root/doors.rs | 7 +- crates/busbar/src/root/serve.rs | 20 +- .../root/tests/fixtures/transport_fold.txt | 2 +- .../tests/fixtures/composed_battery_wire.txt | 13 +- crates/busbar/tests/ws_composed_battery.rs | 280 ++++++++---------- .../src/dispatch/kinds/transport.rs | 24 ++ crates/plugin-loader/src/dispatch/load.rs | 11 + 16 files changed, 265 insertions(+), 188 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad941f84c8..cb8cc98a47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1106,7 +1106,7 @@ dependencies = [ [[package]] name = "busbar-transport-ws" version = "1.6.0" -source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=a6787c0245993b0a9e366df4652b1e829db1b6cf#a6787c0245993b0a9e366df4652b1e829db1b6cf" +source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=364b14eb443a7bc6a182d385ff0d5195ce111bd9#364b14eb443a7bc6a182d385ff0d5195ce111bd9" dependencies = [ "busbar-contract", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 29c178b089..f821d125f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ busbar-transport-http = { git = "https://github.com/GetBusbar/busbar-transport-h busbar-transport-grpc = { git = "https://github.com/GetBusbar/busbar-transport-grpc", rev = "5607182ed5d28da20a6e1699aad512909d24142e" } # THE WS TRANSPORT: the websocket framer door (kind: transport), pulled at a pinned rev; the logic # crate the root links (behind plane-streaming) and the ws-conformance subject drives. -busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "a6787c0245993b0a9e366df4652b1e829db1b6cf" } +busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "364b14eb443a7bc6a182d385ff0d5195ce111bd9" } busbar-export-webhook-plugin = { git = "https://github.com/GetBusbar/busbar-export-webhook", rev = "3ad5a711aad433f2015a980cf3a792afa5fd3a7a" } # THE TCP TRANSPORT: the identity framer door (kind: transport), one pin for the logic crate the root # links and the cdylib crate the loader's both-ways proof builds. Same repo, same rev, one resolved copy. diff --git a/crates/busbar-core-connector/src/framer.rs b/crates/busbar-core-connector/src/framer.rs index f3369c1713..38739a154f 100644 --- a/crates/busbar-core-connector/src/framer.rs +++ b/crates/busbar-core-connector/src/framer.rs @@ -62,6 +62,10 @@ pub struct DoorFacts { pub claims: Vec<&'static str>, /// The claims it composes over; empty = directly over the host's socket. pub composes_over: Vec<&'static str>, + /// Its claims whose first unit opens at an UPGRADE (`UNIT0_UPGRADE` on the claim row): an entry + /// with one ADOPTS the stream another framer hands up at the upgrade (that framer's `detach`), + /// read off its claim rows, never off a layer list (ARCHITECT Q128 U7). + pub upgrades: Vec<&'static str>, } /// A transport entry's framer table, as the host reaches it. diff --git a/crates/busbar-core-connector/src/tests/support.rs b/crates/busbar-core-connector/src/tests/support.rs index 89d360e102..ebc084455b 100644 --- a/crates/busbar-core-connector/src/tests/support.rs +++ b/crates/busbar-core-connector/src/tests/support.rs @@ -82,6 +82,7 @@ impl TestDoor { name: name.to_owned(), claims: claims.to_vec(), composes_over: composes_over.to_vec(), + upgrades: Vec::new(), }, knobs, framings: Mutex::new(HashMap::new()), @@ -94,6 +95,12 @@ impl TestDoor { } } + /// This door, its every claim opening at an UPGRADE (`UNIT0_UPGRADE` on its claim rows). + pub fn upgrading(mut self) -> Self { + self.facts.upgrades = self.facts.claims.clone(); + self + } + /// An identity entry over the host's socket, claiming `scheme`. pub fn identity(scheme: &'static str) -> Self { Self::new(scheme, &[scheme], &[], Knobs::default()) diff --git a/crates/busbar-core-connector/src/tests/wire_tests.rs b/crates/busbar-core-connector/src/tests/wire_tests.rs index c70b1c0afe..6c5fed0e33 100644 --- a/crates/busbar-core-connector/src/tests/wire_tests.rs +++ b/crates/busbar-core-connector/src/tests/wire_tests.rs @@ -556,6 +556,58 @@ fn a_composing_entry_adopts_the_stream_its_lower_layer_hands_up() { }); } +/// AN UPGRADE FRAMER IS ENTERED BY ADOPT (ARCHITECT Q128 U7 / Q8): an entry that composes over +/// nothing frames the host's own socket, and when a claim of its opens at an upgrade it ADOPTS the +/// stream another framer hands up at the upgrade (that framer's `detach`), what the lower framer +/// held first in front of it, framing both ways from there. RED: the same entry with no upgrade claim +/// refuses the stream as a handoff mismatch (the rule read the layer list alone, so an upgrade +/// framer that names no layer could never be entered). +#[test] +fn an_upgrade_framer_adopts_the_stream_another_framer_hands_up() { + worker().block_on(async { + let upgrading = HostWire::new(Arc::new( + TestDoor::new("msg", &["msg"], &[], Knobs::default()).upgrading(), + )) + .expect("over the host's socket"); + assert!(upgrading.adopts() && !upgrading.composes()); + let lower = wire(); + let (client, server, _l) = pair(&lower).await; + lower + .write(&client, StreamId(0), ScratchBytes::new(b"early"), false) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(50)).await; + let raw = lower.detach(&server).expect("hands up"); + let adopted = upgrading + .adopt_from(raw, Vec::new(), None) + .await + .expect("adopted at the upgrade"); + assert_eq!(read_n(&upgrading, &adopted, 5).await, b"early"); + upgrading + .write(&adopted, StreamId(0), ScratchBytes::new(b"over"), false) + .await + .unwrap(); + assert_eq!(read_n(&lower, &client, 4).await, b"over"); + upgrading.close(adopted, CloseReason::Normal); + + let plain = HostWire::new(Arc::new(TestDoor::new( + "msg", + &["msg"], + &[], + Knobs::default(), + ))) + .expect("over the host's socket"); + assert!(!plain.adopts()); + let (_c2, s2, _l2) = pair(&lower).await; + let raw = lower.detach(&s2).expect("hands up"); + assert_eq!( + plain.adopt_from(raw, Vec::new(), None).await.err(), + Some(TransportError::HandoffMismatch), + "an entry with no upgrade claim and no layer adopts nothing" + ); + }); +} + /// RED (SEAM-4n, ARCHITECT ruling): a dialled framing is begun with the FULL target its need /// declared (`scheme://host:port/path`), never the bare authority; the socket goes to the authority /// the entry's own `locate` reads off that target. A target that asks for connection security is diff --git a/crates/busbar-core-connector/src/wire.rs b/crates/busbar-core-connector/src/wire.rs index 84ff32f45c..b89b7fc15c 100644 --- a/crates/busbar-core-connector/src/wire.rs +++ b/crates/busbar-core-connector/src/wire.rs @@ -411,6 +411,14 @@ impl HostWire { !self.door.facts().composes_over.is_empty() } + /// Whether this entry ADOPTS a stream another framer hands up: it composes over that layer, or + /// a claim of its opens at an upgrade (ARCHITECT Q128 U7: an upgrade framer composes over + /// nothing and is entered by `adopt` after the upgraded framer's `detach`). + #[must_use] + pub fn adopts(&self) -> bool { + self.composes() || !self.door.facts().upgrades.is_empty() + } + /// What a framing on this entry is told the handshake established: its claim. fn established(&self) -> Established { Established { @@ -474,7 +482,8 @@ impl HostWire { /// # Errors /// /// [`TransportError::HandoffMismatch`]: `raw` comes from a layer this entry does not declare it - /// composes over; [`TransportError::Framing`]: the entry refused to adopt it. + /// composes over, and no claim of the entry opens at an upgrade; [`TransportError::Framing`]: + /// the entry refused to adopt it. pub async fn adopt_from( &self, raw: RawStream, @@ -482,7 +491,8 @@ impl HostWire { door: Option>, ) -> Result { let door = door.unwrap_or_else(|| Arc::clone(&self.door)); - if !door.facts().composes_over.contains(&raw.from()) { + let facts = door.facts(); + if !facts.composes_over.contains(&raw.from()) && facts.upgrades.is_empty() { return Err(TransportError::HandoffMismatch); } let (framing, y) = Framing::adopt(door, SIDE_ACCEPT, &[], &self.established()) diff --git a/crates/busbar-core-connector/tests/universal_needs.rs b/crates/busbar-core-connector/tests/universal_needs.rs index 1858f1d210..03f46b0152 100644 --- a/crates/busbar-core-connector/tests/universal_needs.rs +++ b/crates/busbar-core-connector/tests/universal_needs.rs @@ -258,6 +258,7 @@ fn open_door( name: plugin.name().to_owned(), claims: stated.claims, composes_over: stated.composes_over, + upgrades: stated.upgrades, }, plugin, })) diff --git a/crates/busbar/src/main.rs b/crates/busbar/src/main.rs index 9664b99c35..c649a95616 100644 --- a/crates/busbar/src/main.rs +++ b/crates/busbar/src/main.rs @@ -861,7 +861,7 @@ async fn run(data_workers: usize, late_services: std::sync::Arc { /// The top-level models catalog a section's `session.model` names an entry of /// (`root::serve::catalog_routes`, ARCHITECT Q-L5B-ROUTE); `None` = the section's own table. pub catalog: Option<&'a std::collections::HashMap>, - /// The linked wires composed over the data carrier (`crate::root::serve::upgrade_carriers`): - /// a need over one dials its member's base URL in its own scheme ([`spelled_for`]). + /// The linked upgrade lines (`crate::root::serve::upgrade_carriers`: the claims that open at an + /// upgrade): a need over one dials its member's base URL in its own scheme ([`spelled_for`]). pub upgrades: Vec<&'static str>, } @@ -1745,9 +1745,9 @@ pub fn member_routes( Ok(routes) } -/// THE BASE URL A NEED'S FRAMER READS: a need over `transport`, a framer composed over the -/// carrier the operator's `base_url` names (`upgrades`: the linked wires composing over the data -/// carrier, `crate::root::serve::upgrade_carriers`), dials the same authority and path under the +/// THE BASE URL A NEED'S FRAMER READS: a need over `transport`, a framer the carrier the operator's +/// `base_url` names is upgraded to (`upgrades`: the linked claims that open at an upgrade, +/// `crate::root::serve::upgrade_carriers`), dials the same authority and path under the /// framer's own scheme, its secured form for a secured base (`http://h` -> `://h`, /// `https://h` -> `s://h`, the pairing every upgrade-over-HTTP scheme keeps, RFC 6455 section /// 3). `None` when the need dials the base URL as written. diff --git a/crates/busbar/src/root/doors.rs b/crates/busbar/src/root/doors.rs index f2f14f39bc..33df1f89ec 100644 --- a/crates/busbar/src/root/doors.rs +++ b/crates/busbar/src/root/doors.rs @@ -155,6 +155,7 @@ impl Dispatched { name: plugin.name().to_owned(), claims: stated.claims, composes_over: stated.composes_over, + upgrades: stated.upgrades, }; Ok(Self { plugin, @@ -415,14 +416,16 @@ impl busbar_contract::Transport for RootWire { } /// A composing entry ([`HostWire::composed`]) adopts the stream the layer under it hands up - /// (`from`'s own detach); an entry over the host's socket adopts nothing. + /// (`from`'s own detach), and so does an UPGRADE framer (a claim of its opens at an upgrade: + /// it composes over nothing and is entered by adopt after the upgraded framer's detach, ARCHITECT + /// Q128 U7 / Q8); any other entry over the host's socket adopts nothing. fn adopt<'a>( &'a self, from: &'a dyn busbar_contract::Transport, conn: busbar_contract::transport::wire::Conn, keys: &'a busbar_contract::TransportKeyHandle, ) -> busbar_contract::Fut<'a, busbar_contract::transport::wire::Conn> { - if !self.wire.composes() { + if !self.wire.adopts() { return self.wire.adopt(conn, keys); } Box::pin(async move { diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index b01c7afe3e..f283239b9e 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -1355,17 +1355,19 @@ fn line_auth(flags: u32, data_chain: &[String]) -> busbar_kernel::guest::LineAut #[cfg(linked_axis_node)] const CORE_CLAIMANT: &str = "core"; -/// The data listener's own framer: a claim over a carrier that composes over it is an upgrade line. -const DATA_CARRIER: &str = "http"; - -/// THE UPGRADE CARRIERS: the linked wires that compose over the data listener's own framer, whose -/// claims are upgrade lines (their connection handed over after the head) and served as sessions. +/// THE UPGRADE CARRIERS: the claims of the linked transport doors whose first unit opens at an +/// UPGRADE (`UNIT0_UPGRADE` on the claim row) — upgrade lines (their connection handed over after the +/// head), served as sessions. Read off the claim rows, never off a layer list (ARCHITECT Q128 U7): an +/// upgrade framer composes over nothing. A door whose Statement is refused contributes none; its +/// refusal is the boot's, at the connector's build. #[must_use] -pub fn upgrade_carriers(transports: &[crate::root::linked::LinkedTransport]) -> Vec<&'static str> { - transports +pub fn upgrade_carriers( + doors: &[(&str, busbar_contract::abi::mechanism::door::DoorFn)], +) -> Vec<&'static str> { + doors .iter() - .filter(|t| t.composes_over.contains(&DATA_CARRIER)) - .map(|t| t.key) + .filter_map(|(_, door)| crate::root::loader::dispatch::kinds::transport::stated(*door).ok()) + .flat_map(|facts| facts.upgrades) .collect() } diff --git a/crates/busbar/src/root/tests/fixtures/transport_fold.txt b/crates/busbar/src/root/tests/fixtures/transport_fold.txt index c333eb3b00..bb4d084a27 100644 --- a/crates/busbar/src/root/tests/fixtures/transport_fold.txt +++ b/crates/busbar/src/root/tests/fixtures/transport_fold.txt @@ -6,6 +6,6 @@ tcp - http - sse http -ws http +ws - grpc - stdio - diff --git a/crates/busbar/tests/fixtures/composed_battery_wire.txt b/crates/busbar/tests/fixtures/composed_battery_wire.txt index 6d18293915..a257bb3c8f 100644 --- a/crates/busbar/tests/fixtures/composed_battery_wire.txt +++ b/crates/busbar/tests/fixtures/composed_battery_wire.txt @@ -1,6 +1,7 @@ -# THE WIRE tests/ws_composed_battery.rs drives, as data: ` `. The key -# is looked up in the linked transport table (build.rs `$OUT_DIR/linked_transports.rs`), which is -# where the battery gets the wire's build, its declared layers and the layers it is built over; the -# secure scheme is the dial target the wire must refuse over a cleartext layer. Its cleartext target -# scheme is its key. -ws wss +# THE WIRE tests/ws_composed_battery.rs drives, as data: ` `. The key is looked up in the linked transport table (build.rs +# `$OUT_DIR/linked_transports.rs`), which is where the battery gets the wire's build; the secure +# scheme is the dial target the wire must refuse over a cleartext carrier; the third is the framer an +# inbound upgrade arrives on, whose `detach` hands the stream up for the wire to `adopt` (the wire +# composes over nothing: ARCHITECT Q128 U7 / Q8). Its cleartext target scheme is its key. +ws wss http diff --git a/crates/busbar/tests/ws_composed_battery.rs b/crates/busbar/tests/ws_composed_battery.rs index 0aed0a70e7..cebc421bd1 100644 --- a/crates/busbar/tests/ws_composed_battery.rs +++ b/crates/busbar/tests/ws_composed_battery.rs @@ -1,27 +1,35 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! The upgrade wire's cross-transport composition battery: the six cells that drive a real instance -//! of the layers it composes over underneath it. +//! The upgrade wire's cross-transport battery: the six cells that drive a real instance of the +//! framer an upgrade arrives on, and the wire entered by ADOPT after that framer's `detach`. +//! +//! ## The shape (ARCHITECT Q128 U7 / Q8) +//! +//! The wire composes over NOTHING: no transport names another, the carrier under a dial is the +//! connector's choice from the target's scheme, and an inbound upgrade reaches the wire by `adopt` of +//! the stream the upgraded framer's `detach` hands up (its claim row states the upgrade). These cells +//! were the composition battery while the wire composed over `http`/`tcp`; they keep their observable +//! bytes on the detach -> adopt path. //! //! ## Why this lives here and not in the wire's own crate //! //! A transport crate is a plugin-kind crate: its own `Cargo.toml` is not allowed to name a sibling //! transport even in `[dev-dependencies]`, because a dev-dependency's SHIPPED closure is linked into //! that crate's `cargo test` binary whole (`kind-isolation:closure`'s `closure-test-reach` -//! finding). Composing OVER another wire is real and shipped (an adoption through a contract trait), -//! but PROVING it with a real lower layer needs every layer linked, and the one crate that links +//! finding). The hand-up across two framers is real and shipped (an adoption through a contract +//! trait), but PROVING it with a real upgraded framer needs both linked, and the one crate that links //! them all is the composition root. //! //! ## How this file reaches the wires //! //! Through the linked transport table, exactly as the boot seal does: build.rs hands this test each //! linked row's `KEY`, `COMPOSES_OVER` and `build` (`$OUT_DIR/linked_transports.rs`) and the same -//! bottom-up fold `root::registry::compose` runs. The wire under test is named once, as data -//! (`tests/fixtures/composed_battery_wire.txt`); the layer it is built over, and the layer under -//! that, are read off the fold — and the layer it is built over is held to the shipped fold -//! (`src/root/tests/fixtures/transport_fold.txt`), so a declaration that re-layers the wire turns -//! this battery red. The file names no transport crate and spells no transport key. +//! bottom-up fold `root::registry::compose` runs. The wire under test and the framer it is upgraded +//! from are named once, as data (`tests/fixtures/composed_battery_wire.txt`); the wire's own layer is +//! held to the shipped fold (`src/root/tests/fixtures/transport_fold.txt`: it is built over +//! nothing), so a declaration that re-layers the wire turns this battery red. The file names no +//! transport crate and spells no transport key. //! //! The file needs every linked wire (`linked_every_transport`, emitted by build.rs): a build that //! leaves one unlinked is a different composition, not a smaller one. @@ -57,31 +65,27 @@ fn rows(text: &'static str) -> impl Iterator ` row")) } -/// The wire under test, its layers and its targets — every key read off the linked table. +/// The wire under test, the framer it is upgraded from, and its targets — every key read off the +/// linked table. struct Stack { /// The wire's linked row. wire: &'static LinkedWire, - /// The wire as the fold built it, over `upper`. - composed: Arc, - /// The layer the fold built the wire over (the one an inbound upgrade arrives on). + /// The framer an inbound upgrade arrives on, as the fold built it. upper: Arc, - /// The first linked layer `upper` declares (the one an outbound dial goes through). - bottom: Arc, - /// The scheme of a target the wire must refuse over a cleartext layer. + /// The scheme of a target the wire must refuse over a cleartext carrier. secure: &'static str, } impl Stack { /// A fresh fold of every linked wire at the default settings. fn fold() -> Self { - Self::fold_with(&TransportSettings::default()) - } - - fn fold_with(settings: &TransportSettings) -> Self { - let (key, secure) = rows(BATTERY_WIRE) + let (key, rest) = rows(BATTERY_WIRE) .next() .expect("the battery names its wire"); - let built = fold_linked_transports(settings); + let (secure, from) = rest + .split_once(' ') + .expect("` `"); + let built = fold_linked_transports(&TransportSettings::default()); let find = |key: &str| { built .iter() @@ -89,29 +93,19 @@ impl Stack { .map(|(row, t)| (*row, Arc::clone(t))) .unwrap_or_else(|| panic!("`{key}` is not a linked wire")) }; - let (wire, composed) = find(key); - let upper_key = composed - .composed_over() - .unwrap_or_else(|| panic!("the fold built `{key}` over nothing")); + let (wire, folded) = find(key); let (_, shipped) = rows(TRANSPORT_FOLD) .find(|(k, _)| *k == key) .unwrap_or_else(|| panic!("the shipped fold has no `{key}` row")); assert_eq!( - upper_key, shipped, - "the fold built `{key}` over a layer the shipped fold does not name" + (folded.composed_over(), shipped), + (None, "-"), + "the fold built `{key}` over nothing, as the shipped fold says" ); - let (upper_row, upper) = find(upper_key); - let bottom_key = upper_row - .composes_over - .iter() - .find(|l| LINKED_TRANSPORTS.iter().any(|r| r.key == **l)) - .unwrap_or_else(|| panic!("`{upper_key}` declares no linked layer")); - let (_, bottom) = find(bottom_key); + let (_, upper) = find(from); Self { wire, - composed, upper, - bottom, secure, } } @@ -125,18 +119,58 @@ impl Stack { (self.wire.build)(lower, settings) } - /// A fresh instance of the wire over the dial layer, at the default settings. + /// A fresh instance of the wire, at the default settings: it dials over the carrier the + /// connector picks from the target's scheme. fn dialler(&self) -> Arc { - self.build( - Some(Arc::clone(&self.bottom)), - &TransportSettings::default(), - ) + self.build(None, &TransportSettings::default()) } /// A cleartext target on `addr`, whose scheme is the wire's key. fn target(&self, addr: impl std::fmt::Display, path: &str) -> &'static str { Box::leak(format!("{}://{addr}{path}", self.wire.key).into_boxed_str()) } + + /// THE UPGRADED PAIR: the upgraded-from framer listens and accepts the dialler's upgrade, the + /// wire built at `server` ADOPTS the stream that framer hands up, and `client` dials the wire's + /// target at `path`. Answers (the client wire, its connection, the adopting wire, its + /// connection). + async fn upgraded( + &self, + server: &TransportSettings, + client: Arc, + path: &str, + ) -> ( + Arc, + busbar_contract::transport::wire::Conn, + Arc, + busbar_contract::transport::wire::Conn, + ) { + let upper = Arc::clone(&self.upper); + let adopter = self.build(None, server); + let keys = test_key_handle(); + let listener = upper + .listen(&ListenerCfg("127.0.0.1:0".to_string(), None), &keys) + .await + .unwrap(); + let addr = listener.local_addr(); + let upgrade_task = { + let (upper, adopter) = (upper.clone(), adopter.clone()); + tokio::spawn(async move { + let keys = test_key_handle(); + let upper_conn = upper.accept(&listener).await.unwrap(); + adopter.adopt(&*upper, upper_conn, &keys).await.unwrap() + }) + }; + let client_conn = client + .dial( + &verified_upstream(self.wire.key, self.target(addr, path)), + &keys, + ) + .await + .unwrap(); + let server_conn = upgrade_task.await.unwrap(); + (client, client_conn, adopter, server_conn) + } } /// A minimal listener config view, built only from `busbar_contract`'s public @@ -181,11 +215,11 @@ fn verified_upstream( ) } -/// The in-band upgrade, driven through the seam the design names: the layer below accepts the -/// connection, the wire adopts the stream it gives up, and the handshake runs on the layer that -/// speaks it. The facts of the pre-upgrade layer do not survive it — that layer no longer knows the -/// connection — and the composed chain the adopted connection reports is the real one, not a name -/// for itself. +/// The in-band upgrade, driven through the seam the design names: the upgraded-from framer accepts +/// the connection, the wire adopts the stream it gives up (its `detach`), and the handshake runs on +/// the wire that speaks it. The facts of the pre-upgrade framer do not survive it — that framer no +/// longer knows the connection — and the chain the adopted connection reports is the real one, not +/// a name for itself. #[tokio::test] async fn an_in_band_upgrade_over_the_layer_below_with_cleared_facts() { let stack = Stack::fold(); @@ -221,13 +255,13 @@ async fn an_in_band_upgrade_over_the_layer_below_with_cleared_facts() { assert_eq!( before, - vec![stack.bottom.key(), upper.key()], - "the layer below named itself" + vec![upper.key()], + "the upgraded-from framer named itself" ); assert_eq!( adopter.arrival(&upgraded).transport_chain, - vec![stack.bottom.key(), upper.key(), stack.wire.key], - "the composed chain, not a name for itself" + vec![upper.key(), stack.wire.key], + "the real chain, not a name for itself" ); assert_eq!( after_source.port, 0, @@ -249,38 +283,24 @@ async fn an_in_band_upgrade_over_the_layer_below_with_cleared_facts() { assert_eq!(frame.bytes.as_slice(), b"after the upgrade"); } -/// The genuine network path, over the layers this wire composes over rather than over sockets of -/// its own: the upper layer binds and accepts, the bottom layer dials, and this one does the one -/// thing it owns — the handshake — on the streams they give up. +/// The genuine network path: the upgraded-from framer binds and accepts, the wire dials over the +/// carrier its target's scheme picks, and the wire does the one thing it owns — the handshake — on +/// both ends, the accepting end on the stream the upgraded-from framer gave up. #[tokio::test] async fn a_composed_round_trip_over_the_layers_below() { let stack = Stack::fold(); - let server_t = Arc::clone(&stack.composed); - let client_t = stack.dialler(); - let keys = test_key_handle(); - let listener = server_t - .listen(&ListenerCfg("127.0.0.1:0".to_string(), None), &keys) - .await - .unwrap(); - let addr = listener.local_addr(); - - let accept_task = { - let server_t = server_t.clone(); - tokio::spawn(async move { server_t.accept(&listener).await }) - }; - - let dest = verified_upstream(stack.wire.key, stack.target(addr, "/")); - let client_conn = client_t.dial(&dest, &keys).await.unwrap(); - let server_conn = accept_task.await.unwrap().unwrap(); + let (client_t, client_conn, server_t, server_conn) = stack + .upgraded(&TransportSettings::default(), stack.dialler(), "/") + .await; // Both ends report the stack they actually stand on, not a name for themselves. assert_eq!( server_t.arrival(&server_conn).transport_chain, - vec![stack.bottom.key(), stack.upper.key(), stack.wire.key] + vec![stack.upper.key(), stack.wire.key] ); assert_eq!( client_t.arrival(&client_conn).transport_chain, - vec![stack.bottom.key(), stack.wire.key] + vec![stack.wire.key] ); client_t @@ -296,78 +316,46 @@ async fn a_composed_round_trip_over_the_layers_below() { assert_eq!(frame.bytes.as_slice(), b"hello over the layers below"); } -/// The layer this instance reports is the one it was built over, and it is one the wire declares — -/// which is what the registry's boot check compares. A composition nobody declared refuses the boot -/// rather than running as a stack the declarations do not describe. +/// The layer this instance reports is the one it declares: NONE. An upgrade wire composes over +/// nothing, so it is built over nothing whatever the fold hands it, and reports no layer — which is +/// what the registry's boot check compares. RED: a wire that still declared a layer would be built +/// over it and report it. #[tokio::test] async fn the_layer_reported_is_one_the_transport_declares() { let stack = Stack::fold(); let settings = TransportSettings::default(); - let built = fold_linked_transports(&settings); - let mut over = Vec::new(); - for layer in stack.wire.composes_over { - let Some((_, lower)) = built.iter().find(|(row, _)| row.key == *layer) else { - continue; - }; - let t = stack.build(Some(Arc::clone(lower)), &settings); - let used = t.composed_over().expect("built over a layer, it names one"); - assert_eq!( - used, *layer, - "the layer reported is the one it was built over" - ); - assert!( - stack.wire.composes_over.contains(&used), - "`{used}` is a layer this wire declares it composes over" - ); - over.push(used); - } - // Both halves of the stack the other cells stand on were built over and answered. - assert!( - over.contains(&stack.upper.key()), - "built over the upper layer" - ); assert!( - over.contains(&stack.bottom.key()), - "built over the dial layer" + stack.wire.composes_over.is_empty(), + "the upgrade wire declares no layer: {:?}", + stack.wire.composes_over ); assert_eq!(stack.build(None, &settings).composed_over(), None); + assert_eq!( + stack + .build(Some(Arc::clone(&stack.upper)), &settings) + .composed_over(), + None, + "handed a layer it does not declare, it is still built over nothing" + ); + assert!(stack.wire.session, "the upgrade wire carries sessions"); } /// The size of message this connection will accept is the operator's number, not the WebSocket -/// library's. Left to the default, a listener declaring a 1 KiB body cap would still buffer 64 MiB +/// library's. Left to the default, a deployment declaring a 1 KiB body cap would still buffer 64 MiB /// per connection before saying no — the deployment's own limit silently widened by four orders of -/// magnitude, at the one layer where an oversized message is cheapest to refuse. +/// magnitude, at the one layer where an oversized message is cheapest to refuse. The accepting wire +/// is built with the deployment's cap, as the composition root builds it, and adopts the upgrade. #[tokio::test] async fn the_message_cap_is_the_operator_s_and_not_the_library_s() { const CAP: usize = 1024; let stack = Stack::fold(); - let t = Arc::clone(&stack.composed); - // The listener is where the operator's configuration reaches this transport at all. - let keys = test_key_handle(); - let listener = t - .listen( - &ListenerCfg("127.0.0.1:0".to_string(), Some(CAP as i64)), - &keys, - ) - .await - .unwrap(); - let addr = listener.local_addr(); - let accept_task = { - let t = t.clone(); - tokio::spawn(async move { t.accept(&listener).await }) + let capped = TransportSettings { + request_body_max_bytes: CAP, + ..TransportSettings::default() }; - // The peer is not capped at the operator's number, because the cap this test is about is the // RECEIVER's: a limit that only holds when the far side agrees to it is not a limit. - let peer = stack.dialler(); - let a = peer - .dial( - &verified_upstream(stack.wire.key, stack.target(addr, "/")), - &keys, - ) - .await - .unwrap(); - let b = accept_task.await.unwrap().unwrap(); + let (peer, a, t, b) = stack.upgraded(&capped, stack.dialler(), "/").await; let oversized = vec![b'w'; 2 * CAP]; peer.write(&a, StreamId(0), ScratchBytes::new(&oversized)) @@ -386,45 +374,19 @@ async fn the_message_cap_is_the_operator_s_and_not_the_library_s() { ); } -/// And the other lifecycle: an instance that only ever DIALS holds the same ceiling. -/// -/// The cell above reaches the transport through `listen`, which is the seam a served instance -/// learns the deployment's configuration through. A dial-side instance never reaches it — nothing -/// binds it, so nothing hands it a config view — and an upstream that streams audio is exactly the -/// connection an unbounded message ceiling costs the most on. The composition root holds the number -/// in both cases, so it names it at the wire's build here — the settings the linked row is built -/// with — and the same field answers. +/// And the other lifecycle: an instance that only ever DIALS holds the same ceiling — the settings +/// the linked row is built with, since nothing binds a dial-side instance or hands it a config view. #[tokio::test] async fn a_dial_only_instance_holds_the_ceiling_its_root_named() { const CAP: usize = 1024; let stack = Stack::fold(); - // No `listen` on this instance: the ceiling arrives only through the build's settings. let capped = TransportSettings { request_body_max_bytes: CAP, ..TransportSettings::default() }; - let t = stack.build(Some(Arc::clone(&stack.bottom)), &capped); - + let t = stack.build(None, &capped); // The upstream is not capped at this number: it is the dialler's ceiling under test. - let upstream = Arc::clone(&stack.composed); - let keys = test_key_handle(); - let listener = upstream - .listen(&ListenerCfg("127.0.0.1:0".to_string(), None), &keys) - .await - .unwrap(); - let addr = listener.local_addr(); - let accept_task = { - let upstream = upstream.clone(); - tokio::spawn(async move { upstream.accept(&listener).await }) - }; - let mine = t - .dial( - &verified_upstream(stack.wire.key, stack.target(addr, "/")), - &keys, - ) - .await - .unwrap(); - let theirs = accept_task.await.unwrap().unwrap(); + let (t, mine, upstream, theirs) = stack.upgraded(&TransportSettings::default(), t, "/").await; // At the ceiling the message is a message, so this is a ceiling and not a smaller default. let at_cap = vec![b'k'; CAP]; @@ -453,8 +415,8 @@ async fn a_dial_only_instance_holds_the_ceiling_its_root_named() { } /// A secure target is a statement that the bytes are encrypted before they leave, and this wire -/// encrypts nothing: it upgrades whatever stream the layer below gives it. Over a cleartext lower -/// layer the handshake would therefore go out as a plain HTTP GET, with no certificate ever +/// encrypts nothing: it frames whatever stream its carrier gives it. Over a cleartext carrier the +/// handshake would therefore go out as a plain HTTP GET, with no certificate ever /// validated, while the destination said it was secure. The dial is refused instead, and nothing /// reaches the wire. #[tokio::test] diff --git a/crates/plugin-loader/src/dispatch/kinds/transport.rs b/crates/plugin-loader/src/dispatch/kinds/transport.rs index b6b65627da..d2d994757e 100644 --- a/crates/plugin-loader/src/dispatch/kinds/transport.rs +++ b/crates/plugin-loader/src/dispatch/kinds/transport.rs @@ -60,6 +60,24 @@ pub struct TransportFacts { /// The customer settings it reads, by their 1.5.5 config paths (`TransportTail::settings`), in /// its order: the host deals each one's value to its `open`. pub settings: Vec<&'static str>, + /// The claims whose first unit opens at an UPGRADE (`Claim::unit0_trigger` = + /// `UNIT0_UPGRADE`): the host's upgrade lines, read off the claim rows and never off a layer + /// list (ARCHITECT Q128 U7). A framer with one adopts the stream another framer hands up at the + /// upgrade (its `detach`), whatever that framer is. + pub upgrades: Vec<&'static str>, +} + +/// WHAT A COMPILED-IN TRANSPORT DOOR STATES, read off its Statement without binding it (no +/// `validate` or `open` runs), checked as a bind checks it. +/// +/// # Errors +/// +/// The door, its Statement or its transport tail is refused. +pub fn stated( + door_fn: busbar_contract::abi::mechanism::door::DoorFn, +) -> Result { + let st = crate::dispatch::load::statement_of(door_fn).map_err(|e| e.to_string())?; + tail_facts(&st) } /// A plugin string, interned. @@ -126,6 +144,12 @@ fn tail_facts(st: &Statement) -> Result { .iter() .map(|s| owned(s.path, "settings")) .collect::>()?, + upgrades: names + .iter() + .zip(rows) + .filter(|(_, row)| row.unit0_trigger == transport::UNIT0_UPGRADE) + .map(|(c, _)| owned(*c, "claim")) + .collect::>()?, }) } diff --git a/crates/plugin-loader/src/dispatch/load.rs b/crates/plugin-loader/src/dispatch/load.rs index 7b0fdb6908..aba653086c 100644 --- a/crates/plugin-loader/src/dispatch/load.rs +++ b/crates/plugin-loader/src/dispatch/load.rs @@ -104,6 +104,17 @@ impl LinkedRow { } } +/// A compiled-in door's Statement, checked as [`rendering_of`] checks it, for a kind's own reader +/// of its tail (`kinds::transport::stated`). +/// +/// # Errors +/// +/// The refusal the door or its Statement earns. +pub(crate) fn statement_of(door_fn: DoorFn) -> Result { + let (door, _) = read_door(door_fn(), None)?; + statement(&door) +} + /// THE RENDERING a door's Statement makes, for any kind: the door's head checks (magic, mechanism, /// size, a known kind at this host's ABI version for it) and the Statement's, then /// [`render`]. The pack tool signs it into a dropped plugin's manifest; [`LinkedRow::of`] states it From 899f59f45d9688bbd0e12cad76c07ed6b8130dbb Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:28:22 -0700 Subject: [PATCH 47/82] FLIP-STREAMING: the streaming door's root cells use the key's data-plane token (the door's audience binding is the deployment mount table's, judged by the streams|mint|wrong-audience oracle cell; the RED arm is the unkeyed request); xtask infra pins the three-key legacy roster --- crates/busbar/src/root/tests/serve_door.rs | 58 +++++++--------------- qa/teller-steps.json | 2 +- xtask/tests/infra.rs | 4 +- 3 files changed, 22 insertions(+), 42 deletions(-) diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index d0495fd492..0a87554838 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -16,7 +16,7 @@ //! //! THE STREAMING DOOR, the same way (the `root-voice` leg of qa/capability-equality.json, ARCHITECT //! 2026-10-05 Q1): the browser's ephemeral-secret mint, claimed by the streaming plane's door, served -//! end to end — an audience-bound caller, the door's DIRECT route to the top-level catalog model +//! end to end — a keyed caller, the door's DIRECT route to the top-level catalog model //! `streams.session.model` names, the provider's own credential presented by the auth plugin, the //! caller's spend posted against the key that presented it. @@ -542,10 +542,8 @@ struct Streaming { gov: Arc, app: Arc, key: busbar_contract::records::VirtualKey, - /// The key's plain data-plane token. + /// The key's data-plane token. plain: String, - /// The key's token bound to the door's audience, as the browser-facing mint is reached with. - bound: String, money: Arc, post: Arc, book: crate::root::durability::NodeBook, @@ -559,7 +557,6 @@ struct Streaming { /// the composition itself, and its claims mounted on the data router. #[cfg(feature = "plane-streaming")] async fn streaming(instance: &'static str, budget: Option) -> Streaming { - use busbar_kernel::governance::signing::TokenVerifier; use busbar_plane_streaming::door::{door as streaming_door, NAME as PLANE}; let published = Published(instance); @@ -635,14 +632,9 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { } }; let signer = TokenSigner::from_secret_bytes(&[7u8; 32], DEFAULT_KID); - let verifier = TokenVerifier::single(signer.kid(), signer.verifying_key()); let gov = Arc::new( - GovState::new_with_signer( - Arc::new(MemoryStore::new()), - None, - Some(TokenSigner::from_secret_bytes(&[7u8; 32], DEFAULT_KID)), - ) - .expect("governance"), + GovState::new_with_signer(Arc::new(MemoryStore::new()), None, Some(signer)) + .expect("governance"), ); let (key, plain) = gov .mint_signed( @@ -655,17 +647,6 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { 1_700_000_000, ) .expect("mint"); - let generation = verifier - .verify(plain.expose_secret(), 1_700_000_000, None) - .expect("the plain token's claims") - .generation; - let bound = signer.mint_for_audience( - &key.id, - 4_000_000_000, - generation.as_deref(), - &format!("{PUBLIC}/v1/realtime"), - None, - ); gov.hydrate_budgets(&cost, 0).expect("hydrate"); let node = Arc::new(Node::new()); let book = crate::root::durability::node_book_over(Box::new(|| CARD.pin())); @@ -750,7 +731,6 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { app, key, plain: plain.expose_secret().clone(), - bound, money, post, book, @@ -777,32 +757,32 @@ impl Streaming { } } -/// THE STREAMING DOOR'S MINT, SERVED (the `root-voice` leg, ARCHITECT 2026-10-05 Q1): an -/// audience-bound keyed caller's `POST /v1/realtime/client_secrets` is one unit the door carries -/// (ARRIVAL), admitted only for a token bound to the door's audience (AUTHENTICATE), dialled to the -/// destination the guard judged (VERIFY) on the DIRECT route to the catalog model -/// `streams.session.model` names (ROUTE), sent WITH THE PROVIDER'S OWN CREDENTIAL and never the -/// caller's token (EGRESS-AUTH), charged to the key that presented it (METER, GOVERNANCE-BUDGET), -/// and closed once with its one line on the node's book (AUDIT, EXIT). +/// THE STREAMING DOOR'S MINT, SERVED (the `root-voice` leg, ARCHITECT 2026-10-05 Q1): a keyed +/// caller's `POST /v1/realtime/client_secrets` is one unit the door carries (ARRIVAL), admitted for +/// the key its token resolves (AUTHENTICATE), dialled to the destination the guard judged (VERIFY) on +/// the DIRECT route to the catalog model `streams.session.model` names (ROUTE), sent WITH THE +/// PROVIDER'S OWN CREDENTIAL and never the caller's token (EGRESS-AUTH), charged to the key that +/// presented it (METER, GOVERNANCE-BUDGET), and closed once with its one line on the node's book +/// (AUDIT, EXIT). (The door's audience binding is the deployment's mount table's, judged end to end +/// by the `streams|mint|wrong-audience` oracle cell.) /// -/// RED ARM: the same caller presenting its PLAIN data-plane token (bound to no audience) is refused -/// before anything is dialled or charged. +/// RED ARM: the same request with no credential is refused before anything is dialled or charged. #[cfg(feature = "plane-streaming")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn a_streaming_mint_is_served_through_the_door_under_the_providers_credential() { let _one = PUBLISHING.lock().await; let mut s = streaming("serve-door-streaming", None).await; - let refused = send_body(&s.router, MINT, Some(&s.plain), "{}").await; + let refused = send_body(&s.router, MINT, None, "{}").await; assert_eq!( refused.status().as_u16(), u16::try_from(refusal_status(ReasonCode::Unauthenticated)).expect("a status"), - "a token bound to no audience is refused at the streaming door" + "an unkeyed caller is refused at the streaming door" ); assert!(s.heard.try_recv().is_err(), "nothing was dialled for it"); assert_eq!(s.requests(), 0, "nor charged"); - let response = send_body(&s.router, MINT, Some(&s.bound), "{}").await; + let response = send_body(&s.router, MINT, Some(&s.plain), "{}").await; assert_eq!(response.status(), StatusCode::OK, "served through the door"); let body = axum::body::to_bytes(response.into_body(), 1 << 16) .await @@ -822,8 +802,8 @@ async fn a_streaming_mint_is_served_through_the_door_under_the_providers_credent "the provider's own credential: {head}" ); assert!( - !head.contains(&s.bound) && !head.contains(&s.plain), - "no caller token reaches the provider: {head}" + !head.contains(&s.plain), + "the caller's token never reaches the provider: {head}" ); assert!( head.contains(r#""model":"m-cap""#), @@ -849,7 +829,7 @@ async fn a_streaming_mint_is_served_through_the_door_under_the_providers_credent async fn a_spent_key_is_refused_at_the_streaming_door_before_any_dial() { let _one = PUBLISHING.lock().await; let mut s = streaming("serve-door-streaming-spent", Some(0)).await; - let response = send_body(&s.router, MINT, Some(&s.bound), "{}").await; + let response = send_body(&s.router, MINT, Some(&s.plain), "{}").await; assert_eq!( response.status().as_u16(), 429, diff --git a/qa/teller-steps.json b/qa/teller-steps.json index a16de05ce4..57ba3c99fc 100644 --- a/qa/teller-steps.json +++ b/qa/teller-steps.json @@ -466,7 +466,7 @@ "state": "proven", "leg": "root-voice", "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", - "note": "on the streaming door's served leg: only a token bound to the door's audience is admitted to the mint; the same key's plain data-plane token is refused before any dial or charge" + "note": "on the streaming door's served leg: the mint is admitted for the key its token resolves, and the same request with no credential is refused before any dial or charge" } }, "verify": { diff --git a/xtask/tests/infra.rs b/xtask/tests/infra.rs index bb84f2d6a3..b3a10a3d7f 100644 --- a/xtask/tests/infra.rs +++ b/xtask/tests/infra.rs @@ -488,8 +488,8 @@ fn strip_comment_line_keeps_string_literals_intact() { #[test] fn the_plane_key_contract_matches_plane_keys_sh() { - assert_eq!(planes::PLANE_KEYS, ["llm", "mcp", "a2a", "voice"]); - assert_eq!(planes::plane_keys_protocol(), vec!["mcp", "a2a", "voice"]); + assert_eq!(planes::PLANE_KEYS, ["llm", "mcp", "a2a"]); + assert_eq!(planes::plane_keys_protocol(), vec!["mcp", "a2a"]); let src = planes::plane_src_roots(); assert_eq!(src[0], "crates/busbar-llm/src"); assert!(!src.contains(&"crates/busbar-llm-codec/src".to_string())); From 6c6f82c38d6f15ec947b82471edb6ca7343e5ace Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:38:27 -0700 Subject: [PATCH 48/82] FLIP-STREAMING: the root's session-door cells find the door by the audio class it meters (the composition root's sources name no plane instance); the config-schema reader follows a string constant written as another constant's path, within the declaring crate --- crates/busbar/src/root/serve.rs | 2 +- crates/busbar/src/root/tests/serve_door.rs | 103 ++++++++++++++------- qa/capability-equality.json | 6 +- qa/teller-steps.json | 16 ++-- xtask/src/gates/config_schema/declared.rs | 43 ++++++++- 5 files changed, 122 insertions(+), 48 deletions(-) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index f283239b9e..1b085ff7a6 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2681,7 +2681,7 @@ mod planes_tests; #[cfg(all( test, - any(feature = "plane-decisions", feature = "plane-streaming"), + any(feature = "plane-decisions", linked_axis_plane_door), linked_axis_node ))] #[path = "tests/serve_door.rs"] diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index 0a87554838..f6988b4004 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -522,20 +522,20 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { } /// The streaming door's mint claim. -#[cfg(feature = "plane-streaming")] +#[cfg(linked_axis_plane_door)] const MINT: &str = "/v1/realtime/client_secrets"; /// The provider's answer to the mint: an ephemeral secret and its expiry. -#[cfg(feature = "plane-streaming")] +#[cfg(linked_axis_plane_door)] const MINTED: &str = r#"{"value":"ek_door_0001","expires_at":1767225600}"#; /// The deployment's public base URL the streaming door fronts. -#[cfg(feature = "plane-streaming")] +#[cfg(linked_axis_plane_door)] const PUBLIC: &str = "http://gw.test"; /// The streaming door, served as production composes it, and what each `root-voice` cell reads. -#[cfg(feature = "plane-streaming")] -struct Streaming { +#[cfg(linked_axis_plane_door)] +struct SessionDoor { _published: Published, router: axum::Router, heard: tokio::sync::mpsc::UnboundedReceiver, @@ -549,16 +549,49 @@ struct Streaming { book: crate::root::durability::NodeBook, } -/// [`Streaming`]: the linked streaming door bound through the loader's one load, its needs declared +/// The billable class the session door meters a caller's audio under: how the harness finds that +/// door among the linked plane doors without spelling the plane. +#[cfg(linked_axis_plane_door)] +const AUDIO_CLASS: &str = "audio_seconds_in"; + +/// The linked plane door that meters audio (the session door), bound under `instance`; `None` in a +/// build that links none. +#[cfg(linked_axis_plane_door)] +fn session_door( + instance: &'static str, + dispatcher: &Arc, + conns: Arc, +) -> Option { + crate::LINKED.plane_doors.iter().find_map(|door| { + let row = LinkedRow::of(*door).expect("the door states its Statement"); + let plane = load_linked::( + &row, + Bind { + instance: Arc::from(instance), + max_inflight_cap: 64, + sink: Arc::new(NoSink), + dispatcher: dispatcher.adopter(), + conns: Some(Arc::clone(&conns)), + }, + ) + .expect("the linked door binds"); + plane + .served() + .billable_classes + .contains(&AUDIO_CLASS) + .then_some(plane) + }) +} + +/// [`SessionDoor`]: the linked session door (the plane door that meters audio) bound through the +/// loader's one load, its needs declared /// on the connector over every linked transport door; a signing governance book with one key (in a /// group whose all-time budget is `budget`, the door's per-request fee one, or no group and no fee); /// an OpenAI-protocol provider on a loopback far end answering the mint, reached through the catalog /// model `streams.session.model` names; the door composed under [`PUBLIC`] with its egress sealed by /// the composition itself, and its claims mounted on the data router. -#[cfg(feature = "plane-streaming")] -async fn streaming(instance: &'static str, budget: Option) -> Streaming { - use busbar_plane_streaming::door::{door as streaming_door, NAME as PLANE}; - +#[cfg(linked_axis_plane_door)] +async fn session_served(instance: &'static str, budget: Option) -> Option { let published = Published(instance); let (port, heard) = far_end_answering(MINTED).await; let judge = crate::root::connector::guard_for(&busbar_kernel::config::Destinations { @@ -580,18 +613,12 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { ) .expect("the connector builds"); let dispatcher = Arc::new(Dispatcher::new(DispatchConfig::default())); - let row = LinkedRow::of(streaming_door).expect("the door states its Statement"); - let plane = load_linked::( - &row, - Bind { - instance: Arc::from(instance), - max_inflight_cap: 64, - sink: Arc::new(NoSink), - dispatcher: dispatcher.adopter(), - conns: Some(Arc::clone(&connector) as Arc), - }, - ) - .expect("the linked door binds"); + let plane = session_door( + instance, + &dispatcher, + Arc::clone(&connector) as Arc, + )?; + let plane_name = plane.name().to_owned(); let section_key = plane.served().section; let groups: BTreeMap = budget @@ -620,7 +647,7 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { None => CostModel::flat(1), Some(_) => { let fees: busbar_kernel::config::PlaneFeesMap = [( - PLANE.to_string(), + plane_name.clone(), busbar_kernel_ledger::cost::PlaneFees { per_request: 1, per_session: 0, @@ -708,7 +735,7 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { }), None, ) - .expect("the streaming door composes, its egress sealed"); + .expect("the session door composes, its egress sealed"); let _ = std::fs::remove_file(&key_file); assert!( served.planes[0].live.current().egress.is_some(), @@ -723,7 +750,7 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { let doors = door_routes(served, || CARD.pin(), &[], &[]).expect("its claims mount"); let (router, _admin, _handle) = busbar_kernel::build_split_routers_serving(Arc::clone(&app), doors, 1 << 20, 0, false); - Streaming { + Some(SessionDoor { _published: published, router, heard, @@ -734,11 +761,11 @@ async fn streaming(instance: &'static str, budget: Option) -> Streaming { money, post, book, - } + }) } -#[cfg(feature = "plane-streaming")] -impl Streaming { +#[cfg(linked_axis_plane_door)] +impl SessionDoor { /// The requests the governance book admitted for the key, this window. fn requests(&self) -> u64 { self.gov @@ -767,17 +794,20 @@ impl Streaming { /// by the `streams|mint|wrong-audience` oracle cell.) /// /// RED ARM: the same request with no credential is refused before anything is dialled or charged. -#[cfg(feature = "plane-streaming")] +#[cfg(linked_axis_plane_door)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn a_streaming_mint_is_served_through_the_door_under_the_providers_credential() { +async fn a_session_door_mint_is_served_under_the_providers_credential() { let _one = PUBLISHING.lock().await; - let mut s = streaming("serve-door-streaming", None).await; + let Some(mut s) = session_served("serve-door-session", None).await else { + eprintln!("skip: this build links no session door"); + return; + }; let refused = send_body(&s.router, MINT, None, "{}").await; assert_eq!( refused.status().as_u16(), u16::try_from(refusal_status(ReasonCode::Unauthenticated)).expect("a status"), - "an unkeyed caller is refused at the streaming door" + "an unkeyed caller is refused at the session door" ); assert!(s.heard.try_recv().is_err(), "nothing was dialled for it"); assert_eq!(s.requests(), 0, "nor charged"); @@ -824,11 +854,14 @@ async fn a_streaming_mint_is_served_through_the_door_under_the_providers_credent /// at admission — over budget, before the provider is dialled, charged nothing, no line written — /// while a key with room is served (the mint cell above). RED: a door that dialled before admission /// would have reached the far end. -#[cfg(feature = "plane-streaming")] +#[cfg(linked_axis_plane_door)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn a_spent_key_is_refused_at_the_streaming_door_before_any_dial() { +async fn a_spent_key_is_refused_at_the_session_door_before_any_dial() { let _one = PUBLISHING.lock().await; - let mut s = streaming("serve-door-streaming-spent", Some(0)).await; + let Some(mut s) = session_served("serve-door-session-spent", Some(0)).await else { + eprintln!("skip: this build links no session door"); + return; + }; let response = send_body(&s.router, MINT, Some(&s.plain), "{}").await; assert_eq!( response.status().as_u16(), diff --git a/qa/capability-equality.json b/qa/capability-equality.json index 612d9deaa3..bf9f97f2d1 100644 --- a/qa/capability-equality.json +++ b/qa/capability-equality.json @@ -1110,7 +1110,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential" } }, { @@ -1120,7 +1120,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_streaming_door_before_any_dial" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_session_door_before_any_dial" } }, { @@ -1210,7 +1210,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential" + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential" } }, { diff --git a/qa/teller-steps.json b/qa/teller-steps.json index 57ba3c99fc..f756eae8e1 100644 --- a/qa/teller-steps.json +++ b/qa/teller-steps.json @@ -444,7 +444,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the browser's mint is one unit the door carries end to end, from the data router's claim to the provider's answer relayed to the caller" } }, @@ -465,7 +465,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint is admitted for the key its token resolves, and the same request with no credential is refused before any dial or charge" } }, @@ -476,7 +476,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint reaches the provider through the connector at the destination its guard judged, the catalog model's provider on loopback" } }, @@ -497,7 +497,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_streaming_door_before_any_dial", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_session_door_before_any_dial", "note": "on the streaming door's served leg: a key whose group budget is spent is refused 429 at admission, the provider never dialled and nothing charged" } }, @@ -508,7 +508,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint is routed DIRECT to the catalog model streams.session.model names, its locked session carrying that model to the provider" } }, @@ -519,7 +519,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the served mint is one request on the governance ledger of the key that presented it" } }, @@ -530,7 +530,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the served mint writes exactly one line on the node's book, the unit's one record" } }, @@ -541,7 +541,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_streaming_mint_is_served_through_the_door_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint's money facts and node facts close once at its end, nothing left open on either" } } diff --git a/xtask/src/gates/config_schema/declared.rs b/xtask/src/gates/config_schema/declared.rs index f7213d25f4..576e5a8cfe 100644 --- a/xtask/src/gates/config_schema/declared.rs +++ b/xtask/src/gates/config_schema/declared.rs @@ -67,6 +67,9 @@ fn spec() -> WalkSpec { #[derive(Clone, Default)] struct Facts { consts: Vec<(String, String)>, + /// String constants written as another constant's path (`const A: &str = config::SECTION;`), + /// resolved through the same reader once every file's literals are known. + aliases: Vec<(String, String)>, decls: Vec), String>>, core_owned: Option>, } @@ -98,6 +101,11 @@ fn facts(path: &str, raw: &str) -> Facts { } else if ty.contains("str") && !ty.contains('[') && value.len() >= 2 { if let Some(lit) = value.strip_prefix('"').and_then(|v| v.strip_suffix('"')) { out.consts.push((name, lit.to_string())); + } else if value + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':') + { + out.aliases.push((name, value.to_string())); } } } @@ -179,7 +187,9 @@ fn base(cx: &Ctx) -> Result, String> { for f in files { let rel = f.rel_str(); let facts = facts(&rel, &f.text); - if facts.consts.len() + facts.decls.len() > 0 || facts.core_owned.is_some() { + if facts.consts.len() + facts.aliases.len() + facts.decls.len() > 0 + || facts.core_owned.is_some() + { out.insert(rel, facts); } } @@ -241,6 +251,24 @@ fn resolve( if let Some(v) = found { return Ok(v); } + // A path through a module of the declaring file's own crate (`config::SECTION`): the crate's + // own definitions, when they agree. + if segs.len() > 1 { + if let Some(krate) = file + .strip_prefix(&format!("{ROOT}/")) + .and_then(|r| r.split_once('/')) + .map(|(k, _)| format!("{ROOT}/{k}/")) + { + let own: BTreeSet<&String> = defs + .iter() + .filter(|(p, _)| p.starts_with(&krate)) + .map(|(_, v)| v) + .collect(); + if own.len() == 1 { + return Ok(own.into_iter().next().cloned().unwrap_or_default()); + } + } + } let values: BTreeSet<&String> = defs.iter().map(|(_, v)| v).collect(); match values.len() { 1 => Ok(values.into_iter().next().cloned().unwrap_or_default()), @@ -307,6 +335,19 @@ pub fn read(cx: &Ctx) -> Result { .push((path.clone(), lit.clone())); } } + // An alias resolves to the literal its path names; one pass, an alias of a literal (an alias of + // an alias the reader cannot follow is left out, and a declaration naming it is then refused). + let mut resolved: Vec<(String, String, String)> = Vec::new(); + for (path, f) in &files { + for (name, target) in &f.aliases { + if let Ok(lit) = resolve(target, path, &consts) { + resolved.push((name.clone(), path.clone(), lit)); + } + } + } + for (name, path, lit) in resolved { + consts.entry(name).or_default().push((path, lit)); + } let core_owned: BTreeSet = files .values() .find_map(|f| f.core_owned.clone()) From 3e99851e29738108daee60a6300045b59f8a3195 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:40:03 -0700 Subject: [PATCH 49/82] WIP: voice conformance harness re-homed onto the streaming plane's door (linked + dropped, RED arm per assertion) --- crates/busbar-plane-streaming/Cargo.toml | 8 + .../examples/voice_conform/codec_legs.rs | 1292 ++++++++++++++++ .../examples/voice_conform/composition.rs | 1344 +++++++++++++++++ .../examples/voice_conform/door.rs | 754 +++++++++ .../examples/voice_conform/governance.rs | 334 ++++ .../examples/voice_conform/main.rs | 86 ++ 6 files changed, 3818 insertions(+) create mode 100644 crates/busbar-plane-streaming/examples/voice_conform/codec_legs.rs create mode 100644 crates/busbar-plane-streaming/examples/voice_conform/composition.rs create mode 100644 crates/busbar-plane-streaming/examples/voice_conform/door.rs create mode 100644 crates/busbar-plane-streaming/examples/voice_conform/governance.rs create mode 100644 crates/busbar-plane-streaming/examples/voice_conform/main.rs diff --git a/crates/busbar-plane-streaming/Cargo.toml b/crates/busbar-plane-streaming/Cargo.toml index 47e733c4e2..db4a50956d 100644 --- a/crates/busbar-plane-streaming/Cargo.toml +++ b/crates/busbar-plane-streaming/Cargo.toml @@ -72,3 +72,11 @@ busbar-plugin-loader = { path = "../plugin-loader" } [[example]] name = "streaming_door" crate-type = ["cdylib"] + +# THE VOICE CONFORMANCE HARNESS (`testing/voice-conformance/`, ARCHITECT Q6): a dev-only binary +# that drives the door above BOTH ways — linked, and the `streaming_door` cdylib dlopened — through +# the loader, over the dev-dependencies only. Never shipped; `cargo build --example voice_conform +# --example streaming_door` builds the pair the rig's legs run. +[[example]] +name = "voice_conform" +path = "examples/voice_conform/main.rs" diff --git a/crates/busbar-plane-streaming/examples/voice_conform/codec_legs.rs b/crates/busbar-plane-streaming/examples/voice_conform/codec_legs.rs new file mode 100644 index 0000000000..a6cf4cbd16 --- /dev/null +++ b/crates/busbar-plane-streaming/examples/voice_conform/codec_legs.rs @@ -0,0 +1,1292 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE FIXTURE LEGS — `spec-per-dialect`, `replay`, `cross-parity` — over the plane's own codecs +//! (`busbar_plane_streaming::codec`, the module the door's sessions run) AND through the door's +//! session pieces. +//! +//! Each fixture is judged twice. The CODEC half is the old leg, unchanged: wire → IR → wire → IR +//! must be stable (spec), a captured transcript must re-derive its concept skeleton (replay), a +//! concept bridged A → IR → B → IR must keep its load-bearing fields (cross). The DOOR half pushes +//! the same wire bytes (for cross: the bridged B wire) through a live session on the door that +//! speaks the dialect — the caller's frames as caller pieces, the far end's as far-end pieces — on +//! the linked and the dropped door, and holds what the door relays to the SESSION RULES the plane +//! states (`session_pump`): a caller frame reaches the far end as it arrived, except that the +//! caller's session configuration is replaced by the locked one; a far-end frame reaches the caller +//! as it arrived, except that usage and rate-limit reports are consumed (usage becomes reported +//! units), a far-end tool RESULT is never relayed, and a barge-in also cancels and truncates the +//! far end's response at the audio the caller heard. Compared on the correlation fingerprint, the +//! same rule the codec half uses. The expectation is computed from the wire by the codec, never by +//! the door. + +use std::collections::BTreeMap; +use std::fs; +use std::path::Path; + +use busbar_contract::abi::mechanism::call::Outcome; +use busbar_contract::abi::plane::{FROM_CALLER, FROM_FAR_END, UNITS_REPORTED}; +use busbar_plane_streaming::codec::ir::{ + DecodeState, DuplexReader, DuplexWriter, GeminiLiveCodec, IrClientEvent, IrDuplexControl, + IrDuplexTool, IrServerEvent, OpenAiRealtimeCodec, SessionConfig, WireEvent, +}; +use busbar_plane_streaming::door::{read_settings, RIDES_LIVE_SOCKET, RIDES_REALTIME_SOCKET}; +use busbar_plane_streaming::driven::{GEMINI_SOCKET_PATH, REALTIME_SOCKET_PATH}; +use busbar_plane_streaming::session_door::PER_SESSION_CLASS; +use busbar_plugin_loader::dispatch::kinds::plane::Plane; +use busbar_plugin_loader::dispatch::Plugin; +use bytes::Bytes; +use serde_json::Value; + +use crate::door::{judge, open, result, Log, Rig, Session, PUBLIC, SESSION}; + +// ── the two dialects ──────────────────────────────────────────────────────────────────────────── + +/// A duplex dialect the plane dials, and the session door that speaks it. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Dialect { + /// OpenAI Realtime: the browser's sideband socket (claim 2). + OpenAi, + /// Gemini Live: the Gemini socket (claim 3). + Gemini, +} + +impl Dialect { + /// The dialect a leg names. + pub fn of(name: &str) -> Option { + match name { + "openai" => Some(Dialect::OpenAi), + "gemini" => Some(Dialect::Gemini), + _ => None, + } + } + + /// Its name in the legs. + pub const fn name(self) -> &'static str { + match self { + Dialect::OpenAi => "openai", + Dialect::Gemini => "gemini", + } + } + + /// The door claim that opens a session in it. + pub const fn claim(self) -> u32 { + match self { + Dialect::OpenAi => 2, + Dialect::Gemini => 3, + } + } + + /// The declared need its far frames ride. + pub const fn ride(self) -> u32 { + match self { + Dialect::OpenAi => RIDES_REALTIME_SOCKET, + Dialect::Gemini => RIDES_LIVE_SOCKET, + } + } + + /// The far end's socket, under the member's base URL. + pub const fn socket(self) -> &'static str { + match self { + Dialect::OpenAi => REALTIME_SOCKET_PATH, + Dialect::Gemini => GEMINI_SOCKET_PATH, + } + } + + /// Read a caller frame. + pub fn read_up(self, frame: &[u8], st: &mut DecodeState) -> Vec { + let w = WireEvent(Bytes::copy_from_slice(frame)); + match self { + Dialect::OpenAi => OpenAiRealtimeCodec.read_up(w, st), + Dialect::Gemini => GeminiLiveCodec.read_up(w, st), + } + } + + /// Read a far-end frame. + pub fn read_down(self, frame: &[u8], st: &mut DecodeState) -> Vec { + let w = WireEvent(Bytes::copy_from_slice(frame)); + match self { + Dialect::OpenAi => OpenAiRealtimeCodec.read_down(w, st), + Dialect::Gemini => GeminiLiveCodec.read_down(w, st), + } + } + + /// Frame a caller event; `None` when the dialect has no verb for it. + pub fn write_up(self, ev: IrClientEvent, st: &mut DecodeState) -> Option { + match self { + Dialect::OpenAi => OpenAiRealtimeCodec.write_up(ev, st), + Dialect::Gemini => GeminiLiveCodec.write_up(ev, st), + } + } + + /// Frame a far-end event; `None` when it is not a frame on its own. + pub fn write_down(self, ev: IrServerEvent, st: &mut DecodeState) -> Option { + match self { + Dialect::OpenAi => OpenAiRealtimeCodec.write_down(ev, st), + Dialect::Gemini => GeminiLiveCodec.write_down(ev, st), + } + } +} + +fn bytes_of(v: &Value) -> Vec { + serde_json::to_vec(v).expect("serialize wire value") +} + +// ── the shared normal form (the old harness's, verbatim in meaning) ───────────────────────────── + +/// Both direction-split IR enums collapsed onto one list, so concepts compare uniformly. +#[derive(Clone, Debug, PartialEq)] +pub enum Norm { + /// Config essentials only: instructions|voice|tools|max (the survivable fields). + Config(String), + /// The configured modalities. + ConfigModalities(Vec), + /// The session's acknowledgement. + Connect, + /// Uplink audio. + AudioUp(Vec), + /// Downlink audio. + AudioDown(Vec), + /// An item's audio is done. + AudioDone, + /// A conversation item. + Item(Value), + /// Speech began. + SpeechStart, + /// Speech stopped. + SpeechStop, + /// A truncate, at the ms heard. + Truncate(u64), + /// Commit the uplink. + Commit, + /// Clear the uplink. + Clear, + /// Ask for a response. + ResponseCreate, + /// Cancel the response. + ResponseCancel, + /// Delete an item. + ItemDelete, + /// Usage: audio in, audio out, text in, text out. + Usage(u64, u64, u64, u64), + /// A rate-limit report. + RateLimits, + /// An error: code, message. + Error(String, String), + /// A call opened: id, name. + ToolOpen(String, String), + /// A call's arguments: id, parsed. + ToolArgs(String, Value), + /// A call closed: id. + ToolClose(String), + /// A call's result: id, parsed output. + ToolResult(String, Value), +} + +fn cfg_essentials(config: &SessionConfig) -> Norm { + Norm::Config(format!( + "{:?}|{:?}|{}|{:?}", + config.instructions, + config.voice, + config.tools.len(), + config.max_output_tokens + )) +} + +fn norm_tool(t: &IrDuplexTool) -> Norm { + match t { + IrDuplexTool::CallOpen { call_id, name, .. } => { + Norm::ToolOpen(call_id.clone(), name.clone()) + } + IrDuplexTool::CallArgs { + call_id, + json_delta, + .. + } => Norm::ToolArgs( + call_id.clone(), + serde_json::from_slice(json_delta).unwrap_or(Value::Null), + ), + IrDuplexTool::CallClose { call_id, .. } => Norm::ToolClose(call_id.clone()), + IrDuplexTool::CallResult { + call_id, output, .. + } => Norm::ToolResult( + call_id.clone(), + serde_json::from_slice(output).unwrap_or(Value::Null), + ), + } +} + +/// Caller events, normalized. +pub fn norm_up(evs: &[IrClientEvent]) -> Vec { + let mut out = Vec::new(); + for e in evs { + match e { + IrClientEvent::AudioFrame(f) => out.push(Norm::AudioUp(f.media.to_vec())), + IrClientEvent::Tool(t) => out.push(norm_tool(t)), + IrClientEvent::Control(c) => match c { + IrDuplexControl::SessionConfigure { config } => { + out.push(cfg_essentials(config)); + out.push(Norm::ConfigModalities(config.modalities.clone())); + } + IrDuplexControl::ItemCreate { item } => out.push(Norm::Item(item.clone())), + IrDuplexControl::ItemTruncate { + audio_played_ms, .. + } => out.push(Norm::Truncate(*audio_played_ms)), + IrDuplexControl::InputAudioCommit => out.push(Norm::Commit), + IrDuplexControl::InputAudioClear => out.push(Norm::Clear), + IrDuplexControl::ResponseCreate { .. } => out.push(Norm::ResponseCreate), + IrDuplexControl::ResponseCancel => out.push(Norm::ResponseCancel), + IrDuplexControl::ItemDelete { .. } => out.push(Norm::ItemDelete), + }, + } + } + out +} + +/// Far-end events, normalized. +pub fn norm_down(evs: &[IrServerEvent]) -> Vec { + let mut out = Vec::new(); + for e in evs { + match e { + IrServerEvent::SessionCreated { .. } => out.push(Norm::Connect), + IrServerEvent::Tool(t) => out.push(norm_tool(t)), + IrServerEvent::SpeechStarted { .. } => out.push(Norm::SpeechStart), + IrServerEvent::SpeechStopped { .. } => out.push(Norm::SpeechStop), + IrServerEvent::AudioFrame(f) => out.push(Norm::AudioDown(f.media.to_vec())), + IrServerEvent::AudioDone { .. } => out.push(Norm::AudioDone), + IrServerEvent::Usage(u) => { + out.push(Norm::Usage(u.audio_in, u.audio_out, u.text_in, u.text_out)); + } + IrServerEvent::RateLimits => out.push(Norm::RateLimits), + IrServerEvent::Error { code, message } => { + out.push(Norm::Error(code.clone(), message.clone())); + } + } + } + out +} + +/// One correlated tool call, keyed by call id: `(name, args, result)`. +type CallEntry = (Option, Option, Option); + +/// A call-collapsed fingerprint: tool events merged by call id (an atomic Gemini `toolCall` +/// decodes to a streamed triple and the stateless writer re-frames per event, so arity differs +/// legitimately; the correlation must survive). Everything else compared verbatim, in order. +#[derive(Debug, Default, PartialEq)] +pub struct Fingerprint { + calls: BTreeMap, + other: Vec, +} + +/// The fingerprint of `norms`. +pub fn fingerprint(norms: &[Norm]) -> Fingerprint { + let mut fp = Fingerprint::default(); + for n in norms { + match n { + Norm::ToolOpen(id, name) => { + let e = fp.calls.entry(id.clone()).or_default(); + if e.0.is_none() && !name.is_empty() { + e.0 = Some(name.clone()); + } + } + Norm::ToolArgs(id, args) => { + let e = fp.calls.entry(id.clone()).or_default(); + if e.1.is_none() && !args.is_null() { + e.1 = Some(args.clone()); + } + } + Norm::ToolClose(id) => { + fp.calls.entry(id.clone()).or_default(); + } + Norm::ToolResult(id, out) => { + let e = fp.calls.entry(id.clone()).or_default(); + if e.2.is_none() { + e.2 = Some(out.clone()); + } + } + other => fp.other.push(other.clone()), + } + } + fp +} + +/// A concept's tag, for the replay skeleton. +fn tag(n: &Norm) -> &'static str { + match n { + Norm::Config(_) => "config", + Norm::ConfigModalities(_) => "config-modalities", + Norm::Connect => "connect", + Norm::AudioUp(_) => "audio-in", + Norm::AudioDown(_) => "audio-out", + Norm::AudioDone => "audio-done", + Norm::Item(_) => "item", + Norm::SpeechStart => "speech-start", + Norm::SpeechStop => "speech-stop", + Norm::Truncate(_) => "truncate", + Norm::Commit => "commit", + Norm::Clear => "clear", + Norm::ResponseCreate => "response-create", + Norm::ResponseCancel => "cancel", + Norm::ItemDelete => "item-delete", + Norm::Usage(..) => "usage", + Norm::RateLimits => "rate-limits", + Norm::Error(..) => "error", + Norm::ToolOpen(..) => "tool-open", + Norm::ToolArgs(..) => "tool-args", + Norm::ToolClose(..) => "tool-close", + Norm::ToolResult(..) => "tool-result", + } +} + +/// `expected` appears, in order, as a subsequence of `tags`. +fn ordered_subsequence(tags: &[&str], expected: &[&str]) -> Result<(), String> { + let mut i = 0; + for want in expected { + match tags[i..].iter().position(|t| t == want) { + Some(off) => i += off + 1, + None => { + return Err(format!( + "expected concept '{want}' not found after position {i} in {tags:?}" + )) + } + } + } + Ok(()) +} + +// ── wires, and what a session does with them ──────────────────────────────────────────────────── + +/// Which side of the session a frame comes from. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Dir { + /// The caller. + Up, + /// The far end. + Down, +} + +/// One frame pushed to a session. +#[derive(Clone, Debug)] +pub struct Wire { + /// Its side. + pub dir: Dir, + /// Its bytes. + pub bytes: Vec, +} + +/// Drive `wires` through ONE session on `p`'s door for `dialect`. +pub fn relay(p: &Plugin, dialect: Dialect, wires: &[Wire]) -> Log { + let opened = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 77, dialect.claim()); + if opened.outcome != Outcome::Ready { + s.log.anomalies.push(format!("open {:?}", opened.outcome)); + } + for w in wires { + let from = match w.dir { + Dir::Up => FROM_CALLER, + Dir::Down => FROM_FAR_END, + }; + s.push(from, 0, &w.bytes); + } + s.log +} + +/// What the session rules say the door must relay for a sequence of wires. +pub struct Expected { + far: Vec, + caller: Vec, + tokens: Vec<(u32, u64)>, + answered: bool, +} + +/// The locked config, as it reads on the dialect's wire. +fn locked_norms(dialect: Dialect, locked: &SessionConfig) -> Vec { + let (mut w, mut r) = (DecodeState::default(), DecodeState::default()); + let configure = IrClientEvent::Control(IrDuplexControl::SessionConfigure { + config: locked.clone(), + }); + match dialect.write_up(configure, &mut w) { + Some(wire) => norm_up(&dialect.read_up(&wire.0, &mut r)), + None => Vec::new(), + } +} + +/// A barge-in's cancel and truncate, at `heard_ms` of `item`, as they read on the dialect's wire +/// (a dialect with no verb for them frames nothing). +fn barge_norms(dialect: Dialect, item: &str, heard_ms: u64) -> Vec { + let (mut w, mut r) = (DecodeState::default(), DecodeState::default()); + let mut out = Vec::new(); + for ev in [ + IrClientEvent::Control(IrDuplexControl::ResponseCancel), + IrClientEvent::Control(IrDuplexControl::ItemTruncate { + item_ref: item.to_string(), + content_index: 0, + audio_played_ms: heard_ms, + }), + ] { + if let Some(wire) = dialect.write_up(ev, &mut w) { + out.extend(norm_up(&dialect.read_up(&wire.0, &mut r))); + } + } + out +} + +/// The session rules over `wires`, read by the codec (never by the door). +pub fn expect(dialect: Dialect, wires: &[Wire]) -> Expected { + let locked = read_settings(SESSION) + .expect("the leg's section reads") + .session; + let locked = locked_norms(dialect, &locked); + let mut st = DecodeState::default(); + let mut x = Expected { + far: Vec::new(), + caller: Vec::new(), + tokens: Vec::new(), + answered: false, + }; + let mut sums = [0_u64; 4]; + for w in wires { + match w.dir { + Dir::Up => { + for ev in dialect.read_up(&w.bytes, &mut st) { + match ev { + IrClientEvent::Control(IrDuplexControl::SessionConfigure { .. }) => { + x.far.extend(locked.iter().cloned()); + } + ev => x.far.extend(norm_up(std::slice::from_ref(&ev))), + } + } + } + Dir::Down => { + x.answered = true; + for ev in dialect.read_down(&w.bytes, &mut st) { + match ev { + IrServerEvent::Usage(u) => { + for (sum, n) in sums.iter_mut().zip([ + u.audio_in, + u.audio_out, + u.text_in, + u.text_out, + ]) { + *sum = sum.saturating_add(n); + } + } + IrServerEvent::RateLimits + | IrServerEvent::Tool(IrDuplexTool::CallResult { .. }) => {} + IrServerEvent::SpeechStarted { item_id, .. } => { + let heard = st.flush_playback(); + x.far.extend(barge_norms(dialect, &item_id, heard)); + x.caller.push(Norm::SpeechStart); + } + ev => x.caller.extend(norm_down(std::slice::from_ref(&ev))), + } + } + } + } + } + x.tokens = (0_u32..).zip(sums).filter(|(_, n)| *n != 0).collect(); + x +} + +/// What the door relayed, read back by the codec: the far end's frames, the caller's, and every +/// concept's tag in the order the door emitted it. +pub struct Relayed { + far: Vec, + caller: Vec, + tags: Vec<&'static str>, +} + +/// Read `log`'s frames back under `dialect`. +pub fn decode_relay(dialect: Dialect, log: &Log) -> Relayed { + let (mut su, mut sd) = (DecodeState::default(), DecodeState::default()); + let mut r = Relayed { + far: Vec::new(), + caller: Vec::new(), + tags: Vec::new(), + }; + for e in &log.emissions { + let n = if e.to_far { + norm_up(&dialect.read_up(&e.frame, &mut su)) + } else { + norm_down(&dialect.read_down(&e.frame, &mut sd)) + }; + r.tags.extend(n.iter().map(tag)); + if e.to_far { + r.far.extend(n); + } else { + r.caller.extend(n); + } + } + r +} + +/// The door's relay of a session against the session rules: every answer READY, no record row, +/// every far frame on the dialect's socket need, both sides' frames on the expected fingerprint, +/// the far end's token reports as reported units (and the session fee once the far end answered). +pub fn relay_check(dialect: Dialect, log: &Log, x: &Expected) -> Result { + if !log.anomalies.is_empty() { + return Err(format!( + "the door did not answer every piece READY: {:?}", + log.anomalies + )); + } + if log.records != 0 { + return Err(format!( + "the door wrote {} record row(s) on a session", + log.records + )); + } + for e in log.emissions.iter().filter(|e| e.to_far) { + if e.need != dialect.ride() || e.verb != "GET" || e.target != dialect.socket() { + return Err(format!( + "a far frame rode need {} to {} {}, not the {} socket (need {})", + e.need, + e.verb, + e.target, + dialect.name(), + dialect.ride() + )); + } + } + let r = decode_relay(dialect, log); + if fingerprint(&r.far) != fingerprint(&x.far) { + return Err(format!( + "the far end was sent {:?}; the session rules say {:?}", + r.far, x.far + )); + } + if fingerprint(&r.caller) != fingerprint(&x.caller) { + return Err(format!( + "the caller was sent {:?}; the session rules say {:?}", + r.caller, x.caller + )); + } + if let Some(u) = log.units.iter().find(|u| u.1 != UNITS_REPORTED) { + return Err(format!("a unit was not reported by the far end: {u:?}")); + } + let tokens: Vec<(u32, u64)> = log + .units + .iter() + .filter(|u| u.0 < 4) + .map(|u| (u.0, u.2)) + .collect(); + if tokens != x.tokens { + return Err(format!( + "the token units reported {tokens:?}; the far end's reports add up to {:?}", + x.tokens + )); + } + let fee = log + .units + .iter() + .any(|u| u.0 == PER_SESSION_CLASS && u.2 == 1); + if fee != x.answered { + return Err(format!( + "the session fee unit reported {fee}, the far end answered {}", + x.answered + )); + } + Ok(format!( + "the {} door relayed {} far / {} caller concept(s) by the session rules, {} token unit(s)", + dialect.name(), + r.far.len(), + r.caller.len(), + tokens.len() + )) +} + +/// THE PLANTED WRONG DOOR for a relay: one that reports a token the far end never sent. +pub fn plant_units(log: &Log) -> Log { + let mut planted = log.clone(); + planted.units.push((1, UNITS_REPORTED, 7)); + planted +} + +/// Judge one relay of `wires` on both doors, with `codec` (the codec half's verdict) folded in. +fn judge_relay( + rig: &Rig, + slice: &str, + what: &str, + dialect: Dialect, + wires: &[Wire], + codec: &Result, +) -> bool { + let x = expect(dialect, wires); + let seen = rig.both(|p| relay(p, dialect, wires)); + judge( + slice, + what, + &seen, + &|log: &Log| { + let codec = codec.clone()?; + let door = relay_check(dialect, log, &x)?; + Ok(format!("codec: {codec}; door: {door}")) + }, + &plant_units, + ) +} + +// ══════════════════════════════════════════════════════════════════════════════════════════════ +// spec-per-dialect +// ══════════════════════════════════════════════════════════════════════════════════════════════ + +/// Fixtures that legitimately decode to NOTHING, with the reason (the codec's documented drops). +fn drop_reason(dialect: Dialect, fixture: &str) -> Option<&'static str> { + match (dialect, fixture) { + (Dialect::Gemini, "goAway.json") => { + Some("gemini_go_away: no OpenAI advance-disconnect twin (drop+warn)") + } + (Dialect::Gemini, "toolCallCancellation.json") => { + Some("gemini_tool_call_cancellation: no OpenAI server-driven cancel (drop+warn)") + } + (Dialect::Gemini, "serverContent.inputTranscription.json") => { + Some("input transcription side-channel: no shared IR home (drop+warn)") + } + (Dialect::Gemini, "serverContent.outputTranscription.json") => { + Some("output transcription side-channel: no shared IR home (drop+warn)") + } + _ => None, + } +} + +/// A decoded fixture: the direction the codec recognized it in, and its IR. +enum Decoded { + Up(Vec), + Down(Vec), + Empty, +} + +fn decode(dialect: Dialect, frame: &[u8]) -> Decoded { + let up = dialect.read_up(frame, &mut DecodeState::default()); + if !up.is_empty() { + return Decoded::Up(up); + } + let down = dialect.read_down(frame, &mut DecodeState::default()); + if !down.is_empty() { + return Decoded::Down(down); + } + Decoded::Empty +} + +fn spec_verdict(n1: &[Norm], n2: &[Norm], arity: usize) -> Result { + if n1 == n2 { + return Ok(format!("IR-fixpoint stable ({arity} IR event(s))")); + } + if fingerprint(n1) == fingerprint(n2) { + return Ok(format!( + "IR-fixpoint stable by correlation fingerprint (atomic expansion, {arity} event(s))" + )); + } + Err(format!("round-trip diverged: {n1:?} != {n2:?}")) +} + +/// The codec half of one fixture, and the direction its frame is pushed from. +fn spec_codec(dialect: Dialect, fixture: &str, frame: &[u8]) -> (Result, Dir) { + match decode(dialect, frame) { + Decoded::Up(ir1) => { + let (mut w, mut r) = (DecodeState::default(), DecodeState::default()); + let mut ir2 = Vec::new(); + for e in &ir1 { + if let Some(wire) = dialect.write_up(e.clone(), &mut w) { + ir2.extend(dialect.read_up(&wire.0, &mut r)); + } + } + ( + spec_verdict(&norm_up(&ir1), &norm_up(&ir2), ir1.len()), + Dir::Up, + ) + } + Decoded::Down(ir1) => { + let (mut w, mut r) = (DecodeState::default(), DecodeState::default()); + let mut ir2 = Vec::new(); + for e in &ir1 { + if let Some(wire) = dialect.write_down(e.clone(), &mut w) { + ir2.extend(dialect.read_down(&wire.0, &mut r)); + } + } + ( + spec_verdict(&norm_down(&ir1), &norm_down(&ir2), ir1.len()), + Dir::Down, + ) + } + // A documented drop is a far-end concept with no shared home: pushed from the far end, the + // door must carry it on no wire at all. + Decoded::Empty => match drop_reason(dialect, fixture) { + Some(reason) => (Ok(format!("documented drop — {reason}")), Dir::Down), + None => ( + Err("decoded to NO IR events and is not a documented drop".to_string()), + Dir::Down, + ), + }, + } +} + +/// `spec `: every fixture, codec half and door half. +pub fn spec(rig: &Rig, dialect_name: &str, dir: &Path) -> i32 { + let Some(dialect) = Dialect::of(dialect_name) else { + result(dialect_name, false, "dialect", "unknown dialect"); + return 1; + }; + let mut fixtures: Vec = match fs::read_dir(dir) { + Ok(rd) => rd + .filter_map(Result::ok) + .map(|e| e.file_name().to_string_lossy().to_string()) + .filter(|n| n.ends_with(".json")) + .collect(), + Err(e) => { + result( + dialect.name(), + false, + "fixtures", + &format!("{}: {e}", dir.display()), + ); + return 1; + } + }; + fixtures.sort(); + if fixtures.is_empty() { + result(dialect.name(), false, "fixtures", "no .json fixtures"); + return 1; + } + let mut fails = 0; + for f in &fixtures { + let frame = match fs::read(dir.join(f)) + .map_err(|e| e.to_string()) + .and_then(|b| { + serde_json::from_slice::(&b) + .map(|v| bytes_of(&v)) + .map_err(|e| e.to_string()) + }) { + Ok(frame) => frame, + Err(e) => { + result( + dialect.name(), + false, + f, + &format!("unreadable fixture: {e}"), + ); + fails += 1; + continue; + } + }; + let (codec, side) = spec_codec(dialect, f, &frame); + let wires = [Wire { + dir: side, + bytes: frame, + }]; + if !judge_relay(rig, dialect.name(), f, dialect, &wires, &codec) { + fails += 1; + } + } + i32::from(fails > 0) +} + +// ══════════════════════════════════════════════════════════════════════════════════════════════ +// replay +// ══════════════════════════════════════════════════════════════════════════════════════════════ + +fn read_jsonl(path: &Path) -> Result, String> { + let text = fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?; + text.lines() + .filter(|l| !l.trim().is_empty()) + .map(|l| serde_json::from_str(l).map_err(|e| format!("{}: {e}", path.display()))) + .collect() +} + +/// A transcript's frames, as the session saw them (client → caller, server → far end). +fn transcript_wires(lines: &[Value]) -> Vec { + lines + .iter() + .filter_map(|line| { + let ev = line.get("event")?; + let dir = match line.get("dir").and_then(Value::as_str) { + Some("client") => Dir::Up, + Some("server") => Dir::Down, + _ => return None, + }; + Some(Wire { + dir, + bytes: bytes_of(ev), + }) + }) + .collect() +} + +/// The codec half of a replay: one session state, every frame decoded in order, every decoded +/// event re-framed to valid wire JSON, and the skeleton in order. +fn replay_codec(dialect: Dialect, wires: &[Wire], skeleton: &[&str]) -> Result { + let (mut st, mut wst) = (DecodeState::default(), DecodeState::default()); + let (mut tags, mut decoded, mut reencoded) = (Vec::new(), 0_usize, 0_usize); + let valid = |w: Option| { + w.is_some_and(|w| serde_json::from_slice::(&w.0).is_ok_and(|v| !v.is_null())) + }; + for w in wires { + match w.dir { + Dir::Up => { + let irs = dialect.read_up(&w.bytes, &mut st); + for ir in &irs { + decoded += 1; + reencoded += usize::from(valid(dialect.write_up(ir.clone(), &mut wst))); + } + tags.extend(norm_up(&irs).iter().map(tag)); + } + Dir::Down => { + let irs = dialect.read_down(&w.bytes, &mut st); + for ir in &irs { + decoded += 1; + reencoded += usize::from(valid(dialect.write_down(ir.clone(), &mut wst))); + } + tags.extend(norm_down(&irs).iter().map(tag)); + } + } + } + ordered_subsequence(&tags, skeleton)?; + Ok(format!( + "{decoded} IR events, {reencoded} re-framed, skeleton {skeleton:?} in order" + )) +} + +/// `replay `: each dialect's captured transcript, codec half and door half. +pub fn replay(rig: &Rig, root: &Path) -> i32 { + // The skeletons the codec re-derives; the door's relay must show the same concepts in the + // order it emitted them, less the far end's usage, which a session consumes into units. + let cases: [(Dialect, &[&str], &[&str]); 2] = [ + ( + Dialect::OpenAi, + &[ + "config", + "connect", + "audio-in", + "speech-start", + "tool-args", + "tool-close", + "tool-result", + "audio-out", + "speech-start", + "cancel", + ], + &[ + "config", + "connect", + "audio-in", + "speech-start", + "tool-args", + "tool-close", + "tool-result", + "audio-out", + "speech-start", + "cancel", + ], + ), + ( + Dialect::Gemini, + &[ + "config", + "connect", + "audio-in", + "tool-open", + "tool-args", + "tool-close", + "tool-result", + "audio-out", + "speech-start", + "audio-done", + "usage", + ], + &[ + "config", + "connect", + "audio-in", + "tool-open", + "tool-args", + "tool-close", + "tool-result", + "audio-out", + "speech-start", + "audio-done", + ], + ), + ]; + let mut fails = 0; + for (dialect, codec_skeleton, door_skeleton) in cases { + let what = format!("{}-transcript", dialect.name()); + let lines = match read_jsonl(&root.join(dialect.name()).join("transcript.jsonl")) { + Ok(lines) => lines, + Err(e) => { + result("default", false, &what, &e); + fails += 1; + continue; + } + }; + let wires = transcript_wires(&lines); + let codec = replay_codec(dialect, &wires, codec_skeleton); + let x = expect(dialect, &wires); + let seen = rig.both(|p| relay(p, dialect, &wires)); + let pass = judge( + "default", + &what, + &seen, + &|log: &Log| { + let codec = codec.clone()?; + let door = relay_check(dialect, log, &x)?; + ordered_subsequence(&decode_relay(dialect, log).tags, door_skeleton)?; + Ok(format!( + "codec: {codec}; door: {door}, skeleton {door_skeleton:?} in emitted order" + )) + }, + &plant_units, + ); + if !pass { + fails += 1; + } + } + i32::from(fails > 0) +} + +// ══════════════════════════════════════════════════════════════════════════════════════════════ +// cross-parity +// ══════════════════════════════════════════════════════════════════════════════════════════════ + +/// One bridge: the source's norms, the bridged-and-re-read norms, and the bridged wires (the B +/// dialect's frames, pushed through B's door). +struct Bridged { + n1: Vec, + n2: Vec, + wires: Vec, +} + +/// Bridge the frames `vals` FROM `a` TO `b` as one exchange (one source session, one destination). +fn bridge(a: Dialect, b: Dialect, vals: &[Vec]) -> Bridged { + let (mut rst, mut wst, mut bst) = ( + DecodeState::default(), + DecodeState::default(), + DecodeState::default(), + ); + let mut out = Bridged { + n1: Vec::new(), + n2: Vec::new(), + wires: Vec::new(), + }; + for v in vals { + let up = a.read_up(v, &mut rst); + if !up.is_empty() { + out.n1.extend(norm_up(&up)); + for e in up { + if let Some(w) = b.write_up(e, &mut wst) { + out.n2.extend(norm_up(&b.read_up(&w.0, &mut bst))); + out.wires.push(Wire { + dir: Dir::Up, + bytes: w.0.to_vec(), + }); + } + } + continue; + } + let down = a.read_down(v, &mut rst); + out.n1.extend(norm_down(&down)); + for e in down { + if let Some(w) = b.write_down(e, &mut wst) { + out.n2.extend(norm_down(&b.read_down(&w.0, &mut bst))); + out.wires.push(Wire { + dir: Dir::Down, + bytes: w.0.to_vec(), + }); + } + } + } + out +} + +fn pair_slice(a: Dialect, b: Dialect) -> &'static str { + match (a, b) { + (Dialect::OpenAi, Dialect::OpenAi) => "oo", + (Dialect::OpenAi, Dialect::Gemini) => "og", + (Dialect::Gemini, Dialect::OpenAi) => "go", + (Dialect::Gemini, Dialect::Gemini) => "gg", + } +} + +/// Why a concept has no decoding source fixture (never faked as a pass). +fn none_reason(concept: &str, from: Dialect) -> &'static str { + match (concept, from) { + ("input turn commit / end-of-audio", Dialect::Gemini) => { + "documented drop — gemini audioStreamEnd is dropped by the codec" + } + ("server-side VAD speech boundary", Dialect::Gemini) => { + "dialect-only concept — Gemini has no explicit server-VAD boundary fixture; exercised \ + as the openai_speech_boundary asymmetry in og" + } + ("input transcription" | "output transcription", _) => { + "documented side-channel drop+warn — no shared IR home; the same-dialect round-trip \ + is exercised in spec-per-dialect" + } + _ => "no decoding fixture on the source side (exercised in the reverse pair)", + } +} + +/// Concepts the map documents as dropping toward `to` (implicit or absent there). +fn directional_drop(concept: &str, to: Dialect) -> bool { + matches!( + (concept, to), + ("input turn commit / end-of-audio", Dialect::Gemini) + | ("barge-in / truncate / cancel", Dialect::Gemini) + ) +} + +fn cfg_from(norms: &[Norm]) -> Vec { + norms + .iter() + .filter_map(|n| match n { + Norm::Config(s) => Some(s.clone()), + _ => None, + }) + .collect() +} + +/// Do the shared concept's load-bearing fields survive `a` → IR → `b` → IR? +fn survives( + concept: &str, + n1: &[Norm], + n2: &[Norm], + a: Dialect, + b: Dialect, +) -> Result { + let (f1, f2) = (fingerprint(n1), fingerprint(n2)); + let (an, bn) = (a.name(), b.name()); + if f1 == f2 { + return Ok(format!("shared fields survive {an}→{bn} ({concept})")); + } + if concept.contains("session") && cfg_from(n1) == cfg_from(n2) && !cfg_from(n1).is_empty() { + return Ok(format!( + "session config essentials survive {an}→{bn} (modalities-text/VAD/format dropped per map)" + )); + } + if f2.calls.is_empty() && f2.other.is_empty() && directional_drop(concept, b) { + return Ok(format!( + "{concept}: documented directional drop {an}→{bn} (implicit / no counterpart per map)" + )); + } + Err(format!( + "{concept}: shared fields did not survive {an}→{bn}: {f1:?} != {f2:?}" + )) +} + +fn drop_if(cond: bool, msg: &str) -> Result { + if cond { + Ok(format!("accounted for — {msg}")) + } else { + Err(format!("NOT accounted for — {msg}")) + } +} + +/// A one-dialect-only concept bridged toward the other must be ACCOUNTED FOR. +fn asym_drop(id: &str, n1: &[Norm], n2: &[Norm]) -> Result { + let has = |ns: &[Norm], pred: &dyn Fn(&Norm) -> bool| ns.iter().any(pred); + match id { + "openai_buffer_clear" => drop_if( + !has(n2, &|n| matches!(n, Norm::Clear)), + "clear has no Gemini twin — dropped", + ), + "openai_response_overrides" => drop_if( + !has(n2, &|n| matches!(n, Norm::ResponseCreate)), + "per-response overrides dropped (Gemini is setup-time only)", + ), + "openai_truncate_precision" => drop_if( + !has(n2, &|n| matches!(n, Norm::Truncate(_))), + "sample-accurate truncate dropped (Gemini interrupted carries no ms)", + ), + "openai_structured_error" => drop_if( + !has(n2, &|n| matches!(n, Norm::Error(..))), + "structured error dropped toward Gemini (WS close codes instead)", + ), + "openai_event_id" | "openai_noise_reduction" => drop_if( + true, + "field never enters the IR (dropped at decode); bridged config omits it", + ), + "openai_semantic_vad" | "openai_g711" => drop_if( + has(n2, &|n| matches!(n, Norm::Config(_))), + "config bridges; semantic_vad/g711 specifics dropped per map", + ), + "openai_speech_boundary" => drop_if( + has(n2, &|n| matches!(n, Norm::SpeechStart)) || n1.is_empty(), + "boundary maps to interrupted; ms offset dropped", + ), + "openai_uplink_rate" => drop_if( + !n2.is_empty() + && n1 + .iter() + .zip(n2.iter()) + .all(|(a, b)| matches!((a, b), (Norm::AudioUp(x), Norm::AudioUp(y)) if x == y)), + "uplink audio bridges verbatim at its true rate; the resample toward Gemini's 16 kHz \ + input is not performed", + ), + "gemini_go_away" | "gemini_tool_call_cancellation" => drop_if( + n1.is_empty() && n2.is_empty(), + "no OpenAI twin — dropped at decode (drop+warn)", + ), + "gemini_generation_complete" => drop_if( + has(n2, &|n| matches!(n, Norm::AudioDone)), + "turnComplete→AudioDone survives; generationComplete collapsed", + ), + "gemini_audio_stream_end" => drop_if( + has(n1, &|n| matches!(n, Norm::Commit)) && has(n2, &|n| matches!(n, Norm::Commit)), + "audioStreamEnd ↔ input_audio_buffer.commit: the end-of-uplink turn survives", + ), + "gemini_setup_complete" => drop_if( + has(n2, &|n| matches!(n, Norm::Connect)), + "ack maps to session.created; gate semantics are the session's, not IR", + ), + other => Err(format!("no asymmetry handler for id '{other}'")), + } +} + +fn read_fixture(path: &Path) -> Option> { + let v: Value = serde_json::from_slice(&fs::read(path).ok()?).ok()?; + Some(bytes_of(&v)) +} + +/// `cross `: every shared concept, codec half and door half +/// (the bridged wire through the destination dialect's door), then every asymmetry row. +pub fn cross(rig: &Rig, pair: &str, oa: &Path, ge: &Path, map: &Value) -> i32 { + let (a, b) = match pair { + "oo" => (Dialect::OpenAi, Dialect::OpenAi), + "og" => (Dialect::OpenAi, Dialect::Gemini), + "go" => (Dialect::Gemini, Dialect::OpenAi), + "gg" => (Dialect::Gemini, Dialect::Gemini), + other => { + result(other, false, "pair", "unknown ordered pair"); + return 1; + } + }; + let slice = pair_slice(a, b); + let dir_for = |d: Dialect| if d == Dialect::OpenAi { oa } else { ge }; + let mut fails = 0; + let Some(concepts) = map["concepts"].as_array() else { + result( + slice, + false, + "map", + "the cross-dialect map has no concepts array", + ); + return 1; + }; + for c in concepts { + let concept = c["concept"].as_str().unwrap_or("?"); + let present: Vec<(String, Vec)> = c[a.name()]["fixtures"] + .as_array() + .map(Vec::as_slice) + .unwrap_or_default() + .iter() + .filter_map(|fx| { + let name = fx + .as_str() + .filter(|n| !n.is_empty() && !n.ends_with(".jsonl"))?; + Some((name.to_string(), read_fixture(&dir_for(a).join(name))?)) + }) + .collect(); + let mut chosen = present.iter().find_map(|(name, v)| { + let br = bridge(a, b, std::slice::from_ref(v)); + (!br.n1.is_empty()).then(|| (name.clone(), br)) + }); + // A concept whose transform is stated ACROSS events (the streamed⟷atomic tool call) can + // bridge one frame to nothing alone; judge its fixtures as the one exchange the map states. + if chosen.as_ref().is_some_and(|(_, br)| br.n2.is_empty()) && present.len() > 1 { + let vals: Vec> = present.iter().map(|(_, v)| v.clone()).collect(); + let names: Vec<&str> = present.iter().map(|(n, _)| n.as_str()).collect(); + chosen = Some((names.join("+"), bridge(a, b, &vals))); + } + match chosen { + None => println!( + "SUBITEM {slice}:{concept} PENDING — {}", + none_reason(concept, a) + ), + Some((name, br)) => { + let codec = survives(concept, &br.n1, &br.n2, a, b); + let what = format!("shared:{name}"); + if !judge_relay(rig, slice, &what, b, &br.wires, &codec) { + fails += 1; + } + } + } + } + let Some(asym) = map["asymmetry"].as_array() else { + result( + slice, + false, + "map", + "the cross-dialect map has no asymmetry array", + ); + return 1; + }; + for row in asym { + let id = row["id"].as_str().unwrap_or("?"); + let origin = row["dialect"].as_str().unwrap_or("?"); + let fixture = row["fixture"].as_str().unwrap_or_default(); + let what = format!("asym:{id}"); + // A row is EXERCISED in its origin → other direction only. + if a.name() != origin || a == b { + result( + slice, + true, + &what, + &format!( + "not this pair's drop direction (origin={origin}); exercised in the \ + {origin}→other pair" + ), + ); + continue; + } + let name = fixture + .strip_prefix(&format!("{origin}/")) + .unwrap_or(fixture); + let Some(v) = read_fixture(&dir_for(a).join(name)) else { + result(slice, false, &what, &format!("fixture {fixture} missing")); + fails += 1; + continue; + }; + let br = bridge(a, b, std::slice::from_ref(&v)); + match asym_drop(id, &br.n1, &br.n2) { + Ok(d) => result(slice, true, &what, &d), + Err(d) => { + result(slice, false, &what, &d); + fails += 1; + } + } + } + i32::from(fails > 0) +} + +/// The codec half of the V4 governance checkpoint: an OpenAI-only concept bridged toward Gemini +/// is down-scoped, never widened. Answers the bridged norms' text. +pub fn downscope_bridge(update: &Value) -> String { + format!( + "{:?}", + bridge(Dialect::OpenAi, Dialect::Gemini, &[bytes_of(update)]).n2 + ) +} + +/// The frame bytes of a JSON value. +pub fn frame(v: &Value) -> Vec { + bytes_of(v) +} + +/// The far-end concepts a log's frames carry toward the caller, decoded under `dialect`. +pub fn caller_norms(dialect: Dialect, log: &Log) -> Vec { + decode_relay(dialect, log).caller +} + +/// The concepts a log's frames carry toward the far end, decoded under `dialect`. +pub fn far_norms(dialect: Dialect, log: &Log) -> Vec { + decode_relay(dialect, log).far +} + +/// The token units a usage frame reports, read by the codec: `(class, amount)`, zeros left out. +pub fn usage_tokens(dialect: Dialect, usage_frame: &[u8]) -> Vec<(u32, u64)> { + expect( + dialect, + &[Wire { + dir: Dir::Down, + bytes: usage_frame.to_vec(), + }], + ) + .tokens +} diff --git a/crates/busbar-plane-streaming/examples/voice_conform/composition.rs b/crates/busbar-plane-streaming/examples/voice_conform/composition.rs new file mode 100644 index 0000000000..64b52f44cf --- /dev/null +++ b/crates/busbar-plane-streaming/examples/voice_conform/composition.rs @@ -0,0 +1,1344 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE COMPOSITION LEGS, AT THE DOOR. Each leg judged one seam of a served session; the plane is +//! now served only through its door, so each leg judges the DOOR'S HALF of that seam — what the +//! door states, answers and refuses to do — and names the half that is the kernel's (the plane +//! driver's walk, the identity crate, the money steps, the transport), which the plane cannot see +//! and this harness therefore cannot judge. + +use busbar_contract::abi::host::conn::connector::DIRECTION_OUTBOUND; +use busbar_contract::abi::mechanism::call::Outcome; +use busbar_contract::abi::plane::{ + FROM_CALLER, FROM_FAR_END, FROM_KERNEL, PIECE_HAS_STATUS, PIECE_LAST, PRINCIPAL_NONE, + PRINCIPAL_REQUIRED, ROUTE_DIRECT, ROUTE_POOL, +}; +use busbar_contract::media::base64_encode; +use busbar_plane_streaming::claims::{HTTP_TRANSPORT, WS_TRANSPORT}; +use busbar_plane_streaming::door::{ + refusal_body, AUDIT_KIND, LIVE_STYLE, REALTIME_STYLE, REFUSAL_NO_DOOR, RIDES_LIVE_SOCKET, + RIDES_REALTIME_PASS, RIDES_REALTIME_SOCKET, +}; +use busbar_plane_streaming::driven::{ + mint_reply, GEMINI_SOCKET_PATH, MINT_FAILED_STATUS, REALTIME_SOCKET_PATH, SESSION_OPEN_ACTION, +}; +use busbar_plane_streaming::provider::{GEMINI_LIVE, OPENAI_REALTIME}; +use busbar_plugin_loader::dispatch::kinds::plane::Plane; +use busbar_plugin_loader::dispatch::Plugin; +use serde_json::{json, Value}; + +use crate::codec_legs::{caller_norms, far_norms, fingerprint, frame, usage_tokens, Dialect, Norm}; +use crate::door::{ + arrive, attempt, drive, judge, open, piece, refusal, request, result, session, Answer, Arrived, + Emission, Log, Opened, Piece, Rendered, Rig, Session, CALLER_REF, MODEL, PUBLIC, SESSION, +}; + +// ── the frames the legs push ──────────────────────────────────────────────────────────────────── + +/// A far end's OpenAI Realtime usage report. +pub const USAGE_DONE: &[u8] = br#"{"type":"response.done","response":{"usage":{"input_token_details":{"audio_tokens":10,"text_tokens":3},"output_token_details":{"audio_tokens":20,"text_tokens":4}}}}"#; + +/// A far end's Gemini Live usage report. +pub const GEMINI_USAGE: &[u8] = br#"{"usageMetadata":{"promptTokenCount":140,"responseTokenCount":80,"totalTokenCount":220,"promptTokensDetails":[{"modality":"TEXT","tokenCount":40},{"modality":"AUDIO","tokenCount":100}],"responseTokensDetails":[{"modality":"AUDIO","tokenCount":80}]}}"#; + +/// The cumulative units a session reports once `USAGE_DONE` closed a turn holding `secs` seconds +/// of the caller's audio: the four token classes, the audio seconds and the session fee. +pub fn usage_units(secs: u64) -> String { + format!("0:1=10 1:1=20 2:1=3 3:1=4 4:1={secs} 6:1=1") +} + +/// `ms` milliseconds of the caller's 24 kHz PCM16 audio, as an OpenAI Realtime frame. +pub fn uplink(ms: usize) -> Vec { + frame(&json!({"type": "input_audio_buffer.append", + "audio": base64_encode(&vec![0_u8; ms * 48])})) +} + +/// `ms` milliseconds of the caller's 16 kHz PCM16 audio, as a Gemini Live frame. +pub fn gemini_uplink(ms: usize) -> Vec { + frame( + &json!({"realtimeInput": {"audio": {"mimeType": "audio/pcm;rate=16000", + "data": base64_encode(&vec![0_u8; ms * 32])}}}), + ) +} + +/// `bytes` bytes of the far end's OpenAI Realtime audio. +pub fn downlink(bytes: usize) -> Vec { + frame(&json!({"type": "response.output_audio.delta", + "delta": base64_encode(&vec![0_u8; bytes])})) +} + +/// The OpenAI Realtime mint's far-end answer. +const MINTED: &[u8] = br#"{"value":"ek_oracle_0001","expires_at":1767225600}"#; + +/// One `answer` as a line: the step, the outcome and the cumulative units. +fn units_of(what: &str, a: &Answer) -> String { + format!("{what} {:?} units=[{}]", a.outcome, a.units_line()) +} + +/// `composition `. +pub fn run(rig: &Rig, slice: &str) -> i32 { + let pass = match slice { + "provider-credential" => provider_credential(rig, slice), + "metering-lease" => metering_lease(rig, slice), + "session-scope" => session_scope(rig, slice), + "gemini-live-route" => gemini_live_route(rig, slice), + "provider-dial" => provider_dial(rig, slice), + "admit-refusal" => admit_refusal(rig, slice), + "route-failover" => route_failover(rig, slice), + "audit-record" => audit_record(rig, slice), + "exit-terminal" => exit_terminal(rig, slice), + "tool-reply" => tool_reply(rig, slice), + other => { + result(other, false, "slice", "unknown composition slice"); + false + } + }; + i32::from(!pass) +} + +// ── provider-credential ───────────────────────────────────────────────────────────────────────── + +/// One need the Statement declares. +#[derive(Clone, Debug, PartialEq, Eq)] +struct NeedLine { + direction: u32, + transport: String, + auth: String, + target: String, +} + +/// One far request a door answered. +#[derive(Clone, Debug, PartialEq, Eq)] +struct Ride { + door: &'static str, + verb: String, + target: String, + need: u32, + fields: Vec<(String, String)>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct CredObs { + needs: Vec, + dialect_auth: Vec, + rides: Vec, +} + +/// Head fields that carry a credential: none may ride a request the door answers. +const CREDENTIAL_FIELDS: &[&str] = &[ + "authorization", + "proxy-authorization", + "x-goog-api-key", + "x-api-key", + "api-key", + "openai-api-key", + "cookie", +]; + +fn ride(door: &'static str, a: &Answer) -> Ride { + Ride { + door, + verb: a.verb.clone(), + target: a.target.clone(), + need: a.need, + fields: a.fields.clone(), + } +} + +fn cred_script(p: &Plugin) -> CredObs { + let served = p.served(); + let needs = served + .need_auths + .iter() + .zip(&served.need_transports) + .zip(&served.need_targets) + .map(|((da, t), g)| NeedLine { + direction: da.0, + transport: (*t).to_string(), + auth: da.1.to_string(), + target: (*g).to_string(), + }) + .collect(); + let dialect_auth = served + .dialect_auth + .iter() + .map(|(d, style)| { + let name = served.dialects.get(*d as usize).copied().unwrap_or("?"); + format!("{name}={style}") + }) + .collect(); + let _ = open(p, SESSION, Some(PUBLIC)); + let mut rides = vec![ + ride("mint", &piece(p, &attempt(7, 0, 1))), + ride("sdp", &piece(p, &attempt(8, 1, 1))), + ]; + let mut s = Session::open(p, 20, 2); + rides.push(ride("sideband", &s.push(FROM_CALLER, 0, &uplink(100)))); + let mut g = Session::open(p, 21, 3); + rides.push(ride("gemini", &g.push(FROM_CALLER, 0, &gemini_uplink(100)))); + CredObs { + needs, + dialect_auth, + rides, + } +} + +fn cred_check(o: &CredObs) -> Result { + if o.rides.len() != 4 { + return Err(format!("expected four far requests, saw {:?}", o.rides)); + } + for r in &o.rides { + let (transport, style) = match r.door { + "mint" | "sdp" => (HTTP_TRANSPORT, REALTIME_STYLE), + "sideband" => (WS_TRANSPORT, REALTIME_STYLE), + _ => (WS_TRANSPORT, LIVE_STYLE), + }; + let Some(need) = (r.need as usize) + .checked_sub(1) + .and_then(|i| o.needs.get(i)) + else { + return Err(format!( + "the {} request rides need {}, which the Statement does not declare", + r.door, r.need + )); + }; + if need.direction != DIRECTION_OUTBOUND + || need.transport != transport + || need.auth != style + || !need.target.is_empty() + { + return Err(format!( + "the {} request rides {need:?}, not an outbound {transport} need under the \ + {style} style whose address is the member's", + r.door + )); + } + if let Some((name, _)) = r.fields.iter().find(|(n, v)| { + CREDENTIAL_FIELDS.contains(&n.to_ascii_lowercase().as_str()) + || v.to_ascii_lowercase().starts_with("bearer ") + }) { + return Err(format!( + "the {} request carries a credential field `{name}`", + r.door + )); + } + if r.verb.is_empty() || r.target.is_empty() { + return Err(format!( + "the {} request names no verb/target: {r:?}", + r.door + )); + } + } + let mint = &o.rides[0]; + if !mint + .fields + .iter() + .any(|(n, v)| n.eq_ignore_ascii_case("openai-safety-identifier") && v == CALLER_REF) + { + return Err(format!( + "the mint does not name the caller by the kernel's reference alone: {:?}", + mint.fields + )); + } + for want in [ + format!("{OPENAI_REALTIME}={REALTIME_STYLE}"), + format!("{GEMINI_LIVE}={LIVE_STYLE}"), + ] { + if !o.dialect_auth.contains(&want) { + return Err(format!( + "the tail's dialect auth lacks {want}: {:?}", + o.dialect_auth + )); + } + } + Ok( + "every far request (mint, SDP, the sideband and Gemini sockets) rides a declared outbound \ + need whose auth style the kernel binds the provider credential to; none carries a \ + credential field, the caller is named only by the kernel's reference, and each dialect \ + states its far end's credential style. Resolving the catalog's secret reference, the \ + set-once composition and an unresolvable reference composing nothing are the \ + composition root's (busbar), not the door's" + .to_string(), + ) +} + +fn provider_credential(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "the door's far requests ride declared needs and carry no credential", + &rig.both(cred_script), + &cred_check, + &|o: &CredObs| { + let mut planted = o.clone(); + planted.rides[0] + .fields + .push(("authorization".to_string(), "Bearer sk-caller".to_string())); + planted + }, + ) +} + +// ── metering-lease ────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct MeterObs { + classes: Vec, + fees: Vec, + admitted: Vec, + lines: Vec, +} + +fn meter_script(p: &Plugin) -> MeterObs { + let served = p.served(); + let classes = served + .billable_classes + .iter() + .zip(&served.billable_families) + .map(|(c, f)| format!("{c}/{f}")) + .collect(); + let fees = served.fee_units.iter().map(|f| (*f).to_string()).collect(); + let _ = open(p, SESSION, Some(PUBLIC)); + let admitted = (0..5).map(|claim| arrive(p, claim).units).collect(); + let audio = uplink(2000); + let mut lines = Vec::new(); + let a = piece(p, &session(20, 2, FROM_CALLER, 0, &audio)); + lines.push(units_of("caller audio", &a)); + let turn = Piece { + attempt_no: 1, + ..session(20, 2, FROM_KERNEL, 0, &[]) + }; + lines.push(units_of("turn attempt", &piece(p, &turn))); + let far = Piece { + status: 101, + ..session(20, 2, FROM_FAR_END, PIECE_HAS_STATUS, USAGE_DONE) + }; + lines.push(units_of("far usage", &piece(p, &far))); + lines.push(units_of( + "collect", + &piece(p, &session(20, 2, FROM_KERNEL, 0, &[])), + )); + let short = Piece { + units_cap: 2, + ..session(20, 2, FROM_CALLER, 0, &audio) + }; + lines.push(units_of("short", &piece(p, &short))); + lines.push(units_of( + "short re-call", + &piece(p, &session(20, 2, FROM_CALLER, 0, &audio)), + )); + lines.push(units_of( + "caller end", + &piece(p, &session(20, 2, FROM_CALLER, PIECE_LAST, &[])), + )); + lines.push(units_of( + "unanswered audio", + &piece(p, &session(30, 2, FROM_CALLER, 0, &audio)), + )); + lines.push(units_of( + "unanswered end", + &piece(p, &session(30, 2, FROM_CALLER, PIECE_LAST, &[])), + )); + MeterObs { + classes, + fees, + admitted, + lines, + } +} + +fn meter_expected() -> Vec { + vec![ + "caller audio Ready units=[]".to_string(), + "turn attempt Ready units=[]".to_string(), + format!("far usage Ready units=[{}]", usage_units(2)), + format!("collect Ready units=[{}]", usage_units(2)), + "short Failed units=[needed 6]".to_string(), + format!("short re-call Ready units=[{}]", usage_units(2)), + format!("caller end Ready units=[{}]", usage_units(4)), + "unanswered audio Ready units=[]".to_string(), + "unanswered end Ready units=[4:1=2]".to_string(), + ] +} + +fn meter_check(o: &MeterObs) -> Result { + let classes = [ + "audio_tokens_in/token", + "audio_tokens_out/token", + "text_tokens_in/token", + "text_tokens_out/token", + "audio_seconds_in/duration", + "tool_calls/count", + "per_session/count", + ]; + if o.classes != classes { + return Err(format!("the tail's billable classes are {:?}", o.classes)); + } + if o.fees != ["per_session"] { + return Err(format!("the tail's fee units are {:?}", o.fees)); + } + if o.admitted.iter().any(|n| *n != 0) { + return Err(format!( + "an arrival estimated units before the far end reported any: {:?}", + o.admitted + )); + } + let want = meter_expected(); + if o.lines != want { + return Err(format!("the session reported {:?}; want {want:?}", o.lines)); + } + Ok( + "the door counts in its six declared classes plus the per-session fee, admits on no \ + estimate, and reports every turn's far-end tokens and audio seconds as cumulative \ + REPORTED units: once per turn (a short answer's re-call does not count its audio \ + twice), the open turn settled once at the end, and no fee unless the far end answered. \ + Reserving against the caller's budget chain, capping at its remaining bucket and \ + ledgering are the kernel's session account" + .to_string(), + ) +} + +fn metering_lease(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "a session's units, reported per declared class", + &rig.both(meter_script), + &meter_check, + &|o: &MeterObs| { + let mut planted = o.clone(); + planted.lines[2] = planted.lines[2].replace("1:1=20", "1:1=21"); + planted + }, + ) +} + +// ── session-scope ─────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ScopeObs { + scope_kinds: Vec, + arrivals: Vec, + refused: Rendered, +} + +const SCOPE_CLAIMS: [u32; 7] = [0, 1, 2, 3, 4, 5, 9]; + +fn scope_script(p: &Plugin) -> ScopeObs { + let scope_kinds = p + .declared() + .scope_kinds + .iter() + .map(|k| (*k).to_string()) + .collect(); + let _ = open(p, SESSION, Some(PUBLIC)); + ScopeObs { + scope_kinds, + arrivals: SCOPE_CLAIMS.iter().map(|c| arrive(p, *c)).collect(), + refused: refusal(p, 0, 0, 403, "the key holds no session grant for this door"), + } +} + +/// A rendered refusal's error type and message, read back. +fn error_of(r: &Rendered) -> (String, String) { + let v: Value = serde_json::from_str(&r.body).unwrap_or(Value::Null); + ( + v["error"]["type"].as_str().unwrap_or_default().to_string(), + v["error"]["message"] + .as_str() + .unwrap_or_default() + .to_string(), + ) +} + +fn scope_check(o: &ScopeObs) -> Result { + if o.scope_kinds != ["session"] { + return Err(format!("the tail's grant kinds are {:?}", o.scope_kinds)); + } + for (claim, a) in SCOPE_CLAIMS.iter().zip(&o.arrivals) { + let door = *claim <= 4; + let ok = if door { + a.outcome == Outcome::Ready && a.principal == PRINCIPAL_REQUIRED && a.op_class == 0 + } else { + a.outcome == Outcome::Refused && a.refusal == (REFUSAL_NO_DOOR, 404) + }; + if !ok { + return Err(format!("claim {claim} arrived as {a:?}")); + } + } + let (kind, words) = error_of(&o.refused); + if o.refused.outcome != Outcome::Ready + || kind != "permission_error" + || words != "the key holds no session grant for this door" + || o.refused.status != 0 + { + return Err(format!("the grant refusal rendered as {:?}", o.refused)); + } + Ok( + "the door declares the `session` grant kind, every one of its five doors asks for a \ + principal (none admits anonymously) under the one session-open operation, an \ + unpublished claim is refused 404 with the plane's own code, and a grant refusal renders \ + as the dialect's permission_error. Matching the key's grants (wildcard, explicit, \ + another pool, an empty list) is the kernel's approve step" + .to_string(), + ) +} + +fn session_scope(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "the door's grant kind and principal need", + &rig.both(scope_script), + &scope_check, + &|o: &ScopeObs| { + let mut planted = o.clone(); + planted.arrivals[2].principal = PRINCIPAL_NONE; + planted + }, + ) +} + +// ── gemini-live-route ─────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct RouteObs { + opened: Opened, + arrived: Arrived, + dialect: String, + handshake: Log, +} + +const GEMINI_SETUP: &[u8] = br#"{"setup":{"model":"models/gemini-2.0-flash-exp","generationConfig":{"responseModalities":["AUDIO"]}}}"#; +const SETUP_COMPLETE: &[u8] = br#"{"setupComplete":{}}"#; + +fn route_script(p: &Plugin) -> RouteObs { + let opened = open(p, SESSION, Some(PUBLIC)); + let arrived = arrive(p, 3); + let dialect = p + .served() + .dialects + .get(arrived.dialect as usize) + .copied() + .unwrap_or("?") + .to_string(); + let mut s = Session::open(p, 40, 3); + s.push(FROM_CALLER, 0, GEMINI_SETUP); + s.push(FROM_FAR_END, 0, SETUP_COMPLETE); + RouteObs { + opened, + arrived, + dialect, + handshake: s.log, + } +} + +fn route_check(o: &RouteObs) -> Result { + let claimed = |line: &str, dialect: u16| { + o.opened + .claims + .iter() + .any(|c| c.line == line && c.refusal_dialect == dialect) + }; + if o.opened.outcome != Outcome::Ready + || !claimed("GET /v1/realtime/gemini/{call_id} ws", 1) + || !claimed("GET /v1/realtime/sideband/{call_id} ws", 0) + { + return Err(format!( + "the snapshot does not claim the Gemini socket (refused in Gemini's dialect) beside \ + the sideband: {:?}", + o.opened + )); + } + if o.opened.audience != "https://gw.example.com/v1/realtime" { + return Err(format!("the one audience is {}", o.opened.audience)); + } + let a = &o.arrived; + if a.outcome != Outcome::Ready + || a.principal != PRINCIPAL_REQUIRED + || a.route != (ROUTE_DIRECT, MODEL.to_string()) + || o.dialect != GEMINI_LIVE + { + return Err(format!( + "the Gemini door arrived as {a:?} in dialect {}", + o.dialect + )); + } + let h = &o.handshake; + if !h.anomalies.is_empty() { + return Err(format!( + "the handshake was not answered READY: {:?}", + h.anomalies + )); + } + let far: Vec<&Emission> = h.emissions.iter().filter(|e| e.to_far).collect(); + let near: Vec<&Emission> = h.emissions.iter().filter(|e| !e.to_far).collect(); + if far.len() != 1 + || far[0].need != RIDES_LIVE_SOCKET + || far[0].verb != "GET" + || far[0].target != GEMINI_SOCKET_PATH + { + return Err(format!("the caller's setup reached the far end as {far:?}")); + } + if !far_norms(Dialect::Gemini, h) + .iter() + .any(|n| matches!(n, Norm::Config(_))) + { + return Err("the far end was not sent a Gemini setup".to_string()); + } + let relayed: Value = near + .first() + .and_then(|e| serde_json::from_slice(&e.frame).ok()) + .unwrap_or(Value::Null); + let sent: Value = serde_json::from_slice(SETUP_COMPLETE).unwrap_or(Value::Null); + if near.len() != 1 || relayed != sent || caller_norms(Dialect::Gemini, h) != [Norm::Connect] { + return Err(format!( + "the far end's setupComplete reached the caller as {near:?}" + )); + } + Ok( + "the Gemini Live socket is claimed under its own path (its refusals in Gemini's dialect) \ + beside the OpenAI doors under the one audience, arrives as gemini_live for a principal \ + on the session model's DIRECT route, and the handshake crosses the door: the caller's \ + setup reaches the far end as a Gemini setup on the Live socket need, the far end's \ + setupComplete reaches the caller verbatim" + .to_string(), + ) +} + +fn gemini_live_route(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "the Gemini Live door: claim, arrival and handshake", + &rig.both(route_script), + &route_check, + &|o: &RouteObs| { + let mut planted = o.clone(); + for c in &mut planted.opened.claims { + c.refusal_dialect = 0; + } + planted + }, + ) +} + +// ── provider-dial ─────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct DialObs { + openai: Log, + gemini: Log, +} + +fn dial_script(p: &Plugin) -> DialObs { + let _ = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 50, 2); + s.push(FROM_CALLER, 0, &uplink(100)); + s.push(FROM_FAR_END, 0, USAGE_DONE); + let mut g = Session::open(p, 51, 3); + g.push(FROM_CALLER, 0, &gemini_uplink(100)); + g.push(FROM_FAR_END, 0, GEMINI_USAGE); + DialObs { + openai: s.log, + gemini: g.log, + } +} + +fn dial_one( + dialect: Dialect, + log: &Log, + usage: &[u8], + need: u32, + socket: &str, +) -> Result { + if !log.anomalies.is_empty() { + return Err(format!( + "{}: not every piece READY: {:?}", + dialect.name(), + log.anomalies + )); + } + let far: Vec<&Emission> = log.emissions.iter().filter(|e| e.to_far).collect(); + if far.is_empty() + || far + .iter() + .any(|e| e.verb != "GET" || e.target != socket || e.need != need) + { + return Err(format!( + "{}: the session's far frames are not GET {socket} on need {need}: {far:?}", + dialect.name() + )); + } + let want = usage_tokens(dialect, usage); + let got: Vec<(u32, u64)> = log + .units + .iter() + .filter(|u| u.0 < 4) + .map(|u| (u.0, u.2)) + .collect(); + if want.is_empty() || got != want || !log.units.iter().any(|u| u.0 == 6 && u.2 == 1) { + return Err(format!( + "{}: the far end's usage {want:?} was reported as {:?}", + dialect.name(), + log.units + )); + } + Ok(format!( + "{}: GET {socket} on need {need}, the far end's usage reported as {got:?} plus the fee", + dialect.name() + )) +} + +fn dial_check(o: &DialObs) -> Result { + let a = dial_one( + Dialect::OpenAi, + &o.openai, + USAGE_DONE, + RIDES_REALTIME_SOCKET, + REALTIME_SOCKET_PATH, + )?; + let b = dial_one( + Dialect::Gemini, + &o.gemini, + GEMINI_USAGE, + RIDES_LIVE_SOCKET, + GEMINI_SOCKET_PATH, + )?; + Ok(format!( + "a session names the dialect's socket under the member's base URL on its declared need \ + and reports what arrives over it ({a}; {b}). Opening the socket (net guard, TLS, the \ + upgrade) is the kernel transport's" + )) +} + +fn provider_dial(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "a session's far frames ride the dialect's socket and its usage is reported", + &rig.both(dial_script), + &dial_check, + &|o: &DialObs| { + let mut planted = o.clone(); + if let Some(e) = planted.openai.emissions.iter_mut().find(|e| e.to_far) { + e.need = RIDES_REALTIME_PASS; + } + planted + }, + ) +} + +// ── admit-refusal ─────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct AdmitObs { + rendered: Vec<(u16, Rendered)>, + after: Vec, + drive: (Outcome, Vec), + control: Answer, +} + +const REFUSALS: [(u16, &str, &str); 3] = [ + (429, "the caller's budget is exhausted", "rate_limit_error"), + (403, "the key holds no session grant", "permission_error"), + (401, "no credential was presented", "authentication_error"), +]; + +fn admit_script(p: &Plugin) -> AdmitObs { + let _ = open(p, SESSION, Some(PUBLIC)); + let rendered = REFUSALS + .iter() + .map(|(status, words, _)| (*status, refusal(p, 60, 0, u32::from(*status), *words))) + .collect(); + let far = piece(p, &session(60, 2, FROM_FAR_END, 0, &downlink(96))); + let collect = piece(p, &session(60, 2, FROM_KERNEL, 0, &[])); + let after = vec![ + format!("far {:?} emitted={}", far.outcome, far.emitted.len()), + format!( + "collect {:?} emitted={}", + collect.outcome, + collect.emitted.len() + ), + ]; + let drive = drive(p); + let mut s = Session::open(p, 61, 2); + let control = s.push(FROM_CALLER, 0, &uplink(100)); + AdmitObs { + rendered, + after, + drive, + control, + } +} + +fn admit_check(o: &AdmitObs) -> Result { + for ((status, r), (_, words, kind)) in o.rendered.iter().zip(REFUSALS) { + let body: Value = + serde_json::from_slice(&refusal_body(*status, words)).unwrap_or(Value::Null); + let (k, w) = error_of(r); + if r.outcome != Outcome::Ready + || k != kind + || w != words + || serde_json::from_str::(&r.body).ok() != Some(body) + || r.fields != [("content-type".to_string(), "application/json".to_string())] + || r.status != 0 + || r.records != 0 + { + return Err(format!("the kernel's {status} refusal rendered as {r:?}")); + } + } + if o.after != ["far Refused emitted=0", "collect Refused emitted=0"] { + return Err(format!( + "a refused unit's stream was answered as though a session were open: {:?}", + o.after + )); + } + if o.drive != (Outcome::Ready, Vec::new()) { + return Err(format!( + "drive named a session after the refusal: {:?}", + o.drive + )); + } + let c = &o.control; + if c.outcome != Outcome::Ready || !c.to_far || c.need != RIDES_REALTIME_SOCKET { + return Err(format!( + "the admitted control did not reach the far end: {c:?}" + )); + } + Ok( + "the kernel's refusal (budget 429, grant 403, credential 401) is rendered in the \ + dialect's error envelope with the kernel's status kept and no record written; a refused \ + unit opens no session and dials nothing (its stream's far piece and collection are \ + refused, drive names nothing), while an admitted control dials. Reading the caller's \ + chain dry and refusing before the hold is the kernel's admit step" + .to_string(), + ) +} + +fn admit_refusal(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "a refusal opens nothing and is rendered in the dialect's shape", + &rig.both(admit_script), + &admit_check, + &|o: &AdmitObs| { + let mut planted = o.clone(); + planted.rendered[0].1.body = + String::from_utf8_lossy(&refusal_body(500, "x")).into_owned(); + planted + }, + ) +} + +// ── route-failover ────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct FailObs { + arrived: Arrived, + first: Answer, + failed: Answer, + second: Answer, + caller: Answer, + answered: Answer, + terminal_attempt: Answer, + terminal: Answer, +} + +fn fail_script(p: &Plugin) -> FailObs { + let _ = open(p, SESSION, Some(PUBLIC)); + let arrived = arrive(p, 0); + let first = piece(p, &attempt(70, 0, 1)); + let failed = piece( + p, + &Piece { + status: 503, + ..request( + 70, + 0, + FROM_FAR_END, + PIECE_HAS_STATUS, + b"upstream unavailable", + ) + }, + ); + let second = piece(p, &attempt(70, 0, 2)); + let caller = piece(p, &request(70, 0, FROM_CALLER, PIECE_LAST, &[])); + let answered = piece( + p, + &Piece { + status: 200, + ..request(70, 0, FROM_FAR_END, PIECE_HAS_STATUS | PIECE_LAST, MINTED) + }, + ); + let terminal_attempt = piece(p, &attempt(71, 0, 1)); + let terminal = piece( + p, + &Piece { + status: 503, + ..request(71, 0, FROM_FAR_END, PIECE_HAS_STATUS | PIECE_LAST, b"down") + }, + ); + FailObs { + arrived, + first, + failed, + second, + caller, + answered, + terminal_attempt, + terminal, + } +} + +/// Whether two ATTEMPTs answered the same far request: verb, target, fields, body and need. +fn same_request(a: &Answer, b: &Answer) -> bool { + a.verb == b.verb + && a.target == b.target + && a.fields == b.fields + && a.emitted == b.emitted + && a.need == b.need +} + +fn fail_check(o: &FailObs) -> Result { + if o.arrived.outcome != Outcome::Ready || o.arrived.route != (ROUTE_DIRECT, MODEL.to_string()) { + return Err(format!("the mint arrived as {:?}", o.arrived)); + } + let f = &o.first; + if f.outcome != Outcome::Ready + || !f.to_far + || f.verb != "POST" + || f.target != "/v1/realtime/client_secrets" + || f.need != RIDES_REALTIME_PASS + || f.emitted.is_empty() + { + return Err(format!("attempt 1 sent {f:?}")); + } + if o.failed.outcome != Outcome::Ready || !o.failed.emitted.is_empty() || o.failed.done { + return Err(format!( + "a failing far end's first bytes reached the caller (the walk could no longer fail \ + over): {:?}", + o.failed + )); + } + if !same_request(&o.second, f) || !same_request(&o.terminal_attempt, f) { + return Err(format!( + "a later attempt sent a different request: {:?} vs {:?}", + o.second, f + )); + } + if o.caller.outcome != Outcome::Ready || !o.caller.emitted.is_empty() { + return Err(format!( + "the re-pushed caller body was answered {:?}", + o.caller + )); + } + let a = &o.answered; + if a.outcome != Outcome::Ready + || !a.done + || a.status != 200 + || a.emitted != mint_reply(200, MINTED).body + { + return Err(format!( + "attempt 2's answer was not the caller's whole answer, free of attempt 1: {a:?}" + )); + } + let t = &o.terminal; + if t.outcome != Outcome::Ready + || !t.done + || t.status != u32::from(MINT_FAILED_STATUS) + || !String::from_utf8_lossy(&t.emitted) + .contains("client-secret endpoint returned 503 Service Unavailable") + { + return Err(format!("the last attempt's failure was rendered {t:?}")); + } + Ok( + "the mint names the session model's DIRECT route; a failing far end reaches the caller \ + with nothing before its last piece, so the walk may still fail over; each ATTEMPT is \ + answered with the same request afresh, and the next far end's answer is the caller's \ + whole answer; a last attempt that fails is the served plane's 502. Tripping the \ + breaker cell and refusing further dials are the kernel walk's" + .to_string(), + ) +} + +fn route_failover(rig: &Rig, slice: &str) -> bool { + let failover = judge( + slice, + "each attempt is answered afresh and nothing reaches the caller before the answer", + &rig.both(fail_script), + &fail_check, + &|o: &FailObs| { + let mut planted = o.clone(); + planted.second.emitted = b"planted".to_vec(); + planted + }, + ); + let unrouted = judge( + slice, + "a section with no session model names no route", + &rig.both(|p| { + let _ = open(p, b"{}", Some(PUBLIC)); + arrive(p, 0) + }), + &|a: &Arrived| { + if a.outcome == Outcome::Ready && a.route == (ROUTE_POOL, String::new()) { + Ok( + "with no `streams.session.model` the door names no route for the walk to \ + resolve (the door steps refuse an unnamed keyed route)" + .to_string(), + ) + } else { + Err(format!("an unrouted mint arrived as {a:?}")) + } + }, + &|a: &Arrived| Arrived { + route: (ROUTE_DIRECT, MODEL.to_string()), + ..a.clone() + }, + ); + failover && unrouted +} + +// ── audit-record ──────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct AuditObs { + audit_kind: String, + op_classes: Vec, + record_kinds: Vec, + op_of: Vec<(Outcome, u32)>, + records: u32, +} + +fn audit_script(p: &Plugin) -> AuditObs { + let served = p.served(); + let record_kinds = p + .declared() + .record_kinds + .iter() + .map(|k| (*k).to_string()) + .collect(); + let _ = open(p, SESSION, Some(PUBLIC)); + let op_of = (0..5) + .map(|c| { + let a = arrive(p, c); + (a.outcome, a.op_class) + }) + .collect(); + let mut records = 0; + for stream in [80, 81] { + let mut s = Session::open(p, stream, 2); + s.push(FROM_CALLER, 0, &uplink(100)); + s.push(FROM_FAR_END, 0, USAGE_DONE); + s.push(FROM_CALLER, PIECE_LAST, &[]); + records += s.log.records; + } + for x in [ + attempt(82, 0, 1), + request(82, 0, FROM_CALLER, PIECE_LAST, &[]), + Piece { + status: 200, + ..request(82, 0, FROM_FAR_END, PIECE_HAS_STATUS | PIECE_LAST, MINTED) + }, + ] { + records += piece(p, &x).records; + } + records += refusal(p, 83, 0, 403, "refused").records; + AuditObs { + audit_kind: served.audit_kind.to_string(), + op_classes: served.op_classes.iter().map(|c| (*c).to_string()).collect(), + record_kinds, + op_of, + records, + } +} + +fn audit_check(o: &AuditObs) -> Result { + if o.audit_kind != AUDIT_KIND || o.op_classes != [SESSION_OPEN_ACTION] { + return Err(format!( + "the tail states audit kind {} and op classes {:?}", + o.audit_kind, o.op_classes + )); + } + if !o.record_kinds.is_empty() { + return Err(format!( + "the tail declares record kinds {:?}", + o.record_kinds + )); + } + if o.op_of + .iter() + .any(|(outcome, op)| *outcome != Outcome::Ready || *op != 0) + { + return Err(format!( + "a door's unit is not the one session open: {:?}", + o.op_of + )); + } + if o.records != 0 { + return Err(format!( + "the door wrote {} record row(s) of its own beside the kernel's one fixed record", + o.records + )); + } + Ok(format!( + "every door's unit is the one `{SESSION_OPEN_ACTION}` operation under the `{AUDIT_KIND}` \ + audit kind, and the door writes NO row of its own across two whole sessions, a mint and \ + a refusal (it declares no record kind), so the kernel's one fixed record per unit is the \ + only row; writing that row is the kernel's audit step" + )) +} + +fn audit_record(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "the audit kind and operation the kernel's one row is written under", + &rig.both(audit_script), + &audit_check, + &|o: &AuditObs| AuditObs { + records: o.records + 1, + ..o.clone() + }, + ) +} + +// ── exit-terminal ─────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ExitObs { + end: Answer, + after: Vec, + drive: (Outcome, Vec), + done: u32, + interrupted: Answer, +} + +fn exit_script(p: &Plugin) -> ExitObs { + let _ = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 90, 2); + s.push(FROM_CALLER, 0, &uplink(1000)); + s.push(FROM_FAR_END, 0, USAGE_DONE); + s.push(FROM_CALLER, 0, &uplink(1000)); + let end = s.push(FROM_CALLER, PIECE_LAST, &[]); + let collect = piece(p, &session(90, 2, FROM_KERNEL, 0, &[])); + let far = piece(p, &session(90, 2, FROM_FAR_END, 0, &downlink(96))); + let after = vec![ + format!( + "collect {:?} emitted={}", + collect.outcome, + collect.emitted.len() + ), + format!("far {:?} emitted={}", far.outcome, far.emitted.len()), + ]; + let drive = drive(p); + let mut t = Session::open(p, 91, 2); + let interrupted = t.push(FROM_CALLER, PIECE_LAST, &[]); + ExitObs { + end, + after, + drive, + done: s.log.done + t.log.done, + interrupted, + } +} + +fn exit_check(o: &ExitObs) -> Result { + if o.end.outcome != Outcome::Ready || !o.end.done || o.end.units_line() != usage_units(2) { + return Err(format!( + "the caller's end did not settle the open turn once and end the session: {:?}", + o.end + )); + } + if o.after != ["collect Refused emitted=0", "far Refused emitted=0"] { + return Err(format!("an ended session still answered: {:?}", o.after)); + } + if o.drive != (Outcome::Ready, Vec::new()) { + return Err(format!("drive named an ended session: {:?}", o.drive)); + } + if o.done != 2 { + return Err(format!("{} end(s) across two sessions", o.done)); + } + let i = &o.interrupted; + if i.outcome != Outcome::Ready || !i.done || !i.units.is_empty() { + return Err(format!( + "a session ended before its first frame answered {i:?}" + )); + } + Ok( + "a session ends ONCE: the caller's end settles the open turn's audio seconds once \ + (cumulative units final), carries the end exactly once, and afterwards the stream is \ + refused on every side and drive names nothing; a session torn down before its first \ + frame ends with no units and no fee. Sealing the session's one line is the kernel's \ + exit step" + .to_string(), + ) +} + +fn exit_terminal(rig: &Rig, slice: &str) -> bool { + judge( + slice, + "one session, one end", + &rig.both(exit_script), + &exit_check, + &|o: &ExitObs| ExitObs { + done: o.done + 1, + ..o.clone() + }, + ) +} + +// ── tool-reply ────────────────────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ToolObs { + log: Log, + /// Frames emitted once the call was announced. + announced: usize, + /// Frames emitted once the turn's usage arrived. + usage_at: usize, + /// The cumulative units once the turn closed. + units: Vec<(u32, u32, u64)>, +} + +struct ToolWire { + dialect: Dialect, + announce: Vec>, + usage: &'static [u8], + reply: Vec, +} + +fn tool_wire(dialect: Dialect) -> ToolWire { + match dialect { + Dialect::OpenAi => ToolWire { + dialect, + announce: vec![ + frame(&json!({"type": "response.output_item.added", + "item": {"type": "function_call", "call_id": "call_x", "name": "lookup"}})), + frame(&json!({"type": "response.function_call_arguments.delta", + "call_id": "call_x", "delta": "{\"q\":1}"})), + frame(&json!({"type": "response.function_call_arguments.done", + "call_id": "call_x", "name": "lookup", "arguments": "{\"q\":1}"})), + ], + usage: USAGE_DONE, + reply: frame(&json!({"type": "conversation.item.create", + "item": {"type": "function_call_output", "call_id": "call_x", + "output": "{\"ok\":true}"}})), + }, + Dialect::Gemini => ToolWire { + dialect, + announce: vec![frame(&json!({"toolCall": {"functionCalls": [ + {"id": "call_x", "name": "lookup", "args": {"q": 1}}]}}))], + usage: GEMINI_USAGE, + reply: frame(&json!({"toolResponse": {"functionResponses": [ + {"id": "call_x", "name": "lookup", "response": {"ok": true}}]}})), + }, + } +} + +fn tool_script(p: &Plugin, w: &ToolWire) -> ToolObs { + let _ = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 95, w.dialect.claim()); + for f in &w.announce { + s.push(FROM_FAR_END, 0, f); + } + let announced = s.emitted(); + s.push(FROM_FAR_END, 0, w.usage); + let usage_at = s.emitted(); + let units = s.log.units.clone(); + s.push(FROM_CALLER, 0, &w.reply); + ToolObs { + log: s.log, + announced, + usage_at, + units, + } +} + +fn phase(log: &Log, from: usize, to: usize) -> Log { + Log { + emissions: log.emissions.get(from..to).unwrap_or_default().to_vec(), + ..Log::default() + } +} + +fn tool_check(dialect: Dialect, o: &ToolObs) -> Result { + let name = dialect.name(); + if !o.log.anomalies.is_empty() { + return Err(format!( + "{name}: not every piece READY: {:?}", + o.log.anomalies + )); + } + let announce = phase(&o.log, 0, o.announced); + if announce.emissions.iter().any(|e| e.to_far) { + return Err(format!( + "{name}: the door sent the far end something for a call it was only to relay (the \ + gateway authors no result): {:?}", + announce.emissions + )); + } + let call = vec![ + Norm::ToolOpen("call_x".to_string(), "lookup".to_string()), + Norm::ToolArgs("call_x".to_string(), json!({"q": 1})), + Norm::ToolClose("call_x".to_string()), + ]; + let relayed = caller_norms(dialect, &announce); + if fingerprint(&relayed) != fingerprint(&call) { + return Err(format!( + "{name}: the call reached the caller as {relayed:?}" + )); + } + if o.usage_at != o.announced { + return Err(format!( + "{name}: the turn's usage was relayed instead of reported" + )); + } + if !o.units.iter().any(|u| *u == (5, 1, 1)) { + return Err(format!( + "{name}: the call was not counted once: {:?}", + o.units + )); + } + let reply = phase(&o.log, o.usage_at, o.log.emissions.len()); + let carried = far_norms(dialect, &reply); + let ok = reply.emissions.len() == 1 + && reply.emissions[0].to_far + && matches!(carried.as_slice(), [Norm::ToolResult(id, out)] if id == "call_x" && *out == json!({"ok": true})); + if !ok { + return Err(format!( + "{name}: the caller's own result reached the far end as {carried:?} ({} frame(s))", + reply.emissions.len() + )); + } + Ok(format!( + "{name}: the far end's call reached the caller as it was made and the door sent the far \ + end nothing for it; the call counted once; the caller's own result reached the far end \ + alone (no door-authored response)" + )) +} + +fn tool_reply(rig: &Rig, slice: &str) -> bool { + let mut pass = true; + for dialect in [Dialect::OpenAi, Dialect::Gemini] { + let w = tool_wire(dialect); + pass &= judge( + slice, + &format!("{} tool call relayed, never answered", dialect.name()), + &rig.both(|p| tool_script(p, &w)), + &|o: &ToolObs| tool_check(dialect, o), + &|o: &ToolObs| { + let mut planted = o.clone(); + planted.log.emissions.insert( + 0, + Emission { + to_far: true, + frame: w.reply.clone(), + need: dialect.ride(), + verb: "GET".to_string(), + target: dialect.socket().to_string(), + }, + ); + planted.announced += 1; + planted.usage_at += 1; + planted + }, + ); + } + pass +} diff --git a/crates/busbar-plane-streaming/examples/voice_conform/door.rs b/crates/busbar-plane-streaming/examples/voice_conform/door.rs new file mode 100644 index 0000000000..a45c4ea4a7 --- /dev/null +++ b/crates/busbar-plane-streaming/examples/voice_conform/door.rs @@ -0,0 +1,754 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE DOOR, BOTH WAYS: the plumbing every leg drives the streaming plane through. +//! +//! The same door is loaded twice through the ONE loader: compiled in +//! (`busbar_plane_streaming::door::door`, [`load_linked`]) and dropped in (the `streaming_door` +//! example `cdylib` beside this harness, [`load_dropped`] against the linked row's own Statement). +//! Every leg builds a fresh instance of each, drives both with one script, and [`judge`]s the pair: +//! the two observations must be equal, the observation must pass the leg's check, and the leg's +//! PLANTED WRONG ANSWER must be refused by the same check (the RED arm, run in the same process on +//! every assertion). A dropped door that is not built or does not load is a FAIL, never a skip. + +use std::cell::Cell; +use std::fmt::Debug; +use std::path::PathBuf; +use std::sync::Arc; + +use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome, Span, BLOB_JSON, BLOB_OCTETS}; +use busbar_contract::abi::mechanism::lifecycle::{slot as life, TickIn, TickOut}; +use busbar_contract::abi::plane::{ + slot, ArriveIn, ArriveOut, OnPieceIn, OnPieceOut, OutField, PlaneDriveIn, PlaneDriveOut, + PlaneOpenIn, PlaneOpenOut, RecordWrite, RefusalIn, RefusalOut, UnitCount, CLAIM_EXACT, + CLAIM_OPEN, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_KERNEL, REFUSAL_KERNEL, +}; +use busbar_plane_streaming::door; +use busbar_plugin_loader::dispatch::kinds::plane::Plane; +use busbar_plugin_loader::dispatch::{ + in_head, load_dropped, load_linked, out_head, Bind, DispatchConfig, Dispatcher, Frame, + LinkedRow, NoSink, Plugin, +}; + +/// The environment variable naming the dropped-in door's library, when it is not beside the +/// harness. +pub const DOOR_ENV: &str = "VOICE_CONFORM_DOOR"; + +/// The environment variable that turns every planted wrong answer ON as the subject: each leg must +/// then go RED (the RED arm, observable from the runner). +pub const RED_ENV: &str = "VOICE_CONFORM_RED"; + +/// The deployment's public base URL every leg opens the door under. +pub const PUBLIC: &str = "https://gw.example.com/ignored?q=1#f"; + +/// The `streams:` section every leg opens under: the session params a session locks. +pub const SESSION: &[u8] = br#"{"session":{"model":"m-cap"}}"#; + +/// The model the section names: the DIRECT route a reaching door names. +pub const MODEL: &str = "m-cap"; + +/// The kernel's opaque reference for the caller (never the principal, never a credential). +pub const CALLER_REF: &str = "c0ffee"; + +/// A value all of whose bytes are zero. +pub fn z() -> T { + // SAFETY: called only for the plane ABI's plain C structs (`in`/`out` frames, `UnitCount`, + // `OutField`, `RecordWrite`): integers, raw pointers and spans, for which all-zero is valid. + unsafe { std::mem::zeroed() } +} + +/// `n` zeroed buffer elements. +fn zeros(n: usize) -> Vec { + (0..n).map(|_| z()).collect() +} + +/// A JSON blob over static bytes. +pub fn json(b: &'static [u8]) -> Blob { + Blob { + ptr: b.as_ptr(), + len: b.len(), + fmt: BLOB_JSON, + flags: 0, + } +} + +/// An octet blob over `b`. +fn octets(b: &[u8]) -> Blob { + Blob { + ptr: b.as_ptr(), + len: b.len(), + fmt: BLOB_OCTETS, + flags: 0, + } +} + +/// A static string, as the ABI lends it. +pub fn text(s: &'static str) -> AbiStr { + AbiStr { + ptr: s.as_ptr(), + len: s.len(), + } +} + +/// A string the plugin lent, copied. +pub fn read(s: AbiStr) -> String { + if s.ptr.is_null() || s.len == 0 { + return String::new(); + } + // SAFETY: the plugin's text, valid while its generation is live (read before the next call). + String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(s.ptr, s.len) }).into_owned() +} + +/// The bytes a span names in `buf`; `` for a span past it. +fn at(buf: &[u8], s: Span) -> String { + let from = s.offset as usize; + buf.get(from..from + s.len as usize).map_or_else( + || "".to_string(), + |b| String::from_utf8_lossy(b).into_owned(), + ) +} + +/// Keep a line printable: one line, bounded. +pub fn clip(s: &str) -> String { + let one: String = s.chars().map(|c| if c == '\n' { ' ' } else { c }).collect(); + if one.chars().count() > 360 { + let head: String = one.chars().take(360).collect(); + format!("{head}…") + } else { + one + } +} + +/// Whether the planted wrong answers are the subject (`VOICE_CONFORM_RED` set). +pub fn red_mode() -> bool { + std::env::var_os(RED_ENV).is_some() +} + +// ── the two doors ─────────────────────────────────────────────────────────────────────────────── + +/// The harness's one dispatcher and the two ways it reaches the door. +pub struct Rig { + dispatcher: Dispatcher, + count: Cell, + dropped_at: Result, + stated: Vec, +} + +/// Where the dropped-in door is: `VOICE_CONFORM_DOOR`, else the `streaming_door` library beside +/// this harness (`cargo build --example` puts both in the profile's `examples/`). +fn dropped_path() -> Result { + if let Some(p) = std::env::var_os(DOOR_ENV) { + let p = PathBuf::from(p); + return if p.is_file() { + Ok(p) + } else { + Err(format!("{DOOR_ENV}={} names no file", p.display())) + }; + } + let exe = std::env::current_exe().map_err(|e| format!("the harness has no path: {e}"))?; + let Some(dir) = exe.parent() else { + return Err("the harness's path has no directory".to_string()); + }; + let name = format!( + "{}streaming_door{}", + std::env::consts::DLL_PREFIX, + std::env::consts::DLL_SUFFIX + ); + let path = dir.join(name); + if path.is_file() { + Ok(path) + } else { + Err(format!( + "the dropped-in door is not built beside the harness ({}): build the \ + `streaming_door` example", + path.display() + )) + } +} + +impl Rig { + /// The dispatcher, the linked row's Statement, and where the dropped-in door lives. + pub fn boot() -> Self { + let row = LinkedRow::of(door::door).expect("the streaming door states itself"); + Rig { + dispatcher: Dispatcher::new(DispatchConfig::default()), + count: Cell::new(0), + dropped_at: dropped_path(), + stated: row.statement, + } + } + + /// The bind for one fresh instance (two instances never share a label). + fn bind(&self, leg: &str) -> Bind { + let n = self.count.get() + 1; + self.count.set(n); + Bind { + instance: Arc::from(format!("{leg}-{n}")), + max_inflight_cap: 8, + sink: Arc::new(NoSink), + dispatcher: self.dispatcher.adopter(), + conns: None, + } + } + + /// A fresh instance of the LINKED door. + pub fn linked(&self) -> Plugin { + let row = LinkedRow::of(door::door).expect("the streaming door states itself"); + load_linked::(&row, self.bind("linked")).expect("the linked streaming door loads") + } + + /// A fresh instance of the DROPPED door, admitted against the Statement the linked row states. + pub fn dropped(&self) -> Result, String> { + let path = match &self.dropped_at { + Ok(path) => path, + Err(e) => return Err(e.clone()), + }; + load_dropped::(path, &self.stated, self.bind("dropped")).map_err(|e| { + format!( + "the dropped-in door at {} did not load: {e:?}", + path.display() + ) + }) + } + + /// `script` run on a fresh instance of each door. + pub fn both(&self, script: impl Fn(&Plugin) -> T) -> (T, Result) { + let linked = script(&self.linked()); + let dropped = self.dropped().map(|p| script(&p)); + (linked, dropped) + } +} + +// ── the verdict ───────────────────────────────────────────────────────────────────────────────── + +/// One `RESULT` line. +pub fn result(slice: &str, pass: bool, what: &str, detail: &str) { + let verdict = if pass { "PASS" } else { "FAIL" }; + println!("RESULT {slice} {verdict} {what} — {}", clip(detail)); +} + +/// A leg's check over one observation: `Ok(what it proved)` or `Err(what is wrong)`. +pub type Check<'a, T> = &'a dyn Fn(&T) -> Result; + +/// JUDGE ONE ASSERTION over both doors. PASS only when (1) the dropped door loaded and answered +/// exactly as the linked door did, (2) the linked door's observation passes `check`, and (3) the +/// RED ARM bit: `plant` (the leg's wrong door, restated as the wrong answer it would give) changes +/// the observation and `check` refuses it. Under [`RED_ENV`] the planted answer IS the subject, so +/// the line must come out FAIL. Prints the line; answers whether it passed. +pub fn judge( + slice: &str, + what: &str, + seen: &(T, Result), + check: Check<'_, T>, + plant: &dyn Fn(&T) -> T, +) -> bool { + let (linked, dropped) = seen; + let subject = if red_mode() { + plant(linked) + } else { + linked.clone() + }; + let mut wrong = Vec::new(); + match dropped { + Ok(dropped) if *dropped == subject => {} + Ok(dropped) => wrong.push(format!( + "the linked and dropped doors answered differently: {} vs {}", + clip(&format!("{subject:?}")), + clip(&format!("{dropped:?}")) + )), + Err(e) => wrong.push(e.clone()), + } + let proved = match check(&subject) { + Ok(proved) => proved, + Err(e) => { + wrong.push(e); + String::new() + } + }; + if !red_mode() { + let planted = plant(linked); + if planted == *linked { + wrong.push("RED arm: the planted wrong answer changed nothing".to_string()); + } else if let Ok(accepted) = check(&planted) { + wrong.push(format!( + "RED arm: the check accepted a planted wrong answer ({accepted})" + )); + } + } + if wrong.is_empty() { + result( + slice, + true, + what, + &format!("{proved}; linked == dropped; planted wrong answer refused"), + ); + true + } else { + result(slice, false, what, &wrong.join("; ")); + false + } +} + +// ── the lifecycle ─────────────────────────────────────────────────────────────────────────────── + +/// One claim a snapshot publishes. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Claimed { + /// `VERB TARGET CARRIER[ exact][ open]`. + pub line: String, + /// The dialect a refusal on it is rendered in. + pub refusal_dialect: u16, +} + +/// What an `open` answered: its outcome, and the snapshot it published. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Opened { + /// The outcome. + pub outcome: Outcome, + /// The published claims. + pub claims: Vec, + /// The audience a caller's token must carry. + pub audience: String, +} + +/// `open` generation 1 over `settings`, under `public_url`. +pub fn open( + p: &Plugin, + settings: &'static [u8], + public_url: Option<&'static str>, +) -> Opened { + let mut i: PlaneOpenIn = z(); + i.open.head = in_head(); + i.open.generation = 1; + i.open.settings = json(settings); + if let Some(u) = public_url { + i.public_url = text(u); + } + let mut o: PlaneOpenOut = z(); + o.open.head = out_head(); + let mut f = Frame::new(i, o); + let c = p.call(life::OPEN, &mut f); + let mut opened = Opened { + outcome: c.outcome, + claims: Vec::new(), + audience: String::new(), + }; + let snap = f.out.snapshot; + if c.outcome != Outcome::Ready || snap.is_null() { + return opened; + } + // SAFETY: a READY open publishes its generation's snapshot, valid until that generation's + // `retire` (never called here). + let s = unsafe { &*snap }; + for n in 0..s.claims_len { + // SAFETY: the snapshot names `claims_len` claims. + let c = unsafe { &*s.claims.add(n) }; + let exact = if c.flags & CLAIM_EXACT != 0 { + " exact" + } else { + "" + }; + let open = if c.flags & CLAIM_OPEN != 0 { + " open" + } else { + "" + }; + opened.claims.push(Claimed { + line: format!( + "{} {} {}{exact}{open}", + read(c.verb), + read(c.target), + read(c.carrier) + ), + refusal_dialect: c.refusal_dialect, + }); + } + opened.audience = read(s.audience); + opened +} + +/// What `arrive` answered for a claim. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Arrived { + /// The outcome. + pub outcome: Outcome, + /// The dialect index. + pub dialect: u32, + /// The op class index. + pub op_class: u32, + /// The principal need. + pub principal: u32, + /// The route class and the entry it names. + pub route: (u8, String), + /// The plane's refusal code and status. + pub refusal: (u32, u32), + /// The units the admission was told to expect. + pub units: u32, +} + +/// `arrive` on `claim`. +pub fn arrive(p: &Plugin, claim: u32) -> Arrived { + let mut units: Vec = zeros(8); + let mut a: Frame = Frame::new(z(), z()); + a.input.head = in_head(); + a.input.claim = claim; + a.input.unit = 900 + u64::from(claim); + a.input.units_buf = units.as_mut_ptr(); + a.input.units_cap = units.len(); + a.out.head = out_head(); + let c = p.call(slot::ARRIVE, &mut a); + Arrived { + outcome: c.outcome, + dialect: a.out.dialect, + op_class: a.out.op_class, + principal: a.out.principal_need, + route: (a.out.route, read(a.out.pool)), + refusal: (a.out.refusal, a.out.refusal_status), + units: a.out.units_written, + } +} + +/// What `refusal` rendered. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Rendered { + /// The outcome. + pub outcome: Outcome, + /// The rendered body. + pub body: String, + /// Its head fields. + pub fields: Vec<(String, String)>, + /// The status it answered (`0` = the kernel's). + pub status: u32, + /// The record writes it made. + pub records: u32, +} + +/// The kernel refuses unit `unit` on `dialect` with `status` and `words`: the door renders it. +pub fn refusal( + p: &Plugin, + unit: u64, + dialect: u32, + status: u32, + words: &'static str, +) -> Rendered { + let mut reply = vec![0u8; 4096]; + let mut fields: Vec = zeros(4); + let mut arena = vec![0u8; 512]; + let mut records: Vec = zeros(4); + let mut i: RefusalIn = z(); + i.head = in_head(); + i.cause = REFUSAL_KERNEL; + i.status = status; + i.dialect = dialect; + i.text = text(words); + i.unit = unit; + (i.reply_buf, i.reply_cap) = (reply.as_mut_ptr(), reply.len()); + (i.fields_buf, i.fields_cap) = (fields.as_mut_ptr(), fields.len()); + (i.arena_buf, i.arena_cap) = (arena.as_mut_ptr(), arena.len()); + (i.records_buf, i.records_cap) = (records.as_mut_ptr(), records.len()); + let mut o: RefusalOut = z(); + o.head = out_head(); + let mut f = Frame::new(i, o); + let c = p.call(slot::REFUSAL, &mut f); + let out = f.out; + let written = (out.reply_written as usize).min(reply.len()); + let n = (out.fields_written as usize).min(fields.len()); + Rendered { + outcome: c.outcome, + body: String::from_utf8_lossy(&reply[..written]).into_owned(), + fields: fields[..n] + .iter() + .map(|f| (at(&arena, f.name), at(&arena, f.value))) + .collect(), + status: out.status, + records: out.records_written, + } +} + +/// `drive`: the streams whose sessions owe output of their own. +pub fn drive(p: &Plugin) -> (Outcome, Vec) { + let mut sessions = [0_u64; 16]; + let mut f: Frame = Frame::new(z(), z()); + f.input.drive.head = in_head(); + (f.input.sessions_buf, f.input.sessions_cap) = (sessions.as_mut_ptr(), sessions.len()); + f.out.head = out_head(); + let c = p.call(life::DRIVE, &mut f); + let n = (f.out.sessions_written as usize).min(sessions.len()); + (c.outcome, sessions[..n].to_vec()) +} + +/// `tick` at `now_ns`: its outcome and the next tick it asks for. +pub fn tick(p: &Plugin, now_ns: u64) -> (Outcome, u64) { + let mut k: Frame = Frame::new(z(), z()); + k.input.head = in_head(); + k.input.now_ns = now_ns; + k.out.head = out_head(); + let c = p.call(life::TICK, &mut k); + (c.outcome, k.out.next_tick_ns) +} + +// ── a piece and its answer ────────────────────────────────────────────────────────────────────── + +/// One piece pushed to a unit. +#[derive(Clone, Copy, Debug)] +pub struct Piece<'a> { + /// The kernel's unit key. + pub unit: u64, + /// A live session's stream (`0` = a request unit). + pub stream: u64, + /// The claim it arrived on. + pub claim: u32, + /// Who pushed it. + pub from: u32, + /// `PIECE_*`. + pub flags: u32, + /// An ATTEMPT's number (`0` = none). + pub attempt_no: u32, + /// Its bytes. + pub bytes: &'a [u8], + /// The far end's status, with `PIECE_HAS_STATUS`. + pub status: u32, + /// The capacity of the units buffer the host lends. + pub units_cap: usize, +} + +/// A piece of request unit `unit` on `claim`. +pub const fn request(unit: u64, claim: u32, from: u32, flags: u32, bytes: &[u8]) -> Piece<'_> { + Piece { + unit, + stream: 0, + claim, + from, + flags, + attempt_no: 0, + bytes, + status: 0, + units_cap: 16, + } +} + +/// The ATTEMPT that opens attempt `n` of request unit `unit` on `claim`. +pub const fn attempt(unit: u64, claim: u32, n: u32) -> Piece<'static> { + Piece { + attempt_no: n, + ..request(unit, claim, FROM_KERNEL, 0, &[]) + } +} + +/// A piece of the live session on stream `stream` (claim `claim`). +pub const fn session(stream: u64, claim: u32, from: u32, flags: u32, bytes: &[u8]) -> Piece<'_> { + Piece { + stream, + ..request(stream, claim, from, flags, bytes) + } +} + +/// What the door answered one piece with. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Answer { + /// The outcome. + pub outcome: Outcome, + /// The bytes it emitted. + pub emitted: Vec, + /// `more`. + pub more: u32, + /// The emission is bound for the far end. + pub to_far: bool, + /// The unit (or session) is done. + pub done: bool, + /// The caller's status. + pub status: u32, + /// The far request's verb and target. + pub verb: String, + /// See `verb`. + pub target: String, + /// The head fields it wrote. + pub fields: Vec<(String, String)>, + /// The units it reported: (class, source, amount). + pub units: Vec<(u32, u32, u64)>, + /// A short answer's units room. + pub units_needed: u32, + /// The declared need a far request rides (`0` = none). + pub need: u32, + /// The record writes it made. + pub records: u32, +} + +impl Answer { + /// The units as `class:source=amount` words. + pub fn units_line(&self) -> String { + if self.units_needed != 0 { + return format!("needed {}", self.units_needed); + } + self.units + .iter() + .map(|(c, s, a)| format!("{c}:{s}={a}")) + .collect::>() + .join(" ") + } +} + +/// Push `x` and read the answer. +pub fn piece(p: &Plugin, x: &Piece<'_>) -> Answer { + let mut reply = vec![0u8; 1 << 18]; + let mut units: Vec = zeros(x.units_cap); + let mut records: Vec = zeros(4); + let mut fields: Vec = zeros(8); + let mut arena = vec![0u8; 4096]; + let mut i: OnPieceIn = z(); + i.head = in_head(); + (i.unit, i.stream, i.claim, i.from, i.flags) = (x.unit, x.stream, x.claim, x.from, x.flags); + (i.attempt_no, i.bytes, i.status_code) = (x.attempt_no, octets(x.bytes), x.status); + i.caller_ref = text(CALLER_REF); + (i.reply_buf, i.reply_cap) = (reply.as_mut_ptr(), reply.len()); + (i.units_buf, i.units_cap) = (units.as_mut_ptr(), units.len()); + (i.records_buf, i.records_cap) = (records.as_mut_ptr(), records.len()); + (i.fields_buf, i.fields_cap) = (fields.as_mut_ptr(), fields.len()); + (i.arena_buf, i.arena_cap) = (arena.as_mut_ptr(), arena.len()); + let mut o: OnPieceOut = z(); + o.head = out_head(); + let mut f = Frame::new(i, o); + let c = p.call(slot::ON_PIECE, &mut f); + let o = f.out; + let emitted = (o.emitted as usize).min(reply.len()); + let nu = (o.units_written as usize).min(units.len()); + let nf = (o.fields_written as usize).min(fields.len()); + Answer { + outcome: c.outcome, + emitted: reply[..emitted].to_vec(), + more: o.more, + to_far: o.flags & EMIT_TO_FAR_END != 0, + done: o.flags & EMIT_DONE != 0, + status: o.reply_status, + verb: at(&arena, o.verb), + target: at(&arena, o.target), + fields: fields[..nf] + .iter() + .map(|f| (at(&arena, f.name), at(&arena, f.value))) + .collect(), + units: units[..nu] + .iter() + .map(|u| (u.class, u.source, u.amount)) + .collect(), + units_needed: o.units_needed, + need: o.need, + records: o.records_written, + } +} + +// ── one live session ──────────────────────────────────────────────────────────────────────────── + +/// One frame the door emitted on a session. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Emission { + /// Bound for the far end (else the caller). + pub to_far: bool, + /// The frame. + pub frame: Vec, + /// The declared need a far frame rides. + pub need: u32, + /// A far frame's verb and target. + pub verb: String, + /// See `verb`. + pub target: String, +} + +/// Everything one session showed: every answer that was not READY (or owed `more`), every frame +/// in the order it was emitted, the last cumulative units, the record writes and how many answers +/// ended it. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Log { + /// `what Outcome` per answer that was not a plain READY. + pub anomalies: Vec, + /// Every frame, in emission order. + pub emissions: Vec, + /// The last READY answer's cumulative units. + pub units: Vec<(u32, u32, u64)>, + /// Record writes across every answer. + pub records: u32, + /// Answers that carried the session's end. + pub done: u32, +} + +/// A live session on one door instance. +pub struct Session<'p> { + p: &'p Plugin, + stream: u64, + claim: u32, + /// What it showed so far. + pub log: Log, +} + +impl<'p> Session<'p> { + /// The session on `stream` (door `claim`), opened by the caller's first (empty) piece. + pub fn open(p: &'p Plugin, stream: u64, claim: u32) -> Self { + let mut s = Session { + p, + stream, + claim, + log: Log::default(), + }; + let a = piece(p, &session(stream, claim, FROM_CALLER, 0, &[])); + s.note("open", &a); + s + } + + fn note(&mut self, what: &str, a: &Answer) { + if a.outcome != Outcome::Ready || a.more != 0 { + self.log + .anomalies + .push(format!("{what} {:?} more={}", a.outcome, a.more)); + } + if !a.emitted.is_empty() { + self.log.emissions.push(Emission { + to_far: a.to_far, + frame: a.emitted.clone(), + need: a.need, + verb: a.verb.clone(), + target: a.target.clone(), + }); + } + if a.outcome == Outcome::Ready { + self.log.units.clone_from(&a.units); + } + self.log.records += a.records; + if a.done { + self.log.done += 1; + } + } + + /// Push `bytes` from `from` (with `flags`), then collect every frame the session still owes. + /// Answers the piece's own answer. + pub fn push(&mut self, from: u32, flags: u32, bytes: &[u8]) -> Answer { + let a = piece( + self.p, + &session(self.stream, self.claim, from, flags, bytes), + ); + self.note("piece", &a); + if a.outcome == Outcome::Ready && !a.done { + self.collect(); + } + a + } + + /// Collect (`FROM_KERNEL`, nothing, no attempt) until the session owes nothing. + pub fn collect(&mut self) { + for _ in 0..64 { + let a = piece( + self.p, + &session(self.stream, self.claim, FROM_KERNEL, 0, &[]), + ); + let quiet = a.emitted.is_empty() && !a.done; + self.note("collect", &a); + if quiet || a.done || a.outcome != Outcome::Ready { + return; + } + } + self.log.anomalies.push("collect never ran dry".to_string()); + } + + /// The frames emitted so far. + pub fn emitted(&self) -> usize { + self.log.emissions.len() + } +} diff --git a/crates/busbar-plane-streaming/examples/voice_conform/governance.rs b/crates/busbar-plane-streaming/examples/voice_conform/governance.rs new file mode 100644 index 0000000000..c34b059a8c --- /dev/null +++ b/crates/busbar-plane-streaming/examples/voice_conform/governance.rs @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE FIVE VISION CHECKPOINTS, AT THE DOOR — observations, never a conformance result (the runner +//! keeps governance out of the conformance tally). Each is probed on both doors and judged like +//! any leg; the process always exits 0. + +use busbar_contract::abi::mechanism::call::Outcome; +use busbar_contract::abi::plane::{FROM_CALLER, FROM_FAR_END, PIECE_LAST}; +use busbar_plane_streaming::session_door::CEILING_TICK_NS; +use busbar_plane_streaming::session_pump::SESSION_CEILING_REASON; +use busbar_plugin_loader::dispatch::kinds::plane::Plane; +use busbar_plugin_loader::dispatch::Plugin; +use serde_json::json; + +use crate::codec_legs::{caller_norms, downscope_bridge, far_norms, frame, Dialect, Norm}; +use crate::composition::{downlink, uplink, usage_units, USAGE_DONE}; +use crate::door::{ + drive, judge, open, piece, refusal, result, session, tick, Emission, Log, Piece, Rendered, Rig, + Session, PUBLIC, SESSION, +}; + +/// `governance `: one observation; always exit 0. +pub fn run(rig: &Rig, checkpoint: &str) -> i32 { + match checkpoint { + "V1-barge-in-preemption" => barge_in(rig, checkpoint), + "V2-turn-budget-enforcement" => ceiling(rig, checkpoint), + "V3-metering-lease-settled" => settled_once(rig, checkpoint), + "V4-dialect-downscope" => downscope(rig, checkpoint), + "D2-hard-close-on-exhaustion" => hard_close(rig, checkpoint), + other => result(other, false, "checkpoint", "unknown checkpoint"), + } + 0 +} + +// ── V1: a barge-in preempts the turn ──────────────────────────────────────────────────────────── + +fn barge_script(p: &Plugin) -> Log { + let _ = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 100, 2); + // 96 bytes of 24 kHz PCM16: the caller has heard 2 ms of the far end's answer. + s.push(FROM_FAR_END, 0, &downlink(96)); + s.push( + FROM_FAR_END, + 0, + &frame(&json!({"type": "input_audio_buffer.speech_started", + "audio_start_ms": 0, "item_id": "it1"})), + ); + s.log +} + +fn barge_check(log: &Log) -> Result { + let far = far_norms(Dialect::OpenAi, log); + let near = caller_norms(Dialect::OpenAi, log); + if !log.anomalies.is_empty() { + return Err(format!("not every piece READY: {:?}", log.anomalies)); + } + if far != [Norm::ResponseCancel, Norm::Truncate(2)] { + return Err(format!("the barge-in sent the far end {far:?}")); + } + if near != [Norm::AudioDown(vec![0; 96]), Norm::SpeechStart] { + return Err(format!("the caller was sent {near:?}")); + } + Ok( + "the far end's speech_started preempts the turn: the door cancels the response and \ + truncates the item at the 2 ms the caller heard, and tells the caller" + .to_string(), + ) +} + +fn barge_in(rig: &Rig, checkpoint: &str) { + judge( + checkpoint, + "barge-in", + &rig.both(barge_script), + &barge_check, + &|l: &Log| { + let mut planted = l.clone(); + if let Some(i) = planted.emissions.iter().rposition(|e| e.to_far) { + planted.emissions.remove(i); + } + planted + }, + ); +} + +// ── V2: a session is bounded ──────────────────────────────────────────────────────────────────── + +const CEILING: &[u8] = br#"{"session":{"model":"m-cap"},"session_max_secs":1}"#; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct CeilingObs { + next: u64, + owed: Vec, + log: Log, +} + +fn ceiling_script(p: &Plugin) -> CeilingObs { + let _ = open(p, CEILING, Some(PUBLIC)); + let mut s = Session::open(p, 110, 2); + let (_, next) = tick(p, 1_000); + let _ = tick(p, 1_000 + 1_000_000_000); + let (_, owed) = drive(p); + s.collect(); + CeilingObs { + next, + owed, + log: s.log, + } +} + +fn ceiling_check(o: &CeilingObs) -> Result { + if o.next != 1_000 + CEILING_TICK_NS { + return Err(format!( + "a ceilinged generation asked for its next tick at {}", + o.next + )); + } + if o.owed != [110] { + return Err(format!("drive named {:?} once the ceiling passed", o.owed)); + } + let told = caller_norms(Dialect::OpenAi, &o.log); + let said = matches!(told.as_slice(), [Norm::Error(code, _)] if code == SESSION_CEILING_REASON); + if !o.log.anomalies.is_empty() || !said || o.log.done != 1 { + return Err(format!( + "past its ceiling the session told the caller {told:?} and ended {} time(s)", + o.log.done + )); + } + Ok(format!( + "a session past `streams.session_max_secs` is told why ({SESSION_CEILING_REASON}) and \ + ended once, on the kernel's tick clock; a money cut is the kernel's, rendered through \ + `refusal`" + )) +} + +fn ceiling(rig: &Rig, checkpoint: &str) { + judge( + checkpoint, + "the wall-clock ceiling", + &rig.both(ceiling_script), + &ceiling_check, + &|o: &CeilingObs| { + let mut planted = o.clone(); + planted.log.done = 0; + planted + }, + ); +} + +// ── V3: a turn's units settle once ────────────────────────────────────────────────────────────── + +/// The three answers of a turn whose report is re-called once, as lines. +#[derive(Clone, Debug, PartialEq, Eq)] +struct Settled { + lines: Vec, +} + +fn settle_script(p: &Plugin) -> Settled { + let _ = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 120, 2); + s.push(FROM_CALLER, 0, &uplink(1000)); + let narrow = Piece { + units_cap: 2, + ..session(120, 2, FROM_FAR_END, 0, USAGE_DONE) + }; + let a = piece(p, &narrow); + let b = piece(p, &session(120, 2, FROM_FAR_END, 0, USAGE_DONE)); + let c = piece(p, &session(120, 2, FROM_CALLER, PIECE_LAST, &[])); + Settled { + lines: vec![ + format!("narrow {:?} units=[{}]", a.outcome, a.units_line()), + format!("re-call {:?} units=[{}]", b.outcome, b.units_line()), + format!( + "end {:?} done={} units=[{}]", + c.outcome, + c.done, + c.units_line() + ), + ], + } +} + +fn settle_check(o: &Settled) -> Result { + let lines = &o.lines; + let want = [ + "narrow Failed units=[needed 6]".to_string(), + format!("re-call Ready units=[{}]", usage_units(1)), + format!("end Ready done=true units=[{}]", usage_units(1)), + ]; + if *lines != want { + return Err(format!("the turn settled as {lines:?}; want {want:?}")); + } + Ok( + "a turn's usage settles exactly once: the re-call of a short answer reports the same \ + cumulative units, and the end adds nothing already settled" + .to_string(), + ) +} + +fn settled_once(rig: &Rig, checkpoint: &str) { + judge( + checkpoint, + "units settle once", + &rig.both(settle_script), + &settle_check, + &|o: &Settled| { + let mut planted = o.clone(); + planted.lines[1] = planted.lines[1].replace("0:1=10", "0:1=20"); + planted + }, + ); +} + +// ── V4: crossing a dialect down-scopes ────────────────────────────────────────────────────────── + +fn widened(text: &str) -> bool { + text.contains("semantic") || text.contains("g711") +} + +fn downscope_script(p: &Plugin) -> Log { + let _ = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 140, 2); + s.push(FROM_CALLER, 0, &frame(&downscope_update())); + s.log +} + +fn downscope_update() -> serde_json::Value { + json!({"type": "session.update", "session": {"instructions": "x", + "turn_detection": {"type": "semantic_vad", "eagerness": "high"}, + "input_audio_format": "g711_ulaw", "output_audio_format": "g711_ulaw"}}) +} + +fn downscope_check(log: &Log) -> Result { + let bridged = downscope_bridge(&downscope_update()); + if !bridged.contains("Config") || widened(&bridged) { + return Err(format!("the bridge toward Gemini widened: {bridged}")); + } + let far: Vec<&Emission> = log.emissions.iter().filter(|e| e.to_far).collect(); + if !log.anomalies.is_empty() + || far.len() != 1 + || far + .iter() + .any(|e| widened(&String::from_utf8_lossy(&e.frame))) + || !far_norms(Dialect::OpenAi, log) + .iter() + .any(|n| matches!(n, Norm::Config(_))) + { + return Err(format!( + "the caller's session.update reached the far end as {far:?}" + )); + } + Ok( + "an OpenAI-only semantic_vad/g711 concept is down-scoped, never widened, toward Gemini; \ + and at the door the caller's session.update never reaches the far end: the locked \ + session params do" + .to_string(), + ) +} + +fn downscope(rig: &Rig, checkpoint: &str) { + judge( + checkpoint, + "down-scope", + &rig.both(downscope_script), + &downscope_check, + &|l: &Log| { + let mut planted = l.clone(); + for e in &mut planted.emissions { + if e.to_far { + e.frame = frame(&downscope_update()); + } + } + planted + }, + ); +} + +// ── D2: an ended session is closed hard ───────────────────────────────────────────────────────── + +#[derive(Clone, Debug, PartialEq, Eq)] +struct CloseObs { + log: Log, + late: (Outcome, usize), + cut: Rendered, +} + +fn close_script(p: &Plugin) -> CloseObs { + let _ = open(p, SESSION, Some(PUBLIC)); + let mut s = Session::open(p, 130, 2); + s.push(FROM_FAR_END, 0, &downlink(96)); + s.push(FROM_CALLER, PIECE_LAST, &[]); + let late = piece(p, &session(130, 2, FROM_FAR_END, 0, &downlink(96))); + CloseObs { + log: s.log, + late: (late.outcome, late.emitted.len()), + cut: refusal(p, 130, 0, 429, "the caller's budget is exhausted"), + } +} + +fn close_check(o: &CloseObs) -> Result { + if o.log.done != 1 || !o.log.anomalies.is_empty() { + return Err(format!("the session did not end once: {:?}", o.log)); + } + if o.late != (Outcome::Refused, 0) { + return Err(format!( + "far-end audio after the end was answered {:?}", + o.late + )); + } + let v: serde_json::Value = serde_json::from_str(&o.cut.body).unwrap_or_default(); + if o.cut.outcome != Outcome::Ready || v["error"]["type"] != "rate_limit_error" { + return Err(format!("the cut rendered as {:?}", o.cut)); + } + Ok( + "once a session has ended no far-end audio reaches the caller (the stream is refused) \ + and the cut renders in the dialect's error shape; reading the chain dry and cutting \ + are the kernel's" + .to_string(), + ) +} + +fn hard_close(rig: &Rig, checkpoint: &str) { + judge( + checkpoint, + "hard close", + &rig.both(close_script), + &close_check, + &|o: &CloseObs| CloseObs { + late: (Outcome::Ready, 128), + ..o.clone() + }, + ); +} diff --git a/crates/busbar-plane-streaming/examples/voice_conform/main.rs b/crates/busbar-plane-streaming/examples/voice_conform/main.rs new file mode 100644 index 0000000000..5740167c04 --- /dev/null +++ b/crates/busbar-plane-streaming/examples/voice_conform/main.rs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE VOICE CONFORMANCE HARNESS — the driver behind `testing/voice-conformance/`, re-homed onto +//! the streaming plane's DOOR (ARCHITECT Q6: the streaming plane's conformance MUST-set, built +//! against `busbar-plane-streaming`, linked and dlopened, RED arm kept). +//! +//! A dev-only example: never shipped, no production dependency. Every assertion drives the door +//! through `busbar-plugin-loader` BOTH ways — the LINKED door (`busbar_plane_streaming::door::door`) +//! and the DROPPED door (the `streaming_door` example `cdylib`, dlopened) — and requires both to +//! answer identically, then judges the answer, then requires the leg's planted wrong answer to be +//! refused by the same judgment (`door::judge`). `VOICE_CONFORM_RED=1` makes the planted answers +//! the subject: every leg must then go RED. +//! +//! Output contract (the runner greps `^RESULT `): each assertion prints exactly one line +//! RESULT +//! Non-`RESULT` lines (`SUBITEM`) are ignored by the runner. +//! +//! ```text +//! voice_conform spec +//! voice_conform replay +//! voice_conform cross +//! voice_conform governance +//! voice_conform composition +//! ``` + +use std::path::Path; + +mod codec_legs; +mod composition; +mod door; +mod governance; + +fn usage() -> ! { + eprintln!( + "usage:\n voice_conform spec \n voice_conform replay \ + \n voice_conform cross \ + \n voice_conform governance \n voice_conform composition " + ); + std::process::exit(2); +} + +/// Argument `i`, or the usage and exit 2. +fn nth(args: &[String], i: usize) -> &str { + match args.get(i) { + Some(a) => a.as_str(), + None => usage(), + } +} + +fn main() { + let args: Vec = std::env::args().collect(); + let rig = door::Rig::boot(); + let code = match nth(&args, 1) { + "spec" => codec_legs::spec(&rig, nth(&args, 2), Path::new(nth(&args, 3))), + "replay" => codec_legs::replay(&rig, Path::new(nth(&args, 2))), + "cross" => { + let map: serde_json::Value = match std::fs::read(nth(&args, 5)) + .map_err(|e| e.to_string()) + .and_then(|b| serde_json::from_slice(&b).map_err(|e| e.to_string())) + { + Ok(map) => map, + Err(e) => { + door::result( + nth(&args, 2), + false, + "map", + &format!("{}: {e}", nth(&args, 5)), + ); + std::process::exit(1); + } + }; + codec_legs::cross( + &rig, + nth(&args, 2), + Path::new(nth(&args, 3)), + Path::new(nth(&args, 4)), + &map, + ) + } + "governance" => governance::run(&rig, nth(&args, 2)), + "composition" => composition::run(&rig, nth(&args, 2)), + _ => usage(), + }; + std::process::exit(code); +} From 05c5ced5cca49bb800a894d5f48021633c85fad0 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:44:01 -0700 Subject: [PATCH 50/82] voice conformance rig: legs re-homed onto the streaming plane's door (linked + dropped, RED arm per assertion); conform-bin builds the voice_conform example and the streaming_door cdylib; boot-validate-config reads the plane's own config tests --- scripts/verify-1.6.0-done.sh | 2 +- testing/shadow-oracle/rigs-ledger.sh | 4 +- testing/voice-conformance/README.md | 184 +++++++----------- .../voice-conformance/legs/admit-refusal.sh | 30 ++- .../voice-conformance/legs/audit-record.sh | 28 ++- .../voice-conformance/legs/cross-parity.sh | 39 ++-- .../voice-conformance/legs/exit-terminal.sh | 30 +-- .../legs/gemini-live-route.sh | 23 +-- testing/voice-conformance/legs/governance.sh | 40 ++-- .../voice-conformance/legs/metering-lease.sh | 24 ++- .../legs/provider-credential.sh | 22 +-- .../voice-conformance/legs/provider-dial.sh | 25 +-- testing/voice-conformance/legs/replay.sh | 22 +-- .../voice-conformance/legs/route-failover.sh | 31 +-- .../voice-conformance/legs/session-scope.sh | 21 +- .../legs/spec-per-dialect.sh | 24 +-- testing/voice-conformance/legs/tool-reply.sh | 39 +--- testing/voice-conformance/lib/conform-bin.sh | 50 ++--- .../voice-conformance/voice-conformance.sh | 53 +++-- xtask/src/conformance_record/rigs.rs | 7 +- 20 files changed, 270 insertions(+), 428 deletions(-) diff --git a/scripts/verify-1.6.0-done.sh b/scripts/verify-1.6.0-done.sh index bbb73706d0..5bb2cccfbc 100755 --- a/scripts/verify-1.6.0-done.sh +++ b/scripts/verify-1.6.0-done.sh @@ -21,7 +21,7 @@ # every filtered step declares its test count so a filter that selects nothing is # RED rather than vacuously green (see filtered_cargo_test). # config-stability cargo xtask gate config-schema (config-schema.snapshot.json byte-stable). -# test cargo test --workspace + cargo test -p busbar-voice --features runtime. +# test cargo test --workspace. # conformance the conformance rigs' selftests + the voice legs =ready. # teller-steps the H2 matrix holds on BOTH its columns: every rig cell id still resolves to the # scenario/script/leg/suite that owns it, and the rigs behind them RUN and pass diff --git a/testing/shadow-oracle/rigs-ledger.sh b/testing/shadow-oracle/rigs-ledger.sh index c6d82d941e..0ff1b21074 100755 --- a/testing/shadow-oracle/rigs-ledger.sh +++ b/testing/shadow-oracle/rigs-ledger.sh @@ -89,7 +89,7 @@ # # ARMING # MCP_SUBJECT_BUSBAR_BIN / A2A_SUBJECT_BUSBAR_BIN are set from --bin for the MCP and A2A legs. The -# voice battery arms itself (it drives its OWN `voice-conform` harness binary built from this tree, +# voice battery arms itself (it drives its OWN `voice_conform` harness (busbar-plane-streaming example) from this tree, # never busbar's server binary — see testing/voice-conformance/lib/conform-bin.sh), so --bin plays # no part in that leg; it is still required, so a run cannot silently skip the two legs that do need # it by never providing one. @@ -651,7 +651,7 @@ run_voice() { say "== rig: voice conformance battery ==" local log="${WORK}/voice-results.tsv" : >"$log" - # No busbar binary is threaded through: the voice legs drive their OWN `voice-conform` harness + # No busbar binary is threaded through: the voice legs drive their OWN `voice_conform` harness # binary built from this tree (testing/voice-conformance/lib/conform-bin.sh), never a server # busbar binary. --bin is still required at the top of this script so a caller cannot silently # skip the two legs that DO need it by never supplying one. diff --git a/testing/voice-conformance/README.md b/testing/voice-conformance/README.md index 43be0545e9..98ea15666a 100644 --- a/testing/voice-conformance/README.md +++ b/testing/voice-conformance/README.md @@ -1,130 +1,92 @@ -# Voice (4th-plane) conformance battery — SCAFFOLD - -A conformance battery for busbar's **voice** plane, at structural parity with the -sibling MCP (`testing/mcp-conformance/`) and A2A (`testing/a2a-*`) batteries. - -**This is a scaffold.** The voice runtime does not exist yet, so no leg asserts -real conformance today. What lives here is the **shape** — the runner, the leg -scaffolds, the verdict emitter, the CI workflow — landed **green** in an honest -"scaffolded, legs pending" state, so that: - -1. the shape is enforced (and its accounting proven) *before* the first real leg - depends on it, and -2. filling a leg later is a **drop-in**, not a rebuild. - -## Why a scaffold is a legitimate green here - -The MCP and A2A batteries make `NOT ARMED, SO NOT RUN` a **RED** state, because -for those planes the runtime exists and a disarmed subject leg renders as the -identical green tick a leg that judged busbar and passed would produce — the -false green those batteries exist to refuse. - -Voice has **nothing to arm against yet**. So the honest report is **PENDING** — -stated per leg, and never dressed up as a conformance pass. Following the MCP -workflow's own guidance, the transition from PENDING to a real armed-or-red leg -is **exercised by `voice-conformance.sh --selftest`** rather than asserted by a -real run that cannot happen. The self-test proves that the moment a leg is marked -`ready` it is held to the same anti-vacuity discipline the other batteries use: a -ready leg that executed nothing is **RED**, not green. - -## The two-leg rule (inherited, enforced the day the legs light up) - -- **CONTROL runs ALWAYS.** A battery that cannot judge a known-good third-party - dialect peer cannot be trusted to judge busbar. (Scaffolded today.) -- **SUBJECT is ARMED OR RED.** Once a leg is `ready`, an armed run that executed - nothing is RED. `--selftest` drives that transition in both directions so the - rule is one somebody has watched work. - -## The legs - -Legs are **discovered from `legs/*.sh`, never enumerated** — the same rule -`testing/verdict-covers-every-leg.py` applies to the workflow one level out. Each -`legs/.sh` declares `LEG_KIND`, `LEG_STATUS`, `LEG_SLICES` and a -`leg_execute` function. - -| leg | kind | slices | what it will judge | -|---|---|---|---| -| `spec-per-dialect` | conformance | `openai`, `gemini` | the voice spec battery, run once **per dialect** (the matrix) | -| `replay` | conformance | `default` | captured-transcript replay: a recorded session must re-derive identically | -| `cross-parity` | conformance | `oo og go gg` | the **4 ordered** OpenAI⟷Gemini pairs must agree where the mapping says they must | -| `governance` | governance | 5 checkpoints | the 5 vision checkpoints — **NOT a conformance result** | - -### The dialect matrix - -`spec-per-dialect` runs once per dialect in `{openai, gemini}`. In CI it is a job -matrix; in the runner it is the leg's `LEG_SLICES`. `cross-parity` drives all -four **ordered** pairs — `oo`, `og`, `go`, `gg` — because a mapping that is not -identity *within* a dialect (`oo`, `gg`) is already broken and only running the -cross pairs would never see it. +# Voice (streaming-plane) conformance battery + +The conformance battery for busbar's **streaming** plane (voice is one dialect inside it, #18), at +structural parity with the sibling MCP (`testing/mcp-conformance/`) and A2A (`testing/a2a-*`) +batteries. It is the streaming plane's conformance MUST-set (DEV-GREEN exit #4). + +## What a leg drives: the plane's DOOR, both ways + +The plane is served only through its memory-ABI door (`busbar_plane_streaming::door`). Every leg +shells out to one dev-only harness, `busbar-plane-streaming`'s `voice_conform` example +(`crates/busbar-plane-streaming/examples/voice_conform/`), which loads the door through +`busbar-plugin-loader` **both ways** (ARCHITECT Q6): + +- **linked** — `busbar_plane_streaming::door::door`, through `load_linked`; +- **dropped** — the `streaming_door` example `cdylib`, dlopened through `load_dropped` against the + linked row's own Statement. + +Each assertion drives a fresh instance of each door with one script and is PASS only when + +1. the two doors answered **identically** (a dropped door that is not built or does not load is a + FAIL, never a skip), +2. the answer passes the leg's check, and +3. **the RED arm bit**: the leg's planted wrong answer (the wrong door, restated as the answer it + would give) is refused by the same check. `VOICE_CONFORM_RED=1` makes the planted answers the + subject; every conformance leg must then go RED. + +`lib/conform-bin.sh` builds the pair once (`cargo build -p busbar-plane-streaming --example +voice_conform --example streaming_door`); `VOICE_CONFORM_BIN` names a prebuilt harness (checked for +staleness against the plane, loader and contract sources), `VOICE_CONFORM_DOOR` a dropped door that is +not beside it. + +## The legs — each judges the DOOR'S half of its seam + +Legs are **discovered from `legs/*.sh`, never enumerated**. Each leg's header names what it judges at +the door and which half of the seam is the kernel's (the plane driver's walk, the identity crate, the +money steps, the transport), which a plane cannot see and the harness therefore cannot judge. + +| leg | slices | what it judges at the door | +|---|---|---| +| `spec-per-dialect` | `openai`, `gemini` | every fixture: codec round trip, and the door's relay of it by the session rules | +| `replay` | `default` | each captured transcript through one session: codec skeleton; the door's relay and skeleton | +| `cross-parity` | `oo og go gg` | the codec bridge per the cross-dialect map; the bridged wire through the destination door | +| `provider-credential` | 1 | every far request rides a declared outbound need; none carries a credential | +| `metering-lease` | 1 | a session's units, reported per declared class, once per turn; the fee once answered | +| `session-scope` | 1 | the `session` grant kind; every door asks for a principal; 404 off the guest list | +| `gemini-live-route` | 1 | the Gemini Live door: claim, arrival, and the handshake across it | +| `provider-dial` | 1 | a session's far frames ride the dialect's socket need; the host connector dials | +| `admit-refusal` | 1 | a refusal renders in the dialect's shape and opens nothing, dials nothing | +| `route-failover` | 1 | each ATTEMPT answered afresh on the pass need; nothing reaches the caller early | +| `audit-record` | 1 | the audit kind and operation the kernel's one row is written under; no door row | +| `exit-terminal` | 1 | one session, one end; an ended stream is refused on every side | +| `tool-reply` | 1 | a tool call is relayed to the caller and never answered by busbar (Law 11) | +| `governance` | 5 checkpoints | the 5 vision checkpoints — **NOT a conformance result** | + +**The session rules** the fixture legs hold the door to (`session_pump`): a caller frame reaches the +far end as sent, except that the caller's session configuration is replaced by the locked one; a +far-end frame reaches the caller as sent, except that usage and rate-limit reports are consumed (usage +becomes reported units), a far-end tool result is never relayed, and a barge-in also cancels and +truncates the far end's response at the audio the caller heard. The expectation is computed from the +wire by the codec, never by the door. ### Governance is not a conformance result -The `governance` leg observes voice **product policy**, not the voice -**protocol** — barge-in preemption, turn-budget enforcement, metering-lease -settlement (`cost_reserve`/`cost_settle`), dialect down-scope, and the **D2 -hard-close-on-exhaustion** checkpoint. Exactly as `testing/a2a-governance/` can -never contribute to the A2A verdict, this leg's findings are **observations**: -the runner enforces the separation in code, and `--selftest` proves a governance -FAIL cannot move the conformance verdict. - -## How a leg gets filled (the drop-in) - -Edit `legs/.sh`: - -1. flip `LEG_STATUS=pending` → `LEG_STATUS=ready`; -2. implement `leg_execute ` to print **one `RESULT - ` line per assertion**. - -Nothing in `voice-conformance.sh`, in the verdict emitter, or in -`.github/workflows/voice-conformance.yml` changes. The runner immediately holds -the now-ready leg to the anti-vacuity rule: a `ready` leg that emits no `RESULT` -line for a slice is **RED**. - -The inputs later legs consume are authored by another agent and only -**referenced** here: - -- `testing/voice-conformance/fixtures/{openai,gemini}/` — captured transcripts - and per-dialect spec fixtures; -- `qa/evidence/voice-cross-dialect-mapping.*` — the OpenAI⟷Gemini equivalence - the `cross-parity` leg is judged against. +The `governance` leg observes product policy, not protocol. Exactly as `testing/a2a-governance/` can +never contribute to the A2A verdict, the runner keeps a governance FAIL out of the conformance tally, +and `--selftest` proves it. ## The verdict emitter -The runner's verdict **mirrors `verdict-covers-every-leg.py`** one level in: it -holds the set of legs it *reported on* to equality with the set *discovered*, so -a leg cannot be added to the tree and then silently dropped from the verdict. It -also enforces a **floor** on the leg count (a gutted battery satisfies every -equality) and keeps governance out of the conformance tally. `--selftest` injects -each of those faults through the real emitter and watches the check bite. +The runner holds the set of legs it **reported on** to equality with the set **discovered**, enforces +a floor on the leg count, keeps governance out of the conformance tally, and makes a `ready` leg that +emitted no `RESULT` line RED. `--selftest` injects each of those faults through the real emitter and +watches the check bite, then accounts the shipped legs (which runs them: it builds the harness). ## Usage ```bash -# prove the scaffold's own accounting bites, then exit 0 bash testing/voice-conformance/voice-conformance.sh --selftest - -# run everything and emit the honest verdict (the default) -bash testing/voice-conformance/voice-conformance.sh --verdict - -# one leg (what the workflow's per-leg jobs run) +bash testing/voice-conformance/voice-conformance.sh --verdict # the default bash testing/voice-conformance/voice-conformance.sh --leg spec-per-dialect --slice openai -bash testing/voice-conformance/voice-conformance.sh --leg governance - -# what is declared +VOICE_CONFORM_RED=1 bash testing/voice-conformance/voice-conformance.sh --verdict # must be RED bash testing/voice-conformance/voice-conformance.sh --list ``` ## Layout ``` -voice-conformance.sh the runner: --selftest | --verdict | --leg | --list -legs/spec-per-dialect.sh per-dialect spec leg (matrix: openai, gemini) -legs/replay.sh captured-transcript replay -legs/cross-parity.sh the 4 ordered OpenAI<->Gemini pairs -legs/governance.sh the 5 vision checkpoints (NOT a conformance result) -fixtures/{openai,gemini}/ INPUTS authored elsewhere; referenced, not created here +voice-conformance.sh the runner: --selftest | --verdict | --leg | --list +lib/conform-bin.sh builds/locates the voice_conform harness and the dropped-in door +legs/*.sh one leg each (LEG_KIND, LEG_STATUS, LEG_SLICES, leg_execute) +fixtures/{openai,gemini}/ captured transcripts and per-dialect spec fixtures ``` - -The CI workflow is `.github/workflows/voice-conformance.yml`: `gate-selftest` -proves the accounting and the coverage lint bite, one job per leg runs the -scaffolded leg green, and `verdict` asserts every leg executed. diff --git a/testing/voice-conformance/legs/admit-refusal.sh b/testing/voice-conformance/legs/admit-refusal.sh index e34988c91a..4781068ed8 100644 --- a/testing/voice-conformance/legs/admit-refusal.sh +++ b/testing/voice-conformance/legs/admit-refusal.sh @@ -4,27 +4,19 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: admit-refusal — a key whose budget is already spent is refused AT THE DOOR, before any provider -# dial (BUSBAR-1.6.0.md THE DESIGN, §1's teller loop, step 4 ADMIT: a refused reserve costs zero bytes and zero charge). +# LEG: admit-refusal — a refused unit opens nothing and dials nothing, and its refusal is rendered in +# the dialect's error shape (BUSBAR-1.6.0.md THE DESIGN §1, step 4 ADMIT; Part 3 §12 "refused → refusal"). # -# `topology::begin_session` reserves the D2 lease strictly AFTER the destination gate and strictly -# BEFORE any socket is touched (`topology::open_admitted_session`'s doc comment: "the session's own -# charge... fires only AFTER the gate clears"). This leg drives that real function with a REFUSE-ALL -# cap (`Some(0)`, the reserve-then-settle lease's own "already spent" shape) over the substrate's -# `FixtureHost` and asserts: (a) `begin_session` returns `Err(StartError::BudgetRefused)` — the -# plane's own refusal, not a generic one; (b) no cost lease was ever opened host-side -# (`FixtureHost::leases_opened()` does not advance across the refusal); (c) no ledger posting landed -# for the presenting principal (`FixtureHost::ledger_usage`) — voice posts no separate "kernel-floor" -# line at Admit the way BUSBAR-1.6.0.md describes for an ALREADY-DIALED provider push (nothing is ever -# dialed here, so nothing is owed and voice's design posts none); (d) a sanity/negative control — the SAME -# destination with an uncapped budget opens cleanly — so a leg that always failed (or always passed) -# could not hide behind this result. +# THE DOOR'S HALF (judged, linked AND dropped door, `voice_conform composition admit-refusal`): the +# kernel's refusal — budget 429, grant 403, credential 401 — is rendered by the door's `refusal` op in +# the realtime dialects' error envelope (the kernel's status kept, `content-type: application/json`, +# no record row); a refused unit's stream has no session (its far-end piece and its collection are +# refused, `drive` names nothing), while an admitted control on the same door dials (its frame rides +# the realtime socket need). RED arm: a door that renders the budget refusal as a server error fails. # -# WAS RED (this leg's own history): before this leg existed, `governance.sh`'s D2 checkpoint probed -# exhaustion AFTER a session had already opened and spent partway through its cap; nothing proved the -# DOOR itself refuses a caller who arrives already spent, or that the refusal costs zero dial/zero -# ledger. A caller with `Some(0)` remaining could only be judged by mid-session exhaustion, never by an -# admission that never opens at all. +# THE KERNEL'S HALF (not the door's, so not judged here): reading the caller's budget chain dry and +# refusing before any hold or ledger posting — the admit step, proven on the served leg by +# crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_refuses_an_over_budget_caller_before_it_dials. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/audit-record.sh b/testing/voice-conformance/legs/audit-record.sh index 459503f19c..094179cadd 100644 --- a/testing/voice-conformance/legs/audit-record.sh +++ b/testing/voice-conformance/legs/audit-record.sh @@ -4,25 +4,19 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: audit-record — one governed voice session lands EXACTLY ONE new admin-audit entry, carrying the -# plane's own action literal and outcome (BUSBAR-1.6.0.md's audit-chain discipline, applied to the -# voice-plane mutation a session open represents). +# LEG: audit-record — the audit kind and operation the kernel's ONE row per unit is written under, and +# no row of the door's own beside it (BUSBAR-1.6.0.md THE DESIGN §1, the fixed record; Part 3 §12 "audit"). # -# Before this leg, NOTHING in `busbar-voice` ever called `JournalHost::audit_record` — a session could -# open, meter and close without leaving a single row on the admin audit trail. `topology:: -# open_admitted_session` now journals ONE row (`action = "streaming.session.open"`, `outcome = "applied"`, -# `principal` = the session's owner) at its single `Ok` success point, through the live host -# (`VoiceRuntime::audit_session`, a no-op on the pre-host/dev-default runtime with nothing to journal -# through). This leg drives that real code path twice over the substrate's `FixtureHost` — a full -# `EngineHost` double whose `audit_record` now RECORDS (not a no-op) — and asserts: +# THE DOOR'S HALF (judged, linked AND dropped door): the tail states the `streaming_session` audit kind +# and the one `streaming.session.open` operation class; every one of the five doors' units arrives +# as that operation; and the door writes NO record row of its own — it declares no record kind, and +# across two whole sessions, a mint and a refusal its answers carry zero record writes — so the +# kernel's one fixed record is the only row (never doubled). RED arm: a door that writes a row fails. # -# * a clean session open lands EXACTLY ONE row, shaped `("streaming.session.open", "streaming:", -# "applied", )` — the literal action/outcome vocabulary, not a placeholder; -# * a SECOND, independent session adds exactly one MORE row (two sessions -> two rows, never -# doubled, never dropped) — so a leg that always saw "at least one" could not pass by luck. -# -# WAS RED: with no call site at all, `FixtureHost::audit_log()` stayed empty across every session this -# leg opens; the very first assertion (`len() == 1`) is what a truly wired call site must clear. +# NOT THE DOOR'S: the door does not write the session's audit row — the plane is a pure kind and the +# kernel writes the one fixed record at the unit's audit step (served leg: +# crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_audits_each_call_once). The plane's +# `session_unit` keeps a session row internally, but the door surfaces none (no record kind declared). # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/cross-parity.sh b/testing/voice-conformance/legs/cross-parity.sh index ee574c059c..f4cd5cea2a 100644 --- a/testing/voice-conformance/legs/cross-parity.sh +++ b/testing/voice-conformance/legs/cross-parity.sh @@ -4,31 +4,22 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: cross-parity — the 4 ORDERED OpenAI<->Gemini pairs. +# LEG: cross-parity — the 4 ORDERED OpenAI<->Gemini pairs (oo, og, go, gg). # -# The cross-dialect mapping (qa/evidence/voice-cross-dialect-mapping.*, authored by another agent) -# declares which behaviours MUST be equivalent across the two dialects. This leg drives all four -# ORDERED pairs and asserts the mapping holds in each direction: -# -# oo openai -> openai (self-parity: the mapping must be identity within a dialect) -# og openai -> gemini (a session captured on openai, re-derived under gemini, must map) -# go gemini -> openai -# gg gemini -> gemini -# -# Both diagonal pairs (oo, gg) are ordered slices in their own right, not skipped: a mapping that is -# not identity within a dialect is already broken, and only running the cross pairs would never see -# it. That is the cross-parity analogue of the sibling batteries' "a control that exercises a -# different path from the subject proves less than it appears to". -# -# STATUS: ready. Each ordered pair is driven as read(A) → shared IR → write(B) → IR against the -# machine-readable `qa/evidence/voice-cross-dialect-map.json`. For every SHARED concept the map -# declares, the load-bearing fields must survive the bridge (compared on a correlation-collapsed -# fingerprint, so a streamed⟷atomic tool-call reframing counts as agreement, and documented -# non-survivors — text modality, VAD specifics, truncate ms — are excluded per the map). And EVERY -# row of the asymmetry table is exercised as a documented drop+warn in its origin→other direction, so -# a one-dialect-only concept is accounted for, never silently lost. Concepts whose source-dialect -# fixture cannot decode (the one genuine codec gap, or a documented drop) are printed as PENDING -# sub-items, never faked green. +# For every shared concept of the machine-readable `qa/evidence/voice-cross-dialect-map.json`: +# * CODEC: the plane's own codecs (`busbar_plane_streaming::codec`, the module the door's sessions +# run) bridge the concept A → IR → B → IR keeping its load-bearing fields (correlation-collapsed +# fingerprint; documented non-survivors excluded per the map); +# * DOOR: the bridged B wire is pushed through a live session on the door that speaks B, on the +# linked AND the dropped door, and what the door relays must equal the session rules (a caller +# frame reaches the far end as sent, the caller's session config replaced by the locked one; a +# far-end frame reaches the caller as sent, usage consumed into reported units, a barge-in also +# cancelling and truncating upstream). RED arm per concept: a door reporting a token the far end +# never sent fails. +# Every asymmetry row is exercised as a documented drop+warn in its origin→other direction (codec); +# concepts with no decoding source fixture print a SUBITEM PENDING line, never a pass. The diagonal +# pairs (oo, gg) are slices in their own right: a mapping that is not identity within a dialect is +# already broken. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/exit-terminal.sh b/testing/voice-conformance/legs/exit-terminal.sh index 1d881919da..6e6de1f46e 100644 --- a/testing/voice-conformance/legs/exit-terminal.sh +++ b/testing/voice-conformance/legs/exit-terminal.sh @@ -4,31 +4,15 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: exit-terminal — one voice session ends ONCE: exactly one metering-lease settlement and exactly -# one admin-audit row, even when the session is INTERRUPTED (torn down before it runs a single frame, -# and independently closed twice — the shape a parked per-frame handler's stale guard plus the node's -# own sweep produce, BUSBAR-1.6.0.md THE DESIGN, §1's teller loop, the "exit" step — ONE exit path). +# LEG: exit-terminal — one session ends ONCE (BUSBAR-1.6.0.md THE DESIGN §1, the exit step). # -# TWO INDEPENDENT PROOFS, both over the substrate's `FixtureHost` (the same faithful in-memory -# `CostHold`/audit-ring stand-in the admit/route/audit legs use): +# THE DOOR'S HALF (judged, linked AND dropped door): the caller's end settles the open turn's audio +# seconds once and carries the session's end exactly once (cumulative units final); afterwards the +# stream is refused on every side (a collection and a far-end piece) and `drive` names nothing; a +# session torn down before its first frame ends with no units and no fee. RED arm: a second end fails. # -# 1. METERING SETTLES EXACTLY ONCE UNDER A DOUBLE CLOSE. `MeteringHost::cost_close` is the primitive -# `LeaseCloseGuard::drop` calls, and its OWN contract is that a redundant close is a harmless -# `None` -- "no double refund" (see `runtime/metering.rs`'s doc comment on `LeaseCloseGuard`). -# This leg reserves, settles a real increment, then closes the SAME lease id TWICE directly -# against the host: the FIRST close must return the exact settled amount: the SECOND -- the -# "interrupted, closed again" case -- must return nothing, proving there is nothing left to -# double-settle. -# 2. THE SESSION'S ONE AUDIT ROW SURVIVES AN INTERRUPTION. A session opened through the real -# `topology::begin_session` (lands its one `audit-record` row, see the `audit-record` leg), then -# torn down immediately -- the core dropped and the D2 close guard dropped -- without ever -# running a frame. Exactly one audit row must remain: not zero (the interruption did not erase -# the row the open already wrote) and not two (nothing re-fires on teardown). -# -# WAS RED: before `open_admitted_session` wrote its one audit row (see `audit-record`), there was no -# row to prove survives anything at all; and before this leg existed, nothing exercised the SAME -# double-close shape production's own `LeaseCloseGuard`/parked-handler race notes as the reason a -# by-value guard (not a refcount-gated `Drop`) is required. +# THE KERNEL'S HALF: sealing the session's one line and evicting its durable row (served leg: +# crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_ends_each_call_once). # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/gemini-live-route.sh b/testing/voice-conformance/legs/gemini-live-route.sh index f1c83a52ee..0672ead8fb 100644 --- a/testing/voice-conformance/legs/gemini-live-route.sh +++ b/testing/voice-conformance/legs/gemini-live-route.sh @@ -4,23 +4,14 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: gemini-live-route (the second-dialect route) — the Gemini Live dialect has a MOUNTED route, not just a codec. +# LEG: gemini-live-route — the Gemini Live dialect has a door, not just a codec. # -# The spec-per-dialect and cross-parity legs already prove the Gemini Live codec is correct in -# isolation: wire<->IR round trips, and agreement with OpenAI Realtime where the cross-dialect map -# says the two must agree. Neither leg drives the MOUNT: `PLANE_DECL.wire_format_names` named -# `gemini_live` as a dialect the plane speaks, but `voice_claims` / `voice_ws_arrivals` named only the -# OpenAI base, so a caller had no ingress path to reach it at all — a real second dialect the plane -# could not actually serve. -# -# This leg judges the route, on the plane's own PUBLIC functions (the same ones the composition root -# and the core router call): the dispatch slot claims a Gemini-labelled base distinct from the OpenAI -# one, the plane still admits exactly one audience for both, a Gemini WS-accept arrival is declared -# and keyed to this plane's own slot, and — the wire handshake itself — a provider's `setupComplete` -# relays to the client verbatim through the EXACT `SessionCore` type the mounted -# route's `WsArrivalSpec` closure closes over. -# -# WAS RED: no ingress route spoke Gemini Live at all. +# THE DOOR'S HALF (judged, linked AND dropped door): the snapshot claims `GET +# /v1/realtime/gemini/{call_id}` (an upgrade line whose refusals render in Gemini's dialect) beside the +# OpenAI doors under the ONE audience; `arrive` on it answers the gemini_live dialect for a principal +# on the session model's DIRECT route; and the handshake crosses the door — the caller's `setup` +# reaches the far end as a Gemini setup on the Live socket need, the far end's `setupComplete` reaches +# the caller verbatim. RED arm: a Gemini claim refused in the OpenAI dialect fails. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/governance.sh b/testing/voice-conformance/legs/governance.sh index 76bad60cf2..5528f27578 100644 --- a/testing/voice-conformance/legs/governance.sh +++ b/testing/voice-conformance/legs/governance.sh @@ -4,32 +4,22 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: governance — the 5 vision checkpoints. NOT A CONFORMANCE RESULT. +# LEG: governance — the 5 vision checkpoints, observed at the streaming plane's DOOR (linked AND +# dropped). NOT A CONFORMANCE RESULT: the runner keeps a governance FAIL out of the conformance verdict +# (proven by `--selftest`), exactly as testing/a2a-governance/ can never move the A2A verdict. # -# This leg observes busbar's voice PRODUCT POLICY, not the voice PROTOCOL. Exactly as -# testing/a2a-governance/ can never contribute to the A2A conformance verdict — a perfectly -# conformant agent that ignores every budget scores 100% on conformance — this leg's findings are -# OBSERVATIONS. The runner enforces the separation in code: a governance leg that FAILs cannot move -# the conformance verdict, and `--selftest` proves that separation bites. -# -# THE 5 VISION CHECKPOINTS (scaffolded; the real assertions and their fixtures are authored -# elsewhere — this file only fixes their names and order): -# -# V1-barge-in-preemption a caller barge-in must preempt the in-flight turn within budget. -# V2-turn-budget-enforcement a turn that overruns its token/time budget must be bounded. -# V3-metering-lease-settled every turn's metering lease (cost_reserve/cost_settle — the D2 host -# seam this battery's base commit wired) must settle; none may leak. -# V4-dialect-downscope crossing the OpenAI<->Gemini boundary must down-scope, never widen, -# what the far dialect is permitted to see. -# D2-hard-close-on-exhaustion the D2 checkpoint: on budget/lease exhaustion the session must HARD -# CLOSE, not degrade open. A soft-degrade here is the exact failure D2 -# exists to forbid. -# -# STATUS: ready. Each checkpoint is probed over the REAL T2 runtime (feature `runtime`) and recorded -# as an OBSERVATION — the runner records these but never lets a governance finding move the -# conformance verdict (proven by `--selftest`). The load-bearing one is D2-hard-close-on-exhaustion: -# it settles a real `LocalLease` past its cap and asserts the carrier HARD-closes (response.cancel -# upstream, no post-close audio reaches the client), reusing the runtime's actual exhaustion path. +# V1-barge-in-preemption a far-end speech_started cancels the response and truncates the item at +# the ms the caller heard, upstream, and tells the caller. +# V2-turn-budget-enforcement a session past `streams.session_max_secs` is told why (session_expired) +# and ended once, on the kernel's tick clock. +# V3-metering-lease-settled a turn's usage settles exactly once (a short answer's re-call reports +# the same cumulative units; the end adds nothing settled). +# V4-dialect-downscope an OpenAI-only semantic_vad/g711 concept is down-scoped toward Gemini +# (codec), and the caller's session.update never reaches the far end: +# the locked session params do (door). +# D2-hard-close-on-exhaustion once a session has ended no far-end audio reaches the caller and the cut +# renders in the dialect's error shape; reading the chain dry and cutting +# are the kernel's. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/metering-lease.sh b/testing/voice-conformance/legs/metering-lease.sh index 6ee2c932ee..fb0f7cc52a 100644 --- a/testing/voice-conformance/legs/metering-lease.sh +++ b/testing/voice-conformance/legs/metering-lease.sh @@ -4,21 +4,19 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: metering-lease — a served session's money hop is the HOST's, and its ceiling is the CALLER's. +# LEG: metering-lease — a session's units, reported per declared class (BUSBAR-1.6.0.md THE DESIGN +# §1 step "meter"; Part 3 §12: "only far-end-reported units bill"). # -# The governance leg proves the D2 lease hard-closes at whatever cap it is handed. This leg judges the -# question one step earlier, which fails independently: WHERE the lease lives and WHOSE budget sets -# its cap. A served session must reserve on the host's own reserve-then-settle lease — the one the -# rest of the deployment's spend flows through — with the ceiling read off the presenting principal's -# real budget chain: the tightest remaining bucket, widened from the budget projection's micro-units -# into the lease's nanodollars. +# THE DOOR'S HALF (judged, linked AND dropped door): the tail declares the six counted classes plus the +# per-session fee unit; `arrive` admits on no estimate; every session answer reports cumulative +# REPORTED units — a turn's far-end tokens and its audio seconds once per turn (a short answer's re-call +# does not count its audio twice), the open turn settled once at the end, and the fee only once the +# far end answered (a session whose far end never answered reports none). RED arm: a door reporting one +# token more than the far end sent fails. # -# The two boundaries are judged too: a caller with nothing capped anywhere in its chain has no ceiling -# to impose and stays uncapped (exactly as an unbudgeted model call does), and a caller whose budget -# is already spent is denied at the reserve, so it never opens a session at all. -# -# WAS RED: every session reserved an uncapped in-process cell, so no caller's budget could reach a -# live session and the host never saw the lease. +# THE KERNEL'S HALF: the session account — reserving against the caller's budget chain, capping at its +# tightest remaining bucket, ledgering the counts (served leg: +# crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_meters_each_turn_per_declared_class). # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/provider-credential.sh b/testing/voice-conformance/legs/provider-credential.sh index c07869800e..e43cd9c2e1 100644 --- a/testing/voice-conformance/legs/provider-credential.sh +++ b/testing/voice-conformance/legs/provider-credential.sh @@ -4,21 +4,17 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: provider-credential — a MOUNTED voice door can actually reach a realtime provider. +# LEG: provider-credential — the plane never holds the realtime provider's credential. # -# The plane's two one-shot HTTP passes (the browser `ek_` mint and the SDP broker) are governed and -# audience-checked whether or not a provider is composed — so "governed" is not the same claim as -# "serves". This leg judges the second: the composition root hands the plane the provider ORIGIN and -# the secret REFERENCE the deployment's own provider catalog declares for it, the plane resolves that -# reference through the deployment's ordinary secret resolver, and the mint / SDP passes serve under -# the resulting credential instead of reporting that there is nothing to dial. +# THE DOOR'S HALF (judged, linked AND dropped door): every far request the door answers — the mint, +# the SDP offer, the sideband socket and the Gemini socket — names the declared OUTBOUND need it rides, +# under the auth style the kernel binds the provider credential to (`bearer` for OpenAI Realtime, +# `x-goog-api-key` for Gemini Live) and with the member's address, never a plane config path; no +# request carries a credential field, and the caller is named only by the kernel's opaque reference. +# RED arm: a mint that carries the caller's `Authorization` fails. # -# It also judges the two ways this must fail: a reference that does not resolve composes NOTHING (an -# unresolvable credential must never become an empty one), and the endpoint is set-once, so nothing -# later in the process can silently swap a deployment's realtime credential. -# -# WAS RED: nothing composed a provider on any deployment, so both passes answered "governed, but no -# provider credential composed" and the plane's real key had no way in. +# THE COMPOSITION ROOT'S HALF (not the door's): resolving the catalog's secret reference through the +# deployment's resolver, set-once, an unresolvable reference composing nothing. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/provider-dial.sh b/testing/voice-conformance/legs/provider-dial.sh index e9fddce9f8..1856996e53 100644 --- a/testing/voice-conformance/legs/provider-dial.sh +++ b/testing/voice-conformance/legs/provider-dial.sh @@ -4,24 +4,15 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: provider-dial (the provider-dial leg) — a session actually DIALS the composed provider; the WS legs' upstream dial -# is no longer uncomposed. +# LEG: provider-dial — a session's far end is reached through the HOST CONNECTOR, never by the plane +# (Law 3; ARCHITECT Q128). # -# `topology::dial_provider` has existed since K1: breaker-admitted, net-guarded, counted on the shared -# upstream-attempt family. But nothing in the mounted routes called it — the telephony and Gemini WS -# accepts served the client socket only, discarding the uplink into a channel with no receiver, so no -# conformance leg (and no real deployment) ever drove a live provider socket end to end. -# -# This leg proves the wiring, not just the library function: it stands up a tiny loopback WS -# "provider" (no network, no vendor credential — a stand-in exactly as the shadow-oracle's mock -# upstream stands in for a real one), dials it through `dial_provider`'s own net-guarded path, feeds -# the frame it sends through the SAME `SessionCore` type the mounted routes open, and asserts the -# session's D2 metering lease actually settles the usage that arrived over that live socket — not a -# fixture, a socket. -# -# WAS RED: `topology::dial_provider` existed and passed its own unit tests in isolation, but no leg (and -# no mounted route) ever called it, so "the dial exists" and "a session dials it" were different, unproven -# claims. +# THE DOOR'S HALF (judged, linked AND dropped door): a session's far frames name the dialect's socket +# under the member's base URL (`GET /v1/realtime`, the Gemini BidiGenerateContent path) on the declared +# need they ride (door::RIDES_REALTIME_SOCKET / RIDES_LIVE_SOCKET), and the usage the far end sends over +# that socket is reported as units. The plane opens no socket and holds no guard (its closure is +# contract + codecs: tests/purity.rs); the net guard, TLS and the upgrade are the connector's. RED arm: +# a socket frame that names the one-shot pass need fails. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/replay.sh b/testing/voice-conformance/legs/replay.sh index a62ce16795..5bd67be7c3 100644 --- a/testing/voice-conformance/legs/replay.sh +++ b/testing/voice-conformance/legs/replay.sh @@ -6,19 +6,15 @@ # # LEG: replay — captured-transcript replay. # -# A recorded voice session (a captured transcript under -# testing/voice-conformance/fixtures/{openai,gemini}/) must re-derive IDENTICALLY when replayed -# through busbar's voice plane: same turn boundaries, same tool invocations, same barge-in points, -# same settlement. Replay is the leg that catches a change which passes the live spec but silently -# alters behaviour a real caller already depended on — the regression a spec-only battery cannot see. -# -# STATUS: ready. Each dialect's captured `transcript.jsonl` golden is driven through the real codec — -# threading ONE session `DecodeState`, honoring each line's `dir` (client → uplink, server → -# downlink) — and the decoded IR must re-derive the expected ordered concept skeleton -# (config → connect → audio → tool call → tool result → audio → barge-in → close/complete) with no -# load-bearing drop, and every decoded frame must re-encode to valid wire JSON. The Gemini uplink -# `realtimeInput.audio{}` frames the shipped codec does not read are recorded as a documented replay -# sub-item (the openai→gemini bridge still exercises that concept in cross-parity). +# Each dialect's captured `transcript.jsonl` (testing/voice-conformance/fixtures/{openai,gemini}/): +# * CODEC: decoded through ONE session state in order, every event re-framed to valid wire JSON, and +# the expected concept skeleton (config → connect → audio → tool call → tool result → audio → +# barge-in → close/complete) re-derived in order; +# * DOOR: the whole transcript driven through ONE live session on the door (client lines as caller +# pieces, server lines as far-end pieces), linked AND dropped; what the door relays must equal the +# session rules, and the same skeleton must appear in the order the door emitted it (less the far +# end's usage, which the session consumes into reported units). RED arm: a door reporting a token +# the transcript never sent fails. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/route-failover.sh b/testing/voice-conformance/legs/route-failover.sh index 512b7861d3..806cfa504a 100644 --- a/testing/voice-conformance/legs/route-failover.sh +++ b/testing/voice-conformance/legs/route-failover.sh @@ -4,28 +4,17 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: route-failover — a down provider trips the breaker on its first hard-down strike, and a tripped -# breaker refuses EVERY further dial before any socket/URL work — the documented terminal outcome -# (BUSBAR-1.6.0.md THE DESIGN, §1's teller loop, step 5 ROUTE) with no repeated egress once the cell is open. +# LEG: route-failover — the door's half of the route walk (BUSBAR-1.6.0.md THE DESIGN §1 step 5 ROUTE; +# Part 3 §12 "The route pump": the walk — pool, member pick, breaker, failover, exhaustion — is the +# kernel's, and the driver builds no second one). # -# `topology::dial_provider` probes the `(pool, lane)` breaker cell through the host seam FIRST — before -# any DNS/guard/socket work — and folds a real dial's outcome back into the SAME cell -# (`host.breaker_record_signal`). This leg drives that real function twice over the substrate's -# `FixtureHost` (a real in-memory breaker, not a stand-in that always admits): -# -# 1. breaker CLOSED, a real dial to a target the default fail-closed `GuardPolicy` refuses (a -# plaintext `ws://` loopback address) — a genuine `DialProviderError::Dial(_)`, and the guard -# refusal's canonical signal (Auth-class) trips the cell HARD DOWN on this first strike. -# 2. breaker now OPEN — a SECOND dial, to a syntactically GARBAGE target a real dial would fail -# differently on (`DialProviderError::Dial(Url(_))`), must instead come back -# `DialProviderError::BreakerOpen` with a positive `Retry-After` — proving the breaker check runs -# STRICTLY BEFORE any dial attempt, not just that a dial eventually fails again. -# -# WAS RED (this leg's own history): `dial_provider`'s breaker-admit-first order and its fold-back onto -# the cell existed and were unit-tested in isolation, but no conformance leg judged the OBSERVABLE -# outcome from the plane's own public seam — that a real hard-down dial actually opens the SAME cell a -# second dial is judged against, and that the terminal refusal it then returns names ZERO further dial -# attempt (as opposed to, say, a retried dial that merely fails again for a different reason). +# THE DOOR'S HALF (judged, linked AND dropped door, the mint door): it names the session model's DIRECT +# route (and none with no model configured); each ATTEMPT is answered afresh with the same request on +# the declared pass need (door::RIDES_REALTIME_PASS) — the host connector dials it; a failing far end +# reaches the caller with nothing before its last piece, so the walk may still fail over; the next +# attempt's answer is the caller's whole answer, free of the failed one; and a last attempt that fails +# is the served plane's 502. The plane opens no socket and holds no breaker or guard. RED arm: a retry +# that sends a different request fails. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/session-scope.sh b/testing/voice-conformance/legs/session-scope.sh index f46b2d6504..ee9dd5cf96 100644 --- a/testing/voice-conformance/legs/session-scope.sh +++ b/testing/voice-conformance/legs/session-scope.sh @@ -4,21 +4,16 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: session-scope — the plane's one declared scope kind is a gate, not vocabulary. +# LEG: session-scope — the plane's `session` grant kind, at the door. # -# Holding a key that is valid for the voice door's AUDIENCE is not the same as being GRANTED a session -# on it: the audience check answers "is this token for this door", the grant answers "may this caller -# walk through it". The plane declares a `session` scope kind, which is what an operator's -# `allowed_scopes: [{ kind: session, value: … }]` entry validates against, and this leg judges that -# the door actually asks the question — the way MCP double-gates a tool and A2A gates an agent. +# THE DOOR'S HALF (judged, linked AND dropped door): the tail declares the `session` grant kind; every +# one of the five doors asks for a principal (none admits anonymously) under the one session-open +# operation; an unpublished claim is refused 404 with the plane's own code; a grant refusal renders as +# the dialect's permission_error. RED arm: a door that admits anonymously fails. # -# The whole grant semantic is judged, not just the happy path: a key with no scope list at all is the -# store's wildcard and is granted every kind; a key with an explicit list must carry the session grant -# for this voice pool, so a model-plane key, a session grant aimed at another pool, and an empty list -# are all refused. -# -# WAS RED: any key valid for the plane's audience opened a session, and the declared scope kind was -# vocabulary nothing consulted. +# THE KERNEL'S HALF: matching the presenting key's grants (wildcard, an explicit session grant, one aimed +# at another pool, an empty list) — the approve step (served leg: +# crates/busbar/src/root/tests/gauntlet_kernel.rs::served_rider_refuses_a_grant_short_of_the_operation). # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/spec-per-dialect.sh b/testing/voice-conformance/legs/spec-per-dialect.sh index 4be828fcad..dc1b3626ff 100644 --- a/testing/voice-conformance/legs/spec-per-dialect.sh +++ b/testing/voice-conformance/legs/spec-per-dialect.sh @@ -4,21 +4,17 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: spec-per-dialect — the voice conformance spec, run once PER DIALECT. +# LEG: spec-per-dialect — the voice conformance spec, run once PER DIALECT (openai, gemini). # -# The matrix is the two dialects busbar's voice plane will speak: openai and gemini. Each slice runs -# the full spec battery against that dialect's peer and holds it to the dialect's own required -# scenario set (set equality, never a floor — see the sibling MCP `assert_covered`). -# -# STATUS: ready. Wired against the `busbar-voice` plane. Each slice drives EVERY captured fixture in -# `testing/voice-conformance/fixtures/$dialect/` through the dialect's real codec -# (`OpenAiRealtimeCodec` / `GeminiLiveCodec`) as wire JSON → IR → wire JSON, and asserts round-trip -# stability at the level the codec guarantees (IR-fixpoint; some families are also byte-stable, and -# an atomic Gemini `toolCall` that decodes to a streamed triple is held to the correlation -# fingerprint, since the stateless writer re-frames per event). A fixture that decodes to NOTHING is -# only accepted when it is a documented drop+warn; anything else fails RED. One fixture — the Gemini -# `realtimeInput.audio{}` uplink shape the shipped codec does not read — stays an HONEST PENDING -# sub-item (printed, never dressed as a pass), not a reason to weaken the whole leg. +# Every captured fixture in `testing/voice-conformance/fixtures/$dialect/`: +# * CODEC: wire JSON → IR → wire JSON → IR through the dialect's codec in the plane crate +# (`OpenAiRealtimeCodec` / `GeminiLiveCodec`), stable at the level the codec guarantees +# (IR-fixpoint, or the correlation fingerprint for an atomic Gemini toolCall); a fixture that +# decodes to nothing passes only as a documented drop; +# * DOOR: pushed through a live session on the dialect's door (the caller's frames as caller pieces, +# the far end's as far-end pieces), linked AND dropped; the door must relay it by the session rules +# (a documented drop on no wire at all), every far frame on the dialect's socket need, the far end's +# tokens reported as units. RED arm per fixture: a door reporting a token never sent fails. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/legs/tool-reply.sh b/testing/voice-conformance/legs/tool-reply.sh index d85951e6b8..d8ae22fad9 100755 --- a/testing/voice-conformance/legs/tool-reply.sh +++ b/testing/voice-conformance/legs/tool-reply.sh @@ -4,38 +4,17 @@ # shellcheck shell=bash # shellcheck disable=SC2034 # LEG_KIND/LEG_STATUS/LEG_SLICES are read by voice-conformance.sh on source # -# LEG: tool-reply — a call the node does not serve is answered by the client, through the governed wait. +# LEG: tool-reply — a tool call is RELAYED, never answered (Law 11; QUESTIONS Q98; ARCHITECT Q128). # -# The tool moat has two halves and only one of them was reachable from a served session. A call for a -# tool the node itself serves executes in-process and the client can neither see nor forge its result; -# that half is unchanged and is not what this leg judges. The other half is a call the node does NOT -# serve: the answer can only come from the client, so the call's leg is a WAIT the composition root -# enters when it plans the leg, and something on the socket has to be able to end it. +# THE DOOR'S HALF (judged on both duplex dialects, OpenAI Realtime and Gemini Live, linked AND dropped +# door): the model's tool call reaches the caller as it was made (dialect translation only); the door +# sends the far end NOTHING for it (busbar authors no tool result); the call is counted once; and the +# caller's own result reaches the far end unchanged and alone (no door-authored response). RED arm: a +# door that answers the call upstream fails. # -# Three facts, driven through a REAL session core on EVERY duplex dialect the plane serves (OpenAI -# Realtime and Gemini Live — one runtime, two codecs, which is the point of the neutral IR): -# -# * the wake — the client's `function_call_output` (Gemini: `toolResponse.functionResponses`) goes to -# the node's table FIRST and reaches the model only because the wait was woken; -# * the refusal — a reply naming a call nobody is waiting on is refused by identifier and carried on -# no wire at all, rather than paid out against whichever call happens to be standing; -# * the sweep — the tick beside the pump ends a call nobody answered AT THE DEADLINE THE PLANE -# DECLARES, so its unit exits under that deadline rather than settling as though the answer had -# arrived. -# -# The deadline is `busbar_plane_streaming::plane::TOOL_REPLY_DEADLINE_SECS`, read from the plane crate the -# composition root builds its own reply leg out of — one declaration and two readers, so a change to -# the figure moves this leg with it. Both edges are judged: nothing is ended one millisecond before -# that wall, and the unanswered call IS ended at it. What the ending then does to a unit — the exit -# under `Failed(Route, DeadlineExceeded)` — is the composition root's own identity cell, which drives -# this same wall through this same tick over the node's real table. -# -# WAS RED: the runtime executed every tool call in-process and carried a client-authored result -# upstream verbatim, so on the served path the wait was entered and then nothing ever woke it and -# nothing ever swept it. A client that never replied held the call's hold open forever, and a forged -# reply rode upstream under whatever call was open. Then the wake and the sweep landed and the -# DEADLINE still did not: the tick ended whatever it found, at whatever wall it was handed, so a -# call could be settled before the client it was delivered to had run out of time to answer it. +# NOT PORTED, BY RULING: the old leg certified in-process tool execution and busbar waking, refusing +# and sweeping client tool replies through an open-call table; those assertions violate Law 11 and are +# not carried in any form. # shellcheck source=../lib/conform-bin.sh disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")/../lib/conform-bin.sh" diff --git a/testing/voice-conformance/lib/conform-bin.sh b/testing/voice-conformance/lib/conform-bin.sh index 2e82e7f267..f539dba15b 100644 --- a/testing/voice-conformance/lib/conform-bin.sh +++ b/testing/voice-conformance/lib/conform-bin.sh @@ -6,8 +6,10 @@ # # SHARED HELPER for the voice-conformance legs — NOT a leg (it lives outside legs/, so the runner's # `legs/*.sh` glob never discovers it). Sourced by each `legs/.sh` to locate the REAL Rust -# conformance harness (`busbar-voice`'s `voice-conform` bin) the legs shell out to. The legs reuse the -# crate's production codecs + runtime through this bin; they never reimplement a codec in shell. +# conformance harness the legs shell out to: `busbar-plane-streaming`'s dev-only `voice_conform` +# example, which drives the streaming plane's DOOR both ways through the loader — the linked door and +# the `streaming_door` example cdylib, dlopened (ARCHITECT Q6). The legs reuse the plane's own door +# and codecs through this harness; they never reimplement a codec — or a door — in shell. # Resolved from THIS file's location (testing/voice-conformance/lib/): the battery dir, repo root, # fixtures, and the cross-dialect map. Computed once at source time. @@ -17,15 +19,17 @@ VC_ROOT="$(cd "$VC_DIR/../.." && pwd)" # repo root VC_FIXTURES="$VC_DIR/fixtures" VC_MAP="$VC_ROOT/qa/evidence/voice-cross-dialect-map.json" -# The trees the harness IS. A `voice-conform` older than any file under these is a binary that would +# The trees the harness IS. A `voice_conform` older than any file under these is a binary that would # judge source it was not built from — which is not an error at run time, it is a PASS about code # nobody is running. The legs are in the list for the reason the note below already gives: the bin # answers a `composition ` subcommand per leg, so a leg added after the binary was built is a # slice the binary has never heard of. _vc_harness_trees() { printf '%s\n' \ - "$VC_ROOT/crates/busbar-voice/src" \ - "$VC_ROOT/crates/busbar-plane-streaming/src/codec" \ + "$VC_ROOT/crates/busbar-plane-streaming/src" \ + "$VC_ROOT/crates/busbar-plane-streaming/examples" \ + "$VC_ROOT/crates/plugin-loader/src" \ + "$VC_ROOT/crates/busbar-contract/src" \ "$VC_DIR/legs" \ "$VC_DIR/lib" } @@ -46,39 +50,37 @@ _vc_newer_than() { return 1 } -# Echo the path to the built `voice-conform` binary, building it once if necessary. -# * $VOICE_CONFORM_BIN, if set, names the binary the workflow built once and exported — and it is -# CHECKED before it is used. It was taken verbatim, which meant a path to nothing, or to a -# binary built before the source beside it changed, was believed: a stale harness runs, passes, -# and reports conformance about code that is not in the tree. That is the same false green the -# runner's own anti-vacuity rule exists to refuse, one level further in, and unlike a missing -# binary it leaves no trace at all — every leg reports PASS. -# * else the harness is built (cargo is incremental, so an up-to-date binary costs a lock and a -# stat; a binary older than the legs it serves once reported "unknown composition slice" for -# four legs that existed only in source, and this is what stops that recurring) — features -# `runtime,test-support` — the D2 governance probe needs the -# async session engine, and the admit/route/audit/exit composition legs drive the substrate's -# `FixtureHost` test double over the real `EngineHost` seam) — cargo noise goes to stderr so it -# never pollutes the RESULT lines the runner parses on stdout. +# Echo the path to the built `voice_conform` harness, building it (and the dropped-in door beside it) +# once if necessary. +# * $VOICE_CONFORM_BIN, if set, names the harness the workflow built once and exported — and it is +# CHECKED before it is used: a path to nothing, or to a binary built before the source beside it +# changed, would run, pass, and report conformance about code that is not in the tree (the same +# false green the runner's anti-vacuity rule refuses, one level further in). The dropped-in door +# it loads is found beside it (or at $VOICE_CONFORM_DOOR); a door that is not there is a FAIL in +# every assertion, never a skip. +# * else both examples are built (cargo is incremental, so an up-to-date pair costs a lock and a +# stat; a harness older than the legs it serves would answer "unknown composition slice" for a leg +# that exists only in source) — cargo noise goes to stderr so it never pollutes the RESULT lines +# the runner parses on stdout. voice_conform_bin() { if [ -n "${VOICE_CONFORM_BIN:-}" ]; then if [ ! -x "$VOICE_CONFORM_BIN" ]; then - printf 'voice-conform: VOICE_CONFORM_BIN=%s is not an executable file\n' \ + printf 'voice_conform: VOICE_CONFORM_BIN=%s is not an executable file\n' \ "$VOICE_CONFORM_BIN" >&2 return 1 fi local newer if newer="$(_vc_newer_than "$VOICE_CONFORM_BIN")"; then - printf 'voice-conform: VOICE_CONFORM_BIN=%s is older than %s — it would report conformance about source it was not built from\n' \ + printf 'voice_conform: VOICE_CONFORM_BIN=%s is older than %s — it would report conformance about source it was not built from\n' \ "$VOICE_CONFORM_BIN" "$newer" >&2 return 1 fi printf '%s' "$VOICE_CONFORM_BIN" return 0 fi - local bin="${CARGO_TARGET_DIR:-$VC_ROOT/target}/debug/voice-conform" - cargo build -q --manifest-path "$VC_ROOT/Cargo.toml" \ - -p busbar-voice --features runtime,test-support --bin voice-conform >&2 || return 1 + local bin="${CARGO_TARGET_DIR:-$VC_ROOT/target}/debug/examples/voice_conform" + cargo build -q --manifest-path "$VC_ROOT/Cargo.toml" -p busbar-plane-streaming \ + --example voice_conform --example streaming_door >&2 || return 1 [ -x "$bin" ] || return 1 printf '%s' "$bin" } diff --git a/testing/voice-conformance/voice-conformance.sh b/testing/voice-conformance/voice-conformance.sh index 4e6d61445e..782bd44a7f 100755 --- a/testing/voice-conformance/voice-conformance.sh +++ b/testing/voice-conformance/voice-conformance.sh @@ -34,34 +34,29 @@ # work before the first real leg depends on it. # # THE DECLARED LEGS (discovered from `legs/*.sh`, never enumerated here — the same rule -# `verdict-covers-every-leg.py` applies to the workflow, one level in): +# `verdict-covers-every-leg.py` applies to the workflow, one level in). Every leg drives the streaming +# plane's DOOR through `busbar-plane-streaming`'s dev-only `voice_conform` harness, BOTH ways — the +# linked door and the `streaming_door` cdylib, dlopened through the loader — requires the two to answer +# identically, and carries a RED arm per assertion (the leg's planted wrong answer must be refused; +# `VOICE_CONFORM_RED=1` makes the planted answers the subject, and every leg must then go RED). Each +# leg judges the DOOR'S half of its seam and names the half that is the kernel's (lib/conform-bin.sh): # -# spec-per-dialect the voice spec battery, per dialect. MATRIX: openai, gemini. -# replay captured-transcript replay: a recorded session must re-derive identically. -# cross-parity the 4 ORDERED OpenAI<->Gemini pairs (oo, og, go, gg) must agree where the -# cross-dialect mapping says they must. -# provider-credential the realtime provider credential the mint / SDP passes dial under, composed -# from the deployment's own provider catalog + secret resolver. -# metering-lease a session's money hop is the HOST's reserve-then-settle lease, capped by the -# presenting principal's own remaining budget. -# session-scope the plane's declared `session` scope kind, enforced at session open. -# gemini-live-route (the second-dialect route) the Gemini Live dialect has a MOUNTED WS-accept route (claim, admission, -# arrival, and the wire handshake itself), not just a codec the spec/cross-parity -# legs exercise off to the side. -# provider-dial (the provider-dial leg) a session actually DIALS the composed provider through a real (loopback) -# socket via `topology::dial_provider`, and its D2 metering lease settles the -# usage that arrived over it — the WS legs' upstream dial is no longer uncomposed. -# admit-refusal a key whose budget is already spent is refused AT THE DOOR -# (`StartError::BudgetRefused`) before any host-side lease is opened and before -# any ledger posting — no provider dial has anything left to reach. -# route-failover a hard-down provider dial trips the breaker cell on its first strike, and the -# tripped cell refuses every FURTHER dial before any socket/URL work — the -# documented terminal outcome, with no repeated egress once the cell is open. -# audit-record one governed session lands EXACTLY ONE new admin-audit entry, carrying the -# plane's own action literal (`streaming.session.open`) and outcome (`applied`). -# exit-terminal one session ends ONCE: a metering lease settles exactly once under a double -# close, and a session's one admin-audit row survives being torn down before it -# ever runs a frame. +# spec-per-dialect every fixture, per dialect (MATRIX: openai, gemini): codec round trip, and the +# door's relay of it by the session rules. +# replay each captured transcript through one session: codec skeleton, and the door's +# relay and skeleton in emitted order. +# cross-parity the 4 ORDERED OpenAI<->Gemini pairs (oo, og, go, gg): the codec bridge per the +# cross-dialect map, and the bridged wire through the destination dialect's door. +# provider-credential every far request rides a declared outbound need; none carries a credential. +# metering-lease a session's units, reported per declared class, once per turn; fee once answered. +# session-scope the `session` grant kind; every door asks for a principal; 404 off the guest list. +# gemini-live-route the Gemini Live door: its claim, its arrival, the handshake across it. +# provider-dial a session's far frames ride the dialect's socket need (the host connector dials). +# admit-refusal a refusal is rendered in the dialect's shape and opens nothing, dials nothing. +# route-failover each ATTEMPT answered afresh on the pass need; nothing reaches the caller early. +# audit-record the audit kind and operation the kernel's one row is written under; no door row. +# exit-terminal one session, one end; a stream that ended is refused on every side. +# tool-reply a tool call is relayed to the caller and never answered by busbar (Law 11). # governance the 5 vision checkpoints (incl. D2 hard-close-on-exhaustion). GOVERNANCE IS # NOT A CONFORMANCE RESULT — it can never move the conformance verdict, exactly # as `testing/a2a-governance/` can never contribute to the A2A verdict. @@ -100,9 +95,9 @@ VOICE_LEGS_DIR="${VOICE_LEGS_DIR:-$HERE/legs}" # A FLOOR on the declared-leg count, for the same reason the python verdict linter has one: every # equality below would hold for a battery that had been gutted to a single leg, so the count is -# checked first. Thirteen legs ship today (spec-per-dialect, replay, cross-parity, +# checked first. Fourteen legs ship today (spec-per-dialect, replay, cross-parity, # provider-credential, metering-lease, session-scope, gemini-live-route, provider-dial, -# admit-refusal, route-failover, audit-record, exit-terminal, plus governance). +# admit-refusal, route-failover, audit-record, exit-terminal, tool-reply, plus governance). MIN_LEGS="${VOICE_MIN_LEGS:-3}" say() { printf '%s\n' "$*"; } diff --git a/xtask/src/conformance_record/rigs.rs b/xtask/src/conformance_record/rigs.rs index 3b8d2b424a..4faf195f83 100644 --- a/xtask/src/conformance_record/rigs.rs +++ b/xtask/src/conformance_record/rigs.rs @@ -848,14 +848,15 @@ impl Runner { ][..], ), ( + // The `streams:` section's grammar, now the streaming plane's own (`config::` + // in busbar-plane-streaming; the legacy busbar-voice crate is deleted). "boot-validate-config", &[ "cargo", "test", "-p", - "busbar-voice", - "--features", - "runtime", + "busbar-plane-streaming", + "--lib", "config::", ][..], ), From 7a51e4ef9a3e505a44180df979d11ebe8cfb187e Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:45:45 -0700 Subject: [PATCH 51/82] FLIP-STREAMING: serve_door is carried wherever the node axis is (no unknown cfg); the ws battery's chains are read relative to the upgraded-from framer's own carrier --- crates/busbar/src/root/serve.rs | 6 +---- crates/busbar/src/root/tests/serve_door.rs | 10 --------- crates/busbar/tests/ws_composed_battery.rs | 26 +++++++++++++++------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index 1b085ff7a6..11377fdfad 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2679,11 +2679,7 @@ mod tests; #[path = "tests/serve_planes.rs"] mod planes_tests; -#[cfg(all( - test, - any(feature = "plane-decisions", linked_axis_plane_door), - linked_axis_node -))] +#[cfg(all(test, linked_axis_node))] #[path = "tests/serve_door.rs"] mod door_tests; diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index f6988b4004..3cb6940bb0 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -522,19 +522,15 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { } /// The streaming door's mint claim. -#[cfg(linked_axis_plane_door)] const MINT: &str = "/v1/realtime/client_secrets"; /// The provider's answer to the mint: an ephemeral secret and its expiry. -#[cfg(linked_axis_plane_door)] const MINTED: &str = r#"{"value":"ek_door_0001","expires_at":1767225600}"#; /// The deployment's public base URL the streaming door fronts. -#[cfg(linked_axis_plane_door)] const PUBLIC: &str = "http://gw.test"; /// The streaming door, served as production composes it, and what each `root-voice` cell reads. -#[cfg(linked_axis_plane_door)] struct SessionDoor { _published: Published, router: axum::Router, @@ -551,12 +547,10 @@ struct SessionDoor { /// The billable class the session door meters a caller's audio under: how the harness finds that /// door among the linked plane doors without spelling the plane. -#[cfg(linked_axis_plane_door)] const AUDIO_CLASS: &str = "audio_seconds_in"; /// The linked plane door that meters audio (the session door), bound under `instance`; `None` in a /// build that links none. -#[cfg(linked_axis_plane_door)] fn session_door( instance: &'static str, dispatcher: &Arc, @@ -590,7 +584,6 @@ fn session_door( /// an OpenAI-protocol provider on a loopback far end answering the mint, reached through the catalog /// model `streams.session.model` names; the door composed under [`PUBLIC`] with its egress sealed by /// the composition itself, and its claims mounted on the data router. -#[cfg(linked_axis_plane_door)] async fn session_served(instance: &'static str, budget: Option) -> Option { let published = Published(instance); let (port, heard) = far_end_answering(MINTED).await; @@ -764,7 +757,6 @@ async fn session_served(instance: &'static str, budget: Option) -> Option u64 { @@ -794,7 +786,6 @@ impl SessionDoor { /// by the `streams|mint|wrong-audience` oracle cell.) /// /// RED ARM: the same request with no credential is refused before anything is dialled or charged. -#[cfg(linked_axis_plane_door)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn a_session_door_mint_is_served_under_the_providers_credential() { let _one = PUBLISHING.lock().await; @@ -854,7 +845,6 @@ async fn a_session_door_mint_is_served_under_the_providers_credential() { /// at admission — over budget, before the provider is dialled, charged nothing, no line written — /// while a key with room is served (the mint cell above). RED: a door that dialled before admission /// would have reached the far end. -#[cfg(linked_axis_plane_door)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn a_spent_key_is_refused_at_the_session_door_before_any_dial() { let _one = PUBLISHING.lock().await; diff --git a/crates/busbar/tests/ws_composed_battery.rs b/crates/busbar/tests/ws_composed_battery.rs index cebc421bd1..7dde000ff5 100644 --- a/crates/busbar/tests/ws_composed_battery.rs +++ b/crates/busbar/tests/ws_composed_battery.rs @@ -254,13 +254,15 @@ async fn an_in_band_upgrade_over_the_layer_below_with_cleared_facts() { let (before, after_source, upgraded) = upgrade_task.await.unwrap(); assert_eq!( - before, - vec![upper.key()], - "the upgraded-from framer named itself" + before.last(), + Some(&upper.key()), + "the upgraded-from framer named itself on top of its carrier: {before:?}" ); + let mut chain = before.clone(); + chain.push(stack.wire.key); assert_eq!( adopter.arrival(&upgraded).transport_chain, - vec![upper.key(), stack.wire.key], + chain, "the real chain, not a name for itself" ); assert_eq!( @@ -294,13 +296,21 @@ async fn a_composed_round_trip_over_the_layers_below() { .await; // Both ends report the stack they actually stand on, not a name for themselves. + let served = server_t.arrival(&server_conn).transport_chain; assert_eq!( - server_t.arrival(&server_conn).transport_chain, - vec![stack.upper.key(), stack.wire.key] + served[served.len().saturating_sub(2)..], + [stack.upper.key(), stack.wire.key], + "the wire on top of the framer it was upgraded from: {served:?}" ); + let dialled = client_t.arrival(&client_conn).transport_chain; assert_eq!( - client_t.arrival(&client_conn).transport_chain, - vec![stack.wire.key] + dialled.last(), + Some(&stack.wire.key), + "the wire on top of its carrier: {dialled:?}" + ); + assert!( + !dialled.contains(&stack.upper.key()), + "a dial composes over no framer: {dialled:?}" ); client_t From 48b36ae09eac55d2eaa27d570dd51f4adfe2f73d Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:46:45 -0700 Subject: [PATCH 52/82] config-schema snapshot: the decisions door's declared section is fingerprinted beside streams (the door Statement's SECTION_DECLARING row, Q5's reader; additive; streams: unchanged byte for byte) --- crates/busbar-kernel/src/config/config-schema.snapshot.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/busbar-kernel/src/config/config-schema.snapshot.json b/crates/busbar-kernel/src/config/config-schema.snapshot.json index a97b6d878b..1e833a307e 100644 --- a/crates/busbar-kernel/src/config/config-schema.snapshot.json +++ b/crates/busbar-kernel/src/config/config-schema.snapshot.json @@ -433,6 +433,10 @@ "optional": true, "type": "ConfigMgmtCfg" }, + "decisions": { + "optional": true, + "type": "DeclaredSections" + }, "export": { "optional": true, "type": "ExportDefs" From a2926462d4209a15b49ee9eb67b47fd0c78509a7 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:58:57 -0700 Subject: [PATCH 53/82] serve_door: the session-door finder answers the bound Plugin --- crates/busbar/src/root/tests/serve_door.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index 3cb6940bb0..9d1b1050c8 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -555,7 +555,7 @@ fn session_door( instance: &'static str, dispatcher: &Arc, conns: Arc, -) -> Option { +) -> Option> { crate::LINKED.plane_doors.iter().find_map(|door| { let row = LinkedRow::of(*door).expect("the door states its Statement"); let plane = load_linked::( From 9bd2a5898116745b005ee71ad7b348aa82483d58 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:15:23 -0700 Subject: [PATCH 54/82] reachability: the streaming roster row is the door plane busbar-plane-streaming; the green fixture's streaming is door-shaped ARCHITECT Q2 (2): the streaming row's linked_crate is busbar-plane-streaming (on disk: plane-streaming), registered by its `plane-door` row (p3-reach-door, PR #469) and served through the one composition's driver. The green fixture follows: its crate linked on `plane-door`, no session flip, no plane_claims token, no units_voice module, and a root/serve.rs whose one composition builds the driver and whose served route drives a unit. The self-test cases that used streaming as their rider or registration exemplar now use a2a (the test-scope flip, the fold's missing gauntlet axis, the in-root entry key) and mcp (the plane-axis registration RED, the root-units reach); decision_door plants its serve.rs over the fixture's own. --- .../crates/busbar/Cargo.toml | 6 +- .../crates/busbar/src/main.rs | 2 +- .../busbar/src/root/gauntlet_install.rs | 1 - .../crates/busbar/src/root/mod.rs | 2 +- .../crates/busbar/src/root/registry.rs | 2 - .../crates/busbar/src/root/serve.rs | 15 ++++ .../busbar/src/root/tests/units_voice.rs | 7 -- .../crates/busbar/src/root/units_mcp.rs | 4 ++ .../crates/busbar/src/root/units_voice.rs | 30 -------- xtask/src/gates/reachability.rs | 71 ++++++++----------- 10 files changed, 55 insertions(+), 85 deletions(-) create mode 100644 xtask/fixtures/reachability-green/crates/busbar/src/root/serve.rs delete mode 100644 xtask/fixtures/reachability-green/crates/busbar/src/root/tests/units_voice.rs delete mode 100644 xtask/fixtures/reachability-green/crates/busbar/src/root/units_voice.rs diff --git a/xtask/fixtures/reachability-green/crates/busbar/Cargo.toml b/xtask/fixtures/reachability-green/crates/busbar/Cargo.toml index 615d17912d..42d5849598 100644 --- a/xtask/fixtures/reachability-green/crates/busbar/Cargo.toml +++ b/xtask/fixtures/reachability-green/crates/busbar/Cargo.toml @@ -11,7 +11,7 @@ path = "src/main.rs" busbar-llm = { path = "../busbar-llm", optional = true } busbar-mcp = { path = "../busbar-mcp", optional = true } busbar-a2a = { path = "../busbar-a2a", optional = true } -busbar-voice = { path = "../busbar-voice", optional = true } +busbar-plane-streaming = { path = "../busbar-plane-streaming", optional = true } busbar-plane-decisions = { path = "../busbar-plane-decisions", optional = true } # The linked table the real root's build script turns into the `LINKED` table `main.rs` includes. @@ -19,7 +19,7 @@ busbar-plane-decisions = { path = "../busbar-plane-decisions", optional = true } proto-llm = "busbar-llm" plane-mcp = "busbar-mcp" plane-a2a = "busbar-a2a" -plane-streaming = "busbar-voice" +plane-streaming = "busbar-plane-streaming" plane-decisions = "busbar-plane-decisions" # The axes each linked crate registers on; the plane axis is what `register_planes()` folds. @@ -27,7 +27,7 @@ plane-decisions = "busbar-plane-decisions" proto-llm = "plane protocols" plane-mcp = "plane protocols diagnostics" plane-a2a = "plane diagnostics" -plane-streaming = "plane diagnostics" +plane-streaming = "plane-door" plane-decisions = "plane" # The decisions plane's entry module is in the composition root, as on the real tree. diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/main.rs b/xtask/fixtures/reachability-green/crates/busbar/src/main.rs index 9564632e59..5e83c12b97 100644 --- a/xtask/fixtures/reachability-green/crates/busbar/src/main.rs +++ b/xtask/fixtures/reachability-green/crates/busbar/src/main.rs @@ -16,7 +16,7 @@ fn serve() -> u64 { root::plane_node::answer() + root::units_mcp::answer() + root::units_a2a::answer() - + root::units_voice::answer() + + root::serve::serve(&root::serve::compose_planes()) + root::units_decision::answer() } diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/root/gauntlet_install.rs b/xtask/fixtures/reachability-green/crates/busbar/src/root/gauntlet_install.rs index 1cc03fd4f0..6c227d542d 100644 --- a/xtask/fixtures/reachability-green/crates/busbar/src/root/gauntlet_install.rs +++ b/xtask/fixtures/reachability-green/crates/busbar/src/root/gauntlet_install.rs @@ -19,5 +19,4 @@ pub fn install() { flip_one_shot_to_kernel(busbar_llm::PLANE_DECLARATION.key); flip_one_shot_to_kernel(busbar_mcp::PLANE_KEY); flip_one_shot_to_kernel(busbar_a2a::PLANE_KEY); - flip_session_to_kernel(busbar_voice::PLANE_KEY); } diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/root/mod.rs b/xtask/fixtures/reachability-green/crates/busbar/src/root/mod.rs index 203ed684cc..9f0aadcc92 100644 --- a/xtask/fixtures/reachability-green/crates/busbar/src/root/mod.rs +++ b/xtask/fixtures/reachability-green/crates/busbar/src/root/mod.rs @@ -3,7 +3,7 @@ pub mod gauntlet_kernel; pub mod plane_decisions; pub mod plane_node; pub mod registry; +pub mod serve; pub mod units_a2a; pub mod units_decision; pub mod units_mcp; -pub mod units_voice; diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/root/registry.rs b/xtask/fixtures/reachability-green/crates/busbar/src/root/registry.rs index 98055308c4..77f446885e 100644 --- a/xtask/fixtures/reachability-green/crates/busbar/src/root/registry.rs +++ b/xtask/fixtures/reachability-green/crates/busbar/src/root/registry.rs @@ -2,14 +2,12 @@ use busbar_plane_a2a::A2aPlane; use busbar_plane_decisions::DecisionPlane; use busbar_plane_llm::LlmPlane; use busbar_plane_mcp::McpPlane; -use busbar_plane_streaming::StreamingPlane; pub fn plane_claims() -> Vec<&'static str> { let mut claims: Vec<&'static str> = Vec::new(); claims.push(LlmPlane::KEY); claims.push(McpPlane::KEY); claims.push(A2aPlane::KEY); - claims.push(StreamingPlane::KEY); claims.push(DecisionPlane::KEY); claims } diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/root/serve.rs b/xtask/fixtures/reachability-green/crates/busbar/src/root/serve.rs new file mode 100644 index 0000000000..0577ebed72 --- /dev/null +++ b/xtask/fixtures/reachability-green/crates/busbar/src/root/serve.rs @@ -0,0 +1,15 @@ +// The door planes' one composition, in miniature: exactly one function reached from `fn main()` +// builds a `PlaneDriver`, and a reached line in this module drives a unit through it (the served data +// route). A plane whose crate is linked on the `plane-door` axis is served here. +pub struct ServedPlane { + driver: PlaneDriver, +} + +pub fn compose_planes() -> Vec { + let driver = PlaneDriver::new(1); + vec![ServedPlane { driver }] +} + +pub fn serve(planes: &[ServedPlane]) -> u64 { + planes.iter().map(|p| p.driver.unit(1)).sum() +} diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/root/tests/units_voice.rs b/xtask/fixtures/reachability-green/crates/busbar/src/root/tests/units_voice.rs deleted file mode 100644 index 5d08fb00eb..0000000000 --- a/xtask/fixtures/reachability-green/crates/busbar/src/root/tests/units_voice.rs +++ /dev/null @@ -1,7 +0,0 @@ -use super::VoiceUnit; - -#[test] -fn the_unit_drives() { - let unit = VoiceUnit::new(1); - assert_eq!(unit.n, 1); -} diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/root/units_mcp.rs b/xtask/fixtures/reachability-green/crates/busbar/src/root/units_mcp.rs index c117448369..223fd631b8 100644 --- a/xtask/fixtures/reachability-green/crates/busbar/src/root/units_mcp.rs +++ b/xtask/fixtures/reachability-green/crates/busbar/src/root/units_mcp.rs @@ -24,3 +24,7 @@ pub fn answer() -> u64 { #[cfg(test)] #[path = "tests/units_mcp.rs"] mod tests; + +/// The root unit the generated table reaches when the manifest lists this module under +/// `[package.metadata.busbar.root-units]` — the self-test's generated-table cases plant that row. +pub const ROOT_UNIT: fn() -> u64 = answer; diff --git a/xtask/fixtures/reachability-green/crates/busbar/src/root/units_voice.rs b/xtask/fixtures/reachability-green/crates/busbar/src/root/units_voice.rs deleted file mode 100644 index a59c8dee12..0000000000 --- a/xtask/fixtures/reachability-green/crates/busbar/src/root/units_voice.rs +++ /dev/null @@ -1,30 +0,0 @@ -pub struct VoiceUnit { - n: u64, -} - -impl VoiceUnit { - pub fn new(n: u64) -> Self { - VoiceUnit { n } - } -} - -impl busbar_kernel::teller::Units for VoiceUnit { - fn drive(&self) -> u64 { - self.n - } -} - -/// The reached caller: `main` -> `serve` -> here. It returns something OTHER than the unit, so -/// the construction below is a CALL of the constructor rather than the constructor's own body. -pub fn answer() -> u64 { - let unit = VoiceUnit::new(1); - unit.drive() -} - -/// The root unit the generated table reaches when the manifest lists this module under -/// `[package.metadata.busbar.root-units]` — the self-test's generated-table cases plant that row. -pub const ROOT_UNIT: fn() -> u64 = answer; - -#[cfg(test)] -#[path = "tests/units_voice.rs"] -mod tests; diff --git a/xtask/src/gates/reachability.rs b/xtask/src/gates/reachability.rs index 5e60c21c82..adcc69e141 100644 --- a/xtask/src/gates/reachability.rs +++ b/xtask/src/gates/reachability.rs @@ -289,12 +289,13 @@ const ROSTER: &[Plane] = &[ }, Plane { key: "streaming", - on_disk: "voice", - module: "units_voice", - linked_crate: "busbar-voice", + on_disk: "plane-streaming", + module: "units_streaming", + linked_crate: "busbar-plane-streaming", register_tokens: &["StreamingPlane"], - note: "#18: streaming is the PLANE, voice is one dialect inside it. The rename has not \ - landed, so the module and the crate are still spelled `voice`", + note: "#18: streaming is the PLANE (voice is one dialect inside it), a DOOR plane since \ + FLIP-STREAMING: its crate is `busbar-plane-streaming`, registered by its `plane-door` \ + row (Part 2 #2) and served through the one composition's driver (Q-SO10)", }, Plane { key: "decision", @@ -2437,8 +2438,8 @@ impl Gate for ReachabilityGate { let f = &findings[p.key]; // THE ON-DISK SPELLING IS IN THE TITLE WHENEVER IT DIFFERS. A reader who finds // `reachability:unit-path:streaming` red has to know, without opening this file, that - // the module it is about is `units_voice.rs` — the #18 rename has not landed and a row - // naming a file that is not there is a row nobody can act on. + // the crate it is about — a row naming a file that is not there is a row nobody can act + // on. let who = if p.key == p.on_disk { format!("plane `{}`", p.key) } else { @@ -2716,15 +2717,13 @@ impl Gate for ReachabilityGate { cx, FIX_GREEN, "a key flipped only under #[cfg(test)] reds its unit-path and root-reach rows", - &[row_unit_path("streaming"), row_root_reach("streaming")], + &[row_unit_path("a2a"), row_root_reach("a2a")], { let mut ov = Overlay::new(); ov.set( INSTALL_RS, - FIXTURE_GREEN_INSTALL.replace( - VOICE_FLIP_LINE, - &format!(" #[cfg(test)]\n{VOICE_FLIP_LINE}"), - ), + FIXTURE_GREEN_INSTALL + .replace(A2A_FLIP_LINE, &format!(" #[cfg(test)]\n{A2A_FLIP_LINE}")), ); ov }, @@ -2781,9 +2780,9 @@ impl Gate for ReachabilityGate { ov.set( INSTALL_RS, FIXTURE_GREEN_INSTALL.replace( - VOICE_FLIP_LINE, + A2A_FLIP_LINE, &format!( - "{VOICE_FLIP_LINE} flip_one_shot_to_kernel(crate::root::plane_decisions::PLANE_DECLARATION.key);\n" + "{A2A_FLIP_LINE} flip_one_shot_to_kernel(crate::root::plane_decisions::PLANE_DECLARATION.key);\n" ), ), ); @@ -2900,7 +2899,7 @@ impl Gate for ReachabilityGate { cx, FIX_GREEN, "a plane whose linked-axes row carries no gauntlet axis is not flipped by the fold", - &[row_unit_path("streaming"), row_root_reach("streaming")], + &[row_unit_path("a2a"), row_root_reach("a2a")], folded_install(false), "no fold over `LINKED` flips it", )); @@ -3088,17 +3087,19 @@ impl Gate for ReachabilityGate { cx, FIX_GREEN, "a crate linked but not on the plane axis, and no plane_claims token, reds its registration row", - &[row_registered("streaming")], + &[row_registered("mcp")], { let mut ov = claims_name_no_plane(); ov.set( CRATE_MANIFEST, - FIXTURE_LINKED_MANIFEST - .replace("plane-streaming = \"plane diagnostics\"", "plane-streaming = \"diagnostics\""), + FIXTURE_LINKED_MANIFEST.replace( + "plane-mcp = \"plane protocols diagnostics\"", + "plane-mcp = \"protocols diagnostics\"", + ), ); ov }, - "no linked-table row for `busbar-voice` on the `plane` or `plane-door` axis", + "no linked-table row for `busbar-mcp` on the `plane` or `plane-door` axis", )); // A DOOR ROW IS REGISTRATION (Part 2 #2, compiled-in = dropped-in): a door-shaped plane — // its declaration row on no `plane` axis, its crate linked again on `plane-door`, no @@ -3141,7 +3142,7 @@ impl Gate for ReachabilityGate { cx, FIX_GREEN, "a module `main.rs` reaches only through a root-units row of the generated table is reached", - evidenced(voice_reached_only_through_root_units(true)), + evidenced(mcp_reached_only_through_root_units(true)), )); report.push(red_over( self, @@ -3149,8 +3150,8 @@ impl Gate for ReachabilityGate { FIX_GREEN, "the same module with no root-units row reds the root-module row", &[ROW_ROOT_MODULE.to_string()], - voice_reached_only_through_root_units(false), - "units_voice", + mcp_reached_only_through_root_units(false), + "units_mcp", )); report @@ -3168,19 +3169,19 @@ fn claims_name_no_plane() -> Overlay { ov } -/// The green fixture with `main.rs` no longer calling into `units_voice` — reached, if at all, only +/// The green fixture with `main.rs` no longer calling into `units_mcp` — reached, if at all, only /// through a root-units row of the manifest (planted when `listed`). -fn voice_reached_only_through_root_units(listed: bool) -> Overlay { +fn mcp_reached_only_through_root_units(listed: bool) -> Overlay { let mut ov = Overlay::new(); ov.set( MAIN_RS, - FIXTURE_GREEN_MAIN.replace(" + root::units_voice::answer()\n", ""), + FIXTURE_GREEN_MAIN.replace(" + root::units_mcp::answer()\n", ""), ); if listed { ov.set( CRATE_MANIFEST, format!( - "{FIXTURE_LINKED_MANIFEST}\n[package.metadata.busbar.root-units]\nroot-voice = \"units_voice\"\n" + "{FIXTURE_LINKED_MANIFEST}\n[package.metadata.busbar.root-units]\nroot-mcp = \"units_mcp\"\n" ), ); } @@ -3207,7 +3208,6 @@ const FIXTURE_GREEN_DECISION: &str = include_str!("../../fixtures/reachability-green/crates/busbar/src/root/plane_decisions.rs"); const A2A_FLIP_LINE: &str = " flip_one_shot_to_kernel(busbar_a2a::PLANE_KEY);\n"; const MCP_FLIP_LINE: &str = " flip_one_shot_to_kernel(busbar_mcp::PLANE_KEY);\n"; -const VOICE_FLIP_LINE: &str = " flip_session_to_kernel(busbar_voice::PLANE_KEY);\n"; const INSTALL_CALL_LINE: &str = " root::gauntlet_install::install();\n"; /// The decisions plane's entry as it is on the real tree: a declaration and hooks, no `Units` impl — @@ -3257,14 +3257,9 @@ fn decision_door(door_row: bool, driven: bool, twice: bool) -> Overlay { ); calls.push_str(" let _ = root::serve::compose_again();\n"); } + // The green fixture's root already carries `root/serve.rs` (the streaming door plane's one + // composition); this plants its decisions-shaped variant over it. ov.set(SERVE_RS, serve); - ov.set( - "crates/busbar/src/root/mod.rs", - format!( - "{}pub mod serve;\n", - include_str!("../../fixtures/reachability-green/crates/busbar/src/root/mod.rs") - ), - ); ov.set( MAIN_RS, FIXTURE_GREEN_MAIN.replace(INSTALL_CALL_LINE, &format!("{INSTALL_CALL_LINE}{calls}")), @@ -3401,15 +3396,11 @@ fn folded_install(all: bool) -> Overlay { .replace( "plane-mcp = \"plane protocols diagnostics\"", "plane-mcp = \"plane protocols diagnostics gauntlet-one-shot\"", - ) - .replace( - "plane-a2a = \"plane diagnostics\"", - "plane-a2a = \"plane diagnostics gauntlet-one-shot\"", ); if all { manifest = manifest.replace( - "plane-streaming = \"plane diagnostics\"", - "plane-streaming = \"plane diagnostics gauntlet-session\"", + "plane-a2a = \"plane diagnostics\"", + "plane-a2a = \"plane diagnostics gauntlet-one-shot\"", ); } let mut ov = Overlay::new(); From a9ceac78cb33aa4e88b97a2c67a9288e9d6e51a5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:16:30 -0700 Subject: [PATCH 55/82] voice_conform: clippy (auto-deref, contains); xtask infra test: the plane-key contract follows the FLIP-STREAMING roster strike of voice --- .../examples/voice_conform/composition.rs | 4 ++-- xtask/tests/infra.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/busbar-plane-streaming/examples/voice_conform/composition.rs b/crates/busbar-plane-streaming/examples/voice_conform/composition.rs index 64b52f44cf..a9412f4557 100644 --- a/crates/busbar-plane-streaming/examples/voice_conform/composition.rs +++ b/crates/busbar-plane-streaming/examples/voice_conform/composition.rs @@ -744,7 +744,7 @@ fn admit_script(p: &Plugin) -> AdmitObs { let _ = open(p, SESSION, Some(PUBLIC)); let rendered = REFUSALS .iter() - .map(|(status, words, _)| (*status, refusal(p, 60, 0, u32::from(*status), *words))) + .map(|(status, words, _)| (*status, refusal(p, 60, 0, u32::from(*status), words))) .collect(); let far = piece(p, &session(60, 2, FROM_FAR_END, 0, &downlink(96))); let collect = piece(p, &session(60, 2, FROM_KERNEL, 0, &[])); @@ -1289,7 +1289,7 @@ fn tool_check(dialect: Dialect, o: &ToolObs) -> Result { "{name}: the turn's usage was relayed instead of reported" )); } - if !o.units.iter().any(|u| *u == (5, 1, 1)) { + if !o.units.contains(&(5, 1, 1)) { return Err(format!( "{name}: the call was not counted once: {:?}", o.units diff --git a/xtask/tests/infra.rs b/xtask/tests/infra.rs index bb84f2d6a3..b3a10a3d7f 100644 --- a/xtask/tests/infra.rs +++ b/xtask/tests/infra.rs @@ -488,8 +488,8 @@ fn strip_comment_line_keeps_string_literals_intact() { #[test] fn the_plane_key_contract_matches_plane_keys_sh() { - assert_eq!(planes::PLANE_KEYS, ["llm", "mcp", "a2a", "voice"]); - assert_eq!(planes::plane_keys_protocol(), vec!["mcp", "a2a", "voice"]); + assert_eq!(planes::PLANE_KEYS, ["llm", "mcp", "a2a"]); + assert_eq!(planes::plane_keys_protocol(), vec!["mcp", "a2a"]); let src = planes::plane_src_roots(); assert_eq!(src[0], "crates/busbar-llm/src"); assert!(!src.contains(&"crates/busbar-llm-codec/src".to_string())); From e46935ba49137371347f896be2e637bc433daf55 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:31:53 -0700 Subject: [PATCH 56/82] ws battery: a dialled connection is read for its life (attach_reader), as the plane driver reads its far end: the handshake's answer is ingested and the client's first message leaves with it; the server-read cells bound their wait --- crates/busbar/tests/ws_composed_battery.rs | 25 +++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/busbar/tests/ws_composed_battery.rs b/crates/busbar/tests/ws_composed_battery.rs index 7dde000ff5..01e6a363e0 100644 --- a/crates/busbar/tests/ws_composed_battery.rs +++ b/crates/busbar/tests/ws_composed_battery.rs @@ -173,6 +173,21 @@ impl Stack { } } +/// A DIALLED CONNECTION IS READ FOR ITS LIFE, as the plane driver reads its far end: the reader +/// ingests the handshake's answer, and what the wire owes the far end (a message written before the +/// answer arrived) leaves with it. A writer that never reads would hold its first message until it +/// did. The reader is aborted when the returned handle drops out of use. +fn attach_reader( + t: &Arc, + conn: &busbar_contract::transport::wire::Conn, +) -> tokio::task::JoinHandle<()> { + let (t, conn) = (Arc::clone(t), conn.clone()); + tokio::spawn(async move { + let mut frames = t.frames(conn); + while frames.next().await.is_some() {} + }) +} + /// A minimal listener config view, built only from `busbar_contract`'s public /// `ConfigView`/`TransportConfigView` traits: a bind address and, optionally, the body cap. struct ListenerCfg(String, Option); @@ -294,6 +309,7 @@ async fn a_composed_round_trip_over_the_layers_below() { let (client_t, client_conn, server_t, server_conn) = stack .upgraded(&TransportSettings::default(), stack.dialler(), "/") .await; + let reader = attach_reader(&client_t, &client_conn); // Both ends report the stack they actually stand on, not a name for themselves. let served = server_t.arrival(&server_conn).transport_chain; @@ -322,8 +338,13 @@ async fn a_composed_round_trip_over_the_layers_below() { .await .unwrap(); let mut frames = server_t.frames(server_conn); - let (_s, frame) = frames.next().await.unwrap().unwrap(); + let (_s, frame) = tokio::time::timeout(Duration::from_secs(5), frames.next()) + .await + .expect("the server reads what the client wrote") + .unwrap() + .unwrap(); assert_eq!(frame.bytes.as_slice(), b"hello over the layers below"); + reader.abort(); } /// The layer this instance reports is the one it declares: NONE. An upgrade wire composes over @@ -366,6 +387,7 @@ async fn the_message_cap_is_the_operator_s_and_not_the_library_s() { // The peer is not capped at the operator's number, because the cap this test is about is the // RECEIVER's: a limit that only holds when the far side agrees to it is not a limit. let (peer, a, t, b) = stack.upgraded(&capped, stack.dialler(), "/").await; + let reader = attach_reader(&peer, &a); let oversized = vec![b'w'; 2 * CAP]; peer.write(&a, StreamId(0), ScratchBytes::new(&oversized)) @@ -382,6 +404,7 @@ async fn the_message_cap_is_the_operator_s_and_not_the_library_s() { TransportError::Framing, "a message past the operator's cap is a framing refusal" ); + reader.abort(); } /// And the other lifecycle: an instance that only ever DIALS holds the same ceiling — the settings From 7265da9df8b9355e787bae6ed0390a4727b3a2d6 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:32:19 -0700 Subject: [PATCH 57/82] busbar-transport-ws pinned at 119ef7af47 (transport-ws#13 head: the framer is the kind's entry file transport.rs; no API change) --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0e3045137..8577317912 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1100,7 +1100,7 @@ dependencies = [ [[package]] name = "busbar-transport-ws" version = "1.6.0" -source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=364b14eb443a7bc6a182d385ff0d5195ce111bd9#364b14eb443a7bc6a182d385ff0d5195ce111bd9" +source = "git+https://github.com/GetBusbar/busbar-transport-ws?rev=119ef7af477bbccef96a953a1dfcf584644385a8#119ef7af477bbccef96a953a1dfcf584644385a8" dependencies = [ "busbar-contract", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 1169086da9..6950d8b4fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ busbar-transport-http = { git = "https://github.com/GetBusbar/busbar-transport-h busbar-transport-grpc = { git = "https://github.com/GetBusbar/busbar-transport-grpc", rev = "5607182ed5d28da20a6e1699aad512909d24142e" } # THE WS TRANSPORT: the websocket framer door (kind: transport), pulled at a pinned rev; the logic # crate the root links (behind plane-streaming) and the ws-conformance subject drives. -busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "364b14eb443a7bc6a182d385ff0d5195ce111bd9" } +busbar-transport-ws = { git = "https://github.com/GetBusbar/busbar-transport-ws", rev = "119ef7af477bbccef96a953a1dfcf584644385a8" } busbar-export-webhook-plugin = { git = "https://github.com/GetBusbar/busbar-export-webhook", rev = "3ad5a711aad433f2015a980cf3a792afa5fd3a7a" } # THE TCP TRANSPORT: the identity framer door (kind: transport), one pin for the logic crate the root # links and the cdylib crate the loader's both-ways proof builds. Same repo, same rev, one resolved copy. From b88ca40087832742482f923430ba00fcc16397f2 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:24:19 -0700 Subject: [PATCH 58/82] RELEASE_REF and the oracle pin move to busbar-release 30d072960a (#168): the build-test plan strikes the voice runtime row, whose busbar-voice crate this lane deletes; no engine change --- .github/workflows/promote.yml | 2 +- testing/shadow-oracle/oracle-rust.pin | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index 62d1f89917..a5b2515d79 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -12,7 +12,7 @@ on: [pull_request, merge_group] concurrency: { group: "promote-${{ github.event.pull_request.number || github.event.merge_group.head_ref }}", cancel-in-progress: "${{ github.event_name == 'pull_request' && github.base_ref != 'dev' }}" } permissions: { contents: read } env: - RELEASE_REF: 4a7a7bba2cef472ab4078ab3b50afc5b99c2c4ac + RELEASE_REF: 30d072960aa13af8a2bb59fc87de4251c2238ba1 SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} TO: ${{ github.base_ref || github.event.merge_group.base_ref }} BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} diff --git a/testing/shadow-oracle/oracle-rust.pin b/testing/shadow-oracle/oracle-rust.pin index 3bc3e51c09..e9c000f7cd 100644 --- a/testing/shadow-oracle/oracle-rust.pin +++ b/testing/shadow-oracle/oracle-rust.pin @@ -237,8 +237,15 @@ # (c7de1ab) lacks dev's engine fixes and is NOT pinned. promote.yml RELEASE_REF moves to the same sha in the # same commit, so CI and lane proofs run ONE engine. # +# 2026-10-06 (busbar P3 FLIP-STREAMING / DEL-VOICE): moved 4a7a7bba2cef -> 30d072960aa1 (busbar-release +# `p3-flip-streaming-plan`, PR #168 into dev, cut from 4a7a7bba2cef). One commit, no engine change: the +# build-test plan's `voice runtime` row leaves with the deleted busbar-voice crate (plan-lint refuses a row +# naming a package the tree lacks). The judge's code is 4a7a7bba2cef's byte for byte. promote.yml +# RELEASE_REF moves to the same sha in the same commit. Feature branch head; re-pin to the merge commit +# when it lands. +# # To move the pin: set release_ref to the new busbar-release commit and re-run. release_repo=https://github.com/GetBusbar/busbar-release.git -release_ref=4a7a7bba2cef472ab4078ab3b50afc5b99c2c4ac +release_ref=30d072960aa13af8a2bb59fc87de4251c2238ba1 crate=busbar-release-oracle bin=busbar-oracle From 553fc181f0c4e9176c9eda828ce2fc844ae07a2d Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:25:46 -0700 Subject: [PATCH 59/82] export conformance test host: dest_judge takes the flags word (the lane's DEST_* flags signature), as predev's new DiskHost double did not --- crates/plugin-loader/src/tests/export_conformance_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/plugin-loader/src/tests/export_conformance_tests.rs b/crates/plugin-loader/src/tests/export_conformance_tests.rs index 140d6002c2..eec200059a 100644 --- a/crates/plugin-loader/src/tests/export_conformance_tests.rs +++ b/crates/plugin-loader/src/tests/export_conformance_tests.rs @@ -289,7 +289,7 @@ impl HostServices for DiskHost { mono_ns: 0, } } - fn dest_judge(&self, _: &str, _: u32, _: bool, _: Option) -> Ran { + fn dest_judge(&self, _: &str, _: u32, _: u32, _: Option) -> Ran { Ran::Now(Stored::refused(NO_SERVICE)) } fn records_get(&self, _: &Caller, _: &str, _: &[u8], _: Later) -> Ran { From 0456742cfe9a749a1841c7c70a2a92ae7b6e3683 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:49:38 -0700 Subject: [PATCH 60/82] merge fallout: the late-attach record-store test opens the build's linked store (Q-STORE (B): no row is a default, a 3-field row); serve_door's money import rides plane-decisions, the only cells that use it --- crates/busbar/src/root/tests/serve.rs | 10 +++++----- crates/busbar/src/root/tests/serve_door.rs | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/busbar/src/root/tests/serve.rs b/crates/busbar/src/root/tests/serve.rs index 7c3357dc4a..a15588077f 100644 --- a/crates/busbar/src/root/tests/serve.rs +++ b/crates/busbar/src/root/tests/serve.rs @@ -512,14 +512,14 @@ async fn the_late_attach_binds_the_governance_store_as_the_record_store() { }; let opened = axis .open( - // The build's default store, as boot opens it (the linked store axis's default row). + // The build's linked store, as boot opens it (Q-STORE (B): no row is a default; the + // store is the one config names, and every build links the memory store). StoreDoor::Linked( crate::LINKED .stores - .iter() - .find(|s| s.2) - .expect("the build links a default store") - .4, + .first() + .expect("the build links a store") + .2, ), "records-attach", b"{}", diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index 9d1b1050c8..4c104cf61e 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -36,7 +36,9 @@ use busbar_kernel::plane_driver::{refusal_status, EndPost, PlaneMoney}; use busbar_plane_decisions::plane_door::door as decisions_door; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use super::planes_tests::{composed_services, money, Published, PUBLISHING}; +#[cfg(feature = "plane-decisions")] +use super::planes_tests::money; +use super::planes_tests::{composed_services, Published, PUBLISHING}; use super::{compose_planes, door_routes, DoorEgress}; use crate::root::door_steps::{provider_routes, DoorReach, OutboundAuths}; use crate::root::loader::dispatch::kinds::plane::Plane; From 2f018313d8358b3ef20c4d04bfbb89569fd34a1a Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:58:37 -0700 Subject: [PATCH 61/82] voice conformance harness: its own testkit crate (testing/voice-conformance/harness), as the ws rig's subject is kind-isolation:closure went red on the lane: busbar-plane-streaming's voice_conform example used the loader, so the plane's dev-edge on busbar-plugin-loader was no longer only its both-ways witness (tests/conformance.rs, the one granted file) and the test graph reached busbar-kernel-ledger/-wal. The harness moves out unchanged (ARCHITECT Q6: built against busbar-plane-streaming, linked and dlopened, RED arm kept) into the member voice-conformance-harness, listed beside testing/ws-conformance/subject as a non-product manifest; it finds the plane's streaming_door cdylib in the profile's examples/ beside its binary, and conform-bin.sh builds the pair. --- Cargo.lock | 11 +++++++ Cargo.toml | 1 + crates/busbar-plane-streaming/Cargo.toml | 10 ++----- testing/shadow-oracle/rigs-ledger.sh | 2 +- testing/voice-conformance/README.md | 13 +++++---- testing/voice-conformance/harness/Cargo.toml | 29 +++++++++++++++++++ .../harness/src}/codec_legs.rs | 0 .../harness/src}/composition.rs | 0 .../voice-conformance/harness/src}/door.rs | 7 +++-- .../harness/src}/governance.rs | 0 .../voice-conformance/harness/src}/main.rs | 2 +- testing/voice-conformance/lib/conform-bin.sh | 11 ++++--- .../voice-conformance/voice-conformance.sh | 2 +- xtask/src/gates/kind_isolation.rs | 11 +++++++ 14 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 testing/voice-conformance/harness/Cargo.toml rename {crates/busbar-plane-streaming/examples/voice_conform => testing/voice-conformance/harness/src}/codec_legs.rs (100%) rename {crates/busbar-plane-streaming/examples/voice_conform => testing/voice-conformance/harness/src}/composition.rs (100%) rename {crates/busbar-plane-streaming/examples/voice_conform => testing/voice-conformance/harness/src}/door.rs (99%) rename {crates/busbar-plane-streaming/examples/voice_conform => testing/voice-conformance/harness/src}/governance.rs (100%) rename {crates/busbar-plane-streaming/examples/voice_conform => testing/voice-conformance/harness/src}/main.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index 1f0d7c4409..f472adb035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4289,6 +4289,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "voice-conformance-harness" +version = "0.0.0" +dependencies = [ + "busbar-contract", + "busbar-plane-streaming", + "busbar-plugin-loader", + "bytes", + "serde_json", +] + [[package]] name = "vsimd" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index ebe35a038a..3434fa2f27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "crates/busbar-auth-oauth", "crates/busbar-auth-webhook-signature", "testing/ws-conformance/subject", + "testing/voice-conformance/harness", "xtask", ] diff --git a/crates/busbar-plane-streaming/Cargo.toml b/crates/busbar-plane-streaming/Cargo.toml index db4a50956d..8e1c986587 100644 --- a/crates/busbar-plane-streaming/Cargo.toml +++ b/crates/busbar-plane-streaming/Cargo.toml @@ -73,10 +73,6 @@ busbar-plugin-loader = { path = "../plugin-loader" } name = "streaming_door" crate-type = ["cdylib"] -# THE VOICE CONFORMANCE HARNESS (`testing/voice-conformance/`, ARCHITECT Q6): a dev-only binary -# that drives the door above BOTH ways — linked, and the `streaming_door` cdylib dlopened — through -# the loader, over the dev-dependencies only. Never shipped; `cargo build --example voice_conform -# --example streaming_door` builds the pair the rig's legs run. -[[example]] -name = "voice_conform" -path = "examples/voice_conform/main.rs" +# THE VOICE CONFORMANCE HARNESS that drives the door above both ways (`testing/voice-conformance/`, +# ARCHITECT Q6) is its own testkit crate, `testing/voice-conformance/harness`: this crate's one +# user of the loader stays its own both-ways witness, `tests/conformance.rs`. diff --git a/testing/shadow-oracle/rigs-ledger.sh b/testing/shadow-oracle/rigs-ledger.sh index 0ff1b21074..6a5821db57 100755 --- a/testing/shadow-oracle/rigs-ledger.sh +++ b/testing/shadow-oracle/rigs-ledger.sh @@ -89,7 +89,7 @@ # # ARMING # MCP_SUBJECT_BUSBAR_BIN / A2A_SUBJECT_BUSBAR_BIN are set from --bin for the MCP and A2A legs. The -# voice battery arms itself (it drives its OWN `voice_conform` harness (busbar-plane-streaming example) from this tree, +# voice battery arms itself (it drives its OWN `voice_conform` harness (testing/voice-conformance/harness) from this tree, # never busbar's server binary — see testing/voice-conformance/lib/conform-bin.sh), so --bin plays # no part in that leg; it is still required, so a run cannot silently skip the two legs that do need # it by never providing one. diff --git a/testing/voice-conformance/README.md b/testing/voice-conformance/README.md index 98ea15666a..85c461b212 100644 --- a/testing/voice-conformance/README.md +++ b/testing/voice-conformance/README.md @@ -7,8 +7,8 @@ batteries. It is the streaming plane's conformance MUST-set (DEV-GREEN exit #4). ## What a leg drives: the plane's DOOR, both ways The plane is served only through its memory-ABI door (`busbar_plane_streaming::door`). Every leg -shells out to one dev-only harness, `busbar-plane-streaming`'s `voice_conform` example -(`crates/busbar-plane-streaming/examples/voice_conform/`), which loads the door through +shells out to one dev-only harness, the `voice_conform` binary of the testkit crate +`voice-conformance-harness` (`testing/voice-conformance/harness/`), which loads the door through `busbar-plugin-loader` **both ways** (ARCHITECT Q6): - **linked** — `busbar_plane_streaming::door::door`, through `load_linked`; @@ -24,10 +24,11 @@ Each assertion drives a fresh instance of each door with one script and is PASS would give) is refused by the same check. `VOICE_CONFORM_RED=1` makes the planted answers the subject; every conformance leg must then go RED. -`lib/conform-bin.sh` builds the pair once (`cargo build -p busbar-plane-streaming --example -voice_conform --example streaming_door`); `VOICE_CONFORM_BIN` names a prebuilt harness (checked for -staleness against the plane, loader and contract sources), `VOICE_CONFORM_DOOR` a dropped door that is -not beside it. +`lib/conform-bin.sh` builds the pair once (`cargo build -p voice-conformance-harness --bin +voice_conform` and `cargo build -p busbar-plane-streaming --example streaming_door`); +`VOICE_CONFORM_BIN` names a prebuilt harness (checked for staleness against the plane, loader, +contract and harness sources), `VOICE_CONFORM_DOOR` a dropped door that is not in the profile's +`examples/` beside it. ## The legs — each judges the DOOR'S half of its seam diff --git a/testing/voice-conformance/harness/Cargo.toml b/testing/voice-conformance/harness/Cargo.toml new file mode 100644 index 0000000000..188b523c05 --- /dev/null +++ b/testing/voice-conformance/harness/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "voice-conformance-harness" +version = "0.0.0" +publish = false +edition = "2021" +license = "Apache-2.0" +rust-version = "1.97" +description = "The voice (streaming-plane) conformance harness: drives busbar-plane-streaming's door both ways, linked and dropped in, through the plugin loader." + +# TESTKIT, NOT PRODUCT PATH (ARCHITECT Q6: the streaming plane's conformance MUST-set, built against +# `busbar-plane-streaming`, linked and dlopened, RED arm kept). `testing/voice-conformance/`'s legs +# shell out to this binary. It is not shipped and not built by `cargo build --bin busbar`: it is the +# same kind of member as `testing/ws-conformance/subject`, an input to a conformance leg. It lives +# outside the plane crate so the plane's only user of the loader is its own both-ways witness +# (`tests/conformance.rs`), the one dev-edge on the loader a plugin crate is granted. +[[bin]] +name = "voice_conform" +path = "src/main.rs" + +[dependencies] +busbar-contract = { path = "../../../crates/busbar-contract" } +# THE PLANE UNDER TEST: its linked door (`busbar_plane_streaming::door::door`) and its codecs, which +# compute every expectation from the wire. The dropped-in door is the plane's own `streaming_door` +# example cdylib. +busbar-plane-streaming = { path = "../../../crates/busbar-plane-streaming" } +# THE ONE LOADER both doors are loaded through (`load_linked`, `load_dropped`). +busbar-plugin-loader = { path = "../../../crates/plugin-loader" } +serde_json = { workspace = true } +bytes = { workspace = true } diff --git a/crates/busbar-plane-streaming/examples/voice_conform/codec_legs.rs b/testing/voice-conformance/harness/src/codec_legs.rs similarity index 100% rename from crates/busbar-plane-streaming/examples/voice_conform/codec_legs.rs rename to testing/voice-conformance/harness/src/codec_legs.rs diff --git a/crates/busbar-plane-streaming/examples/voice_conform/composition.rs b/testing/voice-conformance/harness/src/composition.rs similarity index 100% rename from crates/busbar-plane-streaming/examples/voice_conform/composition.rs rename to testing/voice-conformance/harness/src/composition.rs diff --git a/crates/busbar-plane-streaming/examples/voice_conform/door.rs b/testing/voice-conformance/harness/src/door.rs similarity index 99% rename from crates/busbar-plane-streaming/examples/voice_conform/door.rs rename to testing/voice-conformance/harness/src/door.rs index a45c4ea4a7..138ad4d310 100644 --- a/crates/busbar-plane-streaming/examples/voice_conform/door.rs +++ b/testing/voice-conformance/harness/src/door.rs @@ -134,8 +134,9 @@ pub struct Rig { stated: Vec, } -/// Where the dropped-in door is: `VOICE_CONFORM_DOOR`, else the `streaming_door` library beside -/// this harness (`cargo build --example` puts both in the profile's `examples/`). +/// Where the dropped-in door is: `VOICE_CONFORM_DOOR`, else the plane's `streaming_door` example +/// library in the profile's `examples/` beside this harness's binary (`cargo build -p +/// busbar-plane-streaming --example streaming_door` puts it there). fn dropped_path() -> Result { if let Some(p) = std::env::var_os(DOOR_ENV) { let p = PathBuf::from(p); @@ -154,7 +155,7 @@ fn dropped_path() -> Result { std::env::consts::DLL_PREFIX, std::env::consts::DLL_SUFFIX ); - let path = dir.join(name); + let path = dir.join("examples").join(name); if path.is_file() { Ok(path) } else { diff --git a/crates/busbar-plane-streaming/examples/voice_conform/governance.rs b/testing/voice-conformance/harness/src/governance.rs similarity index 100% rename from crates/busbar-plane-streaming/examples/voice_conform/governance.rs rename to testing/voice-conformance/harness/src/governance.rs diff --git a/crates/busbar-plane-streaming/examples/voice_conform/main.rs b/testing/voice-conformance/harness/src/main.rs similarity index 96% rename from crates/busbar-plane-streaming/examples/voice_conform/main.rs rename to testing/voice-conformance/harness/src/main.rs index 5740167c04..6d3f44ca91 100644 --- a/crates/busbar-plane-streaming/examples/voice_conform/main.rs +++ b/testing/voice-conformance/harness/src/main.rs @@ -5,7 +5,7 @@ //! the streaming plane's DOOR (ARCHITECT Q6: the streaming plane's conformance MUST-set, built //! against `busbar-plane-streaming`, linked and dlopened, RED arm kept). //! -//! A dev-only example: never shipped, no production dependency. Every assertion drives the door +//! A dev-only testkit binary: never shipped, no production dependency. Every assertion drives the door //! through `busbar-plugin-loader` BOTH ways — the LINKED door (`busbar_plane_streaming::door::door`) //! and the DROPPED door (the `streaming_door` example `cdylib`, dlopened) — and requires both to //! answer identically, then judges the answer, then requires the leg's planted wrong answer to be diff --git a/testing/voice-conformance/lib/conform-bin.sh b/testing/voice-conformance/lib/conform-bin.sh index f539dba15b..58e759894d 100644 --- a/testing/voice-conformance/lib/conform-bin.sh +++ b/testing/voice-conformance/lib/conform-bin.sh @@ -6,8 +6,8 @@ # # SHARED HELPER for the voice-conformance legs — NOT a leg (it lives outside legs/, so the runner's # `legs/*.sh` glob never discovers it). Sourced by each `legs/.sh` to locate the REAL Rust -# conformance harness the legs shell out to: `busbar-plane-streaming`'s dev-only `voice_conform` -# example, which drives the streaming plane's DOOR both ways through the loader — the linked door and +# conformance harness the legs shell out to: the `voice_conform` binary of the testkit crate +# `testing/voice-conformance/harness`, which drives the streaming plane's DOOR both ways through the loader — the linked door and # the `streaming_door` example cdylib, dlopened (ARCHITECT Q6). The legs reuse the plane's own door # and codecs through this harness; they never reimplement a codec — or a door — in shell. @@ -28,6 +28,7 @@ _vc_harness_trees() { printf '%s\n' \ "$VC_ROOT/crates/busbar-plane-streaming/src" \ "$VC_ROOT/crates/busbar-plane-streaming/examples" \ + "$VC_DIR/harness/src" \ "$VC_ROOT/crates/plugin-loader/src" \ "$VC_ROOT/crates/busbar-contract/src" \ "$VC_DIR/legs" \ @@ -78,9 +79,11 @@ voice_conform_bin() { printf '%s' "$VOICE_CONFORM_BIN" return 0 fi - local bin="${CARGO_TARGET_DIR:-$VC_ROOT/target}/debug/examples/voice_conform" + local bin="${CARGO_TARGET_DIR:-$VC_ROOT/target}/debug/voice_conform" + cargo build -q --manifest-path "$VC_ROOT/Cargo.toml" -p voice-conformance-harness \ + --bin voice_conform >&2 || return 1 cargo build -q --manifest-path "$VC_ROOT/Cargo.toml" -p busbar-plane-streaming \ - --example voice_conform --example streaming_door >&2 || return 1 + --example streaming_door >&2 || return 1 [ -x "$bin" ] || return 1 printf '%s' "$bin" } diff --git a/testing/voice-conformance/voice-conformance.sh b/testing/voice-conformance/voice-conformance.sh index 782bd44a7f..7f486746a3 100755 --- a/testing/voice-conformance/voice-conformance.sh +++ b/testing/voice-conformance/voice-conformance.sh @@ -35,7 +35,7 @@ # # THE DECLARED LEGS (discovered from `legs/*.sh`, never enumerated here — the same rule # `verdict-covers-every-leg.py` applies to the workflow, one level in). Every leg drives the streaming -# plane's DOOR through `busbar-plane-streaming`'s dev-only `voice_conform` harness, BOTH ways — the +# plane's DOOR through the dev-only `voice_conform` harness (testing/voice-conformance/harness), BOTH ways — the # linked door and the `streaming_door` cdylib, dlopened through the loader — requires the two to answer # identically, and carries a RED arm per assertion (the leg's planted wrong answer must be refused; # `VOICE_CONFORM_RED=1` makes the planted answers the subject, and every leg must then go RED). Each diff --git a/xtask/src/gates/kind_isolation.rs b/xtask/src/gates/kind_isolation.rs index c5c3ca10a5..285dd52756 100644 --- a/xtask/src/gates/kind_isolation.rs +++ b/xtask/src/gates/kind_isolation.rs @@ -491,6 +491,17 @@ const OFF_TREE_MANIFESTS: &[(&str, &str)] = &[ never builds it, and it carries no kind and no plane or transport instance of its own. It \ is an input to the conformance leg, never a crate a rule here is written about.", ), + ( + "testing/voice-conformance/harness/Cargo.toml", + "the voice (streaming-plane) conformance HARNESS: a testkit binary the \ + `testing/voice-conformance/` legs shell out to, which loads the streaming plane's door both \ + ways through the loader (ARCHITECT Q6). It is a workspace member so \ + `testing/voice-conformance/lib/conform-bin.sh` can build it with \ + `-p voice-conformance-harness`, and it is not a crate of the product tree: it ships in no \ + artifact, `cargo build --bin busbar` never builds it, and it carries no kind. It lives \ + outside the plane crate so the plane's one user of the loader is its own both-ways witness. \ + It is an input to the conformance leg, never a crate a rule here is written about.", + ), ]; /// `qa/construction.toml`'s `[gate.plugin_kinds]` keys, each mapped onto the kind it names here. From 2b4c8c882f885f6f0958d5e07b9e47d789b95bcd Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:04:51 -0700 Subject: [PATCH 62/82] single-plane legs: the registration scan's non-vacuity floor is four contract-typed items (busbar-voice deleted; the streaming plane registers through its door), and ws is built over nothing in the shipped stack (transport-ws#13 states an empty composes_over, Q8) --- crates/busbar/src/root/tests/registry.rs | 4 +++- .../busbar/tests/plane_registration_is_contract_data.rs | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/busbar/src/root/tests/registry.rs b/crates/busbar/src/root/tests/registry.rs index f5793bd173..70f2e318cf 100644 --- a/crates/busbar/src/root/tests/registry.rs +++ b/crates/busbar/src/root/tests/registry.rs @@ -513,8 +513,10 @@ fn the_shipped_transport_stack_composes() { // The transport whose `new()` yields something that refuses every connection is the one that // must be built through `over`, and the rows say it was. `grpc` is a door that frames the // host's socket: it is built over nothing. + // `ws` is a framer door too (transport-ws#13, ARCHITECT Q8): it states an empty composes_over + // and takes the upgraded connection by adopt, so it is built over nothing. if ws_linked() { - assert_eq!(composed_over("ws"), Some("http")); + assert_eq!(composed_over("ws"), None); } assert_eq!(composed_over("grpc"), None); assert_eq!(composed_over("sse"), Some("http")); diff --git a/crates/busbar/tests/plane_registration_is_contract_data.rs b/crates/busbar/tests/plane_registration_is_contract_data.rs index 63394e7c49..8ff1b03b30 100644 --- a/crates/busbar/tests/plane_registration_is_contract_data.rs +++ b/crates/busbar/tests/plane_registration_is_contract_data.rs @@ -138,12 +138,13 @@ fn every_plane_registration_item_is_contract_data_naming_no_kernel_type() { ); // NON-VACUITY: the engine planes and the composition root's decisions plane register through the - // contract type. Fewer than five read means the scan read nothing, and zero offenders over zero - // items is not a pass. + // contract type. Fewer than four read means the scan read nothing, and zero offenders over zero + // items is not a pass. (Four, at today's number: FLIP-STREAMING deleted busbar-voice, and the + // streaming plane registers through its door's Statement, not a `PLANE_DECLARATION`.) let contract_typed = items.iter().filter(|i| i.ty == CONTRACT_TYPE).count(); assert!( - contract_typed >= 5, - "expected at least five `{CONTRACT_TYPE}` registration items under crates/, found \ + contract_typed >= 4, + "expected at least four `{CONTRACT_TYPE}` registration items under crates/, found \ {contract_typed}: {:?}", items.iter().map(|i| &i.at).collect::>() ); From 5897c63a83adb3fd61d9b38102bf4455accc4bff Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:09:22 -0700 Subject: [PATCH 63/82] streaming door validate reads the stage-3g deal: {streams:
} is unwrapped to its section (as the a2a/mcp doors read it), and a refusal names why The oracle's seven streams cells recorded nothing on the flipped build: every boot was refused 'config.yaml: invalid YAML: streams: Refused', because the boot's deal hands a declared-verb plane {verb: section} at validate while the door parsed the blob as the bare section (deny_unknown_fields). predev (busbar-voice) records all seven against golden/1.6.0-pre. The conformance script gains the dealt shape, Ready and with an unknown key Refused. --- crates/busbar-plane-streaming/src/door.rs | 24 ++++++++++++++++--- .../tests/conformance.rs | 11 +++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index a1e94293e0..520b441025 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -460,6 +460,24 @@ pub fn read_settings(settings: &[u8]) -> Result { serde_json::from_slice::(settings).map_err(|e| e.to_string()) } +/// THE SECTION A DEALT `validate` BLOB CARRIES. The boot's stage-3g deal hands a declared-verb +/// plugin `{:
}` (`config_validate::deal`), where `open` and `refresh` are handed the +/// section itself; the one verb this plane declares is [`SECTION`], so a blob that is exactly +/// `{streams: …}` is unwrapped to its section and any other blob is the section already. +#[must_use] +pub fn dealt_section(settings: &[u8]) -> std::borrow::Cow<'_, [u8]> { + match serde_json::from_slice::>(settings) { + Ok(map) if map.len() == 1 => match map.get(SECTION) { + Some(section) => serde_json::to_vec(section).map_or( + std::borrow::Cow::Borrowed(settings), + std::borrow::Cow::Owned, + ), + None => std::borrow::Cow::Borrowed(settings), + }, + _ => std::borrow::Cow::Borrowed(settings), + } +} + /// ONE READING of the public base URL: parse it and REPLACE the path wholesale, dropping query and /// fragment, so a public URL with a path of its own cannot produce a second spelling. #[must_use] @@ -636,10 +654,10 @@ macro_rules! slot { }; } -slot!(Validate, ValidateIn, OutHead, |_, input, _out| { - match read_settings(input.field(|i| &i.settings).bytes()) { +slot!(Validate, ValidateIn, OutHead, |_, input, out| { + match read_settings(&dealt_section(input.field(|i| &i.settings).bytes())) { Ok(_) => Outcome::Ready, - Err(_) => Outcome::Refused, + Err(words) => out.fail(busbar_contract::abi::sdk::life::Refusal::refused(words)), } }); diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index d0ce78812e..e414c92c26 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -189,6 +189,15 @@ fn script(p: &Plugin, public_url: Option<&'static str>) -> Vec { "validate ceiling {:?}", validate(p, br#"{"session_max_secs":1800}"#) )); + // The stage-3g deal's shape, `{streams:
}` (the boot's validate hands this). + t.push(format!( + "validate dealt {:?}", + validate(p, br#"{"streams":{"session":{"model":"m-cap"}}}"#) + )); + t.push(format!( + "validate dealt unknown key {:?}", + validate(p, br#"{"streams":{"nonsense_key":1}}"#) + )); let (outcome, snap) = open(p, 1, br#"{"session_max_secs":1800}"#, public_url); t.push(format!("open {outcome:?} {snap}")); @@ -282,6 +291,8 @@ fn expected(public: bool) -> Vec { "validate zero ceiling Refused".into(), "validate empty Ready".into(), "validate ceiling Ready".into(), + "validate dealt Ready".into(), + "validate dealt unknown key Refused".into(), format!("open Ready {}", snap(1)), format!("refresh Ready {}", snap(2)), "retire 1 Ready".into(), From e0eb1a88a90f09e66b9fe705de6949c83b6bd4d6 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:56:28 -0700 Subject: [PATCH 64/82] golden/1.6.0-pre: the two streams cells whose far-end request leads with its credential (streams|mint|client-secrets, streams|sdp|calls) re-recorded at the flipped head order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ARCHITECT 2026-10-04 18:10 (streaming PARK 09760681df): the far end's auth-first header order STANDS; the streams cells are golden/1.6.0-pre only (§12 pins statuses, not head order), so those two cells' head order is re-recorded after the flip and their status and body must match. They do: the flipped build's recording differs from the pre-fold one only in the upstream request's first two header lines (authorization before content-type), in headers and raw alike; status, answer and every byte of both bodies are identical. The other five streams cells and the 1.5.5 neutrality|routes|voice-shaped-404 cell are byte-identical as recorded. --- .../golden/1.6.0-pre/cells/streams__mint__client-secrets.json | 2 +- .../golden/1.6.0-pre/cells/streams__sdp__calls.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__mint__client-secrets.json b/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__mint__client-secrets.json index 1b962292c1..1850ea7489 100644 --- a/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__mint__client-secrets.json +++ b/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__mint__client-secrets.json @@ -1 +1 @@ -{"applied":["capture"],"body":{"json":{"steps":[{"body":{"json":{"expires_at_unix":1767225600,"value":"ek_oracle_0001"}},"header_order":["content-type","content-length","connection","date"],"headers":{"connection":"close","content-length":"55","content-type":"application/json"},"request":"POST /v1/realtime/client_secrets","status":200,"status_line":"HTTP/1.1 200 OK"}],"upstream":[{"answered":200,"body":"{\"expires_after\":{\"anchor\":\"created_at\",\"seconds\":600},\"session\":{\"model\":\"m-cap\"}}","headers":[["content-type","application/json"],["authorization","Bearer oracle-cred-streams-0002"],["openai-safety-identifier",""],["host","127.0.0.1:"],["content-length","83"]],"method":"POST","path":"/v1/realtime/client_secrets","proto":"h1","raw":"POST /v1/realtime/client_secrets HTTP/1.1\r\ncontent-type: application/json\r\nauthorization: Bearer oracle-cred-streams-0002\r\nopenai-safety-identifier: \r\nhost: 127.0.0.1:\r\ncontent-length: 83\r\n\r\n{\"expires_after\":{\"anchor\":\"created_at\",\"seconds\":600},\"session\":{\"model\":\"m-cap\"}}","role":"fixed"}]}},"effects":{},"headers":{},"status":0} +{"applied":["capture"],"body":{"json":{"steps":[{"body":{"json":{"expires_at_unix":1767225600,"value":"ek_oracle_0001"}},"header_order":["content-type","content-length","connection","date"],"headers":{"connection":"close","content-length":"55","content-type":"application/json"},"request":"POST /v1/realtime/client_secrets","status":200,"status_line":"HTTP/1.1 200 OK"}],"upstream":[{"answered":200,"body":"{\"expires_after\":{\"anchor\":\"created_at\",\"seconds\":600},\"session\":{\"model\":\"m-cap\"}}","headers":[["authorization","Bearer oracle-cred-streams-0002"],["content-type","application/json"],["openai-safety-identifier",""],["host","127.0.0.1:"],["content-length","83"]],"method":"POST","path":"/v1/realtime/client_secrets","proto":"h1","raw":"POST /v1/realtime/client_secrets HTTP/1.1\r\nauthorization: Bearer oracle-cred-streams-0002\r\ncontent-type: application/json\r\nopenai-safety-identifier: \r\nhost: 127.0.0.1:\r\ncontent-length: 83\r\n\r\n{\"expires_after\":{\"anchor\":\"created_at\",\"seconds\":600},\"session\":{\"model\":\"m-cap\"}}","role":"fixed"}]}},"effects":{},"headers":{},"status":0} diff --git a/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__sdp__calls.json b/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__sdp__calls.json index 252c307d4b..6d16387650 100644 --- a/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__sdp__calls.json +++ b/testing/shadow-oracle/golden/1.6.0-pre/cells/streams__sdp__calls.json @@ -1 +1 @@ -{"applied":["capture"],"body":{"json":{"steps":[{"body":{"text":"v=0\r\no=- 2 2 IN IP4 127.0.0.1\r\ns=oracle-answer\r\nt=0 0\r\n"},"header_order":["content-type","content-length","connection","date"],"headers":{"connection":"close","content-length":"55","content-type":"application/sdp"},"request":"POST /v1/realtime/calls","status":201,"status_line":"HTTP/1.1 201 Created"}],"upstream":[{"answered":201,"body":"v=0\r\no=- 1 1 IN IP4 127.0.0.1\r\ns=oracle-offer\r\nt=0 0\r\n","headers":[["content-type","application/sdp"],["authorization","Bearer oracle-cred-streams-0006"],["host","127.0.0.1:"],["content-length","54"]],"method":"POST","path":"/v1/realtime/calls","proto":"h1","raw":"POST /v1/realtime/calls HTTP/1.1\r\ncontent-type: application/sdp\r\nauthorization: Bearer oracle-cred-streams-0006\r\nhost: 127.0.0.1:\r\ncontent-length: 54\r\n\r\nv=0\r\no=- 1 1 IN IP4 127.0.0.1\r\ns=oracle-offer\r\nt=0 0\r\n","role":"fixed"}]}},"effects":{},"headers":{},"status":0} +{"applied":["capture"],"body":{"json":{"steps":[{"body":{"text":"v=0\r\no=- 2 2 IN IP4 127.0.0.1\r\ns=oracle-answer\r\nt=0 0\r\n"},"header_order":["content-type","content-length","connection","date"],"headers":{"connection":"close","content-length":"55","content-type":"application/sdp"},"request":"POST /v1/realtime/calls","status":201,"status_line":"HTTP/1.1 201 Created"}],"upstream":[{"answered":201,"body":"v=0\r\no=- 1 1 IN IP4 127.0.0.1\r\ns=oracle-offer\r\nt=0 0\r\n","headers":[["authorization","Bearer oracle-cred-streams-0006"],["content-type","application/sdp"],["host","127.0.0.1:"],["content-length","54"]],"method":"POST","path":"/v1/realtime/calls","proto":"h1","raw":"POST /v1/realtime/calls HTTP/1.1\r\nauthorization: Bearer oracle-cred-streams-0006\r\ncontent-type: application/sdp\r\nhost: 127.0.0.1:\r\ncontent-length: 54\r\n\r\nv=0\r\no=- 1 1 IN IP4 127.0.0.1\r\ns=oracle-offer\r\nt=0 0\r\n","role":"fixed"}]}},"effects":{},"headers":{},"status":0} From 5a7f77f61b30c2db21a01252bd009e8c341c6648 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:31:47 -0700 Subject: [PATCH 65/82] RELEASE_REF and the oracle pin move to busbar-release dev 1869c0032e (the merge of #168 into dev, which carries #166 5b89bdf and #157 0ede76aea9): the pin is a dev commit that contains every earlier pin --- .github/workflows/promote.yml | 2 +- testing/shadow-oracle/oracle-rust.pin | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index a5b2515d79..7913a65f7a 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -12,7 +12,7 @@ on: [pull_request, merge_group] concurrency: { group: "promote-${{ github.event.pull_request.number || github.event.merge_group.head_ref }}", cancel-in-progress: "${{ github.event_name == 'pull_request' && github.base_ref != 'dev' }}" } permissions: { contents: read } env: - RELEASE_REF: 30d072960aa13af8a2bb59fc87de4251c2238ba1 + RELEASE_REF: 1869c0032ecf2cdad8b1153ccf25311cb6baad75 SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} TO: ${{ github.base_ref || github.event.merge_group.base_ref }} BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} diff --git a/testing/shadow-oracle/oracle-rust.pin b/testing/shadow-oracle/oracle-rust.pin index e9c000f7cd..b206cef8ab 100644 --- a/testing/shadow-oracle/oracle-rust.pin +++ b/testing/shadow-oracle/oracle-rust.pin @@ -244,8 +244,14 @@ # RELEASE_REF moves to the same sha in the same commit. Feature branch head; re-pin to the merge commit # when it lands. # +# 2026-10-06 (ARCHITECT RELEASE_REF rule: the pin is always a busbar-release DEV commit that contains +# every earlier pin): moved 30d072960aa1 -> 1869c0032ecf, the merge of busbar-release #168 into dev +# (#168 = the voice-runtime row strike above, merged with dev 5b89bdf67326 = #166). It contains +# 4a7a7bba2cef, 0ede76aea9 (#157) and 5b89bdf67326 (#166). promote.yml RELEASE_REF moves to the same +# sha in the same commit. +# # To move the pin: set release_ref to the new busbar-release commit and re-run. release_repo=https://github.com/GetBusbar/busbar-release.git -release_ref=30d072960aa13af8a2bb59fc87de4251c2238ba1 +release_ref=1869c0032ecf2cdad8b1153ccf25311cb6baad75 crate=busbar-release-oracle bin=busbar-oracle From 9afb185c93099aa6c149bfb7de0a74acbe7ec162 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:08:44 -0700 Subject: [PATCH 66/82] FLIP-STREAMING selftests: the include! dual-compile plant names a plane crate the tree still holds (busbar-a2a; busbar-voice is deleted), and the door-section orphan case takes the tree's map carrier away, since a door owns the section it declares (DECL-FOLD) --- xtask/src/gates/config_schema/selftest.rs | 25 +++++++++++++---------- xtask/src/gates/plane_purity/mod.rs | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/xtask/src/gates/config_schema/selftest.rs b/xtask/src/gates/config_schema/selftest.rs index 55038a266d..4585038f01 100644 --- a/xtask/src/gates/config_schema/selftest.rs +++ b/xtask/src/gates/config_schema/selftest.rs @@ -451,16 +451,28 @@ pub fn run<'a>(gate: &'a dyn Gate, cx: &'a Ctx) -> Report<'a> { )); // A DOOR plane's Statement declares its section in a `Section` row flagged SECTION_DECLARING - // (no PlaneDecl), and the reader reads it there: a door row naming a section no struct carries is - // an orphan exactly as a PlaneDecl's is. A row that only CONSUMES a section declares nothing. + // (no PlaneDecl), and the reader reads it there. A door OWNS the section it declares (the + // kernel folds it into the pre-pass's map carrier, DECL-FOLD), so, like an owned PlaneDecl + // section below, it is an orphan when no carrier holds it: the case takes the tree's map carrier + // away. A row that only CONSUMES a section declares nothing. let door_row = |flags: &str| { format!( "const ZZ_SECTION: &str = \"zz_door_no_such_carrier\";\nconst SECTIONS: &[Section] = \ &[Section {{\n name: abi_str(ZZ_SECTION),\n flags: {flags},\n _reserved: 0,\n}}];\n" ) }; + let prepass = "crates/busbar-kernel/src/config/prepass.rs"; + let no_tree_map_carrier = |ov: &mut Overlay| { + ov.set( + prepass, + cx.read(prepass) + .unwrap_or_default() + .replace("= Declared::Any;", "= Declared::ByField;"), + ); + }; let mut ov = Overlay::new(); ov.set(plane_fixture, door_row("SECTION_DECLARING")); + no_tree_map_carrier(&mut ov); report.push(prove_rows_red( cx, gate, @@ -512,15 +524,6 @@ pub fn run<'a>(gate: &'a dyn Gate, cx: &'a Ctx) -> Report<'a> { // with no map carrier the owned section is an orphan (RED); with a fixture map carrier it is // grammar (GREEN). A plane that does NOT own its declaring section stays an orphan either way // (the "no struct carries" case above). - let prepass = "crates/busbar-kernel/src/config/prepass.rs"; - let no_tree_map_carrier = |ov: &mut Overlay| { - ov.set( - prepass, - cx.read(prepass) - .unwrap_or_default() - .replace("= Declared::Any;", "= Declared::ByField;"), - ); - }; let mut ov = Overlay::new(); ov.set(plane_fixture, declaration("\"zz_owned\"", "\"zz_owned\"")); no_tree_map_carrier(&mut ov); diff --git a/xtask/src/gates/plane_purity/mod.rs b/xtask/src/gates/plane_purity/mod.rs index dac8f4a90a..c738769894 100644 --- a/xtask/src/gates/plane_purity/mod.rs +++ b/xtask/src/gates/plane_purity/mod.rs @@ -485,7 +485,7 @@ impl Gate for PlanePurityGate { "an include! dual-compile, carrying a pragma that must not launder it", &[ROW_PATH_INCLUDE], &plant_at, - "include!(\"../../../busbar-voice/src/witness.rs\"); // plane-purity: frozen-wire abuse\n", + "include!(\"../../../busbar-a2a/src/witness.rs\"); // plane-purity: frozen-wire abuse\n", &[ROW_PATH_INCLUDE, "planted_plane_purity.rs:1"], )); From a9eebf94e8568795d4bf4e3b330979b5836727be Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 7 Oct 2026 02:06:36 -0700 Subject: [PATCH 67/82] root leg: the streaming door's root cells live in root/tests/serve_tests.rs, predev's name for the serve fold's door-cells file (#497 renamed serve_door.rs); the capability-equality root column, the teller-steps root legs and the leg-file selftest name it --- crates/busbar/tests/capability_equality.rs | 10 +++++++--- qa/capability-equality.json | 10 +++++----- qa/teller-steps.json | 18 +++++++++--------- xtask/src/gates/teller_steps.rs | 6 +++--- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/busbar/tests/capability_equality.rs b/crates/busbar/tests/capability_equality.rs index 337aaa69a9..ec8f4c41b2 100644 --- a/crates/busbar/tests/capability_equality.rs +++ b/crates/busbar/tests/capability_equality.rs @@ -982,7 +982,7 @@ fn the_root_leg_matrix_runs_once_per_leg() { /// when a module the root declares (`pub mod ;`, `crates/busbar/src/root/.rs`) declares /// the file as one of its own test modules through `#[path = "tests/.rs"]` (ARCHITECT 2026-10-05 /// Q1: a door plane's leg lives in the serving module's test file, e.g. `serve.rs`'s -/// `tests/serve_door.rs`). Anything else is refused, naming the file. +/// `tests/serve_tests.rs`). Anything else is refused, naming the file. fn leg_module_declared(root: &Path, mod_rs: &str, file: &str) -> Result<(), String> { let path = Path::new(file); let module = path @@ -1016,7 +1016,7 @@ fn leg_module_declared(root: &Path, mod_rs: &str, file: &str) -> Result<(), Stri } /// THE LEG-FILE RULE, BOTH WAYS: a test file a declared root module carries through `#[path]` is a -/// leg file (the streaming door's `serve.rs` -> `tests/serve_door.rs`); a file no declared module +/// leg file (the streaming door's `serve.rs` -> `tests/serve_tests.rs`); a file no declared module /// carries is refused. RED: the rule read `pub mod ;` alone and refused the first. #[test] fn selftest_a_leg_file_is_a_module_or_a_declared_modules_path_test_file() { @@ -1024,7 +1024,11 @@ fn selftest_a_leg_file_is_a_module_or_a_declared_modules_path_test_file() { let mod_rs = std::fs::read_to_string(root.join("crates/busbar/src/root/mod.rs")) .expect("the composition root's mod.rs"); assert_eq!( - leg_module_declared(&root, &mod_rs, "crates/busbar/src/root/tests/serve_door.rs"), + leg_module_declared( + &root, + &mod_rs, + "crates/busbar/src/root/tests/serve_tests.rs" + ), Ok(()) ); assert_eq!( diff --git a/qa/capability-equality.json b/qa/capability-equality.json index b1476dac91..35752b1ad9 100644 --- a/qa/capability-equality.json +++ b/qa/capability-equality.json @@ -100,12 +100,12 @@ "note": "the A2A plane's SERVED root leg: the inbound call driven through the real router and the kernel-loop rider `gauntlet_install::install()` registers for the plane's key (DECISIONS #28), each root cell running the plane's own served witness (`busbar_a2a::testkit::SERVED`) and requiring its `drive` to have run inside the loop" }, "root-voice": { - "file": "crates/busbar/src/root/tests/serve_door.rs", + "file": "crates/busbar/src/root/tests/serve_tests.rs", "columns": [ "streaming-client", "streaming-server" ], - "note": "the streaming plane's SERVED root leg since FLIP-STREAMING: the streaming door, linked and bound through the loader's one load, composed as production seals it (its egress over the connector, its DIRECT route to the catalog model streams.session.model names) and mounted on the data router, driven end to end by root/tests/serve_door.rs (carried by serve.rs through #[path]); the kernel-loop session rider no longer carries this plane" + "note": "the streaming plane's SERVED root leg since FLIP-STREAMING: the streaming door, linked and bound through the loader's one load, composed as production seals it (its egress over the connector, its DIRECT route to the catalog model streams.session.model names) and mounted on the data router, driven end to end by root/tests/serve_tests.rs (carried by serve.rs through #[path]); the kernel-loop session rider no longer carries this plane" }, "root-admin": { "file": "crates/busbar/src/root/units_admin/tests/units_admin.rs", @@ -1105,7 +1105,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential" + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential" } }, { @@ -1115,7 +1115,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_session_door_before_any_dial" + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_spent_key_is_refused_at_the_session_door_before_any_dial" } }, { @@ -1205,7 +1205,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential" + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential" } }, { diff --git a/qa/teller-steps.json b/qa/teller-steps.json index f756eae8e1..e04d1baad8 100644 --- a/qa/teller-steps.json +++ b/qa/teller-steps.json @@ -91,7 +91,7 @@ }, "root-voice": { "plane": "voice", - "file": "crates/busbar/src/root/tests/serve_door.rs", + "file": "crates/busbar/src/root/tests/serve_tests.rs", "feature": "plane-streaming" }, "root-admin": { @@ -444,7 +444,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the browser's mint is one unit the door carries end to end, from the data router's claim to the provider's answer relayed to the caller" } }, @@ -465,7 +465,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint is admitted for the key its token resolves, and the same request with no credential is refused before any dial or charge" } }, @@ -476,7 +476,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint reaches the provider through the connector at the destination its guard judged, the catalog model's provider on loopback" } }, @@ -497,7 +497,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_spent_key_is_refused_at_the_session_door_before_any_dial", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_spent_key_is_refused_at_the_session_door_before_any_dial", "note": "on the streaming door's served leg: a key whose group budget is spent is refused 429 at admission, the provider never dialled and nothing charged" } }, @@ -508,7 +508,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint is routed DIRECT to the catalog model streams.session.model names, its locked session carrying that model to the provider" } }, @@ -519,7 +519,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the served mint is one request on the governance ledger of the key that presented it" } }, @@ -530,7 +530,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the served mint writes exactly one line on the node's book, the unit's one record" } }, @@ -541,7 +541,7 @@ "root": { "state": "proven", "leg": "root-voice", - "test": "crates/busbar/src/root/tests/serve_door.rs::a_session_door_mint_is_served_under_the_providers_credential", + "test": "crates/busbar/src/root/tests/serve_tests.rs::a_session_door_mint_is_served_under_the_providers_credential", "note": "on the streaming door's served leg: the mint's money facts and node facts close once at its end, nothing left open on either" } } diff --git a/xtask/src/gates/teller_steps.rs b/xtask/src/gates/teller_steps.rs index 2700a16c55..09e278cca3 100644 --- a/xtask/src/gates/teller_steps.rs +++ b/xtask/src/gates/teller_steps.rs @@ -945,8 +945,8 @@ fn run_root_legs_gating(cx: &Ctx) -> i32 { } /// The module a lifted-out test file is MOUNTED as when its declaring module names it otherwise -/// than the `::tests` convention: `crates/busbar/src/root/tests/serve_door.rs` reached by -/// `#[path = "tests/serve_door.rs"] mod door_tests;` in `root/serve.rs` is `root::serve::door_tests`. +/// than the `::tests` convention: `crates/busbar/src/root/tests/serve_tests.rs` reached by +/// `#[path = "tests/serve_tests.rs"] mod door_tests;` in `root/serve.rs` is `root::serve::door_tests`. /// `None` when no sibling declares it under another name (the convention then applies). fn path_mounted(cx: &Ctx, file: &str) -> Option { let (dir, stem) = file.strip_suffix(".rs")?.rsplit_once("/tests/")?; @@ -1065,7 +1065,7 @@ fn run_root_legs(cx: &Ctx) -> i32 { let mut unknown: Vec = Vec::new(); let mut per_leg: BTreeMap = BTreeMap::new(); for (leg, file, func) in &cells { - // A test body mounted under ANOTHER module name (`#[path = "tests/serve_door.rs"] mod + // A test body mounted under ANOTHER module name (`#[path = "tests/serve_tests.rs"] mod // door_tests;` in `serve.rs`) is that module: libtest spells it `root::serve::door_tests::f`. if let Some(module) = path_mounted(cx, file) { let path = format!("{module}::{func}"); From 3be9ad80f3a4b3f9794c99e0ece6b33fc3797498 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:17:42 -0700 Subject: [PATCH 68/82] pre-flight: fmt / lock / abi header --- xtask/src/gates/dep_wall.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xtask/src/gates/dep_wall.rs b/xtask/src/gates/dep_wall.rs index c70a1d9637..a047336f70 100644 --- a/xtask/src/gates/dep_wall.rs +++ b/xtask/src/gates/dep_wall.rs @@ -58,10 +58,7 @@ const BANNED_PATHS: &[&str] = &["std::net", "tokio::net"]; /// `busbar-mcp` is not here either (P3 DEL-MCP, ARCHITECT 2026-10-05): the engine is deleted, and the /// mcp plane is its door crate `busbar-plane-mcp`, a `gate.plugin_kinds.plane` row this wall already /// scans. Its eight pins in the ledger drained with it and are struck there. -const LEGACY: &[&str] = &[ - "crates/busbar-llm", - "crates/busbar-a2a", -]; +const LEGACY: &[&str] = &["crates/busbar-llm", "crates/busbar-a2a"]; /// The external source a plugin repository resolves from. const EXTERNAL_SOURCE: &str = "git+https://github.com/GetBusbar/"; From 51e1dae281f5fb8fc1d7397c1004098da608887a Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:23:21 -0700 Subject: [PATCH 69/82] streaming door: state no stream ceiling (stream_ceiling_secs: 0) in its PlaneTail; reachability selftest: the on-disk-spelling case declares plane-streaming, streaming's on-disk spelling since the flip --- crates/busbar-plane-streaming/src/door.rs | 4 ++++ xtask/src/gates/reachability.rs | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 520b441025..28d7c4b19f 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -321,6 +321,10 @@ pub const TAIL: &PlaneTail = &PlaneTail { admin_routes: ptr::null(), admin_routes_len: 0, admin_openapi: Blob::ABSENT, + // No stream ceiling (ARCHITECT STREAM-CEILING, 2026-10-07): a live session runs as long as its + // caller keeps it open, so the streaming plane states none; a streamed answer's send runs under + // the deployment's ceiling. + stream_ceiling_secs: 0, }; /// The inbound auth style of a door read without a credential (no door of the plane is, today). diff --git a/xtask/src/gates/reachability.rs b/xtask/src/gates/reachability.rs index c4bf7a79cd..4d655237bf 100644 --- a/xtask/src/gates/reachability.rs +++ b/xtask/src/gates/reachability.rs @@ -3091,7 +3091,8 @@ impl Gate for ReachabilityGate { }), "the plane-crate population is empty", )); - // THE ON-DISK SPELLING MAPS: a plane crate declaring `voice` is the streaming plane's. + // THE ON-DISK SPELLING MAPS: a plane crate declaring `plane-streaming` is the streaming + // plane's. report.push(green_over( self, cx, @@ -3106,7 +3107,7 @@ impl Gate for ReachabilityGate { ov.set( "crates/busbar-plane-voice/src/lib.rs", "pub struct VoicePlane;\n\nimpl PlaneMeta for VoicePlane {\n \ - const KEY: &'static str = \"voice\";\n}\n", + const KEY: &'static str = \"plane-streaming\";\n}\n", ); ov }), From 08fe75a40f1a700bcca6e7b8f374ca16bac539d5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:49:41 -0700 Subject: [PATCH 70/82] FLIP-STREAMING CI fixes: merge seams with predev's census, door-section reader and P-item pin - census: deleted_plane_crates is the pure (base, now, is_deleted) helper its tests call; the call site reads the base/now plane_crates lists and the ledger check (predev's Ctx-taking version broke census_moveout_tests: E0061). - session_pump_tests: predev's P-item voice tool-args pin called the removed executor seam (serves_all, a 4-arg on_server_frame). Under LAW 11 / Q98 the plane runs no tool, so the pin now holds the streamed fragments reaching the CALLER whole, in order, closed and counted once, with nothing written upstream. - plane_host_universal_purity: busbar-voice was the second caller of the breaker_* four, gate_attached, gate_decide, tap_attached and transform_over; a2a is now their sole caller, so each gets an allowlist row with its reason. - config-schema: the merge took predev's literal-only door-section reader, so neither the streaming door (abi_str(SECTION)) nor the decisions door (abi_str(tail::SECTION_DECLARING)) was read and the snapshot's streams:/decisions: rows drifted. Door rows now resolve a constant path as a PlaneDecl's names do, skip CONSUMED rows, and fold the declaring section into owned (DECL-FOLD), as this branch's selftest door-row cases expect. - reachability selftest: the mcp registration case names the linked crate busbar-plane-mcp. - kind-isolation selftest: the missing-step case removes busbar-plane-a2a/src/plane.rs, the one legacy-face-only plane left (the streaming plane.rs is deleted). Proved on Latchkey: xtask census::moveout_tests (9 pass), busbar-plane-streaming session_pump (18 pass), busbar --test plane_host_universal_purity (3 pass), selftest reachability, selftest kind-isolation, selftest kind-isolation-ship, gate config-schema (snapshot-drift PASS). The config-schema selftest's remaining reds on the dev runner are all the absent v1.5.3 baseline tag (shallow checkout), none from the door-row cases. Not addressed: structure-lint:plane-dup:unledgered 22 -> 41. The streaming door crate is now scanned as the streaming plane's home in place of busbar-voice, and its claims/config/driven/meta modules and symbols collide with the decisions door crate's. Clearing them needs a unification or signed ledger claims, an owner decision. --- .../src/tests/session_pump_tests.rs | 58 +++++++++++++------ .../tests/fixtures/single_plane_allowlist.txt | 8 +++ xtask/src/gates/config_schema/declared.rs | 31 ++++++---- xtask/src/gates/construction/census.rs | 34 +++++------ xtask/src/gates/kind_isolation.rs | 6 +- xtask/src/gates/reachability.rs | 2 +- 6 files changed, 90 insertions(+), 49 deletions(-) diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index 9f57c382b3..0f583d46d8 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -163,47 +163,71 @@ fn a_tool_call_is_relayed_to_the_caller_as_is_and_the_gateway_answers_nothing() /// P-ITEM: VOICE TOOL-ARGS (spec DONE item 2, "All P-item behaviours match 1.5.5"; the drive log's /// P5, commit 470351a480: "streamed tool-call arguments are discarded"). The model streams a call's -/// arguments as several partial-JSON fragments; they reach the executor CONCATENATED, as one call, -/// run once, on the call's close and not before. +/// arguments as several partial-JSON fragments; none of them is lost, and they reach the caller +/// whole, in order, as one call, counted once. /// /// Voice is new in 1.6.0; the 1.5.5 behaviour it matches is the llm surface's own streamed tool /// call (owner correction 2026-09-28): 1.5.5 accumulated every `InputJsonDelta` fragment of an open /// tool block and emitted the call ONCE on its block stop with the fully reassembled arguments, /// because parsing each fragment alone lost the arguments and split one call into many (v1.5.5 -/// `crates/busbar/src/proto/gemini/writer.rs:734-780`). +/// `crates/busbar/src/proto/gemini/writer.rs:734-780`). The streaming plane holds no tool executor +/// (LAW 11, QUESTIONS Q98): the caller runs the call, so the pin is on what the caller receives, +/// and the gateway answers nothing upstream on any fragment or on the close. #[test] -fn p_item_voice_tool_args_streamed_fragments_reach_the_executor_whole_and_once() { +fn p_item_voice_tool_args_streamed_fragments_reach_the_caller_whole_and_once() { let mut p = pump(); let mut sink = Turns::default(); - let mut runs = Vec::new(); + let mut down = Vec::new(); let open = wire(serde_json::json!({"type":"response.output_item.added", "item":{"type":"function_call","call_id":"cw","name":"weather"}})); - let (_, r) = p.on_server_frame(open, 0, &mut sink, &serves_all); - assert!(r.is_empty(), "an announced call is not run"); + let out = p.on_server_frame(open, 0, &mut sink); + assert!(out.upstream.is_empty(), "an announced call is not answered"); + down.extend(out.downlink); for fragment in ["{\"lo", "c\":\"S", "F\"}"] { let delta = wire( serde_json::json!({"type":"response.function_call_arguments.delta", "call_id":"cw","delta":fragment}), ); - let (_, r) = p.on_server_frame(delta, 0, &mut sink, &serves_all); + let out = p.on_server_frame(delta, 0, &mut sink); assert!( - r.is_empty(), - "a call is not run on a fragment of its arguments" + out.upstream.is_empty(), + "a fragment of a call's arguments is not answered" ); + down.extend(out.downlink); } let done = wire( serde_json::json!({"type":"response.function_call_arguments.done", "call_id":"cw"}), ); - let (_, r) = p.on_server_frame(done, 0, &mut sink, &serves_all); - runs.extend(r); - assert_eq!(runs.len(), 1, "one call, run once on its close"); - assert_eq!(runs[0].name, "weather"); - assert_eq!(runs[0].call_id, "cw"); + let out = p.on_server_frame(done, 0, &mut sink); + assert!(out.upstream.is_empty(), "a closed call is not answered"); + down.extend(out.downlink); + let frames: Vec = down + .iter() + .map(|w| serde_json::from_slice(&w.0).expect("a downlink frame is json")) + .collect(); + let args: String = frames + .iter() + .filter(|f| f["type"] == "response.function_call_arguments.delta") + .map(|f| f["delta"].as_str().expect("a delta carries its fragment")) + .collect(); assert_eq!( - runs[0].args, b"{\"loc\":\"SF\"}", - "the fragments reach the executor whole, in order" + args, "{\"loc\":\"SF\"}", + "the fragments reach the caller whole, in order: {}", + texts(&down) ); + assert_eq!( + frames + .iter() + .filter(|f| f["type"] == "response.function_call_arguments.done") + .count(), + 1, + "one call, closed once: {}", + texts(&down) + ); + p.settle_open_turn(&mut sink); + assert_eq!(sink.closed.len(), 1); + assert_eq!(sink.closed[0].1.tool_calls, 1, "one call, counted once"); } /// A table that records what the pump asked of it. diff --git a/crates/busbar/tests/fixtures/single_plane_allowlist.txt b/crates/busbar/tests/fixtures/single_plane_allowlist.txt index 4f07b0765f..4ad869c326 100644 --- a/crates/busbar/tests/fixtures/single_plane_allowlist.txt +++ b/crates/busbar/tests/fixtures/single_plane_allowlist.txt @@ -6,6 +6,10 @@ admission_door llm Neutral single-consumer capability: generic signature, no for any_content_hook llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. audit_emit a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-MCP (the MCP plane is served through its door and calls no EngineHost method). Re-review if a second plane consumes it. audit_record llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. +breaker_admit a2a Neutral single-consumer capability: generic signature (scope, pool, lane), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. +breaker_record_signal a2a Neutral single-consumer capability: generic signature (pool, lane, signal), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. +breaker_record_success a2a Neutral single-consumer capability: generic signature (pool, lane), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. +breaker_retry_after_secs a2a Neutral single-consumer capability: generic signature (pool, lane), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. breaker_settle a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-MCP (the MCP plane is served through its door and calls no EngineHost method). Re-review if a second plane consumes it. budget_state llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. caller_in_hook_groups llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. @@ -16,6 +20,8 @@ default_probe_timeout_secs llm Neutral single-consumer capability: generic signa destination_guard llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. finish_admitted llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. finish_rejected llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. +gate_attached a2a Neutral single-consumer capability: the kernel-sealed hook gate's presence check (plane key, container), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. +gate_decide a2a Neutral single-consumer capability: the kernel-sealed hook gate (the hook sees the content inside the kernel), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. global_gates llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. govern_admit_reason a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-MCP (the MCP plane is served through its door and calls no EngineHost method). Re-review if a second plane consumes it. governance llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. @@ -40,6 +46,7 @@ rewrite_hooks llm Neutral single-consumer capability: generic signature, no fore secret_resolver a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane (card signing / pool membership / request-finish). settle_residual llm Neutral single-consumer capability: the kernel spells the usage.residual row and the plane hands over only a count map (#334, MONEY LAW); sole current caller is the LLM plane's settle step. Re-review if a second plane consumes it. subkey_sign a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane (card signing / pool membership / request-finish). +tap_attached a2a Neutral single-consumer capability: the kernel-sealed transform tap's presence check (plane key, container), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. tap_hooks llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. tap_hooks_candidate llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. tap_hooks_response llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. @@ -49,4 +56,5 @@ telemetry_failover llm Neutral single-consumer capability: generic signature, no telemetry_translation llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. telemetry_upstream_attempt llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. telemetry_upstream_failure llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. +transform_over a2a Neutral single-consumer capability: the kernel-sealed transform pass (the hook rewrites the content inside the kernel), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. verify_token_test llm test-only (cfg test/test-support) raw-token verifier for the LLM routing-policy test seam; never linked in a production binary. diff --git a/xtask/src/gates/config_schema/declared.rs b/xtask/src/gates/config_schema/declared.rs index 4300e461e2..220b45a84f 100644 --- a/xtask/src/gates/config_schema/declared.rs +++ b/xtask/src/gates/config_schema/declared.rs @@ -106,13 +106,15 @@ fn facts(path: &str, raw: &str) -> Facts { } } } - // A PLANE DOOR's Statement declares its sections as `Section { name: abi_str("…"), flags: … }` + // A PLANE DOOR's Statement declares its sections as `Section { name: abi_str(…), flags: … }` // rows: the one flagged `SECTION_DECLARING` is its declaring section, and a row flagged neither // declaring nor consumed is a section it owns beside it (the loader's own reading, // `registration().owns`). A door-only plane has no `PLANE_DECLARATION`; this is its declaration. - // Read only when EVERY row names its section as a string LITERAL: a door that spells them - // through other items still has its `PLANE_DECLARATION` row, which this census reads instead; - // a door-only plane states literals (its own test holds them equal to its grammar's constants). + // A door owns the grammar of the section it declares (the kernel folds it into the declared- + // section map carrier, DECL-FOLD), exactly as a PlaneDecl whose `owned_config_sections` names its + // `config_section`. A row's name is a string literal or a path to a string constant, resolved + // as a PlaneDecl's is; a row that only CONSUMES a section declares nothing and is not read. A + // door with a declaring or owned row spelled any other way is not read here. let mut from = 0; while let Some(at) = text[from..].find(DOOR_SECTIONS) { let start = from + at + DOOR_SECTIONS.len(); @@ -125,26 +127,35 @@ fn facts(path: &str, raw: &str) -> Facts { }; let (mut declaring, mut owned) = (None, Vec::new()); let mut rows = &text[open..close]; - let mut literal = true; + let mut readable = true; while let Some(row) = rows.find("Section {") { let Some(body) = block(rows, row) else { break }; rows = &rows[row + body.len() + 2..]; + let flags = field(body, "flags").map(str::trim).unwrap_or_default(); + if flags.contains("SECTION_CONSUMED") { + continue; + } let name = body .find("abi_str(") .map(|i| &body[i + "abi_str(".len()..]) .and_then(|n| n.find(')').map(|e| n[..e].trim().to_string())); - let flags = field(body, "flags").map(str::trim).unwrap_or_default(); - let Some(name) = name.filter(|n| n.starts_with('"') && n.ends_with('"')) else { - literal = false; + let Some(name) = name.filter(|n| { + (n.len() >= 2 && n.starts_with('"') && n.ends_with('"')) + || (!n.is_empty() + && n.chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':')) + }) else { + readable = false; continue; }; if flags.contains("SECTION_DECLARING") { declaring = Some(name); - } else if !flags.contains("SECTION_CONSUMED") { + } else { owned.push(name); } } - if let (true, Some(declaring)) = (literal, declaring) { + if let (true, Some(declaring)) = (readable, declaring) { + owned.insert(0, declaring.clone()); out.decls.push(Ok((declaring, owned))); } } diff --git a/xtask/src/gates/construction/census.rs b/xtask/src/gates/construction/census.rs index 9a71ee138b..90241ac7fb 100644 --- a/xtask/src/gates/construction/census.rs +++ b/xtask/src/gates/construction/census.rs @@ -175,7 +175,11 @@ pub fn ceiling_census(cx: &Ctx, cfg: &Cfg) -> Vec { cx.abs(d).exists() })); let mut moved = moved_out_by_kind(cx, cfg, &doc, &base.sha, &now_deleted, &renamed); - if let Some(n) = deleted_plane_crates(cx, cfg, &doc, &now_deleted) { + let base_planes = doc.table_or_empty("gate").list_of("plane_crates"); + let now_planes = cfg.plane_crates().unwrap_or_default(); + if let Some(n) = deleted_plane_crates(&base_planes, &now_planes, |dir| { + now_deleted.contains(dir) && !cx.abs(dir).exists() + }) { moved.insert("plane_crates".to_string(), n as i64); } bad.extend(lowered_floors(&cfg.doc, &doc, base.short(), &moved)); @@ -308,29 +312,21 @@ fn moved_out_by_kind( /// THE SAME RULED WAY DOWN FOR `gate.plane_crates` (P3 DEL-MCP, ARCHITECT 2026-10-05: "delete /// busbar-mcp, add `crates/busbar-mcp` to [gate.census.deleted]"). The legacy-engine list may lose /// an entry only when its crate was DELETED under the ledger: the entries the base listed and this -/// tree does not, `Some(n)` when EVERY one names a `crates/` that `[gate.census.deleted]` -/// lists and that is absent from this tree ([`excused_out`], no move-out arm — a legacy engine is -/// retired, never pinned back). `None` when nothing left the list or any one left any other way, so -/// the `plane_crates` floor drop stays RED. +/// tree does not, `Some(n)` when EVERY one names a `crates/` that is a ruled deletion +/// (`is_deleted`: `[gate.census.deleted]` lists it and it is absent from this tree; [`excused_out`], +/// no move-out arm — a legacy engine is retired, never pinned back). `None` when nothing left the +/// list or any one left any other way, so the `plane_crates` floor drop stays RED. fn deleted_plane_crates( - cx: &Ctx, - cfg: &Cfg, - base_doc: &Document, - deleted: &BTreeSet, + base: &[String], + now: &[String], + is_deleted: impl Fn(&str) -> bool, ) -> Option { - let now = cfg.plane_crates().unwrap_or_default(); - let gone: Vec = base_doc - .table_or_empty("gate") - .list_of("plane_crates") - .into_iter() + let gone: Vec = base + .iter() .filter(|c| !now.contains(c)) .map(|c| format!("crates/{c}")) .collect(); - excused_out( - &gone, - |_| false, - |dir| deleted.contains(dir) && !cx.abs(dir).exists(), - ) + excused_out(&gone, |_| false, is_deleted) } /// A plugin crate MOVED OUT (TODO PATH TO DEV-GREEN P5: filter-repo into its own repo, pinned back diff --git a/xtask/src/gates/kind_isolation.rs b/xtask/src/gates/kind_isolation.rs index 6161f295cd..a88fc449b0 100644 --- a/xtask/src/gates/kind_isolation.rs +++ b/xtask/src/gates/kind_isolation.rs @@ -10170,15 +10170,17 @@ impl Gate for KindIsolationGate { // table, so the finding names the step the loop expected. The subject is a plane with the // legacy face only: a plane that also serves through its door (the flipped ones) is judged // by its door's slots instead, so removing its legacy file leaves the door face standing. + // The a2a plane is the one left with the legacy face only (FLIP-STREAMING deleted the + // streaming plane's `plane.rs`; it serves through its door alone). let mut ov = Overlay::new(); - ov.remove("crates/busbar-plane-streaming/src/plane.rs"); + ov.remove("crates/busbar-plane-a2a/src/plane.rs"); report.push(prove_rows_red( cx, subject, "a data plane that implements none of the strict step list", &[ROW_STEPS], ov, - &["missing-step", "busbar-plane-streaming"], + &["missing-step", "busbar-plane-a2a"], )); // A PLANE THAT RUNS THE KERNEL'S DECISION, both ways the ruling names: an `approve` of its diff --git a/xtask/src/gates/reachability.rs b/xtask/src/gates/reachability.rs index 4d655237bf..1186bae750 100644 --- a/xtask/src/gates/reachability.rs +++ b/xtask/src/gates/reachability.rs @@ -3273,7 +3273,7 @@ impl Gate for ReachabilityGate { ); ov }, - "no linked-table row for `busbar-mcp` on the `plane` or `plane-door` axis", + "no linked-table row for `busbar-plane-mcp` on the `plane` or `plane-door` axis", )); // A DOOR ROW IS REGISTRATION (Part 2 #2, compiled-in = dropped-in): a door-shaped plane — // its declaration row on no `plane` axis, its crate linked again on `plane-door`, no From 9bec27330a6da8eb8f17907e9266fdf555235bdc Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:50:24 -0700 Subject: [PATCH 71/82] pre-flight: fmt / lock / abi header --- crates/busbar-plane-streaming/src/tests/session_pump_tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs index 0f583d46d8..6c57d0fe3a 100644 --- a/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs +++ b/crates/busbar-plane-streaming/src/tests/session_pump_tests.rs @@ -212,7 +212,8 @@ fn p_item_voice_tool_args_streamed_fragments_reach_the_caller_whole_and_once() { .map(|f| f["delta"].as_str().expect("a delta carries its fragment")) .collect(); assert_eq!( - args, "{\"loc\":\"SF\"}", + args, + "{\"loc\":\"SF\"}", "the fragments reach the caller whole, in order: {}", texts(&down) ); From 16963ef94cdab0118790a097f3866c7aa81f2fa5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:27:33 -0700 Subject: [PATCH 72/82] FLIP-STREAMING CI fixes: refusal-collapse scan reads the streaming door; plane-dup ledger signs the streaming door crate p_item_refusal_reason_collapse: the streaming renderer it scanned, busbar-plane-streaming/src/plane.rs fn refusal_render, was deleted by the flip. The streaming plane's refusal renderer is now door.rs fn refusal_body (status and text to the realtime error envelope, no reason match), so the scan and its planted case read that. structure-lint plane-dup:unledgered (41 -> 19, base 22): the streaming plane's home moved from busbar-voice to its door crate busbar-plane-streaming, which brought 24 names into the cross-plane scan. 21 of them are unrelated concerns sharing a name or each plane's own body for the same door slot (claims table, door, driven, meta, arrive, refusal_body, read_settings, ...); each now has a signed DISTINCT row for exactly the planes it is declared in. The three that are real copies (settle, absolute, claim) are deliberately left unsigned and stay findings. --- xtask/src/gates/structure_lint/plane_dups.rs | 34 +++++++++++++++++++ xtask/tests/p_item_refusal_reason_collapse.rs | 12 +++---- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/xtask/src/gates/structure_lint/plane_dups.rs b/xtask/src/gates/structure_lint/plane_dups.rs index 630c6fbd45..b64b9383c1 100644 --- a/xtask/src/gates/structure_lint/plane_dups.rs +++ b/xtask/src/gates/structure_lint/plane_dups.rs @@ -114,7 +114,41 @@ pub fn ledger() -> Vec { note: note.to_string(), planes: vec!["mcp".to_string(), "a2a".to_string()], }; + // FLIP-STREAMING: the streaming plane's home moved from `busbar-voice` to its door crate + // `busbar-plane-streaming`, and every row below was argued for exactly the planes it names + // against that crate. NOT signed, because they are copies and not distinct concerns: `settle` + // (decisions' `plane_door` and streaming's `piece`), `absolute` (a2a's `serve` and streaming's + // `door`) and `claim` (mcp's `tool_claims` and streaming's `claims`) — those stay findings. + let s = |name: &str, planes: &[&str], note: &str| LedgerRow { + name: name.to_string(), + class: Class::Distinct, + concern: String::new(), + note: note.to_string(), + planes: planes.iter().map(|p| (*p).to_string()).collect(), + }; vec![ + s("claims.rs", &["decisions", "streaming"], "Each plane's own claim table: decisions declares two exact-path claims under its decision-inbound scheme, streaming five dialect-tagged claims over ws and http under its streaming-key scheme. The table is per plane by construction; the claim grammar they both build is the contract's."), + s("diagnostics.rs", &["mcp", "streaming"], "Each plane's own diagnostic catalogue handed to the root's install_diagnostics: the MCP_* entries and STREAMING_SESSION_LEASE_EXHAUSTED. They share no entry; the catalogue type is the contract's."), + s("door.rs", &["mcp", "streaming"], "Each plane's door declaration: its Statement tail, its ROUTES table, its snapshot and settings. A door is per plane by definition; the ABI it fills is the contract's."), + s("driven.rs", &["decisions", "streaming"], "Each plane's answers to the kernel plane driver's crossings (arrive, on_piece, refusal): jev's systemone operation for decisions, the mint and SDP-offer doors for streaming. The slots are the ABI's; the answers are the plane's."), + s("meta.rs", &["decisions", "streaming"], "Each plane's PlaneMeta with its own meter classes: decisions' single decision class, streaming's audio token classes. The classes are what makes each plane's meter its own."), + s("usage.rs", &["llm", "streaming"], "Unrelated: llm's engine file bills a non-streaming token response to a key's budget (record_resp_usage); streaming's codec file is the Realtime and Gemini duplex usage IR and its float-tolerant count reader."), + s("Answer", &["mcp", "streaming"], "Unrelated: mcp's Answer is the reply to one MCP arrival (here with a status and body, or far); streaming's is a RequestUnit's per-piece answer (nothing, attempt, to the far end, to the caller, refused)."), + s("Arrival", &["mcp", "streaming"], "Unrelated: mcp's Arrival is one carrier line read (a LineUnit and an optional dispatch body); streaming's is its arrive slot's answer (door, op class, dialect)."), + s("CredentialPlacement", &["a2a", "streaming"], "Unrelated: a2a's places a leased credential in an HTTP header (Authorization Bearer or a named header); streaming's places a provider key on a WebSocket dial per dialect (a query parameter or a prefixed header)."), + s("Dialect", &["a2a", "streaming"], "Unrelated: a2a's Dialect is the push-config verb spelling (V03 or V10); streaming's is its voice dialect roster (OpenAI Realtime, Gemini Live, Twilio Media Streams, one-shot transcribe and tts)."), + s("Held", &["mcp", "streaming"], "Unrelated: mcp's Held is one generation's catalogue, tools section and pools; streaming's is a one-request door unit's state (its RequestUnit, what it owes, a short answer)."), + s("Outbound", &["a2a", "streaming"], "Unrelated: a2a's Outbound is the caller's request in three forms for the outbound hop; streaming's is a session frame plan (upstream and downlink wire events, close, a refused reply)."), + s("Reply", &["mcp", "streaming"], "Unrelated: mcp's Reply is a caller's answer to one of busbar's own asks (answered or failed); streaming's is the caller's HTTP answer (status, fields, body, the rtc call id)."), + s("Route", &["mcp", "streaming"], "Each plane's own row type for its door's ROUTES table: mcp's carries verb, target, carrier and open; streaming's adds upgrade, auth, dialect and refusal dialect. The row is shaped by what the plane routes on."), + s("Session", &["mcp", "streaming"], "Unrelated: mcp's Session is the carrier session's state (streams, buffered events, revision, owner); streaming's is the Realtime or Gemini enum over a codec's SessionUnit."), + s("Unit", &["decisions", "streaming"], "Unrelated: decisions' Unit is one unit's attempt, reading and owed bytes in its plane door; streaming's is the Realtime or Gemini codec enum its session door dispatches over."), + s("arrive", &["mcp", "decisions", "streaming"], "Each plane's body for the arrive slot: mcp reads a carrier line, decisions classifies verb and target through its ops table, streaming maps a claim index to a door. The slot is the ABI's; what an arrival means is the plane's."), + s("open", &["mcp", "streaming"], "Unrelated: mcp's door_listen open starts a subscriptions/listen subscription and its tool_claims open builds a claim with no credential scheme; streaming's constructs a live Session for a door."), + s("outbound", &["mcp", "streaming"], "Unrelated: mcp's builds the OutboundCall request (POST, path, body) for one tool-call round; streaming's is a const naming a connection need on the provider egress class."), + s("parse", &["a2a", "streaming"], "Unrelated: a2a's reads an agent card value into an AgentCard; streaming's reads wire bytes into a JSON value in its codec."), + s("read_settings", &["decisions", "streaming"], "Each plane judges its own settings blob by its own grammar and answers its own section for an empty one: DecisionsSection for decisions, StreamsCfg for streaming. The grammar is the plane's."), + s("refusal_body", &["decisions", "streaming"], "Each plane renders the kernel's refusal in its own wire's error shape: jev's error code and message for decisions, the OpenAI REST error envelope for streaming. The status and text are the kernel's; the shape is the dialect's."), d("config.rs", "The parse ORDER these two files shared is gone: plane::config::split_section owns the reserved-key refusals, the two typed lifts and the sequence they run in, and all THREE plane sections are read through it. What is left in each file is that plane's GRAMMAR and nothing else — they share no type, no field, no value rule, no sentence and no caller. What they share is a filename, which on every plane says the same true and un-actionable thing: this is where that section's grammar is written."), // STRUCK at P3 DEL-MCP (ARCHITECT 2026-10-05: busbar-mcp deleted, the mcp plane is served // through its door crate alone): `transport.rs`, `judge`, `revalidate`, `observed_pin`, diff --git a/xtask/tests/p_item_refusal_reason_collapse.rs b/xtask/tests/p_item_refusal_reason_collapse.rs index 49b7852d06..11a53fd20e 100644 --- a/xtask/tests/p_item_refusal_reason_collapse.rs +++ b/xtask/tests/p_item_refusal_reason_collapse.rs @@ -41,8 +41,8 @@ const RENDERERS: &[(&str, &[&str])] = &[ &["refusal_words"], ), ( - "crates/busbar-plane-streaming/src/plane.rs", - &["refusal_render"], + "crates/busbar-plane-streaming/src/door.rs", + &["refusal_body"], ), ( "crates/busbar/src/root/units_admin/admin_mount.rs", @@ -215,7 +215,7 @@ fn p_item_refusal_reason_collapse_the_classifier_names_every_reason() { #[test] fn p_item_refusal_reason_collapse_the_scan_catches_a_reason_match() { let reasons = vocabulary(); - let old = r#"fn refusal_render(reason: busbar_contract::unit::RefusalReason) -> (&'static str, &'static str) { + let old = r#"fn refusal_body(reason: busbar_contract::unit::RefusalReason) -> (&'static str, &'static str) { use busbar_contract::unit::RefusalReason as R; match reason { R::RateLimited | R::InFlightCap => ("rate_limited", "too many"), @@ -224,7 +224,7 @@ fn p_item_refusal_reason_collapse_the_scan_catches_a_reason_match() { } "#; let planted = findings(&reasons, &|f| { - if f == "crates/busbar-plane-streaming/src/plane.rs" { + if f == "crates/busbar-plane-streaming/src/door.rs" { old.to_string() } else { read(f) @@ -233,8 +233,8 @@ fn p_item_refusal_reason_collapse_the_scan_catches_a_reason_match() { assert_eq!( planted, [ - "crates/busbar-plane-streaming/src/plane.rs fn refusal_render names the reason R::InFlightCap", - "crates/busbar-plane-streaming/src/plane.rs fn refusal_render names the reason R::RateLimited", + "crates/busbar-plane-streaming/src/door.rs fn refusal_body names the reason R::InFlightCap", + "crates/busbar-plane-streaming/src/door.rs fn refusal_body names the reason R::RateLimited", ] ); let spelled = r#"fn kind_of(reason: &str, status: u16) -> &'static str { From 809d67259bddefafbc3bf6e5fec5662110271767 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 10 Oct 2026 08:38:27 -0700 Subject: [PATCH 73/82] FLIP-STREAMING reds: the root names no streaming vocabulary, the purity allowlist drops the eight rows predev took off EngineHost, seal-witness re-arms its floor at the measured 2140 - kind-isolation:law0 (busbar x plane/transport/vendor): the session-door cells, registry pins and root test comments are rewritten against kind-neutral facts (the door found by its audio class, the provider protocol read off its Statement's bearer dialect, the upgrade wire read off LINKED); the catalog-route test's provider is a neutral label. busbar x plane drains 829 -> 805 and its [[law0]] row is lowered to 805 (a fall gives its row back); transport and vendor return to predev's 208 and 90. - plane_host_universal_purity: breaker_*, gate_*, tap_attached and transform_over left the universal EngineHost on predev (db5392793f), so their allowlist rows were stale; the fixture is predev's. - seal-witness: deleting busbar-voice took the crates/ population to 2140; SCAN_FLOOR re-arms at the measured count (Law 9), as 0e2fee34e1 did after egress_auth retired. --- crates/busbar/src/root/serve.rs | 11 +-- crates/busbar/src/root/tests/registry.rs | 69 ++++++++--------- crates/busbar/src/root/tests/serve_planes.rs | 4 +- crates/busbar/src/root/tests/serve_tests.rs | 74 ++++++++++++------- .../tests/fixtures/single_plane_allowlist.txt | 8 -- crates/busbar/tests/plane_isomorphism.rs | 4 +- .../busbar/tests/plane_owned_sections_boot.rs | 6 +- .../plane_registration_is_contract_data.rs | 8 +- qa/kind-isolation.toml | 4 +- xtask/src/gates/seal_witness.rs | 17 +++-- 10 files changed, 109 insertions(+), 96 deletions(-) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index a9734c60f9..af85a617e8 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -3444,11 +3444,12 @@ mod tests; pub(crate) mod planes_tests; // The doors served end to end through this composition: the decisions plane's (the `root-decisions` -// leg's own cells, qa/capability-equality.json, under its feature), the MCP plane's (the `root-mcp` -// leg's loop cells, under the linked plane-door axis) and the streaming plane's (the `root-voice` -// leg's cells), each gated item by item inside, so either plane's switch alone -// still compiles its own. Its MCP cells reach the door_steps helpers that need the default build's -// linked auth rows (gated with the node-axis plane that carries that build). +// leg's own cells, qa/capability-equality.json, under its feature) and the MCP plane's (the +// `root-mcp` leg's loop cells, under the linked plane-door axis), each gated item by item inside, +// so either plane's switch alone still compiles its own, and the session door's root-leg cells, +// which find their door among the linked plane doors. Its MCP cells reach the door_steps helpers +// that need the default build's linked auth rows (gated with the node-axis plane that carries that +// build). #[cfg(all(test, linked_axis_node))] #[path = "tests/serve_tests.rs"] mod door_tests; diff --git a/crates/busbar/src/root/tests/registry.rs b/crates/busbar/src/root/tests/registry.rs index 321a71fe8f..eb50a0f8ac 100644 --- a/crates/busbar/src/root/tests/registry.rs +++ b/crates/busbar/src/root/tests/registry.rs @@ -38,7 +38,7 @@ const TRANSPORT_FOLD: &str = include_str!("fixtures/transport_fold.txt"); /// The sealed walk over the forty declared claims, most specific first. The decisions plane's /// claim is its door snapshot's (FLIP-DECISIONS), mounted by the serve fold, as the MCP plane's are -/// (FLIP-MCP) and the streaming plane's are (FLIP-STREAMING), so none of them is here. +/// (FLIP-MCP) and every other door plane's are, so none of them is here. /// /// Pinned as text rather than as indices so that a diff of it reads as a routing change. See /// the test that reads it for what a change to this snapshot means. The rows are fixture DATA @@ -51,14 +51,17 @@ const SEALED_ORDER: &str = include_str!("fixtures/sealed_order.txt"); #[cfg(linked_every_plane)] const CLAIMS_PER_PLANE: &str = include_str!("fixtures/claims_per_plane.txt"); -/// Whether this build links the WS framer (its transport row, on the `transport` and -/// `connector-door` axes): the sixth wire, composed over http. Since FLIP-STREAMING no plane rides -/// the kernel's SESSION loop: the streaming plane is a door plane (`plane-door` axis) whose claims -/// are its snapshot's and are mounted by the serve fold, not declared in the linked claims table, -/// so the ws wire is the transport row's alone. Every pinned number below is a statement about ONE -/// composition, the shipped one. -fn ws_linked() -> bool { - crate::LINKED.transports.iter().any(|t| t.key == "ws") +/// Whether this build links a wire that opens at an upgrade (a transport row stating an upgrade +/// claim, on the `transport` and `connector-door` axes): the sixth wire. No plane rides the +/// kernel's SESSION loop: the session plane is a door plane (`plane-door` axis) whose claims are +/// its snapshot's and are mounted by the serve fold, not declared in the linked claims table, so +/// the wire is the transport row's alone. Every pinned number below is a statement about ONE +/// composition, the shipped one. Read off `LINKED`, so this source names no wire. +fn upgrade_wire_linked() -> bool { + crate::LINKED + .transports + .iter() + .any(|t| !(t.upgrades)().is_empty()) } /// The non-comment rows of a fixture file, in order. Read only by the shipped-composition pins, @@ -83,7 +86,7 @@ fn six_transports_and_five_planes_register() { let registry = linked_registry(); assert_eq!( registry.count(PluginKind::Transport), - if ws_linked() { 6 } else { 5 } + if upgrade_wire_linked() { 6 } else { 5 } ); // Every linked plane and the core one: a plane this build does not link registers nothing. assert_eq!( @@ -116,10 +119,10 @@ fn six_transports_and_five_planes_register() { // The decisions plane is not on this table: it registers through its door (`plane-door` axis), // whose Statement the kernel folds into its row, and its claim is its door snapshot's // (FLIP-DECISIONS). - // The ws wire is registered exactly when its transport row is linked. + // The upgrade wire is registered exactly when its transport row is linked. assert_eq!( registry.resolve(PluginKind::Transport, "ws").is_some(), - ws_linked() + upgrade_wire_linked() ); } @@ -127,8 +130,8 @@ fn six_transports_and_five_planes_register() { /// what a reader checks the design's own table against; a plane that gains or loses a claim /// should have to say so here. // Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them -// because the numbers below are that composition's, not a subset of it. The streaming plane's claims -// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. +// because the numbers below are that composition's, not a subset of it. A door plane's claims are +// its door snapshot's, mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn the_planes_declare_forty_claims() { @@ -142,8 +145,8 @@ fn the_planes_declare_forty_claims() { (key.to_string(), n.parse().expect("a claim count")) }) .collect(); - // Three rows: FLIP-MCP, FLIP-DECISIONS and FLIP-STREAMING each took a plane's claims out of the - // linked table (its door's snapshot claims them, mounted by the serve fold). + // Three rows: each plane that became a door plane took its claims out of the linked table (its + // door's snapshot claims them, mounted by the serve fold). assert_eq!(pinned.len(), 3, "three planes are pinned: {pinned:?}"); for (key, n) in &pinned { assert_eq!( @@ -181,15 +184,15 @@ fn the_planes_declare_forty_claims() { /// them, mounted by the serve fold behind the data listener's guest list): ten cross-family pairs; /// the same-family pairs were unchanged. FLIP-DECISIONS took the decisions plane's two claims out of /// it too (its door's snapshot claims `POST /v1/systemone` alone, mounted by the serve fold): ten -/// cross-family pairs and one same-family pair. FLIP-STREAMING took the streaming plane's five +/// cross-family pairs and one same-family pair. The session plane's flip to its door took its five /// claims out (its door's snapshot claims them, mounted by the serve fold): ten cross-family and 22 /// same-family pairs. The three planes' claims never overlapped one another (none of them states a -/// header claim, the MCP plane's stream name is not on the path planes' transport, and no exact path -/// of one matches another's suffix, substring or prefix selectors), so 100 cross-family and 64 +/// header claim, the tool plane's stream name is not on the path planes' transport, and no exact +/// path of one matches another's suffix, substring or prefix selectors), so 100 cross-family and 64 /// same-family became 70 and 41. // Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them -// because the numbers below are that composition's, not a subset of it. The streaming plane's claims -// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. +// because the numbers below are that composition's, not a subset of it. A door plane's claims are +// its door snapshot's, mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn one_hundred_and_eleven_cross_plane_pairs_overlap() { @@ -230,8 +233,8 @@ fn one_hundred_and_eleven_cross_plane_pairs_overlap() { /// A pair that fits none of these would be the interesting one: a conservative answer with no /// account of itself. There is none, and the assertion is that there is none. // Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them -// because the numbers below are that composition's, not a subset of it. The streaming plane's claims -// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. +// because the numbers below are that composition's, not a subset of it. A door plane's claims are +// its door snapshot's, mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn every_remaining_path_overlap_is_a_shape_and_not_a_gap() { @@ -269,8 +272,8 @@ fn every_remaining_path_overlap_is_a_shape_and_not_a_gap() { // (FLIP-DECISIONS). assert_eq!(tail, 17); assert_eq!(variable, 24); - // The fragment pairs were the streaming plane's audio suffixes against the llm plane's; its - // claims left this table with FLIP-STREAMING. + // The fragment pairs were the session plane's audio suffixes against the model plane's; its + // claims left this table when it became a door plane. assert_eq!(fragments, 0); } @@ -282,8 +285,8 @@ fn every_remaining_path_overlap_is_a_shape_and_not_a_gap() { /// and each of those is a different thing to have to explain. The refusal list is pinned empty, /// which is the whole claim of this file — the declared set of planes seals. // Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them -// because the numbers below are that composition's, not a subset of it. The streaming plane's claims -// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. +// because the numbers below are that composition's, not a subset of it. A door plane's claims are +// its door snapshot's, mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn every_cross_plane_overlap_is_resolved_by_precedence_and_none_refuses() { @@ -324,8 +327,8 @@ fn every_cross_plane_overlap_is_resolved_by_precedence_and_none_refuses() { /// as a routing change rather than as a permutation of opaque indices. A claim added, removed or /// respelled has to update it, on purpose, with the new order visible in the same diff. // Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them -// because the numbers below are that composition's, not a subset of it. The streaming plane's claims -// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. +// because the numbers below are that composition's, not a subset of it. A door plane's claims are +// its door snapshot's, mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn the_sealed_order_of_the_forty_claims_is_pinned() { @@ -509,7 +512,7 @@ fn the_shipped_transport_stack_composes() { // NO TRANSPORT NAMES ANOTHER (ARCHITECT Q128 U7; TRANSPORT-STACK (2), :4721): every row is // built over nothing, the carrier being the connector's choice from the target's scheme, and // `sse` is a claim of the http entry, served by its wire, not a layer over it. - if ws_linked() { + if upgrade_wire_linked() { assert_eq!(composed_over("ws"), None); } assert_eq!(composed_over("grpc"), None); @@ -623,8 +626,8 @@ fn a_claim_on_a_transport_with_no_crate_refuses_at_boot() { /// the claims sealed, and the one transport gap was all that stood between the declared /// composition and a node that boots. // Pinned against the SHIPPED composition (every linked plane on). Compiled out with any of them -// because the numbers below are that composition's, not a subset of it. The streaming plane's claims -// are its door snapshot's (FLIP-STREAMING), mounted by the serve fold, so they are not counted here. +// because the numbers below are that composition's, not a subset of it. A door plane's claims are +// its door snapshot's, mounted by the serve fold, so they are not counted here. #[cfg(linked_every_plane)] #[test] fn the_seal_answers_now_that_every_claim_names_a_registered_transport() { @@ -1012,7 +1015,7 @@ fn a_dropped_in_wire_on_a_linked_key_is_refused_as_a_second_linked_row_is() { #[test] fn the_upgrade_lines_are_read_off_the_door_statements() { let lines = crate::root::serve::upgrade_carriers(crate::LINKED.transports); - if ws_linked() { + if upgrade_wire_linked() { assert_eq!( lines, ["ws"], diff --git a/crates/busbar/src/root/tests/serve_planes.rs b/crates/busbar/src/root/tests/serve_planes.rs index f2cf197c4d..8e74a27c04 100644 --- a/crates/busbar/src/root/tests/serve_planes.rs +++ b/crates/busbar/src/root/tests/serve_planes.rs @@ -218,7 +218,7 @@ fn a_session_model_from_the_top_level_catalog_is_folded_into_the_route_table() { use busbar_contract::abi::plane::ROUTE_DIRECT; let catalog: std::collections::HashMap = serde_yaml::from_str( - "gpt-rt: { provider: openai, upstream_model: gpt-realtime, attempt_timeout_ms: 900 }\n", + "gpt-rt: { provider: p, upstream_model: gpt-realtime, attempt_timeout_ms: 900 }\n", ) .expect("the catalog"); let section: serde_yaml::Value = @@ -227,7 +227,7 @@ fn a_session_model_from_the_top_level_catalog_is_folded_into_the_route_table() { assert_eq!( routes["models"]["gpt-rt"], serde_yaml::from_str::( - "{ provider: openai, upstream_model: gpt-realtime, attempt_timeout_ms: 900 }" + "{ provider: p, upstream_model: gpt-realtime, attempt_timeout_ms: 900 }" ) .expect("yaml"), "the catalog entry's routing facts, folded" diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index c2ddf6757c..f0cfc8f847 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -18,8 +18,8 @@ //! the MCP plane's door, composed here the same way, each core capability driven through the data //! router and asserted where it lands. //! -//! The `session_door` module below is the `root-voice` leg's cells (ARCHITECT 2026-10-05 Q1): the -//! streaming door, served end to end the same way. +//! The `session_door` module below is the session door's root-leg cells (ARCHITECT 2026-10-05 Q1): +//! the linked plane door that meters audio, served end to end the same way. #[cfg(feature = "plane-decisions")] use std::collections::BTreeMap; @@ -586,11 +586,12 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { ); } -/// THE ROOT-VOICE LEG'S CELLS (ARCHITECT 2026-10-05 Q1): the streaming door, served as production -/// composes it — the browser's ephemeral-secret mint, a keyed caller, the door's DIRECT route to the -/// top-level catalog model `streams.session.model` names, the provider's own credential presented by -/// the auth plugin, the caller's spend posted against the key that presented it. The door is found -/// among the linked plane doors by the audio class it meters, so a build that links none skips. +/// THE SESSION DOOR'S ROOT-LEG CELLS (ARCHITECT 2026-10-05 Q1): the session door, served as +/// production composes it — the browser's ephemeral-secret mint, a keyed caller, the door's DIRECT +/// route to the top-level catalog model its section's `session.model` names, the provider's own +/// credential presented by the auth plugin, the caller's spend posted against the key that +/// presented it. The door is found among the linked plane doors by the audio class it meters, so a +/// build that links none skips. mod session_door { use std::collections::BTreeMap; use std::sync::Arc; @@ -632,13 +633,13 @@ mod session_door { /// The provider's credential, as its file holds it. const CREDENTIAL: &str = "sk-door-test"; - /// The streaming door's mint claim. + /// The session door's mint claim. const MINT: &str = "/v1/realtime/client_secrets"; /// The provider's answer to the mint: an ephemeral secret and its expiry. const MINTED: &str = r#"{"value":"ek_door_0001","expires_at":1767225600}"#; - /// The deployment's public base URL the streaming door fronts. + /// The deployment's public base URL the session door fronts. const PUBLIC: &str = "http://gw.test"; /// The billable class the session door meters a caller's audio under: how the harness finds that @@ -723,7 +724,7 @@ mod session_door { (port, heard) } - /// The streaming door, served as production composes it, and what each `root-voice` cell reads. + /// The session door, served as production composes it, and what each root-leg cell reads. struct SessionDoor { _published: Published, router: axum::Router, @@ -766,13 +767,13 @@ mod session_door { }) } - /// [`SessionDoor`]: the linked session door (the plane door that meters audio) bound through the - /// loader's one load, its needs declared on the connector over every linked transport door; a - /// signing governance book with one key (in a group whose all-time budget is `budget`, the door's - /// per-request fee one, or no group and no fee); an OpenAI-protocol provider on a loopback far - /// end answering the mint, reached through the catalog model `streams.session.model` names; the - /// door composed under [`PUBLIC`] with its egress sealed by the composition itself, and its - /// claims mounted on the data router. + /// [`SessionDoor`]: the linked session door (the plane door that meters audio) bound through + /// the loader's one load, its needs declared on the connector over every linked transport door; + /// a signing governance book with one key (in a group whose all-time budget is `budget`, the + /// door's per-request fee one, or no group and no fee); a provider on the protocol the door + /// states a bearer default for, on a loopback far end answering the mint, reached through the + /// catalog model its section's `session.model` names; the door composed under [`PUBLIC`] with + /// its egress sealed by the composition itself, and its claims mounted on the data router. async fn session_served(instance: &'static str, budget: Option) -> Option { let published = Published(instance); let (port, heard) = far_end_answering(MINTED).await; @@ -802,6 +803,23 @@ mod session_door { )?; let plane_name = plane.name().to_owned(); let section_key = plane.served().section; + // THE PROVIDER'S PROTOCOL, read off the door's Statement rather than spelled here: the last + // dialect it states a `bearer` default outbound style for (its far-end spellings follow its + // arrival dialects), so this harness names no dialect. + let protocol = { + let served = plane.served(); + served + .dialect_auth + .iter() + .rev() + .filter(|(_, style)| *style == "bearer") + .find_map(|(d, _)| { + usize::try_from(*d) + .ok() + .and_then(|i| served.dialects.get(i).copied()) + }) + .expect("the session door states a bearer-style dialect") + }; let groups: BTreeMap = budget .map(|amount| { @@ -873,7 +891,7 @@ mod session_door { )); std::fs::write(&key_file, CREDENTIAL).expect("the credential file"); let provider: busbar_kernel::config::ProviderCfg = serde_yaml::from_str(&format!( - "{{protocol: openai, base_url: 'http://127.0.0.1:{port}', api_key: {{file: '{}'}}, error_map: {{}}}}", + "{{protocol: {protocol}, base_url: 'http://127.0.0.1:{port}', api_key: {{file: '{}'}}, error_map: {{}}}}", key_file.display() )) .expect("a provider entry"); @@ -958,7 +976,7 @@ mod session_door { .requests } - /// The node book's lines for the streaming door's catalog route. + /// The node book's lines for the session door's catalog route. fn lines(&self) -> usize { let rows = self.book.durability.lock().expect("unpoisoned").read_back(); rows.iter() @@ -967,14 +985,14 @@ mod session_door { } } - /// THE STREAMING DOOR'S MINT, SERVED (the `root-voice` leg, ARCHITECT 2026-10-05 Q1): a keyed - /// caller's `POST /v1/realtime/client_secrets` is one unit the door carries (ARRIVAL), admitted - /// for the key its token resolves (AUTHENTICATE), dialled to the destination the guard judged - /// (VERIFY) on the DIRECT route to the catalog model `streams.session.model` names (ROUTE), sent - /// WITH THE PROVIDER'S OWN CREDENTIAL and never the caller's token (EGRESS-AUTH), charged to the - /// key that presented it (METER, GOVERNANCE-BUDGET), and closed once with its one line on the - /// node's book (AUDIT, EXIT). (The door's audience binding is the deployment's mount table's, - /// judged end to end by the `streams|mint|wrong-audience` oracle cell.) + /// THE SESSION DOOR'S MINT, SERVED (its root leg, ARCHITECT 2026-10-05 Q1): a keyed caller's + /// `POST /v1/realtime/client_secrets` is one unit the door carries (ARRIVAL), admitted for the + /// key its token resolves (AUTHENTICATE), dialled to the destination the guard judged (VERIFY) + /// on the DIRECT route to the catalog model its section's `session.model` names (ROUTE), sent + /// WITH THE PROVIDER'S OWN CREDENTIAL and never the caller's token (EGRESS-AUTH), charged to + /// the key that presented it (METER, GOVERNANCE-BUDGET), and closed once with its one line on + /// the node's book (AUDIT, EXIT). (The door's audience binding is the deployment's mount + /// table's, judged end to end by the oracle's wrong-audience mint cell.) /// /// RED ARM: the same request with no credential is refused before anything is dialled or charged. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -1032,7 +1050,7 @@ mod session_door { assert_eq!(s.lines(), 1, "its one line on the node's book"); } - /// ADMIT, at the streaming door (`root-voice`): a key whose group budget is already spent is + /// ADMIT, at the session door (its root leg): a key whose group budget is already spent is /// refused at admission — over budget, before the provider is dialled, charged nothing, no line /// written — while a key with room is served (the mint cell above). RED: a door that dialled /// before admission would have reached the far end. diff --git a/crates/busbar/tests/fixtures/single_plane_allowlist.txt b/crates/busbar/tests/fixtures/single_plane_allowlist.txt index 17986f49d0..efb574ca9e 100644 --- a/crates/busbar/tests/fixtures/single_plane_allowlist.txt +++ b/crates/busbar/tests/fixtures/single_plane_allowlist.txt @@ -6,10 +6,6 @@ admission_door llm Neutral single-consumer capability: generic signature, no for any_content_hook llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. audit_emit a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-MCP (the MCP plane is served through its door and calls no EngineHost method). Re-review if a second plane consumes it. audit_record llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. -breaker_admit a2a Neutral single-consumer capability: generic signature (scope, pool, lane), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. -breaker_record_signal a2a Neutral single-consumer capability: generic signature (pool, lane, signal), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. -breaker_record_success a2a Neutral single-consumer capability: generic signature (pool, lane), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. -breaker_retry_after_secs a2a Neutral single-consumer capability: generic signature (pool, lane), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. breaker_settle a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-MCP (the MCP plane is served through its door and calls no EngineHost method). Re-review if a second plane consumes it. budget_state llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. caller_in_hook_groups llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. @@ -20,8 +16,6 @@ default_probe_timeout_secs llm Neutral single-consumer capability: generic signa destination_guard llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. finish_admitted llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. finish_rejected llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. -gate_attached a2a Neutral single-consumer capability: the kernel-sealed hook gate's presence check (plane key, container), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. -gate_decide a2a Neutral single-consumer capability: the kernel-sealed hook gate (the hook sees the content inside the kernel), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. global_gates llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. govern_admit_reason a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-MCP (the MCP plane is served through its door and calls no EngineHost method). Re-review if a second plane consumes it. governance llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. @@ -46,7 +40,6 @@ rewrite_hooks llm Neutral single-consumer capability: generic signature, no fore secret_resolver a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane (card signing / pool membership / request-finish). settle_residual llm Neutral single-consumer capability: the kernel spells the usage.residual row and the plane hands over only a count map (#334, MONEY LAW); sole current caller is the LLM plane's settle step. Re-review if a second plane consumes it. subkey_sign a2a Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the A2A plane (card signing / pool membership / request-finish). -tap_attached a2a Neutral single-consumer capability: the kernel-sealed transform tap's presence check (plane key, container), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. tap_hooks llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. tap_hooks_candidate llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. tap_hooks_response llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. @@ -56,5 +49,4 @@ telemetry_failover llm Neutral single-consumer capability: generic signature, no telemetry_translation llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. telemetry_upstream_attempt llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. telemetry_upstream_failure llm Neutral single-consumer capability: generic signature, no foreign-plane vocabulary; sole current caller is the LLM plane's request/metering/telemetry path. Re-review if a second plane consumes it. -transform_over a2a Neutral single-consumer capability: the kernel-sealed transform pass (the hook rewrites the content inside the kernel), no foreign-plane vocabulary; sole current caller is the A2A plane since FLIP-STREAMING (busbar-voice, its other caller, is deleted and the streaming plane is served through its door). Re-review if a second plane consumes it. verify_token_test llm test-only (cfg test/test-support) raw-token verifier for the LLM routing-policy test seam; never linked in a production binary. diff --git a/crates/busbar/tests/plane_isomorphism.rs b/crates/busbar/tests/plane_isomorphism.rs index e33c2667fe..74b9b36513 100644 --- a/crates/busbar/tests/plane_isomorphism.rs +++ b/crates/busbar/tests/plane_isomorphism.rs @@ -520,8 +520,8 @@ fn the_reflected_hook_set_and_constants_are_the_doctrine() { } // The doctrine's installed-plane axis, verbatim (the same two the composition root installs as // a PlaneDecl: P3 DEL-MCP, ARCHITECT 2026-10-05, took mcp off it — the plane is served through its - // memory-ABI door alone and installs no PlaneDecl — and the streaming plane left it with - // FLIP-STREAMING: door-only). + // memory-ABI door alone and installs no PlaneDecl — and the session plane left it when it too + // became door-only). let keys: Vec<&str> = plane_ledger_columns().iter().map(|(k, _)| *k).collect(); assert_eq!( keys.len(), diff --git a/crates/busbar/tests/plane_owned_sections_boot.rs b/crates/busbar/tests/plane_owned_sections_boot.rs index 3c851de061..ca86347f65 100644 --- a/crates/busbar/tests/plane_owned_sections_boot.rs +++ b/crates/busbar/tests/plane_owned_sections_boot.rs @@ -30,9 +30,9 @@ fn owning_planes() -> Vec<&'static PlaneDecl> { .filter(|d| !d.owned_config_sections.is_empty()) .collect(); // Non-vacuity, against the doctrine: with every plane compiled in, some linked plane owns a - // section exactly when the doctrine pins an `owned-section` row. It pins none since FLIP-MCP and - // FLIP-STREAMING: both section-owning planes are door planes, whose sections their doors' - // Statements declare (DECL-FOLD), not a linked PlaneDecl. + // section exactly when the doctrine pins an `owned-section` row. It pins none: both + // section-owning planes are door planes, whose sections their doors' Statements declare + // (DECL-FOLD), not a linked PlaneDecl. #[cfg(linked_every_plane)] assert_eq!( owning.is_empty(), diff --git a/crates/busbar/tests/plane_registration_is_contract_data.rs b/crates/busbar/tests/plane_registration_is_contract_data.rs index 00fd525f58..9b4b862ebd 100644 --- a/crates/busbar/tests/plane_registration_is_contract_data.rs +++ b/crates/busbar/tests/plane_registration_is_contract_data.rs @@ -138,11 +138,9 @@ fn every_plane_registration_item_is_contract_data_naming_no_kernel_type() { ); // NON-VACUITY: the engine planes register through the contract type: llm and a2a. The floor was - // five (those two, mcp, voice and decisions); FLIP-MCP and FLIP-DECISIONS each deleted one item, - // since each of those planes' rows is now its door's Statement, folded by the composition root, - // and FLIP-STREAMING deleted busbar-voice, the streaming plane registering through its door's - // Statement. Fewer than two read means the scan read nothing, and zero offenders over zero items - // is not a pass. + // five; each plane flip to a door since deleted one item, since each flipped plane's row is now + // its door's Statement, folded by the composition root. Fewer than two read means the scan read + // nothing, and zero offenders over zero items is not a pass. let contract_typed = items.iter().filter(|i| i.ty == CONTRACT_TYPE).count(); assert!( contract_typed >= 2, diff --git a/qa/kind-isolation.toml b/qa/kind-isolation.toml index 9434e9b016..3a2d5d76d9 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -1543,8 +1543,8 @@ cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain [[law0]] crate = "busbar" axis = "plane" -count = "822" -cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; RE-ARMED 798 -> 822 (Law 9, BUSBAR-1.6.0.md: it pins its ceiling at TODAY'S MEASURED VALUE): the row went live on predev at dc83ff1553 (#597) and measured 826 there; the 798 was f830faa691's, nine hours older than the arming, so the row armed red. 822 is predev 9c0a394771's measure, at or under the arming value" +count = "805" +cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; RE-ARMED 798 -> 822 (Law 9, BUSBAR-1.6.0.md: it pins its ceiling at TODAY'S MEASURED VALUE): the row went live on predev at dc83ff1553 (#597) and measured 826 there; the 798 was f830faa691's, nine hours older than the arming, so the row armed red. 822 is predev 9c0a394771's measure, at or under the arming value; LOWERED 822 -> 805 by FLIP-STREAMING (Law 9: the ceiling ratchets down): predev measured 829 (seven over the row), and the flip drained 24 from the root (the deleted plane's rows leave the root's claim fixtures, and its session-door cells, registry pins and test comments name no plane)" [[law0]] crate = "busbar" diff --git a/xtask/src/gates/seal_witness.rs b/xtask/src/gates/seal_witness.rs index 827fcd6058..2b4ae325e2 100644 --- a/xtask/src/gates/seal_witness.rs +++ b/xtask/src/gates/seal_witness.rs @@ -125,14 +125,15 @@ const ROOTS: &[&str] = &["crates"]; const EXCLUDE: &[&str] = &["/target/"]; /// The denominator floor: a walk that finds fewer files than this is broken, not clean. /// -/// ARMED AT THE MEASURED POPULATION (Law 9), not a margin below it: 2174 `.rs` files under -/// `crates/` on predev 39374ec00e (the gate prints the count in both judging rows' detail). It -/// read 200 — about a tenth of the tree — so a walk that silently dropped nine files in ten still -/// scanned "clean". It was first armed at 2181 on predev 0fd08ee75d; predev then retired the -/// kernel-identity and kernel `egress_auth` modules (24 files out, 15 in), and a floor above the -/// tree it guards refuses every walk. A tree that grows keeps this floor; a walk that comes back -/// short of it is refused. -const SCAN_FLOOR: usize = 2174; +/// ARMED AT THE MEASURED POPULATION (Law 9), not a margin below it: 2140 `.rs` files under +/// `crates/` on FLIP-STREAMING merged onto predev (the gate prints the count in both judging rows' +/// detail). It read 200 — about a tenth of the tree — so a walk that silently dropped nine files in +/// ten still scanned "clean". It was first armed at 2181 on predev 0fd08ee75d; predev then retired +/// the kernel-identity and kernel `egress_auth` modules (24 files out, 15 in), re-arming it at 2174 +/// on predev 39374ec00e; FLIP-STREAMING then deleted a legacy plane crate (its plane is served +/// through its door), and a floor above the tree it guards refuses every walk. A tree +/// that grows keeps this floor; a walk that comes back short of it is refused. +const SCAN_FLOOR: usize = 2140; /// Is `rel` a file that compiles only into a TEST, BENCH or EXAMPLE target — never into a shipped /// lib/bin? A `tests/` directory anywhere on the path (which covers `src/tests/`), a `benches/` or From f5e1d6ef32125383d43f85eabf607befd38d7ca8 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:04:04 -0700 Subject: [PATCH 74/82] FLIP-STREAMING reds: scan floors re-measured after busbar-voice's deletion; the secret-source scan drives the streaming door - population FLOOR 941 -> 926 (23 crates), tracing SCAN_FLOOR 971 -> 955, no-deferral DISCOVERY_FLOOR 932 -> 917: the counts CI measured at b2897a2bc once the busbar-voice crate left the tree (a reviewed removal of a whole crate, not a scan gone blind). Fixes the settings-leak, response-header, blocking-ffi, tracing, no-deferral and no-deferral-strict-done gate rows, their selftests, and the two xtask infra no_deferral tests. - secret_source.rs: the linked streaming door (found by the audio class it meters, as its root cells find it) gets the compose-time and across-an-apply drivers the decisions door has; serve_tests.rs' session_door rig takes the provider credential as a secret reference (session_keyed) and answers the composition's refusal. Fixes every_linked_door_plane_is_driven. Not proven on a runner: the Latchkey run budget is spent (RUN_CAP), so the PR's CI is the proof. The kind-isolation / kind-isolation-ship armed rows are not addressed here. --- crates/busbar/src/root/tests/secret_source.rs | 70 ++++++++++++ crates/busbar/src/root/tests/serve_tests.rs | 102 +++++++++++++----- xtask/src/gates/no_deferral.rs | 7 +- xtask/src/gates/population.rs | 11 +- xtask/src/gates/tracing.rs | 11 +- 5 files changed, 160 insertions(+), 41 deletions(-) diff --git a/crates/busbar/src/root/tests/secret_source.rs b/crates/busbar/src/root/tests/secret_source.rs index 813c4a2b15..85a78cf9ae 100644 --- a/crates/busbar/src/root/tests/secret_source.rs +++ b/crates/busbar/src/root/tests/secret_source.rs @@ -220,6 +220,7 @@ fn driven() -> Vec { )]; #[cfg(linked_section_decisions)] names.push(super::door_tests::decisions_name()); + names.extend(super::door_tests::session_door::name()); names } @@ -428,3 +429,72 @@ async fn decisions_a_provider_key_gone_by_an_apply_reaches_the_caller_without_it .expect("the body reads"); assert_clean(instance, status, &headers, &body, &secret); } + +/// THE SESSION DOOR, COMPOSE TIME: a provider whose `api_key` does not resolve refuses the boot +/// with the operator's text, which names the source; nothing is served to a caller. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn session_door_an_unresolved_provider_key_refuses_the_boot_naming_its_source() { + let _one = PUBLISHING.lock().await; + let instance = "session-key-compose"; + let path = missing(instance); + let secret = SecretRef::file(path.clone()); + refused_naming_its_source(&secret); + let Some(composed) = super::door_tests::session_door::session_keyed( + instance, + None, + &format!("{{file: '{path}'}}"), + ) + .await + else { + eprintln!("skip: this build links no session door"); + return; + }; + let Err(refusal) = composed else { + panic!("a provider key that does not resolve refuses the composition"); + }; + println!("SECRET-SOURCE {instance}: compose refused (operator): {refusal}"); + assert!( + !source_in(&refusal, &secret).is_empty(), + "the operator's refusal names the source: {refusal}" + ); +} + +/// THE SESSION DOOR, ACROSS AN APPLY: a provider served while its `api_key` file was there, the +/// file then gone and the configuration applied: the door keeps the generation it serves, and the +/// caller's mint is served by it, its render clean of the source. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn session_door_a_provider_key_gone_by_an_apply_reaches_the_caller_without_its_source() { + let _one = PUBLISHING.lock().await; + let instance = "session-key-apply"; + let path = missing(instance); + std::fs::write(&path, "sk-secret-source-test").expect("the credential file"); + let secret = SecretRef::file(path.clone()); + let Some(composed) = super::door_tests::session_door::session_keyed( + instance, + None, + &format!("{{file: '{path}'}}"), + ) + .await + else { + let _ = std::fs::remove_file(&path); + eprintln!("skip: this build links no session door"); + return; + }; + let serving = composed.expect("the door composes"); + std::fs::remove_file(&path).expect("the credential file goes"); + refused_naming_its_source(&secret); + let before = serving.live.current().generation; + serving.live.apply(&serving.app); + assert_eq!( + serving.live.current().generation, + before, + "the door keeps the generation it serves" + ); + let response = serving.mint().await; + let status = response.status(); + let headers = response.headers().clone(); + let body = axum::body::to_bytes(response.into_body(), 1 << 20) + .await + .expect("the body reads"); + assert_clean(instance, status, &headers, &body, &secret); +} diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index 311173b5f4..ad13686ec9 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -595,8 +595,8 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { /// route to the top-level catalog model its section's `session.model` names, the provider's own /// credential presented by the auth plugin, the caller's spend posted against the key that /// presented it. The door is found among the linked plane doors by the audio class it meters, so a -/// build that links none skips. -mod session_door { +/// build that links none skips. The secret-source scan (`secret_source.rs`) drives the same door. +pub(super) mod session_door { use std::collections::BTreeMap; use std::sync::Arc; @@ -729,15 +729,17 @@ mod session_door { } /// The session door, served as production composes it, and what each root-leg cell reads. - struct SessionDoor { + pub(in crate::root::serve) struct SessionDoor { _published: Published, router: axum::Router, heard: tokio::sync::mpsc::UnboundedReceiver, gov: Arc, - app: Arc, + pub(in crate::root::serve) app: Arc, key: busbar_contract::records::VirtualKey, /// The key's data-plane token. plain: String, + /// What a config apply refreshes the served door onto (`DoorApply::apply`). + pub(in crate::root::serve) live: Arc, money: Arc, post: Arc, book: crate::root::durability::NodeBook, @@ -771,22 +773,15 @@ mod session_door { }) } - /// [`SessionDoor`]: the linked session door (the plane door that meters audio) bound through - /// the loader's one load, its needs declared on the connector over every linked transport door; - /// a signing governance book with one key (in a group whose all-time budget is `budget`, the - /// door's per-request fee one, or no group and no fee); a provider on the protocol the door - /// states a bearer default for, on a loopback far end answering the mint, reached through the - /// catalog model its section's `session.model` names; the door composed under [`PUBLIC`] with - /// its egress sealed by the composition itself, and its claims mounted on the data router. - async fn session_served(instance: &'static str, budget: Option) -> Option { - let published = Published(instance); - let (port, heard) = far_end_answering(MINTED).await; + /// The process's connector over every linked transport door, its destination guard letting + /// the loopback far end through. + fn connector() -> Arc { let judge = crate::root::connector::guard_for(&busbar_kernel::config::Destinations { block_private_addresses: false, ..Default::default() }) .expect("the guard"); - let connector = busbar_core_connector::process::build( + busbar_core_connector::process::build( || { crate::root::connector::entries( crate::LINKED_TRANSPORT_DOORS, @@ -798,7 +793,54 @@ mod session_door { Arc::new(|_| {}), busbar_core_connector::pool::PoolPosture::NONE, ) - .expect("the connector builds"); + .expect("the connector builds") + } + + /// The linked session door's Statement name (the plane door that meters audio, bound only to + /// read it); `None` in a build that links none. It holds the publishing lock, so it is called + /// outside a runtime. + pub(in crate::root::serve) fn name() -> Option { + let _one = PUBLISHING.blocking_lock(); + let instance = "session-door-name"; + let _published = Published(instance); + let dispatcher = Arc::new(Dispatcher::new(DispatchConfig::default())); + session_door(instance, &dispatcher, connector() as Arc) + .map(|plane| plane.name().to_owned()) + } + + /// [`session_keyed`] over a provider credential its file holds; the composition must succeed. + async fn session_served(instance: &'static str, budget: Option) -> Option { + let key_file = std::env::temp_dir().join(format!( + "busbar-serve-door-{instance}-{}", + std::process::id() + )); + std::fs::write(&key_file, CREDENTIAL).expect("the credential file"); + let api_key = format!("{{file: '{}'}}", key_file.display()); + let served = session_keyed(instance, budget, &api_key).await; + let _ = std::fs::remove_file(&key_file); + served.map(|s| { + s.unwrap_or_else(|e| panic!("the session door composes, its egress sealed: {e}")) + }) + } + + /// [`SessionDoor`]: the linked session door (the plane door that meters audio) bound through + /// the loader's one load, its needs declared on the connector over every linked transport door; + /// a signing governance book with one key (in a group whose all-time budget is `budget`, the + /// door's per-request fee one, or no group and no fee); a provider on the protocol the door + /// states a bearer default for, its credential the secret reference `api_key` (YAML), on a + /// loopback far end answering the mint, reached through the catalog model its section's + /// `session.model` names; the door composed under [`PUBLIC`] with its egress sealed by the + /// composition itself, and its claims mounted on the data router. `None` in a build that links + /// no session door; the composition's refusal is answered rather than panicked on: what the + /// root's boot dies on. + pub(in crate::root::serve) async fn session_keyed( + instance: &'static str, + budget: Option, + api_key: &str, + ) -> Option> { + let published = Published(instance); + let (port, heard) = far_end_answering(MINTED).await; + let connector = connector(); let dispatcher = Arc::new(Dispatcher::new(DispatchConfig::default())); let plane = session_door( instance, @@ -889,14 +931,8 @@ mod session_door { )); let one = Arc::clone(&money); - let key_file = std::env::temp_dir().join(format!( - "busbar-serve-door-{instance}-{}", - std::process::id() - )); - std::fs::write(&key_file, CREDENTIAL).expect("the credential file"); let provider: busbar_kernel::config::ProviderCfg = serde_yaml::from_str(&format!( - "{{protocol: {protocol}, base_url: 'http://127.0.0.1:{port}', api_key: {{file: '{}'}}, error_map: {{}}}}", - key_file.display() + "{{protocol: {protocol}, base_url: 'http://127.0.0.1:{port}', api_key: {api_key}, error_map: {{}}}}", )) .expect("a provider entry"); let providers = provider_routes(&std::collections::HashMap::from([( @@ -928,7 +964,7 @@ mod session_door { section_key, serde_yaml::from_str("session: {model: m-cap}").expect("a section"), ); - let mut served = compose_planes( + let mut served = match compose_planes( &[(instance.to_string(), plane)], &dispatcher, &composed_services(), @@ -940,13 +976,15 @@ mod session_door { journal: Arc::clone(&post) as Arc, }), None, - ) - .expect("the session door composes, its egress sealed"); - let _ = std::fs::remove_file(&key_file); + ) { + Ok(served) => served, + Err(refusal) => return Some(Err(refusal)), + }; assert!( served.planes[0].live.current().egress.is_some(), "the composition sealed its egress" ); + let live = Arc::clone(&served.planes[0].live); served.post = Some(Arc::clone(&post)); let app = busbar_kernel::test_support::TestApp::new().keys_chain(); let app = groups @@ -956,7 +994,7 @@ mod session_door { let doors = door_routes(served, || CARD.pin(), &[], &[]).expect("its claims mount"); let (router, _admin, _handle) = busbar_kernel::build_split_routers_serving(Arc::clone(&app), doors, 1 << 20, 0, false); - Some(SessionDoor { + Some(Ok(SessionDoor { _published: published, router, heard, @@ -964,13 +1002,19 @@ mod session_door { app, key, plain: plain.expose_secret().clone(), + live, money, post, book, - }) + })) } impl SessionDoor { + /// The keyed caller's mint through the door: its response. + pub(in crate::root::serve) async fn mint(&self) -> axum::response::Response { + send_body(&self.router, MINT, Some(&self.plain), "{}").await + } + /// The requests the governance book admitted for the key, this window. fn requests(&self) -> u64 { self.gov diff --git a/xtask/src/gates/no_deferral.rs b/xtask/src/gates/no_deferral.rs index be66810b5b..0775f180c9 100644 --- a/xtask/src/gates/no_deferral.rs +++ b/xtask/src/gates/no_deferral.rs @@ -74,11 +74,12 @@ pub const ROW_STALE_WAIVER: &str = "no-deferral:stale-waiver"; pub const ROW_STRICT_DONE: &str = "no-deferral:strict-done"; /// The denominator floor, a `const` in the gate's own module with no environment override. It is -/// pinned AT the measured count: discovery found 932 shipped source files on predev 6ca8584fc0 (it -/// was 50, about 5% of the tree), and a drop below 932 is refused as UNPROVEN until a reviewed diff +/// pinned AT the measured count: discovery found 917 shipped source files after FLIP-STREAMING +/// (#503) deleted the `busbar-voice` crate (932 on predev 6ca8584fc0; it was 50, about 5% of the +/// tree), and a drop below 917 is refused as UNPROVEN until a reviewed diff /// re-measures. The selftest plant cuts the scan set to one file under the floor and must go red. /// The only way to lower one is a reviewable source edit. -pub const DISCOVERY_FLOOR: usize = 932; +pub const DISCOVERY_FLOOR: usize = 917; const WAIVERS: &str = "scripts/no-deferral.waivers"; /// The plan every waiver's expiry is looked up in. It was `docs/design/1.6.0-TRACKER.md` until diff --git a/xtask/src/gates/population.rs b/xtask/src/gates/population.rs index eedb2f8a13..c358e6b0bf 100644 --- a/xtask/src/gates/population.rs +++ b/xtask/src/gates/population.rs @@ -17,7 +17,7 @@ //! //! * [`Population::below_floor`] — the aggregate ratchet. It is not `> 0`: a walk that collapses to //! a handful of files reports no findings and reads exactly like a clean tree. The floor is a -//! RATCHET pinned AT the measured count ([`FLOOR`], 941 on predev bd39476618), not a margin +//! RATCHET pinned AT the measured count ([`FLOOR`], 926 after FLIP-STREAMING), not a margin //! below it: one file fewer is refused until a reviewed diff re-measures. It is raised as the //! tree grows, never lowered to accommodate a scan that stopped finding things. //! * [`Population::drained`] — a crate that has a `src/` and contributed NOTHING. The floor catches @@ -33,11 +33,14 @@ use crate::ctx::{Ctx, Overlay, SourceFile, WalkSpec}; /// and added 4, and a floor above the count leaves every gate's floor row red on a clean tree. It /// was re-pinned from 941 to 942 when predev grew by one file, which the selftest plant caught, /// and back to 941 when #648 (q128-kernel-ledger) deleted `busbar-kernel-ledger/src/usage/series.rs` -/// — the one non-test file that merge removed, a reviewed removal and not a scan that went blind. A -/// drop below 941 is refused until a reviewed diff re-measures; the selftest plant +/// — the one non-test file that merge removed, a reviewed removal and not a scan that went blind. +/// It was re-pinned from 941 to 926 across 23 crates when FLIP-STREAMING (#503) deleted the +/// `busbar-voice` crate (its sessions are served through the streaming plane's door): a reviewed +/// removal of a whole crate, measured by the PR's own CI run. A drop below 926 is refused until a +/// reviewed diff re-measures; the selftest plant /// removes one file and fails if the floor sits under the count. Lowering it is how a gate stops /// reading the repository without saying so, so a diff that lowers it is the diff to refuse. -pub const FLOOR: usize = 941; +pub const FLOOR: usize = 926; /// The `.rs` under `crates/` that are not test scaffolding, plus the accounting to refuse a /// population that cannot support a verdict. diff --git a/xtask/src/gates/tracing.rs b/xtask/src/gates/tracing.rs index 3ef7c78dff..a5c1b511b0 100644 --- a/xtask/src/gates/tracing.rs +++ b/xtask/src/gates/tracing.rs @@ -44,20 +44,21 @@ pub const ROW_CLOSES: &str = "tracing:attribute-closes"; const SCAN_ROOT: &str = "crates"; const EXCLUDE_TESTS_DIR: &str = "/tests/"; -/// The denominator floor, pinned AT the measured count: the crates walk reads 971 files on this -/// branch merged with predev bd39476618, after busbar-kernel-ledger's usage/series.rs left (972 on +/// The denominator floor, pinned AT the measured count: the crates walk reads 955 files after +/// FLIP-STREAMING (#503) deleted the `busbar-voice` crate (971 on this branch merged with predev +/// bd39476618, after busbar-kernel-ledger's usage/series.rs left; 972 on /// predev 3bc48823df; 971 on predev 39374ec00e; 980 on 5e672d125d, before the egress_auth / /// auth_cache / ingress_sigv4 files left the kernel crates; it was 130 against a tree that had -/// grown past 700). A drop below 971 is refused until a reviewed diff re-measures; +/// grown past 700). A drop below 955 is refused until a reviewed diff re-measures; /// the selftest plant removes one file and fails if the floor sits under the count. It is a /// `const` here and has no environment override: the only way to lower one is a reviewable /// source edit. -const SCAN_FLOOR: usize = 971; +const SCAN_FLOOR: usize = 955; /// THE SUBJECT FLOOR (item 228). The file floor above proves the walk opened the crates; it says /// nothing about whether the thing this gate judges is still there. "Every `#[instrument]` has a /// level" is as vacuous over zero SPANS as over zero files, and the files-to-spans ratio is ~150:1 -/// (971 files, 5 attributes), so every span could leave the tree with the file floor +/// (955 files, 5 attributes), so every span could leave the tree with the file floor /// untouched. Armed at the measured count (arrival.rs 3, ingress/mod.rs 2); like the file floor it /// has no override, and lowering it is a reviewable source edit that says which span went where. const SPAN_FLOOR: usize = 5; From bce41f58550977b50bf88d569c5f9ae91aa7f48a Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:05:31 -0700 Subject: [PATCH 75/82] busbar tests: DoorReach gets catalog: None; provider_routes takes the MetadataPosture at the streaming serve test --- crates/busbar/src/root/tests/door_steps.rs | 1 + crates/busbar/src/root/tests/serve_tests.rs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/busbar/src/root/tests/door_steps.rs b/crates/busbar/src/root/tests/door_steps.rs index 5e926f5506..34f2a43796 100644 --- a/crates/busbar/src/root/tests/door_steps.rs +++ b/crates/busbar/src/root/tests/door_steps.rs @@ -963,6 +963,7 @@ fn jwt_bearer_routes( auths: std::sync::Arc::new(linked_mint_plugin(table)), conns: std::sync::Arc::new(busbar_core_connector::Connector::new()), stream_ceiling_secs: 1, + catalog: None, upgrades: Vec::new(), }; let section: serde_yaml::Value = diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index 4f9c005c07..4b9c5f218e 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -935,10 +935,10 @@ pub(super) mod session_door { "{{protocol: {protocol}, base_url: 'http://127.0.0.1:{port}', api_key: {api_key}, error_map: {{}}}}", )) .expect("a provider entry"); - let providers = provider_routes(&std::collections::HashMap::from([( - "p".to_string(), - provider, - )])); + let providers = provider_routes( + &std::collections::HashMap::from([("p".to_string(), provider)]), + &busbar_kernel::config_validate::MetadataPosture::default(), + ); let catalog: std::collections::HashMap = serde_yaml::from_str("{m-cap: {provider: p}}").expect("the models catalog"); let secrets = busbar_kernel::config::secret::SecretResolver::builtins_only(); From 4dc7c00f854ef2a18e4c1021673c8e871b747405 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 10:35:36 -0700 Subject: [PATCH 76/82] kind-isolation: the kernel names no deleted plane crate; standing lowered to the merged tree - busbar-kernel's plane_host names `busbar-voice` nowhere: the D3 witness's pointer to the deleted crate's topology tests and the rewrite-tap test's `busbar_voice::mount` path were stale once the crate went, and with it gone the three mentions read as plane vocabulary (busbar-kernel x plane 1176 against its 1173 ceiling, predev 1617e9f019 measures 1173); now 1173 - qa/kind-isolation.standing.txt written by `gate kind-isolation --posture --write-standing`: deps legacy -> plane 2 -> 1 (the busbar-voice -> busbar-plane-streaming edge left with the crate), and the two test-deps rows no finding carries struck (stale on predev too) --- crates/busbar-kernel/src/plane_host/mod.rs | 4 ++-- crates/busbar-kernel/src/plane_host/tests/mod_tests.rs | 7 ++++--- qa/kind-isolation.standing.txt | 4 +--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/busbar-kernel/src/plane_host/mod.rs b/crates/busbar-kernel/src/plane_host/mod.rs index 01f6797d74..ee3b8c6d2b 100644 --- a/crates/busbar-kernel/src/plane_host/mod.rs +++ b/crates/busbar-kernel/src/plane_host/mod.rs @@ -2938,8 +2938,8 @@ pub trait ContainerGateSink: PlaneSlots { } // ───────────────────────────────────────────────────────────────────────────────────────────── -// D3 WITNESS — the gauntlet siblings COEXIST and share ONE `admit_open` gate. (That `begin_session` -// actually CALLS `run_gauntlet_session` at its call site is pinned in busbar-voice's topology tests.) +// D3 WITNESS — the gauntlet siblings COEXIST and share ONE `admit_open` gate. (That a session +// open actually CALLS `run_gauntlet_session` at its call site is the serving door's to pin.) // ───────────────────────────────────────────────────────────────────────────────────────────── #[cfg(test)] #[path = "tests/gauntlet_session_tests.rs"] diff --git a/crates/busbar-kernel/src/plane_host/tests/mod_tests.rs b/crates/busbar-kernel/src/plane_host/tests/mod_tests.rs index 7b24be6b9a..8d13aff4c5 100644 --- a/crates/busbar-kernel/src/plane_host/tests/mod_tests.rs +++ b/crates/busbar-kernel/src/plane_host/tests/mod_tests.rs @@ -660,9 +660,10 @@ fn stale_host_ctx_through_a_real_vtable_slot_fails_closed() { /// 1. It admits ANY JSON object as the replacement. It does not know, and cannot check, the TYPE the /// receiving plane will decode into. For MCP and A2A the target is `serde_json::Value`, so bytes /// core commits always parse and their "output I cannot read back" arms are unreachable through -/// this host. For `busbar-voice` the target is `SessionConfig`, which rejects `{"voice": 7}` as a -/// type mismatch — so THAT plane's arm is genuinely reachable through core's real host, and its -/// refusal (`busbar_voice::mount::committed_session_config`) is a live fix, not a dormant one. +/// this host. For a session plane the target is a typed session configuration, which rejects +/// `{"voice": 7}` as a type mismatch — so THAT plane's arm is genuinely reachable through +/// core's real host, and its refusal (its door's `committed_session_config`) is a live fix, +/// not a dormant one. /// 2. The commit is a WHOLESALE REPLACEMENT, not a merge. A hook that names one field produces /// `arguments` carrying ONLY that field — every other key the caller (or the plane's own lock) /// had is gone from the committed bytes. A plane whose target type defaults its absent fields diff --git a/qa/kind-isolation.standing.txt b/qa/kind-isolation.standing.txt index 33d39201af..ea422af5a8 100644 --- a/qa/kind-isolation.standing.txt +++ b/qa/kind-isolation.standing.txt @@ -2,7 +2,5 @@ # One line per finding: \t\t\t
. The `--posture` run excuses exactly these, # at or below these figures, and reds anything new or higher. Written ONLY by # `cargo xtask gate --posture --write-standing`, which strikes and lowers and never adds. -kind-isolation:deps unlisted-dep-edge legacy -> plane 2 +kind-isolation:deps unlisted-dep-edge legacy -> plane 1 kind-isolation:deps unlisted-transitional crates/busbar-llm 1 -kind-isolation:test-deps unlisted-dep-edge cleanliness -> legacy 3 -kind-isolation:test-deps unlisted-dep-edge legacy -> cleanliness 2 From d034e7fa6d674338966ea222eeb1ca2f9cf47463 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:35:06 -0700 Subject: [PATCH 77/82] streaming door: an authentication refusal reads in the dialects' vendor terms (streams|mint|no-provider) The door rendered every kernel refusal as the kernel's own text, so the mint door's 401 on a `streams: {}` deployment (no session model: the door steps refuse the unnamed keyed route at authenticate) answered `{"message":"unauthenticated","code":null}` where 1.6.0-pre/1.5.5 answered the bad-key envelope `{"message":"Incorrect API key provided.","code":"invalid_api_key"}`. The oracle cell streams|mint|no-provider was red at the branch head bce41f5855 and on the merged tree alike. `refusal` now renders by the reason's class (the one classification, abi::plane::class_of): an Unauthenticated-class reason answers with the dialects' vendor copy and code, as the llm plane's `kernel_refusal` and the kernel's residual `unauthorized_response` do; every other reason keeps `refusal_body`, the kernel's status and text (spec Part 3 "Refusals", BUSBAR-1.6.0.md:2655-2662: a refusal with no unit is rendered by the plane's path rule; FOLD-LLM2 l.4878: = the 1.5.5 residual). Proof: conformance.rs an_authentication_refusal_reads_in_the_dialects_vendor_terms, both ways, with its RED arm and a hook-veto 401 control that keeps its words; the refusal-collapse scan reads the new renderer too. --- crates/busbar-plane-streaming/src/door.rs | 43 +++++++-- .../tests/conformance.rs | 93 +++++++++++++++++++ xtask/tests/p_item_refusal_reason_collapse.rs | 2 +- 3 files changed, 130 insertions(+), 8 deletions(-) diff --git a/crates/busbar-plane-streaming/src/door.rs b/crates/busbar-plane-streaming/src/door.rs index 28d7c4b19f..7d9a018024 100644 --- a/crates/busbar-plane-streaming/src/door.rs +++ b/crates/busbar-plane-streaming/src/door.rs @@ -61,11 +61,11 @@ use busbar_contract::abi::mechanism::lifecycle::{ }; use busbar_contract::abi::mechanism::ticket::Ticket; use busbar_contract::abi::plane::{ - ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, OpClass, OutField, - PlaneCancelIn, PlaneCancelOut, PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, - PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, - ServeIn, ServeOut, UnitCount, CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, EMIT_DONE, - EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, INGRESS_DUPLEX_SESSION, + class_of, reason_of, ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, + OpClass, OutField, PlaneCancelIn, PlaneCancelOut, PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, + PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, ProjectOut, RefusalClass, + RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, CANCEL_FAILED, CLAIM_EXACT, CLAIM_OPEN, + EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, INGRESS_DUPLEX_SESSION, INGRESS_REQUEST_RESPONSE, PIECE_HAS_STATUS, PIECE_LAST, PIECE_OUT_TEXT, ROUTE_DIRECT, SHAPE_PIECEWISE, SPAN_ABSENT, UNITS_ESTIMATED, UNITS_REPORTED, }; @@ -1109,6 +1109,35 @@ slot!(OnPiece, OnPieceIn, OnPieceOut, |instance, input, out| { /// as the error's type, its text as the message. #[must_use] pub fn refusal_body(status: u16, text: &str) -> Vec { + error_envelope(status, text, None) +} + +/// The message the realtime dialects answer a bad or missing credential with: the vendor's own copy, +/// as 1.5.5's residual answered a refused credential on these targets. +pub const AUTH_FAILURE_MESSAGE: &str = "Incorrect API key provided."; + +/// The error code the realtime dialects answer a bad or missing credential with, beside +/// [`AUTH_FAILURE_MESSAGE`]. +pub const AUTH_FAILURE_CODE: &str = "invalid_api_key"; + +/// A refusal the kernel hands the door, rendered by its reason's class (the one classification, +/// `busbar_contract::abi::plane`): an authentication refusal reads in the dialects' own vendor terms +/// ([`AUTH_FAILURE_MESSAGE`], [`AUTH_FAILURE_CODE`]), never the kernel's word, which would be a +/// busbar tell; every other refusal is [`refusal_body`], the kernel's status and text. A refusal with +/// no unit (the kernel authenticates before `arrive`) is the 1.5.5 residual's answer for the target. +#[must_use] +pub fn refusal_render(status: u16, reason: u32, text: &str) -> Vec { + let authentication = + reason_of(reason).is_some_and(|r| class_of(r) == RefusalClass::Unauthenticated); + if authentication { + error_envelope(status, AUTH_FAILURE_MESSAGE, Some(AUTH_FAILURE_CODE)) + } else { + refusal_body(status, text) + } +} + +/// The OpenAI REST error envelope: `status` read as the error's type, `message` and `code` as given. +fn error_envelope(status: u16, message: &str, code: Option<&str>) -> Vec { let kind = match status { 401 => "authentication_error", 403 => "permission_error", @@ -1118,7 +1147,7 @@ pub fn refusal_body(status: u16, text: &str) -> Vec { _ => "server_error", }; serde_json::to_vec(&serde_json::json!({ - "error": {"message": text, "type": kind, "param": null, "code": null} + "error": {"message": message, "type": kind, "param": null, "code": code} })) .unwrap_or_default() } @@ -1134,7 +1163,7 @@ slot!(Refusal, RefusalIn, RefusalOut, |_, input, out| { let given = input.get(); let status = u16::try_from(given.status).unwrap_or(0); let text = input.field(|i| &i.text).as_str().unwrap_or_default(); - let body = refusal_body(status, text); + let body = refusal_render(status, given.reason, text); let (mut reply, mut fields, mut arena) = (input.reply_buf(), input.fields_buf(), input.arena_buf()); reply.extend(&body); diff --git a/crates/busbar-plane-streaming/tests/conformance.rs b/crates/busbar-plane-streaming/tests/conformance.rs index d00fa6ae12..4446405229 100644 --- a/crates/busbar-plane-streaming/tests/conformance.rs +++ b/crates/busbar-plane-streaming/tests/conformance.rs @@ -911,3 +911,96 @@ fn a_refusal_is_rendered_in_the_dialects_error_shape() { assert_eq!(body["error"]["message"], "no sessions today"); } } + +/// The kernel's refusal of `reason` at `status`, worded `words`, for a unit-less request on +/// `target`, rendered by the door's `refusal`: the outcome and the body it wrote. +fn refused( + p: &Plugin, + status: u32, + reason: busbar_contract::caps::ReasonCode, + words: &'static str, + target: &'static str, +) -> (Outcome, String) { + use busbar_contract::abi::plane::{reason_code, RefusalIn, RefusalOut, REFUSAL_KERNEL}; + let mut reply = vec![0u8; 1024]; + let mut fields: Vec = vec![z(); 4]; + let mut arena = vec![0u8; 256]; + let mut f: Frame = Frame::new(z(), z()); + (f.input.head, f.out.head) = (in_head(), out_head()); + f.input.cause = REFUSAL_KERNEL; + f.input.status = status; + f.input.reason = reason_code(reason); + f.input.text = text(words); + f.input.target = text(target); + (f.input.reply_buf, f.input.reply_cap) = (reply.as_mut_ptr(), reply.len()); + (f.input.fields_buf, f.input.fields_cap) = (fields.as_mut_ptr(), fields.len()); + (f.input.arena_buf, f.input.arena_cap) = (arena.as_mut_ptr(), arena.len()); + let c = p.call(slot::REFUSAL, &mut f); + let written = usize::try_from(f.out.reply_written).expect("a length"); + ( + c.outcome, + String::from_utf8_lossy(&reply[..written.min(reply.len())]).into_owned(), + ) +} + +/// AN AUTHENTICATION REFUSAL READS IN THE DIALECTS' OWN VENDOR TERMS (spec Part 3, "Refusals": a +/// refusal with no unit is rendered by the plane's path rule, the 1.5.5 residual's answer; oracle +/// cell `streams|mint|no-provider`): the kernel's `Unauthenticated` on the mint door answers as a bad +/// key does at the vendor, never with the kernel's own word, which is a busbar tell. Every other +/// reason keeps the kernel's text, a 401 included (a hook's veto states its own). Both ways. +/// +/// RED ARM: the kernel's word rendered as the message (the door before this rule) is not the +/// vendor's answer. +#[test] +fn an_authentication_refusal_reads_in_the_dialects_vendor_terms() { + use busbar_contract::caps::ReasonCode; + let vendor = serde_json::json!({"error": { + "message": door::AUTH_FAILURE_MESSAGE, + "type": "authentication_error", + "param": null, + "code": door::AUTH_FAILURE_CODE, + }}); + assert_eq!(door::AUTH_FAILURE_MESSAGE, "Incorrect API key provided."); + assert_eq!(door::AUTH_FAILURE_CODE, "invalid_api_key"); + let kernel_word: serde_json::Value = + serde_json::from_slice(&door::refusal_body(401, "unauthenticated")).expect("JSON"); + assert_ne!( + kernel_word, vendor, + "RED ARM: the kernel's word is not the vendor's answer" + ); + + let d = Dispatcher::new(DispatchConfig::default()); + let mut doors = vec![linked(&d)]; + doors.extend(dropped(&d)); + for p in &doors { + assert_eq!(open(p, 1, b"{}", Some(PUBLIC)).0, Outcome::Ready); + let (outcome, body) = refused( + p, + 401, + ReasonCode::Unauthenticated, + "unauthenticated", + "/v1/realtime/client_secrets", + ); + assert_eq!(outcome, Outcome::Ready, "{body}"); + let body: serde_json::Value = serde_json::from_str(&body).expect("JSON"); + assert_eq!( + body, vendor, + "the mint door's 401 is the vendor's bad-key answer" + ); + + let (outcome, body) = refused( + p, + 401, + ReasonCode::HookVeto, + "the hook says no", + "/v1/realtime/client_secrets", + ); + assert_eq!(outcome, Outcome::Ready, "{body}"); + let body: serde_json::Value = serde_json::from_str(&body).expect("JSON"); + assert_eq!( + body["error"]["message"], "the hook says no", + "a veto keeps its words" + ); + assert_eq!(body["error"]["code"], serde_json::Value::Null); + } +} diff --git a/xtask/tests/p_item_refusal_reason_collapse.rs b/xtask/tests/p_item_refusal_reason_collapse.rs index bc37b550b8..119e81879a 100644 --- a/xtask/tests/p_item_refusal_reason_collapse.rs +++ b/xtask/tests/p_item_refusal_reason_collapse.rs @@ -42,7 +42,7 @@ const RENDERERS: &[(&str, &[&str])] = &[ ("crates/busbar-plane-a2a/src/plane.rs", &["refusal_render"]), ( "crates/busbar-plane-streaming/src/door.rs", - &["refusal_body"], + &["refusal_body", "refusal_render"], ), ( "crates/busbar/src/root/units_admin/admin_mount.rs", From 7bb3dae8d168fe55eb6dc9c6706536da38416a1e Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:41:06 -0700 Subject: [PATCH 78/82] root tests: the session door's cells are a cfg on the build, never a skip (#712's no-silent-skip); the collapse plant keeps the door's other renderer - crates/busbar `linked-section`: plane-streaming = "streams", so the build sets `linked_section_streams` where the door that declares `streams` is linked (held to the door's Statement by root::linked's test, as the tools and decisions rows are) - serve_tests `session_door` and secret_source's two session-door cells are compiled under that cfg and fail, never skip, when the door is not found (no_silent_skip named the four `skip:` prints) - p_item_refusal_reason_collapse: the plant puts `refusal_body` back the old way inside the door's real source, so `refusal_render` is still read (and clean) and the plant's two findings stay exact --- crates/busbar/Cargo.toml | 1 + crates/busbar/src/root/tests/secret_source.rs | 23 +++++++++++-------- crates/busbar/src/root/tests/serve_tests.rs | 21 +++++++++-------- xtask/tests/p_item_refusal_reason_collapse.rs | 12 ++++++++-- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index c5cfeda997..be71ac3d2f 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -455,6 +455,7 @@ plane-mcp-door = "busbar_plane_mcp::DECLARES" [package.metadata.busbar.linked-section] plane-mcp-door = "tools" plane-decisions = "decisions" +plane-streaming = "streams" # The key an `auths` row answers on the auth axis — the name its signed tarball's manifest states. [package.metadata.busbar.linked-name] diff --git a/crates/busbar/src/root/tests/secret_source.rs b/crates/busbar/src/root/tests/secret_source.rs index 4fa141c811..66de1db48c 100644 --- a/crates/busbar/src/root/tests/secret_source.rs +++ b/crates/busbar/src/root/tests/secret_source.rs @@ -215,11 +215,16 @@ fn statement_name(door: busbar_contract::abi::mechanism::door::DoorFn) -> String /// The doors this file drives, by Statement name. fn driven() -> Vec { + #[cfg_attr( + not(any(linked_section_decisions, linked_section_streams)), + allow(unused_mut) + )] let mut names = vec![statement_name( crate::root::door_steps::tests::tool_door::line_door(), )]; #[cfg(linked_section_decisions)] names.push(super::door_tests::decisions_name()); + #[cfg(linked_section_streams)] names.extend(super::door_tests::session_door::name()); names } @@ -432,6 +437,7 @@ async fn decisions_a_provider_key_gone_by_an_apply_reaches_the_caller_without_it /// THE SESSION DOOR, COMPOSE TIME: a provider whose `api_key` does not resolve refuses the boot /// with the operator's text, which names the source; nothing is served to a caller. +#[cfg(linked_section_streams)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn session_door_an_unresolved_provider_key_refuses_the_boot_naming_its_source() { let _one = PUBLISHING.lock().await; @@ -439,16 +445,13 @@ async fn session_door_an_unresolved_provider_key_refuses_the_boot_naming_its_sou let path = missing(instance); let secret = SecretRef::file(path.clone()); refused_naming_its_source(&secret); - let Some(composed) = super::door_tests::session_door::session_keyed( + let composed = super::door_tests::session_door::session_keyed( instance, None, &format!("{{file: '{path}'}}"), ) .await - else { - eprintln!("skip: this build links no session door"); - return; - }; + .expect("the build links the session door"); let Err(refusal) = composed else { panic!("a provider key that does not resolve refuses the composition"); }; @@ -462,6 +465,7 @@ async fn session_door_an_unresolved_provider_key_refuses_the_boot_naming_its_sou /// THE SESSION DOOR, ACROSS AN APPLY: a provider served while its `api_key` file was there, the /// file then gone and the configuration applied: the door keeps the generation it serves, and the /// caller's mint is served by it, its render clean of the source. +#[cfg(linked_section_streams)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn session_door_a_provider_key_gone_by_an_apply_reaches_the_caller_without_its_source() { let _one = PUBLISHING.lock().await; @@ -469,16 +473,15 @@ async fn session_door_a_provider_key_gone_by_an_apply_reaches_the_caller_without let path = missing(instance); std::fs::write(&path, "sk-secret-source-test").expect("the credential file"); let secret = SecretRef::file(path.clone()); - let Some(composed) = super::door_tests::session_door::session_keyed( + let composed = super::door_tests::session_door::session_keyed( instance, None, &format!("{{file: '{path}'}}"), ) - .await - else { + .await; + let Some(composed) = composed else { let _ = std::fs::remove_file(&path); - eprintln!("skip: this build links no session door"); - return; + panic!("the build links the session door"); }; let serving = composed.expect("the door composes"); std::fs::remove_file(&path).expect("the credential file goes"); diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index 4b9c5f218e..d219725983 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -594,8 +594,11 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { /// production composes it — the browser's ephemeral-secret mint, a keyed caller, the door's DIRECT /// route to the top-level catalog model its section's `session.model` names, the provider's own /// credential presented by the auth plugin, the caller's spend posted against the key that -/// presented it. The door is found among the linked plane doors by the audio class it meters, so a -/// build that links none skips. The secret-source scan (`secret_source.rs`) drives the same door. +/// presented it. The door is found among the linked plane doors by the audio class it meters; the +/// module is compiled where the build links the door that declares `streams` +/// (`linked_section_streams`), and there a door not found is a failure, never a skip. The +/// secret-source scan (`secret_source.rs`) drives the same door. +#[cfg(linked_section_streams)] pub(super) mod session_door { use std::collections::BTreeMap; use std::sync::Arc; @@ -1046,10 +1049,9 @@ pub(super) mod session_door { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn a_session_door_mint_is_served_under_the_providers_credential() { let _one = PUBLISHING.lock().await; - let Some(mut s) = session_served("serve-door-session", None).await else { - eprintln!("skip: this build links no session door"); - return; - }; + let mut s = session_served("serve-door-session", None) + .await + .expect("the build links the session door"); let refused = send_body(&s.router, MINT, None, "{}").await; assert_eq!( @@ -1105,10 +1107,9 @@ pub(super) mod session_door { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn a_spent_key_is_refused_at_the_session_door_before_any_dial() { let _one = PUBLISHING.lock().await; - let Some(mut s) = session_served("serve-door-session-spent", Some(0)).await else { - eprintln!("skip: this build links no session door"); - return; - }; + let mut s = session_served("serve-door-session-spent", Some(0)) + .await + .expect("the build links the session door"); let response = send_body(&s.router, MINT, Some(&s.plain), "{}").await; assert_eq!( response.status().as_u16(), diff --git a/xtask/tests/p_item_refusal_reason_collapse.rs b/xtask/tests/p_item_refusal_reason_collapse.rs index 119e81879a..3b92202f95 100644 --- a/xtask/tests/p_item_refusal_reason_collapse.rs +++ b/xtask/tests/p_item_refusal_reason_collapse.rs @@ -223,11 +223,19 @@ fn p_item_refusal_reason_collapse_the_scan_catches_a_reason_match() { } } "#; + // The plant puts `refusal_body` back the old way inside the door's real source, so every other + // renderer the scan names in that file (`refusal_render`) is still read, and still clean. let planted = findings(&reasons, &|f| { + let real = read(f); if f == "crates/busbar-plane-streaming/src/door.rs" { - old.to_string() + let start = real + .find("fn refusal_body(") + .expect("the door's refusal_body"); + let body = body_of(&real[start..], "refusal_body").expect("its body"); + let end = start + real[start..].find(body).expect("the body in place") + body.len(); + format!("{}{old}{}", &real[..start], &real[end..]) } else { - read(f) + real } }); assert_eq!( From 19c618042cae95238bc553c03171b8f24de9f88f Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:51:39 -0700 Subject: [PATCH 79/82] root tests: the session door's cells sit under one cfg each file (secret_source's two in a gated module), the cfg named once per site --- crates/busbar/src/root/tests/secret_source.rs | 135 +++++++++--------- crates/busbar/src/root/tests/serve_tests.rs | 5 +- 2 files changed, 71 insertions(+), 69 deletions(-) diff --git a/crates/busbar/src/root/tests/secret_source.rs b/crates/busbar/src/root/tests/secret_source.rs index 66de1db48c..65063f3401 100644 --- a/crates/busbar/src/root/tests/secret_source.rs +++ b/crates/busbar/src/root/tests/secret_source.rs @@ -215,10 +215,8 @@ fn statement_name(door: busbar_contract::abi::mechanism::door::DoorFn) -> String /// The doors this file drives, by Statement name. fn driven() -> Vec { - #[cfg_attr( - not(any(linked_section_decisions, linked_section_streams)), - allow(unused_mut) - )] + // Mutated only where the build links a door beyond the line door. + #[allow(unused_mut)] let mut names = vec![statement_name( crate::root::door_steps::tests::tool_door::line_door(), )]; @@ -435,71 +433,76 @@ async fn decisions_a_provider_key_gone_by_an_apply_reaches_the_caller_without_it assert_clean(instance, status, &headers, &body, &secret); } -/// THE SESSION DOOR, COMPOSE TIME: a provider whose `api_key` does not resolve refuses the boot -/// with the operator's text, which names the source; nothing is served to a caller. +/// THE SESSION DOOR'S CELLS, compiled where the build links that door: a door not found is a +/// failure, never a skip. #[cfg(linked_section_streams)] -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn session_door_an_unresolved_provider_key_refuses_the_boot_naming_its_source() { - let _one = PUBLISHING.lock().await; - let instance = "session-key-compose"; - let path = missing(instance); - let secret = SecretRef::file(path.clone()); - refused_naming_its_source(&secret); - let composed = super::door_tests::session_door::session_keyed( - instance, - None, - &format!("{{file: '{path}'}}"), - ) - .await - .expect("the build links the session door"); - let Err(refusal) = composed else { - panic!("a provider key that does not resolve refuses the composition"); - }; - println!("SECRET-SOURCE {instance}: compose refused (operator): {refusal}"); - assert!( - !source_in(&refusal, &secret).is_empty(), - "the operator's refusal names the source: {refusal}" - ); -} +mod session_door_cells { + use super::*; -/// THE SESSION DOOR, ACROSS AN APPLY: a provider served while its `api_key` file was there, the -/// file then gone and the configuration applied: the door keeps the generation it serves, and the -/// caller's mint is served by it, its render clean of the source. -#[cfg(linked_section_streams)] -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn session_door_a_provider_key_gone_by_an_apply_reaches_the_caller_without_its_source() { - let _one = PUBLISHING.lock().await; - let instance = "session-key-apply"; - let path = missing(instance); - std::fs::write(&path, "sk-secret-source-test").expect("the credential file"); - let secret = SecretRef::file(path.clone()); - let composed = super::door_tests::session_door::session_keyed( - instance, - None, - &format!("{{file: '{path}'}}"), - ) - .await; - let Some(composed) = composed else { - let _ = std::fs::remove_file(&path); - panic!("the build links the session door"); - }; - let serving = composed.expect("the door composes"); - std::fs::remove_file(&path).expect("the credential file goes"); - refused_naming_its_source(&secret); - let before = serving.live.current().generation; - serving.live.apply(&serving.app); - assert_eq!( - serving.live.current().generation, - before, - "the door keeps the generation it serves" - ); - let response = serving.mint().await; - let status = response.status(); - let headers = response.headers().clone(); - let body = axum::body::to_bytes(response.into_body(), 1 << 20) + /// THE SESSION DOOR, COMPOSE TIME: a provider whose `api_key` does not resolve refuses the boot + /// with the operator's text, which names the source; nothing is served to a caller. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn session_door_an_unresolved_provider_key_refuses_the_boot_naming_its_source() { + let _one = PUBLISHING.lock().await; + let instance = "session-key-compose"; + let path = missing(instance); + let secret = SecretRef::file(path.clone()); + refused_naming_its_source(&secret); + let composed = super::super::door_tests::session_door::session_keyed( + instance, + None, + &format!("{{file: '{path}'}}"), + ) .await - .expect("the body reads"); - assert_clean(instance, status, &headers, &body, &secret); + .expect("the build links the session door"); + let Err(refusal) = composed else { + panic!("a provider key that does not resolve refuses the composition"); + }; + println!("SECRET-SOURCE {instance}: compose refused (operator): {refusal}"); + assert!( + !source_in(&refusal, &secret).is_empty(), + "the operator's refusal names the source: {refusal}" + ); + } + + /// THE SESSION DOOR, ACROSS AN APPLY: a provider served while its `api_key` file was there, the + /// file then gone and the configuration applied: the door keeps the generation it serves, and the + /// caller's mint is served by it, its render clean of the source. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn session_door_a_provider_key_gone_by_an_apply_reaches_the_caller_without_its_source() { + let _one = PUBLISHING.lock().await; + let instance = "session-key-apply"; + let path = missing(instance); + std::fs::write(&path, "sk-secret-source-test").expect("the credential file"); + let secret = SecretRef::file(path.clone()); + let composed = super::super::door_tests::session_door::session_keyed( + instance, + None, + &format!("{{file: '{path}'}}"), + ) + .await; + let Some(composed) = composed else { + let _ = std::fs::remove_file(&path); + panic!("the build links the session door"); + }; + let serving = composed.expect("the door composes"); + std::fs::remove_file(&path).expect("the credential file goes"); + refused_naming_its_source(&secret); + let before = serving.live.current().generation; + serving.live.apply(&serving.app); + assert_eq!( + serving.live.current().generation, + before, + "the door keeps the generation it serves" + ); + let response = serving.mint().await; + let status = response.status(); + let headers = response.headers().clone(); + let body = axum::body::to_bytes(response.into_body(), 1 << 20) + .await + .expect("the body reads"); + assert_clean(instance, status, &headers, &body, &secret); + } } // The scan's fixed-class half and its kept RED arm (status row 127). diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index d219725983..e3d4878896 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -595,9 +595,8 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { /// route to the top-level catalog model its section's `session.model` names, the provider's own /// credential presented by the auth plugin, the caller's spend posted against the key that /// presented it. The door is found among the linked plane doors by the audio class it meters; the -/// module is compiled where the build links the door that declares `streams` -/// (`linked_section_streams`), and there a door not found is a failure, never a skip. The -/// secret-source scan (`secret_source.rs`) drives the same door. +/// module is compiled where the build links that door, and there a door not found is a failure, +/// never a skip. The secret-source scan (`secret_source.rs`) drives the same door. #[cfg(linked_section_streams)] pub(super) mod session_door { use std::collections::BTreeMap; From 15cf97e8e06f512c2ceb13362d55ace521718c23 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:59:32 -0700 Subject: [PATCH 80/82] kind-isolation: busbar x plane pinned at the merged tree's 808 (805 + the session door's three cfg sites the no-silent-skip rule asks for), under the 822 row and predev's 824 --- qa/kind-isolation.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/kind-isolation.toml b/qa/kind-isolation.toml index 91e9f76826..fa3023ec15 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -1558,8 +1558,8 @@ cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain [[law0]] crate = "busbar" axis = "plane" -count = "805" -cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; RE-ARMED 798 -> 822 (Law 9, BUSBAR-1.6.0.md: it pins its ceiling at TODAY'S MEASURED VALUE): the row went live on predev at dc83ff1553 (#597) and measured 826 there; the 798 was f830faa691's, nine hours older than the arming, so the row armed red. 822 is predev 9c0a394771's measure, at or under the arming value; LOWERED 822 -> 805 by FLIP-STREAMING (Law 9: the ceiling ratchets down): predev measured 829 (seven over the row), and the flip drained 24 from the root (the deleted plane's rows leave the root's claim fixtures, and its session-door cells, registry pins and test comments name no plane)" +count = "808" +cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; RE-ARMED 798 -> 822 (Law 9, BUSBAR-1.6.0.md: it pins its ceiling at TODAY'S MEASURED VALUE): the row went live on predev at dc83ff1553 (#597) and measured 826 there; the 798 was f830faa691's, nine hours older than the arming, so the row armed red. 822 is predev 9c0a394771's measure, at or under the arming value; LOWERED 822 -> 805 by FLIP-STREAMING (Law 9: the ceiling ratchets down): predev measured 829 (seven over the row), and the flip drained 24 from the root (the deleted plane's rows leave the root's claim fixtures, and its session-door cells, registry pins and test comments name no plane); RE-MEASURED 805 -> 808 on FLIP-STREAMING merged with predev 62fdbdf3bc, still a lowering of the 822 row and under predev's own 824: the root-crate no-silent-skip rule (#712) makes the session door's root cells a cfg naming the subject, never a skip, and `linked_section_streams` is that cfg at its three sites (serve_tests' session_door module, secret_source's session_door_cells module and its driven() roster), as `linked_section_decisions` is the decisions door's" [[law0]] crate = "busbar" From a85c40236fbd672c50e5e9d76d78b92a5a602d6e Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 13 Oct 2026 05:45:17 -0700 Subject: [PATCH 81/82] session door consumers read predev's three-field dialect_auth (dialect, style, params): serve_tests' bearer-dialect finder and the voice-conformance harness's credential leg --- crates/busbar/src/root/tests/serve_tests.rs | 4 ++-- testing/voice-conformance/harness/src/composition.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index 952af6669f..c04c8a17ed 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -861,8 +861,8 @@ pub(super) mod session_door { .dialect_auth .iter() .rev() - .filter(|(_, style)| *style == "bearer") - .find_map(|(d, _)| { + .filter(|(_, style, _)| *style == "bearer") + .find_map(|(d, _, _)| { usize::try_from(*d) .ok() .and_then(|i| served.dialects.get(i).copied()) diff --git a/testing/voice-conformance/harness/src/composition.rs b/testing/voice-conformance/harness/src/composition.rs index a9412f4557..5fb080afba 100644 --- a/testing/voice-conformance/harness/src/composition.rs +++ b/testing/voice-conformance/harness/src/composition.rs @@ -162,7 +162,7 @@ fn cred_script(p: &Plugin) -> CredObs { let dialect_auth = served .dialect_auth .iter() - .map(|(d, style)| { + .map(|(d, style, _)| { let name = served.dialects.get(*d as usize).copied().unwrap_or("?"); format!("{name}={style}") }) From f05708d65fbd78fa4d6be6184db3cd656645b0c3 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:27:43 -0700 Subject: [PATCH 82/82] planes: streaming joins the kind-only roster, so a vanished streaming door row is PLANE-ROOT-MISSING (#503, row 88) FLIP-STREAMING took voice off PLANE_KEYS with crates/busbar-voice. The streaming plane is now located only by its plane-streaming door row (door_planes -> busbar-plane-streaming). Off both rosters, losing that row's plane-door axis would have dropped the plane from structure-lint's plane-roots roster in silence. mcp is kept on the roster by PLANE_KEYS_KIND_ONLY, and #787 puts a2a there the same way; streaming now joins it, in the Rust list and its shell twin (pinned equal by xtask/tests/infra.rs). RED: the structure-lint selftest takes the plane-door axis off plane-streaming's manifest row and requires PLANE-ROOT-MISSING naming streaming. With streaming off PLANE_KEYS_KIND_ONLY the plane leaves the roster and the case reds. --- scripts/plane-keys.sh | 5 +++-- xtask/src/gates/structure_lint/selftest.rs | 26 ++++++++++++++++++++++ xtask/src/planes.rs | 6 +++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/scripts/plane-keys.sh b/scripts/plane-keys.sh index ed1f19ebf4..bbcfa1626c 100644 --- a/scripts/plane-keys.sh +++ b/scripts/plane-keys.sh @@ -57,10 +57,11 @@ PLANE_KEYS="llm a2a" # PLANE_KEYS_KIND_ONLY — plane keys whose plane has NO legacy-engine crate any more and lives only in -# its plane-kind crate `crates/busbar-plane-` (P3 DEL-MCP, ARCHITECT 2026-10-05: `mcp`). Never a +# its plane-kind crate `crates/busbar-plane-` (P3 DEL-MCP, ARCHITECT 2026-10-05: `mcp`; +# FLIP-STREAMING: `streaming`, whose legacy crate was `crates/busbar-voice`). Never a # `crates/busbar-` root; STILL a plane noun, so a needle gate that bans the other planes' keys # keeps banning it (`plane_noun_keys_other`). Leaving PLANE_KEYS must not let the word in. -PLANE_KEYS_KIND_ONLY="mcp" +PLANE_KEYS_KIND_ONLY="mcp streaming" # The protocol subset: every plane key except `llm`. Derived from PLANE_KEYS so adding a plane in # one place flows here automatically. diff --git a/xtask/src/gates/structure_lint/selftest.rs b/xtask/src/gates/structure_lint/selftest.rs index 4d1e4f28a0..2b640a10f6 100644 --- a/xtask/src/gates/structure_lint/selftest.rs +++ b/xtask/src/gates/structure_lint/selftest.rs @@ -326,6 +326,32 @@ pub fn run<'a>(gate: &'a StructureLintGate, cx: &'a Ctx) -> Report<'a> { ), } + // The same for STREAMING since FLIP-STREAMING: `voice` left the legacy roster with + // `crates/busbar-voice`, and the plane's door row is all that locates it. Without `streaming` on + // the kind-only roster, taking that row's `plane-door` axis off would drop the plane from the + // roster in silence. + match cx.read(&manifest_rel) { + Ok(text) if text.contains("plane-streaming = \"plane-door\"") => { + let mut ov = Overlay::new(); + ov.set( + &manifest_rel, + text.replace("plane-streaming = \"plane-door\"", "plane-streaming = \"\""), + ); + report.push(tree_case( + cx, + gate, + "the streaming plane whose door row left the manifest is refused, not silently unscanned", + &[roots::ROW_PLANE_ROOTS], + ov, + &["PLANE-ROOT-MISSING", "streaming"], + )); + } + _ => report.note_infra_failure( + "structure-lint selftest: the manifest carries no `plane-streaming = \"plane-door\"` row, \ + so the streaming plane's plane-root rule has nothing to plant against", + ), + } + // A PLANE THE OLD CONSTANT NEVER NAMED (item 224). `PLANES` was `["mcp", "a2a"]`, so a second // home for voice could not be reported: the row "every plane resolves to exactly one home" was // true of the two planes it had been given. A second declaration of voice, in a directory named diff --git a/xtask/src/planes.rs b/xtask/src/planes.rs index fd1e4afc32..9c0737a50f 100644 --- a/xtask/src/planes.rs +++ b/xtask/src/planes.rs @@ -47,8 +47,10 @@ pub const PLANE_KEYS: [&str; 2] = ["llm", "a2a"]; /// `busbar-plane-` and is located by its door row ([`door_planes`]). Twin of /// `scripts/plane-keys.sh`'s `PLANE_KEYS_KIND_ONLY`. A roster that must notice a plane VANISHING /// (structure-lint's plane-roots row) reads these beside [`PLANE_KEYS`]: leaving the legacy roster -/// must not take a plane off the roster that refuses an unlocatable plane. -pub const PLANE_KEYS_KIND_ONLY: [&str; 1] = ["mcp"]; +/// must not take a plane off the roster that refuses an unlocatable plane. `streaming` joined when +/// FLIP-STREAMING took `voice` off [`PLANE_KEYS`]: `crates/busbar-voice` is deleted and the plane is +/// `busbar-plane-streaming`, located by its `plane-streaming` door row. +pub const PLANE_KEYS_KIND_ONLY: [&str; 2] = ["mcp", "streaming"]; /// The default ownership grammar. Overridable for a fixture tree, the way /// `PLANE_ROOTS_GRAMMAR` is in the shell.