code wiki / (root) / nx_sigfixture.nx

nx_sigfixture.nx source

↩ module page · 97 lines · 5214 B

1// nx_sigfixture.nx -- A PROCESS THAT DIES BY THE SIGNAL YOU NAME: THE ONE FIXTURE FOR EVERY WAIT-STATUS RULER. 2// 3// WHY (2026-09-05, measured on the promote path). nx_behaveprobe reported rc=0 for a gate the crash guard 4// had killed with SIGSEGV, twice, and that receipt cleared a crashing gate for promote. The estate's rule 5// already existed (wait_status_rc: a signal death reads 128+signal, never 0; nx_waitrc_gate proves it 6// in-process with a forked child) -- the served probe was simply older than the rule. What the estate had 7// no way to do was ASK A LIVE, REGISTERED RULER how it reads a signal death, because no registered subject 8// dies by a signal on purpose. This organ is that subject: nx_behaveprobe, nx_job_run, /api/gate_run and 9// every other consumer of the tr_run_capture family can be pointed at it and their rc read back. 10// 11// usage: nx_sigfixture <signal 1..64> -- prints one line, then kill(getpid(), signal) on ITSELF 12// nx_sigfixture exit <code> -- prints one line, then exits with that code (the control arm) 13// SIGTERM (15) is the honest signal: no guard catches it, so the wait status carries a TRUE signal death 14// and a correct ruler reads 143 where the old accessor read 0. SIGKILL (9) is the watchdog class (the 15// bounded capture maps its own kill to TR_ERR_TIMEOUT). A guarded fault signal (SIGSEGV 11) is caught by the 16// default-on crash guard and surfaces as exit 139 -- that is the guard's contract and this fixture does not 17// pretend otherwise: pass 11 and you measure the guard, pass 15 and you measure the ruler. 18// The line it prints BEFORE dying exists so a capture is never empty by cause: an empty capture plus rc=0 19// is the shape of a tool that never ran, and this fixture must never be mistaken for one. 20// If the signal is blocked or ignored the process is still alive afterwards; it says so and exits 21// SF_EXIT_SURVIVED, never 0, so a ruler cannot be acquitted by a signal that never landed. 22// license_tier: ORIGINAL expect_exit: 143 23import "nx_syscalls.nx" 24 25const SF_SYS_GETPID_RV64: i64 = 172 // rv64 getpid, translated by __syscall (witness: nx_getpid_const_probe) 26const SF_EXIT_USAGE: i64 = 2 27const SF_EXIT_SURVIVED: i64 = 3 // the fleet's UNOBSERVABLE code: the signal never landed 28const SF_SIGNAL_MIN: i64 = 1 29const SF_SIGNAL_MAX: i64 = 64 30const SF_DIGIT_ZERO: i64 = 48 31const SF_DIGIT_NINE: i64 = 57 32const SF_DEC_BASE: i64 = 10 33const SF_NUM_CAP: i64 = 32 34 35func sf_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 36func sf_pn(v: i64) -> i64 { 37 if v == 0 { sf_puts("0" as *u8); return 0 } 38 var x: i64 = v 39 if x < 0 { sf_puts("-" as *u8); x = 0 - x } 40 let t: *u8 = sys_mmap(SF_NUM_CAP) 41 var n: i64 = 0 42 while x > 0 { t[n] = (SF_DIGIT_ZERO + (x % SF_DEC_BASE)) as u8; n = n + 1; x = x / SF_DEC_BASE } 43 let o: *u8 = sys_mmap(SF_NUM_CAP) 44 var i: i64 = n 45 var j: i64 = 0 46 while i > 0 { i = i - 1; o[j] = t[i]; j = j + 1 } 47 o[j] = 0 as u8 48 sf_puts(o) 49 return 0 50} 51// decimal parse; -1 when any byte is not a digit or the string is empty 52func sf_atoi(s: *u8) -> i64 { 53 var v: i64 = 0 54 var i: i64 = 0 55 while s[i] != (0 as u8) { 56 let c: i64 = s[i] as i64 57 if c < SF_DIGIT_ZERO { return 0 - 1 } 58 if c > SF_DIGIT_NINE { return 0 - 1 } 59 v = v * SF_DEC_BASE + (c - SF_DIGIT_ZERO) 60 i = i + 1 61 } 62 if i == 0 { return 0 - 1 } 63 return v 64} 65func sf_eq(a: *u8, b: *u8) -> i64 { 66 var i: i64 = 0 67 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 68 if b[i] != (0 as u8) { return 0 } 69 return 1 70} 71func sf_usage() -> i64 { 72 sf_puts("usage: nx_sigfixture <signal 1..64> | nx_sigfixture exit <code> -- dies by that signal on itself (SIGTERM=15 is a true signal death, SIGKILL=9 the watchdog class, SIGSEGV=11 measures the crash guard); a wait-status ruler must read 128+signal, never 0\n" as *u8) 73 return SF_EXIT_USAGE 74} 75 76func main(argc: i64, argv: *i64) -> i64 { 77 if argc < 2 { let u: i64 = sf_usage(); sys_exit(u); return u } 78 let a1: *u8 = argv[1] as *u8 79 if sf_eq(a1, "exit" as *u8) == 1 { 80 if argc < 3 { let u2: i64 = sf_usage(); sys_exit(u2); return u2 } 81 let code: i64 = sf_atoi(argv[2] as *u8) 82 if code < 0 { let u3: i64 = sf_usage(); sys_exit(u3); return u3 } 83 sf_puts("SIGFIXTURE control arm: exiting with code " as *u8); sf_pn(code); sf_puts(" -- a ruler must read exactly this\n" as *u8) 84 sys_exit(code) 85 return code 86 } 87 let sig: i64 = sf_atoi(a1) 88 if sig < SF_SIGNAL_MIN { let u4: i64 = sf_usage(); sys_exit(u4); return u4 } 89 if sig > SF_SIGNAL_MAX { let u5: i64 = sf_usage(); sys_exit(u5); return u5 } 90 let pid: i64 = __syscall(SF_SYS_GETPID_RV64, 0, 0, 0, 0, 0, 0) 91 sf_puts("SIGFIXTURE dying by signal " as *u8); sf_pn(sig); sf_puts(" on pid " as *u8); sf_pn(pid) 92 sf_puts(" -- a ruler that reads this run as rc=0 has lost the signal; the honest value is 128+signal\n" as *u8) 93 nx_kill(pid, sig) 94 sf_puts("SIGFIXTURE SURVIVED the signal -- it was blocked or ignored; exiting the UNOBSERVABLE code, never 0\n" as *u8) 95 sys_exit(SF_EXIT_SURVIVED) 96 return SF_EXIT_SURVIVED 97}