code wiki / _hdl_build / nx_jobhealth_gate.nx

nx_jobhealth_gate.nx source

↩ module page · 160 lines · 8447 B

1// nx_jobhealth_gate.nx -- DOES EVERY REGISTERED CLOCK JOB HAVE AN ORGAN THAT CAN ACTUALLY BE EXECUTED? 2// 3// WHY THIS EXISTS. Measured 2026-08-01: four clock jobs (p384kat, tls12prf, gaterollup, feedgate) had not 4// written a verdict in 6.1 DAYS. The scheduler was healthy the whole time -- proven by registering a fresh 5// job with an ABSOLUTE organ path and watching it dispatch within ~120s. The real cause is that those rows 6// carry BARE-NAME organ paths (nx_p384_ecdh_gate.elf, ...) and no such file exists at the dispatcher's cwd. 7// clk_dispatch_run chmods, forks, execs, and treats a child exit of 127 as "did not run" -- deliberately 8// NOT counting it as dispatched. That is honest accounting AND a silent failure: the job dies on every 9// single tick, the plane still lists it, and the health snapshot still reports the scheduler UP. 10// 11// I ALMOST FILED THAT AS "THE SCHEDULER IS DEAD" (sev-8, retracted within minutes). Four jobs stopping 12// within 40 seconds of each other looked like one dispatcher failure; it was four identical exec failures. 13// AN INFERENCE FROM CORRELATED TIMESTAMPS IS A HYPOTHESIS, NOT A DIAGNOSIS -- this gate is the cheap 14// discriminator I should have had, made standing so nobody has to re-derive it. 15// 16// THE 2026 JOB-QUEUE RULE THIS IMPLEMENTS: "if a job can fail without showing up somewhere you look, it 17// will -- make lost work impossible to ignore." Mature queues answer with a dead-letter queue plus 18// staleness alerting; this substrate has neither, so a registration that can NEVER execute is 19// indistinguishable from one that simply has not come due yet. 20// 21// WHY STAT AND NOT EXEC: a health check must never become an arbitrary-code trigger. Readability is 22// exactly the predicate execve fails on with 127, so this catches the real class without running anything. 23// 24// Rides nx_gate_verdict (migrate-on-touch law D001/L009 -- a new gate must inherit the base class rather 25// than hand-roll its verdict, so the verdict is emitted in ONE place and cannot be written before the work). 26// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 27import "nx_store_seed_lib.nx" 28import "nx_gate_verdict.nx" 29 30const JH_PLANE: *u8 = "knowledge/store/clockjobs-" 31// ★The plane is APPEND-ONLY and the clock applies LAST-DECLARATION-WINS per name (clk_merge_store). 32// A gate that resolves every appended row therefore reports SUPERSEDED rows as live failures and can 33// never go green no matter how the operator fixes them -- measured 2026-08-14, when two corrected rows 34// were appended and the gate kept naming the old ones. Fold to desired state FIRST, then resolve. 35const JH_MAXJOBS: i64 = 512 36const JH_NAMEW: i64 = 256 37func jh_streq(a: *u8, b: *u8) -> i64 { 38 var i: i64 = 0 39 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 40 if b[i] != (0 as u8) { return 0 } 41 return 1 42} 43const JH_CAP: i64 = 65536 44 45// EXACTLY THE PREDICATE execve FAILS ON WITH 127: can this path be opened for reading? 46func jh_resolves(path: *u8) -> i64 { 47 let fd: i64 = sys_openat_rd(path) 48 if fd < 0 { return 0 } 49 sys_close(fd) 50 return 1 51} 52 53func main() -> i64 { 54 gv_head("=== nx_jobhealth_gate: can every registered clock job actually be executed? ===" as *u8) 55 let ctr: *i64 = gv_ctr() 56 57 let buf: *u8 = sys_mmap(JH_CAP) 58 let dn: i64 = sts_load(JH_PLANE, buf, JH_CAP) 59 60 // T1 -- an EMPTY plane would make the organ check vacuously true, so prove there is something to check 61 var rows: i64 = 0 62 var q: i64 = 0 63 while q < dn { if buf[q] == (10 as u8) { rows = rows + 1 } q = q + 1 } 64 var nonempty: i64 = 0 65 if rows > 0 { nonempty = 1 } 66 gv_check("clockjobs plane is non-empty (else the organ check is vacuous)" as *u8, nonempty, ctr) 67 68 // T2 -- every organ path (col 2) resolves to a readable file 69 var unresolved: i64 = 0 70 var checked: i64 = 0 71 let nm: *u8 = sys_mmap(JH_NAMEW) 72 let og: *u8 = sys_mmap(JH_NAMEW) 73 let tnm: *u8 = sys_mmap(JH_MAXJOBS*JH_NAMEW) 74 let tog: *u8 = sys_mmap(JH_MAXJOBS*JH_NAMEW) 75 var njob: i64 = 0 76 var i: i64 = 0 77 var ls: i64 = 0 78 while i < dn { 79 if buf[i] == (10 as u8) { 80 if i > ls { 81 var f: i64 = 0 82 var p: i64 = ls 83 var w: i64 = 0 84 var g: i64 = 0 85 while p < i { 86 if buf[p] == (9 as u8) { f = f + 1 } else { 87 if f == 0 { if w < 255 { nm[w] = buf[p]; w = w + 1 } } 88 else { if f == 2 { if g < 511 { og[g] = buf[p]; g = g + 1 } } } 89 } 90 p = p + 1 91 } 92 nm[w] = 0 as u8 93 og[g] = 0 as u8 94 // ★MATCH THE RESOLVER YOU REPORT ON. clk_dispatch_one SPLITS this field on spaces and execs 95 // argv[0] -- argv support landed 2026-08-07 -- so resolving the WHOLE string reported every 96 // argument-carrying row as dying at exec 127 when the clock in fact runs it fine. MEASURED 97 // 2026-08-14: 7 rows flagged, of which 5 were this false alarm (fallbackharden x2, ddqbeat, 98 // tmpstorereap, swarmheal) and only 2 were real (bare names with no .elf). A false alarm on a 99 // scheduler is expensive precisely because it sends every reader off to re-verify the row and 100 // the elf -- the two things that were never wrong. 101 var sp0: i64 = 0 102 var cut: i64 = 0 103 while cut == 0 { 104 if sp0 >= g { cut = 1 } else { 105 if og[sp0] == (32 as u8) { og[sp0] = 0 as u8; cut = 1 } else { sp0 = sp0 + 1 } 106 } 107 } 108 if g > 0 { 109 // LAST-DECLARATION-WINS fold, mirroring clk_merge_store: replace in place if the name 110 // is already known, else append. Only the surviving rows are resolved, below. 111 var slot: i64 = 0 - 1 112 var q: i64 = 0 113 while q < njob { 114 if jh_streq(((tnm as i64) + q*JH_NAMEW) as *u8, nm) == 1 { slot = q; q = njob } 115 q = q + 1 116 } 117 if slot < 0 { if njob < JH_MAXJOBS { slot = njob; njob = njob + 1 } } 118 if slot >= 0 { 119 var c1: i64 = 0 120 while c1 < JH_NAMEW - 1 { if nm[c1] == (0 as u8) { c1 = JH_NAMEW } else { tnm[slot*JH_NAMEW + c1] = nm[c1]; c1 = c1 + 1 } } 121 var e1: i64 = 0 122 while e1 < JH_NAMEW { if nm[e1] == (0 as u8) { tnm[slot*JH_NAMEW + e1] = 0 as u8; e1 = JH_NAMEW } else { e1 = e1 + 1 } } 123 var c2: i64 = 0 124 while c2 < JH_NAMEW - 1 { if og[c2] == (0 as u8) { c2 = JH_NAMEW } else { tog[slot*JH_NAMEW + c2] = og[c2]; c2 = c2 + 1 } } 125 var e2: i64 = 0 126 while e2 < JH_NAMEW { if og[e2] == (0 as u8) { tog[slot*JH_NAMEW + e2] = 0 as u8; e2 = JH_NAMEW } else { e2 = e2 + 1 } } 127 } 128 } 129 } 130 ls = i + 1 131 } 132 i = i + 1 133 } 134 // resolve the FOLDED desired state -- one verdict per NAME, superseded rows already discarded 135 var r: i64 = 0 136 while r < njob { 137 let rog: *u8 = ((tog as i64) + r*JH_NAMEW) as *u8 138 checked = checked + 1 139 if jh_resolves(rog) == 0 { 140 unresolved = unresolved + 1 141 gv_head(" UNRESOLVABLE-ORGAN -- dies at exec with 127 on EVERY tick:" as *u8) 142 gv_head(((tnm as i64) + r*JH_NAMEW) as *u8) 143 gv_head(rog) 144 } 145 r = r + 1 146 } 147 gv_head(" (folded to desired state: one row per name, last declaration wins)" as *u8) 148 var allres: i64 = 0 149 if unresolved == 0 { allres = 1 } 150 gv_check("every registered clock organ resolves to a readable file" as *u8, allres, ctr) 151 152 // T3 -- NON-VACUITY: the predicate must be able to say NO, or T2's pass proves nothing 153 var neg: i64 = 0 154 if jh_resolves("knowledge/_nx_jobhealth_absent_control.elf" as *u8) == 0 { neg = 1 } 155 gv_check("non-vacuity: an impossible path is REPORTED unresolvable" as *u8, neg, ctr) 156 157 let rc: i64 = gv_verdict("JOBHEALTH-GATE" as *u8, ctr, "every clockjobs organ is executable; a bare name that cannot exec dies silently every tick" as *u8) 158 sys_exit(rc) 159 return rc 160}