code wiki / _hdl_build / nx_procchurn.nx

nx_procchurn.nx source

↩ module page · 343 lines · 20244 B

1// nx_procchurn.nx -- the PROCESS-CHURN axis the health plane never had (debt seq1317 / seq1318). 2// 3// WHY THIS EXISTS: 2026-07-30, nx_health said {overall:OK, degraded:0, down:0} with all 14 services UP 4// while the NAS was forking 78.3 processes/sec sustained, burning 231 permil of an 8-core box in the 5// KERNEL against 98 permil in userspace (a 2.36x kernel:user inversion) and taking 91718 context 6// switches/sec. nx_resmon owns MEMORY, nx_netobs owns NETWORK -- the process-churn axis had NO organ, so 7// the largest CPU consumer on the platform was invisible to every instrument we owned and was found by 8// hand. This is that instrument. 9// LAW: liveness is not performance. A board that only asks "is it up" cannot see the machine burning. 10// 11// WHAT IT MEASURES (two /proc/stat samples bracketing a real window -- deltas, never since-boot totals, 12// because a since-boot average hides a storm that started an hour ago): 13// forks/sec -- `processes` delta. THE headline: fork+exec is the most expensive thing a kernel does. 14// ctxsw/sec -- `ctxt` delta. Rises with churn AND with thrash; corroborates, never diagnoses alone. 15// intr/sec -- `intr` delta, first field. Device pressure, reported for context (not thresholded). 16// kernel:user -- system:user+nice jiffies as permil. 1000 = parity. THE inversion detector. 17// busy/idle -- share of measured capacity, self-normalising (no HZ and no core count needed -- 18// every share is a fraction of the SAME jiffy total, so the arithmetic cannot drift 19// when the box changes size. That is what makes this instrument hardware-portable). 20// 21// Policy lives in nx_procchurn_lib.nx so the gate exercises the SAME code (rule 9 + rule 15); thresholds 22// are data-driven from knowledge/status/procchurn.conf (rule 11). Read-only. No hw writes (Rule 26). 23// Exit code IS the verdict so a caller can gate on it: 0=GREEN 1=AMBER 2=RED 3=UNMEASURED. 24// license_tier: ORIGINAL expect_exit: 0 25import "nx_procchurn_lib.nx" 26 27const PC_STATBUF: i64 = 8192 28const PC_CONFBUF: i64 = 4096 29const PC_FIELD_BYTES: i64 = 256 30const PC_NCPU_FIELDS: i64 = 10 31const PC_DEF_WINDOW_MS: i64 = 3000 32const PC_MIN_WINDOW_MS: i64 = 1000 33const PC_MAX_WINDOW_MS: i64 = 10000 34const PC_US_PER_MS: i64 = 1000 35const PC_DEF_F_AMBER: i64 = 20 36const PC_DEF_F_RED: i64 = 50 37const PC_DEF_R_AMBER: i64 = 1000 38const PC_DEF_R_RED: i64 = 1500 39const PC_DEF_C_AMBER: i64 = 20000 40const PC_DEF_C_RED: i64 = 50000 41const PC_EXIT_UNMEASURED: i64 = 3 42const PC_JBUF: i64 = 512 43const PC_MODE_644: i64 = 420 44// ---- OB5 constants. PC_MS_PER_S is declared separately from PC_US_PER_MS although both are 1000: 45// ---- one constant serving two unrelated purposes can never be moved for either of them. 46const PC_SEEK_SET: i64 = 0 47const PC_SEEK_END: i64 = 2 48const PC_MS_PER_S: i64 = 1000 49// Two measurement windows of margin. The frame this run just appended is at most ONE window old, so a 50// bar of two windows can only be blown by a frame this run did NOT write -- which is precisely the 51// condition (a silently failed append) that the freshness check exists to catch. 52const PC_ACT_AGE_WINDOWS: i64 = 2 53// Journal tail read for the consumer. Sized to hold many frames, not one: a partial final write must not 54// be able to push the last COMPLETE frame out of the window, and reading the head of an append-only file 55// would sample its past rather than its present. 56const PC_ACTTAIL: i64 = 4096 57// OB4 bounds. PC_PIDCAP sits above this host measured process count (780 on 2026-09-03) with headroom, 58// and reaching it sets a cap_hit flag that turns the count into a declared FLOOR rather than a silent 59// total. PC_TALLY_MAX bounds the distinct-parent table: a storm from many parents is reported truncated 60// and SAYS so, which is the only honest failure a fixed table has. 61// PC_PATHBUF and PC_STATBUF are NOT redeclared here -- the lib and this organ already own one each. 62const PC_PIDCAP: i64 = 4096 63const PC_PIDS_BYTES: i64 = 32768 64const PC_TALLY_MAX: i64 = 64 65const PC_TALLY_BYTES: i64 = 512 66 67// APPEND one frame to the trend journal. Append-only and never truncating: history is sacred (rule 13), 68// and the burst structure this organ measures is only visible ACROSS runs. Failure to journal is NOT 69// fatal -- a read-only or full filesystem must not cost us the live measurement -- but it IS reported, 70// because a silently-missing journal would make the trend look flat instead of absent. 71func pc_jappend(path: *u8, buf: *u8, n: i64) -> i64 { 72 let fd: i64 = sys_openat_append(path, PC_MODE_644) 73 if fd < 0 { return 0 - 1 } 74 let w: i64 = sys_write(fd, buf, n) 75 sys_close(fd) 76 return w 77} 78 79// read one scalar counter line ("processes 4153147") -- returns the value or -1 FAIL-CLOSED. 80func pc_scalar(buf: *u8, n: i64, key: *u8, out: *i64) -> i64 { 81 let c: i64 = pc_line_ints(buf, n, key, out, 1) 82 if c < 1 { return 0 - 1 } 83 return out[0] 84} 85 86func main() -> i64 { 87 let cbuf: *u8 = sys_mmap(PC_CONFBUF) 88 let cn: i64 = rm_read("knowledge/status/procchurn.conf" as *u8, cbuf, PC_CONFBUF) 89 var win_ms: i64 = rm_conf(cbuf, cn, "window-ms" as *u8, PC_DEF_WINDOW_MS) 90 if win_ms < PC_MIN_WINDOW_MS { win_ms = PC_MIN_WINDOW_MS } 91 if win_ms > PC_MAX_WINDOW_MS { win_ms = PC_MAX_WINDOW_MS } 92 let f_amber: i64 = rm_conf(cbuf, cn, "forks-per-sec-amber" as *u8, PC_DEF_F_AMBER) 93 let f_red: i64 = rm_conf(cbuf, cn, "forks-per-sec-red" as *u8, PC_DEF_F_RED) 94 let r_amber: i64 = rm_conf(cbuf, cn, "sysuser-permil-amber" as *u8, PC_DEF_R_AMBER) 95 let r_red: i64 = rm_conf(cbuf, cn, "sysuser-permil-red" as *u8, PC_DEF_R_RED) 96 let c_amber: i64 = rm_conf(cbuf, cn, "ctxsw-per-sec-amber" as *u8, PC_DEF_C_AMBER) 97 let c_red: i64 = rm_conf(cbuf, cn, "ctxsw-per-sec-red" as *u8, PC_DEF_C_RED) 98 99 let b1: *u8 = sys_mmap(PC_STATBUF) 100 let b2: *u8 = sys_mmap(PC_STATBUF) 101 let c1: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 102 let c2: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 103 let sc: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 104 105 // ---- SAMPLE 1 -> window -> SAMPLE 2. The window is measured, never assumed: sys_sleep_ms can 106 // ---- oversleep on a loaded box, and using the REQUESTED window would inflate every rate. 107 let t1: i64 = sys_now_us() 108 let n1: i64 = rm_read("/proc/stat" as *u8, b1, PC_STATBUF) 109 let p1: i64 = pc_scalar(b1, n1, "processes " as *u8, sc) 110 let x1: i64 = pc_scalar(b1, n1, "ctxt " as *u8, sc) 111 let i1: i64 = pc_scalar(b1, n1, "intr " as *u8, sc) 112 let nf1: i64 = pc_line_ints(b1, n1, "cpu " as *u8, c1, PC_NCPU_FIELDS) 113 // OB4: the pid set BEFORE the window. getdents only -- no per-process reads here. 114 let prev: *i64 = sys_mmap(PC_PIDS_BYTES) as *i64 115 let pfull: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 116 let nprev: i64 = pc_pids(prev, PC_PIDCAP, pfull) 117 sys_sleep_ms(win_ms) 118 let t2: i64 = sys_now_us() 119 let n2: i64 = rm_read("/proc/stat" as *u8, b2, PC_STATBUF) 120 let p2: i64 = pc_scalar(b2, n2, "processes " as *u8, sc) 121 let x2: i64 = pc_scalar(b2, n2, "ctxt " as *u8, sc) 122 let i2: i64 = pc_scalar(b2, n2, "intr " as *u8, sc) 123 let nf2: i64 = pc_line_ints(b2, n2, "cpu " as *u8, c2, PC_NCPU_FIELDS) 124 let elapsed_ms: i64 = (t2 - t1) / PC_US_PER_MS 125 // ---- OB4: WHO IS FORKING. Attribute the processes that appeared inside this window to their 126 // ---- PARENTS, and publish it as a DECLARED LOWER BOUND beside the kernel counter. 127 // COST, BUDGETED BEFORE IT WAS WRITTEN RATHER THAN AFTER: the enumeration is getdents on /proc, a 128 // handful of syscalls regardless of process count. Only the pids that are NEW get a stat read, and 129 // at this host measured fork rate that is a few dozen per window -- roughly 50 syscalls per beat, not 130 // the ~2300 a full per-process walk would cost. That arithmetic is why this runs on every beat 131 // instead of hiding behind a severity check: an instrument must not become the load it measures, and 132 // the way to honour that is to size the work before writing it, not to add a switch afterwards. 133 let cur: *i64 = sys_mmap(PC_PIDS_BYTES) as *i64 134 let cfull: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 135 let ncur: i64 = pc_pids(cur, PC_PIDCAP, cfull) 136 let tpar: *i64 = sys_mmap(PC_TALLY_BYTES) as *i64 137 let tcnt: *i64 = sys_mmap(PC_TALLY_BYTES) as *i64 138 let sb4: *u8 = sys_mmap(PC_STATBUF) 139 let pb4: *u8 = sys_mmap(PC_PATHBUF) 140 let so4: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 141 let ao4: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 142 var attr_ok: i64 = 0 143 if nprev >= 0 { if ncur >= 0 { attr_ok = 1 } } 144 if attr_ok == 1 { pc_fork_by_parent(prev, nprev, cur, ncur, tpar, tcnt, PC_TALLY_MAX, sb4, pb4, so4, ao4) } 145 146 var forks_ps: i64 = 0 - 1 147 if p1 >= 0 { if p2 >= 0 { forks_ps = pc_rate(p2 - p1, elapsed_ms) } } 148 var ctxsw_ps: i64 = 0 - 1 149 if x1 >= 0 { if x2 >= 0 { ctxsw_ps = pc_rate(x2 - x1, elapsed_ms) } } 150 var intr_ps: i64 = 0 - 1 151 if i1 >= 0 { if i2 >= 0 { intr_ps = pc_rate(i2 - i1, elapsed_ms) } } 152 153 var cpu_ok: i64 = 0 154 if nf1 >= PC_NCPU_FIELDS { if nf2 >= PC_NCPU_FIELDS { cpu_ok = 1 } } 155 var usr_d: i64 = 0 156 var sys_d: i64 = 0 157 var irq_d: i64 = 0 158 var idle_d: i64 = 0 159 var iow_d: i64 = 0 160 var tot_d: i64 = 0 161 if cpu_ok == 1 { 162 var k: i64 = 0 163 while k < PC_NCPU_FIELDS { tot_d = tot_d + (c2[k] - c1[k]); k = k + 1 } 164 usr_d = (c2[0] - c1[0]) + (c2[1] - c1[1]) 165 sys_d = c2[2] - c1[2] 166 idle_d = c2[3] - c1[3] 167 iow_d = c2[4] - c1[4] 168 irq_d = (c2[5] - c1[5]) + (c2[6] - c1[6]) 169 } 170 var ratio_pm: i64 = 0 - 1 171 if cpu_ok == 1 { ratio_pm = pc_ratio_permil(sys_d, usr_d) } 172 var usr_pm: i64 = 0 - 1 173 var sys_pm: i64 = 0 - 1 174 var irq_pm: i64 = 0 - 1 175 var busy_pm: i64 = 0 - 1 176 if tot_d > 0 { 177 usr_pm = (usr_d * 1000) / tot_d 178 sys_pm = (sys_d * 1000) / tot_d 179 irq_pm = (irq_d * 1000) / tot_d 180 busy_pm = ((tot_d - idle_d - iow_d) * 1000) / tot_d 181 } 182 183 // FAIL-CLOSED: a negative (unmeasurable) axis compares BELOW every threshold and would otherwise be 184 // published as a confident GREEN. That is the partial-as-complete defect the operator banned, so an 185 // incomplete sample gets its OWN exit code and prints no verdict at all. 186 var measured: i64 = 1 187 if forks_ps < 0 { measured = 0 } 188 if ctxsw_ps < 0 { measured = 0 } 189 if ratio_pm < 0 { measured = 0 } 190 if cpu_ok == 0 { measured = 0 } 191 if tot_d <= 0 { measured = 0 } 192 193 rm_puts("=== nx_procchurn -- the process-churn axis nx_health lacks (seq1317) ===\n" as *u8) 194 rm_puts("window_ms=" as *u8); rm_num(elapsed_ms) 195 rm_puts(" (requested " as *u8); rm_num(win_ms); rm_puts(", MEASURED not assumed)\n" as *u8) 196 if measured == 0 { 197 rm_puts("verdict=UNMEASURED sev=" as *u8); rm_num(PC_EXIT_UNMEASURED) 198 rm_puts(" why=a /proc/stat counter was absent or went backwards (wrap/reboot); NO verdict is" as *u8) 199 rm_puts(" published off a partial sample\n" as *u8) 200 return PC_EXIT_UNMEASURED 201 } 202 rm_puts("forks_per_sec=" as *u8); rm_num(forks_ps) 203 rm_puts(" (amber>=" as *u8); rm_num(f_amber); rm_puts(" red>=" as *u8); rm_num(f_red); rm_puts(")\n" as *u8) 204 rm_puts("ctxsw_per_sec=" as *u8); rm_num(ctxsw_ps) 205 rm_puts(" (amber>=" as *u8); rm_num(c_amber); rm_puts(" red>=" as *u8); rm_num(c_red); rm_puts(")\n" as *u8) 206 rm_puts("intr_per_sec=" as *u8); rm_num(intr_ps); rm_puts(" (context only, not thresholded)\n" as *u8) 207 rm_puts("kernel_user_permil=" as *u8); rm_num(ratio_pm) 208 rm_puts(" (1000=parity; amber>=" as *u8); rm_num(r_amber); rm_puts(" red>=" as *u8); rm_num(r_red); rm_puts(")\n" as *u8) 209 rm_puts("cpu_share_permil: user=" as *u8); rm_num(usr_pm) 210 rm_puts(" system=" as *u8); rm_num(sys_pm) 211 rm_puts(" irq_softirq=" as *u8); rm_num(irq_pm) 212 rm_puts(" busy=" as *u8); rm_num(busy_pm); rm_puts("\n" as *u8) 213 rm_puts("jiffies_delta: user+nice=" as *u8); rm_num(usr_d) 214 rm_puts(" system=" as *u8); rm_num(sys_d) 215 rm_puts(" total=" as *u8); rm_num(tot_d); rm_puts("\n" as *u8) 216 217 // QUEUE COMPOSITION (seq1555) -- the number that separates an I/O-blocked queue from a compute one, 218 // and it is FREE: /proc/stat already carries procs_running and procs_blocked, so this costs ZERO extra 219 // syscalls and needs NO admission gate (unlike a /proc walk, which is refused exactly when a deep 220 // queue makes it most interesting -- the priority-lane trap). 221 // WHY IT MATTERS: loadavg counts TASK_UNINTERRUPTIBLE as well as runnable, so a load of 14.94 against 222 // a 50.7pct-idle CPU says the QUEUE is deep but never says WHAT it waits on. iowait cannot answer 223 // either -- Linux only accrues it while a CPU is otherwise IDLE, so a D-state task on a busy core is 224 // invisible to it. procs_blocked counts those tasks DIRECTLY. 225 // READ IT AS: blocked >> running => the queue is I/O/lock-bound, and adding CPU would change nothing. 226 var blocked: i64 = 0 - 1 227 var running: i64 = 0 - 1 228 if pc_line_ints(b2, n2, "procs_blocked " as *u8, sc, 1) > 0 { blocked = sc[0] } 229 if pc_line_ints(b2, n2, "procs_running " as *u8, sc, 1) > 0 { running = sc[0] } 230 rm_puts("queue: procs_running=" as *u8); rm_num(running) 231 rm_puts(" procs_blocked=" as *u8); rm_num(blocked) 232 rm_puts(" (blocked >> running = I/O or lock bound, NOT compute; more CPU would not help)\n" as *u8) 233 234 // OB4 REPORT. The coverage line is the whole point: forks_attributed is a census of SURVIVORS and 235 // kernel_forks is the true count, so the ratio says how much of the churn this attribution can even 236 // see. A low ratio is not an error -- it is the short-lived majority, and it is the reason a pid diff 237 // must never be published as a fork rate. When /proc could not be enumerated the axis says so rather 238 // than printing a zero, because zero attributed and zero looked at are opposite findings. 239 if attr_ok == 0 { rm_puts("forkattrib=UNOBSERVABLE (/proc could not be enumerated; NOT the same as nothing forked)\n" as *u8) } 240 if attr_ok == 1 { 241 let kforks: i64 = p2 - p1 242 rm_puts("forkattrib=LOWER-BOUND new_survivors=" as *u8); rm_num(ao4[0]) 243 rm_puts(" attributed=" as *u8); rm_num(ao4[1]) 244 rm_puts(" distinct_parents=" as *u8); rm_num(ao4[2]) 245 rm_puts(" vanished_before_read=" as *u8); rm_num(ao4[3]) 246 rm_puts(" kernel_forks=" as *u8); rm_num(kforks) 247 rm_puts(" coverage_permil=" as *u8); rm_num(pc_attrib_permil(ao4[1], kforks)) 248 rm_puts(" pids_seen=" as *u8); rm_num(ncur) 249 rm_puts(" cap_hit=" as *u8); rm_num(cfull[0]) 250 rm_puts(" (a SURVIVOR census: the short-lived majority is invisible to it BY CONSTRUCTION, so coverage_permil is how much of the kernel fork count this attribution could account for -- never read the attributed number as a fork rate)\n" as *u8) 251 var tp4: i64 = 0 252 while tp4 < ao4[2] { 253 rm_puts(" parent ppid=" as *u8); rm_num(tpar[tp4]) 254 rm_puts(" spawned=" as *u8); rm_num(tcnt[tp4]) 255 rm_puts("\n" as *u8) 256 tp4 = tp4 + 1 257 } 258 } 259 let sev: i64 = pc_verdict(forks_ps, ratio_pm, ctxsw_ps, f_amber, f_red, r_amber, r_red, c_amber, c_red) 260 261 // ---- TREND: one append-only frame per run. A single verdict is a snapshot; the storm is bursty, 262 // ---- so only the journal can show whether a fix actually landed. 263 let jb: *u8 = sys_mmap(PC_JBUF) 264 let jn: i64 = pc_frame(jb, sys_now_realtime_sec(), elapsed_ms, forks_ps, ctxsw_ps, intr_ps, 265 ratio_pm, usr_pm, sys_pm, busy_pm, sev, running, blocked) 266 let jw: i64 = pc_jappend("knowledge/status/procchurn.jrnl" as *u8, jb, jn) 267 rm_puts("journal=knowledge/status/procchurn.jrnl " as *u8) 268 if jw < 0 { rm_puts("APPEND-FAILED (measurement still valid; the TREND is what was lost)\n" as *u8) } 269 if jw >= 0 { rm_puts("frame_bytes=" as *u8); rm_num(jw); rm_puts("\n" as *u8) } 270 271 // ---- OB5: CONSUME THE SEVERITY WE JUST WROTE -- the actuator half of a loop that had only a sensor. 272 // It reads the frame back OFF DISK rather than reusing `sev` above, ON PURPOSE: reading the live 273 // variable would prove the arithmetic and prove nothing about the WIRE, and the wire is exactly where 274 // a producer and a consumer that are each correct in isolation still disagree. If the append silently 275 // failed, this reads the PREVIOUS frame, its age blows the bar, and the action is UNOBSERVABLE -- 276 // which is the honest answer, and is one a read of the in-memory value could never have produced. 277 let ab: *u8 = sys_mmap(PC_ACTTAIL) 278 var an: i64 = 0 279 let afd: i64 = sys_openat_rd("knowledge/status/procchurn.jrnl" as *u8) 280 if afd >= 0 { 281 let asz: i64 = sys_lseek(afd, 0, PC_SEEK_END) 282 var aoff: i64 = 0 283 if asz > PC_ACTTAIL { aoff = asz - PC_ACTTAIL } 284 sys_lseek(afd, aoff, PC_SEEK_SET) 285 an = sys_read(afd, ab, PC_ACTTAIL) 286 sys_close(afd) 287 } 288 if an < 0 { an = 0 } 289 let ao: *i64 = sys_mmap(PC_FIELD_BYTES) as *i64 290 let ard: i64 = pc_sev_last(ab, an, ao) 291 // THE STALENESS BAR IS DERIVED, NOT PICKED. The frame this run just appended is at most one 292 // measurement window old, so two windows leaves a full window of margin -- the same shape as a drain 293 // deadline that follows its socket timeout rather than inventing a number. A conf row OVERRIDES the 294 // derivation, and the provenance is PRINTED so the next reader never has to guess which one won. 295 var act_max_age: i64 = (win_ms / PC_MS_PER_S) * PC_ACT_AGE_WINDOWS 296 var act_prov: *u8 = "DERIVED-from-window" as *u8 297 let act_conf: i64 = rm_conf(cbuf, cn, "act-max-age-s" as *u8, 0) 298 if act_conf > 0 { act_max_age = act_conf; act_prov = "CONF" as *u8 } 299 var disk_sev: i64 = 0 - 1 300 var disk_age: i64 = 0 - 1 301 if ard == 1 { disk_sev = ao[0]; disk_age = sys_now_realtime_sec() - ao[1] } 302 let act: i64 = pc_sev_consumer(disk_sev, disk_age, act_max_age) 303 rm_puts("act=" as *u8); rm_puts(pc_act_name(act)) 304 rm_puts(" read=" as *u8) 305 if ard == 1 { rm_puts("FRAME" as *u8) } 306 if ard == 0 { rm_puts("NO-FRAME" as *u8) } 307 if ard < 0 { rm_puts("UNPARSEABLE" as *u8) } 308 rm_puts(" disk_sev=" as *u8); rm_num(disk_sev) 309 rm_puts(" age_s=" as *u8); rm_num(disk_age) 310 rm_puts(" max_age_s=" as *u8); rm_num(act_max_age) 311 rm_puts(" bar=" as *u8); rm_puts(act_prov) 312 rm_puts("\n" as *u8) 313 // TRUNCATE-write, never append. A pre-deploy check that greps an append-only journal is vacuously 314 // green FOREVER, because a marker that ever appeared still matches; that is measured estate law, not 315 // a preference. A rewritten file carrying one canonical line is the only shape a consumer can read. 316 let sb: *u8 = sys_mmap(PC_JBUF) 317 var so: i64 = 0 318 so = pc_cat(sb, so, "PROCCHURN-ACT act=" as *u8) 319 so = pc_cat(sb, so, pc_act_name(act)) 320 so = pc_cat(sb, so, " disk_sev=" as *u8) 321 so = pc_catn(sb, so, disk_sev) 322 so = pc_cat(sb, so, " age_s=" as *u8) 323 so = pc_catn(sb, so, disk_age) 324 so = pc_cat(sb, so, " max_age_s=" as *u8) 325 so = pc_catn(sb, so, act_max_age) 326 so = pc_cat(sb, so, " ts=" as *u8) 327 so = pc_catn(sb, so, sys_now_realtime_sec()) 328 so = pc_cat(sb, so, "\n" as *u8) 329 let sfd: i64 = sys_openat_wr("knowledge/status/procchurn.act" as *u8, PC_MODE_644) 330 if sfd >= 0 { sys_write(sfd, sb, so); sys_close(sfd) } 331 if sfd < 0 { rm_puts("act_status=WRITE-FAILED (the action stands; only its durable receipt was lost)\n" as *u8) } 332 333 rm_puts("verdict=" as *u8) 334 if sev == 0 { rm_puts("GREEN" as *u8) } 335 if sev == 1 { rm_puts("AMBER" as *u8) } 336 if sev == 2 { rm_puts("RED" as *u8) } 337 rm_puts(" sev=" as *u8); rm_num(sev); rm_puts("\n" as *u8) 338 // A RED/AMBER churn axis names no producer -- point the operator at the tool that does, so the 339 // on-demand diagnosis path is self-documenting (nx_ctxtop classifies SYSTEM=DSM / ESTATE=nx_* / 340 // KERNEL, 2026-08-12). Only on AMBER/RED: a GREEN box needs no producer hunt. 341 if sev >= 1 { rm_puts("next=run nx_ctxtop -- it names WHO produces the churn and classifies it (SYSTEM=DSM / ESTATE=nx_* / KERNEL); this axis is not GREEN but does not name the producer\n" as *u8) } 342 return sev 343}