code wiki / (root) / nx_provcheck.nx

nx_provcheck.nx source

↩ module page · 111 lines · 5601 B

1// nx_provcheck.nx -- ASK THE ARTIFACT WHAT IT WAS BUILT FROM, THEN ASK THE TREE (2026-08-07). 2// 3// This is the question the treecanon manifest exists to approximate, answered from facts instead: 4// "is the deployed binary built from the sources that are in the tree right now?" 5// 6// The manifest answers a DIFFERENT question and answers it backwards. It is generated from the LAPTOP 7// mirror, pushed to the NAS, and used to judge the NAS tree -- while edits land on the NAS through 8// nx_fs_write and builds compile FROM the NAS. So it compares truth against a stale copy of a mirror and 9// reports truth as a fork. Measured 2026-08-07: four different hashes for one header inside ten minutes, 10// builds blocked by my own newer edit, and a manifest push that lost a file-lock race and simply did not 11// land. Every one of those is the architecture, not a bug in the guards. 12// 13// ★★★★★★A MANIFEST IS A CLAIM ABOUT A TREE AT A PAST INSTANT; A CLOSURE HASH IS A FACT ABOUT THE BINARY 14// IN YOUR HAND. `<target>.provenance` records the closure the build actually read; this recomputes the 15// closure from the tree as it stands and compares. No push, no watermark, no census, nothing that can go 16// stale between the write and the read, and no second tree required to have an opinion. 17// 18// VERDICTS -- each its own bucket because each has a different remedy: 19// CURRENT recorded closure == tree closure. The artifact IS its sources. 20// DRIFTED they differ. Something in the import closure changed since the build. 21// UNRECORDED the sidecar has no closure_sha256 (built before this existed, or by the async path). 22// *NOT A FAILURE AND NOT A PASS* -- it is "I could not look", and it must never be 23// reported as either. A guard that turns absence into a verdict is the one that gets 24// switched off after it lies once. 25// NOSIDECAR no .provenance at all. 26// 27// nx_provcheck <target> [root] root default "." (run from the buildroot's parent) 28// exit 0 CURRENT | 1 DRIFTED | 2 UNRECORDED/NOSIDECAR | 3 usage 29// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 30import "nx_tool_run.nx" 31import "nx_buildecho.nx" 32 33const PC_CAP: i64 = 262144 34const PC_TMO: i64 = 90000 35 36func pp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 37func pe(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(2,s,n); return 0 } 38func pcat(d: *u8, o: i64, s: *u8) -> i64 { var x: i64=o; var i: i64=0; while s[i]!=(0 as u8){d[x]=s[i];x=x+1;i=i+1} return x } 39func psame64(a: *u8, b: *u8) -> i64 { var i: i64=0; while i<64 { if a[i]!=b[i] { return 0 } i=i+1 } return 1 } 40 41func main(argc: i64, argv: *i64) -> i64 { 42 if argc < 2 { pe("usage: nx_provcheck <target> [root]\n" as *u8); sys_exit(3); return 3 } 43 let tgt: *u8 = argv[1] as *u8 44 var root: *u8 = "." as *u8 45 if argc >= 3 { let r: *u8 = argv[2] as *u8; if r[0] != (0 as u8) { root = r } } 46 47 // ---- 1. what did the BUILD record? 48 let sp: *u8 = sys_mmap(512) 49 var so: i64 = pcat(sp, 0, root) 50 so = pcat(sp, so, "/" as *u8) 51 so = pcat(sp, so, tgt) 52 so = pcat(sp, so, ".provenance" as *u8) 53 sp[so] = 0 as u8 54 let box: *i64 = sys_mmap(16) as *i64 55 let sc: *u8 = sys_read_file(sp, box) 56 if sc == (0 as *u8) { 57 pp("PROVCHECK " as *u8); pp(tgt); pp(" verdict=NOSIDECAR path=" as *u8); pp(sp); pp("\n" as *u8) 58 sys_exit(2); return 2 59 } 60 let rec: *u8 = sys_mmap(80) 61 if be_after_hex(sc, box[0], "closure_sha256=" as *u8, rec) == 0 { 62 // The sidecar exists but predates closure recording. Say exactly that. 63 pp("PROVCHECK " as *u8); pp(tgt); pp(" verdict=UNRECORDED (sidecar has no closure_sha256 -- built before closure provenance, or by the async path). This is NOT a pass and NOT a failure: rebuild to record one.\n" as *u8) 64 sys_exit(2); return 2 65 } 66 67 // ---- 2. what does the TREE say NOW? 68 let che: *u8 = sys_mmap(512) 69 var co: i64 = pcat(che, 0, root) 70 co = pcat(che, co, "/nx_closurehash.elf" as *u8) 71 che[co] = 0 as u8 72 let cfd: i64 = sys_openat_rd(che) 73 if cfd < 0 { 74 pe("nx_provcheck: nx_closurehash.elf not found beside the target; cannot recompute the tree closure\n" as *u8) 75 sys_exit(2); return 2 76 } 77 sys_close(cfd) 78 let broot: *u8 = sys_mmap(512) 79 var bo: i64 = pcat(broot, 0, root) 80 bo = pcat(broot, bo, "/buildroot" as *u8) 81 broot[bo] = 0 as u8 82 let out: *u8 = sys_mmap(PC_CAP) 83 let ol: *i64 = sys_mmap(16) as *i64 84 let av: *i64 = sys_mmap(64) as *i64 85 av[0] = che as i64 86 av[1] = tgt as i64 87 av[2] = broot as i64 88 av[3] = 0 89 ol[0] = 0 90 tr_run_capture_to(che, av, out, PC_CAP - 4, ol, PC_TMO) 91 let now: *u8 = sys_mmap(80) 92 if be_after_hex(out, ol[0], "closure_sha=" as *u8, now) == 0 { 93 pe("nx_provcheck: could not recompute the closure (nx_closurehash produced no closure_sha)\n" as *u8) 94 sys_exit(2); return 2 95 } 96 97 pp("PROVCHECK " as *u8); pp(tgt) 98 pp(" recorded=" as *u8); pp(rec) 99 pp(" now=" as *u8); pp(now) 100 if psame64(rec, now) == 1 { 101 pp(" verdict=CURRENT (the artifact IS its sources)\n" as *u8) 102 return 0 103 } 104 // DRIFTED does not say WHICH file moved -- nx_closurehash's per-file listing does, and duplicating 105 // that here would be a second ruler for one invariant. 106 pp(" verdict=DRIFTED (a source in the import closure changed since this artifact was built; run nx_closurehash " as *u8) 107 pp(tgt) 108 pp(" to see which)\n" as *u8) 109 sys_exit(1) 110 return 1 111}