Skip to content

mcp stdio: relaying calls reach a child one at a time so each ask reaches only its caller; a lease reads whole frames; Frames reads each byte once (plane-mcp findings 5, 13, 14) - #645

Closed
MattJackson wants to merge 42 commits into
predevfrom
lane-mcpfix-stdio-v2

Conversation

@MattJackson

Copy link
Copy Markdown
Collaborator

MCP-FIX group D (stdio): plane-mcp audit findings 5 [HIGH, security], 13 and 14.

This is lane-mcpfix-stdio with its fix commit's wip: prefix dropped (no force-push: pushed as lane-mcpfix-stdio-v2; the RED is 11b3ca7, pushed as lane-mcpfix-stdio-v2-red; origin/lane-mcpfix-stdio-red 9754c46 is the superseded refuse-when-two-open design).

Finding 5 (security): a request over stdio names no call it serves (no related-request id on the wire; the progress token is never echoed; the child numbers its own ids), so a child's ask could reach the wrong caller (first reader wins). Ruling applied: calls to one stdio child whose asks may be relayed are SERIALISED per child, in arrival order (door_program Relaying line, in_line; take_turn in tool_door.rs). A call behind waits (Step::Wait, woken when the call ahead leaves, or at its own timeout: deadline, then answered as an upstream failure, never sent). A call whose asks went to its caller holds its place until its retry takes it up or the ask's state TTL lapses (ProgramRelay::hold). The ask belongs to the first call in line (first_in_line, Peer::owner, decided once by the first exchange to read it). An ask raised while no call is open is refused (-32001 ask_unattributed).

Finding 13: busbar-core-connector program.rs: a lease opened mid-frame skips that frame's rest and reads from the next frame.

Finding 14: tool_program.rs Frames scans only new bytes and parses a message once, when whole (linear, not quadratic, in a message read in small pieces).

Open points (the wire's limits, stated)

  • An ask raised by a finished call after the next call opened is attributed to that next call.
  • A serial child holding a relayed ask blocks the member's next relaying call until the retry or the ask TTL (300 s); a waiting call is answered busy past its timeout:.

Spec (docs/design/BUSBAR-1.6.0.md): Law 11, 2125-2132 (an ask is relayed down the session it belongs to, as-is); the no-blocking rule, 1276-1283 (bounded work on the worker).

RED plant: 11b3ca7: tool_program::tests::each_callers_ask_reaches_only_that_caller (finding 5), tests/alloc_gate.rs a_childs_message_read_in_small_pieces_is_parsed_once (finding 14), busbar-core-connector program::tests::a_lease_opened_mid_frame_reads_from_the_next_frame (finding 13). Each compiles against the unfixed tree and fails by assertion.

Merges (landing order): origin/predev, lane-mcpfix-deadplane (#638: tests/alloc_gate.rs keeps the counting allocator and the finding-14 plant; its decode pin drove the deleted Plane impl), lane-mcpfix-sessions-v2 (#640; both touch door_program.rs, tool_program.rs, tool_door.rs).

Proof: Latchkey cli-56d97d32-ba80-4936-87c9-ff577ec079ab on e066934: RED-EXIT=1 (PLANT-RED finding5, finding14, finding13); GREEN-EXIT=0: fmt --check, clippy -p busbar-plane-mcp -p busbar-core-connector --all-targets -D warnings, cargo test -p busbar-plane-mcp, cargo test -p busbar-core-connector (lib, no_thread_per_connection, no_unix_carrier, off_task_reactor; universal_needs needs the hop's dlopen cdylibs and is left to CI), cargo build -p busbar --bin busbar.

Includes #634, #637, #638, #639 and #640 by merge.

…pls the unserved Plane traits (finding 12; spec Law 11, never-constructed). Source scan: the honest plant for a pure deletion; fails while NestedPlane, SAMPLING_OP, impl Plane/SessionPlane for McpPlane exist
…wledging them, and a live ask lapses and is round-capped (findings 16, 17)

Plants the failing tests for plane-mcp findings 16 and 17 (DESIGN 1.6.0 lines 379-390).
era() answers logging/setLevel and resources/(un)subscribe with -32601 and initialize advertises
neither resources.subscribe nor logging; a live ask armed at t lapses at t + ASK_TIMEOUT_NS and a
request past MAX_LIVE_ASK_ROUNDS is not livened. arm, lapsed and may_liven are present as the
unfixed behaviour (no deadline, no cap) so the plant compiles and fails by assertion.
… its live asks (findings 16, 17)

16: resources/subscribe, resources/unsubscribe and logging/setLevel were acknowledged with {} and
dropped while initialize advertised resources.subscribe. Nothing on the plane announces a resource
change (every Listen::step is fed no updates) and a line answer carries no log records to filter, so
delivery is not possible on this carrier: the verbs now answer -32601, initialize no longer
advertises subscribe or logging, and the unused with_level, subscriptions_full, MAX_RESOURCE_SUBS and
the retained-uri Era variants are removed. A watch is subscriptions/listen's; subscribe stays for the
old revisions on their sessions (DESIGN 1.6.0 lines 379-390).

17: a live ask is armed with ASK_TIMEOUT_NS when issued (first ask and each next ask of a round); the
tick drops a lapsed entry and emits the documented fallback (the input_required result, line.rs
fallback) on its session; the retry unit carries round+1 and a request past MAX_LIVE_ASK_ROUNDS is
handed the input_required result as it is instead of being livened again.
… struck cross-plane sampling route (finding 12)

The crate serves only plane_door::door; nothing constructed McpPlane as a Plane. Removes src/tool_plane.rs
(impl Plane / SessionPlane for McpPlane, the sampling row to DestinationFacts::NestedPlane, the roots row, the
second meter that disagreed with the served door's) and tool_meta::SAMPLING_OP. Law 11 (spec 420-421, 2123-2129):
no call is routed to another plane on the content's say-so; spec 1930: a capability that is never constructed
does not ship. Server and the McpPlane field were read only by that impl and go with it; McpPlane stays as the
PlaneMeta/Plugin carrier the door reads (tool_door.rs PLANE/CLAIMS). The served door's meter is untouched.

Tests driving the dead impl retire under the coordinator's finding-12 order (F25, spec 1181); surviving
assertions move to plane-free tables (conformance, declarations_agree).
…indings 2, 3)

Plants three tests in busbar-plane-mcp src/tests/call.rs, driving the pure settle the door
serves every tools/call answer through (call::settle_call):

- a_result_reaches_its_caller_as_the_upstream_sent_it: a result carrying `Vec<String>` and
  `<b>x</b>` in content text, structuredContent and _meta must reach the caller unchanged, its
  bytes the upstream's own. Fails today: the built-in markup strip serves `Vec` and `x`
  (finding 2, call.rs:1382).
- structured_output_breaking_the_published_schema_is_relayed_unchanged: a structuredContent
  that fails the tool's published outputSchema must be relayed unchanged, dispatched, audited
  applied. Fails today: busbar answers its own isError "The structured result was NOT served"
  (finding 3, call.rs:1355-1378). Replaces structured_output_breaking_the_published_schema_is_a_tool_failure,
  which asserted the replacement.
- the_task_path_names_no_rewrite_of_the_result: a source plant over door_tasks.rs (the task
  continuation is reached only through a live door). Fails today: door_tasks.rs:755 runs the
  strip over every completed task result.

Spec (docs/design/BUSBAR-1.6.0.md): Law 11, lines 2123-2133 ("A plane reads content ONLY to
translate dialects and to meter usage", 2126; "Code that answers, executes or branches on what a
request or response asks for is a defect: it is deleted", 2131-2132); product hard rules
3369-3373 ("every client header and body field passes through unchanged, except what busbar
governs").
…caller text says relayed

Three plants for the MCP-FIX sampling group (kernel ingress audit H5; plane-mcp findings 19, 20):

- crates/busbar/tests/plane_host_universal_purity.rs
  no_crate_carries_a_resolved_completion_seam: a source scan (comments stripped, non-test files)
  of every crates/*/src for the kernel's resolved-completion seam (CompletionArrival,
  CompletionIngress, install_completion_ingress, set_test_completion_ingress, completion_ingress)
  and the LLM plane's synthesize_completion. Grep-style plant: the fix is a pure deletion.
- crates/busbar-plane-mcp/tests/purity.rs
  the_plane_satisfies_no_upstream_ask: a source scan of the plane's src for the satisfier
  vocabulary (InputRequiredLoop::may_satisfy). Grep-style plant: the fix is a pure deletion.
- crates/busbar-plane-mcp/src/tests/call.rs
  an_ask_refusal_tells_the_caller_the_ask_is_relayed_never_satisfied: runtime assertion over the
  caller-visible refusal text (an ungranted sampling ask; a complete result still carrying
  requestState), which today says busbar "satisfies" asks and that an ask "terminates at busbar
  and is never forwarded to you".
  a_granted_sampling_ask_is_relayed_to_the_caller_unchanged: a PIN, green on predev: the HTTP
  upstream path already relays a granted sampling ask byte for byte.

Spec: docs/design/BUSBAR-1.6.0.md Law 11 lines 2123-2133 (2127-2132: busbar routes no call to
another plane on the content's say-so, answers nothing on the caller's behalf; code that answers
what a response asks for is deleted); the relay-only ruling 418-422.
…ip and the outputSchema replacement are deleted (findings 2, 3)

Finding 2 [HIGH] (markup stripper on tool results):
- call.rs completed(): the result no longer passes through sanitize::normalise_json. The
  caller's answer carries the upstream's own `result` bytes (serde_json RawValue over the far
  end's answer) under the caller's id, instead of a re-serialised parsed Value; the one
  addition is the dialect's `resultType: complete` on a result object that carries none. A
  `resultType` the upstream sent is no longer overwritten.
- door_tasks.rs continuation_answered(): a completed task stores the upstream's result as it
  came (End::Completed(value)).
- sanitize.rs: normalise_json deleted (no caller left). normalise / normalise_opt stay: they
  serve busbar's own section text (catalogue descriptions, prompts/get, resources/read) and
  busbar's words on a failed leg.
- busbar-plane-mcp Cargo.toml: serde_json names `raw_value` itself.

Finding 3 [HIGH] (structuredContent schema failure replaced by busbar's error):
- call.rs completed(): the outputschema::check branch and its "The structured result was NOT
  served" answer are deleted; the upstream's result is relayed unchanged, dispatched, audited
  applied.
- outputschema.rs and src/tests/outputschema_tests.rs deleted: the check has no caller ("a
  capability that is never constructed does not ship", THE DESIGN 1930).

Tests retired: src/tests/outputschema_tests.rs (16 tests of the deleted check);
sanitize_tests.rs every_wrapper_normalises_through_the_same_function loses its normalise_json
half (renamed the_optional_wrapper_normalises_through_the_same_function, normalise_opt half
kept). call.rs a_result_is_normalised_stamped_and_dispatched is renamed
a_result_is_stamped_and_dispatched and now pins the relayed bytes and the empty-result stamp.

Prose: call.rs module doc, sanitize.rs module doc, tools_config.rs output_schema doc,
tests/sanitize_complexity.rs doc, docs/mcp.md (output_schema row, catalogue and
markup-normalisation paragraphs) no longer claim results are stripped or validated.

Spec (docs/design/BUSBAR-1.6.0.md): Law 11, 2123-2133 (2126 "A plane reads content ONLY to
translate dialects and to meter usage"; 2131-2132 "Code that answers, executes or branches on
what a request or response asks for is a defect: it is deleted, never added"); product hard
rules 3369-3373 ("every client header and body field passes through unchanged, except what
busbar governs"); 1930.
… clock refuses sealed state, own and relayed (finding 11)

Plants for the two MEDIUM findings; both fail by assertion on the unfixed tree:
- ask: the state minted for the final round, re-sent with no inputResponses, proceeds
  to the tool (spec 379-390, the ask gate).
- door: a host whose clock.now fails (served and failing, or unserved) accepts a lapsed
  own state and a lapsed relayed state because now=0, and mints a state stamped at the
  epoch (spec 1576, 3295-3370 fail closed).
…aled state (findings 10, 11)

10: decide() redeemed the final round's state before the answered check, so the state
minted for the last round, re-sent with no inputResponses, proceeded to the tool. The
answered check now runs before the final redeem; the refusal spends nothing (spec 379-390,
the ask gate; ask.rs module promise).

11: DoorSeal::now returns None when clock.now fails, and the reads that fed a sealed
state's window (decide_ask, relay_ask, the task relay's mint and open) no longer
substitute 0: decide_ask treats a failed clock as no sealer, relay_ask refuses as no
sealer, the task relay mints and opens nothing. Fail closed (spec 1576, 3295-3370).
…m's ask is relayed to the caller

Kernel ingress audit H5 (Law 11) and plane-mcp findings 19 (InputRequiredLoop::may_satisfy) and 20
(prose says asks terminate at busbar or are satisfied by it; the code relays them).

1.5.5 did not do this: `git grep -n -i 'synthesize_completion\|completion_ingress\|createMessage'
v1.5.5 -- crates/` is empty (exit 1); 1.5.5 had no MCP plane. So this is a deletion, not a parity
break.

busbar-kernel (crates/busbar-kernel/src/ingress/arrival.rs): the resolved-completion seam is
deleted — CompletionArrival, CompletionIngress, INSTALLED_COMPLETION_INGRESS,
install_completion_ingress, TEST_COMPLETION_INGRESS_HOOK, set_test_completion_ingress,
completion_ingress. It had no in-tree reader; it existed so a plane could run a chat completion on
the LLM plane on an upstream's say-so.

busbar-llm: synthesize_completion (native_ingress.rs) is deleted with its production install
(linked::install_protocol_seams, which now installs only the stream translator) and its test-kit
seed (testkit/mod.rs). The ArrivalCtx downcast it shared with the test kit's shell moves into
testkit/shell.rs, its one remaining caller. Prose naming the re-entry is corrected (lib.rs,
native_ingress.rs, unit/audit.rs).

busbar-plane-mcp:
- client/jsonrpc.rs: the satisfier is deleted — InputRequiredLoop (new/may_satisfy/rounds) and its
  AskRefusal (Ungranted/LoopExhausted), constructed by nothing but their own tests. The module
  header said an upstream's ask "terminates at busbar" and is "NEVER proxied outward"; it now says
  what the code does: a granted ask is relayed to the caller with inputRequests as sent, an
  ungranted one is refused, relayed rounds are capped. ServerRequestGrants::allows loses a false
  dead_code allow (the stdio relay reads it).
- call.rs: the caller-visible texts. The ungranted refusal said the server "asked busbar to
  satisfy" an ask; it now says the ask was not relayed to the caller. The terminal check's refusal
  said "An upstream's ask terminates at busbar and is never forwarded to you"; it now says an ask is
  relayed only as an input-required result and this answer (a complete result still carrying an
  ask's field) is not served. The audit reason word `ask_not_proxied` is unchanged.
- tools_config.rs, tool_ops.rs (OP_SAMPLING doc), tool_records.rs (the "synthesize a completion"
  and "sampling spend" rows), client/peer.rs: satisfier prose reworded to "relayed to the caller".
- The HTTP upstream path (call::judge_ask -> Settled::Relay) and the stdio relay
  (tool_program.rs) already relay a granted sampling/elicitation/roots ask unchanged and satisfy
  none; nothing on a served path changes. The stdio cross-caller correlation is untouched.
- `max_input_required_rounds` stays: it is a predev config key (in the config schema snapshot and
  docs/mcp.md), and it already caps RELAYED rounds (judge_ask, tool_door.rs); only its doc changes
  ("never relay one").
- Tests retired with the satisfier (client/tests/ask_tests.rs): an_ungranted_ask_is_refused_and_
  the_message_names_the_grant, a_revoked_grant_bites_on_the_very_next_retry,
  each_grant_is_independent, a_hostile_upstream_cannot_amplify_cost_by_asking_forever,
  the_bound_is_per_dispatch, a_cap_of_zero_denies_every_round. They drove only the deleted loop;
  the live gate's grant and round-cap refusals are held by tests/call.rs
  (an_ungranted_ask_is_refused_and_never_forwarded, an_ask_mixing_an_ungranted_kind_is_refused,
  the_round_cap_bites_before_the_grant_is_read) and client/tests/peer_tests.rs.

busbar (tests only): plane_node.rs comments and the purity self-test fixture no longer name the
deleted synthesizer.

Spec (docs/design/BUSBAR-1.6.0.md): Law 11, lines 2123-2133 (2127-2132: busbar routes no call to
another plane on the content's say-so, answers nothing on the caller's behalf; code that answers
what a response asks for is deleted, never added); the relay-only ruling, lines 418-422 ("routes
no call to another plane"); B.3 item 10, line 5139 (an elicitation request is plane traffic to the
caller); the mcp bullet, lines 379-390 (baseline is predev behaviour).
…finding 4)

Two served-door tests in the root rig (crates/busbar/src/root/tests/door_steps.rs):
- a_host_the_deployment_refuses_is_refused_in_the_arguments: the rig's deployment blocks
  `blocked.example` (security.blocked_metadata_hosts); a call whose arguments name it must be
  refused 403 / -32000 / tool_argument_refused before it is sent. The plane's own predicate admits
  it, so this fails on the unfixed tree: the plane never asks the host.
- every_internal_address_the_guard_refuses_is_refused_in_the_arguments: the nine addresses the
  connector's guard refuses as internal (198.18.0.1, 192.0.0.8, 0.1.2.3, [::ffff:198.18.0.1],
  [::1%25lo], [fe80::1%25eth0], 224.0.0.1, 255.255.255.255, [ff02::1]) are refused in arguments
  by that same judgement.

The rig's kernel services now carry the deployment's one destination judge (the connector's
guard), as root::serve::kernel_services composes it in production, so the test host's dest.judge
is the kernel's real judgement, not a stub.

Spec (docs/design/BUSBAR-1.6.0.md): 5140 (B.3 item 11: argument-embedded URL judging is
dest.judge), 1579 (the dest.judge row), 614-621 (one guard, every scattered check deleted).
…anged (finding 2)

Plants an_upstream_error_message_reaches_its_caller_unchanged in busbar-plane-mcp
src/tests/call.rs: an upstream error whose message carries `Vec<String>` and `<b>x</b>` must
reach the caller byte-identical inside busbar's failure words. Fails today:
call.rs upstream_failure_result runs the markup strip over the whole text, serving `Vec` and
`x`. The envelope shape (isError result) is unchanged by this plant.

Spec (docs/design/BUSBAR-1.6.0.md): Law 11, 2126 and 2131-2132; product hard rules 3369-3373.
…d (finding 2)

call.rs upstream_failure_result no longer runs sanitize::normalise over its text. The text is
busbar's fixed wording ("The MCP server `<server>` did not complete this tool call: ...")
around the reason, and on an upstream error the reason carries the upstream's own JSON-RPC
error message, which the strip rewrote (`Vec<String>` -> `Vec`). The envelope (an isError
result) is unchanged. sanitize.rs and tests/sanitize_complexity.rs docs no longer name the
failure text as a strip site.

Spec (docs/design/BUSBAR-1.6.0.md): Law 11, 2126 and 2131-2132; product hard rules 3369-3373.
The structured-output check it reported is deleted (34df85d), so nothing emits the code.
Retired the way the catalogue retires a code (busbar-kernel diagnostics 8013): the number and
slug are kept, `retired: true`, the title marked RETIRED, the summary says what was removed and
why, the action is "Nothing emits this code." The plane's declares.json carries the same
title/summary/action (src/tests/diagnostics_tests.rs the_declares_file_states_the_catalog holds
it to the constant). docs/diagnostics-mcp.{md,json} edited by hand in the kernel renderer's exact
form (render_markdown_for appends " *(retired)*" to the heading; render_json_for writes
"retired": true): no committed-page test exists for the mcp page.

Spec (docs/design/BUSBAR-1.6.0.md): Law 11, 2131-2132; 1930.
…used as ask_unavailable naming the clock (finding 11)

The relayed state's seal is one function, seal_relayed, shared by relay_ask and the task relay
(behaviour unchanged: a clock failure still answers as NoSealer). The plants call it with each
site's window and assert the refusal reason ask_unavailable and the CLOCK_UNAVAILABLE words;
both fail by assertion (spec 3295-3370, fail closed).
…y text (findings 10, 11)

A clock.now failure answered as NoSealer, whose text says the deployment has no
auth.signing_key. busbar's own ask and a relayed state presented to decide_ask now refuse
with ClockUnavailable (reason word ask_clock_unavailable); a relayed ask and a task relay's
park refuse with Unavailable (ask_unavailable) carrying CLOCK_UNAVAILABLE. Still fail
closed, never the epoch (spec 3295-3370).
… rules are deleted (finding 4)

plane-mcp finding 4 [HIGH]: the argument SSRF guard decided in the plane (argguard::judge_host
over busbar_contract::net::host_is_cloud_metadata / is_alternate_ipv4_encoding /
host_is_private_or_loopback under the registration's allow_private) and never asked the host.

- argguard.rs: judge_host now asks the host's ONE destination judge for every host the walk
  finds (an IPv6 literal bracketed, as dest.judge reads host[:port]) and renders its DEST_*
  verdict; a host that gives no verdict refuses the value (fail closed). SsrfPolicy and the local
  host rules are deleted; the walk, the scheme allowlist and the host reader stay.
- call.rs: the guard leaves admit_trusted (and its allow_private parameter) for
  judge_arguments, run on the admitted call, so the door's ask seal and the judge never hold the
  unit's handle counter at once. The refusal bytes are unchanged: 403, JSON-RPC -32000,
  data.reason tool_argument_refused, the same call-log line.
- tool_door.rs: dest_verdict asks dest.judge (Services::dest_judge_as) under the deployment's
  default egress class, DEST_REFUSE_PRIVATE unless the registration granted allow_private, name
  only (never DEST_RESOLVE, so it never pends), each on a fresh handle of the unit's ticket.
- door_tasks.rs: the task path's re-judgement of the arguments asks the same judge.
- tests: argguard_tests judge through a stand-in host over the contract's address predicates;
  call tests prove the host's verdict alone decides, each host is asked once for the called tool,
  and a host with no verdict refuses.

Spec (docs/design/BUSBAR-1.6.0.md): 5140 (Appendix C B.3 item 11: argument-embedded URL judging
is dest.judge, called where it is called today), 5111 (the B.2 row: dest.judge may pend: no),
1579 (the dest.judge row: judged against the egress rules, allow-list, class, metadata hosts),
614-621 (every outbound check in ONE place; every scattered check deleted in its favour),
4945 (class 0 of dest.judge is the deployment's security section).
…d-frame lease skips the frame's rest; Frames reads a message once

Three plants, each failing on the unfixed tree by assertion (all compile against it):

- finding 5 [HIGH, SECURITY] (src/tests/tool_program.rs each_callers_ask_reaches_only_that_caller):
  two callers A and B on one stdio child, the child asks each for a sampling. The stand-in door
  keeps the child's line of relayed calls (`open`, arrival order). First-reader-wins
  (`peer.claim`) hands A's ask to B's exchange, which reads it first. Asserted: A's ask reaches
  only A, B's only B, neither is refused; an ask raised with no call in the line is refused once
  (`ask_unattributed`). Law 11 (spec 2125-2132): an ask is relayed down the session it belongs
  to, as-is.
- finding 13 [MEDIUM] (busbar-core-connector program_tests a_lease_opened_mid_frame_reads_from_the_next_frame):
  a lease opened while the program is part way through one frame reads that frame's rest as its
  first bytes. The test framer gains a `piece` knob so one frame arrives in several pieces, as the
  stdio line framer cuts a line its sink cannot hold.
- finding 14 [MEDIUM] (tests/alloc_gate.rs a_childs_message_read_in_small_pieces_is_parsed_once):
  a 2000-item message pushed in 64-byte pieces; Frames::push re-parses everything held per piece,
  so the allocation count is quadratic. Asserted: under 20 per item. The no-blocking rule (spec
  1276-1283): bounded work on the worker.
… tests stay deleted (finding 12); predev's refusal_words class table lived only in that unserved impl, so the xtask refusal-collapse scan drops its mcp row
…gotiator, through the door

tests/sessions.rs drives the MCP door over its own ABI under stand-in host services and a
stand-in connector (two upstreams: one that requires a session, one stateless) and asserts
THE DESIGN section 2 mcp bullet (docs/design/BUSBAR-1.6.0.md lines 379-390), finding 1 of the
plane-mcp audit:

- a 2025-06-18 initialize answers its revision and an Mcp-Session-Id of 128 bits; a request in
  that session is served and lowered (no stateless-only members);
- a session presented by another owner is 404 on POST and DELETE; the owner's DELETE ends it;
- a GET naming no revision opens the 2024-11-05 stream with its endpoint event, a message POSTed
  to the address is 202 and answered on the stream; a GET naming a revision, a plain GET and a
  DELETE with no session are 405;
- an upstream that refuses the stateless revision and requires initialize is reached by
  negotiation (and re-initialised when it forgets the session); a stateless upstream sees the
  stateless request exactly as the dialect builder writes it, with no handshake;
- subscribe stays for the old revisions (line 380): a 2025-06-18 session's resources/subscribe
  hears the announcing upstream's notifications/resources/updated on its GET stream.

Fails on the unfixed tree by assertion: every non-POST claim is refused 405 and initialize is
refused -32602.

src/tests/session_tests.rs plants finding 18: a push whose trim frees more than its own cost
underflows `cost - freed` (tool_sessions.rs push), a panic under overflow checks.
…oth directions; the session table's byte account never underflows

Finding 1 (plane-mcp, HIGH). THE DESIGN section 2, the mcp bullet (docs/design/BUSBAR-1.6.0.md
lines 379-390), wired on the served path through a new door child, src/door_sessions.rs:

- Inbound on the endpoint's three claims: a POST carrying the stateless marker, or naming neither
  a session nor initialize, is the stateless path untouched (adapt::classify_post); initialize
  opens a session in the revision negotiation picks (revision::negotiate; no revision config), its
  id 128 bits from the host's random.fill, bound to its owner {principal, credential} = the
  kernel's caller reference (derived from the key id) or "<ungoverned>"; any other owner, unknown,
  ended or expired id is 404 on every path (POST, GET, DELETE, the 2024-11-05 message address);
  a dispatched session method is raised (adapt::raise, with the header and parameter mirror) into
  the one dispatch and lowered after it (adapt::lower_result); an MCP-Protocol-Version that
  disagrees with the session is 400; DELETE ends the owner's session.
- GET: with a session, that session's event stream (resumed after Last-Event-ID from the table);
  with none and no MCP-Protocol-Version, the 2024-11-05 stream whose first event names the message
  address (adapt::endpoint_event), its answers delivered on the stream and its POSTs answered 202
  (revision::sessionless_get); a GET naming a revision, a GET accepting no event stream and a
  DELETE naming no session are 405 (lines 387-390). The streams are held as K6 sessions on the
  door's existing drive/tick machinery (door_listen).
- Sessions are this process's; per-owner session and byte quotas are tool_sessions::Bounds; the
  ungoverned chain's single owner isolates nothing, and the instance's first 2024-11-05 stream
  says so once as the declared diagnostic mcp-legacy-stream-ungoverned (lines 385-387).
- Subscribe stays for the old revisions (line 380, lane-lead ruling): a session's
  resources/subscribe, resources/unsubscribe and logging/setLevel are answered from its own state
  under a per-owner subscription cap; an upstream's notifications/resources/updated (on the event
  stream it answers a relayed call with, or from a stdio child) reaches every watching session's
  GET stream after the subscriber's entitlement is re-asked; its notifications/message reaches the
  caller's session past that session's level floor. initialize declares resources.subscribe and
  logging on every session revision.
- Outbound (client::negotiate on the served path): the walk's first hop is the stateless request
  byte for byte as before (the predev fence), or, to an upstream busbar holds a session with, that
  request lowered into it; a refusal moves the client ladder over the door's own connector
  (initialize, notifications/initialized, the lowered request), and a session the upstream forgot
  (404) is re-initialised once. Verify-on-call's tools/list negotiates the same way. An upstream
  that answered the stateless revision is remembered (tool_sessions::UpstreamTable) and never
  renegotiated; 401/403/407/429 are not revision signals. The ladder's last rung (the 2024-11-05
  event stream as a client) is not carried: an upstream that refuses both the stateless request and
  initialize fails the call with an upstream-failure answer naming the revision.
- The line carrier's initialize negotiates too (line.rs): a session revision a client asks for is
  answered in it and the carrier session's following lines are raised and lowered.
- The prose that claimed ONE revision is rewritten (codec.rs PROTOCOL_VERSION, checks.rs
  SUPPORTED_PROTOCOL_VERSIONS, which stays the stateless dispatch's data.supported so the stateless
  path's bytes do not move, door.rs 405 words, docs/mcp.md).

Finding 18 (LOW): tool_sessions push releases freed - cost when a trim frees more than the pushed
event cost, instead of underflowing cost - freed.

An unconfigured node claims no /mcp route, so its GET /mcp stays the 1.5.5 404.
…initialize negotiation (Era::Opened, the raised line) re-applied on L's carrier, which refuses logging/setLevel and resources/(un)subscribe with -32601 and declares neither in any revision it speaks; the subscription uri bound moves to door_sessions.rs, where the session revisions keep subscribe
…a lease reads whole frames; Frames reads each byte once

Finding 5 [HIGH, SECURITY]: a request over stdio names no call it serves (no related-request id on
the wire; the progress token is never echoed; the child numbers its own ids), so the door makes
attribution unambiguous by SERIALISING (lane-lead ruling): calls to a member whose grants let its
asks be relayed reach its child one at a time, in arrival order (door_program `Relaying` line,
`in_line`); a call behind waits (Step::Wait, woken when the call ahead leaves, or at its own
`timeout:` deadline, then answered as an upstream failure, never sent: `take_turn`). A call whose
asks went to its caller holds its place until its retry takes it up or the ask's state TTL lapses
(`ProgramRelay::hold`). The ask is the call's first in line (`first_in_line`, `Peer::owner`,
decided once by the first exchange to read it); an ask raised with no call in line is refused
once (`-32001` `ask_unattributed`). Law 11, spec 2125-2132.

Finding 13: busbar-core-connector program.rs: a lease opened mid-frame skips that frame's rest.
Finding 14: tool_program.rs Frames scans only new bytes and parses a value once, when whole
(spec 1276, 1283).
…te.rs keeps the counting allocator and finding 14's stdio plant; its decode pin drove the deleted unserved Plane impl (finding 12) and goes with it
@MattJackson

Copy link
Copy Markdown
Collaborator Author

Merged up with #616 (ask) and #615 (sampling), which sit ahead of this PR in the predev merge queue and touch the same files (call.rs, purity.rs, door_tasks.rs), so this PR stays clean when they land. Resolutions: call.rs's settle doc says both an ask and a result are relayed; purity.rs keeps both source scans; the relayed task leg opens the retry's state and keeps ask's rule that an unreadable clock opens nothing.

Proof of the combined tree: Latchkey cli-af296ba4-4bea-4bc7-8d9b-22c41bf0b838 (every mcpfix group plus ask and sampling; identical on all touched paths to the bounds head): RED on 6d297d1 failed 3 of 3 by assertion; GREEN-EXIT=0 for fmt --check, clippy -p busbar-kernel -p busbar-llm -p busbar-plane-mcp --all-targets -D warnings, cargo test -p busbar-kernel / busbar-llm / busbar-plane-mcp, cargo build -p busbar --bin busbar, busbar plane_host_universal_purity and plane_node.

@MattJackson
MattJackson marked this pull request as draft October 8, 2026 03:27
auto-merge was automatically disabled October 8, 2026 03:27

Pull request was converted to draft

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

promote into predev: NO VERDICT @145985233

The hop ended without writing a verdict (runner lost, timeout, cancel, or it died before the engine judged): hop shard 3: cancelled at step Run bash "$GITHUB_WORKSPACE/busbar-release/ci/promote/hop.sh"; hop shard 2: cancelled at step Run bash "$GITHUB_WORKSPACE/busbar-release/ci/promote/hop.sh"; hop shard 1: cancelled at step Run bash "$GITHUB_WORKSPACE/busbar-release/ci/promote/hop.sh"; hop shard 4: cancelled; publish: runner lost at step Run bash "$GITHUB_WORKSPACE/busbar-release/ci/promote/fallback.sh", Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1; hop: cancelled (The run was canceled by @MattJackson.) (The run was canceled by @MattJackson.) (The run was canceled by @MattJackson.) (The run was canceled by @MattJackson.).

No failing-test list exists for this run. Run: https://github.com/GetBusbar/busbar/actions/runs/37720338793 . Re-run the hop, or read the job's step list in the run page.

@MattJackson

Copy link
Copy Markdown
Collaborator Author

Closed: superseded by #703 (the combined MCP-FIX stack carries this piece). Branch kept.

@MattJackson
MattJackson deleted the lane-mcpfix-stdio-v2 branch October 10, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant