diff --git a/crates/busbar-contract/src/lib.rs b/crates/busbar-contract/src/lib.rs index d6cef7e076..c75f24244e 100644 --- a/crates/busbar-contract/src/lib.rs +++ b/crates/busbar-contract/src/lib.rs @@ -197,8 +197,7 @@ pub use redacted::{constant_time_eq, sha256_hex, Redacted}; pub use scratch::{Scratch, ScratchRefused}; pub use signal::{Signal, SignalBag, SignalValue}; pub use transport::{ - check_composition, CompositionError, FrameStream, Fut, Registered, Transport, - TransportConfigView, TransportMeta, TRANSPORT_ABI, + check_composition, CompositionError, FrameStream, Fut, Registered, TransportMeta, TRANSPORT_ABI, }; pub use transport::{Carrier, Framer}; pub use unit::{ @@ -209,8 +208,8 @@ pub use unit::{ pub use wire::{ ArrivalRecord, CertFacts, CloseReason, Conn, ConnHandle, Decode, Direction, DiscardCode, Encode, EnvelopeField, Frame, FrameCursor, FrameMeta, Framing, Handoff, HandshakeTrigger, - Listener, ListenerHandle, RawIo, RawStream, StatusAt, TransportEnvelope, TransportError, - Unit0Trigger, WireStatus, WireStatusClass, + RawIo, RawStream, StatusAt, TransportEnvelope, TransportError, Unit0Trigger, WireStatus, + WireStatusClass, }; /// The default admin-scope CEILING for an identity provider that names none (`read-only`). Relocated diff --git a/crates/busbar-contract/src/transport/mod.rs b/crates/busbar-contract/src/transport/mod.rs index eede420f86..f98dd93932 100644 --- a/crates/busbar-contract/src/transport/mod.rs +++ b/crates/busbar-contract/src/transport/mod.rs @@ -2,10 +2,11 @@ //! //! A transport cannot name a plane and cannot name a unit. It yields and writes frames, inbound //! and outbound, and it knows no protocol and no principal. A transport is a swappable kind (#3): -//! compiled in, or dropped in over the HOT lane's `#[repr(C)]` transport decl and admitted by the same -//! signed-load pipeline as every plugin. The controls on one are that admission, review, the source -//! denylist, and the frame-honesty tests that turn red for a transport whose reported byte counts -//! inflate or deflate against what actually moved. +//! compiled in or dropped in, it is one memory-ABI framer door the connector serves over the host's +//! sockets, admitted by the same signed-load pipeline as every plugin. There is no transport trait: +//! the connector reaches a transport only through its door (step 36). The controls on one are that +//! admission, review, the source denylist, and the frame-honesty tests that turn red for a transport +//! whose reported byte counts inflate or deflate against what actually moved. // The transport-facing vocabulary, folded in from the former `busbar-contract-transport` crate // (DECISIONS #38): a KIND is a match-arm, not a crate, so the transport half lives here as one @@ -29,11 +30,7 @@ pub use stack::{ Framer, FramerOut, HostTime, Located, Role, Side, TransportRow, }; -use crate::bounded::ScratchBytes; -use crate::dest::{TransportKeyHandle, VerifiedDestination}; use crate::ids::StreamId; -use crate::plugin::Plugin; -use crate::unit::{ConfigView, Refusal}; // `Frame` is the plane-facing frame, which lives one level up in the contract's own `wire` module // (it borrows the arena the contract owns). Every other wire name below arrives via the // `pub use wire::{…}` re-export of this module's own transport `wire` submodule. @@ -102,8 +99,8 @@ pub use trust::{ClientIdentity, EgressTrust}; /// `busbar_contract::transport::` resolves exactly as `busbar_contract::transport::` did. pub use wire::{ ArrivalRecord, CertFacts, CloseReason, Conn, ConnHandle, Decode, Direction, DiscardCode, - Encode, FrameMeta, Framing, Handoff, HandshakeTrigger, Listener, ListenerHandle, RawIo, - RawStream, StatusAt, TransportError, Unit0Trigger, WireStatus, WireStatusClass, + Encode, FrameMeta, Framing, Handoff, HandshakeTrigger, RawIo, RawStream, StatusAt, + TransportError, Unit0Trigger, WireStatus, WireStatusClass, }; /// Everything a transport declares about itself. @@ -175,131 +172,3 @@ pub trait TransportMeta { selector_forms: Self::SELECTOR_FORMS, }]; } - -/// The transport's own configuration block, as a read-only view. -pub trait TransportConfigView: ConfigView { - /// The address this transport should bind to. - fn bind(&self) -> Option<&str>; -} - -/// How bytes move. -/// -/// Every call is asynchronous except closing, which must be able to run on a drop path. -pub trait Transport: Plugin + Send + Sync + 'static { - /// What the bottom layer knows about a connection, before any plane is chosen. - fn arrival(&self, conn: &Conn) -> ArrivalRecord; - - /// Open a listening socket. - fn listen<'a>( - &'a self, - cfg: &'a dyn TransportConfigView, - keys: &'a TransportKeyHandle, - ) -> Fut<'a, Listener>; - - /// Take the next connection off a listener. - fn accept<'a>(&'a self, l: &'a Listener) -> Fut<'a, Conn>; - - /// Dial a verified destination. - fn dial<'a>( - &'a self, - dest: &'a VerifiedDestination, - keys: &'a TransportKeyHandle, - ) -> Fut<'a, Conn>; - - /// The frame pump for one connection. - /// - /// This takes one clone of the connection handle; writing, closing and upgrading take another. - fn frames(&self, conn: Conn) -> FrameStream; - - /// Queue bytes for one stream, returning how many were queued. - /// - /// The bytes are copied into a per-connection slab, because the arena they came from is reset - /// as soon as the frame is queued. - fn write<'a>( - &'a self, - conn: &'a Conn, - stream: StreamId, - bytes: ScratchBytes<'a>, - ) -> Fut<'a, usize>; - - /// Render an outbound envelope and body as this transport's own wire bytes. - /// - /// The byte layout of an envelope belongs to the transport, and only to the transport: an - /// `http` request line and folded headers, a gRPC length-prefixed message, a WebSocket payload - /// are three different objects that no neutral layout describes. The egress unit used to write - /// one anyway — `name: value`, a blank line, the body — because it must run the lane - /// cross-check over the same bytes it hands to `write`, and had nothing else to run it over. - /// That made the check honest about ONE buffer and wrong about which bytes were in it. - /// - /// The fields arrive POST-DECORATION: what the egress-auth unit added, and what it substituted - /// a secret into, are already here. The bytes that come back are the bytes the cross-check - /// reads and the bytes `write` is given, which is what the design means by the envelope still - /// equalling the verified destination after decoration. - /// - /// Into the arena, because the hot path allocates nowhere else. - /// - /// # Errors - /// - /// The arena had no room, or the envelope names something this transport cannot express. - fn encode_envelope<'a>( - &self, - fields: &[(&str, &[u8])], - body: &[u8], - arena: &'a dyn crate::bounded::PlaneAlloc, - ) -> Result, crate::wire::Encode>; - - /// Adopt a connection a lower layer is handing up, becoming the new top of the stack. - /// - /// The upgrade belongs to the TARGET, not the source. The connection that comes out belongs to - /// this transport's registry, and only this transport can put it there — which is why the - /// source could never express the upgrade as a method of its own: it would have had to return a - /// handle it has no way to build. Here the target asks the source for its stream through - /// [`Transport::detach`], and what it gets back is a stream the source has already given up. - /// - /// A source this transport does not compose over is refused with - /// [`TransportError::HandoffMismatch`], and so is a stream that is not the shape this layer can - /// adopt: an upgrade neither leg declared is not one the session may continue on. - fn adopt<'a>( - &'a self, - from: &'a dyn Transport, - conn: Conn, - keys: &'a TransportKeyHandle, - ) -> Fut<'a, Conn>; - - /// Give up the byte stream under a connection, for the layer adopting it. - /// - /// Removes the connection from this transport's own registry, so it is never read from or - /// written to here again. `None` when the connection is unknown, when a reader still holds it - /// (an upgrade never races an in-flight read, by the at-most-one-upgrade-in-flight rule; a - /// caller that violates that ordering sees `None` rather than a torn stream), or when this - /// transport has no raw stream to give — which every implementor that is a bottom layer or - /// hands nothing up must still answer, explicitly, as `None`. - fn detach(&self, conn: &Conn) -> Option; - - /// The layer this instance was actually built over, where it was built over one. - /// - /// What the registry's boot check compares against `COMPOSES_OVER`. A transport that opens its - /// own socket answers `None` and is checked only on what it declares; a composed one names the - /// layer under it, and a name that is not in its declaration refuses the boot. - fn composed_over(&self) -> Option<&'static str>; - - /// Close a connection. - fn close(&self, conn: Conn, reason: CloseReason); - - /// Write a refusal for bytes that never reached a plane. - /// - /// This is the pre-decode path: no plane is known yet, so the kernel renders the refusal - /// through the transport's own generic envelope rather than through a dialect. - /// - /// `stream` names how much of the connection the refusal is about. On a transport whose first - /// unit opens per stream, a refusal is one stream's — the other streams on that connection - /// belong to other units, which have done nothing wrong and must go on to complete. `None` is - /// the whole connection, which is the only honest reading on a transport that carries one. - fn unit0_refusal<'a>( - &'a self, - conn: Conn, - stream: Option, - refusal: &'a Refusal, - bytes: ScratchBytes<'a>, - ) -> Fut<'a, ()>; -} diff --git a/crates/busbar-contract/src/transport/wire.rs b/crates/busbar-contract/src/transport/wire.rs index ebf4d9f18f..8a7fa82ad0 100644 --- a/crates/busbar-contract/src/transport/wire.rs +++ b/crates/busbar-contract/src/transport/wire.rs @@ -15,7 +15,6 @@ use core::fmt; use std::future::Future; use std::pin::Pin; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; /// Which way bytes are moving. @@ -389,62 +388,6 @@ pub struct Handoff { pub binding_fact: &'static str, } -/// A listening socket a transport opened. -/// -/// The kernel holds it; a plane never sees one. The handle is opaque because what is behind it is -/// the transport's business and nothing else's. -/// -/// The identity is the contract's, minted here at construction and never by a transport, because a -/// node always holds at least two listeners — data and admin — and the only thing that told them -/// apart was a bound address string a configuration is free to duplicate. A transport keying its -/// own per-listener state on that string was keying it on something two listeners could share. -#[derive(Clone)] -pub struct Listener { - id: u64, - handle: Arc, -} - -/// The next listener identity. Node-local and monotonic; nothing outside the node ever sees one. -static NEXT_LISTENER_ID: AtomicU64 = AtomicU64::new(1); - -impl Listener { - /// Wrap a transport's own listener, giving it an identity nothing else holds. - #[must_use] - pub fn new(handle: Arc) -> Self { - Self { - id: NEXT_LISTENER_ID.fetch_add(1, Ordering::Relaxed), - handle, - } - } - - /// The listener's node-local identity. - #[must_use] - pub fn id(&self) -> u64 { - self.id - } - - /// The local address the listener is bound to. - #[must_use] - pub fn local_addr(&self) -> String { - self.handle.local_addr() - } -} - -impl fmt::Debug for Listener { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Listener") - .field("id", &self.id) - .field("local_addr", &self.handle.local_addr()) - .finish() - } -} - -/// What a transport puts behind a [`Listener`]. -pub trait ListenerHandle: Send + Sync { - /// The local address the listener is bound to. - fn local_addr(&self) -> String; -} - /// A connection a transport accepted or dialled. /// /// The type index calls this a cloneable, reference-counted handle: the frame pump takes one diff --git a/crates/busbar-contract/src/wire.rs b/crates/busbar-contract/src/wire.rs index 4c5af6648b..c520e83c94 100644 --- a/crates/busbar-contract/src/wire.rs +++ b/crates/busbar-contract/src/wire.rs @@ -11,8 +11,8 @@ pub use crate::transport::wire::{ ArrivalRecord, CertFacts, CloseReason, Conn, ConnHandle, Decode, Direction, DiscardCode, - Encode, FrameMeta, Framing, Handoff, HandshakeTrigger, Listener, ListenerHandle, RawIo, - RawStream, StatusAt, TransportError, Unit0Trigger, WireStatus, WireStatusClass, + Encode, FrameMeta, Framing, Handoff, HandshakeTrigger, RawIo, RawStream, StatusAt, + TransportError, Unit0Trigger, WireStatus, WireStatusClass, }; use crate::bounded::{BoundedVec, ScratchBytes, SlabBytes, MAX_CURSOR_BYTES, MAX_KEYS}; diff --git a/crates/busbar-contract/tests/handles_and_handoff.rs b/crates/busbar-contract/tests/handles_and_handoff.rs index 0bf6947f40..0e80ff4487 100644 --- a/crates/busbar-contract/tests/handles_and_handoff.rs +++ b/crates/busbar-contract/tests/handles_and_handoff.rs @@ -3,9 +3,9 @@ //! The opaque handles, and the one value that crosses an in-band upgrade. //! -//! A listener, a connection and a detached stream are all "a handle plus the few facts the layer -//! above is allowed to read". Every one of those facts was reachable and none of them was read back -//! by a test: the bound address a listener reports, the peer a connection names, and — the one that +//! A connection and a detached stream are each "a handle plus the few facts the layer above is +//! allowed to read". Every one of those facts was reachable and none of them was read back by a +//! test: the peer a connection names, and — the one that //! decides whether a session happens at all — the layer a detached stream says handed it up. A //! handoff is only admissible between the layers that declared it, so a stream that forgot which //! layer made it is a session adopted by whoever dialled it. @@ -13,16 +13,9 @@ //! The `Debug` renderings are proofs too, not decoration: they are what a transport author reads off //! a log line while an upgrade is failing, and each of these types hand-rolls one. -use busbar_contract::transport::{Conn, ConnHandle, Listener, ListenerHandle, RawStream}; +use busbar_contract::transport::{Conn, ConnHandle, RawStream}; use std::sync::Arc; -struct Bound(&'static str); -impl ListenerHandle for Bound { - fn local_addr(&self) -> String { - self.0.to_string() - } -} - struct Socket { id: u64, peer: &'static str, @@ -36,31 +29,6 @@ impl ConnHandle for Socket { } } -#[test] -fn a_listener_reports_the_address_its_own_handle_is_bound_to() { - // The address comes from the transport's handle, not from the contract: a listener that - // answered with something of its own would have the kernel reporting a socket nobody is on. - let listener = Listener::new(Arc::new(Bound("127.0.0.1:8443"))); - assert_eq!(listener.local_addr(), "127.0.0.1:8443"); - let rendered = format!("{listener:?}"); - assert!(rendered.contains("127.0.0.1:8443"), "{rendered}"); - assert!(rendered.contains("Listener"), "{rendered}"); -} - -#[test] -fn listener_identities_are_minted_here_and_never_repeat() { - // Two listeners on one address are still two listeners, and the identity is what tells them - // apart. Minted at construction, monotonic, and never handed to a transport to choose. - let first = Listener::new(Arc::new(Bound("0.0.0.0:0"))); - let second = Listener::new(Arc::new(Bound("0.0.0.0:0"))); - assert!( - second.id() > first.id(), - "identities go forward: {} then {}", - first.id(), - second.id() - ); -} - #[test] fn a_connection_answers_with_its_own_handles_identity_and_peer() { let conn = Conn::new(Arc::new(Socket { diff --git a/crates/busbar-contract/tests/object_safety.rs b/crates/busbar-contract/tests/object_safety.rs index 3677597f34..483dfef36a 100644 --- a/crates/busbar-contract/tests/object_safety.rs +++ b/crates/busbar-contract/tests/object_safety.rs @@ -1,4 +1,4 @@ -//! One object-safety fixture per kind trait, and a working implementation of the two largest. +//! One object-safety fixture per kind trait, and a working implementation of the plane. //! //! The honesty table of the design lists object safety as a compile-time property proven by a //! fixture per kind. That is what the assertions below are: each one only compiles if the trait @@ -13,8 +13,7 @@ use busbar_contract::bounded::{ Facts, Ir, Labels, PlaneAlloc, PlaneAllocBudget, ScratchBytes, Span, }; use busbar_contract::dest::{ - AuthDecoration, DestinationFacts, EgressBody, RoutePlan, TransportKeyHandle, - VerifiedDestination, + AuthDecoration, DestinationFacts, EgressBody, RoutePlan, VerifiedDestination, }; use busbar_contract::ids::{ AdminVerbId, LaneId, MeterClassDecl, OpClassId, RecordSchemaId, SchemeKey, StreamId, @@ -27,23 +26,19 @@ use busbar_contract::plane::{ Ingress, Plane, PlaneMeta, PlaneSessionState, Progress, Response, SessionPlane, UnitDraft, }; use busbar_contract::plugin::{AbiVersion, Kind, Plugin}; -use busbar_contract::transport::{ - Carrier, FrameStream, Framer, Fut, Transport, TransportConfigView, TransportMeta, -}; +use busbar_contract::transport::{Carrier, Framer, TransportMeta}; use busbar_contract::unit::{ AuditFacts, Clock, ConfigView, Ctx, FinishClass, Refusal, SessionView, TransportView, Unit, UnitEnd, UsageLocators, }; use busbar_contract::wire::{ - ArrivalRecord, CloseReason, Conn, Decode, DiscardCode, Encode, Frame, FrameCursor, Listener, - StatusAt, TransportError, Unit0Trigger, + Decode, DiscardCode, Encode, Frame, FrameCursor, StatusAt, Unit0Trigger, }; // ── the fixture per kind: each of these only compiles if the trait is object-safe ───────────── const _PLANE: Option<&dyn Plane> = None; const _SESSION_PLANE: Option<&dyn SessionPlane> = None; -const _TRANSPORT: Option<&dyn Transport> = None; // The two transport roles (TRANSPORT-STACK): a transport plugin is one or the other. const _CARRIER: Option<&dyn Carrier> = None; const _FRAMER: Option<&dyn Framer> = None; @@ -103,12 +98,6 @@ impl ConfigView for EmptyConfig { } } -impl TransportConfigView for EmptyConfig { - fn bind(&self) -> Option<&str> { - None - } -} - /// A one-shot transport stack. struct OneShotStack; @@ -296,9 +285,10 @@ impl SessionPlane for FixturePlane { } } -// ── a transport, implemented in full ────────────────────────────────────────────────────────── +// ── a transport's declarations ──────────────────────────────────────────────────────────────── -/// The smallest transport that answers every call the pump makes. +/// A transport's declarations. A transport is a memory-ABI framer door, not a trait object (step +/// 36), so what a fixture can state of one is its kind and its declared facts. struct FixtureTransport; impl Plugin for FixtureTransport { @@ -331,93 +321,6 @@ impl TransportMeta for FixtureTransport { const STATUS_NAMESPACE: Option<&'static str> = Some("fixture"); } -impl Transport for FixtureTransport { - fn arrival(&self, conn: &Conn) -> ArrivalRecord { - ArrivalRecord { - source: conn.peer(), - port: 0, - alpn: None, - sni: None, - peer_cert: None, - transport_chain: vec!["fixture"], - } - } - - fn listen<'a>( - &'a self, - _cfg: &'a dyn TransportConfigView, - _keys: &'a TransportKeyHandle, - ) -> Fut<'a, Listener> { - Box::pin(async { Err(TransportError::Refused) }) - } - - fn accept<'a>(&'a self, _l: &'a Listener) -> Fut<'a, Conn> { - Box::pin(async { Err(TransportError::Closed) }) - } - - fn dial<'a>( - &'a self, - _dest: &'a VerifiedDestination, - _keys: &'a TransportKeyHandle, - ) -> Fut<'a, Conn> { - Box::pin(async { Err(TransportError::Refused) }) - } - - fn frames(&self, _conn: Conn) -> FrameStream { - Box::pin(futures::stream::empty()) - } - - fn write<'a>( - &'a self, - _conn: &'a Conn, - _stream: StreamId, - bytes: ScratchBytes<'a>, - ) -> Fut<'a, usize> { - let n = bytes.len(); - Box::pin(async move { Ok(n) }) - } - - fn encode_envelope<'a>( - &self, - _fields: &[(&str, &[u8])], - body: &[u8], - arena: &'a dyn PlaneAlloc, - ) -> Result, Encode> { - arena - .alloc_bytes(body) - .map_err(|_| Encode::ScratchExhausted) - } - - fn adopt<'a>( - &'a self, - _from: &'a dyn Transport, - _conn: Conn, - _keys: &'a TransportKeyHandle, - ) -> Fut<'a, Conn> { - Box::pin(async { Err(TransportError::HandoffMismatch) }) - } - - fn detach(&self, _conn: &Conn) -> Option { - None - } - - fn composed_over(&self) -> Option<&'static str> { - None - } - - fn close(&self, _conn: Conn, _reason: CloseReason) {} - - fn unit0_refusal<'a>( - &'a self, - _conn: Conn, - _stream: Option, - _refusal: &'a Refusal, - _bytes: ScratchBytes<'a>, - ) -> Fut<'a, ()> { - Box::pin(async { Ok(()) }) - } -} - // ── the tests ───────────────────────────────────────────────────────────────────────────────── /// Every kind can be held behind a pointer, which is how the registry holds one. @@ -425,11 +328,9 @@ impl Transport for FixtureTransport { fn every_kind_is_object_safe() { let plane: &dyn Plane = &FixturePlane; let session_plane: &dyn SessionPlane = &FixturePlane; - let transport: &dyn Transport = &FixtureTransport; assert_eq!(plane.kind(), Kind::Plane); assert_eq!(session_plane.key(), "fixture"); - assert_eq!(transport.kind(), Kind::Transport); } /// A plane call runs, with a real context, and the borrow story holds. diff --git a/crates/busbar-contract/tests/transport_registry.rs b/crates/busbar-contract/tests/transport_registry.rs index 3d0f8da47a..85abe07871 100644 --- a/crates/busbar-contract/tests/transport_registry.rs +++ b/crates/busbar-contract/tests/transport_registry.rs @@ -1,18 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! The registry's boot check over what a node was actually composed out of, the kind's ABI, and the -//! identity a listener carries. +//! The registry's boot check over what a node was actually composed out of, and the kind's ABI. //! //! `COMPOSES_OVER` was declared, typed, and read by nothing: a transport could name a layer no //! deployment had, and a composition root could build one over a layer it never declared, and both //! booted. A declaration nothing checks is the frame-honesty problem one layer up — the stack a //! node reports is the stack its declarations describe, so the description has to be true. -use busbar_contract::transport::{ - check_composition, CompositionError, Listener, ListenerHandle, Registered, -}; -use std::sync::Arc; +use busbar_contract::transport::{check_composition, CompositionError, Registered}; fn declared( key: &'static str, @@ -29,8 +25,8 @@ fn declared( #[test] fn the_real_stack_boots() { // `tcp → tls → http → { sse, ws, grpc }`, with `stdio` standing on its own — the seven - // transports' actual `TransportMeta::COMPOSES_OVER` declarations and `Transport::composed_over` - // answers, side by side. `tcp`, `tls` and `http` each open their own socket and answer `None` + // transports' actual `TransportMeta::COMPOSES_OVER` declarations and the `composed_over` each + // registered row records, side by side. `tcp`, `tls` and `http` each open their own socket and answer `None` // regardless of what they declare; `sse`, `ws` and `grpc` are only ever built by holding (or // being handed) another instance, so they name it; `stdio` composes over nothing at all. let registry = [ @@ -104,33 +100,3 @@ fn the_transport_kind_has_one_abi_generation() { busbar_contract::transport::AbiVersion(1) ); } - -struct Bound(&'static str); -impl ListenerHandle for Bound { - fn local_addr(&self) -> String { - self.0.to_string() - } -} - -#[test] -fn two_listeners_on_one_address_are_still_two_listeners() { - // A node always holds at least two — data and admin — and a configuration is free to give them - // the same bound address. A transport keying per-listener state on the address string was - // keying it on something two listeners could share. - let data = Listener::new(Arc::new(Bound("127.0.0.1:8080"))); - let admin = Listener::new(Arc::new(Bound("127.0.0.1:8080"))); - assert_eq!(data.local_addr(), admin.local_addr()); - assert_ne!(data.id(), admin.id(), "the identity is not the address"); -} - -#[test] -fn a_listener_keeps_its_identity_across_clones() { - let listener = Listener::new(Arc::new(Bound("127.0.0.1:0"))); - let held = listener.clone(); - assert_eq!( - listener.id(), - held.id(), - "cloning a handle is not a new listener" - ); - assert!(format!("{listener:?}").contains(&listener.id().to_string())); -} diff --git a/crates/busbar/build.rs b/crates/busbar/build.rs index af389df585..8d9a9653e4 100644 --- a/crates/busbar/build.rs +++ b/crates/busbar/build.rs @@ -582,23 +582,23 @@ fn linked_transports_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> ( .iter() .any(|(f, a)| *f == key && a.split_whitespace().any(|x| x == DOOR_AXIS)); let n = rows.matches("LinkedWire {").count(); - let (build, claims) = if door { - // A door row's entry exports its memory-ABI `door`; the root's doors module (mounted - // here under its own name, the tests including this file have no root) builds it, and - // reads every scheme it claims off its Statement. - door_builds.push_str(&format!( - "fn __door_build_{n}(lower: Option, settings: &::busbar_contract::transport::TransportSettings) -> Wire {{\n \ - self::__busbar_doors::build(::{entry}::KEY, ::{entry}::door, lower, settings, None)\n}}\n\ - fn __door_claims_{n}() -> Vec<&'static str> {{\n \ - self::__busbar_doors::claims_of(::{entry}::door)\n}}\n" - )); - (format!("__door_build_{n}"), format!("__door_claims_{n}")) - } else { - door_builds.push_str(&format!( - "fn __row_claims_{n}() -> Vec<&'static str> {{\n vec![::{entry}::KEY]\n}}\n" - )); - (format!("::{entry}::build"), format!("__row_claims_{n}")) - }; + // A TRANSPORT LOADS ONLY THROUGH ITS DOOR (step 36): a transport row off the door axis is a + // manifest the root refuses (`src/linked_gen.rs`), and so is this table. + assert!( + door, + "Cargo.toml: `{key}` is on the transport axis but not the `{DOOR_AXIS}` axis; a \ + transport is linked only as its memory-ABI door" + ); + // A door row's entry exports its memory-ABI `door`; the root's doors module (mounted here + // under its own name, the tests including this file have no root) builds it, and reads + // every scheme it claims off its Statement. + door_builds.push_str(&format!( + "fn __door_build_{n}(settings: &::busbar_contract::transport::TransportSettings) -> Wire {{\n \ + self::__busbar_doors::build(::{entry}::KEY, ::{entry}::door, settings, None)\n}}\n\ + fn __door_claims_{n}() -> Vec<&'static str> {{\n \ + self::__busbar_doors::claims_of(::{entry}::door)\n}}\n" + )); + let (build, claims) = (format!("__door_build_{n}"), format!("__door_claims_{n}")); rows.push_str(&format!( " LinkedWire {{ key: ::{entry}::KEY, composes_over: ::{entry}::COMPOSES_OVER, \ session: ::{entry}::SESSION, build: {build}, claims: {claims} }},\n" @@ -607,9 +607,9 @@ fn linked_transports_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> ( let source = format!( "// @generated by build.rs from Cargo.toml `[package.metadata.busbar.linked]` (the transport axis).\n\ /// A built wire.\n\ - pub(crate) type Wire = ::std::sync::Arc;\n\ - /// A wire's build: handed the layer built beneath it, where one is, and the settings.\n\ - pub(crate) type BuildWire = fn(Option, &::busbar_contract::transport::TransportSettings) -> Wire;\n\ + pub(crate) type Wire = ::std::sync::Arc<::busbar_core_connector::wire::HostWire>;\n\ + /// A wire's build: handed the settings, it answers the wire the host serves the row's door over.\n\ + pub(crate) type BuildWire = fn(&::busbar_contract::transport::TransportSettings) -> Wire;\n\ /// One linked wire: its registry key, the layers it declares, whether it carries a session,\n\ /// its build, and every scheme its entry claims (its own first).\n\ #[allow(dead_code)]\n\ @@ -626,8 +626,8 @@ fn linked_transports_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> ( /// The transport rows the manifest declares that this build does not link.\n\ #[allow(dead_code)]\n\ pub(crate) const LINKED_TRANSPORTS_OFF: usize = {off};\n\ - /// The boot seal's fold: each round builds the first row whose every linked layer is built,\n\ - /// over the first of its declared layers that was. Panics where the rows declare each other.\n\ + /// The boot seal's fold: each round builds the first row whose every linked layer is built.\n\ + /// Panics where the rows declare each other.\n\ #[allow(dead_code)]\n\ pub(crate) fn fold_linked_transports(\n \ settings: &::busbar_contract::transport::TransportSettings,\n\ @@ -645,10 +645,7 @@ fn linked_transports_source(manifest: &str, enabled: &dyn Fn(&str) -> bool) -> ( }})\n \ .unwrap_or_else(|| panic!(\"`{{}}` has no bottom\", pending[0].key));\n \ let row = pending.remove(at);\n \ - let lower = row.composes_over.iter().find_map(|l| {{\n \ - built.iter().find(|(r, _)| r.key == *l).map(|(_, t)| ::std::sync::Arc::clone(t))\n \ - }});\n \ - built.push((row, (row.build)(lower, settings)));\n \ + built.push((row, (row.build)(settings)));\n \ }}\n \ built\n\ }}\n" diff --git a/crates/busbar/src/linked_gen.rs b/crates/busbar/src/linked_gen.rs index 2103c800f6..1b2b891425 100644 --- a/crates/busbar/src/linked_gen.rs +++ b/crates/busbar/src/linked_gen.rs @@ -408,33 +408,29 @@ pub(crate) fn linked_source( .filter(|(_, a)| a.iter().any(|x| x == TRANSPORT_AXIS)) .enumerate() { - let (build, claims, upgrades) = if axes.iter().any(|x| x == DOOR_AXIS) { - door_builds.push_str(&format!( - "fn __door_build_{n}(\n lower: Option>,\n \ - settings: &busbar_contract::transport::TransportSettings,\n\ - ) -> std::sync::Arc {{\n \ - crate::root::doors::build({e}::KEY, {e}::door, lower, settings, Some(crate::root::connector::address_carrier()))\n}}\n\ - fn __door_claims_{n}() -> Vec<&'static str> {{\n \ - crate::root::doors::claims_of({e}::door)\n}}\n\ - fn __door_upgrades_{n}() -> Vec<&'static str> {{\n \ - crate::root::doors::upgrades_of({e}::door)\n}}\n" - )); - ( - format!("__door_build_{n}"), - format!("__door_claims_{n}"), - format!("__door_upgrades_{n}"), - ) - } else { - door_builds.push_str(&format!( - "fn __row_claims_{n}() -> Vec<&'static str> {{\n vec![{e}::KEY]\n}}\n\ - fn __row_upgrades_{n}() -> Vec<&'static str> {{\n Vec::new()\n}}\n" - )); - ( - format!("{e}::build"), - format!("__row_claims_{n}"), - format!("__row_upgrades_{n}"), - ) - }; + // A TRANSPORT LOADS ONLY THROUGH ITS DOOR (step 36): there is no transport trait for a row + // to build itself as, so a transport row without the door axis is a manifest this root + // cannot link. + assert!( + axes.iter().any(|x| x == DOOR_AXIS), + "Cargo.toml: `{e}` is on the `{TRANSPORT_AXIS}` axis but not the `{DOOR_AXIS}` axis; a \ + transport is linked only as its memory-ABI door" + ); + door_builds.push_str(&format!( + "fn __door_build_{n}(\n \ + settings: &busbar_contract::transport::TransportSettings,\n\ + ) -> std::sync::Arc {{\n \ + crate::root::doors::build({e}::KEY, {e}::door, settings, Some(crate::root::connector::address_carrier()))\n}}\n\ + fn __door_claims_{n}() -> Vec<&'static str> {{\n \ + crate::root::doors::claims_of({e}::door)\n}}\n\ + fn __door_upgrades_{n}() -> Vec<&'static str> {{\n \ + crate::root::doors::upgrades_of({e}::door)\n}}\n" + )); + let (build, claims, upgrades) = ( + format!("__door_build_{n}"), + format!("__door_claims_{n}"), + format!("__door_upgrades_{n}"), + ); out.push_str(&format!( "crate::root::linked::LinkedTransport {{ key: {e}::KEY, composes_over: \ {e}::COMPOSES_OVER, build: {build}, claims: {claims}, upgrades: {upgrades} }}, " diff --git a/crates/busbar/src/root/doors.rs b/crates/busbar/src/root/doors.rs index fc215aea88..41afecf331 100644 --- a/crates/busbar/src/root/doors.rs +++ b/crates/busbar/src/root/doors.rs @@ -5,9 +5,9 @@ // on the memory ABI — compiled in (its row's `door`) or dropped in (a `kind: transport` image with // the one door symbol) — is admitted through the ONE dispatcher's door validation, opened, and // presented to the connector as the entry's framer table ([`Dispatched`]); the connector serves it -// over the host's sockets ([`HostWire`]), which the root presents at the kernel's legacy transport -// seam through [`RootWire`]. Compiled in and dropped in are the same table, the same crossing and -// the same wire. +// over the host's sockets ([`HostWire`]). Compiled in and dropped in are the same table, the same +// crossing and the same wire. A transport reaches the connector only as this framer door: there is +// no transport trait for the root to present it at (step 36). // // Written to be `include!`d: the root mounts it as `root::doors`, and the build's generated table of // linked transports (`$OUT_DIR/linked_transports.rs`, which the integration tests include) mounts it @@ -267,149 +267,6 @@ impl FramerDoor for Dispatched { } } -/// THE ROOT'S ADAPTER FROM THE CONNECTOR'S HOST-SIDE SURFACE TO THE KERNEL'S LEGACY TRANSPORT SEAM. -/// The connector is core and presents no plugin face; the kernel's listeners, accept loop and -/// upgrades still speak `busbar_contract::Transport`, so the root wraps a [`HostWire`] in this and -/// every method ONLY delegates. Transitional: `RootWire` deletes with the legacy stack at TODO step 36. -/// -/// No entry composes over another (ARCHITECT Q128 U7; `BUSBAR-1.6.0.md` :4721): every inbound -/// socket is the connector's listener's, and a stream an entry hands up is adopted by the entry the -/// connector picks, so this adapter binds, accepts and adopts nothing itself. -pub struct RootWire { - wire: HostWire, -} - -impl std::fmt::Debug for RootWire { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("RootWire") - .field("wire", &self.wire) - .finish_non_exhaustive() - } -} - -impl RootWire { - /// `wire`, presented at the legacy seam. - #[must_use] - pub fn new(wire: HostWire) -> Self { - Self { wire } - } -} - -impl busbar_contract::Plugin for RootWire { - fn key(&self) -> &'static str { - self.wire.key() - } - fn kind(&self) -> busbar_contract::Kind { - busbar_contract::Kind::Transport - } - fn abi(&self) -> busbar_contract::transport::AbiVersion { - busbar_contract::transport::TRANSPORT_ABI - } -} - -impl busbar_contract::Transport for RootWire { - fn arrival( - &self, - conn: &busbar_contract::transport::wire::Conn, - ) -> busbar_contract::transport::wire::ArrivalRecord { - self.wire.arrival(conn) - } - - /// An entry listens through nothing here: every inbound socket is the connector's listener's - /// (the one listener source, ARCHITECT ruling 2026-09-30). - fn listen<'a>( - &'a self, - _cfg: &'a dyn busbar_contract::TransportConfigView, - _keys: &'a busbar_contract::TransportKeyHandle, - ) -> busbar_contract::Fut<'a, busbar_contract::transport::wire::Listener> { - Box::pin(async move { Err(busbar_contract::transport::wire::TransportError::Refused) }) - } - - /// Nothing is accepted here: the connector's listener accepts. - fn accept<'a>( - &'a self, - _l: &'a busbar_contract::transport::wire::Listener, - ) -> busbar_contract::Fut<'a, busbar_contract::transport::wire::Conn> { - Box::pin(async move { Err(busbar_contract::transport::wire::TransportError::Closed) }) - } - - fn dial<'a>( - &'a self, - dest: &'a busbar_contract::VerifiedDestination, - keys: &'a busbar_contract::TransportKeyHandle, - ) -> busbar_contract::Fut<'a, busbar_contract::transport::wire::Conn> { - self.wire.dial(dest, keys) - } - - fn frames( - &self, - conn: busbar_contract::transport::wire::Conn, - ) -> busbar_contract::transport::FrameStream { - self.wire.frames(conn) - } - - fn write<'a>( - &'a self, - conn: &'a busbar_contract::transport::wire::Conn, - stream: busbar_contract::StreamId, - bytes: busbar_contract::ScratchBytes<'a>, - ) -> busbar_contract::Fut<'a, usize> { - // The kernel's write carries no text bit yet: it takes FrameMeta::text from the plane's - // PIECE_OUT_TEXT after SERVE-WIRE's P1; until then every write here is binary. - self.wire.write(conn, stream, bytes, false) - } - - fn encode_envelope<'a>( - &self, - fields: &[(&str, &[u8])], - body: &[u8], - arena: &'a dyn busbar_contract::PlaneAlloc, - ) -> Result, busbar_contract::transport::wire::Encode> { - self.wire.encode_envelope(fields, body, arena) - } - - /// Nothing is adopted at this seam: which entry adopts a handed-up stream is the connector's - /// choice (ARCHITECT Q128 U7). - fn adopt<'a>( - &'a self, - _from: &'a dyn busbar_contract::Transport, - conn: busbar_contract::transport::wire::Conn, - keys: &'a busbar_contract::TransportKeyHandle, - ) -> busbar_contract::Fut<'a, busbar_contract::transport::wire::Conn> { - self.wire.adopt(conn, keys) - } - - fn detach( - &self, - conn: &busbar_contract::transport::wire::Conn, - ) -> Option { - self.wire.detach(conn) - } - - /// No entry is composed over another. - fn composed_over(&self) -> Option<&'static str> { - None - } - - fn close( - &self, - conn: busbar_contract::transport::wire::Conn, - reason: busbar_contract::transport::wire::CloseReason, - ) { - self.wire.close(conn, reason); - } - - fn unit0_refusal<'a>( - &'a self, - conn: busbar_contract::transport::wire::Conn, - stream: Option, - refusal: &'a busbar_contract::Refusal, - bytes: busbar_contract::ScratchBytes<'a>, - ) -> busbar_contract::Fut<'a, ()> { - self.wire.unit0_refusal(conn, stream, refusal, bytes) - } -} - /// Where a served wire's framed connections find their carrier: the root hands the process's /// connector's address carrier (`crate::root::connector::address_carrier`); a mount with no root /// hands none, and a framed dial on it is refused. @@ -423,46 +280,30 @@ fn ride(wire: HostWire, riding: Riding) -> HostWire { } } -/// A door, opened with the deployment's `settings` and served over the host's sockets, presented -/// at the legacy transport seam, its framed connections riding `riding`. -/// -/// # Errors -/// -/// The door would not open, or it composes over a layer (it does not frame the host's socket). -pub fn host_wire( - plugin: Plugin, - settings: &busbar_contract::transport::TransportSettings, - riding: Riding, -) -> Result, String> { - served(plugin, settings, riding).map(|(_, wire)| wire) -} - -/// An opened door and the wire the legacy seam presents over it. -pub type Served = (Arc, Arc); +/// An opened door and the wire the host serves it over. +pub type Served = (Arc, Arc); -/// [`host_wire`], and the opened door itself: the one instance the connector serves as an entry and -/// the legacy seam presents (a dropped-in door is opened once). +/// A door, opened with the deployment's `settings` and served over the host's sockets, its framed +/// connections riding `riding`; and the opened door itself, the one instance the connector serves +/// as an entry (a dropped-in door is opened once). /// /// # Errors /// -/// As [`host_wire`]. +/// The door would not open, or it composes over a layer (it does not frame the host's socket). pub fn served( plugin: Plugin, settings: &busbar_contract::transport::TransportSettings, riding: Riding, ) -> Result { let door: Arc = Arc::new(Dispatched::open(plugin, settings)?); - let wire = Arc::new(RootWire::new(ride( - HostWire::new(Arc::clone(&door))?, - riding, - ))); + let wire = Arc::new(ride(HostWire::new(Arc::clone(&door))?, riding)); Ok((door, wire)) } /// A linked row's build: its door admitted through the one validation, bound under the row's name /// `row` ([`row_bind`]), opened with the deployment's `settings`, served over the host's sockets. No -/// entry composes over another (ARCHITECT Q128 U7), so the layer the registry hands a build is not -/// read: the carrier is the connector's choice from the target's scheme (`riding`, [`Riding`]). +/// entry composes over another (ARCHITECT Q128 U7), so a build is handed no layer: the carrier is +/// the connector's choice from the target's scheme (`riding`, [`Riding`]). /// /// # Panics /// @@ -471,17 +312,15 @@ pub fn served( pub fn build( row: &str, door: DoorFn, - _lower: Option>, settings: &busbar_contract::transport::TransportSettings, riding: Riding, -) -> Arc { +) -> Arc { LinkedRow::of(door) .and_then(|linked| load_linked::(&linked, row_bind(row))) .map_err(|e| e.to_string()) .and_then(|plugin| Dispatched::open(plugin, settings)) .and_then(|door| HostWire::new(Arc::new(door))) - .map(|wire| ride(wire, riding)) - .map(|wire| Arc::new(RootWire::new(wire)) as Arc) + .map(|wire| Arc::new(ride(wire, riding))) .unwrap_or_else(|e| panic!("a linked transport door is refused: {e}")) } diff --git a/crates/busbar/src/root/linked.rs b/crates/busbar/src/root/linked.rs index 0a11788b11..7ca4cbb860 100644 --- a/crates/busbar/src/root/linked.rs +++ b/crates/busbar/src/root/linked.rs @@ -177,7 +177,8 @@ pub struct LinkedTransport { pub key: &'static str, /// The layers it declares, in the order it would take one. pub composes_over: &'static [&'static str], - /// Build the wire over `lower`. + /// Open the row's door and serve it over the host's sockets (it is handed no layer: no transport + /// names another). pub build: BuildTransport, /// Every scheme its entry claims, its own first: a door row's are read off the door's /// Statement (ONE ENTRY PER PLUGIN, the schemes are its claims), and the seal registers each. @@ -190,11 +191,11 @@ pub struct LinkedTransport { /// A row's claims, read when the seal runs. pub type ClaimsOf = fn() -> Vec<&'static str>; -/// A wire's build: handed the layer built beneath it, where one is, and the deployment's settings. +/// A wire's build: handed the deployment's settings, it answers the wire the host serves the row's +/// door over. pub type BuildTransport = fn( - Option>, &busbar_contract::transport::TransportSettings, -) -> Arc; +) -> Arc; /// One linked plane's contribution to the boot seal: the pure plane registered under its own key, /// and the claims that plane declares. diff --git a/crates/busbar/src/root/registry.rs b/crates/busbar/src/root/registry.rs index 0481607950..12228dea7a 100644 --- a/crates/busbar/src/root/registry.rs +++ b/crates/busbar/src/root/registry.rs @@ -75,14 +75,14 @@ //! order, which is the one thing the check exists to prevent — for the declarations. See the section //! above for the live routes it does not reach. //! -//! ## The shape that would pass both checks and still refuse every connection +//! ## No transport is built over another //! -//! One of the transports has a constructor that yields a serviceable transport and a constructor -//! that does not: `ws` built over nothing refuses every listen, accept and dial. A root -//! that forgot the composition would register, pass `check_composition` — because `composed_over()` -//! returns `None` and the check reads a declaration — and then refuse every connection. That is why -//! the fold hands every wire the layer it built beneath it, and why the registered rows record what -//! each was actually built over. +//! A transport is linked or dropped in only as its memory-ABI door, and the host serves every door +//! over its own sockets: the carrier is the connector's choice from the target's scheme (ARCHITECT +//! Q128 U7). A door that names a transport it composes over is refused at its build +//! (`HostWire::new`), so every registered row records no layer, and `check_composition` holds each +//! declaration against that. There is no transport trait whose instance could answer otherwise +//! (step 36). //! //! ## The fold, bottom-up — both doors, one pass //! @@ -94,11 +94,9 @@ //! row 84.) The fold does not ask which door a row came in by, except to build it. //! //! A wire is built once every layer it declares that this build carries is built, in row order -//! otherwise — so a layer always exists before anything that names it — and is handed the first of -//! its declared layers that was built (`COMPOSES_OVER` order is the composition order). A dropped-in -//! door is served by the connector over the host's sockets and is handed no layer. Its registered -//! row records the layer it answers it was composed over, and `check_composition` holds that against -//! the declaration. +//! otherwise — so a layer always exists before anything that names it — and is handed no layer: a +//! linked or dropped-in door is served by the connector over the host's sockets. Its registered row +//! records no layer, and `check_composition` holds that against the declaration. //! //! A dropped-in wire claiming a key a linked wire already holds is refused by the registry exactly //! as a second linked row with that key would be: two plugins of one kind declaring one key. @@ -106,8 +104,9 @@ use std::sync::Arc; use busbar_contract::transport::TransportSettings; -use busbar_contract::{check_composition, CompositionError, Plugin, Registered, Transport}; +use busbar_contract::{check_composition, CompositionError, Plugin, Registered}; use busbar_core_admin::admin_codec::AdminPlane; +use busbar_core_connector::wire::HostWire; use busbar_kernel::registry::{seal_claims, ClaimConflict, PlaneClaim, Registry, ResolvedOverlap}; use crate::root::linked::{Linked, LinkedClaims, LinkedTransport}; @@ -183,8 +182,8 @@ pub struct BootRegistry { pub resolved: Vec, /// Every transport as the composition check read it, in the order the fold built them. pub registered: Vec, - /// The built transports, index for index with `registered`. - pub transports: Vec>, + /// The wires the host serves the transport doors over, index for index with `registered`. + pub transports: Vec>, /// The keys of the rows that came in DROPPED IN rather than linked. pub dropped: Vec<&'static str>, } @@ -216,8 +215,10 @@ pub fn plane_claims(planes: &[LinkedClaims]) -> Vec { .collect() } -/// A scheme a transport entry claims past its own, in the registry under its own key (the entry's -/// wire serves it; ONE ENTRY PER PLUGIN, the schemes are its claims). +/// A transport entry in the registry, under one of the schemes it claims: its own, and each one past +/// it under its own key (the entry's wire serves them all; ONE ENTRY PER PLUGIN, the schemes are its +/// claims). A transport is a door the connector serves, not a trait object the registry holds +/// (step 36), so the registry holds its key. struct Claimed(&'static str); impl Plugin for Claimed { @@ -232,8 +233,8 @@ impl Plugin for Claimed { } } -/// One folded wire: its row as the composition check reads it, and the built transport. -pub type Built = (Registered, Arc); +/// One folded wire: its row as the composition check reads it, and the wire the host serves it over. +pub type Built = (Registered, Arc); /// A transport DOOR dropped into `plugins.dir`: a memory-ABI entry admitted through the one /// dispatcher's door validation and served over the host's sockets (`crate::root::doors`). @@ -244,8 +245,8 @@ pub struct DroppedDoor { pub claims: Vec<&'static str>, /// The layers it declares. pub composes_over: Vec<&'static str>, - /// The entry, served. - pub wire: Arc, + /// The wire the host serves the entry over. + pub wire: Arc, /// The opened door the wire serves: the entry the connector serves for it, after the linked /// rows (`crate::root::connector::boot`). pub door: Arc, @@ -291,10 +292,11 @@ impl Row<'_> { /// FOLD THE TRANSPORTS, BOTTOM-UP, BOTH DOORS IN ONE PASS. /// /// The rows are `linked` then `dropped` (module docs). Each round builds the first row whose every -/// declared layer this composition carries is already built, and hands it the first of its -/// declared layers that was — so a layer always exists before anything that names it. The -/// registered row records the layer the built wire answers it was composed over, which -/// `check_composition` holds against its declaration. +/// declared layer this composition carries is already built — so a layer always exists before +/// anything that names it. Every row is a door the host serves over its own sockets and is built +/// over nothing (no transport names another, ARCHITECT Q128 U7; a door that names a layer is refused +/// at its build), so its registered row records no layer, which `check_composition` holds against +/// its declaration. /// /// # Errors /// @@ -324,18 +326,14 @@ pub fn compose( }); }; let row = pending.remove(ready); - let lower = row.composes_over().iter().find_map(|l| at(&built, l)); - let (transport, claims): (Arc, Vec<&'static str>) = match row { - Row::Linked(row) => { - let lower = lower.map(|i| Arc::clone(&built[i].1)); - ((row.build)(lower, settings), (row.claims)()) - } + let (transport, claims): (Arc, Vec<&'static str>) = match row { + Row::Linked(row) => ((row.build)(settings), (row.claims)()), Row::Door(door) => (Arc::clone(&door.wire), door.claims.clone()), }; let registered = Registered { key: row.key(), composes_over: row.composes_over(), - composed_over: transport.composed_over(), + composed_over: None, }; // ONE ENTRY, ITS SCHEMES ITS CLAIMS (TRANSPORT-STACK, ONE ENTRY PER PLUGIN): every scheme // the entry claims past its own is registered under its own key, served by the same wire, @@ -372,11 +370,11 @@ pub fn seal( dropped: Dropped, settings: TransportSettings, ) -> Result { - let (registered, transports): (Vec, Vec>) = + let (registered, transports): (Vec, Vec>) = compose(linked.transports, dropped, &settings)? .into_iter() .unzip(); - let registry = register_all(®istered, &transports, linked.claims)?; + let registry = register_all(®istered, linked.claims)?; let claims = plane_claims(linked.claims); let sealed = seal_claims(&claims); @@ -459,20 +457,15 @@ fn check_claim_transports( /// The registry refused an entry — a duplicate key, or a kind it does not take. fn register_all( registered: &[Registered], - transports: &[Arc], planes: &[LinkedClaims], ) -> Result { let mut registry = Registry::new(); let core = core_planes(); - // A row registered under a scheme its entry claims past its own is the same wire under another - // key: registered as that claim, so the registry's key is the scheme. - let transports = registered.iter().zip(transports).map(|(row, t)| { - if row.key == t.key() { - Arc::clone(t) as Arc - } else { - Arc::new(Claimed(row.key)) as Arc - } - }); + // Every row is registered under its own key: an entry's own scheme and each scheme it claims + // past it, so the registry's key is the scheme. + let transports = registered + .iter() + .map(|row| Arc::new(Claimed(row.key)) as Arc); let planes = planes.iter().chain(core.iter()).map(|row| (row.plane)()); for plugin in transports.chain(planes).collect::>() { registry.register(plugin).map_err(BootRefusal::Registry)?; diff --git a/crates/busbar/src/root/tests/registry.rs b/crates/busbar/src/root/tests/registry.rs index a64c095919..49ea81894f 100644 --- a/crates/busbar/src/root/tests/registry.rs +++ b/crates/busbar/src/root/tests/registry.rs @@ -28,9 +28,7 @@ fn linked_rows() -> Vec { /// The registry the seal fills: the folded transports, then every linked plane and the core one. fn linked_registry() -> Registry { - let (rows, transports): (Vec, Vec>) = - linked_fold().into_iter().unzip(); - register_all(&rows, &transports, crate::LINKED.claims).expect("nothing collides on a key") + register_all(&linked_rows(), crate::LINKED.claims).expect("nothing collides on a key") } /// The shipped transport fold (` ` rows, in build order), as data. @@ -640,10 +638,7 @@ fn the_seal_answers_now_that_every_claim_names_a_registered_transport() { fn the_operators_body_cap_reaches_every_mounted_planes_transport() { const CAP: usize = 1024; static SEEN: std::sync::Mutex> = std::sync::Mutex::new(Vec::new()); - fn recording( - _: Option>, - settings: &TransportSettings, - ) -> Arc { + fn recording(settings: &TransportSettings) -> Arc { SEEN.lock() .expect("seen") .push(settings.request_body_max_bytes); @@ -651,7 +646,7 @@ fn the_operators_body_cap_reaches_every_mounted_planes_transport() { .transports .iter() .find(|r| r.composes_over.is_empty()); - (own.expect("a wire that opens its own socket").build)(None, settings) + (own.expect("a wire that opens its own socket").build)(settings) } let limits = busbar_kernel::config::limits::LimitsResolved { request_body_max_bytes: CAP, @@ -870,7 +865,7 @@ fn the_wires_linked_row(wire: &DroppedDoor) -> LinkedTransport { LinkedTransport { key: wire.key, composes_over: &[], - build: |_, _| dropped_doors()[0].wire.clone(), + build: |_| dropped_doors()[0].wire.clone(), claims: || vec![dropped_doors()[0].key], upgrades: Vec::new, } diff --git a/crates/busbar/tests/composition_root_names_no_linked_plugin.rs b/crates/busbar/tests/composition_root_names_no_linked_plugin.rs index 3a35e18df9..3871faf435 100644 --- a/crates/busbar/tests/composition_root_names_no_linked_plugin.rs +++ b/crates/busbar/tests/composition_root_names_no_linked_plugin.rs @@ -259,8 +259,10 @@ unit-b = "beta" /// THE TRANSPORT AND CLAIMS AXES, and the rows no feature can drop. A row whose crate is a required /// dependency is linked in every build and its key only names the row; two rows may share a crate, /// each naming its own entry; a `transport` row fills `transports` with the entry's `KEY`, -/// `COMPOSES_OVER`, `build`, its claims and its upgrade claims; a `claims` row fills `claims` with its `PLANE` and `CLAIMS`. A row -/// on an OPTIONAL crate still needs its feature, and `claims` off the plane axis is refused. +/// `COMPOSES_OVER`, and the build, claims and upgrade claims its `door` answers; a `claims` row fills +/// `claims` with its `PLANE` and `CLAIMS`. A row on an OPTIONAL crate still needs its feature, +/// `claims` off the plane axis is refused, and so is a `transport` row off the door axis: a +/// transport is linked only as its memory-ABI door (step 36). #[test] fn the_generator_folds_the_transport_and_claims_axes() { let manifest = r#" @@ -278,8 +280,8 @@ wire-high = "busbar-wire" [package.metadata.busbar.linked-axes] host = "plane claims" -wire-low = "transport" -wire-high = "transport" +wire-low = "transport transport-door" +wire-high = "transport transport-door" [package.metadata.busbar.linked-entry] wire-high = "busbar_wire::linked::high" @@ -293,12 +295,12 @@ host = "unit" 1, "two rows on one crate link it once: {out}" ); - // A row off the door axis claims its own key and opens no upgrade (a door row reads both off - // its door's Statement: `crate::root::doors::{claims_of, upgrades_of}`). - assert!(out.contains("key: busbar_wire::linked::KEY, composes_over: busbar_wire::linked::COMPOSES_OVER, build: busbar_wire::linked::build, claims: __row_claims_0, upgrades: __row_upgrades_0 }"), "{out}"); + // A door row is built from its door and reads its claims and upgrades off the door's Statement + // (`crate::root::doors::{build, claims_of, upgrades_of}`). + assert!(out.contains("key: busbar_wire::linked::KEY, composes_over: busbar_wire::linked::COMPOSES_OVER, build: __door_build_0, claims: __door_claims_0, upgrades: __door_upgrades_0 }"), "{out}"); assert!( out.contains( - "fn __row_claims_0() -> Vec<&'static str> {\n vec![busbar_wire::linked::KEY]\n}" + "fn __door_claims_0() -> Vec<&'static str> {\n crate::root::doors::claims_of(busbar_wire::linked::door)\n}" ), "{out}" ); @@ -321,6 +323,10 @@ host = "unit" "wire-low = \"busbar-plane-host\"", ), manifest.replace("host = \"plane claims\"", "host = \"claims\""), + manifest.replace( + "wire-low = \"transport transport-door\"", + "wire-low = \"transport\"", + ), ] { let refused = std::panic::catch_unwind(|| linked_source(&broken, &|f: &str| f == "host")); assert!(refused.is_err(), "must be refused:\n{broken}"); diff --git a/crates/busbar/tests/transport_composed_over_consistency.rs b/crates/busbar/tests/transport_composed_over_consistency.rs index 418a61987a..5e85316a90 100644 --- a/crates/busbar/tests/transport_composed_over_consistency.rs +++ b/crates/busbar/tests/transport_composed_over_consistency.rs @@ -1,17 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2026 Busbar Inc and contributors -//! `Transport::composed_over` has no default body: every implementor answers it explicitly. This -//! is the witness that the linked wires' answers agree with how they are actually built — by the -//! same bottom-up fold the boot seal (`busbar::root::registry::compose`) runs over the linked -//! transport table, which build.rs hands this test as data (`$OUT_DIR/linked_transports.rs`). -//! -//! A wire that opens its own socket answers `None` no matter what it declares in `COMPOSES_OVER`; a -//! wire that is only ever built by being handed another instance names that instance's key. Both -//! halves are checked against the shipped fold (`src/root/tests/fixtures/transport_fold.txt`, the -//! same rows the root's own fold test reads): every wire's answer is the layer that table names, and -//! a composed wire's answer is one of the layers it declares — which is exactly what the registry's -//! boot check (`busbar_contract::transport::registry::check_composition`) relies on being true. The +//! Every linked wire is built over nothing, as the boot seal records it. A transport is linked only +//! as its memory-ABI door (step 36), and the host serves a door over its own sockets: the door is +//! refused at its build if it names a transport it composes over (`HostWire::new`), so the seal's +//! rows record no layer (`composed_over: None`, which the registry's boot check, +//! `busbar_contract::transport::registry::check_composition`, holds against each declaration). This +//! is the witness that the linked wires agree with that — built by the same bottom-up fold the boot +//! seal (`busbar::root::registry::compose`) runs over the linked transport table, which build.rs +//! hands this test as data (`$OUT_DIR/linked_transports.rs`), and checked against the shipped fold +//! (`src/root/tests/fixtures/transport_fold.txt`, the same rows the root's own fold test reads). The //! test names no transport: every key it holds is one a linked row declared. use busbar_contract::transport::TransportSettings; @@ -50,44 +48,21 @@ fn every_real_transport_answers_composed_over_consistently_with_its_construction LINKED_TRANSPORTS.len(), "every linked wire is built" ); - let mut composed = 0; for (row, t) in &built { assert_eq!(t.key(), row.key, "a wire answers the key its row declares"); let (_, expected) = shipped .iter() .find(|(key, _)| *key == row.key) .unwrap_or_else(|| panic!("`{}` is linked but the shipped fold has no row", row.key)); - let over = t.composed_over(); - match over { - // A wire that opens its own socket: `None`, regardless of what it declares. - None => assert_eq!( - *expected, None, - "{} opens its own socket and must answer composed_over() = None", - row.key - ), - // A wire only ever built composed: the layer it was actually given, which must be one - // of the layers `COMPOSES_OVER` names and the one the shipped fold names. - Some(over) => { - composed += 1; - assert!( - row.composes_over.contains(&over), - "{}.composed_over() = {over:?}, which is not in its own COMPOSES_OVER {:?}", - row.key, - row.composes_over - ); - assert_eq!( - Some(over), - *expected, - "{} is built over a layer the shipped fold does not name", - row.key - ); - } - } + assert_eq!( + *expected, None, + "{} is served over the host's sockets and is built over nothing", + row.key + ); } // NO TRANSPORT NAMES ANOTHER (ARCHITECT ruling Q128 U7; TRANSPORT-STACK (2), :4721): the carrier // is the connector's choice from the target's scheme, so no shipped wire is built over another // and the shipped fold names no layer. - assert_eq!(composed, 0, "a wire was built over another"); assert!( shipped.iter().all(|(_, over)| over.is_none()), "the shipped fold names a layer" diff --git a/qa/construction.toml b/qa/construction.toml index dba9b40cb2..a2c36b9f86 100644 --- a/qa/construction.toml +++ b/qa/construction.toml @@ -959,8 +959,11 @@ transport_file = "crates/busbar-contract/src/transport/mod.rs" # `Store` stood here until the `kinds::Store` journal trait was struck from busbar-contract # (STORE-NOALLOC, ARCHITECT R7 2026-10-01): it had no implementor and no caller; the store kind is # its v3 table and `abi::sdk::store::StoreSlots`. Struck in the same commit, as above. +# `Transport` stood here until the legacy `busbar_contract::Transport` trait was struck (TODO step +# 36): a transport reaches the connector only as its memory-ABI framer door, and the trait had no +# implementor left. Struck in the same commit, as above. traits = [ - "Plane", "SessionPlane", "Transport", + "Plane", "SessionPlane", ] max_defaulted = 0 diff --git a/qa/contract-stateless.toml b/qa/contract-stateless.toml index 280bca77d1..f9d7a00395 100644 --- a/qa/contract-stateless.toml +++ b/qa/contract-stateless.toml @@ -115,11 +115,6 @@ 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" -item = "NEXT_LISTENER_ID" - [[finding]] rule = "contract-stateless:thread-local" file = "crates/busbar-contract/src/abi/hot/host.rs" diff --git a/qa/kind-isolation.toml b/qa/kind-isolation.toml index e1a6a61751..f03bc4b246 100644 --- a/qa/kind-isolation.toml +++ b/qa/kind-isolation.toml @@ -959,8 +959,9 @@ drain = "none: the same granted class as busbar -> busbar-kernel's test edge. # `impl Plane for Wire` in a transport and `impl Transport for P` in a plane left that row # byte-identical in both directions: it never asked whether a crate implements SOMEBODY ELSE'S face. # -# Four crates do today. Each row is the exact number, exact in both directions, with the line that -# deletes it. The SHIP twin owes zero: at the tag there is no such thing as a reviewed one. +# No crate does today: each STRUCK note below names the line that deleted one. A row is the exact +# number, exact in both directions, with the line that deletes it. The SHIP twin owes zero: at the +# tag there is no such thing as a reviewed one. # # crate — the package that implements it. # face — the entry trait of a kind that is not its own. @@ -969,13 +970,10 @@ drain = "none: the same granted class as busbar -> busbar-kernel's test edge. # why — what the number is made of. # drain — the line that deletes it. -[[face]] -crate = "busbar" -face = "Transport" -count = "1" -cite = "BUSBAR-1.6.0.md THE DESIGN, §5 — the host owns every socket; the connector serves a framer entry over them. ARCHITECT ruling (Option R): `busbar-core-connector` is core and presents no plugin face, so the adapter onto the kernel's legacy transport seam lives in the composition root. F12 relocation (ARCHITECT): the retired tcp carrier implemented this face in busbar-transport-tcp; it moved to the connector, then to the root." -why = "`busbar::root::doors::RootWire` wraps the connector's `HostWire` and implements the kernel's legacy transport seam by only delegating to the connector's host-side surface, so the kernel's listeners and upgrades drive a memory-ABI framer door over the host's sockets. One implementation, in shipped source. Transitional: RootWire deletes with the legacy stack at step 36." -drain = "drains at TODO step 36, when the kernel's serve/egress path stops speaking `Transport` and the engine is deleted; RootWire deletes with the legacy stack at step 36." +# STRUCK (step 36): `busbar` × Transport. The root's one transport face was `doors.rs`'s `RootWire`, +# which presented the connector's `HostWire` at the legacy `busbar_contract::Transport` seam; the +# boot seal now reads every transport row from its door's facts, the trait is deleted from the +# contract, and the root implements no transport face. No FACE_GRANTS entry replaces it. # STRUCK (row 84, S1): `busbar-plugin-loader` × Transport. The loader's one transport face was # `transport_adapter.rs`'s `WireTransport`, which presented a HOT-decl transport to the kernel; the diff --git a/xtask/src/gates/kind_isolation.rs b/xtask/src/gates/kind_isolation.rs index e6bf30eddb..a4c8802474 100644 --- a/xtask/src/gates/kind_isolation.rs +++ b/xtask/src/gates/kind_isolation.rs @@ -4486,6 +4486,31 @@ fn resolve_head(bound: &BTreeMap, head: &str) -> String { } } +/// The `[[face]]` row the ratchet proofs plant once the ledger carries none: `(crate, face, the +/// crate's directory, the face's path)` — a transport implementing the plane's face, the shape the +/// other `:faces` fixtures plant. +const PLANTED_FACE: (&str, &str, &str, &str) = ( + "busbar-transport-stdio", + "Plane", + "crates/busbar-transport-stdio", + "busbar_contract::plane::Plane", +); + +/// [`PLANTED_FACE`] as a `[[face]]` row at count 1, appended to the ledger. +fn planted_face_row(cx: &Ctx) -> Result { + let text = cx.read(REGISTRY_FILE)?; + let (krate, face, _, _) = PLANTED_FACE; + let mut ov = Overlay::new(); + ov.set( + REGISTRY_FILE, + format!( + "{text}\n[[face]]\ncrate = \"{krate}\"\nface = \"{face}\"\ncount = \"1\"\n\ + cite = \"planted\"\nwhy = \"planted\"\ndrain = \"planted\"\n" + ), + ); + Ok(ov) +} + /// The ledger's first `[[face]]` row, as `(crate, face, the crate's directory)`. fn first_face_row(cx: &Ctx) -> Result<(String, String, String), String> { let text = cx.read(REGISTRY_FILE)?; @@ -4623,14 +4648,9 @@ const FACE_PATHS: &[(&str, &str, &[&str])] = &[ "Plane", &["busbar_contract::plane::Plane", "busbar_contract::Plane"], ), - ( - "transport", - "Transport", - &[ - "busbar_contract::transport::Transport", - "busbar_contract::Transport", - ], - ), + // The transport kind's entry is its memory-ABI framer door; the contract defines no transport + // trait (step 36), so the face is matched by its label, as a name. + ("transport", "Transport", &[]), // The store kind's face is the RECORD store a store plugin implements (store-memory: // `records::RecordStore as Store`), not `verb_store::Store`, the admin verbs' DR seam that the // loader's store adapter answers on the host side (Part 2 #33). @@ -9312,58 +9332,94 @@ impl Gate for KindIsolationGate { // THE RATCHET, BOTH WAYS. The faces that exist today are held at their exact count: a // SECOND one in the same crate is a landing that grew the coupling. The subject is the // ledger's first `[[face]]` row, read off the ledger rather than named here, so the case - // follows the debt as it drains instead of naming a row that has gone. + // follows the debt as it drains instead of naming a row that has gone. Once the debt has + // drained and the ledger carries no row, the proofs plant one of their own + // ([`planted_face_row`]): a row and two implementations it does not cover, and a row that + // covers none — so the ratchet stays proven with nothing left to hold. if !self.ship { let row = first_face_row(cx); - let names = row - .as_ref() - .map(|(k, f, _)| (k.clone(), f.clone())) - .unwrap_or_default(); - let grown = row.as_ref().map(|(_, face, dir)| { - let path = FACE_PATHS - .iter() - .find(|(_, label, _)| label == face) - .and_then(|(_, label, paths)| paths.first().copied().or(Some(*label))) - .unwrap_or(face.as_str()) - .to_string(); - let mut ov = Overlay::new(); - ov.set( - format!("{dir}/src/planted_second_face.rs"), - format!("pub struct Second;\nimpl {path} for Second {{}}\n"), - ); - ov - }); - match grown { - Ok(ov) => report.push(prove_rows_red( - cx, - subject, - "a second implementation of a reviewed foreign face is a landing that grew it", - &[ROW_FACES], - ov, - &["face-ratchet", &names.0, &names.1], - )), - Err(why) => report.push(unplantable( - "a second implementation of a reviewed foreign face is a landing that grew it", - &[ROW_FACES], - &["face-ratchet"], - why.clone(), - )), + match &row { + Ok((krate, face, dir)) => { + let path = FACE_PATHS + .iter() + .find(|(_, label, _)| label == face) + .and_then(|(_, label, paths)| paths.first().copied().or(Some(*label))) + .unwrap_or(face.as_str()) + .to_string(); + let mut ov = Overlay::new(); + ov.set( + format!("{dir}/src/planted_second_face.rs"), + format!("pub struct Second;\nimpl {path} for Second {{}}\n"), + ); + report.push(prove_rows_red( + cx, + subject, + "a second implementation of a reviewed foreign face is a landing that grew it", + &[ROW_FACES], + ov, + &["face-ratchet", krate, face], + )); + } + Err(_) => match planted_face_row(cx) { + Ok(mut ov) => { + let (krate, face, dir, path) = PLANTED_FACE; + for n in ["one", "two"] { + ov.set( + format!("{dir}/src/planted_face_{n}.rs"), + format!("pub struct Face{n};\nimpl {path} for Face{n} {{}}\n"), + ); + } + report.push(prove_rows_red( + cx, + subject, + "a second implementation of a reviewed foreign face is a landing that grew it", + &[ROW_FACES], + ov, + &["face-ratchet", krate, face], + )); + } + Err(why) => report.push(unplantable( + "a second implementation of a reviewed foreign face is a landing that grew it", + &[ROW_FACES], + &["face-ratchet"], + why, + )), + }, } // AND A ROW WHOSE IMPLEMENTATION IS GONE IS A DEAD ALLOWANCE. - report.push(plant_registry( - cx, - subject, - "a reviewed face row that covers no implementation any more is struck", - &[ROW_FACES], - row.map(|(krate, face, _)| { - ( + match row { + Ok((krate, face, _)) => report.push(plant_registry( + cx, + subject, + "a reviewed face row that covers no implementation any more is struck", + &[ROW_FACES], + Ok(( format!("crate = \"{krate}\"\nface = \"{face}\""), format!("crate = \"{krate}-planted\"\nface = \"{face}\""), - ) - }), - &["dead-face", "-planted", "Strike the row"], - )); + )), + &["dead-face", "-planted", "Strike the row"], + )), + Err(_) => { + let (krate, _, _, _) = PLANTED_FACE; + match planted_face_row(cx) { + Ok(ov) => report.push(prove_rows_red( + cx, + subject, + "a reviewed face row that covers no implementation any more is struck", + &[ROW_FACES], + ov, + &["dead-face", krate, "Strike the row"], + )), + Err(why) => report.push(unplantable( + "a reviewed face row that covers no implementation any more is struck", + &[ROW_FACES], + &["dead-face"], + why, + )), + } + } + } } // ── THE WAYS IN THAT ARE NOT A DEPENDENCY ────────────────────────────────────────────────