code wiki / _hdl_build / nx_build_admit.nx
nx_build_admit.nx
buildroot/runtime/_hdl_build/nx_build_admit.nx
about
nx_build_admit.nx -- BUILD ADMISSION CONTROL (the missing gate that let the fleet wedge the host).
INCIDENT 2026-07-20: ~10 concurrent sessions were compiling at once (registry grew 198->236 GREEN tools in
ONE session). Every /api/build forks nx_cc_sovereign (549KB) on a memory-limited Synology and NOTHING
serialized or admitted them -> the NAS went userspace-wedged (sshd could not complete a banner, DSM could
not serve a page, every sovereign daemon refused TCP) and then off the network entirely. Measured, not
guessed. nx_swarm_admit ALREADY documents itself as the gate "BEFORE any heavy launch (model decode, codec,
image-gen, BIG BUILD)" -- but /api/build never called it. A compile IS a heavy launch. This organ is the
build-shaped admission verdict, with ZERO deps beyond nx_syscalls so it can never itself be the thing that
fails under load.
LIAR-KILLED: every number is read live from /proc on the host being protected -- no estimate, no cache.
MemAvailable from /proc/meminfo (the honest figure: reclaimable included, unlike MemFree)
1-minute load from /proc/loadavg, carried as CENTI-load (integer; no floats, sovereign law)
nx_build_admit check [floor_mb] [max_centiload]
exit 0 GRANT -- headroom exists, compile now
exit 3 DENY-LOAD -- below the memory floor; compiling now risks wedging the HOST, not just the build
exit 4 QUEUE -- load above the ceiling; hand to nx_orchestrate wait-for-opening and come back
exit 2 usage | 5 unreadable /proc (fail-CLOSED: cannot measure => cannot admit)
COMPOSES (does not duplicate): the QUEUE verdict is designed to be absorbed by nx_orchestrate's
wait-for-opening deploy queue -- the same anti-collision primitive shipped this session, pointed at COMPUTE
instead of deploys. The structural half is mgmt-owned: ma_do_build must consult this BEFORE forking the
compiler (filed rung) -- until then this is the session-callable discipline half, same two-tier pattern as
nx_route_diff/nx_tooldiff (detector live, enforcement filed).
FAIL-CLOSED BY CONSTRUCTION: an unreadable /proc returns exit 5, never GRANT. A guard that cannot measure
must refuse, never wave through -- the same law as an instrument that must not score bytes it did not fetch.
ENVELOPE (declared in output): 64KB per /proc read; thresholds are NAMED CONSTS (rule-11) and argv-overridable.
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 1 imports · 0 importers
imports: nx_syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 32 | const BA_MEMINFO: *u8 = "/proc/meminfo" as *u8 |
| 33 | const BA_LOADAVG: *u8 = "/proc/loadavg" as *u8 |
| 34 | const BA_READCAP: i64 = 65536 |
| 35 | const BA_OUTCAP: i64 = 4096 |
| 39 | const BA_FLOOR_MB: i64 = 512 |
| 40 | const BA_MAX_CENTILOAD: i64 = 400 |
| 61 | const BA_STAT: *u8 = "/proc/stat" as *u8 |
| 64 | const BA_PROCSRUN_OFF: i64 = 13 |
| 65 | const BA_CENTI_PER_CPU: i64 = 100 // 1.00 x ncpu, in centi-load |
| 66 | const BA_NCPU_FALLBACK: i64 = 4 // only if /proc/stat is unreadable; conservative, never unbounded |
| 92 | const BA_KB_PER_MB: i64 = 1024 |
| 93 | const BA_CENTI: i64 = 100 |
| 94 | const BA_STDOUT: i64 = 1 |
| 95 | const BA_STDERR: i64 = 2 |
| 96 | const BA_ZERO: i64 = 48 |
| 97 | const BA_NINE: i64 = 57 |
| 98 | const BA_DOT: i64 = 46 |
| 99 | const BA_NL: i64 = 10 |
| 100 | const BA_SP: i64 = 32 |
| 101 | const BA_EXIT_USAGE: i64 = 2 |
| 102 | const BA_EXIT_DENY: i64 = 3 |
| 103 | const BA_EXIT_QUEUE: i64 = 4 |
| 104 | const BA_EXIT_UNREADABLE: i64 = 5 |
functions
| 69 | func ba_ncpu(buf: *u8, n: i64) -> i64 called by 1: main |
| 116 | func ba_verdict(avail_mb: i64, floor_mb: i64, load1: i64, max_load: i64, procs_run: i64, ncpu: i64) -> i64 called by 1: main |
| 123 | func ba_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 124 | func ba_werr(s: *u8) -> i64 { sys_write(BA_STDERR, s, ba_slen(s)); return 0 } |
| 125 | func 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 } called by 1: main |
| 126 | func ba_puti(b: *u8, off: i64, v: i64) -> i64 |
| 138 | func ba_read(path: *u8, buf: *u8, cap: i64) -> i64 |
| 148 | func ba_find(buf: *u8, n: i64, needle: *u8) -> i64 |
| 162 | func ba_uint_at(buf: *u8, p: i64, n: i64) -> i64 called by 1: main |
| 180 | func ba_centi_head(buf: *u8, n: i64) -> i64 called by 1: main |
| 204 | func main(argc: i64, argv: *i64) -> i64 |