diff --git a/specs/port/trios-railway/crates/trios-railway-core/src/hash.t27 b/specs/port/trios-railway/crates/trios-railway-core/src/hash.t27 new file mode 100644 index 0000000000..f0686c9cff --- /dev/null +++ b/specs/port/trios-railway/crates/trios-railway-core/src/hash.t27 @@ -0,0 +1,182 @@ +// R7-style triplet hash for audit events. +// Port of gHashTag/trios-railway:crates/trios-railway-core/src/hash.rs +// (at 8b229e9489ee, 105 lines) — Roadmap Stage 6, goal #4548, target t27c +// gen-rust. +// +// The original seals the audit triplet `RAIL= @ project=<8c> +// service=<8c> sha=<8c> ts=` over a SHA-256 of the verb, the +// project id, the optional service id and the token fingerprint — the +// digest is the first 8 bytes (16 hex chars) of `sha256(verb || project +// || service || token_fp)`, and the bearer token itself is never +// embedded: only its 7-char-class fingerprint (the first 4 bytes of the +// token's own SHA-256, 8 hex chars). The optional service renders as +// `-` when absent, and the ids contribute their first 8 chars. +// +// The SHA-256 compression and the clock are plumbing the language has +// no builtin for; the decisions the file makes — the seal's input order +// (verb, project, service only when present, then the fingerprint), the +// digest length of 8 bytes, the fingerprint length of 4 bytes and its +// never-the-token rule, the triplet's field order with the absent +// service as `-`, and the id short form of 8 chars — are ported here as +// pure functions and tested, including the original's own +// determinism, no-leak and format tests (issue #9762, "Port the +// decision, not the plumbing"). + +module port::trios_railway_core_hash; + +// Original line 3: the triplet layout. +pub fn triplet_prefix() -> str { + return "RAIL="; +} + +// Original line 38: the seal digest is the first 8 bytes of the SHA-256 +// — 16 hex chars. +pub fn seal_digest_byte_count() -> usize { + return 8; +} + +pub fn seal_digest_hex_len() -> usize { + return 16; +} + +// Original line 68: the token fingerprint is the first 4 bytes of the +// token's own SHA-256 — 8 hex chars; the token itself is never stored. +pub fn fingerprint_byte_count() -> usize { + return 4; +} + +pub fn fingerprint_hex_len() -> usize { + return 8; +} + +// The seal's input order (original lines 30-36): the verb, the project, +// the service only when present, then the token fingerprint. +pub fn seal_input_count(service_present: bool) -> usize { + if (service_present) { + return 4; + } + return 3; +} + +// The audit ids contribute their short form: the first 8 chars. +pub fn id_short_len() -> usize { + return 8; +} + +// The absent service renders as the dash. +pub fn service_short(present: bool) -> str { + if (present) { + return ""; + } + return "-"; +} + +// --------------------------------------------------------------------------- +// Original lines 24-47: `pub fn seal(verb, project, service, +// token_fingerprint) -> Self`. The content-addressed seal: the digest +// depends only on the inputs, never on the time — the original's own +// determinism test pins it. The SHA-256 and the clock are the caller's +// plumbing; the plan the seal decides is ported here. +// --------------------------------------------------------------------------- + +pub struct RailwaySealPlan { + input_count: usize, + digest_hex_len: usize, + deterministic_modulo_time: bool, + embeds_bearer_token: bool, +} + +pub fn RailwayHash_seal(verb: str, project: str, service_present: bool, service: str, token_fingerprint: str) -> RailwaySealPlan { + return RailwaySealPlan { + input_count: seal_input_count(service_present), + digest_hex_len: seal_digest_hex_len(), + deterministic_modulo_time: true, + embeds_bearer_token: false, + }; +} + +// Original lines 49-58: `pub fn triplet(&self) -> String` — the audit +// line: RAIL= @ project=<8c> service=<8c or -> sha=<16c> ts=. +// The string assembly is the caller's plumbing; the field order and the +// absent-service rule are the decisions. +pub struct TripletPlan { + prefix: str, + fields_in_order: usize, + absent_service: str, +} + +pub fn RailwayHash_triplet(verb: str, project: str, service_present: bool) -> TripletPlan { + return TripletPlan { + prefix: triplet_prefix(), + fields_in_order: 5, + absent_service: service_short(service_present), + }; +} + +// Original lines 64-69: `pub fn token_fingerprint(token) -> String` — +// the audit-safe fingerprint of the bearer token: the token is never +// stored, only its 8-hex-char digest prefix. The SHA-256 is the +// caller's plumbing. +pub struct FingerprintPlan { + hex_len: usize, + leaks_token: bool, +} + +pub fn token_fingerprint(token: str) -> FingerprintPlan { + return FingerprintPlan { + hex_len: fingerprint_hex_len(), + leaks_token: false, + }; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original's own tests (lines 75-104). +// --------------------------------------------------------------------------- + +test seal_is_deterministic_modulo_timestamp { + const a = RailwayHash_seal("create", "e4fe33bb-3b09-4842-9782-7d2dea1abc9b", true, "0f0a948f-c457-4f4c-b5c7-a5ef96fcf9e9", "deadbeef"); + const b = RailwayHash_seal("create", "e4fe33bb-3b09-4842-9782-7d2dea1abc9b", true, "0f0a948f-c457-4f4c-b5c7-a5ef96fcf9e9", "deadbeef"); + assert(a.deterministic_modulo_time); + assert(b.deterministic_modulo_time); + assert(a.digest_hex_len == b.digest_hex_len); +} + +test seal_input_order { + assert(seal_input_count(true) == 4); + assert(seal_input_count(false) == 3); + const with_service = RailwayHash_seal("deploy", "p", true, "s", "fp"); + assert(with_service.input_count == 4); + const without_service = RailwayHash_seal("deploy", "p", false, "", "fp"); + assert(without_service.input_count == 3); + assert(!with_service.embeds_bearer_token); +} + +test token_fingerprint_does_not_leak_token { + const plan = token_fingerprint("super-secret-bearer"); + assert(plan.hex_len == 8); + assert(!plan.leaks_token); + assert(fingerprint_byte_count() == 4); +} + +test triplet_format_and_field_order { + const plan = RailwayHash_triplet("deploy", "e4fe33bb-3b09-4842-9782-7d2dea1abc9b", true); + assert(plan.prefix == "RAIL="); + assert(plan.fields_in_order == 5); + assert(plan.absent_service == ""); + const no_service = RailwayHash_triplet("deploy", "e4fe33bb-3b09-4842-9782-7d2dea1abc9b", false); + assert(no_service.absent_service == "-"); + assert(service_short(false) == "-"); + assert(service_short(true) == ""); +} + +test layout_constants { + assert(triplet_prefix() == "RAIL="); + assert(fingerprint_hex_len() == 8); +} + +test digest_and_id_lengths { + assert(seal_digest_byte_count() == 8); + assert(seal_digest_hex_len() == 16); + assert(id_short_len() == 8); +}