diff --git a/specs/port/trios/crates/trios-bridge/src/server.t27 b/specs/port/trios/crates/trios-bridge/src/server.t27 new file mode 100644 index 0000000000..05bce968e2 --- /dev/null +++ b/specs/port/trios/crates/trios-bridge/src/server.t27 @@ -0,0 +1,203 @@ +// WebSocket server for the Trinity Agent Bridge. +// Port of gHashTag/trios:crates/trios-bridge/src/server.rs +// (at 8b229e9489ee, 165 lines) — Roadmap Stage 6, goal #4548, target +// t27c gen-rust. +// +// The original runs the agent-bridge WebSocket server: a new server +// carries its agent router, a broadcast channel with room for 1024 +// queued messages, the GitHub repo and the optional GitHub token; the +// getters read them back (the token optional, so an absent token +// answers none); subscribe joins the broadcast; broadcast_message +// serializes a bridge message and fans it to every subscriber; serve +// accepts connections, forwarding broadcasts out and dispatching +// incoming text through handle_message (a Close frame or a stream +// error ends the read loop, the handshake failure only logs); and +// handle_message routes by message type — a SendCommand for an agent +// that is not registered and not the broadcast target answers the +// agent_not_found error to everyone, otherwise the message forwards; +// ClaimIssue and UpdateStatus apply to the router and then broadcast +// the fresh board state; ListAgents answers the board state; anything +// else forwards unchanged. +// +// The WebSocket accept loop, the serialization and the router state +// are plumbing; the decisions — the 1024-deep broadcast capacity, the +// token's optionality, the dispatch table with its agent_not_found +// gate and the three board-refreshing variants — are ported here as +// pure functions and tested (issue #9121, "Port the decision, not +// the plumbing"). + +module port::trios_bridge_server; + +// --------------------------------------------------------------------------- +// Original lines 25-33: `pub fn new(github_repo, github_token)` — the +// server carries the router, a 1024-slot broadcast channel, the repo +// and the optional token. +// --------------------------------------------------------------------------- + +pub struct BridgeServerPlan { + broadcast_capacity: usize, + token_present: bool, + repo: str, +} + +pub fn BridgeServer_new(github_repo: str, github_token_present: bool) -> BridgeServerPlan { + return BridgeServerPlan { + broadcast_capacity: 1024, + token_present: github_token_present, + repo: github_repo, + }; +} + +pub fn broadcast_channel_capacity() -> usize { + return 1024; +} + +// --------------------------------------------------------------------------- +// Original lines 36-53: the getters — router, repo, and the optional +// token answering none when absent; subscribe joins the broadcast. +// --------------------------------------------------------------------------- + +pub fn BridgeServer_router() -> bool { + return true; +} + +pub fn BridgeServer_github_repo(plan: BridgeServerPlan) -> str { + return plan.repo; +} + +pub fn BridgeServer_github_token(token_present: bool) -> bool { + return token_present; +} + +pub fn BridgeServer_subscribe() -> bool { + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 56-60: `broadcast_message` — serialize then fan out; +// a failed send is tolerated (the `let _`), so the broadcast answers +// Ok regardless. +// --------------------------------------------------------------------------- + +pub fn BridgeServer_broadcast_message(send_failed: bool) -> bool { + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 63-119: `serve` — the accept loop. The read loop +// continues on text frames, ends on a Close frame or a stream error, +// ignores other frame kinds, and a failed handshake only logs (the +// server keeps accepting). +// --------------------------------------------------------------------------- + +pub fn BridgeServer_serve(handshake_failed: bool) -> bool { + return true; +} + +pub fn read_loop_continues(frame_kind: str) -> bool { + if (frame_kind == "text") { + return true; + } + if (frame_kind == "close") { + return false; + } + if (frame_kind == "error") { + return false; + } + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 122-165: `handle_message` — the dispatch table. The +// SendCommand gate: a target that is neither the broadcast literal nor +// a registered agent answers the agent_not_found error broadcast; +// otherwise the message forwards. ClaimIssue and UpdateStatus apply +// and refresh the board state for everyone; ListAgents answers the +// board state; anything else forwards unchanged. +// --------------------------------------------------------------------------- + +pub const BROADCAST_TARGET: str = "broadcast"; + +pub fn BridgeServer_handle_message(msg_kind: str, target: str, target_registered: bool) -> str { + if (msg_kind == "send_command") { + if (target != BROADCAST_TARGET and !target_registered) { + return "agent_not_found"; + } + return "forward"; + } + if (msg_kind == "claim_issue") { + return "apply_and_board"; + } + if (msg_kind == "update_status") { + return "apply_and_board"; + } + if (msg_kind == "list_agents") { + return "board_state"; + } + return "forward"; +} + +pub fn agent_not_found_error_code() -> str { + return "agent_not_found"; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original. +// --------------------------------------------------------------------------- + +test new_carries_capacity_repo_and_token { + const with_token = BridgeServer_new("gHashTag/trios", true); + assert(with_token.broadcast_capacity == 1024); + assert(with_token.token_present); + assert(with_token.repo == "gHashTag/trios"); + const without_token = BridgeServer_new("gHashTag/t27", false); + assert(!without_token.token_present); + assert(broadcast_channel_capacity() == 1024); +} + +test getters_read_back { + const plan = BridgeServer_new("gHashTag/trios", false); + assert(BridgeServer_router()); + assert(BridgeServer_github_repo(plan) == "gHashTag/trios"); + assert(!BridgeServer_github_token(false)); + assert(BridgeServer_github_token(true)); + assert(BridgeServer_subscribe()); +} + +test broadcast_tolerates_failed_sends { + assert(BridgeServer_broadcast_message(false)); + assert(BridgeServer_broadcast_message(true)); +} + +test serve_accepts_past_failures { + assert(BridgeServer_serve(true)); + assert(BridgeServer_serve(false)); +} + +test read_loop_frame_rules { + assert(read_loop_continues("text")); + assert(!read_loop_continues("close")); + assert(!read_loop_continues("error")); + assert(read_loop_continues("ping")); +} + +test broadcast_target_literal { + assert(BROADCAST_TARGET == "broadcast"); +} + +test send_command_gate { + assert(BridgeServer_handle_message("send_command", "broadcast", false) == "forward"); + assert(BridgeServer_handle_message("send_command", "agent-7", true) == "forward"); + assert(BridgeServer_handle_message("send_command", "agent-7", false) == "agent_not_found"); + assert(BridgeServer_handle_message("send_command", "broadcast", true) == "forward"); + assert(agent_not_found_error_code() == "agent_not_found"); +} + +test dispatch_table_variants { + assert(BridgeServer_handle_message("claim_issue", "", true) == "apply_and_board"); + assert(BridgeServer_handle_message("update_status", "", true) == "apply_and_board"); + assert(BridgeServer_handle_message("list_agents", "", true) == "board_state"); + assert(BridgeServer_handle_message("unknown_kind", "", true) == "forward"); + assert(BridgeServer_handle_message("board_state", "", true) == "forward"); +}