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}