From 0b6d7bc183115fcb366516cd2de205f29a984d23 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:23:26 -0700 Subject: [PATCH 1/3] kernel: the word interner (Registration, its vocabulary and freeze) moves out of busbar-contract THE DESIGN A.8 "Statelessness" (BUSBAR-1.6.0.md l.3701) and C.2 (l.3893: "Contract statics (codec.rs, records.rs:94, ids.rs:330): deleted, with host tables used instead"). The image's one vocabulary is process state, so it is the host's: busbar_kernel::vocabulary holds Registration, MAX_VOCABULARY, VOCABULARY and FROZEN, moved verbatim but for the docs that reasoned about a plugin reaching the interner (a plugin's closure is the contract, which now holds none of it). Callers in the composition root and busbar-llm name busbar_kernel::Registration; plane crates' docs name the host's interner in prose. qa/contract-stateless.toml strikes the three ids.rs rows (LazyLock, FROZEN, VOCABULARY) this drains. RED: the vocabulary tests move with it (busbar-kernel tests/vocabulary_{cap,freeze,resolve,intern}.rs), naming busbar_kernel::Registration; the contract-stateless ledger no longer forgives ids.rs. --- crates/busbar-contract/src/caps/egress.rs | 2 +- crates/busbar-contract/src/ids.rs | 169 ---------------- crates/busbar-contract/src/lib.rs | 4 +- crates/busbar-contract/tests/boot_cells.rs | 31 --- crates/busbar-kernel/src/lib.rs | 3 + crates/busbar-kernel/src/vocabulary.rs | 181 ++++++++++++++++++ .../tests/vocabulary_cap.rs | 9 +- .../tests/vocabulary_freeze.rs | 4 +- .../busbar-kernel/tests/vocabulary_intern.rs | 39 ++++ .../tests/vocabulary_resolve.rs | 3 +- crates/busbar-llm/src/engine/build_runtime.rs | 2 +- crates/busbar-llm/src/engine/tables.rs | 2 +- crates/busbar-llm/src/unit/tests/chain.rs | 4 +- crates/busbar-llm/src/unit/tests/route.rs | 2 +- crates/busbar-plane-a2a/src/lib.rs | 4 +- crates/busbar-plane-llm/src/lib.rs | 4 +- crates/busbar-plane-streaming/src/lib.rs | 2 +- crates/busbar/src/root/durability/mod.rs | 4 +- crates/busbar/src/root/kernel.rs | 10 +- crates/busbar/src/root/plane_node.rs | 5 +- crates/busbar/src/root/tests/kernel.rs | 6 +- qa/contract-stateless.toml | 15 -- 22 files changed, 257 insertions(+), 248 deletions(-) create mode 100644 crates/busbar-kernel/src/vocabulary.rs rename crates/{busbar-contract => busbar-kernel}/tests/vocabulary_cap.rs (74%) rename crates/{busbar-contract => busbar-kernel}/tests/vocabulary_freeze.rs (96%) create mode 100644 crates/busbar-kernel/tests/vocabulary_intern.rs rename crates/{busbar-contract => busbar-kernel}/tests/vocabulary_resolve.rs (95%) diff --git a/crates/busbar-contract/src/caps/egress.rs b/crates/busbar-contract/src/caps/egress.rs index c1f38d67f9..d753482227 100644 --- a/crates/busbar-contract/src/caps/egress.rs +++ b/crates/busbar-contract/src/caps/egress.rs @@ -30,7 +30,7 @@ impl VerifiedDestination { /// /// The lane is named, not spelled: a configured lane's name is a runtime string and /// [`LaneId`] is a borrowed static one, so the name comes from the composition root's - /// registration interner ([`crate::Registration::lane`]) rather than from a literal + /// registration interner (the kernel's `Registration::lane`) rather than from a literal /// at the sealing site. That is what lets a deployment's own lanes be sealed at all, and it is /// why this signature does not take a `String`: a lane id minted per unit would be a leak per /// request, which is the one thing the interning rule exists to forbid. diff --git a/crates/busbar-contract/src/ids.rs b/crates/busbar-contract/src/ids.rs index 164107f495..993e9b3a8c 100644 --- a/crates/busbar-contract/src/ids.rs +++ b/crates/busbar-contract/src/ids.rs @@ -308,172 +308,3 @@ pub struct Estimate { /// One line per class the unit may consume. pub per_class: crate::bounded::BoundedVec, } - -/// How many distinct keys one image may ever intern. -/// -/// The freeze below is what a composition root does. This is what holds where there is no -/// composition root to do it: a test binary, a bench, a fuzz target, or a dynamically loaded plugin -/// linked against its own copy of this crate, which gets its own statics and therefore its own -/// vocabulary. In each of those the freeze never happens, and this ceiling is the whole bound. -/// -/// Sized for configuration, not for traffic: it is every open-vocabulary category a plugin may -/// declare against, summed across every kind of plugin key, for a deployment far larger than any -/// that has been configured. A node that reaches it has a defect, not a big configuration. -pub const MAX_VOCABULARY: usize = 4096; - -/// The image's one vocabulary, and whether it is still open. -/// -/// A `static` rather than state on the registration value because the LEAKED STRINGS are the -/// resource and the resource is process-wide. Ownership of a registration value bounds nothing: -/// the value is constructible by anyone, so a bound that lives inside one instance is a bound per -/// instance, which is no bound at all against a caller that makes a fresh instance per request. -static VOCABULARY: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new())); -static FROZEN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); - -/// The one place a configured string becomes a declared key. -/// -/// Every identifier on this surface is a borrowed static string, because the declarations that -/// carry them are associated constants and a constant cannot own a heap allocation. But lanes, -/// hosts, dialects and other plugin-declared identifiers are CONFIGURED: they are read at boot, -/// and a value read at boot is not static on its own. Something has to bridge those two facts, and -/// until now nothing named what — so every plane coped with a const table the composition root was -/// expected to seal somehow, and one transport leaked a fresh string on every dial, which is a leak -/// per request. -/// -/// This is that bridge, and it is the composition root's. The root builds one of these at boot, -/// interns every config-derived open-vocabulary key through it, and hands the resulting static -/// names to the registrations that declare them. The set is finite because configuration is finite; -/// it is leaked ONCE because interning is idempotent; and its size is a fixed term of the node's -/// resident memory rather than a term that grows with traffic. Nothing outside registration may -/// intern: a key minted per unit would be exactly the leak this replaces. -/// -/// ## How "nothing outside registration" is held -/// -/// Not by who owns this value. This value is constructible by anyone, and it has to be: a plane, a -/// transport and a unit crate all build one in their own tests, and the contract is the plugin- -/// visible ABI, so any seal on the constructor is a seal a plugin can name. What holds instead is -/// that the resource being spent — leaked `&'static str` — is process-wide, so the bound is -/// process-wide too. Every registration in one image reads and writes ONE vocabulary. The -/// composition root fills it at boot and calls [`freeze`](Self::freeze); after that, [`key`] is a -/// LOOKUP: a name the root registered still resolves, from anywhere, and a name it did not is -/// refused. A plane that holds a registration of its own and asks for a name it took off a request -/// gets `None`, having allocated nothing, no matter how many registrations it makes. -/// -/// The refusal is returned as a value and never raised as a panic, because the name that reaches it -/// is client-supplied: a panic there is a way for a request to stop the node, which is a worse -/// failure than the leak being closed. And [`MAX_VOCABULARY`] bounds an image whose freeze never -/// happens at all, which is every image that has no composition root in it. -/// -/// [`key`]: Self::key -#[derive(Debug, Default)] -pub struct Registration { - interned: std::collections::HashSet<&'static str>, -} - -impl Registration { - /// A registration with nothing interned yet. - #[must_use] - pub fn new() -> Self { - Self::default() - } - - /// The static name for one configured key: interned if the vocabulary is open, resolved if it - /// is already there, and refused otherwise. - /// - /// Idempotent: asking twice yields the same name and leaks once. `None` says the key is not in - /// this image's vocabulary and cannot be added to it — because boot is over, or because the - /// ceiling is reached — which is a refusal to route on that name, not an error to recover from. - pub fn key(&mut self, value: &str) -> Option<&'static str> { - let mut vocabulary = VOCABULARY - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let found = if let Some(existing) = vocabulary.get(value) { - *existing - } else if FROZEN.load(std::sync::atomic::Ordering::Acquire) - || vocabulary.len() >= MAX_VOCABULARY - { - return None; - } else { - let leaked: &'static str = Box::leak(value.to_owned().into_boxed_str()); - vocabulary.insert(leaked); - leaked - }; - drop(vocabulary); - self.interned.insert(found); - Some(found) - } - - /// The [`LaneId`] for one configured lane name. - /// - /// The priced axis is declared as a borrowed static name and a configured lane's name is read - /// at boot, so something has to bridge the two — and the bridge is this one, not a second id - /// type that owns its string. An owned lane id would double the type every rate-card key, every - /// verified destination and every locator comparison is written in, for one configured value; - /// interning keeps the axis one `Copy` name and pays a fixed registration-time allocation for - /// it. Refuses for the same reasons [`key`](Self::key) does: a lane nobody registered is not a - /// lane this node routes to. - pub fn lane(&mut self, name: &str) -> Option { - self.key(name).map(LaneId::new) - } - - /// The static name the vocabulary already holds for `value`, or `None`. A LOOKUP ONLY, open - /// vocabulary or closed: it never interns, so it is the one form a per-unit path may call with a - /// name the unit reported. Boot and config registration are what put a name here. - #[must_use] - pub fn resolve(value: &str) -> Option<&'static str> { - VOCABULARY - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .get(value) - .copied() - } - - /// [`Self::resolve`] as the [`MeterClassId`] a usage line carries: a class the vocabulary holds - /// resolves, and a class nobody declared or configured does not. - #[must_use] - pub fn meter_class(value: &str) -> Option { - Self::resolve(value).map(MeterClassId::new) - } - - /// Close the image's vocabulary. The composition root's last registration-time act. - /// - /// Idempotent, and one-way: there is no thaw, because a vocabulary that can be reopened is a - /// vocabulary a request path can reopen. Calling it before the root has finished registering - /// costs the node its own configured names and fails the boot loudly — which is the right shape - /// for a first-party ordering mistake, and is not something a request can cause. - pub fn freeze() { - FROZEN.store(true, std::sync::atomic::Ordering::Release); - } - - /// Whether the vocabulary has been closed. - #[must_use] - pub fn is_frozen() -> bool { - FROZEN.load(std::sync::atomic::Ordering::Acquire) - } - - /// How many distinct keys this IMAGE has interned. - /// - /// The fixed resident-memory term is counted from here, so it is readable rather than inferred, - /// and it is the process's count rather than one registration's because the leak is the - /// process's. - #[must_use] - pub fn interned() -> usize { - VOCABULARY - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .len() - } - - /// How many distinct keys this registration has resolved. - #[must_use] - pub fn len(&self) -> usize { - self.interned.len() - } - - /// Whether this registration has resolved nothing. - #[must_use] - pub fn is_empty(&self) -> bool { - self.interned.is_empty() - } -} diff --git a/crates/busbar-contract/src/lib.rs b/crates/busbar-contract/src/lib.rs index d6cef7e076..ab8a2de437 100644 --- a/crates/busbar-contract/src/lib.rs +++ b/crates/busbar-contract/src/lib.rs @@ -178,8 +178,8 @@ pub use grammar::{ pub use ids::{ AdminVerbId, BucketChain, BucketRef, BucketScope, CapDimension, ClaimKey, ClassDirection, ClassEstimate, CorrelationRef, CorrelationValue, Estimate, LaneId, MeterClassDecl, - MeterClassId, OpClassId, PrincipalId, RecordSchemaId, Registration, SchemeAlt, SchemeKey, - SessionId, StreamId, TransportId, UnitKey, UpstreamIdx, MAX_VOCABULARY, + MeterClassId, OpClassId, PrincipalId, RecordSchemaId, SchemeAlt, SchemeKey, SessionId, + StreamId, TransportId, UnitKey, UpstreamIdx, }; pub use kinds::{ Ack, Challenge, ChallengeState, ContentFacts, CredentialLocator, EnvelopeFields, Head, diff --git a/crates/busbar-contract/tests/boot_cells.rs b/crates/busbar-contract/tests/boot_cells.rs index cf707dbeb5..e137da92c5 100644 --- a/crates/busbar-contract/tests/boot_cells.rs +++ b/crates/busbar-contract/tests/boot_cells.rs @@ -182,37 +182,6 @@ fn overlapping_rungs_within_one_plane_are_an_ordering_question_not_a_refusal() { assert!(!specific.overlaps(&other)); } -/// A registration interns each configured key exactly once, and reuses it after that. -/// -/// This is the leak-once rule as a value rather than as a sentence. A plane's declarations are -/// associated constants, so a configured lane or host has to become a static name somewhere; here -/// is where, and here only. The idempotence is the whole point: a key interned per unit would be a -/// leak that grows with traffic, which is precisely what one transport was doing per dial. -#[test] -fn a_registration_interns_a_configured_key_exactly_once() { - let mut reg = busbar_contract::ids::Registration::new(); - assert!(reg.is_empty()); - - let lane = String::from("acme-frontier"); - let first = reg - .key(&lane) - .expect("the vocabulary is open and far from its ceiling"); - let second = reg - .key(&lane) - .expect("a key already registered always resolves"); - - assert_eq!(first, "acme-frontier"); - assert_eq!(first, second); - // The SAME name, not an equal one: interning twice leaks once. - assert!(std::ptr::eq(first, second)); - assert_eq!(reg.len(), 1); - - // A different key is a second entry, and the fixed term is readable from the count. - let other = reg.key("gadget-frontier").expect("the vocabulary is open"); - assert_ne!(first, other); - assert_eq!(reg.len(), 2); -} - /// Registration refuses a transport that publishes a reserved fact key it did not declare. /// /// The six reserved keys are the structural values the arrival grammar resolves against, and a diff --git a/crates/busbar-kernel/src/lib.rs b/crates/busbar-kernel/src/lib.rs index 4d9e0f9f2a..2e267df1a5 100644 --- a/crates/busbar-kernel/src/lib.rs +++ b/crates/busbar-kernel/src/lib.rs @@ -347,6 +347,9 @@ pub use busbar_contract::transport::transport; #[path = "tests/transport_tests.rs"] mod transport_tests; pub mod trust; +pub mod vocabulary; +/// The image's word interner, at the crate root where the composition root names it. +pub use vocabulary::{Registration, MAX_VOCABULARY}; // ── THE CRATE-ROOT SURFACE ─────────────────────────────────────────────────────────────────────── // The moved crate-root items live in three modules split by concern (`appbuild`, `preflight`, diff --git a/crates/busbar-kernel/src/vocabulary.rs b/crates/busbar-kernel/src/vocabulary.rs new file mode 100644 index 0000000000..002980c5c6 --- /dev/null +++ b/crates/busbar-kernel/src/vocabulary.rs @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! THE IMAGE'S WORD INTERNER: the one place a configured string becomes a `&'static str` key +//! ([`Registration`]), its ceiling ([`MAX_VOCABULARY`]) and its one-way freeze. It is the kernel's +//! because it is process state: `busbar-contract` holds no `static`, `OnceLock`, `LazyLock` or +//! `thread_local` outside tests (THE DESIGN A.8 "Statelessness", `BUSBAR-1.6.0.md` l.3701; gate +//! `contract-stateless`), and the contract's types it hands out ([`LaneId`], [`MeterClassId`]) +//! are plain borrowed names. + +use busbar_contract::{LaneId, MeterClassId}; + +/// How many distinct keys one image may ever intern. +/// +/// The freeze below is what a composition root does. This is what holds where there is no +/// composition root to do it: a test binary, a bench or a fuzz target, which has no boot phase. In +/// each of those the freeze never happens, and this ceiling is the whole bound. (A plugin never +/// holds a vocabulary: its closure is `busbar-contract`, which holds no static — THE DESIGN A.8, +/// `BUSBAR-1.6.0.md` l.3701, gate `contract-stateless` — so the interner is the kernel's.) +/// +/// Sized for configuration, not for traffic: it is every open-vocabulary category a plugin may +/// declare against, summed across every kind of plugin key, for a deployment far larger than any +/// that has been configured. A node that reaches it has a defect, not a big configuration. +pub const MAX_VOCABULARY: usize = 4096; + +/// The image's one vocabulary, and whether it is still open. +/// +/// A `static` rather than state on the registration value because the LEAKED STRINGS are the +/// resource and the resource is process-wide. Ownership of a registration value bounds nothing: +/// the value is constructible by anyone, so a bound that lives inside one instance is a bound per +/// instance, which is no bound at all against a caller that makes a fresh instance per request. +static VOCABULARY: std::sync::LazyLock>> = + std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new())); +static FROZEN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + +/// The one place a configured string becomes a declared key. +/// +/// Every identifier on this surface is a borrowed static string, because the declarations that +/// carry them are associated constants and a constant cannot own a heap allocation. But lanes, +/// hosts, dialects and other plugin-declared identifiers are CONFIGURED: they are read at boot, +/// and a value read at boot is not static on its own. Something has to bridge those two facts, and +/// until now nothing named what — so every plane coped with a const table the composition root was +/// expected to seal somehow, and one transport leaked a fresh string on every dial, which is a leak +/// per request. +/// +/// This is that bridge, and it is the composition root's. The root builds one of these at boot, +/// interns every config-derived open-vocabulary key through it, and hands the resulting static +/// names to the registrations that declare them. The set is finite because configuration is finite; +/// it is leaked ONCE because interning is idempotent; and its size is a fixed term of the node's +/// resident memory rather than a term that grows with traffic. Nothing outside registration may +/// intern: a key minted per unit would be exactly the leak this replaces. +/// +/// ## How "nothing outside registration" is held +/// +/// First by where it lives: the kernel, which no plugin can name (a plugin's closure is +/// `busbar-contract` alone, Part 2 #40(a)). Then, inside the host, not by who owns this value: it +/// is constructible by any host crate, and it has to be, for their own tests. What holds instead is +/// that the resource being spent — leaked `&'static str` — is process-wide, so the bound is +/// process-wide too. Every registration in one image reads and writes ONE vocabulary. The +/// composition root fills it at boot and calls [`freeze`](Self::freeze); after that, [`key`] is a +/// LOOKUP: a name the root registered still resolves, from anywhere, and a name it did not is +/// refused. A plane that holds a registration of its own and asks for a name it took off a request +/// gets `None`, having allocated nothing, no matter how many registrations it makes. +/// +/// The refusal is returned as a value and never raised as a panic, because the name that reaches it +/// is client-supplied: a panic there is a way for a request to stop the node, which is a worse +/// failure than the leak being closed. And [`MAX_VOCABULARY`] bounds an image whose freeze never +/// happens at all, which is every image that has no composition root in it. +/// +/// [`key`]: Self::key +#[derive(Debug, Default)] +pub struct Registration { + interned: std::collections::HashSet<&'static str>, +} + +impl Registration { + /// A registration with nothing interned yet. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// The static name for one configured key: interned if the vocabulary is open, resolved if it + /// is already there, and refused otherwise. + /// + /// Idempotent: asking twice yields the same name and leaks once. `None` says the key is not in + /// this image's vocabulary and cannot be added to it — because boot is over, or because the + /// ceiling is reached — which is a refusal to route on that name, not an error to recover from. + pub fn key(&mut self, value: &str) -> Option<&'static str> { + let mut vocabulary = VOCABULARY + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let found = if let Some(existing) = vocabulary.get(value) { + *existing + } else if FROZEN.load(std::sync::atomic::Ordering::Acquire) + || vocabulary.len() >= MAX_VOCABULARY + { + return None; + } else { + let leaked: &'static str = Box::leak(value.to_owned().into_boxed_str()); + vocabulary.insert(leaked); + leaked + }; + drop(vocabulary); + self.interned.insert(found); + Some(found) + } + + /// The [`LaneId`] for one configured lane name. + /// + /// The priced axis is declared as a borrowed static name and a configured lane's name is read + /// at boot, so something has to bridge the two — and the bridge is this one, not a second id + /// type that owns its string. An owned lane id would double the type every rate-card key, every + /// verified destination and every locator comparison is written in, for one configured value; + /// interning keeps the axis one `Copy` name and pays a fixed registration-time allocation for + /// it. Refuses for the same reasons [`key`](Self::key) does: a lane nobody registered is not a + /// lane this node routes to. + pub fn lane(&mut self, name: &str) -> Option { + self.key(name).map(LaneId::new) + } + + /// The static name the vocabulary already holds for `value`, or `None`. A LOOKUP ONLY, open + /// vocabulary or closed: it never interns, so it is the one form a per-unit path may call with a + /// name the unit reported. Boot and config registration are what put a name here. + #[must_use] + pub fn resolve(value: &str) -> Option<&'static str> { + VOCABULARY + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .get(value) + .copied() + } + + /// [`Self::resolve`] as the [`MeterClassId`] a usage line carries: a class the vocabulary holds + /// resolves, and a class nobody declared or configured does not. + #[must_use] + pub fn meter_class(value: &str) -> Option { + Self::resolve(value).map(MeterClassId::new) + } + + /// Close the image's vocabulary. The composition root's last registration-time act. + /// + /// Idempotent, and one-way: there is no thaw, because a vocabulary that can be reopened is a + /// vocabulary a request path can reopen. Calling it before the root has finished registering + /// costs the node its own configured names and fails the boot loudly — which is the right shape + /// for a first-party ordering mistake, and is not something a request can cause. + pub fn freeze() { + FROZEN.store(true, std::sync::atomic::Ordering::Release); + } + + /// Whether the vocabulary has been closed. + #[must_use] + pub fn is_frozen() -> bool { + FROZEN.load(std::sync::atomic::Ordering::Acquire) + } + + /// How many distinct keys this IMAGE has interned. + /// + /// The fixed resident-memory term is counted from here, so it is readable rather than inferred, + /// and it is the process's count rather than one registration's because the leak is the + /// process's. + #[must_use] + pub fn interned() -> usize { + VOCABULARY + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .len() + } + + /// How many distinct keys this registration has resolved. + #[must_use] + pub fn len(&self) -> usize { + self.interned.len() + } + + /// Whether this registration has resolved nothing. + #[must_use] + pub fn is_empty(&self) -> bool { + self.interned.is_empty() + } +} diff --git a/crates/busbar-contract/tests/vocabulary_cap.rs b/crates/busbar-kernel/tests/vocabulary_cap.rs similarity index 74% rename from crates/busbar-contract/tests/vocabulary_cap.rs rename to crates/busbar-kernel/tests/vocabulary_cap.rs index 8fc648ecc6..89497b00eb 100644 --- a/crates/busbar-contract/tests/vocabulary_cap.rs +++ b/crates/busbar-kernel/tests/vocabulary_cap.rs @@ -4,15 +4,14 @@ //! The vocabulary is bounded even in an image where nobody ever freezes it. //! //! The freeze is what a composition root does; the cap is what holds where there is no composition -//! root to do it. A dynamically loaded plugin linked against its own copy of this crate has its own -//! statics, so it has its own vocabulary and its own open window — and a test binary, a fuzz target -//! or a bench has no boot phase at all. In every one of those images the freeze never happens, and -//! the only thing standing between a per-request `key()` and an unbounded leak is this ceiling. +//! root to do it: a test binary, a fuzz target or a bench has no boot phase at all. In every one of +//! those images the freeze never happens, and the only thing standing between a per-request `key()` +//! and an unbounded leak is this ceiling. //! //! Its own test binary on purpose: the vocabulary is one per process, so a test that fills it must //! not share a process with a test that counts it. -use busbar_contract::{Registration, MAX_VOCABULARY}; +use busbar_kernel::{Registration, MAX_VOCABULARY}; #[test] fn interning_stops_at_the_ceiling_rather_than_growing_with_traffic() { diff --git a/crates/busbar-contract/tests/vocabulary_freeze.rs b/crates/busbar-kernel/tests/vocabulary_freeze.rs similarity index 96% rename from crates/busbar-contract/tests/vocabulary_freeze.rs rename to crates/busbar-kernel/tests/vocabulary_freeze.rs index 76ba89f1a6..89a5348b8b 100644 --- a/crates/busbar-contract/tests/vocabulary_freeze.rs +++ b/crates/busbar-kernel/tests/vocabulary_freeze.rs @@ -20,9 +20,9 @@ //! Its own test binary on purpose: the freeze is a property of the process, so a test that takes it //! must not share a process with a test that needs the vocabulary open. -use busbar_contract::Registration; +use busbar_kernel::Registration; -/// A registration built AFTER the freeze — the plugin-side one this is all about — resolves what +/// A registration built AFTER the freeze — the late caller this is all about — resolves what /// boot registered and refuses everything else. #[test] fn a_registration_built_after_the_freeze_resolves_but_never_interns() { diff --git a/crates/busbar-kernel/tests/vocabulary_intern.rs b/crates/busbar-kernel/tests/vocabulary_intern.rs new file mode 100644 index 0000000000..eff2602a63 --- /dev/null +++ b/crates/busbar-kernel/tests/vocabulary_intern.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! The kernel's word interner interns each configured key exactly once (moved here with the +//! interner from `busbar-contract`, which holds no static: THE DESIGN A.8, `BUSBAR-1.6.0.md` +//! l.3701). Its own test binary, because the vocabulary is the process's. + +use busbar_kernel::Registration; + +/// A registration interns each configured key exactly once, and reuses it after that. +/// +/// This is the leak-once rule as a value rather than as a sentence. A plane's declarations are +/// associated constants, so a configured lane or host has to become a static name somewhere; here +/// is where, and here only. The idempotence is the whole point: a key interned per unit would be a +/// leak that grows with traffic, which is precisely what one transport was doing per dial. +#[test] +fn a_registration_interns_a_configured_key_exactly_once() { + let mut reg = Registration::new(); + assert!(reg.is_empty()); + + let lane = String::from("acme-frontier"); + let first = reg + .key(&lane) + .expect("the vocabulary is open and far from its ceiling"); + let second = reg + .key(&lane) + .expect("a key already registered always resolves"); + + assert_eq!(first, "acme-frontier"); + assert_eq!(first, second); + // The SAME name, not an equal one: interning twice leaks once. + assert!(std::ptr::eq(first, second)); + assert_eq!(reg.len(), 1); + + // A different key is a second entry, and the fixed term is readable from the count. + let other = reg.key("gadget-frontier").expect("the vocabulary is open"); + assert_ne!(first, other); + assert_eq!(reg.len(), 2); +} diff --git a/crates/busbar-contract/tests/vocabulary_resolve.rs b/crates/busbar-kernel/tests/vocabulary_resolve.rs similarity index 95% rename from crates/busbar-contract/tests/vocabulary_resolve.rs rename to crates/busbar-kernel/tests/vocabulary_resolve.rs index e6586454a0..bc1f17b3c3 100644 --- a/crates/busbar-contract/tests/vocabulary_resolve.rs +++ b/crates/busbar-kernel/tests/vocabulary_resolve.rs @@ -8,7 +8,8 @@ //! must allocate nothing for a name nobody registered, with the vocabulary open or closed. Its own //! test binary, because the vocabulary is the process's. -use busbar_contract::{MeterClassId, Registration}; +use busbar_contract::MeterClassId; +use busbar_kernel::Registration; #[test] fn resolving_answers_what_registration_holds_and_interns_nothing() { diff --git a/crates/busbar-llm/src/engine/build_runtime.rs b/crates/busbar-llm/src/engine/build_runtime.rs index 9c3012c08c..68a8353016 100644 --- a/crates/busbar-llm/src/engine/build_runtime.rs +++ b/crates/busbar-llm/src/engine/build_runtime.rs @@ -125,7 +125,7 @@ pub(crate) fn build_runtime( // registered, and interns a name the root has not reached yet at most once for the image. What // it buys is that the lane table is seated with its static names at BUILD time, off the request // path, and therefore before this generation can be published. - let mut registration = busbar_contract::Registration::new(); + let mut registration = busbar_kernel::Registration::new(); // ── lanes (one per model, in the carrier's deterministic sorted order — `lanes[i]` IS lane `i`) ── let mut lanes: Vec = Vec::with_capacity(input.lanes.len()); diff --git a/crates/busbar-llm/src/engine/tables.rs b/crates/busbar-llm/src/engine/tables.rs index 828eb1e111..62689924b1 100644 --- a/crates/busbar-llm/src/engine/tables.rs +++ b/crates/busbar-llm/src/engine/tables.rs @@ -17,7 +17,7 @@ pub(crate) struct Lane { pub(crate) provider: String, pub(crate) base_url: String, /// THE DIAL TARGET THIS LANE NAMES, as the node's interned `&'static str` — `base_url` seated - /// through `busbar_contract::Registration` ONCE, here, when the generation's lane table is + /// through `busbar_kernel::Registration` ONCE, here, when the generation's lane table is /// built. A planned leg names its upstream as a borrowed static string, and a configured /// `base_url` is a runtime `String`, so something has to bridge them; doing it while the plan is /// being built means one process-wide vocabulary acquisition per candidate per request to diff --git a/crates/busbar-llm/src/unit/tests/chain.rs b/crates/busbar-llm/src/unit/tests/chain.rs index 8cc1e6e5f3..35c89b84a7 100644 --- a/crates/busbar-llm/src/unit/tests/chain.rs +++ b/crates/busbar-llm/src/unit/tests/chain.rs @@ -581,8 +581,8 @@ fn kernel_seal() -> KernelSeal { /// interner itself: process-wide, filled from the deployment's own tables, and idempotent, so a /// second leg over the same lane name leaks nothing further. A per-leg interner would be a leak per /// request wearing a test's clothes, which is exactly the shape the rule forbids. -static LANES: std::sync::LazyLock> = - std::sync::LazyLock::new(|| std::sync::Mutex::new(busbar_contract::Registration::new())); +static LANES: std::sync::LazyLock> = + std::sync::LazyLock::new(|| std::sync::Mutex::new(busbar_kernel::Registration::new())); /// The destination set the trust unit would have sealed, over the lane this deployment CONFIGURED — /// the name read back off the running routing tables, not a literal spelled here. diff --git a/crates/busbar-llm/src/unit/tests/route.rs b/crates/busbar-llm/src/unit/tests/route.rs index e39d374a27..4955c32f61 100644 --- a/crates/busbar-llm/src/unit/tests/route.rs +++ b/crates/busbar-llm/src/unit/tests/route.rs @@ -5,8 +5,8 @@ use super::*; use crate::test_support::{LaneSpec, MockResponse, MockServer, MockServerState, TestApp}; use busbar_contract::caps::KernelSeal; -use busbar_contract::Registration; use busbar_kernel::store::{now as store_now, BreakerState}; +use busbar_kernel::Registration; use serde_json::json; /// A kernel seal for the length of one leg, and the step-5 token minted from it — exactly as diff --git a/crates/busbar-plane-a2a/src/lib.rs b/crates/busbar-plane-a2a/src/lib.rs index ffd2a89c17..6c3c34b4b1 100644 --- a/crates/busbar-plane-a2a/src/lib.rs +++ b/crates/busbar-plane-a2a/src/lib.rs @@ -237,8 +237,8 @@ use busbar_contract::plugin::{AbiVersion, Kind, Plugin}; /// One configured agent this plane may name. /// /// Every string is borrowed for the life of the program, because a plane's declarations are read at -/// registration and sealed. Configured names reach here through the seam that says so: -/// [`busbar_contract::ids::Registration`]. The composition root builds one at boot, interns every +/// registration and sealed. Configured names reach here through the seam that says so: the host's +/// word interner (its `Registration`). The composition root builds one at boot, interns every /// config-derived key through it exactly once, and hands over names that outlive it — so nothing /// after registration can vary them, and the memory the names occupy is a fixed term rather than /// one that grows with traffic. diff --git a/crates/busbar-plane-llm/src/lib.rs b/crates/busbar-plane-llm/src/lib.rs index 35cdce39e4..ec0084683a 100644 --- a/crates/busbar-plane-llm/src/lib.rs +++ b/crates/busbar-plane-llm/src/lib.rs @@ -45,8 +45,8 @@ use busbar_contract::plugin::{AbiVersion, Kind, Plugin}; /// One configured upstream this plane may name. /// /// Every string is borrowed for the life of the program, because a plane's declarations are read at -/// registration and sealed. Configured names reach here through the seam that says so: -/// [`busbar_contract::ids::Registration`]. The composition root builds one at boot, interns every +/// registration and sealed. Configured names reach here through the seam that says so: the host's +/// word interner (its `Registration`). The composition root builds one at boot, interns every /// config-derived key through it exactly once, and hands over names that outlive it — so nothing /// after registration can vary them, and the memory the names occupy is a fixed term rather than /// one that grows with traffic. diff --git a/crates/busbar-plane-streaming/src/lib.rs b/crates/busbar-plane-streaming/src/lib.rs index dcca40cf59..a7d0c8e851 100644 --- a/crates/busbar-plane-streaming/src/lib.rs +++ b/crates/busbar-plane-streaming/src/lib.rs @@ -101,7 +101,7 @@ pub use register::{plane_for_root, CAP_KEY}; /// /// Borrowed for the life of the program, the same way `busbar-plane-llm::Upstream` is: the /// composition root interns every config-derived key once through -/// [`busbar_contract::ids::Registration`] and hands over names that outlive it. +/// the host's word interner (its `Registration`) and hands over names that outlive it. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Upstream { /// The priced lane this upstream is reached on. diff --git a/crates/busbar/src/root/durability/mod.rs b/crates/busbar/src/root/durability/mod.rs index 6cbd71c896..961a84f5a3 100644 --- a/crates/busbar/src/root/durability/mod.rs +++ b/crates/busbar/src/root/durability/mod.rs @@ -1437,7 +1437,7 @@ fn meter_class_of(class: &str) -> Option { .into_iter() .find(|reserved| *reserved == class) .map(busbar_contract::caps::MeterClassId::new) - .or_else(|| busbar_contract::Registration::meter_class(class)) + .or_else(|| busbar_kernel::Registration::meter_class(class)) } /// What one journalled posting leaves for the read-time totals. @@ -2238,7 +2238,7 @@ pub fn decode_audit(record: &JournalRecord) -> Result, Strin if record.class != RecordClass::Transaction { return Ok(None); } - from_journal_body(&record.body, &busbar_contract::Registration::meter_class) + from_journal_body(&record.body, &busbar_kernel::Registration::meter_class) } /// The journal body of a sealed checkpoint. diff --git a/crates/busbar/src/root/kernel.rs b/crates/busbar/src/root/kernel.rs index d42851747b..63d8c820b0 100644 --- a/crates/busbar/src/root/kernel.rs +++ b/crates/busbar/src/root/kernel.rs @@ -5,7 +5,7 @@ //! //! Three things the design draws have, until now, existed in the tree only as test doubles: an //! implementor of `busbar_kernel::teller::Units`, an implementor of -//! `busbar_kernel::inflight::ArrivalDoor`, and a live `busbar_contract::Registration`. This file is +//! `busbar_kernel::inflight::ArrivalDoor`, and a live `busbar_kernel::Registration`. This file is //! where the production ones live, and it is the only place in the workspace entitled to name all //! fourteen units at once. //! @@ -81,12 +81,12 @@ pub fn new_kernel() -> busbar_kernel::teller::Kernel { /// Config-derived open-vocabulary keys — a lane, a pool, a model, a provider host, a dialect name, /// a loaded plugin's key — are leaked into `&'static str` exactly once, here, at registration. The /// resulting allocation is fixed and countable; a leak per connection or per dial is a defect -/// rather than a variant of the rule. [`busbar_contract::Registration`] enforces the "exactly +/// rather than a variant of the rule. [`busbar_kernel::Registration`] enforces the "exactly /// once, and never after boot" half itself: it is idempotent, and it refuses a new key once the /// image's vocabulary is frozen. #[must_use] -pub fn new_registration() -> busbar_contract::Registration { - busbar_contract::Registration::new() +pub fn new_registration() -> busbar_kernel::Registration { + busbar_kernel::Registration::new() } // --------------------------------------------------------------------------------------------- @@ -973,7 +973,7 @@ pub(crate) fn card_from_raw( /// apply, both of which are registration time (config is finite). The reserved four, every installed /// plane's declared billable classes, and every open class a lane's `units:` names go into the /// process vocabulary, so a unit's one line can resolve the classes -/// it reports by lookup ([`busbar_contract::Registration::resolve`]) and never intern one per unit. +/// it reports by lookup ([`busbar_kernel::Registration::resolve`]) and never intern one per unit. pub(crate) fn register_classes(rates: &busbar_kernel::rate_apply::RawRates<'_>) { let mut registration = new_registration(); for class in busbar_contract::records::RESERVED_UNITS { diff --git a/crates/busbar/src/root/plane_node.rs b/crates/busbar/src/root/plane_node.rs index 6796e73bbc..bc2c099e17 100644 --- a/crates/busbar/src/root/plane_node.rs +++ b/crates/busbar/src/root/plane_node.rs @@ -49,12 +49,13 @@ use busbar_contract::caps::{ Grant, Meter, OpClassId, OriginKind, Outcome, Pass, PrincipalId, Refusal, Route, SeatVerdict, VerifiedDestination, Verify, }; -use busbar_contract::{LaneId, Registration, UnitKey}; +use busbar_contract::{LaneId, UnitKey}; use busbar_kernel::plane_host::PlaneAnswer; use busbar_kernel::slice::GroupLeaseSlip; use busbar_kernel::teller::{ AccrualMeter, Ended, Evidence, RouteAwait, RouteLeg, Screen, UnitCtx, Units, }; +use busbar_kernel::Registration; use busbar_kernel_audit::{ AuditInputs, Controls, FinishClass as RecordFinish, OpClassId as RecordOpClass, OutcomeFacts, Subject, Usage as RecordUsage, UsageLine as RecordLine, What, @@ -1521,7 +1522,7 @@ fn post_late( if let Some(class) = counts .classes .keys() - .find(|class| busbar_contract::Registration::resolve(class).is_none()) + .find(|class| busbar_kernel::Registration::resolve(class).is_none()) { busbar_kernel::diagnostics::diag_error!( busbar_kernel::diagnostics::METER_CLASS_UNDECLARED, diff --git a/crates/busbar/src/root/tests/kernel.rs b/crates/busbar/src/root/tests/kernel.rs index 531ba51242..0035abd19c 100644 --- a/crates/busbar/src/root/tests/kernel.rs +++ b/crates/busbar/src/root/tests/kernel.rs @@ -2141,13 +2141,13 @@ fn applying_rates_registers_the_classes_a_unit_may_report() { ); for class in busbar_contract::records::RESERVED_UNITS { assert!( - busbar_contract::Registration::resolve(class).is_some(), + busbar_kernel::Registration::resolve(class).is_some(), "{class}" ); } - assert!(busbar_contract::Registration::resolve("configured_open_units").is_some()); + assert!(busbar_kernel::Registration::resolve("configured_open_units").is_some()); assert_eq!( - busbar_contract::Registration::resolve("a_class_nobody_declared_or_configured"), + busbar_kernel::Registration::resolve("a_class_nobody_declared_or_configured"), None ); } diff --git a/qa/contract-stateless.toml b/qa/contract-stateless.toml index 7669df3bec..8f055bd494 100644 --- a/qa/contract-stateless.toml +++ b/qa/contract-stateless.toml @@ -13,11 +13,6 @@ rule = "contract-stateless:once-cell" file = "crates/busbar-contract/src/codec.rs" item = "OnceLock" -[[finding]] -rule = "contract-stateless:once-cell" -file = "crates/busbar-contract/src/ids.rs" -item = "LazyLock" - [[finding]] rule = "contract-stateless:once-cell" file = "crates/busbar-contract/src/testkit/warn_capture.rs" @@ -185,16 +180,6 @@ rule = "contract-stateless:static" file = "crates/busbar-contract/src/codec.rs" item = "WALL_CLOCK" -[[finding]] -rule = "contract-stateless:static" -file = "crates/busbar-contract/src/ids.rs" -item = "FROZEN" - -[[finding]] -rule = "contract-stateless:static" -file = "crates/busbar-contract/src/ids.rs" -item = "VOCABULARY" - [[finding]] rule = "contract-stateless:static" file = "crates/busbar-contract/src/records.rs" From d452e0e532999c73011107ecf669b89fdc473475 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:31:02 -0700 Subject: [PATCH 2/3] contract: the scope-kind registry static is deleted; the VirtualKey wire carries every kind as data THE DESIGN A.8 "Statelessness" (BUSBAR-1.6.0.md l.3701) and C.2 (l.3893, "records.rs:94 ... deleted"). records::scope_kinds::REGISTERED was write-only: since 1.6.0 SDK-SCOPEKINDS the wire serializes every kind (registered or not) under its own allowed_{kind}s field, and nothing in the workspace read is_registered but its own test. The registry, register/is_registered and register_scope_kind go; the kernel's plane fold stops registering scope kinds; the store conformance fold's scope-kind round trip runs as before over the key's own data. qa/contract-stateless.toml strikes the records.rs REGISTERED row this drains. --- crates/busbar-contract/src/records.rs | 81 +++++-------------- .../src/tests/records_tests.rs | 22 ++--- crates/busbar-kernel/src/plane/registry.rs | 9 --- crates/plugin-loader/src/conformance/store.rs | 6 +- qa/contract-stateless.toml | 5 -- 5 files changed, 29 insertions(+), 94 deletions(-) diff --git a/crates/busbar-contract/src/records.rs b/crates/busbar-contract/src/records.rs index 2e4d16bfa2..978cc0193d 100644 --- a/crates/busbar-contract/src/records.rs +++ b/crates/busbar-contract/src/records.rs @@ -61,57 +61,25 @@ impl ScopeRef { } } -/// THE NEUTRAL SCOPE-KIND REGISTRY — the ENGINE's vocabulary of `ScopeRef` kinds (beyond the -/// built-in `pool`). This crate names NO concrete plane kind: the registry starts with only `pool` -/// (core's own admission kind) and every plane kind (`mcp_server`, `mcp_tool`, an A2A `agent`, …) is -/// registered at boot by the composition root, which iterates each installed `PlaneDecl.scope_kinds` -/// and calls [`register_scope_kind`]. The wire-field NAME for a kind is the frozen convention -/// `allowed_{kind}s` (`pool`→`allowed_pools`, `mcp_server`→`allowed_mcp_servers`, …), so the config -/// grammar keys stay byte-identical while the vocabulary itself lives here rather than being -/// hard-coded. +/// THE SCOPE-KIND WIRE CONVENTION. A [`ScopeRef`] kind beyond the built-in `pool` (core's own +/// admission kind) is a plane's own (`mcp_server`, `mcp_tool`, an A2A `agent`, …) and is DATA off +/// its `PlaneDecl.scope_kinds`; this crate names no concrete plane kind and holds no registry of +/// them. The wire-field NAME for a kind is the frozen convention `allowed_{kind}s` +/// (`pool`→`allowed_pools`, `mcp_server`→`allowed_mcp_servers`, …), so the config grammar keys +/// stay byte-identical. /// -/// ## The registry does NOT gate the wire (1.6.0 SDK-SCOPEKINDS) +/// ## The wire is a carrier, and holds no process state (THE DESIGN A.8) /// -/// The [`VirtualKey`] wire is a pure, lossless CARRIER: every kind, registered or not, serializes to -/// its own `allowed_{kind}s` field and reads back as the same kind (the naming convention is a -/// bijection, see [`scope_kinds::kind_for_wire_field`]). It used to refuse to serialize an -/// unregistered kind, and that made a DROPPED-IN store plugin unable to hand back a key it had just -/// been given: a `cdylib` statically links its own copy of this crate, so its registry is a -/// DIFFERENT, EMPTY static that the engine's boot registration never reaches, while a compiled-in -/// store shares the engine's. The same plugin behaved differently by build (DECISIONS #11 / spec -/// Part 2 #2). Kind VALIDATION is the engine's — it owns the registry and asks -/// [`scope_kinds::is_registered`]; a plugin is an opaque carrier of the kind string. +/// The [`VirtualKey`] wire is a pure, lossless CARRIER: every kind serializes to its own +/// `allowed_{kind}s` field and reads back as the same kind (the naming convention is a bijection, +/// see [`scope_kinds::kind_for_wire_field`]). It used to keep a process-global registry of kinds and +/// refuse to serialize an unregistered one, which made a DROPPED-IN store plugin unable to hand back +/// a key it had just been given: a `cdylib` statically links its own copy of this crate, so its +/// registry was a different, empty static. The refusal went first (1.6.0 SDK-SCOPEKINDS); the +/// registry it consulted went with it, because nothing read it after, and `busbar-contract` holds +/// no `static` (THE DESIGN A.8 "Statelessness", `BUSBAR-1.6.0.md` l.3701; C.2 l.3893). A plugin is +/// an opaque carrier of the kind string. pub mod scope_kinds { - use std::collections::BTreeSet; - use std::sync::RwLock; - - /// The built-in kind every deployment always has — core's admission-pool topology. Seeded so a - /// pool grant serializes with no registration and so the pool-only wire shape is unconditional. - const BUILTIN_POOL_KIND: &str = "pool"; - - static REGISTERED: RwLock> = RwLock::new(BTreeSet::new()); - - /// REGISTER a scope kind into the engine's vocabulary. Idempotent. Called at boot for every - /// `PlaneDecl.scope_kinds` entry — the composition root supplies the string, so no plane token - /// is ever a literal in a neutral crate. Registration is NOT required to persist a grant of the - /// kind: the wire carries every kind (see the module doc). - pub fn register(kind: &str) { - let mut w = REGISTERED.write().unwrap_or_else(|e| e.into_inner()); - w.insert(kind.to_string()); - } - - /// Whether `kind` is in the ENGINE's vocabulary. `pool` is always registered. Only meaningful in - /// the engine's own process: a dropped-in plugin's copy of this registry is never populated. - pub fn is_registered(kind: &str) -> bool { - if kind == BUILTIN_POOL_KIND { - return true; - } - REGISTERED - .read() - .unwrap_or_else(|e| e.into_inner()) - .contains(kind) - } - /// The frozen wire-field name for a scope `kind` — `allowed_{kind}s`. pub fn wire_field_for(kind: &str) -> String { format!("allowed_{kind}s") @@ -127,20 +95,13 @@ pub mod scope_kinds { } } -/// REGISTER a `ScopeRef` kind's wire field — see [`scope_kinds`]. The composition root calls this at -/// boot for every installed plane's declared `scope_kinds`, so this neutral crate carries no plane -/// vocabulary of its own. -pub fn register_scope_kind(kind: &str) { - scope_kinds::register(kind); -} - -/// The KIND-PARTITIONED wire shape for [`VirtualKey::allowed_scopes`] (1.6.0): each REGISTERED -/// scope kind gets its OWN named wire field, `allowed_{kind}s` — `allowed_pools` (kind `pool`), +/// The KIND-PARTITIONED wire shape for [`VirtualKey::allowed_scopes`] (1.6.0): each scope kind +/// gets its OWN named wire field, `allowed_{kind}s` — `allowed_pools` (kind `pool`), /// `allowed_mcp_servers` (kind `mcp_server`), `allowed_mcp_tools` (kind `mcp_tool`) — each a plain /// array of bare value strings, never a `{kind, value}` object. The in-memory -/// `Option>` is partitioned by kind on write and reassembled on read. The kind -/// vocabulary lives in the neutral [`scope_kinds`] registry (populated at boot from each installed -/// `PlaneDecl.scope_kinds`), NOT hard-coded here — so this crate names no concrete plane. +/// `Option>` is partitioned by kind on write and reassembled on read. The kind is +/// the entry's own data, named by the [`scope_kinds`] convention, NOT hard-coded here — so this +/// crate names no concrete plane. /// /// Wire-compat invariants, all pinned by tests: /// - the pool-only shape stays BYTE-IDENTICAL to the pre-generalization diff --git a/crates/busbar-contract/src/tests/records_tests.rs b/crates/busbar-contract/src/tests/records_tests.rs index 6c0341da4e..b88a1f322b 100644 --- a/crates/busbar-contract/src/tests/records_tests.rs +++ b/crates/busbar-contract/src/tests/records_tests.rs @@ -267,10 +267,6 @@ fn attribution_fields_round_trip_and_are_backward_compatible() { /// partitioned on write and reassembled on read. #[test] fn scope_kinds_survive_store_round_trip() { - // The plane scope kinds are registered at boot from each `PlaneDecl.scope_kinds`; a unit test - // registers them itself (idempotent) so the neutral crate's SOURCE names no plane kind. - register_scope_kind("grid_server"); - register_scope_kind("grid_tool"); let mut k = sample_key(); k.allowed_scopes = Some(vec![ ScopeRef::pool("fast"), @@ -298,22 +294,18 @@ fn scope_kinds_survive_store_round_trip() { assert!(rt.scope_allowed("pool", "fast")); } -/// A scope kind this process NEVER registered round-trips VERBATIM under its own -/// `allowed_{kind}s` field (1.6.0 SDK-SCOPEKINDS) - never remapped into `allowed_pools` (the pre-P0 -/// escalation) and never dropped (which would widen the grant toward the `None` wildcard). +/// Any scope kind round-trips VERBATIM under its own `allowed_{kind}s` field (1.6.0 +/// SDK-SCOPEKINDS) - never remapped into `allowed_pools` (the pre-P0 escalation) and never dropped +/// (which would widen the grant toward the `None` wildcard). /// /// This replaces the old "unregistered kind is a hard serialize error" rule. That rule made the -/// wire consult a process-global registry, and a dropped-in store plugin's copy of it is never +/// wire consult a process-global registry, and a dropped-in store plugin's copy of it was never /// populated, so the plugin could not hand back a key carrying a plane grant it had just stored. -/// Kind validation is the engine's; the wire is an opaque carrier. The kind below is one no test -/// in this crate registers, so this runs against an unregistered kind whatever the test order. +/// The registry is gone (the contract holds no static, THE DESIGN A.8); the wire is an opaque +/// carrier, and a kind nobody declared round-trips like any other. #[test] fn unregistered_scope_kind_round_trips_verbatim_under_its_own_field() { let kind = "sdk_scopekinds_never_registered"; - assert!( - !scope_kinds::is_registered(kind), - "fixture precondition: the kind must be unregistered" - ); let mut k = sample_key(); k.allowed_scopes = Some(vec![ScopeRef { kind: kind.to_string(), @@ -343,8 +335,6 @@ fn unregistered_scope_kind_round_trips_verbatim_under_its_own_field() { /// `allowed_pools: []` beside a GRID field stays the EMPTY pool set - never "all". #[test] fn grid_scope_wire_fields_are_additive() { - register_scope_kind("grid_server"); - register_scope_kind("grid_tool"); // Pool-only and None grants must not grow grid fields on the wire. let pool_only = sample_key(); let v = serde_json::to_value(&pool_only).unwrap(); diff --git a/crates/busbar-kernel/src/plane/registry.rs b/crates/busbar-kernel/src/plane/registry.rs index 9c18619836..bca855d3a1 100644 --- a/crates/busbar-kernel/src/plane/registry.rs +++ b/crates/busbar-kernel/src/plane/registry.rs @@ -329,15 +329,6 @@ pub fn merged_boot_plane_decls( .position(|k| *k == d.key) .unwrap_or(canonical.len()) }); - // REGISTER EACH PLANE'S SCOPE KINDS with the neutral `busbar_contract` scope-kind wire registry, so a - // `VirtualKey` grant of a plane's kind (`mcp_server`, …) serializes to its `allowed_{kind}s` wire - // field instead of failing the write. The kind strings are DATA off each `PlaneDecl.scope_kinds` - // — core names no plane vocabulary here. Idempotent, so re-folding under the test surface is safe. - for d in &decls { - for kind in d.scope_kinds { - busbar_contract::records::register_scope_kind(kind); - } - } // PLANE-OWNED-CONFIG DUP-CLAIM GUARD (1.6.0 config-seam, stage 1). Refuse the boot if two planes // claim the same top-level config section, or if a plane claims a section core STILL owns // concretely (`CORE_OWNED_CONCRETE_SECTIONS`) — the invariant that makes the later section moves diff --git a/crates/plugin-loader/src/conformance/store.rs b/crates/plugin-loader/src/conformance/store.rs index daa52101db..ecc4426be4 100644 --- a/crates/plugin-loader/src/conformance/store.rs +++ b/crates/plugin-loader/src/conformance/store.rs @@ -833,10 +833,8 @@ pub(super) fn fold(s: &Subject, leg: Leg) -> Fold { }); r.line("usage at the end", LEASED, || ans(b.get_usage(ub, w))); - // ── the scope-kind round trip (DECISIONS #11): the engine registers the plane kinds in ITS - // process; a dropped-in store never does, and must hand the key back byte-identically ── - busbar_contract::records::register_scope_kind(plane_kind("server")); - busbar_contract::records::register_scope_kind(plane_kind("tool")); + // ── the scope-kind round trip (DECISIONS #11): a plane's kinds are the key's own data, and a + // dropped-in store must hand the key back byte-identically ── let sk = scoped_key(); r.line("scope-kind put", 1, || ans(b.put_key(&sk))); let back = r.step("scope-kind get", LEASED, || match b.get_key(&sk.id) { diff --git a/qa/contract-stateless.toml b/qa/contract-stateless.toml index 8f055bd494..017418a225 100644 --- a/qa/contract-stateless.toml +++ b/qa/contract-stateless.toml @@ -180,11 +180,6 @@ rule = "contract-stateless:static" file = "crates/busbar-contract/src/codec.rs" item = "WALL_CLOCK" -[[finding]] -rule = "contract-stateless:static" -file = "crates/busbar-contract/src/records.rs" -item = "REGISTERED" - [[finding]] rule = "contract-stateless:static" file = "crates/busbar-contract/src/testkit/warn_capture.rs" From c192c85a4e261130bd2ebb8f32ff0d91a7bad9d1 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:37:02 -0700 Subject: [PATCH 3/3] contract-stateless: a module declared behind the dev-only test-seal feature is test code Coordinator ruling 2026-10-14 (ruling 1 (a)): the gate counts busbar-contract state "outside test code" (BUSBAR-1.6.0.md l.3701), and a module its parent declares under construction: tests/test_seal_is_dev_only.rs refuses every non-dev edge that enables the feature. The scan skips such a module's files; qa/contract-stateless.toml strikes the three testkit warn_capture rows (OnceLock, GATE, ONCE) that were never shipped state. RED (selftest): the same planted static in a module declared with no gate, or behind another feature, is RED naming it; behind the test-seal gate it is green. --- qa/contract-stateless.toml | 15 ----- xtask/src/gates/contract_stateless.rs | 91 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/qa/contract-stateless.toml b/qa/contract-stateless.toml index 017418a225..4b601975dc 100644 --- a/qa/contract-stateless.toml +++ b/qa/contract-stateless.toml @@ -13,11 +13,6 @@ rule = "contract-stateless:once-cell" file = "crates/busbar-contract/src/codec.rs" item = "OnceLock" -[[finding]] -rule = "contract-stateless:once-cell" -file = "crates/busbar-contract/src/testkit/warn_capture.rs" -item = "OnceLock" - [[finding]] rule = "contract-stateless:static" file = "crates/busbar-contract/src/abi/hot/host.rs" @@ -180,16 +175,6 @@ rule = "contract-stateless:static" file = "crates/busbar-contract/src/codec.rs" item = "WALL_CLOCK" -[[finding]] -rule = "contract-stateless:static" -file = "crates/busbar-contract/src/testkit/warn_capture.rs" -item = "GATE" - -[[finding]] -rule = "contract-stateless:static" -file = "crates/busbar-contract/src/testkit/warn_capture.rs" -item = "ONCE" - [[finding]] rule = "contract-stateless:static" file = "crates/busbar-contract/src/transport/wire.rs" diff --git a/xtask/src/gates/contract_stateless.rs b/xtask/src/gates/contract_stateless.rs index 45d2829489..416f3d8dce 100644 --- a/xtask/src/gates/contract_stateless.rs +++ b/xtask/src/gates/contract_stateless.rs @@ -7,6 +7,12 @@ //! `open`. So the contract has no `static`, `OnceLock`, `LazyLock`, `OnceCell` or `thread_local!` //! outside test code. //! +//! TEST CODE includes a module its parent declares behind the dev-only `test-seal` feature +//! (`#[cfg(any(test, feature = "test-seal"))]` or `#[cfg(feature = "test-seal")]`, coordinator +//! ruling 2026-10-14): `tests/test_seal_is_dev_only.rs` refuses every non-dev edge that enables the +//! feature, so such a module is compiled only into tests and never into a shipped image. The same +//! static behind any other gate, or none, is still a finding. +//! //! REPORT-ONLY, armed at today's count as the drain-only ledger `qa/contract-stateless.toml` (see //! [`super::one_abi`]); the rows are the work list of the step that moves the registries into the //! kernel. @@ -42,6 +48,50 @@ pub const SPEC: Spec = Spec { /// The once-cell spellings, each read as a word. const ONCE: &[&str] = &["OnceLock", "LazyLock", "OnceCell", "Lazy"]; +/// The attributes that confine a module to tests and the dev-only `test-seal` feature. +const TEST_SEAL_GATES: &[&str] = &[ + "#[cfg(any(test, feature = \"test-seal\"))]", + "#[cfg(feature = \"test-seal\")]", +]; + +/// Every module a file declares directly under a [`TEST_SEAL_GATES`] attribute, as the path its +/// source lives at without the extension (`crates/busbar-contract/src/testkit`): the file +/// `.rs` and everything under `/` are test code. +fn test_seal_modules(files: &[crate::ctx::SourceFile]) -> Vec { + let mut out = Vec::new(); + for f in files { + let rel = f.rel_str(); + let dir = match rel.rsplit_once('/') { + Some((d, "lib.rs" | "mod.rs" | "main.rs")) => d.to_string(), + _ => rel.trim_end_matches(".rs").to_string(), + }; + let lines: Vec<&str> = f.text.lines().map(str::trim).collect(); + for pair in lines.windows(2) { + if !TEST_SEAL_GATES.contains(&pair[0]) { + continue; + } + let Some(at) = pair[1].find("mod ") else { + continue; + }; + let vis = pair[1][..at].trim(); + if !(vis.is_empty() || vis.starts_with("pub")) { + continue; + } + if let Some(name) = pair[1][at + 4..].trim().strip_suffix(';') { + out.push(format!("{dir}/{}", name.trim())); + } + } + } + out +} + +/// Whether `rel` is the source of one of `modules` ([`test_seal_modules`]). +fn in_test_seal_module(rel: &str, modules: &[String]) -> bool { + modules + .iter() + .any(|m| rel == format!("{m}.rs") || rel.starts_with(&format!("{m}/"))) +} + /// The name a `static` item declares on this (blanked) line, if it declares one. `'static` is a /// lifetime and never counts. fn static_item(counted: &str) -> Option { @@ -85,8 +135,12 @@ pub fn scan(cx: &Ctx) -> (Vec, Vec) { )], ); } + let sealed = test_seal_modules(&files); for f in &files { let rel = f.rel_str(); + if in_test_seal_module(&rel, &sealed) { + continue; + } for sl in one_abi::production(&f.text) { if let Some(name) = static_item(&sl.counted) { findings.push(Finding::new(ROW_STATIC, &rel, name, sl.no)); @@ -160,6 +214,43 @@ impl Gate for ContractStatelessGate { ), ], ); + // THE TEST-SEAL MODULE (coordinator ruling 2026-10-14): a module declared behind the + // dev-only feature is test code, and the SAME static under any other declaration is RED. + let lib = "crates/busbar-contract/src/lib.rs"; + let seal_file = "crates/busbar-contract/src/planted_seal/mod.rs"; + let declared = |attr: &str| { + let mut ov = Overlay::new(); + let text = cx.read(lib).unwrap_or_default(); + ov.set(lib, format!("{text}\n{attr}pub mod planted_seal;\n")); + ov.set( + seal_file, + "pub static PLANTED_SEAL_STATE: u8 = 0;\n".to_string(), + ); + ov + }; + report.push(crate::gates::prove_rows_green( + cx, + self, + "a static in a module declared behind the dev-only test-seal feature is test code", + &[ROW_STATIC], + declared("#[cfg(any(test, feature = \"test-seal\"))]\n"), + )); + report.push(crate::gates::prove_rows_red( + cx, + self, + "the same static in a module declared with no test-seal gate is RED", + &[ROW_STATIC], + declared(""), + &[seal_file, "PLANTED_SEAL_STATE"], + )); + report.push(crate::gates::prove_rows_red( + cx, + self, + "the same static behind another feature is RED", + &[ROW_STATIC], + declared("#[cfg(feature = \"planted-feature\")]\n"), + &[seal_file, "PLANTED_SEAL_STATE"], + )); report.push(crate::gates::prove_rows_green( cx, self,