From fbec053158202f65a0085a7754d810cd016eaaf4 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:23:02 -0700 Subject: [PATCH 01/49] FLIP-LLM kernel: the far end's hook overrides on the kernel's own egress walk, and the driver's flip seams (BUSBAR-1.6.0.md Part 3 section 12, lines 2573-2576 "the route step is the kernel's existing egress walk ... the driver builds no second walk", 2655-2659 "A far end exposes its candidates and a constraint the hooks apply") Ported from the lane-dg-llm snapshot onto the foundation (5e0560fd) without its a2a/h2/DEL-LLM pieces: - busbar-kernel-egress: the walk takes the unit's hook Shape (Walk::next_shaped): each pick's admissible members as the hooks keep and order them (the order is the pick's preference), a required restrict no member satisfies sheds Shed::restrict_no_lane; Walk::walking names the pool the walk is in. Member.passthrough (a pool's upstream_credentials: passthrough), the latency port. - EgressFarEnd: candidates/constrain/remaining/failure (the routing tap's untried count and last failover label), MemberFacts (tags, tier, cost), the restrict's veto at the fallback boundary, the exhaustion terminal's own words (Pick/End::Exhausted carry Shed::detail), a terminal's dispatch relayed and never failed over or counted as an attempt, the latency signal at the response head, the pool-passthrough credential lending (an empty credential when the caller presented none). - the driver: DriverSteps::refused_words and ::attempting, render_as's Retry-After, the head stated once; hooks.rs binds the unit's dialect, reads the current generation (HostSource), the production EngineCaller, a taps-only unit proceeds over an unreadable request, the access amendment names the caller's governance key. - the kernel's own tables view (ConfigTables) and the root's config projection on App; ingress: any_body_ingress; telemetry, core_routes, router and caller-credential seams the door path uses. - tests: the K5 far-end constraint and restrict cases, the least-bad relay, the pool-passthrough lending, the K5 hook cases over the driver double's project (plane_driver_hook_cases.rs). --- crates/busbar-kernel-egress/src/lib.rs | 2 +- crates/busbar-kernel-egress/src/pool.rs | 5 + crates/busbar-kernel-egress/src/ports.rs | 4 + crates/busbar-kernel-egress/src/walk.rs | 99 +++++- crates/busbar-kernel/src/appbuild.rs | 25 ++ .../src/auth/caller_credential.rs | 8 + crates/busbar-kernel/src/core_routes.rs | 9 + crates/busbar-kernel/src/export/traces.rs | 37 ++- crates/busbar-kernel/src/ingress/arrival.rs | 13 + crates/busbar-kernel/src/ingress/mod.rs | 231 +++++++------- .../busbar-kernel/src/plane_driver/cancel.rs | 7 + .../busbar-kernel/src/plane_driver/far_end.rs | 239 ++++++++++++++- .../busbar-kernel/src/plane_driver/hooks.rs | 136 +++++++-- crates/busbar-kernel/src/plane_driver/mod.rs | 62 +++- .../busbar-kernel/src/plane_driver/route.rs | 65 +++- .../src/plane_driver/tests/far_end_tests.rs | 200 +++++++++++- .../src/plane_driver/tests/money_tests.rs | 2 + .../src/plane_host/engine_view.rs | 121 ++++++++ crates/busbar-kernel/src/plane_host/mod.rs | 3 +- .../plane_host/tests/config_tables_tests.rs | 122 ++++++++ crates/busbar-kernel/src/plane_routes.rs | 4 + crates/busbar-kernel/src/router.rs | 98 +++++- crates/busbar-kernel/src/state.rs | 26 +- .../busbar-kernel/src/store/in_memory/mod.rs | 2 +- crates/busbar-kernel/src/telemetry.rs | 38 +++ crates/busbar-kernel/src/test_support/mod.rs | 20 +- .../src/tests/members/egress/route/mod.rs | 1 + crates/busbar-kernel/tests/plane_driver.rs | 176 +++++++++-- .../tests/support/plane_driver_cases.rs | 8 +- .../tests/support/plane_driver_hook_cases.rs | 286 ++++++++++++++++++ .../src/mcp/tests/roots_changed_tests.rs | 1 + crates/busbar/src/root/serve.rs | 1 + 32 files changed, 1813 insertions(+), 238 deletions(-) create mode 100644 crates/busbar-kernel/src/plane_host/tests/config_tables_tests.rs create mode 100644 crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs diff --git a/crates/busbar-kernel-egress/src/lib.rs b/crates/busbar-kernel-egress/src/lib.rs index df1457939a..a9aaa09cc7 100644 --- a/crates/busbar-kernel-egress/src/lib.rs +++ b/crates/busbar-kernel-egress/src/lib.rs @@ -74,7 +74,7 @@ pub use pool::{ Failover, Member, OnExhausted, Pool, DEFAULT_FAILOVER_CAP, DEFAULT_FAILOVER_DEADLINE_SECS, }; pub use select::{RequestCtx, WeightedFloor}; -pub use walk::{Step, Taken, Walk, WalkPorts}; +pub use walk::{Shape, Step, Taken, Walk, WalkPorts}; pub use wire::Shed; #[cfg(test)] diff --git a/crates/busbar-kernel-egress/src/pool.rs b/crates/busbar-kernel-egress/src/pool.rs index d91f91871b..be262cb29b 100644 --- a/crates/busbar-kernel-egress/src/pool.rs +++ b/crates/busbar-kernel-egress/src/pool.rs @@ -38,6 +38,10 @@ pub struct Member { pub context_max: Option, /// The lane this member is priced on, as the trust unit sealed it. pub lane: Option, + /// Through this pool the member is reached with the caller's own credential (the pool's + /// `upstream_credentials: passthrough`): its auth call is lent the caller's verified + /// credential, and nothing else is presented when the caller presented none. + pub passthrough: bool, } impl Member { @@ -51,6 +55,7 @@ impl Member { attempt_timeout_ms: None, context_max: None, lane: None, + passthrough: false, } } } diff --git a/crates/busbar-kernel-egress/src/ports.rs b/crates/busbar-kernel-egress/src/ports.rs index 8ce566cf94..b3c44711ec 100644 --- a/crates/busbar-kernel-egress/src/ports.rs +++ b/crates/busbar-kernel-egress/src/ports.rs @@ -395,6 +395,10 @@ pub trait Telemetry: Send + Sync { /// One upstream attempt was started against this member. fn upstream_attempt(&self, pool: &str, destination: DestinationId); + /// A served attempt's time to the upstream's response head, in milliseconds (the destination's + /// latency signal, a bounded proxy that never waits out a streamed body). + fn upstream_latency(&self, _destination: DestinationId, _ms: f64) {} + /// One attempt failed, with the disposition label the previous release used. fn upstream_failure(&self, pool: &str, destination: DestinationId, disposition: &'static str); diff --git a/crates/busbar-kernel-egress/src/walk.rs b/crates/busbar-kernel-egress/src/walk.rs index d239d49cd4..01c14993ef 100644 --- a/crates/busbar-kernel-egress/src/walk.rs +++ b/crates/busbar-kernel-egress/src/walk.rs @@ -54,6 +54,21 @@ pub struct WalkPorts<'p> { pub pools: &'p HashMap, } +/// THE SHAPE A UNIT'S HOOKS PUT ON ITS PICKS (the design's route pump, "Hooks": the routing-stage policy +/// keeps and orders the routed pool's members; a required restrict holds a pool the walk spills +/// into): each pick's admissible members, the ones kept, and the order the walk prefers among them +/// (empty = the weighted floor's own). `None` = a required restriction no member satisfies: the +/// walk answers [`Shed::restrict_no_lane`] from there. +pub trait Shape { + /// `members` of a pick, as the unit's hooks leave them: the walk's own pool (`primary`) or a + /// pool it spills into. + fn shape( + &self, + members: Vec, + primary: bool, + ) -> Option<(Vec, Vec)>; +} + /// The walk's whole budget over `pool`, seconds: the pool's request timeout, `0` for a pool that is /// not configured. #[must_use] @@ -184,6 +199,17 @@ impl Walk { &self.ctx } + /// The pool the walk is in and whether it is the walk's own (`true`) rather than a spill; + /// `None` once every path is spent. + #[must_use] + pub fn walking(&self) -> Option<(&str, bool)> { + match &self.phase { + Phase::Primary(name) => Some((name, true)), + Phase::Spill(name) | Phase::Terminal(name) => Some((name, false)), + Phase::Shed(_) => None, + } + } + /// The walk ends here, before its pool's terminal: every step from now answers `shed`. pub fn refuse(&mut self, shed: Shed) { self.phase = Phase::Shed(shed); @@ -203,6 +229,17 @@ impl Walk { ports: &WalkPorts<'_>, affinity: Option, token: &Pass, + ) -> Step { + self.next_shaped(ports, affinity, None, token) + } + + /// [`Self::next`], each pick's members as the unit's hooks `shape` them. + pub fn next_shaped( + &mut self, + ports: &WalkPorts<'_>, + affinity: Option, + shape: Option<&dyn Shape>, + token: &Pass, ) -> Step { loop { if self.ctx.expired(ports.clock.now_secs()) { @@ -218,9 +255,13 @@ impl Walk { return Step::Shed(Shed::empty_pool()); } if self.hops <= pool.failover.max_hops { - if let Some(taken) = self.pick(ports, pool, affinity, token, true) { - self.hops += 1; - return Step::Take(taken); + match self.pick(ports, pool, affinity, shape, token, true) { + Ok(Some(taken)) => { + self.hops += 1; + return Step::Take(taken); + } + Ok(None) => {} + Err(shed) => return Step::Shed(shed), } } self.phase = Phase::Terminal(name); @@ -229,8 +270,10 @@ impl Walk { let Some(pool) = ports.pools.get(&name) else { return Step::Shed(pool_shed(ports, &name, token)); }; - if let Some(taken) = self.pick(ports, pool, None, token, false) { - return Step::Take(taken); + match self.pick(ports, pool, None, shape, token, false) { + Ok(Some(taken)) => return Step::Take(taken), + Ok(None) => {} + Err(shed) => return Step::Shed(shed), } self.phase = Phase::Terminal(name); } @@ -285,17 +328,34 @@ impl Walk { /// THE ONE PICK, over `pool`'s members this unit has not tried: the walk's own pool /// (`primary`, with the unit's affinity) and every pool it spills into (degraded). + /// + /// # Errors + /// + /// The unit's hooks hold the pool to a restriction no member satisfies: that shed, which every + /// step from here answers. fn pick( &mut self, ports: &WalkPorts<'_>, pool: &Pool, affinity: Option, + shape: Option<&dyn Shape>, token: &Pass, primary: bool, - ) -> Option { - let members = pool.admissible_members(); + ) -> Result, Shed> { + let admissible = pool.admissible_members().into_owned(); + let (members, order) = match shape { + Some(shape) => match shape.shape(admissible, primary) { + Some(shaped) => shaped, + None => { + let shed = Shed::restrict_no_lane(); + self.phase = Phase::Shed(shed.clone()); + return Err(shed); + } + }, + None => (admissible, Vec::new()), + }; let now = ports.clock.now_secs(); - let mut picked = pick_among( + let Some(mut picked) = pick_among( &PickInput { breaker: ports.breaker, capacity: ports.capacity, @@ -303,18 +363,29 @@ impl Walk { pool: &pool.name, members: &members, affinity, - preference: None, + preference: (!order.is_empty()).then_some(order.as_slice()), now, token, }, &mut self.ctx, - )?; - let member = members + ) else { + return Ok(None); + }; + let Some(member) = members .iter() - .find(|m| m.destination == picked.destination)? - .clone(); + .find(|m| m.destination == picked.destination) + .cloned() + else { + return Ok(None); + }; let probe = picked.take_probe_epoch(); - Some(self.take(&pool.name, member, picked.permit, probe, !primary)) + Ok(Some(self.take( + &pool.name, + member, + picked.permit, + probe, + !primary, + ))) } /// Take `member` of `pool` for the next attempt: excluded from every pick of the unit after it. diff --git a/crates/busbar-kernel/src/appbuild.rs b/crates/busbar-kernel/src/appbuild.rs index ffe15abd09..e455a5c168 100644 --- a/crates/busbar-kernel/src/appbuild.rs +++ b/crates/busbar-kernel/src/appbuild.rs @@ -540,6 +540,19 @@ impl InstalledLimits { } } +/// WHAT THE COMPOSITION ROOT KEEPS OF A RESOLVED CONFIGURATION beside the generation built from it +/// (spec Part 1 line 569: a door plane is handed one validated `{section: value}` object per +/// `open`/`refresh`): the root projects the sections, providers and pool bounds its door planes are +/// sealed over, and reads the projection back when the generation is swapped in. +pub type ConfigProjection = fn(&config::RootCfg) -> std::sync::Arc; + +static CONFIG_PROJECTION: std::sync::OnceLock = std::sync::OnceLock::new(); + +/// Bind the composition root's [`ConfigProjection`], once, before the first build. +pub fn set_config_projection(project: ConfigProjection) { + let _ = CONFIG_PROJECTION.set(project); +} + #[cold] // boot/admin-only — keeps hot text dense (never inlined into a warm path) #[inline(never)] pub fn build_app_from_config( @@ -551,6 +564,8 @@ pub fn build_app_from_config( config_paths: (Option, Option), prior: Option<&state::App>, ) -> Result<(state::App, Option, InstalledLimits), String> { + // The root's projection of this configuration, taken before any part of it is consumed. + let config_projection = CONFIG_PROJECTION.get().map(|project| project(&cfg)); // Install the resolved operational limits process-wide BEFORE any subsystem reads them — // running here (not in main) so a config APPLY/RELOAD refreshes them too. The values threaded // explicitly (client/store/router/TLS) read `cfg.limits` directly; the deep call-stack sites @@ -1634,6 +1649,14 @@ pub fn build_app_from_config( // Populate the NEUTRAL carrier field-by-field from the already-resolved config (pre-resolved secret // plaintexts + rate-card-derived costs + resolved context/tokens are in `lane_inputs`/`pool_inputs`). + // THE KERNEL'S OWN TABLES over the same resolved sections, for the read seam when no plane + // contributes a runtime of its own. + let config_tables = Arc::new(busbar_kernel::plane_host::ConfigTables::of( + &lane_inputs, + &pool_inputs, + &by_model, + cfg.upstream_credentials, + )); let fallback_build_input = PlaneBuildInput { lanes: lane_inputs, pools: pool_inputs, @@ -1747,6 +1770,7 @@ pub fn build_app_from_config( crate::proxy::set_hook_content_max_bytes(cfg.limits.hook_content_max_bytes); let app = App { + config_projection, plane_sections: Some(cfg.plane_sections.clone()), // Telemetry-bank slot table for this generation (built above as a local, BEFORE the // config-derived collections moved into the fallback plane's runtime bundle so its @@ -1758,6 +1782,7 @@ pub fn build_app_from_config( // seam; the snapshot names only the `&'static str` key, and `App::llm_runtime` downcasts the // slot on the money path. Absent slot (featureless build) reads the empty default. fallback_runtime_key, + config_tables, store, // The container planes' breaker cells: PROCESS-LIFETIME, reused across an apply/reload the // way the HTTP client pool and governance state are — a config swap must not un-trip a diff --git a/crates/busbar-kernel/src/auth/caller_credential.rs b/crates/busbar-kernel/src/auth/caller_credential.rs index 70b67f73ed..8207315d0e 100644 --- a/crates/busbar-kernel/src/auth/caller_credential.rs +++ b/crates/busbar-kernel/src/auth/caller_credential.rs @@ -64,6 +64,14 @@ impl ConsumedCredentials { pub struct CallerCredential(pub(super) String); impl CallerCredential { + /// THE CREDENTIAL LENT TO THE HOST'S EGRESS (L2 credential lending): the bytes, zeroised on + /// drop and printed as presence only, for the one outbound auth call of a passthrough member + /// (`FieldsRequest::caller_credential`). Never handed to a plane. + #[must_use] + pub fn lend(&self) -> busbar_contract::redacted::Redacted> { + busbar_contract::redacted::Redacted::new(self.0.as_bytes().to_vec()) + } + /// A ref over `token`, for a test that stands in for the gate. #[cfg(any(test, feature = "test-support"))] pub fn for_test(token: &str) -> Self { diff --git a/crates/busbar-kernel/src/core_routes.rs b/crates/busbar-kernel/src/core_routes.rs index 7b1a2b2c7d..24b96d409b 100644 --- a/crates/busbar-kernel/src/core_routes.rs +++ b/crates/busbar-kernel/src/core_routes.rs @@ -158,6 +158,15 @@ impl CoreRouter { self } + /// The methods a core route is already mounted under at exactly `path`. + pub(crate) fn methods_at(&self, path: &str) -> Vec { + self.table + .by_path + .get(path) + .map(|rows| rows.iter().map(|(m, _)| *m).collect()) + .unwrap_or_default() + } + /// Split into the mounted router and the table describing it. pub(crate) fn into_parts(self) -> (Router>, CoreRouteTable) { (self.router, self.table) diff --git a/crates/busbar-kernel/src/export/traces.rs b/crates/busbar-kernel/src/export/traces.rs index 4ab6e590ee..c50cb71a0a 100644 --- a/crates/busbar-kernel/src/export/traces.rs +++ b/crates/busbar-kernel/src/export/traces.rs @@ -59,9 +59,13 @@ fn record(projection: Projection, facts: &[(F, Value)]) -> Arc { Arc::new(rec.finish()) } -/// A span as it opened: when (monotonic, and epoch microseconds), and the stream's fields it -/// carries. -struct Opened(Instant, u64, Fields); +/// A span as it opened: when (monotonic, and epoch microseconds), the stream's fields it carries, +/// and its own id for the export, unique for the process (the subscriber's span ids are reused +/// once a span closes, so two requests' spans would otherwise share an id and a trace). +struct Opened(Instant, u64, Fields, u64); + +/// The next export id: one per span opened, never reused, never zero. +static NEXT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); /// The `traces` fields a span records, by the field's own name — and only those: a span field that /// shares a name with another stream's field (`key_id`, say) is not a traces fact. @@ -87,24 +91,37 @@ impl LookupSpan<'a>> Layer for Producer { let mut fields = Fields(Vec::new()); attrs.record(&mut fields); let epoch = UNIX_EPOCH.elapsed().unwrap_or_default().as_micros() as u64; + let own = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); span.extensions_mut() - .insert(Opened(Instant::now(), epoch, fields)); + .insert(Opened(Instant::now(), epoch, fields, own)); } /// The span's record: its facts, its parent's id, its ROOT's id as the trace id (a root is its /// own trace), its own id and name, when it opened and how long it stayed open. fn on_close(&self, id: Id, ctx: Context<'_, S>) { let Some(span) = ctx.span(&id) else { return }; - let Some(Opened(start, epoch, Fields(mut facts))) = span.extensions_mut().remove() else { + let Some(Opened(start, epoch, Fields(mut facts), own)) = span.extensions_mut().remove() + else { return; }; - let hex = |id: &Id| Value::from(format!("{:016x}", id.into_u64())); - let parent = span.parent().map(|p| (F::ParentSpanId, hex(&p.id()))); - let root = span.scope().last().map_or(id.clone(), |r| r.id()); + let hex = |id: u64| Value::from(format!("{id:016x}")); + // A span's export id, as it opened (`Opened`); one opened before this layer saw it keeps + // the subscriber's id. + let export_id = |s: &tracing_subscriber::registry::SpanRef<'_, S>| { + s.extensions() + .get::() + .map_or_else(|| s.id().into_u64(), |o| o.3) + }; + let parent = span.parent().map(|p| (F::ParentSpanId, hex(export_id(&p)))); + let root = span + .scope() + .last() + .filter(|r| r.id() != id) + .map_or(own, |r| export_id(&r)); let lasted = start.elapsed().as_micros() as u64; facts.extend(parent.into_iter().chain([ - (F::TraceId, hex(&root)), - (F::SpanId, hex(&id)), + (F::TraceId, hex(root)), + (F::SpanId, hex(own)), (F::Name, Value::from(span.name())), (F::Start, Value::from(epoch)), (F::DurationUs, Value::from(lasted)), diff --git a/crates/busbar-kernel/src/ingress/arrival.rs b/crates/busbar-kernel/src/ingress/arrival.rs index 09a99fcba3..09370023e4 100644 --- a/crates/busbar-kernel/src/ingress/arrival.rs +++ b/crates/busbar-kernel/src/ingress/arrival.rs @@ -275,6 +275,19 @@ fn test_body_ingress() -> &'static [BodyIngressEntry] { TEST_BODY_INGRESS_HOOK.get().map(|f| f()).unwrap_or(&[]) } +/// Whether any body-model arrival is installed (a plane registered its arrivals on the body axis): +/// the convenience surfaces that hand their unit to one (`/{name}/v1/messages`, +/// `/{provider}/{model}/v1/messages`) are mounted only then; a plane served through its door +/// claims those paths itself. +#[must_use] +pub fn any_body_ingress() -> bool { + #[cfg(any(test, feature = "test-support"))] + if !test_body_ingress().is_empty() { + return true; + } + INSTALLED_BODY_INGRESS.get().is_some_and(|a| !a.is_empty()) +} + /// RESOLVE A BODY-MODEL DIALECT'S UNIVERSAL ARRIVAL BY NAME — the lookup `protocol_dispatch` performs /// for a body-model protocol. Consults the installed table first, then the test hook. `None` when no /// LLM plane is linked (core booted plane-agnostic): the caller then answers the honest no-handler diff --git a/crates/busbar-kernel/src/ingress/mod.rs b/crates/busbar-kernel/src/ingress/mod.rs index ac56b4700d..034a1a9799 100644 --- a/crates/busbar-kernel/src/ingress/mod.rs +++ b/crates/busbar-kernel/src/ingress/mod.rs @@ -189,116 +189,7 @@ pub fn admit_check( // bucket label, not an internal credential handle. Server-side tracing records the // full detail either way. tracing::info!(key_id = %key.id, blocked = ?blocked, "governance: limit bucket blocked admission"); - use crate::governance::LimitBlocked; - let (status, kind, message, retry_after) = match &blocked { - LimitBlocked::Limit { - group, - // The per-tier token caps (`tokens_input`/…) surface as a rate limit exactly like - // the aggregate `tokens` metric — a 429 naming the tier — NOT as an over-quota - // block. Without this arm they would fall to the budget/quota arm below and return - // the vendor quota status (Bedrock 400), silently mislabelling the block. - metric: - metric @ ("requests" | "tokens" | "tokens_input" | "tokens_output" - | "tokens_cache_read" | "tokens_cache_write"), - window, - pool: limit_pool, - retry_after, - .. - } => ( - StatusCode::TOO_MANY_REQUESTS, - crate::proxy::KIND_RATE_LIMIT, - format!( - "Rate limit exceeded (group '{group}': {metric} per {}{}). Please retry \ - after the indicated time.", - window.unwrap_or("total"), - pool_scope_suffix(limit_pool), - ), - *retry_after, - ), - LimitBlocked::Limit { - group, - metric: "concurrent", - .. - } => ( - StatusCode::TOO_MANY_REQUESTS, - crate::proxy::KIND_RATE_LIMIT, - format!( - "Too many concurrent requests (group '{group}' is at its in-flight \ - limit). Please retry shortly." - ), - None, - ), - LimitBlocked::Limit { - group, - metric: "budget", - window, - pool: limit_pool, - retry_after, - .. - } => ( - // Native quota status differs by vendor (Bedrock's - // ServiceQuotaExceededException is 400; every other vendor surfaces - // over-quota as 429). The writer owns that mapping. - crate::proto::decl_for(proto) - .map(|d| d.quota_exceeded_status) - .unwrap_or(StatusCode::TOO_MANY_REQUESTS), - crate::proxy::KIND_INSUFFICIENT_QUOTA, - format!( - "You have exceeded your current quota (group '{group}' budget per {}{} \ - exhausted). Please check your plan and billing details.", - window.unwrap_or("total"), - pool_scope_suffix(limit_pool), - ), - *retry_after, - ), - // FAIL-SAFE catch-all for any FUTURE metric not yet given an explicit arm above. It - // maps to a generic 429 rate limit — never the vendor quota status — so a new metric - // can never silently inherit `budget`'s Bedrock-400 semantics. Every metric that exists - // today (`requests`/`tokens`/the four `tokens_*` tiers/`concurrent`/`budget`) is matched - // explicitly above; this arm only exists to keep the string match exhaustive. - LimitBlocked::Limit { - group, - metric, - window, - pool: limit_pool, - retry_after, - .. - } => ( - StatusCode::TOO_MANY_REQUESTS, - crate::proxy::KIND_RATE_LIMIT, - format!( - "Rate limit exceeded (group '{group}': {metric} per {}{}). Please retry \ - after the indicated time.", - window.unwrap_or("total"), - pool_scope_suffix(limit_pool), - ), - *retry_after, - ), - // A FROZEN group (`enabled: false`) is an administrative freeze, not a quota: the - // vendor-plausible shape is a permission denial. - LimitBlocked::Disabled(group) => ( - StatusCode::FORBIDDEN, - crate::proxy::KIND_PERMISSION, - format!( - "Your API key does not currently have access to this resource (group \ - '{group}' is disabled)." - ), - None, - ), - // FAIL-CLOSED: a key bound to a group this node's config does not know is not - // admitted; the message names the missing bucket so the operator can fix it. - LimitBlocked::MissingGroup(group) => ( - crate::proto::decl_for(proto) - .map(|d| d.quota_exceeded_status) - .unwrap_or(StatusCode::TOO_MANY_REQUESTS), - crate::proxy::KIND_INSUFFICIENT_QUOTA, - format!( - "Your quota configuration is incomplete (group '{group}' is not \ - configured). Please contact your administrator." - ), - None, - ), - }; + let (status, kind, message, retry_after) = limit_refusal(proto, &blocked); let mut resp = ingress_error(proto, status, kind, &message); // Standard `Retry-After` for a rolling window so a well-behaved SDK backs off the // right amount ('total' never rolls: no header). @@ -312,6 +203,126 @@ pub fn admit_check( } } +/// THE LIMIT REFUSAL a blocked admission is answered with: its status in `proto`, its kind word, +/// its message (naming the group, metric and window, never the key), and its Retry-After seconds +/// (a rolling window's; `total` never rolls). One spelling, read by every door that admits. +#[must_use] +pub fn limit_refusal( + proto: &str, + blocked: &crate::governance::LimitBlocked, +) -> (StatusCode, &'static str, String, Option) { + use crate::governance::LimitBlocked; + match blocked { + LimitBlocked::Limit { + group, + // The per-tier token caps (`tokens_input`/…) surface as a rate limit exactly like + // the aggregate `tokens` metric — a 429 naming the tier — NOT as an over-quota + // block. Without this arm they would fall to the budget/quota arm below and return + // the vendor quota status (Bedrock 400), silently mislabelling the block. + metric: + metric @ ("requests" | "tokens" | "tokens_input" | "tokens_output" | "tokens_cache_read" + | "tokens_cache_write"), + window, + pool: limit_pool, + retry_after, + .. + } => ( + StatusCode::TOO_MANY_REQUESTS, + crate::proxy::KIND_RATE_LIMIT, + format!( + "Rate limit exceeded (group '{group}': {metric} per {}{}). Please retry \ + after the indicated time.", + window.unwrap_or("total"), + pool_scope_suffix(limit_pool), + ), + *retry_after, + ), + LimitBlocked::Limit { + group, + metric: "concurrent", + .. + } => ( + StatusCode::TOO_MANY_REQUESTS, + crate::proxy::KIND_RATE_LIMIT, + format!( + "Too many concurrent requests (group '{group}' is at its in-flight \ + limit). Please retry shortly." + ), + None, + ), + LimitBlocked::Limit { + group, + metric: "budget", + window, + pool: limit_pool, + retry_after, + .. + } => ( + // Native quota status differs by vendor (Bedrock's + // ServiceQuotaExceededException is 400; every other vendor surfaces + // over-quota as 429). The writer owns that mapping. + crate::proto::decl_for(proto) + .map(|d| d.quota_exceeded_status) + .unwrap_or(StatusCode::TOO_MANY_REQUESTS), + crate::proxy::KIND_INSUFFICIENT_QUOTA, + format!( + "You have exceeded your current quota (group '{group}' budget per {}{} \ + exhausted). Please check your plan and billing details.", + window.unwrap_or("total"), + pool_scope_suffix(limit_pool), + ), + *retry_after, + ), + // FAIL-SAFE catch-all for any FUTURE metric not yet given an explicit arm above. It + // maps to a generic 429 rate limit — never the vendor quota status — so a new metric + // can never silently inherit `budget`'s Bedrock-400 semantics. Every metric that exists + // today (`requests`/`tokens`/the four `tokens_*` tiers/`concurrent`/`budget`) is matched + // explicitly above; this arm only exists to keep the string match exhaustive. + LimitBlocked::Limit { + group, + metric, + window, + pool: limit_pool, + retry_after, + .. + } => ( + StatusCode::TOO_MANY_REQUESTS, + crate::proxy::KIND_RATE_LIMIT, + format!( + "Rate limit exceeded (group '{group}': {metric} per {}{}). Please retry \ + after the indicated time.", + window.unwrap_or("total"), + pool_scope_suffix(limit_pool), + ), + *retry_after, + ), + // A FROZEN group (`enabled: false`) is an administrative freeze, not a quota: the + // vendor-plausible shape is a permission denial. + LimitBlocked::Disabled(group) => ( + StatusCode::FORBIDDEN, + crate::proxy::KIND_PERMISSION, + format!( + "Your API key does not currently have access to this resource (group \ + '{group}' is disabled)." + ), + None, + ), + // FAIL-CLOSED: a key bound to a group this node's config does not know is not + // admitted; the message names the missing bucket so the operator can fix it. + LimitBlocked::MissingGroup(group) => ( + crate::proto::decl_for(proto) + .map(|d| d.quota_exceeded_status) + .unwrap_or(StatusCode::TOO_MANY_REQUESTS), + crate::proxy::KIND_INSUFFICIENT_QUOTA, + format!( + "Your quota configuration is incomplete (group '{group}' is not \ + configured). Please contact your administrator." + ), + None, + ), + } +} + /// Run the governance guards (pool ACL / unpriced-model / the atomic group-limit admission) for a /// request that is about to be forwarded. Returns the protocol-native rejection response already /// passed through `finish_rejected`. The statuses are deliberately vendor-faithful and never 402: diff --git a/crates/busbar-kernel/src/plane_driver/cancel.rs b/crates/busbar-kernel/src/plane_driver/cancel.rs index fbb5b2352f..cf66d527fe 100644 --- a/crates/busbar-kernel/src/plane_driver/cancel.rs +++ b/crates/busbar-kernel/src/plane_driver/cancel.rs @@ -92,6 +92,13 @@ pub(crate) struct Facts { pub(crate) far_end_answered: bool, /// A byte of the reply reached the caller. pub(crate) streamed: bool, + /// The reply's head was stated to the caller (once: a head with no body may precede the + /// answer's first byte). + pub(crate) headed: bool, + /// The live attempt's answer is relayed as the unit's: a terminal's dispatch (a spill, the + /// least-bad bypass, a queued slot), which 1.5.5 never failed over, so a retry verdict on it + /// renders the answer instead. + pub(crate) relayed: bool, /// The last cumulative units the plane reported. pub(crate) units: Vec, } diff --git a/crates/busbar-kernel/src/plane_driver/far_end.rs b/crates/busbar-kernel/src/plane_driver/far_end.rs index acb556936b..219a4b2bb0 100644 --- a/crates/busbar-kernel/src/plane_driver/far_end.rs +++ b/crates/busbar-kernel/src/plane_driver/far_end.rs @@ -51,7 +51,7 @@ use crate::proxy::egress_unit::{ Journal, Outcome, Permit, Telemetry, UpstreamStatus, }, walk::budget_secs, - Member, Pool, Shed, Step, Taken, Walk, WalkPorts, WeightedFloor, + Member, Pool, Shape, Shed, Step, Taken, Walk, WalkPorts, WeightedFloor, }; use busbar_contract::abi::auth::{AuthPoint, AuthPoints, STYLE_NEEDS_HEADERS}; use busbar_contract::abi::transport::{ @@ -66,6 +66,7 @@ use busbar_contract::redacted::Redacted; use busbar_contract::transport::registry::status_ns; use busbar_contract::transport::wire::{WireStatus, WireStatusClass}; +use super::hooks::{CandidateFacts, Candidates, Constraint}; use super::route::{FarEnd, FarPiece, OutboundRequest, Pick}; /// The bytes one read of the far end takes. @@ -251,8 +252,11 @@ impl Egress { walk, live: None, probe: None, + failed: None, }), probe_of: None, + described: None, + constraint: Mutex::new(None), } } @@ -282,8 +286,11 @@ impl Egress { walk, live: None, probe: Some(member), + failed: None, }), probe_of: Some(destination), + described: None, + constraint: Mutex::new(None), }) } } @@ -323,6 +330,9 @@ struct State { walk: Walk, live: Option, probe: Option, + /// Why the last attempt failed over, in the walk's failover vocabulary (the `routing` stage + /// tap's `previous_failure`). + failed: Option<&'static str>, } /// ONE UNIT'S FAR END over its [`Egress`]. @@ -332,6 +342,97 @@ pub struct EgressFarEnd<'e> { state: Mutex, /// The member a health probe is pinned to; `None` for a unit's walk. probe_of: Option, + /// What the hooks are shown of each member beyond the walk's own facts, by member name: the + /// operator's tags, tier and cost (the plane's section states them). + described: Option>>, + /// The hooks' constraint on the walk, once the request stage set one. + constraint: Mutex>, +} + +/// What the operator states of one member for the hooks: its tags, its tier and its cost. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct MemberFacts { + /// The member's model, as the hooks name it (empty = the member's name). + pub model: String, + /// The operator's tags. + pub tags: Vec, + /// The operator's tier. + pub tier: Option, + /// The operator's cost per million tokens. + pub cost_per_mtok: Option, +} + +impl EgressFarEnd<'_> { + /// What the hooks are shown of each member beyond the walk's own facts, by member name. + #[must_use] + pub fn described(mut self, facts: Arc>) -> Self { + self.described = Some(facts); + self + } + + fn facts_of(&self, member: &Member) -> Option<&MemberFacts> { + self.described.as_ref().and_then(|d| d.get(&member.name)) + } + + fn tags_of(&self, member: &Member) -> &[String] { + self.facts_of(member).map_or(&[], |f| f.tags.as_slice()) + } + + /// `members` as the hooks' constraint leaves them for one pick: the routed pool's members the + /// hooks kept, in their order; a fallback pool's held to their restricts (`Err` = a required + /// restrict no member satisfies). The order, as destinations, rides beside. + fn constrained( + &self, + members: Vec, + primary: bool, + ) -> Result<(Vec, Vec), ()> { + let constraint = self + .constraint + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(); + let Some(c) = constraint else { + return Ok((members, Vec::new())); + }; + if primary { + let kept: Vec = members + .iter() + .enumerate() + .filter(|(i, _)| c.keep.as_ref().is_none_or(|k| k.contains(i))) + .map(|(_, m)| m.clone()) + .collect(); + let order: Vec = c + .order + .iter() + .flatten() + .filter_map(|i| members.get(*i)) + .map(|m| m.destination) + .collect(); + return Ok((kept, order)); + } + let tagged = members + .iter() + .enumerate() + .map(|(i, m)| (i, self.tags_of(m))); + let kept = c.enforce(tagged).map_err(|_| ())?; + Ok(( + kept.into_iter() + .filter_map(|i| members.get(i).cloned()) + .collect(), + Vec::new(), + )) + } +} + +/// The walk's picks as the unit's hooks shape them ([`EgressFarEnd::constrained`]). +impl Shape for EgressFarEnd<'_> { + fn shape( + &self, + members: Vec, + primary: bool, + ) -> Option<(Vec, Vec)> { + self.constrained(members, primary).ok() + } } fn exhausted(shed: &Shed) -> Pick { @@ -340,6 +441,20 @@ fn exhausted(shed: &Shed) -> Pick { retry_after: shed .retry_after_secs .map(|s| u32::try_from(s).unwrap_or(u32::MAX)), + detail: shed.detail, + } +} + +/// The walk's shed as the unit is answered: a required gate restriction no member satisfies is the +/// hooks' veto in the shed's words; any other shed is the walk's exhaustion. +fn shed_pick(shed: &Shed) -> Pick { + if shed.gate_rejected { + Pick::Vetoed { + status: u32::from(shed.status), + text: shed.detail.to_string(), + } + } else { + exhausted(shed) } } @@ -500,9 +615,11 @@ impl EgressFarEnd<'_> { }; let name = member.name.clone(); let route = self.egress.routes.get(&member.destination); - let passthrough = route - .and_then(|r| r.auth.as_ref()) - .is_some_and(|a| a.passthrough); + // Through its pool (`upstream_credentials: passthrough`), or by its own binding. + let passthrough = member.passthrough + || route + .and_then(|r| r.auth.as_ref()) + .is_some_and(|a| a.passthrough); let provider = route.map(|r| r.provider.clone()).unwrap_or_default(); w.live = Some(Live { pool: pool.clone(), @@ -542,9 +659,12 @@ impl EgressFarEnd<'_> { return self.claim_member(&mut w, String::new(), member, None, None, false, 1); } } - match w.walk.next(&ports, self.route.affinity, token) { + match w + .walk + .next_shaped(&ports, self.route.affinity, Some(self), token) + { Step::Take(taken) => return self.admit_taken(&mut w, taken), - Step::Shed(shed) => return exhausted(&shed), + Step::Shed(shed) => return shed_pick(&shed), Step::Wait(wait) => wait, } }; @@ -569,6 +689,7 @@ impl EgressFarEnd<'_> { fn no_answer(&self, token: &Pass, cause: NoAnswer) -> FarPiece { let e = self.egress; let mut w = self.lock(); + let mut failed = None; if let Some(live) = w.live.as_mut() { let now = e.clock.now_secs(); live.probe = None; @@ -594,6 +715,10 @@ impl EgressFarEnd<'_> { e.telemetry.upstream_failure(&pool, destination, failure); e.telemetry.failover(&pool, failover); } + failed = Some(failover); + } + if failed.is_some() { + w.failed = failed; } self.settle(&mut w); fail_over() @@ -610,6 +735,11 @@ impl EgressFarEnd<'_> { .map_or(remaining.max(1), |ms| attempt_cap_ms(ms, remaining)) } + /// Whether the live attempt's member relays the caller's own credential. + fn live_passthrough(&self) -> bool { + self.lock().live.as_ref().is_some_and(|l| l.passthrough) + } + /// ONE CALL to the member's auth binding for this attempt's fields (THE DESIGN, section 6). async fn auth_fields( &self, @@ -642,8 +772,16 @@ impl EgressFarEnd<'_> { } else { Vec::new() }, - caller_credential: if binding.passthrough { - self.route.caller_credential.clone() + // A passthrough member's one auth call is lent the caller's verified credential; a + // caller who presented none lends an empty one, so nothing is presented (never the + // operator's own key), as 1.5.5's `present_caller` did. + caller_credential: if binding.passthrough || self.live_passthrough() { + Some( + self.route + .caller_credential + .clone() + .unwrap_or_else(|| Redacted::new(Vec::new())), + ) } else { None }, @@ -748,14 +886,18 @@ impl EgressFarEnd<'_> { .map(|(n, v)| (n.as_str(), v.as_slice())) .collect(); let cap_ms = self.attempt_cap(); - let pool = { + let (pool, degraded) = { let w = self.lock(); - w.live - .as_ref() - .map(|l| Self::metric_pool(&l.pool, &l.member).to_string()) - .unwrap_or_default() + w.live.as_ref().map_or((String::new(), false), |l| { + ( + Self::metric_pool(&l.pool, &l.member).to_string(), + l.degraded, + ) + }) }; - if self.probe_of.is_none() { + // A terminal's dispatch (a spill, the least-bad bypass, a queued slot) is not one of the + // walk's attempts: 1.5.5's degraded forward counted none (`busbar_upstream_attempts_total`). + if self.probe_of.is_none() && !degraded { e.telemetry.upstream_attempt(&pool, destination); } // 3. The connector: the judged, pinned dial and the framer's encode. @@ -1017,6 +1159,9 @@ impl EgressFarEnd<'_> { live.probe = None; live.spent = e.breaker.spend_budget(destination); live.delivered = self.route.wants_stream && !bytes.is_empty(); + let head_ms = e.clock.now_millis().saturating_sub(live.anchor_ms); + #[allow(clippy::cast_precision_loss)] + e.telemetry.upstream_latency(destination, head_ms as f64); return FarPiece { bytes, status: far_status, @@ -1024,6 +1169,7 @@ impl EgressFarEnd<'_> { fail_over: false, fields: false, head: Vec::new(), + relayed: live.degraded, }; } let classified = e.breaker.classify(destination, status); @@ -1055,6 +1201,7 @@ impl EgressFarEnd<'_> { .upstream_failure(&pool, destination, classified.label); } live.error_left = Some(e.error_body_max); + let relayed = live.degraded; let piece = cap( live, FarPiece { @@ -1064,6 +1211,7 @@ impl EgressFarEnd<'_> { fail_over: false, fields: false, head: Vec::new(), + relayed, }, ); if piece.last { @@ -1080,6 +1228,7 @@ impl EgressFarEnd<'_> { let (pool, failed) = (live.pool.clone(), live.member.clone()); w.walk.refused_for_size(&e.ports(), &pool, &failed); } + w.failed = Some(classified.label); self.settle(&mut w); fail_over() } @@ -1117,6 +1266,68 @@ impl FarEnd for EgressFarEnd<'_> { ) -> impl Future> + Send + 'a { self.next_piece(token) } + + fn candidates(&self, _token: &Pass) -> Option { + let pool = self.egress.pools.get(&self.route.pool)?; + let members = pool.admissible_members(); + Some(Candidates { + pool: pool.name.clone(), + members: members + .iter() + .enumerate() + .map(|(idx, m)| { + let facts = self.facts_of(m); + CandidateFacts { + idx, + model: facts + .filter(|f| !f.model.is_empty()) + .map_or_else(|| m.name.clone(), |f| f.model.clone()), + provider: self + .egress + .routes + .get(&m.destination) + .map(|r| r.provider.clone()) + .unwrap_or_default(), + weight: m.weight, + context_max: m.context_max.and_then(|c| usize::try_from(c).ok()), + tier: facts.and_then(|f| f.tier.clone()), + cost_per_mtok: facts.and_then(|f| f.cost_per_mtok), + tags: facts.map(|f| f.tags.clone()).unwrap_or_default(), + ..CandidateFacts::default() + } + }) + .collect(), + }) + } + + fn remaining(&self, _token: &Pass) -> Option { + let w = self.lock(); + let Some((name, primary)) = w.walk.walking() else { + return Some(0); + }; + let pool = self.egress.pools.get(name)?; + let members = self + .constrained(pool.admissible_members().into_owned(), primary) + .map(|(members, _)| members) + .unwrap_or_default(); + Some( + members + .iter() + .filter(|m| !w.walk.ctx().is_excluded(m.destination)) + .count(), + ) + } + + fn failure(&self, _token: &Pass) -> Option<&'static str> { + self.lock().failed + } + + fn constrain(&self, _token: &Pass, constraint: Constraint) { + *self + .constraint + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(constraint); + } } #[cfg(test)] diff --git a/crates/busbar-kernel/src/plane_driver/hooks.rs b/crates/busbar-kernel/src/plane_driver/hooks.rs index 54099a49df..1f11d86328 100644 --- a/crates/busbar-kernel/src/plane_driver/hooks.rs +++ b/crates/busbar-kernel/src/plane_driver/hooks.rs @@ -85,6 +85,8 @@ pub struct Bind<'a> { pub pool: &'a str, /// The principal the kernel verified, when there is one. pub principal: Option<&'a str>, + /// The dialect the unit arrived in: the index into the plane's dialects. + pub dialect: u32, } /// The stage taps a unit binds, by stage. @@ -151,6 +153,8 @@ pub struct UnitHooks { pub groups: GroupScope, /// The access amendment of a hook handed the prompt. pub reads: HookRead, + /// The dialect the unit arrived in, by name (the label of a view the plane could not read). + pub dialect: String, } impl std::fmt::Debug for UnitHooks { @@ -212,8 +216,11 @@ pub trait CallerFacts: Send + Sync { fn rate_headroom(&self, principal: &str, pool: &str) -> Option; /// The caller's budget chain. fn budget(&self, principal: &str) -> Vec; - /// Leave the access amendment of a hook handed the prompt. - fn hook_read(&self, hook: &str, principal: Option<&str>, dialect: &str, identity: bool); + /// Leave the access amendment of a hook handed the prompt: the kernel's one seam (the node + /// journal), field names only. + fn hook_read(&self, hook: &str, principal: Option<&str>, dialect: &str, identity: bool) { + crate::audit::amend::hook_read(hook, principal, dialect, identity); + } /// Fire the `response` taps of a unit refused at authentication. fn denied_taps(&self, taps: &[TapEntry], request_id: u64, dialect: &str, status: u16) { let shape = StageShape::zeroed(request_id, "", dialect, false); @@ -235,6 +242,63 @@ pub trait CallerFacts: Send + Sync { } } +/// THE PRODUCTION CALLER FACTS: what the hooks may know of a verified caller, read where 1.5.5's +/// hook seam read it (v1.5.5 `crates/busbar/src/proxy/hooks.rs`): the caller's governance key +/// (looked up by the principal the kernel verified, its id), its `groups:` membership walked against +/// the generation's group registry (`EngineHost::caller_in_hook_groups`, self + ancestors), and its +/// rate headroom and budget chain over the governance state and card the generation pins +/// (`EngineHost::rate_headroom` / `budget_state`, pure observations). +pub struct EngineCaller { + /// The current generation's engine host, read per call (a config apply replaces it). + pub host: HostSource, + /// The principal's governance key, by its id (`GovState::lookup_by_sub`). + pub keys: KeyLookup, +} + +/// The CURRENT generation's engine host, read where it is needed: a config apply replaces the +/// generation (its hook registry, its group tree, its card), and a unit bound after the apply binds +/// the new one, as 1.5.5's request read the snapshot it arrived on. +pub type HostSource = Arc Arc + Send + Sync>; + +/// A governance key lookup by principal id. +pub type KeyLookup = + Arc Option> + Send + Sync>; + +impl EngineCaller { + fn key_of(&self, principal: &str) -> Option> { + (self.keys)(principal) + } +} + +impl CallerFacts for EngineCaller { + fn key(&self, principal: &str) -> Option { + self.key_of(principal).map(|k| CallerKey { + id: k.id.clone(), + name: k.name.clone(), + }) + } + + fn in_groups(&self, principal: Option<&str>, groups: &[String]) -> bool { + let key = principal.and_then(|p| self.key_of(p)); + (self.host)().caller_in_hook_groups(key.as_ref().and_then(|k| k.group.as_deref()), groups) + } + + fn rate_headroom(&self, principal: &str, pool: &str) -> Option { + let key = self.key_of(principal)?; + let host = (self.host)(); + let pin = host.meter_pin()?; + host.rate_headroom(&pin, &key, Some(pool), crate::store::now()) + } + + fn budget(&self, principal: &str) -> Vec { + let host = (self.host)(); + match (self.key_of(principal), host.meter_pin()) { + (Some(key), Some(pin)) => host.budget_state(&pin, &key, crate::store::now()), + _ => Vec::new(), + } + } +} + /// The hooks one unit binds, before the caller's facts: `None` when no hook of any stage is bound. struct Parts { rewrites: RewriteChain, @@ -276,7 +340,12 @@ impl Parts { } /// The unit's hooks, with what `caller` knows of the verified principal. - fn bind(self, bind: &Bind<'_>, caller: &Arc, request_id: u64) -> UnitHooks { + fn bind( + self, + bind: &Bind<'_>, + (caller, dialects): (&Arc, &[String]), + request_id: u64, + ) -> UnitHooks { let principal = bind.principal.map(str::to_string); let groups_caller = Arc::clone(caller); let reads_caller = Arc::clone(caller); @@ -300,6 +369,10 @@ impl Parts { reads_caller.hook_read(hook, principal, dialect, id); }, ), + dialect: dialects + .get(bind.dialect as usize) + .cloned() + .unwrap_or_default(), } } } @@ -335,7 +408,11 @@ impl HookBinder for BoundHooks { self.taps.clone(), self.requested, )?; - Some(parts.bind(bind, &self.caller, (self.next_request_id)())) + Some(parts.bind( + bind, + (&self.caller, &self.dialects), + (self.next_request_id)(), + )) } fn denied(&self, dialect: u32, status: u16) { @@ -357,8 +434,8 @@ impl HookBinder for BoundHooks { /// declared signals and its one request-id counter), with what `caller` knows of a verified /// principal. pub struct HostHooks { - /// The generation's engine host. - pub host: Arc, + /// The current generation's engine host, read once per bound unit. + pub host: HostSource, /// What the hooks may know of a verified caller. pub caller: Arc, /// The plane's dialects, in its tail's order. @@ -367,7 +444,8 @@ pub struct HostHooks { impl HookBinder for HostHooks { fn bind(&self, bind: &Bind<'_>) -> Option { - let h = &*self.host; + let host = (self.host)(); + let h = &*host; let parts = Parts::of( (h.rewrite_hooks(), h.global_gates()), (h.pool_rewrites(bind.pool), h.pool_gates(bind.pool)), @@ -380,18 +458,19 @@ impl HookBinder for HostHooks { }, *h.requested_signals(), )?; - Some(parts.bind(bind, &self.caller, h.next_request_id())) + Some(parts.bind(bind, (&self.caller, &self.dialects), h.next_request_id())) } fn denied(&self, dialect: u32, status: u16) { - let taps = self.host.tap_hooks_response(); + let host = (self.host)(); + let taps = host.tap_hooks_response(); if taps.is_empty() { return; } denied_taps( &*self.caller, taps, - self.host.next_request_id(), + host.next_request_id(), &self.dialects, (dialect, status), ); @@ -736,7 +815,6 @@ struct Asked<'a> { hooks: &'a UnitHooks, pool: &'a str, candidates: &'a [Candidate<'a>], - principal: Option<&'a str>, /// The walk named its candidates (an empty pool, then, has nothing to rank). named: bool, } @@ -750,8 +828,11 @@ impl Asked<'_> { } } + /// The access amendment of a hook handed the prompt, naming whose content it was: the + /// caller's governance key, as the previous release named it. fn read(&self, hook: &str, identity: bool) { - (self.hooks.reads)(hook, self.principal, &self.view.dialect, identity); + let whose = self.hooks.key.as_ref().map(|k| k.id.as_str()); + (self.hooks.reads)(hook, whose, &self.view.dialect, identity); } /// One decision of `resolved`, under its deadline, its failure walked down its on-error chain. @@ -1148,21 +1229,42 @@ impl PlaneUnits<'_, S, F, C> { .principal .as_ref() .map(|p| p.as_str().to_string()); + let dialect = self.lock().decoded.as_ref().map_or(0, |d| d.dialect); let Some(hooks) = binder.bind(&Bind { pool: &walk_pool, principal: principal.as_deref(), + dialect, }) else { return Ok(()); }; + self.lock().hooked_pool.clone_from(&walk_pool); // The unit's correlation id, a native `u64` on its span (never a formatted string), the // same value every hook payload and tap of the unit carries. let span = tracing::debug_span!("forward", request_id = tracing::field::Empty); span.record("request_id", hooks.request_id); let mut view = match self.project(None) { Ok(view) => view, + // A request the plane cannot read is the request's failure where a hook must judge + // it (a rewrite, a gate, a route policy); a tap only observes, and observes the zeroed + // shape (the previous release's stage capture), the request proceeding. + Err(Stopped::Unreadable) + if hooks.rewrites.is_empty() + && hooks.gates.is_empty() + && hooks.policy.is_none() => + { + Projection { + pool: walk_pool.clone(), + dialect: hooks.dialect.clone(), + ..Projection::default() + } + } Err(stopped) => { // The unit's response taps still see it, in the zeroed shape. - self.lock().hooked = Some((hooks, Projection::default())); + let zeroed = Projection { + dialect: hooks.dialect.clone(), + ..Projection::default() + }; + self.lock().hooked = Some((hooks, zeroed)); return Err(stopped); } }; @@ -1171,7 +1273,7 @@ impl PlaneUnits<'_, S, F, C> { } else { walk_pool }; - let principal = principal.as_deref(); + self.lock().hooked_pool.clone_from(&pool); // 1. THE REWRITE CHAIN. for (timeout, hook) in &hooks.rewrites { @@ -1235,7 +1337,6 @@ impl PlaneUnits<'_, S, F, C> { &all, &mut keep, &mut constraint, - principal, ) .await; if let Err(stopped) = decided { @@ -1263,12 +1364,10 @@ impl PlaneUnits<'_, S, F, C> { self.far.constrain(token, constraint); } self.lock().hooked = Some((hooks, view)); - self.lock().hooked_pool = pool; Ok(()) } /// Steps 3 and 4: the gates reconciled, then the base policy when no gate ordered. - #[allow(clippy::too_many_arguments)] async fn decide_route( &self, hooks: &UnitHooks, @@ -1277,7 +1376,6 @@ impl PlaneUnits<'_, S, F, C> { all: &[CandidateFacts], keep: &mut HashSet, constraint: &mut Constraint, - principal: Option<&str>, ) -> Result<(), Stopped> { let mut gate_order: Option<(Vec, &'static str)> = None; if !hooks.gates.is_empty() { @@ -1287,7 +1385,6 @@ impl PlaneUnits<'_, S, F, C> { hooks, pool, candidates: &shown, - principal, named, }; let outcomes: Vec = @@ -1410,7 +1507,6 @@ impl PlaneUnits<'_, S, F, C> { hooks, pool, candidates: &shown, - principal, named, }; match Box::pin(asked.decide(policy)).await { diff --git a/crates/busbar-kernel/src/plane_driver/mod.rs b/crates/busbar-kernel/src/plane_driver/mod.rs index 4b22348725..806a2e3dd5 100644 --- a/crates/busbar-kernel/src/plane_driver/mod.rs +++ b/crates/busbar-kernel/src/plane_driver/mod.rs @@ -85,11 +85,13 @@ use tokio::sync::{watch, Notify}; pub use cancel::{CancelBill, Checkpoint, MoneySeam}; pub use epoch::FlushEpoch; pub use far_end::{ - AuthBinding, Egress, EgressFarEnd, MemberRoute, ResponseKeep, UnitRoute, DEFAULT_ERROR_BODY_MAX, + AuthBinding, Egress, EgressFarEnd, MemberFacts, MemberRoute, ResponseKeep, UnitRoute, + DEFAULT_ERROR_BODY_MAX, }; pub use hooks::{ - Bind, BoundHooks, CallerFacts, CallerKey, CandidateFacts, Candidates, Constraint, GroupScope, - HookBinder, HookRead, HostHooks, Projection, Restrict, RewriteChain, StageTaps, UnitHooks, + Bind, BoundHooks, CallerFacts, CallerKey, CandidateFacts, Candidates, Constraint, EngineCaller, + GroupScope, HookBinder, HookRead, HostHooks, HostSource, Projection, Restrict, RewriteChain, + StageTaps, UnitHooks, }; pub use money::{EndPost, FeeRefund, PlaneMoney, UnitMoney}; pub use needs::{resolve_member_needs, MemberAuth, NeedRefusal}; @@ -211,12 +213,12 @@ pub struct PlaneDriver { reload: watch::Sender, /// The kernel's record write path and the instance the plane's writes are keyed by. records: Option<(Arc, Caller)>, + /// Which hooks bind to a unit of this plane; `None` = none ever does. + hooks: Option>, driver: Option, services: Arc, /// The open duplex sessions, by stream: what wakes each when `drive` names it (R-B). sessions: Mutex>>, - /// Which hooks bind to a unit of this plane; `None` = none ever does. - hooks: Option>, } impl Drop for PlaneDriver { @@ -276,10 +278,10 @@ impl PlaneDriver { buried: Mutex::new(Vec::new()), reload: watch::channel(false).0, records: None, + hooks: None, driver, services, sessions: Mutex::default(), - hooks: None, }) } @@ -403,6 +405,20 @@ pub trait DriverSteps: Units { /// The units the plane's `arrive` expects the unit to do (its admission estimate, THE DESIGN /// §7 `admission: estimate`), told with [`Self::decoded`]. An estimate never bills. fn expected(&self, _ctx: &UnitCtx, _units: &[UnitCount]) {} + + /// The words of the refusal one of these steps raised, where it has its own: its Retry-After + /// seconds (a rolling limit's wait, `RefusalIn::retry_after_s`, the previous release's + /// `Retry-After` on a limit's 429) and its message (`RefusalIn::text`, the kernel's own message + /// for the refusal: the previous release's sentence for a pool the key may not use or a model + /// no rate prices). `(None, None)` = the reason's own spelling, no wait. + fn refused_words(&self) -> (Option, Option) { + (None, None) + } + + /// An attempt of the unit starts on a member of `provider`, the unit's request read in its + /// `dialect` (the one `arrive` answered): the previous release's translation counter counts an + /// attempt whose far end speaks another dialect. + fn attempting(&self, _ctx: &UnitCtx, _dialect: u32, _provider: &str) {} } /// A refusal or failure the plane rendered, for the caller. @@ -494,6 +510,11 @@ impl PlaneUnits<'_, S, F, C> { self.lock().decoded.clone() } + /// The status the plane's `arrive` refused the arrival under, when it refused it. + pub fn declined_status(&self) -> Option { + self.lock().declined.map(|(_, status)| status) + } + /// The status a refusal for `reason` goes out under, as [`Self::render`] chooses it. fn status_for(&self, reason: ReasonCode) -> u32 { let st = self.lock(); @@ -570,7 +591,7 @@ impl PlaneUnits<'_, S, F, C> { /// The plane renders a refusal (`refusal`, ticketless, one re-call when short); the kernel's /// generic failure, with no body, when it cannot. fn render(&self, reason: ReasonCode) -> Rendered { - self.render_as(reason, None, None) + self.render_as(reason, None, None, None) } /// [`Self::render`], or under the status and Retry-After the walk chose (an exhaustion @@ -581,6 +602,7 @@ impl PlaneUnits<'_, S, F, C> { reason: ReasonCode, walk: Option<(u32, Option)>, said: Option<&str>, + retry_after: Option, ) -> Rendered { let (unit, dialect, declined) = { let st = self.lock(); @@ -595,7 +617,7 @@ impl PlaneUnits<'_, S, F, C> { (None, Some((_, status))) => status, (None, None) => self.driver.config.status(dialect, reason), }; - let retry_after_s = walk.and_then(|(_, r)| r).unwrap_or(0); + let retry_after_s = walk.and_then(|(_, r)| r).or(retry_after).unwrap_or(0); let text = said.unwrap_or(reason.as_str()); let caps = self.driver.config.caps; let (mut reply, mut fields, mut arena) = ( @@ -681,6 +703,7 @@ impl PlaneUnits<'_, S, F, C> { ReasonCode::HookVeto, Some((veto.status, None)), Some(veto.text.as_str()), + None, ); self.lock().rendered = Some(rendered); self.response_tap(true, veto.status); @@ -727,24 +750,32 @@ impl PlaneUnits<'_, S, F, C> { route::End::Failed(reason) | route::End::Cancel(reason, _) => { self.response_tap(false, self.status_for(*reason)); } - route::End::Exhausted(status, _) => self.response_tap(false, *status), + route::End::Exhausted(status, ..) => self.response_tap(false, *status), route::End::Vetoed(status, _) => self.response_tap(true, *status), } let answer = match end { route::End::Done => StepAnswer::proceed(token, RoutePlan::default()), route::End::Failed(reason) => StepAnswer::refuse(token, Refusal::new(reason)), - route::End::Exhausted(status, retry_after) => { + route::End::Exhausted(status, retry_after, detail) => { // THE WALK'S EXHAUSTION TERMINAL: its status and its Retry-After floor, handed to // the plane's `refusal` (RefusalIn.retry_after_s), which renders them in its dialect. - let rendered = - self.render_as(ReasonCode::BreakerOpen, Some((status, retry_after)), None); + let rendered = self.render_as( + ReasonCode::BreakerOpen, + Some((status, retry_after)), + Some(detail), + None, + ); self.lock().rendered = Some(rendered); StepAnswer::refuse(token, Refusal::new(ReasonCode::BreakerOpen)) } route::End::Vetoed(status, text) => { // THE WALK'S REFUSAL FOR A HOOK'S RESTRICTION: answered as the hook would be. - let rendered = - self.render_as(ReasonCode::HookVeto, Some((status, None)), Some(&text)); + let rendered = self.render_as( + ReasonCode::HookVeto, + Some((status, None)), + Some(&text), + None, + ); self.lock().rendered = Some(rendered); StepAnswer::refuse(token, Refusal::new(ReasonCode::HookVeto)) } @@ -870,7 +901,8 @@ impl Units for PlaneUnits<'_, S, }; let body = match reason { Some(reason) if !pending => { - let rendered = self.render(reason); + let (retry_after, said) = self.steps.refused_words(); + let rendered = self.render_as(reason, None, said.as_deref(), retry_after); if let (Some(binder), true) = (&self.driver.hooks, is_authentication(reason)) { // A unit refused at authentication: the response taps see the previous // release's synthetic completion, under the status the caller is answered. diff --git a/crates/busbar-kernel/src/plane_driver/route.rs b/crates/busbar-kernel/src/plane_driver/route.rs index bf76c0f9e8..83283ab5b7 100644 --- a/crates/busbar-kernel/src/plane_driver/route.rs +++ b/crates/busbar-kernel/src/plane_driver/route.rs @@ -61,6 +61,10 @@ pub struct FarPiece { /// attempt fails over: the pump moves to the next member WITHOUT pushing the piece to the /// plane, while no byte has reached the caller. pub fail_over: bool, + /// On the reply's first piece: the far end relays this answer as the unit's (a terminal's + /// dispatch: a spill, the least-bad bypass, a queued slot), so it is never failed over: a + /// retry verdict renders it, as 1.5.5 relayed a degraded dispatch's answer. + pub relayed: bool, } /// What the walk answers for an attempt: the member to try, or the pool's exhaustion terminal. @@ -86,6 +90,9 @@ pub enum Pick { status: u32, /// The Retry-After seconds. retry_after: Option, + /// The terminal's words (`busbar_kernel_egress::wire::Shed::detail`), the previous + /// release's sentence for that shed. + detail: &'static str, }, /// The walk refuses for a hook's restriction: a fallback pool no member of which satisfies a /// required restrict the hooks decided (the previous release failed closed there rather than @@ -375,8 +382,9 @@ pub(crate) enum End { Done, /// The unit failed; nothing was cancelled. Failed(ReasonCode), - /// The walk had no member left: its exhaustion terminal's status and Retry-After seconds. - Exhausted(u32, Option), + /// The walk had no member left: its exhaustion terminal's status, Retry-After seconds and + /// words. + Exhausted(u32, Option, &'static str), /// The walk refused for a hook's restriction: the status and the words. Vetoed(u32, String), /// The driver cancels the unit; the answer of the op that was in flight, if one was. @@ -680,7 +688,22 @@ async fn until(left: Option) { } } -impl super::PlaneUnits<'_, S, F, C> { +/// What the attempt loop tells a unit's kernel steps of each attempt: its [`super::DriverSteps`] +/// hook, and nothing for a unit with no steps of its own (a health probe). +pub trait AttemptSteps { + /// An attempt starts on a member of `provider`, the unit's request read in `dialect`. + fn attempting(&self, _ctx: &UnitCtx, _dialect: u32, _provider: &str) {} +} + +impl AttemptSteps for S { + fn attempting(&self, ctx: &UnitCtx, dialect: u32, provider: &str) { + super::DriverSteps::attempting(self, ctx, dialect, provider); + } +} + +impl AttemptSteps for () {} + +impl super::PlaneUnits<'_, S, F, C> { /// S3: every attempt of the unit, until its reply is complete, it fails, or the driver cancels. /// A session's turn leg ([`session::Turn`]) is the same walk: each attempt picks a member /// inside the destination set sealed at the open (a member outside it is passed over, never @@ -707,19 +730,23 @@ impl super::PlaneUnits<'_, S, F, C> { provider, }) => { run.bufs.passthrough = passthrough; + self.steps.attempting(run.ctx, run.bufs.dialect, &provider); run.bufs.provider = provider; ((name, pool), None) } Ok(Pick::Exhausted { status, retry_after, - }) if attempt_no == 1 => { - ((String::new(), String::new()), Some((status, retry_after))) - } + detail, + }) if attempt_no == 1 => ( + (String::new(), String::new()), + Some((status, retry_after, detail)), + ), Ok(Pick::Exhausted { status, retry_after, - }) => return End::Exhausted(status, retry_after), + detail, + }) => return End::Exhausted(status, retry_after, detail), Ok(Pick::Vetoed { status, text }) => return End::Vetoed(status, text), Err(cause) => return End::Cancel(cause, None), }; @@ -784,7 +811,9 @@ impl super::PlaneUnits<'_, S, F, C> { } if !far_bound { return match terminal { - Some((status, retry_after)) => End::Exhausted(status, retry_after), + Some((status, retry_after, detail)) => { + End::Exhausted(status, retry_after, detail) + } None => End::Failed(ReasonCode::DestinationUnreachable), }; } @@ -813,7 +842,13 @@ impl super::PlaneUnits<'_, S, F, C> { failed = self.far.failure(run.token); continue 'attempt; } - run.lock().facts.far_end_answered = true; + { + let mut facts = run.lock(); + facts.facts.far_end_answered = true; + if first { + facts.facts.relayed = piece.relayed; + } + } run.bufs.input.clear(); run.bufs.input.extend_from_slice(&piece.bytes); let status = piece.status.filter(|_| first); @@ -918,12 +953,18 @@ impl super::PlaneUnits<'_, S, F, C> { } _ => { let n = emitted.len(); - let streamed = run.lock().facts.streamed; + let (streamed, relayed) = { + let held = run.lock(); + (held.facts.streamed, held.facts.relayed) + }; // A retry verdict fails over only before the first byte; after it, it is hard. - if out.verdict == VERDICT_RETRY && !streamed { + // A relayed answer (a terminal's dispatch) is never failed over. + if out.verdict == VERDICT_RETRY && !streamed && !relayed { return Step::Retry; } - if !streamed && (out.reply_status != 0 || !emitted.is_empty()) { + let headed = run.lock().facts.headed; + if !headed && !streamed && (out.reply_status != 0 || !emitted.is_empty()) { + run.lock().facts.headed = true; // THE ANSWER COMMITS: no failover after the first byte, so the member that // answered is the unit's serving member, the one 1.5.5 ledgered and metered // the response under (v1.5.5 `crates/busbar/src/proxy/usage.rs` diff --git a/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs b/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs index cbce9c7f00..4e47889d78 100644 --- a/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs +++ b/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs @@ -707,6 +707,42 @@ async fn a_401_takes_the_member_down_and_is_relayed_to_the_plane() { ); } +/// THE LEAST-BAD TERMINAL'S ANSWER IS RELAYED (1.5.5 relayed a degraded dispatch's answer and never +/// failed it over): every member failed over, the terminal takes the least-bad one past its +/// breaker, and its answered failure reaches the plane marked relayed, so a retry verdict renders +/// it rather than ending the unit on the exhaustion shed. An ordinary attempt's answer is not. +#[tokio::test] +async fn the_least_bad_terminals_answer_is_relayed_and_an_ordinary_attempts_is_not() { + let r = rig( + &[("a.test", Script::Answer(503, None, vec![b"down"]))], + OnExhausted::LeastBad, + None, + ); + let t = token(); + let far = r.egress.unit(route()); + assert!(matches!(far.member(&t, 1).await, Pick::Member { .. })); + assert!(far.send(&t, request()).await); + let first = far.next(&t).await.expect("a piece"); + assert!( + first.fail_over, + "the walk's own attempt fails over: {first:?}" + ); + assert!(!first.relayed); + assert!( + matches!(far.member(&t, 2).await, Pick::Member { .. }), + "the least-bad terminal takes the member again" + ); + assert!(far.send(&t, request()).await); + let relayed = far.next(&t).await.expect("a piece"); + assert!(!relayed.fail_over, "{relayed:?}"); + assert!( + relayed.relayed, + "the terminal's answer is the unit's: {relayed:?}" + ); + assert_eq!(relayed.status.map(|(s, _)| s), Some(503)); + assert_eq!(relayed.bytes, b"down"); +} + /// A PASSTHROUGH member's 401 is the caller's own key failing (1.5.5's attempt classifier): nothing /// is recorded against the member, and the answer is relayed to the plane as it came. #[tokio::test] @@ -792,7 +828,8 @@ async fn exhaustion_sheds_with_the_retry_after_floor() { far.member(&t, 2).await, Pick::Exhausted { status: 503, - retry_after: Some(2) + retry_after: Some(2), + detail: busbar_kernel_egress::wire::DETAIL_OVERLOADED, } ); // A genuine cooldown is quoted instead of the floor. @@ -806,7 +843,8 @@ async fn exhaustion_sheds_with_the_retry_after_floor() { far.member(&t, 1).await, Pick::Exhausted { status: 503, - retry_after: Some(9) + retry_after: Some(9), + detail: busbar_kernel_egress::wire::DETAIL_OVERLOADED, } ); } @@ -1105,6 +1143,47 @@ async fn passthrough_hands_the_callers_credential_only_to_its_member() { assert_eq!(relays, vec![false, true]); } +/// PASSTHROUGH THROUGH A POOL: a member its pool reaches with the caller's own credential (the +/// pool's `upstream_credentials: passthrough`) has its one auth call lent the caller's credential, +/// though its binding is its provider's own; and a caller who presented none lends an empty one, so +/// nothing is presented (never the operator's key), as 1.5.5's `present_caller` did. +#[tokio::test] +async fn a_passthrough_pool_member_is_lent_the_callers_credential_or_an_empty_one() { + let mut r = rig( + &[ + ("a.test", Script::Answer(200, None, vec![b"ok"])), + ("b.test", Script::Answer(200, None, vec![b"ok"])), + ], + OnExhausted::Status503, + None, + ); + for member in &mut r.egress.pools.get_mut(POOL).unwrap().members { + member.passthrough = true; + } + let t = token(); + for credential in [Some(b"caller-key".to_vec()), None] { + let far = r.egress.unit(UnitRoute { + caller_credential: credential.clone().map(Into::into), + ..route() + }); + let Pick::Member { + name, passthrough, .. + } = far.member(&t, 1).await + else { + panic!("a member"); + }; + assert!(passthrough, "{name}: the plane is told the member relays"); + assert!(far.send(&t, request()).await); + let _ = drain(&far, &t).await; + } + let lent = r.auth.callers.lock().unwrap().clone(); + let presented: Vec>> = lent.into_iter().collect(); + assert_eq!( + presented, + vec![Some(b"caller-key".to_vec()), Some(Vec::new())] + ); +} + /// AUTH POINTS: a member whose style signs the body (`HeadBody`) has its one auth call made at /// `HeadBody` with the whole body, the bytes the framer sends; a member whose style is over the /// head is called at `Head` and lent no body. @@ -1226,9 +1305,116 @@ async fn a_context_length_refusal_excludes_only_admissible_smaller_windows() { ); } -#[allow(unsafe_code)] -#[path = "probe_unit_tests.rs"] -mod probe_unit; +/// THE HOOKS' CONSTRAINT ON THE WALK (ARCHITECT K5: `FarEnd` candidates/constrain): the walk +/// reports its pool's members with what the operator states of them, and once constrained picks +/// only the members the hooks kept, the hooks' order first. +#[tokio::test] +async fn the_hooks_constraint_orders_and_restricts_the_pick() { + use crate::plane_driver::{Constraint, MemberFacts}; + let r = rig( + &[ + ("a.test", Script::Answer(200, None, vec![b"a"])), + ("b.test", Script::Answer(200, None, vec![b"b"])), + ("c.test", Script::Answer(200, None, vec![b"c"])), + ], + OnExhausted::Status503, + None, + ); + let t = token(); + let described = Arc::new(HashMap::from([( + "m2".to_string(), + MemberFacts { + tags: vec!["baa".to_string()], + tier: Some("gold".to_string()), + ..MemberFacts::default() + }, + )])); + let far = r.egress.unit(route()).described(described); + let candidates = far + .candidates(&t) + .expect("the walk names its pool's members"); + assert_eq!(candidates.pool, POOL); + let names: Vec<(usize, &str, &str)> = candidates + .members + .iter() + .map(|c| (c.idx, c.model.as_str(), c.provider.as_str())) + .collect(); + assert_eq!(names, [(0, "m0", "p0"), (1, "m1", "p1"), (2, "m2", "p2")]); + assert_eq!(candidates.members[2].tags, ["baa"]); + assert_eq!(candidates.members[2].tier.as_deref(), Some("gold")); + // The hooks keep m1 and m2 and prefer m2: the weighted walk (m0 heaviest) is overridden. + far.constrain( + &t, + Constraint { + keep: Some(vec![1, 2]), + order: Some(vec![2]), + ..Constraint::default() + }, + ); + let picked = |p: Pick| match p { + Pick::Member { name, .. } => name, + other => panic!("{other:?}"), + }; + assert_eq!( + picked(far.member(&t, 1).await), + "m2", + "the hooks' order first" + ); + assert!(far.send(&t, request()).await); + let _ = drain(&far, &t).await; + assert_eq!( + picked(far.member(&t, 2).await), + "m1", + "then what the hooks kept" + ); +} + +/// A required restrict the hooks decided holds at the fallback pool's boundary: no member there +/// carries its tags, so the walk refuses as the hook would (the previous release failed closed +/// there rather than spill to an ineligible far end). +#[tokio::test] +async fn a_required_restrict_refuses_at_the_fallback_pool() { + use crate::plane_driver::{Constraint, Restrict}; + let mut r = rig( + &[("a.test", Script::Refused)], + OnExhausted::FallbackPool("fb".into()), + None, + ); + let fallback = Pool { + name: "fb".into(), + members: vec![Member::new(DestinationId::new(1), "m0", 1)], + failover: Failover { + timeout_secs: 30, + max_hops: 3, + exclusions: Vec::new(), + }, + on_exhausted: OnExhausted::Status503, + }; + r.egress.pools.insert("fb".to_string(), fallback); + let t = token(); + let far = r.egress.unit(route()); + far.constrain( + &t, + Constraint { + restricts: vec![Restrict { + tags_any: vec!["baa".to_string()], + on_empty: crate::config::PolicyOnError::Reject, + name: "compliance", + }], + ..Constraint::default() + }, + ); + assert!(matches!(far.member(&t, 1).await, Pick::Member { .. })); + assert!(!far.send(&t, request()).await, "the dial is refused"); + assert_eq!( + far.member(&t, 2).await, + Pick::Vetoed { + status: 503, + text: "No upstream satisfies a required gate's restriction. Please retry shortly." + .to_string(), + } + ); +} /// THE DISPATCH RECORD NAMES ITS UNIT (ARCHITECT P3 (c), 2026-10-02): the write-ahead record the walk /// makes before each dial carries the unit the walk serves, so the root writes it under that unit's @@ -1251,3 +1437,7 @@ async fn every_dispatch_is_recorded_under_its_unit() { assert_eq!(kept.len(), 1, "one record, before the dial"); assert_eq!(kept[0].unit, busbar_contract::UnitKey::new(77)); } + +#[allow(unsafe_code)] +#[path = "probe_unit_tests.rs"] +mod probe_unit; diff --git a/crates/busbar-kernel/src/plane_driver/tests/money_tests.rs b/crates/busbar-kernel/src/plane_driver/tests/money_tests.rs index 2414109087..6226695795 100644 --- a/crates/busbar-kernel/src/plane_driver/tests/money_tests.rs +++ b/crates/busbar-kernel/src/plane_driver/tests/money_tests.rs @@ -649,6 +649,8 @@ fn a_cancel_bill_carries_the_floor_counts_and_never_an_estimate() { let facts = crate::plane_driver::cancel::Facts { far_end_answered: true, streamed: true, + headed: true, + relayed: false, units: vec![ floor(INPUT, 40), UnitCount { diff --git a/crates/busbar-kernel/src/plane_host/engine_view.rs b/crates/busbar-kernel/src/plane_host/engine_view.rs index a889aabaf6..d400ee8a6b 100644 --- a/crates/busbar-kernel/src/plane_host/engine_view.rs +++ b/crates/busbar-kernel/src/plane_host/engine_view.rs @@ -149,3 +149,124 @@ impl EngineTablesView for EmptyEngineTablesView { busbar_contract::config::UpstreamCreds::default() } } + +/// THE KERNEL'S OWN TABLES VIEW over the `pools:`/`models:` sections it resolved (the kernel-owned +/// sections, spec Part 1 line 569: the host resolves and validates them): the lanes in the lane +/// table's order (the order the lane store and the telemetry bank are indexed by), the direct-model +/// index, each pool's members and its fallback pool, and the all-pools upstream-credential default. +/// The read seam answers from it when no plane contributed a runtime of its own, so `/stats`, the +/// `/metrics` lane families, `/v1/models` and the admin pool listing read the same tables a +/// door-served plane walks. The live `on_exhausted: queue` depth is the one the walk reports +/// ([`set_pool_queued_depth`]). +#[derive(Debug, Default)] +pub struct ConfigTables { + lanes: Vec<(String, String, String)>, + by_model: std::collections::HashMap, + pools: std::collections::HashMap>, + fallbacks: std::collections::HashMap, + upstream_credentials: busbar_contract::config::UpstreamCreds, +} + +impl ConfigTables { + /// The tables of one resolved configuration generation. + #[must_use] + pub fn of( + lanes: &[crate::plane_host::LaneInput], + pools: &[crate::plane_host::PoolInput], + by_model: &std::collections::HashMap, + upstream_credentials: busbar_contract::config::UpstreamCreds, + ) -> Self { + ConfigTables { + lanes: lanes + .iter() + .map(|l| (l.model.clone(), l.provider.clone(), l.base_url.clone())) + .collect(), + by_model: by_model.clone(), + pools: pools + .iter() + .map(|p| { + ( + p.name.clone(), + p.members.iter().map(|m| (m.lane_idx, m.weight)).collect(), + ) + }) + .collect(), + fallbacks: pools + .iter() + .filter_map(|p| match &p.on_exhausted { + crate::plane_host::OnExhaustedInput::FallbackPool(to) => { + Some((p.name.clone(), to.clone())) + } + _ => None, + }) + .collect(), + upstream_credentials, + } + } +} + +/// The live `on_exhausted: queue` depth of each pool, as the walk reports it. +static QUEUED: std::sync::LazyLock>> = + std::sync::LazyLock::new(Default::default); + +/// Record `pool`'s live wait-terminal depth (the walk's balanced park/leave count). +pub fn set_pool_queued_depth(pool: &str, depth: u64) { + QUEUED + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .insert(pool.to_string(), depth); +} + +impl EngineTablesView for ConfigTables { + fn pools(&self) -> Vec<(&str, Vec)> { + self.pools + .iter() + .map(|(name, members)| (name.as_str(), members.iter().map(|(i, _)| *i).collect())) + .collect() + } + fn pool_exists(&self, pool: &str) -> bool { + self.pools.contains_key(pool) + } + fn model_indices(&self) -> Vec<(&str, usize)> { + self.by_model + .iter() + .map(|(m, &idx)| (m.as_str(), idx)) + .collect() + } + fn model_index(&self, model: &str) -> Option { + self.by_model.get(model).copied() + } + fn lane_view(&self, idx: usize) -> Option> { + self.lanes + .get(idx) + .map(|(model, provider, base_url)| LaneView { + model, + provider, + base_url, + }) + } + fn lane_count(&self) -> usize { + self.lanes.len() + } + fn pool_members(&self, pool: &str) -> Vec<(usize, u32)> { + self.pools.get(pool).cloned().unwrap_or_default() + } + fn queued_depth(&self, pool: &str) -> u64 { + QUEUED + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .get(pool) + .copied() + .unwrap_or(0) + } + fn on_exhausted_fallback(&self, pool: &str) -> Option { + self.fallbacks.get(pool).cloned() + } + fn upstream_creds(&self) -> busbar_contract::config::UpstreamCreds { + self.upstream_credentials + } +} + +#[cfg(test)] +#[path = "tests/config_tables_tests.rs"] +mod config_tables_tests; diff --git a/crates/busbar-kernel/src/plane_host/mod.rs b/crates/busbar-kernel/src/plane_host/mod.rs index 42544c8b42..914ccc2300 100644 --- a/crates/busbar-kernel/src/plane_host/mod.rs +++ b/crates/busbar-kernel/src/plane_host/mod.rs @@ -1830,7 +1830,8 @@ pub use crate::plane_host::build_input::{ TripInput, TripModeInput, }; pub use crate::plane_host::engine_view::{ - EmptyEngineTablesView, EngineTablesView, LaneView, EMPTY_VIEW, + set_pool_queued_depth, ConfigTables, EmptyEngineTablesView, EngineTablesView, LaneView, + EMPTY_VIEW, }; use crate::store::Unavailable; use crate::trust::validate::{Lapsed, Standing}; diff --git a/crates/busbar-kernel/src/plane_host/tests/config_tables_tests.rs b/crates/busbar-kernel/src/plane_host/tests/config_tables_tests.rs new file mode 100644 index 0000000000..59d4abe27a --- /dev/null +++ b/crates/busbar-kernel/src/plane_host/tests/config_tables_tests.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! The kernel's own tables view over the `pools:`/`models:` sections it resolved: the read seam's +//! answer when no plane contributes a runtime, so it must project exactly what the sections say. + +use std::collections::HashMap; + +use super::{ConfigTables, EngineTablesView}; +use crate::plane_host::{AuthStyleInput, LaneInput, OnExhaustedInput, PoolInput, PoolMemberInput}; + +fn lane(model: &str, provider: &str) -> LaneInput { + LaneInput { + model: model.to_string(), + provider: provider.to_string(), + protocol: "widget".to_string(), + base_url: format!("https://{provider}.invalid"), + path: None, + path_base: None, + organization: None, + project: None, + upstream_model: None, + api_key: busbar_contract::redacted::Redacted::new(String::new()), + auth_style: AuthStyleInput::Default, + scope: None, + token_url: None, + subject: None, + error_map: HashMap::new(), + health: None, + allow_metadata_hosts: Vec::new(), + context_max: None, + lane_default_max_tokens: None, + attempt_timeout_ms: None, + reasoning: false, + prompt_caching: false, + lane_caps: Default::default(), + max_concurrent: 1, + limited: false, + budget: -1, + } +} + +fn member(model: &str, lane_idx: usize, weight: u32) -> PoolMemberInput { + PoolMemberInput { + model: model.to_string(), + lane_idx, + weight, + reasoning: None, + attempt_timeout_ms: None, + tier: None, + cost_per_mtok: None, + tags: Vec::new(), + } +} + +fn pool(name: &str, members: Vec, on_exhausted: OnExhaustedInput) -> PoolInput { + PoolInput { + name: name.to_string(), + members, + failover: None, + affinity: None, + on_exhausted, + upstream_credentials: None, + breaker: None, + } +} + +#[test] +fn the_tables_project_the_resolved_sections_in_lane_order() { + let lanes = vec![lane("m-a", "pa"), lane("m-b", "pb")]; + let pools = vec![ + pool( + "primary", + vec![member("m-a", 0, 3), member("m-b", 1, 1)], + OnExhaustedInput::FallbackPool("spare".to_string()), + ), + pool( + "spare", + vec![member("m-b", 1, 1)], + OnExhaustedInput::default(), + ), + ]; + let by_model: HashMap = [("m-a".to_string(), 0), ("m-b".to_string(), 1)].into(); + let t = ConfigTables::of( + &lanes, + &pools, + &by_model, + busbar_contract::config::UpstreamCreds::Passthrough, + ); + assert_eq!(t.lane_count(), 2); + let b = t.lane_view(1).expect("lane 1"); + assert_eq!( + (b.model, b.provider, b.base_url), + ("m-b", "pb", "https://pb.invalid") + ); + assert!(t.lane_view(2).is_none()); + assert_eq!(t.model_index("m-a"), Some(0)); + assert!(t.pool_exists("primary") && !t.pool_exists("m-a")); + assert_eq!(t.pool_members("primary"), vec![(0, 3), (1, 1)]); + assert_eq!(t.on_exhausted_fallback("primary").as_deref(), Some("spare")); + assert_eq!(t.on_exhausted_fallback("spare"), None); + assert_eq!( + t.upstream_creds(), + busbar_contract::config::UpstreamCreds::Passthrough + ); + let mut pools_seen: Vec<(&str, Vec)> = t.pools(); + pools_seen.sort(); + assert_eq!( + pools_seen, + vec![("primary", vec![0, 1]), ("spare", vec![1])] + ); +} + +#[test] +fn the_queue_depth_is_the_one_the_walk_reports() { + let t = ConfigTables::default(); + assert_eq!(t.queued_depth("config-tables-q"), 0); + super::set_pool_queued_depth("config-tables-q", 2); + assert_eq!(t.queued_depth("config-tables-q"), 2); + super::set_pool_queued_depth("config-tables-q", 0); + assert_eq!(t.queued_depth("config-tables-q"), 0); +} diff --git a/crates/busbar-kernel/src/plane_routes.rs b/crates/busbar-kernel/src/plane_routes.rs index b2fafaf7eb..e95a06b5b4 100644 --- a/crates/busbar-kernel/src/plane_routes.rs +++ b/crates/busbar-kernel/src/plane_routes.rs @@ -102,6 +102,10 @@ pub struct PlaneReqCtx { pub gov: Option, /// The middleware-resolved auth principal, or `None` on a `RouteAuth::None` route. pub principal: Option, + /// The caller's verified credential as the auth gate extracted it, lent for the host's egress + /// alone (a passthrough member's outbound auth call); `None` when the caller presented none or + /// the route bypassed the gate. A plane handler never reads it. + pub caller_credential: Option>>, /// The live engine handle, type-erased. The core adapter erases the router's `Arc` /// state here; a plane still coupled to the engine downcasts it (a transitional reach that the /// per-subsystem App-sever removes), but the SEAM names no core type. diff --git a/crates/busbar-kernel/src/router.rs b/crates/busbar-kernel/src/router.rs index cbf3ec5a05..2864d8af25 100644 --- a/crates/busbar-kernel/src/router.rs +++ b/crates/busbar-kernel/src/router.rs @@ -347,6 +347,9 @@ pub(crate) fn base_data_router( crate::core_routes::CoreRouteTable, ) { use busbar_contract::abi::mechanism::route::{RouteAuth, RouteMethod}; + // A door plane whose claim is the whole tree (`/` and every path under it) claims the + // convenience surfaces' paths too, so the kernel mounts none of its own there. + let door_claims_root = doors.iter().any(|d| d.path.starts_with("/{*")); // EVERY core route is mounted through `CoreRouter::route`, which takes the handler and the // admission bar in ONE act (`core_routes`): a route the auth middleware knows nothing about is // not a thing this function can produce. @@ -415,19 +418,27 @@ pub(crate) fn base_data_router( RouteMethod::Get, RouteAuth::Key, endpoints::list_models_v1beta, - ) - .route( - "/{name}/v1/messages", - RouteMethod::Post, - RouteAuth::Key, - ingress::named, - ) - .route( - "/{provider}/{model}/v1/messages", - RouteMethod::Post, - RouteAuth::Key, - ingress::adhoc, ); + // THE CONVENIENCE SURFACES hand their unit to a body-model arrival, so they are mounted only + // where a plane registered one and no door plane claims the whole tree (a plane served through + // its door parses these paths itself). + let router = if crate::ingress::arrival::any_body_ingress() && !door_claims_root { + router + .route( + "/{name}/v1/messages", + RouteMethod::Post, + RouteAuth::Key, + ingress::named, + ) + .route( + "/{provider}/{model}/v1/messages", + RouteMethod::Post, + RouteAuth::Key, + ingress::adhoc, + ) + } else { + router + }; // THE PLANES' DATA ROUTES, contributed through the registry rather than named here. For every // registered plane with a `mount` fn AND a runtime object this generation (its slot), the plane's // own `mount` mounts its routes from that slot, each door opening only when that plane's own @@ -457,9 +468,26 @@ pub(crate) fn base_data_router( // the protocol fallback, by the same adapter and at the same bar a plane route takes. A claim // carries no slot: its handler holds what it serves. let unslotted: std::sync::Arc = std::sync::Arc::new(()); + let door_paths: std::collections::BTreeSet = + doors.iter().map(|spec| spec.path.clone()).collect(); for spec in doors { router = mount_plane_route(router, std::sync::Arc::clone(&unslotted), spec); } + // A verb no route on a door path takes is answered as the previous release answered it on that + // path, from its catch-all: the native-envelope 405, after the data-plane bar, and never with + // an `Allow` field (a door path is no fixed route of the previous release's, so it named none). + for path in door_paths { + let claimed = router + .methods_at(&path) + .into_iter() + .map(crate::plugin_routes::method_filter_of) + .reduce(axum::routing::MethodFilter::or); + if let Some(rest) = claimed.and_then(unclaimed_verbs) { + router = router.map_router(|r| { + r.route(&path, axum::routing::on(rest, method_not_allowed_handler)) + }); + } + } // 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 @@ -506,6 +534,47 @@ pub(crate) fn base_data_router( .into_parts() } +/// Every verb a door path's claims leave unclaimed, as one filter; `None` when they claim them all. +/// `HEAD` rides `GET` where `GET` is claimed (axum serves it from the `GET` handler). +fn unclaimed_verbs(claimed: axum::routing::MethodFilter) -> Option { + use axum::routing::MethodFilter; + let all = [ + (MethodFilter::GET, axum::http::Method::GET), + (MethodFilter::POST, axum::http::Method::POST), + (MethodFilter::PUT, axum::http::Method::PUT), + (MethodFilter::PATCH, axum::http::Method::PATCH), + (MethodFilter::DELETE, axum::http::Method::DELETE), + (MethodFilter::OPTIONS, axum::http::Method::OPTIONS), + (MethodFilter::TRACE, axum::http::Method::TRACE), + (MethodFilter::CONNECT, axum::http::Method::CONNECT), + (MethodFilter::HEAD, axum::http::Method::HEAD), + ]; + let get_claimed = claimed_has(claimed, &axum::http::Method::GET); + all.into_iter() + .filter(|(_, m)| !claimed_has(claimed, m)) + .filter(|(_, m)| !(get_claimed && *m == axum::http::Method::HEAD)) + .map(|(f, _)| f) + .reduce(MethodFilter::or) +} + +/// Whether `filter` takes `method`. +fn claimed_has(filter: axum::routing::MethodFilter, method: &axum::http::Method) -> bool { + use axum::routing::MethodFilter; + let one = match *method { + axum::http::Method::GET => MethodFilter::GET, + axum::http::Method::POST => MethodFilter::POST, + axum::http::Method::PUT => MethodFilter::PUT, + axum::http::Method::PATCH => MethodFilter::PATCH, + axum::http::Method::DELETE => MethodFilter::DELETE, + axum::http::Method::OPTIONS => MethodFilter::OPTIONS, + axum::http::Method::TRACE => MethodFilter::TRACE, + axum::http::Method::CONNECT => MethodFilter::CONNECT, + axum::http::Method::HEAD => MethodFilter::HEAD, + _ => return false, + }; + filter.or(one) == filter +} + /// Mount ONE neutral [`busbar_kernel::plane_routes::PlaneRouteSpec`] onto the core router (S4a /// Option A). This is the SINGLE place a plane's neutral route touches core router vocabulary: it /// calls the SAME [`crate::core_routes::CoreRouter::route`] the legacy `mount` fns called, with the @@ -549,6 +618,10 @@ fn mount_plane_route( // The plane never sees the caller's credential: the headers the gate consumed go here, // before the context — and the HOT request head built from it — exists (#65, #40(b)). auth::ConsumedCredentials::strip_from(consumed.as_deref(), &mut headers); + let caller_credential = consumed + .as_deref() + .and_then(|c| c.caller.as_ref()) + .map(auth::CallerCredential::lend); let handler = handler.clone(); let slot = slot.clone(); let ctx_path = ctx_path.clone(); @@ -579,6 +652,7 @@ fn mount_plane_route( caller_principal, gov, principal: principal.map(|axum::extract::Extension(p)| p), + caller_credential, engine, host, slot, diff --git a/crates/busbar-kernel/src/state.rs b/crates/busbar-kernel/src/state.rs index 887c0267da..5b83442207 100644 --- a/crates/busbar-kernel/src/state.rs +++ b/crates/busbar-kernel/src/state.rs @@ -80,6 +80,13 @@ pub struct App { /// An ABSENT slot — the featureless binary boots with no fallback plane configured, so none was /// inserted — reads as the substrate-resident empty view, never a panic. Neutral: names no dialect. pub fallback_runtime_key: &'static str, + /// The composition root's projection of the configuration this generation was built from + /// ([`crate::appbuild::ConfigProjection`]); `None` when the root bound none. + pub config_projection: Option>, + /// The kernel's own tables over the `pools:`/`models:` sections this generation resolved: the + /// read seam's answer when no plane contributed a runtime of its own (a door-served plane walks + /// these sections through its door). + pub config_tables: Arc, pub store: Arc, /// THE CONTAINER PLANES' BREAKER CELLS — the degenerate single-member cell per registered /// container-plane member (the breaker-all-planes audit's closing design). Live state, shared by @@ -538,7 +545,7 @@ impl App { self.plane_slot(key), ) { (Some(viewer), Some(slot)) => viewer(slot.as_ref()), - _ => &busbar_kernel::plane_host::EMPTY_VIEW, + _ => &*self.config_tables, } } @@ -802,8 +809,14 @@ pub struct AppHandle { /// host. Bound once by the composition root ([`attach_on_swap`](Self::attach_on_swap)), which is /// the one place allowed to name the plane that owns them; unbound, a swap re-attaches nothing. attach: std::sync::OnceLock)>, + /// What the composition root re-seals against a NEW generation once it is swapped in (its door + /// planes' refresh); unbound, a swap re-seals nothing. + on_apply: std::sync::OnceLock, } +/// The composition root's re-seal over a newly swapped-in generation. +pub type OnApply = Arc) + Send + Sync>; + impl AppHandle { pub fn new(app: Arc) -> Self { Self { @@ -812,6 +825,7 @@ impl AppHandle { swapping: std::sync::atomic::AtomicBool::new(false), snapshot_host: std::sync::Mutex::new(None), attach: std::sync::OnceLock::new(), + on_apply: std::sync::OnceLock::new(), } } @@ -823,6 +837,13 @@ impl AppHandle { let _ = self.attach.set(attach); } + /// Bind what every [`swap`](Self::swap) re-seals against the incoming generation once it is the + /// current one: the composition root's door planes, refreshed onto its configuration (spec + /// Part 1 line 569, one validated object per `refresh`). First binding wins. + pub fn attach_on_apply(&self, on_apply: OnApply) { + let _ = self.on_apply.set(on_apply); + } + /// Bind the composition-root-owned ENGINE HOST for the CURRENT generation — the host the active /// health probers hold a `Weak` to (App-retype WEDGE 2f). Called once at boot after the probers are /// spawned against this same host, so the handle owns the only strong reference the probers depend @@ -901,6 +922,9 @@ impl AppHandle { } } self.current.store(next.clone()); + if let Some(on_apply) = self.on_apply.get() { + on_apply(&next); + } // RETIRE the OUTGOING generation's engine host (App-retype WEDGE 2f). The active health probers // hold a `Weak` to the composition-root-owned host of the snapshot they were // spawned against; dropping it here makes their `Weak::upgrade` fail, so they exit rather than diff --git a/crates/busbar-kernel/src/store/in_memory/mod.rs b/crates/busbar-kernel/src/store/in_memory/mod.rs index dde0ccb2cd..89e3df3023 100644 --- a/crates/busbar-kernel/src/store/in_memory/mod.rs +++ b/crates/busbar-kernel/src/store/in_memory/mod.rs @@ -526,7 +526,7 @@ use busbar_kernel::store::{BreakerCfg, TripConfig, TripMode}; /// back to the ADR-0002 defaults. Was an inherent `config::BreakerCfg::to_runtime` method; now a /// free function because BOTH the config type and the runtime type are foreign to this crate /// (substrate-owned), which the orphan rule forbids an inherent impl over. -pub(crate) fn breaker_cfg_to_runtime(cfg: &crate::config::BreakerCfg) -> BreakerCfg { +pub fn breaker_cfg_to_runtime(cfg: &crate::config::BreakerCfg) -> BreakerCfg { let trip = cfg .trip .as_ref() diff --git a/crates/busbar-kernel/src/telemetry.rs b/crates/busbar-kernel/src/telemetry.rs index 0e77f21c3e..1ebceb3922 100644 --- a/crates/busbar-kernel/src/telemetry.rs +++ b/crates/busbar-kernel/src/telemetry.rs @@ -303,6 +303,44 @@ pub(crate) fn request_finished( } } +/// `busbar_requests_total` + `busbar_request_duration_seconds` for one finished request of the +/// plane that serves the `pools` map (the fallback plane's 1.5.5 families, `{ingress_protocol, +/// pool, outcome}` and no `plane` label), its outcome classified from the caller's `status` +/// ([`outcome_of`]): the request families a unit served through that plane's door emits at its end, +/// as the previous release's `ingress::finish_inner` emitted them. +pub fn model_request_finished( + app: &App, + ingress_protocol: &str, + pool: &str, + status: u16, + seconds: f64, +) { + let outcome = outcome_of(status); + request_finished( + app, + crate::plane::fallback_key(), + ingress_protocol, + pool, + outcome, + seconds, + ); + // The best-effort request log, under the same compute gate the previous release's request path + // read (`ingress::finish_inner`): produced only when a configured export subscribes to it. + if app + .export_projections + .wants_stream(busbar_contract::abi::export::ExportStream::Logs) + { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + crate::export::deliver_request_log(&crate::export::RequestLogFacts { + ts: busbar_kernel::store::now(), + ingress_protocol, + pool, + outcome, + latency_ms: (seconds * 1000.0) as u64, + }); + } +} + // `upstream_attempt_on` / `upstream_failure_on` — THE EMIT for this family, on EVERY plane — MOVED // DOWN to the neutral substrate (`busbar_kernel::telemetry`) so a plane's own synchronous client // leg can name them without reaching into core. They take NO `&App` and diff --git a/crates/busbar-kernel/src/test_support/mod.rs b/crates/busbar-kernel/src/test_support/mod.rs index 5fdfa9212e..9649774d2e 100644 --- a/crates/busbar-kernel/src/test_support/mod.rs +++ b/crates/busbar-kernel/src/test_support/mod.rs @@ -1694,6 +1694,11 @@ impl TestApp { // Built and inserted ONLY when a real fallback (LLM) plane owns the key — otherwise `lanes`/ // `by_model`/the `self.*` tables simply drop unused, and `App::llm_runtime` reads the empty // default (a surface with no LLM plane never routes through `engine_tables` anyway). + // THE KERNEL'S OWN TABLES over the same lanes and pools, as production `appbuild` builds them: + // the read seam answers from them when the fallback plane contributes no runtime of its own + // (a door plane), and reads the empty default when no fallback plane is registered. + let mut config_tables = + std::sync::Arc::::default(); if crate::plane::is_fallback(crate::plane::fallback_key()) { // Assemble the NEUTRAL `PlaneBuildInput` (money-path Phase 3-4 C) exactly as production // `appbuild` does, then hand it to the fallback (LLM) plane's REGISTERED `build_runtime` @@ -1758,6 +1763,12 @@ impl TestApp { name, }) .collect(); + config_tables = std::sync::Arc::new(busbar_kernel::plane_host::ConfigTables::of( + &lane_inputs, + &pool_inputs, + &by_model, + self.upstream_credentials, + )); let default_failover = self .failover_cfg .as_ref() @@ -1897,6 +1908,10 @@ impl TestApp { // above into `plane_slots` under this interned key; the snapshot names only the key, and // `App::llm_runtime` downcasts the slot on the money path. fallback_runtime_key, + // The kernel's own tables over the fixture's lanes and pools (built above when a fallback + // plane is registered), read when that plane contributes no runtime of its own. + config_tables, + config_projection: None, store: store.clone(), plane_breakers: std::sync::Arc::new(crate::store::PlaneBreakers::new()), session_store: std::sync::Arc::new(crate::session::SessionStore::new(1024, None)), @@ -2144,10 +2159,13 @@ pub fn hook_axis_stand_in( registry: &std::sync::Arc, ) -> Result, String> { use busbar_plugin_loader::dispatch::{DispatchConfig, Dispatcher}; + static DISPATCHER: std::sync::OnceLock> = std::sync::OnceLock::new(); + let dispatcher = + DISPATCHER.get_or_init(|| std::sync::Arc::new(Dispatcher::new(DispatchConfig::default()))); let rows = busbar_plugin_loader::hook_door::HookRows::new( crate::preflight::STAND_IN_HOOK_DOORS, Some(registry.as_ref()), - std::sync::Arc::new(Dispatcher::new(DispatchConfig::default())), + dispatcher.clone(), )?; Ok(std::sync::Arc::new(rows)) } diff --git a/crates/busbar-kernel/src/tests/members/egress/route/mod.rs b/crates/busbar-kernel/src/tests/members/egress/route/mod.rs index 87957b8e74..30314f58c5 100644 --- a/crates/busbar-kernel/src/tests/members/egress/route/mod.rs +++ b/crates/busbar-kernel/src/tests/members/egress/route/mod.rs @@ -246,6 +246,7 @@ impl Node { Pick::Exhausted { status, retry_after, + .. } => { return Routed::Refused(Refusal { status, diff --git a/crates/busbar-kernel/tests/plane_driver.rs b/crates/busbar-kernel/tests/plane_driver.rs index c8d089cd11..fcdec7d512 100644 --- a/crates/busbar-kernel/tests/plane_driver.rs +++ b/crates/busbar-kernel/tests/plane_driver.rs @@ -15,6 +15,9 @@ mod common; #[path = "support/plane_driver_cases.rs"] mod cases; +#[path = "support/plane_driver_hook_cases.rs"] +mod hook_cases; + use std::collections::HashMap; use std::future::Future; use std::pin::Pin; @@ -23,16 +26,16 @@ use std::sync::{Arc, Mutex, OnceLock}; use std::task::{Context, Poll, Waker}; use std::time::{Duration, Instant}; +use busbar_contract::abi::hook::{MessageView, RequestView}; use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome, Span}; use busbar_contract::abi::mechanism::ticket::Ticket; use busbar_contract::abi::plane::{ - ArriveIn, ArriveOut, OnPieceIn, OnPieceOut, OutField, ProjectIn, ProjectOut, RecordWrite, - RefusalIn, RefusalOut, + ArriveIn, ArriveOut, OnPieceIn, OnPieceOut, OutField, RecordWrite, RefusalIn, RefusalOut, UnitCount, CANCEL_ABORTED, CANCEL_FAILED, CANCEL_OK_PARTIAL, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, PIECE_FIELDS, PIECE_HAS_STATUS, PIECE_LAST, PIECE_OUT_TEXT, PRINCIPAL_OPTIONAL, RECORD_PUT, UNITS_ESTIMATED, UNITS_REPORTED, VERDICT_RETRY, }; -use busbar_contract::abi::plane::{ServeIn, ServeOut}; +use busbar_contract::abi::plane::{ProjectIn, ProjectOut, ServeIn, ServeOut, REFUSAL_GATE}; use busbar_contract::caps::OpClassId; use busbar_contract::plane::{TrustKeyDecl, TrustRole}; use busbar_contract::plane_calls::{ @@ -65,6 +68,11 @@ pub(crate) struct Rig { } impl Rig { + /// Every `project` crossing: the body it was handed and the rewrite, when one came. + pub(crate) fn projects(&self) -> Vec<(Vec, Vec)> { + self.plane.projects.lock().unwrap().clone() + } + pub(crate) fn stats(&self) -> [u64; stat::COUNT] { let mut out = [0; stat::COUNT]; for (k, v) in out.iter_mut().enumerate() { @@ -74,7 +82,17 @@ impl Rig { } } -pub(crate) fn rig(_way: Way, caps: BufferCaps, book: cases::Book) -> Rig { +pub(crate) fn rig(way: Way, caps: BufferCaps, book: cases::Book) -> Rig { + rig_with_hooks(way, caps, book, None) +} + +/// [`rig`], the driver binding `hooks` to each unit. +pub(crate) fn rig_with_hooks( + _way: Way, + caps: BufferCaps, + book: cases::Book, + hooks: Option, +) -> Rig { let plane = Arc::new(Double::default()); let book = Arc::new(book); let driver = PlaneDriver::new( @@ -91,6 +109,10 @@ pub(crate) fn rig(_way: Way, caps: BufferCaps, book: cases::Book) -> Rig { ("test_plane", &serde_yaml::Value::Null), ) .expect("the instance is admitted"); + let driver = match hooks { + Some(h) => driver.with_hooks(Arc::new(h)), + None => driver, + }; Rig { plane, driver, @@ -182,6 +204,8 @@ struct Double { held: Mutex, OnPieceOut)>>, next: AtomicU32, stats: [AtomicU64; stat::COUNT], + /// `project` crossings, and the bodies each was handed (with the rewrite, when one came). + projects: Mutex, Vec)>>, /// The driver tickets minted, and every ticket recycled. drivers: Mutex>, recycled: Mutex>, @@ -452,17 +476,6 @@ impl PlaneCalls for Double { now_ns() } - // The hook stage is dormant in these driver proofs (no plane binds hooks): `project` is the - // trait's required op, answered here as the no-projection pure op. - fn project( - &self, - _input: &mut ProjectIn, - _out: &mut ProjectOut, - _grow: Grow<'_, ProjectIn, ProjectOut>, - ) -> Outcome { - Outcome::Ready - } - fn arrive( &self, input: &mut ArriveIn, @@ -529,10 +542,13 @@ impl PlaneCalls for Double { out: &mut RefusalOut, grow: Grow<'_, RefusalIn, RefusalOut>, ) -> Outcome { - // The reason crosses beside its text: a refusal whose code names another reason is FAULT. - let named = - busbar_contract::abi::plane::reason_of(input.reason).map(|r| r.as_str().as_bytes()); - if named != Some(unsafe { text(input.text) }) { + // The reason crosses beside its text (`RefusalIn::text`, the kernel's own message: the + // reason's spelling, or the walk terminal's own words): a refusal whose code names no reason, + // or that carries no text, is FAULT. A gate's refusal carries the hook's own words instead. + let named = busbar_contract::abi::plane::reason_of(input.reason); + if input.cause != REFUSAL_GATE + && (named.is_none() || unsafe { text(input.text) }.is_empty()) + { return Outcome::Fault; } let mut body = [ @@ -597,6 +613,128 @@ impl PlaneCalls for Double { Outcome::Fault } + /// The view this plane projects: each body line `role:text` is a turn, a `sys=` line the + /// system prompt and a `user=` line the end user. A rewrite (1.5.5's `rewrite` object) is + /// applied by replacing the turns with its messages' `role:content`, unless the target is + /// `/no-rewrite` (a rewrite the plane cannot read); `/unprojectable` refuses to project. + fn project( + &self, + input: &mut ProjectIn, + out: &mut ProjectOut, + grow: Grow<'_, ProjectIn, ProjectOut>, + ) -> Outcome { + let target = unsafe { text(input.target) }.to_vec(); + let body = unsafe { bytes(input.body) }.to_vec(); + let rewrite = unsafe { bytes(input.rewrite) }.to_vec(); + self.projects + .lock() + .unwrap() + .push((body.clone(), rewrite.clone())); + if target == b"/unprojectable" { + return Outcome::Refused; + } + let rewritten = (!rewrite.is_empty() && target != b"/no-rewrite") + .then(|| serde_json::from_slice::(&rewrite).ok()) + .flatten() + .and_then(|v| v.get("messages").and_then(|m| m.as_array()).cloned()) + .map(|messages| { + let lines: Vec = messages + .iter() + .map(|m| { + let field = |k: &str| m.get(k).and_then(|v| v.as_str()).unwrap_or(""); + format!("{}:{}", field("role"), field("content")) + }) + .collect(); + lines.join("\n").into_bytes() + }); + let effective = rewritten.clone().unwrap_or(body); + let effective = String::from_utf8_lossy(&effective).into_owned(); + let (mut system, mut user, mut turns) = (None, None, Vec::new()); + for line in effective.split('\n').filter(|l| !l.is_empty()) { + if let Some(v) = line.strip_prefix("sys=") { + system = Some(v.to_string()); + } else if let Some(v) = line.strip_prefix("user=") { + user = Some(v.to_string()); + } else { + let (role, text) = line.split_once(':').unwrap_or(("user", line)); + turns.push((role.to_string(), text.to_string())); + } + } + let mut strings: Vec<&[u8]> = vec![b"pool-a", b"test-dialect"]; + strings.push(system.as_deref().unwrap_or("").as_bytes()); + strings.push(user.as_deref().unwrap_or("").as_bytes()); + for (role, text) in &turns { + strings.push(role.as_bytes()); + strings.push(text.as_bytes()); + } + let rewritten_bytes = rewritten.unwrap_or_default(); + strings.push(&rewritten_bytes); + let arena: usize = strings.iter().map(|b| b.len()).sum(); + for call in 0..2 { + if turns.len() > input.messages_cap || arena > input.arena_cap { + if call == 1 { + return Outcome::Fault; + } + out.messages_needed = turns.len() as u32; + out.arena_needed = arena as u64; + grow(out, input); + *out = busbar_contract::abi::sdk::door::blank_out(); + continue; + } + // SAFETY (below): the driver's buffers, of the capacities checked above. + let mut at = 0usize; + let mut place = |b: &[u8]| -> AbiStr { + unsafe { + std::ptr::copy_nonoverlapping(b.as_ptr(), input.arena_buf.add(at), b.len()) + }; + let s = AbiStr { + ptr: unsafe { input.arena_buf.add(at) }.cast_const(), + len: b.len(), + }; + at += b.len(); + s + }; + let pool = place(strings[0]); + let dialect = place(strings[1]); + let sys = place(strings[2]); + let end_user = place(strings[3]); + for (k, pair) in strings[4..4 + 2 * turns.len()].chunks(2).enumerate() { + let role = place(pair[0]); + let text = place(pair[1]); + unsafe { *input.messages_buf.add(k) = MessageView { role, text } }; + } + let _ = place(&rewritten_bytes); + let start = arena - rewritten_bytes.len(); + out.view = RequestView { + request_id: 0, + pool, + ingress_dialect: dialect, + message_count: turns.len() as u64, + total_chars: turns.iter().map(|(_, t)| t.len() as u64).sum(), + max_tokens: 0, + flags: 0, + signals: input.signals_buf.cast_const(), + signals_len: 0, + }; + out.prompt.system = sys; + out.prompt.message_count = turns.len() as u64; + out.prompt.messages = input.messages_buf.cast_const(); + out.prompt.messages_len = turns.len(); + out.end_user = end_user; + out.body = Span { + offset: busbar_contract::abi::plane::SPAN_ABSENT, + len: 0, + }; + out.rewritten = Span { + offset: start as u32, + len: rewritten_bytes.len() as u32, + }; + out.arena_written = arena as u64; + return Outcome::Ready; + } + Outcome::Fault + } + fn cancel(&self, ticket: Ticket) -> Option { self.count(stat::CANCELS); let u = self.unit_of(ticket); diff --git a/crates/busbar-kernel/tests/support/plane_driver_cases.rs b/crates/busbar-kernel/tests/support/plane_driver_cases.rs index b5c00e1277..c50d276999 100644 --- a/crates/busbar-kernel/tests/support/plane_driver_cases.rs +++ b/crates/busbar-kernel/tests/support/plane_driver_cases.rs @@ -80,6 +80,7 @@ impl Far { fail_over: false, fields: false, head: Vec::new(), + relayed: false, }) .collect(); Far { @@ -120,6 +121,7 @@ impl FarEnd for Far { None => Pick::Exhausted { status: 503, retry_after: Some(2), + detail: "The service is temporarily overloaded. Please retry shortly.", }, }; async move { pick } @@ -138,6 +140,7 @@ impl FarEnd for Far { fail_over: true, fields: false, head: Vec::new(), + relayed: false, }] } else if request.member.starts_with("trailers") { // The body, then the far end's trailers after it. @@ -958,8 +961,9 @@ async fn exhaustion_answers_the_walks_status_and_retry_after() { let rendered = units.take_rendered().expect("the terminal is rendered"); assert_eq!(rendered.status, 503, "{way:?}"); assert_eq!( - rendered.body, b"refused:503:breaker_open:retry=2", - "{way:?}: the plane renders the walk's status and Retry-After" + rendered.body, + b"refused:503:The service is temporarily overloaded. Please retry shortly.:retry=2", + "{way:?}: the plane renders the walk's status, Retry-After and the shed's own words" ); assert_eq!(caller.text(), "", "{way:?}: nothing had streamed"); } diff --git a/crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs b/crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs new file mode 100644 index 0000000000..80d07bf2c9 --- /dev/null +++ b/crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE HOOK STAGE ON THE DRIVER, against the contract-level plane double (`BUSBAR-1.6.0.md` Part 3, +//! section 12, "Hooks"; ARCHITECT K5): no hook bound projects nothing; a request-stage hook's +//! rewrite goes back to the plane (`ProjectIn::rewrite`), which applies it and answers the body the +//! kernel keeps and re-pushes on every attempt; a veto wears the hook's own status and words, +//! rendered by the plane (`REFUSAL_GATE`), before any attempt; a request the plane cannot project +//! is refused before any attempt. The 1.5.5 hook tests themselves run over the real llm plane in +//! the composition root's `hook_parity_driver` suite. + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use busbar_contract::caps::{Outcome, ReasonCode}; +use busbar_contract::hooks::{ + BudgetBucketState, Candidate, PolicyResult, RewriteReply, RoutingContext, RoutingDecision, + RoutingPolicy, RoutingRequest, TransformOutcome, +}; +use busbar_kernel::config::PolicyOnError; +use busbar_kernel::hooks::{RequestedSignals, ResolvedPolicy}; +use busbar_kernel::plane_driver::{BoundHooks, BufferCaps, CallerFacts, CallerKey, StageTaps}; + +use super::cases::{arrival, drive, Book, Caller, Far}; +use super::common::TestUnits; +use super::{rig_with_hooks, Way}; + +/// A caller the hooks know nothing of. +struct Nobody; + +impl CallerFacts for Nobody { + fn key(&self, _: &str) -> Option { + None + } + fn in_groups(&self, _: Option<&str>, groups: &[String]) -> bool { + groups.is_empty() + } + fn rate_headroom(&self, _: &str, _: &str) -> Option { + None + } + fn budget(&self, _: &str) -> Vec { + Vec::new() + } +} + +fn bound( + rewrites: Vec<(Duration, Arc)>, + gates: Vec<(u16, ResolvedPolicy)>, +) -> BoundHooks { + BoundHooks { + rewrites, + gates, + pool_rewrites: Default::default(), + pool_gates: Default::default(), + pool_policies: Default::default(), + taps: StageTaps::default(), + requested: RequestedSignals::default(), + next_request_id: Arc::new(|| 1), + caller: Arc::new(Nobody), + dialects: vec!["test-dialect".to_string()], + } +} + +/// The turns a gate was shown, per call. +type Seen = Arc>>>; + +/// A gate that sees the prompt, records it, and decides `reject`. +struct Gate { + seen: Seen, + reject: Option<(u16, &'static str)>, +} + +#[async_trait::async_trait] +impl RoutingPolicy for Gate { + async fn decide( + &self, + req: &RoutingRequest<'_>, + _: &[Candidate<'_>], + _: &RoutingContext<'_>, + _: Duration, + ) -> PolicyResult { + let turns = req + .prompt + .as_ref() + .map(|p| { + p.messages + .iter() + .map(|(r, t)| (r.to_string(), t.to_string())) + .collect() + }) + .unwrap_or_default(); + self.seen.lock().unwrap().push(turns); + Ok(match self.reject { + Some((status, message)) => RoutingDecision::Reject { + status, + message: message.to_string(), + }, + None => RoutingDecision::Abstain, + }) + } + fn name(&self) -> &'static str { + "gate" + } +} + +fn gate(reject: Option<(u16, &'static str)>) -> (Seen, ResolvedPolicy) { + let seen: Seen = Arc::new(Mutex::new(Vec::new())); + ( + seen.clone(), + ResolvedPolicy::Policy { + policy: Arc::new(Gate { seen, reject }), + on_error: PolicyOnError::default(), + on_error_chain: Vec::new(), + timeout: Duration::from_millis(500), + send_prompt: true, + send_user: false, + on_empty: PolicyOnError::Reject, + }, + ) +} + +/// A rewrite hook replacing the turns with one. +struct Rewriter; + +#[async_trait::async_trait] +impl RoutingPolicy for Rewriter { + async fn decide( + &self, + _: &RoutingRequest<'_>, + _: &[Candidate<'_>], + _: &RoutingContext<'_>, + _: Duration, + ) -> PolicyResult { + Ok(RoutingDecision::Abstain) + } + fn name(&self) -> &'static str { + "rewriter" + } + async fn transform(&self, _: &RoutingRequest<'_>, _: Duration) -> TransformOutcome { + TransformOutcome::Rewrite(RewriteReply { + messages: vec![serde_json::json!({"role": "user", "content": "COMPRESSED"})], + tools: vec![], + }) + } +} + +#[tokio::test] +async fn no_hook_bound_projects_nothing() { + let r = rig_with_hooks(Way::Double, BufferCaps::default(), Book::default(), None); + let (steps, far, caller) = ( + TestUnits::passing(), + Far::new(&["ok"], &[b"done"]), + Caller::default(), + ); + let units = r + .driver + .unit(&steps, &far, &caller, arrival("/call", b"user:hello"), 0); + assert!(matches!(drive(&units).await, Outcome::Completed)); + assert!( + r.projects().is_empty(), + "a unit no hook binds crosses no `project`" + ); +} + +#[tokio::test] +async fn a_rewrite_is_applied_by_the_plane_and_re_pushed_on_every_attempt() { + let (seen, g) = gate(None); + let hooks = bound( + vec![(Duration::from_millis(500), Arc::new(Rewriter))], + vec![(0, g)], + ); + let r = rig_with_hooks( + Way::Double, + BufferCaps::default(), + Book::default(), + Some(hooks), + ); + let (steps, far, caller) = ( + TestUnits::passing(), + Far::new(&["overloaded", "ok"], &[b"done"]), + Caller::default(), + ); + let units = r + .driver + .unit(&steps, &far, &caller, arrival("/call", b"user:hello"), 0); + assert!(matches!(drive(&units).await, Outcome::Completed)); + let projects = r.projects(); + assert_eq!( + projects.len(), + 2, + "the first view, then the rewrite applied and re-projected" + ); + assert!( + projects[0].1.is_empty(), + "the first `project` carries no rewrite" + ); + assert!( + !projects[1].1.is_empty(), + "the second carries the hook's rewrite" + ); + assert_eq!( + *seen.lock().unwrap(), + vec![vec![("user".to_string(), "COMPRESSED".to_string())]], + "the gate after the rewrite sees the rewritten request" + ); + let sent = far.sent(); + assert_eq!( + sent.len(), + 2, + "one failed-over attempt, then the served one" + ); + for request in &sent { + assert_eq!( + request.body, b"user:COMPRESSED", + "every attempt carries the body the rewrite left" + ); + } +} + +#[tokio::test] +async fn a_veto_is_rendered_by_the_plane_with_the_hooks_status_and_words_before_any_attempt() { + let (_, g) = gate(Some((451, "blocked\r\nby policy"))); + let r = rig_with_hooks( + Way::Double, + BufferCaps::default(), + Book::default(), + Some(bound(Vec::new(), vec![(0, g)])), + ); + let (steps, far, caller) = ( + TestUnits::passing(), + Far::new(&["ok"], &[b"done"]), + Caller::default(), + ); + let units = r + .driver + .unit(&steps, &far, &caller, arrival("/call", b"user:hello"), 0); + let outcome = drive(&units).await; + assert!( + matches!(outcome, Outcome::Failed(_, ReasonCode::HookVeto)), + "{outcome:?}" + ); + let rendered = units.take_rendered().expect("the plane rendered the veto"); + assert_eq!(rendered.status, 451); + assert_eq!( + rendered.body, b"refused:451:blockedby policy", + "the hook's status and its sanitised words, in the plane's rendering" + ); + assert!(far.sent().is_empty(), "no attempt was made"); +} + +#[tokio::test] +async fn a_request_the_plane_cannot_project_is_refused_before_any_attempt() { + let (seen, g) = gate(None); + let r = rig_with_hooks( + Way::Double, + BufferCaps::default(), + Book::default(), + Some(bound(Vec::new(), vec![(0, g)])), + ); + let (steps, far, caller) = ( + TestUnits::passing(), + Far::new(&["ok"], &[b"done"]), + Caller::default(), + ); + let units = r.driver.unit( + &steps, + &far, + &caller, + arrival("/unprojectable", b"user:hello"), + 0, + ); + let outcome = drive(&units).await; + assert!( + matches!(outcome, Outcome::Failed(_, ReasonCode::DecodeFailed)), + "{outcome:?}" + ); + assert!( + seen.lock().unwrap().is_empty(), + "no hook saw an unreadable request" + ); + assert!(far.sent().is_empty(), "no attempt was made"); + let rendered = units + .take_rendered() + .expect("the plane rendered the refusal"); + assert_eq!(rendered.status, 400); +} diff --git a/crates/busbar-mcp/src/mcp/tests/roots_changed_tests.rs b/crates/busbar-mcp/src/mcp/tests/roots_changed_tests.rs index 6731723a74..bf55a1746f 100644 --- a/crates/busbar-mcp/src/mcp/tests/roots_changed_tests.rs +++ b/crates/busbar-mcp/src/mcp/tests/roots_changed_tests.rs @@ -158,6 +158,7 @@ fn rpc_ctx( caller_principal: gov.key.as_ref().map(|k| k.id.clone()), gov: Some(gov.clone()), principal: Some(busbar_contract::auth::AuthPrincipal(None)), + caller_credential: None, host: engine_host_from_handle(&handle), engine: handle, slot: Arc::new(()), diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index 8e8bac6e0a..2c4c2d0310 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -1164,6 +1164,7 @@ fn spent() -> Pick { Pick::Exhausted { status: refusal_status(ReasonCode::NoDestination), retry_after: None, + detail: busbar_kernel_egress::wire::DETAIL_OVERLOADED, } } From 7bb6a9cadc6ff0152225dcca1f9dc64b0663ffda Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:33:53 -0700 Subject: [PATCH 02/49] FLIP-LLM plane: the llm plane's `project` binding and exchange/project module, its door's tail facts, and the dialect auth parameters (BUSBAR-1.6.0.md Part 3 section 12 lines 2568-2571 "`project`. A pure plane op that writes the hook kind's own `RequestView`"; lines 2655-2658 "`project` may return a rewrite, which the plane applies and re-projects; `PromptView` carries plane-flattened messages; the projection may carry the end user") Ported from the lane-dg-llm snapshot (f804c379a4, d6bca8750c, cd46f09076, b0b7299f38 and the plane parts of 24cda1eeb5/5ac7cc8790/b26be1ca6d/99d4dd92e9/e581423073), without the LLM-UA, DEL-LLM or a2a pieces: - busbar-plane-llm exchange/project.rs: the hook view read through the operation's own reader, as 1.5.5's hook seam read it (turns, screenable characters, tools, end user, the system field and one (role, text) per wire turn); a request-stage rewrite applied in the unit's own dialect and kept as the unit's request, so every attempt is written from it; an unreadable body REFUSED, the unit's refusal then reading 1.5.5's unreadable-body sentence; a gate veto rendered as the gate's own words. - plane_door: the `project` slot (strings in the host arena, turns in the host turn buffer, the one re-call when short); an arrival names its route (the model as a pool first, else a direct entry; ARCHITECT Q-SW6/Q-FL2/Q-FL3); the token classes under the card names 1.5.5 priced (input, output, cache_read, cache_write); `pool` as the registration noun and the grant's scope kind; each dialect's default outbound style and one need per style (ARCHITECT Q-L1-AUTH (A)). - AUTHPARAMS (ARCHITECT RULINGS 2026-10-03 14:45Z, Q-L6-AUTHPARAMS): DialectAuth.params appended (layout golden and C header follow), checked by check_dialect_auth and kept by the loader; the anthropic key-family and bedrock sigv4 parameters the dialects state. - the far request carries its dialect's declared static fields (ARCHITECT SD-3 (1)) and lays a same-dialect caller's fields over busbar's as 1.5.5's header map did; a whole rendered answer states its length. - the auth plugins' compiled-in entries (busbar-auth-sigv4, busbar-auth-oauth) for the `auths` row. --- crates/busbar-auth-oauth/src/lib.rs | 10 + crates/busbar-auth-sigv4/src/lib.rs | 10 + .../busbar-contract/include/busbar_plugin.h | 4 +- crates/busbar-contract/src/abi/plane/check.rs | 8 + crates/busbar-contract/src/abi/plane/mod.rs | 9 + .../src/abi/tests/plane_check_tests.rs | 24 +- .../tests/golden/abi-layout.golden | 3 +- crates/busbar-contract/tests/layout_golden.rs | 2 +- .../plane_driver/tests/probe_unit_tests.rs | 5 +- .../tests/plane_attempt_parity_tests.rs | 1 + .../engine/tests/plane_reply_parity_tests.rs | 1 + .../busbar-plane-decisions/src/plane_door.rs | 1 + .../tests/anthropic_hardening_tests.rs | 39 +++ .../src/codec/bedrock/tests/tests.rs | 42 +++ crates/busbar-plane-llm/src/dialect.rs | 42 +++ .../busbar-plane-llm/src/exchange/attempt.rs | 48 +++- crates/busbar-plane-llm/src/exchange/mod.rs | 1 + .../busbar-plane-llm/src/exchange/project.rs | 197 +++++++++++++ .../busbar-plane-llm/src/exchange/refuse.rs | 29 +- .../src/exchange/reply/mod.rs | 15 +- .../busbar-plane-llm/src/exchange/shaping.rs | 25 +- crates/busbar-plane-llm/src/plane_door.rs | 260 +++++++++++++++--- .../tests/exchange_attempt.rs | 113 +++++++- .../busbar-plane-llm/tests/exchange_reply.rs | 1 + .../tests/hook_absent_projection.rs | 46 ++++ .../tests/hook_max_tokens_saturation_1_5_5.rs | 60 ++++ crates/busbar-plane-llm/tests/plane_door.rs | 51 +++- .../plugin-loader/src/dispatch/kinds/plane.rs | 36 ++- 28 files changed, 1013 insertions(+), 70 deletions(-) create mode 100644 crates/busbar-plane-llm/src/exchange/project.rs create mode 100644 crates/busbar-plane-llm/tests/hook_absent_projection.rs create mode 100644 crates/busbar-plane-llm/tests/hook_max_tokens_saturation_1_5_5.rs diff --git a/crates/busbar-auth-oauth/src/lib.rs b/crates/busbar-auth-oauth/src/lib.rs index 66a94662ff..2714b3c0a1 100644 --- a/crates/busbar-auth-oauth/src/lib.rs +++ b/crates/busbar-auth-oauth/src/lib.rs @@ -177,6 +177,16 @@ busbar_contract::plugin_door! { }, } +/// THE COMPILED-IN ENTRY a composition root's `auths` row names: the door at +/// `compiled_in::door::door`, the same [`door`] the dropped-in build exports (compiled-in = +/// dropped-in). +pub mod compiled_in { + /// The door. + pub mod door { + pub use crate::door; + } +} + // The dropped door's one symbol, under `dropped-in` only: a build linking this crate beside other // plugins must not carry a second `busbar_plugin_door`. #[cfg(feature = "dropped-in")] diff --git a/crates/busbar-auth-sigv4/src/lib.rs b/crates/busbar-auth-sigv4/src/lib.rs index 73aebb8e3c..17a8023cca 100644 --- a/crates/busbar-auth-sigv4/src/lib.rs +++ b/crates/busbar-auth-sigv4/src/lib.rs @@ -117,6 +117,16 @@ busbar_contract::plugin_door! { }, } +/// THE COMPILED-IN ENTRY a composition root's `auths` row names: the door at +/// `compiled_in::door::door`, the same [`door`] the dropped-in build exports (compiled-in = +/// dropped-in). +pub mod compiled_in { + /// The door. + pub mod door { + pub use crate::door; + } +} + // The dropped door's one symbol, under `dropped-in` only: a build linking this crate beside other // plugins must not carry a second `busbar_plugin_door`. #[cfg(feature = "dropped-in")] diff --git a/crates/busbar-contract/include/busbar_plugin.h b/crates/busbar-contract/include/busbar_plugin.h index 4abde3b8db..f7cb7bb222 100644 --- a/crates/busbar-contract/include/busbar_plugin.h +++ b/crates/busbar-contract/include/busbar_plugin.h @@ -2356,6 +2356,7 @@ struct bb_plane_DialectAuth { uint32_t dialect; uint32_t _reserved; bb_mech_AbiStr style; + bb_mech_Blob params; }; /* One operation class the plane serves one level down, and the display name a refusal naming the */ @@ -4583,11 +4584,12 @@ BB_ASSERT(offsetof(bb_plane_Ops, serve) == 104, "bb_plane_Ops.serve: offset"); BB_ASSERT(offsetof(bb_plane_Ops, hydrate) == 112, "bb_plane_Ops.hydrate: offset"); BB_ASSERT(offsetof(bb_plane_Ops, start) == 120, "bb_plane_Ops.start: offset"); BB_ASSERT(offsetof(bb_plane_Ops, project) == 128, "bb_plane_Ops.project: offset"); -BB_ASSERT(sizeof(bb_plane_DialectAuth) == 24, "bb_plane_DialectAuth: size"); +BB_ASSERT(sizeof(bb_plane_DialectAuth) == 48, "bb_plane_DialectAuth: size"); BB_ASSERT(BB_ALIGNOF(bb_plane_DialectAuth) == 8, "bb_plane_DialectAuth: alignment"); BB_ASSERT(offsetof(bb_plane_DialectAuth, dialect) == 0, "bb_plane_DialectAuth.dialect: offset"); BB_ASSERT(offsetof(bb_plane_DialectAuth, _reserved) == 4, "bb_plane_DialectAuth._reserved: offset"); BB_ASSERT(offsetof(bb_plane_DialectAuth, style) == 8, "bb_plane_DialectAuth.style: offset"); +BB_ASSERT(offsetof(bb_plane_DialectAuth, params) == 24, "bb_plane_DialectAuth.params: offset"); BB_ASSERT(sizeof(bb_plane_OpClass) == 32, "bb_plane_OpClass: size"); BB_ASSERT(BB_ALIGNOF(bb_plane_OpClass) == 8, "bb_plane_OpClass: alignment"); BB_ASSERT(offsetof(bb_plane_OpClass, op) == 0, "bb_plane_OpClass.op: offset"); diff --git a/crates/busbar-contract/src/abi/plane/check.rs b/crates/busbar-contract/src/abi/plane/check.rs index 2e429ade52..62c4224a09 100644 --- a/crates/busbar-contract/src/abi/plane/check.rs +++ b/crates/busbar-contract/src/abi/plane/check.rs @@ -804,6 +804,14 @@ pub fn check_dialect_auth(entries: &[DialectAuth], dialects_len: u64) -> Result< for d in entries { index(d.dialect, dialects_len, "dialect_auth.dialect")?; named(d.style, "dialect_auth.style")?; + crate::abi::mechanism::check::blob( + &d.params, + "dialect_auth.params", + "dialect_auth.params", + )?; + if d.params.len > 0 && d.params.fmt != crate::abi::mechanism::call::BLOB_JSON { + return Err(fault(Rule::Contradiction, "dialect_auth.params.fmt")); + } } Ok(()) } diff --git a/crates/busbar-contract/src/abi/plane/mod.rs b/crates/busbar-contract/src/abi/plane/mod.rs index 850d7ff8c0..1c9e5bd6fb 100644 --- a/crates/busbar-contract/src/abi/plane/mod.rs +++ b/crates/busbar-contract/src/abi/plane/mod.rs @@ -788,6 +788,15 @@ pub struct DialectAuth { pub _reserved: u32, /// The style, an open string. pub style: AbiStr, + /// The style's parameters for this dialect, a [`super::mechanism::call::BLOB_JSON`] object the + /// host hands the auth plugin's `open_outbound` as its settings at seal, under the provider's + /// own (ARCHITECT RULING 2026-10-03, Q-L6-AUTHPARAMS; ruling 2026-09-28 "the kernel resolves the + /// dialect's declared parameters at seal into OpenOutboundIn::settings"). [`Blob::ABSENT`]: the + /// style takes none. A value `{"host_label_after": [labels], "default": word, "unread": text}` + /// is resolved at seal from the provider's base URL host: the dotted label after one of + /// `labels` when it reads as a dashed name ending in a number (`-...-`, three + /// parts or more), else `default`, with `unread` logged as the operator's warning. + pub params: Blob, } /// One operation class the plane serves one level down, and the display name a refusal naming the diff --git a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs index 37fcffc540..1fe25e935b 100644 --- a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs +++ b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs @@ -12,9 +12,9 @@ use crate::abi::hook::{MessageView, SignalEntry, SignalValue, SIGNAL_TAG_BOOL, S use crate::abi::host::conn::connector::{ Need, DIRECTION_OUTBOUND, KEEP_ALL_EXCEPT_DENIED, KEEP_NAMED, KEEP_RESPONSE_HEADERS_MAX, }; -use crate::abi::mechanism::call::AbiStr; use crate::abi::mechanism::call::Outcome; use crate::abi::mechanism::call::Outcome::{Failed, Pending, Ready, Refused}; +use crate::abi::mechanism::call::{AbiStr, Blob}; use crate::abi::mechanism::check::{check_needs, fault}; use crate::abi::mechanism::door::{Section, SECTION_DECLARING, SECTION_REQUIRED}; use crate::abi::plane::*; @@ -922,6 +922,7 @@ fn dialect_auth_names_a_dialect_and_a_style() { dialect: 0, _reserved: 0, style: s("st"), + params: Blob::ABSENT, }; assert_eq!(check_dialect_auth(&[d], 1), Ok(())); assert_eq!( @@ -933,6 +934,27 @@ fn dialect_auth_names_a_dialect_and_a_style() { assert_eq!( check_dialect_auth(&[bad], 1), f(Rule::Missing, "dialect_auth.style") + ); // The style's parameters: a JSON object, a counted blob never NULL. + const PARAMS: &[u8] = br#"{"service":"s"}"#; + let mut with = d; + with.params = Blob { + ptr: PARAMS.as_ptr(), + len: PARAMS.len(), + fmt: crate::abi::mechanism::call::BLOB_JSON, + flags: 0, + }; + assert_eq!(check_dialect_auth(&[with], 1), Ok(())); + let mut octets = with; + octets.params.fmt = crate::abi::mechanism::call::BLOB_OCTETS; + assert_eq!( + check_dialect_auth(&[octets], 1), + f(Rule::Contradiction, "dialect_auth.params.fmt") + ); + let mut dangling = with; + dangling.params.ptr = null(); + assert_eq!( + check_dialect_auth(&[dangling], 1), + f(Rule::NullWithCount, "dialect_auth.params") ); } diff --git a/crates/busbar-contract/tests/golden/abi-layout.golden b/crates/busbar-contract/tests/golden/abi-layout.golden index 3ccc63a6a8..67a22e687c 100644 --- a/crates/busbar-contract/tests/golden/abi-layout.golden +++ b/crates/busbar-contract/tests/golden/abi-layout.golden @@ -1833,7 +1833,8 @@ pkind::Ops.__align=8 pkind::DialectAuth.dialect=0 pkind::DialectAuth._reserved=4 pkind::DialectAuth.style=8 -pkind::DialectAuth.__size=24 +pkind::DialectAuth.params=24 +pkind::DialectAuth.__size=48 pkind::DialectAuth.__align=8 pkind::OpClass.op=0 pkind::OpClass.name=16 diff --git a/crates/busbar-contract/tests/layout_golden.rs b/crates/busbar-contract/tests/layout_golden.rs index f4db84107b..a6946a2eda 100644 --- a/crates/busbar-contract/tests/layout_golden.rs +++ b/crates/busbar-contract/tests/layout_golden.rs @@ -1642,7 +1642,7 @@ fn compute_layout() -> String { pkind::Ops, [head, arrive, on_piece, refusal, serve, hydrate, start, project] ); - record!(s, pkind::DialectAuth, [dialect, _reserved, style]); + record!(s, pkind::DialectAuth, [dialect, _reserved, style, params]); record!(s, pkind::OpClass, [op, name]); record!(s, pkind::BillableClass, [class, family]); record!(s, pkind::RouteCost, [class, _reserved, weight]); diff --git a/crates/busbar-kernel/src/plane_driver/tests/probe_unit_tests.rs b/crates/busbar-kernel/src/plane_driver/tests/probe_unit_tests.rs index 64c77fd947..4fc16a3ef4 100644 --- a/crates/busbar-kernel/src/plane_driver/tests/probe_unit_tests.rs +++ b/crates/busbar-kernel/src/plane_driver/tests/probe_unit_tests.rs @@ -19,9 +19,8 @@ use busbar_contract::abi::mechanism::call::{Outcome as AbiOutcome, Span}; use busbar_contract::abi::mechanism::ticket::Ticket as PlaneTicket; use busbar_contract::abi::plane::{ ArriveIn, ArriveOut, OnPieceIn, OnPieceOut, ProjectIn, ProjectOut, RefusalIn, RefusalOut, - ServeIn, ServeOut, - UnitCount, CLAIM_PROBE, EMIT_DONE, EMIT_TO_FAR_END, FROM_FAR_END, FROM_KERNEL, TAIL_PROBES, - UNITS_REPORTED, + ServeIn, ServeOut, UnitCount, CLAIM_PROBE, EMIT_DONE, EMIT_TO_FAR_END, FROM_FAR_END, + FROM_KERNEL, TAIL_PROBES, UNITS_REPORTED, }; use busbar_contract::caps::OpClassId; use busbar_contract::plane_calls::{ diff --git a/crates/busbar-llm/src/engine/tests/plane_attempt_parity_tests.rs b/crates/busbar-llm/src/engine/tests/plane_attempt_parity_tests.rs index 3767f39a7b..6264f9dddc 100644 --- a/crates/busbar-llm/src/engine/tests/plane_attempt_parity_tests.rs +++ b/crates/busbar-llm/src/engine/tests/plane_attempt_parity_tests.rs @@ -30,6 +30,7 @@ fn plane_lane(dialect: &'static str, model: &str, path: Option<&str>, base: Opti prompt_caching: false, caps: Default::default(), error_map: Default::default(), + statics: &[], } } diff --git a/crates/busbar-llm/src/engine/tests/plane_reply_parity_tests.rs b/crates/busbar-llm/src/engine/tests/plane_reply_parity_tests.rs index 4faf0bf30a..a5f611e864 100644 --- a/crates/busbar-llm/src/engine/tests/plane_reply_parity_tests.rs +++ b/crates/busbar-llm/src/engine/tests/plane_reply_parity_tests.rs @@ -301,6 +301,7 @@ fn lane(egress: &'static str) -> Lane { prompt_caching: false, caps: Default::default(), error_map: Default::default(), + statics: &[], } } diff --git a/crates/busbar-plane-decisions/src/plane_door.rs b/crates/busbar-plane-decisions/src/plane_door.rs index fc4ebe273b..f1e686f299 100644 --- a/crates/busbar-plane-decisions/src/plane_door.rs +++ b/crates/busbar-plane-decisions/src/plane_door.rs @@ -108,6 +108,7 @@ const DIALECT_AUTH: &[DialectAuth] = &[DialectAuth { dialect: tail::DIALECT_AUTH[0].0, _reserved: 0, style: abi_str(tail::DIALECT_AUTH[0].1), + params: Blob::ABSENT, }]; const SCOPE_KINDS: &[AbiStr] = &[abi_str(tail::SCOPE_KINDS[0])]; diff --git a/crates/busbar-plane-llm/src/codec/anthropic/tests/anthropic_hardening_tests.rs b/crates/busbar-plane-llm/src/codec/anthropic/tests/anthropic_hardening_tests.rs index f075be2774..df823a2ca5 100644 --- a/crates/busbar-plane-llm/src/codec/anthropic/tests/anthropic_hardening_tests.rs +++ b/crates/busbar-plane-llm/src/codec/anthropic/tests/anthropic_hardening_tests.rs @@ -4135,3 +4135,42 @@ fn unmodelled_block_is_left_out_of_the_ir_and_spliced_back_same_dialect() { "{out}" ); } + +/// THE TAIL RESTATES THIS SCHEME as the `api-key` style's parameters (ARCHITECT RULING 2026-10-03, +/// Q-L6-AUTHPARAMS): the same families, own header and passthrough, in the auth plugin's words. +#[test] +fn the_tails_api_key_parameters_restate_the_credential_family_table() { + use busbar_contract::protocol::CredentialHeader as H; + let Some(EgressScheme::Static { + families, + own, + passthrough, + }) = DECL.egress_scheme + else { + panic!("anthropic declares a static credential scheme"); + }; + let words = |h: H| match h { + H::Raw { header, trim_start } => { + let mut o = serde_json::json!({ "header": header }); + if trim_start { + o["trim_start"] = serde_json::json!(true); + } + o + } + H::Bearer => serde_json::json!({}), + }; + let params: serde_json::Value = + serde_json::from_str(crate::dialect::KEY_FAMILY_PARAMS).expect("the parameters are JSON"); + let stated: Vec = families + .iter() + .map(|f| { + let mut row = words(f.presented_as); + row["prefix"] = serde_json::json!(f.prefix); + row + }) + .collect(); + assert_eq!(params["families"], serde_json::json!(stated)); + assert_eq!(params["own"], words(own)); + assert_eq!(params["header"], words(own)["header"]); + assert_eq!(params["passthrough"], words(passthrough)); +} diff --git a/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs b/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs index 84d6406bd0..d108b63a45 100644 --- a/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs +++ b/crates/busbar-plane-llm/src/codec/bedrock/tests/tests.rs @@ -6729,3 +6729,45 @@ fn the_served_tier_is_read_and_written() { }); assert_eq!(tier.as_deref(), Some("flex")); } + +/// THE TAIL RESTATES THIS SCHEME as the `sigv4` style's parameters (ARCHITECT RULING 2026-10-03, +/// Q-L6-AUTHPARAMS): the service, the content type, and the region read from the provider's host +/// after the service labels the codec's rule reads, else its default with its warning. +#[test] +fn the_tails_sigv4_parameters_restate_the_signing_scheme() { + let Some(busbar_contract::protocol::EgressScheme::SigV4 { + service, + region_of_host, + default_region, + content_type, + }) = super::DECL.egress_scheme + else { + panic!("the signing dialect declares a SigV4 scheme"); + }; + let params: serde_json::Value = + serde_json::from_str(crate::dialect::SIGNED_PARAMS).expect("the parameters are JSON"); + assert_eq!(params["service"], serde_json::json!(service)); + assert_eq!(params["content_type"], serde_json::json!(content_type)); + assert_eq!( + params["region"]["default"], + serde_json::json!(default_region) + ); + let labels: Vec<&str> = params["region"]["host_label_after"] + .as_array() + .expect("the region is read after the service labels") + .iter() + .filter_map(serde_json::Value::as_str) + .collect(); + // Every label the codec's rule reads a region after, and no other. + for label in &labels { + assert_eq!( + region_of_host(&format!("{label}.eu-west-3.amazonaws.com")), + Some("eu-west-3"), + "{label}" + ); + } + assert_eq!(region_of_host("runtime.eu-west-3.amazonaws.com"), None); + assert!(params["region"]["unread"] + .as_str() + .is_some_and(|w| w.contains("defaulting SigV4 scope to us-east-1"))); +} diff --git a/crates/busbar-plane-llm/src/dialect.rs b/crates/busbar-plane-llm/src/dialect.rs index 88e7e392aa..0482006649 100644 --- a/crates/busbar-plane-llm/src/dialect.rs +++ b/crates/busbar-plane-llm/src/dialect.rs @@ -49,6 +49,14 @@ pub struct Dialect { pub scheme_alt: &'static str, /// The egress-auth scheme that decorates a request to an upstream of this dialect. pub egress_scheme: &'static str, + /// THE DIALECT'S DEFAULT OUTBOUND AUTH STYLE (the design's auth points: a member's style is its + /// provider's `auth:`, else its plane's default for its dialect; ARCHITECT Q-L1-AUTH (A)): the + /// style an auth plugin serves, stated in the plane tail's `dialect_auth`. + pub egress_style: &'static str, + /// THE DEFAULT STYLE'S PARAMETERS for this dialect, a JSON object (`""`: none), stated in the + /// plane tail's `dialect_auth.params` and handed the auth plugin's `open_outbound` as its + /// settings at seal, under the provider's own (ARCHITECT RULING 2026-10-03, Q-L6-AUTHPARAMS). + pub egress_params: &'static str, /// The request headers busbar GOVERNS for this dialect (lower-case): its credential headers and /// its tenant selectors. A same-dialect route forwards every other client header unchanged; these /// never pass, because busbar's upstream credential and configuration replace them (OWNER HARD @@ -95,6 +103,28 @@ const OPENAI_GOVERNED: &[&str] = &[ "openai-project", ]; +/// The `api-key` style's parameters for the dialect whose credentials come in families (its +/// codec's `EgressScheme::Static`): an api key presented in `x-api-key` with leading whitespace +/// trimmed, an oauth token as a bearer, the operator's own key in `x-api-key`, a passthrough caller's +/// as a bearer. +pub(crate) const KEY_FAMILY_PARAMS: &str = concat!( + r#"{"header":"x-api-key","#, + r#""families":[{"prefix":"sk-ant-api","header":"x-api-key","trim_start":true},"#, + r#"{"prefix":"sk-ant-oat"}],"#, + r#""own":{"header":"x-api-key"},"passthrough":{}}"#, +); + +/// The `sigv4` style's parameters for the signed dialect (its codec's `EgressScheme::SigV4`): the +/// service, the content type the signature covers, and the region read from the provider's host by +/// the codec's rule (`derive_sigv4_region`), else its default with the codec's warning. +pub(crate) const SIGNED_PARAMS: &str = concat!( + r#"{"service":"bedrock","content_type":"application/json","#, + r#""region":{"host_label_after":["bedrock-runtime","bedrock-runtime-fips","bedrock","bedrock-fips"],"#, + r#""default":"us-east-1","#, + r#""unread":"could not derive AWS region from Bedrock endpoint host; defaulting SigV4 scope to "#, + r#"us-east-1 (set a bedrock-runtime[-fips]..amazonaws.com host)"}}"#, +); + /// The table, one row per dialect, in the order the codec crate declares them. pub const DIALECTS: &[Dialect] = &[ Dialect { @@ -108,6 +138,8 @@ pub const DIALECTS: &[Dialect] = &[ cache_write_pointer: Some("/usage/cache_creation_input_tokens"), scheme_alt: "api-key", egress_scheme: "bearer", + egress_style: "api-key", + egress_params: KEY_FAMILY_PARAMS, governed_headers: &["authorization", "x-api-key"], governed_query: &[], tenant_headers: &[], @@ -128,6 +160,8 @@ pub const DIALECTS: &[Dialect] = &[ cache_write_pointer: None, scheme_alt: "bearer", egress_scheme: "bearer", + egress_style: "bearer", + egress_params: "", governed_headers: OPENAI_GOVERNED, governed_query: &[], tenant_headers: OPENAI_TENANT, @@ -145,6 +179,8 @@ pub const DIALECTS: &[Dialect] = &[ cache_write_pointer: None, scheme_alt: "api-key", egress_scheme: "bearer", + egress_style: "x-goog-api-key", + egress_params: "", governed_headers: &["authorization", "x-goog-api-key", "x-goog-user-project"], governed_query: &["key"], tenant_headers: &[], @@ -162,6 +198,8 @@ pub const DIALECTS: &[Dialect] = &[ cache_write_pointer: Some("/usage/cacheWriteInputTokens"), scheme_alt: "request-signature", egress_scheme: "request-signature", + egress_style: "sigv4", + egress_params: SIGNED_PARAMS, governed_headers: &[ "authorization", "x-amz-date", @@ -183,6 +221,8 @@ pub const DIALECTS: &[Dialect] = &[ cache_write_pointer: Some("/usage/input_tokens_details/cache_write_tokens"), scheme_alt: "bearer", egress_scheme: "bearer", + egress_style: "bearer", + egress_params: "", governed_headers: OPENAI_GOVERNED, governed_query: &[], tenant_headers: OPENAI_TENANT, @@ -200,6 +240,8 @@ pub const DIALECTS: &[Dialect] = &[ cache_write_pointer: None, scheme_alt: "bearer", egress_scheme: "bearer", + egress_style: "bearer", + egress_params: "", governed_headers: &["authorization"], governed_query: &[], tenant_headers: &[], diff --git a/crates/busbar-plane-llm/src/exchange/attempt.rs b/crates/busbar-plane-llm/src/exchange/attempt.rs index e2deb4d10d..1baed8b02a 100644 --- a/crates/busbar-plane-llm/src/exchange/attempt.rs +++ b/crates/busbar-plane-llm/src/exchange/attempt.rs @@ -662,7 +662,14 @@ fn head_fields( // A native client's user-agent, never a UA-less request (1.5.5's bytes); a same-dialect // caller's own replaces it below. let user_agent = ("user-agent".to_string(), user_agent.as_bytes().to_vec()); - let mut fields = vec![content_type, user_agent, accept]; + // The dialect's declared static fields first, as the previous release's credential map led + // with them (SD-3 (1)); a same-dialect caller's own value replaces one below. + let mut fields: Vec<(String, Vec)> = lane + .statics + .iter() + .map(|(n, v)| ((*n).to_string(), v.as_bytes().to_vec())) + .collect(); + fields.extend([content_type, user_agent, accept]); if arrived.dialect != egress { fields.extend(tenant); return Ok(fields); @@ -675,12 +682,47 @@ fn head_fields( (!governed(&name)).then(|| (name, value.to_vec())) }) .collect(); - fields.retain(|(own, _)| !forwarded.iter().any(|(name, _)| name == own)); - fields.extend(forwarded); + fields = forward_over(fields, forwarded); fields.extend(tenant); Ok(fields) } +/// The caller's forwarded fields laid over busbar's own as the previous release's header map laid +/// them (an `http::HeaderMap`, which keeps one position per name, the position the name was first +/// inserted at): a name busbar already writes keeps busbar's position and takes the caller's values; +/// a name it does not is appended, in the caller's order; every value of a repeated name is kept, +/// together at its name's position. +fn forward_over( + own: Vec<(String, Vec)>, + forwarded: Vec<(String, Vec)>, +) -> Vec<(String, Vec)> { + let mut named: Vec<(String, Vec>)> = Vec::new(); + let put = |named: &mut Vec<(String, Vec>)>, name: String, value: Vec| match named + .iter_mut() + .find(|(n, _)| *n == name) + { + Some((_, values)) => values.push(value), + None => named.push((name, vec![value])), + }; + for (name, value) in own { + put(&mut named, name, value); + } + let mut replaced: Vec = Vec::new(); + for (name, value) in forwarded { + if !replaced.contains(&name) { + if let Some((_, values)) = named.iter_mut().find(|(n, _)| *n == name) { + values.clear(); + } + replaced.push(name.clone()); + } + put(&mut named, name, value); + } + named + .into_iter() + .flat_map(|(name, values)| values.into_iter().map(move |v| (name.clone(), v))) + .collect() +} + /// BUILD ONE ATTEMPT'S FAR-END REQUEST for `member` of `pool`. /// /// # Errors diff --git a/crates/busbar-plane-llm/src/exchange/mod.rs b/crates/busbar-plane-llm/src/exchange/mod.rs index 5ad9e01de3..ab0a6a4030 100644 --- a/crates/busbar-plane-llm/src/exchange/mod.rs +++ b/crates/busbar-plane-llm/src/exchange/mod.rs @@ -6,6 +6,7 @@ pub mod arrive; pub mod attempt; pub mod multipart; pub mod probe; +pub mod project; pub mod refuse; pub mod reply; pub mod shaping; diff --git a/crates/busbar-plane-llm/src/exchange/project.rs b/crates/busbar-plane-llm/src/exchange/project.rs new file mode 100644 index 0000000000..a2b636c473 --- /dev/null +++ b/crates/busbar-plane-llm/src/exchange/project.rs @@ -0,0 +1,197 @@ +//! THE HOOK PROJECTION, sans I/O: what a hook is shown of an arrival (`BUSBAR-1.6.0.md` Part 3, +//! section 12, "Hooks"; THE DESIGN section 11.7), and a request-stage hook's rewrite applied to +//! the arrival in its own dialect. +//! +//! One read, through the operation's own reader, as the previous release's hook seam read it: the +//! size signals (turns, characters, tools), the end user the dialect spells, and the prompt view +//! (the system field and one `(role, text)` entry per wire turn, an in-band system turn at its wire +//! position, a turn with nothing readable still an entry). A body the reader refuses is the +//! request's failure ([`Unreadable`]); a body there is no reader for projects the zeroed shape. +//! +//! The output-cap signal is the previous release's own read (`max_tokens_for` at `v1.5.5`): the +//! dialect's cap key off the caller's body, an out-of-range cap saturated to `u32::MAX`, never +//! dropped. The IR's cap is the far end's, and a reader drops a cap it cannot carry; the hook +//! signal is the caller's ask. + +use busbar_contract::hooks::RewriteReply; +use busbar_contract::ir::facts::{ContentItem, IrFacts, Shape, Slot}; +use serde_json::Value; + +use super::arrive::Arrived; +use super::attempt::stream_intent; +use super::{decl, handler_of}; +use crate::codec::translate::TranslateCodec; + +/// The dialect whose output cap the previous release read under `max_output_tokens`. +const RESPONSES: &str = "responses"; + +/// What a hook is shown of one arrival. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct HookView { + /// The wire turn count. + pub turn_count: usize, + /// The screenable text's characters, system included. + pub text_chars: usize, + /// The request declares tools. + pub has_tools: bool, + /// The caller's output cap, saturated. + pub max_tokens: Option, + /// The caller asked for a stream. + pub stream: bool, + /// The dialect's own system field, flattened; `None` when empty or absent. + pub system: Option, + /// One `(role, text)` entry per wire turn, in request order. + pub turns: Vec<(String, String)>, + /// The end user, as the dialect spells it. + pub end_user: Option, +} + +/// The operation's reader refused the body: the request fails (the previous release's 400). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Unreadable; + +/// The client-facing message for a body the operation's reader refuses: content-free, it names the +/// failure, never the field or the value. +pub const UNREADABLE_BODY_MESSAGE: &str = + "request body could not be read as a valid request for this endpoint"; + +/// The previous release's output-cap signal: the dialect's cap key (`max_output_tokens` for the +/// responses dialect, `max_tokens` for every other) off the caller's body, saturated. +#[must_use] +pub fn max_tokens_for(v: &Value, dialect: &str) -> Option { + let key = if dialect == RESPONSES { + "max_output_tokens" + } else { + "max_tokens" + }; + v.get(key) + .and_then(Value::as_u64) + .map(|n| u32::try_from(n).unwrap_or(u32::MAX)) +} + +/// The facts the operation's reader reads off `arrived`; `Ok(None)` when there is no reader or no +/// body to read. +fn facts(arrived: &Arrived) -> Result>, Unreadable> { + let Some(handler) = handler_of(arrived) else { + return Ok(None); + }; + let read = match &arrived.parsed { + Some(v) if v.is_object() => handler.read_facts_value(v), + _ if arrived.body.is_empty() => return Ok(None), + // A body that is not a JSON object (a multipart upload) is read by the byte reader. + _ => handler.read_facts(&arrived.body, &arrived.content_type), + }; + read.map(Some).map_err(|_| Unreadable) +} + +/// One bucket's pieces joined with a newline. +fn join(pieces: Vec) -> Option { + match pieces.len() { + 0 => None, + 1 => pieces.into_iter().next(), + _ => Some(pieces.join("\n")), + } +} + +/// The prompt view of `ir`: the system slot's text, and one `(role, text)` entry per turn. +fn prompt(ir: &dyn IrFacts) -> (Option, Vec<(String, String)>) { + let mut system: Vec = Vec::new(); + let mut turns: Vec<(&'static str, Vec)> = Vec::new(); + for item in ir.content() { + let piece = match &item { + ContentItem::Text { text, .. } => text.to_string(), + other => other.screenable_text().into_owned(), + }; + match item.slot() { + Slot::System => system.push(piece), + slot => { + let i = slot.turn_index().unwrap_or(0); + while turns.len() <= i { + turns.push((item.author(), Vec::new())); + } + turns[i].1.push(piece); + } + } + } + ( + join(system).filter(|s| !s.is_empty()), + turns + .into_iter() + .map(|(role, pieces)| (role.to_string(), join(pieces).unwrap_or_default())) + .collect(), + ) +} + +/// PROJECT one arrival for the hooks. +/// +/// # Errors +/// +/// [`Unreadable`] when the operation's reader refuses the body. +pub fn project(arrived: &Arrived) -> Result { + let stream = handler_of(arrived) + .map(|h| stream_intent(h, arrived.parsed.as_ref()).wants_stream) + .unwrap_or(false); + let max_tokens = arrived + .parsed + .as_ref() + .and_then(|v| max_tokens_for(v, arrived.dialect)); + let Some(ir) = facts(arrived)? else { + let shape = Shape::EMPTY; + return Ok(HookView { + turn_count: shape.turn_count, + text_chars: shape.text_chars, + has_tools: shape.has_tools, + max_tokens, + stream, + ..HookView::default() + }); + }; + let shape = ir.shape(); + let (system, turns) = prompt(&*ir); + Ok(HookView { + turn_count: shape.turn_count, + text_chars: shape.text_chars, + has_tools: shape.has_tools, + max_tokens, + stream, + system, + turns, + end_user: ir.end_user().map(str::to_string), + }) +} + +/// A rewrite as it crosses: the hook's `{messages, tools}`. +fn read_rewrite(rewrite: &[u8]) -> Option { + let v: Value = serde_json::from_slice(rewrite).ok()?; + let list = |k: &str| { + v.get(k) + .and_then(Value::as_array) + .cloned() + .unwrap_or_default() + }; + Some(RewriteReply { + messages: list("messages"), + tools: list("tools"), + }) +} + +/// APPLY a request-stage hook's rewrite to `arrived` in its own dialect: the dialect frames the +/// rewrite's messages (and tools) into its conversation container. Answers the rewritten body when +/// it applied; `None` leaves the arrival untouched (the previous release: a rewrite that cannot be +/// applied leaves the request unmodified). +pub fn apply_rewrite(arrived: &mut Arrived, rewrite: &[u8]) -> Option> { + let reply = read_rewrite(rewrite)?; + if reply.messages.is_empty() { + return None; + } + let dialect = decl(arrived.dialect).and_then(|d| d.dialect())?; + let mut v = arrived.parsed.clone()?; + let obj = v.as_object_mut()?; + if !dialect.apply_rewrite_to_ingress_body(obj, &reply.messages, &reply.tools) { + return None; + } + let bytes = crate::codec::json::to_vec(&v).ok()?; + arrived.body.clone_from(&bytes); + arrived.parsed = Some(v); + Some(bytes) +} diff --git a/crates/busbar-plane-llm/src/exchange/refuse.rs b/crates/busbar-plane-llm/src/exchange/refuse.rs index b95c5dfa8a..183845fac7 100644 --- a/crates/busbar-plane-llm/src/exchange/refuse.rs +++ b/crates/busbar-plane-llm/src/exchange/refuse.rs @@ -13,8 +13,9 @@ use busbar_contract::abi::plane::reason_of; use busbar_contract::protocol::{ - ProtocolDecl, APPLICATION_JSON, KIND_API_ERROR, KIND_INSUFFICIENT_QUOTA, KIND_INVALID_REQUEST, - KIND_NOT_FOUND, KIND_OVERLOADED, KIND_PERMISSION, KIND_RATE_LIMIT, KIND_REQUEST_TOO_LARGE, + ProtocolDecl, APPLICATION_JSON, KIND_API_ERROR, KIND_AUTHENTICATION, KIND_INSUFFICIENT_QUOTA, + KIND_INVALID_REQUEST, KIND_NOT_FOUND, KIND_OVERLOADED, KIND_PERMISSION, KIND_RATE_LIMIT, + KIND_REQUEST_TOO_LARGE, KIND_TIMEOUT, }; use serde_json::Value; @@ -101,6 +102,30 @@ pub fn kind_of(reason: &str, status: u16) -> &'static str { } } +/// The kind a hook's refusal wears, by the status the hook chose (the previous release's +/// `reject_kind_for_status`): a hook 429 reads as a rate limit, a 408 as a timeout, the 503 of a +/// load-bearing hook that could not answer as overloaded (retryable), and a status with no natural +/// kind as an invalid request. +#[must_use] +pub fn gate_kind(status: u16) -> &'static str { + match status { + 401 => KIND_AUTHENTICATION, + 403 => KIND_PERMISSION, + 404 => KIND_NOT_FOUND, + 408 => KIND_TIMEOUT, + 429 => KIND_RATE_LIMIT, + 503 => KIND_OVERLOADED, + _ => KIND_INVALID_REQUEST, + } +} + +/// RENDER A HOOK'S REFUSAL in `envelope`'s dialect: the hook's own status and words, as the +/// kernel's hook engine clamped and sanitised them. +#[must_use] +pub fn gate_refusal(envelope: &str, status: u16, text: &str) -> Rendered { + render(envelope, status, gate_kind(status), text, 0) +} + /// The sentence the previous release answered a model with when it named no pool and no configured /// model: the dialect's own copy where the path gave one (`shaped`), else the neutral sentence. #[must_use] diff --git a/crates/busbar-plane-llm/src/exchange/reply/mod.rs b/crates/busbar-plane-llm/src/exchange/reply/mod.rs index 53636b5eac..57483b2948 100644 --- a/crates/busbar-plane-llm/src/exchange/reply/mod.rs +++ b/crates/busbar-plane-llm/src/exchange/reply/mod.rs @@ -203,11 +203,24 @@ fn json_array(ctx: &ReplyCtx<'_>) -> bool { .is_some_and(|p| p.json_array) } +/// A WHOLE answer, rendered here in full (a translated answer, a judged failure, a refusal): its head +/// states its length, so the caller is sent it under that length, as the previous release sent a +/// buffered answer; a relayed answer states none and is sent as it is relayed. fn answered<'a>(r: Rendered, units: Units, verdict: Verdict, fault: Option) -> Piece<'a> { + let mut fields = r.fields; + if !fields + .iter() + .any(|(n, _)| n.eq_ignore_ascii_case("content-length")) + { + fields.push(( + "content-length".to_string(), + r.body.len().to_string().into_bytes(), + )); + } Piece { head: Some(Head { status: r.status, - fields: r.fields, + fields, }), bytes: Cow::Owned(r.body), units, diff --git a/crates/busbar-plane-llm/src/exchange/shaping.rs b/crates/busbar-plane-llm/src/exchange/shaping.rs index 1f44f2c457..e99575493a 100644 --- a/crates/busbar-plane-llm/src/exchange/shaping.rs +++ b/crates/busbar-plane-llm/src/exchange/shaping.rs @@ -27,6 +27,14 @@ use serde_json::Value; use crate::codec::DECLS; +/// The provider `auth:` overrides that present under a scheme of their own, not the dialect's, so +/// carry none of its static fields (the previous release's `egress_auth::resolve`). +const AUTH_API_KEY: &str = "api-key"; +/// See [`AUTH_API_KEY`]. +const AUTH_JWT_BEARER: &str = "jwt-bearer"; +/// See [`AUTH_API_KEY`]. +const AUTH_OAUTH_CLIENT_CREDENTIALS: &str = "oauth-client-credentials"; + /// The dialect a provider speaks when it names none. pub const DEFAULT_PROTOCOL: &str = "anthropic"; /// The global output-token default when `limits` names none. @@ -65,6 +73,12 @@ pub struct Lane { pub caps: LaneCaps, /// The provider's error-code map. pub error_map: HashMap, + /// The far end's dialect's declared static fields (`ProtocolDecl::static_headers`, ARCHITECT + /// SD-3 (1) "anthropic S2-a"), written on every far request of a lane presented under the + /// dialect's own credential scheme; none under an operator's `auth:` override that is not the + /// dialect's scheme (`api-key`, `jwt-bearer`, `oauth-client-credentials`), whose declaration + /// carries none, as the previous release wrote them. + pub statics: &'static [(&'static str, &'static str)], } impl Lane { @@ -175,6 +189,8 @@ struct ProviderCfg { #[serde(default)] error_map: HashMap, #[serde(default)] + auth: Option, + #[serde(default)] max_output_key: Option, #[serde(default)] anthropic_adaptive_thinking: Option, @@ -377,13 +393,17 @@ impl Shaping { ) })?; let asked = p.protocol.as_deref().unwrap_or(DEFAULT_PROTOCOL); - let dialect = DECLS + let decl = DECLS .iter() .find(|d| d.name == asked && d.codec.is_some()) - .map(|d| d.name) .ok_or_else(|| { format!("provider '{}' uses unknown protocol '{asked}'", m.provider) })?; + let dialect = decl.name; + let statics = match p.auth.as_deref() { + Some(AUTH_API_KEY | AUTH_JWT_BEARER | AUTH_OAUTH_CLIENT_CREDENTIALS) => &[][..], + _ => decl.static_headers, + }; let wire = m.upstream_model.as_deref().unwrap_or(&name); let lane = Lane { model: name.clone(), @@ -400,6 +420,7 @@ impl Shaping { prompt_caching: m.prompt_caching.unwrap_or(false), caps: lane_caps(p, wire), error_map: p.error_map.clone(), + statics, }; lanes.insert(name, lane); } diff --git a/crates/busbar-plane-llm/src/plane_door.rs b/crates/busbar-plane-llm/src/plane_door.rs index 03d278f94c..55258960f7 100644 --- a/crates/busbar-plane-llm/src/plane_door.rs +++ b/crates/busbar-plane-llm/src/plane_door.rs @@ -11,8 +11,9 @@ //! The composition root links [`door`] on its `plane-door` axis under the llm fold's //! development-only switch `llm-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 llm door, and with the switch on the root's existing llm row still answers every arrival until -//! the serve path hands this door the arrivals it takes, so one arrival never has two servers. +//! no llm door. With the switch on, the serve path composes this door with the kernel-owned sections +//! it reads and mounts its claims as the data routes, ahead of the root's existing llm row, so every +//! arrival this door claims is served through the plane driver and the row answers none of them. //! //! One unit, as the plane driver serves it: //! @@ -42,11 +43,14 @@ use std::mem::size_of; use std::ptr; use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use busbar_contract::abi::hook::{REQUEST_HAS_MAX_TOKENS, REQUEST_HAS_TOOLS, REQUEST_STREAM}; use busbar_contract::abi::host::conn::connector::{ Need, DIRECTION_OUTBOUND, EGRESS_PROVIDER, KEEP_ALL_EXCEPT_DENIED, }; use busbar_contract::abi::host::service::ClockReading; -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_CONSUMED, SECTION_DECLARING, }; @@ -55,13 +59,14 @@ use busbar_contract::abi::mechanism::lifecycle::{ }; use busbar_contract::abi::mechanism::ticket::{CompletionHandle, Ticket}; use busbar_contract::abi::plane::{ - ArriveIn, ArriveOut, BillableClass, OnPieceIn, OnPieceOut, OpClass, OutField, PlaneDriveIn, - PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, PlaneTail, ProjectIn, - ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, CANCEL_ABORTED, CLAIM_EXACT, - CLAIM_PROBE, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, - INGRESS_REQUEST_RESPONSE, INGRESS_RESPONSE_STREAM, PIECE_HAS_STATUS, PIECE_LAST, - PRINCIPAL_NONE, PRINCIPAL_REQUIRED, SHAPE_PIECEWISE, TAIL_FALLBACK, TAIL_PROBES, - UNITS_REPORTED, VERDICT_HARD, VERDICT_NONE, VERDICT_OK, VERDICT_RETRY, + ArriveIn, ArriveOut, BillableClass, DialectAuth, OnPieceIn, OnPieceOut, OpClass, OutField, + PlaneDriveIn, PlaneDriveOut, PlaneOpenIn, PlaneOpenOut, PlaneRefreshOut, PlaneSnapshot, + PlaneTail, ProjectIn, ProjectOut, RefusalIn, RefusalOut, ServeIn, ServeOut, UnitCount, + CANCEL_ABORTED, CLAIM_EXACT, CLAIM_PROBE, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, + FROM_FAR_END, FROM_KERNEL, INGRESS_REQUEST_RESPONSE, INGRESS_RESPONSE_STREAM, PIECE_HAS_STATUS, + PIECE_LAST, PRINCIPAL_NONE, PRINCIPAL_REQUIRED, REFUSAL_GATE, ROUTE_DIRECT, ROUTE_POOL, + SHAPE_PIECEWISE, SPAN_ABSENT, TAIL_FALLBACK, TAIL_PROBES, UNITS_REPORTED, VERDICT_HARD, + VERDICT_NONE, VERDICT_OK, VERDICT_RETRY, }; use busbar_contract::abi::sdk::door::{abi_str, statement}; use busbar_contract::abi::sdk::life::Refusal; @@ -79,7 +84,7 @@ use crate::exchange::arrive::{self, envelope_for, Arrived, Declined}; use crate::exchange::attempt::{self, stream_intent, FarRequest}; use crate::exchange::reply::{At, Piece, Reply, ReplyCtx, Units, Verdict}; use crate::exchange::shaping::{sections, Shaping}; -use crate::exchange::{handler_of, probe, refuse}; +use crate::exchange::{handler_of, probe, project, refuse}; use crate::LlmPlane; // ── the Statement ──────────────────────────────────────────────────────────────────────────────── @@ -92,6 +97,8 @@ const MAX_INFLIGHT: u32 = 1024; /// The human label. const LABEL: &str = "LLM"; +/// One registration of the `pools:` map. +const POOL_NOUN: &str = "pool"; /// The transport claim a far end is reached over. const TRANSPORT: &str = "http"; @@ -130,9 +137,19 @@ const fn op(i: usize) -> OpClass { OpClass { op: name, name } } +/// THE TOKEN CLASSES' CARD NAMES, in [`METER`]'s order: the classes 1.5.5's `rate_card:` prices +/// (`input`, `output`, `cache_read`, `cache_write`), which the previous release's plane declaration +/// ledgered its four token tiers under, so a door-served count is priced by the card as written. +pub const TOKEN_CLASSES: [&str; 4] = [ + busbar_contract::records::UNIT_INPUT, + busbar_contract::records::UNIT_OUTPUT, + busbar_contract::records::UNIT_CACHE_READ, + busbar_contract::records::UNIT_CACHE_WRITE, +]; + const fn billable(i: usize) -> BillableClass { BillableClass { - class: abi_str(METER[i].key.as_str()), + class: abi_str(TOKEN_CLASSES[i]), family: abi_str(METER[i].family), } } @@ -174,18 +191,57 @@ const _: () = assert!( "the tail states every dialect, op class and token class the plane declares" ); -/// The egress-auth schemes the dialects decorate a far-end request with ([`DIALECTS`]' -/// `egress_scheme`), each named once. -pub const EGRESS_SCHEMES: &[&str] = &[DIALECTS[0].egress_scheme, DIALECTS[3].egress_scheme]; +/// THE OUTBOUND AUTH STYLES THE PLANE'S MEMBERS ARE BOUND UNDER (the design's auth points): each +/// dialect's default ([`DIALECTS`]' `egress_style`), then the provider `auth:` overrides the +/// configuration grammar accepts (`api-key` is also a dialect default), each named once. One +/// outbound need per style ([`NEEDS`]): a member dials the need its resolved style names. +pub const EGRESS_STYLES: &[&str] = &[ + DIALECTS[1].egress_style, + DIALECTS[0].egress_style, + DIALECTS[2].egress_style, + DIALECTS[3].egress_style, + "jwt-bearer", + "oauth-client-credentials", +]; const _: () = assert!( - const_eq(DIALECTS[0].egress_scheme, DIALECTS[1].egress_scheme) - && const_eq(DIALECTS[0].egress_scheme, DIALECTS[2].egress_scheme) - && const_eq(DIALECTS[0].egress_scheme, DIALECTS[4].egress_scheme) - && const_eq(DIALECTS[0].egress_scheme, DIALECTS[5].egress_scheme) - && !const_eq(DIALECTS[0].egress_scheme, DIALECTS[3].egress_scheme), - "EGRESS_SCHEMES names every dialect's scheme, each once" + const_eq(DIALECTS[1].egress_style, DIALECTS[4].egress_style) + && const_eq(DIALECTS[1].egress_style, DIALECTS[5].egress_style) + && !const_eq(DIALECTS[0].egress_style, DIALECTS[1].egress_style) + && !const_eq(DIALECTS[2].egress_style, DIALECTS[1].egress_style) + && !const_eq(DIALECTS[3].egress_style, DIALECTS[1].egress_style), + "EGRESS_STYLES names every dialect's default style, each once" ); +const fn dialect_auth(i: usize) -> DialectAuth { + let params = DIALECTS[i].egress_params.as_bytes(); + DialectAuth { + dialect: i as u32, + _reserved: 0, + style: abi_str(DIALECTS[i].egress_style), + // The default style's parameters (ARCHITECT RULING 2026-10-03, Q-L6-AUTHPARAMS). + params: if params.is_empty() { + Blob::ABSENT + } else { + Blob { + ptr: params.as_ptr(), + len: params.len(), + fmt: busbar_contract::abi::mechanism::call::BLOB_JSON, + flags: 0, + } + }, + } +} + +/// THE TAIL'S `dialect_auth`: each dialect's default outbound style (ARCHITECT Q-L1-AUTH (A)). +const DIALECT_AUTH: &[DialectAuth] = &[ + dialect_auth(0), + dialect_auth(1), + dialect_auth(2), + dialect_auth(3), + dialect_auth(4), + dialect_auth(5), +]; + const fn const_eq(a: &str, b: &str) -> bool { let (a, b) = (a.as_bytes(), b.as_bytes()); if a.len() != b.len() { @@ -241,9 +297,20 @@ const fn need(auth: &'static str) -> Need { } /// THE PLANE'S NEEDS: outbound to a configured provider over the claim's transport, one per -/// egress-auth scheme a dialect decorates with ([`EGRESS_SCHEMES`]), as the decisions plane -/// declares its one. -pub const NEEDS: &[Need] = &[need(EGRESS_SCHEMES[0]), need(EGRESS_SCHEMES[1])]; +/// outbound style a member may be bound under ([`EGRESS_STYLES`]), each naming its style, as the +/// decisions plane's one need names its. +pub const NEEDS: &[Need] = &[ + need(EGRESS_STYLES[0]), + need(EGRESS_STYLES[1]), + need(EGRESS_STYLES[2]), + need(EGRESS_STYLES[3]), + need(EGRESS_STYLES[4]), + need(EGRESS_STYLES[5]), +]; + +/// THE RESOURCE A GRANT NAMES on this plane: a pool (a key's `allowed_pools`, the previous release's +/// `scope_kinds: ["pool"]`), so the kernel judges a route as named against the caller's grant. +const SCOPE_KINDS: &[AbiStr] = &[abi_str("pool")]; /// THE STATEMENT TAIL: the plane's static facts. pub const TAIL: &PlaneTail = &PlaneTail { @@ -258,18 +325,20 @@ pub const TAIL: &PlaneTail = &PlaneTail { _reserved: 0, scope: abi_str(sections::POOLS), label: abi_str(LABEL), - subject_noun: NONE, - admin_noun: NONE, + // What one registration of the `pools:` map is called, in a refusal naming it ("a pool may not + // be named `hooks`") and in the admin surface, as the previous release's declaration named it. + subject_noun: abi_str(POOL_NOUN), + admin_noun: abi_str(POOL_NOUN), audit_kind: NONE, signing_domain: NONE, signing_kid_prefix: NONE, cli_help: NONE, dialects: DIALECT_NAMES.as_ptr(), dialects_len: DIALECT_NAMES.len(), - dialect_auth: ptr::null(), - dialect_auth_len: 0, - scope_kinds: ptr::null(), - scope_kinds_len: 0, + 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(), @@ -383,6 +452,9 @@ struct UnitState { started: Option, /// The answer the driver's re-call is owed. pending: Option, + /// `project` found the body unreadable: the unit's refusal reads the previous release's + /// unreadable-body sentence. + unreadable: bool, } impl UnitState { @@ -398,6 +470,7 @@ impl UnitState { reply: None, started: None, pending: None, + unreadable: false, } } @@ -507,6 +580,21 @@ fn dialect_index(name: &str) -> u32 { .unwrap_or(0) } +/// THE ROUTE AN ARRIVAL NAMES (ARCHITECT Q-SW6 / Q-FL3, 2026-10-02): the model it asked for, as the +/// entry inside the plane's own sections, and what that entry is. The previous release resolved the +/// model as a pool first, then as a configured model (its by-model lane); the plane says which by its +/// own tables, and the kernel resolves the entry. A model that is neither is named as a direct entry +/// the kernel does not hold, so the kernel refuses it (`no_destination`, rendered as 1.5.5's 404). +#[must_use] +pub fn route_of<'a>(shaping: &Shaping, model: &'a str) -> (u8, &'a str) { + let class = if shaping.pools.contains_key(model) { + ROUTE_POOL + } else { + ROUTE_DIRECT + }; + (class, model) +} + fn op_class_index(arrived: &Arrived) -> u32 { OPS.iter() .position(|op| op.as_str() == arrived.operation.name()) @@ -952,6 +1040,8 @@ slot!( out.set(|o| &o.op_class, op_class_index(&arrived)); out.set(|o| &o.dialect, dialect_index(arrived.dialect)); out.set(|o| &o.principal_need, PRINCIPAL_REQUIRED); + let (class, entry) = route_of(&unit.shaping, &arrived.model); + out.route(class, entry); unit.arrived = Some(arrived); guard(&door.units).insert(given.unit, unit); Outcome::Ready @@ -1040,7 +1130,9 @@ slot!( // A model that resolved to no destination reads the previous release's not-found // sentence, which names the model the caller asked for; every other refusal reads // the kernel's own text. + let unreadable = held.as_ref().is_some_and(|u| u.unreadable); let text = match arrived { + _ if unreadable => project::UNREADABLE_BODY_MESSAGE.to_string(), Some(a) if given.reason == crate::refusal::reason::NO_DESTINATION => { refuse::model_not_found( &a.model, @@ -1051,13 +1143,12 @@ slot!( } _ => String::from_utf8_lossy(input.field(|i| &i.text).bytes()).into_owned(), }; - refuse::kernel_refusal( - envelope, - given.reason, - u16::try_from(given.status).unwrap_or(500), - &text, - given.retry_after_s, - ) + let status = u16::try_from(given.status).unwrap_or(500); + if given.cause == REFUSAL_GATE { + refuse::gate_refusal(envelope, status, &text) + } else { + refuse::kernel_refusal(envelope, given.reason, status, &text, given.retry_after_s) + } } }; let (mut reply, mut fields, mut arena) = @@ -1107,8 +1198,99 @@ slot!( ); slot!( - /// `project`: not bound yet; the driver's hook stage answers it at the llm flip. - Project, ProjectIn, ProjectOut, |_, _, _| { Outcome::Refused } + /// `project`: the hook view of the unit's request ([`crate::exchange::project`]), its strings in + /// the host's arena and its turns in the host's turn buffer; with a request-stage hook's + /// rewrite, the rewrite applied to the unit's request first (kept as the unit's request, so + /// every attempt is written from it), the rewritten body answered and THAT body projected. A + /// body the operation's reader refuses is REFUSED, and the unit's refusal then reads the + /// previous release's unreadable-body sentence. + Project, ProjectIn, ProjectOut, |instance, input, mut out| { + let Some(door) = instance.get() else { + return Outcome::Failed; + }; + let given = input.get(); + let mut units = guard(&door.units); + // A unit that never arrived (or arrived declined, or as a probe) has nothing to project. + let Some(arrived) = units.get_mut(&given.unit).and_then(|u| u.arrived.as_mut()) else { + return Outcome::Refused; + }; + let rewrite = input.field(|i| &i.rewrite).bytes(); + let rewritten = if rewrite.is_empty() { + None + } else { + project::apply_rewrite(arrived, rewrite) + }; + let view = match project::project(arrived) { + Ok(view) => view, + Err(project::Unreadable) => { + if let Some(unit) = units.get_mut(&given.unit) { + unit.unreadable = true; + } + return Outcome::Refused; + } + }; + let dialect = arrived.dialect; + let pool = arrived.model.clone(); + drop(units); + let (signals, mut arena, mut turns) = + (input.signals_buf(), input.arena_buf(), input.messages_buf()); + let pool = arena.span(pool.as_bytes()); + let dialect = arena.span(dialect.as_bytes()); + let system = view.system.as_deref().map(|s| arena.span(s.as_bytes())); + let end_user = view.end_user.as_deref().map(|s| arena.span(s.as_bytes())); + for (role, text) in &view.turns { + let role = arena.span(role.as_bytes()); + let text = arena.span(text.as_bytes()); + turns.push_turn(&arena, role, text); + } + let body = rewritten.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); + out.set(|o| &o.body, Span { offset: SPAN_ABSENT, len: 0 }); + if short { + // The driver re-calls once with the buffers this named, the same rewrite with it: + // applying it again to the request it already rewrote writes the same body. + return Outcome::Failed; + } + let mut flags = 0; + if view.max_tokens.is_some() { + flags |= REQUEST_HAS_MAX_TOKENS; + } + if view.has_tools { + flags |= REQUEST_HAS_TOOLS; + } + if view.stream { + flags |= REQUEST_STREAM; + } + out.host_str(|o| &o.view.pool, &arena, pool); + out.host_str(|o| &o.view.ingress_dialect, &arena, dialect); + out.set(|o| &o.view.message_count, view.turn_count as u64); + out.set(|o| &o.view.total_chars, view.text_chars as u64); + out.set(|o| &o.view.max_tokens, view.max_tokens.unwrap_or(0)); + out.set(|o| &o.view.flags, flags); + out.host_rows(|o| &o.view.signals, &signals); + out.set(|o| &o.view.signals_len, sw); + if let Some(system) = system { + out.host_str(|o| &o.prompt.system, &arena, system); + } + 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(end_user) = end_user { + out.host_str(|o| &o.end_user, &arena, end_user); + } + out.set( + |o| &o.rewritten, + body.unwrap_or(Span { offset: SPAN_ABSENT, len: 0 }), + ); + Outcome::Ready + } ); busbar_contract::plugin_door! { diff --git a/crates/busbar-plane-llm/tests/exchange_attempt.rs b/crates/busbar-plane-llm/tests/exchange_attempt.rs index 04ebdb9cca..80a4710ed5 100644 --- a/crates/busbar-plane-llm/tests/exchange_attempt.rs +++ b/crates/busbar-plane-llm/tests/exchange_attempt.rs @@ -16,6 +16,8 @@ fn shaping() -> Shaping { "providers": { "oai": { "protocol": "openai", "base_url": "https://api.example" }, "ant": { "protocol": "anthropic", "base_url": "https://anthropic.example" }, + "ant-key": { "protocol": "anthropic", "base_url": "https://az.example", "auth": "api-key" }, + "ant-bearer": { "protocol": "anthropic", "base_url": "https://b.example", "auth": "bearer" }, "goo": { "protocol": "gemini", "base_url": "https://gemini.example" }, "fixed": { "protocol": "openai", "base_url": "https://x.example", "path": "/custom/chat" } }, @@ -23,6 +25,8 @@ fn shaping() -> Shaping { "gpt": { "provider": "oai" }, "gpt-alias": { "provider": "oai", "upstream_model": "gpt-4o" }, "claude": { "provider": "ant", "default_max_tokens": 321 }, + "claude-key": { "provider": "ant-key", "default_max_tokens": 321 }, + "claude-bearer": { "provider": "ant-bearer", "default_max_tokens": 321 }, "gem": { "provider": "goo", "upstream_model": "gemini-pro" }, "fixed": { "provider": "fixed" } }, @@ -132,20 +136,21 @@ fn a_same_dialect_caller_s_fields_all_go_out_but_the_governed_ones() { assert_eq!( names, [ + "anthropic-version", + "content-type", "user-agent", "accept", - "content-type", - "anthropic-version", "anthropic-beta", "anthropic-beta", "x-client-trace" ], - "busbar's native defaults the caller did not send, then every caller field but the credential" + "busbar's own fields at their positions (the caller's values where it sent one), then every \ + other caller field but the credential, as 1.5.5's header map laid them" ); assert_eq!( field(&r, "user-agent"), [b"Anthropic/Python 0.39.0".as_slice()], - "a caller that sent no user-agent: the native client's (1.5.5's bytes)" + "a caller that sent no user-agent: the native client's (1.5.5's bytes, Q10)" ); assert_eq!(field(&r, "accept"), [b"application/json".as_slice()]); assert_eq!( @@ -436,3 +441,103 @@ fn the_providers_tenant_goes_out_and_the_callers_does_not() { assert_eq!(field(&r, "openai-organization"), [b"org-cfg".as_slice()]); assert_eq!(field(&r, "openai-project"), [b"proj-cfg".as_slice()]); } + +/// THE DIALECT'S STATIC FIELDS (ARCHITECT SD-3 (1), "anthropic S2-a": anthropic declares +/// `anthropic-version`, written verbatim; byte-identical upstream requests): a caller that sends no +/// field of its own still reaches an anthropic far end with the pinned version and the native +/// client's user-agent, same-dialect or translated; under an operator's `auth: api-key` override, +/// whose scheme declares no static field, none goes out; `auth: bearer` is the dialect's own scheme +/// and keeps it. A same-dialect caller's own value replaces it. +#[test] +fn no_client_header_leaves_egress_unchanged() { + let none = head(&[]); + let body = r#"{"model":"claude","max_tokens":5,"messages":[{"role":"user","content":"hi"}]}"#; + let a = arrived("/v1/messages", &none, body); + let r = build(&a, &none, &shaping(), "p", "claude").expect("built"); + assert_eq!(field(&r, "anthropic-version"), [b"2023-06-01".as_slice()]); + assert_eq!( + field(&r, "user-agent"), + [b"Anthropic/Python 0.39.0".as_slice()] + ); + let names: Vec<&str> = r.fields.iter().map(|(n, _)| n.as_str()).collect(); + assert_eq!( + names, + ["anthropic-version", "content-type", "user-agent", "accept"] + ); + + let openai = r#"{"model":"claude","messages":[{"role":"user","content":"hi"}]}"#; + let a = arrived("/v1/chat/completions", &none, openai); + let r = build(&a, &none, &shaping(), "p", "claude").expect("built"); + assert_eq!( + field(&r, "anthropic-version"), + [b"2023-06-01".as_slice()], + "a translated route into anthropic carries it too" + ); + + let a = arrived("/v1/messages", &none, &body.replace("claude", "claude-key")); + let r = build(&a, &none, &shaping(), "p", "claude-key").expect("built"); + assert!( + field(&r, "anthropic-version").is_empty(), + "an api-key override declares no static field" + ); + let a = arrived( + "/v1/messages", + &none, + &body.replace("claude", "claude-bearer"), + ); + let r = build(&a, &none, &shaping(), "p", "claude-bearer").expect("built"); + assert_eq!(field(&r, "anthropic-version"), [b"2023-06-01".as_slice()]); + + let own = head(&[("anthropic-version", "2024-01-01")]); + let a = arrived("/v1/messages", &own, body); + let r = build(&a, &own, &shaping(), "p", "claude").expect("built"); + assert_eq!( + field(&r, "anthropic-version"), + [b"2024-01-01".as_slice()], + "the same-dialect caller's own value wins" + ); + + let a = arrived( + "/v1/chat/completions", + &none, + &openai.replace("claude", "gpt"), + ); + let r = build(&a, &none, &shaping(), "p", "gpt").expect("built"); + assert!( + field(&r, "anthropic-version").is_empty(), + "openai declares none" + ); +} + +/// THE PREVIOUS RELEASE'S HEADER-MAP ORDER (oracle `egress.auth|cred-identity|*`, `route.529|*`): a +/// same-dialect caller's own fields lie over busbar's as a header map lays them, one position per +/// name: a name busbar writes (its static field, `content-type`, `user-agent`, `accept`) keeps +/// busbar's position and takes the caller's value, a new name is appended in the caller's order, +/// and a repeated name keeps every value at its position. +#[test] +fn a_same_dialect_callers_fields_keep_busbars_positions() { + let h = head(&[ + ("user-agent", "Anthropic/Python 0.39.0"), + ("accept", "application/json"), + ("anthropic-version", "2023-06-01"), + ("content-type", "application/json"), + ("x-trace", "a"), + ("x-trace", "b"), + ]); + let body = r#"{"model":"claude","max_tokens":5,"messages":[{"role":"user","content":"hi"}]}"#; + let a = arrived("/v1/messages", &h, body); + let r = build(&a, &h, &shaping(), "p", "claude").expect("built"); + let names: Vec<&str> = r.fields.iter().map(|(n, _)| n.as_str()).collect(); + assert_eq!( + names, + [ + "anthropic-version", + "content-type", + "user-agent", + "accept", + "x-trace", + "x-trace" + ] + ); + assert_eq!(field(&r, "x-trace"), [b"a".as_slice(), b"b".as_slice()]); +} diff --git a/crates/busbar-plane-llm/tests/exchange_reply.rs b/crates/busbar-plane-llm/tests/exchange_reply.rs index 668a04ba79..daa93edfa2 100644 --- a/crates/busbar-plane-llm/tests/exchange_reply.rs +++ b/crates/busbar-plane-llm/tests/exchange_reply.rs @@ -659,6 +659,7 @@ fn lane(dialect: &'static str) -> Lane { prompt_caching: false, caps: Default::default(), error_map: Default::default(), + statics: &[], } } diff --git a/crates/busbar-plane-llm/tests/hook_absent_projection.rs b/crates/busbar-plane-llm/tests/hook_absent_projection.rs new file mode 100644 index 0000000000..3f3d9993ef --- /dev/null +++ b/crates/busbar-plane-llm/tests/hook_absent_projection.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! ABSENT SEMANTICS for the hook projection, on the plane that projects it (ported from the +//! predev-only `busbar-llm` test `hook_non_chat_projection_tests:: +//! absent_semantics_hold_for_opless_and_bodyless`, whose `read_hook_facts` is the plane's `project` +//! on the driver): an arrival the plane has no operation reader for, or one with no body, projects +//! the EMPTY view (no turn, no system text, no size signal) and is never refused as unreadable. + +use busbar_plane_llm::exchange::arrive::Arrived; +use busbar_plane_llm::exchange::project::{project, HookView}; + +fn arrived(dialect: &'static str, body: &[u8], parsed: Option) -> Arrived { + Arrived { + dialect, + operation: busbar_contract::operation::OpVerb::EMBEDDINGS, + model: "m0".to_string(), + content_type: "application/json".to_string(), + body: body.to_vec(), + parsed, + path: "/v1/embeddings".to_string(), + query: None, + path_model: None, + } +} + +#[test] +fn absent_semantics_hold_for_opless_and_bodyless() { + // A JSON object body under a dialect with no operation reader: the empty view, not a refusal. + let v = serde_json::json!({"input": "x"}); + let view = project(&arrived( + "not-a-protocol", + &serde_json::to_vec(&v).expect("serializes"), + Some(v), + )) + .expect("an opless arrival is not unreadable"); + assert_eq!(view, HookView::default()); + // No body at all: the empty view. + let view = + project(&arrived("openai", b"", None)).expect("a bodyless arrival is not unreadable"); + assert_eq!(view.turns, Vec::<(String, String)>::new()); + assert_eq!( + (view.system, view.text_chars, view.turn_count), + (None, 0, 0) + ); +} diff --git a/crates/busbar-plane-llm/tests/hook_max_tokens_saturation_1_5_5.rs b/crates/busbar-plane-llm/tests/hook_max_tokens_saturation_1_5_5.rs new file mode 100644 index 0000000000..5c12df8fce --- /dev/null +++ b/crates/busbar-plane-llm/tests/hook_max_tokens_saturation_1_5_5.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! The size signal a hook sees for an output cap above `u32::MAX`, as 1.5.5 projected it +//! (HOOK-DRIFTS, lane-hook-drifts 7a0964496e: "1.5.5 behaviour wins"). +//! +//! In 1.5.5 the hook projection read the cap from the caller's body and saturated it +//! (`max_tokens_for`: `u32::try_from(n).unwrap_or(u32::MAX)`), under the dialect's own key +//! (`max_output_tokens` for the responses dialect, `max_tokens` for every other), and the v1.5.5 +//! hook tests pinned it: `max_tokens_saturates_not_wraps` expected `Some(u32::MAX)` for +//! `5_000_000_000`, and `max_tokens_signal_is_dialect_aware_for_responses` expected `Some(u32::MAX)` +//! for `u64::MAX`. The plane's hook projection (`project`) reads it so; the IR's cap is the far +//! end's, which a reader drops when it cannot carry it, and it is not the hook signal. + +use busbar_plane_llm::exchange::arrive::arrive; +use busbar_plane_llm::exchange::project::{max_tokens_for, project}; + +fn hook_max_tokens(target: &str, body: serde_json::Value) -> Option { + let bytes = serde_json::to_vec(&body).expect("body serializes"); + let fields: &[(&[u8], &[u8])] = &[(b"content-type", b"application/json")]; + let arrived = arrive("POST", target, fields, &bytes, &()).expect("the request arrives"); + project(&arrived).expect("the request reads").max_tokens +} + +#[test] +fn an_anthropic_cap_above_u32_saturates() { + let body = serde_json::json!({ + "model": "m0", + "max_tokens": 5_000_000_000u64, + "messages": [{"role": "user", "content": "hi"}] + }); + assert_eq!(hook_max_tokens("/v1/messages", body), Some(u32::MAX)); +} + +#[test] +fn an_openai_cap_above_u32_saturates() { + let body = serde_json::json!({ + "model": "m0", + "max_tokens": u64::MAX, + "messages": [{"role": "user", "content": "hi"}] + }); + assert_eq!( + hook_max_tokens("/v1/chat/completions", body), + Some(u32::MAX) + ); +} + +#[test] +fn the_cap_is_read_under_the_dialects_own_key() { + let responses = + serde_json::json!({"input": "hi", "max_tokens": 999, "max_output_tokens": 4096}); + assert_eq!(max_tokens_for(&responses, "responses"), Some(4096)); + let anthropic = serde_json::json!({"max_tokens": 512}); + assert_eq!(max_tokens_for(&anthropic, "anthropic"), Some(512)); + assert_eq!(max_tokens_for(&anthropic, "gemini"), Some(512)); + assert_eq!( + max_tokens_for(&serde_json::json!({"max_tokens": u64::MAX}), "anthropic"), + Some(u32::MAX) + ); +} diff --git a/crates/busbar-plane-llm/tests/plane_door.rs b/crates/busbar-plane-llm/tests/plane_door.rs index bce1063ff3..8b03300d98 100644 --- a/crates/busbar-plane-llm/tests/plane_door.rs +++ b/crates/busbar-plane-llm/tests/plane_door.rs @@ -10,8 +10,8 @@ use busbar_contract::abi::plane::{TAIL_FALLBACK, TAIL_PROBES, UNITS_REPORTED}; use busbar_plane_llm::dialect::DIALECTS; use busbar_plane_llm::exchange::reply::Units; use busbar_plane_llm::plane_door::{ - claims, counts, read_settings, DENY_RESPONSE_HEADERS, EGRESS_SCHEMES, NEEDS, OPEN_CLASSES, - STATEMENT, TAIL, VERSION, + claims, counts, read_settings, route_of, DENY_RESPONSE_HEADERS, EGRESS_STYLES, NEEDS, + OPEN_CLASSES, STATEMENT, TAIL, VERSION, }; #[test] @@ -43,22 +43,40 @@ fn the_tail_is_one_the_contract_accepts() { assert_eq!(OPEN_CLASSES, &[("search_units", "units")]); } +/// ARCHITECT Q-L1-AUTH (A): every dialect states its default outbound style in the tail, and every +/// style a member may be bound under (each dialect's default, the provider `auth:` overrides) has +/// exactly one outbound need naming it. #[test] -fn every_dialects_egress_scheme_has_its_one_outbound_need() { +fn every_dialects_default_style_is_stated_and_every_style_has_its_one_outbound_need() { assert_eq!(check_needs(NEEDS), Ok(())); assert_eq!(STATEMENT.needs_len, NEEDS.len()); - for d in DIALECTS { + assert_eq!(TAIL.dialect_auth_len, DIALECTS.len()); + for (i, d) in DIALECTS.iter().enumerate() { + // SAFETY: the tail's `'static` dialect_auth list of `dialect_auth_len` entries. + let stated = unsafe { *TAIL.dialect_auth.add(i) }; + assert_eq!(stated.dialect as usize, i); + // SAFETY: a `'static` str the door states (abi_str). + let style = unsafe { std::slice::from_raw_parts(stated.style.ptr, stated.style.len) }; + assert_eq!(style, d.egress_style.as_bytes(), "{}", d.name); assert_eq!( - EGRESS_SCHEMES + EGRESS_STYLES .iter() - .filter(|s| **s == d.egress_scheme) + .filter(|s| **s == d.egress_style) .count(), 1, "{}", d.name ); } - assert_eq!(NEEDS.len(), EGRESS_SCHEMES.len()); + for style in [ + "api-key", + "bearer", + "jwt-bearer", + "oauth-client-credentials", + ] { + assert!(EGRESS_STYLES.contains(&style), "{style}"); + } + assert_eq!(NEEDS.len(), EGRESS_STYLES.len()); } #[test] @@ -255,3 +273,22 @@ fn red_no_dialects_governed_response_field_leaks_through_a_need() { } } } + +/// THE ROUTE AN ARRIVAL NAMES (ARCHITECT Q-SW6 / Q-FL3, 2026-10-02): its model, verbatim, as a pool +/// when the plane's `pools` names it, else as a direct entry, the previous release's order (a pool +/// first, then a configured model). A model that is neither is still named, as a direct entry the +/// kernel does not hold, so the kernel refuses it (`no_destination`, 1.5.5's 404). +#[test] +fn an_arrival_routes_over_its_model_a_pool_first_then_a_direct_entry() { + use busbar_contract::abi::plane::{ROUTE_DIRECT, ROUTE_POOL}; + let shaping = read_settings( + br#"{"providers":{"ant":{"protocol":"anthropic","base_url":"https://anthropic.example"}}, + "models":{"claude":{"provider":"ant"},"both":{"provider":"ant"}}, + "pools":{"p":{"members":["claude"]},"both":{"members":["claude"]}}}"#, + ) + .expect("a well-formed generation reads"); + assert_eq!(route_of(&shaping, "p"), (ROUTE_POOL, "p")); + assert_eq!(route_of(&shaping, "claude"), (ROUTE_DIRECT, "claude")); + assert_eq!(route_of(&shaping, "both"), (ROUTE_POOL, "both")); + assert_eq!(route_of(&shaping, "nope"), (ROUTE_DIRECT, "nope")); +} diff --git a/crates/plugin-loader/src/dispatch/kinds/plane.rs b/crates/plugin-loader/src/dispatch/kinds/plane.rs index ee27a4a9f6..4deb2b70df 100644 --- a/crates/plugin-loader/src/dispatch/kinds/plane.rs +++ b/crates/plugin-loader/src/dispatch/kinds/plane.rs @@ -34,10 +34,10 @@ use std::sync::Arc; -use busbar_contract::abi::mechanism::call::{AbiStr, Outcome}; +use busbar_contract::abi::mechanism::call::{AbiStr, Blob, Outcome}; use busbar_contract::abi::mechanism::call::{InHead, OutHead}; use busbar_contract::abi::mechanism::check::{fault, reported, Fault, Rule}; -use busbar_contract::abi::mechanism::door::{Statement, SECTION_DECLARING}; +use busbar_contract::abi::mechanism::door::{Statement, SECTION_CONSUMED, SECTION_DECLARING}; use busbar_contract::abi::mechanism::lifecycle::{ slot as life, CancelOut, DriveIn, OpenIn, RefreshIn, }; @@ -110,6 +110,9 @@ pub struct PlaneFacts { pub struct ServedFacts { /// The key of the one section the Statement declares. pub section: &'static str, + /// The keys of the sections the Statement reads beside it (`SECTION_CONSUMED`), in its order: + /// the plane opens with all of them, one `{section: value}` object (spec Part 1 §4). + pub consumed: Vec<&'static str>, /// The tail's operation classes, in order. pub op_classes: Vec<&'static str>, /// The tail's `audit_kind`. @@ -130,8 +133,9 @@ pub struct ServedFacts { /// The tail's dialects, in order. pub dialects: Vec<&'static str>, /// The tail's `dialect_auth`: each dialect's default outbound style, by its dialect index - /// (THE DESIGN §6 step 2: a provider entry's `auth:`, else this). - pub dialect_auth: Vec<(u32, &'static str)>, + /// (THE DESIGN §6 step 2: a provider entry's `auth:`, else this), and the style's parameters + /// for it, a JSON object's text (empty: none; ARCHITECT RULING 2026-10-03, Q-L6-AUTHPARAMS). + pub dialect_auth: Vec<(u32, &'static str, &'static [u8])>, } /// ONE NEED'S RESPONSE-HEAD RULE, as its Statement declares it. @@ -183,6 +187,12 @@ fn tail_facts(st: &Statement) -> Result { .collect(); check_refusal_statuses(&refusal_statuses, tail.dialects_len as u64) .map_err(|f| format!("the plane tail breaks {:?} at {}", f.rule, f.field))?; + // Each dialect's default style and its parameters, judged before they are kept. + busbar_contract::abi::plane::check::check_dialect_auth( + &listed(tail.dialect_auth, tail.dialect_auth_len), + tail.dialects_len as u64, + ) + .map_err(|f| format!("the plane tail breaks {:?} at {}", f.rule, f.field))?; Ok(PlaneFacts { bounds: Bounds::of(&tail), refusal_statuses, @@ -192,6 +202,11 @@ fn tail_facts(st: &Statement) -> Result { .iter() .find(|s| s.flags & SECTION_DECLARING != 0) .map_or("", |s| kept(s.name)), + consumed: sections + .iter() + .filter(|s| s.flags & SECTION_CONSUMED != 0) + .map(|s| kept(s.name)) + .collect(), op_classes: listed(tail.op_classes, tail.op_classes_len) .into_iter() .map(|c| kept(c.op)) @@ -229,7 +244,7 @@ fn tail_facts(st: &Statement) -> Result { .collect(), dialect_auth: listed(tail.dialect_auth, tail.dialect_auth_len) .into_iter() - .map(|d| (d.dialect, kept(d.style))) + .map(|d| (d.dialect, kept(d.style), kept_blob(d.params))) .collect(), }, }) @@ -246,6 +261,17 @@ fn kept(s: AbiStr) -> &'static str { String::from_utf8_lossy(bytes).into_owned().leak() } +/// A tail blob's bytes, kept for the process (empty when absent). +fn kept_blob(b: Blob) -> &'static [u8] { + if b.ptr.is_null() || b.len == 0 { + return &[]; + } + // SAFETY: `check_dialect_auth` judged the blob (`tail_facts`): a non-NULL span of `len` bytes of + // `'static` plugin data, read here while the plugin is loaded and copied. + let bytes = unsafe { std::slice::from_raw_parts(b.ptr, b.len) }; + bytes.to_vec().leak() +} + /// A tail list of `n` `T`s at `p`, copied. fn listed(p: *const T, n: usize) -> Vec { if p.is_null() || n == 0 { From 826d1fa800c27be49796ff1d5ee14180bc0877a1 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:34:08 -0700 Subject: [PATCH 03/49] FLIP-LLM serve path: under the `llm-on-driver` switch the llm door is composed on the serve path and every arrival it claims is served through the kernel's plane driver, its hook stage bound and its egress the model-serving walk (BUSBAR-1.6.0.md Part 3 section 12 lines 2669-2674 "The switch ... a fold adds only its door rows. A development-only cargo feature flips a plane onto the driver during its fold ... The llm flip's gate is every hook test green on the driver"; lines 2573-2576 the route pump is the kernel's existing egress walk) Ported from the lane-dg-llm snapshot's FLIP-LLM (2), (4), (5), (6), (8a), (8b), oracle rounds and follow-ups (24cda1eeb5, e91a20b141, 5ac7cc8790, b26be1ca6d, c679bf1cd5, 072d65cd54, 99d4dd92e9, 8b7b35dd0d, e581423073, 80397326aa, 5613e7ba55), onto predev's serving slice, without the default flip, DEL-LLM, DECL-FOLD, h2 or K7-door-probe pieces: - root::door_steps: kernel_sections (providers' dialect fields, models, pools, limits) beside the door sections; the plane serving the pools map walks the uniform model-serving section; DoorConfig per generation (the root's ConfigProjection); the destination guard at verify with 1.5.5's words; a blocked admission's message and Retry-After; 1.5.5's flat request fee on that plane; the dialect's auth parameters sealed under the provider's own (sealed_settings). - root::model_egress (new): the model-serving walk over the kernel's own lane cells, every bound the configuration states (weights, attempt timeouts, max_concurrent, context windows, failover budget, on_exhausted, per-pool breaker ladders), outcomes through the lane store's record calls. - root::serve: the HookStage (HostHooks over the current generation's engine host, EngineCaller), a config apply refreshing the door-served llm, the open front door admitting anonymously on the pools plane, the forward span, the request families and translation counter. - busbar Cargo.toml: `llm-on-driver` carries the sigv4 and oauth auth rows (auth-sigv4, auth-oauth on the `auths` axis, ARCHITECT Q-L1-AUTH (A)), so the default build is unchanged. - tests: tests/hook_parity_driver (the 1.5.5 hook tests of llm origin, 69, verbatim names and values, plus the 16 held legacy-crate hook tests; 85/85 green on the driver), root::serve hook-seat tests, the AUTHPARAMS seal test, computed_refusal_sites pins the hook-stage and destination-guard sites. --- Cargo.lock | 1 + crates/busbar/Cargo.toml | 23 +- crates/busbar/src/main.rs | 120 +- crates/busbar/src/root/door_steps.rs | 403 ++++++- crates/busbar/src/root/mod.rs | 1 + crates/busbar/src/root/model_egress.rs | 507 +++++++++ crates/busbar/src/root/serve.rs | 605 ++++++++-- crates/busbar/src/root/tests/door_steps.rs | 63 +- crates/busbar/src/root/tests/serve_door.rs | 13 +- .../busbar/src/root/tests/serve_hook_seats.rs | 548 +++++++++ crates/busbar/src/root/tests/serve_money.rs | 22 +- crates/busbar/src/root/tests/serve_planes.rs | 15 +- .../tests/fixtures/computed_refusal_sites.txt | 6 + .../busbar/tests/hook_parity_driver/held.rs | 581 ++++++++++ .../busbar/tests/hook_parity_driver/main.rs | 35 + .../tests/hook_parity_driver/policies.rs | 254 +++++ .../tests/hook_parity_driver/projection.rs | 768 +++++++++++++ crates/busbar/tests/hook_parity_driver/rig.rs | 658 +++++++++++ .../busbar/tests/hook_parity_driver/seam.rs | 1010 +++++++++++++++++ .../hook_parity_driver/speech_request.json | 6 + 20 files changed, 5539 insertions(+), 100 deletions(-) create mode 100644 crates/busbar/src/root/model_egress.rs create mode 100644 crates/busbar/src/root/tests/serve_hook_seats.rs create mode 100644 crates/busbar/tests/hook_parity_driver/held.rs create mode 100644 crates/busbar/tests/hook_parity_driver/main.rs create mode 100644 crates/busbar/tests/hook_parity_driver/policies.rs create mode 100644 crates/busbar/tests/hook_parity_driver/projection.rs create mode 100644 crates/busbar/tests/hook_parity_driver/rig.rs create mode 100644 crates/busbar/tests/hook_parity_driver/seam.rs create mode 100644 crates/busbar/tests/hook_parity_driver/speech_request.json diff --git a/Cargo.lock b/Cargo.lock index 8516881c55..a537ba0f64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,6 +314,7 @@ dependencies = [ "busbar-auth-oauth", "busbar-auth-oidc", "busbar-auth-oidc-plugin", + "busbar-auth-sigv4", "busbar-contract", "busbar-core-admin", "busbar-core-connector", diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index b7982f35e9..d2437c6ac7 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -141,6 +141,14 @@ busbar-auth-admin-tokens-plugin = { workspace = true, optional = true } # serves the style opens the binding), linked through the `auths` axis under `auth-header` # (ARCHITECT Q-L1-AUTH (A), 2026-10-03). Staged in-tree; its only busbar edge is `busbar-contract`. busbar-auth-header = { path = "../busbar-auth-header", optional = true } +# THE OUTBOUND SIGNING AND TOKEN STYLES (sigv4; jwt-bearer, oauth-client-credentials): the auth plugins +# an llm member is bound under when its provider's dialect default is `sigv4` (bedrock) or its `auth:` +# names an OAuth grant (THE DESIGN §6 step 3), linked through the `auths` axis under `auth-sigv4` and +# `auth-oauth` (ARCHITECT Q-L1-AUTH (A), 2026-10-03). Carried by the llm fold's switch while it is in +# flight (the default build serves llm through its `proto-llm` row and binds no member through them). +# Staged in-tree; their only busbar edge is `busbar-contract`. +busbar-auth-sigv4 = { path = "../busbar-auth-sigv4", optional = true } +busbar-auth-oauth = { path = "../busbar-auth-oauth", optional = true } # THE REQUEST-LOG FILE SINK (`module: request-log-file`), a first-party EXPORT plugin linked through # the `exports` axis (K9b) — the same row a signed tarball of it registers when dropped in. It lives # in its own repo (GetBusbar/busbar-export-file) and is pulled at a PINNED rev; the workspace `[patch]` @@ -294,7 +302,7 @@ plane-decisions-door = ["plane-decisions"] # 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 `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"] +llm-on-driver = ["proto-llm", "dep:busbar-plane-llm", "auth-sigv4", "auth-oauth"] # 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"] @@ -336,6 +344,11 @@ auth-admin-tokens = ["dep:busbar-auth-admin-tokens-plugin"] # plugin serving a plane need's header style, on the `auths` axis. Off, a member whose style it serves # binds only when a signed tarball of the module is dropped into `plugins/`. auth-header = ["dep:busbar-auth-header"] +# THE OUTBOUND SIGNING STYLE'S LINKED ROW: `sigv4` (on with the llm fold's switch). +auth-sigv4 = ["dep:busbar-auth-sigv4"] +# THE OUTBOUND TOKEN STYLES' LINKED ROW: `jwt-bearer`, `oauth-client-credentials` (on with the llm +# fold's switch). +auth-oauth = ["dep:busbar-auth-oauth"] 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"] loom-model = ["busbar-kernel/loom-model"] @@ -368,6 +381,8 @@ export-otlp = "busbar-export-otlp" # THE AUTH AXIS: the operator credential's module, under its packaging feature. auth-admin-tokens = "busbar-auth-admin-tokens-plugin" auth-header = "busbar-auth-header" +auth-sigv4 = "busbar-auth-sigv4" +auth-oauth = "busbar-auth-oauth" # THE COLD-KIND AXIS's in-process rows: the default store (a required dependency: every build links # it) and the ranking hooks (under their feature). store-memory = "busbar-store-memory" @@ -417,6 +432,8 @@ export-prometheus = "export-doors" export-otlp = "export-doors" auth-admin-tokens = "auths" auth-header = "auths" +auth-sigv4 = "auths" +auth-oauth = "auths" store-memory = "stores" hooks-ranking = "hooks" secret-env = "secret-doors" @@ -446,6 +463,8 @@ busbar-secret-file = "busbar_secret_file::door" # An auth plugin's entry is its crate root; its memory-ABI door is `::door::door`. busbar-auth-admin-tokens-plugin = "busbar_auth_admin_tokens_plugin" busbar-auth-header = "busbar_auth_header::compiled_in" +busbar-auth-sigv4 = "busbar_auth_sigv4::compiled_in" +busbar-auth-oauth = "busbar_auth_oauth::compiled_in" # A plane door row's entry is the module that exports its `door`. plane-decisions-door = "busbar_plane_decisions::plane_door" llm-on-driver = "busbar_plane_llm::plane_door" @@ -457,6 +476,8 @@ busbar-export-otlp = "busbar_export_otlp::door" [package.metadata.busbar.linked-name] auth-admin-tokens = "admin-tokens" auth-header = "busbar-auth-header" +auth-sigv4 = "busbar-auth-sigv4" +auth-oauth = "busbar-auth-oauth" # The root's own unit modules (the kernel-loop halves of planes that still live in the root), each # compiled when its row's cargo feature is on (the node under the `linked_axis_node` cfg that diff --git a/crates/busbar/src/main.rs b/crates/busbar/src/main.rs index aa5a6956ba..3c2cf4fb93 100644 --- a/crates/busbar/src/main.rs +++ b/crates/busbar/src/main.rs @@ -767,6 +767,12 @@ async fn run(data_workers: usize, late_services: std::sync::Arc), - ); + )); + // The kernel's own App (its lane store, its telemetry bank), through its swap handle once it exists (a config apply + // rebuilds the store), the boot App's until then. + let door_live_handle: std::sync::Arc< + std::sync::OnceLock>, + > = std::sync::Arc::default(); + let door_models = root::model_egress::ModelServing { + pools: door_model_pools, + lanes: app + .engine_tables_view() + .model_indices() + .into_iter() + .map(|(model, lane)| (model.to_string(), lane)) + .collect(), + app: { + let (live, boot) = ( + std::sync::Arc::clone(&door_live_handle), + std::sync::Arc::clone(&app), + ); + std::sync::Arc::new(move || { + live.get() + .map_or_else(|| std::sync::Arc::clone(&boot), |h| h.load()) + }) + }, + }; let door_reach = root::door_steps::DoorReach { providers: &door_providers, secrets: &*app.secret_resolver, @@ -853,17 +886,40 @@ async fn run(data_workers: usize, late_services: std::sync::Arc()) + else { + return; + }; + let models = root::model_egress::ModelServing { + pools: cfg.model_pools.clone(), + lanes: next + .engine_tables_view() + .model_indices() + .into_iter() + .map(|(model, lane)| (model.to_string(), lane)) + .collect(), + app: { + let (live, fallback) = + (std::sync::Arc::clone(&live), std::sync::Arc::clone(next)); + std::sync::Arc::new(move || { + live.get() + .map_or_else(|| std::sync::Arc::clone(&fallback), |h| h.load()) + }) + }, + }; + let reach = root::door_steps::DoorReach { + providers: &cfg.providers, + secrets: &*next.secret_resolver, + auths: &auths, + conns: Arc::clone(root::connector::the()) + as Arc, + stream_ceiling_secs: busbar_kernel::config::limits::installed().map_or( + busbar_kernel::config::limits::DEFAULT_UPSTREAM_REQUEST_TIMEOUT_SECS, + |l| l.upstream_request_timeout_secs, + ), + models: Some(&models), + }; + served.refresh( + &cfg.sections, + &root::serve::DoorEgress { + reach: &reach, + journal: std::sync::Arc::clone(&post) + as std::sync::Arc, + }, + ); + })); + } // THE ROOT-DRIVEN ADMIN SURFACE (composition-root switch-over S1), default-ON. The router that // answers the admin operations is unchanged; what the wrap adds is the path a request takes to // reach it — through the kernel's loop, past the auth, scope, admission, usage and audit units, diff --git a/crates/busbar/src/root/door_steps.rs b/crates/busbar/src/root/door_steps.rs index 7eeae80120..435213e397 100644 --- a/crates/busbar/src/root/door_steps.rs +++ b/crates/busbar/src/root/door_steps.rs @@ -211,8 +211,13 @@ fn members(pool: &serde_yaml::Value) -> Vec { /// THE KEY A DOOR PLANE'S ENTRY IS PRICED, METERED AND ROUTED UNDER (ARCHITECT Q-FL3: "keys the /// kernel's state by (plane key, model entry)"; #42/#47 one card per plane): `"\u{1f}"`. +/// The plane that owns the flat card (the empty plane key: 1.5.5's `rate_card:` and its lanes, +/// `busbar_kernel_ledger::cost::split_plane_lane`) keys its entries bare, as 1.5.5 did. #[must_use] pub fn plane_lane(plane: &str, entry: &str) -> String { + if plane.is_empty() { + return entry.to_string(); + } format!("{plane}{PLANE_LANE_SEP}{entry}") } @@ -230,6 +235,39 @@ pub fn egress_pool(plane: &str, routed: &Routed) -> String { } } +/// The pools and key the destination guard reads, for the plane serving the `pools` map. +struct DoorPoolView<'a> { + pools: &'a DoorPools, + key: Option<&'a VirtualKey>, + app: &'a App, +} + +impl busbar_kernel::door::PoolView for DoorPoolView<'_> { + fn has_key(&self) -> bool { + self.key.is_some() + } + + fn key_is_scoped(&self) -> bool { + self.key.is_some_and(|k| k.allowed_scopes.is_some()) + } + + fn pool_allowed(&self, pool: &str) -> bool { + self.key.is_none_or(|k| k.scope_allowed("pool", pool)) + } + + fn on_exhausted_fallback(&self, pool: &str) -> Option { + self.pools.fallback(pool).map(str::to_owned) + } + + fn is_configured(&self, name: &str) -> bool { + self.pools.pools().contains_key(name) || self.pools.entries().iter().any(|e| e == name) + } + + fn is_unpriced(&self, name: &str) -> bool { + self.app.cost.model_unpriced(name) + } +} + // ── admission ──────────────────────────────────────────────────────────────────────────────────── /// HOW A UNIT IS ADMITTED (ARCHITECT P3 (a), 2026-10-02). @@ -321,6 +359,10 @@ pub struct DoorFacts { /// Each need's response-head rule, in Statement need order: what of a far end's head reaches /// the plane on that need. pub keeps: Vec, + /// The plane's dialect names, by the index `arrive` answers, and each provider's dialect: an + /// attempt whose far end speaks another dialect than the caller's is counted as a translation + /// (`busbar_translations_total`), as the previous release counted it. Empty = none counted. + pub translations: (Arc<[String]>, Arc>), } /// What one unit carries between its steps. @@ -336,6 +378,9 @@ struct DoorUnit { grant: Option, /// Whether the unit's money facts were opened (it was charged). charged: bool, + /// The words of the refusal one of its steps raised, where it has its own (its Retry-After + /// seconds; its message). + refused: (Option, Option), /// The key the unit's record was written under on the host's unit records, once authenticate /// passed; it is struck when these steps drop. recorded: Option, @@ -477,7 +522,15 @@ impl<'s> DoorSteps<'s> { let units = self.expected_units(&expected); let grant = gov .try_admit_estimated(&self.app.cost, key, &pool, self.arrived, &models, &units) - .map_err(|blocked| refusal_for(&blocked))?; + .map_err(|blocked| { + // The plane serving the `pools` map answers a blocked admission in the previous + // release's words (`busbar_kernel::ingress::limit_refusal`). + if self.facts.plane.is_empty() { + self.lock().refused.1 = + Some(busbar_kernel::ingress::limit_refusal("", &blocked).2); + } + refusal_for(&blocked) + })?; money.open( ctx.key, UnitMoney { @@ -490,7 +543,14 @@ impl<'s> DoorSteps<'s> { classes: Arc::clone(&self.facts.classes), arrived: self.arrived, mode: exhaustion_of(&self.app, key), - fee: FeeRefund::PlaneFeeUnits(Arc::clone(&self.facts.fee_units)), + // The plane serving the `pools` map bills 1.5.5's flat request fee and refunds it + // under 1.5.5's rule (a non-2xx caller status); every other plane's fee is its + // reported fee units (#47). + fee: if self.facts.plane.is_empty() { + FeeRefund::CallerStatus + } else { + FeeRefund::PlaneFeeUnits(Arc::clone(&self.facts.fee_units)) + }, charge: grant.charge().clone(), }, ); @@ -534,6 +594,19 @@ impl DriverSteps for DoorSteps<'_> { fn expected(&self, _ctx: &UnitCtx, units: &[UnitCount]) { self.lock().expected = units.to_vec(); } + + fn refused_words(&self) -> (Option, Option) { + self.lock().refused.clone() + } + + fn attempting(&self, _ctx: &UnitCtx, dialect: u32, provider: &str) { + let (names, providers) = &self.facts.translations; + if let (Some(from), Some(to)) = (names.get(dialect as usize), providers.get(provider)) { + if from != to { + busbar_kernel::telemetry::translation(from, to); + } + } + } } impl Units for DoorSteps<'_> { @@ -588,6 +661,26 @@ impl Units for DoorSteps<'_> { // An unknown route seals nothing and is refused at admission, after its grant was judged // (1.5.5's order); the empty set is an answer at this step. let named = self.lock().named.clone(); + // THE PLANE SERVING THE `pools` MAP judges the route as named with the previous release's + // destination guard (`busbar_kernel::door::destination_guard`): the key's pool grant, its + // fallback pools' grants, then a rate card's coverage of the name, refused in 1.5.5's words. + if self.facts.plane.is_empty() { + if let Some(name) = named + .as_ref() + .and_then(|(_, n)| n.as_deref()) + .and_then(|n| std::str::from_utf8(n).ok()) + { + let view = DoorPoolView { + pools: self.pools, + key: self.key.as_deref(), + app: &self.app, + }; + if let Err(refusal) = busbar_kernel::door::destination_guard(&view, name) { + self.lock().refused.1 = Some(refusal.message()); + return SeatVerdict::refuse(token, Refusal::new(refusal.reason())); + } + } + } let routed = named.and_then(|(class, n)| self.pools.resolve(class, n.as_deref())); let sealed: Vec = routed .iter() @@ -635,6 +728,7 @@ impl Units for DoorSteps<'_> { Admission::Keyed => { if let Some(key) = self.key.clone() { if let Err(refusal) = self.charge(ctx, &key) { + self.lock().refused.0 = refusal.retry_after_secs(); return SeatVerdict::refuse(token, refusal); } } @@ -767,6 +861,7 @@ pub fn door_facts( .collect(), audit_kind: OpClassId::new(audit_kind), keeps, + translations: (Arc::from(Vec::new()), Arc::default()), } } @@ -859,6 +954,195 @@ pub fn compose_egress( }) } +// ── one configuration generation, as the door planes are sealed over it ───────────────────────── + +/// WHAT A DOOR PLANE IS SEALED OVER, for ONE configuration generation (spec Part 1 line 569: one +/// validated `{section: value}` object per `open`/`refresh`): the kernel-owned sections it reads +/// ([`kernel_sections`]), the providers its members are reached through ([`provider_routes`]) and +/// the model-serving pools' bounds ([`crate::root::model_egress::ModelPools`]). Projected from each +/// resolved configuration as it is built (`busbar_kernel::appbuild::set_config_projection`), and +/// read back when that generation is swapped in. +#[derive(Debug)] +pub struct DoorConfig { + /// The kernel-owned sections. + pub sections: BTreeMap<&'static str, serde_yaml::Value>, + /// The providers, by name. + pub providers: BTreeMap, + /// The model-serving pools' bounds. + pub model_pools: crate::root::model_egress::ModelPools, +} + +/// The [`DoorConfig`] of `cfg` (the root's `ConfigProjection`). +#[must_use] +pub fn door_config(cfg: &busbar_kernel::config::RootCfg) -> Arc { + Arc::new(DoorConfig { + sections: kernel_sections(cfg), + providers: provider_routes(&cfg.providers), + model_pools: crate::root::model_egress::ModelPools::of(cfg), + }) +} + +// ── the kernel-owned sections a door plane reads ──────────────────────────────────────────────── + +/// THE KERNEL-OWNED SECTIONS A DOOR PLANE MAY READ, as the configuration validated and resolved +/// them (spec Part 1 §4: "A plane's settings reach it as ONE validated JSON object `{section: +/// value}` per open/refresh, reserved keys and secrets stripped"; "A plane receives, at open, the +/// dialect fields of the providers it references and resolves model -> dialect itself"): `providers` +/// (catalog-merged, each provider's dialect fields: no `base_url`, no credential reference, no +/// health or token endpoint), `models`, `pools` (each pool's members and its fallback pool) and +/// `limits` (the output-cap default and the effort budgets). The previous release kept these in the +/// kernel; they are the root's to hand a door plane whose Statement names them. `pools` is present +/// whenever `models` is: a deployment with models and no pools routes to its models directly. +#[must_use] +pub fn kernel_sections( + cfg: &busbar_kernel::config::RootCfg, +) -> BTreeMap<&'static str, serde_yaml::Value> { + use serde_json::{json, Map, Value}; + fn opt(m: &mut Map, key: &str, v: Option) { + if let Some(v) = v { + m.insert(key.to_string(), v); + } + } + fn output_key(k: busbar_kernel::ir::lane_caps::MaxOutputKeyCfg) -> &'static str { + use busbar_kernel::ir::lane_caps::MaxOutputKeyCfg; + match k { + MaxOutputKeyCfg::MaxTokens => "max_tokens", + MaxOutputKeyCfg::MaxCompletionTokens => "max_completion_tokens", + } + } + let mut out = BTreeMap::new(); + let mut providers = Map::new(); + for (name, p) in &cfg.providers { + let mut m = Map::new(); + m.insert("protocol".into(), json!(p.protocol)); + opt(&mut m, "path", p.path.as_ref().map(|v| json!(v))); + opt(&mut m, "path_base", p.path_base.as_ref().map(|v| json!(v))); + opt( + &mut m, + "organization", + p.organization.as_ref().map(|v| json!(v)), + ); + opt(&mut m, "project", p.project.as_ref().map(|v| json!(v))); + m.insert("error_map".into(), json!(p.error_map)); + opt(&mut m, "auth", p.auth.map(style_word).map(|v| json!(v))); + opt( + &mut m, + "max_output_key", + p.max_output_key.map(output_key).map(|v| json!(v)), + ); + opt( + &mut m, + "anthropic_adaptive_thinking", + p.anthropic_adaptive_thinking.map(|v| json!(v)), + ); + opt( + &mut m, + "native_structured_output", + p.native_structured_output.map(|v| json!(v)), + ); + let rules: Vec = p + .model_capabilities + .iter() + .map(|r| { + let mut c = Map::new(); + c.insert("models".into(), json!(r.models)); + opt( + &mut c, + "max_output_key", + r.max_output_key.map(output_key).map(|v| json!(v)), + ); + opt( + &mut c, + "anthropic_adaptive_thinking", + r.anthropic_adaptive_thinking.map(|v| json!(v)), + ); + opt( + &mut c, + "native_structured_output", + r.native_structured_output.map(|v| json!(v)), + ); + opt(&mut c, "reasoning_none", r.reasoning_none.map(|v| json!(v))); + opt( + &mut c, + "thinking_always_on", + r.thinking_always_on.map(|v| json!(v)), + ); + Value::Object(c) + }) + .collect(); + if !rules.is_empty() { + m.insert("model_capabilities".into(), Value::Array(rules)); + } + providers.insert(name.clone(), Value::Object(m)); + } + let mut models = Map::new(); + for (name, model) in &cfg.models { + let mut m = Map::new(); + m.insert(MODEL_PROVIDER_KEY.into(), json!(model.provider)); + opt( + &mut m, + "upstream_model", + model.upstream_model.as_ref().map(|v| json!(v)), + ); + opt( + &mut m, + "default_max_tokens", + model.default_max_tokens.map(|v| json!(v)), + ); + opt(&mut m, "reasoning", model.reasoning.map(|v| json!(v))); + opt( + &mut m, + "prompt_caching", + model.prompt_caching.map(|v| json!(v)), + ); + models.insert(name.clone(), Value::Object(m)); + } + let mut pools = Map::new(); + for (name, pool) in &cfg.pools { + let members: Vec = pool + .members + .iter() + .map(|member| { + let mut m = Map::new(); + m.insert("model".into(), json!(member.model)); + opt(&mut m, "reasoning", member.reasoning.map(|v| json!(v))); + opt(&mut m, "context_max", member.context_max.map(|v| json!(v))); + Value::Object(m) + }) + .collect(); + let mut p = Map::new(); + p.insert(POOL_MEMBERS_KEY.into(), Value::Array(members)); + if let Some(busbar_kernel::config::pools::OnExhaustedCfg::FallbackPool(fallback)) = + &pool.on_exhausted + { + p.insert( + ON_EXHAUSTED_KEY.into(), + json!({ FALLBACK_POOL_KEY: fallback }), + ); + } + pools.insert(name.clone(), Value::Object(p)); + } + let limits = json!({ + "default_max_tokens": cfg.limits.default_max_tokens, + "reasoning_effort_budgets": { + "minimal": cfg.limits.reasoning_effort_budgets.minimal, + "low": cfg.limits.reasoning_effort_budgets.low, + "medium": cfg.limits.reasoning_effort_budgets.medium, + "high": cfg.limits.reasoning_effort_budgets.high, + }, + }); + let yaml = |v: Value| serde_yaml::to_value(v).unwrap_or(serde_yaml::Value::Null); + if !providers.is_empty() { + out.insert("providers", yaml(Value::Object(providers))); + } + if !models.is_empty() || !pools.is_empty() { + out.insert(RESERVED_MODELS_KEY, yaml(Value::Object(models))); + out.insert(RESERVED_POOLS_KEY, yaml(Value::Object(pools))); + } + out.insert("limits", yaml(limits)); + out +} + // ── the members' routes (THE DESIGN §6 steps 2-3) ────────────────────────────────────────────── /// ONE `providers:` ENTRY as a door plane's member reaches it (THE DESIGN §6 step 2; #50, #51): the @@ -1127,6 +1411,9 @@ pub struct DoorReach<'a> { pub conns: Arc, /// Whole seconds. pub stream_ceiling_secs: u64, + /// The model-serving pools the plane serving the `pools` map walks + /// ([`crate::root::model_egress`]); `None` where none is composed. + pub models: Option<&'a crate::root::model_egress::ModelServing>, } impl std::fmt::Debug for DoorReach<'_> { @@ -1173,6 +1460,8 @@ pub fn member_routes( name: String, provider: &'p ProviderRoute, style: String, + /// The dialect default style's parameters, when the style is that default. + params: &'static [u8], } let mut resolved = Vec::new(); for entry in pools.entries() { @@ -1197,10 +1486,17 @@ pub fn member_routes( served .dialect_auth .iter() - .find(|(d, _)| *d == at) - .map(|(_, style)| (*style).to_string()) + .find(|(d, _, _)| *d == at) + .map(|(_, style, params)| ((*style).to_string(), *params)) }); - let style = provider.style.clone().or(default).ok_or_else(|| { + // The provider's own `auth:` binds its style alone; the dialect's default carries its + // declared parameters (Q-L6-AUTHPARAMS). + let (style, params) = match (&provider.style, default) { + (Some(style), _) => (Some(style.clone()), &[][..]), + (None, Some((style, params))) => (Some(style), params), + (None, None) => (None, &[][..]), + }; + let style = style.ok_or_else(|| { format!( "member '{entry}': provider '{name}' states no `auth:` and its plane declares no \ default style for the dialect '{dialect}'" @@ -1211,6 +1507,7 @@ pub fn member_routes( name: name.to_string(), provider, style, + params, }); } let needs: Vec = served @@ -1256,6 +1553,8 @@ pub fn member_routes( r.entry, r.style ) })?; + let settings = sealed_settings(r.params, &r.provider.base_url, &settings) + .map_err(|e| format!("member '{}': {e}", r.entry))?; let handle = auth .open_outbound(&r.style, &credential, &settings) .map_err(|e| format!("provider '{}' {e}", r.name))?; @@ -1283,6 +1582,100 @@ pub fn member_routes( Ok(routes) } +/// THE SETTINGS A MEMBER'S BINDING IS OPENED WITH (ARCHITECT RULING 2026-10-03, Q-L6-AUTHPARAMS; +/// ruling 2026-09-28: the kernel resolves the dialect's declared parameters at seal into +/// `OpenOutboundIn::settings`): the dialect's `params` object, each value written +/// `{"host_label_after": [labels], "default": word, "unread": warning}` resolved from the provider's +/// `base_url` host ([`host_label`]; `default` when no label reads, its `unread` warning then handed +/// on as `_unread` for the binding to log with each use), then the provider's own settings +/// over it. +/// +/// # Errors +/// +/// Parameters that are not a JSON object, or a host directive that is malformed. +pub fn sealed_settings( + params: &[u8], + base_url: &str, + provider: &serde_json::Value, +) -> Result { + if params.is_empty() { + return Ok(provider.clone()); + } + let serde_json::Value::Object(mut sealed) = serde_json::from_slice(params) + .map_err(|e| format!("its dialect's auth parameters are not JSON: {e}"))? + else { + return Err("its dialect's auth parameters are not a JSON object".to_string()); + }; + let mut unread: Vec<(String, serde_json::Value)> = Vec::new(); + for (key, value) in &mut sealed { + let Some(directive) = value.get("host_label_after") else { + continue; + }; + let labels: Vec<&str> = directive + .as_array() + .map(|a| a.iter().filter_map(serde_json::Value::as_str).collect()) + .unwrap_or_default(); + let default = value + .get("default") + .and_then(serde_json::Value::as_str) + .ok_or_else(|| format!("its dialect's auth parameter `{key}` names no default"))? + .to_string(); + let resolved = match host_label(base_url_host(base_url), &labels) { + Some(label) => label.to_string(), + None => { + // The operator warning the dialect states for a host its rule cannot read, handed + // to the binding as `_unread`: the plugin logs it where 1.5.5 did, with each + // use of the default. + if let Some(words) = value.get("unread").filter(|w| w.is_string()) { + unread.push((format!("{key}_unread"), words.clone())); + } + default + } + }; + *value = serde_json::Value::String(resolved); + } + sealed.extend(unread); + if let serde_json::Value::Object(own) = provider { + for (key, value) in own { + sealed.insert(key.clone(), value.clone()); + } + } + Ok(serde_json::Value::Object(sealed)) +} + +/// The host of `base_url`: after its scheme, before its port, path, query or fragment. +fn base_url_host(base_url: &str) -> &str { + let rest = base_url.split_once("://").map_or(base_url, |(_, r)| r); + let authority = rest.split(['/', '?', '#']).next().unwrap_or(""); + let authority = authority.rsplit_once('@').map_or(authority, |(_, a)| a); + authority.split(':').next().unwrap_or("") +} + +/// The dotted label of `host` right after the first of `after`, when it reads as a dashed name +/// ending in a number (three parts or more, every part before the last alphabetic, the last all +/// digits: the shape the dialect's own rule accepted, 1.5.5 `derive_sigv4_region`). +fn host_label<'h>(host: &'h str, after: &[&str]) -> Option<&'h str> { + let shaped = |label: &str| { + let parts: Vec<&str> = label.split('-').collect(); + let Some((last, leading)) = parts.split_last() else { + return false; + }; + parts.len() >= 3 + && parts.iter().all(|p| !p.is_empty()) + && last.bytes().all(|b| b.is_ascii_digit()) + && leading + .iter() + .all(|p| p.bytes().all(|b| b.is_ascii_alphabetic())) + }; + let labels: Vec<&str> = host.split('.').collect(); + labels.iter().enumerate().find_map(|(i, label)| { + if !after.contains(label) { + return None; + } + labels.get(i + 1).copied().filter(|next| shaped(next)) + }) +} + #[cfg(test)] #[path = "tests/door_steps.rs"] mod tests; diff --git a/crates/busbar/src/root/mod.rs b/crates/busbar/src/root/mod.rs index 8544823f90..460e6110ee 100644 --- a/crates/busbar/src/root/mod.rs +++ b/crates/busbar/src/root/mod.rs @@ -92,6 +92,7 @@ pub mod legacy; pub mod linked; pub(crate) mod loader; pub mod migration; +pub mod model_egress; pub mod observe; pub mod otlp; #[cfg(feature = "plane-decisions")] diff --git a/crates/busbar/src/root/model_egress.rs b/crates/busbar/src/root/model_egress.rs new file mode 100644 index 0000000000..dec1b62ceb --- /dev/null +++ b/crates/busbar/src/root/model_egress.rs @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE EGRESS OF THE PLANE SERVING THE `pools` MAP, sealed with the previous release's pool +//! semantics (BUSBAR-1.6.0.md Part 3 §12 "The route pump": the route step is the kernel's existing +//! egress walk; Part 1: customer-visible behaviour is 1.5.5's). +//! +//! The generic door egress ([`crate::root::door_steps::compose_egress`]) seals every member at +//! weight 1 under the default breaker ladder on a breaker of its own. The `pools:` grammar is the +//! kernel's and says more, and 1.5.5 honoured all of it: each member's weight and attempt timeout, +//! each model's concurrency cap and context window, each pool's failover budget, hop cap and +//! exclusions, its exhaustion terminal and its breaker ladder. And the breaker cells a member trips +//! are the kernel's own lane cells (`AppHandle::store`), keyed by the model's lane index, so the +//! admission the kernel reads, the `busbar_lane_*` gauges a scrape renders and the admin health views +//! all read the trip a door-served attempt recorded, as they read the previous release's. Every +//! outcome is recorded through the lane store's own record calls, the ones the previous release's +//! attempt made (success, transient, rate limit, client fault, hard-down), so the lane's lifetime +//! counters `/stats` renders count it too. A route that names a model directly walks it alone under +//! the model's name (its metric label) on the lane-default cell (`""`), as 1.5.5 attributed a +//! model-routed fault. + +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::sync::Arc; + +use busbar_contract::caps::{Pass, Route}; +use busbar_contract::dest::DestinationId; +use busbar_kernel_egress::ports::{ + Admit, Breaker, Classified, Outcome, Unavailable, UpstreamStatus, +}; +use busbar_kernel_egress::{Failover, Member, OnExhausted, Pool}; + +use crate::root::adapters::{BreakerAdapter, BreakerPolicy}; + +/// The kernel's App, as the live configuration answers it (a config apply rebuilds it, its lane +/// store and its telemetry bank with it). +pub type AppSource = Arc Arc + Send + Sync>; + +/// One configured pool: its members (model, weight, attempt timeout), failover bounds and +/// exhaustion terminal. +#[derive(Debug, Clone)] +struct ModelPool { + members: Vec<(String, u32, Option)>, + failover: Failover, + on_exhausted: OnExhausted, + /// Its members are reached with the caller's own credential (the pool's + /// `upstream_credentials:`, else the all-pools default, is `passthrough`). + passthrough: bool, +} + +/// One model's own bounds: its concurrency cap, its attempt timeout and its context window. +#[derive(Debug, Clone, Default)] +struct ModelLane { + max_concurrent: Option, + attempt_timeout_ms: Option, + context_max: Option, +} + +/// THE MODEL-SERVING POOLS as the configuration resolved them, captured before the configuration +/// is consumed: every pool and model bound the walk honours, and the breaker ladder of each pool. +#[derive(Debug, Clone, Default)] +pub struct ModelPools { + pools: BTreeMap, + lanes: BTreeMap, + breaker: BreakerPolicy, + /// The all-pools `upstream_credentials:` default is `passthrough`: a route naming a model + /// directly reaches it with the caller's own credential. + passthrough: bool, + /// Each pool's ladder as the lane store's record calls take it (the previous release's + /// `resolve_breaker_cfg`: the pool's own, else the default); `""` = the lane-default cell's. + records: HashMap, +} + +impl ModelPools { + /// The pools and models `cfg` resolved. + #[must_use] + pub fn of(cfg: &busbar_kernel::config::RootCfg) -> Self { + use busbar_kernel::config::pools::OnExhaustedCfg; + let mut lanes: BTreeMap = cfg + .models + .iter() + .map(|(name, m)| { + ( + name.clone(), + ModelLane { + max_concurrent: m.max_concurrent, + attempt_timeout_ms: m.attempt_timeout_ms, + context_max: None, + }, + ) + }) + .collect(); + let mut pools = BTreeMap::new(); + for (name, pool) in &cfg.pools { + for member in &pool.members { + if let (Some(lane), Some(c)) = (lanes.get_mut(&member.model), member.context_max) { + lane.context_max.get_or_insert(c); + } + } + let failover = pool.failover.as_ref().map_or( + Failover { + timeout_secs: busbar_kernel::config::DEFAULT_FAILOVER_DEADLINE_SECS, + max_hops: busbar_kernel::config::DEFAULT_FAILOVER_CAP, + exclusions: Vec::new(), + }, + |f| Failover { + timeout_secs: f.timeout_secs, + max_hops: f.max_hops, + exclusions: f.exclusions.clone().unwrap_or_default(), + }, + ); + let on_exhausted = match &pool.on_exhausted { + None | Some(OnExhaustedCfg::Reject) => OnExhausted::Status503, + Some(OnExhaustedCfg::LeastBad) => OnExhausted::LeastBad, + Some(OnExhaustedCfg::FallbackPool(p)) => OnExhausted::FallbackPool(p.clone()), + Some(OnExhaustedCfg::Queue { max_ms }) => OnExhausted::Queue { max_ms: *max_ms }, + }; + pools.insert( + name.clone(), + ModelPool { + members: pool + .members + .iter() + .map(|m| (m.model.clone(), m.weight, m.attempt_timeout_ms)) + .collect(), + failover, + on_exhausted, + passthrough: pool + .upstream_credentials + .unwrap_or(cfg.upstream_credentials) + == busbar_contract::config::UpstreamCreds::Passthrough, + }, + ); + } + let mut records: HashMap = cfg + .pools + .iter() + .map(|(name, pool)| { + ( + name.clone(), + pool.breaker + .as_ref() + .map(busbar_kernel::store::breaker_cfg_to_runtime) + .unwrap_or_default(), + ) + }) + .collect(); + records.insert(String::new(), busbar_kernel::store::BreakerCfg::default()); + ModelPools { + pools, + lanes, + breaker: BreakerPolicy::from_pools(&cfg.pools), + passthrough: cfg.upstream_credentials + == busbar_contract::config::UpstreamCreds::Passthrough, + records, + } + } +} + +/// WHAT THE MODEL-SERVING EGRESS IS SEALED OVER beside the plane's member routes: the pools, the +/// lane index of each model in the generation's lane table (the kernel's breaker cells are keyed by +/// it), and the breaker unit those cells live in, as the live configuration answers it. +pub struct ModelServing { + /// The pools and models. + pub pools: ModelPools, + /// Each model's lane index. + pub lanes: HashMap, + /// The kernel's App, read on every call: its lane store and its telemetry bank. + pub app: AppSource, +} + +impl std::fmt::Debug for ModelServing { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ModelServing") + .field("pools", &self.pools) + .field("lanes", &self.lanes) + .finish_non_exhaustive() + } +} + +/// The kernel's breaker port over its lane store: a model-named direct route is mapped onto the +/// lane-default cell, and every outcome is recorded through the store's own record calls. +struct DirectCells { + inner: BreakerAdapter, + app: AppSource, + records: HashMap, + direct: HashSet, +} + +/// The lane a destination of this egress names (its destination is the model's lane index). +fn lane_of(destination: DestinationId) -> usize { + usize::try_from(destination.get()).unwrap_or(usize::MAX) +} + +impl DirectCells { + fn cell<'p>(&self, pool: &'p str) -> &'p str { + if self.direct.contains(pool) { + "" + } else { + pool + } + } +} + +impl Breaker for DirectCells { + fn try_admit( + &self, + pool: &str, + destination: DestinationId, + now: u64, + ) -> Result { + self.inner.try_admit(self.cell(pool), destination, now) + } + + fn ready(&self, pool: &str, destination: DestinationId, now: u64, token: &Pass) -> bool { + self.inner.ready(self.cell(pool), destination, now, token) + } + + fn admissible(&self, destination: DestinationId) -> bool { + self.inner.admissible(destination) + } + + fn cooldown_remaining( + &self, + pool: &str, + destination: DestinationId, + now: u64, + token: &Pass, + ) -> u64 { + self.inner + .cooldown_remaining(self.cell(pool), destination, now, token) + } + + fn classify(&self, destination: DestinationId, status: UpstreamStatus) -> Classified { + self.inner.classify(destination, status) + } + + fn observe( + &self, + pool: &str, + destination: DestinationId, + outcome: Outcome, + _now: u64, + _token: &Pass, + ) -> bool { + let cell = self.cell(pool); + let Some(cfg) = self.records.get(cell) else { + return false; + }; + let (store, lane) = (Arc::clone(&(self.app)().store), lane_of(destination)); + match outcome { + Outcome::Success => { + store.record_success_in(cell, lane); + false + } + Outcome::Transient { retry_after } => { + store.record_transient_in(cell, lane, "transient", cfg, retry_after) + } + Outcome::HardDown => store.record_hard_down_all_cells(lane, "hard_down"), + Outcome::RecordNothing => false, + } + } + + fn judge( + &self, + pool: &str, + destination: DestinationId, + status: UpstreamStatus, + now: u64, + token: &Pass, + ) -> (Classified, bool) { + let classified = self.classify(destination, status); + let code = status + .code + .filter(|c| c.namespace == busbar_contract::transport::registry::status_ns::HTTP) + .map(|c| c.code); + let cell = self.cell(pool); + let lane = lane_of(destination); + let tripped = match (classified.outcome, code) { + // The previous release's rate-limit record: its own ladder step, the far end's wait + // honoured. + (Outcome::Transient { retry_after }, Some(429)) => match self.records.get(cell) { + Some(cfg) => { + (self.app)() + .store + .record_rate_limit_in(cell, lane, now, cfg, retry_after) + } + None => false, + }, + // The caller's own fault: counted on the lane, its breaker untouched. + (Outcome::RecordNothing, Some(400..=499)) => { + (self.app)().store.record_client_fault(lane); + false + } + (outcome, _) => self.observe(pool, destination, outcome, now, token), + }; + (classified, tripped) + } + + fn suppressing(&self, destination: DestinationId, now: u64) -> bool { + (self.app)() + .store + .lane_needs_probe(lane_of(destination), now) + } + + /// A health probe's answer, recorded as 1.5.5's prober recorded it (`engine/health.rs`): a + /// success recovers the lane in every cell, then joins every cell's window; a transient failure + /// joins every cell's window under its own pool's ladder; a hard-down parks the lane in every + /// cell; a client fault records nothing. + fn probed(&self, destination: DestinationId, outcome: Outcome, now: u64, _token: &Pass) { + let (app, lane) = ((self.app)(), lane_of(destination)); + let store = &app.store; + match outcome { + Outcome::Success => { + if store.lane_needs_probe(lane, now) { + store.recover_lane(lane); + } + store.record_probe_success_all_cells(lane); + } + Outcome::Transient { retry_after } => { + let resolve = |pool: &str| self.records.get(pool).cloned().unwrap_or_default(); + store.record_probe_failure_all_cells(lane, "health-probe", &resolve, retry_after); + } + Outcome::HardDown => { + let _ = + store.record_hard_down_all_cells(lane, "health-probe hard-down (auth/billing)"); + } + Outcome::RecordNothing => {} + } + } + + fn release_probe(&self, pool: &str, destination: DestinationId, epoch: u64, now: u64) { + self.inner + .release_probe(self.cell(pool), destination, epoch, now); + } + + fn spend_budget(&self, destination: DestinationId) -> bool { + self.inner.spend_budget(destination) + } + + fn refund_budget(&self, destination: DestinationId) { + self.inner.refund_budget(destination); + } +} + +/// The walk's counters on the kernel's telemetry bank, as the previous release's attempt counted +/// them: each member by its lane index, under the pool label the walk names (a pool, or the model a +/// direct route names); the wait-terminal depth as the root's walk counters keep it. +struct ModelTelemetry { + app: AppSource, + queued: crate::root::egress_ports::WalkTelemetry, +} + +impl busbar_kernel_egress::ports::Telemetry for ModelTelemetry { + fn upstream_attempt(&self, pool: &str, destination: DestinationId) { + busbar_kernel::telemetry::upstream_attempt(&(self.app)(), pool, lane_of(destination)); + } + + fn upstream_failure(&self, pool: &str, destination: DestinationId, disposition: &'static str) { + busbar_kernel::telemetry::upstream_failure( + &(self.app)(), + pool, + lane_of(destination), + disposition, + ); + } + + fn failover(&self, pool: &str, reason: &'static str) { + busbar_kernel::telemetry::failover(&(self.app)(), pool, reason); + } + + fn breaker_trip(&self, pool: &str, destination: DestinationId) { + busbar_kernel::telemetry::breaker_trip(&(self.app)(), pool, lane_of(destination)); + } + + fn upstream_latency(&self, destination: DestinationId, ms: f64) { + // The lane's latency signal (the admin pool listing's `latency_ms`, the `fastest` order's + // input), as 1.5.5's served attempt recorded it. + (self.app)() + .store + .record_latency_in("", lane_of(destination), ms); + } + + fn queued(&self, pool: &str, delta: i64) { + busbar_kernel_egress::ports::Telemetry::queued(&self.queued, pool, delta); + // The scrape's pool-queue gauge reads the depth the kernel's tables report. + busbar_kernel::plane_host::set_pool_queued_depth( + pool, + u64::try_from(self.queued.depth(pool)).unwrap_or(0), + ); + } +} + +/// THE EGRESS OF THE PLANE SERVING THE `pools` MAP (module doc): one member per sealed entry (its +/// destination the model's lane index, its name the model's), a pool per configured pool with its +/// own bounds and terminal, a pool per model a route may name directly, the kernel's breaker cells +/// under each pool's ladder, each model's concurrency cap, the node's clock and counters, and +/// `journal`. +/// +/// # Errors +/// +/// A configured pool naming a model with no sealed route or no lane: the load is refused, naming +/// both. +#[allow(clippy::too_many_arguments)] +pub fn compose( + facts: &crate::root::door_steps::DoorFacts, + serving: &ModelServing, + caller: busbar_contract::conn::InstanceId, + conns: Arc, + routes: &BTreeMap, + journal: Arc, + stream_ceiling_secs: u64, +) -> Result { + let plan = &serving.pools; + let mut sealed = HashMap::new(); + let mut names = Vec::new(); + let mut limits = Vec::new(); + let mut member_of = |model: &str, + weight: u32, + attempt: Option, + passthrough: bool| + -> Result { + let lane = *serving + .lanes + .get(model) + .ok_or_else(|| format!("model '{model}' has no lane in this generation"))?; + let destination = DestinationId::new(lane as u64); + let route = routes + .get(model) + .ok_or_else(|| format!("model '{model}' has no sealed route"))?; + let own = plan.lanes.get(model).cloned().unwrap_or_default(); + if let std::collections::hash_map::Entry::Vacant(slot) = sealed.entry(destination) { + let mut route = route.clone(); + route.keep = facts + .keeps + .get(route.need.0 as usize) + .cloned() + .unwrap_or_default(); + slot.insert(route); + names.push((destination, model.to_string())); + if let Some(n) = own.max_concurrent { + limits.push((destination, n)); + } + } + let mut member = Member::new(destination, model, weight); + member.attempt_timeout_ms = attempt.or(own.attempt_timeout_ms); + member.context_max = own.context_max.map(|c| c as u64); + member.passthrough = passthrough; + Ok(member) + }; + let mut built: HashMap = HashMap::new(); + for (name, pool) in &plan.pools { + let members = pool + .members + .iter() + .map(|(model, weight, attempt)| { + member_of(model, *weight, *attempt, pool.passthrough) + .map_err(|e| format!("pool '{name}': {e}")) + }) + .collect::, _>>()?; + let mut p = Pool::new(name.clone(), members); + p.failover = pool.failover.clone(); + p.on_exhausted = pool.on_exhausted.clone(); + built.insert(name.clone(), p); + } + let mut direct = HashSet::new(); + for model in routes.keys() { + if built.contains_key(model) || !serving.lanes.contains_key(model) { + continue; + } + let member = member_of(model, 1, None, plan.passthrough)?; + let mut p = Pool::new(model.clone(), vec![member]); + p.failover = Failover { + timeout_secs: busbar_kernel::config::DEFAULT_FAILOVER_DEADLINE_SECS, + max_hops: busbar_kernel::config::DEFAULT_FAILOVER_CAP, + exclusions: Vec::new(), + }; + built.insert(model.clone(), p); + direct.insert(model.clone()); + } + let app = Arc::clone(&serving.app); + let breaker = DirectCells { + inner: BreakerAdapter::over( + Arc::new(move || app().store.breaker_unit()), + plan.breaker.clone(), + ), + app: Arc::clone(&serving.app), + records: plan.records.clone(), + direct, + }; + Ok(busbar_kernel::plane_driver::Egress { + caller, + conns, + breaker: Arc::new(breaker), + capacity: Arc::new(crate::root::egress_ports::MemberPermits::new(limits)), + clock: Arc::new(crate::root::egress_ports::NodeClock::new()), + journal, + telemetry: Arc::new(ModelTelemetry { + app: Arc::clone(&serving.app), + queued: crate::root::egress_ports::WalkTelemetry::new(names), + }), + floor: busbar_kernel_egress::WeightedFloor::new(), + pools: built, + routes: sealed, + stream_ceiling_secs, + error_body_max: busbar_kernel::plane_driver::DEFAULT_ERROR_BODY_MAX, + }) +} diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index 2c4c2d0310..e33a81af9f 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -332,17 +332,22 @@ pub struct ServedPlane { pub snapshot: OwnedSnapshot, /// The `audit_kind` its tail states: what its units are audited under. pub audit_kind: &'static str, - /// Its key, scope kind, billable classes and fee units, read off its tail. - pub facts: DoorFacts, - /// The pools and entries its section states (ARCHITECT Q-SW6). - pub pools: DoorPools, + /// What its units are served by for the CURRENT configuration generation: a config apply + /// refreshes the plane onto the new generation and swaps this ([`Served::refresh`]); a unit + /// holds the generation it started on. + pub live: arc_swap::ArcSwap, + /// The bound plane, for its `refresh`. + pub plugin: DoorPlane, + /// The facts its door states, read once at bind. + pub served_facts: crate::root::loader::dispatch::kinds::plane::ServedFacts, + /// The generation its instance last opened or refreshed onto. + pub generation: std::sync::atomic::AtomicU64, /// Its money steps: the same seam its driver reports units, cancel bills and abandoned ends to. pub money: Arc, - /// Its egress, sealed for the generation: the kernel's walk over the connector. `None` = none - /// composed: every walk of its units is exhausted at once (nothing is dialled). - pub egress: Option>, /// The kernel's host services it was admitted to: its units' records are written there. pub kernel: Arc, + /// Its tail's dialects, in order: what a unit's dialect index names. + pub dialects: Vec<&'static str>, } impl std::fmt::Debug for ServedPlane { @@ -350,11 +355,25 @@ impl std::fmt::Debug for ServedPlane { f.debug_struct("ServedPlane") .field("instance", &self.instance) .field("snapshot", &self.snapshot) - .field("facts", &self.facts) + .field("facts", &self.live.load().facts) .finish_non_exhaustive() } } +/// WHAT A DOOR PLANE'S UNITS ARE SERVED BY for ONE configuration generation: its key, scope kind, +/// billable classes and fee units read off its tail (with the generation's provider dialects), the +/// pools and entries its section states (ARCHITECT Q-SW6) and its egress, sealed for the +/// generation (the kernel's walk over the connector; `None` = none composed: every walk of its +/// units is exhausted at once and nothing is dialled). +pub struct PlaneLive { + /// The tail's facts. + pub facts: DoorFacts, + /// The section's pools. + pub pools: DoorPools, + /// The sealed egress. + pub egress: Option>, +} + /// EVERY DOOR PLANE THIS PROCESS SERVES, composed once after the first app is built. #[derive(Debug, Default)] pub struct Served { @@ -386,6 +405,7 @@ impl Served { /// A configured plane that will not compose ([`compose_planes`]), or, in a build that links no /// node to post a unit's money on, any configured door plane at all, which the boot refuses rather /// than leaving its claims unserved. +#[allow(clippy::too_many_arguments)] pub fn compose_served( gov: Option>, doors: &[(String, DoorPlane)], @@ -393,6 +413,7 @@ pub fn compose_served( late: &LateServices, sections: &BTreeMap<&'static str, serde_yaml::Value>, reach: &crate::root::door_steps::DoorReach<'_>, + host: Option, ) -> Result { let Some(gov) = gov else { if !doors.is_empty() { @@ -405,6 +426,10 @@ pub fn compose_served( let post = Arc::new(crate::root::plane_node::NodeEndPost::new( crate::root::plane_node::node(), )); + let stage = host.map(|host| HookStage { + host, + gov: Arc::clone(&gov), + }); let site = Arc::clone(&post); let money = move || { Arc::new(PlaneMoney::new( @@ -416,13 +441,22 @@ 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( + doors, + dispatcher, + late, + sections, + &money, + Some(&egress), + stage.as_ref(), + )?; served.post = Some(post); Ok(served) } #[cfg(not(linked_axis_node))] { - let _ = (gov, dispatcher, late, reach); + let _ = (gov, dispatcher, late, reach, host); match doors .iter() .find(|(_, plugin)| sections.contains_key(plugin.served().section)) @@ -436,6 +470,31 @@ pub fn compose_served( } } +/// THE KERNEL'S HOOK STAGE as a door plane's units bind it: the current generation's engine host +/// (its hook registry, its group tree, its card), read per unit so a config apply reaches the next +/// unit, and the governance state a verified principal's key is read from. +pub struct HookStage { + /// The current generation's engine host. + pub host: busbar_kernel::plane_driver::HostSource, + /// The governance state. + pub gov: Arc, +} + +impl HookStage { + /// The binder for a plane whose tail states `dialects`. + fn binder(&self, dialects: &[&str]) -> Arc { + let gov = Arc::clone(&self.gov); + Arc::new(busbar_kernel::plane_driver::HostHooks { + host: Arc::clone(&self.host), + caller: Arc::new(busbar_kernel::plane_driver::EngineCaller { + host: Arc::clone(&self.host), + keys: Arc::new(move |principal: &str| gov.lookup_by_sub(principal)), + }), + dialects: dialects.iter().map(|d| (*d).to_string()).collect(), + }) + } +} + /// WHAT A DOOR PLANE'S EGRESS IS SEALED OVER: how its members are reached ([`DoorReach`]) and the /// journal its walk's dispatch records are written to (the node's end post, ARCHITECT P3 (c)). pub struct DoorEgress<'a> { @@ -460,6 +519,7 @@ pub struct DoorEgress<'a> { /// /// A plane that will not open, publish a snapshot, be admitted, publish its admin routes or seal /// its members' routes, named: the boot refuses it, as it refuses a plane that will not bind. +#[allow(clippy::too_many_arguments)] pub fn compose_planes( doors: &[(String, DoorPlane)], dispatcher: &Arc, @@ -467,6 +527,7 @@ pub fn compose_planes( sections: &BTreeMap<&'static str, serde_yaml::Value>, money: &dyn Fn() -> Arc, egress: Option<&DoorEgress<'_>>, + hooks: Option<&HookStage>, ) -> Result { let mut served = Served::default(); if doors.is_empty() { @@ -485,7 +546,12 @@ pub fn compose_planes( ); continue; }; - let snapshot = open(plugin, section).map_err(|e| format!("{instance}: {e}"))?; + let settings = settings_of(&served_facts, section, sections); + // A MODEL-SERVING PLANE in the previous release's layout reads the top-level `models:` beside + // its `pools:`: its walk is the uniform model-serving section over both (#49). + let walked = walked_section(&served_facts, section, sections); + let section = &walked; + let snapshot = open(plugin, &settings).map_err(|e| format!("{instance}: {e}"))?; let calls = Arc::new(PlaneInstance::new( plugin.clone(), Arc::clone(dispatcher), @@ -517,6 +583,15 @@ pub fn compose_planes( ) .map_err(|e| format!("{instance}: {e}"))? .with_records(Arc::clone(&kernel), caller); + // THE HOOK STAGE (ARCHITECT K5; spec Part 3 §12 "Hooks": the hook stages run at the head of + // the route leg, in the hook order 1.5.5 used for that plane): the kernel's hooks bind to + // the units of the plane that serves the `pools` map, the one 1.5.5 ran them on. + let driver = match hooks { + Some(stage) if served_facts.section == busbar_contract::section::RESERVED_POOLS_KEY => { + driver.with_hooks(stage.binder(&served_facts.dialects)) + } + _ => driver, + }; let routes = snapshot .admin_routes .iter() @@ -556,65 +631,230 @@ pub fn compose_planes( )); } } - let facts = door_facts( - plugin.name(), + let live = seal_live( + instance, + plugin, + &served_facts, &declared.scope_kinds, - &served_facts.billable_classes, - &served_facts.fee_units, - served_facts.audit_kind, - served_facts - .keeps - .iter() - .map(|k| busbar_kernel::plane_driver::ResponseKeep { - mode: k.mode, - kept: k.kept.iter().map(|n| (*n).to_string()).collect(), - denied: k.denied.iter().map(|n| (*n).to_string()).collect(), - }) - .collect(), - ); - let pools = DoorPools::of(section); - // 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, - &pools, - &served_facts, - egress.reach, - ) - .map_err(|e| format!("{instance}: {e}"))?; - Some(Arc::new( - crate::root::door_steps::compose_egress( - &facts, - &pools, - plugin.instance(), - Arc::clone(&egress.reach.conns), - &routes, - Arc::clone(&egress.journal), - egress.reach.stream_ceiling_secs, - ) - .map_err(|e| format!("{instance}: {e}"))?, - )) - } - None => None, - }; + section, + egress, + )?; served.planes.push(ServedPlane { instance: instance.clone(), driver: Arc::new(driver), snapshot, audit_kind: served_facts.audit_kind, - facts, - pools, + live: arc_swap::ArcSwap::from_pointee(live), + plugin: plugin.clone(), + served_facts: served_facts.clone(), + generation: std::sync::atomic::AtomicU64::new(1), money: plane_money, - egress, kernel: Arc::clone(&kernel), + dialects: served_facts.dialects.clone(), }); } Ok(served) } +/// THE GENERATION A DOOR PLANE'S UNITS ARE SERVED BY ([`PlaneLive`]), sealed over its walked +/// `section`: the tail's facts (the flat card's empty plane key for the plane serving the `pools` +/// map, which owns 1.5.5's `rate_card:`; each provider's dialect, for the translation counter), the +/// section's pools and, with `egress`, each member's route resolved and its credential bound by the +/// auth plugin serving its style over the connector its needs were declared on (THE DESIGN §6 steps +/// 2-3), the plane serving the `pools` map walking its pools with the previous release's semantics +/// on the kernel's own breaker cells ([`crate::root::model_egress`]). +/// +/// # Errors +/// +/// A member whose route or credential binding cannot be resolved, named. +fn seal_live( + instance: &str, + plugin: &DoorPlane, + served_facts: &crate::root::loader::dispatch::kinds::plane::ServedFacts, + scope_kinds: &[&str], + section: &serde_yaml::Value, + egress: Option<&DoorEgress<'_>>, +) -> Result { + let models_plane = served_facts.section == busbar_contract::section::RESERVED_POOLS_KEY; + let card_plane = if models_plane { "" } else { plugin.name() }; + let mut facts = door_facts( + card_plane, + scope_kinds, + &served_facts.billable_classes, + &served_facts.fee_units, + served_facts.audit_kind, + served_facts + .keeps + .iter() + .map(|k| busbar_kernel::plane_driver::ResponseKeep { + mode: k.mode, + kept: k.kept.iter().map(|n| (*n).to_string()).collect(), + denied: k.denied.iter().map(|n| (*n).to_string()).collect(), + }) + .collect(), + ); + if let Some(egress) = egress { + facts.translations = ( + served_facts + .dialects + .iter() + .map(|d| (*d).to_string()) + .collect(), + Arc::new( + egress + .reach + .providers + .iter() + .map(|(name, p)| (name.clone(), p.protocol.clone())) + .collect(), + ), + ); + } + let pools = DoorPools::of(section); + let egress = match egress { + Some(egress) => { + let routes = + crate::root::door_steps::member_routes(section, &pools, served_facts, egress.reach) + .map_err(|e| format!("{instance}: {e}"))?; + let sealed = match egress.reach.models { + Some(models) if models_plane => crate::root::model_egress::compose( + &facts, + models, + plugin.instance(), + Arc::clone(&egress.reach.conns), + &routes, + Arc::clone(&egress.journal), + egress.reach.stream_ceiling_secs, + ), + _ => crate::root::door_steps::compose_egress( + &facts, + &pools, + plugin.instance(), + Arc::clone(&egress.reach.conns), + &routes, + Arc::clone(&egress.journal), + egress.reach.stream_ceiling_secs, + ), + }; + Some(Arc::new(sealed.map_err(|e| format!("{instance}: {e}"))?)) + } + None => None, + }; + Ok(PlaneLive { + facts, + pools, + egress, + }) +} + +impl std::fmt::Debug for PlaneLive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PlaneLive") + .field("facts", &self.facts) + .field("pools", &self.pools) + .field("egress", &self.egress.is_some()) + .finish() + } +} + +impl Served { + /// A CONFIG APPLY, as the door planes take it (spec Part 1 line 569: one validated object per + /// `refresh`): each served plane that declares the kernel-owned `pools` map is refreshed onto the + /// next generation with the new configuration's sections, its previous generation retired (a + /// unit that arrived on it keeps its own hold), and the generation its units are served by is + /// sealed anew over the new providers and pool bounds and swapped in; a unit already running + /// keeps the generation it started on, as 1.5.5's request kept the snapshot it arrived on. A + /// plane that will not refresh or seal keeps serving its previous generation, and says why. + pub fn refresh( + &self, + sections: &BTreeMap<&'static str, serde_yaml::Value>, + egress: &DoorEgress<'_>, + ) { + use std::sync::atomic::Ordering; + for p in &self.planes { + let facts = &p.served_facts; + if facts.section != busbar_contract::section::RESERVED_POOLS_KEY { + continue; + } + let Some(section) = sections.get(facts.section) else { + continue; + }; + let settings = settings_of(facts, section, sections); + let walked = walked_section(facts, section, sections); + let prior = p.generation.load(Ordering::Acquire); + let next = prior.saturating_add(1); + if let Err(e) = refresh(&p.plugin, &settings, next) { + tracing::error!(instance = %p.instance, error = %e, "door plane did not refresh onto the new configuration; it keeps serving the previous one"); + continue; + } + retire(&p.plugin, prior); + p.generation.store(next, Ordering::Release); + let scope_kinds = p.plugin.declared().scope_kinds; + match seal_live( + &p.instance, + &p.plugin, + facts, + &scope_kinds, + &walked, + Some(egress), + ) { + Ok(live) => p.live.store(Arc::new(live)), + Err(e) => { + tracing::error!(instance = %p.instance, error = %e, "door plane's new configuration did not seal; it keeps serving the previous one"); + } + } + } + } +} + +/// THE SETTINGS A DOOR PLANE OPENS WITH (spec Part 1 §4: one validated JSON object `{section: +/// value}`): a plane that declares the kernel-owned `pools` map (the previous release's top-level +/// model-serving layout) is handed it and each section it reads beside it (`SECTION_CONSUMED`) +/// that this deployment writes, keyed; a plane that declares a section of its own is handed that +/// section, as its door reads it. +fn settings_of( + facts: &crate::root::loader::dispatch::kinds::plane::ServedFacts, + section: &serde_yaml::Value, + sections: &BTreeMap<&'static str, serde_yaml::Value>, +) -> serde_yaml::Value { + if facts.section != busbar_contract::section::RESERVED_POOLS_KEY { + return section.clone(); + } + let mut keyed = serde_yaml::Mapping::new(); + keyed.insert(facts.section.into(), section.clone()); + for name in &facts.consumed { + if let Some(value) = sections.get(name) { + keyed.insert((*name).into(), value.clone()); + } + } + serde_yaml::Value::Mapping(keyed) +} + +/// THE SECTION A DOOR PLANE'S WALK IS SEALED OVER: its declaring section, or, for a plane that +/// declares a `pools` map and reads the `models` map beside it (the previous release's top-level +/// layout), the uniform model-serving section over both (`{models, pools}`, #49). +fn walked_section( + facts: &crate::root::loader::dispatch::kinds::plane::ServedFacts, + section: &serde_yaml::Value, + sections: &BTreeMap<&'static str, serde_yaml::Value>, +) -> serde_yaml::Value { + use busbar_contract::section::{RESERVED_MODELS_KEY, RESERVED_POOLS_KEY}; + let models = facts + .consumed + .contains(&RESERVED_MODELS_KEY) + .then(|| sections.get(RESERVED_MODELS_KEY)) + .flatten(); + match models { + Some(models) if facts.section == RESERVED_POOLS_KEY => { + let mut uniform = serde_yaml::Mapping::new(); + uniform.insert(RESERVED_MODELS_KEY.into(), models.clone()); + uniform.insert(RESERVED_POOLS_KEY.into(), section.clone()); + serde_yaml::Value::Mapping(uniform) + } + _ => section.clone(), + } +} + /// `open` the plane, generation 1, its settings `section` as JSON; the snapshot it published. fn open(plugin: &DoorPlane, section: &serde_yaml::Value) -> Result { let settings = serde_json::to_vec(section).map_err(|e| format!("its section: {e}"))?; @@ -656,6 +896,52 @@ fn open(plugin: &DoorPlane, section: &serde_yaml::Value) -> Result Result<(), String> { + use busbar_contract::abi::mechanism::lifecycle::RefreshIn; + use busbar_contract::abi::plane::PlaneRefreshOut; + let settings = serde_json::to_vec(section).map_err(|e| format!("its section: {e}"))?; + let mut frame = Frame::new( + RefreshIn { + head: in_head(), + generation, + settings: Blob { + ptr: settings.as_ptr(), + len: settings.len(), + fmt: BLOB_JSON, + flags: 0, + }, + secrets: std::ptr::null(), + secrets_len: 0, + }, + PlaneRefreshOut { + head: out_head(), + snapshot: std::ptr::null(), + }, + ); + let (called, snapshot) = plugin.refresh(&mut frame); + if called.outcome != AbiOutcome::Ready { + return Err(format!("it did not refresh: {:?}", called.outcome)); + } + snapshot + .map(|_| ()) + .ok_or_else(|| "its snapshot did not pass the host's checks".to_string()) +} + +/// `retire` the plane's `generation` (a unit that arrived on it keeps its own hold). +fn retire(plugin: &DoorPlane, generation: u64) { + use busbar_contract::abi::mechanism::lifecycle::{slot as life, GenIn}; + let mut frame = Frame::new( + GenIn { + head: in_head(), + generation, + }, + out_head(), + ); + let _ = plugin.call(life::RETIRE, &mut frame); +} + // ── the data routes: a claimed arrival, driven ────────────────────────────────────────────────── /// THE DOOR PLANES' DATA ROUTES (SERVE-WIRE P2, TODO U6-U7; ARCHITECT Q-SW1 2026-10-02: the route @@ -667,7 +953,7 @@ fn open(plugin: &DoorPlane, section: &serde_yaml::Value) -> Result, post: Arc, /// The card history a unit is pinned to at its door: the process's (`ROOT_CARD`). pin: fn() -> Option, @@ -690,7 +976,7 @@ impl std::fmt::Debug for DataRoutes { /// A claim the data listener cannot mount, two claims at an equal precedence, or (a build with no /// node) any claim at all: no unit could be driven for it. pub fn data_routes( - served: Served, + served: Arc, data_chain: &[String], core: &[( String, @@ -735,6 +1021,8 @@ pub struct DoorRequest { pub body: Bytes, /// The auth gate's verdict for the caller. pub gov: busbar_contract::records::PlaneRequestCtx, + /// The caller's verified credential, lent for a passthrough member's outbound auth call alone. + pub credential: Option>>, /// The generation serving the request. pub app: Arc, } @@ -831,7 +1119,7 @@ type DoorClaim = (usize, u32); /// refuses it. #[cfg(linked_axis_node)] pub fn door_routes( - served: Served, + served: Arc, pin: fn() -> Option, data_chain: &[String], core: &[(String, RouteMethod, RouteAuth)], @@ -981,6 +1269,7 @@ impl DoorRequest { headers: ctx.headers, body: ctx.body, gov: ctx.gov.unwrap_or_default(), + credential: ctx.caller_credential, app, }) } @@ -998,6 +1287,7 @@ impl DataRoutes { headers, body, gov, + credential, app, } = req; let fields: HeadFields = headers @@ -1017,17 +1307,31 @@ impl DataRoutes { Some(key) => PrincipalId::new(key.id.as_str()), None => PrincipalId::new(AuthPrincipal(None).actor_id()), }; - let open = self.served.planes[plane] - .snapshot - .claims - .get(claim as usize) - .is_some_and(|c| c.flags & CLAIM_OPEN != 0); + // An anonymous caller is admitted on an open claim; and, on the plane serving the `pools` + // map, on every claim when the deployment's data front door is open (`auth.chain: []` + // without `keys`: 1.5.5 admitted every model request anonymously, its open relay). Any + // other plane's credential claim fails closed (ARCHITECT P3 (a)). + let pools_plane = self.served.planes[plane].served_facts.section + == busbar_contract::section::RESERVED_POOLS_KEY; + let open = (pools_plane && app.auth.is_open()) + || self.served.planes[plane] + .snapshot + .claims + .get(claim as usize) + .is_some_and(|c| c.flags & CLAIM_OPEN != 0); let key = gov.key.clone(); let (caller, reply) = IngressCaller::new(); let unit = async move { let caller = caller; - self.drive(plane, app, principal, key, open, &caller, arrival) - .await + self.drive( + plane, + app, + (principal, key, credential), + open, + &caller, + arrival, + ) + .await }; reply.answer(Box::pin(unit)).await } @@ -1042,19 +1346,23 @@ impl DataRoutes { &self, plane: usize, app: Arc, - principal: PrincipalId, - key: Option>, + (principal, key, credential): UnitCaller, open: bool, caller: &IngressCaller, arrival: Arrival, ) -> Option { let served = &self.served.planes[plane]; + // The generation this unit is served by, held to its end. + let live = served.live.load_full(); + let started = std::time::Instant::now(); + let claim = arrival.claim; + let app_for_metrics = Arc::clone(&app); let node = self.post.node(); let unit = node.mint(); let arrived = node.arrived(); let steps = DoorSteps::new( - &served.facts, - &served.pools, + &live.facts, + &live.pools, node.resolver(), app, Some(&*served.money), @@ -1067,18 +1375,31 @@ impl DataRoutes { }, ); let far = DoorFar { - egress: served.egress.as_deref(), + egress: live.egress.as_deref(), steps: &steps, unit, + credential, far: OnceLock::new(), }; let units = served.driver.unit(&steps, &far, caller, arrival, 0); let money = Arc::clone(&served.money); - let facts = served.facts.clone(); + let facts = live.facts.clone(); let late: crate::root::linked::node::Late = Box::new(move || report_of(&money, unit, &facts)); - let _taken = node - .drive_borrowed( + // THE UNIT'S REQUEST SPAN, as the previous release's forward opened one around the whole + // walk (at the hot-path level, so the OTLP export carries it): the pool and the dialect the + // request was read in are recorded once the plane has read it. + let span = tracing::span!( + busbar_kernel::observability::HOTPATH_LEVEL, + "forward", + plane = %served.instance, + pool = tracing::field::Empty, + ingress = tracing::field::Empty, + request_id = tracing::field::Empty + ); + let _taken = { + use tracing::Instrument as _; + node.drive_borrowed( unit, arrived, &principal, @@ -1087,7 +1408,18 @@ impl DataRoutes { late, (self.pin)(), ) - .await; + .instrument(span.clone()) + .await + }; + if let Some(decoded) = units.decoded() { + if let Some(pool) = decoded.pool.as_ref() { + span.record("pool", String::from_utf8_lossy(pool).as_ref()); + } + if let Some(dialect) = served.dialects.get(decoded.dialect as usize) { + span.record("ingress", *dialect); + } + } + drop(span); let rendered = units.take_rendered(); let status = rendered .as_ref() @@ -1095,6 +1427,45 @@ impl DataRoutes { .or_else(|| caller.stated()) .unwrap_or_else(|| refusal_status(ReasonCode::PlanePanic)); served.money.settle_end(unit, status); + // THE REQUEST FAMILIES of the plane serving the `pools` map (the flat card's plane), as the + // previous release's `ingress::finish_inner` emitted them: for every request its dialect + // read (decoded, or refused for its body; a path it does not serve, or a verb it does not + // take, never reached a dialect), under the dialect it arrived in and the pool or model it + // named ("unresolved" when it named none the configuration holds). + if live.facts.plane.is_empty() { + let decoded = units.decoded(); + let counted = decoded.is_some() + || units + .declined_status() + .is_some_and(|s| s != 404 && s != 405); + if counted { + let dialect = decoded.as_ref().map_or_else( + || { + served + .snapshot + .claims + .get(claim as usize) + .map_or(0, |c| u32::from(c.refusal_dialect)) + }, + |d| d.dialect, + ); + let pool = decoded + .and_then(|d| d.pool) + .map(|p| String::from_utf8_lossy(&p).into_owned()) + .filter(|name| { + live.pools.pools().contains_key(name) + || live.pools.entries().iter().any(|e| e == name) + }) + .unwrap_or_else(|| "unresolved".to_string()); + busbar_kernel::telemetry::model_request_finished( + &app_for_metrics, + served.dialects.get(dialect as usize).copied().unwrap_or(""), + &pool, + u16::try_from(status).unwrap_or(u16::MAX), + started.elapsed().as_secs_f64(), + ); + } + } rendered } } @@ -1138,6 +1509,8 @@ struct DoorFar<'d, 's> { egress: Option<&'d Egress>, steps: &'d DoorSteps<'s>, unit: busbar_contract::UnitKey, + /// The caller's verified credential, lent to the walk for a passthrough member alone. + credential: Option>>, far: OnceLock>>, } @@ -1151,6 +1524,7 @@ impl<'d> DoorFar<'d, '_> { Some(egress.unit(UnitRoute { unit: self.unit, pool: egress_pool(self.steps.plane(), &routed), + caller_credential: self.credential.clone(), ..UnitRoute::default() })) }) @@ -1280,9 +1654,27 @@ impl IngressReply { rendered = &mut unit => Err(rendered), }; let rendered = match first { + // A WHOLE ANSWER goes out whole: an answer whose head states its length (one the plane + // rendered in full) is collected to its end and sent under the length of what was + // written, as the previous release sent a buffered answer; any other answer goes out + // piece by piece as the unit writes it. + Ok(Some(h)) if whole_answer(&h.1) => { + let whole = collect(body, unit).await; + return stated(unlengthed(h), Body::from(whole)); + } Ok(Some(h)) => return stated(h, Body::new(ReplyBody(body, Some(unit)))), Ok(None) => unit.await, Err(rendered) => match head.try_recv() { + // The unit ended in the poll that stated its head: a whole answer is every piece it + // wrote, sent under its length. + Ok(h) if whole_answer(&h.1) => { + let mut body = body; + let mut whole = Vec::new(); + while let Ok(bytes) = body.try_recv() { + whole.extend_from_slice(&bytes); + } + return stated(unlengthed(h), Body::from(whole)); + } Ok(h) => return stated(h, Body::new(ReplyBody(body, None))), Err(_) => rendered, }, @@ -1297,6 +1689,63 @@ impl IngressReply { } } +/// Whether a reply head is a WHOLE answer's: it states its length and is no event stream (SSE, or +/// the AWS event-stream framing, which the previous release relayed piece by piece). +fn whole_answer(fields: &HeadFields) -> bool { + let named = |field: &[u8]| { + fields + .iter() + .find(|(name, _)| name.eq_ignore_ascii_case(field)) + .map(|(_, value)| String::from_utf8_lossy(value).to_ascii_lowercase()) + }; + named(b"content-length").is_some() + && !named(b"content-type").is_some_and(|v| { + v.starts_with("text/event-stream") + || v.starts_with("application/vnd.amazon.eventstream") + }) +} + +/// A whole answer's head without its stated length: the response states the length of what was +/// written. +fn unlengthed((status, mut fields): Head) -> Head { + fields.retain(|(name, _)| !name.eq_ignore_ascii_case(b"content-length")); + (status, fields) +} + +/// Every piece the unit writes until it ends, the unit driven meanwhile (its writes wait on this +/// side), then whatever it wrote last. +async fn collect(mut body: mpsc::Receiver, mut unit: DrivenUnit) -> Vec { + let mut whole = Vec::new(); + loop { + tokio::select! { + biased; + piece = body.recv() => match piece { + Some(bytes) => whole.extend_from_slice(&bytes), + None => { + let _ = unit.await; + break; + } + }, + _ = &mut unit => { + while let Ok(bytes) = body.try_recv() { + whole.extend_from_slice(&bytes); + } + break; + } + } + } + whole +} + +/// Who a door unit serves: the principal the auth gate verified, its governance key, and its +/// verified credential (lent for a passthrough member's outbound auth call alone). +#[cfg(linked_axis_node)] +type UnitCaller = ( + PrincipalId, + Option>, + Option>>, +); + /// A unit the data door drives: what it rendered for its caller when it ended before any byte. pub type DrivenUnit = std::pin::Pin> + Send>>; @@ -1365,3 +1814,7 @@ mod door_tests; #[cfg(all(test, linked_axis_node))] #[path = "tests/serve_money.rs"] mod money_tests; + +#[cfg(all(test, feature = "llm-on-driver", linked_axis_node))] +#[path = "tests/serve_hook_seats.rs"] +mod hook_seat_tests; diff --git a/crates/busbar/src/root/tests/door_steps.rs b/crates/busbar/src/root/tests/door_steps.rs index 331abec9d7..1cd7f006b6 100644 --- a/crates/busbar/src/root/tests/door_steps.rs +++ b/crates/busbar/src/root/tests/door_steps.rs @@ -289,7 +289,7 @@ fn styled() -> crate::root::loader::dispatch::kinds::plane::ServedFacts { crate::root::loader::dispatch::kinds::plane::ServedFacts { need_auths: vec![(DIRECTION_OUTBOUND, "bearer")], dialects: vec!["d"], - dialect_auth: vec![(0, "bearer")], + dialect_auth: vec![(0, "bearer", b"")], ..Default::default() } } @@ -329,6 +329,7 @@ fn resolve( auths: &auths, conns, stream_ceiling_secs: 1, + models: None, }; super::member_routes(§ion, &DoorPools::of(§ion), &styled(), &reach) } @@ -381,6 +382,65 @@ fn a_member_that_cannot_be_reached_refuses_the_load_naming_it() { ); } +/// A MEMBER'S BINDING IS OPENED WITH ITS DIALECT'S PARAMETERS (ARCHITECT RULING 2026-10-03, +/// Q-L6-AUTHPARAMS): the params object, a host directive resolved from the provider's base URL +/// (the label after a named one, when it reads as a dashed name ending in a number), the +/// provider's own settings over it; no params hands the provider's settings as they are. +#[test] +fn a_members_binding_is_opened_with_its_dialects_parameters_under_the_providers_own() { + use super::sealed_settings; + let params = br#"{"service":"svc","content_type":"application/json","region":{"host_label_after":["svc-runtime","svc-runtime-fips","svc"],"default":"us-east-1","unread":"w"}}"#; + let region = |base_url: &str| { + sealed_settings(params, base_url, &serde_json::json!({})).expect("the parameters seal") + ["region"] + .clone() + }; + for (base_url, want) in [ + ("https://svc-runtime.eu-west-1.amazonaws.com", "eu-west-1"), + ( + "https://svc-runtime-fips.us-gov-west-1.amazonaws.com/", + "us-gov-west-1", + ), + ( + "https://vpce-0abc-1xyz.svc-runtime.ap-southeast-2.vpce.amazonaws.com:443/x", + "ap-southeast-2", + ), + ("https://svc.ca-central-1.amazonaws.com", "ca-central-1"), + // No label reads: the default. + ("https://svc-runtime.amazonaws.com", "us-east-1"), + ("http://127.0.0.1:9000", "us-east-1"), + ("https://svc-runtime.eu-west.amazonaws.com", "us-east-1"), + ] { + assert_eq!(region(base_url), serde_json::json!(want), "{base_url}"); + } + let sealed = sealed_settings( + params, + "https://svc-runtime.eu-west-1.amazonaws.com", + &serde_json::json!({ "service": "own", "scope": "s" }), + ) + .expect("the parameters seal"); + assert_eq!( + sealed["service"], "own", + "the provider's own setting stands" + ); + assert_eq!(sealed["scope"], "s"); + assert_eq!(sealed["content_type"], "application/json"); + assert!( + sealed.get("region_unread").is_none(), + "a host whose region reads hands on no warning" + ); + let defaulted = sealed_settings(params, "http://127.0.0.1:9000", &serde_json::json!({})) + .expect("the parameters seal"); + assert_eq!(defaulted["region"], "us-east-1"); + assert_eq!( + defaulted["region_unread"], "w", + "the warning travels with the default" + ); + let own = serde_json::json!({ "token_url": "https://t" }); + assert_eq!(sealed_settings(b"", "https://x", &own), Ok(own)); + assert!(sealed_settings(b"[1]", "https://x", &serde_json::json!({})).is_err()); +} + // ── the minted credential of a member bound under an OAuth grant (TODO row 22) ───────────────── /// A connection table standing in for the token endpoint: framed needs, each declaration and each @@ -602,6 +662,7 @@ async fn a_member_under_an_oauth_grant_presents_its_minted_then_refreshed_bearer auths: &auths, conns: std::sync::Arc::new(busbar_core_connector::Connector::new()), stream_ceiling_secs: 1, + models: None, }; let section: serde_yaml::Value = serde_yaml::from_str("models: {m: {provider: p}}").expect("yaml"); diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index f1c0cde3d2..1e9f395b83 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -233,6 +233,7 @@ async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { auths: &auths, conns: Arc::clone(&connector) as Arc, stream_ceiling_secs: 600, + models: None, }; // THE COMPOSITION, AS PRODUCTION SEALS IT: the door opened with its section (one model), @@ -254,16 +255,17 @@ async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { reach: &reach, journal: Arc::clone(&post) as Arc, }), + None, ) .expect("the door plane composes, its egress sealed"); let _ = std::fs::remove_file(&key_file); let composed = &mut served.planes[0]; assert!( - composed.egress.is_some(), + composed.live.load().egress.is_some(), "the composition sealed its egress" ); let money_steps = Arc::clone(&composed.money); - let plane_key = composed.facts.plane.clone(); + let plane_key = composed.live.load().facts.plane.clone(); served.post = Some(Arc::clone(&post)); let app = busbar_kernel::test_support::TestApp::new() .keys_chain() @@ -271,7 +273,7 @@ async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { .cost(CostModel::flat(1)) .build(); // THE DATA ROUTER, BUILT WITH THE DOOR'S ROUTES (Q-SW1: its construction, no static). - let doors = door_routes(served, || CARD.pin(), &[], &[]).expect("its claims mount"); + let doors = door_routes(Arc::new(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); @@ -372,9 +374,10 @@ async fn the_data_router_built_with_the_door_serves_only_its_claims() { §ions, &money, None, + None, ) .expect("the door plane composes"); - let doors = door_routes(served, || CARD.pin(), &[], &[]).expect("its claims mount"); + let doors = door_routes(Arc::new(served), || CARD.pin(), &[], &[]).expect("its claims mount"); assert!(!doors.is_empty(), "the claim is a route"); let (router, _admin, _handle) = busbar_kernel::build_split_routers_serving(app, doors, 1 << 20, 0, false); @@ -398,7 +401,7 @@ async fn the_data_router_built_with_the_door_serves_only_its_claims() { "the kernel wrote the text, the plane rendered it: {body}" ); assert!( - door_routes(super::Served::default(), || CARD.pin(), &[], &[]) + door_routes(Arc::new(super::Served::default()), || CARD.pin(), &[], &[]) .expect("nothing to mount") .is_empty(), "a composition that claims nothing mounts nothing" diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs new file mode 100644 index 0000000000..fc1c767e76 --- /dev/null +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE SEAT ORDER OF THE REQUEST-STAGE HOOKS, ON THE DOOR (ported from the predev-only `busbar-llm` +//! test `hook_seat_order_tests`, BUSBAR-1.6.0.md Part 5 F25: before any engine deletion every test +//! names its new home). The llm plane served through its door, end to end: a keyed caller behind +//! the data listener's auth gate, admitted and charged by the door's money steps, its unit's hooks +//! bound by the kernel's hook stage (the global rewrite, the request-stage tap, a decision gate and +//! the candidate tap), its far end a real loopback upstream through the process connector. The +//! admission door charges the request first, then the global rewrite runs, then the request-stage +//! tap observes the REWRITTEN request, then the decision gate decides, and only after the gate does +//! the candidate tap fire. A gate reject refunds the billable request (the fee base) but keeps the +//! admission count. +//! +//! Every seat is proven structurally rather than by wall-clock order where the seat is a detached +//! task (taps are fire-and-forget): the rewrite hook reads the ledger AT the moment it runs, the +//! request tap's payload carries the rewritten prompt, and a rejecting gate stops everything seated +//! after it — so a request tap that still arrives was seated before the gate and a candidate tap +//! that never arrives was seated after it. + +use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, Mutex}; + +use busbar_contract::conn::{DeclaredConns, PollConns}; +use busbar_contract::hooks::{ + Candidate, PolicyResult, RewriteReply, RoutingContext, RoutingDecision, RoutingPolicy, + RoutingRequest, TransformOutcome, +}; +use busbar_kernel::cost::CostModel; +use busbar_kernel::governance::signing::{TokenSigner, DEFAULT_KID}; +use busbar_kernel::governance::{GovState, MemoryStore, NewKeySpec}; +use busbar_kernel::hooks::ResolvedPolicy; +use busbar_kernel::plane_driver::{EndPost, PlaneMoney}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use super::planes_tests::{composed_services, Published, PUBLISHING}; +use super::{compose_planes, door_routes, DoorEgress, HookStage}; +use crate::root::door_steps::{kernel_sections, provider_routes, DoorReach, OutboundAuths}; +use crate::root::loader::dispatch::kinds::plane::Plane; +use crate::root::loader::dispatch::{ + load_linked, Bind, DispatchConfig, Dispatcher, LinkedRow, NoSink, +}; +use crate::root::plane_node::{Node, NodeEndPost}; + +/// The card history the served unit is pinned to at its door: one entry, no price. +static CARD: std::sync::LazyLock = + std::sync::LazyLock::new(|| { + let holder = crate::root::kernel::RootHistory::default(); + holder.apply( + crate::root::kernel::card_from_config( + std::iter::empty::<(&str, busbar_contract::billing::RawTierRates)>(), + 0, + true, + ), + 1_000, + ); + holder + }); + +/// The marker the rewrite hook plants in the prompt; a tap payload carrying it saw the rewrite. +const REWRITTEN: &str = "rewritten-by-the-global-rewrite-seat"; + +/// The far end's answer: an openai chat completion. +const ANSWER: &str = r#"{"id":"chatcmpl-1","object":"chat.completion","created":0,"model":"m0","choices":[{"index":0,"message":{"role":"assistant","content":"hi"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}"#; + +/// One probe policy playing one seat: it appends its seat name to the shared log when it runs and +/// (for the rewrite seat) samples the key's admission count at that instant. +struct SeatProbe { + seat: &'static str, + log: Arc>>, + /// Rewrite seat only: reads the key's `requests` count when the rewrite runs. + requests_now: Option u64 + Send + Sync>>, + /// Gate seat only: the reject the gate answers with. + reject: Option<(u16, &'static str)>, + /// Tap seats: the last delivered projection. + last_payload: Mutex>>, +} + +impl SeatProbe { + fn new(seat: &'static str, log: &Arc>>) -> Self { + Self { + seat, + log: log.clone(), + requests_now: None, + reject: None, + last_payload: Mutex::new(None), + } + } +} + +#[async_trait::async_trait] +impl RoutingPolicy for SeatProbe { + async fn decide( + &self, + _req: &RoutingRequest<'_>, + _candidates: &[Candidate<'_>], + _ctx: &RoutingContext<'_>, + _budget: std::time::Duration, + ) -> PolicyResult { + self.log.lock().unwrap().push(self.seat.to_string()); + Ok(match self.reject { + Some((status, message)) => RoutingDecision::Reject { + status, + message: message.to_string(), + }, + None => RoutingDecision::Abstain, + }) + } + fn name(&self) -> &'static str { + self.seat + } + async fn transform( + &self, + _req: &RoutingRequest<'_>, + _budget: std::time::Duration, + ) -> TransformOutcome { + let requests = self.requests_now.as_ref().map(|f| f()); + self.log + .lock() + .unwrap() + .push(format!("{}:requests={:?}", self.seat, requests)); + TransformOutcome::Rewrite(RewriteReply { + messages: vec![serde_json::json!({"role": "user", "content": REWRITTEN})], + tools: vec![], + }) + } + async fn notify( + &self, + tap: std::sync::Arc, + _budget: std::time::Duration, + ) { + let projection = serde_json::to_vec(&tap.projection_json()).expect("the tap's JSON"); + self.log.lock().unwrap().push(self.seat.to_string()); + *self.last_payload.lock().unwrap() = Some(projection); + } +} + +fn gate(policy: Arc) -> ResolvedPolicy { + ResolvedPolicy::Policy { + policy, + on_error: busbar_kernel::config::PolicyOnError::default(), + on_error_chain: Vec::new(), + timeout: std::time::Duration::from_millis(500), + send_prompt: false, + send_user: false, + on_empty: busbar_kernel::config::PolicyOnError::Reject, + } +} + +/// Poll a tap probe until its detached delivery lands (bounded), or return `None`. +async fn delivered(tap: &SeatProbe, budget_ms: u64) -> Option> { + for _ in 0..(budget_ms / 10) { + if let Some(p) = tap.last_payload.lock().unwrap().clone() { + return Some(p); + } + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + None +} + +/// A far end on loopback answering every request with [`ANSWER`]. +async fn far_end() -> u16 { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("a loopback port"); + let port = listener.local_addr().expect("its address").port(); + tokio::spawn(async move { + while let Ok((mut socket, _)) = listener.accept().await { + tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + let mut got = Vec::new(); + loop { + let Ok(n) = socket.read(&mut buf).await else { + return; + }; + if n == 0 { + return; + } + got.extend_from_slice(&buf[..n]); + let text = String::from_utf8_lossy(&got).to_string(); + if let Some(at) = text.find("\r\n\r\n") { + let length = text[..at] + .lines() + .find_map(|l| { + let (name, value) = l.split_once(':')?; + name.eq_ignore_ascii_case("content-length") + .then(|| value.trim().parse::().ok()) + .flatten() + }) + .unwrap_or(0); + if got.len() >= at + 4 + length { + break; + } + } + } + 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() + ); + let _ = socket.write_all(reply.as_bytes()).await; + let _ = socket.shutdown().await; + }); + } + }); + port +} + +/// The key's ledger as the test reads it: (admission count, derived spend in cents, the durable +/// row's admission count, the durable row's billable count). +type Ledger = (u64, i64, u64, u64); + +/// What a run left: the caller's status, the seat log, the two tap probes and the ledger reader. +struct Ran { + status: u16, + log: Arc>>, + request_tap: Arc, + candidate_tap: Arc, + read: Arc Ledger + Send + Sync>, +} + +/// ONE KEYED REQUEST through the llm door with the four probed seats installed, a rejecting gate +/// when `reject_at_gate`. +async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { + // The registry rows that own the `pools:`/`models:` sections the deployment writes. + busbar_llm::testkit::install_test_seams(); + let port = far_end().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), + judge, + &[], + Arc::new(|_| {}), + ) + .expect("the connector builds"); + + // THE LLM DOOR, linked, bound through the loader's one load, its needs declared on the + // connector. + let dispatcher = Arc::new(Dispatcher::new(DispatchConfig::default())); + let row = LinkedRow::of(busbar_plane_llm::plane_door::door).expect("the door states itself"); + 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"); + + // THE DEPLOYMENT: one openai provider on the far end, one model, pool `p` over it. + let key_file = std::env::temp_dir().join(format!( + "busbar-hook-seats-{}-{instance}", + std::process::id() + )); + std::fs::write(&key_file, "sk-seats").expect("the credential file"); + let deploy = busbar_kernel::config::deploy_from_yaml_str(&format!( + "providers:\n oai:\n api_key: {{ file: '{}' }}\nmodels:\n m0:\n provider: oai\n\ + pools:\n p:\n members:\n - model: m0\n", + key_file.display() + )) + .expect("a deployment"); + let defs: HashMap = serde_yaml::from_str(&format!( + "oai:\n protocol: openai\n base_url: 'http://127.0.0.1:{port}'\n" + )) + .expect("the providers"); + let cfg = busbar_kernel::config::resolve(&deploy, &defs).expect("resolves"); + let providers = provider_routes(&cfg.providers); + let sections = kernel_sections(&cfg); + let model_pools = crate::root::model_egress::ModelPools::of(&cfg); + + // THE MONEY: a signing governance book with one minted key, the node's book bound. + let signer = TokenSigner::from_secret_bytes(&[7u8; 32], DEFAULT_KID); + let gov = Arc::new( + GovState::new_with_signer(Arc::new(MemoryStore::new()), None, Some(signer)) + .expect("governance"), + ); + let (key, token) = gov + .mint_signed( + NewKeySpec { + name: "seats".to_string(), + ..Default::default() + }, + 4_000_000_000, + 1_700_000_000, + ) + .expect("mint"); + gov.hydrate_budgets(&CostModel::flat(1), 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 site = Arc::clone(&post); + let book_money = Arc::clone(&gov); + let plane_money = move || { + Arc::new(PlaneMoney::new( + Arc::clone(&book_money), + Arc::clone(&site) as Arc, + )) + }; + + // THE GENERATION: the lane, the pool, the flat 1-cent fee, and the four seats. + let mut app = busbar_kernel::test_support::TestApp::new() + .keys_chain() + .governance(Arc::clone(&gov)) + .cost(CostModel::flat(1)) + .lane(busbar_kernel::test_support::LaneSpec::new( + "m0", + "openai", + &format!("http://127.0.0.1:{port}"), + )) + .pool("p", &[(0, 1)]) + .build(); + let log = Arc::new(Mutex::new(Vec::new())); + let read: Arc Ledger + Send + Sync> = { + let (gov, cost, key_id) = (Arc::clone(&gov), app.cost.clone(), key.id.clone()); + Arc::new(move || { + let u = gov + .usage_for(cost.as_ref(), &key_id, busbar_kernel::store::now()) + .expect("usage read") + .expect("the key exists"); + gov.flush_budgets(); + // Window 0 is the key's all-time bucket. + let row = gov.store().get_usage(&key_id, 0).expect("ledger row"); + ( + u.requests, + u.spend_cents, + row.requests, + row.billable_requests, + ) + }) + }; + let mut rewrite = SeatProbe::new("rewrite", &log); + let read_for_rewrite = Arc::clone(&read); + rewrite.requests_now = Some(Box::new(move || read_for_rewrite().0)); + let request_tap = Arc::new(SeatProbe::new("request-tap", &log)); + let candidate_tap = Arc::new(SeatProbe::new("candidate-tap", &log)); + let mut gate_probe = SeatProbe::new("gate", &log); + if reject_at_gate { + gate_probe.reject = Some((451, "the gate says no")); + } + { + let a = Arc::get_mut(&mut app).expect("sole owner"); + a.rewrite_hooks = vec![(std::time::Duration::from_millis(500), Arc::new(rewrite))]; + // The request tap holds the prompt grant so its payload carries the (rewritten) messages. + let rt: Arc = request_tap.clone(); + a.tap_hooks = vec![(std::time::Duration::from_millis(500), true, rt, Vec::new())]; + let ct: Arc = candidate_tap.clone(); + a.tap_hooks_candidate = + vec![(std::time::Duration::from_millis(500), false, ct, Vec::new())]; + a.global_gates = vec![(0u16, gate(Arc::new(gate_probe)))]; + } + + // THE COMPOSITION, as production seals it: the door opened with the deployment's sections, + // its egress the model-serving walk over the kernel's lane cells, its hooks the kernel's stage. + let secrets = busbar_kernel::config::secret::SecretResolver::builtins_only(); + let auths = OutboundAuths::new(Arc::clone(&dispatcher), crate::LINKED.auths, None, None); + let models = crate::root::model_egress::ModelServing { + pools: model_pools, + lanes: HashMap::from([("m0".to_string(), 0)]), + app: { + let app = Arc::clone(&app); + Arc::new(move || Arc::clone(&app)) + }, + }; + let reach = DoorReach { + providers: &providers, + secrets: &secrets, + auths: &auths, + conns: Arc::clone(&connector) as Arc, + stream_ceiling_secs: 600, + models: Some(&models), + }; + let stage = HookStage { + host: { + let app = Arc::clone(&app); + Arc::new(move || busbar_kernel::plane_host::engine_host(&app)) + }, + gov: Arc::clone(&gov), + }; + let sections: BTreeMap<&'static str, serde_yaml::Value> = sections; + let mut served = compose_planes( + &[(instance.to_string(), plane)], + &dispatcher, + &composed_services(), + §ions, + &plane_money, + Some(&DoorEgress { + reach: &reach, + journal: Arc::clone(&post) as Arc, + }), + Some(&stage), + ) + .expect("the llm door composes"); + let _ = std::fs::remove_file(&key_file); + served.post = Some(Arc::clone(&post)); + let doors = door_routes( + Arc::new(served), + || CARD.pin(), + &[], + &busbar_kernel::base_data_core_lines(&app), + ) + .expect("its claims mount"); + let (router, _admin, _handle) = + busbar_kernel::build_split_routers_serving(Arc::clone(&app), doors, 1 << 20, 0, false); + + // THE REQUEST, as the keyed caller. + let response = { + use tower::ServiceExt as _; + let req = axum::http::Request::builder() + .method("POST") + .uri("/v1/chat/completions") + .header("authorization", format!("Bearer {}", token.expose_secret())) + .header("content-type", "application/json") + .body(axum::body::Body::from( + serde_json::json!({"model": "p", "max_tokens": 16, + "messages": [{"role": "user", "content": "the original prompt"}]}) + .to_string(), + )) + .expect("a request"); + router + .clone() + .oneshot(req) + .await + .expect("the router answers") + }; + let status = response.status().as_u16(); + let _ = axum::body::to_bytes(response.into_body(), 1 << 16).await; + Ran { + status, + log, + request_tap, + candidate_tap, + read, + } +} + +/// A rejecting decision gate: the admission charge, the rewrite and the request tap all happened +/// before it; the candidate tap never fires; the billable request is refunded and the admission +/// count is kept. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn migrated_request_hook_seats_after_admit_before_candidate() { + let _one = PUBLISHING.lock().await; + let instance = "serve-hook-seats-reject"; + let _published = Published(instance); + let ran = run(instance, true).await; + assert_eq!( + ran.status, 451, + "the gate's reject is what the caller receives" + ); + + // Admission door before rewrite: the rewrite seat saw the request already counted. + let log = ran.log.lock().unwrap().clone(); + assert!( + log.iter().any(|e| e == "rewrite:requests=Some(1)"), + "the rewrite seat must run AFTER the admission door charged the request; log: {log:?}" + ); + // Rewrite before the request tap: the tap's payload carries the rewritten prompt, and the tap + // was delivered at all even though the gate rejected (so it was seated before the gate). + let payload = delivered(&ran.request_tap, 2_000) + .await + .expect("the request-stage tap fires before the gate and is delivered despite the reject"); + let text = String::from_utf8_lossy(&payload); + assert!( + text.contains(REWRITTEN), + "the request tap must observe the REWRITTEN request: {text}" + ); + assert!( + !text.contains("the original prompt"), + "the request tap must not see the pre-rewrite prompt: {text}" + ); + // The gate ran (it produced the 451) and the candidate tap, seated after the gates, never + // fires on a rejected request. + assert!( + log.iter().any(|e| e == "gate"), + "the gate seat ran; log: {log:?}" + ); + assert!( + delivered(&ran.candidate_tap, 300).await.is_none(), + "the candidate tap is seated after the gates, so a gate reject must never reach it" + ); + // The reject consumed the requests slot but refunded the fee base (a flat 1-cent fee per + // billable request, so derived spend reads the billable count directly). + let (requests, spend, row_requests, row_billable) = (ran.read)(); + assert_eq!(requests, 1, "the admission count is never refunded"); + assert_eq!( + spend, 0, + "the billable request (the fee base) is refunded on a gate reject" + ); + assert_eq!( + (row_requests, row_billable), + (1, 0), + "the durable ledger row keeps the admission and drops the billable request" + ); +} + +/// The positive twin: an abstaining gate lets the request through, the candidate tap fires AFTER +/// the gate decided, and a served request keeps its billable request (one fee). +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn candidate_tap_fires_after_an_abstaining_gate_and_a_served_request_bills_once() { + let _one = PUBLISHING.lock().await; + let instance = "serve-hook-seats-serve"; + let _published = Published(instance); + let ran = run(instance, false).await; + assert_eq!(ran.status, 200, "the live lane serves the request"); + assert!( + delivered(&ran.candidate_tap, 2_000).await.is_some(), + "the candidate tap fires on a request the gates let through" + ); + let log = ran.log.lock().unwrap().clone(); + let gate_at = log.iter().position(|e| e == "gate").expect("the gate ran"); + let cand_at = log + .iter() + .position(|e| e == "candidate-tap") + .expect("the candidate tap was delivered"); + let rewrite_at = log + .iter() + .position(|e| e.starts_with("rewrite:")) + .expect("the rewrite ran"); + assert!( + rewrite_at < gate_at && gate_at < cand_at, + "seat order must be rewrite, gate, candidate tap; log: {log:?}" + ); + // The unit's money settles at its end, which the caller's last byte may precede: read the + // ledger once it has (bounded). + let mut ledger = (ran.read)(); + for _ in 0..200 { + if ledger.3 == 1 { + break; + } + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + ledger = (ran.read)(); + } + let (requests, spend, row_requests, row_billable) = ledger; + assert_eq!((requests, row_requests), (1, 1)); + assert_eq!( + (spend, row_billable), + (1, 1), + "a served request keeps its one billable request (1-cent flat fee)" + ); +} diff --git a/crates/busbar/src/root/tests/serve_money.rs b/crates/busbar/src/root/tests/serve_money.rs index 7ab589188e..c945d13337 100644 --- a/crates/busbar/src/root/tests/serve_money.rs +++ b/crates/busbar/src/root/tests/serve_money.rs @@ -70,11 +70,17 @@ fn governed(instance: &'static str, keys_chain: bool) -> Option { §ions, &move || Arc::clone(&one), None, + None, ) .expect("the door plane composes"); served.post = Some(Arc::clone(&post)); - let routes = door_routes(served, || crate::root::kernel::ROOT_CARD.pin(), &[], &[]) - .expect("its claims mount"); + let routes = door_routes( + Arc::new(served), + || crate::root::kernel::ROOT_CARD.pin(), + &[], + &[], + ) + .expect("its claims mount"); let app = busbar_kernel::test_support::TestApp::new(); let app = if keys_chain { app.keys_chain() } else { app }; let app = app @@ -146,8 +152,11 @@ async fn a_keyed_unit_is_admitted_and_its_money_settles_at_its_end() { let (status, body) = g.post("/call/direct:m", true).await; assert_eq!( (status, body.as_str()), - (503, "refused:503:breaker_open"), - "admitted, then the walk is exhausted" + ( + 503, + "refused:503:The service is temporarily overloaded. Please retry shortly." + ), + "admitted, then the walk is exhausted (the shed in 1.5.5's words)" ); assert_eq!(g.requests(), 1, "its request was charged at admission"); assert_eq!(g.money.open_units(), 0, "its money facts closed at its end"); @@ -199,7 +208,10 @@ async fn an_anonymous_unit_on_an_open_claim_routes_and_opens_no_money() { let (status, body) = g.post("/open", false).await; assert_eq!( (status, body.as_str()), - (503, "refused:503:breaker_open"), + ( + 503, + "refused:503:The service is temporarily overloaded. Please retry shortly." + ), "admitted, then the walk is exhausted" ); assert_eq!(g.requests(), 0, "nothing charged"); diff --git a/crates/busbar/src/root/tests/serve_planes.rs b/crates/busbar/src/root/tests/serve_planes.rs index e54d94b0dd..dec66eef56 100644 --- a/crates/busbar/src/root/tests/serve_planes.rs +++ b/crates/busbar/src/root/tests/serve_planes.rs @@ -128,7 +128,7 @@ fn a_configured_door_plane_is_opened_driven_and_its_admin_routes_published() { let mut sections = BTreeMap::new(); sections.insert("test_plane", serde_yaml::Value::Mapping(Default::default())); let late = composed_services(); - let served = compose_planes(&doors, &dispatcher, &late, §ions, &money, None) + let served = compose_planes(&doors, &dispatcher, &late, §ions, &money, None, None) .expect("the door plane composes"); assert_eq!(served.planes.len(), 1, "one plane composed"); let p = &served.planes[0]; @@ -166,8 +166,16 @@ fn a_door_plane_whose_section_is_absent_stays_unopened() { }; let doors = vec![(instance.to_string(), plane)]; let late = composed_services(); - let served = compose_planes(&doors, &dispatcher, &late, &BTreeMap::new(), &money, None) - .expect("nothing to compose is not a refusal"); + let served = compose_planes( + &doors, + &dispatcher, + &late, + &BTreeMap::new(), + &money, + None, + None, + ) + .expect("nothing to compose is not a refusal"); assert!(served.planes.is_empty(), "LAW 7: no section, no plane"); } @@ -181,6 +189,7 @@ fn no_door_plane_composes_nothing_and_needs_no_services() { &BTreeMap::new(), &money, None, + None, ) .expect("an empty composition"); assert!(served.planes.is_empty()); diff --git a/crates/busbar/tests/fixtures/computed_refusal_sites.txt b/crates/busbar/tests/fixtures/computed_refusal_sites.txt index 62542da676..ae898f1349 100644 --- a/crates/busbar/tests/fixtures/computed_refusal_sites.txt +++ b/crates/busbar/tests/fixtures/computed_refusal_sites.txt @@ -9,3 +9,9 @@ busbar/src/root/units_admin/mod.rs verbs_reason(refusal.reason) busbar/src/root/units_admin/mod.rs verbs_reason(refusal.reason) busbar-kernel/src/plane_driver/mod.rs reason busbar-kernel/src/plane_driver/mod.rs cause +# Reviewed (lane-dg-llm): the hook stage's refusal at the head of the route leg (HookVeto or +# DecodeFailed, the unit's money finished first; K5 de3ea6acc4) and the door's destination guard at +# verify, before admission (PoolNotPermitted or NoRate; FLIP-LLM b26be1ca6d). Neither is an +# overdraft ceiling or a stale slice. +busbar-kernel/src/plane_driver/mod.rs reason +busbar/src/root/door_steps.rs refusal.reason() diff --git a/crates/busbar/tests/hook_parity_driver/held.rs b/crates/busbar/tests/hook_parity_driver/held.rs new file mode 100644 index 0000000000..a74e0e2b7b --- /dev/null +++ b/crates/busbar/tests/hook_parity_driver/held.rs @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! The hook tests the legacy llm crate carries beyond the 1.5.5 set, held on the driver before that +//! crate is deleted: the access amendment a hook handed the prompt leaves (`hook_access_tests`), +//! the operation-general projection (`hook_non_chat_projection_tests`) and the in-band system turn +//! (`hook_system_turn_tests`), each under its name with its values. The two seat-order tests ride +//! the serving path's real admission and ledger, and are held there. + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use busbar_contract::hooks::{ + Candidate, PolicyResult, RewriteReply, RoutingContext, RoutingDecision, RoutingPolicy, + RoutingRequest, TransformOutcome, +}; +use busbar_kernel::audit::amend::{node_recent, Access, AmendBody, Reader}; +use busbar_kernel::config::PolicyOnError; +use busbar_kernel::hooks::{FallbackHook, ResolvedPolicy}; +use serde_json::{json, Value}; + +use crate::policies::capturing; +use crate::rig::{member, Answered, Hooks, Pool, Rig}; + +// ── the access amendment ───────────────────────────────────────────────────────────────────────── + +/// Every hook access the node journal holds for `name` (the journal is process-wide: every hook +/// here carries a name no other test uses). +fn accesses(name: &str) -> Vec { + node_recent() + .into_iter() + .filter_map(|a| match a.body { + AmendBody::Access(access) if access.reader == Reader::Hook && access.name == name => { + Some(access) + } + _ => None, + }) + .collect() +} + +/// A hook that abstains, rewrites nothing and swallows a tap: only its name matters. +struct Named { + name: &'static str, + fails: bool, +} + +#[async_trait::async_trait] +impl RoutingPolicy for Named { + async fn decide( + &self, + _: &RoutingRequest<'_>, + _: &[Candidate<'_>], + _: &RoutingContext<'_>, + _: Duration, + ) -> PolicyResult { + if self.fails { + return Err("deliberately broken".into()); + } + Ok(RoutingDecision::Abstain) + } + fn name(&self) -> &'static str { + self.name + } + async fn transform(&self, _: &RoutingRequest<'_>, _: Duration) -> TransformOutcome { + TransformOutcome::Abstain + } +} + +fn named(name: &'static str) -> Arc { + Arc::new(Named { name, fails: false }) +} + +fn policy( + primary: Arc, + send_prompt: bool, + send_user: bool, + on_error_chain: Vec, +) -> ResolvedPolicy { + ResolvedPolicy::Policy { + policy: primary, + on_error: PolicyOnError::Weighted, + on_error_chain, + timeout: Duration::from_millis(500), + send_prompt, + send_user, + on_empty: PolicyOnError::Reject, + } +} + +fn access_body() -> Value { + json!({ + "model": "m0", + "max_tokens": 16, + "system": "sys prompt", + "metadata": {"user_id": "alice"}, + "messages": [{"role": "user", "content": "hello"}] + }) +} + +fn one() -> Pool { + Pool { + name: "p", + members: vec![member("m0", "m0")], + } +} + +async fn decide(resolved: ResolvedPolicy) { + let rig = Rig::new( + one(), + None, + Hooks { + policy: Some(resolved), + ..Hooks::default() + }, + ); + rig.fire(&access_body()).await; +} + +#[tokio::test] +async fn a_decision_gate_handed_the_prompt_leaves_exactly_one_access_amendment() { + decide(policy( + named("access-decide-prompt"), + true, + true, + Vec::new(), + )) + .await; + let seen = accesses("access-decide-prompt"); + assert_eq!(seen.len(), 1, "one access per hand-over: {seen:?}"); + assert_eq!(seen[0].op_class.as_str(), "anthropic"); + assert_eq!( + seen[0].fields, + vec!["content".to_string(), "identity".to_string()], + "field NAMES that crossed, never their values" + ); +} + +#[tokio::test] +async fn a_decision_gate_handed_only_the_shape_leaves_no_access_amendment() { + decide(policy( + named("access-decide-shape"), + false, + true, + Vec::new(), + )) + .await; + assert!(accesses("access-decide-shape").is_empty()); +} + +#[tokio::test] +async fn an_on_error_fallback_handed_the_prompt_leaves_its_own_access_amendment() { + let primary: Arc = Arc::new(Named { + name: "access-primary-fails", + fails: true, + }); + let fallback = FallbackHook { + policy: named("access-fallback-prompt"), + timeout: Duration::from_millis(500), + send_prompt: true, + send_user: false, + on_empty: PolicyOnError::Reject, + }; + decide(policy(primary, true, false, vec![fallback])).await; + assert_eq!(accesses("access-primary-fails").len(), 1); + let seen = accesses("access-fallback-prompt"); + assert_eq!( + seen.len(), + 1, + "the fallback was handed the prompt: {seen:?}" + ); + assert_eq!(seen[0].fields, vec!["content".to_string()]); +} + +#[tokio::test] +async fn an_on_error_fallback_without_the_prompt_grant_leaves_no_access_amendment() { + let primary: Arc = Arc::new(Named { + name: "access-primary-fails-2", + fails: true, + }); + let fallback = FallbackHook { + policy: named("access-fallback-shape"), + timeout: Duration::from_millis(500), + send_prompt: false, + send_user: false, + on_empty: PolicyOnError::Reject, + }; + decide(policy(primary, true, false, vec![fallback])).await; + assert!(accesses("access-fallback-shape").is_empty()); +} + +#[tokio::test] +async fn a_rewrite_hook_handed_the_prompt_leaves_exactly_one_access_amendment() { + let rig = Rig::new( + one(), + None, + Hooks { + rewrites: vec![(Duration::from_millis(500), named("access-rewrite"))], + ..Hooks::default() + }, + ); + rig.fire(&access_body()).await; + let seen = accesses("access-rewrite"); + assert_eq!(seen.len(), 1, "one access per hand-over: {seen:?}"); + assert_eq!(seen[0].fields, vec!["content".to_string()]); +} + +#[tokio::test] +async fn a_global_tap_leaves_an_access_amendment_only_when_handed_the_prompt() { + let ms = Duration::from_millis(500); + let mut hooks = Hooks::default(); + hooks.taps.request = vec![ + (ms, true, named("access-tap-prompt"), Vec::new()), + (ms, false, named("access-tap-shape"), Vec::new()), + ]; + let rig = Rig::new(one(), None, hooks); + rig.fire(&json!({ + "model": "p", + "messages": [{"role": "user", "content": "hi"}], + "max_tokens": 10 + })) + .await; + assert_eq!(accesses("access-tap-prompt").len(), 1); + assert!(accesses("access-tap-shape").is_empty()); +} + +// ── the operation-general projection ───────────────────────────────────────────────────────────── + +/// The exact strings a `prompt: ro` hook is shown for `body` posted to `path`, flattened across +/// turns, and the size signal. +async fn gate_view(path: &str, content_type: &str, body: &[u8]) -> (String, usize) { + let (seen, policy) = capturing(true, false, None); + let rig = Rig::new( + Pool { + name: "p", + members: vec![member("m0", "m0").provider("oai")], + }, + None, + Hooks { + policy: Some(policy), + ..Hooks::default() + }, + ); + rig.fire_with( + &crate::common::TestUnits::passing(), + path, + &[("content-type", content_type)], + body, + ) + .await; + let captured = seen.lock().unwrap().clone(); + let c = captured.unwrap_or_else(|| panic!("the hook was shown the request at {path}")); + let (system, messages) = c.prompt.expect("a prompt: ro hook is shown the prompt"); + let mut out: Vec = system.into_iter().collect(); + out.extend(messages.into_iter().map(|(_, t)| t)); + (out.join("\n"), c.total_chars) +} + +async fn json_view(path: &str, v: Value) -> (String, usize) { + gate_view( + path, + "application/json", + &serde_json::to_vec(&v).expect("body serializes"), + ) + .await +} + +#[tokio::test] +async fn embeddings_body_is_no_longer_gate_blind() { + let (view, chars) = json_view( + "/v1/embeddings", + json!({"model": "m0", "input": "SCREEN-THIS-INPUT"}), + ) + .await; + assert!(chars > 0, "embeddings request must not project empty"); + assert!(view.contains("SCREEN-THIS-INPUT")); +} + +#[tokio::test] +async fn image_body_is_no_longer_gate_blind() { + let (view, _) = json_view( + "/v1/images/generations", + json!({"model": "m0", "prompt": "SCREEN-THIS-PROMPT"}), + ) + .await; + assert!(view.contains("SCREEN-THIS-PROMPT")); +} + +#[tokio::test] +async fn speech_body_projects_input_and_instructions() { + // The OpenAI `/v1/audio/speech` request body as a client sends it. + let v: Value = serde_json::from_str(include_str!("speech_request.json")) + .expect("the speech request fixture is JSON"); + let (view, _) = json_view("/v1/audio/speech", v).await; + assert!(view.contains("SPEAK-THIS")); + assert!( + view.contains("STYLE-INSTRUCTIONS"), + "instructions must be screened" + ); +} + +#[tokio::test] +async fn moderation_body_projects_text_and_marks_image_url_opaque() { + let (view, _) = json_view( + "/v1/moderations", + json!({ + "model": "m0", + "input": [ + {"type": "text", "text": "SCREEN-THIS-TEXT"}, + {"type": "image_url", "image_url": {"url": "https://x.test/y.png"}} + ] + }), + ) + .await; + assert!(view.contains("SCREEN-THIS-TEXT")); + // The image URL is present-but-unscreenable, shown as the marker — not empty, not leaked. + assert!(view.contains(busbar_contract::ir::facts::OPAQUE_CONTENT_MARKER)); + assert!(!view.contains("x.test")); +} + +#[tokio::test] +async fn rerank_body_projects_query_and_documents() { + let (view, _) = json_view( + "/v2/rerank", + json!({"model": "m0", "query": "THE-QUERY", "documents": ["DOC-ONE", "DOC-TWO"]}), + ) + .await; + assert!(view.contains("THE-QUERY") && view.contains("DOC-ONE") && view.contains("DOC-TWO")); +} + +#[tokio::test] +async fn transcription_prompt_is_seen_through_the_byte_seam() { + let boundary = "----busbartestBOUNDARY"; + let body = format!( + "--{b}\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nm0\r\n\ + --{b}\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nSCREEN-THIS-PROMPT\r\n\ + --{b}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.mp3\"\r\n\ + Content-Type: audio/mpeg\r\n\r\nRAWAUDIOBYTES\r\n\ + --{b}--\r\n", + b = boundary + ); + let ct = format!("multipart/form-data; boundary={boundary}"); + let (view, _) = gate_view("/v1/audio/transcriptions", &ct, body.as_bytes()).await; + assert!( + view.contains("SCREEN-THIS-PROMPT"), + "the multipart transcription prompt must be screenable through the byte reader" + ); +} + +// ── the in-band system turn ────────────────────────────────────────────────────────────────────── + +/// What a hook was shown. +#[derive(Debug, Clone, PartialEq)] +struct Seen { + system: Option, + messages: Vec<(String, String)>, + message_count: usize, + total_chars: usize, +} + +/// A headroom-shaped compressor: echoes every turn as `{role, content}`, keeps a `system` turn and +/// the last turn verbatim, and replaces every other turn's text. +struct Compressor(Mutex>); + +#[async_trait::async_trait] +impl RoutingPolicy for Compressor { + async fn decide( + &self, + _: &RoutingRequest<'_>, + _: &[Candidate<'_>], + _: &RoutingContext<'_>, + _: Duration, + ) -> PolicyResult { + Ok(RoutingDecision::Abstain) + } + fn name(&self) -> &'static str { + "system-turn-compressor" + } + async fn transform(&self, req: &RoutingRequest<'_>, _: Duration) -> TransformOutcome { + let prompt = req.prompt.as_ref().expect("a rw hook is shown the prompt"); + let messages: Vec<(String, String)> = prompt + .messages + .iter() + .map(|(r, t)| (r.to_string(), t.to_string())) + .collect(); + *self.0.lock().unwrap() = Some(Seen { + system: prompt.system.as_deref().map(str::to_string), + messages: messages.clone(), + message_count: req.message_count, + total_chars: req.total_chars, + }); + let last = messages.len().saturating_sub(1); + TransformOutcome::Rewrite(RewriteReply { + messages: messages + .iter() + .enumerate() + .map(|(i, (role, text))| { + let text = if i == last || role == "system" { + text.clone() + } else { + "COMPRESSED".to_string() + }; + json!({"role": role, "content": text}) + }) + .collect(), + tools: vec![], + }) + } +} + +/// One rw hook over `v` posted to `path` in a same-dialect pool served by `provider`: what the +/// hook saw, and the body the far end was sent. +async fn rewrite(path: &str, provider: &'static str, v: Value) -> (Seen, Value) { + let hook = Arc::new(Compressor(Mutex::new(None))); + let rig = Rig::new( + Pool { + name: "p", + members: vec![member("m0", "m0").provider(provider)], + }, + None, + Hooks { + rewrites: vec![(Duration::from_millis(50), hook.clone())], + ..Hooks::default() + }, + ); + let answer: Answered = rig + .fire_with( + &crate::common::TestUnits::passing(), + path, + &[("content-type", "application/json")], + &serde_json::to_vec(&v).expect("body serializes"), + ) + .await; + let seen = hook.0.lock().unwrap().clone().expect("the hook fired"); + let sent = answer + .sent + .last() + .expect("the rewritten request was dispatched"); + ( + seen, + serde_json::from_slice(&sent.body).expect("the far end's body is JSON"), + ) +} + +fn turns(pairs: &[(&str, &str)]) -> Vec<(String, String)> { + pairs + .iter() + .map(|(r, t)| (r.to_string(), t.to_string())) + .collect() +} + +#[tokio::test] +async fn a_rewrite_keeps_the_in_band_system_turn_upstream() { + let (seen, sent) = rewrite( + "/v1/chat/completions", + "oai", + json!({ + "model": "m0", + "messages": [ + {"role": "system", "content": "OPERATOR SYSTEM PROMPT"}, + {"role": "user", "content": "a long earlier turn"}, + {"role": "assistant", "content": "an earlier answer"}, + {"role": "user", "content": "the ask"} + ] + }), + ) + .await; + assert_eq!( + seen, + Seen { + system: None, + messages: turns(&[ + ("system", "OPERATOR SYSTEM PROMPT"), + ("user", "a long earlier turn"), + ("assistant", "an earlier answer"), + ("user", "the ask"), + ]), + message_count: 4, + total_chars: 22 + 19 + 17 + 7, + }, + "the hook must be shown the 1.5.5 view" + ); + assert_eq!( + serde_json::to_string(&sent["messages"]).unwrap(), + r#"[{"content":"OPERATOR SYSTEM PROMPT","role":"system"},{"content":"COMPRESSED","role":"user"},{"content":"COMPRESSED","role":"assistant"},{"content":"the ask","role":"user"}]"#, + "the operator's system prompt must reach the provider, exactly as 1.5.5 sent it" + ); +} + +#[tokio::test] +async fn a_mid_conversation_developer_turn_keeps_its_place_and_role() { + let (seen, sent) = rewrite( + "/v1/chat/completions", + "oai", + json!({ + "model": "m0", + "messages": [ + {"role": "user", "content": "u1"}, + {"role": "developer", "content": "DEV"}, + {"role": "user", "content": "u2"} + ] + }), + ) + .await; + assert_eq!(seen.system, None); + assert_eq!( + seen.messages, + turns(&[("user", "u1"), ("developer", "DEV"), ("user", "u2")]) + ); + assert_eq!((seen.message_count, seen.total_chars), (3, 7)); + assert_eq!( + sent["messages"], + json!([ + {"role": "user", "content": "COMPRESSED"}, + {"role": "developer", "content": "COMPRESSED"}, + {"role": "user", "content": "u2"} + ]), + "1.5.5 shipped whatever the hook answered for the developer turn" + ); +} + +#[tokio::test] +async fn cohere_in_band_system_turn_is_a_turn() { + let (seen, sent) = rewrite( + "/v2/chat", + "coh", + json!({ + "model": "m0", + "messages": [ + {"role": "system", "content": "OPERATOR SYSTEM PROMPT"}, + {"role": "user", "content": "earlier"}, + {"role": "user", "content": "hi"} + ] + }), + ) + .await; + assert_eq!(seen.system, None); + assert_eq!( + seen.messages, + turns(&[ + ("system", "OPERATOR SYSTEM PROMPT"), + ("user", "earlier"), + ("user", "hi") + ]) + ); + assert_eq!(seen.message_count, 3); + assert_eq!(sent["messages"][0]["role"], "system"); + assert_eq!(sent["messages"][0]["content"], "OPERATOR SYSTEM PROMPT"); +} + +#[tokio::test] +async fn responses_instructions_are_system_and_a_system_item_is_a_turn() { + let (seen, policy) = capturing(true, false, None); + let rig = Rig::new( + one(), + None, + Hooks { + policy: Some(policy), + ..Hooks::default() + }, + ); + rig.fire_with( + &crate::common::TestUnits::passing(), + "/v1/responses", + &[("content-type", "application/json")], + &serde_json::to_vec(&json!({ + "model": "m0", + "instructions": "INSTR", + "input": [ + {"role": "system", "content": "S"}, + {"role": "user", "content": "hi"} + ] + })) + .unwrap(), + ) + .await; + let c = seen.lock().unwrap().clone().expect("the hook was shown it"); + let (system, messages) = c.prompt.expect("prompt shown"); + assert_eq!(system.as_deref(), Some("INSTR")); + assert_eq!(messages, turns(&[("system", "S"), ("user", "hi")])); + assert_eq!(c.message_count, 2); +} diff --git a/crates/busbar/tests/hook_parity_driver/main.rs b/crates/busbar/tests/hook_parity_driver/main.rs new file mode 100644 index 0000000000..623579ec6d --- /dev/null +++ b/crates/busbar/tests/hook_parity_driver/main.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE 1.5.5 HOOK PARITY SUITE, ON THE DRIVER (`BUSBAR-1.6.0.md` Part 3, section 12, "The switch": +//! "The llm flip's gate is every hook test green on the driver — 184: the 70 of llm origin through +//! `project`, the 114 of kernel, loader and ranking origin"; Appendix A: "the hook parity suite is +//! the 184 v1.5.5 hook tests"). +//! +//! The llm-origin tests of `v1.5.5` (`crates/busbar/src/proxy/tests/hook_seam_tests.rs` and +//! `hook_opt_in_projection_tests.rs` at the tag), each under its 1.5.5 name, with the values 1.5.5 +//! asserted, run with llm served on the plane driver: the REAL llm plane door, linked and loaded +//! through the one loader, driven by the kernel's plane driver with its hook stage bound +//! (`plane_driver::hooks`), every hook a 1.5.5 test's own in-process policy. What a hook sees of +//! the request is what the plane's `project` wrote; what the caller is answered is what the plane +//! rendered. The 114 of kernel, loader and ranking origin are unchanged by the plane's path and run +//! in their own crates (`busbar-kernel` hooks tests, the plugin loader's hook tests, the ranking +//! hook's tests). +//! +//! Development-only, like the switch it proves: built with the `llm-on-driver` switch. + +#![cfg(feature = "llm-on-driver")] + +/// The plane under proof, named once: the composition root's one edge to it. +extern crate busbar_plane_llm as plane; +/// Its plane-kind facts (the operation classes the driver is configured with). +type PlaneFacts = plane::LlmPlane; + +#[path = "../../../busbar-kernel/tests/common/mod.rs"] +mod common; + +mod held; +mod policies; +mod projection; +mod rig; +mod seam; diff --git a/crates/busbar/tests/hook_parity_driver/policies.rs b/crates/busbar/tests/hook_parity_driver/policies.rs new file mode 100644 index 0000000000..c1ad66bbf6 --- /dev/null +++ b/crates/busbar/tests/hook_parity_driver/policies.rs @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! The 1.5.5 hook tests' own in-process policies: a capturing policy, a canned gate, a rewriting +//! gate, an erroring policy and a capturing tap. + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use busbar_contract::hooks::{ + Candidate, PolicyResult, RewriteReply, RoutingContext, RoutingDecision, RoutingPolicy, + RoutingRequest, TransformOutcome, +}; +use busbar_kernel::config::PolicyOnError; +use busbar_kernel::hooks::{ResolvedPolicy, TapEntry}; +use serde_json::Value; + +/// The prompt as the policy saw it: (flattened system, [(role, text)]). +pub type SeenPrompt = (Option, Vec<(String, String)>); +/// The identity as the policy saw it: (key_id, key_name, end-user). +pub type SeenIdentity = (Option, Option, Option); + +/// What the policy saw through the seam. +#[derive(Clone, Default, Debug)] +pub struct CapturedReq { + pub prompt: Option, + pub identity: Option, + pub max_tokens: Option, + pub message_count: usize, + pub total_chars: usize, + pub request_id: u64, +} + +/// A policy that records the projection it was handed and returns a fixed decision. +pub struct CapturingPolicy { + pub seen: Arc>>, + pub reject: Option<(u16, String)>, +} + +#[async_trait::async_trait] +impl RoutingPolicy for CapturingPolicy { + async fn decide( + &self, + req: &RoutingRequest<'_>, + _candidates: &[Candidate<'_>], + _ctx: &RoutingContext<'_>, + _budget: Duration, + ) -> PolicyResult { + *self.seen.lock().unwrap() = Some(CapturedReq { + prompt: req.prompt.as_ref().map(|p| { + ( + p.system.as_deref().map(str::to_string), + p.messages + .iter() + .map(|(r, t)| (r.to_string(), t.to_string())) + .collect(), + ) + }), + identity: req + .identity + .as_ref() + .map(|i| (i.key_id.clone(), i.key_name.clone(), i.user.clone())), + max_tokens: req.max_tokens, + message_count: req.message_count, + total_chars: req.total_chars, + request_id: req.request_id, + }); + Ok(match &self.reject { + Some((status, message)) => RoutingDecision::Reject { + status: *status, + message: message.clone(), + }, + None => RoutingDecision::Abstain, + }) + } + fn name(&self) -> &'static str { + "capture" + } +} + +/// A capturing policy as a resolved hook, with the grants given. +pub fn capturing( + send_prompt: bool, + send_user: bool, + reject: Option<(u16, String)>, +) -> (Arc>>, ResolvedPolicy) { + let seen = Arc::new(Mutex::new(None)); + ( + seen.clone(), + ResolvedPolicy::Policy { + policy: Arc::new(CapturingPolicy { seen, reject }), + on_error: PolicyOnError::default(), + on_error_chain: Vec::new(), + timeout: Duration::from_millis(500), + send_prompt, + send_user, + on_empty: PolicyOnError::Reject, + }, + ) +} + +/// A canned decision. +pub enum Canned { + Order(Vec), + Restrict(Vec), + Reject(u16, &'static str), +} + +/// A gate returning a fixed decision. +pub struct CannedGate { + pub canned: Canned, + pub name: &'static str, +} + +#[async_trait::async_trait] +impl RoutingPolicy for CannedGate { + async fn decide( + &self, + _req: &RoutingRequest<'_>, + _candidates: &[Candidate<'_>], + _ctx: &RoutingContext<'_>, + _budget: Duration, + ) -> PolicyResult { + Ok(match &self.canned { + Canned::Order(order) => RoutingDecision::Prefer(order.clone()), + Canned::Restrict(tags) => RoutingDecision::Restrict { + tags_any: tags.clone(), + }, + Canned::Reject(status, message) => RoutingDecision::Reject { + status: *status, + message: (*message).to_string(), + }, + }) + } + fn name(&self) -> &'static str { + self.name + } +} + +/// A canned decision as a resolved gate (shape-only, fail-closed defaults). +pub fn canned_gate(canned: Canned, name: &'static str) -> ResolvedPolicy { + ResolvedPolicy::Policy { + policy: Arc::new(CannedGate { canned, name }), + on_error: PolicyOnError::default(), + on_error_chain: Vec::new(), + timeout: Duration::from_millis(500), + send_prompt: false, + send_user: false, + on_empty: PolicyOnError::Reject, + } +} + +/// A rewrite gate: abstains as a decision, rewrites the messages on transform. +pub struct RewritingGate(pub Vec); + +#[async_trait::async_trait] +impl RoutingPolicy for RewritingGate { + async fn decide( + &self, + _req: &RoutingRequest<'_>, + _candidates: &[Candidate<'_>], + _ctx: &RoutingContext<'_>, + _budget: Duration, + ) -> PolicyResult { + Ok(RoutingDecision::Abstain) + } + fn name(&self) -> &'static str { + "rewriter" + } + async fn transform(&self, _req: &RoutingRequest<'_>, _budget: Duration) -> TransformOutcome { + TransformOutcome::Rewrite(RewriteReply { + messages: self.0.clone(), + tools: vec![], + }) + } +} + +/// A rewrite gate replacing the turns with one user turn. +pub fn rewriting(content: &str) -> (Duration, Arc) { + ( + Duration::from_millis(500), + Arc::new(RewritingGate(vec![ + serde_json::json!({"role": "user", "content": content}), + ])), + ) +} + +/// A policy whose decide always errors. +pub struct ErroringPolicy; + +#[async_trait::async_trait] +impl RoutingPolicy for ErroringPolicy { + async fn decide( + &self, + _req: &RoutingRequest<'_>, + _candidates: &[Candidate<'_>], + _ctx: &RoutingContext<'_>, + _budget: Duration, + ) -> PolicyResult { + Err("deliberately broken".into()) + } + fn name(&self) -> &'static str { + "erroring" + } +} + +/// A tap whose `notify` records the delivered projection. +pub struct CaptureTap { + pub last: Mutex>>, +} + +#[async_trait::async_trait] +impl RoutingPolicy for CaptureTap { + async fn decide( + &self, + _req: &RoutingRequest<'_>, + _cands: &[Candidate<'_>], + _ctx: &RoutingContext<'_>, + _budget: Duration, + ) -> PolicyResult { + Ok(RoutingDecision::Abstain) + } + fn name(&self) -> &'static str { + "capture-tap" + } + async fn notify( + &self, + tap: Arc, + _budget: Duration, + ) { + let projection = serde_json::to_vec(&tap.projection_json()).expect("the tap's JSON"); + *self.last.lock().unwrap() = Some(projection); + } +} + +/// An in-process tap capture and its tap entry. +pub fn webhook_tap() -> (Arc, TapEntry) { + let cap = Arc::new(CaptureTap { + last: Mutex::new(None), + }); + let policy: Arc = cap.clone(); + (cap, (Duration::from_millis(500), false, policy, Vec::new())) +} + +/// The tap's delivered projection (taps are detached tasks). +pub async fn wait_for_tap_body(cap: &CaptureTap) -> Value { + for _ in 0..200 { + if let Some(body) = cap.last.lock().unwrap().clone() { + return serde_json::from_slice(&body).expect("tap payload is JSON"); + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + panic!("tap was never delivered"); +} diff --git a/crates/busbar/tests/hook_parity_driver/projection.rs b/crates/busbar/tests/hook_parity_driver/projection.rs new file mode 100644 index 0000000000..dac3caf0a6 --- /dev/null +++ b/crates/busbar/tests/hook_parity_driver/projection.rs @@ -0,0 +1,768 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! `v1.5.5` `crates/busbar/src/proxy/tests/hook_opt_in_projection_tests.rs`, on the driver: what a +//! `prompt: ro` hook is shown of a request — the prompt view, the size signals, the end user, the +//! reasoning shapes and the opaque-content marker — as the llm plane's `project` writes it and the +//! driver hands it to the hook. Each test keeps its 1.5.5 name and the values 1.5.5 asserted. +//! +//! What a crossing does not carry is not asserted: whether a string was borrowed from the body +//! (every string crosses into the host's arena), and the system-only character count (the hook +//! wire never carried it; where a 1.5.5 test pinned it, the request carries no turn, so the total +//! it IS shown equals it). + +use serde_json::{json, Value}; + +use crate::policies::{capturing, CapturedReq}; +use crate::rig::{member, Hooks, Pool, Rig}; + +/// The fixed non-content marker shown in place of opaque provider-encrypted reasoning. +const MARKER: &str = "[busbar:redacted_reasoning]"; + +/// Where each dialect's request is posted, and what its body needs beside the content. +fn target(dialect: &str, mut v: Value) -> (&'static str, Value) { + let obj = v.as_object_mut().expect("a JSON object body"); + match dialect { + "anthropic" => { + obj.entry("model").or_insert(json!("m0")); + obj.entry("max_tokens").or_insert(json!(16)); + ("/v1/messages", v) + } + "openai" => { + obj.entry("model").or_insert(json!("m0")); + ("/v1/chat/completions", v) + } + "responses" => { + obj.entry("model").or_insert(json!("m0")); + ("/v1/responses", v) + } + "gemini" => ("/v1beta/models/m0:generateContent", v), + "bedrock" => ("/model/m0/converse", v), + "cohere" => { + obj.entry("model").or_insert(json!("m0")); + ("/v2/chat", v) + } + other => panic!("no target for {other}"), + } +} + +/// What a `prompt: ro` base policy is shown of `v` posted in `dialect`; `None` when the request +/// never reached the hooks. +async fn shown(dialect: &str, v: Value) -> Option { + let (seen, policy) = capturing(true, true, None); + let rig = Rig::new( + Pool { + name: "p", + members: vec![member("m0", "m0")], + }, + None, + Hooks { + policy: Some(policy), + ..Hooks::default() + }, + ); + let (path, body) = target(dialect, v); + let answer = rig + .fire_with( + &crate::common::TestUnits::passing(), + path, + if dialect == "anthropic" { + &[ + ("content-type", "application/json"), + ("anthropic-version", "2023-06-01"), + ] + } else { + &[("content-type", "application/json")] + }, + &serde_json::to_vec(&body).expect("body serializes"), + ) + .await; + let captured = seen.lock().unwrap().clone(); + if captured.is_none() { + eprintln!( + "{dialect} {path}: {} {} {:?}", + answer.status, + answer.text(), + answer.outcome + ); + } + captured +} + +/// [`shown`], when the request must reach the hooks. +async fn seen(dialect: &str, v: Value) -> CapturedReq { + shown(dialect, v) + .await + .unwrap_or_else(|| panic!("the hook was shown the {dialect} request")) +} + +fn prompt(c: &CapturedReq) -> (Option, Vec<(String, String)>) { + c.prompt + .clone() + .expect("a prompt: ro hook is shown the prompt") +} + +fn turns(pairs: &[(&str, &str)]) -> Vec<(String, String)> { + pairs + .iter() + .map(|(r, t)| (r.to_string(), t.to_string())) + .collect() +} + +#[tokio::test] +async fn prompt_projection_flattens_string_and_block_content() { + let c = seen( + "anthropic", + json!({ + "system": "be brief", + "messages": [ + {"role": "user", "content": "hello"}, + {"role": "assistant", "content": [ + {"type": "text", "text": "part one"}, + {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "AAAA"}}, + {"type": "text", "text": "part two"} + ]} + ] + }), + ) + .await; + let (system, messages) = prompt(&c); + assert_eq!(system.as_deref(), Some("be brief")); + assert_eq!( + messages, + turns(&[("user", "hello"), ("assistant", "part one\npart two")]) + ); +} + +#[tokio::test] +async fn prompt_projection_system_blocks_and_absent() { + let c = seen( + "anthropic", + json!({ + "system": [{"type": "text", "text": "sys a"}, {"type": "text", "text": "sys b"}], + "messages": [{"role": "user", "content": "hi"}] + }), + ) + .await; + assert_eq!(prompt(&c).0.as_deref(), Some("sys a\nsys b")); + + let c = seen( + "anthropic", + json!({"messages": [{"role": "user", "content": "hi"}]}), + ) + .await; + assert_eq!(prompt(&c).0, None); +} + +#[tokio::test] +async fn prompt_projection_keeps_empty_entries_aligned() { + let c = seen( + "anthropic", + json!({ + "messages": [ + {"role": "user", "content": [ + {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "AAAA"}} + ]}, + {"role": "assistant", "content": "second turn"} + ] + }), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages.len(), 2, "media-only entries must not vanish"); + assert_eq!(messages[0].0, "user"); + assert_eq!(messages[0].1, "", "media-only turn reads as empty text"); + assert_eq!( + messages[1], + ("assistant".to_string(), "second turn".to_string()) + ); + assert_eq!(c.message_count, 2); +} + +#[tokio::test] +async fn system_text_chars_counts_block_arrays() { + // No turn carries text, so the total a hook is shown is the system's own count. + let c = seen( + "anthropic", + json!({ + "system": [{"type": "text", "text": "abcde"}, {"type": "text", "text": "fgh"}], + "messages": [{"role": "user", "content": ""}] + }), + ) + .await; + assert_eq!(c.total_chars, 8); + assert_eq!(prompt(&c).0.as_deref(), Some("abcde\nfgh")); + + let c = seen( + "anthropic", + json!({"system": "plain", "messages": [{"role": "user", "content": ""}]}), + ) + .await; + assert_eq!(c.total_chars, 5); +} + +#[tokio::test] +async fn prompt_projection_reads_gemini_contents() { + let c = seen( + "gemini", + json!({ + "systemInstruction": {"parts": [{"text": "be brief"}]}, + "contents": [ + {"role": "user", "parts": [{"text": "hello"}]}, + {"role": "model", "parts": [ + {"text": "part one"}, + {"inlineData": {"mimeType": "image/png", "data": "AAAA"}}, + {"text": "part two"} + ]} + ] + }), + ) + .await; + let (system, messages) = prompt(&c); + assert_eq!(system.as_deref(), Some("be brief")); + assert_eq!( + messages, + turns(&[("user", "hello"), ("assistant", "part one\npart two")]) + ); + assert_eq!(c.message_count, 2); + assert_eq!(c.total_chars, 8 + 5 + 16); +} + +#[tokio::test] +async fn prompt_projection_reads_responses_input() { + let c = seen( + "responses", + json!({ + "instructions": "be brief", + "input": [ + {"role": "user", "content": "hello"}, + {"role": "assistant", "content": [{"type": "output_text", "text": "hi there"}]} + ] + }), + ) + .await; + let (system, messages) = prompt(&c); + assert_eq!(system.as_deref(), Some("be brief")); + assert_eq!( + messages, + turns(&[("user", "hello"), ("assistant", "hi there")]) + ); + assert_eq!(c.message_count, 2); + + // A bare-string `input` is ONE implicit user turn. + let c = seen("responses", json!({"input": "just a question"})).await; + assert_eq!(prompt(&c).1, turns(&[("user", "just a question")])); + assert_eq!(c.message_count, 1); + assert_eq!(c.total_chars, 15); + + // Top-level typed items carry text at the item root, roles inferred from `type`. + let c = seen( + "responses", + json!({ + "input": [ + {"type": "input_text", "text": "hello"}, + {"type": "output_text", "text": "hi back"} + ] + }), + ) + .await; + assert_eq!( + prompt(&c).1, + turns(&[("user", "hello"), ("assistant", "hi back")]), + "top-level input_text/output_text items must project with inferred roles, not blank" + ); + assert_eq!(c.message_count, 2); + assert_eq!(c.total_chars, 12); +} + +#[tokio::test] +async fn max_tokens_signal_is_dialect_aware_for_responses() { + // Responses ingress: only `max_output_tokens` is present. + let c = seen( + "responses", + json!({"input": "hi", "max_output_tokens": 4096}), + ) + .await; + assert_eq!(c.max_tokens, Some(4096)); + // A stray `max_tokens` on a responses body is NOT the signal. + let c = seen( + "responses", + json!({"input": "hi", "max_tokens": 999, "max_output_tokens": 4096}), + ) + .await; + assert_eq!(c.max_tokens, Some(4096)); + // Every other dialect reads `max_tokens`. + let c = seen( + "anthropic", + json!({"messages": [{"role": "user", "content": "hi"}], "max_tokens": 512}), + ) + .await; + assert_eq!(c.max_tokens, Some(512)); + let c = seen( + "gemini", + json!({"contents": [{"role": "user", "parts": [{"text": "hi"}]}], "max_tokens": 512}), + ) + .await; + assert_eq!(c.max_tokens, Some(512)); + // An absurd cap saturates rather than wrapping. + let c = seen( + "openai", + json!({"messages": [{"role": "user", "content": "hi"}], "max_tokens": u64::MAX}), + ) + .await; + assert_eq!(c.max_tokens, Some(u32::MAX)); +} + +#[tokio::test] +async fn prompt_projection_reads_bedrock_messages() { + let c = seen( + "bedrock", + json!({ + "system": [{"text": "sys"}], + "messages": [ + {"role": "user", "content": [{"text": "hello"}, {"text": "again"}]} + ] + }), + ) + .await; + let (system, messages) = prompt(&c); + assert_eq!(system.as_deref(), Some("sys")); + assert_eq!(messages[0].1, "hello\nagain"); + assert_eq!(c.message_count, 1); + assert_eq!(c.total_chars, 3 + 10); +} + +fn user_of(c: &CapturedReq) -> Option { + c.identity.clone().and_then(|(_, _, u)| u) +} + +#[tokio::test] +async fn body_end_user_reads_both_dialects() { + let hi = json!([{"role": "user", "content": "hi"}]); + let c = seen("openai", json!({"user": "alice", "messages": hi})).await; + assert_eq!(user_of(&c).as_deref(), Some("alice")); + let c = seen( + "anthropic", + json!({"metadata": {"user_id": "bob"}, "messages": hi}), + ) + .await; + assert_eq!(user_of(&c).as_deref(), Some("bob")); + let c = seen("anthropic", json!({"messages": hi})).await; + assert_eq!(user_of(&c), None); +} + +/// One assistant turn carrying `block`, in `dialect`'s own conversation shape. +fn assistant_turn(dialect: &str, block: Value) -> Value { + match dialect { + "responses" => json!({"input": [block]}), + "gemini" => json!({"contents": [{"role": "model", "parts": [block]}]}), + _ => json!({"messages": [{"role": "assistant", "content": [block]}]}), + } +} + +#[tokio::test] +async fn prompt_projection_sees_anthropic_thinking_text() { + let c = seen( + "anthropic", + assistant_turn( + "anthropic", + json!({"type": "thinking", "thinking": "SMUGGLED", "signature": "sig"}), + ), + ) + .await; + assert_eq!(prompt(&c).1, turns(&[("assistant", "SMUGGLED")])); +} + +#[tokio::test] +async fn prompt_projection_sees_bedrock_reasoning_text() { + let c = seen( + "bedrock", + assistant_turn( + "bedrock", + json!({"reasoningContent": {"reasoningText": {"text": "SMUGGLED", "signature": "sig-xyz"}}}), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages, turns(&[("assistant", "SMUGGLED")])); + assert!(!messages[0].1.contains("sig-xyz")); +} + +#[tokio::test] +async fn prompt_projection_sees_responses_reasoning_summary_only() { + let c = seen( + "responses", + assistant_turn( + "responses", + json!({"type": "reasoning", "summary": [{"type": "summary_text", "text": "SMUGGLED"}]}), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].1, "SMUGGLED"); +} + +#[tokio::test] +async fn prompt_projection_marks_anthropic_redacted_thinking() { + let c = seen( + "anthropic", + assistant_turn( + "anthropic", + json!({"type": "redacted_thinking", "data": "OPAQUE_CIPHERTEXT_BYTES"}), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages, turns(&[("assistant", MARKER)])); + assert!(!messages[0].1.contains("OPAQUE_CIPHERTEXT_BYTES")); +} + +#[tokio::test] +async fn prompt_projection_marks_bedrock_redacted_content() { + let c = seen( + "bedrock", + assistant_turn( + "bedrock", + json!({"reasoningContent": {"redactedContent": "OPAQUE_CIPHERTEXT_BYTES"}}), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages, turns(&[("assistant", MARKER)])); + assert!(!messages[0].1.contains("OPAQUE_CIPHERTEXT_BYTES")); +} + +#[tokio::test] +async fn prompt_projection_marks_responses_encrypted_content_only_reasoning() { + let c = seen( + "responses", + assistant_turn( + "responses", + json!({"type": "reasoning", "encrypted_content": "OPAQUE_BLOB_XYZ"}), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages, turns(&[("assistant", MARKER)])); + assert!(!messages[0].1.contains("OPAQUE_BLOB_XYZ")); +} + +#[tokio::test] +async fn prompt_projection_and_total_chars_mark_responses_reasoning_with_empty_content_array() { + let c = seen( + "responses", + assistant_turn( + "responses", + json!({"type": "reasoning", "content": [], "encrypted_content": "OPAQUE_BLOB_XYZ"}), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages, turns(&[("assistant", MARKER)])); + assert!(!messages[0].1.contains("OPAQUE_BLOB_XYZ")); + assert_eq!( + c.total_chars, + MARKER.chars().count(), + "the size signal counts the marker's length, not silently 0" + ); +} + +#[tokio::test] +async fn block_text_responses_reasoning_rejects_malformed_encrypted_content() { + // A malformed blob is not a real opaque blob: an item carrying nothing else projects no + // marker (the reader drops it, as it never ships it). + for blob in [json!(""), json!(123)] { + let c = seen( + "responses", + json!({"input": [ + {"type": "reasoning", "encrypted_content": blob}, + {"role": "user", "content": "q"} + ]}), + ) + .await; + assert!( + prompt(&c).1.iter().all(|(_, t)| !t.contains(MARKER)), + "{blob}: a malformed encrypted_content must not read as opaque content" + ); + } +} + +#[tokio::test] +async fn prompt_projection_responses_reasoning_prefers_text_over_encrypted_content() { + let c = seen( + "responses", + assistant_turn( + "responses", + json!({ + "type": "reasoning", + "content": [{"type": "reasoning_text", "text": "VISIBLE"}], + "encrypted_content": "ENC_BLOB_123" + }), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].1, "VISIBLE"); + assert!(!messages[0].1.contains(MARKER)); + assert!(!messages[0].1.contains("ENC_BLOB_123")); +} + +#[tokio::test] +async fn responses_single_part_reasoning_text_borrows() { + let c = seen( + "responses", + assistant_turn( + "responses", + json!({"type": "reasoning", "content": [{"type": "reasoning_text", "text": "one part"}]}), + ), + ) + .await; + assert_eq!(prompt(&c).1[0].1, "one part"); + let c = seen( + "responses", + assistant_turn( + "responses", + json!({"type": "reasoning", "summary": [{"type": "summary_text", "text": "just a summary"}]}), + ), + ) + .await; + assert_eq!(prompt(&c).1[0].1, "just a summary"); +} + +#[tokio::test] +async fn responses_multi_part_reasoning_text_concatenates() { + let c = seen( + "responses", + assistant_turn( + "responses", + json!({ + "type": "reasoning", + "content": [ + {"type": "reasoning_text", "text": "first "}, + {"type": "reasoning_text", "text": "second "} + ], + "summary": [{"type": "summary_text", "text": "third"}] + }), + ), + ) + .await; + assert_eq!(prompt(&c).1[0].1, "first second third"); +} + +#[tokio::test] +async fn prompt_projection_attributes_responses_reasoning_to_assistant() { + let c = seen( + "responses", + assistant_turn( + "responses", + json!({"type": "reasoning", "content": [{"type": "reasoning_text", "text": "chain of thought"}]}), + ), + ) + .await; + let (_, messages) = prompt(&c); + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].0, "assistant"); +} + +#[tokio::test] +async fn prompt_projection_mixed_text_and_thinking_turn_joins_both() { + let c = seen( + "anthropic", + json!({ + "messages": [ + {"role": "assistant", "content": [ + {"type": "text", "text": "visible answer"}, + {"type": "thinking", "thinking": "SMUGGLED", "signature": "sig"} + ]} + ] + }), + ) + .await; + assert_eq!(prompt(&c).1[0].1, "visible answer\nSMUGGLED"); +} + +#[tokio::test] +async fn prompt_projection_gemini_thought_part_still_projects() { + let c = seen( + "gemini", + json!({ + "contents": [ + {"role": "model", "parts": [ + {"text": "the answer", "thought": false}, + {"text": "reasoning about it", "thought": true, "thoughtSignature": "sig"} + ]} + ] + }), + ) + .await; + assert_eq!(prompt(&c).1[0].1, "the answer\nreasoning about it"); +} + +#[tokio::test] +async fn total_text_chars_counts_reasoning_text() { + let c = seen( + "anthropic", + assistant_turn( + "anthropic", + json!({"type": "thinking", "thinking": "12345"}), + ), + ) + .await; + assert_eq!(c.total_chars, 5); + let c = seen( + "bedrock", + assistant_turn( + "bedrock", + json!({"reasoningContent": {"reasoningText": {"text": "1234567"}}}), + ), + ) + .await; + assert_eq!(c.total_chars, 7); + let c = seen( + "responses", + assistant_turn( + "responses", + json!({"type": "reasoning", "summary": [{"type": "summary_text", "text": "123"}]}), + ), + ) + .await; + assert_eq!(c.total_chars, 3); +} + +#[tokio::test] +async fn size_signal_and_projection_agree_on_reasoning() { + let c = seen( + "anthropic", + json!({ + "messages": [ + {"role": "assistant", "content": [ + {"type": "text", "text": "visible"}, + {"type": "thinking", "thinking": "hidden reasoning"} + ]}, + {"role": "assistant", "content": [ + {"type": "redacted_thinking", "data": "opaque"} + ]} + ] + }), + ) + .await; + let projected: usize = prompt(&c).1.iter().map(|(_, t)| t.chars().count()).sum(); + // One separator newline per block boundary within a turn; the size signal counts text only. + assert_eq!(projected, c.total_chars + 1); +} + +#[tokio::test] +async fn every_known_protocol_has_a_declared_reasoning_wire_shape() { + // (dialect, a reasoning block in its own shape, the text it must show, or the marker). + let rows: Vec<(&str, Option, Option<&str>)> = vec![ + ( + "anthropic", + Some(json!({"type": "thinking", "thinking": "W"})), + Some("W"), + ), + ( + "anthropic", + Some(json!({"type": "redacted_thinking", "data": "b64"})), + None, + ), + ( + "bedrock", + Some(json!({"reasoningContent": {"reasoningText": {"text": "W"}}})), + Some("W"), + ), + ( + "bedrock", + Some(json!({"reasoningContent": {"redactedContent": "b64"}})), + None, + ), + ( + "responses", + Some(json!({"type": "reasoning", "summary": [{"type": "summary_text", "text": "W"}]})), + Some("W"), + ), + ( + "responses", + Some(json!({"type": "reasoning", "encrypted_content": "OPAQUE_BLOB"})), + None, + ), + // No dialect-specific reasoning shape: the generic text probe covers it. + ("gemini", None, None), + ("openai", None, None), + ("cohere", None, None), + ]; + let dialects: Vec<&str> = crate::plane::dialect::DIALECTS + .iter() + .map(|d| d.name) + .collect(); + for d in &dialects { + assert!( + rows.iter().any(|(p, _, _)| p == d), + "dialect '{d}' has no row in this witness table — declare its reasoning wire shape" + ); + } + assert_eq!( + rows.iter() + .map(|(p, _, _)| *p) + .collect::>() + .len(), + dialects.len(), + "witness table covers a different dialect set than the plane's" + ); + for (dialect, sample, text) in rows { + let Some(sample) = sample else { continue }; + let c = seen(dialect, assistant_turn(dialect, sample)).await; + let shown = prompt(&c).1; + let turn = &shown.last().expect("the reasoning turn is shown").1; + match text { + Some(needle) => assert!(turn.contains(needle), "{dialect}: {turn:?}"), + None => assert_eq!(turn, MARKER, "{dialect}"), + } + } +} + +#[tokio::test] +async fn apply_rewrite_to_body_echoes_redacted_marker_as_visible_text() { + let body = json!({ + "model": "m0", + "max_tokens": 16, + "messages": [ + {"role": "assistant", "content": [ + {"type": "redacted_thinking", "data": "OPAQUE_CIPHERTEXT_BYTES"} + ]} + ] + }); + let c = seen("anthropic", body.clone()).await; + assert_eq!(prompt(&c).1[0].1, MARKER); + + // A hook that echoes exactly what it was projected (the common "pass through" rewrite shape). + let echo = std::sync::Arc::new(crate::policies::RewritingGate(vec![json!({ + "role": "assistant", + "content": MARKER, + })])); + let rig = Rig::new( + Pool { + name: "p", + members: vec![member("m0", "m0")], + }, + None, + Hooks { + rewrites: vec![(std::time::Duration::from_millis(500), echo)], + ..Hooks::default() + }, + ); + let answer = rig.fire(&body).await; + let sent: Value = serde_json::from_slice( + &answer + .sent + .last() + .expect("the rewritten request was dispatched") + .body, + ) + .expect("the far end's body is JSON"); + let msgs = sent["messages"].as_array().expect("messages array"); + assert_eq!(msgs.len(), 1); + assert_eq!(msgs[0]["content"].as_str(), Some(MARKER)); + assert!(!sent.to_string().contains("OPAQUE_CIPHERTEXT_BYTES")); +} diff --git a/crates/busbar/tests/hook_parity_driver/rig.rs b/crates/busbar/tests/hook_parity_driver/rig.rs new file mode 100644 index 0000000000..6405307fbb --- /dev/null +++ b/crates/busbar/tests/hook_parity_driver/rig.rs @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE RIG the hook parity suite runs on: the REAL llm plane door (linked, loaded through the one +//! loader and driven through the one dispatcher), the kernel's plane driver with the hook stage +//! bound ([`BoundHooks`]), the kernel's passing steps, a far end that walks the configured pools +//! the way the egress walk does (its candidates, the hooks' constraint, a fallback pool holding the +//! restricts), and a caller that records what it was answered. No fixture plugin: the plane is the +//! real one, the hooks are the 1.5.5 tests' own in-process policies. + +use std::collections::{HashMap, VecDeque}; +use std::future::Future; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use busbar_contract::abi::mechanism::call::{Blob, Outcome as AbiOutcome, BLOB_OCTETS}; +use busbar_contract::abi::mechanism::lifecycle::{OpenIn, OpenOut}; +use busbar_contract::abi::plane::{PlaneOpenIn, PlaneOpenOut, UnitCount}; +use busbar_contract::abi::transport::STATUS_SUCCESS; +use busbar_contract::caps::{Canary, Outcome, Pass, ReasonCode, Route, StepName}; +use busbar_contract::hooks::{BudgetBucketState, RoutingPolicy}; +use busbar_kernel::hooks::{RequestedSignals, ResolvedPolicy}; +use busbar_kernel::plane_driver::{ + refusal_status, Arrival, BoundHooks, BufferCaps, CallerEnd, CallerFacts, CallerKey, CancelBill, + CandidateFacts, Candidates, Checkpoint, Constraint, DriverConfig, FarEnd, FarPiece, MoneySeam, + OutboundRequest, Pick, PlaneDriver, StageTaps, +}; +use busbar_kernel::slice::{ConcurrencyGauge, LeaseCell}; +use busbar_kernel::teller::{run_unit_async, AccrualMeter, Ended, Kernel, Run, UnitCtx}; +use busbar_plugin_loader::dispatch::{ + in_head, kinds::plane::Plane, load_linked, out_head, plane_calls::PlaneInstance, Bind, + DispatchConfig, Dispatcher, Frame, LinkedRow, NoSink, Plugin, +}; +use serde_json::{json, Value}; + +use super::common::{cell, ctx, TestUnits}; + +// ── the deployment ─────────────────────────────────────────────────────────────────────────────── + +/// One member of a pool: the model it serves (a key of `models:`), the model its far end names in +/// its answer (what tells a test WHICH member served), its tags, and whether it is down. +#[derive(Clone, Debug)] +pub struct Member { + pub model: &'static str, + pub label: &'static str, + pub provider: &'static str, + pub tags: Vec, + pub dead: bool, +} + +/// A member served by the anthropic far end. +pub fn member(model: &'static str, label: &'static str) -> Member { + Member { + model, + label, + provider: "ant", + tags: Vec::new(), + dead: false, + } +} + +impl Member { + pub fn tags(mut self, tags: &[&str]) -> Self { + self.tags = tags.iter().map(|t| t.to_string()).collect(); + self + } + pub fn dead(mut self) -> Self { + self.dead = true; + self + } + pub fn provider(mut self, p: &'static str) -> Self { + self.provider = p; + self + } +} + +/// A pool and its members. +#[derive(Clone, Debug)] +pub struct Pool { + pub name: &'static str, + pub members: Vec, +} + +/// The plane's settings for these pools: one far end per protocol, every member a model. +fn settings(pools: &[&Pool]) -> Vec { + let mut models = serde_json::Map::new(); + let mut sections = serde_json::Map::new(); + for p in pools { + let names: Vec = p.members.iter().map(|m| json!(m.model)).collect(); + sections.insert(p.name.to_string(), json!({ "members": names })); + for m in &p.members { + models.insert(m.model.to_string(), json!({ "provider": m.provider })); + } + } + serde_json::to_vec(&json!({ + "providers": { + "ant": { "protocol": "anthropic", "base_url": "https://anthropic.example" }, + "oai": { "protocol": "openai", "base_url": "https://openai.example" }, + "brk": { "protocol": "bedrock", "base_url": "https://bedrock.example" }, + "goo": { "protocol": "gemini", "base_url": "https://gemini.example" }, + "coh": { "protocol": "cohere", "base_url": "https://cohere.example" }, + }, + "models": models, + "pools": sections, + })) + .expect("settings serialize") +} + +// ── the far end ───────────────────────────────────────────────────────────────────────────────── + +/// What a far end answers one attempt. +fn answer_of(m: &Member) -> Vec { + let body = match m.provider { + "oai" => json!({ + "id": "chatcmpl-1", "object": "chat.completion", "model": m.label, + "choices": [{"index": 0, "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop"}], + "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2} + }), + _ => json!({ + "id": "msg_1", "type": "message", "role": "assistant", "model": m.label, + "content": [{"type": "text", "text": "hi"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 1, "output_tokens": 1} + }), + }; + vec![FarPiece { + bytes: serde_json::to_vec(&body).expect("answer serializes"), + status: Some((200, u32::from(STATUS_SUCCESS))), + last: true, + head: vec![(b"content-type".to_vec(), b"application/json".to_vec())], + ..FarPiece::default() + }] +} + +/// One step of a walk: a pool's member, or the walk's refusal (its status and words). +type Step = Result<(String, Member), (u32, String)>; +/// What a walk has left. +type Plan = VecDeque; +/// A reply head: the status and the fields. +type Head = (u32, Vec<(Vec, Vec)>); + +/// A WALK over the configured pools: the primary pool's live members in pool order (the hooks' +/// order first, when they set one, restricted to what they kept), then the fallback pool's, its +/// members held to the hooks' restricts (a required restrict nothing satisfies refuses, as 1.5.5 +/// failed closed at the fallback boundary). A dead member fails over before the plane sees it. +pub struct Far { + primary: Pool, + fallback: Option, + constraint: Mutex>, + plan: Mutex>, + tried: Mutex, + sent: Mutex>, + current: Mutex>, +} + +impl Far { + pub fn new(primary: Pool, fallback: Option) -> Self { + Far { + primary, + fallback, + constraint: Mutex::new(None), + plan: Mutex::new(None), + tried: Mutex::new(0), + sent: Mutex::new(Vec::new()), + current: Mutex::new(VecDeque::new()), + } + } + + /// Every request the plane bound for the far end, in order. + pub fn sent(&self) -> Vec { + self.sent.lock().unwrap().clone() + } + + fn plan(&self) -> Plan { + let c = self.constraint.lock().unwrap().clone().unwrap_or_default(); + let live: Vec = (0..self.primary.members.len()) + .filter(|i| c.keep.as_ref().is_none_or(|k| k.contains(i))) + .collect(); + let mut order: Vec = c + .order + .clone() + .unwrap_or_default() + .into_iter() + .filter(|i| live.contains(i)) + .collect(); + for i in &live { + if !order.contains(i) { + order.push(*i); + } + } + let mut plan: VecDeque<_> = order + .into_iter() + .map(|i| { + Ok(( + self.primary.name.to_string(), + self.primary.members[i].clone(), + )) + }) + .collect(); + if let Some(fb) = &self.fallback { + let tagged = fb + .members + .iter() + .enumerate() + .map(|(i, m)| (i, m.tags.as_slice())); + match c.enforce(tagged) { + Ok(kept) => plan.extend( + kept.into_iter() + .map(|i| Ok((fb.name.to_string(), fb.members[i].clone()))), + ), + Err(_) => plan.push_back(Err(( + 503, + "No upstream satisfies a required gate's restriction. Please retry shortly." + .to_string(), + ))), + } + } + plan + } +} + +fn candidate(i: usize, m: &Member) -> CandidateFacts { + CandidateFacts { + idx: i, + model: m.model.to_string(), + provider: m.provider.to_string(), + weight: 1, + tags: m.tags.clone(), + ..CandidateFacts::default() + } +} + +impl FarEnd for Far { + fn member<'a>( + &'a self, + _: &'a Pass, + _attempt_no: u32, + ) -> impl Future + Send + 'a { + let pick = { + let mut plan = self.plan.lock().unwrap(); + let plan = plan.get_or_insert_with(|| self.plan()); + match plan.pop_front() { + Some(Ok((pool, m))) => { + *self.tried.lock().unwrap() += 1; + Pick::Member { + name: m.model.to_string(), + pool, + passthrough: false, + provider: m.provider.to_string(), + } + } + Some(Err((status, text))) => Pick::Vetoed { status, text }, + None => Pick::Exhausted { + status: 503, + retry_after: None, + detail: "The service is temporarily overloaded. Please retry shortly.", + }, + } + }; + async move { pick } + } + + fn send<'a>( + &'a self, + _: &'a Pass, + request: OutboundRequest, + ) -> impl Future + Send + 'a { + let m = self + .primary + .members + .iter() + .chain(self.fallback.iter().flat_map(|f| f.members.iter())) + .find(|m| m.model == request.member) + .cloned(); + self.sent.lock().unwrap().push(request); + let ok = match m { + Some(m) if !m.dead => { + *self.current.lock().unwrap() = answer_of(&m).into(); + true + } + _ => false, + }; + async move { ok } + } + + fn next<'a>( + &'a self, + _: &'a Pass, + ) -> impl Future> + Send + 'a { + let piece = self.current.lock().unwrap().pop_front(); + async move { piece } + } + + fn remaining(&self, _: &Pass) -> Option { + self.plan.lock().unwrap().as_ref().map(VecDeque::len) + } + + fn failure(&self, _: &Pass) -> Option<&'static str> { + Some("connect") + } + + fn candidates(&self, _: &Pass) -> Option { + Some(Candidates { + pool: self.primary.name.to_string(), + members: self + .primary + .members + .iter() + .enumerate() + .map(|(i, m)| candidate(i, m)) + .collect(), + }) + } + + fn constrain(&self, _: &Pass, constraint: Constraint) { + *self.constraint.lock().unwrap() = Some(constraint); + } +} + +// ── the caller and the books ───────────────────────────────────────────────────────────────────── + +/// What the caller was answered. +#[derive(Default)] +pub struct Caller { + head: Mutex>, + bytes: Mutex>, +} + +impl CallerEnd for Caller { + fn head(&self, status: u32, fields: Vec<(Vec, Vec)>) { + *self.head.lock().unwrap() = Some((status, fields)); + } + + async fn write(&self, bytes: &[u8]) -> bool { + self.bytes.lock().unwrap().extend_from_slice(bytes); + true + } + + async fn write_text(&self, bytes: &[u8]) -> bool { + self.write(bytes).await + } +} + +/// The money seam: nothing to settle in a hook test. +#[derive(Default)] +pub struct Book; + +impl MoneySeam for Book { + fn checkpoint(&self, _: &UnitCtx, _: &[UnitCount]) -> Checkpoint { + Checkpoint::Continue + } + fn cancelled(&self, _: &UnitCtx, _: &CancelBill) {} + fn abandoned(&self, _: &UnitCtx, _: Ended) {} + fn served(&self, _: &UnitCtx, _: &str, _: &str) {} +} + +// ── the hooks ──────────────────────────────────────────────────────────────────────────────────── + +/// What the hooks may know of the caller: its key by principal, its group, nothing else; a hook +/// handed the prompt leaves its access amendment on the kernel's node journal. +#[derive(Default)] +pub struct Callers { + pub keys: HashMap, + /// `principal -> the groups it is in` (self and ancestors). + pub groups: HashMap>, +} + +impl CallerFacts for Callers { + fn key(&self, principal: &str) -> Option { + self.keys.get(principal).cloned() + } + fn in_groups(&self, principal: Option<&str>, groups: &[String]) -> bool { + if groups.is_empty() { + return true; + } + let Some(mine) = principal.and_then(|p| self.groups.get(p)) else { + return false; + }; + groups.iter().any(|g| mine.contains(g)) + } + fn rate_headroom(&self, _: &str, _: &str) -> Option { + None + } + fn budget(&self, _: &str) -> Vec { + Vec::new() + } +} + +/// The deployment's hooks, as a test states them. +#[derive(Default)] +pub struct Hooks { + pub rewrites: Vec<(Duration, Arc)>, + pub gates: Vec<(u16, ResolvedPolicy)>, + pub pool_rewrites: Vec<(Duration, Arc)>, + pub pool_gates: Vec<(u16, ResolvedPolicy)>, + pub policy: Option, + pub taps: StageTaps, + pub callers: Callers, +} + +/// The process's one request-id counter, as the binder is handed it. +pub fn counter() -> Arc u64 + Send + Sync> { + static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); + Arc::new(|| NEXT.fetch_add(1, std::sync::atomic::Ordering::SeqCst)) +} + +impl Hooks { + fn bound(self, pool: &str, dialects: Vec) -> BoundHooks { + let mut gates = self.gates; + gates.sort_by_key(|(p, _)| *p); + BoundHooks { + rewrites: self.rewrites, + gates, + pool_rewrites: std::iter::once((pool.to_string(), self.pool_rewrites)).collect(), + pool_gates: std::iter::once((pool.to_string(), self.pool_gates)).collect(), + pool_policies: self + .policy + .map(|p| (pool.to_string(), p)) + .into_iter() + .collect(), + taps: self.taps, + requested: RequestedSignals::default(), + next_request_id: counter(), + caller: Arc::new(self.callers), + dialects, + } + } +} + +// ── one unit ───────────────────────────────────────────────────────────────────────────────────── + +fn octets(b: &[u8]) -> Blob { + Blob { + ptr: b.as_ptr(), + len: b.len(), + fmt: BLOB_OCTETS, + flags: 0, + } +} + +/// The llm door, linked, opened over `settings`. +fn door(dispatcher: &Dispatcher, settings: &[u8]) -> Plugin { + let row = + LinkedRow::of(crate::plane::plane_door::door).expect("the plane door states its Statement"); + let plugin = load_linked::( + &row, + Bind { + instance: Arc::from("the-plane"), + max_inflight_cap: 64, + sink: Arc::new(NoSink), + dispatcher: dispatcher.adopter(), + conns: None, + }, + ) + .expect("the plane door loads"); + let mut open = Frame::new( + PlaneOpenIn { + open: OpenIn { + head: in_head(), + host: std::ptr::null(), + settings: octets(settings), + secrets: std::ptr::null(), + secrets_len: 0, + generation: 1, + err_buf: std::ptr::null_mut(), + err_cap: 0, + }, + public_url: busbar_contract::abi::mechanism::call::AbiStr { + ptr: std::ptr::null(), + len: 0, + }, + }, + PlaneOpenOut { + open: OpenOut { + head: out_head(), + instance: std::ptr::null_mut(), + err_len: 0, + }, + snapshot: std::ptr::null(), + }, + ); + let (called, _) = plugin.open(&mut open); + assert_eq!(called.outcome, AbiOutcome::Ready, "the plane door opens"); + plugin +} + +/// What one unit came to. +pub struct Answered { + pub status: u32, + pub fields: Vec<(Vec, Vec)>, + pub body: Vec, + pub sent: Vec, + pub outcome: Outcome, +} + +impl Answered { + /// The answer's body as JSON. + pub fn json(&self) -> Value { + serde_json::from_slice(&self.body).unwrap_or(Value::Null) + } + /// The model the answer names: which member served. + pub fn model(&self) -> String { + self.json() + .get("model") + .and_then(Value::as_str) + .unwrap_or_default() + .to_string() + } + /// The answer's body as text. + pub fn text(&self) -> String { + String::from_utf8_lossy(&self.body).into_owned() + } +} + +/// THE RIG: one deployment (its pools and its hooks), ready to drive units. +pub struct Rig { + driver: PlaneDriver, + primary: Pool, + fallback: Option, + _plugin: Plugin, +} + +impl Rig { + pub fn new(primary: Pool, fallback: Option, hooks: Hooks) -> Self { + let dispatcher = Arc::new(Dispatcher::new(DispatchConfig { + workers: 2, + ..DispatchConfig::default() + })); + let mut pools = vec![&primary]; + if let Some(f) = &fallback { + pools.push(f); + } + let plugin = door(&dispatcher, &settings(&pools)); + let calls = Arc::new(PlaneInstance::new(plugin.clone(), dispatcher, 1)); + let refusal_statuses = calls.refusal_statuses(); + let dialects: Vec = crate::plane::dialect::DIALECTS + .iter() + .map(|d| d.name.to_string()) + .collect(); + let op_classes = + ::OP_CLASSES.to_vec(); + let driver = PlaneDriver::new( + calls, + DriverConfig { + caps: BufferCaps::default(), + op_classes, + status_of: refusal_status, + refusal_statuses, + caller_refs: None, + }, + Arc::new(Book), + Arc::new(busbar_kernel::host_services::KernelServices::new()), + ("pools", &serde_yaml::Value::Null), + ) + .expect("the plane instance is admitted") + .with_hooks(Arc::new(hooks.bound(primary.name, dialects))); + Rig { + driver, + primary, + fallback, + _plugin: plugin, + } + } + + /// One unit: `body` posted to `target` with `fields`, the kernel steps `steps`. + pub async fn fire_with( + &self, + steps: &TestUnits, + target: &str, + fields: &[(&str, &str)], + body: &[u8], + ) -> Answered { + let far = Far::new(self.primary.clone(), self.fallback.clone()); + let caller = Caller::default(); + let arrival = Arrival { + claim: 0, + method: b"POST".to_vec(), + target: target.as_bytes().to_vec(), + fields: fields + .iter() + .map(|(n, v)| (n.as_bytes().to_vec(), v.as_bytes().to_vec())) + .collect(), + body: Arc::from(body), + }; + let units = self.driver.unit(steps, &far, &caller, arrival, 0); + let outcome = drive(&units).await; + let rendered = units.take_rendered(); + let (status, fields, body) = match rendered { + Some(r) => (r.status, r.fields, r.body), + None => { + let head = caller.head.lock().unwrap().clone().unwrap_or_default(); + (head.0, head.1, caller.bytes.lock().unwrap().clone()) + } + }; + Answered { + status, + fields, + body, + sent: far.sent(), + outcome, + } + } + + /// One Anthropic Messages unit with every kernel step passing. + pub async fn fire(&self, body: &Value) -> Answered { + self.fire_with( + &TestUnits::passing(), + "/v1/messages", + &[ + ("content-type", "application/json"), + ("anthropic-version", "2023-06-01"), + ], + &serde_json::to_vec(body).expect("body serializes"), + ) + .await + } + + /// One unit refused at authentication. + pub async fn fire_unauthenticated(&self, target: &str, body: &[u8]) -> Answered { + self.fire_with( + &TestUnits::refusing(StepName::Authenticate, ReasonCode::Unauthenticated), + target, + &[("content-type", "application/json")], + body, + ) + .await + } +} + +/// Run one unit through the one loop; its outcome. +async fn drive(units: &busbar_kernel::plane_driver::PlaneUnits<'_, S, F, C>) -> Outcome +where + S: busbar_kernel::plane_driver::DriverSteps + Sync, + F: FarEnd, + C: CallerEnd, +{ + let kernel = Kernel::new(); + let (gauge, canary, leases, meter) = ( + ConcurrencyGauge::new(), + Canary::new(), + LeaseCell::new(), + AccrualMeter::new(), + ); + let cell = cell(&kernel); + let run = Run { + cell: &cell, + parent: None, + leases: &leases, + gauge: &gauge, + canary: &canary, + meter: &meter, + }; + match run_unit_async(&kernel, units, &ctx(7), run, units).await { + Ended::Settled { end, .. } => end.outcome(), + Ended::AlreadySettled => panic!("nothing else holds this unit's cell"), + } +} diff --git a/crates/busbar/tests/hook_parity_driver/seam.rs b/crates/busbar/tests/hook_parity_driver/seam.rs new file mode 100644 index 0000000000..82edc5fa3e --- /dev/null +++ b/crates/busbar/tests/hook_parity_driver/seam.rs @@ -0,0 +1,1010 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! `v1.5.5` `crates/busbar/src/proxy/tests/hook_seam_tests.rs`, on the driver: the opt-in grants +//! (what a hook is shown), the decision gates and the base policy (reject, restrict, order and +//! their reconcile), the on-error chain, the rewrite chain, the stage taps, the reject envelopes +//! and the correlation id. Each test keeps its 1.5.5 name and the values 1.5.5 asserted. + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use busbar_kernel::config::PolicyOnError; +use busbar_kernel::hooks::{FallbackHook, ResolvedPolicy}; +use busbar_kernel::plane_driver::{CallerKey, Constraint, Restrict}; +use serde_json::{json, Value}; + +use crate::policies::{ + canned_gate, capturing, rewriting, wait_for_tap_body, webhook_tap, Canned, CannedGate, + ErroringPolicy, +}; +use crate::rig::{member, Callers, Hooks, Pool, Rig}; + +/// The principal the kernel's passing steps verify. +const PRINCIPAL: &str = "acct:battery"; + +fn pool(members: Vec) -> Pool { + Pool { name: "p", members } +} + +/// One live member. +fn one() -> Pool { + pool(vec![member("m0", "m0")]) +} + +/// One dead member. +fn one_dead() -> Pool { + pool(vec![member("m0", "m0").dead()]) +} + +fn body() -> Value { + // The end-user id spelled the way this dialect spells it (`metadata.user_id` on Anthropic), + // and the `user` key beside it, as a client may send both. + json!({ + "model": "m0", + "max_tokens": 16, + "system": "sys prompt", + "user": "alice", + "metadata": {"user_id": "alice"}, + "messages": [{"role": "user", "content": "hello"}] + }) +} + +fn chat_body() -> Value { + json!({ + "model": "p", + "messages": [{"role": "user", "content": "hi"}], + "max_tokens": 10 + }) +} + +/// The previous release's `run`: a one-member pool whose base route policy is a capturing +/// policy with these grants and this decision, one unit of `v`; what it saw and the answer. +async fn run( + send_prompt: bool, + send_user: bool, + reject: Option<(u16, String)>, + v: Value, +) -> (crate::rig::Answered, Option) { + let (seen, policy) = capturing(send_prompt, send_user, reject); + let rig = Rig::new( + one(), + None, + Hooks { + policy: Some(policy), + ..Hooks::default() + }, + ); + let answer = rig.fire(&v).await; + let captured = seen.lock().unwrap().clone(); + (answer, captured) +} + +fn gates(gates: Vec<(u16, ResolvedPolicy)>) -> Hooks { + Hooks { + gates, + ..Hooks::default() + } +} + +#[tokio::test] +async fn default_flags_project_nothing() { + let (answer, captured) = run(false, false, None, body()).await; + let captured = captured.expect("policy must have been called"); + assert!(captured.prompt.is_none(), "send_prompt off ⇒ no prompt"); + assert!(captured.identity.is_none(), "send_user off ⇒ no identity"); + assert_eq!(answer.status, 200, "abstain ⇒ the walk's own pick serves"); +} + +#[tokio::test] +async fn global_gate_reject_short_circuits_the_request() { + let (_, gate) = capturing(false, false, Some((451, "blocked by global policy".into()))); + let rig = Rig::new(one_dead(), None, gates(vec![(0, gate)])); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 451, + "a global reject gate must short-circuit with its clamped status, before any dispatch" + ); + assert!(answer.sent.is_empty(), "no far end was reached"); +} + +#[tokio::test] +async fn global_gate_abstain_does_not_reject() { + let (_, gate) = capturing(false, false, None); + let rig = Rig::new(one_dead(), None, gates(vec![(0, gate)])); + let answer = rig.fire(&chat_body()).await; + assert_ne!( + answer.status, 451, + "an abstaining global gate must not reject the request" + ); + assert_eq!(answer.sent.len(), 1, "the request proceeded to the walk"); +} + +#[test] +fn enforce_restricts_reapplies_compliance_tags_across_pools() { + // Fallback pool "fb": member 0 carries `baa`, member 1 carries nothing. + let tags: Vec> = vec![vec!["baa".to_string()], vec![]]; + let members = || tags.iter().enumerate().map(|(i, t)| (i, t.as_slice())); + let restrict = |tag: &str, on_empty: PolicyOnError, name: &'static str| Constraint { + restricts: vec![Restrict { + tags_any: vec![tag.to_string()], + on_empty, + name, + }], + ..Constraint::default() + }; + + // A required `baa` restrict narrows the fallback pool to ONLY the tagged member. + assert_eq!( + restrict("baa", PolicyOnError::Reject, "baa-gate") + .enforce(members()) + .unwrap(), + vec![0], + "only the baa-tagged fallback lane may survive a required restrict" + ); + // A required restrict with NO matching fallback member fails CLOSED, never spills. + assert!( + restrict("hipaa", PolicyOnError::Reject, "hipaa-gate") + .enforce(members()) + .is_err(), + "a required restrict with no eligible fallback lane must fail closed, not spill" + ); + // A `weighted` restrict with no match is an advisory escape — candidates pass unchanged. + assert_eq!( + restrict("hipaa", PolicyOnError::Weighted, "hipaa-advisory") + .enforce(members()) + .unwrap() + .len(), + 2, + "a weighted restrict with no eligible lane escapes (candidates unchanged)" + ); + // No active restrict → identity. + assert_eq!(Constraint::default().enforce(members()).unwrap().len(), 2); +} + +#[tokio::test] +async fn base_policy_restrict_persists_across_fallback_pool_hop() { + let primary = pool(vec![member("primary", "primary").tags(&["baa"]).dead()]); + let fallback = Pool { + name: "fb", + members: vec![member("fbmember", "fbmember")], + }; + let rig = Rig::new( + primary, + Some(fallback), + Hooks { + policy: Some(canned_gate( + Canned::Restrict(vec!["baa".to_string()]), + "compliance", + )), + ..Hooks::default() + }, + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 503, + "a base-policy compliance restrict must fail closed at the fallback boundary" + ); + assert!( + answer.text().contains("restriction"), + "the 503 must be the compliance fail-closed, not a generic transport 503: {}", + answer.text() + ); + assert!( + answer.sent.iter().all(|r| r.member != "fbmember"), + "the ineligible fallback member is never served" + ); +} + +#[tokio::test] +async fn completion_tap_fires_synthetic_rejected_by_auth() { + let (cap, tap) = webhook_tap(); + let mut hooks = Hooks::default(); + hooks.taps.response = vec![tap]; + let rig = Rig::new(one_dead(), None, hooks); + let answer = rig + .fire_unauthenticated("/v1/messages", &serde_json::to_vec(&chat_body()).unwrap()) + .await; + assert_eq!(answer.status, 401); + let payload = wait_for_tap_body(&cap).await; + assert_eq!(payload["stage"]["at"], "response"); + assert_eq!(payload["stage"]["outcome"], "rejected_by_auth"); + assert_eq!(payload["stage"]["status"], 401); +} + +#[tokio::test] +async fn completion_tap_status_is_protocol_native_gemini_400() { + let (cap, tap) = webhook_tap(); + let mut hooks = Hooks::default(); + hooks.taps.response = vec![tap]; + let rig = Rig::new(one_dead(), None, hooks); + // Gemini ingress path + bad key → the served response is HTTP 400 (not 401). + let answer = rig + .fire_unauthenticated( + "/v1beta/models/gemini-x:generateContent", + br#"{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}"#, + ) + .await; + assert_eq!(answer.status, 400, "gemini bad-key is native 400"); + let payload = wait_for_tap_body(&cap).await; + assert_eq!(payload["stage"]["outcome"], "rejected_by_auth"); + assert_eq!( + payload["stage"]["status"], 400, + "the tap status must match the client-visible native status, not a hardcoded 401" + ); +} + +#[tokio::test] +async fn completion_tap_fires_synthetic_rejected_by_gate() { + let (cap, tap) = webhook_tap(); + let mut hooks = gates(vec![( + 0, + canned_gate(Canned::Reject(451, "denied"), "denier"), + )]); + hooks.taps.response = vec![tap]; + let rig = Rig::new(one_dead(), None, hooks); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 451); + let payload = wait_for_tap_body(&cap).await; + assert_eq!(payload["stage"]["at"], "response"); + assert_eq!(payload["stage"]["outcome"], "rejected_by_gate"); + assert_eq!(payload["stage"]["status"], 451); +} + +#[tokio::test] +async fn completion_tap_reports_ok_outcome() { + let (cap, tap) = webhook_tap(); + let mut hooks = Hooks::default(); + hooks.taps.response = vec![tap]; + let rig = Rig::new(pool(vec![member("m0", "served")]), None, hooks); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + let payload = wait_for_tap_body(&cap).await; + assert_eq!(payload["stage"]["at"], "response"); + assert_eq!(payload["stage"]["outcome"], "ok"); + assert_eq!(payload["stage"]["status"], 200); +} + +#[tokio::test] +async fn attempt_tap_carries_attempt_story() { + let (cap, tap) = webhook_tap(); + let mut hooks = Hooks::default(); + hooks.taps.routing = vec![tap]; + let rig = Rig::new(pool(vec![member("m0", "served")]), None, hooks); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + let payload = wait_for_tap_body(&cap).await; + assert_eq!(payload["stage"]["at"], "routing"); + assert_eq!(payload["stage"]["attempt_number"], 1); + assert_eq!(payload["stage"]["model"], "m0"); + assert!( + payload["stage"].get("previous_failure").is_none(), + "no failure precedes the first attempt" + ); +} + +#[tokio::test] +async fn route_tap_reports_surviving_candidates() { + let (cap, tap) = webhook_tap(); + let mut hooks = Hooks::default(); + hooks.taps.candidate = vec![tap]; + let rig = Rig::new(pool(vec![member("m0", "served")]), None, hooks); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + let payload = wait_for_tap_body(&cap).await; + assert_eq!(payload["stage"]["at"], "candidate"); + assert_eq!(payload["stage"]["remaining_candidates"], 1); +} + +/// The body the far end was sent. +fn sent_body(answer: &crate::rig::Answered) -> Value { + let last = answer + .sent + .last() + .expect("upstream must have been dispatched"); + serde_json::from_slice(&last.body).expect("the far end's body is JSON") +} + +#[tokio::test] +async fn same_protocol_passthrough_carries_global_rewrite() { + let rig = Rig::new( + one(), + None, + Hooks { + rewrites: vec![rewriting("COMPRESSED")], + ..Hooks::default() + }, + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + assert_eq!( + sent_body(&answer)["messages"][0]["content"], + "COMPRESSED", + "the upstream must see the REWRITTEN body on the same-protocol fast path" + ); +} + +#[tokio::test] +async fn pool_scoped_rw_gate_rewrites_the_body() { + let rig = Rig::new( + one(), + None, + Hooks { + pool_rewrites: vec![rewriting("POOL-COMPRESSED")], + ..Hooks::default() + }, + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + assert_eq!( + sent_body(&answer)["messages"][0]["content"], + "POOL-COMPRESSED", + "a pool-scoped rw gate must rewrite the dispatched body" + ); +} + +#[tokio::test] +async fn on_error_fallback_hook_fires_and_decides() { + let gate = ResolvedPolicy::Policy { + policy: Arc::new(ErroringPolicy), + on_error: PolicyOnError::Weighted, + on_error_chain: vec![FallbackHook { + policy: Arc::new(CannedGate { + canned: Canned::Reject(451, "fallback says no"), + name: "backup", + }), + timeout: Duration::from_millis(500), + send_prompt: false, + send_user: false, + on_empty: PolicyOnError::Reject, + }], + timeout: Duration::from_millis(50), + send_prompt: false, + send_user: false, + on_empty: PolicyOnError::Reject, + }; + let rig = Rig::new(one_dead(), None, gates(vec![(0, gate)])); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 451, + "the failed gate's fallback hook must fire and its reject must be honored" + ); +} + +#[tokio::test] +async fn on_error_chain_exhausted_applies_terminal() { + let gate = ResolvedPolicy::Policy { + policy: Arc::new(ErroringPolicy), + on_error: PolicyOnError::Reject, + on_error_chain: vec![FallbackHook { + policy: Arc::new(ErroringPolicy), + timeout: Duration::from_millis(50), + send_prompt: false, + send_user: false, + on_empty: PolicyOnError::Reject, + }], + timeout: Duration::from_millis(50), + send_prompt: false, + send_user: false, + on_empty: PolicyOnError::Reject, + }; + let rig = Rig::new(one_dead(), None, gates(vec![(0, gate)])); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 503, + "an exhausted chain must land on the fail-closed reject terminal" + ); + assert!(answer.sent.is_empty(), "refused before any dispatch"); +} + +#[tokio::test] +async fn on_error_reject_terminal_short_circuits_before_a_live_lane_ever_dispatches() { + let gate = ResolvedPolicy::Policy { + policy: Arc::new(ErroringPolicy), + on_error: PolicyOnError::Reject, + on_error_chain: vec![], + timeout: Duration::from_millis(50), + send_prompt: false, + send_user: false, + on_empty: PolicyOnError::Reject, + }; + let rig = Rig::new( + pool(vec![member("m0", "live-lane")]), + None, + gates(vec![(0, gate)]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 503, + "an errored gate terminating in `on_error: reject` must fail closed BEFORE dispatch — a \ + live, healthy lane must never actually serve this request" + ); + assert!(answer.sent.is_empty()); +} + +#[tokio::test] +async fn global_request_stage_tap_fires_on_a_real_dispatched_request() { + let (cap, tap) = webhook_tap(); + let mut hooks = Hooks::default(); + hooks.taps.request = vec![tap]; + let rig = Rig::new(pool(vec![member("m0", "live-lane")]), None, hooks); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 200, + "the request must still dispatch normally" + ); + let payload = wait_for_tap_body(&cap).await; + assert_eq!( + payload["op"], "notify", + "the global request-stage tap must receive the real notify wire envelope: {payload}" + ); +} + +#[tokio::test] +async fn pool_gate_reject_fires_from_pool_runtime_gates() { + let rig = Rig::new( + one_dead(), + None, + Hooks { + pool_gates: vec![( + 0, + canned_gate(Canned::Reject(451, "pool gate says no"), "pg"), + )], + ..Hooks::default() + }, + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 451, + "a pool gate must fire in the phase-2 reconcile" + ); +} + +#[tokio::test] +async fn reject_priority_tie_break_surfaces_lowest_priority() { + // Deliberately stated out of order: the stable sort must put 1 before 5. + let rig = Rig::new( + one_dead(), + None, + gates(vec![ + ( + 5, + canned_gate(Canned::Reject(451, "high priority number"), "late"), + ), + ( + 1, + canned_gate(Canned::Reject(452, "low priority number"), "early"), + ), + ]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 452, + "the lowest-priority rejecting gate must supply the surfacing status" + ); +} + +#[tokio::test] +async fn multi_restrict_disjoint_intersection_fails_closed() { + let rig = Rig::new( + pool(vec![ + member("eu-lane", "eu-lane").tags(&["eu"]).dead(), + member("baa-lane", "baa-lane").tags(&["baa"]).dead(), + ]), + None, + gates(vec![ + (0, canned_gate(Canned::Restrict(vec!["eu".into()]), "geo")), + ( + 1, + canned_gate(Canned::Restrict(vec!["baa".into()]), "hipaa"), + ), + ]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!( + answer.status, 503, + "disjoint concurrent restricts must intersect to empty and fail closed" + ); +} + +#[tokio::test] +async fn multi_restrict_intersection_dispatches_only_the_survivor() { + let rig = Rig::new( + pool(vec![ + member("eu-only", "eu-only").tags(&["eu"]).dead(), + member("eu-baa", "both").tags(&["eu", "baa"]), + ]), + None, + gates(vec![ + (0, canned_gate(Canned::Restrict(vec!["eu".into()]), "geo")), + ( + 1, + canned_gate(Canned::Restrict(vec!["baa".into()]), "hipaa"), + ), + ]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + assert_eq!( + answer.model(), + "both", + "dispatch must stay inside the restrict intersection" + ); +} + +#[tokio::test] +async fn stale_order_filtered_against_post_restrict_set() { + let rig = Rig::new( + pool(vec![ + member("excluded", "excluded").tags(&["a"]).dead(), + member("kept-lane", "kept").tags(&["b"]), + ]), + None, + gates(vec![ + (0, canned_gate(Canned::Order(vec![0]), "orderer")), + ( + 1, + canned_gate(Canned::Restrict(vec!["b".into()]), "restrictor"), + ), + ]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + assert_eq!( + answer.model(), + "kept", + "a t0 order naming only restricted-out members must abstain to the surviving set" + ); +} + +#[tokio::test] +async fn order_last_in_chain_wins() { + let rig = Rig::new( + pool(vec![member("lane-a", "alpha"), member("lane-b", "beta")]), + None, + gates(vec![ + (0, canned_gate(Canned::Order(vec![0, 1]), "first")), + (1, canned_gate(Canned::Order(vec![1, 0]), "second")), + ]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + assert_eq!( + answer.model(), + "beta", + "the LAST ordering gate in the chain must win the reconcile" + ); +} + +#[tokio::test] +async fn last_order_gate_filtered_to_empty_abstains_to_base_not_to_a_lower_gate() { + let rig = Rig::new( + pool(vec![ + member("base-lane", "base").tags(&["keep"]), + member("low-lane", "low").tags(&["keep"]), + member("excluded-lane", "excluded").dead(), + ]), + None, + gates(vec![ + ( + 0, + canned_gate(Canned::Restrict(vec!["keep".into()]), "restrictor"), + ), + (10, canned_gate(Canned::Order(vec![1]), "low-orderer")), + (20, canned_gate(Canned::Order(vec![2]), "high-orderer")), + ]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + assert_eq!( + answer.model(), + "base", + "a filtered-to-empty highest-priority order must abstain to the pool's base ordering, \ + not fall through to a lower-priority gate's stale order" + ); +} + +#[tokio::test] +async fn global_gate_order_arm_is_honored() { + let rig = Rig::new( + pool(vec![member("lane-a", "alpha"), member("lane-b", "beta")]), + None, + gates(vec![(0, canned_gate(Canned::Order(vec![1]), "prefer-b"))]), + ); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + assert_eq!( + answer.model(), + "beta", + "a global ordering gate must steer dispatch (the ORDER arm is live)" + ); +} + +#[tokio::test] +async fn send_prompt_projects_content_only() { + let (_, captured) = run(true, false, None, body()).await; + let captured = captured.expect("policy must have been called"); + let (system, messages) = captured.prompt.expect("send_prompt on ⇒ prompt present"); + assert_eq!(system.as_deref(), Some("sys prompt")); + assert_eq!(messages, vec![("user".to_string(), "hello".to_string())]); + assert!(captured.identity.is_none(), "send_user off ⇒ no identity"); +} + +#[tokio::test] +async fn send_user_projects_identity_only() { + let (_, captured) = run(false, true, None, body()).await; + let captured = captured.expect("policy must have been called"); + assert!(captured.prompt.is_none(), "send_prompt off ⇒ no prompt"); + let (key_id, key_name, user) = captured.identity.expect("send_user on ⇒ identity present"); + assert_eq!(key_id, None, "no governance ⇒ no key id"); + assert_eq!(key_name, None, "no governance ⇒ no key name"); + assert_eq!(user.as_deref(), Some("alice")); +} + +#[tokio::test] +async fn reject_decision_maps_to_reject_request_outcome() { + let (answer, captured) = run(true, false, Some((451, "PII detected".into())), body()).await; + assert!(captured.is_some(), "the policy decided"); + assert_eq!(answer.status, 451); + assert_eq!(answer.json()["error"]["message"], "PII detected"); + assert!( + answer.sent.is_empty(), + "a rejection is a decision: nothing dispatched" + ); +} + +#[tokio::test] +async fn max_tokens_saturates_not_wraps() { + let v = json!({ + "model": "m0", + "max_tokens": 5_000_000_000u64, + "messages": [{"role": "user", "content": "hi"}] + }); + let (_, captured) = run(false, false, None, v).await; + let captured = captured.expect("policy must have been called"); + // 1.5.5: an absurd caller cap still signals "the largest cap there is". + assert_eq!(captured.max_tokens, Some(u32::MAX)); + assert_ne!( + captured.max_tokens, + Some(5_000_000_000u64 as u32), + "the one thing that must never happen is a wrap to a small number" + ); +} + +/// A rig whose verified caller has `key`, and whose base policy is a `send_user` capture. +async fn identity_seen(key: Option) -> crate::policies::CapturedReq { + let (seen, policy) = capturing(false, true, None); + let mut callers = Callers::default(); + if let Some(key) = key { + callers.keys.insert(PRINCIPAL.to_string(), key); + } + let rig = Rig::new( + one(), + None, + Hooks { + policy: Some(policy), + callers, + ..Hooks::default() + }, + ); + rig.fire(&body()).await; + let captured = seen.lock().unwrap().clone(); + captured.expect("policy called") +} + +#[tokio::test] +async fn send_user_projects_governance_key_identity() { + let secret = "bb_sk_secret-material-never-projected"; + let captured = identity_seen(Some(CallerKey { + id: "vk_123".to_string(), + name: "sales-team".to_string(), + })) + .await; + let (key_id, key_name, user) = captured.identity.expect("identity present"); + assert_eq!(key_id.as_deref(), Some("vk_123")); + assert_eq!(key_name.as_deref(), Some("sales-team")); + assert_eq!(user.as_deref(), Some("alice")); + // The secret NEVER rides the projection, under any configuration. + assert_ne!(key_id.as_deref(), Some(secret)); + assert_ne!(key_name.as_deref(), Some(secret)); +} + +#[tokio::test] +async fn send_user_falls_back_to_synthesized_group_key_identity() { + // The key the identity step resolved for a group/SSO principal: its id/name carry the + // principal. + let captured = identity_seen(Some(CallerKey { + id: "eng-oncall".to_string(), + name: "eng-oncall".to_string(), + })) + .await; + let (key_id, key_name, _user) = captured.identity.expect("identity present"); + assert_eq!( + key_id.as_deref(), + Some("eng-oncall"), + "a group principal's synthesized key id must project, not fall through to None" + ); + assert_eq!(key_name.as_deref(), Some("eng-oncall")); +} + +#[tokio::test] +async fn send_user_prefers_resolved_key_over_disabled_legacy_lookup() { + // The identity a hook sees is the key of the principal the kernel VERIFIED (the authoritative + // admission), never a second lookup of the raw token. + let captured = identity_seen(Some(CallerKey { + id: "synthesized-principal".to_string(), + name: "synthesized-principal".to_string(), + })) + .await; + let (key_id, key_name, _user) = captured.identity.expect("identity present"); + assert_eq!( + key_id.as_deref(), + Some("synthesized-principal"), + "must resolve to the key auth actually authorized (the synthesized key), \ + not the disabled legacy key a raw `lookup` would hit" + ); + assert_eq!(key_name.as_deref(), Some("synthesized-principal")); +} + +#[tokio::test] +async fn forward_with_pool_keyed_threads_group_key_to_pool_policy() { + let captured = identity_seen(Some(CallerKey { + id: "eng-oncall".to_string(), + name: "eng-oncall".to_string(), + })) + .await; + let (key_id, _, _) = captured.identity.expect("identity present"); + assert_eq!( + key_id.as_deref(), + Some("eng-oncall"), + "the resolved key must reach a pool's send_user policy" + ); +} + +#[tokio::test] +async fn reject_status_and_message_resanitized_at_the_seam() { + let (answer, _) = run( + false, + false, + Some((500, "evil\r\ninjected\u{202E}spoof".into())), + body(), + ) + .await; + assert_eq!(answer.status, 403); + assert_eq!( + answer.json()["error"]["message"], + "evilinjectedspoof", + "seam must re-sanitize" + ); +} + +/// One unit refused by a gate rejecting with `status` and `message`, at `target` in `dialect`. +async fn gate_refusal( + status: u16, + message: &'static str, + target: &str, + fields: &[(&str, &str)], + body: &[u8], +) -> crate::rig::Answered { + let rig = Rig::new( + one_dead(), + None, + gates(vec![( + 0, + canned_gate(Canned::Reject(status, message), "guard"), + )]), + ); + rig.fire_with(&crate::common::TestUnits::passing(), target, fields, body) + .await +} + +const ANTHROPIC: &[(&str, &str)] = &[ + ("content-type", "application/json"), + ("anthropic-version", "2023-06-01"), +]; + +#[tokio::test] +async fn reject_408_maps_to_anthropic_timeout_envelope() { + let answer = gate_refusal( + 408, + "guardrail: request deadline", + "/v1/messages", + ANTHROPIC, + &serde_json::to_vec(&chat_body()).unwrap(), + ) + .await; + assert_eq!(answer.status, 408); + let v = answer.json(); + assert_eq!(v["error"]["type"], "timeout_error"); + assert_eq!(v["error"]["message"], "guardrail: request deadline"); +} + +#[tokio::test] +async fn reject_rides_the_full_forward_path() { + let (seen, policy) = capturing(false, false, Some((451, "PII detected".into()))); + let rig = Rig::new( + Pool { + name: "pa", + members: vec![member("m0", "m0")], + }, + None, + Hooks { + policy: Some(policy), + ..Hooks::default() + }, + ); + let answer = rig + .fire(&json!({ + "model": "pa", + "messages": [{"role": "user", "content": "hi"}], + "max_tokens": 5 + })) + .await; + assert_eq!( + answer.status, 451, + "the hook's status must reach the caller" + ); + let v = answer.json(); + assert_eq!(v["type"], "error"); + assert_eq!(v["error"]["type"], "invalid_request_error"); + assert_eq!(v["error"]["message"], "PII detected"); + assert!( + seen.lock().unwrap().is_some(), + "the policy must actually have decided" + ); +} + +#[test] +fn reject_kind_mapping_matches_status_semantics() { + use crate::plane::exchange::refuse::gate_kind; + use busbar_contract::protocol::{ + KIND_AUTHENTICATION, KIND_INVALID_REQUEST, KIND_NOT_FOUND, KIND_PERMISSION, + KIND_RATE_LIMIT, KIND_TIMEOUT, + }; + assert_eq!(gate_kind(401), KIND_AUTHENTICATION); + assert_eq!(gate_kind(403), KIND_PERMISSION); + assert_eq!(gate_kind(404), KIND_NOT_FOUND); + assert_eq!(gate_kind(408), KIND_TIMEOUT); + assert_eq!(gate_kind(429), KIND_RATE_LIMIT); + for other in [400, 422, 451, 499] { + assert_eq!(gate_kind(other), KIND_INVALID_REQUEST); + } +} + +#[tokio::test] +async fn reject_message_reaches_anthropic_error_body() { + let answer = gate_refusal( + 451, + "PII detected in message 3", + "/v1/messages", + ANTHROPIC, + &serde_json::to_vec(&chat_body()).unwrap(), + ) + .await; + assert_eq!(answer.status, 451); + let v = answer.json(); + assert_eq!(v["type"], "error"); + assert_eq!( + v["error"]["message"], "PII detected in message 3", + "the hook's sanitized reject message must reach the client verbatim" + ); +} + +#[tokio::test] +async fn reject_produces_bedrock_native_envelope() { + let answer = gate_refusal( + 429, + "quota guardrail: try later", + "/model/m0/converse", + &[("content-type", "application/json")], + br#"{"messages":[{"role":"user","content":[{"text":"hi"}]}]}"#, + ) + .await; + assert_eq!(answer.status, 429); + assert!( + answer + .fields + .iter() + .any(|(n, _)| n.eq_ignore_ascii_case(b"x-amzn-errortype")), + "Bedrock error envelope must carry x-amzn-errortype" + ); + assert_eq!( + answer.json()["message"], + "quota guardrail: try later", + "Bedrock error body carries the message field" + ); +} + +#[test] +fn request_id_counter_is_unique_and_monotonic_across_sequential_requests() { + // The counter the driver's binder stamps each unit from is the process's one counter + // (`App::next_request_id`). + let app = busbar_kernel::test_support::TestApp::new().build(); + let a = app.next_request_id(); + let b = app.next_request_id(); + let c = app.next_request_id(); + assert!(b > a, "the counter must strictly increase: {a} then {b}"); + assert!(c > b, "the counter must strictly increase: {b} then {c}"); + assert_eq!(b, a + 1, "a plain fetch_add(1): no gaps between requests"); + assert_eq!(c, a + 2); + let app2 = busbar_kernel::test_support::TestApp::new().build(); + let d = app2.next_request_id(); + assert_ne!( + d, a, + "two independently-seeded counters must not coincide (boot entropy seed)" + ); +} + +#[tokio::test] +async fn same_request_id_joins_gate_decision_and_completion_tap() { + let (cap, tap) = webhook_tap(); + let (seen, gate) = capturing(false, false, None); + let mut hooks = gates(vec![(0, gate)]); + hooks.taps.response = vec![tap]; + let rig = Rig::new(pool(vec![member("m0", "served")]), None, hooks); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + let gate_request_id = seen + .lock() + .unwrap() + .as_ref() + .expect("the gate ran and captured the RoutingRequest") + .request_id; + let payload = wait_for_tap_body(&cap).await; + let tap_request_id = payload["request"]["request_id"] + .as_u64() + .expect("response tap payload carries request_id as a plain integer"); + assert_eq!( + gate_request_id, tap_request_id, + "the pre-forward routing decision and the post-response response tap for the SAME \ + request must carry the IDENTICAL request_id — that identity is the join-key contract" + ); +} + +/// A layer capturing the value recorded onto a native `u64` field named `request_id`. +#[derive(Clone, Default)] +struct RequestIdSpanCapture(Arc>>); + +impl tracing_subscriber::Layer for RequestIdSpanCapture { + fn on_record( + &self, + _id: &tracing::span::Id, + values: &tracing::span::Record<'_>, + _ctx: tracing_subscriber::layer::Context<'_, S>, + ) { + struct Vis<'a>(&'a Mutex>); + impl tracing::field::Visit for Vis<'_> { + fn record_u64(&mut self, field: &tracing::field::Field, value: u64) { + if field.name() == "request_id" { + *self.0.lock().unwrap() = Some(value); + } + } + fn record_debug(&mut self, _: &tracing::field::Field, _: &dyn std::fmt::Debug) {} + } + values.record(&mut Vis(&self.0)); + } +} + +#[tokio::test] +async fn request_id_is_recorded_as_native_u64_tracing_field() { + let (cap, tap) = webhook_tap(); + let mut hooks = Hooks::default(); + hooks.taps.response = vec![tap]; + let rig = Rig::new(pool(vec![member("m0", "served")]), None, hooks); + let seen = Arc::new(Mutex::new(None)); + use tracing_subscriber::layer::SubscriberExt as _; + let subscriber = tracing_subscriber::registry().with(RequestIdSpanCapture(seen.clone())); + let _guard = tracing::subscriber::set_default(subscriber); + tracing::callsite::rebuild_interest_cache(); + let answer = rig.fire(&chat_body()).await; + assert_eq!(answer.status, 200); + let payload = wait_for_tap_body(&cap).await; + let tap_id = payload["request"]["request_id"] + .as_u64() + .expect("the tap carries the id"); + assert_eq!( + *seen.lock().unwrap(), + Some(tap_id), + "the unit's span records the SAME id the response tap carries, as a native u64" + ); +} diff --git a/crates/busbar/tests/hook_parity_driver/speech_request.json b/crates/busbar/tests/hook_parity_driver/speech_request.json new file mode 100644 index 0000000000..5be3d38816 --- /dev/null +++ b/crates/busbar/tests/hook_parity_driver/speech_request.json @@ -0,0 +1,6 @@ +{ + "model": "gpt-4o-mini-tts", + "input": "SPEAK-THIS", + "voice": "alloy", + "instructions": "STYLE-INSTRUCTIONS" +} From 322796909cdb80335069142ae23b49f285bcf0fa Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:07:15 -0700 Subject: [PATCH 04/49] FLIP-LLM: the test plane refuses only a refusal with no reason or no text (RefusalIn::text is the kernel's own message: the reason's spelling or the walk terminal's own words, BUSBAR-1.6.0.md Part 3 section 12 line 2573 the route pump's exhaustion terminals); the kernel sections' providers key is read off Kind::Transport.root() (root_key_spelling) --- crates/busbar/src/root/door_steps.rs | 5 ++++- crates/busbar/tests/fixtures/plane_driver_test_plane.rs | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/busbar/src/root/door_steps.rs b/crates/busbar/src/root/door_steps.rs index 435213e397..4cdc5eb77b 100644 --- a/crates/busbar/src/root/door_steps.rs +++ b/crates/busbar/src/root/door_steps.rs @@ -1133,7 +1133,10 @@ pub fn kernel_sections( }); let yaml = |v: Value| serde_yaml::to_value(v).unwrap_or(serde_yaml::Value::Null); if !providers.is_empty() { - out.insert("providers", yaml(Value::Object(providers))); + out.insert( + busbar_contract::plugin::Kind::Transport.root(), + yaml(Value::Object(providers)), + ); } if !models.is_empty() || !pools.is_empty() { out.insert(RESERVED_MODELS_KEY, yaml(Value::Object(models))); diff --git a/crates/busbar/tests/fixtures/plane_driver_test_plane.rs b/crates/busbar/tests/fixtures/plane_driver_test_plane.rs index 80afedb614..d4f4fc365f 100644 --- a/crates/busbar/tests/fixtures/plane_driver_test_plane.rs +++ b/crates/busbar/tests/fixtures/plane_driver_test_plane.rs @@ -1043,9 +1043,10 @@ extern "C" fn refusal(_: *mut c_void, input: *const c_void, out: *mut c_void) -> unsafe { let i = &*input.cast::(); let o = &mut *out.cast::(); - // The reason crosses beside its text: a refusal whose code names another reason is FAULT. - let named = busbar_contract::abi::plane::reason_of(i.reason).map(|r| r.as_str().as_bytes()); - if named != Some(text(i.text)) { + // The reason crosses beside its text (`RefusalIn::text`, the kernel's own message: the + // reason's spelling, or the walk terminal's own words): a refusal whose code names no reason, + // or that carries no text, is FAULT. + if busbar_contract::abi::plane::reason_of(i.reason).is_none() || text(i.text).is_empty() { return RawOutcome::of(Outcome::Fault); } let mut body = [ From c1b8c4c13bb9800bd8b2ed16e820b8896efa63c1 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:21:03 -0700 Subject: [PATCH 05/49] FLIP-LLM gates: the design-bindings citations of the 1.5.5 hook tests now held twice (the legacy crate's and the driver's verbatim home) name their legacy file, the switch's auth rows and instance-noun leaks are written down, the config projection sits above the boot build's blocking-ffi exemption - design-bindings: PB-1/6/28/46/84 and their siblings cite the busbar-llm test by path.rs::name (the pattern PB-5 already uses), since the hook parity suite on the driver repeats each 1.5.5 name verbatim (BUSBAR-1.6.0.md Part 3 section 12 "The switch": the hook tests run verbatim); the ledger regenerated, 104 mapped, none unproven. - kind-isolation: busbar -> busbar-auth-sigv4 / busbar-auth-oauth, each 1 declaration, carried by the `llm-on-driver` switch (ARCHITECT Q-L1-AUTH (A)). - instance-noun-neutrality: the switch's three files that name the plane under proof (the parity suite's main.rs, the serve hook-seat tests and their cfg in serve.rs), each struck with the switch at the llm fold's D3 (1.6.0-TODO.md "the `llm-on-driver` dev cargo feature"). - blocking-ffi: set_config_projection moves above the boot build's allow marker, which exempts the call directly below it only. --- crates/busbar/src/main.rs | 7 +++-- qa/DESIGN-BINDINGS.md | 12 +++---- qa/design-bindings.json | 38 +++++++++++------------ qa/instance-noun-neutrality.toml | 32 +++++++++++++++++++ qa/kind-isolation.toml | 20 ++++++++++++ xtask/src/gates/design_bindings/tables.rs | 18 +++++------ 6 files changed, 90 insertions(+), 37 deletions(-) diff --git a/crates/busbar/src/main.rs b/crates/busbar/src/main.rs index 3c2cf4fb93..d60e5c53ab 100644 --- a/crates/busbar/src/main.rs +++ b/crates/busbar/src/main.rs @@ -778,6 +778,10 @@ async fn run(data_workers: usize, late_services: std::sync::Arcgate: `xtask/src/gates/construction.rs` | -| PB-1 | hook `on_empty` | mapped | PASS | | test: `enforce_restricts_reapplies_compliance_tags_across_pools`
test: `multi_restrict_disjoint_intersection_fails_closed` | +| PB-1 | hook `on_empty` | mapped | PASS | | test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::multi_restrict_disjoint_intersection_fails_closed` | | PB-2 | per-lane `max_concurrent` | mapped | PASS | | test: `excluded_reasons_records_at_capacity`
test: `at_capacity_reject_sheds_503_not_queued`
test: `queue_dispatches_when_permit_frees_before_deadline`
test: `queue_times_out_to_503_when_capacity_never_frees` | | PB-3 | tripped / budget-exhausted / at-capacity lanes | mapped | PASS | | test: `ordered_walk_skips_tripped_preferred_to_next`
test: `ordered_walk_skips_excluded_preferred`
test: `at_capacity_plus_tripped_member_rejects_503`
test: `least_bad_never_reaches_an_excluded_member`
test: `strengthened_lane_availability_invariant` | | PB-4 | `on_exhausted` terminals | mapped | PASS | | oracle-cell: `crosscut.traps\|exhausted-retry-after-floor`
oracle-cell: `route.failover\|fo\|all-down`
oracle-cell: `route.failover\|lb\|member-down`
test: `at_capacity_default_no_on_exhausted_sheds_503`
test: `retry_after_reflects_cooldown_when_a_member_is_tripped`
test: `retry_after_has_saturation_floor_when_purely_at_capacity`
test: `retry_after_empty_candidate_set_uses_floor_not_one`
test: `least_bad_still_serves_the_only_member_after_it_was_tried`
test: `a_fallback_pool_applies_its_own_exclusions`
test: `at_capacity_fallback_chain_spills_through_to_third_pool`
test: `at_capacity_self_referential_fallback_stays_503`
test: `queue_skips_wait_and_rejects_when_no_candidate_at_capacity`
test: `queue_won_permit_but_breaker_now_open_never_dispatches`
test: `queue_two_waiters_one_freed_permit_wakes_exactly_one`
test: `test_scrape_gauges_pool_queued_reads_live_depth` | | PB-5 | pick order | mapped | PASS | | oracle-cell: `route.failover\|fo\|all-up`
test: `crates/busbar-llm/src/engine/tests/ordered_walk_tests.rs::sticky_affinity_never_selects_zero_weight_drained_member`
test: `sticky_fall_through_records_reason`
test: `test_sticky_yields_when_tripped`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::order_last_in_chain_wins`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::stale_order_filtered_against_post_restrict_set`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::last_order_gate_filtered_to_empty_abstains_to_base_not_to_a_lower_gate`
test: `ordered_walk_falls_through_to_swrr_when_no_preferred_ready`
test: `ordered_walk_empty_order_is_swrr` | -| PB-6 | hook seats for migrated hooks | mapped | PASS | | test: `global_gate_reject_short_circuits_the_request`
test: `global_request_stage_tap_fires_on_a_real_dispatched_request`
test: `migrated_request_hook_seats_after_admit_before_candidate` | +| PB-6 | hook seats for migrated hooks | mapped | PASS | | test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_gate_reject_short_circuits_the_request`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_request_stage_tap_fires_on_a_real_dispatched_request`
test: `crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::migrated_request_hook_seats_after_admit_before_candidate` | | PB-7 | inbound shed | mapped | PASS | | test: `inbound_cap_sheds_the_excess_and_serves_the_admitted`
test: `a_saturated_gate_sheds_instead_of_parking_the_caller`
oracle-cell: `concurrency\|inbound-shed\|n8` | | PB-8 | request bounds for `http`/`sse` units | mapped | PASS | | oracle-cell: `route.failover\|fo\|primary-down`
test: `test_saturated_lane_respects_deadline_no_infinite_spin`
test: `test_context_length_failover_no_penalty`
test: `test_prefers_larger_context_max`
test: `a_changed_upstream_timeout_rebuilds_the_client_an_unrelated_apply_reuses_it`
test: `test_member_override_wins_over_model_default`
test: `test_attempt_cap_budget_floor`
test: `route_deadline_503_carries_detail_request_timeout`
test: `pick_among_none_lands_on_on_exhausted_with_overloaded_and_retry_after` | | PB-9 | revocation / rotation | mapped | PASS | | test: `rotate_invalidates_the_outstanding_signed_token`
test: `local_revoke_rejects_the_very_next_auth_attempt`
test: `peer_revoke_written_to_the_store_is_honoured_within_the_window` | @@ -54,7 +54,7 @@ The three words are not interchangeable: | PB-25 | usage absent from a stream | mapped | PASS | | test: `stream_without_usage_frame_bills_zero_on_every_dialect` | | PB-26 | pre-charge vs post-charge exits | mapped | PASS | | oracle-cell: `billing\|key-usage\|upstream-down-refunded`
test: `test_unpriced_passthrough_model_rejected_when_rate_card_present`
oracle-cell: `http.crosscut\|unknown-path\|openai-suffix`
test: `test_governance_pool_acl_403_openai_native_envelope`
test: `test_governance_pool_acl_403_bedrock_native_envelope`
test: `test_fallback_pool_acl_denies_key_not_allowed_on_fallback_target`
test: `test_finish_refunds_flat_fee_on_non_2xx_keeps_on_2xx`
test: `test_pre_routing_failure_does_not_refund_prior_charge`
test: `test_bedrock_non_object_body_is_400` | | PB-27 | zero-token ends | mapped | PASS | | oracle-cell: `billing\|key-usage\|upstream-down-refunded`
oracle-cell: `route.failover\|fo\|primary-cut-stream`
test: `test_mid_stream_transport_error_does_not_bill_partial_usage`
test: `test_streaming_translate_abort_trips_breaker_and_skips_billing`
test: `test_untranslatable_2xx_does_not_charge_tokens`
test: `test_cross_protocol_nonstream_over_cap_body_returns_500_uncharged`
test: `test_streaming_pre_first_byte_transport_error_refunds_budget`
test: `test_streaming_nonsse_post_first_byte_cut_refunds_the_lane_unit`
test: `test_streaming_nonsse_mid_body_transport_error_records_transient`
test: `test_cancel_drop_bills_partial_tokens`
test: `test_cancel_drop_mid_stream_refunds_budget`
test: `test_finish_refunds_flat_fee_on_non_2xx_keeps_on_2xx` | -| PB-28 | `on_empty: weighted` semantics | mapped | PASS | | test: `enforce_restricts_reapplies_compliance_tags_across_pools` | +| PB-28 | `on_empty: weighted` semantics | mapped | PASS | | test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools` | | PB-29 | gate vs routing-policy 503 literals | mapped | PASS | | test: `decision_gate_that_cannot_complete_has_its_own_503_body`
test: `decision_gate_restriction_leaving_no_lane_has_its_own_503_body`
test: `base_policy_that_cannot_select_has_its_own_503_body`
test: `base_policy_restriction_leaving_no_lane_has_its_own_503_body`
test: `the_four_503_literals_are_distinct` | | PB-30 | protocol detection | mapped | PASS | | oracle-cell: `http.crosscut\|api-prefix\|data`
oracle-cell: `http.crosscut\|unknown-path\|anthropic-beta`
oracle-cell: `http.crosscut\|unknown-path\|bare`
test: `resolver_table`
test: `test_api_root_unmatched_paths_speak_the_admin_envelope`
test: `test_fallback_bedrock_404_is_native_envelope_with_amzn_headers`
test: `test_fallback_openai_404_is_json_no_amzn_headers` | | PB-31 | plugin-declared routes | mapped | PASS | | test: `confinement_rejects_hook_out_of_namespace`
test: `confinement_export_metrics_exception_and_reserved_routes`
test: `route_dispatches_to_handle_http_and_resolves_live_after_swap`
test: `head_dispatches_to_the_declared_get_route`
test: `over_cap_response_headers_are_rejected_not_silently_truncated`
test: `under_cap_response_headers_relay_unchanged`
test: `paths_awaiting_restart_flags_only_newly_declared_unmounted_paths`
test: `collision_is_a_loud_failure_naming_the_owner` | @@ -72,7 +72,7 @@ The three words are not interchangeable: | PB-43 | operational routes | mapped | PASS | | oracle-cell: `hooks\|metrics-hooks@hooks-webrequest`
oracle-cell: `ops.scrape\|metricshooks\|key`
oracle-cell: `ops.scrape\|metrics\|key`
oracle-cell: `ops.scrape\|stats\|key`
test: `test_is_ready_any_cell_false_when_every_cell_open`
test: `test_is_ready_any_cell_true_when_a_pool_cell_is_ready`
test: `test_is_ready_is_side_effect_free`
test: `healthz_returns_a_real_response_not_the_default`
test: `thread_per_core_boots_and_serves_healthz`
test: `counter_renders_with_hook_label_and_verbatim_name`
test: `histogram_renders_as_summary`
test: `busbar_prefix_is_reserved`
test: `type_conflict_for_shared_name_is_dropped`
test: `test_stats_reports_at_capacity_when_lane_saturated`
test: `test_stats_limit_is_numeric_alias_of_max_concurrent`
test: `test_recovery_hint_ms` | | PB-44 | `in_flight_reserve` | mapped | PASS | | test: `a_full_table_refuses_every_counted_unit_at_the_cap_and_holds_no_slot_back`
test: `the_administrative_listener_and_the_heartbeat_are_outside_the_cap`
test: `the_cap_holds_when_everything_arrives_at_once` | | PB-45 | revoke / rotate | mapped | PASS | | test: `a_failed_read_does_not_close_the_staleness_window`
test: `a_successful_read_unions_and_closes_the_window`
test: `a_hung_store_does_not_park_the_reactor`
test: `test_key_state_distinguishes_disable_revoke_and_tombstone`
test: `local_revoke_rejects_the_very_next_auth_attempt` | -| PB-46 | migrated hook seats | mapped | PASS | | test: `migrated_request_hook_seats_after_admit_before_candidate`
test: `candidate_tap_fires_after_an_abstaining_gate_and_a_served_request_bills_once` | +| PB-46 | migrated hook seats | mapped | PASS | | test: `crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::migrated_request_hook_seats_after_admit_before_candidate`
test: `crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::candidate_tap_fires_after_an_abstaining_gate_and_a_served_request_bills_once` | | PB-47 | scoped draws and fallback | mapped | PASS | | oracle-cell: `route.failover\|fb\|member-down`
test: `crates/busbar-kernel/src/governance/tests/limits_tests.rs::pool_scoped_accrual_and_refund_mirror_the_charge`
test: `crates/busbar-kernel/src/governance/tests/limits_tests.rs::budget_block_carries_downgrade_target`
test: `test_budget_exhaustion_downgrades_pool`
test: `test_downgrade_cycle_terminates_via_the_revisit_guard` | | PB-48 | stall sweep on `http`/`sse` | mapped | PASS | | test: `crates/busbar-kernel/tests/recovery_and_ticks.rs::a_slow_unit_is_not_a_lost_one`
test: `crates/busbar-kernel/tests/recovery_and_ticks.rs::a_lost_task_is_settled_within_one_tick`
test: `crates/busbar-llm/src/engine/engine_tests/stream_deadline_tests.rs::a_proxied_stream_is_cut_at_the_configured_second_and_by_nothing_earlier`
test: `crates/busbar-llm/src/engine/engine_tests/stream_deadline_tests.rs::the_total_deadline_the_stream_rides_defaults_to_five_minutes` | | PB-49 | config overlay subsystem | mapped | PASS | | test: `a_read_only_config_dir_boots_without_an_overlay_instead_of_refusing`
test: `f_every_persist_entry_point_refuses_a_none_locked_overlay`
test: `d_overlay_precedence_config_over_env_over_default`
test: `write_is_0600`
test: `merge_into_applies_tombstones`
test: `safe_mode_requested_matches_the_exact_flag_only` | @@ -110,7 +110,7 @@ The three words are not interchangeable: | PB-81 | plugin call deadlines | mapped | PASS | | test: `the_inflight_cap_saturates_and_fails_on_the_caller_deadline_through_resolve_one`
test: `dlopen_slow_gate_hits_the_deadline`
test: `a_panicking_hook_is_broken_through_the_axis_never_a_verdict`
lint: `xtask/src/gates/blocking_ffi.rs` | | PB-82 | usage extraction | mapped | PASS | | test: `read_response_subtracts_cached_prefix_from_prompt_tokens`
test: `read_response_subtracts_cached_prefix_from_input_tokens`
test: `test_cached_content_token_count_reads_into_cache_read`
test: `test_gemini_usage_counts_thinking_tokens_as_output`
test: `adds_include_usage_when_absent`
test: `test_streaming_openai_egress_without_client_opt_in_still_gets_include_usage_injected`
test: `strip_same_proto_usage_fires_without_object_field` | | PB-83 | breaker scope | mapped | PASS | | oracle-cell: `route.failover\|fb\|member-401`
test: `test_pool_breaker_isolation`
test: `test_record_hard_down_all_cells_trips_default_and_every_pool`
test: `test_budget_is_lane_global_across_pools`
test: `test_unbounded_lane_skips_the_semaphore_bounded_still_enforces` | -| PB-84 | response-stage taps | mapped | PASS | | test: `completion_tap_reports_ok_outcome`
test: `completion_tap_fires_synthetic_rejected_by_gate`
test: `completion_tap_fires_synthetic_rejected_by_auth`
oracle-cell: `hooks\|hooked-pool\|unauth@hooks-webrequest` | +| PB-84 | response-stage taps | mapped | PASS | | test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_reports_ok_outcome`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_fires_synthetic_rejected_by_gate`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_fires_synthetic_rejected_by_auth`
oracle-cell: `hooks\|hooked-pool\|unauth@hooks-webrequest` | | PB-85 | `max_tokens` injection | mapped | PASS | | test: `per_model_then_global_then_4096`
test: `test_requires_max_tokens_per_protocol`
test: `test_openai_explicit_max_tokens_preserved_over_lane_default`
test: `test_openai_omits_max_tokens_injects_fallback_for_anthropic` | | PB-86 | usage locators are plane-normalized | mapped | PASS | | lint: `xtask/src/gates/kernel_token_wire_purity.rs`
gate: `xtask/src/gates/construction.rs` | | PB-87 | non-chat billing classes | mapped | PASS | | test: `rerank_resp_billing_is_flat`
test: `rerank_resp_billing_counts_search_units_as_an_open_class`
test: `a_reranks_search_units_reach_the_price` | @@ -121,7 +121,7 @@ The three words are not interchangeable: | PB-92 | `VirtualKey.expires_at` | mapped | PASS | | test: `a_key_row_whose_expires_at_is_in_the_past_still_verifies` | | PB-93 | 1.6.0-only store ops on an ABI-2 store | mapped | PASS | | oracle-cell: `plugins.store-persist\|store-mysql`
oracle-cell: `plugins.store-persist\|store-postgres`
oracle-cell: `plugins.store-persist\|store-sqlite`
oracle-cell: `plugins.store-persist\|store-valkey`
test: `unsupported_alone_opens_the_inert_default_on_every_defaulting_verb`
test: `a_real_answer_passes_through_untouched` | | PB-94 | upstream credential mode | mapped | PASS | | test: `test_passthrough_forwards_caller_token`
test: `the_family_table_decides_first_and_the_mode_decides_the_rest`
test: `override_present_runs_full_lookup`
test: `golden_migrate_auth_upstream_credentials_moves_to_pools` | -| PB-95 | migrated tap stages | mapped | PASS | | test: `attempt_tap_carries_attempt_story`
test: `route_tap_reports_surviving_candidates` | +| PB-95 | migrated tap stages | mapped | PASS | | test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::attempt_tap_carries_attempt_story`
test: `crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::route_tap_reports_surviving_candidates` | | PB-96 | streaming byte layout (the terminal-usage settle guarantee) | mapped | PASS | | test: `test_translate_anthropic_egress_to_openai_ingress`
test: `test_translate_openai_egress_to_anthropic_ingress`
test: `bedrock_stream_framing_emits_one_metadata_delta_then_guards_duplicate`
test: `test_translate_openai_include_usage_egress_to_bedrock_ingress_single_metadata`
test: `test_duplicate_terminal_message_delta_after_stop_is_dropped`
test: `test_tool_id_remap_is_a_stable_reversible_bijection`
test: `cohere_tool_ids_pass_through_verbatim_no_decode`
test: `strip_same_proto_usage_fires_without_object_field` | | PB-97 | pristine request bytes | mapped | PASS | | test: `a_same_dialect_hop_is_the_callers_bytes_with_governed_splices`
test: `pristine_same_proto_is_byte_identical_body_model`
test: `pristine_same_proto_is_byte_identical_url_model`
test: `claude_on_vertex_drops_model_and_injects_anthropic_version`
test: `invalidator_3_model_rewrite_forces_non_pristine`
test: `invalidator_4_same_proto_model_shim_strip_forces_non_pristine` | | PB-98 | `error_map` and lane state carry-over | mapped | PASS | | test: `test_normalize_raw_error_with_provider_override`
test: `restored_halfopen_state_normalizes_to_open`
test: `restore_does_not_clobber_new_limit_with_unlimited_sentinel`
test: `test_hard_down_follows_identity_across_rebuild`
test: `least_busy_prefers_most_headroom`
test: `least_busy_all_saturated_ranks_by_idx`
test: `cheapest_all_unknown_abstains`
test: `usage_all_unknown_abstains`
test: `fastest_orders_by_latency`
test: `from_ranked_never_produces_reject`
test: `from_ranked_empty_is_abstain`
test: `dlopen_notify_is_fire_and_forget` | diff --git a/qa/design-bindings.json b/qa/design-bindings.json index 8ecf626703..3aa53f9023 100644 --- a/qa/design-bindings.json +++ b/qa/design-bindings.json @@ -63,21 +63,21 @@ "checks": [ { "kind": "test", - "ref": "enforce_restricts_reapplies_compliance_tags_across_pools", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools", "proves": "a Reject restrict with no eligible lane fails closed; the Weighted arm passes candidates unchanged", "source": "curated", "status": "mapped" }, { "kind": "test", - "ref": "multi_restrict_disjoint_intersection_fails_closed", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::multi_restrict_disjoint_intersection_fails_closed", "proves": "two restricts intersecting to empty produce a 503 (status only, not the literal body)", "source": "curated", "status": "mapped" } ], "verdict": "PASS", - "verdict_detail": "test:enforce_restricts_reapplies_compliance_tags_across_pools, test:multi_restrict_disjoint_intersection_fails_closed" + "verdict_detail": "test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools, test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::multi_restrict_disjoint_intersection_fails_closed" }, { "id": "PB-2", @@ -363,28 +363,28 @@ "checks": [ { "kind": "test", - "ref": "global_gate_reject_short_circuits_the_request", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_gate_reject_short_circuits_the_request", "proves": "a decision-gate reject ends the request", "source": "curated", "status": "mapped" }, { "kind": "test", - "ref": "global_request_stage_tap_fires_on_a_real_dispatched_request", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_request_stage_tap_fires_on_a_real_dispatched_request", "proves": "request-stage taps fire through fire_global_taps", "source": "curated", "status": "mapped" }, { "kind": "test", - "ref": "migrated_request_hook_seats_after_admit_before_candidate", + "ref": "crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::migrated_request_hook_seats_after_admit_before_candidate", "proves": "a decision-gate reject consumes the requests slot and refunds the billable request; rewrite, request taps and gates all run after the admission charge", "source": "hand", "status": "mapped" } ], "verdict": "PASS", - "verdict_detail": "test:global_gate_reject_short_circuits_the_request, test:global_request_stage_tap_fires_on_a_real_dispatched_request, test:migrated_request_hook_seats_after_admit_before_candidate" + "verdict_detail": "test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_gate_reject_short_circuits_the_request, test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_request_stage_tap_fires_on_a_real_dispatched_request, test:crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::migrated_request_hook_seats_after_admit_before_candidate" }, { "id": "PB-7", @@ -1351,14 +1351,14 @@ "checks": [ { "kind": "test", - "ref": "enforce_restricts_reapplies_compliance_tags_across_pools", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools", "proves": "for a gate, Weighted leaves the candidate set unchanged", "source": "curated", "status": "mapped" } ], "verdict": "PASS", - "verdict_detail": "test:enforce_restricts_reapplies_compliance_tags_across_pools" + "verdict_detail": "test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools" }, { "id": "PB-29", @@ -2288,21 +2288,21 @@ "checks": [ { "kind": "test", - "ref": "migrated_request_hook_seats_after_admit_before_candidate", + "ref": "crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::migrated_request_hook_seats_after_admit_before_candidate", "proves": "a migrated request-stage chain seats after the admission door and ahead of the candidate tap: admission → rewrite → request taps → gates → candidate tap", "source": "hand", "status": "mapped" }, { "kind": "test", - "ref": "candidate_tap_fires_after_an_abstaining_gate_and_a_served_request_bills_once", + "ref": "crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::candidate_tap_fires_after_an_abstaining_gate_and_a_served_request_bills_once", "proves": "the candidate tap fires only after the gates decided, on a served request", "source": "hand", "status": "mapped" } ], "verdict": "PASS", - "verdict_detail": "test:migrated_request_hook_seats_after_admit_before_candidate, test:candidate_tap_fires_after_an_abstaining_gate_and_a_served_request_bills_once" + "verdict_detail": "test:crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::migrated_request_hook_seats_after_admit_before_candidate, test:crates/busbar-llm/src/engine/tests/hook_seat_order_tests.rs::candidate_tap_fires_after_an_abstaining_gate_and_a_served_request_bills_once" }, { "id": "PB-47", @@ -4009,21 +4009,21 @@ "checks": [ { "kind": "test", - "ref": "completion_tap_reports_ok_outcome", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_reports_ok_outcome", "proves": "the response stage reports ok", "source": "curated", "status": "mapped" }, { "kind": "test", - "ref": "completion_tap_fires_synthetic_rejected_by_gate", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_fires_synthetic_rejected_by_gate", "proves": "the synthetic outcome rejected_by_gate", "source": "curated", "status": "mapped" }, { "kind": "test", - "ref": "completion_tap_fires_synthetic_rejected_by_auth", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_fires_synthetic_rejected_by_auth", "proves": "OWNER DECISION: an auth refusal on a hooked pool fires the completion tap with outcome rejected_by_auth and the protocol-native status (binding amended)", "source": "hand", "status": "mapped" @@ -4037,7 +4037,7 @@ } ], "verdict": "PASS", - "verdict_detail": "test:completion_tap_reports_ok_outcome, test:completion_tap_fires_synthetic_rejected_by_gate, test:completion_tap_fires_synthetic_rejected_by_auth, oracle-cell:hooks|hooked-pool|unauth@hooks-webrequest" + "verdict_detail": "test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_reports_ok_outcome, test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_fires_synthetic_rejected_by_gate, test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_fires_synthetic_rejected_by_auth, oracle-cell:hooks|hooked-pool|unauth@hooks-webrequest" }, { "id": "PB-85", @@ -4383,21 +4383,21 @@ "checks": [ { "kind": "test", - "ref": "attempt_tap_carries_attempt_story", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::attempt_tap_carries_attempt_story", "proves": "the routing-stage payload carries a 1-based attempt_number", "source": "curated", "status": "mapped" }, { "kind": "test", - "ref": "route_tap_reports_surviving_candidates", + "ref": "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::route_tap_reports_surviving_candidates", "proves": "remaining_candidates on the candidate stage", "source": "curated", "status": "mapped" } ], "verdict": "PASS", - "verdict_detail": "test:attempt_tap_carries_attempt_story, test:route_tap_reports_surviving_candidates" + "verdict_detail": "test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::attempt_tap_carries_attempt_story, test:crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::route_tap_reports_surviving_candidates" }, { "id": "PB-96", diff --git a/qa/instance-noun-neutrality.toml b/qa/instance-noun-neutrality.toml index 19c2d66837..ae94173976 100644 --- a/qa/instance-noun-neutrality.toml +++ b/qa/instance-noun-neutrality.toml @@ -70,3 +70,35 @@ file = "crates/busbar-plane-llm/src/codec/openai_chat/tests/handler_tests.rs" count = 2 category = "cross-plugin" wave = "cross-plugin cleanup — broker law #8 (plugins never name each other)" + +# RE-ARM, measured (p3-flip-llm, 2026-10-04): the llm fold's development-only switch (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 llm flip's gate is every hook test +# green on the driver"). The hook parity suite names the plane under proof once, in its main.rs (the +# `llm-on-driver` cfg, the one `extern crate`, the plane-facts alias); the serve path's hook-seat tests +# (the predev seat-order tests' door home) name the door they drive and the cfg that compiles them. +# Each row is struck when the switch is deleted with the llm fold (D3, `1.6.0-TODO.md`: "the +# `llm-on-driver` dev cargo feature | the llm flip | deleted in the llm fold's D3"). +[[leak]] +noun = "llm" +kind = "plane" +file = "crates/busbar/tests/hook_parity_driver/main.rs" +count = 3 +category = "composition-root" +wave = "keystone / composition-root — register_planes both-ways self-registration" + +[[leak]] +noun = "llm" +kind = "plane" +file = "crates/busbar/src/root/tests/serve_hook_seats.rs" +count = 3 +category = "composition-root" +wave = "keystone / composition-root — register_planes both-ways self-registration" + +[[leak]] +noun = "llm" +kind = "plane" +file = "crates/busbar/src/root/serve.rs" +count = 1 +category = "composition-root" +wave = "keystone / composition-root — register_planes both-ways self-registration" diff --git a/qa/kind-isolation.toml b/qa/kind-isolation.toml index 04aad30ca2..a3b230c0e6 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -621,6 +621,26 @@ cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 / §6 step 3 — the compositio why = "busbar names busbar-auth-header: declared optional (feature `auth-header`, default-on) in the shipped graph, 1 declaration(s) at this commit — the root compiles in the header auth plugin (bearer/api-key/x-goog-api-key) a door plane's member egress binds its style through (door_steps `OutboundAuths`). The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack." drain = "none: BUSBAR-1.6.0.md grants this class (the root names the auth axis). 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-auth-oauth" +half = "shipped" +count = "1" +verdict = "allowed" +cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 / §6 step 3 — the composition root is the one thing that names all three axes; the auth plugin that serves a door plane's outbound style is linked on the `auths` axis (ARCHITECT Q-L1-AUTH (A) 2026-10-03); Part 3 §12 \"The switch\" (the llm fold's development-only `llm-on-driver` carries it)." +why = "busbar names busbar-auth-oauth: declared optional (feature `auth-oauth`, carried by the llm fold's `llm-on-driver` switch, not in `default`) in the shipped graph, 1 declaration(s) at this commit — the root compiles in the auth plugin serving the token styles (jwt-bearer, oauth-client-credentials) that a door-served llm member's egress binds its style through (door_steps `OutboundAuths`). The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack." +drain = "none: BUSBAR-1.6.0.md grants this class (the root names the auth axis). 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-auth-sigv4" +half = "shipped" +count = "1" +verdict = "allowed" +cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 / §6 step 3 — the composition root is the one thing that names all three axes; the auth plugin that serves a door plane's outbound style is linked on the `auths` axis (ARCHITECT Q-L1-AUTH (A) 2026-10-03); Part 3 §12 \"The switch\" (the llm fold's development-only `llm-on-driver` carries it)." +why = "busbar names busbar-auth-sigv4: declared optional (feature `auth-sigv4`, carried by the llm fold's `llm-on-driver` switch, not in `default`) in the shipped graph, 1 declaration(s) at this commit — the root compiles in the auth plugin serving the signing style (sigv4) that a door-served llm member's egress binds its style through (door_steps `OutboundAuths`). The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack." +drain = "none: BUSBAR-1.6.0.md grants this class (the root names the auth axis). 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-contract" diff --git a/xtask/src/gates/design_bindings/tables.rs b/xtask/src/gates/design_bindings/tables.rs index 0617e8b3b8..abf1fec687 100644 --- a/xtask/src/gates/design_bindings/tables.rs +++ b/xtask/src/gates/design_bindings/tables.rs @@ -19,8 +19,8 @@ pub static SEED: &[(&str, &[SeededCheck])] = &[ ("gate", "xtask/src/gates/inventory_coverage.rs", "every qa/evidence/inventory/*.md row id either has a citing shadow-oracle cell that PASSes on the golden ledger or is a NAMED gap in qa/inventory-gaps.json; a row with neither turns the check red, and so does a gap entry naming a row that no longer exists or has since been covered"), ]), ("PB-1", &[ - ("test", "enforce_restricts_reapplies_compliance_tags_across_pools", "a Reject restrict with no eligible lane fails closed; the Weighted arm passes candidates unchanged"), - ("test", "multi_restrict_disjoint_intersection_fails_closed", "two restricts intersecting to empty produce a 503 (status only, not the literal body)"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools", "a Reject restrict with no eligible lane fails closed; the Weighted arm passes candidates unchanged"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::multi_restrict_disjoint_intersection_fails_closed", "two restricts intersecting to empty produce a 503 (status only, not the literal body)"), ]), ("PB-2", &[ ("test", "excluded_reasons_records_at_capacity", "a saturated lane's try_admit yields no pick and records AtCapacity"), @@ -60,8 +60,8 @@ pub static SEED: &[(&str, &[SeededCheck])] = &[ ("test", "ordered_walk_empty_order_is_swrr", "no ordering gate means the SWRR floor"), ]), ("PB-6", &[ - ("test", "global_gate_reject_short_circuits_the_request", "a decision-gate reject ends the request"), - ("test", "global_request_stage_tap_fires_on_a_real_dispatched_request", "request-stage taps fire through fire_global_taps"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_gate_reject_short_circuits_the_request", "a decision-gate reject ends the request"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::global_request_stage_tap_fires_on_a_real_dispatched_request", "request-stage taps fire through fire_global_taps"), ]), ("PB-8", &[ ("test", "test_saturated_lane_respects_deadline_no_infinite_spin", "the pick_among guard is bounded by failover.timeout_secs and resolves 503"), @@ -177,7 +177,7 @@ pub static SEED: &[(&str, &[SeededCheck])] = &[ ("test", "test_finish_refunds_flat_fee_on_non_2xx_keeps_on_2xx", "the flat fee is kept after 2xx headers"), ]), ("PB-28", &[ - ("test", "enforce_restricts_reapplies_compliance_tags_across_pools", "for a gate, Weighted leaves the candidate set unchanged"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::enforce_restricts_reapplies_compliance_tags_across_pools", "for a gate, Weighted leaves the candidate set unchanged"), ]), ("PB-30", &[ ("test", "resolver_table", "the detect ladder rungs: headers, SigV4 prefix, path suffixes, catch-all"), @@ -485,8 +485,8 @@ pub static SEED: &[(&str, &[SeededCheck])] = &[ ("test", "test_unbounded_lane_skips_the_semaphore_bounded_still_enforces", "max_concurrent is lane-global"), ]), ("PB-84", &[ - ("test", "completion_tap_reports_ok_outcome", "the response stage reports ok"), - ("test", "completion_tap_fires_synthetic_rejected_by_gate", "the synthetic outcome rejected_by_gate"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_reports_ok_outcome", "the response stage reports ok"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::completion_tap_fires_synthetic_rejected_by_gate", "the synthetic outcome rejected_by_gate"), ]), ("PB-85", &[ ("test", "per_model_then_global_then_4096", "default_max_tokens is injected only when the IR carries none; a caller value survives"), @@ -521,8 +521,8 @@ pub static SEED: &[(&str, &[SeededCheck])] = &[ ("test", "golden_migrate_auth_upstream_credentials_moves_to_pools", "the 1.5.5 key lands under pools"), ]), ("PB-95", &[ - ("test", "attempt_tap_carries_attempt_story", "the routing-stage payload carries a 1-based attempt_number"), - ("test", "route_tap_reports_surviving_candidates", "remaining_candidates on the candidate stage"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::attempt_tap_carries_attempt_story", "the routing-stage payload carries a 1-based attempt_number"), + ("test", "crates/busbar-llm/src/engine/tests/hook_seam_tests.rs::route_tap_reports_surviving_candidates", "remaining_candidates on the candidate stage"), ]), ("PB-96", &[ ("test", "test_translate_anthropic_egress_to_openai_ingress", "[DONE] for an openai ingress"), From 13bdeef1d51923885103587503705db90a698ac8 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:25:17 -0700 Subject: [PATCH 06/49] oracle register: the PLANELOG accepted difference (egress.image-detail|high|{anthropic,bedrock,gemini}, body only) and its CHANGELOG line, ported from 118b2f53c9 (TLSDEST not taken): OWNER SIGNED 2026-10-04, #85 (OWNER 2026-09-28 PLUGIN LOGGING: one log file per plugin instance); the plugins.logs paragraph that said plugin log lines had not moved is corrected --- CHANGELOG.md | 11 +++-- .../shadow-oracle/accepted-differences.json | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cdd575fd2..471e278e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -610,13 +610,14 @@ hook payload schemas are unchanged, as is the transport version (`busbar_abi() = exported symbols. A sink that wants the back-channel implements one new defaulted SDK method, `drain_observations`. -**A new `plugins.logs` block is accepted and checked; plugin log lines have not moved.** The block +**A new `plugins.logs` block is accepted and checked, and plugin log lines move to per-plugin log files.** The block names the directory for per-plugin log files (`dir`, default `logs/plugins`), the default level (`level`, default `info`), per-instance levels (`levels`), and rotation (`rotate_mb` and `keep`). -A level word that names no level, or a `rotate_mb` of `0`, refuses the boot and names the key. This -build writes no per-plugin log file: the plugins it loads do not run through the path that writes -them, so a plugin's log lines reach the Busbar log exactly as they did in 1.5.5, and setting -`plugins.logs` changes nothing an operator can observe beyond that check. +A level word that names no level, or a `rotate_mb` of `0`, refuses the boot and names the key. + +- 1.6.0 Changed: a plugin's log lines, the LLM plane's included (it is a plugin in 1.6.0), are written to that plugin's own log file under `plugins.logs.dir` (for example `logs/plugins/llm.log`), not to the Busbar log. + In 1.5.5 the plane's warnings (for example `dropping image_url.detail: ...`) were lines of the + Busbar log. **Migration:** point log collection at `plugins.logs.dir` as well as the Busbar log. ### Spec fidelity diff --git a/testing/shadow-oracle/accepted-differences.json b/testing/shadow-oracle/accepted-differences.json index fe987f8d49..54a9c51578 100644 --- a/testing/shadow-oracle/accepted-differences.json +++ b/testing/shadow-oracle/accepted-differences.json @@ -2229,6 +2229,53 @@ "baseline": "1.5.5", "by": "2026-12-31" } + }, + { + "id": "PLANELOG the llm plane's warnings are written to its own plugin log file", + "kind": "improvement", + "changelog": "1.6.0 Changed: a plugin's log lines, the LLM plane's included (it is a plugin in 1.6.0), are written to that plugin's own log file under `plugins.logs.dir` (for example `logs/plugins/llm.log`), not to the Busbar log.", + "by": "owner (Matthew Jackson), signed 2026-10-04 (PLANELOG, OWNER SIGNED on the p3-flip-llm park); 2026-09-28 PLUGIN LOGGING amendment (\"all plugins should log to their plugin's log file\"), #85", + "cells": "^egress\\.image-detail\\|high\\|(anthropic|bedrock|gemini)$", + "expected_cells": 3, + "classes": [ + "body" + ], + "premise": { + "all": [ + { + "cell": "egress.image-detail|high|anthropic", + "body_contains": "\"status\":200" + }, + { + "not": { + "cell": "egress.image-detail|high|anthropic", + "body_contains": "dropping image_url.detail" + } + }, + { + "not": { + "cell": "egress.image-detail|high|bedrock", + "body_contains": "dropping image_url.detail" + } + }, + { + "not": { + "cell": "egress.image-detail|high|gemini", + "body_contains": "dropping image_url.detail" + } + } + ] + }, + "signoff": { + "who": "owner (Matthew Jackson)", + "when": "2026-10-04", + "ruling": "OWNER SIGNED 2026-10-04 (PLANELOG): the llm plane's warnings move to its own plugin log file per the 2026-09-28 PLUGIN LOGGING amendment, #85 'log changes from 1.5.5' (BUSBAR-1.6.0.md)" + }, + "expires": { + "baseline": "1.5.5", + "by": "2026-12-31" + }, + "rationale": "#85 (OWNER 2026-09-28 PLUGIN LOGGING: one log file per plugin instance, not the host log) moves the llm plane's log lines, as every plugin's, to its own plugin log file. MEASURED 2026-10-03 (lane-dg-llm, door build): the three cells read busbar.log after a request whose image_url.detail the target dialect cannot carry. 1.5.5 wrote the plane's warning `dropping image_url.detail: ... detail=\"high\"` into busbar.log; with the llm plane served through its door the host writes it to the plane's own plugin log file (recorded: work/capture/logs/plugins/llm.log holds `WARN llm plane busbar_plane_llm::codec::chat_handle: dropping image_url.detail: no cross-protocol carrier exists for this cost/latency hint (not even on a same-protocol OpenAI round-trip) detail=\"high\"`), so busbar.log lacks that one line. The upstream request, the status and every money class are unchanged; only the read_file step's body differs." } ] } From 9eb179c8a767f64c171148d369d796ffc4d8c8ca Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:30:13 -0700 Subject: [PATCH 07/49] FLIP-LLM money: the llm plane declares its per-request fee unit and reports it exactly where 1.5.5 billed the flat request fee; the plane's report is the one fee decider on the door (owner #77, money-B1 ruling on the p3-flip-llm park, option (b); BUSBAR-1.6.0.md Part 0 "money = f(ledger, ratecard)": the ledger must carry what 1.5.5 billed) - busbar-plane-llm: `per_request` (busbar_contract::plane::PER_REQUEST, family `request`) is the tail's last billable class and its one fee unit. The answer that opens the caller's reply under a success status incurs it (1.5.5's finish kept the fee for a 2xx caller status and refunded it otherwise); from then every answer that carries counts carries the far end's last cumulative counts and the fee unit's 1. A non-2xx reply, a far-end request and a probe incur none. - root door_steps: every door plane's fee refund is its reported fee units (FeeRefund::PlaneFeeUnits); the pools plane's caller-status special case is gone, so one decider remains. - the ledger identity holds on the switch build: the node's books carry the 4 x 30000 micro-units of flat fee the legacy rows carry (tests/ledger_identity.rs green under `llm-on-driver`). --- crates/busbar-plane-llm/src/plane_door.rs | 136 +++++++++++++++++++- crates/busbar-plane-llm/tests/plane_door.rs | 15 ++- crates/busbar/src/root/door_steps.rs | 12 +- 3 files changed, 149 insertions(+), 14 deletions(-) diff --git a/crates/busbar-plane-llm/src/plane_door.rs b/crates/busbar-plane-llm/src/plane_door.rs index 55258960f7..04895eba9e 100644 --- a/crates/busbar-plane-llm/src/plane_door.rs +++ b/crates/busbar-plane-llm/src/plane_door.rs @@ -177,17 +177,31 @@ const fn open_class(k: usize) -> BillableClass { } } +/// THE FEE UNIT the plane counts: one per billable request (`busbar_contract::plane::PER_REQUEST`), +/// reported as a count of 1 exactly where 1.5.5 billed its flat request fee (a reply whose caller +/// status is a success) and never where 1.5.5 refunded it (owner #77, money-B1: the plane's report is +/// the ONE fee decider). It is the last of the tail's billable classes, and never usage. +pub const FEE_CLASS: &str = busbar_contract::plane::PER_REQUEST; +/// [`FEE_CLASS`]'s index in the tail's billable classes. +pub const FEE_CLASS_INDEX: u32 = (METER.len() + OPEN_CLASSES.len()) as u32; + const BILLABLE_CLASSES: &[BillableClass] = &[ billable(0), billable(1), billable(2), billable(3), open_class(0), + BillableClass { + class: abi_str(FEE_CLASS), + family: abi_str("request"), + }, ]; +/// The tail's fee units: [`FEE_CLASS`]. +const FEE_UNITS: &[AbiStr] = &[abi_str(FEE_CLASS)]; const _: () = assert!( DIALECTS.len() == DIALECT_NAMES.len() && OPS.len() == OP_CLASSES.len() - && METER.len() + OPEN_CLASSES.len() == BILLABLE_CLASSES.len(), + && METER.len() + OPEN_CLASSES.len() + 1 == BILLABLE_CLASSES.len(), "the tail states every dialect, op class and token class the plane declares" ); @@ -345,8 +359,8 @@ pub const TAIL: &PlaneTail = &PlaneTail { billable_classes_len: BILLABLE_CLASSES.len(), route_cost: ptr::null(), route_cost_len: 0, - fee_units: ptr::null(), - fee_units_len: 0, + fee_units: FEE_UNITS.as_ptr(), + fee_units_len: FEE_UNITS.len(), record_kinds: ptr::null(), record_kinds_len: 0, egress_targets: ptr::null(), @@ -455,6 +469,10 @@ struct UnitState { /// `project` found the body unreadable: the unit's refusal reads the previous release's /// unreadable-body sentence. unreadable: bool, + /// The far end's last cumulative counts, as the unit last reported them. + reported: Vec, + /// The caller was answered under a success status: the request's fee unit was incurred. + fee: bool, } impl UnitState { @@ -471,6 +489,8 @@ impl UnitState { started: None, pending: None, unreadable: false, + reported: Vec::new(), + fee: false, } } @@ -657,6 +677,29 @@ fn verdict(v: Verdict) -> u32 { } } +/// THE REQUEST'S FEE UNIT on `answer` (owner #77, money-B1: the plane's report is the one fee +/// decider): incurred when the caller's reply opens under a success status, which is where 1.5.5 +/// kept its flat request fee (its finish refunded the fee for a non-2xx caller status, and only +/// then). From that answer on, every answer that carries counts carries the fee unit's 1 beside the +/// far end's last cumulative counts; an answer that carries none leaves the last report standing. +/// A probe is no billable request. +fn fee_unit(unit: &mut UnitState, answer: &mut Answer) { + if !answer.units.is_empty() { + unit.reported.clone_from(&answer.units); + } + let incurred = + !unit.fee && !unit.probe && !answer.to_far_end && (200..=299).contains(&answer.status); + unit.fee |= incurred; + if unit.fee && (incurred || !answer.units.is_empty()) { + answer.units.clone_from(&unit.reported); + answer.units.push(UnitCount { + class: FEE_CLASS_INDEX, + source: UNITS_REPORTED, + amount: 1, + }); + } +} + /// A reply piece as the caller's answer. fn to_caller(piece: Piece<'_>) -> Answer { let (status, fields) = piece @@ -1086,7 +1129,8 @@ slot!( passthrough: given.passthrough != 0, clock: if given.from == FROM_CALLER { None } else { door.clock() }, }; - let answer = answer(&mut unit, given.from, &piece); + let mut answer = answer(&mut unit, given.from, &piece); + fee_unit(&mut unit, &mut answer); unit.pending = Some(Pending { answer, sent: 0, @@ -1306,3 +1350,87 @@ busbar_contract::plugin_door! { serve: Safe, hydrate: Safe, start: Safe, project: Safe, }, } + +#[cfg(test)] +mod fee_unit_tests { + use super::*; + + fn tokens(n: u64) -> Vec { + vec![UnitCount { + class: 0, + source: UNITS_REPORTED, + amount: n, + }] + } + + /// A report as plain triples (the ABI row has no `PartialEq`). + fn seen(units: &[UnitCount]) -> Vec<(u32, u32, u64)> { + units + .iter() + .map(|u| (u.class, u.source, u.amount)) + .collect() + } + + fn fee() -> UnitCount { + UnitCount { + class: FEE_CLASS_INDEX, + source: UNITS_REPORTED, + amount: 1, + } + } + + /// THE FEE UNIT (owner #77, money-B1): reported 1 from the answer that opens the caller's reply + /// under a success status, beside the far end's last cumulative counts on every later answer + /// that carries counts; an answer that carries none leaves the last report standing. + #[test] + fn a_success_reply_incurs_the_fee_unit_and_every_later_report_keeps_it() { + let mut unit = UnitState::new(Arc::default()); + let mut head = Answer { + status: 200, + ..Answer::default() + }; + fee_unit(&mut unit, &mut head); + assert_eq!( + seen(&head.units), + seen(&[fee()]), + "incurred at the success head" + ); + let mut chunk = Answer::default(); + fee_unit(&mut unit, &mut chunk); + assert!( + chunk.units.is_empty(), + "a countless answer leaves the report" + ); + let mut last = Answer { + units: tokens(7), + ..Answer::default() + }; + fee_unit(&mut unit, &mut last); + assert_eq!(seen(&last.units), seen(&[tokens(7), vec![fee()]].concat())); + } + + /// 1.5.5 refunded its flat fee for a non-2xx caller status: no fee unit is reported, so the + /// unit's end refunds; nor for an answer bound for the far end, nor for a probe. + #[test] + fn a_failed_reply_a_far_request_and_a_probe_incur_no_fee_unit() { + for (status, to_far_end, probe) in + [(503, false, false), (200, true, false), (200, false, true)] + { + let mut unit = UnitState::new(Arc::default()); + unit.probe = probe; + let mut answer = Answer { + status, + to_far_end, + units: tokens(3), + ..Answer::default() + }; + fee_unit(&mut unit, &mut answer); + assert_eq!( + seen(&answer.units), + seen(&tokens(3)), + "{status} {to_far_end} {probe}" + ); + assert!(!unit.fee); + } + } +} diff --git a/crates/busbar-plane-llm/tests/plane_door.rs b/crates/busbar-plane-llm/tests/plane_door.rs index 8b03300d98..86ef5a3cc9 100644 --- a/crates/busbar-plane-llm/tests/plane_door.rs +++ b/crates/busbar-plane-llm/tests/plane_door.rs @@ -37,10 +37,21 @@ fn the_tail_is_one_the_contract_accepts() { assert_eq!(TAIL.dialects_len, DIALECTS.len()); assert_eq!(TAIL.op_classes_len, 7); assert_eq!( - TAIL.billable_classes_len, 5, - "tokens in, out, cache read, cache write, then the open classes" + TAIL.billable_classes_len, 6, + "tokens in, out, cache read, cache write, the open classes, then the per-request fee unit" ); assert_eq!(OPEN_CLASSES, &[("search_units", "units")]); + // THE FEE UNIT (owner #77, money-B1): one per billable request, the last billable class. + assert_eq!(TAIL.fee_units_len, 1); + // SAFETY: the tail's `'static` fee-unit list of `fee_units_len` entries. + let fee = unsafe { *TAIL.fee_units }; + // SAFETY: a `'static` str the door states (abi_str). + let fee = unsafe { std::slice::from_raw_parts(fee.ptr, fee.len) }; + assert_eq!(fee, busbar_contract::plane::PER_REQUEST.as_bytes()); + assert_eq!( + busbar_plane_llm::plane_door::FEE_CLASS_INDEX as usize, + TAIL.billable_classes_len - 1 + ); } /// ARCHITECT Q-L1-AUTH (A): every dialect states its default outbound style in the tail, and every diff --git a/crates/busbar/src/root/door_steps.rs b/crates/busbar/src/root/door_steps.rs index 4cdc5eb77b..02b547cfb0 100644 --- a/crates/busbar/src/root/door_steps.rs +++ b/crates/busbar/src/root/door_steps.rs @@ -543,14 +543,10 @@ impl<'s> DoorSteps<'s> { classes: Arc::clone(&self.facts.classes), arrived: self.arrived, mode: exhaustion_of(&self.app, key), - // The plane serving the `pools` map bills 1.5.5's flat request fee and refunds it - // under 1.5.5's rule (a non-2xx caller status); every other plane's fee is its - // reported fee units (#47). - fee: if self.facts.plane.is_empty() { - FeeRefund::CallerStatus - } else { - FeeRefund::PlaneFeeUnits(Arc::clone(&self.facts.fee_units)) - }, + // THE PLANE'S REPORT IS THE ONE FEE DECIDER (owner #77, money-B1): every plane's + // fee is its reported fee units (#47); the plane serving the `pools` map reports + // its per-request unit exactly where 1.5.5 kept its flat fee. + fee: FeeRefund::PlaneFeeUnits(Arc::clone(&self.facts.fee_units)), charge: grant.charge().clone(), }, ); From 49de832b59f2c8a5758468657e88e4073be36a71 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:31:08 -0700 Subject: [PATCH 08/49] =?UTF-8?q?LLM-UA=20on=20the=20door=20(OWNER=20RULIN?= =?UTF-8?q?G=20Q10;=20PROTO-FIXES=200ac5fc9826=20already=20restored=20the?= =?UTF-8?q?=20behaviour=20on=20predev):=20the=20ruling's=20pins=20ported?= =?UTF-8?q?=20from=20lane-llm-ua=20(6adf6fb4c1,=200830cf130e,=20bffce13ffc?= =?UTF-8?q?)=20=E2=80=94=20tests/exchange=5Fuser=5Fagent.rs=20(a=20same-di?= =?UTF-8?q?alect=20far=20end=20sees=20exactly=20the=20caller's=20user-agen?= =?UTF-8?q?t,=20a=20caller=20that=20sent=20none=20carries=20the=20dialect'?= =?UTF-8?q?s=201.5.5=20fingerprint,=20a=20translated=20hop=20writes=20the?= =?UTF-8?q?=20far=20dialect's),=20and=20each=20dialect's=20egress=5Fuser?= =?UTF-8?q?=5Fagent=20and=20head=5Ffields=20state=20where=20the=20fingerpr?= =?UTF-8?q?int=20is=20written?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/codec/anthropic/mod.rs | 3 + .../busbar-plane-llm/src/codec/bedrock/mod.rs | 3 + .../busbar-plane-llm/src/codec/cohere/mod.rs | 3 + .../busbar-plane-llm/src/codec/gemini/mod.rs | 3 + .../src/codec/openai_chat/mod.rs | 3 + .../src/codec/openai_responses/mod.rs | 3 + .../busbar-plane-llm/src/exchange/attempt.rs | 4 + .../tests/exchange_user_agent.rs | 107 ++++++++++++++++++ 8 files changed, 129 insertions(+) create mode 100644 crates/busbar-plane-llm/tests/exchange_user_agent.rs diff --git a/crates/busbar-plane-llm/src/codec/anthropic/mod.rs b/crates/busbar-plane-llm/src/codec/anthropic/mod.rs index 8280a831f4..81640e93a4 100644 --- a/crates/busbar-plane-llm/src/codec/anthropic/mod.rs +++ b/crates/busbar-plane-llm/src/codec/anthropic/mod.rs @@ -197,6 +197,9 @@ pub const DECL: ProtocolDecl = ProtocolDecl { // Anthropic Python SDK is Stainless-generated and emits `/Python <ver>`. RELEASE // OBLIGATION: re-verify each version against the latest published SDK before a release and bump // (the `test_egress_ua_versions_are_pinned_and_present` guard forces the change to be conscious). + // OWNER RULING Q10: the plane writes this on a translated route into this dialect, on a + // same-dialect route whose caller sent no user-agent (1.5.5's bytes), and on the + // kernel-originated health probe; a same-dialect caller's own user-agent replaces it. egress_user_agent: "Anthropic/Python 0.39.0", has_model_in_url: false, auth_failure_status_and_kind: ( diff --git a/crates/busbar-plane-llm/src/codec/bedrock/mod.rs b/crates/busbar-plane-llm/src/codec/bedrock/mod.rs index 541006c82b..eef589d4a6 100644 --- a/crates/busbar-plane-llm/src/codec/bedrock/mod.rs +++ b/crates/busbar-plane-llm/src/codec/bedrock/mod.rs @@ -237,6 +237,9 @@ pub const DECL: ProtocolDecl = ProtocolDecl { max_citations_per_delta: Some(1), // AWS Bedrock is reached via boto3/botocore. RELEASE OBLIGATION: re-verify/bump per release; // `test_egress_ua_versions_are_pinned_and_present` guards drift. + // OWNER RULING Q10: the plane writes this on a translated route into this dialect, on a + // same-dialect route whose caller sent no user-agent (1.5.5's bytes), and on the + // kernel-originated health probe; a same-dialect caller's own user-agent replaces it. egress_user_agent: "Boto3/1.35.0 md/Botocore#1.35.0", has_model_in_url: true, auth_failure_status_and_kind: ( diff --git a/crates/busbar-plane-llm/src/codec/cohere/mod.rs b/crates/busbar-plane-llm/src/codec/cohere/mod.rs index b76a382e1e..87476b3e99 100644 --- a/crates/busbar-plane-llm/src/codec/cohere/mod.rs +++ b/crates/busbar-plane-llm/src/codec/cohere/mod.rs @@ -107,6 +107,9 @@ pub const DECL: ProtocolDecl = ProtocolDecl { max_citations_per_delta: Some(1), // Cohere Python SDK UA. RELEASE OBLIGATION: re-verify/bump per release; // `test_egress_ua_versions_are_pinned_and_present` guards drift. + // OWNER RULING Q10: the plane writes this on a translated route into this dialect, on a + // same-dialect route whose caller sent no user-agent (1.5.5's bytes), and on the + // kernel-originated health probe; a same-dialect caller's own user-agent replaces it. egress_user_agent: "cohere-python/5.11.0", has_model_in_url: false, auth_failure_status_and_kind: ( diff --git a/crates/busbar-plane-llm/src/codec/gemini/mod.rs b/crates/busbar-plane-llm/src/codec/gemini/mod.rs index 1bdd6d1eac..dfce29304a 100644 --- a/crates/busbar-plane-llm/src/codec/gemini/mod.rs +++ b/crates/busbar-plane-llm/src/codec/gemini/mod.rs @@ -182,6 +182,9 @@ pub const DECL: ProtocolDecl = ProtocolDecl { max_citations_per_delta: None, // Google GenAI SDK UA. RELEASE OBLIGATION: re-verify/bump per release; // `test_egress_ua_versions_are_pinned_and_present` guards drift. + // OWNER RULING Q10: the plane writes this on a translated route into this dialect, on a + // same-dialect route whose caller sent no user-agent (1.5.5's bytes), and on the + // kernel-originated health probe; a same-dialect caller's own user-agent replaces it. egress_user_agent: "google-genai-sdk/0.8.0 gl-python/3.11", has_model_in_url: true, auth_failure_status_and_kind: ( diff --git a/crates/busbar-plane-llm/src/codec/openai_chat/mod.rs b/crates/busbar-plane-llm/src/codec/openai_chat/mod.rs index 542d3bb570..b32d0e263e 100644 --- a/crates/busbar-plane-llm/src/codec/openai_chat/mod.rs +++ b/crates/busbar-plane-llm/src/codec/openai_chat/mod.rs @@ -137,6 +137,9 @@ pub const DECL: ProtocolDecl = ProtocolDecl { max_citations_per_delta: None, // OpenAI Python SDK UA (the Responses surface shares it). RELEASE OBLIGATION: re-verify/bump per // release; the `test_egress_ua_versions_are_pinned_and_present` guard forces a conscious change. + // OWNER RULING Q10: the plane writes this on a translated route into this dialect, on a + // same-dialect route whose caller sent no user-agent (1.5.5's bytes), and on the + // kernel-originated health probe; a same-dialect caller's own user-agent replaces it. egress_user_agent: "OpenAI/Python 1.54.0", has_model_in_url: false, auth_failure_status_and_kind: ( diff --git a/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs b/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs index 0d703c0254..9e7c3ee0f4 100644 --- a/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs +++ b/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs @@ -102,6 +102,9 @@ pub const DECL: ProtocolDecl = ProtocolDecl { max_citations_per_delta: None, // Same OpenAI Python SDK UA as the Chat surface (one vendor, two surfaces). RELEASE OBLIGATION: // re-verify/bump per release; `test_egress_ua_versions_are_pinned_and_present` guards drift. + // OWNER RULING Q10: the plane writes this on a translated route into this dialect, on a + // same-dialect route whose caller sent no user-agent (1.5.5's bytes), and on the + // kernel-originated health probe; a same-dialect caller's own user-agent replaces it. egress_user_agent: "OpenAI/Python 1.54.0", has_model_in_url: false, auth_failure_status_and_kind: ( diff --git a/crates/busbar-plane-llm/src/exchange/attempt.rs b/crates/busbar-plane-llm/src/exchange/attempt.rs index 1baed8b02a..68a4a2deeb 100644 --- a/crates/busbar-plane-llm/src/exchange/attempt.rs +++ b/crates/busbar-plane-llm/src/exchange/attempt.rs @@ -614,6 +614,10 @@ fn legal_field_value(v: &[u8]) -> bool { /// busbar is invisible to upstreams (OWNER HARD RULE 2026-10-02). The caller's value of a name /// replaces busbar's native default; the per-connection mechanics are the kernel's to drop as it /// writes the head. A translated route forwards no caller field: none maps between dialects. +/// +/// `user-agent` (OWNER RULING Q10, LLM-UA): a translated route writes the far dialect's declared +/// `egress_user_agent`. A same-dialect route carries the caller's own unchanged, and a caller that +/// sent none gets the far dialect's declared fingerprint, as 1.5.5 sent. fn head_fields( lane: &Lane, handler: &dyn OperationHandler, diff --git a/crates/busbar-plane-llm/tests/exchange_user_agent.rs b/crates/busbar-plane-llm/tests/exchange_user_agent.rs new file mode 100644 index 0000000000..08579b8010 --- /dev/null +++ b/crates/busbar-plane-llm/tests/exchange_user_agent.rs @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! OWNER RULING Q10 (LLM-UA): the far end's `user-agent`. On a same-dialect route the caller's +//! passes through unchanged, and a caller that sent none gets the dialect's declared native-SDK +//! fingerprint, as 1.5.5 sent. On a translated route the plane writes the far dialect's declared +//! fingerprint, and no caller value reaches the far end. + +use serde_json::json; + +use busbar_plane_llm::exchange::arrive::arrive; +use busbar_plane_llm::exchange::attempt::{build, FarRequest}; +use busbar_plane_llm::exchange::shaping::Shaping; + +fn shaping() -> Shaping { + Shaping::from_settings(&json!({ + "providers": { + "oai": { "protocol": "openai", "base_url": "https://api.example" }, + "ant": { "protocol": "anthropic", "base_url": "https://anthropic.example" }, + "goo": { "protocol": "gemini", "base_url": "https://gemini.example" } + }, + "models": { + "gpt": { "provider": "oai" }, + "claude": { "provider": "ant", "default_max_tokens": 321 }, + "gem": { "provider": "goo", "upstream_model": "gemini-pro" } + }, + "pools": { "p": { "members": ["gpt", "claude", "gem"] } } + })) + .expect("reads") +} + +const CHAT: &str = r#"{"model":"m","messages":[{"role":"user","content":"hi"}]}"#; +const MESSAGES: &str = + r#"{"model":"m","max_tokens":5,"messages":[{"role":"user","content":"hi"}]}"#; + +fn far(target: &str, body: &str, caller_ua: Option<&'static str>, member: &str) -> FarRequest { + let mut h: Vec<(&[u8], &[u8])> = + vec![(b"content-type".as_slice(), b"application/json".as_slice())]; + if let Some(ua) = caller_ua { + h.push((b"user-agent".as_slice(), ua.as_bytes())); + } + let a = arrive("POST", target, &h, body.as_bytes(), &()).expect("arrives"); + build(&a, &h, &shaping(), "p", member).expect("built") +} + +fn user_agent(r: &FarRequest) -> Vec<&[u8]> { + r.fields + .iter() + .filter(|(n, _)| n.eq_ignore_ascii_case("user-agent")) + .map(|(_, v)| v.as_slice()) + .collect() +} + +#[test] +fn a_same_dialect_far_end_sees_exactly_the_caller_s_user_agent() { + for (target, body, member) in [ + ("/v1/messages", MESSAGES, "claude"), + ("/v1/chat/completions", CHAT, "gpt"), + ] { + let r = far(target, body, Some("curl/8.5.0"), member); + assert_eq!(user_agent(&r), [b"curl/8.5.0".as_slice()], "{target}"); + } +} + +#[test] +fn a_same_dialect_caller_that_sent_no_user_agent_carries_the_1_5_5_fingerprint() { + for (target, body, member, fingerprint) in [ + ( + "/v1/messages", + MESSAGES, + "claude", + "Anthropic/Python 0.39.0", + ), + ("/v1/chat/completions", CHAT, "gpt", "OpenAI/Python 1.54.0"), + ] { + let r = far(target, body, None, member); + assert_eq!(user_agent(&r), [fingerprint.as_bytes()], "{target}"); + } +} + +#[test] +fn a_translated_far_end_sees_the_far_dialect_s_fingerprint_never_the_caller_s() { + for (target, body, member, fingerprint) in [ + ( + "/v1/chat/completions", + CHAT, + "claude", + "Anthropic/Python 0.39.0", + ), + ("/v1/messages", MESSAGES, "gpt", "OpenAI/Python 1.54.0"), + ( + "/v1/chat/completions", + CHAT, + "gem", + "google-genai-sdk/0.8.0 gl-python/3.11", + ), + ] { + for caller in [None, Some("curl/8.5.0")] { + let r = far(target, body, caller, member); + assert_eq!( + user_agent(&r), + [fingerprint.as_bytes()], + "{target} -> {member}, caller {caller:?}" + ); + } + } +} From 5816b1e9e70e460afa3915aab4b56e876dfebba9 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:36:47 -0700 Subject: [PATCH 09/49] DECL-FOLD on the llm flip: every door plane's Statement folds into the kernel plane registry before the config prepass (ARCHITECT RULING 2026-10-03 Q-DEL-A2A-DECL; BUSBAR-1.6.0.md #49 OWNER-LOCKED: a plane's config section comes from its Statement `sections.owns`; R2-C), ported from 9b277c574d with the fallback/probe facts of 2deb3c952e - contract: plane_calls::PlaneRegistration (the registry facts a door states, its own validate, and `fallback`: the tail's TAIL_FALLBACK). - loader: the plane kind keeps the tail's nouns, billable families, TAIL_PROBES and TAIL_FALLBACK at bind; kinds::plane::registration builds one registration for a linked and a dropped door. - kernel: plane::door::fold turns a registration into a PlaneDecl whose every word is the door's. - root: plane_rows folds every door candidate beside the linked rows; a key a linked row registers keeps that row, so under `llm-on-driver` the proto-llm row keeps the llm key until the default flip. Not taken: the a2a pieces (DECL-FOLD 2's facing/audience, CLAIM_PATTERN, caller_credential_refusal), which are not on the plane ABI of this tree. --- crates/busbar-contract/src/plane_calls.rs | 53 ++ crates/busbar-kernel/src/plane/door.rs | 512 ++++++++++++++++++ crates/busbar-kernel/src/plane/mod.rs | 1 + .../src/plane/tests/door_tests.rs | 203 +++++++ crates/busbar/src/root/boot.rs | 2 +- crates/busbar/src/root/linked.rs | 38 +- .../plugin-loader/src/dispatch/kinds/plane.rs | 96 ++++ .../src/tests/plane_conformance_tests.rs | 32 ++ 8 files changed, 935 insertions(+), 2 deletions(-) create mode 100644 crates/busbar-kernel/src/plane/door.rs create mode 100644 crates/busbar-kernel/src/plane/tests/door_tests.rs diff --git a/crates/busbar-contract/src/plane_calls.rs b/crates/busbar-contract/src/plane_calls.rs index 21fbc67705..63d6784fae 100644 --- a/crates/busbar-contract/src/plane_calls.rs +++ b/crates/busbar-contract/src/plane_calls.rs @@ -70,6 +70,59 @@ pub type Grow<'a, I, O> = &'a mut dyn FnMut(&O, &mut I); /// last crossing returned. pub type Lent = Arc<dyn Any + Send + Sync>; +/// A door's own `validate` over a whole section (its settings, JSON): `Ok`, or the door's words. +pub type SectionJudge = Arc<dyn Fn(&[u8]) -> Result<(), String> + Send + Sync>; + +/// THE REGISTRY FACTS A PLANE DOOR STATES (ARCHITECT RULING 2026-10-03, Q-DEL-A2A-DECL; spec #49 +/// and R2-C: a plane's config section, scope kinds and the rest are DERIVED from its Statement, +/// never declared in the root). The loader reads them off a door's Statement (`sections`) and its +/// plane tail, linked or dropped alike, and the kernel folds them into its plane registry before the +/// config prepass (`busbar_kernel::plane::door::fold`). Every word is kept for the process. +#[derive(Clone)] +pub struct PlaneRegistration { + /// The plane's registry key: the door's Statement name. + pub key: &'static str, + /// The section the Statement declares (`SECTION_DECLARING`): the plane's config verb. + pub section: &'static str, + /// The tail's label (`PlaneTail::label`). + pub label: &'static str, + /// The tail's subject noun. + pub subject_noun: &'static str, + /// The tail's admin noun. + pub admin_noun: &'static str, + /// The tail's audit kind. + pub audit_kind: &'static str, + /// The tail's signing domain and key-id prefix; `None` = it signs nothing. + pub signing: Option<(&'static str, &'static str)>, + /// The tail's dialects, in order (the plane's wire formats). + pub dialects: Vec<&'static str>, + /// The tail's scope kinds. + pub scope_kinds: Vec<&'static str>, + /// The tail's billable classes, each with its unit family. + pub billable_classes: Vec<(&'static str, &'static str)>, + /// The tail's fee units. + pub fee_units: Vec<&'static str>, + /// The tail's record kinds. + pub record_kinds: Vec<&'static str>, + /// The tail's kernel-owned trust keys. + pub trust_keys: Vec<crate::plane::TrustKeyDecl>, + /// Whether the tail states `TAIL_FALLBACK`: the plane is the catch-all every unclaimed path + /// falls through to, and its card is the flat one (at most one registered plane). + pub fallback: bool, + /// The door's own `validate` over a whole section (its settings, JSON): `Ok`, or the door's + /// words. The kernel runs it where the section is parsed and where an admin write lands. + pub validate: SectionJudge, +} + +impl std::fmt::Debug for PlaneRegistration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PlaneRegistration") + .field("key", &self.key) + .field("section", &self.section) + .finish_non_exhaustive() + } +} + /// WHAT A PLANE INSTANCE DECLARES FOR ITS ADMISSION: its host label and what its Statement tail /// states, read once at bind (the words kept for the process): its record kinds, its signing /// declaration (domain, key-id prefix), its scope kinds and its trust keys. The kernel admits the diff --git a/crates/busbar-kernel/src/plane/door.rs b/crates/busbar-kernel/src/plane/door.rs new file mode 100644 index 0000000000..fc22249ad1 --- /dev/null +++ b/crates/busbar-kernel/src/plane/door.rs @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE DOOR PLANES' REGISTRY FOLD (ARCHITECT RULING 2026-10-03, Q-DEL-A2A-DECL; `BUSBAR-1.6.0.md` +//! #49, OWNER-LOCKED: "the config SECTION name for a plane comes from that plane's Statement +//! `sections.owns`"; R2-C: duplicate declarations are derived, not declared). +//! +//! A plane served through its memory-ABI door states everything its registry row holds: its +//! declaring section, and in its plane tail its scope kinds, nouns, signing, billable classes, fee +//! units, record kinds, trust keys and passthrough refusal. The loader reads those facts off a door, +//! linked or dropped alike, as a [`PlaneRegistration`]; [`fold`] turns one into the kernel's +//! [`PlaneDecl`], so the composition root installs it beside the linked rows BEFORE the config +//! prepass, and the prepass lifts the plane's section from the registry like any other. No root row +//! and no plane literal: every word here is the door's. +//! +//! What the row DOES: +//! * its section parses through the door's own `validate` and is carried as written +//! ([`DoorSection`]), so a section the door refuses fails `--validate` and boot in the door's words; +//! * when the section is one of the 1.5.3 named-definition maps (the kernel's frozen +//! `NAMED_MAP_SECTIONS`: a registration per key beside the reserved section words), the admin +//! named-map CRUD (`/<section>/{name}`) is generated from the row: the reads +//! ([`PlaneDecl::named_def_list`], `named_def_get`, `registry_contains`) project each registration +//! onto the shared view (its trust keys read by the kernel, which owns them), and a write is +//! judged by the door's `validate` over a one-entry section, in the 1.5.3 named-map wording; +//! * the per-registration hook attach is re-resolved from the section's `hooks:` lists. +//! +//! What it does NOT do: claim a path, bind an audience or build an engine runtime. The door's routes +//! are mounted by the serve row, and the door serves them. +//! +//! THE HOOK TABLE. A registry row's hooks are plain `fn` pointers, which carry no state, so each +//! folded door gets the hooks of its own table index ([`MAX_DOOR_PLANES`] of them, each a +//! monomorphised copy that reads its door by index). A process folding more doors than that is +//! refused at boot, naming the cap. + +use std::sync::{Mutex, OnceLock}; + +use busbar_contract::plane::{BillableClass, PlaneDeclaration, TrustKeyDecl, TrustRole}; +use busbar_contract::plane_calls::PlaneRegistration; + +use crate::plane::config::{ContainerGateInputs, PlaneCfg}; +use crate::plane::registry::{BuildCtx, PlaneDecl, PlaneHooks}; + +/// The most door planes one process folds into its registry. +pub const MAX_DOOR_PLANES: usize = 16; + +/// One folded door: its registration and the row built from it. +struct Folded { + reg: PlaneRegistration, + dialects: &'static [&'static str], + decl: &'static PlaneDecl, +} + +/// The folded doors, by hook-table index. +static DOORS: [OnceLock<Folded>; MAX_DOOR_PLANES] = [const { OnceLock::new() }; MAX_DOOR_PLANES]; + +/// The next free hook-table index; held while a fold claims one. +static NEXT: Mutex<usize> = Mutex::new(0); + +/// FOLD a door plane's registration into a registry row. Folding the same key twice answers the row +/// the first fold built (a test binary folds per test; the composition root folds once). +/// +/// # Errors +/// +/// A registration that names no key or no section (a plane is installed by its name and configured +/// by its section), or a process past [`MAX_DOOR_PLANES`]. +pub fn fold(reg: PlaneRegistration) -> Result<&'static PlaneDecl, String> { + if reg.key.is_empty() || reg.section.is_empty() { + return Err(format!( + "plane door `{}` states no name or no declaring section; a plane is installed by its \ + name and configured by its section", + reg.key + )); + } + let mut next = NEXT + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(f) = DOORS[..*next] + .iter() + .filter_map(OnceLock::get) + .find(|f| f.reg.key == reg.key) + { + return Ok(f.decl); + } + let index = *next; + if index >= MAX_DOOR_PLANES { + return Err(format!( + "plane door `{}`: this process already folds {MAX_DOOR_PLANES} door planes, the most one \ + process folds", + reg.key + )); + } + let declaration = declaration_of(®); + // The named-map surface is the 1.5.3 named-definition maps' (the frozen section list), so a + // door plane whose verb is one of them answers its CRUD and any other answers none. + let named = busbar_kernel::plane::config::NAMED_MAP_SECTIONS.contains(®.section); + let decl: &'static PlaneDecl = Box::leak(Box::new(PlaneDecl::assemble( + declaration, + HOOK_TABLE[index].clone_hooks(named), + ))); + let dialects: &'static [&'static str] = reg.dialects.clone().leak(); + let _ = DOORS[index].set(Folded { + reg, + dialects, + decl, + }); + *next = index + 1; + Ok(decl) +} + +/// The registration's contract declaration, every word the door's. +fn declaration_of(reg: &PlaneRegistration) -> PlaneDeclaration { + PlaneDeclaration { + key: reg.key, + fallback: reg.fallback, + config_section: reg.section, + scope_kinds: reg.scope_kinds.clone().leak(), + subject_noun: reg.subject_noun, + admin_noun: reg.admin_noun, + audit_kind: reg.audit_kind, + card_signing_domain: reg.signing.map(|(d, _)| d), + card_kid_prefix: reg.signing.map(|(_, p)| p), + owned_config_sections: &[], + billable_classes: reg + .billable_classes + .iter() + .map(|&(class, family)| BillableClass { class, family }) + .collect::<Vec<_>>() + .leak(), + fee_units: reg.fee_units.clone().leak(), + metric_families: &[], + record_kinds: reg.record_kinds.clone().leak(), + required_config_sections: &[], + trust_keys: reg.trust_keys.clone().leak(), + served_op_classes: &[], + } +} + +/// The folded door at `index`. +fn door(index: usize) -> Option<&'static Folded> { + DOORS.get(index).and_then(OnceLock::get) +} + +// ── THE SECTION ─────────────────────────────────────────────────────────────────────────────── + +/// A DOOR PLANE'S SECTION, as written: the door judged it (`validate`) and is handed it raw; the +/// kernel reads only the words it owns (the registrations' names, their trust keys and `hooks:` +/// lists, and the reserved section words). Also the plane's per-generation runtime slot, which the +/// named-map reads project. +#[derive(Debug, Clone)] +pub struct DoorSection { + /// The section's key. + pub section: &'static str, + /// The section as written (`Null` when absent). + pub value: serde_yaml::Value, +} + +impl DoorSection { + fn registrations(&self) -> impl Iterator<Item = (&str, &serde_yaml::Value)> { + crate::trust::section::registrations(&self.value) + } + + /// A hook list under `hooks:` in `v`, as written (non-strings skipped: the door refused them). + fn hooks_of(v: &serde_yaml::Value) -> Vec<String> { + v.as_mapping() + .and_then(|m| m.get(busbar_contract::plugin::Kind::Hook.root())) + .and_then(serde_yaml::Value::as_sequence) + .map(|s| { + s.iter() + .filter_map(|h| h.as_str().map(str::to_string)) + .collect() + }) + .unwrap_or_default() + } +} + +impl PlaneCfg for DoorSection { + fn secret_refs(&self) -> Vec<(String, &busbar_contract::secret_ref::SecretRef)> { + // A door resolves its own settings' references through the secret service it is handed; the + // kernel holds no typed reference of a door's section. + Vec::new() + } + + fn contains_def(&self, name: &str) -> bool { + self.registrations().any(|(n, _)| n == name) + } + + fn def_names(&self) -> Vec<&str> { + self.registrations().map(|(n, _)| n).collect() + } + + fn entry_document(&self, name: &str) -> Option<serde_json::Value> { + self.registrations() + .find(|(n, _)| *n == name) + .and_then(|(_, v)| serde_json::to_value(v).ok()) + } + + fn insert_def(&mut self, name: &str, def: &serde_json::Value) -> Result<(), String> { + let entry: serde_yaml::Value = serde_yaml::to_value(def) + .map_err(|e| format!("invalid `{}.{name}` definition: {e}", self.section))?; + if !self.value.is_mapping() { + self.value = serde_yaml::Value::Mapping(serde_yaml::Mapping::new()); + } + if let Some(m) = self.value.as_mapping_mut() { + m.insert(serde_yaml::Value::String(name.to_string()), entry); + } + Ok(()) + } + + fn container_gates(&self) -> ContainerGateInputs { + ContainerGateInputs { + section_hooks: Self::hooks_of(&self.value), + containers: self + .registrations() + .map(|(n, v)| (n.to_string(), Self::hooks_of(v))) + .collect(), + } + } + + fn validate_registry(&self) -> Result<(), String> { + Ok(()) + } + + fn is_present(&self) -> bool { + match &self.value { + serde_yaml::Value::Null => false, + serde_yaml::Value::Mapping(m) => !m.is_empty(), + _ => true, + } + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn clone_box(&self) -> Box<dyn PlaneCfg> { + Box::new(self.clone()) + } + + fn clone_arc_any(&self) -> std::sync::Arc<dyn std::any::Any + Send + Sync> { + std::sync::Arc::new(self.clone()) + } +} + +/// The section as the door's `validate` reads it: JSON, in the order it was written. +fn settings_of(value: &serde_yaml::Value) -> Result<Vec<u8>, String> { + if value.is_null() { + return Ok(Vec::new()); + } + serde_json::to_vec(value).map_err(|e| format!("the section is not representable as JSON: {e}")) +} + +/// One registration projected onto the shared named-definition view: its name, and the trust keys +/// the kernel owns (the pin's mechanism and whether a fingerprint is approved, the re-verification +/// cadence as written or its declared default); `None` for a key the plane does not declare. +fn view_of( + name: &str, + entry: &serde_yaml::Value, + keys: &[TrustKeyDecl], +) -> crate::api::NamedDefView { + let map = entry.as_mapping(); + let field = |key: &str| map.and_then(|m| m.get(key)).filter(|v| !v.is_null()); + let pin = keys.iter().find(|k| k.role == TrustRole::Pin); + let ttl = keys.iter().find(|k| k.role == TrustRole::ReverifyTtl); + let pin_obj = pin + .and_then(|k| field(k.key)) + .and_then(serde_yaml::Value::as_mapping); + crate::api::NamedDefView { + name: name.to_string(), + module: String::new(), + settings_keys: Vec::new(), + max_admin_scope: None, + token_configured: None, + browser_login_configured: None, + pin_mechanism: pin.map(|_| { + pin_obj + .and_then(|m| m.get("mechanism")) + .and_then(serde_yaml::Value::as_str) + .unwrap_or_default() + .to_string() + }), + fingerprint_pinned: pin.map(|_| { + pin_obj + .and_then(|m| m.get("fingerprint")) + .is_some_and(|v| !v.is_null()) + }), + reverify_ttl: ttl.map(|k| { + field(k.key) + .and_then(serde_yaml::Value::as_str) + .map(str::to_string) + .or_else(|| k.default.map(str::to_string)) + .unwrap_or_default() + }), + unparseable: None, + } +} + +// ── THE HOOKS, ONE COPY PER TABLE INDEX ───────────────────────────────────────────────────────── + +fn wire_formats<const I: usize>() -> &'static [&'static str] { + door(I).map_or(&[], |d| d.dialects) +} + +fn build<const I: usize>( + ctx: &BuildCtx, +) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> { + let d = door(I)?; + let mine = |s: &DoorSection| s.section == d.reg.section && s.is_present(); + if let Some(s) = ctx + .agent_defs + .downcast_ref::<DoorSection>() + .filter(|s| mine(s)) + { + return Some(std::sync::Arc::new(s.clone())); + } + let raw = ctx.endpoint_slot.as_deref()?; + let (section, value) = raw.downcast_ref::<(&'static str, serde_yaml::Value)>()?; + let s = DoorSection { + section, + value: value.clone(), + }; + mine(&s).then(|| std::sync::Arc::new(s) as std::sync::Arc<dyn std::any::Any + Send + Sync>) +} + +/// The generation's section of the door at `I`, off the neutral slot seam. +fn slot_section<const I: usize>( + slots: &dyn crate::plane_host::PlaneSlots, +) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> { + slots.plane_slot(door(I)?.reg.key).cloned() +} + +fn named_def_list<const I: usize>( + slots: &dyn crate::plane_host::PlaneSlots, +) -> Vec<crate::api::NamedDefView> { + let (Some(d), Some(slot)) = (door(I), slot_section::<I>(slots)) else { + return Vec::new(); + }; + slot.downcast_ref::<DoorSection>() + .map(|s| { + s.registrations() + .map(|(n, v)| view_of(n, v, &d.reg.trust_keys)) + .collect() + }) + .unwrap_or_default() +} + +fn named_def_get<const I: usize>( + slots: &dyn crate::plane_host::PlaneSlots, + name: &str, +) -> Option<crate::api::NamedDefView> { + let d = door(I)?; + let slot = slot_section::<I>(slots)?; + let s = slot.downcast_ref::<DoorSection>()?; + let view = s + .registrations() + .find(|(n, _)| *n == name) + .map(|(n, v)| view_of(n, v, &d.reg.trust_keys)); + view +} + +fn registry_contains<const I: usize>( + slots: &dyn crate::plane_host::PlaneSlots, + name: &str, +) -> bool { + let Some(slot) = slot_section::<I>(slots) else { + return false; + }; + slot.downcast_ref::<DoorSection>() + .is_some_and(|s| s.contains_def(name)) +} + +fn reresolve_gates<const I: usize>(next: &mut dyn crate::plane_host::ContainerGateSink) { + let Some(d) = door(I) else { + return; + }; + let section = next + .plane_slot(d.reg.key) + .and_then(|slot| slot.downcast_ref::<DoorSection>().cloned()); + let gates = section + .map(|s| s.container_gates()) + .unwrap_or(ContainerGateInputs { + section_hooks: Vec::new(), + containers: Vec::new(), + }); + let containers: Vec<(&str, &[String])> = gates + .containers + .iter() + .map(|(n, h)| (n.as_str(), h.as_slice())) + .collect(); + next.reresolve_container_gates(d.reg.key, &containers, &gates.section_hooks); +} + +/// An admin write of one registration, judged by the door over a one-entry section. The door's +/// value rule speaks for itself (it names its registration, `` `<section>.<name>`: ``); any other +/// refusal is a shape refusal and reads as the 1.5.3 named-map sentence the in-core sections speak +/// (`invalid `<section>.<name>` definition: …`). +fn config_validate<const I: usize>(name: &str, def: &serde_json::Value) -> Result<(), String> { + let Some(d) = door(I) else { + return Ok(()); + }; + let section = d.reg.section; + let one = serde_json::json!({ name: def }); + let bytes = serde_json::to_vec(&one).map_err(|e| e.to_string())?; + (d.reg.validate)(&bytes).map_err(|e| { + if e.starts_with(&format!("`{section}.{name}`")) { + e + } else { + format!("invalid `{section}.{name}` definition: {e}") + } + }) +} + +fn parse_section<const I: usize>(value: &serde_yaml::Value) -> Result<Box<dyn PlaneCfg>, String> { + let Some(d) = door(I) else { + return Err("a door plane's section was parsed before its door was folded".to_string()); + }; + (d.reg.validate)(&settings_of(value)?)?; + Ok(Box::new(DoorSection { + section: d.reg.section, + value: value.clone(), + })) +} + +fn default_section<const I: usize>() -> Box<dyn PlaneCfg> { + Box::new(DoorSection { + section: door(I).map_or("", |d| d.reg.section), + value: serde_yaml::Value::Null, + }) +} + +/// One table index's hooks, as data a `const` table can hold. +struct HookRow { + wire_format_names: fn() -> &'static [&'static str], + build: fn(&BuildCtx) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>, + config_validate: fn(&str, &serde_json::Value) -> Result<(), String>, + named_def_list: fn(&dyn crate::plane_host::PlaneSlots) -> Vec<crate::api::NamedDefView>, + named_def_get: fn(&dyn crate::plane_host::PlaneSlots, &str) -> Option<crate::api::NamedDefView>, + registry_contains: fn(&dyn crate::plane_host::PlaneSlots, &str) -> bool, + reresolve_gates: fn(&mut dyn crate::plane_host::ContainerGateSink), + parse_section: fn(&serde_yaml::Value) -> Result<Box<dyn PlaneCfg>, String>, + default_section: fn() -> Box<dyn PlaneCfg>, +} + +impl HookRow { + const fn of<const I: usize>() -> HookRow { + HookRow { + wire_format_names: wire_formats::<I>, + build: build::<I>, + config_validate: config_validate::<I>, + named_def_list: named_def_list::<I>, + named_def_get: named_def_get::<I>, + registry_contains: registry_contains::<I>, + reresolve_gates: reresolve_gates::<I>, + parse_section: parse_section::<I>, + default_section: default_section::<I>, + } + } + + /// The registry row's hooks: this index's, and nothing a door plane does not do; the named-map + /// surface only when `named`. + fn clone_hooks(&self, named: bool) -> PlaneHooks { + PlaneHooks { + wire_format_names: self.wire_format_names, + claims: |_| Vec::new(), + admission: |_| None, + build: self.build, + routes: None, + admin_routes: None, + openapi: None, + hydrate: None, + start: None, + config_validate: named.then_some(self.config_validate), + named_def_list: named.then_some(self.named_def_list), + named_def_get: named.then_some(self.named_def_get), + registry_contains: named.then_some(self.registry_contains), + reresolve_gates: Some(self.reresolve_gates), + openapi_schemas: None, + on_swap: None, + parse_section: Some(self.parse_section), + parse_endpoint: None, + lower_endpoint: None, + build_runtime: None, + viewer: None, + retain_verify_gates: None, + default_section: Some(self.default_section), + resolve_provider: None, + } + } +} + +/// Every table index's hooks. +static HOOK_TABLE: [HookRow; MAX_DOOR_PLANES] = [ + HookRow::of::<0>(), + HookRow::of::<1>(), + HookRow::of::<2>(), + HookRow::of::<3>(), + HookRow::of::<4>(), + HookRow::of::<5>(), + HookRow::of::<6>(), + HookRow::of::<7>(), + HookRow::of::<8>(), + HookRow::of::<9>(), + HookRow::of::<10>(), + HookRow::of::<11>(), + HookRow::of::<12>(), + HookRow::of::<13>(), + HookRow::of::<14>(), + HookRow::of::<15>(), +]; + +#[cfg(test)] +#[path = "tests/door_tests.rs"] +mod tests; diff --git a/crates/busbar-kernel/src/plane/mod.rs b/crates/busbar-kernel/src/plane/mod.rs index a9ddc6e8dd..12798beaf8 100644 --- a/crates/busbar-kernel/src/plane/mod.rs +++ b/crates/busbar-kernel/src/plane/mod.rs @@ -92,6 +92,7 @@ pub mod approvals; pub mod config; +pub mod door; pub mod observe; pub(crate) mod quarantine; /// The durable demotion record, which the root attaches to the kernel's host services. diff --git a/crates/busbar-kernel/src/plane/tests/door_tests.rs b/crates/busbar-kernel/src/plane/tests/door_tests.rs new file mode 100644 index 0000000000..04472448ea --- /dev/null +++ b/crates/busbar-kernel/src/plane/tests/door_tests.rs @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE DOOR PLANES' REGISTRY FOLD (ARCHITECT RULING 2026-10-03, Q-DEL-A2A-DECL): a registration a +//! door states becomes a registry row whose every word is the door's, whose section is judged by +//! the door's own `validate`, and whose named-map surface is generated from the row. + +use std::sync::Arc; + +use busbar_contract::plane::{PinMechanismDecl, TrustKeyDecl, TrustRole}; +use busbar_contract::plane_calls::PlaneRegistration; + +use super::*; + +/// A 1.5.3 named-definition section, read off the frozen list (no literal here). +const NAMED: &str = busbar_kernel::plane::config::NAMED_MAP_SECTIONS[3]; + +/// A registration as a door states one: a named-definition section `fleet`, a pin and a cadence, +/// and a `validate` that refuses an entry without `url` in its own value-rule words and anything +/// that is not a mapping as a shape refusal. +fn registration(key: &'static str, section: &'static str) -> PlaneRegistration { + PlaneRegistration { + key, + section, + label: "Fleet", + subject_noun: "fleet member", + admin_noun: "member", + audit_kind: "fleet_member", + signing: Some(("fleet/signing/v1", "fleet-")), + dialects: vec!["first", "second"], + fallback: false, + scope_kinds: vec!["member"], + billable_classes: vec![("bytes", "byte")], + fee_units: vec!["request"], + record_kinds: vec!["member_row"], + trust_keys: vec![ + TrustKeyDecl { + key: "pin", + role: TrustRole::Pin, + fingerprint: true, + default: None, + mechanisms: &[PinMechanismDecl { + token: "unpinned", + root: false, + }], + }, + TrustKeyDecl { + key: "reverify_ttl", + role: TrustRole::ReverifyTtl, + fingerprint: false, + default: Some("5s"), + mechanisms: &[], + }, + ], + validate: Arc::new(move |bytes: &[u8]| { + if bytes.is_empty() { + return Ok(()); + } + let doc: serde_json::Value = + serde_json::from_slice(bytes).map_err(|e| e.to_string())?; + let Some(map) = doc.as_object() else { + return Err("expected a map".to_string()); + }; + for (name, entry) in map { + if name == "hooks" || name == "upstream_credentials" { + continue; + } + if !entry.is_object() { + return Err("invalid type: expected struct Member".to_string()); + } + if entry.get("url").is_none() { + return Err(format!("`{section}.{name}`: `url:` must name the endpoint")); + } + } + Ok(()) + }), + } +} + +#[test] +fn a_door_registration_folds_into_a_row_every_word_of_which_is_the_doors() { + let decl = fold(registration("door-fold-row", "door_fold_row")).expect("folds"); + assert_eq!(decl.key, "door-fold-row"); + assert_eq!(decl.config_section, "door_fold_row"); + assert_eq!(decl.scope_kinds, &["member"]); + assert_eq!( + (decl.subject_noun, decl.admin_noun, decl.audit_kind), + ("fleet member", "member", "fleet_member") + ); + assert_eq!(decl.card_signing_domain, Some("fleet/signing/v1")); + assert_eq!(decl.card_kid_prefix, Some("fleet-")); + assert_eq!(decl.billable_classes[0].class, "bytes"); + assert_eq!(decl.billable_classes[0].family, "byte"); + assert_eq!(decl.fee_units, &["request"]); + assert_eq!(decl.record_kinds, &["member_row"]); + assert_eq!(decl.trust_keys.len(), 2); + assert_eq!((decl.wire_format_names)(), &["first", "second"]); + assert!(decl.parse_section.is_some()); + assert!( + decl.named_def_list.is_none() && decl.config_validate.is_none(), + "a section that is no 1.5.3 named-definition map answers no named-map CRUD" + ); + // Folding the same key again answers the first row. + let again = fold(registration("door-fold-row", "door_fold_row")).expect("folds"); + assert!(std::ptr::eq(decl, again)); +} + +#[test] +fn a_registration_with_no_name_or_section_is_refused() { + assert!(fold(registration("", "s")).is_err()); + assert!(fold(registration("door-fold-nosection", "")).is_err()); +} + +#[test] +fn the_section_is_judged_by_the_door_and_carried_as_written() { + let decl = fold(registration("door-fold-parse", NAMED)).expect("folds"); + let parse = decl.parse_section.expect("a section parser"); + let bad: serde_yaml::Value = serde_yaml::from_str("zed: {pin: {mechanism: unpinned}}").unwrap(); + assert_eq!( + parse(&bad).unwrap_err(), + format!("`{NAMED}.zed`: `url:` must name the endpoint"), + "the door's refusal, in its words" + ); + let good: serde_yaml::Value = serde_yaml::from_str( + "hooks: [audit]\nzed: {url: 'https://z', hooks: [gate]}\nalpha: {url: 'https://a'}\n", + ) + .unwrap(); + let cfg = parse(&good).expect("the door accepts it"); + assert!(cfg.is_present()); + assert_eq!( + cfg.def_names(), + vec!["zed", "alpha"], + "written order, reserved words skipped" + ); + assert!(cfg.contains_def("alpha") && !cfg.contains_def("hooks")); + let gates = cfg.container_gates(); + assert_eq!(gates.section_hooks, vec!["audit".to_string()]); + assert_eq!( + gates.containers, + vec![ + ("zed".to_string(), vec!["gate".to_string()]), + ("alpha".to_string(), Vec::new()) + ] + ); + assert!(!(decl.default_section.expect("a default"))().is_present()); +} + +#[test] +fn a_registration_view_reads_the_kernel_owned_trust_keys() { + let decl = fold(registration("door-fold-view", NAMED)).expect("folds"); + let keys = decl.trust_keys; + let pinned: serde_yaml::Value = serde_yaml::from_str( + "url: https://z\npin: {mechanism: jws, key: k, fingerprint: 'sha256:x'}\nreverify_ttl: 1m", + ) + .unwrap(); + let v = view_of("zed", &pinned, keys); + assert_eq!(v.name, "zed"); + assert_eq!(v.pin_mechanism.as_deref(), Some("jws")); + assert_eq!(v.fingerprint_pinned, Some(true)); + assert_eq!(v.reverify_ttl.as_deref(), Some("1m")); + let bare: serde_yaml::Value = serde_yaml::from_str("url: https://a").unwrap(); + let v = view_of("alpha", &bare, keys); + assert_eq!(v.fingerprint_pinned, Some(false)); + assert_eq!( + v.reverify_ttl.as_deref(), + Some("5s"), + "the declared default" + ); + let v = view_of("alpha", &bare, &[]); + assert_eq!( + (v.pin_mechanism, v.fingerprint_pinned, v.reverify_ttl), + (None, None, None), + "a plane that declares no trust key shows none" + ); +} + +#[test] +fn an_admin_write_is_judged_by_the_door_in_the_named_map_wording() { + let decl = fold(registration("door-fold-write", NAMED)).expect("folds"); + let validate = decl.config_validate.expect("a write judge"); + assert_eq!( + validate("zed", &serde_json::json!({"url": "https://z"})), + Ok(()) + ); + assert_eq!( + validate("zed", &serde_json::json!({"pin": {}})).unwrap_err(), + format!("`{NAMED}.zed`: `url:` must name the endpoint"), + "a value rule names its registration itself" + ); + assert_eq!( + validate("zed", &serde_json::json!("nope")).unwrap_err(), + format!("invalid `{NAMED}.zed` definition: invalid type: expected struct Member"), + "a shape refusal reads as the 1.5.3 named-map sentence" + ); + let mut cfg = (decl.default_section.expect("a default"))(); + cfg.insert_def("zed", &serde_json::json!({"url": "https://z"})) + .expect("inserts"); + assert_eq!(cfg.def_names(), vec!["zed"]); + assert_eq!( + cfg.entry_document("zed"), + Some(serde_json::json!({"url": "https://z"})) + ); +} diff --git a/crates/busbar/src/root/boot.rs b/crates/busbar/src/root/boot.rs index c5d75e79d2..c1da2c9669 100644 --- a/crates/busbar/src/root/boot.rs +++ b/crates/busbar/src/root/boot.rs @@ -495,7 +495,7 @@ pub fn dropped_planes_of( } /// The door planes [`dropped_planes_of`] discovered, waiting for the dispatcher. -static DOOR_CANDIDATES: std::sync::OnceLock<Vec<crate::root::loader::boot::Candidate>> = +pub(crate) static DOOR_CANDIDATES: std::sync::OnceLock<Vec<crate::root::loader::boot::Candidate>> = std::sync::OnceLock::new(); /// The door planes [`load_door_planes`] bound, held for the process. diff --git a/crates/busbar/src/root/linked.rs b/crates/busbar/src/root/linked.rs index ac31f163e9..a1048ce76d 100644 --- a/crates/busbar/src/root/linked.rs +++ b/crates/busbar/src/root/linked.rs @@ -460,13 +460,49 @@ pub fn plane_rows( .map(hot_plane_row) .collect::<Result<Vec<PlaneDecl>, String>>()? .leak(); - let rows: Vec<&'static PlaneDecl> = linked.planes.iter().chain(hot_rows).collect(); + let mut rows: Vec<&'static PlaneDecl> = linked.planes.iter().chain(hot_rows).collect(); + rows.extend(door_rows()?); let declared: Vec<&PlaneDeclaration> = rows.iter().map(|d| &d.declaration).collect(); busbar_contract::plane::check_metric_families(&declared, PLANE_CARRIED_SERIES)?; busbar_contract::plane::check_served_op_classes(&declared)?; Ok(rows) } +/// THE DOOR PLANES' REGISTRY ROWS (DECL-FOLD; ARCHITECT RULING 2026-10-03, Q-DEL-A2A-DECL; spec #49 +/// and R2-C): every plane [`dropped_planes_of`] discovered through a door, linked or dropped, bound +/// once through the loader's one load on a dispatcher of its own (the process's is built after the +/// configuration is read, and the rows must be in before the config prepass), and its Statement +/// folded into a registry row by the kernel (`busbar_kernel::plane::door::fold`). The row's every +/// word is the door's; a plane a linked row already registers keeps that row (the boot fold keeps +/// the first row of a key). A door that will not bind refuses the boot, as its load would. +fn door_rows() -> Result<Vec<&'static PlaneDecl>, String> { + let doors = crate::root::boot::DOOR_CANDIDATES + .get() + .map_or(&[][..], Vec::as_slice); + if doors.is_empty() { + return Ok(Vec::new()); + } + let probe: &'static crate::root::loader::dispatch::Dispatcher = + Box::leak(Box::new(crate::root::loader::dispatch::Dispatcher::new( + crate::root::loader::dispatch::DispatchConfig::default(), + ))); + crate::root::loader::boot::load_planes( + doors, + crate::root::boot::plugin_logs(), + Arc::new(crate::root::loader::dispatch::NoSink), + probe.adopter(), + u32::MAX, + None, + )? + .into_iter() + .map(|(_, plane)| { + busbar_kernel::plane::door::fold(crate::root::loader::dispatch::kinds::plane::registration( + Arc::new(plane), + )) + }) + .collect() +} + /// THE FIRST-PARTY SERIES A PLANE MAY CARRY — the host-owned list a plane's `busbar_`-named metric /// family must be on, with exactly these label keys in this order, to be admitted (ARCHITECT RULING /// S2-c; #65: a plane cannot mint an arbitrary `busbar_` series, and a carried one renders byte for diff --git a/crates/plugin-loader/src/dispatch/kinds/plane.rs b/crates/plugin-loader/src/dispatch/kinds/plane.rs index 4deb2b70df..7a05fccd45 100644 --- a/crates/plugin-loader/src/dispatch/kinds/plane.rs +++ b/crates/plugin-loader/src/dispatch/kinds/plane.rs @@ -136,6 +136,15 @@ pub struct ServedFacts { /// (THE DESIGN §6 step 2: a provider entry's `auth:`, else this), and the style's parameters /// for it, a JSON object's text (empty: none; ARCHITECT RULING 2026-10-03, Q-L6-AUTHPARAMS). pub dialect_auth: Vec<(u32, &'static str, &'static [u8])>, + /// The tail's `label`, `subject_noun` and `admin_noun` (what the kernel's registry entry names + /// the plane and one registration by). + pub nouns: (&'static str, &'static str, &'static str), + /// The tail's billable classes' unit families, parallel to [`Self::billable_classes`]. + pub billable_families: Vec<&'static str>, + /// Whether the tail states `TAIL_PROBES`: the plane answers the kernel's health probe unit. + pub probes: bool, + /// Whether the tail states `TAIL_FALLBACK`: the plane is the catch-all. + pub fallback: bool, } /// ONE NEED'S RESPONSE-HEAD RULE, as its Statement declares it. @@ -246,6 +255,17 @@ fn tail_facts(st: &Statement) -> Result<PlaneFacts, String> { .into_iter() .map(|d| (d.dialect, kept(d.style), kept_blob(d.params))) .collect(), + nouns: ( + kept(tail.label), + kept(tail.subject_noun), + kept(tail.admin_noun), + ), + billable_families: listed(tail.billable_classes, tail.billable_classes_len) + .into_iter() + .map(|c| kept(c.family)) + .collect(), + probes: tail.flags & busbar_contract::abi::plane::TAIL_PROBES != 0, + fallback: tail.flags & busbar_contract::abi::plane::TAIL_FALLBACK != 0, }, }) } @@ -357,6 +377,82 @@ impl crate::dispatch::Plugin<Plane> { } } +/// THE REGISTRY FACTS `plugin` STATES (ARCHITECT RULING 2026-10-03, Q-DEL-A2A-DECL): its Statement +/// name, its declaring section and its tail's words, read off the facts kept at bind, and its own +/// `validate` (ticket-less; it never pends) over a whole section. The same function for a linked and +/// a dropped door: both bound through the one load. +#[must_use] +pub fn registration( + plugin: Arc<crate::dispatch::Plugin<Plane>>, +) -> busbar_contract::plane_calls::PlaneRegistration { + let served = plugin.served(); + let declared = plugin.declared(); + let (label, subject_noun, admin_noun) = served.nouns; + let key: &'static str = plugin.name().to_string().leak(); + busbar_contract::plane_calls::PlaneRegistration { + key, + section: served.section, + label, + subject_noun, + admin_noun, + audit_kind: served.audit_kind, + signing: declared.signing, + dialects: served.dialects.clone(), + scope_kinds: declared.scope_kinds.clone(), + billable_classes: served + .billable_classes + .iter() + .copied() + .zip(served.billable_families.iter().copied()) + .collect(), + fee_units: served.fee_units.clone(), + record_kinds: declared.record_kinds.clone(), + trust_keys: declared.trust_keys.clone(), + fallback: served.fallback, + validate: Arc::new(move |settings: &[u8]| validate(&plugin, settings)), + } +} + +/// `validate` `settings` on `plugin` (ticket-less; it never pends): `Ok` when READY, else the +/// plugin's words. +fn validate(plugin: &crate::dispatch::Plugin<Plane>, settings: &[u8]) -> Result<(), String> { + use busbar_contract::abi::mechanism::call::{Blob, BLOB_ABSENT, BLOB_JSON}; + use busbar_contract::abi::mechanism::lifecycle::ValidateIn; + let blob = if settings.is_empty() { + Blob { + ptr: std::ptr::null(), + len: 0, + fmt: BLOB_ABSENT, + flags: 0, + } + } else { + Blob { + ptr: settings.as_ptr(), + len: settings.len(), + fmt: BLOB_JSON, + flags: 0, + } + }; + let mut f = Frame::new( + ValidateIn { + head: in_head(), + settings: blob, + err_buf: std::ptr::null_mut(), + err_cap: 0, + }, + out_head(), + ); + let c = plugin.call(life::VALIDATE, &mut f); + match c.outcome { + Outcome::Ready => Ok(()), + o => Err(c + .error + .map(|e| String::from_utf8_lossy(&e).into_owned()) + .filter(|t| !t.is_empty()) + .unwrap_or_else(|| format!("{o:?}"))), + } +} + /// The tail's kernel-owned trust keys, judged PER ELEMENT: each key by `check_trust_keys`, each /// pin's mechanisms by `check_pin_mechanisms`. `check_tail` proved only that no list is counted over /// a NULL pointer; the elements are read here, once, at bind. diff --git a/crates/plugin-loader/src/tests/plane_conformance_tests.rs b/crates/plugin-loader/src/tests/plane_conformance_tests.rs index 0b72d023c7..55a3c88eaf 100644 --- a/crates/plugin-loader/src/tests/plane_conformance_tests.rs +++ b/crates/plugin-loader/src/tests/plane_conformance_tests.rs @@ -1763,4 +1763,36 @@ mod door { assert_eq!(first, published, "generation 1's copy outlives its retire"); } } + + /// DECL-FOLD (ARCHITECT RULING 2026-10-03, Q-DEL-A2A-DECL): the registry facts a door states are + /// read off its Statement and tail at bind, linked and dropped alike, and its own `validate` + /// judges a section: the kernel folds these into its plane registry before the config prepass. + #[test] + fn a_door_states_its_registry_facts_and_judges_its_section() { + let check = |p: Plugin<Plane>| { + let reg = crate::dispatch::kinds::plane::registration(Arc::new(p)); + assert_eq!(reg.key, "plane-door"); + assert_eq!(reg.section, "door"); + assert_eq!( + (reg.label, reg.subject_noun, reg.admin_noun, reg.audit_kind), + ("Door", "door", "door", "door") + ); + assert_eq!(reg.dialects, vec!["door/1"]); + assert_eq!(reg.billable_classes, vec![("bytes", "bytes")]); + assert_eq!(reg.record_kinds, vec!["last"]); + assert!(reg.scope_kinds.is_empty() && reg.trust_keys.is_empty()); + assert_eq!(reg.signing, None); + assert_eq!((reg.validate)(b"{}"), Ok(())); + assert!( + (reg.validate)(plug::BAD_SETTINGS).is_err(), + "the door refuses its bad settings" + ); + format!("{reg:?}") + }; + let linked = check(linked()); + let Some(dropped) = dropped() else { + return; + }; + assert_eq!(linked, check(dropped), "the same facts, whichever door"); + } } From e35bcda143b5ff02471477da4ffb21cde36ab796 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:38:45 -0700 Subject: [PATCH 10/49] FLIP-LLM K7: the door-served llm runs 1.5.5's active health probes through the kernel's probe unit (BUSBAR-1.6.0.md Part 3 section 12 "Nested units, work continuations and probes are units of their own ... a health probe is a kernel-originated unit pinned to one member"; "Probes (K7). A pure probe schedule, bound to the far end; a member's health follows the 1.5.5 lane health configuration"), ported from 2deb3c952e - root::model_egress: each model's provider `health:` over the process defaults resolved per lane (ModelPools.probes), and ModelServing::probe_members for the schedule. - root::serve: the plane serving the `pools` map whose tail states TAIL_PROBES arms a PlaneProbes target per generation over its sealed egress and spawns the kernel probe service's probers on a schedule phase-stable across a config apply; probe unit keys come from the node's one mint (Node::kernel_and_keys). - main: under `llm-on-driver` the legacy row's on-host probers are not spawned beside them, so a lane is probed once, as 1.5.5 probed it. --- crates/busbar/src/main.rs | 9 +++- crates/busbar/src/root/model_egress.rs | 61 +++++++++++++++++++++ crates/busbar/src/root/plane_node.rs | 20 +++++-- crates/busbar/src/root/serve.rs | 75 ++++++++++++++++++++++++-- 4 files changed, 157 insertions(+), 8 deletions(-) diff --git a/crates/busbar/src/main.rs b/crates/busbar/src/main.rs index d60e5c53ab..80271f0ad6 100644 --- a/crates/busbar/src/main.rs +++ b/crates/busbar/src/main.rs @@ -946,7 +946,14 @@ async fn run(data_workers: usize, late_services: std::sync::Arc<root::serve::Lat // first config swap, retiring these boot probers (their `Weak` fails to upgrade) exactly as the old // `Weak<App>` did when the boot snapshot drained. // Built only when some linked entry re-anchors work on it: a build with none holds no host. - let on_host = LINKED.on_host; + // Under the llm fold's switch the llm plane's health probes run through its door as the K7 + // probe unit (`root::serve`'s probe target over the plane's sealed egress), so the legacy row's + // probers are not spawned beside them: one prober per lane, as 1.5.5 ran. + let on_host = if cfg!(feature = "llm-on-driver") { + &[][..] + } else { + LINKED.on_host + }; let boot_host = (!on_host.is_empty()).then(|| busbar_kernel::plane_host::engine_host(&app)); if let Some(host) = &boot_host { for spawn in on_host { diff --git a/crates/busbar/src/root/model_egress.rs b/crates/busbar/src/root/model_egress.rs index dec1b62ceb..cf61dfb8d5 100644 --- a/crates/busbar/src/root/model_egress.rs +++ b/crates/busbar/src/root/model_egress.rs @@ -68,6 +68,9 @@ pub struct ModelPools { /// Each pool's ladder as the lane store's record calls take it (the previous release's /// `resolve_breaker_cfg`: the pool's own, else the default); `""` = the lane-default cell's. records: HashMap<String, busbar_kernel::store::BreakerCfg>, + /// Each model's active health probing (K7): its provider's `health:` over the process-wide + /// defaults, as 1.5.5's prober resolved it per lane. + probes: BTreeMap<String, busbar_kernel::probe::ProbeCfg>, } impl ModelPools { @@ -145,6 +148,33 @@ impl ModelPools { }) .collect(); records.insert(String::new(), busbar_kernel::store::BreakerCfg::default()); + let probes = cfg + .models + .iter() + .filter_map(|(name, m)| { + let health = cfg.providers.get(&m.provider)?.health.as_ref()?; + Some(( + name.clone(), + busbar_kernel::probe::ProbeCfg::resolve( + match health.mode { + busbar_kernel::config::HealthMode::None => { + busbar_kernel::plane_host::HealthModeInput::None + } + busbar_kernel::config::HealthMode::Dead => { + busbar_kernel::plane_host::HealthModeInput::Dead + } + busbar_kernel::config::HealthMode::Active => { + busbar_kernel::plane_host::HealthModeInput::Active + } + }, + health.interval_secs, + health.timeout_secs, + cfg.limits.default_probe_interval_secs, + cfg.limits.default_probe_timeout_secs, + ), + )) + }) + .collect(); ModelPools { pools, lanes, @@ -152,10 +182,41 @@ impl ModelPools { passthrough: cfg.upstream_credentials == busbar_contract::config::UpstreamCreds::Passthrough, records, + probes, } } } +impl ModelServing { + /// THE MEMBERS THE HEALTH-PROBE SERVICE SCHEDULES (K7), in lane order: each model with a + /// probing mode, at its lane's index and destination, under its resolved settings. + #[must_use] + pub fn probe_members( + &self, + ) -> ( + usize, + Vec<(DestinationId, busbar_kernel::probe::ProbeMember)>, + ) { + let mut members: Vec<(DestinationId, busbar_kernel::probe::ProbeMember)> = self + .lanes + .iter() + .filter_map(|(model, lane)| { + let cfg = *self.pools.probes.get(model)?; + Some(( + DestinationId::new(*lane as u64), + busbar_kernel::probe::ProbeMember { + index: *lane, + name: model.clone(), + cfg, + }, + )) + }) + .collect(); + members.sort_by_key(|(_, m)| m.index); + (self.lanes.values().max().map_or(0, |m| m + 1), members) + } +} + /// WHAT THE MODEL-SERVING EGRESS IS SEALED OVER beside the plane's member routes: the pools, the /// lane index of each model in the generation's lane table (the kernel's breaker cells are keyed by /// it), and the breaker unit those cells live in, as the live configuration answers it. diff --git a/crates/busbar/src/root/plane_node.rs b/crates/busbar/src/root/plane_node.rs index b7fa82a81b..e872eda58a 100644 --- a/crates/busbar/src/root/plane_node.rs +++ b/crates/busbar/src/root/plane_node.rs @@ -178,7 +178,7 @@ impl LaneNames { /// One per process. The per-request half is the plane's unit, which a plane's arrival hands this /// node ([`Handed`]) and which is thrown away with the unit. pub struct Node { - kernel: busbar_kernel::teller::Kernel, + kernel: Arc<busbar_kernel::teller::Kernel>, inflight: busbar_kernel::inflight::InFlight, gauge: busbar_kernel::slice::ConcurrencyGauge, canary: busbar_contract::caps::Canary, @@ -191,7 +191,7 @@ pub struct Node { /// The names this node has already put through that interner, so the request path does not put /// them through it again. Shared with the resolver every unit is lent ([`Node::resolver`]). lane_names: Arc<Mutex<LaneNames>>, - next_key: busbar_kernel::door::UnitKeyMint, + next_key: Arc<busbar_kernel::door::UnitKeyMint>, /// How many slots the drop guard has MARKED since the sweep last walked the table. /// /// A counter rather than a walk on every arrival: a unit whose task went away leaves its slot @@ -282,7 +282,7 @@ impl Node { durability_token: kernel.durability_token(), usage_token: kernel.usage_token(), book: std::sync::OnceLock::new(), - kernel, + kernel: Arc::new(kernel), // The data listener already carries the operator-configured inbound-concurrency layer, // which is where this deployment's admission-to-the-node decision is made and has always // been made. A second cap here would be a second answer to one question, and the one @@ -301,7 +301,7 @@ impl Node { resolved: HashMap::new(), consulted: 0, })), - next_key: busbar_kernel::door::UnitKeyMint::default(), + next_key: Arc::default(), marked: AtomicU64::new(0), lost: Mutex::new(HashMap::new()), mono: AtomicU64::new(0), @@ -312,6 +312,18 @@ impl Node { } } + /// The node's kernel and its one unit-key mint, shared with a unit the node does not drive but + /// whose key must be unique beside its own (a door plane's health probe, K7). + #[must_use] + pub fn kernel_and_keys( + &self, + ) -> ( + Arc<busbar_kernel::teller::Kernel>, + Arc<busbar_kernel::door::UnitKeyMint>, + ) { + (Arc::clone(&self.kernel), Arc::clone(&self.next_key)) + } + /// How many of this node's units have driven the engine through the Route seam. /// /// The instrument the switch-over is measured with, and it measures what a status cannot: a diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index e33a81af9f..daa6523ccb 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -348,6 +348,8 @@ pub struct ServedPlane { pub kernel: Arc<KernelServices>, /// Its tail's dialects, in order: what a unit's dialect index names. pub dialects: Vec<&'static str>, + /// The health-probe schedule its generations share, phase-stable across a config apply. + pub probe_schedule: Arc<busbar_kernel::probe::ProbeSchedule>, } impl std::fmt::Debug for ServedPlane { @@ -372,6 +374,9 @@ pub struct PlaneLive { pub pools: DoorPools, /// The sealed egress. pub egress: Option<Arc<Egress>>, + /// The health-probe service's target for this generation (K7), when the plane answers probes + /// and a member probes: held here, so a replaced generation's probers exit at their next tick. + pub probes: Option<Arc<busbar_kernel::plane_driver::PlaneProbes>>, } /// EVERY DOOR PLANE THIS PROCESS SERVES, composed once after the first app is built. @@ -631,7 +636,7 @@ pub fn compose_planes( )); } } - let live = seal_live( + let mut live = seal_live( instance, plugin, &served_facts, @@ -639,9 +644,16 @@ pub fn compose_planes( section, egress, )?; + let driver = Arc::new(driver); + let probe_schedule = Arc::new(busbar_kernel::probe::ProbeSchedule::new( + egress + .and_then(|e| e.reach.models) + .map_or(0, |m| m.probe_members().0), + )); + arm_probes(&driver, &served_facts, &mut live, egress, &probe_schedule); served.planes.push(ServedPlane { instance: instance.clone(), - driver: Arc::new(driver), + driver, snapshot, audit_kind: served_facts.audit_kind, live: arc_swap::ArcSwap::from_pointee(live), @@ -651,6 +663,7 @@ pub fn compose_planes( money: plane_money, kernel: Arc::clone(&kernel), dialects: served_facts.dialects.clone(), + probe_schedule, }); } Ok(served) @@ -744,9 +757,62 @@ fn seal_live( facts, pools, egress, + probes: None, }) } +/// THE HEALTH PROBES OF ONE GENERATION (K7; 1.5.5 `health:` per provider, `none | dead | active`): +/// for the plane serving the `pools` map whose tail states `TAIL_PROBES`, a probe target over +/// `live`'s sealed egress and `driver`, its probers spawned as a new generation of `schedule` (the +/// previous generation's exit at their next tick). A build without the node has no kernel to run +/// a probe unit on and probes nothing. +fn arm_probes( + driver: &Arc<PlaneDriver>, + served_facts: &crate::root::loader::dispatch::kinds::plane::ServedFacts, + live: &mut PlaneLive, + egress: Option<&DoorEgress<'_>>, + schedule: &Arc<busbar_kernel::probe::ProbeSchedule>, +) { + #[cfg(linked_axis_node)] + { + let (Some(sealed), Some(models)) = (&live.egress, egress.and_then(|e| e.reach.models)) + else { + return; + }; + if served_facts.section != busbar_contract::section::RESERVED_POOLS_KEY { + return; + } + let (lanes, members) = models.probe_members(); + if members.is_empty() { + return; + } + let (kernel, keys) = crate::root::plane_node::node().kernel_and_keys(); + let Some(target) = busbar_kernel::plane_driver::PlaneProbes::new( + if served_facts.probes { + busbar_contract::abi::plane::TAIL_PROBES + } else { + 0 + }, + Arc::clone(driver), + Arc::clone(sealed), + kernel, + keys, + (0..lanes) + .map(|i| busbar_contract::DestinationId::new(i as u64)) + .collect(), + ) else { + return; + }; + let target = Arc::new(target); + let members: Vec<busbar_kernel::probe::ProbeMember> = + members.into_iter().map(|(_, m)| m).collect(); + busbar_kernel::probe::spawn_probers(&target, schedule, &members); + live.probes = Some(target); + } + #[cfg(not(linked_axis_node))] + let _ = (driver, served_facts, live, egress, schedule); +} + impl std::fmt::Debug for PlaneLive { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PlaneLive") @@ -798,7 +864,10 @@ impl Served { &walked, Some(egress), ) { - Ok(live) => p.live.store(Arc::new(live)), + Ok(mut live) => { + arm_probes(&p.driver, facts, &mut live, Some(egress), &p.probe_schedule); + p.live.store(Arc::new(live)); + } Err(e) => { tracing::error!(instance = %p.instance, error = %e, "door plane's new configuration did not seal; it keeps serving the previous one"); } From c20c36841a70168d01e4390248298f749632899f Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:55:12 -0700 Subject: [PATCH 11/49] =?UTF-8?q?llm=20plane:=20`accept`=20is=20busbar's?= =?UTF-8?q?=20own=20on=20every=20far=20request=20(transport=20neutrality?= =?UTF-8?q?=20ruling:=20the=20http=20door=20writes=20no=20default=20accept?= =?UTF-8?q?,=20so=20the=20plane=20writes=20it=20where=201.5.5=20did=20?= =?UTF-8?q?=E2=80=94=20every=20llm=20request=20carried=20its=20own=20expli?= =?UTF-8?q?cit=20accept,=20the=20single=20answer's=20or=20the=20dialect's?= =?UTF-8?q?=20stream=20framing,=20never=20a=20client=20default);=20a=20sam?= =?UTF-8?q?e-dialect=20caller's=20accept=20no=20longer=20replaces=20it=20(?= =?UTF-8?q?1.5.5's=20bytes:=20the=20oracle's=20same-dialect=20cells=20carr?= =?UTF-8?q?y=20application/json,=20not=20the=20caller's=20*/*).=20Conforma?= =?UTF-8?q?nce=20pins=20the=20per-request=20fee=20unit=20beside=20the=20fa?= =?UTF-8?q?r=20end's=20counts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../busbar-plane-llm/src/exchange/attempt.rs | 11 ++- crates/busbar-plane-llm/src/plane_door.rs | 84 ------------------- crates/busbar-plane-llm/tests/conformance.rs | 5 +- .../tests/exchange_attempt.rs | 18 ++++ 4 files changed, 30 insertions(+), 88 deletions(-) diff --git a/crates/busbar-plane-llm/src/exchange/attempt.rs b/crates/busbar-plane-llm/src/exchange/attempt.rs index 68a4a2deeb..77a4e80a57 100644 --- a/crates/busbar-plane-llm/src/exchange/attempt.rs +++ b/crates/busbar-plane-llm/src/exchange/attempt.rs @@ -608,6 +608,13 @@ fn legal_field_value(v: &[u8]) -> bool { v.iter().all(|b| *b == b'\t' || (0x20..0x7f).contains(b)) } +/// The `accept` field: busbar's own, the answer framing it reads the far end's reply in (the +/// operation's single answer or its dialect's stream framing), written on every far request exactly +/// as 1.5.5 wrote it (its reqwest client sent its own `accept` and never fell back to a transport +/// default), and never replaced by a same-dialect caller's value (1.5.5's bytes: the oracle's +/// same-dialect cells carry busbar's `application/json`, not the caller's `*/*`). +const ACCEPT: &str = "accept"; + /// The head fields a native client of `lane`'s dialect sends, /// then, when the caller speaks that dialect, every field the caller sent but the ones busbar /// governs ([`governed`]): @@ -648,7 +655,7 @@ fn head_fields( d.egress_stream_accept }); let accept = ( - "accept".to_string(), + ACCEPT.to_string(), handler .egress_accept(stream_accept, wants_stream) .as_bytes() @@ -683,7 +690,7 @@ fn head_fields( .iter() .filter_map(|(name, value)| { let name = std::str::from_utf8(name).ok()?.to_ascii_lowercase(); - (!governed(&name)).then(|| (name, value.to_vec())) + (!governed(&name) && name != ACCEPT).then(|| (name, value.to_vec())) }) .collect(); fields = forward_over(fields, forwarded); diff --git a/crates/busbar-plane-llm/src/plane_door.rs b/crates/busbar-plane-llm/src/plane_door.rs index 04895eba9e..de00bcd11e 100644 --- a/crates/busbar-plane-llm/src/plane_door.rs +++ b/crates/busbar-plane-llm/src/plane_door.rs @@ -1350,87 +1350,3 @@ busbar_contract::plugin_door! { serve: Safe<Serve>, hydrate: Safe<Hydrate>, start: Safe<Start>, project: Safe<Project>, }, } - -#[cfg(test)] -mod fee_unit_tests { - use super::*; - - fn tokens(n: u64) -> Vec<UnitCount> { - vec![UnitCount { - class: 0, - source: UNITS_REPORTED, - amount: n, - }] - } - - /// A report as plain triples (the ABI row has no `PartialEq`). - fn seen(units: &[UnitCount]) -> Vec<(u32, u32, u64)> { - units - .iter() - .map(|u| (u.class, u.source, u.amount)) - .collect() - } - - fn fee() -> UnitCount { - UnitCount { - class: FEE_CLASS_INDEX, - source: UNITS_REPORTED, - amount: 1, - } - } - - /// THE FEE UNIT (owner #77, money-B1): reported 1 from the answer that opens the caller's reply - /// under a success status, beside the far end's last cumulative counts on every later answer - /// that carries counts; an answer that carries none leaves the last report standing. - #[test] - fn a_success_reply_incurs_the_fee_unit_and_every_later_report_keeps_it() { - let mut unit = UnitState::new(Arc::default()); - let mut head = Answer { - status: 200, - ..Answer::default() - }; - fee_unit(&mut unit, &mut head); - assert_eq!( - seen(&head.units), - seen(&[fee()]), - "incurred at the success head" - ); - let mut chunk = Answer::default(); - fee_unit(&mut unit, &mut chunk); - assert!( - chunk.units.is_empty(), - "a countless answer leaves the report" - ); - let mut last = Answer { - units: tokens(7), - ..Answer::default() - }; - fee_unit(&mut unit, &mut last); - assert_eq!(seen(&last.units), seen(&[tokens(7), vec![fee()]].concat())); - } - - /// 1.5.5 refunded its flat fee for a non-2xx caller status: no fee unit is reported, so the - /// unit's end refunds; nor for an answer bound for the far end, nor for a probe. - #[test] - fn a_failed_reply_a_far_request_and_a_probe_incur_no_fee_unit() { - for (status, to_far_end, probe) in - [(503, false, false), (200, true, false), (200, false, true)] - { - let mut unit = UnitState::new(Arc::default()); - unit.probe = probe; - let mut answer = Answer { - status, - to_far_end, - units: tokens(3), - ..Answer::default() - }; - fee_unit(&mut unit, &mut answer); - assert_eq!( - seen(&answer.units), - seen(&tokens(3)), - "{status} {to_far_end} {probe}" - ); - assert!(!unit.fee); - } - } -} diff --git a/crates/busbar-plane-llm/tests/conformance.rs b/crates/busbar-plane-llm/tests/conformance.rs index 944ffd7e7e..be6a59cf63 100644 --- a/crates/busbar-plane-llm/tests/conformance.rs +++ b/crates/busbar-plane-llm/tests/conformance.rs @@ -397,8 +397,9 @@ fn assert_the_planes_answers(t: &[String]) { "a same-dialect answer is the far end's bytes: {all}" ); assert!( - far.contains(r#"units=["0:7:true", "1:3:true", "2:0:true", "3:0:true"]"#), - "the far end's counts, in the tail's class order: {all}" + far.contains(r#"units=["0:7:true", "1:3:true", "2:0:true", "3:0:true", "5:1:true"]"#), + "the far end's counts, in the tail's class order, then the per-request fee unit a success \ + reply incurs (owner #77, money-B1): {all}" ); let narrow = line(t, "far_end narrow "); assert!( diff --git a/crates/busbar-plane-llm/tests/exchange_attempt.rs b/crates/busbar-plane-llm/tests/exchange_attempt.rs index 80a4710ed5..c7d8afdb8a 100644 --- a/crates/busbar-plane-llm/tests/exchange_attempt.rs +++ b/crates/busbar-plane-llm/tests/exchange_attempt.rs @@ -541,3 +541,21 @@ fn a_same_dialect_callers_fields_keep_busbars_positions() { ); assert_eq!(field(&r, "x-trace"), [b"a".as_slice(), b"b".as_slice()]); } + +/// THE `accept` FIELD IS BUSBAR'S OWN (1.5.5's bytes: the oracle's same-dialect cells, e.g. +/// `llm|openai|openai|request|ok`, carry `accept: application/json` though the caller sent `*/*`): +/// every far request carries the answer framing busbar reads the reply in, a same-dialect caller's +/// `accept` never replaces it, and the transport is left no default to fill. +#[test] +fn a_same_dialect_callers_accept_never_replaces_busbars_own() { + let h = head(&[("accept", "*/*"), ("x-trace", "a")]); + let body = r#"{"model":"gpt","messages":[{"role":"user","content":"hi"}]}"#; + let a = arrived("/v1/chat/completions", &h, body); + let r = build(&a, &h, &shaping(), "p", "gpt").expect("built"); + assert_eq!(field(&r, "accept"), [b"application/json".as_slice()]); + assert_eq!(field(&r, "x-trace"), [b"a".as_slice()]); + let streamed = r#"{"model":"gpt","stream":true,"messages":[{"role":"user","content":"hi"}]}"#; + let a = arrived("/v1/chat/completions", &h, streamed); + let r = build(&a, &h, &shaping(), "p", "gpt").expect("built"); + assert_eq!(field(&r, "accept"), [b"text/event-stream".as_slice()]); +} From 89926b63d4054085bb2377f5d5aefe7eca07140b Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:25:18 -0700 Subject: [PATCH 12/49] money census: the hook stage's carried refusal site is pinned once (the predev merge brought 833cef693e's pin of the same plane_driver route_async site this branch had reviewed and pinned from 5613e7ba55) --- crates/busbar/tests/fixtures/computed_refusal_sites.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/busbar/tests/fixtures/computed_refusal_sites.txt b/crates/busbar/tests/fixtures/computed_refusal_sites.txt index 46fbae7041..ae898f1349 100644 --- a/crates/busbar/tests/fixtures/computed_refusal_sites.txt +++ b/crates/busbar/tests/fixtures/computed_refusal_sites.txt @@ -8,7 +8,6 @@ busbar-llm/src/unit/verify.rs refusal.reason() busbar/src/root/units_admin/mod.rs verbs_reason(refusal.reason) busbar/src/root/units_admin/mod.rs verbs_reason(refusal.reason) busbar-kernel/src/plane_driver/mod.rs reason -busbar-kernel/src/plane_driver/mod.rs reason busbar-kernel/src/plane_driver/mod.rs cause # Reviewed (lane-dg-llm): the hook stage's refusal at the head of the route leg (HookVeto or # DecodeFailed, the unit's money finished first; K5 de3ea6acc4) and the door's destination guard at From 19d7f7813c99bd89c99906df2e1294d8d6ee40b6 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:32:43 -0700 Subject: [PATCH 13/49] gates on the flip's latest ports: the two boot-time leaks DECL-FOLD makes (a folded door's &'static registry row, the door rows' probe dispatcher) are reviewed hold-escape known_sites; main.rs's switch-scoped skip of the legacy probers is a recorded llm instance-noun row, struck with the switch at D3 --- qa/construction.toml | 7 +++++++ qa/instance-noun-neutrality.toml | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/qa/construction.toml b/qa/construction.toml index 72853c261d..a911cc11db 100644 --- a/qa/construction.toml +++ b/qa/construction.toml @@ -1253,6 +1253,13 @@ known_sites = [ # catalogue, whose entries are `&'static Diagnostic` with `&'static str` text by the catalogue's # type; one allocation per declared code, once, at boot, before the catalogue is first read. "crates/busbar/src/root/linked.rs::declared_diagnostics", + # REVIEWED 2026-10-04 (DECL-FOLD on the llm flip, ARCHITECT RULING Q-DEL-A2A-DECL): a folded door + # plane's registry row is a `&'static PlaneDecl` by the registry's type (rows live as long as the + # process, as a linked plane's image does); one allocation per door, once, at boot. + "crates/busbar-kernel/src/plane/door.rs::fold", + # REVIEWED 2026-10-04 (DECL-FOLD): the probe dispatcher the door rows bind on before the config + # prepass, ahead of the process's one dispatcher; one, once, at boot. Neither is a money hold. + "crates/busbar/src/root/linked.rs::door_rows", ] # The escape list, mirroring crates/busbar-contract/src/caps/fixtures/lint_rules.rs HOLD_ESCAPES. A test in that diff --git a/qa/instance-noun-neutrality.toml b/qa/instance-noun-neutrality.toml index ae94173976..94a28f170f 100644 --- a/qa/instance-noun-neutrality.toml +++ b/qa/instance-noun-neutrality.toml @@ -102,3 +102,11 @@ file = "crates/busbar/src/root/serve.rs" count = 1 category = "composition-root" wave = "keystone / composition-root — register_planes both-ways self-registration" + +[[leak]] +noun = "llm" +kind = "plane" +file = "crates/busbar/src/main.rs" +count = 1 +category = "composition-root" +wave = "keystone / composition-root — register_planes both-ways self-registration" From b42ddc847f98c18ed97e1a4c271d9a97655cf6ac Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 19:27:34 -0700 Subject: [PATCH 14/49] FLIP-LLM deny fixes (hop 37251900048): every feature leg compiles, the switch's code names no plane, the stand-in hook axis keeps its own dispatcher - main.rs: the config-apply refresh of the door planes is gated on `linked_axis_node` (Served::post exists only there), so --no-default-features and every single-plane-* leg compile again. - instance-noun-neutrality:llm green again, no debt row: build.rs emits `linked_fold_on_driver` (a `<plane>-on-driver` fold switch is on, BUSBAR-1.6.0.md Part 3 section 12 "The switch") and `LINKED_PLANE_DOORS` (each linked plane-door row's door, resolved as the root resolves its entry). The hook parity suite and the serve hook-seat tests gate on the cfg and find the plane under proof as the linked door whose Statement declares the `pools` map; its dialects and op classes are read off the door's tail; the seat tests install the legacy row's seams through the manifest's `node_plane` alias; main.rs's legacy-prober skip reads the cfg. The 1.5.5 test reject_kind_mapping_matches_status_semantics keeps its name and values, now proven end to end through the door (each gate status's Anthropic error type). The four switch rows added to qa/instance-noun-neutrality.toml are gone. - busbar-kernel test_support: hook_axis_stand_in opens each registry's rows on a dispatcher of its own again (adeb523486, which a port had reverted to one shared dispatcher): a slow-gate test's wedged worker no longer FAULTs dlopen_decide_order_and_abstain in the timing and core-duplex-ws legs. --- crates/busbar-kernel/src/test_support/mod.rs | 5 +- crates/busbar/build.rs | 48 ++++++++++++++ crates/busbar/src/main.rs | 13 ++-- crates/busbar/src/root/serve.rs | 2 +- .../busbar/src/root/tests/serve_hook_seats.rs | 39 +++++++++++- .../busbar/tests/hook_parity_driver/main.rs | 12 ++-- .../tests/hook_parity_driver/projection.rs | 6 +- crates/busbar/tests/hook_parity_driver/rig.rs | 63 ++++++++++++++++--- .../busbar/tests/hook_parity_driver/seam.rs | 39 +++++++----- qa/instance-noun-neutrality.toml | 40 ------------ 10 files changed, 183 insertions(+), 84 deletions(-) diff --git a/crates/busbar-kernel/src/test_support/mod.rs b/crates/busbar-kernel/src/test_support/mod.rs index 9649774d2e..47fb4a1d8d 100644 --- a/crates/busbar-kernel/src/test_support/mod.rs +++ b/crates/busbar-kernel/src/test_support/mod.rs @@ -2159,13 +2159,10 @@ pub fn hook_axis_stand_in( registry: &std::sync::Arc<busbar_plugin_loader::PluginRegistry>, ) -> Result<std::sync::Arc<dyn busbar_contract::hook_calls::HookAxis>, String> { use busbar_plugin_loader::dispatch::{DispatchConfig, Dispatcher}; - static DISPATCHER: std::sync::OnceLock<std::sync::Arc<Dispatcher>> = std::sync::OnceLock::new(); - let dispatcher = - DISPATCHER.get_or_init(|| std::sync::Arc::new(Dispatcher::new(DispatchConfig::default()))); let rows = busbar_plugin_loader::hook_door::HookRows::new( crate::preflight::STAND_IN_HOOK_DOORS, Some(registry.as_ref()), - dispatcher.clone(), + std::sync::Arc::new(Dispatcher::new(DispatchConfig::default())), )?; Ok(std::sync::Arc::new(rows)) } diff --git a/crates/busbar/build.rs b/crates/busbar/build.rs index 41ce4aa331..1591d93f4d 100644 --- a/crates/busbar/build.rs +++ b/crates/busbar/build.rs @@ -165,6 +165,24 @@ fn main() { std::path::PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("linked_planes.rs"); std::fs::write(out, linked_planes_source(&manifest, &enabled)).expect("write linked_planes.rs"); + // THE LINKED PLANE DOORS FOR THE INTEGRATION TESTS (tests only include it): each enabled linked + // row on the `plane-door` axis, its memory-ABI `door` resolved as the root resolves its entry, + // so a test finds a door by what its Statement states without naming a plane crate. + let out = std::path::PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")) + .join("linked_plane_doors.rs"); + std::fs::write(out, linked_plane_doors_source(&manifest, &enabled)) + .expect("write linked_plane_doors.rs"); + // `linked_fold_on_driver`: a development-only fold switch is on (a `<plane>-on-driver` feature, + // BUSBAR-1.6.0.md Part 3 section 12 "The switch"), so its proofs on the driver compile without a + // test spelling the plane the switch carries. + println!("cargo::rustc-check-cfg=cfg(linked_fold_on_driver)"); + if declared_features(&manifest) + .iter() + .any(|f| f.ends_with("-on-driver") && enabled(f)) + { + println!("cargo::rustc-cfg=linked_fold_on_driver"); + } + // THE LINKED WIRES FOR THE INTEGRATION TESTS (tests only include it): each linked transport row's // entry (`KEY`, `COMPOSES_OVER`, `SESSION`, `build`), the rows this build leaves unlinked, and the same // bottom-up fold the boot seal runs — so a test addresses a wire by the key its row declares and @@ -299,6 +317,36 @@ fn linked_planes_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> Strin out } +/// `static LINKED_PLANE_DOORS`: every enabled `[package.metadata.busbar.linked]` row carrying the +/// `plane-door` axis, its door at its entry (`linked-entry` by row, then by crate, else +/// `<crate>::linked`), in manifest order. An entry inside the root itself is no test's to reach. +fn linked_plane_doors_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> String { + let axes = metadata_map(manifest, "package.metadata.busbar.linked-axes"); + let entries = metadata_map(manifest, "package.metadata.busbar.linked-entry"); + let mut out = String::from( + "// @generated by build.rs from Cargo.toml `[package.metadata.busbar.linked]`.\n\ + pub(crate) static LINKED_PLANE_DOORS: &[::busbar_contract::abi::mechanism::door::DoorFn] = &[\n", + ); + for (feature, krate) in metadata_map(manifest, "package.metadata.busbar.linked") { + let is_door = axes + .iter() + .any(|(f, a)| *f == feature && a.split_whitespace().any(|x| x == "plane-door")); + if !(enabled(&feature) && is_door) { + continue; + } + let entry = entries + .iter() + .find(|(k, _)| *k == feature) + .or_else(|| entries.iter().find(|(k, _)| *k == krate)) + .map_or_else(|| format!("{}::linked", ident(&krate)), |(_, e)| e.clone()); + if !entry.starts_with("crate::") { + out.push_str(&format!(" ::{entry}::door,\n")); + } + } + out.push_str("];\n"); + out +} + /// `(source, off)`: `static LINKED_TRANSPORTS` over every `[package.metadata.busbar.linked]` row /// carrying the `transport` axis that this build links (its feature on, or its crate a required /// dependency — the rule `linked_source` links by), each row's entry resolved as the root resolves it diff --git a/crates/busbar/src/main.rs b/crates/busbar/src/main.rs index 80271f0ad6..8385dd4755 100644 --- a/crates/busbar/src/main.rs +++ b/crates/busbar/src/main.rs @@ -946,10 +946,11 @@ async fn run(data_workers: usize, late_services: std::sync::Arc<root::serve::Lat // first config swap, retiring these boot probers (their `Weak` fails to upgrade) exactly as the old // `Weak<App>` did when the boot snapshot drained. // Built only when some linked entry re-anchors work on it: a build with none holds no host. - // Under the llm fold's switch the llm plane's health probes run through its door as the K7 - // probe unit (`root::serve`'s probe target over the plane's sealed egress), so the legacy row's - // probers are not spawned beside them: one prober per lane, as 1.5.5 ran. - let on_host = if cfg!(feature = "llm-on-driver") { + // While a plane's fold switch is on, the plane serving the `pools` map runs its health probes + // through its door as the K7 probe unit (`root::serve`'s probe target over the plane's sealed + // egress), so the legacy row's probers are not spawned beside them: one prober per lane, as + // 1.5.5 ran. + let on_host = if cfg!(linked_fold_on_driver) { &[][..] } else { LINKED.on_host @@ -1005,7 +1006,9 @@ async fn run(data_workers: usize, late_services: std::sync::Arc<root::serve::Lat credential_handle.set(std::sync::Arc::clone(&app_handle)); let _ = door_live_handle.set(std::sync::Arc::clone(&app_handle)); // A CONFIG APPLY REFRESHES THE DOOR PLANES onto the new generation (`Served::refresh`): the - // swapped-in App's projected configuration, its lane table and its secret seam. + // swapped-in App's projected configuration, its lane table and its secret seam. A build with no + // node composes no door plane's money, so it has none to refresh. + #[cfg(linked_axis_node)] if let Some(post) = served.post.clone() { let (served, auths, live) = ( std::sync::Arc::clone(&served), diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index daa6523ccb..9e6142de11 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -1884,6 +1884,6 @@ mod door_tests; #[path = "tests/serve_money.rs"] mod money_tests; -#[cfg(all(test, feature = "llm-on-driver", linked_axis_node))] +#[cfg(all(test, linked_fold_on_driver, linked_axis_node))] #[path = "tests/serve_hook_seats.rs"] mod hook_seat_tests; diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs index fc1c767e76..b0376f8e69 100644 --- a/crates/busbar/src/root/tests/serve_hook_seats.rs +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -42,6 +42,39 @@ use crate::root::loader::dispatch::{ }; use crate::root::plane_node::{Node, NodeEndPost}; +// The plane the node is handed, as the manifest's linked table names it (the `node` axis): the +// legacy row's test seams the registry rows that own the `pools:`/`models:` sections come from. +include!(concat!(env!("OUT_DIR"), "/node_plane.rs")); + +/// THE LINKED DOOR THAT SERVES THE `pools` MAP, found by what its Statement declares (the composition +/// root names no plane): every linked plane door bound on a probe dispatcher of its own, the one +/// whose declaring section is `pools` kept. +fn pools_door() -> busbar_contract::abi::mechanism::door::DoorFn { + crate::LINKED + .plane_doors + .iter() + .copied() + .find(|door| { + let probe = Dispatcher::new(DispatchConfig::default()); + let row = LinkedRow::of(*door).expect("a linked door states itself"); + load_linked::<Plane>( + &row, + Bind { + instance: Arc::from("pools-door-probe"), + max_inflight_cap: 64, + sink: Arc::new(NoSink), + dispatcher: probe.adopter(), + conns: None, + }, + ) + .expect("a linked door binds") + .served() + .section + == busbar_contract::section::RESERVED_POOLS_KEY + }) + .expect("the fold switch links the door serving the `pools` map") +} + /// The card history the served unit is pinned to at its door: one entry, no price. static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = std::sync::LazyLock::new(|| { @@ -223,7 +256,7 @@ struct Ran { /// when `reject_at_gate`. async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { // The registry rows that own the `pools:`/`models:` sections the deployment writes. - busbar_llm::testkit::install_test_seams(); + node_plane::testkit::install_test_seams(); let port = far_end().await; let judge = crate::root::connector::guard_for(&busbar_kernel::config::Destinations { block_private_addresses: false, @@ -241,7 +274,7 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { // THE LLM DOOR, linked, bound through the loader's one load, its needs declared on the // connector. let dispatcher = Arc::new(Dispatcher::new(DispatchConfig::default())); - let row = LinkedRow::of(busbar_plane_llm::plane_door::door).expect("the door states itself"); + let row = LinkedRow::of(pools_door()).expect("the door states itself"); let plane = load_linked::<Plane>( &row, Bind { @@ -398,7 +431,7 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { }), Some(&stage), ) - .expect("the llm door composes"); + .expect("the door serving the pools map composes"); let _ = std::fs::remove_file(&key_file); served.post = Some(Arc::clone(&post)); let doors = door_routes( diff --git a/crates/busbar/tests/hook_parity_driver/main.rs b/crates/busbar/tests/hook_parity_driver/main.rs index 623579ec6d..854b7b4713 100644 --- a/crates/busbar/tests/hook_parity_driver/main.rs +++ b/crates/busbar/tests/hook_parity_driver/main.rs @@ -16,14 +16,14 @@ //! in their own crates (`busbar-kernel` hooks tests, the plugin loader's hook tests, the ranking //! hook's tests). //! -//! Development-only, like the switch it proves: built with the `llm-on-driver` switch. +//! Development-only, like the switch it proves: built with the plane's development-only fold switch +//! (`linked_fold_on_driver`). -#![cfg(feature = "llm-on-driver")] +#![cfg(linked_fold_on_driver)] -/// The plane under proof, named once: the composition root's one edge to it. -extern crate busbar_plane_llm as plane; -/// Its plane-kind facts (the operation classes the driver is configured with). -type PlaneFacts = plane::LlmPlane; +// The linked plane doors (`LINKED_PLANE_DOORS`), generated from the manifest: the plane under proof +// is the one whose Statement declares the `pools` map, found by what it states, never by name. +include!(concat!(env!("OUT_DIR"), "/linked_plane_doors.rs")); #[path = "../../../busbar-kernel/tests/common/mod.rs"] mod common; diff --git a/crates/busbar/tests/hook_parity_driver/projection.rs b/crates/busbar/tests/hook_parity_driver/projection.rs index dac3caf0a6..4665910ba7 100644 --- a/crates/busbar/tests/hook_parity_driver/projection.rs +++ b/crates/busbar/tests/hook_parity_driver/projection.rs @@ -692,10 +692,8 @@ async fn every_known_protocol_has_a_declared_reasoning_wire_shape() { ("openai", None, None), ("cohere", None, None), ]; - let dialects: Vec<&str> = crate::plane::dialect::DIALECTS - .iter() - .map(|d| d.name) - .collect(); + let dialects = crate::rig::dialects(); + let dialects: Vec<&str> = dialects.iter().map(String::as_str).collect(); for d in &dialects { assert!( rows.iter().any(|(p, _, _)| p == d), diff --git a/crates/busbar/tests/hook_parity_driver/rig.rs b/crates/busbar/tests/hook_parity_driver/rig.rs index 6405307fbb..d1356b4834 100644 --- a/crates/busbar/tests/hook_parity_driver/rig.rs +++ b/crates/busbar/tests/hook_parity_driver/rig.rs @@ -439,10 +439,58 @@ fn octets(b: &[u8]) -> Blob { } } -/// The llm door, linked, opened over `settings`. +/// THE LINKED DOOR SERVING THE `pools` MAP, found by what its Statement declares +/// (`LINKED_PLANE_DOORS`, the manifest's plane-door rows): the plane under proof. +pub(crate) fn pools_door() -> busbar_contract::abi::mechanism::door::DoorFn { + crate::LINKED_PLANE_DOORS + .iter() + .copied() + .find(|door| { + let dispatcher = Dispatcher::new(DispatchConfig::default()); + let row = LinkedRow::of(*door).expect("a linked door states itself"); + load_linked::<Plane>( + &row, + Bind { + instance: Arc::from("pools-door-probe"), + max_inflight_cap: 64, + sink: Arc::new(NoSink), + dispatcher: dispatcher.adopter(), + conns: None, + }, + ) + .expect("a linked door binds") + .served() + .section + == busbar_contract::section::RESERVED_POOLS_KEY + }) + .expect("the fold switch links the door serving the `pools` map") +} + +/// The plane under proof's dialects, in its tail's order, as its door states them. +pub(crate) fn dialects() -> Vec<String> { + let dispatcher = Dispatcher::new(DispatchConfig::default()); + let row = LinkedRow::of(pools_door()).expect("the plane door states its Statement"); + load_linked::<Plane>( + &row, + Bind { + instance: Arc::from("the-plane-facts"), + max_inflight_cap: 64, + sink: Arc::new(NoSink), + dispatcher: dispatcher.adopter(), + conns: None, + }, + ) + .expect("the plane door loads") + .served() + .dialects + .iter() + .map(|d| (*d).to_string()) + .collect() +} + +/// The door serving the `pools` map, linked, opened over `settings`. fn door(dispatcher: &Dispatcher, settings: &[u8]) -> Plugin<Plane> { - let row = - LinkedRow::of(crate::plane::plane_door::door).expect("the plane door states its Statement"); + let row = LinkedRow::of(pools_door()).expect("the plane door states its Statement"); let plugin = load_linked::<Plane>( &row, Bind { @@ -534,12 +582,13 @@ impl Rig { let plugin = door(&dispatcher, &settings(&pools)); let calls = Arc::new(PlaneInstance::new(plugin.clone(), dispatcher, 1)); let refusal_statuses = calls.refusal_statuses(); - let dialects: Vec<String> = crate::plane::dialect::DIALECTS + let served = plugin.served(); + let dialects: Vec<String> = served.dialects.iter().map(|d| (*d).to_string()).collect(); + let op_classes: Vec<_> = served + .op_classes .iter() - .map(|d| d.name.to_string()) + .map(|c| busbar_contract::caps::OpClassId::new(c)) .collect(); - let op_classes = - <crate::PlaneFacts as busbar_contract::plane::PlaneMeta>::OP_CLASSES.to_vec(); let driver = PlaneDriver::new( calls, DriverConfig { diff --git a/crates/busbar/tests/hook_parity_driver/seam.rs b/crates/busbar/tests/hook_parity_driver/seam.rs index 82edc5fa3e..49b0fc7bbc 100644 --- a/crates/busbar/tests/hook_parity_driver/seam.rs +++ b/crates/busbar/tests/hook_parity_driver/seam.rs @@ -854,20 +854,31 @@ async fn reject_rides_the_full_forward_path() { ); } -#[test] -fn reject_kind_mapping_matches_status_semantics() { - use crate::plane::exchange::refuse::gate_kind; - use busbar_contract::protocol::{ - KIND_AUTHENTICATION, KIND_INVALID_REQUEST, KIND_NOT_FOUND, KIND_PERMISSION, - KIND_RATE_LIMIT, KIND_TIMEOUT, - }; - assert_eq!(gate_kind(401), KIND_AUTHENTICATION); - assert_eq!(gate_kind(403), KIND_PERMISSION); - assert_eq!(gate_kind(404), KIND_NOT_FOUND); - assert_eq!(gate_kind(408), KIND_TIMEOUT); - assert_eq!(gate_kind(429), KIND_RATE_LIMIT); - for other in [400, 422, 451, 499] { - assert_eq!(gate_kind(other), KIND_INVALID_REQUEST); +/// The gate's status picks the refusal's kind, rendered in the caller's dialect (here Anthropic's +/// `error.type`): 401, 403, 404, 408 and 429 their own kinds, every other 4xx an invalid request. +#[tokio::test] +async fn reject_kind_mapping_matches_status_semantics() { + for (status, kind) in [ + (401, "authentication_error"), + (403, "permission_error"), + (404, "not_found_error"), + (408, "timeout_error"), + (429, "rate_limit_error"), + (400, "invalid_request_error"), + (422, "invalid_request_error"), + (451, "invalid_request_error"), + (499, "invalid_request_error"), + ] { + let answer = gate_refusal( + status, + "guardrail", + "/v1/messages", + ANTHROPIC, + &serde_json::to_vec(&chat_body()).unwrap(), + ) + .await; + assert_eq!(answer.status, u32::from(status), "{status}"); + assert_eq!(answer.json()["error"]["type"], kind, "{status}"); } } diff --git a/qa/instance-noun-neutrality.toml b/qa/instance-noun-neutrality.toml index 94a28f170f..19c2d66837 100644 --- a/qa/instance-noun-neutrality.toml +++ b/qa/instance-noun-neutrality.toml @@ -70,43 +70,3 @@ file = "crates/busbar-plane-llm/src/codec/openai_chat/tests/handler_tests.rs" count = 2 category = "cross-plugin" wave = "cross-plugin cleanup — broker law #8 (plugins never name each other)" - -# RE-ARM, measured (p3-flip-llm, 2026-10-04): the llm fold's development-only switch (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 llm flip's gate is every hook test -# green on the driver"). The hook parity suite names the plane under proof once, in its main.rs (the -# `llm-on-driver` cfg, the one `extern crate`, the plane-facts alias); the serve path's hook-seat tests -# (the predev seat-order tests' door home) name the door they drive and the cfg that compiles them. -# Each row is struck when the switch is deleted with the llm fold (D3, `1.6.0-TODO.md`: "the -# `llm-on-driver` dev cargo feature | the llm flip | deleted in the llm fold's D3"). -[[leak]] -noun = "llm" -kind = "plane" -file = "crates/busbar/tests/hook_parity_driver/main.rs" -count = 3 -category = "composition-root" -wave = "keystone / composition-root — register_planes both-ways self-registration" - -[[leak]] -noun = "llm" -kind = "plane" -file = "crates/busbar/src/root/tests/serve_hook_seats.rs" -count = 3 -category = "composition-root" -wave = "keystone / composition-root — register_planes both-ways self-registration" - -[[leak]] -noun = "llm" -kind = "plane" -file = "crates/busbar/src/root/serve.rs" -count = 1 -category = "composition-root" -wave = "keystone / composition-root — register_planes both-ways self-registration" - -[[leak]] -noun = "llm" -kind = "plane" -file = "crates/busbar/src/main.rs" -count = 1 -category = "composition-root" -wave = "keystone / composition-root — register_planes both-ways self-registration" From 9c071905e75a221c60a50f65e2becf9d1af52c30 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:27:33 -0700 Subject: [PATCH 15/49] DECL-FOLD: the door rows bind on the process's one dispatcher (booted as main's first act, before any plane registers), not a leaked probe dispatcher of their own, so the switch build's thread count is the configured dispatcher's (root_dispatcher_boot) and no hold escapes; the hold-escapes exemption for door_rows is withdrawn --- crates/busbar/src/root/linked.rs | 13 ++++++------- qa/construction.toml | 3 --- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/busbar/src/root/linked.rs b/crates/busbar/src/root/linked.rs index a1048ce76d..3c8130d1ba 100644 --- a/crates/busbar/src/root/linked.rs +++ b/crates/busbar/src/root/linked.rs @@ -470,8 +470,8 @@ pub fn plane_rows( /// THE DOOR PLANES' REGISTRY ROWS (DECL-FOLD; ARCHITECT RULING 2026-10-03, Q-DEL-A2A-DECL; spec #49 /// and R2-C): every plane [`dropped_planes_of`] discovered through a door, linked or dropped, bound -/// once through the loader's one load on a dispatcher of its own (the process's is built after the -/// configuration is read, and the rows must be in before the config prepass), and its Statement +/// once through the loader's one load on the process's one dispatcher (booted as main's first act, +/// before any plane registers and before the config prepass), and its Statement /// folded into a registry row by the kernel (`busbar_kernel::plane::door::fold`). The row's every /// word is the door's; a plane a linked row already registers keeps that row (the boot fold keeps /// the first row of a key). A door that will not bind refuses the boot, as its load would. @@ -482,15 +482,14 @@ fn door_rows() -> Result<Vec<&'static PlaneDecl>, String> { if doors.is_empty() { return Ok(Vec::new()); } - let probe: &'static crate::root::loader::dispatch::Dispatcher = - Box::leak(Box::new(crate::root::loader::dispatch::Dispatcher::new( - crate::root::loader::dispatch::DispatchConfig::default(), - ))); + // The process's one dispatcher, booted as main's first act before any plane registers (so the + // fold adds no worker of its own to the process's thread count). + let dispatcher = crate::root::dispatch::dispatcher(); crate::root::loader::boot::load_planes( doors, crate::root::boot::plugin_logs(), Arc::new(crate::root::loader::dispatch::NoSink), - probe.adopter(), + dispatcher.adopter(), u32::MAX, None, )? diff --git a/qa/construction.toml b/qa/construction.toml index 8368f9b6fa..7b16bf0a48 100644 --- a/qa/construction.toml +++ b/qa/construction.toml @@ -1257,9 +1257,6 @@ known_sites = [ # plane's registry row is a `&'static PlaneDecl` by the registry's type (rows live as long as the # process, as a linked plane's image does); one allocation per door, once, at boot. "crates/busbar-kernel/src/plane/door.rs::fold", - # REVIEWED 2026-10-04 (DECL-FOLD): the probe dispatcher the door rows bind on before the config - # prepass, ahead of the process's one dispatcher; one, once, at boot. Neither is a money hold. - "crates/busbar/src/root/linked.rs::door_rows", ] # The escape list, mirroring crates/busbar-contract/src/caps/fixtures/lint_rules.rs HOLD_ESCAPES. A test in that From 6f6b8d2dbb4cdcbbe927255ad5724a266c6cd8a5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:07:10 -0700 Subject: [PATCH 16/49] llm codec: an echoed request default is no unrepresentable control (ARCHITECT RULING 2026-10-04) A Responses answer restates the request's tools, tool_choice and parallel_tool_calls, at their defaults when the caller set none. A TRANSLATE answer drops them, and the drop walk named each as an egress.control_unrepresentable row, so the 11 llm|*|responses|request|ok(_stream) cells gained three audit rows 1.5.5 never wrote. The answer walk (drops::Carried) now takes the dialect's declared echoed defaults (ANSWER_DEFAULTS for Responses): a member equal to its default names no drop and writes no row; any value the caller set is still named and audited. Test: an_echoed_request_default_names_no_drop_and_a_caller_set_value_still_does (the echo names nothing; RED arm: a real tool, a directed choice and parallel calls off are each still named). --- .../src/codec/anthropic/reader.rs | 2 + .../src/codec/bedrock/reader.rs | 2 + .../src/codec/cohere/reader.rs | 2 + crates/busbar-plane-llm/src/codec/drops.rs | 18 +++++++- .../src/codec/gemini/reader.rs | 2 + .../src/codec/openai_chat/reader.rs | 2 + .../src/codec/openai_responses/mod.rs | 10 +++++ .../src/codec/openai_responses/reader.rs | 2 + .../src/codec/tests/drops_tests.rs | 41 ++++++++++++++++++- 9 files changed, 79 insertions(+), 2 deletions(-) diff --git a/crates/busbar-plane-llm/src/codec/anthropic/reader.rs b/crates/busbar-plane-llm/src/codec/anthropic/reader.rs index fd98f66675..85bf6662e2 100644 --- a/crates/busbar-plane-llm/src/codec/anthropic/reader.rs +++ b/crates/busbar-plane-llm/src/codec/anthropic/reader.rs @@ -210,6 +210,7 @@ impl ProtocolReader for AnthropicReader { map: super::map::RESPONSE_PATHS, code: super::RESPONSE_CODE, drops: super::RESPONSE_DROPS, + defaults: &[], }) } @@ -218,6 +219,7 @@ impl ProtocolReader for AnthropicReader { map: super::map::STREAM_PATHS, code: super::STREAM_CODE, drops: super::STREAM_DROPS, + defaults: &[], }) } diff --git a/crates/busbar-plane-llm/src/codec/bedrock/reader.rs b/crates/busbar-plane-llm/src/codec/bedrock/reader.rs index 090e7b24cb..afcb8df724 100644 --- a/crates/busbar-plane-llm/src/codec/bedrock/reader.rs +++ b/crates/busbar-plane-llm/src/codec/bedrock/reader.rs @@ -1482,6 +1482,7 @@ impl ProtocolReader for BedrockReader { map: super::map::RESPONSE_PATHS, code: super::RESPONSE_CODE, drops: super::RESPONSE_DROPS, + defaults: &[], }) } @@ -1490,6 +1491,7 @@ impl ProtocolReader for BedrockReader { map: super::map::STREAM_PATHS, code: super::STREAM_CODE, drops: super::STREAM_DROPS, + defaults: &[], }) } diff --git a/crates/busbar-plane-llm/src/codec/cohere/reader.rs b/crates/busbar-plane-llm/src/codec/cohere/reader.rs index e351a0a089..a14ca9bd74 100644 --- a/crates/busbar-plane-llm/src/codec/cohere/reader.rs +++ b/crates/busbar-plane-llm/src/codec/cohere/reader.rs @@ -1251,6 +1251,7 @@ impl ProtocolReader for CohereReader { map: super::map::RESPONSE_PATHS, code: super::RESPONSE_CODE, drops: super::RESPONSE_DROPS, + defaults: &[], }) } @@ -1259,6 +1260,7 @@ impl ProtocolReader for CohereReader { map: super::map::STREAM_PATHS, code: super::STREAM_CODE, drops: super::STREAM_DROPS, + defaults: &[], }) } diff --git a/crates/busbar-plane-llm/src/codec/drops.rs b/crates/busbar-plane-llm/src/codec/drops.rs index 0835bbc298..0c9b58c4f2 100644 --- a/crates/busbar-plane-llm/src/codec/drops.rs +++ b/crates/busbar-plane-llm/src/codec/drops.rs @@ -642,6 +642,12 @@ pub struct Carried { /// never scanned (Responses lists 763 stream paths; test /// `every_drop_list_is_sorted_for_the_walks_search`). pub drops: &'static [&'static str], + /// The request defaults the far end ECHOES in its answer, each a member's key and its default + /// as JSON text (`("tool_choice", "\"auto\"")`). A member equal to its default carries nothing + /// the caller set, so its drop is no unrepresentable control and the walk does not name it; + /// any other value of it is named like every drop (ARCHITECT RULING 2026-10-04: a dropped value + /// equal to the protocol default, or an echoed default, writes no audit row). + pub defaults: &'static [(&'static str, &'static str)], } /// Where a wire path stands against what a dialect carries. @@ -710,6 +716,13 @@ impl Carried { } } + /// `value`, under `key`, is the default the dialect declares the far end echoes for it. + fn echoes_default(&self, key: &str, value: &Value) -> bool { + self.defaults.iter().any(|(k, default)| { + *k == key && serde_json::from_str::<Value>(default).is_ok_and(|d| d == *value) + }) + } + /// The objects at `path` are a union keyed by their `type` member (notation A `type=<arm>`). fn uses_arms(&self, path: &str) -> bool { let arm = join(path, "type="); @@ -782,7 +795,10 @@ impl Carried { None => path.to_string(), }; for (key, child) in obj { - if child.is_null() || (arm.is_some() && key == crate::codec::keys::TYPE) { + if child.is_null() + || (arm.is_some() && key == crate::codec::keys::TYPE) + || self.echoes_default(key, child) + { continue; } let p = join(&base, key); diff --git a/crates/busbar-plane-llm/src/codec/gemini/reader.rs b/crates/busbar-plane-llm/src/codec/gemini/reader.rs index e7612c5aa3..5e471bcf52 100644 --- a/crates/busbar-plane-llm/src/codec/gemini/reader.rs +++ b/crates/busbar-plane-llm/src/codec/gemini/reader.rs @@ -1618,6 +1618,7 @@ impl ProtocolReader for GeminiReader { map: super::map::RESPONSE_PATHS, code: super::RESPONSE_CODE, drops: super::RESPONSE_DROPS, + defaults: &[], }) } @@ -1626,6 +1627,7 @@ impl ProtocolReader for GeminiReader { map: super::map::RESPONSE_PATHS, code: super::RESPONSE_CODE, drops: super::RESPONSE_DROPS, + defaults: &[], }) } diff --git a/crates/busbar-plane-llm/src/codec/openai_chat/reader.rs b/crates/busbar-plane-llm/src/codec/openai_chat/reader.rs index 556264650d..85064b56e9 100644 --- a/crates/busbar-plane-llm/src/codec/openai_chat/reader.rs +++ b/crates/busbar-plane-llm/src/codec/openai_chat/reader.rs @@ -1248,6 +1248,7 @@ impl ProtocolReader for OpenAiReader { map: super::map::RESPONSE_PATHS, code: super::RESPONSE_CODE, drops: super::RESPONSE_DROPS, + defaults: &[], }) } @@ -1256,6 +1257,7 @@ impl ProtocolReader for OpenAiReader { map: super::map::STREAM_PATHS, code: super::STREAM_CODE, drops: super::STREAM_DROPS, + defaults: &[], }) } diff --git a/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs b/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs index 9e7c3ee0f4..6a990ee195 100644 --- a/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs +++ b/crates/busbar-plane-llm/src/codec/openai_responses/mod.rs @@ -1140,6 +1140,16 @@ const STREAM_CODE: &[&str] = &[ /// The answer paths INSIDE a subtree this dialect carries that its code does not carry, named by the /// drop walk (DF-MAP-IR-GAPS section E: its A, B and C paths that a coarse map row covers). +/// THE REQUEST DEFAULTS A RESPONSES ANSWER ECHOES (the response object restates the request's +/// `tools`, `tool_choice` and `parallel_tool_calls`, at their defaults when the request set none): +/// an echoed default is no control the caller set, so a TRANSLATE answer that cannot carry it +/// names no drop for it (ARCHITECT RULING 2026-10-04). Keyed by the member, its default as JSON. +pub(super) const ANSWER_DEFAULTS: &[(&str, &str)] = &[ + (keys::TOOLS, "[]"), + (keys::TOOL_CHOICE, "\"auto\""), + (keys::PARALLEL_TOOL_CALLS, "true"), +]; + const RESPONSE_DROPS: &[&str] = &[ "error.misalignment", "instructions[].type=additional_tools", diff --git a/crates/busbar-plane-llm/src/codec/openai_responses/reader.rs b/crates/busbar-plane-llm/src/codec/openai_responses/reader.rs index 1bbfdb7326..7e7f97411c 100644 --- a/crates/busbar-plane-llm/src/codec/openai_responses/reader.rs +++ b/crates/busbar-plane-llm/src/codec/openai_responses/reader.rs @@ -1881,6 +1881,7 @@ impl ProtocolReader for ResponsesReader { map: super::map::RESPONSE_PATHS, code: super::RESPONSE_CODE, drops: super::RESPONSE_DROPS, + defaults: super::ANSWER_DEFAULTS, }) } @@ -1889,6 +1890,7 @@ impl ProtocolReader for ResponsesReader { map: super::map::STREAM_PATHS, code: super::STREAM_CODE, drops: super::STREAM_DROPS, + defaults: super::ANSWER_DEFAULTS, }) } diff --git a/crates/busbar-plane-llm/src/codec/tests/drops_tests.rs b/crates/busbar-plane-llm/src/codec/tests/drops_tests.rs index 52f39c5a27..fb10c434c6 100644 --- a/crates/busbar-plane-llm/src/codec/tests/drops_tests.rs +++ b/crates/busbar-plane-llm/src/codec/tests/drops_tests.rs @@ -586,6 +586,7 @@ fn the_answer_walk_names_the_shallowest_uncarried_member() { ], code: &["type=ping"], drops: &[], + defaults: &[], }; let answer = json!({"id": "m", "content": [{"type": "x", "deep": 1}], "container": {"id": "c"}, "usage": {"input_tokens": 1, "output_tokens_details": {"t": 1}}, "stop_details": null}); @@ -612,6 +613,7 @@ fn the_answer_walk_names_the_shallowest_uncarried_member() { map: &["contentBlockDelta.delta.text"], code: &[], drops: &[], + defaults: &[], }; assert_eq!( keyed.unmapped( @@ -622,6 +624,33 @@ fn the_answer_walk_names_the_shallowest_uncarried_member() { ); } +/// AN ECHOED DEFAULT IS NO UNREPRESENTABLE CONTROL (ARCHITECT RULING 2026-10-04): a Responses +/// answer restates the request's `tools`, `tool_choice` and `parallel_tool_calls`, at their +/// defaults when the caller set none; a TRANSLATE answer drops them, and the walk names none, so +/// no `egress.control_unrepresentable` row is written for them (the 1.5.5 rows for +/// `llm|*|responses|request|ok`). RED arm: a value the caller actually set (a real tool, a +/// directed choice, parallel calls off) is still named, so its drop is still audited. +#[test] +fn an_echoed_request_default_names_no_drop_and_a_caller_set_value_still_does() { + let carried = crate::codec::proto_codec::with_reader("responses", |r| r.response_carried()) + .flatten() + .expect("the Responses answer is walked"); + let echoed = json!({"id": "resp_1", "object": "response", "status": "completed", + "output": [], "tools": [], "tool_choice": "auto", "parallel_tool_calls": true}); + assert!( + carried.unmapped("", &echoed).is_empty(), + "an echoed default is no control the caller set: {:?}", + carried.unmapped("", &echoed) + ); + let set = json!({"id": "resp_1", "object": "response", "status": "completed", + "output": [], + "tools": [{"type": "function", "name": "lookup", "parameters": {"type": "object"}}], + "tool_choice": "required", "parallel_tool_calls": false}); + let mut named = carried.unmapped("", &set); + named.sort(); + assert_eq!(named, vec!["parallel_tool_calls", "tool_choice", "tools"]); +} + /// DF-MAP-IR-GAPS SECTION E (ruled: every unmapped response/stream path is named, no extra /// bucket): every wire path of its list is named by the far end's drop walk when an answer carries /// it. RED before: the walk did not exist (a buffered answer named blocks and vendor metadata only; @@ -708,7 +737,17 @@ fn the_answer_walk_names_only_true_drops_over_the_golden_corpus() { .copied() .filter(|p| { let pointer = format!("/{}", p.replace('.', "/")); - body.pointer(&pointer).is_some_and(|v| !v.is_null()) + // An echoed request default is no drop (ARCHITECT RULING 2026-10-04). + let echoed = |v: &Value| { + far == "responses" + && crate::codec::openai_responses::ANSWER_DEFAULTS + .iter() + .any(|(k, d)| { + *k == *p && serde_json::from_str::<Value>(d).is_ok_and(|d| d == *v) + }) + }; + body.pointer(&pointer) + .is_some_and(|v| !v.is_null() && !echoed(v)) }) .collect(); assert_eq!(named, expected, "{name}"); From 5158a31003d6eacaea1c92ca5e0d0637d682633f Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:08:47 -0700 Subject: [PATCH 17/49] fmt; the plane_driver test imports no unused refusal cause --- crates/busbar-kernel/tests/plane_driver.rs | 4 ++-- crates/busbar/src/root/serve.rs | 16 ++++++++++------ crates/busbar/src/root/tests/serve_money.rs | 9 ++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/busbar-kernel/tests/plane_driver.rs b/crates/busbar-kernel/tests/plane_driver.rs index e8c3d6ab84..1dbfca5aff 100644 --- a/crates/busbar-kernel/tests/plane_driver.rs +++ b/crates/busbar-kernel/tests/plane_driver.rs @@ -34,8 +34,8 @@ use busbar_contract::abi::plane::{ RefusalIn, RefusalOut, UnitCount, AUDIT_APPLIED, CANCEL_ABORTED, CANCEL_FAILED, CANCEL_OK_PARTIAL, EMIT_DONE, EMIT_FINAL_STATUS, EMIT_MESSAGE_END, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, PIECE_FIELDS, PIECE_HAS_STATUS, PIECE_LAST, - PIECE_OUT_TEXT, PRINCIPAL_OPTIONAL, RECORD_AUDIT, RECORD_PUT, REFUSAL_ARRIVE, REFUSAL_KERNEL, - ROUTE_LOCAL, ROUTE_SESSION, UNITS_ESTIMATED, UNITS_REPORTED, VERDICT_RETRY, + PIECE_OUT_TEXT, PRINCIPAL_OPTIONAL, RECORD_AUDIT, RECORD_PUT, REFUSAL_ARRIVE, ROUTE_LOCAL, + ROUTE_SESSION, UNITS_ESTIMATED, UNITS_REPORTED, VERDICT_RETRY, }; use busbar_contract::abi::plane::{ServeIn, ServeOut, REFUSAL_GATE}; use busbar_contract::caps::OpClassId; diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index b325cc0f09..4b3f512d72 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -604,9 +604,9 @@ impl DoorAppliers { let walked = walked_section(facts, section, sections); let now = p.current(); let next = now.generation + 1; - if let Err(e) = - crate::root::loader::dispatch::kinds::plane::refresh_door(&p.plugin, &settings, next) - { + if let Err(e) = crate::root::loader::dispatch::kinds::plane::refresh_door( + &p.plugin, &settings, next, + ) { tracing::error!(plane = name, error = %e, "door plane did not refresh onto the new configuration; it keeps serving the previous one"); continue; } @@ -1036,9 +1036,13 @@ pub fn compose_planes( // Q-L3B-DOOR-EXCHANGE), held on the connection table its need is declared on. if let (Some(egress), Some(table)) = (egress, plugin.conn_table()) { let pools = DoorPools::of(section); - let routes = - crate::root::door_steps::member_routes(section, &pools, &served_facts, egress.reach) - .map_err(|e| format!("{instance}: {e}"))?; + let routes = crate::root::door_steps::member_routes( + section, + &pools, + &served_facts, + egress.reach, + ) + .map_err(|e| format!("{instance}: {e}"))?; crate::root::door_steps::bind_member_fetches( &served_facts, &routes, diff --git a/crates/busbar/src/root/tests/serve_money.rs b/crates/busbar/src/root/tests/serve_money.rs index c3fbb795bd..bf68f0f870 100644 --- a/crates/busbar/src/root/tests/serve_money.rs +++ b/crates/busbar/src/root/tests/serve_money.rs @@ -143,13 +143,8 @@ fn governed_with( ) .expect("the door plane composes"); served.post = Some(Arc::clone(&post)); - let routes = door_routes( - served, - || crate::root::kernel::ROOT_CARD.pin(), - &[], - &[], - ) - .expect("its claims mount"); + let routes = door_routes(served, || crate::root::kernel::ROOT_CARD.pin(), &[], &[]) + .expect("its claims mount"); let app = busbar_kernel::test_support::TestApp::new(); let app = if keys_chain { app.keys_chain() } else { app }; let app = groups From c515ae213ab44b2790a01de08cb65c72f0ba0bac Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:28:16 -0700 Subject: [PATCH 18/49] U14: the door serving the pools map witnesses its capability column (Q128 U14; BUSBAR-1.6.0.md Part 3 section 12) Eleven cells driven end to end through the data router built with the door's claims, the kernel's hook stage, the model-serving walk over the kernel's lane cells and the node's book, each reading the capability where the kernel keeps it: breaker-trip (the member's (pool, lane) cell stops admitting), breaker-fastfail (a tripped member is never dialled again), failover-reroute (the twin's answer before the first byte), disposition (a far end's refusal of the request is relayed, not failed over, not charged to the cell), egress-auth (the member's bound credential, never the caller's token), metrics (the attempt on the scrape under its pool and lane), governance-budget (the fee on the key; a spent budget refuses with Retry-After before any dial), audit-chain (one record per unit), hooks-gate (refused before dispatch), hooks-tap (the request tap sees the rewritten request) and catalogue (a restricted key lists only the pool it reaches and its member). The cells live in root/tests/serve_door.rs (coordinator ruling: a #[path] test file of a declared root module is a leg file): its decisions items are gated on plane-decisions and the door cells on the fold switch. The shared rig is serve_hook_seats' run(), generalized (members and weights, pooled members, allowed pools, a day budget). --- crates/busbar/src/root/serve.rs | 8 +- crates/busbar/src/root/tests/serve_door.rs | 376 ++++++++++++++++++ .../busbar/src/root/tests/serve_hook_seats.rs | 359 +++++++++++++---- 3 files changed, 671 insertions(+), 72 deletions(-) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index 4b3f512d72..3c4bc8855d 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2975,7 +2975,13 @@ mod tests; #[path = "tests/serve_planes.rs"] mod planes_tests; -#[cfg(all(test, feature = "plane-decisions", linked_axis_node))] +// The door test file: the decisions door's served route, and the capability cells of the door +// serving the `pools` map under the fold switch. +#[cfg(all( + test, + linked_axis_node, + any(feature = "plane-decisions", linked_fold_on_driver) +))] #[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 36d1d2ba28..4b50c95b6a 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -14,30 +14,48 @@ //! 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. +#[cfg(feature = "plane-decisions")] use std::collections::BTreeMap; +#[cfg(feature = "plane-decisions")] use std::sync::Arc; +#[cfg(feature = "plane-decisions")] use axum::http::StatusCode; +#[cfg(feature = "plane-decisions")] use busbar_contract::caps::ReasonCode; +#[cfg(feature = "plane-decisions")] use busbar_contract::conn::{DeclaredConns, PollConns}; +#[cfg(feature = "plane-decisions")] use busbar_kernel::cost::CostModel; +#[cfg(feature = "plane-decisions")] use busbar_kernel::governance::signing::{TokenSigner, DEFAULT_KID}; +#[cfg(feature = "plane-decisions")] use busbar_kernel::governance::{GovState, MemoryStore, NewKeySpec, PLANE_LANE_SEP}; +#[cfg(feature = "plane-decisions")] use busbar_kernel::plane_driver::{refusal_status, EndPost, PlaneMoney}; +#[cfg(feature = "plane-decisions")] use busbar_plane_decisions::plane_door::door as decisions_door; +#[cfg(feature = "plane-decisions")] use tokio::io::{AsyncReadExt, AsyncWriteExt}; +#[cfg(feature = "plane-decisions")] use super::planes_tests::{composed_services, money, Published, PUBLISHING}; +#[cfg(feature = "plane-decisions")] use super::{compose_planes, door_routes, DoorEgress}; +#[cfg(feature = "plane-decisions")] use crate::root::door_steps::{provider_routes, DoorReach, OutboundAuths}; +#[cfg(feature = "plane-decisions")] use crate::root::loader::dispatch::kinds::plane::Plane; +#[cfg(feature = "plane-decisions")] use crate::root::loader::dispatch::{ load_linked, Bind, DispatchConfig, Dispatcher, LinkedRow, NoSink, }; +#[cfg(feature = "plane-decisions")] use crate::root::plane_node::{Node, NodeEndPost}; /// The deployment's dated card history the served unit is pinned to at its door: one entry, no /// price (billing off: the counts are the unit's fact and price at nothing). +#[cfg(feature = "plane-decisions")] static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = std::sync::LazyLock::new(|| { let holder = crate::root::kernel::RootHistory::default(); @@ -53,16 +71,20 @@ static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = }); /// 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. +#[cfg(feature = "plane-decisions")] 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 { use tower::ServiceExt as _; let mut req = axum::http::Request::builder().method("POST").uri(path); @@ -81,6 +103,7 @@ 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<String>) { let listener = tokio::net::TcpListener::bind("127.0.0.1:0") .await @@ -135,6 +158,7 @@ async fn far_end() -> (u16, tokio::sync::mpsc::UnboundedReceiver<String>) { /// 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. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[cfg(feature = "plane-decisions")] async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { let _one = PUBLISHING.lock().await; let instance = "serve-door-served"; @@ -345,6 +369,7 @@ async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { /// 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. #[tokio::test] +#[cfg(feature = "plane-decisions")] async fn the_data_router_built_with_the_door_serves_only_its_claims() { let _one = PUBLISHING.lock().await; let instance = "serve-door"; @@ -422,6 +447,7 @@ async fn the_data_router_built_with_the_door_serves_only_its_claims() { /// 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. #[tokio::test] +#[cfg(feature = "plane-decisions")] async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { let _one = PUBLISHING.lock().await; let instance = "serve-door-carriers"; @@ -485,3 +511,353 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { refused ); } + +// ── THE DOOR SERVING THE `pools` MAP: ITS CAPABILITY CELLS (Q128 U14) ─────────────────────────── +// +// The capability-equality matrix's root column for this plane, witnessed on its door path: each +// cell below drives a keyed caller through the data router built with the door's claims, the +// kernel's hook stage, the model-serving walk over the kernel's lane cells and the node's book +// (`super::hook_seat_tests::rig`), and reads the capability where the kernel keeps it. + +#[cfg(linked_fold_on_driver)] +use super::hook_seat_tests::{far_end_answering, rig, RigOpts, REWRITTEN}; +#[cfg(linked_fold_on_driver)] +use super::planes_tests::{Published as Withdrawn, PUBLISHING as ONE_PUBLISHER}; + +/// A served chat completion, marked by the member that answered it. +#[cfg(linked_fold_on_driver)] +const SERVED_BY_TWIN: &str = r#"{"id":"chatcmpl-2","object":"chat.completion","created":0,"model":"m1","choices":[{"index":0,"message":{"role":"assistant","content":"served-by-the-twin"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}"#; + +/// A far end that cannot serve now. +#[cfg(linked_fold_on_driver)] +const OVERLOADED: &str = r#"{"error":{"message":"overloaded","type":"server_error"}}"#; + +/// A far end refusing the request itself. +#[cfg(linked_fold_on_driver)] +const MALFORMED: &str = r#"{"error":{"message":"bad request","type":"invalid_request_error"}}"#; + +/// BREAKER-TRIP: a member whose far end fails records into the kernel's ONE breaker cell for its +/// (pool, lane), and the cell opens: the walk's failure benched it. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_trips_a_failing_members_breaker_cell() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-breaker-trip"; + let _published = Withdrawn(instance); + let (down, twin) = ( + far_end_answering(503, OVERLOADED).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ); + let rig = rig( + instance, + RigOpts { + members: &[(down.port, 100), (twin.port, 1)], + ..RigOpts::default() + }, + ) + .await; + let ready = || rig.app.store.ready_in("p", 0, busbar_kernel::store::now()); + assert!(ready(), "the member's cell admits before any failure"); + let (status, _, _) = rig.chat().await; + assert_eq!(status, 200, "the twin serves"); + assert_eq!(down.served(), 1, "the failing member was dialled once"); + assert!( + !ready(), + "the failure recorded into the member's (pool, lane) cell and opened it" + ); +} + +/// BREAKER-FASTFAIL: a tripped member is refused before dispatch, at once: the next unit never +/// dials it, and its twin serves. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_refuses_a_tripped_member_before_dispatch() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-breaker-fastfail"; + let _published = Withdrawn(instance); + let (down, twin) = ( + far_end_answering(503, OVERLOADED).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ); + let rig = rig( + instance, + RigOpts { + members: &[(down.port, 100), (twin.port, 1)], + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 200); + assert_eq!(down.served(), 1); + let started = std::time::Instant::now(); + assert_eq!(rig.chat().await.0, 200, "the twin serves the next unit"); + assert_eq!( + down.served(), + 1, + "the tripped member is refused before dispatch: never dialled again" + ); + assert_eq!(twin.served(), 2); + assert!( + started.elapsed() < std::time::Duration::from_secs(5), + "refused at once, not after a timeout" + ); +} + +/// FAILOVER-REROUTE: a second interchangeable candidate is tried before the first byte, through +/// the one walk: the first member fails, the twin's answer is the caller's. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_reroutes_to_the_twin_before_the_first_byte() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-failover"; + let _published = Withdrawn(instance); + let (down, twin) = ( + far_end_answering(503, OVERLOADED).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ); + let rig = rig( + instance, + RigOpts { + members: &[(down.port, 100), (twin.port, 1)], + ..RigOpts::default() + }, + ) + .await; + let (status, _, body) = rig.chat().await; + assert_eq!(status, 200); + assert!( + String::from_utf8_lossy(&body).contains("served-by-the-twin"), + "the twin's answer reached the caller: {}", + String::from_utf8_lossy(&body) + ); + assert_eq!((down.served(), twin.served()), (1, 1)); +} + +/// DISPOSITION: the far end's answer is classified before it is relayed: a refusal of the request +/// itself is the caller's own fault, relayed with its status, never failed over and never charged +/// to the member's breaker cell. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_classifies_a_refused_request_as_the_callers_fault() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-disposition"; + let _published = Withdrawn(instance); + let (refusing, twin) = ( + far_end_answering(400, MALFORMED).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ); + let rig = rig( + instance, + RigOpts { + members: &[(refusing.port, 100), (twin.port, 1)], + ..RigOpts::default() + }, + ) + .await; + let (status, _, _) = rig.chat().await; + assert_eq!(status, 400, "the far end's refusal is relayed"); + assert_eq!( + (refusing.served(), twin.served()), + (1, 0), + "a caller's fault is not failed over" + ); + assert!( + rig.app.store.ready_in("p", 0, busbar_kernel::store::now()), + "nor charged to the member's cell" + ); +} + +/// EGRESS-AUTH: the member's credential is the one the egress mechanism binds and injects; the +/// caller's own token never crosses. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_injects_the_members_credential_not_the_callers() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-egress-auth"; + let _published = Withdrawn(instance); + let far = far_end_answering(200, SERVED_BY_TWIN).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 200); + let seen = far.authorizations.lock().unwrap().clone(); + assert_eq!(seen, vec!["Bearer sk-seats".to_string()]); + assert!( + !seen[0].contains(&rig.token), + "the caller's token never crosses" + ); +} + +/// METRICS: the door's traffic appears on the scrape, under the pool and lane it was served on. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_doors_traffic_appears_on_the_metrics_scrape() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-metrics"; + let _published = Withdrawn(instance); + let far = far_end_answering(200, SERVED_BY_TWIN).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 200); + let scrape = busbar_kernel::metrics::render(); + assert!( + scrape + .lines() + .any(|l| l.starts_with("busbar_upstream_attempts_total{") + && l.contains("pool=\"p\"") + && l.contains("lane=\"m0\"")), + "the attempt is on the scrape under its pool and lane:\n{scrape}" + ); +} + +/// GOVERNANCE-BUDGET: the unit's spend is attributed to the presenting key, and a key whose budget +/// is spent is refused before any far end is dialled. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_attributes_spend_and_a_spent_budget_refuses() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-budget"; + let _published = Withdrawn(instance); + let far = far_end_answering(200, SERVED_BY_TWIN).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + budget_cents: Some(1), + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 200); + let (requests, spend, _, billable) = rig.ledger_after(1).await; + assert_eq!( + (requests, spend, billable), + (1, 1, 1), + "the one fee on the key" + ); + let (status, head, _) = rig.chat().await; + assert_eq!(status, 429, "the spent budget refuses"); + assert!( + head.contains_key("retry-after"), + "with a Retry-After: {head:?}" + ); + assert_eq!(far.served(), 1, "the refused unit dialled nothing"); +} + +/// AUDIT-CHAIN: every unit the door serves seals exactly one record on the node's chain. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_seals_one_audit_record_per_unit() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-audit"; + let _published = Withdrawn(instance); + let far = far_end_answering(200, SERVED_BY_TWIN).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 200); + let _ = rig.ledger_after(1).await; + assert_eq!(rig.audit_records(), 1, "one record for the one unit"); +} + +/// HOOKS-GATE: a decision gate refuses the door's traffic before dispatch. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_doors_gate_hook_refuses_before_dispatch() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-gate"; + let _published = Withdrawn(instance); + let far = far_end_answering(200, SERVED_BY_TWIN).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + reject_at_gate: true, + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 451, "the gate's own status"); + assert_eq!(far.served(), 0, "nothing was dispatched"); +} + +/// HOOKS-TAP: the rewrite and observe hooks run over the door's payloads: the request-stage tap +/// sees the request as the global rewrite left it. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_doors_tap_hooks_observe_the_rewritten_request() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-tap"; + let _published = Withdrawn(instance); + let far = far_end_answering(200, SERVED_BY_TWIN).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 200); + let payload = rig + .request_tap_payload(2_000) + .await + .expect("the request-stage tap is delivered"); + let text = String::from_utf8_lossy(&payload); + assert!(text.contains(REWRITTEN), "{text}"); + assert!(!text.contains("the original prompt"), "{text}"); +} + +/// CATALOGUE: what a restricted key may SEE is the one catalogue walk's answer: the pool it may +/// reach and that pool's members, never a model it cannot reach. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_doors_catalogue_shows_a_restricted_key_only_what_it_reaches() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-catalogue"; + let _published = Withdrawn(instance); + let (a, b) = ( + far_end_answering(200, SERVED_BY_TWIN).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ); + let rig = rig( + instance, + RigOpts { + members: &[(a.port, 1), (b.port, 1)], + pooled: Some(1), + allowed_pools: Some(&["p"]), + ..RigOpts::default() + }, + ) + .await; + let (status, _, body) = rig.send("GET", "/v1/models", None).await; + assert_eq!(status, 200); + let listed: serde_json::Value = serde_json::from_slice(&body).expect("a JSON list"); + let ids: Vec<&str> = listed["data"] + .as_array() + .expect("the list") + .iter() + .filter_map(|m| m["id"].as_str()) + .collect(); + assert_eq!( + ids, + ["p", "m0"], + "the reachable pool and its member, nothing else" + ); +} diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs index 974a939808..27b63f1cdb 100644 --- a/crates/busbar/src/root/tests/serve_hook_seats.rs +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -91,7 +91,7 @@ static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = }); /// The marker the rewrite hook plants in the prompt; a tap payload carrying it saw the rewrite. -const REWRITTEN: &str = "rewritten-by-the-global-rewrite-seat"; +pub(super) const REWRITTEN: &str = "rewritten-by-the-global-rewrite-seat"; /// The far end's answer: an openai chat completion. const ANSWER: &str = r#"{"id":"chatcmpl-1","object":"chat.completion","created":0,"model":"m0","choices":[{"index":0,"message":{"role":"assistant","content":"hi"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}"#; @@ -191,18 +191,40 @@ async fn delivered(tap: &SeatProbe, budget_ms: u64) -> Option<Vec<u8>> { None } -/// A far end on loopback answering every request with [`ANSWER`]. -async fn far_end() -> u16 { +/// A FAR END ON LOOPBACK: every request it reads is answered with its status and body; it counts +/// the requests it served and keeps each one's `authorization` field. +pub(super) struct FarEnd { + /// Its port. + pub port: u16, + /// The requests it served. + pub hits: Arc<std::sync::atomic::AtomicUsize>, + /// Each served request's `authorization` field, as received. + pub authorizations: Arc<Mutex<Vec<String>>>, +} + +impl FarEnd { + /// The requests it served so far. + pub fn served(&self) -> usize { + self.hits.load(std::sync::atomic::Ordering::SeqCst) + } +} + +/// A far end on loopback answering every request with `status` and `body` (JSON). +pub(super) async fn far_end_answering(status: u16, body: &'static str) -> FarEnd { let listener = tokio::net::TcpListener::bind("127.0.0.1:0") .await .expect("a loopback port"); let port = listener.local_addr().expect("its address").port(); + let hits = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let authorizations = Arc::new(Mutex::new(Vec::new())); + let (served, seen) = (Arc::clone(&hits), Arc::clone(&authorizations)); tokio::spawn(async move { while let Ok((mut socket, _)) = listener.accept().await { + let (served, seen) = (Arc::clone(&served), Arc::clone(&seen)); tokio::spawn(async move { let mut buf = vec![0u8; 16 * 1024]; let mut got = Vec::new(); - loop { + let head = loop { let Ok(n) = socket.read(&mut buf).await else { return; }; @@ -212,36 +234,48 @@ async fn far_end() -> u16 { got.extend_from_slice(&buf[..n]); let text = String::from_utf8_lossy(&got).to_string(); if let Some(at) = text.find("\r\n\r\n") { - let length = text[..at] - .lines() - .find_map(|l| { - let (name, value) = l.split_once(':')?; - name.eq_ignore_ascii_case("content-length") - .then(|| value.trim().parse::<usize>().ok()) - .flatten() + let field = |name: &str| { + text[..at].lines().find_map(|l| { + let (n, v) = l.split_once(':')?; + n.eq_ignore_ascii_case(name).then(|| v.trim().to_string()) }) + }; + let length = field("content-length") + .and_then(|v| v.parse::<usize>().ok()) .unwrap_or(0); if got.len() >= at + 4 + length { - break; + break field("authorization").unwrap_or_default(); } } - } + }; + served.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + seen.lock().unwrap().push(head); + let reason = if status == 200 { "OK" } else { "Error" }; 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() + "HTTP/1.1 {status} {reason}\r\ncontent-type: application/json\r\n\ + content-length: {}\r\nconnection: close\r\n\r\n{body}", + body.len() ); let _ = socket.write_all(reply.as_bytes()).await; let _ = socket.shutdown().await; }); } }); - port + FarEnd { + port, + hits, + authorizations, + } +} + +/// A far end on loopback answering every request with [`ANSWER`]. +async fn far_end() -> u16 { + far_end_answering(200, ANSWER).await.port } /// The key's ledger as the test reads it: (admission count, derived spend in cents, the durable /// row's admission count, the durable row's billable count). -type Ledger = (u64, i64, u64, u64); +pub(super) type Ledger = (u64, i64, u64, u64); /// What a run left: the caller's status, the seat log, the two tap probes and the ledger reader. struct Ran { @@ -252,12 +286,120 @@ struct Ran { read: Arc<dyn Fn() -> Ledger + Send + Sync>, } -/// ONE KEYED REQUEST through the llm door with the four probed seats installed, a rejecting gate -/// when `reject_at_gate`. -async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { +/// HOW A RIG IS BUILT: the far ends of its pool `p`'s members (each a model `m<i>` on a provider +/// of its own, at `weight`), the gate's verdict, and the key's all-time budget. +#[derive(Clone, Copy, Default)] +pub(super) struct RigOpts<'a> { + /// Each member's far-end port and weight, in order. + pub members: &'a [(u16, u32)], + /// The global gate rejects with 451. + pub reject_at_gate: bool, + /// The key's group holds this many cents a day (a flat 1-cent fee per request). + pub budget_cents: Option<u64>, + /// How many of the members (the first ones) pool `p` holds; the rest are models no pool + /// names. `None`: all of them. + pub pooled: Option<usize>, + /// The pools the key may reach (`None`: every pool). + pub allowed_pools: Option<&'a [&'a str]>, +} + +/// THE LLM DOOR, SERVED END TO END, as production composes it: the data router built with the +/// door's claims, a keyed caller's token, the generation's App and the node's book. +pub(super) struct DoorRig { + /// The data router. + pub router: axum::Router, + /// The caller's bearer token. + pub token: String, + /// The generation. + pub app: Arc<busbar_kernel::state::App>, + /// The node's book: every unit's line and audit record. + pub book: Arc<std::sync::Mutex<crate::root::durability::Durability>>, + /// The key's id. + pub key_id: String, + log: Arc<Mutex<Vec<String>>>, + request_tap: Arc<SeatProbe>, + candidate_tap: Arc<SeatProbe>, + read: Arc<dyn Fn() -> Ledger + Send + Sync>, +} + +impl DoorRig { + /// One request through the door as the keyed caller: its status, head and body. + pub async fn send( + &self, + method: &str, + path: &str, + body: Option<serde_json::Value>, + ) -> (u16, axum::http::HeaderMap, Vec<u8>) { + use tower::ServiceExt as _; + let mut req = axum::http::Request::builder() + .method(method) + .uri(path) + .header("authorization", format!("Bearer {}", self.token)); + if body.is_some() { + req = req.header("content-type", "application/json"); + } + let req = req + .body(match body { + Some(b) => axum::body::Body::from(b.to_string()), + None => axum::body::Body::empty(), + }) + .expect("a request"); + let response = self + .router + .clone() + .oneshot(req) + .await + .expect("the router answers"); + let status = response.status().as_u16(); + let head = response.headers().clone(); + let bytes = axum::body::to_bytes(response.into_body(), 1 << 20) + .await + .expect("the body") + .to_vec(); + (status, head, bytes) + } + + /// One chat completion on pool `p`. + pub async fn chat(&self) -> (u16, axum::http::HeaderMap, Vec<u8>) { + self.send( + "POST", + "/v1/chat/completions", + Some(serde_json::json!({"model": "p", "max_tokens": 16, + "messages": [{"role": "user", "content": "the original prompt"}]})), + ) + .await + } + + /// The key's ledger, once its last unit's money settled (bounded). + pub async fn ledger_after(&self, billable: u64) -> Ledger { + let mut ledger = (self.read)(); + for _ in 0..200 { + if ledger.3 >= billable { + break; + } + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + ledger = (self.read)(); + } + ledger + } + + /// The audit records the node's book sealed so far. + pub fn audit_records(&self) -> usize { + self.book + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .audit_records + .len() + } +} + +/// BUILD THE RIG: the door serving the `pools` map, linked and bound through the loader's one load +/// on the process connector, over `opts`' far ends, its egress the model-serving walk over the +/// kernel's lane cells, its hooks the kernel's stage with the four probed seats installed. +pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { // The registry rows that own the `pools:`/`models:` sections the deployment writes. node_plane::testkit::install_test_seams(); - let port = far_end().await; + busbar_kernel::metrics::init(); let judge = crate::root::connector::guard_for(&busbar_kernel::config::Destinations { block_private_addresses: false, ..Default::default() @@ -293,45 +435,94 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { ) .expect("the linked door binds"); - // THE DEPLOYMENT: one openai provider on the far end, one model, pool `p` over it. + // THE DEPLOYMENT: one openai provider per member, one model per provider, pool `p` over them. let key_file = std::env::temp_dir().join(format!( "busbar-hook-seats-{}-{instance}", std::process::id() )); std::fs::write(&key_file, "sk-seats").expect("the credential file"); - let deploy = busbar_kernel::config::deploy_from_yaml_str(&format!( - "providers:\n oai:\n api_key: {{ file: '{}' }}\nmodels:\n m0:\n provider: oai\n\ - pools:\n p:\n members:\n - model: m0\n", - key_file.display() - )) - .expect("a deployment"); - let defs: HashMap<String, busbar_kernel::config::ProviderDef> = serde_yaml::from_str(&format!( - "oai:\n protocol: openai\n base_url: 'http://127.0.0.1:{port}'\n" - )) - .expect("the providers"); + let mut yaml = String::from("providers:\n"); + for (i, _) in opts.members.iter().enumerate() { + yaml.push_str(&format!( + " oai{i}:\n api_key: {{ file: '{}' }}\n", + key_file.display() + )); + } + yaml.push_str("models:\n"); + for (i, _) in opts.members.iter().enumerate() { + yaml.push_str(&format!(" m{i}:\n provider: oai{i}\n")); + } + yaml.push_str("pools:\n p:\n members:\n"); + let pooled = opts.pooled.unwrap_or(opts.members.len()); + for (i, (_, weight)) in opts.members.iter().enumerate().take(pooled) { + yaml.push_str(&format!(" - model: m{i}\n weight: {weight}\n")); + } + let deploy = busbar_kernel::config::deploy_from_yaml_str(&yaml).expect("a deployment"); + let mut defs_yaml = String::new(); + for (i, (port, _)) in opts.members.iter().enumerate() { + defs_yaml.push_str(&format!( + "oai{i}:\n protocol: openai\n base_url: 'http://127.0.0.1:{port}'\n" + )); + } + let defs: HashMap<String, busbar_kernel::config::ProviderDef> = + serde_yaml::from_str(&defs_yaml).expect("the providers"); let cfg = busbar_kernel::config::resolve(&deploy, &defs).expect("resolves"); let providers = provider_routes(&cfg.providers); let sections = kernel_sections(&cfg); let model_pools = crate::root::model_egress::ModelPools::of(&cfg); - // THE MONEY: a signing governance book with one minted key, the node's book bound. + // THE MONEY: a signing governance book with one minted key (in a budgeted group when asked), + // the node's book bound. let signer = TokenSigner::from_secret_bytes(&[7u8; 32], DEFAULT_KID); let gov = Arc::new( GovState::new_with_signer(Arc::new(MemoryStore::new()), None, Some(signer)) .expect("governance"), ); + let groups: BTreeMap<String, busbar_kernel::config::GroupCfg> = opts + .budget_cents + .map(|amount| { + let limit = busbar_kernel::config::groups::LimitCfg { + metric: busbar_kernel::config::groups::LimitMetric::Budget, + amount, + per: Some(busbar_kernel::config::groups::LimitWindow::Day), + scope: None, + on_exhaust: None, + downgrade_to: None, + admission: None, + on_exhaustion: None, + }; + ( + format!("{instance}-group"), + busbar_kernel::config::GroupCfg { + parent: None, + enabled: true, + limits: vec![limit], + ..Default::default() + }, + ) + }) + .into_iter() + .collect(); + let cost = if groups.is_empty() { + CostModel::flat(1) + } else { + CostModel::resolve_parts(None, 1, &groups) + }; let (key, token) = gov .mint_signed( NewKeySpec { name: "seats".to_string(), + group: groups.keys().next().cloned(), + allowed_pools: opts + .allowed_pools + .map(|pools| pools.iter().map(|p| (*p).to_string()).collect()), ..Default::default() }, 4_000_000_000, 1_700_000_000, ) .expect("mint"); - gov.hydrate_budgets(&CostModel::flat(1), 0) - .expect("hydrate"); + 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)); @@ -345,18 +536,26 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { )) }; - // THE GENERATION: the lane, the pool, the flat 1-cent fee, and the four seats. - let mut app = busbar_kernel::test_support::TestApp::new() + // THE GENERATION: the lanes, the pool, the fee, and the four seats. + let mut builder = busbar_kernel::test_support::TestApp::new() .keys_chain() .governance(Arc::clone(&gov)) - .cost(CostModel::flat(1)) - .lane(busbar_kernel::test_support::LaneSpec::new( - "m0", + .cost(cost); + for (i, (port, _)) in opts.members.iter().enumerate() { + builder = builder.lane(busbar_kernel::test_support::LaneSpec::new( + &format!("m{i}"), "openai", &format!("http://127.0.0.1:{port}"), - )) - .pool("p", &[(0, 1)]) - .build(); + )); + } + let weights: Vec<(usize, u32)> = opts + .members + .iter() + .enumerate() + .take(pooled) + .map(|(i, (_, w))| (i, *w)) + .collect(); + let mut app = builder.pool("p", &weights).build(); let log = Arc::new(Mutex::new(Vec::new())); let read: Arc<dyn Fn() -> Ledger + Send + Sync> = { let (gov, cost, key_id) = (Arc::clone(&gov), app.cost.clone(), key.id.clone()); @@ -382,7 +581,7 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { let request_tap = Arc::new(SeatProbe::new("request-tap", &log)); let candidate_tap = Arc::new(SeatProbe::new("candidate-tap", &log)); let mut gate_probe = SeatProbe::new("gate", &log); - if reject_at_gate { + if opts.reject_at_gate { gate_probe.reject = Some((451, "the gate says no")); } { @@ -408,7 +607,9 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { )); let models = crate::root::model_egress::ModelServing { pools: model_pools, - lanes: HashMap::from([("m0".to_string(), 0)]), + lanes: (0..opts.members.len()) + .map(|i| (format!("m{i}"), i)) + .collect(), app: { let app = Arc::clone(&app); Arc::new(move || Arc::clone(&app)) @@ -456,31 +657,12 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { .expect("its claims mount"); let (router, _admin, _handle) = busbar_kernel::build_split_routers_serving(Arc::clone(&app), doors, 1 << 20, 0, false); - - // THE REQUEST, as the keyed caller. - let response = { - use tower::ServiceExt as _; - let req = axum::http::Request::builder() - .method("POST") - .uri("/v1/chat/completions") - .header("authorization", format!("Bearer {}", token.expose_secret())) - .header("content-type", "application/json") - .body(axum::body::Body::from( - serde_json::json!({"model": "p", "max_tokens": 16, - "messages": [{"role": "user", "content": "the original prompt"}]}) - .to_string(), - )) - .expect("a request"); - router - .clone() - .oneshot(req) - .await - .expect("the router answers") - }; - let status = response.status().as_u16(); - let _ = axum::body::to_bytes(response.into_body(), 1 << 16).await; - Ran { - status, + DoorRig { + router, + token: token.expose_secret().to_string(), + app, + book: Arc::clone(&book.durability), + key_id: key.id.to_string(), log, request_tap, candidate_tap, @@ -488,6 +670,41 @@ async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { } } +/// ONE KEYED REQUEST through the llm door with the four probed seats installed, a rejecting gate +/// when `reject_at_gate`. +async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { + let port = far_end().await; + let rig = rig( + instance, + RigOpts { + members: &[(port, 1)], + reject_at_gate, + ..RigOpts::default() + }, + ) + .await; + let (status, _, _) = rig.chat().await; + Ran { + status, + log: Arc::clone(&rig.log), + request_tap: Arc::clone(&rig.request_tap), + candidate_tap: Arc::clone(&rig.candidate_tap), + read: Arc::clone(&rig.read), + } +} + +impl DoorRig { + /// The request-stage tap's last delivered projection (bounded wait). + pub async fn request_tap_payload(&self, budget_ms: u64) -> Option<Vec<u8>> { + delivered(&self.request_tap, budget_ms).await + } + + /// The seat log. + pub fn seats(&self) -> Vec<String> { + self.log.lock().unwrap().clone() + } +} + /// A rejecting decision gate: the admission charge, the rewrite and the request tap all happened /// before it; the candidate tap never fires; the billable request is refunded and the admission /// count is kept. From 46019c3a2c21948ad407decafa80da746ea01bf7 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:45:15 -0700 Subject: [PATCH 19/49] census pin follows the teller (no Refusal::new site there since SEAM-4j root fix); the gated-screen driver test expects the entry its double projects --- crates/busbar-kernel/tests/plane_driver.rs | 3 ++- crates/busbar/tests/fixtures/computed_refusal_sites.txt | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/busbar-kernel/tests/plane_driver.rs b/crates/busbar-kernel/tests/plane_driver.rs index 1dbfca5aff..c142c5e379 100644 --- a/crates/busbar-kernel/tests/plane_driver.rs +++ b/crates/busbar-kernel/tests/plane_driver.rs @@ -1923,7 +1923,8 @@ async fn a_gate_first_plane_screens_its_entry_before_the_far_end_and_stops_at_th .contains(&busbar_contract::caps::StepName::Admit), "a veto admits nothing: the door was never asked" ); - assert_eq!(*binder.asked.lock().unwrap(), vec![String::new()]); + // The entry the plane's projection names (this double projects `pool-a`). + assert_eq!(*binder.asked.lock().unwrap(), vec!["pool-a".to_string()]); assert!(far.sent().is_empty(), "nothing reached the far end"); let rendered = units.take_rendered().expect("the veto is rendered"); assert_eq!(rendered.status, 451); diff --git a/crates/busbar/tests/fixtures/computed_refusal_sites.txt b/crates/busbar/tests/fixtures/computed_refusal_sites.txt index 3f9220a061..ae898f1349 100644 --- a/crates/busbar/tests/fixtures/computed_refusal_sites.txt +++ b/crates/busbar/tests/fixtures/computed_refusal_sites.txt @@ -15,8 +15,3 @@ busbar-kernel/src/plane_driver/mod.rs cause # overdraft ceiling or a stale slice. busbar-kernel/src/plane_driver/mod.rs reason busbar/src/root/door_steps.rs refusal.reason() -# teller.rs `screened` (SEAM-4j, Mode B): a gate-first plane's screen stops the unit at the -# Approve seat BEFORE the door, so nothing was admitted and no money ends; both sites carry the -# screen's own reason (the hook veto). -busbar-kernel/src/teller.rs reason -busbar-kernel/src/teller.rs reason From 352aed654930b3ee1a536744a4df06b5b60e9b0b Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:49:04 -0700 Subject: [PATCH 20/49] serve: the unit handler admits an anonymous caller on the pools-map plane when the data front door is open (1.5.5's open relay), as before the seam merge took its handler (inbound_concurrency_shed under llm-on-driver) --- crates/busbar/src/root/serve.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index 3c4bc8855d..bc020a1c85 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2259,11 +2259,18 @@ impl DataRoutes { Some(key) => PrincipalId::new(key.id.as_str()), None => PrincipalId::new(AuthPrincipal(None).actor_id()), }; - let open = self.served.planes[plane] - .snapshot - .claims - .get(claim as usize) - .is_some_and(|c| c.flags & CLAIM_OPEN != 0); + // An anonymous caller is admitted on an open claim; and, on the plane serving the `pools` + // map, on every claim when the deployment's data front door is open (`auth.chain: []` + // without `keys`: 1.5.5 admitted every model request anonymously, its open relay). Any + // other plane's credential claim fails closed (ARCHITECT P3 (a)). + let pools_plane = self.served.planes[plane].live.served_facts.section + == busbar_contract::section::RESERVED_POOLS_KEY; + let open = (pools_plane && app.auth.is_open()) + || self.served.planes[plane] + .snapshot + .claims + .get(claim as usize) + .is_some_and(|c| c.flags & CLAIM_OPEN != 0); let key = gov.key.clone(); let (caller, reply) = IngressCaller::arriving(body); let unit = async move { From b88d79951acdca35596573ddbd96f73f2b0bdf9a Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:59:35 -0700 Subject: [PATCH 21/49] dialect compiler refuses a repeated key; openai_responses.toml states each answer row once (ARCHITECT Q128 WARN) The Responses map stated output[].type=web_search_call.id and .status twice in [rows.response] (ir = web_search, then prim id/status): invalid TOML, and the compiler emitted both rows. The two prim rows are struck (the web_search_call members are the web_search concept, as the parent row and its siblings state); map.gen.rs and the translation matrix lose the repeated paths, the set of carried paths unchanged (oracle-neutral). xtask dialect: read_all refuses a table that states a key twice, naming file, table and key. Test: dialect::tests::a_mapping_file_that_repeats_a_key_is_refused. --- .../dialects/openai_responses.toml | 2 - .../src/codec/openai_responses/map.gen.rs | 2 - docs/llm-translation-matrix.md | 6 +-- xtask/src/dialect.rs | 52 ++++++++++++++++--- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/crates/busbar-plane-llm/dialects/openai_responses.toml b/crates/busbar-plane-llm/dialects/openai_responses.toml index d7f2048a52..62b7d4b25f 100644 --- a/crates/busbar-plane-llm/dialects/openai_responses.toml +++ b/crates/busbar-plane-llm/dialects/openai_responses.toml @@ -117,8 +117,6 @@ park = true "output[].type=function_call.call_id" = { prim = "call_id" } "output[].type=function_call.name" = { prim = "name" } "output[].type=function_call.arguments" = { prim = "arguments" } -"output[].type=web_search_call.id" = { prim = "id" } -"output[].type=web_search_call.status" = { prim = "status" } "service_tier" = { prim = "served_tier" } "usage.input_tokens_details.cache_write_tokens" = { prim = "cache_write_tokens" } diff --git a/crates/busbar-plane-llm/src/codec/openai_responses/map.gen.rs b/crates/busbar-plane-llm/src/codec/openai_responses/map.gen.rs index 3fa937a72f..81c64a633a 100644 --- a/crates/busbar-plane-llm/src/codec/openai_responses/map.gen.rs +++ b/crates/busbar-plane-llm/src/codec/openai_responses/map.gen.rs @@ -111,8 +111,6 @@ pub(crate) const RESPONSE_PATHS: &[&str] = &[ "output[].type=function_call.call_id", "output[].type=function_call.name", "output[].type=function_call.arguments", - "output[].type=web_search_call.id", - "output[].type=web_search_call.status", "service_tier", "usage.input_tokens_details.cache_write_tokens", ]; diff --git a/docs/llm-translation-matrix.md b/docs/llm-translation-matrix.md index 711ee4e916..60b72fa589 100644 --- a/docs/llm-translation-matrix.md +++ b/docs/llm-translation-matrix.md @@ -28,7 +28,7 @@ it; `-` means the dialect has no form for it. | openai_chat | response | 34 | 1 | | openai_chat | stream | 4 | 0 | | openai_responses | request | 46 | 5 | -| openai_responses | response | 40 | 3 | +| openai_responses | response | 38 | 3 | | openai_responses | stream | 13 | 0 | ## request @@ -162,7 +162,7 @@ it; `-` means the dialect has no form for it. | finish_reason | `stop_reason` | `stopReason` | `finish_reason` | `candidates[].finishReason` | `choices[].finish_reason` | - | | grounding_metadata | - | - | - | `candidates[].groundingMetadata` | - | - | | guardrail | - | `trace.guardrail` | - | - | - | - | -| id | `id` | - | `id` | - | `id` | `id`<br>`output[].type=message.id`<br>`output[].type=web_search_call.id` | +| id | `id` | - | `id` | - | `id` | `id`<br>`output[].type=message.id` | | incomplete_details | - | - | - | - | - | `incomplete_details` | | index | - | - | - | `candidates[].index` | `choices[].index` | - | | input_tokens | `usage.input_tokens` | `usage.inputTokens` | `usage.tokens.input_tokens`<br>`usage.billed_units.input_tokens` | `usageMetadata.promptTokenCount` | `usage.prompt_tokens` | `usage.input_tokens` | @@ -189,7 +189,7 @@ it; `-` means the dialect has no form for it. | safety_verdict | - | `trace.guardrail.inputAssessment{}.contentPolicy.filters[].type`<br>`trace.guardrail.inputAssessment{}.contentPolicy.filters[].action`<br>`trace.guardrail.inputAssessment{}.contentPolicy.filters[].detected`<br>`trace.guardrail.outputAssessments{}[].contentPolicy.filters[].type`<br>`trace.guardrail.outputAssessments{}[].contentPolicy.filters[].action`<br>`trace.guardrail.outputAssessments{}[].contentPolicy.filters[].detected` | - | `candidates[].safetyRatings[].category`<br>`candidates[].safetyRatings[].blocked`<br>`promptFeedback.safetyRatings[].category`<br>`promptFeedback.safetyRatings[].blocked` | `moderation.input.type=moderation_results.results[].categories`<br>`moderation.output.type=moderation_results.results[].categories` | `moderation.input.type=moderation_result.categories`<br>`moderation.output.type=moderation_result.categories` | | search_units | - | - | `usage.billed_units.search_units` | - | - | - | | served_tier | `usage.service_tier` | `serviceTier.type` | - | `usageMetadata.serviceTier` | `service_tier` | `service_tier` | -| status | - | - | - | - | - | `status`<br>`output[].type=message.status`<br>`output[].type=web_search_call.status` | +| status | - | - | - | - | - | `status`<br>`output[].type=message.status` | | stop_sequence | `stop_sequence` | - | - | - | - | - | | summary | - | - | - | - | - | `output[].type=reasoning.summary` | | system_fingerprint | - | - | - | - | `system_fingerprint` | - | diff --git a/xtask/src/dialect.rs b/xtask/src/dialect.rs index 41bea200fc..36082b43c4 100644 --- a/xtask/src/dialect.rs +++ b/xtask/src/dialect.rs @@ -565,22 +565,43 @@ fn compile_one(all: &[Dialect], d: &Dialect, answer: &BTreeSet<String>) -> Resul )) } -/// Read every mapping file under [`DIALECT_DIR`]. +/// Read every mapping file under [`DIALECT_DIR`], refusing one that repeats a key in a table: a +/// repeated key is invalid TOML, and two rows for one wire path would compile into contradictory +/// mapping rows (ARCHITECT, Q128 WARN). fn read_all(cx: &Ctx) -> Result<Vec<Dialect>, String> { let files = cx .walk(&WalkSpec::new([DIALECT_DIR]).ext("toml").min_files(1)) .map_err(|e| e.to_string())?; - Ok(files + files .iter() - .map(|f| Dialect { - name: f + .map(|f| { + let name = f .rel .file_stem() .map(|s| s.to_string_lossy().into_owned()) - .unwrap_or_default(), - doc: toml_lite::parse_text(&f.text), + .unwrap_or_default(); + let doc = toml_lite::parse_text(&f.text); + refuse_repeated_keys(&format!("{DIALECT_DIR}/{name}.toml"), &doc)?; + Ok(Dialect { name, doc }) }) - .collect()) + .collect() +} + +/// `Err` naming the first key a table of `doc` states twice (the file `at`, the table, the key). +pub fn refuse_repeated_keys(at: &str, doc: &toml_lite::Document) -> Result<(), String> { + for (table, t) in &doc.tables { + let mut seen: Vec<&str> = Vec::new(); + for (key, _) in &t.entries { + if seen.contains(&key.as_str()) { + return Err(format!( + "{at}: [{table}] states `{key}` twice; a key is stated once (TOML), and two \ + rows for one wire path would compile into contradictory mapping rows" + )); + } + seen.push(key); + } + } + Ok(()) } /// Compile every mapping file under [`DIALECT_DIR`]. @@ -820,3 +841,20 @@ pub fn main(cx: &Ctx, args: &[String]) -> i32 { } } } + +#[cfg(test)] +mod tests { + /// A mapping file that repeats a key in a table is refused, naming the file, the table and the + /// key; the same file with the key stated once compiles past the check. RED before: the + /// compiler read the repeated row and emitted both. + #[test] + fn a_mapping_file_that_repeats_a_key_is_refused() { + let twice = crate::toml_lite::parse_text( + "[rows.response]\n\"a.id\" = { ir = \"x\" }\n\"a.id\" = { prim = \"id\" }\n", + ); + let err = super::refuse_repeated_keys("d.toml", &twice).expect_err("refused"); + assert!(err.contains("d.toml") && err.contains("[rows.response]") && err.contains("`a.id`")); + let once = crate::toml_lite::parse_text("[rows.response]\n\"a.id\" = { ir = \"x\" }\n"); + super::refuse_repeated_keys("d.toml", &once).expect("a key stated once"); + } +} From 25cce7333c8261fbe0b1def4e7fd145894ba5bd1 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 04:43:22 -0700 Subject: [PATCH 22/49] root serve test: the records-attach store is the in-memory row config names (Q-STORE (B): no linked store is a default since #465); fmt --- crates/busbar/src/root/tests/serve.rs | 9 +++++---- xtask/src/dialect.rs | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/busbar/src/root/tests/serve.rs b/crates/busbar/src/root/tests/serve.rs index c441e24698..a73a8aba73 100644 --- a/crates/busbar/src/root/tests/serve.rs +++ b/crates/busbar/src/root/tests/serve.rs @@ -508,14 +508,15 @@ 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 in-memory store, as boot opens it for `store: {module: memory}` (no row is a + // default: the store is the one config names, Q-STORE = (B)). StoreDoor::Linked( crate::LINKED .stores .iter() - .find(|s| s.2) - .expect("the build links a default store") - .4, + .find(|s| s.0 == "memory") + .expect("the build links the in-memory store") + .3, ), "records-attach", b"{}", diff --git a/xtask/src/dialect.rs b/xtask/src/dialect.rs index 36082b43c4..1a12307778 100644 --- a/xtask/src/dialect.rs +++ b/xtask/src/dialect.rs @@ -853,7 +853,9 @@ mod tests { "[rows.response]\n\"a.id\" = { ir = \"x\" }\n\"a.id\" = { prim = \"id\" }\n", ); let err = super::refuse_repeated_keys("d.toml", &twice).expect_err("refused"); - assert!(err.contains("d.toml") && err.contains("[rows.response]") && err.contains("`a.id`")); + assert!( + err.contains("d.toml") && err.contains("[rows.response]") && err.contains("`a.id`") + ); let once = crate::toml_lite::parse_text("[rows.response]\n\"a.id\" = { ir = \"x\" }\n"); super::refuse_repeated_keys("d.toml", &once).expect("a key stated once"); } From d0cd917d5dadde1fb5ccfe1ef0359fb7ccaed81c Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 05:15:53 -0700 Subject: [PATCH 23/49] llm plane: a pools section dealt alone (stage 3g, #465) is not judged against models it does not state The boot's new stage 3g deals each door its declared-verb section and asks its validate; the llm door is dealt {pools: ...} without the sections it consumes, and read every member as an unknown model, refusing a valid deployment at boot (boot_lines_neutrality, boot_tls_refusal_once under llm-on-driver). The member check runs when the blob states models (open and refresh hand it the consumed sections); the kernel's config validation refuses an undefined member in 1.5.5's words. Test: a_pools_section_dealt_alone_names_no_unknown_model (RED arm: with models stated, still refused). --- crates/busbar-plane-llm/src/exchange/shaping.rs | 6 +++++- crates/busbar-plane-llm/tests/exchange_shaping.rs | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/busbar-plane-llm/src/exchange/shaping.rs b/crates/busbar-plane-llm/src/exchange/shaping.rs index e99575493a..69500e56ac 100644 --- a/crates/busbar-plane-llm/src/exchange/shaping.rs +++ b/crates/busbar-plane-llm/src/exchange/shaping.rs @@ -424,7 +424,11 @@ impl Shaping { }; lanes.insert(name, lane); } - for (name, members) in &pools { + // A pool member is judged against the models the settings STATE: a blob dealt the plane's + // own section alone (the boot's stage 3g deal, `{pools: ...}`) states none, and the + // kernel's config validation refuses an undefined member there in 1.5.5's words. + let models_stated = settings.get(sections::MODELS).is_some(); + for (name, members) in pools.iter().filter(|_| models_stated) { if let Some(m) = members.iter().find(|m| !lanes.contains_key(&m.model)) { return Err(format!( "pool '{name}' references unknown model '{}'", diff --git a/crates/busbar-plane-llm/tests/exchange_shaping.rs b/crates/busbar-plane-llm/tests/exchange_shaping.rs index d8b79bd23d..260a41726c 100644 --- a/crates/busbar-plane-llm/tests/exchange_shaping.rs +++ b/crates/busbar-plane-llm/tests/exchange_shaping.rs @@ -82,6 +82,20 @@ fn a_generation_the_plane_cannot_serve_is_refused() { .contains("unknown model")); } +/// THE DEALT SECTION ALONE (the boot's stage 3g deal hands the plane `{pools: ...}`, no `models`): +/// its members are not judged against models the blob does not state, so a valid deployment is not +/// refused at boot; with `models` stated, an undefined member is still refused (the RED arm). +#[test] +fn a_pools_section_dealt_alone_names_no_unknown_model() { + let dealt = json!({ "pools": { "op": { "members": [{ "model": "test-model" }] } } }); + Shaping::from_settings(&dealt).expect("the dealt section alone reads"); + let mut stated = dealt.clone(); + stated["models"] = json!({}); + assert!(Shaping::from_settings(&stated) + .unwrap_err() + .contains("unknown model")); +} + #[test] fn empty_settings_are_an_empty_generation_with_the_defaults() { let s = Shaping::from_settings(&json!({})).expect("reads"); From fca5772fca77d71850300516479367e1df205c28 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 07:33:49 -0700 Subject: [PATCH 24/49] WIP: door-path gaps (ARCHITECT Q128): the dropped-controls audit row through RECORD_AUDIT (AUDIT_DEGRADED, 1.5.5 bytes; header regenerated) and the ranking signals (MemberSignals: latency, free concurrency, budget, breaker state, error rate, p95 reservoir; pool member tier/tags/cost) on the door far end The dropped-controls door test passes; the ranking-signal door test is still red (the pool ranker is not shown the candidates yet). --- .../busbar-contract/include/busbar_plugin.h | 1 + crates/busbar-contract/src/abi/plane/check.rs | 2 +- crates/busbar-contract/src/abi/plane/mod.rs | 4 + .../src/abi/tests/plane_check_tests.rs | 4 +- .../busbar-kernel/src/plane_driver/far_end.rs | 50 ++++- crates/busbar-kernel/src/plane_driver/mod.rs | 7 +- .../src/exchange/reply/mod.rs | 18 +- crates/busbar-plane-llm/src/plane_door.rs | 61 +++++- crates/busbar/src/root/egress_ports.rs | 6 + crates/busbar/src/root/model_egress.rs | 206 ++++++++++++++++-- crates/busbar/src/root/serve.rs | 37 +++- crates/busbar/src/root/tests/serve_door.rs | 128 +++++++++++ .../busbar/src/root/tests/serve_hook_seats.rs | 73 ++++++- 13 files changed, 553 insertions(+), 44 deletions(-) diff --git a/crates/busbar-contract/include/busbar_plugin.h b/crates/busbar-contract/include/busbar_plugin.h index 9e78ce22dc..70b84ae9af 100644 --- a/crates/busbar-contract/include/busbar_plugin.h +++ b/crates/busbar-contract/include/busbar_plugin.h @@ -405,6 +405,7 @@ extern "C" { #define BB_PLANE_AUDIT_NONE UINT32_C(0) /* [`ServeOut::audit`]: no audit row. */ #define BB_PLANE_AUDIT_APPLIED UINT32_C(1) /* [`ServeOut::audit`]: the request applied; the row's outcome is `applied`. */ #define BB_PLANE_AUDIT_REJECTED UINT32_C(2) /* [`ServeOut::audit`]: the request was rejected after it was judged; the row's outcome is */ +#define BB_PLANE_AUDIT_DEGRADED UINT32_C(3) /* A [`RECORD_AUDIT`] write's outcome only (never [`ServeOut::audit`]): the unit was served with */ #define BB_PLANE_CHAIN_LENGTH_PREFIXED UINT32_C(1) /* [`RecordChain::framing`]: each field of the record's digest is length-prefixed. */ #define BB_PLANE_CHAIN_PIPE_SEPARATED UINT32_C(2) /* [`RecordChain::framing`]: the fields of the record's digest are joined by `|`. */ #define BB_PLANE_CHAIN_DIGESTS_SCOPE UINT32_C(1) /* [`RecordChain::flags`]: the record's scope enters its digest. */ diff --git a/crates/busbar-contract/src/abi/plane/check.rs b/crates/busbar-contract/src/abi/plane/check.rs index d1ad499531..12f726499a 100644 --- a/crates/busbar-contract/src/abi/plane/check.rs +++ b/crates/busbar-contract/src/abi/plane/check.rs @@ -151,7 +151,7 @@ fn records(buf: &[RecordWrite], n: u64, arena: u64, b: &Bounds) -> Result<(), Fa code( u64::from(r.kind), u64::from(super::AUDIT_APPLIED), - u64::from(super::AUDIT_REJECTED), + u64::from(super::AUDIT_DEGRADED), "record.audit_outcome", )?; if r.key.len == 0 { diff --git a/crates/busbar-contract/src/abi/plane/mod.rs b/crates/busbar-contract/src/abi/plane/mod.rs index 8c920828d9..692796b4f4 100644 --- a/crates/busbar-contract/src/abi/plane/mod.rs +++ b/crates/busbar-contract/src/abi/plane/mod.rs @@ -574,6 +574,10 @@ pub const AUDIT_APPLIED: u32 = 1; /// [`ServeOut::audit`]: the request was rejected after it was judged; the row's outcome is /// `rejected`. pub const AUDIT_REJECTED: u32 = 2; +/// A [`RECORD_AUDIT`] write's outcome only (never [`ServeOut::audit`]): the unit was served with +/// something the caller asked for left out (a control the far end's form cannot carry); the row's +/// outcome is `degraded`, the 1.5.5 row's. +pub const AUDIT_DEGRADED: u32 = 3; /// [`RecordChain::framing`]: each field of the record's digest is length-prefixed. pub const CHAIN_LENGTH_PREFIXED: u32 = 1; diff --git a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs index b173a11567..cb0f76fca7 100644 --- a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs +++ b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs @@ -700,7 +700,7 @@ fn an_audit_record_names_its_outcome_and_action() { record_kinds: 0, ..bounds() }; - for outcome in [AUDIT_APPLIED, AUDIT_REJECTED] { + for outcome in [AUDIT_APPLIED, AUDIT_REJECTED, AUDIT_DEGRADED] { r.kind = outcome; assert_eq!( check_on_piece(Ready, &o, (&[], &[r], &[]), &caps(), &none), @@ -708,7 +708,7 @@ fn an_audit_record_names_its_outcome_and_action() { "outcome {outcome}" ); } - for outcome in [AUDIT_NONE, AUDIT_REJECTED + 1, u32::MAX] { + for outcome in [AUDIT_NONE, AUDIT_DEGRADED + 1, u32::MAX] { r.kind = outcome; assert_eq!( piece(&o, &[], &[r], &[]), diff --git a/crates/busbar-kernel/src/plane_driver/far_end.rs b/crates/busbar-kernel/src/plane_driver/far_end.rs index 705856a0d8..a04c299c0f 100644 --- a/crates/busbar-kernel/src/plane_driver/far_end.rs +++ b/crates/busbar-kernel/src/plane_driver/far_end.rs @@ -319,6 +319,7 @@ impl Egress { }), probe_of: None, described: None, + signals: None, constraint: Mutex::new(None), } } @@ -353,6 +354,7 @@ impl Egress { }), probe_of: Some(destination), described: None, + signals: None, constraint: Mutex::new(None), }) } @@ -410,10 +412,39 @@ pub struct EgressFarEnd<'e> { /// What the hooks are shown of each member beyond the walk's own facts, by member name: the /// operator's tags, tier and cost (the plane's section states them). described: Option<Arc<HashMap<String, MemberFacts>>>, + /// Each member's live standing as the ranking hooks read it ([`MemberSignals`]). + signals: Option<Arc<dyn MemberSignals>>, /// The hooks' constraint on the walk, once the request stage set one. constraint: Mutex<Option<Constraint>>, } +/// ONE MEMBER'S LIVE STANDING, as the ranking hooks read it (1.5.5 fed every candidate these from +/// the lane store, and the ones a deployment declares as catalog signals): its latency signal, its +/// free concurrency, its remaining lifetime budget, and its breaker state, recent error rate and +/// p95 latency in the routing pool. +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub struct MemberStanding { + /// The latency signal (milliseconds), once a sample is recorded. + pub latency_ms: Option<f64>, + /// The free concurrency. + pub available_concurrency: usize, + /// The remaining lifetime request budget; `None` = unlimited. + pub budget_remaining: Option<i64>, + /// The breaker state in the pool (`closed` | `open` | `half_open`). + pub breaker_state: Option<&'static str>, + /// The recent error rate in the pool, once an outcome is in its window. + pub error_rate: Option<f64>, + /// The p95 latency (whole milliseconds), once a sample is held. + pub latency_p95_ms: Option<u64>, +} + +/// WHERE A MEMBER'S STANDING IS READ: the composition root's lane store, by the routing pool and +/// the member's destination. +pub trait MemberSignals: Send + Sync { + /// `destination`'s standing in `pool`. + fn standing(&self, pool: &str, destination: DestinationId) -> MemberStanding; +} + /// What the operator states of one member for the hooks: its tags, its tier and its cost. #[derive(Debug, Clone, Default, PartialEq)] pub struct MemberFacts { @@ -435,6 +466,13 @@ impl EgressFarEnd<'_> { self } + /// Where each member's live standing is read for the hooks. + #[must_use] + pub fn signals(mut self, signals: Arc<dyn MemberSignals>) -> Self { + self.signals = Some(signals); + self + } + fn facts_of(&self, member: &Member) -> Option<&MemberFacts> { self.described.as_ref().and_then(|d| d.get(&member.name)) } @@ -1419,6 +1457,11 @@ impl FarEnd for EgressFarEnd<'_> { .enumerate() .map(|(idx, m)| { let facts = self.facts_of(m); + let standing = self + .signals + .as_ref() + .map(|s| s.standing(&pool.name, m.destination)) + .unwrap_or_default(); CandidateFacts { idx, model: facts @@ -1435,7 +1478,12 @@ impl FarEnd for EgressFarEnd<'_> { tier: facts.and_then(|f| f.tier.clone()), cost_per_mtok: facts.and_then(|f| f.cost_per_mtok), tags: facts.map(|f| f.tags.clone()).unwrap_or_default(), - ..CandidateFacts::default() + latency_ms: standing.latency_ms, + available_concurrency: standing.available_concurrency, + budget_remaining: standing.budget_remaining, + breaker_state: standing.breaker_state, + error_rate: standing.error_rate, + latency_p95_ms: standing.latency_p95_ms, } }) .collect(), diff --git a/crates/busbar-kernel/src/plane_driver/mod.rs b/crates/busbar-kernel/src/plane_driver/mod.rs index a953a154bc..55880162b4 100644 --- a/crates/busbar-kernel/src/plane_driver/mod.rs +++ b/crates/busbar-kernel/src/plane_driver/mod.rs @@ -87,8 +87,8 @@ use tokio::sync::{watch, Notify}; pub use cancel::{CancelBill, Checkpoint, MoneySeam}; pub use epoch::FlushEpoch; pub use far_end::{ - AuthBinding, Egress, EgressFarEnd, MemberFacts, MemberRoute, ResponseKeep, Ride, UnitRoute, - DEFAULT_ERROR_BODY_MAX, + AuthBinding, Egress, EgressFarEnd, MemberFacts, MemberRoute, MemberSignals, MemberStanding, + ResponseKeep, Ride, UnitRoute, DEFAULT_ERROR_BODY_MAX, }; pub use hooks::{ Bind, BoundHooks, CallerFacts, CallerKey, CandidateFacts, Candidates, Constraint, EngineCaller, @@ -252,10 +252,11 @@ pub(crate) fn audit_row_to( value: &[u8], principal: Option<&PrincipalId>, ) -> Result<(), ()> { - use busbar_contract::abi::plane::{AUDIT_APPLIED, AUDIT_REJECTED}; + use busbar_contract::abi::plane::{AUDIT_APPLIED, AUDIT_DEGRADED, AUDIT_REJECTED}; let outcome = match outcome { AUDIT_APPLIED => busbar_contract::vocab::OUTCOME_APPLIED, AUDIT_REJECTED => busbar_contract::vocab::OUTCOME_REJECTED, + AUDIT_DEGRADED => busbar_contract::vocab::OUTCOME_DEGRADED, _ => return Err(()), }; let action = std::str::from_utf8(key).map_err(|_| ())?; diff --git a/crates/busbar-plane-llm/src/exchange/reply/mod.rs b/crates/busbar-plane-llm/src/exchange/reply/mod.rs index 57483b2948..dbb052485e 100644 --- a/crates/busbar-plane-llm/src/exchange/reply/mod.rs +++ b/crates/busbar-plane-llm/src/exchange/reply/mod.rs @@ -149,6 +149,11 @@ pub struct Piece<'a> { pub fault: Option<Fault>, /// The caller's answer is complete. pub done: bool, + /// The wire paths of a delivered TRANSLATE answer that did not cross to the caller's dialect + /// (design F3 "Drops"), already warned: the door reports one `egress.control_unrepresentable` + /// audit row per path (`<path> from <dialect>`), 1.5.5's row. Empty on a relay of the caller's + /// own dialect and on every end that delivers nothing. + pub dropped: Vec<String>, } enum State { @@ -227,6 +232,7 @@ fn answered<'a>(r: Rendered, units: Units, verdict: Verdict, fault: Option<Fault verdict, fault, done: true, + dropped: Vec::new(), } } @@ -371,6 +377,7 @@ impl Reply { units: units.clone(), fault: None, done: false, + dropped: Vec::new(), }; } let end = relay.end(); @@ -393,6 +400,7 @@ impl Reply { }, fault, done: true, + dropped: end.dropped, } } } @@ -427,6 +435,7 @@ impl Reply { verdict: Verdict::Hard, fault: Some(Fault::Transient(cut.reason)), done: true, + dropped: Vec::new(), } } } @@ -505,6 +514,10 @@ fn whole_piece<'a>( wire::token_usage_of(&w.usage).as_ref(), wire::open_units_of(&w.usage), ); + let dropped = match w.end { + whole::WholeEnd::Delivered => std::mem::take(&mut w.dropped), + _ => Vec::new(), + }; let (verdict, fault) = match w.end { whole::WholeEnd::Delivered => (Verdict::Ok, None), whole::WholeEnd::FailedGeneration => ( @@ -517,5 +530,8 @@ fn whole_piece<'a>( whole::WholeEnd::Cut => (Verdict::Hard, Some(Fault::Transient("transport"))), whole::WholeEnd::IngressUnsupported | whole::WholeEnd::OverCap => (Verdict::Hard, None), }; - answered(w.answer, units, verdict, fault) + Piece { + dropped, + ..answered(w.answer, units, verdict, fault) + } } diff --git a/crates/busbar-plane-llm/src/plane_door.rs b/crates/busbar-plane-llm/src/plane_door.rs index 56f0d84892..09e9a69241 100644 --- a/crates/busbar-plane-llm/src/plane_door.rs +++ b/crates/busbar-plane-llm/src/plane_door.rs @@ -69,6 +69,7 @@ use busbar_contract::abi::plane::{ VERDICT_NONE, VERDICT_OK, VERDICT_RETRY, }; use busbar_contract::abi::plane::{PlaneCancelIn, PlaneCancelOut}; +use busbar_contract::abi::plane::{RecordWrite, AUDIT_DEGRADED, RECORD_AUDIT}; use busbar_contract::abi::sdk::door::{abi_str, statement}; use busbar_contract::abi::sdk::life::Refusal; use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; @@ -426,8 +427,14 @@ struct Answer { verdict: u32, /// The caller's reply is complete. done: bool, + /// The unit's audit rows, `(action, resource)`, each written as a degraded `RECORD_AUDIT`: + /// what a TRANSLATE attempt could not carry (1.5.5's `egress.control_unrepresentable` rows). + audits: Vec<(Vec<u8>, Vec<u8>)>, } +/// The action of a dropped control's audit row (1.5.5's). +const DROPPED_CONTROL: &[u8] = b"egress.control_unrepresentable"; + impl Answer { /// The walk ends here: nothing for the far end, nothing more for the caller. fn hard() -> Self { @@ -775,6 +782,9 @@ fn attempt(unit: &mut UnitState, piece: &PieceIn<'_>) -> Answer { }; match built { Ok(request) => { + // A control the far end's dialect cannot carry, one degraded row each (`<control> on + // <dialect>`, the row 1.5.5 wrote as it translated the request). + let egress = unit.shaping.lane(&unit.member).map_or("", |l| l.dialect); let answer = Answer { to_far_end: true, request: Some(( @@ -782,6 +792,16 @@ fn attempt(unit: &mut UnitState, piece: &PieceIn<'_>) -> Answer { request.target.as_bytes().to_vec(), )), fields: owned(&request.fields), + audits: request + .dropped_controls + .iter() + .map(|c| { + ( + DROPPED_CONTROL.to_vec(), + format!("{c} on {egress}").into_bytes(), + ) + }) + .collect(), ..Answer::default() }; unit.request = Some(request); @@ -831,8 +851,21 @@ fn far_end(unit: &mut UnitState, piece: &PieceIn<'_>) -> Answer { let Some(reply) = unit.reply.as_mut() else { return Answer::hard(); }; - let fed = reply.feed(&ctx, piece.bytes, piece.last, at(piece.clock, unit.started)); - to_caller(fed) + let mut fed = reply.feed(&ctx, piece.bytes, piece.last, at(piece.clock, unit.started)); + // An answer member the caller's dialect has no form for, one degraded row each (`<path> from + // <dialect>`, the row 1.5.5 wrote as it delivered the translated answer). + let dropped = std::mem::take(&mut fed.dropped); + let mut answer = to_caller(fed); + answer.audits = dropped + .iter() + .map(|p| { + ( + DROPPED_CONTROL.to_vec(), + format!("{p} from {}", lane.dialect).into_bytes(), + ) + }) + .collect(); + answer } /// The plane's answer to one piece. @@ -862,12 +895,16 @@ fn settle( out: &mut Out<'_, OnPieceOut>, fields: &HostBuf<'_, OutField>, units: &HostBuf<'_, UnitCount>, + records: &HostBuf<'_, RecordWrite>, arena: &HostBuf<'_, u8>, ) -> bool { - let short = !(fields.fits() && units.fits() && arena.fits()); + let short = !(fields.fits() && units.fits() && records.fits() && arena.fits()); let (fw, fnd) = fields.settle(short); let (uw, und) = units.settle(short); + let (rw, rnd) = records.settle(short); let (aw, and) = arena.settle(short); + out.set(|o| &o.records_written, rw as u32); + out.set(|o| &o.records_needed, rnd as u32); 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); @@ -889,8 +926,12 @@ fn deliver( return (Outcome::Fault, false); }; if !p.opened { - let (mut fields, mut units, mut arena) = - (input.fields_buf(), input.units_buf(), input.arena_buf()); + let (mut fields, mut units, mut records, mut arena) = ( + input.fields_buf(), + input.units_buf(), + input.records_buf(), + input.arena_buf(), + ); let request = p .answer .request @@ -903,7 +944,15 @@ fn deliver( }); } units.extend(&p.answer.units); - if settle(out, &fields, &units, &arena) { + for (action, resource) in &p.answer.audits { + records.push(RecordWrite { + kind: AUDIT_DEGRADED, + op: RECORD_AUDIT, + key: arena.span(action), + value: arena.span(resource), + }); + } + if settle(out, &fields, &units, &records, &arena) { return (Outcome::Failed, false); } if let Some((verb, target)) = request { diff --git a/crates/busbar/src/root/egress_ports.rs b/crates/busbar/src/root/egress_ports.rs index 967efc6076..4091bc2a68 100644 --- a/crates/busbar/src/root/egress_ports.rs +++ b/crates/busbar/src/root/egress_ports.rs @@ -100,6 +100,12 @@ impl MemberPermits { } } + /// The free slots of `destination`; `None` for a member with no limit. + #[must_use] + pub fn available(&self, destination: DestinationId) -> Option<usize> { + self.limits.get(&destination).map(|s| s.available_permits()) + } + fn slot(destination: DestinationId, held: Option<OwnedSemaphorePermit>) -> Permit { Permit::new(Box::new(Slot { destination, diff --git a/crates/busbar/src/root/model_egress.rs b/crates/busbar/src/root/model_egress.rs index cf61dfb8d5..ec90d07aa1 100644 --- a/crates/busbar/src/root/model_egress.rs +++ b/crates/busbar/src/root/model_egress.rs @@ -71,6 +71,9 @@ pub struct ModelPools { /// Each model's active health probing (K7): its provider's `health:` over the process-wide /// defaults, as 1.5.5's prober resolved it per lane. probes: BTreeMap<String, busbar_kernel::probe::ProbeCfg>, + /// What each pool's members show the ranking hooks, by pool then model: the member's tier and + /// tags, and its model's cost per million tokens off the rate card (1.5.5's pool member meta). + meta: BTreeMap<String, Arc<HashMap<String, busbar_kernel::plane_driver::MemberFacts>>>, } impl ModelPools { @@ -175,7 +178,34 @@ impl ModelPools { )) }) .collect(); + let meta = cfg + .pools + .iter() + .map(|(name, pool)| { + let members = pool + .members + .iter() + .map(|m| { + ( + m.model.clone(), + busbar_kernel::plane_driver::MemberFacts { + model: m.model.clone(), + tags: m.tags.clone(), + tier: m.tier.clone(), + cost_per_mtok: cfg + .rate_card + .as_ref() + .and_then(|card| card.get(&m.model)) + .map(busbar_kernel::config::rate_entry_per_mtok), + }, + ) + }) + .collect(); + (name.clone(), Arc::new(members)) + }) + .collect(); ModelPools { + meta, pools, lanes, breaker: BreakerPolicy::from_pools(&cfg.pools), @@ -238,6 +268,108 @@ impl std::fmt::Debug for ModelServing { } } +/// The number of latency samples a lane's p95 reservoir holds (1.5.5's ring). +const LATENCY_RESERVOIR_LEN: usize = 128; + +/// ONE LANE'S P95 LATENCY RESERVOIR (1.5.5's `LatencyReservoir`, moved verbatim): a ring of the +/// last [`LATENCY_RESERVOIR_LEN`] samples in microseconds, written lock-free. +#[derive(Debug)] +pub struct LatencyReservoir { + cursor: std::sync::atomic::AtomicUsize, + samples: [std::sync::atomic::AtomicU32; LATENCY_RESERVOIR_LEN], +} + +impl Default for LatencyReservoir { + fn default() -> Self { + Self { + cursor: std::sync::atomic::AtomicUsize::new(0), + samples: std::array::from_fn(|_| std::sync::atomic::AtomicU32::new(0)), + } + } +} + +impl LatencyReservoir { + /// Record one sample, in milliseconds. A non-finite or non-positive sample is ignored. + pub fn record(&self, latency_ms: f64) { + use std::sync::atomic::Ordering; + if !latency_ms.is_finite() || latency_ms <= 0.0 { + return; + } + // `as` saturates a float-to-int cast, so an absurdly long sample clamps to `u32::MAX` us. + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let us = ((latency_ms * 1000.0).round() as u32).max(1); + let slot = self.cursor.fetch_add(1, Ordering::Relaxed) % LATENCY_RESERVOIR_LEN; + self.samples[slot].store(us, Ordering::Relaxed); + } + + /// The nearest-rank 95th percentile of the held samples, in whole milliseconds rounded UP; + /// `None` while the ring holds no sample. + #[must_use] + pub fn p95_ms(&self) -> Option<u64> { + let mut held: Vec<u32> = self + .samples + .iter() + .map(|s| s.load(std::sync::atomic::Ordering::Relaxed)) + .filter(|v| *v != 0) + .collect(); + if held.is_empty() { + return None; + } + held.sort_unstable(); + let rank = (held.len() * 95).div_ceil(100); + Some(u64::from(held[rank - 1]).div_ceil(1000)) + } +} + +/// EACH MEMBER'S STANDING AS THE RANKING HOOKS READ IT, off the generation's lane store, as 1.5.5's +/// candidate projection read it: the latency signal, the free concurrency (the member's own permits +/// where its `max_concurrent` is stated, the lane's otherwise), the remaining lifetime budget, the +/// breaker state and error rate in the routing pool, and the lane's p95 reservoir. +pub struct ModelSignals { + app: AppSource, + permits: Arc<crate::root::egress_ports::MemberPermits>, + reservoirs: Arc<HashMap<usize, LatencyReservoir>>, +} + +impl busbar_kernel::plane_driver::MemberSignals for ModelSignals { + fn standing( + &self, + pool: &str, + destination: DestinationId, + ) -> busbar_kernel::plane_driver::MemberStanding { + let (app, lane) = ((self.app)(), lane_of(destination)); + let store = &app.store; + let state = match store.breaker_state_snapshot_in(pool, lane) { + busbar_kernel::store::BreakerState::Closed => "closed", + busbar_kernel::store::BreakerState::Open { .. } => "open", + busbar_kernel::store::BreakerState::HalfOpen => "half_open", + }; + busbar_kernel::plane_driver::MemberStanding { + latency_ms: store.lane_latency_ms(lane), + available_concurrency: self + .permits + .available(destination) + .unwrap_or_else(|| store.available_permits(lane)), + budget_remaining: store.lane_budget_remaining(lane), + breaker_state: Some(state), + error_rate: store.error_rate_in(pool, lane, busbar_kernel::store::now()), + latency_p95_ms: self + .reservoirs + .get(&lane) + .and_then(LatencyReservoir::p95_ms), + } + } +} + +/// What the hooks are shown of the model-serving pools beside the walk: each pool's member meta +/// and the members' live standing. +pub struct ModelShown { + /// Each pool's member meta, by pool. + pub described: BTreeMap<String, Arc<HashMap<String, busbar_kernel::plane_driver::MemberFacts>>>, + /// The members' live standing. + pub signals: Arc<dyn busbar_kernel::plane_driver::MemberSignals>, +} + /// The kernel's breaker port over its lane store: a model-named direct route is mapped onto the /// lane-default cell, and every outcome is recorded through the store's own record calls. struct DirectCells { @@ -409,6 +541,8 @@ impl Breaker for DirectCells { struct ModelTelemetry { app: AppSource, queued: crate::root::egress_ports::WalkTelemetry, + /// Each lane's p95 reservoir, fed only while the generation declares the signal. + reservoirs: Arc<HashMap<usize, LatencyReservoir>>, } impl busbar_kernel_egress::ports::Telemetry for ModelTelemetry { @@ -436,9 +570,19 @@ impl busbar_kernel_egress::ports::Telemetry for ModelTelemetry { fn upstream_latency(&self, destination: DestinationId, ms: f64) { // The lane's latency signal (the admin pool listing's `latency_ms`, the `fastest` order's // input), as 1.5.5's served attempt recorded it. - (self.app)() - .store - .record_latency_in("", lane_of(destination), ms); + let app = (self.app)(); + let lane = lane_of(destination); + app.store.record_latency_in("", lane, ms); + // The same sample into the lane's p95 reservoir, ONLY while the generation declares the + // signal, as 1.5.5 collected it. + if app + .requested_signals + .wants(busbar_contract::signal::Signal::CandidateLatencyP95Ms) + { + if let Some(r) = self.reservoirs.get(&lane) { + r.record(ms); + } + } } fn queued(&self, pool: &str, delta: i64) { @@ -470,7 +614,7 @@ pub fn compose( routes: &BTreeMap<String, busbar_kernel::plane_driver::MemberRoute>, journal: Arc<dyn busbar_kernel_egress::ports::Journal>, stream_ceiling_secs: u64, -) -> Result<busbar_kernel::plane_driver::Egress, String> { +) -> Result<(busbar_kernel::plane_driver::Egress, ModelShown), String> { let plan = &serving.pools; let mut sealed = HashMap::new(); let mut names = Vec::new(); @@ -538,6 +682,22 @@ pub fn compose( built.insert(model.clone(), p); direct.insert(model.clone()); } + let permits = Arc::new(crate::root::egress_ports::MemberPermits::new(limits)); + let reservoirs: Arc<HashMap<usize, LatencyReservoir>> = Arc::new( + serving + .lanes + .values() + .map(|lane| (*lane, LatencyReservoir::default())) + .collect(), + ); + let shown = ModelShown { + described: plan.meta.clone(), + signals: Arc::new(ModelSignals { + app: Arc::clone(&serving.app), + permits: Arc::clone(&permits), + reservoirs: Arc::clone(&reservoirs), + }), + }; let app = Arc::clone(&serving.app); let breaker = DirectCells { inner: BreakerAdapter::over( @@ -548,21 +708,25 @@ pub fn compose( records: plan.records.clone(), direct, }; - Ok(busbar_kernel::plane_driver::Egress { - caller, - conns, - breaker: Arc::new(breaker), - capacity: Arc::new(crate::root::egress_ports::MemberPermits::new(limits)), - clock: Arc::new(crate::root::egress_ports::NodeClock::new()), - journal, - telemetry: Arc::new(ModelTelemetry { - app: Arc::clone(&serving.app), - queued: crate::root::egress_ports::WalkTelemetry::new(names), - }), - floor: busbar_kernel_egress::WeightedFloor::new(), - pools: built, - routes: sealed, - stream_ceiling_secs, - error_body_max: busbar_kernel::plane_driver::DEFAULT_ERROR_BODY_MAX, - }) + Ok(( + busbar_kernel::plane_driver::Egress { + caller, + conns, + breaker: Arc::new(breaker), + capacity: permits, + clock: Arc::new(crate::root::egress_ports::NodeClock::new()), + journal, + telemetry: Arc::new(ModelTelemetry { + app: Arc::clone(&serving.app), + queued: crate::root::egress_ports::WalkTelemetry::new(names), + reservoirs, + }), + floor: busbar_kernel_egress::WeightedFloor::new(), + pools: built, + routes: sealed, + stream_ceiling_secs, + error_body_max: busbar_kernel::plane_driver::DEFAULT_ERROR_BODY_MAX, + }, + shown, + )) } diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index bc020a1c85..ad589acee1 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -421,6 +421,9 @@ pub struct DoorLive { /// The health-probe service's target for this generation (K7), when the plane answers probes /// and a member probes: held here, so a replaced generation's probers exit at their next tick. pub probes: Option<Arc<busbar_kernel::plane_driver::PlaneProbes>>, + /// What the ranking hooks are shown of the model-serving pools' members beside the walk: their + /// meta and their live standing (`None` for a plane whose egress is the generic walk). + pub shown: Option<crate::root::model_egress::ModelShown>, } /// What a door plane's egress is re-sealed over on a config apply: the providers the deployment @@ -556,6 +559,7 @@ impl DoorApply { egress, facts: self.facts.clone(), probes: None, + shown: None, }) } } @@ -1162,7 +1166,8 @@ fn seal_live( &routes, Arc::clone(&egress.journal), egress.reach.stream_ceiling_secs, - ), + ) + .map(|(e, shown)| (e, Some(shown))), _ => crate::root::door_steps::compose_egress( &facts, &pools, @@ -1171,12 +1176,18 @@ fn seal_live( &routes, Arc::clone(&egress.journal), egress.reach.stream_ceiling_secs, - ), + ) + .map(|e| (e, None)), }; - Some(Arc::new(sealed.map_err(|e| format!("{instance}: {e}"))?)) + let (sealed, shown) = sealed.map_err(|e| format!("{instance}: {e}"))?; + Some((Arc::new(sealed), shown)) } None => None, }; + let (egress, shown) = match egress { + Some((e, shown)) => (Some(e), shown), + None => (None, None), + }; Ok(DoorLive { section: section.clone(), generation, @@ -1184,6 +1195,7 @@ fn seal_live( egress, facts, probes: None, + shown, }) } @@ -2188,6 +2200,7 @@ impl DataRoutes { ); let far = DoorFar { egress: live.egress.as_deref(), + shown: live.shown.as_ref(), steps: &steps, unit, credential: door.credential, @@ -2438,6 +2451,7 @@ impl DataRoutes { } let far = DoorFar { egress: live.egress.as_deref(), + shown: live.shown.as_ref(), steps: &steps, unit, credential, @@ -2577,6 +2591,8 @@ fn report_of( #[cfg(linked_axis_node)] struct DoorFar<'d, 's> { egress: Option<&'d Egress>, + /// What the hooks are shown of the members beside the walk. + shown: Option<&'d crate::root::model_egress::ModelShown>, steps: &'d DoorSteps<'s>, unit: busbar_contract::UnitKey, /// The caller's verified credential, lent to the walk for a passthrough member alone. @@ -2591,13 +2607,22 @@ impl<'d> DoorFar<'d, '_> { .get_or_init(|| { let egress = self.egress?; let routed = self.steps.routed()?; - Some(egress.unit(UnitRoute { + let pool = egress_pool(self.steps.plane(), &routed); + let described = self.shown.and_then(|s| s.described.get(&pool).cloned()); + let mut far = egress.unit(UnitRoute { unit: self.unit, - pool: egress_pool(self.steps.plane(), &routed), + pool, caller_credential: self.credential.clone(), once: self.steps.once(), ..UnitRoute::default() - })) + }); + if let Some(described) = described { + far = far.described(described); + } + if let Some(shown) = self.shown { + far = far.signals(Arc::clone(&shown.signals)); + } + Some(far) }) .as_ref() } diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index 4b50c95b6a..2442f84bb1 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -861,3 +861,131 @@ async fn the_pools_doors_catalogue_shows_a_restricted_key_only_what_it_reaches() "the reachable pool and its member, nothing else" ); } + +/// An anthropic message whose usage carries a member the openai caller's dialect has no form for. +#[cfg(linked_fold_on_driver)] +const ANTHROPIC_ANSWER: &str = r#"{"id":"msg_1","type":"message","role":"assistant","model":"m0","content":[{"type":"text","text":"hi"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":1,"output_tokens":1,"output_tokens_details":{"reasoning_tokens":0}}}"#; + +/// THE DROPPED-CONTROLS AUDIT ROW ON THE DOOR (ARCHITECT, Q128 gap): a TRANSLATE attempt that +/// cannot carry a control the caller set writes 1.5.5's `egress.control_unrepresentable` row, +/// outcome `degraded`, `<control> on <dialect>`; an answer member the caller's dialect has no form +/// for writes `<path> from <dialect>`. Both reach the kernel's audit chain through the plane's +/// `RECORD_AUDIT` write. RED arm: a request that sets nothing the far dialect drops writes no row. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_audits_a_control_the_far_dialect_cannot_carry() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-dropped-controls"; + let _published = Withdrawn(instance); + let far = far_end_answering(200, ANTHROPIC_ANSWER).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + dialect: Some("anthropic"), + ..RigOpts::default() + }, + ) + .await; + let rows = |resource: &str| { + busbar_kernel::audit::auditlog::AUDIT_LOG + .list_filtered( + 0, + 1000, + Some("egress.control_unrepresentable"), + Some(resource), + ) + .into_iter() + .filter(|e| e.principal == rig.key_id && e.outcome == "degraded") + .count() + }; + let (status, _, _) = rig + .send( + "POST", + "/v1/chat/completions", + Some(serde_json::json!({"model": "p", "max_tokens": 16, + "messages": [{"role": "user", "content": "hi"}]})), + ) + .await; + assert_eq!(status, 200); + assert_eq!( + rows("logit_bias on anthropic"), + 0, + "nothing set, nothing dropped" + ); + let (status, _, _) = rig + .send( + "POST", + "/v1/chat/completions", + Some(serde_json::json!({"model": "p", "max_tokens": 16, + "logit_bias": {"50256": -100}, + "messages": [{"role": "user", "content": "hi"}]})), + ) + .await; + assert_eq!(status, 200); + assert_eq!( + rows("logit_bias on anthropic"), + 1, + "the dropped control, one row" + ); + assert!( + rows("usage.output_tokens_details from anthropic") >= 1, + "the answer member the caller's dialect cannot carry" + ); +} + +/// THE RANKING SIGNALS ON THE DOOR (ARCHITECT, Q128 gap): the hooks see each candidate as 1.5.5's +/// projection fed it: the pool member's tier and tags, the lane's latency signal once a sample is +/// recorded, its free concurrency, its remaining budget, and, where the generation declares them, +/// its breaker state, error rate and p95 latency in the routing pool. RED before: every one of them +/// was empty on the door path. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_doors_hooks_see_each_candidates_standing() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-signals"; + let _published = Withdrawn(instance); + let (a, b) = ( + far_end_answering(200, SERVED_BY_TWIN).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ); + let rig = rig( + instance, + RigOpts { + members: &[(a.port, 1), (b.port, 1)], + described: true, + ..RigOpts::default() + }, + ) + .await; + assert_eq!(rig.chat().await.0, 200); + let first = rig.gate_saw(); + assert_eq!(first.len(), 2, "{first:?}"); + assert!( + first[0].starts_with("m0 tier=Some(\"t0\") tags=[\"g0\"] latency=false avail=true"), + "{first:?}" + ); + assert!( + first[0].contains("CandidateBreakerState=closed"), + "the declared breaker state: {first:?}" + ); + assert!( + !first[0].contains("CandidateLatencyP95Ms"), + "no p95 before a sample: {first:?}" + ); + assert_eq!(rig.chat().await.0, 200); + let second = rig.gate_saw(); + let second: Vec<String> = second + .into_iter() + .filter(|c| c.contains("latency=true")) + .collect(); + assert!( + !second.is_empty(), + "the served attempt's latency sample: {second:?}" + ); + assert!( + second[0].contains("CandidateLatencyP95Ms=true") + && second[0].contains("CandidateErrorRate=true"), + "the p95 reservoir and the error rate, once fed: {second:?}" + ); +} diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs index 27b63f1cdb..e68ba9d171 100644 --- a/crates/busbar/src/root/tests/serve_hook_seats.rs +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -107,6 +107,8 @@ struct SeatProbe { reject: Option<(u16, &'static str)>, /// Tap seats: the last delivered projection. last_payload: Mutex<Option<Vec<u8>>>, + /// Gate seat: each candidate the last decide was shown, as one line. + candidates: Mutex<Vec<String>>, } impl SeatProbe { @@ -117,6 +119,7 @@ impl SeatProbe { requests_now: None, reject: None, last_payload: Mutex::new(None), + candidates: Mutex::new(Vec::new()), } } } @@ -126,11 +129,42 @@ impl RoutingPolicy for SeatProbe { async fn decide( &self, _req: &RoutingRequest<'_>, - _candidates: &[Candidate<'_>], + candidates: &[Candidate<'_>], _ctx: &RoutingContext<'_>, _budget: std::time::Duration, ) -> PolicyResult { self.log.lock().unwrap().push(self.seat.to_string()); + *self.candidates.lock().unwrap() = candidates + .iter() + .map(|c| { + format!( + "{} tier={:?} tags={:?} latency={} avail={} budget={:?} signals={:?}", + c.model, + c.tier, + c.tags, + c.latency_ms.is_some(), + c.available_concurrency > 0, + c.budget_remaining, + c.signals + .iter() + .map(|(s, v)| format!( + "{s:?}={}", + match v { + busbar_contract::SignalValue::Str(t) => t.to_string(), + other => format!( + "{}", + matches!( + other, + busbar_contract::SignalValue::F64(_) + | busbar_contract::SignalValue::U64(_) + ) + ), + } + )) + .collect::<Vec<_>>() + ) + }) + .collect(); Ok(match self.reject { Some((status, message)) => RoutingDecision::Reject { status, @@ -301,6 +335,11 @@ pub(super) struct RigOpts<'a> { pub pooled: Option<usize>, /// The pools the key may reach (`None`: every pool). pub allowed_pools: Option<&'a [&'a str]>, + /// The members' dialect (`None`: `openai`). + pub dialect: Option<&'a str>, + /// Each pool member states a tier `t<i>` and a tag `g<i>`, and the generation declares the + /// three candidate catalog signals (breaker state, error rate, p95 latency). + pub described: bool, } /// THE LLM DOOR, SERVED END TO END, as production composes it: the data router built with the @@ -319,10 +358,16 @@ pub(super) struct DoorRig { log: Arc<Mutex<Vec<String>>>, request_tap: Arc<SeatProbe>, candidate_tap: Arc<SeatProbe>, + gate_seat: Arc<SeatProbe>, read: Arc<dyn Fn() -> Ledger + Send + Sync>, } impl DoorRig { + /// Each candidate the pool's ranking policy was last shown, one line each. + pub fn gate_saw(&self) -> Vec<String> { + self.gate_seat.candidates.lock().unwrap().clone() + } + /// One request through the door as the keyed caller: its status, head and body. pub async fn send( &self, @@ -456,12 +501,16 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { let pooled = opts.pooled.unwrap_or(opts.members.len()); for (i, (_, weight)) in opts.members.iter().enumerate().take(pooled) { yaml.push_str(&format!(" - model: m{i}\n weight: {weight}\n")); + if opts.described { + yaml.push_str(&format!(" tier: t{i}\n tags: [g{i}]\n")); + } } let deploy = busbar_kernel::config::deploy_from_yaml_str(&yaml).expect("a deployment"); let mut defs_yaml = String::new(); for (i, (port, _)) in opts.members.iter().enumerate() { defs_yaml.push_str(&format!( - "oai{i}:\n protocol: openai\n base_url: 'http://127.0.0.1:{port}'\n" + "oai{i}:\n protocol: {}\n base_url: 'http://127.0.0.1:{port}'\n", + opts.dialect.unwrap_or("openai") )); } let defs: HashMap<String, busbar_kernel::config::ProviderDef> = @@ -544,7 +593,10 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { for (i, (port, _)) in opts.members.iter().enumerate() { builder = builder.lane(busbar_kernel::test_support::LaneSpec::new( &format!("m{i}"), - "openai", + match opts.dialect { + Some("anthropic") => "anthropic", + _ => "openai", + }, &format!("http://127.0.0.1:{port}"), )); } @@ -581,6 +633,8 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { let request_tap = Arc::new(SeatProbe::new("request-tap", &log)); let candidate_tap = Arc::new(SeatProbe::new("candidate-tap", &log)); let mut gate_probe = SeatProbe::new("gate", &log); + // The pool's ranking policy, which is shown the candidates (an abstaining ranker). + let ranker = Arc::new(SeatProbe::new("ranker", &log)); if opts.reject_at_gate { gate_probe.reject = Some((451, "the gate says no")); } @@ -594,6 +648,18 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { a.tap_hooks_candidate = vec![(std::time::Duration::from_millis(500), false, ct, Vec::new())]; a.global_gates = vec![(0u16, gate(Arc::new(gate_probe)))]; + if opts.described { + let r: Arc<dyn RoutingPolicy> = ranker.clone(); + a.pool_orderings.insert("p".to_string(), gate(r)); + use busbar_contract::Signal; + for s in [ + Signal::CandidateBreakerState, + Signal::CandidateErrorRate, + Signal::CandidateLatencyP95Ms, + ] { + a.requested_signals.insert(s); + } + } } // THE COMPOSITION, as production seals it: the door opened with the deployment's sections, @@ -666,6 +732,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { log, request_tap, candidate_tap, + gate_seat: ranker, read, } } From 259a3f7a82096382f4ed39e44270f9f8502fbd94 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 07:34:48 -0700 Subject: [PATCH 25/49] WIP: the door unit's far end hands the hook stage the walk's candidates, remaining, failure and takes the hooks' constraint (they defaulted to none on the door path) --- crates/busbar/src/root/serve.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index ad589acee1..c5d3f0a42b 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2667,6 +2667,31 @@ impl FarEnd for DoorFar<'_, '_> { None => false, } } + + // THE WALK'S FACTS FOR THE HOOK STAGE, as the unit's egress walk states them: its candidates, + // what it has left and why it last failed, and the hooks' constraint handed to it. Without + // these the kernel's hooks were shown no candidate and their constraint never reached the + // walk on the door path. + fn remaining(&self, token: &Pass<Route>) -> Option<usize> { + self.far().and_then(|far| far.remaining(token)) + } + + fn failure(&self, token: &Pass<Route>) -> Option<&'static str> { + self.far().and_then(|far| far.failure(token)) + } + + fn candidates( + &self, + token: &Pass<Route>, + ) -> Option<busbar_kernel::plane_driver::Candidates> { + self.far().and_then(|far| far.candidates(token)) + } + + fn constrain(&self, token: &Pass<Route>, constraint: busbar_kernel::plane_driver::Constraint) { + if let Some(far) = self.far() { + far.constrain(token, constraint); + } + } } // ── a session's caller side over today's upgrade (TRANSITIONAL) ────────────────────────────────── From 2a9fa1f0f5018b5ff884016b37d9fea93f293265 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 07:39:09 -0700 Subject: [PATCH 26/49] fmt --- crates/busbar/src/root/serve.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index c5d3f0a42b..a240fbd800 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2680,10 +2680,7 @@ impl FarEnd for DoorFar<'_, '_> { self.far().and_then(|far| far.failure(token)) } - fn candidates( - &self, - token: &Pass<Route>, - ) -> Option<busbar_kernel::plane_driver::Candidates> { + fn candidates(&self, token: &Pass<Route>) -> Option<busbar_kernel::plane_driver::Candidates> { self.far().and_then(|far| far.candidates(token)) } From 601c4917b479a57b5e6c9221c2f05c2d5b988c3a Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 07:51:44 -0700 Subject: [PATCH 27/49] =?UTF-8?q?llm=20plane:=20on=5Fpiece=20states=20the?= =?UTF-8?q?=20breaker's=20fault=20reading=20apart=20from=20its=20verdict?= =?UTF-8?q?=20(ARCHITECT=20BREAKER=20OUTCOME;=20spec=20Part=203=20=C2=A712?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A judged far-end error reads per 1.5.5's classify.rs dispositions: context length fails over with nothing recorded (RETRY + FAULT_NONE); an error_map entry naming rate_limit, and every transient, counts toward the trip (RETRY + FAULT_TRANSIENT); the caller's own bad input records nothing (HARD + FAULT_CALLER); a refused credential or account trips every cell (FAULT_HARD); a 2xx that failed after its head is the compensating transient. Written once per answer, on its first window. --- crates/busbar-plane-llm/src/plane_door.rs | 32 ++++++++++++- .../busbar-plane-llm/tests/exchange_reply.rs | 48 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/crates/busbar-plane-llm/src/plane_door.rs b/crates/busbar-plane-llm/src/plane_door.rs index 09e9a69241..e6919b35fc 100644 --- a/crates/busbar-plane-llm/src/plane_door.rs +++ b/crates/busbar-plane-llm/src/plane_door.rs @@ -76,15 +76,17 @@ use busbar_contract::abi::sdk::publish::{ClaimSpec, SnapshotSpec}; use busbar_contract::abi::sdk::{ open_failed, Generations, HostBuf, Instance, Lent, Out, Safe, SafeSlot, Services, }; +use busbar_contract::abi::transport::{FAULT_CALLER, FAULT_HARD, FAULT_NONE, FAULT_TRANSIENT}; use busbar_contract::ids::{MeterClassDecl, OpClassId}; use busbar_contract::plane::PlaneMeta; +use busbar_contract::upstream::Disposition; use serde_json::Value; use crate::codec::ir::rerank::SEARCH_UNITS_CLASS; use crate::dialect::DIALECTS; use crate::exchange::arrive::{self, envelope_for, Arrived, Declined}; use crate::exchange::attempt::{self, stream_intent, FarRequest}; -use crate::exchange::reply::{At, Piece, Reply, ReplyCtx, Units, Verdict}; +use crate::exchange::reply::{At, Fault, Piece, Reply, ReplyCtx, Units, Verdict}; use crate::exchange::shaping::{sections, Shaping}; use crate::exchange::{handler_of, probe, project, refuse}; use crate::LlmPlane; @@ -425,6 +427,8 @@ struct Answer { units: Vec<UnitCount>, /// `VERDICT_*`. verdict: u32, + /// `FAULT_*`: the breaker's reading of this answer, written once, on its first window. + fault: u8, /// The caller's reply is complete. done: bool, /// The unit's audit rows, `(action, resource)`, each written as a degraded `RECORD_AUDIT`: @@ -680,6 +684,28 @@ pub fn counts(units: &Units) -> Vec<UnitCount> { out } +/// THE BREAKER'S READING of a judged answer (ARCHITECT BREAKER OUTCOME: one neutral vocabulary, +/// stated apart from the walk's verdict), as 1.5.5's classifier recorded it per disposition (v1.5.5 +/// `crates/busbar-llm/src/engine/attempt/classify.rs`): the caller's own bad input records nothing +/// against the destination; a transient failure (an `error_map` entry naming `rate_limit` among +/// them) counts toward the pool cell's trip; a refused credential or account trips every cell; a +/// request too large for the window fails over with nothing recorded. A 2xx that failed after its +/// head (a stream's terminal error, a cut body, an untranslatable answer, a failed generation) is +/// the compensating transient 1.5.5 recorded. +#[must_use] +pub fn breaker_fault(f: Option<&Fault>) -> u8 { + match f { + None => FAULT_NONE, + Some(Fault::Transient(_)) => FAULT_TRANSIENT, + Some(Fault::Judged { disposition, .. }) => match disposition { + Disposition::ClientFault => FAULT_CALLER, + Disposition::TransientUpstream => FAULT_TRANSIENT, + Disposition::HardDown => FAULT_HARD, + Disposition::ContextLength => FAULT_NONE, + }, + } +} + fn verdict(v: Verdict) -> u32 { match v { Verdict::None => VERDICT_NONE, @@ -714,6 +740,7 @@ fn fee_unit(unit: &mut UnitState, answer: &mut Answer) { /// A reply piece as the caller's answer. fn to_caller(piece: Piece<'_>) -> Answer { + let fault = breaker_fault(piece.fault.as_ref()); let (status, fields) = piece .head .map_or((0, Vec::new()), |h| (u32::from(h.status), owned(&h.fields))); @@ -723,6 +750,7 @@ fn to_caller(piece: Piece<'_>) -> Answer { bytes: piece.bytes.into_owned(), units: counts(&piece.units), verdict: verdict(piece.verdict), + fault, done: piece.done, ..Answer::default() } @@ -976,6 +1004,8 @@ fn deliver( out.set(|o| &o.emitted, n as u64); out.set(|o| &o.more, u32::from(more)); out.set(|o| &o.verdict, p.answer.verdict); + // Once per answer: a reading recorded on every window of a long answer would count it again. + out.set(|o| &o.fault, std::mem::take(&mut p.answer.fault)); out.set(|o| &o.flags, far | if done { EMIT_DONE } else { 0 }); if !more { unit.pending = None; diff --git a/crates/busbar-plane-llm/tests/exchange_reply.rs b/crates/busbar-plane-llm/tests/exchange_reply.rs index daa93edfa2..b25809be59 100644 --- a/crates/busbar-plane-llm/tests/exchange_reply.rs +++ b/crates/busbar-plane-llm/tests/exchange_reply.rs @@ -793,6 +793,54 @@ fn a_reply_judges_a_far_end_error() { } } +/// THE BREAKER'S READING beside the walk's verdict (ARCHITECT BREAKER OUTCOME): +/// a request too large for the window fails over with nothing recorded (RETRY, FAULT_NONE); an +/// `error_map` entry naming `rate_limit` fails over and counts toward the trip (RETRY, +/// FAULT_TRANSIENT); the caller's own bad input records nothing (HARD, FAULT_CALLER); a refused +/// credential trips every cell (HARD, FAULT_HARD) — 1.5.5's per-disposition records. +#[test] +fn a_judged_far_end_error_carries_the_breakers_reading_apart_from_its_verdict() { + use busbar_contract::abi::transport::{FAULT_CALLER, FAULT_HARD, FAULT_NONE, FAULT_TRANSIENT}; + use busbar_plane_llm::plane_door::breaker_fault; + let arrived = arrival("openai", false); + let mut lane = lane("openai"); + lane.error_map = map(&[("1302", "rate_limit")]); + let ctx = ReplyCtx { + arrived: &arrived, + lane: &lane, + intent: stream_intent(chat_handler("openai"), arrived.parsed.as_ref()), + passthrough: false, + }; + let error = |code: &str| { + format!(r#"{{"error":{{"message":"m","type":"invalid_request_error","code":"{code}"}}}}"#) + }; + for (status, code, verdict, reading) in [ + (400, "context_length_exceeded", Verdict::Retry, FAULT_NONE), + (400, "1302", Verdict::Retry, FAULT_TRANSIENT), + (400, "bad_field", Verdict::Hard, FAULT_CALLER), + (401, "invalid_api_key", Verdict::Hard, FAULT_HARD), + (503, "busy", Verdict::Retry, FAULT_TRANSIENT), + ] { + let mut reply = Reply::new(&ctx, status, JSON_HEAD); + let body = error(code); + let piece = reply.feed(&ctx, body.as_bytes(), true, AT); + assert!(piece.done, "{status} {code}"); + assert_eq!(piece.verdict, verdict, "{status} {code}"); + assert_eq!( + breaker_fault(piece.fault.as_ref()), + reading, + "{status} {code}" + ); + } + // RED: a 2xx that failed after its head is the compensating transient 1.5.5 recorded; an + // answer with no fault is no reading at all. + assert_eq!( + breaker_fault(Some(&Fault::Transient("upstream-generation-failed"))), + FAULT_TRANSIENT + ); + assert_eq!(breaker_fault(None), FAULT_NONE); +} + /// A cut: a stream after its first byte ends on the caller's error frame, `Hard`, the far end's /// transfer at fault; a whole answer cut is the caller's 502. #[test] From b3f02a559d4d68d6266e61d8a7d4a968575b6ee8 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 07:59:52 -0700 Subject: [PATCH 28/49] U11 port: a body-model caller's smuggled router key never reaches the far end nor makes a JSON-array caller (legacy test_gemini_json_array_shim_ignored_for_body_model_ingress; v1.5.5 proxy/wire.rs strip_router_shim_keys) --- .../tests/exchange_attempt.rs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/busbar-plane-llm/tests/exchange_attempt.rs b/crates/busbar-plane-llm/tests/exchange_attempt.rs index c7d8afdb8a..b7a81b544e 100644 --- a/crates/busbar-plane-llm/tests/exchange_attempt.rs +++ b/crates/busbar-plane-llm/tests/exchange_attempt.rs @@ -229,6 +229,33 @@ fn a_same_dialect_unknown_body_member_goes_out() { assert_eq!(v["x_vendor_flag"], true, "the unknown member, unchanged"); } +/// A body-model caller that smuggles busbar's own array-stream router key into its body is not a +/// JSON-array caller (only a path-model arrival's URL makes one), and the key never reaches the far +/// end: 1.5.5 stripped every dialect's router key before every upstream call (v1.5.5 +/// `crates/busbar/src/proxy/wire.rs` `strip_router_shim_keys`; the retired legacy cover +/// `test_gemini_json_array_shim_ignored_for_body_model_ingress`). +#[test] +fn a_body_model_callers_smuggled_router_key_never_reaches_the_far_end() { + let h = head(&[("content-type", "application/json")]); + let a = arrived( + "/v1/chat/completions", + &h, + r#"{"model":"gpt","stream":true,"__busbar_gemini_json_array":true,"messages":[]}"#, + ); + assert!( + !a.path_model.as_ref().is_some_and(|p| p.json_array), + "a body key never makes a JSON-array caller" + ); + let r = build(&a, &h, &shaping(), "p", "gpt").expect("built"); + assert!(!r.pristine, "the router key is a governed member"); + let v: Value = busbar_plane_llm::codec::json::parse(&r.body).expect("json"); + assert!(v.get("__busbar_gemini_json_array").is_none(), "{v}"); + assert_eq!( + v["stream"], true, + "a body-model far end keeps the caller's stream" + ); +} + #[test] fn a_translated_route_drops_a_member_the_far_dialect_cannot_carry() { let h = head(&[("content-type", "application/json")]); From 56bbad998c21b2d4079fbcc31ea19616f8f91311 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 08:14:58 -0700 Subject: [PATCH 29/49] =?UTF-8?q?plane=20ABI:=20ArriveOut=20states=20ROUTE?= =?UTF-8?q?=5FSTREAM=20and=20an=20opaque=20sticky-routing=20key=20(ARCHITE?= =?UTF-8?q?CT=20Q1=20ArriveOut,=202026-10-05;=20spec=20Part=203=20=C2=A712?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit route_flags gains ROUTE_STREAM = 4 (the caller asked to stream: the kernel applies the stream ceiling and the #62 cut rules from it). ArriveOut appends affinity, the sticky key the plane computes as 1.5.5 did, opaque to the kernel; absent = no affinity. It is an AbiStr (plane memory valid to the next call, kept by the SDK like the pool) rather than a Span: arrive lends no arena for a span to index. The validator admits ROUTE_STREAM, bounds the key by MAX_TEXT, refuses a key counted with no bytes, and refuses one on any answer that admits nothing (RED arms in plane_check_tests). PlaneCalls::arrived_affinity copies it out (dispatcher: copy_str). SDK setters stream() and affinity(). Layout golden and C header regenerated (ArriveOut 168 -> 184). For SEAM to merge rather than redo. --- .../busbar-contract/include/busbar_plugin.h | 5 +- crates/busbar-contract/src/abi/plane/check.rs | 16 +++-- crates/busbar-contract/src/abi/plane/mod.rs | 17 ++++- crates/busbar-contract/src/abi/sdk/out.rs | 19 +++++ .../src/abi/tests/plane_check_tests.rs | 71 ++++++++++++++++++- crates/busbar-contract/src/plane_calls.rs | 8 +++ .../tests/golden/abi-layout.golden | 3 +- crates/busbar-contract/tests/layout_golden.rs | 3 +- crates/busbar/tests/plane_driver.rs | 8 +++ crates/busbar/tests/plane_rider.rs | 4 ++ .../plugin-loader/src/dispatch/plane_calls.rs | 4 ++ 11 files changed, 146 insertions(+), 12 deletions(-) diff --git a/crates/busbar-contract/include/busbar_plugin.h b/crates/busbar-contract/include/busbar_plugin.h index 5951a8a97c..89b58953e2 100644 --- a/crates/busbar-contract/include/busbar_plugin.h +++ b/crates/busbar-contract/include/busbar_plugin.h @@ -374,6 +374,7 @@ extern "C" { #define BB_PLANE_ROUTE_SCOPE_SEPARATOR ", " /* What joins the reachable entries a [`ROUTE_SCOPE`] refusal's [`RefusalIn::text`] names. */ #define BB_PLANE_ROUTE_ONCE UINT8_C(1) /* [`ArriveOut::route_flags`]: the unit's operation is performed AT MOST ONCE (ARCHITECT round 4 */ #define BB_PLANE_ROUTE_SESSION UINT8_C(2) /* [`ArriveOut::route_flags`]: the unit is served as a DUPLEX SESSION (K6; ARCHITECT round 5 */ +#define BB_PLANE_ROUTE_STREAM UINT8_C(4) /* [`ArriveOut::route_flags`]: the caller asked for its answer STREAMED (ARCHITECT Q1 ArriveOut, */ #define BB_PLANE_FROM_CALLER UINT32_C(0) /* [`OnPieceIn::from`]: the piece is the caller's. */ #define BB_PLANE_FROM_FAR_END UINT32_C(1) /* [`OnPieceIn::from`]: the piece is the far end's. */ #define BB_PLANE_FROM_KERNEL UINT32_C(2) /* [`OnPieceIn::from`]: the piece is the kernel's. With [`OnPieceIn::attempt_no`] above `0` it */ @@ -2600,6 +2601,7 @@ struct bb_plane_ArriveOut { uint8_t route; uint8_t route_flags; uint8_t _route_reserved[6]; + bb_mech_AbiStr affinity; }; /* `on_piece`'s `in`. */ @@ -4837,7 +4839,7 @@ BB_ASSERT(offsetof(bb_plane_ArriveIn, body) == 136, "bb_plane_ArriveIn.body: off BB_ASSERT(offsetof(bb_plane_ArriveIn, units_buf) == 160, "bb_plane_ArriveIn.units_buf: offset"); BB_ASSERT(offsetof(bb_plane_ArriveIn, units_cap) == 168, "bb_plane_ArriveIn.units_cap: offset"); BB_ASSERT(offsetof(bb_plane_ArriveIn, method) == 176, "bb_plane_ArriveIn.method: offset"); -BB_ASSERT(sizeof(bb_plane_ArriveOut) == 168, "bb_plane_ArriveOut: size"); +BB_ASSERT(sizeof(bb_plane_ArriveOut) == 184, "bb_plane_ArriveOut: size"); BB_ASSERT(BB_ALIGNOF(bb_plane_ArriveOut) == 8, "bb_plane_ArriveOut: alignment"); BB_ASSERT(offsetof(bb_plane_ArriveOut, head) == 0, "bb_plane_ArriveOut.head: offset"); BB_ASSERT(offsetof(bb_plane_ArriveOut, op_class) == 96, "bb_plane_ArriveOut.op_class: offset"); @@ -4854,6 +4856,7 @@ BB_ASSERT(offsetof(bb_plane_ArriveOut, pool) == 144, "bb_plane_ArriveOut.pool: o BB_ASSERT(offsetof(bb_plane_ArriveOut, route) == 160, "bb_plane_ArriveOut.route: offset"); BB_ASSERT(offsetof(bb_plane_ArriveOut, route_flags) == 161, "bb_plane_ArriveOut.route_flags: offset"); BB_ASSERT(offsetof(bb_plane_ArriveOut, _route_reserved) == 162, "bb_plane_ArriveOut._route_reserved: offset"); +BB_ASSERT(offsetof(bb_plane_ArriveOut, affinity) == 168, "bb_plane_ArriveOut.affinity: offset"); BB_ASSERT(sizeof(bb_plane_OnPieceIn) == 312, "bb_plane_OnPieceIn: size"); BB_ASSERT(BB_ALIGNOF(bb_plane_OnPieceIn) == 8, "bb_plane_OnPieceIn: alignment"); BB_ASSERT(offsetof(bb_plane_OnPieceIn, head) == 0, "bb_plane_OnPieceIn.head: offset"); diff --git a/crates/busbar-contract/src/abi/plane/check.rs b/crates/busbar-contract/src/abi/plane/check.rs index 3327a25da6..84267a4fe3 100644 --- a/crates/busbar-contract/src/abi/plane/check.rs +++ b/crates/busbar-contract/src/abi/plane/check.rs @@ -32,9 +32,9 @@ use super::{ INGRESS_REQUEST_RESPONSE, INGRESS_RESPONSE_STREAM, INGRESS_SUBSCRIPTION, MAX_REFUSAL_TEXT, MECHANISM_PEER_KEY, MECHANISM_ROOT, PIECE_OUT_TEXT, PIN_FINGERPRINT, PRINCIPAL_OPTIONAL, RECORD_AUDIT, RECORD_PUT, REFUSAL_ANY_DIALECT, ROUTE_DIRECT, ROUTE_LOCAL, ROUTE_ONCE, - ROUTE_POOL, ROUTE_PUBLIC, ROUTE_SCOPE, ROUTE_SESSION, SHAPE_PIECEWISE, SHAPE_WHOLE, - TAIL_FALLBACK, TAIL_HOOKS_GATED, TAIL_PROBES, TRUST_PIN, TRUST_PRIVATE_REACH, UNITS_ESTIMATED, - VERDICT_HARD, + ROUTE_POOL, ROUTE_PUBLIC, ROUTE_SCOPE, ROUTE_SESSION, ROUTE_STREAM, SHAPE_PIECEWISE, + SHAPE_WHOLE, TAIL_FALLBACK, TAIL_HOOKS_GATED, TAIL_PROBES, TRUST_PIN, TRUST_PRIVATE_REACH, + UNITS_ESTIMATED, VERDICT_HARD, }; use crate::abi::hook::{ signal, MessageView, SignalEntry, REQUEST_HAS_MAX_TOKENS, REQUEST_HAS_TOOLS, REQUEST_STREAM, @@ -223,9 +223,13 @@ pub fn check_arrive( if ![ROUTE_POOL, ROUTE_DIRECT, ROUTE_LOCAL, ROUTE_SCOPE].contains(&out.route) { return Err(fault(Rule::UnknownCode, "arrive.route")); } - if out.route_flags & !(ROUTE_ONCE | ROUTE_SESSION) != 0 { + if out.route_flags & !(ROUTE_ONCE | ROUTE_SESSION | ROUTE_STREAM) != 0 { return Err(fault(Rule::UnknownCode, "arrive.route_flags")); } + text(out.affinity, "arrive.affinity")?; + if out.affinity.len > MAX_TEXT { + return Err(fault(Rule::OverMax, "arrive.affinity")); + } // A unit the plane answers itself names no entry (one routed by scope may name its // candidates). if out.route == ROUTE_LOCAL && (out.pool.len != 0 || !out.pool.ptr.is_null()) { @@ -252,6 +256,10 @@ pub fn check_arrive( // The pool names where an admitted unit routes: an answer that admits nothing names none. return Err(fault(Rule::Contradiction, "arrive.pool")); } + // The sticky key names how an admitted unit routes: an answer that admits nothing states none. + if outcome != Outcome::Ready && (out.affinity.len != 0 || !out.affinity.ptr.is_null()) { + return Err(fault(Rule::Contradiction, "arrive.affinity")); + } if outcome == Outcome::Refused { text(out.head.error, "arrive.refusal_text")?; if out.head.error.len as u64 > MAX_REFUSAL_TEXT { diff --git a/crates/busbar-contract/src/abi/plane/mod.rs b/crates/busbar-contract/src/abi/plane/mod.rs index 186939a911..f24aeaf074 100644 --- a/crates/busbar-contract/src/abi/plane/mod.rs +++ b/crates/busbar-contract/src/abi/plane/mod.rs @@ -427,6 +427,13 @@ pub const ROUTE_ONCE: u8 = 1; /// one unit with one line. Unset, the unit's route is one request's. pub const ROUTE_SESSION: u8 = 2; +/// [`ArriveOut::route_flags`]: the caller asked for its answer STREAMED (ARCHITECT Q1 ArriveOut, +/// 2026-10-05). The plane states it, because only the plane reads its dialect's body; the kernel +/// applies what a stream means to the walk from it: the stream ceiling bounds the whole send rather +/// than the pool's request timeout, and once a byte of the answer has reached the caller a cut is +/// not a refund (Part 2 #62). Unset, the answer is the caller's whole or not at all. +pub const ROUTE_STREAM: u8 = 4; + /// [`OnPieceIn::from`]: the piece is the caller's. pub const FROM_CALLER: u32 = 0; /// [`OnPieceIn::from`]: the piece is the far end's. @@ -1403,11 +1410,17 @@ pub struct ArriveOut { /// [`ROUTE_POOL`] on every /// other outcome. pub route: u8, - /// On READY: `ROUTE_*` flag bits ([`ROUTE_ONCE`], [`ROUTE_SESSION`]); `0` on every other - /// outcome. A tail addition, in what was padding. + /// On READY: `ROUTE_*` flag bits ([`ROUTE_ONCE`], [`ROUTE_SESSION`], [`ROUTE_STREAM`]); `0` + /// on every other outcome. A tail addition, in what was padding. pub route_flags: u8, /// Alignment padding. pub _route_reserved: [u8; 6], + /// On READY: the unit's STICKY-ROUTING KEY, opaque to the kernel (ARCHITECT Q1 ArriveOut, + /// 2026-10-05): the plane computes it from what only it reads (its dialect's body, its own + /// section's settings), and the walk prefers one stable member for every unit stating the same + /// key, with no plane or protocol knowledge. Absent (NULL, `0`) = no affinity, and absent on + /// every other outcome. Plane memory, valid until the instance's next call. A tail addition. + pub affinity: AbiStr, } /// `on_piece`'s `in`. diff --git a/crates/busbar-contract/src/abi/sdk/out.rs b/crates/busbar-contract/src/abi/sdk/out.rs index 651b686aac..bf5adf4246 100644 --- a/crates/busbar-contract/src/abi/sdk/out.rs +++ b/crates/busbar-contract/src/abi/sdk/out.rs @@ -264,6 +264,25 @@ impl Out<'_, crate::abi::plane::ArriveOut> { let flags = self.get().route_flags | crate::abi::plane::ROUTE_SESSION; self.set(|o| &o.route_flags, flags); } + + /// THE CALLER ASKED FOR ITS ANSWER STREAMED ([`crate::abi::plane::ROUTE_STREAM`]): the kernel + /// bounds the send by the stream ceiling, and a cut after the first byte is not a refund. + pub fn stream(&mut self) { + let flags = self.get().route_flags | crate::abi::plane::ROUTE_STREAM; + self.set(|o| &o.route_flags, flags); + } + + /// THE UNIT'S STICKY-ROUTING KEY ([`crate::abi::plane::ArriveOut::affinity`]), opaque to the + /// kernel and kept until the instance's next call. An empty key states none. + pub fn affinity(&mut self, key: &str) { + if key.is_empty() { + return; + } + if let Some(kept) = self.kept { + let key = kept.text(key.to_string()); + self.put(|o| &o.affinity, key); + } + } } impl Out<'_, crate::abi::plane::ServeOut> { diff --git a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs index 75e978341a..2d74eb723b 100644 --- a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs +++ b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs @@ -14,7 +14,7 @@ use crate::abi::host::conn::connector::{ }; use crate::abi::mechanism::call::Outcome; use crate::abi::mechanism::call::Outcome::{Failed, Pending, Ready, Refused}; -use crate::abi::mechanism::call::{AbiStr, Blob}; +use crate::abi::mechanism::call::{AbiStr, Blob, MAX_TEXT}; use crate::abi::mechanism::check::{check_needs, fault}; use crate::abi::mechanism::door::{Section, SECTION_DECLARING, SECTION_REQUIRED}; use crate::abi::plane::*; @@ -2736,7 +2736,14 @@ fn a_plane_naming_a_kernel_money_verdict_is_malformed_not_the_verdict() { /// contract does not define is FAULT, and an answer that admits nothing states none. #[test] fn an_arrivals_route_flags_are_once_and_session() { - for flags in [ROUTE_ONCE, ROUTE_SESSION, ROUTE_ONCE | ROUTE_SESSION] { + for flags in [ + ROUTE_ONCE, + ROUTE_SESSION, + ROUTE_STREAM, + ROUTE_ONCE | ROUTE_SESSION, + ROUTE_ONCE | ROUTE_STREAM, + ROUTE_ONCE | ROUTE_SESSION | ROUTE_STREAM, + ] { let mut o: ArriveOut = z(); o.route = ROUTE_LOCAL; o.route_flags = flags; @@ -2748,7 +2755,7 @@ fn an_arrivals_route_flags_are_once_and_session() { } let mut o: ArriveOut = z(); o.route = ROUTE_LOCAL; - o.route_flags = ROUTE_SESSION << 1; + o.route_flags = ROUTE_STREAM << 1; assert_eq!( check_arrive(Ready, &o, &[], 4, &bounds()), f(Rule::UnknownCode, "arrive.route_flags") @@ -2764,6 +2771,64 @@ fn an_arrivals_route_flags_are_once_and_session() { ); } +/// THE STICKY-ROUTING KEY (ARCHITECT Q1 ArriveOut, 2026-10-05): a READY arrival may state an +/// opaque key, bounded like every plane text; a key counted with no bytes is FAULT, and an answer +/// that admits nothing states none (RED arms). +#[test] +fn an_arrivals_affinity_is_an_admitted_units_own_bounded_key() { + let key = b"session-7"; + let mut o: ArriveOut = z(); + o.route = ROUTE_LOCAL; + assert_eq!(check_arrive(Ready, &o, &[], 4, &bounds()), Ok(()), "none"); + o.affinity = AbiStr { + ptr: key.as_ptr(), + len: key.len(), + }; + assert_eq!(check_arrive(Ready, &o, &[], 4, &bounds()), Ok(()), "a key"); + o.affinity = AbiStr { + ptr: std::ptr::null(), + len: 3, + }; + assert_eq!( + check_arrive(Ready, &o, &[], 4, &bounds()), + f(Rule::NullWithCount, "arrive.affinity") + ); + let long = vec![b'k'; MAX_TEXT + 1]; + o.affinity = AbiStr { + ptr: long.as_ptr(), + len: long.len(), + }; + assert_eq!( + check_arrive(Ready, &o, &[], 4, &bounds()), + f(Rule::OverMax, "arrive.affinity") + ); + for outcome in [Pending, Failed] { + let mut o: ArriveOut = z(); + o.units_needed = u32::from(outcome == Failed); + o.affinity = AbiStr { + ptr: key.as_ptr(), + len: key.len(), + }; + assert_eq!( + check_arrive(outcome, &o, &[], 0, &bounds()), + f(Rule::Contradiction, "arrive.affinity"), + "{outcome:?}" + ); + } + let mut o: ArriveOut = z(); + o.refusal = 3; + o.refusal_status = 404; + o.affinity = AbiStr { + ptr: key.as_ptr(), + len: key.len(), + }; + assert_eq!( + check_arrive(Refused, &o, &[], 4, &bounds()), + f(Rule::Contradiction, "arrive.affinity"), + "a refused arrival routes nowhere" + ); +} + /// THE ROUTE CLASS (ARCHITECT Q-SW6 amended by Q-FL3, 2026-10-02, Q-L3B-LOCAL and Q-DEL-A2A-SELECT): /// a READY arrival names whether its entry is a pool or a model routed directly, that the plane /// answers it itself, or that the kernel routes it by the principal's scope (both naming no entry); any other class is FAULT, and an answer that admits nothing names the default diff --git a/crates/busbar-contract/src/plane_calls.rs b/crates/busbar-contract/src/plane_calls.rs index 7810234319..d6fdd4bef1 100644 --- a/crates/busbar-contract/src/plane_calls.rs +++ b/crates/busbar-contract/src/plane_calls.rs @@ -274,6 +274,14 @@ pub trait PlaneCalls: Send + Sync { /// plane's memory while that answer is the instance's last; `None` when it named none. fn arrived_pool(&self, out: &ArriveOut) -> Option<Vec<u8>>; + /// The sticky-routing key a READY `arrive` stated ([`ArriveOut::affinity`], ARCHITECT Q1 + /// ArriveOut), copied out of the plane's memory while that answer is the instance's last; + /// `None` when it stated none. Opaque: the kernel only hashes it. + fn arrived_affinity(&self, out: &ArriveOut) -> Option<Vec<u8>> { + let _ = out; + None + } + /// The words a REFUSED `arrive` stated in its `head.error` (abi/plane "A refused arrival"), /// copied out of the plane's memory while that answer is the instance's last; `None` when it /// stated none. Opaque: the kernel hands them to the plane's `refusal` unparsed. diff --git a/crates/busbar-contract/tests/golden/abi-layout.golden b/crates/busbar-contract/tests/golden/abi-layout.golden index ea71a7f38e..7aa79011b6 100644 --- a/crates/busbar-contract/tests/golden/abi-layout.golden +++ b/crates/busbar-contract/tests/golden/abi-layout.golden @@ -2011,7 +2011,8 @@ pkind::ArriveOut.pool=144 pkind::ArriveOut.route=160 pkind::ArriveOut.route_flags=161 pkind::ArriveOut._route_reserved=162 -pkind::ArriveOut.__size=168 +pkind::ArriveOut.affinity=168 +pkind::ArriveOut.__size=184 pkind::ArriveOut.__align=8 pkind::OnPieceIn.head=0 pkind::OnPieceIn.unit=88 diff --git a/crates/busbar-contract/tests/layout_golden.rs b/crates/busbar-contract/tests/layout_golden.rs index 5472cac2e4..573de6a9a6 100644 --- a/crates/busbar-contract/tests/layout_golden.rs +++ b/crates/busbar-contract/tests/layout_golden.rs @@ -1780,7 +1780,8 @@ fn compute_layout() -> String { pool, route, route_flags, - _route_reserved + _route_reserved, + affinity ] ); record!( diff --git a/crates/busbar/tests/plane_driver.rs b/crates/busbar/tests/plane_driver.rs index 37b11929c9..9a53c196dc 100644 --- a/crates/busbar/tests/plane_driver.rs +++ b/crates/busbar/tests/plane_driver.rs @@ -209,6 +209,10 @@ fn stats(plugin: &Plugin<Plane>) -> [u64; cases::stat::COUNT] { route: 0, route_flags: 0, _route_reserved: [0; 6], + affinity: busbar_contract::abi::mechanism::call::AbiStr { + ptr: std::ptr::null(), + len: 0, + }, }, ); assert_eq!( @@ -380,6 +384,10 @@ fn zero_arrive_out() -> ArriveOut { route: 0, route_flags: 0, _route_reserved: [0; 6], + affinity: busbar_contract::abi::mechanism::call::AbiStr { + ptr: std::ptr::null(), + len: 0, + }, } } diff --git a/crates/busbar/tests/plane_rider.rs b/crates/busbar/tests/plane_rider.rs index a5e0115b92..9a8ded057d 100644 --- a/crates/busbar/tests/plane_rider.rs +++ b/crates/busbar/tests/plane_rider.rs @@ -173,6 +173,10 @@ fn arrive(plugin: &Plugin<Plane>, target: &[u8]) -> (AbiOutcome, Vec<u64>) { route: 0, route_flags: 0, _route_reserved: [0; 6], + affinity: AbiStr { + ptr: std::ptr::null(), + len: 0, + }, }, ); let outcome = plugin.call(slot::ARRIVE, &mut frame).outcome; diff --git a/crates/plugin-loader/src/dispatch/plane_calls.rs b/crates/plugin-loader/src/dispatch/plane_calls.rs index 011f96f55d..7f3d5e8199 100644 --- a/crates/plugin-loader/src/dispatch/plane_calls.rs +++ b/crates/plugin-loader/src/dispatch/plane_calls.rs @@ -90,6 +90,10 @@ impl PlaneCalls for PlaneInstance { crate::dispatch::plugin::copy_str(out.pool) } + fn arrived_affinity(&self, out: &ArriveOut) -> Option<Vec<u8>> { + crate::dispatch::plugin::copy_str(out.affinity) + } + fn arrived_refusal(&self, out: &ArriveOut) -> Option<Vec<u8>> { crate::dispatch::plugin::copy_str(out.head.error) } From eafda77521883d4abfdfba4ee22352e1a5959d5e Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:05:04 -0700 Subject: [PATCH 30/49] WIP: the door routes on what the plane states at arrive (ARCHITECT Q1 ArriveOut): ROUTE_STREAM and the sticky key reach the walk; Q31 cut and item-367 floor in the relay Kernel: Decoded carries the opaque affinity; DriverSteps::affinity told sticky_hash (FNV-1a, 1.5.5's stable_hash). Root: DoorSteps keeps stream/affinity; DoorFar's UnitRoute takes wants_stream and affinity; kernel_sections deals a pool's affinity.header_name to the plane. Plane: arrive states ROUTE_STREAM from the caller's stream intent and, for a pool route, the key 1.5.5 read (the pool's affinity header, default x-session-id, else chat's body system); Shaping reads the pools' affinity headers. Relay: a non-stream same-dialect answer states a floor checkpoint as it relays (item 367) and its end's report replaces it even at zero; a JSON body cut short bills only what the far end reported (Q31). Door tests: stream outlives the pool timeout, stream ceiling cuts, #62 stream cut keeps the budget unit (buffered cut refunds), session pinning (header, body system, pool header_name), cut non-stream bills only the report. Still red: caller drop mid-relay bills 0. --- crates/busbar-kernel/src/plane_driver/mod.rs | 27 +- .../src/exchange/reply/mod.rs | 17 +- .../src/exchange/reply/relay.rs | 24 + .../busbar-plane-llm/src/exchange/shaping.rs | 24 + crates/busbar-plane-llm/src/plane_door.rs | 53 ++- crates/busbar-plane-llm/tests/plane_door.rs | 41 ++ crates/busbar/src/root/door_steps.rs | 29 +- crates/busbar/src/root/serve.rs | 2 + crates/busbar/src/root/tests/serve_door.rs | 419 +++++++++++++++++- .../busbar/src/root/tests/serve_hook_seats.rs | 176 +++++++- 10 files changed, 791 insertions(+), 21 deletions(-) diff --git a/crates/busbar-kernel/src/plane_driver/mod.rs b/crates/busbar-kernel/src/plane_driver/mod.rs index 55880162b4..cb1525c527 100644 --- a/crates/busbar-kernel/src/plane_driver/mod.rs +++ b/crates/busbar-kernel/src/plane_driver/mod.rs @@ -512,8 +512,21 @@ pub struct Decoded { pub pool: Option<Vec<u8>>, /// What [`Decoded::pool`] names: `ROUTE_POOL` or `ROUTE_DIRECT` (ARCHITECT Q-FL3). pub route: u8, - /// The `ROUTE_*` flag bits its `arrive` stated (`ROUTE_ONCE`, `ROUTE_SESSION`). + /// The `ROUTE_*` flag bits its `arrive` stated (`ROUTE_ONCE`, `ROUTE_SESSION`, + /// `ROUTE_STREAM`). pub route_flags: u8, + /// The sticky-routing key its `arrive` stated, opaque; `None` = none. + pub affinity: Option<Vec<u8>>, +} + +/// THE WALK'S AFFINITY POSITION for a plane's opaque sticky key: FNV-1a over its bytes, the hash +/// 1.5.5 put on a session key (v1.5.5 `stable_hash`, `store::fnv1a_u64`), so one session lands on +/// the member it landed on before. +#[must_use] +pub fn sticky_hash(key: &[u8]) -> u64 { + key.iter().fold(crate::store::FNV1A_OFFSET_BASIS, |h, b| { + (h ^ u64::from(*b)).wrapping_mul(crate::store::FNV1A_PRIME) + }) } /// THE KERNEL STEPS A PLANE'S UNIT IS SERVED UNDER ([`PlaneDriver::unit`]'s `steps`): the loop's @@ -528,6 +541,11 @@ pub trait DriverSteps: Units { /// not retried on another member), told with [`Self::decoded`]. fn route_flags(&self, _ctx: &UnitCtx, _flags: u8) {} + /// The sticky-routing key the unit's `arrive` stated (ARCHITECT Q1 ArriveOut), as the walk's + /// affinity position: [`sticky_hash`] of the plane's opaque key. Told with [`Self::decoded`], + /// and only when the plane stated a key. + fn affinity(&self, _ctx: &UnitCtx, _hash: u64) {} + /// The units the plane's `arrive` expects the unit to do (its admission estimate, THE DESIGN /// §7 `admission: estimate`), told with [`Self::decoded`]. An estimate never bills. fn expected(&self, _ctx: &UnitCtx, _units: &[UnitCount]) {} @@ -744,6 +762,10 @@ impl<S, F, C> PlaneUnits<'_, S, F, C> { pool, route: o.route, route_flags: o.route_flags, + affinity: (outcome == AbiOutcome::Ready) + .then(|| self.driver.calls.arrived_affinity(&o)) + .flatten() + .filter(|k| !k.is_empty()), }) } } @@ -1116,6 +1138,9 @@ impl<S: DriverSteps + Sync, F: FarEnd, C: CallerEnd> Units for PlaneUnits<'_, S, if let (Some(op), Some(d)) = (op, decoded.as_ref()) { self.steps.decoded(ctx, op, d.route, d.pool.as_deref()); self.steps.route_flags(ctx, d.route_flags); + if let Some(key) = &d.affinity { + self.steps.affinity(ctx, sticky_hash(key)); + } self.steps.expected(ctx, &d.expected); } self.lock().decoded = decoded; diff --git a/crates/busbar-plane-llm/src/exchange/reply/mod.rs b/crates/busbar-plane-llm/src/exchange/reply/mod.rs index dbb052485e..99ad168d60 100644 --- a/crates/busbar-plane-llm/src/exchange/reply/mod.rs +++ b/crates/busbar-plane-llm/src/exchange/reply/mod.rs @@ -113,6 +113,10 @@ pub struct Units { pub cache_write: u64, /// Every open class the answer counted beside its tokens, verbatim. pub open: BTreeMap<String, u64>, + /// The token counts are a FLOOR over the bytes relayed so far, not the far end's report. + pub floor: bool, + /// The token counts are stated even at zero: they replace a floor the answer stated before. + pub stated: bool, } impl Units { @@ -130,6 +134,8 @@ impl Units { cache_read: u.cache_read.unwrap_or(0), cache_write: u.cache_creation.unwrap_or(0), open, + floor: false, + stated: false, } } } @@ -365,6 +371,11 @@ impl Reply { if !last { if let Some(u) = relay.streamed_usage() { *units = Units::of(Some(&u), BTreeMap::new()); + } else if let Some(floor) = relay.relayed_floor() { + *units = Units { + floor: true, + ..Units::of(Some(&floor), BTreeMap::new()) + }; } return Piece { verdict: if head.is_some() { @@ -387,7 +398,11 @@ impl Reply { let fault = end.stream_fault.map(Fault::Transient).or(end .generation_failed .then_some(Fault::Transient("upstream-generation-failed"))); - let units = Units::of(end.usage.as_ref(), end.open_units); + // The end's report replaces a floor stated while relaying, even when it is zero. + let units = Units { + stated: units.floor, + ..Units::of(end.usage.as_ref(), end.open_units) + }; self.state = State::Done; Piece { head, diff --git a/crates/busbar-plane-llm/src/exchange/reply/relay.rs b/crates/busbar-plane-llm/src/exchange/reply/relay.rs index 9cb68a1051..6d1559b379 100644 --- a/crates/busbar-plane-llm/src/exchange/reply/relay.rs +++ b/crates/busbar-plane-llm/src/exchange/reply/relay.rs @@ -384,6 +384,11 @@ impl Relay { open_billing = read.billing(); None } + // A JSON body that stops short of its end is one the far end CUT (its + // transfer failed before its last byte): it bills only the usage the far + // end reported in the bytes in hand, never the floor (OWNER RULING Q31, + // oracle cell `route.failover|fo|primary-cut-body`). + Err(_) if cut_short(&buf) => wire::reported_usage(self.ingress, &buf), Err(_) => { fault_unreadable(self.ingress, buf.len()); Some(wire::unrecovered_usage(self.ingress, &buf)) @@ -458,6 +463,20 @@ impl Relay { self.translate.as_ref().and_then(|t| t.usage()) } + /// THE FLOOR A NON-STREAM SAME-DIALECT ANSWER HAS INCURRED SO FAR, as it relays: it was + /// generated whole before its first byte, so a caller that leaves mid-relay bills the floor + /// over the bytes relayed, never 0 (item 367, Q33 (a), told; #62 applied to buffered relays). + /// `None` for a stream, a translated answer, or one that meters nothing. + #[must_use] + pub fn relayed_floor(&self) -> Option<TokenUsage> { + (self.translate.is_none() + && !self.far_is_stream + && self.meter + && self.handler.taps_usage() + && self.upstream_bytes > 0) + .then(|| wire::estimate_usage_from_truncated_tail(self.upstream_bytes)) + } + /// The usage an answer that ended early had incurred: what a stream's readers accumulated; /// for a same-dialect whole body, the usage the bytes in hand report, else the floor over them /// (a failed far-end transfer bills only what the far end reported, never the floor). @@ -472,6 +491,11 @@ impl Relay { } } +/// A JSON body that ends before its value does: the far end's transfer stopped short. +fn cut_short(buf: &[u8]) -> bool { + serde_json::from_slice::<serde::de::IgnoredAny>(buf).is_err_and(|e| e.is_eof()) +} + fn bytes_or_nothing<'a>(out: Vec<u8>) -> Fed<'a> { if out.is_empty() { Fed::Nothing diff --git a/crates/busbar-plane-llm/src/exchange/shaping.rs b/crates/busbar-plane-llm/src/exchange/shaping.rs index 69500e56ac..9ab04f6d47 100644 --- a/crates/busbar-plane-llm/src/exchange/shaping.rs +++ b/crates/busbar-plane-llm/src/exchange/shaping.rs @@ -136,6 +136,9 @@ pub struct Shaping { pub lanes: BTreeMap<String, Lane>, /// Every pool's members, in the pool's order, by pool name. pub pools: BTreeMap<String, Vec<Member>>, + /// The session-affinity header a pool's `affinity.header_name` names, by pool name; a pool that + /// names none reads [`DEFAULT_AFFINITY_HEADER`]. + pub affinity_headers: BTreeMap<String, String>, /// The global output-token default. pub default_max_tokens: u32, /// The effort → thinking-budget table. @@ -338,7 +341,20 @@ pub mod sections { pub const LIMITS: &str = "limits"; } +/// The header a pool's session affinity reads when its `affinity` names none (1.5.5's). +pub const DEFAULT_AFFINITY_HEADER: &str = "x-session-id"; + impl Shaping { + /// The header the session affinity of the unit routed over `pool` reads: the pool's own + /// `affinity.header_name`, else [`DEFAULT_AFFINITY_HEADER`] (v1.5.5 + /// `crates/busbar/src/ingress/mod.rs` `affinity_header_for`). + #[must_use] + pub fn affinity_header(&self, pool: &str) -> &str { + self.affinity_headers + .get(pool) + .map_or(DEFAULT_AFFINITY_HEADER, String::as_str) + } + /// READ THE TABLES from the settings object. /// /// # Errors @@ -353,6 +369,7 @@ impl Shaping { section(settings, sections::MODELS)?.unwrap_or_default(); let limits: Option<LimitsCfg> = section(settings, sections::LIMITS)?; let mut pools: BTreeMap<String, Vec<Member>> = BTreeMap::new(); + let mut affinity_headers: BTreeMap<String, String> = BTreeMap::new(); let mut context: HashMap<String, Option<usize>> = HashMap::new(); if let Some(Value::Object(sec)) = settings.get(sections::POOLS) { for (name, pool) in sec { @@ -382,6 +399,12 @@ impl Shaping { members.push(m); } pools.insert(name.clone(), members); + if let Some(h) = pool + .pointer("/affinity/header_name") + .and_then(Value::as_str) + { + affinity_headers.insert(name.clone(), h.to_string()); + } } } let mut lanes = BTreeMap::new(); @@ -443,6 +466,7 @@ impl Shaping { Ok(Shaping { lanes, pools, + affinity_headers, default_max_tokens: limits .as_ref() .and_then(|l| l.default_max_tokens) diff --git a/crates/busbar-plane-llm/src/plane_door.rs b/crates/busbar-plane-llm/src/plane_door.rs index e6919b35fc..b3ed81e6f4 100644 --- a/crates/busbar-plane-llm/src/plane_door.rs +++ b/crates/busbar-plane-llm/src/plane_door.rs @@ -65,8 +65,8 @@ use busbar_contract::abi::plane::{ CANCEL_ABORTED, CLAIM_EXACT, CLAIM_PROBE, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, FROM_FAR_END, FROM_KERNEL, INGRESS_REQUEST_RESPONSE, INGRESS_RESPONSE_STREAM, PIECE_HAS_STATUS, PIECE_LAST, PRINCIPAL_NONE, PRINCIPAL_REQUIRED, REFUSAL_GATE, ROUTE_DIRECT, ROUTE_POOL, - SHAPE_PIECEWISE, SPAN_ABSENT, TAIL_FALLBACK, TAIL_PROBES, UNITS_REPORTED, VERDICT_HARD, - VERDICT_NONE, VERDICT_OK, VERDICT_RETRY, + SHAPE_PIECEWISE, SPAN_ABSENT, TAIL_FALLBACK, TAIL_PROBES, UNITS_FLOOR, UNITS_REPORTED, + VERDICT_HARD, VERDICT_NONE, VERDICT_OK, VERDICT_RETRY, }; use busbar_contract::abi::plane::{PlaneCancelIn, PlaneCancelOut}; use busbar_contract::abi::plane::{RecordWrite, AUDIT_DEGRADED, RECORD_AUDIT}; @@ -657,10 +657,15 @@ pub fn counts(units: &Units) -> Vec<UnitCount> { units.cache_write, ]; let mut out = Vec::new(); - if tokens.iter().any(|&n| n != 0) { + let source = if units.floor { + UNITS_FLOOR + } else { + UNITS_REPORTED + }; + if units.stated || tokens.iter().any(|&n| n != 0) { out.extend(tokens.iter().zip(0u32..).map(|(&amount, class)| UnitCount { class, - source: UNITS_REPORTED, + source, amount, })); } @@ -738,6 +743,32 @@ fn fee_unit(unit: &mut UnitState, answer: &mut Answer) { } } +/// THE UNIT'S STICKY-ROUTING KEY, as 1.5.5 derived it (v1.5.5 `crates/busbar/src/ingress/mod.rs` +/// `affinity_header_for`, `crates/busbar/src/proxy/engine/mod.rs` the affinity hash): the value of +/// the pool's affinity header (`header`, matched case-blind) where the caller sent one readable as +/// text, else the operation's body key (chat's non-empty `system`); `None` = no affinity. The kernel +/// hashes it and picks the member; the plane never does. +#[must_use] +pub fn affinity_key( + header: &str, + caller: &[(&[u8], &[u8])], + handler: Option<&dyn busbar_contract::codec::OperationHandler>, + body: Option<&Value>, +) -> Option<String> { + let visible = |v: &[u8]| v.iter().all(|b| *b == b'\t' || (0x20..0x7f).contains(b)); + caller + .iter() + .find(|(n, _)| n.eq_ignore_ascii_case(header.as_bytes())) + .map(|(_, v)| *v) + .filter(|v| visible(v)) + .and_then(|v| std::str::from_utf8(v).ok()) + .map(str::to_string) + .or_else(|| { + let (h, b) = (handler?, body?); + h.body_affinity_key(b).map(str::to_string) + }) +} + /// A reply piece as the caller's answer. fn to_caller(piece: Piece<'_>) -> Answer { let fault = breaker_fault(piece.fault.as_ref()); @@ -1169,6 +1200,20 @@ slot!( out.set(|o| &o.principal_need, PRINCIPAL_REQUIRED); let (class, entry) = route_of(&unit.shaping, &arrived.model); out.route(class, entry); + let handler = handler_of(&arrived); + if handler.is_some_and(|h| stream_intent(h, arrived.parsed.as_ref()).wants_stream) { + out.stream(); + } + if class == ROUTE_POOL { + if let Some(key) = affinity_key( + unit.shaping.affinity_header(entry), + &unit.caller_fields(), + handler, + arrived.parsed.as_ref(), + ) { + out.affinity(&key); + } + } unit.arrived = Some(arrived); guard(&door.units).insert(given.unit, unit); Outcome::Ready diff --git a/crates/busbar-plane-llm/tests/plane_door.rs b/crates/busbar-plane-llm/tests/plane_door.rs index 86ef5a3cc9..0d199d41e7 100644 --- a/crates/busbar-plane-llm/tests/plane_door.rs +++ b/crates/busbar-plane-llm/tests/plane_door.rs @@ -303,3 +303,44 @@ fn an_arrival_routes_over_its_model_a_pool_first_then_a_direct_entry() { assert_eq!(route_of(&shaping, "both"), (ROUTE_POOL, "both")); assert_eq!(route_of(&shaping, "nope"), (ROUTE_DIRECT, "nope")); } + +/// THE STICKY-ROUTING KEY (ARCHITECT Q1 ArriveOut, 2026-10-05), as 1.5.5 derived it: the pool's +/// `affinity.header_name` (else `x-session-id`), matched case-blind, wins over chat's non-empty body +/// `system`; a header value that is not text is no key; neither is no affinity. +#[test] +fn the_sticky_key_is_the_pools_header_else_the_chat_bodys_system() { + use busbar_plane_llm::plane_door::affinity_key; + let shaping = read_settings( + br#"{"providers":{"ant":{"protocol":"anthropic","base_url":"https://anthropic.example"}}, + "models":{"claude":{"provider":"ant"}}, + "pools":{"p":{"members":["claude"]}, + "u":{"members":["claude"],"affinity":{"mode":"session","header_name":"x-user-id"}}}}"#, + ) + .expect("a well-formed generation reads"); + assert_eq!(shaping.affinity_header("p"), "x-session-id"); + assert_eq!(shaping.affinity_header("u"), "x-user-id"); + let chat = busbar_plane_llm::codec::DECLS + .iter() + .find(|d| d.name == "anthropic") + .and_then(|d| d.handler) + .and_then(|h| h.operation_handler(busbar_contract::operation::OpVerb::CHAT)); + let body = serde_json::json!({"model": "p", "system": "be brief", "messages": []}); + let session: &[(&[u8], &[u8])] = &[(b"X-Session-Id", b"s-1")]; + assert_eq!( + affinity_key("x-session-id", session, chat, Some(&body)), + Some("s-1".to_string()) + ); + assert_eq!( + affinity_key("x-user-id", session, chat, Some(&body)), + Some("be brief".to_string()), + "another pool's header is not this pool's" + ); + let unreadable: &[(&[u8], &[u8])] = &[(b"x-session-id", b"s\x01")]; + assert_eq!( + affinity_key("x-session-id", unreadable, chat, Some(&body)), + Some("be brief".to_string()) + ); + let plain = serde_json::json!({"model": "p", "system": "", "messages": []}); + assert_eq!(affinity_key("x-session-id", &[], chat, Some(&plain)), None); + assert_eq!(affinity_key("x-session-id", &[], None, Some(&body)), None); +} diff --git a/crates/busbar/src/root/door_steps.rs b/crates/busbar/src/root/door_steps.rs index f11e74bbd8..1cff5e4b4a 100644 --- a/crates/busbar/src/root/door_steps.rs +++ b/crates/busbar/src/root/door_steps.rs @@ -405,6 +405,10 @@ struct DoorUnit { expected: Vec<UnitCount>, /// Its operation is performed at most once (`ROUTE_ONCE`). once: bool, + /// Its caller asked for the answer streamed (`ROUTE_STREAM`). + stream: bool, + /// The walk's affinity position its `arrive`'s sticky key names. + affinity: Option<u64>, routed: Option<Routed>, /// The governance book's grant: its in-flight holds, released when the unit's steps drop. grant: Option<AdmitGrant>, @@ -533,6 +537,18 @@ impl<'s> DoorSteps<'s> { self.lock().once } + /// Whether the unit's caller asked for its answer streamed (its `arrive`'s `ROUTE_STREAM`). + #[must_use] + pub fn wants_stream(&self) -> bool { + self.lock().stream + } + + /// The walk's affinity position the unit's `arrive` named, where it stated a sticky key. + #[must_use] + pub fn affinity(&self) -> Option<u64> { + self.lock().affinity + } + /// The key of the plane the unit is of. #[must_use] pub fn plane(&self) -> &str { @@ -681,7 +697,13 @@ impl DriverSteps for DoorSteps<'_> { } fn route_flags(&self, _ctx: &UnitCtx, flags: u8) { - self.lock().once = flags & busbar_contract::abi::plane::ROUTE_ONCE != 0; + let mut u = self.lock(); + u.once = flags & busbar_contract::abi::plane::ROUTE_ONCE != 0; + u.stream = flags & busbar_contract::abi::plane::ROUTE_STREAM != 0; + } + + fn affinity(&self, _ctx: &UnitCtx, hash: u64) { + self.lock().affinity = Some(hash); } } @@ -1296,6 +1318,11 @@ pub fn kernel_sections( json!({ FALLBACK_POOL_KEY: fallback }), ); } + // The header a pool's session affinity reads (1.5.5's `affinity.header_name`): the plane + // reads the caller's key from it, and the kernel routes on that key (ARCHITECT Q1). + if let Some(header) = pool.affinity.as_ref().and_then(|a| a.header_name.as_ref()) { + p.insert("affinity".into(), json!({ "header_name": header })); + } pools.insert(name.clone(), Value::Object(p)); } let limits = json!({ diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index ad98faacd2..dcfcd04366 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -2706,6 +2706,8 @@ impl<'d> DoorFar<'d, '_> { pool, caller_credential: self.credential.clone(), once: self.steps.once(), + wants_stream: self.steps.wants_stream(), + affinity: self.steps.affinity(), ..UnitRoute::default() }); if let Some(described) = described { diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index 2442f84bb1..b2e511db5b 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -520,7 +520,9 @@ async fn a_door_claiming_one_path_over_two_carriers_mounts_it_once() { // (`super::hook_seat_tests::rig`), and reads the capability where the kernel keeps it. #[cfg(linked_fold_on_driver)] -use super::hook_seat_tests::{far_end_answering, rig, RigOpts, REWRITTEN}; +use super::hook_seat_tests::{ + chunk, far_end_answering, far_end_scripted, rig, RigOpts, Script, REWRITTEN, +}; #[cfg(linked_fold_on_driver)] use super::planes_tests::{Published as Withdrawn, PUBLISHING as ONE_PUBLISHER}; @@ -989,3 +991,418 @@ async fn the_pools_doors_hooks_see_each_candidates_standing() { "the p95 reservoir and the error rate, once fed: {second:?}" ); } + +// ── what only the plane reads, the kernel routes on (ARCHITECT Q1 ArriveOut, 2026-10-05) ──────── + +/// A streamed far end's head: chunked, so a far end that closes before its last chunk CUT it. +#[cfg(linked_fold_on_driver)] +const STREAM_HEAD: &str = "HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\n\ + transfer-encoding: chunked\r\nconnection: close\r\n\r\n"; + +/// The streamed answer's frames, in order: a delta, the stop, the usage, the end. +#[cfg(linked_fold_on_driver)] +const FRAMES: [&str; 4] = [ + r#"{"id":"c1","object":"chat.completion.chunk","created":1,"model":"m0","choices":[{"index":0,"delta":{"role":"assistant","content":"hi"},"finish_reason":null}]}"#, + r#"{"id":"c1","object":"chat.completion.chunk","created":1,"model":"m0","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}"#, + r#"{"id":"c1","object":"chat.completion.chunk","created":1,"model":"m0","choices":[],"usage":{"prompt_tokens":11,"completion_tokens":7,"total_tokens":18}}"#, + "[DONE]", +]; + +/// One event-stream frame, as a chunk. +#[cfg(linked_fold_on_driver)] +fn frame(data: &str) -> Vec<u8> { + chunk(format!("data: {data}\n\n").as_bytes()) +} + +/// A stream that pauses `pause_ms` after its first frame, then ends cleanly. +#[cfg(linked_fold_on_driver)] +fn stream_pausing(pause_ms: u64) -> Script { + Script { + head: STREAM_HEAD.to_string(), + pieces: vec![ + (0, frame(FRAMES[0])), + (pause_ms, frame(FRAMES[1])), + (0, frame(FRAMES[2])), + (0, frame(FRAMES[3])), + ], + finish: Some(b"0\r\n\r\n".to_vec()), + } +} + +/// A caller's chat on pool `p` asking for its answer streamed. +#[cfg(linked_fold_on_driver)] +fn streamed_chat() -> serde_json::Value { + serde_json::json!({"model": "p", "stream": true, "max_tokens": 16, + "messages": [{"role": "user", "content": "hi"}]}) +} + +/// THE STREAM CEILING, NOT THE POOL'S TIMEOUT, bounds a streamed answer (the plane states +/// `ROUTE_STREAM`; v1.5.5 bounded a stream's whole send by the stream ceiling): a stream that +/// outlives its pool's one-second failover timeout is delivered whole. Before the plane stated the +/// stream, the door cut it at the pool's timeout. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_bounds_a_stream_by_the_stream_ceiling_not_the_pools_timeout() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-stream-outlives-timeout"; + let _published = Withdrawn(instance); + let far = far_end_scripted(stream_pausing(2_000)).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + failover_timeout_secs: Some(1), + ..RigOpts::default() + }, + ) + .await; + let (status, _, body) = rig + .send("POST", "/v1/chat/completions", Some(streamed_chat())) + .await; + let body = String::from_utf8_lossy(&body); + assert_eq!(status, 200, "{body}"); + assert!( + body.contains("[DONE]") && body.contains("\"hi\""), + "the stream outlived the pool's timeout whole: {body}" + ); +} + +/// THE STREAM CEILING CUTS a streamed answer that outlives it, however long its pool's timeout +/// (RED before the plane stated `ROUTE_STREAM`: the door read the stream as buffered and waited the +/// pool's timeout out). +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_cuts_a_stream_at_the_stream_ceiling() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-stream-ceiling"; + let _published = Withdrawn(instance); + let far = far_end_scripted(stream_pausing(4_000)).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + failover_timeout_secs: Some(60), + stream_ceiling_secs: Some(1), + ..RigOpts::default() + }, + ) + .await; + let started = std::time::Instant::now(); + let (status, _, body) = rig + .send("POST", "/v1/chat/completions", Some(streamed_chat())) + .await; + let body = String::from_utf8_lossy(&body); + assert_eq!(status, 200, "the stream had begun: {body}"); + assert!( + body.contains("\"hi\""), + "the first frame reached the caller: {body}" + ); + assert!(!body.contains("[DONE]"), "the ceiling cut it: {body}"); + assert!( + started.elapsed() < std::time::Duration::from_millis(3_500), + "cut at the one-second ceiling, not when the far end ended: {:?}", + started.elapsed() + ); +} + +/// A MID-STREAM CUT IS NOT A REFUND (Part 2 #62): a streamed answer the far end cuts after its +/// first byte reached the caller keeps the member's lifetime budget unit it spent (RED before the +/// plane stated `ROUTE_STREAM`: the door refunded it as a buffered answer's). +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_keeps_the_budget_unit_of_a_stream_cut_after_its_first_byte() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-stream-cut"; + let _published = Withdrawn(instance); + let far = far_end_scripted(Script { + head: STREAM_HEAD.to_string(), + pieces: vec![(0, frame(FRAMES[0])), (50, frame(FRAMES[1]))], + finish: None, + }) + .await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + lane_budget: Some(5), + ..RigOpts::default() + }, + ) + .await; + let (status, _, body) = rig + .send("POST", "/v1/chat/completions", Some(streamed_chat())) + .await; + assert_eq!(status, 200, "{}", String::from_utf8_lossy(&body)); + assert_eq!( + rig.app.store.lane_budget_remaining(0), + Some(4), + "the streamed bytes were delivered: the unit is spent, not refunded" + ); +} + +/// THE CONTROL: a buffered answer the far end cuts reaches the caller whole or not at all, so its +/// budget unit is given back (v1.5.5 `engine/mod.rs:329-353`). +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_refunds_the_budget_unit_of_a_buffered_answer_cut() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-buffered-cut"; + let _published = Withdrawn(instance); + let far = far_end_scripted(Script { + head: format!( + "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\ + connection: close\r\n\r\n", + WHOLE.len() + ), + pieces: vec![(0, WHOLE.as_bytes()[..40].to_vec())], + finish: None, + }) + .await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + lane_budget: Some(5), + ..RigOpts::default() + }, + ) + .await; + let _ = rig.chat().await; + assert_eq!(rig.app.store.lane_budget_remaining(0), Some(5)); +} + +/// A whole same-dialect answer that reports 1500 + 90 tokens. +#[cfg(linked_fold_on_driver)] +const WHOLE: &str = r#"{"id":"chatcmpl-9","object":"chat.completion","created":0,"model":"m0","choices":[{"index":0,"message":{"role":"assistant","content":"hello there"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1500,"completion_tokens":90,"total_tokens":1590}}"#; + +/// A far end that answers [`WHOLE`] under its full length but writes only `prefix` of it, then +/// holds the connection `hold_ms` and closes (a cut). +#[cfg(linked_fold_on_driver)] +fn whole_cut_after(prefix: usize, hold_ms: u64) -> Script { + Script { + head: format!( + "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\ + connection: close\r\n\r\n", + WHOLE.len() + ), + pieces: vec![ + (0, WHOLE.as_bytes()[..prefix].to_vec()), + (hold_ms, Vec::new()), + ], + finish: None, + } +} + +/// OWNER RULING Q31, oracle cell `route.failover|fo|primary-cut-body` (legacy +/// `nonstream_drop_billing_tests`, rows 338/342): a same-dialect non-stream answer the far end cuts +/// AFTER its `usage` bills exactly what the far end reported; one cut BEFORE its `usage` reported +/// nothing and bills nothing, never a floor over the relayed bytes. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_bills_a_cut_non_stream_answer_only_what_its_far_end_reported() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-nonstream-cut"; + let _published = Withdrawn(instance); + let far = far_end_scripted(whole_cut_after(WHOLE.len() - 1, 0)).await; + let late = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + ..RigOpts::default() + }, + ) + .await; + let _ = late.chat().await; + assert_eq!(late.tokens_after().await, 1590, "the far end's own report"); + drop(late); + drop(_published); + + let instance = "serve-door-nonstream-cut-early"; + let _published = Withdrawn(instance); + let usage_at = WHOLE.find(r#","usage""#).expect("the usage member"); + let far = far_end_scripted(whole_cut_after(usage_at, 0)).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + ..RigOpts::default() + }, + ) + .await; + let _ = rig.chat().await; + assert_eq!(rig.tokens_after().await, 0, "no report, no charge"); +} + +/// ITEM 367 (Q33 (a), told), legacy row 338: a same-dialect non-stream answer the CALLER drops +/// mid-relay was generated whole before its first byte, so it bills what it relayed: never 0. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_bills_a_non_stream_answer_the_caller_dropped_mid_relay() { + use http_body_util::BodyExt as _; + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-nonstream-drop"; + let _published = Withdrawn(instance); + let usage_at = WHOLE.find(r#","usage""#).expect("the usage member"); + let far = far_end_scripted(whole_cut_after(usage_at, 5_000)).await; + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + ..RigOpts::default() + }, + ) + .await; + let response = rig + .open( + "POST", + "/v1/chat/completions", + Some(serde_json::json!({"model": "p", "max_tokens": 16, + "messages": [{"role": "user", "content": "hi"}]})), + &[], + ) + .await; + assert_eq!(response.status().as_u16(), 200); + let mut body = response.into_body(); + let first = tokio::time::timeout(std::time::Duration::from_secs(5), body.frame()) + .await + .expect("the first bytes arrive before the far end's hold") + .expect("a frame") + .expect("relayed"); + assert!(first.data_ref().is_some_and(|b| !b.is_empty())); + // THE DISCONNECT: the caller goes before the answer's end. + drop(body); + assert_ne!( + rig.tokens_after().await, + 0, + "a non-stream answer dropped mid-relay never bills 0" + ); +} + +/// SESSION AFFINITY (legacy rows 2-4; v1.5.5 `affinity_header_for` and the sticky position): the +/// plane states the session key it reads (the pool's header, `x-session-id` by default, else chat's +/// body `system`) and the kernel pins every unit with that key to one member; a unit without one +/// is spread by the weighted floor. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_pins_a_session_to_one_member() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-affinity"; + let _published = Withdrawn(instance); + let fars = [ + far_end_answering(200, SERVED_BY_TWIN).await, + far_end_answering(200, SERVED_BY_TWIN).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ]; + let rig = rig( + instance, + RigOpts { + members: &[(fars[0].port, 1), (fars[1].port, 1), (fars[2].port, 1)], + ..RigOpts::default() + }, + ) + .await; + let served = || fars.iter().map(|f| f.served()).collect::<Vec<_>>(); + let chat = serde_json::json!({"model": "p", "max_tokens": 16, + "messages": [{"role": "user", "content": "hi"}]}); + for _ in 0..6 { + let r = rig + .open( + "POST", + "/v1/chat/completions", + Some(chat.clone()), + &[("x-session-id", "session-42")], + ) + .await; + assert_eq!(r.status().as_u16(), 200); + let _ = axum::body::to_bytes(r.into_body(), 1 << 20).await; + } + let pinned = served(); + assert_eq!( + pinned.iter().filter(|n| **n == 6).count(), + 1, + "one member served the whole session: {pinned:?}" + ); + // The body's `system` is the key when no header names one. + let system = serde_json::json!({"model": "p", "max_tokens": 16, "system": "be brief", + "messages": [{"role": "user", "content": "hi"}]}); + let before = served(); + for _ in 0..3 { + assert_eq!( + rig.send("POST", "/v1/chat/completions", Some(system.clone())) + .await + .0, + 200 + ); + } + let after = served(); + let moved: Vec<usize> = after.iter().zip(&before).map(|(a, b)| a - b).collect(); + assert_eq!( + moved.iter().filter(|n| **n == 3).count(), + 1, + "one member served the system-keyed session: {moved:?}" + ); + // No key: the weighted floor spreads the units. + let before = served(); + for _ in 0..3 { + assert_eq!(rig.chat().await.0, 200); + } + let after = served(); + let moved = after.iter().zip(&before).filter(|(a, b)| a > b).count(); + assert!(moved >= 2, "unkeyed units spread: {before:?} -> {after:?}"); +} + +/// A POOL'S OWN AFFINITY HEADER (`affinity.header_name`) is the one its sessions are read from; the +/// default header is then no key. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn the_pools_door_reads_a_session_from_the_pools_own_affinity_header() { + let _one = ONE_PUBLISHER.lock().await; + let instance = "serve-door-affinity-header"; + let _published = Withdrawn(instance); + let fars = [ + far_end_answering(200, SERVED_BY_TWIN).await, + far_end_answering(200, SERVED_BY_TWIN).await, + far_end_answering(200, SERVED_BY_TWIN).await, + ]; + let rig = rig( + instance, + RigOpts { + members: &[(fars[0].port, 1), (fars[1].port, 1), (fars[2].port, 1)], + affinity_header: Some("x-user-id"), + ..RigOpts::default() + }, + ) + .await; + let served = || fars.iter().map(|f| f.served()).collect::<Vec<_>>(); + let chat = serde_json::json!({"model": "p", "max_tokens": 16, + "messages": [{"role": "user", "content": "hi"}]}); + let send = |name: &'static str, value: &'static str| { + let (rig, chat) = (&rig, chat.clone()); + async move { + let r = rig + .open("POST", "/v1/chat/completions", Some(chat), &[(name, value)]) + .await; + assert_eq!(r.status().as_u16(), 200); + let _ = axum::body::to_bytes(r.into_body(), 1 << 20).await; + } + }; + for _ in 0..4 { + send("x-user-id", "user-7").await; + } + assert_eq!( + served().iter().filter(|n| **n == 4).count(), + 1, + "the pool's header pins: {:?}", + served() + ); + let before = served(); + for _ in 0..3 { + send("x-session-id", "session-42").await; + } + let after = served(); + let moved = after.iter().zip(&before).filter(|(a, b)| a > b).count(); + assert!( + moved >= 2, + "the default header is not this pool's key: {before:?} -> {after:?}" + ); +} diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs index e68ba9d171..937e2fac3b 100644 --- a/crates/busbar/src/root/tests/serve_hook_seats.rs +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -243,6 +243,98 @@ impl FarEnd { } } +/// What a scripted far end writes for each request it reads: its raw head, then each piece after +/// its delay, then (when `finish` is set) the closing bytes; it then closes the connection, so a +/// script whose head promises more than it writes is a far end that CUT its answer. +#[derive(Clone)] +pub(super) struct Script { + /// The status line and head fields, through the blank line. + pub head: String, + /// Each piece of the body, after its delay in milliseconds. + pub pieces: Vec<(u64, Vec<u8>)>, + /// The bytes that complete the body (a chunked body's last chunk); `None` cuts it. + pub finish: Option<Vec<u8>>, +} + +/// One chunk of a chunked body. +pub(super) fn chunk(bytes: &[u8]) -> Vec<u8> { + let mut out = format!("{:x}\r\n", bytes.len()).into_bytes(); + out.extend_from_slice(bytes); + out.extend_from_slice(b"\r\n"); + out +} + +/// A far end on loopback answering every request by `script`. +pub(super) async fn far_end_scripted(script: Script) -> FarEnd { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("a loopback port"); + let port = listener.local_addr().expect("its address").port(); + let hits = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let authorizations = Arc::new(Mutex::new(Vec::new())); + let (served, seen) = (Arc::clone(&hits), Arc::clone(&authorizations)); + tokio::spawn(async move { + while let Ok((mut socket, _)) = listener.accept().await { + let (served, seen, script) = (Arc::clone(&served), Arc::clone(&seen), script.clone()); + tokio::spawn(async move { + let Some(head) = read_request(&mut socket).await else { + return; + }; + served.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + seen.lock().unwrap().push(head); + if socket.write_all(script.head.as_bytes()).await.is_err() { + return; + } + for (delay, piece) in &script.pieces { + tokio::time::sleep(std::time::Duration::from_millis(*delay)).await; + if socket.write_all(piece).await.is_err() { + return; + } + let _ = socket.flush().await; + } + if let Some(finish) = &script.finish { + let _ = socket.write_all(finish).await; + } + let _ = socket.shutdown().await; + }); + } + }); + FarEnd { + port, + hits, + authorizations, + } +} + +/// Read one request off `socket` through its body; its `authorization` field (empty when none), +/// or `None` when the connection closed first. +async fn read_request(socket: &mut tokio::net::TcpStream) -> Option<String> { + let mut buf = vec![0u8; 16 * 1024]; + let mut got = Vec::new(); + loop { + let n = socket.read(&mut buf).await.ok()?; + if n == 0 { + return None; + } + got.extend_from_slice(&buf[..n]); + let text = String::from_utf8_lossy(&got).to_string(); + if let Some(at) = text.find("\r\n\r\n") { + let field = |name: &str| { + text[..at].lines().find_map(|l| { + let (n, v) = l.split_once(':')?; + n.eq_ignore_ascii_case(name).then(|| v.trim().to_string()) + }) + }; + let length = field("content-length") + .and_then(|v| v.parse::<usize>().ok()) + .unwrap_or(0); + if got.len() >= at + 4 + length { + return Some(field("authorization").unwrap_or_default()); + } + } + } +} + /// A far end on loopback answering every request with `status` and `body` (JSON). pub(super) async fn far_end_answering(status: u16, body: &'static str) -> FarEnd { let listener = tokio::net::TcpListener::bind("127.0.0.1:0") @@ -340,6 +432,14 @@ pub(super) struct RigOpts<'a> { /// Each pool member states a tier `t<i>` and a tag `g<i>`, and the generation declares the /// three candidate catalog signals (breaker state, error rate, p95 latency). pub described: bool, + /// Pool `p`'s `failover.timeout_secs` (`None`: the default). + pub failover_timeout_secs: Option<u64>, + /// The node's stream ceiling, in seconds (`None`: 600). + pub stream_ceiling_secs: Option<u64>, + /// Pool `p`'s `affinity.header_name` (`None`: the pool states no affinity block). + pub affinity_header: Option<&'a str>, + /// Each member's lifetime request budget (`None`: unlimited). + pub lane_budget: Option<i64>, } /// THE LLM DOOR, SERVED END TO END, as production composes it: the data router built with the @@ -360,6 +460,7 @@ pub(super) struct DoorRig { candidate_tap: Arc<SeatProbe>, gate_seat: Arc<SeatProbe>, read: Arc<dyn Fn() -> Ledger + Send + Sync>, + tokens: Arc<dyn Fn() -> u64 + Send + Sync>, } impl DoorRig { @@ -375,11 +476,33 @@ impl DoorRig { path: &str, body: Option<serde_json::Value>, ) -> (u16, axum::http::HeaderMap, Vec<u8>) { + let response = self.open(method, path, body, &[]).await; + let status = response.status().as_u16(); + let head = response.headers().clone(); + let bytes = axum::body::to_bytes(response.into_body(), 1 << 20) + .await + .expect("the body") + .to_vec(); + (status, head, bytes) + } + + /// One request through the door as the keyed caller, with `fields` beside its own: the + /// response, its body unread. + pub async fn open( + &self, + method: &str, + path: &str, + body: Option<serde_json::Value>, + fields: &[(&str, &str)], + ) -> axum::response::Response { use tower::ServiceExt as _; let mut req = axum::http::Request::builder() .method(method) .uri(path) .header("authorization", format!("Bearer {}", self.token)); + for (name, value) in fields { + req = req.header(*name, *value); + } if body.is_some() { req = req.header("content-type", "application/json"); } @@ -389,19 +512,25 @@ impl DoorRig { None => axum::body::Body::empty(), }) .expect("a request"); - let response = self - .router + self.router .clone() .oneshot(req) .await - .expect("the router answers"); - let status = response.status().as_u16(); - let head = response.headers().clone(); - let bytes = axum::body::to_bytes(response.into_body(), 1 << 20) - .await - .expect("the body") - .to_vec(); - (status, head, bytes) + .expect("the router answers") + } + + /// The tokens on the key's ledger once they are nonzero or the wait is spent (the accrual is + /// write-behind). + pub async fn tokens_after(&self) -> u64 { + let mut tokens = (self.tokens)(); + for _ in 0..100 { + if tokens != 0 { + break; + } + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + tokens = (self.tokens)(); + } + tokens } /// One chat completion on pool `p`. @@ -505,6 +634,14 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { yaml.push_str(&format!(" tier: t{i}\n tags: [g{i}]\n")); } } + if let Some(secs) = opts.failover_timeout_secs { + yaml.push_str(&format!(" failover:\n timeout_secs: {secs}\n")); + } + if let Some(header) = opts.affinity_header { + yaml.push_str(&format!( + " affinity:\n mode: session\n header_name: {header}\n" + )); + } let deploy = busbar_kernel::config::deploy_from_yaml_str(&yaml).expect("a deployment"); let mut defs_yaml = String::new(); for (i, (port, _)) in opts.members.iter().enumerate() { @@ -591,14 +728,18 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { .governance(Arc::clone(&gov)) .cost(cost); for (i, (port, _)) in opts.members.iter().enumerate() { - builder = builder.lane(busbar_kernel::test_support::LaneSpec::new( + let lane = busbar_kernel::test_support::LaneSpec::new( &format!("m{i}"), match opts.dialect { Some("anthropic") => "anthropic", _ => "openai", }, &format!("http://127.0.0.1:{port}"), - )); + ); + builder = builder.lane(match opts.lane_budget { + Some(n) => lane.budget(n), + None => lane, + }); } let weights: Vec<(usize, u32)> = opts .members @@ -627,6 +768,14 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { ) }) }; + let tokens: Arc<dyn Fn() -> u64 + Send + Sync> = { + let (gov, cost, key_id) = (Arc::clone(&gov), app.cost.clone(), key.id.clone()); + Arc::new(move || { + gov.usage_for(cost.as_ref(), &key_id, busbar_kernel::store::now()) + .expect("usage read") + .map_or(0, |u| u.tokens) + }) + }; let mut rewrite = SeatProbe::new("rewrite", &log); let read_for_rewrite = Arc::clone(&read); rewrite.requests_now = Some(Box::new(move || read_for_rewrite().0)); @@ -686,7 +835,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { secrets: &secrets, auths, conns: Arc::clone(&connector) as Arc<dyn PollConns>, - stream_ceiling_secs: 600, + stream_ceiling_secs: opts.stream_ceiling_secs.unwrap_or(600), models: Some(&models), upgrades: Vec::new(), }; @@ -734,6 +883,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { candidate_tap, gate_seat: ranker, read, + tokens, } } From 22ae1ceca2d7ed24b5b463a41ceca7c749f8d742 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:22:32 -0700 Subject: [PATCH 31/49] llm plane: a caller that leaves a usable partial answer cancels OK_PARTIAL (1.5.5's drop arm, item 367): door caller-drop test green The plane's cancel answered ABORTED for every unit, so the kernel billed nothing on a caller drop. It now answers CANCEL_OK_PARTIAL when a byte of a success answer was relayed and nothing failed it (no reader terminal error, no translate abort), and the floor or the streamed usage the answer stated so far bills. Door suite 24/24 green; RED arms proven by neutralising the plane's ROUTE_STREAM, affinity, cut and partial statements: all 7 new door cells fail (the buffered-cut control passes both ways). --- .../src/exchange/reply/mod.rs | 7 ++++++ .../src/exchange/reply/relay.rs | 12 ++++++++++ crates/busbar-plane-llm/src/plane_door.rs | 23 +++++++++++++------ crates/busbar/src/root/tests/serve_door.rs | 7 ++++++ 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/crates/busbar-plane-llm/src/exchange/reply/mod.rs b/crates/busbar-plane-llm/src/exchange/reply/mod.rs index 99ad168d60..931c23177e 100644 --- a/crates/busbar-plane-llm/src/exchange/reply/mod.rs +++ b/crates/busbar-plane-llm/src/exchange/reply/mod.rs @@ -421,6 +421,13 @@ impl Reply { } } + /// The caller had a usable part of a success answer when it left (see [`relay::Relay::partial`]): + /// its cancel bills the units the answer stated so far. + #[must_use] + pub fn partial(&self) -> bool { + matches!(&self.state, State::Relay { relay, .. } if relay.partial()) + } + /// CUT the answer: the far end's transfer failed (`transport`) or the kernel's ceiling for it /// expired, before its last piece. pub fn cut(&mut self, ctx: &ReplyCtx<'_>, transport: bool) -> Piece<'static> { diff --git a/crates/busbar-plane-llm/src/exchange/reply/relay.rs b/crates/busbar-plane-llm/src/exchange/reply/relay.rs index 6d1559b379..ee4ba1ad5e 100644 --- a/crates/busbar-plane-llm/src/exchange/reply/relay.rs +++ b/crates/busbar-plane-llm/src/exchange/reply/relay.rs @@ -456,6 +456,18 @@ impl Relay { } } + /// THE CALLER HAD A USABLE PART: a byte of the answer was relayed and nothing failed it (no + /// reader's terminal error, no translate abort), so a caller that leaves now leaves a partial + /// answer whose incurred units bill (v1.5.5 `FirstByteBody`'s drop arm). + #[must_use] + pub fn partial(&self) -> bool { + self.first_byte + && !self + .translate + .as_ref() + .is_some_and(|t| t.aborted() || t.terminal_error().is_some()) + } + /// The usage a stream's readers have read so far (cheap: nothing is scanned); `None` for a /// relay that reads its usage only at the end. #[must_use] diff --git a/crates/busbar-plane-llm/src/plane_door.rs b/crates/busbar-plane-llm/src/plane_door.rs index b3ed81e6f4..2d85c51456 100644 --- a/crates/busbar-plane-llm/src/plane_door.rs +++ b/crates/busbar-plane-llm/src/plane_door.rs @@ -62,11 +62,11 @@ 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_ABORTED, CLAIM_EXACT, CLAIM_PROBE, EMIT_DONE, EMIT_TO_FAR_END, FROM_CALLER, - FROM_FAR_END, FROM_KERNEL, INGRESS_REQUEST_RESPONSE, INGRESS_RESPONSE_STREAM, PIECE_HAS_STATUS, - PIECE_LAST, PRINCIPAL_NONE, PRINCIPAL_REQUIRED, REFUSAL_GATE, ROUTE_DIRECT, ROUTE_POOL, - SHAPE_PIECEWISE, SPAN_ABSENT, TAIL_FALLBACK, TAIL_PROBES, UNITS_FLOOR, UNITS_REPORTED, - VERDICT_HARD, VERDICT_NONE, VERDICT_OK, VERDICT_RETRY, + CANCEL_ABORTED, CANCEL_OK_PARTIAL, CLAIM_EXACT, CLAIM_PROBE, EMIT_DONE, EMIT_TO_FAR_END, + FROM_CALLER, FROM_FAR_END, FROM_KERNEL, INGRESS_REQUEST_RESPONSE, INGRESS_RESPONSE_STREAM, + PIECE_HAS_STATUS, PIECE_LAST, PRINCIPAL_NONE, PRINCIPAL_REQUIRED, REFUSAL_GATE, ROUTE_DIRECT, + ROUTE_POOL, SHAPE_PIECEWISE, SPAN_ABSENT, TAIL_FALLBACK, TAIL_PROBES, UNITS_FLOOR, + UNITS_REPORTED, VERDICT_HARD, VERDICT_NONE, VERDICT_OK, VERDICT_RETRY, }; use busbar_contract::abi::plane::{PlaneCancelIn, PlaneCancelOut}; use busbar_contract::abi::plane::{RecordWrite, AUDIT_DEGRADED, RECORD_AUDIT}; @@ -1138,12 +1138,21 @@ slot!( slot!( /// `cancel`: the unit on the cancelled ticket ends; nothing it owed is delivered. Cancel, PlaneCancelIn, PlaneCancelOut, |instance, input, mut out| { + let mut partial = false; if let Some(door) = instance.get() { if let Some(unit) = guard(&door.tickets).remove(&input.cancel.ticket) { - guard(&door.units).remove(&unit); + partial = guard(&door.units) + .remove(&unit) + .is_some_and(|u| u.reply.as_ref().is_some_and(Reply::partial)); } } - out.set(|o| &o.cancel.disposition, CANCEL_ABORTED); + // A caller that left a usable part of the answer leaves a PARTIAL unit: the units the + // answer stated so far bill (1.5.5's drop arm; item 367 for a buffered relay). Any other + // cancel bills nothing. + out.set( + |o| &o.cancel.disposition, + if partial { CANCEL_OK_PARTIAL } else { CANCEL_ABORTED }, + ); Outcome::Ready } ); diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index b2e511db5b..775d92e2b9 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -1271,11 +1271,18 @@ async fn the_pools_door_bills_a_non_stream_answer_the_caller_dropped_mid_relay() assert!(first.data_ref().is_some_and(|b| !b.is_empty())); // THE DISCONNECT: the caller goes before the answer's end. drop(body); + // The driver finishes a buried unit at the start of its next one. + let rig = std::sync::Arc::new(rig); + let next = { + let rig = std::sync::Arc::clone(&rig); + tokio::spawn(async move { rig.chat().await }) + }; assert_ne!( rig.tokens_after().await, 0, "a non-stream answer dropped mid-relay never bills 0" ); + next.abort(); } /// SESSION AFFINITY (legacy rows 2-4; v1.5.5 `affinity_header_for` and the sticky position): the From 856c979a7394b80a9fb2b228634808be6989053f Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 09:02:02 -0700 Subject: [PATCH 32/49] door: the store door's tuple index after predev's LinkedStore reshape; the head latency sample in fractional milliseconds, as 1.5.5 measured it (a sub-millisecond head records, not the EWMA's none: the ranking-signals cell's flake) --- crates/busbar-kernel-egress/src/ports.rs | 8 ++++++++ crates/busbar-kernel/src/plane_driver/far_end.rs | 12 ++++++++++-- crates/busbar/src/root/egress_ports.rs | 4 ++++ crates/busbar/src/root/tests/serve.rs | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/busbar-kernel-egress/src/ports.rs b/crates/busbar-kernel-egress/src/ports.rs index b3c44711ec..97b68294cc 100644 --- a/crates/busbar-kernel-egress/src/ports.rs +++ b/crates/busbar-kernel-egress/src/ports.rs @@ -383,6 +383,14 @@ pub trait Clock: Send + Sync { /// seconds, and a budget close to a second boundary would collapse to zero. fn now_millis(&self) -> u128; + /// Microseconds since the same fixed point, for a latency sample: 1.5.5 measured an + /// attempt's latency in fractional milliseconds, so a sub-millisecond answer still records + /// (an integer `0` is the EWMA's "no sample"). A clock without the precision answers its + /// milliseconds scaled. + fn now_micros(&self) -> u128 { + self.now_millis().saturating_mul(1000) + } + /// A future that completes no earlier than `ms` milliseconds from now. fn sleep(&self, ms: u64) -> BoxFut<'_, ()>; } diff --git a/crates/busbar-kernel/src/plane_driver/far_end.rs b/crates/busbar-kernel/src/plane_driver/far_end.rs index a04c299c0f..64daf20d59 100644 --- a/crates/busbar-kernel/src/plane_driver/far_end.rs +++ b/crates/busbar-kernel/src/plane_driver/far_end.rs @@ -375,6 +375,8 @@ struct Live { passthrough: bool, /// When the send started, ms. anchor_ms: u128, + /// The same anchor, in microseconds: the head's latency sample is taken from it. + anchor_us: u128, /// The far end answered. answered: bool, /// One unit of lifetime budget was spent on the success. @@ -734,6 +736,7 @@ impl EgressFarEnd<'_> { degraded, passthrough, anchor_ms: 0, + anchor_us: 0, answered: false, spent: false, delivered: false, @@ -1051,6 +1054,7 @@ impl EgressFarEnd<'_> { }, ); let now_ms = e.clock.now_millis(); + let now_us = e.clock.now_micros(); match opened { Ok(conn) => { { @@ -1059,6 +1063,7 @@ impl EgressFarEnd<'_> { live.keep = keep; live.conn = Some(conn); live.anchor_ms = now_ms; + live.anchor_us = now_us; } } if request.text && !request.body.is_empty() { @@ -1338,9 +1343,12 @@ impl EgressFarEnd<'_> { live.probe = None; live.spent = e.breaker.spend_budget(destination); live.delivered = self.route.wants_stream && !bytes.is_empty(); - let head_ms = e.clock.now_millis().saturating_sub(live.anchor_ms); + // In fractional milliseconds, as 1.5.5 measured it (`elapsed().as_secs_f64() * 1000`): + // a sub-millisecond head is a sample, not the EWMA's "none". + let head_us = e.clock.now_micros().saturating_sub(live.anchor_us); #[allow(clippy::cast_precision_loss)] - e.telemetry.upstream_latency(destination, head_ms as f64); + e.telemetry + .upstream_latency(destination, head_us as f64 / 1000.0); return FarPiece { bytes, status: far_status, diff --git a/crates/busbar/src/root/egress_ports.rs b/crates/busbar/src/root/egress_ports.rs index 4091bc2a68..efe86ffbe4 100644 --- a/crates/busbar/src/root/egress_ports.rs +++ b/crates/busbar/src/root/egress_ports.rs @@ -58,6 +58,10 @@ impl Clock for NodeClock { self.origin.elapsed().as_millis() } + fn now_micros(&self) -> u128 { + self.origin.elapsed().as_micros() + } + fn sleep(&self, ms: u64) -> BoxFut<'_, ()> { Box::pin(tokio::time::sleep(std::time::Duration::from_millis(ms))) } diff --git a/crates/busbar/src/root/tests/serve.rs b/crates/busbar/src/root/tests/serve.rs index aa2aef1364..311253df6f 100644 --- a/crates/busbar/src/root/tests/serve.rs +++ b/crates/busbar/src/root/tests/serve.rs @@ -515,7 +515,7 @@ async fn the_late_attach_binds_the_governance_store_as_the_record_store() { .stores .first() .expect("the build links a store") - .3, + .2, ), "records-attach", b"{}", From b639fc112b5f32154e62a989fa5faf5db5320866 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:55:51 -0700 Subject: [PATCH 33/49] door: seal_live takes the plane's breaker fact as its eighth argument (clippy too_many_arguments, as compose_planes) --- crates/busbar/src/root/serve.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/busbar/src/root/serve.rs b/crates/busbar/src/root/serve.rs index 8f94b47c59..71523104e3 100644 --- a/crates/busbar/src/root/serve.rs +++ b/crates/busbar/src/root/serve.rs @@ -1174,6 +1174,7 @@ pub(crate) fn compose_planes_over( /// # Errors /// /// A member whose route or credential binding cannot be resolved, named. +#[allow(clippy::too_many_arguments)] fn seal_live( instance: &str, plugin: &DoorPlane, From 8b59f76e9049bf38f1be1644f0a1f081e058cdc8 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:41:21 -0700 Subject: [PATCH 34/49] kernel: a session's hook stage carries the dialect its unit arrived in as a required fact, never a default: state_stage reads it from the decode (a unit reaches its route leg only through the decode step, which refuses an undecoded unit), and SessionStage::new takes it; a session on a later dialect binds its hooks there (test) --- .../busbar-kernel/src/plane_driver/hooks.rs | 32 +++++------ .../src/tests/host_stage_tests.rs | 54 +++++++++++++++++++ 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/crates/busbar-kernel/src/plane_driver/hooks.rs b/crates/busbar-kernel/src/plane_driver/hooks.rs index ecb97df3e3..7f70ec54ef 100644 --- a/crates/busbar-kernel/src/plane_driver/hooks.rs +++ b/crates/busbar-kernel/src/plane_driver/hooks.rs @@ -1290,15 +1290,22 @@ impl<S, F: FarEnd, C> PlaneUnits<'_, S, F, C> { return; }; let d = self.driver; - let (unit, principal, dialect) = { + // A unit reaches its route leg only through the decode step, which refuses every unit + // its plane did not decode (`DecodeFailed`), so its dialect is always known here; a unit + // with none would state no stage, and the in-session services refuse a unit they do not + // hold by name, never binding it under some other dialect. + let (unit, principal, arrived_in) = { let st = self.lock(); + let Some(arrived_in) = st.decoded.as_ref().map(|x| x.dialect) else { + return; + }; ( st.unit, st.principal.as_ref().map(|p| p.as_str().to_string()), - st.decoded.as_ref().map_or(0, |x| x.dialect), + arrived_in, ) }; - let arrived_in = dialect; + let dialect = arrived_in; let (pool, dialect) = match &d.hooks { Some(binder) => ( self.far @@ -1314,8 +1321,8 @@ impl<S, F: FarEnd, C> PlaneUnits<'_, S, F, C> { runtime, d.hooks.clone(), (pool, principal, dialect), - ) - .arrived_in(arrived_in); + arrived_in, + ); let _held = d.services.units().staged(unit, Arc::new(stage)); } @@ -1886,13 +1893,16 @@ impl Drop for OwedStage { impl SessionStage { /// The stage of a unit of instance `instance`, routed over `pool` for `principal`, its hooks - /// bound by `binder` (none = no hook binds), run on `runtime`. + /// bound by `binder` (none = no hook binds), run on `runtime`; the unit arrived in dialect + /// `arrived_in` (the index into the plane's dialects; its name is `dialect`), and its hooks bind + /// there. There is no default dialect: a stage is stated only for a unit its plane decoded. #[must_use] pub fn new( instance: Arc<str>, runtime: tokio::runtime::Handle, binder: Option<Arc<dyn HookBinder>>, (pool, principal, dialect): (String, Option<String>, String), + arrived_in: u32, ) -> Self { Self { instance, @@ -1901,19 +1911,11 @@ impl SessionStage { pool, principal, dialect, - arrived_in: 0, + arrived_in, bound: std::sync::OnceLock::new(), } } - /// The same stage, its unit having arrived in dialect `at` (the index into the plane's - /// dialects; `new` binds under the first). - #[must_use] - pub fn arrived_in(mut self, at: u32) -> Self { - self.arrived_in = at; - self - } - /// The unit's hooks, bound once. fn hooks(&self) -> Option<&UnitHooks> { self.bound diff --git a/crates/busbar-kernel/src/tests/host_stage_tests.rs b/crates/busbar-kernel/src/tests/host_stage_tests.rs index b9f7e19b22..eb4bd051c6 100644 --- a/crates/busbar-kernel/src/tests/host_stage_tests.rs +++ b/crates/busbar-kernel/src/tests/host_stage_tests.rs @@ -275,6 +275,7 @@ fn staged(s: &KernelServices, unit: u64, instance: &str, pool: &str, binder: Arc tokio::runtime::Handle::current(), Some(binder), (pool.to_string(), None, "d0".to_string()), + 0, ); assert!(s.units().staged(unit, Arc::new(stage))); } @@ -544,6 +545,7 @@ async fn the_stage_refuses_every_unit_it_does_not_hold() { tokio::runtime::Handle::current(), None, (String::new(), None, String::new()), + 0, ); assert!(!s.units().staged(3, Arc::new(late))); } @@ -566,6 +568,7 @@ async fn a_unit_no_hook_binds_passes_unchanged() { tokio::runtime::Handle::current(), None, (String::new(), None, String::new()), + 0, )) )); assert_eq!( @@ -582,3 +585,54 @@ async fn a_unit_no_hook_binds_passes_unchanged() { ); assert_eq!(scan(&s, Some(1), b"secret").await.value, svc::CONTENT_PASS); } + +/// A binder that records the dialect each unit it binds arrived in. +struct DialectSeen { + hooks: BoundHooks, + seen: std::sync::Mutex<Vec<u32>>, +} + +impl HookBinder for DialectSeen { + fn bind(&self, bind: &Bind<'_>) -> Option<UnitHooks> { + self.seen.lock().unwrap().push(bind.dialect); + self.hooks.bind(bind) + } +} + +/// A session that arrived in a dialect other than the plane's first binds its hooks THERE: the +/// stage carries the dialect its unit arrived in, never a default. +#[tokio::test] +async fn a_session_on_a_later_dialect_binds_its_hooks_in_that_dialect() { + let s = services(&Arc::new(AtomicU64::new(0))); + let mut bound = hooks(vec![], vec![], None); + bound.dialects = vec!["d0".into(), "d1".into(), "d2".into()]; + let binder = Arc::new(DialectSeen { + hooks: bound, + seen: std::sync::Mutex::new(Vec::new()), + }); + s.units().admitted( + 4, + UnitRecord { + principal: None, + depth: 0, + }, + ); + let stage = SessionStage::new( + Arc::from("inst"), + tokio::runtime::Handle::current(), + Some(Arc::clone(&binder) as Arc<dyn HookBinder>), + ("pool-z".to_string(), None, "d2".to_string()), + 2, + ); + assert!(s.units().staged(4, Arc::new(stage))); + let pass = scan(&s, Some(4), b"a tool result").await; + assert_eq!( + (pass.outcome, pass.value), + (Outcome::Ready, svc::CONTENT_PASS) + ); + assert_eq!( + *binder.seen.lock().unwrap(), + vec![2], + "bound in the dialect it arrived in" + ); +} From 02d8d72569dd782c89361871e40493611cd540bf Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 7 Oct 2026 07:08:07 -0700 Subject: [PATCH 35/49] oauth pin: b7d15c7 + the compiled_in entry (auth-oauth p3-flip-llm-pin 22abf69), keeping predev's loopback-allowed mint need (spec l.611); oauth dev 98fb861 merged it back to operator-infrastructure --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f91d83437..8b4d7cb24b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,7 +454,7 @@ dependencies = [ [[package]] name = "busbar-auth-oauth" version = "1.6.0" -source = "git+https://github.com/GetBusbar/busbar-auth-oauth?rev=98fb861d14914982fdba6f8666681fb62ba1d6b1#98fb861d14914982fdba6f8666681fb62ba1d6b1" +source = "git+https://github.com/GetBusbar/busbar-auth-oauth?rev=22abf6939076b1fe1bb711442ceb47920de2996f#22abf6939076b1fe1bb711442ceb47920de2996f" dependencies = [ "base64 0.22.1", "busbar-contract", diff --git a/Cargo.toml b/Cargo.toml index 93b64d76f6..8bb3cb71ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ busbar-auth-header = { git = "https://github.com/GetBusbar/busbar-auth-header", # THE OAUTH MINTING STYLES (kind: auth): the logic crate the root links on its `auths` axis under the # llm fold's switch (feature `auth-oauth`), and the dev edge the root's door_steps tests mint a member's # credential through. One pin. -busbar-auth-oauth = { git = "https://github.com/GetBusbar/busbar-auth-oauth", rev = "98fb861d14914982fdba6f8666681fb62ba1d6b1" } +busbar-auth-oauth = { git = "https://github.com/GetBusbar/busbar-auth-oauth", rev = "22abf6939076b1fe1bb711442ceb47920de2996f" } # THE OUTBOUND SIGNING STYLE (kind: auth): the logic crate the root links on its `auths` axis under the # llm fold's switch (feature `auth-sigv4`), pulled at a pinned rev (P5). busbar-auth-sigv4 = { git = "https://github.com/GetBusbar/busbar-auth-sigv4", rev = "c22578acc927269f27e25dcef41ff7a4d91403ee" } From e5d118d7b51f6b8294c22df3208b0a2fb830baa9 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:23:22 -0700 Subject: [PATCH 36/49] root tests after the #511 merge: predev's node-hooks arm pins the walk's shed in 1.5.5's words, as this line's sibling cells do (RefusalIn.text is the kernel's own message, every plane); the boundary walk's core-route floor counts the kernel's own guarded routes (3), the body-ingress convenience routes being a plane's (spec l.4093) --- crates/busbar/src/root/tests/door_steps.rs | 6 +++++- crates/busbar/src/root/tests/serve_money.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/busbar/src/root/tests/door_steps.rs b/crates/busbar/src/root/tests/door_steps.rs index 7d0369d2b5..b0ed1d6431 100644 --- a/crates/busbar/src/root/tests/door_steps.rs +++ b/crates/busbar/src/root/tests/door_steps.rs @@ -3028,8 +3028,12 @@ pub(crate) mod door_boundary { door_checked > 0, "the door's half of the boundary was asserted" ); + // The kernel's own guarded routes (`/stats`, `/v1/models`, `/v1beta/models`): the convenience + // body-ingress routes are a plane's and mount only where a plane installs body ingress (the + // kernel owns no route that exists for one plugin, BUSBAR-1.6.0.md l.4093), which this rig + // does not; a door claims its own paths and is walked in the door arm above. assert!( - checked >= 4, + checked >= 3, "the walk covered only {checked} guarded core routes — it is not seeing the router" ); for path in &boot_plugin_paths { diff --git a/crates/busbar/src/root/tests/serve_money.rs b/crates/busbar/src/root/tests/serve_money.rs index a2e8adeff4..a6bcb43dcd 100644 --- a/crates/busbar/src/root/tests/serve_money.rs +++ b/crates/busbar/src/root/tests/serve_money.rs @@ -540,8 +540,12 @@ async fn node_boot_hooks_arm() { let (status, body) = g.post("/call/direct:m", true).await; assert_eq!( (status, body.as_str()), - (503, "refused:503:breaker_open"), - "admitted, then the walk is exhausted" + ( + 503, + "refused:503:The service is temporarily overloaded. Please retry shortly." + ), + "admitted, then the walk is exhausted (the shed in 1.5.5's words, as every plane is handed \ + them: RefusalIn.text is the kernel's own message for the refusal)" ); assert_eq!(g.requests(), 1, "its request was charged at admission"); assert_eq!(g.post.open_units(), 0, "its node facts closed at its end"); From 60157df4bd45017bfa0f4521c525187a3a76e196 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 7 Oct 2026 09:01:20 -0700 Subject: [PATCH 37/49] mcp door: an exhausted walk's refusal says the reason's own word, as predev served it The kernel plane driver now hands every plane the walk's own sentence in RefusalIn.text for an exhausted walk (the model door renders it in 1.5.5's words). The mcp door's baseline is predev (spec l.4593, l.4834: for the new planes the baseline is current predev behaviour), and predev handed this door the reason word `breaker_open` there. With a resolved server the door renders its own -32030 sentence and never repeats the text, so that arm is unchanged; but two paths repeat it: - the generic arm (BreakerOpen with no resolved server: a call whose tool left the catalogue on a reload while it waited out its server's timeout): predev 503 {"error":{"code":-32000,"message":"breaker_open"},"id":41,"jsonrpc":"2.0"}, #464 said "The service is temporarily overloaded. Please retry shortly." instead; - a task's continuation refused by the walk (a stalled or a tripped server): tasks/get stated {"code":-32603,"message":"the MCP upstream call failed: breaker_open"} on predev, the walk's sentence on #464. The door's refusal slot now takes the reason's own word for a kernel refusal whose reason is BreakerOpen (every walk terminal the kernel renders as an exhaustion), so both paths serve predev's bytes again. Four root cells in tools_door pin the bytes, each run green on predev fb5f12aa54 first: the tripped server's -32030 control, the generic arm, and the two task paths. --- crates/busbar-plane-mcp/src/tool_door.rs | 14 +- crates/busbar/src/root/tests/serve_tests.rs | 300 ++++++++++++++++++++ 2 files changed, 313 insertions(+), 1 deletion(-) diff --git a/crates/busbar-plane-mcp/src/tool_door.rs b/crates/busbar-plane-mcp/src/tool_door.rs index 914e5c10c5..5c64dc2609 100644 --- a/crates/busbar-plane-mcp/src/tool_door.rs +++ b/crates/busbar-plane-mcp/src/tool_door.rs @@ -2865,7 +2865,19 @@ slot!( _ => None, }) }); - let text = input.field(|i| &i.text).bytes(); + // THE WORDS the refusal is said in: the kernel's text, but for the walk's exhaustion (an open + // breaker, a walk out of members or out of time) the reason's own word, as predev's door said + // it, wherever this door repeats the words (its generic error, a task's failure): the + // kernel's text there is the walk's own sentence, which predev never handed this door. + let text: &[u8] = if given.cause != REFUSAL_ARRIVE + && given.reason == busbar_contract::abi::plane::RefusalCode::BreakerOpen.code() + { + busbar_contract::caps::ReasonCode::BreakerOpen + .as_str() + .as_bytes() + } else { + input.field(|i| &i.text).bytes() + }; // The tool a refused `tools/call` named, where its arrival read one. let called = instance.get().and_then(|plane| { plane.units.with(&given.unit, |unit| { diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index 8731ca62ce..fa5dd09a2a 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -1877,6 +1877,306 @@ mod tools_door { let (status, answer) = call(&rig, &reader, "cat_read_file", serde_json::json!({})).await; assert_eq!(status, StatusCode::OK, "its own tool is served: {answer}"); } + + // ── THE WALK'S EXHAUSTION ON THIS DOOR: PREDEV'S BYTES ────────────────────────────────────── + // + // The kernel hands every plane the walk's own sentence for an exhausted walk + // (`RefusalIn.text`); this door's baseline is predev's (spec l.4452), which said the reason's + // own word there. Each cell below drives a `tools/call` to an exhausted walk on one path the + // door's refusal slot takes, and pins the bytes predev serves on it. + + /// The `tools/call` of `tool` with the tasks extension declared (a call that creates a task + /// where its tool supports one). + fn task_call_of(tool: &str) -> String { + let mut call: serde_json::Value = + serde_json::from_str(&call_of(tool, serde_json::json!({}))).expect("the call"); + call["params"]["_meta"]["io.modelcontextprotocol/clientCapabilities"] = + serde_json::json!({ "extensions": { "io.modelcontextprotocol/tasks": {} } }); + call.to_string() + } + + /// `tasks/get` of `task_id` on `rig`, the extension declared: the status and the JSON-RPC body. + async fn task_get(rig: &Rig, task_id: &str) -> (u16, serde_json::Value) { + let verb = serde_json::json!({ + "jsonrpc": "2.0", "id": 43, "method": "tasks/get", + "params": { + "taskId": task_id, + "_meta": { + "io.modelcontextprotocol/protocolVersion": protocol_version(), + "io.modelcontextprotocol/clientCapabilities": + { "extensions": { "io.modelcontextprotocol/tasks": {} } }, + }, + }, + }) + .to_string(); + let (status, body) = send_as( + &rig.router, + Some(&rig.token), + &verb, + "tasks/get", + Some(task_id), + ) + .await; + ( + status.as_u16(), + serde_json::from_slice(&body).expect("JSON-RPC"), + ) + } + + /// `tasks/get` of `task_id` until the task is terminal (or the test gives up): its body. + async fn task_settled(rig: &Rig, task_id: &str) -> serde_json::Value { + for _ in 0..600 { + let (_, body) = task_get(rig, task_id).await; + if ["completed", "failed", "cancelled"] + .iter() + .any(|s| body["result"]["status"] == *s) + { + return body; + } + tokio::time::sleep(Duration::from_millis(25)).await; + } + panic!("task {task_id} never settled"); + } + + /// A tool server answering its tool list as approved and holding every `tools/call` unanswered. + async fn stalling_calls() -> (u16, Heard) { + tool_server_replying(Arc::new(|r: &str| { + if r.contains("\"tools/list\"") { + let list = serde_json::json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": tool_listing()}}); + (200, list.to_string()) + } else { + (STALL, String::new()) + } + })) + .await + } + + /// Wait for the server to hear a `tools/call`. + async fn heard_a_call(heard: &mut Heard) { + tokio::time::timeout(Duration::from_secs(10), async { + loop { + let r = heard.recv().await.expect("the server hears"); + if r.contains("\"tools/call\"") { + return; + } + } + }) + .await + .expect("the call reached the server"); + } + + /// Publish generation 2 of `rig`'s door over the `tools:` section `section` (the operator's + /// reload): the plane's catalogue moves; the kernel's sealed egress does not. + fn reload(rig: &Rig, section: &serde_yaml::Value) { + use crate::root::loader::dispatch::{in_head, out_head, Frame}; + use busbar_contract::abi::mechanism::call::{Blob, Outcome as AbiOutcome, BLOB_JSON}; + use busbar_contract::abi::mechanism::lifecycle::RefreshIn; + use busbar_contract::abi::plane::PlaneRefreshOut; + let settings = serde_json::to_vec(section).expect("json"); + let mut frame = Frame::new( + RefreshIn { + head: in_head(), + generation: 2, + settings: Blob { + ptr: settings.as_ptr(), + len: settings.len(), + fmt: BLOB_JSON, + flags: 0, + }, + secrets: std::ptr::null(), + secrets_len: 0, + }, + PlaneRefreshOut { + head: out_head(), + snapshot: std::ptr::null(), + }, + ); + let (called, snapshot) = rig.plane.refresh(&mut frame); + assert_eq!(called.outcome, AbiOutcome::Ready, "the refresh is taken"); + assert!(snapshot.is_some(), "generation 2 is published"); + } + + /// EXHAUSTED, THE SERVER RESOLVED (the control the two cells after it stand beside): a tripped + /// server's call is refused as the served engine refused it, `503` / `-32030` / + /// `upstream_unavailable` with its sentence and the wait, byte for byte as predev serves it; the + /// walk's own sentence never reaches the caller. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn a_tripped_servers_exhausted_walk_is_refused_in_predevs_bytes() { + let _one = PUBLISHING.lock().await; + let instance = "serve-door-tools-exhausted-bytes"; + let _published = Published(instance); + let (port, mut heard) = answering_calls_with(503).await; + let rig = rig_tools(instance, port, registration("flaky", port, ""), &|app| app); + fail_to_the_trip(&rig, "flaky_read_file", &mut heard, 0).await; + + let (status, headers, body) = send_headed( + &rig.router, + Some(&rig.token), + &call_of("flaky_read_file", serde_json::json!({})), + "tools/call", + Some("flaky_read_file"), + ) + .await; + let body = String::from_utf8_lossy(&body).to_string(); + let wait: u64 = headers + .get("retry-after") + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.parse().ok()) + .unwrap_or_else(|| panic!("a Retry-After: {headers:?} {body}")); + assert!(wait > 0, "the open cell's wait: {headers:?}"); + assert_eq!(status.as_u16(), 503, "{headers:?} {body}"); + assert_eq!( + body, + format!( + "{{\"error\":{{\"code\":-32030,\"data\":{{\"reason\":\"upstream_unavailable\",\ + \"retry_after_ms\":{},\"server\":\"flaky\"}},\"message\":\"MCP server `flaky` is \ + unavailable: its circuit breaker is open after repeated failures; busbar did not \ + dispatch this call. Retry after {wait}s.\"}},\"id\":41,\"jsonrpc\":\"2.0\"}}", + wait * 1000 + ), + "{headers:?}" + ); + assert!(drain(&mut heard).is_empty(), "nothing reached the server"); + assert!(rig.all_ended(), "every unit ended"); + } + + /// EXHAUSTED, THE SERVER NO LONGER RESOLVED (the generic arm of the door's refusal slot): a call + /// dispatched to a server that never answers, whose tool leaves the catalogue (an operator's + /// reload) while the call waits out the server's `timeout:`, is refused when the walk is + /// exhausted as predev refused it: the walk's status, `-32000`, and the reason's own word, no + /// data and no wait. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn an_exhausted_walk_whose_tool_left_the_catalogue_is_refused_in_predevs_bytes() { + let _one = PUBLISHING.lock().await; + let instance = "serve-door-tools-exhausted-unlisted"; + let _published = Published(instance); + let (port, mut heard) = stalling_calls().await; + let rig = rig_tools( + instance, + port, + registration("stall", port, " timeout: 2s\n"), + &|app| app, + ); + let (router, token) = (rig.router.clone(), rig.token.clone()); + let pending = tokio::spawn(async move { + send_headed( + &router, + Some(&token), + &call_of("stall_read_file", serde_json::json!({})), + "tools/call", + Some("stall_read_file"), + ) + .await + }); + heard_a_call(&mut heard).await; + // The reload: the server is registered under another name, so the stalled call's tool is no + // longer one the catalogue publishes. + reload(&rig, ®istration("other", port, "")); + + let (status, headers, body) = tokio::time::timeout(Duration::from_secs(20), pending) + .await + .expect("the stalled call was answered") + .expect("the call ran"); + let body = String::from_utf8_lossy(&body).to_string(); + assert_eq!(status.as_u16(), 503, "{headers:?} {body}"); + assert_eq!( + body, r#"{"error":{"code":-32000,"message":"breaker_open"},"id":41,"jsonrpc":"2.0"}"#, + "{headers:?}" + ); + assert!( + headers.get("retry-after").is_none(), + "no wait on this arm: {headers:?}" + ); + assert!(rig.all_ended(), "every unit ended"); + } + + /// EXHAUSTED UNDER A TASK (the continuation's refusal): a task whose call is dispatched to a + /// server that never answers fails when its continuation's walk is exhausted, and `tasks/get` + /// states the failure as predev stated it: `-32603`, the upstream call failed, and the reason's + /// own word. + #[cfg(linked_axis_node)] + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn a_tasks_exhausted_walk_fails_the_task_in_predevs_bytes() { + let _one = PUBLISHING.lock().await; + let instance = "serve-door-tools-exhausted-task"; + let _published = Published(instance); + let (port, mut heard) = stalling_calls().await; + let tools: serde_yaml::Value = serde_yaml::from_str(&format!( + "stall:\n url: \"http://127.0.0.1:{port}/rpc\"\n {PIN}\n timeout: 2s\n \ + tools_allow:\n read_file: {{ schema_hash: \"{}\", task_support: optional }}\n", + tool_digest() + )) + .expect("a section"); + let rig = rig_tools(instance, port, tools, &|app| app); + + let (status, body) = send_as( + &rig.router, + Some(&rig.token), + &task_call_of("stall_read_file"), + "tools/call", + Some("stall_read_file"), + ) + .await; + let created: serde_json::Value = serde_json::from_slice(&body).expect("JSON-RPC"); + assert_eq!(status.as_u16(), 200, "{created}"); + let task_id = created["result"]["taskId"] + .as_str() + .unwrap_or_else(|| panic!("a task: {created}")) + .to_string(); + heard_a_call(&mut heard).await; + + let settled = task_settled(&rig, &task_id).await; + assert_eq!(settled["result"]["status"], "failed", "{settled}"); + assert_eq!( + settled["result"]["error"].to_string(), + r#"{"code":-32603,"message":"the MCP upstream call failed: breaker_open"}"#, + "{settled}" + ); + } + + /// EXHAUSTED UNDER A TASK, THE SERVER TRIPPED: a task created on a tool whose server's cell is + /// open fails as predev failed it, its `tasks/get` stating `-32603`, the upstream call failed, + /// and the reason's own word. + #[cfg(linked_axis_node)] + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn a_task_on_a_tripped_server_fails_in_predevs_bytes() { + let _one = PUBLISHING.lock().await; + let instance = "serve-door-tools-exhausted-task-tripped"; + let _published = Published(instance); + let (port, mut heard) = answering_calls_with(503).await; + let tools: serde_yaml::Value = serde_yaml::from_str(&format!( + "flaky:\n url: \"http://127.0.0.1:{port}/rpc\"\n {PIN}\n \ + tools_allow:\n read_file: {{ schema_hash: \"{}\", task_support: optional }}\n", + tool_digest() + )) + .expect("a section"); + let rig = rig_tools(instance, port, tools, &|app| app); + fail_to_the_trip(&rig, "flaky_read_file", &mut heard, 0).await; + + let (status, headers, body) = send_headed( + &rig.router, + Some(&rig.token), + &task_call_of("flaky_read_file"), + "tools/call", + Some("flaky_read_file"), + ) + .await; + let created: serde_json::Value = serde_json::from_slice(&body).expect("JSON-RPC"); + assert_eq!(status.as_u16(), 200, "{headers:?} {created}"); + let task_id = created["result"]["taskId"] + .as_str() + .unwrap_or_else(|| panic!("a task: {created}")) + .to_string(); + + let settled = task_settled(&rig, &task_id).await; + assert_eq!(settled["result"]["status"], "failed", "{settled}"); + assert_eq!( + settled["result"]["error"].to_string(), + r#"{"code":-32603,"message":"the MCP upstream call failed: breaker_open"}"#, + "{settled}" + ); + assert!(drain(&mut heard).is_empty(), "nothing reached the server"); + } } #[cfg(feature = "plane-decisions")] From 4635be292bc979a2e7500f58efcc5f7c8c94d9b5 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 7 Oct 2026 09:01:20 -0700 Subject: [PATCH 38/49] plane_integration: the body-ingress convenience routes are mounted, guarded and walked /{name}/v1/messages and /{provider}/{model}/v1/messages are 1.5.5 surface that the kernel router mounts only where a plane installed body ingress (any_body_ingress). The audience-bound boundary walk's app installs it through its linked planes, so the cell now states it: body ingress is installed, both patterns are in the router's core-route table on POST with a key's bar (never RouteAuth::None), and the walk reaches both, an anonymous caller refused 401 and a keyed caller admitted. This is the positive proof beside the root door_boundary floor that counts the kernel's own guarded routes. --- crates/busbar/tests/plane_integration.rs | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/crates/busbar/tests/plane_integration.rs b/crates/busbar/tests/plane_integration.rs index c276ccf560..521253747d 100644 --- a/crates/busbar/tests/plane_integration.rs +++ b/crates/busbar/tests/plane_integration.rs @@ -664,6 +664,10 @@ mod metrics_scrape { } } +/// The kernel's body-ingress convenience routes (`/{name}/v1/messages`, +/// `/{provider}/{model}/v1/messages`): 1.5.5 surface, mounted where a plane installed body ingress. +const BODY_INGRESS_ROUTES: [&str; 2] = ["/{name}/v1/messages", "/{provider}/{model}/v1/messages"]; + /// THE PLANE-BOUNDARY RATCHET (1.6.0), driven by the ROUTER TABLE rather than by a hand-listed /// sample. /// @@ -822,6 +826,27 @@ async fn an_audience_bound_token_is_confined_to_its_door_plane() { RouteAuth::None — the bypass set this names is not the one the router built" ); } + // THE BODY-INGRESS CONVENIENCE ROUTES (1.5.5 surface): the kernel mounts them only where a + // plane installed body ingress (`busbar_kernel::ingress::arrival::any_body_ingress`), and this + // app's linked planes install it, as the default build's boot does. So both are mounted here, + // each guarded (a key's bar, never `RouteAuth::None`), and the walk below must reach both: an + // anonymous caller refused, a keyed caller admitted. + assert!( + busbar_kernel::ingress::arrival::any_body_ingress(), + "the linked planes installed no body ingress, so the convenience routes are not mounted \ + and this walk proves nothing about them" + ); + for pattern in BODY_INGRESS_ROUTES { + assert!( + core_routes + .iter() + .any(|(path, method, auth)| path == pattern + && method == "POST" + && *auth != busbar_contract::abi::mechanism::route::RouteAuth::None), + "{pattern} is a body-ingress convenience route, mounted guarded on POST wherever body \ + ingress is installed; the router built none: {core_routes:?}" + ); + } let boot_plugin_paths: Vec<String> = busbar_kernel::boot_route_paths_of(&app); assert!( !core_routes.is_empty(), @@ -871,6 +896,7 @@ async fn an_audience_bound_token_is_confined_to_its_door_plane() { .to_string(); let mut checked = 0usize; + let mut body_walked: Vec<String> = Vec::new(); let mut door_checked = 0usize; for route in &core_routes { let path = concrete(&route.0); @@ -998,8 +1024,29 @@ async fn an_audience_bound_token_is_confined_to_its_door_plane() { route.1, path ); + if BODY_INGRESS_ROUTES.contains(&route.0.as_str()) { + assert_eq!( + anon, 401, + "an anonymous caller is refused on the convenience route {} {}", + route.1, path + ); + assert!( + plain.status() != 401 && plain.status() != 403, + "a keyed caller is admitted on the convenience route {} {}: {}", + route.1, + path, + plain.status() + ); + body_walked.push(route.0.clone()); + } checked += 1; } + for pattern in BODY_INGRESS_ROUTES { + assert!( + body_walked.iter().any(|p| p == pattern), + "the convenience route {pattern} was never walked: {body_walked:?}" + ); + } assert_eq!( door_checked, core_routes From 9a918bf3f10499c0821ce7a58e17b879e16d8924 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 7 Oct 2026 09:15:54 -0700 Subject: [PATCH 39/49] mcp door: the open breaker's word is the door's own constant, so the plane names no kernel-side vocabulary (purity: busbar_contract::caps is the kernel's) --- crates/busbar-plane-mcp/src/tool_door.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/busbar-plane-mcp/src/tool_door.rs b/crates/busbar-plane-mcp/src/tool_door.rs index 5c64dc2609..68d8ffe3c6 100644 --- a/crates/busbar-plane-mcp/src/tool_door.rs +++ b/crates/busbar-plane-mcp/src/tool_door.rs @@ -421,6 +421,10 @@ slot!( /// The most units the instance keeps state for at once; past it, the oldest is dropped first. pub const MAX_UNITS: usize = 4096; +/// The open breaker's reason as the kernel spells it (`RefusalCode::BreakerOpen`'s word): the words +/// this door says a walk's exhaustion in where it repeats them, as predev's door said it. +const BREAKER_OPEN_WORD: &str = "breaker_open"; + /// One unit's state, from its arrival to its end. struct CallUnit { /// The unit's own key, as the kernel minted it. @@ -2872,9 +2876,7 @@ slot!( let text: &[u8] = if given.cause != REFUSAL_ARRIVE && given.reason == busbar_contract::abi::plane::RefusalCode::BreakerOpen.code() { - busbar_contract::caps::ReasonCode::BreakerOpen - .as_str() - .as_bytes() + BREAKER_OPEN_WORD.as_bytes() } else { input.field(|i| &i.text).bytes() }; From 107781c1d3618f84c642701bb75d340835d60cf9 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:08:49 -0700 Subject: [PATCH 40/49] root tests: the tool door's refusal words for an exhausted walk are fixture data (tool_door_wire.txt), as its other wire words are, so the root's test source names no plane; the busbar -> busbar-auth-sigv4 dep row is struck (the extracted crate is no shipped edge, measured) --- .../src/root/tests/fixtures/tool_door_wire.txt | 5 +++++ crates/busbar/src/root/tests/serve_tests.rs | 14 ++++++++------ qa/kind-isolation.toml | 10 ---------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt b/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt index d410d5fb07..3637476627 100644 --- a/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt +++ b/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt @@ -39,3 +39,8 @@ plane_key = mcp # resource noun AuditRow::tool names a tool by: `<noun>:<published name>`). audit_action_tool_call = mcp_tool.call audit_resource_tool = mcp_tool +# The plane's refusal words for an exhausted walk, byte for byte as predev served them (tool_door.rs +# RefusalSlot; the `*_in_predevs_bytes` cells in serve_tests.rs): a tripped server's sentence +# ({server} and {wait} filled by the cell), and a task continuation the walk refused. +tripped_server_message = MCP server `{server}` is unavailable: its circuit breaker is open after repeated failures; busbar did not dispatch this call. Retry after {wait}s. +continuation_refused_error = {"code":-32603,"message":"the MCP upstream call failed: breaker_open"} diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index fa5dd09a2a..1af20d1388 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -2029,10 +2029,12 @@ mod tools_door { body, format!( "{{\"error\":{{\"code\":-32030,\"data\":{{\"reason\":\"upstream_unavailable\",\ - \"retry_after_ms\":{},\"server\":\"flaky\"}},\"message\":\"MCP server `flaky` is \ - unavailable: its circuit breaker is open after repeated failures; busbar did not \ - dispatch this call. Retry after {wait}s.\"}},\"id\":41,\"jsonrpc\":\"2.0\"}}", - wait * 1000 + \"retry_after_ms\":{},\"server\":\"flaky\"}},\"message\":\"{}\"}},\"id\":41,\ + \"jsonrpc\":\"2.0\"}}", + wait * 1000, + surface("tripped_server_message") + .replace("{server}", "flaky") + .replace("{wait}", &wait.to_string()) ), "{headers:?}" ); @@ -2129,7 +2131,7 @@ mod tools_door { assert_eq!(settled["result"]["status"], "failed", "{settled}"); assert_eq!( settled["result"]["error"].to_string(), - r#"{"code":-32603,"message":"the MCP upstream call failed: breaker_open"}"#, + surface("continuation_refused_error"), "{settled}" ); } @@ -2172,7 +2174,7 @@ mod tools_door { assert_eq!(settled["result"]["status"], "failed", "{settled}"); assert_eq!( settled["result"]["error"].to_string(), - r#"{"code":-32603,"message":"the MCP upstream call failed: breaker_open"}"#, + surface("continuation_refused_error"), "{settled}" ); assert!(drain(&mut heard).is_empty(), "nothing reached the server"); diff --git a/qa/kind-isolation.toml b/qa/kind-isolation.toml index 5b0488fc74..432d1bc6e6 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -582,16 +582,6 @@ cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 / §6 step 3 — the compositio why = "busbar names busbar-auth-oauth: declared optional (feature `auth-oauth`, carried by the llm fold's `llm-on-driver` switch, not in `default`) in the shipped graph, 1 declaration(s) at this commit — the root compiles in the auth plugin serving the token styles (jwt-bearer, oauth-client-credentials) that a door-served llm member's egress binds its style through (door_steps `OutboundAuths`). The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack." drain = "none: BUSBAR-1.6.0.md grants this class (the root names the auth axis). 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-auth-sigv4" -half = "shipped" -count = "1" -verdict = "allowed" -cite = "BUSBAR-1.6.0.md THE DESIGN, §1 / §9 / §6 step 3 — the composition root is the one thing that names all three axes; the auth plugin that serves a door plane's outbound style is linked on the `auths` axis (ARCHITECT Q-L1-AUTH (A) 2026-10-03); Part 3 §12 \"The switch\" (the llm fold's development-only `llm-on-driver` carries it)." -why = "busbar names busbar-auth-sigv4: declared optional (feature `auth-sigv4`, carried by the llm fold's `llm-on-driver` switch, not in `default`) in the shipped graph, 1 declaration(s) at this commit — the root compiles in the auth plugin serving the signing style (sigv4) that a door-served llm member's egress binds its style through (door_steps `OutboundAuths`). The count is TODAY'S MEASUREMENT, exact in both directions — above it is the landing that grew the coupling, below it is stale slack." -drain = "none: BUSBAR-1.6.0.md grants this class (the root names the auth axis). 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-contract" From 723188d551659405a067bec9099157b0823e0def Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:18:07 -0700 Subject: [PATCH 41/49] Fix PR 464 reds: PlaneRegistration fallback field, duplicate dialect key check, kernel law0 secret hit - endpoints_cross_plane.rs: the listing plane's PlaneRegistration states `fallback: false` (the field the contract gained); restores check/clippy/test rows. - xtask dialect.rs: drop the branch's `refuse_repeated_keys` in read_all, which the predev merge left beside predev's own compile_one check for the same Q128 case. It fired first with different words, so `xtask selftest dialect-map` could not see its expected "\"stop_reason\" is written twice". dialect.rs now matches predev. - far_end_tests.rs: the hooks-constraint test's upstream bodies were b"a"/b"b"/b"c"; "c" is the secret instance id (busbar-secret-c), so busbar-kernel x instance:secret rose to 118 against its 117 ceiling (law0 findings 18 -> 19). The bodies are not asserted; they are now b"ok", as in the neighbouring test. Proved on Latchkey: xtask gate kind-isolation (law0 back to 18 findings, kernel x instance:secret at 117), xtask selftest dialect-map (green), cargo test -p busbar-kernel --test endpoints_cross_plane (13 passed), and the edited far_end test (passed). --- .../src/plane_driver/tests/far_end_tests.rs | 6 +-- .../tests/endpoints_cross_plane.rs | 1 + xtask/src/dialect.rs | 54 +++---------------- 3 files changed, 11 insertions(+), 50 deletions(-) diff --git a/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs b/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs index 58fe639248..f786af27bc 100644 --- a/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs +++ b/crates/busbar-kernel/src/plane_driver/tests/far_end_tests.rs @@ -1457,9 +1457,9 @@ async fn the_hooks_constraint_orders_and_restricts_the_pick() { use crate::plane_driver::{Constraint, MemberFacts}; let r = rig( &[ - ("a.test", Script::Answer(200, None, vec![b"a"])), - ("b.test", Script::Answer(200, None, vec![b"b"])), - ("c.test", Script::Answer(200, None, vec![b"c"])), + ("a.test", Script::Answer(200, None, vec![b"ok"])), + ("b.test", Script::Answer(200, None, vec![b"ok"])), + ("c.test", Script::Answer(200, None, vec![b"ok"])), ], OnExhausted::Status503, None, diff --git a/crates/busbar-kernel/tests/endpoints_cross_plane.rs b/crates/busbar-kernel/tests/endpoints_cross_plane.rs index 15e1e2bf3f..c0576d0922 100644 --- a/crates/busbar-kernel/tests/endpoints_cross_plane.rs +++ b/crates/busbar-kernel/tests/endpoints_cross_plane.rs @@ -461,6 +461,7 @@ fn with_listing_door( record_kinds: Vec::new(), trust_keys: Vec::new(), caller_credential_refusal: None, + fallback: false, validate: Arc::new(|_: &[u8]| Ok(())), facing: Arc::new(move |_: &[u8], _: &[u8], _: Option<&str>| { Ok(DoorFacing { diff --git a/xtask/src/dialect.rs b/xtask/src/dialect.rs index f25597c800..315797bb0f 100644 --- a/xtask/src/dialect.rs +++ b/xtask/src/dialect.rs @@ -575,43 +575,22 @@ fn compile_one(all: &[Dialect], d: &Dialect, answer: &BTreeSet<String>) -> Resul )) } -/// Read every mapping file under [`DIALECT_DIR`], refusing one that repeats a key in a table: a -/// repeated key is invalid TOML, and two rows for one wire path would compile into contradictory -/// mapping rows (ARCHITECT, Q128 WARN). +/// Read every mapping file under [`DIALECT_DIR`]. fn read_all(cx: &Ctx) -> Result<Vec<Dialect>, String> { let files = cx .walk(&WalkSpec::new([DIALECT_DIR]).ext("toml").min_files(1)) .map_err(|e| e.to_string())?; - files + Ok(files .iter() - .map(|f| { - let name = f + .map(|f| Dialect { + name: f .rel .file_stem() .map(|s| s.to_string_lossy().into_owned()) - .unwrap_or_default(); - let doc = toml_lite::parse_text(&f.text); - refuse_repeated_keys(&format!("{DIALECT_DIR}/{name}.toml"), &doc)?; - Ok(Dialect { name, doc }) + .unwrap_or_default(), + doc: toml_lite::parse_text(&f.text), }) - .collect() -} - -/// `Err` naming the first key a table of `doc` states twice (the file `at`, the table, the key). -pub fn refuse_repeated_keys(at: &str, doc: &toml_lite::Document) -> Result<(), String> { - for (table, t) in &doc.tables { - let mut seen: Vec<&str> = Vec::new(); - for (key, _) in &t.entries { - if seen.contains(&key.as_str()) { - return Err(format!( - "{at}: [{table}] states `{key}` twice; a key is stated once (TOML), and two \ - rows for one wire path would compile into contradictory mapping rows" - )); - } - seen.push(key); - } - } - Ok(()) + .collect()) } /// Compile every mapping file under [`DIALECT_DIR`]. @@ -851,22 +830,3 @@ pub fn main(cx: &Ctx, args: &[String]) -> i32 { } } } - -#[cfg(test)] -mod tests { - /// A mapping file that repeats a key in a table is refused, naming the file, the table and the - /// key; the same file with the key stated once compiles past the check. RED before: the - /// compiler read the repeated row and emitted both. - #[test] - fn a_mapping_file_that_repeats_a_key_is_refused() { - let twice = crate::toml_lite::parse_text( - "[rows.response]\n\"a.id\" = { ir = \"x\" }\n\"a.id\" = { prim = \"id\" }\n", - ); - let err = super::refuse_repeated_keys("d.toml", &twice).expect_err("refused"); - assert!( - err.contains("d.toml") && err.contains("[rows.response]") && err.contains("`a.id`") - ); - let once = crate::toml_lite::parse_text("[rows.response]\n\"a.id\" = { ir = \"x\" }\n"); - super::refuse_repeated_keys("d.toml", &once).expect("a key stated once"); - } -} From 85ca2a3433ffb22a39fccaaf4bdc75faa494ab86 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 10 Oct 2026 08:11:14 -0700 Subject: [PATCH 42/49] Give the healthz door's PlaneRegistration its fallback field opening_door() in crates/busbar-kernel/tests/endpoints_cross_plane.rs came in from predev (#659) after this branch added PlaneRegistration::fallback, so the kernel's integration tests stopped compiling (E0063) and took build:check, clippy, workspace-compile and every test lane with them. The door is not the catch-all plane, so it states fallback: false like with_listing_door beside it. Not proved on Latchkey: the dev run budget (RUN_CAP) is spent; reasoned from the CI rustc output and the struct definition. This PR's CI is the proof. --- crates/busbar-kernel/tests/endpoints_cross_plane.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/busbar-kernel/tests/endpoints_cross_plane.rs b/crates/busbar-kernel/tests/endpoints_cross_plane.rs index b854e0f6c0..420f5b3f91 100644 --- a/crates/busbar-kernel/tests/endpoints_cross_plane.rs +++ b/crates/busbar-kernel/tests/endpoints_cross_plane.rs @@ -586,6 +586,7 @@ fn opening_door() -> &'static busbar_kernel::plane::registry::PlaneDecl { record_kinds: Vec::new(), trust_keys: Vec::new(), caller_credential_refusal: None, + fallback: false, validate: Arc::new(|_: &[u8]| Ok(())), facing: Arc::new(|_: &[u8], _: &[u8], _: Option<&str>| { Ok(busbar_contract::plane_calls::DoorFacing::default()) From 5cec7a0cda79a92410899212aa49eb47fd065079 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:27:37 -0700 Subject: [PATCH 43/49] FLIP-LLM law0: the composition root names no plane, dialect or wire; the llm plane's hook cases live in its own crate ARCHITECT ruling on #464 (BUSBAR-1.6.0.md l.1995-2006): crates/busbar is Family::Neutral and its law0 ceilings do not move, so the rise this landing made (busbar x plane 857, x transport 209, x vendor 188 against predev's 822/208/90) is drained rather than re-armed. - The 1.5.5 hook cases of llm origin (seam, projection, held, policies) and the words they speak to the plane (its settings, each far end's answer, the plain request the rig fires) move to the llm plane's own crate, crates/busbar-plane-llm/tests/on_driver/. They are still proven through the plugin door: the root's hook_parity_driver suite links the plane's door, loads it through the one loader and drives it on the kernel's plane driver, and compiles the plane's cases beside its rig from the manifest's [package.metadata.busbar.driver-proofs] row (build.rs writes the one #[path]). The rig names no plane and no dialect; its plane open states no provider facts (#722). - The root's pools-door cells (serve_hook_seats.rs, the fold-on-driver cells of serve_tests.rs) read the dialects they speak as data, the plane's own door words (tests/on_driver/words.txt, DRIVER_WORDS), instead of spelling them. - The tool door's exhausted-walk sentences take the plane's key off the fixture's plane_key. - model_egress reads the far end's number in the kernel's first reserved status numbering, as the kernel's far end does, instead of naming the wire. - Comments in the root and the kernel's hook cases name the plane serving the pools map, not the plane; the reviewed refusal-site note names no lane by plane. Customer-visible behaviour is unchanged: no production path changes but the status numbering read, which is the same constant. --- .../tests/support/plane_driver_hook_cases.rs | 4 +- .../tests/on_driver}/held.rs | 2 +- .../busbar-plane-llm/tests/on_driver/mod.rs | 24 ++++++ .../tests/on_driver}/policies.rs | 0 .../tests/on_driver}/projection.rs | 4 +- .../tests/on_driver}/seam.rs | 6 +- .../tests/on_driver}/speech_request.json | 0 .../busbar-plane-llm/tests/on_driver/wire.rs | 66 ++++++++++++++++ .../tests/on_driver/words.txt | 14 ++++ crates/busbar/Cargo.toml | 10 +++ crates/busbar/build.rs | 59 +++++++++++++++ crates/busbar/src/root/door_steps.rs | 2 +- crates/busbar/src/root/model_egress.rs | 9 ++- crates/busbar/src/root/tests/door_steps.rs | 6 ++ .../root/tests/fixtures/tool_door_wire.txt | 7 +- .../busbar/src/root/tests/serve_hook_seats.rs | 61 +++++++++------ crates/busbar/src/root/tests/serve_tests.rs | 45 +++++------ .../tests/fixtures/computed_refusal_sites.txt | 4 +- .../busbar/tests/hook_parity_driver/main.rs | 38 +++++----- crates/busbar/tests/hook_parity_driver/rig.rs | 75 ++++++------------- 20 files changed, 299 insertions(+), 137 deletions(-) rename crates/{busbar/tests/hook_parity_driver => busbar-plane-llm/tests/on_driver}/held.rs (99%) create mode 100644 crates/busbar-plane-llm/tests/on_driver/mod.rs rename crates/{busbar/tests/hook_parity_driver => busbar-plane-llm/tests/on_driver}/policies.rs (100%) rename crates/{busbar/tests/hook_parity_driver => busbar-plane-llm/tests/on_driver}/projection.rs (99%) rename crates/{busbar/tests/hook_parity_driver => busbar-plane-llm/tests/on_driver}/seam.rs (99%) rename crates/{busbar/tests/hook_parity_driver => busbar-plane-llm/tests/on_driver}/speech_request.json (100%) create mode 100644 crates/busbar-plane-llm/tests/on_driver/wire.rs create mode 100644 crates/busbar-plane-llm/tests/on_driver/words.txt diff --git a/crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs b/crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs index 80d07bf2c9..9a93a332de 100644 --- a/crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs +++ b/crates/busbar-kernel/tests/support/plane_driver_hook_cases.rs @@ -6,8 +6,8 @@ //! rewrite goes back to the plane (`ProjectIn::rewrite`), which applies it and answers the body the //! kernel keeps and re-pushes on every attempt; a veto wears the hook's own status and words, //! rendered by the plane (`REFUSAL_GATE`), before any attempt; a request the plane cannot project -//! is refused before any attempt. The 1.5.5 hook tests themselves run over the real llm plane in -//! the composition root's `hook_parity_driver` suite. +//! is refused before any attempt. The 1.5.5 hook tests themselves run over the real plane serving +//! the `pools` map in the composition root's `hook_parity_driver` suite. use std::sync::{Arc, Mutex}; use std::time::Duration; diff --git a/crates/busbar/tests/hook_parity_driver/held.rs b/crates/busbar-plane-llm/tests/on_driver/held.rs similarity index 99% rename from crates/busbar/tests/hook_parity_driver/held.rs rename to crates/busbar-plane-llm/tests/on_driver/held.rs index a74e0e2b7b..737861995d 100644 --- a/crates/busbar/tests/hook_parity_driver/held.rs +++ b/crates/busbar-plane-llm/tests/on_driver/held.rs @@ -19,7 +19,7 @@ use busbar_kernel::config::PolicyOnError; use busbar_kernel::hooks::{FallbackHook, ResolvedPolicy}; use serde_json::{json, Value}; -use crate::policies::capturing; +use super::policies::capturing; use crate::rig::{member, Answered, Hooks, Pool, Rig}; // ── the access amendment ───────────────────────────────────────────────────────────────────────── diff --git a/crates/busbar-plane-llm/tests/on_driver/mod.rs b/crates/busbar-plane-llm/tests/on_driver/mod.rs new file mode 100644 index 0000000000..84a302070a --- /dev/null +++ b/crates/busbar-plane-llm/tests/on_driver/mod.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE LLM PLANE'S DRIVER PROOFS: the 1.5.5 hook tests of llm origin, each under its 1.5.5 name and +//! asserting the values 1.5.5 asserted, kept here, in the plane's own crate, where the dialect names +//! they are written in belong (BUSBAR-1.6.0.md Law 5; ARCHITECT ruling on #464: the composition root +//! names zero plane or dialect instance vocabulary). +//! +//! They are proven through the plugin door: the composition root's `hook_parity_driver` suite links +//! this plane's door, loads it through the one loader, drives it on the kernel's plane driver with the +//! hook stage bound, and compiles these cases beside its rig (the root's manifest row +//! `[package.metadata.busbar.driver-proofs]`, which its `build.rs` turns into the one `#[path]` the +//! suite includes). The kernel and the loader are the root's to link, never a plane's, so the cases +//! are compiled into the root's test binary rather than this crate's: this directory has no +//! `main.rs`, and `cargo test -p busbar-plane-llm` does not build it. +//! +//! Development-only, like the switch it proves: compiled only under the plane's fold switch +//! (`llm-on-driver`). + +pub(crate) mod held; +pub(crate) mod policies; +pub(crate) mod projection; +pub(crate) mod seam; +pub(crate) mod wire; diff --git a/crates/busbar/tests/hook_parity_driver/policies.rs b/crates/busbar-plane-llm/tests/on_driver/policies.rs similarity index 100% rename from crates/busbar/tests/hook_parity_driver/policies.rs rename to crates/busbar-plane-llm/tests/on_driver/policies.rs diff --git a/crates/busbar/tests/hook_parity_driver/projection.rs b/crates/busbar-plane-llm/tests/on_driver/projection.rs similarity index 99% rename from crates/busbar/tests/hook_parity_driver/projection.rs rename to crates/busbar-plane-llm/tests/on_driver/projection.rs index 4665910ba7..a32584188a 100644 --- a/crates/busbar/tests/hook_parity_driver/projection.rs +++ b/crates/busbar-plane-llm/tests/on_driver/projection.rs @@ -13,7 +13,7 @@ use serde_json::{json, Value}; -use crate::policies::{capturing, CapturedReq}; +use super::policies::{capturing, CapturedReq}; use crate::rig::{member, Hooks, Pool, Rig}; /// The fixed non-content marker shown in place of opaque provider-encrypted reasoning. @@ -735,7 +735,7 @@ async fn apply_rewrite_to_body_echoes_redacted_marker_as_visible_text() { assert_eq!(prompt(&c).1[0].1, MARKER); // A hook that echoes exactly what it was projected (the common "pass through" rewrite shape). - let echo = std::sync::Arc::new(crate::policies::RewritingGate(vec![json!({ + let echo = std::sync::Arc::new(super::policies::RewritingGate(vec![json!({ "role": "assistant", "content": MARKER, })])); diff --git a/crates/busbar/tests/hook_parity_driver/seam.rs b/crates/busbar-plane-llm/tests/on_driver/seam.rs similarity index 99% rename from crates/busbar/tests/hook_parity_driver/seam.rs rename to crates/busbar-plane-llm/tests/on_driver/seam.rs index 49b0fc7bbc..b10da639dd 100644 --- a/crates/busbar/tests/hook_parity_driver/seam.rs +++ b/crates/busbar-plane-llm/tests/on_driver/seam.rs @@ -14,7 +14,7 @@ use busbar_kernel::hooks::{FallbackHook, ResolvedPolicy}; use busbar_kernel::plane_driver::{CallerKey, Constraint, Restrict}; use serde_json::{json, Value}; -use crate::policies::{ +use super::policies::{ canned_gate, capturing, rewriting, wait_for_tap_body, webhook_tap, Canned, CannedGate, ErroringPolicy, }; @@ -65,7 +65,7 @@ async fn run( send_user: bool, reject: Option<(u16, String)>, v: Value, -) -> (crate::rig::Answered, Option<crate::policies::CapturedReq>) { +) -> (crate::rig::Answered, Option<super::policies::CapturedReq>) { let (seen, policy) = capturing(send_prompt, send_user, reject); let rig = Rig::new( one(), @@ -672,7 +672,7 @@ async fn max_tokens_saturates_not_wraps() { } /// A rig whose verified caller has `key`, and whose base policy is a `send_user` capture. -async fn identity_seen(key: Option<CallerKey>) -> crate::policies::CapturedReq { +async fn identity_seen(key: Option<CallerKey>) -> super::policies::CapturedReq { let (seen, policy) = capturing(false, true, None); let mut callers = Callers::default(); if let Some(key) = key { diff --git a/crates/busbar/tests/hook_parity_driver/speech_request.json b/crates/busbar-plane-llm/tests/on_driver/speech_request.json similarity index 100% rename from crates/busbar/tests/hook_parity_driver/speech_request.json rename to crates/busbar-plane-llm/tests/on_driver/speech_request.json diff --git a/crates/busbar-plane-llm/tests/on_driver/wire.rs b/crates/busbar-plane-llm/tests/on_driver/wire.rs new file mode 100644 index 0000000000..4b52c3ade8 --- /dev/null +++ b/crates/busbar-plane-llm/tests/on_driver/wire.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! WHAT THE ROOT'S RIG SPEAKS TO THIS PLANE: the settings a case's pools become (one far end per +//! dialect, every member a model), each far end's answer, and the one plain request the rig's `fire` +//! posts (an Anthropic Messages request). The rig itself names no plane and no dialect; these are +//! this plane's words, so they live here. + +use serde_json::{json, Value}; + +use crate::rig::{Member, Pool}; + +/// The far end a member is served by unless its case says otherwise: the Anthropic one. +pub const DEFAULT_PROVIDER: &str = "ant"; + +/// The target of the rig's plain unit: Anthropic Messages. +pub const FIRE_TARGET: &str = "/v1/messages"; + +/// The request fields of the rig's plain unit. +pub const FIRE_FIELDS: &[(&str, &str)] = &[ + ("content-type", "application/json"), + ("anthropic-version", "2023-06-01"), +]; + +/// The plane's settings for these pools: one far end per protocol, every member a model. +pub fn settings(pools: &[&Pool]) -> Vec<u8> { + let mut models = serde_json::Map::new(); + let mut sections = serde_json::Map::new(); + for p in pools { + let names: Vec<Value> = p.members.iter().map(|m| json!(m.model)).collect(); + sections.insert(p.name.to_string(), json!({ "members": names })); + for m in &p.members { + models.insert(m.model.to_string(), json!({ "provider": m.provider })); + } + } + serde_json::to_vec(&json!({ + "providers": { + "ant": { "protocol": "anthropic", "base_url": "https://anthropic.example" }, + "oai": { "protocol": "openai", "base_url": "https://openai.example" }, + "brk": { "protocol": "bedrock", "base_url": "https://bedrock.example" }, + "goo": { "protocol": "gemini", "base_url": "https://gemini.example" }, + "coh": { "protocol": "cohere", "base_url": "https://cohere.example" }, + }, + "models": models, + "pools": sections, + })) + .expect("settings serialize") +} + +/// What a member's far end answers one attempt, in its far end's dialect. +pub fn answer_of(m: &Member) -> Value { + match m.provider { + "oai" => json!({ + "id": "chatcmpl-1", "object": "chat.completion", "model": m.label, + "choices": [{"index": 0, "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop"}], + "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2} + }), + _ => json!({ + "id": "msg_1", "type": "message", "role": "assistant", "model": m.label, + "content": [{"type": "text", "text": "hi"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 1, "output_tokens": 1} + }), + } +} diff --git a/crates/busbar-plane-llm/tests/on_driver/words.txt b/crates/busbar-plane-llm/tests/on_driver/words.txt new file mode 100644 index 0000000000..a660d5f5fb --- /dev/null +++ b/crates/busbar-plane-llm/tests/on_driver/words.txt @@ -0,0 +1,14 @@ +# THE LLM PLANE'S DOOR WORDS, read as data by the composition root's pools-door cells under the fold +# switch (crates/busbar src/root/tests/serve_hook_seats.rs `word`, and the door cells of +# serve_tests.rs): `key = value`, one per line, `#` a comment. The root names no plane and no dialect +# (ARCHITECT ruling, #464), so the dialects those cells speak are this plane's words, kept here. +# +# The dialect the cells' members and callers speak: the caller's chat completions, and each member's +# far end, unless a cell names another. +dialect = openai +# A far dialect that cannot carry a control the caller set (`logit_bias`), so a TRANSLATE attempt +# writes 1.5.5's `egress.control_unrepresentable` rows (`<control> on <dialect>`, +# `<path> from <dialect>`), and that dialect's answer: a message whose usage carries a member the +# caller's dialect has no form for. +untranslatable_dialect = anthropic +untranslatable_answer = {"id":"msg_1","type":"message","role":"assistant","model":"m0","content":[{"type":"text","text":"hi"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":1,"output_tokens":1,"output_tokens_details":{"reasoning_tokens":0}}} diff --git a/crates/busbar/Cargo.toml b/crates/busbar/Cargo.toml index 7abf2c7f08..a691ba2340 100644 --- a/crates/busbar/Cargo.toml +++ b/crates/busbar/Cargo.toml @@ -486,6 +486,16 @@ auth-admin-tokens = "busbar-auth-admin-tokens" plane-a2a = "busbar-a2a" plane-streaming = "busbar-voice" +# THE DRIVER PROOFS of a development-only fold switch (a `<plane>-on-driver` feature, BUSBAR-1.6.0.md +# Part 3 section 12 "The switch"): cargo feature -> the plane crate whose `tests/on_driver/` holds the +# plane-origin hook cases (`mod.rs`) and the words the root's door cells read as data (`words.txt`). +# Test builds only: `build.rs` emits `$OUT_DIR/driver_proofs.rs` (the `#[path]` that +# `tests/hook_parity_driver` compiles beside its rig) and `$OUT_DIR/driver_words.rs` +# (`DRIVER_WORDS`), so the composition root's sources name neither the plane nor its dialects: the +# cases are written in the plane's own words and live in the plane's crate (ARCHITECT ruling, #464). +[package.metadata.busbar.driver-proofs] +llm-on-driver = "busbar-plane-llm" + [package.metadata.busbar.root-units] # The node's own row, keyed to the node (not a feature): it compiles in every build, so its card # repricer and book binding run in every build (ARCHITECT Q1). diff --git a/crates/busbar/build.rs b/crates/busbar/build.rs index 2483ddb289..df3c488700 100644 --- a/crates/busbar/build.rs +++ b/crates/busbar/build.rs @@ -200,6 +200,16 @@ fn main() { println!("cargo::rustc-cfg=linked_fold_on_driver"); } + // THE DRIVER PROOFS OF THE FOLD SWITCH (tests only include them): the enabled + // `[package.metadata.busbar.driver-proofs]` row's plane-owned cases (`mod proofs`, which the + // `hook_parity_driver` suite compiles beside its rig) and the words its door cells read as data + // (`DRIVER_WORDS`), both at the row's crate's own `tests/on_driver/`, so neither the suite nor + // the root's door cells spell the plane under proof or its dialects. + let (proofs, words) = driver_proofs_source(&manifest, &enabled); + let out_dir = std::path::PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); + std::fs::write(out_dir.join("driver_proofs.rs"), proofs).expect("write driver_proofs.rs"); + std::fs::write(out_dir.join("driver_words.rs"), words).expect("write driver_words.rs"); + // THE LINKED WIRES FOR THE INTEGRATION TESTS (tests only include it): each linked transport row's // entry (`KEY`, `COMPOSES_OVER`, `SESSION`, `build`), the rows this build leaves unlinked, and the same // bottom-up fold the boot seal runs — so a test addresses a wire by the key its row declares and @@ -375,6 +385,55 @@ fn node_plane_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> String { out } +/// `(proofs, words)` for the one enabled `[package.metadata.busbar.driver-proofs]` row (cargo feature +/// -> the plane crate whose fold switch it is): `proofs` is `pub(crate) mod proofs;` at that crate's +/// `tests/on_driver/mod.rs`, `words` is `DRIVER_WORDS`, that directory's `words.txt`. The crate's +/// directory is its `[dependencies]` row's `path`. No row enabled: no module and empty words; more +/// than one: a compile error in the one suite that includes them, since a build proves one switch. +fn driver_proofs_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> (String, String) { + let head = + "// @generated by build.rs from Cargo.toml `[package.metadata.busbar.driver-proofs]`.\n"; + let rows: Vec<(String, String)> = + metadata_map(manifest, "package.metadata.busbar.driver-proofs") + .into_iter() + .filter(|(feature, _)| enabled(feature)) + .collect(); + let mut proofs = String::from(head); + let mut words = String::from(head); + match rows.as_slice() { + [] => words.push_str("#[allow(dead_code)]\npub(crate) const DRIVER_WORDS: &str = \"\";\n"), + [(feature, krate)] => { + let dependency = metadata_value(manifest, "dependencies", krate).unwrap_or_else(|| { + panic!("driver-proofs row `{feature}`: `{krate}` is no dependency") + }); + let path = dependency + .split_once("path") + .and_then(|(_, rest)| rest.split('"').nth(1)) + .unwrap_or_else(|| { + panic!("driver-proofs row `{feature}`: `{krate}` has no `path`") + }); + let at = std::path::Path::new(&env::var("CARGO_MANIFEST_DIR").expect("manifest dir")) + .join(path) + .join("tests/on_driver"); + proofs.push_str(&format!( + "#[path = {:?}]\npub(crate) mod proofs;\n", + at.join("mod.rs").display().to_string() + )); + words.push_str(&format!( + "#[allow(dead_code)]\npub(crate) const DRIVER_WORDS: &str = include_str!({:?});\n", + at.join("words.txt").display().to_string() + )); + } + _ => { + proofs.push_str( + "compile_error!(\"more than one driver-proofs row is enabled; a build proves one fold switch\");\n", + ); + words.push_str("#[allow(dead_code)]\npub(crate) const DRIVER_WORDS: &str = \"\";\n"); + } + } + (proofs, words) +} + /// `static SERVED_WITNESSES` over `<crate>::testkit::SERVED` for every /// `[package.metadata.busbar.served-witness]` row whose cargo feature is enabled, in manifest order. /// A build with the feature off has no row, exactly as it has no served leg for that plane. diff --git a/crates/busbar/src/root/door_steps.rs b/crates/busbar/src/root/door_steps.rs index e0ab084795..3ce78bb841 100644 --- a/crates/busbar/src/root/door_steps.rs +++ b/crates/busbar/src/root/door_steps.rs @@ -2442,7 +2442,7 @@ fn base_url_host(base_url: &str) -> &str { /// The dotted label of `host` right after the first of `after`, when it reads as a dashed name /// ending in a number (three parts or more, every part before the last alphabetic, the last all -/// digits: the shape the dialect's own rule accepted, 1.5.5 `derive_sigv4_region`). +/// digits: the shape the dialect's own rule accepted, 1.5.5's region derivation). fn host_label<'h>(host: &'h str, after: &[&str]) -> Option<&'h str> { let shaped = |label: &str| { let parts: Vec<&str> = label.split('-').collect(); diff --git a/crates/busbar/src/root/model_egress.rs b/crates/busbar/src/root/model_egress.rs index eb8d3ad6bf..513d41f81c 100644 --- a/crates/busbar/src/root/model_egress.rs +++ b/crates/busbar/src/root/model_egress.rs @@ -462,10 +462,11 @@ impl Breaker for DirectCells { token: &Pass<Route>, ) -> (Classified, bool) { let classified = self.classify(destination, status); - let code = status - .code - .filter(|c| c.namespace == busbar_contract::transport::registry::status_ns::HTTP) - .map(|c| c.code); + // The far end's number in the kernel's first reserved numbering (the one the previous + // release's ladder reads its 429 and 4xx in), read as the kernel's far end reads it. + let code = status.code.and_then(|c| { + c.in_namespace(busbar_contract::transport::registry::status_ns::RESERVED[0]) + }); let cell = self.cell(pool); let lane = lane_of(destination); let tripped = match (classified.outcome, code) { diff --git a/crates/busbar/src/root/tests/door_steps.rs b/crates/busbar/src/root/tests/door_steps.rs index 6a5dd89671..93aa1384ee 100644 --- a/crates/busbar/src/root/tests/door_steps.rs +++ b/crates/busbar/src/root/tests/door_steps.rs @@ -1046,6 +1046,12 @@ pub(crate) mod tool_door { .unwrap_or_else(|| panic!("fixtures/tool_door_wire.txt has no `{key}` row")) } + /// A fixture sentence with its `{PLANE}` filled: the plane's own key (`plane_key`), upper-cased, + /// as the plane's sentences spell it. + pub(crate) fn sentence(key: &str) -> String { + surface(key).replace("{PLANE}", &surface("plane_key").to_ascii_uppercase()) + } + /// The section the tool door owns beside its settings: its endpoint block, as the fixture /// states it (the root spells no plane's section). pub(crate) fn endpoint_section() -> &'static str { diff --git a/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt b/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt index 3637476627..8529939751 100644 --- a/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt +++ b/crates/busbar/src/root/tests/fixtures/tool_door_wire.txt @@ -41,6 +41,7 @@ audit_action_tool_call = mcp_tool.call audit_resource_tool = mcp_tool # The plane's refusal words for an exhausted walk, byte for byte as predev served them (tool_door.rs # RefusalSlot; the `*_in_predevs_bytes` cells in serve_tests.rs): a tripped server's sentence -# ({server} and {wait} filled by the cell), and a task continuation the walk refused. -tripped_server_message = MCP server `{server}` is unavailable: its circuit breaker is open after repeated failures; busbar did not dispatch this call. Retry after {wait}s. -continuation_refused_error = {"code":-32603,"message":"the MCP upstream call failed: breaker_open"} +# ({server} and {wait} filled by the cell), and a task continuation the walk refused. {PLANE} is the +# plane's own key above (`plane_key`), upper-cased, as its sentences spell it. +tripped_server_message = {PLANE} server `{server}` is unavailable: its circuit breaker is open after repeated failures; busbar did not dispatch this call. Retry after {wait}s. +continuation_refused_error = {"code":-32603,"message":"the {PLANE} upstream call failed: breaker_open"} diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs index eb8f0fd53b..258f021a5c 100644 --- a/crates/busbar/src/root/tests/serve_hook_seats.rs +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -1,17 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! THE SEAT ORDER OF THE REQUEST-STAGE HOOKS, ON THE DOOR (ported from the predev-only `busbar-llm` -//! test `hook_seat_order_tests`, BUSBAR-1.6.0.md Part 5 F25: before any engine deletion every test -//! names its new home). The llm plane served through its door, end to end: a keyed caller behind -//! the data listener's auth gate, admitted and charged by the door's money steps, its unit's hooks -//! bound by the kernel's hook stage (the global rewrite, the request-stage tap, a decision gate and -//! the candidate tap), its far end a real loopback upstream through the process connector. The +//! THE SEAT ORDER OF THE REQUEST-STAGE HOOKS, ON THE DOOR (ported from the predev-only engine test +//! `hook_seat_order_tests`, BUSBAR-1.6.0.md Part 5 F25: before any engine deletion every test names +//! its new home). The plane serving the `pools` map, served through its door end to end: a keyed +//! caller behind the data listener's auth gate, admitted and charged by the door's money steps, its +//! unit's hooks bound by the kernel's hook stage (the global rewrite, the request-stage tap, a gate +//! and the candidate tap), its far end a real loopback upstream through the process connector. The //! admission door charges the request first, then the global rewrite runs, then the request-stage -//! tap observes the REWRITTEN request, then the decision gate decides, and only after the gate does -//! the candidate tap fire. A gate reject refunds the billable request (the fee base) but keeps the +//! tap observes the REWRITTEN request, then the gate decides, and only after the gate does the +//! candidate tap fire. A gate reject refunds the billable request (the fee base) but keeps the //! admission count. //! +//! The root names no plane and no dialect: the dialects these cells speak are the plane's own door +//! words (`DRIVER_WORDS`, its `tests/on_driver/words.txt`), read as data through [`word`]. +//! //! Every seat is proven structurally rather than by wall-clock order where the seat is a detached //! task (taps are fire-and-forget): the rewrite hook reads the ledger AT the moment it runs, the //! request tap's payload carries the rewritten prompt, and a rejecting gate stops everything seated @@ -46,6 +49,24 @@ use crate::root::plane_node::{Node, NodeEndPost}; // legacy row's test seams the registry rows that own the `pools:`/`models:` sections come from. include!(concat!(env!("OUT_DIR"), "/node_plane.rs")); +// The words of the plane under proof that these cells read as data (`DRIVER_WORDS`: the plane's own +// `tests/on_driver/words.txt`, from the manifest's driver-proofs row). +include!(concat!(env!("OUT_DIR"), "/driver_words.rs")); + +/// One of the plane's door words (`key = value` per line, `#` a comment): what the root's cells +/// would otherwise have to spell, a dialect of the plane under proof. +pub(super) fn word(key: &str) -> &'static str { + DRIVER_WORDS + .lines() + .map(str::trim) + .filter(|l| !l.is_empty() && !l.starts_with('#')) + .find_map(|l| { + let (k, v) = l.split_once('=')?; + (k.trim() == key).then(|| v.trim()) + }) + .unwrap_or_else(|| panic!("the plane's door words have no `{key}` row")) +} + /// THE LINKED DOOR THAT SERVES THE `pools` MAP, found by what its Statement declares (the composition /// root names no plane): every linked plane door bound on a probe dispatcher of its own, the one /// whose declaring section is `pools` kept. @@ -93,7 +114,7 @@ static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = /// The marker the rewrite hook plants in the prompt; a tap payload carrying it saw the rewrite. pub(super) const REWRITTEN: &str = "rewritten-by-the-global-rewrite-seat"; -/// The far end's answer: an openai chat completion. +/// The far end's answer: a chat completion in the door words' `dialect`. const ANSWER: &str = r#"{"id":"chatcmpl-1","object":"chat.completion","created":0,"model":"m0","choices":[{"index":0,"message":{"role":"assistant","content":"hi"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}"#; /// One probe policy playing one seat: it appends its seat name to the shared log when it runs and @@ -427,8 +448,8 @@ pub(super) struct RigOpts<'a> { pub pooled: Option<usize>, /// The pools the key may reach (`None`: every pool). pub allowed_pools: Option<&'a [&'a str]>, - /// The members' dialect (`None`: `openai`). - pub dialect: Option<&'a str>, + /// The members' dialect (`None`: the door words' `dialect`). + pub dialect: Option<&'static str>, /// Each pool member states a tier `t<i>` and a tag `g<i>`, and the generation declares the /// three candidate catalog signals (breaker state, error rate, p95 latency). pub described: bool, @@ -442,7 +463,7 @@ pub(super) struct RigOpts<'a> { pub lane_budget: Option<i64>, } -/// THE LLM DOOR, SERVED END TO END, as production composes it: the data router built with the +/// THE POOLS DOOR, SERVED END TO END, as production composes it: the data router built with the /// door's claims, a keyed caller's token, the generation's App and the node's book. pub(super) struct DoorRig { /// The data router. @@ -593,7 +614,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { ) .expect("the connector builds"); - // THE LLM DOOR, linked, bound through the loader's one load, its needs declared on the + // THE POOLS DOOR, linked, bound through the loader's one load, its needs declared on the // connector. let dispatcher = Arc::new(Dispatcher::new(DispatchConfig::default())); let row = LinkedRow::of(pools_door()).expect("the door states itself"); @@ -611,7 +632,8 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { ) .expect("the linked door binds"); - // THE DEPLOYMENT: one openai provider per member, one model per provider, pool `p` over them. + // THE DEPLOYMENT: one provider per member (in the members' dialect), one model per provider, + // pool `p` over them. let key_file = std::env::temp_dir().join(format!( "busbar-hook-seats-{}-{instance}", std::process::id() @@ -649,7 +671,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { for (i, (port, _)) in opts.members.iter().enumerate() { defs_yaml.push_str(&format!( "oai{i}:\n protocol: {}\n base_url: 'http://127.0.0.1:{port}'\n", - opts.dialect.unwrap_or("openai") + opts.dialect.unwrap_or(word("dialect")) )); } let defs: HashMap<String, busbar_kernel::config::ProviderDef> = @@ -732,10 +754,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { for (i, (port, _)) in opts.members.iter().enumerate() { let lane = busbar_kernel::test_support::LaneSpec::new( &format!("m{i}"), - match opts.dialect { - Some("anthropic") => "anthropic", - _ => "openai", - }, + opts.dialect.unwrap_or(word("dialect")), &format!("http://127.0.0.1:{port}"), ); builder = builder.lane(match opts.lane_budget { @@ -889,7 +908,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { } } -/// ONE KEYED REQUEST through the llm door with the four probed seats installed, a rejecting gate +/// ONE KEYED REQUEST through the pools door with the four probed seats installed, a rejecting gate /// when `reject_at_gate`. async fn run(instance: &'static str, reject_at_gate: bool) -> Ran { let port = far_end().await; @@ -924,7 +943,7 @@ impl DoorRig { } } -/// A rejecting decision gate: the admission charge, the rewrite and the request tap all happened +/// A rejecting gate: the admission charge, the rewrite and the request tap all happened /// before it; the candidate tap never fires; the billable request is refunded and the admission /// count is kept. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] diff --git a/crates/busbar/src/root/tests/serve_tests.rs b/crates/busbar/src/root/tests/serve_tests.rs index eb7a65744e..ff5aed6c1d 100644 --- a/crates/busbar/src/root/tests/serve_tests.rs +++ b/crates/busbar/src/root/tests/serve_tests.rs @@ -60,7 +60,6 @@ use crate::root::plane_node::{Node, NodeEndPost}; #[cfg(feature = "plane-decisions")] /// The deployment's dated card history the served unit is pinned to at its door: one entry, no /// price (billing off: the counts are the unit's fact and price at nothing). -#[cfg(feature = "plane-decisions")] static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = std::sync::LazyLock::new(|| { let holder = crate::root::kernel::RootHistory::default(); @@ -77,28 +76,23 @@ static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = #[cfg(feature = "plane-decisions")] /// The decisions plane's one claim, with one model configured. -#[cfg(feature = "plane-decisions")] pub(super) const CLAIMED: &str = "/v1/systemone"; #[cfg(feature = "plane-decisions")] /// The provider's credential, as its file holds it. -#[cfg(feature = "plane-decisions")] const CREDENTIAL: &str = "sk-door-test"; #[cfg(feature = "plane-decisions")] /// 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}}"#; #[cfg(feature = "plane-decisions")] /// A far end's transient failure, as [`far_end_failing_first`] answers it. -#[cfg(feature = "plane-decisions")] const UNAVAILABLE: &str = r#"{"error":"unavailable"}"#; #[cfg(feature = "plane-decisions")] /// 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")] pub(super) async fn send( router: &axum::Router, path: &str, @@ -122,7 +116,6 @@ pub(super) async fn send( #[cfg(feature = "plane-decisions")] /// 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")] pub(super) async fn far_end() -> (u16, tokio::sync::mpsc::UnboundedReceiver<String>) { far_end_failing_first(0).await } @@ -130,7 +123,6 @@ pub(super) async fn far_end() -> (u16, tokio::sync::mpsc::UnboundedReceiver<Stri #[cfg(feature = "plane-decisions")] /// [`far_end`], but its first `failures` connections are answered with a bare transient failure /// (503, no `Retry-After`) instead of [`ANSWER`]. -#[cfg(feature = "plane-decisions")] async fn far_end_failing_first( failures: usize, ) -> (u16, tokio::sync::mpsc::UnboundedReceiver<String>) { @@ -227,7 +219,6 @@ async fn far_end_answering(script: Script) -> (u16, tokio::sync::mpsc::Unbounded /// 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. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -#[cfg(feature = "plane-decisions")] async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { let _one = PUBLISHING.lock().await; let instance = "serve-door-served"; @@ -448,7 +439,6 @@ async fn a_claimed_request_is_served_through_the_door_and_its_money_posted() { /// 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. #[tokio::test] -#[cfg(feature = "plane-decisions")] async fn the_data_router_built_with_the_door_serves_only_its_claims() { let _one = PUBLISHING.lock().await; let instance = "serve-door"; @@ -616,9 +606,9 @@ mod tools_door { use crate::root::door_steps::tests::hook_parity; use crate::root::door_steps::tests::tool_door::{ - protocol_version, rig_tools, send_as, send_headed, surface, three_tools, tool_digest, - tool_listing, tool_server, tool_server_listing, tool_server_replying, Footing, Rig, STALL, - TOOL_DESCRIPTION, + protocol_version, rig_tools, send_as, send_headed, sentence, surface, three_tools, + tool_digest, tool_listing, tool_server, tool_server_listing, tool_server_replying, Footing, + Rig, STALL, TOOL_DESCRIPTION, }; use crate::root::serve::planes_tests::{Published, PUBLISHING}; @@ -2145,7 +2135,7 @@ mod tools_door { \"retry_after_ms\":{},\"server\":\"flaky\"}},\"message\":\"{}\"}},\"id\":41,\ \"jsonrpc\":\"2.0\"}}", wait * 1000, - surface("tripped_server_message") + sentence("tripped_server_message") .replace("{server}", "flaky") .replace("{wait}", &wait.to_string()) ), @@ -2244,7 +2234,7 @@ mod tools_door { assert_eq!(settled["result"]["status"], "failed", "{settled}"); assert_eq!( settled["result"]["error"].to_string(), - surface("continuation_refused_error"), + sentence("continuation_refused_error"), "{settled}" ); } @@ -2287,7 +2277,7 @@ mod tools_door { assert_eq!(settled["result"]["status"], "failed", "{settled}"); assert_eq!( settled["result"]["error"].to_string(), - surface("continuation_refused_error"), + sentence("continuation_refused_error"), "{settled}" ); assert!(drain(&mut heard).is_empty(), "nothing reached the server"); @@ -2779,8 +2769,8 @@ async fn a_plane_stating_no_breaker_fact_keeps_the_default_bench() { // (`super::hook_seat_tests::rig`), and reads the capability where the kernel keeps it. #[cfg(linked_fold_on_driver)] use super::hook_seat_tests::{ - chunk, far_end_answering as seat_far_end, far_end_scripted, rig, RigOpts, Script as SeatScript, - REWRITTEN, + chunk, far_end_answering as seat_far_end, far_end_scripted, rig, word as door_word, RigOpts, + Script as SeatScript, REWRITTEN, }; #[cfg(linked_fold_on_driver)] use super::planes_tests::{Published as Withdrawn, PUBLISHING as ONE_PUBLISHER}; @@ -3037,7 +3027,7 @@ async fn the_pools_door_seals_one_audit_record_per_unit() { assert_eq!(rig.audit_records(), 1, "one record for the one unit"); } -/// HOOKS-GATE: a decision gate refuses the door's traffic before dispatch. +/// HOOKS-GATE: a rejecting gate refuses the door's traffic before dispatch. #[cfg(linked_fold_on_driver)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn the_pools_doors_gate_hook_refuses_before_dispatch() { @@ -3123,10 +3113,6 @@ async fn the_pools_doors_catalogue_shows_a_restricted_key_only_what_it_reaches() ); } -/// An anthropic message whose usage carries a member the openai caller's dialect has no form for. -#[cfg(linked_fold_on_driver)] -const ANTHROPIC_ANSWER: &str = r#"{"id":"msg_1","type":"message","role":"assistant","model":"m0","content":[{"type":"text","text":"hi"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":1,"output_tokens":1,"output_tokens_details":{"reasoning_tokens":0}}}"#; - /// THE DROPPED-CONTROLS AUDIT ROW ON THE DOOR (ARCHITECT, Q128 gap): a TRANSLATE attempt that /// cannot carry a control the caller set writes 1.5.5's `egress.control_unrepresentable` row, /// outcome `degraded`, `<control> on <dialect>`; an answer member the caller's dialect has no form @@ -3138,12 +3124,15 @@ async fn the_pools_door_audits_a_control_the_far_dialect_cannot_carry() { let _one = ONE_PUBLISHER.lock().await; let instance = "serve-door-dropped-controls"; let _published = Withdrawn(instance); - let far = seat_far_end(200, ANTHROPIC_ANSWER).await; + // The far dialect is the plane's own door word (`untranslatable_dialect`), and so is its answer: + // a message whose usage carries a member the caller's dialect has no form for. + let far_dialect = door_word("untranslatable_dialect"); + let far = seat_far_end(200, door_word("untranslatable_answer")).await; let rig = rig( instance, RigOpts { members: &[(far.port, 1)], - dialect: Some("anthropic"), + dialect: Some(far_dialect), ..RigOpts::default() }, ) @@ -3170,7 +3159,7 @@ async fn the_pools_door_audits_a_control_the_far_dialect_cannot_carry() { .await; assert_eq!(status, 200); assert_eq!( - rows("logit_bias on anthropic"), + rows(format!("logit_bias on {far_dialect}").as_str()), 0, "nothing set, nothing dropped" ); @@ -3185,12 +3174,12 @@ async fn the_pools_door_audits_a_control_the_far_dialect_cannot_carry() { .await; assert_eq!(status, 200); assert_eq!( - rows("logit_bias on anthropic"), + rows(format!("logit_bias on {far_dialect}").as_str()), 1, "the dropped control, one row" ); assert!( - rows("usage.output_tokens_details from anthropic") >= 1, + rows(format!("usage.output_tokens_details from {far_dialect}").as_str()) >= 1, "the answer member the caller's dialect cannot carry" ); } diff --git a/crates/busbar/tests/fixtures/computed_refusal_sites.txt b/crates/busbar/tests/fixtures/computed_refusal_sites.txt index ae898f1349..93ba0478d8 100644 --- a/crates/busbar/tests/fixtures/computed_refusal_sites.txt +++ b/crates/busbar/tests/fixtures/computed_refusal_sites.txt @@ -9,9 +9,9 @@ busbar/src/root/units_admin/mod.rs verbs_reason(refusal.reason) busbar/src/root/units_admin/mod.rs verbs_reason(refusal.reason) busbar-kernel/src/plane_driver/mod.rs reason busbar-kernel/src/plane_driver/mod.rs cause -# Reviewed (lane-dg-llm): the hook stage's refusal at the head of the route leg (HookVeto or +# Reviewed (lane-dg): the hook stage's refusal at the head of the route leg (HookVeto or # DecodeFailed, the unit's money finished first; K5 de3ea6acc4) and the door's destination guard at -# verify, before admission (PoolNotPermitted or NoRate; FLIP-LLM b26be1ca6d). Neither is an +# verify, before admission (PoolNotPermitted or NoRate; the door flip b26be1ca6d). Neither is an # overdraft ceiling or a stale slice. busbar-kernel/src/plane_driver/mod.rs reason busbar/src/root/door_steps.rs refusal.reason() diff --git a/crates/busbar/tests/hook_parity_driver/main.rs b/crates/busbar/tests/hook_parity_driver/main.rs index 854b7b4713..fdfe92947d 100644 --- a/crates/busbar/tests/hook_parity_driver/main.rs +++ b/crates/busbar/tests/hook_parity_driver/main.rs @@ -2,34 +2,36 @@ // Copyright (C) 2026 Busbar Inc and contributors //! THE 1.5.5 HOOK PARITY SUITE, ON THE DRIVER (`BUSBAR-1.6.0.md` Part 3, section 12, "The switch": -//! "The llm flip's gate is every hook test green on the driver — 184: the 70 of llm origin through -//! `project`, the 114 of kernel, loader and ranking origin"; Appendix A: "the hook parity suite is -//! the 184 v1.5.5 hook tests"). +//! the flip's gate is every hook test green on the driver; Appendix A: the hook parity suite is the +//! 184 v1.5.5 hook tests). //! -//! The llm-origin tests of `v1.5.5` (`crates/busbar/src/proxy/tests/hook_seam_tests.rs` and -//! `hook_opt_in_projection_tests.rs` at the tag), each under its 1.5.5 name, with the values 1.5.5 -//! asserted, run with llm served on the plane driver: the REAL llm plane door, linked and loaded +//! The plane-origin tests of `v1.5.5`, each under its 1.5.5 name, with the values 1.5.5 asserted, +//! run with the plane under proof served on the plane driver: its REAL door, linked and loaded //! through the one loader, driven by the kernel's plane driver with its hook stage bound -//! (`plane_driver::hooks`), every hook a 1.5.5 test's own in-process policy. What a hook sees of -//! the request is what the plane's `project` wrote; what the caller is answered is what the plane -//! rendered. The 114 of kernel, loader and ranking origin are unchanged by the plane's path and run -//! in their own crates (`busbar-kernel` hooks tests, the plugin loader's hook tests, the ranking -//! hook's tests). +//! (`plane_driver::hooks`), every hook a 1.5.5 test's own in-process policy. What a hook sees of the +//! request is what the plane's `project` wrote; what the caller is answered is what the plane +//! rendered. The tests of kernel, loader and ranking origin are unchanged by the plane's path and +//! run in their own crates. //! -//! Development-only, like the switch it proves: built with the plane's development-only fold switch +//! THE COMPOSITION ROOT NAMES NO PLANE (BUSBAR-1.6.0.md THE DESIGN, the root is `Family::Neutral`), +//! so this suite is only the driver half: the rig (`rig.rs`) and the kernel's shared test steps. The +//! cases, written in the plane's own dialects, live in the plane's crate and are compiled in here +//! as `proofs` from the manifest's `[package.metadata.busbar.driver-proofs]` row (`build.rs` writes +//! the one `#[path]`); the plane under proof is the one whose Statement declares the `pools` map, +//! found by what it states, never by name. +//! +//! Development-only, like the switch it proves: built with a plane's development-only fold switch //! (`linked_fold_on_driver`). #![cfg(linked_fold_on_driver)] -// The linked plane doors (`LINKED_PLANE_DOORS`), generated from the manifest: the plane under proof -// is the one whose Statement declares the `pools` map, found by what it states, never by name. +// The linked plane doors (`LINKED_PLANE_DOORS`), generated from the manifest. include!(concat!(env!("OUT_DIR"), "/linked_plane_doors.rs")); +// The plane-owned cases (`mod proofs`), generated from the manifest. +include!(concat!(env!("OUT_DIR"), "/driver_proofs.rs")); + #[path = "../../../busbar-kernel/tests/common/mod.rs"] mod common; -mod held; -mod policies; -mod projection; mod rig; -mod seam; diff --git a/crates/busbar/tests/hook_parity_driver/rig.rs b/crates/busbar/tests/hook_parity_driver/rig.rs index 3f778108db..d4e5bc3c25 100644 --- a/crates/busbar/tests/hook_parity_driver/rig.rs +++ b/crates/busbar/tests/hook_parity_driver/rig.rs @@ -1,12 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! THE RIG the hook parity suite runs on: the REAL llm plane door (linked, loaded through the one -//! loader and driven through the one dispatcher), the kernel's plane driver with the hook stage -//! bound ([`BoundHooks`]), the kernel's passing steps, a far end that walks the configured pools -//! the way the egress walk does (its candidates, the hooks' constraint, a fallback pool holding the -//! restricts), and a caller that records what it was answered. No fixture plugin: the plane is the -//! real one, the hooks are the 1.5.5 tests' own in-process policies. +//! THE RIG the hook parity suite runs on: the REAL plane door serving the `pools` map (linked, loaded +//! through the one loader and driven through the one dispatcher), the kernel's plane driver with the +//! hook stage bound ([`BoundHooks`]), the kernel's passing steps, a far end that walks the configured +//! pools the way the egress walk does (its candidates, the hooks' constraint, a fallback pool holding +//! the restricts), and a caller that records what it was answered. No fixture plugin: the plane is +//! the real one, the hooks are the 1.5.5 tests' own in-process policies. +//! +//! The rig names no plane and no dialect. What it speaks to the plane under proof (the settings its +//! pools become, each far end's answer, the request a plain unit posts) is that plane's own, in the +//! plane's crate beside its cases (`crate::proofs::wire`). use std::collections::{HashMap, VecDeque}; use std::future::Future; @@ -31,7 +35,7 @@ use busbar_plugin_loader::dispatch::{ in_head, kinds::plane::Plane, load_linked, out_head, plane_calls::PlaneInstance, Bind, DispatchConfig, Dispatcher, Frame, LinkedRow, NoSink, Plugin, }; -use serde_json::{json, Value}; +use serde_json::Value; use super::common::{cell, ctx, TestUnits}; @@ -48,12 +52,12 @@ pub struct Member { pub dead: bool, } -/// A member served by the anthropic far end. +/// A member served by the plane's default far end (`wire::DEFAULT_PROVIDER`). pub fn member(model: &'static str, label: &'static str) -> Member { Member { model, label, - provider: "ant", + provider: crate::proofs::wire::DEFAULT_PROVIDER, tags: Vec::new(), dead: false, } @@ -81,49 +85,16 @@ pub struct Pool { pub members: Vec<Member>, } -/// The plane's settings for these pools: one far end per protocol, every member a model. +/// The plane's settings for these pools, in the plane's own words. fn settings(pools: &[&Pool]) -> Vec<u8> { - let mut models = serde_json::Map::new(); - let mut sections = serde_json::Map::new(); - for p in pools { - let names: Vec<Value> = p.members.iter().map(|m| json!(m.model)).collect(); - sections.insert(p.name.to_string(), json!({ "members": names })); - for m in &p.members { - models.insert(m.model.to_string(), json!({ "provider": m.provider })); - } - } - serde_json::to_vec(&json!({ - "providers": { - "ant": { "protocol": "anthropic", "base_url": "https://anthropic.example" }, - "oai": { "protocol": "openai", "base_url": "https://openai.example" }, - "brk": { "protocol": "bedrock", "base_url": "https://bedrock.example" }, - "goo": { "protocol": "gemini", "base_url": "https://gemini.example" }, - "coh": { "protocol": "cohere", "base_url": "https://cohere.example" }, - }, - "models": models, - "pools": sections, - })) - .expect("settings serialize") + crate::proofs::wire::settings(pools) } // ── the far end ───────────────────────────────────────────────────────────────────────────────── -/// What a far end answers one attempt. +/// What a far end answers one attempt: the plane's own answer for that member's far end, whole. fn answer_of(m: &Member) -> Vec<FarPiece> { - let body = match m.provider { - "oai" => json!({ - "id": "chatcmpl-1", "object": "chat.completion", "model": m.label, - "choices": [{"index": 0, "message": {"role": "assistant", "content": "hi"}, - "finish_reason": "stop"}], - "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2} - }), - _ => json!({ - "id": "msg_1", "type": "message", "role": "assistant", "model": m.label, - "content": [{"type": "text", "text": "hi"}], - "stop_reason": "end_turn", - "usage": {"input_tokens": 1, "output_tokens": 1} - }), - }; + let body = crate::proofs::wire::answer_of(m); vec![FarPiece { bytes: serde_json::to_vec(&body).expect("answer serializes"), status: Some((200, u32::from(STATUS_SUCCESS))), @@ -540,6 +511,8 @@ fn door(dispatcher: &Dispatcher, settings: &[u8]) -> Plugin<Plane> { fmt: busbar_contract::abi::mechanism::call::BLOB_ABSENT, flags: 0, }, + providers: std::ptr::null(), + providers_len: 0, }, PlaneOpenOut { open: OpenOut { @@ -673,15 +646,13 @@ impl Rig { } } - /// One Anthropic Messages unit with every kernel step passing. + /// One plain unit (the plane's own plain request, `wire::FIRE_TARGET` with `wire::FIRE_FIELDS`) + /// with every kernel step passing. pub async fn fire(&self, body: &Value) -> Answered { self.fire_with( &TestUnits::passing(), - "/v1/messages", - &[ - ("content-type", "application/json"), - ("anthropic-version", "2023-06-01"), - ], + crate::proofs::wire::FIRE_TARGET, + crate::proofs::wire::FIRE_FIELDS, &serde_json::to_vec(body).expect("body serializes"), ) .await From fabf44cd0249ed312de21fccfee8f14438b607ff Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:04:01 -0700 Subject: [PATCH 44/49] FLIP-LLM reds: the kernel's plane row gives its slack back; the population and tracing floors re-arm at the measured count - qa/kind-isolation.toml: busbar-kernel x plane LOWERED 1175 -> 1173, predev d7929b661d's measure (the law0-base reading on #464); the drain leaves the cell there, so the row takes the slack back (Law 9: the ceiling ratchets down). - gates::population::FLOOR 941 -> 943 and tracing SCAN_FLOOR 971 -> 973: this landing adds two non-test sources (busbar/src/root/model_egress.rs, busbar-plane-llm/src/exchange/project.rs), and the one-file-short plants of settings-leak, response-header, blocking-ffi and tracing are red only when the floor sits AT the count. --- qa/kind-isolation.toml | 4 ++-- xtask/src/gates/population.rs | 8 +++++--- xtask/src/gates/tracing.rs | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/qa/kind-isolation.toml b/qa/kind-isolation.toml index c257d53e69..f03353e174 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -1794,8 +1794,8 @@ cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain [[law0]] crate = "busbar-kernel" axis = "plane" -count = "1175" -cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; LOWERED 1193 -> 1175, predev 9c0a394771's measure: the drain landed without giving its row back (Law 9: the ceiling ratchets down)" +count = "1173" +cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; LOWERED 1193 -> 1175, predev 9c0a394771's measure: the drain landed without giving its row back (Law 9: the ceiling ratchets down); LOWERED 1175 -> 1173, predev d7929b661d's measure (#464's law0-base reading): slack under the row is lowered to the measured value (Law 9)" [[law0]] crate = "busbar-kernel" diff --git a/xtask/src/gates/population.rs b/xtask/src/gates/population.rs index eedb2f8a13..6550196c88 100644 --- a/xtask/src/gates/population.rs +++ b/xtask/src/gates/population.rs @@ -33,11 +33,13 @@ 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 raised from 941 to 943 when FLIP-LLM (#464) added `busbar/src/root/model_egress.rs` and +/// `busbar-plane-llm/src/exchange/project.rs` over predev d7929b661d. A drop below 943 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 = 943; /// 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..3371ac2b4a 100644 --- a/xtask/src/gates/tracing.rs +++ b/xtask/src/gates/tracing.rs @@ -48,11 +48,13 @@ const EXCLUDE_TESTS_DIR: &str = "/tests/"; /// 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); 973 once FLIP-LLM (#464) added `busbar/src/root/model_egress.rs` and +/// `busbar-plane-llm/src/exchange/project.rs` over predev d7929b661d. A drop below 973 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 = 973; /// 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 From 7a157ea5c86750e58fb396acf065a2c289a423a3 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:45:00 -0700 Subject: [PATCH 45/49] FLIP-LLM: the kernel's plane row stays at predev's 1175 Lowering it (fabf44cd02) armed the whole [[law0]] row on this candidate, and the row is red at predev's own tree on cells this landing does not touch (busbar x instance:secret, x plane, x vendor, busbar-plugin-loader x transport), so Law 9 refused it as armed red. This landing's law0 figure is predev's again (busbar x plane/transport/vendor and busbar-kernel x plane at the merge-base's measure); the kernel cell's slack is predev's, and its row is left to the drain that owns it. --- 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 f03353e174..c257d53e69 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -1794,8 +1794,8 @@ cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain [[law0]] crate = "busbar-kernel" axis = "plane" -count = "1173" -cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; LOWERED 1193 -> 1175, predev 9c0a394771's measure: the drain landed without giving its row back (Law 9: the ceiling ratchets down); LOWERED 1175 -> 1173, predev d7929b661d's measure (#464's law0-base reading): slack under the row is lowered to the measured value (Law 9)" +count = "1175" +cite = "measured on predev f830faa691 (ARCHITECT 2026-10-07, Law 9); the drain is THE DESIGN §1 / DoD item 2: core names no instance; LOWERED 1193 -> 1175, predev 9c0a394771's measure: the drain landed without giving its row back (Law 9: the ceiling ratchets down)" [[law0]] crate = "busbar-kernel" From c3a7bc55c20a43d773641818834ae4bf2482f345 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:05:33 -0700 Subject: [PATCH 46/49] busbar tests: DoorReach gets models: None; provider_routes takes the MetadataPosture at the serve_hook_seats test --- crates/busbar/src/root/tests/door_steps.rs | 1 + crates/busbar/src/root/tests/serve_hook_seats.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/busbar/src/root/tests/door_steps.rs b/crates/busbar/src/root/tests/door_steps.rs index 948abf1fd7..54f7d5e533 100644 --- a/crates/busbar/src/root/tests/door_steps.rs +++ b/crates/busbar/src/root/tests/door_steps.rs @@ -1022,6 +1022,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, + models: None, upgrades: Vec::new(), }; let section: serde_yaml::Value = diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs index 258f021a5c..950ae37ed8 100644 --- a/crates/busbar/src/root/tests/serve_hook_seats.rs +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -677,7 +677,10 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { let defs: HashMap<String, busbar_kernel::config::ProviderDef> = serde_yaml::from_str(&defs_yaml).expect("the providers"); let cfg = busbar_kernel::config::resolve(&deploy, &defs).expect("resolves"); - let providers = provider_routes(&cfg.providers); + let providers = provider_routes( + &cfg.providers, + &busbar_kernel::config_validate::MetadataPosture::default(), + ); let sections = kernel_sections(&cfg); let model_pools = crate::root::model_egress::ModelPools::of(&cfg); From 35c3f185a2d83232eb3788d2a165df77ea584ab2 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 10:02:11 -0700 Subject: [PATCH 47/49] kernel plane_driver: predev's unpresentable-credential cell after the merge: the far end's exhausted pick carries its detail, and the plane is handed the walk terminal's own words (the exhausted pool's 1.5.5 sentence, a static str), so the render is exact, identical across sources and names none of them --- crates/busbar-kernel/tests/plane_driver.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/busbar-kernel/tests/plane_driver.rs b/crates/busbar-kernel/tests/plane_driver.rs index 688903d8d4..9dc9563b89 100644 --- a/crates/busbar-kernel/tests/plane_driver.rs +++ b/crates/busbar-kernel/tests/plane_driver.rs @@ -2351,6 +2351,7 @@ impl busbar_kernel::plane_driver::FarEnd for Unpresentable { None => Pick::Exhausted { status: 503, retry_after: Some(2), + detail: "The service is temporarily overloaded. Please retry shortly.", }, }; async move { pick } @@ -2424,11 +2425,11 @@ async fn drive_over<F: busbar_kernel::plane_driver::FarEnd>( /// answers) the kernel's own resolver refuses with the operator's text, which names the source (the /// control: the markers are there to be found). A unit whose every member's credential cannot be /// presented (the auth call refused; the program never spawned) ends at the walk's terminal: the -/// plane's `refusal` is handed the one reason (`breaker_open`, class `Unreachable`) and that -/// reason's own word, and the caller's render is byte-identical whichever source failed and carries -/// none of its markers. RED: a kernel that handed the plane any operator text would fail the -/// double's own rule (a kernel refusal's text is its reason's word, else FAULT and an empty body), -/// the byte-identity across sources, and the marker scan. +/// plane's `refusal` is handed the one reason (`breaker_open`, class `Unreachable`) and the walk +/// terminal's own words (the exhausted pool's 1.5.5 sentence, a `&'static str` the far end's seam +/// carries: no operator text fits it), and the caller's render is byte-identical whichever source +/// failed and carries none of its markers. RED: a kernel that handed the plane any operator text +/// would fail the exact render, the byte-identity across sources, and the marker scan. #[tokio::test] async fn a_credential_that_cannot_be_presented_reaches_the_caller_as_a_fixed_class_refusal() { use busbar_contract::abi::plane::{class_of, RefusalClass}; @@ -2470,8 +2471,9 @@ async fn a_credential_that_cannot_be_presented_reaches_the_caller_as_a_fixed_cla String::from_utf8_lossy(&rendered.body) ); assert_eq!( - rendered.body, b"refused:503:breaker_open:retry=2", - "the plane is handed the fixed reason and its own word: {render}" + rendered.body, + b"refused:503:The service is temporarily overloaded. Please retry shortly.:retry=2", + "the plane is handed the fixed reason and the walk terminal's own words: {render}" ); let found = markers_in(&render, &failing); assert!( From 6200ab34a4482ffe053bfe302cd5fcea12f0ce4c Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:09:20 -0700 Subject: [PATCH 48/49] mcp tasks: a terminal state the instance holds and still owes the host answers tasks/get A continuation the kernel refuses (its walk exhausted, a tripped server) fails its task in the instance and owes the host its settle until the next create's sweep. resolve() read the host's live row back over it, so tasks/get answered `working` until another tools/call arrived. The served engine's runner answered the failure at once (`-32603`, the upstream call failed, the reason's word). The instance's unsettled terminal state is newer than the host's live row, so it answers, as the settled-handle branch beside it already did. RED before: root::serve::door_tests::tools_door::{a_tasks_exhausted_walk_fails_the_task_in_predevs_bytes, a_task_on_a_tripped_server_fails_in_predevs_bytes} never settle on predev 1617e9f019 (transplanted) and on the merge; green after. --- crates/busbar-plane-mcp/src/door_tasks.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/busbar-plane-mcp/src/door_tasks.rs b/crates/busbar-plane-mcp/src/door_tasks.rs index 516fe5023b..4237233b8e 100644 --- a/crates/busbar-plane-mcp/src/door_tasks.rs +++ b/crates/busbar-plane-mcp/src/door_tasks.rs @@ -1804,6 +1804,16 @@ fn resolve( return Resolved::Task(Box::new(task.clone())); } } + // A TERMINAL STATE THIS INSTANCE HOLDS AND STILL OWES THE HOST (a continuation the kernel + // refused, its settle owed to the next create's sweep) is newer than the live row the host + // keeps until then: the instance's own word answers, as the served engine's runner answered a + // call it could not carry at once. The host's live row would otherwise overwrite it. + if let Some(task) = held + .as_ref() + .filter(|t| t.unsettled && t.status().is_terminal()) + { + return Resolved::Task(Box::new(task.clone())); + } // A host that lists none of the plane's records: what the instance holds answers. let unlisted = |held: Option<Task>| held.map_or(Resolved::Unknown, |t| Resolved::Task(Box::new(t))); From 47d556f10b1dc7b570afd3254be49b4d5cffed4e Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:09:20 -0700 Subject: [PATCH 49/49] secret-source scan: the door serving the pools map is driven under the fold switch predev's every_linked_door_plane_is_driven names each linked door plane with no driver; the fold-on-driver build links the door serving the pools map, so it joins the scan: a provider whose api_key does not resolve refuses the composition naming its source (operator text), and one whose file is gone by an apply keeps the generation it serves and answers the caller clean of the source. The hook-seat rig composes fallibly (try_rig), takes the providers' api_key as a reference, and re-runs the root's model refresh (DoorAppliers::refresh_models) as the apply hook does. The root names no plane or dialect: the cells reach the door by its declaring section and read the far end's answer from the rig. --- crates/busbar/src/root/tests/secret_source.rs | 86 +++++++++++++++ .../busbar/src/root/tests/serve_hook_seats.rs | 102 +++++++++++++++--- 2 files changed, 174 insertions(+), 14 deletions(-) diff --git a/crates/busbar/src/root/tests/secret_source.rs b/crates/busbar/src/root/tests/secret_source.rs index edffce24e7..35c1087706 100644 --- a/crates/busbar/src/root/tests/secret_source.rs +++ b/crates/busbar/src/root/tests/secret_source.rs @@ -220,6 +220,8 @@ fn driven() -> Vec<String> { )]; #[cfg(linked_section_decisions)] names.push(super::door_tests::decisions_name()); + #[cfg(linked_fold_on_driver)] + names.push(statement_name(super::hook_seat_tests::pools_door())); names } @@ -429,6 +431,90 @@ async fn decisions_a_provider_key_gone_by_an_apply_reaches_the_caller_without_it assert_clean(instance, status, &headers, &body, &secret); } +/// THE POOLS 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_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn pools_door_an_unresolved_provider_key_refuses_the_boot_naming_its_source() { + use super::hook_seat_tests::{far_end_answering, try_rig, RigOpts, ANSWER}; + let _one = PUBLISHING.lock().await; + let instance = "pools-key-compose"; + let _published = Published(instance); + let path = missing(instance); + let secret = SecretRef::file(path.clone()); + refused_naming_its_source(&secret); + let far = far_end_answering(200, ANSWER).await; + let api_key = format!("{{ file: '{path}' }}"); + let Err(refusal) = try_rig( + instance, + RigOpts { + members: &[(far.port, 1)], + api_key: Some(&api_key), + ..RigOpts::default() + }, + ) + .await + 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}" + ); + assert_eq!( + far.hits.load(std::sync::atomic::Ordering::SeqCst), + 0, + "nothing reached the far end" + ); +} + +/// THE POOLS 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 is +/// served by it, its render clean of the source. +#[cfg(linked_fold_on_driver)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn pools_door_a_provider_key_gone_by_an_apply_reaches_the_caller_without_its_source() { + use super::hook_seat_tests::{far_end_answering, rig, RigOpts, ANSWER}; + let _one = PUBLISHING.lock().await; + let instance = "pools-key-apply"; + let _published = Published(instance); + let path = missing(instance); + std::fs::write(&path, "sk-secret-source-test").expect("the credential file"); + let secret = SecretRef::file(path.clone()); + let far = far_end_answering(200, ANSWER).await; + let api_key = format!("{{ file: '{path}' }}"); + let rig = rig( + instance, + RigOpts { + members: &[(far.port, 1)], + api_key: Some(&api_key), + ..RigOpts::default() + }, + ) + .await; + let (status, _, body) = rig.chat().await; + assert_eq!( + status, + 200, + "served while it resolves: {}", + String::from_utf8_lossy(&body) + ); + std::fs::remove_file(&path).expect("the credential file goes"); + refused_naming_its_source(&secret); + let before = rig.generation(); + rig.apply(); + assert_eq!( + rig.generation(), + before, + "the door keeps the generation it serves" + ); + let (status, headers, body) = rig.chat().await; + let status = StatusCode::from_u16(status).expect("a status"); + assert_clean(instance, status, &headers, &body, &secret); + assert_eq!(status, StatusCode::OK, "the kept generation serves"); +} + // The scan's fixed-class half and its kept RED arm (status row 127). #[path = "secret_source_class.rs"] mod class; diff --git a/crates/busbar/src/root/tests/serve_hook_seats.rs b/crates/busbar/src/root/tests/serve_hook_seats.rs index 950ae37ed8..b6b67ad79b 100644 --- a/crates/busbar/src/root/tests/serve_hook_seats.rs +++ b/crates/busbar/src/root/tests/serve_hook_seats.rs @@ -70,7 +70,7 @@ pub(super) fn word(key: &str) -> &'static str { /// THE LINKED DOOR THAT SERVES THE `pools` MAP, found by what its Statement declares (the composition /// root names no plane): every linked plane door bound on a probe dispatcher of its own, the one /// whose declaring section is `pools` kept. -fn pools_door() -> busbar_contract::abi::mechanism::door::DoorFn { +pub(super) fn pools_door() -> busbar_contract::abi::mechanism::door::DoorFn { crate::LINKED .plane_doors .iter() @@ -115,7 +115,7 @@ static CARD: std::sync::LazyLock<crate::root::kernel::RootHistory> = pub(super) const REWRITTEN: &str = "rewritten-by-the-global-rewrite-seat"; /// The far end's answer: a chat completion in the door words' `dialect`. -const ANSWER: &str = r#"{"id":"chatcmpl-1","object":"chat.completion","created":0,"model":"m0","choices":[{"index":0,"message":{"role":"assistant","content":"hi"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}"#; +pub(super) const ANSWER: &str = r#"{"id":"chatcmpl-1","object":"chat.completion","created":0,"model":"m0","choices":[{"index":0,"message":{"role":"assistant","content":"hi"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}"#; /// One probe policy playing one seat: it appends its seat name to the shared log when it runs and /// (for the rewrite seat) samples the key's admission count at that instant. @@ -461,6 +461,9 @@ pub(super) struct RigOpts<'a> { pub affinity_header: Option<&'a str>, /// Each member's lifetime request budget (`None`: unlimited). pub lane_budget: Option<i64>, + /// Every provider's `api_key`, a secret reference as written (YAML) (`None`: a credential file + /// of the rig's own, gone once the door composed). + pub api_key: Option<&'a str>, } /// THE POOLS DOOR, SERVED END TO END, as production composes it: the data router built with the @@ -476,6 +479,11 @@ pub(super) struct DoorRig { pub book: Arc<std::sync::Mutex<crate::root::durability::Durability>>, /// The key's id. pub key_id: String, + /// The served door's generations, as a config apply refreshes them. + appliers: super::DoorAppliers, + /// A config apply of the same deployment onto the door ([`DoorAppliers::refresh_models`], as + /// the root's apply hook runs it), its secrets resolved anew. + reapply: Arc<dyn Fn() + Send + Sync>, log: Arc<Mutex<Vec<String>>>, request_tap: Arc<SeatProbe>, candidate_tap: Arc<SeatProbe>, @@ -485,6 +493,22 @@ pub(super) struct DoorRig { } impl DoorRig { + /// Apply the deployment again, as an operator's config apply does: the door refreshes onto it, + /// or keeps the generation it serves. + pub fn apply(&self) { + (self.reapply)(); + } + + /// The generation the door serves. + pub fn generation(&self) -> u64 { + self.appliers + .0 + .first() + .expect("one door") + .current() + .generation + } + /// Each candidate the pool's ranking policy was last shown, one line each. pub fn gate_saw(&self) -> Vec<String> { self.gate_seat.candidates.lock().unwrap().clone() @@ -592,6 +616,14 @@ impl DoorRig { /// on the process connector, over `opts`' far ends, its egress the model-serving walk over the /// kernel's lane cells, its hooks the kernel's stage with the four probed seats installed. pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { + try_rig(instance, opts) + .await + .unwrap_or_else(|e| panic!("the door serving the pools map composes: {e}")) +} + +/// [`rig`], the composition's refusal answered rather than panicked on: what the root's boot dies +/// on. +pub(super) async fn try_rig(instance: &'static str, opts: RigOpts<'_>) -> Result<DoorRig, String> { // The registry rows that own the `pools:`/`models:` sections the deployment writes. node_plane::testkit::install_test_seams(); busbar_kernel::snapshot::init(); @@ -638,12 +670,15 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { "busbar-hook-seats-{}-{instance}", std::process::id() )); - std::fs::write(&key_file, "sk-seats").expect("the credential file"); + let own_key = format!("{{ file: '{}' }}", key_file.display()); + if opts.api_key.is_none() { + std::fs::write(&key_file, "sk-seats").expect("the credential file"); + } let mut yaml = String::from("providers:\n"); for (i, _) in opts.members.iter().enumerate() { yaml.push_str(&format!( - " oai{i}:\n api_key: {{ file: '{}' }}\n", - key_file.display() + " oai{i}:\n api_key: {}\n", + opts.api_key.unwrap_or(&own_key) )); } yaml.push_str("models:\n"); @@ -666,7 +701,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { " affinity:\n mode: session\n header_name: {header}\n" )); } - let deploy = busbar_kernel::config::deploy_from_yaml_str(&yaml).expect("a deployment"); + let deploy = busbar_kernel::config::deploy_from_yaml_str(&yaml).map_err(|e| e.to_string())?; let mut defs_yaml = String::new(); for (i, (port, _)) in opts.members.iter().enumerate() { defs_yaml.push_str(&format!( @@ -676,7 +711,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { } let defs: HashMap<String, busbar_kernel::config::ProviderDef> = serde_yaml::from_str(&defs_yaml).expect("the providers"); - let cfg = busbar_kernel::config::resolve(&deploy, &defs).expect("resolves"); + let cfg = busbar_kernel::config::resolve(&deploy, &defs).map_err(|e| e.join("; "))?; let providers = provider_routes( &cfg.providers, &busbar_kernel::config_validate::MetadataPosture::default(), @@ -845,7 +880,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { crate::root::loader::dispatch::ConnTable::NoNeeds, )); let models = crate::root::model_egress::ModelServing { - pools: model_pools, + pools: model_pools.clone(), lanes: (0..opts.members.len()) .map(|i| (format!("m{i}"), i)) .collect(), @@ -857,7 +892,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { let reach = DoorReach { providers: &providers, secrets: &secrets, - auths, + auths: Arc::clone(&auths), conns: Arc::clone(&connector) as Arc<dyn PollConns>, stream_ceiling_secs: opts.stream_ceiling_secs.unwrap_or(600), models: Some(&models), @@ -871,7 +906,7 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { gov: Arc::clone(&gov), }; let sections: BTreeMap<&'static str, serde_yaml::Value> = sections; - let mut served = compose_planes( + let served = compose_planes( &[(instance.to_string(), plane)], &dispatcher, &composed_services(), @@ -883,10 +918,47 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { journal: Arc::clone(&post) as Arc<dyn busbar_kernel_egress::ports::Journal>, }), Some(&stage), - ) - .expect("the door serving the pools map composes"); + ); let _ = std::fs::remove_file(&key_file); + let mut served = served?; served.post = Some(Arc::clone(&post)); + let appliers = served.appliers(); + // THE APPLY, as the root's apply hook runs it: the same deployment's sections, the pools and + // lanes, the door's egress resealed over the providers with their secrets resolved anew. + let reapply: Arc<dyn Fn() + Send + Sync> = { + let (appliers, app, post) = (appliers.clone(), Arc::clone(&app), Arc::clone(&post)); + let (connector, auths) = (Arc::clone(&connector), Arc::clone(&auths)); + let lanes = opts.members.len(); + let stream_ceiling_secs = opts.stream_ceiling_secs.unwrap_or(600); + let (sections, providers) = (sections.clone(), providers.clone()); + Arc::new(move || { + let secrets = busbar_kernel::config::secret::SecretResolver::builtins_only(); + let models = crate::root::model_egress::ModelServing { + pools: model_pools.clone(), + lanes: (0..lanes).map(|i| (format!("m{i}"), i)).collect(), + app: { + let app = Arc::clone(&app); + Arc::new(move || Arc::clone(&app)) + }, + }; + let reach = DoorReach { + providers: &providers, + secrets: &secrets, + auths: Arc::clone(&auths), + conns: Arc::clone(&connector) as Arc<dyn PollConns>, + stream_ceiling_secs, + models: Some(&models), + upgrades: Vec::new(), + }; + appliers.refresh_models( + §ions, + &DoorEgress { + reach: &reach, + journal: Arc::clone(&post) as Arc<dyn busbar_kernel_egress::ports::Journal>, + }, + ); + }) + }; let doors = door_routes( served, || CARD.pin(), @@ -896,19 +968,21 @@ pub(super) async fn rig(instance: &'static str, opts: RigOpts<'_>) -> DoorRig { .expect("its claims mount"); let (router, _admin, _handle) = busbar_kernel::build_split_routers_serving(Arc::clone(&app), doors, 1 << 20, 0, false); - DoorRig { + Ok(DoorRig { router, token: token.expose_secret().to_string(), app, book: Arc::clone(&book.durability), key_id: key.id.to_string(), + appliers, + reapply, log, request_tap, candidate_tap, gate_seat: ranker, read, tokens, - } + }) } /// ONE KEYED REQUEST through the pools door with the four probed seats installed, a rejecting gate