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}