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}