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}