From 2827d511aad004895b613c63b039169a99f5c3ec Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 20:38:14 +0700 Subject: [PATCH 1/8] claw: ledger_union.t27 -- the t27b ledger union of a port batch as law (WIP, Refs #10398) Co-Authored-By: Claude Opus 5.5 --- specs/claw/ledger_union.t27 | 863 ++++++++++++++++++++++++++++++++++++ 1 file changed, 863 insertions(+) create mode 100644 specs/claw/ledger_union.t27 diff --git a/specs/claw/ledger_union.t27 b/specs/claw/ledger_union.t27 new file mode 100644 index 0000000000..b6358b8809 --- /dev/null +++ b/specs/claw/ledger_union.t27 @@ -0,0 +1,863 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/claw/ledger_union.t27 -- the one merge rule the claw-batch job needs, written once: how the +; t27b expectations ledger of a port pull request folds into a batch branch (#10398, lane 5 of +; the TRI CLAW epic #10393). +; -- +; Why it exists: every port pull request appends lines to docs/reports/t27b_expectations.json, so +; any two of them conflict in git even when they touch different specs. The fork batch #10098 was +; folded by hand with a shell loop and a Python union in a scratch folder; this file is that union +; as law, lowered to gen/c/claw/ledger_union.c and called from the claw-batch job in +; .github/workflows/gate-topology.yml. +; -- +; The rule, three sides keyed by the path field: base (the merge base), theirs (the pull request +; head), ours (the batch branch so far). +; - The result is ours plus what theirs added over base, sorted by path bytes -- the order the +; ledger is blessed in. +; - Ours wins where theirs kept the base entry: that is the batch's own progress (master moved on). +; - A key two sides hold with different values is refused, never picked: theirs adding a path ours +; also added with another verdict, and theirs changing or removing an entry of base that ours +; does not match. +; - The header and footer are ours; theirs changing either, to something ours does not hold, is +; refused. +; Fail closed: a side that is not the ledger's shape (one entry per line, two-space indent, a comma +; on every entry but the last, paths strictly ascending, no backslash in a path) is refused too, +; so the result is never a guess. A refusal writes one line naming the reason, the side and the +; path, and the job leaves that pull request out of the batch. +; -- +; Two smaller rules the job asks of the same module, so they are law and not grep: +; - marker_lines counts git conflict-marker lines; the batch is checked to hold none. +; - pr_scope decides whether a pull request's file list stays inside specs/port, its own seal and +; the ledger -- the only shape a batch folds. +; Every string is COUNTED (gen-c lowers str to const char*), the specs/claw/text.t27 idiom. +; ASCII only (L3). phi^2 + 1/phi^2 = 3 | TRINITY + +module ClawLedgerUnion; + +use claw::text; + +/// The ledger the union is for, as git names it. +pub const LEDGER : str = "docs/reports/t27b_expectations.json"; +pub const LEDGER_LEN : usize = 35; + +/// Every entry line starts with these bytes; the path runs from here to the next quote. +pub const ENTRY_HEAD : str = " {\"path\": \""; +pub const ENTRY_HEAD_LEN : usize = 12; + +/// The header ends with this key and a newline. +pub const ENTRIES_OPEN : str = "\"entries\": ["; +pub const ENTRIES_OPEN_LEN : usize = 12; + +/// The footer starts with these bytes; what follows them is whitespace only. +pub const FOOTER : str = " ]\n}"; +pub const FOOTER_LEN : usize = 4; + +/// Verdicts of ledger_union, in stats[0]. +pub const U_OK : u32 = 0; +pub const U_SHAPE : u32 = 1; +pub const U_UNSORTED : u32 = 2; +pub const U_BOTH_ADDED : u32 = 3; +pub const U_THEIRS_CHANGED : u32 = 4; +pub const U_FRAME : u32 = 5; +pub const U_OVERFLOW : u32 = 6; + +pub const REASONS : [7]str = [ + "ok", + "a side is not in the ledger's shape", + "a side's paths are not strictly ascending", + "both sides added this path with different values", + "theirs changed or removed an entry that ours does not match", + "theirs changed the header or footer and ours differs", + "the union does not fit the output buffer", +]; +pub const REASON_LENS : [7]usize = [2, 35, 41, 48, 59, 52, 40]; + +/// The sides, in the order ledger_union takes them; stats[3] names one (3 when none applies). +pub const SIDES : [3]str = ["base", "theirs", "ours"]; +pub const SIDE_LENS : [3]usize = [4, 6, 4]; + +/// stats[0] verdict, stats[1] entries theirs added, stats[2] entries written, stats[3] side. +pub const STATS_LEN : usize = 4; + +/// Verdicts of pr_scope, in stats[0]. +pub const SC_OK : u32 = 0; +pub const SC_OUTSIDE : u32 = 1; +pub const SC_SEAL : u32 = 2; +pub const SC_BAD : u32 = 3; +pub const SC_EMPTY : u32 = 4; + +pub const SCOPE_REASONS : [5]str = [ + "in scope", + "a file outside specs/port, its seal and the ledger", + "a seal whose spec is not a specs/port file of the same pull request", + "an unreadable scope record", + "no files", +]; +pub const SCOPE_REASON_LENS : [5]usize = [8, 50, 67, 26, 8]; + +pub const PORT_DIR : str = "specs/port/"; +pub const PORT_DIR_LEN : usize = 11; +pub const SEAL_DIR : str = ".trinity/seals/"; +pub const SEAL_DIR_LEN : usize = 15; + +/// Index of the newline that ends the line starting at s, or n. +fn lu_eol(x : []const u8, n : usize, s : usize) -> usize { + var i : usize = s; + while (i < n and x[i] != '\n') { i += 1; } + return i; +} + +/// Do the bytes of x at s, inside n, spell the counted literal p? +fn lu_at(x : []const u8, n : usize, s : usize, p : str, pn : usize) -> bool { + if (s + pn > n) { return false; } + var k : usize = 0; + while (k < pn) { + if (x[s + k] != p[k]) { return false; } + k += 1; + } + return true; +} + +/// Byte order of a[a0..a1) against b[b0..b1): -1, 0 or 1, a shorter prefix first -- the order of +/// Python's sorted() over path.encode(), which is how the ledger is blessed. +fn lu_cmp(a : []const u8, a0 : usize, a1 : usize, b : []const u8, b0 : usize, b1 : usize) -> i32 { + var i : usize = 0; + while (a0 + i < a1 and b0 + i < b1) { + if (a[a0 + i] < b[b0 + i]) { return -1; } + if (a[a0 + i] > b[b0 + i]) { return 1; } + i += 1; + } + if (a0 + i < a1) { return 1; } + if (b0 + i < b1) { return -1; } + return 0; +} + +/// Copy x[s..e) to dst at w, the put_str contract for a buffer instead of a literal. +fn lu_copy(dst : []u8, cap : usize, w : usize, x : []const u8, s : usize, e : usize) -> usize { + var i : usize = s; + while (i < e) { + if (w + (i - s) < cap) { dst[w + (i - s)] = x[i]; } + i += 1; + } + return w + (e - s); +} + +/// End of the path that starts at ks on a line ending at e: the closing quote. e when there is no +/// closing quote or the path holds a backslash, which this rule does not unescape. +fn lu_key_end(x : []const u8, e : usize, ks : usize) -> usize { + var i : usize = ks; + while (i < e) { + if (x[i] == '"') { return i; } + if (x[i] == 92) { return e; } + i += 1; + } + return e; +} + +/// End of the value of the entry line starting at p: the line without its trailing comma. +fn lu_val_end(x : []const u8, n : usize, p : usize) -> usize { + const e : usize = lu_eol(x, n, p); + if (x[e - 1] == ',') { return e - 1; } + return e; +} + +/// Check one side and find its frame: fr[0] = first entry line, fr[1] = footer. Returns U_OK, +/// U_SHAPE or U_UNSORTED. +fn lu_frame(x : []const u8, n : usize, fr : []usize) -> u32 { + var s : usize = 0; + var hs : usize = n; + while (s < n and hs == n) { + const e : usize = lu_eol(x, n, s); + if (lu_at(x, e, s, ENTRY_HEAD, ENTRY_HEAD_LEN)) { hs = s; } else { s = e + 1; } + } + if (hs == n) { return U_SHAPE; } + if (hs < ENTRIES_OPEN_LEN + 1) { return U_SHAPE; } + if (lu_at(x, hs - 1, hs - 1 - ENTRIES_OPEN_LEN, ENTRIES_OPEN, ENTRIES_OPEN_LEN) == false) { return U_SHAPE; } + var p : usize = hs; + var fe : usize = n; + var pks : usize = 0; + var pke : usize = 0; + var first : bool = true; + var comma : bool = true; + while (p < n and fe == n) { + const e : usize = lu_eol(x, n, p); + if (lu_at(x, e, p, ENTRY_HEAD, ENTRY_HEAD_LEN) == false) { + fe = p; + } else { + if (comma == false) { return U_SHAPE; } + const ks : usize = p + ENTRY_HEAD_LEN; + const ke : usize = lu_key_end(x, e, ks); + if (ke >= e or ke == ks) { return U_SHAPE; } + if (x[e - 1] == ',') { + if (x[e - 2] != '}') { return U_SHAPE; } + comma = true; + } else if (x[e - 1] == '}') { + comma = false; + } else { + return U_SHAPE; + } + if (first == false) { + if (lu_cmp(x, pks, pke, x, ks, ke) >= 0) { return U_UNSORTED; } + } + pks = ks; + pke = ke; + first = false; + p = e + 1; + } + } + if (fe == n or comma) { return U_SHAPE; } + if (lu_at(x, n, fe, FOOTER, FOOTER_LEN) == false) { return U_SHAPE; } + var t : usize = fe + FOOTER_LEN; + while (t < n) { + if (x[t] != '\n' and x[t] != ' ' and x[t] != 13) { return U_SHAPE; } + t += 1; + } + fr[0] = hs; + fr[1] = fe; + return U_OK; +} + +/// The refusal line into out, and the verdict into stats: "refused: ()", then +/// ": " when x[ks..ke) names one. +fn lu_refuse(out : []u8, cap : usize, stats : []u32, code : u32, side : u32, x : []const u8, ks : usize, ke : usize) -> usize { + stats[0] = code; + stats[3] = side; + var w : usize = put_str(out, cap, 0, "refused: ", 9); + w = put_str(out, cap, w, REASONS[(code) as usize], REASON_LENS[(code) as usize]); + if (side < 3) { + w = put_str(out, cap, w, " (", 2); + w = put_str(out, cap, w, SIDES[(side) as usize], SIDE_LENS[(side) as usize]); + w = put_str(out, cap, w, ")", 1); + } + if (ke > ks) { + w = put_str(out, cap, w, ": ", 2); + w = lu_copy(out, cap, w, x, ks, ke); + } + return put_str(out, cap, w, "\n", 1); +} + +/// The union of the ledger texts base b[0..bn), theirs t[0..tn) and ours o[0..on), into out. +/// Returns the bytes written. stats[0] is U_OK when out holds the merged ledger; any other verdict +/// means out holds one refusal line instead. U_OVERFLOW: the return exceeds cap and out is clipped. +pub fn ledger_union(b : []const u8, bn : usize, t : []const u8, tn : usize, o : []const u8, on : usize, out : []u8, cap : usize, stats : []u32) -> usize { + var k : usize = 0; + while (k < STATS_LEN) { + stats[k] = 0; + k += 1; + } + stats[3] = 3; + var fb : [2]usize = undefined; + var ft : [2]usize = undefined; + var fo : [2]usize = undefined; + var c : u32 = lu_frame(b, bn, &fb); + if (c != U_OK) { return lu_refuse(out, cap, stats, c, 0, b, 0, 0); } + c = lu_frame(t, tn, &ft); + if (c != U_OK) { return lu_refuse(out, cap, stats, c, 1, t, 0, 0); } + c = lu_frame(o, on, &fo); + if (c != U_OK) { return lu_refuse(out, cap, stats, c, 2, o, 0, 0); } + if (lu_cmp(t, 0, ft[0], b, 0, fb[0]) != 0 and lu_cmp(t, 0, ft[0], o, 0, fo[0]) != 0) { + return lu_refuse(out, cap, stats, U_FRAME, 1, t, 0, 0); + } + if (lu_cmp(t, ft[1], tn, b, fb[1], bn) != 0 and lu_cmp(t, ft[1], tn, o, fo[1], on) != 0) { + return lu_refuse(out, cap, stats, U_FRAME, 1, t, 0, 0); + } + var w : usize = lu_copy(out, cap, 0, o, 0, fo[0]); + var pb : usize = fb[0]; + var pt : usize = ft[0]; + var po : usize = fo[0]; + var count : u32 = 0; + var added : u32 = 0; + while (pb < fb[1] or pt < ft[1] or po < fo[1]) { + const hb : bool = pb < fb[1]; + const ht : bool = pt < ft[1]; + const ho : bool = po < fo[1]; + var kbs : usize = 0; + var kbe : usize = 0; + var kts : usize = 0; + var kte : usize = 0; + var kos : usize = 0; + var koe : usize = 0; + if (hb) { + kbs = pb + ENTRY_HEAD_LEN; + kbe = lu_key_end(b, bn, kbs); + } + if (ht) { + kts = pt + ENTRY_HEAD_LEN; + kte = lu_key_end(t, tn, kts); + } + if (ho) { + kos = po + ENTRY_HEAD_LEN; + koe = lu_key_end(o, on, kos); + } + // The smallest key, ties going to the earlier side: 0 base, 1 theirs, 2 ours. + var m : u32 = 3; + if (hb) { m = 0; } + if (ht) { + if (m == 3) { + m = 1; + } else if (lu_cmp(t, kts, kte, b, kbs, kbe) < 0) { + m = 1; + } + } + if (ho) { + if (m == 3) { + m = 2; + } else if (m == 0) { + if (lu_cmp(o, kos, koe, b, kbs, kbe) < 0) { m = 2; } + } else if (lu_cmp(o, kos, koe, t, kts, kte) < 0) { + m = 2; + } + } + var in_b : bool = false; + var in_t : bool = false; + var in_o : bool = false; + if (m == 0) { + in_b = true; + in_t = ht and lu_cmp(t, kts, kte, b, kbs, kbe) == 0; + in_o = ho and lu_cmp(o, kos, koe, b, kbs, kbe) == 0; + } else if (m == 1) { + in_t = true; + in_o = ho and lu_cmp(o, kos, koe, t, kts, kte) == 0; + } else { + in_o = true; + } + var vbe : usize = 0; + var vte : usize = 0; + var voe : usize = 0; + if (in_b) { vbe = lu_val_end(b, bn, pb); } + if (in_t) { vte = lu_val_end(t, tn, pt); } + if (in_o) { voe = lu_val_end(o, on, po); } + var same_tb : bool = in_t == in_b; + if (same_tb and in_b) { same_tb = lu_cmp(t, pt, vte, b, pb, vbe) == 0; } + var same_ot : bool = in_o == in_t; + if (same_ot and in_t) { same_ot = lu_cmp(o, po, voe, t, pt, vte) == 0; } + var emit : u32 = 3; + if (same_tb or same_ot) { + if (in_o) { emit = 2; } + } else if (in_b == false and in_o == false) { + emit = 1; + added += 1; + } else if (in_b == false) { + return lu_refuse(out, cap, stats, U_BOTH_ADDED, 1, t, kts, kte); + } else { + return lu_refuse(out, cap, stats, U_THEIRS_CHANGED, 1, b, kbs, kbe); + } + if (emit != 3) { + if (count > 0) { w = put_str(out, cap, w, ",\n", 2); } + if (emit == 2) { w = lu_copy(out, cap, w, o, po, voe); } else { w = lu_copy(out, cap, w, t, pt, vte); } + count += 1; + } + if (in_b) { pb = lu_eol(b, bn, pb) + 1; } + if (in_t) { pt = lu_eol(t, tn, pt) + 1; } + if (in_o) { po = lu_eol(o, on, po) + 1; } + } + w = put_str(out, cap, w, "\n", 1); + w = lu_copy(out, cap, w, o, fo[1], on); + stats[1] = added; + stats[2] = count; + if (w > cap) { stats[0] = U_OVERFLOW; } + return w; +} + +/// Git conflict-marker lines in x[0..n): a line whose first seven bytes are all '<', all '=', all +/// '>' or all '|'. A batch is checked to hold none. +pub fn marker_lines(x : []const u8, n : usize) -> u32 { + var c : u32 = 0; + var s : usize = 0; + while (s < n) { + const e : usize = lu_eol(x, n, s); + if (e - s >= 7) { + const m : u8 = x[s]; + if (m == '<' or m == '=' or m == '>' or m == '|') { + var j : usize = 1; + while (j < 7 and x[s + j] == m) { j += 1; } + if (j == 7) { c += 1; } + } + } + s = e + 1; + } + return c; +} + +/// Does x[s..e) end with the counted literal p? +fn lu_ends(x : []const u8, s : usize, e : usize, p : str, pn : usize) -> bool { + if (e - s < pn) { return false; } + return lu_at(x, e, e - pn, p, pn); +} + +/// Is x[s..e) a .t27 file under specs/port with no ".." in it? +fn lu_port_spec(x : []const u8, s : usize, e : usize) -> bool { + if (lu_at(x, e, s, PORT_DIR, PORT_DIR_LEN) == false) { return false; } + if (lu_ends(x, s, e, ".t27", 4) == false) { return false; } + var i : usize = s; + while (i + 1 < e) { + if (x[i] == '.' and x[i + 1] == '.') { return false; } + i += 1; + } + return true; +} + +/// Index of the first tab in x[s..e), or e. +fn lu_tab(x : []const u8, s : usize, e : usize) -> usize { + var i : usize = s; + while (i < e and x[i] != '\t') { i += 1; } + return i; +} + +/// Is there a record with tag g whose second field equals x[fs..fe)? Returns the index just past +/// that field (its tab, or the line end), n when there is none. +fn lu_find(x : []const u8, n : usize, g : u8, fs : usize, fe : usize) -> usize { + var s : usize = 0; + while (s < n) { + const e : usize = lu_eol(x, n, s); + if (e > s + 1 and x[s] == g and x[s + 1] == '\t') { + const t : usize = lu_tab(x, s + 2, e); + if (lu_cmp(x, s + 2, t, x, fs, fe) == 0) { return t; } + } + s = e + 1; + } + return n; +} + +/// Does the seal x[ps..pe) have an S record naming a specs/port .t27 file that is also an F record? +fn lu_seal_ok(x : []const u8, n : usize, ps : usize, pe : usize) -> bool { + const sp : usize = lu_find(x, n, 'S', ps, pe); + if (sp >= n) { return false; } + const se : usize = lu_eol(x, n, sp); + if (sp >= se or x[sp] != '\t') { return false; } + if (lu_port_spec(x, sp + 1, se) == false) { return false; } + return lu_find(x, n, 'F', sp + 1, se) < n; +} + +/// Whether one pull request may join a port batch. The records in x[0..n), one per line: +/// F every file the pull request changes +/// S each changed seal and the spec_path field it records +/// In scope: every F is the ledger, a specs/port .t27 file, or a .trinity/seals/*.json whose S +/// record names a specs/port .t27 file that is itself an F of the same pull request. Returns the +/// bytes of the refusal line written to out (0 when in scope); stats[0] verdict, stats[1] files. +pub fn pr_scope(x : []const u8, n : usize, out : []u8, cap : usize, stats : []u32) -> usize { + stats[0] = SC_OK; + stats[1] = 0; + var code : u32 = SC_OK; + var bs : usize = 0; + var be : usize = 0; + var s : usize = 0; + while (s < n and code == SC_OK) { + const e : usize = lu_eol(x, n, s); + if (e > s) { + const tagged : bool = e > s + 2 and x[s + 1] == '\t'; + if (tagged and x[s] == 'F') { + stats[1] += 1; + const ps : usize = s + 2; + const ledger : bool = e - ps == LEDGER_LEN and lu_at(x, e, ps, LEDGER, LEDGER_LEN); + if (lu_tab(x, ps, e) < e) { + code = SC_BAD; + } else if (ledger == false and lu_port_spec(x, ps, e) == false) { + if (lu_at(x, e, ps, SEAL_DIR, SEAL_DIR_LEN) and lu_ends(x, ps, e, ".json", 5)) { + if (lu_seal_ok(x, n, ps, e) == false) { code = SC_SEAL; } + } else { + code = SC_OUTSIDE; + } + } + if (code != SC_OK) { + bs = ps; + be = e; + } + } else if (tagged and x[s] == 'S') { + const t : usize = lu_tab(x, s + 2, e); + if (t >= e or t == s + 2) { + code = SC_BAD; + bs = s; + be = e; + } + } else { + code = SC_BAD; + bs = s; + be = e; + } + } + s = e + 1; + } + if (code == SC_OK and stats[1] == 0) { code = SC_EMPTY; } + if (code == SC_OK) { return 0; } + stats[0] = code; + var w : usize = put_str(out, cap, 0, "refused: ", 9); + w = put_str(out, cap, w, SCOPE_REASONS[(code) as usize], SCOPE_REASON_LENS[(code) as usize]); + if (be > bs) { + w = put_str(out, cap, w, ": ", 2); + w = lu_copy(out, cap, w, x, bs, be); + } + return put_str(out, cap, w, "\n", 1); +} + +// Test fixtures: the frame and line builders the tests below share. + +/// The frame of a test ledger: the real one carries more header keys, none of which the rule reads. +pub const FIX_HEAD : str = "{\n \"entries\": [\n"; +pub const FIX_HEAD_LEN : usize = 16; +pub const FIX_FOOT : str = " ]\n}\n"; +pub const FIX_FOOT_LEN : usize = 5; + +/// One entry line as the ratchet blesses it: path, verdict, a comma unless last, a newline. +fn fixture_line(dst : []u8, cap : usize, w : usize, path : str, plen : usize, verdict : str, vlen : usize, last : bool) -> usize { + var x : usize = put_str(dst, cap, w, ENTRY_HEAD, ENTRY_HEAD_LEN); + x = put_str(dst, cap, x, path, plen); + x = put_str(dst, cap, x, "\", \"t27b\": \"", 12); + x = put_str(dst, cap, x, verdict, vlen); + x = put_str(dst, cap, x, "\"}", 2); + if (last == false) { x = put_str(dst, cap, x, ",", 1); } + return put_str(dst, cap, x, "\n", 1); +} + +/// The trios-chat ledger paths of the real #10098 conflict (PR #9945 into the fork batch). +pub const CHAT_A : str = "specs/port/trios/crates/trios-chat/rings/CR-CHAT-02/src/epoch_rollover_wraparound_guard.t27"; +pub const CHAT_B : str = "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/pcs_healing.t27"; +pub const CHAT_C : str = "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/proposal_ref_collision.t27"; +pub const CHAT_D : str = "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/proposal_validation.t27"; +pub const CHAT_E : str = "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/ratchet_tree_resolution_guard.t27"; +pub const CHAT_F : str = "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/ratchet_tree_update_integrity_guard.t27"; +pub const CHAT_G : str = "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/unmerged_leaves_bound_guard.t27"; + +/// Three tiny sides with two-letter paths, for the negative controls: base {aa, cc}, then whatever +/// the test writes. +fn fixture_pair(dst : []u8, cap : usize, p1 : str, v1 : str, v1n : usize, p2 : str, v2 : str, v2n : usize) -> usize { + var w : usize = put_str(dst, cap, 0, FIX_HEAD, FIX_HEAD_LEN); + w = fixture_line(dst, cap, w, p1, 2, v1, v1n, false); + w = fixture_line(dst, cap, w, p2, 2, v2, v2n, true); + return put_str(dst, cap, w, FIX_FOOT, FIX_FOOT_LEN); +} + +test replays_the_ledger_conflict_of_pr_9945_in_the_10098_batch { + // The entries around the conflict, byte for byte from git: base cd8a65666 (lines 853-855), + // theirs = PR #9945 head (853-856), ours = the batch before it, 9ce20d7d6^1 (855 and 871-875; + // 856-870 are left out, they sort outside the conflict). The batch commit 9ce20d7d6 holds the + // expected order: theirs' proposal_validation lands between ours' two additions and the guard. + var b : [2048]u8 = undefined; + var bn : usize = put_str(&b, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + bn = fixture_line(&b, 2048, bn, CHAT_A, 91, "pass", 4, false); + bn = fixture_line(&b, 2048, bn, CHAT_E, 89, "pass", 4, false); + bn = fixture_line(&b, 2048, bn, CHAT_G, 87, "pass", 4, true); + bn = put_str(&b, 2048, bn, FIX_FOOT, FIX_FOOT_LEN); + var t : [2048]u8 = undefined; + var tn : usize = put_str(&t, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 2048, tn, CHAT_A, 91, "pass", 4, false); + tn = fixture_line(&t, 2048, tn, CHAT_D, 79, "pass", 4, false); + tn = fixture_line(&t, 2048, tn, CHAT_E, 89, "pass", 4, false); + tn = fixture_line(&t, 2048, tn, CHAT_G, 87, "pass", 4, true); + tn = put_str(&t, 2048, tn, FIX_FOOT, FIX_FOOT_LEN); + var o : [2048]u8 = undefined; + var on : usize = put_str(&o, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 2048, on, CHAT_A, 91, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_B, 71, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_C, 82, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_E, 89, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_F, 95, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_G, 87, "pass", 4, true); + on = put_str(&o, 2048, on, FIX_FOOT, FIX_FOOT_LEN); + var x : [2048]u8 = undefined; + var xn : usize = put_str(&x, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 2048, xn, CHAT_A, 91, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_B, 71, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_C, 82, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_D, 79, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_E, 89, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_F, 95, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_G, 87, "pass", 4, true); + xn = put_str(&x, 2048, xn, FIX_FOOT, FIX_FOOT_LEN); + var out : [4096]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &o, on, &out, 4096, &st); + assert st[0] == U_OK; + assert st[1] == 1; + assert st[2] == 7; + assert m == xn; + assert lu_cmp(&out, 0, m, &x, 0, xn) == 0; + assert marker_lines(&out, m) == 0; +} + +test the_same_addition_on_both_sides_is_written_once { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [512]u8 = undefined; + var tn : usize = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "bb", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &t, tn, &out, 1024, &st); + assert st[0] == U_OK; + assert st[1] == 0; + assert st[2] == 3; + assert m == tn; + assert lu_cmp(&out, 0, m, &t, 0, tn) == 0; +} + +test ours_progress_wins_where_theirs_kept_the_base_entry { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "unimplemented", 13, "cc", "pass", 4); + var o : [256]u8 = undefined; + const on : usize = fixture_pair(&o, 256, "aa", "pass", 4, "cc", "pass", 4); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &b, bn, &o, on, &out, 1024, &st); + assert st[0] == U_OK; + assert st[2] == 2; + assert m == on; + assert lu_cmp(&out, 0, m, &o, 0, on) == 0; +} + +test ours_removing_an_entry_theirs_kept_stays_removed { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var o : [256]u8 = undefined; + var on : usize = put_str(&o, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 256, on, "cc", 2, "pass", 4, true); + on = put_str(&o, 256, on, FIX_FOOT, FIX_FOOT_LEN); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &b, bn, &o, on, &out, 1024, &st); + assert st[0] == U_OK; + assert st[2] == 1; + assert lu_cmp(&out, 0, m, &o, 0, on) == 0; +} + +test two_additions_of_one_path_with_different_verdicts_are_refused { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [512]u8 = undefined; + var tn : usize = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "bb", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + var o : [512]u8 = undefined; + var on : usize = put_str(&o, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 512, on, "aa", 2, "pass", 4, false); + on = fixture_line(&o, 512, on, "bb", 2, "unimplemented", 13, false); + on = fixture_line(&o, 512, on, "cc", 2, "pass", 4, true); + on = put_str(&o, 512, on, FIX_FOOT, FIX_FOOT_LEN); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &o, on, &out, 1024, &st); + assert st[0] == U_BOTH_ADDED; + assert st[3] == 1; + assert buf_has(&out, m, "refused: both sides added this path with different values (theirs): bb\n", 71) == true; +} + +test theirs_changing_a_base_entry_is_refused { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [256]u8 = undefined; + const tn : usize = fixture_pair(&t, 256, "aa", "pass", 4, "cc", "unimplemented", 13); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &b, bn, &out, 1024, &st); + assert st[0] == U_THEIRS_CHANGED; + assert buf_has(&out, m, "(theirs): cc\n", 13) == true; +} + +test theirs_removing_a_base_entry_is_refused { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [256]u8 = undefined; + var tn : usize = put_str(&t, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 256, tn, "aa", 2, "pass", 4, true); + tn = put_str(&t, 256, tn, FIX_FOOT, FIX_FOOT_LEN); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &b, bn, &out, 1024, &st); + assert st[0] == U_THEIRS_CHANGED; + assert buf_has(&out, m, ": cc\n", 5) == true; +} + +test theirs_and_ours_making_the_same_change_agree { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [256]u8 = undefined; + const tn : usize = fixture_pair(&t, 256, "aa", "pass", 4, "cc", "unimplemented", 13); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &t, tn, &out, 1024, &st); + assert st[0] == U_OK; + assert lu_cmp(&out, 0, m, &t, 0, tn) == 0; +} + +test a_header_theirs_changed_is_refused_and_one_ours_changed_is_kept { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var h : [256]u8 = undefined; + var hn : usize = put_str(&h, 256, 0, "{\n \"max_not_pass\": 74,\n \"entries\": [\n", 37); + hn = fixture_line(&h, 256, hn, "aa", 2, "pass", 4, false); + hn = fixture_line(&h, 256, hn, "cc", 2, "pass", 4, true); + hn = put_str(&h, 256, hn, FIX_FOOT, FIX_FOOT_LEN); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + var m : usize = ledger_union(&b, bn, &h, hn, &b, bn, &out, 1024, &st); + assert st[0] == U_FRAME; + m = ledger_union(&b, bn, &b, bn, &h, hn, &out, 1024, &st); + assert st[0] == U_OK; + assert m == hn; + assert lu_cmp(&out, 0, m, &h, 0, hn) == 0; +} + +test a_side_out_of_order_or_with_a_duplicate_is_refused { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var u : [256]u8 = undefined; + const un : usize = fixture_pair(&u, 256, "cc", "pass", 4, "aa", "pass", 4); + var d : [256]u8 = undefined; + const dn : usize = fixture_pair(&d, 256, "aa", "pass", 4, "aa", "pass", 4); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + var m : usize = ledger_union(&b, bn, &u, un, &b, bn, &out, 1024, &st); + assert st[0] == U_UNSORTED; + assert st[3] == 1; + m = ledger_union(&b, bn, &b, bn, &d, dn, &out, 1024, &st); + assert st[0] == U_UNSORTED; + assert st[3] == 2; + assert buf_has(&out, m, "(ours)", 6) == true; +} + +test a_side_not_in_the_ledger_shape_is_refused { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + // The last entry carries a comma. + var x : [256]u8 = undefined; + var xn : usize = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, false); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + var m : usize = ledger_union(&b, bn, &x, xn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + // A middle entry lacks its comma. + xn = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = fixture_line(&x, 256, xn, "cc", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&b, bn, &x, xn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + // A path with a backslash. + xn = fixture_pair(&x, 256, "aa", "pass", 4, "c\\", "pass", 4); + m = ledger_union(&b, bn, &x, xn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + // No footer. + m = ledger_union(&b, bn - FIX_FOOT_LEN, &b, bn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + assert st[3] == 0; + // Conflict markers in place of entries. + xn = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = put_str(&x, 256, xn, "<<<<<<< HEAD\n", 13); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&b, bn, &b, bn, &x, xn, &out, 1024, &st); + assert st[0] == U_SHAPE; + assert buf_has(&out, m, "refused: a side is not in the ledger's shape (ours)\n", 52) == true; +} + +test a_union_too_big_for_the_buffer_says_so { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var out : [16]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &b, bn, &b, bn, &out, 16, &st); + assert st[0] == U_OVERFLOW; + assert m == bn; + assert m > 16; +} + +test marker_lines_counts_only_full_markers_at_a_line_start { + var x : [256]u8 = undefined; + var n : usize = put_str(&x, 256, 0, "<<<<<<< HEAD\na\n=======\nb\n>>>>>>> pr\n", 36); + assert marker_lines(&x, n) == 3; + n = put_str(&x, 256, 0, "<<<<<< six\n a =======\n|||||||\n", 30); + assert marker_lines(&x, n) == 1; + n = put_str(&x, 256, 0, "{\"a\": \"=======\"}\n", 17); + assert marker_lines(&x, n) == 0; +} + +test a_port_spec_its_seal_and_the_ledger_are_in_scope { + var x : [512]u8 = undefined; + var n : usize = put_str(&x, 512, 0, "F\tdocs/reports/t27b_expectations.json\n", 38); + n = put_str(&x, 512, n, "F\tspecs/port/a/b.t27\n", 21); + n = put_str(&x, 512, n, "F\t.trinity/seals/port::a::b.json\n", 33); + n = put_str(&x, 512, n, "S\t.trinity/seals/port::a::b.json\tspecs/port/a/b.t27\n", 52); + var out : [256]u8 = undefined; + var st : [2]u32 = undefined; + const m : usize = pr_scope(&x, n, &out, 256, &st); + assert m == 0; + assert st[0] == SC_OK; + assert st[1] == 3; +} + +test a_file_outside_the_port_shape_is_named { + var x : [512]u8 = undefined; + var n : usize = put_str(&x, 512, 0, "F\tspecs/port/a/b.t27\n", 21); + n = put_str(&x, 512, n, "F\t.github/workflows/x.yml\n", 26); + var out : [256]u8 = undefined; + var st : [2]u32 = undefined; + var m : usize = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; + assert buf_has(&out, m, ": .github/workflows/x.yml\n", 26) == true; + n = put_str(&x, 512, 0, "F\tspecs/port/../claw/text.t27\n", 30); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; + n = put_str(&x, 512, 0, "F\tspecs/port/a/b.zig\n", 21); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; +} + +test a_seal_counts_only_with_its_own_port_spec { + var x : [512]u8 = undefined; + var out : [256]u8 = undefined; + var st : [2]u32 = undefined; + // No S record for the seal. + var n : usize = put_str(&x, 512, 0, "F\t.trinity/seals/port::a::b.json\n", 33); + var m : usize = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_SEAL; + // The spec it seals is not part of the pull request. + n = put_str(&x, 512, n, "S\t.trinity/seals/port::a::b.json\tspecs/port/a/b.t27\n", 52); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_SEAL; + // It seals a spec outside specs/port. + n = put_str(&x, 512, 0, "F\t.trinity/seals/claw_X.json\nF\tspecs/claw/x.t27\n", 48); + n = put_str(&x, 512, n, "S\t.trinity/seals/claw_X.json\tspecs/claw/x.t27\n", 46); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_SEAL; + assert buf_has(&out, m, ": .trinity/seals/claw_X.json\n", 29) == true; +} + +test an_empty_or_unreadable_file_list_is_refused { + var x : [128]u8 = undefined; + var out : [256]u8 = undefined; + var st : [2]u32 = undefined; + var m : usize = pr_scope(&x, 0, &out, 256, &st); + assert st[0] == SC_EMPTY; + var n : usize = put_str(&x, 128, 0, "X\tspecs/port/a.t27\n", 19); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_BAD; + n = put_str(&x, 128, 0, "F\tspecs/port/a.t27\textra\n", 25); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_BAD; + assert m > 0; +} + +test the_reason_tables_match_their_lengths { + // Every reason ends in a letter: a length one short or one long would end elsewhere. + var r : [128]u8 = undefined; + var i : usize = 0; + while (i < 7) { + const k : usize = put_str(&r, 128, 0, REASONS[i], REASON_LENS[i]); + assert r[k - 1] >= 'a' and r[k - 1] <= 'z'; + i += 1; + } + i = 0; + while (i < 5) { + const k : usize = put_str(&r, 128, 0, SCOPE_REASONS[i], SCOPE_REASON_LENS[i]); + assert r[k - 1] >= 'a' and r[k - 1] <= 'z'; + i += 1; + } + const s : usize = put_str(&r, 128, 0, SCOPE_REASONS[2], SCOPE_REASON_LENS[2]); + assert buf_has(&r, s, "same pull request", 17) == true; +} From b0c82558ac2105d6344d0e40519e84fc83f87d99 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 20:47:12 +0700 Subject: [PATCH 2/8] claw: claw-batch job in gate-topology.yml folds port PRs by the ledger union (WIP, Refs #10398) Drops scripts/gen_w387_lean.py, an unreferenced wave generator, so the foreign line count shrinks. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/gate-topology.yml | 38 ++++++++ .github/workflows/spec-guards.yml | 3 +- scripts/gen_w387_lean.py | 141 ---------------------------- 3 files changed, 40 insertions(+), 142 deletions(-) delete mode 100644 scripts/gen_w387_lean.py diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index c384b839fe..1aef53933f 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -19,6 +19,7 @@ on: # passing, and there was no way to fire it at the default branch and find # out. `tri gates unmeasured` reports the gap; this is what closes it. workflow_dispatch: + inputs: {prs: {description: "claw-batch only: N:headsha pairs of specs/port pull requests to fold into one batch", required: false, default: "", type: string}} # Only the claw-t27f job below runs on this schedule (#10268). schedule: [{cron: "*/10 * * * *"}] @@ -204,3 +205,40 @@ jobs: M) printf '%s\n' "$a" >> "$GITHUB_STEP_SUMMARY";; esac done < /tmp/plan + + # claw-batch (#10398, lane 5 of #10393): fold the port pull requests named in prs ("N:headsha ...") into ONE batch pull + # request. The merge rule is specs/claw/ledger_union.t27 (gen/c/claw/ledger_union.c; the printf'd harness is machinery: + # u union, s pr_scope, m marker_lines). A moved head, a file outside specs/port + its seal + the ledger, or refused ledger + # lines leave a pull request out, named; a conflict in any other file stops the batch. Token: contents + pull_requests + # write. Opened as a draft with no-auto-merge (issue-gate.yml would squash it), then a MERGE-commit auto-merge: never + # squash, so every folded head stays a parent and GitHub marks it merged. + claw-batch: + if: github.event_name == 'workflow_dispatch' && inputs.prs != '' + runs-on: ubuntu-24.04 + permissions: {contents: read} + steps: + - {id: tok, uses: actions/create-github-app-token@v2, with: {app-id: "${{ vars.CLAW_APP_ID || secrets.CLAW_APP_ID }}", private-key: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", owner: gHashTag, repositories: t27, permission-contents: write, permission-pull-requests: write}} + - {uses: actions/checkout@v6, with: {ref: master, fetch-depth: 0, token: "${{ steps.tok.outputs.token }}"}} + - env: {GH_TOKEN: "${{ steps.tok.outputs.token }}", PRS: "${{ inputs.prs }}", SLUG: "${{ steps.tok.outputs.app-slug }}", RUN: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", B: "claw/batch-${{ github.run_id }}"} + shell: bash {0} + run: | + set -uo pipefail; L=docs/reports/t27b_expectations.json; d=$(mktemp -d); ok=(); out=(); stop="" + printf '#include "gen/c/claw/ledger_union.c"\n#include \n#include \nstatic size_t rd(FILE*f,uint8_t*b,size_t k){size_t n=f?fread(b,1,k,f):k;if(n==k)exit(3);if(f!=stdin)fclose(f);return n;}\nint main(int c,char**v){size_t k=1<<24,m,n;uint8_t*b=malloc(k),*t=malloc(k),*o=malloc(k),*w=malloc(k);uint32_t s[4];\nif(c==5&&*v[1]==117){n=rd(fopen(v[2],"rb"),b,k);size_t tn=rd(fopen(v[3],"rb"),t,k),on=rd(fopen(v[4],"rb"),o,k);m=ledger_union(b,n,t,tn,o,on,w,k,s);if(m>k)return 3;fwrite(w,1,m,s[0]?stderr:stdout);return s[0]?1:0;}\nn=rd(stdin,b,k);if(c==2&&*v[1]==115){m=pr_scope(b,n,w,k,s);if(m>k)return 3;fwrite(w,1,m,stdout);return m?1:0;}\nif(c==2&&*v[1]==109){uint32_t x=marker_lines(b,n);printf("%%u\\n",x);return x?1:0;}\nreturn 2;}\n' | cc -O1 -w -I . -x c -o "$d/lu" - || exit 1 + U=$(gh api "users/$SLUG[bot]" -q .id) || exit 1; git config user.name "$SLUG[bot]"; git config user.email "$U+$SLUG[bot]@users.noreply.github.com"; git checkout -q -B "$B" origin/master + for e in $PRS; do n=${e%%:*}; h=${e#*:} + if [ -n "$stop" ]; then out+=("#$n: not tried, the batch stopped at $stop"); continue; fi; [[ "$e" =~ ^[0-9]+:[0-9a-f]{40}$ ]] || { out+=("$e: not N:headsha"); continue; } + read -r st cur base br <<< "$(gh pr view "$n" --json state,headRefOid,baseRefName,headRefName -q '"\(.state) \(.headRefOid) \(.baseRefName) \(.headRefName)"' /dev/null | jq -r '.spec_path // empty' 2>/dev/null)"; done; } | "$d/lu" s) || { out+=("#$n: ${r:-the file list is unreadable}"); continue; } + m="Merge PR #$n ($br) into the port batch"; git merge -q --no-ff -m "$m" "$h" "$d/log" 2>&1 && { ok+=("$n:$h"); continue; } + u=$(git diff --name-only --diff-filter=U); [ "$u" = "$L" ] || { git merge --abort; stop="#$n"; out+=("#$n: conflicts outside the ledger, the batch stops here: $(echo $u)"); continue; } + if git show ":1:$L" > "$d/b" && git show ":3:$L" > "$d/t" && git show ":2:$L" > "$d/o" && "$d/lu" u "$d/b" "$d/t" "$d/o" > "$L" 2> "$d/why" && git add "$L" && git commit -q -m "$m"; then ok+=("$n:$h"); else git merge --abort; out+=("#$n: ledger $(cat "$d/why")"); fi + done + { for x in "${ok[@]}"; do echo "- folded #${x%%:*} at ${x#*:}"; done; for x in "${out[@]}"; do echo "- left out $x"; done; } > "$d/list"; cat "$d/list" >> "$GITHUB_STEP_SUMMARY"; [ ${#ok[@]} -gt 0 ] || { echo "::error::claw-batch folded no pull request"; exit 1; } + bad=0; while read -r f; do [ -f "$f" ] && ! "$d/lu" m < "$f" > /dev/null && { echo "::error file=$f::conflict-marker lines in the batch"; bad=1; }; done < <(git diff --name-only --diff-filter=d origin/master HEAD) + jq empty "$L" && "$d/lu" u "$L" "$L" "$L" > /dev/null || { echo "::error file=$L::the batch ledger is not JSON in the ledger shape"; bad=1; }; [ $bad = 0 ] && git push -q origin "HEAD:refs/heads/$B" || exit 1 + { echo "Port batch of ${#ok[@]} pull requests, folded by the claw-batch job of gate-topology.yml: $RUN"; echo; echo "Merge with a merge commit, never squash: every folded head is a parent here, so GitHub marks it merged when this lands."; echo; cat "$d/list"; echo + for x in "${ok[@]}"; do gh pr view "${x%%:*}" --json closingIssuesReferences -q '.closingIssuesReferences[].number' "$d/body" + p=$(gh pr create --draft --label no-auto-merge --base master --head "$B" --title "claw: port batch of ${#ok[@]} pull requests" --body-file "$d/body") || exit 1; echo "batch pull request: $p" >> "$GITHUB_STEP_SUMMARY" + gh pr ready "$p" && gh pr merge "$p" --merge --auto || echo "::warning::$p is open but its merge-commit auto-merge is not set" diff --git a/.github/workflows/spec-guards.yml b/.github/workflows/spec-guards.yml index 9c07bd6f79..dff2c16932 100644 --- a/.github/workflows/spec-guards.yml +++ b/.github/workflows/spec-guards.yml @@ -129,9 +129,10 @@ jobs: ./target/release/t27c gen-c specs/claw/sarif_feed.t27 > gen/c/claw/sarif_feed.c ./target/release/t27c gen-c specs/claw/suggest_feed.t27 > gen/c/claw/suggest_feed.c ./target/release/t27c gen-c specs/claw/refresh.t27 > gen/c/claw/refresh.c + ./target/release/t27c gen-c specs/claw/ledger_union.t27 > gen/c/claw/ledger_union.c ./target/release/t27c gen-rust specs/tri/fmt_source.t27 > bootstrap/gen/rust/tri/fmt_source.rs ./target/release/t27c gen-json specs/igla/own_model_board.t27 > gen/json/igla/own_model_board.json - git diff --exit-code -- gen/c/claw/gates.c gen/c/claw/t27f_gate.c gen/c/claw/sarif_feed.c gen/c/claw/suggest_feed.c gen/c/claw/refresh.c bootstrap/gen/rust/tri/fmt_source.rs gen/json/igla/own_model_board.json + git diff --exit-code -- gen/c/claw/gates.c gen/c/claw/t27f_gate.c gen/c/claw/sarif_feed.c gen/c/claw/suggest_feed.c gen/c/claw/refresh.c gen/c/claw/ledger_union.c bootstrap/gen/rust/tri/fmt_source.rs gen/json/igla/own_model_board.json # #7565: test only the specs this change can affect. specs/ci/affected.t27 picks them (the # changed specs and every spec that imports one through `use`, transitively); the graph is the diff --git a/scripts/gen_w387_lean.py b/scripts/gen_w387_lean.py deleted file mode 100644 index 8da2a68350..0000000000 --- a/scripts/gen_w387_lean.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env python3 -"""Append Wave Loop 387 generic theorems to Trinity.TernaryInference.lean.""" - -from pathlib import Path - -# The repository root, derived from where this file sits, not from the -# machine it was written on. This was an absolute path into one -# developer's home directory, so it could run for exactly one person -- -# and the secret-scan gate rejects that path, which is how it was found. -# (The old literal is deliberately not quoted here: a comment naming it -# trips the same gate.) -ROOT = Path(__file__).resolve().parent.parent -LEAN_FILE = ROOT / "proofs" / "lean4" / "Trinity" / "TernaryInference.lean" - - -def var_names(n: int) -> list[str]: - skip = {"at", "by", "do", "if", "in", "or", "to"} - names = [] - i = 0 - while len(names) < n: - if i < 26: - candidate = chr(ord("a") + i) - else: - first = chr(ord("a") + (i - 26) // 26) - second = chr(ord("a") + (i - 26) % 26) - candidate = first + second - i += 1 - if candidate in skip: - continue - names.append(candidate) - return names - - -def nest_mac(base: str, activations: list[str], weight: str) -> str: - expr = base - for act in activations: - expr = f"ternaryMac ({expr}) {act} (TernaryWeight.mk .{weight})" - return expr - - -def plus_chain(n: int) -> tuple[str, str]: - names = var_names(n) - lhs = nest_mac("0", names, "plus") - rhs = " + ".join(names) - return lhs, rhs - - -def minus_chain(n: int) -> tuple[str, str]: - names = var_names(n) - lhs = nest_mac("0", names, "minus") - rhs = " + ".join(names) - return lhs, f"-({rhs})" - - -def cancellation_chain(depth: int) -> tuple[str, str]: - expr = "x" - for i in range(depth): - w = "plus" if i % 2 == 0 else "minus" - expr = f"ternaryMac ({expr}) a (TernaryWeight.mk .{w})" - rhs = "x" if depth % 2 == 0 else "ternaryMac x a (TernaryWeight.mk .plus)" - return expr, rhs - - -def zero_weight_closure(before: int, after: int) -> tuple[str, str]: - total = before + 1 + after - names = var_names(total) - plus_idx = before - - def chain(acts: list[str]) -> str: - expr = "x" - for i, act in enumerate(acts): - w = "plus" if i == plus_idx else "zero" - expr = f"ternaryMac ({expr}) {act} (TernaryWeight.mk .{w})" - return expr - - lhs = chain(names) - reordered = list(names) - reordered[0], reordered[-1] = reordered[-1], reordered[0] - rhs = chain(reordered) - return lhs, rhs - - -def theorem_block(name: str, doc: str, vars: str, lhs: str, rhs: str) -> str: - return f"""/-- {doc} -/ - -theorem {name} ({vars} : Int) : - {lhs} = {rhs} := by - simp [ternaryMac_eq_acc_plus_mul, ternaryMul, ternaryDecode] - <;> try omega -""" - - -def main() -> None: - text = LEAN_FILE.read_text() - if "ternaryMacZeroWeightTwentyTwoPairClosureGeneric" in text: - print("W387 Lean theorems already present.") - return - - blocks: list[str] = [] - - # 65-variable plus accumulation - lhs, rhs = plus_chain(65) - vars = " ".join(var_names(65)) - blocks.append(theorem_block( - "ternaryMacAccumulateSixtyFivePlusGeneric", - "Generic theorem: 65-variable plus accumulation. **289 generic ∀ milestone.**", - vars, lhs, rhs, - )) - - # 64-variable minus accumulation - lhs, rhs = minus_chain(64) - vars = " ".join(var_names(64)) - blocks.append(theorem_block( - "ternaryMacAccumulateSixtyFourMinusGeneric", - "Generic theorem: 64-variable minus accumulation lattice.", - vars, lhs, rhs, - )) - - # depth-47 cancellation (odd depth leaves residual plus-weight MAC) - lhs, rhs = cancellation_chain(47) - blocks.append(theorem_block( - "ternaryMacQuadragintupleSeptemCancellationGeneric", - "Generic theorem: depth-47 activation cancellation. mac^47(x,a,[.plus,.minus,...]) = mac(x,a,.plus).", - "x a", lhs, rhs, - )) - - # zero-weight closure: 22 zero before + plus + 22 zero after - lhs, rhs = zero_weight_closure(22, 22) - vars = " ".join(var_names(45)) - blocks.append(theorem_block( - "ternaryMacZeroWeightTwentyTwoPairClosureGeneric", - "Generic theorem: 22 zero-weight MACs before and after a plus-weight MAC are transparent. **292 generic ∀ milestone.**", - f"x {vars}", lhs, rhs, - )) - - LEAN_FILE.write_text(text + "\n\n" + "\n\n".join(blocks) + "\n") - print("Appended W387 theorems to TernaryInference.lean") - - -if __name__ == "__main__": - main() From 110cdeb067e1536b9799d8befab330300c42f72c Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 21:00:17 +0700 Subject: [PATCH 3/8] claw: ledger_union tests kill the mutation survivors; two dead guards removed (WIP, Refs #10398) Co-Authored-By: Claude Opus 5.5 --- specs/claw/ledger_union.t27 | 238 ++++++++++++++++++++++++++++++++++-- 1 file changed, 230 insertions(+), 8 deletions(-) diff --git a/specs/claw/ledger_union.t27 b/specs/claw/ledger_union.t27 index b6358b8809..f8c2ab8e03 100644 --- a/specs/claw/ledger_union.t27 +++ b/specs/claw/ledger_union.t27 @@ -223,11 +223,9 @@ fn lu_refuse(out : []u8, cap : usize, stats : []u32, code : u32, side : u32, x : stats[3] = side; var w : usize = put_str(out, cap, 0, "refused: ", 9); w = put_str(out, cap, w, REASONS[(code) as usize], REASON_LENS[(code) as usize]); - if (side < 3) { - w = put_str(out, cap, w, " (", 2); - w = put_str(out, cap, w, SIDES[(side) as usize], SIDE_LENS[(side) as usize]); - w = put_str(out, cap, w, ")", 1); - } + w = put_str(out, cap, w, " (", 2); + w = put_str(out, cap, w, SIDES[(side) as usize], SIDE_LENS[(side) as usize]); + w = put_str(out, cap, w, ")", 1); if (ke > ks) { w = put_str(out, cap, w, ": ", 2); w = lu_copy(out, cap, w, x, ks, ke); @@ -420,10 +418,11 @@ fn lu_find(x : []const u8, n : usize, g : u8, fs : usize, fe : usize) -> usize { /// Does the seal x[ps..pe) have an S record naming a specs/port .t27 file that is also an F record? fn lu_seal_ok(x : []const u8, n : usize, ps : usize, pe : usize) -> bool { + // sp is the tab before the spec field; with no S record, or one without a spec field, it is + // the end of a line or of x, and the spec range is empty. const sp : usize = lu_find(x, n, 'S', ps, pe); - if (sp >= n) { return false; } const se : usize = lu_eol(x, n, sp); - if (sp >= se or x[sp] != '\t') { return false; } + if (sp >= se) { return false; } if (lu_port_spec(x, sp + 1, se) == false) { return false; } return lu_find(x, n, 'F', sp + 1, se) < n; } @@ -765,6 +764,168 @@ test a_union_too_big_for_the_buffer_says_so { assert st[0] == U_OVERFLOW; assert m == bn; assert m > 16; + // A buffer of exactly the union's size is enough, one byte less is not. + var fit : [256]u8 = undefined; + var k : usize = ledger_union(&b, bn, &b, bn, &b, bn, &fit, bn, &st); + assert st[0] == U_OK; + assert k == bn; + k = ledger_union(&b, bn, &b, bn, &b, bn, &fit, bn - 1, &st); + assert st[0] == U_OVERFLOW; +} + +test the_byte_helpers_stay_inside_their_ranges { + var x : [16]u8 = undefined; + const n : usize = put_str(&x, 16, 0, "abz.t27", 7); + var y : [16]u8 = undefined; + const yn : usize = put_str(&y, 16, 0, "abc", 3); + // A line search, a literal match and a comparison never read past their bounds. + assert lu_eol(&x, 3, 0) == 3; + assert lu_at(&x, 2, 0, "abz", 3) == false; + assert lu_at(&x, 3, 0, "abz", 3) == true; + assert lu_cmp(&x, 0, 2, &y, 0, 2) == 0; + assert lu_cmp(&x, 0, 2, &y, 0, yn) == -1; + // A shorter prefix sorts first, from either side. + assert lu_cmp(&x, 0, 2, &x, 0, 3) == -1; + assert lu_cmp(&x, 0, 3, &x, 0, 2) == 1; + // A suffix match needs the whole suffix inside the range. + assert lu_ends(&x, 3, n, ".t27", 4) == true; + assert lu_ends(&x, 5, n, ".t27", 4) == false; + // A copy writes only its range, and only below cap. + var d : [8]u8 = undefined; + var w : usize = put_str(&d, 8, 0, "zzzz", 4); + w = lu_copy(&d, 8, 0, &x, 3, 5); + assert w == 2; + assert d[2] == 'z'; + w = put_str(&d, 8, 0, "zzzz", 4); + w = lu_copy(&d, 2, 0, &x, 3, 5); + assert d[0] == '.'; + assert d[1] == 't'; +} + +test the_header_must_end_with_the_entries_key { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var x : [256]u8 = undefined; + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + // Another key in its place. + var xn : usize = put_str(&x, 256, 0, "{\n \"rows\": [\n", 13); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + var m : usize = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + assert st[3] == 0; + // A space between the key and the newline. + xn = put_str(&x, 256, 0, "{\n \"entries\": [ \n", 17); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + // An entry on the first line, with no header at all. + xn = fixture_line(&x, 256, 0, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + // No entries. + xn = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert st[0] == U_SHAPE; + // The shortest header, the key line alone, is enough. + xn = put_str(&x, 256, 0, "\"entries\": [\n", 13); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &x, xn, &x, xn, &out, 1024, &st); + assert st[0] == U_OK; + assert m == xn; +} + +test a_footer_theirs_changed_is_refused_and_a_frame_both_changed_agrees { + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + // The same entries with one more newline after the footer, which the shape allows. + var f : [256]u8 = undefined; + var fz : usize = put_str(&f, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + fz = fixture_line(&f, 256, fz, "aa", 2, "pass", 4, false); + fz = fixture_line(&f, 256, fz, "cc", 2, "pass", 4, true); + fz = put_str(&f, 256, fz, " ]\n}\n\n", 6); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + var m : usize = ledger_union(&b, bn, &f, fz, &b, bn, &out, 1024, &st); + assert st[0] == U_FRAME; + assert st[3] == 1; + m = ledger_union(&b, bn, &f, fz, &f, fz, &out, 1024, &st); + assert st[0] == U_OK; + assert m == fz; + assert lu_cmp(&out, 0, m, &f, 0, fz) == 0; + m = ledger_union(&b, bn, &b, bn, &f, fz, &out, 1024, &st); + assert st[0] == U_OK; + assert lu_cmp(&out, 0, m, &f, 0, fz) == 0; + // A header both sides changed the same way agrees too. + var h : [256]u8 = undefined; + var hn : usize = put_str(&h, 256, 0, "{\n \"max_not_pass\": 74,\n \"entries\": [\n", 37); + hn = fixture_line(&h, 256, hn, "aa", 2, "pass", 4, false); + hn = fixture_line(&h, 256, hn, "cc", 2, "pass", 4, true); + hn = put_str(&h, 256, hn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&b, bn, &h, hn, &h, hn, &out, 1024, &st); + assert st[0] == U_OK; + assert lu_cmp(&out, 0, m, &h, 0, hn) == 0; +} + +test additions_at_either_end_of_the_ledger_are_kept { + // Theirs adds a path before the first entry (a prefix of it, so it sorts first) and one after + // the last; ours adds one after the last too. The merge runs until all three sides are spent. + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [512]u8 = undefined; + var tn : usize = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "a", 1, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "dd", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + var o : [512]u8 = undefined; + var on : usize = put_str(&o, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 512, on, "aa", 2, "pass", 4, false); + on = fixture_line(&o, 512, on, "cc", 2, "pass", 4, false); + on = fixture_line(&o, 512, on, "ee", 2, "pass", 4, true); + on = put_str(&o, 512, on, FIX_FOOT, FIX_FOOT_LEN); + var x : [512]u8 = undefined; + var xn : usize = put_str(&x, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 512, xn, "a", 1, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "aa", 2, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "cc", 2, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "dd", 2, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "ee", 2, "pass", 4, true); + xn = put_str(&x, 512, xn, FIX_FOOT, FIX_FOOT_LEN); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &o, on, &out, 1024, &st); + assert st[0] == U_OK; + assert st[1] == 2; + assert st[2] == 5; + assert m == xn; + assert lu_cmp(&out, 0, m, &x, 0, xn) == 0; +} + +test both_sides_removing_one_entry_agree { + // Theirs and ours both dropped aa; ours also added bb, which sorts between aa and cc. + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [256]u8 = undefined; + var tn : usize = put_str(&t, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 256, tn, "cc", 2, "pass", 4, true); + tn = put_str(&t, 256, tn, FIX_FOOT, FIX_FOOT_LEN); + var o : [256]u8 = undefined; + const on : usize = fixture_pair(&o, 256, "bb", "pass", 4, "cc", "pass", 4); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &o, on, &out, 1024, &st); + assert st[0] == U_OK; + assert st[1] == 0; + assert st[2] == 2; + assert m == on; + assert lu_cmp(&out, 0, m, &o, 0, on) == 0; } test marker_lines_counts_only_full_markers_at_a_line_start { @@ -775,6 +936,13 @@ test marker_lines_counts_only_full_markers_at_a_line_start { assert marker_lines(&x, n) == 1; n = put_str(&x, 256, 0, "{\"a\": \"=======\"}\n", 17); assert marker_lines(&x, n) == 0; + // Eight in a row still start with seven. + n = put_str(&x, 256, 0, "========\n", 9); + assert marker_lines(&x, n) == 1; + // Only the first n bytes count: a marker cut short by n is not one. + n = put_str(&x, 256, 0, "aaaa\n<<<<<<<\n", 13); + assert marker_lines(&x, 8) == 0; + assert marker_lines(&x, n) == 1; } test a_port_spec_its_seal_and_the_ledger_are_in_scope { @@ -834,7 +1002,12 @@ test an_empty_or_unreadable_file_list_is_refused { var st : [2]u32 = undefined; var m : usize = pr_scope(&x, 0, &out, 256, &st); assert st[0] == SC_EMPTY; - var n : usize = put_str(&x, 128, 0, "X\tspecs/port/a.t27\n", 19); + assert m == 18; + assert buf_has(&out, m, "refused: no files\n", 18) == true; + var n : usize = put_str(&x, 128, 0, "\n", 1); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_EMPTY; + n = put_str(&x, 128, 0, "X\tspecs/port/a.t27\n", 19); m = pr_scope(&x, n, &out, 256, &st); assert st[0] == SC_BAD; n = put_str(&x, 128, 0, "F\tspecs/port/a.t27\textra\n", 25); @@ -843,6 +1016,55 @@ test an_empty_or_unreadable_file_list_is_refused { assert m > 0; } +test the_scope_rule_reads_each_record_exactly { + var x : [512]u8 = undefined; + var out : [256]u8 = undefined; + var st : [2]u32 = undefined; + // A blank line between records is skipped. + var n : usize = put_str(&x, 512, 0, "F\tspecs/port/a.t27\n\nF\tdocs/reports/t27b_expectations.json\n", 58); + var m : usize = pr_scope(&x, n, &out, 256, &st); + assert m == 0; + assert st[0] == SC_OK; + assert st[1] == 2; + // The first refusal is the one named, and the list is not read past it. + n = put_str(&x, 512, 0, "F\t.github/x.yml\nF\tspecs/port/a.t27\n", 35); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; + assert st[1] == 1; + assert buf_has(&out, m, ": .github/x.yml\n", 16) == true; + // An F record with no path. + n = put_str(&x, 512, 0, "F\t\n", 3); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_BAD; + // The ledger is matched whole: one byte off, or its name as a prefix, is outside. + n = put_str(&x, 512, 0, "F\tdocs/reports/t27b_expectations.jsox\n", 38); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; + n = put_str(&x, 512, 0, "F\tdocs/reports/t27b_expectations.json.bak\n", 42); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; + // A seal is a .json in the seal folder; either half alone is outside. + n = put_str(&x, 512, 0, "F\tdocs/x.json\n", 14); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; + n = put_str(&x, 512, 0, "F\t.trinity/seals/x.txt\n", 23); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_OUTSIDE; + // An S record needs a seal field and a spec field. + n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nS\tabc\n", 25); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_BAD; + n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nS\t\tspecs/port/a.t27\n", 39); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_BAD; + // A seal whose S record has no spec field, with and without the last newline. + n = put_str(&x, 512, 0, "F\t.trinity/seals/a.json\nS\t.trinity/seals/a.json\n", 48); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_SEAL; + m = pr_scope(&x, n - 1, &out, 256, &st); + assert st[0] == SC_SEAL; +} + test the_reason_tables_match_their_lengths { // Every reason ends in a letter: a length one short or one long would end elsewhere. var r : [128]u8 = undefined; From 162f7e4d3bd3585c1c928527e327b9ba13141bb0 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 21:01:58 +0700 Subject: [PATCH 4/8] claw: ledger_union.c lowered and sealed, 24/24 tests (Closes #10398) gen/c/claw/ledger_union.c is t27c gen-c of specs/claw/ledger_union.t27; the seal records 24 passing tests, 0 vacuous, run on the Railway lab. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/claw_ClawLedgerUnion.json | 33 + gen/c/claw/ledger_union.c | 1173 ++++++++++++++++++++++ 2 files changed, 1206 insertions(+) create mode 100644 .trinity/seals/claw_ClawLedgerUnion.json create mode 100644 gen/c/claw/ledger_union.c diff --git a/.trinity/seals/claw_ClawLedgerUnion.json b/.trinity/seals/claw_ClawLedgerUnion.json new file mode 100644 index 0000000000..14b6380523 --- /dev/null +++ b/.trinity/seals/claw_ClawLedgerUnion.json @@ -0,0 +1,33 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [ + { + "spec": "specs/claw/text.t27", + "spec_hash": "sha256:b65da69dff231c61a6d3a48558af9e17af42e1acbe3f4763348f1f0c2f118f21" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:6a22f33e2b9949516806cb0cd0eccb7c4014768b626ed5c488d20dcd76b311c3", + "gen_hash_rust": "sha256:5e23ce907b80e85072e9256c53177d5d080ac6113b69d7c1a482d65e5e47ed7b", + "gen_hash_verilog": "sha256:cfd43a4e7c531717afbf4960f0df2dcbd06b3a247f5bad3b18881b920214f549", + "gen_hash_zig": "sha256:7600b876617435503ecfd1f939b029d4e195a73113413ff01e37c7767b21b186", + "module": "ClawLedgerUnion", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T14:01:32Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:a9caacd65156c675f82a5c84aba1cd7c0d19050b239886551b30ff869a8d358f", + "spec_path": "specs/claw/ledger_union.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 24, + "total": 24, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/gen/c/claw/ledger_union.c b/gen/c/claw/ledger_union.c new file mode 100644 index 0000000000..1f22fb70fe --- /dev/null +++ b/gen/c/claw/ledger_union.c @@ -0,0 +1,1173 @@ +/* ============================================================================ + Generated from t27 spec: ClawLedgerUnion + DO NOT EDIT - generated by t27c gen-c + phi^2 + 1/phi^2 = 3 | TRINITY + ============================================================================ */ + +#include +#include +#include +#include +#define t27_assert(c, m) do { if (!(c)) { __builtin_trap(); } } while (0) +#define assert_eq(a, b) do { if ((a) != (b)) { __builtin_trap(); } } while (0) + +#ifndef CLAWLEDGERUNION_H +#define CLAWLEDGERUNION_H + +/* ------------------------------------------------------- + Constants + ------------------------------------------------------- */ + +#define LEDGER "docs/reports/t27b_expectations.json" +#define LEDGER_LEN 35 +#define ENTRY_HEAD " {\"path\": \"" +#define ENTRY_HEAD_LEN 12 +#define ENTRIES_OPEN "\"entries\": [" +#define ENTRIES_OPEN_LEN 12 +#define FOOTER " ]\n}" +#define FOOTER_LEN 4 +#define U_OK 0 +#define U_SHAPE 1 +#define U_UNSORTED 2 +#define U_BOTH_ADDED 3 +#define U_THEIRS_CHANGED 4 +#define U_FRAME 5 +#define U_OVERFLOW 6 +static const const char* REASONS[7] = { "ok", "a side is not in the ledger's shape", "a side's paths are not strictly ascending", "both sides added this path with different values", "theirs changed or removed an entry that ours does not match", "theirs changed the header or footer and ours differs", "the union does not fit the output buffer" }; +static const size_t REASON_LENS[7] = { 2, 35, 41, 48, 59, 52, 40 }; +static const const char* SIDES[3] = { "base", "theirs", "ours" }; +static const size_t SIDE_LENS[3] = { 4, 6, 4 }; +#define STATS_LEN 4 +#define SC_OK 0 +#define SC_OUTSIDE 1 +#define SC_SEAL 2 +#define SC_BAD 3 +#define SC_EMPTY 4 +static const const char* SCOPE_REASONS[5] = { "in scope", "a file outside specs/port, its seal and the ledger", "a seal whose spec is not a specs/port file of the same pull request", "an unreadable scope record", "no files" }; +static const size_t SCOPE_REASON_LENS[5] = { 8, 50, 67, 26, 8 }; +#define PORT_DIR "specs/port/" +#define PORT_DIR_LEN 11 +#define SEAL_DIR ".trinity/seals/" +#define SEAL_DIR_LEN 15 +#define FIX_HEAD "{\n \"entries\": [\n" +#define FIX_HEAD_LEN 16 +#define FIX_FOOT " ]\n}\n" +#define FIX_FOOT_LEN 5 +#define CHAT_A "specs/port/trios/crates/trios-chat/rings/CR-CHAT-02/src/epoch_rollover_wraparound_guard.t27" +#define CHAT_B "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/pcs_healing.t27" +#define CHAT_C "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/proposal_ref_collision.t27" +#define CHAT_D "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/proposal_validation.t27" +#define CHAT_E "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/ratchet_tree_resolution_guard.t27" +#define CHAT_F "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/ratchet_tree_update_integrity_guard.t27" +#define CHAT_G "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/unmerged_leaves_bound_guard.t27" + +/* ------------------------------------------------------- + Function prototypes + ------------------------------------------------------- */ + +size_t lu_eol(uint8_t* x, size_t n, size_t s); +bool lu_at(uint8_t* x, size_t n, size_t s, const char* p, size_t pn); +int32_t lu_cmp(uint8_t* a, size_t a0, size_t a1, uint8_t* b, size_t b0, size_t b1); +size_t lu_copy(uint8_t* dst, size_t cap, size_t w, uint8_t* x, size_t s, size_t e); +size_t lu_key_end(uint8_t* x, size_t e, size_t ks); +size_t lu_val_end(uint8_t* x, size_t n, size_t p); +uint32_t lu_frame(uint8_t* x, size_t n, size_t* fr); +size_t lu_refuse(uint8_t* out, size_t cap, uint32_t* stats, uint32_t code, uint32_t side, uint8_t* x, size_t ks, size_t ke); +size_t ledger_union(uint8_t* b, size_t bn, uint8_t* t, size_t tn, uint8_t* o, size_t on, uint8_t* out, size_t cap, uint32_t* stats); +uint32_t marker_lines(uint8_t* x, size_t n); +bool lu_ends(uint8_t* x, size_t s, size_t e, const char* p, size_t pn); +bool lu_port_spec(uint8_t* x, size_t s, size_t e); +size_t lu_tab(uint8_t* x, size_t s, size_t e); +size_t lu_find(uint8_t* x, size_t n, uint8_t g, size_t fs, size_t fe); +bool lu_seal_ok(uint8_t* x, size_t n, size_t ps, size_t pe); +size_t pr_scope(uint8_t* x, size_t n, uint8_t* out, size_t cap, uint32_t* stats); +size_t fixture_line(uint8_t* dst, size_t cap, size_t w, const char* path, size_t plen, const char* verdict, size_t vlen, bool last); +size_t fixture_pair(uint8_t* dst, size_t cap, const char* p1, const char* v1, size_t v1n, const char* p2, const char* v2, size_t v2n); +bool buf_has(uint8_t* buf, size_t buf_len, const char* needle, size_t needle_len); +size_t put_str(uint8_t* dst, size_t cap, size_t w, const char* s, size_t s_len); + +/* ------------------------------------------------------- + Function implementations + ------------------------------------------------------- */ + +size_t lu_eol(uint8_t* x, size_t n, size_t s) { + size_t i = s; + while (((i < n) && (x[i] != '\n'))) { + i += 1; + } + return i; +} + +bool lu_at(uint8_t* x, size_t n, size_t s, const char* p, size_t pn) { + if (((s + pn) > n)) { + return false; + } + size_t k = 0; + while ((k < pn)) { + if ((x[(s + k)] != p[k])) { + return false; + } + k += 1; + } + return true; +} + +int32_t lu_cmp(uint8_t* a, size_t a0, size_t a1, uint8_t* b, size_t b0, size_t b1) { + size_t i = 0; + while ((((a0 + i) < a1) && ((b0 + i) < b1))) { + if ((a[(a0 + i)] < b[(b0 + i)])) { + return -1; + } + if ((a[(a0 + i)] > b[(b0 + i)])) { + return 1; + } + i += 1; + } + if (((a0 + i) < a1)) { + return 1; + } + if (((b0 + i) < b1)) { + return -1; + } + return 0; +} + +size_t lu_copy(uint8_t* dst, size_t cap, size_t w, uint8_t* x, size_t s, size_t e) { + size_t i = s; + while ((i < e)) { + if (((w + (i - s)) < cap)) { + dst[(w + (i - s))] = x[i]; + } + i += 1; + } + return (w + (e - s)); +} + +size_t lu_key_end(uint8_t* x, size_t e, size_t ks) { + size_t i = ks; + while ((i < e)) { + if ((x[i] == '"')) { + return i; + } + if ((x[i] == 92)) { + return e; + } + i += 1; + } + return e; +} + +size_t lu_val_end(uint8_t* x, size_t n, size_t p) { + size_t e = lu_eol(x, n, p); + if ((x[(e - 1)] == ',')) { + return (e - 1); + } + return e; +} + +uint32_t lu_frame(uint8_t* x, size_t n, size_t* fr) { + size_t s = 0; + size_t hs = n; + while (((s < n) && (hs == n))) { + size_t e = lu_eol(x, n, s); + if (lu_at(x, e, s, ENTRY_HEAD, ENTRY_HEAD_LEN)) { + hs = s; + } else { + s = (e + 1); + } + } + if ((hs == n)) { + return U_SHAPE; + } + if ((hs < (ENTRIES_OPEN_LEN + 1))) { + return U_SHAPE; + } + if ((lu_at(x, (hs - 1), ((hs - 1) - ENTRIES_OPEN_LEN), ENTRIES_OPEN, ENTRIES_OPEN_LEN) == false)) { + return U_SHAPE; + } + size_t p = hs; + size_t fe = n; + size_t pks = 0; + size_t pke = 0; + bool first = true; + bool comma = true; + while (((p < n) && (fe == n))) { + size_t e = lu_eol(x, n, p); + if ((lu_at(x, e, p, ENTRY_HEAD, ENTRY_HEAD_LEN) == false)) { + fe = p; + } else { + if ((comma == false)) { + return U_SHAPE; + } + size_t ks = (p + ENTRY_HEAD_LEN); + size_t ke = lu_key_end(x, e, ks); + if (((ke >= e) || (ke == ks))) { + return U_SHAPE; + } + if ((x[(e - 1)] == ',')) { + if ((x[(e - 2)] != '}')) { + return U_SHAPE; + } + comma = true; + } else if ((x[(e - 1)] == '}')) { + comma = false; + } else { + return U_SHAPE; + } + if ((first == false)) { + if ((lu_cmp(x, pks, pke, x, ks, ke) >= 0)) { + return U_UNSORTED; + } + } + pks = ks; + pke = ke; + first = false; + p = (e + 1); + } + } + if (((fe == n) || comma)) { + return U_SHAPE; + } + if ((lu_at(x, n, fe, FOOTER, FOOTER_LEN) == false)) { + return U_SHAPE; + } + size_t t = (fe + FOOTER_LEN); + while ((t < n)) { + if ((((x[t] != '\n') && (x[t] != ' ')) && (x[t] != 13))) { + return U_SHAPE; + } + t += 1; + } + fr[0] = hs; + fr[1] = fe; + return U_OK; +} + +size_t lu_refuse(uint8_t* out, size_t cap, uint32_t* stats, uint32_t code, uint32_t side, uint8_t* x, size_t ks, size_t ke) { + stats[0] = code; + stats[3] = side; + size_t w = put_str(out, cap, 0, "refused: ", 9); + w = put_str(out, cap, w, REASONS[((size_t)(code))], REASON_LENS[((size_t)(code))]); + w = put_str(out, cap, w, " (", 2); + w = put_str(out, cap, w, SIDES[((size_t)(side))], SIDE_LENS[((size_t)(side))]); + w = put_str(out, cap, w, ")", 1); + if ((ke > ks)) { + w = put_str(out, cap, w, ": ", 2); + w = lu_copy(out, cap, w, x, ks, ke); + } + return put_str(out, cap, w, "\n", 1); +} + +size_t ledger_union(uint8_t* b, size_t bn, uint8_t* t, size_t tn, uint8_t* o, size_t on, uint8_t* out, size_t cap, uint32_t* stats) { + size_t k = 0; + while ((k < STATS_LEN)) { + stats[k] = 0; + k += 1; + } + stats[3] = 3; + size_t fb[2] = {0}; + size_t ft[2] = {0}; + size_t fo[2] = {0}; + uint32_t c = lu_frame(b, bn, &fb); + if ((c != U_OK)) { + return lu_refuse(out, cap, stats, c, 0, b, 0, 0); + } + c = lu_frame(t, tn, &ft); + if ((c != U_OK)) { + return lu_refuse(out, cap, stats, c, 1, t, 0, 0); + } + c = lu_frame(o, on, &fo); + if ((c != U_OK)) { + return lu_refuse(out, cap, stats, c, 2, o, 0, 0); + } + if (((lu_cmp(t, 0, ft[0], b, 0, fb[0]) != 0) && (lu_cmp(t, 0, ft[0], o, 0, fo[0]) != 0))) { + return lu_refuse(out, cap, stats, U_FRAME, 1, t, 0, 0); + } + if (((lu_cmp(t, ft[1], tn, b, fb[1], bn) != 0) && (lu_cmp(t, ft[1], tn, o, fo[1], on) != 0))) { + return lu_refuse(out, cap, stats, U_FRAME, 1, t, 0, 0); + } + size_t w = lu_copy(out, cap, 0, o, 0, fo[0]); + size_t pb = fb[0]; + size_t pt = ft[0]; + size_t po = fo[0]; + uint32_t count = 0; + uint32_t added = 0; + while ((((pb < fb[1]) || (pt < ft[1])) || (po < fo[1]))) { + bool hb = (pb < fb[1]); + bool ht = (pt < ft[1]); + bool ho = (po < fo[1]); + size_t kbs = 0; + size_t kbe = 0; + size_t kts = 0; + size_t kte = 0; + size_t kos = 0; + size_t koe = 0; + if (hb) { + kbs = (pb + ENTRY_HEAD_LEN); + kbe = lu_key_end(b, bn, kbs); + } + if (ht) { + kts = (pt + ENTRY_HEAD_LEN); + kte = lu_key_end(t, tn, kts); + } + if (ho) { + kos = (po + ENTRY_HEAD_LEN); + koe = lu_key_end(o, on, kos); + } + uint32_t m = 3; + if (hb) { + m = 0; + } + if (ht) { + if ((m == 3)) { + m = 1; + } else if ((lu_cmp(t, kts, kte, b, kbs, kbe) < 0)) { + m = 1; + } + } + if (ho) { + if ((m == 3)) { + m = 2; + } else if ((m == 0)) { + if ((lu_cmp(o, kos, koe, b, kbs, kbe) < 0)) { + m = 2; + } + } else if ((lu_cmp(o, kos, koe, t, kts, kte) < 0)) { + m = 2; + } + } + bool in_b = false; + bool in_t = false; + bool in_o = false; + if ((m == 0)) { + in_b = true; + in_t = (ht && (lu_cmp(t, kts, kte, b, kbs, kbe) == 0)); + in_o = (ho && (lu_cmp(o, kos, koe, b, kbs, kbe) == 0)); + } else if ((m == 1)) { + in_t = true; + in_o = (ho && (lu_cmp(o, kos, koe, t, kts, kte) == 0)); + } else { + in_o = true; + } + size_t vbe = 0; + size_t vte = 0; + size_t voe = 0; + if (in_b) { + vbe = lu_val_end(b, bn, pb); + } + if (in_t) { + vte = lu_val_end(t, tn, pt); + } + if (in_o) { + voe = lu_val_end(o, on, po); + } + bool same_tb = (in_t == in_b); + if ((same_tb && in_b)) { + same_tb = (lu_cmp(t, pt, vte, b, pb, vbe) == 0); + } + bool same_ot = (in_o == in_t); + if ((same_ot && in_t)) { + same_ot = (lu_cmp(o, po, voe, t, pt, vte) == 0); + } + uint32_t emit = 3; + if ((same_tb || same_ot)) { + if (in_o) { + emit = 2; + } + } else if (((in_b == false) && (in_o == false))) { + emit = 1; + added += 1; + } else if ((in_b == false)) { + return lu_refuse(out, cap, stats, U_BOTH_ADDED, 1, t, kts, kte); + } else { + return lu_refuse(out, cap, stats, U_THEIRS_CHANGED, 1, b, kbs, kbe); + } + if ((emit != 3)) { + if ((count > 0)) { + w = put_str(out, cap, w, ",\n", 2); + } + if ((emit == 2)) { + w = lu_copy(out, cap, w, o, po, voe); + } else { + w = lu_copy(out, cap, w, t, pt, vte); + } + count += 1; + } + if (in_b) { + pb = (lu_eol(b, bn, pb) + 1); + } + if (in_t) { + pt = (lu_eol(t, tn, pt) + 1); + } + if (in_o) { + po = (lu_eol(o, on, po) + 1); + } + } + w = put_str(out, cap, w, "\n", 1); + w = lu_copy(out, cap, w, o, fo[1], on); + stats[1] = added; + stats[2] = count; + if ((w > cap)) { + stats[0] = U_OVERFLOW; + } + return w; +} + +uint32_t marker_lines(uint8_t* x, size_t n) { + uint32_t c = 0; + size_t s = 0; + while ((s < n)) { + size_t e = lu_eol(x, n, s); + if (((e - s) >= 7)) { + uint8_t m = x[s]; + if (((((m == '<') || (m == '=')) || (m == '>')) || (m == '|'))) { + size_t j = 1; + while (((j < 7) && (x[(s + j)] == m))) { + j += 1; + } + if ((j == 7)) { + c += 1; + } + } + } + s = (e + 1); + } + return c; +} + +bool lu_ends(uint8_t* x, size_t s, size_t e, const char* p, size_t pn) { + if (((e - s) < pn)) { + return false; + } + return lu_at(x, e, (e - pn), p, pn); +} + +bool lu_port_spec(uint8_t* x, size_t s, size_t e) { + if ((lu_at(x, e, s, PORT_DIR, PORT_DIR_LEN) == false)) { + return false; + } + if ((lu_ends(x, s, e, ".t27", 4) == false)) { + return false; + } + size_t i = s; + while (((i + 1) < e)) { + if (((x[i] == '.') && (x[(i + 1)] == '.'))) { + return false; + } + i += 1; + } + return true; +} + +size_t lu_tab(uint8_t* x, size_t s, size_t e) { + size_t i = s; + while (((i < e) && (x[i] != '\t'))) { + i += 1; + } + return i; +} + +size_t lu_find(uint8_t* x, size_t n, uint8_t g, size_t fs, size_t fe) { + size_t s = 0; + while ((s < n)) { + size_t e = lu_eol(x, n, s); + if ((((e > (s + 1)) && (x[s] == g)) && (x[(s + 1)] == '\t'))) { + size_t t = lu_tab(x, (s + 2), e); + if ((lu_cmp(x, (s + 2), t, x, fs, fe) == 0)) { + return t; + } + } + s = (e + 1); + } + return n; +} + +bool lu_seal_ok(uint8_t* x, size_t n, size_t ps, size_t pe) { + size_t sp = lu_find(x, n, 'S', ps, pe); + size_t se = lu_eol(x, n, sp); + if ((sp >= se)) { + return false; + } + if ((lu_port_spec(x, (sp + 1), se) == false)) { + return false; + } + return (lu_find(x, n, 'F', (sp + 1), se) < n); +} + +size_t pr_scope(uint8_t* x, size_t n, uint8_t* out, size_t cap, uint32_t* stats) { + stats[0] = SC_OK; + stats[1] = 0; + uint32_t code = SC_OK; + size_t bs = 0; + size_t be = 0; + size_t s = 0; + while (((s < n) && (code == SC_OK))) { + size_t e = lu_eol(x, n, s); + if ((e > s)) { + bool tagged = ((e > (s + 2)) && (x[(s + 1)] == '\t')); + if ((tagged && (x[s] == 'F'))) { + stats[1] += 1; + size_t ps = (s + 2); + bool ledger = (((e - ps) == LEDGER_LEN) && lu_at(x, e, ps, LEDGER, LEDGER_LEN)); + if ((lu_tab(x, ps, e) < e)) { + code = SC_BAD; + } else if (((ledger == false) && (lu_port_spec(x, ps, e) == false))) { + if ((lu_at(x, e, ps, SEAL_DIR, SEAL_DIR_LEN) && lu_ends(x, ps, e, ".json", 5))) { + if ((lu_seal_ok(x, n, ps, e) == false)) { + code = SC_SEAL; + } + } else { + code = SC_OUTSIDE; + } + } + if ((code != SC_OK)) { + bs = ps; + be = e; + } + } else if ((tagged && (x[s] == 'S'))) { + size_t t = lu_tab(x, (s + 2), e); + if (((t >= e) || (t == (s + 2)))) { + code = SC_BAD; + bs = s; + be = e; + } + } else { + code = SC_BAD; + bs = s; + be = e; + } + } + s = (e + 1); + } + if (((code == SC_OK) && (stats[1] == 0))) { + code = SC_EMPTY; + } + if ((code == SC_OK)) { + return 0; + } + stats[0] = code; + size_t w = put_str(out, cap, 0, "refused: ", 9); + w = put_str(out, cap, w, SCOPE_REASONS[((size_t)(code))], SCOPE_REASON_LENS[((size_t)(code))]); + if ((be > bs)) { + w = put_str(out, cap, w, ": ", 2); + w = lu_copy(out, cap, w, x, bs, be); + } + return put_str(out, cap, w, "\n", 1); +} + +size_t fixture_line(uint8_t* dst, size_t cap, size_t w, const char* path, size_t plen, const char* verdict, size_t vlen, bool last) { + size_t x = put_str(dst, cap, w, ENTRY_HEAD, ENTRY_HEAD_LEN); + x = put_str(dst, cap, x, path, plen); + x = put_str(dst, cap, x, "\", \"t27b\": \"", 12); + x = put_str(dst, cap, x, verdict, vlen); + x = put_str(dst, cap, x, "\"}", 2); + if ((last == false)) { + x = put_str(dst, cap, x, ",", 1); + } + return put_str(dst, cap, x, "\n", 1); +} + +size_t fixture_pair(uint8_t* dst, size_t cap, const char* p1, const char* v1, size_t v1n, const char* p2, const char* v2, size_t v2n) { + size_t w = put_str(dst, cap, 0, FIX_HEAD, FIX_HEAD_LEN); + w = fixture_line(dst, cap, w, p1, 2, v1, v1n, false); + w = fixture_line(dst, cap, w, p2, 2, v2, v2n, true); + return put_str(dst, cap, w, FIX_FOOT, FIX_FOOT_LEN); +} + +bool buf_has(uint8_t* buf, size_t buf_len, const char* needle, size_t needle_len) { + if ((needle_len > buf_len)) { + return false; + } + size_t start = 0; + while (((start + needle_len) <= buf_len)) { + size_t k = 0; + size_t diff = 0; + while ((k < needle_len)) { + if ((buf[(start + k)] != needle[k])) { + diff += 1; + } + k += 1; + } + if ((diff == 0)) { + return true; + } + start += 1; + } + return false; +} + +size_t put_str(uint8_t* dst, size_t cap, size_t w, const char* s, size_t s_len) { + size_t i = 0; + while ((i < s_len)) { + if (((w + i) < cap)) { + dst[(w + i)] = s[i]; + } + i += 1; + } + return (w + s_len); +} + +/* ------------------------------------------------------- + Tests + ------------------------------------------------------- */ + +void test_replays_the_ledger_conflict_of_pr_9945_in_the_10098_batch(void) { + uint8_t b[2048] = {0}; + size_t bn = put_str(&b, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + bn = fixture_line(&b, 2048, bn, CHAT_A, 91, "pass", 4, false); + bn = fixture_line(&b, 2048, bn, CHAT_E, 89, "pass", 4, false); + bn = fixture_line(&b, 2048, bn, CHAT_G, 87, "pass", 4, true); + bn = put_str(&b, 2048, bn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t t[2048] = {0}; + size_t tn = put_str(&t, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 2048, tn, CHAT_A, 91, "pass", 4, false); + tn = fixture_line(&t, 2048, tn, CHAT_D, 79, "pass", 4, false); + tn = fixture_line(&t, 2048, tn, CHAT_E, 89, "pass", 4, false); + tn = fixture_line(&t, 2048, tn, CHAT_G, 87, "pass", 4, true); + tn = put_str(&t, 2048, tn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t o[2048] = {0}; + size_t on = put_str(&o, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 2048, on, CHAT_A, 91, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_B, 71, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_C, 82, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_E, 89, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_F, 95, "pass", 4, false); + on = fixture_line(&o, 2048, on, CHAT_G, 87, "pass", 4, true); + on = put_str(&o, 2048, on, FIX_FOOT, FIX_FOOT_LEN); + uint8_t x[2048] = {0}; + size_t xn = put_str(&x, 2048, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 2048, xn, CHAT_A, 91, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_B, 71, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_C, 82, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_D, 79, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_E, 89, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_F, 95, "pass", 4, false); + xn = fixture_line(&x, 2048, xn, CHAT_G, 87, "pass", 4, true); + xn = put_str(&x, 2048, xn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[4096] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &o, on, &out, 4096, &st); + assert((st[0] == U_OK)); + assert((st[1] == 1)); + assert((st[2] == 7)); + assert((m == xn)); + assert((lu_cmp(&out, 0, m, &x, 0, xn) == 0)); + assert((marker_lines(&out, m) == 0)); +} + +void test_the_same_addition_on_both_sides_is_written_once(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[512] = {0}; + size_t tn = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "bb", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &t, tn, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((st[1] == 0)); + assert((st[2] == 3)); + assert((m == tn)); + assert((lu_cmp(&out, 0, m, &t, 0, tn) == 0)); +} + +void test_ours_progress_wins_where_theirs_kept_the_base_entry(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "unimplemented", 13, "cc", "pass", 4); + uint8_t o[256] = {0}; + size_t on = fixture_pair(&o, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &b, bn, &o, on, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((st[2] == 2)); + assert((m == on)); + assert((lu_cmp(&out, 0, m, &o, 0, on) == 0)); +} + +void test_ours_removing_an_entry_theirs_kept_stays_removed(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t o[256] = {0}; + size_t on = put_str(&o, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 256, on, "cc", 2, "pass", 4, true); + on = put_str(&o, 256, on, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &b, bn, &o, on, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((st[2] == 1)); + assert((lu_cmp(&out, 0, m, &o, 0, on) == 0)); +} + +void test_two_additions_of_one_path_with_different_verdicts_are_refused(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[512] = {0}; + size_t tn = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "bb", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t o[512] = {0}; + size_t on = put_str(&o, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 512, on, "aa", 2, "pass", 4, false); + on = fixture_line(&o, 512, on, "bb", 2, "unimplemented", 13, false); + on = fixture_line(&o, 512, on, "cc", 2, "pass", 4, true); + on = put_str(&o, 512, on, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &o, on, &out, 1024, &st); + assert((st[0] == U_BOTH_ADDED)); + assert((st[3] == 1)); + assert((buf_has(&out, m, "refused: both sides added this path with different values (theirs): bb\n", 71) == true)); +} + +void test_theirs_changing_a_base_entry_is_refused(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[256] = {0}; + size_t tn = fixture_pair(&t, 256, "aa", "pass", 4, "cc", "unimplemented", 13); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &b, bn, &out, 1024, &st); + assert((st[0] == U_THEIRS_CHANGED)); + assert((buf_has(&out, m, "(theirs): cc\n", 13) == true)); +} + +void test_theirs_removing_a_base_entry_is_refused(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[256] = {0}; + size_t tn = put_str(&t, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 256, tn, "aa", 2, "pass", 4, true); + tn = put_str(&t, 256, tn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &b, bn, &out, 1024, &st); + assert((st[0] == U_THEIRS_CHANGED)); + assert((buf_has(&out, m, ": cc\n", 5) == true)); +} + +void test_theirs_and_ours_making_the_same_change_agree(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[256] = {0}; + size_t tn = fixture_pair(&t, 256, "aa", "pass", 4, "cc", "unimplemented", 13); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &t, tn, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((lu_cmp(&out, 0, m, &t, 0, tn) == 0)); +} + +void test_a_header_theirs_changed_is_refused_and_one_ours_changed_is_kept(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t h[256] = {0}; + size_t hn = put_str(&h, 256, 0, "{\n \"max_not_pass\": 74,\n \"entries\": [\n", 37); + hn = fixture_line(&h, 256, hn, "aa", 2, "pass", 4, false); + hn = fixture_line(&h, 256, hn, "cc", 2, "pass", 4, true); + hn = put_str(&h, 256, hn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &h, hn, &b, bn, &out, 1024, &st); + assert((st[0] == U_FRAME)); + m = ledger_union(&b, bn, &b, bn, &h, hn, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((m == hn)); + assert((lu_cmp(&out, 0, m, &h, 0, hn) == 0)); +} + +void test_a_side_out_of_order_or_with_a_duplicate_is_refused(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t u[256] = {0}; + size_t un = fixture_pair(&u, 256, "cc", "pass", 4, "aa", "pass", 4); + uint8_t d[256] = {0}; + size_t dn = fixture_pair(&d, 256, "aa", "pass", 4, "aa", "pass", 4); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &u, un, &b, bn, &out, 1024, &st); + assert((st[0] == U_UNSORTED)); + assert((st[3] == 1)); + m = ledger_union(&b, bn, &b, bn, &d, dn, &out, 1024, &st); + assert((st[0] == U_UNSORTED)); + assert((st[3] == 2)); + assert((buf_has(&out, m, "(ours)", 6) == true)); +} + +void test_a_side_not_in_the_ledger_shape_is_refused(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + uint8_t x[256] = {0}; + size_t xn = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, false); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + size_t m = ledger_union(&b, bn, &x, xn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + xn = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = fixture_line(&x, 256, xn, "cc", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&b, bn, &x, xn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + xn = fixture_pair(&x, 256, "aa", "pass", 4, "c\\", "pass", 4); + m = ledger_union(&b, bn, &x, xn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + m = ledger_union(&b, (bn - FIX_FOOT_LEN), &b, bn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + assert((st[3] == 0)); + xn = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = put_str(&x, 256, xn, "<<<<<<< HEAD\n", 13); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&b, bn, &b, bn, &x, xn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + assert((buf_has(&out, m, "refused: a side is not in the ledger's shape (ours)\n", 52) == true)); +} + +void test_a_union_too_big_for_the_buffer_says_so(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t out[16] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &b, bn, &b, bn, &out, 16, &st); + assert((st[0] == U_OVERFLOW)); + assert((m == bn)); + assert((m > 16)); + uint8_t fit[256] = {0}; + size_t k = ledger_union(&b, bn, &b, bn, &b, bn, &fit, bn, &st); + assert((st[0] == U_OK)); + assert((k == bn)); + k = ledger_union(&b, bn, &b, bn, &b, bn, &fit, (bn - 1), &st); + assert((st[0] == U_OVERFLOW)); +} + +void test_the_byte_helpers_stay_inside_their_ranges(void) { + uint8_t x[16] = {0}; + size_t n = put_str(&x, 16, 0, "abz.t27", 7); + uint8_t y[16] = {0}; + size_t yn = put_str(&y, 16, 0, "abc", 3); + assert((lu_eol(&x, 3, 0) == 3)); + assert((lu_at(&x, 2, 0, "abz", 3) == false)); + assert((lu_at(&x, 3, 0, "abz", 3) == true)); + assert((lu_cmp(&x, 0, 2, &y, 0, 2) == 0)); + assert((lu_cmp(&x, 0, 2, &y, 0, yn) == -1)); + assert((lu_cmp(&x, 0, 2, &x, 0, 3) == -1)); + assert((lu_cmp(&x, 0, 3, &x, 0, 2) == 1)); + assert((lu_ends(&x, 3, n, ".t27", 4) == true)); + assert((lu_ends(&x, 5, n, ".t27", 4) == false)); + uint8_t d[8] = {0}; + size_t w = put_str(&d, 8, 0, "zzzz", 4); + w = lu_copy(&d, 8, 0, &x, 3, 5); + assert((w == 2)); + assert((d[2] == 'z')); + w = put_str(&d, 8, 0, "zzzz", 4); + w = lu_copy(&d, 2, 0, &x, 3, 5); + assert((d[0] == '.')); + assert((d[1] == 't')); +} + +void test_the_header_must_end_with_the_entries_key(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t x[256] = {0}; + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t xn = put_str(&x, 256, 0, "{\n \"rows\": [\n", 13); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + size_t m = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + assert((st[3] == 0)); + xn = put_str(&x, 256, 0, "{\n \"entries\": [ \n", 17); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + xn = fixture_line(&x, 256, 0, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + xn = put_str(&x, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &b, bn, &b, bn, &out, 1024, &st); + assert((st[0] == U_SHAPE)); + xn = put_str(&x, 256, 0, "\"entries\": [\n", 13); + xn = fixture_line(&x, 256, xn, "aa", 2, "pass", 4, true); + xn = put_str(&x, 256, xn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&x, xn, &x, xn, &x, xn, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((m == xn)); +} + +void test_a_footer_theirs_changed_is_refused_and_a_frame_both_changed_agrees(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t f[256] = {0}; + size_t fz = put_str(&f, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + fz = fixture_line(&f, 256, fz, "aa", 2, "pass", 4, false); + fz = fixture_line(&f, 256, fz, "cc", 2, "pass", 4, true); + fz = put_str(&f, 256, fz, " ]\n}\n\n", 6); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &f, fz, &b, bn, &out, 1024, &st); + assert((st[0] == U_FRAME)); + assert((st[3] == 1)); + m = ledger_union(&b, bn, &f, fz, &f, fz, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((m == fz)); + assert((lu_cmp(&out, 0, m, &f, 0, fz) == 0)); + m = ledger_union(&b, bn, &b, bn, &f, fz, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((lu_cmp(&out, 0, m, &f, 0, fz) == 0)); + uint8_t h[256] = {0}; + size_t hn = put_str(&h, 256, 0, "{\n \"max_not_pass\": 74,\n \"entries\": [\n", 37); + hn = fixture_line(&h, 256, hn, "aa", 2, "pass", 4, false); + hn = fixture_line(&h, 256, hn, "cc", 2, "pass", 4, true); + hn = put_str(&h, 256, hn, FIX_FOOT, FIX_FOOT_LEN); + m = ledger_union(&b, bn, &h, hn, &h, hn, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((lu_cmp(&out, 0, m, &h, 0, hn) == 0)); +} + +void test_additions_at_either_end_of_the_ledger_are_kept(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[512] = {0}; + size_t tn = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "a", 1, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "dd", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t o[512] = {0}; + size_t on = put_str(&o, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + on = fixture_line(&o, 512, on, "aa", 2, "pass", 4, false); + on = fixture_line(&o, 512, on, "cc", 2, "pass", 4, false); + on = fixture_line(&o, 512, on, "ee", 2, "pass", 4, true); + on = put_str(&o, 512, on, FIX_FOOT, FIX_FOOT_LEN); + uint8_t x[512] = {0}; + size_t xn = put_str(&x, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + xn = fixture_line(&x, 512, xn, "a", 1, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "aa", 2, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "cc", 2, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "dd", 2, "pass", 4, false); + xn = fixture_line(&x, 512, xn, "ee", 2, "pass", 4, true); + xn = put_str(&x, 512, xn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &o, on, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((st[1] == 2)); + assert((st[2] == 5)); + assert((m == xn)); + assert((lu_cmp(&out, 0, m, &x, 0, xn) == 0)); +} + +void test_both_sides_removing_one_entry_agree(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[256] = {0}; + size_t tn = put_str(&t, 256, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 256, tn, "cc", 2, "pass", 4, true); + tn = put_str(&t, 256, tn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t o[256] = {0}; + size_t on = fixture_pair(&o, 256, "bb", "pass", 4, "cc", "pass", 4); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &o, on, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((st[1] == 0)); + assert((st[2] == 2)); + assert((m == on)); + assert((lu_cmp(&out, 0, m, &o, 0, on) == 0)); +} + +void test_marker_lines_counts_only_full_markers_at_a_line_start(void) { + uint8_t x[256] = {0}; + size_t n = put_str(&x, 256, 0, "<<<<<<< HEAD\na\n=======\nb\n>>>>>>> pr\n", 36); + assert((marker_lines(&x, n) == 3)); + n = put_str(&x, 256, 0, "<<<<<< six\n a =======\n|||||||\n", 30); + assert((marker_lines(&x, n) == 1)); + n = put_str(&x, 256, 0, "{\"a\": \"=======\"}\n", 17); + assert((marker_lines(&x, n) == 0)); + n = put_str(&x, 256, 0, "========\n", 9); + assert((marker_lines(&x, n) == 1)); + n = put_str(&x, 256, 0, "aaaa\n<<<<<<<\n", 13); + assert((marker_lines(&x, 8) == 0)); + assert((marker_lines(&x, n) == 1)); +} + +void test_a_port_spec_its_seal_and_the_ledger_are_in_scope(void) { + uint8_t x[512] = {0}; + size_t n = put_str(&x, 512, 0, "F\tdocs/reports/t27b_expectations.json\n", 38); + n = put_str(&x, 512, n, "F\tspecs/port/a/b.t27\n", 21); + n = put_str(&x, 512, n, "F\t.trinity/seals/port::a::b.json\n", 33); + n = put_str(&x, 512, n, "S\t.trinity/seals/port::a::b.json\tspecs/port/a/b.t27\n", 52); + uint8_t out[256] = {0}; + uint32_t st[2] = {0}; + size_t m = pr_scope(&x, n, &out, 256, &st); + assert((m == 0)); + assert((st[0] == SC_OK)); + assert((st[1] == 3)); +} + +void test_a_file_outside_the_port_shape_is_named(void) { + uint8_t x[512] = {0}; + size_t n = put_str(&x, 512, 0, "F\tspecs/port/a/b.t27\n", 21); + n = put_str(&x, 512, n, "F\t.github/workflows/x.yml\n", 26); + uint8_t out[256] = {0}; + uint32_t st[2] = {0}; + size_t m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); + assert((buf_has(&out, m, ": .github/workflows/x.yml\n", 26) == true)); + n = put_str(&x, 512, 0, "F\tspecs/port/../claw/text.t27\n", 30); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); + n = put_str(&x, 512, 0, "F\tspecs/port/a/b.zig\n", 21); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); +} + +void test_a_seal_counts_only_with_its_own_port_spec(void) { + uint8_t x[512] = {0}; + uint8_t out[256] = {0}; + uint32_t st[2] = {0}; + size_t n = put_str(&x, 512, 0, "F\t.trinity/seals/port::a::b.json\n", 33); + size_t m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_SEAL)); + n = put_str(&x, 512, n, "S\t.trinity/seals/port::a::b.json\tspecs/port/a/b.t27\n", 52); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_SEAL)); + n = put_str(&x, 512, 0, "F\t.trinity/seals/claw_X.json\nF\tspecs/claw/x.t27\n", 48); + n = put_str(&x, 512, n, "S\t.trinity/seals/claw_X.json\tspecs/claw/x.t27\n", 46); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_SEAL)); + assert((buf_has(&out, m, ": .trinity/seals/claw_X.json\n", 29) == true)); +} + +void test_an_empty_or_unreadable_file_list_is_refused(void) { + uint8_t x[128] = {0}; + uint8_t out[256] = {0}; + uint32_t st[2] = {0}; + size_t m = pr_scope(&x, 0, &out, 256, &st); + assert((st[0] == SC_EMPTY)); + assert((m == 18)); + assert((buf_has(&out, m, "refused: no files\n", 18) == true)); + size_t n = put_str(&x, 128, 0, "\n", 1); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_EMPTY)); + n = put_str(&x, 128, 0, "X\tspecs/port/a.t27\n", 19); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_BAD)); + n = put_str(&x, 128, 0, "F\tspecs/port/a.t27\textra\n", 25); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_BAD)); + assert((m > 0)); +} + +void test_the_scope_rule_reads_each_record_exactly(void) { + uint8_t x[512] = {0}; + uint8_t out[256] = {0}; + uint32_t st[2] = {0}; + size_t n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\n\nF\tdocs/reports/t27b_expectations.json\n", 58); + size_t m = pr_scope(&x, n, &out, 256, &st); + assert((m == 0)); + assert((st[0] == SC_OK)); + assert((st[1] == 2)); + n = put_str(&x, 512, 0, "F\t.github/x.yml\nF\tspecs/port/a.t27\n", 35); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); + assert((st[1] == 1)); + assert((buf_has(&out, m, ": .github/x.yml\n", 16) == true)); + n = put_str(&x, 512, 0, "F\t\n", 3); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_BAD)); + n = put_str(&x, 512, 0, "F\tdocs/reports/t27b_expectations.jsox\n", 38); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); + n = put_str(&x, 512, 0, "F\tdocs/reports/t27b_expectations.json.bak\n", 42); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); + n = put_str(&x, 512, 0, "F\tdocs/x.json\n", 14); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); + n = put_str(&x, 512, 0, "F\t.trinity/seals/x.txt\n", 23); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_OUTSIDE)); + n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nS\tabc\n", 25); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_BAD)); + n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nS\t\tspecs/port/a.t27\n", 39); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_BAD)); + n = put_str(&x, 512, 0, "F\t.trinity/seals/a.json\nS\t.trinity/seals/a.json\n", 48); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_SEAL)); + m = pr_scope(&x, (n - 1), &out, 256, &st); + assert((st[0] == SC_SEAL)); +} + +void test_the_reason_tables_match_their_lengths(void) { + uint8_t r[128] = {0}; + size_t i = 0; + while ((i < 7)) { + size_t k = put_str(&r, 128, 0, REASONS[i], REASON_LENS[i]); + assert(((r[(k - 1)] >= 'a') && (r[(k - 1)] <= 'z'))); + i += 1; + } + i = 0; + while ((i < 5)) { + size_t k = put_str(&r, 128, 0, SCOPE_REASONS[i], SCOPE_REASON_LENS[i]); + assert(((r[(k - 1)] >= 'a') && (r[(k - 1)] <= 'z'))); + i += 1; + } + size_t s = put_str(&r, 128, 0, SCOPE_REASONS[2], SCOPE_REASON_LENS[2]); + assert((buf_has(&r, s, "same pull request", 17) == true)); +} + + +/* ------------------------------------------------------- + Test runner (compile with -DT27_TEST_MAIN to execute) + ------------------------------------------------------- */ + +#ifdef T27_TEST_MAIN +#include +int main(void) { + test_replays_the_ledger_conflict_of_pr_9945_in_the_10098_batch(); + test_the_same_addition_on_both_sides_is_written_once(); + test_ours_progress_wins_where_theirs_kept_the_base_entry(); + test_ours_removing_an_entry_theirs_kept_stays_removed(); + test_two_additions_of_one_path_with_different_verdicts_are_refused(); + test_theirs_changing_a_base_entry_is_refused(); + test_theirs_removing_a_base_entry_is_refused(); + test_theirs_and_ours_making_the_same_change_agree(); + test_a_header_theirs_changed_is_refused_and_one_ours_changed_is_kept(); + test_a_side_out_of_order_or_with_a_duplicate_is_refused(); + test_a_side_not_in_the_ledger_shape_is_refused(); + test_a_union_too_big_for_the_buffer_says_so(); + test_the_byte_helpers_stay_inside_their_ranges(); + test_the_header_must_end_with_the_entries_key(); + test_a_footer_theirs_changed_is_refused_and_a_frame_both_changed_agrees(); + test_additions_at_either_end_of_the_ledger_are_kept(); + test_both_sides_removing_one_entry_agree(); + test_marker_lines_counts_only_full_markers_at_a_line_start(); + test_a_port_spec_its_seal_and_the_ledger_are_in_scope(); + test_a_file_outside_the_port_shape_is_named(); + test_a_seal_counts_only_with_its_own_port_spec(); + test_an_empty_or_unreadable_file_list_is_refused(); + test_the_scope_rule_reads_each_record_exactly(); + test_the_reason_tables_match_their_lengths(); + printf("All %d tests passed.\n", 24); + return 0; +} +#endif /* T27_TEST_MAIN */ + +#endif /* CLAWLEDGERUNION_H */ From 9ed706e48e5c155a8333429154ba4913a0acefe1 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 21:14:45 +0700 Subject: [PATCH 5/8] claw: ledger_union kills two more survivors, drops two unreachable guards (Refs #10398) Co-Authored-By: Claude Opus 5.5 --- specs/claw/ledger_union.t27 | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/specs/claw/ledger_union.t27 b/specs/claw/ledger_union.t27 index f8c2ab8e03..a773303e58 100644 --- a/specs/claw/ledger_union.t27 +++ b/specs/claw/ledger_union.t27 @@ -169,7 +169,7 @@ fn lu_frame(x : []const u8, n : usize, fr : []usize) -> u32 { const e : usize = lu_eol(x, n, s); if (lu_at(x, e, s, ENTRY_HEAD, ENTRY_HEAD_LEN)) { hs = s; } else { s = e + 1; } } - if (hs == n) { return U_SHAPE; } + // With no entry line hs is n; the checks below, or the empty entry list after them, refuse it. if (hs < ENTRIES_OPEN_LEN + 1) { return U_SHAPE; } if (lu_at(x, hs - 1, hs - 1 - ENTRIES_OPEN_LEN, ENTRIES_OPEN, ENTRIES_OPEN_LEN) == false) { return U_SHAPE; } var p : usize = hs; @@ -419,10 +419,10 @@ fn lu_find(x : []const u8, n : usize, g : u8, fs : usize, fe : usize) -> usize { /// Does the seal x[ps..pe) have an S record naming a specs/port .t27 file that is also an F record? fn lu_seal_ok(x : []const u8, n : usize, ps : usize, pe : usize) -> bool { // sp is the tab before the spec field; with no S record, or one without a spec field, it is - // the end of a line or of x, and the spec range is empty. + // the end of a line or of x, the spec range below is empty or reversed, and lu_port_spec + // refuses it at its prefix check. const sp : usize = lu_find(x, n, 'S', ps, pe); const se : usize = lu_eol(x, n, sp); - if (sp >= se) { return false; } if (lu_port_spec(x, sp + 1, se) == false) { return false; } return lu_find(x, n, 'F', sp + 1, se) < n; } @@ -908,6 +908,26 @@ test additions_at_either_end_of_the_ledger_are_kept { assert lu_cmp(&out, 0, m, &x, 0, xn) == 0; } +test an_addition_of_theirs_after_ours_is_spent_is_kept { + // Base and ours end at cc; theirs goes on to dd, which the merge must still reach. + var b : [256]u8 = undefined; + const bn : usize = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + var t : [512]u8 = undefined; + var tn : usize = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "dd", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + var out : [1024]u8 = undefined; + var st : [4]u32 = undefined; + const m : usize = ledger_union(&b, bn, &t, tn, &b, bn, &out, 1024, &st); + assert st[0] == U_OK; + assert st[1] == 1; + assert st[2] == 3; + assert m == tn; + assert lu_cmp(&out, 0, m, &t, 0, tn) == 0; +} + test both_sides_removing_one_entry_agree { // Theirs and ours both dropped aa; ours also added bb, which sorts between aa and cc. var b : [256]u8 = undefined; @@ -1057,6 +1077,10 @@ test the_scope_rule_reads_each_record_exactly { n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nS\t\tspecs/port/a.t27\n", 39); m = pr_scope(&x, n, &out, 256, &st); assert st[0] == SC_BAD; + // A tag is one letter and a tab: an S with more letters before its tab is no record. + n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nSab\tc\n", 25); + m = pr_scope(&x, n, &out, 256, &st); + assert st[0] == SC_BAD; // A seal whose S record has no spec field, with and without the last newline. n = put_str(&x, 512, 0, "F\t.trinity/seals/a.json\nS\t.trinity/seals/a.json\n", 48); m = pr_scope(&x, n, &out, 256, &st); From 430cceae9771d2194764212377b58e6965081da0 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 21:16:33 +0700 Subject: [PATCH 6/8] claw: ledger_union.c regenerated and resealed, 25/25 tests (Closes #10398) Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/claw_ClawLedgerUnion.json | 16 ++++++------ gen/c/claw/ledger_union.c | 31 ++++++++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/.trinity/seals/claw_ClawLedgerUnion.json b/.trinity/seals/claw_ClawLedgerUnion.json index 14b6380523..12c7f6f8d3 100644 --- a/.trinity/seals/claw_ClawLedgerUnion.json +++ b/.trinity/seals/claw_ClawLedgerUnion.json @@ -7,23 +7,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:6a22f33e2b9949516806cb0cd0eccb7c4014768b626ed5c488d20dcd76b311c3", - "gen_hash_rust": "sha256:5e23ce907b80e85072e9256c53177d5d080ac6113b69d7c1a482d65e5e47ed7b", - "gen_hash_verilog": "sha256:cfd43a4e7c531717afbf4960f0df2dcbd06b3a247f5bad3b18881b920214f549", - "gen_hash_zig": "sha256:7600b876617435503ecfd1f939b029d4e195a73113413ff01e37c7767b21b186", + "gen_hash_c": "sha256:f5f2bdf2bfdd9ef25d0ba280851b3504149efd01b36191943b9ec731eddfec99", + "gen_hash_rust": "sha256:0c4d2ff8d06ea85b4c205e68dedc8081b1c3768ce0fdcfad6d948cd72b952254", + "gen_hash_verilog": "sha256:7beda094f46814f7db05772cd52e22de60e4ba6f845b94b01a8d761b3596ffab", + "gen_hash_zig": "sha256:86d1598c1805d89624d659204e8a37c5409e3b1e2745ad9c32947b97a5bcda11", "module": "ClawLedgerUnion", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T14:01:32Z", + "sealed_at": "2026-10-18T14:15:59Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:a9caacd65156c675f82a5c84aba1cd7c0d19050b239886551b30ff869a8d358f", + "spec_hash": "sha256:38a31c67e68321f02ef60d23f2e563f51e528921112a22dc6cfe9c5d1ccead69", "spec_path": "specs/claw/ledger_union.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 24, - "total": 24, + "passed": 25, + "total": 25, "vacuous": 0 }, "toolchain": { diff --git a/gen/c/claw/ledger_union.c b/gen/c/claw/ledger_union.c index 1f22fb70fe..2a51808913 100644 --- a/gen/c/claw/ledger_union.c +++ b/gen/c/claw/ledger_union.c @@ -176,9 +176,6 @@ uint32_t lu_frame(uint8_t* x, size_t n, size_t* fr) { s = (e + 1); } } - if ((hs == n)) { - return U_SHAPE; - } if ((hs < (ENTRIES_OPEN_LEN + 1))) { return U_SHAPE; } @@ -485,9 +482,6 @@ size_t lu_find(uint8_t* x, size_t n, uint8_t g, size_t fs, size_t fe) { bool lu_seal_ok(uint8_t* x, size_t n, size_t ps, size_t pe) { size_t sp = lu_find(x, n, 'S', ps, pe); size_t se = lu_eol(x, n, sp); - if ((sp >= se)) { - return false; - } if ((lu_port_spec(x, (sp + 1), se) == false)) { return false; } @@ -971,6 +965,25 @@ void test_additions_at_either_end_of_the_ledger_are_kept(void) { assert((lu_cmp(&out, 0, m, &x, 0, xn) == 0)); } +void test_an_addition_of_theirs_after_ours_is_spent_is_kept(void) { + uint8_t b[256] = {0}; + size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); + uint8_t t[512] = {0}; + size_t tn = put_str(&t, 512, 0, FIX_HEAD, FIX_HEAD_LEN); + tn = fixture_line(&t, 512, tn, "aa", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "cc", 2, "pass", 4, false); + tn = fixture_line(&t, 512, tn, "dd", 2, "pass", 4, true); + tn = put_str(&t, 512, tn, FIX_FOOT, FIX_FOOT_LEN); + uint8_t out[1024] = {0}; + uint32_t st[4] = {0}; + size_t m = ledger_union(&b, bn, &t, tn, &b, bn, &out, 1024, &st); + assert((st[0] == U_OK)); + assert((st[1] == 1)); + assert((st[2] == 3)); + assert((m == tn)); + assert((lu_cmp(&out, 0, m, &t, 0, tn) == 0)); +} + void test_both_sides_removing_one_entry_agree(void) { uint8_t b[256] = {0}; size_t bn = fixture_pair(&b, 256, "aa", "pass", 4, "cc", "pass", 4); @@ -1108,6 +1121,9 @@ void test_the_scope_rule_reads_each_record_exactly(void) { n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nS\t\tspecs/port/a.t27\n", 39); m = pr_scope(&x, n, &out, 256, &st); assert((st[0] == SC_BAD)); + n = put_str(&x, 512, 0, "F\tspecs/port/a.t27\nSab\tc\n", 25); + m = pr_scope(&x, n, &out, 256, &st); + assert((st[0] == SC_BAD)); n = put_str(&x, 512, 0, "F\t.trinity/seals/a.json\nS\t.trinity/seals/a.json\n", 48); m = pr_scope(&x, n, &out, 256, &st); assert((st[0] == SC_SEAL)); @@ -1157,6 +1173,7 @@ int main(void) { test_the_header_must_end_with_the_entries_key(); test_a_footer_theirs_changed_is_refused_and_a_frame_both_changed_agrees(); test_additions_at_either_end_of_the_ledger_are_kept(); + test_an_addition_of_theirs_after_ours_is_spent_is_kept(); test_both_sides_removing_one_entry_agree(); test_marker_lines_counts_only_full_markers_at_a_line_start(); test_a_port_spec_its_seal_and_the_ledger_are_in_scope(); @@ -1165,7 +1182,7 @@ int main(void) { test_an_empty_or_unreadable_file_list_is_refused(); test_the_scope_rule_reads_each_record_exactly(); test_the_reason_tables_match_their_lengths(); - printf("All %d tests passed.\n", 24); + printf("All %d tests passed.\n", 25); return 0; } #endif /* T27_TEST_MAIN */ From 585d1ee3915d3f7c08622d53c9821fa02c86d7b1 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 21:35:53 +0700 Subject: [PATCH 7/8] claw: claw-batch marker check refuses an unreadable file instead of skipping it (Refs #10398) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/gate-topology.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index 1aef53933f..1ae64d499c 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -236,7 +236,7 @@ jobs: if git show ":1:$L" > "$d/b" && git show ":3:$L" > "$d/t" && git show ":2:$L" > "$d/o" && "$d/lu" u "$d/b" "$d/t" "$d/o" > "$L" 2> "$d/why" && git add "$L" && git commit -q -m "$m"; then ok+=("$n:$h"); else git merge --abort; out+=("#$n: ledger $(cat "$d/why")"); fi done { for x in "${ok[@]}"; do echo "- folded #${x%%:*} at ${x#*:}"; done; for x in "${out[@]}"; do echo "- left out $x"; done; } > "$d/list"; cat "$d/list" >> "$GITHUB_STEP_SUMMARY"; [ ${#ok[@]} -gt 0 ] || { echo "::error::claw-batch folded no pull request"; exit 1; } - bad=0; while read -r f; do [ -f "$f" ] && ! "$d/lu" m < "$f" > /dev/null && { echo "::error file=$f::conflict-marker lines in the batch"; bad=1; }; done < <(git diff --name-only --diff-filter=d origin/master HEAD) + bad=0; while read -r f; do "$d/lu" m < "$f" > /dev/null || { echo "::error file=$f::conflict-marker lines in the batch, or unreadable"; bad=1; }; done < <(git diff --name-only --diff-filter=d origin/master HEAD) jq empty "$L" && "$d/lu" u "$L" "$L" "$L" > /dev/null || { echo "::error file=$L::the batch ledger is not JSON in the ledger shape"; bad=1; }; [ $bad = 0 ] && git push -q origin "HEAD:refs/heads/$B" || exit 1 { echo "Port batch of ${#ok[@]} pull requests, folded by the claw-batch job of gate-topology.yml: $RUN"; echo; echo "Merge with a merge commit, never squash: every folded head is a parent here, so GitHub marks it merged when this lands."; echo; cat "$d/list"; echo for x in "${ok[@]}"; do gh pr view "${x%%:*}" --json closingIssuesReferences -q '.closingIssuesReferences[].number' "$d/body" From f0e3abd0367fabfc43925fc0bd36c3bd8390efb5 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 21:36:46 +0700 Subject: [PATCH 8/8] census: re-bless quiet + shell for the claw-batch job (Refs #10398) shell: jobs 92 -> 93, run: steps 324 -> 325, runner-shell steps 303 -> 304 -- the new claw-batch job in gate-topology.yml is one job with one run: step. quiet: named-a-path-but-not-quiet 220 -> 233 -- that step names the ledger, the union harness and its temp files; quiet shapes stay 33, since the marker check no longer skips a missing file. Blessed on the Railway lab with tri census pin --bless. Co-Authored-By: Claude Opus 5.5 --- tools/census/quiet.txt | 2 +- tools/census/shell.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/census/quiet.txt b/tools/census/quiet.txt index ca3fcdc104..b42450c69a 100644 --- a/tools/census/quiet.txt +++ b/tools/census/quiet.txt @@ -2,7 +2,7 @@ GATE STEPS WHOSE PASS SURVIVES THE SUBJECT GOING MISSING workflow files read 67 steps in a quiet shape 33 - named a path but not quiet 220 (--excluded prints them) + named a path but not quiet 233 (--excluded prints them) by shape: failure branch passes 14 `… 2>/dev/null … || echo PASSED` diff --git a/tools/census/shell.txt b/tools/census/shell.txt index 4da213f5cb..8bf429ac63 100644 --- a/tools/census/shell.txt +++ b/tools/census/shell.txt @@ -1,11 +1,11 @@ WHICH INTERPRETER EACH GATE STEP IS HANDED TO, AND WHO SAYS SO workflow files read 67 - jobs 92 - run: steps 324 + jobs 93 + run: steps 325 who names the shell: - the runner does 303 no container, so bash -eo pipefail + the runner does 304 no container, so bash -eo pipefail a `shell:` key does 6 NOBODY 15 a container and no `shell:` key