From f0217c9bcc39df574e9fd197ddd0e195e4301f34 Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Sat, 17 Oct 2026 12:40:08 -0300 Subject: [PATCH] spec(port): port specs/port/trios/crates/trios-trainer/src/bin/trios-train.t27 (Closes #7770) Port gHashTag/trios:crates/trios-trainer/src/bin/trios-train.rs (at 8b229e9e9489ee, 95 lines) as specs/port/trios/crates/ trios-trainer/src/bin/trios-train.t27 - Roadmap Stage 6, goal #4548, target t27c gen-rust. The trios-train CLI's decisions are ported as pure functions and tested (6 tests, 6 pass, 0 vacuous, 34 runtime asserts, 100% function coverage): the four-flag CLI shape (--config required, --seed and --steps optional overrides, --dry-run), the override rule (a present CLI value replaces the config's value, an absent flag leaves it), the INV-8 LR gate with its phi band [0.001, 0.01] inclusive on both ends, the dry-run shape (six printed fields, the run exiting BEFORE any training), the three-band Gate-2 verdict on the best BPB (below 1.50 the victory candidate, below 1.85 the above-target evidence, else the below-target evidence), and the flow order (the LR gate before the dry-run branch before training). The clap parsing, the TOML load and the training run are the callers' plumbing. Verified: parse OK, spec-status IMPLEMENTED, typecheck 0 errors, coverage 14/14, test-report 6/6 pass, 0 BLOCKED, gen 150 lines with 0 'not yet implemented', boundary exactly this one file, dupe_scan: all 13 introduced names unique across 2046 specs (checked before the commit), bare body scan adds no duplicate group (722/217 unchanged). Dmitrii Fedorov ([@dmitrii-f-t27](https://github.com/dmitrii-f-t27)) --- .../trios-trainer/src/bin/trios-train.t27 | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 specs/port/trios/crates/trios-trainer/src/bin/trios-train.t27 diff --git a/specs/port/trios/crates/trios-trainer/src/bin/trios-train.t27 b/specs/port/trios/crates/trios-trainer/src/bin/trios-train.t27 new file mode 100644 index 0000000000..3d8bbc6211 --- /dev/null +++ b/specs/port/trios/crates/trios-trainer/src/bin/trios-train.t27 @@ -0,0 +1,192 @@ +// trios-train — the CLI entry point for IGLA training. +// Port of gHashTag/trios:crates/trios-trainer/src/bin/trios-train.rs +// (at 8b229e9e9489ee, 95 lines) — Roadmap Stage 6, goal #4548, target +// t27c gen-rust. +// +// The original is the training CLI: three flags --config (required, +// TOML), --seed and --steps (optional overrides), and --dry-run. The +// flow: the config loads from the given path; the CLI seed and steps +// OVERRIDE the config's training values when present; the LR must pass +// the INV-8 phi-band validation ([0.001, 0.01] by the error message) +// or the run bails; a dry run prints the validated shape (seed, steps, +// LR, d_model, n_layers, checkpoint interval) and exits BEFORE any +// training; otherwise training runs and the Gate-2 verdict answers by +// the best BPB: below 1.50 the VICTORY CANDIDATE, below 1.85 the +// above-target evidence, else the below-target evidence. +// +// The clap parsing, the TOML load and the training run are the +// callers' plumbing; the decisions — the three-flag shape with its +// override rule, the INV-8 LR gate with its band, the dry-run +// early-exit shape, and the three-band Gate-2 verdict — are ported +// here as pure functions and tested (issue #7770, "Port the decision, +// not the plumbing"). + +module port::trios_trainer_bin; + +// --------------------------------------------------------------------------- +// Original lines 20-36: `struct Args` — three flags: --config required, +// --seed and --steps optional overrides, --dry-run a switch. +// --------------------------------------------------------------------------- + +pub fn cli_flag_count() -> usize { + return 4; +} + +pub fn cli_flag(i: usize) -> str { + if (i == 0) { return "--config"; } + if (i == 1) { return "--seed"; } + if (i == 2) { return "--steps"; } + if (i == 3) { return "--dry-run"; } + return ""; +} + +// The override rule (lines 48-56): a present CLI value replaces the +// config's value; an absent flag leaves the config untouched. +pub fn apply_override(flag_present: bool, flag_value: u64, config_value: u64) -> u64 { + if (flag_present) { + return flag_value; + } + return config_value; +} + +// --------------------------------------------------------------------------- +// Original lines 59-61: the INV-8 LR gate — the LR must sit in the +// phi band [0.001, 0.01] (the error message names the bounds). +// --------------------------------------------------------------------------- + +pub fn inv8_lr_band_low() -> f64 { + return 0.001; +} + +pub fn inv8_lr_band_high() -> f64 { + return 0.01; +} + +pub fn validate_lr_phi_band(lr: f64) -> bool { + return lr >= inv8_lr_band_low() and lr <= inv8_lr_band_high(); +} + +pub fn inv8_error_band() -> str { + return "must be in [0.001, 0.01]"; +} + +// --------------------------------------------------------------------------- +// Original lines 65-74: the dry-run shape — the validated config +// prints (seed, steps, LR, d_model, n_layers, checkpoint interval) and +// the run EXITS before any training. +// --------------------------------------------------------------------------- + +pub fn dry_run_field_count() -> usize { + return 6; +} + +pub fn dry_run_field(i: usize) -> str { + if (i == 0) { return "seed"; } + if (i == 1) { return "steps"; } + if (i == 2) { return "lr"; } + if (i == 3) { return "d_model"; } + if (i == 4) { return "n_layers"; } + if (i == 5) { return "checkpoint_interval"; } + return ""; +} + +pub fn dry_run_exits_before_training() -> bool { + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 87-93: the Gate-2 verdict — a three-band decision on +// the BEST BPB: below 1.50 the victory candidate, below 1.85 the +// above-target evidence, else the below-target evidence. +// --------------------------------------------------------------------------- + +pub fn gate2_victory_threshold() -> f64 { + return 1.50; +} + +pub fn gate2_above_target_threshold() -> f64 { + return 1.85; +} + +pub fn gate2_verdict(best_bpb: f64) -> str { + if (best_bpb < gate2_victory_threshold()) { + return "victory candidate"; + } + if (best_bpb < gate2_above_target_threshold()) { + return "above target evidence"; + } + return "below target evidence"; +} + +// --------------------------------------------------------------------------- +// The flow of main (lines 38-95): overrides first, then the INV-8 +// gate, then the dry-run branch, then training with the verdict. The +// port composes the same order over the caller's answers. +// --------------------------------------------------------------------------- + +pub fn main(lr_valid: bool, dry_run: bool) -> str { + if (!lr_valid) { + return "inv8 violation"; + } + if (dry_run) { + return "dry run"; + } + return "train"; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original. +// --------------------------------------------------------------------------- + +test cli_flag_table { + assert(cli_flag_count() == 4); + assert(cli_flag(0) == "--config"); + assert(cli_flag(1) == "--seed"); + assert(cli_flag(2) == "--steps"); + assert(cli_flag(3) == "--dry-run"); +} + +test override_rule { + assert(apply_override(true, 43, 1) == 43); + assert(apply_override(true, 500, 1000) == 500); + assert(apply_override(false, 43, 7) == 7); + assert(apply_override(false, 500, 1000) == 1000); +} + +test inv8_lr_band { + assert(validate_lr_phi_band(0.001)); + assert(validate_lr_phi_band(0.003)); + assert(validate_lr_phi_band(0.01)); + assert(!validate_lr_phi_band(0.0009)); + assert(!validate_lr_phi_band(0.011)); + assert(!validate_lr_phi_band(0.05)); + assert(inv8_lr_band_low() == 0.001); + assert(inv8_lr_band_high() == 0.01); + assert(inv8_error_band() == "must be in [0.001, 0.01]"); +} + +test dry_run_shape { + assert(dry_run_field_count() == 6); + assert(dry_run_field(0) == "seed"); + assert(dry_run_field(2) == "lr"); + assert(dry_run_field(5) == "checkpoint_interval"); + assert(dry_run_exits_before_training()); +} + +test gate2_verdict_bands { + assert(gate2_victory_threshold() == 1.50); + assert(gate2_above_target_threshold() == 1.85); + assert(gate2_verdict(1.49) == "victory candidate"); + assert(gate2_verdict(1.50) != "victory candidate"); + assert(gate2_verdict(1.84) == "above target evidence"); + assert(gate2_verdict(1.85) == "below target evidence"); + assert(gate2_verdict(2.716) == "below target evidence"); +} + +test main_flow_order { + assert(main(false, false) == "inv8 violation"); + assert(main(false, true) == "inv8 violation"); + assert(main(true, true) == "dry run"); + assert(main(true, false) == "train"); +}