nx_closurehash_gate.nx source
↩ module page · 206 lines · 9963 B
1// nx_closurehash_gate.nx -- PROVE nx_closurehash ACTUALLY READS THE CLOSURE (2026-08-07).
2//
3// The organ claims something strong: that its one hash covers every source the compiler will read. That
4// claim has an obvious trivial-wrong implementation -- hash ONLY the target file and print a number --
5// and such a fake would pass every naive test: it resolves, it is deterministic, it emits 64 hex chars.
6// *A TEST THAT THE TRIVIAL WRONG IMPLEMENTATION ALSO PASSES MEASURES NOTHING.*
7//
8// T3 is the tooth that decides it: mutate ONE BYTE of a TRANSITIVE dependency (depth 2, never named by
9// the target) and require the closure hash to CHANGE. A target-only hasher cannot pass that, and neither
10// can a one-level hasher.
11//
12// Fixtures are built on disk under /tmp so the walk is exercised for real -- a fixture held in memory
13// would test the parser and skip the resolver, which is where the last two defects actually were
14// (a scanner matching its own source text, and a resolver blind to a third search path).
15//
16// nx_closurehash_gate [elf] elf default ./_build/nx_closurehash.sov.elf
17// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
18import "nx_tool_run.nx"
19import "nx_gate_verdict.nx"
20
21const CG_ROOT: *u8 = "/tmp/chgate"
22const CG_RT: *u8 = "/tmp/chgate/runtime"
23const CG_CAP: i64 = 65536
24const CG_MODE: i64 = 493
25
26func cgp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
27func cgn(v: i64) -> i64 {
28 var m: i64 = v
29 if m < 0 { cgp("-" as *u8); m = 0 - m }
30 let t: *u8 = sys_mmap(32)
31 var k: i64 = 0
32 if m == 0 { t[0] = 48 as u8; k = 1 }
33 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
34 let b: *u8 = sys_mmap(32)
35 var i: i64 = 0
36 while i < k { b[i] = t[k-1-i]; i = i + 1 }
37 sys_write(1, b, k)
38 return 0
39}
40func cgwrite(path: *u8, body: *u8) -> i64 {
41 let fd: i64 = sys_openat_wr(path, 420)
42 if fd < 0 { return 0 - 1 }
43 var n: i64 = 0
44 while body[n] != (0 as u8) { n = n + 1 }
45 sys_write(fd, body, n)
46 sys_close(fd)
47 return n
48}
49func cgcat(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 }
50// pull the 64-hex value after "closure_sha=" out of a capture. Scans every byte: the value is never at
51// offset 0, and a merged stdout+stderr capture moves it again.
52func cg_sha(buf: *u8, n: i64, out: *u8) -> i64 {
53 let k: *u8 = "closure_sha=" as *u8
54 var i: i64 = 0
55 while i + 12 <= n {
56 var m: i64 = 0
57 var hit: i64 = 1
58 while m < 12 { if buf[i+m] != k[m] { hit = 0; m = 12 } else { m = m + 1 } }
59 if hit == 1 {
60 var s: i64 = i + 12
61 var o: i64 = 0
62 while o < 64 { if s + o < n { out[o] = buf[s+o]; o = o + 1 } else { o = 64 } }
63 out[64] = 0 as u8
64 return 1
65 }
66 i = i + 1
67 }
68 return 0
69}
70func cg_num_after(buf: *u8, n: i64, key: *u8) -> i64 {
71 var ln: i64 = 0
72 while key[ln] != (0 as u8) { ln = ln + 1 }
73 var i: i64 = 0
74 while i + ln < n {
75 var m: i64 = 0
76 var hit: i64 = 1
77 while m < ln { if buf[i+m] != key[m] { hit = 0; m = ln } else { m = m + 1 } }
78 if hit == 1 {
79 var s: i64 = i + ln
80 var v: i64 = 0
81 var got: i64 = 0
82 while s < n {
83 let c: i64 = buf[s] as i64
84 if c >= 48 { if c <= 57 { v = v*10 + (c-48); got = 1; s = s + 1 } else { s = n } } else { s = n }
85 }
86 if got == 1 { return v }
87 return 0 - 1
88 }
89 i = i + 1
90 }
91 return 0 - 1
92}
93func cg_same(a: *u8, b: *u8) -> i64 {
94 var i: i64 = 0
95 while i < 64 { if a[i] != b[i] { return 0 } i = i + 1 }
96 return 1
97}
98
99// 1 if `p` can be stat'd. Used to RESOLVE THE SUBJECT rather than assume it.
100func cg_exists(p: *u8) -> i64 {
101 let sb: *u8 = sys_mmap(256)
102 let r: i64 = sys_fstatat(p, sb)
103 sys_munmap(sb, 256)
104 if r < 0 { return 0 }
105 return 1
106}
107
108func main(argc: i64, argv: *i64) -> i64 {
109 // SUBJECT RESOLUTION (fixed 2026-08-08). The default used to be the AUTHOR'S LAPTOP ABSOLUTE
110 // PATH, so this gate scored 4/4 on that one machine and could only ever fork-fail (rc=127)
111 // anywhere else -- and it did, 1/4 RED, the first time it ever ran on the NAS.
112 // *A GATE THAT HARDCODES ITS AUTHOR'S ABSOLUTE PATH IS PROVEN ONLY ON ITS AUTHOR'S MACHINE.*
113 // Probe deployed locations first, build tree last; literals are hoisted to `let` because an
114 // inline-cast string literal indexed directly never compares equal (banked NishiLang trap).
115 let c1: *u8 = "./_offc/nx_closurehash.elf" as *u8
116 let c2: *u8 = "../nx_closurehash.elf" as *u8
117 let c3: *u8 = "./nx_closurehash.elf" as *u8
118 let c4: *u8 = "./_build/nx_closurehash.sov.elf" as *u8
119 let c5: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/_build/nx_closurehash.sov.elf" as *u8
120 var elf: *u8 = c5
121 var found: i64 = 0
122 if cg_exists(c5) == 1 { found = 1 }
123 if cg_exists(c4) == 1 { elf = c4; found = 1 }
124 if cg_exists(c3) == 1 { elf = c3; found = 1 }
125 if cg_exists(c2) == 1 { elf = c2; found = 1 }
126 if cg_exists(c1) == 1 { elf = c1; found = 1 }
127 if argc >= 2 { let e: *u8 = argv[1] as *u8; if e[0] != (0 as u8) { elf = e; found = cg_exists(e) } }
128 // PRINT THE SUBJECT. An absent subject must be visible as "I could not look", never silently
129 // scored as four failing teeth -- rc=127 read as a RED verdict is a diagnosis nobody made.
130 cgp("# subject=" as *u8); cgp(elf)
131 if found == 0 { cgp(" *** ABSENT -- teeth below measure NOTHING ***" as *u8) }
132 cgp("\n" as *u8)
133
134 sys_mkdir(CG_ROOT, CG_MODE)
135 sys_mkdir(CG_RT, CG_MODE)
136
137 let pa: *u8 = sys_mmap(256); var x: i64 = cgcat(pa, 0, CG_RT); x = cgcat(pa, x, "/cg_a.nx" as *u8); pa[x] = 0 as u8
138 let pb: *u8 = sys_mmap(256); var y: i64 = cgcat(pb, 0, CG_RT); y = cgcat(pb, y, "/cg_b.nx" as *u8); pb[y] = 0 as u8
139 let pc: *u8 = sys_mmap(256); var z: i64 = cgcat(pc, 0, CG_RT); z = cgcat(pc, z, "/cg_c.nx" as *u8); pc[z] = 0 as u8
140
141 // depth-2 chain: a -> b -> c. `c` is NEVER named by `a`, which is the whole point of T3.
142 cgwrite(pa, "import \"cg_b.nx\"\nfunc cg_a() -> i64 { return 1 }\n" as *u8)
143 cgwrite(pb, "import \"cg_c.nx\"\nfunc cg_b() -> i64 { return 2 }\n" as *u8)
144 cgwrite(pc, "func cg_c() -> i64 { return 3 }\n" as *u8)
145
146 let out: *u8 = sys_mmap(CG_CAP)
147 let ol: *i64 = sys_mmap(16) as *i64
148 let av: *i64 = sys_mmap(64) as *i64
149 let h1: *u8 = sys_mmap(80)
150 let h2: *u8 = sys_mmap(80)
151 let h3: *u8 = sys_mmap(80)
152 var pass: i64 = 0
153 var total: i64 = 0
154
155 // ---- T1: the depth-2 closure resolves completely and counts 3 files
156 av[0] = elf as i64; av[1] = "cg_a" as *u8 as i64; av[2] = CG_ROOT as i64; av[3] = 0
157 ol[0] = 0
158 tr_run_capture_to(elf, av, out, CG_CAP, ol, 60000)
159 let f1: i64 = cg_num_after(out, ol[0], "files=" as *u8)
160 let u1: i64 = cg_num_after(out, ol[0], "unresolved=" as *u8)
161 let g1: i64 = cg_sha(out, ol[0], h1)
162 total = total + 1
163 var t1: i64 = 0
164 if f1 == 3 { if u1 == 0 { if g1 == 1 { t1 = 1 } } }
165 if t1 == 1 { pass = pass + 1; cgp("T1 GREEN depth-2 closure complete (files=3 unresolved=0)\n" as *u8) } else { cgp("T1 RED files=" as *u8); cgn(f1); cgp(" unresolved=" as *u8); cgn(u1); cgp("\n" as *u8) }
166
167 // ---- T2: DETERMINISM -- unchanged tree must give the identical hash
168 ol[0] = 0
169 tr_run_capture_to(elf, av, out, CG_CAP, ol, 60000)
170 cg_sha(out, ol[0], h2)
171 total = total + 1
172 if cg_same(h1, h2) == 1 { pass = pass + 1; cgp("T2 GREEN deterministic across runs\n" as *u8) } else { cgp("T2 RED hash changed with no edit\n" as *u8) }
173
174 // ---- T3: ANTI-VACUITY. One byte in the DEPTH-2 dependency must move the closure hash.
175 // A hasher that reads only the target -- or only its direct imports -- passes T1, T2 and T4 and
176 // FAILS HERE. This is the tooth that makes the organ's claim mean anything.
177 cgwrite(pc, "func cg_c() -> i64 { return 4 }\n" as *u8)
178 ol[0] = 0
179 tr_run_capture_to(elf, av, out, CG_CAP, ol, 60000)
180 cg_sha(out, ol[0], h3)
181 total = total + 1
182 if cg_same(h1, h3) == 0 { pass = pass + 1; cgp("T3 GREEN one byte at DEPTH 2 changed the closure hash -- a target-only hasher cannot pass this\n" as *u8) } else { cgp("T3 RED transitive edit did NOT change the hash: the closure is not being read\n" as *u8) }
183
184 // ---- T4: a missing import must be COUNTED and must exit non-zero, never silently dropped.
185 // *A PROVENANCE RECORD THAT DROPS WHAT IT COULD NOT FIND IS A FORGERY* -- and a clean exit on an
186 // incomplete closure is exactly how a caller gating a promote would be misled.
187 cgwrite(pb, "import \"cg_missing_zz.nx\"\nfunc cg_b() -> i64 { return 2 }\n" as *u8)
188 ol[0] = 0
189 let rc: i64 = tr_run_capture_to(elf, av, out, CG_CAP, ol, 60000)
190 let u4: i64 = cg_num_after(out, ol[0], "unresolved=" as *u8)
191 total = total + 1
192 var t4: i64 = 0
193 if u4 >= 1 { if rc != 0 { t4 = 1 } }
194 if t4 == 1 { pass = pass + 1; cgp("T4 GREEN missing import COUNTED (unresolved=" as *u8); cgn(u4); cgp(") and exit non-zero\n" as *u8) } else { cgp("T4 RED unresolved=" as *u8); cgn(u4); cgp(" rc=" as *u8); cgn(rc); cgp(" -- an incomplete closure must not exit clean\n" as *u8) }
195
196 cgp("nx_closurehash_gate " as *u8); cgn(pass); cgp("/" as *u8); cgn(total); cgp("\n" as *u8)
197 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
198 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
199 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
200 let ctr__dry: *i64 = gv_ctr()
201 ctr__dry[0] = pass
202 ctr__dry[1] = total
203 let rc__dry: i64 = gv_verdict("CLOSUREHASH-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
204 sys_exit(rc__dry)
205 return rc__dry
206}