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}