From ca0d71272424262cda696d42d911177a9be6dd79 Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Sat, 17 Oct 2026 19:15:28 -0300 Subject: [PATCH] spec(port): port specs/port/trios-railway/bin/seed-agent/src/main.t27 (Closes #9800) Port gHashTag/trios-railway:bin/seed-agent/src/main.rs (at 5cc46e798dda, main from line 89) as specs/port/trios-railway/bin/seed-agent/src/ main.t27 - Roadmap Stage 6, goal #4548, target t27c gen-rust. The ADR-0081 pull-based trainer worker's boot decisions are ported as pure functions and tested (6 tests, 6 pass, 0 vacuous, 25 runtime asserts, 100% function coverage): the L-NEON-RENAME URL chain (a present, non-empty --neon-url flag bound to RAILWAY_POSTGRES_URL wins; an absent or empty one falls back to the legacy NEON_DATABASE_URL; neither is the boot error), the connect-retry patience (up to 30 attempts, 2s apart - the attempt that reaches the cap is the bail), the pull loop's wait shape (a trained or pruned experiment pulls the next at once, an idle queue waits the 30s poll interval, an iteration error backs off 30s), the shutdown release (the signal ends the loop cleanly), and the early-stop rule the CLI carries (at step 1000 a BPB strictly exceeding the 2.60 ceiling prunes - the ceiling itself does not prune). The CLI defaults (acc1, local-perplexity, seed-agent-auto, poll 30s, step 1000, ceiling 2.60, trainer external) are carried as consts. The TLS setup, the Postgres connection, the UUID, the worker registration and the iteration runs are the caller's plumbing. All four generators (gen, gen-rust, gen-c, gen-verilog) emit this spec before the push. Verified: parse OK, spec-status IMPLEMENTED, typecheck 0 errors, coverage 6/6, test-report 6/6 pass, 0 BLOCKED, gen 110 lines with 0 'not yet implemented', boundary exactly this one file, dupe_scan: all 6 function names unique, bare scan reports zero error lines. Dmitrii Fedorov ([@dmitrii-f-t27](https://github.com/dmitrii-f-t27)) --- .../trios-railway/bin/seed-agent/src/main.t27 | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 specs/port/trios-railway/bin/seed-agent/src/main.t27 diff --git a/specs/port/trios-railway/bin/seed-agent/src/main.t27 b/specs/port/trios-railway/bin/seed-agent/src/main.t27 new file mode 100644 index 0000000000..bb8c5b545d --- /dev/null +++ b/specs/port/trios-railway/bin/seed-agent/src/main.t27 @@ -0,0 +1,146 @@ +module port::trios_railway::seed_agent_main; + +// Port of gHashTag/trios-railway:bin/seed-agent/src/main.rs (at +// 5cc46e798dda, main from line 89) - t27 issue #9800, Roadmap Stage 6 +// (goal #4548), target t27c gen-rust. The ADR-0081 pull-based trainer +// worker's boot decisions as pure functions: the L-NEON-RENAME URL +// chain, the connect-retry patience (30 attempts, 2s apart), the pull +// loop's wait shape (trained and pruned pull next at once, idle waits +// the poll interval, an error backs off), the shutdown release, and the +// early-stop rule the CLI carries. The TLS setup, the Postgres +// connection, the UUID, the worker registration and the iteration runs +// are the caller's plumbing. + +// CLI defaults (original lines 43-84). +pub const SEED_AGENT_DEFAULT_ACC = "acc1"; +pub const SEED_AGENT_DEFAULT_SVC_ID = "local-perplexity"; +pub const SEED_AGENT_DEFAULT_SVC_NAME = "seed-agent-auto"; +pub const SEED_AGENT_POLL_IDLE_SECS: u64 = 30; +pub const SEED_AGENT_EARLY_STOP_STEP: i32 = 1000; +pub const SEED_AGENT_EARLY_STOP_BPB_CEILING: f64 = 2.60; +pub const SEED_AGENT_TRAINER_KIND = "external"; + +// Lines 152-166: the connect retry - up to 30 attempts, 2s apart; +// attempt 30 is the bail. +pub const SEED_AGENT_MAX_CONNECT_ATTEMPTS: u32 = 30; +pub const SEED_AGENT_CONNECT_RETRY_SLEEP_SECS: u64 = 2; + +// Line 206: an iteration error backs off before the next pull. +pub const SEED_AGENT_ERROR_BACKOFF_SECS: u64 = 30; + +// Exit codes (R5 - honest exits: the missing URL is the one boot +// failure that surfaces). +pub const SEED_AGENT_EXIT_OK: i32 = 0; +pub const SEED_AGENT_EXIT_FAIL: i32 = 1; + +// The iteration outcomes of the pull loop (lines 193-207). +pub const SEED_AGENT_OUTCOME_TRAINED: i32 = 0; +pub const SEED_AGENT_OUTCOME_PRUNED: i32 = 1; +pub const SEED_AGENT_OUTCOME_IDLE: i32 = 2; +pub const SEED_AGENT_OUTCOME_ERROR: i32 = 3; + +// Lines 110-115, the L-NEON-RENAME chain: a present, non-empty --neon-url +// flag (clap-bound to RAILWAY_POSTGRES_URL) wins; an absent or empty one +// falls back to the legacy NEON_DATABASE_URL; neither is the boot error. +pub fn seed_agent_url_source(neon_flag_present: bool, neon_flag: []const u8, legacy_env_present: bool) -> i32 { + if (neon_flag_present and neon_flag.len > 0) { + return 1; + } + if (legacy_env_present) { + return 2; + } + return 0; +} + +// Lines 151-169: the connect loop stays patient while attempts remain - +// the attempt that reaches the cap is the bail. +pub fn seed_agent_retry_still_patient(attempt: u32) -> bool { + return attempt < SEED_AGENT_MAX_CONNECT_ATTEMPTS; +} + +// Lines 193-207: the loop's wait - a finished experiment (trained or +// pruned) pulls the next at once, an idle queue waits the poll interval, +// an iteration error backs off. +pub fn seed_agent_iteration_wait_secs(outcome: i32) -> u64 { + if (outcome == SEED_AGENT_OUTCOME_IDLE) { + return SEED_AGENT_POLL_IDLE_SECS; + } + if (outcome == SEED_AGENT_OUTCOME_ERROR) { + return SEED_AGENT_ERROR_BACKOFF_SECS; + } + return 0; +} + +// Lines 188-192: the shutdown signal ends the loop - the claimed row is +// released and the worker stops; anything else keeps pulling. +pub fn seed_agent_loop_continues(shutdown_seen: bool) -> bool { + if (shutdown_seen) { + return false; + } + return true; +} + +// Lines 66-68, the early-stop rule the CLI carries: at the early-stop +// step, a BPB exceeding the ceiling prunes the experiment - strictly +// exceeding; the ceiling itself does not prune. +pub fn seed_agent_early_stop_prunes(bpb: f64, ceiling: f64) -> bool { + if (bpb > ceiling) { + return true; + } + return false; +} + +// Line 89: the boot gate - the worker resolves its Postgres URL first; +// with none from any source the boot fails before the retry loop or the +// pull loop can start. With a URL the worker registers and pulls until +// the shutdown signal, exiting cleanly. +pub fn main(neon_flag_present: bool, neon_flag: []const u8, legacy_env_present: bool) -> i32 { + const source = seed_agent_url_source(neon_flag_present, neon_flag, legacy_env_present); + if (source == 0) { + return SEED_AGENT_EXIT_FAIL; + } + return SEED_AGENT_EXIT_OK; +} + +test url_chain_l_neon_rename { + assert(seed_agent_url_source(true, "postgres://railway/db", false) == 1); + assert(seed_agent_url_source(true, "postgres://railway/db", true) == 1); + assert(seed_agent_url_source(true, "", true) == 2); + assert(seed_agent_url_source(false, "", true) == 2); + assert(seed_agent_url_source(false, "", false) == 0); + assert(seed_agent_url_source(true, "", false) == 0); +} + +test boot_gate_exits_on_missing_url { + assert(main(true, "postgres://railway/db", false) == SEED_AGENT_EXIT_OK); + assert(main(false, "", true) == SEED_AGENT_EXIT_OK); + assert(main(false, "", false) == SEED_AGENT_EXIT_FAIL); + assert(main(true, "", false) == SEED_AGENT_EXIT_FAIL); +} + +test connect_retry_patience { + assert(seed_agent_retry_still_patient(0) == true); + assert(seed_agent_retry_still_patient(1) == true); + assert(seed_agent_retry_still_patient(29) == true); + assert(seed_agent_retry_still_patient(30) == false); + assert(seed_agent_retry_still_patient(31) == false); +} + +test loop_wait_shape { + assert(seed_agent_iteration_wait_secs(SEED_AGENT_OUTCOME_TRAINED) == 0); + assert(seed_agent_iteration_wait_secs(SEED_AGENT_OUTCOME_PRUNED) == 0); + assert(seed_agent_iteration_wait_secs(SEED_AGENT_OUTCOME_IDLE) == 30); + assert(seed_agent_iteration_wait_secs(SEED_AGENT_OUTCOME_ERROR) == 30); +} + +test shutdown_ends_the_loop { + assert(seed_agent_loop_continues(false) == true); + assert(seed_agent_loop_continues(true) == false); +} + +test early_stop_ceiling_is_strict { + assert(seed_agent_early_stop_prunes(2.61, 2.60) == true); + assert(seed_agent_early_stop_prunes(2.60, 2.60) == false); + assert(seed_agent_early_stop_prunes(2.59, 2.60) == false); + assert(seed_agent_early_stop_prunes(3.0, 2.60) == true); +}