nx_import_qualified_gate.nx source
↩ module page · 665 lines · 35943 B
1// nx_import_qualified_gate.nx -- LN14 QUALIFIED IMPORTS: the referee for
2// runtime/nx_import.nx's `import "m.nx" as ns` / `ns::name` rung.
3//
4// WHAT IT PROVES, and the shape of the proof:
5//
6// 1. THE CENSUS IS MEASURED, NOT INHERITED. The matrix row this rung hangs off
7// cites "22 shadow collisions found 2026-06-23". That number is a
8// measurement with a date on it, so this gate RE-TAKES it every run over the
9// full population of runtime/ and runtime/_hdl_build/ (composing
10// nx_janitor_dupname, the organ that already owns that walk -- there is no
11// second ruler here) and prints what it finds, with the SHADOW/COPY/
12// UNREADABLE partition reconciled against the total.
13//
14// 2. A REAL COLLISION FROM THAT CENSUS IS THE FIXTURE. The gate picks the first
15// collision whose two copies genuinely DIFFER and which declares at least one
16// top-level name in BOTH copies, then compiles two programs that import both
17// copies: one plainly, one with the _hdl_build copy qualified. Plain must
18// fail; qualified must build clean. The candidate is checked for solo
19// buildability FIRST, so a RED cannot be a red for some unrelated reason.
20//
21// 3. TWO REAL COMPILERS, NOT A SYNTHESISED MUTANT. Every fixture is built with
22// the CHALLENGER passed in argv AND with the live baseline
23// _offc/nx_cc_sovereign.elf. The qualified program must build under the
24// challenger and must be REFUSED by the baseline: that pair is the bite, and
25// neither half is available to a gate that only ever sees one compiler.
26//
27// 4. EVERY REFUSAL NAMES ITS RULE. The three neg-controls do not merely check
28// "was it refused" -- each greps the compiler's own stderr for the exact
29// rule token nx_import.nx emits (ALIAS-UNKNOWN-NAMESPACE,
30// ALIAS-NO-SUCH-EXPORT, ALIAS-MODULE-ALREADY-IMPORTED). A mutant that
31// disabled one check but still refused for a different reason passes a
32// "was it refused" suite and fails this one.
33//
34// 5. POSITIVE CONTROLS, BECAUSE A GUARD THAT REFUSES EVERYTHING PASSES EVERY
35// NEGATIVE TEST. An ordinary unaliased fixture must BUILD AND RUN under both
36// compilers, and the shipping resolver must still resolve every import edge
37// in the compiler's own closure, bound to the count of files it spliced.
38// Without those, a noexec mount or a missing assembler reads as a verdict
39// about the language.
40//
41// SCOPE STATED PLAINLY, so the next reader does not have to infer it:
42// * The fixture SOURCES are written into runtime/_hdl_build/ and not /tmp,
43// because the import resolver walks up from the importing file's directory
44// and a /tmp source can never reach runtime/nx_syscalls.nx -- a dialect fact
45// pinned by the diff-corpus lane, not a preference. They are written to
46// exactly ONE of the two censused directories, so the census is structurally
47// incapable of counting its own fixtures as a collision. Their content is
48// deterministic, so two concurrent runs write identical bytes. Every .s,
49// .log and .out goes to /tmp/nx_import_qualified_gate/ and every product ELF
50// to _build/ (NAS /tmp is noexec).
51// * When NO alias is declared anywhere in a closure, a stray `::` is left to
52// the tokenizer and produces a parse error rather than a named import
53// refusal. That is deliberate -- scanning every line of every unaliased
54// closure for `::` would charge the whole estate for a feature it is not
55// using -- and it is declared here rather than discovered later.
56//
57// Usage: nx_import_qualified_gate <challenger_cc_elf>
58// The gate CWD is buildroot; /api/build stages into the nishihost ROOT, so the
59// challenger is normally ../nx_compile_x86.sov.elf.new . THERE IS NO DEFAULT:
60// a silent default once made a sibling gate return ten confident verdicts about
61// a stale binary nobody had asked it to test.
62//
63// BITE RESULT, MEASURED 2026-08-25 (planted known-bad on the REAL subject):
64// nx_gate_bite cannot drive this gate. Its END-TO-END mode hands the rebuilt
65// SUBJECT to the gate as argv[1], so the only subject that type-checks here is
66// nx_compile_x86 -- and every mutant build of that DEPLOYS a mutant compiler to
67// the serving root while other seats are compiling through it. Measured, not
68// assumed: a bite with subject=this gate reported
69// `baseline: build_exit=0 gate_exit=3 green=0 ... UNCONTROLLED`, because a gate
70// binary is not a compiler and the gate correctly SKIPped.
71// So the mutation was driven by hand against a subject with ZERO blast radius:
72// imp_alias_exports (nx_import.nx) forced to `return 1`. It is unreachable for
73// any program without an `as` clause, so no other organ in the estate could be
74// affected. Result: 1 valid mutant, 1 KILLED, 0 not-reached --
75// `export-scanner-...` went PASS -> FAIL and the verdict RED 14/15, while every
76// fork-based tooth stayed PASS because those run the UNMUTATED challenger. That
77// split is what attributes the kill to the subject rather than to the harness.
78// Restore proven by BYTES, not asserted: nx_import.nx returned to sha256
79// 9ba376726307bcb5ef633fdf11d127ef62ee788b0fe11bd6e2f6f65e725329a3 (52253 B)
80// and this gate rebuilt to the pre-mutation artifact e37648120ff44b69 exactly.
81//
82// license_tier: ORIGINAL Read-only outside its own fixtures (Rule 26).
83
84import "nx_syscalls.nx"
85import "nx_gate_verdict.nx"
86import "nx_ccbuild_lib.nx"
87import "nx_import.nx"
88import "nx_janitor_dupname.nx"
89
90// jdn_collect's OWN cap is 30000 entries; this must match it or the buffer it
91// writes into is smaller than the buffer it believes it has.
92const IQ_JDN_MAXK: i64 = 30000
93// POSIX NAME_MAX is 255, plus the NUL jdn_collect writes. Sound, not chosen.
94const IQ_NAME_MAX: i64 = 256
95// The same 4 MiB the compiler driver allocates for an expanded closure
96// (nx_compile_x86.nx NX_COMPILE_X86_EXPAND_CAP). Reused rather than re-guessed.
97const IQ_EXPAND_CAP: i64 = 4194304
98// Largest .nx in the tree is well under this; jdn_pair_differs needs one buffer
99// per side and reports -1 rather than truncating, which the partition counts.
100const IQ_PAIR_CAP: i64 = 4194304
101// No fixture below is more than ~20 short lines; the only variable part is a
102// single filesystem basename (IQ_NAME_MAX).
103const IQ_FIXTURE_CAP: i64 = 4096
104const IQ_PATH_CAP: i64 = 1024
105const IQ_MODE_644: i64 = 420
106const IQ_MODE_755: i64 = 493
107// A wait status carries the exit code in its second byte.
108const IQ_STATUS_SHIFT: i64 = 256
109const IQ_BYTE: i64 = 256
110// NOTE: the closure floor is NOT a constant here. The first draft of this gate
111// guessed 50 against a real closure of 30 and failed a resolver that was working
112// perfectly -- a hand-picked bound that is either unreachable or vacuous, and
113// that one was both. It is DERIVED at runtime from the root file's own declared
114// import edges; see the positive control in main().
115
116func iq_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
117
118func iq_cat(d: *u8, o: i64, s: *u8) -> i64 {
119 var i: i64 = 0
120 while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 }
121 return o + i
122}
123
124func iq_catb(d: *u8, o: i64, b: i64) -> i64 { d[o] = b as u8; return o + 1 }
125
126// `import "<name>"` + newline. The quote byte is CONSTRUCTED rather than written
127// as an escape inside a literal: a bare escaped quote is the one construct this
128// dialect's lexer is least reliable about, and a fixture that fails to assemble
129// correctly is a gate that measures its own string handling.
130func iq_imp(d: *u8, o: i64, name: *u8) -> i64 {
131 var p: i64 = iq_cat(d, o, "import " as *u8)
132 p = iq_catb(d, p, 0x22)
133 p = iq_cat(d, p, name)
134 p = iq_catb(d, p, 0x22)
135 return iq_catb(d, p, 0x0A)
136}
137
138func iq_imp_as(d: *u8, o: i64, name: *u8, ns: *u8) -> i64 {
139 var p: i64 = iq_cat(d, o, "import " as *u8)
140 p = iq_catb(d, p, 0x22)
141 p = iq_cat(d, p, name)
142 p = iq_catb(d, p, 0x22)
143 p = iq_cat(d, p, " as " as *u8)
144 p = iq_cat(d, p, ns)
145 return iq_catb(d, p, 0x0A)
146}
147
148// Truncate-write. Returns bytes written, or negative. A fixture that failed to
149// land must be a PRECONDITION failure, never a silent pass on stale content from
150// a previous run.
151func iq_put(path: *u8, body: *u8, n: i64) -> i64 {
152 let fd: i64 = sys_openat_wr(path, IQ_MODE_644)
153 if fd < 0 { return 0 - 1 }
154 let w: i64 = sys_write(fd, body, n)
155 sys_close(fd)
156 if w != n { return 0 - 2 }
157 return n
158}
159
160// Does the file at `path` contain `needle`? Composes sys_read_file, which sizes
161// its buffer from the file and cannot short-read, so there is no cap to guess.
162func iq_contains(path: *u8, needle: *u8) -> i64 {
163 let lp: *u8 = sys_mmap(16)
164 let lo: *i64 = lp as *i64
165 *lo = 0
166 let buf: *u8 = sys_read_file(path, lo)
167 if buf == (0 as *u8) { return 0 }
168 let n: i64 = *lo
169 let nl: i64 = iq_slen(needle)
170 if nl == 0 { return 0 }
171 if n < nl { return 0 }
172 var i: i64 = 0
173 while i <= n - nl {
174 var j: i64 = 0
175 var eq: i64 = 1
176 while j < nl {
177 if buf[i + j] != needle[j] { eq = 0 }
178 j = j + 1
179 }
180 if eq == 1 { return 1 }
181 i = i + 1
182 }
183 return 0
184}
185
186func iq_join(out: *u8, a: *u8, b: *u8) -> i64 {
187 var p: i64 = iq_cat(out, 0, a)
188 p = iq_cat(out, p, b)
189 out[p] = 0 as u8
190 return p
191}
192
193func iq_exit_code(st: i64) -> i64 {
194 if st < 0 { return 0 - 1 }
195 return (st / IQ_STATUS_SHIFT) % IQ_BYTE
196}
197
198func iq_kv(k: *u8, v: i64) -> i64 {
199 gv_puts(" " as *u8)
200 gv_puts(k)
201 gv_puts("=" as *u8)
202 gv_num(v)
203 gv_puts("\n" as *u8)
204 return 0
205}
206
207// Compile <src> with <cc>, tagging every temp with <tag>. Returns cb_build's rc
208// (0 ok, 1 compile-fail, 2 asm-fail, 3 rename-fail). The compiler's stderr lands
209// in a REAL log so a refusal can be read for the rule it names, never /dev/null.
210func iq_build(cc: *u8, src: *u8, tag: *u8, envp: *i64, elf_out: *u8, cclog: *u8) -> i64 {
211 let s_tmp: *u8 = sys_mmap(IQ_PATH_CAP)
212 let a_tmp: *u8 = sys_mmap(IQ_PATH_CAP)
213 let a_log: *u8 = sys_mmap(IQ_PATH_CAP)
214 var p: i64 = iq_cat(s_tmp, 0, "/tmp/nx_import_qualified_gate/" as *u8)
215 p = iq_cat(s_tmp, p, tag)
216 p = iq_cat(s_tmp, p, ".s" as *u8)
217 s_tmp[p] = 0 as u8
218 p = iq_cat(a_tmp, 0, "_build/_nxiq_asm_" as *u8)
219 p = iq_cat(a_tmp, p, tag)
220 p = iq_cat(a_tmp, p, ".elf" as *u8)
221 a_tmp[p] = 0 as u8
222 p = iq_cat(a_log, 0, "/tmp/nx_import_qualified_gate/" as *u8)
223 p = iq_cat(a_log, p, tag)
224 p = iq_cat(a_log, p, ".asm.log" as *u8)
225 a_log[p] = 0 as u8
226 let lfd: i64 = sys_openat_wr(cclog, IQ_MODE_644)
227 let rc: i64 = cb_build(cc, src, s_tmp, elf_out, envp, lfd, a_tmp, a_log)
228 sys_close(lfd)
229 return rc
230}
231
232// How many top-level names does alias slot `k` share with alias slot `j`?
233// Composes the SHIPPING export scanner, so the census and the compiler cannot
234// disagree about what a module declares -- there is one definition of "export"
235// in this estate and it is nx_import.nx's.
236func iq_shared_syms(ctx: *ExpandCtx, k: i64, j: i64) -> i64 {
237 let ea: i64 = imp_alias_slot(ctx, k, AL_F_EXPADDR)
238 if ea == 0 { return 0 }
239 let eaddr: *i64 = ea as *i64
240 let elen: *i64 = imp_alias_slot(ctx, k, AL_F_EXPLEN) as *i64
241 let cnt: i64 = imp_alias_slot(ctx, k, AL_F_EXPN)
242 var shared: i64 = 0
243 var i: i64 = 0
244 while i < cnt {
245 if imp_alias_exports(ctx, j, eaddr[i] as *u8, 0, elen[i]) == 1 { shared = shared + 1 }
246 i = i + 1
247 }
248 return shared
249}
250
251func main(argc: i64, argv: *i64) -> i64 {
252 sys_ignore_sigpipe()
253 let ctr: *i64 = gv_ctr()
254 gv_head("nx_import_qualified_gate -- LN14 namespace-qualified imports: two organs may export one identifier" as *u8)
255
256 let have_tree: i64 = cb_anchor_root()
257 var challenger: *u8 = "\x00" as *u8
258 var given: i64 = 0
259 if argc >= 2 { challenger = argv[1] as *u8; given = 1 }
260 if given == 0 {
261 gv_puts(" NO CHALLENGER IN ARGV. This gate compares a CANDIDATE compiler against the live one and\n" as *u8)
262 gv_puts(" will not guess which candidate you mean: a silent default once made a sibling gate return\n" as *u8)
263 gv_puts(" ten confident verdicts about a stale binary nobody had asked it to test. Gate CWD is\n" as *u8)
264 gv_puts(" buildroot and /api/build stages into the nishihost ROOT, so pass ../nx_compile_x86.sov.elf.new\n" as *u8)
265 gv_puts(" MEASURED 2026-08-25: /api/gate_run does NOT forward arguments to the gate, so this gate can\n" as *u8)
266 gv_puts(" only ever report SKIP on that surface -- by construction, not by fault. Drive it with\n" as *u8)
267 gv_puts(" nx_job_run nx_import_qualified_gate /tmp/<out> <challenger>. A MISSING SUBJECT IS NOT A\n" as *u8)
268 gv_puts(" FAILING SUBJECT, so this path returns SKIP and never RED: a gate that is permanently red\n" as *u8)
269 gv_puts(" on a roster is one everybody learns to ignore.\n" as *u8)
270 }
271 let baseline: *u8 = "_offc/nx_cc_sovereign.elf\x00" as *u8
272 gv_puts(" challenger=" as *u8); gv_puts(challenger); gv_puts("\n" as *u8)
273 gv_puts(" baseline=" as *u8); gv_puts(baseline); gv_puts("\n\n" as *u8)
274
275 let envp: *i64 = sys_mmap(8 * 2) as *i64
276 envp[0] = "PATH=/usr/bin:/bin\x00" as *u8 as i64
277 envp[1] = 0
278 sys_mkdir("/tmp/nx_import_qualified_gate\x00" as *u8, IQ_MODE_755)
279
280 // ---- preconditions: I COULD NOT LOOK is not IT IS BROKEN ----------------
281 var ok: i64 = 1
282 if gv_need("challenger-compiler-named-in-argv" as *u8, given, ctr) == 0 { ok = 0 }
283 if gv_need("buildroot-tree-reachable-from-cwd" as *u8, have_tree, ctr) == 0 { ok = 0 }
284 let fch: i64 = sys_openat_rd(challenger)
285 var have_ch: i64 = 0
286 if fch >= 0 { have_ch = 1; sys_close(fch) }
287 if gv_need("challenger-elf-readable" as *u8, have_ch, ctr) == 0 { ok = 0 }
288 let fbl: i64 = sys_openat_rd(baseline)
289 var have_bl: i64 = 0
290 if fbl >= 0 { have_bl = 1; sys_close(fbl) }
291 if gv_need("baseline-elf-readable" as *u8, have_bl, ctr) == 0 { ok = 0 }
292 let fas: i64 = sys_openat_rd("_offc/nxasm_x86_main.elf\x00" as *u8)
293 var have_as: i64 = 0
294 if fas >= 0 { have_as = 1; sys_close(fas) }
295 if gv_need("sovereign-assembler-present" as *u8, have_as, ctr) == 0 { ok = 0 }
296 if ok == 0 {
297 gv_puts("\n" as *u8)
298 sys_exit(gv_verdict("IMPORT-QUALIFIED" as *u8, ctr, "preconditions absent -- SKIP is not a pass" as *u8))
299 }
300
301 // ---- 1. THE CENSUS, TAKEN BEFORE ANY FIXTURE EXISTS ---------------------
302 // Order matters: the fixtures are written after this point, so nothing this
303 // gate creates can be inside the population it reports.
304 let fnames: *u8 = sys_mmap(IQ_JDN_MAXK * IQ_NAME_MAX)
305 let fnoff_raw: *u8 = sys_mmap((IQ_JDN_MAXK + 2) * 8)
306 let fnoff: *i64 = fnoff_raw as *i64
307 let hc_raw: *u8 = sys_mmap(16)
308 let hc_out: *i64 = hc_raw as *i64
309 hc_out[0] = 0
310 let NF: i64 = jdn_scan(fnames, fnoff, hc_out)
311 let hcount: i64 = hc_out[0]
312 let ncoll: i64 = jdn_collisions(fnames, fnoff, hcount, NF)
313 gv_puts(" CENSUS runtime/_hdl_build vs runtime, re-measured this run:\n" as *u8)
314 iq_kv("hdl_build_nx_files" as *u8, hcount)
315 iq_kv("runtime_nx_files" as *u8, NF - hcount)
316 iq_kv("shadow_collisions" as *u8, ncoll)
317 iq_kv("corpus_complete" as *u8, 1)
318
319 // partition the collisions by CONTENT, and reconcile the parts to the total
320 let br: *u8 = sys_mmap(IQ_PAIR_CAP)
321 let bh: *u8 = sys_mmap(IQ_PAIR_CAP)
322 var c_differ: i64 = 0
323 var c_same: i64 = 0
324 var c_unread: i64 = 0
325 var ci: i64 = hcount
326 while ci < NF {
327 if jdn_is_shadowed(fnames, fnoff, ci, hcount) == 1 {
328 let nm: *u8 = (fnames as i64 + fnoff[ci]) as *u8
329 let d: i64 = jdn_pair_differs(nm, br, bh, IQ_PAIR_CAP)
330 if d == 1 { c_differ = c_differ + 1 }
331 if d == 0 { c_same = c_same + 1 }
332 if d < 0 { c_unread = c_unread + 1 }
333 }
334 ci = ci + 1
335 }
336 iq_kv("collisions_SHADOW_differing_bytes" as *u8, c_differ)
337 iq_kv("collisions_COPY_identical_bytes" as *u8, c_same)
338 iq_kv("collisions_UNREADABLE" as *u8, c_unread)
339 var partition_ok: i64 = 0
340 if c_differ + c_same + c_unread == ncoll { partition_ok = 1 }
341 gv_check("census-partition-SUMS-to-the-collision-total" as *u8, partition_ok, ctr)
342
343 // ---- 2. PICK A REAL COLLISION AS THE FIXTURE ----------------------------
344 // Requirements, in the order they are cheap to test: the two copies must
345 // DIFFER (an identical copy collides with nothing), they must declare at
346 // least one top-level name in COMMON (that shared name is the whole point),
347 // and the runtime copy must compile on its own (so a later RED cannot be a
348 // red for an unrelated reason).
349 let realname: *u8 = sys_mmap(IQ_NAME_MAX)
350 realname[0] = 0 as u8
351 var real_shared: i64 = 0
352 var cand_tried: i64 = 0
353 var have_real: i64 = 0
354 let pr: *u8 = sys_mmap(IQ_PATH_CAP)
355 let ph: *u8 = sys_mmap(IQ_PATH_CAP)
356 let lp1: *i64 = sys_mmap(16) as *i64
357 let lp2: *i64 = sys_mmap(16) as *i64
358 let solo_body: *u8 = sys_mmap(IQ_FIXTURE_CAP)
359 let dotdot: *u8 = sys_mmap(IQ_PATH_CAP)
360 ci = hcount
361 while ci < NF {
362 if have_real == 0 {
363 if jdn_is_shadowed(fnames, fnoff, ci, hcount) == 1 {
364 let nm: *u8 = (fnames as i64 + fnoff[ci]) as *u8
365 if jdn_pair_differs(nm, br, bh, IQ_PAIR_CAP) == 1 {
366 cand_tried = cand_tried + 1
367 iq_join(pr, "runtime/\x00" as *u8, nm)
368 iq_join(ph, "runtime/_hdl_build/\x00" as *u8, nm)
369 lp1[0] = 0
370 lp2[0] = 0
371 let s1: *u8 = sys_read_file(pr, lp1)
372 let s2: *u8 = sys_read_file(ph, lp2)
373 if s1 != (0 as *u8) { if s2 != (0 as *u8) {
374 // A FRESH context per candidate: alias slots are
375 // write-once by design, so reusing one would refuse.
376 let cctx: *ExpandCtx = expand_ctx_new(sys_mmap(IQ_FIXTURE_CAP), IQ_FIXTURE_CAP)
377 let ka: i64 = imp_alias_add(cctx, "rt\x00" as *u8, 0, 2)
378 let kb: i64 = imp_alias_add(cctx, "hb\x00" as *u8, 0, 2)
379 if ka >= 0 { if kb >= 0 {
380 imp_alias_scan_exports(cctx, ka, s1, lp1[0])
381 imp_alias_scan_exports(cctx, kb, s2, lp2[0])
382 let sh: i64 = iq_shared_syms(cctx, ka, kb)
383 if sh > 0 {
384 // solo-buildability precondition
385 iq_join(dotdot, "../\x00" as *u8, nm)
386 var q: i64 = iq_imp(solo_body, 0, "nx_syscalls.nx\x00" as *u8)
387 q = iq_imp(solo_body, q, dotdot)
388 q = iq_cat(solo_body, q, "func main() -> i64 { sys_exit(0)\n return 0\n}\n" as *u8)
389 if iq_put("runtime/_hdl_build/_nxiq_real_solo.nx\x00" as *u8, solo_body, q) > 0 {
390 let srcb: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_real_solo.nx\x00" as *u8, "solo\x00" as *u8, envp, "_build/_nxiq_solo.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/solo.cc.log\x00" as *u8)
391 if srcb == 0 {
392 var w: i64 = 0
393 while nm[w] != (0 as u8) { realname[w] = nm[w]; w = w + 1 }
394 realname[w] = 0 as u8
395 real_shared = sh
396 have_real = 1
397 }
398 }
399 }
400 } }
401 } }
402 }
403 }
404 }
405 ci = ci + 1
406 }
407 gv_puts(" REAL COLLISION FIXTURE:\n" as *u8)
408 gv_puts(" chosen=" as *u8)
409 if have_real == 1 { gv_puts(realname) } else { gv_puts("NONE" as *u8) }
410 gv_puts("\n" as *u8)
411 iq_kv("candidates_tried" as *u8, cand_tried)
412 iq_kv("shared_top_level_names_in_both_copies" as *u8, real_shared)
413
414 // ---- 3. WRITE THE SYNTHETIC FIXTURES ------------------------------------
415 let b: *u8 = sys_mmap(IQ_FIXTURE_CAP)
416 var n: i64 = 0
417 var writes_ok: i64 = 1
418
419 n = iq_cat(b, 0, "func nxiq_collide(x: i64) -> i64 { return x + 101 }\nfunc nxiq_a_only(x: i64) -> i64 { return nxiq_collide(x) }\n" as *u8)
420 if iq_put("runtime/_hdl_build/_nxiq_a.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
421
422 n = iq_cat(b, 0, "func nxiq_collide(x: i64) -> i64 { return x + 202 }\nfunc nxiq_b_only(x: i64) -> i64 { return nxiq_collide(x) }\n" as *u8)
423 if iq_put("runtime/_hdl_build/_nxiq_b.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
424
425 // control: an ordinary unaliased program that must build and run everywhere
426 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
427 n = iq_imp(b, n, "_nxiq_a.nx\x00" as *u8)
428 n = iq_cat(b, n, "func main() -> i64 {\n var code: i64 = 0\n if nxiq_a_only(0) != 101 { code = 7 }\n sys_exit(code)\n return 0\n}\n" as *u8)
429 if iq_put("runtime/_hdl_build/_nxiq_ctl.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
430
431 // THE QUALIFIED PROGRAM: reaches BOTH definitions of one identifier.
432 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
433 n = iq_imp(b, n, "_nxiq_a.nx\x00" as *u8)
434 n = iq_imp_as(b, n, "_nxiq_b.nx\x00" as *u8, "bee\x00" as *u8)
435 n = iq_cat(b, n, "func main() -> i64 {\n let a: i64 = nxiq_collide(0)\n let bb: i64 = bee::nxiq_collide(0)\n let c: i64 = bee::nxiq_b_only(0)\n var code: i64 = 0\n if a != 101 { code = 1 }\n if bb != 202 { code = 2 }\n if c != 202 { code = 3 }\n sys_exit(code)\n return 0\n}\n" as *u8)
436 if iq_put("runtime/_hdl_build/_nxiq_qual.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
437
438 // THE SAME PROGRAM WITHOUT QUALIFICATION. If it builds at all, `a` and `bb`
439 // are one binding, so one of the two checks MUST fail: it cannot exit 0.
440 // That is structural, not a snapshot of today's resolution order.
441 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
442 n = iq_imp(b, n, "_nxiq_a.nx\x00" as *u8)
443 n = iq_imp(b, n, "_nxiq_b.nx\x00" as *u8)
444 n = iq_cat(b, n, "func main() -> i64 {\n let a: i64 = nxiq_collide(0)\n let bb: i64 = nxiq_collide(0)\n let c: i64 = nxiq_b_only(0)\n var code: i64 = 0\n if a != 101 { code = 1 }\n if bb != 202 { code = 2 }\n if c != 202 { code = 3 }\n sys_exit(code)\n return 0\n}\n" as *u8)
445 if iq_put("runtime/_hdl_build/_nxiq_plain.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
446
447 // the rename must REMOVE the unqualified spelling, not merely add one
448 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
449 n = iq_imp_as(b, n, "_nxiq_b.nx\x00" as *u8, "bee\x00" as *u8)
450 n = iq_cat(b, n, "func main() -> i64 {\n sys_exit(nxiq_b_only(0))\n return 0\n}\n" as *u8)
451 if iq_put("runtime/_hdl_build/_nxiq_strip.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
452
453 // neg-control 1: a namespace nobody declared
454 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
455 n = iq_imp_as(b, n, "_nxiq_b.nx\x00" as *u8, "bee\x00" as *u8)
456 n = iq_cat(b, n, "func main() -> i64 {\n sys_exit(zzz::nxiq_collide(0))\n return 0\n}\n" as *u8)
457 if iq_put("runtime/_hdl_build/_nxiq_unkns.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
458
459 // neg-control 2: a name that module does not declare
460 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
461 n = iq_imp_as(b, n, "_nxiq_b.nx\x00" as *u8, "bee\x00" as *u8)
462 n = iq_cat(b, n, "func main() -> i64 {\n sys_exit(bee::nxiq_not_declared(0))\n return 0\n}\n" as *u8)
463 if iq_put("runtime/_hdl_build/_nxiq_noexport.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
464
465 // neg-control 3: one module reached both plainly and under an alias
466 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
467 n = iq_imp(b, n, "_nxiq_b.nx\x00" as *u8)
468 n = iq_imp_as(b, n, "_nxiq_b.nx\x00" as *u8, "bee\x00" as *u8)
469 n = iq_cat(b, n, "func main() -> i64 {\n sys_exit(0)\n return 0\n}\n" as *u8)
470 if iq_put("runtime/_hdl_build/_nxiq_conflict.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
471
472 // the real pair, both ways
473 if have_real == 1 {
474 iq_join(dotdot, "../\x00" as *u8, realname)
475 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
476 n = iq_imp(b, n, dotdot)
477 n = iq_imp(b, n, realname)
478 n = iq_cat(b, n, "func main() -> i64 {\n sys_exit(0)\n return 0\n}\n" as *u8)
479 if iq_put("runtime/_hdl_build/_nxiq_real_plain.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
480 n = iq_imp(b, 0, "nx_syscalls.nx\x00" as *u8)
481 n = iq_imp(b, n, dotdot)
482 n = iq_imp_as(b, n, realname, "hb\x00" as *u8)
483 n = iq_cat(b, n, "func main() -> i64 {\n sys_exit(0)\n return 0\n}\n" as *u8)
484 if iq_put("runtime/_hdl_build/_nxiq_real_qual.nx\x00" as *u8, b, n) < 0 { writes_ok = 0 }
485 }
486 if gv_need("fixtures-written" as *u8, writes_ok, ctr) == 0 {
487 gv_puts("\n" as *u8)
488 sys_exit(gv_verdict("IMPORT-QUALIFIED" as *u8, ctr, "fixture tree not writable -- SKIP is not a pass" as *u8))
489 }
490
491 // ---- 4. BUILD AND RUN ---------------------------------------------------
492 let ctl_ch: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_ctl.nx\x00" as *u8, "ctlch\x00" as *u8, envp, "_build/_nxiq_ctl_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/ctlch.cc.log\x00" as *u8)
493 var ctl_ch_rc: i64 = 0 - 1
494 if ctl_ch == 0 { ctl_ch_rc = iq_exit_code(cb_run_capture("_build/_nxiq_ctl_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/ctlch.out\x00" as *u8, envp, 2)) }
495 let ctl_bl: i64 = iq_build(baseline, "runtime/_hdl_build/_nxiq_ctl.nx\x00" as *u8, "ctlbl\x00" as *u8, envp, "_build/_nxiq_ctl_bl.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/ctlbl.cc.log\x00" as *u8)
496 var ctl_bl_rc: i64 = 0 - 1
497 if ctl_bl == 0 { ctl_bl_rc = iq_exit_code(cb_run_capture("_build/_nxiq_ctl_bl.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/ctlbl.out\x00" as *u8, envp, 2)) }
498
499 let qual_ch: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_qual.nx\x00" as *u8, "qualch\x00" as *u8, envp, "_build/_nxiq_qual_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/qualch.cc.log\x00" as *u8)
500 var qual_ch_rc: i64 = 0 - 1
501 if qual_ch == 0 { qual_ch_rc = iq_exit_code(cb_run_capture("_build/_nxiq_qual_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/qualch.out\x00" as *u8, envp, 2)) }
502 let qual_bl: i64 = iq_build(baseline, "runtime/_hdl_build/_nxiq_qual.nx\x00" as *u8, "qualbl\x00" as *u8, envp, "_build/_nxiq_qual_bl.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/qualbl.cc.log\x00" as *u8)
503
504 let plain_ch: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_plain.nx\x00" as *u8, "plainch\x00" as *u8, envp, "_build/_nxiq_plain_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/plainch.cc.log\x00" as *u8)
505 var plain_ch_rc: i64 = 0 - 1
506 if plain_ch == 0 { plain_ch_rc = iq_exit_code(cb_run_capture("_build/_nxiq_plain_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/plainch.out\x00" as *u8, envp, 2)) }
507
508 let strip_ch: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_strip.nx\x00" as *u8, "stripch\x00" as *u8, envp, "_build/_nxiq_strip_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/stripch.cc.log\x00" as *u8)
509 let unkns_ch: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_unkns.nx\x00" as *u8, "unknsch\x00" as *u8, envp, "_build/_nxiq_unkns_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/unknsch.cc.log\x00" as *u8)
510 let noexp_ch: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_noexport.nx\x00" as *u8, "noexpch\x00" as *u8, envp, "_build/_nxiq_noexp_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/noexpch.cc.log\x00" as *u8)
511 let confl_ch: i64 = iq_build(challenger, "runtime/_hdl_build/_nxiq_conflict.nx\x00" as *u8, "conflch\x00" as *u8, envp, "_build/_nxiq_confl_ch.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/conflch.cc.log\x00" as *u8)
512
513 var realp_ch: i64 = 0 - 1
514 var realq_ch: i64 = 0 - 1
515 if have_real == 1 {
516 realp_ch = iq_build(challenger, "runtime/_hdl_build/_nxiq_real_plain.nx\x00" as *u8, "realp\x00" as *u8, envp, "_build/_nxiq_realp.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/realp.cc.log\x00" as *u8)
517 realq_ch = iq_build(challenger, "runtime/_hdl_build/_nxiq_real_qual.nx\x00" as *u8, "realq\x00" as *u8, envp, "_build/_nxiq_realq.elf\x00" as *u8, "/tmp/nx_import_qualified_gate/realq.cc.log\x00" as *u8)
518 }
519
520 gv_puts(" MEASURED (build rc: 0 ok 1 compile-fail 2 asm-fail 3 rename-fail; run rc: -1 not run)\n" as *u8)
521 iq_kv("ctl_challenger_build" as *u8, ctl_ch)
522 iq_kv("ctl_challenger_run" as *u8, ctl_ch_rc)
523 iq_kv("ctl_baseline_build" as *u8, ctl_bl)
524 iq_kv("ctl_baseline_run" as *u8, ctl_bl_rc)
525 iq_kv("qualified_challenger_build" as *u8, qual_ch)
526 iq_kv("qualified_challenger_run" as *u8, qual_ch_rc)
527 iq_kv("qualified_baseline_build" as *u8, qual_bl)
528 iq_kv("unqualified_challenger_build" as *u8, plain_ch)
529 iq_kv("unqualified_challenger_run" as *u8, plain_ch_rc)
530 iq_kv("strip_probe_build" as *u8, strip_ch)
531 iq_kv("negctl_unknown_ns_build" as *u8, unkns_ch)
532 iq_kv("negctl_no_such_export_build" as *u8, noexp_ch)
533 iq_kv("negctl_alias_and_plain_build" as *u8, confl_ch)
534 iq_kv("real_pair_unqualified_build" as *u8, realp_ch)
535 iq_kv("real_pair_qualified_build" as *u8, realq_ch)
536
537 // ---- 5. THE POSITIVE CONTROL ON THE SHIPPING RESOLVER -------------------
538 // Bound to its denominator: the count of DISTINCT files the expander actually
539 // resolved and spliced. A resolver that silently stopped resolving would
540 // either return a negative rc or splice a handful of files, and the floor
541 // catches the second case. This exercises the in-process copy of the new
542 // nx_import.nx; the forked builds above exercise the challenger's copy.
543 let obuf: *u8 = sys_mmap(IQ_EXPAND_CAP)
544 let ectx: *ExpandCtx = expand_ctx_new(obuf, IQ_EXPAND_CAP)
545 let erc: i64 = expand_imports(ectx, "runtime/nx_compile_x86.nx\x00" as *u8)
546 let enp: *i64 = ectx.n_paths
547 let efiles: i64 = enp[0]
548 // THE FLOOR IS DERIVED FROM THE ROOT ITSELF, using the SHIPPING predicate the
549 // preprocessor uses to recognise an import line -- so the control and the
550 // subject agree on what an import edge IS by construction. The closure must
551 // hold at least the root's own declared edges plus the root, and a resolver
552 // that quietly stopped resolving cannot clear that.
553 let rlp: *i64 = sys_mmap(16) as *i64
554 *rlp = 0
555 let rsrc: *u8 = sys_read_file("runtime/nx_compile_x86.nx\x00" as *u8, rlp)
556 var direct: i64 = 0
557 if rsrc != (0 as *u8) {
558 let rn: i64 = *rlp
559 var rp: i64 = 0
560 while rp < rn {
561 var at: i64 = 0
562 if rp == 0 { at = 1 }
563 if rp > 0 { if rsrc[rp-1] == 0x0A { at = 1 } }
564 if at == 1 { if starts_with_import(rsrc, rp) == 1 { direct = direct + 1 } }
565 rp = rp + 1
566 }
567 }
568 let edge_floor: i64 = direct + 1
569 iq_kv("compiler_closure_expand_rc" as *u8, erc)
570 iq_kv("compiler_closure_files_resolved" as *u8, efiles)
571 iq_kv("root_declared_import_edges" as *u8, direct)
572 iq_kv("derived_closure_floor" as *u8, edge_floor)
573
574 // ---- 6. THE EXPORT SCANNER, UNIT-LEVEL ----------------------------------
575 let sb: *u8 = sys_mmap(IQ_FIXTURE_CAP)
576 var sn: i64 = iq_cat(sb, 0, "func alpha(x: i64) -> i64 { return x }\nconst beta: i64 = 3\nstruct gamma {\n delta: i64,\n}\nfunc main() -> i64 { return 0 }\n func indented_is_not_top_level() -> i64 { return 0 }\n" as *u8)
577 sb[sn] = 0 as u8
578 let uctx: *ExpandCtx = expand_ctx_new(sys_mmap(IQ_FIXTURE_CAP), IQ_FIXTURE_CAP)
579 let uk: i64 = imp_alias_add(uctx, "u\x00" as *u8, 0, 1)
580 let ucnt: i64 = imp_alias_scan_exports(uctx, uk, sb, sn)
581 var scan_ok: i64 = 0
582 if ucnt == 3 {
583 if imp_alias_exports(uctx, uk, "alpha\x00" as *u8, 0, 5) == 1 {
584 if imp_alias_exports(uctx, uk, "beta\x00" as *u8, 0, 4) == 1 {
585 if imp_alias_exports(uctx, uk, "gamma\x00" as *u8, 0, 5) == 1 {
586 if imp_alias_exports(uctx, uk, "main\x00" as *u8, 0, 4) == 0 {
587 if imp_alias_exports(uctx, uk, "delta\x00" as *u8, 0, 5) == 0 { scan_ok = 1 }
588 }
589 }
590 }
591 }
592 }
593 iq_kv("export_scanner_decls_found" as *u8, ucnt)
594 gv_puts("\n" as *u8)
595
596 // ---- 7. TEETH -----------------------------------------------------------
597 var t: i64 = 0
598
599 t = 0
600 if ctl_ch == 0 { if ctl_ch_rc == 0 { t = 1 } }
601 gv_check("pos-control-ordinary-unaliased-program-BUILDS-AND-RUNS-under-challenger" as *u8, t, ctr)
602
603 t = 0
604 if ctl_bl == 0 { if ctl_bl_rc == 0 { t = 1 } }
605 gv_check("pos-control-same-program-BUILDS-AND-RUNS-under-baseline-so-a-RED-below-is-the-language-not-the-environment" as *u8, t, ctr)
606
607 t = 0
608 if erc == 0 { if efiles >= edge_floor { t = 1 } }
609 gv_check("pos-control-shipping-resolver-still-resolves-EVERY-import-edge-of-the-compiler-closure" as *u8, t, ctr)
610
611 gv_check("export-scanner-finds-func-const-struct-at-column-0-and-excludes-main-and-struct-fields" as *u8, scan_ok, ctr)
612
613 t = 0
614 if qual_ch == 0 { if qual_ch_rc == 0 { t = 1 } }
615 gv_check("qualified-program-reaches-BOTH-same-named-exports-with-the-right-values" as *u8, t, ctr)
616
617 t = 0
618 if plain_ch != 0 { t = 1 }
619 if plain_ch_rc != 0 { t = 1 }
620 gv_check("same-program-WITHOUT-qualification-cannot-reach-both-bindings" as *u8, t, ctr)
621
622 t = 0
623 if strip_ch != 0 { t = 1 }
624 gv_check("rename-REMOVES-the-unqualified-spelling-rather-than-adding-a-second-one" as *u8, t, ctr)
625
626 t = 0
627 if qual_bl != 0 { t = 1 }
628 gv_check("neg-control-BASELINE-compiler-refuses-the-qualified-program-so-the-green-above-is-the-change" as *u8, t, ctr)
629
630 t = 0
631 if unkns_ch != 0 {
632 if iq_contains("/tmp/nx_import_qualified_gate/unknsch.cc.log\x00" as *u8, "rule=ALIAS-UNKNOWN-NAMESPACE\x00" as *u8) == 1 { t = 1 }
633 }
634 gv_check("neg-control-undeclared-namespace-refused-and-the-log-NAMES-rule-ALIAS-UNKNOWN-NAMESPACE" as *u8, t, ctr)
635
636 t = 0
637 if noexp_ch != 0 {
638 if iq_contains("/tmp/nx_import_qualified_gate/noexpch.cc.log\x00" as *u8, "rule=ALIAS-NO-SUCH-EXPORT\x00" as *u8) == 1 { t = 1 }
639 }
640 gv_check("neg-control-name-the-module-does-not-declare-refused-and-the-log-NAMES-rule-ALIAS-NO-SUCH-EXPORT" as *u8, t, ctr)
641
642 t = 0
643 if confl_ch != 0 {
644 if iq_contains("/tmp/nx_import_qualified_gate/conflch.cc.log\x00" as *u8, "rule=ALIAS-MODULE-ALREADY-IMPORTED\x00" as *u8) == 1 { t = 1 }
645 }
646 gv_check("neg-control-module-imported-plainly-AND-aliased-refused-and-the-log-NAMES-rule-ALIAS-MODULE-ALREADY-IMPORTED" as *u8, t, ctr)
647
648 // The real-collision teeth abstain rather than acquit when the census found
649 // no usable candidate -- a population of zero is the absence of evidence
650 // about the subject, not a pass.
651 if gv_subjects("real shadow collisions usable as a fixture" as *u8, have_real, ctr) == 1 {
652 gv_check("fixture-reached-the-condition-both-copies-declare-a-shared-top-level-name" as *u8, 1, ctr)
653 t = 0
654 if realp_ch != 0 { t = 1 }
655 gv_check("REAL-census-collision-imported-both-ways-UNQUALIFIED-fails-to-build" as *u8, t, ctr)
656 t = 0
657 if realq_ch == 0 { t = 1 }
658 gv_check("REAL-census-collision-imported-both-ways-QUALIFIED-builds-clean" as *u8, t, ctr)
659 }
660
661 gv_puts("\n" as *u8)
662 // gv_verdict journals its own outcome; a second call here would double-count
663 // this gate in every harness-erosion query built on that journal.
664 return gv_verdict("IMPORT-QUALIFIED" as *u8, ctr, "challenger vs live baseline over a re-measured collision census" as *u8)
665}