From 34f72ee0577c632349eb41f4e34aa93d2f843679 Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Fri, 16 Oct 2026 23:23:45 -0300 Subject: [PATCH] spec(port): port specs/port/trios-railway/crates/trios-railway-audit/src/event.t27 (Closes #9790) Port gHashTag/trios-railway:crates/trios-railway-audit/src/event.rs (at 8b229e9489ee, 182 lines) as specs/port/trios-railway/crates/ trios-railway-audit/src/event.t27 - Roadmap Stage 6, goal #4548, target t27c gen-rust. The AU-02 audit writer's decisions are ported as pure functions and tested (6 tests, 6 pass, 0 vacuous, 20 runtime asserts), including the original's own four artifact tests: the Gate-2 contribution rule (bpb strictly below target_bpb), the image_sha kept-length rule (at most eight characters, a shorter sha kept whole), the insert shape (four columns, returning the id), and the Neon env fallback order (RAILWAY_POSTGRES_URL preferred, legacy NEON_DATABASE_URL accepted). The clock is a caller parameter and the sha slicing is the caller's string plumbing guided by the kept-length decision. The audit_event transport keeps its name with an undefined; body no test calls. Verified: parse OK, spec-status IMPLEMENTED, typecheck 0 errors, coverage 8/9 (only the transport untested, by design), test-report 6/6 pass, 0 BLOCKED, gen 100 lines with 0 'not yet implemented', boundary exactly this one file, dupe_scan: all 9 names unique across 2040 specs, bare body scan adds no duplicate group (752/221 unchanged). Dmitrii Fedorov ([@dmitrii-f-t27](https://github.com/dmitrii-f-t27)) --- .../crates/trios-railway-audit/src/event.t27 | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 specs/port/trios-railway/crates/trios-railway-audit/src/event.t27 diff --git a/specs/port/trios-railway/crates/trios-railway-audit/src/event.t27 b/specs/port/trios-railway/crates/trios-railway-audit/src/event.t27 new file mode 100644 index 0000000000..49e9546e60 --- /dev/null +++ b/specs/port/trios-railway/crates/trios-railway-audit/src/event.t27 @@ -0,0 +1,172 @@ +// AU-02 — the Neon audit_event writer and its JSON artifact. +// Port of gHashTag/trios-railway:crates/trios-railway-audit/src/event.rs +// (at 8b229e9489ee, 182 lines) — Roadmap Stage 6, goal #4548, target t27c +// gen-rust. +// +// The original writes one audit row into the Neon igla_race_trials table +// and returns the inserted row id, never silently swallowing errors (R5); +// the JSON artifact it emits to stdout lets the hourly watchdog parse the +// result without re-querying Neon. The tokio-postgres connect, the INSERT +// round-trip and the JSON serialization are plumbing; the decisions the +// file makes — the Gate-2 contribution rule (this row contributes exactly +// when bpb < target_bpb, strictly), the image_sha kept-length rule (at +// most the first eight characters, a shorter sha kept whole), the insert +// shape (four columns, returning the id), and the Neon env fallback order +// (RAILWAY_POSTGRES_URL preferred, the legacy NEON_DATABASE_URL accepted) +// — are ported here as pure functions and tested, including the +// original's own four artifact tests. The clock is a parameter the caller +// passes (the original stamps Utc::now inside; the port carries the ts it +// is given), and the sha truncation is carried as the kept-length +// decision — the slicing itself is the caller's string plumbing. The +// audit_event transport keeps its name with an `undefined;` body that no +// test calls (issue #9790, "Port the decision, not the plumbing"). + +module port::trios_railway_audit_event; + +// --------------------------------------------------------------------------- +// Original lines 49-67: `pub struct AuditArtifact` — the stdout artifact +// for the hourly watchdog. Only the facts the decisions read. +// --------------------------------------------------------------------------- + +pub struct AuditArtifact { + ts: i64, + seed: i32, + bpb: f64, + step: i32, + image_sha: str, + row_id: i64, + target_bpb: f64, + gate2_contribution: bool, +} + +// --------------------------------------------------------------------------- +// Original lines 69-91: `impl AuditArtifact { pub fn new(...) -> Self }`. +// The artifact carries the writer's output; the clock is the caller's +// (Utc::now inside the original becomes the ts parameter), and the +// image_sha is carried as passed — the kept-length decision below is the +// truncation rule the caller applies. The Gate-2 contribution is the +// one computed decision: strictly below the target. +// --------------------------------------------------------------------------- + +pub fn AuditArtifact_new( + seed: i32, + bpb: f64, + step: i32, + image_sha: str, + row_id: i64, + target_bpb: f64, + ts: i64, +) -> AuditArtifact { + return AuditArtifact { + ts: ts, + seed: seed, + bpb: bpb, + step: step, + image_sha: image_sha, + row_id: row_id, + target_bpb: target_bpb, + gate2_contribution: gate2_contributes(bpb, target_bpb), + }; +} + +// Original line 88: `gate2_contribution: bpb < target_bpb` — this row +// contributes to Gate-2 PASS exactly when the recorded BPB is strictly +// below the target. +pub fn gate2_contributes(bpb: f64, target_bpb: f64) -> bool { + if (bpb < target_bpb) { + return true; + } + return false; +} + +// Original line 85: `image_sha[..image_sha.len().min(8)]` — at most the +// first eight characters; a shorter sha kept whole. +pub fn image_sha_kept_len(sha_len: usize) -> usize { + if (sha_len > 8) { + return 8; + } + return sha_len; +} + +// --------------------------------------------------------------------------- +// Original lines 109-140: `pub async fn audit_event(...) -> Result`. +// Connects to Neon, inserts one audit row and returns the id. Pure +// transport — it never silently swallows errors (R5); the insert shape +// and the env fallback order are its decisions, ported below. +// --------------------------------------------------------------------------- + +pub fn audit_event(seed: i32, bpb: f64, step: i32, image_sha: str, neon_url: str) -> ?i64 { + return undefined; // plumbing: Neon connect + INSERT ... RETURNING id (lines 109-140) +} + +// Original lines 129-131: the insert writes four columns — seed, bpb, +// step, image_sha — and returns the id. +pub fn insert_column_count() -> usize { + return 4; +} + +pub fn insert_returns_id() -> bool { + return true; +} + +// Original lines 101-103: the connection string comes from +// RAILWAY_POSTGRES_URL, the legacy NEON_DATABASE_URL accepted as the +// fallback (L-NEON-RENAME). +pub fn preferred_neon_env() -> str { + return "RAILWAY_POSTGRES_URL"; +} + +pub fn legacy_neon_env() -> str { + return "NEON_DATABASE_URL"; +} + +pub fn neon_env_var_count() -> usize { + return 2; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original's own tests (lines 146-181). No test calls the audit_event +// transport. +// --------------------------------------------------------------------------- + +test artifact_gate2_contribution_true_below_target { + const a = AuditArtifact_new(100, 1.83, 2000, "abc123de", 42, 1.85, 1700000000); + assert(a.gate2_contribution); + assert(a.seed == 100); + assert(a.row_id == 42); +} + +test artifact_gate2_contribution_false_at_or_above_target { + const a = AuditArtifact_new(101, 2.10, 1500, "xyz", 43, 1.85, 1700000000); + assert(!a.gate2_contribution); + assert(!gate2_contributes(1.85, 1.85)); +} + +test image_sha_truncated_to_eight { + assert(image_sha_kept_len(16) == 8); + assert(image_sha_kept_len(9) == 8); + assert(image_sha_kept_len(8) == 8); +} + +test image_sha_short_kept_whole { + assert(image_sha_kept_len(3) == 3); + assert(image_sha_kept_len(1) == 1); +} + +test artifact_carries_the_writer_output { + const a = AuditArtifact_new(100, 1.83, 2000, "deadbeef", 7, 1.85, 1700000000); + assert(a.ts == 1700000000); + assert(a.bpb == 1.83); + assert(a.step == 2000); + assert(a.target_bpb == 1.85); + assert(a.image_sha == "deadbeef"); +} + +test insert_shape_and_env_fallback { + assert(insert_column_count() == 4); + assert(insert_returns_id()); + assert(preferred_neon_env() == "RAILWAY_POSTGRES_URL"); + assert(legacy_neon_env() == "NEON_DATABASE_URL"); + assert(neon_env_var_count() == 2); +}