code wiki / _hdl_build / nx_gatekit_runner_gate.nx
nx_gatekit_runner_gate.nx source
↩ module page · 120 lines · 6877 B
1// nx_gatekit_runner_gate.nx -- THE BOUNDED RUNNER MUST REPORT A KILLED OR CRASHED SUBJECT AS A FAILURE.
2//
3// MEASURED 2026-08-18 (lane B, first trial of nx_gate_roster_run over the deployed-dark gates):
4// 1787075461 trial nx_activities_gate GREEN exit=0 ms=60443
5// 1787075522 trial nx_doctor_apply_gate GREEN exit=0 ms=60018
6// Both were KILLED by gk_run_cap3's 60 s watchdog and journaled as GREEN, because the runner returned
7// wait_exit_code(status) = (status>>8)&0xFF, which is 0 for a child that died by signal. gk_run had the
8// same hole, so a SEGFAULTING subject read as a pass through every gate built on the kit.
9// ★★★★★★ A WATCHDOG-KILLED SUBJECT THAT RETURNS THE EXIT CODE OF A CLEAN ONE TURNS EVERY TIMEOUT INTO A
10// PASS -- BOUNDING THE WAIT WITHOUT REPORTING THE KILL IS HALF A FIX, AND THE MISSING HALF READS GREEN.
11// The fix is one helper in the base lib (gk_wait_code: 128+signal when the child died by signal) used by
12// both gk_run and gk_run_cap3. This gate is its bite proof, and it uses ITSELF as the subject
13// (`--as-subject <mode>` re-execs /proc/self/exe) so it needs no fixture on disk.
14//
15// TEETH: exit0 -> 0 . exit5 -> 5 . self-SIGKILL -> 137 . a 3 s sleeper under a 500 ms bound -> 137 within
16// 2.5 s . captured output of a clean subject still arrives . BITE: kill fires (rc != 0) on the sleeper
17// and is silent (rc == 0) on the clean subject.
18// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
19import "nx_gatekit_lib.nx"
20import "nx_gate_verdict.nx"
21
22const RG_SLEEP_MS: i64 = 3000
23const RG_BOUND_MS: i64 = 500
24const RG_MAX_KILL_MS: i64 = 2500
25const RG_SIGKILL_RC: i64 = 137
26const RG_CAP: i64 = 4096
27
28func main(argc: i64, argv: *i64) -> i64 {
29 // ---- subject modes: this binary IS the subject when asked ----
30 if argc >= 3 {
31 let a1: *u8 = argv[1] as *u8
32 if gk_streq(a1, "--as-subject" as *u8) == 1 {
33 let mode: *u8 = argv[2] as *u8
34 if gk_streq(mode, "exit0" as *u8) == 1 { let m0: *u8 = "SUBJECT-OK\n" as *u8; gk_say(m0, gk_len(m0)); return 0 }
35 if gk_streq(mode, "exit5" as *u8) == 1 { return 5 }
36 if gk_streq(mode, "sleep" as *u8) == 1 { sys_sleep_ms(RG_SLEEP_MS); return 0 }
37 if gk_streq(mode, "selfkill" as *u8) == 1 {
38 // die by a signal that cannot be caught: the only way to make WEXITSTATUS carry
39 // nothing. (A null deref is NOT that -- the runtime's crash handler exits 139, so a
40 // segv tooth passed with and without the fix and was VACUOUS; measured 2026-08-18.)
41 nx_kill(__syscall(172, 0, 0, 0, 0, 0, 0), 9)
42 return 0
43 }
44 return 9
45 }
46 }
47
48 let ctr: *i64 = gv_ctr()
49 gv_head("nx_gatekit_runner_gate -- a killed or crashed subject must not read as exit 0" as *u8)
50 let self: *u8 = "/proc/self/exe" as *u8
51 let flag: *u8 = "--as-subject" as *u8
52
53 let r0: i64 = gk_run(self, flag, "exit0" as *u8, 0 as *u8, 0 as *u8)
54 var c1: i64 = 0
55 if r0 == 0 { c1 = 1 }
56 gv_check("T1 gk_run: clean subject returns 0" as *u8, c1, ctr)
57
58 let r5: i64 = gk_run(self, flag, "exit5" as *u8, 0 as *u8, 0 as *u8)
59 var c2: i64 = 0
60 if r5 == 5 { c2 = 1 }
61 gv_check("T2 gk_run: exit(5) returns 5 (exit codes still pass through)" as *u8, c2, ctr)
62
63 let rs: i64 = gk_run(self, flag, "selfkill" as *u8, 0 as *u8, 0 as *u8)
64 var c3: i64 = 0
65 if rs == RG_SIGKILL_RC { c3 = 1 }
66 gv_check("T3 gk_run: a subject that DIES BY SIGNAL (SIGKILL) returns 137, never 0" as *u8, c3, ctr)
67
68 let ob: *u8 = sys_mmap(RG_CAP)
69 let ol: *i64 = sys_mmap(16) as *i64
70 let t0: i64 = sys_now_ms()
71 let rk: i64 = gk_run_capture_ms(self, flag, "sleep" as *u8, 0 as *u8, 0 as *u8, RG_BOUND_MS, ob, RG_CAP, ol)
72 let el: i64 = sys_now_ms() - t0
73 var c4: i64 = 0
74 if rk == RG_SIGKILL_RC { c4 = 1 }
75 gv_check("T4 gk_run_capture_ms: a sleeper past its bound returns 137 (128+SIGKILL), never 0" as *u8, c4, ctr)
76 var c5: i64 = 0
77 if el < RG_MAX_KILL_MS { c5 = 1 }
78 gv_check("T5 gk_run_capture_ms: the bound is a ceiling (killed well before the subject own 3 s)" as *u8, c5, ctr)
79
80 let ob2: *u8 = sys_mmap(RG_CAP)
81 let ol2: *i64 = sys_mmap(16) as *i64
82 let rc0: i64 = gk_run_capture_ms(self, flag, "exit0" as *u8, 0 as *u8, 0 as *u8, RG_BOUND_MS, ob2, RG_CAP, ol2)
83 var got: i64 = 0
84 if ol2[0] > 0 { got = gk_out_has(ob2, ol2[0], "SUBJECT-OK" as *u8) }
85 var c6: i64 = 0
86 if rc0 == 0 { if got == 1 { c6 = 1 } }
87 gv_check("T6 gk_run_capture_ms: a clean subject under the same bound returns 0 with its output captured" as *u8, c6, ctr)
88
89 var bad: i64 = 0
90 if rk != 0 { bad = 1 }
91 var good: i64 = 0
92 if rc0 != 0 { good = 1 }
93 gv_bite("T7 BITE: the kill is REPORTED on the sleeper (rc != 0) and SILENT on the clean subject (rc == 0)" as *u8, bad, good, ctr)
94
95 // ---- gk_srcpath: the ROOT must be PROBED, not assumed (2026-09-03) ----------------------------
96 // This primitive shipped with the laptop's absolute prefix baked in, so it could only ever succeed on
97 // one host -- and on every other host it returns 0, which its callers publish as "this target has no
98 // source". That failure is silent and reads as a property of the SUBJECT. These teeth are the
99 // regression guard: they assert resolution WITHOUT any host-specific prefix, so re-hardcoding a root
100 // turns the gate RED on whichever host is not the one that was hardcoded.
101 let spb: *u8 = sys_mmap(4096)
102 let hit1: i64 = gk_srcpath("nx_gatekit_runner_gate" as *u8, spb)
103 gv_check("T8 gk_srcpath resolves a target living in runtime/_hdl_build/ and the path it returns EXISTS" as *u8,
104 hit1 * gk_exists(spb), ctr)
105 // the load-bearing one: run from the tree root the answer must be RELATIVE, i.e. no baked-in host root
106 var rel: i64 = 1
107 if spb[0] == (47 as u8) { rel = 0 }
108 gv_check("T9 gk_srcpath prefers a PROBED relative root over any host-specific prefix (a shared primitive must work on both hosts)" as *u8,
109 rel, ctr)
110 let hit2: i64 = gk_srcpath("nx_gatekit_lib" as *u8, spb)
111 gv_check("T10 gk_srcpath also resolves a target living in the OTHER directory (both dirs, one ruler)" as *u8,
112 hit2 * gk_exists(spb), ctr)
113 let miss: i64 = gk_srcpath("nx_this_target_does_not_exist_anywhere" as *u8, spb)
114 var emptied: i64 = 0
115 if spb[0] == (0 as u8) { emptied = 1 }
116 gv_check("neg-control-gk_srcpath-invents-no-path-for-an-absent-target (returns 0 AND clears out, never a plausible miss)" as *u8,
117 ((miss == 0) as i64) * emptied, ctr)
118
119 return gv_verdict("gatekit_runner" as *u8, ctr, "a subject killed by the watchdog or by a signal returns 128+sig from gk_run / gk_run_capture_ms, and gk_srcpath resolves a target by PROBING roots rather than assuming one host's" as *u8)
120}