code wiki / (root) / nx_mgmt_call.nx

nx_mgmt_call.nx source

↩ module page · 161 lines · 10509 B

1// nx_mgmt_call.nx -- the mgmt-API driver forked as an MCP tool. Mints a FRESH admin session from the NAS key bundle 2// (no staleness, no stored secret) and issues one authenticated call to the loopback mgmt API (:18098), printing the 3// response body, or retains it privately with --response-file. This is what makes the SOTA control plane (deploy/reconcile/restart/services/health) reachable over 4// MCP without the shell. Runs as the (root) tools daemon's child, which can read the elderwesto-owned key bundle. 5// CLI: nx_mgmt_call <METHOD> <path> [body] e.g. nx_mgmt_call GET /api/services 6// Body encoding is route-specific. Current form routes require target=..., not JSON. 7// Example: nx_mgmt_call POST /api/organ_run 'target=nx_filehash&confirm=yes&args=nx_mgmt_api.elf.new' 8// Resolve deployment targets through deploy_targets.conf; tool names and deployment names differ. 9import "nx_mgmt_private_response.nx" 10import "nx_session_mint_lib.nx" // msm_mint_b64, msm_slen 11import "nx_tool_http_backend.nx" // thb_fetch (loopback HTTP client) 12const MC_MAGIC_86400: i64 = 86400 13const MC_MAGIC_131072: i64 = 131072 14const MC_MAGIC_1048576: i64 = 1048576 15 16const MC_KEYS: *u8 = "opaque_keys.bin" as *u8 // relative to the daemon cwd (nishihost) 17const MC_REALM: *u8 = "nishi_site_admin" as *u8 18const MC_HANDLE: *u8 = "elderwesto" as *u8 19const MC_PORT: i64 = 18098 20 21func mc_puts(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { buf[off] = s[i]; off = off + 1; i = i + 1 } return off } 22func mc_putn(buf: *u8, off: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { buf[off] = s[i]; off = off + 1; i = i + 1 } return off } 23func mc_puti(buf: *u8, off: i64, v: i64) -> i64 { 24 if v == 0 { buf[off] = 0x30 as u8; return off + 1 } 25 let tmp: *u8 = sys_mmap(32); var k: i64 = 0; var m: i64 = v 26 while m > 0 { tmp[k] = (0x30 + (m % 10)) as u8; k = k + 1; m = m / 10 } 27 var j: i64 = k - 1 28 while j >= 0 { buf[off] = tmp[j]; off = off + 1; j = j - 1 } 29 return off 30} 31 32func mc_private_end(r:*NxMgmtPrivateResponse,session:*u8,reply:*u8,reply_n:i64)->i64{ 33 if (session as i64)>0{var i:i64=0;while i<512{session[i]=0 as u8;i=i+1}} 34 if (reply as i64)>0{var i:i64=0;while i<reply_n{reply[i]=0 as u8;i=i+1}} 35 mpr_cleanup(r);let reported:i64=mpr_report(r) 36 var rc:i64=0;if r.code!=0{rc=4};if reported!=0{rc=5} 37 sys_munmap_direct(r as *u8,__size_of(NxMgmtPrivateResponse));return rc 38} 39func main(argc: i64, argv: *i64) -> i64 { 40 if argc < 3 { 41 let help:*u8="usage: nx_mgmt_call <METHOD> <path> [route-specific-body] [--response-file <private-path>]\nPrivate mode requires the body argument (empty for GET); response body is retained0600 and stdout reports metadata only. Form or JSON encoding is defined by the API route.\n" 42 sys_write(2,help,msm_slen(help));sys_write(1,help,msm_slen(help));return 1 43 } 44 let method: *u8 = argv[1] as *u8 45 let path: *u8 = argv[2] as *u8 46 var body: *u8 = "" as *u8 47 var body_n: i64 = 0 48 if argc >= 4 { body = argv[3] as *u8; body_n = msm_slen(body) } 49 50 var private:*NxMgmtPrivateResponse=0 as *NxMgmtPrivateResponse 51 if argc>=5{ 52 if argc!=6 || mpr_eq(argv[4] as *u8,mpr_len(argv[4] as *u8),"--response-file")!=1{ 53 let diagnostic:*u8="invalid private response arguments\n" 54 sys_write(2,diagnostic,msm_slen(diagnostic)) 55 mpr_put("{\"action\":\"PRIVATE-RESPONSE\",\"request_sent\":0,\"code\":-22,\"stage\":\"invalid-private-arguments\"}\n");return 1 56 } 57 private=sys_mmap_try(__size_of(NxMgmtPrivateResponse)) as *NxMgmtPrivateResponse 58 if (private as i64)<=0{mpr_put("{\"action\":\"PRIVATE-RESPONSE\",\"request_sent\":0,\"code\":-12}\n");return 4} 59 let prepared:i64=mpr_begin(argv[5] as *u8,private) 60 if prepared!=0{private.code=prepared;return mc_private_end(private,0 as *u8,0 as *u8,0)} 61 } 62 63 // 1. mint a fresh admin session 64 let now: i64 = sys_now_realtime_sec() 65 let b64: *u8 = sys_mmap(512) 66 let blen: i64 = msm_mint_b64(MC_KEYS, MC_REALM, msm_slen(MC_REALM), MC_HANDLE, msm_slen(MC_HANDLE), now, MC_MAGIC_86400, b64) 67 // Same channel loss as the FETCH-FAIL path below: a mint failure was invisible to MCP callers. 68 if blen < 0 { 69 if (private as i64)>0{private.code=blen;private.stage="session-mint";return mc_private_end(private,b64,0 as *u8,0)} 70 sys_write(2, "MINT-FAIL\n" as *u8, 10) 71 sys_write(1, "NX-MGMT-CALL MINT-FAIL: could not mint an admin session from the key bundle (opaque_keys.bin unreadable or realm/handle rejected). The call was NEVER SENT, so it is safe to retry.\n" as *u8, 181) 72 return 2 73 } 74 75 // 2. build the authenticated request 76 // Private mode derives request extent; legacy stdout mode retains its existing contract. 77 var req_capacity:i64=MC_MAGIC_131072 78 if (private as i64)>0{ 79 let fixed:i64=msm_slen(" HTTP/1.0\r\nHost: 127.0.0.1\r\nX-Nishi-Session: \r\nContent-Type: application/json\r\nContent-Length: \r\nConnection: close\r\n\r\n") 80 let method_n:i64=msm_slen(method);let path_n:i64=msm_slen(path) 81 if body_n>9223372036854775807-fixed-blen-20-method_n-path_n{private.code=0-75;private.stage="request-size";return mc_private_end(private,b64,0 as *u8,0)} 82 req_capacity=fixed+blen+20+method_n+path_n+body_n 83 } 84 let req:*u8=sys_mmap_try(req_capacity) 85 if (req as i64)<=0{ 86 if (private as i64)>0{private.code=0-12;private.stage="request-allocation";return mc_private_end(private,b64,0 as *u8,0)} 87 sys_write(2,"request allocation failed\n",26);return 2 88 } 89 var o: i64 = 0 90 o = mc_puts(req, o, method); o = mc_puts(req, o, " " as *u8); o = mc_puts(req, o, path); o = mc_puts(req, o, " HTTP/1.0\r\n" as *u8) 91 o = mc_puts(req, o, "Host: 127.0.0.1\r\n" as *u8) 92 o = mc_puts(req, o, "X-Nishi-Session: " as *u8); o = mc_putn(req, o, b64, blen); o = mc_puts(req, o, "\r\n" as *u8) 93 if body_n > 0 { 94 o = mc_puts(req, o, "Content-Type: application/json\r\n" as *u8) 95 o = mc_puts(req, o, "Content-Length: " as *u8); o = mc_puti(req, o, body_n); o = mc_puts(req, o, "\r\n" as *u8) 96 } 97 o = mc_puts(req, o, "Connection: close\r\n\r\n" as *u8) 98 if body_n > 0 { o = mc_putn(req, o, body, body_n) } 99 100 // 3. call the loopback mgmt API 101 let out:*u8=sys_mmap_try(MC_MAGIC_1048576) 102 if (out as i64)<=0{ 103 sys_munmap_direct(req,req_capacity) 104 if (private as i64)>0{private.code=0-12;private.stage="response-allocation";return mc_private_end(private,b64,0 as *u8,0)} 105 sys_write(2,"response allocation failed\n",27);return 2 106 } 107 if (private as i64)>0{private.sent=0-1} 108 let n: i64 = thb_fetch("127.0.0.1" as *u8, 9, MC_PORT, req, o, out, MC_MAGIC_1048576) 109 if (private as i64)>0{ 110 if n<=0{private.code=n;if n==0{private.code=FIO_EIO};private.stage="upstream-outcome-unknown"}else{private.sent=1;mpr_publish(out,n,MC_MAGIC_1048576,private)} 111 let result:i64=mc_private_end(private,b64,out,MC_MAGIC_1048576) 112 sys_munmap_direct(req,req_capacity);sys_munmap_direct(out,MC_MAGIC_1048576) 113 return result 114 } 115 if n <= 0 { 116 // seq1806: `n` was the ONLY discriminator available and it was DISCARDED. A bare "FETCH-FAIL" 117 // cannot distinguish (a) the request NEVER LEFT -- safe to retry -- from (b) the request was 118 // SERVED and only the RESPONSE was lost -- NOT safe to blindly retry a non-idempotent call 119 // such as /api/cap/mint. Those two have OPPOSITE correct responses, and the caller was given 120 // no way to tell them apart. 121 // MEASURED 2026-07-30: two consecutive FETCH-FAILs on /api/build while the build SUCCEEDED 122 // BOTH times (213831B artifact on disk), plus a FETCH-FAIL on /api/cap/mint whose outcome is 123 // still unknown. Every sibling probe organ prints rc= (nx_doh_probe prints rc AND status); 124 // the one organ driving MUTATIONS printed the least. 125 // ⚠mc_puti renders a NEGATIVE as EMPTY (its `while m > 0` never runs), so the sign is handled 126 // explicitly here -- printing nothing for the most common failure code would reproduce the bug. 127 let eb: *u8 = sys_mmap(512); var eo: i64 = 0 128 eo = mc_puts(eb, eo, "FETCH-FAIL rc=" as *u8) 129 if n < 0 { eo = mc_puts(eb, eo, "-" as *u8); eo = mc_puti(eb, eo, 0 - n) } 130 if n >= 0 { eo = mc_puti(eb, eo, n) } 131 eo = mc_puts(eb, eo, " port=" as *u8); eo = mc_puti(eb, eo, MC_PORT) 132 eo = mc_puts(eb, eo, " method=" as *u8); eo = mc_puts(eb, eo, method) 133 eo = mc_puts(eb, eo, " path=" as *u8); eo = mc_puts(eb, eo, path) 134 eo = mc_puts(eb, eo, " reqbytes=" as *u8); eo = mc_puti(eb, eo, o) 135 eo = mc_puts(eb, eo, " OUTCOME=UNKNOWN (the request may have been SERVED and only the response lost -- VERIFY BY ARTIFACT before retrying anything non-idempotent)\n" as *u8) 136 // THE DIAGNOSTIC WAS PERFECT AND THE CHANNEL ATE IT (2026-08-07). Everything above is exactly right 137 // and it went to fd 2 ONLY -- but this organ is forked as an MCP tool and the tools-API capture 138 // surfaces fd 1, so every MCP caller received {"text":""} with isError:false and a MALFORMED 139 // "exit_code": (no value) while this rc / port / OUTCOME=UNKNOWN line existed and was discarded. 140 // MEASURED: I lost FOUR consecutive /api/build attempts unable to tell a failed build from an 141 // unreachable daemon, and only proved the tools plane was alive by probing a DIFFERENT tool. 142 // * A DIAGNOSTIC ON A CHANNEL THE CALLER DOES NOT READ IS INDISTINGUISHABLE FROM SILENCE -- and the 143 // silence then reads as SUCCESS, because an empty body with isError:false looks like "nothing to say". 144 // BOTH fds on purpose: fd 2 keeps shell/log behaviour byte-identical (rule 19), fd 1 makes the 145 // failure visible to the MCP caller who is the one that has to decide whether to retry. 146 sys_write(2, eb, eo) 147 sys_write(1, eb, eo) 148 return 3 149 } 150 151 // 4. print the response BODY (after CRLFCRLF); fall back to the whole response if no header terminator 152 var bo: i64 = 0 153 var found: i64 = 0 154 var i: i64 = 0 155 while i + 3 < n { 156 if out[i] == (13 as u8) { if out[i + 1] == (10 as u8) { if out[i + 2] == (13 as u8) { if out[i + 3] == (10 as u8) { bo = i + 4; found = 1; i = n } } } } 157 i = i + 1 158 } 159 if found == 1 { sys_write(1, ((out as i64) + bo) as *u8, n - bo) } else { sys_write(1, out, n) } 160 return 0 161}