From afc25cad164dcaad6f8d263f6ddfe702619ee87a Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Sat, 17 Oct 2026 03:36:35 -0300 Subject: [PATCH 1/2] spec(port): port specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 (Closes #9795) Port gHashTag/trios-railway:crates/trios-railway-core/src/ transport.rs (at 8b229e9489ee, 210+ lines) as specs/port/ trios-railway/crates/trios-railway-core/src/transport.t27 - Roadmap Stage 6, goal #4548, target t27c gen-rust. The RW-02 client's decisions are ported as pure functions and tested (9 tests, 9 pass, 0 vacuous, 36 runtime asserts, 13/14 coverage - the one untested is the fingerprint transport, by design): the canonical 8-4-4-4-12 hex UUID shape check byte by byte (dashes at 8, 13, 18, 23; hex elsewhere; 36 bytes), the env override chain (team | bearer | personal -> team, project -> project) with the UUID heuristic guessing project tokens, the team default of with_token, the auth-mode readback, the endpoint override, the two auth header names (Authorization: Bearer vs Project-Access-Token), and the query verdicts (GraphQL errors -> graphql, absent data -> empty data). The token itself is never stored - presence flags carry its decisions; the fingerprint delegates to the hash module and keeps its transport name with an undefined; body no test calls. Verified: parse OK, spec-status IMPLEMENTED, typecheck 0 errors, test-report 9/9 pass, 0 BLOCKED, gen 197 lines with 0 'not yet implemented', boundary exactly this one file, dupe_scan: all 16 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-railway-core/src/transport.t27 | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 diff --git a/specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 b/specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 new file mode 100644 index 0000000000..eafcfdeec4 --- /dev/null +++ b/specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 @@ -0,0 +1,262 @@ +// RW-02: the minimal Railway GraphQL client. +// Port of gHashTag/trios-railway:crates/trios-railway-core/src/transport.rs +// (at 8b229e9489ee, 210+ lines) — Roadmap Stage 6, goal #4548, target t27c +// gen-rust. +// +// The original talks to the Railway GraphQL endpoint with either of two +// authorization styles: `team` (the default) sends `Authorization: +// Bearer ` for account tokens; `project` sends +// `Project-Access-Token: ` for project-scoped tokens. The env +// read allows operators to force the mode (team | bearer | personal → +// team, project → project), and otherwise guesses from the token's +// shape: a canonical 8-4-4-4-12 hex UUID means a project token. The +// token fingerprint is suitable for audit lines and never exposes the +// token. The raw query posts the request, joins any GraphQL errors with +// "; ", and treats absent data as an error of its own. +// +// The HTTP round-trip and the hash are plumbing; the decisions — the +// UUID shape check (36 bytes, dashes at 8, 13, 18, 23, hex elsewhere), +// the env-override and heuristic auth-mode chain, the team default of +// with_token, the endpoint override, the two header names, and the +// query verdicts (errors → graphql, empty data → empty) — are ported +// here as pure functions and tested. The token itself is never stored +// in the port: presence flags carry its decisions (issue #9795, "Port +// the decision, not the plumbing"). + +module port::trios_railway_core_transport; + +// Original line 13: the endpoint constant. +pub fn graphql_endpoint() -> str { + return "https://backboard.railway.com/graphql/v2"; +} + +// --------------------------------------------------------------------------- +// Original lines 16-33: `fn is_uuid_like(s) -> bool` — the canonical +// 8-4-4-4-12 hex UUID shape, byte by byte. +// --------------------------------------------------------------------------- + +pub fn is_hex_digit(b: u8) -> bool { + if (b >= '0' and b <= '9') { + return true; + } + if (b >= 'a' and b <= 'f') { + return true; + } + if (b >= 'A' and b <= 'F') { + return true; + } + return false; +} + +pub fn is_dash_position(i: usize) -> bool { + if (i == 8) { return true; } + if (i == 13) { return true; } + if (i == 18) { return true; } + if (i == 23) { return true; } + return false; +} + +pub fn is_uuid_like(s: []const u8) -> bool { + if (s.len != 36) { + return false; + } + var i: usize = 0; + while (i < 36) { + if (is_dash_position(i)) { + if (s[i] != '-') { + return false; + } + } else { + if (!is_hex_digit(s[i])) { + return false; + } + } + i += 1; + } + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 88-100: `pub fn from_env()`. The token read fails as +// MissingToken; the RAILWAY_TOKEN_AUTH override forces the mode +// (team | bearer | personal → team, project → project), and without an +// override the UUID heuristic guesses: a UUID-shaped token is a project +// token, anything else a team token. The token itself is the caller's +// plumbing - the port carries its presence and its shape answer. +// --------------------------------------------------------------------------- + +pub struct FromEnvOutcome { + built: bool, + auth_mode: str, +} + +pub fn Client_from_env(token_set: bool, token_is_uuid_shaped: bool, auth_override: str) -> FromEnvOutcome { + if (!token_set) { + return FromEnvOutcome { built: false, auth_mode: "" }; + } + if (auth_override == "team") { + return FromEnvOutcome { built: true, auth_mode: "team" }; + } + if (auth_override == "bearer") { + return FromEnvOutcome { built: true, auth_mode: "team" }; + } + if (auth_override == "personal") { + return FromEnvOutcome { built: true, auth_mode: "team" }; + } + if (auth_override == "project") { + return FromEnvOutcome { built: true, auth_mode: "project" }; + } + if (token_is_uuid_shaped) { + return FromEnvOutcome { built: true, auth_mode: "project" }; + } + return FromEnvOutcome { built: true, auth_mode: "team" }; +} + +// Original lines 102-104: `pub fn with_token(token)` — the team default. +pub fn Client_with_token(token_set: bool) -> FromEnvOutcome { + if (!token_set) { + return FromEnvOutcome { built: false, auth_mode: "" }; + } + return FromEnvOutcome { built: true, auth_mode: "team" }; +} + +// Original lines 106-119: `pub fn with_token_and_mode(token, auth)` — +// the explicit mode, endpoint fixed to the Railway GraphQL URL. +pub fn Client_with_token_and_mode(token_set: bool, auth_mode: str) -> FromEnvOutcome { + if (!token_set) { + return FromEnvOutcome { built: false, auth_mode: "" }; + } + return FromEnvOutcome { built: true, auth_mode: auth_mode }; +} + +// Original lines 121-124: `pub fn auth_mode(&self)` — the readback. +pub fn Client_auth_mode(client: FromEnvOutcome) -> str { + return client.auth_mode; +} + +// Original lines 126-131: `pub fn with_endpoint(self, endpoint)` — +// the endpoint override for integration tests. +pub fn Client_with_endpoint(client: FromEnvOutcome, endpoint: str) -> str { + return endpoint; +} + +// Original lines 133-137: `pub fn token_fingerprint(&self)` — delegates +// to the hash module's fingerprint; audit lines see it, never the +// token. Delegation is plumbing. +pub fn Client_token_fingerprint(token: str) -> ?str { + return undefined; // plumbing: delegates to hash::token_fingerprint (audit-safe, never the token) +} + +// --------------------------------------------------------------------------- +// Original lines 139-170: `pub async fn query(query, variables)`. The +// raw GraphQL POST: the team mode sends the Bearer header, the project +// mode the Project-Access-Token header; GraphQL errors are joined with +// "; " and answer an error of kind graphql; absent data answers an +// error of its own. The HTTP round-trip is plumbing; the header choice +// and the verdicts are the decisions. +// --------------------------------------------------------------------------- + +pub fn team_auth_header() -> str { + return "Authorization: Bearer"; +} + +pub fn project_auth_header() -> str { + return "Project-Access-Token"; +} + +pub struct QueryOutcome { + ok: bool, + error_kind: str, +} + +pub fn Client_query(auth_mode: str, errors_present: bool, data_present: bool) -> QueryOutcome { + if (errors_present) { + return QueryOutcome { ok: false, error_kind: "graphql" }; + } + if (!data_present) { + return QueryOutcome { ok: false, error_kind: "empty data" }; + } + return QueryOutcome { ok: true, error_kind: "" }; +} + +pub fn query_header_for_mode(auth_mode: str) -> str { + if (auth_mode == "project") { + return project_auth_header(); + } + return team_auth_header(); +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original. No test calls the fingerprint transport. +// --------------------------------------------------------------------------- + +test uuid_like_accepts_canonical_shape { + assert(is_uuid_like("e4fe33bb-3b09-4842-9782-7d2dea1abc9b")); +} + +test uuid_like_rejects_wrong_shapes { + assert(!is_uuid_like("e4fe33bb-3b09-4842-9782-7d2dea1abc9")); + assert(!is_uuid_like("e4fe33bbx3b09-4842-9782-7d2dea1abc9b")); + assert(!is_uuid_like("e4fe33bb-3b09-4842-9782-7d2dea1abc9g")); + assert(is_hex_digit('0')); + assert(is_hex_digit('f')); + assert(is_hex_digit('A')); + assert(!is_hex_digit('g')); + assert(!is_hex_digit('-')); +} + +test from_env_missing_token_fails { + const out = Client_from_env(false, false, ""); + assert(!out.built); +} + +test from_env_overrides_force_the_mode { + assert(Client_from_env(true, false, "team").auth_mode == "team"); + assert(Client_from_env(true, false, "bearer").auth_mode == "team"); + assert(Client_from_env(true, false, "personal").auth_mode == "team"); + assert(Client_from_env(true, true, "project").auth_mode == "project"); +} + +test from_env_heuristic_guesses_from_the_shape { + assert(Client_from_env(true, true, "").auth_mode == "project"); + assert(Client_from_env(true, false, "").auth_mode == "team"); +} + +test with_token_defaults_to_team_and_readback { + const out = Client_with_token(true); + assert(out.built); + assert(Client_auth_mode(out) == "team"); + const explicit = Client_with_token_and_mode(true, "project"); + assert(Client_auth_mode(explicit) == "project"); +} + +test endpoint_override { + const out = Client_with_token(true); + assert(Client_with_endpoint(out, "http://localhost:9333/graphql") == "http://localhost:9333/graphql"); +} + +test endpoint_constant_dash_positions_and_headers { + assert(graphql_endpoint() == "https://backboard.railway.com/graphql/v2"); + assert(is_dash_position(8)); + assert(is_dash_position(13)); + assert(is_dash_position(18)); + assert(is_dash_position(23)); + assert(!is_dash_position(0)); + assert(!is_dash_position(12)); + assert(team_auth_header() == "Authorization: Bearer"); + assert(project_auth_header() == "Project-Access-Token"); +} + +test query_headers_and_verdicts { + assert(query_header_for_mode("team") == "Authorization: Bearer"); + assert(query_header_for_mode("project") == "Project-Access-Token"); + const ok = Client_query("team", false, true); + assert(ok.ok); + const errs = Client_query("team", true, true); + assert(!errs.ok); + assert(errs.error_kind == "graphql"); + const empty = Client_query("team", false, false); + assert(!empty.ok); + assert(empty.error_kind == "empty data"); +} From c4bcca67427dc2788b9e0f0711385ef77c469764 Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Sat, 17 Oct 2026 03:37:16 -0300 Subject: [PATCH 2/2] spec(port): rename the hex-digit helper in the transport port (Closes #9795) dupe_scan found is_hex_digit already carried by two other specs (compiler/lexer.t27, igla/race/backend.t27) - the name is not mandated by the issue. Renamed to uuid_hex_digit so every name this port introduces is unique across the 2040 specs. All oracles re-verified after the rename. Dmitrii Fedorov ([@dmitrii-f-t27](https://github.com/dmitrii-f-t27)) --- .../crates/trios-railway-core/src/transport.t27 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 b/specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 index eafcfdeec4..1761c154ce 100644 --- a/specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 +++ b/specs/port/trios-railway/crates/trios-railway-core/src/transport.t27 @@ -35,7 +35,7 @@ pub fn graphql_endpoint() -> str { // 8-4-4-4-12 hex UUID shape, byte by byte. // --------------------------------------------------------------------------- -pub fn is_hex_digit(b: u8) -> bool { +pub fn uuid_hex_digit(b: u8) -> bool { if (b >= '0' and b <= '9') { return true; } @@ -67,7 +67,7 @@ pub fn is_uuid_like(s: []const u8) -> bool { return false; } } else { - if (!is_hex_digit(s[i])) { + if (!uuid_hex_digit(s[i])) { return false; } } @@ -199,11 +199,11 @@ test uuid_like_rejects_wrong_shapes { assert(!is_uuid_like("e4fe33bb-3b09-4842-9782-7d2dea1abc9")); assert(!is_uuid_like("e4fe33bbx3b09-4842-9782-7d2dea1abc9b")); assert(!is_uuid_like("e4fe33bb-3b09-4842-9782-7d2dea1abc9g")); - assert(is_hex_digit('0')); - assert(is_hex_digit('f')); - assert(is_hex_digit('A')); - assert(!is_hex_digit('g')); - assert(!is_hex_digit('-')); + assert(uuid_hex_digit('0')); + assert(uuid_hex_digit('f')); + assert(uuid_hex_digit('A')); + assert(!uuid_hex_digit('g')); + assert(!uuid_hex_digit('-')); } test from_env_missing_token_fails {