code wiki / _hdl_build / nx_system_triage.nx
nx_system_triage.nx source
↩ module page · 517 lines · 25397 B
1// nx_system_triage.nx -- post-event situational awareness: WHAT IS RUNNING, WHAT IS CRASHED,
2// WHAT IS RECOVERABLE, reported live + durable. Built for the reboot/crash case: /tmp organs die,
3// daemons die, and until now nothing in the team could SAY so -- the operator had to ask Claude.
4// (1) RUNNING -- /proc sweep: every live process whose cmdline is team-ish (nx_/.sov.elf), pid+cmdline
5// (2) DAEMONS -- conf-listed expected daemons (D rows) matched against the same sweep -> RUNNING/DOWN
6// (3) ORGANS -- conf-listed expected elfs (E rows): /tmp/<n>.sov.elf|.elf present?
7// missing + source present = RECOVERABLE (rebuild lane named: S=sov_build_run W=wheeler)
8// missing + source missing = UNRECOVERABLE (loud)
9// (4) CRASH-SIGNALS-- RUNTIME-CRASH count in knowledge/status/issues_durable.log (filed history)
10// Verdict: GREEN (all present, daemons up) / RECOVER (only rebuildable losses) / ATTENTION (unrecoverable
11// or expected daemon down). Durable TRIAGE line -> knowledge/status/system_triage.log; exit 0/2/1.
12// Expected set is DATA-DRIVEN: knowledge/status/triage_expected.conf (add a row = widen coverage).
13// Sovereign (syscalls only, no .sh). license_tier: ORIGINAL
14import "nx_syscalls.nx"
15import "nx_framed_append.nx" // torn-write fix: the TRIAGE record = ONE locked fa_appendz (was ~34 sequential _fp/_fn)
16const ST_MAGIC_1073741824: i64 = 1073741824
17const ST_MAGIC_65536: i64 = 65536
18const ST_MAGIC_65520: i64 = 65520
19const ST_MAGIC_16384: i64 = 16384
20const ST_MAGIC_1048592: i64 = 1048592
21const ST_MAGIC_1048576: i64 = 1048576
22const ST_CAP: i64 = 1024 // bounded TRIAGE record (>= the full field set + verdict)
23func _p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
24func _pn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 }
25func _fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
26func _fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
27func st_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
28func st_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i }
29// does file at path open for read? (presence probe)
30func st_exists(path: *u8) -> i64 {
31 let fd: i64 = sys_openat_rd(path)
32 if fd < 0 { return 0 }
33 sys_close(fd)
34 return 1
35}
36// read up to cap bytes of path into buf; return byte count (0 if absent)
37func st_read_all(path: *u8, buf: *u8, cap: i64) -> i64 {
38 let fd: i64 = sys_openat_rd(path)
39 if fd < 0 { return 0 }
40 var n: i64 = 0
41 var go: i64 = 1
42 while go == 1 { let base: i64 = buf as i64; let r: i64 = sys_read(fd, (base + n) as *u8, cap - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } }
43 sys_close(fd)
44 return n
45}
46// substring scan over a raw byte buffer (hay may contain NULs; pat is NUL-terminated, NUL-free)
47func st_contains(hay: *u8, n: i64, pat: *u8) -> i64 {
48 let pl: i64 = st_len(pat)
49 if pl == 0 { return 0 }
50 var i: i64 = 0
51 while i + pl <= n {
52 var k: i64 = 0
53 var hit: i64 = 1
54 while k < pl { if hay[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
55 if hit == 1 { return 1 }
56 i = i + 1
57 }
58 return 0
59}
60// count occurrences of pat in buffer
61func st_count_pat(hay: *u8, n: i64, pat: *u8) -> i64 {
62 let pl: i64 = st_len(pat)
63 if pl == 0 { return 0 }
64 var cnt: i64 = 0
65 var i: i64 = 0
66 while i + pl <= n {
67 var k: i64 = 0
68 var hit: i64 = 1
69 while k < pl { if hay[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
70 if hit == 1 { cnt = cnt + 1 }
71 i = i + 1
72 }
73 return cnt
74}
75// is dirent name all digits (= a pid dir under /proc)?
76func st_is_pid(name: *u8) -> i64 {
77 var i: i64 = 0
78 if name[0] == (0 as u8) { return 0 }
79 while name[i] != (0 as u8) {
80 if name[i] < (48 as u8) { return 0 }
81 if name[i] > (57 as u8) { return 0 }
82 i = i + 1
83 }
84 return 1
85}
86// organ presence: /tmp/<name>.sov.elf OR /tmp/<name>.elf OR _offc/<name>.elf
87// (_offc = the DURABLE home; 2026-06-10 the vault/audit helpers moved there so a
88// /tmp wipe can never kill them again -- durable presence counts as present)
89func tr_organ_present(name: *u8) -> i64 {
90 let p: *u8 = sys_mmap(256)
91 var o: i64 = 0
92 o = st_cat(p, o, "/tmp/" as *u8); o = st_cat(p, o, name); o = st_cat(p, o, ".sov.elf" as *u8); p[o] = 0 as u8
93 if st_exists(p) == 1 { return 1 }
94 o = 0
95 o = st_cat(p, o, "/tmp/" as *u8); o = st_cat(p, o, name); o = st_cat(p, o, ".elf" as *u8); p[o] = 0 as u8
96 if st_exists(p) == 1 { return 1 }
97 o = 0
98 o = st_cat(p, o, "_offc/" as *u8); o = st_cat(p, o, name); o = st_cat(p, o, ".elf" as *u8); p[o] = 0 as u8
99 return st_exists(p)
100}
101// organ source present: runtime/_hdl_build/<n>.nx OR runtime/<n>.nx OR nxasm/<n>.nx (mirrors runner fallback)
102func tr_source_present(name: *u8) -> i64 {
103 let p: *u8 = sys_mmap(256)
104 var o: i64 = 0
105 o = st_cat(p, o, "runtime/_hdl_build/" as *u8); o = st_cat(p, o, name); o = st_cat(p, o, ".nx" as *u8); p[o] = 0 as u8
106 if st_exists(p) == 1 { return 1 }
107 o = 0
108 o = st_cat(p, o, "runtime/" as *u8); o = st_cat(p, o, name); o = st_cat(p, o, ".nx" as *u8); p[o] = 0 as u8
109 if st_exists(p) == 1 { return 1 }
110 o = 0
111 o = st_cat(p, o, "nxasm/" as *u8); o = st_cat(p, o, name); o = st_cat(p, o, ".nx" as *u8); p[o] = 0 as u8
112 return st_exists(p)
113}
114// free GB on the filesystem holding path. x86 statfs=137 called DIRECT (probe-proven 2026-06-10:
115// rv64 43 returns -9 through the translation table; 137 raw matches df exactly). -1 = statfs failed.
116func tr_free_gb(path: *u8) -> i64 {
117 let buf: *i64 = sys_mmap(144) as *i64
118 let rc: i64 = __syscall(137, path, buf, 0, 0, 0, 0)
119 if rc != 0 { return 0 - 1 }
120 return (buf[1] * buf[4]) / ST_MAGIC_1073741824
121}
122// fork+exec elf with up to 2 args, output muted; exit code (128+sig on crash)
123func tr_run2(elf: *u8, a1: *u8, a2: *u8) -> i64 {
124 let pid: i64 = sys_fork()
125 if pid == 0 {
126 let argv: *i64 = sys_mmap(64) as *i64
127 argv[0] = elf as i64
128 var i: i64 = 1
129 if (a1 as i64) != 0 { argv[i] = a1 as i64; i = i + 1 }
130 if (a2 as i64) != 0 { argv[i] = a2 as i64; i = i + 1 }
131 argv[i] = 0
132 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0
133 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
134 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) }
135 sys_execve(elf, argv, envp)
136 sys_exit(127)
137 }
138 let st: *i64 = sys_mmap(16) as *i64
139 sys_wait4(pid, st, 0)
140 let sig: i64 = st[0] & 0x7f
141 if sig != 0 { return 128 + sig }
142 return (st[0] >> 8) & 0xff
143}
144// one /proc/<pid> entry: read cmdline; if team-ish print pid+cmdline; mark any matched D rows.
145// returns 1 if team-ish (counted as RUNNING). 6 args (register path, no stack args).
146func tr_check_pid(nm: *u8, cmdl: *u8, ppath: *u8, dnames: *i64, dfound: *i64, nd: i64) -> i64 {
147 var po: i64 = 0
148 po = st_cat(ppath, po, "/proc/" as *u8); po = st_cat(ppath, po, nm); po = st_cat(ppath, po, "/cmdline" as *u8); ppath[po] = 0 as u8
149 let cl: i64 = st_read_all(ppath, cmdl, 500)
150 if cl <= 0 { return 0 }
151 var dk: i64 = 0
152 while dk < nd { if st_contains(cmdl, cl, dnames[dk] as *u8) == 1 { dfound[dk] = 1 } dk = dk + 1 }
153 var teamish: i64 = st_contains(cmdl, cl, "nx_" as *u8)
154 if teamish == 0 { teamish = st_contains(cmdl, cl, ".sov.elf" as *u8) }
155 if teamish == 0 { return 0 }
156 var b: i64 = 0
157 while b < cl { if cmdl[b] == (0 as u8) { cmdl[b] = 32 as u8 } b = b + 1 }
158 _p(" pid " as *u8); _p(nm); _p(" " as *u8); sys_write(1, cmdl, cl); _p("\n" as *u8)
159 return 1
160}
161// LAST number following pat in buffer (digits parsed immediately after the final match; -1 = no match)
162func st_last_num_after(hay: *u8, n: i64, pat: *u8) -> i64 {
163 let pl: i64 = st_len(pat)
164 if pl == 0 { return 0 - 1 }
165 var best: i64 = 0 - 1
166 var i: i64 = 0
167 while i + pl <= n {
168 var k: i64 = 0
169 var hit: i64 = 1
170 while k < pl { if hay[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
171 if hit == 1 { best = i + pl }
172 i = i + 1
173 }
174 if best < 0 { return 0 - 1 }
175 var v: i64 = 0
176 var q: i64 = best
177 var any: i64 = 0
178 while q < n {
179 if hay[q] >= (48 as u8) { if hay[q] <= (57 as u8) { v = v * 10 + ((hay[q] as i64) - 48); any = 1; q = q + 1 } else { q = n } } else { q = n }
180 }
181 if any == 0 { return 0 - 1 }
182 return v
183}
184// argv[1] = conf path override, argv[2] = durable log override (defaults below) -- lets the
185// triage GATE run synthetic fixtures without polluting the real trend log (site-gate pattern)
186// argv[3] = beat-log override, argv[4] = boot-journal override (CR5 grace fixtures).
187// CR5 boot-grace (crash-recovery ladder, 2026-06-10): a stale beat/evidence verdict caused by
188// a JOURNALED recent boot (boot_journal.log BOOT row within the conf G window) grades RECOVER,
189// not ATTENTION -- the crash is already measured + revived; a wedged-while-up daemon has no
190// recent BOOT row and stays red. G row absent/0 = grace off.
191func main(argc: i64, argv: *i64) -> i64 {
192 _p("=== SYSTEM TRIAGE: running / crashed / recoverable (post-event situational awareness) ===\n" as *u8)
193 var confpath: *u8 = "knowledge/status/triage_expected.conf" as *u8
194 if argc >= 2 { confpath = argv[1] as *u8 }
195 var logpath: *u8 = "knowledge/status/system_triage.log" as *u8
196 if argc >= 3 { logpath = argv[2] as *u8 }
197 var beatpath: *u8 = "knowledge/status/pulse_daemon.log" as *u8
198 if argc >= 4 { beatpath = argv[3] as *u8 }
199 var journalpath: *u8 = "knowledge/status/boot_journal.log" as *u8
200 if argc >= 5 { journalpath = argv[4] as *u8 }
201 // ---- parse the expected-set conf (data-driven) ----
202 let conf: *u8 = sys_mmap(ST_MAGIC_65536)
203 let cn: i64 = st_read_all(confpath, conf, ST_MAGIC_65520)
204 if cn <= 0 { _p(" TRIAGE: conf MISSING (" as *u8); _p(confpath); _p(") -- fail loud\n" as *u8); sys_exit(1); return 1 }
205 let pool: *u8 = sys_mmap(ST_MAGIC_16384)
206 var poff: i64 = 0
207 let enames: *i64 = sys_mmap(64*8) as *i64
208 let elanes: *u8 = sys_mmap(64)
209 var ne: i64 = 0
210 let dnames: *i64 = sys_mmap(16*8) as *i64
211 var nd: i64 = 0
212 var mingb: i64 = 0
213 var hmax: i64 = 0
214 var grace: i64 = 0
215 let fbud: *i64 = sys_mmap(16*8) as *i64
216 let fpath: *i64 = sys_mmap(16*8) as *i64
217 var nf: i64 = 0
218 let rhost: *i64 = sys_mmap(8*8) as *i64
219 let rport: *i64 = sys_mmap(8*8) as *i64
220 var nr: i64 = 0
221 var ci: i64 = 0
222 while ci < cn {
223 // find end of this line (flag loop, no sentinels)
224 var le: i64 = ci
225 var scan: i64 = 1
226 while scan == 1 {
227 if le >= cn { scan = 0 }
228 else { if conf[le] == (10 as u8) { scan = 0 } else { le = le + 1 } }
229 }
230 // 'E <name> <lane>'
231 if conf[ci] == (69 as u8) {
232 var te: i64 = ci + 2
233 var s2: i64 = 1
234 while s2 == 1 {
235 if te >= le { s2 = 0 }
236 else { if conf[te] == (32 as u8) { s2 = 0 } else { te = te + 1 } }
237 }
238 let nptr: i64 = (pool as i64) + poff
239 var q: i64 = ci + 2
240 while q < te { pool[poff] = conf[q]; poff = poff + 1; q = q + 1 }
241 pool[poff] = 0 as u8; poff = poff + 1
242 if ne < 64 {
243 enames[ne] = nptr
244 elanes[ne] = 83 as u8
245 if te + 1 < le { elanes[ne] = conf[te+1] }
246 ne = ne + 1
247 }
248 }
249 // 'T <min-free-GB>' (disk budget; 0/absent = report-only, no gating)
250 if conf[ci] == (84 as u8) {
251 var tv: i64 = 0
252 var q3: i64 = ci + 2
253 while q3 < le {
254 if conf[q3] >= (48 as u8) { if conf[q3] <= (57 as u8) { tv = tv * 10 + ((conf[q3] as i64) - 48) } }
255 q3 = q3 + 1
256 }
257 mingb = tv
258 }
259 // 'H <max-beat-age-sec>' (heartbeat budget: last DAEMON-BEAT must be younger; 0/absent = skip)
260 if conf[ci] == (72 as u8) {
261 var hv: i64 = 0
262 var q4: i64 = ci + 2
263 while q4 < le {
264 if conf[q4] >= (48 as u8) { if conf[q4] <= (57 as u8) { hv = hv * 10 + ((conf[q4] as i64) - 48) } }
265 q4 = q4 + 1
266 }
267 hmax = hv
268 }
269 // 'G <grace-sec>' (CR5 boot-grace window after a journaled boot; 0/absent = off)
270 if conf[ci] == (71 as u8) {
271 var gv: i64 = 0
272 var q9: i64 = ci + 2
273 while q9 < le {
274 if conf[q9] >= (48 as u8) { if conf[q9] <= (57 as u8) { gv = gv * 10 + ((conf[q9] as i64) - 48) } }
275 q9 = q9 + 1
276 }
277 grace = gv
278 }
279 // 'F <max-age-sec> <logpath>' (evidence freshness: last epoch= in that log must be younger)
280 if conf[ci] == (70 as u8) {
281 var fv: i64 = 0
282 var q5: i64 = ci + 2
283 var s5: i64 = 1
284 while s5 == 1 {
285 if q5 >= le { s5 = 0 }
286 else {
287 if conf[q5] == (32 as u8) { s5 = 0 }
288 else {
289 if conf[q5] >= (48 as u8) { if conf[q5] <= (57 as u8) { fv = fv * 10 + ((conf[q5] as i64) - 48) } }
290 q5 = q5 + 1
291 }
292 }
293 }
294 let fptr: i64 = (pool as i64) + poff
295 var q6: i64 = q5 + 1
296 while q6 < le { pool[poff] = conf[q6]; poff = poff + 1; q6 = q6 + 1 }
297 pool[poff] = 0 as u8; poff = poff + 1
298 if nf < 16 { fbud[nf] = fv; fpath[nf] = fptr; nf = nf + 1 }
299 }
300 // 'R <host-or-ip> <port>' (remote reachability via nx_reach_probe; both tokens kept as strings)
301 if conf[ci] == (82 as u8) {
302 var t7: i64 = ci + 2
303 var s7: i64 = 1
304 while s7 == 1 {
305 if t7 >= le { s7 = 0 }
306 else { if conf[t7] == (32 as u8) { s7 = 0 } else { t7 = t7 + 1 } }
307 }
308 let hptr: i64 = (pool as i64) + poff
309 var q7: i64 = ci + 2
310 while q7 < t7 { pool[poff] = conf[q7]; poff = poff + 1; q7 = q7 + 1 }
311 pool[poff] = 0 as u8; poff = poff + 1
312 let pptr: i64 = (pool as i64) + poff
313 var q8: i64 = t7 + 1
314 while q8 < le { pool[poff] = conf[q8]; poff = poff + 1; q8 = q8 + 1 }
315 pool[poff] = 0 as u8; poff = poff + 1
316 if nr < 8 { rhost[nr] = hptr; rport[nr] = pptr; nr = nr + 1 }
317 }
318 // 'D <substr>'
319 if conf[ci] == (68 as u8) {
320 let dptr: i64 = (pool as i64) + poff
321 var q2: i64 = ci + 2
322 while q2 < le { pool[poff] = conf[q2]; poff = poff + 1; q2 = q2 + 1 }
323 pool[poff] = 0 as u8; poff = poff + 1
324 if nd < 16 { dnames[nd] = dptr; nd = nd + 1 }
325 }
326 ci = le + 1
327 }
328 // ---- /proc sweep: RUNNING team-ish processes + daemon matching ----
329 let dfound: *i64 = sys_mmap(16*8) as *i64
330 var di: i64 = 0
331 while di < nd { dfound[di] = 0; di = di + 1 }
332 var nproc: i64 = 0
333 _p(" -- RUNNING (team-ish processes in /proc) --\n" as *u8)
334 let pfd: i64 = sys_openat_rd("/proc" as *u8)
335 if pfd >= 0 {
336 let dirbuf: *u8 = sys_mmap(ST_MAGIC_65536)
337 let cmdl: *u8 = sys_mmap(512)
338 let ppath: *u8 = sys_mmap(256)
339 var done: i64 = 0
340 while done == 0 {
341 let nb: i64 = sys_getdents64(pfd, dirbuf, ST_MAGIC_65536)
342 if nb <= 0 { done = 1 }
343 else {
344 var off: i64 = 0
345 while off < nb {
346 let dbase: i64 = dirbuf as i64
347 let rec: *u8 = (dbase + off) as *u8
348 let rl: i64 = dirent_reclen(rec)
349 if rl <= 0 { off = nb }
350 else {
351 let nm: *u8 = dirent_name(rec)
352 if st_is_pid(nm) == 1 { nproc = nproc + tr_check_pid(nm, cmdl, ppath, dnames, dfound, nd) }
353 off = off + rl
354 }
355 }
356 }
357 }
358 sys_close(pfd)
359 }
360 if nproc == 0 { _p(" (none)\n" as *u8) }
361 // ---- expected daemons verdicts ----
362 var ddown: i64 = 0
363 if nd > 0 {
364 _p(" -- EXPECTED DAEMONS --\n" as *u8)
365 var dv: i64 = 0
366 while dv < nd {
367 _p(" " as *u8); _p(dnames[dv] as *u8); _p(": " as *u8)
368 if dfound[dv] == 1 { _p("RUNNING\n" as *u8) } else { _p("DOWN\n" as *u8); ddown = ddown + 1 }
369 dv = dv + 1
370 }
371 }
372 // ---- expected organs: present / recoverable / unrecoverable ----
373 _p(" -- EXPECTED ORGANS (/tmp elfs) --\n" as *u8)
374 var present: i64 = 0
375 var recov: i64 = 0
376 var unrec: i64 = 0
377 var ei: i64 = 0
378 while ei < ne {
379 let en: *u8 = enames[ei] as *u8
380 _p(" " as *u8); _p(en); _p(": " as *u8)
381 if tr_organ_present(en) == 1 { _p("PRESENT\n" as *u8); present = present + 1 }
382 else {
383 if tr_source_present(en) == 1 {
384 recov = recov + 1
385 _p("RECOVERABLE via " as *u8)
386 if elanes[ei] == (87 as u8) { _p("wheeler-bootstrap lane\n" as *u8) } else { _p("nx_sov_build_run\n" as *u8) }
387 } else { _p("UNRECOVERABLE (no source!)\n" as *u8); unrec = unrec + 1 }
388 }
389 ei = ei + 1
390 }
391 // ---- remote reachability (the NAS/live-site eye; probe is watchdog-bounded at 10s/row) ----
392 var rdown: i64 = 0
393 if nr > 0 {
394 _p(" -- REACHABILITY (remote, via own DNS+TCP probe) --\n" as *u8)
395 let probe2: i64 = sys_openat_rd("/tmp/nx_reach_probe.sov.elf" as *u8)
396 if probe2 >= 0 { sys_close(probe2) }
397 else { tr_run2("_offc/nx_sov_build_run.elf" as *u8, "nx_reach_probe" as *u8, 0 as *u8) }
398 var ri: i64 = 0
399 while ri < nr {
400 _p(" " as *u8); _p(rhost[ri] as *u8); _p(":" as *u8); _p(rport[ri] as *u8); _p(" " as *u8)
401 let rrc: i64 = tr_run2("/tmp/nx_reach_probe.sov.elf" as *u8, rhost[ri] as *u8, rport[ri] as *u8)
402 if rrc == 0 { _p("REACHABLE\n" as *u8) } else { _p("UNREACHABLE rc=" as *u8); _pn(rrc); _p("\n" as *u8); rdown = rdown + 1 }
403 ri = ri + 1
404 }
405 }
406 // ---- crash signals filed (history; does not gate the verdict) ----
407 let ibuf: *u8 = sys_mmap(ST_MAGIC_1048592)
408 let inn: i64 = st_read_all("knowledge/status/issues_durable.log" as *u8, ibuf, ST_MAGIC_1048576)
409 let ncrash: i64 = st_count_pat(ibuf, inn, "RUNTIME-CRASH" as *u8)
410 _p(" -- CRASH SIGNALS filed (issues_durable.log): " as *u8); _pn(ncrash); _p(" RUNTIME-CRASH lines --\n" as *u8)
411 // ---- disk budget (the 2026-06-10 lesson: 147GB of crash dumps emptied C: and NOTHING said so;
412 // T row in the conf = the threshold; 0/absent = report-only) ----
413 let repo_gb: i64 = tr_free_gb("." as *u8)
414 let tmp_gb: i64 = tr_free_gb("/tmp" as *u8)
415 var dlow: i64 = 0
416 _p(" -- DISK: repo-disk free " as *u8); _pn(repo_gb); _p("GB, /tmp free " as *u8); _pn(tmp_gb)
417 _p("GB (budget min " as *u8); _pn(mingb); _p("GB) --\n" as *u8)
418 if mingb > 0 {
419 if repo_gb >= 0 { if repo_gb < mingb { dlow = dlow + 1; _p(" DISK-LOW: repo disk below budget!\n" as *u8) } }
420 if tmp_gb >= 0 { if tmp_gb < mingb { dlow = dlow + 1; _p(" DISK-LOW: /tmp below budget!\n" as *u8) } }
421 }
422 // ---- heartbeat staleness (a daemon can be ALIVE in /proc yet wedged -- the beat log is the
423 // proof of life; H row = max age budget) ----
424 var stale: i64 = 0
425 var beat_age: i64 = 0 - 1
426 if hmax > 0 {
427 let hbuf: *u8 = sys_mmap(ST_MAGIC_1048592)
428 let hn: i64 = st_read_all(beatpath, hbuf, ST_MAGIC_1048576)
429 let bepoch: i64 = st_last_num_after(hbuf, hn, "DAEMON-BEAT epoch=" as *u8)
430 if bepoch > 0 { beat_age = sys_now_realtime_sec() - bepoch }
431 _p(" -- HEARTBEAT: last DAEMON-BEAT age " as *u8); _pn(beat_age); _p("s (budget " as *u8); _pn(hmax); _p("s) --\n" as *u8)
432 if beat_age < 0 { stale = 1; _p(" BEAT-STALE: no DAEMON-BEAT line found!\n" as *u8) }
433 if beat_age > hmax { stale = 1; _p(" BEAT-STALE: daemon exists but is not beating!\n" as *u8) }
434 }
435 // ---- evidence freshness (claims EXPIRE: a green verdict from days ago is not a grade;
436 // each F row = one durable log whose last epoch= must be within budget) ----
437 var fstale: i64 = 0
438 if nf > 0 {
439 _p(" -- EVIDENCE FRESHNESS (claims expire; per-log budgets) --\n" as *u8)
440 let fb: *u8 = sys_mmap(ST_MAGIC_1048592)
441 var fi: i64 = 0
442 while fi < nf {
443 let fp2: *u8 = fpath[fi] as *u8
444 let fn2: i64 = st_read_all(fp2, fb, ST_MAGIC_1048576)
445 let fe: i64 = st_last_num_after(fb, fn2, "epoch=" as *u8)
446 var age2: i64 = 0 - 1
447 if fe > 0 { age2 = sys_now_realtime_sec() - fe }
448 _p(" " as *u8); _p(fp2); _p(": age " as *u8); _pn(age2); _p("s (budget " as *u8); _pn(fbud[fi]); _p("s) " as *u8)
449 var rowok: i64 = 1
450 if age2 < 0 { rowok = 0 }
451 if age2 > fbud[fi] { rowok = 0 }
452 if rowok == 1 { _p("FRESH\n" as *u8) } else { _p("STALE\n" as *u8); fstale = fstale + 1 }
453 fi = fi + 1
454 }
455 }
456 // ---- CR5 boot-grace: is current staleness explained by a JOURNALED recent boot? ----
457 var graced: i64 = 0
458 var boot_age: i64 = 0 - 1
459 if grace > 0 {
460 if stale + fstale > 0 {
461 let jb: *u8 = sys_mmap(ST_MAGIC_1048592)
462 let jn2: i64 = st_read_all(journalpath, jb, ST_MAGIC_1048576)
463 let be2: i64 = st_last_num_after(jb, jn2, "BOOT boot_epoch=" as *u8)
464 if be2 > 0 {
465 boot_age = sys_now_realtime_sec() - be2
466 if boot_age >= 0 { if boot_age <= grace { graced = stale + fstale } }
467 }
468 }
469 }
470 if graced > 0 {
471 _p(" -- BOOT-GRACE: staleness is explained by a journaled boot " as *u8); _pn(boot_age)
472 _p("s ago (window " as *u8); _pn(grace); _p("s) -> RECOVERING, not wedged --\n" as *u8)
473 }
474 // ---- verdict + durable line ----
475 var verdict: i64 = 0 // 0 GREEN / 2 RECOVER / 1 ATTENTION
476 if fstale > 0 { verdict = 1 }
477 if stale > 0 { verdict = 1 }
478 if graced > 0 { if verdict == 1 { verdict = 2 } } // staleness covered by the journaled boot
479 if recov > 0 { verdict = 2 }
480 if unrec > 0 { verdict = 1 }
481 if ddown > 0 { verdict = 1 }
482 if dlow > 0 { verdict = 1 }
483 if rdown > 0 { verdict = 1 }
484 let lfd: i64 = sys_openat_append(logpath, 0x1a4)
485 if lfd >= 0 {
486 sys_close(lfd) // writability check; the TRIAGE record below is ONE atomic fa_appendz write
487 let tb: *u8 = sys_mmap(ST_CAP + 16)
488 var o: i64 = 0
489 o = fa_cat(tb, o, "TRIAGE epoch=" as *u8); o = fa_catn(tb, o, sys_now_realtime_sec())
490 o = fa_cat(tb, o, " procs_running=" as *u8); o = fa_catn(tb, o, nproc)
491 o = fa_cat(tb, o, " daemons_down=" as *u8); o = fa_catn(tb, o, ddown)
492 o = fa_cat(tb, o, " organs_present=" as *u8); o = fa_catn(tb, o, present)
493 o = fa_cat(tb, o, " organs_recoverable=" as *u8); o = fa_catn(tb, o, recov)
494 o = fa_cat(tb, o, " organs_unrecoverable=" as *u8); o = fa_catn(tb, o, unrec)
495 o = fa_cat(tb, o, " crash_signals=" as *u8); o = fa_catn(tb, o, ncrash)
496 o = fa_cat(tb, o, " disk_repo_gb=" as *u8); o = fa_catn(tb, o, repo_gb)
497 o = fa_cat(tb, o, " disk_tmp_gb=" as *u8); o = fa_catn(tb, o, tmp_gb)
498 o = fa_cat(tb, o, " disk_low=" as *u8); o = fa_catn(tb, o, dlow)
499 o = fa_cat(tb, o, " beat_age=" as *u8); o = fa_catn(tb, o, beat_age)
500 o = fa_cat(tb, o, " beat_stale=" as *u8); o = fa_catn(tb, o, stale)
501 o = fa_cat(tb, o, " evidence_stale=" as *u8); o = fa_catn(tb, o, fstale)
502 o = fa_cat(tb, o, " boot_age=" as *u8); o = fa_catn(tb, o, boot_age)
503 o = fa_cat(tb, o, " graced=" as *u8); o = fa_catn(tb, o, graced)
504 o = fa_cat(tb, o, " reach_down=" as *u8); o = fa_catn(tb, o, rdown)
505 if verdict == 0 { o = fa_cat(tb, o, " verdict=GREEN" as *u8) }
506 if verdict == 2 { o = fa_cat(tb, o, " verdict=RECOVER" as *u8) }
507 if verdict == 1 { o = fa_cat(tb, o, " verdict=ATTENTION" as *u8) }
508 tb[o] = 0 as u8
509 fa_appendz(logpath, tb, ST_CAP)
510 }
511 _p(" durable: " as *u8); _p(logpath); _p("\n" as *u8)
512 if verdict == 0 { _p(" SYSTEM TRIAGE: GREEN (everything expected is alive or present)\n" as *u8); sys_exit(0); return 0 }
513 if verdict == 2 { _p(" SYSTEM TRIAGE: RECOVER (losses are rebuildable -- run the named lanes)\n" as *u8); sys_exit(2); return 2 }
514 _p(" SYSTEM TRIAGE: ATTENTION (unrecoverable organ, daemon down, disk below budget, or beat stale)\n" as *u8)
515 sys_exit(1)
516 return 1
517}