code wiki / _hdl_build / nx_resmon.nx

nx_resmon.nx source

↩ module page · 789 lines · 49276 B

1// nx_resmon.nx -- the RESOURCE axis the health plane never had (debt seq897 / seq1005). 2// 3// WHY THIS EXISTS: on 2026-07-25 nx_health returned {overall:OK, degraded:0, down:0} while the box was 4// at 93.4% swap exhaustion with nr_vmscan_immediate_reclaim 1.33e9 -- sustained thrash. The health plane 5// probes GATEWAYS ONLY, so the single most destabilising condition on the platform was invisible to 6// every instrument we owned. 7// LAW: an instrument that reports OK during the worst outage class it could ever see is not an instrument. 8// 9// TWO AXES: 10// A. PRESSURE -- swap consumed / memory available. A LAGGING indicator: by the time it is red the 11// box is already thrashing. 12// B. LEAK CENSUS -- processes whose VmSize == VmPeak above a COMMITTED-memory floor. A LEADING 13// indicator: it fires while swap is still healthy. This is the fingerprint that 14// identified the leaking organs in the first place. 15// 16// This organ owns the /proc walk and the printing ONLY. Both policy predicates live in nx_resmon_lib.nx 17// so the gate exercises the SAME code this runs (rule 9 single responsibility, rule 15 DRY). 18// Thresholds are data-driven from knowledge/status/resmon.conf (rule 11). Read-only. 19// Exit code IS the verdict so a caller can gate on it: 0=GREEN 1=AMBER 2=RED. 20// 21// license_tier: ORIGINAL expect_exit: 0 22import "nx_resmon_lib.nx" 23import "nx_proc_ctl.nx" 24import "nx_logtail.nx" // lt_read_tail -- THE shared tail reader (runtime/): rm_trend must read the NEWEST rows of its append-only log, never its head (2026-09-02) 25const RM_MAGIC_1941: i64 = 1941 26const RM_MAGIC_1024: i64 = 1024 27const RM_MAGIC_262144: i64 = 262144 28 29const RM_DIRBUF: i64 = 65536 30// AXIS D defaults, in BYTES (see rm_disk_sev for why bytes and not permil). 31const RM_DISK_AMBER_DEFAULT: i64 = 5368709120 32const RM_DISK_RED_DEFAULT: i64 = 1073741824 33// The two volumes this host actually writes to. "." is the estate's own volume (cwd = nishihost, i.e. 34// /volume1); "/" is the ROOT partition, which is SMALL and was measured at 525 MiB free on 2026-08-28 35// while /volume1 had 21.0 TB -- the exact pair that proves a percentage bar is the wrong ruler. 36// WORST VOLUME DECIDES, the same shape the rest of this organ already uses for its axes. 37const RM_DISK_PATH_ESTATE: *u8 = "." 38const RM_DISK_PATH_ROOT: *u8 = "/" 39// AXIS D root-partition bars, derived from the MEASURED 2.3 GiB DSM system partition (privileged du 40// census 2026-08-29, knowledge/status/rootfs_du.txt: 1.2 GiB immovable system baseline): red 256 MiB 41// (~11 percent free -- below this a DSM update or a log burst can wedge the system), amber 512 MiB 42// (~22 percent). Bottom tier of rule 17; resmon.conf rows *-root override. 43const RM_DISK_AMBER_ROOT_DEFAULT: i64 = 536870912 44const RM_DISK_RED_ROOT_DEFAULT: i64 = 268435456 45// TREND AXIS (loadgov LV1 rm_trend_gatereg, 2026-08-30): the slow-leak verdict rm_trend WRITES every 46// beat was never COLLECTED -- a 3.7 GB leaker (nx_seed_announc, +114-186 MB/beat) fired verdict=RED 47// in resmon_trend.log for hours and was found only by a lane reading the log by hand. The collector 48// reads the log's LAST line (positional, the gv_last_line shape) and RAISES the verdict to at least 49// AMBER on a fresh RED -- only-raises; a stale, absent or unparseable line ABSTAINS (UNOBSERVABLE, 50// never an acquittal and never a fabricated alarm). Freshness DERIVED from the OBSERVED trend 51// cadence, not the declared one (rows 1788092862 -> 1788096467 = ~3,605 s, an hourly beat): one full 52// missed beat plus the measured scheduler-lag envelope = 2.5x cadence = 9000 s. conf row 53// trend-max-age-s overrides (rule 17). 54const RM_TREND_LOG: *u8 = "knowledge/status/resmon_trend.log" 55const RM_TREND_MAX_AGE_DEFAULT: i64 = 9000 56const RM_TRENDLOG: *u8 = "knowledge/status/resmon_trend.log" 57// ⚠ITS OWN FILE, NEVER resmon.log: rm_trend PARSES that file for worst_committed_kb= on every row of the 58// window, so a verdict line appended there would be read back as a malformed sample and turn this 59// detector UNOBSERVABLE. ★A WRITER THAT LOGS INTO ITS OWN INPUT POISONS THE MEASUREMENT IT JUST TOOK. 60// ONE WRITER, ONE LOG, ONE ROW -- the estate's own remedy for a gatereg row that measures a race. 61// ★CLOSED VOCABULARY ONLY (GREEN/RED), same law as rm_log: a new dialect here becomes another 62// UNREADABLE-DIALECT row in the rollup this is trying to reach. UNOBSERVABLE therefore rides as 63// `observed=0` BESIDE a GREEN verdict, and that trade is deliberate: the common unobservable cases are 64// benign (a short log at startup, the worst process changing inside the window), so mapping them to RED 65// would make this permanently red and therefore ignored. THE IMPRECISION I CHOSE: a genuinely unreadable 66// log also reads GREEN here; it is distinguishable ONLY by observed=0, so any consumer must read it. 67func rm_trend_log(nm: *u8, newest: i64, grew: i64, span: i64, shrank: i64, flat: i64, delta: i64, observed: i64, v: *u8) -> i64 { 68 let fd: i64 = sys_openat_append(RM_TRENDLOG, 420) 69 if fd < 0 { 70 // ★★★★★AN UNWRITABLE TREND MUST ANNOUNCE, NOT VANISH: a silently missing row reads later as "the 71 // beat did not fire". Same law as nx_rebuild_drain's rd_trend. 72 // ⚠THE FIRST VERSION OF THIS COMMENT BLAMED THE MISSING ANNOUNCE FOR AN "ABSENT" LOG. FALSE, and 73 // corrected here: the very first write SUCCEEDED. I had read the log in the same parallel batch 74 // that launched the ASYNC job -- before it ran -- and diagnosed a write bug that did not exist. 75 // ★★★★★A RACE IN THE OBSERVER LOOKS EXACTLY LIKE A DEFECT IN THE SUBJECT, AND THE COMMENT YOU 76 // WRITE ABOUT IT OUTLIVES THE MISTAKE. The announce is still correct on its own merits. 77 rm_puts("RESMON-TREND trend-log UNWRITABLE rc=" as *u8); rm_num(fd) 78 rm_puts(" path=" as *u8); rm_puts(RM_TRENDLOG); rm_puts("\n" as *u8) 79 return 0 - 1 80 } 81 let b: *u8 = sys_mmap(RM_MAGIC_1024) 82 var o: i64 = 0 83 o = rm_lnum(b, o, sys_now_realtime_sec()) 84 o = rm_lcat(b, o, "\tRESMON-TREND worst=" as *u8) 85 o = rm_lcat(b, o, nm) 86 o = rm_lcat(b, o, " newest_kb=" as *u8); o = rm_lnum(b, o, newest) 87 o = rm_lcat(b, o, " grew_in=" as *u8); o = rm_lnum(b, o, grew) 88 o = rm_lcat(b, o, "/" as *u8); o = rm_lnum(b, o, span) 89 o = rm_lcat(b, o, " shrank=" as *u8); o = rm_lnum(b, o, shrank) 90 o = rm_lcat(b, o, " flat=" as *u8); o = rm_lnum(b, o, flat) 91 o = rm_lcat(b, o, " delta_kb=" as *u8); o = rm_lnum(b, o, delta) 92 o = rm_lcat(b, o, " observed=" as *u8); o = rm_lnum(b, o, observed) 93 o = rm_lcat(b, o, "\tverdict=" as *u8) 94 o = rm_lcat(b, o, v) 95 o = rm_lcat(b, o, "\n" as *u8) 96 sys_write(fd, b, o) 97 sys_close(fd) 98 sys_munmap(b, RM_MAGIC_1024) 99 return 0 100} 101const RM_STATBUF: i64 = 4096 102 103// ★★★★★★AN INSTRUMENT NOTHING CAN READ IS NOT WIRED. This organ printed its verdict to STDOUT ONLY, 104// and knowledge/gatereg.conf is DERIVED from knowledge/status/*.log files that carry a verdict anchor -- 105// so nx_resmon could never enter the roster, never reach nx_gate_rollup, and its RED could never be seen 106// by anyone not running it BY HAND. MEASURED CONSEQUENCE 2026-08-06: swap sat at 938permil with 15 leak 107// suspects and worst=nx_seed_announc at 6.4GB, while nx_gate_rollup read 73/104 and nx_health read 108// overall=OK -- and the live edge stalled hard enough that https://nishifamily.com/writer TIMED OUT for 109// the operator. The organ written precisely because "the single most destabilising condition on the 110// platform was invisible to every instrument we owned" was ITSELF invisible to the one organ that 111// answers "is anything not green?". The header law above indicts this file until this function exists. 112// ★ONE LINE, OVERWRITTEN, NOT APPENDED: nx_gate_rollup reads only the NEWEST line (gv_last_line), and a 113// cumulative status log mined with no time window reports a FIXED problem as a LIVE one -- so a single 114// current-state line is simultaneously what the consumer wants and self-bounding. 115// ★EMITS ONLY THE CLOSED VERDICT VOCABULARY (GREEN/RED), never a new dialect: AMBER maps to RED because 116// a degraded resource state must be SEEN (amber swap is what preceded today's outage), and the nuance 117// rides alongside as state=/sev= for the human. Inventing verdict=AMBER here would have made this organ 118// the twelfth UNREADABLE-DIALECT row in the very rollup it is trying to reach. 119// rm_lcat / rm_lnum MOVED to nx_resmon_lib.nx 2026-08-14: nx_memvel now emits a status line too, and 120// the second organ was about to grow its own copy. ★TWO COPIES OF A HELPER DIVERGE SILENTLY BECAUSE 121// NOTHING COMPARES THEM. (This organ COUNTS leakers; it will not be one -- debt RM_MAGIC_1941.) 122// ---- LOAD-HISTORY COLLECTOR (2026-08-17, the /compare/loadgov rm_load1_centi contract) ---------- 123// The arming protocol for the build-admission load ceiling needs a REPRESENTATIVE loadavg window 124// ("sample over a window, set the ceiling above the normal distribution and below the wedge point, 125// record the measurement" -- nx_sov_build_run's own comment), and NO organ logged loadavg anywhere: 126// this log carried swap and mem only, procchurn is event-sparse. One field on the beat line the 127// key-anchored trend readers already parse -- APPENDED as key=value so every existing reader is 128// unmoved. -1 = /proc/loadavg unreadable, PRINTED not hidden: an absent sample must never read as 129// load 0 (an axis that cannot see must abstain, not acquit). 130const RM_LOADAVG: *u8 = "/proc/loadavg" 131const RM_LOADAVG_CAP: i64 = 128 132const RM_ASCII_ZERO: i64 = 48 // '0' -- decimal-digit floor for the loadavg parser 133const RM_ASCII_NINE: i64 = 57 // '9' -- decimal-digit ceiling 134const RM_ASCII_DOT: i64 = 46 // '.' -- the loadavg decimal point, skipped so 14.49 -> 1449 135func rm_load1_centi() -> i64 { 136 let lb: *u8 = sys_mmap(RM_LOADAVG_CAP) 137 let fd: i64 = sys_openat_rd(RM_LOADAVG) 138 if fd < 0 { sys_munmap(lb, RM_LOADAVG_CAP); return 0 - 1 } 139 let n: i64 = sys_read(fd, lb, RM_LOADAVG_CAP) 140 sys_close(fd) 141 if n <= 0 { sys_munmap(lb, RM_LOADAVG_CAP); return 0 - 1 } 142 // "14.49 ..." -> 1449: loadavg always prints exactly two decimals, so digits-with-dot-skipped IS 143 // the centi value. Separate exit flag, never a clobbered cursor (the banked eol-sentinel hazard). 144 var v: i64 = 0 145 var seen: i64 = 0 146 var i: i64 = 0 147 var go: i64 = 1 148 while go == 1 { 149 if i >= n { go = 0 } else { 150 let c: i64 = lb[i] as i64 151 if c >= RM_ASCII_ZERO { if c <= RM_ASCII_NINE { v = v * 10 + (c - RM_ASCII_ZERO); seen = 1 } else { go = 0 } } else { 152 if c != RM_ASCII_DOT { go = 0 } 153 } 154 i = i + 1 155 } 156 } 157 sys_munmap(lb, RM_LOADAVG_CAP) 158 if seen == 0 { return 0 - 1 } 159 return v 160} 161 162// procs_seen/procs_vm are the CENSUS COVERAGE (2026-08-22) -- see the declaration comment in main(). 163func rm_log(swused_pm: i64, avail_pm: i64, leakers: i64, sustained: i64, leak_observed: i64, worst_kb: i64, worst_nm: *u8, sev: i64, procs_seen: i64, procs_vm: i64) -> i64 { 164 let b: *u8 = sys_mmap(RM_MAGIC_1024) 165 var o: i64 = 0 166 o = rm_lcat(b, o, "RESMON swap_used_permil=" as *u8) 167 o = rm_lnum(b, o, swused_pm) 168 o = rm_lcat(b, o, " mem_avail_permil=" as *u8) 169 o = rm_lnum(b, o, avail_pm) 170 o = rm_lcat(b, o, " load1_centi=" as *u8) 171 o = rm_lnum(b, o, rm_load1_centi()) 172 o = rm_lcat(b, o, " flat_committed_screen=" as *u8) 173 o = rm_lnum(b, o, leakers) 174 // THE TREND LIVES HERE. nx_memvel publishes only the CURRENT value (one overwritten line, so a 175 // per-minute reader stays O(1)); this appended row is what lets anyone answer "how long has this 176 // been true", which is the first question asked of any red monitor. 177 o = rm_lcat(b, o, " sustained_growers=" as *u8) 178 o = rm_lnum(b, o, sustained) 179 o = rm_lcat(b, o, " leak_axis=" as *u8) 180 if leak_observed == 1 { o = rm_lcat(b, o, "OBSERVED" as *u8) } else { o = rm_lcat(b, o, "UNOBSERVABLE" as *u8) } 181 o = rm_lcat(b, o, " worst_committed_kb=" as *u8) 182 o = rm_lnum(b, o, worst_kb) 183 o = rm_lcat(b, o, " worst=" as *u8) 184 o = rm_lcat(b, o, worst_nm) 185 o = rm_lcat(b, o, " procs_scanned=" as *u8) 186 o = rm_lnum(b, o, procs_seen) 187 o = rm_lcat(b, o, " procs_with_vm=" as *u8) 188 o = rm_lnum(b, o, procs_vm) 189 o = rm_lcat(b, o, " state=" as *u8) 190 if sev == 0 { o = rm_lcat(b, o, "GREEN" as *u8) } 191 if sev == 1 { o = rm_lcat(b, o, "AMBER" as *u8) } 192 if sev == 2 { o = rm_lcat(b, o, "RED" as *u8) } 193 o = rm_lcat(b, o, " sev=" as *u8) 194 o = rm_lnum(b, o, sev) 195 o = rm_lcat(b, o, " verdict=" as *u8) 196 if sev == 0 { o = rm_lcat(b, o, "GREEN" as *u8) } else { o = rm_lcat(b, o, "RED" as *u8) } 197 o = rm_lcat(b, o, "\n" as *u8) 198 // ★FIXED 2026-08-07: this was sys_openat_wr, which TRUNCATES. The durable log therefore held 199 // exactly ONE row -- the latest -- so the monitor could report that the host is RED but could 200 // never answer HOW LONG IT HAS BEEN RED, which is the first question anyone asks of a red 201 // monitor and the only one that separates a spike from a standing condition. 202 // A MONITOR THAT OVERWRITES ITS LOG CANNOT SHOW A TREND, AND A VERDICT WITHOUT A DURATION 203 // CANNOT BE TRIAGED. Every other status log in this estate appends; this one was the outlier. 204 // Measured when found: state=RED sev=2, swap_used_permil=817, leak_suspects=15 -- and no way to 205 // tell whether that was minutes or weeks old. 206 // ⚠0x1a4 was a raw file mode here, the same constant the estate consolidated into MODE_0644 on 207 // 2026-08-14 across 39 sites -- this one was missed because it is spelled in hex while the sweep 208 // found the decimal 420. ★THE SAME CONSTANT IN TWO BASES IS TWO CONSTANTS TO EVERY SCANNER. 209 let fd: i64 = sys_openat_append("knowledge/status/resmon.log" as *u8, MODE_0644) 210 if fd < 0 { sys_munmap(b, RM_MAGIC_1024); return 0 - 1 } 211 sys_write(fd, b, o) 212 sys_close(fd) 213 sys_munmap(b, RM_MAGIC_1024) 214 return o 215} 216 217// ---- SLOW-LEAK TREND (2026-08-14) --------------------------------------------------------------- 218// THE HORIZON THIS CLOSES: nx_memvel samples 5 windows of 1200 ms and requires growth in EVERY window, 219// so sustained_growers=0 proves NO FAST LEAK, NOT NO LEAK -- anything slower than that window is 220// invisible BY CONSTRUCTION. MEASURED the day this was written: worst_committed_kb crept 221// 2,529,624 -> 2,539,128 across the beat while sustained_growers read 0 every row, which is the CORRECT 222// answer to the question memvel asks and NOT the whole leak question. 223// ★★★★★★AN INSTRUMENT'S SAMPLING WINDOW IS ITS HORIZON, AND A HORIZON MUST BE PUBLISHED OR IT READS AS 224// COMPLETENESS. This is the same deciding-column law ONE TIMESCALE UP: grew in EVERY window becomes grew 225// in EVERY ROW of the durable log. 226// ★A SECOND TIMESCALE IS A NEW READER OF AN OLD LOG, NOT A NEW COLLECTOR: resmon.log is append-only and 227// already carries worst= and worst_committed_kb= on every beat, so this adds ZERO sampling and ZERO 228// /proc load -- which matters, because the condition it hunts appears exactly when the box is busy and 229// the admission gates are refusing new walkers. 230// ⚠SCOPE, STATED: it tracks only the WORST committed process. A slow leak in a smaller process is 231// invisible to it, exactly as a slow leak is invisible to memvel. Two published horizons, not one hidden. 232const RM_TREND_NAMEW: i64 = 64 233// Max log lines the trend scanner indexes in one pass. resmon.log appends one ~180 B row per beat; at 234// the 86400 s (daily) census cadence 8192 rows is >22 years of history, and rm_trend only analyses the 235// last `trend-rows` (default 12) anyway -- this is the index-array bound, not a policy number. Named so 236// the two sites (array alloc + the bounds guard) can never drift apart (rule 11: one constant, one source). 237const RM_TREND_MAXLINES: i64 = 8192 238const RM_LOGCAP: i64 = 1048576 239 240// The integer following `key` inside [lo,hi); -1 when the key is absent in that span. 241// Separate cursor + explicit flag: writing the exit into the search cursor destroys the position the 242// caller needs, a defect this estate has written four times in one day. 243func rm_line_num(buf: *u8, lo: i64, hi: i64, key: *u8) -> i64 { 244 let kl: i64 = rm_slen(key) 245 var i: i64 = lo 246 var found: i64 = 0 - 1 247 while i + kl <= hi { 248 if found < 0 { 249 var j: i64 = 0 250 var ok: i64 = 1 251 while j < kl { if buf[i+j] != key[j] { ok = 0; j = kl } else { j = j + 1 } } 252 if ok == 1 { found = i + kl } 253 } 254 i = i + 1 255 } 256 if found < 0 { return 0 - 1 } 257 var v: i64 = 0 258 var seen: i64 = 0 259 var p: i64 = found 260 var run: i64 = 1 261 while run == 1 { 262 if p >= hi { run = 0 } else { 263 let c: i64 = buf[p] as i64 264 if c < 48 { run = 0 } else { 265 if c > 57 { run = 0 } else { v = v * 10 + (c - 48); seen = 1; p = p + 1 } 266 } 267 } 268 } 269 if seen == 0 { return 0 - 1 } 270 return v 271} 272 273// The space-terminated word following `key` inside [lo,hi) -> out. 1 on success, 0 when absent. 274func rm_line_word(buf: *u8, lo: i64, hi: i64, key: *u8, out: *u8, cap: i64) -> i64 { 275 let kl: i64 = rm_slen(key) 276 var i: i64 = lo 277 var found: i64 = 0 - 1 278 while i + kl <= hi { 279 if found < 0 { 280 var j: i64 = 0 281 var ok: i64 = 1 282 while j < kl { if buf[i+j] != key[j] { ok = 0; j = kl } else { j = j + 1 } } 283 if ok == 1 { found = i + kl } 284 } 285 i = i + 1 286 } 287 if found < 0 { out[0] = 0 as u8; return 0 } 288 var o: i64 = 0 289 var p: i64 = found 290 var run: i64 = 1 291 while run == 1 { 292 if p >= hi { run = 0 } else { 293 if buf[p] == (32 as u8) { run = 0 } else { 294 if o < cap - 1 { out[o] = buf[p]; o = o + 1 } 295 p = p + 1 296 } 297 } 298 } 299 out[o] = 0 as u8 300 return 1 301} 302 303func rm_streq(a: *u8, b: *u8) -> i64 { 304 var i: i64 = 0 305 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 306 if b[i] != (0 as u8) { return 0 } 307 return 1 308} 309 310// rm_tg_find: literal token scan inside one line window [s,e) -- the row is machine-written 311// key=value, so a token match inside the POSITIONALLY-chosen last line is sound. 312func rm_tg_find(b: *u8, s: i64, e: i64, pat: *u8) -> i64 { 313 var pl: i64 = 0 314 while pat[pl] != (0 as u8) { pl = pl + 1 } 315 var i: i64 = s 316 while i + pl <= e { 317 var k: i64 = 0 318 var m: i64 = 1 319 while k < pl { if b[i+k] != pat[k] { m = 0; k = pl } else { k = k + 1 } } 320 if m == 1 { return i } 321 i = i + 1 322 } 323 return 0 - 1 324} 325// rm_trend_gatereg (loadgov LV1): COLLECT the slow-leak verdict from resmon_trend.log's LAST line. 326// Returns 2 fresh-RED / 0 fresh-GREEN / -1 UNOBSERVABLE (absent, empty, stale, future-stamped, or 327// unparseable -- every cannot-look is an ABSTENTION, never an acquittal). namebuf (>=64 B) receives 328// the row's worst= token on any parse so the raise can NAME its subject. Composes sys_read_file 329// (sized from the file -- the fixed-cap reader class is banned in this organ since 2026-08-29). 330func rm_trend_gatereg(maxage: i64, namebuf: *u8) -> i64 { 331 namebuf[0] = 0 as u8 332 let lp: *i64 = sys_mmap(16) as *i64 333 let b: *u8 = sys_read_file(RM_TREND_LOG, lp) 334 let n: i64 = lp[0] 335 if (b as i64) == 0 { return 0 - 1 } 336 if n <= 0 { return 0 - 1 } 337 var e: i64 = n 338 var t1: i64 = 1 339 while t1 == 1 { if e > 0 { if b[e-1] == (10 as u8) { e = e - 1 } else { t1 = 0 } } else { t1 = 0 } } 340 if e <= 0 { return 0 - 1 } 341 var s: i64 = e 342 var t2: i64 = 1 343 while t2 == 1 { if s > 0 { if b[s-1] != (10 as u8) { s = s - 1 } else { t2 = 0 } } else { t2 = 0 } } 344 var ts: i64 = 0 345 var i: i64 = s 346 var dig: i64 = 1 347 while dig == 1 { if i < e { let c: i64 = b[i] as i64; if c >= 48 { if c <= 57 { ts = ts * 10 + (c - 48); i = i + 1 } else { dig = 0 } } else { dig = 0 } } else { dig = 0 } } 348 if ts <= 0 { return 0 - 1 } 349 let age: i64 = sys_now_realtime_sec() - ts 350 if age < 0 { return 0 - 1 } 351 if age > maxage { return 0 - 1 } 352 let wv: i64 = rm_tg_find(b, s, e, "worst=" as *u8) 353 if wv >= 0 { 354 var wp: i64 = wv + 6 355 var wo: i64 = 0 356 var wg: i64 = 1 357 while wg == 1 { if wp < e { let wc: i64 = b[wp] as i64; if wc == 32 { wg = 0 } else { if wc == 9 { wg = 0 } else { if wo < 63 { namebuf[wo] = b[wp]; wo = wo + 1 } wp = wp + 1 } } } else { wg = 0 } } 358 namebuf[wo] = 0 as u8 359 } 360 if rm_tg_find(b, s, e, "verdict=RED" as *u8) >= 0 { return 2 } 361 if rm_tg_find(b, s, e, "verdict=GREEN" as *u8) >= 0 { return 0 } 362 return 0 - 1 363} 364// ⚠logpath IS A PARAMETER, NOT A CONSTANT, AND THAT IS A TESTABILITY REQUIREMENT NOT A CONVENIENCE. 365// The first cut hardcoded knowledge/status/resmon.log, which meant this detector COULD NOT BE POINTED AT 366// A FIXTURE -- and every gate in this estate works by planting a known-bad and demanding RED. ★★★★★★AN 367// ANALYSIS ORGAN THAT HARDCODES ITS INPUT PATH CANNOT BE GATED, SO ITS GREEN CAN NEVER BE EARNED: it can 368// only ever be run against whatever production happens to hold, which is exactly the state that cannot 369// contain a planted counter-example. Default preserved, so the beat and every existing caller are unchanged. 370func rm_trend(logpath: *u8) -> i64 { 371 // conf read composes sys_read_file -- the fixed RM_STATBUF cap silently dropped the conf tail 372 // once the file outgrew 4096 B (measured 2026-08-29 in main; same class here): remove, never raise. 373 let tclp: *i64 = sys_mmap(16) as *i64 374 var cbuf: *u8 = sys_read_file("knowledge/status/resmon.conf" as *u8, tclp) 375 var cn: i64 = tclp[0] 376 if (cbuf as i64) == 0 { cbuf = sys_mmap(16); cn = 0 } 377 // WINDOW IN ROWS, NOT MINUTES, because the beat interval is the clock's business and this organ must 378 // not assume it. 12 rows at the declared 300s resmonbeat is ~1 hour of history -- long enough that a 379 // creep invisible to a 1.2s window shows up, short enough to still be a LIVE signal and not a fossil. 380 let rows: i64 = rm_conf(cbuf, cn, "trend-rows" as *u8, 12) 381 let lbuf: *u8 = sys_mmap(RM_LOGCAP) 382 // TAIL, NOT HEAD (2026-09-02): rm_read kept the first RM_LOGCAP bytes, so once resmon.log outgrew 1 MiB the 383 // "newest" row was a torn partial line and this verb froze at UNOBSERVABLE for 65 beats. rm_trend_read_tail 384 // (nx_resmon_lib, composing lt_read_tail) keeps the NEWEST rows and declares the envelope in tenv -- 385 // printed on the report line below as log_bytes= tail_trunc=, because a partial read that is not declared 386 // is the defect this replaces. 387 let tenv: *i64 = sys_mmap(64) as *i64 388 let ln: i64 = rm_trend_read_tail(logpath, lbuf, RM_LOGCAP, tenv) 389 if ln <= 0 { rm_puts("RESMON-TREND verdict=UNOBSERVABLE why=log-unreadable path=" as *u8); rm_puts(logpath); rm_puts("\n" as *u8); return 3 } 390 // Collect the START offset of every line, then analyse only the last `rows`. 391 let starts: *i64 = sys_mmap(8 * RM_TREND_MAXLINES) as *i64 392 var nl: i64 = 0 393 starts[0] = 0 394 nl = 1 395 var i: i64 = 0 396 while i < ln { 397 if lbuf[i] == (10 as u8) { if nl < RM_TREND_MAXLINES { starts[nl] = i + 1; nl = nl + 1 } } 398 i = i + 1 399 } 400 // The last entry may be the empty tail after the final newline; drop it. 401 if nl > 0 { if starts[nl - 1] >= ln { nl = nl - 1 } } 402 if nl < rows { 403 rm_puts("RESMON-TREND verdict=UNOBSERVABLE why=not-enough-history rows_available=" as *u8); rm_num(nl) 404 rm_puts(" rows_needed=" as *u8); rm_num(rows); rm_puts("\n" as *u8) 405 return 3 406 } 407 let nmA: *u8 = sys_mmap(RM_TREND_NAMEW) 408 let nmB: *u8 = sys_mmap(RM_TREND_NAMEW) 409 // Newest row first: walk BACKWARDS and stop at the first row that breaks the run. 410 let newest: i64 = nl - 1 411 var lo: i64 = starts[newest] 412 var hi: i64 = ln 413 rm_line_word(lbuf, lo, hi, "worst=" as *u8, nmA, RM_TREND_NAMEW) 414 var newest_kb: i64 = rm_line_num(lbuf, lo, hi, "worst_committed_kb=" as *u8) 415 var prev_kb: i64 = newest_kb 416 var oldest_kb: i64 = newest_kb 417 var grew: i64 = 0 418 var shrank: i64 = 0 419 var flat: i64 = 0 420 var same_name: i64 = 1 421 var readable: i64 = 1 422 var r: i64 = 1 423 // ★WALK THE WHOLE WINDOW. The first cut BROKE OUT at the first non-increase, so grew/delta described 424 // a PREFIX while being printed as if they described the window -- a bounded loop reporting the bound. 425 var walking: i64 = 1 426 while walking == 1 { 427 if r >= rows { walking = 0 } else { 428 let idx: i64 = newest - r 429 lo = starts[idx] 430 hi = starts[idx + 1] 431 rm_line_word(lbuf, lo, hi, "worst=" as *u8, nmB, RM_TREND_NAMEW) 432 let kb: i64 = rm_line_num(lbuf, lo, hi, "worst_committed_kb=" as *u8) 433 if rm_streq(nmA, nmB) == 0 { same_name = 0; walking = 0 } else { 434 if kb < 0 { readable = 0; walking = 0 } else { 435 // walking BACKWARDS: prev_kb is the NEWER sample, kb the OLDER one. 436 if prev_kb > kb { grew = grew + 1 } else { 437 if prev_kb < kb { shrank = shrank + 1 } else { flat = flat + 1 } 438 } 439 prev_kb = kb 440 oldest_kb = kb 441 r = r + 1 442 } 443 } 444 } 445 } 446 let span: i64 = rows - 1 447 rm_puts("RESMON-TREND rows=" as *u8); rm_num(rows) 448 rm_puts(" log_bytes=" as *u8); rm_num(tenv[0]) 449 rm_puts(" tail_trunc=" as *u8); rm_num(tenv[3]) 450 rm_puts(" worst=" as *u8); rm_puts(nmA) 451 rm_puts(" newest_kb=" as *u8); rm_num(newest_kb) 452 rm_puts(" grew_in=" as *u8); rm_num(grew); rm_puts("/" as *u8); rm_num(span) 453 rm_puts(" shrank=" as *u8); rm_num(shrank) 454 rm_puts(" flat=" as *u8); rm_num(flat) 455 rm_puts(" delta_kb=" as *u8); rm_num(newest_kb - oldest_kb) 456 if same_name == 0 { rm_puts(" name_changed=1" as *u8) } 457 // A STRICT-MONOTONIC RUN TEST CANNOT SEE A LEAK THAT IS MONOTONIC IN AGGREGATE BUT FLAT IN 458 // INDIVIDUAL SAMPLES, AND THE SLOWER THE LEAK THE MORE FLAT SAMPLES IT HAS -- SO THE OLD PREDICATE 459 // WENT BLIND EXACTLY AS ITS TARGET GOT HARDER. MEASURED 2026-08-16: it printed 460 // `grew_in=7/11 delta_kb=4752 verdict=GREEN` -- REPORTED 4.75 MB OF GROWTH AND ACQUITTED -- while 461 // nx_wiki_gw.elf rose 2,774,600 -> 2,938,272 KB over 220 beats without ever once falling. The flat 462 // rows are QUANTIZATION: deltas land on multiples of 264 KB, so a ~633 KB/beat creep rounds to zero 463 // about every tenth beat. Strictness also did no work its stated reason claimed -- the SEPARATE 464 // same_name check already excludes "worst in some beats and not others". A LEAK NEVER RETURNS 465 // MEMORY: a SHRINK refutes; flat is what a leak looks like sampled below its own quantum. 466 if same_name == 0 { 467 rm_puts(" verdict=UNOBSERVABLE (the worst process changed inside the window; this ruler follows ONE name)\n" as *u8) 468 rm_trend_log(nmA, newest_kb, grew, span, shrank, flat, newest_kb - oldest_kb, 0, "GREEN" as *u8) 469 return 3 470 } 471 if readable == 0 { 472 rm_puts(" verdict=UNOBSERVABLE (a row lacks worst_committed_kb)\n" as *u8) 473 rm_trend_log(nmA, newest_kb, grew, span, shrank, flat, newest_kb - oldest_kb, 0, "GREEN" as *u8) 474 return 3 475 } 476 if shrank == 0 { if newest_kb > oldest_kb { 477 rm_puts(" verdict=RED (never fell and ended higher: a SLOW leak below memvel's 1200ms window)\n" as *u8) 478 rm_trend_log(nmA, newest_kb, grew, span, shrank, flat, newest_kb - oldest_kb, 1, "RED" as *u8) 479 return 1 480 } } 481 rm_puts(" verdict=GREEN (memory was returned inside the window, or the span did not grow)\n" as *u8) 482 rm_trend_log(nmA, newest_kb, grew, span, shrank, flat, newest_kb - oldest_kb, 1, "GREEN" as *u8) 483 return 0 484} 485 486func main(argc: i64, argv: *i64) -> i64 { 487 // TREND VERB: slow-timescale analysis over this organ's OWN durable log. An ARGLESS invocation keeps 488 // the beat path byte-for-byte identical, so adding a verb cannot change what the clock already does. 489 if argc >= 2 { if rm_streq(argv[1] as *u8, "trend" as *u8) == 1 { 490 // Optional 3rd arg = an ALTERNATE log to analyse, which is what makes this gateable against a 491 // planted fixture. Absent, it reads the production log exactly as before. 492 if argc >= 3 { return rm_trend(argv[2] as *u8) } 493 return rm_trend("knowledge/status/resmon.log" as *u8) 494 } } 495 // CONF READ COMPOSES sys_read_file (sized FROM the file; cannot short-read). The old fixed 496 // RM_STATBUF (4096) cap silently dropped the conf's TAIL the day the file grew to 4,604 B -- 497 // the tail held memvel-max-age-s, so the axis quietly fell back to its bottom-tier default (300) 498 // while the file on disk read correct (measured live 2026-08-29: file said 2400, report said 300). 499 // THE FIXED-CAP-READER CLASS (the nx_cron_reconcile precedent): REMOVE the cap, never raise it. 500 let clp: *i64 = sys_mmap(16) as *i64 501 var cbuf: *u8 = sys_read_file("knowledge/status/resmon.conf" as *u8, clp) 502 var cn: i64 = clp[0] 503 if (cbuf as i64) == 0 { cbuf = sys_mmap(16); cn = 0 } 504 let sw_amber: i64 = rm_conf(cbuf, cn, "swap-used-permil-amber" as *u8, 700) 505 let sw_red: i64 = rm_conf(cbuf, cn, "swap-used-permil-red" as *u8, 850) 506 let av_amber: i64 = rm_conf(cbuf, cn, "mem-avail-permil-amber" as *u8, 250) 507 let av_red: i64 = rm_conf(cbuf, cn, "mem-avail-permil-red" as *u8, 100) 508 let leak_min: i64 = rm_conf(cbuf, cn, "leak-min-kb" as *u8, RM_MAGIC_262144) 509 let leak_amber: i64 = rm_conf(cbuf, cn, "leak-count-amber" as *u8, 3) 510 let leak_red: i64 = rm_conf(cbuf, cn, "leak-count-red" as *u8, 6) 511 // FRESHNESS BOUND on the velocity measurement. Like every default above it this is the BOTTOM tier 512 // of the configuration hierarchy (rule 17), overridden by resmon.conf -- not a tuned constant. 513 // ★ITS FAILURE MODE IS SAFE IN BOTH DIRECTIONS: too tight or too loose, a stale reading never 514 // fabricates an answer, it degrades the axis to UNOBSERVABLE, which can neither raise nor lower 515 // the verdict. A threshold that gates a judgement must be wrong in the direction of saying nothing. 516 let mv_max_age: i64 = rm_conf(cbuf, cn, "memvel-max-age-s" as *u8, 300) 517 // ---- AXIS D thresholds: FREE BYTES, not a percentage. See rm_disk_sev for the derivation and the 518 // three measured volumes that force it. Bottom tier of the config hierarchy (rule 17): resmon.conf 519 // overrides. 1 GiB red / 5 GiB amber are floors a build's scratch and a store append must clear; 520 // they are a FIRST calibration to be ratcheted from observed near-misses, not tuned constants. 521 let disk_amber: i64 = rm_conf(cbuf, cn, "disk-avail-bytes-amber" as *u8, RM_DISK_AMBER_DEFAULT) 522 let disk_red: i64 = rm_conf(cbuf, cn, "disk-avail-bytes-red" as *u8, RM_DISK_RED_DEFAULT) 523 524 // ---- AXIS A: pressure ---- 525 let mbuf: *u8 = sys_mmap(RM_STATBUF) 526 let mn: i64 = rm_read("/proc/meminfo" as *u8, mbuf, RM_STATBUF) 527 let memtotal: i64 = rm_field(mbuf, mn, "MemTotal:" as *u8) 528 let memavail: i64 = rm_field(mbuf, mn, "MemAvailable:" as *u8) 529 let swtotal: i64 = rm_field(mbuf, mn, "SwapTotal:" as *u8) 530 let swfree: i64 = rm_field(mbuf, mn, "SwapFree:" as *u8) 531 var swused_pm: i64 = 0 532 if swtotal > 0 { swused_pm = ((swtotal - swfree) * 1000) / swtotal } 533 var avail_pm: i64 = 1000 534 if memtotal > 0 { avail_pm = (memavail * 1000) / memtotal } 535 536 // ---- AXIS B: leak census ---- 537 let dbuf: *u8 = sys_mmap(RM_DIRBUF) 538 let path: *u8 = sys_mmap(256) 539 let sbuf: *u8 = sys_mmap(RM_STATBUF) 540 var leakers: i64 = 0 541 // COVERAGE OF THIS CENSUS (2026-08-22). A worst= verdict WITHOUT ITS DENOMINATOR CANNOT BE AUDITED: 542 // when a reader asks "why did it not name process X", nothing in the output answers, and the only 543 // way to find out is to re-implement the walk by hand -- which is exactly what had to be done the 544 // day this was added. An independent /proc walk saw 749 numeric entries and 431 carrying VM fields 545 // while this organ published a worst= with no way to tell whether it had seen them all. It agreed, 546 // but AGREEING AND BEING AUDITABLE ARE DIFFERENT PROPERTIES. 547 // *EVERY CLAIM CARRIES ITS COVERAGE -- a census that prints a winner and hides its population is one 548 // silent skip away from naming an innocent, and no reader can tell the two apart. 549 // Purely additive: existing consumers parse by key (rm_line_num), so nothing that reads this line 550 // today changes behaviour (rule 19). 551 var procs_seen: i64 = 0 552 var procs_vm: i64 = 0 553 var worst_kb: i64 = 0 554 let worst_nm: *u8 = sys_mmap(64) 555 worst_nm[0] = 63 as u8 556 worst_nm[1] = 0 as u8 557 // WORST_PID + WORST_VMSIZE (2026-08-14). This organ named the worst holder but not its PID, so 558 // acting on its own RED required re-walking all ~1000 /proc entries by hand -- the scan it had 559 // JUST DONE. An alarm that identifies a culprit by name only is not actionable. 560 // VmSize is published beside RSS+Swap because it is what separates the two explanations for a 561 // standing leak_suspect: a large VmSize with modest RSS is FILE-BACKED mapping (evictable page 562 // cache, e.g. an mmap'd seg-store shard) which this detector's VmSize==VmPeak rule flags as a leak 563 // by construction, whereas VmSize tracking RSS is genuine anonymous growth. Without both numbers a 564 // reader cannot tell an leaking daemon from one doing its job, and this detector has been RED all 565 // day on nx_hub_gw, which a full static mmap/munmap census (nx_mmapbal file) shows is nearly the 566 // CLEANEST daemon in the fleet at 4 unbalanced funcs / 7 sites. 567 let worst_pid: *u8 = sys_mmap(32) 568 worst_pid[0] = 63 as u8 569 worst_pid[1] = 0 as u8 570 var worst_vsz: i64 = 0 571 let fd: i64 = sys_openat_rd("/proc" as *u8) 572 if fd >= 0 { 573 var run: i64 = 1 574 while run == 1 { 575 let n: i64 = sys_getdents64(fd, dbuf, RM_DIRBUF) 576 if n <= 0 { run = 0 } else { 577 var off: i64 = 0 578 while off < n { 579 let rec: *u8 = ((dbuf as i64) + off) as *u8 580 let reclen: i64 = dirent_reclen(rec) 581 if reclen <= 0 { off = n } else { 582 let nm: *u8 = dirent_name(rec) 583 if nm[0] >= (48 as u8) { if nm[0] <= (57 as u8) { 584 procs_seen = procs_seen + 1 585 var p: i64 = 0 586 let pre: *u8 = "/proc/" as *u8 587 var a: i64 = 0 588 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 } 589 a = 0 590 while nm[a] != (0 as u8) { path[p] = nm[a]; p = p + 1; a = a + 1 } 591 let suf: *u8 = "/status" as *u8 592 a = 0 593 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 } 594 path[p] = 0 as u8 595 let sn: i64 = rm_read(path, sbuf, RM_STATBUF) 596 if sn > 0 { 597 let vsz: i64 = rm_field(sbuf, sn, "VmSize:" as *u8) 598 let vpk: i64 = rm_field(sbuf, sn, "VmPeak:" as *u8) 599 let vrs: i64 = rm_field(sbuf, sn, "VmRSS:" as *u8) 600 let vsw: i64 = rm_field(sbuf, sn, "VmSwap:" as *u8) 601 if vsz > 0 { procs_vm = procs_vm + 1 } 602 if rm_is_leaker(vsz, vpk, vrs, vsw, leak_min) == 1 { 603 leakers = leakers + 1 604 var touched: i64 = 0 605 if vrs > 0 { touched = touched + vrs } 606 if vsw > 0 { touched = touched + vsw } 607 if touched > worst_kb { 608 worst_kb = touched 609 // /proc/<pid>/status opens with "Name:\t<comm>\n", so the comm 610 // starts at byte 6 and ends at the first newline. 611 var z: i64 = 6 612 var w: i64 = 0 613 while z < sn { if sbuf[z] == (10 as u8) { z = sn } else { if w < 62 { worst_nm[w] = sbuf[z]; w = w + 1 } z = z + 1 } } 614 worst_nm[w] = 0 as u8 615 var q: i64 = 0 616 while nm[q] != (0 as u8) { if q < 30 { worst_pid[q] = nm[q] } q = q + 1 } 617 if q > 30 { q = 30 } 618 worst_pid[q] = 0 as u8 619 worst_vsz = vsz 620 } 621 } 622 } 623 } } 624 off = off + reclen 625 } 626 } 627 } 628 } 629 sys_close(fd) 630 } 631 632 // ---- AXIS B2: the LEAK axis, fed by the ONLY instrument that can answer it ---- 633 // ★★★★★★A LEAK IS A DERIVATIVE AND THIS ORGAN IS A SNAPSHOT. The census above is a SCREEN 634 // (necessary-not-sufficient): a genuine leaker always shows VmSize==VmPeak, but so does every daemon 635 // that mmaps its arena once and never unmaps -- the leak-FREE design. Driving a verdict off the 636 // screen made this organ RED PERMANENTLY: 17 suspects against a red threshold of 6, while the true 637 // sustained-grower count measured 0. The sufficient half needs two samples in time, which only 638 // nx_memvel takes, so it is READ here rather than re-derived. ★COMPOSE THE RULER, NEVER RE-IMPLEMENT 639 // IT -- a second leak predicate in this file would be the duplicate-ruler defect, and it would be 640 // the WRONG one, because a per-minute beat cannot afford memvel's 5-window observation itself. 641 let vbuf: *u8 = sys_mmap(RM_STATBUF) 642 let vn: i64 = rm_read("knowledge/status/memvel.status" as *u8, vbuf, RM_STATBUF) 643 var sustained: i64 = 0 644 var leak_observed: i64 = 0 645 var mv_age: i64 = 0 - 1 646 if vn > 0 { 647 let stamp: i64 = rm_field(vbuf, vn, "epoch=" as *u8) 648 let scnt: i64 = rm_field(vbuf, vn, "sustained=" as *u8) 649 if stamp > 0 { if scnt >= 0 { 650 mv_age = sys_now_realtime_sec() - stamp 651 // A NEGATIVE AGE IS CLOCK SKEW, NOT FRESHNESS. Accepting it would let a future-stamped file 652 // pass forever, so it is refused exactly like a stale one. 653 if mv_age >= 0 { if mv_age <= mv_max_age { sustained = scnt; leak_observed = 1 } } 654 } } 655 } 656 657 // rm_verdict_blind COMPOSES rm_verdict and can only RAISE: the pressure axes keep their exact 658 // arithmetic, and a leading axis that has been dark for two full freshness windows now contributes an 659 // amber of its own instead of costing nothing. Abstention is still not a leak claim -- this alarms on 660 // the BLINDNESS, never on a leak nobody measured. 661 // ---- AXIS D: DISK. Measure BOTH volumes and let the WORST decide -- a healthy 21 TB data volume 662 // must not mask a root partition with half a gigabyte left. sys_fs_avail_bytes returns STATFS_ERR 663 // (negative) when it could not look, and rm_disk_sev treats that as ABSTAIN, never as roomy. 664 let d_estate: i64 = sys_fs_avail_bytes(RM_DISK_PATH_ESTATE) 665 let d_root: i64 = sys_fs_avail_bytes(RM_DISK_PATH_ROOT) 666 // PER-PATH BARS (2026-08-29). One bar pair judged BOTH volumes, so the 2.3 GiB DSM root 667 // partition -- whose baseline is 1.2 GiB of system packages, PROVEN by the privileged du census 668 // (knowledge/status/rootfs_du.txt) -- could never satisfy the 5 GiB estate amber bar and the 669 // axis was RED BY CONSTRUCTION: the permanently-red detector everyone learns to ignore. The 670 // worst PATH is now chosen by worst SEVERITY under its OWN bars, never by raw bytes under one 671 // bar. Root defaults are derived from the measured partition: red 256 MiB (~11 percent -- below 672 // this a DSM update or a log burst can wedge the system), amber 512 MiB (~22 percent). conf rows 673 // disk-avail-bytes-amber-root / disk-avail-bytes-red-root override (rule 17). Abstain semantics 674 // unchanged: a negative avail yields a negative sev, which can never win the worst-of comparison, 675 // and if BOTH abstain the estate's negative avail flows through to rm_verdict_disk which abstains. 676 let disk_amber_root: i64 = rm_conf(cbuf, cn, "disk-avail-bytes-amber-root" as *u8, RM_DISK_AMBER_ROOT_DEFAULT) 677 let disk_red_root: i64 = rm_conf(cbuf, cn, "disk-avail-bytes-red-root" as *u8, RM_DISK_RED_ROOT_DEFAULT) 678 let sev_de: i64 = rm_disk_sev(d_estate, disk_amber, disk_red) 679 let sev_dr: i64 = rm_disk_sev(d_root, disk_amber_root, disk_red_root) 680 var d_worst: i64 = d_estate 681 var d_worst_is: *u8 = RM_DISK_PATH_ESTATE 682 var d_bar_amber: i64 = disk_amber 683 var d_bar_red: i64 = disk_red 684 if sev_dr > sev_de { d_worst = d_root; d_worst_is = RM_DISK_PATH_ROOT; d_bar_amber = disk_amber_root; d_bar_red = disk_red_root } 685 let dp_estate: i64 = sys_fs_used_permil(RM_DISK_PATH_ESTATE) 686 let dp_root: i64 = sys_fs_used_permil(RM_DISK_PATH_ROOT) 687 688 let sev0: i64 = rm_verdict_disk(swused_pm, avail_pm, sustained, leak_observed, sw_amber, sw_red, av_amber, av_red, leak_amber, leak_red, mv_age, mv_max_age, d_worst, d_bar_amber, d_bar_red) 689 // TREND AXIS COLLECTION (loadgov LV1): the raise happens HERE so it is part of the verdict every 690 // consumer branches on, not a decoration beside it. Only-raises to AMBER: a slow-leak RED is a 691 // multi-window trend, real (this axis's motivating leak was 3.7 GB), but a single axis must not 692 // max the verdict without calibration -- the detector-with-false-positives law. 693 let tmax: i64 = rm_conf(cbuf, cn, "trend-max-age-s" as *u8, RM_TREND_MAX_AGE_DEFAULT) 694 let tnm: *u8 = sys_mmap(64) 695 let taxis: i64 = rm_trend_gatereg(tmax, tnm) 696 var sev: i64 = sev0 697 if taxis == 2 { if sev < 1 { sev = 1 } } 698 699 rm_puts("=== nx_resmon -- the resource axis nx_health lacks (seq897) ===\n" as *u8) 700 rm_puts("swap_used_permil=" as *u8); rm_num(swused_pm) 701 rm_puts(" (amber>=" as *u8); rm_num(sw_amber); rm_puts(" red>=" as *u8); rm_num(sw_red); rm_puts(")\n" as *u8) 702 rm_puts("mem_avail_permil=" as *u8); rm_num(avail_pm) 703 rm_puts(" (amber<=" as *u8); rm_num(av_amber); rm_puts(" red<=" as *u8); rm_num(av_red); rm_puts(")\n" as *u8) 704 rm_puts("swap_total_kb=" as *u8); rm_num(swtotal); rm_puts(" swap_free_kb=" as *u8); rm_num(swfree); rm_puts("\n" as *u8) 705 rm_puts("mem_total_kb=" as *u8); rm_num(memtotal); rm_puts(" mem_avail_kb=" as *u8); rm_num(memavail); rm_puts("\n" as *u8) 706 // ⚠RENAMED 2026-08-14 from leak_suspects=, which was a lie with an authoritative name: this is a 707 // SCREEN, it does not vote, and it never did measure a leak. No consumer outside this file read the 708 // old name (checked over the whole tree), so the rename breaks nothing. 709 rm_puts("flat_committed_screen=" as *u8); rm_num(leakers) 710 rm_puts(" (SCREEN ONLY, casts no vote: VmSize==VmPeak and RSS+Swap>=" as *u8); rm_num(leak_min) 711 rm_puts("kB -- also true of every arena-once daemon, which is the leak-FREE design)\n" as *u8) 712 rm_puts("sustained_growers=" as *u8); rm_num(sustained) 713 rm_puts(" leak_axis=" as *u8) 714 if leak_observed == 1 { rm_puts("OBSERVED" as *u8) } else { rm_puts("UNOBSERVABLE" as *u8) } 715 rm_puts(" memvel_age_s=" as *u8); rm_num(mv_age) 716 rm_puts(" max_age_s=" as *u8); rm_num(mv_max_age) 717 rm_puts(" (amber>=" as *u8); rm_num(leak_amber); rm_puts(" red>=" as *u8); rm_num(leak_red) 718 rm_puts("; UNOBSERVABLE contributes NOTHING to the verdict -- it is not a GREEN vote)\n" as *u8) 719 // ★★★★★★AN ABSTAINING AXIS THAT CANNOT SAY *WHY* IT IS BLIND RAISES NO ALARM ANYWHERE, BY DESIGN: 720 // abstention is safe for the verdict and invisible to the operator, so the safest possible verdict 721 // silently becomes the least informative one. MEASURED 2026-08-21: this axis read UNOBSERVABLE for 722 // 7.2 h (~43 missed 600 s beats) while the pressure axis went amber at swap 752 permil, and FOUR 723 // lanes reached FOUR different mechanisms for it -- because a beat that RAN AND WAS REFUSED and a 724 // beat that IS DEAD are indistinguishable from outside. nx_memvel now stamps memvel.refused on 725 // every admission refusal, so they are separable HERE for the first time, and their remedies are 726 // OPPOSITE: REFUSING means shed load, DEAD means fix the beat. 727 // ⚠THE VERDICT ARITHMETIC IS DELIBERATELY UNTOUCHED. The law asks for an alarm OF ITS OWN, not a 728 // vote: folding blindness into sev would hold this organ RED for as long as the box is busy -- the 729 // permanently-red detector everyone learns to ignore. STRICTLY ADDITIVE, so no run that was GREEN 730 // can now be anything else. 731 // The freshness bound is the SAME mv_max_age the measurement uses, read from conf -- ONE bound, so 732 // the two answers can never disagree about what "fresh" means. 733 let rjb: *u8 = sys_mmap(RM_STATBUF) 734 let rjn: i64 = rm_read("knowledge/status/memvel.refused" as *u8, rjb, RM_STATBUF) 735 var rj_age: i64 = 0 - 1 736 var rj_load: i64 = 0 - 1 737 if rjn > 0 { 738 let rstamp: i64 = rm_field(rjb, rjn, "epoch=" as *u8) 739 if rstamp > 0 { 740 rj_age = sys_now_realtime_sec() - rstamp 741 rj_load = rm_field(rjb, rjn, "load_centi=" as *u8) 742 } 743 } 744 // ★COMPOSE THE RULER, NEVER RE-IMPLEMENT IT: the classification lives in nx_resmon_lib beside the 745 // other two predicates (rm_verdict, rm_sustained_count), so its referee can reach it as a PURE 746 // function while this organ keeps only the printing. An inline copy here would be a second, 747 // UNGATED definition of "blind" -- the duplicate-ruler defect, and the untested copy is always the 748 // one that drifts. The clock-skew refusal and the freshness bound both live inside it now. 749 let blind: i64 = rm_blind_class(leak_observed, rj_age, mv_max_age) 750 rm_puts("leak_axis_blind=" as *u8) 751 if blind == 0 { rm_puts("NO" as *u8) } 752 if blind == 1 { rm_puts("REFUSING" as *u8) } 753 if blind == 2 { rm_puts("DEAD" as *u8) } 754 rm_puts(" refused_age_s=" as *u8); rm_num(rj_age) 755 rm_puts(" refused_load_centi=" as *u8); rm_num(rj_load) 756 rm_puts(" (REFUSING=the beat ran and admission declined it, shed load; DEAD=neither artifact fresh, fix the beat; -1=no refusal stamped yet)\n" as *u8) 757 // AXIS D printed with BOTH volumes and the bar in the SAME units it is judged in, so a reader never 758 // has to convert a permil into a decision. The permil rides as CONTEXT and is explicitly NOT the bar. 759 rm_puts("disk_avail_bytes_estate=" as *u8); rm_num(d_estate) 760 rm_puts(" disk_used_permil_estate=" as *u8); rm_num(dp_estate) 761 rm_puts(" disk_avail_bytes_root=" as *u8); rm_num(d_root) 762 rm_puts(" disk_used_permil_root=" as *u8); rm_num(dp_root) 763 rm_puts("\n" as *u8) 764 rm_puts("disk_axis worst_path=" as *u8); rm_puts(d_worst_is) 765 rm_puts(" worst_avail_bytes=" as *u8); rm_num(d_worst) 766 rm_puts(" (amber<=" as *u8); rm_num(d_bar_amber); rm_puts(" red<=" as *u8); rm_num(d_bar_red) 767 rm_puts("; THE BAR IS BYTES REMAINING, NOT A PERCENTAGE -- a permil bar is size-blind and would alarm" as *u8) 768 rm_puts(" on a 21TB volume at 879 permil while staying silent on a root partition with 525MiB left." as *u8) 769 rm_puts(" A NEGATIVE avail is STATFS_ERR = could-not-look, which ABSTAINS and never scores as roomy.)\n" as *u8) 770 rm_puts("worst_committed_kb=" as *u8); rm_num(worst_kb); rm_puts(" worst=" as *u8); rm_puts(worst_nm) 771 rm_puts(" worst_pid=" as *u8); rm_puts(worst_pid) 772 rm_puts(" worst_vmsize_kb=" as *u8); rm_num(worst_vsz) 773 rm_puts(" procs_scanned=" as *u8); rm_num(procs_seen) 774 rm_puts(" procs_with_vm=" as *u8); rm_num(procs_vm) 775 rm_puts(" (VmSize >> RSS+Swap => FILE-BACKED mapping, evictable, not an anonymous leak)\n" as *u8) 776 rm_puts("trend_axis=" as *u8) 777 if taxis == 2 { rm_puts("RED worst=" as *u8); rm_puts(tnm) } 778 if taxis == 0 { rm_puts("GREEN" as *u8) } 779 if taxis < 0 { rm_puts("UNOBSERVABLE" as *u8) } 780 rm_puts(" (LV1: the slow-leak verdict rm_trend writes is now COLLECTED -- last line of resmon_trend.log, positional; a fresh RED raises the verdict to at least AMBER and NAMES its subject, a stale or absent line ABSTAINS and never acquits)\n" as *u8) 781 rm_puts("verdict=" as *u8) 782 if sev == 0 { rm_puts("GREEN" as *u8) } 783 if sev == 1 { rm_puts("AMBER" as *u8) } 784 if sev == 2 { rm_puts("RED" as *u8) } 785 rm_puts(" sev=" as *u8); rm_num(sev); rm_puts("\n" as *u8) 786 // Durable + rosterable. Without this the whole organ is a private opinion. 787 rm_log(swused_pm, avail_pm, leakers, sustained, leak_observed, worst_kb, worst_nm, sev, procs_seen, procs_vm) 788 return sev 789}