code wiki / (root) / nx_content_get_client_gate.nx

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}