Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# NOW -- Port tools/check_seal_currency.py (Python, 5 functions) to specs/port/tools/check_seal_currency.t27 (published 2026-09-21)

## A bee's work on #4387, published from `queen-4387` (Closes #4387)

- The branch changes 1 file(s): `specs/port/tools/check_seal_currency.t27`.
- `git diff --stat origin/master...queen-4387` reads: 1 file changed, 308 insertions(+)
- This entry is written by the publisher, not by the bee. A pull request must
add exactly one `docs/now/` entry and a bee has no way to know that: its brief
names a boundary file and acceptance criteria, and `docs/now/` is neither.
- What this entry does NOT establish: that the work is correct. The gates on the
pull request judge that, and they are the same gates every other change meets.
308 changes: 308 additions & 0 deletions specs/port/tools/check_seal_currency.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
// specs/port/tools/check_seal_currency.t27
// Port of tools/check_seal_currency.py functions
// Port of t27c(), current_hashes(), scan(), self_check(), and main() from the original Python implementation

module check_seal_currency;

// Constants from the original implementation
pub const SEALS : str = ".trinity/seals";
pub const BACKENDS : [4]str = ["rust", "zig", "c", "verilog"];

// Port of t27c() function from line 49 of the original
fn t27c() -> str {
// Check for built t27c in common locations
if @fileExists("target/release/t27c") && @isExecutable("target/release/t27c") {
return "target/release/t27c";
}
if @fileExists("bootstrap/target/release/t27c") && @isExecutable("bootstrap/target/release/t27c") {
return "bootstrap/target/release/t27c";
}

// Check environment variable
let env = @env("TRI_T27C");
if env.len() > 0 && @fileExists(env) && @isExecutable(env) {
return env;
}

// Exit 2, not 0: a check that could not run has not passed.
@printErr("check_seal_currency: t27c not built. Exit 2 = COULD NOT RUN.");
@printErr(" cargo build --release -p t27c, or set TRI_T27C.");
@exit(2);
// Unreachable but needed for type checking
return "";
}

// Port of current_hashes() function from line 62 of the original
fn current_hashes(binary: str, spec: str) -> [str]str {
let out = @runCommand([binary, "seal", spec]);
let mut result : [str]str = [];

// Parse key=value lines
for line in out.split("\n") {
if line.contains("=") {
let parts = line.split("=", 1);
if parts.len() == 2 {
result[parts[0]] = parts[1];
}
}
}

return result;
}

// Port of scan() function from line 69 of the original
fn scan(binary: str, seal_dir: str) -> ([(str, str, [(str, str, str)])], i32, i32, i32) {
let mut stale : [(str, str, [(str, str, str)])] = [];
let mut none_sealed = 0;
let mut missing = 0;
let mut current = 0;

// Get all seal files
let seal_files = @glob(seal_dir + "/*.json");

for sp in seal_files {
let mut ok = true;
let spec : str = "";

// Try to read and parse the seal file
let content = @readFile(sp);
if content.len() == 0 {
missing += 1;
continue;
}

let d = @parseJson(content);
if d == null {
missing += 1;
continue;
}

spec = @get(d, "spec_path", "");
if spec.len() == 0 || !@fileExists(spec) {
missing += 1;
continue;
}

let cur = current_hashes(binary, spec);
let mut bad : [(str, str, str)] = [];
let mut saw_none = false;

for b in BACKENDS {
let k = "gen_hash_" + b;
let a = @get(cur, k, "");
let stored = @get(d, k, "");

if a.len() == 0 || stored.len() == 0 {
continue;
}

if a.contains("none") || stored.contains("none") {
saw_none = true;
continue;
}

if a != stored {
bad.push((b, stored, a));
}
}

if bad.len() > 0 {
stale.push((sp, spec, bad));
} else if saw_none {
none_sealed += 1;
} else {
current += 1;
}
}

return (stale, none_sealed, missing, current);
}

// Port of self_check() function from line 102 of the original
fn self_check() -> i32 {
/* A seal deliberately given a wrong hash must be reported.

Without this the check could return "0 stale" because it looks in the wrong
place, reads the wrong field, or silently skips every file -- and a zero from
a check that cannot see is indistinguishable from a zero that means healthy.
*/
let binary = t27c();
let specs = @glob("specs/**/*.t27");

if specs.len() == 0 {
@printErr("self-check: no specs to work with. Exit 2.");
return 2;
}

let mut spec : str = "";
for s in specs {
let cur = current_hashes(binary, s);
let rust_hash = @get(cur, "gen_hash_rust", "");
if rust_hash.len() > 0 && !rust_hash.contains("none") {
spec = s;
break;
}
}

if spec.len() == 0 {
@printErr("self-check: no spec generates Rust. Exit 2.");
return 2;
}

let cur = current_hashes(binary, spec);
let tmp_dir = @tempDir();
let d = tmp_dir + "/seals";
@createDir(d, true);

// Create good seal
let mut good : [str]str = cur;
good["spec_path"] = spec;
let good_json = @stringifyJson(good, true, true); // pretty print, sort keys
@writeFile(d + "/good.json", good_json);

// Create bad seal with wrong rust hash
let mut bad : [str]str = good;
bad["gen_hash_rust"] = "sha256:" + "0".repeat(64);
let bad_json = @stringifyJson(bad, true, true);
@writeFile(d + "/bad.json", bad_json);

let (stale, _, _, current) = scan(binary, d);

let ok = stale.len() == 1 &&
@get(stale[0], 0, "") == "bad.json" &&
current == 1;

@print(" self-check: 2 seals scanned; stale reported " + @toString(stale.len()) + " (want 1), ");
@print("current " + @toString(current) + " (want 1) -- " + (if ok { "PASS" } else { "FAIL" }));

return if ok { 0 } else { 1 };
}

// Port of main() function from line 142 of the original
pub fn main() -> i32 {
let args = @getArgs();

if args.contains("--self-check") {
return self_check();
}

if !@dirExists(SEALS) {
@printErr("check_seal_currency: " + SEALS + " is not a directory. Exit 2.");
return 2;
}

let binary = t27c();
let (stale, none_sealed, missing, current) = scan(binary, SEALS);

if args.contains("--stale-specs") {
// Collect unique spec paths from stale entries
let mut spec_set : [str]str = [];
for entry in stale {
let spec = @get(entry, 1, "");
if spec.len() > 0 && !spec_set.contains(spec) {
spec_set.push(spec);
}
}
// Sort the spec set
let mut i = 0;
while i < spec_set.len() {
let mut j = i + 1;
while j < spec_set.len() {
if spec_set[i] > spec_set[j] {
let temp = spec_set[i];
spec_set[i] = spec_set[j];
spec_set[j] = temp;
}
j += 1;
}
i += 1;
}

for spec in spec_set {
@print(spec);
}
return if stale.len() > 0 { 1 } else { 0 };
}

let total = stale.len() + none_sealed + missing + current;
@print("seals scanned: " + @toString(total));
@print(" current : " + @toString(current));
@print(" spec file no longer present : " + @toString(missing));
@print(" sealed with gen_hash=none : " + @toString(none_sealed));
@print(" STALE generated-code hash : " + @toString(stale.len()));

// Print up to 20 stale entries
let mut i = 0;
while i < stale.len() && i < 20 {
let entry = @get(stale, i, null);
if entry != null {
let name = @get(entry, 0, "");
let spec = @get(entry, 1, "");
let bad_list = @get(entry, 2, []);

for j in 0..bad_list.len() {
let bad = @get(bad_list, j, null);
if bad != null {
let b = @get(bad, 0, "");
let stored = @get(bad, 1, "");
let now = @get(bad, 2, "");

// Extract substring after "sha256:" for display (positions 7-18 inclusive = 12 chars)
let stored_short = if stored.len() >= 19 { @slice(stored, 7, 19) } else { stored };
let now_short = if now.len() >= 19 { @slice(now, 7, 19) } else { now };

@print(" " + name + ": gen_hash_" + b + " sealed=" + stored_short + " current=" + now_short + " (" + spec + ")");
}
}
}
i += 1;
}

if stale.len() > 20 {
@print(" ... and " + @toString(stale.len() - 20) + " more");
}

return if stale.len() > 0 { 1 } else { 0 };
}

// Test block for t27c function
test "t27c_function" {
// This test would require mocking the filesystem and environment
// For now, we just check that the function exists and returns a string
let result = t27c();
assert!(result.len() >= 0, "t27c should return a string (possibly empty)");
}

// Test block for current_hashes function
test "current_hashes_function" {
// This test would require mocking the subprocess call
// For now, we just check that the function exists and returns a map
let binary = "echo"; // dummy binary
let spec = "dummy.spec";
let result = current_hashes(binary, spec);
assert!(true, "current_hashes function exists and returns a map");
}

// Test block for scan function
test "scan_function" {
// This test would require mocking the filesystem and subprocess
// For now, we just check that the function exists
let binary = "echo";
let seal_dir = "/tmp";
let result = scan(binary, seal_dir);
assert!(true, "scan function exists");
}

// Test block for self_check function
test "self_check_function" {
let result = self_check();
// self_check can return 0 (success), 1 (failure), or 2 (could not run)
assert!(result == 0 || result == 1 || result == 2, "self_check should return 0, 1, or 2");
}

// Test block for main function
test "main_function" {
let result = main();
// main can return 0 (success) or 1 (failure) or 2 (could not run)
assert!(result == 0 || result == 1 || result == 2, "main should return 0, 1, or 2");
}
Loading