code wiki / _hdl_build / nx_mgmt_api.nx

nx_mgmt_api.nx source

↩ module page · 3990 lines · 251235 B

1// nx_mgmt_api.nx -- THE IO / TRANSPORT ring of the sovereign ecosystem control-plane API (the primary adapter). 2// THIN by design (clean three-tier / hexagonal -- see knowledge/library/arch_*): it owns ONLY transport -- 3// the socket loop, HTTP request parse, routing, JSON serialization, and auth gating. ALL business rules live in 4// the LOGIC core (nx_mgmt_core, mc_*); ALL outside-world access lives in the DATA adapters (nx_mgmt_data, md_*). 5// Dependency direction: api -> {core, data}, data -> core-free, core -> nothing (dependency inversion). The 6// router is a PURE FUNCTION ma_handle(ctx, req, req_n, snapfile, out) -> out_n (bytes in, bytes out, NO socket); 7// the gate drives it IN-PROCESS (no curl/shell). Reuses nx_status_daemon's HTTP-parse + Modern-Auth helpers. 8// 9// TRUE MONITORING (no false +/-): /api/health + /api/services read a live SNAPSHOT (md_read_file) and surface 10// dueling-supervisors / duplicate-instances / crash-loops via the core rules; missing snapshot -> UNKNOWN, 11// never OK. Health/services + all write actions are AUTH-gated; write actions are confirm-gated + fail-closed. 12// 13// Routes (auth = X-Nishi-Session Ed25519 session header, canonical Modern Auth, NO cookies): 14// GET /api -> 200 route index (public) 15// POST /api/login -> 200 {"token":...} | 401 16// GET /api/health -> (auth) 200 {overall,degraded,reasons[...]} | 401 17// GET /api/services -> (auth) 200 {services:[...]} | 401 18// POST /api/upload?target=&seq=&final=[&sha256=] -> (auth) chunked artifact publish; append raw body chunk -> 19// <target>.upload, on final rename -> <target>.new (staged for /api/deploy; NEVER promotes) | 400 | 401 20// POST /api/deploy -> (auth) allowlist+validate->promote->http-health->auto-rollback | 400 | 401 21// POST /api/rollback -> (auth, confirm=yes) | 400 | 401 22// POST /api/reconcile -> (auth, confirm=yes) single-supervisor | 400 | 401 23// POST /api/restart -> (auth, confirm=yes, service=) surgical kick | 400 | 401 24// POST /api/migrate|update -> (auth) 501 reserved (R3b: URL->.site ingest) | 401 25// (any other) -> 404 26// Snapshot line format (the monitor writes; md_* reads): "SUP <n>" / "SVC <name> <port> <state> <procs> <rwin> <rtot>" 27// argv: [1]=port [2]=keysfile [3]=storefile [4]=realm [5]=snapfile [6]=budget. license_tier: ORIGINAL 28import "nx_status_daemon.nx" 29import "nx_mgmt_data.nx" 30import "nx_organkind.nx" // seq1492: ROLE beats NAME for promote/deploy eligibility 31import "nx_mgmt_core.nx" 32import "nx_access_lib.nx" // ag_uid_to_level -- the SHARED access-granting path (session uid -> handle -> level) 33import "nx_os_introspect.nx" // pon_pid_cmdline -- name the port-holder PID the snapshot carries (the os-axis rung) 34import "nx_shard_view.nx" // sv_build_shards_json -- the cross-shard fleet view for /api/shards (CAP-SHARD-VIEW) 35import "nx_fio.nx" // fio_unlink -- canonical sovereign unlinkat (shred a bad/aborted staging file), DRY 36import "nx_sha256.nx" // sha256_digest -- optional integrity check of the reassembled artifact on final chunk 37import "nx_routeguard_lib.nx" // rg_extract/rg_missing -- the DEPLOY CONTRACT guard: refuse a candidate that DROPS live /api routes (5th regression, id=1785447778). Fail-open by construction. 38import "nx_mgmt_upload.nx" // mu_stage_chunk -- the SHIPPED ordered-chunk write primitive (seq0=truncate, seq>0=append); compose it, don't re-open inline (DRY / retire the orphan) 39 40const MA_BODYCAP: i64 = 131072 41// UPLOAD PERFORMANCE (scoped full-body read for /api/upload ONLY): the shared nx_http_server_read_request does a 42// SINGLE sys_read, so a chunk body larger than one read (or split across the wire into >1 read) would be TRUNCATED. 43// That capped the client at ~48KB chunks => ~54 handshakes for a 2.6MB artifact (each handshake ~300-400ms off-LAN). 44// This dedicated 2 MiB reassembly buffer lets the /api/upload path carry a MUCH bigger chunk (collapsing ~54 45// handshakes to ~3), WITHOUT touching the shared reader (blast radius = every daemon) and WITHOUT a hang risk: 46// the continue-read is bounded STRICTLY by Content-Length and this cap, and EOF (sys_read<=0) terminates it. A body 47// whose declared Content-Length exceeds this cap is REFUSED (413) -- never a partial-append-then-success. Rule 11: 48// the cap is a named const, not a magic number; Rule 26: still staging-only (ma_do_upload never touches the live artifact). 49const MA_UPLOAD_REQCAP: i64 = 2097152 // 2 MiB -- the scoped upload-request buffer (headers + one big chunk body) 50const MA_UPLOAD_TOOBIG: i64 = 0 - 1 // sentinel: declared Content-Length would overflow MA_UPLOAD_REQCAP -> 413 refuse 51const MA_SHARDS_CONF: *u8 = "/volume1/homes/elderwesto/nishihost/shards.conf" as *u8 // the shard-registry SSOT the control plane reads 52// ACCESS-GRANTING (lined up with the ecosystem, NOT a private mgmt island): the roles registry + uid->handle index 53// are the SAME files the hub gateway reads, so ONE login + ONE roles row (elderwesto=3) grants access everywhere. 54const MA_ROLES: *u8 = "/volume1/ai/hub/roles.tsv" as *u8 55const MA_IDX: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_uid_handle.tsv" as *u8 56const MA_LVL_READ: i64 = 2 // member+ may READ /api/health + /api/services 57const MA_LVL_ACT: i64 = 3 // OPERATOR required for the write actions (deploy/rollback/reconcile/restart) 58// /api/cap/mint tunables (rule-11: every threshold named -- charset/buffer literals stay inline to match the 59// ma_sanitize_* idiom, but the cap lifetime bounds + allow-length cap are real knobs). 60const CM_DAYS_DEFAULT: i64 = 30 // default cap lifetime when `days` is omitted 61const CM_DAYS_MAX: i64 = 730 // 2 years -- upper bound so a mint can't grant a near-eternal cap 62const CM_SECS_PER_DAY: i64 = 86400 // exp = now + days * this 63const CM_ALLOW_MAX: i64 = 480 // max bytes of the allow= csv (bounds the mint scratch buffers) 64const CM_NAME_MAX: i64 = 120 // max bytes of one tool name inside the csv 65 66// ---- response assembly (transport serialization) ---------------------------------------------------- 67 68func ma_emit_json(out: *u8, prefix: *u8, body: *u8, body_n: i64) -> i64 { 69 var o: i64 = sd_cat(out, 0, prefix) 70 o = sd_catn(out, o, body_n) 71 // api-versioning: every response carries the API-Version header (explicit version negotiation, not just a body 72 // field); Vary: Accept declares content-negotiation. Single point so ALL endpoints are covered uniformly. 73 o = sd_cat(out, o, "\r\nAPI-Version: 2\r\nVary: Accept\r\n\r\n" as *u8) 74 var i: i64 = 0 75 while i < body_n { out[o] = body[i]; o = o + 1; i = i + 1 } 76 return o 77} 78 79// content-negotiation: does the request's Accept header ask for text/plain? (index is header-only, so a match can 80// only come from Accept). Server then serves the text representation instead of JSON -> real Accept-driven negotiation. 81func ma_wants_text(req: *u8, req_n: i64) -> i64 { 82 let t: *u8 = "text/plain" as *u8 83 var i: i64 = 0 84 while i + 10 <= req_n { 85 if req[i] == (116 as u8) { 86 var k: i64 = 0 87 while k < 10 { if req[i + k] != t[k] { k = 100 } else { k = k + 1 } } 88 if k == 10 { return 1 } 89 } 90 i = i + 1 91 } 92 return 0 93} 94func ma_index_text(out: *u8) -> i64 { 95 let body: *u8 = "nishi-mgmt API v2\nspec: /api/openapi.json\nroutes: /api/login /api/health /api/services /api/upload /api/deploy /api/deploy_status /api/unpack /api/build /api/promote /api/promote_toolchain /api/tools/register /api/cap/mint /api/hostctl /api/promote_content /api/rollback /api/reconcile /api/restart /api/migrate /api/update /api/compare/registry /api/compare/upsert /api/compare/regen /api/compare/publish\n" as *u8 96 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 97} 98// NISHI-NATIVE API CONSOLE (NOT swagger/openapi-ui): a fully-sovereign, self-contained console (no CDN, no third-party 99// UI lib) rendered from OUR OWN /api index, surfacing every ecosystem rung -- mgmt API + MCP agent surface (tools/ 100// resources/prompts) + search + the live SOTA critic + SSE + binary RPC. OpenAPI kept only as an interop LINK, not the 101// UI. (nx_cc trap: no '#' or '!' in the literal -> color names, no doctype.) 102func ma_docs(out: *u8) -> i64 { 103 let body: *u8 = "<html><head><meta charset=utf-8><title>Nishi Sovereign API Console</title><style>body{font-family:system-ui;max-width:64em;margin:0 auto;padding:1.5em;background:snow;color:midnightblue}h1{color:seagreen;margin-bottom:.1em}h2{color:seagreen;border-bottom:2px solid mediumseagreen;padding-bottom:.2em;margin-top:1.4em}.sub{color:slategray;margin-top:0}.card{background:white;border:1px solid gainsboro;border-left:4px solid seagreen;border-radius:6px;padding:.6em 1em;margin:.5em 0}code{background:honeydew;color:darkgreen;padding:2px 6px;border-radius:3px}a{color:seagreen}.tag{font-size:.75em;background:mediumseagreen;color:white;padding:1px 7px;border-radius:10px;margin-left:.4em}</style></head><body><h1>Nishi Sovereign API Console</h1><p class=sub>Own TLS 1.3, own auth (OPAQUE + object-capability), never-brick deploys. Zero third-party framework or UI library.</p><h2>Management control plane</h2><div id=routes>loading...</div><h2>Agent surface (MCP)</h2><div class=card><code>POST /mcp</code> <span class=tag>JSON-RPC 2.0</span><br>tools + resources + prompts. Auth: X-Nishi-Cap / Authorization: Bearer / params._cap.</div><div class=card><code>nishi_search</code> / <code>nishi_doc</code><br>sovereign corpus search + document fetch.</div><div class=card><code>nx_ecosystem_maturity_rollup</code> <span class=tag>live</span><br>measured, liar-killed per-domain SOTA grade.</div><div class=card><code>nx_mgmt</code> <span class=tag>admin</span><br>build / deploy / reconcile / restart over the never-brick control plane.</div><h2>Streaming + RPC</h2><div class=card><code>GET /api/events</code> <span class=tag>SSE</span><br>text/event-stream status events (reconnecting).</div><div class=card><code>POST /api/rpc</code> <span class=tag>binary</span><br>sovereign contract-first binary RPC (contract: <a href=/api/rpc.contract>/api/rpc.contract</a>).</div><h2>Machine-readable</h2><div class=card><a href=/api/openapi.json>/api/openapi.json</a> OpenAPI 3.1 for interop &middot; <a href=/api/inventory>/api/inventory</a> versioned artifacts</div><script>fetch('/api').then(function(r){return r.json()}).then(function(s){var h='<p class=sub>version '+s.version+'</p>';var rs=s.routes;for(var i=0;i<rs.length;i++){h+='<div class=card><code>'+rs[i]+'</code></div>'}document.getElementById('routes').innerHTML=h})</script></body></html>" as *u8 104 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 105} 106// webhooks/SSE: GET /api/events -> text/event-stream. Emits status events then closes; EventSource clients auto- 107// reconnect per the retry hint (fits our single-accept server -- no long-lived hold). Real event-driven delivery. 108func ma_events(out: *u8) -> i64 { 109 var o: i64 = sd_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nCache-Control: no-cache\r\nConnection: close\r\nAPI-Version: 2\r\n\r\n" as *u8) 110 o = sd_cat(out, o, "retry: 5000\nevent: hello\ndata: {\"api\":\"nishi-mgmt\",\"stream\":\"status\"}\n\n" as *u8) 111 o = sd_cat(out, o, "event: tick\ndata: {\"ts\":" as *u8) 112 o = sd_catn(out, o, sys_now_realtime_sec()) 113 o = sd_cat(out, o, ",\"health\":\"/api/health\",\"deploy_status\":\"/api/deploy_status\",\"inventory\":\"/api/inventory\"}\n\n" as *u8) 114 return o 115} 116// binary emitter (payload may contain NUL -> explicit length, not sd_len). 117func ma_emit_binary(out: *u8, body: *u8, body_n: i64) -> i64 { 118 var o: i64 = sd_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nConnection: close\r\nAPI-Version: 2\r\nContent-Length: " as *u8) 119 o = sd_catn(out, o, body_n) 120 o = sd_cat(out, o, "\r\n\r\n" as *u8) 121 var i: i64 = 0 122 while i < body_n { out[o] = body[i]; o = o + 1; i = i + 1 } 123 return o 124} 125// grpc-contract-first: SOVEREIGN binary contract-first RPC. POST /api/rpc body = [u32be method_id][args]; response = 126// [u32be status][payload]. Own framing (not gRPC/HTTP2/protobuf -- deliberate, same sovereignty stance as our TLS). 127func ma_rpc(req: *u8, req_n: i64, out: *u8) -> i64 { 128 let boff: i64 = sd_body_off(req, req_n) 129 let body: *u8 = ((req as i64) + boff) as *u8 130 let body_n: i64 = req_n - boff 131 var method: i64 = 0 132 if body_n >= 4 { method = ((body[0] as i64) << 24) | ((body[1] as i64) << 16) | ((body[2] as i64) << 8) | (body[3] as i64) } 133 let resp: *u8 = sys_mmap(4096) 134 resp[0] = 0 as u8; resp[1] = 0 as u8; resp[2] = 0 as u8; resp[3] = 0 as u8 135 var rn: i64 = 4 136 if method == 1 { 137 let p: *u8 = "PONG" as *u8; var k: i64 = 0; while k < 4 { resp[rn] = p[k]; rn = rn + 1; k = k + 1 } 138 } else { if method == 2 { 139 let p: *u8 = "nishi-mgmt v2" as *u8; var k: i64 = 0; while p[k] != (0 as u8) { resp[rn] = p[k]; rn = rn + 1; k = k + 1 } 140 } else { 141 resp[3] = 1 as u8 142 let p: *u8 = "unknown-method" as *u8; var k: i64 = 0; while p[k] != (0 as u8) { resp[rn] = p[k]; rn = rn + 1; k = k + 1 } 143 } } 144 return ma_emit_binary(out, resp, rn) 145} 146func ma_rpc_contract(out: *u8) -> i64 { 147 let body: *u8 = "{\"rpc\":\"nishi-sovereign-binary-rpc\",\"transport\":\"POST /api/rpc over TLS 1.3\",\"framing\":{\"request\":\"[u32be method_id][args]\",\"response\":\"[u32be status][payload]\"},\"note\":\"sovereign binary contract-first RPC -- own framing, not gRPC/HTTP2/protobuf (same sovereignty stance as our own TLS stack)\",\"methods\":[{\"id\":1,\"name\":\"ping\",\"returns\":\"PONG\"},{\"id\":2,\"name\":\"version\",\"returns\":\"version string\"}]}" as *u8 148 return ma_emit_200(out, body) 149} 150// inventory-mgmt-api9: a versioned inventory of the managed deployable artifacts. Each is never-brick-managed (a 151// staged .new + a rollback .prev), built via /api/build and promoted via /api/deploy; GET /api/deploy_status gives 152// the live version verdict. Public discovery (artifact NAMES only, no secrets). 153// ★★★★★★ DERIVED, NOT DECLARED (R0, 2026-07-31). This list was HAND-AUTHORED and had drifted: it named 154// nx_hub_gw.elf and nx_torrent_gw.elf as "managed deployable artifacts" while deploy_targets.conf -- 155// the allowlist /api/deploy ACTUALLY READS -- contained NEITHER. A caller who trusted this route was 156// told a fix could be landed that could not. Declared coverage overstated real coverage, and only the 157// smaller number was ever true. 158// A DERIVED ARTIFACT MUST NOT BE AUTHORABLE: the moment a summary can be written by hand it can disagree 159// with the thing it summarises. This now reads the SAME FILE the deploy path reads, so the two cannot 160// diverge -- if a row is absent the route says so, which is the honest answer. 161// AN INVENTORY THAT OVERSTATES COVERAGE IS WORSE THAN NO INVENTORY: it converts a known gap into a 162// false assurance. 163func ma_inventory(out: *u8) -> i64 { 164 let b: *u8 = sys_mmap(16384) 165 var o: i64 = 0 166 o = sd_cat(b, o, "{\"inventory\":\"managed deployable artifacts -- DERIVED from deploy_targets.conf, the same allowlist /api/deploy reads (never hand-authored: a declared list drifts from the real one and only the smaller is true)\",\"source\":\"deploy_targets.conf\",\"targets\":[" as *u8) 167 168 let lp: *i64 = sys_mmap(16) as *i64 169 lp[0] = 0 170 let f: *u8 = sys_read_file("deploy_targets.conf" as *u8, lp) 171 let n: i64 = lp[0] 172 var count: i64 = 0 173 if n > 0 { 174 var line: i64 = 0 175 while line < n { 176 var eol: i64 = line 177 var es: i64 = 0 178 while es == 0 { if eol >= n { es = 1 } else { if f[eol] == (10 as u8) { es = 1 } else { eol = eol + 1 } } } 179 var skip: i64 = 0 180 if line >= eol { skip = 1 } 181 if skip == 0 { if f[line] == (35 as u8) { skip = 1 } } 182 if skip == 0 { 183 var p: i64 = line 184 var fe: i64 = 0 185 while fe == 0 { if p < eol { if f[p] == (32 as u8) { fe = 1 } else { p = p + 1 } } else { fe = 1 } } 186 if p > line { 187 if count > 0 { o = sd_cat(b, o, "," as *u8) } 188 o = sd_cat(b, o, "\"" as *u8) 189 var z: i64 = line 190 while z < p { b[o] = f[z]; o = o + 1; z = z + 1 } 191 o = sd_cat(b, o, "\"" as *u8) 192 count = count + 1 193 } 194 } 195 line = eol + 1 196 } 197 } 198 o = sd_cat(b, o, "],\"count\":" as *u8) 199 // Inline decimal append -- there is no sd_catn in this file, and REFERENCING A HELPER THAT DOES NOT 200 // EXIST is how a build breaks for a reason unrelated to the change being made. 201 if count <= 0 { b[o] = 48 as u8; o = o + 1 } else { 202 let tmp: *u8 = sys_mmap(32) 203 var m: i64 = count 204 var k: i64 = 0 205 while m > 0 { tmp[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 206 var j: i64 = k - 1 207 while j >= 0 { b[o] = tmp[j]; o = o + 1; j = j - 1 } 208 } 209 // NON-VACUITY: if the conf could not be read the list is EMPTY, and an empty inventory must be 210 // legible as "I could not read the source", never as "nothing is deployable". 211 if count <= 0 { 212 o = sd_cat(b, o, ",\"warning\":\"ZERO targets parsed -- deploy_targets.conf unreadable or empty. This is a READ FAILURE, not an empty fleet.\"" as *u8) 213 } 214 o = sd_cat(b, o, ",\"lifecycle\":{\"build\":\"POST /api/build\",\"stage\":\"POST /api/upload\",\"promote\":\"POST /api/deploy\",\"version_verdict\":\"GET /api/deploy_status\",\"rollback\":\"POST /api/rollback\"}}" as *u8) 215 b[o] = 0 as u8 216 return ma_emit_200(out, b) 217} 218func ma_index(out: *u8) -> i64 { 219 let body: *u8 = "{\"api\":\"nishi-mgmt\",\"version\":2,\"spec\":\"/api/openapi.json\",\"routes\":[\"/api/login\",\"/api/health\",\"/api/services\",\"/api/upload\",\"/api/deploy\",\"/api/deploy_status\",\"/api/unpack\",\"/api/build\",\"/api/gate_run\",\"/api/proc_kill\",\"/api/promote\",\"/api/tools/register\",\"/api/cap/mint\",\"/api/hostctl\",\"/api/promote_content\",\"/api/rollback\",\"/api/reconcile\",\"/api/restart\",\"/api/openapi.json\",\"/api/migrate\",\"/api/update\",\"/api/compare/registry\",\"/api/compare/upsert\",\"/api/compare/regen\",\"/api/compare/publish\"],\"_links\":{\"self\":{\"href\":\"/api\"},\"spec\":{\"href\":\"/api/openapi.json\"},\"health\":{\"href\":\"/api/health\"},\"services\":{\"href\":\"/api/services\"},\"deploy_status\":{\"href\":\"/api/deploy_status\"}}}" as *u8 220 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 221} 222 223// ---- /api/openapi.json (SOTA machine-readable API description == Swagger; enables API-only agent use) -------- 224// GENERATED from the route table (data->JSON), not hand-authored. Public (specs are discovery). Declares the 225// OPAQUE session security scheme + the RFC9457 Problem schema so errors are machine-typed. 226func op_path(out: *u8, o: i64, path: *u8, method: *u8, summary: *u8, authed: i64) -> i64 { 227 o = sd_cat(out, o, "\"" as *u8); o = sd_cat(out, o, path); o = sd_cat(out, o, "\":{\"" as *u8); o = sd_cat(out, o, method) 228 o = sd_cat(out, o, "\":{\"summary\":\"" as *u8); o = sd_cat(out, o, summary); o = sd_cat(out, o, "\",\"security\":" as *u8) 229 if authed == 1 { o = sd_cat(out, o, "[{\"nishiSession\":[]}]" as *u8) } else { o = sd_cat(out, o, "[]" as *u8) } 230 // json-schema-params: POST endpoints declare a typed requestBody; every op declares typed 200 + Problem-typed errors. 231 if (method[0] as i64) == 112 { // 'p' -> post 232 o = sd_cat(out, o, ",\"requestBody\":{\"content\":{\"application/x-www-form-urlencoded\":{\"schema\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}}}}}" as *u8) 233 } 234 o = sd_cat(out, o, ",\"responses\":{\"200\":{\"description\":\"ok\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\"}}}},\"400\":{\"description\":\"bad request\",\"content\":{\"application/problem+json\":{\"schema\":{\"$ref\":\"#/components/schemas/Problem\"}}}},\"401\":{\"description\":\"unauthorized\"},\"403\":{\"description\":\"insufficient level\"}}}}" as *u8) 235 return o 236} 237func ma_openapi(out: *u8) -> i64 { 238 let b: *u8 = sys_mmap(16384); var o: i64 = 0 239 o = sd_cat(b, o, "{\"openapi\":\"3.1.0\",\"info\":{\"title\":\"Nishi Sovereign Management API\",\"version\":\"2\",\"description\":\"Sovereign control plane: deploy any binary + run any control action off-LAN. OPAQUE-PAKE auth, never-brick staged deploys (health+auto-rollback), own TLS 1.3.\"},\"servers\":[{\"url\":\"https://nishifamily.com\"}]," as *u8) 240 o = sd_cat(b, o, "\"components\":{\"securitySchemes\":{\"nishiSession\":{\"type\":\"apiKey\",\"in\":\"header\",\"name\":\"X-Nishi-Session\",\"description\":\"OPAQUE-PAKE session token from POST /api/login\"}},\"schemas\":{\"Problem\":{\"type\":\"object\",\"description\":\"RFC9457 problem detail\",\"properties\":{\"type\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"},\"status\":{\"type\":\"integer\"},\"detail\":{\"type\":\"string\"},\"instance\":{\"type\":\"string\"}}}}}," as *u8) 241 o = sd_cat(b, o, "\"paths\":{" as *u8) 242 o = op_path(b, o, "/api/login" as *u8, "post" as *u8, "OPAQUE-PAKE login -> session token" as *u8, 0) 243 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/health" as *u8, "get" as *u8, "control-plane health snapshot" as *u8, 1) 244 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/services" as *u8, "get" as *u8, "per-service status" as *u8, 1) 245 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/upload" as *u8, "post" as *u8, "stage a binary (.new; sha256; never promotes)" as *u8, 1) 246 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/deploy" as *u8, "post" as *u8, "promote a registry target (validate->promote->async health+auto-rollback watchdog)" as *u8, 1) 247 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/deploy_status" as *u8, "get" as *u8, "last deploy watchdog verdict (RUNNING|DEPLOYED-GREEN|ROLLED-BACK)" as *u8, 1) 248 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/unpack" as *u8, "post" as *u8, "unpack a staged source-tree .pack blob into an allowlisted NAS dir (build-over-API tree-sync). REQUIRES sha256=<hex of the pack YOU uploaded>: the staging slot is shared across sessions, so the unpack is pinned to your exact bytes and REFUSES on mismatch rather than applying a tree you never reviewed (seq1807)" as *u8, 1) 249 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/build" as *u8, "post" as *u8, "compile a target on the NAS (build-over-API) -> stage <target>.sov.elf.new for /api/deploy; on failure returns BUILD-FAILED with a diag tail of the nx_cc/nxasm output plus diag_errors, a window anchored at the first nx_parse: line (multi-error recovery leaves error lines mid-log) (fail-loud)" as *u8, 1) 250 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/promote" as *u8, "post" as *u8, "promote a built one-shot organ ELF (<target>.sov.elf.new -> <target>.elf); STRUCTURAL policy: daemon/oracle names always refused -> /api/deploy, any owner-staged one-shot promotable; confirm=yes; never-brick .prev backup" as *u8, 1) 251 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/tools/register" as *u8, "post" as *u8, "expose an organ as an MCP tool (append GREEN tool_allowlist.conf row + optional tool_schemas.conf row); {name,elf,confirm=yes,[args],[title],[update=yes]}; fail-closed + idempotent; update=yes atomically REPLACES an existing row (repoint elf/args; can never create); callable via the tools daemon's hot-read, discoverable after nx_toolreg_reconcile" as *u8, 1) 252 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/cap/mint" as *u8, "post" as *u8, "mint a root tools-capability token via the on-NAS oracle; {allow=<csv of registered tools>,confirm=yes,[days],[nonce]}; least-authority (* refused, every name must be registered), audited in cap_consent.log" as *u8, 1) 253 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/hostctl" as *u8, "post" as *u8, "run one allowlisted control action (status/torstat/routerctl/kick*/trackerrefresh)" as *u8, 1) 254 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/restart" as *u8, "post" as *u8, "restart a supervised service" as *u8, 1) 255 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/rollback" as *u8, "post" as *u8, "roll back a deploy" as *u8, 1) 256 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/promote_content" as *u8, "post" as *u8, "promote a staged static file" as *u8, 1) 257 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/promote_toolchain" as *u8, "post" as *u8, "promote a staged BUILD TOOLCHAIN binary into buildroot/_offc -- ELF-validated, .prev-banked, canary-compiled+run, auto-rollback on failure" as *u8, 1) 258 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/compare/registry" as *u8, "get" as *u8, "the Nishi Compare registry SSOT (text/plain, save-as-is)" as *u8, 1) 259 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/compare/upsert" as *u8, "post" as *u8, "merge ONE registry line by /compare/<domain> key (commutative across domains; replaced line kept in .hist) then regen the hub" as *u8, 1) 260 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/compare/regen" as *u8, "post" as *u8, "regenerate /compare hub index.html + api.json from the SSOT (idempotent)" as *u8, 1) 261 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/compare/publish" as *u8, "post" as *u8, "promote the staged compare.page bytes as /compare/<domain> page|frontier|bench|api (sha256-pinned, confirm=yes)" as *u8, 1) 262 o = sd_cat(b, o, "," as *u8); o = op_path(b, o, "/api/openapi.json" as *u8, "get" as *u8, "this OpenAPI 3.1 spec (machine-readable)" as *u8, 0) 263 o = sd_cat(b, o, "}}" as *u8) 264 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8, b, o) 265} 266 267// RFC 9457 Problem Details: all error responses are application/problem+json with type/title/status members. 268func ma_emit_404(out: *u8) -> i64 { 269 let body: *u8 = "{\"type\":\"about:blank\",\"title\":\"Not Found\",\"status\":404,\"detail\":\"no such route\"}" as *u8 270 return ma_emit_json(out, "HTTP/1.1 404 Not Found\r\nContent-Type: application/problem+json\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 271} 272 273func ma_emit_403(out: *u8) -> i64 { 274 let body: *u8 = "{\"type\":\"about:blank\",\"title\":\"Forbidden\",\"status\":403,\"detail\":\"your access level is insufficient for this action\"}" as *u8 275 return ma_emit_json(out, "HTTP/1.1 403 Forbidden\r\nContent-Type: application/problem+json\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 276} 277 278func ma_emit_501(out: *u8) -> i64 { 279 let body: *u8 = "{\"type\":\"about:blank\",\"title\":\"Not Implemented\",\"status\":501,\"detail\":\"route exists; action wired in a later rung\"}" as *u8 280 return ma_emit_json(out, "HTTP/1.1 501 Not Implemented\r\nContent-Type: application/problem+json\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 281} 282 283// merge the caller's {"error":"..."} body into an RFC9457 problem+json object (type/title/status standard 284// members + the caller's "error" as an extension member) -> every /api 400 is machine-typed, zero caller churn. 285func ma_emit_400(out: *u8, body: *u8) -> i64 { 286 let b2: *u8 = sys_mmap(4096); var o: i64 = 0 287 o = sd_cat(b2, o, "{\"type\":\"about:blank\",\"title\":\"Bad Request\",\"status\":400," as *u8) 288 var i: i64 = 0; if body[0] == (123 as u8) { i = 1 } // skip the caller body's leading '{' 289 while body[i] != (0 as u8) { b2[o] = body[i]; o = o + 1; i = i + 1 } 290 b2[o] = 0 as u8 291 return ma_emit_json(out, "HTTP/1.1 400 Bad Request\r\nContent-Type: application/problem+json\r\nConnection: close\r\nContent-Length: " as *u8, b2, o) 292} 293 294func ma_emit_200(out: *u8, body: *u8) -> i64 { 295 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 296} 297 298// 503 + Retry-After for BUILD ADMISSION (seq708/768/1390). RFC9457 problem+json like the other emitters, 299// so callers stay machine-typed. Retry-After:30 makes the refusal ACTIONABLE -- a client that gets this 300// knows the host is memory-wedged and when to come back, instead of the current failure mode where the 301// build is accepted, the host OOMs, the supervisor reaps nx_mgmt_api, and the caller sees the deploy 302// path vanish with no explanation at all. A stated refusal beats a silent reap. 303func ma_emit_503(out: *u8, body: *u8) -> i64 { 304 let b2: *u8 = sys_mmap(4096); var o: i64 = 0 305 o = sd_cat(b2, o, "{\"type\":\"about:blank\",\"title\":\"Service Unavailable\",\"status\":503," as *u8) 306 var i: i64 = 0; if body[0] == (123 as u8) { i = 1 } 307 while body[i] != (0 as u8) { b2[o] = body[i]; o = o + 1; i = i + 1 } 308 b2[o] = 0 as u8 309 return ma_emit_json(out, "HTTP/1.1 503 Service Unavailable\r\nContent-Type: application/problem+json\r\nRetry-After: 30\r\nConnection: close\r\nContent-Length: " as *u8, b2, o) 310} 311 312// 413 for an upload chunk whose declared Content-Length would overflow the scoped MA_UPLOAD_REQCAP buffer. 313// Fail-closed: emitted BEFORE any body is read/appended, so nothing is staged (the client must use a smaller chunk). 314func ma_emit_413(out: *u8) -> i64 { 315 let body: *u8 = "{\"error\":\"chunk too large; exceeds MA_UPLOAD_REQCAP (use a smaller chunk)\"}" as *u8 316 return ma_emit_json(out, "HTTP/1.1 413 Payload Too Large\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 317} 318 319func ma_comma(body: *u8, b: i64, first: *i64) -> i64 { 320 if first[0] == 1 { first[0] = 0; return b } 321 return sd_cat(body, b, "," as *u8) 322} 323 324// ---- /api/services (transport: read via DATA, classify via CORE, serialize here) -------------------- 325 326func ma_emit_services(snap: *u8, snap_n: i64, out: *u8) -> i64 { 327 let offs: *i64 = sys_mmap(64) as *i64 328 let lens: *i64 = sys_mmap(64) as *i64 329 let body: *u8 = sys_mmap(MA_BODYCAP) 330 var b: i64 = sd_cat(body, 0, "{\"services\":[" as *u8) 331 var first: i64 = 1 332 var cur: i64 = 0 333 while cur < snap_n { 334 let le: i64 = md_eol(snap, snap_n, cur) 335 let nf: i64 = md_split(snap, cur, le, offs, lens, 8) 336 if nf >= 6 { 337 if md_tok_eq(snap, offs[0], lens[0], "SVC" as *u8) == 1 { 338 if first == 0 { b = sd_cat(body, b, "," as *u8) } 339 first = 0 340 let procs: i64 = md_slice_atoi(snap, offs[4], lens[4]) 341 let rwin: i64 = md_slice_atoi(snap, offs[5], lens[5]) 342 let dup: i64 = mc_is_dup(procs) 343 let lp: i64 = mc_is_loop(rwin) 344 b = sd_cat(body, b, "{\"name\":\"" as *u8) 345 b = md_cat_slice(body, b, snap, offs[1], lens[1]) 346 b = sd_cat(body, b, "\",\"port\":" as *u8) 347 b = sd_catn(body, b, md_slice_atoi(snap, offs[2], lens[2])) 348 b = sd_cat(body, b, ",\"state\":\"" as *u8) 349 b = md_cat_slice(body, b, snap, offs[3], lens[3]) 350 b = sd_cat(body, b, "\",\"procs\":" as *u8) 351 b = sd_catn(body, b, procs) 352 b = sd_cat(body, b, ",\"dup\":" as *u8) 353 b = sd_catn(body, b, dup) 354 b = sd_cat(body, b, ",\"loop\":" as *u8) 355 b = sd_catn(body, b, lp) 356 if nf >= 8 { 357 let holder: i64 = md_slice_atoi(snap, offs[7], lens[7]) // the 8th snapshot token = who REALLY holds the port 358 b = sd_cat(body, b, ",\"holder_pid\":" as *u8); b = sd_catn(body, b, holder) 359 if holder > 0 { 360 var mism: i64 = 0; if md_tok_eq(snap, offs[3], lens[3], "DOWN" as *u8) == 1 { mism = 1 } // DOWN + a foreign holder = a squatter 361 b = sd_cat(body, b, ",\"port_mismatch\":" as *u8); b = sd_catn(body, b, mism) 362 b = sd_cat(body, b, ",\"holder_cmd\":\"" as *u8) 363 let hc: *u8 = sys_mmap(512); let hcn: i64 = pon_pid_cmdline(holder, hc, 512) 364 var z: i64 = 0 365 while z < hcn { let c: i64 = hc[z] & 0xff; if c == 34 { body[b] = 39 as u8; b = b + 1 } else { if c == 92 { body[b] = 47 as u8; b = b + 1 } else { body[b] = c as u8; b = b + 1 } } z = z + 1 } // JSON-safe: " -> ' , \ -> / 366 b = sd_cat(body, b, "\"" as *u8) 367 } 368 } 369 b = sd_cat(body, b, "}" as *u8) 370 } 371 } 372 cur = le + 1 373 } 374 b = sd_cat(body, b, "]}" as *u8) 375 return ma_emit_200(out, body) 376} 377 378func ma_emit_shards(out: *u8) -> i64 { 379 let body: *u8 = sys_mmap(MA_BODYCAP) 380 sv_build_shards_json(MA_SHARDS_CONF, body, MA_BODYCAP) 381 return ma_emit_200(out, body) 382} 383 384func ma_emit_services_file(snapfile: *u8, out: *u8) -> i64 { 385 let sbox: *i64 = sys_mmap(16) as *i64 386 let page: *u8 = md_read_file(snapfile, sbox) 387 if (page as i64) == 0 { 388 let empty: *u8 = sys_mmap(1) 389 return ma_emit_services(empty, 0, out) 390 } 391 return ma_emit_services(page, sbox[0], out) 392} 393 394// ---- /api/nodes (swarm MONITOR pillar: read THIS node's LIVE /proc directly -- the mgmt daemon runs on the NAS) -- 395// BOUNDED /proc reads (openat+read+close); NEVER sys_read_file (mmaps 4 GiB/call unfreed = the supervisor ENOMEM 396// F-class root, nx_hostctl.nx:311). Instantaneous only -- no CPU%-sample sleep -> never stalls the serve loop; 397// loadavg-per-core IS the scheduler-grade pressure signal (runqueue depth). SPOT verdict folded in. 398func mnp_readproc(path: *u8, buf: *u8, cap: i64) -> i64 { 399 let fd: i64 = sys_openat_rd(path) 400 if fd < 0 { return 0 - 1 } 401 let r: i64 = sys_read(fd, buf, cap - 1) 402 sys_close(fd) 403 if r > 0 { buf[r] = 0 as u8 } else { buf[0] = 0 as u8 } 404 return r 405} 406func mnp_skip_sp(buf: *u8, n: i64, p: i64) -> i64 { 407 var i: i64 = p 408 var go: i64 = 1 409 while go == 1 { 410 if i >= n { go = 0 } else { if (buf[i] as i64) == 32 { i = i + 1 } else { go = 0 } } 411 } 412 return i 413} 414func mnp_pdec(buf: *u8, n: i64, p: i64, pend: *i64) -> i64 { 415 var i: i64 = p 416 var v: i64 = 0 417 var go: i64 = 1 418 while go == 1 { 419 if i >= n { go = 0 } else { 420 let c: i64 = buf[i] as i64 421 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); i = i + 1 } else { go = 0 } } else { go = 0 } 422 } 423 } 424 pend[0] = i 425 return v 426} 427func mnp_count_sub(buf: *u8, n: i64, needle: *u8, nl: i64) -> i64 { 428 var c: i64 = 0 429 var i: i64 = 0 430 while i <= n - nl { 431 var j: i64 = 0 432 var ok: i64 = 1 433 while j < nl { if buf[i + j] != needle[j] { ok = 0; j = nl } else { j = j + 1 } } 434 if ok == 1 { c = c + 1; i = i + nl } else { i = i + 1 } 435 } 436 return c 437} 438func mnp_find_after(buf: *u8, n: i64, key: *u8, klen: i64) -> i64 { 439 var i: i64 = 0 440 let last: i64 = n - klen 441 while i <= last { 442 var j: i64 = 0 443 var ok: i64 = 1 444 while j < klen { if buf[i + j] != key[j] { ok = 0; j = klen } else { j = j + 1 } } 445 if ok == 1 { return i + klen } 446 i = i + 1 447 } 448 return 0 - 1 449} 450func mnp_loadavg_milli(buf: *u8) -> i64 { 451 let r: i64 = mnp_readproc("/proc/loadavg" as *u8, buf, 256) 452 if r <= 0 { return 0 } 453 let pend: *i64 = sys_mmap(16) as *i64 454 let ip: i64 = mnp_pdec(buf, r, 0, pend) 455 var frac: i64 = 0 456 var p: i64 = pend[0] 457 if p < r { 458 if (buf[p] as i64) == 46 { 459 p = p + 1 460 var dd: i64 = 0 461 var go: i64 = 1 462 while go == 1 { 463 if dd >= 2 { go = 0 } else { 464 if p >= r { go = 0 } else { 465 let c: i64 = buf[p] as i64 466 if c >= 48 { if c <= 57 { frac = frac * 10 + (c - 48); p = p + 1; dd = dd + 1 } else { go = 0 } } else { go = 0 } 467 } 468 } 469 } 470 while dd < 2 { frac = frac * 10; dd = dd + 1 } 471 } 472 } 473 return ip * 1000 + frac * 10 474} 475func mnp_ncpu(buf: *u8, cap: i64) -> i64 { 476 let r: i64 = mnp_readproc("/proc/cpuinfo" as *u8, buf, cap) 477 if r <= 0 { return 1 } 478 let c: i64 = mnp_count_sub(buf, r, "processor" as *u8, 9) 479 if c < 1 { return 1 } 480 return c 481} 482func mnp_meminfo_kb(buf: *u8, n: i64, key: *u8, klen: i64) -> i64 { 483 let at: i64 = mnp_find_after(buf, n, key, klen) 484 if at < 0 { return 0 - 1 } 485 let s: i64 = mnp_skip_sp(buf, n, at) 486 let pend: *i64 = sys_mmap(16) as *i64 487 return mnp_pdec(buf, n, s, pend) 488} 489func ma_emit_nodes(out: *u8) -> i64 { 490 let small: *u8 = sys_mmap(8192) 491 let big: *u8 = sys_mmap(262144) 492 let load_milli: i64 = mnp_loadavg_milli(small) 493 let ncpu: i64 = mnp_ncpu(big, 262144) 494 var lpc: i64 = load_milli 495 if ncpu > 0 { lpc = load_milli / ncpu } 496 let rmem: i64 = mnp_readproc("/proc/meminfo" as *u8, small, 8192) 497 var mtot: i64 = mnp_meminfo_kb(small, rmem, "MemTotal:" as *u8, 9) 498 var mav: i64 = mnp_meminfo_kb(small, rmem, "MemAvailable:" as *u8, 13) 499 if mtot < 1 { mtot = 1 } 500 if mav < 0 { mav = 0 } 501 let mused_pct: i64 = (mtot - mav) * 100 / mtot 502 let rtcp: i64 = mnp_readproc("/proc/net/tcp" as *u8, big, 262144) 503 var conns: i64 = 0 504 if rtcp > 0 { let cc: i64 = mnp_count_sub(big, rtcp, ":20FB" as *u8, 5); if cc > 1 { conns = cc - 1 } } 505 var verdict: *u8 = "OK" as *u8 506 if lpc >= 700 { verdict = "BUSY" as *u8 } 507 if lpc >= 1000 { verdict = "OVERLOADED" as *u8 } 508 let body: *u8 = sys_mmap(MA_BODYCAP) 509 var b: i64 = sd_cat(body, 0, "{\"nodes\":[{\"name\":\"nas\",\"role\":\"orchestrator\",\"addr\":\"192.168.8.227\",\"load_milli\":" as *u8) 510 b = sd_catn(body, b, load_milli) 511 b = sd_cat(body, b, ",\"ncpu\":" as *u8); b = sd_catn(body, b, ncpu) 512 b = sd_cat(body, b, ",\"load_per_core_milli\":" as *u8); b = sd_catn(body, b, lpc) 513 b = sd_cat(body, b, ",\"mem_used_pct\":" as *u8); b = sd_catn(body, b, mused_pct) 514 b = sd_cat(body, b, ",\"mem_avail_mb\":" as *u8); b = sd_catn(body, b, mav / 1024) 515 b = sd_cat(body, b, ",\"mem_total_mb\":" as *u8); b = sd_catn(body, b, mtot / 1024) 516 b = sd_cat(body, b, ",\"conns8443\":" as *u8); b = sd_catn(body, b, conns) 517 b = sd_cat(body, b, ",\"verdict\":\"" as *u8); b = sd_cat(body, b, verdict) 518 b = sd_cat(body, b, "\",\"ts\":" as *u8); b = sd_catn(body, b, sys_now_realtime_sec()) 519 b = sd_cat(body, b, "}],\"workers\":[{\"name\":\"laptop-5080\",\"addr\":\"192.168.8.193:7861\",\"role\":\"gpu-image\",\"state\":\"probe-via-mesh\"},{\"name\":\"west-3090\",\"addr\":\"10.0.4.13:7861\",\"role\":\"gpu-video\",\"state\":\"probe-via-mesh\"}]," as *u8) 520 b = sd_cat(body, b, "\"thresholds\":{\"busy_per_core_milli\":700,\"overload_per_core_milli\":1000}}" as *u8) 521 body[b] = 0 as u8 522 return ma_emit_200(out, body) 523} 524 525// ---- /api/health (transport orchestration: DATA parse + CORE verdict + serialize) ------------------- 526 527func ma_health_count(snap: *u8, snap_n: i64, supb: *i64, nsvcb: *i64, ndownb: *i64) -> i64 { 528 let offs: *i64 = sys_mmap(64) as *i64 529 let lens: *i64 = sys_mmap(64) as *i64 530 var sup: i64 = 0 531 var nsvc: i64 = 0 532 var ndown: i64 = 0 533 var reasons: i64 = 0 534 var cur: i64 = 0 535 while cur < snap_n { 536 let le: i64 = md_eol(snap, snap_n, cur) 537 let nf: i64 = md_split(snap, cur, le, offs, lens, 8) 538 if nf >= 2 { 539 if md_tok_eq(snap, offs[0], lens[0], "SUP" as *u8) == 1 { sup = md_slice_atoi(snap, offs[1], lens[1]) } 540 } 541 if nf >= 6 { 542 if md_tok_eq(snap, offs[0], lens[0], "SVC" as *u8) == 1 { 543 nsvc = nsvc + 1 544 let procs: i64 = md_slice_atoi(snap, offs[4], lens[4]) 545 let rwin: i64 = md_slice_atoi(snap, offs[5], lens[5]) 546 if mc_is_dup(procs) == 1 { reasons = reasons + 1 } 547 if mc_is_loop(rwin) == 1 { reasons = reasons + 1 } 548 if md_tok_eq(snap, offs[3], lens[3], "DOWN" as *u8) == 1 { reasons = reasons + 1; ndown = ndown + 1 } 549 } 550 } 551 cur = le + 1 552 } 553 if mc_is_duel(sup) == 1 { reasons = reasons + 1 } 554 supb[0] = sup 555 nsvcb[0] = nsvc 556 ndownb[0] = ndown 557 return reasons 558} 559 560func ma_health_reasons(snap: *u8, snap_n: i64, sup: i64, body: *u8, b0: i64) -> i64 { 561 let offs: *i64 = sys_mmap(64) as *i64 562 let lens: *i64 = sys_mmap(64) as *i64 563 var b: i64 = b0 564 let firstbox: *i64 = sys_mmap(8) as *i64 565 firstbox[0] = 1 566 if mc_is_duel(sup) == 1 { 567 b = ma_comma(body, b, firstbox) 568 b = sd_cat(body, b, "\"dueling-supervisors\"" as *u8) 569 } 570 var cur: i64 = 0 571 while cur < snap_n { 572 let le: i64 = md_eol(snap, snap_n, cur) 573 let nf: i64 = md_split(snap, cur, le, offs, lens, 8) 574 if nf >= 6 { 575 if md_tok_eq(snap, offs[0], lens[0], "SVC" as *u8) == 1 { 576 let procs: i64 = md_slice_atoi(snap, offs[4], lens[4]) 577 let rwin: i64 = md_slice_atoi(snap, offs[5], lens[5]) 578 if mc_is_dup(procs) == 1 { 579 b = ma_comma(body, b, firstbox) 580 b = sd_cat(body, b, "\"duplicate-instance:" as *u8) 581 b = md_cat_slice(body, b, snap, offs[1], lens[1]) 582 b = sd_cat(body, b, "\"" as *u8) 583 } 584 if mc_is_loop(rwin) == 1 { 585 b = ma_comma(body, b, firstbox) 586 b = sd_cat(body, b, "\"crash-loop:" as *u8) 587 b = md_cat_slice(body, b, snap, offs[1], lens[1]) 588 b = sd_cat(body, b, "\"" as *u8) 589 } 590 if md_tok_eq(snap, offs[3], lens[3], "DOWN" as *u8) == 1 { 591 b = ma_comma(body, b, firstbox) 592 b = sd_cat(body, b, "\"down:" as *u8) 593 b = md_cat_slice(body, b, snap, offs[1], lens[1]) 594 b = sd_cat(body, b, "\"" as *u8) 595 } 596 } 597 } 598 cur = le + 1 599 } 600 return b 601} 602 603// ---- HOST MEMORY-PRESSURE AXIS (sev-9 1785048333: /api/health said OK while swap sat 99.995% exhausted 604// and MemAvailable=146 permil -- the rollup had NO resource signal at all). MemAvailable, not SwapFree, is 605// the near-term OOM guard (incident law), so it is the primary threshold; swap saturation is the secondary. 606// Thresholds are DATA: knowledge/health_mem.conf rows `mem_avail_min_permil<TAB>N` / `swap_used_max_permil<TAB>N`, 607// compiled defaults when absent. /proc/meminfo unreadable -> permils stay -1 and the axis NEVER fires 608// (fail-safe: a blind instrument must say nothing, not false-DEGRADE). Buffers are lazy static pointers 609// (BSS-array class crashes the handler module; per-call sys_mmap is the debt-883 leak class). 610const MA_MEM_AVAIL_MIN_PERMIL: i64 = 150 611const MA_SWAP_USED_MAX_PERMIL: i64 = 950 612static ma_mi_buf: *u8 613static ma_mc_buf: *u8 614static ma_mem_scr: *i64 615// key -> integer after it (skips spaces/tabs); -1 = key absent / no digits. Serves BOTH /proc/meminfo 616// (`MemTotal: 36182312 kB`) and the conf (`mem_avail_min_permil<TAB>150`). 617func ma_key_int(buf: *u8, n: i64, key: *u8) -> i64 { 618 var kl: i64 = 0 619 while key[kl] != (0 as u8) { kl = kl + 1 } 620 var i: i64 = 0 621 while i + kl < n { 622 var m: i64 = 1 623 var j: i64 = 0 624 while j < kl { if buf[i+j] != key[j] { m = 0; j = kl } else { j = j + 1 } } 625 if m == 1 { 626 var p: i64 = i + kl 627 var sk: i64 = 1 628 while sk == 1 { 629 if p >= n { sk = 0 } else { 630 if buf[p] == (32 as u8) { p = p + 1 } else { if buf[p] == (9 as u8) { p = p + 1 } else { sk = 0 } } 631 } 632 } 633 var v: i64 = 0 634 var any: i64 = 0 635 var go: i64 = 1 636 while go == 1 { 637 if p >= n { go = 0 } else { 638 let c: i64 = buf[p] as i64 639 if c < 48 { go = 0 } else { if c > 57 { go = 0 } else { v = v*10 + (c - 48); any = 1; p = p + 1 } } 640 } 641 } 642 if any == 1 { return v } 643 return 0 - 1 644 } 645 while i < n { if buf[i] == (10 as u8) { break } i = i + 1 } 646 i = i + 1 647 } 648 return 0 - 1 649} 650func ma_read_small(path: *u8, buf: *u8, cap: i64) -> i64 { 651 let fd: i64 = sys_openat_rd(path) 652 if fd < 0 { return 0 - 1 } 653 let n: i64 = sys_read(fd, buf, cap) 654 sys_close(fd) 655 return n 656} 657func ma_mem_conf(key: *u8, defv: i64) -> i64 { 658 if (ma_mc_buf as i64) == 0 { ma_mc_buf = sys_mmap(2048) } 659 let n: i64 = ma_read_small("knowledge/health_mem.conf" as *u8, ma_mc_buf, 2046) 660 if n <= 0 { return defv } 661 let v: i64 = ma_key_int(ma_mc_buf, n, key) 662 if v < 0 { return defv } 663 return v 664} 665// Measure + judge. ma_mem_scr[0]=mem_avail_permil, [1]=swap_used_permil (-1 = unmeasured). Returns 1 on breach. 666func ma_mem_pressure() -> i64 { 667 if (ma_mem_scr as i64) == 0 { ma_mem_scr = sys_mmap(16) as *i64 } 668 ma_mem_scr[0] = 0 - 1 669 ma_mem_scr[1] = 0 - 1 670 if (ma_mi_buf as i64) == 0 { ma_mi_buf = sys_mmap(8192) } 671 let n: i64 = ma_read_small("/proc/meminfo" as *u8, ma_mi_buf, 8190) 672 if n <= 0 { return 0 } 673 let mt: i64 = ma_key_int(ma_mi_buf, n, "MemTotal:" as *u8) 674 let mav: i64 = ma_key_int(ma_mi_buf, n, "MemAvailable:" as *u8) 675 let st: i64 = ma_key_int(ma_mi_buf, n, "SwapTotal:" as *u8) 676 let sf: i64 = ma_key_int(ma_mi_buf, n, "SwapFree:" as *u8) 677 if mt > 0 { if mav >= 0 { ma_mem_scr[0] = mav * 1000 / mt } } 678 if st > 0 { if sf >= 0 { ma_mem_scr[1] = (st - sf) * 1000 / st } } 679 var breach: i64 = 0 680 if ma_mem_scr[0] >= 0 { if ma_mem_scr[0] < ma_mem_conf("mem_avail_min_permil" as *u8, MA_MEM_AVAIL_MIN_PERMIL) { breach = 1 } } 681 if ma_mem_scr[1] >= 0 { if ma_mem_scr[1] > ma_mem_conf("swap_used_max_permil" as *u8, MA_SWAP_USED_MAX_PERMIL) { breach = 1 } } 682 return breach 683} 684 685func ma_emit_health(snap: *u8, snap_n: i64, out: *u8) -> i64 { 686 let supb: *i64 = sys_mmap(8) as *i64 687 let nsvcb: *i64 = sys_mmap(8) as *i64 688 let ndownb: *i64 = sys_mmap(8) as *i64 689 let reasons: i64 = ma_health_count(snap, snap_n, supb, nsvcb, ndownb) 690 let sup: i64 = supb[0] 691 let nsvc: i64 = nsvcb[0] 692 let ndown: i64 = ndownb[0] 693 var noData: i64 = 0 694 if nsvc == 0 { noData = 1 } 695 let memb: i64 = ma_mem_pressure() 696 let verdict: i64 = mc_verdict(noData, reasons + memb) 697 let body: *u8 = sys_mmap(MA_BODYCAP) 698 var b: i64 = sd_cat(body, 0, "{\"overall\":\"" as *u8) 699 if verdict == 0 { b = sd_cat(body, b, "UNKNOWN" as *u8) } 700 if verdict == 1 { b = sd_cat(body, b, "OK" as *u8) } 701 if verdict == 2 { b = sd_cat(body, b, "DEGRADED" as *u8) } 702 b = sd_cat(body, b, "\",\"degraded\":" as *u8) 703 var degcount: i64 = reasons + memb 704 if noData == 1 { degcount = 1 + memb } 705 b = sd_catn(body, b, degcount) 706 b = sd_cat(body, b, ",\"down\":" as *u8) 707 b = sd_catn(body, b, ndown) 708 b = sd_cat(body, b, ",\"supervisors\":" as *u8) 709 b = sd_catn(body, b, sup) 710 b = sd_cat(body, b, ",\"services\":" as *u8) 711 b = sd_catn(body, b, nsvc) 712 if ma_mem_scr[0] >= 0 { 713 b = sd_cat(body, b, ",\"mem_avail_permil\":" as *u8) 714 b = sd_catn(body, b, ma_mem_scr[0]) 715 } 716 if ma_mem_scr[1] >= 0 { 717 b = sd_cat(body, b, ",\"swap_used_permil\":" as *u8) 718 b = sd_catn(body, b, ma_mem_scr[1]) 719 } 720 b = sd_cat(body, b, ",\"reasons\":[" as *u8) 721 if noData == 1 { 722 b = sd_cat(body, b, "\"no-snapshot\"" as *u8) 723 } else { 724 b = ma_health_reasons(snap, snap_n, sup, body, b) 725 } 726 if memb == 1 { 727 if noData == 1 { b = sd_cat(body, b, "," as *u8) } else { if reasons > 0 { b = sd_cat(body, b, "," as *u8) } } 728 b = sd_cat(body, b, "\"mem-pressure:avail_permil=" as *u8) 729 if ma_mem_scr[0] >= 0 { b = sd_catn(body, b, ma_mem_scr[0]) } else { b = sd_cat(body, b, "na" as *u8) } 730 b = sd_cat(body, b, "-swap_used_permil=" as *u8) 731 if ma_mem_scr[1] >= 0 { b = sd_catn(body, b, ma_mem_scr[1]) } else { b = sd_cat(body, b, "na" as *u8) } 732 b = sd_cat(body, b, "\"" as *u8) 733 } 734 b = sd_cat(body, b, "]}" as *u8) 735 return ma_emit_200(out, body) 736} 737 738func ma_emit_health_file(snapfile: *u8, out: *u8) -> i64 { 739 let sbox: *i64 = sys_mmap(16) as *i64 740 let page: *u8 = md_read_file(snapfile, sbox) 741 if (page as i64) == 0 { 742 let empty: *u8 = sys_mmap(1) 743 return ma_emit_health(empty, 0, out) 744 } 745 return ma_emit_health(page, sbox[0], out) 746} 747 748// ---- auth + login (transport) ----------------------------------------------------------------------- 749 750func ma_authed(ctx: *NxAuthContext, req: *u8, req_n: i64) -> i64 { 751 let now_s: i64 = sys_now_realtime_sec() 752 if nx_sa_validate(ctx, req, req_n, now_s) == NX_MAUTH_OK { return 1 } 753 return 0 754} 755 756// ma_level_of: the caller's ACCESS LEVEL for this request. -1 = no/invalid session (=> 401); else 0..3 from the 757// SHARED access-granting path (session uid -> nishi_uid_handle.tsv -> roles.tsv). Composes nx_sa_validate_handle 758// (the SAME realm-bound session validation) + ag_uid_to_level (nx_access_lib) -- so /health + /api gate EXACTLY 759// like the hub gateway. This is what "lines the OPAQUE up with the access-granting system" for the mgmt plane. 760func ma_level_of(ctx: *NxAuthContext, req: *u8, req_n: i64) -> i64 { 761 let now_s: i64 = sys_now_realtime_sec() 762 let uid: *u8 = sys_mmap(64); let uidn: *i64 = sys_mmap(8) as *i64 763 if nx_sa_validate_handle(ctx, req, req_n, now_s, uid, 64, uidn) != NX_MAUTH_OK { return 0 - 1 } 764 return ag_uid_to_level(uid, uidn[0], MA_IDX, MA_ROLES) 765} 766 767func ma_confirmed(req: *u8, req_n: i64) -> i64 { 768 let body_off: i64 = sd_body_off(req, req_n) 769 let body: *u8 = ((req as i64) + body_off) as *u8 770 let body_n: i64 = req_n - body_off 771 let coff: *i64 = sys_mmap(8) as *i64 772 let cn: *i64 = sys_mmap(8) as *i64 773 if sd_form_field(body, body_n, "confirm" as *u8, 7, coff, cn) == 1 { 774 if md_slice_eq(body, coff[0], cn[0], "yes" as *u8, 0, 3) == 1 { return 1 } 775 } 776 return 0 777} 778 779func ma_login(ctx: *NxAuthContext, req: *u8, req_n: i64, out: *u8) -> i64 { 780 let body_off: i64 = sd_body_off(req, req_n) 781 let body: *u8 = ((req as i64) + body_off) as *u8 782 let body_n: i64 = req_n - body_off 783 let hoff: *i64 = sys_mmap(8) as *i64 784 let hn: *i64 = sys_mmap(8) as *i64 785 let poff2: *i64 = sys_mmap(8) as *i64 786 let pnn: *i64 = sys_mmap(8) as *i64 787 var got: i64 = 0 788 if sd_form_field(body, body_n, "handle" as *u8, 6, hoff, hn) == 1 { 789 if sd_form_field(body, body_n, "passphrase" as *u8, 10, poff2, pnn) == 1 { got = 1 } 790 } 791 var o: i64 = 0 792 var ok: i64 = 0 793 if got == 1 { 794 let hbuf: *u8 = sys_mmap(256) 795 let pbuf: *u8 = sys_mmap(512) 796 let h_dec: i64 = sd_urldecode(((body as i64) + hoff[0]) as *u8, hn[0], hbuf, 255) 797 let p_dec: i64 = sd_urldecode(((body as i64) + poff2[0]) as *u8, pnn[0], pbuf, 511) 798 if h_dec > 0 { if p_dec > 0 { 799 let tok: *u8 = sys_mmap(NX_MAUTH_SESSION_TOKEN_BYTES) 800 let tok_n: *i64 = sys_mmap(8) as *i64 801 tok_n[0] = 0 802 if nx_modern_auth_login(ctx, hbuf, h_dec, pbuf, p_dec, tok, NX_MAUTH_SESSION_TOKEN_BYTES, tok_n) == NX_MAUTH_OK { 803 let b64: *u8 = sys_mmap(256) 804 let b64_n: i64 = b64_encode(tok, NX_MAUTH_SESSION_TOKEN_BYTES, b64) 805 o = sd_cat(out, o, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8) 806 o = sd_catn(out, o, 12 + b64_n) 807 o = sd_cat(out, o, "\r\n\r\n{\"token\":\"" as *u8) 808 var z: i64 = 0 809 while z < b64_n { out[o] = b64[z]; o = o + 1; z = z + 1 } 810 o = sd_cat(out, o, "\"}" as *u8) 811 ok = 1 812 } 813 } } 814 } 815 if ok == 0 { o = sd_emit_401_json(out) } 816 return o 817} 818 819// ---- /api/upload (transport: parse query, allowlist via DATA, reassemble chunk -> stage <name>.new) -------- 820// THE keystone that lets an operator PUBLISH an artifact over the authenticated HTTPS API from ANYWHERE (the gap 821// nx_aw_send left: it needs the LAN). Chunked because ONE request (headers+body) must be <= SD_REQCAP (64KB) and 822// artifacts are megabytes: the client POSTs the raw bytes in <=48KB slices; here we APPEND them into <name>.upload 823// and, on the final chunk, atomically rename to <name>.new. ADDITIVE + NEVER-BRICK (#26): this ONLY writes the 824// staging files <name>.upload / <name>.new; it NEVER promotes/activates/touches the live <name>. Promotion + 825// health-check + auto-rollback stays entirely in /api/deploy. Fail-closed everywhere (unknown target/seq gap -> 826// 400 + nothing appended). The mgmt daemon is single-threaded (one request at a time, main's serve loop), so the 827// per-target expected-seq persisted in the <name>.upload.seq sidecar is race-free by construction. 828 829// ---- query-string helpers (the params ride in the request TARGET, since the body is the raw binary chunk) ----- 830 831// Locate the '?' in the request target slice path[0..pn); returns the index of the first byte AFTER '?', or -1. 832func mau_query_off(path: *u8, pn: i64) -> i64 { 833 var i: i64 = 0 834 while i < pn { if (path[i] as i64) == 63 { return i + 1 } i = i + 1 } // 63 = '?' 835 return 0 - 1 836} 837 838// Extract query param `name` from the query string path[qoff..pn) into a null-terminated slice descriptor: 839// on hit sets voff[0]/vlen[0] to the raw value slice (still %-encoded, terminated by '&' or end) and returns 1; 840// 0 if absent. name matched as "name=" at a param boundary (start-of-query or just after '&'). Fail-closed: no 841// url-decode here -- our params are ASCII names/ints/hex, and a stray '%' just stays literal (rejected downstream). 842func mau_qparam(path: *u8, pn: i64, qoff: i64, name: *u8, name_n: i64, voff: *i64, vlen: *i64) -> i64 { 843 if qoff < 0 { return 0 } 844 var pos: i64 = qoff 845 while pos < pn { 846 var m: i64 = 1 847 if pos + name_n + 1 > pn { m = 0 } 848 if m == 1 { 849 var i: i64 = 0 850 while i < name_n { if (path[pos + i] as i64) != (name[i] as i64) { m = 0; i = name_n } else { i = i + 1 } } 851 } 852 if m == 1 { if (path[pos + name_n] as i64) != 61 { m = 0 } } // 61 = '=' 853 // find end of this param's value (next '&' or end-of-query) 854 var vend: i64 = pos 855 var scan: i64 = 1 856 while scan == 1 { 857 if vend >= pn { scan = 0 } 858 if scan == 1 { if (path[vend] as i64) == 38 { scan = 0 } } // 38 = '&' 859 if scan == 1 { vend = vend + 1 } 860 } 861 if m == 1 { voff[0] = pos + name_n + 1; vlen[0] = vend - (pos + name_n + 1); return 1 } 862 pos = vend + 1 863 } 864 return 0 865} 866 867// parse a non-negative decimal from path[off..off+len). Returns the value, or -1 if empty / any non-digit byte 868// (fail-closed: a malformed seq must NOT silently coerce to 0 and get treated as the truncating first chunk). 869func mau_qint(path: *u8, off: i64, len: i64) -> i64 { 870 if len <= 0 { return 0 - 1 } 871 var v: i64 = 0 872 var i: i64 = 0 873 while i < len { 874 let c: i64 = path[off + i] as i64 875 if c < 48 { return 0 - 1 } 876 if c > 57 { return 0 - 1 } 877 v = v * 10 + (c - 48) 878 i = i + 1 879 } 880 return v 881} 882 883// build "<name>.upload" / "<name>.upload.seq" / "<name>.new" from the target slice path[toff..toff+tlen) into buf 884// (null-terminated). suffix is a NUL-terminated cstr. Returns the length written. 885func mau_build_path(path: *u8, toff: i64, tlen: i64, suffix: *u8, buf: *u8) -> i64 { 886 var o: i64 = 0 887 var i: i64 = 0 888 while i < tlen { buf[o] = path[toff + i]; o = o + 1; i = i + 1 } 889 var j: i64 = 0 890 while suffix[j] != (0 as u8) { buf[o] = suffix[j]; o = o + 1; j = j + 1 } 891 buf[o] = 0 as u8 892 return o 893} 894 895// read the expected-next seq from the sidecar file (ASCII int). Returns the int, or -1 if the sidecar is absent 896// or unparseable (=> only seq 0 is valid, which (re)creates it -- a resumed/garbled sidecar can't inject a gap). 897func mau_read_seq(sidecar: *u8) -> i64 { 898 let szp: *i64 = sys_mmap(16) as *i64 899 let buf: *u8 = sys_read_file(sidecar, szp) 900 if (buf as i64) == 0 { return 0 - 1 } 901 let n: i64 = szp[0] 902 if n <= 0 { return 0 - 1 } 903 return mau_qint(buf, 0, n) // reuse the strict digit parser; trailing '\n' -> -1, so keep it clean (no newline) 904} 905 906// write the next-expected seq (ASCII, NO trailing newline) into the sidecar (O_CREAT|O_TRUNC 0644). Returns 0/-1. 907func mau_write_seq(sidecar: *u8, val: i64) -> i64 { 908 let fd: i64 = sys_openat_wr(sidecar, 0x1a4) // 0644 909 if fd < 0 { return 0 - 1 } 910 let t: *u8 = sys_mmap(24) 911 var m: i64 = val 912 var k: i64 = 0 913 if m == 0 { t[0] = 48 as u8; k = 1 } 914 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 915 let ob: *u8 = sys_mmap(24) 916 var i: i64 = 0 917 while i < k { ob[i] = t[k - 1 - i]; i = i + 1 } 918 sys_write(fd, ob, k) 919 sys_close(fd) 920 return 0 921} 922 923// lowercase-hex of a 32-byte digest into out[0..64) (NUL-terminated). 924func mau_hex32(dig: *u8, out: *u8) -> i64 { 925 let hx: *u8 = "0123456789abcdef" as *u8 926 var i: i64 = 0 927 while i < 32 { 928 let b: i64 = dig[i] & 0xff 929 out[i * 2] = hx[(b >> 4) & 0xf] 930 out[i * 2 + 1] = hx[b & 0xf] 931 i = i + 1 932 } 933 out[64] = 0 as u8 934 return 64 935} 936 937// case-insensitive compare of the provided sha256 hex slice path[hoff..hoff+hlen) against the computed 64-char hex. 938// 1 = match, 0 = mismatch (or a non-64-length provided value -> refuse, fail-closed). 939func mau_hex_eq(path: *u8, hoff: i64, hlen: i64, want: *u8) -> i64 { 940 if hlen != 64 { return 0 } 941 var i: i64 = 0 942 while i < 64 { 943 var a: i64 = path[hoff + i] as i64 944 var b: i64 = want[i] as i64 945 if a >= 65 { if a <= 90 { a = a + 32 } } // ASCII upper -> lower 946 if b >= 65 { if b <= 90 { b = b + 32 } } 947 if a != b { return 0 } 948 i = i + 1 949 } 950 return 1 951} 952 953// create every parent directory of `path` (idempotent; EEXIST is fine). Only ever called on paths that 954// already passed the fail-closed target allowlists, so the walk stays inside the pinned namespace. 955func mau_mkdirs(path: *u8) -> i64 { 956 let tmp: *u8 = sys_mmap(320) 957 var i: i64 = 0 958 while path[i] != (0 as u8) { 959 if (path[i] as i64) == 47 { if i > 0 { 960 var k: i64 = 0 961 while k < i { tmp[k] = path[k]; k = k + 1 } 962 tmp[i] = 0 as u8 963 sys_mkdir(tmp, 0x1ed) 964 } } 965 i = i + 1 966 } 967 return 0 968} 969 970// THE handler: parse ?target&seq&final[&sha256] from the request target, allowlist-check the target, then APPEND 971// the raw body chunk into <target>.upload with strict monotonic-seq enforcement; on final=1 (optionally verifying 972// sha256) atomically rename to <target>.new. Returns response bytes. Never touches the live artifact. 973func ma_do_upload(req: *u8, req_n: i64, out: *u8) -> i64 { 974 // -- locate the request target (path + query) and the body slice -- 975 let poff: *i64 = sys_mmap(8) as *i64 976 let plen: *i64 = sys_mmap(8) as *i64 977 poff[0] = 0; plen[0] = 0 978 sd_find_path(req, req_n, poff, plen) 979 let path: *u8 = ((req as i64) + poff[0]) as *u8 980 let pn: i64 = plen[0] 981 let qoff: i64 = mau_query_off(path, pn) 982 if qoff < 0 { return ma_emit_400(out, "{\"error\":\"missing query params (target/seq/final)\"}" as *u8) } 983 984 let toff: *i64 = sys_mmap(8) as *i64 985 let tlen: *i64 = sys_mmap(8) as *i64 986 let soff: *i64 = sys_mmap(8) as *i64 987 let slen: *i64 = sys_mmap(8) as *i64 988 let foff: *i64 = sys_mmap(8) as *i64 989 let flen: *i64 = sys_mmap(8) as *i64 990 if mau_qparam(path, pn, qoff, "target" as *u8, 6, toff, tlen) != 1 { return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) } 991 if mau_qparam(path, pn, qoff, "seq" as *u8, 3, soff, slen) != 1 { return ma_emit_400(out, "{\"error\":\"missing seq\"}" as *u8) } 992 if mau_qparam(path, pn, qoff, "final" as *u8, 5, foff, flen) != 1 { return ma_emit_400(out, "{\"error\":\"missing final\"}" as *u8) } 993 994 // -- fail-closed allowlist (DATA ring): service binary OR static-content namespace. unknown -> nothing written. -- 995 var tok_ok: i64 = md_upload_target_ok(path, toff[0], tlen[0]) 996 if tok_ok != 1 { tok_ok = md_content_target_ok(path, toff[0], tlen[0]) } 997 if tok_ok != 1 { return ma_emit_400(out, "{\"error\":\"target not allowlisted\"}" as *u8) } 998 999 let seq: i64 = mau_qint(path, soff[0], slen[0]) 1000 let fin: i64 = mau_qint(path, foff[0], flen[0]) 1001 if seq < 0 { return ma_emit_400(out, "{\"error\":\"bad seq (non-numeric)\"}" as *u8) } 1002 if fin < 0 { return ma_emit_400(out, "{\"error\":\"bad final (non-numeric)\"}" as *u8) } 1003 1004 // -- build the staging + sidecar + promote paths from the (allowlisted) target basename -- 1005 let stage: *u8 = sys_mmap(256) 1006 let sidecar: *u8 = sys_mmap(256) 1007 let newp: *u8 = sys_mmap(256) 1008 mau_build_path(path, toff[0], tlen[0], ".upload" as *u8, stage) 1009 mau_build_path(path, toff[0], tlen[0], ".upload.seq" as *u8, sidecar) 1010 mau_build_path(path, toff[0], tlen[0], ".new" as *u8, newp) 1011 1012 // -- strict monotonic-seq gate (single-threaded daemon => race-free sidecar) -- 1013 if seq == 0 { 1014 // first chunk: (re)start the transfer -- TRUNCATE stage, reset sidecar. Any prior partial is discarded. 1015 // Multi-page content targets may live in subdirs (wholesale sites): create parents inside the 1016 // pinned, allowlist-validated namespace so the stage write cannot fail on a missing directory. 1017 mau_mkdirs(stage) 1018 } else { 1019 let expected: i64 = mau_read_seq(sidecar) 1020 if expected != seq { 1021 // IDEMPOTENT REPLAY-ACK (safe-to-run-twice): the edge relay LOSES responses AND re-delivers STALE 1022 // earlier chunks under rapid chunk storms; the client then re-sees a chunk the daemon ALREADY 1023 // appended, and a plain bad-seq reject desyncs+aborts the whole transfer (observed: 650KB/80-chunk 1024 // uploads dying at ~12-14). (a) non-final replay: seq < expected -> those bytes are ALREADY in the 1025 // stage; ack WITHOUT appending (a second append would corrupt the artifact; the final sha256 1026 // still end-to-end verifies the reassembled bytes). BROADENED from seq==expected-1 to seq<expected 1027 // (2026-07-24) so a stale duplicate of ANY earlier chunk is a no-op, not a fatal bad-seq. 1028 if expected >= 1 { if seq < expected { if fin != 1 { 1029 let ab2: *u8 = sys_mmap(256) 1030 var a2: i64 = sd_cat(ab2, 0, "{\"action\":\"UPLOAD\",\"target\":\"" as *u8) 1031 a2 = md_cat_slice(ab2, a2, path, toff[0], tlen[0]) 1032 a2 = sd_cat(ab2, a2, "\",\"seq\":" as *u8) 1033 a2 = sd_catn(ab2, a2, seq) 1034 a2 = sd_cat(ab2, a2, ",\"final\":0,\"replay\":1}" as *u8) 1035 return ma_emit_200(out, ab2) 1036 } } } 1037 // (b) final-chunk replay AFTER a completed transfer: sidecar already dropped (expected=-1) and 1038 // <target>.new exists. Fail-closed: ack ONLY when the client's sha256 param matches the 1039 // staged .new bytes (the client always sends sha on final) -- then this replay IS the same 1040 // completed upload; re-emit the STAGED response instead of desyncing the client. 1041 if expected < 0 { if fin == 1 { 1042 let rhoff: *i64 = sys_mmap(8) as *i64 1043 let rhlen: *i64 = sys_mmap(8) as *i64 1044 if mau_qparam(path, pn, qoff, "sha256" as *u8, 6, rhoff, rhlen) == 1 { 1045 let rszp: *i64 = sys_mmap(16) as *i64 1046 let rfull: *u8 = sys_read_file(newp, rszp) 1047 if (rfull as i64) != 0 { 1048 let rdig: *u8 = sys_mmap(32) 1049 sha256_digest(rfull, rszp[0], rdig) 1050 let rhex: *u8 = sys_mmap(72) 1051 mau_hex32(rdig, rhex) 1052 if mau_hex_eq(path, rhoff[0], rhlen[0], rhex) == 1 { 1053 let rb2: *u8 = sys_mmap(320) 1054 var b3: i64 = sd_cat(rb2, 0, "{\"action\":\"UPLOAD\",\"target\":\"" as *u8) 1055 b3 = md_cat_slice(rb2, b3, path, toff[0], tlen[0]) 1056 b3 = sd_cat(rb2, b3, "\",\"bytes\":" as *u8) 1057 b3 = sd_catn(rb2, b3, rszp[0]) 1058 b3 = sd_cat(rb2, b3, ",\"final\":1,\"replay\":1,\"staged\":\"" as *u8) 1059 b3 = md_cat_slice(rb2, b3, path, toff[0], tlen[0]) 1060 b3 = sd_cat(rb2, b3, ".new\"}" as *u8) 1061 return ma_emit_200(out, rb2) 1062 } 1063 } 1064 } 1065 } } 1066 return ma_emit_400(out, "{\"error\":\"bad seq\"}" as *u8) // gap / out-of-order / no prior seq=0 -> reject, append NOTHING 1067 } 1068 } 1069 1070 // -- body slice: everything past the CRLFCRLF header terminator (raw chunk bytes) -- 1071 let body_off: i64 = sd_body_off(req, req_n) 1072 let body: *u8 = ((req as i64) + body_off) as *u8 1073 let body_n: i64 = req_n - body_off 1074 1075 // -- write the chunk via the SHIPPED primitive (seq0 truncates+creates, seq>0 appends) -- compose, don't re-open -- 1076 if mu_stage_chunk(stage, seq, body, body_n) < 0 { return ma_emit_400(out, "{\"error\":\"cannot open staging file\"}" as *u8) } 1077 // advance the expected-seq sidecar to the next chunk number 1078 mau_write_seq(sidecar, seq + 1) 1079 1080 // -- non-final chunk: ack and wait for the next -- 1081 if fin != 1 { 1082 let ab: *u8 = sys_mmap(256) 1083 var a: i64 = sd_cat(ab, 0, "{\"action\":\"UPLOAD\",\"target\":\"" as *u8) 1084 a = md_cat_slice(ab, a, path, toff[0], tlen[0]) 1085 a = sd_cat(ab, a, "\",\"seq\":" as *u8) 1086 a = sd_catn(ab, a, seq) 1087 a = sd_cat(ab, a, ",\"final\":0}" as *u8) 1088 return ma_emit_200(out, ab) 1089 } 1090 1091 // -- FINAL chunk: read back the assembled staging file (for size + optional integrity) -- 1092 let szp: *i64 = sys_mmap(16) as *i64 1093 let full: *u8 = sys_read_file(stage, szp) 1094 let total: i64 = szp[0] 1095 if (full as i64) == 0 { return ma_emit_400(out, "{\"error\":\"staging file vanished before finalize\"}" as *u8) } 1096 1097 // -- optional sha256 verification of the WHOLE assembled artifact -- 1098 let hoff: *i64 = sys_mmap(8) as *i64 1099 let hlen: *i64 = sys_mmap(8) as *i64 1100 if mau_qparam(path, pn, qoff, "sha256" as *u8, 6, hoff, hlen) == 1 { 1101 let dig: *u8 = sys_mmap(32) 1102 sha256_digest(full, total, dig) 1103 let hexbuf: *u8 = sys_mmap(72) 1104 mau_hex32(dig, hexbuf) 1105 if mau_hex_eq(path, hoff[0], hlen[0], hexbuf) != 1 { 1106 fio_unlink(stage) // shred the corrupt upload (never leave a bad .upload around) 1107 fio_unlink(sidecar) 1108 return ma_emit_400(out, "{\"error\":\"sha256 mismatch; staging deleted\"}" as *u8) 1109 } 1110 } 1111 1112 // -- atomically stage for /api/deploy: <target>.upload -> <target>.new (never the live <target>) -- 1113 let rr: i64 = sys_renameat(stage, newp) 1114 fio_unlink(sidecar) // transfer done; drop the seq sidecar 1115 if rr != 0 { return ma_emit_400(out, "{\"error\":\"stage rename failed\"}" as *u8) } 1116 1117 let rb: *u8 = sys_mmap(320) 1118 var b: i64 = sd_cat(rb, 0, "{\"action\":\"UPLOAD\",\"target\":\"" as *u8) 1119 b = md_cat_slice(rb, b, path, toff[0], tlen[0]) 1120 b = sd_cat(rb, b, "\",\"bytes\":" as *u8) 1121 b = sd_catn(rb, b, total) 1122 b = sd_cat(rb, b, ",\"final\":1,\"staged\":\"" as *u8) 1123 b = md_cat_slice(rb, b, path, toff[0], tlen[0]) 1124 b = sd_cat(rb, b, ".new\"}" as *u8) 1125 return ma_emit_200(out, rb) 1126} 1127 1128// ---- write actions (transport orchestration: parse here, allowlist/exec/probe via DATA, decide via CORE) -- 1129// Each is a thin pipeline: parse request -> (data) resolve/validate/exec/probe -> (core) decide -> serialize. 1130 1131const MA_DEPLOY_STATUS: *u8 = "/tmp/nx_ma_deploy_status" as *u8 1132func ma_write_status(s: *u8) -> i64 { 1133 let fd: i64 = sys_openat_wr(MA_DEPLOY_STATUS, 0x1a4) 1134 if fd >= 0 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd, s, n); sys_close(fd) } 1135 return 0 1136} 1137// GET /api/deploy_status -> the last deploy's async watchdog verdict (RUNNING|DEPLOYED-GREEN|ROLLED-BACK|PROMOTE-FAILED|none). 1138// ---- GET /api/adnet/invoice (debt 1785513943): make the billing pipeline REACHABLE --------------- 1139// nx_adnet_bill held correct CPM/CPC math that NOTHING called -- zero production callers, only its gate. 1140// A green gate on an uncallable library is still the baseline, so the capability ships as a ROUTE: a 1141// mgmt route is instantly callable, whereas a new MCP tool needs every seat to reconnect. 1142// THIN BY CONSTRUCTION: file access is md_adnet_invoice_report (data ring), money math is nx_adnet_bill, 1143// the rate/event join is nx_adnet_invoice. This function owns transport and nothing else. 1144// 503 rather than 200-with-nothing: an empty invoice and an unreadable one must never look alike. 1145// ---- POST /api/adnet/creative (debt 1785512202): advertiser creative upload ----------------------- 1146// CLOSES THE CHICKEN-AND-EGG: nx_adnet_selfserve requires the img field to be a FIRST-PARTY url, and 1147// nothing in the ecosystem ever let an advertiser produce one -- so no client could onboard at all. 1148// The raw request body IS the png. THIN BY CONSTRUCTION: validation and content-addressed naming are 1149// nx_adnet_creative (gated 13/13), the write is md_adnet_creative_store (data ring); this owns transport. 1150// EVERY REFUSAL NAMES A REMEDIABLE REASON. An advertiser who cannot tell WHY their upload bounced will 1151// mail the operator a png instead, and the self-serve path quietly dies of support load. 1152func ma_do_adnet_creative(req: *u8, req_n: i64, out: *u8) -> i64 { 1153 let body_off: i64 = sd_body_off(req, req_n) 1154 let body: *u8 = ((req as i64) + body_off) as *u8 1155 let body_n: i64 = req_n - body_off 1156 if body_n <= 0 { 1157 return ma_emit_400(out, "{\"error\":\"empty body: POST the png bytes as the raw request body\",\"spec\":\"png, exactly 728x90, at most 65536 bytes\"}" as *u8) 1158 } 1159 let url: *u8 = sys_mmap(256) 1160 let v: i64 = md_adnet_creative_store(body, body_n, url, 256) 1161 if v == ACR_OK { 1162 let rb: *u8 = sys_mmap(1024) 1163 var b: i64 = sd_cat(rb, 0, "{\"action\":\"CREATIVE-ACCEPTED\",\"url\":\"" as *u8) 1164 b = sd_cat(rb, b, url) 1165 b = sd_cat(rb, b, "\",\"note\":\"content-addressed. Put this url in the img field of your inventory row. Re-uploading identical bytes is idempotent; changed artwork mints a NEW url, so the creative can be cached without ever going stale.\"}" as *u8) 1166 rb[b] = 0 as u8 1167 return ma_emit_200(out, rb) 1168 } 1169 if v < 0 { 1170 return ma_emit_503(out, "{\"error\":\"creative validated but could not be written to the docroot; NOTHING was stored\"}" as *u8) 1171 } 1172 let rsn: *u8 = sys_mmap(128) 1173 acr_reason(v, rsn) 1174 let eb: *u8 = sys_mmap(512) 1175 var e: i64 = sd_cat(eb, 0, "{\"action\":\"CREATIVE-REFUSED\",\"reason\":\"" as *u8) 1176 e = sd_cat(eb, e, rsn) 1177 e = sd_cat(eb, e, "\",\"spec\":\"png, exactly 728x90, at most 65536 bytes\",\"why\":\"the weight ceiling is enforced at intake: a banner heavy enough to slow the host page reflects on the client who paid for it\"}" as *u8) 1178 eb[e] = 0 as u8 1179 return ma_emit_400(out, eb) 1180} 1181 1182func ma_do_adnet_invoice(out: *u8) -> i64 { 1183 let rep: *u8 = sys_mmap(65536) 1184 let n: i64 = md_adnet_invoice_report(rep, 65536) 1185 if n <= 0 { 1186 return ma_emit_503(out, "{\"error\":\"adnet invoice unavailable: inventory or rate card unreadable. Refusing to emit an empty invoice, which a biller cannot tell apart from a genuinely zero one.\",\"basis\":\"viewable\"}" as *u8) 1187 } 1188 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\nContent-Length: " as *u8, rep, n) 1189} 1190 1191func ma_do_deploy_status(out: *u8) -> i64 { 1192 let b: *u8 = sys_mmap(256); var n: i64 = 0 1193 let fd: i64 = sys_openat_rd(MA_DEPLOY_STATUS) 1194 if fd >= 0 { n = sys_read(fd, b, 200); sys_close(fd) } 1195 let rb: *u8 = sys_mmap(512); var o: i64 = sd_cat(rb, 0, "{\"deploy_status\":\"" as *u8) 1196 if n > 0 { var i: i64=0; while i<n { if b[i]!=(10 as u8) { if b[i]!=(13 as u8) { rb[o]=b[i]; o=o+1 } } i=i+1 } } else { o = sd_cat(rb, o, "none" as *u8) } 1197 o = sd_cat(rb, o, "\"}" as *u8); rb[o] = 0 as u8 1198 return ma_emit_200(out, rb) 1199} 1200 1201// sanitize a build target name to [a-zA-Z0-9_] ONLY (no /, ., ..) -> prevents path escape in runtime/<name>.nx. 1202// Copies the slice into buf (NUL-terminated). Returns 1 if valid+copied, 0 if empty/too-long/illegal char. 1203func ma_sanitize_name(src: *u8, off: i64, len: i64, buf: *u8, cap: i64) -> i64 { 1204 if len <= 0 { return 0 } 1205 if len >= cap { return 0 } 1206 var i: i64 = 0 1207 while i < len { 1208 let c: i64 = src[off + i] as i64 1209 var ok: i64 = 0 1210 if c >= 48 { if c <= 57 { ok = 1 } } 1211 if c >= 65 { if c <= 90 { ok = 1 } } 1212 if c >= 97 { if c <= 122 { ok = 1 } } 1213 if c == 95 { ok = 1 } 1214 if ok == 0 { return 0 } 1215 buf[i] = src[off + i] 1216 i = i + 1 1217 } 1218 buf[len] = 0 as u8 1219 return 1 1220} 1221// JSON-escape the LAST <maxsrc> bytes of src[0..n) into dst (NUL-terminated), for a build-diagnostic tail. 1222// Escapes " and \ and control bytes (newline/CR/tab -> \n \r \t; other <0x20 dropped). Bounds dst to <cap>. 1223// Returns the dst byte length. Surfaces the real nx_cc/nxasm error+line in a BUILD-FAILED JSON string field. 1224func ma_json_esc_tail(src: *u8, n: i64, maxsrc: i64, dst: *u8, cap: i64) -> i64 { 1225 var start: i64 = 0 1226 if n > maxsrc { start = n - maxsrc } 1227 var o: i64 = 0 1228 var i: i64 = start 1229 while i < n { 1230 if o + 8 >= cap { i = n } else { 1231 let c: i64 = src[i] as i64 1232 if c == 34 { dst[o] = 92 as u8; o = o + 1; dst[o] = 34 as u8; o = o + 1 } 1233 else { if c == 92 { dst[o] = 92 as u8; o = o + 1; dst[o] = 92 as u8; o = o + 1 } 1234 else { if c == 10 { dst[o] = 92 as u8; o = o + 1; dst[o] = 110 as u8; o = o + 1 } 1235 else { if c == 13 { dst[o] = 92 as u8; o = o + 1; dst[o] = 114 as u8; o = o + 1 } 1236 else { if c == 9 { dst[o] = 92 as u8; o = o + 1; dst[o] = 116 as u8; o = o + 1 } 1237 else { if c >= 32 { dst[o] = src[i]; o = o + 1 } } } } } } 1238 i = i + 1 1239 } 1240 } 1241 dst[o] = 0 as u8 1242 return o 1243} 1244// First offset of NUL-terminated needle in src[0..n), or -1. Naive scan -- build logs are 1245// tail-bounded 16KB here, so there is no pathological input to be clever about. 1246func ma_find(src: *u8, n: i64, pat: *u8) -> i64 { 1247 var pl: i64 = 0 1248 while pat[pl] != (0 as u8) { pl = pl + 1 } 1249 if pl == 0 { return 0 - 1 } 1250 var i: i64 = 0 1251 var found: i64 = 0 - 1 1252 while i + pl <= n { 1253 var k: i64 = 0 1254 var ok: i64 = 1 1255 while k < pl { 1256 if src[i + k] != pat[k] { ok = 0; k = pl } else { k = k + 1 } 1257 } 1258 if ok == 1 { found = i; i = n } else { i = i + 1 } 1259 } 1260 return found 1261} 1262// file exists (openable for read)? 1 yes / 0 no. 1263func ma_path_exists(path: *u8) -> i64 { 1264 let fd: i64 = sys_openat_rd(path) 1265 if fd >= 0 { sys_close(fd); return 1 } 1266 return 0 1267} 1268// CROSS-TREE DUP-SHADOW detector (seq207/seq164, the clobber-landmine class): a <name>.nx present in BOTH 1269// buildroot/runtime/_hdl_build (probed FIRST by nx_sov_build_run) AND buildroot/runtime (the flat shadow) is a 1270// hazard -- a resolution-order change or a stale flat copy silently regresses the service (it detonated on 1271// nx_mgmt_api this session, dropping the F-210 control plane). Returns 1 if <name>.nx exists in both dirs. 1272// Composes nx_dup_source_check's exact detection logic, wired INLINE at build time = fail-loud, non-blocking. 1273// BANK THE STAGED SLOT BEFORE A REBUILD OVERWRITES IT (2026-08-03, debt 1785771729). 1274// MEASURED: /api/build stages <t>.sov.elf.new by overwrite, with NO warning and NO backup -- so a second 1275// seat's pending release vanishes without a trace the moment anyone rebuilds that target. That is the SAME 1276// blind-clobber class nx_fs_write already refuses via expect= (seq1379); here BANKING is the right remedy 1277// rather than refusing, because refusing would break every ordinary rebuild, while a .prev costs one rename 1278// and keeps the bytes. Wires the EXISTING .prev primitive (promote/deploy/route all use it) -- never a 2nd. 1279// Returns 1 if a prior staged artifact was banked, else 0. Fail-safe: any failure leaves the slot untouched. 1280func ma_bank_staged(nm: *u8) -> i64 { 1281 let cur: *u8 = sys_mmap(512) 1282 var c: i64 = sd_cat(cur, 0, nm) 1283 c = sd_cat(cur, c, ".sov.elf.new" as *u8) 1284 cur[c] = 0 as u8 1285 let fd: i64 = sys_openat_rd(cur) 1286 if fd < 0 { return 0 } 1287 sys_close(fd) 1288 let prev: *u8 = sys_mmap(512) 1289 var p: i64 = sd_cat(prev, 0, cur) 1290 p = sd_cat(prev, p, ".prev" as *u8) 1291 prev[p] = 0 as u8 1292 if sys_renameat(cur, prev) != 0 { return 0 } 1293 return 1 1294} 1295// RESTORE the banked staged artifact when the compile FAILED (2026-08-03, same debt). 1296// WHY THIS IS MANDATORY, not tidiness: 45 tool_allowlist rows EXECUTE a `<t>.sov.elf.new` directly (the 1297// `stagedref` class, incl. 5 deliberate `*_staged` tools). BEFORE banking, a failed rebuild left the old .new 1298// untouched and those tools kept working; WITH banking and no restore, a failed compile would leave the slot 1299// EMPTY and silently break every one of them. A BANK WITHOUT A RESTORE-ON-FAILURE CONVERTS A HARMLESS FAILED 1300// BUILD INTO AN OUTAGE -- pair the copy with the exit code, always. 1301func ma_restore_staged(nm: *u8) -> i64 { 1302 let cur: *u8 = sys_mmap(512) 1303 var c: i64 = sd_cat(cur, 0, nm) 1304 c = sd_cat(cur, c, ".sov.elf.new" as *u8) 1305 cur[c] = 0 as u8 1306 let prev: *u8 = sys_mmap(512) 1307 var p: i64 = sd_cat(prev, 0, cur) 1308 p = sd_cat(prev, p, ".prev" as *u8) 1309 prev[p] = 0 as u8 1310 // Only restore into an EMPTY slot: if the compile actually produced a .new we must never clobber it 1311 // with the older banked copy (that would be the very silent-overwrite this whole change exists to stop). 1312 let fd: i64 = sys_openat_rd(cur) 1313 if fd >= 0 { sys_close(fd); return 0 } 1314 if sys_renameat(prev, cur) != 0 { return 0 } 1315 return 1 1316} 1317func ma_build_dup_shadow(nm: *u8) -> i64 { 1318 let pa: *u8 = sys_mmap(256) 1319 var a: i64 = sd_cat(pa, 0, "buildroot/runtime/_hdl_build/" as *u8) 1320 a = sd_cat(pa, a, nm); a = sd_cat(pa, a, ".nx" as *u8); pa[a] = 0 as u8 1321 let pb: *u8 = sys_mmap(256) 1322 var b: i64 = sd_cat(pb, 0, "buildroot/runtime/" as *u8) 1323 b = sd_cat(pb, b, nm); b = sd_cat(pb, b, ".nx" as *u8); pb[b] = 0 as u8 1324 if ma_path_exists(pa) == 1 { if ma_path_exists(pb) == 1 { return 1 } } 1325 return 0 1326} 1327// POST /api/build {target=<name>}: COMPILE a target ON THE NAS via the on-NAS toolchain (nx_hostctl buildrun) 1328// -> stage <name>.sov.elf.new for /api/deploy. The build half of build-over-API: source->binary with ZERO WSL. 1329// Name sanitized to [a-zA-Z0-9_] (no path escape); target must already exist in the synced buildroot/runtime. 1330// On success, a `dup_shadow` field warns fail-loud if the source basename exists in BOTH source dirs (seq207). 1331func ma_do_build(req: *u8, req_n: i64, out: *u8) -> i64 { 1332 let body_off: i64 = sd_body_off(req, req_n) 1333 let body: *u8 = ((req as i64) + body_off) as *u8 1334 let body_n: i64 = req_n - body_off 1335 let toff: *i64 = sys_mmap(8) as *i64 1336 let tn: *i64 = sys_mmap(8) as *i64 1337 if sd_form_field(body, body_n, "target" as *u8, 6, toff, tn) != 1 { 1338 return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) 1339 } 1340 let nm: *u8 = sys_mmap(128) 1341 if ma_sanitize_name(body, toff[0], tn[0], nm, 120) != 1 { 1342 return ma_emit_400(out, "{\"error\":\"invalid target name (only [a-zA-Z0-9_])\"}" as *u8) 1343 } 1344 // BUILD ADMISSION -- MUST run BEFORE the nx_cc fork below (seq708/768/1390). Forking the compiler on 1345 // a memory-wedged host is what OOM-reaps this very daemon and takes the deploy path down for every 1346 // seat, so the check has to gate the fork, not report on it afterwards. 1347 // 1348 // TWO BLOCKING VERDICTS (corrected 2026-07-30). Exit 3 = DENY, MemAvailable below the floor: the 1349 // measured 2026-07-20 wedge, where userspace could not fork at all. Exit 4 = QUEUE, the load ceiling. 1350 // THE EXIT-4 BLOCK WAS LIVE IN THE DEPLOYED BINARY AND IN THE LAPTOP TREE BUT ABSENT FROM THIS ONE -- 1351 // the buildroot /api/build actually compiles -- so the next build by ANY seat would have silently 1352 // deleted seq1475's backpressure, with no error anywhere. Landed here so a rebuild cannot lose it. 1353 // Blocking on exit 4 is safe ONLY because nx_build_admit now CONFIRMS the load against procs_running 1354 // (the TRUE run queue, R not D) before returning it: a disk-bound host no longer reads as CPU 1355 // saturation, so the false refusals that deadlocked every seat are gone. Gate nx_build_admit_gate 1356 // 10/10 including a revert-detecting neg-control. usage/unreadable-proc still fall through to build. 1357 let admit: i64 = md_exec_build_admit() 1358 if admit == 3 { 1359 return ma_emit_503(out, "{\"error\":\"build refused: host below the memory floor. Forking the compiler now risks wedging the host and OOM-reaping the mgmt API for every seat. The detector prints the live figure and the floor it used -- run nx_build_admit.elf check to see both. Retry after 30s.\",\"detector\":\"nx_build_admit\",\"verdict\":\"DENY-MEM\"}" as *u8) 1360 } 1361 if admit == 4 { 1362 return ma_emit_503(out, "{\"error\":\"build REFUSED and DROPPED (nothing is queued -- this request is not deferred, it is forgotten; re-issue it yourself): host load is above 1.00 x ncpu AND the run queue confirms CPU saturation (procs_running >= ncpu), so this is real compute contention, not disk wait. Retry after 30s. If this build is itself the repair for the saturation, use the sovereign path _offc/nx_sov_build_run.elf <target> --build-only, which does not consult mgmt admission.\",\"detector\":\"nx_build_admit\",\"verdict\":\"REFUSED-LOAD\"}" as *u8) 1363 } 1364 // ---- GATE-DRY RATCHET (2026-07-31, debts 1785529506 / 1785530277): STOP THE BLEEDING ON D001 ---- 1365 // L009 -- gates that hand-roll their verdict instead of inheriting nx_gate_verdict -- grew from 1366 // 2035/2167 to 2041/2182 within a SINGLE SESSION on 2026-07-31. A campaign that only migrates old 1367 // gates LOSES to a tree that adds new ones, so the count is gated HERE, at the one act that admits 1368 // a new gate to the ecosystem. This is the missing half: the migrator removes, the ratchet holds. 1369 // 1370 // IT IS A RATCHET, NOT A WALL. Only a gate with NO deployed artefact -- a NEW one -- is held to the 1371 // base class. The 2041 existing breaches are GRANDFATHERED and migrated by their owner lanes; 1372 // refusing them here would stop every seat dead. Same grandfathering nx_magicratchet described. 1373 // 1374 // u26a0THE PREDECESSOR THIS SHOULD HAVE COPIED DOES NOT EXIST: nx_magicratchet is asserted "wired into 1375 // /api/build" in FOUR comments in nx_law_warden.nx, yet grep finds ZERO call sites here AND ZERO in 1376 // the deployed mgmt binary, and a two-build experiment (clean -> BUILT, +3 literals >=1024 -> BUILT) 1377 // proves it never fires. Rule 11 has no submission gate. This one is written fresh and, unlike that 1378 // one, is proven by a NEGATIVE CONTROL rather than by a comment. 1379 // 1380 // FAIL-OPEN: md_exec_gatedry returns -1 when the detector is absent, and the build proceeds. 1381 // u26a0ENFORCEMENT WITHDRAWN 2026-07-31, SAME SESSION IT LANDED -- MY GRANDFATHER TEST WAS WRONG. 1382 // I keyed "is this gate NEW?" on "has no deployed .elf". That is FALSE for most of the corpus: 1383 // the ledger measures 2877 gate sources against 175 binaries, i.e. ~94pc of gates WERE NEVER 1384 // COMPILED. So deployclass_gate and roleaccess_gate -- in the tree for weeks -- both read as NEW 1385 // and their rebuilds were REFUSED. A ratchet that blocks the existing corpus is a WALL, and a wall 1386 // at a shared chokepoint stops every seat. 1387 // The detector (nx_gatedry) is sound and stays live; only the REFUSAL is withdrawn until the 1388 // grandfather test is sound. A correct one needs a BANKED BASELINE of known gate names (the 1389 // Notion model: violations are recorded, and only an INCREASE against the record is refused) -- 1390 // absence of a build artefact is not evidence of newness. 1391 // Per the 2026 guidance this should also re-enter as a WARNING first and escalate to an error. 1392 if md_name_is_gate(nm) == 1 { 1393 if md_gate_in_baseline(nm) == 0 { 1394 let gsrc: *u8 = sys_mmap(256) 1395 var gp: i64 = 0 1396 let gpre: *u8 = "buildroot/runtime/_hdl_build/" 1397 var gi: i64 = 0 1398 while gpre[gi] != (0 as u8) { gsrc[gp] = gpre[gi]; gp = gp + 1; gi = gi + 1 } 1399 gi = 0 1400 while nm[gi] != (0 as u8) { gsrc[gp] = nm[gi]; gp = gp + 1; gi = gi + 1 } 1401 let gsuf: *u8 = ".nx" 1402 gi = 0 1403 while gsuf[gi] != (0 as u8) { gsrc[gp] = gsuf[gi]; gp = gp + 1; gi = gi + 1 } 1404 gsrc[gp] = 0 as u8 1405 let gdv: i64 = md_exec_gatedry(gsrc) 1406 if gdv == 1 { 1407 return ma_emit_400(out, "{\"error\":\"NEW GATE REFUSED: it hand-rolls its verdict instead of inheriting nx_gate_verdict (L009/D001). Existing gates are GRANDFATHERED -- this applies only to a gate with no deployed artefact, so the fix is to write the new one on the base class, not to migrate anything. Use gv_ctr/gv_head/gv_check/gv_verdict. Run nx_gatedry <src> to see the classification.\",\"detector\":\"nx_gatedry\",\"verdict\":\"CUSTOM-VERDICT\"}" as *u8) 1408 } 1409 if gdv == 2 { 1410 return ma_emit_400(out, "{\"error\":\"NEW GATE REFUSED: it emits no verdict= anchor, so nx_gate_green reads a PASSING run as NOT-GREEN and the gate is structurally unjudgeable (seq585). Emit through nx_gate_verdict (gv_verdict) and the anchor comes with it. Run nx_gatedry <src> to see the classification.\",\"detector\":\"nx_gatedry\",\"verdict\":\"NO-VERDICT\"}" as *u8) 1411 } 1412 } 1413 } 1414 1415 // ---- R1 (seq1506): LEASE-GATE THE BUILD -- the flagger, not a brave driver ---------------------------- 1416 // OPERATOR: "why cant we clearly state when we are switching out or updating and coordinate like road 1417 // construction". Concurrent builds of the SAME target are how a session ships a regression from a 1418 // mid-churn snapshot: it happened TWICE today (mgmt lost /api/gate_run + /api/proc_kill, 21->19 routes, 1419 // because a sibling built while the tree was being edited). A race nobody can see is not a risk anyone 1420 // can manage -- so make the collision IMPOSSIBLE and, when it happens, NAME THE HOLDER. 1421 // ADOPTION not invention (seq1410, 4th instance today): nx_lease already existed, gate-proven, with ZERO 1422 // callers here. Reused by its exit-code contract (0=acquired, 3=BUSY) -- no import, no reimplementation. 1423 // TTL 600s is why this cannot deadlock the ecosystem: a session that dies mid-build cannot hold the lane 1424 // closed. A lock without a TTL would be a worse defect than the race it prevents. 1425 // ---- seq1337 RESTORED (lost to source churn; operator: "this is the top fix as its wasting our work") ---- 1426 // WHY THIS MUST EXIST: a compile that outlives the edge read window is INDISTINGUISHABLE from an infra 503 1427 // at the caller, which is exactly how a COMPILER HANG masquerades as an outage. I lived it repeatedly today: 1428 // FETCH-FAIL/503 with no way to tell "still compiling" from "the edge died" from "the host is wedged", so the 1429 // only strategy left is retry-hammering -- which adds load to the very condition being waited on. 1430 // async=yes DETACHES the compile and returns AT ONCE with a poll path. The caller then polls a marker that 1431 // can ONLY exist once the build actually finished, so the two states become DIFFERENT OBSERVABLES rather 1432 // than one ambiguous timeout. The marker is written to a tmp path and RENAMED (atomic), so a reader never 1433 // sees a half-written verdict. The path carries a millisecond timestamp, so a PREVIOUS run's marker can 1434 // never be mistaken for this one's result -- staleness is foreclosed by construction, not by cleanup. 1435 let aoff: *i64 = sys_mmap(8) as *i64 1436 let anf: *i64 = sys_mmap(8) as *i64 1437 var want_async: i64 = 0 1438 if sd_form_field(body, body_n, "async" as *u8, 5, aoff, anf) == 1 { if anf[0] >= 1 { if body[aoff[0]] == (121 as u8) { want_async = 1 } } } 1439 if want_async == 1 { 1440 sys_mkdir("_jobs" as *u8, 0x1ed) 1441 let stamp: i64 = sys_now_realtime_ms() 1442 let jdone: *u8 = sys_mmap(256) 1443 let jout: *u8 = sys_mmap(256) 1444 let jtmp: *u8 = sys_mmap(256) 1445 var jo: i64 = sd_cat(jdone, 0, "_jobs/build_" as *u8); jo = sd_cat(jdone, jo, nm); jo = sd_cat(jdone, jo, "_" as *u8); jo = sd_catn(jdone, jo, stamp); jo = sd_cat(jdone, jo, ".done" as *u8); jdone[jo] = 0 as u8 1446 var jq: i64 = sd_cat(jout, 0, "_jobs/build_" as *u8); jq = sd_cat(jout, jq, nm); jq = sd_cat(jout, jq, "_" as *u8); jq = sd_catn(jout, jq, stamp); jq = sd_cat(jout, jq, ".out" as *u8); jout[jq] = 0 as u8 1447 var jt: i64 = sd_cat(jtmp, 0, "_jobs/build_" as *u8); jt = sd_cat(jtmp, jt, nm); jt = sd_cat(jtmp, jt, "_" as *u8); jt = sd_catn(jtmp, jt, stamp); jt = sd_cat(jtmp, jt, ".tmp" as *u8); jtmp[jt] = 0 as u8 1448 // Acquire BEFORE forking: the parent returns immediately, so if we left the lease to the sync path 1449 // below an async build would run UNGATED and a sibling could start a racing build of the same target. 1450 // The CHILD releases it when the compile actually ends (see below) -- releasing in the parent would 1451 // reopen the lane while the compiler is still running, which is the exact race R1 exists to prevent. 1452 let alnm: *u8 = sys_mmap(192) 1453 md_lease_name(nm, alnm) 1454 if md_lease_run("acquire" as *u8, alnm, "mgmt-api-build" as *u8, "600" as *u8, 4, "/tmp/nx_ma_lease.out" as *u8) == 3 { 1455 let albb: *u8 = sys_mmap(2048) 1456 let albn: i64 = dp_read("/tmp/nx_ma_lease.out" as *u8, albb, 900) 1457 let acb: *u8 = sys_mmap(4096) 1458 var aco: i64 = sd_cat(acb, 0, "{\"conflict\":\"lease-busy\",\"error\":\"another session is ALREADY BUILDING this exact target -- refusing to race it (seq1506 R1). The holder is named below; the lease is TTL-bounded so a dead session cannot hold the lane closed.\",\"holder\":\"" as *u8) 1459 aco = ma_gate_esc(acb, aco, albb, albn) 1460 aco = sd_cat(acb, aco, "\",\"retry_after_s\":30}" as *u8) 1461 acb[aco] = 0 as u8 1462 return ma_emit_400(out, acb) 1463 } 1464 let abanked: i64 = ma_bank_staged(nm) // bank BEFORE the fork so BOTH build paths are covered (debt 1785771729) 1465 let apid: i64 = sys_fork() 1466 if apid == 0 { 1467 // DETACHED WORKER: shed the request fds so the parent's response is not held open by us, then 1468 // compile, then publish the verdict by ATOMIC RENAME. The lease is released HERE, not in the 1469 // parent, because the parent returns long before the compile ends -- releasing early would let a 1470 // sibling start a racing build of the same target while this one is still running. 1471 nx_setsid() 1472 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1473 if dn >= 0 { sys_dup3(dn, 0, 0); sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 1474 md_exec_hostctl_capture2("buildrun" as *u8, nm, jout) 1475 let abuf: *u8 = sys_mmap(16384) 1476 let an2: i64 = dp_read(jout, abuf, 16380) 1477 var built: i64 = 0 1478 var abytes: i64 = 0 1479 if an2 > 0 { abytes = hh_after(abuf, an2, "size=" as *u8); if abytes > 0 { built = 1 } } 1480 // same restore contract as the sync path: a failed async compile must not strand a stagedref tool 1481 if built == 0 { if abanked == 1 { ma_restore_staged(nm) } } 1482 let mb: *u8 = sys_mmap(256) 1483 var mo: i64 = sd_cat(mb, 0, "state=DONE built=" as *u8) 1484 mo = sd_catn(mb, mo, built) 1485 mo = sd_cat(mb, mo, " bytes=" as *u8) 1486 mo = sd_catn(mb, mo, abytes) 1487 mo = sd_cat(mb, mo, "\n" as *u8) 1488 let mfd: i64 = sys_openat_wr(jtmp, 0x1a4) 1489 if mfd >= 0 { sys_write(mfd, mb, mo); sys_close(mfd); sys_renameat(jtmp, jdone) } 1490 md_lease_run("release" as *u8, alnm, "mgmt-api-build" as *u8, "0" as *u8, 3, "/tmp/nx_ma_lease_async.out" as *u8) 1491 sys_exit(0) 1492 } 1493 let ab: *u8 = sys_mmap(2048) 1494 var abo: i64 = sd_cat(ab, 0, "{\"action\":\"BUILD-STARTED\",\"target\":\"" as *u8) 1495 abo = sd_cat(ab, abo, nm) 1496 abo = sd_cat(ab, abo, "\",\"poll\":\"" as *u8); abo = sd_cat(ab, abo, jdone) 1497 abo = sd_cat(ab, abo, "\",\"out\":\"" as *u8); abo = sd_cat(ab, abo, jout) 1498 abo = sd_cat(ab, abo, "\",\"note\":\"ABSENT poll-file = STILL COMPILING, which is NOT an edge failure -- do not retry the build. When it appears it reads state=DONE built=<0|1> bytes=<n> and is written by ATOMIC RENAME so you never read a partial verdict. A build that NEVER produces it is a COMPILER HANG (seq1337), not an outage. Read it with nx_fs read.\"}" as *u8) 1499 ab[abo] = 0 as u8 1500 return ma_emit_200(out, ab) 1501 } 1502 let lnm: *u8 = sys_mmap(192) 1503 md_lease_name(nm, lnm) 1504 if md_lease_run("acquire" as *u8, lnm, "mgmt-api-build" as *u8, "600" as *u8, 4, "/tmp/nx_ma_lease.out" as *u8) == 3 { 1505 let lb: *u8 = sys_mmap(2048) 1506 let ln: i64 = dp_read("/tmp/nx_ma_lease.out" as *u8, lb, 900) 1507 let cb: *u8 = sys_mmap(4096) 1508 var co: i64 = sd_cat(cb, 0, "{\"conflict\":\"lease-busy\",\"error\":\"another session is ALREADY BUILDING this exact target -- refusing to race it (seq1506 R1). Building concurrently is how a mid-churn snapshot ships a regression. The holder is named below; the lease carries a TTL so a dead session can never hold the lane closed.\",\"holder\":\"" as *u8) 1509 co = ma_gate_esc(cb, co, lb, ln) 1510 co = sd_cat(cb, co, "\",\"retry_after_s\":30}" as *u8) 1511 cb[co] = 0 as u8 1512 return ma_emit_400(out, cb) 1513 } 1514 let banked: i64 = ma_bank_staged(nm) // debt 1785771729: never silently clobber a staged artifact 1515 md_exec_hostctl_capture2("buildrun" as *u8, nm, "/tmp/nx_ma_build.out" as *u8) 1516 // Release IMMEDIATELY after the compile: everything below is formatting, so every later return path is 1517 // already covered and no exit can strand the lease. 1518 md_lease_run("release" as *u8, lnm, "mgmt-api-build" as *u8, "0" as *u8, 3, "/tmp/nx_ma_lease.out" as *u8) 1519 let pbuf: *u8 = sys_mmap(16384) 1520 let pn: i64 = dp_read("/tmp/nx_ma_build.out" as *u8, pbuf, 16380) 1521 let sz: i64 = hh_after(pbuf, pn, "size=" as *u8) 1522 if sz > 0 { 1523 let rb: *u8 = sys_mmap(512) 1524 var b: i64 = sd_cat(rb, 0, "{\"action\":\"BUILT\",\"bytes\":" as *u8) 1525 b = sd_catn(rb, b, sz) 1526 b = sd_cat(rb, b, ",\"staged\":\"" as *u8) 1527 b = sd_cat(rb, b, nm) 1528 b = sd_cat(rb, b, ".sov.elf.new\"" as *u8) 1529 // FAIL-LOUD, non-blocking: a SILENT bank is just a quieter clobber -- say so, and name where it went. 1530 if banked == 1 { 1531 b = sd_cat(rb, b, ",\"banked_prev\":\"" as *u8) 1532 b = sd_cat(rb, b, nm) 1533 b = sd_cat(rb, b, ".sov.elf.new.prev (a PRIOR staged artifact existed and was banked, not overwritten -- if you did not stage it, another seat did: check it before promoting)\"" as *u8) 1534 } 1535 // seq207 fail-loud, non-blocking: warn if a stale flat shadow of this source exists (clobber landmine). 1536 if ma_build_dup_shadow(nm) == 1 { 1537 b = sd_cat(rb, b, ",\"dup_shadow\":\"" as *u8) 1538 b = sd_cat(rb, b, nm) 1539 b = sd_cat(rb, b, ".nx exists in BOTH buildroot/runtime/_hdl_build (resolved first) AND buildroot/runtime (stale shadow); reconcile to ONE canonical dir (seq207)\"" as *u8) 1540 } 1541 b = sd_cat(rb, b, "}" as *u8) 1542 rb[b] = 0 as u8 1543 return ma_emit_200(out, rb) 1544 } 1545 // FAIL-LOUD (seq202, the organ-authoring multiplier): surface the captured nx_cc/nxasm diagnostic tail so an 1546 // API-first session sees the actual error+line, not a blind "no elf". The tail is the last of the build output. 1547 // COMPILE FAILED -> put the banked staged artifact BACK, or a stagedref tool that executes it breaks. 1548 var restored: i64 = 0 1549 if banked == 1 { restored = ma_restore_staged(nm) } 1550 let fb: *u8 = sys_mmap(8192) 1551 var fo: i64 = sd_cat(fb, 0, "{\"action\":\"BUILD-FAILED\",\"verdict\":\"nx_cc/nxasm produced no elf; is the target in buildroot/runtime?\",\"diag\":\"" as *u8) 1552 let db: *u8 = sys_mmap(2048) 1553 let dl: i64 = ma_json_esc_tail(pbuf, pn, 1400, db, 2040) 1554 fo = sd_cat(fb, fo, db) 1555 fo = sd_cat(fb, fo, "\"" as *u8) 1556 // MULTI-ERROR DIAG (2026-08-05). The tail window above was right when the compiler died AT 1557 // its first error (the error WAS the tail). With multi-error recovery the compiler parses 1558 // PAST its errors, so on a big target the error lines sit mid-log and the tail shows only 1559 // progress dots + the N-error summary -- a diag that names the count but hides every error. 1560 // Surface a second window anchored at the FIRST nx_parse: line; errors cluster from there. 1561 let ep: i64 = ma_find(pbuf, pn, "nx_parse:" as *u8) 1562 if ep >= 0 { 1563 var ewin: i64 = pn - ep 1564 if ewin > 2400 { ewin = 2400 } 1565 let eb2: *u8 = sys_mmap(8192) 1566 let el: i64 = ma_json_esc_tail(((pbuf as i64) + ep) as *u8, ewin, ewin, eb2, 4800) 1567 fo = sd_cat(fb, fo, ",\"diag_errors\":\"" as *u8) 1568 fo = sd_cat(fb, fo, eb2) 1569 fo = sd_cat(fb, fo, "\"" as *u8) 1570 } 1571 if restored == 1 { fo = sd_cat(fb, fo, ",\"staged_restored\":\"the prior staged artifact was put BACK after this failed compile (a stagedref tool executes it directly)\"" as *u8) } 1572 fo = sd_cat(fb, fo, "}" as *u8) 1573 fb[fo] = 0 as u8 1574 return ma_emit_200(out, fb) 1575} 1576 1577// POST /api/unpack {dest=<key>}: unpack a STAGED source-tree blob (<key>.pack.new from /api/upload) into a 1578// fail-closed NAS dir via the on-NAS nx_treepack. The tree-sync half of build-over-API: the whole source tree 1579// arrives in ONE /api/upload + this one call, instead of ~15k per-file SSH pushes. Never-brick: dest is 1580// allowlisted (unknown -> 400); nx_treepack writes only under the resolved dir. 1581// CONTRACT CHANGE (seq1807, deliberate fail-closed break): `sha256=<hex of the pack you uploaded>` is now 1582// REQUIRED. The staging slot is global and a sibling can overwrite it between your upload and your unpack, so 1583// a call that does not name its own bytes CANNOT be served safely -- omitting it is 400, never a best-effort 1584// unpack. Callers: take the digest /api/upload returns (or sha256 your pack locally) and pass it through. 1585func ma_do_unpack(req: *u8, req_n: i64, out: *u8) -> i64 { 1586 let body_off: i64 = sd_body_off(req, req_n) 1587 let body: *u8 = ((req as i64) + body_off) as *u8 1588 let body_n: i64 = req_n - body_off 1589 let doff: *i64 = sys_mmap(8) as *i64 1590 let dn: *i64 = sys_mmap(8) as *i64 1591 if sd_form_field(body, body_n, "dest" as *u8, 4, doff, dn) != 1 { 1592 return ma_emit_400(out, "{\"error\":\"missing dest\"}" as *u8) 1593 } 1594 let packbuf: *u8 = sys_mmap(128) 1595 let destbuf: *u8 = sys_mmap(256) 1596 if md_unpack_resolve(body, doff[0], dn[0], packbuf, destbuf) != 1 { 1597 return ma_emit_400(out, "{\"error\":\"unknown unpack dest (not in allowlist)\"}" as *u8) 1598 } 1599 // seq1807 CAS -- A SHARED MUTABLE STAGING SLOT WITH NO OWNERSHIP IS A RACE THAT SILENTLY SHIPS THE WRONG 1600 // SOURCE. `<key>.pack.new` is ONE global slot, so a sibling's in-flight /api/upload can replace the blob 1601 // between MY upload and MY unpack -- and this route used to apply whatever happened to be sitting there. 1602 // MEASURED cross-session source contamination: a 14,334B pack was staged, 114,316B was on disk, and 11 1603 // files from a FOREIGN pack were written into buildroot/runtime. The caller must now NAME THE BYTES IT 1604 // STAGED; we hash the slot and refuse on mismatch. This is the SAME guard already proven live on 1605 // /api/compare/publish -- it existed on a STATIC PAGE and not on the SOURCE TREE (an adoption gap, not 1606 // missing code). It also subsumes a declared-file-count check: bytes that hash equal cannot contain a 1607 // different file count. Fail-closed: no sha256 -> 400, nothing unpacked. 1608 let hoff: *i64 = sys_mmap(8) as *i64 1609 let hn: *i64 = sys_mmap(8) as *i64 1610 if sd_form_field(body, body_n, "sha256" as *u8, 6, hoff, hn) != 1 { 1611 return ma_emit_400(out, "{\"error\":\"missing sha256 (hex of the pack bytes you uploaded -- pins the unpack to YOUR source; a shared staging slot without it can apply another session tree)\"}" as *u8) 1612 } 1613 let szp: *i64 = sys_mmap(16) as *i64 1614 let stg: *u8 = md_read_file(packbuf, szp) 1615 if (stg as i64) == 0 { 1616 return ma_emit_400(out, "{\"error\":\"nothing staged; chunk-upload to /api/upload?target=buildsrc.pack first\"}" as *u8) 1617 } 1618 let dig: *u8 = sys_mmap(32) 1619 sha256_digest(stg, szp[0], dig) 1620 let hexbuf: *u8 = sys_mmap(72) 1621 mau_hex32(dig, hexbuf) 1622 if mau_hex_eq(body, hoff[0], hn[0], hexbuf) != 1 { 1623 return ma_emit_400(out, "{\"error\":\"sha256 mismatch: the staging slot holds different bytes than you staged (another session re-staged it) -- REFUSING to unpack a tree you did not review; re-upload and retry\"}" as *u8) 1624 } 1625 md_exec_treepack(packbuf, destbuf, "/tmp/nx_ma_unpack.out" as *u8) 1626 let pbuf: *u8 = sys_mmap(16384) 1627 let pn: i64 = dp_read("/tmp/nx_ma_unpack.out" as *u8, pbuf, 16380) 1628 let files: i64 = hh_after(pbuf, pn, "files=" as *u8) 1629 let failed: i64 = hh_after(pbuf, pn, "failed=" as *u8) 1630 if failed == 0 { if files > 0 { 1631 let rb: *u8 = sys_mmap(256) 1632 var b: i64 = sd_cat(rb, 0, "{\"action\":\"UNPACKED\",\"files\":" as *u8) 1633 b = sd_catn(rb, b, files) 1634 b = sd_cat(rb, b, ",\"failed\":0}" as *u8) 1635 rb[b] = 0 as u8 1636 return ma_emit_200(out, rb) 1637 } } 1638 return ma_emit_200(out, "{\"action\":\"UNPACK-FAILED\",\"verdict\":\"nx_treepack reported failures or zero files\"}" as *u8) 1639} 1640 1641// POST /api/put_source?name=<name> {body = raw .nx source text}: WRITE ONE source file to buildroot/runtime/<name>.nx 1642// over the pure API. NishiLang source is TEXT, so it rides the request body verbatim -- no pack, no binary upload, 1643// no WSL. Then /api/build compiles it -> a brand-new organ goes source->binary with ZERO shell. Fail-closed: name 1644// sanitized to [a-zA-Z0-9_] (no dot/slash/dot-dot -> no path escape); writes EXACTLY runtime/<name>.nx (O_TRUNC). 1645// Additive/never-brick: only overwrites that one SOURCE file (never a live artifact); the compiler is the next step. 1646func ma_gate_esc(d: *u8, o: i64, s: *u8, n: i64) -> i64 { 1647 var i: i64 = 0 1648 var b: i64 = o 1649 while i < n { 1650 let c: i64 = (s[i] as i64) & 0xff 1651 var hit: i64 = 0 1652 if c == 34 { d[b] = 92 as u8; b = b + 1; d[b] = 34 as u8; b = b + 1; hit = 1 } 1653 if c == 92 { d[b] = 92 as u8; b = b + 1; d[b] = 92 as u8; b = b + 1; hit = 1 } 1654 if c == 10 { d[b] = 92 as u8; b = b + 1; d[b] = 110 as u8; b = b + 1; hit = 1 } 1655 if c == 13 { d[b] = 92 as u8; b = b + 1; d[b] = 114 as u8; b = b + 1; hit = 1 } 1656 if c == 9 { d[b] = 92 as u8; b = b + 1; d[b] = 116 as u8; b = b + 1; hit = 1 } 1657 if hit == 0 { if c >= 32 { d[b] = s[i]; b = b + 1 } } 1658 i = i + 1 1659 } 1660 return b 1661} 1662 1663// ---- /api/organ_run: run an ALREADY-VETTED organ over the control plane (seq1426) ------------------ 1664// THE GAP IT CLOSES. The ship loop is API-pure end to end -- build, promote, register, mint, gate_run -- 1665// except for the last step: actually RUNNING what you just shipped. /api/gate_run deliberately executes 1666// VERIFIERS ONLY (name must end gate/test/kat) and that bound must stay, and a freshly registered tool 1667// has no mcp__nishi__ stub until a FULL client restart. So a brand-new organ was unreachable in-session 1668// and every session fell back to ssh -- measured at 33-44% of all tool calls, the single largest 1669// self-sufficiency leak in the ecosystem. 1670// 1671// AUTHORITY: this grants NOTHING NEW. tool_allowlist.conf is the set an operator has already vetted as 1672// callable-over-MCP (field 0 = name, field 1 = absolute elf, field 2 = GREEN). /mcp tools/call already 1673// executes exactly that set. This route is a SECOND PRESENTER of the SAME authority, the way 1674// Authorization: Bearer is a second presenter of the X-Nishi-Cap token -- it changes WHICH DOOR the 1675// request arrives at, never WHAT MAY BE RUN. An organ absent from the allowlist, or not GREEN, is 1676// refused here exactly as it is there. 1677// 1678// NEVER-BRICK: the elf path comes from the ALLOWLIST ROW, never from the caller, so no argument can 1679// redirect execution; the run is deadline-bounded and process-group reaped (dep_run_capture_bounded); 1680// and callers cannot reach a daemon or deployer unless an operator deliberately vetted it GREEN, which 1681// is the same decision that already exposes it over /mcp. 1682const MA_ORGAN_ALLOWLIST: *u8 = "tool_allowlist.conf" as *u8 1683const MA_ORGAN_CONFCAP: i64 = 262144 1684const MA_ORGAN_MAXARGS: i64 = 12 1685 1686// Resolve `nm` to its allowlisted absolute elf path. 1 = found + GREEN (path written to outp), 1687// 0 = absent or not GREEN. Fail-closed: an unreadable/truncated conf resolves NOTHING. 1688func ma_organ_resolve(nm: *u8, outp: *u8) -> i64 { 1689 let buf: *u8 = sys_mmap(MA_ORGAN_CONFCAP) 1690 let n: i64 = dp_read(MA_ORGAN_ALLOWLIST, buf, MA_ORGAN_CONFCAP - 1) 1691 if n <= 0 { return 0 } 1692 var nl: i64 = 0 1693 while nm[nl] != (0 as u8) { nl = nl + 1 } 1694 var ls: i64 = 0 1695 var i: i64 = 0 1696 while i <= n { 1697 var eol: i64 = 0 1698 if i == n { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } } 1699 if eol == 1 { 1700 if i > ls { if buf[ls] != (35 as u8) { 1701 // field 0 = name, up to the first TAB 1702 var t0: i64 = ls 1703 while t0 < i { if buf[t0] == (9 as u8) { t0 = i + 1 } else { t0 = t0 + 1 } } 1704 var tab1: i64 = ls 1705 var found1: i64 = 0 - 1 1706 while tab1 < i { if buf[tab1] == (9 as u8) { found1 = tab1; tab1 = i } else { tab1 = tab1 + 1 } } 1707 if found1 > 0 { if found1 - ls == nl { 1708 var m: i64 = 1 1709 var c: i64 = 0 1710 while c < nl { if buf[ls+c] != nm[c] { m = 0; c = nl } else { c = c + 1 } } 1711 if m == 1 { 1712 // field 1 = elf path, field 2 = status 1713 var p2: i64 = found1 + 1 1714 var found2: i64 = 0 - 1 1715 var q: i64 = p2 1716 while q < i { if buf[q] == (9 as u8) { found2 = q; q = i } else { q = q + 1 } } 1717 if found2 > 0 { 1718 // status must begin GREEN 1719 var s3: i64 = found2 + 1 1720 var green: i64 = 0 1721 if s3 + 5 <= i { 1722 if buf[s3] == (71 as u8) { if buf[s3+1] == (82 as u8) { if buf[s3+2] == (69 as u8) { 1723 if buf[s3+3] == (69 as u8) { if buf[s3+4] == (78 as u8) { green = 1 } } } } } 1724 } 1725 if green == 1 { 1726 var o: i64 = 0 1727 var k: i64 = p2 1728 while k < found2 { outp[o] = buf[k]; o = o + 1; k = k + 1 } 1729 outp[o] = 0 as u8 1730 if o > 0 { return 1 } 1731 } 1732 return 0 1733 } 1734 } 1735 } } 1736 } } 1737 ls = i + 1 1738 } 1739 i = i + 1 1740 } 1741 return 0 1742} 1743 1744func ma_do_organ_run(req: *u8, req_n: i64, out: *u8) -> i64 { 1745 let body_off: i64 = sd_body_off(req, req_n) 1746 let body: *u8 = ((req as i64) + body_off) as *u8 1747 let body_n: i64 = req_n - body_off 1748 let toff: *i64 = sys_mmap(8) as *i64 1749 let tn: *i64 = sys_mmap(8) as *i64 1750 if sd_form_field(body, body_n, "target" as *u8, 6, toff, tn) != 1 { 1751 return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) 1752 } 1753 let nm: *u8 = sys_mmap(128) 1754 if ma_sanitize_name(body, toff[0], tn[0], nm, 120) != 1 { 1755 return ma_emit_400(out, "{\"error\":\"invalid target name (only [a-zA-Z0-9_])\"}" as *u8) 1756 } 1757 if ma_confirmed(req, req_n) == 0 { 1758 return ma_emit_400(out, "{\"error\":\"organ_run requires confirm=yes\"}" as *u8) 1759 } 1760 let ep: *u8 = sys_mmap(512) 1761 if ma_organ_resolve(nm, ep) != 1 { 1762 return ma_emit_400(out, "{\"error\":\"target refused: not a GREEN row in tool_allowlist.conf. This route runs only what an operator has ALREADY vetted as callable over /mcp -- it is a second door onto that same set, never a wider one. Register the organ first (POST /api/tools/register).\"}" as *u8) 1763 } 1764 var dl: i64 = 12000 1765 let doff: *i64 = sys_mmap(8) as *i64 1766 let dn: *i64 = sys_mmap(8) as *i64 1767 if sd_form_field(body, body_n, "deadline_ms" as *u8, 11, doff, dn) == 1 { 1768 let v: i64 = mau_qint(body, doff[0], dn[0]) 1769 if v > 0 { dl = v } 1770 } 1771 if dl < 1000 { dl = 1000 } 1772 if dl > 300000 { dl = 300000 } 1773 // args: space-separated, each sanitized to [A-Za-z0-9_./-]. The ELF PATH is never taken from the 1774 // caller (it comes from the allowlist row), so args can only ever be argv[1..]. 1775 let argbuf: *u8 = sys_mmap(4096) 1776 var nargs: i64 = 0 1777 let argv: *i64 = sys_mmap(8 * (MA_ORGAN_MAXARGS + 2)) as *i64 1778 let aoff: *i64 = sys_mmap(8) as *i64 1779 let an: *i64 = sys_mmap(8) as *i64 1780 if sd_form_field(body, body_n, "args" as *u8, 4, aoff, an) == 1 { 1781 var w: i64 = 0 1782 var st: i64 = 0 1783 var started: i64 = 0 1784 var k: i64 = 0 1785 while k <= an[0] { 1786 var brk: i64 = 0 1787 if k == an[0] { brk = 1 } else { 1788 let ch: i64 = body[aoff[0] + k] as i64 1789 if ch == 32 { brk = 1 } 1790 if ch == 43 { brk = 1 } 1791 } 1792 if brk == 1 { 1793 if started == 1 { 1794 argbuf[w] = 0 as u8 1795 w = w + 1 1796 started = 0 1797 if nargs < MA_ORGAN_MAXARGS { argv[nargs] = (argbuf as i64) + st; nargs = nargs + 1 } // dep_run_capture_bounded supplies argv[0]=elf itself, so these are argv[1..] already 1798 } 1799 } else { 1800 let ch2: i64 = body[aoff[0] + k] as i64 1801 var okc: i64 = 0 1802 if ch2 >= 48 { if ch2 <= 57 { okc = 1 } } 1803 if ch2 >= 65 { if ch2 <= 90 { okc = 1 } } 1804 if ch2 >= 97 { if ch2 <= 122 { okc = 1 } } 1805 if ch2 == 95 { okc = 1 } 1806 if ch2 == 46 { okc = 1 } 1807 if ch2 == 47 { okc = 1 } 1808 if ch2 == 45 { okc = 1 } 1809 if okc == 1 { 1810 if started == 0 { st = w; started = 1 } 1811 if w < 4000 { argbuf[w] = ch2 as u8; w = w + 1 } 1812 } 1813 } 1814 k = k + 1 1815 } 1816 } 1817 argv[nargs] = 0 1818 1819 let rc: i64 = dep_run_capture_bounded(ep, argv, nargs, "/tmp/nx_ma_organrun.out" as *u8, dl) 1820 let cbuf: *u8 = sys_mmap(65536) 1821 let cn: i64 = dp_read("/tmp/nx_ma_organrun.out" as *u8, cbuf, 16384) 1822 let rb: *u8 = sys_mmap(65536) 1823 var b: i64 = sd_cat(rb, 0, "{\"action\":\"ORGAN-RUN\",\"target\":\"" as *u8) 1824 b = sd_cat(rb, b, nm) 1825 b = sd_cat(rb, b, "\",\"elf\":\"" as *u8) 1826 b = sd_cat(rb, b, ep) 1827 b = sd_cat(rb, b, "\",\"argc\":" as *u8) 1828 b = sd_catn(rb, b, nargs) 1829 b = sd_cat(rb, b, ",\"exit_code\":" as *u8) 1830 if rc < 0 { b = sd_cat(rb, b, "-1" as *u8) } 1831 if rc >= 0 { b = sd_catn(rb, b, rc) } 1832 b = sd_cat(rb, b, ",\"deadline_ms\":" as *u8) 1833 b = sd_catn(rb, b, dl) 1834 b = sd_cat(rb, b, ",\"verdict\":\"" as *u8) 1835 if rc == 0 { b = sd_cat(rb, b, "OK" as *u8) } 1836 if rc == 127 { b = sd_cat(rb, b, "NOT-FOUND" as *u8) } 1837 if rc < 0 { b = sd_cat(rb, b, "TIMEOUT-OR-SIGNAL" as *u8) } 1838 if rc > 0 { if rc != 127 { b = sd_cat(rb, b, "NONZERO" as *u8) } } 1839 b = sd_cat(rb, b, "\",\"bytes\":" as *u8) 1840 b = sd_catn(rb, b, cn) 1841 b = sd_cat(rb, b, ",\"output\":\"" as *u8) 1842 b = ma_gate_esc(rb, b, cbuf, cn) 1843 b = sd_cat(rb, b, "\"}" as *u8) 1844 rb[b] = 0 as u8 1845 return ma_emit_200(out, rb) 1846} 1847 1848// POST /api/gate_run {target=<name>[&deadline_ms=<n>]} (seq1349): run a promoted gate/test ELF, return its 1849// transcript + exit code + verdict, so a session can PROVE what it just built with zero ssh. Verdict comes ONLY 1850// from the exit code -- a second, weaker judge is how a false GREEN gets manufactured (seq585). Wait is BOUNDED 1851// (seq1383): an unbounded wait in a request handler is a DoS on the whole daemon by construction; it took mgmt 1852// down once. Timeout returns NAME which half failed (seq1425) instead of one opaque code. 1853func ma_do_gate_run(req: *u8, req_n: i64, out: *u8) -> i64 { 1854 let body_off: i64 = sd_body_off(req, req_n) 1855 let body: *u8 = ((req as i64) + body_off) as *u8 1856 let body_n: i64 = req_n - body_off 1857 let toff: *i64 = sys_mmap(8) as *i64 1858 let tn: *i64 = sys_mmap(8) as *i64 1859 if sd_form_field(body, body_n, "target" as *u8, 6, toff, tn) != 1 { 1860 return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) 1861 } 1862 let nm: *u8 = sys_mmap(128) 1863 if ma_sanitize_name(body, toff[0], tn[0], nm, 120) != 1 { 1864 return ma_emit_400(out, "{\"error\":\"invalid target name (only [a-zA-Z0-9_])\"}" as *u8) 1865 } 1866 if md_gate_name_ok(nm) != 1 { 1867 return ma_emit_400(out, "{\"error\":\"target refused: /api/gate_run executes VERIFIERS ONLY -- the name must end in gate, test or kat. That bound is what makes this route never-brick: it cannot reach a daemon, promoter or deployer.\"}" as *u8) 1868 } 1869 var dl: i64 = 12000 1870 let doff: *i64 = sys_mmap(8) as *i64 1871 let dn: *i64 = sys_mmap(8) as *i64 1872 if sd_form_field(body, body_n, "deadline_ms" as *u8, 11, doff, dn) == 1 { 1873 let v: i64 = mau_qint(body, doff[0], dn[0]) 1874 if v > 0 { dl = v } 1875 } 1876 if dl < 1000 { dl = 1000 } 1877 if dl > 300000 { dl = 300000 } 1878 let ep: *u8 = sys_mmap(256) 1879 var eo: i64 = sd_cat(ep, 0, "/volume1/homes/elderwesto/nishihost/" as *u8) 1880 eo = sd_cat(ep, eo, nm) 1881 eo = sd_cat(ep, eo, ".elf" as *u8) 1882 ep[eo] = 0 as u8 1883 let cbuf: *u8 = sys_mmap(65536) 1884 let clen: *i64 = sys_mmap(16) as *i64 1885 clen[0] = 0 1886 let rc: i64 = md_exec_gate_capture(ep, cbuf, 16384, clen, dl) 1887 let cn: i64 = clen[0] 1888 let rb: *u8 = sys_mmap(65536) 1889 var b: i64 = sd_cat(rb, 0, "{\"action\":\"GATE-RUN\",\"target\":\"" as *u8) 1890 b = sd_cat(rb, b, nm) 1891 b = sd_cat(rb, b, "\",\"exit_code\":" as *u8) 1892 if rc < 0 { b = sd_cat(rb, b, "-1" as *u8) } 1893 if rc >= 0 { b = sd_catn(rb, b, rc) } 1894 b = sd_cat(rb, b, ",\"deadline_ms\":" as *u8) 1895 b = sd_catn(rb, b, dl) 1896 b = sd_cat(rb, b, ",\"verdict\":\"" as *u8) 1897 if rc == 0 { b = sd_cat(rb, b, "GREEN" as *u8) } 1898 if rc == 127 { b = sd_cat(rb, b, "NOT-FOUND" as *u8) } 1899 if rc == (0 - 1) { b = sd_cat(rb, b, "SIGNALLED" as *u8) } 1900 if rc == (0 - 5) { b = sd_cat(rb, b, "TIMEOUT" as *u8) } // TR_ERR_TIMEOUT: watchdog fired, worker SIGKILLed, no leak (gate T5) 1901 if rc == (0 - 2) { b = sd_cat(rb, b, "HARNESS-PIPE-FAIL" as *u8) } 1902 if rc == (0 - 3) { b = sd_cat(rb, b, "HARNESS-FORK-FAIL" as *u8) } 1903 if rc == (0 - 4) { b = sd_cat(rb, b, "HARNESS-WAIT-FAIL" as *u8) } 1904 if rc > 0 { if rc != 127 { b = sd_cat(rb, b, "RED" as *u8) } } 1905 b = sd_cat(rb, b, "\",\"bytes\":" as *u8) 1906 b = sd_catn(rb, b, cn) 1907 b = sd_cat(rb, b, ",\"output\":\"" as *u8) 1908 b = ma_gate_esc(rb, b, cbuf, cn) 1909 b = sd_cat(rb, b, "\"" as *u8) 1910 b = sd_cat(rb, b, "}" as *u8) 1911 rb[b] = 0 as u8 1912 return ma_emit_200(out, rb) 1913} 1914 1915// POST /api/proc_kill {match=<needle>&confirm=yes} (seq1383): the verb whose absence forced ssh. Bound in 1916// md_proc_kill_needle_ok, enforced in code. killed:0 is reported honestly as a MISS, never as success. 1917func ma_do_proc_kill(req: *u8, req_n: i64, out: *u8) -> i64 { 1918 let body_off: i64 = sd_body_off(req, req_n) 1919 let body: *u8 = ((req as i64) + body_off) as *u8 1920 let body_n: i64 = req_n - body_off 1921 let moff: *i64 = sys_mmap(8) as *i64 1922 let mn: *i64 = sys_mmap(8) as *i64 1923 if sd_form_field(body, body_n, "match" as *u8, 5, moff, mn) != 1 { 1924 return ma_emit_400(out, "{\"error\":\"missing match (the cmdline needle, e.g. nx_tools_api_serve.elf.new)\"}" as *u8) 1925 } 1926 if ma_confirmed(req, req_n) == 0 { 1927 return ma_emit_400(out, "{\"error\":\"proc_kill requires confirm=yes\"}" as *u8) 1928 } 1929 if mn[0] <= 0 { return ma_emit_400(out, "{\"error\":\"empty match\"}" as *u8) } 1930 if mn[0] > 120 { return ma_emit_400(out, "{\"error\":\"match too long\"}" as *u8) } 1931 let nd: *u8 = sys_mmap(256) 1932 var i: i64 = 0 1933 while i < mn[0] { nd[i] = body[moff[0] + i]; i = i + 1 } 1934 nd[mn[0]] = 0 as u8 1935 if md_proc_kill_needle_ok(nd) != 1 { 1936 return ma_emit_400(out, "{\"error\":\"match refused: proc_kill targets OUR OWN organs only -- the needle must be >=6 chars, must contain .elf, and must not reach the supervisor (nx_hostctl/supervise). Killing the guard would stop every respawn in the ecosystem.\"}" as *u8) 1937 } 1938 let killed: i64 = md_kill_by_name(nd) 1939 let rb: *u8 = sys_mmap(1024) 1940 var b: i64 = sd_cat(rb, 0, "{\"action\":\"PROC-KILL\",\"match\":\"" as *u8) 1941 b = ma_gate_esc(rb, b, nd, mn[0]) 1942 b = sd_cat(rb, b, "\",\"killed\":" as *u8) 1943 b = sd_catn(rb, b, killed) 1944 b = sd_cat(rb, b, ",\"note\":\"guard-supervised daemons respawn from the on-disk binary; killed:0 means NOTHING matched, not success\"}" as *u8) 1945 rb[b] = 0 as u8 1946 return ma_emit_200(out, rb) 1947} 1948 1949func ma_do_put_source(req: *u8, req_n: i64, out: *u8) -> i64 { 1950 let poff: *i64 = sys_mmap(8) as *i64 1951 let plen: *i64 = sys_mmap(8) as *i64 1952 poff[0] = 0 1953 plen[0] = 0 1954 sd_find_path(req, req_n, poff, plen) 1955 let path: *u8 = ((req as i64) + poff[0]) as *u8 1956 let pn: i64 = plen[0] 1957 let qoff: i64 = mau_query_off(path, pn) 1958 if qoff < 0 { return ma_emit_400(out, "{\"error\":\"missing query param name\"}" as *u8) } 1959 let noff: *i64 = sys_mmap(8) as *i64 1960 let nlen: *i64 = sys_mmap(8) as *i64 1961 if mau_qparam(path, pn, qoff, "name" as *u8, 4, noff, nlen) != 1 { 1962 return ma_emit_400(out, "{\"error\":\"missing name\"}" as *u8) 1963 } 1964 let nm: *u8 = sys_mmap(128) 1965 if ma_sanitize_name(path, noff[0], nlen[0], nm, 120) != 1 { 1966 return ma_emit_400(out, "{\"error\":\"invalid name (only [a-zA-Z0-9_])\"}" as *u8) 1967 } 1968 let fp: *u8 = sys_mmap(256) 1969 var fo: i64 = sd_cat(fp, 0, "runtime/" as *u8) 1970 fo = sd_cat(fp, fo, nm) 1971 fo = sd_cat(fp, fo, ".nx" as *u8) 1972 fp[fo] = 0 as u8 1973 let body_off: i64 = sd_body_off(req, req_n) 1974 let body: *u8 = ((req as i64) + body_off) as *u8 1975 let body_n: i64 = req_n - body_off 1976 if body_n <= 0 { return ma_emit_400(out, "{\"error\":\"empty body (POST the raw .nx source)\"}" as *u8) } 1977 let fd: i64 = sys_openat_wr(fp, 0x1a4) 1978 if fd < 0 { return ma_emit_400(out, "{\"error\":\"cannot open source file for write\"}" as *u8) } 1979 var off: i64 = 0 1980 while off < body_n { 1981 let w: i64 = sys_write(fd, ((body as i64) + off) as *u8, body_n - off) 1982 if w <= 0 { sys_close(fd); return ma_emit_400(out, "{\"error\":\"write failed\"}" as *u8) } 1983 off = off + w 1984 } 1985 sys_close(fd) 1986 let rb: *u8 = sys_mmap(256) 1987 var b: i64 = sd_cat(rb, 0, "{\"action\":\"WROTE\",\"file\":\"runtime/" as *u8) 1988 b = sd_cat(rb, b, nm) 1989 b = sd_cat(rb, b, ".nx\",\"bytes\":" as *u8) 1990 b = sd_catn(rb, b, body_n) 1991 b = sd_cat(rb, b, "}" as *u8) 1992 rb[b] = 0 as u8 1993 return ma_emit_200(out, rb) 1994} 1995 1996func ma_do_deploy(req: *u8, req_n: i64, out: *u8) -> i64 { 1997 let body_off: i64 = sd_body_off(req, req_n) 1998 let body: *u8 = ((req as i64) + body_off) as *u8 1999 let body_n: i64 = req_n - body_off 2000 let toff: *i64 = sys_mmap(8) as *i64 2001 let tn: *i64 = sys_mmap(8) as *i64 2002 if sd_form_field(body, body_n, "target" as *u8, 6, toff, tn) != 1 { 2003 return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) 2004 } 2005 let kindb: *i64 = sys_mmap(8) as *i64 2006 let srcbuf: *u8 = sys_mmap(512) 2007 let subbuf: *u8 = sys_mmap(64) 2008 let urlbuf: *u8 = sys_mmap(256) 2009 let rbbuf: *u8 = sys_mmap(64) // per-target rollback sub (generalized deploy; legacy rows default to "rollback") 2010 if md_resolve_target("knowledge/hosting/deploy_targets.conf" as *u8, body, toff[0], tn[0], kindb, srcbuf, subbuf, urlbuf, rbbuf) != 1 { 2011 return ma_emit_400(out, "{\"error\":\"unknown target (not in deploy_targets.conf allowlist)\"}" as *u8) 2012 } 2013 // Derive the build-path name when the row's artifact is absent. ⚠THE SUFFIX IS NOT ALWAYS ".new": rows 2014 // name EITHER "<t>.new" (hostctl) OR "<t>.elf.new" (mgmtapi, relate, opaquelogin...). Stripping a fixed 4 2015 // chars turned "nx_mgmt_api.elf.new" into "nx_mgmt_api.elf.sov.elf.new" -- a name that exists nowhere, so 2016 // the fallback silently did nothing for every .elf.new row. It LOOKED correct only because a stale 2017 // .elf.new was still on disk; consuming that leftover exposed the flaw immediately. Strip ".elf.new" 2018 // when present, else ".new". 2019 var valid: i64 = md_validate_artifact(srcbuf, kindb[0]); if valid == 0 { let sl: i64 = md_len(srcbuf); var cut: i64 = 4; if sl > 8 { if (srcbuf[sl-8] as i64) == 46 { if (srcbuf[sl-7] as i64) == 101 { if (srcbuf[sl-6] as i64) == 108 { if (srcbuf[sl-5] as i64) == 102 { cut = 8 } } } } } if sl > cut { let altp: *u8 = sys_mmap(512); md_copy_slice_z(altp, srcbuf, 0, sl - cut, 512); let ao: i64 = sd_cat(altp, sl - cut, ".sov.elf.new" as *u8); altp[ao] = 0 as u8; if md_validate_artifact(altp, kindb[0]) == 1 { md_copy_slice_z(srcbuf, altp, 0, ao, 512); valid = 1 } } } 2020 if valid == 0 { 2021 // honest ops: "no staged artifact" and "staged artifact failed validation" are different failures 2022 let pfd: i64 = sys_openat_rd(srcbuf) 2023 if pfd < 0 { return ma_emit_200(out, "{\"action\":\"ABORT\",\"verdict\":\"nothing staged for this target (chunk-upload to /api/upload first); NOTHING promoted, site untouched\"}" as *u8) } 2024 sys_close(pfd) 2025 return ma_emit_200(out, "{\"action\":\"ABORT\",\"verdict\":\"staged artifact failed validation (bad ELF/too small); NOTHING promoted, site untouched\"}" as *u8) 2026 } 2027 // ---- ROUTE-SUPERSET GUARD (id=1785447778, the 5th mgmt route regression -- one of them mine) ------- 2028 // A deploy that DELETES live API surface is always wrong, whatever the host load. I removed 2029 // /api/gate_run + /api/proc_kill by promoting a 528323-byte artifact over a 575195-byte one; the 2030 // byte DECREASE was visible and nothing checked it. nx_route_diff already printed the right words 2031 // ('route(s) vanished = deploy contract regression') and was never wired into this path. 2032 // FAIL-OPEN BY CONSTRUCTION, and that is the whole safety argument: only a POSITIVE detection of a 2033 // vanished route refuses. Unreadable live artifact, unreadable candidate, zero routes extracted -- 2034 // every one of those PROCEEDS. A buggy guard here could otherwise refuse every future deploy 2035 // INCLUDING ITS OWN FIX, which is exactly the deadlock that made load-gating deploys the wrong 2036 // idea (id=1785450386). Deliberate surface removal stays possible via confirm_route_loss=yes. 2037 let rg_live: *u8 = sys_mmap(512) 2038 let rg_sl: i64 = md_len(srcbuf) 2039 if rg_sl > 4 { 2040 md_copy_slice_z(rg_live, srcbuf, 0, rg_sl - 4, 512) // strip the trailing ".new" 2041 let rg_lb: *u8 = sys_mmap(RG_BUF) 2042 let rg_ln: i64 = rg_read(rg_live, rg_lb, RG_BUF) 2043 if rg_ln > 0 { 2044 let rg_cb: *u8 = sys_mmap(RG_BUF) 2045 let rg_cn: i64 = rg_read(srcbuf, rg_cb, RG_BUF) 2046 if rg_cn > 0 { 2047 let rg_nm: *u8 = sys_mmap(RG_MAXR * RG_NAMEMAX) 2048 let rg_ls: *i64 = sys_mmap(RG_MAXR * 8) as *i64 2049 let rg_tr: *i64 = sys_mmap(16) as *i64 2050 let rg_ms: *i64 = sys_mmap(RG_MAXR * 8) as *i64 2051 let rg_rc: i64 = rg_extract(rg_lb, rg_ln, rg_nm, rg_ls, rg_tr) 2052 if rg_rc > 0 { 2053 let rg_miss: i64 = rg_missing(rg_nm, rg_ls, rg_rc, rg_cb, rg_cn, rg_ms, RG_MAXR) 2054 if rg_miss > 0 { 2055 let rg_co: *i64 = sys_mmap(8) as *i64 2056 let rg_cl: *i64 = sys_mmap(8) as *i64 2057 var rg_ok: i64 = 0 2058 if sd_form_field(body, body_n, "confirm_route_loss" as *u8, 18, rg_co, rg_cl) == 1 { 2059 if md_slice_eq(body, rg_co[0], rg_cl[0], "yes" as *u8, 0, 3) == 1 { rg_ok = 1 } 2060 } 2061 if rg_ok == 0 { 2062 let rgb: *u8 = sys_mmap(4096) 2063 var rgo: i64 = sd_cat(rgb, 0, "{\"action\":\"ABORT\",\"verdict\":\"DEPLOY CONTRACT REGRESSION -- the staged artifact DROPS " as *u8) 2064 rgo = sd_catn(rgb, rgo, rg_miss) 2065 rgo = sd_cat(rgb, rgo, " of " as *u8) 2066 rgo = sd_catn(rgb, rgo, rg_rc) 2067 rgo = sd_cat(rgb, rgo, " /api routes the LIVE binary serves. Promoting it would DELETE working API surface -- this is how /api/gate_run and /api/proc_kill vanished five times. NOTHING promoted, live untouched. Rebuild the candidate from a source tree that still carries those routes (a byte-count DECREASE after an addition is a revert), or pass confirm_route_loss=yes if the removal is DELIBERATE.\",\"routes_live\":" as *u8) 2068 rgo = sd_catn(rgb, rgo, rg_rc) 2069 rgo = sd_cat(rgb, rgo, ",\"routes_missing\":" as *u8) 2070 rgo = sd_catn(rgb, rgo, rg_miss) 2071 rgo = sd_cat(rgb, rgo, ",\"detector\":\"nx_routeguard\"}" as *u8) 2072 rgb[rgo] = 0 as u8 2073 return ma_emit_200(out, rgb) 2074 } 2075 } 2076 } 2077 } 2078 } 2079 } 2080 2081 // ---- PRE-DEPLOY GATE, NOW ACTUALLY PRE-DEPLOY AND ACTUALLY A GATE (seq1798) ---------------------------- 2082 // WHAT WAS WRONG, AND IT WAS WORSE THAN 'ADVISORY': this check used to run AFTER md_exec_hostctl had 2083 // ALREADY PROMOTED the artifact, so it was a POST-DEPLOY REPORT WEARING A PRE-DEPLOY LABEL. The old 2084 // comment there claimed flipping it to a hard refusal was 'a ONE-LINE change here' -- it was not, because 2085 // by that point the irreversible act had happened and a refusal could only ever describe something that 2086 // was already live. Evaluating it HERE is what makes refusal mean anything at all. 2087 // PLACED BEFORE THE LEASE ON PURPOSE: a refusal returns early, and acquiring the lease first would STRAND 2088 // the lane until its TTL expired -- the same reasoning the lease block below already states for itself. 2089 // POLICY (operator decision, 2026-07-31): a BLOCK-severity blocker REFUSES. The escape hatch is 2090 // confirm_gate_override=yes -- deliberate, named, and echoed in the response, exactly the shape the 2091 // routeguard above uses for confirm_route_loss. An advisory BLOCK is a disabled gate arrived at SILENTLY; 2092 // an override is a disabled gate arrived at DELIBERATELY, and only the second one is honest. 2093 // UNAVAILABLE STAYS FAIL-OPEN BY DESIGN: if nx_deploy_ready cannot be read we do NOT wedge every seat on 2094 // a broken instrument. A gate that cannot be satisfied produces a bypass, not safety. 2095 var pdg: i64 = 0 - 1 2096 let pdrc: i64 = md_exec_deploy_ready() 2097 if pdrc >= 0 { 2098 let pdsz: *i64 = sys_mmap(16) as *i64 2099 let pdbuf: *u8 = md_read_file("/tmp/nx_ma_deploy_ready.out" as *u8, pdsz) 2100 if (pdbuf as i64) != 0 { pdg = hh_after(pdbuf, pdsz[0], "\"blockers\":" as *u8) } 2101 } 2102 var pdg_ovr: i64 = 0 2103 let pgo: *i64 = sys_mmap(8) as *i64 2104 let pgl: *i64 = sys_mmap(8) as *i64 2105 if sd_form_field(body, body_n, "confirm_gate_override" as *u8, 21, pgo, pgl) == 1 { 2106 if md_slice_eq(body, pgo[0], pgl[0], "yes" as *u8, 0, 3) == 1 { pdg_ovr = 1 } 2107 } 2108 if pdg > 0 { if pdg_ovr == 0 { 2109 let pgb: *u8 = sys_mmap(4096) 2110 var pgq: i64 = sd_cat(pgb, 0, "{\"action\":\"REFUSED\",\"verdict\":\"PRE-DEPLOY GATE BLOCKED -- an INDEPENDENT METHOD IS REPORTING FAILURE, so nothing ships. NOTHING promoted, live untouched. Run nx_deploy_ready check to see the blocker and its remediation (today: evidence-honesty -- nx_sota_status names the domain, then nx_swcompare_evidence <domain> re-measures it). If you must ship before that is green, pass confirm_gate_override=yes: it proceeds AND is reported as an AUDITED override, never a silent bypass.\",\"pre_deploy_gate\":\"DEPLOY-BLOCKED\",\"gate_blockers\":" as *u8) 2111 pgq = sd_catn(pgb, pgq, pdg) 2112 pgq = sd_cat(pgb, pgq, ",\"gate_exit\":" as *u8) 2113 pgq = sd_catn(pgb, pgq, pdrc) 2114 pgq = sd_cat(pgb, pgq, ",\"override\":\"confirm_gate_override=yes\",\"detector\":\"nx_deploy_ready\"}" as *u8) 2115 pgb[pgq] = 0 as u8 2116 return ma_emit_400(out, pgb) 2117 } } 2118 2119 // ---- R1b (seq1531 residual): LEASE-GATE THE DEPLOY ----------------------------------------------------- 2120 // A deploy race is STRICTLY WORSE than a build race: two sessions promoting the same target can interleave 2121 // a half-swapped artifact on the LIVE plane. Same nx_lease, same exit-code contract (0=acquired, 3=BUSY), 2122 // same TTL anti-deadlock property as R1. 2123 // ACQUIRED HERE, NOT EARLIER, ON PURPOSE: every failure path above (unknown target, nothing staged, failed 2124 // validation) returns early, so acquiring before them would STRAND the lane until the TTL expired. Taking 2125 // it immediately before the promote and releasing immediately after makes the guarded region atomic and 2126 // leaves no exit that can strand it. 2127 let dnmb: *u8 = sys_mmap(192) 2128 let dlease: *u8 = sys_mmap(224) 2129 dlease[0] = 0 as u8 2130 if ma_sanitize_name(body, toff[0], tn[0], dnmb, 120) == 1 { 2131 var dlo: i64 = sd_cat(dlease, 0, "deploy-" as *u8) 2132 dlo = sd_cat(dlease, dlo, dnmb) 2133 dlease[dlo] = 0 as u8 2134 if md_lease_run("acquire" as *u8, dlease, "mgmt-api-deploy" as *u8, "600" as *u8, 4, "/tmp/nx_ma_lease.out" as *u8) == 3 { 2135 let dlb: *u8 = sys_mmap(2048) 2136 let dln: i64 = dp_read("/tmp/nx_ma_lease.out" as *u8, dlb, 900) 2137 let dcb: *u8 = sys_mmap(4096) 2138 var dco: i64 = sd_cat(dcb, 0, "{\"conflict\":\"lease-busy\",\"error\":\"another session is ALREADY DEPLOYING this target -- refusing to race the LIVE plane (seq1531 R1b). Concurrent promotes can interleave a half-swapped artifact. The holder is named below; the lease is TTL-bounded so a dead session can never hold the lane closed.\",\"holder\":\"" as *u8) 2139 dco = ma_gate_esc(dcb, dco, dlb, dln) 2140 dco = sd_cat(dcb, dco, "\",\"retry_after_s\":30}" as *u8) 2141 dcb[dco] = 0 as u8 2142 return ma_emit_400(out, dcb) 2143 } 2144 } 2145 let promote_rc: i64 = md_exec_hostctl(subbuf) 2146 if dlease[0] != (0 as u8) { md_lease_run("release" as *u8, dlease, "mgmt-api-deploy" as *u8, "0" as *u8, 3, "/tmp/nx_ma_lease.out" as *u8) } 2147 if promote_rc != 0 { 2148 ma_write_status("PROMOTE-FAILED" as *u8) 2149 return ma_emit_200(out, "{\"action\":\"PROMOTE-FAILED\",\"verdict\":\"hostctl promote returned nonzero; nothing further\"}" as *u8) 2150 } 2151 // DETACHED health+rollback watchdog: waits out a slow guard-respawn (~up to 30s) WITHOUT racing the edge- 2152 // proxy read window (the sync path made restart-targets time out / conservatively roll back). Writes the 2153 // final verdict to a status file -> poll GET /api/deploy_status. NEVER-BRICK preserved: a failed health 2154 // check still auto-rolls-back to .prev inside the watchdog. 2155 ma_write_status("RUNNING" as *u8) 2156 let dpid: i64 = sys_fork() 2157 if dpid == 0 { 2158 let dpid2: i64 = sys_fork() 2159 if dpid2 == 0 { 2160 var fdc: i64 = 3; while fdc < 256 { sys_close(fdc); fdc = fdc + 1 } // release the inherited client socket + ALL fds -> the response returns cleanly (no status=0) AND the health fetch runs on clean fds 2161 if md_health_probe(urlbuf) == 1 { ma_write_status("DEPLOYED-GREEN" as *u8) } else { md_exec_hostctl(rbbuf); ma_write_status("ROLLED-BACK" as *u8) } 2162 sys_exit(0) 2163 } 2164 sys_exit(0) 2165 } 2166 let dst: *i64 = sys_mmap(16) as *i64; sys_wait4(dpid, dst, 0) 2167 // PRE-DEPLOY GATE, now ATTACHED to every deploy verdict (2026-07-30). ADVISORY ON PURPOSE, and that is 2168 // a deliberate engineering call, not a half-measure: the live blocker (evidence-honesty) is unrelated to 2169 // any particular target, and nx_deploy_ready's own manifest says a gate that halts every deploy gets 2170 // disabled, and a disabled gate protects nothing. So step one is to make it IMPOSSIBLE TO DEPLOY BLIND -- 2171 // the verdict now rides on the response every caller already reads. Flipping it to a hard refusal is the 2172 // right end state and is a ONE-LINE change here, gated on blockers reaching 0 first. 2173 var drx: i64 = 0 - 1 2174 // seq1798: REUSE the PRE-PROMOTE evaluation. Re-forking the checker here would (a) waste a fork and 2175 // (b) risk REPORTING A DIFFERENT VERDICT than the one the gate decision was actually made on -- the 2176 // report must describe the decision that was taken, not a fresh roll of the same dice. 2177 let drc: i64 = pdrc 2178 if drc >= 0 { 2179 let dsz: *i64 = sys_mmap(16) as *i64 2180 let dbuf: *u8 = md_read_file("/tmp/nx_ma_deploy_ready.out" as *u8, dsz) 2181 if (dbuf as i64) != 0 { drx = hh_after(dbuf, dsz[0], "\"blockers\":" as *u8) } 2182 } 2183 // seq1798: 512 was sized for the old short verdicts; the OVERRIDDEN string alone is ~230B and would 2184 // have overrun it. A response buffer that silently overruns is how a truthful verdict becomes garbage. 2185 let gb: *u8 = sys_mmap(2048) 2186 var gq: i64 = sd_cat(gb, 0, "{\"action\":\"PROMOTED\",\"verdict\":\"promoted; health+auto-rollback watchdog running\",\"status\":\"GET /api/deploy_status\",\"pre_deploy_gate\":\"" as *u8) 2187 if drx < 0 { gq = sd_cat(gb, gq, "UNAVAILABLE-fail-open (nx_deploy_ready unreadable; deploy proceeded rather than wedge every seat)" as *u8) } else { if drx > 0 { gq = sd_cat(gb, gq, "DEPLOY-BLOCKED-OVERRIDDEN (blockers were present and confirm_gate_override=yes was passed -- this deploy was DELIBERATELY and AUDITABLY allowed, not silently permitted; without that flag it would have been REFUSED before the promote)" as *u8) } else { gq = sd_cat(gb, gq, "DEPLOY-SAFE" as *u8) } } 2188 gq = sd_cat(gb, gq, "\",\"gate_blockers\":" as *u8) 2189 gq = sd_catn(gb, gq, drx) 2190 // gate_exit is the gate's OWN exit status. It was hardcoded 0 for every verdict until 2026-07-30, so 2191 // seeing a 3 here is the live proof that a gate which could not signal refusal now can. 2192 gq = sd_cat(gb, gq, ",\"gate_override\":" as *u8) 2193 if pdg_ovr == 1 { gq = sd_cat(gb, gq, "\"AUDITED\"" as *u8) } else { gq = sd_cat(gb, gq, "\"none\"" as *u8) } 2194 gq = sd_cat(gb, gq, ",\"gate_exit\":" as *u8) 2195 gq = sd_catn(gb, gq, drc) 2196 gq = sd_cat(gb, gq, "}" as *u8) 2197 gb[gq] = 0 as u8 2198 return ma_emit_200(out, gb) 2199} 2200 2201// ---- /api/hostctl (P2 off-LAN parity): run ONE allowlisted hostctl action + return its captured output ---- 2202// so the operator can drive the FULL control surface (torstat/routerctl/status/kick*/trackerrefresh) from a 2203// phone. Auth = MA_LVL_ACT (checked by ma_handle). Allowlist fail-closed (md_hostctl_action_ok); single argv 2204// element to execve (no shell). Output returned as text/plain (raw hostctl dump, capped to the response buffer). 2205func ma_do_hostctl(req: *u8, req_n: i64, out: *u8) -> i64 { 2206 let body_off: i64 = sd_body_off(req, req_n) 2207 let body: *u8 = ((req as i64) + body_off) as *u8 2208 let body_n: i64 = req_n - body_off 2209 let toff: *i64 = sys_mmap(8) as *i64 2210 let tn: *i64 = sys_mmap(8) as *i64 2211 if sd_form_field(body, body_n, "sub" as *u8, 3, toff, tn) != 1 { 2212 return ma_emit_400(out, "{\"error\":\"missing sub\"}" as *u8) 2213 } 2214 let subbuf: *u8 = sys_mmap(64); md_copy_slice_z(subbuf, body, toff[0], tn[0], 64) 2215 if md_hostctl_action_ok(subbuf) != 1 { 2216 return ma_emit_400(out, "{\"error\":\"sub not in hostctl action allowlist (status|torstat|routerctl|receipts|kick*|trackerrefresh)\"}" as *u8) 2217 } 2218 md_exec_hostctl_capture(subbuf, "/tmp/nx_ma_hostctl.out" as *u8) 2219 let szp: *i64 = sys_mmap(16) as *i64 2220 let outbuf: *u8 = md_read_file("/tmp/nx_ma_hostctl.out" as *u8, szp) 2221 if (outbuf as i64) == 0 { return ma_emit_200(out, "{\"action\":\"HOSTCTL\",\"out\":\"(no output)\"}" as *u8) } 2222 var on: i64 = szp[0] 2223 if on > 60000 { on = 60000 } // cap to fit the response buffer (raw dumps stay well under) 2224 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\nContent-Length: " as *u8, outbuf, on) 2225} 2226 2227// ---- /api/promote_content: promote a STAGED static file <t>.new -> live <t> (atomic, .prev-backed) ---------- 2228// STATIC CONTENT ONLY (md_content_target_ok namespace). Never touches a service binary; a bad promote reverts 2229// itself (if live->prev succeeded but new->live failed, prev is restored) so the live file is never lost. 2230// target rides in the form body (like /api/deploy); requires confirm=yes (a promote changes the public site). 2231func ma_do_promote_content(req: *u8, req_n: i64, out: *u8) -> i64 { 2232 let body_off: i64 = sd_body_off(req, req_n) 2233 let body: *u8 = ((req as i64) + body_off) as *u8 2234 let body_n: i64 = req_n - body_off 2235 let toff: *i64 = sys_mmap(8) as *i64 2236 let tn: *i64 = sys_mmap(8) as *i64 2237 if sd_form_field(body, body_n, "target" as *u8, 6, toff, tn) != 1 { 2238 return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) 2239 } 2240 if md_content_target_ok(body, toff[0], tn[0]) != 1 { 2241 return ma_emit_400(out, "{\"error\":\"target not in the content namespace\"}" as *u8) 2242 } 2243 if ma_confirmed(req, req_n) == 0 { 2244 return ma_emit_400(out, "{\"error\":\"promote_content requires confirm=yes\"}" as *u8) 2245 } 2246 let livep: *u8 = sys_mmap(256) 2247 let newp: *u8 = sys_mmap(256) 2248 let prevp: *u8 = sys_mmap(256) 2249 // bare live path = just the target slice, NUL-terminated (no empty-literal suffix; that aliases the pool) 2250 var lo: i64 = 0 2251 while lo < tn[0] { livep[lo] = body[toff[0] + lo]; lo = lo + 1 } 2252 livep[lo] = 0 as u8 2253 mau_build_path(body, toff[0], tn[0], ".new" as *u8, newp) 2254 mau_build_path(body, toff[0], tn[0], ".prev" as *u8, prevp) 2255 // staged file must exist 2256 let nfd: i64 = sys_openat_rd(newp) 2257 if nfd < 0 { return ma_emit_400(out, "{\"error\":\"no staged .new for target (upload first)\"}" as *u8) } 2258 sys_close(nfd) 2259 // back up the live file if present (rename live -> .prev, atomically replacing any older .prev) 2260 var had_live: i64 = 0 2261 let lfd: i64 = sys_openat_rd(livep) 2262 if lfd >= 0 { sys_close(lfd); had_live = 1 } 2263 if had_live == 1 { 2264 if sys_renameat(livep, prevp) != 0 { return ma_emit_400(out, "{\"error\":\"backup rename failed; live untouched\"}" as *u8) } 2265 } 2266 // promote 2267 if sys_renameat(newp, livep) != 0 { 2268 if had_live == 1 { sys_renameat(prevp, livep) } // restore -- the live file is never lost 2269 return ma_emit_400(out, "{\"error\":\"promote rename failed; previous file restored\"}" as *u8) 2270 } 2271 let rb: *u8 = sys_mmap(320) 2272 var b: i64 = sd_cat(rb, 0, "{\"action\":\"PROMOTE_CONTENT\",\"target\":\"" as *u8) 2273 b = md_cat_slice(rb, b, body, toff[0], tn[0]) 2274 b = sd_cat(rb, b, "\",\"live\":1,\"prev\":" as *u8) 2275 b = sd_catn(rb, b, had_live) 2276 b = sd_cat(rb, b, "}" as *u8) 2277 return ma_emit_200(out, rb) 2278} 2279 2280// NEVER-BRICK guard: is `path` a real ELF (magic 0x7f 'E' 'L' 'F')? A missing, empty, or truncated build 2281// output must NEVER be promoted onto a live organ path -- that would replace a working organ with garbage. 2282func mau_is_elf(path: *u8) -> i64 { 2283 let fd: i64 = sys_openat_rd(path) 2284 if fd < 0 { return 0 } 2285 let hb: *u8 = sys_mmap(8) 2286 let n: i64 = sys_read(fd, hb, 4) 2287 sys_close(fd) 2288 if n < 4 { return 0 } 2289 if hb[0] != (0x7f as u8) { return 0 } 2290 if hb[1] != (69 as u8) { return 0 } // 'E' 2291 if hb[2] != (76 as u8) { return 0 } // 'L' 2292 if hb[3] != (70 as u8) { return 0 } // 'F' 2293 return 1 2294} 2295 2296// Fold an ASCII hex digit to lowercase so a caller may send either case. 2297func ma_hexlc(c: i64) -> i64 { 2298 if c >= 65 { if c <= 70 { return c + 32 } } 2299 return c 2300} 2301// Compare a NUL-terminated 64-char hex digest against a form-field slice, case-insensitively. 2302// Length is checked FIRST: a short or long field is a MISMATCH, never a prefix match. 2303func ma_hex_eq(hex: *u8, buf: *u8, off: i64, n: i64) -> i64 { 2304 if n != 64 { return 0 } 2305 var i: i64 = 0 2306 while i < 64 { 2307 let a: i64 = ma_hexlc(hex[i] as i64) 2308 let b: i64 = ma_hexlc(buf[off+i] as i64) 2309 if a != b { return 0 } 2310 i = i + 1 2311 } 2312 return 1 2313} 2314 2315// POST /api/promote {target=<organ>&confirm=yes}: promote a built ONE-SHOT organ ELF (<target>.sov.elf.new from 2316// /api/build) to its live <target>.elf -- the API-pure replacement for the SSH `cp X.sov.elf.new X.elf` step, so 2317// the whole edit -> /api/build -> /api/promote loop needs zero shell. NEVER-BRICK, fail-closed at every step: 2318// (1) name sanitized to [a-zA-Z0-9_] (no dot/slash/dot-dot -> no path escape), 2319// (2) md_promote_organ_ok allowlist (daemons REFUSED -> they use the health-checked /api/deploy), 2320// (3) confirm=yes required, 2321// (4) the staged file must exist AND be a valid ELF (mau_is_elf) -- garbage is never promoted, 2322// (5) md_promote_staged keeps the old live as <target>.elf.prev (one-command rollback). 2323// Auth = MA_LVL_ACT, enforced by the dispatcher (ma_handle) before this is reached. 2324// ---- D001 STRUCTURAL GUARD: a gate that rolls its own verdict is NOT PROMOTABLE ---------------- 2325// WHY HERE AND NOT IN 2800 LEAVES: D001 (gates hand-rolling verdict emission instead of inheriting 2326// nx_gate_verdict) sat at 2 permil for nine days and was then moved to 172 permil by migrating leaves 2327// one at a time. That is O(LEAVES) FOREVER -- it remediates what exists and does nothing about the next 2328// gate someone hand-writes tomorrow. The corpus is a LINEAGE, and a constraint belongs at the ONE place 2329// every gate must pass through to become real. Promote is that place: a gate may be built and run and 2330// iterated on freely, but it cannot become a promoted organ of this ecosystem while it emits a verdict 2331// nothing else can read. A rule nothing must remember beats a list. 2332// NAME-BASED, matching /api/gate_run's own bound (_gate/_test/_kat) -- a rule nothing must remember 2333// beats a registry that must be maintained. 2334// ESCAPE HATCH, deliberate: allow_own_verdict=yes. A guard that CANNOT BE SATISFIED produces a bypass, 2335// not safety. Legitimate exceptions (a gate proving the base class itself, a fixture) say so explicitly 2336// and land in the audit trail instead of quietly routing around the check. 2337// FAIL-OPEN ON MISSING SOURCE, deliberate and narrow: if the .nx cannot be found we do NOT refuse -- 2338// mgmt promotes organs whose source may legitimately not be in this tree, and turning "I could not 2339// check" into "denied" would break unrelated promotes. Absent source is UNKNOWN, not GUILTY. 2340const MA_D001_BUF: i64 = 262144 2341 2342func ma_name_is_oracle(nm: *u8, n: i64) -> i64 { 2343 if n >= 5 { 2344 if nm[n-5] == (95 as u8) { 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 } } } } } 2345 } 2346 if n >= 5 { 2347 if nm[n-5] == (95 as u8) { 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 } } } } } 2348 } 2349 if n >= 4 { 2350 if nm[n-4] == (95 as u8) { if nm[n-3] == (107 as u8) { if nm[n-2] == (97 as u8) { if nm[n-1] == (116 as u8) { return 1 } } } } 2351 } 2352 return 0 2353} 2354 2355// ⚠ A TRUNCATED DUPLICATE OF THIS FUNCTION WAS REMOVED HERE (2026-07-31). A botched edit had left a 2356// half-written `ma_d001_scan` header followed by an ORPHANED TAIL of the previous function, then this 2357// complete definition -- so the file carried TWO definitions of one name and DID NOT COMPILE AT ALL. 2358// That is why this lane's edits sat SOURCE-ONLY-NOT-BUILT: not caution, a syntax error nobody had run 2359// into because nothing rebuilt the file. The compiler's dup-def guard (shipped today) named it exactly. 2360// ★★★★★ A FILE THAT DOES NOT COMPILE BLOCKS EVERY LANE THAT TOUCHES IT, SILENTLY, UNTIL SOMEONE BUILDS IT. 2361func ma_d001_scan(path: *u8, buf: *u8) -> i64 { 2362 let fd: i64 = sys_openat_rd(path) 2363 if fd < 0 { return 0 - 1 } 2364 var total: i64 = 0 2365 var run: i64 = 1 2366 while run == 1 { 2367 if total >= MA_D001_BUF { run = 0 } else { 2368 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, MA_D001_BUF - total) 2369 if r <= 0 { run = 0 } else { total = total + r } 2370 } 2371 } 2372 sys_close(fd) 2373 let needle: *u8 = "nx_gate_verdict" as *u8 2374 var nl: i64 = 0 2375 while needle[nl] != (0 as u8) { nl = nl + 1 } 2376 var i: i64 = 0 2377 while i + nl <= total { 2378 var m: i64 = 1 2379 var k: i64 = 0 2380 while k < nl { if buf[i+k] != needle[k] { m = 0; k = nl } else { k = k + 1 } } 2381 if m == 1 { return 1 } 2382 i = i + 1 2383 } 2384 return 0 2385} 2386 2387// 1 inherits · 0 rolls its own · -1 source not found (treated as UNKNOWN, never as a refusal) 2388func ma_inherits_verdict(nm: *u8, nmlen: i64) -> i64 { 2389 let buf: *u8 = sys_mmap(MA_D001_BUF) 2390 let p: *u8 = sys_mmap(256) 2391 var o: i64 = sd_cat(p, 0, "buildroot/runtime/_hdl_build/" as *u8) 2392 o = sd_cat(p, o, nm) 2393 o = sd_cat(p, o, ".nx" as *u8) 2394 p[o] = 0 as u8 2395 let r1: i64 = ma_d001_scan(p, buf) 2396 if r1 >= 0 { return r1 } 2397 o = sd_cat(p, 0, "buildroot/runtime/" as *u8) 2398 o = sd_cat(p, o, nm) 2399 o = sd_cat(p, o, ".nx" as *u8) 2400 p[o] = 0 as u8 2401 let r2: i64 = ma_d001_scan(p, buf) 2402 if r2 >= 0 { return r2 } 2403 // BOTH ROOTS. The first version probed only buildroot/... because that is where the TOOLS daemon 2404 // sees the tree -- and mgmt does not necessarily share that cwd. The guard therefore found no source, 2405 // returned UNKNOWN, failed open, and a non-inheriting gate promoted cleanly while the guard was live. 2406 // THIS IS THE EXACT DEFECT THAT MADE nx_gate_migrate INERT -- cwd-relative paths with an assumed cwd -- 2407 // reproduced by me in the fix for it, hours later, in the same session. gm_resolve solved it by 2408 // probing every plausible root instead of asserting one; do the same rather than assume again. 2409 o = sd_cat(p, 0, "runtime/_hdl_build/" as *u8) 2410 o = sd_cat(p, o, nm) 2411 o = sd_cat(p, o, ".nx" as *u8) 2412 p[o] = 0 as u8 2413 let r3: i64 = ma_d001_scan(p, buf) 2414 if r3 >= 0 { return r3 } 2415 o = sd_cat(p, 0, "runtime/" as *u8) 2416 o = sd_cat(p, o, nm) 2417 o = sd_cat(p, o, ".nx" as *u8) 2418 p[o] = 0 as u8 2419 return ma_d001_scan(p, buf) 2420} 2421 2422 2423func ma_do_promote(req: *u8, req_n: i64, out: *u8) -> i64 { 2424 let body_off: i64 = sd_body_off(req, req_n) 2425 let body: *u8 = ((req as i64) + body_off) as *u8 2426 let body_n: i64 = req_n - body_off 2427 let toff: *i64 = sys_mmap(8) as *i64 2428 let tn: *i64 = sys_mmap(8) as *i64 2429 if sd_form_field(body, body_n, "target" as *u8, 6, toff, tn) != 1 { 2430 return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) 2431 } 2432 let nm: *u8 = sys_mmap(128) 2433 if ma_sanitize_name(body, toff[0], tn[0], nm, 120) != 1 { 2434 return ma_emit_400(out, "{\"error\":\"invalid target name (only [a-zA-Z0-9_])\"}" as *u8) 2435 } 2436 // seq1492: ROLE BEATS NAME. Both ship verbs used to guess from the NAME and DISAGREED -- promote 2437 // called nx_torrent_get a daemon while deploy had never heard of it, so a gate-proven binary was 2438 // unshippable by any sanctioned route. A DECLARED kind now decides: one-shot/oracle promote by right, 2439 // a declared daemon is refused here (it needs the health-probed /api/deploy), and an UNDECLARED name 2440 // falls through to the legacy allowlist -- so this is ADDITIVE and inert for every organ not yet in 2441 // organ_kind.conf. Asymmetry is deliberate: misfiling a daemon as a one-shot would let promote swap a 2442 // binary under a live process with no probe and no rollback (rule 26); the reverse merely blocks it. 2443 let ma_kind: i64 = ok_kind_of_path("knowledge/status/organ_kind.conf" as *u8, nm) 2444 var ma_shipok: i64 = 0 2445 if ma_kind == OK_UNKNOWN { if md_promote_organ_ok(nm) == 1 { ma_shipok = 1 } } 2446 if ma_kind != OK_UNKNOWN { if ok_may_promote(ma_kind) == 1 { ma_shipok = 1 } } 2447 // seq1789: THE SUFFIX IS A DECLARATION; THE SUBSTRING WAS A GUESS. The legacy fall-through 2448 // md_promote_organ_ok -> md_promote_deny asks `does the name CONTAIN "serve"`, so 2449 // nx_survey_serve_gate -- an oracle that runs to completion -- was refused as a daemon and could not 2450 // ship by any sanctioned route. Its gate read exit=127 and was the ONLY red holding the surveys 2451 // domain (35/35 grounded, 3/4 gates green) off MEASURED-HONEST. 2452 // SAFE BY CONSTRUCTION, not by promise: ok_kind_or_suffix can only ever yield OK_ORACLE for an 2453 // UNDECLARED name, which is the safe side of the asymmetry this policy protects -- a DECLARED daemon 2454 // still loses here because ok_may_promote(OK_DAEMON) is 0 and the declared kind takes precedence. 2455 // The two existing guards still bind: md_promote_deny_hard (credential oracles -- mint/vault/secret/ 2456 // keygen/login) refuses NON-OVERRIDABLY, and a staged ELF built HERE is still required, so this can 2457 // never promote a binary that did not come from /api/build on this host. 2458 if ma_shipok != 1 { 2459 if ok_may_promote(ok_kind_or_suffix(ma_kind, nm)) == 1 { 2460 if md_promote_deny_hard(nm) == 0 { 2461 if md_staged_elf_ok(nm) == 1 { ma_shipok = 1 } 2462 } 2463 } 2464 } 2465 if ma_shipok != 1 { 2466 // ★★★★★ AN ERROR MESSAGE IS PART OF THE API, AND A STALE ONE MISDIRECTS EVERY READER. 2467 // This text described a defect that HAS BEEN FIXED (debt 1785453784): md_organ_kind now reads the 2468 // surviving SSOT knowledge/status/organ_kind.conf -- verified in source at nx_mgmt_data.nx:1183. 2469 // The old wording still told operators "WHERE TO DECLARE IT IS CURRENTLY BROKEN" and "do not add 2470 // rows anywhere", which is now WRONG AND ACTIVELY HARMFUL: it forbids exactly the action that 2471 // resolves the refusal. A message that outlives its defect is worse than no message, because a 2472 // reader trusts it and stops. 2473 return ma_emit_400(out, "{\"error\":\"target refused: this name is not promotable. A DAEMON must use the health-checked /api/deploy (validate -> promote -> http-health -> auto-rollback), not /api/promote -- that is the correct path, not a workaround. An UNDECLARED name additionally needs a staged .sov.elf.new or an enumerated allow. TO DECLARE A KIND: add a row to knowledge/status/organ_kind.conf, which IS the live SSOT the reader opens (values are unhyphenated, e.g. oneshot). The older plural knowledge/organ_kinds.conf is RETIRED and is NOT read -- do not add rows there.\"}" as *u8) 2474 } 2475 if ma_confirmed(req, req_n) == 0 { 2476 return ma_emit_400(out, "{\"error\":\"promote requires confirm=yes\"}" as *u8) 2477 } 2478 let nmlen: i64 = sd_len(nm) 2479 // D001 STRUCTURAL GUARD. The helpers above were shipped without this call site once, so the guard 2480 // existed and nothing invoked it -- built-but-not-wired, the exact class this guard exists to end. 2481 if ma_name_is_oracle(nm, nmlen) == 1 { 2482 if ma_inherits_verdict(nm, nmlen) == 0 { 2483 let d1off: *i64 = sys_mmap(8) as *i64 2484 let d1n: *i64 = sys_mmap(8) as *i64 2485 if sd_form_field(body, body_n, "allow_own_verdict" as *u8, 17, d1off, d1n) != 1 { 2486 return ma_emit_400(out, "{\"error\":\"D001: this gate rolls its own verdict instead of inheriting nx_gate_verdict, so nothing can read its outcome -- nx_gate_green cannot judge it and it records no harness.jrnl frame, so flake and erosion stay invisible for it. Migrate it (nx_gate_dry_apply <gate> <out>, then nx_gate_migrate verify <gate> <out>) and promote again. Deliberate exception: resend with allow_own_verdict=yes.\"}" as *u8) 2487 } 2488 } 2489 } 2490 let stagedp: *u8 = sys_mmap(160) 2491 mau_build_path(nm, 0, nmlen, ".sov.elf.new" as *u8, stagedp) 2492 if mau_is_elf(stagedp) != 1 { 2493 // u2605RECOVERABLE AFTER A LOST RESPONSE (seq1780, the other half of the 2494 // self-verifying receipt). Transport loses RESPONSES, not requests 2495 // (seq290), so a caller who saw nothing must be able to ASK rather than 2496 // guess -- and "nothing staged" is EXACTLY what a promote that already 2497 // succeeded looks like on a retry. Answer with the LIVE bytes + sha256: 2498 // equal to the artefact you built means it landed, different means it 2499 // did not. A bare error here forced every caller to hash out-of-band, 2500 // which is the workaround this whole arc exists to delete. 2501 let livechk: *u8 = sys_mmap(160) 2502 mau_build_path(nm, 0, nmlen, ".elf" as *u8, livechk) 2503 let rszp: *i64 = sys_mmap(16) as *i64 2504 let rbytes: *u8 = md_read_file(livechk, rszp) 2505 if (rbytes as i64) != 0 { 2506 let rdig2: *u8 = sys_mmap(32) 2507 sha256_digest(rbytes, rszp[0], rdig2) 2508 let rhex2: *u8 = sys_mmap(72) 2509 mau_hex32(rdig2, rhex2) 2510 let rrb: *u8 = sys_mmap(640) 2511 var ro: i64 = sd_cat(rrb, 0, "{\"action\":\"NOTHING-STAGED\",\"target\":\"" as *u8) 2512 ro = sd_cat(rrb, ro, nm) 2513 ro = sd_cat(rrb, ro, "\",\"live_bytes\":" as *u8) 2514 ro = sd_catn(rrb, ro, rszp[0]) 2515 ro = sd_cat(rrb, ro, ",\"live_sha256\":\"" as *u8) 2516 ro = sd_cat(rrb, ro, rhex2) 2517 ro = sd_cat(rrb, ro, "\",\"note\":\"nothing staged. If you just promoted and lost the response, compare live_sha256 with the artefact you built: equal means it IS live. Otherwise run /api/build first.\"}" as *u8) 2518 rrb[ro] = 0 as u8 2519 return ma_emit_200(out, rrb) 2520 } 2521 return ma_emit_400(out, "{\"error\":\"no valid staged <target>.sov.elf.new and no live artefact to report (run /api/build first; must be an ELF)\"}" as *u8) 2522 } 2523 // ---- EXPECTED-DIGEST GATE (2026-07-30): DEPLOY A DIGEST, NOT A TAG -------------------------------- 2524 // THE RACE THIS CLOSES, MEASURED: /api/build writes _build/<t>.sov.elf and a LATER promote copies that 2525 // PATH. Any sibling rebuilding the same target in between OVERWRITES it. I built nx_mgmt_api at 578865, 2526 // verified it, and a SIBLING'S 579263 WENT LIVE -- I verified one artifact and shipped another, and 2527 // nothing anywhere detected it. mgmt then walked 578789 -> 578865 -> 579263 -> 580657 inside ONE HOUR, 2528 // so this is the NORMAL condition on a shared tree, not a freak event. 2529 // A PATH IS A PROMISE ABOUT A LOCATION, NOT ABOUT CONTENT. The build lease does not cover it: that lease 2530 // is released when the COMPILE ends, so it locks the WRONG INTERVAL -- the hazard lives after it. 2531 // The response already returns a sha256 RECEIPT of what was installed, but a POST-HOC receipt can only 2532 // say what shipped; it cannot PREVENT shipping the wrong thing. This is the missing half: hash the 2533 // STAGED bytes BEFORE any mutation and refuse if they are not the ones the caller says it built. 2534 // OPTIONAL BY DESIGN -- omit expect_sha256 and behaviour is byte-for-byte what it was, so this is purely 2535 // additive. Placed AFTER the ELF/lost-response block (so it cannot preempt the NOTHING-STAGED recovery) 2536 // and BEFORE the lease acquire (so a refusal can never strand the lane -- the R1d law: lock the smallest 2537 // region that actually races). 2538 let xoff: *i64 = sys_mmap(8) as *i64 2539 let xn: *i64 = sys_mmap(8) as *i64 2540 if sd_form_field(body, body_n, "expect_sha256" as *u8, 13, xoff, xn) == 1 { 2541 let sszp: *i64 = sys_mmap(16) as *i64 2542 let sbytes: *u8 = md_read_file(stagedp, sszp) 2543 if (sbytes as i64) == 0 { 2544 return ma_emit_400(out, "{\"error\":\"expect_sha256 was supplied but the staged artefact could not be read -- refusing rather than promoting unverified bytes. Live untouched.\",\"verdict\":\"STAGED-UNREADABLE\"}" as *u8) 2545 } 2546 let sdig: *u8 = sys_mmap(32) 2547 sha256_digest(sbytes, sszp[0], sdig) 2548 let shex: *u8 = sys_mmap(72) 2549 mau_hex32(sdig, shex) 2550 if ma_hex_eq(shex, body, xoff[0], xn[0]) != 1 { 2551 // NAME BOTH SIDES. A refusal that will not say what it saw forces the caller to go and look, 2552 // which is the retry-hammering failure mode -- end the investigation inside this one response. 2553 let xb: *u8 = sys_mmap(1024) 2554 var xo: i64 = sd_cat(xb, 0, "{\"conflict\":\"artefact-mismatch\",\"error\":\"the STAGED artefact is NOT the one you built -- a sibling session almost certainly rebuilt this target between your build and this promote. LIVE IS UNTOUCHED. Rebuild, re-read the digest, and promote the artefact you actually verified.\",\"target\":\"" as *u8) 2555 xo = sd_cat(xb, xo, nm) 2556 xo = sd_cat(xb, xo, "\",\"staged_bytes\":" as *u8) 2557 xo = sd_catn(xb, xo, sszp[0]) 2558 xo = sd_cat(xb, xo, ",\"staged_sha256\":\"" as *u8) 2559 xo = sd_cat(xb, xo, shex) 2560 xo = sd_cat(xb, xo, "\",\"expected_sha256\":\"" as *u8) 2561 xo = ma_gate_esc(xb, xo, ((body as i64) + xoff[0]) as *u8, xn[0]) 2562 xo = sd_cat(xb, xo, "\",\"retry_after_s\":0}" as *u8) 2563 xb[xo] = 0 as u8 2564 return ma_emit_400(out, xb) 2565 } 2566 } 2567 // bridge the build/live naming (<t>.sov.elf.new -> <t>.elf.new), then promote via the never-brick primitive 2568 // (backs up live <t>.elf -> .prev, atomically renames .elf.new -> .elf, chmod +x). 2569 // ---- R1d: LEASE-GATE THE PROMOTE (seq1541 residual) ------------------------------------------------ 2570 // Taken AFTER the ELF validation above and immediately before the first mutating rename, so a bad/absent 2571 // artifact returns early WITHOUT stranding the lane -- same placement law as R1b, learned there by a 2572 // failed proof: lock the smallest region that actually races. 2573 let please: *u8 = sys_mmap(256) 2574 md_lease_name_pfx("promote-" as *u8, nm, please) 2575 if md_lease_run("acquire" as *u8, please, "mgmt-api-promote" as *u8, "300" as *u8, 4, "/tmp/nx_ma_lease.out" as *u8) == 3 { 2576 let plb: *u8 = sys_mmap(2048) 2577 let pln: i64 = dp_read("/tmp/nx_ma_lease.out" as *u8, plb, 900) 2578 let pcb: *u8 = sys_mmap(4096) 2579 var pco: i64 = sd_cat(pcb, 0, "{\"conflict\":\"lease-busy\",\"error\":\"another session is ALREADY PROMOTING this organ -- refusing to race it (seq1541 R1d). Two promotes can interleave the .prev backup and the atomic rename, leaving a rollback that points at the wrong generation. The holder is named below; TTL-bounded.\",\"holder\":\"" as *u8) 2580 pco = ma_gate_esc(pcb, pco, plb, pln) 2581 pco = sd_cat(pcb, pco, "\",\"retry_after_s\":30}" as *u8) 2582 pcb[pco] = 0 as u8 2583 return ma_emit_400(out, pcb) 2584 } 2585 let elfnewp: *u8 = sys_mmap(160) 2586 mau_build_path(nm, 0, nmlen, ".elf.new" as *u8, elfnewp) 2587 if sys_renameat(stagedp, elfnewp) != 0 { 2588 return ma_emit_400(out, "{\"error\":\"stage-rename failed; live untouched\"}" as *u8) 2589 } 2590 let livep: *u8 = sys_mmap(160) 2591 mau_build_path(nm, 0, nmlen, ".elf" as *u8, livep) 2592 if md_promote_staged(livep) != 1 { 2593 // ★RECOVERABLE AFTER A LOST RESPONSE. Transport loses RESPONSES, not 2594 // requests (the banked seq290 law), so a caller who saw nothing must be 2595 // able to ASK rather than guess. Nothing staged now can mean the promote 2596 // ALREADY SUCCEEDED and its receipt was lost -- so answer with the LIVE 2597 // bytes + hash instead of a bare error. The caller compares that hash to 2598 // the build it intended: equal = it landed, different = it did not. 2599 // ★An idempotent-looking retry must report STATE, not just refuse. 2600 let qszp: *i64 = sys_mmap(16) as *i64 2601 let qb: *u8 = md_read_file(livep, qszp) 2602 if (qb as i64) != 0 { 2603 let qdig: *u8 = sys_mmap(32) 2604 sha256_digest(qb, qszp[0], qdig) 2605 let qhex: *u8 = sys_mmap(72) 2606 mau_hex32(qdig, qhex) 2607 let qrb: *u8 = sys_mmap(512) 2608 var qo: i64 = sd_cat(qrb, 0, "{\"action\":\"NOTHING-STAGED\",\"target\":\"" as *u8) 2609 qo = sd_cat(qrb, qo, nm) 2610 qo = sd_cat(qrb, qo, "\",\"live_bytes\":" as *u8) 2611 qo = sd_catn(qrb, qo, qszp[0]) 2612 qo = sd_cat(qrb, qo, ",\"live_sha256\":\"" as *u8) 2613 qo = sd_cat(qrb, qo, qhex) 2614 qo = sd_cat(qrb, qo, "\",\"note\":\"nothing staged: either the promote already landed and its response was lost, or the build is an earlier generation (seq1484 backwards-walk guard). Compare live_sha256 with the artefact you built -- equal means it IS live. Deliberate reverse gear is /api/rollback.\"}" as *u8) 2615 qrb[qo] = 0 as u8 2616 return ma_emit_200(out, qrb) 2617 } 2618 return ma_emit_400(out, "{\"error\":\"promote refused: no staged .elf.new and no live artefact to report. Live untouched.\"}" as *u8) 2619 } 2620 let rb: *u8 = sys_mmap(320) 2621 md_lease_run("release" as *u8, please, "mgmt-api-promote" as *u8, "0" as *u8, 3, "/tmp/nx_ma_lease.out" as *u8) 2622 // ★SELF-VERIFYING PROMOTE (2026-07-30). A promote used to answer only 2623 // "PROMOTED", so the caller had NO way to confirm WHICH bytes went live from 2624 // the response alone -- and when the response was LOST in transport (three 2625 // times in one day) a stale binary read as shipped. Every caller had to 2626 // md5 the artefact out-of-band, which is a workaround for a missing field. 2627 // Hash the LIVE file we just installed and return its size + sha256, so the 2628 // receipt names the exact bytes now running and a caller can verify without 2629 // a second channel. ★A DEPLOY VERB THAT CANNOT NAME WHAT IT DEPLOYED IS NOT 2630 // A RECEIPT. 2631 let lszp: *i64 = sys_mmap(16) as *i64 2632 let lbytes: *u8 = md_read_file(livep, lszp) 2633 let lhex: *u8 = sys_mmap(72) 2634 lhex[0] = 0 as u8 2635 if (lbytes as i64) != 0 { 2636 let ldig: *u8 = sys_mmap(32) 2637 sha256_digest(lbytes, lszp[0], ldig) 2638 mau_hex32(ldig, lhex) 2639 } 2640 var b: i64 = sd_cat(rb, 0, "{\"action\":\"PROMOTED\",\"target\":\"" as *u8) 2641 b = sd_cat(rb, b, nm) 2642 b = sd_cat(rb, b, "\",\"live\":\"" as *u8) 2643 b = sd_cat(rb, b, nm) 2644 b = sd_cat(rb, b, ".elf\",\"bytes\":" as *u8) 2645 b = sd_catn(rb, b, lszp[0]) 2646 b = sd_cat(rb, b, ",\"sha256\":\"" as *u8) 2647 b = sd_cat(rb, b, lhex) 2648 b = sd_cat(rb, b, "\",\"prev\":1}" as *u8) 2649 rb[b] = 0 as u8 2650 return ma_emit_200(out, rb) 2651} 2652 2653// ---- /api/promote_toolchain: install a BUILD TOOLCHAIN binary, canary-proven -- eats seq891/903 ---- 2654// Rationale + the path-not-taken live in nx_mgmt_data.nx (md_toolchain_target_ok / md_tc_install). 2655// 2656// THE GAP: the ecosystem could build and deploy every SERVICE over its own API but not the COMPILER 2657// that builds them, so a proven compiler fix could not be landed API-first (rule 27). On 2026-07-30 2658// nx_fnptr_slot_probe was GREEN on the laptop compiler and RED on the hub compiler -- obj.fn_field(args) 2659// silently emitting no indirect call for every organ in the tree -- with the fix built and nowhere to go. 2660// 2661// NEVER-BRICK BY CONSTRUCTION (rule 26), not by promise: install -> BUILD the canary with the new 2662// toolchain through the REAL production build path -> RUN it and require a GREEN self-check -> on ANY 2663// failure restore the banked .prev. ⚠The canary MUST RUN, not merely compile: a broken compiler emits a 2664// perfectly valid ELF that computes garbage, which is precisely the seq715/seq1012 silent-miscompile 2665// class an ELF-magic check waves straight through. Refusals before the install leave the live toolchain 2666// byte-untouched; failures after it are rolled back automatically. 2667func ma_do_promote_toolchain(req: *u8, req_n: i64, out: *u8) -> i64 { 2668 let body_off: i64 = sd_body_off(req, req_n) 2669 let body: *u8 = ((req as i64) + body_off) as *u8 2670 let body_n: i64 = req_n - body_off 2671 let toff: *i64 = sys_mmap(8) as *i64 2672 let tn: *i64 = sys_mmap(8) as *i64 2673 if sd_form_field(body, body_n, "target" as *u8, 6, toff, tn) != 1 { 2674 return ma_emit_400(out, "{\"error\":\"missing target\"}" as *u8) 2675 } 2676 let nm: *u8 = sys_mmap(128) 2677 if ma_sanitize_base(body, toff[0], tn[0], nm, 120) != 1 { 2678 return ma_emit_400(out, "{\"error\":\"invalid target name (basename only, no slash, no ..)\"}" as *u8) 2679 } 2680 if md_toolchain_target_ok(nm) != 1 { 2681 return ma_emit_400(out, "{\"error\":\"target refused: not a toolchain binary (nx_cc_sovereign.elf | nxasm_x86_main.elf | nx_sov_build_run.elf)\"}" as *u8) 2682 } 2683 if ma_confirmed(req, req_n) == 0 { 2684 return ma_emit_400(out, "{\"error\":\"promote_toolchain requires confirm=yes\"}" as *u8) 2685 } 2686 let sz: i64 = md_tc_install(nm) 2687 if sz == 0 { 2688 return ma_emit_400(out, "{\"error\":\"no valid staged <target>.new (run /api/upload first; must be an ELF at or above the size floor). Live toolchain untouched.\"}" as *u8) 2689 } 2690 // CANARY half 1 -- does the new toolchain COMPILE? Deliberately goes through hostctl buildrun, the 2691 // same path /api/build uses, so the canary exercises production wiring rather than a private shortcut. 2692 let brc: i64 = md_exec_hostctl_capture2("buildrun" as *u8, "nx_tc_canary" as *u8, "/tmp/nx_tc_canary.build.out" as *u8) 2693 var failed: i64 = 0 2694 if brc != 0 { failed = 1 } 2695 // CANARY half 2 -- does what it produced RUN and self-check GREEN? (exit 0 = all 8 checks passed) 2696 var rrc: i64 = 0 2697 if failed == 0 { 2698 let cpath: *u8 = "/volume1/homes/elderwesto/nishihost/nx_tc_canary.sov.elf.new" as *u8 2699 nx_chmod(cpath, MD_TC_MODE_EXEC) 2700 let noargs: *i64 = sys_mmap(16) as *i64 2701 rrc = dep_run_capture(cpath, noargs, 0, "/tmp/nx_tc_canary.run.out" as *u8) 2702 if rrc != 0 { failed = 1 } 2703 } 2704 if failed == 1 { 2705 let rolled: i64 = md_tc_rollback(nm) 2706 let eb: *u8 = sys_mmap(512) 2707 var e: i64 = sd_cat(eb, 0, "{\"action\":\"ROLLED-BACK\",\"target\":\"" as *u8) 2708 e = sd_cat(eb, e, nm) 2709 e = sd_cat(eb, e, "\",\"canary_build_rc\":" as *u8); e = sd_catn(eb, e, brc) 2710 e = sd_cat(eb, e, ",\"canary_run_rc\":" as *u8); e = sd_catn(eb, e, rrc) 2711 e = sd_cat(eb, e, ",\"restored_prev\":" as *u8); e = sd_catn(eb, e, rolled) 2712 e = sd_cat(eb, e, ",\"detail\":\"canary failed; previous toolchain restored. See /tmp/nx_tc_canary.build.out and .run.out\"}" as *u8) 2713 eb[e] = 0 as u8 2714 return ma_emit_400(out, eb) 2715 } 2716 let rb: *u8 = sys_mmap(512) 2717 var b: i64 = sd_cat(rb, 0, "{\"action\":\"TOOLCHAIN-PROMOTED\",\"target\":\"" as *u8) 2718 b = sd_cat(rb, b, nm) 2719 b = sd_cat(rb, b, "\",\"bytes\":" as *u8); b = sd_catn(rb, b, sz) 2720 b = sd_cat(rb, b, ",\"live\":\"buildroot/_offc/" as *u8); b = sd_cat(rb, b, nm) 2721 b = sd_cat(rb, b, "\",\"prev\":1,\"canary\":\"GREEN\"}" as *u8) 2722 rb[b] = 0 as u8 2723 return ma_emit_200(out, rb) 2724} 2725 2726func ma_write_str(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 } 2727 2728// sanitize a filename BASENAME: [a-zA-Z0-9_.-], no slash, no ".." -> no path escape. NUL-terminates buf. 2729func ma_sanitize_base(src: *u8, off: i64, len: i64, buf: *u8, cap: i64) -> i64 { 2730 if len <= 0 { return 0 } 2731 if len >= cap { return 0 } 2732 var i: i64 = 0 2733 var prevdot: i64 = 0 2734 while i < len { 2735 let c: i64 = src[off + i] as i64 2736 var ok: i64 = 0 2737 if c >= 48 { if c <= 57 { ok = 1 } } 2738 if c >= 65 { if c <= 90 { ok = 1 } } 2739 if c >= 97 { if c <= 122 { ok = 1 } } 2740 if c == 95 { ok = 1 } 2741 if c == 45 { ok = 1 } 2742 if c == 46 { ok = 1; if prevdot == 1 { return 0 } prevdot = 1 } else { prevdot = 0 } 2743 if ok == 0 { return 0 } 2744 buf[i] = src[off + i] 2745 i = i + 1 2746 } 2747 buf[len] = 0 as u8 2748 return 1 2749} 2750 2751// POST /api/tools/register {name=<tool>&elf=<basename>&confirm=yes[&args=<pinned>][&title=<title>]}: expose an 2752// organ as an MCP tool over the pure API -- the owner-gated replacement for the SSH allowlist-add (nx_fs_write 2753// DENIES the tool allowlist by construction, so this is the ONLY sovereign path). Appends a GREEN row to 2754// tool_allowlist.conf -> the tool is CALLABLE immediately (the tools daemon hot-reads the allowlist per call); 2755// with a title it also appends a tool_schemas.conf row -> DISCOVERABLE in tools/list after the next 2756// nx_toolreg_reconcile sweep. Fail-closed: name sanitized [a-zA-Z0-9_]; elf sanitized basename (no path escape) 2757// resolved under nishihost and validated as a real ELF; idempotent (already-registered -> no dup row); confirm=yes. 2758// update=yes: atomically REPLACE the existing name-keyed row instead (repoint elf/pinned-args; .prev banked, 2759// tmp+rename; refuses unknown names = update can never create). Eats the ssh-once row-repoint class. 2760// Auth = MA_LVL_ACT (owner), enforced by the dispatcher. Least-authority holds: caps still gate WHO may call it. 2761// sized: pinned-arg rows and schema titles are short one-liners; 4096 is ample and bounds the decode 2762const MA_ARGSDEC_CAP: i64 = 4096 2763// RESTORED AGAIN 2026-07-30 (3rd backdate; nx_srcguard now watches these files -- seq1439). 2764// seq732: never let an appended row GLUE onto an unterminated last line (it once corrupted two rows). 2765func ma_ensure_trailing_nl(path: *u8, af: i64) -> i64 { 2766 let rfd: i64 = sys_openat_rd(path) 2767 if rfd < 0 { return 0 } 2768 let sz: i64 = sys_lseek(rfd, 0, 2) 2769 if sz > 0 { 2770 sys_lseek(rfd, sz - 1, 0) 2771 let lb: *u8 = sys_mmap(2) 2772 let rn: i64 = sys_read(rfd, lb, 1) 2773 if rn == 1 { if lb[0] != (10 as u8) { ma_write_str(af, "\n" as *u8) } } 2774 } 2775 sys_close(rfd) 2776 return 0 2777} 2778 2779// seq722: TRI-STATE -- 1 present, 0 absent (missing/empty file = 0 so append safely CREATES), -1 UNKNOWN 2780// when the read filled the buffer and may be truncated. Callers append ONLY on 0, so a truncated read can 2781// never manufacture a duplicate row. 2782func ma_schema_has_name(nm: *u8) -> i64 { 2783 let fd: i64 = sys_openat_rd("knowledge/tool_schemas.conf" as *u8) 2784 if fd < 0 { return 0 } 2785 let cap: i64 = 1 << 20 2786 let buf: *u8 = sys_mmap(cap) 2787 let n: i64 = sys_read(fd, buf, cap - 1) 2788 sys_close(fd) 2789 if n <= 0 { return 0 } 2790 if n >= (cap - 1) { return 0 - 1 } 2791 buf[n] = 0 as u8 2792 var nl: i64 = 0 2793 while nm[nl] != (0 as u8) { nl = nl + 1 } 2794 var i: i64 = 0 2795 while i + nl < n { 2796 var ls: i64 = 0 2797 if i == 0 { ls = 1 } else { if buf[i-1] == (10 as u8) { ls = 1 } } 2798 if ls == 1 { 2799 var m: i64 = 1 2800 var j: i64 = 0 2801 while j < nl { if buf[i+j] != nm[j] { m = 0; j = nl } else { j = j + 1 } } 2802 if m == 1 { if buf[i+nl] == (9 as u8) { return 1 } } 2803 } 2804 i = i + 1 2805 } 2806 return 0 2807} 2808 2809// seq722+seq1106: append ONE schema row with the percent-DECODED title; control bytes and TAB are scrubbed 2810// to spaces so a hostile title can never inject a second row or split the 8-column layout. 2811func ma_schema_append_row(nm: *u8, body: *u8, toff: i64, tn: i64) -> i64 { 2812 let sf: i64 = sys_openat_append("knowledge/tool_schemas.conf" as *u8, 420) 2813 if sf < 0 { return 0 } 2814 ma_ensure_trailing_nl("knowledge/tool_schemas.conf" as *u8, sf) 2815 let tdec: *u8 = sys_mmap(MA_ARGSDEC_CAP) 2816 let tdn: i64 = sd_urldecode(((body as i64) + toff) as *u8, tn, tdec, MA_ARGSDEC_CAP - 1) 2817 var k: i64 = 0 2818 while k < tdn { if (tdec[k] as i64) < 32 { tdec[k] = 32 as u8 } k = k + 1 } 2819 ma_write_str(sf, nm); ma_write_str(sf, "\t" as *u8) 2820 sys_write(sf, tdec, tdn) 2821 ma_write_str(sf, "\t0\t1\t0\t1\t" as *u8); ma_write_str(sf, nm) 2822 ma_write_str(sf, " output (registered via /api/tools/register)\n" as *u8) 2823 sys_close(sf) 2824 return 1 2825} 2826 2827func ma_do_tools_register(req: *u8, req_n: i64, out: *u8) -> i64 { 2828 let body_off: i64 = sd_body_off(req, req_n) 2829 let body: *u8 = ((req as i64) + body_off) as *u8 2830 let body_n: i64 = req_n - body_off 2831 let noff: *i64 = sys_mmap(8) as *i64 2832 let nn: *i64 = sys_mmap(8) as *i64 2833 if sd_form_field(body, body_n, "name" as *u8, 4, noff, nn) != 1 { 2834 return ma_emit_400(out, "{\"error\":\"missing name\"}" as *u8) 2835 } 2836 let nm: *u8 = sys_mmap(128) 2837 if ma_sanitize_name(body, noff[0], nn[0], nm, 120) != 1 { 2838 return ma_emit_400(out, "{\"error\":\"invalid tool name (only [a-zA-Z0-9_])\"}" as *u8) 2839 } 2840 let eoff: *i64 = sys_mmap(8) as *i64 2841 let en: *i64 = sys_mmap(8) as *i64 2842 if sd_form_field(body, body_n, "elf" as *u8, 3, eoff, en) != 1 { 2843 return ma_emit_400(out, "{\"error\":\"missing elf (organ basename in nishihost)\"}" as *u8) 2844 } 2845 let base: *u8 = sys_mmap(160) 2846 // F-210b: ONE optional "_offc/" prefix (the staged-organ home) -- the only subdir allowed; the 2847 // remainder still passes the strict basename sanitize, so no other path shape can slip through. 2848 var exo: i64 = eoff[0] 2849 var exn: i64 = en[0] 2850 var offc: i64 = 0 2851 if exn > 6 { 2852 if body[exo] == (95 as u8) { if body[exo+1] == (111 as u8) { if body[exo+2] == (102 as u8) { if body[exo+3] == (102 as u8) { if body[exo+4] == (99 as u8) { if body[exo+5] == (47 as u8) { 2853 offc = 1 2854 exo = exo + 6 2855 exn = exn - 6 2856 } } } } } } 2857 } 2858 if ma_sanitize_base(body, exo, exn, base, 150) != 1 { 2859 return ma_emit_400(out, "{\"error\":\"invalid elf path (basename or _offc/basename; [a-zA-Z0-9_.-], no other slash/..)\"}" as *u8) 2860 } 2861 let elfp: *u8 = sys_mmap(256) 2862 var eo: i64 = sd_cat(elfp, 0, "/volume1/homes/elderwesto/nishihost/" as *u8) 2863 if offc == 1 { eo = sd_cat(elfp, eo, "_offc/" as *u8) } 2864 eo = sd_cat(elfp, eo, base) 2865 elfp[eo] = 0 as u8 2866 if ma_confirmed(req, req_n) == 0 { 2867 return ma_emit_400(out, "{\"error\":\"register requires confirm=yes\"}" as *u8) 2868 } 2869 // register-update verb (eats the ssh-once row-repoint class seq70/72/64): update=yes REPLACES the 2870 // existing name-keyed allowlist row atomically (.prev banked, tmp+rename, hot-read applies at once). 2871 // Fail-closed both ways: update can NEVER create (unknown name -> 400 below) and plain register 2872 // still refuses duplicates (ALREADY-REGISTERED unchanged). 2873 let upoff: *i64 = sys_mmap(8) as *i64 2874 let upn: *i64 = sys_mmap(8) as *i64 2875 var want_update: i64 = 0 2876 if sd_form_field(body, body_n, "update" as *u8, 6, upoff, upn) == 1 { if upn[0] >= 1 { if body[upoff[0]] == (121 as u8) { want_update = 1 } } } 2877 if md_allow_has_name(nm) == 1 { 2878 if want_update == 1 { 2879 if mau_is_elf(elfp) != 1 { 2880 return ma_emit_400(out, "{\"error\":\"elf not found or not a valid ELF under nishihost\"}" as *u8) 2881 } 2882 let uaoff: *i64 = sys_mmap(8) as *i64 2883 let uan: *i64 = sys_mmap(8) as *i64 2884 var uargp: *u8 = 0 as *u8 2885 var uargn: i64 = 0 2886 let uhave: i64 = sd_form_field(body, body_n, "args" as *u8, 4, uaoff, uan) 2887 if uhave == 1 { if uan[0] > 0 { uargp = ((body as i64) + uaoff[0]) as *u8; uargn = uan[0] } } 2888 // seq1281: an OMITTED args= PRESERVES the row's existing pinned args -- an omission must never 2889 // silently unpin a fixed-arg oracle into caller-controlled argv. Explicit args= still replaces. 2890 if uhave != 1 { 2891 let upre: *u8 = sys_mmap(MA_ARGSDEC_CAP) 2892 let upn: i64 = md_allow_get_args(nm, upre, MA_ARGSDEC_CAP - 1) 2893 if upn > 0 { uargp = upre; uargn = upn } 2894 } 2895 // seq722: update=yes HONORS title= -- upsert the schema row (append-if-absent). 2896 var uschema: i64 = 0 2897 let utoff: *i64 = sys_mmap(8) as *i64 2898 let utn: *i64 = sys_mmap(8) as *i64 2899 if sd_form_field(body, body_n, "title" as *u8, 5, utoff, utn) == 1 { if utn[0] > 0 { 2900 if ma_schema_has_name(nm) == 0 { uschema = ma_schema_append_row(nm, body, utoff[0], utn[0]) } 2901 } } 2902 if md_allow_update_row(nm, elfp, uargp, uargn) != 1 { 2903 return ma_emit_400(out, "{\"error\":\"row update failed (allowlist unreadable or row vanished); allowlist untouched\"}" as *u8) 2904 } 2905 let ub: *u8 = sys_mmap(512) 2906 var uo: i64 = sd_cat(ub, 0, "{\"action\":\"ROW-UPDATED\",\"name\":\"" as *u8) 2907 uo = sd_cat(ub, uo, nm) 2908 uo = sd_cat(ub, uo, "\",\"elf\":\"" as *u8) 2909 uo = sd_cat(ub, uo, elfp) 2910 uo = sd_cat(ub, uo, "\",\"schema\":" as *u8) 2911 uo = sd_catn(ub, uo, uschema) 2912 uo = sd_cat(ub, uo, ",\"note\":\"allowlist row replaced atomically (.prev banked); hot-read applies immediately; schema=1 = tool_schemas.conf row appended, run nx_toolreg_reconcile for tools/list discovery\"}" as *u8) 2913 ub[uo] = 0 as u8 2914 return ma_emit_200(out, ub) 2915 } 2916 let rbx: *u8 = sys_mmap(256) 2917 var bx: i64 = sd_cat(rbx, 0, "{\"action\":\"ALREADY-REGISTERED\",\"name\":\"" as *u8) 2918 bx = sd_cat(rbx, bx, nm) 2919 bx = sd_cat(rbx, bx, "\"}" as *u8) 2920 rbx[bx] = 0 as u8 2921 return ma_emit_200(out, rbx) 2922 } 2923 if want_update == 1 { 2924 return ma_emit_400(out, "{\"error\":\"update target not registered (update can never create; register without update=yes)\"}" as *u8) 2925 } 2926 if mau_is_elf(elfp) != 1 { 2927 return ma_emit_400(out, "{\"error\":\"elf not found or not a valid ELF under nishihost\"}" as *u8) 2928 } 2929 let aoff: *i64 = sys_mmap(8) as *i64 2930 let an: *i64 = sys_mmap(8) as *i64 2931 var have_args: i64 = 0 2932 if sd_form_field(body, body_n, "args" as *u8, 4, aoff, an) == 1 { if an[0] > 0 { have_args = 1 } } 2933 // APPEND the GREEN allowlist row: name<TAB>abs-elf<TAB>GREEN[<TAB>pinned-args]<NL> 2934 let af: i64 = sys_openat_append("tool_allowlist.conf" as *u8, 420) 2935 if af < 0 { return ma_emit_400(out, "{\"error\":\"cannot open tool_allowlist.conf for append\"}" as *u8) } 2936 ma_write_str(af, nm); ma_write_str(af, "\t" as *u8); ma_write_str(af, elfp); ma_write_str(af, "\tGREEN" as *u8) 2937 if have_args == 1 { ma_write_str(af, "\t" as *u8); sys_write(af, ((body as i64) + aoff[0]) as *u8, an[0]) } 2938 ma_write_str(af, "\n" as *u8) 2939 sys_close(af) 2940 // OPTIONAL schema row (title -> discoverable). Cautious default annotations (ro=0 destr=1 idem=0 open=1). 2941 let toff: *i64 = sys_mmap(8) as *i64 2942 let tn: *i64 = sys_mmap(8) as *i64 2943 var have_title: i64 = 0 2944 if sd_form_field(body, body_n, "title" as *u8, 5, toff, tn) == 1 { if tn[0] > 0 { have_title = 1 } } 2945 // seq722: upsert via the shared helper; did_schema (not have_title) feeds the response so "schema":1 2946 // is never a lie, and the tri-state UNKNOWN skips rather than risking a duplicate row. 2947 var did_schema: i64 = 0 2948 if have_title == 1 { if ma_schema_has_name(nm) == 0 { did_schema = ma_schema_append_row(nm, body, toff[0], tn[0]) } } 2949 let rb: *u8 = sys_mmap(512) 2950 var b: i64 = sd_cat(rb, 0, "{\"action\":\"REGISTERED\",\"name\":\"" as *u8) 2951 b = sd_cat(rb, b, nm) 2952 b = sd_cat(rb, b, "\",\"elf\":\"" as *u8); b = sd_cat(rb, b, elfp) 2953 b = sd_cat(rb, b, "\",\"callable\":1,\"schema\":" as *u8); b = sd_catn(rb, b, did_schema) 2954 b = sd_cat(rb, b, ",\"note\":\"callable now (allowlist hot-read); run nx_toolreg_reconcile / next sweep for tools/list discovery\"}" as *u8) 2955 rb[b] = 0 as u8 2956 return ma_emit_200(out, rb) 2957} 2958 2959// POST /api/cap/mint {allow=<csv>&confirm=yes[&days=N][&nonce=N]}: mint a ROOT tools-capability token via the 2960// on-NAS nx_cap_mint oracle -- the owner-gated API replacement for the SSH mint recipe, closing the sovereign 2961// register->mint->call loop (/api/tools/register exposes the organ, /api/cap/mint grants a cap naming EXACTLY 2962// those tools, the MCP client calls with _cap=<token>). Distinct from the tools daemon's /api/cap/issue, which 2963// only ATTENUATES a presented cap (can never name a newly registered tool outside the presenter's set) -- root 2964// mint was the one grant still requiring shell. Fail-closed least-authority BY CONSTRUCTION: 2965// * `*` (all-tools) and any char outside [a-zA-Z0-9_,] are REFUSED (charset gate); 2966// * every csv element must ALREADY be a registered first-field of tool_allowlist.conf (md_allow_has_name), 2967// so a cap can never pre-authorize a tool that does not exist; 2968// * days clamped 1..730 (default 30); nonce = positive digits (default mint-epoch: unique, revocable-by-nonce); 2969// * confirm=yes required; the HMAC keyfile NEVER crosses the API (the oracle reads it on-NAS); 2970// * every successful mint is appended to cap_consent.log (the ledger the tools daemon serves at 2971// GET /api/cap/consent-log) -- an unaudited cap cannot exist. 2972// Auth = MA_LVL_ACT (owner), enforced by the dispatcher like every other write route. 2973func ma_do_cap_mint(req: *u8, req_n: i64, out: *u8) -> i64 { 2974 let body_off: i64 = sd_body_off(req, req_n) 2975 let body: *u8 = ((req as i64) + body_off) as *u8 2976 let body_n: i64 = req_n - body_off 2977 let aoff: *i64 = sys_mmap(8) as *i64 2978 let an: *i64 = sys_mmap(8) as *i64 2979 if sd_form_field(body, body_n, "allow" as *u8, 5, aoff, an) != 1 { 2980 return ma_emit_400(out, "{\"error\":\"missing allow (csv of registered tool names)\"}" as *u8) 2981 } 2982 if an[0] <= 0 { return ma_emit_400(out, "{\"error\":\"empty allow\"}" as *u8) } 2983 if an[0] >= CM_ALLOW_MAX { return ma_emit_400(out, "{\"error\":\"allow too long\"}" as *u8) } 2984 // charset + csv-structure gate. `*` fails the charset, so a mint-everything cap is impossible here. 2985 let csv: *u8 = sys_mmap(512) 2986 var ci: i64 = 0 2987 var prevc: i64 = 44 2988 while ci < an[0] { 2989 let cc: i64 = body[aoff[0] + ci] as i64 2990 var cok: i64 = 0 2991 if cc >= 48 { if cc <= 57 { cok = 1 } } 2992 if cc >= 65 { if cc <= 90 { cok = 1 } } 2993 if cc >= 97 { if cc <= 122 { cok = 1 } } 2994 if cc == 95 { cok = 1 } 2995 if cc == 44 { if prevc == 44 { return ma_emit_400(out, "{\"error\":\"malformed allow csv\"}" as *u8) } cok = 1 } 2996 if cok == 0 { return ma_emit_400(out, "{\"error\":\"invalid allow charset (tool names [a-zA-Z0-9_] comma-separated; * refused -- name the tools)\"}" as *u8) } 2997 csv[ci] = body[aoff[0] + ci] 2998 prevc = cc 2999 ci = ci + 1 3000 } 3001 if prevc == 44 { return ma_emit_400(out, "{\"error\":\"malformed allow csv\"}" as *u8) } 3002 csv[an[0]] = 0 as u8 3003 // least-authority: EVERY element must already be registered (first TAB-field of tool_allowlist.conf). 3004 let nmb: *u8 = sys_mmap(128) 3005 var es: i64 = 0 3006 var ej: i64 = 0 3007 while ej <= an[0] { 3008 var atend: i64 = 0 3009 if ej == an[0] { atend = 1 } else { if csv[ej] == (44 as u8) { atend = 1 } } 3010 if atend == 1 { 3011 let el: i64 = ej - es 3012 if el >= CM_NAME_MAX { return ma_emit_400(out, "{\"error\":\"tool name too long\"}" as *u8) } 3013 var ek: i64 = 0 3014 while ek < el { nmb[ek] = csv[es + ek]; ek = ek + 1 } 3015 nmb[el] = 0 as u8 3016 if md_allow_has_name(nmb) != 1 { 3017 let eb400: *u8 = sys_mmap(320) 3018 var e4: i64 = sd_cat(eb400, 0, "{\"error\":\"allow names an unregistered tool: " as *u8) 3019 e4 = sd_cat(eb400, e4, nmb) 3020 e4 = sd_cat(eb400, e4, " (register it first via /api/tools/register)\"}" as *u8) 3021 eb400[e4] = 0 as u8 3022 return ma_emit_400(out, eb400) 3023 } 3024 es = ej + 1 3025 } 3026 ej = ej + 1 3027 } 3028 var days: i64 = CM_DAYS_DEFAULT 3029 let doff: *i64 = sys_mmap(8) as *i64 3030 let dn: *i64 = sys_mmap(8) as *i64 3031 if sd_form_field(body, body_n, "days" as *u8, 4, doff, dn) == 1 { if dn[0] > 0 { days = md_slice_atoi(body, doff[0], dn[0]) } } 3032 if days < 1 { return ma_emit_400(out, "{\"error\":\"invalid days (1..730)\"}" as *u8) } 3033 if days > CM_DAYS_MAX { return ma_emit_400(out, "{\"error\":\"invalid days (1..730)\"}" as *u8) } 3034 let now: i64 = sys_now_realtime_sec() 3035 var nonce: i64 = now 3036 let xoff: *i64 = sys_mmap(8) as *i64 3037 let xn: *i64 = sys_mmap(8) as *i64 3038 if sd_form_field(body, body_n, "nonce" as *u8, 5, xoff, xn) == 1 { if xn[0] > 0 { nonce = md_slice_atoi(body, xoff[0], xn[0]) } } 3039 if nonce <= 0 { return ma_emit_400(out, "{\"error\":\"invalid nonce (positive integer)\"}" as *u8) } 3040 if ma_confirmed(req, req_n) == 0 { 3041 return ma_emit_400(out, "{\"error\":\"cap mint requires confirm=yes\"}" as *u8) 3042 } 3043 let exp: i64 = now + days * CM_SECS_PER_DAY 3044 let expstr: *u8 = sys_mmap(32) 3045 var eo2: i64 = sd_catn(expstr, 0, exp) 3046 expstr[eo2] = 0 as u8 3047 let noncestr: *u8 = sys_mmap(32) 3048 var no2: i64 = sd_catn(noncestr, 0, nonce) 3049 noncestr[no2] = 0 as u8 3050 let rc: i64 = md_exec_capmint(csv, expstr, noncestr, "/tmp/nx_ma_capmint.out" as *u8) 3051 if rc != 0 { 3052 let fb: *u8 = sys_mmap(256) 3053 var fo: i64 = sd_cat(fb, 0, "{\"error\":\"mint failed (oracle exit " as *u8) 3054 fo = sd_catn(fb, fo, rc) 3055 fo = sd_cat(fb, fo, "; token withheld)\"}" as *u8) 3056 fb[fo] = 0 as u8 3057 return ma_emit_400(out, fb) 3058 } 3059 let szp: *i64 = sys_mmap(16) as *i64 3060 let tok: *u8 = md_read_file("/tmp/nx_ma_capmint.out" as *u8, szp) 3061 if (tok as i64) == 0 { return ma_emit_400(out, "{\"error\":\"mint produced no token\"}" as *u8) } 3062 var tl: i64 = szp[0] 3063 var trim: i64 = 1 3064 while trim == 1 { 3065 trim = 0 3066 if tl > 0 { 3067 let lastc: i64 = tok[tl - 1] as i64 3068 if lastc == 10 { tl = tl - 1; trim = 1 } 3069 if lastc == 13 { tl = tl - 1; trim = 1 } 3070 } 3071 } 3072 if tl <= 0 { return ma_emit_400(out, "{\"error\":\"mint produced no token\"}" as *u8) } 3073 // audit BEFORE the token leaves the process: an unaudited cap must not exist. 3074 let cf: i64 = sys_openat_append("cap_consent.log" as *u8, 420) 3075 if cf >= 0 { 3076 let al: *u8 = sys_mmap(1024) 3077 var ao: i64 = sd_cat(al, 0, "CAPMINT epoch=" as *u8) 3078 ao = sd_catn(al, ao, now) 3079 ao = sd_cat(al, ao, " allow=" as *u8) 3080 ao = sd_cat(al, ao, csv) 3081 ao = sd_cat(al, ao, " exp=" as *u8) 3082 ao = sd_cat(al, ao, expstr) 3083 ao = sd_cat(al, ao, " nonce=" as *u8) 3084 ao = sd_cat(al, ao, noncestr) 3085 ao = sd_cat(al, ao, " days=" as *u8) 3086 ao = sd_catn(al, ao, days) 3087 ao = sd_cat(al, ao, " src=/api/cap/mint\n" as *u8) 3088 sys_write(cf, al, ao) 3089 sys_close(cf) 3090 } 3091 let rb: *u8 = sys_mmap(2048) 3092 var b: i64 = sd_cat(rb, 0, "{\"action\":\"MINTED\",\"allow\":\"" as *u8) 3093 b = sd_cat(rb, b, csv) 3094 b = sd_cat(rb, b, "\",\"days\":" as *u8) 3095 b = sd_catn(rb, b, days) 3096 b = sd_cat(rb, b, ",\"exp\":" as *u8) 3097 b = sd_catn(rb, b, exp) 3098 b = sd_cat(rb, b, ",\"nonce\":" as *u8) 3099 b = sd_catn(rb, b, nonce) 3100 b = sd_cat(rb, b, ",\"cap\":\"" as *u8) 3101 b = md_cat_slice(rb, b, tok, 0, tl) 3102 b = sd_cat(rb, b, "\",\"note\":\"pass as _cap on MCP tool calls; audited in cap_consent.log; revoke by nonce\"}" as *u8) 3103 rb[b] = 0 as u8 3104 return ma_emit_200(out, rb) 3105} 3106 3107func ma_do_rollback(req: *u8, req_n: i64, out: *u8) -> i64 { 3108 if ma_confirmed(req, req_n) == 0 { 3109 return ma_emit_400(out, "{\"error\":\"rollback requires confirm=yes\"}" as *u8) 3110 } 3111 // TARGET IS HONOURED OR THE CALL IS REFUSED (sev-9 incident 2026-07-30, id=1785447965). 3112 // This verb previously read NO TARGET AT ALL: it ran hostctl's `rollback` sub, which is 3113 // hardcoded to sites.elf -- THE PUBLIC EDGE. An operator following nx_route_diff.nx:183's own 3114 // remedy line ('route(s) vanished = deploy contract regression; rollback: POST /api/rollback') 3115 // sent target=mgmtapi&confirm=yes to recover a regressed mgmt binary, and killed and reverted 3116 // the PUBLIC EDGE instead -- receiving 200 ROLLED-BACK for it. The parameter is exactly what 3117 // convinced the caller they were specific. 3118 // LAW: A DESTRUCTIVE VERB MUST EITHER HONOUR ITS TARGET OR REFUSE THE CALL. Silently 3119 // retargeting is strictly worse than having no parameter at all. 3120 // Absent target stays back-compatible; a target this verb CANNOT honour is now a 400. 3121 let rb_boff: i64 = sd_body_off(req, req_n) 3122 let rb_body: *u8 = ((req as i64) + rb_boff) as *u8 3123 let rb_bn: i64 = req_n - rb_boff 3124 let rb_toff: *i64 = sys_mmap(8) as *i64 3125 let rb_tn: *i64 = sys_mmap(8) as *i64 3126 if sd_form_field(rb_body, rb_bn, "target" as *u8, 6, rb_toff, rb_tn) == 1 { 3127 var rb_ok: i64 = 0 3128 if md_slice_eq(rb_body, rb_toff[0], rb_tn[0], "edge" as *u8, 0, 4) == 1 { rb_ok = 1 } 3129 if md_slice_eq(rb_body, rb_toff[0], rb_tn[0], "sites" as *u8, 0, 5) == 1 { rb_ok = 1 } 3130 if md_slice_eq(rb_body, rb_toff[0], rb_tn[0], "sites.elf" as *u8, 0, 9) == 1 { rb_ok = 1 } 3131 if rb_ok == 0 { 3132 return ma_emit_400(out, "{\"error\":\"rollback refused: this verb has ONE reverse gear -- it restores sites.elf.prev (the EDGE) and nothing else. It cannot roll back mgmt, an organ, or any other artifact, so a target it cannot honour is REFUSED rather than silently retargeted (sev-9, 2026-07-30). Say target=edge to mean the edge deliberately, or omit target. To reverse a mgmt/organ deploy, rebuild the intended generation and use /api/deploy or /api/promote.\"}" as *u8) 3133 } 3134 } 3135 let rc: i64 = md_exec_hostctl("rollback" as *u8) 3136 // NAME WHAT WAS TOUCHED. The old body said only ROLLED-BACK, so the 2026-07-30 mis-aim was 3137 // invisible in the response and only surfaced in /tmp/mgmt_api.log afterwards. A destructive 3138 // verb that does not name its artifact makes its own incidents undiagnosable at the call site. 3139 if rc == 0 { return ma_emit_200(out, "{\"action\":\"ROLLED-BACK\",\"artifact\":\"sites.elf\",\"note\":\"the EDGE was restored from sites.elf.prev and respawned by the supervisor; this verb touches NOTHING else\",\"rc\":0}" as *u8) } 3140 return ma_emit_200(out, "{\"action\":\"ROLLBACK-FAILED\",\"artifact\":\"sites.elf\"}" as *u8) 3141} 3142 3143func ma_do_reconcile(req: *u8, req_n: i64, out: *u8) -> i64 { 3144 if ma_confirmed(req, req_n) == 0 { 3145 return ma_emit_400(out, "{\"error\":\"reconcile requires confirm=yes (it kills/adopts live supervisors)\"}" as *u8) 3146 } 3147 let rc: i64 = md_exec_hostctl("reconcile" as *u8) 3148 let body: *u8 = sys_mmap(256) 3149 var b: i64 = sd_cat(body, 0, "{\"action\":\"RECONCILE\",\"rc\":" as *u8) 3150 b = sd_catn(body, b, rc) 3151 b = sd_cat(body, b, ",\"note\":\"single-supervisor reconcile; dueling supervisors killed if present\"}" as *u8) 3152 return ma_emit_200(out, body) 3153} 3154 3155func ma_do_restart(req: *u8, req_n: i64, out: *u8) -> i64 { 3156 let body_off: i64 = sd_body_off(req, req_n) 3157 let body: *u8 = ((req as i64) + body_off) as *u8 3158 let body_n: i64 = req_n - body_off 3159 let soff: *i64 = sys_mmap(8) as *i64 3160 let sn: *i64 = sys_mmap(8) as *i64 3161 if sd_form_field(body, body_n, "service" as *u8, 7, soff, sn) != 1 { 3162 return ma_emit_400(out, "{\"error\":\"missing service\"}" as *u8) 3163 } 3164 let subbuf: *u8 = sys_mmap(64) 3165 if md_restart_sub(body, soff[0], sn[0], subbuf) != 1 { 3166 // DIRECT restart (no hostctl sub): allowlisted name -> kill-by-name; the root supervise guard 3167 // respawns the on-disk binary. Promote a staged .new first and this IS the API-pure deploy loop. 3168 let dname: *u8 = sys_mmap(64) 3169 if md_direct_restart_ok(body, soff[0], sn[0], dname) == 1 { 3170 if ma_confirmed(req, req_n) == 0 { 3171 return ma_emit_400(out, "{\"error\":\"restart requires confirm=yes\"}" as *u8) 3172 } 3173 // ---- R1c: LEASE-GATE THE RESTART (seq1541 residual) --------------------------------------- 3174 // The highest-risk lane of the three: this promotes a staged binary AND kills a LIVE daemon. 3175 // Two sessions restarting the same service concurrently can interleave promote-and-kill so the 3176 // guard respawns a binary neither of them verified. Acquired immediately before the mutation and 3177 // released immediately after, so the guarded region is atomic and no exit can strand the lane. 3178 let rlease: *u8 = sys_mmap(256) 3179 md_lease_name_pfx("restart-" as *u8, dname, rlease) 3180 if md_lease_run("acquire" as *u8, rlease, "mgmt-api-restart" as *u8, "300" as *u8, 4, "/tmp/nx_ma_lease.out" as *u8) == 3 { 3181 let rlb: *u8 = sys_mmap(2048) 3182 let rln: i64 = dp_read("/tmp/nx_ma_lease.out" as *u8, rlb, 900) 3183 let rcb: *u8 = sys_mmap(4096) 3184 var rco: i64 = sd_cat(rcb, 0, "{\"conflict\":\"lease-busy\",\"error\":\"another session is ALREADY RESTARTING this service -- refusing to race it (seq1541 R1c). Concurrent promote+kill can leave the guard respawning a binary neither session verified. The holder is named below; the lease is TTL-bounded so a dead session cannot hold the lane closed.\",\"holder\":\"" as *u8) 3185 rco = ma_gate_esc(rcb, rco, rlb, rln) 3186 rco = sd_cat(rcb, rco, "\",\"retry_after_s\":30}" as *u8) 3187 rcb[rco] = 0 as u8 3188 return ma_emit_400(out, rcb) 3189 } 3190 let promoted: i64 = md_promote_staged(dname) // .new -> live (.prev kept) if staged 3191 let killed: i64 = md_kill_by_name(dname) // guard respawns the (now-new) on-disk binary 3192 md_lease_run("release" as *u8, rlease, "mgmt-api-restart" as *u8, "0" as *u8, 3, "/tmp/nx_ma_lease.out" as *u8) 3193 let rb2: *u8 = sys_mmap(256) 3194 var b2: i64 = sd_cat(rb2, 0, "{\"action\":\"RESTART-DIRECT\",\"service\":\"" as *u8) 3195 b2 = md_cat_slice(rb2, b2, body, soff[0], sn[0]) 3196 b2 = sd_cat(rb2, b2, "\",\"promoted\":" as *u8) 3197 b2 = sd_catn(rb2, b2, promoted) 3198 b2 = sd_cat(rb2, b2, ",\"killed\":" as *u8) 3199 b2 = sd_catn(rb2, b2, killed) 3200 b2 = sd_cat(rb2, b2, ",\"note\":\"supervise guard respawns the on-disk binary\"}" as *u8) 3201 return ma_emit_200(out, rb2) 3202 } 3203 // seq1433: this list DRIFTED from the two allowlists it describes -- office/officejs/toolsapi were wired 3204 // into md_direct_restart_ok but never named here, so a caller asking for a service that IS supported was 3205 // told it was unknown. An error message that lies about the allowlist sends the caller to the shell. 3206 return ma_emit_400(out, "{\"error\":\"unknown service (allow: reader|torrent|torrentgw|docportal|siteedit|sites|survey|office|officejs|toolsapi|seed). NOTE torrent = the :8097 torrent DAEMON; seed = the :6881 BitTorrent SEEDER -- they are different processes and restarting one does not restart the other.\"}" as *u8) 3207 } 3208 if ma_confirmed(req, req_n) == 0 { 3209 return ma_emit_400(out, "{\"error\":\"restart requires confirm=yes\"}" as *u8) 3210 } 3211 let rc: i64 = md_exec_hostctl(subbuf) 3212 let rbody: *u8 = sys_mmap(256) 3213 var b: i64 = sd_cat(rbody, 0, "{\"action\":\"RESTART\",\"service\":\"" as *u8) 3214 b = md_cat_slice(rbody, b, body, soff[0], sn[0]) 3215 b = sd_cat(rbody, b, "\",\"rc\":" as *u8) 3216 b = sd_catn(rbody, b, rc) 3217 b = sd_cat(rbody, b, "}" as *u8) 3218 return ma_emit_200(out, rbody) 3219} 3220 3221// /api/route: upsert a proxy_routes.conf row (host prefix port mode). The API-pure "expose a loopback daemon on 3222// the edge" primitive -- the last manual step in ingest->live-customizable-site. Reload with /api/restart 3223// service=sites (kill -> supervise guard respawns sites.elf -> re-reads the table). Fail-closed validation. 3224func ma_do_route(req: *u8, req_n: i64, out: *u8) -> i64 { 3225 let body_off: i64 = sd_body_off(req, req_n) 3226 let body: *u8 = ((req as i64) + body_off) as *u8 3227 let body_n: i64 = req_n - body_off 3228 let hoff: *i64 = sys_mmap(8) as *i64 3229 let hn: *i64 = sys_mmap(8) as *i64 3230 let poff: *i64 = sys_mmap(8) as *i64 3231 let pnn: *i64 = sys_mmap(8) as *i64 3232 let roff: *i64 = sys_mmap(8) as *i64 3233 let rn: *i64 = sys_mmap(8) as *i64 3234 let moff: *i64 = sys_mmap(8) as *i64 3235 let mn: *i64 = sys_mmap(8) as *i64 3236 if sd_form_field(body, body_n, "host" as *u8, 4, hoff, hn) != 1 { return ma_emit_400(out, "{\"error\":\"missing host (e.g. andelinwest.com)\"}" as *u8) } 3237 if sd_form_field(body, body_n, "prefix" as *u8, 6, poff, pnn) != 1 { return ma_emit_400(out, "{\"error\":\"missing prefix (path prefix, e.g. /site)\"}" as *u8) } 3238 if sd_form_field(body, body_n, "port" as *u8, 4, roff, rn) != 1 { return ma_emit_400(out, "{\"error\":\"missing port (loopback daemon port 1024-65535)\"}" as *u8) } 3239 let hostb: *u8 = sys_mmap(96) 3240 let prefb: *u8 = sys_mmap(96) 3241 let modeb: *u8 = sys_mmap(32) 3242 md_copy_slice_z(hostb, body, hoff[0], hn[0], 95) 3243 md_copy_slice_z(prefb, body, poff[0], pnn[0], 95) 3244 if sd_form_field(body, body_n, "mode" as *u8, 4, moff, mn) == 1 { md_copy_slice_z(modeb, body, moff[0], mn[0], 31) } else { md_copy_slice_z(modeb, "buffered" as *u8, 0, 8, 31) } 3245 let port: i64 = md_slice_atoi(body, roff[0], rn[0]) 3246 if md_route_valid(hostb, prefb, port, modeb) != 1 { return ma_emit_400(out, "{\"error\":\"invalid route: host=[a-z0-9.-] with a dot, prefix=/[a-z0-9/_-], port 1024-65535, mode buffered|stream|gated\"}" as *u8) } 3247 if ma_confirmed(req, req_n) == 0 { return ma_emit_400(out, "{\"error\":\"route write requires confirm=yes (it edits the edge proxy table)\"}" as *u8) } 3248 // path = NX_SD2_PROXY_CONF in nx_sites_daemon_v2 (the table sites.elf actually loads; knowledge/hosting does NOT exist on the NAS) 3249 if md_route_append("/volume1/homes/elderwesto/nishihost/proxy_routes.conf" as *u8, hostb, prefb, port, modeb) != 1 { return ma_emit_400(out, "{\"error\":\"route write failed (proxy_routes.conf unreadable/empty or not writable); live table untouched\"}" as *u8) } 3250 let rb: *u8 = sys_mmap(512) 3251 var b: i64 = sd_cat(rb, 0, "{\"action\":\"ROUTE-ADDED\",\"host\":\"" as *u8) 3252 b = md_cat_slice(rb, b, body, hoff[0], hn[0]) 3253 b = sd_cat(rb, b, "\",\"prefix\":\"" as *u8) 3254 b = md_cat_slice(rb, b, body, poff[0], pnn[0]) 3255 b = sd_cat(rb, b, "\",\"port\":" as *u8) 3256 b = sd_catn(rb, b, port) 3257 b = sd_cat(rb, b, ",\"note\":\"row upserted; POST /api/restart service=sites confirm=yes to reload the edge\"}" as *u8) 3258 return ma_emit_200(out, rb) 3259} 3260 3261// /api/srcwrite?path=<rel>&confirm=yes : MCP-native SOURCE WRITE — the last shell-retiring primitive. Writes the 3262// raw request body to an ALLOWLISTED path (knowledge/compare/* or buildroot/runtime/*) under the NAS home, 3263// atomically (.new + renameat). Fail-closed: path must be in the allowlist, no "..", charset [a-z0-9_/.-], and 3264// end in a source/data extension (.matrix/.nx/.q/.axes). This lets Claude sync a .matrix or a .nx source to the 3265// NAS over MCP (nx_mgmt POST /api/srcwrite?path=... <content>) -> then /api/build + /api/compare/regen, ZERO WSL. 3266func sd_ends(s: *u8, n: i64, suf: *u8) -> i64 { 3267 let sl: i64 = sd_len(suf) 3268 if sl > n { return 0 } 3269 var i: i64 = 0 3270 while i < sl { if s[n-sl+i] != suf[i] { return 0 } i=i+1 } 3271 return 1 3272} 3273func ma_srcwrite_pathok(p: *u8, n: i64) -> i64 { 3274 if n < 8 { return 0 } 3275 if n > 200 { return 0 } 3276 var i: i64 = 0 3277 while i < n { 3278 let c: i64 = p[i] as i64 3279 var ok: i64 = 0 3280 if c>=97 { if c<=122 { ok=1 } } 3281 if c>=48 { if c<=57 { ok=1 } } 3282 if c==47 { ok=1 } 3283 if c==46 { ok=1 } 3284 if c==95 { ok=1 } 3285 if c==45 { ok=1 } 3286 if ok==0 { return 0 } 3287 if c==46 { if i+1<n { if p[i+1]==(46 as u8) { return 0 } } } // no ".." 3288 i=i+1 3289 } 3290 let kc: i64 = sd_starts(p, n, "knowledge/compare/" as *u8) 3291 let rc: i64 = sd_starts(p, n, "runtime/" as *u8) 3292 if kc==0 { if rc==0 { return 0 } } 3293 // extension gate: .matrix/.nx/.q/.axes 3294 if sd_ends(p, n, ".matrix" as *u8)==1 { return 1 } 3295 if sd_ends(p, n, ".nx" as *u8)==1 { return 1 } 3296 if sd_ends(p, n, ".q" as *u8)==1 { return 1 } 3297 if sd_ends(p, n, ".axes" as *u8)==1 { return 1 } 3298 return 0 3299} 3300func ma_do_srcwrite(req: *u8, req_n: i64, out: *u8) -> i64 { 3301 let body_off: i64 = sd_body_off(req, req_n) 3302 let body: *u8 = ((req as i64) + body_off) as *u8 3303 let body_n: i64 = req_n - body_off 3304 let poff: *i64 = sys_mmap(8) as *i64 3305 let pn: *i64 = sys_mmap(8) as *i64 3306 let coff: *i64 = sys_mmap(8) as *i64 3307 let cn: *i64 = sys_mmap(8) as *i64 3308 if sd_form_field(body, body_n, "path" as *u8, 4, poff, pn) != 1 { return ma_emit_400(out, "{\"error\":\"missing path= (knowledge/compare/* or runtime/*)\"}" as *u8) } 3309 let pb: *u8 = sys_mmap(256) 3310 md_copy_slice_z(pb, body, poff[0], pn[0], 255) 3311 if ma_srcwrite_pathok(pb, pn[0]) != 1 { return ma_emit_400(out, "{\"error\":\"path not allowlisted: knowledge/compare/* or runtime/*, ext .matrix/.nx/.q/.axes, no .., [a-z0-9_/.-]\"}" as *u8) } 3312 if ma_confirmed(req, req_n) == 0 { return ma_emit_400(out, "{\"error\":\"srcwrite requires confirm=yes\"}" as *u8) } 3313 // content = the LAST form field (raw slice; caller must NOT put '&' in the content -- send it last, unencoded) 3314 // ROOT FIX 2026-07-31: content used to go through sd_form_field, which TERMINATES AT the ampersand. 3315 // But ampersand is BITWISE AND -- an ordinary operator -- so this route silently TRUNCATED most real 3316 // NishiLang source at the first `x & mask`. It could not even carry its own repair: this very file 3317 // contains 29 of them. A transport whose delimiter is a language operator will silently truncate real 3318 // source, and it truncates rather than refusing, which is the worse failure. 3319 // content is DOCUMENTED as the LAST field, so the correct read is "everything after content= to the 3320 // end of the body" -- no delimiter, nothing to collide with. Field-BOUNDARY matched so a trailing 3321 // "...&mycontent=" cannot impersonate the real field. 3322 var csc: i64 = 0 - 1 3323 var cq: i64 = 0 3324 while cq + 8 <= body_n { 3325 var atb: i64 = 0 3326 if cq == 0 { atb = 1 } 3327 if cq > 0 { if body[cq - 1] == (38 as u8) { atb = 1 } } 3328 if atb == 1 { 3329 if body[cq] == (99 as u8) { if body[cq+1] == (111 as u8) { if body[cq+2] == (110 as u8) { 3330 if body[cq+3] == (116 as u8) { if body[cq+4] == (101 as u8) { if body[cq+5] == (110 as u8) { 3331 if body[cq+6] == (116 as u8) { if body[cq+7] == (61 as u8) { csc = cq + 8; break } } } } } } } } 3332 } 3333 cq = cq + 1 3334 } 3335 if csc < 0 { return ma_emit_400(out, "{\"error\":\"missing content= (the file body; send it as the LAST field)\"}" as *u8) } 3336 coff[0] = csc 3337 cn[0] = body_n - csc 3338 if cn[0] < 1 { return ma_emit_400(out, "{\"error\":\"empty content\"}" as *u8) } 3339 if cn[0] > 2000000 { return ma_emit_400(out, "{\"error\":\"content too large (>2MB); chunk via /api/upload for binaries\"}" as *u8) } 3340 // resolve under buildroot/ (verified NAS layout: both runtime/ AND knowledge/compare/ live under nishihost/ 3341 // buildroot/ -- that's where /api/build compiles from and where nishi_compare_regen reads the .matrix). atomic. 3342 let full: *u8 = sys_mmap(512) 3343 var fo: i64 = sd_cat(full, 0, "/volume1/homes/elderwesto/nishihost/buildroot/" as *u8) 3344 fo = md_cat_slice(full, fo, body, poff[0], pn[0]) 3345 let tmp: *u8 = sys_mmap(560) 3346 var to: i64 = 0 3347 var ci: i64 = 0 3348 while ci < fo { tmp[to]=full[ci]; to=to+1; ci=ci+1 } 3349 to = sd_cat(tmp, to, ".new" as *u8) 3350 let fd: i64 = sys_openat_wr(tmp, 420) // 0644 3351 if fd < 0 { return ma_emit_400(out, "{\"error\":\"open-for-write failed (dir missing/not writable); live file untouched\"}" as *u8) } 3352 var w: i64 = 0 3353 while w < cn[0] { let kw: i64 = sys_write(fd, ((body as i64)+coff[0]+w) as *u8, cn[0]-w); if kw<=0 { w=cn[0] } else { w=w+kw } } 3354 sys_close(fd) 3355 if sys_renameat(tmp, full) != 0 { return ma_emit_400(out, "{\"error\":\"atomic rename failed; live file untouched\"}" as *u8) } 3356 let rb: *u8 = sys_mmap(512) 3357 var b: i64 = sd_cat(rb, 0, "{\"action\":\"SRC-WRITTEN\",\"path\":\"" as *u8) 3358 b = md_cat_slice(rb, b, body, poff[0], pn[0]) 3359 b = sd_cat(rb, b, "\",\"bytes\":" as *u8) 3360 b = sd_catn(rb, b, cn[0]) 3361 b = sd_cat(rb, b, ",\"note\":\"synced over MCP; /api/build or /api/compare/regen to publish\"}" as *u8) 3362 return ma_emit_200(out, rb) 3363} 3364 3365// ---- routing (transport) ---------------------------------------------------------------------------- 3366 3367func ma_is_write_route(path: *u8, pn: i64) -> i64 { 3368 if sd_starts(path, pn, "/api/migrate" as *u8) == 1 { return 1 } 3369 if sd_starts(path, pn, "/api/update" as *u8) == 1 { return 1 } 3370 return 0 3371} 3372 3373func ma_path_eq(path: *u8, pn: i64, s: *u8) -> i64 { 3374 let sl: i64 = sd_len(s) 3375 if pn != sl { return 0 } 3376 return sd_starts(path, pn, s) 3377} 3378 3379// the VISIBLE health dashboard: a self-contained no-cookie SPA (mirrors nx_status_daemon sd_shell). Login form -> 3380// POST /api/login -> token in sessionStorage -> fetch /api/health + /api/services with X-Nishi-Session -> render 3381// tiles. The shell is PUBLIC (just a login form); the DATA stays gated by ma_authed. THIS is what makes the 3382// already-built control plane SEEN at nishifamily.com/health behind the operator's login. [[feedback-tools-must-surface-in-ui]] 3383// C1 FLEET UPGRADE (containers SOTA path, beat-Portainer rung 1): the dashboard now renders per-service FLEET 3384// CARDS (state badge, port, procs, dup/crash-loop/port-squatter warnings from the same snapshot the supervisor 3385// writes) + allowlisted Restart actions (POST /api/restart, confirm=yes, owner-level -- same gate as ever) + 3386// the /api/deploy_status strip + an SSE live dot (/api/events retry:5000 = a 5s live tick). All same-origin, 3387// self-contained, no third-party bytes. Portainer needs a root-equivalent engine socket; this rides OUR engine. 3388func ma_emit_dashboard(out: *u8) -> i64 { 3389 var o: i64 = 0 3390 o = sd_cat(out, o, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n" as *u8) 3391 o = sd_cat(out, o, "<!DOCTYPE html><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi - Fleet</title>" as *u8) 3392 o = sd_cat(out, o, "<style>body{font-family:-apple-system,Segoe UI,sans-serif;max-width:1080px;margin:4vh auto;padding:0 20px;color:#1c1c1e}#login{max-width:330px}input{width:100%;padding:10px;margin:.45rem 0;box-sizing:border-box;border:1px solid #ccc;border-radius:7px}button{padding:10px 18px;border:0;border-radius:7px;background:#0a6;color:#fff;font-size:1rem;cursor:pointer}.e{color:#b00;min-height:1.2em}.card{border:1px solid #e2e2e6;border-radius:10px;padding:14px 18px;margin:14px 0;background:#fbfbfd}.card h3{margin:.2rem 0 .6rem}pre{white-space:pre-wrap;word-break:break-word;font-size:.85rem;background:#f2f2f7;padding:10px;border-radius:7px;margin:0}#bar{display:flex;justify-content:space-between;align-items:center}" as *u8) 3393 o = sd_cat(out, o, ".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px}.svc{border:1px solid #e2e2e6;border-radius:10px;padding:12px 14px;background:#fbfbfd}.svc h4{margin:0 0 6px;font-size:.95rem;word-break:break-all}.up{color:#0a6;font-weight:700}.down{color:#b00;font-weight:700}.meta{color:#666;font-size:.8rem;margin:2px 0}.warn{color:#b60;font-size:.8rem;font-weight:600}.sm{padding:5px 10px;font-size:.8rem;background:#345;margin-top:8px}.dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:#0a6;margin-left:10px;opacity:.2;transition:opacity .3s}.on{opacity:1}#sum{display:flex;gap:18px;flex-wrap:wrap;font-size:.9rem;color:#444;margin:10px 0}</style>" as *u8) 3394 o = sd_cat(out, o, "<div id=login><h2>&#128274; Nishi - Fleet</h2><input id=h placeholder=handle autocomplete=username autofocus><input id=p type=password placeholder=passphrase autocomplete=current-password><button id=b>Sign in</button><p id=e class=e></p></div>" as *u8) 3395 o = sd_cat(out, o, "<div id=content hidden><div id=bar><h2>&#128737;&#65039; Sovereign Fleet<span id=live class=dot></span></h2><button id=r>Refresh</button></div><div id=sum></div><div id=fleet class=grid></div><div id=tiles></div></div>" as *u8) 3396 o = sd_cat(out, o, "<script>var L=document.getElementById('login'),C=document.getElementById('content'),E=document.getElementById('e'),T=document.getElementById('tiles'),F=document.getElementById('fleet'),S=document.getElementById('sum'),D=document.getElementById('live'),lastL=0;" as *u8) 3397 o = sd_cat(out, o, "var RMAP={'nx_media_server_auth.elf':'reader','nx_torrent_gw.elf':'torrentgw','nx_torrent_daemon':'torrent'};" as *u8) 3398 o = sd_cat(out, o, "function esc(s){return String(s).split('&').join('&amp;').split('<').join('&lt;')}function tile(t,o){return '<div class=card><h3>'+t+'</h3><pre>'+esc(JSON.stringify(o,null,2))+'</pre></div>'}" as *u8) 3399 o = sd_cat(out, o, "function fleetCards(s){var a=(s&&s.services)||[],up=0,down=0,h='';for(var i=0;i<a.length;i++){var v=a[i];if(v.state=='UP'){up++}else{down++}h+='<div class=svc><h4>'+esc(v.name)+'</h4><div class=meta><span class='+(v.state=='UP'?'up':'down')+'>'+esc(v.state)+'</span> &middot; port '+esc(v.port)+' &middot; procs '+esc(v.procs)+'</div>';if(v.dup){h+='<div class=warn>duplicate procs</div>'}if(v.loop){h+='<div class=warn>crash-loop window</div>'}if(v.port_mismatch){h+='<div class=warn>port squatter pid '+esc(v.holder_pid)+': '+esc(v.holder_cmd||'')+'</div>'}var rs=RMAP[v.name];if(rs){h+='<button class=sm data-rs='+rs+' data-nm='+encodeURIComponent(v.name)+'>Restart</button>'}h+='</div>'}S.innerHTML='<span><b>'+a.length+'</b> services</span><span class=up>'+up+' up</span>'+(down?'<span class=down>'+down+' down</span>':'<span class=up>all serving</span>')+'<span id=dep class=meta></span>';return h}" as *u8) 3400 o = sd_cat(out, o, "function doRestart(rs,nm){if(!confirm('Restart '+nm+' (hostctl '+rs+')?')){return}var hdr={'X-Nishi-Session':sessionStorage.nx_sess||'','Content-Type':'application/x-www-form-urlencoded'};fetch('/api/restart',{method:'POST',headers:hdr,body:'service='+rs+'&confirm=yes'}).then(function(r){return r.json()}).then(function(j){alert('restart '+nm+' rc='+j.rc)}).catch(function(){alert('restart failed')});setTimeout(load,2500)}" as *u8) 3401 o = sd_cat(out, o, "F.onclick=function(ev){var t=ev.target;if(t&&t.getAttribute&&t.getAttribute('data-rs')){doRestart(t.getAttribute('data-rs'),decodeURIComponent(t.getAttribute('data-nm')))}};" as *u8) 3402 o = sd_cat(out, o, "function load(){lastL=Date.now();var hdr={'X-Nishi-Session':sessionStorage.nx_sess||''};fetch('/api/health',{headers:hdr}).then(function(r){if(r.ok){return r.json()}throw 0}).then(function(h){fetch('/api/services',{headers:hdr}).then(function(r){return r.ok?r.json():{}}).then(function(s){F.innerHTML=fleetCards(s);T.innerHTML=tile('System Health',h);L.hidden=true;C.hidden=false;fetch('/api/deploy_status',{headers:hdr}).then(function(r){return r.ok?r.json():{}}).then(function(d){var el=document.getElementById('dep');if(el){el.textContent='deploy: '+(d.state||d.status||'idle')}}).catch(function(){})})}).catch(function(){sessionStorage.removeItem('nx_sess');L.hidden=false;C.hidden=true})}" as *u8) 3403 o = sd_cat(out, o, "function sseGo(){try{var es=new EventSource('/api/events');es.addEventListener('tick',function(){D.className='dot on';setTimeout(function(){D.className='dot'},700);if(sessionStorage.nx_sess&&Date.now()-lastL>9000){load()}})}catch(e){}}" as *u8) 3404 o = sd_cat(out, o, "document.getElementById('b').onclick=function(){E.textContent='';var b='handle='+encodeURIComponent(document.getElementById('h').value)+'&passphrase='+encodeURIComponent(document.getElementById('p').value);fetch('/api/login',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:b}).then(function(r){if(r.ok){return r.json()}throw 0}).then(function(j){sessionStorage.nx_sess=j.token;load()}).catch(function(){E.textContent='Wrong handle or passphrase.'})};document.getElementById('r').onclick=load;if(sessionStorage.nx_sess){load()}sseGo();</script>" as *u8) 3405 return o 3406} 3407 3408// ---- resource-limits-api4 (per-client rate limiting) + idempotency-keys: BOUNDED static tables -> scale without 3409// unbounded growth. Client identity = a hash of the auth/forwarding header (loopback-safe; the edge forwards it). ---- 3410const RL_SLOTS: i64 = 128 3411const RL_WINDOW: i64 = 10 // seconds 3412const RL_MAX: i64 = 300 // requests / window / client (generous; protects vs floods, won't lock out an operator) 3413static rl_cid: *i64 3414static rl_win: *i64 3415static rl_cnt: *i64 3416const IDEM_SLOTS: i64 = 512 3417const IDEM_TTL: i64 = 3600 // seconds a processed Idempotency-Key is remembered 3418static idem_key: *i64 3419static idem_ts: *i64 3420// lazy-init the tables once (mmap zeroes them). Static POINTERS to mmap'd regions -> reliable (BSS static ARRAYS in 3421// the handler module are not, which crashed the first build on startup). 3422func ma_state_init() -> i64 { 3423 if (rl_cid as i64) == 0 { 3424 rl_cid = sys_mmap(RL_SLOTS * 8) as *i64 3425 rl_win = sys_mmap(RL_SLOTS * 8) as *i64 3426 rl_cnt = sys_mmap(RL_SLOTS * 8) as *i64 3427 idem_key = sys_mmap(IDEM_SLOTS * 8) as *i64 3428 idem_ts = sys_mmap(IDEM_SLOTS * 8) as *i64 3429 } 3430 return 0 3431} 3432func ma_slot(h: i64, slots: i64) -> i64 { var x: i64 = h; if x < 0 { x = 0 - x } return x - (x / slots) * slots } 3433// hash a header's value (bytes after "key" up to CR/LF). 0 => header absent. 3434func ma_hdr_hash(req: *u8, req_n: i64, key: *u8, keyn: i64) -> i64 { 3435 var i: i64 = 0 3436 while i + keyn < req_n { 3437 var k: i64 = 0 3438 while k < keyn { if req[i + k] != key[k] { k = keyn + 9 } else { k = k + 1 } } 3439 if k == keyn { 3440 var v: i64 = i + keyn 3441 if v < req_n { if req[v] == (32 as u8) { v = v + 1 } } 3442 var h: i64 = 0 3443 var go: i64 = 1 3444 while go == 1 { if v >= req_n { go = 0 } else { let c: i64 = req[v] as i64; if c == 13 { go = 0 } else { if c == 10 { go = 0 } else { h = h * 31 + c; v = v + 1 } } } } 3445 if h == 0 { h = 1 } 3446 return h 3447 } 3448 i = i + 1 3449 } 3450 return 0 3451} 3452func ma_client_id(req: *u8, req_n: i64) -> i64 { 3453 var h: i64 = ma_hdr_hash(req, req_n, "X-Nishi-Session:" as *u8, 16) 3454 if h == 0 { h = ma_hdr_hash(req, req_n, "Authorization:" as *u8, 14) } 3455 if h == 0 { h = ma_hdr_hash(req, req_n, "X-Forwarded-For:" as *u8, 16) } 3456 if h == 0 { h = 1 } 3457 return h 3458} 3459func ma_rate_ok(cid: i64, now: i64) -> i64 { 3460 ma_state_init() 3461 let s: i64 = ma_slot(cid, RL_SLOTS) 3462 if rl_cid[s] != cid { rl_cid[s] = cid; rl_win[s] = now; rl_cnt[s] = 0 } 3463 if now - rl_win[s] >= RL_WINDOW { rl_win[s] = now; rl_cnt[s] = 0 } 3464 rl_cnt[s] = rl_cnt[s] + 1 3465 if rl_cnt[s] > RL_MAX { return 0 } 3466 return 1 3467} 3468func ma_emit_429(out: *u8) -> i64 { 3469 let body: *u8 = "{\"type\":\"about:blank\",\"title\":\"Too Many Requests\",\"status\":429,\"detail\":\"per-client rate limit exceeded; retry after the window\"}" as *u8 3470 return ma_emit_json(out, "HTTP/1.1 429 Too Many Requests\r\nContent-Type: application/problem+json\r\nRetry-After: 10\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 3471} 3472func ma_idem_seen(ik: i64, now: i64) -> i64 { let s: i64 = ma_slot(ik, IDEM_SLOTS); if idem_key[s] == ik { if now - idem_ts[s] < IDEM_TTL { return 1 } } return 0 } 3473func ma_idem_record(ik: i64, now: i64) -> i64 { let s: i64 = ma_slot(ik, IDEM_SLOTS); idem_key[s] = ik; idem_ts[s] = now; return 0 } 3474func ma_emit_idem_replay(out: *u8) -> i64 { 3475 let body: *u8 = "{\"idempotent\":true,\"replayed\":true,\"note\":\"this Idempotency-Key was already processed in the TTL window; the original mutation was NOT re-executed\"}" as *u8 3476 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nIdempotent-Replayed: true\r\nConnection: close\r\nContent-Length: " as *u8, body, sd_len(body)) 3477} 3478// ---- /api/compare: concurrent-work coordination for Nishi Compare (registry SSOT + server-side hub regen) ---- 3479// GET /api/compare/registry (READ) -> text/plain, the EXACT registry SSOT (a valid registry file, save-as-is) 3480// POST /api/compare/upsert (ACT) -> body = ONE raw registry line "title|kind|href|radar|stat"; merged by the 3481// /compare/<domain> key inside href: different domains merge commutatively (concurrent sessions cannot clobber), 3482// same domain replaces with the previous line preserved in registry.hist; then the hub regenerates server-side. 3483// POST /api/compare/regen (ACT) -> regenerate hub index.html + api.json from the SSOT (idempotent recovery). 3484func ma_do_cmp_registry(out: *u8) -> i64 { 3485 let szp: *i64 = sys_mmap(16) as *i64 3486 let b: *u8 = md_read_file("knowledge/compare/registry" as *u8, szp) 3487 var n: i64 = szp[0] 3488 let e: *u8 = sys_mmap(8) 3489 var body: *u8 = e 3490 if (b as i64) != 0 { if n > 0 { body = b } } 3491 if (b as i64) == 0 { n = 0 } 3492 return ma_emit_json(out, "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\nContent-Length: " as *u8, body, n) 3493} 3494func ma_do_cmp_upsert(req: *u8, req_n: i64, out: *u8) -> i64 { 3495 let body_off: i64 = sd_body_off(req, req_n) 3496 let body: *u8 = ((req as i64) + body_off) as *u8 3497 var bn: i64 = req_n - body_off 3498 while bn > 0 { 3499 let ct: i64 = body[bn - 1] as i64 3500 if ct == 10 { bn = bn - 1 } else { if ct == 13 { bn = bn - 1 } else { break } } 3501 } 3502 if bn < 10 { return ma_emit_400(out, "{\"error\":\"body must be one raw registry line: title|kind|href|radar|stat\"}" as *u8) } 3503 if bn > 1600 { return ma_emit_400(out, "{\"error\":\"registry line too long (max 1600 bytes)\"}" as *u8) } 3504 var pipes: i64 = 0 3505 var i: i64 = 0 3506 while i < bn { 3507 let c2: i64 = body[i] as i64 3508 if c2 < 32 { return ma_emit_400(out, "{\"error\":\"control characters not allowed (single line only)\"}" as *u8) } 3509 if c2 == 124 { pipes = pipes + 1 } 3510 i = i + 1 3511 } 3512 if pipes < 4 { return ma_emit_400(out, "{\"error\":\"need 5 pipe-separated fields: title|kind|href|radar|stat\"}" as *u8) } 3513 let domb: *u8 = sys_mmap(128) 3514 let domn: i64 = md_cmp_domain_of(body, 0, bn, domb, 120) 3515 if domn < 1 { return ma_emit_400(out, "{\"error\":\"href field must be /compare/<domain>..., domain charset [a-z0-9_-]\"}" as *u8) } 3516 let repb: *i64 = sys_mmap(8) as *i64 3517 repb[0] = 0 3518 let entries: i64 = md_cmp_upsert(body, bn, domb, domn, repb) 3519 if entries < 0 { return ma_emit_400(out, "{\"error\":\"registry write failed (knowledge/compare dir missing on host?)\"}" as *u8) } 3520 let rg: i64 = md_cmp_regen() 3521 let rb: *u8 = sys_mmap(512) 3522 var b2: i64 = sd_cat(rb, 0, "{\"action\":\"UPSERTED\",\"domain\":\"" as *u8) 3523 b2 = sd_cat(rb, b2, domb) 3524 b2 = sd_cat(rb, b2, "\",\"replaced\":" as *u8) 3525 b2 = sd_catn(rb, b2, repb[0]) 3526 b2 = sd_cat(rb, b2, ",\"entries\":" as *u8) 3527 b2 = sd_catn(rb, b2, entries) 3528 b2 = sd_cat(rb, b2, ",\"regen\":\"" as *u8) 3529 if rg == 1 { b2 = sd_cat(rb, b2, "OK" as *u8) } else { b2 = sd_cat(rb, b2, "FAILED" as *u8) } 3530 b2 = sd_cat(rb, b2, "\",\"hub\":\"/compare\"}" as *u8) 3531 rb[b2] = 0 as u8 3532 return ma_emit_200(out, rb) 3533} 3534// POST /api/compare/publish (ACT, confirm=yes): promote the STAGED compare.page.new (from chunked /api/upload 3535// target=compare.page) as a per-domain compare artifact. Form fields: domain=<atom> kind=<page|frontier|bench|api> 3536// sha256=<hex-of-staged-bytes> confirm=yes. The REQUIRED sha256 pins THIS publish to THIS content, so concurrent 3537// sessions sharing the one staging slot can never cross-publish each other's bytes (the concurrency contract). 3538// Paths are derived server-side from the validated domain atom + kind enum -- no caller paths, no traversal. 3539func ma_do_cmp_publish(req: *u8, req_n: i64, out: *u8) -> i64 { 3540 if ma_confirmed(req, req_n) != 1 { return ma_emit_400(out, "{\"error\":\"compare publish requires confirm=yes\"}" as *u8) } 3541 let body_off: i64 = sd_body_off(req, req_n) 3542 let body: *u8 = ((req as i64) + body_off) as *u8 3543 let body_n: i64 = req_n - body_off 3544 let doff: *i64 = sys_mmap(8) as *i64 3545 let dn: *i64 = sys_mmap(8) as *i64 3546 if sd_form_field(body, body_n, "domain" as *u8, 6, doff, dn) != 1 { return ma_emit_400(out, "{\"error\":\"missing domain\"}" as *u8) } 3547 let domb: *u8 = sys_mmap(128) 3548 if md_cmp_dom_ok(body, doff[0], dn[0], domb) != 1 { return ma_emit_400(out, "{\"error\":\"invalid domain (charset [a-z0-9_-], first char alphanumeric, max 60)\"}" as *u8) } 3549 let koff: *i64 = sys_mmap(8) as *i64 3550 let kn: *i64 = sys_mmap(8) as *i64 3551 if sd_form_field(body, body_n, "kind" as *u8, 4, koff, kn) != 1 { return ma_emit_400(out, "{\"error\":\"missing kind (page|frontier|bench|api)\"}" as *u8) } 3552 var kind: i64 = 0 3553 if md_slice_eq(body, koff[0], kn[0], "page" as *u8, 0, 4) == 1 { kind = 1 } 3554 if md_slice_eq(body, koff[0], kn[0], "frontier" as *u8, 0, 8) == 1 { kind = 2 } 3555 if md_slice_eq(body, koff[0], kn[0], "bench" as *u8, 0, 5) == 1 { kind = 3 } 3556 if md_slice_eq(body, koff[0], kn[0], "api" as *u8, 0, 3) == 1 { kind = 4 } 3557 if kind == 0 { return ma_emit_400(out, "{\"error\":\"unknown kind (page|frontier|bench|api)\"}" as *u8) } 3558 let hoff: *i64 = sys_mmap(8) as *i64 3559 let hn: *i64 = sys_mmap(8) as *i64 3560 if sd_form_field(body, body_n, "sha256" as *u8, 6, hoff, hn) != 1 { return ma_emit_400(out, "{\"error\":\"missing sha256 (hex of the uploaded compare.page bytes -- pins the publish to your content)\"}" as *u8) } 3561 let szp: *i64 = sys_mmap(16) as *i64 3562 let stg: *u8 = md_read_file("compare.page.new" as *u8, szp) 3563 if (stg as i64) == 0 { return ma_emit_400(out, "{\"error\":\"nothing staged; chunk-upload to /api/upload?target=compare.page first\"}" as *u8) } 3564 let dig: *u8 = sys_mmap(32) 3565 sha256_digest(stg, szp[0], dig) 3566 let hexbuf: *u8 = sys_mmap(72) 3567 mau_hex32(dig, hexbuf) 3568 if mau_hex_eq(body, hoff[0], hn[0], hexbuf) != 1 { return ma_emit_400(out, "{\"error\":\"sha256 mismatch: the staging slot holds different bytes (another session likely re-staged); re-upload and retry\"}" as *u8) } 3569 if md_cmp_publish(domb, kind) != 1 { return ma_emit_400(out, "{\"error\":\"publish failed sanity/install (html must start with < and json with left-brace, min 200 bytes); live files untouched\"}" as *u8) } 3570 fio_unlink("compare.page.new" as *u8) 3571 var rg: i64 = 0 - 1 3572 if kind == 4 { rg = md_cmp_regen() } 3573 let rb: *u8 = sys_mmap(512) 3574 var b2: i64 = sd_cat(rb, 0, "{\"action\":\"PUBLISHED\",\"domain\":\"" as *u8) 3575 b2 = sd_cat(rb, b2, domb) 3576 b2 = sd_cat(rb, b2, "\",\"kind\":\"" as *u8) 3577 b2 = md_cat_slice(rb, b2, body, koff[0], kn[0]) 3578 b2 = sd_cat(rb, b2, "\",\"bytes\":" as *u8) 3579 b2 = sd_catn(rb, b2, szp[0]) 3580 if kind == 4 { 3581 b2 = sd_cat(rb, b2, ",\"hub_regen\":\"" as *u8) 3582 if rg == 1 { b2 = sd_cat(rb, b2, "OK\"" as *u8) } else { b2 = sd_cat(rb, b2, "FAILED\"" as *u8) } 3583 } 3584 b2 = sd_cat(rb, b2, "}" as *u8) 3585 rb[b2] = 0 as u8 3586 return ma_emit_200(out, rb) 3587} 3588func ma_do_cmp_regen(out: *u8) -> i64 { 3589 let rg: i64 = md_cmp_regen() 3590 if rg == 1 { return ma_emit_200(out, "{\"action\":\"REGEN\",\"verdict\":\"OK -- hub index.html + api.json regenerated from the registry SSOT\"}" as *u8) } 3591 return ma_emit_200(out, "{\"action\":\"REGEN-FAILED\",\"verdict\":\"generator output failed sanity (hub elf missing or registry unreadable); live files untouched\"}" as *u8) 3592} 3593 3594// THE ROUTER: pure function, request bytes -> response bytes (no socket). gate drives this directly. 3595// PUBLIC LIVE-TELEMETRY collector (2026-07-13, operator mandate: measure the LIVE rooms server-side, not a VM, 3596// not client screenshots). The video client POSTs a compact QoE JSON line every ~5s (room, member, build, rtt, 3597// sndFps, encMs, res, per-peer dec/rx/drop/drift). Append-only ring -> vqoe.jsonl (CWD=nishihost); GET returns 3598// the tail. This is the real-service pattern: the operator + the improvement loop read live truth directly. 3599// Unauth by design (private family telemetry, no abuse value); per-line capped at 512B. 3600func ma_vqoe(req: *u8, req_n: i64, out: *u8) -> i64 { 3601 let is_post: i64 = (req[0] == (80 as u8)) as i64 // 'P' = POST 3602 if is_post == 1 { 3603 let boff: i64 = sd_body_off(req, req_n) 3604 if boff <= 0 { return ma_emit_200(out, "{\"ok\":0}" as *u8) } 3605 var blen: i64 = req_n - boff 3606 if blen > 512 { blen = 512 } 3607 if blen <= 1 { return ma_emit_200(out, "{\"ok\":0}" as *u8) } 3608 let fd: i64 = sys_openat_append("vqoe.jsonl" as *u8, 0x1a4) 3609 if fd >= 0 { sys_write(fd, ((req as i64) + boff) as *u8, blen); sys_write(fd, "\n" as *u8, 1); sys_close(fd) } 3610 return ma_emit_200(out, "{\"ok\":1}" as *u8) 3611 } 3612 let szb: *i64 = sys_mmap(8) as *i64 3613 let data: *u8 = md_read_file("vqoe.jsonl" as *u8, szb) 3614 if (data as i64) == 0 { return ma_emit_200(out, "{\"vqoe\":\"empty\"}" as *u8) } 3615 let cn: i64 = szb[0] 3616 var st: i64 = 0 3617 if cn > 16384 { st = cn - 16384; while st < cn { if data[st] == (10 as u8) { st = st + 1; break } st = st + 1 } } 3618 let tail: *u8 = sys_mmap(20480) 3619 var w: i64 = 0 3620 var i: i64 = st 3621 while i < cn { tail[w] = data[i]; w = w + 1; i = i + 1 } 3622 tail[w] = 0 as u8 3623 return ma_emit_200(out, tail) 3624} 3625// One 8-byte slot per sd_find_path out-param. sys_mmap rounds each to a FULL PAGE, which is why 3626// leaking two of them per request walked the daemon into VMA/RLIMIT_AS exhaustion (debt 1785517161). 3627const MA_PATHPTR_BUF: i64 = 8 3628 3629func ma_handle(ctx: *NxAuthContext, req: *u8, req_n: i64, snapfile: *u8, out: *u8) -> i64 { 3630 let poff: *i64 = sys_mmap(MA_PATHPTR_BUF) as *i64 3631 let plen: *i64 = sys_mmap(MA_PATHPTR_BUF) as *i64 3632 poff[0] = 0 3633 plen[0] = 0 3634 sd_find_path(req, req_n, poff, plen) 3635 let path: *u8 = ((req as i64) + poff[0]) as *u8 3636 let pn: i64 = plen[0] 3637 // FREE AT THE EXTRACTION POINT. path and pn are plain scalars already copied out, so both buffers 3638 // are dead from here on. Freeing here rather than before each route return makes the ~60 returns 3639 // below leak-free with ZERO edits to the route table -- the fix that does not become a patch 3640 // cascade (rule 3). Do NOT move these frees down into the router. 3641 sys_munmap(poff as *u8, MA_PATHPTR_BUF) 3642 sys_munmap(plen as *u8, MA_PATHPTR_BUF) 3643 if sd_starts(path, pn, "/api/docs" as *u8) == 1 { return ma_docs(out) } // nishi-native API console (public, self-contained) 3644 if sd_starts(path, pn, "/api/inventory" as *u8) == 1 { return ma_inventory(out) } // inventory-mgmt (public discovery) 3645 if sd_starts(path, pn, "/api/events" as *u8) == 1 { return ma_events(out) } // webhooks/SSE event stream (public) 3646 if sd_starts(path, pn, "/api/rpc.contract" as *u8) == 1 { return ma_rpc_contract(out) } // binary RPC contract (public) 3647 if sd_starts(path, pn, "/api/rpc" as *u8) == 1 { return ma_rpc(req, req_n, out) } // sovereign binary contract-first RPC 3648 if sd_starts(path, pn, "/api/vqoe" as *u8) == 1 { return ma_vqoe(req, req_n, out) } // PUBLIC live-room QoE telemetry (POST report / GET tail) 3649 if sd_starts(path, pn, "/api/adnet/invoice" as *u8) == 1 { return ma_do_adnet_invoice(out) } 3650 if sd_starts(path, pn, "/api/adnet/creative" as *u8) == 1 { return ma_do_adnet_creative(req, req_n, out) } 3651 if sd_starts(path, pn, "/api/put_source" as *u8) == 1 { // pure-API source write (owner): body -> runtime/<name>.nx, then /api/build compiles it. Standalone (not in the else-chain) to avoid the deep-else-if miscompile. 3652 let lvps: i64 = ma_level_of(ctx, req, req_n) 3653 if lvps < 0 { return sd_emit_401_json(out) } 3654 if lvps < MA_LVL_ACT { return ma_emit_403(out) } 3655 return ma_do_put_source(req, req_n, out) 3656 } 3657 if sd_starts(path, pn, "/api/organ_run" as *u8) == 1 { // seq1426: run an ALREADY-VETTED organ -- a second door onto the tool_allowlist set, never a wider one. 3658 let lvor: i64 = ma_level_of(ctx, req, req_n) 3659 if lvor < 0 { return sd_emit_401_json(out) } 3660 if lvor < MA_LVL_ACT { return ma_emit_403(out) } 3661 return ma_do_organ_run(req, req_n, out) 3662 } 3663 if sd_starts(path, pn, "/api/gate_run" as *u8) == 1 { // seq1349 SOVEREIGN RUN VERB. Standalone (not the else-chain) for the same deep-else-if reason as put_source. 3664 let lvgr: i64 = ma_level_of(ctx, req, req_n) 3665 if lvgr < 0 { return sd_emit_401_json(out) } 3666 if lvgr < MA_LVL_ACT { return ma_emit_403(out) } 3667 return ma_do_gate_run(req, req_n, out) 3668 } 3669 if sd_starts(path, pn, "/api/proc_kill" as *u8) == 1 { // seq1383 SOVEREIGN PROCESS MANAGEMENT: clear stray/orphaned organs without ssh; the supervisor is unreachable by construction. 3670 let lvpk: i64 = ma_level_of(ctx, req, req_n) 3671 if lvpk < 0 { return sd_emit_401_json(out) } 3672 if lvpk < MA_LVL_ACT { return ma_emit_403(out) } 3673 return ma_do_proc_kill(req, req_n, out) 3674 } 3675 let is_post: i64 = (req[0] == 80 as u8) as i64 3676 let nowr: i64 = sys_now_realtime_sec() 3677 // rate-limit client id from the HEADER REGION only (same body-scan hazard as the idem fix below) 3678 var rid_end: i64 = sd_body_off(req, req_n) 3679 if rid_end <= 0 { rid_end = req_n } 3680 if rid_end > req_n { rid_end = req_n } 3681 if ma_rate_ok(ma_client_id(req, rid_end), nowr) == 0 { return ma_emit_429(out) } // resource-limits-api4 3682 if is_post == 1 { // idempotency-keys: dedup an AUTHED mutating retry carrying a repeated Idempotency-Key (an 3683 // unauthed 401'd attempt must NOT record the key, else a legit authed retry would be wrongly replayed; login too) 3684 // HEADER-BOUNDED SCAN (2026-07-10 root-cause): ma_hdr_hash over the FULL request also scanned the BODY -- 3685 // uploading any binary that EMBEDS the literal "Idempotency-Key:" (this daemon's own elf!) minted a phantom 3686 // deterministic key, so a retried chunk replay-blocked and the upload silently never wrote. Scan headers only. 3687 // /api/upload is EXEMPT outright: its seq gate + final-chunk sha256 + replay-ack ARE its idempotency. 3688 var hdr_end: i64 = sd_body_off(req, req_n) 3689 if hdr_end <= 0 { hdr_end = req_n } 3690 if hdr_end > req_n { hdr_end = req_n } 3691 if sd_starts(path, pn, "/api/upload" as *u8) == 0 { 3692 var hasauth: i64 = ma_hdr_hash(req, hdr_end, "X-Nishi-Session:" as *u8, 16) 3693 if hasauth == 0 { hasauth = ma_hdr_hash(req, hdr_end, "Authorization:" as *u8, 14) } 3694 if hasauth != 0 { 3695 let ik: i64 = ma_hdr_hash(req, hdr_end, "Idempotency-Key:" as *u8, 16) 3696 if ik != 0 { if ma_idem_seen(ik, nowr) == 1 { return ma_emit_idem_replay(out) } ma_idem_record(ik, nowr) } 3697 } 3698 } 3699 } 3700 var o: i64 = 0 3701 if sd_starts(path, pn, "/health" as *u8) == 1 { 3702 o = ma_emit_dashboard(out) 3703 } else { if sd_starts(path, pn, "/api/login" as *u8) == 1 { 3704 if is_post == 1 { o = ma_login(ctx, req, req_n, out) } else { if ma_wants_text(req, req_n) == 1 { o = ma_index_text(out) } else { o = ma_index(out) } } 3705 } else { if sd_starts(path, pn, "/api/health" as *u8) == 1 { 3706 let lv: i64 = ma_level_of(ctx, req, req_n) 3707 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_READ { o = ma_emit_health_file(snapfile, out) } else { o = ma_emit_403(out) } } 3708 } else { if sd_starts(path, pn, "/api/services" as *u8) == 1 { 3709 let lv: i64 = ma_level_of(ctx, req, req_n) 3710 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_READ { o = ma_emit_services_file(snapfile, out) } else { o = ma_emit_403(out) } } 3711 } else { if sd_starts(path, pn, "/api/shards" as *u8) == 1 { 3712 let lv: i64 = ma_level_of(ctx, req, req_n) 3713 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_READ { o = ma_emit_shards(out) } else { o = ma_emit_403(out) } } 3714 } else { if sd_starts(path, pn, "/api/nodes" as *u8) == 1 { 3715 let lv: i64 = ma_level_of(ctx, req, req_n) 3716 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_READ { o = ma_emit_nodes(out) } else { o = ma_emit_403(out) } } 3717 } else { if sd_starts(path, pn, "/api/upload" as *u8) == 1 { 3718 let lv: i64 = ma_level_of(ctx, req, req_n) 3719 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_upload(req, req_n, out) } else { o = ma_emit_403(out) } } 3720 } else { if sd_starts(path, pn, "/api/deploy_status" as *u8) == 1 { 3721 let lv: i64 = ma_level_of(ctx, req, req_n) 3722 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_READ { o = ma_do_deploy_status(out) } else { o = ma_emit_403(out) } } 3723 } else { if sd_starts(path, pn, "/api/build" as *u8) == 1 { 3724 let lv3: i64 = ma_level_of(ctx, req, req_n) 3725 if lv3 < 0 { o = sd_emit_401_json(out) } else { if lv3 >= MA_LVL_ACT { o = ma_do_build(req, req_n, out) } else { o = ma_emit_403(out) } } 3726 } else { if sd_starts(path, pn, "/api/unpack" as *u8) == 1 { 3727 let lv2: i64 = ma_level_of(ctx, req, req_n) 3728 if lv2 < 0 { o = sd_emit_401_json(out) } else { if lv2 >= MA_LVL_ACT { o = ma_do_unpack(req, req_n, out) } else { o = ma_emit_403(out) } } 3729 } else { if sd_starts(path, pn, "/api/deploy" as *u8) == 1 { 3730 let lv: i64 = ma_level_of(ctx, req, req_n) 3731 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_deploy(req, req_n, out) } else { o = ma_emit_403(out) } } 3732 } else { if sd_starts(path, pn, "/api/promote_content" as *u8) == 1 { 3733 let lv: i64 = ma_level_of(ctx, req, req_n) 3734 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_promote_content(req, req_n, out) } else { o = ma_emit_403(out) } } 3735 // ORDER IS LOAD-BEARING: dispatch is sd_starts PREFIX matching, and "/api/promote" is a prefix of 3736 // "/api/promote_toolchain". This row MUST stay above the bare /api/promote row below (the same 3737 // reason /api/promote_content already sits above it) or every toolchain promote silently lands in 3738 // the ORGAN promote handler, gets refused by md_promote_organ_ok, and the endpoint looks dead. 3739 } else { if sd_starts(path, pn, "/api/promote_toolchain" as *u8) == 1 { 3740 let lv: i64 = ma_level_of(ctx, req, req_n) 3741 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_promote_toolchain(req, req_n, out) } else { o = ma_emit_403(out) } } 3742 } else { if sd_starts(path, pn, "/api/promote" as *u8) == 1 { 3743 let lv: i64 = ma_level_of(ctx, req, req_n) 3744 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_promote(req, req_n, out) } else { o = ma_emit_403(out) } } 3745 } else { if sd_starts(path, pn, "/api/tools/register" as *u8) == 1 { 3746 let lv: i64 = ma_level_of(ctx, req, req_n) 3747 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_tools_register(req, req_n, out) } else { o = ma_emit_403(out) } } 3748 } else { if sd_starts(path, pn, "/api/cap/mint" as *u8) == 1 { 3749 let lv: i64 = ma_level_of(ctx, req, req_n) 3750 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_cap_mint(req, req_n, out) } else { o = ma_emit_403(out) } } 3751 } else { if sd_starts(path, pn, "/api/rollback" as *u8) == 1 { 3752 let lv: i64 = ma_level_of(ctx, req, req_n) 3753 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_rollback(req, req_n, out) } else { o = ma_emit_403(out) } } 3754 } else { if sd_starts(path, pn, "/api/reconcile" as *u8) == 1 { 3755 let lv: i64 = ma_level_of(ctx, req, req_n) 3756 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_reconcile(req, req_n, out) } else { o = ma_emit_403(out) } } 3757 } else { if sd_starts(path, pn, "/api/restart" as *u8) == 1 { 3758 let lv: i64 = ma_level_of(ctx, req, req_n) 3759 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_restart(req, req_n, out) } else { o = ma_emit_403(out) } } 3760 } else { if sd_starts(path, pn, "/api/route" as *u8) == 1 { 3761 let lv: i64 = ma_level_of(ctx, req, req_n) 3762 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_route(req, req_n, out) } else { o = ma_emit_403(out) } } 3763 } else { if sd_starts(path, pn, "/api/hostctl" as *u8) == 1 { 3764 let lv: i64 = ma_level_of(ctx, req, req_n) 3765 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_hostctl(req, req_n, out) } else { o = ma_emit_403(out) } } 3766 } else { if sd_starts(path, pn, "/api/compare/publish" as *u8) == 1 { 3767 let lv: i64 = ma_level_of(ctx, req, req_n) 3768 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_cmp_publish(req, req_n, out) } else { o = ma_emit_403(out) } } 3769 } else { if sd_starts(path, pn, "/api/compare/upsert" as *u8) == 1 { 3770 let lv: i64 = ma_level_of(ctx, req, req_n) 3771 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_cmp_upsert(req, req_n, out) } else { o = ma_emit_403(out) } } 3772 } else { if sd_starts(path, pn, "/api/compare/regen" as *u8) == 1 { 3773 let lv: i64 = ma_level_of(ctx, req, req_n) 3774 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_cmp_regen(out) } else { o = ma_emit_403(out) } } 3775 } else { if sd_starts(path, pn, "/api/compare/registry" as *u8) == 1 { 3776 let lv: i64 = ma_level_of(ctx, req, req_n) 3777 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_READ { o = ma_do_cmp_registry(out) } else { o = ma_emit_403(out) } } 3778 } else { if sd_starts(path, pn, "/api/srcwrite" as *u8) == 1 { 3779 let lv: i64 = ma_level_of(ctx, req, req_n) 3780 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_do_srcwrite(req, req_n, out) } else { o = ma_emit_403(out) } } 3781 } else { if ma_is_write_route(path, pn) == 1 { 3782 let lv: i64 = ma_level_of(ctx, req, req_n) 3783 if lv < 0 { o = sd_emit_401_json(out) } else { if lv >= MA_LVL_ACT { o = ma_emit_501(out) } else { o = ma_emit_403(out) } } 3784 } else { if ma_path_eq(path, pn, "/api/openapi.json" as *u8) == 1 { 3785 o = ma_openapi(out) 3786 } else { if ma_path_eq(path, pn, "/api" as *u8) == 1 { 3787 o = ma_index(out) 3788 } else { 3789 o = ma_emit_404(out) 3790 } } } } } } } } } } } } } } } } } } } } } } } } } } } } } 3791 return o 3792} 3793 3794// ---- scoped full-body read for the /api/upload path (performance: big chunks -> few handshakes) -------------- 3795// The serve loop's single sys_read may leave a large POST body only PARTIALLY read (or, for a body split across 3796// the wire, mostly unread). For every OTHER route (health/services/deploy/...) the body is a tiny form and one read 3797// suffices, so those stay on the untouched shared path. ONLY /api/upload carries a multi-KB..MB raw chunk, so ONLY 3798// it needs the continue-read below. This keeps the change surgically scoped (zero behavior change elsewhere). 3799 3800// Cheap prefix probe on the request TARGET: is this a POST to /api/upload? Reuses sd_find_path (same path locator 3801// ma_handle uses) so it sees the exact request-line path, never a header/body. Returns 1 iff POST + path starts 3802// with "/api/upload". Called BEFORE any body reassembly so we only ever grow the buffer for the upload route. 3803func ma_req_is_upload(req: *u8, req_n: i64) -> i64 { 3804 if req_n < 4 { return 0 } 3805 if req[0] != (80 as u8) { return 0 } // 'P' -- POST (upload is always POST; a GET can't carry a chunk body) 3806 let poff: *i64 = sys_mmap(MA_PATHPTR_BUF) as *i64 3807 let plen: *i64 = sys_mmap(MA_PATHPTR_BUF) as *i64 3808 poff[0] = 0; plen[0] = 0 3809 sd_find_path(req, req_n, poff, plen) 3810 let path: *u8 = ((req as i64) + poff[0]) as *u8 3811 let rc: i64 = sd_starts(path, plen[0], "/api/upload" as *u8) 3812 sys_munmap(poff as *u8, MA_PATHPTR_BUF) 3813 sys_munmap(plen as *u8, MA_PATHPTR_BUF) 3814 return rc 3815} 3816 3817// Continue-read the REMAINING declared body of an /api/upload request into `buf` (already holding the `prefix_n` 3818// bytes the serve loop read: request line + headers + whatever body bytes rode the first read). `cl` = Content-Length, 3819// `body_off` = offset of the first body byte. Loops sys_read appending after the already-read bytes until the FULL 3820// body is present, OR sys_read<=0 (EOF/err -> stop; the caller hands whatever is present to ma_do_upload, which 3821// fail-closes on a short/garbled body via sha256), OR the buffer cap is hit. Returns the new total length (prefix + 3822// all body bytes read), or MA_UPLOAD_TOOBIG (a sentinel < 0) if the declared body cannot fit MA_UPLOAD_REQCAP. 3823// NO HANG BY CONSTRUCTION: every iteration either makes progress (want>0 and read>0) or terminates (read<=0, or 3824// want<=0 meaning the body is already complete, or the cap boundary). The read length is clamped so we never write 3825// past the cap and never read more than the body needs. 3826func ma_upload_fill_body(cfd: i64, buf: *u8, cap: i64, prefix_n: i64, body_off: i64, cl: i64) -> i64 { 3827 // total request bytes we ultimately need = header bytes (body_off) + the declared body (cl) 3828 let need_total: i64 = body_off + cl 3829 if need_total > cap { return MA_UPLOAD_TOOBIG } // declared body won't fit the scoped buffer -> refuse, append NOTHING 3830 var have: i64 = prefix_n 3831 if have >= need_total { return have } // whole body already arrived in the first read -> done 3832 var go: i64 = 1 3833 while go == 1 { 3834 let want: i64 = need_total - have // bytes still missing (>0 here); bounds the read so we never over-read past this body 3835 let r: i64 = sys_read(cfd, ((buf as i64) + have) as *u8, want) 3836 if r <= 0 { go = 0 } // EOF / error -> stop (partial); caller's sha256 gate fail-closes a short body 3837 else { 3838 have = have + r 3839 if have >= need_total { go = 0 } // full body present -> stop 3840 } 3841 } 3842 return have 3843} 3844 3845 3846// ---- ACCEPT-LOOP UNBLOCK (2026-07-31) ------------------------------------------------------------- 3847// MEASURED ROOT CAUSE of the "MGMT :18098 DOWN, ALL SEATS" sev-9, with the state transition captured: 3848// t=40s child: nx_hostctl buildrun nx_codewiki wchan=do_wait probe=TIMEOUT 3849// t=48s child gone wchan=inet_csk_accept probe=HTTP/1.1 200 OK 3850// This is a SINGLE-ACCEPT server: it forks a job and blocks in wait4 for that job's ENTIRE life, so it 3851// never returns to accept(). Connections pile into the backlog (Recv-Q observed climbing 8 -> 34) while 3852// the LISTEN socket stays open -- which is exactly why every port-probe health check reported UP. 3853// nx_codewiki takes 6+ MINUTES, so one build froze the control plane for every seat for six minutes. 3854// :18098 was never down. It was blocked. 3855// 3856// FIX: fork a handler for the LONG routes so the parent returns to accept() immediately. 3857// 3858// WHY NOT FORK EVERY ROUTE: /api/deploy and /api/restart SELF-RESTART this daemon via md_self_pid() + 3859// md_delayed_kill(). In a forked child, md_self_pid() is the CHILD's pid, so the SIGTERM would land on 3860// the handler instead of the daemon and the self-restart would silently no-op. Those routes therefore 3861// stay INLINE, exactly as before, and are byte-identical in behaviour. 3862// The forked set is the set that runs a long SUBPROCESS and needs no parent identity. 3863func ma_route_is_long(p: *u8, n: i64) -> i64 { 3864 if sd_starts(p, n, "/api/build" as *u8) == 1 { return 1 } 3865 if sd_starts(p, n, "/api/gate_run" as *u8) == 1 { return 1 } 3866 if sd_starts(p, n, "/api/organ_run" as *u8) == 1 { return 1 } 3867 if sd_starts(p, n, "/api/hostctl" as *u8) == 1 { return 1 } 3868 if sd_starts(p, n, "/api/promote_toolchain" as *u8) == 1 { return 1 } 3869 return 0 3870} 3871 3872// Concurrency ceiling for forked handlers. Not a magic number: heavy builds are already gated by 3873// nx_build_admit, so this only bounds pathological fan-out. At the cap we block for ONE child -- a real 3874// overload, bounded, and still vastly better than blocking on every single job. 3875const MA_MAX_HANDLERS: i64 = 8 3876 3877func main(argc: i64, argv: *i64) -> i64 { 3878 if argc < 7 { sys_write(2, "usage: nx_mgmt_api <port> <keysfile> <storefile> <realm> <snapfile> <budget>\n" as *u8, 77); return 1 } 3879 let port: i64 = sd_atoi(argv[1] as *u8) 3880 let keysfile: *u8 = argv[2] as *u8 3881 let storefile: *u8 = argv[3] as *u8 3882 let realm: *u8 = argv[4] as *u8 3883 let snapfile: *u8 = argv[5] as *u8 3884 let budget: i64 = sd_atoi(argv[6] as *u8) 3885 let realm_n: i64 = sd_len(realm) 3886 3887 // ---- fail-fast: arm the realm context at startup (Rule 20) ---- 3888 let oprf_seed: *u8 = sys_mmap(32) 3889 let akp: *u8 = sys_mmap(32) 3890 let akb: *u8 = sys_mmap(33) 3891 let edp: *u8 = sys_mmap(32) 3892 let edb: *u8 = sys_mmap(32) 3893 if nx_uas_server_keys_load_or_init(keysfile, oprf_seed, akp, akb, edp, edb) != NX_UAS_OK { sys_write(2, "FATAL: server-key bundle\n" as *u8, 24); return 2 } 3894 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 3895 if nx_auth_context_init(ctx, realm, realm_n, realm, realm_n, storefile as i64, oprf_seed, edp, edb, 86400, 19456, 2, 1, 5, 1) != NX_MAUTH_OK { sys_write(2, "FATAL: context init\n" as *u8, 20); return 3 } // KSF/TTL match the /login minter (nishi_site_admin) so canonical accounts authenticate here 3896 3897 let addr: *u8 = sys_mmap(16) 3898 if nx_http_server_addr_loopback(addr, port) != 16 { return 4 } 3899 let lv: *i64 = sys_mmap(8) as *i64 3900 // R5 ADOPTION: hot listener (SO_REUSEPORT) so a NEW mgmt can bind :18098 while the OLD one is still 3901 // answering. Deploying mgmtapi currently kills the daemon MID-RESPONSE -- that is the FETCH-FAIL returned 3902 // by literally every /api/deploy today (~12x in one session), and it is also why the seq1563 deploy lease 3903 // strands: the process dies before it can reach its own release. With both old and new able to hold the 3904 // port, the old can finish in flight and exit instead of vanishing. 3905 // ⚠ BOTH sides need the option, so the FIRST deploy after this ships still cannot hand off -- the running 3906 // old process bound without it. Hot restart begins from the deploy AFTER this one. Proven by nx_hotlisten_gate 3/3. 3907 let lfd: i64 = nx_http_server_listen_hot(addr, 64, lv) 3908 if lfd < 0 { return 4 } 3909 3910 let req: *u8 = sys_mmap(SD_REQCAP) 3911 let out: *u8 = sys_mmap(SD_OUTCAP) 3912 // Scoped upload reassembly buffer (2 MiB): used ONLY when an /api/upload request's body arrived incomplete in 3913 // the single shared read. Allocated once (lazy anonymous pages -> only touched pages cost RAM). Immutable-deploy 3914 // safe: it's per-process scratch, no persistent state. 3915 let ubuf: *u8 = sys_mmap(MA_UPLOAD_REQCAP) 3916 var served: i64 = 0 3917 3918 // Reap buffers hoisted OUT of the loop: everything else in this body mmaps per iteration already, 3919 // and this fix must not add to that. 3920 let ma_reapb: *i64 = sys_mmap(8) as *i64 3921 var ma_live: i64 = 0 3922 3923 while served < budget { 3924 // ZOMBIE SWEEP (2026-07-31). SECOND MEASURED DEFECT: children were never reaped -- 3925 // [nx_debtlive.elf] sat <defunct> for 5h50m and was STILL uncollected after the parent 3926 // recovered, with 96 zombies host-wide. A non-blocking sweep every iteration means no child 3927 // can leak, and it can never block the accept loop the way the old per-child wait did. 3928 while sys_wait4(0 - 1, ma_reapb, 1) > 0 { if ma_live > 0 { ma_live = ma_live - 1 } } 3929 let av: *i64 = sys_mmap(8) as *i64 3930 let cfd: i64 = nx_http_server_accept_one(lfd, av) 3931 if cfd < 0 { served = served + 1 } 3932 if cfd >= 0 { 3933 let om: *i64 = sys_mmap(8) as *i64 3934 let opo: *i64 = sys_mmap(8) as *i64 3935 let opl: *i64 = sys_mmap(8) as *i64 3936 let ocl: *i64 = sys_mmap(8) as *i64 3937 let obo: *i64 = sys_mmap(8) as *i64 3938 let orn: *i64 = sys_mmap(8) as *i64 3939 let rrc: i64 = nx_http_server_read_request(cfd, req, SD_REQCAP, om, opo, opl, ocl, obo, orn) 3940 if rrc == NXS_OK { 3941 var req_ptr: *u8 = req 3942 var req_len: i64 = orn[0] 3943 var refuse413: i64 = 0 3944 // --- SCOPED full-body read: ONLY for /api/upload whose declared body wasn't fully read in the one 3945 // shared sys_read. Every other route (and any upload already complete in `req`) is byte-unchanged. --- 3946 if ma_req_is_upload(req, orn[0]) == 1 { 3947 let have_body: i64 = orn[0] - obo[0] 3948 if ocl[0] > have_body { 3949 // reassemble the request into the big buffer: copy the already-read prefix, then continue-read the rest 3950 var c: i64 = 0 3951 while c < orn[0] { ubuf[c] = req[c]; c = c + 1 } 3952 let full: i64 = ma_upload_fill_body(cfd, ubuf, MA_UPLOAD_REQCAP, orn[0], obo[0], ocl[0]) 3953 if full == MA_UPLOAD_TOOBIG { refuse413 = 1 } 3954 else { req_ptr = ubuf; req_len = full } 3955 } 3956 } 3957 var o: i64 = 0 3958 if refuse413 == 1 { 3959 o = ma_emit_413(out) 3960 nx_http_server_send_response_nokeep_close(cfd, out, o) 3961 } else { 3962 let ma_pp: *u8 = ((req as i64) + opo[0]) as *u8 3963 if ma_route_is_long(ma_pp, opl[0]) == 1 { 3964 // At the ceiling, block for exactly ONE handler to finish -- bounded, and only 3965 // under genuine fan-out, not on every job as before. 3966 if ma_live >= MA_MAX_HANDLERS { 3967 if sys_wait4(0 - 1, ma_reapb, 0) > 0 { ma_live = ma_live - 1 } 3968 } 3969 let ma_hp: i64 = sys_fork() 3970 if ma_hp == 0 { 3971 sys_close(lfd) 3972 let ma_co: i64 = ma_handle(ctx, req_ptr, req_len, snapfile, out) 3973 nx_http_server_send_response_nokeep_close(cfd, out, ma_co) 3974 sys_close(cfd) 3975 sys_exit(0) 3976 } 3977 if ma_hp > 0 { ma_live = ma_live + 1 } 3978 } else { 3979 o = ma_handle(ctx, req_ptr, req_len, snapfile, out) 3980 nx_http_server_send_response_nokeep_close(cfd, out, o) 3981 } 3982 } 3983 } 3984 sys_close(cfd) 3985 served = served + 1 3986 } 3987 } 3988 sys_close(lfd) 3989 return 0 3990}