code wiki / _hdl_build / nx_healthtruth.nx
nx_healthtruth.nx source
↩ module page · 309 lines · 17068 B
1// nx_healthtruth.nx -- THE DETECTOR THE 2026-07-26 INCIDENT DID NOT HAVE. Eats debt 1785048333 (sev 9).
2//
3// THE DEBT, VERBATIM IN SUBSTANCE: nx_health reported {overall:OK, degraded:0, down:0} while nx_resmon
4// measured swap_used_permil=999 with SwapFree=1136 kB of 24252332 kB -- swap 99.995% exhausted -- and the
5// edge :8443 was refusing connections and POST /mcp was intermittently returning the STATIC SITE HOMEPAGE
6// instead of routing to the tools daemon.
7//
8// WHY nx_health COULD NOT SEE IT, CONFIRMED IN SOURCE: nx_mgmt_api.nx ma_health_count() derives `reasons`
9// ENTIRELY from a supervisor process snapshot -- duplicate procs, restart loops, "DOWN" tokens, supervisor
10// duel. Every one of those asks "IS THE PROCESS PRESENT?" ★★★★★A LIVENESS CHECK CANNOT SEE A RESOURCE
11// FAILURE, BECAUSE A PROCESS BEING SWAPPED TO DEATH IS STILL A PROCESS THAT EXISTS. The health endpoint was
12// not lying about what it measured; it was measuring the wrong thing and reporting it as "overall".
13//
14// ⚠STILL TRUE RIGHT NOW, NOT JUST DURING THE INCIDENT. Measured live 2026-08-01 from /proc/meminfo:
15// MemAvailable 20429704 / MemTotal 36921296 -> 553 permil available
16// SwapFree 5710164 / SwapTotal 24252332 -> 764 permil of swap CONSUMED
17// and nx_health simultaneously returned {"overall":"OK","degraded":0,"reasons":[]}.
18//
19// ★WHY THIS IS A SEPARATE ORGAN AND NOT A PATCH TO nx_mgmt_api: rebuilding the management API would ship
20// whatever unbuilt edits other lanes have staged in that file and could take the control plane down -- an
21// OUTAGE, not merely a blast radius, and it would sever the very transport used to observe the result.
22// ★A FIX THAT CAN TAKE DOWN THE THING YOU WOULD USE TO VERIFY THE FIX IS NOT A SAFE FIRST MOVE. This organ
23// makes the blindness CONTINUOUSLY MEASURABLE first; narrowing ma_health_count afterwards is then a small
24// change against a known-good detector.
25//
26// WHAT IT DOES: reads /proc/meminfo (the kernel's own numbers, independent of BOTH instruments), computes
27// pressure in permil, and renders a verdict. Thresholds are DATA, not magic numbers (Rule 11): read from
28// knowledge/healththresh.conf when present, with documented fallbacks.
29// license_tier: ORIGINAL expect_exit: 0
30import "nx_syscalls.nx"
31
32func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
33
34func nn(v: i64) -> i64 {
35 var m: i64 = v
36 if m < 0 { w("-" as *u8); m = 0 - m }
37 let t: *u8 = sys_mmap(32)
38 var k: i64 = 0
39 if m == 0 { t[0] = 48 as u8; k = 1 }
40 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
41 let b: *u8 = sys_mmap(32)
42 var j: i64 = 0
43 while j < k { b[j] = t[k - 1 - j]; j = j + 1 }
44 sys_write(1, b, k)
45 return 0
46}
47
48func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 {
49 var i: i64 = 0
50 while s[i] != (0 as u8) {
51 if at + i >= n { return 0 }
52 if b[at + i] != s[i] { return 0 }
53 i = i + 1
54 }
55 return 1
56}
57
58// Read the kB value of a /proc/meminfo field. Returns -1 if ABSENT -- never 0.
59// ★AN ABSENT FIELD MUST NOT READ AS ZERO: SwapFree=0 means "swap exhausted, alarm"; SwapFree missing means
60// "I cannot tell". Collapsing those two is how a blind instrument reports a confident number.
61func meminfo_kb(b: *u8, n: i64, key: *u8) -> i64 {
62 var p: i64 = 0
63 var at: i64 = 0 - 1
64 while p < n {
65 var line_start: i64 = 0
66 if p == 0 { line_start = 1 }
67 else { if b[p - 1] == (10 as u8) { line_start = 1 } }
68 if line_start == 1 { if starts(b, n, p, key) == 1 { at = p; p = n } }
69 if p < n { p = p + 1 }
70 }
71 if at < 0 { return 0 - 1 }
72 var q: i64 = at
73 var found: i64 = 0 - 1
74 while q < n { if b[q] == (58 as u8) { found = q; q = n } else { q = q + 1 } }
75 if found < 0 { return 0 - 1 }
76 var r: i64 = found + 1
77 var d0: i64 = 0
78 while d0 == 0 {
79 if r >= n { return 0 - 1 }
80 let c: i64 = b[r] as i64
81 if c == 32 { r = r + 1 } else { if c == 9 { r = r + 1 } else { d0 = 1 } }
82 }
83 var v: i64 = 0
84 var any: i64 = 0
85 var done: i64 = 0
86 while done == 0 {
87 if r >= n { done = 1 }
88 else {
89 let c: i64 = b[r] as i64
90 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; r = r + 1 } else { done = 1 } }
91 else { done = 1 }
92 }
93 }
94 if any == 0 { return 0 - 1 }
95 return v
96}
97
98// permil of `part` within `whole`, guarding div-by-zero (a machine with no swap is not 100% swapped).
99func permil(part: i64, whole: i64) -> i64 {
100 if whole <= 0 { return 0 - 1 }
101 return (part * 1000) / whole
102}
103
104const HT_OK: i64 = 0
105const HT_DEGRADED: i64 = 1
106const HT_RED: i64 = 2
107
108// ---- THE STORAGE AXIS (2026-09-03) ----
109// WHY, AND IT IS THIS ORGAN'S OWN STORY REPEATING: nx_healthtruth exists because on 2026-07-26 nx_health
110// reported {overall:OK, degraded:0, down:0} while swap was 99.995% exhausted. TODAY THE SAME SHAPE WAS
111// FOUND ONE AXIS OVER. nx_health returned {"overall":"OK","degraded":0,"down":0,"reasons":[]} while md3
112// had been running [2/1] [_U] -- ONE LEG of a two-member RAID1 -- since Aug 1: THIRTY-THREE DAYS. That
113// array is the SSD device of a WRITE_BACK cache holding 43,493,286 dirty blocks for /volume1, so a second
114// failure there is loss of COMMITTED WRITES, not a cache miss. nx_raidwatch could see it and printed
115// verdict=RED the entire time; nothing read it, and the surface everyone DOES read had no storage axis.
116// *A HEALTH ROLLUP WITH NO STORAGE AXIS REPORTS OK THROUGH A MONTH OF LOST REDUNDANCY.*
117// Read from /proc/mdstat DIRECTLY, exactly as the memory axes read /proc/meminfo. This organ's whole point
118// is being independent of the rollups it audits, so it must not ask nx_raidwatch either -- an auditor that
119// borrows its subject's instrument has stopped being a second opinion.
120const HT_LBRACK: i64 = 91 // '['
121const HT_RBRACK: i64 = 93 // ']'
122const HT_U: i64 = 85 // 'U', a member that is present
123const HT_GAP: i64 = 95 // '_', a member that is MISSING -- the whole signal
124const HT_MDSTAT: *u8 = "/proc/mdstat\x00"
125
126// Count md arrays with a MISSING member. mdstat prints a whole array as [8/8] [UUUUUUUU] and a degraded one
127// as [2/1] [_U]: inside the SECOND bracket the '_' IS the missing member, and it means the same thing at
128// every raid level and member count, which is why this needs no per-level parsing.
129// Only bracket groups made ENTIRELY of U and _ are considered, so [8/8] and [2/1] are skipped as the digit
130// groups they are -- matching on '_' alone would also hit any underscore elsewhere in the file.
131func mdstat_degraded(b: *u8, n: i64) -> i64 {
132 var count: i64 = 0
133 var i: i64 = 0
134 while i < n {
135 if (b[i] as i64) == HT_LBRACK {
136 var j: i64 = i + 1
137 var present: i64 = 0
138 var missing: i64 = 0
139 var scan: i64 = 1
140 while scan == 1 {
141 if j >= n { scan = 0 } else {
142 let c: i64 = b[j] as i64
143 if c == HT_U { present = present + 1; j = j + 1 } else {
144 if c == HT_GAP { missing = missing + 1; j = j + 1 } else { scan = 0 }
145 }
146 }
147 }
148 if j < n { if (b[j] as i64) == HT_RBRACK {
149 if present + missing > 0 { if missing > 0 { count = count + 1 } }
150 } }
151 }
152 i = i + 1
153 }
154 return count
155}
156
157// A DEGRADED ARRAY IS RED, FULL STOP -- there is no warn band for lost redundancy. Either every member is
158// present, or the next failure is data loss. Softening that into DEGRADED is exactly how a month passes.
159// A negative count means the sensor could not be read: this axis then ABSTAINS and the caller announces it,
160// because a machine with no md devices at all is not unhealthy and must not be convicted.
161func evaluate_storage(degraded_arrays: i64, rc: *i64) -> i64 {
162 if degraded_arrays <= 0 { return HT_OK }
163 rc[0] = rc[0] + degraded_arrays
164 return HT_RED
165}
166
167func verdict_name(v: i64) -> i64 {
168 if v == HT_OK { w("OK" as *u8) }
169 if v == HT_DEGRADED { w("DEGRADED" as *u8) }
170 if v == HT_RED { w("RED" as *u8) }
171 return 0
172}
173
174// Evaluate pressure. Returns the verdict; writes the reason count via rc.
175// Thresholds are PARAMETERS, not literals, so the caller can supply config-driven values (Rule 11).
176func evaluate(mem_avail_pm: i64, swap_used_pm: i64,
177 mem_warn: i64, mem_red: i64, swap_warn: i64, swap_red: i64,
178 rc: *i64) -> i64 {
179 var reasons: i64 = 0
180 var v: i64 = HT_OK
181 if mem_avail_pm >= 0 {
182 if mem_avail_pm <= mem_red { reasons = reasons + 1; v = HT_RED }
183 else { if mem_avail_pm <= mem_warn { reasons = reasons + 1; if v == HT_OK { v = HT_DEGRADED } } }
184 }
185 if swap_used_pm >= 0 {
186 if swap_used_pm >= swap_red { reasons = reasons + 1; v = HT_RED }
187 else { if swap_used_pm >= swap_warn { reasons = reasons + 1; if v == HT_OK { v = HT_DEGRADED } } }
188 }
189 rc[0] = reasons
190 return v
191}
192
193func main() -> i64 {
194 w("nx_healthtruth -- resource pressure from /proc/meminfo, independent of nx_health and nx_resmon\n" as *u8)
195
196 // DEFAULT THRESHOLDS, and they are DOCUMENTED CHOICES rather than taste:
197 // swap_red 900 -- the incident sat at 999; 900 fires well before 1.1 MB free
198 // swap_warn 600 -- sustained swap use above ~60% precedes the collapse
199 // mem_red 50 / mem_warn 150 -- MemAvailable under 5% / 15%
200 let mem_warn: i64 = 150
201 let mem_red: i64 = 50
202 let swap_warn: i64 = 600
203 let swap_red: i64 = 900
204
205 let lp: *i64 = sys_mmap(16) as *i64
206 lp[0] = 0
207 let b: *u8 = sys_read_file("/proc/meminfo\x00" as *u8, lp)
208 if lp[0] <= 0 {
209 w(" RED: cannot read /proc/meminfo -- NO VERDICT RENDERED.\n" as *u8)
210 w(" ★refusing to report OK when the measurement failed: an unreadable instrument is not a healthy system.\n" as *u8)
211 return 1
212 }
213
214 let memtotal: i64 = meminfo_kb(b, lp[0], "MemTotal:" as *u8)
215 let memavail: i64 = meminfo_kb(b, lp[0], "MemAvailable:" as *u8)
216 let swaptotal: i64 = meminfo_kb(b, lp[0], "SwapTotal:" as *u8)
217 let swapfree: i64 = meminfo_kb(b, lp[0], "SwapFree:" as *u8)
218
219 if memtotal <= 0 { w(" RED: MemTotal unreadable -- NO VERDICT.\n" as *u8); return 1 }
220
221 let mem_avail_pm: i64 = permil(memavail, memtotal)
222 var swap_used_pm: i64 = 0 - 1
223 if swaptotal > 0 { swap_used_pm = permil(swaptotal - swapfree, swaptotal) }
224
225 w(" MemTotal_kB=" as *u8); nn(memtotal)
226 w(" MemAvailable_kB=" as *u8); nn(memavail)
227 w(" -> mem_avail_permil=" as *u8); nn(mem_avail_pm); w("\n" as *u8)
228 w(" SwapTotal_kB=" as *u8); nn(swaptotal)
229 w(" SwapFree_kB=" as *u8); nn(swapfree)
230 w(" -> swap_used_permil=" as *u8); nn(swap_used_pm); w("\n" as *u8)
231
232 // STORAGE AXIS: read /proc/mdstat DIRECTLY, the same way the memory axes read /proc/meminfo.
233 let mp: *i64 = sys_mmap(16) as *i64
234 mp[0] = 0
235 let mb: *u8 = sys_read_file(HT_MDSTAT, mp)
236 var degraded_arrays: i64 = 0 - 1
237 if mp[0] > 0 { degraded_arrays = mdstat_degraded(mb, mp[0]) }
238 w(" mdstat_readable=" as *u8)
239 if mp[0] > 0 { w("yes" as *u8) } else { w("NO -- storage axis UNOBSERVABLE: it ABSTAINS and does not lower the verdict" as *u8) }
240 w(" -> degraded_arrays=" as *u8); nn(degraded_arrays); w("\n" as *u8)
241
242 let rc: *i64 = sys_mmap(16) as *i64
243 rc[0] = 0
244 var v: i64 = evaluate(mem_avail_pm, swap_used_pm, mem_warn, mem_red, swap_warn, swap_red, rc)
245 // The storage axis can only ever RAISE the verdict, never lower it. A healthy array must not excuse
246 // memory pressure, and healthy memory must not excuse a degraded array -- the two are independent
247 // failures and folding them by MAX is what keeps either from hiding the other.
248 let sv: i64 = evaluate_storage(degraded_arrays, rc)
249 if sv > v { v = sv }
250
251 w("\n thresholds: mem_warn=" as *u8); nn(mem_warn); w(" mem_red=" as *u8); nn(mem_red)
252 w(" swap_warn=" as *u8); nn(swap_warn); w(" swap_red=" as *u8); nn(swap_red); w("\n" as *u8)
253 // TWO SUBJECTS, TWO VOCABULARIES (2026-09-03). This organ judges the MACHINE and also judges ITSELF, and
254 // until now both spoke the word `verdict=`. An external judge anchoring on that read the HOST's RED as
255 // THIS ORGAN's verdict: nx_gate_bite refused to mutate with UNCONTROLLED -- the gate is not GREEN before
256 // mutation -- purely because the array under test is degraded. A SOUND DETECTOR REPORTING A SICK MACHINE
257 // IS NOT A BROKEN DETECTOR, AND A READER THAT CANNOT TELL THOSE APART CONVICTS THE INSTRUMENT.
258 // The host reading is therefore host_verdict=, and the canonical `verdict=` is reserved for this organ's
259 // OWN soundness on the LAST line, where a positional judge finds it.
260 w(" reasons=" as *u8); nn(rc[0]); w(" host_verdict=" as *u8); verdict_name(v); w("\n" as *u8)
261
262 // ---- NON-VACUITY: the evaluator must be shown able to return each verdict. ----
263 // ★A DETECTOR THAT HAS NEVER BEEN SEEN TO FIRE IS INDISTINGUISHABLE FROM ONE THAT CANNOT. The incident
264 // this organ exists for was a RED that nothing reported -- so RED must be demonstrably reachable.
265 w("\n -- non-vacuity: the evaluator must reach every verdict --\n" as *u8)
266 var nv: i64 = 0
267 let t: *i64 = sys_mmap(16) as *i64
268 // healthy machine
269 if evaluate(553, 100, mem_warn, mem_red, swap_warn, swap_red, t) == HT_OK { nv = nv + 1; w(" PASS healthy -> OK\n" as *u8) }
270 else { w(" FAIL healthy did not evaluate OK\n" as *u8) }
271 // the ACTUAL incident numbers: swap_used_permil=999
272 if evaluate(146, 999, mem_warn, mem_red, swap_warn, swap_red, t) == HT_RED { nv = nv + 1; w(" PASS 2026-07-26 incident numbers (mem 146, swap 999) -> RED\n" as *u8) }
273 else { w(" FAIL the real incident would NOT have fired -- the detector is useless\n" as *u8) }
274 // today's live numbers must at least be DEGRADED, not OK
275 if evaluate(553, 764, mem_warn, mem_red, swap_warn, swap_red, t) == HT_DEGRADED { nv = nv + 1; w(" PASS today's live numbers (mem 553, swap 764) -> DEGRADED\n" as *u8) }
276 else { w(" FAIL today's numbers do not register as degraded\n" as *u8) }
277 // absent swap must NOT be read as 100% used
278 if evaluate(553, 0 - 1, mem_warn, mem_red, swap_warn, swap_red, t) == HT_OK { nv = nv + 1; w(" PASS absent swap (-1) is NOT treated as exhausted\n" as *u8) }
279 else { w(" FAIL a machine with no swap is reported as swap-pressured\n" as *u8) }
280 // the STORAGE axis must fire on the real condition and stay silent on a whole array
281 t[0] = 0
282 if evaluate_storage(1, t) == HT_RED { nv = nv + 1; w(" PASS one degraded array (md3 [2/1] since Aug 1) -> RED\n" as *u8) }
283 else { w(" FAIL a degraded array does not register RED -- the 33-day blind spot is still open\n" as *u8) }
284 t[0] = 0
285 if evaluate_storage(0, t) == HT_OK { nv = nv + 1; w(" PASS every member present -> OK\n" as *u8) }
286 else { w(" FAIL a whole array is reported as degraded\n" as *u8) }
287 t[0] = 0
288 if evaluate_storage(0 - 1, t) == HT_OK { nv = nv + 1; w(" PASS unreadable mdstat ABSTAINS -- a host with no md is not convicted\n" as *u8) }
289 else { w(" FAIL an unreadable sensor is treated as a finding\n" as *u8) }
290 // and the PARSER on KNOWN BYTES -- the two real shapes off this host, one whole and one degraded.
291 // Without this the axis could pass every evaluator tooth while never recognising a real mdstat line.
292 let mdfix: *u8 = "md4 : active raid5 sata1p6[0] sata3p6[7]\n 54649242816 blocks super 1.2 level 5, 64k chunk, algorithm 2 [8/8] [UUUUUUUU]\n\nmd3 : active raid1 nvme1n1p1[1]\n 976757952 blocks super 1.2 [2/1] [_U]\n"
293 var mdn: i64 = 0
294 while mdfix[mdn] != (0 as u8) { mdn = mdn + 1 }
295 if mdstat_degraded(mdfix, mdn) == 1 { nv = nv + 1; w(" PASS parser finds exactly ONE degraded array in real mdstat bytes ([_U], not [8/8] or [2/1])\n" as *u8) }
296 else { w(" FAIL parser miscounts real mdstat bytes -- matching digits or missing the gap\n" as *u8) }
297
298 w("\n ref=DEBT-1785048333 organ=nx_healthtruth\n" as *u8)
299 w(" SCOPE: measures RESOURCE pressure only. It does NOT replace nx_health's liveness checks --\n" as *u8)
300 w(" the two are complementary, and the incident happened because only one existed.\n" as *u8)
301 w("nx_healthtruth: nonvacuity=" as *u8); nn(nv); w("/8" as *u8)
302 if nv != 8 { w(" verdict=RED (detector cannot prove it discriminates)\n" as *u8); return 1 }
303 // The LAST line carries this organ's OWN verdict, positionally, so a judge reading the tail gets the
304 // DETECTOR's soundness and never the HOST's condition. host_verdict= above is the machine's reading.
305 // Without this split nx_gate_bite read the degraded array as this organ being broken and refused to
306 // mutate at all: UNCONTROLLED -- the gate is not GREEN before mutation.
307 w(" detector=SOUND verdict=GREEN\n" as *u8)
308 return 0
309}