code wiki / _hdl_build / nx_sov_build_run.nx
nx_sov_build_run.nx source
↩ module page · 532 lines · 36152 B
1// nx_sov_build_run.nx -- FULLY SOVEREIGN, REUSABLE build runner (operator: "no sh and no c build,
2// from the hardware layer up"). Generalizes nx_retire_gcc_orchestrator from hardcoded targets to ANY
3// module passed as argv[1]. The build path is bits-up sovereign end to end:
4// nx_cc_sovereign.elf <module>.nx -> _build/<module>.s (the team's SELF-HOSTED compiler)
5// nxasm_x86_main.elf <module>.s -> _build/<module>.sov.elf (the team's x86-64 assembler+linker)
6// _build/<module>.sov.elf (run it; exit = its exit)
7// ⚠OUTPUT PATH IS _build/, RELATIVE TO CWD -- NOT /tmp/. These three lines said /tmp/ until 2026-07-30,
8// left stale by the flock change that moved artifacts to a per-target _build/<name>.lock+.s+.sov.elf so
9// concurrent sweeps stop overwriting each other mid-build. A doc that disagrees with the code is a defect,
10// not a nit: it is why callers hunt for the artifact, and it hid a REAL brick hazard -- nx_hostctl
11// cmd_buildrun (nx_hostctl.nx:3089) still reads /tmp/<name>.sov.elf, so this runner and that supervisor
12// MUST be shipped as a matched pair or every /api/build in the ecosystem fails "no /tmp/<name>.sov.elf".
13// NO gcc, NO bash, NO .sh anywhere. Orchestration is NishiLang sys_fork/dup3/execve/wait4.
14// Recompile-retry guards the known-good compiler's empty-.s nondeterminism. Usage:
15// nx_sov_build_run.elf <module-basename-in-runtime/_hdl_build>
16// license_tier: ORIGINAL Reuses the _run spine from nx_retire_gcc_orchestrator.
17import "nx_syscalls.nx"
18import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
19
20// TOOLCHAIN NAMES -- the binaries that BUILD everything else, so they may never be installed as a
21// side effect of a build (see the refusal at the install site; seq1464).
22// ⚠BOTH COMPILER NAMES: the module is `nx_compile_x86` but it ships as `nx_cc_sovereign.elf`, so a
23// guard that knew only one name would leave the other door open -- which is exactly the door the
24// regressed compiler came through. `nx_sov_build_run` is listed because a runner that can overwrite
25// ITSELF mid-build is the same hazard pointed inward.
26func sbr_name_is(a: *u8, b: *u8) -> i64 {
27 var i: i64 = 0
28 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
29 if b[i] != (0 as u8) { return 0 }
30 return 1
31}
32func sbr_is_toolchain(name: *u8) -> i64 {
33 if sbr_name_is(name, "nx_compile_x86" as *u8) == 1 { return 1 }
34 if sbr_name_is(name, "nx_cc_sovereign" as *u8) == 1 { return 1 }
35 if sbr_name_is(name, "nxasm_x86_main" as *u8) == 1 { return 1 }
36 if sbr_name_is(name, "nxasm_x86" as *u8) == 1 { return 1 }
37 if sbr_name_is(name, "nx_sov_build_run" as *u8) == 1 { return 1 }
38 return 0
39}
40const SBR_MAGIC_4096: i64 = 4096 // read-chunk size (folded from the buildroot branch's rule-11 sweep, 2026-07-29 merge)
41
42const SBR_OK: i64 = 0
43const SBR_USAGE: i64 = 2
44const SBR_COMPILE_FAIL: i64 = 3
45const SBR_ASM_FAIL: i64 = 4
46const SBR_ADMIT_REFUSED: i64 = 6 // build admission said no; distinct from a compile/assemble failure
47 // (3 and 4 raw would COLLIDE with COMPILE_FAIL/ASM_FAIL -- a retry-later
48 // signal must never decode as a broken build)
49const SBR_CANON_REFUSED: i64 = 7 // tree-canon divergence: building would compile a FORKED copy
50const SBR_MIN_ASM_BYTES: i64 = 128 // empty/failed .s is ~0-byte header; the smallest real program (_min42) is 540B
51const SBR_MAX_RETRIES: i64 = 12
52
53func sbr_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
54// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
55// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
56// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
57// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
58func sbr_putn(v: i64) -> i64 { nxi_out(v); return 0 }
59
60// append NUL-terminated s into dst at off; return new offset (no NUL written)
61func sbr_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i }
62
63// bounded byte-range equality on (base,offset,len) pairs -- conf/manifest lines are not NUL-terminated
64func sbr_ceq_at(a: *u8, ao: i64, al: i64, b: *u8, bo: i64, bl: i64) -> i64 {
65 if al != bl { return 0 }
66 var i: i64 = 0
67 while i < al { if a[ao+i] != b[bo+i] { return 0 } i = i + 1 }
68 return 1
69}
70
71func sbr_exitcode(status: i64) -> i64 { return (status >> 8) & 0xff }
72
73// fork + optional stdout/stderr redirect + execve; parent waits; returns child's WEXITSTATUS,
74// OR 128+signal if the child died to a signal (shell convention). Without the signal check a
75// SEGFAULTED tool decodes as rc=0 = silent fake success (caught live 2026-06-09: nxasm segfaulted
76// on the 2.4MB compiler .s, the runner reported the assemble step OK, and the missing ELF
77// surfaced later as a confusing execve-127). Composes [[feedback-evidence-driven-live-grading]].
78// redir_err>=0 -> child stderr (e.g. /dev/null to mute nx_cc's symbol-table dump).
79func sbr_run(path: *u8, argv: *i64, envp: *i64, redir_out: i64, redir_err: i64) -> i64 {
80 let pid: i64 = sys_fork()
81 if pid == 0 {
82 if redir_out >= 0 { sys_dup3(redir_out, 1, 0) }
83 if redir_err >= 0 { sys_dup3(redir_err, 2, 0) }
84 sys_execve(path, argv, envp)
85 sys_exit(127)
86 }
87 let st: *i64 = sys_mmap(16) as *i64
88 sys_wait4(pid, st, 0)
89 let sig: i64 = st[0] & 0x7f
90 if sig != 0 { return 128 + sig }
91 return sbr_exitcode(st[0])
92}
93
94// byte-size of a file (open, read in chunks, count) -- the determinism check for the empty-.s guard.
95func sbr_filesize(path: *u8) -> i64 {
96 let fd: i64 = sys_openat_rd(path)
97 if fd < 0 { return 0 }
98 let buf: *u8 = sys_mmap(SBR_MAGIC_4096)
99 var total: i64 = 0
100 var n: i64 = sys_read(fd, buf, SBR_MAGIC_4096)
101 while n > 0 { total = total + n; n = sys_read(fd, buf, SBR_MAGIC_4096) }
102 sys_close(fd)
103 return total
104}
105
106func main(argc: i64, argv: *i64) -> i64 {
107 if argc < 2 { sbr_puts("usage: nx_sov_build_run <module-basename> [--build-only|--install] [args-forwarded-to-program...]\n" as *u8); sys_exit(SBR_USAGE); return SBR_USAGE }
108 let name: *u8 = argv[1] as *u8
109
110 // FLAGS (argv[2]): --build-only (--b*) = skip the run; --install (--i*) = FORCE-install /tmp/<name>.sov.elf
111 // to _offc/<name>.elf even with NO existing twin (makes a reusable TOOL permanent on first build) + skip run.
112 // Default = refresh-IF-PRESENT (LM-026) + run. Opt-in so throwaway gates/probes never clutter _offc.
113 // Everything AFTER the optional flag is FORWARDED to the built program (fwd_start below).
114 var force_install: i64 = 0
115 var skip_run: i64 = 0
116 var fwd_start: i64 = 2
117 if argc >= 3 {
118 let fl: *u8 = argv[2] as *u8
119 if fl[0] == (45 as u8) { if fl[1] == (45 as u8) {
120 if fl[2] == (98 as u8) { skip_run = 1 } // --build-only
121 if fl[2] == (105 as u8) { force_install = 1; skip_run = 1 } // --install
122 fwd_start = 3 // a --flag is the lane's, never forwarded
123 } }
124 }
125
126 let src: *u8 = sys_mmap(512); var o: i64 = 0
127 o = sbr_cat(src, o, "runtime/_hdl_build/" as *u8); o = sbr_cat(src, o, name); o = sbr_cat(src, o, ".nx" as *u8); src[o] = 0 as u8
128 // fall back to runtime/<name>.nx if the module isn't under _hdl_build (additive; the older slice stack lives in runtime/)
129 let chk: i64 = sys_openat_rd(src)
130 if chk < 0 {
131 o = 0
132 o = sbr_cat(src, o, "runtime/" as *u8); o = sbr_cat(src, o, name); o = sbr_cat(src, o, ".nx" as *u8); src[o] = 0 as u8
133 }
134 if chk >= 0 { sys_close(chk) }
135 // 2nd fallback: nxasm/<name>.nx -- so the TOOLCHAIN itself (assembler/linker) is built
136 // through THIS runner's retry guard, never direct 1-shot (2026-06-09 lesson: a direct
137 // unguarded nxasm rebuild handed back a nondeterministic miscompile that exited pre-pass0).
138 let chk2: i64 = sys_openat_rd(src)
139 if chk2 < 0 {
140 o = 0
141 o = sbr_cat(src, o, "nxasm/" as *u8); o = sbr_cat(src, o, name); o = sbr_cat(src, o, ".nx" as *u8); src[o] = 0 as u8
142 }
143 if chk2 >= 0 { sys_close(chk2) }
144 // 3rd fallback: runtime/wiki/<name>.nx -- the internal wiki engine (nx_wiki_main) and
145 // its render organs live here, NOT in runtime/ or _hdl_build/. Mirrors the fallbacks
146 // above exactly (reopen src; if still missing, rewrite to the next candidate path).
147 // Added 2026-06-15 (a wiki organ otherwise mis-compiled on a bogus nxasm/<name>.nx path
148 // and surfaced as a misleading "COMPILE-FAIL (empty .s)").
149 let chk3: i64 = sys_openat_rd(src)
150 if chk3 < 0 {
151 o = 0
152 o = sbr_cat(src, o, "runtime/wiki/" as *u8); o = sbr_cat(src, o, name); o = sbr_cat(src, o, ".nx" as *u8); src[o] = 0 as u8
153 }
154 if chk3 >= 0 { sys_close(chk3) }
155 // FINAL existence verdict: if NONE of the candidate paths resolved to a real file, fail
156 // LOUD with SOURCE-NOT-FOUND rather than running the compiler on a non-existent path
157 // (which produces an empty .s and the misleading "COMPILE-FAIL" above). Fail-fast at the
158 // boundary (Cardinal 12 + Cardinal 20: a build that can't find its source must say so).
159 let chkf: i64 = sys_openat_rd(src)
160 if chkf < 0 {
161 sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name)
162 sbr_puts(": SOURCE-NOT-FOUND (probed runtime/_hdl_build/, runtime/, nxasm/, runtime/wiki/)\n" as *u8)
163 sys_exit(SBR_USAGE); return SBR_USAGE
164 }
165 sys_close(chkf)
166 // BUILD OUTPUT LIVES IN THE REPO, NOT /tmp (operator 2026-07-27). WSL's /tmp is wiped
167 // between sessions, which silently deleted built ELFs mid-run and made scripts that
168 // referenced them fail in confusing ways (a documented recurring trap). `_build/` is
169 // repo-local and durable; mkdir is idempotent (EEXIST is fine, hence the ignored rc).
170 sys_mkdir("_build\x00" as *u8, 493)
171 // PER-TARGET BUILD LOCK (seq1260, 2026-07-29): two concurrent builds of the SAME target used to
172 // interleave on _build/<name>.{s,sov.elf} -- one overwrote the other's artifact MID-SWEEP and a
173 // BD control curve read garbage (the /api/build shared-capture class at the local tier). Blocking
174 // flock on _build/<name>.lock serializes per target, held through compile->assemble->install and
175 // RELEASED before the run phase (a long-running target must never wedge future rebuilds; an
176 // already-running process keeps its inode across the atomic rename, so rebuild-under-run is safe).
177 // Cross-target builds never contend; process exit releases (crash-safe); the lock file is NEVER
178 // unlinked (the documented lock-file law).
179 let lockpath: *u8 = sys_mmap(256); o = 0
180 o = sbr_cat(lockpath, o, "_build/" as *u8); o = sbr_cat(lockpath, o, name); o = sbr_cat(lockpath, o, ".lock" as *u8); lockpath[o] = 0 as u8
181 let lockfd: i64 = sys_openat_wr(lockpath, 0x1a4)
182 if lockfd >= 0 { sys_flock(lockfd, SYS_LOCK_EX) }
183 let spath: *u8 = sys_mmap(256); o = 0
184 o = sbr_cat(spath, o, "_build/" as *u8); o = sbr_cat(spath, o, name); o = sbr_cat(spath, o, ".s" as *u8); spath[o] = 0 as u8
185 let elfpath: *u8 = sys_mmap(256); o = 0
186 o = sbr_cat(elfpath, o, "_build/" as *u8); o = sbr_cat(elfpath, o, name); o = sbr_cat(elfpath, o, ".sov.elf" as *u8); elfpath[o] = 0 as u8
187 // FAIL-LOUD (seq132, 2026-07-19): remove any STALE _build/<name>.sov.elf from a prior build BEFORE
188 // compiling, so a failed build can never leave the previous working binary behind to be staged as a
189 // false "BUILT" (the masking trap that cost a 3-reship cascade).
190 //
191 // ROOT FIX seq363 (2026-07-30): this used to TRUNCATE the stale artifact to 0 bytes via O_TRUNC and
192 // leave it there. That achieved the anti-staleness goal but replaced one fail-open with a weaker one:
193 // after a COMPILE-FAIL a 0-byte file REMAINED on disk, so `does the elf exist?` -- the check every
194 // naive caller actually writes -- still answered YES for a build that produced nothing. Measured
195 // 2026-07-30: a deliberately-broken target left _build/<t>.sov.elf at 0 bytes, and separately a PIPE
196 // swallows this runner's exit code, so neither the artifact nor `$?` was trustworthy.
197 //
198 // UNLINK instead. The invariant becomes the one callers already assume: THE ARTIFACT EXISTS IF AND
199 // ONLY IF THE BUILD SUCCEEDED. Downstream ELF-magic/size guards (nx_hostctl cmd_buildrun, /api/promote,
200 // md_tc_elf_size) all still hold -- this makes them belt-and-suspenders rather than load-bearing.
201 // Success path is unchanged: the atomic rename below creates the file fresh.
202 sys_unlinkat(elfpath)
203
204 // SOVEREIGN compiler (self-hosted), NOT nx_cc_known_good (= the C
205 // bootstrap, FORBIDDEN on the build path per operator law -- C is
206 // for benchmarking only). Fixed 2026-06-09 during C2-deploy.
207 let compiler: *u8 = "_offc/nx_cc_sovereign.elf" as *u8
208 // the CURRENT (post-cl-shift-fix, 2026-06-09) sovereign assembler. The older _offc/nxasm_x86.elf
209 // (2026-05-30) systemically mis-encodes registers->r15 and segfaults even ret-42 -- do NOT use it.
210 let asm_tool: *u8 = "_offc/nxasm_x86_main.elf" as *u8
211
212 let envp: *i64 = sys_mmap(8*4) as *i64
213 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
214
215 let devnull: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) // mute nx_cc's symbol-table dump
216
217 // ---- BUILD ADMISSION (2026-08-01): consult nx_build_admit BEFORE forking the compiler.
218 // MEASURED THE NIGHT THIS LANDED: load average 132.58, 334MB available of 36GB, four concurrent
219 // compiles -- builds were being OOM-killed mid-flight for every seat sharing this host, and two of
220 // mine died that way. nx_build_admit has existed since 2026-07-30 and NOTHING called it: its own
221 // header names ma_do_build as the filed rung, and THIS runner -- the path agents actually invoke --
222 // was never wired either. A COMPILE IS A HEAVY LAUNCH.
223 // Refuse LOUDLY rather than pile on and be killed. That is the memfloor law already on the board:
224 // REFUSE, NEVER SILENTLY SHRINK. A build that never starts is recoverable; a wedged host is not.
225 // 0 GRANT | 3 DENY-LOAD (below the memory floor) | 4 QUEUE (above the load ceiling)
226 // 5 CANNOT-MEASURE (unreadable /proc -> fail-CLOSED by construction, never wave through)
227 // FAIL-OPEN ON ABSENCE ONLY: a missing admitter makes execve return 127 and we proceed, so a tree
228 // without it still builds -- but any verdict it DOES return is obeyed. Absence is not permission.
229 let sbr_admit: *u8 = "_build/nx_build_admit.sov.elf" as *u8
230 // THRESHOLDS ARE EXPLICIT, AND ONLY THE CAUSAL ONE IS ARMED (2026-08-01).
231 // MEMORY FLOOR 512MB IS ARMED: it is the DIRECTLY MEASURED cause -- builds were OOM-killed
232 // tonight at 334MB available, which is below this floor, so this check would have refused
233 // exactly the builds that died instead of letting them pile on and be killed.
234 // LOAD CEILING IS DELIBERATELY DISABLED (1000000 centiload = load 10000, unreachable) because
235 // it is UNCALIBRATED for this host: the admitter default is 400 (load 4.00) while this Synology
236 // IDLES at 17.62/24.80/36.19 and only wedged at 132.58. Arming it at the default would refuse
237 // every build for every seat -- including this runner own rebuild, a self-bricking guard.
238 // Picking a number that merely produces the outcome I want would be FITTING, NOT MODELLING.
239 // TO ARM IT: sample /proc/loadavg over a representative window, set the ceiling above the normal
240 // distribution and below the wedge point, and record the measurement that justifies the number.
241 let sbr_aargv: *i64 = sys_mmap(8*6) as *i64
242 sbr_aargv[0] = sbr_admit as i64
243 sbr_aargv[1] = "check" as *u8 as i64
244 // 1024 NOT 512: this MATCHES ma_do_build / md_exec_build_admit, which passes check 1024 100000.
245 // Two call sites carrying two different floors is the rule-11 defect in miniature -- one
246 // threshold, one source. mgmt is the REFERENCE because its value was chosen after the
247 // 2026-07-20 host-wedge incident. If this number ever moves, move BOTH.
248 sbr_aargv[2] = "1024" as *u8 as i64
249 sbr_aargv[3] = "1000000" as *u8 as i64
250 sbr_aargv[4] = 0
251 let sbr_arc: i64 = sbr_run(sbr_admit, sbr_aargv, envp, 0 - 1, 0 - 1)
252 // EXIT IS SBR_ADMIT_REFUSED(6) FOR ALL THREE DENIALS (restored in the 2026-08-03 fork merge --
253 // the NAS branch had regressed to returning the admitter's raw 3/4/5, which COLLIDE with
254 // COMPILE_FAIL(3)/ASM_FAIL(4): a wait-and-retry signal decoding as a broken build). The REASON
255 // stays in the message; the code says only "admission refused, not a build failure".
256 if sbr_arc == 3 {
257 sbr_puts("[nx_sov_build_run] REFUSED-BUILD-ADMIT rc=3 DENY-MEM -- available memory is BELOW THE FLOOR. Compiling now risks wedging the HOST, not just this build. Wait for headroom and retry.\n" as *u8)
258 sys_exit(SBR_ADMIT_REFUSED); return SBR_ADMIT_REFUSED
259 }
260 if sbr_arc == 4 {
261 sbr_puts("[nx_sov_build_run] REFUSED-BUILD-ADMIT rc=4 QUEUE -- 1-minute load is ABOVE THE CEILING. Hand this to nx_orchestrate wait-for-opening and come back.\n" as *u8)
262 sys_exit(SBR_ADMIT_REFUSED); return SBR_ADMIT_REFUSED
263 }
264 if sbr_arc == 5 {
265 sbr_puts("[nx_sov_build_run] REFUSED-BUILD-ADMIT rc=5 CANNOT-MEASURE -- /proc unreadable, so admission FAILS CLOSED by construction. A guard that cannot measure must refuse, never wave through.\n" as *u8)
266 sys_exit(SBR_ADMIT_REFUSED); return SBR_ADMIT_REFUSED
267 }
268 if sbr_arc != 0 {
269 sbr_puts("[nx_sov_build_run] BUILD ADMISSION DID NOT RUN (rc=" as *u8); sbr_putn(sbr_arc)
270 sbr_puts(", expected _build/nx_build_admit.sov.elf). PROCEEDING UNGUARDED -- this build is not protected against wedging the host. Build nx_build_admit to close this.\n" as *u8)
271 }
272
273 // ---- TREE-CANON ADMISSION (2026-08-03, debt 1785622769): a build must never compile a copy
274 // that diverges from canon. ../knowledge/tree_canon.conf (cwd is buildroot; the conf lives at
275 // the nishihost root) lists tree-root-relative paths that MUST be byte-identical across every
276 // tree that can build them. Row grammar: '#' comment, blank ignored, '!path' = FREEZE EVERY
277 // BUILD while divergent (shared infrastructure -- a fork there miscompiles everything
278 // downstream), 'path' = refuse only the build OF THAT FILE (a lane's own divergence must not
279 // block unrelated targets). The other tree's sizes come from the last pushed authoring
280 // manifest (../knowledge/status/treecanon_laptop.mf, produced by nx_treediff on that tree).
281 // SIZE IS A SCREEN, NOT PROOF OF IDENTITY (nx_treediff's own declared bound) -- equal-size
282 // rewrites pass here; nx_treecanon_gate stays the tree-level instrument.
283 // ⚠the manifest is AS FRESH AS ITS LAST PUSH: a stale one can false-pass a scoped row. The
284 // authoring loop owns pushing it after edits; this check is the floor, not the ceiling.
285 // FAIL DIRECTION SPLIT, same design as build admission above: fixtures ABSENT -> PROCEED LOUD
286 // (a tree without them still builds; a silent unwired guard is how guards die); fixtures
287 // PRESENT and a governed row diverges -> REFUSE, fail CLOSED, exit SBR_CANON_REFUSED(7).
288 let cn_conf_l: *i64 = sys_mmap(16) as *i64
289 let cn_conf: *u8 = sys_read_file("../knowledge/tree_canon.conf" as *u8, cn_conf_l)
290 let cn_mf_l: *i64 = sys_mmap(16) as *i64
291 let cn_mf: *u8 = sys_read_file("../knowledge/status/treecanon_laptop.mf" as *u8, cn_mf_l)
292 if (cn_conf as i64) == 0 {
293 sbr_puts("[nx_sov_build_run] TREE-CANON NOT CHECKED: ../knowledge/tree_canon.conf absent. PROCEEDING UNGUARDED against cross-tree divergence.\n" as *u8)
294 }
295 if (cn_conf as i64) != 0 { if (cn_mf as i64) == 0 {
296 sbr_puts("[nx_sov_build_run] TREE-CANON NOT CHECKED: ../knowledge/status/treecanon_laptop.mf absent -- generate with nx_treediff on the authoring tree and push it. PROCEEDING UNGUARDED.\n" as *u8)
297 } }
298 if (cn_conf as i64) != 0 { if (cn_mf as i64) != 0 {
299 // the canon rows are relative to the runtime root; src is "runtime/<rel>" for every probe
300 // path except the nxasm/ fallback (toolchain sources are not canon-governed rows today)
301 var cn_isrt: i64 = 1
302 let cn_rt: *u8 = "runtime/" as *u8
303 var cn_i: i64 = 0
304 while cn_i < 8 { if src[cn_i] != cn_rt[cn_i] { cn_isrt = 0 } cn_i = cn_i + 1 }
305 var cn_srclen: i64 = 0
306 while src[cn_srclen] != (0 as u8) { cn_srclen = cn_srclen + 1 }
307 let cn_path: *u8 = sys_mmap(512)
308 let cn_cl: i64 = cn_conf_l[0]
309 let cn_ml: i64 = cn_mf_l[0]
310 var cn_p: i64 = 0
311 while cn_p < cn_cl {
312 var cn_e: i64 = cn_p
313 var cn_go: i64 = 1
314 while cn_go == 1 {
315 if cn_e >= cn_cl { cn_go = 0 }
316 if cn_go == 1 { if cn_conf[cn_e] == (10 as u8) { cn_go = 0 } }
317 if cn_go == 1 { cn_e = cn_e + 1 }
318 }
319 var cn_len: i64 = cn_e - cn_p
320 if cn_len > 0 { if cn_conf[cn_p + cn_len - 1] == (13 as u8) { cn_len = cn_len - 1 } } // CRLF-tolerant
321 var cn_frozen: i64 = 0
322 var cn_ps: i64 = cn_p
323 if cn_len > 0 { if cn_conf[cn_ps] == (33 as u8) { cn_frozen = 1; cn_ps = cn_ps + 1; cn_len = cn_len - 1 } }
324 var cn_live: i64 = 0
325 if cn_len > 0 { if cn_conf[cn_ps] != (35 as u8) { cn_live = 1 } }
326 if cn_live == 1 {
327 // relevant = frozen row, OR the row IS the file this build compiles
328 var cn_rel: i64 = cn_frozen
329 if cn_isrt == 1 { if sbr_ceq_at(src, 8, cn_srclen - 8, cn_conf, cn_ps, cn_len) == 1 { cn_rel = 1 } }
330 if cn_rel == 1 {
331 var cq: i64 = 0
332 cq = sbr_cat(cn_path, cq, "runtime/" as *u8)
333 var ck: i64 = 0
334 while ck < cn_len { cn_path[cq] = cn_conf[cn_ps + ck]; cq = cq + 1; ck = ck + 1 }
335 cn_path[cq] = 0 as u8
336 let cn_nas: i64 = sbr_filesize(cn_path)
337 // find the row in the authoring manifest: lines are "<bytes> <relpath>"
338 var cn_found: i64 = 0
339 var cn_lap: i64 = 0 - 1
340 var mp: i64 = 0
341 while mp < cn_ml {
342 var me: i64 = mp
343 var mg: i64 = 1
344 while mg == 1 {
345 if me >= cn_ml { mg = 0 }
346 if mg == 1 { if cn_mf[me] == (10 as u8) { mg = 0 } }
347 if mg == 1 { me = me + 1 }
348 }
349 var ml2: i64 = me - mp
350 if ml2 > 0 { if cn_mf[mp + ml2 - 1] == (13 as u8) { ml2 = ml2 - 1 } }
351 // split at the first space
352 var ms: i64 = 0 - 1
353 var mi: i64 = 0
354 while mi < ml2 { if ms < 0 { if cn_mf[mp + mi] == (32 as u8) { ms = mi } } mi = mi + 1 }
355 if ms > 0 {
356 if sbr_ceq_at(cn_mf, mp + ms + 1, ml2 - ms - 1, cn_conf, cn_ps, cn_len) == 1 {
357 cn_found = 1
358 var mv: i64 = 0
359 var md: i64 = 0
360 while md < ms { mv = mv * 10 + ((cn_mf[mp + md] as i64) - 48); md = md + 1 }
361 cn_lap = mv
362 }
363 }
364 mp = me + 1
365 }
366 var cn_div: i64 = 0
367 if cn_found == 0 { cn_div = 1 }
368 if cn_found == 1 { if cn_lap != cn_nas { cn_div = 1 } }
369 if cn_nas == 0 { cn_div = 1 }
370 if cn_div == 1 {
371 sbr_puts("[nx_sov_build_run] REFUSED-TREE-CANON rc=7: canon path " as *u8)
372 sbr_puts(cn_path)
373 sbr_puts(" DIVERGES across trees (this tree=" as *u8); sbr_putn(cn_nas)
374 sbr_puts("B, authoring manifest=" as *u8); sbr_putn(cn_lap)
375 sbr_puts("B; -1 = absent). Building now would compile a FORKED copy -- the exact class that applied the unfreed-mmap fix twice. Remedy: body-diff and converge the file in BOTH trees (merge per file, never wholesale), regenerate + push knowledge/status/treecanon_laptop.mf, then rebuild.\n" as *u8)
376 sys_exit(SBR_CANON_REFUSED); return SBR_CANON_REFUSED
377 }
378 }
379 }
380 cn_p = cn_e + 1
381 }
382 } }
383
384 // ---- compile+ASSEMBLE with recompile-retry until the .s ASSEMBLES CLEANLY (compiler nondeterminism guard).
385 // ROOT FIX 2026-06-25: the old guard stopped at the first NON-EMPTY .s -- but nx_cc's nondeterminism also
386 // TRUNCATES (a non-empty .s missing the trailing `main` -> nxasm rc=102 "UNDEFINED label: main", caught x3
387 // assembling nx_raci_sov / nx_pattern_library). So success now REQUIRES rc_a==0 from nxasm, not merely
388 // non-empty bytes -> truncation-nondeterminism becomes reliability. The assemble lands in tmpelf (atomic
389 // rename after the loop). If a real DETERMINISTIC miscompile, the retries exhaust + we LOUD-fail saying so. ----
390 let tmpelf: *u8 = sys_mmap(256); o = 0
391 o = sbr_cat(tmpelf, o, "_build/_sbr_asm_out." as *u8); o = sbr_cat(tmpelf, o, name); tmpelf[o] = 0 as u8
392 var asmbytes: i64 = 0
393 var rc_a: i64 = 0 - 1
394 var built: i64 = 0
395 var tries: i64 = 0
396 while tries < SBR_MAX_RETRIES {
397 let sfd: i64 = sys_openat_wr(spath, 0x1a4)
398 let cc: *i64 = sys_mmap(8*4) as *i64
399 cc[0] = compiler as i64; cc[1] = src as i64; cc[2] = 0
400 let rc_c: i64 = sbr_run(compiler, cc, envp, sfd, devnull)
401 sys_close(sfd)
402 asmbytes = sbr_filesize(spath)
403 if rc_c == 0 { if asmbytes > SBR_MIN_ASM_BYTES {
404 let aa2: *i64 = sys_mmap(8*4) as *i64
405 aa2[0] = asm_tool as i64; aa2[1] = spath as i64; aa2[2] = tmpelf as i64; aa2[3] = 0
406 rc_a = sbr_run(asm_tool, aa2, envp, 0 - 1, 0 - 1)
407 if rc_a == 0 { built = 1; tries = SBR_MAX_RETRIES }
408 } }
409 if tries != SBR_MAX_RETRIES { tries = tries + 1 }
410 }
411 if built == 0 {
412 if asmbytes <= SBR_MIN_ASM_BYTES {
413 // ROOT-CAUSE SURFACE (2026-07-06): the retry loop mutes nx_cc's stderr, so a plain source error
414 // (undefined function / parse / arg-count) hid behind this opaque "empty .s" (cost a ~15-build blind
415 // hunt). Re-run the compiler ONCE with stderr VISIBLE so the REAL diagnostic always prints. Same
416 // one-look answer as _offc/nx_ccdiag.elf, now automatic on every failure.
417 sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name); sbr_puts(": COMPILE-FAIL (empty .s) -- nx_cc says:\n---- nx_cc stderr ----\n" as *u8)
418 let dcc: *i64 = sys_mmap(8*4) as *i64
419 dcc[0] = compiler as i64; dcc[1] = src as i64; dcc[2] = 0
420 sbr_run(compiler, dcc, envp, devnull, 0 - 1) // stdout->devnull, stderr VISIBLE = the real cause
421 sbr_puts("----------------------\n(usually an undefined fn / missing import / parse error -- NOT a codegen crash; fix the source above)\n" as *u8)
422 sys_exit(SBR_COMPILE_FAIL); return SBR_COMPILE_FAIL
423 }
424 if rc_a > 128 {
425 sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name); sbr_puts(": NXASM-CRASHED sig=" as *u8); sbr_putn(rc_a - 128); sbr_puts(" (assembler died on this .s -> capacity/robustness bug)\n" as *u8)
426 sys_exit(SBR_ASM_FAIL); return SBR_ASM_FAIL
427 }
428 sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name); sbr_puts(": NXASM-FAIL rc=" as *u8); sbr_putn(rc_a)
429 sbr_puts(" after recompile-retries -- DETERMINISTIC (not the nondeterministic-truncation class). rc=102 = nxasm 'UNDEFINED label: main' = USUALLY a no-main LIBRARY built standalone -> run its _gate/importer (the one with main), NOT the lib. Else a real deterministic compiler/source miscompile.\n" as *u8)
430 sys_exit(SBR_ASM_FAIL); return SBR_ASM_FAIL
431 }
432
433 // CORRECTED 2026-06-25: the old comment here claimed the rc=6/102 "ctx-x-output-path" class was
434 // "non-reproducible / nxasm outpath codegen CLEAN". That was WRONG -- the real rc=102 is nxasm's pass-2
435 // "UNDEFINED label: main" (axc_label_resolve), i.e. nx_cc emitted a TRUNCATED .s missing `main`. It is the
436 // COMPILER truncation-nondeterminism, not the output path. The retry loop above now requires a clean assemble,
437 // so a truncated .s no longer escapes. tmpelf holds the good ELF -> atomic rename install (temp+rename kept
438 // for atomicity = never overwrite a running binary mid-write).
439 sys_renameat(tmpelf, elfpath)
440
441 // ---- LM-026 ROOT FIX: refresh the INSTALLED artifact if one exists -----------------------------
442 // Historical trap (cost a full session on the MMU rung): this runner builds to /tmp/<name>.sov.elf,
443 // but gates/organs fork _offc/<name>.elf -- so a rebuild left _offc STALE and source edits silently
444 // never took effect. Now, on a SUCCESSFUL build, if _offc/<name>.elf ALREADY EXISTS (i.e. <name> is
445 // an installed artifact that something forks), atomically refresh it from the fresh build. This is
446 // refresh-IF-PRESENT only: throwaway gate/probe builds (run straight from /tmp, no _offc twin) never
447 // get a spurious _offc entry. Atomic (write .sbrtmp + renameat) = no torn binary, retires LM-026 at
448 // the source. The reactive nx_offc_install guardrail stays as the belt-and-suspenders detector.
449 let offcpath: *u8 = sys_mmap(256); o = 0
450 o = sbr_cat(offcpath, o, "_offc/" as *u8); o = sbr_cat(offcpath, o, name); o = sbr_cat(offcpath, o, ".elf" as *u8); offcpath[o] = 0 as u8
451 let oex: i64 = sys_openat_rd(offcpath)
452 var do_install: i64 = 0
453 if oex >= 0 { sys_close(oex); do_install = 1 } // refresh-IF-PRESENT (LM-026)
454 if force_install == 1 { do_install = 1 } // --install: install even with NO twin (make a tool permanent)
455 // ⚠⚠TOOLCHAIN IS NEVER AUTO-INSTALLED (rule 26, added 2026-07-30 after seq1464; RESTORED in the
456 // 2026-08-03 fork merge -- the NAS branch had dropped this entire guard, so any successful build
457 // of a toolchain binary silently replaced the deployed one, canary-less, even under --build-only).
458 // MEASURED INCIDENT: buildroot/_offc/nx_cc_sovereign.elf was found LIVE at 520103 bytes -- the
459 // output of a rebuild made while buildroot was stale -- instead of the 543126 that had been
460 // deliberately promoted. The hub was therefore compiling EVERY organ with a compiler whose
461 // optimiser was 11712 source-bytes short, and its .s came out 16430B larger from identical
462 // sources. Nobody chose that; a build installed it.
463 //
464 // THE INVARIANT: refresh-IF-PRESENT is right for ordinary organs (it retires LM-026 staleness) and
465 // CATASTROPHIC for the toolchain, because the toolchain is what BUILT the thing being installed --
466 // a bad build then replaces the compiler that produced it and every subsequent build inherits the
467 // damage, with no promote, no canary and no .prev. That is a self-modifying build path.
468 // The toolchain has exactly ONE lawful update route: /api/promote_toolchain, which ELF-validates,
469 // banks .prev, chmod +x, CANARY-COMPILES AND RUNS, and auto-rolls-back. This guard makes that route
470 // the only one BY CONSTRUCTION rather than by convention.
471 // ★Applies to --install too: an explicit flag must not be able to brick the toolchain either.
472 if sbr_is_toolchain(name) == 1 {
473 if do_install == 1 {
474 sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name)
475 sbr_puts(": REFUSED to auto-install a TOOLCHAIN binary into _offc (rule 26 / seq1464).\n" as *u8)
476 sbr_puts(" The build artifact is staged and usable; promote it deliberately via\n" as *u8)
477 sbr_puts(" POST /api/promote_toolchain (ELF-validated, .prev-banked, canary-run, auto-rollback).\n" as *u8)
478 }
479 do_install = 0
480 }
481 if do_install == 1 {
482 let il: *i64 = sys_mmap(16) as *i64
483 let ib: *u8 = sys_read_file(elfpath, il)
484 if (ib as i64) != 0 {
485 let otmp: *u8 = sys_mmap(256); var oo: i64 = 0
486 oo = sbr_cat(otmp, oo, "_offc/" as *u8); oo = sbr_cat(otmp, oo, name); oo = sbr_cat(otmp, oo, ".elf.sbrtmp" as *u8); otmp[oo] = 0 as u8
487 let ofd: i64 = sys_openat_wr(otmp, 493) // 0755 = executable (works for refresh AND force-install-new)
488 if ofd >= 0 { sys_write(ofd, ib, il[0]); sys_close(ofd); sys_renameat(otmp, offcpath) }
489 if force_install == 1 { sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name); sbr_puts(": INSTALLED -> _offc/" as *u8); sbr_puts(name); sbr_puts(".elf (--install)\n" as *u8) }
490 }
491 }
492
493 // release the per-target build lock HERE -- artifacts are final (atomic renames done); the run
494 // phase below must not hold it (a daemon target would wedge every future rebuild of itself).
495 if lockfd >= 0 { sys_flock(lockfd, SYS_LOCK_UN); sys_close(lockfd) }
496
497 // ---- build-only mode: argv[2] starting "--b" (--build-only) SKIPS the run, so a sovereign
498 // bring-up/supervisor can build a dependency without triggering its side effects (e.g. a worker
499 // would otherwise start a live download). The build path above is byte-for-byte unchanged;
500 // this only short-circuits before the run. Default (no 2nd arg) behaves exactly as before. ----
501 // --build-only OR --install both SKIP the run (skip_run was set from argv[2] at the top).
502 if skip_run == 1 {
503 // ARTIFACT PATH IS PART OF THE OUTPUT CONTRACT (2026-07-30). --build-only used to print only
504 // "asm=NNNNB", so the caller was told the build SUCCEEDED but not WHERE the ELF landed -- and the
505 // header comments still said /tmp/<name>.sov.elf while the code has written _build/<name>.sov.elf
506 // since the flock change. Every caller therefore had to go hunting (measured: 3 shell round-trips
507 // to locate one artifact). A tool that produces a file MUST say where it put it; the run path
508 // below already prints elf=<path>, so build-only was the odd one out. Same line shape as the run
509 // branch so existing log scrapers see a superset, never a changed field (rule 19).
510 sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name); sbr_puts(": asm=" as *u8); sbr_putn(asmbytes)
511 sbr_puts("B SOVEREIGN build (nx_cc->nxasm_x86, no gcc, no run) elf=" as *u8); sbr_puts(elfpath)
512 sbr_puts("\n" as *u8)
513 sys_exit(SBR_OK); return SBR_OK
514 }
515
516 // ---- run the sovereign ELF WITH FORWARDED ARGS; report; exit with its code ----
517 // ARG-FORWARDING (2026-07-14 debt-eat): argv[fwd_start..argc) pass through to the built program, so a
518 // daemon/tool builds AND launches with its runtime args in one command (e.g. a cap-secret path). Before
519 // this, extra args were silently DROPPED -- caught live when the self-gated git server ran AUTH OFF.
520 let rr: *i64 = sys_mmap(8 * 20) as *i64
521 rr[0] = elfpath as i64
522 var ra: i64 = 1
523 var ai: i64 = fwd_start
524 while ai < argc { if ra < 19 { rr[ra] = argv[ai]; ra = ra + 1 } ai = ai + 1 }
525 rr[ra] = 0
526 let rc_r: i64 = sbr_run(elfpath, rr, envp, 0 - 1, 0 - 1)
527 sbr_puts("[nx_sov_build_run] " as *u8); sbr_puts(name); sbr_puts(": asm=" as *u8); sbr_putn(asmbytes)
528 sbr_puts("B SOVEREIGN(nx_cc->nxasm_x86, no gcc) elf=" as *u8); sbr_puts(elfpath)
529 sbr_puts(" run-exit=" as *u8); sbr_putn(rc_r); sbr_puts("\n" as *u8)
530 sys_exit(rc_r)
531 return rc_r
532}