diff --git a/specs/port/trios/crates/trios-trainer/src/ledger.t27 b/specs/port/trios/crates/trios-trainer/src/ledger.t27 new file mode 100644 index 0000000000..1de46e9836 --- /dev/null +++ b/specs/port/trios/crates/trios-trainer/src/ledger.t27 @@ -0,0 +1,188 @@ +// Ledger — triplet-validated row emission with embargo block. +// Port of gHashTag/trios:crates/trios-trainer/src/ledger.rs +// (at 8b229e9489ee, 165+ lines) — Roadmap Stage 6, goal #4548, target +// t27c gen-rust. +// +// The original emits ledger rows under a triplet contract — every row +// MUST carry BPB, step, seed, sha, the jsonl row and the gate status — +// and guards the emission with an embargo block: a SHA on the blocked +// list answers EmbargoBlocked before anything is written; a NaN BPB or +// an empty jsonl row answers InvalidTriplet; the serialize and write +// steps answer their own errors. The default embargo list is empty +// (the comment names it the place for failed-validation commits), and +// Default delegates to new. get_commit_sha shells out to +// `git rev-parse HEAD`, trimming the answer. +// +// The filesystem append and the git subprocess are the callers' +// plumbing; the decisions — the triplet's six fields with the +// row-carrying validation, the embargo gate FIRST, the NaN/empty +// InvalidTriplet rule, the empty default list, the delegation, and +// the git command shape — are ported here as pure functions and +// tested, including the original's own embargo and triplet tests +// (issue #7324, "Port the decision, not the plumbing"). + +module port::trios_trainer_ledger; + +// --------------------------------------------------------------------------- +// Original lines 13-20: `pub struct Triplet` — the six-field contract +// every ledger row MUST satisfy. +// --------------------------------------------------------------------------- + +pub fn triplet_field_count() -> usize { + return 6; +} + +pub fn triplet_field(i: usize) -> str { + if (i == 0) { return "bpb"; } + if (i == 1) { return "step"; } + if (i == 2) { return "seed"; } + if (i == 3) { return "sha"; } + if (i == 4) { return "jsonl_row"; } + if (i == 5) { return "gate_status"; } + return ""; +} + +// Original lines 24-32: the full row carries the agent and the +// timestamp on top of the triplet fields. +pub fn ledger_row_field_count() -> usize { + return 8; +} + +// --------------------------------------------------------------------------- +// Original lines 43-56: `EmbargoBlock::new` — the default blocked list +// is EMPTY (the comment names it the place for failed-validation +// commits); `is_embargoed` is the membership gate. +// --------------------------------------------------------------------------- + +pub fn EmbargoBlock_new() -> usize { + return 0; +} + +pub fn EmbargoBlock_is_embargoed(blocked_count: usize, sha_index: usize) -> bool { + // The membership answer: the caller hands the blocked list's size + // and the index of the sha within it; a not-found index means + // not embargoed. + if (sha_index < blocked_count) { + return true; + } + return false; +} + +// Original lines 58-62: `impl Default` — the delegation to new. +pub fn EmbargoBlock_default() -> usize { + return EmbargoBlock_new(); +} + +// --------------------------------------------------------------------------- +// Original lines 67-110: `emit_row` — the emission order: the embargo +// gate FIRST (an embargoed sha answers EmbargoBlocked before anything +// is written), then the serialize, then the append, then the triplet +// validation with the NaN/empty rule. The write itself is the +// caller's plumbing; the order and the gates are the decisions. +// --------------------------------------------------------------------------- + +pub fn emit_row_order() -> str { + return "embargo,serialize,append,validate"; +} + +pub fn emit_row(sha_embargoed: bool, bpb_is_nan: bool, jsonl_empty: bool) -> str { + if (sha_embargoed) { + return "embargo blocked"; + } + if (bpb_is_nan or jsonl_empty) { + return "invalid triplet"; + } + return "ok"; +} + +// The InvalidTriplet rule of lines 105-107: a NaN bpb OR an empty +// jsonl row is the violation. +pub fn triplet_is_invalid(bpb_is_nan: bool, jsonl_empty: bool) -> bool { + return bpb_is_nan or jsonl_empty; +} + +pub fn emit_error_kind(i: usize) -> str { + if (i == 0) { return "embargo blocked"; } + if (i == 1) { return "serialize error"; } + if (i == 2) { return "write error"; } + if (i == 3) { return "invalid triplet"; } + return ""; +} + +pub fn emit_error_count() -> usize { + return 4; +} + +// The append contract: the ledger opens create+append — rows are only +// ever appended, never truncated. +pub fn ledger_opens_append_only() -> bool { + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 128-140: `get_commit_sha` — `git rev-parse HEAD`, +// the answer trimmed; a failing command answers the error. +// --------------------------------------------------------------------------- + +pub fn get_commit_sha_command() -> str { + return "git rev-parse HEAD"; +} + +pub fn get_commit_sha(command_succeeded: bool, sha: str) -> str { + if (!command_succeeded) { + return "git rev-parse failed"; + } + return sha; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original, including its own embargo and triplet tests. +// --------------------------------------------------------------------------- + +test triplet_contract { + assert(triplet_field_count() == 6); + assert(triplet_field(0) == "bpb"); + assert(triplet_field(3) == "sha"); + assert(triplet_field(4) == "jsonl_row"); + assert(triplet_field(5) == "gate_status"); + assert(ledger_row_field_count() == 8); +} + +test embargo_block_default_and_delegation { + assert(EmbargoBlock_new() == 0); + assert(EmbargoBlock_default() == EmbargoBlock_new()); +} + +test embargo_membership_gate { + // The original's own test: "deadbeef" blocked, "goodcommit" not. + assert(EmbargoBlock_is_embargoed(1, 0)); + assert(!EmbargoBlock_is_embargoed(1, 1)); + assert(!EmbargoBlock_is_embargoed(0, 0)); +} + +test emission_order_and_gates { + assert(emit_row_order() == "embargo,serialize,append,validate"); + assert(emit_row(true, false, false) == "embargo blocked"); + assert(emit_row(false, true, false) == "invalid triplet"); + assert(emit_row(false, false, true) == "invalid triplet"); + assert(emit_row(false, false, false) == "ok"); + assert(triplet_is_invalid(true, false)); + assert(triplet_is_invalid(false, true)); + assert(!triplet_is_invalid(false, false)); +} + +test error_catalogue_and_append_contract { + assert(emit_error_count() == 4); + assert(emit_error_kind(0) == "embargo blocked"); + assert(emit_error_kind(1) == "serialize error"); + assert(emit_error_kind(2) == "write error"); + assert(emit_error_kind(3) == "invalid triplet"); + assert(ledger_opens_append_only()); +} + +test git_sha_command_shape { + assert(get_commit_sha_command() == "git rev-parse HEAD"); + assert(get_commit_sha(true, "abc1234") == "abc1234"); + assert(get_commit_sha(false, "") == "git rev-parse failed"); +}