nx_content_get_client.nx source
↩ module page · 383 lines · 18328 B
1// nx_content_get_client.nx -- THE SOVEREIGN NAS->laptop DOWNLOAD CLIENT. Retires nx_content_get_client.py.
2//
3// WHY THIS EXISTS (operator standing order, restated 2026-09-04: "we shouldnt be using ps1 or py or
4// anything else in our build path only as external tooling being liar killers and validators etc").
5// nx_content_get has been the sovereign SOURCE since 2026-09-03 and its only driver was a Python script
6// written on 2026-09-04 that shells out to a Python MCP wire. A SHIPPER IS THE BUILD PATH BY DEFINITION,
7// so it can never be one of the external oracles that order permits. This is the NishiLang driver.
8// Its outbound sibling nx_content_put_client.nx made exactly this move for the write direction and its
9// own first line says so; this file is the half that was missing.
10//
11// nx_content_get_client [dryrun] <remote_src> <local_dest> <capfile> <url>
12//
13// MEASURED COST OF THE GAP IT CLOSES (hostops debt 1788534921, 2026-09-04): with the NAS array degraded
14// and every NAS build refused, the laptop farm could compile and SHIP but could not BITE, because its
15// nx_gate_bite.nx was 39,842 bytes against the NAS 98,879 and nothing could refresh it without a human
16// copy or retyping bytes through an agent transcript. An outbound-only lane is a fallback that works
17// only until the thing you need is on the other side.
18//
19// THE PROTOCOL IS THE SERVER OWN AND IT IS STATELESS, WHICH CHANGES THIS CLIENT SHAPE:
20// begin <src> -> CG-BEGIN src= total_bytes= sha256= chunk_raw= nchunks=
21// chunk <src> <index> -> CG-CHUNK OK ... chunk_sha256= b64=<data>
22// There is no transfer id and no commit: in a get the bytes accumulate on the CLIENT, so there is no
23// server-side state to open, resume or close. The put client id-threading and commit-unknown handling
24// are therefore ABSENT rather than mirrored -- carrying them would be machinery with no invariant to
25// protect, which the server own header names as the asymmetry a symmetric copy gets wrong.
26//
27// NOTHING IS WRITTEN TO <local_dest> UNTIL THE WHOLE-FILE DIGEST MATCHES.
28// The server header states the exposure precisely: the SOURCE can change under a multi-call read, so
29// chunks reassembled from two generations give a franken-file that is byte-valid and semantically
30// nonsense. The per-chunk digest catches a CORRUPTED chunk and CANNOT catch a consistent read of a
31// CHANGED file; only the whole-file sha256 declared by begin can. So this client assembles in memory,
32// verifies, then writes ONCE. A partial or franken download must never be able to masquerade as a
33// refreshed source -- and a size check would not do, because a same-size rewrite passes it.
34//
35// THE CHUNK SIZE IS READ FROM THE SERVER RECEIPT, NEVER COMPUTED HERE. nx_content_get derives it from
36// the RESPONSE capture cap (163,840) rather than the request cap, which is the one term a symmetric copy
37// of the upload client would get wrong. Recomputing it here would install a second ruler that drifts the
38// moment the server reserve changes, and the drift would surface as truncated downloads.
39//
40// A SERVER DECISION IS NEVER RETRIED, AND IT HAS TWO VOCABULARIES.
41// Measured 2026-09-04 on the Python driver this retires: a capability denial arrives as a JSON-RPC
42// code -32001 carrying no CG- token, so a classifier testing only the ORGAN vocabulary read it as a
43// transport fault and retried SIX TIMES over about 30 seconds -- against an array the hostops lane had
44// filed as degraded and harmful to retry against. This client has NO retry loop at all: one call, one
45// answer. That is not a simplification, it is the correct shape for a stateless protocol whose failures
46// are either decisions (which will not change) or transport faults (which the caller can re-issue with
47// full knowledge, because an abandoned get leaks no server state).
48//
49// REFUSE RATHER THAN ESCAPE, on the sibling precedent. Every value on the wire comes from a known-safe
50// alphabet -- a path, decimal digits, and a capability token -- so a byte needing JSON escaping means the
51// input is wrong, not that an escaper is owed. A hand-rolled escaper fails silently and produces a
52// malformed request the server rejects for the wrong reason; a refusal names the offending value.
53//
54// exit: 0 ok | 2 usage | 3 local-io | 4 refused-unsafe-arg | 5 begin-failed | 6 chunk-failed | 7 digest-mismatch
55// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
56import "nx_syscalls.nx"
57import "nx_base64.nx"
58import "nx_sha256.nx"
59import "nx_https_post_lib.nx"
60import "nx_jobfollow_lib.nx"
61
62const CG_DIGEST: i64 = 32
63const CG_SHAHEX: i64 = 64
64const CG_RESP: i64 = 262144
65const CG_REQ: i64 = 262144
66const CG_NUMBUF: i64 = 32
67const CG_MODE_RW: i64 = 420
68// Poll budget for following a JOB-STARTED promotion: 40 x 3000ms = 120s, matched to the put twin so
69// the two clients cannot disagree about how long the job lane is allowed to take.
70const CG_POLLS: i64 = 40
71const CG_POLL_MS: i64 = 3000
72// Opens exactly THREE objects and ONE array, so the tail closes the bracket then three braces -- the same
73// arithmetic pairing the sibling gate asserts rather than trusts, after a first draft there carried a
74// DOUBLED params key whose four-brace tail balanced against itself and produced well-formed JSON of the
75// WRONG SHAPE, rejected by the server for a reason naming neither the client nor the real fault.
76const CG_PRE: *u8 = "{\x22jsonrpc\x22:\x222.0\x22,\x22id\x22:1,\x22method\x22:\x22tools/call\x22,\x22params\x22:{\x22name\x22:\x22nx_content_get\x22,\x22arguments\x22:{\x22argv\x22:["
77const CG_TAIL: *u8 = "}}}"
78const CG_QUOTE: i64 = 34
79const CG_BACKSLASH: i64 = 92
80const CG_SPACE: i64 = 32
81const CG_DEL: i64 = 127
82
83func cg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
84func cg_out(s: *u8) -> i64 { sys_write(1, s, cg_slen(s)); return 0 }
85func cg_eo(s: *u8) -> i64 { sys_write(2, s, cg_slen(s)); return 0 }
86func cg_num(v: i64) -> i64 {
87 let b: *u8 = sys_mmap(CG_NUMBUF)
88 var m: i64 = v
89 var i: i64 = CG_NUMBUF - 1
90 if m == 0 { i = i - 1; b[i] = 48 as u8 }
91 while m > 0 { i = i - 1; b[i] = (48 + (m % 10)) as u8; m = m / 10 }
92 sys_write(1, (b as i64 + i) as *u8, (CG_NUMBUF - 1) - i)
93 return 0
94}
95func cg_itoa(v: i64, out: *u8) -> i64 {
96 if v == 0 { out[0] = 48 as u8; return 1 }
97 let tmp: *u8 = sys_mmap(CG_NUMBUF)
98 var m: i64 = v
99 var i: i64 = 0
100 while m > 0 { tmp[i] = (48 + (m % 10)) as u8; m = m / 10; i = i + 1 }
101 var j: i64 = 0
102 while j < i { out[j] = tmp[i - 1 - j]; j = j + 1 }
103 return i
104}
105func cg_hex_into(d: *u8, out: *u8) -> i64 {
106 var i: i64 = 0
107 while i < CG_DIGEST {
108 let v: i64 = d[i] as i64
109 let hi: i64 = (v >> 4) & 15
110 let lo: i64 = v & 15
111 if hi < 10 { out[i * 2] = (48 + hi) as u8 } else { out[i * 2] = (87 + hi) as u8 }
112 if lo < 10 { out[i * 2 + 1] = (48 + lo) as u8 } else { out[i * 2 + 1] = (87 + lo) as u8 }
113 i = i + 1
114 }
115 out[CG_SHAHEX] = 0 as u8
116 return 0
117}
118func cg_json_safe(s: *u8, n: i64) -> i64 {
119 var i: i64 = 0
120 while i < n {
121 let c: i64 = s[i] as i64
122 if c == CG_QUOTE { return 0 }
123 if c == CG_BACKSLASH { return 0 }
124 if c < CG_SPACE { return 0 }
125 if c == CG_DEL { return 0 }
126 i = i + 1
127 }
128 return 1
129}
130// index just past needle, or -1. Exits on a FLAG, never by clobbering the cursor -- the loop-exit
131// sentinel defect this estate has recorded four times in one day.
132func cg_find(buf: *u8, n: i64, needle: *u8) -> i64 {
133 let m: i64 = cg_slen(needle)
134 if m == 0 { return 0 - 1 }
135 var i: i64 = 0
136 var hit: i64 = 0 - 1
137 while i + m <= n {
138 var j: i64 = 0
139 var same: i64 = 1
140 while j < m { if buf[i + j] != needle[j] { same = 0; j = m } else { j = j + 1 } }
141 if same == 1 { if hit < 0 { hit = i + m } }
142 i = i + 1
143 }
144 return hit
145}
146func cg_int_after(buf: *u8, n: i64, key: *u8) -> i64 {
147 let p: i64 = cg_find(buf, n, key)
148 if p < 0 { return 0 - 1 }
149 var i: i64 = p
150 var v: i64 = 0
151 var got: i64 = 0
152 var go: i64 = 1
153 while go == 1 {
154 if i >= n { go = 0 } else {
155 let c: i64 = buf[i] as i64
156 if c < 48 { go = 0 } else {
157 if c > 57 { go = 0 } else { v = v * 10 + (c - 48); got = 1; i = i + 1 }
158 }
159 }
160 }
161 if got == 0 { return 0 - 1 }
162 return v
163}
164// is c a base64 alphabet byte? The b64 value ends at the first byte that is not one.
165func cg_b64ch(c: i64) -> i64 {
166 if c >= 65 { if c <= 90 { return 1 } }
167 if c >= 97 { if c <= 122 { return 1 } }
168 if c >= 48 { if c <= 57 { return 1 } }
169 if c == 43 { return 1 }
170 if c == 47 { return 1 }
171 if c == 61 { return 1 }
172 return 0
173}
174func cg_put_arg(req: *u8, off: i64, val: *u8, vlen: i64, first: i64) -> i64 {
175 if cg_json_safe(val, vlen) == 0 { return 0 - 1 }
176 var o: i64 = off
177 if first == 0 { req[o] = 44 as u8; o = o + 1 }
178 req[o] = CG_QUOTE as u8; o = o + 1
179 var i: i64 = 0
180 while i < vlen { req[o + i] = val[i]; i = i + 1 }
181 o = o + vlen
182 req[o] = CG_QUOTE as u8; o = o + 1
183 return o
184}
185func cg_put_lit(req: *u8, off: i64, lit: *u8) -> i64 {
186 var o: i64 = off
187 let n: i64 = cg_slen(lit)
188 var i: i64 = 0
189 while i < n { req[o + i] = lit[i]; i = i + 1 }
190 return o + n
191}
192func cg_streq(a: *u8, b: *u8) -> i64 {
193 var i: i64 = 0
194 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
195 if b[i] != (0 as u8) { return 0 }
196 return 1
197}
198// A SERVER DECISION, IN EITHER VOCABULARY. The organ speaks CG-; the transport speaks -32001. Both are
199// decisions and neither changes on a re-issue. This client does not retry at all, so this exists to
200// REPORT the distinction to the caller rather than to gate a loop -- a caller that cannot tell a denial
201// from a dropped packet will retry the one that cannot possibly succeed.
202func cg_decided(buf: *u8, n: i64) -> i64 {
203 if cg_find(buf, n, "capability denied" as *u8) >= 0 { return 1 }
204 if cg_find(buf, n, "-32001" as *u8) >= 0 { return 1 }
205 if cg_find(buf, n, "CG-REFUSED" as *u8) >= 0 { return 1 }
206 return 0
207}
208
209// Does this argument carry a readcap= prefix? Exactly 8 bytes, compared explicitly rather than by a
210// shared prefix helper, because a loose match would swallow the expect= argument beside it.
211func cg_is_readcap(a: *u8) -> i64 {
212 let want: *u8 = "readcap=" as *u8
213 var i: i64 = 0
214 while i < 8 {
215 if a[i] == (0 as u8) { return 0 }
216 if a[i] != want[i] { return 0 }
217 i = i + 1
218 }
219 return 1
220}
221func main(argc: i64, argv: *i64) -> i64 {
222 // dryrun emits the BEGIN request this client WOULD send and exits without opening a socket, so the
223 // envelope is a decidable deterministic artifact a gate can fork. Same reasoning as the sibling.
224 var base: i64 = 0
225 if argc > 1 { if cg_streq(argv[1] as *u8, "dryrun" as *u8) == 1 { base = 1 } }
226 if argc < 5 + base {
227 cg_eo("usage: nx_content_get_client [dryrun] <remote_src> <local_dest> <capfile> <url>\n" as *u8)
228 return 2
229 }
230 let src: *u8 = argv[1 + base] as *u8
231 let dest: *u8 = argv[2 + base] as *u8
232 let capfile: *u8 = argv[3 + base] as *u8
233 let url: *u8 = argv[4 + base] as *u8
234
235 // OPTIONAL read capability, used ONLY to follow the edge JOB-STARTED promotion to its artifact.
236 // A SCAN, not a positional slot, so it composes with the existing optional expect= argument in
237 // either order and breaks no existing caller -- the same shape nx_gate_bite uses for its subject
238 // keyword, adopted here for the same reason.
239 var rcraw: *u8 = 0 as *u8
240 var rclen: i64 = 0
241 var ai: i64 = 1
242 while ai < argc {
243 let av: *u8 = argv[ai] as *u8
244 if cg_is_readcap(av) == 1 {
245 let rcbox: *i64 = sys_mmap(16) as *i64
246 rcraw = sys_read_file(((av as i64) + 8) as *u8, rcbox)
247 if rcraw as i64 == 0 { cg_eo("CC-IO: cannot read readcap file\n" as *u8); return 3 }
248 rclen = rcbox[0]
249 while rclen > 0 {
250 let c2: i64 = rcraw[rclen - 1] as i64
251 if c2 <= CG_SPACE { rclen = rclen - 1 } else { rclen = 0 - rclen }
252 }
253 if rclen < 0 { rclen = 0 - rclen }
254 }
255 ai = ai + 1
256 }
257
258 let capbox: *i64 = sys_mmap(16) as *i64
259 let capraw: *u8 = sys_read_file(capfile, capbox)
260 if capraw as i64 == 0 { cg_eo("CG-IO: cannot read cap file\n" as *u8); return 3 }
261 var caplen: i64 = capbox[0]
262 // trim trailing whitespace -- a cap with a stray newline is the silent-401 class
263 while caplen > 0 {
264 let c: i64 = capraw[caplen - 1] as i64
265 if c <= CG_SPACE { caplen = caplen - 1 } else { caplen = 0 - caplen }
266 }
267 if caplen < 0 { caplen = 0 - caplen }
268 if cg_json_safe(capraw, caplen) == 0 {
269 cg_eo("CG-REFUSED: the capability token carries a byte that would need JSON escaping -- refusing rather than escaping badly\n" as *u8)
270 return 4
271 }
272
273 let store_i: i64 = hf_store_load()
274 let resp: *u8 = sys_mmap(CG_RESP + 16)
275 let req: *u8 = sys_mmap(CG_REQ + 16)
276 let nb: *u8 = sys_mmap(CG_NUMBUF)
277
278 // ---- BEGIN ----
279 var o: i64 = cg_put_lit(req, 0, CG_PRE)
280 o = cg_put_arg(req, o, "begin" as *u8, 5, 1)
281 o = cg_put_arg(req, o, src, cg_slen(src), 0)
282 if o < 0 { cg_eo("CG-REFUSED: remote src carries a byte needing JSON escaping\n" as *u8); return 4 }
283 o = cg_put_lit(req, o, "],\x22_cap\x22:" as *u8)
284 o = cg_put_arg(req, o, capraw, caplen, 1)
285 o = cg_put_lit(req, o, CG_TAIL)
286 if base == 1 { sys_write(1, req, o); sys_write(1, "\n" as *u8, 1); return 0 }
287
288 let n0: i64 = jf_post_follow(store_i, url, req, o, resp, CG_RESP, rcraw, rclen, CG_POLLS, CG_POLL_MS)
289 if n0 <= 0 { cg_eo("CG-BEGIN: post failed (transport) -- re-issue is safe, a get leaks no server state\n" as *u8); return 5 }
290 if cg_decided(resp, n0) == 1 {
291 cg_eo("CG-BEGIN: the SERVER DECIDED (refusal or capability denial). DO NOT RETRY -- the answer cannot change.\n" as *u8)
292 sys_write(2, resp, n0)
293 cg_eo("\n" as *u8)
294 return 5
295 }
296 let total: i64 = cg_int_after(resp, n0, "total_bytes=" as *u8)
297 let craw: i64 = cg_int_after(resp, n0, "chunk_raw=" as *u8)
298 let nch: i64 = cg_int_after(resp, n0, "nchunks=" as *u8)
299 let sp: i64 = cg_find(resp, n0, "sha256=" as *u8)
300 if total < 0 { cg_eo("CG-BEGIN: no total_bytes in receipt\n" as *u8); return 5 }
301 if craw <= 0 { cg_eo("CG-BEGIN: no chunk_raw in receipt (the server owns this number; it is never computed here)\n" as *u8); return 5 }
302 if nch <= 0 { cg_eo("CG-BEGIN: no nchunks in receipt\n" as *u8); return 5 }
303 if sp < 0 { cg_eo("CG-BEGIN: no sha256 in receipt -- without the whole-file digest a franken-read is undetectable, so this refuses rather than downloading blind\n" as *u8); return 5 }
304 let want: *u8 = sys_mmap(CG_SHAHEX + 8)
305 var wi: i64 = 0
306 while wi < CG_SHAHEX { want[wi] = resp[sp + wi]; wi = wi + 1 }
307 want[CG_SHAHEX] = 0 as u8
308 cg_out("CG-BEGIN src=" as *u8); cg_out(src)
309 cg_out(" bytes=" as *u8); cg_num(total)
310 cg_out(" chunk_raw=" as *u8); cg_num(craw)
311 cg_out(" nchunks=" as *u8); cg_num(nch)
312 cg_out(" sha256=" as *u8); cg_out(want); cg_out("\n" as *u8)
313
314 // ---- CHUNKS, assembled IN MEMORY. Nothing touches <local_dest> until the digest matches. ----
315 let acc: *u8 = sys_mmap(total + 64)
316 var got: i64 = 0
317 var idx: i64 = 0
318 while idx < nch {
319 var co: i64 = cg_put_lit(req, 0, CG_PRE)
320 co = cg_put_arg(req, co, "chunk" as *u8, 5, 1)
321 co = cg_put_arg(req, co, src, cg_slen(src), 0)
322 let xl: i64 = cg_itoa(idx, nb)
323 co = cg_put_arg(req, co, nb, xl, 0)
324 co = cg_put_lit(req, co, "],\x22_cap\x22:" as *u8)
325 co = cg_put_arg(req, co, capraw, caplen, 1)
326 co = cg_put_lit(req, co, CG_TAIL)
327 let cn: i64 = jf_post_follow(store_i, url, req, co, resp, CG_RESP, rcraw, rclen, CG_POLLS, CG_POLL_MS)
328 if cn <= 0 { cg_eo("CG-CHUNK: post failed (transport) at index " as *u8); cg_num(idx); cg_eo("\n" as *u8); return 6 }
329 if cg_decided(resp, cn) == 1 {
330 cg_eo("CG-CHUNK: the SERVER DECIDED at index " as *u8)
331 cg_num(idx)
332 cg_eo(" -- DO NOT RETRY\n" as *u8)
333 return 6
334 }
335 let bp: i64 = cg_find(resp, cn, "b64=" as *u8)
336 if bp < 0 { cg_eo("CG-CHUNK: no b64 payload at index " as *u8); cg_num(idx); cg_eo("\n" as *u8); return 6 }
337 var be: i64 = bp
338 var scan: i64 = 1
339 while scan == 1 {
340 if be >= cn { scan = 0 } else {
341 if cg_b64ch(resp[be] as i64) == 1 { be = be + 1 } else { scan = 0 }
342 }
343 }
344 let raw: i64 = b64_decode((resp as i64 + bp) as *u8, be - bp, (acc as i64 + got) as *u8)
345 if raw <= 0 { cg_eo("CG-CHUNK: base64 decode produced nothing at index " as *u8); cg_num(idx); cg_eo("\n" as *u8); return 6 }
346 got = got + raw
347 idx = idx + 1
348 }
349
350 // ---- THE WHOLE-FILE DIGEST DECIDES. A size match is not an identity. ----
351 if got != total {
352 cg_eo("CG-ASSEMBLY: reassembled " as *u8); cg_num(got)
353 cg_eo(" bytes against a declared " as *u8); cg_num(total)
354 cg_eo(" -- nothing written\n" as *u8)
355 return 7
356 }
357 let d: *u8 = sys_mmap(CG_DIGEST + 8)
358 sha256_digest(acc, got, d)
359 let hex: *u8 = sys_mmap(CG_SHAHEX + 8)
360 cg_hex_into(d, hex)
361 if cg_streq(hex, want) == 0 {
362 cg_eo("CG-DIGEST MISMATCH got=" as *u8)
363 sys_write(2, hex, CG_SHAHEX)
364 cg_eo(" want=" as *u8)
365 sys_write(2, want, CG_SHAHEX)
366 cg_eo("\n The source changed under the read, or a chunk is corrupt. NOTHING WAS WRITTEN.\n" as *u8)
367 return 7
368 }
369 let fd: i64 = sys_openat_wr(dest, CG_MODE_RW)
370 if fd < 0 { cg_eo("CG-IO: cannot open local dest for write\n" as *u8); return 3 }
371 var off: i64 = 0
372 while off < got {
373 let w: i64 = sys_write(fd, (acc as i64 + off) as *u8, got - off)
374 if w <= 0 { sys_close(fd); cg_eo("CG-IO: short write\n" as *u8); return 3 }
375 off = off + w
376 }
377 sys_close(fd)
378 cg_out("E2E OK: bytes=" as *u8); cg_num(got)
379 cg_out(" chunks=" as *u8); cg_num(nch)
380 cg_out(" sha256=" as *u8); cg_out(hex)
381 cg_out(" -> " as *u8); cg_out(dest); cg_out("\n" as *u8)
382 return 0
383}