nx_mcp_stdio_gate.nx source
↩ module page · 197 lines · 9881 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.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}