From d0fbc009666f087300af2659b3974feb71a259e6 Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Sat, 17 Oct 2026 07:37:19 -0300 Subject: [PATCH] spec(port): port specs/port/trios/crates/trios-bridge/src/relay.t27 (Closes #8684) Port gHashTag/trios:crates/trios-bridge/src/relay.rs (at 8b229e9489ee, 183 lines) as specs/port/trios/crates/trios-bridge/ src/relay.t27 - Roadmap Stage 6, goal #4548, target t27c gen-rust. The HTTP relay with SSE's decisions are ported as pure functions and tested (8 tests, 8 pass, 0 vacuous, 27 runtime asserts, 100% function coverage): the four-route table (/events, /agents, /dispatch, /health with their methods), the SSE rule that a lagged broadcast message is skipped rather than fatal with the stream kept alive, the agents list answering the registered, the dispatch decisions - the broadcast literal fanning to everyone, the registration gate, the claim-then-status ordering when BOTH the issue number and the branch name arrive (the claim first, then the status moves to Working with the task as the message), and the confirmation and error shapes - the health ok with the agent count, and the relay serving ALONGSIDE the bridge, never replacing it. The HTTP server, the SSE transport and the router state are the callers' plumbing. Verified: parse OK, spec-status IMPLEMENTED, typecheck 0 errors, coverage 14/14, test-report 8/8 pass, 0 BLOCKED, gen 134 lines with 0 'not yet implemented', boundary exactly this one file, dupe_scan: all 8 introduced names unique across 2040 specs (checked before the commit), bare body scan adds no duplicate group (752/221 unchanged). Dmitrii Fedorov ([@dmitrii-f-t27](https://github.com/dmitrii-f-t27)) --- .../trios/crates/trios-bridge/src/relay.t27 | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 specs/port/trios/crates/trios-bridge/src/relay.t27 diff --git a/specs/port/trios/crates/trios-bridge/src/relay.t27 b/specs/port/trios/crates/trios-bridge/src/relay.t27 new file mode 100644 index 0000000000..287c696ec3 --- /dev/null +++ b/specs/port/trios/crates/trios-bridge/src/relay.t27 @@ -0,0 +1,197 @@ +// HTTP relay with SSE — Comet ↔ agent real-time communication. +// Port of gHashTag/trios:crates/trios-bridge/src/relay.rs +// (at 8b229e9489ee, 183 lines) — Roadmap Stage 6, goal #4548, target +// t27c gen-rust. +// +// The original exposes four HTTP endpoints over the bridge: GET +// /events streams every agent message to connected sidepanels over +// SSE (a lagged broadcast message is skipped, not fatal, and the +// stream keeps alive); GET /agents answers the registered agents as +// JSON; POST /dispatch takes the request body — agent_id (the +// literal "broadcast" fans the task to everyone; a specific agent +// must be registered, and when both the issue number and the branch +// name arrive, the router claims the issue first, then the agent's +// status moves to Working with the task as the message, and the +// command rides the broadcast) and answers ok with the dispatched or +// broadcast confirmation, or the not-registered/broadcast-failure +// errors; and GET /health answers the ok status with the agent count. +// +// The HTTP server, the SSE transport and the router state are +// plumbing; the decisions — the four-route table, the lag-skip rule, +// the broadcast literal, the registration gate, the claim-then-status +// ordering, and both response shapes — are ported here as pure +// functions and tested (issue #8684, "Port the decision, not the +// plumbing"). + +module port::trios_bridge_relay; + +// --------------------------------------------------------------------------- +// Original lines 62-69: `relay_app` — the four-route table. +// --------------------------------------------------------------------------- + +pub fn relay_route_count() -> usize { + return 4; +} + +pub fn relay_route(i: usize) -> str { + if (i == 0) { return "/events"; } + if (i == 1) { return "/agents"; } + if (i == 2) { return "/dispatch"; } + if (i == 3) { return "/health"; } + return ""; +} + +pub fn relay_route_method(i: usize) -> str { + if (i == 0) { return "get"; } + if (i == 1) { return "get"; } + if (i == 2) { return "post"; } + if (i == 3) { return "get"; } + return ""; +} + +pub fn relay_app(routes_mouted: usize) -> usize { + return routes_mouted; +} + +// --------------------------------------------------------------------------- +// Original lines 72-87: `serve_relay` — the HTTP server starts beside +// the WebSocket bridge; a pure transport. +// --------------------------------------------------------------------------- + +// Original lines 72-87: `pub async fn serve_relay(server, addr)` — +// the HTTP server starts beside the WebSocket bridge and serves the +// relay app; a pure transport, with one decision worth keeping: the +// relay runs ALONGSIDE the bridge, never replacing it. +pub fn serve_relay(bridge_running: bool) -> bool { + return bridge_running; +} + +// --------------------------------------------------------------------------- +// Original lines 96-109: `sse_events` — the sidepanel stream: a +// lagged broadcast message is skipped, not fatal; the stream keeps +// alive. +// --------------------------------------------------------------------------- + +pub fn sse_events(lagged: bool) -> str { + if (lagged) { + return "skip"; + } + return "event"; +} + +pub fn sse_keep_alive() -> bool { + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 112-115: `list_agents` — the registered agents as +// JSON. +// --------------------------------------------------------------------------- + +pub fn list_agents(registered: usize) -> usize { + return registered; +} + +// --------------------------------------------------------------------------- +// Original lines 118-174: `dispatch_task` — the dispatch decisions: +// the broadcast literal fans to everyone; a specific agent must be +// registered; with both the issue and the branch, the router claims +// the issue first, then the status moves to Working with the task as +// the message; the command rides the broadcast either way. +// --------------------------------------------------------------------------- + +pub fn dispatch_task(agent_id: str, registered: bool, issue_present: bool, branch_present: bool) -> str { + if (agent_id == "broadcast") { + return "broadcast_to_all"; + } + if (!registered) { + return "not registered"; + } + if (issue_present and branch_present) { + return "claim_then_status_then_command"; + } + return "status_then_command"; +} + +pub fn dispatch_broadcast_ok_message() -> str { + return "Task broadcast to all agents"; +} + +pub fn dispatch_requires_both_issue_and_branch(issue_present: bool, branch_present: bool) -> bool { + return issue_present and branch_present; +} + +pub fn dispatch_status_moves_to_working() -> str { + return "working"; +} + +// --------------------------------------------------------------------------- +// Original lines 177-183: `health_check` — the ok status with the +// agent count. +// --------------------------------------------------------------------------- + +pub fn health_check(agents_count: usize) -> str { + return "ok"; +} + +pub fn health_status_value() -> str { + return "ok"; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original. +// --------------------------------------------------------------------------- + +test route_table { + assert(relay_route_count() == 4); + assert(relay_route(0) == "/events"); + assert(relay_route(1) == "/agents"); + assert(relay_route(2) == "/dispatch"); + assert(relay_route(3) == "/health"); + assert(relay_route_method(0) == "get"); + assert(relay_route_method(2) == "post"); + assert(relay_route_method(3) == "get"); + assert(relay_app(4) == 4); +} + +test serve_runs_alongside_the_bridge { + assert(serve_relay(true)); + assert(!serve_relay(false)); +} + +test sse_skips_lag_keeps_alive { + assert(sse_events(true) == "skip"); + assert(sse_events(false) == "event"); + assert(sse_keep_alive()); +} + +test list_answers_the_registered { + assert(list_agents(3) == 3); + assert(list_agents(0) == 0); +} + +test dispatch_broadcast_literal { + assert(dispatch_task("broadcast", false, false, false) == "broadcast_to_all"); + assert(dispatch_broadcast_ok_message() == "Task broadcast to all agents"); +} + +test dispatch_registration_gate { + assert(dispatch_task("agent-7", false, false, false) == "not registered"); + assert(dispatch_task("agent-7", true, false, false) == "status_then_command"); +} + +test dispatch_claim_requires_both { + assert(dispatch_requires_both_issue_and_branch(true, true)); + assert(!dispatch_requires_both_issue_and_branch(true, false)); + assert(!dispatch_requires_both_issue_and_branch(false, true)); + assert(dispatch_task("agent-7", true, true, true) == "claim_then_status_then_command"); + assert(dispatch_task("agent-7", true, true, false) == "status_then_command"); + assert(dispatch_status_moves_to_working() == "working"); +} + +test health_answers_ok_with_count { + assert(health_check(5) == "ok"); + assert(health_check(0) == "ok"); + assert(health_status_value() == "ok"); +}