nx_content_get_client_gate.nx source
↩ module page · 268 lines · 14451 B
1// nx_content_get_client_gate.nx -- THE GATE FOR THE SOVEREIGN DOWNLOAD CLIENT, 2026-09-04.
2//
3// SUBJECT: the BUILT nx_content_get_client elf, forked in `dryrun` mode so the JSON-RPC envelope it
4// would put on the wire becomes a deterministic artifact with no socket involved.
5//
6// WHY dryrun IS THE RIGHT SUBJECT, and it is the sibling's reasoning inherited rather than re-derived:
7// a malformed envelope is rejected by the SERVER, and the server's refusal names neither the client nor
8// the real fault -- it reports a schema or argument error about a request nobody can see. The upload
9// client's first draft carried a DOUBLED params key with a four-brace tail that balanced against itself:
10// syntactically valid JSON of entirely the wrong SHAPE, which no live run would have named. T2 is here
11// because this client copies that constant's structure and could copy its defect.
12//
13// THE ASYMMETRY WORTH STATING: the upload gate carries an EXTERNAL SHA-256 KAT, because its client
14// hashes the local file before begin and the digest it sends is checkable against the FIPS 180-2
15// published value for "abc". THIS CLIENT HASHES NOTHING AT BEGIN -- in a get the digest is DECLARED BY
16// THE SERVER and the client's job is to verify its reassembly against it. So there is no KAT to run
17// here, and inventing one would be a tooth that tests nothing. That absence is declared rather than
18// quietly omitted, because a reader comparing the two gates should be able to see WHY they differ.
19//
20// T3 IS THE TOOTH THE UPLOAD GATE CANNOT HAVE, and it is the one that guards this client's whole
21// premise. In a get the client knows NEITHER the total size NOR the whole-file digest -- both are the
22// server's, read from its begin receipt. So the begin argv must contain EXACTLY TWO elements: the verb
23// and the source path. If some future edit ever has this client compute a size or a digest and send it,
24// the argv grows and this tooth fires. That is the "the chunk size is the server's, never computed here"
25// rule made mechanical instead of merely written in a comment.
26//
27// T6/T7 ARE THE REFUSAL PAIR, on the sibling's precedent. T6 requires a source carrying a double-quote
28// to be REFUSED rather than escaped, because a hand-rolled JSON escaper is wrong in exactly the cases
29// nobody tests. On its own T6 would pass on a client that refused EVERYTHING, so T7 requires a safe
30// source to be ACCEPTED. Neither is evidence without the other.
31//
32// Teeth, in order:
33// T1 the envelope's braces and brackets balance.
34// T2 "params" appears EXACTLY ONCE (the doubled-key defect that shipped in the sibling's first draft).
35// T3 the begin argv carries EXACTLY TWO elements -- the client sends no size and no digest of its own.
36// T4 the remote source appears verbatim in the envelope.
37// T5 the capability token appears verbatim (a client that dropped it would 401 for the wrong reason).
38// T6 NEG-CONTROL: a source containing a quote is REFUSED (exit 4), never escaped.
39// T7 POSITIVE CONTROL: a safe source is ACCEPTED (exit 0), so T6 cannot pass on a refuse-everything client.
40// T8 the argv array opens with the begin verb.
41// T9 NEG-CONTROL: an unreadable capability file is CG-IO (exit 3), not a silent empty token.
42// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
43import "nx_syscalls.nx"
44import "nx_gate_verdict.nx"
45import "nx_tool_run.nx"
46
47const CGG_S_A: *u8 = "buildroot/_build/nx_content_get_client.sov.elf" as *u8
48const CGG_S_B: *u8 = "_build/nx_content_get_client.sov.elf" as *u8
49const CGG_S_C: *u8 = "_offc/nx_content_get_client.elf" as *u8
50const CGG_S_D: *u8 = "nx_content_get_client.elf" as *u8
51const CGG_DIR: *u8 = "/tmp/cgcli" as *u8
52const CGG_CAPF: *u8 = "/tmp/cgcli/cap.txt" as *u8
53const CGG_NOCAP: *u8 = "/tmp/cgcli/no_such_cap.txt" as *u8
54const CGG_CAP: i64 = 65536
55const CGG_MODE: i64 = 420
56const CGG_SRC: *u8 = "runtime/nx_gate_bite.nx" as *u8
57const CGG_DEST: *u8 = "/tmp/cgcli/pulled.nx" as *u8
58const CGG_CAPTOK: *u8 = "tok~1~abcDEF.ghi-jkl" as *u8
59const CGG_URL: *u8 = "https://example.invalid/mcp" as *u8
60const CGG_LBRACE: i64 = 123
61const CGG_RBRACE: i64 = 125
62const CGG_LBRACK: i64 = 91
63const CGG_RBRACK: i64 = 93
64const CGG_COMMA: i64 = 44
65
66func cgg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
67func cgg_count(buf: *u8, n: i64, needle: *u8) -> i64 {
68 let m: i64 = cgg_slen(needle)
69 if m == 0 { return 0 }
70 var i: i64 = 0
71 var c: i64 = 0
72 while i + m <= n {
73 var j: i64 = 0
74 var same: i64 = 1
75 while j < m { if buf[i + j] != needle[j] { same = 0; j = m } else { j = j + 1 } }
76 if same == 1 { c = c + 1 }
77 i = i + 1
78 }
79 return c
80}
81func cgg_has(buf: *u8, n: i64, needle: *u8) -> i64 {
82 if cgg_count(buf, n, needle) > 0 { return 1 }
83 return 0
84}
85func cgg_byte_count(buf: *u8, n: i64, b: i64) -> i64 {
86 var i: i64 = 0
87 var c: i64 = 0
88 while i < n { if buf[i] == (b as u8) { c = c + 1 } i = i + 1 }
89 return c
90}
91// count elements inside the FIRST bracketed array: commas between the opening and closing bracket, +1.
92// Reading the argv arity from the artifact, never assuming it.
93func cgg_argv_len(buf: *u8, n: i64) -> i64 {
94 var i: i64 = 0
95 var start: i64 = 0 - 1
96 while i < n { if buf[i] == (CGG_LBRACK as u8) { if start < 0 { start = i } } i = i + 1 }
97 if start < 0 { return 0 - 1 }
98 var j: i64 = start + 1
99 var commas: i64 = 0
100 var go: i64 = 1
101 while go == 1 {
102 if j >= n { go = 0 } else {
103 if buf[j] == (CGG_RBRACK as u8) { go = 0 } else {
104 if buf[j] == (CGG_COMMA as u8) { commas = commas + 1 }
105 j = j + 1
106 }
107 }
108 }
109 return commas + 1
110}
111func cgg_write_file(path: *u8, buf: *u8, n: i64) -> i64 {
112 let fd: i64 = sys_openat_wr(path, CGG_MODE)
113 if fd < 0 { return 0 - 1 }
114 var off: i64 = 0
115 var go: i64 = 1
116 while go == 1 {
117 if off >= n { go = 0 } else {
118 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off)
119 if w <= 0 { go = 0 } else { off = off + w }
120 }
121 }
122 sys_close(fd)
123 return off
124}
125func cgg_exists(p: *u8) -> i64 {
126 let fd: i64 = sys_openat_rd(p)
127 if fd < 0 { return 0 }
128 sys_close(fd)
129 return 1
130}
131func cgg_resolve() -> *u8 {
132 if cgg_exists(CGG_S_A) == 1 { return CGG_S_A }
133 if cgg_exists(CGG_S_B) == 1 { return CGG_S_B }
134 if cgg_exists(CGG_S_C) == 1 { return CGG_S_C }
135 if cgg_exists(CGG_S_D) == 1 { return CGG_S_D }
136 return 0 as *u8
137}
138func cgg_run(subj: *u8, src: *u8, capf: *u8, out: *u8, ol: *i64) -> i64 {
139 let av: *i64 = sys_mmap(64) as *i64
140 av[0] = subj as i64
141 av[1] = "dryrun" as i64
142 av[2] = src as i64
143 av[3] = CGG_DEST as i64
144 av[4] = capf as i64
145 av[5] = CGG_URL as i64
146 av[6] = 0
147 return tr_run_capture(subj, av, out, CGG_CAP, ol)
148}
149
150func main(argc: i64, argv: *i64) -> i64 {
151 let ctr: *i64 = gv_ctr()
152 gv_head("nx_content_get_client gate -- the JSON-RPC envelope, made inspectable so a wrong SHAPE cannot hide behind a server error" as *u8)
153
154 var subj: *u8 = cgg_resolve()
155 if argc > 1 { subj = argv[1] as *u8 }
156 if subj as i64 == 0 {
157 gv_need("a-built-nx_content_get_client-artifact-on-any-known-path" as *u8, 0, ctr)
158 return gv_verdict("content_get_client" as *u8, ctr, "ABSTAINED: no built subject found, so nothing was measured -- a fork of a missing elf exits 127 and would have convicted a subject that never ran" as *u8)
159 }
160 gv_puts(" subject resolved by stat: " as *u8); gv_puts(subj); gv_puts("\n" as *u8)
161
162 sys_mkdir(CGG_DIR, 0x1ff)
163 cgg_write_file(CGG_CAPF, CGG_CAPTOK, cgg_slen(CGG_CAPTOK))
164
165 let out: *u8 = sys_mmap(CGG_CAP + 16)
166 let ol: *i64 = sys_mmap(16) as *i64
167 let rc: i64 = cgg_run(subj, CGG_SRC, CGG_CAPF, out, ol)
168 let n: i64 = *ol
169
170 // ---- T1 balance ----
171 let lb: i64 = cgg_byte_count(out, n, CGG_LBRACE)
172 let rb: i64 = cgg_byte_count(out, n, CGG_RBRACE)
173 let lk: i64 = cgg_byte_count(out, n, CGG_LBRACK)
174 let rk: i64 = cgg_byte_count(out, n, CGG_RBRACK)
175 gv_puts(" [T1] exit=" as *u8); gv_num(rc); gv_puts(" bytes=" as *u8); gv_num(n)
176 gv_puts(" braces " as *u8); gv_num(lb); gv_puts("/" as *u8); gv_num(rb)
177 gv_puts(" brackets " as *u8); gv_num(lk); gv_puts("/" as *u8); gv_num(rk); gv_puts("\n" as *u8)
178 var t1: i64 = 0
179 if rc == 0 { if lb > 0 { if lb == rb { if lk == rk { if lk > 0 { t1 = 1 } } } } }
180 gv_check("the-envelope-braces-and-brackets-BALANCE" as *u8, t1, ctr)
181
182 // ---- T2 the doubled-key defect ----
183 let np: i64 = cgg_count(out, n, "\x22params\x22" as *u8)
184 gv_puts(" [T2] occurrences of the params key=" as *u8); gv_num(np); gv_puts(" want=1\n" as *u8)
185 gv_check("the-params-key-appears-EXACTLY-ONCE (the sibling's first draft doubled it and its four-brace tail balanced against itself -- valid JSON of the wrong SHAPE)" as *u8, (np == 1) as i64, ctr)
186
187 // ---- T3 the get-specific invariant: the client sends no size and no digest of its own ----
188 let al: i64 = cgg_argv_len(out, n)
189 gv_puts(" [T3] begin argv element count=" as *u8); gv_num(al); gv_puts(" want=2 (verb + src)\n" as *u8)
190 gv_check("the-begin-argv-carries-EXACTLY-TWO-elements-so-the-client-sends-NO-size-and-NO-digest-of-its-own (in a get both are the SERVER's, read from its receipt -- this makes that rule mechanical instead of a comment)" as *u8, (al == 2) as i64, ctr)
191
192 // ---- T4 the remote source ----
193 let hs: i64 = cgg_has(out, n, CGG_SRC)
194 gv_puts(" [T4] remote src present=" as *u8); gv_num(hs); gv_puts("\n" as *u8)
195 gv_check("the-remote-source-appears-VERBATIM-in-the-envelope" as *u8, hs, ctr)
196
197 // ---- T5 capability token ----
198 let hc: i64 = cgg_has(out, n, CGG_CAPTOK)
199 gv_puts(" [T5] cap token present=" as *u8); gv_num(hc); gv_puts("\n" as *u8)
200 gv_check("the-capability-token-appears-VERBATIM (a client that dropped it would 401 for a reason naming the wrong subject)" as *u8, hc, ctr)
201
202 // ---- T8 the verb ----
203 let hv: i64 = cgg_has(out, n, "[\x22begin\x22" as *u8)
204 gv_puts(" [T8] argv opens with the begin verb=" as *u8); gv_num(hv); gv_puts("\n" as *u8)
205 gv_check("the-argv-array-opens-with-the-begin-verb" as *u8, hv, ctr)
206
207 // ---- T6 NEG-CONTROL: refuse-never-escape ----
208 let bad: *u8 = sys_mmap(128)
209 bad[0] = 114 as u8
210 bad[1] = 34 as u8
211 bad[2] = 120 as u8
212 bad[3] = 0 as u8
213 let out2: *u8 = sys_mmap(CGG_CAP + 16)
214 let ol2: *i64 = sys_mmap(16) as *i64
215 let rc6: i64 = cgg_run(subj, bad, CGG_CAPF, out2, ol2)
216 gv_puts(" [T6] source containing a quote -> exit=" as *u8); gv_num(rc6); gv_puts(" want=4\n" as *u8)
217 gv_check("neg-control-a-source-carrying-a-quote-is-REFUSED-not-escaped (a hand-rolled escaper is wrong in exactly the cases nobody tests)" as *u8, (rc6 == 4) as i64, ctr)
218
219 // ---- T7 POSITIVE CONTROL ----
220 gv_puts(" [T7] safe source -> exit=" as *u8); gv_num(rc); gv_puts(" want=0\n" as *u8)
221 gv_check("POSITIVE-CONTROL-a-safe-source-is-ACCEPTED (without this T6 passes on a client that refuses everything)" as *u8, (rc == 0) as i64, ctr)
222
223 // ---- T9 NEG-CONTROL: an unreadable cap file is named, never silently empty ----
224 let out3: *u8 = sys_mmap(CGG_CAP + 16)
225 let ol3: *i64 = sys_mmap(16) as *i64
226 let rc9: i64 = cgg_run(subj, CGG_SRC, CGG_NOCAP, out3, ol3)
227 gv_puts(" [T9] unreadable capfile -> exit=" as *u8); gv_num(rc9); gv_puts(" want=3\n" as *u8)
228 gv_check("neg-control-an-unreadable-capability-file-is-CG-IO-exit-3 (a client that sent an empty token would 401 and send every reader after the wrong subject)" as *u8, (rc9 == 3) as i64, ctr)
229
230 // ---- EVERY POST MUST ROUTE THROUGH THE JOB-LANE FOLLOWER ----
231 // MEASURED 2026-09-04, and this gate was GREEN throughout the eleven days it shipped broken: the
232 // edge answers a tools/call with `JOB-STARTED id=<n>` and the real receipt lands in
233 // _jobs/job_<n>.out. A client that posts directly reads the JOB id as the ORGAN's id -- both are
234 // spelled `id=` and every reader here scans that key unanchored -- and then reports a receipt that
235 // never existed. NO ENVELOPE TOOTH CAN SEE THAT, because the envelope is perfect; only the ROUTING
236 // is wrong, which is exactly why nine green teeth said nothing.
237 // SCOPE, STATED RATHER THAN IMPLIED: this proves the client CANNOT BYPASS the follower. It does not
238 // prove the follower works -- that is the push-then-pull round trip, and a gate tooth must not be
239 // read as standing in for it.
240 var srcn: i64 = 0
241 let srcbox: *i64 = sys_mmap(16) as *i64
242 // The source path is CWD-relative and this gate runs from two different roots, so it is PROBED,
243 // never assumed: a bare compare-style path is correct in one caller and a confident false negative
244 // in its neighbour, and no grep can tell the two apart.
245 var src: *u8 = sys_read_file("runtime/nx_content_get_client.nx" as *u8, srcbox)
246 if (src as i64) == 0 { src = sys_read_file("buildroot/runtime/nx_content_get_client.nx" as *u8, srcbox) }
247 if (src as i64) != 0 { srcn = srcbox[0] }
248 var direct: i64 = 0 - 1
249 var routed: i64 = 0 - 1
250 if srcn > 0 {
251 direct = cgg_count(src, srcn, "hp_post_json(" as *u8)
252 routed = cgg_count(src, srcn, "jf_post_follow(" as *u8)
253 }
254 gv_check("the-subject-source-IS-READABLE-so-the-two-routing-teeth-below-are-not-vacuous (a gate that cannot open its subject must ABSTAIN, never acquit)" as *u8, (srcn > 0) as i64, ctr)
255 gv_check("NO-post-bypasses-the-job-lane-follower (a direct transport post reads a JOB-STARTED id as the organ's own id and reports a receipt that never existed)" as *u8, (direct == 0) as i64, ctr)
256 gv_check("at-least-one-post-IS-routed-through-the-follower (anti-vacuity: without this, the tooth above passes on a client that posts nothing at all)" as *u8, (routed > 0) as i64, ctr)
257
258 gv_kv("envelope_bytes", n)
259 gv_kv("argv_elements", al)
260 gv_kv("params_key_count", np)
261 gv_kv("exit_safe_source", rc)
262 gv_kv("exit_quoted_source", rc6)
263 gv_kv("exit_missing_capfile", rc9)
264 gv_kv("subject_source_bytes", srcn)
265 gv_kv("direct_transport_posts", direct)
266 gv_kv("follower_routed_posts", routed)
267 return gv_verdict("content_get_client" as *u8, ctr, "the download client's begin envelope is well-formed, carries no client-computed size or digest, and refuses an unsafe source rather than escaping it" as *u8)
268}