code wiki / _hdl_build / nx_mgmt_cap_data_candidate_t186.nx
nx_mgmt_cap_data_candidate_t186.nx source
↩ module page · 2857 lines · 156099 B
1// nx_mgmt_data.nx -- the DATA / ADAPTER layer of the management plane (the OUTER ring; secondary adapters).
2// The ONLY layer that touches the outside world for STATE: it parses snapshot bytes, reads the data-driven
3// config allowlists, and drives the secondary adapters (the hostctl exec + the real-HTTP health probe). It has
4// NO knowledge of transport (no HTTP/socket/auth); the IO ring depends on IT, never the reverse (ports &
5// adapters / dependency inversion). Grounded in knowledge/library/arch_* (three-tier DATA tier, hexagonal
6// secondary adapters, loose coupling). Reuses the SOTA-gated nx_deploy_lib (validate/exec) + nx_http_health_lib
7// (probe) -- DRY. license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
10import "_hdl_build/nx_adnet_invoice.nx"
11import "_hdl_build/nx_adnet_creative.nx"
12import "nx_deploy_lib.nx"
13import "nx_http_health_lib.nx"
14import "nx_tool_run.nx" // seq1443: tr_run_capture_to -- the GATE-PROVEN bounded exec (see md_exec_gate_capture)
15import "_hdl_build/nx_staging_guard.nx" // sg_classify -- REFUSE a promote that walks the target backwards (staging hygiene)
16
17// Owned deployment target record for guarded installation. Eight columns are the
18// registry schema, not a maximum record/path size. Legacy resolver callers remain
19// unchanged until their output contracts are migrated.
20import "nx_file_install.nx"
21import "nx_cap_mint_candidate_t186.nx"
22const MD_TARGET_COLUMNS: i64=8
23struct NxDeployTargetRecord {
24 bytes: *u8,
25 allocation: i64,
26 name: *u8,
27 kind_text: *u8,
28 staged: *u8,
29 deploy: *u8,
30 health: *u8,
31 rollback: *u8,
32 live: *u8,
33 process: *u8,
34 stage: *u8,
35 code: i64,
36 line: i64,
37 conflicting_line: i64,
38}
39func md_target_init(out: *NxDeployTargetRecord) -> i64 {
40 let raw: *u8=out as *u8;var i: i64=0
41 while i<__size_of(NxDeployTargetRecord) { raw[i]=0 as u8;i=i+1 }
42 out.stage="target-input";out.code=FIO_EINVAL;return 0
43}
44func md_target_close(out: *NxDeployTargetRecord) -> i64 {
45 var rc: i64=0
46 if (out.bytes as i64)!=0 { rc=sys_munmap(out.bytes,out.allocation) }
47 md_target_init(out);return rc
48}
49func md_target_space(c: u8) -> i64 {
50 return (c==(32 as u8) || c==(9 as u8) || c==(13 as u8)) as i64
51}
52func md_target_field(out: *NxDeployTargetRecord,index: i64,value: *u8) -> i64 {
53 if index==0 { out.name=value }
54 if index==1 { out.kind_text=value }
55 if index==2 { out.staged=value }
56 if index==3 { out.deploy=value }
57 if index==4 { out.health=value }
58 if index==5 { out.rollback=value }
59 if index==6 { out.live=value }
60 if index==7 { out.process=value }
61 return 0
62}
63// Reads the complete caller-owned snapshot before selecting a unique name.
64// Missing/malformed/ambiguous records never fall back to compiled-in defaults.
65// Output owns a length-derived row copy; it never aliases caller snapshot bytes.
66// Initialize/close the result between uses; do not reinitialize a live result.
67func md_target_decode(snapshot: *u8,n: i64,name: *u8,name_length: i64,out: *NxDeployTargetRecord) -> i64 {
68 md_target_init(out)
69 if (snapshot as i64)==0 || n<0 || (name as i64)==0 || name_length<=0 { return out.code }
70 var i: i64=0
71 while i<name_length {
72 if (name[i] as i64)<=32 || name[i]==(127 as u8) { return out.code };i=i+1
73 }
74 var selected: i64=0-1;var selected_end: i64=0
75 var cursor: i64=0;var line: i64=1
76 while cursor<n {
77 let end: i64=md_eol(snapshot,n,cursor)
78 i=cursor
79 while i<end {
80 let ch: i64=snapshot[i] as i64
81 if ch==0 || ch==127 || (ch<32 && md_target_space(snapshot[i])==0) {
82 out.stage="registry-control-byte";out.code=FIO_EBADMSG;out.line=line;return out.code
83 }
84 i=i+1
85 }
86 var start: i64=cursor
87 while start<end && md_target_space(snapshot[start])==1 { start=start+1 }
88 if start<end && snapshot[start]!=(35 as u8) {
89 var token_end: i64=start
90 while token_end<end && md_target_space(snapshot[token_end])==0 { token_end=token_end+1 }
91 if md_slice_eq(snapshot,start,token_end-start,name,0,name_length)==1 {
92 if selected>=0 {
93 out.stage="duplicate-target";out.code=FIO_EEXIST;out.conflicting_line=line;return out.code
94 }
95 selected=start;selected_end=end;out.line=line
96 }
97 }
98 cursor=end+1;line=line+1
99 }
100 if selected<0 { out.stage="target-absent";out.code=FI_ENOENT;return out.code }
101 let length: i64=selected_end-selected
102 if length==0x7fffffffffffffff { out.stage="row-size-overflow";out.code=FIO_EINVAL;return out.code }
103 let copy: *u8=sys_mmap(length+1)
104 if (copy as i64)<0 { out.stage="row-allocation";out.code=copy as i64;return out.code }
105 fi_copy(copy,snapshot+selected,length);copy[length]=0 as u8
106 var fields: i64=0;i=0
107 while i<length {
108 while i<length && md_target_space(copy[i])==1 { copy[i]=0 as u8;i=i+1 }
109 if i<length {
110 if fields==MD_TARGET_COLUMNS { break }
111 md_target_field(out,fields,copy+i);fields=fields+1
112 while i<length && md_target_space(copy[i])==0 { i=i+1 }
113 }
114 }
115 var rc: i64=0;var stage: *u8="target-resolved"
116 if fields!=MD_TARGET_COLUMNS || i<length { rc=FIO_EBADMSG;stage="target-columns" }
117 if rc==0 && fi_path_valid(out.live)==0 { rc=FIO_EBADMSG;stage="target-live-path" }
118 if rc==0 { if md_streq(out.kind_text,"toolchain-v2")==1 { if md_streq(out.deploy,"-")!=1 || md_streq(out.health,"-")!=1 || md_streq(out.rollback,"-")!=1 { rc=FIO_EBADMSG;stage="versioned-target-actions" } } }
119 if rc!=0 {
120 let original_line: i64=out.line
121 sys_munmap(copy,length+1);md_target_init(out);out.line=original_line
122 } else { out.bytes=copy;out.allocation=length+1 }
123 out.stage=stage;out.code=rc;return rc
124}
125
126func md_target_read(path: *u8,snapshot_allowance: i64,name: *u8,name_length: i64,out: *NxDeployTargetRecord) -> i64 {
127 md_target_init(out)
128 if snapshot_allowance<=0 || (path as i64)==0 { return out.code }
129 let region: *NxFileReadRegion=sys_mmap(__size_of(NxFileReadRegion)) as *NxFileReadRegion
130 if (region as i64)<0 { out.stage="reader-allocation";out.code=region as i64;return out.code }
131 fio_region_init(region);var rc: i64=fio_region_open(path,region)
132 var stage: *u8=region.stage
133 let n: i64=region.total;var bytes: *u8=0 as *u8
134 if rc==0 && n>snapshot_allowance { rc=FIO_EINVAL;stage="snapshot-admission" }
135 if rc==0 && n==0 { rc=FI_ENOENT;stage="target-absent" }
136 if rc==0 {
137 bytes=sys_mmap(n)
138 if (bytes as i64)<0 { rc=bytes as i64;bytes=0 as *u8;stage="snapshot-allocation" }
139 }
140 if rc==0 {
141 let got: i64=fio_region_next(region,bytes,n)
142 if got!=n { rc=got;if rc>=0 { rc=FIO_EIO };stage=region.stage }
143 }
144 fio_region_close(region)
145 if rc==0 && region.code!=0 { rc=region.code;stage=region.stage }
146 if rc==0 { rc=md_target_decode(bytes,n,name,name_length,out) }
147 else { out.code=rc;out.stage=stage }
148 if (bytes as i64)!=0 { sys_munmap(bytes,n) }
149 sys_munmap(region as *u8,__size_of(NxFileReadRegion))
150 return rc
151}
152
153// One data-plane operation: resolve the requested registered target and apply the
154// caller-approved immutable intent. Authentication and approval belong to the
155// management boundary; merely supplying a digest does not confer authority.
156struct NxRegisteredInstallResult {
157 target: NxDeployTargetRecord,
158 install: NxFileInstallResult,
159 stage: *u8,
160 code: i64,
161}
162func md_registered_install_init(out: *NxRegisteredInstallResult) -> i64 {
163 md_target_init(&out.target)
164 out.install.stage="not-started";out.install.code=0;out.install.already_published=0
165 out.install.io.stage="not-started";out.install.io.code=0
166 out.install.io.written=0;out.install.io.close_code=0
167 fio_replace_init(&out.install.replacement)
168 out.stage="approval-input";out.code=FIO_EINVAL;return 0
169}
170func md_registered_install_close(out: *NxRegisteredInstallResult) -> i64 {
171 let rc: i64=md_target_close(&out.target)
172 md_registered_install_init(out);return rc
173}
174// Owns the resolved row until close, so reporting never relies on a later
175// registry reread. A failed resolver cannot dispatch installation or restart.
176// The caller accounts separately for snapshot, intent, scratch and result memory.
177func md_install_registered(registry: *u8,snapshot_allowance: i64,target: *u8,target_length: i64,intent: *u8,intent_allowance: i64,approved_intent_digest: *u8,scratch: *u8,scratch_bytes: i64,out: *NxRegisteredInstallResult) -> i64 {
178 md_registered_install_init(out)
179 if (approved_intent_digest as i64)==0 || fi_path_valid(intent)==0 || intent_allowance<=0 || (scratch as i64)==0 || scratch_bytes<=0 { return out.code }
180 out.stage="target-resolution"
181 out.code=md_target_read(registry,snapshot_allowance,target,target_length,&out.target)
182 if out.code!=0 { return out.code }
183 out.stage="artifact-installation"
184 out.code=fi_install_bound(intent,intent_allowance,scratch,scratch_bytes,out.target.live,approved_intent_digest,&out.install)
185 if out.code==0 { out.stage="artifact-installed-serving-unverified" }
186 return out.code
187}
188
189// ---- parse primitives over a buffer (slices, no null terminators) -----------------------------------
190func md_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
191
192// ASCII '2' -- the generation suffix on <live>.prev2. NAMED so the rotation carries no bare literal.
193const MD_CH_DIGIT_TWO: i64 = 50
194// THE ONE ROLLBACK-CHAIN ROTATION (2026-08-21). Rotate <prev> one generation deeper to <prev>2 so that a
195// SECOND promote of the same target cannot destroy the pre-change binary. Returns 1 if a generation was
196// rotated, 0 if there was nothing to rotate. Fail-safe: any failure leaves both slots exactly as found.
197//
198// MEASURED THIS SESSION: two promotes of ONE target left live and .prev BOTH holding new builds and the
199// original GONE -- and an older-generation binary CANNOT be rebuilt. The slot holds "the PREVIOUS
200// PROMOTE", never "the last known good", so the SECOND promote is the one that leaves you with no way
201// back. The only defence was the operator remembering to copy the artifact aside first, and
202// *A SAFETY PROPERTY THAT DEPENDS ON SOMEONE REMEMBERING IS AN ADOPTION GAP WITH EXTRA STEPS.
203//
204// *WHY A FUNCTION AND NOT N INLINE EDITS: this estate implements the live -> .prev bank FIVE TIMES
205// (nx_mgmt_data x3, nx_mgmt_api x1, nx_hostctl x1), found by TRACING the call chain rather than by
206// assuming -- the first two candidates I inspected were a staged-slot bank and a static-content promote,
207// neither of which is this. EVERY COPY OF A PATTERN IS A PLACE A FIX CAN FAIL TO ARRIVE, which
208// nx_hostctl.nx says in those words about its own 27 deploy twins. All FOUR copies inside this daemon
209// now call THIS. nx_hostctl is a SEPARATE BINARY and carries its own twin -- NAMED here so the next
210// reader inherits the fact instead of re-deriving it.
211// Bounded deliberately: exactly one extra file per target, no directory, no new import.
212func md_rotate_prev(prevp: *u8) -> i64 {
213 let pf: i64 = sys_openat_rd(prevp)
214 if pf < 0 { return 0 }
215 sys_close(pf)
216 let p2: *u8 = sys_mmap(256)
217 var i: i64 = 0
218 while prevp[i] != (0 as u8) { p2[i] = prevp[i]; i = i + 1 }
219 p2[i] = MD_CH_DIGIT_TWO as u8
220 p2[i + 1] = 0 as u8
221 if sys_renameat(prevp, p2) != 0 { return 0 }
222 return 1
223}
224
225func md_cat_slice(d: *u8, o: i64, src: *u8, off: i64, len: i64) -> i64 {
226 var i: i64 = 0
227 while i < len { d[o] = src[off + i]; o = o + 1; i = i + 1 }
228 return o
229}
230
231func md_tok_eq(src: *u8, off: i64, len: i64, s: *u8) -> i64 {
232 let sl: i64 = md_len(s)
233 if sl != len { return 0 }
234 var i: i64 = 0
235 while i < len { if (src[off + i] as i64) != (s[i] as i64) { return 0 } i = i + 1 }
236 return 1
237}
238
239func md_slice_atoi(src: *u8, off: i64, len: i64) -> i64 {
240 var v: i64 = 0
241 var i: i64 = 0
242 while i < len {
243 let c: i64 = src[off + i] as i64
244 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
245 i = i + 1
246 }
247 return v
248}
249
250// index of '\n' at-or-after start, or n.
251func md_eol(snap: *u8, n: i64, start: i64) -> i64 {
252 var i: i64 = start
253 var f: i64 = 0
254 while f == 0 {
255 if i >= n { f = 1 } else { if (snap[i] as i64) == 10 { f = 1 } else { i = i + 1 } }
256 }
257 return i
258}
259
260// split snap[ls..le) on spaces into up-to-maxf (offs,lens) absolute slices. returns field count.
261func md_split(snap: *u8, ls: i64, le: i64, offs: *i64, lens: *i64, maxf: i64) -> i64 {
262 var nf: i64 = 0
263 var i: i64 = ls
264 while i < le {
265 var sk: i64 = 1
266 while sk == 1 { if i >= le { sk = 0 } else { if (snap[i] as i64) == 32 { i = i + 1 } else { sk = 0 } } }
267 if i < le {
268 let st: i64 = i
269 var sc: i64 = 1
270 while sc == 1 { if i >= le { sc = 0 } else { if (snap[i] as i64) == 32 { sc = 0 } else { i = i + 1 } } }
271 if nf < maxf { offs[nf] = st; lens[nf] = i - st; nf = nf + 1 }
272 }
273 }
274 return nf
275}
276
277func md_slice_eq(a: *u8, ao: i64, al: i64, b: *u8, bo: i64, bl: i64) -> i64 {
278 if al != bl { return 0 }
279 var i: i64 = 0
280 while i < al { if (a[ao + i] as i64) != (b[bo + i] as i64) { return 0 } i = i + 1 }
281 return 1
282}
283
284func md_copy_slice_z(dst: *u8, src: *u8, off: i64, len: i64, cap: i64) -> i64 {
285 var n: i64 = len
286 if n > cap - 1 { n = cap - 1 }
287 var i: i64 = 0
288 while i < n { dst[i] = src[off + i]; i = i + 1 }
289 dst[n] = 0 as u8
290 return n
291}
292
293// ---- file / config adapters -------------------------------------------------------------------------
294func md_read_file(path: *u8, szbox: *i64) -> *u8 {
295 szbox[0] = 0
296 return sys_read_file(path, szbox)
297}
298
299// ---- ADNET BILLING (debt 1785513943): the outside-world half of the invoice route ----------------
300// Lives HERE, in the DATA ring, not in nx_mgmt_api: that ring owns transport only (see the api header).
301// The money math stays in nx_adnet_bill and the join in nx_adnet_invoice -- this function is purely the
302// file access those two are deliberately free of.
303// FAIL-CLOSED: an unreadable INVENTORY or RATE CARD returns 0 (the route answers 503) rather than an
304// empty invoice -- "no rows" and "could not read the rows" must never look alike to a biller. An absent
305// JOURNAL is different and legitimate: it means zero events, so it degrades to an empty count.
306const MD_ADNET_INV: *u8 = "/volume1/homes/elderwesto/nishihost/sites/nishifamily/synth/adnet_inventory.txt" as *u8
307const MD_ADNET_RATES: *u8 = "/volume1/homes/elderwesto/nishihost/knowledge/status/adnet_rates.conf" as *u8
308const MD_ADNET_SERVED: *u8 = "/volume1/homes/elderwesto/nishihost/adnet_impressions.log" as *u8
309const MD_ADNET_CLICKS: *u8 = "/volume1/homes/elderwesto/nishihost/adnet_clicks.log" as *u8
310const MD_ADNET_VIEW: *u8 = "/volume1/homes/elderwesto/nishihost/knowledge/status/adnet_viewable.log" as *u8
311
312// ---- ADNET CREATIVE INTAKE (debt 1785512202): the outside-world half of the upload route --------
313// Validation and naming live in nx_adnet_creative (pure, gated 13/13); this is only the file write.
314// CONTENT-ADDRESSED, so the write is IDEMPOTENT by construction (rule 10): re-uploading identical bytes
315// lands on the identical path. No overwrite hazard, no version skew, and the URL doubles as a cache key.
316// Returns the ACR_* verdict; urlout receives the first-party url ONLY on ACR_OK.
317const MD_ADNET_SYNTH: *u8 = "/volume1/homes/elderwesto/nishihost/sites/nishifamily/synth/" as *u8
318
319func md_adnet_creative_store(b: *u8, n: i64, urlout: *u8, urlcap: i64) -> i64 {
320 urlout[0] = 0 as u8
321 let v: i64 = acr_validate(b, n)
322 if v != ACR_OK { return v }
323 let nm: *u8 = sys_mmap(64)
324 if acr_name(b, n, nm, 64) == 0 { return ACR_NOT_PNG }
325 let path: *u8 = sys_mmap(512)
326 var o: i64 = 0
327 var i: i64 = 0
328 while MD_ADNET_SYNTH[i] != (0 as u8) { path[o] = MD_ADNET_SYNTH[i]; o = o + 1; i = i + 1 }
329 i = 0
330 while nm[i] != (0 as u8) { path[o] = nm[i]; o = o + 1; i = i + 1 }
331 path[o] = 0 as u8
332 let fd: i64 = sys_openat_wr(path, 420)
333 if fd < 0 { return 0 - 1 }
334 let w: i64 = sys_write(fd, b, n)
335 sys_close(fd)
336 if w != n { return 0 - 1 }
337 if acr_url(b, n, urlout, urlcap) == 0 { return 0 - 1 }
338 return ACR_OK
339}
340
341func md_adnet_invoice_report(out: *u8, cap: i64) -> i64 {
342 let bx: *i64 = sys_mmap(16) as *i64
343 let inv: *u8 = md_read_file(MD_ADNET_INV, bx)
344 if (inv as i64) == 0 { return 0 }
345 let iln: i64 = bx[0]
346 let bx2: *i64 = sys_mmap(16) as *i64
347 let rates: *u8 = md_read_file(MD_ADNET_RATES, bx2)
348 if (rates as i64) == 0 { return 0 }
349 let rln: i64 = bx2[0]
350 let bx3: *i64 = sys_mmap(16) as *i64
351 var served: *u8 = md_read_file(MD_ADNET_SERVED, bx3)
352 var sln: i64 = bx3[0]
353 if (served as i64) == 0 { served = "" as *u8; sln = 0 }
354 let bx4: *i64 = sys_mmap(16) as *i64
355 var view: *u8 = md_read_file(MD_ADNET_VIEW, bx4)
356 var vln: i64 = bx4[0]
357 if (view as i64) == 0 { view = "" as *u8; vln = 0 }
358 let bx5: *i64 = sys_mmap(16) as *i64
359 var clk: *u8 = md_read_file(MD_ADNET_CLICKS, bx5)
360 var cln: i64 = bx5[0]
361 if (clk as i64) == 0 { clk = "" as *u8; cln = 0 }
362 return ainv_report(inv, iln, rates, rln, served, sln, view, vln, clk, cln, out, cap)
363}
364
365// resolve a deploy target NAME (slice nm[off..off+len)) against the allowlist file -> kind + src/sub/url
366// copied null-terminated into caller buffers. 1 = resolved, 0 = unknown (fail-closed). '#' = comment line.
367// COMPILED-IN fallback for the deploy plane's OWN bootstrap targets, so a freshly-deployed mgmt API can deploy
368// mgmtapi/hostctl/torrentstack off-LAN WITHOUT first getting an updated deploy_targets.conf onto the NAS (the
369// file isn't upload-able off-LAN). The file (md_resolve_target) still WINS when present -> it stays the
370// extensible SSOT; this only covers the plane's self-knowledge. Each: kind, src(staged .new), sub(promote), url(health), rb(rollback).
371func md_builtin_target(nm: *u8, off: i64, len: i64, kindb: *i64, srcbuf: *u8, subbuf: *u8, urlbuf: *u8, rbbuf: *u8) -> i64 {
372 if md_slice_eq(nm, off, len, "mgmtapi" as *u8, 0, 7) == 1 {
373 // health = LOCAL TCP-connect to the mgmt API's own port :18098 (robust). The old "https://.../api/" HTTP
374 // probe ran nx_research_fetch from the NAS -> nishifamily.com, which hits DSM's loopback nginx (coin-flip)
375 // -> flaky false-rollback. The new mgmt respawns on :18098 within the 30s retry window -> port-connect greens.
376 kindb[0]=0; md_copy_slice_z(srcbuf, "nx_mgmt_api.elf.new" as *u8, 0, 19, 512); md_copy_slice_z(subbuf, "mgmtdeploy" as *u8, 0, 10, 64); md_copy_slice_z(urlbuf, "port:18098" as *u8, 0, 10, 256); md_copy_slice_z(rbbuf, "mgmtrollback" as *u8, 0, 12, 64); return 1 }
377 if md_slice_eq(nm, off, len, "hostctl" as *u8, 0, 7) == 1 {
378 // health = LOCAL TCP-connect to sites.elf :8443 (robust, like torrentstack). The old "https://.../api/"
379 // HTTP-fetch probe needed nx_research_fetch+CA from the mgmt cwd + hit the :443 DSM-nginx coin-flip + raced
380 // the self-swap -> it ALWAYS false-rolled-back (why no hostctl deploy landed since 07-09). sites.elf stays
381 // up across a self-swap (only the supervisor re-execs), so the port-connect greens reliably.
382 kindb[0]=0; md_copy_slice_z(srcbuf, "nx_hostctl.new" as *u8, 0, 14, 512); md_copy_slice_z(subbuf, "selfswap" as *u8, 0, 8, 64); md_copy_slice_z(urlbuf, "port:8443" as *u8, 0, 9, 256); md_copy_slice_z(rbbuf, "superrollback" as *u8, 0, 13, 64); return 1 }
383 if md_slice_eq(nm, off, len, "torrentstack" as *u8, 0, 12) == 1 {
384 kindb[0]=0; md_copy_slice_z(srcbuf, "nx_torrent_daemon.sov.elf.new" as *u8, 0, 29, 512); md_copy_slice_z(subbuf, "torrentdeploy" as *u8, 0, 13, 64); md_copy_slice_z(urlbuf, "port:8097" as *u8, 0, 9, 256); md_copy_slice_z(rbbuf, "torrentrollback" as *u8, 0, 15, 64); return 1 }
385 // ethical CLEAN-SERVE daemon (:8102, /clean) -- first-class builtin so it deploys purely over the API (no NAS
386 // deploy_targets.conf write). hostctl cleanservedeploy promotes the .new + guard respawns; health = TCP :8102.
387 if md_slice_eq(nm, off, len, "cleanserve" as *u8, 0, 10) == 1 {
388 kindb[0]=0; md_copy_slice_z(srcbuf, "nx_clean_serve_daemon.elf.new" as *u8, 0, 29, 512); md_copy_slice_z(subbuf, "cleanservedeploy" as *u8, 0, 16, 64); md_copy_slice_z(urlbuf, "port:8102" as *u8, 0, 9, 256); md_copy_slice_z(rbbuf, "cleanserverollback" as *u8, 0, 18, 64); return 1 }
389 // DOCPORTAL admin daemon (:18456, /search + /doc + /api) -- first-class builtin so the SEARCH daemon deploys
390 // purely over the API (no more manual .sov.elf.new swap). hostctl docportaldeploy promotes the .sov.elf.new the
391 // build stages + guard respawns; health = local TCP :18456; rollback = docportalrollback (.prev -> live).
392 if md_slice_eq(nm, off, len, "docportal" as *u8, 0, 9) == 1 {
393 kindb[0]=0; md_copy_slice_z(srcbuf, "nx_docportal_admin_daemon.sov.elf.new" as *u8, 0, 37, 512); md_copy_slice_z(subbuf, "docportaldeploy" as *u8, 0, 15, 64); md_copy_slice_z(urlbuf, "port:18456" as *u8, 0, 10, 256); md_copy_slice_z(rbbuf, "docportalrollback" as *u8, 0, 17, 64); return 1 }
394 return 0
395}
396func md_resolve_target(cfgpath: *u8, nm: *u8, off: i64, len: i64, kindb: *i64, srcbuf: *u8, subbuf: *u8, urlbuf: *u8, rbbuf: *u8) -> i64 {
397 let szp: *i64 = sys_mmap(16) as *i64
398 var buf: *u8 = md_read_file(cfgpath, szp)
399 // primary path (knowledge/hosting/, the root-owned data plane) ABSENT -> fall back to the operator-writable
400 // bootstrap conf in the daemon cwd (nishihost/deploy_targets.conf). knowledge/ is root-owned (the root mgmt
401 // daemon created it), so the elderwesto bootstrap that REGISTERS deploy targets can only write the cwd -- this
402 // fallback is what lets a new target (e.g. relate) be registered WITHOUT root. Primary still WINS when present.
403 if (buf as i64) == 0 { buf = md_read_file("deploy_targets.conf" as *u8, szp) }
404 // both configs ABSENT -> still honor the compiled-in bootstrap targets, else the whole deploy plane is dead
405 // off-LAN when the NAS lacks the files (the live-400 that caught this).
406 if (buf as i64) == 0 { return md_builtin_target(nm, off, len, kindb, srcbuf, subbuf, urlbuf, rbbuf) }
407 let n: i64 = szp[0]
408 let offs: *i64 = sys_mmap(64) as *i64
409 let lens: *i64 = sys_mmap(64) as *i64
410 var cur: i64 = 0
411 while cur < n {
412 let le: i64 = md_eol(buf, n, cur)
413 var isc: i64 = 0
414 if le > cur { if (buf[cur] as i64) == 35 { isc = 1 } }
415 if isc == 0 {
416 let nf: i64 = md_split(buf, cur, le, offs, lens, 8)
417 if nf >= 5 {
418 if md_slice_eq(buf, offs[0], lens[0], nm, off, len) == 1 {
419 // Versioned-only rows cannot fall through to legacy hostctl action execution.
420 if md_slice_eq(buf,offs[1],lens[1],"toolchain-v2",0,12)==1 { return 2 }
421 kindb[0] = md_slice_atoi(buf, offs[1], lens[1])
422 md_copy_slice_z(srcbuf, buf, offs[2], lens[2], 512)
423 md_copy_slice_z(subbuf, buf, offs[3], lens[3], 64)
424 md_copy_slice_z(urlbuf, buf, offs[4], lens[4], 256)
425 // OPTIONAL 6th field = per-target rollback sub (generalized deploy: a torrent target must
426 // roll back the TORRENT binary, not sites.elf). Absent (5-field legacy rows) -> "rollback".
427 if nf >= 6 { md_copy_slice_z(rbbuf, buf, offs[5], lens[5], 64) } else { md_copy_slice_z(rbbuf, "rollback" as *u8, 0, 8, 64) }
428 return 1
429 }
430 }
431 }
432 cur = le + 1
433 }
434 // not in the file -> try the compiled-in bootstrap targets (off-LAN self-enable). Fail-closed if neither.
435 return md_builtin_target(nm, off, len, kindb, srcbuf, subbuf, urlbuf, rbbuf)
436}
437
438// FAIL-CLOSED allowlist of artifact names that /api/upload may STAGE (write <name>.upload -> <name>.new). These
439// are the deployable binaries the on-NAS supervisor promotes from *.new (HC_*_NEW in nx_hostctl). 1 = allowed,
440// 0 = refused (unknown target -> 400, NOTHING written). Names checked as a slice (nm[off..off+len)) so the caller
441// can hand a query-string slice without copying. Data lives HERE (the DATA ring), not buried in the transport layer.
442// NOTE the deliberate absence of directory separators in every entry -- an upload target is a BARE basename, so a
443// caller can never traverse ('/' or '..' would fail every md_slice_eq below), which keeps the staging write pinned
444// to the mgmt daemon's cwd by construction (defense-in-depth over the allowlist itself).
445func md_upload_target_ok(nm: *u8, off: i64, len: i64) -> i64 {
446 if md_slice_eq(nm, off, len, "nx_mgmt_api.elf" as *u8, 0, 15) == 1 { return 1 }
447 if md_slice_eq(nm, off, len, "sites.elf" as *u8, 0, 9) == 1 { return 1 }
448 if md_slice_eq(nm, off, len, "nx_gallery_serve.elf" as *u8, 0, 20) == 1 { return 1 }
449 if md_slice_eq(nm, off, len, "nx_gallery_gateway.elf" as *u8, 0, 22) == 1 { return 1 }
450 if md_slice_eq(nm, off, len, "nx_docportal_admin_daemon.elf" as *u8, 0, 29) == 1 { return 1 }
451 if md_slice_eq(nm, off, len, "nx_hostctl" as *u8, 0, 10) == 1 { return 1 }
452 if md_slice_eq(nm, off, len, "nx_wiki_gw.elf" as *u8, 0, 14) == 1 { return 1 }
453 if md_slice_eq(nm, off, len, "nx_hub_gw.elf" as *u8, 0, 13) == 1 { return 1 }
454 if md_slice_eq(nm, off, len, "nx_torrent_gw.elf" as *u8, 0, 17) == 1 { return 1 }
455 // P1 off-LAN parity: the torrent STACK binaries (deployed cross-dir into /volume1/ai/torrent/ by the
456 // torrentdeploy hostctl sub). Staged as <name>.new in nishihost cwd like every other target.
457 if md_slice_eq(nm, off, len, "nx_torrent_daemon.sov.elf" as *u8, 0, 25) == 1 { return 1 }
458 if md_slice_eq(nm, off, len, "nx_torrent_seedeval.elf" as *u8, 0, 23) == 1 { return 1 }
459 // build-over-API: the tree-pack primitive elf + the source-tree blob (unpacked by /api/unpack via nx_treepack).
460 if md_slice_eq(nm, off, len, "nx_treepack.elf" as *u8, 0, 15) == 1 { return 1 }
461 if md_slice_eq(nm, off, len, "buildsrc.pack" as *u8, 0, 13) == 1 { return 1 }
462 if md_slice_eq(nm, off, len, "buildknow.pack" as *u8, 0, 14) == 1 { return 1 }
463 // /api/compare server-side regen: the hub generator elf (exec'd by md_cmp_regen; updatable over the API).
464 if md_slice_eq(nm, off, len, "nx_swcompare_hub.elf" as *u8, 0, 20) == 1 { return 1 }
465 // /api/compare/publish staging slot: page bytes arrive chunked here, then publish pins them by sha256.
466 if md_slice_eq(nm, off, len, "compare.page" as *u8, 0, 12) == 1 { return 1 }
467 // the Relationship OS daemon (binds loopback :8027; conf row `relate` promotes it once hostctl ships relatedeploy).
468 if md_slice_eq(nm, off, len, "nx_relate_daemon.elf" as *u8, 0, 20) == 1 { return 1 }
469 // the site-visuals editor (loopback :18466; cut over API-pure via /api/restart service=siteedit which
470 // promotes the staged .new -> the FULL editor deploy loop is upload+restart, zero ssh).
471 if md_slice_eq(nm, off, len, "nx_siteedit_daemon.elf" as *u8, 0, 22) == 1 { return 1 }
472 // seq1433 HALF-WIRED DEPLOY LOOP FIXED: md_direct_restart_ok mapped service=toolsapi -> nx_tools_api_serve.elf
473 // and hc_restart_ok/hc_guard_tapi allowed+respawned it, but there was NO upload row -- so the staging slot
474 // nx_tools_api_serve.elf.new could never be written over the API and /api/restart toolsapi could only ever
475 // re-promote a STALE artifact. A restart verb without a staging slot is not a deploy loop. This is the daemon
476 // that gates EVERY agent capability = the one binary the ecosystem could not update API-first (cf. galxgw seq1049).
477 if md_slice_eq(nm, off, len, "nx_tools_api_serve.elf" as *u8, 0, 22) == 1 { return 1 }
478 // the Nishi Pulse survey/insights daemon (:8031, cron-reconciled; /api/restart service=survey promotes
479 // the staged .new -> the survey deploy loop is upload+restart, zero ssh/scp).
480 if md_slice_eq(nm, off, len, "nx_survey_daemon.elf" as *u8, 0, 20) == 1 { return 1 }
481 // the ETHICAL CLEAN-SERVE daemon (:8102, /clean -- neutralize attacks + PRESERVE safe ads + safety receipt;
482 // SSRF-guarded public fetch proxy). Deploy loop = /api/upload + /api/deploy target=cleanserve (hostctl supervise).
483 if md_slice_eq(nm, off, len, "nx_clean_serve_daemon.elf" as *u8, 0, 25) == 1 { return 1 }
484 // the Nishi Office daemon (:8030, cron-reconciled) + its client JS. Deploy loop = /api/upload + /api/restart
485 // service=office (daemon: promote .new + kill -> nx_office_reconcile respawns) / officejs (JS: promote only,
486 // the daemon reads office_app.js per-request). Zero ssh -- matches the survey pattern.
487 if md_slice_eq(nm, off, len, "nx_office_daemon.elf" as *u8, 0, 20) == 1 { return 1 }
488 if md_slice_eq(nm, off, len, "office_app.js" as *u8, 0, 13) == 1 { return 1 }
489 // THE BUILD TOOLCHAIN ITSELF (seq891/903). Staged as <name>.new in nishihost cwd like every other
490 // target, then promoted into buildroot/_offc by /api/promote_toolchain -- which validates the ELF,
491 // banks .prev, chmod +x, CANARY-COMPILES and auto-rolls-back. Uploading merely STAGES; it can never
492 // touch the live compiler, so these rows are safe on their own. Closes the gap where the ecosystem
493 // could deploy every service over its own API but not the compiler that builds them.
494 if md_slice_eq(nm, off, len, "nx_cc_sovereign.elf" as *u8, 0, 19) == 1 { return 1 }
495 if md_slice_eq(nm, off, len, "nxasm_x86_main.elf" as *u8, 0, 18) == 1 { return 1 }
496 if md_slice_eq(nm, off, len, "nx_sov_build_run.elf" as *u8, 0, 20) == 1 { return 1 }
497 return 0
498}
499
500// ---- /api/unpack: resolve a fail-closed unpack destination (dest-key -> staged .pack + abs NAS dir) ----------
501// NEVER-BRICK (#26): only allowlisted dest keys resolve; an unknown key -> 400, nothing written. Each key maps to
502// the STAGED pack (<key>.pack.new, from /api/upload) + a FIXED abs dir under nishihost (nx_treepack writes only
503// under it). Extend by adding a row. `buildsrc` = the runtime source tree for build-over-API.
504func md_unpack_resolve(nm: *u8, off: i64, len: i64, packbuf: *u8, destbuf: *u8) -> i64 {
505 if md_slice_eq(nm, off, len, "buildsrc" as *u8, 0, 8) == 1 {
506 md_copy_slice_z(packbuf, "buildsrc.pack.new" as *u8, 0, 17, 128)
507 md_copy_slice_z(destbuf, "/volume1/homes/elderwesto/nishihost/buildroot/runtime" as *u8, 0, 53, 256)
508 return 1
509 }
510 // `buildknow` = buildroot/knowledge DATA ring (2026-08-05, debt 1785937893): compare .q/.axes corpus banks
511 // and other knowledge data the buildroot-CWD generators (gapmap frontier) read; same staged-pack discipline.
512 if md_slice_eq(nm, off, len, "buildknow" as *u8, 0, 9) == 1 {
513 md_copy_slice_z(packbuf, "buildknow.pack.new" as *u8, 0, 18, 128)
514 md_copy_slice_z(destbuf, "/volume1/homes/elderwesto/nishihost/buildroot/knowledge" as *u8, 0, 55, 256)
515 return 1
516 }
517 return 0
518}
519// fork+exec the on-NAS nx_treepack (unpack mode) with (packpath, destpath); capture stdout -> outpath; exit code.
520func md_exec_treepack(packpath: *u8, destpath: *u8, outpath: *u8) -> i64 {
521 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_treepack.elf" as *u8
522 let args: *i64 = sys_mmap(32) as *i64
523 args[0] = "unpack" as *u8 as i64
524 args[1] = packpath as i64
525 args[2] = destpath as i64
526 return dep_run_capture(helf, args, 3, outpath)
527}
528
529// ---- CONTENT-PUBLISH namespace (publish-from-anywhere for STATIC site files, 2026-07-06) ------------------
530// A content target is `sites/nishifamily/synth/<basename>`: FIXED directory prefix (extend = add a prefix row
531// here, data-ring) + strict basename charset [a-z0-9_.-] (first char alphanumeric, no ".." run, bounded) +
532// extension in {.html, .png, .stl}. '/' is impossible inside the basename by charset, and the prefix is fixed,
533// so path traversal is impossible BY CONSTRUCTION. Services (.elf) stay on md_upload_target_ok + /api/deploy
534// (health-checked promotion); this namespace is static files promoted by /api/promote_content (atomic
535// .prev-backed swap -- no health-check needed, and it can never touch a binary or leave the site down).
536func md_content_ext_ok(nm: *u8, off: i64, len: i64) -> i64 {
537 if len > 5 {
538 var m: i64 = 1
539 let e1: *u8 = ".html" as *u8
540 var i: i64 = 0
541 while i < 5 { if (nm[off + len - 5 + i] as i64) != (e1[i] as i64) { m = 0; i = 5 } else { i = i + 1 } }
542 if m == 1 { return 1 }
543 }
544 if len > 4 {
545 var m2: i64 = 1
546 let e2: *u8 = ".png" as *u8
547 var j: i64 = 0
548 while j < 4 { if (nm[off + len - 4 + j] as i64) != (e2[j] as i64) { m2 = 0; j = 4 } else { j = j + 1 } }
549 if m2 == 1 { return 1 }
550 var m3: i64 = 1
551 let e3: *u8 = ".stl" as *u8
552 var k: i64 = 0
553 while k < 4 { if (nm[off + len - 4 + k] as i64) != (e3[k] as i64) { m3 = 0; k = 4 } else { k = k + 1 } }
554 if m3 == 1 { return 1 }
555 // whole-site static set (site-factory publish): the self-emitted sitemap.xml + robots.txt
556 var m4: i64 = 1
557 let e4: *u8 = ".xml" as *u8
558 var k4: i64 = 0
559 while k4 < 4 { if (nm[off + len - 4 + k4] as i64) != (e4[k4] as i64) { m4 = 0; k4 = 4 } else { k4 = k4 + 1 } }
560 if m4 == 1 { return 1 }
561 var m5: i64 = 1
562 let e5: *u8 = ".txt" as *u8
563 var k5: i64 = 0
564 while k5 < 4 { if (nm[off + len - 4 + k5] as i64) != (e5[k5] as i64) { m5 = 0; k5 = 4 } else { k5 = k5 + 1 } }
565 if m5 == 1 { return 1 }
566 }
567 // the sovereign video-client set (2026-07-11): app.v2.js + nx_video_client.wasm ride /api/upload ->
568 // /api/promote_content like every other static file (retires the nx_aw_send ssh push). Same trust rank
569 // as .html (which can carry <script> anyway); binaries (.elf) stay OUT of this namespace by construction.
570 if len > 3 {
571 var m6: i64 = 1
572 let e6: *u8 = ".js" as *u8
573 var k6: i64 = 0
574 while k6 < 3 { if (nm[off + len - 3 + k6] as i64) != (e6[k6] as i64) { m6 = 0; k6 = 3 } else { k6 = k6 + 1 } }
575 if m6 == 1 { return 1 }
576 }
577 if len > 5 {
578 var m7: i64 = 1
579 let e7: *u8 = ".wasm" as *u8
580 var k7: i64 = 0
581 while k7 < 5 { if (nm[off + len - 5 + k7] as i64) != (e7[k7] as i64) { m7 = 0; k7 = 5 } else { k7 = k7 + 1 } }
582 if m7 == 1 { return 1 }
583 }
584 // the EVIDENCE workstream (2026-07-16): every published evidence run carries api.json machine detail
585 // beside its index.html (dashboards speak plain english; machines get JSON). Same trust rank as .txt.
586 if len > 5 {
587 var m8: i64 = 1
588 let e8: *u8 = ".json" as *u8
589 var k8: i64 = 0
590 while k8 < 5 { if (nm[off + len - 5 + k8] as i64) != (e8[k8] as i64) { m8 = 0; k8 = 5 } else { k8 = k8 + 1 } }
591 if m8 == 1 { return 1 }
592 }
593 return 0
594}
595// prefix TABLE (the data ring this namespace was designed to grow by): returns the matched prefix length,
596// or -1. Each row is a FIXED site subdirectory; extend = add a row.
597func md_content_pfx(nm: *u8, off: i64, len: i64) -> i64 {
598 let p1: *u8 = "sites/nishifamily/synth/" as *u8
599 let l1: i64 = 24
600 if len > l1 {
601 var i: i64 = 0
602 var m: i64 = 1
603 while i < l1 { if (nm[off + i] as i64) != (p1[i] as i64) { m = 0; i = l1 } else { i = i + 1 } }
604 if m == 1 { return l1 }
605 }
606 let p2: *u8 = "sites/nishifamily/swgpu/" as *u8
607 let l2: i64 = 24
608 if len > l2 {
609 var i2: i64 = 0
610 var m2: i64 = 1
611 while i2 < l2 { if (nm[off + i2] as i64) != (p2[i2] as i64) { m2 = 0; i2 = l2 } else { i2 = i2 + 1 } }
612 if m2 == 1 { return l2 }
613 }
614 // the SITE-FACTORY showcase (generated archetype gallery) -- publishes via upload+promote_content
615 let p3: *u8 = "sites/nishifamily/factory/" as *u8
616 let l3: i64 = 26
617 if len > l3 {
618 var i3: i64 = 0
619 var m3: i64 = 1
620 while i3 < l3 { if (nm[off + i3] as i64) != (p3[i3] as i64) { m3 = 0; i3 = l3 } else { i3 = i3 + 1 } }
621 if m3 == 1 { return l3 }
622 }
623 // the SOVEREIGN-INFINIGEN showcases (/world, /gsplat) -- retires the flaky ssh-cat push (2026-07-09):
624 // publish = /api/upload (chunked+staged) -> /api/promote_content (atomic .prev-backed swap)
625 let p4: *u8 = "sites/nishifamily/world/" as *u8
626 let l4: i64 = 24
627 if len > l4 {
628 var i4: i64 = 0
629 var m4: i64 = 1
630 while i4 < l4 { if (nm[off + i4] as i64) != (p4[i4] as i64) { m4 = 0; i4 = l4 } else { i4 = i4 + 1 } }
631 if m4 == 1 { return l4 }
632 }
633 let p5: *u8 = "sites/nishifamily/gsplat/" as *u8
634 let l5: i64 = 25
635 if len > l5 {
636 var i5: i64 = 0
637 var m5: i64 = 1
638 while i5 < l5 { if (nm[off + i5] as i64) != (p5[i5] as i64) { m5 = 0; i5 = l5 } else { i5 = i5 + 1 } }
639 if m5 == 1 { return l5 }
640 }
641 // WHOLESALE-emitted multi-page sites (site-factory R-SITESHAPE): subdir paths allowed under this
642 // prefix via the guarded '/' rule in md_content_target_ok (never doubled, ".." runs still refused).
643 let p6: *u8 = "sites/nishifamily/wholesale/" as *u8
644 let l6: i64 = 28
645 if len > l6 {
646 var i6: i64 = 0
647 var m6: i64 = 1
648 while i6 < l6 { if (nm[off + i6] as i64) != (p6[i6] as i64) { m6 = 0; i6 = l6 } else { i6 = i6 + 1 } }
649 if m6 == 1 { return l6 }
650 }
651 // the public generate-UI over nx_gen (R10 of the Infinigen ladder)
652 let p7: *u8 = "sites/nishifamily/generate/" as *u8
653 let l7: i64 = 27
654 if len > l7 {
655 var i7: i64 = 0
656 var m7: i64 = 1
657 while i7 < l7 { if (nm[off + i7] as i64) != (p7[i7] as i64) { m7 = 0; i7 = l7 } else { i7 = i7 + 1 } }
658 if m7 == 1 { return l7 }
659 }
660 // the sovereign VIDEO CODEC client set (2026-07-11): index.html + app.v2.js + nx_video_client.wasm +
661 // ver.txt. Retires the last ssh (nx_aw_send) in the codec ship loop -- deploy8XX becomes /api/upload ->
662 // /api/promote_content, and the ship gate verifies the :8443 sovereign edge.
663 let p8: *u8 = "sites/nishifamily/video/" as *u8
664 let l8: i64 = 24
665 if len > l8 {
666 var i8: i64 = 0
667 var m8: i64 = 1
668 while i8 < l8 { if (nm[off + i8] as i64) != (p8[i8] as i64) { m8 = 0; i8 = l8 } else { i8 = i8 + 1 } }
669 if m8 == 1 { return l8 }
670 }
671 // the EVIDENCE workstream namespace (2026-07-16, operator: "publish evidence consistent workstream"):
672 // /evidence/<run>/ = nx_evidence_pack output (index.html + api.json + screenshots/recordings),
673 // published via the proven upload->promote_content lane (nx_content_ship ship.manifest). Subdir runs
674 // ride the same guarded '/' rule as wholesale/.
675 let p9: *u8 = "sites/nishifamily/evidence/" as *u8
676 let l9: i64 = 27
677 if len > l9 {
678 var i9: i64 = 0
679 var m9: i64 = 1
680 while i9 < l9 { if (nm[off + i9] as i64) != (p9[i9] as i64) { m9 = 0; i9 = l9 } else { i9 = i9 + 1 } }
681 if m9 == 1 { return l9 }
682 }
683 // the EXPERIENTIAL census page (2026-07-16): EMITTED by nx_s21_census (sync-by-construction) and
684 // republished through this lane on every census re-run -- the page can never drift from disk truth.
685 let p10: *u8 = "sites/nishifamily/experiential/" as *u8
686 let l10: i64 = 31
687 if len > l10 {
688 var i10: i64 = 0
689 var m10: i64 = 1
690 while i10 < l10 { if (nm[off + i10] as i64) != (p10[i10] as i64) { m10 = 0; i10 = l10 } else { i10 = i10 + 1 } }
691 if m10 == 1 { return l10 }
692 }
693 // the COMPARE hub artifacts (2026-08-05, debt 1785937233): /compare api.json + index.html + openapi.json
694 // are laptop-generated (registry is laptop-owned by design) and ship through the proven
695 // upload -> promote_content lane; per-domain spoke pages stay NAS-regen-owned (nx_compare_regen).
696 // This row closes the hub-vs-spoke drift class: the hub gets a DOOR instead of a frozen snapshot.
697 let p11: *u8 = "sites/nishifamily/compare/" as *u8
698 let l11: i64 = 26
699 if len > l11 {
700 var i11: i64 = 0
701 var m11: i64 = 1
702 while i11 < l11 { if (nm[off + i11] as i64) != (p11[i11] as i64) { m11 = 0; i11 = l11 } else { i11 = i11 + 1 } }
703 if m11 == 1 { return l11 }
704 }
705 return 0 - 1
706}
707func md_content_target_ok(nm: *u8, off: i64, len: i64) -> i64 {
708 let pl: i64 = md_content_pfx(nm, off, len)
709 if pl < 0 { return 0 }
710 if len <= pl + 4 { return 0 } // needs prefix + at least an "a.png"-sized basename
711 if len > pl + 64 { return 0 } // bounded basename
712 let c0: i64 = nm[off + pl] as i64 // first basename char: alphanumeric only (blocks ".x" "-x" "..")
713 var ok0: i64 = 0
714 if c0 >= 97 { if c0 <= 122 { ok0 = 1 } }
715 if c0 >= 48 { if c0 <= 57 { ok0 = 1 } }
716 if ok0 == 0 { return 0 }
717 var j: i64 = pl
718 var prevdot: i64 = 0
719 var prevslash: i64 = 0
720 while j < len {
721 let c: i64 = nm[off + j] as i64
722 var okc: i64 = 0
723 if c >= 97 { if c <= 122 { okc = 1 } }
724 if c >= 48 { if c <= 57 { okc = 1 } }
725 if c == 95 { okc = 1 }
726 if c == 45 { okc = 1 }
727 // subdir separator for multi-page sites: never doubled, never after a dot (with the ".."-run
728 // refusal below and the pinned prefix, traversal stays impossible by construction).
729 if c == 47 {
730 if prevslash == 1 { return 0 }
731 if prevdot == 1 { return 0 }
732 okc = 1
733 prevslash = 1
734 } else { prevslash = 0 }
735 if c == 46 {
736 if prevdot == 1 { return 0 } // ".." run -> refuse
737 okc = 1
738 prevdot = 1
739 } else { prevdot = 0 }
740 if okc == 0 { return 0 }
741 j = j + 1
742 }
743 return md_content_ext_ok(nm, off, len)
744}
745
746// ---- COMPARE namespace (Nishi Compare registry SSOT + server-side hub regen, 2026-07-09) --------------------
747// The CONCURRENT-WORK coordination plane for /compare: many sessions publish comparisons, so the shared registry
748// + hub are mutated through THIS one serialized daemon instead of racing raw file writes. The unit of mutation is
749// the COMPARISON RECORD keyed by its /compare/<domain> href segment: different-domain upserts are commutative
750// (merge, no clobber possible); same-domain upserts replace, with the previous line preserved in registry.hist
751// (additive-only). After a mutation the hub index.html + api.json are regenerated SERVER-SIDE from the SSOT by the
752// on-NAS nx_swcompare_hub.elf, so the published surface can never reflect a session's stale partial registry.
753// Installs are sanity-gated + .prev-backed atomic renames (never-brick: a failed regen leaves live files untouched).
754
755// extract the /compare/<domain> merge key from a registry line (field 3 of title|kind|href|radar|stat).
756// Returns domain length copied into domb (NUL-terminated), or 0 if the line/href is malformed. Charset [a-z0-9_-].
757func md_cmp_domain_of(src: *u8, off: i64, len: i64, domb: *u8, cap: i64) -> i64 {
758 var p: i64 = 0
759 var f: i64 = 0
760 while p < len {
761 if (src[off + p] as i64) == 124 { f = f + 1; if f == 2 { p = p + 1; break } }
762 p = p + 1
763 }
764 if f != 2 { return 0 }
765 let pfx: *u8 = "/compare/" as *u8
766 var k: i64 = 0
767 while k < 9 {
768 if p + k >= len { return 0 }
769 if (src[off + p + k] as i64) != (pfx[k] as i64) { return 0 }
770 k = k + 1
771 }
772 var q: i64 = p + 9
773 var o: i64 = 0
774 while q < len {
775 let c: i64 = src[off + q] as i64
776 if c == 124 { break }
777 if c == 47 { break }
778 var okc: i64 = 0
779 if c >= 97 { if c <= 122 { okc = 1 } }
780 if c >= 48 { if c <= 57 { okc = 1 } }
781 if c == 95 { okc = 1 }
782 if c == 45 { okc = 1 }
783 if okc == 0 { return 0 }
784 if o < cap - 1 { domb[o] = src[off + q]; o = o + 1 }
785 q = q + 1
786 }
787 domb[o] = 0 as u8
788 if o < 1 { return 0 }
789 return o
790}
791
792func md_cmp_ws(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
793func md_cmp_wn(fd: i64, v: i64) -> i64 {
794 var m: i64 = v
795 if m < 0 { md_cmp_ws(fd, "-" as *u8); m = 0 - m }
796 let t: *u8 = sys_mmap(24)
797 var k: i64 = 0
798 if m == 0 { t[0] = 48 as u8; k = 1 }
799 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
800 let o2: *u8 = sys_mmap(24)
801 var i: i64 = 0
802 while i < k { o2[i] = t[k - 1 - i]; i = i + 1 }
803 sys_write(fd, o2, k)
804 return 0
805}
806
807// merge ONE registry line into the SSOT by domain key: replace the matching entry (old line -> .hist) or append.
808// Atomic (write registry.tmp -> rename); additive history appended AFTER the rename lands. Returns the new entry
809// count, or -1 on write failure (registry untouched -- the tmp+rename never half-writes the live file).
810func md_cmp_upsert(line: *u8, ln: i64, domb: *u8, domn: i64, replacedb: *i64) -> i64 {
811 let szp: *i64 = sys_mmap(16) as *i64
812 let old: *u8 = md_read_file("knowledge/compare/registry" as *u8, szp)
813 let on: i64 = szp[0]
814 let nb: *u8 = sys_mmap(262144)
815 let d2: *u8 = sys_mmap(128)
816 let oldline: *u8 = sys_mmap(4096)
817 var oldn: i64 = 0
818 var o: i64 = 0
819 var entries: i64 = 0
820 var replaced: i64 = 0
821 if (old as i64) != 0 {
822 var i: i64 = 0
823 while i < on {
824 let le: i64 = md_eol(old, on, i)
825 var wrote: i64 = 0
826 if le > i {
827 if (old[i] as i64) != 35 {
828 let dl2: i64 = md_cmp_domain_of(old, i, le - i, d2, 120)
829 if dl2 > 0 {
830 entries = entries + 1
831 if md_slice_eq(d2, 0, dl2, domb, 0, domn) == 1 {
832 replaced = 1
833 wrote = 1
834 oldn = 0
835 var c: i64 = 0
836 while c < (le - i) { if c < 4090 { oldline[c] = old[i + c]; oldn = c + 1 } c = c + 1 }
837 var w2: i64 = 0
838 while w2 < ln { nb[o] = line[w2]; o = o + 1; w2 = w2 + 1 }
839 nb[o] = 10 as u8
840 o = o + 1
841 }
842 }
843 }
844 }
845 if wrote == 0 {
846 var c2: i64 = i
847 while c2 < le { nb[o] = old[c2]; o = o + 1; c2 = c2 + 1 }
848 nb[o] = 10 as u8
849 o = o + 1
850 }
851 i = le + 1
852 }
853 }
854 if replaced == 0 {
855 var w3: i64 = 0
856 while w3 < ln { nb[o] = line[w3]; o = o + 1; w3 = w3 + 1 }
857 nb[o] = 10 as u8
858 o = o + 1
859 entries = entries + 1
860 }
861 let fd: i64 = sys_openat_wr("knowledge/compare/registry.tmp" as *u8, 0x1a4)
862 if fd < 0 { replacedb[0] = replaced; return 0 - 1 }
863 sys_write(fd, nb, o)
864 sys_close(fd)
865 if sys_renameat("knowledge/compare/registry.tmp" as *u8, "knowledge/compare/registry" as *u8) != 0 {
866 replacedb[0] = replaced
867 return 0 - 1
868 }
869 let hf: i64 = sys_openat_append("knowledge/compare/registry.hist" as *u8, 0x1a4)
870 if hf >= 0 {
871 md_cmp_ws(hf, "ts=" as *u8)
872 md_cmp_wn(hf, sys_now_realtime_sec())
873 md_cmp_ws(hf, " op=upsert domain=" as *u8)
874 var hd: i64 = 0
875 while hd < domn { sys_write(hf, ((domb as i64) + hd) as *u8, 1); hd = hd + 1 }
876 md_cmp_ws(hf, " replaced=" as *u8)
877 md_cmp_wn(hf, replaced)
878 md_cmp_ws(hf, "\n" as *u8)
879 if replaced == 1 { if oldn > 0 {
880 md_cmp_ws(hf, " prev: " as *u8)
881 sys_write(hf, oldline, oldn)
882 md_cmp_ws(hf, "\n" as *u8)
883 } }
884 sys_close(hf)
885 }
886 replacedb[0] = replaced
887 return entries
888}
889
890// install a generator-captured output file as a live docroot file: sanity (size + first byte) -> write tmp ->
891// back up live -> rename tmp over live; on failure the previous live file is restored (mirror of promote_content).
892func md_cmp_install(srcp: *u8, tmpp: *u8, prevp: *u8, livep: *u8, firstc: i64) -> i64 {
893 let szp: *i64 = sys_mmap(16) as *i64
894 let b: *u8 = md_read_file(srcp, szp)
895 let n: i64 = szp[0]
896 if (b as i64) == 0 { return 0 }
897 if n < 200 { return 0 }
898 if (b[0] as i64) != firstc { return 0 }
899 let fd: i64 = sys_openat_wr(tmpp, 0x1a4)
900 if fd < 0 { return 0 }
901 sys_write(fd, b, n)
902 sys_close(fd)
903 var had: i64 = 0
904 let pf: i64 = sys_openat_rd(livep)
905 if pf >= 0 { sys_close(pf); had = 1 }
906 if had == 1 { md_rotate_prev(prevp); if sys_renameat(livep, prevp) != 0 { return 0 } }
907 if sys_renameat(tmpp, livep) != 0 {
908 if had == 1 { sys_renameat(prevp, livep) }
909 return 0
910 }
911 return 1
912}
913
914// regenerate the /compare hub (index.html + api.json) from the registry SSOT via the on-NAS hub generator elf.
915// Fail-safe: generator output must pass sanity before install; a missing elf / bad output leaves live files alone.
916func md_cmp_regen() -> i64 {
917 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_swcompare_hub.elf" as *u8
918 // Capture the generator output to an ELDERWESTO-OWNED scratch dir (knowledge/compare/, CWD=nishihost), NOT
919 // world-writable /tmp. WHY (2026-07-14 root cause): a root-era mgmt run left /tmp/nx_ma_cmp_*.out root-owned;
920 // after the root->elderwesto guard migration this daemon could no longer OVERWRITE them -> dep_run_capture's
921 // sys_openat_wr failed (EACCES), it ran the generator WITHOUT redirect, and md_cmp_install re-installed the
922 // STALE file every regen (silent "OK", frozen hub). A path this daemon owns always truncates fresh -> correct
923 // install, or an empty capture that fails md_cmp_install's sanity gate -> honest REGEN-FAILED (never stale).
924 let a1: *i64 = sys_mmap(16) as *i64
925 a1[0] = "html" as *u8 as i64
926 dep_run_capture(helf, a1, 1, "knowledge/compare/.regen_html.out" as *u8)
927 let a2: *i64 = sys_mmap(16) as *i64
928 a2[0] = "json" as *u8 as i64
929 dep_run_capture(helf, a2, 1, "knowledge/compare/.regen_json.out" as *u8)
930 let ok1: i64 = md_cmp_install("knowledge/compare/.regen_html.out" as *u8, "sites/nishifamily/compare/index.html.tmp2" as *u8, "sites/nishifamily/compare/index.html.prev" as *u8, "sites/nishifamily/compare/index.html" as *u8, 60)
931 let ok2: i64 = md_cmp_install("knowledge/compare/.regen_json.out" as *u8, "sites/nishifamily/compare/api.json.tmp2" as *u8, "sites/nishifamily/compare/api.json.prev" as *u8, "sites/nishifamily/compare/api.json" as *u8, 123)
932 if ok1 == 1 { if ok2 == 1 { return 1 } }
933 return 0
934}
935
936// validate a bare compare DOMAIN atom: charset [a-z0-9_-], first char alphanumeric, len 1..60 -> copy NUL-terminated.
937// Path segments are built ONLY from this validated atom + fixed literals, so traversal is impossible by construction.
938func md_cmp_dom_ok(src: *u8, off: i64, len: i64, domb: *u8) -> i64 {
939 if len < 1 { return 0 }
940 if len > 60 { return 0 }
941 let c0: i64 = src[off] as i64
942 var ok0: i64 = 0
943 if c0 >= 97 { if c0 <= 122 { ok0 = 1 } }
944 if c0 >= 48 { if c0 <= 57 { ok0 = 1 } }
945 if ok0 == 0 { return 0 }
946 var i: i64 = 0
947 while i < len {
948 let c: i64 = src[off + i] as i64
949 var okc: i64 = 0
950 if c >= 97 { if c <= 122 { okc = 1 } }
951 if c >= 48 { if c <= 57 { okc = 1 } }
952 if c == 95 { okc = 1 }
953 if c == 45 { okc = 1 }
954 if okc == 0 { return 0 }
955 domb[i] = src[off + i]
956 i = i + 1
957 }
958 domb[len] = 0 as u8
959 return 1
960}
961func md_cmp_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } d[o + i] = 0 as u8; return o + i }
962
963// publish the STAGED compare.page.new as the live artifact for (domain, kind). kind: 1=page 2=frontier 3=bench 4=api.
964// Server derives the FIXED docroot path from the validated domain atom + a kind enum (no caller-supplied paths at
965// all). Dirs are created as needed; install is sanity-gated + .prev-backed (md_cmp_install). kind=api ALSO refreshes
966// the hub data-link marker knowledge/compare/<domain>-api.json (tmp+rename). Returns 1 ok / 0 fail (live untouched).
967func md_cmp_publish(domb: *u8, kind: i64) -> i64 {
968 let base: *u8 = sys_mmap(512)
969 var o: i64 = md_cmp_cat(base, 0, "sites/nishifamily/compare/" as *u8)
970 o = md_cmp_cat(base, o, domb)
971 sys_mkdir(base, 0x1ed)
972 if kind == 2 { o = md_cmp_cat(base, o, "/frontier" as *u8); sys_mkdir(base, 0x1ed) }
973 if kind == 3 { o = md_cmp_cat(base, o, "/bench" as *u8); sys_mkdir(base, 0x1ed) }
974 let live: *u8 = sys_mmap(512)
975 var lo: i64 = md_cmp_cat(live, 0, base)
976 var fc: i64 = 60
977 if kind == 4 { lo = md_cmp_cat(live, lo, "/api.json" as *u8); fc = 123 } else { lo = md_cmp_cat(live, lo, "/index.html" as *u8) }
978 let tmpp: *u8 = sys_mmap(512)
979 var to: i64 = md_cmp_cat(tmpp, 0, live)
980 to = md_cmp_cat(tmpp, to, ".tmp2" as *u8)
981 let prevp: *u8 = sys_mmap(512)
982 var po: i64 = md_cmp_cat(prevp, 0, live)
983 po = md_cmp_cat(prevp, po, ".prev" as *u8)
984 let oki: i64 = md_cmp_install("compare.page.new" as *u8, tmpp, prevp, live, fc)
985 if oki != 1 { return 0 }
986 if kind == 4 {
987 let mk: *u8 = sys_mmap(512)
988 var mo: i64 = md_cmp_cat(mk, 0, "knowledge/compare/" as *u8)
989 mo = md_cmp_cat(mk, mo, domb)
990 mo = md_cmp_cat(mk, mo, "-api.json" as *u8)
991 let mt: *u8 = sys_mmap(512)
992 var mto: i64 = md_cmp_cat(mt, 0, mk)
993 mto = md_cmp_cat(mt, mto, ".tmp" as *u8)
994 let szp: *i64 = sys_mmap(16) as *i64
995 let b: *u8 = md_read_file("compare.page.new" as *u8, szp)
996 if (b as i64) != 0 { if szp[0] > 0 {
997 let fd: i64 = sys_openat_wr(mt, 0x1a4)
998 if fd >= 0 { sys_write(fd, b, szp[0]); sys_close(fd); sys_renameat(mt, mk) }
999 } }
1000 }
1001 return 1
1002}
1003
1004// map an allowlisted service name -> the proven nx_aw_hostctl surgical-restart sub (fail-closed: unknown -> 0).
1005// ---- RESTART ALLOWLISTS: ONE OWNER PER LANE, TABLE-DRIVEN (2026-08-28) ------------------------------
1006// WHY THIS SHAPE. These two allowlists were if-chains and the /api/restart error message was a THIRD
1007// hand-written copy of both. That is the duplicate-ruler defect, and THIS FILE ALREADY RECORDS IT
1008// HAPPENING -- seq1433: "this list DRIFTED from the two allowlists it describes -- office/officejs/
1009// toolsapi were wired into md_direct_restart_ok but never named here, so a caller asking for a service
1010// that IS supported was told it was unknown." That drift was repaired by hand, which fixes the instance
1011// and leaves the shape. ★WHEN TWO THINGS MUST AGREE, MAKE DISAGREEMENT IMPOSSIBLE BY CONSTRUCTION --
1012// DO NOT COORDINATE BY DISCIPLINE. The rows below are now the ONLY declaration of a restart target: the
1013// resolvers read them and the published message is GENERATED from them (md_tbl_names), so the message
1014// can never again name a route that does not resolve, nor omit one that does. Adding a route is ONE
1015// data row, and nx_restart_routes_gate proves both directions.
1016//
1017// GRAMMAR: <svc>|<target>;<svc>|<target>;... Separators are plain printable ASCII with NO escape
1018// sequence -- a bare tab or newline inside an nx literal is a banked lexer hazard, and '|' is already
1019// the separator the published message uses, so md_tbl_names emits it verbatim.
1020// MATCHING IS EXACT-LENGTH, INHERITED NOT RE-IMPLEMENTED: md_slice_eq refuses on a length mismatch, so
1021// "site" still cannot match "siteedit" and "office" cannot match "officejs" -- the property the if-chain
1022// got from its hand-counted literal lengths, now DERIVED from the row's own field width. That deletes
1023// the hand-counted-length class outright (a literal and a number beside it that drift in silence).
1024const MD_TBL_FIELD: i64 = 124 // '|' -- field separator, and the message's own display separator
1025const MD_TBL_ROW: i64 = 59 // ';' -- row terminator
1026// The resolvers write into a caller-allocated buffer. ONE const owns that size and BOTH the callers in
1027// nx_mgmt_api and the copies here read it, so the allocation and the bound cannot disagree.
1028const MD_RESTART_BUFCAP: i64 = 64
1029//
1030// LANE 1 -- hostctl-sub restarts: the mgmt daemon runs `nx_hostctl <sub>`; hostctl owns the kill+respawn.
1031const MD_RESTART_SUB_TBL: *u8 = "reader|kickreader;torrent|kicktorrent;torrentgw|kicktorrentgw;docportal|kickdocportal;"
1032//
1033// LANE 2 -- DIRECT restarts (no hostctl sub): svc -> the exact process cmdline needle. The mgmt daemon
1034// (root, itself guard-supervised) kills by name; a supervisor respawns the on-disk binary. Promote a
1035// staged .new first and this IS the full API-pure deploy loop. WHY EACH ROW EXISTS, kept with the row it
1036// explains so a reader can still grep the svc name and find its history:
1037// siteedit -- the andelinwest site editor.
1038// sites -- sites.elf = THE EDGE. Restart (kill -> guard respawns) RE-READS proxy_routes.conf, so
1039// this doubles as the API-pure route reload AND an edge redeploy if sites.elf.new is
1040// staged. The needle "sites.elf" is unique to the edge (nx_sites_daemon_v2 and
1041// nx_sites_reconciled do not contain it).
1042// survey -- Nishi Pulse: kill -> the nx_survey_reconcile cron respawns <=60s (not hostctl-guarded;
1043// the reconcile row IS its supervisor -- crash+reboot proven 2026-07-10).
1044// office -- kill -> nx_office_reconcile cron respawns <=60s (SO_REUSEADDR = fast rebind).
1045// officejs -- the client JS: promote office_app.js.new -> live. kill-by-name matches NO process (it
1046// is a file, not a daemon) -> harmless; the daemon reads it fresh on the next request.
1047// toolsapi -- the R0 agent-facing tools daemon :18096 (hostctl guard-supervised): respawn <=15s.
1048// Makes the tools/MCP plane API-pure-deployable.
1049// seed -- BitTorrent SEEDER :6881. ★NOTE THE SPLIT, IT IS DELIBERATE: md_promote_staged would
1050// target nishihost/nx_torrent_seed.elf.new, but the LIVE seeder lives at
1051// /volume1/ai/torrent/ and is promoted there by cmd_torrentdeploy. No such .new exists at
1052// the nishihost root, so the promote half cleanly NO-OPS and only the kill does the work.
1053// Promoting to the nishihost path instead would place a binary NOTHING EVER RUNS while
1054// reporting a successful restart.
1055// clock -- nx_clock_tickless.elf (guard-supervised). Its main loop only exits at 120 windows x
1056// 1800s = 60 HOURS per life, so without this route a promoted clock binary sat on disk
1057// while the running process kept executing the previous code for up to 60 hours -- every
1058// clock fix adopted by TIMEOUT rather than by deploy. ★PROMOTED IS A CLAIM ABOUT THE
1059// DISK; ADOPTED IS A CLAIM ABOUT A PROCESS. Safe: the same respawn the life budget
1060// already performs, just on demand; schedule state persists to knowledge/store/
1061// clocksched- at every window end and clk_edf_migrate re-arms stale deadlines.
1062// wiki -- nx_wiki_gw.elf :18791 (guard-supervised). Added 2026-08-17 when it was the
1063// worst-committed process on the box (~0.7 MB/beat past 3.0 GB, swap 757->787 permil):
1064// the one daemon driving the host toward the swap cliff was ALSO the one /api/restart
1065// could not bounce, so the only remediation was a hard host action.
1066// email -- nx_email_portal_daemon.elf :18465. Added 2026-08-28 for EXACTLY the reason the wiki row
1067// was, one daemon later: measured worst_committed_kb=2,783,996 (2.78 GB) with swap at
1068// 943-951 permil against a 850 RED bar, nx_resmon verdict=RED sev=2 -- and no sanctioned
1069// way to recycle it. SUPERVISOR VERIFIED BEFORE WIRING, not assumed: daemons.reg row
1070// `email_portal_daemon ... | 18465 | revive | http` is armed to REVIVE, and this daemon is
1071// absent from nx_hostctl's supervise roster, so there is exactly ONE supervisor and this
1072// route cannot create the dueling-supervisor amplifier this estate has hit three times.
1073// Kill -> revive is the same respawn that supervisor already performs on a crash.
1074// ⚠HONEST RESIDUAL: unlike the wiki, a mail portal holds a maildir; the kill is a normal
1075// process death, so anything the daemon had not yet fsynced is lost exactly as it would
1076// be on any crash. Recycle it when the box is quiet, not mid-delivery.
1077const MD_RESTART_DIRECT_TBL: *u8 = "siteedit|nx_siteedit_daemon.elf;sites|sites.elf;survey|nx_survey_daemon.elf;office|nx_office_daemon.elf;officejs|office_app.js;toolsapi|nx_tools_api_serve.elf;seed|nx_torrent_seed.elf;clock|nx_clock_tickless.elf;wiki|nx_wiki_gw.elf;email|nx_email_portal_daemon.elf;"
1078
1079// Scan ONE table row starting at `i`: barbox gets the '|' offset (-1 if the row has none), endbox gets
1080// the row terminator (the ';' or the NUL). ONE scanner, TWO consumers (lookup + names), so a grammar
1081// change cannot land in one and be missed by the other. The cursor is never clobbered to exit -- `end`
1082// is a separate flag-cum-result, the banked remedy for the loop-exit-sentinel class.
1083func md_tbl_row(tbl: *u8, i: i64, barbox: *i64, endbox: *i64) -> i64 {
1084 var p: i64 = i
1085 var bar: i64 = 0 - 1
1086 var end: i64 = 0 - 1
1087 while end < 0 {
1088 let c: i64 = tbl[p] as i64
1089 if c == 0 { end = p } else {
1090 if c == MD_TBL_ROW { end = p } else {
1091 if c == MD_TBL_FIELD { if bar < 0 { bar = p } }
1092 p = p + 1
1093 }
1094 }
1095 }
1096 barbox[0] = bar
1097 endbox[0] = end
1098 return end
1099}
1100
1101// A row is USABLE only with a non-empty name AND a non-empty target. A malformed row is SKIPPED, never
1102// half-read: without this, a row like "x|;" would resolve to an EMPTY needle and md_kill_by_name("")
1103// is a kill request with no subject. The guard is wrong in the direction of doing nothing.
1104func md_tbl_row_ok(i: i64, bar: i64, end: i64) -> i64 {
1105 if bar <= i { return 0 }
1106 if end <= bar + 1 { return 0 }
1107 return 1
1108}
1109
1110// Walk a table; on an EXACT-LENGTH svc match copy that row's target into outbuf and return 1, else 0.
1111// Composes md_slice_eq and md_copy_slice_z -- the same two primitives the if-chains used, so matching
1112// and copying behaviour is inherited rather than rewritten.
1113func md_tbl_lookup(tbl: *u8, svc: *u8, off: i64, len: i64, outbuf: *u8, cap: i64) -> i64 {
1114 let barbox: *i64 = sys_mmap(16) as *i64
1115 let endbox: *i64 = sys_mmap(16) as *i64
1116 var i: i64 = 0
1117 var done: i64 = 0
1118 var hit: i64 = 0
1119 while done == 0 {
1120 if tbl[i] == (0 as u8) { done = 1 } else {
1121 md_tbl_row(tbl, i, barbox, endbox)
1122 let bar: i64 = barbox[0]
1123 let end: i64 = endbox[0]
1124 if md_tbl_row_ok(i, bar, end) == 1 {
1125 if md_slice_eq(svc, off, len, tbl, i, bar - i) == 1 {
1126 md_copy_slice_z(outbuf, tbl, bar + 1, end - bar - 1, cap)
1127 hit = 1
1128 done = 1
1129 }
1130 }
1131 if done == 0 { if tbl[end] == (0 as u8) { done = 1 } else { i = end + 1 } }
1132 }
1133 }
1134 return hit
1135}
1136
1137// Append every declared svc name from a table as "a|b|c" (no trailing separator), returning the new
1138// offset. THE PUBLISHED ERROR MESSAGE IS BUILT FROM THIS, so it is a projection of the resolver's own
1139// data and cannot drift from it.
1140func md_tbl_names(tbl: *u8, dst: *u8, o: i64) -> i64 {
1141 let barbox: *i64 = sys_mmap(16) as *i64
1142 let endbox: *i64 = sys_mmap(16) as *i64
1143 var out: i64 = o
1144 var i: i64 = 0
1145 var first: i64 = 1
1146 var done: i64 = 0
1147 while done == 0 {
1148 if tbl[i] == (0 as u8) { done = 1 } else {
1149 md_tbl_row(tbl, i, barbox, endbox)
1150 let bar: i64 = barbox[0]
1151 let end: i64 = endbox[0]
1152 if md_tbl_row_ok(i, bar, end) == 1 {
1153 if first == 0 { dst[out] = MD_TBL_FIELD as u8; out = out + 1 }
1154 var k: i64 = i
1155 while k < bar { dst[out] = tbl[k]; out = out + 1; k = k + 1 }
1156 first = 0
1157 }
1158 if tbl[end] == (0 as u8) { done = 1 } else { i = end + 1 }
1159 }
1160 }
1161 return out
1162}
1163
1164func md_restart_sub(svc: *u8, off: i64, len: i64, subbuf: *u8) -> i64 {
1165 return md_tbl_lookup(MD_RESTART_SUB_TBL, svc, off, len, subbuf, MD_RESTART_BUFCAP)
1166}
1167
1168// DIRECT-restart allowlist (no hostctl sub needed): svc name -> the exact process cmdline needle. The mgmt
1169// daemon (root, itself guard-supervised) kills by name; the hostctl supervise guard respawns the on-disk
1170// binary <=15s -- so promote-a-staged-.new + /api/restart = the full API-pure editor deploy loop.
1171// THE RESOLVER IS NOW A PROJECTION OF THE TABLE ABOVE. It was an if-chain of hand-counted literal
1172// lengths; every row's reasoning is preserved verbatim in the MD_RESTART_DIRECT_TBL comment block, where
1173// it stays greppable by svc name. Behaviour is EQUIVALENT, not merely similar, and that equivalence is
1174// proven exhaustively (all 9 legacy names -> their exact legacy needles, plus prefix/suffix/case/
1175// cross-table negatives) by nx_restart_routes_gate rather than asserted here.
1176func md_direct_restart_ok(svc: *u8, off: i64, len: i64, namebuf: *u8) -> i64 {
1177 return md_tbl_lookup(MD_RESTART_DIRECT_TBL, svc, off, len, namebuf, MD_RESTART_BUFCAP)
1178}
1179// -- /api/route: validate + append a proxy_routes.conf row (data ring). Format: "<host> <prefix> <port> <mode>".
1180func md_catn(d: *u8, o: i64, v: i64) -> i64 {
1181 if v == 0 { d[o] = 48 as u8; return o + 1 }
1182 var m: i64 = v
1183 var oo: i64 = o
1184 if m < 0 { d[oo] = 45 as u8; oo = oo + 1; m = 0 - m }
1185 var nd: i64 = 1
1186 var t: i64 = m
1187 while t >= 10 { nd = nd + 1; t = t / 10 }
1188 var i: i64 = nd - 1
1189 while i >= 0 { d[oo + i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 }
1190 return oo + nd
1191}
1192// fail-closed validation of a route (null-terminated host/prefix/mode + numeric port).
1193func md_route_valid(host: *u8, prefix: *u8, port: i64, mode: *u8) -> i64 {
1194 let hl: i64 = md_len(host)
1195 if hl < 3 { return 0 }
1196 if hl > 64 { return 0 }
1197 if (host[0] as i64) == 46 { return 0 }
1198 if (host[0] as i64) == 45 { return 0 }
1199 var hasdot: i64 = 0
1200 var i: i64 = 0
1201 while i < hl {
1202 let c: i64 = host[i] as i64
1203 var ok: i64 = 0
1204 if c >= 97 { if c <= 122 { ok = 1 } }
1205 if c >= 48 { if c <= 57 { ok = 1 } }
1206 if c == 45 { ok = 1 }
1207 if c == 46 { ok = 1; hasdot = 1; if i > 0 { if (host[i - 1] as i64) == 46 { return 0 } } }
1208 if ok == 0 { return 0 }
1209 i = i + 1
1210 }
1211 if hasdot == 0 { return 0 }
1212 let pl: i64 = md_len(prefix)
1213 if pl < 2 { return 0 }
1214 if pl > 48 { return 0 }
1215 if (prefix[0] as i64) != 47 { return 0 }
1216 i = 1
1217 while i < pl {
1218 let c: i64 = prefix[i] as i64
1219 var ok: i64 = 0
1220 if c >= 97 { if c <= 122 { ok = 1 } }
1221 if c >= 48 { if c <= 57 { ok = 1 } }
1222 if c == 95 { ok = 1 }
1223 if c == 45 { ok = 1 }
1224 if c == 47 { ok = 1; if (prefix[i - 1] as i64) == 47 { return 0 } }
1225 if c == 46 { ok = 1; if (prefix[i - 1] as i64) == 46 { return 0 } }
1226 if ok == 0 { return 0 }
1227 i = i + 1
1228 }
1229 if port < 1024 { return 0 }
1230 if port > 65535 { return 0 }
1231 var mok: i64 = 0
1232 if md_cstr_eq(mode, "buffered" as *u8) == 1 { mok = 1 }
1233 if md_cstr_eq(mode, "stream" as *u8) == 1 { mok = 1 }
1234 if md_cstr_eq(mode, "gated" as *u8) == 1 { mok = 1 }
1235 if mok == 0 { return 0 }
1236 return 1
1237}
1238// atomically upsert the route row into knowledge/hosting/proxy_routes.conf: preserve every OTHER line, replace
1239// any existing "<host> <prefix> ..." row, append the new one; tmp+rename. Returns 1 ok / 0 write-fail.
1240func md_route_append(confp: *u8, host: *u8, prefix: *u8, port: i64, mode: *u8) -> i64 {
1241 let szp: *i64 = sys_mmap(16) as *i64
1242 szp[0] = 0
1243 let old: *u8 = md_read_file(confp, szp)
1244 let oldn: i64 = szp[0]
1245 // NEVER-BRICK: refuse to write when the existing table is unreadable/empty. A transient read failure
1246 // (fd exhaustion etc.) with old==0 would otherwise REPLACE the populated edge table with one row ->
1247 // every proxied surface incl. /api itself lost = self-lockout. The live table always has rows; a
1248 // genuinely fresh bootstrap is an ssh-once operation, not this API's job. Fail-closed.
1249 if (old as i64) == 0 { return 0 }
1250 if oldn == 0 { return 0 }
1251 // build the dedup match key: "<host> <prefix> "
1252 let mk: *u8 = sys_mmap(160)
1253 var ko: i64 = 0
1254 var a: i64 = 0
1255 while host[a] != (0 as u8) { mk[ko] = host[a]; ko = ko + 1; a = a + 1 }
1256 mk[ko] = 32 as u8; ko = ko + 1
1257 a = 0
1258 while prefix[a] != (0 as u8) { mk[ko] = prefix[a]; ko = ko + 1; a = a + 1 }
1259 mk[ko] = 32 as u8; ko = ko + 1
1260 mk[ko] = 0 as u8
1261 let mkl: i64 = ko
1262 let out: *u8 = sys_mmap(262144)
1263 var o: i64 = 0
1264 if (old as i64) != 0 {
1265 var ls: i64 = 0
1266 while ls < oldn {
1267 var le: i64 = ls
1268 var sc: i64 = 1
1269 while sc == 1 { if le >= oldn { sc = 0 } else { if (old[le] as i64) == 10 { sc = 0 } else { le = le + 1 } } }
1270 // does this line start with the match key?
1271 var m: i64 = 1
1272 if ls + mkl > le + 1 { m = 0 }
1273 if m == 1 {
1274 var j: i64 = 0
1275 while j < mkl { if (old[ls + j] as i64) != (mk[j] as i64) { m = 0; j = mkl } else { j = j + 1 } }
1276 }
1277 if m == 0 {
1278 var k: i64 = ls
1279 while k <= le { if k < oldn { out[o] = old[k]; o = o + 1 } k = k + 1 }
1280 }
1281 ls = le + 1
1282 }
1283 }
1284 // append the new row (ensure a trailing newline precedes if the file didn't end in one)
1285 if o > 0 { if (out[o - 1] as i64) != 10 { out[o] = 10 as u8; o = o + 1 } }
1286 a = 0
1287 while host[a] != (0 as u8) { out[o] = host[a]; o = o + 1; a = a + 1 }
1288 out[o] = 32 as u8; o = o + 1
1289 a = 0
1290 while prefix[a] != (0 as u8) { out[o] = prefix[a]; o = o + 1; a = a + 1 }
1291 out[o] = 32 as u8; o = o + 1
1292 o = md_catn(out, o, port)
1293 out[o] = 32 as u8; o = o + 1
1294 a = 0
1295 while mode[a] != (0 as u8) { out[o] = mode[a]; o = o + 1; a = a + 1 }
1296 out[o] = 10 as u8; o = o + 1
1297 // NEVER-BRICK: bank the pre-edit table as confp+".prev" FIRST (recovery: cp .prev back), then
1298 // atomic write: tmp = confp + ".tmp", rename over confp. Same idiom as binary deploys.
1299 let prevp: *u8 = sys_mmap(512)
1300 var pj: i64 = 0
1301 while confp[pj] != (0 as u8) { prevp[pj] = confp[pj]; pj = pj + 1 }
1302 prevp[pj] = 46 as u8; prevp[pj + 1] = 112 as u8; prevp[pj + 2] = 114 as u8; prevp[pj + 3] = 101 as u8; prevp[pj + 4] = 118 as u8; prevp[pj + 5] = 0 as u8
1303 let pfd: i64 = sys_openat_wr(prevp, 0x1a4)
1304 if pfd >= 0 { sys_write(pfd, old, oldn); sys_close(pfd) }
1305 let tmpp: *u8 = sys_mmap(512)
1306 var tj: i64 = 0
1307 while confp[tj] != (0 as u8) { tmpp[tj] = confp[tj]; tj = tj + 1 }
1308 tmpp[tj] = 46 as u8; tmpp[tj + 1] = 116 as u8; tmpp[tj + 2] = 109 as u8; tmpp[tj + 3] = 112 as u8; tmpp[tj + 4] = 0 as u8
1309 let fd: i64 = sys_openat_wr(tmpp, 0x1a4)
1310 if fd < 0 { return 0 }
1311 sys_write(fd, out, o)
1312 sys_close(fd)
1313 sys_renameat(tmpp, confp)
1314 return 1
1315}
1316// kill every process whose /proc/<pid>/cmdline CONTAINS needle (full-cmdline match -- the 15-char comm
1317// truncation trap). Returns processes signalled. Mirrors the proven hostctl proc_kill_by_name.
1318func md_pk_contains(hay: *u8, hn: i64, needle: *u8, nl: i64) -> i64 {
1319 if nl == 0 { return 0 }
1320 var i: i64 = 0
1321 while i + nl <= hn {
1322 var j: i64 = 0
1323 var ok: i64 = 1
1324 while j < nl { if (hay[i + j] as i64) != (needle[j] as i64) { ok = 0; j = nl } else { j = j + 1 } }
1325 if ok == 1 { return 1 }
1326 i = i + 1
1327 }
1328 return 0
1329}
1330func md_pk_atoi(s: *u8) -> i64 {
1331 var v: i64 = 0
1332 var i: i64 = 0
1333 while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 }
1334 return v
1335}
1336// promote a staged <cwd>/<name>.new -> live <name> (.prev kept), chmod +x. Returns 1 if a .new existed
1337// and was promoted, else 0 (restart still valid -- just reloads the same on-disk binary). cwd = nishihost.
1338// IDEMPOTENCY for /api/tools/register: is <nm> already the first TAB-field of a line in tool_allowlist.conf?
1339// Reads the (small) allowlist raw; matches a line that starts with "<nm>\t". Fail-open to 0 (absent) so a
1340// missing/unreadable allowlist doesn't block a first registration.
1341func md_allow_has_name(nm: *u8) -> i64 {
1342 let fd: i64 = sys_openat_rd("tool_allowlist.conf" as *u8)
1343 if fd < 0 { return 0 }
1344 let cap: i64 = 1 << 18
1345 let buf: *u8 = sys_mmap(cap)
1346 let n: i64 = sys_read(fd, buf, cap - 1)
1347 sys_close(fd)
1348 if n <= 0 { return 0 }
1349 buf[n] = 0 as u8
1350 let nl: i64 = md_len(nm)
1351 var i: i64 = 0
1352 while i + nl < n {
1353 var ls: i64 = 0
1354 if i == 0 { ls = 1 } else { if buf[i-1] == (10 as u8) { ls = 1 } }
1355 if ls == 1 {
1356 var m: i64 = 1
1357 var j: i64 = 0
1358 while j < nl { if buf[i+j] != nm[j] { m = 0; j = nl } else { j = j + 1 } }
1359 if m == 1 { if buf[i+nl] == (9 as u8) { return 1 } }
1360 }
1361 i = i + 1
1362 }
1363 return 0
1364}
1365
1366// seq1281 (RESTORED AGAIN 2026-07-30 -- 3rd backdate, see seq1439/nx_srcguard): read the existing pinned-
1367// args column (4th TAB field .. EOL) of tool <nm>'s allowlist row into dst. Returns copied length; 0 = no
1368// row / no args / unreadable. Lets register-update PRESERVE pinned args when args= is omitted -- an omitted
1369// field must never silently widen a pinned oracle into caller-controlled argv.
1370func md_allow_get_args(nm: *u8, dst: *u8, cap: i64) -> i64 {
1371 let fd: i64 = sys_openat_rd("tool_allowlist.conf" as *u8)
1372 if fd < 0 { return 0 }
1373 let bcap: i64 = 1 << 18
1374 let buf: *u8 = sys_mmap(bcap)
1375 let n: i64 = sys_read(fd, buf, bcap - 1)
1376 sys_close(fd)
1377 if n <= 0 { return 0 }
1378 buf[n] = 0 as u8
1379 let nl: i64 = md_len(nm)
1380 var i: i64 = 0
1381 while i + nl < n {
1382 var ls: i64 = 0
1383 if i == 0 { ls = 1 } else { if buf[i-1] == (10 as u8) { ls = 1 } }
1384 if ls == 1 {
1385 var m: i64 = 1
1386 var j: i64 = 0
1387 while j < nl { if buf[i+j] != nm[j] { m = 0; j = nl } else { j = j + 1 } }
1388 if m == 1 { if buf[i+nl] == (9 as u8) {
1389 var p: i64 = i + nl + 1
1390 var tabs: i64 = 0
1391 var argst: i64 = 0
1392 while p < n {
1393 let c: i64 = buf[p] as i64
1394 if c == 10 { p = n } else {
1395 if c == 9 { tabs = tabs + 1; if tabs == 2 { argst = p + 1; p = n } }
1396 if p < n { p = p + 1 }
1397 }
1398 }
1399 if argst == 0 { return 0 }
1400 var o: i64 = 0
1401 var q: i64 = argst
1402 while q < n {
1403 if buf[q] == (10 as u8) { q = n } else {
1404 if o < cap - 1 { dst[o] = buf[q]; o = o + 1 }
1405 q = q + 1
1406 }
1407 }
1408 dst[o] = 0 as u8
1409 return o
1410 } }
1411 }
1412 i = i + 1
1413 }
1414 return 0
1415}
1416
1417// atomically REPLACE the tool_allowlist.conf row for tool <nm> -- the register-update verb's mutation
1418// (eats the ssh-once row-repoint class: evidence_checkin/mvault/clock repoints). Preserves every OTHER
1419// line byte-exact, drops the existing "<nm>\t..." row(s), appends the replacement
1420// "<nm>\t<elfp>\tGREEN[\t<args>]" row; banks .prev FIRST then tmp+rename (md_route_append idiom).
1421// UPDATE CAN NEVER CREATE: refuses (0) when no row matches. NEVER-BRICK: refuses when the allowlist is
1422// unreadable/empty so a transient read failure cannot truncate the live tool table. 1 ok / 0 refused.
1423func md_allow_update_row(nm: *u8, elfp: *u8, argp: *u8, argn: i64) -> i64 {
1424 let szp: *i64 = sys_mmap(16) as *i64
1425 szp[0] = 0
1426 let old: *u8 = md_read_file("tool_allowlist.conf" as *u8, szp)
1427 let oldn: i64 = szp[0]
1428 if (old as i64) == 0 { return 0 }
1429 if oldn == 0 { return 0 }
1430 let nl: i64 = md_len(nm)
1431 let out: *u8 = sys_mmap(262144)
1432 var o: i64 = 0
1433 var found: i64 = 0
1434 var ls: i64 = 0
1435 while ls < oldn {
1436 var le: i64 = ls
1437 var sc: i64 = 1
1438 while sc == 1 { if le >= oldn { sc = 0 } else { if (old[le] as i64) == 10 { sc = 0 } else { le = le + 1 } } }
1439 var m: i64 = 0
1440 if ls + nl < le {
1441 if (old[ls + nl] as i64) == 9 {
1442 m = 1
1443 var j: i64 = 0
1444 while j < nl { if old[ls + j] != nm[j] { m = 0; j = nl } else { j = j + 1 } }
1445 }
1446 }
1447 if m == 1 { found = 1 } else {
1448 var k: i64 = ls
1449 while k <= le { if k < oldn { out[o] = old[k]; o = o + 1 } k = k + 1 }
1450 }
1451 ls = le + 1
1452 }
1453 if found == 0 { return 0 }
1454 if o > 0 { if (out[o - 1] as i64) != 10 { out[o] = 10 as u8; o = o + 1 } }
1455 var a: i64 = 0
1456 while nm[a] != (0 as u8) { out[o] = nm[a]; o = o + 1; a = a + 1 }
1457 out[o] = 9 as u8; o = o + 1
1458 a = 0
1459 while elfp[a] != (0 as u8) { out[o] = elfp[a]; o = o + 1; a = a + 1 }
1460 out[o] = 9 as u8; o = o + 1
1461 out[o] = 71 as u8; o = o + 1
1462 out[o] = 82 as u8; o = o + 1
1463 out[o] = 69 as u8; o = o + 1
1464 out[o] = 69 as u8; o = o + 1
1465 out[o] = 78 as u8; o = o + 1
1466 if argn > 0 {
1467 out[o] = 9 as u8; o = o + 1
1468 a = 0
1469 while a < argn { out[o] = argp[a]; o = o + 1; a = a + 1 }
1470 }
1471 out[o] = 10 as u8; o = o + 1
1472 let pfd: i64 = sys_openat_wr("tool_allowlist.conf.prev" as *u8, 0x1a4)
1473 if pfd >= 0 { sys_write(pfd, old, oldn); sys_close(pfd) }
1474 let fd: i64 = sys_openat_wr("tool_allowlist.conf.nxtmp" as *u8, 0x1a4)
1475 if fd < 0 { return 0 }
1476 sys_write(fd, out, o)
1477 sys_close(fd)
1478 sys_renameat("tool_allowlist.conf.nxtmp" as *u8, "tool_allowlist.conf" as *u8)
1479 return 1
1480}
1481
1482func md_streq(a: *u8, b: *u8) -> i64 {
1483 var i: i64 = 0
1484 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
1485 if b[i] != (0 as u8) { return 0 }
1486 return 1
1487}
1488
1489// FAIL-CLOSED promote policy for POST /api/promote -- STRUCTURAL, still by-construction (F-210b eaten 07-18):
1490// (1) compiled-in DENY first: daemons + credential oracles (family substrings, so new members inherit the
1491// refusal). Daemons carry live connections -> the health-checked auto-rollback /api/deploy, NEVER a rename.
1492// The deny can never be overridden by any data plane or later rule.
1493// (2) the enumerated one-shot allows (back-compat fast path).
1494// (3) else STAGED-ARTIFACT rule: a name whose <name>.sov.elf.new exists as a real ELF is promotable. Staging is
1495// only reachable via the owner-gated /api/build//api/upload, so this widens promote to OWNER-only actions,
1496// never to a write cap -- the same stance as nx_fs_write denying the tool allowlist, without the
1497// add-a-name-recompile-mgmt treadmill for every new one-shot organ.
1498func md_contains(a: *u8, sub: *u8) -> i64 {
1499 var i: i64 = 0
1500 while a[i] != (0 as u8) {
1501 var j: i64 = 0
1502 var mism: i64 = 0
1503 var run: i64 = 1
1504 while run == 1 {
1505 if sub[j] == (0 as u8) { run = 0 } else {
1506 if a[i+j] == (0 as u8) { mism = 1; run = 0 } else {
1507 if a[i+j] != sub[j] { mism = 1; run = 0 } else { j = j + 1 }
1508 }
1509 }
1510 }
1511 if mism == 0 { return 1 }
1512 i = i + 1
1513 }
1514 return 0
1515}
1516// ---------- organ ROLE, declared (seq1492) ----------
1517//
1518// u2605THE DEFECT THIS REPLACES: promote classified by NAME SUBSTRING, so a
1519// one-shot worker called nx_torrent_get was refused as a "daemon" while
1520// /api/deploy refused it as an unknown target. **Two verbs disagreeing about an
1521// artefact's KIND leave it unshippable** -- and the only remaining way to update
1522// it was the raw scp/ssh path that seq1439 identified as the WORK-DESTROYER. A
1523// substring is not a role, exactly as a substring is not a hazard.
1524//
1525// Policy lives in DATA (rule 11): knowledge/status/organ_kind.conf, rows
1526// <name><TAB-or-SPACE>one-shot|oneshot|daemon|oracle|lib
1527// Returns 1 = one-shot (promotable), 2 = daemon/oracle (deploy lane), 0 = undeclared.
1528// Undeclared falls through to the historical name heuristics, so nothing regresses
1529// and the heuristic becomes the DEFAULT rather than the law.
1530//
1531// ★PERMANENT ROOT FIX 2026-07-31 (debt 1785453784, which /api/promote's own 400 text described but
1532// nobody had closed). TWO defects, and fixing only the first would have LOOKED right while still failing:
1533// 1. WRONG PATH. This opened the SINGULAR-less plural "knowledge/organ_kinds.conf", which was renamed to
1534// .RETIRED-seq1754-use-status-organ_kind. open() returned <0 -> return 0 -> EVERY organ fell through
1535// to the name heuristic, so NO declaration anywhere was readable and gate promotion was a coin flip.
1536// Now reads the surviving SSOT knowledge/status/organ_kind.conf. ONE source of truth, not two.
1537// 2. WRONG SEPARATOR. The surviving file is SPACE-separated (`nx_build_admit oneshot`) but this parser
1538// accepted ONLY a TAB (9), so a path-only fix would have found the file, parsed nothing, and still
1539// returned 0 -- a silent no-op that reads as success. Now takes the FIRST tab OR space.
1540// The value test below already tolerates both spellings: it checks only the leading "on", matching
1541// `oneshot` and `one-shot` alike. Verified against the real file before editing, not assumed.
1542func md_organ_kind(nm: *u8) -> i64 {
1543 let fd: i64 = sys_openat_rd("knowledge/status/organ_kind.conf" as *u8)
1544 if fd < 0 { return 0 }
1545 let buf: *u8 = sys_mmap(65536)
1546 let n: i64 = sys_read(fd, buf, 65536)
1547 sys_close(fd)
1548 if n <= 0 { return 0 }
1549 var nl: i64 = 0
1550 while nm[nl] != (0 as u8) { nl = nl + 1 }
1551 var ls: i64 = 0
1552 while ls < n {
1553 var le: i64 = ls
1554 var g: i64 = 1
1555 while g == 1 { if le >= n { g = 0 } else { if buf[le] == (10 as u8) { g = 0 } else { le = le + 1 } } }
1556 if le > ls { if buf[ls] != (35 as u8) {
1557 var sep: i64 = 0 - 1
1558 var q: i64 = ls
1559 while q < le {
1560 if sep < 0 {
1561 if buf[q] == (9 as u8) { sep = q }
1562 else { if buf[q] == (32 as u8) { sep = q } }
1563 }
1564 q = q + 1
1565 }
1566 if sep > ls {
1567 if sep - ls == nl {
1568 var same: i64 = 1
1569 var c: i64 = 0
1570 while c < nl { if buf[ls + c] != nm[c] { same = 0; c = nl } else { c = c + 1 } }
1571 if same == 1 {
1572 let vs: i64 = sep + 1
1573 if vs < le { if buf[vs] == (111 as u8) { if vs + 1 < le { if buf[vs + 1] == (110 as u8) { return 1 } } } }
1574 return 2
1575 }
1576 }
1577 }
1578 } }
1579 ls = le + 1
1580 }
1581 return 0
1582}
1583
1584// Does `w` occur in `nm` starting at a TOKEN BOUNDARY -- the start of the name,
1585// or immediately after '_'? Organ names are underscore-tokenised, so this asks
1586// "is one of the words in this name `w`" instead of "do these letters appear
1587// anywhere".
1588func mdh_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8) { n=n+1 } return n }
1589
1590func mdh_tail_eq(nm: *u8, n: i64, suf: *u8) -> i64 {
1591 let sl: i64 = mdh_len(suf)
1592 if sl > n { return 0 }
1593 var i: i64 = 0
1594 while i < sl { if nm[n - sl + i] != suf[i] { return 0 } i = i + 1 }
1595 return 1
1596}
1597
1598func md_tok_at(nm: *u8, w: *u8) -> i64 {
1599 let wl: i64 = mdh_len(w)
1600 let nl: i64 = mdh_len(nm)
1601 var i: i64 = 0
1602 while i + wl <= nl {
1603 var boundary: i64 = 0
1604 if i == 0 { boundary = 1 } else { if nm[i - 1] == (95 as u8) { boundary = 1 } }
1605 if boundary == 1 {
1606 var j: i64 = 0
1607 var m: i64 = 1
1608 while j < wl { if nm[i + j] != w[j] { m = 0; j = wl } else { j = j + 1 } }
1609 if m == 1 { return 1 }
1610 }
1611 i = i + 1
1612 }
1613 return 0
1614}
1615
1616// Does the name end in one of the verifier suffixes the system already treats as
1617// a declaration (_gate/_test/_kat -- the /api/gate_run bound)?
1618func md_name_is_oracle(nm: *u8) -> i64 {
1619 let n: i64 = mdh_len(nm)
1620 if mdh_tail_eq(nm, n, "_gate" as *u8) == 1 { return 1 }
1621 if mdh_tail_eq(nm, n, "_test" as *u8) == 1 { return 1 }
1622 if mdh_tail_eq(nm, n, "_kat" as *u8) == 1 { return 1 }
1623 return 0
1624}
1625
1626// NON-OVERRIDABLE deny: credential oracles and key material. A conf row must
1627// never be able to make these promotable -- otherwise the role registry becomes
1628// a privilege-escalation surface (config that grants authority).
1629//
1630// ★★★★★ ROOT-FIXED 2026-07-31 (debt 1785511766). This used to ask
1631// md_contains -- a RAW SUBSTRING -- which is the exact defect the sibling
1632// md_promote_deny was already fixed for at seq1789 ("THE SUFFIX IS A
1633// DECLARATION; THE SUBSTRING WAS A GUESS", the nx_survey_serve_gate collision),
1634// left unfixed one layer down here in the NON-OVERRIDABLE deny.
1635//
1636// "vault" means CREDENTIAL CUSTODY here, but as a substring it also matches the
1637// entire MEDIA vault family -- nx_mvault, nx_mvault_coll, nx_mvault_walk. One
1638// substring, two unrelated meanings. The result was that gate-proven media-vault
1639// binaries were unshippable by any sanctioned route, and the 2026-07-23 session
1640// resorted to an ssh cp/mv rename to ship them.
1641// ★★★★★**A GUARD THAT CANNOT BE SATISFIED PRODUCES A BYPASS, NOT SAFETY.**
1642//
1643// TWO CHANGES, BOTH STRICTLY SAFE -- this narrows FALSE positives only, and every
1644// real credential organ below still denies (proven by nx_promote_deny_gate):
1645// 1. TOKEN-BOUNDARY, not substring. "vault" still matches nx_vault_gateway (the
1646// word is a token) but no longer matches nx_mvault (the letters are merely
1647// inside one). A glued credential name like nx_secretstore STILL denies,
1648// because the boundary is checked at the START of the token only.
1649// 2. An ORACLE SUFFIX is exempt. Promoting nx_cap_mint_gate installs
1650// nx_cap_mint_gate.elf -- it CANNOT swap nx_cap_mint.elf -- so a verifier can
1651// never be the credential organ it verifies. Safe by construction, and it
1652// reuses the system's own _gate/_test/_kat rule rather than inventing one.
1653func md_promote_deny_hard(nm: *u8) -> i64 {
1654 if md_name_is_oracle(nm) == 1 { return 0 }
1655 if md_tok_at(nm, "mint" as *u8) == 1 { return 1 }
1656 if md_tok_at(nm, "vault" as *u8) == 1 { return 1 }
1657 if md_tok_at(nm, "secret" as *u8) == 1 { return 1 }
1658 if md_tok_at(nm, "keygen" as *u8) == 1 { return 1 }
1659 if md_tok_at(nm, "login" as *u8) == 1 { return 1 }
1660 return 0
1661}
1662
1663func md_promote_deny(nm: *u8) -> i64 {
1664 // u2605CONVERGED 2026-07-30 (seq1754). This used to consult its OWN role conf
1665 // (knowledge/organ_kinds.conf) -- a SECOND classifier for a concept a
1666 // sibling had already implemented properly as nx_organkind
1667 // (ok_kind_of_path over knowledge/status/organ_kind.conf), wired into the
1668 // promote handler ABOVE this function. Two confs and two readers for one
1669 // concept is the sprawl we keep warning about, and I built half of it by
1670 // not checking /code/tools before starting.
1671 //
1672 // The canonical reader now decides FIRST at the API layer; this function is
1673 // only reached for an UNDECLARED name, where it is the legacy name
1674 // heuristic -- so the duplicate lookup is removed and its rows were merged
1675 // into the canonical conf. deny_hard STAYS: credential oracles must be
1676 // refused non-overridably regardless of any declared kind.
1677 if md_promote_deny_hard(nm) == 1 { return 1 }
1678 if md_contains(nm, "serve" as *u8) == 1 { return 1 }
1679 if md_contains(nm, "daemon" as *u8) == 1 { return 1 }
1680 if md_contains(nm, "mint" as *u8) == 1 { return 1 }
1681 if md_contains(nm, "vault" as *u8) == 1 { return 1 }
1682 if md_contains(nm, "secret" as *u8) == 1 { return 1 }
1683 if md_contains(nm, "keygen" as *u8) == 1 { return 1 }
1684 if md_contains(nm, "login" as *u8) == 1 { return 1 }
1685 if md_contains(nm, "router" as *u8) == 1 { return 1 }
1686 if md_contains(nm, "hostctl" as *u8) == 1 { return 1 }
1687 if md_contains(nm, "signaling" as *u8) == 1 { return 1 }
1688 if md_contains(nm, "gateway" as *u8) == 1 { return 1 }
1689 if md_contains(nm, "torrent" as *u8) == 1 { return 1 }
1690 if md_contains(nm, "mgmt" as *u8) == 1 { return 1 }
1691 if md_contains(nm, "_gw" as *u8) == 1 { return 1 }
1692 if md_streq(nm, "sites" as *u8) == 1 { return 1 }
1693 return 0
1694}
1695func md_staged_elf_ok(nm: *u8) -> i64 {
1696 let p: *u8 = sys_mmap(192)
1697 var o: i64 = 0
1698 var i: i64 = 0
1699 while nm[i] != (0 as u8) { p[o] = nm[i]; o = o + 1; i = i + 1 }
1700 let sfx: *u8 = ".sov.elf.new" as *u8
1701 var j: i64 = 0
1702 while sfx[j] != (0 as u8) { p[o] = sfx[j]; o = o + 1; j = j + 1 }
1703 p[o] = 0 as u8
1704 let fd: i64 = sys_openat_rd(p)
1705 if fd < 0 { return 0 }
1706 let hb: *u8 = sys_mmap(8)
1707 let r: i64 = sys_read(fd, hb, 4)
1708 sys_close(fd)
1709 if r != 4 { return 0 }
1710 if hb[0] != (127 as u8) { return 0 }
1711 if hb[1] != (69 as u8) { return 0 }
1712 if hb[2] != (76 as u8) { return 0 }
1713 if hb[3] != (70 as u8) { return 0 }
1714 return 1
1715}
1716func md_promote_organ_ok(nm: *u8) -> i64 {
1717 if md_promote_deny(nm) == 1 { return 0 }
1718 if md_streq(nm, "nx_ecosystem_maturity_rollup" as *u8) == 1 { return 1 }
1719 if md_streq(nm, "nx_ecomat_seed" as *u8) == 1 { return 1 }
1720 if md_streq(nm, "nx_ecomat_beat" as *u8) == 1 { return 1 }
1721 if md_streq(nm, "nx_ecomat_page" as *u8) == 1 { return 1 }
1722 if md_streq(nm, "nx_tool_argecho" as *u8) == 1 { return 1 }
1723 // 07-17 (eat the ssh-once deploy debt): the fork-exec MCP TOOL organ family -- one-shot elfs the
1724 // tools daemon spawns per call. NOT daemons (those stay refused -> /api/deploy) and NOT the
1725 // credential oracles (nx_session_mint / nx_cap_mint stay OFF this list deliberately).
1726 if md_streq(nm, "nx_shelltool" as *u8) == 1 { return 1 }
1727 if md_streq(nm, "nx_common_tasks" as *u8) == 1 { return 1 }
1728 if md_streq(nm, "nx_frontier_board" as *u8) == 1 { return 1 }
1729 if md_streq(nm, "nx_page_verify" as *u8) == 1 { return 1 }
1730 if md_streq(nm, "nx_store_seed" as *u8) == 1 { return 1 }
1731 if md_streq(nm, "nx_workflow" as *u8) == 1 { return 1 }
1732 if md_streq(nm, "nx_memory" as *u8) == 1 { return 1 }
1733 if md_streq(nm, "nx_heal" as *u8) == 1 { return 1 }
1734 if md_streq(nm, "nx_fs" as *u8) == 1 { return 1 }
1735 if md_streq(nm, "nx_fs_write" as *u8) == 1 { return 1 }
1736 if md_streq(nm, "nx_site_publish" as *u8) == 1 { return 1 }
1737 if md_streq(nm, "nx_https_get" as *u8) == 1 { return 1 }
1738 if md_streq(nm, "nx_verify" as *u8) == 1 { return 1 }
1739 // 07-17 (stem-first-byte-fab lane): the compare-publish pipeline organs -> API-promotable, so a
1740 // brand-new compare domain publishes end-to-end over MCP (build -> promote -> regen), zero shell.
1741 if md_streq(nm, "nx_compare_regen" as *u8) == 1 { return 1 }
1742 if md_streq(nm, "nx_swcompare_matrix" as *u8) == 1 { return 1 }
1743 if md_streq(nm, "nx_swcompare_sota" as *u8) == 1 { return 1 }
1744 if md_streq(nm, "nx_swcompare_hub" as *u8) == 1 { return 1 }
1745 if md_streq(nm, "nx_maturity_board" as *u8) == 1 { return 1 }
1746 // (3) staged-artifact rule: owner-staged one-shot builds are promotable (deny above already refused
1747 // every daemon/oracle shape, so this can only ever admit tool-organ names).
1748 if md_staged_elf_ok(nm) == 1 { return 1 }
1749 return 0
1750}
1751
1752// ---- seq1484: PROMOTION PROVENANCE -- a promote may not walk a binary BACKWARDS ------------------
1753// THE BLEED THIS STOPS (measured 2026-07-30): the mgmt API was reverted THREE times and the compiler
1754// TWICE in a single session, each time by promoting a binary built elsewhere from a stale tree. Every
1755// existing control passed it: the ELF is valid, the size is plausible, and promote_toolchain's canary
1756// COMPILES AND RUNS it GREEN -- because a stale-but-working binary does all of that perfectly.
1757// ★LIVENESS IS NOT CURRENCY. "It works" cannot distinguish the newest build from last week's.
1758//
1759// The invariant that CAN is the same one already protecting sources (nx_symdrop) and tree pushes
1760// (nx_treepack REFUSED-WOULD-DROP-SYMBOLS): a normal promotion installs content this target has NEVER
1761// held; a revert installs content it ALREADY HELD. So keep an append-only per-target content-hash
1762// history. Staged == newest -> a harmless re-promote, allowed. Staged never seen -> a real advance,
1763// allowed and recorded. Staged matches an EARLIER generation -> THE BINARY WOULD GO BACKWARDS, refused
1764// and named. No build-time provenance, no clock, no size heuristic, nothing to spoof by touching a file.
1765//
1766// ESCAPE HATCH BY DESIGN, NOT BY FLAG: going backwards deliberately is what /api/rollback is FOR, and it
1767// does not route through here. A `force` parameter would just be the hole re-opened under a nicer name.
1768const MD_PROV_HIST: *u8 = "knowledge/promote_history.tsv" as *u8
1769const MD_PROV_CAP: i64 = 1048576
1770const MD_PROV_RDCH: i64 = 262144
1771const MD_PROV_FNV_OFF: i64 = 1469598103934665603
1772const MD_PROV_FNV_PRM: i64 = 1099511628211
1773const MD_PROV_TAB: i64 = 9
1774const MD_PROV_NL: i64 = 10
1775
1776// FNV-1a over a whole file, streamed so a large ELF needs no full-size buffer. 0 = unreadable.
1777func md_prov_hash(path: *u8) -> i64 {
1778 let fd: i64 = sys_openat_rd(path)
1779 if fd < 0 { return 0 }
1780 let b: *u8 = sys_mmap(MD_PROV_RDCH)
1781 var h: i64 = MD_PROV_FNV_OFF
1782 var go: i64 = 1
1783 while go == 1 {
1784 let n: i64 = sys_read(fd, b, MD_PROV_RDCH)
1785 if n <= 0 { go = 0 } else {
1786 var i: i64 = 0
1787 while i < n { h = h ^ (b[i] as i64); h = h * MD_PROV_FNV_PRM; i = i + 1 }
1788 }
1789 }
1790 sys_close(fd)
1791 return h
1792}
1793// Walk the history for `name`. out3[0]=generations seen, out3[1]=1 if h is the NEWEST, out3[2]=index of
1794// an EARLIER generation equal to h (-1 if none).
1795func md_prov_probe(name: *u8, h: i64, out3: *i64) -> i64 {
1796 out3[0] = 0; out3[1] = 0; out3[2] = 0 - 1
1797 let buf: *u8 = sys_mmap(MD_PROV_CAP)
1798 let n: i64 = dp_read(MD_PROV_HIST, buf, MD_PROV_CAP - 1)
1799 if n <= 0 { return 0 }
1800 var nl: i64 = 0
1801 while name[nl] != (0 as u8) { nl = nl + 1 }
1802 var gen: i64 = 0
1803 var ls: i64 = 0
1804 var i: i64 = 0
1805 while i <= n {
1806 var eol: i64 = 0
1807 if i == n { eol = 1 } else { if buf[i] == (MD_PROV_NL as u8) { eol = 1 } }
1808 if eol == 1 {
1809 if i > ls {
1810 var tab: i64 = 0 - 1
1811 var t: i64 = ls
1812 while t < i { if buf[t] == (MD_PROV_TAB as u8) { tab = t; t = i } else { t = t + 1 } }
1813 if tab > 0 { if tab - ls == nl {
1814 var m: i64 = 1
1815 var c: i64 = 0
1816 while c < nl { if buf[ls+c] != name[c] { m = 0; c = nl } else { c = c + 1 } }
1817 if m == 1 {
1818 var v: i64 = 0
1819 var neg: i64 = 0
1820 var k: i64 = tab + 1
1821 if k < i { if buf[k] == (45 as u8) { neg = 1; k = k + 1 } }
1822 while k < i { v = v * 10 + ((buf[k] as i64) - 48); k = k + 1 }
1823 if neg == 1 { v = 0 - v }
1824 if v == h { out3[2] = gen; out3[1] = 1 } else { out3[1] = 0 }
1825 gen = gen + 1
1826 }
1827 } }
1828 }
1829 ls = i + 1
1830 }
1831 i = i + 1
1832 }
1833 out3[0] = gen
1834 return gen
1835}
1836func md_prov_record(name: *u8, h: i64) -> i64 {
1837 let line: *u8 = sys_mmap(512)
1838 var o: i64 = 0
1839 var i: i64 = 0
1840 while name[i] != (0 as u8) { line[o] = name[i]; o = o + 1; i = i + 1 }
1841 line[o] = MD_PROV_TAB as u8; o = o + 1
1842 var m: i64 = h
1843 if m < 0 { line[o] = 45 as u8; o = o + 1; m = 0 - m }
1844 let t: *u8 = sys_mmap(32)
1845 var k: i64 = 0
1846 if m == 0 { t[0] = 48 as u8; k = 1 }
1847 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
1848 var z: i64 = k - 1
1849 while z >= 0 { line[o] = t[z]; o = o + 1; z = z - 1 }
1850 line[o] = MD_PROV_NL as u8; o = o + 1
1851 let fd: i64 = sys_openat_append(MD_PROV_HIST, 0x1a4)
1852 if fd < 0 { return 0 - 1 }
1853 sys_write(fd, line, o)
1854 sys_fsync(fd)
1855 sys_close(fd)
1856 return 0
1857}
1858// 1 = this staged content may be promoted; 0 = it would walk `name` BACKWARDS.
1859func md_prov_ok(name: *u8, stagedpath: *u8) -> i64 {
1860 let h: i64 = md_prov_hash(stagedpath)
1861 if h == 0 { return 1 } // unreadable: leave the decision to the existing checks
1862 let p: *i64 = sys_mmap(64) as *i64
1863 md_prov_probe(name, h, p)
1864 if p[0] == 0 { md_prov_record(name, h); return 1 } // first sighting = the baseline
1865 if p[1] == 1 { return 1 } // identical to the newest = harmless re-promote
1866 if p[2] >= 0 { return 0 } // seen EARLIER but not newest = A REVERT
1867 md_prov_record(name, h)
1868 return 1
1869}
1870
1871// STAGING HYGIENE (2026-08-06, debt 1785531571). md_prov_ok above refuses only content this target
1872// ALREADY HELD, so a staged artifact that was never itself promoted is a FIRST SIGHTING: md_prov_ok
1873// records it as the baseline and returns ALLOW -- even when it PREDATES the live binary it replaces.
1874// MEASURED on this deploy root the same day (nx_staghyg scan): 584 staged artifacts, 88 with a live
1875// counterpart, 31 of those OLDER than the binary they would replace, and 7 that would DROP live
1876// capability tokens DESPITE BEING LARGER -- so neither mtime nor size alone catches the class.
1877// allow_loss=1 is the deliberate operator override (/api/promote allow_capability_loss=yes); 0 is
1878// fail-closed. Placed BEFORE any rename, so a refusal leaves live, .prev AND the staged file untouched.
1879// POST-PROMOTE MIRROR REFRESH (2026-08-14, debt 1786758132). Forks nx_offc_install <bare> refresh.
1880// ★SAFE TO CALL AUTOMATICALLY BECAUSE THE `refresh` VERB CREATES NOTHING: it installs the promoted
1881// binary only where an _offc mirror ALREADY EXISTS (proven both directions -- nx_resmon REFRESHED,
1882// nx_memvel_gate SKIP). An unconditional install here would quietly WIDEN what runners can fork.
1883func md_exec_offc_refresh(bare: *u8) -> i64 {
1884 let oelf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_offc_install.elf" as *u8
1885 let args: *i64 = sys_mmap(16) as *i64
1886 args[0] = bare as i64
1887 args[1] = "refresh" as *u8 as i64
1888 return dep_run(oelf, args, 2)
1889}
1890
1891func md_promote_staged_ex(name: *u8, allow_loss: i64) -> i64 {
1892 let live: *u8 = sys_mmap(160)
1893 let newp: *u8 = sys_mmap(160)
1894 let prevp: *u8 = sys_mmap(160)
1895 var lo: i64 = 0
1896 while name[lo] != (0 as u8) { live[lo] = name[lo]; newp[lo] = name[lo]; prevp[lo] = name[lo]; lo = lo + 1 }
1897 live[lo] = 0 as u8
1898 md_copy_slice_z(newp, name, 0, lo, 160)
1899 md_copy_slice_z(prevp, name, 0, lo, 160)
1900 var no: i64 = lo
1901 let ns: *u8 = ".new" as *u8
1902 var a: i64 = 0
1903 while ns[a] != (0 as u8) { newp[no] = ns[a]; no = no + 1; a = a + 1 }
1904 newp[no] = 0 as u8
1905 // does <name>.new exist?
1906 let nfd: i64 = sys_openat_rd(newp)
1907 if nfd < 0 { return 0 }
1908 sys_close(nfd)
1909 // seq1484 PROVENANCE: refuse a promotion that would walk this target BACKWARDS to content
1910 // it already held. Placed BEFORE any rename, so a refusal leaves live and .prev untouched.
1911 if md_prov_ok(live, newp) == 0 { return 0 - 2 }
1912 if allow_loss == 0 {
1913 let sgcfg: *i64 = sys_mmap(8 * SG_C_SLOTS) as *i64
1914 sg_conf_load(sgcfg)
1915 let sgout: *i64 = sys_mmap(8 * SG_OUT_SLOTS) as *i64
1916 let sgcls: i64 = sg_classify(live, newp, sys_now_realtime_sec(), sgcfg, sgout)
1917 if sg_allows(sgcls) == 0 { return 0 - 3 }
1918 }
1919 var po: i64 = lo
1920 let ps: *u8 = ".prev" as *u8
1921 a = 0
1922 while ps[a] != (0 as u8) { prevp[po] = ps[a]; po = po + 1; a = a + 1 }
1923 prevp[po] = 0 as u8
1924 md_rotate_prev(prevp) // rotate .prev -> .prev2 FIRST: a 2nd promote must not destroy the original
1925 sys_renameat(live, prevp) // keep the old live as .prev (rollback)
1926 sys_renameat(newp, live) // promote .new -> live
1927 nx_chmod(live, 0x1ed)
1928 // ---- MAINTAIN THE _offc MIRROR (2026-08-14, debt 1786758132) --------------------------------
1929 // THE INVARIANT: _offc/<n>.elf must equal the promoted root binary. nx_job_run and organ-to-organ
1930 // forks resolve _offc/ while promote writes the ROOT, so WITHOUT THIS a promote reports success
1931 // while every caller keeps executing the OLD artifact, with no signal anywhere.
1932 // MEASURED THE DAY THIS LANDED: 20 stale _offc artifacts including the assembler, the build runner
1933 // and nx_mgmt_api itself -- and a stale copy of a CORRECT security scanner read as a VACUOUS
1934 // detector convincingly enough that a sev-8 was filed against the wrong thing.
1935 // ★HOOKED HERE, NOT IN ma_do_promote, BECAUSE THIS IS THE ONE RENAME: /api/restart and /api/deploy
1936 // reach this function WITHOUT passing through the promote handler, so hooking the handler would have
1937 // fixed one path of four.
1938 // ★THE RETURN VALUE IS DELIBERATELY IGNORED AND THE CALL IS LAST: the rename above has ALREADY
1939 // succeeded, so a mirror failure must never un-promote or fail a promotion that worked. Worst case
1940 // the mirror stays stale and the offccensus beat reports it -- which is exactly the pre-existing
1941 // state, so this can only improve on it.
1942 if lo > 4 {
1943 let bare: *u8 = sys_mmap(160)
1944 md_copy_slice_z(bare, name, 0, lo - 4, 160)
1945 md_exec_offc_refresh(bare)
1946 }
1947 return 1
1948}
1949
1950// BACK-COMPAT WRAPPER: every existing call site keeps its exact signature and gets the guard by default
1951// (fail-closed). Only a caller that DELIBERATELY passes the override reaches _ex with allow_loss=1 --
1952// so wiring the guard cannot be forgotten at a call site, which is the built-but-not-wired class this
1953// ecosystem keeps rediscovering. Returns: 1 promoted, 0 nothing staged, -2 provenance revert, -3 staging hygiene.
1954func md_promote_staged(name: *u8) -> i64 { return md_promote_staged_ex(name, 0) }
1955// ---- TOOLCHAIN PROMOTE (never-brick) -- eats seq891/seq903 -------------------------------------
1956// THE GAP THIS CLOSES: the ecosystem could build and deploy every SERVICE over its own API but could
1957// NOT update the COMPILER that builds them, so a PROVEN compiler fix could not be landed API-first
1958// (rule 27). Measured cost on 2026-07-30: nx_fnptr_slot_probe was GREEN on the laptop compiler and RED
1959// on the hub compiler, i.e. obj.fn_field(args) silently emitted no indirect call for every organ in the
1960// tree, and the fix existed but had nowhere to go. A toolchain you cannot update is a toolchain whose
1961// bugs are permanent.
1962//
1963// ⚠THE PATH NOT TAKEN (seq903, and it must stay not-taken): shipping the toolchain through
1964// /api/upload + /api/unpack looks tempting because it touches only this module. nx_treepack writes every
1965// output file 0644 NON-EXECUTABLE and UNLINKS-then-recreates on any open failure, so unpacking over
1966// buildroot/_offc/nx_cc_sovereign.elf would either install a non-executable compiler or destroy the live
1967// one -- EVERY BUILD FOR EVERY SEAT, from a call that looks like a routine source sync.
1968//
1969// ⚠MATCHED-PAIR RULE (seq1315): nx_sov_build_run writes _build/<name>.sov.elf while nx_hostctl
1970// cmd_buildrun reads /tmp/<name>.sov.elf, so those two may only ever be promoted TOGETHER. They are
1971// admitted here because that pair-ship is a legitimate wave -- and admitting them is SAFE precisely
1972// because the caller canary-compiles and auto-rolls-back, so a mismatched pair cannot survive a promote.
1973//
1974// DIALECT NOTE: plain-if (no `else`), no empty string literals, <=6 params. This module is IMPORTED by
1975// nx_mgmt_api, and it must be compiled by TODAY'S hub compiler -- the one that still carries seq533
1976// (imported `else` desyncs the parser), seq907 (an empty literal aliases the next literal) and seq239
1977// (>6 params mishandled). The fix ships in a binary that the defect itself has to be able to build.
1978const MD_TC_MIN_ELF: i64 = 4096 // size floor: refuse a truncated upload or an HTML error page
1979const MD_TC_MODE_EXEC: i64 = 0x1ed // 0755 -- a compiler that is not executable is a dead ecosystem
1980
1981func md_toolchain_target_ok(nm: *u8) -> i64 {
1982 if md_streq(nm, "nx_cc_sovereign.elf" as *u8) == 1 { return 1 }
1983 if md_streq(nm, "nxasm_x86_main.elf" as *u8) == 1 { return 1 }
1984 if md_streq(nm, "nx_sov_build_run.elf" as *u8) == 1 { return 1 }
1985 return 0
1986}
1987
1988func md_tc_live(nm: *u8, buf: *u8) -> i64 {
1989 var o: i64 = md_cmp_cat(buf, 0, "buildroot/_offc/" as *u8)
1990 o = md_cmp_cat(buf, o, nm)
1991 return o
1992}
1993func md_tc_prev(nm: *u8, buf: *u8) -> i64 {
1994 var o: i64 = md_tc_live(nm, buf)
1995 o = md_cmp_cat(buf, o, ".prev" as *u8)
1996 return o
1997}
1998func md_tc_staged(nm: *u8, buf: *u8) -> i64 {
1999 var o: i64 = md_cmp_cat(buf, 0, nm)
2000 o = md_cmp_cat(buf, o, ".new" as *u8)
2001 return o
2002}
2003
2004// ELF magic + size floor. Returns the byte size on success, 0 on refusal. Validating the ARTIFACT (not
2005// an exit code) is the seq363/hostctl lesson: a 0-byte or non-ELF file must never reach the live slot.
2006func md_tc_elf_size(p: *u8) -> i64 {
2007 let fd: i64 = sys_openat_rd(p)
2008 if fd < 0 { return 0 }
2009 let sz: i64 = sys_lseek(fd, 0, 2)
2010 if sz < MD_TC_MIN_ELF { sys_close(fd); return 0 }
2011 sys_lseek(fd, 0, 0)
2012 let hb: *u8 = sys_mmap(8)
2013 var ok: i64 = 0
2014 if sys_read(fd, hb, 4) == 4 {
2015 if hb[0] == (0x7f as u8) {
2016 if hb[1] == (69 as u8) {
2017 if hb[2] == (76 as u8) {
2018 if hb[3] == (70 as u8) { ok = 1 }
2019 }
2020 }
2021 }
2022 }
2023 sys_close(fd)
2024 if ok == 0 { return 0 }
2025 return sz
2026}
2027
2028// Install staged <nm>.new -> buildroot/_offc/<nm>, banking the outgoing binary as .prev FIRST.
2029// Returns the installed size, or 0 if nothing was touched. Ordering is deliberate: validate BEFORE
2030// renaming anything, so a refused upload leaves the live compiler completely untouched.
2031const MD_TC_ERR_BACKUP: i64 = 0 - 3
2032func md_tc_install(nm: *u8) -> i64 {
2033 let stagedp: *u8 = sys_mmap(256)
2034 let livep: *u8 = sys_mmap(256)
2035 let prevp: *u8 = sys_mmap(256)
2036 md_tc_staged(nm, stagedp)
2037 md_tc_live(nm, livep)
2038 md_tc_prev(nm, prevp)
2039 let sz: i64 = md_tc_elf_size(stagedp)
2040 if sz == 0 { return 0 }
2041 // seq1484: the canary proves the incoming toolchain WORKS, which a stale-but-working one
2042 // also does. Provenance is what proves it is not last week s build. Checked before any rename.
2043 if md_prov_ok(livep, stagedp) == 0 { return 0 - 2 }
2044 // A failed bank must never consume the candidate or overwrite the live compiler.
2045 let previous: i64=sys_openat_rd(prevp)
2046 if previous >= 0 {
2047 sys_close(previous)
2048 if md_rotate_prev(prevp) != 1 { return MD_TC_ERR_BACKUP }
2049 }
2050 if previous < 0 { if previous != (0-2) { return MD_TC_ERR_BACKUP } }
2051 if sys_renameat(livep, prevp) != 0 { return MD_TC_ERR_BACKUP }
2052 if sys_renameat(stagedp, livep) != 0 {
2053 sys_renameat(prevp, livep) // stage-rename failed: put the old one straight back
2054 nx_chmod(livep, MD_TC_MODE_EXEC)
2055 return 0
2056 }
2057 nx_chmod(livep, MD_TC_MODE_EXEC)
2058 return sz
2059}
2060
2061
2062// Restore buildroot/_offc/<nm>.prev -> live. This is the rollback half of never-brick and it is called
2063// on CANARY FAILURE, so the ecosystem can never be left with a compiler that cannot compile.
2064func md_tc_rollback(nm: *u8) -> i64 {
2065 let livep: *u8 = sys_mmap(256)
2066 let prevp: *u8 = sys_mmap(256)
2067 md_tc_live(nm, livep)
2068 md_tc_prev(nm, prevp)
2069 if md_tc_elf_size(prevp) == 0 { return 0 }
2070 if sys_renameat(prevp, livep) != 0 { return 0 }
2071 nx_chmod(livep, MD_TC_MODE_EXEC)
2072 return 1
2073}
2074
2075// Our own pid. getpid = syscall 39 on x86-64.
2076func md_self_pid() -> i64 { return __syscall(172, 0, 0, 0, 0, 0, 0) } // rv64 getpid=172. Was raw x86 39, which IS an RV64 KEY (umount2) the backend translated to ioctl(16) -> -ENOTTY, so the mgmt API's own pid was -25 (debt idx 2277)
2077// Fork a detached child that waits, then SIGTERMs the given pid. Used so a self-restart can FINISH WRITING
2078// ITS RESPONSE before the process goes away: the reply reaches the caller, then the guard respawns the
2079// already-promoted binary. SIGTERM (not KILL) so a daemon that later grows a drain handler gets to use it.
2080func md_delayed_kill(pid: i64, delay_ms: i64) -> i64 {
2081 let p: i64 = sys_fork()
2082 if p == 0 {
2083 nx_setsid()
2084 sys_sleep_ms(delay_ms)
2085 nx_kill(pid, 15)
2086 sys_exit(0)
2087 }
2088 return p
2089}
2090func md_kill_by_name(needle: *u8) -> i64 {
2091 var self_hit: i64 = 0
2092 let nn: i64 = md_len(needle)
2093 let fd: i64 = sys_openat_rd("/proc" as *u8)
2094 if fd < 0 { return 0 }
2095 let buf: *u8 = sys_mmap(65536)
2096 let path: *u8 = sys_mmap(256)
2097 let clbuf: *u8 = sys_mmap(8192)
2098 var killed: i64 = 0
2099 var run: i64 = 1
2100 while run == 1 {
2101 let n: i64 = sys_getdents64(fd, buf, 65536)
2102 if n <= 0 { run = 0 } else {
2103 var off: i64 = 0
2104 while off < n {
2105 let rec: *u8 = ((buf as i64 + off) as *u8)
2106 let reclen: i64 = dirent_reclen(rec)
2107 if reclen <= 0 { off = n } else {
2108 let name: *u8 = dirent_name(rec)
2109 if name[0] >= (48 as u8) { if name[0] <= (57 as u8) {
2110 var p: i64 = 0
2111 let pre: *u8 = "/proc/" as *u8
2112 var a: i64 = 0
2113 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 }
2114 a = 0
2115 while name[a] != (0 as u8) { path[p] = name[a]; p = p + 1; a = a + 1 }
2116 let suf: *u8 = "/cmdline" as *u8
2117 a = 0
2118 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 }
2119 path[p] = 0 as u8
2120 let cfd: i64 = sys_openat_rd(path)
2121 if cfd >= 0 {
2122 let cln: i64 = sys_read(cfd, clbuf, 8192)
2123 sys_close(cfd)
2124 if cln > 0 { if md_pk_contains(clbuf, cln, needle, nn) == 1 {
2125 // ---- R5 SEQUENCING: NEVER SIGKILL OURSELVES MID-RESPONSE ----------------
2126 // TWO defects, one root. (1) mgmt restarting/deploying ITSELF matches its own
2127 // cmdline here and SIGKILLs the process that is writing the reply -- that IS
2128 // the FETCH-FAIL every /api/deploy returns (~12x in one session), and it is why
2129 // the seq1563 deploy lease strands (we die before reaching our own release).
2130 // (2) NEW with SO_REUSEPORT: a hot restart runs old and new under the SAME
2131 // cmdline, so a name-matched kill would murder the freshly-spawned instance too
2132 // -- adopting REUSEPORT without this turns a handoff into an outage.
2133 // So: skip our own pid here, remember it, and schedule a DELAYED self-exit
2134 // after the loop. The reply is written first, THEN we go; the supervise guard
2135 // respawns the already-promoted binary. FETCH-FAIL becomes a real JSON body.
2136 let vpid: i64 = md_pk_atoi(name)
2137 if vpid == md_self_pid() { self_hit = 1 } else {
2138 nx_kill(vpid, 9)
2139 killed = killed + 1
2140 }
2141 } }
2142 }
2143 } }
2144 off = off + reclen
2145 }
2146 }
2147 }
2148 }
2149 sys_close(fd)
2150 // We matched OURSELVES: schedule the exit for AFTER the response is on the wire. 1500ms is the
2151 // reply-write window, not a guess at compile time -- the caller returns immediately after this.
2152 // Counted in `killed` so the JSON stays honest about what is going away.
2153 if self_hit == 1 { md_delayed_kill(md_self_pid(), 1500); killed = killed + 1 }
2154 return killed
2155}
2156
2157// ---- secondary adapters: validate / exec / probe ----------------------------------------------------
2158func md_validate_artifact(path: *u8, kind: i64) -> i64 { return dep_validate(path, kind) }
2159
2160// drive the proven allowlisted nx_aw_hostctl with one sub -> its exit code (the supervisor/deploy exec port).
2161func md_exec_hostctl(sub: *u8) -> i64 {
2162 // Run the ON-NAS supervisor CLI directly. The mgmt daemon runs under the (root) supervisor, so it can drive the
2163 // surgical restart subs. The prior "_offc/nx_aw_hostctl.elf" is the LAPTOP->NAS bridge and is ABSENT on the NAS,
2164 // so /api/restart + /api/deploy were gate-proven but never live-executable. Absolute path (mgmt cwd=nishihost).
2165 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl" as *u8
2166 let args: *i64 = sys_mmap(16) as *i64
2167 args[0] = sub as i64
2168 return dep_run(helf, args, 1)
2169}
2170
2171// P2 off-LAN parity: run one allowlisted hostctl sub and CAPTURE its stdout to outpath (for /api/hostctl -> the
2172// phone gets torstat/routerctl/status output). Same on-NAS nx_hostctl the deploy path uses; single argv element
2173// (execve, no shell) so no injection; the allowlist below fail-closes to a curated safe read/action set.
2174func md_exec_hostctl_capture(sub: *u8, outpath: *u8) -> i64 {
2175 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl" as *u8
2176 let args: *i64 = sys_mmap(16) as *i64
2177 args[0] = sub as i64
2178 return dep_run_capture(helf, args, 1, outpath)
2179}
2180// 2-arg variant (e.g. `buildrun <target>`): run the on-NAS nx_hostctl <sub> <arg>, capture stdout -> outpath.
2181func md_exec_hostctl_capture2(sub: *u8, arg: *u8, outpath: *u8) -> i64 {
2182 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl" as *u8
2183 let args: *i64 = sys_mmap(16) as *i64
2184 args[0] = sub as i64
2185 args[1] = arg as i64
2186 return dep_run_capture(helf, args, 2, outpath)
2187}
2188// ---- BUILD ADMISSION (seq708/768/1390) --------------------------------------------------------
2189// THE INCIDENT THIS PREVENTS: /api/build is the one heavyweight mgmt op -- it forks the sovereign nx_cc
2190// toolchain to compile a source tree -- and it did so with NO memory admission. Under a build-heavy
2191// session the host runs out of memory and the supervisor OOM-reaps nx_mgmt_api, taking the WHOLE deploy
2192// path down for every seat. Confirmed twice (seq698/708) and REPRODUCED LIVE 2026-07-30 (seq1390):
2193// both transports died mid-session (503 / status=0, TLS fine so the EDGE was healthy and the BACKEND
2194// was gone) and self-recovered only when the guard respawned it.
2195//
2196// ⚠seq768 recorded this fix as "WRITTEN + BUILT + VERIFIED, staged awaiting one rename" and was marked
2197// EATEN -- but on 2026-07-30 md_exec_build_admit / ma_emit_503 / the ma_do_build call site were found in
2198// NEITHER the laptop SSOT NOR the NAS buildroot (grep: 0 matches across 6972 files). The work never
2199// reached a source tree, so every build-heavy session kept re-rolling the outage. Rebuilt here, in the
2200// SSOT, where a rebuild cannot lose it. ★LAW: a debt is not eaten until its fix is IN A SOURCE TREE --
2201// "built and staged" is not landed, and a binary nobody can rebuild is a rumour.
2202//
2203// FAIL-OPEN BY DESIGN (rule 26 / F881 ratchet stance): nx_build_admit exits 0 GRANT / 3 DENY (below the
2204// memory floor) / 4 QUEUE (load ceiling) / 2 usage / 5 unreadable-proc. We block ONLY on exit 3, the
2205// definitive memory wedge that actually causes the incident. Load-queueing and unreadable /proc both
2206// fall through to GRANT so admission control can never soft-brick the ecosystem's build path -- a
2207// refused-when-it-should-have-built is a worse failure here than an occasional reap.
2208// Floor 1024 MB; load ceiling deliberately huge so MEMORY is the sole gate (the measured cause).
2209// THE ADMISSION ENVELOPE LIVES IN ONE FILE (2026-08-18): knowledge/build_admit.conf, read by nx_build_admit
2210// itself on every check (argv > conf > host-derived). This path passes NO thresholds any more -- for one
2211// day it carried them as named consts here, which was still a second copy beside nx_sov_build_run's and
2212// nx_orchestrate's bare `headroom:`; three callers on one conf cannot disagree. A caller that MEANS a
2213// different envelope still says so on argv, and the report line prints envelope_src so it shows.
2214func md_exec_build_admit() -> i64 {
2215 let belf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_build_admit.elf" as *u8
2216 let bf: i64 = sys_openat_rd(belf)
2217 if bf < 0 { return 0 } // detector absent -> GRANT (never block on a missing guard)
2218 sys_close(bf)
2219 let args: *i64 = sys_mmap(32) as *i64
2220 args[0] = "check" as *u8 as i64
2221 return dep_run_capture(belf, args, 1, "/tmp/nx_build_admit.out" as *u8)
2222}
2223
2224// ---- GATE-DRY RATCHET RUNNER (2026-07-31, debts 1785529506 / 1785530277) -----------------------
2225// L009 -- gate organs that hand-roll their verdict instead of inheriting nx_gate_verdict -- is not
2226// merely large, it is GROWING: two warden scans hours apart on 2026-07-31 read 2035/2167 then
2227// 2041/2182. A migration campaign that only removes old breaches LOSES to a tree that adds new ones,
2228// so D001 cannot be closed by migrating alone. The 2026 practice for exactly this shape is a RATCHET
2229// (Notion bans an INCREASE in violation count and requires a deliberate re-bank).
2230//
2231// u26a0AND THE PREDECESSOR THIS WAS SUPPOSED TO COPY DOES NOT EXIST. nx_magicratchet is asserted "wired
2232// into /api/build" in FOUR comments in nx_law_warden.nx, but grep finds ZERO call sites in this source
2233// AND ZERO in the deployed mgmt binary, and a two-build experiment (clean -> BUILT, +3 literals >=1024
2234// -> BUILT, not refused) proves it never fires. So this is written fresh, not modelled on prose.
2235//
2236// FAIL-OPEN, the same stance as build admission and the pre-deploy gate: a missing or unreadable
2237// detector returns -1 and the caller proceeds. A guard that cannot be read must never wedge the build
2238// path for every seat.
2239func md_exec_gatedry(srcpath: *u8) -> i64 {
2240 let gelf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_gatedry.elf" as *u8
2241 let gf: i64 = sys_openat_rd(gelf)
2242 if gf < 0 { return 0 - 1 }
2243 sys_close(gf)
2244 let args: *i64 = sys_mmap(16) as *i64
2245 args[0] = srcpath as i64
2246 return dep_run_capture(gelf, args, 1, "/tmp/nx_gatedry.out" as *u8)
2247}
2248
2249// ---- F881 RULE-11 RATCHET RUNNER (2026-08-15, debt 1785530277) ---------------------------------
2250// The wiring four comments in nx_law_warden.nx asserted as fact, and which the comment above correctly
2251// records as never having existed. Confirmed absent TWICE today before writing this: nx_wiredclaim
2252// reports CLAIMED-UNWIRED call_sites=0, and behaviourally four real /api/build runs of one organ left
2253// its baseline at -1 while a single direct call created one. The chokepoint was exercised; nothing
2254// refused.
2255//
2256// WHY THIS IS SAFE WHERE L009 WAS NOT, and the difference is the whole reason it can land: L009 keyed
2257// "is this gate NEW?" on "has no deployed .elf", which was false for ~94pc of the corpus and turned the
2258// ratchet into a WALL at a shared chokepoint. nx_magicratchet uses the model that comment says a correct
2259// one needs -- a BANKED PER-ORGAN BASELINE, refusing only an INCREASE against the record. First sight of
2260// any organ self-baselines and GRANTS, so the existing corpus passes by construction.
2261//
2262// It was NOT safe to wire before today. Measured and fixed 2026-08-15, both in nx_magicratchet:
2263// - its parser skipped a leading '-', so countfile's -1 (UNMEASURED) read as a COUNT OF 1 and the
2264// fail-open branch could never fire;
2265// - it resolved _hdl_build/<n>.nx unconditionally, blind to every organ under runtime/.
2266// Together those banked baselines from files never opened, arming exactly the false refusal that killed
2267// L009. All four paths are now proven: resolves in either tree, unresolvable -> ALLOW-UNCOUNTED, and
2268// REFUSE still fires naming the offending line.
2269//
2270// FAIL-OPEN, the same stance as build admission and the gate-dry runner: a missing or unreadable
2271// detector returns -1 and the caller proceeds. A guard that cannot be read must never wedge the build
2272// path for every seat.
2273func md_exec_magicratchet(nm: *u8) -> i64 {
2274 let melf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_magicratchet.elf" as *u8
2275 let mf: i64 = sys_openat_rd(melf)
2276 if mf < 0 { return 0 - 1 }
2277 sys_close(mf)
2278 let args: *i64 = sys_mmap(16) as *i64
2279 args[0] = "check" as *u8 as i64
2280 args[1] = nm as i64
2281 return dep_run_capture(melf, args, 2, "/tmp/nx_magicratchet.out" as *u8)
2282}
2283
2284// Does this organ name end in the terminal token `_gate`? TERMINAL, not substring -- the seq1789
2285// lesson banked in nx_organkind_gate T12: `nx_survey_serve_gate` ENDS with _gate, `nx_gate_bite`
2286// merely CONTAINS it, and treating containment as the test misclassifies the second.
2287func md_name_is_gate(nm: *u8) -> i64 {
2288 var n: i64 = 0
2289 while nm[n] != (0 as u8) { n = n + 1 }
2290 if n < 5 { return 0 }
2291 if nm[n-5] != (95 as u8) { return 0 }
2292 if nm[n-4] != (103 as u8) { return 0 }
2293 if nm[n-3] != (97 as u8) { return 0 }
2294 if nm[n-2] != (116 as u8) { return 0 }
2295 if nm[n-1] != (101 as u8) { return 0 }
2296 return 1
2297}
2298
2299// Is this organ ALREADY DEPLOYED? That is the GRANDFATHER TEST and it is what makes this a ratchet
2300// rather than a wall: 2041 existing gates hand-roll their verdicts, and refusing all of them would
2301// stop the ecosystem dead. Only a gate with NO deployed artefact -- i.e. a NEW one -- is held to the
2302// base class. Existing breaches are migrated by their owner lanes, never blocked here.
2303// ---- THE BANKED BASELINE: HOW THE RATCHET TELLS NEW FROM OLD (2026-07-31, debt 1785558585) ----
2304// MY FIRST ATTEMPT USED "has no deployed .elf" AS THE NEWNESS TEST AND THAT WAS WRONG. The ledger
2305// measures 2877 gate sources against 175 binaries -- ~94pc of gates were NEVER COMPILED -- so
2306// long-existing gates read as NEW and their rebuilds were REFUSED. A ratchet that cannot tell new
2307// from old is a WALL, and a wall at a shared chokepoint stops every seat. Withdrawn within minutes.
2308//
2309// THE CORRECT TEST IS A RECORD, which is what a ratchet actually is. Notion's ESLint ratcheting keeps
2310// a CHECKED-IN file of known violations and requires approval only when the count INCREASES against
2311// it. knowledge/status/gatedry_baseline.out is that record: the enumerated gate corpus at the moment
2312// the ratchet landed (2189 entries). A gate NOT in the record is NEW and is held to the base class;
2313// everything in the record is grandfathered and migrated by its owner lane.
2314//
2315// FAIL-OPEN BY CONSTRUCTION: an absent or unreadable baseline returns 1 (== "known", allow). If the
2316// record cannot be read we CANNOT distinguish new from old, and the only safe answer is to permit --
2317// otherwise a missing file silently rebuilds the exact wall this replaced.
2318func md_gate_in_baseline(nm: *u8) -> i64 {
2319 let bp: *u8 = "knowledge/status/gatedry_baseline.out" as *u8
2320 let ln: *i64 = sys_mmap(16) as *i64
2321 ln[0] = 0
2322 let buf: *u8 = sys_read_file(bp, ln)
2323 if buf as i64 == 0 { return 1 }
2324 let n: i64 = ln[0]
2325 if n <= 0 { return 1 }
2326 let pat: *u8 = sys_mmap(256)
2327 var o: i64 = 0
2328 pat[o] = (47 as u8)
2329 o = o + 1
2330 var bi: i64 = 0
2331 while nm[bi] != (0 as u8) { pat[o] = nm[bi]; o = o + 1; bi = bi + 1 }
2332 pat[o] = (46 as u8)
2333 o = o + 1
2334 pat[o] = (110 as u8)
2335 o = o + 1
2336 pat[o] = (120 as u8)
2337 o = o + 1
2338 let pn: i64 = o
2339 var k: i64 = 0
2340 while k + pn <= n {
2341 var j: i64 = 0
2342 var hit: i64 = 1
2343 while j < pn { if buf[k+j] != pat[j] { hit = 0; j = pn } else { j = j + 1 } }
2344 if hit == 1 { return 1 }
2345 k = k + 1
2346 }
2347 return 0
2348}
2349
2350func md_organ_deployed(nm: *u8) -> i64 {
2351 let p: *u8 = sys_mmap(256)
2352 let pre: *u8 = "/volume1/homes/elderwesto/nishihost/"
2353 var o: i64 = 0
2354 var i: i64 = 0
2355 while pre[i] != (0 as u8) { p[o] = pre[i]; o = o + 1; i = i + 1 }
2356 i = 0
2357 while nm[i] != (0 as u8) { p[o] = nm[i]; o = o + 1; i = i + 1 }
2358 let suf: *u8 = ".elf"
2359 i = 0
2360 while suf[i] != (0 as u8) { p[o] = suf[i]; o = o + 1; i = i + 1 }
2361 p[o] = 0 as u8
2362 let fd: i64 = sys_openat_rd(p)
2363 if fd < 0 { return 0 }
2364 sys_close(fd)
2365 return 1
2366}
2367
2368// ---- PRE-DEPLOY SAFETY GATE RUNNER (2026-07-30) -------------------------------------------------
2369// nx_deploy_ready computes deploy_safe/blockers/DEPLOY-BLOCKED and publishes it -- and NOTHING AT THE
2370// DEPLOY CHOKEPOINT EVER CONSULTED IT. It is referenced by ecomat seeding, tooldiff and the cron beat,
2371// but nx_mgmt_api never called it, so the one act the gate exists to guard ran unguarded. MEASURED:
2372// the gate returned verdict DEPLOY-BLOCKED (blockers=1) while two of my own deploys succeeded minutes
2373// apart. A gate that is computed, published and unreachable from the act it guards IS the baseline.
2374//
2375// WHY WE PARSE JSON AND NOT THE EXIT CODE: nx_deploy_ready calls sys_exit(0) UNCONDITIONALLY -- even
2376// when the verdict is DEPLOY-BLOCKED -- so its exit status carries no verdict at all and no caller
2377// checking $? could ever act on it. Fixing that is a published-contract change (other callers may
2378// treat nonzero as failure), so it is filed separately rather than changed underneath them here.
2379//
2380// FAIL-OPEN, same stance as build admission: a missing or unreadable gate returns -1 and the caller
2381// proceeds. A guard that cannot be read must never wedge the deploy path for every seat.
2382func md_exec_deploy_ready(target: *u8) -> i64 {
2383 let delf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_deploy_ready.elf" as *u8
2384 let df: i64 = sys_openat_rd(delf)
2385 if df < 0 { return 0 - 1 }
2386 sys_close(df)
2387 let args: *i64 = sys_mmap(32) as *i64
2388 args[0] = "check" as *u8 as i64
2389 // LOOSE COUPLING (2026-09-02): pass the deploy TARGET so nx_deploy_ready scopes its evidence-honesty block
2390 // to the target's own domain + shared-fate domains (knowledge/registry/deploy_domain.conf), never to an
2391 // unrelated domain's RED. argv[2] is the default manifest because the target rides argv[3]; an EMPTY
2392 // target keeps the old one-arg call, i.e. GLOBAL scope, so no caller changes behaviour by accident.
2393 var nargs: i64 = 1
2394 if target != (0 as *u8) { if target[0] != (0 as u8) {
2395 args[1] = "knowledge/registry/deploy_checks.tsv" as *u8 as i64
2396 args[2] = target as i64
2397 nargs = 3
2398 } }
2399 // Return the gate's EXIT CODE (0 safe / 3 DEPLOY-BLOCKED) so the caller can surface it. Until today this
2400 // code was constant 0 and therefore meaningless; reporting it live is what proves the new contract landed.
2401 let drc0: i64 = dep_run_capture(delf, args, nargs, "/tmp/nx_ma_deploy_ready.out" as *u8); if drc0 >= 0 { return drc0 }
2402 return 0
2403}
2404
2405// mint a ROOT tools-capability token via the on-NAS nx_cap_mint oracle (CLI: <keyfile> <allow-csv> <exp> <nonce>
2406// -> token on stdout, nonzero exit on refusal). The HMAC keyfile is read BY THE ORACLE on-NAS and never crosses
2407// the API. Absolute paths (same stance as the hostctl/treepack exec ports; mgmt cwd=nishihost but explicit wins).
2408// stdout captured -> outpath; caller treats nonzero exit OR empty capture as mint-failed (fail-closed).
2409func md_exec_capmint(allow: *u8, expstr: *u8, noncestr: *u8, outpath: *u8) -> i64 {
2410 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_cap_mint.elf" as *u8
2411 let args: *i64 = sys_mmap(40) as *i64
2412 args[0] = "/volume1/homes/elderwesto/nishihost/tools_cap_secret.key" as *u8 as i64
2413 args[1] = allow as i64
2414 args[2] = expstr as i64
2415 args[3] = noncestr as i64
2416 return dep_run_capture(helf, args, 4, outpath)
2417}
2418// ---- /api/gate_run + /api/proc_kill support (seq1349/1383). RE-APPLIED after a 4th backdate (seq1445).
2419// gate_run bound: name must end gate/test/kat, resolves ONLY a promoted top-level nishihost/<n>.elf, so a pure
2420// verifier is all this route can ever reach -- never a daemon, promoter or deployer.
2421// ⚠ seq1443: dep_run_capture_bounded DUPLICATES tr_run_capture_to (nx_tool_run.nx, seq1412) which is gate-proven
2422// (nx_tool_run_timeout_gate T5 = no leak after a kill) and uses a WATCHDOG FORK because a poll design is not
2423// buildable without sys_fcntl. ADOPT IT next; kept here only so the live verbs stop vanishing from source.
2424// ---- R1 (seq1506): LEASE-GATE THE BUILD PATH -------------------------------------------------------------
2425// OPERATOR 2026-07-30: "why cant we clearly state when we are switching out or updating and coordinate like
2426// road construction". This is the flagger. Concurrent builds of the SAME target are how a session ships a
2427// regression from a mid-churn snapshot -- it happened twice today (21->19 routes lost, then again).
2428// ADOPTION, NOT INVENTION (seq1410's law, 4th instance today): nx_lease ALREADY EXISTS, is gate-proven, and
2429// had ZERO callers in the build path. We reuse it as a SUBPROCESS via its exit-code contract rather than
2430// importing it -- verified live: acquire=0 prints LS-ACQUIRED, BUSY=3 prints "LS-BUSY <name> holder=<who>",
2431// release=0. Exit codes are the contract, so no import coupling and no second implementation.
2432// TTL is the reason this can never deadlock the ecosystem: a session that dies mid-build cannot hold the
2433// lane closed -- the lease expires on its own. A lock without a TTL would be a worse defect than the race.
2434// ⚠ lease NAME grammar is [a-zA-Z0-9_-] ONLY: "build:X" is REFUSED, so the name is built as "build-<target>".
2435func md_lease_run(verb: *u8, name: *u8, owner: *u8, ttl: *u8, nargs: i64, outpath: *u8) -> i64 {
2436 let elf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_lease.elf" as *u8
2437 let args: *i64 = sys_mmap(48) as *i64
2438 args[0] = verb as i64
2439 args[1] = name as i64
2440 args[2] = owner as i64
2441 args[3] = ttl as i64
2442 return dep_run_capture(elf, args, nargs, outpath)
2443}
2444// Build the lease name "build-<target>" into buf. Target is already [A-Za-z0-9_]-sanitized by the caller.
2445func md_lease_name(target: *u8, buf: *u8) -> i64 {
2446 var o: i64 = 0
2447 let p: *u8 = "build-" as *u8
2448 while p[o] != (0 as u8) { buf[o] = p[o]; o = o + 1 }
2449 var i: i64 = 0
2450 while target[i] != (0 as u8) { buf[o] = target[i]; o = o + 1; i = i + 1 }
2451 buf[o] = 0 as u8
2452 return o
2453}
2454// Build "<prefix><src>" into buf, COPYING ONLY lease-legal chars [a-zA-Z0-9_-] from src.
2455// ⚠ nx_lease REFUSES any other byte, and the things we most want to lock are named with dots
2456// ("nx_tools_api_serve.elf") -- so a naive concat produces LS-REFUSED bad name and the guard silently
2457// never engages. A guard that cannot be named is a guard that does not exist; filter, do not assume.
2458func md_lease_name_pfx(prefix: *u8, src: *u8, buf: *u8) -> i64 {
2459 var o: i64 = 0
2460 while prefix[o] != (0 as u8) { buf[o] = prefix[o]; o = o + 1 }
2461 var i: i64 = 0
2462 while src[i] != (0 as u8) {
2463 let c: i64 = src[i] as i64
2464 var ok: i64 = 0
2465 if c >= 48 { if c <= 57 { ok = 1 } }
2466 if c >= 65 { if c <= 90 { ok = 1 } }
2467 if c >= 97 { if c <= 122 { ok = 1 } }
2468 if c == 95 { ok = 1 }
2469 if c == 45 { ok = 1 }
2470 if ok == 1 { buf[o] = src[i]; o = o + 1 }
2471 i = i + 1
2472 }
2473 buf[o] = 0 as u8
2474 return o
2475}
2476func md_gate_name_ok(nm: *u8) -> i64 {
2477 var n: i64 = 0
2478 while nm[n] != (0 as u8) { n = n + 1 }
2479 if n >= 4 { if nm[n-4] == (103 as u8) { if nm[n-3] == (97 as u8) { if nm[n-2] == (116 as u8) { if nm[n-1] == (101 as u8) { return 1 } } } } }
2480 if n >= 4 { if nm[n-4] == (116 as u8) { if nm[n-3] == (101 as u8) { if nm[n-2] == (115 as u8) { if nm[n-1] == (116 as u8) { return 1 } } } } }
2481 if n >= 3 { if nm[n-3] == (107 as u8) { if nm[n-2] == (97 as u8) { if nm[n-1] == (116 as u8) { return 1 } } } }
2482 return 0
2483}
2484// seq1443 ADOPTION: this now delegates to tr_run_capture_to (runtime/nx_tool_run.nx, seq1412) instead of my
2485// own dep_run_capture_bounded, which was a DUPLICATE of it -- a live instance of the ecosystem's own law that
2486// THE BOTTLENECK IS NOT BUILDING PRIMITIVES, IT IS ADOPTING THEM.
2487// The adopted primitive is STRICTLY better and its header says why mine could not work: bounding the drain
2488// needs O_NONBLOCK on the read end and THERE IS NO sys_fcntl in nx_syscalls, so a poll design is NOT
2489// BUILDABLE. It uses a WATCHDOG FORK -- the watchdog SIGKILLs the worker, the dying worker drops the last
2490// write end, and the parent's blocking read gets its EOF naturally, needing no new syscall. It also closes
2491// wfd BEFORE forking the watchdog (order is load-bearing: fork first and the watchdog inherits the write end,
2492// so the pipe never EOFs -- the exact hang the bound exists to remove, reintroduced by the fix).
2493// It ships nx_tool_run_timeout_gate whose T5 is "second timeout identical (no leak after a kill)" = the
2494// no-leak tooth seq1425 was missing. Capturing to a BUFFER also deletes the /tmp/nx_ma_gaterun.out temp file.
2495// Returns the child's exit code, or TR_ERR_TIMEOUT when the deadline fired.
2496func md_exec_gate_capture(elfpath: *u8, out: *u8, cap: i64, outlen: *i64, deadline_ms: i64) -> i64 {
2497 let argv: *i64 = sys_mmap(32) as *i64
2498 argv[0] = elfpath as i64
2499 argv[1] = 0
2500 return tr_run_capture_to(elfpath, argv, out, cap, outlen, deadline_ms)
2501}
2502// (md_exec_gate_capture_OLD REMOVED 2026-08-07 by a different seat than the one that landed the seq1443
2503// adoption above. It had ZERO callers -- grep finds only its own definition -- but it still called
2504// dep_run_capture_bounded, whose definition went away when md_exec_gate_capture adopted
2505// tr_run_capture_to. DEAD CODE STILL HAS TO COMPILE: that one unreachable line made /api/build fail for
2506// target=nx_mgmt_api for EVERY seat, i.e. it took down the deploy path itself, and the second reported
2507// error -- a bogus "sd_catn arg 3 is a POINTER" at nx_mgmt_api.nx:1907, where dl is plainly `var dl:
2508// i64` -- was a CASCADE of this same undefined name, not a second defect.
2509// A REFACTOR THAT DELETES A DEFINITION BUT LEAVES ITS DEAD CALLER HAS NOT LANDED, IT HAS HALF-LANDED.)
2510// proc_kill bound: >=6 chars AND must contain .elf (our own organs only, never a system process) AND must not
2511// reach the supervisor (killing the guard stops every respawn). Killing a guard-supervised daemon = a restart.
2512func md_str_contains(hay: *u8, pat: *u8) -> i64 {
2513 let hn: i64 = md_len(hay)
2514 let pn: i64 = md_len(pat)
2515 if pn == 0 { return 0 }
2516 if pn > hn { return 0 }
2517 var i: i64 = 0
2518 while i + pn <= hn {
2519 var k: i64 = 0
2520 var hit: i64 = 1
2521 while k < pn { if hay[i+k] != pat[k] { hit = 0; k = pn } else { k = k + 1 } }
2522 if hit == 1 { return 1 }
2523 i = i + 1
2524 }
2525 return 0
2526}
2527func md_proc_kill_needle_ok(nm: *u8) -> i64 {
2528 if md_len(nm) < 6 { return 0 }
2529 if md_str_contains(nm, ".elf" as *u8) == 0 { return 0 }
2530 if md_str_contains(nm, "supervise" as *u8) == 1 { return 0 }
2531 if md_str_contains(nm, "nx_hostctl" as *u8) == 1 { return 0 }
2532 return 1
2533}
2534func md_cstr_eq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } if b[i]!=(0 as u8) { return 0 } return 1 }
2535// FAIL-CLOSED allowlist for /api/hostctl. Curated to fast (<~15s) read + safe-maintenance subs so a synchronous
2536// API call returns promptly. DEPLOY/selfswap/rollback are DELIBERATELY excluded (they promote/re-exec -> use the
2537// guarded /api/deploy). Long-running (nettap 60s, portmap timeouts) excluded until an async job path exists.
2538func md_hostctl_action_ok(nm: *u8) -> i64 {
2539 if md_cstr_eq(nm, "status" as *u8) == 1 { return 1 } // supervisor snapshot
2540 if md_cstr_eq(nm, "torstat" as *u8) == 1 { return 1 } // per-torrent seedeval + metadata diag
2541 if md_cstr_eq(nm, "routerctl" as *u8) == 1 { return 1 } // GL.iNet dashboard (model/wan/forwards/clients)
2542 if md_cstr_eq(nm, "receipts" as *u8) == 1 { return 1 } // op-receipts ledger (read-only)
2543 if md_cstr_eq(nm, "kicktorrent" as *u8) == 1 { return 1 } // restart the torrent daemon
2544 if md_cstr_eq(nm, "kickseed" as *u8) == 1 { return 1 } // restart the :6881 seeder
2545 if md_cstr_eq(nm, "kickworkers" as *u8) == 1 { return 1 } // restart stale download workers
2546 if md_cstr_eq(nm, "kickseedann" as *u8) == 1 { return 1 } // restart the DHT/LSD announcer
2547 if md_cstr_eq(nm, "trackerrefresh" as *u8) == 1 { return 1 } // refresh the tracker list (detached)
2548 if md_cstr_eq(nm, "galxpipeline" as *u8) == 1 { return 1 } // analysis-on-ingest: thumbnails + NXVI (detached, idempotent)
2549 if md_cstr_eq(nm, "durindexrun" as *u8) == 1 { return 1 } // duration-index batch (detached, idempotent)
2550 if md_cstr_eq(nm, "searchpagerank" as *u8) == 1 { return 1 } // search: PageRank build on the live web shard (detached, idempotent, additive pr:)
2551 if md_cstr_eq(nm, "searchcompact" as *u8) == 1 { return 1 } // search: web-shard compaction (detached, idempotent, verifies-before-swap)
2552 if md_cstr_eq(nm, "durindexstat" as *u8) == 1 { return 1 } // duration-index coverage (read-only)
2553 return 0
2554}
2555
2556// REAL-HTTP health: GET url -> require 200 + non-empty body. 1 healthy / 0 not (the false-green killer).
2557// LOCAL TCP liveness: connect to 127.0.0.1:port -> 1 if something is listening (service up), 0 if refused.
2558// Dependency-free (no external fetcher, no CA store, no edge round-trip) = the robust health signal for a restart.
2559func md_tcp_alive(port: i64) -> i64 {
2560 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0); if fd < 0 { return 0 }
2561 let sa: *u8 = sys_mmap(16)
2562 sa[0]=2 as u8; sa[1]=0 as u8; sa[2]=((port>>8)&0xff) as u8; sa[3]=(port&0xff) as u8
2563 sa[4]=127 as u8; sa[5]=0 as u8; sa[6]=0 as u8; sa[7]=1 as u8
2564 var z: i64=8; while z<16 { sa[z]=0 as u8; z=z+1 }
2565 let r: i64 = nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS); sys_close(fd)
2566 if r == 0 { return 1 }
2567 return 0
2568}
2569// ---- DEPLOY LISTEN VERIFICATION (debt 1786068492) -----------------------------------------------------
2570// THE DEFECT THIS CLOSES IS NOT "THE CHECK WAS MISSING" -- THE CHECK WAS HERE AND IT GREENED ON THE WRONG
2571// PROCESS. hc_deploy_one promotes, then proc_kill_by_name(SIGKILL), then returns IMMEDIATELY; the deploy
2572// watchdog then called md_tcp_alive() with NO initial delay. SIGKILL is asynchronous -- the kernel releases
2573// the dying process's listening socket some time AFTER the syscall returns -- so a connect issued inside that
2574// window SUCCEEDS AGAINST THE OUTGOING LISTENER and the deploy is recorded DEPLOYED-GREEN having never once
2575// observed the incoming binary. That is precisely why debt 1786068492 records "the auto-rollback health check
2576// did not catch this either": it ran, and it PASSED, on the process being replaced.
2577// AAAAAA A LEVEL-TRIGGERED PROBE CANNOT WITNESS A HANDOFF -- "SOMETHING IS LISTENING" IS TRUE BOTH BEFORE AND
2578// AFTER, SO THE ONE INSTANT THAT DISTINGUISHES A REAL REPLACEMENT FROM A NO-OP IS THE ONLY THING IT NEVER
2579// SAMPLES. The same blindness is why a surviving second instance, or a kill that matched nothing, also greens.
2580//
2581// THIS IS A DETECTOR, NOT A CURE, AND THE DIFFERENCE IS STATED SO NO READER OVER-CLAIMS IT. It does NOT close
2582// the race: with kill-then-respawn there is still an instant at which ZERO processes hold the socket, and only
2583// descriptor handoff removes that (knowledge/compare/trafficsafety.matrix -- nginx, HAProxy and Envoy pass the
2584// fd and never re-bind; HAProxy measured 155 connection failures per million over 180 reloads before moving
2585// it). SO_REUSEPORT is NOT the fix either: it is accept-distribution, and its own documentation states it
2586// CREATES a drop window when the listening set changes. What this buys is that the failure is now NAMED and
2587// LOUD instead of silent and green.
2588// AAAAA AND IT PROVES ONLY THAT THE PORT ANSWERS, NEVER THAT THE RIGHT BINARY ANSWERS -- a promote of an
2589// unadjudicated artifact that listens perfectly is GREEN here and always will be. That is a different axis
2590// (artifact provenance) and it is guarded separately, above, by the routeguard and staging-hygiene classes.
2591//
2592// THREE NAMED OUTCOMES, BECAUSE UNOBSERVED MUST BE ITS OWN BUCKET: folding "I could not witness the handoff"
2593// into either GREEN or ROLLED-BACK is the false-proof-with-an-authoritative-name defect. An axis that cannot
2594// see must ABSTAIN, not acquit.
2595const MD_HP_NEVER_UP: i64 = 0 // the port never accepted inside the up budget -> the incoming process never listened -> ROLL BACK
2596const MD_HP_VERIFIED: i64 = 1 // REFUSED observed, then ACCEPTED -> the socket demonstrably changed hands
2597const MD_HP_UNVERIFIED: i64 = 2 // never observed REFUSED, but the port accepts -> healthy-looking, provenance UNPROVEN
2598
2599// probe budget slots (rule 11: the loops below index these, they never carry a bare number)
2600const MD_PC_UP_TRIES: i64 = 0
2601const MD_PC_UP_TICK_MS: i64 = 1
2602const MD_PC_DOWN_TICK_MS: i64 = 2
2603const MD_PC_SLOTS: i64 = 3
2604
2605// DERIVATION OF EVERY NUMBER HERE, stated because a timeout without a derivation is a magic number wearing a
2606// units suffix:
2607// up_tries x up_tick_ms must exceed the guard-respawn bound hc_deploy_one ITSELF announces to the operator
2608// ("guard respawns the NEW binary ... <=15s"). 10 x 3000 = 30s is 2x that bound and is the value this path
2609// was ALREADY calibrated at -- REUSED, not re-invented, so no deploy that passes today can begin failing
2610// because a budget moved under it.
2611// down_tick_ms is NOT a second budget. The REFUSED edge is a process-teardown transient (SIGKILL -> fd table
2612// release), sub-second on an idle box, and AN EVENT CAN ONLY BE OBSERVED BY SAMPLING FASTER THAN IT LASTS --
2613// so the down phase samples in milliseconds where the up phase samples in seconds. The down WINDOW is
2614// DERIVED as exactly ONE up-tick (md_probe_down_tries), so this file holds ONE calibrated quantity, not two
2615// that can silently drift apart.
2616func md_probe_conf(out: *i64) -> i64 {
2617 out[MD_PC_UP_TRIES] = 10
2618 out[MD_PC_UP_TICK_MS] = 3000
2619 // DEFAULT 0 = DOWN-WATCH OFF, and that default is a SAFETY DECISION, not a tuning choice.
2620 // MEASURED 2026-08-21 on the live control plane: sampling for the REFUSED edge before deciding
2621 // anything extended the watchdog's life by one up-tick (~3s) BEFORE it could write a verdict, and the
2622 // watchdog is KILLED inside that window -- deploy_status stuck at RUNNING, no terminal state, one
2623 // nx_mgmt_api process left, and NO segfault in dmesg (so killed, not crashed). The previous
2624 // level-triggered probe survived only because it wrote within milliseconds.
2625 // AAAAAA A DETECTOR THAT LENGTHENS THE LIFE OF THE PROCESS THAT MUST SURVIVE TO REPORT ITS VERDICT CAN
2626 // DESTROY THE VERDICT IT EXISTS TO PRODUCE -- and here that verdict IS the never-brick auto-rollback,
2627 // so the "improvement" silently disarmed a safety control it was written to strengthen.
2628 // THE EDGE IS STILL CAUGHT FOR FREE with the watch off: if the FIRST up-sample is REFUSED we have
2629 // witnessed the port down, and a later ACCEPT is a genuine handoff. If the first sample already
2630 // ACCEPTS we never saw it drop -- which is precisely UNVERIFIED. Identical timing to the incumbent,
2631 // zero added lifetime, and the silent false-green still becomes a NAMED state.
2632 out[MD_PC_DOWN_TICK_MS] = 0
2633 let szp: *i64 = sys_mmap(16) as *i64
2634 let buf: *u8 = md_read_file("deploy_probe.conf" as *u8, szp)
2635 if (buf as i64) == 0 { return 0 } // ABSENT -> compiled-in defaults. FAIL-OPEN: a probe that cannot read a conf must still probe.
2636 let n: i64 = szp[0]
2637 let offs: *i64 = sys_mmap(64) as *i64
2638 let lens: *i64 = sys_mmap(64) as *i64
2639 var cur: i64 = 0
2640 while cur < n {
2641 let le: i64 = md_eol(buf, n, cur)
2642 var isc: i64 = 0
2643 if le > cur { if (buf[cur] as i64) == 35 { isc = 1 } }
2644 if isc == 0 {
2645 let nf: i64 = md_split(buf, cur, le, offs, lens, 8)
2646 if nf >= 2 {
2647 let v: i64 = md_slice_atoi(buf, offs[1], lens[1])
2648 // v > 0 REFUSES a zero or unparsable value rather than adopting it: a 0 tick spins the box and
2649 // a 0 try-count turns the whole probe into an unconditional pass. Defensive at the boundary.
2650 if v > 0 {
2651 if md_tok_eq(buf, offs[0], lens[0], "up_tries" as *u8) == 1 { out[MD_PC_UP_TRIES] = v }
2652 if md_tok_eq(buf, offs[0], lens[0], "up_tick_ms" as *u8) == 1 { out[MD_PC_UP_TICK_MS] = v }
2653 if md_tok_eq(buf, offs[0], lens[0], "down_tick_ms" as *u8) == 1 { out[MD_PC_DOWN_TICK_MS] = v }
2654 }
2655 }
2656 }
2657 cur = le + 1
2658 }
2659 return 1
2660}
2661
2662// The down window is exactly ONE up-tick, DERIVED -- never a second typed constant that can drift.
2663func md_probe_down_tries(cfg: *i64) -> i64 {
2664 let t: i64 = cfg[MD_PC_DOWN_TICK_MS]
2665 if t <= 0 { return 1 }
2666 var k: i64 = cfg[MD_PC_UP_TICK_MS] / t
2667 if k < 1 { k = 1 }
2668 return k
2669}
2670
2671// "port:<N>" grammar, factored out so the deploy watchdog and the legacy 1/0 probe read the SAME grammar.
2672// TWO READERS OF ONE GRAMMAR IS HOW A WIRE MISMATCH IS BORN.
2673func md_url_is_port(url: *u8) -> i64 {
2674 if url[0] != (112 as u8) { return 0 }
2675 if url[1] != (111 as u8) { return 0 }
2676 if url[2] != (114 as u8) { return 0 }
2677 if url[3] != (116 as u8) { return 0 }
2678 if url[4] != (58 as u8) { return 0 }
2679 return 1
2680}
2681func md_url_port(url: *u8) -> i64 {
2682 var pt: i64 = 0
2683 var pi: i64 = 5
2684 while url[pi] != (0 as u8) { if url[pi] >= (48 as u8) { if url[pi] <= (57 as u8) { pt = pt*10 + ((url[pi] as i64) - 48) } } pi = pi + 1 }
2685 return pt
2686}
2687
2688// EDGE-TRIGGERED listener verification on 127.0.0.1:<port>. COMPOSES md_tcp_alive -- the one TCP ruler in this
2689// plane -- and deliberately does NOT fork an HTTP prober: the forked-fetcher leg of md_health_probe below is
2690// recorded IN ITS OWN COMMENT as having ALWAYS false-rolled-back because the fetcher is not reliably present at
2691// the mgmt cwd, and re-introducing a fork here would re-introduce exactly that fragility.
2692// SPLIT FOR TESTABILITY: the budget arrives as a PARAMETER, so a gate can drive THIS EXACT CODE PATH at
2693// millisecond timescales instead of the production 30s one. AAAAA A LOGIC THAT CAN ONLY BE EXERCISED AT
2694// PRODUCTION TIMESCALES IS A LOGIC NOBODY WILL TEST -- and an untested probe is how the level-triggered one
2695// survived. The production entry point (md_probe_listen_edge) is immediately below and loads the conf.
2696func md_probe_listen_edge_cfg(port: i64, cfg: *i64) -> i64 {
2697 // PHASE 1 -- watch for the REFUSED edge. Bounded, and NEVER a rollback trigger on its own: some targets
2698 // restart WITHOUT ever dropping the socket (the hostctl self-swap leaves sites.elf :8443 up throughout),
2699 // and refusing those would be a detector that fails in the DESTRUCTIVE direction. Missing the edge only
2700 // DOWNGRADES the verdict; it can never cause a rollback.
2701 var saw_down: i64 = 0
2702 // OPT-IN ONLY (see md_probe_conf): this watch costs watchdog LIFETIME before any verdict is written,
2703 // and that cost once destroyed the verdict itself. OFF by default; a caller that can afford the extra
2704 // latency before deciding enables it by setting down_tick_ms. With it off the first up-sample below
2705 // still supplies the edge for free.
2706 if cfg[MD_PC_DOWN_TICK_MS] > 0 {
2707 let dtries: i64 = md_probe_down_tries(cfg)
2708 var d: i64 = 0
2709 var scanning: i64 = 1
2710 while scanning == 1 {
2711 if md_tcp_alive(port) == 0 { saw_down = 1; scanning = 0 }
2712 else {
2713 d = d + 1
2714 if d >= dtries { scanning = 0 } else { sys_sleep_ms(cfg[MD_PC_DOWN_TICK_MS]) }
2715 }
2716 }
2717 }
2718 // PHASE 2 -- the incoming process must LISTEN inside the up budget. This is the tooth that fires on the
2719 // exact failure debt 1786068492 describes: a new process that starts, cannot bind, and exits.
2720 var at: i64 = 0
2721 while at < cfg[MD_PC_UP_TRIES] {
2722 if md_tcp_alive(port) == 1 {
2723 if saw_down == 1 { return MD_HP_VERIFIED }
2724 return MD_HP_UNVERIFIED
2725 }
2726 saw_down = 1 // a REFUSED seen during the up wait IS the same edge, observed later
2727 at = at + 1
2728 if at < cfg[MD_PC_UP_TRIES] { sys_sleep_ms(cfg[MD_PC_UP_TICK_MS]) }
2729 }
2730 return MD_HP_NEVER_UP
2731}
2732
2733// PRODUCTION ENTRY POINT: load the conf, then run the SAME logic the gate exercises. There is exactly ONE
2734// implementation of the edge check; this wrapper only supplies the budget.
2735func md_probe_listen_edge(port: i64) -> i64 {
2736 let cfg: *i64 = sys_mmap(8 * MD_PC_SLOTS) as *i64
2737 md_probe_conf(cfg)
2738 return md_probe_listen_edge_cfg(port, cfg)
2739}
2740
2741// Named-outcome health for the DEPLOY watchdog. port:<N> -> edge-verified listener check. Anything else -> the
2742// HTTP leg unchanged and still level-triggered ON PURPOSE: an HTTP 200 with a non-empty body already proves an
2743// APPLICATION answered, which is strictly stronger than "something holds the socket", so an edge phase would
2744// add nothing there.
2745func md_deploy_health(url: *u8) -> i64 {
2746 if md_url_is_port(url) == 1 { return md_probe_listen_edge(md_url_port(url)) }
2747 if md_health_probe(url) == 1 { return MD_HP_VERIFIED }
2748 return MD_HP_NEVER_UP
2749}
2750
2751// The status SUFFIX for each outcome. The FIRST token of the status line is deliberately left to the caller and
2752// unchanged (DEPLOYED-GREEN / ROLLED-BACK), so every existing reader of /api/deploy_status keeps working and
2753// this is purely ADDITIVE -- rule 19.
2754func md_hp_suffix(code: i64) -> *u8 {
2755 if code == MD_HP_VERIFIED { return " listen=VERIFIED" as *u8 }
2756 if code == MD_HP_UNVERIFIED { return " listen=UNVERIFIED-may-be-outgoing-process" as *u8 }
2757 return " listen=NEVER-LISTENED" as *u8
2758}
2759
2760func md_health_probe(url: *u8) -> i64 {
2761 // "port:<N>" -> LOCAL TCP-connect health (no nx_research_fetch/CA/edge dependency -- the robust default for
2762 // restart-targets: the earlier /torrent HTTP probe needed an on-NAS fetcher+CA that isn't at the mgmt cwd,
2763 // so it always failed -> conservative rollback). Otherwise the HTTP-fetch probe below. Both retry 10x3s.
2764 if url[0]==(112 as u8) { if url[1]==(111 as u8) { if url[2]==(114 as u8) { if url[3]==(116 as u8) { if url[4]==(58 as u8) {
2765 var pt: i64=0; var pi: i64=5; while url[pi]!=(0 as u8) { if url[pi]>=(48 as u8) { if url[pi]<=(57 as u8) { pt=pt*10+((url[pi] as i64)-48) } } pi=pi+1 }
2766 // DELEGATES to the edge-verified probe so there is exactly ONE listener-verification implementation in
2767 // this plane. The 1/0 contract of THIS function is preserved EXACTLY (up=1, down=0) -- the richer
2768 // three-state answer is reached through md_deploy_health, so no existing 1/0 consumer changes meaning.
2769 if md_probe_listen_edge(pt) == MD_HP_NEVER_UP { return 0 }
2770 return 1
2771 } } } } }
2772 let pargs: *i64 = sys_mmap(16) as *i64
2773 pargs[0] = url as i64
2774 let pbuf: *u8 = sys_mmap(16384)
2775 // 10 tries x 3s = up to 30s: GENEROUS, because /api/deploy now runs this in a DETACHED watchdog (not on the
2776 // request path) -> it no longer races the edge-proxy read window, so it can wait out a slow guard-respawn
2777 // (~10-15s) and confirm 200+body -> GREEN, instead of a premature conservative rollback.
2778 var attempt: i64 = 0
2779 while attempt < 10 {
2780 dep_run_capture("_offc/nx_research_fetch.elf" as *u8, pargs, 1, "/tmp/nx_ma_deploy_health.out" as *u8)
2781 let pn: i64 = dp_read("/tmp/nx_ma_deploy_health.out" as *u8, pbuf, 16380)
2782 let st: i64 = hh_after(pbuf, pn, "status=" as *u8)
2783 let bbn: i64 = hh_after(pbuf, pn, "body_bytes=" as *u8)
2784 if st == 200 { if bbn > 0 { return 1 } }
2785 attempt = attempt + 1
2786 if attempt < 10 { sys_sleep_ms(3000) }
2787 }
2788 return 0
2789}
2790
2791// Private integration of the existing registered-target and file-session owners.
2792// Canary executable/argv/budgets are supplied by the authenticated host policy,
2793// never copied from request form fields. Caller retains the session through its
2794// durable outcome receipt, then closes it on every path.
2795struct NxToolchainSessionResult {
2796 target: NxDeployTargetRecord,
2797 session: NxFileInstallSession,
2798 reverse_admission: NxFileInstallResult,
2799 publication: NxFileInstallResult,
2800 rollback: NxFileInstallResult,
2801 stage: *u8,
2802 code: i64,
2803 canary_attempted: i64,
2804 canary_exit: i64,
2805 canary_bytes: i64,
2806 canary_truncated: i64,
2807 rollback_attempted: i64,
2808}
2809func md_tc_session_init(out:*NxToolchainSessionResult)->i64 {
2810 md_target_init(&out.target);fi_session_init(&out.session)
2811 fi_session_result_init(&out.reverse_admission);fi_session_result_init(&out.publication);fi_session_result_init(&out.rollback)
2812 out.stage="toolchain-input";out.code=FIO_EINVAL
2813 out.canary_attempted=0;out.canary_exit=0;out.canary_bytes=0;out.canary_truncated=0;out.rollback_attempted=0;return 0
2814}
2815func md_tc_session_close(out:*NxToolchainSessionResult)->i64 {
2816 let released:i64=fi_session_close(&out.session)
2817 let closed:i64=md_target_close(&out.target)
2818 if released!=0 { return released };return closed
2819}
2820func md_tc_session_run(registry:*u8,snapshot_budget:i64,target:*u8,forward:*u8,forward_hash:*u8,reverse:*u8,reverse_hash:*u8,intent_budget:i64,canary:*u8,args:*i64,deadline_ms:i64,scratch:*u8,scratch_bytes:i64,capture:*u8,capture_bytes:i64,out:*NxToolchainSessionResult)->i64 {
2821 if (out as i64)==0 { return FIO_EINVAL }
2822 if out.session.held==1 { return FIO_EEXIST }
2823 md_tc_session_close(out);md_tc_session_init(out)
2824 if (target as i64)==0 || md_toolchain_target_ok(target)!=1 || deadline_ms<=0 || (args as i64)==0 || fi_path_valid(canary)==0 || (capture as i64)==0 || capture_bytes<=0 || (scratch as i64)==0 || scratch_bytes<=0 { return out.code }
2825 out.stage="toolchain-target-resolution"
2826 out.code=md_target_read(registry,snapshot_budget,target,fi_len(target),&out.target)
2827 if out.code!=0 { return out.code }
2828 out.stage="toolchain-target-kind"
2829 if md_streq(out.target.kind_text,"toolchain")!=1 && md_streq(out.target.kind_text,"toolchain-v2")!=1 { out.code=FI_EACCES;return out.code }
2830 out.stage="toolchain-session-begin"
2831 out.code=fi_session_begin(forward,intent_budget,out.target.live,forward_hash,&out.session)
2832 if out.code!=0 { return out.code }
2833 out.stage="toolchain-staged-binding"
2834 if md_streq(out.session.plan.source,out.target.staged)!=1 { out.code=FI_EACCES;return out.code }
2835 out.stage="toolchain-reverse-admission"
2836 out.code=fi_session_prepare_reverse(&out.session,reverse,intent_budget,reverse_hash,scratch,scratch_bytes,&out.reverse_admission)
2837 if out.code!=0 { return out.code }
2838 out.stage="toolchain-publish"
2839 out.code=fi_session_publish(&out.session,scratch,scratch_bytes,&out.publication)
2840 if out.code==0 {
2841 out.stage="toolchain-canary";out.canary_attempted=1
2842 out.canary_exit=tr_run_capture_tr(canary,args,capture,capture_bytes,&out.canary_bytes,deadline_ms,&out.canary_truncated)
2843 if out.canary_exit!=0 || out.canary_truncated!=0 { out.code=FIO_EIO }
2844 }
2845 if out.code!=0 && out.publication.replacement.publication.visible==1 {
2846 out.stage="toolchain-rollback";out.rollback_attempted=1
2847 let restored:i64=fi_session_reverse(&out.session,reverse,intent_budget,reverse_hash,scratch,scratch_bytes,&out.rollback)
2848 if restored!=0 { out.code=restored;out.stage="toolchain-rollback-needs-intervention" }
2849 else { out.stage="toolchain-rejected-restored" }
2850 } else { if out.code==0 { out.stage="toolchain-canary-accepted-receipt-pending" } }
2851 return out.code
2852}
2853
2854// Native minter owns fallible key reads and signing. No shared token file.
2855func md_issue_cap_at(keyfile:*u8,allow:*u8,allen:i64,exp:i64,nonce:i64,out:*u8,capacity:i64)->i64{
2856 return cm_mint_file(keyfile,allow,allen,exp,nonce,out,capacity)
2857}