code wiki / (root) / nx_mcp_stdio_gate_t174.nx

nx_mcp_stdio_gate_t174.nx source

↩ module page · 197 lines · 9891 B

1// nx_mcp_stdio_gate.nx -- lane conn, 2026-09-11. 2// 3// PROVES the connector's per-request failure semantics with REAL child processes, in-process (the 4// connector's main-bearing file cannot be imported, so the recovery + emit logic it drives lives in 5// nx_mcp_pending, which this gate imports directly): 6// * a worker that dies WITHOUT self-reporting (a crash, or the SIGALRM deadline that kills it) becomes 7// a CORRELATED JSON-RPC error for THAT id and the loop CONTINUES (mp_recover_pump returns >= 0); 8// * the emitted error carries the RIGHT id and the JSON-RPC error shape; 9// * a LATER request still succeeds after the failure; 10// * the deadline (death by SIGALRM) maps to the -32012 deadline error, not a generic one; 11// * mo_prefix_id extracts a top-level id from a truncated request prefix (string, number, truncated, 12// nested-only) -- the input to the oversized/malformed -32600 path; 13// * a NEG-CONTROL: a SUCCESSFUL worker is forwarded verbatim and NEVER decorated with an error. 14// 15// BITE-PROVED MANUALLY (recorded in the lane transcript, not via a forked subject elf since the subject 16// is in-process): reverting the failed_slot mechanism so mp_recover_pump returns -1 on any rp_pump -1 17// makes failing-child-continues + later-request-succeeds go RED; restoring the fix returns them GREEN. 18// license_tier: ORIGINAL No hw writes (Rule 26). 19import "nx_mcp_pending_http_t174.nx" 20import "nx_gate_verdict.nx" 21 22func g_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 23 24// literal substring search over an exact byte window (no NUL assumptions on buf). 25func g_contains(buf: *u8, n: i64, pat: *u8) -> i64 { 26 let pn: i64 = g_slen(pat) 27 if pn == 0 { return 1 } 28 var i: i64 = 0 29 while i + pn <= n { 30 var j: i64 = 0 31 while j < pn { if buf[i+j] != pat[j] { break } j = j + 1 } 32 if j == pn { return 1 } 33 i = i + 1 34 } 35 return 0 36} 37 38// Run one planted worker to completion through mp_recover_pump, capturing everything it forwards to the 39// protocol pipe. Returns the captured byte count via *outn (into cap-sized buf) and 1 if no FATAL was 40// hit, 0 if a fatal (-1) ended the drain. mode 0 = child dies non-zero without writing (crash sim); 41// mode 1 = child writes `payload` then exits 0 (success); mode 2 = child blocks past a 1s alarm (deadline). 42func g_run_planted(p: *NxRequestPool, rows: *u8, control: *NxMcpControl, line: *u8, mode: i64, payload: *u8, buf: *u8, cap: i64, outn: *i64) -> i64 { 43 var op: i64 = 0 44 if sys_pipe2(&op, 0) != 0 { *outn = 0; return 0 } 45 let ord: i64 = op & RP_FD_MASK 46 let owr: i64 = (op >> 32) & RP_FD_MASK 47 var bp: i64 = 0 48 if sys_pipe2(&bp, 0) != 0 { *outn = 0; return 0 } 49 let brd: i64 = bp & RP_FD_MASK 50 let bwr: i64 = (bp >> 32) & RP_FD_MASK 51 if mct_read(line, g_slen(line), control) != 0 { *outn = 0; return 0 } 52 let pid: i64 = mp_spawn(p, rows, line, control) 53 if pid == 0 { 54 // CHILD. 55 if mode == 0 { sys_exit(7); return 7 } 56 if mode == 2 { 57 sys_alarm(1) 58 var x: i64 = 0 59 sys_read(brd, (&x) as *u8, 1) // blocks until SIGALRM (no writer ever writes); default-kills by signal 14 60 sys_exit(0); return 0 61 } 62 rp_write(p.child_fd, payload, g_slen(payload)) 63 sys_exit(0); return 0 64 } 65 var fatal: i64 = 0 66 while p.active > 0 { 67 if mp_recover_pump(p, rows, 0, owr, 5) < 0 { fatal = 1; break } 68 } 69 sys_close(owr); sys_close(bwr); sys_close(brd) 70 var got: i64 = 0 71 if fatal == 0 { 72 got = sys_read(ord, buf, cap) 73 if got < 0 { got = 0 } 74 } 75 sys_close(ord) 76 *outn = got 77 if fatal != 0 { return 0 } 78 return 1 79} 80 81func main() -> i64 { 82 let c: *i64 = gv_ctr() 83 let plan: *i64 = gv_plan_new("prefix-id-string\nprefix-id-number\nprefix-id-truncated\nprefix-id-nested-only\nemit-string-id\nemit-number-id\nemit-notification-silent\nfailing-child-continues\nfailing-child-error-has-id\nfailing-child-slot-freed\ndeadline-maps-to-correlated-error\nlater-request-succeeds\nneg-control-success-not-an-error\n" as *u8) 84 85 // ---- mo_prefix_id (feeds the oversized/malformed -32600 path) ---- 86 var k: i64 = 0 87 var o: i64 = 0 88 var l: i64 = 0 89 let s1: *u8 = "{\"jsonrpc\":\"2.0\",\"id\":\"alpha\",\"method\":\"tools/call\"" as *u8 90 let r1: i64 = mo_prefix_id(s1, g_slen(s1), &k, &o, &l) 91 let ok1: i64 = r1 == 1 && k == NX_JSON_STRING && l == 5 && s1[o] == (97 as u8) && s1[o+4] == (97 as u8) 92 gv_plan_check(plan, "prefix-id-string", ok1, c) 93 94 var k2: i64 = 0 95 var o2: i64 = 0 96 var l2: i64 = 0 97 let s2: *u8 = "{\"jsonrpc\":\"2.0\",\"id\": 42 ,\"method\":\"m\"" as *u8 98 let r2: i64 = mo_prefix_id(s2, g_slen(s2), &k2, &o2, &l2) 99 let ok2: i64 = r2 == 1 && k2 == NX_JSON_NUMBER && l2 == 2 && s2[o2] == (52 as u8) && s2[o2+1] == (50 as u8) 100 gv_plan_check(plan, "prefix-id-number", ok2, c) 101 102 var k3: i64 = 0 103 var o3: i64 = 0 104 var l3: i64 = 0 105 let s3: *u8 = "{\"jsonrpc\":\"2.0\",\"id\":\"alph" as *u8 // value cut short: unreadable 106 let r3: i64 = mo_prefix_id(s3, g_slen(s3), &k3, &o3, &l3) 107 gv_plan_check(plan, "prefix-id-truncated", r3 == 0, c) 108 109 var k4: i64 = 0 110 var o4: i64 = 0 111 var l4: i64 = 0 112 let s4: *u8 = "{\"params\":{\"id\":\"x\"},\"method\":\"m\"" as *u8 // id is nested, no top-level id 113 let r4: i64 = mo_prefix_id(s4, g_slen(s4), &k4, &o4, &l4) 114 gv_plan_check(plan, "prefix-id-nested-only", r4 == 0, c) 115 116 // ---- mpe_write_error shape (string id, number id, notification silence) ---- 117 let eb: *u8 = sys_mmap(2048) 118 var ep: i64 = 0 119 if sys_pipe2(&ep, 0) != 0 { return 2 } 120 let erd: i64 = ep & RP_FD_MASK 121 let ewr: i64 = (ep >> 32) & RP_FD_MASK 122 mpe_write_error(ewr, NX_JSON_STRING, "alpha" as *u8, 5, 0, MP_CODE_WORKER, "worker" as *u8, "planted" as *u8, 7, "n" as *u8) 123 sys_close(ewr) 124 let en: i64 = sys_read(erd, eb, 2048) 125 sys_close(erd) 126 let oke: i64 = en > 0 && g_contains(eb, en, "\"id\":\"alpha\"" as *u8) == 1 && g_contains(eb, en, "\"error\"" as *u8) == 1 && g_contains(eb, en, "\"code\":-32603" as *u8) == 1 127 gv_plan_check(plan, "emit-string-id", oke, c) 128 129 let nb: *u8 = sys_mmap(2048) 130 var np: i64 = 0 131 if sys_pipe2(&np, 0) != 0 { return 2 } 132 let nrd: i64 = np & RP_FD_MASK 133 let nwr: i64 = (np >> 32) & RP_FD_MASK 134 mpe_write_error(nwr, NX_JSON_NUMBER, "42" as *u8, 2, 0, MP_CODE_DEADLINE, "deadline" as *u8, "planted" as *u8, 5, "n" as *u8) 135 sys_close(nwr) 136 let nn: i64 = sys_read(nrd, nb, 2048) 137 sys_close(nrd) 138 let okn: i64 = nn > 0 && g_contains(nb, nn, "\"id\":42," as *u8) == 1 && g_contains(nb, nn, "\"code\":-32012" as *u8) == 1 139 gv_plan_check(plan, "emit-number-id", okn, c) 140 141 var sp: i64 = 0 142 if sys_pipe2(&sp, 0) != 0 { return 2 } 143 let srd: i64 = sp & RP_FD_MASK 144 let swr: i64 = (sp >> 32) & RP_FD_MASK 145 let silence_rc: i64 = mpe_write_error(swr, 0, "ignored" as *u8, 7, 0, MP_CODE_WORKER, "worker" as *u8, "planted" as *u8, 0, "n" as *u8) 146 sys_close(swr) 147 let sb: *u8 = sys_mmap(64) 148 let sn: i64 = sys_read(srd, sb, 64) 149 sys_close(srd) 150 gv_plan_check(plan, "emit-notification-silent", silence_rc == 0 && sn == 0, c) 151 152 // ---- REAL planted children through mp_recover_pump ---- 153 let rows: *u8 = sys_mmap(2 * __size_of(NxMcpPending)) 154 let control: *NxMcpControl = sys_mmap(__size_of(NxMcpControl)) as *NxMcpControl 155 let cap: i64 = 4096 156 let buf: *u8 = sys_mmap(cap) 157 158 // Failing worker (crash sim: exits non-zero, writes nothing). 159 let pf: *NxRequestPool = rp_new(2, cap) 160 var fn: i64 = 0 161 let fcont: i64 = g_run_planted(pf, rows, control, 162 "{\"jsonrpc\":\"2.0\",\"id\":\"alpha\",\"method\":\"tools/call\"}" as *u8, 0, "" as *u8, buf, cap, &fn) 163 gv_plan_check(plan, "failing-child-continues", fcont == 1, c) 164 gv_plan_check(plan, "failing-child-error-has-id", 165 fn > 0 && g_contains(buf, fn, "\"id\":\"alpha\"" as *u8) == 1 && g_contains(buf, fn, "\"error\"" as *u8) == 1, c) 166 gv_plan_check(plan, "failing-child-slot-freed", pf.active == 0, c) 167 168 // Deadline worker (blocks past a 1s alarm -> death by signal 14 -> -32012 deadline error). 169 let pd: *NxRequestPool = pf 170 let db: *u8 = sys_mmap(cap) 171 var dn: i64 = 0 172 let dcont: i64 = g_run_planted(pd, rows, control, 173 "{\"jsonrpc\":\"2.0\",\"id\":\"gamma\",\"method\":\"tools/call\"}" as *u8, 2, "" as *u8, db, cap, &dn) 174 gv_plan_check(plan, "deadline-maps-to-correlated-error", 175 dcont == 1 && dn > 0 && g_contains(db, dn, "\"id\":\"gamma\"" as *u8) == 1 && g_contains(db, dn, "\"code\":-32012" as *u8) == 1 && g_contains(db, dn, "deadline" as *u8) == 1, c) 176 177 // Later request on the SAME pool after crash and deadline succeeds without an error. 178 let ps: *NxRequestPool = pf 179 let okbuf: *u8 = sys_mmap(cap) 180 var okn2: i64 = 0 181 let scont: i64 = g_run_planted(ps, rows, control, 182 "{\"jsonrpc\":\"2.0\",\"id\":\"beta\",\"method\":\"tools/call\"}" as *u8, 1, 183 "{\"jsonrpc\":\"2.0\",\"id\":\"beta\",\"result\":{}}\n" as *u8, okbuf, cap, &okn2) 184 gv_plan_check(plan, "later-request-succeeds", 185 scont == 1 && ps.active == 0 && okn2 > 0 && g_contains(okbuf, okn2, "\"id\":\"beta\"" as *u8) == 1 && g_contains(okbuf, okn2, "result" as *u8) == 1, c) 186 gv_plan_check(plan, "neg-control-success-not-an-error", 187 okn2 > 0 && g_contains(okbuf, okn2, "\"error\"" as *u8) == 0, c) 188 rp_cancel_all(ps) 189 190 gv_plan_finish(plan, c) 191 gv_kv("prefix_string_len" as *u8, l) 192 gv_kv("failing_error_bytes" as *u8, fn) 193 gv_kv("deadline_error_bytes" as *u8, dn) 194 gv_kv("later_response_bytes" as *u8, okn2) 195 gv_kv("notification_silence_bytes" as *u8, sn) 196 return gv_verdict("MCP-STDIO" as *u8, c, "Real children: a failed/deadlined worker becomes a correlated error and the loop keeps serving." as *u8) 197}