code wiki / _hdl_build / nx_build_admit.nx

nx_build_admit.nx source

↩ module page · 582 lines · 36420 B

1// nx_build_admit.nx -- BUILD ADMISSION CONTROL (the missing gate that let the fleet wedge the host). 2// INCIDENT 2026-07-20: ~10 concurrent sessions were compiling at once (registry grew 198->236 GREEN tools in 3// ONE session). Every /api/build forks nx_cc_sovereign (549KB) on a memory-limited Synology and NOTHING 4// serialized or admitted them -> the NAS went userspace-wedged (sshd could not complete a banner, DSM could 5// not serve a page, every sovereign daemon refused TCP) and then off the network entirely. Measured, not 6// guessed. nx_swarm_admit ALREADY documents itself as the gate "BEFORE any heavy launch (model decode, codec, 7// image-gen, BIG BUILD)" -- but /api/build never called it. A compile IS a heavy launch. This organ is the 8// build-shaped admission verdict, with ZERO deps beyond nx_syscalls so it can never itself be the thing that 9// fails under load. 10// 11// LIAR-KILLED: every number is read live from /proc on the host being protected -- no estimate, no cache. 12// MemAvailable from /proc/meminfo (the honest figure: reclaimable included, unlike MemFree) 13// 1-minute load from /proc/loadavg, carried as CENTI-load (integer; no floats, sovereign law) 14// 15// nx_build_admit check [floor_mb] [max_centiload] 16// exit 0 GRANT -- headroom exists, compile now 17// exit 3 DENY-MEM -- below the memory floor; compiling now risks wedging the HOST, not just the build 18// exit 4 QUEUE -- a pileup a compiler fork would deepen. THIS ORGAN QUEUES NOTHING ITSELF; whether 19// the build is absorbed is the CALLER's answer. /api/build absorbs it (nx_buildq -> 20// plan-build-<t>- + deployq BQ-<t>, fired by nx_orchestrate on headroom) and reports 21// queue_launch_rc; a DIRECT caller of this detector must re-issue itself, paced. 22// exit 2 usage | 5 unreadable /proc (fail-CLOSED: cannot measure => cannot admit) 23// 24// ✅THE ABSORBER SHIPPED, AND SO DID THE STRUCTURAL HALF -- BOTH CLAIMS THIS PARAGRAPH USED TO MAKE WERE 25// STALE, and they are corrected here from the CODE rather than from belief (re-measured 2026-08-20). 26// The sentence they justified was still telling every reader NOTHING IS QUEUED long after that stopped 27// being true: this organ was committing the exact defect it exists to prevent -- A GOVERNOR EXPLAINING 28// ITSELF WRONGLY IS WORSE THAN A GOVERNOR WITH NO EXPLANATION, because the reader acts on the sentence. 29// (1) ma_do_build DOES consult this BEFORE forking the compiler: it calls md_exec_build_admit() and 30// branches on exit 3 (DENY-MEM -> 503) and exit 4 (QUEUE). The "filed rung" is CLOSED. 31// (2) The wait-for-opening absorber EXISTS: nx_buildq seeds plan-build-<target>- plus a deployq 32// BQ-<target> row whose `headroom:` precondition nx_orchestrate evaluates by forking THIS organ, 33// then fires the build on its first pass with headroom. Proven end to end 2026-08-20: one call 34// returned PUT 1 rows=1 / PUT BQ-<t> rows=18 / BUILDQ QUEUED. 35// (3) THE ONE THING THAT REALLY WAS BROKEN WAS AN ADOPTION GAP, NOT A MECHANISM. /api/build launches 36// the enqueue through nx_job_run, which refuses any name that is not an unpinned GREEN row in 37// tool_allowlist.conf -- and nx_buildq was PROMOTED-UNREGISTERED. That refusal IS nx_job_run's 38// exit 4, and it is the queue_launch_rc=4 recorded in debt 1787178882 alongside a MISSING deployq 39// row and an ABSENT capture file. The launcher was correct; the absorber was uncallable. One 40// /api/tools/register closed it. ★A LAUNCHER'S REFUSAL OF AN UNREGISTERED ORGAN IS 41// INDISTINGUISHABLE FROM A BROKEN MECHANISM UNTIL SOMEONE READS WHAT THAT EXIT CODE MEANS. 42// A DIRECT caller of this organ still queues nothing -- that is a property of the CALLER, and the 43// verdict text now says exactly that instead of asserting a fleet-wide "nothing is queued anywhere". 44// FAIL-CLOSED BY CONSTRUCTION: an unreadable /proc returns exit 5, never GRANT. A guard that cannot measure 45// must refuse, never wave through -- the same law as an instrument that must not score bytes it did not fetch. 46// ENVELOPE (declared in output): 64KB per /proc read; thresholds are NAMED CONSTS (rule-11) and argv-overridable. 47// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 48import "nx_syscalls.nx" 49import "nx_ioadmit_lib.nx" // THE I/O-storm ruler (IOA_BLOCKED_PER_CPU, ioa_ncpu): one calibration, shared with every I/O spawner 50 51const BA_MEMINFO: *u8 = "/proc/meminfo" as *u8 52const BA_LOADAVG: *u8 = "/proc/loadavg" as *u8 53const BA_READCAP: i64 = 65536 54const BA_OUTCAP: i64 = 4096 55// DEFAULTS (rule-11: named, argv-overridable, never buried). Floor chosen from the incident: the host wedged 56// while userspace could not fork -- 512MB available is the point below which a 549KB compiler plus its 57// transient .s output stops being safe on this box. 58const BA_FLOOR_MB: i64 = 512 59const BA_MAX_CENTILOAD: i64 = 400 60// ---- HOST-DERIVED LOAD CEILING (seq1475, 2026-07-30) ------------------------------------------ 61// WHY THIS EXISTS: the memory floor above is NECESSARY BUT NOT SUFFICIENT, measured. On 2026-07-30 the 62// hub flapped -- nx_tools_api_serve down, then nx_mgmt_api down, both self-recovering -- while this 63// detector was GRANTing at mem_available_mb=28437 against a 1024 floor. MEMORY WAS NEVER SCARCE, so 64// the seq708 "OOM" attribution is at best incomplete and a memory-only gate cannot see this failure. 65// The QUEUE verdict for load already existed and was DISABLED IN PRACTICE (callers passed a ceiling of 66// 100000 so memory was the sole gate) -- so the instrument could already measure the thing it was not 67// allowed to act on. 68// 69// ★A LOAD CEILING MUST BE HOST-DERIVED, NOT A CONSTANT (bounds-derived-adaptive-never-taste). "load 4" 70// means saturated on a 4-core box and idle on a 32-core one, so a baked number is wrong everywhere 71// except where it was written. Derive it from the CPU COUNT OF THE RUNNING HOST -- the same law as 72// deriving a supervisor's port table from the running system rather than from a comment. 73// 74// THE MULTIPLIER IS EVIDENCE-BRACKETED, NOT CHOSEN: this host has 8 CPUs (counted from /proc/stat). 75// FLAPPING was observed at load1 = 11.32 => 1.42 x ncpu 76// HEALTHY was observed at load1 = 3.88 => 0.49 x ncpu 77// 1.00 x ncpu sits between a measured-healthy point and a measured-flapping point, and is also the 78// classic saturation definition (runnable threads == cores). It is a BRACKET, not a guess; when more 79// flap/health samples land, tighten it with them and say so. 80const BA_STAT: *u8 = "/proc/stat" as *u8 81// Byte length of the needle "procs_running" -- the offset from the needle start to its integer field in 82// /proc/stat, same convention as the "MemAvailable:"+13 read above. Named, never buried (rule 11). 83const BA_PROCSRUN_OFF: i64 = 13 84const BA_CENTI_PER_CPU: i64 = 100 // 1.00 x ncpu, in centi-load 85const BA_HARD_FACTOR: i64 = 2 // STORM CEILING multiplier: the run-queue excuse in ba_verdict ends 86 // at BA_HARD_FACTOR x max_load. Calibration REUSED from the incumbent 87 // (nx_dstate ships admit_ceiling = 2.00 x ncpu, the same 2x bar); 88 // measured incidents (2026-08-16: load 4.4 x ncpu, 2 runnable, the 89 // fleet in D-state on a swapping RAID) sit far above it, while the 90 // 2026-07-30 motivating case (1.04 x ncpu, 43% CPU idle) sits below. 91// I/O-STORM WITNESS: procs_blocked >= IOA_BLOCKED_PER_CPU x ncpu QUEUEs, INDEPENDENT of max_load -- both 92// wired lanes disable the load ceiling (nx_sov_build_run passes max_centiload=1000000, mgmt 100000), so this 93// is the only axis that can refuse a storm on the lanes that actually run. THE CALIBRATION LIVES IN 94// nx_ioadmit_lib.nx (IOA_BLOCKED_PER_CPU, with its measured bounds: 2026-07-30 healthy disk-wait blocked 1-6 95// on 8 CPUs below; 2026-08-16 outage ~9 above; 2026-08-19 torrent storm exactly 8 at) so that THIS gate and 96// every I/O spawner that asks before forking (nx_torrent_daemon first) share ONE ruler and cannot drift -- 97// two constants with the same value in two files were the duplicate-ruler defect wearing a comment. 98// argv[4] overrides with an absolute count -- an explicit caller decision wins, as above. 99const BA_PROCSBLK_OFF: i64 = 13 // strlen("procs_blocked"), as BA_PROCSRUN_OFF is strlen("procs_running") 100 // -- same value, but each names its own /proc/stat key. 101const BA_NCPU_FALLBACK: i64 = 4 // only if /proc/stat is unreadable; conservative, never unbounded 102 103// ncpu parsing ("cpuN " lines of /proc/stat) is ioa_ncpu in nx_ioadmit_lib.nx -- ONE parser, shared. 104const BA_KB_PER_MB: i64 = 1024 105const BA_CENTI: i64 = 100 106const BA_STDOUT: i64 = 1 107const BA_STDERR: i64 = 2 108const BA_ZERO: i64 = 48 109const BA_NINE: i64 = 57 110const BA_DOT: i64 = 46 111const BA_NL: i64 = 10 112const BA_SP: i64 = 32 113const BA_EXIT_USAGE: i64 = 2 114const BA_EXIT_DENY: i64 = 3 115const BA_EXIT_QUEUE: i64 = 4 116const BA_EXIT_UNREADABLE: i64 = 5 117 118// ---- PURE VERDICT (extracted 2026-07-30) ------------------------------------------------------ 119// The whole admission DECISION as a pure function of already-measured numbers, extracted for exactly the 120// reason hc_keep_delay was: a policy welded into main() beside its own /proc reads CANNOT BE TESTED without 121// a host in the required state, and the two states that matter here -- CPU-saturated vs I/O-saturated -- 122// cannot be summoned on demand on a shared box. As a pure function every quadrant is gateable with no /proc. 123// Returns the process exit code: 0 GRANT / BA_EXIT_DENY memory floor / BA_EXIT_QUEUE load ceiling. 124// ORDER IS DELIBERATE: the memory floor is the ONLY unconditional block (it is the measured 2026-07-20 wedge 125// cause, where userspace could not fork at all) so it is checked FIRST -- no run-queue reading may ever admit 126// a build below it. procs_run < 0 means the run-queue sensor was UNREADABLE, and that must NOT unlock the 127// gate: fail-closed, matching how the loadavg and meminfo reads above refuse rather than assume. 128// Stable diagnostic categories; these are protocol identifiers, not tuning thresholds. 129const BA_REASON_GRANT: i64 = 0 130const BA_REASON_MEMORY: i64 = 1 131const BA_REASON_BLOCKED: i64 = 2 132const BA_REASON_LOAD: i64 = 3 133const BA_REASON_RUN_UNREADABLE: i64 = 4 134const BA_REASON_RUNQUEUE: i64 = 5 135 136// One decision owner for both exit status and explanation. Preserve the incumbent 137// policy while exposing which proxy actually decided; these proxies do not prove saturation. 138func ba_reason(avail_mb: i64, floor_mb: i64, load1: i64, max_load: i64, procs_run: i64, ncpu: i64, procs_blk: i64, blocked_max: i64) -> i64 { 139 if avail_mb < floor_mb { return BA_REASON_MEMORY } 140 if procs_blk >= 0 { if blocked_max > 0 { if procs_blk >= blocked_max { return BA_REASON_BLOCKED } } } 141 if load1 <= max_load { return BA_REASON_GRANT } 142 if load1 > max_load * BA_HARD_FACTOR { return BA_REASON_LOAD } 143 if procs_run < 0 { return BA_REASON_RUN_UNREADABLE } 144 if procs_run < ncpu { return BA_REASON_GRANT } 145 return BA_REASON_RUNQUEUE 146} 147func ba_reason_exit(reason: i64) -> i64 { 148 if reason == BA_REASON_GRANT { return 0 } 149 if reason == BA_REASON_MEMORY { return BA_EXIT_DENY } 150 return BA_EXIT_QUEUE 151} 152func ba_reason_label(reason: i64) -> *u8 { 153 if reason == BA_REASON_GRANT { return "POLICY_CHECKS_PASSED" as *u8 } 154 if reason == BA_REASON_MEMORY { return "MEMORY_FLOOR" as *u8 } 155 if reason == BA_REASON_BLOCKED { return "BLOCKED_COUNT_POLICY" as *u8 } 156 if reason == BA_REASON_LOAD { return "LOAD_HARD_CEILING_POLICY" as *u8 } 157 if reason == BA_REASON_RUN_UNREADABLE { return "RUNQUEUE_UNREADABLE" as *u8 } 158 if reason == BA_REASON_RUNQUEUE { return "LOAD_AND_RUNQUEUE_POLICY" as *u8 } 159 return "UNKNOWN_REASON" as *u8 160} 161func ba_verdict(avail_mb: i64, floor_mb: i64, load1: i64, max_load: i64, procs_run: i64, ncpu: i64, procs_blk: i64, blocked_max: i64) -> i64 { 162 return ba_reason_exit(ba_reason(avail_mb, floor_mb, load1, max_load, procs_run, ncpu, procs_blk, blocked_max)) 163} 164 165func ba_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 166func ba_werr(s: *u8) -> i64 { sys_write(BA_STDERR, s, ba_slen(s)); return 0 } 167func ba_puts(b: *u8, off: i64, s: *u8) -> i64 { var o: i64 = off; var j: i64 = 0; while s[j] != (0 as u8) { b[o] = s[j]; o = o + 1; j = j + 1 } return o } 168func ba_puti(b: *u8, off: i64, v: i64) -> i64 { 169 var o: i64 = off 170 var m: i64 = v 171 if m < 0 { b[o] = 45 as u8; o = o + 1; m = 0 - m } 172 let t: *u8 = sys_mmap(28) 173 var k: i64 = 0 174 if m == 0 { t[0] = BA_ZERO as u8; k = 1 } 175 while m > 0 { t[k] = (BA_ZERO + (m % 10)) as u8; m = m / 10; k = k + 1 } 176 var i: i64 = 0 177 while i < k { b[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } 178 return o 179} 180func ba_read(path: *u8, buf: *u8, cap: i64) -> i64 { 181 let fd: i64 = sys_openat_rd(path) 182 if fd < 0 { return 0 - 1 } 183 var n: i64 = 0 184 var go: i64 = 1 185 while go == 1 { let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } } 186 sys_close(fd) 187 return n 188} 189// index of NUL-term needle in buf[0,n), or -1 190func ba_find(buf: *u8, n: i64, needle: *u8) -> i64 { 191 let m: i64 = ba_slen(needle) 192 if m == 0 { return 0 - 1 } 193 var i: i64 = 0 194 while i + m <= n { 195 var j: i64 = 0 196 var ok: i64 = 1 197 while j < m { if buf[i+j] != needle[j] { ok = 0; j = m } else { j = j + 1 } } 198 if ok == 1 { return i } 199 i = i + 1 200 } 201 return 0 - 1 202} 203// parse the first unsigned integer at/after p; -1 if none before end-of-line 204func ba_uint_at(buf: *u8, p: i64, n: i64) -> i64 { 205 var i: i64 = p 206 while i < n { 207 let c: i64 = buf[i] as i64 208 if c == BA_NL { return 0 - 1 } 209 if c >= BA_ZERO { if c <= BA_NINE { 210 var v: i64 = 0 211 while i < n { 212 let d: i64 = buf[i] as i64 213 if d < BA_ZERO { i = n } else { if d > BA_NINE { i = n } else { v = v * 10 + (d - BA_ZERO); i = i + 1 } } 214 } 215 return v 216 } } 217 i = i + 1 218 } 219 return 0 - 1 220} 221// parse "N.MM" at the head of buf as centi-units (0.52 -> 52); -1 if unparseable 222func ba_centi_head(buf: *u8, n: i64) -> i64 { 223 if n <= 0 { return 0 - 1 } 224 var i: i64 = 0 225 var whole: i64 = 0 226 var got: i64 = 0 227 while i < n { 228 let c: i64 = buf[i] as i64 229 if c >= BA_ZERO { if c <= BA_NINE { whole = whole * 10 + (c - BA_ZERO); got = 1; i = i + 1 } else { i = n } } else { i = n } 230 } 231 if got == 0 { return 0 - 1 } 232 // re-scan for the fractional part (the loop above stopped at the dot or a space) 233 var frac: i64 = 0 234 var fi: i64 = 0 235 var k: i64 = 0 236 while k < n { if buf[k] == (BA_DOT as u8) { fi = k + 1; k = n } else { k = k + 1 } } 237 if fi > 0 { 238 var d1: i64 = 0 239 var d2: i64 = 0 240 if fi < n { let a: i64 = buf[fi] as i64; if a >= BA_ZERO { if a <= BA_NINE { d1 = a - BA_ZERO } } } 241 if fi + 1 < n { let b: i64 = buf[fi+1] as i64; if b >= BA_ZERO { if b <= BA_NINE { d2 = b - BA_ZERO } } } 242 frac = d1 * 10 + d2 243 } 244 return whole * BA_CENTI + frac 245} 246// THE ONE ENVELOPE (2026-08-18). Before this, every caller carried its own copy of the thresholds as argv 247// literals -- /api/build "1024"/"100000", nx_sov_build_run "1024"/"1000000" -- and a caller that passed 248// none got the host-derived ceiling, so the SAME box refused a build under one envelope and a queued retry 249// of it waited on another (measured: a bare `headroom:` orchestrate row waited on ncpu x 100 after 250// /api/build had refused under 100000). Rule 17, made real: argv (explicit caller intent) > this conf > 251// the host-derived / bootstrap defaults. Looked up at knowledge/build_admit.conf, then ../knowledge/ (a 252// caller anchored to buildroot forks this organ with the buildroot as CWD). The report line names which 253// source decided each run, so a threshold's provenance is never a guess. Keys: floor_mb, max_centiload, 254// blocked_max -- any subset; an absent key keeps the existing rule for that knob. 255const BA_CONF: *u8 = "knowledge/build_admit.conf" as *u8 256const BA_CONF_UP: *u8 = "../knowledge/build_admit.conf" as *u8 257const BA_CONF_CAP: i64 = 8192 258const BA_CONF_UNSET: i64 = 0 - 1 259// ---- THE SMOOTHED WITNESS, ARMED BY DATA AND DEFAULTED OFF (2026-09-03) ---- 260// WHY: measured on this host, procs_blocked swung 2..14 -- SEVEN-FOLD -- inside 24 seconds, with FIVE of 261// twelve samples at or above blocked_max while the median sat at 5-6, comfortably under it. A single 262// reading cannot separate a two-second burst from a sustained storm, so admission on one sample refuses at 263// roughly 42 percent on a box that is not actually storming. Eight consecutive builds were refused this way 264// while a sample-gated caller was admitted on its first try, three times running. 265// DEFAULT 1 IS EXACTLY TODAY'S BEHAVIOUR: with one sample the smoothing branch is never taken and procs_blk 266// keeps the identical value it has now, so LANDING THIS CHANGES NOTHING. Arming is a conf edit, re-armable 267// without a rebuild, exactly as max_centiload is armed -- and ba_verdict itself is UNTOUCHED, so only the 268// input it is handed becomes a median, never the decision it makes about that input. 269const BA_BLK_SAMPLES_DEFAULT: i64 = 1 270const BA_BLK_GAP_MS_DEFAULT: i64 = 60 271const BA_MEDIAN_SLOTS: i64 = 64 272// cfv now carries FIVE values, so it must be sized for five. A 32-byte mapping holds four i64 and writing 273// out[4] into it would run off the end -- the kind of silent overrun that reads as a mysterious conf value. 274const BA_CFV_SLOTS: i64 = 64 275const BA_HASH: i64 = 35 // the comment marker ba_line_key must refuse to read as a setting 276// Find a conf key at the START of a line, skipping COMMENT lines and leading spaces. Returns the offset of 277// the needle exactly as ba_find does, so every caller's own key-length arithmetic is unchanged. 278// WHY THIS EXISTS -- measured 2026-09-03 by shipping the bug and being bitten TWICE inside ten minutes: 279// ba_conf_load used a PLAIN SUBSTRING search, so a commented EXAMPLE of a key (the ordinary way anyone 280// documents a conf) was found and ARMED it. One commented example took every admission check on this host 281// from 2 ms to 2005 ms, on the path the clock dispatcher calls before each heavy dispatch; and the comment 282// written to WARN about it spelled the key out again and re-armed it at 243 ms. 283// A CONF READER THAT CANNOT TELL A SETTING FROM A COMMENT MAKES ITS OWN DOCUMENTATION EXECUTABLE. 284// This protects EVERY key, not just the two that exposed it: floor_mb, max_centiload and blocked_max were 285// never safe either, they escaped by luck because the prose near them writes an equals sign with spaces. 286// FIRST MATCH WINS, matching ba_find's contract, so a real setting above commented examples is unaffected. 287func ba_line_key(buf: *u8, n: i64, needle: *u8) -> i64 { 288 let nl: i64 = ba_slen(needle) 289 var i: i64 = 0 290 var found: i64 = 0 - 1 291 while i < n { 292 var p: i64 = i 293 var sp: i64 = 1 294 while sp == 1 { 295 if p >= n { sp = 0 } else { 296 if buf[p] == (BA_SP as u8) { p = p + 1 } else { sp = 0 } 297 } 298 } 299 var iscomment: i64 = 0 300 if p < n { if (buf[p] as i64) == BA_HASH { iscomment = 1 } } 301 if iscomment == 0 { if found < 0 { if p + nl <= n { 302 var j: i64 = 0 303 var ok: i64 = 1 304 while j < nl { 305 if buf[p+j] != needle[j] { ok = 0; j = nl } else { j = j + 1 } 306 } 307 if ok == 1 { found = p } 308 } } } 309 var e: i64 = i 310 var g: i64 = 1 311 while g == 1 { 312 if e >= n { g = 0 } else { 313 if (buf[e] as i64) == BA_NL { g = 0 } else { e = e + 1 } 314 } 315 } 316 i = e + 1 317 } 318 return found 319} 320// out[0]=floor_mb out[1]=max_centiload out[2]=blocked_max out[3]=blocked_samples out[4]=blocked_gap_ms, 321// each BA_CONF_UNSET unless the key is present and positive. Returns 1 when a conf was read, 0 when neither 322// location exists. 323func ba_conf_load(out: *i64) -> i64 { 324 out[0] = BA_CONF_UNSET 325 out[1] = BA_CONF_UNSET 326 out[2] = BA_CONF_UNSET 327 out[3] = BA_CONF_UNSET 328 out[4] = BA_CONF_UNSET 329 let cb: *u8 = sys_mmap(BA_CONF_CAP) 330 var cn: i64 = ba_read(BA_CONF, cb, BA_CONF_CAP) 331 if cn <= 0 { cn = ba_read(BA_CONF_UP, cb, BA_CONF_CAP) } 332 if cn <= 0 { return 0 } 333 let fi: i64 = ba_line_key(cb, cn, "floor_mb=" as *u8) 334 if fi >= 0 { let v: i64 = ba_uint_at(cb, fi + 9, cn); if v > 0 { out[0] = v } } 335 let mi: i64 = ba_line_key(cb, cn, "max_centiload=" as *u8) 336 if mi >= 0 { let v2: i64 = ba_uint_at(cb, mi + 14, cn); if v2 > 0 { out[1] = v2 } } 337 let bi: i64 = ba_line_key(cb, cn, "blocked_max=" as *u8) 338 if bi >= 0 { let v3: i64 = ba_uint_at(cb, bi + 12, cn); if v3 > 0 { out[2] = v3 } } 339 let si: i64 = ba_line_key(cb, cn, "blocked_samples=" as *u8) 340 if si >= 0 { let v4: i64 = ba_uint_at(cb, si + 16, cn); if v4 > 0 { out[3] = v4 } } 341 let gi: i64 = ba_line_key(cb, cn, "blocked_gap_ms=" as *u8) 342 if gi >= 0 { let v5: i64 = ba_uint_at(cb, gi + 15, cn); if v5 > 0 { out[4] = v5 } } 343 // confirm_max_age_s -- the freshness bound on the CONFIRMED level (rung DG7). Unset leaves out[5] at 344 // BA_CONF_UNSET, ba_confirmed then returns 0 without reading anything, and this file behaves exactly as 345 // it did before the capability existed. The key length is DERIVED here rather than hand-counted like 346 // the rows above: a literal length beside a string is a second copy of that string's shape. 347 let xk: *u8 = "confirm_max_age_s=" as *u8 348 let xi: i64 = ba_line_key(cb, cn, xk) 349 if xi >= 0 { let v6: i64 = ba_uint_at(cb, xi + ba_slen(xk), cn); if v6 > 0 { out[5] = v6 } } 350 return 1 351} 352// ---- THE CONFIRMED I/O LEVEL, READ IN CONSTANT TIME (2026-09-04, /compare/degradation rung DG7) ------- 353// A single /proc/stat read cannot separate a two-second fork burst from a sustained storm. MEASURED on this 354// host by nx_blkprofile over 2,400 samples at 100 ms: procs_blocked median 4 against a bar of 8, p90 8, max 355// 15, and 118 permil of SINGLE samples at or above the bar -- roughly one build in eight refused by a spike 356// on a box whose sustained level is HALF the bar. A second run hours later read 240 permil. 357// The smoothing branch below fixes that and pays its whole span HERE, on every caller, the clock 358// dispatcher's pre-dispatch call included; the same runs found the refusal curve STILL FALLING at a 12,000 359// ms span, so no affordable synchronous window is sufficient. The window is therefore paid ONCE per cadence 360// by nx_ioconfirm and read here in constant time -- the way flight control confirms a fault in a continuous 361// monitor and lets the control law read an already-confirmed state. 362// UNSET IS OFF: with confirm_max_age_s absent from the conf this reads nothing and changes no verdict. 363// STALE, ABSENT OR FUTURE-STAMPED FALLS BACK TO THE SINGLE SAMPLE -- a level nobody took must never read as 364// headroom, and clock skew or a forged write is unobservable rather than healthy. 365const BA_CONFIRM: *u8 = "knowledge/status/ioconfirm.status" as *u8 366const BA_CONFIRM_UP: *u8 = "../knowledge/status/ioconfirm.status" as *u8 367const BA_CONFIRM_TS: *u8 = "ts=" as *u8 368const BA_CONFIRM_LVL: *u8 = "blocked_confirmed=" as *u8 369const BA_CONFIRM_AGE_KEY: *u8 = "confirm_max_age_s=" as *u8 370const BA_CONFIRM_CAP: i64 = 4096 371const BA_TIME_SLOTS: i64 = 64 372 373// out[0]=confirmed level, out[1]=age_s. Returns 1 only when a FRESH level was read; 0 otherwise. Keys are 374// found with ba_line_key, which anchors at the line start and refuses a comment, so no key can match inside 375// another key's value or inside prose. Lengths are DERIVED with ba_slen, never hand-counted. 376func ba_confirmed(max_age_s: i64, out: *i64) -> i64 { 377 out[0] = 0 - 1 378 out[1] = 0 - 1 379 if max_age_s <= 0 { return 0 } 380 let cb: *u8 = sys_mmap(BA_CONFIRM_CAP) 381 var cn: i64 = ba_read(BA_CONFIRM, cb, BA_CONFIRM_CAP) 382 if cn <= 0 { cn = ba_read(BA_CONFIRM_UP, cb, BA_CONFIRM_CAP) } 383 var rc: i64 = 0 384 if cn > 0 { 385 let ti: i64 = ba_line_key(cb, cn, BA_CONFIRM_TS) 386 let li: i64 = ba_line_key(cb, cn, BA_CONFIRM_LVL) 387 if ti >= 0 { if li >= 0 { 388 let ts: i64 = ba_uint_at(cb, ti + ba_slen(BA_CONFIRM_TS), cn) 389 let lv: i64 = ba_uint_at(cb, li + ba_slen(BA_CONFIRM_LVL), cn) 390 let tb: *i64 = sys_mmap(BA_TIME_SLOTS) as *i64 391 tb[0] = 0 392 sys_clock_gettime_real(tb) 393 let now: i64 = tb[0] 394 sys_munmap(tb, BA_TIME_SLOTS) 395 if ts > 0 { if lv >= 0 { 396 let age: i64 = now - ts 397 out[1] = age 398 if age >= 0 { if age <= max_age_s { out[0] = lv; rc = 1 } } 399 } } 400 } } 401 } 402 sys_munmap(cb, BA_CONFIRM_CAP) 403 return rc 404} 405 406func main(argc: i64, argv: *i64) -> i64 { 407 // DG7: the confirmed-level constants and ba_confirmed are defined immediately above. 408 var floor_mb: i64 = BA_FLOOR_MB 409 var max_load: i64 = BA_MAX_CENTILOAD 410 var envelope_src: *u8 = "host-derived" as *u8 411 var max_stated: i64 = 0 // 1 once conf or argv states a ceiling; else host-derived below 412 if argc < 2 { ba_werr("usage: nx_build_admit check [floor_mb] [max_centiload] [blocked_max]\n" as *u8); sys_exit(BA_EXIT_USAGE); return BA_EXIT_USAGE } 413 let verb: *u8 = argv[1] as *u8 414 if verb[0] != (99 as u8) { ba_werr("usage: nx_build_admit check [floor_mb] [max_centiload] [blocked_max]\n" as *u8); sys_exit(BA_EXIT_USAGE); return BA_EXIT_USAGE } 415 // -1 = not stated by the caller; resolved to ncpu x IOA_BLOCKED_PER_CPU once ncpu is measured below. 416 var blocked_max: i64 = 0 - 1 417 // conf first (rule 17: below argv, above defaults), then argv may override any of the three 418 var blk_samples: i64 = BA_BLK_SAMPLES_DEFAULT 419 var blk_gap_ms: i64 = BA_BLK_GAP_MS_DEFAULT 420 // PROVENANCE OF THE BLOCKED READING, carried into the report line. A governor that cannot say WHERE its 421 // deciding number came from teaches every reader to guess, and this one has three possible origins. 422 var confirm_max_age: i64 = 0 423 var confirm_age: i64 = 0 - 1 424 var blk_src: *u8 = "instant" as *u8 425 let cfv: *i64 = sys_mmap(BA_CFV_SLOTS) as *i64 426 if ba_conf_load(cfv) == 1 { 427 envelope_src = "conf" as *u8 428 if cfv[0] > 0 { floor_mb = cfv[0] } 429 if cfv[1] > 0 { max_load = cfv[1]; max_stated = 1 } 430 if cfv[2] > 0 { blocked_max = cfv[2] } 431 if cfv[3] > 0 { blk_samples = cfv[3] } 432 if cfv[4] > 0 { blk_gap_ms = cfv[4] } 433 if cfv[5] > 0 { confirm_max_age = cfv[5] } 434 } 435 if argc > 2 { let a: *u8 = argv[2] as *u8; let v: i64 = ba_uint_at(a, 0, ba_slen(a)); if v > 0 { floor_mb = v; envelope_src = "argv" as *u8 } } 436 if argc > 3 { let b: *u8 = argv[3] as *u8; let v2: i64 = ba_uint_at(b, 0, ba_slen(b)); if v2 > 0 { max_load = v2; max_stated = 1; envelope_src = "argv" as *u8 } } 437 if argc > 4 { let c4: *u8 = argv[4] as *u8; let v3: i64 = ba_uint_at(c4, 0, ba_slen(c4)); if v3 > 0 { blocked_max = v3; envelope_src = "argv" as *u8 } } 438 439 let mbuf: *u8 = sys_mmap(BA_READCAP) 440 let mn: i64 = ba_read(BA_MEMINFO, mbuf, BA_READCAP) 441 if mn <= 0 { 442 ba_werr("BUILD-ADMIT verdict=UNREADABLE /proc/meminfo -- cannot measure, therefore REFUSING to admit (fail-closed)\n" as *u8) 443 sys_exit(BA_EXIT_UNREADABLE) 444 return BA_EXIT_UNREADABLE 445 } 446 let mi: i64 = ba_find(mbuf, mn, "MemAvailable:" as *u8) 447 var avail_mb: i64 = 0 - 1 448 if mi >= 0 { let kb: i64 = ba_uint_at(mbuf, mi + 13, mn); if kb >= 0 { avail_mb = kb / BA_KB_PER_MB } } 449 if avail_mb < 0 { 450 ba_werr("BUILD-ADMIT verdict=UNREADABLE MemAvailable absent -- fail-closed, not admitted\n" as *u8) 451 sys_exit(BA_EXIT_UNREADABLE) 452 return BA_EXIT_UNREADABLE 453 } 454 // HOST-DERIVED CEILING (seq1475): if the caller did not pass an explicit max_centiload, compute it 455 // from THIS host's CPU count instead of using the baked constant. An explicit argv override still 456 // wins -- a caller that deliberately states a ceiling is making a decision, and this must not 457 // silently overrule it. Unreadable /proc/stat falls back to a conservative ncpu, never to unbounded. 458 var ncpu: i64 = 0 459 let sbuf: *u8 = sys_mmap(BA_READCAP) 460 let sn: i64 = ba_read(BA_STAT, sbuf, BA_READCAP) 461 if sn > 0 { ncpu = ioa_ncpu(sbuf, sn) } 462 if ncpu <= 0 { ncpu = BA_NCPU_FALLBACK } 463 if max_stated == 0 { max_load = ncpu * BA_CENTI_PER_CPU } 464 // CPU-ONLY CONFIRMATION (2026-07-30, from measurement, not irritation). /proc/loadavg field 1 counts tasks 465 // in R *and* D state, so a disk-bound workload reads as CPU saturation and a CPU-bound compile is refused 466 // while cores sit idle. MEASURED LIVE ON THIS HOST: load1=8.33, which the OS itself split into IO=3.75 and 467 // CPU=4.58 across 8 cores -- 43% of the CPU idle, build refused by 0.33 of load that was disk wait. 468 // nx_procchurn agreed independently (procs_blocked>=1 with procs_running 3-6: "I/O or lock bound, NOT compute"). 469 // procs_running from /proc/stat is the TRUE run queue (R only, D excluded), so a QUEUE verdict is now 470 // CONFIRMED against it before we refuse. This can only ever admit MORE builds, never fewer, and only when 471 // the run queue itself says the CPUs are free. The memory floor (DENY-MEM) is untouched -- that was the 472 // actual 2026-07-20 wedge cause and it remains the one unconditional block. 473 var procs_run: i64 = 0 - 1 474 if sn > 0 { 475 let pri: i64 = ba_find(sbuf, sn, "procs_running" as *u8) 476 if pri >= 0 { procs_run = ba_uint_at(sbuf, pri + BA_PROCSRUN_OFF, sn) } 477 } 478 // I/O-STORM WITNESS (2026-08-17): procs_blocked from the SAME /proc/stat buffer -- zero new syscalls. 479 // -1 = sensor unreadable; ba_verdict lets the axis ABSTAIN then (the other axes rule), while a wholly 480 // unreadable /proc/stat keeps failing closed via the loadavg/meminfo refusals above. 481 var procs_blk: i64 = 0 - 1 482 if sn > 0 { 483 let pbi: i64 = ba_find(sbuf, sn, "procs_blocked" as *u8) 484 if pbi >= 0 { procs_blk = ba_uint_at(sbuf, pbi + BA_PROCSBLK_OFF, sn) } 485 } 486 // SMOOTHING, TAKEN ONLY WHEN ARMED. blk_samples defaults to 1, so this branch runs ZERO times and 487 // procs_blk keeps exactly the value the line above gave it -- landing this edit changes no verdict. 488 // Armed (blocked_samples>1 in the conf), it replaces the single reading with the MEDIAN of that many 489 // spaced samples. It only ever REPLACES a reading that already succeeded: an unreadable sensor stays 490 // -1 and keeps abstaining, because a median of a sensor we could not read would be an invention. 491 if blk_samples > 1 { if procs_blk >= 0 { 492 let mm: *i64 = sys_mmap(BA_MEDIAN_SLOTS) as *i64 493 if ioa_measure_median(mm, blk_samples, blk_gap_ms) == 0 { if mm[1] >= 0 { procs_blk = mm[1] } } 494 sys_munmap(mm, BA_MEDIAN_SLOTS) 495 } } 496 // THE CONFIRMED LEVEL WINS OVER THE INSTANT ONE WHEN IT IS FRESH (rung DG7). Ordered AFTER the median 497 // branch on purpose: both answer the same question and the cheap one should have the last word, since a 498 // level confirmed over a full window by the beat is strictly better evidence than a window this caller 499 // paid for inline. OFF unless confirm_max_age_s is set. Never applied to an unreadable sensor: procs_blk 500 // stays -1 and keeps abstaining, because substituting a level for a reading we could not take would be 501 // an invention. Stale or absent leaves the instant reading exactly where it was -- DEGRADE TO THE 502 // INCUMBENT, NEVER ACQUIT ON A MEASUREMENT NOBODY TOOK. 503 if confirm_max_age > 0 { if procs_blk >= 0 { 504 let cvo: *i64 = sys_mmap(BA_TIME_SLOTS) as *i64 505 if ba_confirmed(confirm_max_age, cvo) == 1 { 506 procs_blk = cvo[0] 507 confirm_age = cvo[1] 508 blk_src = "confirmed" as *u8 509 } else { 510 confirm_age = cvo[1] 511 blk_src = "instant-confirm-unusable" as *u8 512 } 513 sys_munmap(cvo, BA_TIME_SLOTS) 514 } } 515 if blocked_max < 0 { blocked_max = ncpu * IOA_BLOCKED_PER_CPU } 516 let lbuf: *u8 = sys_mmap(BA_READCAP) 517 let ln: i64 = ba_read(BA_LOADAVG, lbuf, BA_READCAP) 518 var load1: i64 = 0 - 1 519 if ln > 0 { 520 var w: i64 = 0 521 while w < ln { if lbuf[w] == (BA_SP as u8) { w = ln } else { w = w + 1 } } 522 load1 = ba_centi_head(lbuf, w) 523 } 524 if load1 < 0 { 525 ba_werr("BUILD-ADMIT verdict=UNREADABLE /proc/loadavg -- fail-closed, not admitted\n" as *u8) 526 sys_exit(BA_EXIT_UNREADABLE) 527 return BA_EXIT_UNREADABLE 528 } 529 let out: *u8 = sys_mmap(BA_OUTCAP) 530 var o: i64 = 0 531 o = ba_puts(out, o, "BUILD-ADMIT mem_available_mb=" as *u8) 532 o = ba_puti(out, o, avail_mb) 533 o = ba_puts(out, o, " floor_mb=" as *u8) 534 o = ba_puti(out, o, floor_mb) 535 o = ba_puts(out, o, " load1_centi=" as *u8) 536 o = ba_puti(out, o, load1) 537 o = ba_puts(out, o, " max_centiload=" as *u8) 538 o = ba_puti(out, o, max_load) 539 o = ba_puts(out, o, " ncpu=" as *u8) 540 o = ba_puti(out, o, ncpu) 541 o = ba_puts(out, o, " procs_running=" as *u8) 542 o = ba_puti(out, o, procs_run) 543 o = ba_puts(out, o, " procs_blocked=" as *u8) 544 o = ba_puti(out, o, procs_blk) 545 o = ba_puts(out, o, " blocked_max=" as *u8) 546 o = ba_puti(out, o, blocked_max) 547 // WHERE THE DECIDING NUMBER CAME FROM. instant = one /proc/stat read (the historical behaviour and the 548 // default). confirmed = a level the nx_ioconfirm beat measured over a full window and this call read in 549 // constant time. instant-confirm-unusable = the feature is armed and the level was absent, stale or 550 // future-stamped, so the single reading stands. confirm_age_s is -1 when nothing was read at all. 551 o = ba_puts(out, o, " blocked_src=" as *u8) 552 o = ba_puts(out, o, blk_src) 553 o = ba_puts(out, o, " confirm_age_s=" as *u8) 554 o = ba_puti(out, o, confirm_age) 555 o = ba_puts(out, o, " confirm_max_age_s=" as *u8) 556 o = ba_puti(out, o, confirm_max_age) 557 o = ba_puts(out, o, " envelope_src=" as *u8) 558 o = ba_puts(out, o, envelope_src) 559 o = ba_puts(out, o, " (bounded /proc reads; load1 is a one-minute average; runqueue is instantaneous; blocked_src and confirm_age_s identify the blocked measurement; thresholds: argv > knowledge/build_admit.conf > host-derived/bootstrap constants)\n" as *u8) 560 // ONE decision, ONE implementation -- main now only RENDERS what ba_verdict decided (gated above). 561 let reason: i64 = ba_reason(avail_mb, floor_mb, load1, max_load, procs_run, ncpu, procs_blk, blocked_max) 562 let verdict: i64 = ba_reason_exit(reason) 563 o = ba_puts(out, o, "reason=" as *u8) 564 o = ba_puts(out, o, ba_reason_label(reason)) 565 o = ba_puts(out, o, " policy_scope=host proxies_do_not_measure_cpu_utilization_or_disk_capacity=1\n" as *u8) 566 if verdict == BA_EXIT_DENY { 567 o = ba_puts(out, o, "VERDICT=DENY-MEM below the memory floor -- forking the compiler now risks wedging the HOST, not merely failing the build (incident 2026-07-20: userspace could not fork, sshd and DSM both stopped serving). Wait, or raise the floor deliberately with evidence.\n" as *u8) 568 sys_write(BA_STDOUT, out, o) 569 sys_exit(BA_EXIT_DENY) 570 return BA_EXIT_DENY 571 } 572 if verdict == BA_EXIT_QUEUE { 573 o = ba_puts(out, o, "VERDICT=QUEUE deferred by the configured policy; reason above identifies the deciding branch. This detector submits no job. Caller enqueue/operation receipts must establish whether work was accepted and completed. REMEDY: re-run the build once the branch named above clears -- the levels measured for this decision are on the BUILD-ADMIT line above and the thresholds they were compared against live in knowledge/build_admit.conf. Do not raise a threshold to pass this check: that re-arms the load the gate exists to refuse.\n" as *u8) 574 sys_write(BA_STDOUT, out, o) 575 sys_exit(BA_EXIT_QUEUE) 576 return BA_EXIT_QUEUE 577 } 578 o = ba_puts(out, o, "VERDICT=GRANT configured policy checks passed; not a resource reservation.\n" as *u8) 579 sys_write(BA_STDOUT, out, o) 580 sys_exit(0) 581 return 0 582}