code wiki / (root) / nx_loadceil_lib.nx

nx_loadceil_lib.nx source

↩ module page · 370 lines · 16511 B

1// nx_loadceil_lib.nx -- DERIVE THE BUILD-ADMISSION LOAD CEILING FROM THE BOX'S OWN HEALTHY-BEAT HISTORY, 2// function side (LV3, loadgov.plan, 2026-08-24). Split from the CLI so nx_loadceil_gate composes it in-process. 3// 4// Removes a real magic number: the ceiling sits DISABLED at an unreachable max_centiload (mgmt 100000, 5// runner 1000000) because, until nx_resmon began logging load1_centi on every beat, there was no history to 6// calibrate it from. That history is now live (resmon.log carries load1_centi= and state= per row), so the 7// ceiling can be a MEASURED quantile of the loads at which the box was actually GREEN. 8// 9// THE ARITHMETIC THIS COUPLES TO, READ FROM ba_verdict: a build QUEUEs when load1 > max_centiload * 10// BA_HARD_FACTOR (=2). So the STORM TRIGGER is 2*max_centiload; this derives the trigger from a high quantile 11// of GREEN load and reports max_centiload = trigger / BA_HARD_FACTOR. Every ba_verdict failure is a QUEUE 12// (defer), never a hard DENY -- so a mis-calibration defers builds, it cannot brick the lane; the safe 13// direction is UP and this derives a HIGH quantile with the whole distribution returned for audit. 14// 15// NO MAGIC: quantile level and minimum sample are conf rows; LC_HARD_FACTOR is mirrored from the coupled 16// organ and NAMED (the gate asserts recommend*factor == trigger). Below the minimum GREEN sample it REFUSES 17// rather than arm from too little history -- the permanently-red-detector failure the plan's risk row names. 18// 19// GENERALISED 2026-08-25 (lane E). The shape here -- parse a line-oriented log, take a per-line key= integer, 20// filter by a per-line marker, sort, nearest-rank quantile, abstain below a sample floor -- is not specific 21// to load. It is now parameterised (lc_collect_kv / lc_recommend_kv) so a SECOND magic number is killed by 22// the SAME calibrator instead of a second ruler: nx_replyreserve derives edge_window.conf reply_reserve_ms 23// from reply_ms= on lane=sync-promoted actlog rows. lc_collect / lc_recommend / lc_line_green are now thin 24// delegates passing the original literals, so the load-ceiling path is unchanged BY CONSTRUCTION rather than 25// by assertion. 26// Three additions the second caller forced, each a defect the first caller never hit: 27// lc_conf_line_num -- LINE-ANCHORED conf lookup mirroring nx_tool_exec_allow.tea_conf_int, the contract 28// that actually owns knowledge/edge_window.conf. The whole-buffer lc_conf_num is WRONG there: the keys 29// edge_window_ms and reply_reserve_ms both occur inside that file PROSE above their rows, so a 30// first-occurrence scan reads the comment, finds no digits, and silently returns the default. 31// lc_read_tail -- a journal is APPEND-ONLY, so a head read samples its PAST. Measured 2026-08-25: every 32// reply_ms= row sat in the last 25 KB of a 30 MB actlog, and a 4 MB read from offset 0 finds ZERO of 33// them -- an abstention for the wrong reason, from an organ that could then never arm itself. 34// lc_first_int -- first integer ANYWHERE after a key on its line, for space-separated conf rows. 35// license_tier: ORIGINAL 36 37import "nx_syscalls.nx" 38 39const LC_LOGCAP: i64 = 4194304 40const LC_MAXBEATS: i64 = 65536 41const LC_CONFCAP: i64 = 8192 42const LC_NL: i64 = 10 43const LC_ZERO: i64 = 48 44const LC_NINE: i64 = 57 45// mirrored from nx_build_admit.BA_HARD_FACTOR -- the storm multiplier the ceiling couples to. 46const LC_HARD_FACTOR: i64 = 2 47const LC_SEEK_SET: i64 = 0 48const LC_SEEK_END: i64 = 2 49 50// verdict codes 51const LC_RECOMMENDED: i64 = 0 52const LC_UNREADABLE: i64 = 2 53const LC_INSUFFICIENT: i64 = 3 54 55// out[] slots filled by lc_recommend 56const LC_O_VERDICT: i64 = 0 57const LC_O_GREEN: i64 = 1 58const LC_O_TOTAL: i64 = 2 59const LC_O_MISSING: i64 = 3 60const LC_O_CAPPED: i64 = 4 61const LC_O_MIN: i64 = 5 62const LC_O_P50: i64 = 6 63const LC_O_P90: i64 = 7 64const LC_O_P99: i64 = 8 65const LC_O_MAX: i64 = 9 66const LC_O_TRIGGER: i64 = 10 67const LC_O_REC: i64 = 11 68const LC_O_Q: i64 = 12 69const LC_O_MINN: i64 = 13 70const LC_O_N: i64 = 16 71 72// meta[] slots filled by lc_read_tail 73const LC_M_SIZE: i64 = 0 74const LC_M_WHOLE: i64 = 1 75const LC_M_BYTES: i64 = 2 76const LC_M_N: i64 = 4 77 78func lc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 79func lc_read(path: *u8, buf: *u8, cap: i64) -> i64 { 80 let fd: i64 = sys_openat_rd(path) 81 if fd < 0 { return 0 - 1 } 82 let n: i64 = sys_read(fd, buf, cap) 83 sys_close(fd) 84 return n 85} 86// Read the LAST cap-1 bytes of a file and drop the partial first line, so the caller parses whole rows from 87// buf[0]. A whole-file read is returned unchanged when the file fits. meta: size / whole(1=entire file) / 88// bytes delivered. Returns bytes usable from buf[0], or -1 when the file cannot be opened or seeked. 89// WHY: an append-only journal RECENT rows are the subject; a head read of a capped window samples the past. 90func lc_read_tail(path: *u8, buf: *u8, cap: i64, meta: *i64) -> i64 { 91 var z: i64 = 0 92 while z < LC_M_N { meta[z] = 0; z = z + 1 } 93 let fd: i64 = sys_openat_rd(path) 94 if fd < 0 { return 0 - 1 } 95 let sz: i64 = sys_lseek(fd, 0, LC_SEEK_END) 96 if sz < 0 { sys_close(fd); return 0 - 1 } 97 meta[LC_M_SIZE] = sz 98 var start: i64 = 0 99 var whole: i64 = 1 100 if sz > cap - 1 { start = sz - (cap - 1); whole = 0 } 101 sys_lseek(fd, start, LC_SEEK_SET) 102 var tot: i64 = 0 103 var go: i64 = 1 104 while go == 1 { 105 let r: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot - 1) 106 if r <= 0 { go = 0 } else { tot = tot + r; if tot >= cap - 1 { go = 0 } } 107 } 108 sys_close(fd) 109 buf[tot] = 0 as u8 110 meta[LC_M_WHOLE] = whole 111 var off: i64 = 0 112 if whole == 0 { 113 var p: i64 = 0 114 var seek: i64 = 1 115 while seek == 1 { 116 if p >= tot { seek = 0 } else { 117 if buf[p] == (LC_NL as u8) { off = p + 1; seek = 0 } else { p = p + 1 } 118 } 119 } 120 } 121 if off > 0 { 122 var k: i64 = 0 123 while k < tot - off { buf[k] = buf[off + k]; k = k + 1 } 124 buf[tot - off] = 0 as u8 125 } 126 meta[LC_M_BYTES] = tot - off 127 return tot - off 128} 129// integer value of `key=` on line [lo,hi); -1 when the key is absent or has no digits. 130func lc_line_num(buf: *u8, lo: i64, hi: i64, key: *u8) -> i64 { 131 let kl: i64 = lc_slen(key) 132 var i: i64 = lo 133 var at: i64 = 0 - 1 134 while i + kl <= hi { 135 if at < 0 { 136 var j: i64 = 0 137 var ok: i64 = 1 138 while j < kl { if buf[i+j] != key[j] { ok = 0; j = kl } else { j = j + 1 } } 139 if ok == 1 { at = i + kl } 140 } 141 i = i + 1 142 } 143 if at < 0 { return 0 - 1 } 144 var v: i64 = 0 145 var seen: i64 = 0 146 var p: i64 = at 147 var go: i64 = 1 148 while go == 1 { 149 if p >= hi { go = 0 } else { 150 let c: i64 = buf[p] as i64 151 if c >= LC_ZERO { if c <= LC_NINE { v = v * 10 + (c - LC_ZERO); seen = 1; p = p + 1 } else { go = 0 } } else { go = 0 } 152 } 153 } 154 if seen == 0 { return 0 - 1 } 155 return v 156} 157// first non-negative integer anywhere in [lo,hi); -1 when the range holds no digit. For space-separated 158// conf rows (reply_reserve_ms 1000), where the value does not abut its key. 159func lc_first_int(buf: *u8, lo: i64, hi: i64) -> i64 { 160 var p: i64 = lo 161 var v: i64 = 0 - 1 162 while p < hi { 163 if v < 0 { 164 let c: i64 = buf[p] as i64 165 if c >= LC_ZERO { if c <= LC_NINE { 166 var acc: i64 = 0 167 var q: i64 = p 168 var go: i64 = 1 169 while go == 1 { 170 if q >= hi { go = 0 } else { 171 let d: i64 = buf[q] as i64 172 if d >= LC_ZERO { if d <= LC_NINE { acc = acc * 10 + (d - LC_ZERO); q = q + 1 } else { go = 0 } } else { go = 0 } 173 } 174 } 175 v = acc 176 } } 177 } 178 p = p + 1 179 } 180 return v 181} 182// 1 when line [lo,hi) contains `key`; an EMPTY key means "no filter" and matches every line. 183func lc_line_has(buf: *u8, lo: i64, hi: i64, key: *u8) -> i64 { 184 let kl: i64 = lc_slen(key) 185 if kl == 0 { return 1 } 186 var i: i64 = lo 187 var found: i64 = 0 188 while i + kl <= hi { 189 if found == 0 { 190 var j: i64 = 0 191 var ok: i64 = 1 192 while j < kl { if buf[i+j] != key[j] { ok = 0; j = kl } else { j = j + 1 } } 193 if ok == 1 { found = 1 } 194 } 195 i = i + 1 196 } 197 return found 198} 199// 1 when line [lo,hi) contains "state=GREEN" 200func lc_line_green(buf: *u8, lo: i64, hi: i64) -> i64 { 201 return lc_line_has(buf, lo, hi, "state=GREEN" as *u8) 202} 203func lc_conf_num(cb: *u8, cn: i64, key: *u8, dflt: i64) -> i64 { 204 let v: i64 = lc_line_num(cb, 0, cn, key) 205 if v < 0 { return dflt } 206 return v 207} 208// first integer after `key` on the FIRST line that STARTS WITH key; dflt when no such line has an integer. 209// Mirrors nx_tool_exec_allow.tea_conf_int, the parser that owns knowledge/edge_window.conf. Line-anchored 210// because that file names its own keys in prose ABOVE their rows -- a whole-buffer scan reads the comment. 211func lc_conf_line_num(cb: *u8, cn: i64, key: *u8, dflt: i64) -> i64 { 212 let kl: i64 = lc_slen(key) 213 var ls: i64 = 0 214 var got: i64 = dflt 215 var done: i64 = 0 216 while ls < cn { 217 var le: i64 = ls 218 var scan: i64 = 1 219 while scan == 1 { 220 if le >= cn { scan = 0 } else { 221 if cb[le] == (LC_NL as u8) { scan = 0 } else { le = le + 1 } 222 } 223 } 224 if done == 0 { 225 if le - ls >= kl { 226 var m: i64 = 1 227 var i: i64 = 0 228 while i < kl { if cb[ls+i] != key[i] { m = 0; i = kl } else { i = i + 1 } } 229 if m == 1 { 230 let v: i64 = lc_first_int(cb, ls + kl, le) 231 if v >= 0 { got = v; done = 1 } 232 } 233 } 234 } 235 ls = le + 1 236 } 237 return got 238} 239// Insertion sort, ascending. 240// FIXED 2026-08-25 (lane E), found by nx_replyreserve_gate on its first run. The inner loop used to exit 241// by CLOBBERING ITS OWN CURSOR -- `else { j = 0 - 1 }` as a stand-in for break -- and the line after it is 242// `a[j+1] = key`. So every element that was ALREADY in order relative to its predecessor was written to 243// a[0] instead of to its own slot: the array kept its original contents everywhere except position 0, 244// which ended up holding the LAST key examined. 245// WHY NOTHING CAUGHT IT: on an already-ascending input every quantile at index >= 1 is still correct by 246// luck, and only a[0] is wrong -- so p50/p90/p99 all read right and ONLY the minimum lied. The incumbent 247// gate's fixture was ascending AND it asserted the max but never the min, which is precisely the blind 248// spot. Both fixtures are now shuffled and the min is asserted. 249// NOTE ON COST, because this fix is not free: the broken loop always exited on its first test, so it was 250// O(n) and never actually moved anything. A correct insertion sort is O(n^2). At the sample sizes both 251// callers see (hundreds to low thousands) that is under a millisecond, but LC_MAXBEATS is 65536 and a full 252// array there would be ~4e9 operations. Bounded and named rather than silently inherited. 253// COST RESOLVED 2026-08-25 (debt 1787668206): the corrected insertion sort above was O(n^2), which at 254// LC_MAXBEATS=65536 is ~4e9 operations -- a 35-day fuse, since resmon.log gains ~288 rows/day. Replaced 255// with an in-place iterative HEAPSORT: O(n log n) worst case, no recursion, no allocation, same ascending 256// order and the same (a, n) contract, so both callers are unchanged. At 65536 this is ~2e6 comparisons 257// instead of ~4e9. Budget the complexity before writing rather than inherit it. 258// NOTE THE EXIT DISCIPLINE: every loop here ends on an explicit FLAG, never by writing a sentinel into 259// its own cursor. That shortcut is precisely what corrupted this function -- `else { j = 0 - 1 }` left 260// a[j+1] writing to a[0] for every already-ordered element -- and it is the estate's recurring defect. 261func lc_sift(a: *i64, root: i64, n: i64) -> i64 { 262 var r: i64 = root 263 var go: i64 = 1 264 while go == 1 { 265 var big: i64 = r 266 let l: i64 = 2 * r + 1 267 let rt: i64 = 2 * r + 2 268 if l < n { if a[l] > a[big] { big = l } } 269 if rt < n { if a[rt] > a[big] { big = rt } } 270 if big == r { go = 0 } else { 271 let t: i64 = a[r]; a[r] = a[big]; a[big] = t 272 r = big 273 } 274 } 275 return 0 276} 277func lc_sort(a: *i64, n: i64) -> i64 { 278 if n < 2 { return 0 } 279 var s: i64 = n / 2 - 1 280 while s >= 0 { lc_sift(a, s, n); s = s - 1 } 281 var e: i64 = n - 1 282 while e > 0 { 283 let t: i64 = a[0]; a[0] = a[e]; a[e] = t 284 lc_sift(a, 0, e) 285 e = e - 1 286 } 287 return 0 288} 289// nearest-rank permil-quantile of a sorted-ascending array (q=990 -> p99); -1 on empty. 290func lc_quant(a: *i64, n: i64, q_permil: i64) -> i64 { 291 if n <= 0 { return 0 - 1 } 292 var idx: i64 = (q_permil * (n - 1)) / 1000 293 if idx < 0 { idx = 0 } 294 if idx >= n { idx = n - 1 } 295 return a[idx] 296} 297// GENERIC collector: fill vals[] with the value_key integer of every line that also carries filter_key 298// (empty filter_key = every line). counts[0]=lines counts[1]=lines-without-the-value counts[2]=capped. 299func lc_collect_kv(buf: *u8, n: i64, vals: *i64, cap: i64, counts: *i64, value_key: *u8, filter_key: *u8) -> i64 { 300 var g: i64 = 0 301 var total: i64 = 0 302 var missing: i64 = 0 303 var capped: i64 = 0 304 var lo: i64 = 0 305 var i: i64 = 0 306 while i <= n { 307 var eol: i64 = 0 308 if i == n { eol = 1 } 309 if i < n { if buf[i] == (LC_NL as u8) { eol = 1 } } 310 if eol == 1 { 311 if i > lo { 312 if total >= LC_MAXBEATS { capped = 1 } else { 313 total = total + 1 314 let val: i64 = lc_line_num(buf, lo, i, value_key) 315 if val < 0 { missing = missing + 1 } else { 316 if lc_line_has(buf, lo, i, filter_key) == 1 { if g < cap { vals[g] = val; g = g + 1 } else { capped = 1 } } 317 } 318 } 319 } 320 lo = i + 1 321 } 322 i = i + 1 323 } 324 counts[0] = total 325 counts[1] = missing 326 counts[2] = capped 327 return g 328} 329// parse a resmon-log buffer, fill greens[] with load1_centi of each GREEN beat; returns green count. 330// counts[0]=total counts[1]=missing-load counts[2]=capped(1 if a bound was hit). 331func lc_collect(buf: *u8, n: i64, greens: *i64, cap: i64, counts: *i64) -> i64 { 332 return lc_collect_kv(buf, n, greens, cap, counts, "load1_centi=" as *u8, "state=GREEN" as *u8) 333} 334// THE GENERIC RULER, pure over a buffer: fills out[LC_O_*] and returns a verdict code. No I/O, no stdout, so 335// a gate composes it on a planted buffer and asserts every value. LC_O_TRIGGER is the value at the requested 336// quantile; LC_O_REC is that divided by LC_HARD_FACTOR (meaningful to the load-ceiling caller -- a caller 337// with no such coupling reads LC_O_TRIGGER and applies its own derivation). 338func lc_recommend_kv(buf: *u8, n: i64, q: i64, minn: i64, value_key: *u8, filter_key: *u8, out: *i64) -> i64 { 339 var k: i64 = 0 340 while k < LC_O_N { out[k] = 0; k = k + 1 } 341 out[LC_O_Q] = q 342 out[LC_O_MINN] = minn 343 if n <= 0 { out[LC_O_VERDICT] = LC_UNREADABLE; return LC_UNREADABLE } 344 let greens: *i64 = sys_mmap(LC_MAXBEATS * 8) as *i64 345 let counts: *i64 = sys_mmap(8 * 4) as *i64 346 let g: i64 = lc_collect_kv(buf, n, greens, LC_MAXBEATS, counts, value_key, filter_key) 347 lc_sort(greens, g) 348 out[LC_O_GREEN] = g 349 out[LC_O_TOTAL] = counts[0] 350 out[LC_O_MISSING] = counts[1] 351 out[LC_O_CAPPED] = counts[2] 352 if g > 0 { 353 out[LC_O_MIN] = greens[0] 354 out[LC_O_P50] = lc_quant(greens, g, 500) 355 out[LC_O_P90] = lc_quant(greens, g, 900) 356 out[LC_O_P99] = lc_quant(greens, g, 990) 357 out[LC_O_MAX] = greens[g-1] 358 } 359 if g < minn { out[LC_O_VERDICT] = LC_INSUFFICIENT; return LC_INSUFFICIENT } 360 let trigger: i64 = lc_quant(greens, g, q) 361 out[LC_O_TRIGGER] = trigger 362 out[LC_O_REC] = trigger / LC_HARD_FACTOR 363 out[LC_O_VERDICT] = LC_RECOMMENDED 364 return LC_RECOMMENDED 365} 366// THE RULER, pure over a buffer: fills out[LC_O_*] and returns a verdict code. No I/O, no stdout, so the 367// gate composes it on a planted buffer and asserts every value. 368func lc_recommend(buf: *u8, n: i64, q: i64, minn: i64, out: *i64) -> i64 { 369 return lc_recommend_kv(buf, n, q, minn, "load1_centi=" as *u8, "state=GREEN" as *u8, out) 370}