code wiki / (root) / nx_content_ship.nx

nx_content_ship.nx source

↩ module page · 221 lines · 11043 B

1// nx_content_ship.nx -- ONE sovereign command for the whole CONTENT ship loop (operator 2026-07-12: "isn't this 2// the type of thing we can bring into nishi and get up via mcp and api"). Replaces the sh-script choreography 3// (mint.body heredoc + grep/sed token extraction + per-file upload/promote calls + tr -d '\r' + edge check) with 4// a single fork+capture organ over the PROVEN pieces: nx_mgmt_client (sovereign TLS-1.3) + the mgmt API. 5// nx_content_ship <manifest> <capfile> [edge] 6// <manifest> rows: put|<remote-target>|<local-file> upload + atomic promote_content (idempotent) 7// verify|<url-path>|<expect-substr> GET via the edge, gate on substring 8// comment rows start with the hash char; blank lines + CRLF tolerated (no tr -d '\r' dance) 9// <capfile> file holding the nx_session_mint capability token (raw, one line) 10// [edge] default https://nishifamily.com 11// Step 0 SELF-MINTS a fresh 24h admin session over HTTPS (tools/call nx_session_mint, cap IN THE BODY -- the 12// dummy session header is ignored by the tools daemon), so there is no pre-step and no stale token. 13// Deploy of BINARIES/services is deliberately NOT here (that is /api/deploy, never-brick) -- this organ moves 14// site CONTENT through the fail-closed content namespace (md_content_pfx allowlist enforced server-side). 15import "nx_tool_run.nx" // tr_run_capture (fork + argv + stdout capture) 16const K_MAGIC_2000: i64 = 2000 17const K_MAGIC_1048576: i64 = 1048576 18const K_MAGIC_262144: i64 = 262144 19const K_MAGIC_4096: i64 = 4096 20const K_MAGIC_65536: i64 = 65536 21const K_MAGIC_4000: i64 = 4000 22 23func cs_slen(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { i = i + 1 } return i } 24func cs_puts(s: *u8) -> i64 { sys_write(1, s, cs_slen(s)); return 0 } 25 26// exact NUL-terminated string equality 27func cs_eq(a: *u8, b: *u8) -> i64 { 28 var i: i64 = 0 29 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 30 if b[i] != (0 as u8) { return 0 } 31 return 1 32} 33 34func cs_contains(buf: *u8, n: i64, needle: *u8) -> i64 { 35 let m: i64 = cs_slen(needle) 36 if m == 0 { return 1 } 37 var i: i64 = 0 38 while i + m <= n { 39 var k: i64 = 0 40 while k < m { if buf[i + k] != needle[k] { k = m + 9 } else { k = k + 1 } } 41 if k == m { return 1 } 42 i = i + 1 43 } 44 return 0 45} 46 47// append NUL-terminated src into dst at off; returns the new off (dst stays NUL-terminated) 48func cs_app(dst: *u8, off: i64, src: *u8) -> i64 { 49 var o: i64 = off 50 var i: i64 = 0 51 while src[i] != (0 as u8) { dst[o] = src[i]; o = o + 1; i = i + 1 } 52 dst[o] = 0 as u8 53 return o 54} 55 56func cs_write_file(path: *u8, content: *u8, n: i64) -> i64 { 57 let fd: i64 = sys_openat_wr(path, 0x1a4) 58 if fd < 0 { return 0 - 1 } 59 sys_write(fd, content, n) 60 sys_close(fd) 61 return 0 62} 63 64// read a whole file into buf (cap bcap), NUL-terminate; returns bytes read or -1 65func cs_read_file(path: *u8, buf: *u8, bcap: i64) -> i64 { 66 let fd: i64 = sys_openat_rd(path) 67 if fd < 0 { return 0 - 1 } 68 var total: i64 = 0 69 while total < bcap - 1 { 70 let r: i64 = sys_read(fd, (buf as i64 + total) as *u8, bcap - 1 - total) 71 if r <= 0 { break } 72 total = total + r 73 } 74 sys_close(fd) 75 buf[total] = 0 as u8 76 return total 77} 78 79func cs_run(path: *u8, av: *i64, out: *u8, cap: i64, olen: *i64) -> i64 { 80 av[0] = path as i64 81 return tr_run_capture(path, av, out, cap, olen) 82} 83 84func main(argc: i64, argv: *i64) -> i64 { 85 if argc < 3 { cs_puts("usage: nx_content_ship <manifest> <capfile> [edge]\n" as *u8); return 1 } 86 let manifest_path: *u8 = argv[1] as *u8 87 let capfile: *u8 = argv[2] as *u8 88 var edge: *u8 = "https://nishifamily.com" as *u8 89 if argc >= 4 { edge = argv[3] as *u8 } 90 91 let mgmtclient: *u8 = "./_offc/nx_mgmt_client.elf" as *u8 92 let mintbody_p: *u8 = "/tmp/nxcs_mint.body" as *u8 93 let dummy_p: *u8 = "/tmp/nxcs_dummy.tok" as *u8 94 let tok_p: *u8 = "/tmp/nxcs.tok" as *u8 95 let body_p: *u8 = "/tmp/nxcs_call.body" as *u8 96 97 let out: *u8 = sys_mmap(K_MAGIC_1048576) 98 let olen: *i64 = sys_mmap(16) as *i64 99 let av: *i64 = sys_mmap(256) as *i64 100 let man: *u8 = sys_mmap(K_MAGIC_262144) 101 let capb: *u8 = sys_mmap(K_MAGIC_4096) 102 let sb: *u8 = sys_mmap(K_MAGIC_65536) // scratch string builder 103 let tok: *u8 = sys_mmap(K_MAGIC_4096) 104 105 // ---- 0a. read cap (strip trailing LF/CR/space) ---- 106 var capn: i64 = cs_read_file(capfile, capb, K_MAGIC_4096) 107 if capn <= 0 { cs_puts("[cship] CAPFILE unreadable\n" as *u8); sys_exit(2); return 2 } 108 while capn > 0 { 109 var drop: i64 = 0 110 if capb[capn - 1] == (10 as u8) { drop = 1 } 111 if capb[capn - 1] == (13 as u8) { drop = 1 } 112 if capb[capn - 1] == (32 as u8) { drop = 1 } 113 if drop == 0 { break } 114 capn = capn - 1 115 } 116 capb[capn] = 0 as u8 117 118 // ---- 0b. mint a fresh session over HTTPS (cap IN THE BODY; dummy session header ignored) ---- 119 cs_puts("[cship] 0 mint session (tools/call nx_session_mint over sovereign TLS)\n" as *u8) 120 var so: i64 = 0 121 so = cs_app(sb, so, "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"nx_session_mint\",\"arguments\":{\"_cap\":\"" as *u8) 122 so = cs_app(sb, so, capb) 123 so = cs_app(sb, so, "\"}}}" as *u8) 124 cs_write_file(mintbody_p, sb, so) 125 cs_write_file(dummy_p, "x" as *u8, 1) 126 av[1] = edge as i64; av[2] = "call" as *u8 as i64; av[3] = "POST" as *u8 as i64; av[4] = "/mcp" as *u8 as i64; av[5] = dummy_p as i64; av[6] = mintbody_p as i64; av[7] = 0 127 cs_run(mgmtclient, av, out, K_MAGIC_1048576, olen) 128 // extract the token: find the text field opener, then copy until the closing quote or an escape 129 let marker: *u8 = "\"text\":\"" as *u8 130 let mlen: i64 = cs_slen(marker) 131 var ti: i64 = 0 - 1 132 var i: i64 = 0 133 while i + mlen <= olen[0] { 134 var k: i64 = 0 135 while k < mlen { if out[i + k] != marker[k] { k = mlen + 9 } else { k = k + 1 } } 136 if k == mlen { ti = i + mlen; i = olen[0] } else { i = i + 1 } 137 } 138 if ti < 0 { cs_puts("[cship] MINT FAILED (no text field)\n" as *u8); sys_write(1, out, olen[0]); sys_exit(3); return 3 } 139 var tn: i64 = 0 140 while ti + tn < olen[0] { 141 if out[ti + tn] == (34 as u8) { break } 142 if out[ti + tn] == (92 as u8) { break } 143 tok[tn] = out[ti + tn] 144 tn = tn + 1 145 if tn >= K_MAGIC_4000 { break } 146 } 147 if tn < 100 { cs_puts("[cship] MINT FAILED (token too short)\n" as *u8); sys_write(1, out, olen[0]); sys_exit(3); return 3 } 148 cs_write_file(tok_p, tok, tn) 149 cs_puts("[cship] 0 mint OK\n" as *u8) 150 151 // ---- 1. parse + execute the manifest (fail-closed per step) ---- 152 let mn: i64 = cs_read_file(manifest_path, man, K_MAGIC_262144) 153 if mn <= 0 { cs_puts("[cship] MANIFEST unreadable\n" as *u8); sys_exit(2); return 2 } 154 var nputs: i64 = 0 155 var nver: i64 = 0 156 var p: i64 = 0 157 while p < mn { 158 var e: i64 = p 159 while e < mn { if man[e] == (10 as u8) { break } e = e + 1 } 160 var le: i64 = e 161 if le > p { if man[le - 1] == (13 as u8) { le = le - 1 } } 162 man[le] = 0 as u8 163 let line: *u8 = (man as i64 + p) as *u8 164 p = e + 1 165 if cs_slen(line) == 0 { continue } 166 if line[0] == (35 as u8) { continue } 167 // split verb|a2|a3 on the pipe char (124), NUL-ing in place 168 var f2: i64 = 0 - 1 169 var f3: i64 = 0 - 1 170 var j: i64 = 0 171 while line[j] != (0 as u8) { 172 if line[j] == (124 as u8) { 173 if f2 < 0 { line[j] = 0 as u8; f2 = j + 1 } else { if f3 < 0 { line[j] = 0 as u8; f3 = j + 1 } } 174 } 175 j = j + 1 176 } 177 if f3 < 0 { cs_puts("[cship] BAD ROW (need verb|a|b): " as *u8); cs_puts(line); cs_puts("\n" as *u8); sys_exit(4); return 4 } 178 let a2: *u8 = (line as i64 + f2) as *u8 179 let a3: *u8 = (line as i64 + f3) as *u8 180 if cs_eq(line, "put" as *u8) == 1 { 181 cs_puts("[cship] put " as *u8); cs_puts(a2); cs_puts("\n" as *u8) 182 av[1] = edge as i64; av[2] = "upload" as *u8 as i64; av[3] = a2 as i64; av[4] = a3 as i64; av[5] = tok_p as i64; av[6] = 0 183 cs_run(mgmtclient, av, out, K_MAGIC_1048576, olen) 184 if cs_contains(out, olen[0], "UPLOAD OK" as *u8) == 0 { cs_puts("[cship] UPLOAD FAILED " as *u8); cs_puts(a2); cs_puts("\n" as *u8); sys_write(1, out, olen[0]); sys_exit(5); return 5 } 185 var bo: i64 = 0 186 bo = cs_app(sb, bo, "target=" as *u8) 187 bo = cs_app(sb, bo, a2) 188 bo = cs_app(sb, bo, "&confirm=yes" as *u8) 189 cs_write_file(body_p, sb, bo) 190 av[1] = edge as i64; av[2] = "call" as *u8 as i64; av[3] = "POST" as *u8 as i64; av[4] = "/api/promote_content" as *u8 as i64; av[5] = tok_p as i64; av[6] = body_p as i64; av[7] = 0 191 cs_run(mgmtclient, av, out, K_MAGIC_1048576, olen) 192 if cs_contains(out, olen[0], "status=200" as *u8) == 0 { cs_puts("[cship] PROMOTE FAILED " as *u8); cs_puts(a2); cs_puts("\n" as *u8); sys_write(1, out, olen[0]); sys_exit(6); return 6 } 193 nputs = nputs + 1 194 continue 195 } 196 if cs_eq(line, "verify" as *u8) == 1 { 197 cs_puts("[cship] verify " as *u8); cs_puts(a2); cs_puts(" ~ " as *u8); cs_puts(a3); cs_puts("\n" as *u8) 198 // D008 MECHANIZED (2026-07-29): the edge transiently serves the static fallback page on a 199 // fresh GET (route fallthrough) -- the single-shot verify here read FALSE-RED twice in one 200 // day while every put had actually landed (manual read-back proved it each time). The 201 // read-back law belongs in the TOOL: retry x4 with a 2s gap; only a PERSISTENT mismatch is 202 // a real failure. Binary targets: verify a text sibling (ver.txt) or read back md5 203 // out-of-band -- the captured stream mixes protocol chatter with the body. 204 var vok: i64 = 0 205 var vt: i64 = 0 206 while vt < 4 { 207 av[1] = edge as i64; av[2] = "call" as *u8 as i64; av[3] = "GET" as *u8 as i64; av[4] = a2 as i64; av[5] = tok_p as i64; av[6] = 0 208 cs_run(mgmtclient, av, out, K_MAGIC_1048576, olen) 209 if cs_contains(out, olen[0], a3) == 1 { vok = 1; vt = 4 } 210 else { vt = vt + 1; if vt < 4 { cs_puts("[cship] verify miss (transient fallthrough class) -- retry\n" as *u8); sys_sleep_ms(K_MAGIC_2000) } } 211 } 212 if vok == 0 { cs_puts("[cship] VERIFY FAILED (persistent across 4 tries 2s apart -- NOT the transient class) " as *u8); cs_puts(a2); cs_puts("\n" as *u8); sys_write(1, out, olen[0]); sys_exit(7); return 7 } 213 nver = nver + 1 214 continue 215 } 216 cs_puts("[cship] UNKNOWN VERB: " as *u8); cs_puts(line); cs_puts("\n" as *u8); sys_exit(4); return 4 217 } 218 cs_puts("[cship] SHIPPED -- every put promoted + every verify green (fail-closed per step)\n" as *u8) 219 sys_exit(0) 220 return 0 221}