nx_toolsapi_bodycap_gate.nx
buildroot/runtime/nx_toolsapi_bodycap_gate.nx
about
nx_toolsapi_bodycap_gate.nx -- END-TO-END referee for the tools-API request-body fix (2026-08-23).
THE DEFECT IT GUARDS: nx_tools_api_serve read a request with ONE sys_read of 64 KiB and handed the partial
bytes to the edge; every tools/call above that answered 413, and the laptop wire rendered the 413 as a bare
{} -- a whole-file source write read as "dropped" and nobody could say why. Behind it, the argv scratch in
ta_mcp_call was a second 64 KiB constant that kept "what fit": a file content above 65535 bytes was written
as its prefix with an OK receipt.
HOW IT PROVES IT: forks the SUBJECT daemon (default: the STAGED ./nx_tools_api_serve.sov.elf.new; argv[1]
`live` = the serving-root binary; any other argv[1] = an explicit elf path) with CWD = /tmp/<gate>/ carrying a
fixture cap secret and a one-row allowlist (argecho, the estate's multi-arg witness organ), mints a cap for
that secret in-process with the estate's own capt_issue, and drives REAL loopback POST /mcp tools/call
requests whose "name" key comes LAST -- after the payload -- so a truncated body can never name the tool.
T1 8 KB argv echoed whole (positive control: fits one read on the old and the new binary)
T1b the 8 KB reply is BYTE-IDENTICAL to the LIVE daemon's reply on a second port (behaviour control)
T2 63 KB argv echoed whole (the measured failing case: the request exceeds 65536 bytes on the wire)
T3 120 KB argv echoed whole (over the old window, under TA_CAPTURE_CAP so the echo itself is not cut)
T4 neg-control: a request declaring Content-Length above EDGE_REQ_MAX answers 413 naming the limit
T5 no reply is the bare {} nor the old "64KiB edge limit" text
T6 fixture reached the condition: the T2 request really was > 65536 bytes
T7 the subject child is gone after the gate (its port refuses again) -- a probe that leaves a daemon is a second instance
A free port is PROVEN free (connect refused) before the subject binds it.
license_tier: ORIGINAL expect_exit: 0 GREEN | 1 RED | 3 SKIP
dependencies 5 imports · 0 importers
imports: nx_syscalls.nxnx_connect.nxnx_http_server.nxnx_cap_token.nxnx_gate_verdict.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 30 | const BG_FIX_DIR: *u8 = "/tmp/nx_toolsapi_bodycap_gate" |
| 31 | const BG_STAGED: *u8 = "/volume1/homes/elderwesto/nishihost/nx_tools_api_serve.sov.elf.new" |
| 32 | const BG_LIVE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_tools_api_serve.elf" |
| 33 | const BG_ARGECHO: *u8 = "/volume1/homes/elderwesto/nishihost/nx_tool_argecho.elf" |
| 34 | const BG_SECRET: *u8 = "bodycap-gate-fixture-secret-not-a-production-key" |
| 35 | const BG_PORT_SUBJECT_DEFAULT: i64 = 18877 // argv[2] overrides; verified FREE before use; refused-again after the kill |
| 36 | const BG_PORT_CONTROL_DEFAULT: i64 = 18878 // argv[3] overrides |
| 37 | const BG_SOCK_TMO_S: i64 = 20 // a reply that does not arrive in 20 s is a hang, reported as one -- never a parked gate |
| 38 | const BG_KB: i64 = 1024 |
| 39 | const BG_PAYLOAD_SMALL: i64 = 8 * BG_KB |
| 40 | const BG_PAYLOAD_EDGE: i64 = 64 * BG_KB // the measured failing class: with the envelope the request exceeds 65536 on the wire |
| 42 | const BG_PAYLOAD_BIG: i64 = 120 * BG_KB // over the old window; under TA_CAPTURE_CAP (163840) so the ECHO is whole |
| 43 | const BG_OLD_WINDOW: i64 = 65536 |
| 44 | const BG_OVER_LIMIT_DECLARED: i64 = 2 * 1048576 // above EDGE_REQ_MAX (= EDGE_RESP_CAP = 1 MiB) |
| 45 | const BG_RESP_CAP: i64 = 2097152 // reply reserve: an echo of BG_PAYLOAD_BIG plus envelope, announced if it fills |
| 46 | const BG_BOOT_TRIES: i64 = 100 // 100 x 100 ms = 10 s for the child to reach listen |
| 47 | const BG_BOOT_STEP_MS: i64 = 100 |
| 48 | const BG_CONNECT_MS: i64 = 3000 |
| 49 | const BG_SIGKILL: i64 = 9 |
| 50 | const BG_CAP_TTL_S: i64 = 600 |
functions
| 51 | func bg_atoi(s: *u8) -> i64 { var v: i64 = 0; var i: i64 = 0; while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } return v } called by 1: main |
| 53 | func bg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 54 | func bg_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var p: i64 = o; while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 } d[p] = 0 as u8; return p } |
| 55 | func bg_catn(d: *u8, o: i64, v: i64) -> i64 |
| 68 | func bg_has(hay: *u8, hn: i64, needle: *u8) -> i64 |
| 82 | func bg_xrun(b: *u8, n: i64) -> i64 called by 1: main |
| 92 | func bg_write_file(path: *u8, data: *u8, n: i64) -> i64 |
| 101 | func bg_connect(port: i64) -> i64 |
| 111 | func bg_spawn(elf: *u8, port: i64) -> i64 |
| 134 | func bg_wait_listen(port: i64) -> i64 |
| 145 | func bg_build(req: *u8, plen: i64, cap: *u8) -> i64 |
| 161 | func bg_roundtrip(port: i64, req: *u8, rn: i64, out: *u8, cap: i64, filled: *i64) -> i64 |
| 180 | func bg_head(b: *u8, n: i64) -> i64 |
| 189 | func bg_kill(pid: i64) -> i64 |
| 198 | func main(argc: i64, argv: *i64) -> i64 |