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}