nx_inc_compile_gate.nx source
↩ module page · 455 lines · 27090 B
1// nx_inc_compile_gate.nx -- VERDICT gate for rung LN10 of /compare/lang: the per-module incremental build
2// cache in nx_inc_compile (watch symbol inc_cache_lookup).
3//
4// DONE-RULE UNDER TEST (lang.plan LN10, verbatim): "gate proves a one-file edit rebuilds only its
5// dependents and the output equals a full build".
6//
7// EVERYTHING RUNS IN AN ISOLATED FIXTURE TREE under <buildroot>/_build/nx_inc_compile_gate/ -- its own
8// runtime/, its own _offc/ copy of the live toolchain AND of the closure ruler, its own _build/ and its own
9// knowledge/ -- so nothing here touches the estate's cache, its toolchain, its confs or its serving root.
10// The fixture is wiped at SETUP, never at teardown: a teardown does not run when a run crashes, and a gate
11// that is not idempotent reports on its first run and lies about every run after.
12// FIXTURE ROOT IS DERIVED, NOT /tmp: measured on this NAS, a byte-identical copy of the compiler placed
13// under /tmp returns execve 127 -- the mount refuses execution -- so every builder run would fail with an
14// empty .s and read as a compiler defect. Build-artifact territory is executable by construction.
15//
16// THE ZERO-COMPILE WITNESS IS STATE, NOT A MESSAGE: nx_sov_build_run writes _build/<t>.s only inside its
17// compile loop. The gate deletes that file before a rebuild; if the artifact is still current and the .s is
18// still ABSENT, no compiler ran. A tooth asserts the .s IS present after a real build, so the absence
19// witness cannot pass vacuously.
20// THE FIXTURE'S OWN buildcache.conf SETS cache_enabled=0 so that B2's whole-target cache cannot serve the
21// hit this gate is attributing to LN10 -- without that, every "zero compile" tooth would measure B2.
22// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
23import "nx_syscalls.nx"
24import "nx_sha256.nx"
25import "nx_gate_verdict.nx"
26import "nx_gatekit_lib.nx"
27import "nx_tool_run.nx"
28
29const IG_PATH: i64 = 4096
30const IG_OUT: i64 = 1048576
31const IG_SHA_HEX: i64 = 64
32const IG_MODE_X: i64 = 493 // 0755
33const IG_MODE_R: i64 = 420 // 0644
34const IG_TMO: i64 = 900000 // the builder's own SBR_BUILD_DEADLINE_MS, inherited not re-chosen
35const IG_VAL_A: i64 = 11 // icfx_lib v1 return value
36const IG_VAL_B: i64 = 19 // icfx_lib v2 -- ONE line differs
37const IG_VAL_C: i64 = 23 // icfx_lib v3
38const IG_SHOW: i64 = 3000 // how much captured output to echo when a tooth fails
39
40static igp_fx: i64
41static igp_tree: i64
42static igp_know: i64
43static igp_rt: i64
44static igp_hdl: i64
45static igp_offc: i64
46static igp_bld: i64
47static igp_cache: i64
48static igp_idx: i64
49static igp_cc: i64
50static igp_asm: i64
51static igp_ruler: i64
52static igp_builder: i64
53static igp_rootarg: i64
54
55func igp_mk(base: *u8, tail: *u8) -> i64 {
56 let b: *u8 = sys_mmap(IG_PATH)
57 var o: i64 = gk_cat(b, 0, base)
58 o = gk_cat(b, o, tail)
59 b[o] = 0 as u8
60 return b as i64
61}
62func igp_init(buildroot: *u8) -> i64 {
63 igp_fx = igp_mk(buildroot, "_build/nx_inc_compile_gate" as *u8)
64 igp_tree = igp_mk(igp_fx as *u8, "/tree" as *u8)
65 igp_know = igp_mk(igp_fx as *u8, "/knowledge" as *u8)
66 igp_rt = igp_mk(igp_tree as *u8, "/runtime" as *u8)
67 igp_hdl = igp_mk(igp_rt as *u8, "/_hdl_build" as *u8)
68 igp_offc = igp_mk(igp_tree as *u8, "/_offc" as *u8)
69 igp_bld = igp_mk(igp_tree as *u8, "/_build" as *u8)
70 igp_cache = igp_mk(igp_bld as *u8, "/inccache" as *u8)
71 igp_idx = igp_mk(igp_cache as *u8, "/index" as *u8)
72 igp_cc = igp_mk(igp_offc as *u8, "/nx_cc_sovereign.elf" as *u8)
73 igp_asm = igp_mk(igp_offc as *u8, "/nxasm_x86_main.elf" as *u8)
74 igp_ruler = igp_mk(igp_offc as *u8, "/nx_closurehash.elf" as *u8)
75 igp_builder = igp_mk(igp_offc as *u8, "/nx_sov_build_run.elf" as *u8)
76 igp_rootarg = igp_mk("--root=" as *u8, igp_tree as *u8)
77 return 0
78}
79func igp(v: i64) -> *u8 { return v as *u8 }
80func ig_p(s: *u8) -> i64 { sys_write(1, s, gk_len(s)); return 0 }
81func ig_nl() -> i64 { let b: *u8 = sys_mmap(2); b[0] = 10 as u8; sys_write(1, b, 1); sys_munmap(b, 2); return 0 }
82func ig_n(v: i64) -> i64 { gv_num(v); return 0 }
83func ig_sub(dir: i64, name: *u8) -> *u8 { return igp_mk(igp(dir), name) as *u8 }
84
85func ig_sha(path: *u8, out: *u8) -> i64 {
86 let ln: *i64 = sys_mmap(16) as *i64
87 ln[0] = 0
88 let buf: *u8 = sys_read_file(path, ln)
89 if (buf as i64) == 0 { out[0] = 0 as u8; return 0 }
90 let d: *u8 = sys_mmap(48)
91 sha256_digest(buf, ln[0], d)
92 var i: i64 = 0
93 while i < 32 {
94 let v: i64 = d[i] as i64
95 let hi: i64 = (v >> 4) & 15
96 let lo: i64 = v & 15
97 if hi < 10 { out[i*2] = (48+hi) as u8 } else { out[i*2] = (87+hi) as u8 }
98 if lo < 10 { out[i*2+1] = (48+lo) as u8 } else { out[i*2+1] = (87+lo) as u8 }
99 i = i + 1
100 }
101 out[IG_SHA_HEX] = 0 as u8
102 sys_free_file(buf, ln[0])
103 return 1
104}
105func ig_hexeq(a: *u8, b: *u8) -> i64 {
106 if a[0] == (0 as u8) { return 0 }
107 if b[0] == (0 as u8) { return 0 }
108 var i: i64 = 0
109 while i < IG_SHA_HEX { if a[i] != b[i] { return 0 } i = i + 1 }
110 return 1
111}
112func ig_copy(src: *u8, dst: *u8, mode: i64) -> i64 {
113 let ln: *i64 = sys_mmap(16) as *i64
114 ln[0] = 0
115 let buf: *u8 = sys_read_file(src, ln)
116 if (buf as i64) == 0 { return 0 }
117 sys_unlinkat(dst)
118 let fd: i64 = sys_openat_wr(dst, mode)
119 if fd < 0 { return 0 }
120 let w: i64 = gk_write_all(fd, buf, ln[0])
121 sys_close(fd)
122 sys_free_file(buf, ln[0])
123 if w != ln[0] { return 0 }
124 nx_chmod(dst, mode)
125 return 1
126}
127func ig_append_byte(path: *u8) -> i64 {
128 let fd: i64 = sys_openat_rdwr(path, IG_MODE_X)
129 if fd < 0 { return 0 - 1 }
130 let end: i64 = sys_lseek(fd, 0, 2)
131 let b: *u8 = sys_mmap(2)
132 b[0] = 0 as u8
133 let w: i64 = sys_write(fd, b, 1)
134 sys_close(fd)
135 if w != 1 { return 0 - 1 }
136 return end + 1
137}
138
139// ---- fixture sources, assembled at RUNTIME (a detector that scans source finds its own fixture) --------
140func ig_write_lib(v: i64) -> i64 {
141 let t: *u8 = sys_mmap(IG_PATH)
142 var o: i64 = gk_cat(t, 0, "import \"nx_syscalls.nx\"" as *u8)
143 t[o] = 10 as u8; o = o + 1
144 o = gk_cat(t, o, "func icfx_val() -> i64 { return " as *u8)
145 o = gk_catn(t, o, v)
146 o = gk_cat(t, o, " }" as *u8)
147 t[o] = 10 as u8; o = o + 1
148 t[o] = 0 as u8
149 let p: *u8 = ig_sub(igp_hdl, "/icfx_lib.nx" as *u8)
150 if gk_write(p, t) < 0 { return 0 }
151 return 1
152}
153func ig_write_prog(name: *u8, letter: *u8, uses_lib: i64) -> i64 {
154 let t: *u8 = sys_mmap(IG_PATH)
155 var o: i64 = gk_cat(t, 0, "import \"nx_syscalls.nx\"" as *u8)
156 t[o] = 10 as u8; o = o + 1
157 if uses_lib == 1 {
158 o = gk_cat(t, o, "import \"icfx_lib.nx\"" as *u8)
159 t[o] = 10 as u8; o = o + 1
160 }
161 o = gk_cat(t, o, "func main(argc: i64, argv: *i64) -> i64 {" as *u8)
162 t[o] = 10 as u8; o = o + 1
163 o = gk_cat(t, o, " sys_write(1, \"" as *u8)
164 o = gk_cat(t, o, letter)
165 o = gk_cat(t, o, "\" as *u8, 1)" as *u8)
166 t[o] = 10 as u8; o = o + 1
167 if uses_lib == 1 { o = gk_cat(t, o, " return icfx_val()" as *u8) } else { o = gk_cat(t, o, " return 7" as *u8) }
168 t[o] = 10 as u8; o = o + 1
169 o = gk_cat(t, o, "}" as *u8)
170 t[o] = 10 as u8; o = o + 1
171 t[o] = 0 as u8
172 let p: *u8 = igp_mk(igp(igp_hdl), name)
173 if gk_write(p as *u8, t) < 0 { return 0 }
174 return 1
175}
176
177// ---- drive the subject ------------------------------------------------------------------------------
178func ig_run(subject: *u8, a1: *u8, a2: *u8, a3: *u8, a4: *u8, a5: *u8, a6: *u8, out: *u8, ol: *i64) -> i64 {
179 let av: *i64 = sys_mmap(8*9) as *i64
180 var k: i64 = 0
181 av[k] = subject as i64; k = k + 1
182 if (a1 as i64) != 0 { av[k] = a1 as i64; k = k + 1 }
183 if (a2 as i64) != 0 { av[k] = a2 as i64; k = k + 1 }
184 if (a3 as i64) != 0 { av[k] = a3 as i64; k = k + 1 }
185 if (a4 as i64) != 0 { av[k] = a4 as i64; k = k + 1 }
186 if (a5 as i64) != 0 { av[k] = a5 as i64; k = k + 1 }
187 if (a6 as i64) != 0 { av[k] = a6 as i64; k = k + 1 }
188 av[k] = 0
189 ol[0] = 0
190 return tr_run_capture_to(subject, av, out, IG_OUT, ol, IG_TMO)
191}
192func ig_show(cond: i64, out: *u8, n: i64) -> i64 {
193 if cond == 1 { return 0 }
194 var m: i64 = n
195 if m > IG_SHOW { m = IG_SHOW }
196 ig_p(" ---- subject output (head) ----" as *u8); ig_nl()
197 sys_write(1, out, m)
198 ig_nl()
199 return 0
200}
201// copy the 64 hex chars that follow `needle` into keyout; 1 ok / 0 absent
202func ig_after_key(out: *u8, n: i64, needle: *u8, keyout: *u8) -> i64 {
203 let p: i64 = gk_out_pos(out, n, needle)
204 if p < 0 { keyout[0] = 0 as u8; return 0 }
205 let off: i64 = p + gk_len(needle)
206 if off + IG_SHA_HEX > n { keyout[0] = 0 as u8; return 0 }
207 var i: i64 = 0
208 while i < IG_SHA_HEX { keyout[i] = out[off+i]; i = i + 1 }
209 keyout[IG_SHA_HEX] = 0 as u8
210 return 1
211}
212func ig_wipe_index() -> i64 { return sys_unlinkat(igp(igp_idx)) }
213func ig_rm_asm(name: *u8) -> i64 { return sys_unlinkat(igp_mk(igp(igp_bld), name) as *u8) }
214
215func main(argc: i64, argv: *i64) -> i64 {
216 gv_head("nx_inc_compile_gate -- LN10 per-module incremental cache: a one-file edit rebuilds ONLY its dependents, and the incremental artifact is byte-identical to a full build" as *u8)
217 let ctr: *i64 = gv_ctr()
218
219 let rt0: *u8 = sys_mmap(IG_PATH)
220 if gv_need("source tree resolvable (gk_corpus_root)" as *u8, gk_corpus_root(rt0), ctr) == 0 {
221 return gv_verdict("nx_inc_compile_gate" as *u8, ctr, "no tree" as *u8)
222 }
223 let rtl: i64 = gk_len(rt0)
224 let buildroot: *u8 = sys_mmap(IG_PATH)
225 var bo: i64 = 0
226 while bo < rtl - 8 { buildroot[bo] = rt0[bo]; bo = bo + 1 }
227 buildroot[bo] = 0 as u8
228 let nishihost: *u8 = sys_mmap(IG_PATH)
229 var ho: i64 = 0
230 if bo > 10 { while ho < bo - 10 { nishihost[ho] = buildroot[ho]; ho = ho + 1 } }
231 nishihost[ho] = 0 as u8
232
233 let subject: *u8 = sys_mmap(IG_PATH)
234 var chosen: *u8 = "argv" as *u8
235 if argc >= 2 {
236 let a1: *u8 = argv[1] as *u8
237 var so: i64 = 0
238 if a1[0] != (47 as u8) {
239 let here: *u8 = sys_mmap(IG_PATH)
240 sys_getcwd(here, IG_PATH)
241 so = gk_cat(subject, 0, here); subject[so] = 47 as u8; so = so + 1
242 }
243 so = gk_cat(subject, so, a1); subject[so] = 0 as u8
244 }
245 if argc < 2 {
246 var so: i64 = gk_cat(subject, 0, nishihost); so = gk_cat(subject, so, "nx_inc_compile.elf" as *u8); subject[so] = 0 as u8
247 chosen = "live(nishihost)" as *u8
248 if gk_exists(subject) == 0 {
249 so = gk_cat(subject, 0, buildroot); so = gk_cat(subject, so, "_build/nx_inc_compile.sov.elf" as *u8); subject[so] = 0 as u8
250 chosen = "built(_build)" as *u8
251 }
252 }
253 let subj_sha: *u8 = sys_mmap(IG_SHA_HEX + 2)
254 let have_subj: i64 = ig_sha(subject, subj_sha)
255 ig_p(" subject=" as *u8); ig_p(subject); ig_p(" [" as *u8); ig_p(chosen); ig_p("] sha256=" as *u8)
256 if have_subj == 1 { ig_p(subj_sha) } else { ig_p("UNREADABLE" as *u8) }
257 ig_nl()
258 if gv_need("subject nx_inc_compile readable" as *u8, have_subj, ctr) == 0 {
259 return gv_verdict("nx_inc_compile_gate" as *u8, ctr, "no subject" as *u8)
260 }
261
262 igp_init(buildroot)
263 ig_p(" fixture=" as *u8); ig_p(igp(igp_fx)); ig_nl()
264 gk_mkdir(igp(igp_fx)); gk_mkdir(igp(igp_tree)); gk_mkdir(igp(igp_know))
265 gk_mkdir(igp(igp_rt)); gk_mkdir(igp(igp_hdl)); gk_mkdir(igp(igp_offc)); gk_mkdir(igp(igp_bld))
266 gk_mkdir(igp(igp_cache))
267 ig_wipe_index()
268 sys_unlinkat(ig_sub(igp_bld, "/icfx_a.s" as *u8))
269 sys_unlinkat(ig_sub(igp_bld, "/icfx_b.s" as *u8))
270 sys_unlinkat(ig_sub(igp_bld, "/icfx_c.s" as *u8))
271 sys_unlinkat(ig_sub(igp_bld, "/icfx_a.sov.elf" as *u8))
272 sys_unlinkat(ig_sub(igp_bld, "/icfx_b.sov.elf" as *u8))
273 sys_unlinkat(ig_sub(igp_bld, "/icfx_c.sov.elf" as *u8))
274 sys_unlinkat(ig_sub(igp_bld, "/icfx_a.lock" as *u8))
275 sys_unlinkat(ig_sub(igp_bld, "/icfx_b.lock" as *u8))
276 sys_unlinkat(ig_sub(igp_bld, "/icfx_c.lock" as *u8))
277
278 // fixture confs: B2 OFF so its whole-target cache cannot serve the hit LN10 is being credited with
279 gk_write(ig_sub(igp_know, "/buildcache.conf" as *u8), "cache_enabled=0\n" as *u8)
280 gk_write(ig_sub(igp_know, "/inccache.conf" as *u8), "cache_enabled=1\ncache_max_bytes=1048576\nruler_timeout_ms=120000\nbuild_timeout_ms=900000\ncrash_guard=nx_crash.nx\ncompiler_path=_offc/nx_cc_sovereign.elf\nassembler_path=_offc/nxasm_x86_main.elf\n" as *u8)
281
282 var ok_src: i64 = 1
283 let p1: *u8 = sys_mmap(IG_PATH)
284 var o1: i64 = gk_cat(p1, 0, rt0); o1 = gk_cat(p1, o1, "nx_syscalls.nx" as *u8); p1[o1] = 0 as u8
285 if ig_copy(p1, ig_sub(igp_rt, "/nx_syscalls.nx" as *u8), IG_MODE_R) == 0 { ok_src = 0 }
286 o1 = gk_cat(p1, 0, rt0); o1 = gk_cat(p1, o1, "nx_tier.nx" as *u8); p1[o1] = 0 as u8
287 if ig_copy(p1, ig_sub(igp_rt, "/nx_tier.nx" as *u8), IG_MODE_R) == 0 { ok_src = 0 }
288 o1 = gk_cat(p1, 0, rt0); o1 = gk_cat(p1, o1, "nx_crash.nx" as *u8); p1[o1] = 0 as u8
289 if ig_copy(p1, ig_sub(igp_rt, "/nx_crash.nx" as *u8), IG_MODE_R) == 0 { ok_src = 0 }
290 if gv_need("fixture closure sources copied (nx_syscalls, nx_tier, nx_crash)" as *u8, ok_src, ctr) == 0 {
291 return gv_verdict("nx_inc_compile_gate" as *u8, ctr, "fixture" as *u8)
292 }
293 var ok_tc: i64 = 1
294 let p2: *u8 = sys_mmap(IG_PATH)
295 var o2: i64 = gk_cat(p2, 0, buildroot); o2 = gk_cat(p2, o2, "_offc/nx_cc_sovereign.elf" as *u8); p2[o2] = 0 as u8
296 let cc_real: *u8 = sys_mmap(IG_SHA_HEX + 2)
297 let cc_copy: *u8 = sys_mmap(IG_SHA_HEX + 2)
298 if ig_sha(p2, cc_real) == 0 { ok_tc = 0 }
299 if ig_copy(p2, igp(igp_cc), IG_MODE_X) == 0 { ok_tc = 0 }
300 o2 = gk_cat(p2, 0, buildroot); o2 = gk_cat(p2, o2, "_offc/nxasm_x86_main.elf" as *u8); p2[o2] = 0 as u8
301 if ig_copy(p2, igp(igp_asm), IG_MODE_X) == 0 { ok_tc = 0 }
302 o2 = gk_cat(p2, 0, nishihost); o2 = gk_cat(p2, o2, "nx_closurehash.elf" as *u8); p2[o2] = 0 as u8
303 if ig_copy(p2, igp(igp_ruler), IG_MODE_X) == 0 { ok_tc = 0 }
304 o2 = gk_cat(p2, 0, nishihost); o2 = gk_cat(p2, o2, "nx_sov_build_run.elf" as *u8); p2[o2] = 0 as u8
305 if ig_copy(p2, igp(igp_builder), IG_MODE_X) == 0 { ok_tc = 0 }
306 if gv_need("fixture _offc holds copies of the compiler, assembler, closure ruler and builder" as *u8, ok_tc, ctr) == 0 {
307 return gv_verdict("nx_inc_compile_gate" as *u8, ctr, "fixture" as *u8)
308 }
309 var cc_same: i64 = 0
310 if ig_sha(igp(igp_cc), cc_copy) == 1 { cc_same = ig_hexeq(cc_real, cc_copy) }
311 gv_check("fixture-reached: the fixture compiler copy re-hashes to the live compiler" as *u8, cc_same, ctr)
312
313 ig_write_lib(IG_VAL_A)
314 ig_write_prog("/icfx_a.nx" as *u8, "a" as *u8, 1)
315 ig_write_prog("/icfx_c.nx" as *u8, "c" as *u8, 1)
316 ig_write_prog("/icfx_b.nx" as *u8, "b" as *u8, 0)
317 gv_check("fixture-reached: three fixture targets written, TWO importing the shared module and one NOT" as *u8, gk_exists(ig_sub(igp_hdl, "/icfx_b.nx" as *u8)), ctr)
318
319 let out: *u8 = sys_mmap(IG_OUT + 16)
320 let ol: *i64 = sys_mmap(16) as *i64
321 let ra: *u8 = igp(igp_rootarg)
322 let sA1: *u8 = sys_mmap(IG_SHA_HEX + 2)
323 let sB1: *u8 = sys_mmap(IG_SHA_HEX + 2)
324 let sC1: *u8 = sys_mmap(IG_SHA_HEX + 2)
325 let sA2: *u8 = sys_mmap(IG_SHA_HEX + 2)
326 let sB2: *u8 = sys_mmap(IG_SHA_HEX + 2)
327 let sA3: *u8 = sys_mmap(IG_SHA_HEX + 2)
328 let kA: *u8 = sys_mmap(IG_SHA_HEX + 2)
329 let kR: *u8 = sys_mmap(IG_SHA_HEX + 2)
330 let eA: *u8 = ig_sub(igp_bld, "/icfx_a.sov.elf" as *u8)
331 let eB: *u8 = ig_sub(igp_bld, "/icfx_b.sov.elf" as *u8)
332 let eC: *u8 = ig_sub(igp_bld, "/icfx_c.sov.elf" as *u8)
333 let asmA: *u8 = ig_sub(igp_bld, "/icfx_a.s" as *u8)
334 let asmB: *u8 = ig_sub(igp_bld, "/icfx_b.s" as *u8)
335 let asmC: *u8 = ig_sub(igp_bld, "/icfx_c.s" as *u8)
336
337 // ---- T1: cold -- every target NEW, every target compiled -------------------------------------------
338 var rc: i64 = ig_run(subject, "build" as *u8, "icfx_a" as *u8, "icfx_b" as *u8, "icfx_c" as *u8, ra, 0 as *u8, out, ol)
339 var c: i64 = (rc == 0) as i64
340 gv_check("T1 cold build: subject exit 0" as *u8, c, ctr); ig_show(c, out, ol[0])
341 gv_check("T1 cold build: every target reports NEW reason=no-record" as *u8, gk_out_has(out, ol[0], "INC icfx_a NEW reason=no-record" as *u8) * gk_out_has(out, ol[0], "INC icfx_b NEW reason=no-record" as *u8) * gk_out_has(out, ol[0], "INC icfx_c NEW reason=no-record" as *u8), ctr)
342 gv_check("T1 cold build: the compiler RAN for all three (the .s witness the hit teeth depend on)" as *u8, gk_exists(asmA) * gk_exists(asmB) * gk_exists(asmC), ctr)
343 gv_check("T1 cold build: all three artifacts present" as *u8, gk_exists(eA) * gk_exists(eB) * gk_exists(eC), ctr)
344 gv_check("T1 cold build: all three artifacts hashable" as *u8, ig_sha(eA, sA1) * ig_sha(eB, sB1) * ig_sha(eC, sC1), ctr)
345 gv_check("T1 cold build: the index was written" as *u8, gk_exists(igp(igp_idx)), ctr)
346
347 // ---- T2: warm, nothing changed -- every target HIT and NOTHING is compiled ------------------------
348 sys_unlinkat(asmA); sys_unlinkat(asmB); sys_unlinkat(asmC)
349 let pre_absent: i64 = ((gk_exists(asmA) == 0) as i64) * ((gk_exists(asmB) == 0) as i64) * ((gk_exists(asmC) == 0) as i64)
350 gv_check("fixture-reached-T2: the .s witnesses really were removed before the warm run" as *u8, pre_absent, ctr)
351 rc = ig_run(subject, "build" as *u8, "icfx_a" as *u8, "icfx_b" as *u8, "icfx_c" as *u8, ra, 0 as *u8, out, ol)
352 c = (rc == 0) as i64
353 gv_check("T2 warm rebuild: subject exit 0" as *u8, c, ctr); ig_show(c, out, ol[0])
354 gv_check("T2 warm rebuild: every target HITs" as *u8, gk_out_has(out, ol[0], "INC icfx_a HIT key=" as *u8) * gk_out_has(out, ol[0], "INC icfx_b HIT key=" as *u8) * gk_out_has(out, ol[0], "INC icfx_c HIT key=" as *u8), ctr)
355 gv_check("T2 warm rebuild: the builder was NOT forked for any of them" as *u8, gk_out_has(out, ol[0], "INC-SKIP icfx_a" as *u8) * gk_out_has(out, ol[0], "INC-SKIP icfx_b" as *u8) * gk_out_has(out, ol[0], "INC-SKIP icfx_c" as *u8), ctr)
356 gv_check("T2 warm rebuild: ZERO compiler invocations (every .s stays ABSENT)" as *u8, ((gk_exists(asmA) == 0) as i64) * ((gk_exists(asmB) == 0) as i64) * ((gk_exists(asmC) == 0) as i64), ctr)
357 gv_check("T2 warm rebuild: artifacts byte-identical to the cold build" as *u8, ig_sha(eA, sA2) * ig_hexeq(sA1, sA2), ctr)
358
359 // ---- T3: ONE-FILE edit of the shared module -- plan names EXACTLY the dependents -------------------
360 ig_write_lib(IG_VAL_B)
361 rc = ig_run(subject, "plan" as *u8, "icfx_a" as *u8, "icfx_b" as *u8, "icfx_c" as *u8, ra, 0 as *u8, out, ol)
362 c = (rc == 0) as i64
363 gv_check("T3 one-file edit: plan exit 0" as *u8, c, ctr); ig_show(c, out, ol[0])
364 gv_check("T3 one-file edit: the DEPENDENTS miss and name the module that moved" as *u8, gk_out_has(out, ol[0], "INC icfx_a MISS reason=module-bytes-moved" as *u8) * gk_out_has(out, ol[0], "INC icfx_c MISS reason=module-bytes-moved" as *u8) * gk_out_has(out, ol[0], "INC-DIRTY icfx_a icfx_lib.nx" as *u8) * gk_out_has(out, ol[0], "INC-DIRTY icfx_c icfx_lib.nx" as *u8), ctr)
365 gv_check("T3 one-file edit: the NON-dependent still HITs -- an edit does not invalidate the estate" as *u8, gk_out_has(out, ol[0], "INC icfx_b HIT key=" as *u8), ctr)
366 gv_check("neg-control-T3-plan-does-not-build: plan forked no builder (no INC-BUILD line) and left the .s absent" as *u8, ((gk_out_has(out, ol[0], "INC-BUILD" as *u8) == 0) as i64) * ((gk_exists(asmA) == 0) as i64), ctr)
367
368 // ---- T4: build after that edit -- ONLY the dependents are compiled ---------------------------------
369 rc = ig_run(subject, "build" as *u8, "icfx_a" as *u8, "icfx_b" as *u8, "icfx_c" as *u8, ra, 0 as *u8, out, ol)
370 c = (rc == 0) as i64
371 gv_check("T4 incremental build: subject exit 0" as *u8, c, ctr); ig_show(c, out, ol[0])
372 gv_check("T4 incremental build: the dependents WERE compiled (.s present for a and c)" as *u8, gk_exists(asmA) * gk_exists(asmC), ctr)
373 gv_check("T4 incremental build: the NON-dependent was NOT compiled (.s for b stays ABSENT)" as *u8, (gk_exists(asmB) == 0) as i64, ctr)
374 gv_check("T4 incremental build: the non-dependent artifact is untouched, byte-identical to T1" as *u8, ig_sha(eB, sB2) * ig_hexeq(sB1, sB2), ctr)
375 var moved_a: i64 = 0
376 if ig_sha(eA, sA2) == 1 { moved_a = (ig_hexeq(sA1, sA2) == 0) as i64 }
377 gv_check("T4 incremental build: the dependent artifact CHANGED (the edit reached the codegen)" as *u8, moved_a, ctr)
378
379 // ---- T5: THE LOAD-BEARING TOOTH -- the incremental artifact equals a FULL build --------------------
380 // The incremental result is banked, then the cache is destroyed and the SAME source is built from
381 // scratch with the cache disabled. Byte equality is the whole promise of a build cache; a cache that
382 // returns a stale object is a silent miscompile, so this is the tooth the rung stands on.
383 ig_wipe_index()
384 sys_unlinkat(asmA)
385 sys_unlinkat(eA)
386 let clean: i64 = ((gk_exists(igp(igp_idx)) == 0) as i64) * ((gk_exists(asmA) == 0) as i64) * ((gk_exists(eA) == 0) as i64)
387 gv_check("fixture-reached-T5: cache, asm and artifact all removed before the full build" as *u8, clean, ctr)
388 rc = ig_run(subject, "build" as *u8, "icfx_a" as *u8, ra, "--no-cache" as *u8, 0 as *u8, 0 as *u8, out, ol)
389 c = (rc == 0) as i64
390 gv_check("T5 full build: subject exit 0 with the cache disabled" as *u8, c, ctr); ig_show(c, out, ol[0])
391 gv_check("fixture-reached-T5: the full build really compiled (.s present)" as *u8, gk_exists(asmA), ctr)
392 gv_check("T5 BYTE IDENTITY: the artifact the incremental path left in place equals the full build, byte for byte" as *u8, ig_sha(eA, sA3) * ig_hexeq(sA2, sA3), ctr)
393
394 // ---- T6: neg-controls ------------------------------------------------------------------------------
395 rc = ig_run(subject, "seed" as *u8, "icfx_a" as *u8, "icfx_b" as *u8, "icfx_c" as *u8, ra, 0 as *u8, out, ol)
396 gv_check("fixture-reached-T6: cache re-seeded for all three targets" as *u8, (rc == 0) as i64 * gk_exists(igp(igp_idx)), ctr)
397 rc = ig_run(subject, "plan" as *u8, "icfx_a" as *u8, ra, "--no-cache" as *u8, 0 as *u8, 0 as *u8, out, ol)
398 gv_check("neg-control-disabled-cache-never-hits: an UNCHANGED tree under --no-cache must MISS, and must not print HIT" as *u8, gk_out_has(out, ol[0], "INC icfx_a MISS reason=disabled-by-flag-or-conf" as *u8) * ((gk_out_has(out, ol[0], "INC icfx_a HIT" as *u8) == 0) as i64), ctr)
399 ig_write_lib(IG_VAL_C)
400 rc = ig_run(subject, "plan" as *u8, "icfx_a" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
401 gv_check("neg-control-changed-input-must-miss: after a one-line edit the HIT detector stays SILENT for the dependent" as *u8, (gk_out_has(out, ol[0], "INC icfx_a HIT" as *u8) == 0) as i64, ctr)
402
403 // ---- T7: the stale-key class -- recording ONE target must invalidate its SIBLING dependents ---------
404 // This is the subtlest way a shared per-module table can produce a FALSE HIT: target A is rebuilt and
405 // records the module's new sha, and target C -- which was never rebuilt -- would then compare its
406 // members against that NEW sha and read as clean while its recorded key describes the OLD tree.
407 rc = ig_run(subject, "build" as *u8, "icfx_a" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
408 c = (rc == 0) as i64
409 gv_check("T7 sibling invalidation: building only the first dependent exits 0" as *u8, c, ctr); ig_show(c, out, ol[0])
410 gv_check("T7 sibling invalidation: recording the new module sha ANNOUNCES the sibling it invalidated" as *u8, gk_out_has(out, ol[0], "INC-INVALIDATE icfx_c by=icfx_lib.nx" as *u8), ctr)
411 rc = ig_run(subject, "plan" as *u8, "icfx_c" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
412 gv_check("neg-control-T7-no-false-hit: the un-rebuilt sibling must NOT read as a hit" as *u8, (gk_out_has(out, ol[0], "INC icfx_c HIT" as *u8) == 0) as i64, ctr); ig_show((gk_out_has(out, ol[0], "INC icfx_c HIT" as *u8) == 0) as i64, out, ol[0])
413
414 // ---- T8: the key is the RULER'S key, not a second one ---------------------------------------------
415 rc = ig_run(subject, "seed" as *u8, "icfx_a" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
416 let got_k: i64 = ig_after_key(out, ol[0], "INC-RECORD icfx_a key=" as *u8, kA)
417 let rav: *i64 = sys_mmap(8*4) as *i64
418 rav[0] = igp_ruler
419 rav[1] = "icfx_a" as *u8 as i64
420 rav[2] = igp_tree
421 rav[3] = 0
422 let rol: *i64 = sys_mmap(16) as *i64
423 rol[0] = 0
424 let rout: *u8 = sys_mmap(IG_OUT + 16)
425 tr_run_capture_to(igp(igp_ruler), rav, rout, IG_OUT, rol, IG_TMO)
426 let got_r: i64 = ig_after_key(rout, rol[0], "closure_sha=" as *u8, kR)
427 ig_p(" recorded key=" as *u8); ig_p(kA); ig_p(" ruler key=" as *u8); ig_p(kR); ig_nl()
428 gv_check("T8 ONE RULER: the key this cache records is byte-identical to nx_closurehash's own closure_sha" as *u8, got_k * got_r * ig_hexeq(kA, kR), ctr)
429
430 // ---- T9: a corrupt index is REFUSED WHOLE, never half-trusted --------------------------------------
431 let ifd: i64 = sys_openat_append(igp(igp_idx), IG_MODE_R)
432 var appended: i64 = 0
433 if ifd >= 0 {
434 let bad: *u8 = "t|icfx_zz|deadbeef|-|0|0|0\n" as *u8
435 if gk_write_all(ifd, bad, gk_len(bad)) > 0 { appended = 1 }
436 sys_close(ifd)
437 }
438 gv_check("fixture-reached-T9: a malformed row really was appended to the index" as *u8, appended, ctr)
439 rc = ig_run(subject, "plan" as *u8, "icfx_a" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
440 gv_check("T9 corrupt index: REFUSED whole and restarted cold -- a half-parsed member list is a false HIT waiting" as *u8, gk_out_has(out, ol[0], "index REFUSED as corrupt" as *u8) * gk_out_has(out, ol[0], "INC icfx_a NEW" as *u8), ctr); ig_show(gk_out_has(out, ol[0], "index REFUSED as corrupt" as *u8), out, ol[0])
441
442 // ---- T10: the toolchain axis the ruler cannot see --------------------------------------------------
443 ig_wipe_index()
444 rc = ig_run(subject, "seed" as *u8, "icfx_a" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
445 gv_check("fixture-reached-T10: re-seeded on a clean index" as *u8, (rc == 0) as i64, ctr)
446 rc = ig_run(subject, "plan" as *u8, "icfx_a" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
447 gv_check("fixture-reached-T10: the target HITs before the toolchain is touched" as *u8, gk_out_has(out, ol[0], "INC icfx_a HIT" as *u8), ctr)
448 let cc_before: i64 = gk_size(igp(igp_cc))
449 let cc_after: i64 = ig_append_byte(igp(igp_cc))
450 gv_check("fixture-reached-T10: the fixture compiler is one byte longer" as *u8, (cc_after == cc_before + 1) as i64, ctr)
451 rc = ig_run(subject, "plan" as *u8, "icfx_a" as *u8, ra, 0 as *u8, 0 as *u8, 0 as *u8, out, ol)
452 gv_check("T10 toolchain axis: a compiler-byte change MISSES on unchanged sources and names the compiler" as *u8, gk_out_has(out, ol[0], "INC icfx_a MISS reason=module-bytes-moved" as *u8) * gk_out_has(out, ol[0], "INC-DIRTY icfx_a _offc/nx_cc_sovereign.elf" as *u8), ctr); ig_show(gk_out_has(out, ol[0], "INC-DIRTY icfx_a _offc/nx_cc_sovereign.elf" as *u8), out, ol[0])
453
454 return gv_verdict("nx_inc_compile_gate" as *u8, ctr, "LN10 inc_cache_lookup" as *u8)
455}