code wiki / _hdl_build / nx_mgmt_data.nx

nx_mgmt_data.nx source

↩ module page · 2142 lines · 114172 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) 15 16// ---- parse primitives over a buffer (slices, no null terminators) ----------------------------------- 17func md_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 18 19func md_cat_slice(d: *u8, o: i64, src: *u8, off: i64, len: i64) -> i64 { 20 var i: i64 = 0 21 while i < len { d[o] = src[off + i]; o = o + 1; i = i + 1 } 22 return o 23} 24 25func md_tok_eq(src: *u8, off: i64, len: i64, s: *u8) -> i64 { 26 let sl: i64 = md_len(s) 27 if sl != len { return 0 } 28 var i: i64 = 0 29 while i < len { if (src[off + i] as i64) != (s[i] as i64) { return 0 } i = i + 1 } 30 return 1 31} 32 33func md_slice_atoi(src: *u8, off: i64, len: i64) -> i64 { 34 var v: i64 = 0 35 var i: i64 = 0 36 while i < len { 37 let c: i64 = src[off + i] as i64 38 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } 39 i = i + 1 40 } 41 return v 42} 43 44// index of '\n' at-or-after start, or n. 45func md_eol(snap: *u8, n: i64, start: i64) -> i64 { 46 var i: i64 = start 47 var f: i64 = 0 48 while f == 0 { 49 if i >= n { f = 1 } else { if (snap[i] as i64) == 10 { f = 1 } else { i = i + 1 } } 50 } 51 return i 52} 53 54// split snap[ls..le) on spaces into up-to-maxf (offs,lens) absolute slices. returns field count. 55func md_split(snap: *u8, ls: i64, le: i64, offs: *i64, lens: *i64, maxf: i64) -> i64 { 56 var nf: i64 = 0 57 var i: i64 = ls 58 while i < le { 59 var sk: i64 = 1 60 while sk == 1 { if i >= le { sk = 0 } else { if (snap[i] as i64) == 32 { i = i + 1 } else { sk = 0 } } } 61 if i < le { 62 let st: i64 = i 63 var sc: i64 = 1 64 while sc == 1 { if i >= le { sc = 0 } else { if (snap[i] as i64) == 32 { sc = 0 } else { i = i + 1 } } } 65 if nf < maxf { offs[nf] = st; lens[nf] = i - st; nf = nf + 1 } 66 } 67 } 68 return nf 69} 70 71func md_slice_eq(a: *u8, ao: i64, al: i64, b: *u8, bo: i64, bl: i64) -> i64 { 72 if al != bl { return 0 } 73 var i: i64 = 0 74 while i < al { if (a[ao + i] as i64) != (b[bo + i] as i64) { return 0 } i = i + 1 } 75 return 1 76} 77 78func md_copy_slice_z(dst: *u8, src: *u8, off: i64, len: i64, cap: i64) -> i64 { 79 var n: i64 = len 80 if n > cap - 1 { n = cap - 1 } 81 var i: i64 = 0 82 while i < n { dst[i] = src[off + i]; i = i + 1 } 83 dst[n] = 0 as u8 84 return n 85} 86 87// ---- file / config adapters ------------------------------------------------------------------------- 88func md_read_file(path: *u8, szbox: *i64) -> *u8 { 89 szbox[0] = 0 90 return sys_read_file(path, szbox) 91} 92 93// ---- ADNET BILLING (debt 1785513943): the outside-world half of the invoice route ---------------- 94// Lives HERE, in the DATA ring, not in nx_mgmt_api: that ring owns transport only (see the api header). 95// The money math stays in nx_adnet_bill and the join in nx_adnet_invoice -- this function is purely the 96// file access those two are deliberately free of. 97// FAIL-CLOSED: an unreadable INVENTORY or RATE CARD returns 0 (the route answers 503) rather than an 98// empty invoice -- "no rows" and "could not read the rows" must never look alike to a biller. An absent 99// JOURNAL is different and legitimate: it means zero events, so it degrades to an empty count. 100const MD_ADNET_INV: *u8 = "/volume1/homes/elderwesto/nishihost/sites/nishifamily/synth/adnet_inventory.txt" as *u8 101const MD_ADNET_RATES: *u8 = "/volume1/homes/elderwesto/nishihost/knowledge/status/adnet_rates.conf" as *u8 102const MD_ADNET_SERVED: *u8 = "/volume1/homes/elderwesto/nishihost/adnet_impressions.log" as *u8 103const MD_ADNET_CLICKS: *u8 = "/volume1/homes/elderwesto/nishihost/adnet_clicks.log" as *u8 104const MD_ADNET_VIEW: *u8 = "/volume1/homes/elderwesto/nishihost/knowledge/status/adnet_viewable.log" as *u8 105 106// ---- ADNET CREATIVE INTAKE (debt 1785512202): the outside-world half of the upload route -------- 107// Validation and naming live in nx_adnet_creative (pure, gated 13/13); this is only the file write. 108// CONTENT-ADDRESSED, so the write is IDEMPOTENT by construction (rule 10): re-uploading identical bytes 109// lands on the identical path. No overwrite hazard, no version skew, and the URL doubles as a cache key. 110// Returns the ACR_* verdict; urlout receives the first-party url ONLY on ACR_OK. 111const MD_ADNET_SYNTH: *u8 = "/volume1/homes/elderwesto/nishihost/sites/nishifamily/synth/" as *u8 112 113func md_adnet_creative_store(b: *u8, n: i64, urlout: *u8, urlcap: i64) -> i64 { 114 urlout[0] = 0 as u8 115 let v: i64 = acr_validate(b, n) 116 if v != ACR_OK { return v } 117 let nm: *u8 = sys_mmap(64) 118 if acr_name(b, n, nm, 64) == 0 { return ACR_NOT_PNG } 119 let path: *u8 = sys_mmap(512) 120 var o: i64 = 0 121 var i: i64 = 0 122 while MD_ADNET_SYNTH[i] != (0 as u8) { path[o] = MD_ADNET_SYNTH[i]; o = o + 1; i = i + 1 } 123 i = 0 124 while nm[i] != (0 as u8) { path[o] = nm[i]; o = o + 1; i = i + 1 } 125 path[o] = 0 as u8 126 let fd: i64 = sys_openat_wr(path, 420) 127 if fd < 0 { return 0 - 1 } 128 let w: i64 = sys_write(fd, b, n) 129 sys_close(fd) 130 if w != n { return 0 - 1 } 131 if acr_url(b, n, urlout, urlcap) == 0 { return 0 - 1 } 132 return ACR_OK 133} 134 135func md_adnet_invoice_report(out: *u8, cap: i64) -> i64 { 136 let bx: *i64 = sys_mmap(16) as *i64 137 let inv: *u8 = md_read_file(MD_ADNET_INV, bx) 138 if (inv as i64) == 0 { return 0 } 139 let iln: i64 = bx[0] 140 let bx2: *i64 = sys_mmap(16) as *i64 141 let rates: *u8 = md_read_file(MD_ADNET_RATES, bx2) 142 if (rates as i64) == 0 { return 0 } 143 let rln: i64 = bx2[0] 144 let bx3: *i64 = sys_mmap(16) as *i64 145 var served: *u8 = md_read_file(MD_ADNET_SERVED, bx3) 146 var sln: i64 = bx3[0] 147 if (served as i64) == 0 { served = "" as *u8; sln = 0 } 148 let bx4: *i64 = sys_mmap(16) as *i64 149 var view: *u8 = md_read_file(MD_ADNET_VIEW, bx4) 150 var vln: i64 = bx4[0] 151 if (view as i64) == 0 { view = "" as *u8; vln = 0 } 152 let bx5: *i64 = sys_mmap(16) as *i64 153 var clk: *u8 = md_read_file(MD_ADNET_CLICKS, bx5) 154 var cln: i64 = bx5[0] 155 if (clk as i64) == 0 { clk = "" as *u8; cln = 0 } 156 return ainv_report(inv, iln, rates, rln, served, sln, view, vln, clk, cln, out, cap) 157} 158 159// resolve a deploy target NAME (slice nm[off..off+len)) against the allowlist file -> kind + src/sub/url 160// copied null-terminated into caller buffers. 1 = resolved, 0 = unknown (fail-closed). '#' = comment line. 161// COMPILED-IN fallback for the deploy plane's OWN bootstrap targets, so a freshly-deployed mgmt API can deploy 162// mgmtapi/hostctl/torrentstack off-LAN WITHOUT first getting an updated deploy_targets.conf onto the NAS (the 163// file isn't upload-able off-LAN). The file (md_resolve_target) still WINS when present -> it stays the 164// extensible SSOT; this only covers the plane's self-knowledge. Each: kind, src(staged .new), sub(promote), url(health), rb(rollback). 165func md_builtin_target(nm: *u8, off: i64, len: i64, kindb: *i64, srcbuf: *u8, subbuf: *u8, urlbuf: *u8, rbbuf: *u8) -> i64 { 166 if md_slice_eq(nm, off, len, "mgmtapi" as *u8, 0, 7) == 1 { 167 // health = LOCAL TCP-connect to the mgmt API's own port :18098 (robust). The old "https://.../api/" HTTP 168 // probe ran nx_research_fetch from the NAS -> nishifamily.com, which hits DSM's loopback nginx (coin-flip) 169 // -> flaky false-rollback. The new mgmt respawns on :18098 within the 30s retry window -> port-connect greens. 170 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 } 171 if md_slice_eq(nm, off, len, "hostctl" as *u8, 0, 7) == 1 { 172 // health = LOCAL TCP-connect to sites.elf :8443 (robust, like torrentstack). The old "https://.../api/" 173 // HTTP-fetch probe needed nx_research_fetch+CA from the mgmt cwd + hit the :443 DSM-nginx coin-flip + raced 174 // the self-swap -> it ALWAYS false-rolled-back (why no hostctl deploy landed since 07-09). sites.elf stays 175 // up across a self-swap (only the supervisor re-execs), so the port-connect greens reliably. 176 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 } 177 if md_slice_eq(nm, off, len, "torrentstack" as *u8, 0, 12) == 1 { 178 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 } 179 // ethical CLEAN-SERVE daemon (:8102, /clean) -- first-class builtin so it deploys purely over the API (no NAS 180 // deploy_targets.conf write). hostctl cleanservedeploy promotes the .new + guard respawns; health = TCP :8102. 181 if md_slice_eq(nm, off, len, "cleanserve" as *u8, 0, 10) == 1 { 182 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 } 183 // DOCPORTAL admin daemon (:18456, /search + /doc + /api) -- first-class builtin so the SEARCH daemon deploys 184 // purely over the API (no more manual .sov.elf.new swap). hostctl docportaldeploy promotes the .sov.elf.new the 185 // build stages + guard respawns; health = local TCP :18456; rollback = docportalrollback (.prev -> live). 186 if md_slice_eq(nm, off, len, "docportal" as *u8, 0, 9) == 1 { 187 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 } 188 return 0 189} 190func md_resolve_target(cfgpath: *u8, nm: *u8, off: i64, len: i64, kindb: *i64, srcbuf: *u8, subbuf: *u8, urlbuf: *u8, rbbuf: *u8) -> i64 { 191 let szp: *i64 = sys_mmap(16) as *i64 192 var buf: *u8 = md_read_file(cfgpath, szp) 193 // primary path (knowledge/hosting/, the root-owned data plane) ABSENT -> fall back to the operator-writable 194 // bootstrap conf in the daemon cwd (nishihost/deploy_targets.conf). knowledge/ is root-owned (the root mgmt 195 // daemon created it), so the elderwesto bootstrap that REGISTERS deploy targets can only write the cwd -- this 196 // fallback is what lets a new target (e.g. relate) be registered WITHOUT root. Primary still WINS when present. 197 if (buf as i64) == 0 { buf = md_read_file("deploy_targets.conf" as *u8, szp) } 198 // both configs ABSENT -> still honor the compiled-in bootstrap targets, else the whole deploy plane is dead 199 // off-LAN when the NAS lacks the files (the live-400 that caught this). 200 if (buf as i64) == 0 { return md_builtin_target(nm, off, len, kindb, srcbuf, subbuf, urlbuf, rbbuf) } 201 let n: i64 = szp[0] 202 let offs: *i64 = sys_mmap(64) as *i64 203 let lens: *i64 = sys_mmap(64) as *i64 204 var cur: i64 = 0 205 while cur < n { 206 let le: i64 = md_eol(buf, n, cur) 207 var isc: i64 = 0 208 if le > cur { if (buf[cur] as i64) == 35 { isc = 1 } } 209 if isc == 0 { 210 let nf: i64 = md_split(buf, cur, le, offs, lens, 8) 211 if nf >= 5 { 212 if md_slice_eq(buf, offs[0], lens[0], nm, off, len) == 1 { 213 kindb[0] = md_slice_atoi(buf, offs[1], lens[1]) 214 md_copy_slice_z(srcbuf, buf, offs[2], lens[2], 512) 215 md_copy_slice_z(subbuf, buf, offs[3], lens[3], 64) 216 md_copy_slice_z(urlbuf, buf, offs[4], lens[4], 256) 217 // OPTIONAL 6th field = per-target rollback sub (generalized deploy: a torrent target must 218 // roll back the TORRENT binary, not sites.elf). Absent (5-field legacy rows) -> "rollback". 219 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) } 220 return 1 221 } 222 } 223 } 224 cur = le + 1 225 } 226 // not in the file -> try the compiled-in bootstrap targets (off-LAN self-enable). Fail-closed if neither. 227 return md_builtin_target(nm, off, len, kindb, srcbuf, subbuf, urlbuf, rbbuf) 228} 229 230// FAIL-CLOSED allowlist of artifact names that /api/upload may STAGE (write <name>.upload -> <name>.new). These 231// are the deployable binaries the on-NAS supervisor promotes from *.new (HC_*_NEW in nx_hostctl). 1 = allowed, 232// 0 = refused (unknown target -> 400, NOTHING written). Names checked as a slice (nm[off..off+len)) so the caller 233// can hand a query-string slice without copying. Data lives HERE (the DATA ring), not buried in the transport layer. 234// NOTE the deliberate absence of directory separators in every entry -- an upload target is a BARE basename, so a 235// caller can never traverse ('/' or '..' would fail every md_slice_eq below), which keeps the staging write pinned 236// to the mgmt daemon's cwd by construction (defense-in-depth over the allowlist itself). 237func md_upload_target_ok(nm: *u8, off: i64, len: i64) -> i64 { 238 if md_slice_eq(nm, off, len, "nx_mgmt_api.elf" as *u8, 0, 15) == 1 { return 1 } 239 if md_slice_eq(nm, off, len, "sites.elf" as *u8, 0, 9) == 1 { return 1 } 240 if md_slice_eq(nm, off, len, "nx_gallery_serve.elf" as *u8, 0, 20) == 1 { return 1 } 241 if md_slice_eq(nm, off, len, "nx_gallery_gateway.elf" as *u8, 0, 22) == 1 { return 1 } 242 if md_slice_eq(nm, off, len, "nx_docportal_admin_daemon.elf" as *u8, 0, 29) == 1 { return 1 } 243 if md_slice_eq(nm, off, len, "nx_hostctl" as *u8, 0, 10) == 1 { return 1 } 244 if md_slice_eq(nm, off, len, "nx_wiki_gw.elf" as *u8, 0, 14) == 1 { return 1 } 245 if md_slice_eq(nm, off, len, "nx_hub_gw.elf" as *u8, 0, 13) == 1 { return 1 } 246 if md_slice_eq(nm, off, len, "nx_torrent_gw.elf" as *u8, 0, 17) == 1 { return 1 } 247 // P1 off-LAN parity: the torrent STACK binaries (deployed cross-dir into /volume1/ai/torrent/ by the 248 // torrentdeploy hostctl sub). Staged as <name>.new in nishihost cwd like every other target. 249 if md_slice_eq(nm, off, len, "nx_torrent_daemon.sov.elf" as *u8, 0, 25) == 1 { return 1 } 250 if md_slice_eq(nm, off, len, "nx_torrent_seedeval.elf" as *u8, 0, 23) == 1 { return 1 } 251 // build-over-API: the tree-pack primitive elf + the source-tree blob (unpacked by /api/unpack via nx_treepack). 252 if md_slice_eq(nm, off, len, "nx_treepack.elf" as *u8, 0, 15) == 1 { return 1 } 253 if md_slice_eq(nm, off, len, "buildsrc.pack" as *u8, 0, 13) == 1 { return 1 } 254 if md_slice_eq(nm, off, len, "buildknow.pack" as *u8, 0, 14) == 1 { return 1 } 255 // /api/compare server-side regen: the hub generator elf (exec'd by md_cmp_regen; updatable over the API). 256 if md_slice_eq(nm, off, len, "nx_swcompare_hub.elf" as *u8, 0, 20) == 1 { return 1 } 257 // /api/compare/publish staging slot: page bytes arrive chunked here, then publish pins them by sha256. 258 if md_slice_eq(nm, off, len, "compare.page" as *u8, 0, 12) == 1 { return 1 } 259 // the Relationship OS daemon (binds loopback :8027; conf row `relate` promotes it once hostctl ships relatedeploy). 260 if md_slice_eq(nm, off, len, "nx_relate_daemon.elf" as *u8, 0, 20) == 1 { return 1 } 261 // the site-visuals editor (loopback :18466; cut over API-pure via /api/restart service=siteedit which 262 // promotes the staged .new -> the FULL editor deploy loop is upload+restart, zero ssh). 263 if md_slice_eq(nm, off, len, "nx_siteedit_daemon.elf" as *u8, 0, 22) == 1 { return 1 } 264 // seq1433 HALF-WIRED DEPLOY LOOP FIXED: md_direct_restart_ok mapped service=toolsapi -> nx_tools_api_serve.elf 265 // and hc_restart_ok/hc_guard_tapi allowed+respawned it, but there was NO upload row -- so the staging slot 266 // nx_tools_api_serve.elf.new could never be written over the API and /api/restart toolsapi could only ever 267 // re-promote a STALE artifact. A restart verb without a staging slot is not a deploy loop. This is the daemon 268 // that gates EVERY agent capability = the one binary the ecosystem could not update API-first (cf. galxgw seq1049). 269 if md_slice_eq(nm, off, len, "nx_tools_api_serve.elf" as *u8, 0, 22) == 1 { return 1 } 270 // the Nishi Pulse survey/insights daemon (:8031, cron-reconciled; /api/restart service=survey promotes 271 // the staged .new -> the survey deploy loop is upload+restart, zero ssh/scp). 272 if md_slice_eq(nm, off, len, "nx_survey_daemon.elf" as *u8, 0, 20) == 1 { return 1 } 273 // the ETHICAL CLEAN-SERVE daemon (:8102, /clean -- neutralize attacks + PRESERVE safe ads + safety receipt; 274 // SSRF-guarded public fetch proxy). Deploy loop = /api/upload + /api/deploy target=cleanserve (hostctl supervise). 275 if md_slice_eq(nm, off, len, "nx_clean_serve_daemon.elf" as *u8, 0, 25) == 1 { return 1 } 276 // the Nishi Office daemon (:8030, cron-reconciled) + its client JS. Deploy loop = /api/upload + /api/restart 277 // service=office (daemon: promote .new + kill -> nx_office_reconcile respawns) / officejs (JS: promote only, 278 // the daemon reads office_app.js per-request). Zero ssh -- matches the survey pattern. 279 if md_slice_eq(nm, off, len, "nx_office_daemon.elf" as *u8, 0, 20) == 1 { return 1 } 280 if md_slice_eq(nm, off, len, "office_app.js" as *u8, 0, 13) == 1 { return 1 } 281 // THE BUILD TOOLCHAIN ITSELF (seq891/903). Staged as <name>.new in nishihost cwd like every other 282 // target, then promoted into buildroot/_offc by /api/promote_toolchain -- which validates the ELF, 283 // banks .prev, chmod +x, CANARY-COMPILES and auto-rolls-back. Uploading merely STAGES; it can never 284 // touch the live compiler, so these rows are safe on their own. Closes the gap where the ecosystem 285 // could deploy every service over its own API but not the compiler that builds them. 286 if md_slice_eq(nm, off, len, "nx_cc_sovereign.elf" as *u8, 0, 19) == 1 { return 1 } 287 if md_slice_eq(nm, off, len, "nxasm_x86_main.elf" as *u8, 0, 18) == 1 { return 1 } 288 if md_slice_eq(nm, off, len, "nx_sov_build_run.elf" as *u8, 0, 20) == 1 { return 1 } 289 return 0 290} 291 292// ---- /api/unpack: resolve a fail-closed unpack destination (dest-key -> staged .pack + abs NAS dir) ---------- 293// NEVER-BRICK (#26): only allowlisted dest keys resolve; an unknown key -> 400, nothing written. Each key maps to 294// the STAGED pack (<key>.pack.new, from /api/upload) + a FIXED abs dir under nishihost (nx_treepack writes only 295// under it). Extend by adding a row. `buildsrc` = the runtime source tree for build-over-API. 296func md_unpack_resolve(nm: *u8, off: i64, len: i64, packbuf: *u8, destbuf: *u8) -> i64 { 297 if md_slice_eq(nm, off, len, "buildsrc" as *u8, 0, 8) == 1 { 298 md_copy_slice_z(packbuf, "buildsrc.pack.new" as *u8, 0, 17, 128) 299 md_copy_slice_z(destbuf, "/volume1/homes/elderwesto/nishihost/buildroot/runtime" as *u8, 0, 53, 256) 300 return 1 301 } 302 // `buildknow` = buildroot/knowledge DATA ring (2026-08-05, debt 1785937893): compare .q/.axes corpus banks 303 // and other knowledge data the buildroot-CWD generators (gapmap frontier) read; same staged-pack discipline. 304 if md_slice_eq(nm, off, len, "buildknow" as *u8, 0, 9) == 1 { 305 md_copy_slice_z(packbuf, "buildknow.pack.new" as *u8, 0, 18, 128) 306 md_copy_slice_z(destbuf, "/volume1/homes/elderwesto/nishihost/buildroot/knowledge" as *u8, 0, 55, 256) 307 return 1 308 } 309 return 0 310} 311// fork+exec the on-NAS nx_treepack (unpack mode) with (packpath, destpath); capture stdout -> outpath; exit code. 312func md_exec_treepack(packpath: *u8, destpath: *u8, outpath: *u8) -> i64 { 313 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_treepack.elf" as *u8 314 let args: *i64 = sys_mmap(32) as *i64 315 args[0] = "unpack" as *u8 as i64 316 args[1] = packpath as i64 317 args[2] = destpath as i64 318 return dep_run_capture(helf, args, 3, outpath) 319} 320 321// ---- CONTENT-PUBLISH namespace (publish-from-anywhere for STATIC site files, 2026-07-06) ------------------ 322// A content target is `sites/nishifamily/synth/<basename>`: FIXED directory prefix (extend = add a prefix row 323// here, data-ring) + strict basename charset [a-z0-9_.-] (first char alphanumeric, no ".." run, bounded) + 324// extension in {.html, .png, .stl}. '/' is impossible inside the basename by charset, and the prefix is fixed, 325// so path traversal is impossible BY CONSTRUCTION. Services (.elf) stay on md_upload_target_ok + /api/deploy 326// (health-checked promotion); this namespace is static files promoted by /api/promote_content (atomic 327// .prev-backed swap -- no health-check needed, and it can never touch a binary or leave the site down). 328func md_content_ext_ok(nm: *u8, off: i64, len: i64) -> i64 { 329 if len > 5 { 330 var m: i64 = 1 331 let e1: *u8 = ".html" as *u8 332 var i: i64 = 0 333 while i < 5 { if (nm[off + len - 5 + i] as i64) != (e1[i] as i64) { m = 0; i = 5 } else { i = i + 1 } } 334 if m == 1 { return 1 } 335 } 336 if len > 4 { 337 var m2: i64 = 1 338 let e2: *u8 = ".png" as *u8 339 var j: i64 = 0 340 while j < 4 { if (nm[off + len - 4 + j] as i64) != (e2[j] as i64) { m2 = 0; j = 4 } else { j = j + 1 } } 341 if m2 == 1 { return 1 } 342 var m3: i64 = 1 343 let e3: *u8 = ".stl" as *u8 344 var k: i64 = 0 345 while k < 4 { if (nm[off + len - 4 + k] as i64) != (e3[k] as i64) { m3 = 0; k = 4 } else { k = k + 1 } } 346 if m3 == 1 { return 1 } 347 // whole-site static set (site-factory publish): the self-emitted sitemap.xml + robots.txt 348 var m4: i64 = 1 349 let e4: *u8 = ".xml" as *u8 350 var k4: i64 = 0 351 while k4 < 4 { if (nm[off + len - 4 + k4] as i64) != (e4[k4] as i64) { m4 = 0; k4 = 4 } else { k4 = k4 + 1 } } 352 if m4 == 1 { return 1 } 353 var m5: i64 = 1 354 let e5: *u8 = ".txt" as *u8 355 var k5: i64 = 0 356 while k5 < 4 { if (nm[off + len - 4 + k5] as i64) != (e5[k5] as i64) { m5 = 0; k5 = 4 } else { k5 = k5 + 1 } } 357 if m5 == 1 { return 1 } 358 } 359 // the sovereign video-client set (2026-07-11): app.v2.js + nx_video_client.wasm ride /api/upload -> 360 // /api/promote_content like every other static file (retires the nx_aw_send ssh push). Same trust rank 361 // as .html (which can carry <script> anyway); binaries (.elf) stay OUT of this namespace by construction. 362 if len > 3 { 363 var m6: i64 = 1 364 let e6: *u8 = ".js" as *u8 365 var k6: i64 = 0 366 while k6 < 3 { if (nm[off + len - 3 + k6] as i64) != (e6[k6] as i64) { m6 = 0; k6 = 3 } else { k6 = k6 + 1 } } 367 if m6 == 1 { return 1 } 368 } 369 if len > 5 { 370 var m7: i64 = 1 371 let e7: *u8 = ".wasm" as *u8 372 var k7: i64 = 0 373 while k7 < 5 { if (nm[off + len - 5 + k7] as i64) != (e7[k7] as i64) { m7 = 0; k7 = 5 } else { k7 = k7 + 1 } } 374 if m7 == 1 { return 1 } 375 } 376 // the EVIDENCE workstream (2026-07-16): every published evidence run carries api.json machine detail 377 // beside its index.html (dashboards speak plain english; machines get JSON). Same trust rank as .txt. 378 if len > 5 { 379 var m8: i64 = 1 380 let e8: *u8 = ".json" as *u8 381 var k8: i64 = 0 382 while k8 < 5 { if (nm[off + len - 5 + k8] as i64) != (e8[k8] as i64) { m8 = 0; k8 = 5 } else { k8 = k8 + 1 } } 383 if m8 == 1 { return 1 } 384 } 385 return 0 386} 387// prefix TABLE (the data ring this namespace was designed to grow by): returns the matched prefix length, 388// or -1. Each row is a FIXED site subdirectory; extend = add a row. 389func md_content_pfx(nm: *u8, off: i64, len: i64) -> i64 { 390 let p1: *u8 = "sites/nishifamily/synth/" as *u8 391 let l1: i64 = 24 392 if len > l1 { 393 var i: i64 = 0 394 var m: i64 = 1 395 while i < l1 { if (nm[off + i] as i64) != (p1[i] as i64) { m = 0; i = l1 } else { i = i + 1 } } 396 if m == 1 { return l1 } 397 } 398 let p2: *u8 = "sites/nishifamily/swgpu/" as *u8 399 let l2: i64 = 24 400 if len > l2 { 401 var i2: i64 = 0 402 var m2: i64 = 1 403 while i2 < l2 { if (nm[off + i2] as i64) != (p2[i2] as i64) { m2 = 0; i2 = l2 } else { i2 = i2 + 1 } } 404 if m2 == 1 { return l2 } 405 } 406 // the SITE-FACTORY showcase (generated archetype gallery) -- publishes via upload+promote_content 407 let p3: *u8 = "sites/nishifamily/factory/" as *u8 408 let l3: i64 = 26 409 if len > l3 { 410 var i3: i64 = 0 411 var m3: i64 = 1 412 while i3 < l3 { if (nm[off + i3] as i64) != (p3[i3] as i64) { m3 = 0; i3 = l3 } else { i3 = i3 + 1 } } 413 if m3 == 1 { return l3 } 414 } 415 // the SOVEREIGN-INFINIGEN showcases (/world, /gsplat) -- retires the flaky ssh-cat push (2026-07-09): 416 // publish = /api/upload (chunked+staged) -> /api/promote_content (atomic .prev-backed swap) 417 let p4: *u8 = "sites/nishifamily/world/" as *u8 418 let l4: i64 = 24 419 if len > l4 { 420 var i4: i64 = 0 421 var m4: i64 = 1 422 while i4 < l4 { if (nm[off + i4] as i64) != (p4[i4] as i64) { m4 = 0; i4 = l4 } else { i4 = i4 + 1 } } 423 if m4 == 1 { return l4 } 424 } 425 let p5: *u8 = "sites/nishifamily/gsplat/" as *u8 426 let l5: i64 = 25 427 if len > l5 { 428 var i5: i64 = 0 429 var m5: i64 = 1 430 while i5 < l5 { if (nm[off + i5] as i64) != (p5[i5] as i64) { m5 = 0; i5 = l5 } else { i5 = i5 + 1 } } 431 if m5 == 1 { return l5 } 432 } 433 // WHOLESALE-emitted multi-page sites (site-factory R-SITESHAPE): subdir paths allowed under this 434 // prefix via the guarded '/' rule in md_content_target_ok (never doubled, ".." runs still refused). 435 let p6: *u8 = "sites/nishifamily/wholesale/" as *u8 436 let l6: i64 = 28 437 if len > l6 { 438 var i6: i64 = 0 439 var m6: i64 = 1 440 while i6 < l6 { if (nm[off + i6] as i64) != (p6[i6] as i64) { m6 = 0; i6 = l6 } else { i6 = i6 + 1 } } 441 if m6 == 1 { return l6 } 442 } 443 // the public generate-UI over nx_gen (R10 of the Infinigen ladder) 444 let p7: *u8 = "sites/nishifamily/generate/" as *u8 445 let l7: i64 = 27 446 if len > l7 { 447 var i7: i64 = 0 448 var m7: i64 = 1 449 while i7 < l7 { if (nm[off + i7] as i64) != (p7[i7] as i64) { m7 = 0; i7 = l7 } else { i7 = i7 + 1 } } 450 if m7 == 1 { return l7 } 451 } 452 // the sovereign VIDEO CODEC client set (2026-07-11): index.html + app.v2.js + nx_video_client.wasm + 453 // ver.txt. Retires the last ssh (nx_aw_send) in the codec ship loop -- deploy8XX becomes /api/upload -> 454 // /api/promote_content, and the ship gate verifies the :8443 sovereign edge. 455 let p8: *u8 = "sites/nishifamily/video/" as *u8 456 let l8: i64 = 24 457 if len > l8 { 458 var i8: i64 = 0 459 var m8: i64 = 1 460 while i8 < l8 { if (nm[off + i8] as i64) != (p8[i8] as i64) { m8 = 0; i8 = l8 } else { i8 = i8 + 1 } } 461 if m8 == 1 { return l8 } 462 } 463 // the EVIDENCE workstream namespace (2026-07-16, operator: "publish evidence consistent workstream"): 464 // /evidence/<run>/ = nx_evidence_pack output (index.html + api.json + screenshots/recordings), 465 // published via the proven upload->promote_content lane (nx_content_ship ship.manifest). Subdir runs 466 // ride the same guarded '/' rule as wholesale/. 467 let p9: *u8 = "sites/nishifamily/evidence/" as *u8 468 let l9: i64 = 27 469 if len > l9 { 470 var i9: i64 = 0 471 var m9: i64 = 1 472 while i9 < l9 { if (nm[off + i9] as i64) != (p9[i9] as i64) { m9 = 0; i9 = l9 } else { i9 = i9 + 1 } } 473 if m9 == 1 { return l9 } 474 } 475 // the EXPERIENTIAL census page (2026-07-16): EMITTED by nx_s21_census (sync-by-construction) and 476 // republished through this lane on every census re-run -- the page can never drift from disk truth. 477 let p10: *u8 = "sites/nishifamily/experiential/" as *u8 478 let l10: i64 = 31 479 if len > l10 { 480 var i10: i64 = 0 481 var m10: i64 = 1 482 while i10 < l10 { if (nm[off + i10] as i64) != (p10[i10] as i64) { m10 = 0; i10 = l10 } else { i10 = i10 + 1 } } 483 if m10 == 1 { return l10 } 484 } 485 // the COMPARE hub artifacts (2026-08-05, debt 1785937233): /compare api.json + index.html + openapi.json 486 // are laptop-generated (registry is laptop-owned by design) and ship through the proven 487 // upload -> promote_content lane; per-domain spoke pages stay NAS-regen-owned (nx_compare_regen). 488 // This row closes the hub-vs-spoke drift class: the hub gets a DOOR instead of a frozen snapshot. 489 let p11: *u8 = "sites/nishifamily/compare/" as *u8 490 let l11: i64 = 26 491 if len > l11 { 492 var i11: i64 = 0 493 var m11: i64 = 1 494 while i11 < l11 { if (nm[off + i11] as i64) != (p11[i11] as i64) { m11 = 0; i11 = l11 } else { i11 = i11 + 1 } } 495 if m11 == 1 { return l11 } 496 } 497 return 0 - 1 498} 499func md_content_target_ok(nm: *u8, off: i64, len: i64) -> i64 { 500 let pl: i64 = md_content_pfx(nm, off, len) 501 if pl < 0 { return 0 } 502 if len <= pl + 4 { return 0 } // needs prefix + at least an "a.png"-sized basename 503 if len > pl + 64 { return 0 } // bounded basename 504 let c0: i64 = nm[off + pl] as i64 // first basename char: alphanumeric only (blocks ".x" "-x" "..") 505 var ok0: i64 = 0 506 if c0 >= 97 { if c0 <= 122 { ok0 = 1 } } 507 if c0 >= 48 { if c0 <= 57 { ok0 = 1 } } 508 if ok0 == 0 { return 0 } 509 var j: i64 = pl 510 var prevdot: i64 = 0 511 var prevslash: i64 = 0 512 while j < len { 513 let c: i64 = nm[off + j] as i64 514 var okc: i64 = 0 515 if c >= 97 { if c <= 122 { okc = 1 } } 516 if c >= 48 { if c <= 57 { okc = 1 } } 517 if c == 95 { okc = 1 } 518 if c == 45 { okc = 1 } 519 // subdir separator for multi-page sites: never doubled, never after a dot (with the ".."-run 520 // refusal below and the pinned prefix, traversal stays impossible by construction). 521 if c == 47 { 522 if prevslash == 1 { return 0 } 523 if prevdot == 1 { return 0 } 524 okc = 1 525 prevslash = 1 526 } else { prevslash = 0 } 527 if c == 46 { 528 if prevdot == 1 { return 0 } // ".." run -> refuse 529 okc = 1 530 prevdot = 1 531 } else { prevdot = 0 } 532 if okc == 0 { return 0 } 533 j = j + 1 534 } 535 return md_content_ext_ok(nm, off, len) 536} 537 538// ---- COMPARE namespace (Nishi Compare registry SSOT + server-side hub regen, 2026-07-09) -------------------- 539// The CONCURRENT-WORK coordination plane for /compare: many sessions publish comparisons, so the shared registry 540// + hub are mutated through THIS one serialized daemon instead of racing raw file writes. The unit of mutation is 541// the COMPARISON RECORD keyed by its /compare/<domain> href segment: different-domain upserts are commutative 542// (merge, no clobber possible); same-domain upserts replace, with the previous line preserved in registry.hist 543// (additive-only). After a mutation the hub index.html + api.json are regenerated SERVER-SIDE from the SSOT by the 544// on-NAS nx_swcompare_hub.elf, so the published surface can never reflect a session's stale partial registry. 545// Installs are sanity-gated + .prev-backed atomic renames (never-brick: a failed regen leaves live files untouched). 546 547// extract the /compare/<domain> merge key from a registry line (field 3 of title|kind|href|radar|stat). 548// Returns domain length copied into domb (NUL-terminated), or 0 if the line/href is malformed. Charset [a-z0-9_-]. 549func md_cmp_domain_of(src: *u8, off: i64, len: i64, domb: *u8, cap: i64) -> i64 { 550 var p: i64 = 0 551 var f: i64 = 0 552 while p < len { 553 if (src[off + p] as i64) == 124 { f = f + 1; if f == 2 { p = p + 1; break } } 554 p = p + 1 555 } 556 if f != 2 { return 0 } 557 let pfx: *u8 = "/compare/" as *u8 558 var k: i64 = 0 559 while k < 9 { 560 if p + k >= len { return 0 } 561 if (src[off + p + k] as i64) != (pfx[k] as i64) { return 0 } 562 k = k + 1 563 } 564 var q: i64 = p + 9 565 var o: i64 = 0 566 while q < len { 567 let c: i64 = src[off + q] as i64 568 if c == 124 { break } 569 if c == 47 { break } 570 var okc: i64 = 0 571 if c >= 97 { if c <= 122 { okc = 1 } } 572 if c >= 48 { if c <= 57 { okc = 1 } } 573 if c == 95 { okc = 1 } 574 if c == 45 { okc = 1 } 575 if okc == 0 { return 0 } 576 if o < cap - 1 { domb[o] = src[off + q]; o = o + 1 } 577 q = q + 1 578 } 579 domb[o] = 0 as u8 580 if o < 1 { return 0 } 581 return o 582} 583 584func 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 } 585func md_cmp_wn(fd: i64, v: i64) -> i64 { 586 var m: i64 = v 587 if m < 0 { md_cmp_ws(fd, "-" as *u8); m = 0 - m } 588 let t: *u8 = sys_mmap(24) 589 var k: i64 = 0 590 if m == 0 { t[0] = 48 as u8; k = 1 } 591 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 592 let o2: *u8 = sys_mmap(24) 593 var i: i64 = 0 594 while i < k { o2[i] = t[k - 1 - i]; i = i + 1 } 595 sys_write(fd, o2, k) 596 return 0 597} 598 599// merge ONE registry line into the SSOT by domain key: replace the matching entry (old line -> .hist) or append. 600// Atomic (write registry.tmp -> rename); additive history appended AFTER the rename lands. Returns the new entry 601// count, or -1 on write failure (registry untouched -- the tmp+rename never half-writes the live file). 602func md_cmp_upsert(line: *u8, ln: i64, domb: *u8, domn: i64, replacedb: *i64) -> i64 { 603 let szp: *i64 = sys_mmap(16) as *i64 604 let old: *u8 = md_read_file("knowledge/compare/registry" as *u8, szp) 605 let on: i64 = szp[0] 606 let nb: *u8 = sys_mmap(262144) 607 let d2: *u8 = sys_mmap(128) 608 let oldline: *u8 = sys_mmap(4096) 609 var oldn: i64 = 0 610 var o: i64 = 0 611 var entries: i64 = 0 612 var replaced: i64 = 0 613 if (old as i64) != 0 { 614 var i: i64 = 0 615 while i < on { 616 let le: i64 = md_eol(old, on, i) 617 var wrote: i64 = 0 618 if le > i { 619 if (old[i] as i64) != 35 { 620 let dl2: i64 = md_cmp_domain_of(old, i, le - i, d2, 120) 621 if dl2 > 0 { 622 entries = entries + 1 623 if md_slice_eq(d2, 0, dl2, domb, 0, domn) == 1 { 624 replaced = 1 625 wrote = 1 626 oldn = 0 627 var c: i64 = 0 628 while c < (le - i) { if c < 4090 { oldline[c] = old[i + c]; oldn = c + 1 } c = c + 1 } 629 var w2: i64 = 0 630 while w2 < ln { nb[o] = line[w2]; o = o + 1; w2 = w2 + 1 } 631 nb[o] = 10 as u8 632 o = o + 1 633 } 634 } 635 } 636 } 637 if wrote == 0 { 638 var c2: i64 = i 639 while c2 < le { nb[o] = old[c2]; o = o + 1; c2 = c2 + 1 } 640 nb[o] = 10 as u8 641 o = o + 1 642 } 643 i = le + 1 644 } 645 } 646 if replaced == 0 { 647 var w3: i64 = 0 648 while w3 < ln { nb[o] = line[w3]; o = o + 1; w3 = w3 + 1 } 649 nb[o] = 10 as u8 650 o = o + 1 651 entries = entries + 1 652 } 653 let fd: i64 = sys_openat_wr("knowledge/compare/registry.tmp" as *u8, 0x1a4) 654 if fd < 0 { replacedb[0] = replaced; return 0 - 1 } 655 sys_write(fd, nb, o) 656 sys_close(fd) 657 if sys_renameat("knowledge/compare/registry.tmp" as *u8, "knowledge/compare/registry" as *u8) != 0 { 658 replacedb[0] = replaced 659 return 0 - 1 660 } 661 let hf: i64 = sys_openat_append("knowledge/compare/registry.hist" as *u8, 0x1a4) 662 if hf >= 0 { 663 md_cmp_ws(hf, "ts=" as *u8) 664 md_cmp_wn(hf, sys_now_realtime_sec()) 665 md_cmp_ws(hf, " op=upsert domain=" as *u8) 666 var hd: i64 = 0 667 while hd < domn { sys_write(hf, ((domb as i64) + hd) as *u8, 1); hd = hd + 1 } 668 md_cmp_ws(hf, " replaced=" as *u8) 669 md_cmp_wn(hf, replaced) 670 md_cmp_ws(hf, "\n" as *u8) 671 if replaced == 1 { if oldn > 0 { 672 md_cmp_ws(hf, " prev: " as *u8) 673 sys_write(hf, oldline, oldn) 674 md_cmp_ws(hf, "\n" as *u8) 675 } } 676 sys_close(hf) 677 } 678 replacedb[0] = replaced 679 return entries 680} 681 682// install a generator-captured output file as a live docroot file: sanity (size + first byte) -> write tmp -> 683// back up live -> rename tmp over live; on failure the previous live file is restored (mirror of promote_content). 684func md_cmp_install(srcp: *u8, tmpp: *u8, prevp: *u8, livep: *u8, firstc: i64) -> i64 { 685 let szp: *i64 = sys_mmap(16) as *i64 686 let b: *u8 = md_read_file(srcp, szp) 687 let n: i64 = szp[0] 688 if (b as i64) == 0 { return 0 } 689 if n < 200 { return 0 } 690 if (b[0] as i64) != firstc { return 0 } 691 let fd: i64 = sys_openat_wr(tmpp, 0x1a4) 692 if fd < 0 { return 0 } 693 sys_write(fd, b, n) 694 sys_close(fd) 695 var had: i64 = 0 696 let pf: i64 = sys_openat_rd(livep) 697 if pf >= 0 { sys_close(pf); had = 1 } 698 if had == 1 { if sys_renameat(livep, prevp) != 0 { return 0 } } 699 if sys_renameat(tmpp, livep) != 0 { 700 if had == 1 { sys_renameat(prevp, livep) } 701 return 0 702 } 703 return 1 704} 705 706// regenerate the /compare hub (index.html + api.json) from the registry SSOT via the on-NAS hub generator elf. 707// Fail-safe: generator output must pass sanity before install; a missing elf / bad output leaves live files alone. 708func md_cmp_regen() -> i64 { 709 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_swcompare_hub.elf" as *u8 710 // Capture the generator output to an ELDERWESTO-OWNED scratch dir (knowledge/compare/, CWD=nishihost), NOT 711 // world-writable /tmp. WHY (2026-07-14 root cause): a root-era mgmt run left /tmp/nx_ma_cmp_*.out root-owned; 712 // after the root->elderwesto guard migration this daemon could no longer OVERWRITE them -> dep_run_capture's 713 // sys_openat_wr failed (EACCES), it ran the generator WITHOUT redirect, and md_cmp_install re-installed the 714 // STALE file every regen (silent "OK", frozen hub). A path this daemon owns always truncates fresh -> correct 715 // install, or an empty capture that fails md_cmp_install's sanity gate -> honest REGEN-FAILED (never stale). 716 let a1: *i64 = sys_mmap(16) as *i64 717 a1[0] = "html" as *u8 as i64 718 dep_run_capture(helf, a1, 1, "knowledge/compare/.regen_html.out" as *u8) 719 let a2: *i64 = sys_mmap(16) as *i64 720 a2[0] = "json" as *u8 as i64 721 dep_run_capture(helf, a2, 1, "knowledge/compare/.regen_json.out" as *u8) 722 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) 723 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) 724 if ok1 == 1 { if ok2 == 1 { return 1 } } 725 return 0 726} 727 728// validate a bare compare DOMAIN atom: charset [a-z0-9_-], first char alphanumeric, len 1..60 -> copy NUL-terminated. 729// Path segments are built ONLY from this validated atom + fixed literals, so traversal is impossible by construction. 730func md_cmp_dom_ok(src: *u8, off: i64, len: i64, domb: *u8) -> i64 { 731 if len < 1 { return 0 } 732 if len > 60 { return 0 } 733 let c0: i64 = src[off] as i64 734 var ok0: i64 = 0 735 if c0 >= 97 { if c0 <= 122 { ok0 = 1 } } 736 if c0 >= 48 { if c0 <= 57 { ok0 = 1 } } 737 if ok0 == 0 { return 0 } 738 var i: i64 = 0 739 while i < len { 740 let c: i64 = src[off + i] as i64 741 var okc: i64 = 0 742 if c >= 97 { if c <= 122 { okc = 1 } } 743 if c >= 48 { if c <= 57 { okc = 1 } } 744 if c == 95 { okc = 1 } 745 if c == 45 { okc = 1 } 746 if okc == 0 { return 0 } 747 domb[i] = src[off + i] 748 i = i + 1 749 } 750 domb[len] = 0 as u8 751 return 1 752} 753func 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 } 754 755// publish the STAGED compare.page.new as the live artifact for (domain, kind). kind: 1=page 2=frontier 3=bench 4=api. 756// Server derives the FIXED docroot path from the validated domain atom + a kind enum (no caller-supplied paths at 757// all). Dirs are created as needed; install is sanity-gated + .prev-backed (md_cmp_install). kind=api ALSO refreshes 758// the hub data-link marker knowledge/compare/<domain>-api.json (tmp+rename). Returns 1 ok / 0 fail (live untouched). 759func md_cmp_publish(domb: *u8, kind: i64) -> i64 { 760 let base: *u8 = sys_mmap(512) 761 var o: i64 = md_cmp_cat(base, 0, "sites/nishifamily/compare/" as *u8) 762 o = md_cmp_cat(base, o, domb) 763 sys_mkdir(base, 0x1ed) 764 if kind == 2 { o = md_cmp_cat(base, o, "/frontier" as *u8); sys_mkdir(base, 0x1ed) } 765 if kind == 3 { o = md_cmp_cat(base, o, "/bench" as *u8); sys_mkdir(base, 0x1ed) } 766 let live: *u8 = sys_mmap(512) 767 var lo: i64 = md_cmp_cat(live, 0, base) 768 var fc: i64 = 60 769 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) } 770 let tmpp: *u8 = sys_mmap(512) 771 var to: i64 = md_cmp_cat(tmpp, 0, live) 772 to = md_cmp_cat(tmpp, to, ".tmp2" as *u8) 773 let prevp: *u8 = sys_mmap(512) 774 var po: i64 = md_cmp_cat(prevp, 0, live) 775 po = md_cmp_cat(prevp, po, ".prev" as *u8) 776 let oki: i64 = md_cmp_install("compare.page.new" as *u8, tmpp, prevp, live, fc) 777 if oki != 1 { return 0 } 778 if kind == 4 { 779 let mk: *u8 = sys_mmap(512) 780 var mo: i64 = md_cmp_cat(mk, 0, "knowledge/compare/" as *u8) 781 mo = md_cmp_cat(mk, mo, domb) 782 mo = md_cmp_cat(mk, mo, "-api.json" as *u8) 783 let mt: *u8 = sys_mmap(512) 784 var mto: i64 = md_cmp_cat(mt, 0, mk) 785 mto = md_cmp_cat(mt, mto, ".tmp" as *u8) 786 let szp: *i64 = sys_mmap(16) as *i64 787 let b: *u8 = md_read_file("compare.page.new" as *u8, szp) 788 if (b as i64) != 0 { if szp[0] > 0 { 789 let fd: i64 = sys_openat_wr(mt, 0x1a4) 790 if fd >= 0 { sys_write(fd, b, szp[0]); sys_close(fd); sys_renameat(mt, mk) } 791 } } 792 } 793 return 1 794} 795 796// map an allowlisted service name -> the proven nx_aw_hostctl surgical-restart sub (fail-closed: unknown -> 0). 797func md_restart_sub(svc: *u8, off: i64, len: i64, subbuf: *u8) -> i64 { 798 if md_slice_eq(svc, off, len, "reader" as *u8, 0, 6) == 1 { md_copy_slice_z(subbuf, "kickreader" as *u8, 0, 10, 64); return 1 } 799 if md_slice_eq(svc, off, len, "torrent" as *u8, 0, 7) == 1 { md_copy_slice_z(subbuf, "kicktorrent" as *u8, 0, 11, 64); return 1 } 800 if md_slice_eq(svc, off, len, "torrentgw" as *u8, 0, 9) == 1 { md_copy_slice_z(subbuf, "kicktorrentgw" as *u8, 0, 13, 64); return 1 } 801 if md_slice_eq(svc, off, len, "docportal" as *u8, 0, 9) == 1 { md_copy_slice_z(subbuf, "kickdocportal" as *u8, 0, 13, 64); return 1 } 802 return 0 803} 804// DIRECT-restart allowlist (no hostctl sub needed): svc name -> the exact process cmdline needle. The mgmt 805// daemon (root, itself guard-supervised) kills by name; the hostctl supervise guard respawns the on-disk 806// binary <=15s -- so promote-a-staged-.new + /api/restart = the full API-pure editor deploy loop. 807func md_direct_restart_ok(svc: *u8, off: i64, len: i64, namebuf: *u8) -> i64 { 808 if md_slice_eq(svc, off, len, "siteedit" as *u8, 0, 8) == 1 { md_copy_slice_z(namebuf, "nx_siteedit_daemon.elf" as *u8, 0, 22, 64); return 1 } 809 // sites.elf = the EDGE. Restart (kill -> guard respawns) RE-READS proxy_routes.conf, so this doubles as 810 // the API-pure route reload AND an edge redeploy if a sites.elf.new is staged. "sites.elf" substring is 811 // unique to the edge (nx_sites_daemon_v2/nx_sites_reconciled don't contain it). 812 if md_slice_eq(svc, off, len, "sites" as *u8, 0, 5) == 1 { md_copy_slice_z(namebuf, "sites.elf" as *u8, 0, 9, 64); return 1 } 813 // Nishi Pulse survey daemon: kill -> the nx_survey_reconcile cron respawns the on-disk binary <=60s 814 // (not hostctl-guarded; the reconcile row IS its supervisor -- crash+reboot proven 2026-07-10). 815 if md_slice_eq(svc, off, len, "survey" as *u8, 0, 6) == 1 { md_copy_slice_z(namebuf, "nx_survey_daemon.elf" as *u8, 0, 20, 64); return 1 } 816 // Nishi Office daemon: promote nx_office_daemon.elf.new + kill -> nx_office_reconcile cron respawns the 817 // on-disk binary <=60s (SO_REUSEADDR = fast rebind; not hostctl-guarded, the reconcile IS its supervisor). 818 if md_slice_eq(svc, off, len, "office" as *u8, 0, 6) == 1 { md_copy_slice_z(namebuf, "nx_office_daemon.elf" as *u8, 0, 20, 64); return 1 } 819 // Nishi Office client JS: promote office_app.js.new -> live. kill-by-name matches NO process (it is a file, 820 // not a daemon) -> harmless; the office daemon reads office_app.js fresh on the next request. 821 if md_slice_eq(svc, off, len, "officejs" as *u8, 0, 8) == 1 { md_copy_slice_z(namebuf, "office_app.js" as *u8, 0, 13, 64); return 1 } 822 // the R0 agent-facing tools daemon (nx_tools_api_serve.elf :18096, hostctl guard-supervised): kill-by-name 823 // -> the supervise guard respawns the on-disk binary <=15s. Makes the tools/MCP plane API-pure-deployable 824 // (e.g. the fork-per-request concurrency upgrade): SMB/upload the .elf.new -> promote -> /api/restart toolsapi. 825 if md_slice_eq(svc, off, len, "toolsapi" as *u8, 0, 8) == 1 { md_copy_slice_z(namebuf, "nx_tools_api_serve.elf" as *u8, 0, 22, 64); return 1 } 826 // BitTorrent SEEDER :6881 (hostctl guard-supervised): kill-by-name -> the guard respawns the on-disk 827 // binary. THIS WAS THE LAST MISSING LINK IN THE SEEDER DEPLOY LOOP (2026-07-30): the fix could be built, 828 // gate-proven and PLACED by torrentdeploy, and then could not be made to RUN by any sanctioned route -- 829 // `torrent` restarts the :8097 DAEMON, not the seeder, and /api/proc_kill needs a fresh M5 session token. 830 // ★NOTE THE SPLIT, IT IS DELIBERATE: md_promote_staged(dname) here would target 831 // nishihost/nx_torrent_seed.elf.new, but the LIVE seeder lives at /volume1/ai/torrent/nx_torrent_seed.elf 832 // and is promoted there by cmd_torrentdeploy. No such .new exists at the nishihost root, so the promote 833 // half cleanly NO-OPS and only the kill does the work. Promoting to the nishihost path instead would 834 // place a binary NOTHING EVER RUNS while reporting a successful restart -- the same name trap that 835 // `.sov.elf` vs `.elf` sets for the deploy half. 836 if md_slice_eq(svc, off, len, "seed" as *u8, 0, 4) == 1 { md_copy_slice_z(namebuf, "nx_torrent_seed.elf" as *u8, 0, 19, 64); return 1 } 837 return 0 838} 839// -- /api/route: validate + append a proxy_routes.conf row (data ring). Format: "<host> <prefix> <port> <mode>". 840func md_catn(d: *u8, o: i64, v: i64) -> i64 { 841 if v == 0 { d[o] = 48 as u8; return o + 1 } 842 var m: i64 = v 843 var oo: i64 = o 844 if m < 0 { d[oo] = 45 as u8; oo = oo + 1; m = 0 - m } 845 var nd: i64 = 1 846 var t: i64 = m 847 while t >= 10 { nd = nd + 1; t = t / 10 } 848 var i: i64 = nd - 1 849 while i >= 0 { d[oo + i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 850 return oo + nd 851} 852// fail-closed validation of a route (null-terminated host/prefix/mode + numeric port). 853func md_route_valid(host: *u8, prefix: *u8, port: i64, mode: *u8) -> i64 { 854 let hl: i64 = md_len(host) 855 if hl < 3 { return 0 } 856 if hl > 64 { return 0 } 857 if (host[0] as i64) == 46 { return 0 } 858 if (host[0] as i64) == 45 { return 0 } 859 var hasdot: i64 = 0 860 var i: i64 = 0 861 while i < hl { 862 let c: i64 = host[i] as i64 863 var ok: i64 = 0 864 if c >= 97 { if c <= 122 { ok = 1 } } 865 if c >= 48 { if c <= 57 { ok = 1 } } 866 if c == 45 { ok = 1 } 867 if c == 46 { ok = 1; hasdot = 1; if i > 0 { if (host[i - 1] as i64) == 46 { return 0 } } } 868 if ok == 0 { return 0 } 869 i = i + 1 870 } 871 if hasdot == 0 { return 0 } 872 let pl: i64 = md_len(prefix) 873 if pl < 2 { return 0 } 874 if pl > 48 { return 0 } 875 if (prefix[0] as i64) != 47 { return 0 } 876 i = 1 877 while i < pl { 878 let c: i64 = prefix[i] as i64 879 var ok: i64 = 0 880 if c >= 97 { if c <= 122 { ok = 1 } } 881 if c >= 48 { if c <= 57 { ok = 1 } } 882 if c == 95 { ok = 1 } 883 if c == 45 { ok = 1 } 884 if c == 47 { ok = 1; if (prefix[i - 1] as i64) == 47 { return 0 } } 885 if c == 46 { ok = 1; if (prefix[i - 1] as i64) == 46 { return 0 } } 886 if ok == 0 { return 0 } 887 i = i + 1 888 } 889 if port < 1024 { return 0 } 890 if port > 65535 { return 0 } 891 var mok: i64 = 0 892 if md_cstr_eq(mode, "buffered" as *u8) == 1 { mok = 1 } 893 if md_cstr_eq(mode, "stream" as *u8) == 1 { mok = 1 } 894 if md_cstr_eq(mode, "gated" as *u8) == 1 { mok = 1 } 895 if mok == 0 { return 0 } 896 return 1 897} 898// atomically upsert the route row into knowledge/hosting/proxy_routes.conf: preserve every OTHER line, replace 899// any existing "<host> <prefix> ..." row, append the new one; tmp+rename. Returns 1 ok / 0 write-fail. 900func md_route_append(confp: *u8, host: *u8, prefix: *u8, port: i64, mode: *u8) -> i64 { 901 let szp: *i64 = sys_mmap(16) as *i64 902 szp[0] = 0 903 let old: *u8 = md_read_file(confp, szp) 904 let oldn: i64 = szp[0] 905 // NEVER-BRICK: refuse to write when the existing table is unreadable/empty. A transient read failure 906 // (fd exhaustion etc.) with old==0 would otherwise REPLACE the populated edge table with one row -> 907 // every proxied surface incl. /api itself lost = self-lockout. The live table always has rows; a 908 // genuinely fresh bootstrap is an ssh-once operation, not this API's job. Fail-closed. 909 if (old as i64) == 0 { return 0 } 910 if oldn == 0 { return 0 } 911 // build the dedup match key: "<host> <prefix> " 912 let mk: *u8 = sys_mmap(160) 913 var ko: i64 = 0 914 var a: i64 = 0 915 while host[a] != (0 as u8) { mk[ko] = host[a]; ko = ko + 1; a = a + 1 } 916 mk[ko] = 32 as u8; ko = ko + 1 917 a = 0 918 while prefix[a] != (0 as u8) { mk[ko] = prefix[a]; ko = ko + 1; a = a + 1 } 919 mk[ko] = 32 as u8; ko = ko + 1 920 mk[ko] = 0 as u8 921 let mkl: i64 = ko 922 let out: *u8 = sys_mmap(262144) 923 var o: i64 = 0 924 if (old as i64) != 0 { 925 var ls: i64 = 0 926 while ls < oldn { 927 var le: i64 = ls 928 var sc: i64 = 1 929 while sc == 1 { if le >= oldn { sc = 0 } else { if (old[le] as i64) == 10 { sc = 0 } else { le = le + 1 } } } 930 // does this line start with the match key? 931 var m: i64 = 1 932 if ls + mkl > le + 1 { m = 0 } 933 if m == 1 { 934 var j: i64 = 0 935 while j < mkl { if (old[ls + j] as i64) != (mk[j] as i64) { m = 0; j = mkl } else { j = j + 1 } } 936 } 937 if m == 0 { 938 var k: i64 = ls 939 while k <= le { if k < oldn { out[o] = old[k]; o = o + 1 } k = k + 1 } 940 } 941 ls = le + 1 942 } 943 } 944 // append the new row (ensure a trailing newline precedes if the file didn't end in one) 945 if o > 0 { if (out[o - 1] as i64) != 10 { out[o] = 10 as u8; o = o + 1 } } 946 a = 0 947 while host[a] != (0 as u8) { out[o] = host[a]; o = o + 1; a = a + 1 } 948 out[o] = 32 as u8; o = o + 1 949 a = 0 950 while prefix[a] != (0 as u8) { out[o] = prefix[a]; o = o + 1; a = a + 1 } 951 out[o] = 32 as u8; o = o + 1 952 o = md_catn(out, o, port) 953 out[o] = 32 as u8; o = o + 1 954 a = 0 955 while mode[a] != (0 as u8) { out[o] = mode[a]; o = o + 1; a = a + 1 } 956 out[o] = 10 as u8; o = o + 1 957 // NEVER-BRICK: bank the pre-edit table as confp+".prev" FIRST (recovery: cp .prev back), then 958 // atomic write: tmp = confp + ".tmp", rename over confp. Same idiom as binary deploys. 959 let prevp: *u8 = sys_mmap(512) 960 var pj: i64 = 0 961 while confp[pj] != (0 as u8) { prevp[pj] = confp[pj]; pj = pj + 1 } 962 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 963 let pfd: i64 = sys_openat_wr(prevp, 0x1a4) 964 if pfd >= 0 { sys_write(pfd, old, oldn); sys_close(pfd) } 965 let tmpp: *u8 = sys_mmap(512) 966 var tj: i64 = 0 967 while confp[tj] != (0 as u8) { tmpp[tj] = confp[tj]; tj = tj + 1 } 968 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 969 let fd: i64 = sys_openat_wr(tmpp, 0x1a4) 970 if fd < 0 { return 0 } 971 sys_write(fd, out, o) 972 sys_close(fd) 973 sys_renameat(tmpp, confp) 974 return 1 975} 976// kill every process whose /proc/<pid>/cmdline CONTAINS needle (full-cmdline match -- the 15-char comm 977// truncation trap). Returns processes signalled. Mirrors the proven hostctl proc_kill_by_name. 978func md_pk_contains(hay: *u8, hn: i64, needle: *u8, nl: i64) -> i64 { 979 if nl == 0 { return 0 } 980 var i: i64 = 0 981 while i + nl <= hn { 982 var j: i64 = 0 983 var ok: i64 = 1 984 while j < nl { if (hay[i + j] as i64) != (needle[j] as i64) { ok = 0; j = nl } else { j = j + 1 } } 985 if ok == 1 { return 1 } 986 i = i + 1 987 } 988 return 0 989} 990func md_pk_atoi(s: *u8) -> i64 { 991 var v: i64 = 0 992 var i: i64 = 0 993 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 } 994 return v 995} 996// promote a staged <cwd>/<name>.new -> live <name> (.prev kept), chmod +x. Returns 1 if a .new existed 997// and was promoted, else 0 (restart still valid -- just reloads the same on-disk binary). cwd = nishihost. 998// IDEMPOTENCY for /api/tools/register: is <nm> already the first TAB-field of a line in tool_allowlist.conf? 999// Reads the (small) allowlist raw; matches a line that starts with "<nm>\t". Fail-open to 0 (absent) so a 1000// missing/unreadable allowlist doesn't block a first registration. 1001func md_allow_has_name(nm: *u8) -> i64 { 1002 let fd: i64 = sys_openat_rd("tool_allowlist.conf" as *u8) 1003 if fd < 0 { return 0 } 1004 let cap: i64 = 1 << 18 1005 let buf: *u8 = sys_mmap(cap) 1006 let n: i64 = sys_read(fd, buf, cap - 1) 1007 sys_close(fd) 1008 if n <= 0 { return 0 } 1009 buf[n] = 0 as u8 1010 let nl: i64 = md_len(nm) 1011 var i: i64 = 0 1012 while i + nl < n { 1013 var ls: i64 = 0 1014 if i == 0 { ls = 1 } else { if buf[i-1] == (10 as u8) { ls = 1 } } 1015 if ls == 1 { 1016 var m: i64 = 1 1017 var j: i64 = 0 1018 while j < nl { if buf[i+j] != nm[j] { m = 0; j = nl } else { j = j + 1 } } 1019 if m == 1 { if buf[i+nl] == (9 as u8) { return 1 } } 1020 } 1021 i = i + 1 1022 } 1023 return 0 1024} 1025 1026// seq1281 (RESTORED AGAIN 2026-07-30 -- 3rd backdate, see seq1439/nx_srcguard): read the existing pinned- 1027// args column (4th TAB field .. EOL) of tool <nm>'s allowlist row into dst. Returns copied length; 0 = no 1028// row / no args / unreadable. Lets register-update PRESERVE pinned args when args= is omitted -- an omitted 1029// field must never silently widen a pinned oracle into caller-controlled argv. 1030func md_allow_get_args(nm: *u8, dst: *u8, cap: i64) -> i64 { 1031 let fd: i64 = sys_openat_rd("tool_allowlist.conf" as *u8) 1032 if fd < 0 { return 0 } 1033 let bcap: i64 = 1 << 18 1034 let buf: *u8 = sys_mmap(bcap) 1035 let n: i64 = sys_read(fd, buf, bcap - 1) 1036 sys_close(fd) 1037 if n <= 0 { return 0 } 1038 buf[n] = 0 as u8 1039 let nl: i64 = md_len(nm) 1040 var i: i64 = 0 1041 while i + nl < n { 1042 var ls: i64 = 0 1043 if i == 0 { ls = 1 } else { if buf[i-1] == (10 as u8) { ls = 1 } } 1044 if ls == 1 { 1045 var m: i64 = 1 1046 var j: i64 = 0 1047 while j < nl { if buf[i+j] != nm[j] { m = 0; j = nl } else { j = j + 1 } } 1048 if m == 1 { if buf[i+nl] == (9 as u8) { 1049 var p: i64 = i + nl + 1 1050 var tabs: i64 = 0 1051 var argst: i64 = 0 1052 while p < n { 1053 let c: i64 = buf[p] as i64 1054 if c == 10 { p = n } else { 1055 if c == 9 { tabs = tabs + 1; if tabs == 2 { argst = p + 1; p = n } } 1056 if p < n { p = p + 1 } 1057 } 1058 } 1059 if argst == 0 { return 0 } 1060 var o: i64 = 0 1061 var q: i64 = argst 1062 while q < n { 1063 if buf[q] == (10 as u8) { q = n } else { 1064 if o < cap - 1 { dst[o] = buf[q]; o = o + 1 } 1065 q = q + 1 1066 } 1067 } 1068 dst[o] = 0 as u8 1069 return o 1070 } } 1071 } 1072 i = i + 1 1073 } 1074 return 0 1075} 1076 1077// atomically REPLACE the tool_allowlist.conf row for tool <nm> -- the register-update verb's mutation 1078// (eats the ssh-once row-repoint class: evidence_checkin/mvault/clock repoints). Preserves every OTHER 1079// line byte-exact, drops the existing "<nm>\t..." row(s), appends the replacement 1080// "<nm>\t<elfp>\tGREEN[\t<args>]" row; banks .prev FIRST then tmp+rename (md_route_append idiom). 1081// UPDATE CAN NEVER CREATE: refuses (0) when no row matches. NEVER-BRICK: refuses when the allowlist is 1082// unreadable/empty so a transient read failure cannot truncate the live tool table. 1 ok / 0 refused. 1083func md_allow_update_row(nm: *u8, elfp: *u8, argp: *u8, argn: i64) -> i64 { 1084 let szp: *i64 = sys_mmap(16) as *i64 1085 szp[0] = 0 1086 let old: *u8 = md_read_file("tool_allowlist.conf" as *u8, szp) 1087 let oldn: i64 = szp[0] 1088 if (old as i64) == 0 { return 0 } 1089 if oldn == 0 { return 0 } 1090 let nl: i64 = md_len(nm) 1091 let out: *u8 = sys_mmap(262144) 1092 var o: i64 = 0 1093 var found: i64 = 0 1094 var ls: i64 = 0 1095 while ls < oldn { 1096 var le: i64 = ls 1097 var sc: i64 = 1 1098 while sc == 1 { if le >= oldn { sc = 0 } else { if (old[le] as i64) == 10 { sc = 0 } else { le = le + 1 } } } 1099 var m: i64 = 0 1100 if ls + nl < le { 1101 if (old[ls + nl] as i64) == 9 { 1102 m = 1 1103 var j: i64 = 0 1104 while j < nl { if old[ls + j] != nm[j] { m = 0; j = nl } else { j = j + 1 } } 1105 } 1106 } 1107 if m == 1 { found = 1 } else { 1108 var k: i64 = ls 1109 while k <= le { if k < oldn { out[o] = old[k]; o = o + 1 } k = k + 1 } 1110 } 1111 ls = le + 1 1112 } 1113 if found == 0 { return 0 } 1114 if o > 0 { if (out[o - 1] as i64) != 10 { out[o] = 10 as u8; o = o + 1 } } 1115 var a: i64 = 0 1116 while nm[a] != (0 as u8) { out[o] = nm[a]; o = o + 1; a = a + 1 } 1117 out[o] = 9 as u8; o = o + 1 1118 a = 0 1119 while elfp[a] != (0 as u8) { out[o] = elfp[a]; o = o + 1; a = a + 1 } 1120 out[o] = 9 as u8; o = o + 1 1121 out[o] = 71 as u8; o = o + 1 1122 out[o] = 82 as u8; o = o + 1 1123 out[o] = 69 as u8; o = o + 1 1124 out[o] = 69 as u8; o = o + 1 1125 out[o] = 78 as u8; o = o + 1 1126 if argn > 0 { 1127 out[o] = 9 as u8; o = o + 1 1128 a = 0 1129 while a < argn { out[o] = argp[a]; o = o + 1; a = a + 1 } 1130 } 1131 out[o] = 10 as u8; o = o + 1 1132 let pfd: i64 = sys_openat_wr("tool_allowlist.conf.prev" as *u8, 0x1a4) 1133 if pfd >= 0 { sys_write(pfd, old, oldn); sys_close(pfd) } 1134 let fd: i64 = sys_openat_wr("tool_allowlist.conf.nxtmp" as *u8, 0x1a4) 1135 if fd < 0 { return 0 } 1136 sys_write(fd, out, o) 1137 sys_close(fd) 1138 sys_renameat("tool_allowlist.conf.nxtmp" as *u8, "tool_allowlist.conf" as *u8) 1139 return 1 1140} 1141 1142func md_streq(a: *u8, b: *u8) -> i64 { 1143 var i: i64 = 0 1144 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 1145 if b[i] != (0 as u8) { return 0 } 1146 return 1 1147} 1148 1149// FAIL-CLOSED promote policy for POST /api/promote -- STRUCTURAL, still by-construction (F-210b eaten 07-18): 1150// (1) compiled-in DENY first: daemons + credential oracles (family substrings, so new members inherit the 1151// refusal). Daemons carry live connections -> the health-checked auto-rollback /api/deploy, NEVER a rename. 1152// The deny can never be overridden by any data plane or later rule. 1153// (2) the enumerated one-shot allows (back-compat fast path). 1154// (3) else STAGED-ARTIFACT rule: a name whose <name>.sov.elf.new exists as a real ELF is promotable. Staging is 1155// only reachable via the owner-gated /api/build//api/upload, so this widens promote to OWNER-only actions, 1156// never to a write cap -- the same stance as nx_fs_write denying the tool allowlist, without the 1157// add-a-name-recompile-mgmt treadmill for every new one-shot organ. 1158func md_contains(a: *u8, sub: *u8) -> i64 { 1159 var i: i64 = 0 1160 while a[i] != (0 as u8) { 1161 var j: i64 = 0 1162 var mism: i64 = 0 1163 var run: i64 = 1 1164 while run == 1 { 1165 if sub[j] == (0 as u8) { run = 0 } else { 1166 if a[i+j] == (0 as u8) { mism = 1; run = 0 } else { 1167 if a[i+j] != sub[j] { mism = 1; run = 0 } else { j = j + 1 } 1168 } 1169 } 1170 } 1171 if mism == 0 { return 1 } 1172 i = i + 1 1173 } 1174 return 0 1175} 1176// ---------- organ ROLE, declared (seq1492) ---------- 1177// 1178// u2605THE DEFECT THIS REPLACES: promote classified by NAME SUBSTRING, so a 1179// one-shot worker called nx_torrent_get was refused as a "daemon" while 1180// /api/deploy refused it as an unknown target. **Two verbs disagreeing about an 1181// artefact's KIND leave it unshippable** -- and the only remaining way to update 1182// it was the raw scp/ssh path that seq1439 identified as the WORK-DESTROYER. A 1183// substring is not a role, exactly as a substring is not a hazard. 1184// 1185// Policy lives in DATA (rule 11): knowledge/status/organ_kind.conf, rows 1186// <name><TAB-or-SPACE>one-shot|oneshot|daemon|oracle|lib 1187// Returns 1 = one-shot (promotable), 2 = daemon/oracle (deploy lane), 0 = undeclared. 1188// Undeclared falls through to the historical name heuristics, so nothing regresses 1189// and the heuristic becomes the DEFAULT rather than the law. 1190// 1191// ★PERMANENT ROOT FIX 2026-07-31 (debt 1785453784, which /api/promote's own 400 text described but 1192// nobody had closed). TWO defects, and fixing only the first would have LOOKED right while still failing: 1193// 1. WRONG PATH. This opened the SINGULAR-less plural "knowledge/organ_kinds.conf", which was renamed to 1194// .RETIRED-seq1754-use-status-organ_kind. open() returned <0 -> return 0 -> EVERY organ fell through 1195// to the name heuristic, so NO declaration anywhere was readable and gate promotion was a coin flip. 1196// Now reads the surviving SSOT knowledge/status/organ_kind.conf. ONE source of truth, not two. 1197// 2. WRONG SEPARATOR. The surviving file is SPACE-separated (`nx_build_admit oneshot`) but this parser 1198// accepted ONLY a TAB (9), so a path-only fix would have found the file, parsed nothing, and still 1199// returned 0 -- a silent no-op that reads as success. Now takes the FIRST tab OR space. 1200// The value test below already tolerates both spellings: it checks only the leading "on", matching 1201// `oneshot` and `one-shot` alike. Verified against the real file before editing, not assumed. 1202func md_organ_kind(nm: *u8) -> i64 { 1203 let fd: i64 = sys_openat_rd("knowledge/status/organ_kind.conf" as *u8) 1204 if fd < 0 { return 0 } 1205 let buf: *u8 = sys_mmap(65536) 1206 let n: i64 = sys_read(fd, buf, 65536) 1207 sys_close(fd) 1208 if n <= 0 { return 0 } 1209 var nl: i64 = 0 1210 while nm[nl] != (0 as u8) { nl = nl + 1 } 1211 var ls: i64 = 0 1212 while ls < n { 1213 var le: i64 = ls 1214 var g: i64 = 1 1215 while g == 1 { if le >= n { g = 0 } else { if buf[le] == (10 as u8) { g = 0 } else { le = le + 1 } } } 1216 if le > ls { if buf[ls] != (35 as u8) { 1217 var sep: i64 = 0 - 1 1218 var q: i64 = ls 1219 while q < le { 1220 if sep < 0 { 1221 if buf[q] == (9 as u8) { sep = q } 1222 else { if buf[q] == (32 as u8) { sep = q } } 1223 } 1224 q = q + 1 1225 } 1226 if sep > ls { 1227 if sep - ls == nl { 1228 var same: i64 = 1 1229 var c: i64 = 0 1230 while c < nl { if buf[ls + c] != nm[c] { same = 0; c = nl } else { c = c + 1 } } 1231 if same == 1 { 1232 let vs: i64 = sep + 1 1233 if vs < le { if buf[vs] == (111 as u8) { if vs + 1 < le { if buf[vs + 1] == (110 as u8) { return 1 } } } } 1234 return 2 1235 } 1236 } 1237 } 1238 } } 1239 ls = le + 1 1240 } 1241 return 0 1242} 1243 1244// Does `w` occur in `nm` starting at a TOKEN BOUNDARY -- the start of the name, 1245// or immediately after '_'? Organ names are underscore-tokenised, so this asks 1246// "is one of the words in this name `w`" instead of "do these letters appear 1247// anywhere". 1248func mdh_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8) { n=n+1 } return n } 1249 1250func mdh_tail_eq(nm: *u8, n: i64, suf: *u8) -> i64 { 1251 let sl: i64 = mdh_len(suf) 1252 if sl > n { return 0 } 1253 var i: i64 = 0 1254 while i < sl { if nm[n - sl + i] != suf[i] { return 0 } i = i + 1 } 1255 return 1 1256} 1257 1258func md_tok_at(nm: *u8, w: *u8) -> i64 { 1259 let wl: i64 = mdh_len(w) 1260 let nl: i64 = mdh_len(nm) 1261 var i: i64 = 0 1262 while i + wl <= nl { 1263 var boundary: i64 = 0 1264 if i == 0 { boundary = 1 } else { if nm[i - 1] == (95 as u8) { boundary = 1 } } 1265 if boundary == 1 { 1266 var j: i64 = 0 1267 var m: i64 = 1 1268 while j < wl { if nm[i + j] != w[j] { m = 0; j = wl } else { j = j + 1 } } 1269 if m == 1 { return 1 } 1270 } 1271 i = i + 1 1272 } 1273 return 0 1274} 1275 1276// Does the name end in one of the verifier suffixes the system already treats as 1277// a declaration (_gate/_test/_kat -- the /api/gate_run bound)? 1278func md_name_is_oracle(nm: *u8) -> i64 { 1279 let n: i64 = mdh_len(nm) 1280 if mdh_tail_eq(nm, n, "_gate" as *u8) == 1 { return 1 } 1281 if mdh_tail_eq(nm, n, "_test" as *u8) == 1 { return 1 } 1282 if mdh_tail_eq(nm, n, "_kat" as *u8) == 1 { return 1 } 1283 return 0 1284} 1285 1286// NON-OVERRIDABLE deny: credential oracles and key material. A conf row must 1287// never be able to make these promotable -- otherwise the role registry becomes 1288// a privilege-escalation surface (config that grants authority). 1289// 1290// ★★★★★ ROOT-FIXED 2026-07-31 (debt 1785511766). This used to ask 1291// md_contains -- a RAW SUBSTRING -- which is the exact defect the sibling 1292// md_promote_deny was already fixed for at seq1789 ("THE SUFFIX IS A 1293// DECLARATION; THE SUBSTRING WAS A GUESS", the nx_survey_serve_gate collision), 1294// left unfixed one layer down here in the NON-OVERRIDABLE deny. 1295// 1296// "vault" means CREDENTIAL CUSTODY here, but as a substring it also matches the 1297// entire MEDIA vault family -- nx_mvault, nx_mvault_coll, nx_mvault_walk. One 1298// substring, two unrelated meanings. The result was that gate-proven media-vault 1299// binaries were unshippable by any sanctioned route, and the 2026-07-23 session 1300// resorted to an ssh cp/mv rename to ship them. 1301// ★★★★★**A GUARD THAT CANNOT BE SATISFIED PRODUCES A BYPASS, NOT SAFETY.** 1302// 1303// TWO CHANGES, BOTH STRICTLY SAFE -- this narrows FALSE positives only, and every 1304// real credential organ below still denies (proven by nx_promote_deny_gate): 1305// 1. TOKEN-BOUNDARY, not substring. "vault" still matches nx_vault_gateway (the 1306// word is a token) but no longer matches nx_mvault (the letters are merely 1307// inside one). A glued credential name like nx_secretstore STILL denies, 1308// because the boundary is checked at the START of the token only. 1309// 2. An ORACLE SUFFIX is exempt. Promoting nx_cap_mint_gate installs 1310// nx_cap_mint_gate.elf -- it CANNOT swap nx_cap_mint.elf -- so a verifier can 1311// never be the credential organ it verifies. Safe by construction, and it 1312// reuses the system's own _gate/_test/_kat rule rather than inventing one. 1313func md_promote_deny_hard(nm: *u8) -> i64 { 1314 if md_name_is_oracle(nm) == 1 { return 0 } 1315 if md_tok_at(nm, "mint" as *u8) == 1 { return 1 } 1316 if md_tok_at(nm, "vault" as *u8) == 1 { return 1 } 1317 if md_tok_at(nm, "secret" as *u8) == 1 { return 1 } 1318 if md_tok_at(nm, "keygen" as *u8) == 1 { return 1 } 1319 if md_tok_at(nm, "login" as *u8) == 1 { return 1 } 1320 return 0 1321} 1322 1323func md_promote_deny(nm: *u8) -> i64 { 1324 // u2605CONVERGED 2026-07-30 (seq1754). This used to consult its OWN role conf 1325 // (knowledge/organ_kinds.conf) -- a SECOND classifier for a concept a 1326 // sibling had already implemented properly as nx_organkind 1327 // (ok_kind_of_path over knowledge/status/organ_kind.conf), wired into the 1328 // promote handler ABOVE this function. Two confs and two readers for one 1329 // concept is the sprawl we keep warning about, and I built half of it by 1330 // not checking /code/tools before starting. 1331 // 1332 // The canonical reader now decides FIRST at the API layer; this function is 1333 // only reached for an UNDECLARED name, where it is the legacy name 1334 // heuristic -- so the duplicate lookup is removed and its rows were merged 1335 // into the canonical conf. deny_hard STAYS: credential oracles must be 1336 // refused non-overridably regardless of any declared kind. 1337 if md_promote_deny_hard(nm) == 1 { return 1 } 1338 if md_contains(nm, "serve" as *u8) == 1 { return 1 } 1339 if md_contains(nm, "daemon" as *u8) == 1 { return 1 } 1340 if md_contains(nm, "mint" as *u8) == 1 { return 1 } 1341 if md_contains(nm, "vault" as *u8) == 1 { return 1 } 1342 if md_contains(nm, "secret" as *u8) == 1 { return 1 } 1343 if md_contains(nm, "keygen" as *u8) == 1 { return 1 } 1344 if md_contains(nm, "login" as *u8) == 1 { return 1 } 1345 if md_contains(nm, "router" as *u8) == 1 { return 1 } 1346 if md_contains(nm, "hostctl" as *u8) == 1 { return 1 } 1347 if md_contains(nm, "signaling" as *u8) == 1 { return 1 } 1348 if md_contains(nm, "gateway" as *u8) == 1 { return 1 } 1349 if md_contains(nm, "torrent" as *u8) == 1 { return 1 } 1350 if md_contains(nm, "mgmt" as *u8) == 1 { return 1 } 1351 if md_contains(nm, "_gw" as *u8) == 1 { return 1 } 1352 if md_streq(nm, "sites" as *u8) == 1 { return 1 } 1353 return 0 1354} 1355func md_staged_elf_ok(nm: *u8) -> i64 { 1356 let p: *u8 = sys_mmap(192) 1357 var o: i64 = 0 1358 var i: i64 = 0 1359 while nm[i] != (0 as u8) { p[o] = nm[i]; o = o + 1; i = i + 1 } 1360 let sfx: *u8 = ".sov.elf.new" as *u8 1361 var j: i64 = 0 1362 while sfx[j] != (0 as u8) { p[o] = sfx[j]; o = o + 1; j = j + 1 } 1363 p[o] = 0 as u8 1364 let fd: i64 = sys_openat_rd(p) 1365 if fd < 0 { return 0 } 1366 let hb: *u8 = sys_mmap(8) 1367 let r: i64 = sys_read(fd, hb, 4) 1368 sys_close(fd) 1369 if r != 4 { return 0 } 1370 if hb[0] != (127 as u8) { return 0 } 1371 if hb[1] != (69 as u8) { return 0 } 1372 if hb[2] != (76 as u8) { return 0 } 1373 if hb[3] != (70 as u8) { return 0 } 1374 return 1 1375} 1376func md_promote_organ_ok(nm: *u8) -> i64 { 1377 if md_promote_deny(nm) == 1 { return 0 } 1378 if md_streq(nm, "nx_ecosystem_maturity_rollup" as *u8) == 1 { return 1 } 1379 if md_streq(nm, "nx_ecomat_seed" as *u8) == 1 { return 1 } 1380 if md_streq(nm, "nx_ecomat_beat" as *u8) == 1 { return 1 } 1381 if md_streq(nm, "nx_ecomat_page" as *u8) == 1 { return 1 } 1382 if md_streq(nm, "nx_tool_argecho" as *u8) == 1 { return 1 } 1383 // 07-17 (eat the ssh-once deploy debt): the fork-exec MCP TOOL organ family -- one-shot elfs the 1384 // tools daemon spawns per call. NOT daemons (those stay refused -> /api/deploy) and NOT the 1385 // credential oracles (nx_session_mint / nx_cap_mint stay OFF this list deliberately). 1386 if md_streq(nm, "nx_shelltool" as *u8) == 1 { return 1 } 1387 if md_streq(nm, "nx_common_tasks" as *u8) == 1 { return 1 } 1388 if md_streq(nm, "nx_frontier_board" as *u8) == 1 { return 1 } 1389 if md_streq(nm, "nx_page_verify" as *u8) == 1 { return 1 } 1390 if md_streq(nm, "nx_store_seed" as *u8) == 1 { return 1 } 1391 if md_streq(nm, "nx_workflow" as *u8) == 1 { return 1 } 1392 if md_streq(nm, "nx_memory" as *u8) == 1 { return 1 } 1393 if md_streq(nm, "nx_heal" as *u8) == 1 { return 1 } 1394 if md_streq(nm, "nx_fs" as *u8) == 1 { return 1 } 1395 if md_streq(nm, "nx_fs_write" as *u8) == 1 { return 1 } 1396 if md_streq(nm, "nx_site_publish" as *u8) == 1 { return 1 } 1397 if md_streq(nm, "nx_https_get" as *u8) == 1 { return 1 } 1398 if md_streq(nm, "nx_verify" as *u8) == 1 { return 1 } 1399 // 07-17 (stem-first-byte-fab lane): the compare-publish pipeline organs -> API-promotable, so a 1400 // brand-new compare domain publishes end-to-end over MCP (build -> promote -> regen), zero shell. 1401 if md_streq(nm, "nx_compare_regen" as *u8) == 1 { return 1 } 1402 if md_streq(nm, "nx_swcompare_matrix" as *u8) == 1 { return 1 } 1403 if md_streq(nm, "nx_swcompare_sota" as *u8) == 1 { return 1 } 1404 if md_streq(nm, "nx_swcompare_hub" as *u8) == 1 { return 1 } 1405 if md_streq(nm, "nx_maturity_board" as *u8) == 1 { return 1 } 1406 // (3) staged-artifact rule: owner-staged one-shot builds are promotable (deny above already refused 1407 // every daemon/oracle shape, so this can only ever admit tool-organ names). 1408 if md_staged_elf_ok(nm) == 1 { return 1 } 1409 return 0 1410} 1411 1412// ---- seq1484: PROMOTION PROVENANCE -- a promote may not walk a binary BACKWARDS ------------------ 1413// THE BLEED THIS STOPS (measured 2026-07-30): the mgmt API was reverted THREE times and the compiler 1414// TWICE in a single session, each time by promoting a binary built elsewhere from a stale tree. Every 1415// existing control passed it: the ELF is valid, the size is plausible, and promote_toolchain's canary 1416// COMPILES AND RUNS it GREEN -- because a stale-but-working binary does all of that perfectly. 1417// ★LIVENESS IS NOT CURRENCY. "It works" cannot distinguish the newest build from last week's. 1418// 1419// The invariant that CAN is the same one already protecting sources (nx_symdrop) and tree pushes 1420// (nx_treepack REFUSED-WOULD-DROP-SYMBOLS): a normal promotion installs content this target has NEVER 1421// held; a revert installs content it ALREADY HELD. So keep an append-only per-target content-hash 1422// history. Staged == newest -> a harmless re-promote, allowed. Staged never seen -> a real advance, 1423// allowed and recorded. Staged matches an EARLIER generation -> THE BINARY WOULD GO BACKWARDS, refused 1424// and named. No build-time provenance, no clock, no size heuristic, nothing to spoof by touching a file. 1425// 1426// ESCAPE HATCH BY DESIGN, NOT BY FLAG: going backwards deliberately is what /api/rollback is FOR, and it 1427// does not route through here. A `force` parameter would just be the hole re-opened under a nicer name. 1428const MD_PROV_HIST: *u8 = "knowledge/promote_history.tsv" as *u8 1429const MD_PROV_CAP: i64 = 1048576 1430const MD_PROV_RDCH: i64 = 262144 1431const MD_PROV_FNV_OFF: i64 = 1469598103934665603 1432const MD_PROV_FNV_PRM: i64 = 1099511628211 1433const MD_PROV_TAB: i64 = 9 1434const MD_PROV_NL: i64 = 10 1435 1436// FNV-1a over a whole file, streamed so a large ELF needs no full-size buffer. 0 = unreadable. 1437func md_prov_hash(path: *u8) -> i64 { 1438 let fd: i64 = sys_openat_rd(path) 1439 if fd < 0 { return 0 } 1440 let b: *u8 = sys_mmap(MD_PROV_RDCH) 1441 var h: i64 = MD_PROV_FNV_OFF 1442 var go: i64 = 1 1443 while go == 1 { 1444 let n: i64 = sys_read(fd, b, MD_PROV_RDCH) 1445 if n <= 0 { go = 0 } else { 1446 var i: i64 = 0 1447 while i < n { h = h ^ (b[i] as i64); h = h * MD_PROV_FNV_PRM; i = i + 1 } 1448 } 1449 } 1450 sys_close(fd) 1451 return h 1452} 1453// Walk the history for `name`. out3[0]=generations seen, out3[1]=1 if h is the NEWEST, out3[2]=index of 1454// an EARLIER generation equal to h (-1 if none). 1455func md_prov_probe(name: *u8, h: i64, out3: *i64) -> i64 { 1456 out3[0] = 0; out3[1] = 0; out3[2] = 0 - 1 1457 let buf: *u8 = sys_mmap(MD_PROV_CAP) 1458 let n: i64 = dp_read(MD_PROV_HIST, buf, MD_PROV_CAP - 1) 1459 if n <= 0 { return 0 } 1460 var nl: i64 = 0 1461 while name[nl] != (0 as u8) { nl = nl + 1 } 1462 var gen: i64 = 0 1463 var ls: i64 = 0 1464 var i: i64 = 0 1465 while i <= n { 1466 var eol: i64 = 0 1467 if i == n { eol = 1 } else { if buf[i] == (MD_PROV_NL as u8) { eol = 1 } } 1468 if eol == 1 { 1469 if i > ls { 1470 var tab: i64 = 0 - 1 1471 var t: i64 = ls 1472 while t < i { if buf[t] == (MD_PROV_TAB as u8) { tab = t; t = i } else { t = t + 1 } } 1473 if tab > 0 { if tab - ls == nl { 1474 var m: i64 = 1 1475 var c: i64 = 0 1476 while c < nl { if buf[ls+c] != name[c] { m = 0; c = nl } else { c = c + 1 } } 1477 if m == 1 { 1478 var v: i64 = 0 1479 var neg: i64 = 0 1480 var k: i64 = tab + 1 1481 if k < i { if buf[k] == (45 as u8) { neg = 1; k = k + 1 } } 1482 while k < i { v = v * 10 + ((buf[k] as i64) - 48); k = k + 1 } 1483 if neg == 1 { v = 0 - v } 1484 if v == h { out3[2] = gen; out3[1] = 1 } else { out3[1] = 0 } 1485 gen = gen + 1 1486 } 1487 } } 1488 } 1489 ls = i + 1 1490 } 1491 i = i + 1 1492 } 1493 out3[0] = gen 1494 return gen 1495} 1496func md_prov_record(name: *u8, h: i64) -> i64 { 1497 let line: *u8 = sys_mmap(512) 1498 var o: i64 = 0 1499 var i: i64 = 0 1500 while name[i] != (0 as u8) { line[o] = name[i]; o = o + 1; i = i + 1 } 1501 line[o] = MD_PROV_TAB as u8; o = o + 1 1502 var m: i64 = h 1503 if m < 0 { line[o] = 45 as u8; o = o + 1; m = 0 - m } 1504 let t: *u8 = sys_mmap(32) 1505 var k: i64 = 0 1506 if m == 0 { t[0] = 48 as u8; k = 1 } 1507 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 1508 var z: i64 = k - 1 1509 while z >= 0 { line[o] = t[z]; o = o + 1; z = z - 1 } 1510 line[o] = MD_PROV_NL as u8; o = o + 1 1511 let fd: i64 = sys_openat_append(MD_PROV_HIST, 0x1a4) 1512 if fd < 0 { return 0 - 1 } 1513 sys_write(fd, line, o) 1514 sys_fsync(fd) 1515 sys_close(fd) 1516 return 0 1517} 1518// 1 = this staged content may be promoted; 0 = it would walk `name` BACKWARDS. 1519func md_prov_ok(name: *u8, stagedpath: *u8) -> i64 { 1520 let h: i64 = md_prov_hash(stagedpath) 1521 if h == 0 { return 1 } // unreadable: leave the decision to the existing checks 1522 let p: *i64 = sys_mmap(64) as *i64 1523 md_prov_probe(name, h, p) 1524 if p[0] == 0 { md_prov_record(name, h); return 1 } // first sighting = the baseline 1525 if p[1] == 1 { return 1 } // identical to the newest = harmless re-promote 1526 if p[2] >= 0 { return 0 } // seen EARLIER but not newest = A REVERT 1527 md_prov_record(name, h) 1528 return 1 1529} 1530 1531func md_promote_staged(name: *u8) -> i64 { 1532 let live: *u8 = sys_mmap(160) 1533 let newp: *u8 = sys_mmap(160) 1534 let prevp: *u8 = sys_mmap(160) 1535 var lo: i64 = 0 1536 while name[lo] != (0 as u8) { live[lo] = name[lo]; newp[lo] = name[lo]; prevp[lo] = name[lo]; lo = lo + 1 } 1537 live[lo] = 0 as u8 1538 md_copy_slice_z(newp, name, 0, lo, 160) 1539 md_copy_slice_z(prevp, name, 0, lo, 160) 1540 var no: i64 = lo 1541 let ns: *u8 = ".new" as *u8 1542 var a: i64 = 0 1543 while ns[a] != (0 as u8) { newp[no] = ns[a]; no = no + 1; a = a + 1 } 1544 newp[no] = 0 as u8 1545 // does <name>.new exist? 1546 let nfd: i64 = sys_openat_rd(newp) 1547 if nfd < 0 { return 0 } 1548 sys_close(nfd) 1549 // seq1484 PROVENANCE: refuse a promotion that would walk this target BACKWARDS to content 1550 // it already held. Placed BEFORE any rename, so a refusal leaves live and .prev untouched. 1551 if md_prov_ok(live, newp) == 0 { return 0 - 2 } 1552 var po: i64 = lo 1553 let ps: *u8 = ".prev" as *u8 1554 a = 0 1555 while ps[a] != (0 as u8) { prevp[po] = ps[a]; po = po + 1; a = a + 1 } 1556 prevp[po] = 0 as u8 1557 sys_renameat(live, prevp) // keep the old live as .prev (rollback) 1558 sys_renameat(newp, live) // promote .new -> live 1559 nx_chmod(live, 0x1ed) 1560 return 1 1561} 1562// ---- TOOLCHAIN PROMOTE (never-brick) -- eats seq891/seq903 ------------------------------------- 1563// THE GAP THIS CLOSES: the ecosystem could build and deploy every SERVICE over its own API but could 1564// NOT update the COMPILER that builds them, so a PROVEN compiler fix could not be landed API-first 1565// (rule 27). Measured cost on 2026-07-30: nx_fnptr_slot_probe was GREEN on the laptop compiler and RED 1566// on the hub compiler, i.e. obj.fn_field(args) silently emitted no indirect call for every organ in the 1567// tree, and the fix existed but had nowhere to go. A toolchain you cannot update is a toolchain whose 1568// bugs are permanent. 1569// 1570// ⚠THE PATH NOT TAKEN (seq903, and it must stay not-taken): shipping the toolchain through 1571// /api/upload + /api/unpack looks tempting because it touches only this module. nx_treepack writes every 1572// output file 0644 NON-EXECUTABLE and UNLINKS-then-recreates on any open failure, so unpacking over 1573// buildroot/_offc/nx_cc_sovereign.elf would either install a non-executable compiler or destroy the live 1574// one -- EVERY BUILD FOR EVERY SEAT, from a call that looks like a routine source sync. 1575// 1576// ⚠MATCHED-PAIR RULE (seq1315): nx_sov_build_run writes _build/<name>.sov.elf while nx_hostctl 1577// cmd_buildrun reads /tmp/<name>.sov.elf, so those two may only ever be promoted TOGETHER. They are 1578// admitted here because that pair-ship is a legitimate wave -- and admitting them is SAFE precisely 1579// because the caller canary-compiles and auto-rolls-back, so a mismatched pair cannot survive a promote. 1580// 1581// DIALECT NOTE: plain-if (no `else`), no empty string literals, <=6 params. This module is IMPORTED by 1582// nx_mgmt_api, and it must be compiled by TODAY'S hub compiler -- the one that still carries seq533 1583// (imported `else` desyncs the parser), seq907 (an empty literal aliases the next literal) and seq239 1584// (>6 params mishandled). The fix ships in a binary that the defect itself has to be able to build. 1585const MD_TC_MIN_ELF: i64 = 4096 // size floor: refuse a truncated upload or an HTML error page 1586const MD_TC_MODE_EXEC: i64 = 0x1ed // 0755 -- a compiler that is not executable is a dead ecosystem 1587 1588func md_toolchain_target_ok(nm: *u8) -> i64 { 1589 if md_streq(nm, "nx_cc_sovereign.elf" as *u8) == 1 { return 1 } 1590 if md_streq(nm, "nxasm_x86_main.elf" as *u8) == 1 { return 1 } 1591 if md_streq(nm, "nx_sov_build_run.elf" as *u8) == 1 { return 1 } 1592 return 0 1593} 1594 1595func md_tc_live(nm: *u8, buf: *u8) -> i64 { 1596 var o: i64 = md_cmp_cat(buf, 0, "buildroot/_offc/" as *u8) 1597 o = md_cmp_cat(buf, o, nm) 1598 return o 1599} 1600func md_tc_prev(nm: *u8, buf: *u8) -> i64 { 1601 var o: i64 = md_tc_live(nm, buf) 1602 o = md_cmp_cat(buf, o, ".prev" as *u8) 1603 return o 1604} 1605func md_tc_staged(nm: *u8, buf: *u8) -> i64 { 1606 var o: i64 = md_cmp_cat(buf, 0, nm) 1607 o = md_cmp_cat(buf, o, ".new" as *u8) 1608 return o 1609} 1610 1611// ELF magic + size floor. Returns the byte size on success, 0 on refusal. Validating the ARTIFACT (not 1612// an exit code) is the seq363/hostctl lesson: a 0-byte or non-ELF file must never reach the live slot. 1613func md_tc_elf_size(p: *u8) -> i64 { 1614 let fd: i64 = sys_openat_rd(p) 1615 if fd < 0 { return 0 } 1616 let sz: i64 = sys_lseek(fd, 0, 2) 1617 if sz < MD_TC_MIN_ELF { sys_close(fd); return 0 } 1618 sys_lseek(fd, 0, 0) 1619 let hb: *u8 = sys_mmap(8) 1620 var ok: i64 = 0 1621 if sys_read(fd, hb, 4) == 4 { 1622 if hb[0] == (0x7f as u8) { 1623 if hb[1] == (69 as u8) { 1624 if hb[2] == (76 as u8) { 1625 if hb[3] == (70 as u8) { ok = 1 } 1626 } 1627 } 1628 } 1629 } 1630 sys_close(fd) 1631 if ok == 0 { return 0 } 1632 return sz 1633} 1634 1635// Install staged <nm>.new -> buildroot/_offc/<nm>, banking the outgoing binary as .prev FIRST. 1636// Returns the installed size, or 0 if nothing was touched. Ordering is deliberate: validate BEFORE 1637// renaming anything, so a refused upload leaves the live compiler completely untouched. 1638func md_tc_install(nm: *u8) -> i64 { 1639 let stagedp: *u8 = sys_mmap(256) 1640 let livep: *u8 = sys_mmap(256) 1641 let prevp: *u8 = sys_mmap(256) 1642 md_tc_staged(nm, stagedp) 1643 md_tc_live(nm, livep) 1644 md_tc_prev(nm, prevp) 1645 let sz: i64 = md_tc_elf_size(stagedp) 1646 if sz == 0 { return 0 } 1647 // seq1484: the canary proves the incoming toolchain WORKS, which a stale-but-working one 1648 // also does. Provenance is what proves it is not last week s build. Checked before any rename. 1649 if md_prov_ok(livep, stagedp) == 0 { return 0 - 2 } 1650 sys_renameat(livep, prevp) // bank the outgoing compiler (rollback source) 1651 if sys_renameat(stagedp, livep) != 0 { 1652 sys_renameat(prevp, livep) // stage-rename failed: put the old one straight back 1653 nx_chmod(livep, MD_TC_MODE_EXEC) 1654 return 0 1655 } 1656 nx_chmod(livep, MD_TC_MODE_EXEC) 1657 return sz 1658} 1659 1660// Restore buildroot/_offc/<nm>.prev -> live. This is the rollback half of never-brick and it is called 1661// on CANARY FAILURE, so the ecosystem can never be left with a compiler that cannot compile. 1662func md_tc_rollback(nm: *u8) -> i64 { 1663 let livep: *u8 = sys_mmap(256) 1664 let prevp: *u8 = sys_mmap(256) 1665 md_tc_live(nm, livep) 1666 md_tc_prev(nm, prevp) 1667 if md_tc_elf_size(prevp) == 0 { return 0 } 1668 if sys_renameat(prevp, livep) != 0 { return 0 } 1669 nx_chmod(livep, MD_TC_MODE_EXEC) 1670 return 1 1671} 1672 1673// Our own pid. getpid = syscall 39 on x86-64. 1674func 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) 1675// Fork a detached child that waits, then SIGTERMs the given pid. Used so a self-restart can FINISH WRITING 1676// ITS RESPONSE before the process goes away: the reply reaches the caller, then the guard respawns the 1677// already-promoted binary. SIGTERM (not KILL) so a daemon that later grows a drain handler gets to use it. 1678func md_delayed_kill(pid: i64, delay_ms: i64) -> i64 { 1679 let p: i64 = sys_fork() 1680 if p == 0 { 1681 nx_setsid() 1682 sys_sleep_ms(delay_ms) 1683 nx_kill(pid, 15) 1684 sys_exit(0) 1685 } 1686 return p 1687} 1688func md_kill_by_name(needle: *u8) -> i64 { 1689 var self_hit: i64 = 0 1690 let nn: i64 = md_len(needle) 1691 let fd: i64 = sys_openat_rd("/proc" as *u8) 1692 if fd < 0 { return 0 } 1693 let buf: *u8 = sys_mmap(65536) 1694 let path: *u8 = sys_mmap(256) 1695 let clbuf: *u8 = sys_mmap(8192) 1696 var killed: i64 = 0 1697 var run: i64 = 1 1698 while run == 1 { 1699 let n: i64 = sys_getdents64(fd, buf, 65536) 1700 if n <= 0 { run = 0 } else { 1701 var off: i64 = 0 1702 while off < n { 1703 let rec: *u8 = ((buf as i64 + off) as *u8) 1704 let reclen: i64 = dirent_reclen(rec) 1705 if reclen <= 0 { off = n } else { 1706 let name: *u8 = dirent_name(rec) 1707 if name[0] >= (48 as u8) { if name[0] <= (57 as u8) { 1708 var p: i64 = 0 1709 let pre: *u8 = "/proc/" as *u8 1710 var a: i64 = 0 1711 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 } 1712 a = 0 1713 while name[a] != (0 as u8) { path[p] = name[a]; p = p + 1; a = a + 1 } 1714 let suf: *u8 = "/cmdline" as *u8 1715 a = 0 1716 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 } 1717 path[p] = 0 as u8 1718 let cfd: i64 = sys_openat_rd(path) 1719 if cfd >= 0 { 1720 let cln: i64 = sys_read(cfd, clbuf, 8192) 1721 sys_close(cfd) 1722 if cln > 0 { if md_pk_contains(clbuf, cln, needle, nn) == 1 { 1723 // ---- R5 SEQUENCING: NEVER SIGKILL OURSELVES MID-RESPONSE ---------------- 1724 // TWO defects, one root. (1) mgmt restarting/deploying ITSELF matches its own 1725 // cmdline here and SIGKILLs the process that is writing the reply -- that IS 1726 // the FETCH-FAIL every /api/deploy returns (~12x in one session), and it is why 1727 // the seq1563 deploy lease strands (we die before reaching our own release). 1728 // (2) NEW with SO_REUSEPORT: a hot restart runs old and new under the SAME 1729 // cmdline, so a name-matched kill would murder the freshly-spawned instance too 1730 // -- adopting REUSEPORT without this turns a handoff into an outage. 1731 // So: skip our own pid here, remember it, and schedule a DELAYED self-exit 1732 // after the loop. The reply is written first, THEN we go; the supervise guard 1733 // respawns the already-promoted binary. FETCH-FAIL becomes a real JSON body. 1734 let vpid: i64 = md_pk_atoi(name) 1735 if vpid == md_self_pid() { self_hit = 1 } else { 1736 nx_kill(vpid, 9) 1737 killed = killed + 1 1738 } 1739 } } 1740 } 1741 } } 1742 off = off + reclen 1743 } 1744 } 1745 } 1746 } 1747 sys_close(fd) 1748 // We matched OURSELVES: schedule the exit for AFTER the response is on the wire. 1500ms is the 1749 // reply-write window, not a guess at compile time -- the caller returns immediately after this. 1750 // Counted in `killed` so the JSON stays honest about what is going away. 1751 if self_hit == 1 { md_delayed_kill(md_self_pid(), 1500); killed = killed + 1 } 1752 return killed 1753} 1754 1755// ---- secondary adapters: validate / exec / probe ---------------------------------------------------- 1756func md_validate_artifact(path: *u8, kind: i64) -> i64 { return dep_validate(path, kind) } 1757 1758// drive the proven allowlisted nx_aw_hostctl with one sub -> its exit code (the supervisor/deploy exec port). 1759func md_exec_hostctl(sub: *u8) -> i64 { 1760 // Run the ON-NAS supervisor CLI directly. The mgmt daemon runs under the (root) supervisor, so it can drive the 1761 // surgical restart subs. The prior "_offc/nx_aw_hostctl.elf" is the LAPTOP->NAS bridge and is ABSENT on the NAS, 1762 // so /api/restart + /api/deploy were gate-proven but never live-executable. Absolute path (mgmt cwd=nishihost). 1763 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl" as *u8 1764 let args: *i64 = sys_mmap(16) as *i64 1765 args[0] = sub as i64 1766 return dep_run(helf, args, 1) 1767} 1768 1769// P2 off-LAN parity: run one allowlisted hostctl sub and CAPTURE its stdout to outpath (for /api/hostctl -> the 1770// phone gets torstat/routerctl/status output). Same on-NAS nx_hostctl the deploy path uses; single argv element 1771// (execve, no shell) so no injection; the allowlist below fail-closes to a curated safe read/action set. 1772func md_exec_hostctl_capture(sub: *u8, outpath: *u8) -> i64 { 1773 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl" as *u8 1774 let args: *i64 = sys_mmap(16) as *i64 1775 args[0] = sub as i64 1776 return dep_run_capture(helf, args, 1, outpath) 1777} 1778// 2-arg variant (e.g. `buildrun <target>`): run the on-NAS nx_hostctl <sub> <arg>, capture stdout -> outpath. 1779func md_exec_hostctl_capture2(sub: *u8, arg: *u8, outpath: *u8) -> i64 { 1780 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl" as *u8 1781 let args: *i64 = sys_mmap(16) as *i64 1782 args[0] = sub as i64 1783 args[1] = arg as i64 1784 return dep_run_capture(helf, args, 2, outpath) 1785} 1786// ---- BUILD ADMISSION (seq708/768/1390) -------------------------------------------------------- 1787// THE INCIDENT THIS PREVENTS: /api/build is the one heavyweight mgmt op -- it forks the sovereign nx_cc 1788// toolchain to compile a source tree -- and it did so with NO memory admission. Under a build-heavy 1789// session the host runs out of memory and the supervisor OOM-reaps nx_mgmt_api, taking the WHOLE deploy 1790// path down for every seat. Confirmed twice (seq698/708) and REPRODUCED LIVE 2026-07-30 (seq1390): 1791// both transports died mid-session (503 / status=0, TLS fine so the EDGE was healthy and the BACKEND 1792// was gone) and self-recovered only when the guard respawned it. 1793// 1794// ⚠seq768 recorded this fix as "WRITTEN + BUILT + VERIFIED, staged awaiting one rename" and was marked 1795// EATEN -- but on 2026-07-30 md_exec_build_admit / ma_emit_503 / the ma_do_build call site were found in 1796// NEITHER the laptop SSOT NOR the NAS buildroot (grep: 0 matches across 6972 files). The work never 1797// reached a source tree, so every build-heavy session kept re-rolling the outage. Rebuilt here, in the 1798// SSOT, where a rebuild cannot lose it. ★LAW: a debt is not eaten until its fix is IN A SOURCE TREE -- 1799// "built and staged" is not landed, and a binary nobody can rebuild is a rumour. 1800// 1801// FAIL-OPEN BY DESIGN (rule 26 / F881 ratchet stance): nx_build_admit exits 0 GRANT / 3 DENY (below the 1802// memory floor) / 4 QUEUE (load ceiling) / 2 usage / 5 unreadable-proc. We block ONLY on exit 3, the 1803// definitive memory wedge that actually causes the incident. Load-queueing and unreadable /proc both 1804// fall through to GRANT so admission control can never soft-brick the ecosystem's build path -- a 1805// refused-when-it-should-have-built is a worse failure here than an occasional reap. 1806// Floor 1024 MB; load ceiling deliberately huge so MEMORY is the sole gate (the measured cause). 1807func md_exec_build_admit() -> i64 { 1808 let belf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_build_admit.elf" as *u8 1809 let bf: i64 = sys_openat_rd(belf) 1810 if bf < 0 { return 0 } // detector absent -> GRANT (never block on a missing guard) 1811 sys_close(bf) 1812 let args: *i64 = sys_mmap(32) as *i64 1813 args[0] = "check" as *u8 as i64 1814 args[1] = "1024" as *u8 as i64 1815 args[2] = "100000" as *u8 as i64 1816 return dep_run_capture(belf, args, 3, "/tmp/nx_build_admit.out" as *u8) 1817} 1818 1819// ---- GATE-DRY RATCHET RUNNER (2026-07-31, debts 1785529506 / 1785530277) ----------------------- 1820// L009 -- gate organs that hand-roll their verdict instead of inheriting nx_gate_verdict -- is not 1821// merely large, it is GROWING: two warden scans hours apart on 2026-07-31 read 2035/2167 then 1822// 2041/2182. A migration campaign that only removes old breaches LOSES to a tree that adds new ones, 1823// so D001 cannot be closed by migrating alone. The 2026 practice for exactly this shape is a RATCHET 1824// (Notion bans an INCREASE in violation count and requires a deliberate re-bank). 1825// 1826// u26a0AND THE PREDECESSOR THIS WAS SUPPOSED TO COPY DOES NOT EXIST. nx_magicratchet is asserted "wired 1827// into /api/build" in FOUR comments in nx_law_warden.nx, but grep finds ZERO call sites in this source 1828// AND ZERO in the deployed mgmt binary, and a two-build experiment (clean -> BUILT, +3 literals >=1024 1829// -> BUILT, not refused) proves it never fires. So this is written fresh, not modelled on prose. 1830// 1831// FAIL-OPEN, the same stance as build admission and the pre-deploy gate: a missing or unreadable 1832// detector returns -1 and the caller proceeds. A guard that cannot be read must never wedge the build 1833// path for every seat. 1834func md_exec_gatedry(srcpath: *u8) -> i64 { 1835 let gelf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_gatedry.elf" as *u8 1836 let gf: i64 = sys_openat_rd(gelf) 1837 if gf < 0 { return 0 - 1 } 1838 sys_close(gf) 1839 let args: *i64 = sys_mmap(16) as *i64 1840 args[0] = srcpath as i64 1841 return dep_run_capture(gelf, args, 1, "/tmp/nx_gatedry.out" as *u8) 1842} 1843 1844// Does this organ name end in the terminal token `_gate`? TERMINAL, not substring -- the seq1789 1845// lesson banked in nx_organkind_gate T12: `nx_survey_serve_gate` ENDS with _gate, `nx_gate_bite` 1846// merely CONTAINS it, and treating containment as the test misclassifies the second. 1847func md_name_is_gate(nm: *u8) -> i64 { 1848 var n: i64 = 0 1849 while nm[n] != (0 as u8) { n = n + 1 } 1850 if n < 5 { return 0 } 1851 if nm[n-5] != (95 as u8) { return 0 } 1852 if nm[n-4] != (103 as u8) { return 0 } 1853 if nm[n-3] != (97 as u8) { return 0 } 1854 if nm[n-2] != (116 as u8) { return 0 } 1855 if nm[n-1] != (101 as u8) { return 0 } 1856 return 1 1857} 1858 1859// Is this organ ALREADY DEPLOYED? That is the GRANDFATHER TEST and it is what makes this a ratchet 1860// rather than a wall: 2041 existing gates hand-roll their verdicts, and refusing all of them would 1861// stop the ecosystem dead. Only a gate with NO deployed artefact -- i.e. a NEW one -- is held to the 1862// base class. Existing breaches are migrated by their owner lanes, never blocked here. 1863// ---- THE BANKED BASELINE: HOW THE RATCHET TELLS NEW FROM OLD (2026-07-31, debt 1785558585) ---- 1864// MY FIRST ATTEMPT USED "has no deployed .elf" AS THE NEWNESS TEST AND THAT WAS WRONG. The ledger 1865// measures 2877 gate sources against 175 binaries -- ~94pc of gates were NEVER COMPILED -- so 1866// long-existing gates read as NEW and their rebuilds were REFUSED. A ratchet that cannot tell new 1867// from old is a WALL, and a wall at a shared chokepoint stops every seat. Withdrawn within minutes. 1868// 1869// THE CORRECT TEST IS A RECORD, which is what a ratchet actually is. Notion's ESLint ratcheting keeps 1870// a CHECKED-IN file of known violations and requires approval only when the count INCREASES against 1871// it. knowledge/status/gatedry_baseline.out is that record: the enumerated gate corpus at the moment 1872// the ratchet landed (2189 entries). A gate NOT in the record is NEW and is held to the base class; 1873// everything in the record is grandfathered and migrated by its owner lane. 1874// 1875// FAIL-OPEN BY CONSTRUCTION: an absent or unreadable baseline returns 1 (== "known", allow). If the 1876// record cannot be read we CANNOT distinguish new from old, and the only safe answer is to permit -- 1877// otherwise a missing file silently rebuilds the exact wall this replaced. 1878func md_gate_in_baseline(nm: *u8) -> i64 { 1879 let bp: *u8 = "knowledge/status/gatedry_baseline.out" as *u8 1880 let ln: *i64 = sys_mmap(16) as *i64 1881 ln[0] = 0 1882 let buf: *u8 = sys_read_file(bp, ln) 1883 if buf as i64 == 0 { return 1 } 1884 let n: i64 = ln[0] 1885 if n <= 0 { return 1 } 1886 let pat: *u8 = sys_mmap(256) 1887 var o: i64 = 0 1888 pat[o] = (47 as u8) 1889 o = o + 1 1890 var bi: i64 = 0 1891 while nm[bi] != (0 as u8) { pat[o] = nm[bi]; o = o + 1; bi = bi + 1 } 1892 pat[o] = (46 as u8) 1893 o = o + 1 1894 pat[o] = (110 as u8) 1895 o = o + 1 1896 pat[o] = (120 as u8) 1897 o = o + 1 1898 let pn: i64 = o 1899 var k: i64 = 0 1900 while k + pn <= n { 1901 var j: i64 = 0 1902 var hit: i64 = 1 1903 while j < pn { if buf[k+j] != pat[j] { hit = 0; j = pn } else { j = j + 1 } } 1904 if hit == 1 { return 1 } 1905 k = k + 1 1906 } 1907 return 0 1908} 1909 1910func md_organ_deployed(nm: *u8) -> i64 { 1911 let p: *u8 = sys_mmap(256) 1912 let pre: *u8 = "/volume1/homes/elderwesto/nishihost/" 1913 var o: i64 = 0 1914 var i: i64 = 0 1915 while pre[i] != (0 as u8) { p[o] = pre[i]; o = o + 1; i = i + 1 } 1916 i = 0 1917 while nm[i] != (0 as u8) { p[o] = nm[i]; o = o + 1; i = i + 1 } 1918 let suf: *u8 = ".elf" 1919 i = 0 1920 while suf[i] != (0 as u8) { p[o] = suf[i]; o = o + 1; i = i + 1 } 1921 p[o] = 0 as u8 1922 let fd: i64 = sys_openat_rd(p) 1923 if fd < 0 { return 0 } 1924 sys_close(fd) 1925 return 1 1926} 1927 1928// ---- PRE-DEPLOY SAFETY GATE RUNNER (2026-07-30) ------------------------------------------------- 1929// nx_deploy_ready computes deploy_safe/blockers/DEPLOY-BLOCKED and publishes it -- and NOTHING AT THE 1930// DEPLOY CHOKEPOINT EVER CONSULTED IT. It is referenced by ecomat seeding, tooldiff and the cron beat, 1931// but nx_mgmt_api never called it, so the one act the gate exists to guard ran unguarded. MEASURED: 1932// the gate returned verdict DEPLOY-BLOCKED (blockers=1) while two of my own deploys succeeded minutes 1933// apart. A gate that is computed, published and unreachable from the act it guards IS the baseline. 1934// 1935// WHY WE PARSE JSON AND NOT THE EXIT CODE: nx_deploy_ready calls sys_exit(0) UNCONDITIONALLY -- even 1936// when the verdict is DEPLOY-BLOCKED -- so its exit status carries no verdict at all and no caller 1937// checking $? could ever act on it. Fixing that is a published-contract change (other callers may 1938// treat nonzero as failure), so it is filed separately rather than changed underneath them here. 1939// 1940// FAIL-OPEN, same stance as build admission: a missing or unreadable gate returns -1 and the caller 1941// proceeds. A guard that cannot be read must never wedge the deploy path for every seat. 1942func md_exec_deploy_ready() -> i64 { 1943 let delf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_deploy_ready.elf" as *u8 1944 let df: i64 = sys_openat_rd(delf) 1945 if df < 0 { return 0 - 1 } 1946 sys_close(df) 1947 let args: *i64 = sys_mmap(16) as *i64 1948 args[0] = "check" as *u8 as i64 1949 // Return the gate's EXIT CODE (0 safe / 3 DEPLOY-BLOCKED) so the caller can surface it. Until today this 1950 // code was constant 0 and therefore meaningless; reporting it live is what proves the new contract landed. 1951 let drc0: i64 = dep_run_capture(delf, args, 1, "/tmp/nx_ma_deploy_ready.out" as *u8); if drc0 >= 0 { return drc0 } 1952 return 0 1953} 1954 1955// mint a ROOT tools-capability token via the on-NAS nx_cap_mint oracle (CLI: <keyfile> <allow-csv> <exp> <nonce> 1956// -> token on stdout, nonzero exit on refusal). The HMAC keyfile is read BY THE ORACLE on-NAS and never crosses 1957// the API. Absolute paths (same stance as the hostctl/treepack exec ports; mgmt cwd=nishihost but explicit wins). 1958// stdout captured -> outpath; caller treats nonzero exit OR empty capture as mint-failed (fail-closed). 1959func md_exec_capmint(allow: *u8, expstr: *u8, noncestr: *u8, outpath: *u8) -> i64 { 1960 let helf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_cap_mint.elf" as *u8 1961 let args: *i64 = sys_mmap(40) as *i64 1962 args[0] = "/volume1/homes/elderwesto/nishihost/tools_cap_secret.key" as *u8 as i64 1963 args[1] = allow as i64 1964 args[2] = expstr as i64 1965 args[3] = noncestr as i64 1966 return dep_run_capture(helf, args, 4, outpath) 1967} 1968// ---- /api/gate_run + /api/proc_kill support (seq1349/1383). RE-APPLIED after a 4th backdate (seq1445). 1969// gate_run bound: name must end gate/test/kat, resolves ONLY a promoted top-level nishihost/<n>.elf, so a pure 1970// verifier is all this route can ever reach -- never a daemon, promoter or deployer. 1971// ⚠ seq1443: dep_run_capture_bounded DUPLICATES tr_run_capture_to (nx_tool_run.nx, seq1412) which is gate-proven 1972// (nx_tool_run_timeout_gate T5 = no leak after a kill) and uses a WATCHDOG FORK because a poll design is not 1973// buildable without sys_fcntl. ADOPT IT next; kept here only so the live verbs stop vanishing from source. 1974// ---- R1 (seq1506): LEASE-GATE THE BUILD PATH ------------------------------------------------------------- 1975// OPERATOR 2026-07-30: "why cant we clearly state when we are switching out or updating and coordinate like 1976// road construction". This is the flagger. Concurrent builds of the SAME target are how a session ships a 1977// regression from a mid-churn snapshot -- it happened twice today (21->19 routes lost, then again). 1978// ADOPTION, NOT INVENTION (seq1410's law, 4th instance today): nx_lease ALREADY EXISTS, is gate-proven, and 1979// had ZERO callers in the build path. We reuse it as a SUBPROCESS via its exit-code contract rather than 1980// importing it -- verified live: acquire=0 prints LS-ACQUIRED, BUSY=3 prints "LS-BUSY <name> holder=<who>", 1981// release=0. Exit codes are the contract, so no import coupling and no second implementation. 1982// TTL is the reason this can never deadlock the ecosystem: a session that dies mid-build cannot hold the 1983// lane closed -- the lease expires on its own. A lock without a TTL would be a worse defect than the race. 1984// ⚠ lease NAME grammar is [a-zA-Z0-9_-] ONLY: "build:X" is REFUSED, so the name is built as "build-<target>". 1985func md_lease_run(verb: *u8, name: *u8, owner: *u8, ttl: *u8, nargs: i64, outpath: *u8) -> i64 { 1986 let elf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_lease.elf" as *u8 1987 let args: *i64 = sys_mmap(48) as *i64 1988 args[0] = verb as i64 1989 args[1] = name as i64 1990 args[2] = owner as i64 1991 args[3] = ttl as i64 1992 return dep_run_capture(elf, args, nargs, outpath) 1993} 1994// Build the lease name "build-<target>" into buf. Target is already [A-Za-z0-9_]-sanitized by the caller. 1995func md_lease_name(target: *u8, buf: *u8) -> i64 { 1996 var o: i64 = 0 1997 let p: *u8 = "build-" as *u8 1998 while p[o] != (0 as u8) { buf[o] = p[o]; o = o + 1 } 1999 var i: i64 = 0 2000 while target[i] != (0 as u8) { buf[o] = target[i]; o = o + 1; i = i + 1 } 2001 buf[o] = 0 as u8 2002 return o 2003} 2004// Build "<prefix><src>" into buf, COPYING ONLY lease-legal chars [a-zA-Z0-9_-] from src. 2005// ⚠ nx_lease REFUSES any other byte, and the things we most want to lock are named with dots 2006// ("nx_tools_api_serve.elf") -- so a naive concat produces LS-REFUSED bad name and the guard silently 2007// never engages. A guard that cannot be named is a guard that does not exist; filter, do not assume. 2008func md_lease_name_pfx(prefix: *u8, src: *u8, buf: *u8) -> i64 { 2009 var o: i64 = 0 2010 while prefix[o] != (0 as u8) { buf[o] = prefix[o]; o = o + 1 } 2011 var i: i64 = 0 2012 while src[i] != (0 as u8) { 2013 let c: i64 = src[i] as i64 2014 var ok: i64 = 0 2015 if c >= 48 { if c <= 57 { ok = 1 } } 2016 if c >= 65 { if c <= 90 { ok = 1 } } 2017 if c >= 97 { if c <= 122 { ok = 1 } } 2018 if c == 95 { ok = 1 } 2019 if c == 45 { ok = 1 } 2020 if ok == 1 { buf[o] = src[i]; o = o + 1 } 2021 i = i + 1 2022 } 2023 buf[o] = 0 as u8 2024 return o 2025} 2026func md_gate_name_ok(nm: *u8) -> i64 { 2027 var n: i64 = 0 2028 while nm[n] != (0 as u8) { n = n + 1 } 2029 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 } } } } } 2030 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 } } } } } 2031 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 } } } } 2032 return 0 2033} 2034// seq1443 ADOPTION: this now delegates to tr_run_capture_to (runtime/nx_tool_run.nx, seq1412) instead of my 2035// own dep_run_capture_bounded, which was a DUPLICATE of it -- a live instance of the ecosystem's own law that 2036// THE BOTTLENECK IS NOT BUILDING PRIMITIVES, IT IS ADOPTING THEM. 2037// The adopted primitive is STRICTLY better and its header says why mine could not work: bounding the drain 2038// needs O_NONBLOCK on the read end and THERE IS NO sys_fcntl in nx_syscalls, so a poll design is NOT 2039// BUILDABLE. It uses a WATCHDOG FORK -- the watchdog SIGKILLs the worker, the dying worker drops the last 2040// write end, and the parent's blocking read gets its EOF naturally, needing no new syscall. It also closes 2041// wfd BEFORE forking the watchdog (order is load-bearing: fork first and the watchdog inherits the write end, 2042// so the pipe never EOFs -- the exact hang the bound exists to remove, reintroduced by the fix). 2043// It ships nx_tool_run_timeout_gate whose T5 is "second timeout identical (no leak after a kill)" = the 2044// no-leak tooth seq1425 was missing. Capturing to a BUFFER also deletes the /tmp/nx_ma_gaterun.out temp file. 2045// Returns the child's exit code, or TR_ERR_TIMEOUT when the deadline fired. 2046func md_exec_gate_capture(elfpath: *u8, out: *u8, cap: i64, outlen: *i64, deadline_ms: i64) -> i64 { 2047 let argv: *i64 = sys_mmap(32) as *i64 2048 argv[0] = elfpath as i64 2049 argv[1] = 0 2050 return tr_run_capture_to(elfpath, argv, out, cap, outlen, deadline_ms) 2051} 2052func md_exec_gate_capture_OLD(elfpath: *u8, outpath: *u8, deadline_ms: i64) -> i64 { 2053 let args: *i64 = sys_mmap(16) as *i64 2054 return dep_run_capture_bounded(elfpath, args, 0, outpath, deadline_ms) 2055} 2056// proc_kill bound: >=6 chars AND must contain .elf (our own organs only, never a system process) AND must not 2057// reach the supervisor (killing the guard stops every respawn). Killing a guard-supervised daemon = a restart. 2058func md_str_contains(hay: *u8, pat: *u8) -> i64 { 2059 let hn: i64 = md_len(hay) 2060 let pn: i64 = md_len(pat) 2061 if pn == 0 { return 0 } 2062 if pn > hn { return 0 } 2063 var i: i64 = 0 2064 while i + pn <= hn { 2065 var k: i64 = 0 2066 var hit: i64 = 1 2067 while k < pn { if hay[i+k] != pat[k] { hit = 0; k = pn } else { k = k + 1 } } 2068 if hit == 1 { return 1 } 2069 i = i + 1 2070 } 2071 return 0 2072} 2073func md_proc_kill_needle_ok(nm: *u8) -> i64 { 2074 if md_len(nm) < 6 { return 0 } 2075 if md_str_contains(nm, ".elf" as *u8) == 0 { return 0 } 2076 if md_str_contains(nm, "supervise" as *u8) == 1 { return 0 } 2077 if md_str_contains(nm, "nx_hostctl" as *u8) == 1 { return 0 } 2078 return 1 2079} 2080func 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 } 2081// FAIL-CLOSED allowlist for /api/hostctl. Curated to fast (<~15s) read + safe-maintenance subs so a synchronous 2082// API call returns promptly. DEPLOY/selfswap/rollback are DELIBERATELY excluded (they promote/re-exec -> use the 2083// guarded /api/deploy). Long-running (nettap 60s, portmap timeouts) excluded until an async job path exists. 2084func md_hostctl_action_ok(nm: *u8) -> i64 { 2085 if md_cstr_eq(nm, "status" as *u8) == 1 { return 1 } // supervisor snapshot 2086 if md_cstr_eq(nm, "torstat" as *u8) == 1 { return 1 } // per-torrent seedeval + metadata diag 2087 if md_cstr_eq(nm, "routerctl" as *u8) == 1 { return 1 } // GL.iNet dashboard (model/wan/forwards/clients) 2088 if md_cstr_eq(nm, "receipts" as *u8) == 1 { return 1 } // op-receipts ledger (read-only) 2089 if md_cstr_eq(nm, "kicktorrent" as *u8) == 1 { return 1 } // restart the torrent daemon 2090 if md_cstr_eq(nm, "kickseed" as *u8) == 1 { return 1 } // restart the :6881 seeder 2091 if md_cstr_eq(nm, "kickworkers" as *u8) == 1 { return 1 } // restart stale download workers 2092 if md_cstr_eq(nm, "kickseedann" as *u8) == 1 { return 1 } // restart the DHT/LSD announcer 2093 if md_cstr_eq(nm, "trackerrefresh" as *u8) == 1 { return 1 } // refresh the tracker list (detached) 2094 if md_cstr_eq(nm, "galxpipeline" as *u8) == 1 { return 1 } // analysis-on-ingest: thumbnails + NXVI (detached, idempotent) 2095 if md_cstr_eq(nm, "durindexrun" as *u8) == 1 { return 1 } // duration-index batch (detached, idempotent) 2096 if md_cstr_eq(nm, "searchpagerank" as *u8) == 1 { return 1 } // search: PageRank build on the live web shard (detached, idempotent, additive pr:) 2097 if md_cstr_eq(nm, "searchcompact" as *u8) == 1 { return 1 } // search: web-shard compaction (detached, idempotent, verifies-before-swap) 2098 if md_cstr_eq(nm, "durindexstat" as *u8) == 1 { return 1 } // duration-index coverage (read-only) 2099 return 0 2100} 2101 2102// REAL-HTTP health: GET url -> require 200 + non-empty body. 1 healthy / 0 not (the false-green killer). 2103// LOCAL TCP liveness: connect to 127.0.0.1:port -> 1 if something is listening (service up), 0 if refused. 2104// Dependency-free (no external fetcher, no CA store, no edge round-trip) = the robust health signal for a restart. 2105func md_tcp_alive(port: i64) -> i64 { 2106 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0); if fd < 0 { return 0 } 2107 let sa: *u8 = sys_mmap(16) 2108 sa[0]=2 as u8; sa[1]=0 as u8; sa[2]=((port>>8)&0xff) as u8; sa[3]=(port&0xff) as u8 2109 sa[4]=127 as u8; sa[5]=0 as u8; sa[6]=0 as u8; sa[7]=1 as u8 2110 var z: i64=8; while z<16 { sa[z]=0 as u8; z=z+1 } 2111 let r: i64 = nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS); sys_close(fd) 2112 if r == 0 { return 1 } 2113 return 0 2114} 2115func md_health_probe(url: *u8) -> i64 { 2116 // "port:<N>" -> LOCAL TCP-connect health (no nx_research_fetch/CA/edge dependency -- the robust default for 2117 // restart-targets: the earlier /torrent HTTP probe needed an on-NAS fetcher+CA that isn't at the mgmt cwd, 2118 // so it always failed -> conservative rollback). Otherwise the HTTP-fetch probe below. Both retry 10x3s. 2119 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) { 2120 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 } 2121 var at: i64=0 2122 while at < 10 { if md_tcp_alive(pt)==1 { return 1 } at=at+1; if at<10 { sys_sleep_ms(3000) } } 2123 return 0 2124 } } } } } 2125 let pargs: *i64 = sys_mmap(16) as *i64 2126 pargs[0] = url as i64 2127 let pbuf: *u8 = sys_mmap(16384) 2128 // 10 tries x 3s = up to 30s: GENEROUS, because /api/deploy now runs this in a DETACHED watchdog (not on the 2129 // request path) -> it no longer races the edge-proxy read window, so it can wait out a slow guard-respawn 2130 // (~10-15s) and confirm 200+body -> GREEN, instead of a premature conservative rollback. 2131 var attempt: i64 = 0 2132 while attempt < 10 { 2133 dep_run_capture("_offc/nx_research_fetch.elf" as *u8, pargs, 1, "/tmp/nx_ma_deploy_health.out" as *u8) 2134 let pn: i64 = dp_read("/tmp/nx_ma_deploy_health.out" as *u8, pbuf, 16380) 2135 let st: i64 = hh_after(pbuf, pn, "status=" as *u8) 2136 let bbn: i64 = hh_after(pbuf, pn, "body_bytes=" as *u8) 2137 if st == 200 { if bbn > 0 { return 1 } } 2138 attempt = attempt + 1 2139 if attempt < 10 { sys_sleep_ms(3000) } 2140 } 2141 return 0 2142}