diff --git a/crates/busbar-plane-llm/tests/alloc_gate.rs b/crates/busbar-plane-llm/tests/alloc_gate.rs index 73f13612f9..4be3fa0ed5 100644 --- a/crates/busbar-plane-llm/tests/alloc_gate.rs +++ b/crates/busbar-plane-llm/tests/alloc_gate.rs @@ -144,3 +144,138 @@ fn same_dialect_relay_does_not_reparse_the_request() { work on a document it is about to send back unchanged" ); } + +// ── F25 REHOME: the whole forward the served door runs, bounded (coverage map v2) ───────────────── + +/// The generation the served door holds: one openai far end, under a pool that names it. +fn served_generation() -> busbar_plane_llm::exchange::shaping::Shaping { + busbar_plane_llm::exchange::shaping::Shaping::from_settings(&serde_json::json!({ + "providers": { "oai": { "protocol": "openai", "base_url": "https://openai.invalid" } }, + "models": { "gpt-4o": { "provider": "oai" } }, + "pools": { "p": { "members": ["gpt-4o"] } } + })) + .expect("a well-formed generation reads") +} + +/// A canned far-end chat completion (the 1.5.5 gate's upstream answer). +const FAR_OK: &[u8] = br#"{"id":"chatcmpl-gate","object":"chat.completion","created":0,"model":"gpt-4o","choices":[{"index":0,"message":{"role":"assistant","content":"ok"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}"#; + +/// ONE served forward, as the plane's door runs it for a unit: the arrival read off the caller's +/// head and body, the far-end request the attempt writes for the member, and the far end's whole +/// answer relayed back to the caller. Answers the caller's status. +fn one_forward(shaping: &busbar_plane_llm::exchange::shaping::Shaping, body: &[u8]) -> u16 { + use busbar_plane_llm::exchange::attempt::{build, stream_intent}; + use busbar_plane_llm::exchange::reply::{At, Reply, ReplyCtx}; + use busbar_plane_llm::exchange::{arrive::arrive, handler_of}; + let caller: &[(&[u8], &[u8])] = &[(b"content-type", b"application/json")]; + let arrived = arrive("POST", "/v1/chat/completions", caller, body, shaping) + .expect("the caller's request arrives"); + let far = build(&arrived, caller, shaping, "p", "gpt-4o").expect("the attempt is written"); + assert!(!far.body.is_empty(), "the far end is sent a body"); + let lane = shaping.lane("gpt-4o").expect("the member's lane"); + let ctx = ReplyCtx { + arrived: &arrived, + lane, + intent: handler_of(&arrived) + .map(|h| stream_intent(h, arrived.parsed.as_ref())) + .unwrap_or_default(), + passthrough: false, + }; + let head: &[(&[u8], &[u8])] = &[(b"content-type", b"application/json")]; + let mut reply = Reply::new(&ctx, 200, head); + let at = At { + now_s: 1_752_000_000, + elapsed_ms: Some(1), + }; + let piece = reply.feed(&ctx, FAR_OK, true, at); + assert!(piece.done, "a whole answer ends on its last piece"); + piece.head.map_or(0, |h| h.status) +} + +/// COMMITTED BOUND (1.5.5's `FORWARD_PASSTHROUGH_MAX_ALLOCS`): the most allocations ONE warmed +/// openai>openai passthrough forward may make, measured as the minimum over several iterations. +const FORWARD_PASSTHROUGH_MAX_ALLOCS: u64 = 107; + +/// LEGACY `crates/busbar-llm/src/engine/tests/alloc_gate_tests.rs::alloc_gate_openai_passthrough_forward` +/// (coverage map v2 row 39). 1.5.5: one warmed, steady-state openai->openai chat forward through the +/// served path (the arrival, the far-end exchange and the relay of the answer, 200 each) allocates +/// at most 107 times, the minimum over the measured iterations. +/// +/// RED: a new per-request allocation on the served forward (a builder per send, a codec boxed per +/// call, a scaling allocation) pushes the warmed minimum over 107. +#[test] +fn alloc_gate_openai_passthrough_forward() { + let shaping = served_generation(); + let body = br#"{"model":"p","messages":[{"role":"user","content":"hi"}],"max_tokens":16}"#; + // WARM UP: first-touch statics are per process, not per request. + assert_eq!(one_forward(&shaping, body), 200, "passthrough must be 200"); + let mut min_allocs = u64::MAX; + for _ in 0..4 { + let mut status = 0; + let count = allocations_of(|| status = one_forward(&shaping, body)); + assert_eq!(status, 200, "passthrough must be 200"); + min_allocs = min_allocs.min(count); + } + println!("served forward openai>openai steady-state min allocations = {min_allocs}"); + assert!( + min_allocs <= FORWARD_PASSTHROUGH_MAX_ALLOCS, + "OPENAI>OPENAI FORWARD-PATH ALLOCATION COUNT REGRESSED: measured {min_allocs} > committed \ + bound {FORWARD_PASSTHROUGH_MAX_ALLOCS}. A new per-request allocation on the forward path \ + (or a scaling one) pushed it over." + ); +} + +/// How many `messages` entries the large body carries (1.5.5's `ECHO_SCALING_MESSAGES`). +const ECHO_SCALING_MESSAGES: usize = 4_000; + +/// COMMITTED BOUND (1.5.5's `ECHO_BODY_SCALING_MAX_ALLOCS`): how many more allocations a request of +/// [`ECHO_SCALING_MESSAGES`] messages may cost than the same request with one, on the buffered +/// delivery arm (the caller asked to stream, the far end answered one body). +const ECHO_BODY_SCALING_MAX_ALLOCS: u64 = 92_000; + +/// An openai chat request with `n` messages, asking to STREAM. +fn openai_stream_body(n: usize) -> Vec { + let messages: Vec = (0..n) + .map(|i| serde_json::json!({ "role": "user", "content": format!("message {i}") })) + .collect(); + serde_json::to_vec(&serde_json::json!({ + "model": "p", + "messages": messages, + "max_tokens": 16, + "stream": true, + })) + .expect("serializes") +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/alloc_gate_tests.rs::alloc_gate_request_echo_body_materialized_once` +/// (coverage map v2 row 40). 1.5.5: on the buffered delivery arm (an openai caller asking to +/// stream, answered one JSON body, 200 each), a body of 4000 messages costs at most 92000 +/// allocations more than the same body with one: the client-chosen node count multiplies no added +/// per-node wave (the deep clone of the parsed request it once paid). +/// +/// RED: the served forward gains a whole-body materialization (a second parse, a deep clone of the +/// request) whose cost scales with the caller's message count past the 92000 delta. +#[test] +fn alloc_gate_request_echo_body_materialized_once() { + let shaping = served_generation(); + let small_body = openai_stream_body(1); + let large_body = openai_stream_body(ECHO_SCALING_MESSAGES); + // WARM both shapes outside the measured window. + assert_eq!(one_forward(&shaping, &small_body), 200); + assert_eq!(one_forward(&shaping, &large_body), 200); + let mut status = (0, 0); + let small = allocations_of(|| status.0 = one_forward(&shaping, &small_body)); + let large = allocations_of(|| status.1 = one_forward(&shaping, &large_body)); + assert_eq!(status, (200, 200), "the arm under test must be 200"); + let delta = large.saturating_sub(small); + println!( + "buffered delivery arm: small={small} large={large} delta={delta} over \ + {ECHO_SCALING_MESSAGES} messages" + ); + assert!( + delta <= ECHO_BODY_SCALING_MAX_ALLOCS, + "THE BUFFERED DELIVERY ARM GAINED A PER-NODE ALLOCATION WAVE: a body with \ + {ECHO_SCALING_MESSAGES} messages cost {delta} allocations more than a one-message body, \ + over the committed bound {ECHO_BODY_SCALING_MAX_ALLOCS}." + ); +} diff --git a/crates/busbar-plane-llm/tests/exchange_probe.rs b/crates/busbar-plane-llm/tests/exchange_probe.rs index 7bf6145a96..c1e9b4da39 100644 --- a/crates/busbar-plane-llm/tests/exchange_probe.rs +++ b/crates/busbar-plane-llm/tests/exchange_probe.rs @@ -62,3 +62,93 @@ fn a_lane_under_a_path_base_is_not_probed_and_a_fixed_path_is_kept() { "/fixed/chat" ); } + +/// The value of the head field `name` on `r`. +fn field_value<'a>(r: &'a busbar_plane_llm::exchange::attempt::FarRequest, name: &str) -> &'a [u8] { + r.fields + .iter() + .find(|(n, _)| n.eq_ignore_ascii_case(name)) + .map(|(_, v)| v.as_slice()) + .unwrap_or_else(|| panic!("the request carries `{name}`")) +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/health_tests.rs::test_probe_sends_native_user_agent_and_accept_headers` +/// (coverage map v2 row 220). 1.5.5: an active probe of an anthropic lane sends the SAME native-SDK +/// fingerprint organic traffic sends: `user-agent: Anthropic/Python 0.39.0` and the non-streaming +/// `accept: application/json`, so a far end cannot tell busbar's probes from its clients. +/// +/// RED: the probe's `user-agent` or `accept` value differs from the one an organic (UA-less, +/// non-streaming) anthropic request carries, or either moves off the pinned literal. +#[test] +fn test_probe_sends_native_user_agent_and_accept_headers() { + use busbar_plane_llm::exchange::arrive::arrive; + use busbar_plane_llm::exchange::attempt::build; + let s = Shaping::from_settings(&json!({ + "providers": { "ant": { "protocol": "anthropic", "base_url": "https://b" } }, + "models": { "claude": { "provider": "ant" } }, + "pools": { "p": { "members": ["claude"] } } + })) + .expect("reads"); + let lane = s.lane("claude").expect("lane"); + let probe = request(lane).expect("probed"); + + // Organic traffic on the same lane: a native caller that sent no user-agent, not streaming. + let head: Vec<(&[u8], &[u8])> = + vec![(b"content-type".as_slice(), b"application/json".as_slice())]; + let body = br#"{"model":"claude","max_tokens":5,"messages":[{"role":"user","content":"hi"}]}"#; + let arrived = arrive("POST", "/v1/messages", &head, body, &()).expect("arrives"); + let organic = build(&arrived, &head, &s, "p", "claude").expect("built"); + + assert_eq!( + field_value(&probe, "user-agent"), + field_value(&organic, "user-agent"), + "probe must send the native User-Agent organic traffic sends" + ); + assert_eq!( + field_value(&probe, "user-agent"), + b"Anthropic/Python 0.39.0" + ); + assert_eq!( + field_value(&probe, "accept"), + field_value(&organic, "accept"), + "probe must send the native Accept organic traffic sends" + ); + assert_eq!(field_value(&probe, "accept"), b"application/json"); +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/health_tests.rs::test_probe_signs_and_sends_same_encoded_path_for_reserved_chars` +/// (coverage map v2 row 230). 1.5.5: a probe of a bedrock model id carrying a reserved `:` sends +/// the `%3A`-encoded path, never the raw `:0/converse`, and the signature's canonical path is +/// computed from that SAME wire path, so the signed and sent paths cannot diverge +/// (`SignatureDoesNotMatch` would park every such lane dead). +/// +/// RED: the raw colon reaches the wire, the probe sends a path other than the one the signature +/// is computed from, or the canonical path stops being the wire path's own encoding. +#[test] +fn test_probe_signs_and_sends_same_encoded_path_for_reserved_chars() { + use busbar_plane_llm::exchange::attempt::{uri_encode_path, wire_and_canonical_path}; + let s = shaping(); + let lane = s.lane("bed").expect("lane"); + let probe = request(lane).expect("probed"); + let (wire_path, canonical) = wire_and_canonical_path(&probe_path(lane)); + assert!( + probe.target.contains("%3A"), + "Bedrock modelId ':' must be percent-encoded on the wire path: {}", + probe.target + ); + assert!( + !probe.target.contains(":0/converse"), + "the raw ':' must NOT survive on the wire path (would diverge from the signed URI): {}", + probe.target + ); + assert_eq!( + probe.target, wire_path, + "the probe sends exactly the wire path the signature is computed from" + ); + assert_eq!( + canonical, + uri_encode_path(&wire_path), + "the signed canonical path is the transmitted wire path's own encoding (signed == sent)" + ); + assert_eq!(probe.target, "/model/anthropic.claude-3%3A0/converse"); +} diff --git a/crates/busbar-plane-llm/tests/exchange_shaping.rs b/crates/busbar-plane-llm/tests/exchange_shaping.rs index 260a41726c..6cf8f2a34c 100644 --- a/crates/busbar-plane-llm/tests/exchange_shaping.rs +++ b/crates/busbar-plane-llm/tests/exchange_shaping.rs @@ -112,3 +112,54 @@ fn the_glob_takes_prefix_suffix_and_middle_wildcards() { assert!(!glob_match("a*c*e", "abde")); assert!(glob_match("exact", "exact") && !glob_match("exact", "exactly")); } + +/// LEGACY `crates/busbar-llm/src/engine/tests/attempt_timeout_precedence_tests.rs::effective_reasoning_member_override_wins` +/// (coverage map v2 row 43). 1.5.5: the pool member's `reasoning` override wins over the model's +/// flag in both directions (true over false AND false over true); a member with no override +/// inherits the model's flag, true or false; a pool or member with no candidate row uses the +/// model's flag. +/// +/// RED: a member's `reasoning: false` stops beating a model's `reasoning: true` (the arm the +/// neighbouring test never drives), or a route with no member row stops reading the model's flag. +#[test] +fn effective_reasoning_member_override_wins() { + let mut v = settings(); + // `claude` states `reasoning: true` at the model level; `quiet` turns it off in its pool only. + v["pools"]["quiet"] = json!({ "members": [ { "model": "claude", "reasoning": false } ] }); + let s = Shaping::from_settings(&v).expect("reads"); + assert_eq!( + s.reasoning("main", "gpt"), + Some(true), + "member true beats model false" + ); + assert_eq!( + s.reasoning("quiet", "claude"), + Some(false), + "member false beats model true" + ); + assert_eq!( + s.reasoning("main", "claude"), + Some(true), + "no member override inherits model true" + ); + assert_eq!( + s.reasoning("alt", "gpt"), + Some(false), + "no member override inherits model false" + ); + assert_eq!( + s.reasoning("alt", "claude"), + Some(true), + "a pool with no row for the member uses the model flag" + ); + assert_eq!( + s.reasoning("no-such-pool", "gpt"), + Some(false), + "no candidate row uses the model flag" + ); + assert_eq!( + s.reasoning("no-such-pool", "claude"), + Some(true), + "no candidate row uses the model flag" + ); +} diff --git a/crates/busbar-plane-llm/tests/exchange_user_agent.rs b/crates/busbar-plane-llm/tests/exchange_user_agent.rs index 08579b8010..d496261d4a 100644 --- a/crates/busbar-plane-llm/tests/exchange_user_agent.rs +++ b/crates/busbar-plane-llm/tests/exchange_user_agent.rs @@ -105,3 +105,107 @@ fn a_translated_far_end_sees_the_far_dialect_s_fingerprint_never_the_caller_s() } } } + +/// One member per far dialect, so every dialect's fingerprint is read off a request the plane builds. +fn every_dialect() -> Shaping { + Shaping::from_settings(&json!({ + "providers": { + "oai": { "protocol": "openai", "base_url": "https://api.example" }, + "ant": { "protocol": "anthropic", "base_url": "https://anthropic.example" }, + "goo": { "protocol": "gemini", "base_url": "https://gemini.example" }, + "bed": { "protocol": "bedrock", "base_url": "https://bedrock.example" }, + "res": { "protocol": "responses", "base_url": "https://responses.example" }, + "coh": { "protocol": "cohere", "base_url": "https://cohere.example" } + }, + "models": { + "gpt": { "provider": "oai" }, + "claude": { "provider": "ant" }, + "gem": { "provider": "goo", "upstream_model": "gemini-pro" }, + "bedrock-claude": { "provider": "bed", "upstream_model": "anthropic.claude-3" }, + "resp": { "provider": "res" }, + "command": { "provider": "coh" } + }, + "pools": { "p": { "members": ["gpt", "claude", "gem", "bedrock-claude", "resp", "command"] } } + })) + .expect("reads") +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/ingress_indistinguishability_tests.rs::test_egress_ua_versions_are_pinned_and_present` +/// (coverage map v2 row 325). 1.5.5: every egress request carries the pinned native-SDK +/// `user-agent` of its far dialect, as a golden literal: anthropic `Anthropic/Python 0.39.0`, +/// openai and responses `OpenAI/Python 1.54.0`, gemini `google-genai-sdk/0.8.0 gl-python/3.11`, +/// bedrock `Boto3/1.35.0 md/Botocore#1.35.0`, cohere `cohere-python/5.11.0`, and a far end no +/// declaration names `okhttp/4.12.0`. None is ever empty or versionless, and the two Stainless +/// Python SDKs share the `/Python <ver>` grammar. +/// +/// RED: any far dialect's request goes out with another (or no) `user-agent`, the foreign default +/// moves, or a pinned version string is edited without touching this table. +#[test] +fn every_dialects_egress_user_agent_version_is_pinned_and_present() { + let s = every_dialect(); + let pinned = [ + ("claude", "Anthropic/Python 0.39.0"), + ("gpt", "OpenAI/Python 1.54.0"), + ("resp", "OpenAI/Python 1.54.0"), + ("gem", "google-genai-sdk/0.8.0 gl-python/3.11"), + ("bedrock-claude", "Boto3/1.35.0 md/Botocore#1.35.0"), + ("command", "cohere-python/5.11.0"), + ]; + // An anthropic caller that sent no user-agent: every far end below sees its own fingerprint, + // the same-dialect one included (1.5.5 sent the native fingerprint to a UA-less caller's far end). + let head: Vec<(&[u8], &[u8])> = + vec![(b"content-type".as_slice(), b"application/json".as_slice())]; + let arrived = arrive("POST", "/v1/messages", &head, MESSAGES.as_bytes(), &()).expect("arrives"); + for (member, want) in pinned { + let r = build(&arrived, &head, &s, "p", member).expect("built"); + assert_eq!( + user_agent(&r), + [want.as_bytes()], + "{member} egress UA drifted from the pinned native-SDK string" + ); + } + // The foreign far end (a dialect no declaration names) still gets a present, plausible UA. + assert_eq!( + busbar_contract::protocol::EGRESS_UA_DEFAULT, + "okhttp/4.12.0", + "the foreign/default egress UA drifted from the pinned literal" + ); + // Every declaration states its pinned literal, so no far dialect can fall to the default. + for d in busbar_plane_llm::codec::DECLS { + let want = match d.name { + "anthropic" => "Anthropic/Python 0.39.0", + "openai" | "responses" => "OpenAI/Python 1.54.0", + "gemini" => "google-genai-sdk/0.8.0 gl-python/3.11", + "bedrock" => "Boto3/1.35.0 md/Botocore#1.35.0", + "cohere" => "cohere-python/5.11.0", + other => panic!("a dialect this table does not pin: {other}"), + }; + assert_eq!(d.egress_user_agent, want, "{}", d.name); + } + let every: Vec<&str> = pinned + .iter() + .map(|(_, ua)| *ua) + .chain([busbar_contract::protocol::EGRESS_UA_DEFAULT]) + .collect(); + for ua in every { + assert!(!ua.is_empty(), "an egress UA must never be empty"); + assert!( + ua.chars().any(|ch| ch.is_ascii_digit()), + "an egress UA must carry a version number: {ua}" + ); + } + for ua in ["Anthropic/Python 0.39.0", "OpenAI/Python 1.54.0"] { + let (title, rest) = ua.split_once('/').expect("Python-SDK UA contains a '/'"); + assert!( + title + .chars() + .next() + .is_some_and(|ch| ch.is_ascii_uppercase()), + "Stainless Python SDK UA must start with a capitalized Title: {ua}" + ); + assert!( + rest.starts_with("Python "), + "Stainless Python SDK UA must be `<Title>/Python <ver>`: {ua}" + ); + } +} diff --git a/crates/busbar-plane-llm/tests/plane_door.rs b/crates/busbar-plane-llm/tests/plane_door.rs index aa3b53e919..97c58e678f 100644 --- a/crates/busbar-plane-llm/tests/plane_door.rs +++ b/crates/busbar-plane-llm/tests/plane_door.rs @@ -404,3 +404,165 @@ fn every_open_class_becomes_one_reported_count_at_its_tail_index() { ); } } + +// ── F25 REHOME: the registry facts and the plane's published identity (coverage map v2) ────────── + +/// The declaration of the dialect named `name`, from the one table the plane serves through. +fn decl_named(name: &str) -> Option<&'static busbar_contract::protocol::ProtocolDecl> { + busbar_plane_llm::codec::DECLS + .iter() + .copied() + .find(|d| d.name == name) +} + +/// The six dialects 1.5.5 shipped, by the names its registry keyed them under. +const SHIPPED_DIALECTS: [&str; 6] = [ + "openai", + "anthropic", + "gemini", + "bedrock", + "cohere", + "responses", +]; + +/// LEGACY `crates/busbar-llm/src/engine/tests/dialect_registry_facts_tests.rs::registry_resolves_openai_and_its_moderation_handler` +/// (coverage map v2 row 98). 1.5.5: the openai registration resolves a handler whose protocol name +/// is its own and which serves the moderation operation; a protocol nobody registered resolves to +/// nothing. +/// +/// RED: the openai declaration loses its handler or its moderation cell, its handler answers to +/// another protocol's name, or an unknown name resolves to some declaration. +#[test] +fn registry_resolves_openai_and_its_moderation_handler() { + use busbar_contract::operation::OpVerb; + let handler = decl_named("openai") + .and_then(|d| d.handler) + .expect("the shipped protocol's handler is registered"); + assert_eq!(handler.protocol_name(), "openai"); + assert!(handler.operation_handler(OpVerb::MODERATION).is_some()); + assert!( + decl_named("zzz-unknown").is_none(), + "unknown protocol -> None" + ); +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/dialect_registry_facts_tests.rs::every_protocol_serves_chat_via_its_request_handler` +/// (coverage map v2 row 99). 1.5.5: chat is reached through the same registry as every other +/// operation, and all six shipped protocols resolve a handler with a chat operation handler. +/// +/// RED: any of the six dialects stops resolving a handler, or its handler stops serving chat. +#[test] +fn every_protocol_serves_chat_via_its_request_handler() { + use busbar_contract::operation::OpVerb; + for proto in SHIPPED_DIALECTS { + let handler = decl_named(proto) + .and_then(|d| d.handler) + .unwrap_or_else(|| panic!("{proto}: protocol registered")); + assert_eq!(handler.protocol_name(), proto); + assert!( + handler.operation_handler(OpVerb::CHAT).is_some(), + "{proto} must serve chat via operation_handler(Chat)" + ); + } + let mut stated: Vec<&str> = DIALECTS.iter().map(|d| d.name).collect(); + stated.sort_unstable(); + let mut shipped = SHIPPED_DIALECTS.to_vec(); + shipped.sort_unstable(); + assert_eq!( + stated, shipped, + "the plane states exactly the six shipped dialects" + ); +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/dialect_registry_facts_tests.rs::chat_declares_its_capabilities` +/// (coverage map v2 row 100). 1.5.5: the chat cell as production resolves it (openai, chat) streams, +/// taps usage from a non-stream body, reads the body's `stream` boolean as the caller's intent, and +/// takes a non-empty top-level `system` as its body affinity key (an empty one is no key). +/// +/// RED: chat stops streaming, stops tapping non-stream usage, misreads the stream flag, or derives +/// an affinity key from an empty `system` (or none from a non-empty one). +#[test] +fn chat_declares_its_capabilities() { + use busbar_contract::operation::OpVerb; + let chat = decl_named("openai") + .and_then(|d| d.handler) + .and_then(|h| h.operation_handler(OpVerb::CHAT)) + .expect("the shipped protocol serves chat"); + assert!(chat.streaming(), "chat streams"); + assert!(chat.taps_usage(), "chat bills tokens from the body"); + assert!( + chat.wants_stream(&serde_json::json!({"stream": true})), + "chat reads the stream boolean" + ); + assert!(!chat.wants_stream(&serde_json::json!({}))); + assert!(!chat.wants_stream(&serde_json::json!({"stream": false}))); + assert_eq!( + chat.body_affinity_key(&serde_json::json!({"system": "you are helpful"})), + Some("you are helpful") + ); + assert_eq!( + chat.body_affinity_key(&serde_json::json!({"system": ""})), + None + ); +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/ingress_integration_tests.rs::test_affinity_header_session_mode_without_name_uses_default` +/// (coverage map v2 row 375). 1.5.5: a pool whose `affinity` is `mode: session` with no +/// `header_name` keys its sticky routing on `x-session-id`. +/// +/// RED: a session-mode pool with no header name reads another header (or none), so a client's +/// `x-session-id` stops pinning its session. +#[test] +fn test_affinity_header_session_mode_without_name_uses_default() { + use busbar_plane_llm::plane_door::affinity_key; + let shaping = read_settings( + br#"{"providers":{"ant":{"protocol":"anthropic","base_url":"https://anthropic.example"}}, + "models":{"claude":{"provider":"ant"}}, + "pools":{"p":{"members":["claude"],"affinity":{"mode":"session"}}}}"#, + ) + .expect("a session-mode pool with no header name reads"); + assert_eq!(shaping.affinity_header("p"), "x-session-id"); + let session: &[(&[u8], &[u8])] = &[(b"x-session-id", b"s-7")]; + assert_eq!( + affinity_key(shaping.affinity_header("p"), session, None, None), + Some("s-7".to_string()), + "the default header is the one the pool keys on" + ); +} + +/// A `'static` string the door states, as its bytes. +fn abi_text(s: busbar_contract::abi::mechanism::call::AbiStr) -> &'static [u8] { + // SAFETY: every `AbiStr` the door states names a `'static` string of `len` bytes. + unsafe { std::slice::from_raw_parts(s.ptr, s.len) } +} + +/// LEGACY `crates/busbar-llm/src/tests/plane_decl_identity_tests.rs::the_plane_declares_its_published_key_and_is_the_fallback` +/// (coverage map v2 row 643). 1.5.5: the plane's operator-visible identity, as literals: the key +/// every metric, log and record carries is `llm`, it is the one fallback catch-all, its config +/// section is `pools`, and a grant on it names a `pool`. +/// +/// RED: the published key, the fallback flag, the declaring section or the scope kind moves. +#[test] +fn the_plane_declares_its_published_key_and_is_the_fallback() { + use busbar_contract::abi::mechanism::door::SECTION_DECLARING; + assert_eq!(abi_text(STATEMENT.name), b"llm"); + assert_ne!( + TAIL.flags & TAIL_FALLBACK, + 0, + "the fallback catch-all every unclaimed path falls through to" + ); + assert_eq!(abi_text(TAIL.scope), b"pools"); + // SAFETY: the Statement's sections are a `'static` list of `sections_len` entries. + let sections = + unsafe { std::slice::from_raw_parts(STATEMENT.sections, STATEMENT.sections_len) }; + let declaring: Vec<&[u8]> = sections + .iter() + .filter(|s| s.flags & SECTION_DECLARING != 0) + .map(|s| abi_text(s.name)) + .collect(); + assert_eq!(declaring, vec![b"pools".as_slice()]); + // SAFETY: the tail's `'static` scope-kind list of `scope_kinds_len` entries. + let kinds = unsafe { std::slice::from_raw_parts(TAIL.scope_kinds, TAIL.scope_kinds_len) }; + let kinds: Vec<&[u8]> = kinds.iter().map(|k| abi_text(*k)).collect(); + assert_eq!(kinds, vec![b"pool".as_slice()]); +} diff --git a/crates/busbar-plane-llm/tests/refusal_envelopes.rs b/crates/busbar-plane-llm/tests/refusal_envelopes.rs index 659740a4bc..9fd9660920 100644 --- a/crates/busbar-plane-llm/tests/refusal_envelopes.rs +++ b/crates/busbar-plane-llm/tests/refusal_envelopes.rs @@ -256,3 +256,207 @@ fn a_refusal_never_names_the_internal_reason() { } } } + +// ── F25 REHOME: the rendered refusal's status and head (coverage map v2) ───────────────────────── + +/// The dialect codec of the dialect named `name`. +fn dialect_codec(name: &str) -> &'static dyn busbar_contract::protocol::DialectCodec { + busbar_plane_llm::codec::DECLS + .iter() + .find(|d| d.name == name) + .and_then(|d| d.dialect()) + .unwrap_or_else(|| panic!("{name} declares a dialect codec")) +} + +/// The host's entropy, so a dialect that mints a request id mints one (constant bytes, so every +/// test in this binary still reads the same document twice). +fn entropy() { + busbar_contract::codec::install_entropy_source(|out| { + out.fill(7); + true + }); +} + +/// The value of the head field `name` (compared without case), if the refusal carries one. +fn head_value<'a>(fields: &'a [(String, Vec<u8>)], name: &str) -> Option<&'a [u8]> { + fields + .iter() + .find(|(n, _)| n.eq_ignore_ascii_case(name)) + .map(|(_, v)| v.as_slice()) +} + +/// LEGACY `crates/busbar-llm/src/engine/tests/ingress_indistinguishability_tests.rs::test_anthropic_ingress_error_request_id_header_matches_body` +/// (coverage map v2 row 330). 1.5.5: an anthropic-dialect refusal carries a `request-id` head field +/// in Anthropic's `req_` shape, EQUAL to the body's `request_id`; no other dialect's refusal +/// carries a `request-id` field. +/// +/// RED: the header and the body mint two different ids, the header goes missing, or another +/// dialect's refusal starts carrying Anthropic's field. +#[test] +fn test_anthropic_ingress_error_request_id_header_matches_body() { + use busbar_contract::protocol::KIND_INVALID_REQUEST; + use busbar_plane_llm::exchange::refuse::render; + entropy(); + let rendered = render("anthropic", 400, KIND_INVALID_REQUEST, "bad json", 0); + let header_rid = std::str::from_utf8( + head_value(&rendered.fields, "request-id") + .expect("anthropic error must carry a request-id header"), + ) + .expect("the request id is text") + .to_string(); + assert!( + header_rid.starts_with("req_"), + "request-id header carries the Anthropic req_ shape; got {header_rid}" + ); + let body: serde_json::Value = + sonic_rs::from_slice(&rendered.body).expect("a refusal is a document"); + assert_eq!( + body["request_id"].as_str(), + Some(header_rid.as_str()), + "the request-id header MUST equal the body request_id so they agree" + ); + for dialect in DIALECTS.iter().filter(|d| **d != "anthropic") { + let other = render(dialect, 400, KIND_INVALID_REQUEST, "bad json", 0); + assert_eq!( + head_value(&other.fields, "request-id"), + None, + "a {dialect} refusal carries no Anthropic request-id field" + ); + } +} + +/// LEGACY `crates/busbar-llm/src/unit/tests/audit.rs::every_refusal_class_renders_byte_identically_on_both_paths` +/// (coverage map v2 row 682). 1.5.5: every refusal class (exactly eight: unauth 401, wrong-scope +/// 403, over-budget 429 `insufficient_quota`, rate-limit 429 with `Retry-After: 30`, unpriced 400, +/// no-pool 404, oversize-body 413, context-overflow 400 carrying the breaker's +/// `context_length_exceeded` code) renders in each of the six dialects with its own status, its +/// dialect's native head fields over the very envelope it sends, the dialect writer's own envelope +/// bytes, and a non-empty body. +/// +/// RED: a class renders another status, drops or invents `retry-after`, wears head fields that do +/// not belong to its own envelope, leaves the writer's vocabulary (over-budget not +/// `insufficient_quota`, the context-length code reaching an openai caller as a raw `type`), renders +/// an empty body, or a class leaves the table. +#[test] +fn every_refusal_class_renders_byte_identically_on_both_paths() { + use busbar_contract::protocol::{ + KIND_AUTHENTICATION, KIND_INSUFFICIENT_QUOTA, KIND_INVALID_REQUEST, KIND_NOT_FOUND, + KIND_PERMISSION, KIND_RATE_LIMIT, KIND_REQUEST_TOO_LARGE, PROVIDER_CODE_CONTEXT_LENGTH, + }; + use busbar_plane_llm::exchange::refuse::render; + entropy(); + // (class, status, kind, message, retry-after seconds) + let classes: [(&str, u16, &str, &str, u32); 8] = [ + ( + "unauth", + 401, + KIND_AUTHENTICATION, + "Invalid API key provided.", + 0, + ), + ( + "wrong-scope", + 403, + KIND_PERMISSION, + "Your API key does not have permission to use pool 'p'.", + 0, + ), + ( + "over-budget", + 429, + KIND_INSUFFICIENT_QUOTA, + "You have exceeded your current quota (group 'bgrp' budget per total exhausted). \ + Please check your plan and billing details.", + 0, + ), + ( + "rate-limit", + 429, + KIND_RATE_LIMIT, + "Rate limit exceeded (group 'g': requests per minute). Please retry after the \ + indicated time.", + 30, + ), + ( + "unpriced", + 400, + KIND_INVALID_REQUEST, + "no configured rate for model 'm'", + 0, + ), + ( + "no-pool", + 404, + KIND_NOT_FOUND, + "The model 'm' does not exist or you do not have access to it.", + 0, + ), + ( + "oversize-body", + 413, + KIND_REQUEST_TOO_LARGE, + "Request body is too large.", + 0, + ), + ( + "context-overflow", + 400, + PROVIDER_CODE_CONTEXT_LENGTH, + "prompt is too long: 300000 tokens > 200000 maximum", + 0, + ), + ]; + assert_eq!(classes.len(), 8, "every refusal class stays in the table"); + assert_eq!(PROVIDER_CODE_CONTEXT_LENGTH, "context_length_exceeded"); + for (name, status, kind, message, retry) in classes { + for dialect in DIALECTS { + let codec = dialect_codec(dialect); + let rendered = render(dialect, status, kind, message, retry); + assert_eq!( + rendered.status, status, + "{name}/{dialect} wears its own status" + ); + assert!( + !rendered.body.is_empty(), + "{name}/{dialect} renders an envelope, not an empty body" + ); + // The body is the dialect writer's own envelope for the class's (status, kind, message). + let own = sonic_rs::to_vec(&codec.write_error(status, kind, message)) + .expect("the envelope serializes"); + assert_eq!( + without_minted_id(&rendered.body), + without_minted_id(&own), + "{name} on the {dialect} wire is not the dialect's own envelope" + ); + // The head: the JSON content type, the dialect's native error fields over the very + // envelope sent (so a minted id in the head is the body's), then the class's own wait. + let sent: serde_json::Value = + sonic_rs::from_slice(&rendered.body).expect("a refusal is a document"); + let mut head = vec![("content-type".to_string(), b"application/json".to_vec())]; + head.extend(codec.error_response_fields(kind, &sent)); + if retry != 0 { + head.push(("retry-after".to_string(), b"30".to_vec())); + } + assert_eq!( + rendered.fields, head, + "{name} on the {dialect} wire carries other head fields than its own" + ); + assert_eq!( + head_value(&rendered.fields, "retry-after"), + (name == "rate-limit").then_some(b"30".as_slice()), + "{name}/{dialect}: only the rolling window advises a wait, and it is 30 seconds" + ); + } + } + // The openai wire, read as its SDK reads it: the over-budget class is the native quota type and + // code, and the breaker's context-length token is projected into the writer's own vocabulary. + let over = render("openai", 429, KIND_INSUFFICIENT_QUOTA, "quota", 0); + let over: serde_json::Value = sonic_rs::from_slice(&over.body).expect("a document"); + assert_eq!(over["error"]["type"], "insufficient_quota"); + assert_eq!(over["error"]["code"], "insufficient_quota"); + let ctx = render("openai", 400, PROVIDER_CODE_CONTEXT_LENGTH, "too long", 0); + let ctx: serde_json::Value = sonic_rs::from_slice(&ctx.body).expect("a document"); + assert_eq!(ctx["error"]["type"], "invalid_request_error"); + let no_pool = render("openai", 404, KIND_NOT_FOUND, "no pool", 0); + assert_eq!(no_pool.status, 404); +} diff --git a/crates/busbar/src/root/tests/declared_credentials.rs b/crates/busbar/src/root/tests/declared_credentials.rs index f1a4af49ca..d4f718fb02 100644 --- a/crates/busbar/src/root/tests/declared_credentials.rs +++ b/crates/busbar/src/root/tests/declared_credentials.rs @@ -1030,3 +1030,72 @@ fn the_main_log_sink_writes_a_declared_diagnostic_in_its_one_five_five_shape() { ] ); } + +/// LEGACY the retiring engine crate's keyless-lane test in `src/engine/tests/auth_style_tests.rs` +/// (coverage map v2 row 73; its name spells an auth plugin's crate id, which the root may not). 1.5.5: a lane whose provider declared `api_key: none` (a keyless +/// local upstream) holds an empty key, and an empty key presents NO auth header at all under any +/// auth style (the dialect's default, the token override, the key-header override): never a bare +/// scheme word with an empty token, never an empty key field. A non-empty credential is +/// unaffected and presents its one header. +/// +/// Here every non-signing binding the fixture records (each dialect's default and the key-header +/// override) and the token override (`auth:` names its own style, which the door binds as written, +/// root door_steps `style_word`; its style word is the fixture's `override_styles.token`) are bound +/// on the linked auth plugins with the empty key, in both credential modes, and asked for their +/// fields. A signing binding is the one that sends fields of its own beside the credential, and is +/// left out: it signs, it presents no key header. +/// +/// RED: a linked auth plugin presents a field for an empty credential (a bare scheme word, an +/// empty key header), or a real credential stops presenting its one header. +#[test] +fn a_keyless_lane_presents_no_credential_field() { + let ctx = |upstream_creds| SigningContext { + host: "upstream.internal", + canonical_uri: "/", + body: b"{}", + timestamp_epoch: 0, + upstream_creds, + }; + let token_override = StyleBinding { + style: fixture()["override_styles"]["token"] + .as_str() + .expect("the token override's style") + .to_string(), + params: serde_json::json!({}), + uses_key: true, + statics: &[], + sent: Vec::new(), + }; + let mut bindings: Vec<(String, StyleBinding)> = fixture()["bindings"] + .as_object() + .expect("bindings") + .keys() + .map(|name| (name.clone(), binding_of(name, "upstream.internal", &[]))) + .filter(|(_, binding)| binding.sent.is_empty()) + .collect(); + bindings.push(("token override".to_string(), token_override.clone())); + assert!( + bindings.len() >= 3, + "the defaults, the key-header override and the token override are all bound" + ); + for (name, binding) in &bindings { + for mode in [UpstreamCreds::Own, UpstreamCreds::Passthrough] { + assert_eq!( + present_bound(binding, "", &ctx(mode)), + Vec::<(String, String)>::new(), + "an empty credential must send NO auth header ({name}, style {}, {mode:?})", + binding.style + ); + } + } + for (name, real) in [ + &bindings[0], + &("token override".to_string(), token_override), + ] { + assert_eq!( + present_bound(real, "SECRETKEY", &ctx(UpstreamCreds::Own)).len(), + 1, + "a real credential still sends its header ({name})" + ); + } +} diff --git a/crates/busbar/src/root/tests/door_steps.rs b/crates/busbar/src/root/tests/door_steps.rs index af6fd9f026..64838b3c0e 100644 --- a/crates/busbar/src/root/tests/door_steps.rs +++ b/crates/busbar/src/root/tests/door_steps.rs @@ -151,6 +151,50 @@ fn the_grant_is_judged_over_the_named_pool_then_its_fallback() { ); } +/// LEGACY the retiring engine crate's `src/engine/tests/ingress_integration_tests.rs::test_adhoc_governance_pool_acl_403_via_router` +/// (coverage map v2 row 463). 1.5.5: the ad-hoc (direct) route runs the same governance guard, +/// keyed on the resolved MODEL: a key allowed only on `other-pool` that names the configured model +/// directly is refused 403 (a permission refusal, the dialect's own `permission_error` envelope). +/// +/// Held here on the door's grant over a direct route and the refusal the seat answers for it: the +/// grant is denied, the seat refuses it as `ScopeDenied`, and the driver's status for that reason +/// is 403 (the plane renders it as its dialect's permission refusal; its own suite holds that). +/// +/// RED: a direct route escapes the key's pool grant (the model served to a key scoped elsewhere), +/// or its refusal stops being a 403. +#[test] +fn test_adhoc_governance_pool_acl_403_via_router() { + use busbar_contract::records::{ScopeRef, VirtualKey}; + let p = pools("models:\n configured-model: {provider: p}\npools:\n other-pool:\n members: [configured-model]"); + let elsewhere = VirtualKey { + allowed_scopes: Some(vec![ScopeRef::pool("other-pool")]), + ..VirtualKey::default() + }; + assert!( + !p.granted( + Some("pool"), + Some(&elsewhere), + ROUTE_DIRECT, + Some(b"configured-model") + ), + "a key allowed only on `other-pool` may not name the configured model directly" + ); + assert!( + p.granted( + Some("pool"), + Some(&elsewhere), + ROUTE_POOL, + Some(b"other-pool") + ), + "the same key is served on the pool it is granted" + ); + assert_eq!( + busbar_kernel::plane_driver::refusal_status(busbar_contract::caps::ReasonCode::ScopeDenied), + 403, + "ad-hoc governance pool-ACL => 403" + ); +} + /// THE MEMBER'S TIER (the core-owned per-member `tier`, `BUSBAR-1.6.0.md` l.4196 (b), R2-G /// l.4285): read off a member written `{name, tier}`, `0` for a bare name, for one with no tier, and /// for a tier that is no unsigned integer; a pool's entries are its members' names either way. diff --git a/crates/busbar/src/root/tests/egress_ports.rs b/crates/busbar/src/root/tests/egress_ports.rs index 90988db16c..7f64c1c8cf 100644 --- a/crates/busbar/src/root/tests/egress_ports.rs +++ b/crates/busbar/src/root/tests/egress_ports.rs @@ -6,6 +6,7 @@ //! until dropped and handed to the longest waiter; the counters label a member by its name and keep //! each pool's wait depth balanced. +use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use busbar_kernel_egress::ports::{Capacity, Clock, DestinationId, Telemetry}; @@ -94,3 +95,325 @@ fn the_counters_label_a_member_by_name_and_keep_each_pools_depth() { t.queued("p", -5); assert_eq!(t.depth("p"), 0, "a depth never reads below empty"); } + +// ── F25 REHOME: the attempt timeout a sealed member carries, the queue's one wake, the p95 ring ─── + +/// A connection table that opens nothing: the sealing below never dials. +#[derive(Debug, Default)] +struct Shut; + +impl busbar_contract::conn::Conns for Shut { + fn open( + &self, + _: busbar_contract::conn::InstanceId, + _: busbar_contract::conn::NeedId, + _: &busbar_contract::conn::OpenDesc<'_>, + ) -> Result<busbar_contract::conn::ConnId, busbar_contract::conn::ConnError> { + Err(busbar_contract::conn::ConnError::Refused) + } + fn write( + &self, + _: busbar_contract::conn::InstanceId, + _: busbar_contract::conn::ConnId, + _: &[u8], + _: bool, + _: bool, + ) -> Result<usize, busbar_contract::conn::ConnError> { + Err(busbar_contract::conn::ConnError::Closed) + } + fn read( + &self, + _: busbar_contract::conn::InstanceId, + _: busbar_contract::conn::ConnId, + _: u64, + _: &mut [u8], + ) -> Result<busbar_contract::conn::Piece, busbar_contract::conn::ConnError> { + Err(busbar_contract::conn::ConnError::Closed) + } + fn wait( + &self, + _: busbar_contract::conn::InstanceId, + _: &[busbar_contract::conn::ConnId], + _: u64, + ) -> Result<usize, busbar_contract::conn::ConnError> { + Err(busbar_contract::conn::ConnError::Closed) + } + fn facts( + &self, + _: busbar_contract::conn::InstanceId, + _: busbar_contract::conn::ConnId, + ) -> Result<busbar_contract::transport::ConnFacts, busbar_contract::conn::ConnError> { + Err(busbar_contract::conn::ConnError::Closed) + } + fn close( + &self, + _: busbar_contract::conn::InstanceId, + _: busbar_contract::conn::ConnId, + ) -> Result<(), busbar_contract::conn::ConnError> { + Err(busbar_contract::conn::ConnError::Closed) + } +} + +impl busbar_contract::conn::PollConns for Shut { + fn poll_read( + &self, + _: busbar_contract::conn::InstanceId, + _: busbar_contract::conn::ConnId, + _: &mut std::task::Context<'_>, + _: &mut [u8], + ) -> std::task::Poll<Result<busbar_contract::conn::Piece, busbar_contract::conn::ConnError>> + { + std::task::Poll::Ready(Err(busbar_contract::conn::ConnError::Closed)) + } +} + +/// A journal that keeps nothing: the sealing below dispatches nothing. +#[derive(Debug)] +struct Unwritten; + +impl busbar_kernel_egress::ports::Journal for Unwritten { + fn dispatched( + &self, + _: &busbar_kernel_egress::ports::Dispatched, + ) -> Result<(), busbar_kernel_egress::ports::DurabilityUnavailable> { + Ok(()) + } + fn abandoned(&self, _: &busbar_kernel_egress::ports::Dispatched) {} +} + +/// A dialect the root links, read as data off the node's dialect fixture (the root's sources name +/// no dialect). +fn a_linked_dialect() -> String { + let path = concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/root/tests/fixtures/plane_node_dialects.json" + ); + let text = std::fs::read_to_string(path).expect("the dialect fixture reads"); + let fixture: serde_json::Value = serde_json::from_str(&text).expect("the fixture is JSON"); + fixture["body_model"] + .as_object() + .and_then(|dialects| dialects.keys().next().cloned()) + .expect("the fixture names a body-model dialect") +} + +/// THE MODEL-SERVING EGRESS, sealed by [`crate::root::model_egress::compose`] the way the serve +/// path seals it, over a deployment where model `patient` states a 10000 ms attempt timeout and +/// model `plain` states none; pool `fast` overrides `patient` to 50 ms and leaves `plain` bare, and +/// pool `slow` names `patient` with no override. Both models are also routable directly. +fn sealed_timeouts() -> busbar_kernel::plane_driver::Egress { + // The deployment names `models:`/`pools:`, which config resolution hands a plane. + busbar_kernel::test_support::register_neutral_test_plane(); + let dialect = a_linked_dialect(); + let mut defs = std::collections::HashMap::new(); + defs.insert( + "far".to_string(), + serde_yaml::from_str::<busbar_kernel::config::ProviderDef>(&format!( + "protocol: {dialect}\nbase_url: https://far.invalid\n" + )) + .expect("a provider def"), + ); + let yaml = "providers:\n far:\n api_key: { env: BUSBAR_TEST_KEY }\nmodels:\n patient:\n provider: far\n \ + attempt_timeout_ms: 10000\n plain:\n provider: far\npools:\n fast:\n \ + members:\n - model: patient\n attempt_timeout_ms: 50\n - model: \ + plain\n slow:\n members:\n - model: patient\n"; + let deploy = busbar_kernel::config::deploy_from_yaml_str(yaml).expect("parses"); + let cfg = busbar_kernel::config::resolve(&deploy, &defs).expect("resolves"); + let app = busbar_kernel::test_support::TestApp::new().build(); + let serving = crate::root::model_egress::ModelServing { + pools: crate::root::model_egress::ModelPools::of(&cfg), + lanes: [("patient".to_string(), 0), ("plain".to_string(), 1)] + .into_iter() + .collect(), + app: Arc::new(move || Arc::clone(&app)), + }; + let route = |provider: &str| busbar_kernel::plane_driver::MemberRoute { + need: busbar_contract::conn::NeedId(0), + base_url: "https://far.invalid".to_string(), + auth: None, + provider: provider.to_string(), + keep: busbar_kernel::plane_driver::ResponseKeep::default(), + rides: Vec::new(), + anchors: busbar_contract::transport::trust::Anchors::default(), + spelled: Vec::new(), + }; + let routes: std::collections::BTreeMap<String, busbar_kernel::plane_driver::MemberRoute> = + [("patient", route("far")), ("plain", route("far"))] + .into_iter() + .map(|(model, sealed)| (model.to_string(), sealed)) + .collect(); + let facts = + crate::root::door_steps::door_facts("door", &["pool"], &[], &[], "unit", Vec::new()); + let (egress, _shown) = crate::root::model_egress::compose( + &facts, + &serving, + busbar_contract::conn::InstanceId(1), + Arc::new(Shut), + &routes, + Arc::new(Unwritten), + 600, + ) + .expect("the pools seal"); + egress +} + +/// The attempt timeout the sealed `pool`'s member `model` carries. +fn sealed_attempt_ms( + egress: &busbar_kernel::plane_driver::Egress, + pool: &str, + model: &str, +) -> Option<u64> { + egress + .pools + .get(pool) + .unwrap_or_else(|| panic!("pool `{pool}` is sealed")) + .members + .iter() + .find(|member| member.name == model) + .unwrap_or_else(|| panic!("`{model}` is a member of `{pool}`")) + .attempt_timeout_ms +} + +/// LEGACY the retiring engine crate's `src/engine/tests/attempt_timeout_precedence_tests.rs::test_member_override_wins_over_model_default` +/// (coverage map v2 row 44). 1.5.5: the pool member's `attempt_timeout_ms` override wins over the +/// model-level default, so one model carries 10000 ms in one pool and 50 ms in another. +/// +/// RED: the sealed member carries the model's 10000 ms (or nothing) instead of its pool's 50 ms. +#[test] +fn a_members_attempt_timeout_wins_over_its_models_default() { + let egress = sealed_timeouts(); + assert_eq!( + sealed_attempt_ms(&egress, "fast", "patient"), + Some(50), + "the pool-member override must beat the model-level default" + ); +} + +/// LEGACY the retiring engine crate's `src/engine/tests/attempt_timeout_precedence_tests.rs::test_model_default_applies_when_member_has_no_override` +/// (coverage map v2 row 45). 1.5.5: a member without an override inherits the model-level default. +/// +/// RED: a member that states no override loses its model's 10000 ms cap (or takes another pool's). +#[test] +fn test_model_default_applies_when_member_has_no_override() { + let egress = sealed_timeouts(); + assert_eq!( + sealed_attempt_ms(&egress, "slow", "patient"), + Some(10_000), + "a member with no override must fall back to the model-level cap" + ); +} + +/// LEGACY the retiring engine crate's `src/engine/tests/attempt_timeout_precedence_tests.rs::test_no_cap_anywhere_is_none` +/// (coverage map v2 row 46). 1.5.5: with neither level set the attempt is uncapped (`None`): it +/// runs under the walk's budget alone. +/// +/// RED: a member whose pool and model state no cap is sealed with one. +#[test] +fn test_no_cap_anywhere_is_none() { + let egress = sealed_timeouts(); + assert_eq!(sealed_attempt_ms(&egress, "fast", "plain"), None); +} + +/// LEGACY the retiring engine crate's `src/engine/tests/attempt_timeout_precedence_tests.rs::test_empty_cands_uses_model_default` +/// (coverage map v2 row 47). 1.5.5: a single-model route has no member rows at all, so the +/// model-level value is the only source: 10000 ms where the model states it, uncapped where it +/// states none. +/// +/// RED: a route naming a model directly drops the model's own attempt timeout (or invents one). +#[test] +fn test_empty_cands_uses_model_default() { + let egress = sealed_timeouts(); + assert_eq!( + sealed_attempt_ms(&egress, "patient", "patient"), + Some(10_000) + ); + assert_eq!(sealed_attempt_ms(&egress, "plain", "plain"), None); +} + +/// LEGACY the retiring engine crate's `src/engine/tests/on_exhausted_tests.rs::queue_two_waiters_one_freed_permit_wakes_exactly_one` +/// (coverage map v2 row 542). 1.5.5: with two waiters parked on a saturated one-slot member, +/// freeing ONE slot lets EXACTLY ONE dispatch; the other stays parked (no thundering herd, the cap +/// never exceeded) until the winner's slot frees in turn. +/// +/// RED: one freed slot wakes both waiters, hands out a second slot, or loses the wake so neither +/// (or only the first) ever proceeds. +#[tokio::test] +async fn the_queue_frees_one_permit_to_exactly_one_of_two_waiters() { + let permits = std::sync::Arc::new(MemberPermits::new([(A, 1)])); + let held = permits.try_acquire(A).expect("the one slot"); + let (woke, mut wakes) = tokio::sync::mpsc::unbounded_channel(); + let waiters: Vec<_> = (0..2) + .map(|_| { + let (permits, woke) = (std::sync::Arc::clone(&permits), woke.clone()); + tokio::spawn(async move { + let got = permits.acquire_any(&[A]).await; + let _ = woke.send(got); + }) + }) + .collect(); + tokio::time::sleep(Duration::from_millis(20)).await; + assert!( + wakes.try_recv().is_err(), + "both waiters park on the full member" + ); + + drop(held); + let first = tokio::time::timeout(Duration::from_secs(5), wakes.recv()) + .await + .expect("exactly one waiter must win the freed slot") + .expect("a waiter answered") + .expect("the slot was handed over"); + assert_eq!(first.0, A); + tokio::time::sleep(Duration::from_millis(50)).await; + assert!( + wakes.try_recv().is_err(), + "while the winner holds the only slot, the surviving waiter must still be parked (no herd)" + ); + assert!( + permits.try_acquire(A).is_none(), + "the single freed slot is held by the one winner — none left for a second dispatcher" + ); + + drop(first); + let second = tokio::time::timeout(Duration::from_secs(5), wakes.recv()) + .await + .expect("the survivor wins the slot the winner freed") + .expect("a waiter answered") + .expect("the slot was handed over"); + assert_eq!(second.0, A); + for waiter in waiters { + tokio::time::timeout(Duration::from_secs(5), waiter) + .await + .expect("both waiters resolve") + .expect("joined"); + } +} + +/// LEGACY the retiring engine crate's `src/engine/tests/signal_catalog_tests.rs::latency_reservoir_p95_is_nearest_rank_and_bounded` +/// (coverage map v2 row 612). 1.5.5: the reservoir behind `CandidateLatencyP95Ms` reads `None` +/// until a sample lands (never a fabricated 0); its p95 is nearest-rank in whole milliseconds +/// rounded up (1..=100 ms gives 95, a 0.2 ms sample gives 1); NaN, zero and negative samples are +/// ignored; and the ring is bounded, so after 1000 samples of 7 ms only those decide (7). +/// +/// RED: an empty ring reads 0, the rank or the rounding moves, a garbage sample is counted, or the +/// ring stops evicting its oldest samples. +#[test] +fn latency_reservoir_p95_is_nearest_rank_and_bounded() { + use crate::root::model_egress::LatencyReservoir; + let ring = LatencyReservoir::default(); + assert_eq!(ring.p95_ms(), None); + for ms in 1..=100u32 { + ring.record(f64::from(ms)); + } + assert_eq!(ring.p95_ms(), Some(95)); + ring.record(f64::NAN); + ring.record(0.0); + ring.record(-3.0); + assert_eq!(ring.p95_ms(), Some(95)); + let tiny = LatencyReservoir::default(); + tiny.record(0.2); + assert_eq!(tiny.p95_ms(), Some(1)); + for _ in 0..1000 { + ring.record(7.0); + } + assert_eq!(ring.p95_ms(), Some(7)); +} diff --git a/crates/busbar/src/root/tests/linked_auth.rs b/crates/busbar/src/root/tests/linked_auth.rs index 416ccc25e8..591f8f73d8 100644 --- a/crates/busbar/src/root/tests/linked_auth.rs +++ b/crates/busbar/src/root/tests/linked_auth.rs @@ -963,3 +963,130 @@ fn bearer(token: &str) -> axum::http::HeaderMap { ); headers } + +/// LEGACY the retiring engine crate's `src/engine/tests/ingress_integration_tests.rs::test_role_bound_principal_governed_like_a_virtual_key` +/// (coverage map v2 row 402). 1.5.5: with governance on and an external data-plane module on the +/// chain, a role-bound principal is governed through a SYNTHESIZED key exactly like a virtual key: +/// its binding's pool ACL admits the granted pool (200) and refuses another (403); a binding that +/// omits `allowed_pools` grants every pool; an explicit `allowed_pools: []` grants nothing (401); +/// and an identified principal whose roles earn no binding is refused outright (401, fail closed). +/// +/// Walked here through the served steps: the data-plane chain judges the request's credential +/// (the test chain's `grp:<role>` module), the verdict resolves to a governance key through the +/// module's `role_bindings` (`resolve_data_plane_identity`), and the door's grant judges the pool +/// the unit names with that key. +/// +/// RED: a role binding's pool ACL stops gating (an ungranted pool served), an omitted grant stops +/// meaning every pool, or an empty or absent binding admits the principal. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_role_bound_principal_governed_like_a_virtual_key() { + use busbar_contract::abi::plane::ROUTE_POOL; + use busbar_kernel::auth::{ + resolve_data_plane_identity, AuthMiddleware, ChainHead, IdentityRefusal, + }; + link(); + let module = "test-groups-module"; + let mut table = std::collections::BTreeMap::new(); + table.insert( + "pool-users".to_string(), + RoleBindingCfg { + allowed_pools: Some(vec!["gpool-a".to_string()]), + ..Default::default() + }, + ); + // OMITTED allowed_pools = ALL pools. + table.insert("batch".to_string(), RoleBindingCfg::default()); + // Explicit [] = NO pools (the empty set, fail closed). + table.insert( + "locked".to_string(), + RoleBindingCfg { + allowed_pools: Some(vec![]), + ..Default::default() + }, + ); + let mut role_bindings = config::RoleBindings::new(); + role_bindings.insert(module.to_string(), table); + let auth = Arc::new(AuthMiddleware::new_builtin(&AuthCfg::with_chain(vec![ + AuthChainEntry::bare(module), + ]))); + let app = busbar_kernel::test_support::TestApp::new() + .auth(Arc::clone(&auth)) + .role_bindings(role_bindings) + .build(); + let pools = crate::root::door_steps::DoorPools::of( + &serde_yaml::from_str( + "models:\n rekey-model: {provider: p}\npools:\n gpool-a:\n members: \ + [rekey-model]\n gpool-b:\n members: [rekey-model]", + ) + .expect("the section"), + ); + // What the caller reads: the identity refusal's 401, the grant's 403, else served. + let answer = |credential: &'static str, pool: &'static str| { + let (auth, app, pools) = (Arc::clone(&auth), Arc::clone(&app), pools.clone()); + async move { + // The test module judges the credential handed beside the head, never the head's own line. + let headers = axum::http::HeaderMap::new(); + let head = ChainHead::of_parts("POST", "/v1/chat/completions", &headers); + let verdict = AuthMiddleware::run_chain_on_request_path( + &auth, + Some(credential.to_string()), + head, + None, + None, + ) + .await; + match resolve_data_plane_identity(&app, verdict) { + Err(IdentityRefusal::NoGrant | IdentityRefusal::Denied) => 401, + Ok((_, gov)) => { + if pools.granted( + Some("pool"), + gov.key.as_deref(), + ROUTE_POOL, + Some(pool.as_bytes()), + ) { + 200 + } else { + busbar_kernel::plane_driver::refusal_status( + busbar_contract::caps::ReasonCode::ScopeDenied, + ) + } + } + } + } + }; + + // Pool ACL from the role binding: gpool-a serves, gpool-b is denied. + assert_eq!( + answer("grp:pool-users", "gpool-a").await, + 200, + "granted pool serves" + ); + assert_eq!( + answer("grp:pool-users", "gpool-b").await, + 403, + "ungranted pool is denied" + ); + // OMITTED allowed_pools grants every pool. + assert_eq!( + answer("grp:batch", "gpool-a").await, + 200, + "omitted allowed_pools grants all" + ); + assert_eq!( + answer("grp:batch", "gpool-b").await, + 200, + "omitted allowed_pools grants all" + ); + // Explicit [] grants the EMPTY SET: no data-plane access at all. + assert_eq!( + answer("grp:locked", "gpool-a").await, + 401, + "allowed_pools [] grants nothing" + ); + // Fail closed: an identified principal whose roles earn no binding is rejected. + assert_eq!( + answer("grp:strangers", "gpool-a").await, + 401, + "unbound roles grant nothing" + ); +} diff --git a/crates/busbar/src/root/tests/policy.rs b/crates/busbar/src/root/tests/policy.rs index 0204eaef75..57b8a2a787 100644 --- a/crates/busbar/src/root/tests/policy.rs +++ b/crates/busbar/src/root/tests/policy.rs @@ -92,3 +92,63 @@ fn an_unset_body_cap_resolves_to_the_transport_default() { "the transport's own default body cap is the 32 MiB this deployment has always had" ); } + +/// LEGACY the retiring engine crate's `src/engine/tests/runtime_carry_tests.rs::a_changed_upstream_timeout_rebuilds_the_client_an_unrelated_apply_reuses_it` +/// (coverage map v2 row 603). 1.5.5: a hot config apply that changes nothing client-relevant REUSES +/// the prior upstream client (its warm connection pool), and an apply that changes the upstream +/// request timeout REBUILDS it, so the new timeout (here the prior + 7 s) takes effect without a +/// restart. +/// +/// This test pins only the SETTINGS half: what a transport door would be opened with +/// (`settings_blob`) and the connector's pool posture. An unchanged apply hands the doors +/// byte-identical settings and the same posture; a changed timeout hands them a different blob +/// carrying the new figure. It does not prove the doors are reopened on a hot apply: the served +/// connector is built once at boot (`root/connector.rs`), and that half (the row's rebuild) is the +/// egress lane's, with its own RED (coordinator ruling 2026-10-14 ~02:55Z). +/// +/// RED: an apply that changed nothing reads as a change, or a changed +/// `limits.upstream_request_timeout_secs` never reaches the settings a door is opened with. +#[test] +fn a_changed_upstream_timeout_changes_the_door_settings_an_unrelated_apply_does_not() { + const DECLARED: [&str; 2] = [ + "limits.upstream_request_timeout_secs", + "limits.request_body_max_bytes", + ]; + let limits = LimitsResolved::default(); + // Reuse half: an apply with an identical client-affecting snapshot. + let prior = client_settings(&limits); + let unchanged = client_settings(&LimitsResolved::default()); + assert_eq!( + crate::root::doors::settings_blob(&DECLARED, &prior), + crate::root::doors::settings_blob(&DECLARED, &unchanged), + "an apply that changes no client-affecting setting must REUSE the prior client's warm pool" + ); + assert_eq!( + crate::root::connector::pool_posture(&prior), + crate::root::connector::pool_posture(&unchanged) + ); + // Rebuild half: bump the upstream request timeout and re-apply. + let changed = LimitsResolved { + upstream_request_timeout_secs: limits.upstream_request_timeout_secs + 7, + ..limits + }; + let rebuilt = client_settings(&changed); + assert_ne!( + crate::root::doors::settings_blob(&DECLARED, &prior), + crate::root::doors::settings_blob(&DECLARED, &rebuilt), + "an apply that changes upstream_request_timeout_secs must REBUILD the client so the new \ + timeout takes effect" + ); + assert_eq!( + rebuilt.request_timeout_secs, + prior.request_timeout_secs + 7, + "the rebuilt client's carried settings snapshot must reflect the new timeout" + ); + let blob: serde_json::Value = + serde_json::from_slice(&crate::root::doors::settings_blob(&DECLARED, &rebuilt)) + .expect("the settings blob is JSON"); + assert_eq!( + blob["limits.upstream_request_timeout_secs"], + serde_json::json!(prior.request_timeout_secs + 7) + ); +} diff --git a/crates/busbar/tests/docs_examples.rs b/crates/busbar/tests/docs_examples.rs index 2de48d06de..5ab0c7d8fb 100644 --- a/crates/busbar/tests/docs_examples.rs +++ b/crates/busbar/tests/docs_examples.rs @@ -306,3 +306,77 @@ fn shipped_config_artifacts_validate() { assert!(stdout.contains("ok: config valid"), "{shipped}: {stdout}"); } } + +/// The protocols this build's binary knows, read off the refusal an operator reads for a provider +/// naming one it does not (`must be one of: ...`), so this file names none of them. +#[cfg(linked_axis_body_ingress)] +fn known_protocols() -> Vec<String> { + let dir = fixture_dir("known-protocols"); + let providers = dir.join("providers.yaml"); + std::fs::write( + &providers, + "probe:\n protocol: definitely-not-a-protocol\n base_url: https://probe.invalid\n", + ) + .unwrap(); + let config = dir.join("config.yaml"); + std::fs::write( + &config, + "listen: \"127.0.0.1:0\"\nstore: {module: memory}\nproviders:\n probe:\n api_key: { env: \ + PROBE_KEY }\nmodels:\n probe-model:\n provider: probe\n", + ) + .unwrap(); + let (code, stdout, stderr) = run_validate(&config, &providers); + let _ = std::fs::remove_dir_all(&dir); + let all = format!("{stdout}{stderr}"); + assert_ne!(code, 0, "an unknown protocol must fail validation: {all}"); + let tail = all + .split("must be one of: ") + .nth(1) + .unwrap_or_else(|| panic!("the refusal lists the known protocols: {all}")); + tail.lines() + .next() + .unwrap_or_default() + .split(", ") + .filter_map(|item| item.split_whitespace().next()) + .map(|item| { + item.trim_end_matches(['.', ',', ';', '"', '\'']) + .to_string() + }) + .filter(|item| !item.is_empty()) + .collect() +} + +/// LEGACY the retiring engine crate's `src/engine/tests/dialect_registry_facts_tests.rs::test_shipped_providers_catalog_valid` +/// (coverage map v2 row 101). 1.5.5: the shipped `providers.yaml` catalog parses, is non-trivial +/// (at least 10 providers), every entry names a protocol this build knows, and every entry's +/// `base_url` is `https://`. +/// +/// RED: the catalog stops parsing, shrinks below 10 entries, an entry names a protocol the binary +/// does not speak (a dead lane at boot), or an entry's `base_url` is not `https://`. +#[cfg(linked_axis_body_ingress)] +#[test] +fn test_shipped_providers_catalog_valid() { + let raw = + std::fs::read_to_string(repo_root().join("providers.yaml")).expect("read providers.yaml"); + let defs: std::collections::BTreeMap<String, serde_yaml::Value> = + serde_yaml::from_str(&raw).expect("parse providers.yaml"); + assert!(defs.len() >= 10, "catalog should be non-trivial"); + let known = known_protocols(); + assert!(!known.is_empty(), "the binary knows at least one protocol"); + for (name, def) in &defs { + let protocol = def["protocol"] + .as_str() + .unwrap_or_else(|| panic!("provider '{name}' names a protocol")); + assert!( + known.iter().any(|k| k == protocol), + "provider '{name}' names unknown protocol '{protocol}' (known: {known:?})" + ); + let base_url = def["base_url"] + .as_str() + .unwrap_or_else(|| panic!("provider '{name}' names a base_url")); + assert!( + base_url.starts_with("https://"), + "provider '{name}' base_url must be https" + ); + } +} diff --git a/crates/busbar/tests/metrics_cross_plane.rs b/crates/busbar/tests/metrics_cross_plane.rs index cd925cecb5..3442007eec 100644 --- a/crates/busbar/tests/metrics_cross_plane.rs +++ b/crates/busbar/tests/metrics_cross_plane.rs @@ -616,3 +616,69 @@ fn test_lane_state_by_model_default_cell_untouched_zero_cooldown_reports_healthy gauge regardless of unrelated pool cells being broken; got:\n{line}" ); } + +/// LEGACY the retiring engine crate's `src/engine/tests/scrape_queued_depth_tests.rs::test_scrape_gauges_pool_queued_reads_live_depth` +/// (coverage map v2 row 604). 1.5.5: `busbar_pool_queued{pool}` reads the LIVE park depth, not a +/// literal 0: `1` while a request is parked on the pool's wait terminal, `0` once it leaves. +/// +/// The park is written where the served walk writes it (`set_pool_queued_depth`, the walk +/// telemetry's balanced park/leave count) and read through the kernel's own tables, the read seam +/// the scrape takes for a door-served plane; the scrape's gauge is then held to whatever the app's +/// read seam answers for the pool at that moment, so the gauge is the seam's reading and never a +/// literal. +/// +/// RED: the door read seam ignores the walk's park depth (reads 0 while parked, or keeps 1 after +/// the leave), or the scrape renders a value other than the live depth its read seam reports. +#[test] +fn the_scrapes_pool_queued_gauge_reads_the_live_depth() { + use busbar_kernel::plane_host::{set_pool_queued_depth, ConfigTables, EngineTablesView}; + register_planes(); + init(); + // Unique labels: the recorder and the park table are process-global. + let pool = "q-live-pool"; + // The linked plane's own default dialect, read off the registry (this cell names none). + let dialect = busbar_kernel::proto::residual_default_dialect() + .or_else(|| { + busbar_kernel::proto::detect_protocol( + "/v1/chat/completions", + &axum::http::HeaderMap::new(), + ) + }) + .expect("a linked plane states a dialect"); + let app = TestApp::new() + .lane(LaneSpec::new("q-live-model", dialect, "http://q")) + .pool(pool, &[(0, 1)]) + .build(); + let door_tables = ConfigTables::default(); + + // A request parks on the pool's wait terminal. + set_pool_queued_depth(pool, 1); + assert_eq!( + door_tables.queued_depth(pool), + 1, + "the door read seam must reflect the live park depth (1 while parked)" + ); + refresh_scrape_gauges(&app); + let out = render(); + let live = app.engine_tables_view().queued_depth(pool) as f64; + assert_eq!( + gauge_value(&out, POOL_QUEUED, pool), + Some(live), + "busbar_pool_queued must render the live depth its read seam reports; got:\n{out}" + ); + + // The request leaves the queue. + set_pool_queued_depth(pool, 0); + assert_eq!( + door_tables.queued_depth(pool), + 0, + "the door read seam must return to 0 once the parked request leaves" + ); + refresh_scrape_gauges(&app); + let out = render(); + assert_eq!( + gauge_value(&out, POOL_QUEUED, pool), + Some(0.0), + "busbar_pool_queued must return to 0 once the parked request leaves; got:\n{out}" + ); +} diff --git a/crates/busbar/tests/metrics_routes_across_export_reload.rs b/crates/busbar/tests/metrics_routes_across_export_reload.rs index 4d915e1b04..7c072a5761 100644 --- a/crates/busbar/tests/metrics_routes_across_export_reload.rs +++ b/crates/busbar/tests/metrics_routes_across_export_reload.rs @@ -305,3 +305,97 @@ fn a_reload_that_removes_the_scrape_sink_keeps_metrics_hooks_and_drops_metrics_a drop(child); let _ = std::fs::remove_dir_all(&dir); } + +/// LEGACY the retiring engine crate's `src/engine/tests/forward_pool_integration_tests.rs::test_metrics_requires_auth_in_chain_mode` +/// (coverage map v2 row 138). 1.5.5: with a data-plane auth chain configured, `GET /metrics` with +/// NO credential is refused `401` (the scrape is credential-gated, never exempt), while the same +/// credential-less client still reaches the declared-open routes: `/healthz` answers with its own +/// verdict (`200` or `503`, never the chain's `401`) and `GET /auth/token?logout=1` serves its +/// signed-out page (`200`). The same scrape with a chain-admitted credential is `200` and carries +/// a series (the legacy test seeded the request counter in process; a spawned node with no traffic +/// yet renders its scrape-time lane series, `busbar_lane_state`). +/// +/// RED: `/metrics` is re-exempted (a credential-less `200`), the chain shuts the declared-open +/// routes too, or an admitted credential's scrape is refused. +#[test] +fn test_metrics_requires_auth_in_chain_mode() { + let dir = fixture_dir(); + let (data_port, admin_port) = (common::boot::free_port(), common::boot::free_port()); + write_configs(&dir, data_port, admin_port); + let log_path = dir.join("out.log"); + let log = std::fs::File::create(&log_path).unwrap(); + let mut child = Reap( + Command::new(common::boot::exe()) + .env("BUSBAR_CONFIG", dir.join("config.yaml")) + .env("BUSBAR_PROVIDERS", dir.join("providers.yaml")) + .env("MOCK_KEY", "x") + .env("BUSBAR_ADMIN_TOKEN", ADMIN_TOKEN) + .env("RUST_LOG", "info") + .stdout(log.try_clone().unwrap()) + .stderr(log) + .spawn() + .expect("spawn busbar"), + ); + let deadline = Instant::now() + Duration::from_secs(30); + while !read_to_string(&log_path).contains("busbar listening") { + if let Some(status) = child.0.try_wait().expect("try_wait") { + panic!("busbar exited ({status}):\n{}", read_to_string(&log_path)); + } + assert!( + Instant::now() < deadline, + "busbar did not listen:\n{}", + read_to_string(&log_path) + ); + std::thread::sleep(Duration::from_millis(50)); + } + let (data, admin) = ( + format!("127.0.0.1:{data_port}"), + format!("127.0.0.1:{admin_port}"), + ); + let minted = http( + &admin, + "POST", + "/api/v1/admin/keys", + Some(r#"{"name":"metrics-scrapers"}"#), + Some(ADMIN_TOKEN), + ); + let token = minted + .body + .split("\"token\":\"") + .nth(1) + .and_then(|rest| rest.split('"').next()) + .unwrap_or_else(|| panic!("the admin API mints a key: {}", minted.body)) + .to_string(); + // The admitted scrape first, past the recorder's boot window. + let authed = settled(&data, "/metrics", &token); + assert_eq!( + authed.status, 200, + "/metrics with the configured credential must be admitted: {}", + authed.body + ); + assert!( + authed.body.contains("busbar_lane_state"), + "authed exposition should contain a metric; got:\n{}", + authed.body + ); + + let unauthed = http(&data, "GET", "/metrics", None, None); + assert_eq!( + unauthed.status, 401, + "/metrics must require auth in chain mode; a 200 means it was re-exempted like /healthz" + ); + let healthz = http(&data, "GET", "/healthz", None, None); + assert!( + matches!(healthz.status, 200 | 503), + "/healthz is declared open: the probe answers (200/503), never the chain's 401; got {}", + healthz.status + ); + let exchange = http(&data, "GET", "/auth/token?logout=1", None, None); + assert_eq!( + exchange.status, 200, + "/auth/token is declared open and must serve its signed-out page with no credential: {}", + exchange.body + ); + drop(child); + let _ = std::fs::remove_dir_all(&dir); +} diff --git a/crates/busbar/tests/metrics_scrape_boot_window.rs b/crates/busbar/tests/metrics_scrape_boot_window.rs index cec3c26a6a..c32511f97e 100644 --- a/crates/busbar/tests/metrics_scrape_boot_window.rs +++ b/crates/busbar/tests/metrics_scrape_boot_window.rs @@ -111,6 +111,8 @@ models: struct Scrape { status: u16, + /// The `content-type` value the answer carried (empty when absent). + content_type: String, body: String, } @@ -281,6 +283,7 @@ fn parse_response(raw: &[u8]) -> Scrape { let Some(split) = text.find("\r\n\r\n") else { return Scrape { status: 0, + content_type: String::new(), body: String::new(), }; }; @@ -292,7 +295,18 @@ fn parse_response(raw: &[u8]) -> Scrape { .and_then(|l| l.split_whitespace().nth(1)) .and_then(|s| s.parse().ok()) .unwrap_or(0); - Scrape { status, body } + let content_type = head + .lines() + .skip(1) + .filter_map(|line| line.split_once(':')) + .find(|(name, _)| name.trim().eq_ignore_ascii_case("content-type")) + .map(|(_, value)| value.trim().to_string()) + .unwrap_or_default(); + Scrape { + status, + content_type, + body, + } } fn read_to_string(path: &Path) -> String { @@ -302,3 +316,57 @@ fn read_to_string(path: &Path) -> String { } s } + +/// LEGACY the retiring engine crate's `src/engine/tests/forward_pool_integration_tests.rs::test_metrics_admitted_in_open_relay_mode` +/// (coverage map v2 row 137). 1.5.5: under an open auth chain (`chain: []`) a `GET /metrics` with +/// no credential is admitted: `200`, a `content-type` that starts `text/plain` (the Prometheus text +/// exposition), and a body that carries a series (the legacy test seeded the request counter in +/// process; a spawned node with no traffic yet renders its scrape-time lane series, `busbar_lane_state`). +/// +/// RED: the open-chain scrape is refused, answers another content type (a JSON or empty-typed +/// body a Prometheus scraper rejects), or the exposition lacks the request counter. +#[test] +fn test_metrics_admitted_in_open_relay_mode() { + let dir = fixture_dir(); + let data_port = free_port(); + let admin_port = free_port(); + write_configs(&dir, data_port, admin_port); + let log_path = dir.join("out.log"); + let log = std::fs::File::create(&log_path).unwrap(); + let log_err = log.try_clone().unwrap(); + let mut child = Command::new(common::boot::exe()) + .env("BUSBAR_CONFIG", dir.join("config.yaml")) + .env("BUSBAR_PROVIDERS", dir.join("providers.yaml")) + .env("MOCK_KEY", "x") + .env("RUST_LOG", "warn") + .stdout(log) + .stderr(log_err) + .spawn() + .expect("spawn busbar"); + let scraped = wait_for_full_scrape(data_port, Duration::from_secs(30)); + let _ = child.kill(); + let _ = child.wait(); + let Scrape { + status, + content_type, + body, + } = scraped.unwrap_or_else(|| { + panic!( + "busbar never answered a full GET /metrics within 30s; log:\n{}", + read_to_string(&log_path) + ) + }); + assert_eq!( + status, 200, + "an open chain admits the scrape with no credential" + ); + assert!( + content_type.starts_with("text/plain"), + "content-type was {content_type}" + ); + assert!( + body.contains("busbar_lane_state"), + "exposition should contain a metric; got:\n{body}" + ); + let _ = std::fs::remove_dir_all(&dir); +} diff --git a/testing/plane-copies/declared-credentials.json b/testing/plane-copies/declared-credentials.json index e1f0156fe5..a637f05063 100644 --- a/testing/plane-copies/declared-credentials.json +++ b/testing/plane-copies/declared-credentials.json @@ -545,4 +545,4 @@ {"body_hex":"7b7d","canonical_uri":"/model/m/converse","dialect":"bedrock","headers":[["authorization","415753342d484d41432d5348413235362043726564656e7469616c3d414b49442f32303135303833302f75732d656173742d312f626564726f636b2f617773345f726571756573742c205369676e6564486561646572733d636f6e74656e742d747970653b686f73743b782d616d7a2d636f6e74656e742d7368613235363b782d616d7a2d646174653b782d616d7a2d73656375726974792d746f6b656e2c205369676e61747572653d36373665363863613362636130313632316636336165306635353437343830336362303733663433353435626161346134653630663039666639393931373339"],["x-amz-date","3230313530383330543132333630305a"],["x-amz-content-sha256","34343133366661333535623336373861313134366164313666376538363439653934666234666332316665373765383331306330363066363163616166663861"],["x-amz-security-token","434c45414e544f4b454e"]],"host":"my-cname-front.example.com","key_hex":"414b49443a5345435245543a434c45414e544f4b454e","mode":"own","timestamp_epoch":1440938160}, {"body_hex":"7b7d","canonical_uri":"/model/m/converse","dialect":"bedrock","headers":[["authorization","415753342d484d41432d5348413235362043726564656e7469616c3d414b49442f32303135303833302f75732d656173742d312f626564726f636b2f617773345f726571756573742c205369676e6564486561646572733d636f6e74656e742d747970653b686f73743b782d616d7a2d636f6e74656e742d7368613235363b782d616d7a2d646174652c205369676e61747572653d31663537383439663639666162646263393365363836363031396266313738383832353037613634613433666530326536613164623164646133366165623639"],["x-amz-date","3230313530383330543132333630305a"],["x-amz-content-sha256","34343133366661333535623336373861313134366164313666376538363439653934666234666332316665373765383331306330363066363163616166663861"]],"host":"my-cname-front.example.com","key_hex":"414b49443a534543524554","mode":"own","timestamp_epoch":1440938160} ] -} +,"override_styles":{"about":"The auth style an operator's provider `auth:` override names (config ProviderAuth) and the door binds it under (root door_steps style_word): the token override binds its own style, never the dialect default.","token":"bearer"}}