nx_sigfixture.nx
buildroot/runtime/nx_sigfixture.nx
about
nx_sigfixture.nx -- A PROCESS THAT DIES BY THE SIGNAL YOU NAME: THE ONE FIXTURE FOR EVERY WAIT-STATUS RULER.
WHY (2026-09-05, measured on the promote path). nx_behaveprobe reported rc=0 for a gate the crash guard
had killed with SIGSEGV, twice, and that receipt cleared a crashing gate for promote. The estate's rule
already existed (wait_status_rc: a signal death reads 128+signal, never 0; nx_waitrc_gate proves it
in-process with a forked child) -- the served probe was simply older than the rule. What the estate had
no way to do was ASK A LIVE, REGISTERED RULER how it reads a signal death, because no registered subject
dies by a signal on purpose. This organ is that subject: nx_behaveprobe, nx_job_run, /api/gate_run and
every other consumer of the tr_run_capture family can be pointed at it and their rc read back.
usage: nx_sigfixture <signal 1..64> -- prints one line, then kill(getpid(), signal) on ITSELF
nx_sigfixture exit <code> -- prints one line, then exits with that code (the control arm)
SIGTERM (15) is the honest signal: no guard catches it, so the wait status carries a TRUE signal death
and a correct ruler reads 143 where the old accessor read 0. SIGKILL (9) is the watchdog class (the
bounded capture maps its own kill to TR_ERR_TIMEOUT). A guarded fault signal (SIGSEGV 11) is caught by the
default-on crash guard and surfaces as exit 139 -- that is the guard's contract and this fixture does not
pretend otherwise: pass 11 and you measure the guard, pass 15 and you measure the ruler.
The line it prints BEFORE dying exists so a capture is never empty by cause: an empty capture plus rc=0
is the shape of a tool that never ran, and this fixture must never be mistaken for one.
If the signal is blocked or ignored the process is still alive afterwards; it says so and exits
SF_EXIT_SURVIVED, never 0, so a ruler cannot be acquitted by a signal that never landed.
license_tier: ORIGINAL expect_exit: 143
dependencies 1 imports · 0 importers
imports: nx_syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 25 | const SF_SYS_GETPID_RV64: i64 = 172 // rv64 getpid, translated by __syscall (witness: nx_getpid_const_probe) |
| 26 | const SF_EXIT_USAGE: i64 = 2 |
| 27 | const SF_EXIT_SURVIVED: i64 = 3 // the fleet's UNOBSERVABLE code: the signal never landed |
| 28 | const SF_SIGNAL_MIN: i64 = 1 |
| 29 | const SF_SIGNAL_MAX: i64 = 64 |
| 30 | const SF_DIGIT_ZERO: i64 = 48 |
| 31 | const SF_DIGIT_NINE: i64 = 57 |
| 32 | const SF_DEC_BASE: i64 = 10 |
| 33 | const SF_NUM_CAP: i64 = 32 |
functions
| 35 | func 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 } |
| 36 | func sf_pn(v: i64) -> i64 |
| 52 | func sf_atoi(s: *u8) -> i64 called by 1: main |
| 65 | func sf_eq(a: *u8, b: *u8) -> i64 called by 1: main |
| 71 | func sf_usage() -> i64 |
| 76 | func main(argc: i64, argv: *i64) -> i64 |