From 40f5afeb7605c34f7a6139a8b40a7c0fa3b5c9b0 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 14 Oct 2026 03:05:02 -0700 Subject: [PATCH] contract: an emit carries the answer's status on an accepted stream's head (INBOUND-LISTEN, l.4913) EmitIn gains a pre-tag tail, `status: u32` and `_status_reserved: u32` (224 -> 232), as deadline_ns and lane were appended. It is stated on the emit that carries an ACCEPTED stream's answer head (the bytes encode rendered), in the numbering of the claim the stream arrived on; a status asking the far end to switch protocols hands the connection to the claimed upgrade framer (detach). It is 0 on every other emit, and never a `:status` field. The spec's l.5037 "no ABI change" rested on this field, which l.4913 ruled and which never landed (ARCHITECT ruling 2026-10-14). check_emit_status is the framer's rule: a status only on the answer head, the answer head always with one, the padding zero. RED: transport_check_tests an_emit_states_a_status_only_on_an_accepted_streams_answer_head names a field and a function that did not exist. The abi-layout golden, its record list and the C header move with the append; every host constructs EmitIn zeroed (blank_in / input / z), so every existing emit states 0. --- .../busbar-contract/include/busbar_plugin.h | 6 +++- .../src/abi/tests/transport_check_tests.rs | 26 +++++++++++++++ .../src/abi/transport/check.rs | 33 +++++++++++++++---- .../busbar-contract/src/abi/transport/mod.rs | 9 +++++ .../tests/golden/abi-layout.golden | 4 ++- crates/busbar-contract/tests/layout_golden.rs | 4 ++- 6 files changed, 72 insertions(+), 10 deletions(-) diff --git a/crates/busbar-contract/include/busbar_plugin.h b/crates/busbar-contract/include/busbar_plugin.h index 0f431b1b54..b4dff4957b 100644 --- a/crates/busbar-contract/include/busbar_plugin.h +++ b/crates/busbar-contract/include/busbar_plugin.h @@ -3322,6 +3322,8 @@ struct bb_transport_EmitIn { bb_transport_FramerSink sink; uint64_t deadline_ns; const bb_transport_DatagramLane *lane; + uint32_t status; + uint32_t _status_reserved; }; /* `encode`'s `in`. The rendered bytes go into `sink.wire`. */ @@ -5708,7 +5710,7 @@ BB_ASSERT(offsetof(bb_transport_IngestIn, end) == 112, "bb_transport_IngestIn.en BB_ASSERT(offsetof(bb_transport_IngestIn, _reserved) == 116, "bb_transport_IngestIn._reserved: offset"); BB_ASSERT(offsetof(bb_transport_IngestIn, sink) == 120, "bb_transport_IngestIn.sink: offset"); BB_ASSERT(offsetof(bb_transport_IngestIn, lane) == 200, "bb_transport_IngestIn.lane: offset"); -BB_ASSERT(sizeof(bb_transport_EmitIn) == 224, "bb_transport_EmitIn: size"); +BB_ASSERT(sizeof(bb_transport_EmitIn) == 232, "bb_transport_EmitIn: size"); BB_ASSERT(BB_ALIGNOF(bb_transport_EmitIn) == 8, "bb_transport_EmitIn: alignment"); BB_ASSERT(offsetof(bb_transport_EmitIn, head) == 0, "bb_transport_EmitIn.head: offset"); BB_ASSERT(offsetof(bb_transport_EmitIn, framing) == 88, "bb_transport_EmitIn.framing: offset"); @@ -5720,6 +5722,8 @@ BB_ASSERT(offsetof(bb_transport_EmitIn, flags) == 124, "bb_transport_EmitIn.flag BB_ASSERT(offsetof(bb_transport_EmitIn, sink) == 128, "bb_transport_EmitIn.sink: offset"); BB_ASSERT(offsetof(bb_transport_EmitIn, deadline_ns) == 208, "bb_transport_EmitIn.deadline_ns: offset"); BB_ASSERT(offsetof(bb_transport_EmitIn, lane) == 216, "bb_transport_EmitIn.lane: offset"); +BB_ASSERT(offsetof(bb_transport_EmitIn, status) == 224, "bb_transport_EmitIn.status: offset"); +BB_ASSERT(offsetof(bb_transport_EmitIn, _status_reserved) == 228, "bb_transport_EmitIn._status_reserved: offset"); BB_ASSERT(sizeof(bb_transport_EncodeIn) == 232, "bb_transport_EncodeIn: size"); BB_ASSERT(BB_ALIGNOF(bb_transport_EncodeIn) == 8, "bb_transport_EncodeIn: alignment"); BB_ASSERT(offsetof(bb_transport_EncodeIn, head) == 0, "bb_transport_EncodeIn.head: offset"); diff --git a/crates/busbar-contract/src/abi/tests/transport_check_tests.rs b/crates/busbar-contract/src/abi/tests/transport_check_tests.rs index 56d9cddeb4..6dc34ca349 100644 --- a/crates/busbar-contract/src/abi/tests/transport_check_tests.rs +++ b/crates/busbar-contract/src/abi/tests/transport_check_tests.rs @@ -917,3 +917,29 @@ fn a_streams_final_status_is_judged_against_its_claims_numbering() { f(Rule::NullWithCount, "finish.final_bytes") ); } + +/// THE ANSWER'S STATUS ON AN EMIT (INBOUND-LISTEN, l.4913): stated on the emit that carries an +/// accepted stream's answer head, and on no other emit. +#[test] +fn an_emit_states_a_status_only_on_an_accepted_streams_answer_head() { + let mut i: EmitIn = z(); + assert_eq!(check_emit_status(&i, false), Ok(())); + i.status = 200; + assert_eq!(check_emit_status(&i, true), Ok(())); + i.status = 101; + assert_eq!(check_emit_status(&i, true), Ok(())); + // RED: a status on an emit that carries no answer head. + assert_eq!( + check_emit_status(&i, false), + f(Rule::Contradiction, "emit.status") + ); + // RED: an accepted stream's answer head with no status. + i.status = 0; + assert_eq!(check_emit_status(&i, true), f(Rule::Missing, "emit.status")); + // RED: the padding is not a channel. + i._status_reserved = 1; + assert_eq!( + check_emit_status(&i, false), + f(Rule::UnknownCode, "emit._status_reserved") + ); +} diff --git a/crates/busbar-contract/src/abi/transport/check.rs b/crates/busbar-contract/src/abi/transport/check.rs index c438ee6681..668dd854bd 100644 --- a/crates/busbar-contract/src/abi/transport/check.rs +++ b/crates/busbar-contract/src/abi/transport/check.rs @@ -13,13 +13,14 @@ pub use crate::abi::mechanism::check::{Fault, Rule}; use super::{ - AcceptOut, ArrivalOut, Claim, ConnFacts, FinishIn, FramePiece, FrameSpan, FramerOut, HeadSlots, - IoOut, ListenOut, LocateOut, SettingDecl, StatusRow, TransportTail, CANCEL_COMPLETED, - CANCEL_NOTHING_MOVED, FACT_DECODES_PAYLOAD, FACT_SIGNS_NOTHING_AFTER_AUTH, FRAMING_DATAGRAM, - FRAMING_STREAM, MAX_ADDR, PIECE_CONTINUED, PIECE_END_OF_FRAME, PIECE_FIELDS, PIECE_HAS_CODE, - PIECE_HAS_RETRY_AFTER, PIECE_STREAM_FAILED, PIECE_TEXT, PIECE_WRITABLE, ROLE_CARRIER, - ROLE_FRAMER, SETTING_FLAG, SETTING_TEXT, STATUS_AT_TERMINAL, STATUS_OTHER, STATUS_SUCCESS, - UNIT0_HANDSHAKE, YIELD_ENDED, YIELD_HAS_DEADLINE, YIELD_MORE, YIELD_STREAM_FULL, + AcceptOut, ArrivalOut, Claim, ConnFacts, EmitIn, FinishIn, FramePiece, FrameSpan, FramerOut, + HeadSlots, IoOut, ListenOut, LocateOut, SettingDecl, StatusRow, TransportTail, + CANCEL_COMPLETED, CANCEL_NOTHING_MOVED, FACT_DECODES_PAYLOAD, FACT_SIGNS_NOTHING_AFTER_AUTH, + FRAMING_DATAGRAM, FRAMING_STREAM, MAX_ADDR, PIECE_CONTINUED, PIECE_END_OF_FRAME, PIECE_FIELDS, + PIECE_HAS_CODE, PIECE_HAS_RETRY_AFTER, PIECE_STREAM_FAILED, PIECE_TEXT, PIECE_WRITABLE, + ROLE_CARRIER, ROLE_FRAMER, SETTING_FLAG, SETTING_TEXT, STATUS_AT_TERMINAL, STATUS_OTHER, + STATUS_SUCCESS, UNIT0_HANDSHAKE, YIELD_ENDED, YIELD_HAS_DEADLINE, YIELD_MORE, + YIELD_STREAM_FULL, }; use super::{ DatagramRoute, KeyingMaterial, RendezvousTerms, FINGERPRINT_BYTES, HANDSHAKE_ANSWERS, @@ -687,6 +688,24 @@ pub fn check_final_status(i: &FinishIn, rows: &[StatusRow], claim: u32) -> Resul Ok(()) } +/// An `emit`'s STATUS ([`EmitIn::status`], INBOUND-LISTEN, `BUSBAR-1.6.0.md` l.4913): stated on +/// the emit that carries an ACCEPTED stream's answer head (`answers_head`), and on no other. The +/// framer refuses the emit that breaks it, before any byte of it reaches the wire. +/// +/// # Errors +/// +/// The rule the emit breaks. +pub fn check_emit_status(i: &EmitIn, answers_head: bool) -> Result<(), Fault> { + if i._status_reserved != 0 { + return Err(fault(Rule::UnknownCode, "emit._status_reserved")); + } + match (answers_head, i.status) { + (true, 0) => Err(fault(Rule::Missing, "emit.status")), + (false, s) if s != 0 => Err(fault(Rule::Contradiction, "emit.status")), + _ => Ok(()), + } +} + /// Every setting: a path and a known kind. /// /// # Errors diff --git a/crates/busbar-contract/src/abi/transport/mod.rs b/crates/busbar-contract/src/abi/transport/mod.rs index adaa649124..699e3bab5b 100644 --- a/crates/busbar-contract/src/abi/transport/mod.rs +++ b/crates/busbar-contract/src/abi/transport/mod.rs @@ -1062,6 +1062,15 @@ pub struct EmitIn { pub deadline_ns: u64, /// The datagram lane ([`datagram`]): NULL for a stream framer. A tail addition (pre-tag v1). pub lane: *const DatagramLane, + /// THE ANSWER'S STATUS (INBOUND-LISTEN, `BUSBAR-1.6.0.md` l.4913): on an ACCEPTED stream's FIRST + /// emit, the one carrying the answer's head (its bytes `encode` rendered), the status the answer + /// carries, in the numbering of the claim the stream arrived on; a status that asks the far end + /// to switch protocols hands the connection to the claimed upgrade framer ([`slot::DETACH`]). + /// `0` on every other emit, where a stated status is refused + /// ([`check::check_emit_status`]). Never a `:status` field. A tail addition (pre-tag v1). + pub status: u32, + /// Alignment padding. + pub _status_reserved: u32, } /// `encode`'s `in`. The rendered bytes go into `sink.wire`. diff --git a/crates/busbar-contract/tests/golden/abi-layout.golden b/crates/busbar-contract/tests/golden/abi-layout.golden index d613b94692..3741f9921a 100644 --- a/crates/busbar-contract/tests/golden/abi-layout.golden +++ b/crates/busbar-contract/tests/golden/abi-layout.golden @@ -1800,7 +1800,9 @@ tkind::EmitIn.flags=124 tkind::EmitIn.sink=128 tkind::EmitIn.deadline_ns=208 tkind::EmitIn.lane=216 -tkind::EmitIn.__size=224 +tkind::EmitIn.status=224 +tkind::EmitIn._status_reserved=228 +tkind::EmitIn.__size=232 tkind::EmitIn.__align=8 tkind::EncodeIn.head=0 tkind::EncodeIn.fields=88 diff --git a/crates/busbar-contract/tests/layout_golden.rs b/crates/busbar-contract/tests/layout_golden.rs index 3a86dc8e1c..06219e70a0 100644 --- a/crates/busbar-contract/tests/layout_golden.rs +++ b/crates/busbar-contract/tests/layout_golden.rs @@ -1613,7 +1613,9 @@ fn compute_layout() -> String { flags, sink, deadline_ns, - lane + lane, + status, + _status_reserved ] ); record!(