nx_heal_lib.nx source
↩ module page · 419 lines · 21093 B
1// nx_heal_lib.nx -- CONSOLIDATED heal-plane tool, LIBRARY half (importable: no main; the CLI is
2// nx_heal.nx, the gate nx_heal_gate.nx asserts on these functions' RETURN VALUES directly).
3// Tool #11 of the 15-tool architecture: one verb-router, composing what exists, ONE MCP stub.
4// READ-ONLY first increment: surfaces the supervision +
5// self-heal + remediation state of the NAS plane so an operator or workstream can SEE/MONITOR over
6// MCP what tonight's prevention layer is doing (tools_selfheal / edge_selfheal / supervisor snap).
7// Verbs:
8// status -> one rollup: supervisor snapshot (UP/DOWN counts + every non-UP line),
9// selfheal_status.tsv, last self-heal log line + remediation count per plane
10// log tools|edge -> tail of that plane's self-heal log
11// remediations -> per-plane remediation event counts + most recent epochs
12// All verbs take an optional basedir (default "." = ~/nishihost on the NAS); the gate points it at a
13// fixture dir. Mutating verbs (reconcile/reap) are a LATER increment behind a separate cap -- this
14// increment is honestly read-only (schema ro=1). license_tier: ORIGINAL
15import "nx_syscalls.nx"
16import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
17import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
18import "nx_seg_store.nx" // ss_readall (canonical file slurp) -- no new primitives
19import "nx_vsz_watchdog_core.nx" // vw_read (bounded file read) / vw_num_at / vw_contains -- portable file+string helpers
20import "nx_os_proc.nx" // osp_* -- OS process-introspection SEAM (Linux procfs / NishiOS-native). NO raw /proc above this.
21
22const NH_BUF_CAP: i64 = 262144 // max bytes considered per surfaced file (logs stay small by design)
23const NH_TAIL_LINES: i64 = 20 // `log` verb: lines of tail shown
24const NH_RECENT_EVTS: i64 = 5 // `remediations`: most recent epochs echoed
25const NH_PATH_CAP: i64 = 512
26const NH_ASCII_0: i64 = 48 // '0' (decimal print)
27
28func nh_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
29// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
30// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
31// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
32// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
33func nh_putn(v: i64) -> i64 { nxi_out(v); return 0 }
34func nh_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 }
35func nh_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 }
36// basedir + "/" + name, NUL-terminated
37func nh_path(out: *u8, base: *u8, name: *u8) -> i64 {
38 var o: i64 = nh_cat(out, 0, base)
39 if o > 0 { if out[o-1] != (47 as u8) { out[o] = 47 as u8; o = o + 1 } }
40 o = nh_cat(out, o, name)
41 out[o] = 0 as u8
42 return o
43}
44// slurp basedir/name; returns len (0 if absent/empty), buffer in *bufp
45func nh_read(base: *u8, name: *u8, bufp: *i64) -> i64 {
46 let p: *u8 = sys_mmap(NH_PATH_CAP)
47 nh_path(p, base, name)
48 let szp: *i64 = sys_mmap(16) as *i64
49 let b: *u8 = ss_readall(p, szp)
50 bufp[0] = b as i64
51 var n: i64 = szp[0]
52 if n < 0 { n = 0 }
53 if n > NH_BUF_CAP { n = NH_BUF_CAP }
54 return n
55}
56// print the LAST `want` lines of buf[0..n)
57func nh_tail(buf: *u8, n: i64, want: i64) -> i64 {
58 var lines: i64 = 0
59 var i: i64 = 0
60 while i < n { if buf[i] == (10 as u8) { lines = lines + 1 } i = i + 1 }
61 if n > 0 { if buf[n-1] != (10 as u8) { lines = lines + 1 } }
62 var skip: i64 = lines - want
63 if skip < 0 { skip = 0 }
64 var at: i64 = 0
65 i = 0
66 while i < n {
67 if at >= skip { sys_write(1, buf + i, 1) }
68 if buf[i] == (10 as u8) { at = at + 1 }
69 i = i + 1
70 }
71 if n > 0 { if buf[n-1] != (10 as u8) { nh_puts("\n" as *u8) } }
72 return lines
73}
74// last line of buf (no trailing-\n dependence); prints "(none)" if empty
75func nh_lastline(buf: *u8, n: i64) -> i64 {
76 if n <= 0 { nh_puts("(none)\n" as *u8); return 0 }
77 var e: i64 = n
78 if buf[e-1] == (10 as u8) { e = e - 1 }
79 var s: i64 = e
80 var go: i64 = 1
81 while go == 1 { go = 0; if s > 0 { if buf[s-1] != (10 as u8) { s = s - 1; go = 1 } } }
82 sys_write(1, buf + s, e - s)
83 nh_puts("\n" as *u8)
84 return 1
85}
86// count newline-terminated lines
87func nh_lines(buf: *u8, n: i64) -> i64 {
88 var c: i64 = 0; var i: i64 = 0
89 while i < n { if buf[i] == (10 as u8) { c = c + 1 } i = i + 1 }
90 if n > 0 { if buf[n-1] != (10 as u8) { c = c + 1 } }
91 return c
92}
93// NUL-terminated `lit` matches buf at position i (bounded by end)?
94func nh_match(buf: *u8, i: i64, end: i64, lit: *u8) -> i64 {
95 var k: i64 = 0
96 while lit[k] != (0 as u8) {
97 if i + k >= end { return 0 }
98 if buf[i + k] != lit[k] { return 0 }
99 k = k + 1
100 }
101 return 1
102}
103// does line [ls,le) contain " UP " ? (supervisor snap: "SVC <name> <port> UP 1 0 0 0")
104func nh_line_has_up(buf: *u8, ls: i64, le: i64) -> i64 {
105 var i: i64 = ls
106 while i < le {
107 if nh_match(buf, i, le, " UP " as *u8) == 1 { return 1 }
108 i = i + 1
109 }
110 return 0
111}
112func nh_is_svc(buf: *u8, ls: i64, le: i64) -> i64 {
113 return nh_match(buf, ls, le, "SVC " as *u8)
114}
115
116// status: supervisor snap rollup + selfheal state per plane
117func nh_status(base: *u8) -> i64 {
118 nh_puts("=== NX-HEAL STATUS (read-only rollup) ===\n" as *u8)
119 let bp: *i64 = sys_mmap(16) as *i64
120 // supervisor snapshot: count SVC UP vs not; echo every non-UP line verbatim
121 let sn: i64 = nh_read(base, "mgmt_snap.json" as *u8, bp)
122 let sb: *u8 = bp[0] as *u8
123 var up: i64 = 0
124 var down: i64 = 0
125 if sn > 0 {
126 var ls: i64 = 0; var i: i64 = 0
127 while i <= sn {
128 var eol: i64 = 0
129 if i == sn { eol = 1 } else { if sb[i] == (10 as u8) { eol = 1 } }
130 if eol == 1 {
131 if nh_is_svc(sb, ls, i) == 1 {
132 if nh_line_has_up(sb, ls, i) == 1 { up = up + 1 } else {
133 down = down + 1
134 nh_puts(" !! " as *u8); sys_write(1, sb + ls, i - ls); nh_puts("\n" as *u8)
135 }
136 }
137 ls = i + 1
138 }
139 i = i + 1
140 }
141 }
142 nh_puts("supervisor: svc-up=" as *u8); nh_putn(up); nh_puts(" svc-DOWN=" as *u8); nh_putn(down)
143 if sn <= 0 { nh_puts(" (no snapshot!)" as *u8) }
144 nh_puts("\n" as *u8)
145 // selfheal_status.tsv verbatim -- RETIRED 2026-07-16 (writer nx_selfheal_monitor.sh retired; the
146 // live truth is the fleet organ below). Kept as a labeled legacy read so old fixtures still resolve.
147 let tn: i64 = nh_read(base, "selfheal_status.tsv" as *u8, bp)
148 if tn > 0 { nh_puts("--- selfheal_status.tsv (RETIRED 07-16, frozen) ---\n" as *u8); sys_write(1, bp[0] as *u8, tn) }
149 // FLEET ORGAN (2026-07-16): nx_daemon_supervisor owns per-daemon supervision now (daemons.reg;
150 // /status on 127.0.0.1:18095 for live state). Its log carries the remediation events (REVIVED /
151 // EXE-PURGE / WEDGE-KILL) that replaced the per-plane bash selfheal logs below.
152 nh_puts("--- fleet organ (daemon_supervisor.log; live state :18095) ---\nlast: " as *u8)
153 let fl: i64 = nh_read(base, "daemon_supervisor.log" as *u8, bp)
154 let fb: *u8 = bp[0] as *u8
155 nh_lastline(fb, fl)
156 var frev: i64 = 0
157 var fpur: i64 = 0
158 var fls: i64 = 0
159 var fi: i64 = 0
160 while fi <= fl {
161 var feol: i64 = 0
162 if fi == fl { feol = 1 } else { if fb[fi] == (10 as u8) { feol = 1 } }
163 if feol == 1 {
164 if fi > fls {
165 if vw_contains((fb as i64 + fls) as *u8, fi - fls, "REVIVED" as *u8, 7) == 1 { frev = frev + 1 }
166 if vw_contains((fb as i64 + fls) as *u8, fi - fls, "EXE-PURGE" as *u8, 9) == 1 { fpur = fpur + 1 }
167 if vw_contains((fb as i64 + fls) as *u8, fi - fls, "WEDGE-KILL" as *u8, 10) == 1 { fpur = fpur + 1 }
168 }
169 fls = fi + 1
170 }
171 fi = fi + 1
172 }
173 nh_puts("revives=" as *u8); nh_putn(frev); nh_puts(" purges+wedgekills=" as *u8); nh_putn(fpur); nh_puts("\n" as *u8)
174 // TENANCY ORGAN (2026-07-16): INV-1..9 sentinel; report on 127.0.0.1:18094; alerts appended on NEW.
175 nh_puts("--- tenancy organ (tenancy_alerts.log; report :18094) ---\nlast: " as *u8)
176 let ta: i64 = nh_read(base, "tenancy_alerts.log" as *u8, bp)
177 nh_lastline(bp[0] as *u8, ta)
178 // per-plane bash selfheal -- RETIRED 2026-07-16 (writers in retired/); frozen last-events kept, labeled
179 nh_puts("--- tools plane (bash selfheal RETIRED 07-16, frozen) ---\nlast: " as *u8)
180 let tl: i64 = nh_read(base, "tools_selfheal.log" as *u8, bp)
181 nh_lastline(bp[0] as *u8, tl)
182 let tr: i64 = nh_read(base, "tools_remediations.log" as *u8, bp)
183 nh_puts("remediations-total=" as *u8); nh_putn(nh_lines(bp[0] as *u8, tr)); nh_puts("\n" as *u8)
184 nh_puts("--- edge plane (bash selfheal RETIRED 07-16, frozen) ---\nlast: " as *u8)
185 let el: i64 = nh_read(base, "edge_selfheal.log" as *u8, bp)
186 nh_lastline(bp[0] as *u8, el)
187 let er: i64 = nh_read(base, "edge_remediations.log" as *u8, bp)
188 nh_puts("remediations-total=" as *u8); nh_putn(nh_lines(bp[0] as *u8, er)); nh_puts("\n" as *u8)
189 nh_puts("verdict=" as *u8)
190 if down == 0 { nh_puts("GREEN\n" as *u8) } else { nh_puts("ATTENTION (services down)\n" as *u8) }
191 return down // 0 = all supervised services UP (gate-assertable; CLI exit mirrors it)
192}
193
194func nh_log(base: *u8, plane: *u8) -> i64 {
195 let bp: *i64 = sys_mmap(16) as *i64
196 var n: i64 = 0
197 if nh_streq(plane, "fleet" as *u8) == 1 { n = nh_read(base, "daemon_supervisor.log" as *u8, bp) } else {
198 if nh_streq(plane, "tenancy" as *u8) == 1 { n = nh_read(base, "tenancy_alerts.log" as *u8, bp) } else {
199 if nh_streq(plane, "tools" as *u8) == 1 { n = nh_read(base, "tools_selfheal.log" as *u8, bp) } else {
200 if nh_streq(plane, "edge" as *u8) == 1 { n = nh_read(base, "edge_selfheal.log" as *u8, bp) } else {
201 nh_puts("nx_heal log: unknown plane (fleet|tenancy|tools|edge)\n" as *u8); return 0 - 1
202 } } } }
203 if n <= 0 { nh_puts("(no log yet for this plane)\n" as *u8); return 0 }
204 return nh_tail(bp[0] as *u8, n, NH_TAIL_LINES) // total lines in the log (gate-assertable)
205}
206
207// ===================== diagnose: the WHY behind a crash-loop / wedge =====================
208// Answers "why is <daemon> restarting?" with EVIDENCE: live pids + their AGES (a long-lived parent
209// with young children = fork-model workers, NOT restarts -- the false-positive that read as a
210// crash-loop), heal-plane remediation recency, and supervisor-log correlation. The classifier is a
211// PURE function (gated on synthetic inputs); the /proc plumbing reuses the vsz-watchdog's proven walk.
212const NH_OLD_SECS: i64 = 600 // a pid older than this = stable long-lived parent
213const NH_RECENT_WIN: i64 = 3600 // remediation recency window (matches selfheal's per-hour accounting)
214const NH_MAX_PIDS: i64 = 64 // matched-pid table cap
215const NH_ALLPID_CAP: i64 = 4096 // max live pids enumerated per scan (via the nx_os_proc seam)
216const NH_CL_CAP: i64 = 4096 // argv0 read cap
217const NH_SUP_CAP: i64 = 262144 // supervisor-log read cap
218// verdicts (returned by the pure classifier)
219const NH_V_DOWN: i64 = 0 // no live process
220const NH_V_STABLE: i64 = 1 // long-lived parent; pid churn = worker children (monitor false-positive)
221const NH_V_LOOP_HEAL: i64 = 2 // young processes + recent heal-plane remediations = selfheal is bouncing it
222const NH_V_LOOP_UNTRACKED: i64 = 3 // young processes, NO heal remediations = something ELSE restarts it
223
224func nh_str_eq(a: *u8, b: *u8) -> i64 { return nh_streq(a, b) }
225// wallclock now = OS boot-epoch + monotonic-since-boot (both via the nx_os_proc seam; NO raw /proc here)
226func nh_wallclock() -> i64 {
227 let be: i64 = osp_boot_epoch()
228 if be <= 0 { return 0 }
229 return be + osp_uptime_s()
230}
231// enumerate processes whose argv0 basename == daemon; fill pids[]/ages[]/ppids[] up to NH_MAX_PIDS.
232// OS-AGNOSTIC by construction: osp_* (nx_os_proc) is the ONLY OS touch-point -- Linux procfs today,
233// NishiOS-native tomorrow, same source. Returns count.
234func nh_scan(daemon: *u8, pids: *i64, ages: *i64, ppids: *i64) -> i64 {
235 let self: i64 = osp_selfpid()
236 let up: i64 = osp_uptime_s()
237 let hz: i64 = osp_hz()
238 let all: *i64 = sys_mmap(8 * NH_ALLPID_CAP) as *i64
239 let np: i64 = osp_list_pids(all, NH_ALLPID_CAP)
240 let a0: *u8 = sys_mmap(NH_CL_CAP)
241 var cnt: i64 = 0
242 var i: i64 = 0
243 while i < np {
244 let pid: i64 = all[i]
245 if pid != self { if cnt < NH_MAX_PIDS {
246 if osp_cmd_argv0(pid, a0, NH_CL_CAP - 1) > 0 {
247 if nh_str_eq(a0, daemon) == 1 {
248 let st: i64 = osp_starttime_ticks(pid)
249 if st >= 0 { if hz > 0 {
250 pids[cnt] = pid
251 ppids[cnt] = osp_ppid(pid)
252 ages[cnt] = up - st / hz
253 cnt = cnt + 1
254 } }
255 }
256 }
257 } }
258 i = i + 1
259 }
260 return cnt
261}
262// count leading-integer epochs in buf within [now-win, now]
263func nh_recent_epochs(buf: *u8, n: i64, now: i64, win: i64) -> i64 {
264 let ep: *i64 = sys_mmap(16) as *i64
265 var c: i64 = 0
266 var ls: i64 = 0
267 var i: i64 = 0
268 while i <= n {
269 var eol: i64 = 0
270 if i == n { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } }
271 if eol == 1 {
272 if i > ls {
273 let v: i64 = vw_num_at(buf, i, ls, ep)
274 if v > 0 { if v >= now - win { if v <= now { c = c + 1 } } }
275 }
276 ls = i + 1
277 }
278 i = i + 1
279 }
280 return c
281}
282// PURE classifier (gated): why-verdict from the evidence triple
283func nh_classify(npids: i64, oldest_age: i64, recent_remed: i64) -> i64 {
284 if npids <= 0 { return NH_V_DOWN }
285 if oldest_age >= NH_OLD_SECS { return NH_V_STABLE }
286 if recent_remed > 0 { return NH_V_LOOP_HEAL }
287 return NH_V_LOOP_UNTRACKED
288}
289
290// diagnose <daemon> -- gather evidence, classify, explain. plane (tools|edge|-) selects which
291// remediations log correlates; base = dir of the heal-plane files (default "."). Returns the verdict.
292func nh_diagnose(daemon: *u8, plane: *u8, base: *u8) -> i64 {
293 nh_puts("=== NX-HEAL DIAGNOSE " as *u8); nh_puts(daemon); nh_puts(" ===\n" as *u8)
294 let pids: *i64 = sys_mmap(8 * NH_MAX_PIDS) as *i64
295 let ages: *i64 = sys_mmap(8 * NH_MAX_PIDS) as *i64
296 let ppids: *i64 = sys_mmap(8 * NH_MAX_PIDS) as *i64
297 let cnt: i64 = nh_scan(daemon, pids, ages, ppids)
298 nh_puts("live processes: " as *u8); nh_putn(cnt); nh_puts("\n" as *u8)
299 var oldest: i64 = 0 - 1
300 var i: i64 = 0
301 while i < cnt {
302 nh_puts(" pid=" as *u8); nh_putn(pids[i])
303 nh_puts(" ppid=" as *u8); nh_putn(ppids[i])
304 nh_puts(" age_s=" as *u8); nh_putn(ages[i]); nh_puts("\n" as *u8)
305 if ages[i] > oldest { oldest = ages[i] }
306 i = i + 1
307 }
308 // heal-plane remediation recency for the selected plane
309 var recent: i64 = 0
310 let bp: *i64 = sys_mmap(16) as *i64
311 let now: i64 = nh_wallclock()
312 if nh_str_eq(plane, "tools" as *u8) == 1 {
313 let n: i64 = nh_read(base, "tools_remediations.log" as *u8, bp)
314 recent = nh_recent_epochs(bp[0] as *u8, n, now, NH_RECENT_WIN)
315 }
316 if nh_str_eq(plane, "edge" as *u8) == 1 {
317 let n2: i64 = nh_read(base, "edge_remediations.log" as *u8, bp)
318 recent = nh_recent_epochs(bp[0] as *u8, n2, now, NH_RECENT_WIN)
319 }
320 nh_puts("heal-plane remediations (last hour, plane=" as *u8); nh_puts(plane); nh_puts("): " as *u8); nh_putn(recent); nh_puts("\n" as *u8)
321 // supervisor-log correlation: how often does the supervisor mention this daemon?
322 let sb: *u8 = sys_mmap(NH_SUP_CAP)
323 let sn: i64 = vw_read(osp_supervisor_log(), sb, NH_SUP_CAP - 1) // path via the seam (deployment-specific)
324 if sn > 0 {
325 var hits: i64 = 0
326 var ls: i64 = 0
327 var j: i64 = 0
328 var lastls: i64 = 0 - 1
329 while j <= sn {
330 var eol: i64 = 0
331 if j == sn { eol = 1 } else { if sb[j] == (10 as u8) { eol = 1 } }
332 if eol == 1 {
333 if vw_contains((sb as i64 + ls) as *u8, j - ls, daemon, vw_slen(daemon)) == 1 { hits = hits + 1; lastls = ls }
334 ls = j + 1
335 }
336 j = j + 1
337 }
338 nh_puts("supervisor-log mentions: " as *u8); nh_putn(hits)
339 if lastls >= 0 {
340 // print the LAST mentioning line verbatim (bounded to its newline)
341 var e: i64 = lastls
342 var ge: i64 = 1
343 while ge == 1 { ge = 0; if e < sn { if sb[e] != (10 as u8) { e = e + 1; ge = 1 } } }
344 nh_puts(" last: " as *u8)
345 sys_write(1, (sb as i64 + lastls) as *u8, e - lastls)
346 }
347 nh_puts("\n" as *u8)
348 } else { nh_puts("supervisor-log: (unreadable -- correlation skipped)\n" as *u8) }
349 // verdict + explanation
350 let v: i64 = nh_classify(cnt, oldest, recent)
351 nh_puts("WHY-VERDICT: " as *u8)
352 if v == NH_V_DOWN { nh_puts("DOWN -- no live process; check its launcher/selfheal + last log lines\n" as *u8) }
353 if v == NH_V_STABLE { nh_puts("STABLE -- long-lived parent (age>=600s); any pid churn is per-connection WORKER children, NOT restarts. A monitor counting pid churn as restarts is a FALSE-POSITIVE crash-loop.\n" as *u8) }
354 if v == NH_V_LOOP_HEAL { nh_puts("RESTART-LOOP (heal-plane) -- young processes + recent selfheal remediations: the selfheal keeps bouncing it; root-cause the underlying failure (its log tail), the restart is a symptom.\n" as *u8) }
355 if v == NH_V_LOOP_UNTRACKED { nh_puts("RESTART-LOOP (UNTRACKED) -- young processes but NO heal-plane remediations: something OUTSIDE the heal plane restarts it (guard/reconcile/crash+respawn); check supervisor-log mentions above.\n" as *u8) }
356 return v
357}
358
359// ===================== live organ surfaces over MCP (2026-07-16) =====================
360// The fleet organ (:18095) and tenancy organ (:18094) serve their LIVE state as loopback HTTP text.
361// This fetches one and prints the BODY -- so `nx_heal fleet|tenancy` puts the live per-row state on MCP
362// through the already-capped heal tool (no new cap, no toolreg row). Plain HTTP GET, loopback-only by
363// construction (hardcoded 127.0.0.1), bounded read, 3s socket timeout -- a hung organ can't hang the tool.
364const NH_FETCH_CAP: i64 = 32768
365func nh_organ_body(port: i64) -> i64 {
366 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
367 if fd < 0 { nh_puts("(organ unreachable: socket failed)\n" as *u8); return 0 - 1 }
368 sys_set_socket_timeout(fd, 3)
369 let sa: *u8 = sys_mmap(16)
370 var i: i64 = 0
371 while i < 16 { sa[i] = 0 as u8; i = i + 1 }
372 sa[0] = 2 as u8
373 sa[2] = ((port / 256) % 256) as u8
374 sa[3] = (port % 256) as u8
375 sa[4] = 127 as u8
376 sa[7] = 1 as u8
377 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) != 0 {
378 sys_close(fd)
379 nh_puts("(organ DOWN on 127.0.0.1:" as *u8); nh_putn(port); nh_puts(" -- the supervisor should revive it)\n" as *u8)
380 return 0 - 1
381 }
382 let req: *u8 = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" as *u8
383 var rl: i64 = 0
384 while req[rl] != (0 as u8) { rl = rl + 1 }
385 sys_write(fd, req, rl)
386 let buf: *u8 = sys_mmap(NH_FETCH_CAP)
387 var n: i64 = 0
388 var rd: i64 = 1
389 while rd > 0 {
390 rd = sys_read(fd, (buf as i64 + n) as *u8, NH_FETCH_CAP - 1 - n)
391 if rd > 0 { n = n + rd }
392 if n >= NH_FETCH_CAP - 1 { rd = 0 }
393 }
394 sys_close(fd)
395 // print past the header terminator (\r\n\r\n); whole buffer if none found
396 var body: i64 = 0
397 i = 0
398 while i + 4 <= n {
399 if buf[i]==(13 as u8) { if buf[i+1]==(10 as u8) { if buf[i+2]==(13 as u8) { if buf[i+3]==(10 as u8) { body = i + 4; i = n } } } }
400 i = i + 1
401 }
402 sys_write(1, (buf as i64 + body) as *u8, n - body)
403 sys_munmap(buf, NH_FETCH_CAP)
404 return 0
405}
406
407func nh_remediations(base: *u8) -> i64 {
408 nh_puts("=== remediation events (force-restarts) ===\n" as *u8)
409 let bp: *i64 = sys_mmap(16) as *i64
410 let tn: i64 = nh_read(base, "tools_remediations.log" as *u8, bp)
411 let tcnt: i64 = nh_lines(bp[0] as *u8, tn)
412 nh_puts("tools: total=" as *u8); nh_putn(tcnt); nh_puts(" recent:\n" as *u8)
413 if tn > 0 { nh_tail(bp[0] as *u8, tn, NH_RECENT_EVTS) }
414 let en: i64 = nh_read(base, "edge_remediations.log" as *u8, bp)
415 let ecnt: i64 = nh_lines(bp[0] as *u8, en)
416 nh_puts("edge: total=" as *u8); nh_putn(ecnt); nh_puts(" recent:\n" as *u8)
417 if en > 0 { nh_tail(bp[0] as *u8, en, NH_RECENT_EVTS) }
418 return tcnt + ecnt // combined event count (gate-assertable)
419}