code wiki / (root) / nx_tools_api_recovery_candidate_v2_t316.nx

nx_tools_api_recovery_candidate_v2_t316.nx source

↩ module page · 2654 lines · 163600 B

1// nx_tools_api.nx -- Exposes the nx_tool_registry over HTTP with GET and MCP-compatible JSON-RPC endpoints for tool discovery and invocation. 2import "nx_jobclaim_lib.nx" 3// nx_tools_api.nx -- R0 of the sovereign AGENT-FACING API: expose the nx_tool_registry over HTTP. 4// TWO surfaces from ONE registry (the ecosystem's capability SSOT), benchmark = MCP, and BEYOND it: 5// GET /api/tools -> {"tools":[{name,description,invoke,status}]} -- SUPERSET: every tool advertises its 6// gate-`status` (proven/not), a provenance field MCP's tools/list has no concept of. 7// POST /mcp -> JSON-RPC 2.0 (initialize + tools/list) -- MCP-COMPATIBLE so ANY MCP client (Claude 8// included) discovers Nishi's tools with zero adapter. 9// PURE FUNCTION ta_handle_pfx(prefix, req, req_n, out) -> out_n (bytes in, bytes out, NO socket) so the gate 10// drives it in-process (nx_tools_api_gate), exactly like ma_handle. Composes nx_tool_registry (data) + nx_tabrec 11// (tr_field, transitive). No new transport -- wired behind the sovereign edge at deploy. license_tier: ORIGINAL 12import "nx_mcp_envelope.nx" 13import "_hdl_build/nx_json_lib.nx" 14import "nx_tool_registry.nx" // tool registry and transitive table records 15import "nx_cap_token.nx" // capt_verify / CAPT_OK -- capability-scoped invocation (R2: the beyond-MCP security exceed) 16import "nx_tool_exec_allow.nx" // tea_run / TEA_OK -- R2 EXECUTION: real tools/call runs a vetted GREEN organ (+ transitive nx_tool_run) 17import "nx_tool_http_backend.nx" // thb_dispatch / THB_OK -- 2nd execution kind: a registered HTTP backend (search/doc) fetched in-process over loopback 18import "sha256.nx" // sha256_digest -- PKCE S256 code-challenge verification (OAuth 2.1 AS) 19import "nx_toolcall_idem_lib.nx" // ti_classify / ti_record -- params._idem Idempotency-Key on tools/call (DI4, 2026-09-05) 20const TA_MAGIC_65536: i64 = 65536 21// seq1301 (2026-07-30): sync tools/call capture cap. DERIVED: worst-case ta_json_esc expansion is 6x 22// (\uXXXX per control byte), jb response buffer = 1MiB => cap = 160KiB keeps 6x escape at 960KiB < jb. 23// The old 64KiB cap clipped organ stdout SILENTLY (an 80KiB source read came back as exactly 65536 24// bytes, no marker -- nearly shipped a truncated daemon). Truncation is now ALSO flagged in _meta. 25const TA_CAPTURE_CAP: i64 = 163840 26const TA_MAGIC_8192: i64 = 8192 27const TA_MAGIC_1048576: i64 = 1048576 28const TA_MAGIC_9999999999: i64 = 9999999999 29const TA_MAGIC_2048: i64 = 2048 30const TA_MAGIC_4096: i64 = 4096 31const TA_MAGIC_1469598: i64 = 1469598 32const TA_MAGIC_16384: i64 = 16384 33const TA_MAGIC_4294967295: i64 = 4294967295 34const TA_MAGIC_3600: i64 = 3600 35const TA_MAGIC_88161: i64 = 88161 36 37const TA_TOOLPFX: *u8 = "knowledge/toolreg-" as *u8 // production registry prefix (mirrors TOOL_PREFIX) 38const TA_MCP_PAGE: i64 = 64 // MCP tools/list page size (cursor pagination); clients walk nextCursor. 39 // 8 made a ~500-tool roster cost ~63 round-trips per session initialize -- 40 // with any per-request store cost that compounds into minutes (2026-07-30 41 // outage). 64 keeps a page well under the 1MiB response buffer. 42const TA_ALL_LIMIT: i64 = 100000 // /api/tools emits ALL tools (effectively-unbounded page) 43// HMAC secret for capability tokens. WARN: PLACEHOLDER for the gate + first increment -- production MUST source this 44// from the sovereign vault (nx_vault), NEVER a compiled constant (a baked secret is forgeable by anyone reading the binary). 45const TA_CAP_SECRET: *u8 = "nishi-tools-cap-hmac-secret-v1-REPLACE-FROM-VAULT" as *u8 46// PROD secret: raw bytes in this keyfile (relative to the server CWD = the NAS nishihost dir), 0600, provisioned once. 47// Loaded per tools/call; falls back to the constant above ONLY when absent (dev / in-process gates). This retires the 48// baked secret in production while keeping the gates deterministic. 49const TA_CAP_KEYFILE: *u8 = "tools_cap_secret.key" as *u8 50 51// ---- byte helpers (self-contained) ------------------------------------------------------------------ 52func ta_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 53func ta_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } return o + i } 54// async-job file landing (rung 24): O_CREAT|O_TRUNC write, full-drain loop. 55// ---- INVOCATION LEDGER (2026-08-04, debt 1785878018) ------------------------------------------ 56// THE DEFECT THIS CLOSES: nx_catalog / nx_wirecensus grade an organ's INVOKED axis from 57// knowledge/status/actlog.jrnl -- but nothing ever wrote a tools/call there. Only organs that 58// self-log, plus harvest ingests, appeared. MEASURED: nx_agreebound_gate, nx_writebench and 59// nx_writebench_gate were each driven over the LIVE /mcp surface (HTTP 200, full output, 60// _meta.exit_code 0) and all three still graded REGISTERED-DARK(S4) "NEVER RUN", while nx_catalog 61// itself had ZERO rows despite many runs. So the banked headline "768 registered / 274 invoked / 62// 183 DARK" was measuring SELF-LOGGING ADOPTION, and an MCP-only tool could never be cleared -- 63// the guard reported a gap that no amount of using the tools could close. 64// ★★★★★A DETECTOR'S COVERAGE IS TWO SETS: WHAT IT MATCHES AND WHERE IT LOOKS. This one looked in a 65// log its own subject never wrote to. The daemon that dispatches the call is the only place that 66// KNOWS a call happened, so the record belongs here. 67// 68// NEVER-BRICK / never-degrade: append-only, best-effort, fail-silent. A missing dir, a full disk or 69// a refused open must NEVER affect the tool result -- logging observes the call, it does not gate 70// it. Row shape matches the existing journal: epoch \t actor \t tool \t verb \t status \t note. 71// (ta_actlog itself is defined below ta_catb/ta_catn, which it uses.) 72 73func ta_job_put(path: *u8, buf: *u8, n: i64) -> i64 { 74 if n < 0 { return 0 - 1 } 75 let fd: i64 = sys_openat_wr(path, 420) 76 if fd < 0 { return fd } 77 var w: i64 = 0 78 while w < n { 79 let r: i64 = sys_write(fd, ((buf as i64) + w) as *u8, n - w) 80 if r != TR_EINTR { 81 if r <= 0 { 82 sys_close(fd) 83 if r < 0 { return r } 84 return 0 - 1 85 } 86 w = w + r 87 } 88 } 89 let closed: i64 = sys_close(fd) 90 if closed < 0 { return closed } 91 return 0 92} 93// Publish the completion marker only after output and marker writes succeed. 94// This orders visible evidence; it does not establish power-loss durability. 95func ta_job_publish(output_path: *u8, output: *u8, output_n: i64, temp_path: *u8, marker: *u8, marker_n: i64, claim_path: *u8) -> i64 { 96 let output_rc: i64 = ta_job_put(output_path, output, output_n) 97 if output_rc != 0 { return output_rc } 98 let marker_rc: i64 = ta_job_put(temp_path, marker, marker_n) 99 if marker_rc != 0 { return marker_rc } 100 return sys_renameat(temp_path, claim_path) 101} 102 103func ta_catb(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[o + i] = s[i]; i = i + 1 } return o + n } 104func ta_catn(d: *u8, o: i64, v: i64) -> i64 { 105 // Keep magnitude nonpositive: the signed minimum has no positive counterpart. 106 var m: i64 = v 107 var w: i64 = o 108 if m < 0 { d[w] = 45 as u8; w = w+1 } 109 if m > 0 { m = 0-m } 110 let first: i64 = w 111 if m == 0 { d[w] = 48 as u8; return w+1 } 112 while m < 0 { 113 d[w] = (48-(m%10)) as u8 114 w = w+1 115 m = m/10 116 } 117 var left: i64 = first 118 var right: i64 = w-1 119 while left < right { 120 let digit: u8 = d[left] 121 d[left] = d[right] 122 d[right] = digit 123 left = left+1 124 right = right-1 125 } 126 return w 127} 128// LATENCY TELEMETRY (2026-08-07). This row already recorded WHAT ran and WHETHER it worked, but 129// never HOW LONG -- and the 503 class is entirely a latency problem: the edge closes the window at 130// 15s (NX_SD2_BACKEND_TIMEOUT_S) and the sync lane is budgeted at 14s, so a call that outruns it is 131// a 503 whose work may still have LANDED. The async lane fixes that, but its membership was a hand 132// written list of tools somebody noticed failing -- you cannot route by latency you never measured. 133// dur_ms is TOTAL HANDLER TIME from the moment authority is granted, because that is the quantity 134// the edge window actually applies; exec time alone would understate it. 135// APPENDED at the end of the free-text tail, so tab-splitting readers and key= scanners both keep 136// working unchanged (rule 19). 137// ONE row writer. `extra` is a NUL-terminated suffix of ` key=value` fields appended BEFORE the newline -- 138// ADDITIVE: the seven positional fields are byte-identical, so every existing reader (nx_actlog, 139// nx_toolatency, the harvest beat) parses exactly what it parsed before; a reader that wants the new 140// fields greps them by name. Added 2026-08-23 for the sync-promoted row (job=, reply_ms=; debt 1787505278). 141const TA_LOG_I64_WIDTH: i64 = 20 142const TA_LOG_I64_MAX: i64 = 9223372036854775807 143func ta_log_size_add(a: i64, b: i64) -> i64 { 144 if a < 0 { return 0-1 } 145 if b < 0 { return 0-1 } 146 if a > TA_LOG_I64_MAX-b { return 0-1 } 147 return a+b 148} 149// One append implementation for legacy calls and additive observations; no second store. 150func ta_journal_write_fd(fd: i64, row: *u8, n: i64) -> i64 { 151 if fd < 0 { return 0-1 } 152 if n < 0 { return 0-1 } 153 if (row as i64) <= 0 { return 0-1 } 154 var done: i64 = 0 155 while done < n { 156 let wrote: i64 = sys_write(fd, ((row as i64)+done) as *u8, n-done) 157 if wrote <= 0 { if wrote < 0 { return wrote }; return 0-1 } 158 done = done+wrote 159 } 160 return 0 161} 162func ta_journal_append(path: *u8, row: *u8, n: i64) -> i64 { 163 if n < 0 { return 0-1 } 164 if (row as i64) <= 0 { return 0-1 } 165 var fd: i64 = 0-1 166 if (path as i64) > 0 { fd = sys_openat_append(path, 420) } else { 167 fd = sys_openat_append("knowledge/status/actlog.jrnl" as *u8, 420) 168 if fd < 0 { fd = sys_openat_append("/volume1/homes/elderwesto/nishihost/knowledge/status/actlog.jrnl" as *u8, 420) } 169 } 170 if fd < 0 { return fd } 171 let written: i64 = ta_journal_write_fd(fd, row, n) 172 let closed: i64 = sys_close(fd) 173 if written < 0 { return written } 174 return closed 175} 176func ta_actlog_x_to(path: *u8, tool: *u8, tlen: i64, lane: *u8, status: *u8, ec: i64, bytes: i64, dur_ms: i64, extra: *u8) -> i64 { 177 if tlen < 0 { return 0-1 } 178 if (tool as i64) <= 0 { return 0-1 } 179 if (lane as i64) <= 0 { return 0-1 } 180 if (status as i64) <= 0 { return 0-1 } 181 if (extra as i64) <= 0 { return 0-1 } 182 let literals: *u8 = "\tmcp\t\tcall\t\ttools/call lane= exit= bytes= dur_ms=\n" as *u8 183 var cap: i64 = ta_log_size_add(4*TA_LOG_I64_WIDTH+1, ta_slen(literals)) 184 cap = ta_log_size_add(cap, ta_slen(lane)) 185 cap = ta_log_size_add(cap, ta_slen(status)) 186 cap = ta_log_size_add(cap, ta_slen(extra)) 187 cap = ta_log_size_add(cap, tlen) 188 if cap < 0 { return 0-1 } 189 let ln: *u8 = sys_mmap(cap) 190 if (ln as i64) <= 0 { return 0-1 } 191 var o: i64 = ta_catn(ln, 0, sys_now_realtime_sec()) 192 o = ta_cat(ln, o, "\tmcp\t" as *u8) 193 o = ta_catb(ln, o, tool, tlen) 194 o = ta_cat(ln, o, "\tcall\t" as *u8) 195 o = ta_cat(ln, o, status) 196 o = ta_cat(ln, o, "\ttools/call lane=" as *u8) 197 o = ta_cat(ln, o, lane) 198 o = ta_cat(ln, o, " exit=" as *u8) 199 o = ta_catn(ln, o, ec) 200 o = ta_cat(ln, o, " bytes=" as *u8) 201 o = ta_catn(ln, o, bytes) 202 o = ta_cat(ln, o, " dur_ms=" as *u8) 203 o = ta_catn(ln, o, dur_ms) 204 o = ta_cat(ln, o, extra) 205 o = ta_cat(ln, o, "\n" as *u8) 206 let result: i64 = ta_journal_append(path, ln, o) 207 sys_munmap(ln, cap) 208 return result 209} 210func ta_actlog_x(tool: *u8, tlen: i64, lane: *u8, status: *u8, ec: i64, bytes: i64, dur_ms: i64, extra: *u8) -> i64 { 211 return ta_actlog_x_to(0 as *u8, tool, tlen, lane, status, ec, bytes, dur_ms, extra) 212} 213func ta_actlog(tool: *u8, tlen: i64, lane: *u8, status: *u8, ec: i64, bytes: i64, dur_ms: i64) -> i64 { 214 return ta_actlog_x(tool, tlen, lane, status, ec, bytes, dur_ms, "" as *u8) 215} 216 217func ta_put1(d: *u8, o: i64, a: i64) -> i64 { d[o] = a as u8; return o + 1 } 218func ta_put2(d: *u8, o: i64, a: i64, b: i64) -> i64 { d[o] = a as u8; d[o + 1] = b as u8; return o + 2 } 219func ta_hexd(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } // 0-9 then a-f 220 221// ---- cap-signing secret: ONE resolver (DRY across tools/call + cap issue/revoke + the status probe) ---------- 222// Resolve the HMAC signing secret EXACTLY as the server does everywhere: prefer the vault-provisioned keyfile 223// (production), else fall back to the dev/gate placeholder constant. `keyfile` is explicit so a gate can drive it 224// against a temp path without touching the server CWD; ta_load_cap_secret pins the production TA_CAP_KEYFILE. 225func ta_load_cap_secret_from(keyfile: *u8, outlen: *i64) -> *u8 { 226 let ksz: *i64 = sys_mmap(16) as *i64 227 let kf: *u8 = sys_read_file(keyfile, ksz) 228 if (kf as i64) != 0 { if ksz[0] > 0 { outlen[0] = ksz[0]; return kf } } 229 let pn: i64 = ta_slen(TA_CAP_SECRET) // hoist the call OUT of the array store (nx_cc: call-in-array-store -> corrupt/SEGV) 230 outlen[0] = pn 231 return TA_CAP_SECRET 232} 233// ---- DEGRADED-MODE ANNOUNCE (debt 1787255203, 2026-08-20) --------------------------------------- 234// THE FALLBACK ABOVE IS NOT THE BUG AND MUST NOT BE REMOVED. It is deliberate, declared and 235// load-bearing: five consumers sign with the baked constant, and nx_tools_api_exec_gate REQUIRES the 236// unprovisioned state BY ITS OWN DESIGN -- it asserts insecure_placeholder and REFUSES exit 3 if a real 237// keyfile is present, a never-brick guard added after its fixture replaced the production allowlist for 238// 2h20m. Deleting the fallback would break the capability gate suite and re-arm that incident. 239// THE BUG IS THAT ENTERING THE DEGRADED MODE WAS SILENT AT RUNTIME: no log, no warn, no marker, on the 240// per-tools/call hot path in PRODUCTION, with GET /api/cap/status -- a poll somebody has to remember to 241// make -- as the only witness. The asymmetry is what makes that urgent rather than merely untidy: 242// fail-CLOSED announces itself the instant it happens; fail-OPEN is silent BY CONSTRUCTION, so its only 243// signal is the compensating control -- and on 2026-08-20 that control (the */5 cap-plane probe) was 244// itself down and had been for hours. 245// **** MAKE THE MODE AN EVENT, NOT A STATE SOMEBODY HAS TO GO AND ASK ABOUT. **** 246// 247// WHY A LATCH AND NOT A LINE PER CALL: this sits on the per-tools/call path. A line per verification 248// would flood the journal under load and -- worse -- would make the signal something every reader learns 249// to filter, which is the same silence with extra bytes. The latch makes the steady-state cost ONE 250// INTEGER COMPARE, and emits only on the FIRST resolution in this process and on every genuine 251// TRANSITION in EITHER direction. A recovery is as much an event as a degradation, and a detector that 252// only ever reports the bad half cannot tell you the fault cleared. 253// 254// WHY ITS OWN JOURNAL AND NOT actlog.jrnl: actlog is the INVOCATION LEDGER that nx_catalog grades the 255// INVOKED axis from. Writing config-health rows into it would silently move a measurement plane that 256// other organs publish numbers off. ONE SUBJECT PER ROW, AND THE WRITER OWNS ITS OWN FILE. 257// 258// FAIL-SILENT, ALWAYS, same contract as ta_actlog: a refused open, a full disk or a missing directory 259// must NEVER affect a verification. Announcing OBSERVES the mode, it does not gate it. stderr is written 260// FIRST so the daemon log carries the event even when the journal cannot be opened at all. 261static ta_capmode: *i64 262const TA_CAPMODE_SLOTS: i64 = 16 263const TA_CAPMODE_UNSEEN: i64 = 0 264const TA_CAPMODE_PROV: i64 = 1 265const TA_CAPMODE_PLACE: i64 = 2 266const TA_CAPMODE_MODE: i64 = 420 267const TA_CAPMODE_LINE: i64 = 512 268const TA_CAPMODE_JRNL: *u8 = "knowledge/status/capmode.jrnl" as *u8 269 270func ta_capmode_announce(mode: i64) -> i64 { 271 if (ta_capmode as i64) == 0 { 272 ta_capmode = sys_mmap(TA_CAPMODE_SLOTS) as *i64 273 ta_capmode[0] = TA_CAPMODE_UNSEEN 274 } 275 if ta_capmode[0] == mode { return 0 } 276 let prev: i64 = ta_capmode[0] 277 ta_capmode[0] = mode 278 let ln: *u8 = sys_mmap(TA_CAPMODE_LINE) 279 var o: i64 = ta_catn(ln, 0, sys_now_realtime_sec()) 280 o = ta_cat(ln, o, "\tnx_tools_api\tcap-secret\t" as *u8) 281 if mode == TA_CAPMODE_PLACE { 282 o = ta_cat(ln, o, "DEGRADED\tcapability tokens are being verified with the BAKED PLACEHOLDER constant, which is forgeable by anyone who can read the binary -- provision " as *u8) 283 o = ta_cat(ln, o, TA_CAP_KEYFILE) 284 o = ta_cat(ln, o, " (0600, server CWD) to retire it" as *u8) 285 } else { 286 o = ta_cat(ln, o, "PROVISIONED\tcapability tokens are being verified with the keyfile-sourced secret" as *u8) 287 } 288 o = ta_cat(ln, o, " prev=" as *u8) 289 o = ta_catn(ln, o, prev) 290 o = ta_cat(ln, o, "\n" as *u8) 291 sys_write(2, ln, o) 292 let fd: i64 = sys_openat_append(TA_CAPMODE_JRNL, TA_CAPMODE_MODE) 293 if fd >= 0 { 294 var w: i64 = 0 295 while w < o { 296 let r: i64 = sys_write(fd, ((ln as i64) + w) as *u8, o - w) 297 if r <= 0 { w = o } else { w = w + r } 298 } 299 sys_close(fd) 300 } 301 sys_munmap(ln, TA_CAPMODE_LINE) 302 return 1 303} 304 305// THE PRODUCTION ENTRY POINT, AND THE ONLY ONE THAT ANNOUNCES. ta_load_cap_secret_from stays a PURE 306// resolver so a gate can drive a temp keyfile, and so GET /api/cap/status (which reaches it through 307// ta_cap_provisioned_from) can PROBE the mode without flipping the latch. That separation is what stops 308// the */5 liveness probe from announcing about itself once every five minutes, forever. 309func ta_load_cap_secret(outlen: *i64) -> *u8 { 310 let s: *u8 = ta_load_cap_secret_from(TA_CAP_KEYFILE, outlen) 311 // hoist the const pointer to a local before comparing (nx_cc: touching a CONST directly is the 312 // documented footgun ta_cap_provisioned_from already works around three lines below). 313 let plc: *u8 = TA_CAP_SECRET 314 if (s as i64) == (plc as i64) { ta_capmode_announce(TA_CAPMODE_PLACE) } else { ta_capmode_announce(TA_CAPMODE_PROV) } 315 return s 316} 317 318 319// 1 if a REAL secret is loaded (a non-empty keyfile whose bytes are NOT the forgeable baked placeholder), else 0. 320// Reveals only a boolean config-health fact -- NEVER the secret. Also catches the footgun of a keyfile that literally 321// contains the placeholder string. 322func ta_cap_provisioned_from(keyfile: *u8) -> i64 { 323 let sl: *i64 = sys_mmap(16) as *i64 324 let s: *u8 = ta_load_cap_secret_from(keyfile, sl) 325 let plc: *u8 = TA_CAP_SECRET // hoist the const ptr to a local before indexing (nx_cc: CONST[i] indexing is broken) 326 let pn: i64 = ta_slen(plc) 327 if sl[0] != pn { return 1 } 328 var i: i64 = 0 329 while i < pn { if s[i] != plc[i] { return 1 } i = i + 1 } 330 return 0 331} 332func ta_cap_provisioned() -> i64 { return ta_cap_provisioned_from(TA_CAP_KEYFILE) } 333 334// a[0..an) equals NUL-terminated b ? (exact) -- used for method/path slice compares. 335func ta_streq_n(a: *u8, an: i64, b: *u8) -> i64 { 336 var i: i64 = 0 337 while i < an { if b[i] == (0 as u8) { return 0 } if a[i] != b[i] { return 0 } i = i + 1 } 338 if b[i] != (0 as u8) { return 0 } 339 return 1 340} 341 342// append s[0..n) into d at o as a JSON-safe string body (RFC 8259: escape " \ and control chars). Returns new o. 343// THE defensive boundary: a description carrying a raw '"' would corrupt the JSON otherwise (silent corruption). 344func ta_json_esc(d: *u8, o: i64, s: *u8, n: i64) -> i64 { 345 var i: i64 = 0 346 while i < n { 347 let c: i64 = (s[i] as i64) & 0xff 348 if c == 34 { o = ta_put2(d, o, 92, 34) } 349 else { if c == 92 { o = ta_put2(d, o, 92, 92) } 350 else { if c == 10 { o = ta_put2(d, o, 92, 110) } 351 else { if c == 13 { o = ta_put2(d, o, 92, 114) } 352 else { if c == 9 { o = ta_put2(d, o, 92, 116) } 353 else { if c < 32 { 354 o = ta_put2(d, o, 92, 117); o = ta_put2(d, o, 48, 48) 355 o = ta_put1(d, o, ta_hexd((c >> 4) & 0xf)); o = ta_put1(d, o, ta_hexd(c & 0xf)) 356 } else { o = ta_put1(d, o, c) } } } } } } 357 i = i + 1 358 } 359 return o 360} 361 362// first offset of NUL-term `needle` in hay[0..hn), or -1. 363func ta_indexof(hay: *u8, hn: i64, needle: *u8) -> i64 { 364 let nl: i64 = ta_slen(needle) 365 if nl == 0 { return 0 } 366 var i: i64 = 0 367 while i + nl <= hn { 368 var m: i64 = 1; var j: i64 = 0 369 while j < nl { if hay[i + j] != needle[j] { m = 0 } j = j + 1 } 370 if m == 1 { return i } 371 i = i + 1 372 } 373 return 0 - 1 374} 375 376// ---- HTTP request shredding ------------------------------------------------------------------------- 377// body offset = just past the CRLFCRLF header terminator (req_n if none -> empty body). 378func ta_body_off(req: *u8, req_n: i64) -> i64 { 379 var i: i64 = 0 380 while i + 3 < req_n { 381 if req[i] == (13 as u8) { if req[i + 1] == (10 as u8) { if req[i + 2] == (13 as u8) { if req[i + 3] == (10 as u8) { return i + 4 } } } } 382 i = i + 1 383 } 384 return req_n 385} 386// parse request line "METHOD SP PATH SP VERSION": mo[0/1]=method off/len, po[0/1]=path off/len. 1 ok / 0 malformed. 387func ta_reqline(req: *u8, req_n: i64, mo: *i64, po: *i64) -> i64 { 388 var i: i64 = 0 389 var sp1: i64 = 0 - 1 390 while i < req_n { if sp1 < 0 { if req[i] == (32 as u8) { sp1 = i } } i = i + 1 } 391 if sp1 < 0 { return 0 } 392 mo[0] = 0; mo[1] = sp1 393 let k: i64 = sp1 + 1 394 var sp2: i64 = 0 - 1 395 i = k 396 while i < req_n { if sp2 < 0 { if req[i] == (32 as u8) { sp2 = i } } i = i + 1 } 397 po[0] = k 398 if sp2 < 0 { po[1] = req_n - k } else { po[1] = sp2 - k } 399 return 1 400} 401 402// ---- JSON body field pluck (single-responsibility, fail-closed) ------------------------------------- 403// string VALUE content of "<keyq>":"content" -> out2[0]=off,out2[1]=len; 1 found / 0 absent. keyq includes quotes. 404func ta_json_str(body: *u8, n: i64, keyq: *u8, out2: *i64) -> i64 { 405 let p: i64 = ta_indexof(body, n, keyq) 406 if p < 0 { return 0 } 407 var i: i64 = p + ta_slen(keyq) 408 var q1: i64 = 0 - 1 409 while i < n { if q1 < 0 { if body[i] == (34 as u8) { q1 = i } } i = i + 1 } 410 if q1 < 0 { return 0 } 411 var q2: i64 = 0 - 1 412 i = q1 + 1 413 while i < n { if q2 < 0 { if body[i] == (34 as u8) { q2 = i } } i = i + 1 } 414 if q2 < 0 { return 0 } 415 out2[0] = q1 + 1; out2[1] = q2 - (q1 + 1) 416 return 1 417} 418// RAW value token after "<keyq>": (number OR quoted string, echoed verbatim for JSON-RPC id). 1 found / 0 absent. 419func ta_json_raw(body: *u8, n: i64, keyq: *u8, out2: *i64) -> i64 { 420 let p: i64 = ta_indexof(body, n, keyq) 421 if p < 0 { return 0 } 422 var i: i64 = p + ta_slen(keyq) 423 var col: i64 = 0 - 1 424 while i < n { if col < 0 { if body[i] == (58 as u8) { col = i } } i = i + 1 } 425 if col < 0 { return 0 } 426 var s: i64 = col + 1 427 var go: i64 = 1 428 while go == 1 { if s >= n { go = 0 } else { let c: i64 = body[s] as i64; if c == 32 { s = s + 1 } else { if c == 9 { s = s + 1 } else { go = 0 } } } } 429 var e: i64 = s 430 var go2: i64 = 1 431 while go2 == 1 { 432 if e >= n { go2 = 0 } else { 433 let c: i64 = body[e] as i64 434 if c == 44 { go2 = 0 } else { if c == 125 { go2 = 0 } else { if c == 32 { go2 = 0 } else { if c == 13 { go2 = 0 } else { if c == 10 { go2 = 0 } else { e = e + 1 } } } } } 435 } 436 } 437 out2[0] = s; out2[1] = e - s 438 return 1 439} 440// string-ARRAY value: parse "<keyq>":[ "s0","s1",... ] -> write each element (JSON-unescaped, NUL-terminated) 441// into the bump buffer `scratch` (capacity scap) and store its pointer (as i64) in out[base+k]. Returns the count 442// k (>=0); 0 for an absent or empty array; -1 refuses malformed or over-capacity input. 443// JSON escapes and raw UTF-8 preserve native argv bytes, excluding embedded NUL. 444// Existing field lookup retained; a present argv must be a complete string-only array. 445// Returns -1 on malformed Unicode/JSON, NUL, argument count or scratch overflow. 446// The caller must refuse execution on failure; no partial argv is a successful result. 447func ta_json_str_array(body: *u8, n: i64, keyq: *u8, out: *i64, base: i64, maxn: i64, scratch: *u8, scap: i64) -> i64 { 448 if base < 0 { return 0 - 1 }; if maxn < 0 { return 0 - 1 }; if scap <= 0 { return 0 - 1 } 449 let p: i64 = ta_indexof(body, n, keyq) 450 if p < 0 { return 0 } 451 var i: i64 = jx_skip_ws(body, n, p + ta_slen(keyq)) 452 if i >= n { return 0 - 1 }; if body[i] != (58 as u8) { return 0 - 1 } 453 i = jx_skip_ws(body, n, i + 1) 454 if i >= n { return 0 - 1 }; if body[i] != (91 as u8) { return 0 - 1 } 455 i = jx_skip_ws(body, n, i + 1) 456 if i >= n { return 0 - 1 } 457 if body[i] == (93 as u8) { return 0 } 458 var k: i64 = 0; var w: i64 = 0 459 while i < n { 460 if body[i] != (34 as u8) { return 0 - 1 } 461 if k >= maxn { return 0 - 1 } 462 if w >= scap { return 0 - 1 } 463 let dst: *u8 = ((scratch as i64) + w) as *u8 464 let end: i64 = jx_copy_str_argv(body, n, i + 1, dst, scap - w) 465 if end < 0 { return 0 - 1 } 466 let used: i64 = jx_len(dst) + 1 467 out[base + k] = dst as i64 468 w = w + used; k = k + 1 469 i = jx_skip_ws(body, n, end) 470 if i >= n { return 0 - 1 } 471 if body[i] == (93 as u8) { return k } 472 if body[i] != (44 as u8) { return 0 - 1 } 473 i = jx_skip_ws(body, n, i + 1) 474 // A closing bracket here is a trailing comma, so the next iteration refuses it. 475 } 476 return 0 - 1 477} 478 479// ---- response assembly (all JSON) ------------------------------------------------------------------- 480func ta_emit(out: *u8, status: *u8, body: *u8, body_n: i64) -> i64 { 481 var o: i64 = ta_cat(out, 0, status) 482 o = ta_cat(out, o, "\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8) 483 o = ta_catn(out, o, body_n) 484 o = ta_cat(out, o, "\r\n\r\n" as *u8) 485 o = ta_catb(out, o, body, body_n) 486 return o 487} 488func ta_emit_lit(out: *u8, status: *u8, lit: *u8) -> i64 { return ta_emit(out, status, lit, ta_slen(lit)) } 489 490// ---- EC50 (2026-09-17): a row registered mid-session reaches the seat by the PROTOCOL, not by a restart --------- 491// The registry plane commits every registration as a new segment and rewrites its manifest (temp + rename), so the 492// manifest's bytes ARE the list-changed generation. tools/list records the generation it served; any later request 493// from a client that accepts text/event-stream, on a moved generation, is answered as the streamable transport's 494// SSE-shaped reply carrying notifications/tools/list_changed BEFORE the result, so a compliant client re-lists and the 495// row it registered a minute ago is callable by name. A client that does not accept SSE (every sovereign JSON client) 496// gets the plain JSON reply it has always parsed. One marker per daemon (v1): a second client sitting on an older 497// generation is nudged on its next call after any further change; it is never stranded for a whole session. 498const TA_MANIFEST_READ: i64 = 4096 // the seg store caps a manifest at 256 segment names; a read that FILLS this is unknown, not silent 499const TA_GEN_HASH_MUL: i64 = 131 // a rolling hash over the manifest bytes: any change in the segment list moves it 500const TA_GEN_HASH_MOD: i64 = 1000000007 501static g_ta_gen_listed: i64 502func ta_reg_gen(prefix: *u8) -> i64 { 503 let pb: *u8 = sys_mmap(TA_MAGIC_2048) 504 var o: i64 = ta_cat(pb, 0, prefix) 505 o = ta_cat(pb, o, "manifest.txt" as *u8) 506 let fd: i64 = sys_openat_rd(pb) 507 if fd < 0 { return 0 - 1 } 508 let mb: *u8 = sys_mmap(TA_MANIFEST_READ) 509 let n: i64 = sys_read(fd, mb, TA_MANIFEST_READ) 510 sys_close(fd) 511 if n <= 0 { return 0 - 1 } 512 if n >= TA_MANIFEST_READ { return 0 - 2 } // beyond the named bound: unknown, and unknown always notifies 513 var h: i64 = n 514 var i: i64 = 0 515 while i < n { h = (h * TA_GEN_HASH_MUL + (mb[i] as i64)) % TA_GEN_HASH_MOD; i = i + 1 } 516 return h + 1 517} 518// does the request accept an event stream? Stock clients send the header name in either case. 519func ta_accepts_sse(req: *u8, req_n: i64) -> i64 { 520 let hv: *i64 = sys_mmap(16) as *i64 521 var found: i64 = ta_header(req, req_n, "Accept:" as *u8, hv) 522 if found == 0 { found = ta_header(req, req_n, "accept:" as *u8, hv) } 523 if found == 0 { return 0 } 524 let vb: *u8 = sys_mmap(TA_MAGIC_2048) 525 var n: i64 = hv[1] 526 if n > TA_MAGIC_2048 - 1 { n = TA_MAGIC_2048 - 1 } 527 var i: i64 = 0 528 while i < n { vb[i] = req[hv[0] + i]; i = i + 1 } 529 vb[n] = 0 as u8 530 if ta_indexof(vb, n, "text/event-stream" as *u8) >= 0 { return 1 } 531 return 0 532} 533// the SSE-shaped reply of the streamable transport: the notification event, then the JSON-RPC result event 534func ta_emit_sse_listchanged(out: *u8, body: *u8, body_n: i64) -> i64 { 535 let sb: *u8 = sys_mmap(body_n + TA_MAGIC_2048) 536 var s: i64 = ta_cat(sb, 0, "event: message\ndata: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/tools/list_changed\"}\n\nevent: message\ndata: " as *u8) 537 s = ta_catb(sb, s, body, body_n) 538 s = ta_cat(sb, s, "\n\n" as *u8) 539 var o: i64 = ta_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nCache-Control: no-cache\r\nConnection: close\r\nContent-Length: " as *u8) 540 o = ta_catn(out, o, s) 541 o = ta_cat(out, o, "\r\n\r\n" as *u8) 542 o = ta_catb(out, o, sb, s) 543 return o 544} 545 546// ---- R1 rich MCP tool schemas (data-driven, benchmark = MCP 2025-06-18 tools spec) -------------------- 547// Per-tool title + annotations (readOnly/destructive/idempotent/openWorld safety hints) + outputSchema + the 548// TYPED argv CONTRACT, sourced from a TAB-separated config so vetting a tool's profile is a data edit (rule 11), 549// not a code change. 550// Row: <name>\t<title>\t<ro>\t<destr>\t<idem>\t<openworld>\t<output-desc>[\t<argv-contract>] 551// col8 argv-contract (07-17, the tool-schema SOTA gap): the tool's exact argv grammar (verbs/args), emitted as 552// the inputSchema argv description so an agent reads the CONTRACT from tools/list instead of a generic blurb. 553// Absent col8 -> the generic argv description (back-compat). Conf is read per call = contract edits are LIVE. 554// UNKNOWN tool (no row) -> MCP-safe defaults: NOT read-only, POSSIBLY destructive, NOT idempotent, open-world 555// (so an agent treats an un-annotated tool as the cautious case). '#' comment + blank lines ignored. 556const TA_SCHEMACONF: *u8 = "knowledge/tool_schemas.conf" as *u8 // server CWD-relative (like tool_allowlist.conf) 557// DERIVED argv contracts (nx_toolgrammar), the col8 FALLBACK. Separate file on purpose: tool_schemas.conf 558// is a SHARED, hand-authored registry and a derived value must never overwrite a human's. Precedence is 559// authored col8 > derived grammar > generic placeholder. Read per request, so a re-harvest is LIVE. 560const TA_GRAMMARCONF: *u8 = "knowledge/tool_grammar.conf" as *u8 561// DERIVED MCP safety annotations (nx_toolflags). Applied ONLY to rows whose output-desc marks them 562// registration-synthesized -- i.e. whose quad was the hardcoded constant ma_schema_append_row writes and 563// never a measurement. MEASURED: 694 of 869 tools (79.9%) published that constant as if it were a fact, 564// so a read-only dashboard shipped as destructiveHint=true and agents refused safe reads. 565const TA_FLAGSCONF: *u8 = "knowledge/tool_flags.conf" as *u8 566// Atomic job-id reservation: the same O_CREAT|O_EXCL claim dl_lock (nx_dispatch_lease) uses. Values 567// PROVEN on this host by nx_exclprobe (first=fd3, second=-17 EEXIST) before being shipped here. 568const TA_SYS_OPENAT: i64 = 257 569const TA_AT_FDCWD: i64 = 0 - 100 570const TA_OEXCL: i64 = 193 // O_CREAT(0x40)|O_EXCL(0x80)|O_WRONLY(0x1) 571const TA_CLAIMMODE: i64 = 420 // 0644 572 573func ta_bool_json(v: i64) -> *u8 { if v == 1 { return "true" as *u8 } return "false" as *u8 } 574 575// idx-th TAB-field of line buf[ls,le) -> out2[0]=off,out2[1]=len; 1 if present, 0 if fewer fields. 576func ta_tsv_field(buf: *u8, ls: i64, le: i64, idx: i64, out2: *i64) -> i64 { 577 var fi: i64 = 0 578 var s: i64 = ls 579 var i: i64 = ls 580 while i <= le { 581 var sep: i64 = 0 582 if i == le { sep = 1 } else { if buf[i] == (9 as u8) { sep = 1 } } 583 if sep == 1 { 584 if fi == idx { out2[0] = s; out2[1] = i - s; return 1 } 585 fi = fi + 1 586 s = i + 1 587 } 588 i = i + 1 589 } 590 return 0 591} 592 593// emit ,"title":.. ,"annotations":{..} ,"outputSchema":{..} for `name` from `conf`. Testable (_from); the 594// production wrapper pins TA_SCHEMACONF. Always emits annotations (defaults if no row) so every MCP tool carries 595// safety hints. Appends to body at offset bin; returns the new offset. 596func ta_emit_mcp_schema_from(conf: *u8, name: *u8, nlen: i64, body: *u8, bin: i64) -> i64 { 597 let szp0: *i64 = sys_mmap(16) as *i64 598 let buf0: *u8 = sys_read_file(conf, szp0) 599 var n0: i64 = 0 600 if (buf0 as i64) != 0 { n0 = szp0[0] } 601 return ta_emit_mcp_schema_buf(buf0, n0, name, nlen, body, bin) 602} 603// Buffer-based core: the schema conf is read ONCE PER REQUEST by the caller, not once per TOOL. 604// At page size 64 a per-tool re-read meant 64 x ~125KB file reads for a single tools/list -- the 605// dominant per-page cost once the toolreg plane was compacted (2026-07-30). 606// DERIVED-grammar lookup: <name>\t<grammar> rows emitted by nx_toolgrammar from each organ's OWN source 607// header. Consulted ONLY when tool_schemas.conf declares no col8, so an AUTHORED contract always wins and 608// a derived one can never overwrite a human's. MEASURED 2026-08-06: 749 of 869 tools (86.2%) had no col8 609// at all and served the generic "positional CLI args" placeholder; the harvest supplies 378 real ones. 610func ta_grammar_lookup(gbuf: *u8, gbn: i64, name: *u8, nlen: i64, out2: *i64) -> i64 { 611 if (gbuf as i64) == 0 { return 0 } 612 var ls: i64 = 0 613 var i: i64 = 0 614 while i <= gbn { 615 var eol: i64 = 0 616 if i == gbn { eol = 1 } else { if gbuf[i] == (10 as u8) { eol = 1 } } 617 if eol == 1 { 618 let le: i64 = i 619 if le > ls { if gbuf[ls] != (35 as u8) { 620 let f0: *i64 = sys_mmap(16) as *i64 621 if ta_tsv_field(gbuf, ls, le, 0, f0) == 1 { 622 if f0[1] == nlen { 623 var m: i64 = 1 624 var c: i64 = 0 625 while c < nlen { if gbuf[f0[0] + c] != name[c] { m = 0 } c = c + 1 } 626 if m == 1 { 627 let ff: *i64 = sys_mmap(16) as *i64 628 if ta_tsv_field(gbuf, ls, le, 1, ff) == 1 { if ff[1] > 0 { out2[0] = ff[0]; out2[1] = ff[1]; return 1 } } 629 return 0 630 } 631 } 632 } 633 } } 634 ls = i + 1 635 } 636 i = i + 1 637 } 638 return 0 639} 640// Does the schema row's output-desc carry the registration-synthesized marker? If so its safety quad is a 641// CONSTANT, not a measurement, and a derived quad may replace it. Any other row is human- or backfill- 642// authored and is left ALONE -- a derived value must never overwrite an authored one. 643func ta_od_has(buf: *u8, od_s: i64, od_l: i64, needle: *u8) -> i64 { 644 let m: i64 = ta_slen(needle) 645 if od_l < m { return 0 } 646 var i: i64 = 0 647 while i + m <= od_l { 648 var j: i64 = 0 649 var hit: i64 = 1 650 while j < m { if buf[od_s + i + j] != needle[j] { hit = 0; j = m } else { j = j + 1 } } 651 if hit == 1 { return 1 } 652 i = i + 1 653 } 654 return 0 655} 656// TWO shapes carry a FABRICATED quad, and both must be overridable: 657// (a) "(registered via /api/tools/register)" -- ma_schema_append_row's hardcoded 0/1/0/1. 658// (b) "auto-derived from source header by nx_schema_backfill" -- that organ's OLD path, whose own 659// header records the evidence: "of the 389 rows this organ has auto-derived, 389 carry the 660// identical quad 0 1 0 1 -- zero exceptions", i.e. it was never looked up either. 661// The organ's NEW path stamps "safety flags STATIC-DERIVED from the source's own syscalls"; that IS a 662// measurement and is left ALONE. Distinguishing (b) from the new path by its own marker is the only 663// honest way to tell a constant from a value that merely looks like one. 664func ta_od_is_regstub(buf: *u8, od_s: i64, od_l: i64) -> i64 { 665 if ta_od_has(buf, od_s, od_l, "safety flags STATIC-DERIVED" as *u8) == 1 { return 0 } 666 if ta_od_has(buf, od_s, od_l, "(registered via /api/tools/register)" as *u8) == 1 { return 1 } 667 if ta_od_has(buf, od_s, od_l, "auto-derived from source header by nx_schema_backfill" as *u8) == 1 { return 1 } 668 return 0 669} 670// <name>\t<ro>\t<destr>\t<idem>\t<open> -> out4[0..3]. 1 found / 0 absent. 671func ta_flags_lookup(fbuf: *u8, fbn: i64, name: *u8, nlen: i64, out4: *i64) -> i64 { 672 if (fbuf as i64) == 0 { return 0 } 673 var ls: i64 = 0 674 var i: i64 = 0 675 while i <= fbn { 676 var eol: i64 = 0 677 if i == fbn { eol = 1 } else { if fbuf[i] == (10 as u8) { eol = 1 } } 678 if eol == 1 { 679 let le: i64 = i 680 if le > ls { if fbuf[ls] != (35 as u8) { 681 let f0: *i64 = sys_mmap(16) as *i64 682 if ta_tsv_field(fbuf, ls, le, 0, f0) == 1 { 683 if f0[1] == nlen { 684 var m: i64 = 1 685 var c: i64 = 0 686 while c < nlen { if fbuf[f0[0] + c] != name[c] { m = 0 } c = c + 1 } 687 if m == 1 { 688 var k: i64 = 0 689 var okall: i64 = 1 690 while k < 4 { 691 if ta_tsv_field(fbuf, ls, le, k + 1, f0) == 1 { 692 if fbuf[f0[0]] == (49 as u8) { out4[k] = 1 } else { out4[k] = 0 } 693 } else { okall = 0 } 694 k = k + 1 695 } 696 return okall 697 } 698 } 699 } 700 } } 701 ls = i + 1 702 } 703 i = i + 1 704 } 705 return 0 706} 707func ta_emit_mcp_schema_buf2(buf: *u8, bufn: i64, gbuf: *u8, gbn: i64, fbuf: *u8, fbn: i64, name: *u8, nlen: i64, body: *u8, bin: i64) -> i64 { 708 var b: i64 = bin 709 var ro: i64 = 0; var de: i64 = 1; var idp: i64 = 0; var ow: i64 = 1 // MCP-safe defaults for an unknown tool 710 var has_title: i64 = 0; var tt_s: i64 = 0; var tt_l: i64 = 0 711 var has_out: i64 = 0; var od_s: i64 = 0; var od_l: i64 = 0 712 var has_av: i64 = 0; var av_s: i64 = 0; var av_l: i64 = 0 // col8: typed argv contract 713 if (buf as i64) != 0 { 714 let n: i64 = bufn 715 var ls: i64 = 0; var i: i64 = 0; var done: i64 = 0 716 while i <= n { 717 var eol: i64 = 0 718 if i == n { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } } 719 if eol == 1 { 720 if done == 0 { 721 let le: i64 = i 722 if le > ls { if buf[ls] != (35 as u8) { 723 let f0: *i64 = sys_mmap(16) as *i64 724 if ta_tsv_field(buf, ls, le, 0, f0) == 1 { 725 if f0[1] == nlen { 726 var m: i64 = 1; var c: i64 = 0 727 while c < nlen { if buf[f0[0] + c] != name[c] { m = 0 } c = c + 1 } 728 if m == 1 { 729 let ff: *i64 = sys_mmap(16) as *i64 730 if ta_tsv_field(buf, ls, le, 1, ff) == 1 { has_title = 1; tt_s = ff[0]; tt_l = ff[1] } 731 if ta_tsv_field(buf, ls, le, 2, ff) == 1 { if buf[ff[0]] == (49 as u8) { ro = 1 } else { ro = 0 } } 732 if ta_tsv_field(buf, ls, le, 3, ff) == 1 { if buf[ff[0]] == (49 as u8) { de = 1 } else { de = 0 } } 733 if ta_tsv_field(buf, ls, le, 4, ff) == 1 { if buf[ff[0]] == (49 as u8) { idp = 1 } else { idp = 0 } } 734 if ta_tsv_field(buf, ls, le, 5, ff) == 1 { if buf[ff[0]] == (49 as u8) { ow = 1 } else { ow = 0 } } 735 if ta_tsv_field(buf, ls, le, 6, ff) == 1 { has_out = 1; od_s = ff[0]; od_l = ff[1] } 736 if ta_tsv_field(buf, ls, le, 7, ff) == 1 { if ff[1] > 0 { has_av = 1; av_s = ff[0]; av_l = ff[1] } } 737 done = 1 738 } 739 } 740 } 741 } } 742 } 743 ls = i + 1 744 } 745 i = i + 1 746 } 747 } 748 // inputSchema: argv array typed by the tool's own contract (col8) when declared; generic otherwise. 749 b = ta_cat(body, b, ",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"argv\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"" as *u8) 750 if has_av == 1 { 751 b = ta_json_esc(body, b, ((buf as i64) + av_s) as *u8, av_l) 752 } else { 753 var gemit: i64 = 0 754 if gbn > 0 { 755 let g2: *i64 = sys_mmap(16) as *i64 756 if ta_grammar_lookup(gbuf, gbn, name, nlen, g2) == 1 { 757 b = ta_json_esc(body, b, ((gbuf as i64) + g2[0]) as *u8, g2[1]) 758 gemit = 1 759 } 760 } 761 if gemit == 0 { b = ta_cat(body, b, "positional CLI args passed to the organ (argv[1..])" as *u8) } 762 } 763 b = ta_cat(body, b, "\"}}}" as *u8) 764 if has_title == 1 { 765 b = ta_cat(body, b, ",\"title\":\"" as *u8) 766 b = ta_json_esc(body, b, ((buf as i64) + tt_s) as *u8, tt_l) 767 b = ta_cat(body, b, "\"" as *u8) 768 } 769 // A CONSTANT WEARING THE SHAPE OF A MEASUREMENT IS WORSE THAN A BLANK. If this row's flags came from 770 // the register API's hardcoded quad, prefer the DERIVED quad (nx_toolflags: the organ's own syscalls 771 // plus one level of its import closure). Authored and backfill-derived rows are never touched. 772 if fbn > 0 { 773 if has_out == 1 { 774 if ta_od_is_regstub(buf, od_s, od_l) == 1 { 775 let q4: *i64 = sys_mmap(64) as *i64 776 if ta_flags_lookup(fbuf, fbn, name, nlen, q4) == 1 { 777 ro = q4[0] 778 de = q4[1] 779 idp = q4[2] 780 ow = q4[3] 781 } 782 } 783 } 784 } 785 b = ta_cat(body, b, ",\"annotations\":{\"readOnlyHint\":" as *u8) 786 b = ta_cat(body, b, ta_bool_json(ro)) 787 b = ta_cat(body, b, ",\"destructiveHint\":" as *u8) 788 b = ta_cat(body, b, ta_bool_json(de)) 789 b = ta_cat(body, b, ",\"idempotentHint\":" as *u8) 790 b = ta_cat(body, b, ta_bool_json(idp)) 791 b = ta_cat(body, b, ",\"openWorldHint\":" as *u8) 792 b = ta_cat(body, b, ta_bool_json(ow)) 793 b = ta_cat(body, b, "}" as *u8) 794 if has_out == 1 { 795 b = ta_cat(body, b, ",\"outputSchema\":{\"type\":\"object\",\"description\":\"" as *u8) 796 b = ta_json_esc(body, b, ((buf as i64) + od_s) as *u8, od_l) 797 b = ta_cat(body, b, "\"}" as *u8) 798 } 799 return b 800} 801// Back-compat presenter: callers with no grammar buffer (the gate's fixture path) keep the old signature 802// and get exactly the previous behaviour -- the generic argv description. Only the live registry walk in 803// ta_emit_tools passes a real grammar buffer, so gate fixtures stay hermetic. 804func ta_emit_mcp_schema_buf(buf: *u8, bufn: i64, name: *u8, nlen: i64, body: *u8, bin: i64) -> i64 { 805 return ta_emit_mcp_schema_buf2(buf, bufn, 0 as *u8, 0, 0 as *u8, 0, name, nlen, body, bin) 806} 807func ta_emit_mcp_schema(name: *u8, nlen: i64, body: *u8, bin: i64) -> i64 { return ta_emit_mcp_schema_from(TA_SCHEMACONF, name, nlen, body, bin) } 808 809// ---- the registry -> JSON tools array (shared by REST + MCP; mcp=1 emits inputSchema, mcp=0 emits invoke/status) -- 810func ta_emit_field(body: *u8, b: i64, rec: *u8, rl: i64, idx: i64) -> i64 { 811 let f2: *i64 = sys_mmap(16) as *i64 812 if tr_field(rec, 0, rl, idx, f2) == 1 { return ta_json_esc(body, b, ((rec as i64) + f2[0]) as *u8, f2[1]) } 813 return b 814} 815// ---- DISCOVERABILITY: description falls back to the schema title ------------------------------------ 816// MEASURED 2026-08-06 over the live tools/list, all 14 pages: of 869 registered tools, 663 (76.3%) carry 817// "<name> output (registered via /api/tools/register)" as their MCP `description`. That string is a 818// REGISTRATION-TIME PLACEHOLDER, not prose: ma_schema_append_row (nx_mgmt_api.nx) writes the registrant's 819// real one-liner into schema col2 (`title`) and SYNTHESISES col7. `description` is the one field every MCP 820// client -- and every LLM choosing a tool -- actually reads, so the estate published a stub to its most-read 821// surface while the real text sat one column away. 587 of those 663 (88.5%) have a usable title already. 822// The information was never missing; it was MISFILED. Preferring the title here fixes all 663 at read time 823// with no data migration and no destructive write. The writer fix is separate (debt 1786056847). 824// Honest limit: this is a READ-side repair. It does not improve a tool whose title is ALSO absent. 825func ta_desc_is_stub(rec: *u8, rl: i64, name: *u8, nlen: i64) -> i64 { 826 let f2: *i64 = sys_mmap(16) as *i64 827 if tr_field(rec, 0, rl, 1, f2) != 1 { return 0 } 828 let ds: i64 = f2[0] 829 let dl: i64 = f2[1] 830 let sfx: *u8 = " output (" as *u8 831 let sl: i64 = ta_slen(sfx) 832 if dl < nlen + sl { return 0 } 833 var i: i64 = 0 834 while i < nlen { if rec[ds + i] != name[i] { return 0 } i = i + 1 } 835 var j: i64 = 0 836 while j < sl { if rec[ds + nlen + j] != sfx[j] { return 0 } j = j + 1 } 837 return 1 838} 839 840// col1 (title) of `name`'s row in the schema-conf buffer -> out2 = (off,len). 1 found / 0 absent. 841// Same row-scan shape as ta_emit_mcp_schema_buf; '#' comments and blank lines ignored. 842func ta_schema_title_buf(buf: *u8, bufn: i64, name: *u8, nlen: i64, out2: *i64) -> i64 { 843 if (buf as i64) == 0 { return 0 } 844 var ls: i64 = 0 845 var i: i64 = 0 846 while i <= bufn { 847 var eol: i64 = 0 848 if i == bufn { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } } 849 if eol == 1 { 850 let le: i64 = i 851 if le > ls { if buf[ls] != (35 as u8) { 852 let f0: *i64 = sys_mmap(16) as *i64 853 if ta_tsv_field(buf, ls, le, 0, f0) == 1 { 854 if f0[1] == nlen { 855 var m: i64 = 1 856 var c: i64 = 0 857 while c < nlen { if buf[f0[0] + c] != name[c] { m = 0 } c = c + 1 } 858 if m == 1 { 859 let ff: *i64 = sys_mmap(16) as *i64 860 if ta_tsv_field(buf, ls, le, 1, ff) == 1 { if ff[1] > 0 { out2[0] = ff[0]; out2[1] = ff[1]; return 1 } } 861 return 0 862 } 863 } 864 } 865 } } 866 ls = i + 1 867 } 868 i = i + 1 869 } 870 return 0 871} 872 873// ---- LIST ORDER IS DATA (2026-09-15) ------------------------------------------------------------------ 874// MEASURED: the plane held 1,977 tools in REGISTRATION order and a seat's MCP client walked 20 pages of 875// 64 and stopped, so every tool registered after roughly the 1,280th -- the NEWEST ~700, i.e. everything 876// the estate built in its last two weeks -- was invisible to every seat. A seat calling one by name got 877// "No such tool available" for a tool that was registered, schema'd and discoverable (nx_content_get at 878// 84% of the plane; its sibling nx_content_put at 65% was visible). No single order fits 1,977 rows into 879// a 1,280 window, so the order is chosen for what a seat can USE, and the rows a window drops are the 880// ones it can least use: 881// (1) the tools the presented ?cap= grants, in registration order -- the seat's own surface, complete; 882// (2) tools with recorded MCP invocations, most-called first -- what seats demonstrably use; 883// (3) every other tool NEWEST FIRST -- a fresh registration lands inside 884// the window; what a window drops is the oldest never-invoked tail. 885// Pagination walks this virtual order, so a client that walks every page still sees every tool, and the 886// partition is exact: every registered tool is emitted exactly once. The rank is DATA: 887// knowledge/status/toolrank-<plane basename>.tsv, rows `name<TAB>calls<TAB>asof`, written by 888// nx_toolatency from actlog.jrnl (its horizon is the ledger's, ~4 MiB), keyed by the plane so a gate can 889// rank a fixture plane without touching production. Absent rank file = tier (2) empty; absent cap = 890// tier (1) empty. The order is DETERMINISTIC for a given (cap, rank file, plane). 891const TA_RANK_DIR: *u8 = "knowledge/status/toolrank-" as *u8 892const TA_RANK_EXT: *u8 = ".tsv" as *u8 893 894// knowledge/status/toolrank-<plane basename>.tsv, e.g. knowledge/toolreg- -> knowledge/status/toolrank-toolreg-.tsv 895func ta_rank_path(prefix: *u8, out: *u8) -> i64 { 896 var pl: i64 = 0 897 while prefix[pl] != (0 as u8) { pl = pl + 1 } 898 var bs: i64 = 0 899 var i: i64 = 0 900 while i < pl { if prefix[i] == (47 as u8) { bs = i + 1 } i = i + 1 } 901 var o: i64 = ta_cat(out, 0, TA_RANK_DIR) 902 var k: i64 = bs 903 while k < pl { out[o] = prefix[k]; o = o + 1; k = k + 1 } 904 o = ta_cat(out, o, TA_RANK_EXT) 905 out[o] = 0 as u8 906 return o 907} 908 909// calls recorded for `name` in the rank buffer (LINE-ANCHORED: `name<TAB>digits`); 0 when absent 910func ta_rank_calls(rb: *u8, rn: i64, name: *u8, nlen: i64) -> i64 { 911 var ls: i64 = 0 912 var i: i64 = 0 913 while i <= rn { 914 var eol: i64 = 0 915 if i == rn { eol = 1 } else { if rb[i] == (10 as u8) { eol = 1 } } 916 if eol == 1 { 917 if i - ls > nlen { if rb[ls + nlen] == (9 as u8) { 918 var m: i64 = 1 919 var c: i64 = 0 920 while c < nlen { if rb[ls + c] != name[c] { m = 0; c = nlen } else { c = c + 1 } } 921 if m == 1 { 922 var v: i64 = 0 923 var q: i64 = ls + nlen + 1 924 var go: i64 = 1 925 while go == 1 { 926 if q >= i { go = 0 } else { 927 let d: i64 = rb[q] as i64 928 if d >= 48 { if d <= 57 { v = v * 10 + (d - 48); q = q + 1 } else { go = 0 } } else { go = 0 } 929 } 930 } 931 return v 932 } 933 } } 934 ls = i + 1 935 } 936 i = i + 1 937 } 938 return 0 939} 940 941// does the presented cap's allow-list (comma-separated names before the first '~') grant `name`? Exact match only. 942func ta_cap_grants(capp: *u8, capn: i64, name: *u8, nlen: i64) -> i64 { 943 if capn <= 0 { return 0 } 944 var s: i64 = 0 945 while s < capn { 946 var e: i64 = s 947 var go: i64 = 1 948 while go == 1 { if e >= capn { go = 0 } else { if capp[e] == (44 as u8) { go = 0 } else { if capp[e] == (126 as u8) { go = 0 } else { e = e + 1 } } } } 949 if e - s == nlen { 950 var m: i64 = 1 951 var c: i64 = 0 952 while c < nlen { if capp[s + c] != name[c] { m = 0; c = nlen } else { c = c + 1 } } 953 if m == 1 { return 1 } 954 } 955 if e < capn { if capp[e] == (126 as u8) { return 0 } } 956 s = e + 1 957 } 958 return 0 959} 960 961func ta_emit_tools(prefix: *u8, body: *u8, bin: i64, mcp: i64, offset: i64, limit: i64, out_more: *i64, capp: *u8, capn: i64) -> i64 { 962 var b: i64 = bin 963 var tidx: i64 = 0 // pagination: index across ALL tools in the CHOSEN order; emit only [offset, offset+limit); flag out_more if beyond 964 // SIZE-TO-NEED. This is the live "what tools exist" surface, and the 64 KiB 965 // ceiling it replaces was already ~1/3 consumed at ~1,072 registered tools. 966 // Past that ceiling reg_index refused with -1, which this function fed into a 967 // pagination walk -- so the tool list would have gone EMPTY, and an empty tool 968 // list is indistinguishable from a healthy registry with nothing in it. 969 let idxbox: *i64 = sys_mmap(16) as *i64 970 let il: i64 = tool_list_pfx(prefix, idxbox) 971 let idxbuf: *u8 = idxbox[0] as *u8 972 var scb: *u8 = 0 as *u8 973 var scn: i64 = 0 974 // Loaded for BOTH surfaces now (was mcp==1 only): the REST tools array needs the schema title as well, 975 // because `description` now falls back to it whenever the registry row carries the registration stub. 976 let sszp: *i64 = sys_mmap(16) as *i64 977 scb = sys_read_file(TA_SCHEMACONF, sszp) 978 if (scb as i64) != 0 { scn = sszp[0] } 979 // read ONCE per request, never once per tool: a per-tool re-read cost 64 x ~125KB on one tools/list page 980 var gcb: *u8 = 0 as *u8 981 var gcn: i64 = 0 982 let gszp: *i64 = sys_mmap(16) as *i64 983 gcb = sys_read_file(TA_GRAMMARCONF, gszp) 984 if (gcb as i64) != 0 { gcn = gszp[0] } 985 var fcb: *u8 = 0 as *u8 986 var fcn: i64 = 0 987 let fszp: *i64 = sys_mmap(16) as *i64 988 fcb = sys_read_file(TA_FLAGSCONF, fszp) 989 if (fcb as i64) != 0 { fcn = fszp[0] } 990 // ---- pass 0: index the plane's names (start,len) so the emit walks a CHOSEN ORDER, not file order ---- 991 var ntools: i64 = 0 992 var i: i64 = 0 993 var ls: i64 = 0 994 while i <= il { 995 var eol: i64 = 0 996 if i == il { eol = 1 } else { if idxbuf[i] == (10 as u8) { eol = 1 } } 997 if eol == 1 { if i - ls > 0 { ntools = ntools + 1 } ls = i + 1 } 998 i = i + 1 999 } 1000 let nst: *i64 = sys_mmap(8 * (ntools + 1)) as *i64 1001 let nln: *i64 = sys_mmap(8 * (ntools + 1)) as *i64 1002 let tier: *i64 = sys_mmap(8 * (ntools + 1)) as *i64 1003 let calls: *i64 = sys_mmap(8 * (ntools + 1)) as *i64 1004 let order: *i64 = sys_mmap(8 * (ntools + 1)) as *i64 1005 var k: i64 = 0 1006 i = 0 1007 ls = 0 1008 while i <= il { 1009 var eol: i64 = 0 1010 if i == il { eol = 1 } else { if idxbuf[i] == (10 as u8) { eol = 1 } } 1011 if eol == 1 { if i - ls > 0 { nst[k] = ls; nln[k] = i - ls; k = k + 1 } ls = i + 1 } 1012 i = i + 1 1013 } 1014 // the plane's rank table, read ONCE per request like the confs above (absent = no tier-2 rows) 1015 let rpath: *u8 = sys_mmap(512) 1016 ta_rank_path(prefix, rpath) 1017 var rb: *u8 = 0 as *u8 1018 var rn: i64 = 0 1019 let rszp: *i64 = sys_mmap(16) as *i64 1020 rb = sys_read_file(rpath, rszp) 1021 if (rb as i64) != 0 { rn = rszp[0] } 1022 // tiers: 1 = granted by the presented cap, 2 = has recorded calls, 3 = everything else 1023 var no: i64 = 0 1024 k = 0 1025 while k < ntools { 1026 let np: *u8 = ((idxbuf as i64) + nst[k]) as *u8 1027 tier[k] = 3 1028 calls[k] = 0 1029 if ta_cap_grants(capp, capn, np, nln[k]) == 1 { tier[k] = 1 } else { 1030 if rn > 0 { calls[k] = ta_rank_calls(rb, rn, np, nln[k]); if calls[k] > 0 { tier[k] = 2 } } 1031 } 1032 k = k + 1 1033 } 1034 k = 0 1035 while k < ntools { if tier[k] == 1 { order[no] = k; no = no + 1 } k = k + 1 } 1036 // tier 2: most-called first, ties broken by registration order (selection over the ranked subset) 1037 var more: i64 = 1 1038 while more == 1 { 1039 var best: i64 = 0 - 1 1040 k = 0 1041 while k < ntools { if tier[k] == 2 { if best < 0 { best = k } else { if calls[k] > calls[best] { best = k } } } k = k + 1 } 1042 if best < 0 { more = 0 } else { order[no] = best; no = no + 1; tier[best] = 0 } 1043 } 1044 k = ntools - 1 1045 while k >= 0 { if tier[k] == 3 { order[no] = k; no = no + 1 } k = k - 1 } 1046 // ---- emit in that order; pagination walks the virtual order ---- 1047 let namez: *u8 = sys_mmap(512) 1048 let po: *i64 = sys_mmap(16) as *i64 1049 let lo: *i64 = sys_mmap(16) as *i64 1050 var first: i64 = 1 1051 var oi: i64 = 0 1052 while oi < no { 1053 let kk: i64 = order[oi] 1054 let nl: i64 = nln[kk] 1055 { 1056 { 1057 var c: i64 = 0 1058 while c < nl { namez[c] = idxbuf[nst[kk] + c]; c = c + 1 } 1059 namez[nl] = 0 as u8 1060 if tool_get_pfx(prefix, namez, po, lo) == 1 { 1061 if tidx >= offset { if tidx < offset + limit { 1062 let rec: *u8 = po[0] as *u8 1063 let rl: i64 = lo[0] 1064 if first == 0 { b = ta_cat(body, b, "," as *u8) } 1065 first = 0 1066 b = ta_cat(body, b, "{\"name\":\"" as *u8) 1067 b = ta_emit_field(body, b, rec, rl, 0) 1068 b = ta_cat(body, b, "\",\"description\":\"" as *u8) 1069 // Prefer the schema title when the registry carries the registration-time stub. 1070 var demit: i64 = 0 1071 if scn > 0 { 1072 if ta_desc_is_stub(rec, rl, namez, nl) == 1 { 1073 let t2: *i64 = sys_mmap(16) as *i64 1074 if ta_schema_title_buf(scb, scn, namez, nl, t2) == 1 { 1075 b = ta_json_esc(body, b, ((scb as i64) + t2[0]) as *u8, t2[1]) 1076 demit = 1 1077 } 1078 } 1079 } 1080 if demit == 0 { b = ta_emit_field(body, b, rec, rl, 1) } 1081 if mcp == 1 { 1082 b = ta_cat(body, b, "\"" as *u8) 1083 b = ta_emit_mcp_schema_buf2(scb, scn, gcb, gcn, fcb, fcn, namez, nl, body, b) // all three confs read once per request above 1084 b = ta_cat(body, b, "}" as *u8) 1085 } else { 1086 b = ta_cat(body, b, "\",\"invoke\":\"" as *u8) 1087 b = ta_emit_field(body, b, rec, rl, 2) 1088 b = ta_cat(body, b, "\",\"status\":\"" as *u8) 1089 b = ta_emit_field(body, b, rec, rl, 3) 1090 b = ta_cat(body, b, "\"}" as *u8) 1091 } 1092 } else { if (out_more as i64) != 0 { out_more[0] = 1 } } } 1093 tidx = tidx + 1 1094 } 1095 } 1096 } 1097 oi = oi + 1 1098 } 1099 return b 1100} 1101 1102// ---- route handlers --------------------------------------------------------------------------------- 1103func ta_index(out: *u8) -> i64 { 1104 let body: *u8 = "{\"api\":\"nishi-tools\",\"version\":1,\"surfaces\":{\"rest\":\"GET /api/tools\",\"mcp\":\"POST /mcp (JSON-RPC 2.0: initialize, tools/list)\"},\"beyond_mcp\":\"each tool carries a gate-status MCP tools/list has no field for\"}" as *u8 1105 return ta_emit_lit(out, "HTTP/1.1 200 OK" as *u8, body) 1106} 1107func ta_emit_page(out: *u8, html: *u8, n: i64) -> i64 { 1108 var o: i64 = ta_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nContent-Length: " as *u8) 1109 o = ta_catn(out, o, n) 1110 o = ta_cat(out, o, "\r\n\r\n" as *u8) 1111 o = ta_catb(out, o, html, n) 1112 return o 1113} 1114// GET /tools -- a self-contained HUMAN page (NO double-quotes in the HTML so it is a clean NishiLang literal): it 1115// fetches /api/tools client-side + renders one card per tool (XSS-escaped). THE login-level UI surface for the 1116// capability ([[feedback-tools-must-surface-in-ui]]). 1117func ta_html_tools(out: *u8) -> i64 { 1118 let h: *u8 = sys_mmap(TA_MAGIC_8192) 1119 var o: i64 = 0 1120 o = ta_cat(h, o, "<!DOCTYPE html><meta charset=utf-8><meta name=viewport content='width=device-width,initial-scale=1'><title>Nishi Tools</title>" as *u8) 1121 o = ta_cat(h, o, "<style>body{font-family:-apple-system,Segoe UI,sans-serif;max-width:900px;margin:4vh auto;padding:0 20px;color:#1c1c1e}h1{font-size:1.4rem;margin-bottom:.2rem}.sub{color:#666;margin:.1rem 0 1.1rem}.card{border:1px solid #e2e2e6;border-radius:10px;padding:12px 16px;margin:12px 0;background:#fbfbfd}.card h3{margin:.1rem 0 .3rem;font-family:ui-monospace,monospace}.inv{font-family:ui-monospace,monospace;font-size:.82rem;color:#555;background:#f2f2f7;padding:6px 8px;border-radius:6px;margin:.4rem 0;overflow-x:auto}.badge{display:inline-block;font-size:.72rem;padding:2px 8px;border-radius:99px;background:#e6f5ec;color:#0a6;font-weight:600}code{font-family:ui-monospace,monospace}.mcp{background:#eef;border:1px solid #dde;border-radius:8px;padding:10px 14px;font-size:.86rem;margin:1rem 0}</style>" as *u8) 1122 o = ta_cat(h, o, "<h1>Nishi Tools</h1><p class=sub>Sovereign agent-facing registry &mdash; MCP-compatible + superset.</p><div class=mcp><b>MCP endpoint:</b> <code>POST /mcp</code> (JSON-RPC 2.0: <code>initialize</code>, <code>tools/list</code>) &mdash; any MCP client connects with zero adapter. &nbsp;<b>REST:</b> <code>GET /api/tools</code>. &nbsp;<b>Beyond MCP:</b> every tool carries a gate-<code>status</code>.</div><div id=t>loading&hellip;</div>" as *u8) 1123 o = ta_cat(h, o, "<script>function esc(s){return String(s).split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;')}fetch('/api/tools').then(function(r){return r.json()}).then(function(j){var a=j.tools||[],b='';for(var i=0;i<a.length;i++){var t=a[i];b+='<div class=card><h3>'+esc(t.name)+' <span class=badge>'+esc(t.status||'')+'</span></h3><div>'+esc(t.description||'')+'</div><div class=inv>'+esc(t.invoke||'')+'</div></div>'}document.getElementById('t').innerHTML=b||'<i>no tools registered</i>'}).catch(function(){document.getElementById('t').innerHTML='<i>failed to load /api/tools</i>'})</script>" as *u8) 1124 return ta_emit_page(out, h, o) 1125} 1126func ta_tools(prefix: *u8, out: *u8) -> i64 { 1127 let jb: *u8 = sys_mmap(TA_MAGIC_1048576) 1128 var b: i64 = ta_cat(jb, 0, "{\"api\":\"nishi-tools\",\"version\":1,\"tools\":[" as *u8) 1129 b = ta_emit_tools(prefix, jb, b, 0, 0, TA_ALL_LIMIT, 0 as *i64, 0 as *u8, 0) 1130 b = ta_cat(jb, b, "]}" as *u8) 1131 return ta_emit(out, "HTTP/1.1 200 OK" as *u8, jb, b) 1132} 1133// extract an HTTP header value into out2 (off,len). `key` includes the trailing colon, e.g. "X-Nishi-Cap:". 1134// Searches only the header region (before the CRLFCRLF), trims leading spaces, stops at CR/LF. 1 found / 0 absent. 1135func ta_header(req: *u8, req_n: i64, key: *u8, out2: *i64) -> i64 { 1136 let hend: i64 = ta_body_off(req, req_n) 1137 let p: i64 = ta_indexof(req, hend, key) 1138 if p < 0 { return 0 } 1139 var vs: i64 = p + ta_slen(key) 1140 var go: i64 = 1 1141 while go == 1 { if vs < hend { if req[vs] == (0x20 as u8) { vs = vs + 1 } else { go = 0 } } else { go = 0 } } 1142 var ve: i64 = vs 1143 var go2: i64 = 1 1144 while go2 == 1 { if ve < hend { let c: i64 = req[ve] as i64; if c == 13 { go2 = 0 } else { if c == 10 { go2 = 0 } else { ve = ve + 1 } } } else { go2 = 0 } } 1145 out2[0] = vs; out2[1] = ve - vs 1146 return 1 1147} 1148 1149// ---- ALTERNATE PRESENTER: the capability on the REQUEST-LINE QUERY STRING (?cap=<token>) ------------------ 1150// WHY THIS EXISTS (measured 2026-07-30; debt seq1235). An MCP client that does not deliver its configured 1151// X-Nishi-Cap header leaves EVERY tools/call denied for a WHOLE SESSION. The deny is indistinguishable from 1152// -- this tool does not exist -- so the session falls back to raw ssh/PowerShell and never revisits: that is 1153// 5,776 of 13,003 measured tool calls (44pct). Custom request headers are the most commonly dropped part of an 1154// HTTP client's config; the URL is the ONE thing every client provably sends, because it is how the request 1155// reached us at all. Binding the cap to the URL makes a session-starts-denied state impossible BY CONSTRUCTION 1156// instead of by remembering to restart a client so a header re-loads. 1157// SAME OBJECT-CAPABILITY, different presenter: the token is still HMAC-verified, expiry-checked, revocation- 1158// checked and least-authority below. This changes WHERE the token is read from, never WHETHER it is proven -- 1159// exactly the relationship the Authorization: Bearer presenter already has to the native header. 1160// PRECEDENCE IS LOWEST ON PURPOSE (see ta_mcp_call): an explicit per-call _cap always wins, so the URL carries 1161// the session BASELINE (least authority) and a deliberate escalation still overrides it. 1162// Present ONLY a read/least-authority cap this way: URLs are logged by proxies far more readily than headers. 1163// Key match is EXACT (len 3 + c,a,p) so a lookalike param like recap= or capx= can never authorize. 1164func ta_query_cap(req: *u8, req_n: i64, out2: *i64) -> i64 { 1165 let mo: *i64 = sys_mmap(16) as *i64 1166 let po: *i64 = sys_mmap(16) as *i64 1167 if ta_reqline(req, req_n, mo, po) == 0 { return 0 } 1168 let poff: i64 = po[0] 1169 let plen: i64 = po[1] 1170 var qi: i64 = 0 1171 var qs: i64 = 0 - 1 1172 while qi < plen { if qs < 0 { if req[poff + qi] == (63 as u8) { qs = qi + 1 } } qi = qi + 1 } 1173 if qs < 0 { return 0 } 1174 // walk k=v pairs separated by '&'; accept the FIRST pair whose key is exactly cap (len 3). 1175 var s: i64 = qs 1176 while s < plen { 1177 var e: i64 = s 1178 var go: i64 = 1 1179 while go == 1 { if e >= plen { go = 0 } else { if req[poff + e] == (38 as u8) { go = 0 } else { e = e + 1 } } } 1180 var eq: i64 = 0 - 1 1181 var k: i64 = s 1182 while k < e { if eq < 0 { if req[poff + k] == (61 as u8) { eq = k } } k = k + 1 } 1183 if eq - s == 3 { 1184 if req[poff + s] == (99 as u8) { if req[poff + s + 1] == (97 as u8) { if req[poff + s + 2] == (112 as u8) { 1185 if e - eq - 1 > 0 { out2[0] = poff + eq + 1; out2[1] = e - eq - 1; return 1 } 1186 } } } 1187 } 1188 s = e + 1 1189 } 1190 return 0 1191} 1192// Liveness marker for the query-string presenter: returns 1 so a gate can assert the presenter is COMPILED IN 1193// rather than merely declared. Asserted by nx_tools_api_gate T14. 1194func ta_query_cap_available() -> i64 { 1195 return 1 1196} 1197 1198// R2: capability-scoped tools/call. Requires an X-Nishi-Cap capability token that GRANTS the requested tool (authority- 1199// in-the-token, verified with NO ambient identity -> confused-deputy is structurally impossible, unlike MCP's OAuth 1200// scopes). No capability, or one that doesn't grant this tool, or a forged/expired one -> JSON-RPC -32001. On success: 1201// ---- REVOCATION denylist: kill a leaked capability BEFORE its expiry (completes issue->delegate->verify->REVOKE) ---- 1202const TA_REVOKED_LIST: *u8 = "cap_revoked.list" as *u8 // newline-separated decimal nonces, relative to the server CWD 1203func ta_openat_append(path: *u8) -> i64 { return __syscall(257, AT_FDCWD, path, 0x441, 0x1a4, 0, 0) } // O_WRONLY|O_CREAT|O_APPEND, 0644 1204func ta_nonce_revoked(nonce: i64) -> i64 { 1205 if nonce < 0 { return 0 } 1206 let szp: *i64 = sys_mmap(16) as *i64 1207 let buf: *u8 = sys_read_file(TA_REVOKED_LIST, szp) 1208 if (buf as i64) == 0 { return 0 } 1209 let n: i64 = szp[0] 1210 let dec: *u8 = sys_mmap(32); let dl: i64 = ta_catn(dec, 0, nonce) 1211 var s: i64 = 0; var i: i64 = 0 1212 while i <= n { 1213 var eol: i64 = 0 1214 if i == n { eol = 1 } else { if buf[i] == (0x0a as u8) { eol = 1 } } 1215 if eol == 1 { 1216 if i - s == dl { var m: i64 = 1; var c: i64 = 0; while c < dl { if buf[s + c] != dec[c] { m = 0 } c = c + 1 } if m == 1 { return 1 } } 1217 s = i + 1 1218 } 1219 i = i + 1 1220 } 1221 return 0 1222} 1223func ta_append_revoked(nonce: i64) -> i64 { 1224 let dec: *u8 = sys_mmap(48); var dl: i64 = ta_catn(dec, 0, nonce); dec[dl] = 0x0a as u8; dl = dl + 1 1225 let fd: i64 = ta_openat_append(TA_REVOKED_LIST) 1226 if fd < 0 { return 0 - 1 } 1227 sys_write(fd, dec, dl); sys_close(fd) 1228 return 0 1229} 1230 1231// a SAFE STUB result (increment 1 proves the AUTHORIZATION; real tool execution is a separate guarded surface). Appends 1232// the ",result"/",error" tail to jb and returns the new offset. 1233// seq1293: exact-line membership test (CR-tolerant) for async_only_tools.conf -- substring matching 1234// would let "nx_fs" shadow "nx_fs_write", so lines must match the WHOLE tool name. 1235func ta_name_in_lines(buf: *u8, n: i64, nm: *u8, nl: i64) -> i64 { 1236 var i: i64 = 0 1237 while i < n { 1238 var e: i64 = i 1239 var s: i64 = 1 1240 while s == 1 { if e >= n { s = 0 } else { if buf[e] == (10 as u8) { s = 0 } else { e = e + 1 } } } 1241 var le: i64 = e 1242 if le > i { if buf[le-1] == (13 as u8) { le = le - 1 } } 1243 if le - i == nl { 1244 var j: i64 = 0 1245 var ok: i64 = 1 1246 while j < nl { if buf[i+j] != nm[j] { ok = 0; j = nl } else { j = j + 1 } } 1247 if ok == 1 { return 1 } 1248 } 1249 i = e + 1 1250 } 1251 return 0 1252} 1253// PER-VERB ASYNC ROUTING (2026-08-06). async_only_tools.conf lines may now be EITHER 1254// <tool> -- the whole tool is forced onto the job lane (existing behaviour, unchanged) 1255// <tool> <verb> -- ONLY that verb is forced; every other verb stays synchronous 1256// WHY THE VERB AND NOT THE TOOL. MEASURED 2026-08-06: `nx_debt add` costs ~0.6s CPU but 23-40s WALL 1257// (0.113 user + 0.515 sys of a 40.026s run; later probes 23s and 25s -- variable, so contention, not a 1258// fixed backoff). Every measured value outruns the edge window, so the response is dropped and the row 1259// NEVER LANDS, while the caller sees a 503 and believes the plane is down. Several findings were lost 1260// that way before it was measured. But `nx_debt find` answers in milliseconds: listing the whole tool 1261// would buy the write back by making EVERY READ a two-step poll -- paying for the fix with the common 1262// case, which is the same trade the fabricated-annotation default made. 1263// ★ THE LATENCY IS A PROPERTY OF THE VERB, SO THE ROUTING MUST BE TOO. 1264// Exact whole-line matching is preserved (a substring rule would let "nx_fs" shadow "nx_fs_write"), and 1265// an absent/empty conf is still byte-identical prior behaviour. 1266func ta_async_match(buf: *u8, n: i64, nm: *u8, nl: i64, av: *i64, ac: i64) -> i64 { 1267 if ta_name_in_lines(buf, n, nm, nl) == 1 { return 1 } 1268 if ac < 1 { return 0 } 1269 // av[0] is RESERVED for the resolved ELF path (filled later by tea_run_argv); the caller's first 1270 // argument is av[1]. Reading av[0] here silently matched nothing -- the conf line was correct, the 1271 // binary was correct, and the routing simply never fired. 1272 // ★ AN ARGV WHOSE SLOT 0 IS RESERVED IS NOT THE ARGV YOU ASSUME -- READ THE BUILDER, NOT THE NAME. 1273 let v: *u8 = av[1] as *u8 1274 if (v as i64) == 0 { return 0 } 1275 var vl: i64 = 0 1276 while v[vl] != (0 as u8) { vl = vl + 1 } 1277 if vl == 0 { return 0 } 1278 let want: i64 = nl + 1 + vl 1279 var i: i64 = 0 1280 while i < n { 1281 var e: i64 = i 1282 var s: i64 = 1 1283 while s == 1 { if e >= n { s = 0 } else { if buf[e] == (10 as u8) { s = 0 } else { e = e + 1 } } } 1284 var le: i64 = e 1285 if le > i { if buf[le-1] == (13 as u8) { le = le - 1 } } 1286 if le - i == want { 1287 var ok: i64 = 1 1288 var j: i64 = 0 1289 while j < nl { if buf[i+j] != nm[j] { ok = 0; j = nl } else { j = j + 1 } } 1290 if ok == 1 { if buf[i+nl] != (32 as u8) { ok = 0 } } 1291 if ok == 1 { 1292 var k: i64 = 0 1293 while k < vl { if buf[i+nl+1+k] != v[k] { ok = 0; k = vl } else { k = k + 1 } } 1294 } 1295 if ok == 1 { return 1 } 1296 } 1297 i = e + 1 1298 } 1299 return 0 1300} 1301// ---- DEFERRED PROMOTION BOOKKEEPING (LANE K 2026-08-23, debt 1787505278) -------------------------------- 1302// THE DEFECT: the TR_PROMOTE reply ("JOB-STARTED id=...") was BUILT only after mkdir + claim + fork + wait4 1303// + actlog, and SENT by the serve loop later still. On a saturated array that bookkeeping overshot the 1304// promote decision -- measured lane=sync-promoted dur_ms p50 16410 / p90 26612 / p99 41422 against a 1305// 14000 ms decision inside a 15000 ms edge window, so 574 permil of promoted calls replied AFTER the edge 1306// had already synthesised a 503: the "raw 503 that had actually landed" class every seat hits. 1307// THE FIX is ORDER, not budget (the Envoy rule: announce cheaply BEFORE the expensive work). The reply 1308// needs ONLY the reserved job id, so ta_mcp_call reserves the claim (the id must be real), parks what the 1309// drainer needs in these process-local statics, and RETURNS the reply at once. A caller that OWNS A 1310// SOCKET calls ta_defer_enable() before ta_handle and ta_promote_finish() after the reply bytes are on the 1311// wire; finish forks the drainer, reaps the middle child and writes the actlog row -- the SAME code that 1312// used to run inline, now ONE function (ta_promote_bookkeep) on both paths. A caller that did not opt in 1313// (a pure-bytes gate driving ta_handle) keeps the old inline ordering byte-for-byte: the deferral is a 1314// contract the serve loop signs, never an assumption about the caller. Fork-per-request makes the statics 1315// per-request by construction; the inline (backpressure) path resets ta_pp_pending in finish. 1316static ta_defer_ok: i64 1317static ta_pp_pending: i64 1318static ta_pp_nmp: i64 1319static ta_pp_nml: i64 1320static ta_pp_jid: i64 1321static ta_pp_jpath: i64 1322static ta_pp_jout: i64 1323static ta_pp_jtmp: i64 1324static ta_pp_xout: i64 1325static ta_pp_precap: i64 1326static ta_pp_wrfd: i64 1327static ta_pp_t0: i64 1328static ta_pp_tdecide: i64 1329const TA_REPLY_MS_UNOBSERVED: i64 = 0 - 1 // inline path: the reply has not been written when the row is stamped 1330 1331func ta_defer_enable() -> i64 { ta_defer_ok = 1; return 0 } 1332func ta_promote_pending() -> i64 { return ta_pp_pending } 1333 1334// the drainer + the row, verbatim from the former inline block. reply_ms < 0 = unobserved (inline path). 1335func ta_promote_bookkeep(nmp: *u8, nml: i64, jidp: i64, jpathp: *u8, joutp: *u8, jtmpp: *u8, xout: *u8, precap: i64, wrfd0: i64, t0: i64, reply_ms: i64) -> i64 { 1336 let pcont: i64 = sys_fork() 1337 if pcont == 0 { 1338 var fdc2: i64 = 3 1339 while fdc2 < 256 { if fdc2 != wrfd0 { sys_close(fdc2) } fdc2 = fdc2 + 1 } 1340 nx_setsid() 1341 let cc2: i64 = sys_fork() 1342 if cc2 != 0 { sys_exit(0) } 1343 let tt_capture_begin: i64 = ta_timing_now_us() 1344 let jbuf: *u8 = sys_mmap(TA_MAGIC_1048576) 1345 var jn: i64 = 0 1346 while jn < precap { if jn < TA_MAGIC_1048576 { jbuf[jn] = xout[jn] } jn = jn + 1 } 1347 var drun: i64 = 1 1348 while drun == 1 { 1349 if jn >= TA_MAGIC_1048576 { drun = 0 } else { 1350 let dr: i64 = sys_read(wrfd0, ((jbuf as i64) + jn) as *u8, TA_MAGIC_1048576 - jn) 1351 if dr > 0 { jn = jn + dr } else { drun = 0 } 1352 } 1353 } 1354 let tt_capture_end: i64 = ta_timing_now_us() 1355 if jn >= TA_MAGIC_1048576 { 1356 let tm: *u8 = "\n[NX-JOB CAPTURE-TRUNCATED at 1048576 bytes -- output incomplete]\n" as *u8 1357 var tl: i64 = 0; while tm[tl] != (0 as u8) { tl = tl + 1 } 1358 var cw: i64 = 0; while cw < tl { if jn - tl + cw >= 0 { jbuf[jn - tl + cw] = tm[cw] } cw = cw + 1 } 1359 } 1360 let dbuf2: *u8 = sys_mmap(256) 1361 // exit=PROMOTED-UNREAPED is HONEST: the drainer is the worker's sibling (the request 1362 // child forked both), so it can drain the pipe to EOF but cannot wait4 the worker -- 1363 // init reaps it. A promoted call trades an exit code for an answer that otherwise does 1364 // not exist at all, and the record says which it is rather than fabricating a 0. 1365 var db2: i64 = ta_cat(dbuf2, 0, "state=DONE rc=0 exit=PROMOTED-UNREAPED bytes=" as *u8) 1366 db2 = ta_catn(dbuf2, db2, jn); dbuf2[db2] = 10 as u8; db2 = db2 + 1 1367 let tt_publish_begin: i64 = ta_timing_now_us() 1368 let published: i64 = ta_job_publish(joutp, jbuf, jn, jtmpp, dbuf2, db2, jpathp) 1369 let tt_publish_end: i64 = ta_timing_now_us() 1370 ta_timing_emit(nmp, nml, "sync-promoted", "promoted_capture_return_unreaped", jidp, tt_capture_begin, tt_capture_end, 0-1, jn) 1371 ta_timing_emit(nmp, nml, "sync-promoted", "job_publish_return_unreaped", jidp, tt_publish_begin, tt_publish_end, published, jn) 1372 if published != 0 { 1373 ta_actlog(nmp, nml, "sync-promoted", "evidence-fail", published, jn, sys_now_ms() - t0) 1374 sys_exit(1) 1375 } 1376 sys_exit(0) 1377 return 0 1378 } 1379 if pcont > 0 { let pst: *i64 = sys_mmap(16) as *i64; sys_wait4(pcont, pst, 0) } 1380 sys_close(wrfd0) 1381 // ROW FIELDS (debt 1787505278): bytes= is the REAL captured byte count (it used to carry the job id, 1382 // mislabelled); the id is job=; reply_ms= is decision -> reply-written, the number reply_reserve_ms 1383 // can be ratcheted from. reply_ms=-1 means the row was stamped on the inline path before any reply. 1384 let xb: *u8 = sys_mmap(96) 1385 var xo: i64 = ta_cat(xb, 0, " job=" as *u8) 1386 xo = ta_catn(xb, xo, jidp) 1387 xo = ta_cat(xb, xo, " reply_ms=" as *u8) 1388 xo = ta_catn(xb, xo, reply_ms) 1389 xb[xo] = 0 as u8 1390 ta_actlog_x(nmp, nml, "sync-promoted" as *u8, "started" as *u8, 0, precap, sys_now_ms() - t0, xb) 1391 return 0 1392} 1393 1394// the serve loop's half of the contract: call AFTER the reply bytes are on the wire. Idempotent no-op 1395// when nothing was deferred, so it is safe on every response path (413, fast, RED, promoted). 1396// SEV-9 2026-09-05 (debt frontdoor/unsafe-call-outcome-unknown-receipt-dropped): THE ASYNC JOB LANE GETS THE SAME 1397// CONTRACT THE PROMOTE LANE ABOVE ALREADY HAS. It reserved the job id, wrote the claim, forked the runner -- and then 1398// sys_wait4()ed the middle child and appended the actlog row BEFORE building its JOB-STARTED reply, so on a storming 1399// array that bookkeeping outlived the 15 s edge window, the edge synthesised 503 'Outcome Unknown', and the reserved 1400// id (the only handle to the outcome) was never delivered while the job ran anyway. Measured 2026-09-05: 7 dropped 1401// receipts, 6 landed, one self-anchored insert issued twice ended at one copy by luck. Same fix, same order: reply 1402// FIRST (the id is real the moment the claim is on disk), park pid1/name/id here, drain in ta_async_finish() which the 1403// serve loop calls right after ta_promote_finish(). A caller that did not opt in (a pure-bytes gate) keeps the inline 1404// wait4+actlog byte-for-byte -- the deferral is the serve loop's contract, never an assumption about the caller. 1405static ta_ap_pending: i64 1406static ta_ap_pid: i64 1407static ta_ap_nmp: i64 1408static ta_ap_nml: i64 1409static ta_ap_jid: i64 1410static ta_ap_t0: i64 1411func ta_async_pending() -> i64 { return ta_ap_pending } 1412func ta_async_finish() -> i64 { 1413 if ta_ap_pending != 1 { return 0 } 1414 ta_ap_pending = 0 1415 if ta_ap_pid > 0 { 1416 let jst: *i64 = sys_mmap(16) as *i64 1417 sys_wait4(ta_ap_pid, jst, 0) 1418 } 1419 ta_actlog(ta_ap_nmp as *u8, ta_ap_nml, "async" as *u8, "started" as *u8, 0, ta_ap_jid, sys_now_ms() - ta_ap_t0) 1420 return 1 1421} 1422 1423func ta_promote_finish() -> i64 { 1424 // the async twin drains in ta_async_finish(), called by the serve loop right after this one 1425 if ta_pp_pending != 1 { return 0 } 1426 ta_pp_pending = 0 1427 let reply_ms: i64 = sys_now_ms() - ta_pp_tdecide 1428 ta_promote_bookkeep(ta_pp_nmp as *u8, ta_pp_nml, ta_pp_jid, ta_pp_jpath as *u8, ta_pp_jout as *u8, ta_pp_jtmp as *u8, ta_pp_xout as *u8, ta_pp_precap, ta_pp_wrfd, ta_pp_t0, reply_ms) 1429 return 1 1430} 1431 1432// Process-local observations share actlog.jrnl. Unknown is -1, never a fabricated zero. 1433// These clocks observe server operations, not physical client arrival or first result byte. 1434const TT_UNKNOWN: i64 = 0-1 1435const TT_I64_MAX: i64 = 9223372036854775807 1436const TT_US_PER_SEC: i64 = 1000000 1437const TT_NS_PER_US: i64 = 1000 1438const TT_NS_PER_SEC: i64 = 1000000000 1439const TT_I64_WIDTH: i64 = 20 1440static tt_clock: *i64 1441static tt_tool: *u8 1442static tt_tool_n: i64 1443static tt_read_begin: i64 1444static tt_read_end: i64 1445static tt_handle_begin: i64 1446static tt_handle_end: i64 1447static tt_write_begin: i64 1448static tt_write_end: i64 1449static tt_write_rc: i64 1450static tt_job: i64 1451static tt_reply_kind: *u8 1452static tt_request_pid: i64 1453static tt_context_active: i64 1454static tt_runner_present: i64 1455static tt_runner_begin: i64 1456static tt_runner_end: i64 1457static tt_runner_code: i64 1458static tt_runner_bytes: i64 1459 1460func ta_timing_now_us() -> i64 { 1461 if (tt_clock as i64) <= 0 { tt_clock = sys_mmap(16) as *i64 } 1462 if (tt_clock as i64) <= 0 { return TT_UNKNOWN } 1463 if sys_clock_gettime_mono(tt_clock) != 0 { return TT_UNKNOWN } 1464 let sec: i64 = tt_clock[0] 1465 let nano: i64 = tt_clock[1] 1466 if sec < 0 { return TT_UNKNOWN } 1467 if nano < 0 { return TT_UNKNOWN } 1468 if nano >= TT_NS_PER_SEC { return TT_UNKNOWN } 1469 if sec > (TT_I64_MAX-nano/TT_NS_PER_US)/TT_US_PER_SEC { return TT_UNKNOWN } 1470 return sec*TT_US_PER_SEC+nano/TT_NS_PER_US 1471} 1472func ta_timing_elapsed(begin: i64, end: i64) -> i64 { 1473 if begin < 0 { return TT_UNKNOWN } 1474 if end < begin { return TT_UNKNOWN } 1475 return end-begin 1476} 1477func ta_timing_field_ok(s: *u8, n: i64) -> i64 { 1478 if n < 0 { return 0 } 1479 if (s as i64) <= 0 { return 0 } 1480 var i: i64 = 0 1481 while i < n { if s[i] < (32 as u8) { return 0 } i = i+1 } 1482 return 1 1483} 1484// `path` is the existing journal in production and an isolated caller path in the gate. 1485// No dur_ms token: legacy latency consumers must not count phase rows as extra tool calls. 1486func ta_timing_emit_to(path: *u8, tool: *u8, tlen: i64, lane: *u8, phase: *u8, job: i64, begin: i64, end: i64, code: i64, bytes: i64) -> i64 { 1487 if ta_timing_field_ok(tool, tlen) == 0 { return 0-1 } 1488 if ta_timing_field_ok(lane, ta_slen(lane)) == 0 { return 0-1 } 1489 if ta_timing_field_ok(phase, ta_slen(phase)) == 0 { return 0-1 } 1490 let a: *u8 = "\tmcp\t" as *u8 1491 let b: *u8 = "\tobserve\t" as *u8 1492 let c: *u8 = "\ttools/timing timing_v=1 lane=" as *u8 1493 let d: *u8 = " job=" as *u8 1494 let context: *u8 = " request_pid=" as *u8 1495 let origin: *u8 = " request_begin_us=" as *u8 1496 let e: *u8 = " clock=monotonic_us clock_scope=same_process_tree begin_us=" as *u8 1497 let f: *u8 = " end_us=" as *u8 1498 let g: *u8 = " elapsed_us=" as *u8 1499 let h: *u8 = " code=" as *u8 1500 let k: *u8 = " bytes=" as *u8 1501 let tail: *u8 = " client_first_byte=unobserved result_first_byte=unobserved\n" as *u8 1502 var cap: i64 = ta_log_size_add(9*TT_I64_WIDTH+1, tlen) 1503 cap = ta_log_size_add(cap, ta_slen(lane)) 1504 cap = ta_log_size_add(cap, ta_slen(phase)) 1505 cap = ta_log_size_add(cap, ta_slen(a)) 1506 cap = ta_log_size_add(cap, ta_slen(b)) 1507 cap = ta_log_size_add(cap, ta_slen(c)) 1508 cap = ta_log_size_add(cap, ta_slen(d)) 1509 cap = ta_log_size_add(cap, ta_slen(context)) 1510 cap = ta_log_size_add(cap, ta_slen(origin)) 1511 cap = ta_log_size_add(cap, ta_slen(e)) 1512 cap = ta_log_size_add(cap, ta_slen(f)) 1513 cap = ta_log_size_add(cap, ta_slen(g)) 1514 cap = ta_log_size_add(cap, ta_slen(h)) 1515 cap = ta_log_size_add(cap, ta_slen(k)) 1516 cap = ta_log_size_add(cap, ta_slen(tail)) 1517 if cap < 0 { return 0-1 } 1518 let row: *u8 = sys_mmap(cap) 1519 if (row as i64) <= 0 { return 0-1 } 1520 var n: i64 = ta_catn(row, 0, sys_now_realtime_sec()) 1521 n = ta_cat(row, n, a); n = ta_catb(row, n, tool, tlen) 1522 n = ta_cat(row, n, b); n = ta_cat(row, n, phase) 1523 n = ta_cat(row, n, c); n = ta_cat(row, n, lane) 1524 n = ta_cat(row, n, d); n = ta_catn(row, n, job) 1525 var request_pid: i64 = TT_UNKNOWN 1526 var request_begin: i64 = TT_UNKNOWN 1527 if tt_context_active == 1 { request_begin = tt_read_begin } 1528 if tt_request_pid > 0 { request_pid = tt_request_pid } 1529 n = ta_cat(row, n, context); n = ta_catn(row, n, request_pid) 1530 n = ta_cat(row, n, origin); n = ta_catn(row, n, request_begin) 1531 n = ta_cat(row, n, e); n = ta_catn(row, n, begin) 1532 n = ta_cat(row, n, f); n = ta_catn(row, n, end) 1533 n = ta_cat(row, n, g); n = ta_catn(row, n, ta_timing_elapsed(begin, end)) 1534 n = ta_cat(row, n, h); n = ta_catn(row, n, code) 1535 n = ta_cat(row, n, k); n = ta_catn(row, n, bytes) 1536 n = ta_cat(row, n, tail) 1537 let result: i64 = ta_journal_append(path, row, n) 1538 sys_munmap(row, cap) 1539 return result 1540} 1541func ta_timing_emit(tool: *u8, tlen: i64, lane: *u8, phase: *u8, job: i64, begin: i64, end: i64, code: i64, bytes: i64) -> i64 { 1542 return ta_timing_emit_to(0 as *u8, tool, tlen, lane, phase, job, begin, end, code, bytes) 1543} 1544func ta_timing_begin() -> i64 { 1545 tt_tool = 0 as *u8; tt_tool_n = 0; tt_job = TT_UNKNOWN 1546 tt_request_pid = TT_UNKNOWN; tt_context_active = 1; tt_runner_present = 0 1547 tt_read_begin = ta_timing_now_us(); tt_read_end = TT_UNKNOWN 1548 tt_handle_begin = TT_UNKNOWN; tt_handle_end = TT_UNKNOWN 1549 tt_write_begin = TT_UNKNOWN; tt_write_end = TT_UNKNOWN; tt_write_rc = TT_UNKNOWN 1550 tt_reply_kind = "response_write_return" as *u8 1551 return 0 1552} 1553func ta_timing_bind(tool: *u8, n: i64) -> i64 { tt_tool = tool; tt_tool_n = n; return 0 } 1554func ta_timing_read_done() -> i64 { tt_read_end = ta_timing_now_us(); return 0 } 1555func ta_timing_handle_begin() -> i64 { tt_handle_begin = ta_timing_now_us(); return 0 } 1556func ta_timing_handle_done() -> i64 { tt_handle_end = ta_timing_now_us(); return 0 } 1557func ta_timing_write_begin() -> i64 { tt_write_begin = ta_timing_now_us(); return 0 } 1558func ta_timing_runner(begin: i64, end: i64, code: i64, bytes: i64) -> i64 { 1559 tt_runner_begin = begin; tt_runner_end = end; tt_runner_code = code; tt_runner_bytes = bytes 1560 tt_runner_present = 1; return 0 1561} 1562func ta_timing_reply_prepare(code: i64) -> i64 { 1563 tt_write_end = ta_timing_now_us(); tt_write_rc = code 1564 if ta_pp_pending == 1 { tt_job = ta_pp_jid; tt_reply_kind = "promoted_ack_write_return" as *u8 } 1565 if ta_ap_pending == 1 { tt_job = ta_ap_jid; tt_reply_kind = "async_ack_write_return" as *u8 } 1566 return 0 1567} 1568func ta_timing_reply_log(bytes: i64) -> i64 { 1569 // Run after existing deferred finish functions; tracing cannot delay drainer creation. 1570 if (tt_tool as i64) <= 0 { return 0 } 1571 if tt_tool_n <= 0 { return 0 } 1572 if tt_runner_present == 1 { 1573 var phase: *u8 = "sync_runner_return" as *u8 1574 if tt_runner_code == TR_PROMOTE { phase = "sync_runner_promoted" as *u8 } 1575 ta_timing_emit(tt_tool, tt_tool_n, "sync", phase, tt_job, tt_runner_begin, tt_runner_end, tt_runner_code, tt_runner_bytes) 1576 } 1577 ta_timing_emit(tt_tool, tt_tool_n, "server", "request_read_return", tt_job, tt_read_begin, tt_read_end, 0, TT_UNKNOWN) 1578 ta_timing_emit(tt_tool, tt_tool_n, "server", "handler_response_return", tt_job, tt_handle_begin, tt_handle_end, 0, TT_UNKNOWN) 1579 var accepted: i64 = TT_UNKNOWN 1580 if tt_write_rc == 0 { accepted = bytes } 1581 return ta_timing_emit(tt_tool, tt_tool_n, "server", tt_reply_kind, tt_job, tt_write_begin, tt_write_end, tt_write_rc, accepted) 1582} 1583 1584// DI4 CONTRACT SYMBOL (/compare/dataio, 2026-09-05): the Idempotency-Key presenter for tools/call. params._idem names 1585// the request; ta_mcp_call classifies it through nx_toolcall_idem_lib BEFORE any execution and records or releases 1586// the outcome at every exit. Returns 1 with out2 = {offset, len} into body when a key is present, else 0. 1587func ta_idempotency_key(body: *u8, bn: i64, out2: *i64) -> i64 { return ta_json_str(body, bn, "\"_idem\"" as *u8, out2) } 1588 1589// Reason describes the first failed presenter. Other presenters may fail differently. 1590func ta_cap_deny(jb:*u8,b0:i64,tool:*u8,tool_n:i64,granted:i64,presented:i64)->i64{ 1591 var b:i64=b0;var cause:*u8="verification-failed";var auth:i64=0 1592 if presented==0{cause="capability-absent";auth=1}else{ 1593 if granted==CAPT_DENY_MAC{cause="signature-or-format-invalid";auth=1} 1594 if granted==CAPT_DENY_EXP{cause="capability-expired";auth=1} 1595 if granted==CAPT_DENY_TOOL{cause="tool-not-granted";auth=1} 1596 if granted==CAPT_DENY_REVOKED{cause="capability-revoked";auth=1} 1597 } 1598 b=ta_cat(jb,b,",\"error\":{\"code\":-32001,\"message\":\"") 1599 if auth==1{b=ta_cat(jb,b,"Capability denied; correct authorization before retrying.")} 1600 else{b=ta_cat(jb,b,"Capability verification failed; inspect verifier before retrying.")} 1601 b=ta_cat(jb,b,"\",\"data\":{\"reason\":");b=ta_catn(jb,b,0-granted) 1602 b=ta_cat(jb,b,",\"presented\":");b=ta_catn(jb,b,presented) 1603 b=ta_cat(jb,b,",\"tool\":\"");b=ta_json_esc(jb,b,tool,tool_n) 1604 b=ta_cat(jb,b,"\",\"presenters\":[\"query:?cap=\",\"header:X-Nishi-Cap\",\"header:Authorization Bearer\",\"body:params._cap\"],\"classification\":\"") 1605 b=ta_cat(jb,b,cause);b=ta_cat(jb,b,"\",\"reason_scope\":\"") 1606 if presented==0{b=ta_cat(jb,b,"no-presenter")}else{b=ta_cat(jb,b,"first-presenter-failure")} 1607 b=ta_cat(jb,b,"\",\"dispatched\":false,\"retry\":\"") 1608 if auth==1{b=ta_cat(jb,b,"after-auth-correction")}else{b=ta_cat(jb,b,"after-verifier-recovery")} 1609 b=ta_cat(jb,b,"\",\"recovery\":{\"automatic\":false") 1610 if auth==1{ 1611 b=ta_cat(jb,b,",\"owner\":\"management-api\",\"method\":\"POST\",\"path\":\"/api/cap/mint\",\"admin_required\":true,\"confirm\":\"yes\",\"allow\":\"") 1612 b=ta_json_esc(jb,b,tool,tool_n) 1613 b=ta_cat(jb,b,"\",\"preserve_profile_grants\":true,\"preserve_expiry\":true,\"wildcard\":false") 1614 }else{b=ta_cat(jb,b,",\"owner\":\"capability-verifier\"")} 1615 return ta_cat(jb,b,"}}}}") 1616} 1617 1618func ta_mcp_call(prefix: *u8, req: *u8, req_n: i64, body: *u8, bn: i64, jb: *u8, bin: i64) -> i64 { 1619 var b: i64 = bin 1620 let nm2: *i64 = sys_mmap(16) as *i64 1621 if ta_json_str(body, bn, "\"name\"" as *u8, nm2) != 1 { 1622 return ta_cat(jb, b, ",\"error\":{\"code\":-32602,\"message\":\"missing params.name\"}}" as *u8) 1623 } 1624 let nmp: *u8 = ((body as i64) + nm2[0]) as *u8 1625 let nml: i64 = nm2[1] 1626 // load the cap-signing secret: prefer the vault-provisioned keyfile (production); else the dev/gate constant. 1627 let slb: *i64 = sys_mmap(16) as *i64 1628 let secret: *u8 = ta_load_cap_secret(slb) 1629 let secretlen: i64 = slb[0] 1630 let cap2: *i64 = sys_mmap(16) as *i64 1631 var granted: i64 = 0 - 9 // sentinel: no capability presented 1632 var capp: *u8 = 0 as *u8 1633 var capl: i64 = 0 1634 // ---- PRESENTER SELECTION: ANY-GRANTS (root fix 2026-07-30) ---------------------------------------------- 1635 // WAS a FIRST-PRESENT-WINS chain: if the X-Nishi-Cap header was present AT ALL, params._cap was never read -- 1636 // it sat inside the else. That contradicted the contract this file states in TWO places (ta_query_cap: "an 1637 // explicit per-call _cap always wins"; the deny text below: "params._cap ... per-call override, wins over all 1638 // others") and it made a session whose header carries a least-authority READ cap STRUCTURALLY unable to 1639 // escalate for even one call: the deliberate override was silently ignored and the caller got a -32001 1640 // indistinguishable from -- no such tool -- which is the exact trigger for the measured 44pct ssh/PowerShell 1641 // fallback that the ?cap= presenter was built to kill. Same session-starts-denied lockout, different door. 1642 // NOW: collect every presenter, try them in documented precedence order, and let the FIRST ONE THAT ACTUALLY 1643 // GRANTS THIS TOOL authorize the call. This CANNOT widen authority -- each candidate is still HMAC-verified, 1644 // expiry-checked, revocation-checked and least-authority-checked against THIS tool name -- so choosing WHICH 1645 // proven token authorizes can never grant what no presented token carries. It deletes exactly one state: a 1646 // caller holding a valid, granting capability refused because a weaker one happened to arrive first. 1647 // Slots in precedence order: 0 body params._cap (per-call override) | 1 X-Nishi-Cap | 2 Bearer | 3 ?cap= 1648 let ps: *i64 = sys_mmap(64) as *i64 1649 var pz: i64 = 0 1650 while pz < 8 { ps[pz] = 0; pz = pz + 1 } 1651 let pcb: *i64 = sys_mmap(16) as *i64 1652 if ta_json_str(body, bn, "\"_cap\"" as *u8, pcb) == 1 { ps[0] = (body as i64) + pcb[0]; ps[1] = pcb[1] } 1653 if ta_header(req, req_n, "X-Nishi-Cap:" as *u8, cap2) == 1 { ps[2] = (req as i64) + cap2[0]; ps[3] = cap2[1] } 1654 // OAuth 2.1 interop (R2): a STOCK MCP client presents the cap as `Authorization: Bearer <cap>` (RFC6750 1655 // header method) -- an ALTERNATE PRESENTER for the SAME object-capability, no ambient scope, still 1656 // HMAC-verified below, no confused-deputy. Scheme match is case-insensitive per RFC7235. 1657 if ta_header(req, req_n, "Authorization:" as *u8, cap2) == 1 { 1658 let bav: *u8 = ((req as i64) + cap2[0]) as *u8 1659 let bavl: i64 = cap2[1] 1660 if bavl > 7 { 1661 let pbl: *u8 = "bearer " as *u8 1662 var bj: i64 = 0 1663 var bok: i64 = 1 1664 while bj < 7 { 1665 var bc: i64 = bav[bj] as i64 1666 if bc >= 65 { if bc <= 90 { bc = bc + 32 } } 1667 if bc != (pbl[bj] as i64) { bok = 0 } 1668 bj = bj + 1 1669 } 1670 if bok == 1 { ps[4] = (bav as i64) + 7; ps[5] = bavl - 7 } 1671 } 1672 } 1673 // ?cap=<token> on the request line: the session BASELINE, and the one presenter every HTTP client provably 1674 // sends. Lowest precedence by construction -- see ta_query_cap for why this presenter exists. 1675 if ta_query_cap(req, req_n, cap2) == 1 { ps[6] = (req as i64) + cap2[0]; ps[7] = cap2[1] } 1676 var pk: i64 = 0 1677 while pk < 4 { 1678 let pcp: i64 = ps[pk + pk] 1679 let pcl: i64 = ps[pk + pk + 1] 1680 if pcl > 0 { 1681 // remember that SOMETHING was presented: drives data.presented and WHICH deny text the caller gets 1682 // (no-capability and wrong-capability have OPPOSITE remedies). 1683 if capl <= 0 { capp = pcp as *u8; capl = pcl } 1684 var pg: i64 = capt_verify(secret, secretlen, pcp as *u8, pcl, nmp, nml, sys_now_realtime_sec()) 1685 // revocation: even a valid, unexpired, tool-granting cap is refused if its nonce is on the denylist 1686 // (kill leaked caps). Checked PER PRESENTER so a revoked one cannot mask a live one behind it. 1687 if pg == CAPT_OK { if ta_nonce_revoked(capt_nonce_of(pcp as *u8, pcl)) == 1 { pg = CAPT_DENY_REVOKED } } 1688 if pg == CAPT_OK { capp = pcp as *u8; capl = pcl; granted = CAPT_OK; pk = 4 } 1689 else { if granted == (0 - 9) { granted = pg } } 1690 } 1691 pk = pk + 1 1692 } 1693 if granted == CAPT_OK { 1694 // Handler clock starts the instant authority is granted -- everything after this point is 1695 // time the CALLER is waiting, which is what the edge's window measures. 1696 let ta_t0: i64 = sys_now_ms() 1697 ta_timing_bind(nmp, nml) 1698 // ---- DI4 IDEMPOTENCY KEY (2026-09-05, /compare/dataio ta_idempotency_key): params._idem names the request; 1699 // the first applied outcome is recorded and a retry under the same name is REPLAYED, never re-executed. 1700 // Decided AFTER authority (a replay still needs a granting cap) and BEFORE any execution or dispatch. Every 1701 // outcome point below records (NEW -> applied) or releases (nothing applied); absent key = unchanged path. 1702 let idb: *i64 = sys_mmap(16) as *i64 1703 var idp: *u8 = 0 as *u8 1704 var idl: i64 = 0 1705 if ta_idempotency_key(body, bn, idb) == 1 { idp = ((body as i64) + idb[0]) as *u8; idl = idb[1] } 1706 let idhex: *u8 = sys_mmap(TI_HEX_CH + 8) 1707 let idrow: *u8 = sys_mmap(TI_ROW_CAP) 1708 idhex[0] = 0 as u8 1709 idrow[0] = 0 as u8 1710 let idst: i64 = ti_classify(TI_LEDGER, TI_DIR, idp, idl, idhex, idrow, TI_ROW_CAP, sys_now_realtime_sec()) 1711 if idst == TI_BADKEY { 1712 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"NX-IDEM REFUSED bad-key: params._idem must be 1..200 printable ASCII characters (33..126, no whitespace) -- a key is a token, not a payload. Nothing was executed.\"}],\"isError\":true,\"_meta\":{\"idem\":\"BADKEY\"}}}" as *u8) 1713 return b 1714 } 1715 if idst == TI_INFLIGHT { 1716 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"NX-IDEM IN-FLIGHT key=" as *u8) 1717 b = ta_json_esc(jb, b, idp, idl) 1718 b = ta_cat(jb, b, " -- another call holding this key has reserved it and not yet recorded an outcome (claim age " as *u8) 1719 b = ta_catn(jb, b, ti_claim_age(TI_DIR, idhex, sys_now_realtime_sec())) 1720 b = ta_cat(jb, b, " s). NOT re-executed. Retry with the same key once it records; if the age keeps growing the holder died between reserve and record: adjudicate the artifact and use a new key.\"}],\"isError\":false,\"_meta\":{\"idem\":\"INFLIGHT\"}}}" as *u8) 1721 return b 1722 } 1723 if idst == TI_EVIDENCE_ERROR { 1724 return ta_cat(jb,b,",\"error\":{\"code\":-32000,\"message\":\"idempotency evidence unavailable; request not executed\",\"data\":{\"state\":\"EVIDENCE_UNAVAILABLE\",\"dispatched\":false,\"next\":\"reconcile unreadable, incomplete or missing retained ledger before retry\"}}}") 1725 } 1726 if idst == TI_REPLAY { 1727 let rjid: i64 = ti_row_int(idrow, "job=" as *u8) 1728 if rjid >= 0 { 1729 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"NX-IDEM REPLAY key=" as *u8) 1730 b = ta_json_esc(jb, b, idp, idl) 1731 b = ta_cat(jb, b, " first_outcome=" as *u8) 1732 b = ta_json_esc(jb, b, idrow, ta_slen(idrow)) 1733 b = ta_cat(jb, b, " -- this key was already applied and its job is real: NOT re-executed. Read _jobs/job_<id>.claim then .out for that id exactly as for a JOB-STARTED reply.\"}],\"isError\":false,\"_meta\":{\"job\":" as *u8) 1734 b = ta_catn(jb, b, rjid) 1735 b = ta_cat(jb, b, ",\"state\":\"REPLAY\",\"idem\":\"REPLAY\"}}}" as *u8) 1736 return b 1737 } 1738 let rop: *u8 = sys_mmap(TI_PATH_CAP) 1739 ti_out_path(TI_DIR, idhex, rop) 1740 let rlb: *i64 = sys_mmap(16) as *i64 1741 let rbuf: *u8 = sys_read_file(rop, rlb) 1742 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"" as *u8) 1743 if (rbuf as i64) != 0 { b = ta_json_esc(jb, b, rbuf, rlb[0]) } 1744 b = ta_cat(jb, b, " [NX-IDEM REPLAY key=" as *u8) 1745 b = ta_json_esc(jb, b, idp, idl) 1746 b = ta_cat(jb, b, " first_outcome=" as *u8) 1747 b = ta_json_esc(jb, b, idrow, ta_slen(idrow)) 1748 if (rbuf as i64) == 0 { b = ta_cat(jb, b, " REPLAY-ARTIFACT-GONE: the recorded output file is no longer on disk, the outcome above is the whole record" as *u8) } 1749 b = ta_cat(jb, b, " -- NOT re-executed]\"}],\"isError\":false,\"_meta\":{\"exit_code\":" as *u8) 1750 b = ta_catn(jb, b, ti_row_int(idrow, "exit=" as *u8)) 1751 b = ta_cat(jb, b, ",\"idem\":\"REPLAY\"}}}" as *u8) 1752 return b 1753 } 1754 // ---- R2 EXECUTE: the capability said WHO may call; the execution allowlist (nx_tool_exec_allow) says 1755 // WHICH vetted, gate-GREEN ELF actually runs (defense in depth, fail-closed). Only a GREEN-allowlisted 1756 // tool is forked+captured; anything else is authorized-but-not-runnable. never-brick: name-only, no path, 1757 // no /bin/sh -- a bad/absent row can only refuse. Capture is bounded (64KB) so the escaped result always 1758 // fits the 1MB jb buffer. Args come from params.arguments.argv (parsed just below into a native argv). 1759 let xcap: i64 = TA_CAPTURE_CAP 1760 let xout: *u8 = sys_mmap(xcap) 1761 let xlen: *i64 = sys_mmap(16) as *i64 1762 let xrc: *i64 = sys_mmap(16) as *i64 1763 // R2 ARGS: parse optional params.arguments.argv (JSON string array) into a native argv vector so 1764 // multi-arg organs are callable over MCP. av[0] is reserved for the resolved ELF path (filled by 1765 // tea_run_argv); parsed args land at av[1..]. Absent/empty argv -> av = [path, 0] = argless, i.e. 1766 // byte-for-byte the prior behavior, so already-working no-arg tools are unaffected (back-compat). 1767 let av: *i64 = sys_mmap(TA_MAGIC_8192) as *i64 1768 // ARGV SCRATCH SIZED FROM THE BODY (2026-08-23): the unescaped argv can never exceed the body that 1769 // carried it, so bn + 1 is the exact bound. The former 64 KiB scratch let ta_json_str_array keep "what 1770 // fit": a file content above 65535 bytes arrived as its prefix and nx_fs_write wrote the truncated file 1771 // with an OK receipt -- silent data loss wearing a constant, behind the serve loop's 64 KiB read window. 1772 let sbcap: i64 = bn + 1 1773 let sb: *u8 = sys_mmap(sbcap) 1774 if (av as i64) <= 0 { 1775 if idst == TI_NEW { ti_release(TI_DIR, idhex) } 1776 return ta_cat(jb, b, ",\"error\":{\"code\":-32603,\"message\":\"argv allocation failed; nothing executed\"}}" as *u8) 1777 } 1778 if (sb as i64) <= 0 { 1779 if idst == TI_NEW { ti_release(TI_DIR, idhex) } 1780 return ta_cat(jb, b, ",\"error\":{\"code\":-32603,\"message\":\"argv allocation failed; nothing executed\"}}" as *u8) 1781 } 1782 let ac: i64 = ta_json_str_array(body, bn, "\"argv\"" as *u8, av, 1, 250, sb, sbcap) 1783 if ac < 0 { 1784 if idst == TI_NEW { ti_release(TI_DIR, idhex) } 1785 return ta_cat(jb, b, ",\"error\":{\"code\":-32602,\"message\":\"argv must be a complete string array with valid Unicode, no NUL and within argument/output capacity; nothing executed\"}}" as *u8) 1786 } 1787 av[1 + ac] = 0 1788 // ---- HTTP-BACKEND dispatch (data-driven, fail-closed, SSRF-safe): a tool declared in tool_backends.conf 1789 // is an already-running sovereign HTTP service reached IN-PROCESS over loopback -- no shim binary. Tried 1790 // BEFORE fork-exec; THB_NOROUTE (not a backend) falls straight through to the fork-exec path below, 1791 // byte-for-byte unchanged for organ tools (never-brick preserving). 1792 let hbcap: i64 = TA_MAGIC_1048576 1793 let hbout: *u8 = sys_mmap(hbcap) 1794 let hblen: *i64 = sys_mmap(16) as *i64 1795 let hbrc: i64 = thb_dispatch(nmp, nml, av, ac, hbout, hbcap, hblen) 1796 if hbrc == THB_OK { 1797 ta_actlog(nmp, nml, "http" as *u8, "ok" as *u8, 0, hblen[0], sys_now_ms() - ta_t0) 1798 // DI4: a keyed HTTP-backend call records its bytes and outcome so a retry under the same key replays them 1799 if idst == TI_NEW { let hop: *u8 = sys_mmap(TI_PATH_CAP); ti_out_path(TI_DIR, idhex, hop); ta_job_put(hop, hbout, hblen[0]); let hrow: *u8 = sys_mmap(TI_ROW_CAP); let hrl: i64 = ti_row_sync(hrow, TI_ROW_CAP, "http" as *u8, nmp, nml, 0, hblen[0], TI_DIR, idhex); ti_record(TI_LEDGER, idp, idl, hrow, hrl) } 1800 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"" as *u8) 1801 b = ta_json_esc(jb, b, hbout, hblen[0]) 1802 b = ta_cat(jb, b, "\"}],\"isError\":false,\"_meta\":{\"backend\":\"http\",\"bytes\":" as *u8) 1803 b = ta_catn(jb, b, hblen[0]) 1804 b = ta_cat(jb, b, "}}}" as *u8) 1805 return b 1806 } 1807 if hbrc == THB_ERR { 1808 if idst == TI_NEW { ti_release(TI_DIR, idhex) } // DI4: nothing was applied, so the key must be judged afresh 1809 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"tool '" as *u8) 1810 b = ta_json_esc(jb, b, nmp, nml) 1811 b = ta_cat(jb, b, "' HTTP backend is declared but unreachable\"}],\"isError\":true}}" as *u8) 1812 return b 1813 } 1814 // ---- ASYNC JOB LANE (rung 24; eats seq1273, defuses the seq1261 1815 // trigger): a long organ run cannot live inside the synchronous 1816 // response window -- the edge read-timeout drops the response and the 1817 // daemon then dies writing to the dead socket (the crash-loop class). 1818 // "_async":"1" anywhere in the body forks the SAME cap+allowlist-gated 1819 // pinned dispatch DETACHED (double-fork, no zombies): stdout lands in 1820 // _jobs/job_<id>.out, then _jobs/job_<id>.done lands ATOMICALLY 1821 // (tmp+rename, written LAST = the completion marker). The call returns 1822 // the job id IMMEDIATELY; poll with the already-registered nx_fs 1823 // (read _jobs/job_<id>.done then .out) -- no new poll surface. 1824 let asyb: *i64 = sys_mmap(16) as *i64 1825 var isasync: i64 = 0 1826 if ta_json_str(body, bn, "\"_async\"" as *u8, asyb) == 1 { if body[asyb[0]] == (49 as u8) { isasync = 1 } } 1827 // ---- ASYNC-ONLY ENFORCEMENT (seq1293, incident 2026-07-29): the DAEMON owns the envelope, 1828 // so the daemon decides -- a tool listed in async_only_tools.conf (one exact name per line, 1829 // hot-read per call like the cap) is FORCED onto the job lane even when the caller forgot 1830 // _async. The 2026-07-29 outage trigger was exactly a heavy sweep running sync, outrunning 1831 // the edge window, and being retried. Absent/empty conf = byte-identical prior behavior. 1832 if isasync == 0 { 1833 let aosz: *i64 = sys_mmap(16) as *i64 1834 let aob: *u8 = sys_read_file("async_only_tools.conf" as *u8, aosz) 1835 if (aob as i64) != 0 { if aosz[0] > 0 { 1836 if ta_async_match(aob, aosz[0], nmp, nml, av, ac) == 1 { isasync = 1 } 1837 } } 1838 } 1839 // Reserve before execution, including the sync lane that may later promote. 1840 // Budget is the existing edge reply reserve; collisions never imply ownership. 1841 let claim_begin:i64=sys_now_ms() 1842 let claim_budget:i64=tea_reply_reserve_ms() 1843 let claim_info:*i64=sys_mmap(32) as *i64 1844 claim_info[0]=0;claim_info[1]=0;claim_info[2]=0;claim_info[3]=0 1845 let jpath:*u8=sys_mmap(256);let jout2:*u8=sys_mmap(256);let jtmp:*u8=sys_mmap(256) 1846 let claim_line:*u8=sys_mmap(JR_LINE) 1847 var jid:i64=JR_ERR_INVALID 1848 let mkdir_rc:i64=sys_mkdir("_jobs",MODE_0755) 1849 if mkdir_rc==0 || mkdir_rc==JR_ERR_EXIST { 1850 if claim_budget>0 && claim_begin<=JR_ID_MAX-claim_budget { 1851 jid=jr_reserve(JR_JOBS,sys_now_realtime_sec(),claim_begin+claim_budget,jpath,256,claim_line,JR_LINE,claim_info) 1852 } 1853 }else{jid=mkdir_rc} 1854 let claim_elapsed:i64=sys_now_ms()-claim_begin 1855 if jid<=0 { 1856 if idst==TI_NEW{ti_release(TI_DIR,idhex)} 1857 b=ta_cat(jb,b,",\"error\":{\"code\":-32003,\"message\":\"Job reservation failed before execution; inspect filesystem errno or configured reply budget\",\"data\":{\"executed\":false,\"reservation_errno\":") 1858 b=ta_catn(jb,b,jid) 1859 b=ta_cat(jb,b,",\"reserved_unstarted_id\":");b=ta_catn(jb,b,claim_info[0]) 1860 b=ta_cat(jb,b,",\"reservation_elapsed_ms\":");b=ta_catn(jb,b,claim_elapsed) 1861 return ta_cat(jb,b,"}}}") 1862 } 1863 jr_mkpath(jout2,256,JR_JOBS,jid,JR_EXT_OUT) 1864 jr_mkpath(jtmp,256,JR_JOBS,jid,".tmp") 1865 if isasync == 1 { 1866 // DI4: the job id IS the outcome of a keyed async call -- recorded the instant it is reserved, BEFORE the fork 1867 // and before the reply, so a lost reply is replayed with the same id instead of a second execution. 1868 if idst == TI_NEW { let arow: *u8 = sys_mmap(TI_ROW_CAP); let arl: i64 = ti_row_job(arow, TI_ROW_CAP, "async" as *u8, nmp, nml, jid); ti_record(TI_LEDGER, idp, idl, arow, arl) } 1869 let pid1: i64 = sys_fork() 1870 if pid1 == 0 { 1871 // middle child: shed every inherited fd above stderr (listen + 1872 // conn sockets included) so a detached job can never pin the 1873 // daemon's ports; then detach the worker and exit at once. 1874 var fdc: i64 = 3 1875 while fdc < 256 { sys_close(fdc); fdc = fdc + 1 } 1876 // ★LEAVE THE DAEMON'S SESSION AND PROCESS GROUP (seq1611). 1877 // Closing fds and double-forking reparents the worker to init but 1878 // leaves it in the DAEMON'S process group, so any group-directed 1879 // signal -- a supervisor cycling the daemon, a request-child sweep 1880 // -- kills it mid-run. MEASURED: short jobs recorded exit=0 and 1881 // finished, while the multi-minute regen died after its first two 1882 // output lines EVERY time, with an EMPTY exit slot (= terminated by 1883 // a signal, not an exit). A detached job must own its own session, 1884 // exactly as hc_spawn_tapi already does for the daemon itself. 1885 nx_setsid() 1886 let pid2: i64 = sys_fork() 1887 if pid2 != 0 { sys_exit(0) } 1888 // seq1301: job output lands on DISK (no json-escape bound applies) -> full 1MiB capture, 1889 // and a clipped capture is MARKED inside the .out itself so a poller can never mistake 1890 // a truncated result for a complete one. 1891 let jbuf: *u8 = sys_mmap(TA_MAGIC_1048576) 1892 let jlen: *i64 = sys_mmap(16) as *i64 1893 let jrc: *i64 = sys_mmap(16) as *i64 1894 // tmo=0: NO TIMEOUT. A detached job exists precisely to outlive 1895 // the synchronous window; inheriting the 120s request timeout 1896 // killed every multi-minute regen at the 2-minute mark (seq1634). 1897 let tt_exec_begin: i64 = ta_timing_now_us() 1898 let jec: i64 = tea_run_pinned_to(nmp, nml, av, jbuf, TA_MAGIC_1048576, jlen, jrc, 0) 1899 let tt_exec_end: i64 = ta_timing_now_us() 1900 if jlen[0] >= TA_MAGIC_1048576 { 1901 let tm: *u8 = "\n[NX-JOB CAPTURE-TRUNCATED at 1048576 bytes -- output incomplete]\n" as *u8 1902 var tl: i64 = 0 1903 while tm[tl] != (0 as u8) { tl = tl + 1 } 1904 var cw: i64 = 0 1905 while cw < tl { if jlen[0] - tl + cw >= 0 { jbuf[jlen[0] - tl + cw] = tm[cw] } cw = cw + 1 } 1906 } 1907 let dbuf: *u8 = sys_mmap(256) 1908 var db: i64 = ta_cat(dbuf, 0, "state=DONE rc=" as *u8) 1909 db = ta_catn(dbuf, db, jrc[0]) 1910 db = ta_cat(dbuf, db, " exit=" as *u8) 1911 db = ta_catn(dbuf, db, jec) 1912 db = ta_cat(dbuf, db, " bytes=" as *u8) 1913 db = ta_catn(dbuf, db, jlen[0]) 1914 db = ta_cat(dbuf, db, "\n" as *u8) 1915 let tt_publish_begin: i64 = ta_timing_now_us() 1916 let published: i64 = ta_job_publish(jout2, jbuf, jlen[0], jtmp, dbuf, db, jpath) 1917 let tt_publish_end: i64 = ta_timing_now_us() 1918 ta_timing_emit(nmp, nml, "async", "async_runner_return", jid, tt_exec_begin, tt_exec_end, jec, jlen[0]) 1919 ta_timing_emit(nmp, nml, "async", "job_publish_return", jid, tt_publish_begin, tt_publish_end, published, jlen[0]) 1920 if published != 0 { 1921 sys_exit(1) 1922 return 0 1923 } 1924 sys_exit(0) 1925 return 0 1926 } 1927 // SEV-9 2026-09-05: REPLY FIRST. The job id is real (the claim is on disk) and the runner is forked; the 1928 // middle-child reap and the fsync'd actlog row are what outlived the edge window under a storm and cost 1929 // every seat its receipt. Under the serve loop's deferral contract they are parked and drained by 1930 // ta_async_finish() AFTER the JOB-STARTED bytes are on the wire; a caller that did not opt in keeps 1931 // the inline order byte-for-byte. 1932 if ta_defer_ok == 1 { 1933 ta_ap_pid = pid1; ta_ap_nmp = nmp as i64; ta_ap_nml = nml; ta_ap_jid = jid; ta_ap_t0 = ta_t0 1934 ta_ap_pending = 1 1935 } else { 1936 if pid1 > 0 { 1937 let jst: *i64 = sys_mmap(16) as *i64 1938 sys_wait4(pid1, jst, 0) 1939 } 1940 ta_actlog(nmp, nml, "async" as *u8, "started" as *u8, 0, jid, sys_now_ms() - ta_t0) 1941 } 1942 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"JOB-STARTED id=" as *u8) 1943 b = ta_catn(jb, b, jid) 1944 b = ta_cat(jb, b, " tool=" as *u8) 1945 b = ta_json_esc(jb, b, nmp, nml) 1946 b = ta_cat(jb, b, " poll: _jobs/job_<id>.out is the OUTPUT, and _jobs/job_<id>.claim is the TERMINAL MARKER this lane writes atomically (temp+rename) when the job ends: ABSENT = still running; state=DONE rc= exit= bytes= = FINISHED, and bytes=0 there means the tool genuinely produced NOTHING rather than that it is still working. READ .claim BEFORE treating an empty .out as RUNNING -- that ambiguity is how a job that died and a job still thinking became the same observation. .done is never written.\"}],\"isError\":false,\"_meta\":{\"job\":" as *u8) 1947 b = ta_catn(jb, b, jid) 1948 b = ta_cat(jb, b, ",\"state\":\"RUNNING\"}}}" as *u8) 1949 return b 1950 } 1951 // SYNCHRONOUS LANE (root fix 2026-07-30, ws=sev-eater). A client is waiting behind the 1952 // edge's MEASURED 15.02s window, so this call gets the SYNC budget -- not the 120s default 1953 // that tea_run_pinned carries for the async lane. Holding a worker slot past the window 1954 // cannot deliver an answer to anyone; it only starves the bounded 16-worker pool, which is 1955 // exactly how a trickle of hung calls took the whole agent surface down for every seat 1956 // (measured: handler in pipe_wait behind an nx_mgmt_call parked in sk_wait_data, 10 stuck 1957 // handler pairs, 4 of 8 requests 503ing). This is the SYNC half of the seq1611/1634 fix: 1958 // that arc correctly gave the DETACHED lane an unbounded budget; the synchronous lane still 1959 // needed one BOUNDED BY THE CALLER'S PATIENCE. Same primitive, opposite direction. 1960 // PROMOTABLE (2026-08-22). The old sync lane, at its deadline, forked a watchdog that SIGKILLed 1961 // the worker -- DESTROYING THE ANSWER of every call that outran the edge window while the work 1962 // ran on and its side effects landed. Two lies followed: the edge synthesised a 503 (work landed, 1963 // caller told it failed), and -- worse -- this handler emitted isError:false with a -5 exit for a 1964 // KILLED organ (success reported for destroyed work). Now the deadline PROMOTES: tr_run_capture_ 1965 // deadline hands back the LIVE worker + pipe and we adopt it onto the SAME job lane the async path 1966 // uses, so the caller gets a JOB-STARTED handle instead of a 503 and the worker finishes. 1967 // Leak-free BY THE SERVE MODEL: tsv_serve is fork-per-request and the request child exit_group's 1968 // after responding, so the worker and the detached drainer reparent to init, which reaps them. 1969 // The deadline is DERIVED live from edge_window.conf (window - reserve), never a typed literal. 1970 let promsrc: *i64 = sys_mmap(16) as *i64 1971 let promote_at: i64 = tea_sync_promote_ms(promsrc) 1972 let wpid: *i64 = sys_mmap(16) as *i64 1973 let wrfd: *i64 = sys_mmap(16) as *i64 1974 let tt_exec_begin: i64 = ta_timing_now_us() 1975 let ec: i64 = tea_run_pinned_deadline(nmp, nml, av, xout, xcap, xlen, xrc, promote_at, wpid, wrfd) 1976 let tt_exec_end: i64 = ta_timing_now_us() 1977 ta_timing_runner(tt_exec_begin, tt_exec_end, ec, xlen[0]) 1978 // Inline runs finalize their already-owned claim, preserving the same result handle 1979 // used by async/promotion. A publication error cannot relabel executed work unexecuted. 1980 var inline_published:i64=0 1981 if ec!=TR_PROMOTE { 1982 var cn:i64=ta_cat(claim_line,0,"state=DONE rc=");cn=ta_catn(claim_line,cn,xrc[0]) 1983 cn=ta_cat(claim_line,cn," exit=");cn=ta_catn(claim_line,cn,ec) 1984 cn=ta_cat(claim_line,cn," bytes=");cn=ta_catn(claim_line,cn,xlen[0]);claim_line[cn]=10;cn=cn+1 1985 if ta_job_publish(jout2,xout,xlen[0],jtmp,claim_line,cn,jpath)==0{inline_published=1} 1986 } 1987 if xrc[0] == TEA_OK { 1988 if ec == TR_PROMOTE { 1989 let jidp:i64=jid 1990 let jpathp:*u8=jpath 1991 let joutp:*u8=jout2 1992 let jtmpp:*u8=jtmp 1993 let precap: i64 = xlen[0] // bytes captured BEFORE promotion; the drainer prepends them to the tail 1994 // DI4: a keyed sync call that outran the window is now a job -- its id is the outcome, recorded before the reply 1995 if idst == TI_NEW { let prow: *u8 = sys_mmap(TI_ROW_CAP); let prl: i64 = ti_row_job(prow, TI_ROW_CAP, "promote" as *u8, nmp, nml, jidp); ti_record(TI_LEDGER, idp, idl, prow, prl) } 1996 // REPLY FIRST (debt 1787505278). Everything above this line is what the reply NEEDS (a 1997 // reserved, real job id). Everything the drainer needs is parked; the fork/wait4/actlog 1998 // run in ta_promote_finish() AFTER the serve loop has put the JOB-STARTED bytes on the 1999 // wire. A caller that did not sign the contract gets the old inline ordering, unchanged. 2000 let tdecide: i64 = sys_now_ms() 2001 if ta_defer_ok == 1 { 2002 ta_pp_nmp = nmp as i64; ta_pp_nml = nml; ta_pp_jid = jidp 2003 ta_pp_jpath = jpathp as i64; ta_pp_jout = joutp as i64; ta_pp_jtmp = jtmpp as i64 2004 ta_pp_xout = xout as i64; ta_pp_precap = precap; ta_pp_wrfd = wrfd[0] 2005 ta_pp_t0 = ta_t0; ta_pp_tdecide = tdecide 2006 ta_pp_pending = 1 2007 } else { 2008 ta_promote_bookkeep(nmp, nml, jidp, jpathp, joutp, jtmpp, xout, precap, wrfd[0], ta_t0, TA_REPLY_MS_UNOBSERVED) 2009 } 2010 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"JOB-STARTED id=" as *u8) 2011 b = ta_catn(jb, b, jidp) 2012 b = ta_cat(jb, b, " tool=" as *u8) 2013 b = ta_json_esc(jb, b, nmp, nml) 2014 b = ta_cat(jb, b, " -- this sync call outran the edge window and was PROMOTED to the job lane, NOT killed. Completion marker: _jobs/job_<id>.claim (state=CLAIMED -> state=DONE); payload: _jobs/job_<id>.out.\"}],\"isError\":false,\"_meta\":{\"job\":" as *u8) 2015 b = ta_catn(jb, b, jidp) 2016 b = ta_cat(jb, b, ",\"state\":\"RUNNING\",\"promoted\":1,\"budget_ms\":" as *u8) 2017 b = ta_catn(jb, b, promote_at) 2018 b = ta_cat(jb, b, ",\"budget_src\":" as *u8) 2019 if promsrc[0] == TEA_SRC_CONF { b = ta_cat(jb, b, "\"conf\"" as *u8) } else { b = ta_cat(jb, b, "\"default\"" as *u8) } 2020 b = ta_cat(jb, b, "}}}" as *u8) 2021 return b 2022 } 2023 // 2a THE SUCCESS-ENVELOPE HONESTY FIX. ec is the RETURN value (child exit 0..255, or a NEGATIVE 2024 // harness sentinel: -2 pipe, -3 fork, -4 wait, -5 timeout, -6 chdir). The old code branched only 2025 // on xrc[0] (the RESOLVE code, set BEFORE the fork), so a killed/failed run was emitted as 2026 // isError:false with a negative exit_code -- the daemon reporting SUCCESS for work it DESTROYED. 2027 // isError keys on the harness sentinels (ec < 0), NEVER on the organ's own exit: a gate exiting 2028 // 1 for RED is a correct answer, not an error. TR_PROMOTE (-7) is handled above, so it is not here. 2029 if ec < 0 { 2030 ta_actlog(nmp, nml, "sync" as *u8, "harness-fail" as *u8, ec, xlen[0], sys_now_ms() - ta_t0) 2031 if idst == TI_NEW { ti_release(TI_DIR, idhex) } // DI4: the harness failed, nothing is known to have applied -- the key is judged afresh 2032 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"the tool run did not complete inside the daemon (harness code " as *u8) 2033 b = ta_catn(jb, b, ec) 2034 b = ta_cat(jb, b, ": -5 deadline, -2 pipe, -3 fork, -4 wait, -6 chdir). This is NOT the organ exit code and NOT a success -- re-run on the async lane (_async=1) for unbounded work.\"}],\"isError\":true,\"_meta\":{\"harness_code\":" as *u8) 2035 b = ta_catn(jb, b, ec) 2036 b = ta_cat(jb, b, "}}}" as *u8) 2037 return ta_claim_meta(jb,b,jid,claim_elapsed,inline_published,1) 2038 } 2039 ta_actlog(nmp, nml, "sync" as *u8, "ok" as *u8, ec, xlen[0], sys_now_ms() - ta_t0) 2040 // DI4: a keyed sync call keeps its bytes beside the job artifacts and records exit+bytes+path, so a retry under 2041 // the same key answers the same text (the organ's own exit code, RED included, is an outcome -- it is recorded) 2042 if idst == TI_NEW { let sop: *u8 = sys_mmap(TI_PATH_CAP); ti_out_path(TI_DIR, idhex, sop); ta_job_put(sop, xout, xlen[0]); let srow: *u8 = sys_mmap(TI_ROW_CAP); let srl: i64 = ti_row_sync(srow, TI_ROW_CAP, "sync" as *u8, nmp, nml, ec, xlen[0], TI_DIR, idhex); ti_record(TI_LEDGER, idp, idl, srow, srl) } 2043 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"" as *u8) 2044 b = ta_json_esc(jb, b, xout, xlen[0]) 2045 // seq1301 put capture_truncated in _meta -- correct, and it is STILL emitted below. But MCP 2046 // CLIENTS DO NOT SURFACE _meta: they render result.content[].text ONLY. So the one consumer who 2047 // acts on a truncated result never saw the warning, and the sole symptom was ABSENCE of the 2048 // tool's own trailing footer -- a negative signal no caller knows to look for. Say it IN THE 2049 // TEXT, where every client actually reads. Guarded by the same condition, so untruncated output 2050 // stays byte-identical to before (rule 19). No backslash escapes here on purpose. 2051 if xlen[0] >= xcap { 2052 b = ta_cat(jb, b, " [NX-TRUNCATED OUTPUT-IS-PARTIAL capture_cap=" as *u8) 2053 b = ta_catn(jb, b, xcap) 2054 b = ta_cat(jb, b, " -- narrow the query or re-run with _async=1]" as *u8) 2055 } 2056 b = ta_cat(jb, b, "\"}],\"isError\":false,\"_meta\":{\"exit_code\":" as *u8) 2057 b = ta_catn(jb, b, ec) 2058 b = ta_cat(jb, b, ",\"bytes\":" as *u8) 2059 b = ta_catn(jb, b, xlen[0]) 2060 // seq1301: a full capture buffer means the organ's stdout was (or may have been) CLIPPED -- 2061 // silence here is how a truncated source nearly shipped. Say so, machine-readably. 2062 if xlen[0] >= xcap { 2063 b = ta_cat(jb, b, ",\"capture_truncated\":1,\"capture_cap\":" as *u8) 2064 b = ta_catn(jb, b, xcap) 2065 } 2066 b = ta_cat(jb, b, "}}}" as *u8) 2067 return ta_claim_meta(jb,b,jid,claim_elapsed,inline_published,1) 2068 } 2069 if idst == TI_NEW { ti_release(TI_DIR, idhex) } // DI4: nothing ran, so a keyed retry must be judged afresh 2070 // capability-valid but NOT on the GREEN execution allowlist -> clean TOOL-level error (not a protocol 2071 // error). rc magnitude: 0=blocked(present,non-GREEN) 1=not-in-allowlist 2=no-allowlist-file. 2072 b = ta_cat(jb, b, ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"tool '" as *u8) 2073 b = ta_json_esc(jb, b, nmp, nml) 2074 b = ta_cat(jb, b, "' is capability-authorized but not on the GREEN execution allowlist (rc=" as *u8) 2075 b = ta_catn(jb, b, 0 - xrc[0]) 2076 b = ta_cat(jb, b, ")\"}],\"isError\":true}}" as *u8) 2077 return ta_claim_meta(jb,b,jid,claim_elapsed,inline_published,0) 2078 } 2079 var presented:i64=0;if capl>0{presented=1} 2080 return ta_cap_deny(jb,b,nmp,nml,granted,presented) 2081} 2082 2083// R3: MCP resources/read -- serve READABLE content (not tool execution). Two resources: the LIVE ecosystem maturity 2084// grade (forks the allowlisted rollup, escapes its output) and the operating doctrine (embedded). Public read-only 2085// (non-sensitive, no side effects); resource not found -> -32002. Composes with the nx_ecosystem_maturity_rollup tool. 2086func ta_mcp_resread(body: *u8, bn: i64, jb: *u8, b0: i64) -> i64 { 2087 var b: i64 = b0 2088 let u2: *i64 = sys_mmap(16) as *i64 2089 if ta_json_str(body, bn, "\"uri\"" as *u8, u2) == 0 { 2090 return ta_cat(jb, b, ",\"error\":{\"code\":-32602,\"message\":\"resources/read requires params.uri\"}}" as *u8) 2091 } 2092 let up: *u8 = ((body as i64) + u2[0]) as *u8 2093 let ul: i64 = u2[1] 2094 if ta_indexof(up, ul, "ecosystem-maturity" as *u8) >= 0 { 2095 let xcap: i64 = TA_MAGIC_65536 2096 let xout: *u8 = sys_mmap(xcap) 2097 let xlen: *i64 = sys_mmap(16) as *i64 2098 let xrc: *i64 = sys_mmap(16) as *i64 2099 let av: *i64 = sys_mmap(64) as *i64 2100 av[1] = 0 2101 tea_run_pinned("nx_ecosystem_maturity_rollup" as *u8, 28, av, xout, xcap, xlen, xrc) 2102 b = ta_cat(jb, b, ",\"result\":{\"contents\":[{\"uri\":\"nishi://sota/ecosystem-maturity\",\"mimeType\":\"text/plain\",\"text\":\"" as *u8) 2103 if xrc[0] == TEA_OK { b = ta_json_esc(jb, b, xout, xlen[0]) } else { b = ta_cat(jb, b, "(maturity rollup unavailable)" as *u8) } 2104 return ta_cat(jb, b, "\"}]}}" as *u8) 2105 } 2106 if ta_indexof(up, ul, "doctrine/operating" as *u8) >= 0 { 2107 b = ta_cat(jb, b, ",\"result\":{\"contents\":[{\"uri\":\"nishi://doctrine/operating\",\"mimeType\":\"text/plain\",\"text\":\"" as *u8) 2108 b = ta_cat(jb, b, "Operate Nishi over sovereign MCP. Tools: nishi_search/nishi_doc (search); nx_status/nx_services/nx_health (status); nx_mgmt (admin: build/deploy/reconcile/restart); nx_ecosystem_maturity_rollup (live grade). Auth: ?cap=token on the MCP URL (session baseline -- use this if your client drops custom headers), X-Nishi-Cap header, Authorization: Bearer cap, or params._cap (per-call, wins over the rest). Build+deploy on the NAS via /api/build + /api/deploy (never-brick, auto-rollback). One-command edit-to-build: nx_ship target tokenfile srcdir." as *u8) 2109 return ta_cat(jb, b, "\"}]}}" as *u8) 2110 } 2111 return ta_cat(jb, b, ",\"error\":{\"code\":-32002,\"message\":\"resource not found\"}}" as *u8) 2112} 2113// R3: MCP prompts/get -- return a reusable prompt template, arguments filled. `operate-nishi` primes an agent on the 2114// sovereign toolset; `ship-target` (arg: target) is a build+deploy prompt. unknown -> -32602. 2115func ta_mcp_promget(body: *u8, bn: i64, jb: *u8, b0: i64) -> i64 { 2116 var b: i64 = b0 2117 let n2: *i64 = sys_mmap(16) as *i64 2118 if ta_json_str(body, bn, "\"name\"" as *u8, n2) == 0 { 2119 return ta_cat(jb, b, ",\"error\":{\"code\":-32602,\"message\":\"prompts/get requires params.name\"}}" as *u8) 2120 } 2121 let np: *u8 = ((body as i64) + n2[0]) as *u8 2122 let nl: i64 = n2[1] 2123 if ta_streq_n(np, nl, "operate-nishi" as *u8) == 1 { 2124 b = ta_cat(jb, b, ",\"result\":{\"description\":\"Prime an agent to operate the Nishi ecosystem over sovereign MCP\",\"messages\":[{\"role\":\"user\",\"content\":{\"type\":\"text\",\"text\":\"" as *u8) 2125 b = ta_cat(jb, b, "You operate the Nishi sovereign ecosystem over MCP; prefer these tools over shell. Search: nishi_search/nishi_doc. Status: nx_status/nx_services/nx_health. Admin build/deploy/reconcile/restart (never-brick): nx_mgmt. Live SOTA grade: nx_ecosystem_maturity_rollup. One-command edit-to-build: nx_ship. Read the resource nishi://doctrine/operating first." as *u8) 2126 return ta_cat(jb, b, "\"}}]}}" as *u8) 2127 } 2128 if ta_streq_n(np, nl, "ship-target" as *u8) == 1 { 2129 let t2: *i64 = sys_mmap(16) as *i64 2130 var tp: *u8 = "TARGET" as *u8 2131 var tl: i64 = 6 2132 if ta_json_str(body, bn, "\"target\"" as *u8, t2) == 1 { tp = ((body as i64) + t2[0]) as *u8; tl = t2[1] } 2133 b = ta_cat(jb, b, ",\"result\":{\"description\":\"Build and deploy a target on the NAS over sovereign APIs\",\"messages\":[{\"role\":\"user\",\"content\":{\"type\":\"text\",\"text\":\"Build and deploy the target '" as *u8) 2134 b = ta_json_esc(jb, b, tp, tl) 2135 b = ta_cat(jb, b, "' on the NAS with ZERO shell: sync+compile via nx_ship (or nx_mgmt POST /api/build target=" as *u8) 2136 b = ta_json_esc(jb, b, tp, tl) 2137 b = ta_cat(jb, b, "), then promote via nx_mgmt POST /api/deploy (never-brick, auto-rollback), and verify with nx_health.\"}}]}}" as *u8) 2138 return b 2139 } 2140 return ta_cat(jb, b, ",\"error\":{\"code\":-32602,\"message\":\"unknown prompt\"}}" as *u8) 2141} 2142func ta_mcp(prefix: *u8, req: *u8, req_n: i64, out: *u8) -> i64 { 2143 let bo: i64 = ta_body_off(req, req_n) 2144 let body: *u8 = ((req as i64) + bo) as *u8 2145 let bn: i64 = req_n - bo 2146 let envelope: *NxMcpEnvelope = sys_mmap(NX_MCP_ENVELOPE_BYTES) as *NxMcpEnvelope 2147 let envelope_kind: i64 = nme_read(body, bn, envelope) 2148 if envelope_kind == 2 { return ta_emit_lit(out, "HTTP/1.1 202 Accepted" as *u8, "" as *u8) } 2149 if envelope_kind != 1 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "" as *u8) } 2150 let idbuf: *u8 = ((body as i64) + envelope.id_off) as *u8 2151 let idlen: i64 = envelope.id_len 2152 var meth: i64 = 0 2153 if envelope_kind == 1 { 2154 let mp: *u8 = ((body as i64) + envelope.method_off) as *u8 2155 let ml: i64 = envelope.method_len 2156 if ta_streq_n(mp, ml, "tools/list" as *u8) == 1 { meth = 2 } else { if ta_streq_n(mp, ml, "initialize" as *u8) == 1 { meth = 1 } else { if ta_streq_n(mp, ml, "tools/call" as *u8) == 1 { meth = 3 } else { if ta_streq_n(mp, ml, "resources/list" as *u8) == 1 { meth = 4 } else { if ta_streq_n(mp, ml, "resources/read" as *u8) == 1 { meth = 5 } else { if ta_streq_n(mp, ml, "prompts/list" as *u8) == 1 { meth = 6 } else { if ta_streq_n(mp, ml, "prompts/get" as *u8) == 1 { meth = 7 } } } } } } } 2157 } 2158 let jb: *u8 = sys_mmap(TA_MAGIC_1048576) 2159 var b: i64 = ta_cat(jb, 0, "{\"jsonrpc\":\"2.0\",\"id\":" as *u8) 2160 b = ta_catb(jb, b, idbuf, idlen) 2161 if meth == 1 { 2162 b = ta_cat(jb, b, ",\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"resources\":{\"listChanged\":false},\"prompts\":{\"listChanged\":false}},\"serverInfo\":{\"name\":\"nishi-tools\",\"version\":\"1\",\"listOrder\":\"cap-first,most-called,newest\"}}}" as *u8) 2163 } else { if meth == 2 { 2164 // MCP cursor pagination: params.cursor is a numeric offset; emit a page + nextCursor iff more remain. 2165 var pgoff: i64 = 0 2166 let cb2: *i64 = sys_mmap(16) as *i64 2167 if ta_json_raw(body, bn, "\"cursor\"" as *u8, cb2) == 1 { pgoff = tr_atoi(body, cb2[0], cb2[1]) } 2168 let moreb: *i64 = sys_mmap(16) as *i64 2169 moreb[0] = 0 2170 b = ta_cat(jb, b, ",\"result\":{\"tools\":[" as *u8) 2171 // the presented ?cap= (session baseline) decides tier 1 of the list order -- see ta_emit_tools 2172 let cq: *i64 = sys_mmap(16) as *i64 2173 var capp: *u8 = 0 as *u8 2174 var capn: i64 = 0 2175 if ta_query_cap(req, req_n, cq) == 1 { capp = ((req as i64) + cq[0]) as *u8; capn = cq[1] } 2176 b = ta_emit_tools(prefix, jb, b, 1, pgoff, TA_MCP_PAGE, moreb, capp, capn) 2177 b = ta_cat(jb, b, "]" as *u8) 2178 if moreb[0] == 1 { b = ta_cat(jb, b, ",\"nextCursor\":\"" as *u8); b = ta_catn(jb, b, pgoff + TA_MCP_PAGE); b = ta_cat(jb, b, "\"" as *u8) } 2179 b = ta_cat(jb, b, "}}" as *u8) 2180 g_ta_gen_listed = ta_reg_gen(prefix) // EC50: this client now holds this generation 2181 } else { if meth == 3 { 2182 b = ta_mcp_call(prefix, req, req_n, body, bn, jb, b) 2183 } else { if meth == 4 { 2184 b = ta_cat(jb, b, ",\"result\":{\"resources\":[{\"uri\":\"nishi://sota/ecosystem-maturity\",\"name\":\"Ecosystem Maturity Grade\",\"description\":\"live liar-killed per-domain maturity toward S-class, measured from the sovereign seg_store\",\"mimeType\":\"text/plain\"},{\"uri\":\"nishi://doctrine/operating\",\"name\":\"Nishi Operating Doctrine\",\"description\":\"how to operate the ecosystem over sovereign MCP + APIs\",\"mimeType\":\"text/plain\"}]}}" as *u8) 2185 } else { if meth == 5 { 2186 b = ta_mcp_resread(body, bn, jb, b) 2187 } else { if meth == 6 { 2188 b = ta_cat(jb, b, ",\"result\":{\"prompts\":[{\"name\":\"operate-nishi\",\"description\":\"Prime an agent to operate the Nishi ecosystem over sovereign MCP\"},{\"name\":\"ship-target\",\"description\":\"Build and deploy a target on the NAS over sovereign APIs\",\"arguments\":[{\"name\":\"target\",\"description\":\"the runtime organ to build and deploy\",\"required\":true}]}]}}" as *u8) 2189 } else { if meth == 7 { 2190 b = ta_mcp_promget(body, bn, jb, b) 2191 } else { 2192 b = ta_cat(jb, b, ",\"error\":{\"code\":-32601,\"message\":\"method not found\"}}" as *u8) 2193 } } } } } } } 2194 // EC50: a moved registry generation rides the SSE-shaped reply to a client that accepts one -- never on 2195 // initialize or tools/list themselves, and never to a client that only speaks JSON 2196 if meth >= 3 { if ta_accepts_sse(req, req_n) == 1 { if ta_reg_gen(prefix) != g_ta_gen_listed { return ta_emit_sse_listchanged(out, jb, b) } } } 2197 return ta_emit(out, "HTTP/1.1 200 OK" as *u8, jb, b) 2198} 2199 2200// ---- THE router: pure request bytes -> response bytes (gate drives this directly, no socket) --------- 2201// POST /api/cap/issue -- SELF-SERVICE capability DELEGATION (pure ocap, no ambient identity). Present YOUR capability 2202// (X-Nishi-Cap header or params._cap) + a narrower "allow" (+ optional "exp"); get back a delegated capability that is a 2203// SUBSET of yours (capt_attenuate refuses any widening AND an invalid/forged input -> you can never delegate more than 2204// you hold). Root minting from the secret stays operator-side (nx_cap_mint). Ratchet: clamp exp' <= parent exp. 2205// R4 per-client consent ledger: cap_consent.log (append-only). One line per delegation: 2206// now <TAB> client <TAB> allow <TAB> exp <TAB> parent_nonce <TAB> child_nonce 2207// In the ocap model the operator's SCOPED, attenuated mint IS the per-client consent (a deliberate least-authority 2208// grant, superior to a yes/no consent screen); this makes that intent EXPLICIT (client-attributed) + AUDITABLE. 2209const TA_CONSENT_LOG: *u8 = "cap_consent.log" as *u8 2210func ta_append_consent(client: *u8, cl: i64, allowp: *u8, allowl: i64, exp: i64, pnonce: i64, cnonce: i64) -> i64 { 2211 let fd: i64 = ta_openat_append(TA_CONSENT_LOG) 2212 if fd < 0 { return 0 - 1 } 2213 let line: *u8 = sys_mmap(TA_MAGIC_8192) 2214 var o: i64 = ta_catn(line, 0, sys_now_realtime_sec()) 2215 line[o] = 9 as u8; o = o + 1 2216 o = ta_catb(line, o, client, cl) 2217 line[o] = 9 as u8; o = o + 1 2218 o = ta_catb(line, o, allowp, allowl) 2219 line[o] = 9 as u8; o = o + 1 2220 o = ta_catn(line, o, exp) 2221 line[o] = 9 as u8; o = o + 1 2222 o = ta_catn(line, o, pnonce) 2223 line[o] = 9 as u8; o = o + 1 2224 o = ta_catn(line, o, cnonce) 2225 line[o] = 10 as u8; o = o + 1 2226 sys_write(fd, line, o) 2227 sys_close(fd) 2228 return 0 2229} 2230// GET /api/cap/consent-log -- PUBLIC audit trail of who-delegated-what-to-which-client (no secrets: nonces + scope only). 2231func ta_cap_consent_log(out: *u8) -> i64 { 2232 let szp: *i64 = sys_mmap(16) as *i64 2233 let buf: *u8 = sys_read_file(TA_CONSENT_LOG, szp) 2234 let jb: *u8 = sys_mmap(TA_MAGIC_1048576) 2235 var b: i64 = ta_cat(jb, 0, "{\"consent_log\":\"" as *u8) 2236 if (buf as i64) != 0 { b = ta_json_esc(jb, b, buf, szp[0]) } 2237 b = ta_cat(jb, b, "\",\"schema\":\"now\\tclient\\tallow\\texp\\tparent_nonce\\tchild_nonce\",\"note\":\"append-only; ocap scoped+attenuated delegation IS the per-client consent, recorded here for audit + revocable via /api/cap/revoke\"}" as *u8) 2238 return ta_emit(out, "HTTP/1.1 200 OK" as *u8, jb, b) 2239} 2240func ta_cap_issue(req: *u8, req_n: i64, out: *u8) -> i64 { 2241 let bo: i64 = ta_body_off(req, req_n) 2242 let body: *u8 = ((req as i64) + bo) as *u8 2243 let bn: i64 = req_n - bo 2244 let slb: *i64 = sys_mmap(16) as *i64 2245 let secret: *u8 = ta_load_cap_secret(slb) 2246 let secretlen: i64 = slb[0] 2247 let cap2: *i64 = sys_mmap(16) as *i64 2248 var pp: *u8 = 0 as *u8 2249 var pl: i64 = 0 2250 if ta_header(req, req_n, "X-Nishi-Cap:" as *u8, cap2) == 1 { pp = ((req as i64) + cap2[0]) as *u8; pl = cap2[1] } 2251 else { let cb: *i64 = sys_mmap(16) as *i64; if ta_json_str(body, bn, "\"_cap\"" as *u8, cb) == 1 { pp = ((body as i64) + cb[0]) as *u8; pl = cb[1] } } 2252 if pl <= 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"present your capability (X-Nishi-Cap header or params._cap)\"}" as *u8) } 2253 let ab: *i64 = sys_mmap(16) as *i64 2254 var allowp: *u8 = 0 as *u8 2255 var allowl: i64 = 0 2256 if ta_json_str(body, bn, "\"allow\"" as *u8, ab) == 1 { allowp = ((body as i64) + ab[0]) as *u8; allowl = ab[1] } 2257 if allowl <= 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"specify a narrower \\\"allow\\\" tool-set (comma-separated)\"}" as *u8) } 2258 var exp: i64 = TA_MAGIC_9999999999 2259 let eb: *i64 = sys_mmap(16) as *i64 2260 if ta_json_raw(body, bn, "\"exp\"" as *u8, eb) == 1 { exp = tr_atoi(body, eb[0], eb[1]) } 2261 // caller-supplied nonce -> a UNIQUE identity per delegated cap so it can be revoked individually (default 7 if omitted) 2262 var dnonce: i64 = 7 2263 let nb: *i64 = sys_mmap(16) as *i64 2264 if ta_json_raw(body, bn, "\"nonce\"" as *u8, nb) == 1 { dnonce = tr_atoi(body, nb[0], nb[1]) } 2265 let ntok: *u8 = sys_mmap(TA_MAGIC_2048) 2266 let ntn: i64 = capt_attenuate(secret, secretlen, pp, pl, allowp, allowl, exp, dnonce, ntok, TA_MAGIC_2048) 2267 if ntn > 0 { 2268 // R4: record the per-client consent (who consented by holding the parent, to which client, what scope, when). 2269 let clb: *i64 = sys_mmap(16) as *i64 2270 var clientp: *u8 = "anonymous" as *u8 2271 var clientl: i64 = 9 2272 if ta_json_str(body, bn, "\"client\"" as *u8, clb) == 1 { clientp = ((body as i64) + clb[0]) as *u8; clientl = clb[1] } 2273 ta_append_consent(clientp, clientl, allowp, allowl, exp, capt_nonce_of(pp, pl), dnonce) 2274 let jb: *u8 = sys_mmap(TA_MAGIC_4096) 2275 var b: i64 = ta_cat(jb, 0, "{\"cap\":\"" as *u8) 2276 b = ta_catb(jb, b, ntok, ntn) 2277 b = ta_cat(jb, b, "\",\"consent_recorded\":true,\"note\":\"delegated capability -- a SUBSET of yours (attenuation is subset-only; widening is refused); per-client consent logged (see GET /api/cap/consent-log)\"}" as *u8) 2278 return ta_emit(out, "HTTP/1.1 200 OK" as *u8, jb, b) 2279 } 2280 return ta_emit_lit(out, "HTTP/1.1 403 Forbidden" as *u8, "{\"error\":\"cannot delegate: your capability is invalid/forged, or the requested tools exceed your grant (no widening)\"}" as *u8) 2281} 2282 2283// POST /api/cap/revoke -- REVOKE a capability you hold: present it (X-Nishi-Cap / params._cap); if its MAC is valid its 2284// nonce is added to the denylist and it (plus any cap sharing that nonce) is refused thereafter. Fail-closed: only a 2285// MAC-valid cap can be revoked (the denylist can't be spammed with garbage). Cascading parent->child is a ratchet. 2286func ta_cap_revoke(req: *u8, req_n: i64, out: *u8) -> i64 { 2287 let bo: i64 = ta_body_off(req, req_n) 2288 let body: *u8 = ((req as i64) + bo) as *u8 2289 let bn: i64 = req_n - bo 2290 let slb: *i64 = sys_mmap(16) as *i64 2291 let secret: *u8 = ta_load_cap_secret(slb) 2292 let secretlen: i64 = slb[0] 2293 let cap2: *i64 = sys_mmap(16) as *i64 2294 var pp: *u8 = 0 as *u8 2295 var pl: i64 = 0 2296 if ta_header(req, req_n, "X-Nishi-Cap:" as *u8, cap2) == 1 { pp = ((req as i64) + cap2[0]) as *u8; pl = cap2[1] } 2297 else { let cb: *i64 = sys_mmap(16) as *i64; if ta_json_str(body, bn, "\"_cap\"" as *u8, cb) == 1 { pp = ((body as i64) + cb[0]) as *u8; pl = cb[1] } } 2298 if pl <= 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"present the capability to revoke (X-Nishi-Cap or params._cap)\"}" as *u8) } 2299 let plb: *i64 = sys_mmap(16) as *i64 2300 if capt_mac_ok(secret, secretlen, pp, pl, plb) == 0 { return ta_emit_lit(out, "HTTP/1.1 403 Forbidden" as *u8, "{\"error\":\"invalid/forged capability -- nothing revoked\"}" as *u8) } 2301 let nn: i64 = capt_nonce_of(pp, pl) 2302 ta_append_revoked(nn) 2303 let jb: *u8 = sys_mmap(256) 2304 var b: i64 = ta_cat(jb, 0, "{\"revoked_nonce\":" as *u8) 2305 b = ta_catn(jb, b, nn) 2306 b = ta_cat(jb, b, ",\"note\":\"denylisted; this capability (and any sharing its nonce) is refused from now on\"}" as *u8) 2307 return ta_emit(out, "HTTP/1.1 200 OK" as *u8, jb, b) 2308} 2309 2310// GET /api/cap/status -- PUBLIC config-health readout for the capability-grant bootstrap: reports whether the server 2311// loaded a REAL signing keyfile or is still on the FORGEABLE baked placeholder. Reveals NO secret (only the boolean 2312// state) -- and if it says placeholder, that secret is already public in the source, so nothing leaks. This is what 2313// lets an operator confirm `nx_cap_keygen` + restart took effect BEFORE minting (otherwise a real token fails with an 2314// opaque -32001 and there is no way to tell why). Needs no capability -- it must be checkable before any cap exists. 2315func ta_cap_status(out: *u8) -> i64 { 2316 if ta_cap_provisioned() == 1 { 2317 return ta_emit_lit(out, "HTTP/1.1 200 OK" as *u8, "{\"cap_signing\":\"provisioned\",\"secure\":true,\"note\":\"a real keyfile is loaded; minted tokens will verify\"}" as *u8) 2318 } 2319 return ta_emit_lit(out, "HTTP/1.1 200 OK" as *u8, "{\"cap_signing\":\"insecure_placeholder\",\"secure\":false,\"note\":\"forgeable baked secret in use -- run nx_cap_keygen in the server CWD and restart nx_tools_api, then re-check\"}" as *u8) 2320} 2321 2322// RFC 9728 Protected Resource Metadata -- lets a stock MCP client DISCOVER how to authenticate to this resource 2323// (bearer token in the Authorization header) and where the authorization server is. GET /.well-known/oauth-protected- 2324// resource. Advertises the ocap-as-bearer interop (R2); native X-Nishi-Cap / _cap remain first-class. 2325func ta_oauth_prm(out: *u8) -> i64 { 2326 return ta_emit_lit(out, "HTTP/1.1 200 OK" as *u8, "{\"resource\":\"https://nishifamily.com/mcp\",\"authorization_servers\":[\"https://nishifamily.com\"],\"bearer_methods_supported\":[\"header\"],\"resource_name\":\"Nishi Sovereign MCP\",\"resource_documentation\":\"https://nishifamily.com/api\"}" as *u8) 2327} 2328// ============================================================================================================ 2329// OAuth 2.1 AUTHORIZATION SERVER (RFC 6749/8252 + PKCE RFC 7636 S256 + AS-metadata RFC 8414 + dynamic client 2330// registration RFC 7591). Interactive consent at /oauth/authorize. STATELESS signed tokens: client_id + auth-code 2331// are capt_issue-signed (no client/token DB); access_token is a real scoped cap. One-time-use codes tracked in a 2332// BOUNDED static-pointer table (like the rate limiter). The issued bearer IS an X-Nishi-Cap -> the resource server 2333// (tools/call) already accepts it (Authorization: Bearer). ocap remains superior; this is for stock OAuth clients. 2334// ============================================================================================================ 2335const OC_SLOTS: i64 = 256 2336static oc_nonce: *i64 // one-time auth-code nonce (0 = free slot) 2337static oc_rhash: *i64 // bound redirect_uri hash 2338static oc_exp: *i64 // code expiry (epoch) 2339static oc_bind: *u8 // OC_SLOTS*160: [0..48)=code_challenge(S256, 43ch) , [48..160)=granted scope (tool CSV) 2340func oauth_state_init() -> i64 { 2341 if (oc_nonce as i64) == 0 { 2342 oc_nonce = sys_mmap(OC_SLOTS * 8) as *i64 2343 oc_rhash = sys_mmap(OC_SLOTS * 8) as *i64 2344 oc_exp = sys_mmap(OC_SLOTS * 8) as *i64 2345 oc_bind = sys_mmap(OC_SLOTS * 160) 2346 } 2347 return 0 2348} 2349func oauth_slotv(h: i64, slots: i64) -> i64 { var x: i64 = h; if x < 0 { x = 0 - x } return x - (x / slots) * slots } 2350func oauth_strhash(s: *u8, n: i64) -> i64 { var h: i64 = TA_MAGIC_1469598; var i: i64 = 0; while i < n { h = h * 31 + (s[i] as i64); i = i + 1 } if h == 0 { h = 1 } return h } 2351// extract param "key=" from a query/form string -> value [off,len) into out2 (0 if absent). Boundary = start or '&'. 2352func oauth_param(src: *u8, slen: i64, key: *u8, klen: i64, out2: *i64) -> i64 { 2353 var i: i64 = 0 2354 while i + klen <= slen { 2355 var atb: i64 = 0 2356 if i == 0 { atb = 1 } else { if src[i - 1] == (38 as u8) { atb = 1 } } 2357 if atb == 1 { 2358 var k: i64 = 0 2359 while k < klen { if src[i + k] != key[k] { k = klen + 9 } else { k = k + 1 } } 2360 if k == klen { if i + klen < slen { if src[i + klen] == (61 as u8) { 2361 let v: i64 = i + klen + 1 2362 var e: i64 = v 2363 var go: i64 = 1 2364 while go == 1 { if e >= slen { go = 0 } else { if src[e] == (38 as u8) { go = 0 } else { e = e + 1 } } } 2365 out2[0] = v 2366 out2[1] = e - v 2367 return 1 2368 } } } 2369 } 2370 i = i + 1 2371 } 2372 return 0 2373} 2374// minimal percent-decode (+ -> space, %XX -> byte) for building the redirect Location. Returns decoded length. 2375func oauth_hexv(c: i64) -> i64 { if c >= 48 { if c <= 57 { return c - 48 } } if c >= 65 { if c <= 70 { return c - 55 } } if c >= 97 { if c <= 102 { return c - 87 } } return 0 } 2376func oauth_urldec(src: *u8, n: i64, dst: *u8) -> i64 { 2377 var i: i64 = 0 2378 var o: i64 = 0 2379 while i < n { 2380 let c: i64 = src[i] as i64 2381 if c == 37 { if i + 2 < n { dst[o] = ((oauth_hexv(src[i+1] as i64) * 16) + oauth_hexv(src[i+2] as i64)) as u8; o = o + 1; i = i + 3 } else { dst[o] = 37 as u8; o = o + 1; i = i + 1 } } 2382 else { if c == 43 { dst[o] = 32 as u8; o = o + 1; i = i + 1 } else { dst[o] = c as u8; o = o + 1; i = i + 1 } } 2383 } 2384 return o 2385} 2386// base64url (no padding) of raw bytes -> out. For 32-byte SHA-256 -> 43 chars (PKCE S256 challenge form). 2387func oauth_b64url(inb: *u8, n: i64, out: *u8) -> i64 { 2388 var o: i64 = 0 2389 var i: i64 = 0 2390 while i + 3 <= n { 2391 let x: i64 = ((inb[i] as i64) << 16) | ((inb[i+1] as i64) << 8) | (inb[i+2] as i64) 2392 out[o] = b64url_enc_char((x >> 18) & 63) as u8; o = o + 1 2393 out[o] = b64url_enc_char((x >> 12) & 63) as u8; o = o + 1 2394 out[o] = b64url_enc_char((x >> 6) & 63) as u8; o = o + 1 2395 out[o] = b64url_enc_char(x & 63) as u8; o = o + 1 2396 i = i + 3 2397 } 2398 let rem: i64 = n - i 2399 if rem == 1 { 2400 let x: i64 = (inb[i] as i64) << 16 2401 out[o] = b64url_enc_char((x >> 18) & 63) as u8; o = o + 1 2402 out[o] = b64url_enc_char((x >> 12) & 63) as u8; o = o + 1 2403 } else { if rem == 2 { 2404 let x: i64 = ((inb[i] as i64) << 16) | ((inb[i+1] as i64) << 8) 2405 out[o] = b64url_enc_char((x >> 18) & 63) as u8; o = o + 1 2406 out[o] = b64url_enc_char((x >> 12) & 63) as u8; o = o + 1 2407 out[o] = b64url_enc_char((x >> 6) & 63) as u8; o = o + 1 2408 } } 2409 return o 2410} 2411func oauth_resp(out: *u8, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 2412 var o: i64 = ta_cat(out, 0, "HTTP/1.1 " as *u8) 2413 o = ta_cat(out, o, status) 2414 o = ta_cat(out, o, "\r\nContent-Type: " as *u8) 2415 o = ta_cat(out, o, ctype) 2416 o = ta_cat(out, o, "\r\nCache-Control: no-store\r\nConnection: close\r\nContent-Length: " as *u8) 2417 o = capt_catn(out, o, blen) 2418 o = ta_cat(out, o, "\r\n\r\n" as *u8) 2419 o = ta_catb(out, o, body, blen) 2420 return o 2421} 2422// GET /.well-known/oauth-authorization-server (RFC 8414) 2423func ta_oauth_asmeta(out: *u8) -> i64 { 2424 let body: *u8 = "{\"issuer\":\"https://nishifamily.com\",\"authorization_endpoint\":\"https://nishifamily.com/oauth/authorize\",\"token_endpoint\":\"https://nishifamily.com/oauth/token\",\"registration_endpoint\":\"https://nishifamily.com/oauth/register\",\"response_types_supported\":[\"code\"],\"grant_types_supported\":[\"authorization_code\"],\"code_challenge_methods_supported\":[\"S256\"],\"token_endpoint_auth_methods_supported\":[\"none\"],\"scopes_supported\":[\"nishi_search\",\"nishi_doc\",\"nx_status\",\"nx_health\",\"nx_services\"]}" as *u8 2425 return oauth_resp(out, "200 OK" as *u8, "application/json" as *u8, body, capt_slen(body)) 2426} 2427// POST /oauth/register (RFC 7591 dynamic client registration). body JSON {redirect_uri, client_name}. client_id is a 2428// STATELESS capt-signed token binding the redirect_uri (its nonce = hash(redirect_uri)) -> no client DB. 2429func ta_oauth_register(req: *u8, req_n: i64, out: *u8) -> i64 { 2430 let bo: i64 = ta_body_off(req, req_n) 2431 let body: *u8 = ((req as i64) + bo) as *u8 2432 let bn: i64 = req_n - bo 2433 let slb: *i64 = sys_mmap(16) as *i64 2434 let secret: *u8 = ta_load_cap_secret(slb) 2435 let rb: *i64 = sys_mmap(16) as *i64 2436 if ta_json_str(body, bn, "\"redirect_uri\"" as *u8, rb) == 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"invalid_client_metadata\",\"error_description\":\"redirect_uri required\"}" as *u8) } 2437 let rp: *u8 = ((body as i64) + rb[0]) as *u8 2438 let rl: i64 = rb[1] 2439 let cidtok: *u8 = sys_mmap(TA_MAGIC_2048) 2440 let cidn: i64 = capt_issue(secret, slb[0], "oauth_client" as *u8, 12, TA_MAGIC_9999999999, oauth_strhash(rp, rl), cidtok, TA_MAGIC_2048) 2441 let jb: *u8 = sys_mmap(TA_MAGIC_4096) 2442 var b: i64 = ta_cat(jb, 0, "{\"client_id\":\"" as *u8) 2443 b = ta_catb(jb, b, cidtok, cidn) 2444 b = ta_cat(jb, b, "\",\"token_endpoint_auth_method\":\"none\",\"grant_types\":[\"authorization_code\"],\"response_types\":[\"code\"],\"redirect_uris\":[\"" as *u8) 2445 b = ta_catb(jb, b, rp, rl) 2446 b = ta_cat(jb, b, "\"]}" as *u8) 2447 return oauth_resp(out, "201 Created" as *u8, "application/json" as *u8, jb, b) 2448} 2449// GET /oauth/authorize?response_type=code&client_id&redirect_uri&scope&state&code_challenge&code_challenge_method=S256 2450// -> INTERACTIVE CONSENT page (the human approves the scope). Approve POSTs to /oauth/approve. 2451func ta_oauth_authorize(req: *u8, req_n: i64, out: *u8) -> i64 { 2452 let mo: *i64 = sys_mmap(16) as *i64 2453 let po: *i64 = sys_mmap(16) as *i64 2454 ta_reqline(req, req_n, mo, po) 2455 let full: *u8 = ((req as i64) + po[0]) as *u8 2456 let flen: i64 = po[1] 2457 var qpos: i64 = 0 - 1 2458 var qi: i64 = 0 2459 while qi < flen { if qpos < 0 { if full[qi] == (63 as u8) { qpos = qi } } qi = qi + 1 } 2460 if qpos < 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\",\"error_description\":\"missing query\"}" as *u8, 62) } 2461 let query: *u8 = ((full as i64) + qpos + 1) as *u8 2462 let qlen: i64 = flen - qpos - 1 2463 let aslb: *i64 = sys_mmap(16) as *i64 2464 let secret: *u8 = ta_load_cap_secret(aslb) 2465 let cib: *i64 = sys_mmap(16) as *i64 2466 let rib: *i64 = sys_mmap(16) as *i64 2467 let ccb: *i64 = sys_mmap(16) as *i64 2468 let scb: *i64 = sys_mmap(16) as *i64 2469 if oauth_param(query, qlen, "client_id" as *u8, 9, cib) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\"}" as *u8, 27) } 2470 if oauth_param(query, qlen, "redirect_uri" as *u8, 12, rib) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\"}" as *u8, 27) } 2471 if oauth_param(query, qlen, "code_challenge" as *u8, 14, ccb) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\",\"error_description\":\"PKCE code_challenge required (S256)\"}" as *u8, 82) } 2472 let cip: *u8 = ((query as i64) + cib[0]) as *u8 2473 let rip: *u8 = ((query as i64) + rib[0]) as *u8 2474 let plb: *i64 = sys_mmap(16) as *i64 2475 if capt_mac_ok(secret, aslb[0], cip, cib[1], plb) == 0 { return oauth_resp(out, "401 Unauthorized" as *u8, "application/json" as *u8, "{\"error\":\"invalid_client\"}" as *u8, 26) } 2476 if capt_nonce_of(cip, cib[1]) != oauth_strhash(rip, rib[1]) { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\",\"error_description\":\"redirect_uri not registered for this client\"}" as *u8, 90) } 2477 var scp: *u8 = "nishi_search" as *u8 2478 var scl: i64 = 12 2479 if oauth_param(query, qlen, "scope" as *u8, 5, scb) == 1 { scp = ((query as i64) + scb[0]) as *u8; scl = scb[1] } 2480 // consent page: nishi-native, self-contained. Approve form re-submits the exact params to /oauth/approve. 2481 let jb: *u8 = sys_mmap(TA_MAGIC_16384) 2482 var b: i64 = ta_cat(jb, 0, "<html><head><meta charset=utf-8><title>Authorize</title><style>body{font-family:system-ui;max-width:34em;margin:3em auto;padding:0 1em;color:midnightblue}h1{color:seagreen}.s{background:honeydew;border-left:4px solid seagreen;padding:.5em 1em;border-radius:5px;margin:1em 0}button{font-size:1em;padding:.5em 1.4em;border:0;border-radius:6px;cursor:pointer}.a{background:seagreen;color:white}.d{background:gainsboro}</style></head><body><h1>Authorize access</h1><p>An application is requesting a Nishi capability. Review the scope you are granting.</p><div class=s><b>Scope:</b> <code>" as *u8) 2483 b = ta_catb(jb, b, scp, scl) 2484 b = ta_cat(jb, b, "</code></div><form method=post action=/oauth/approve>" as *u8) 2485 b = ta_cat(jb, b, "<input type=hidden name=client_id value=\"" as *u8); b = ta_catb(jb, b, cip, cib[1]); b = ta_cat(jb, b, "\">" as *u8) 2486 b = ta_cat(jb, b, "<input type=hidden name=redirect_uri value=\"" as *u8); b = ta_catb(jb, b, rip, rib[1]); b = ta_cat(jb, b, "\">" as *u8) 2487 b = ta_cat(jb, b, "<input type=hidden name=code_challenge value=\"" as *u8); b = ta_catb(jb, b, ((query as i64) + ccb[0]) as *u8, ccb[1]); b = ta_cat(jb, b, "\">" as *u8) 2488 b = ta_cat(jb, b, "<input type=hidden name=scope value=\"" as *u8); b = ta_catb(jb, b, scp, scl); b = ta_cat(jb, b, "\">" as *u8) 2489 let stb: *i64 = sys_mmap(16) as *i64 2490 if oauth_param(query, qlen, "state" as *u8, 5, stb) == 1 { b = ta_cat(jb, b, "<input type=hidden name=state value=\"" as *u8); b = ta_catb(jb, b, ((query as i64) + stb[0]) as *u8, stb[1]); b = ta_cat(jb, b, "\">" as *u8) } 2491 b = ta_cat(jb, b, "<button class=a type=submit>Approve</button></form><p style=color:slategray>Powered by the Nishi sovereign auth plane -- OPAQUE + object-capability. The issued bearer is an attenuated cap.</p></body></html>" as *u8) 2492 return oauth_resp(out, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, jb, b) 2493} 2494// POST /oauth/approve (the consent GRANT). Issues a one-time auth code bound to {redirect_uri, code_challenge, scope} 2495// and 302-redirects to redirect_uri?code=..&state=.. 2496func ta_oauth_approve(req: *u8, req_n: i64, out: *u8) -> i64 { 2497 oauth_state_init() 2498 let bo: i64 = ta_body_off(req, req_n) 2499 let body: *u8 = ((req as i64) + bo) as *u8 2500 let bn: i64 = req_n - bo 2501 let pslb: *i64 = sys_mmap(16) as *i64 2502 let secret: *u8 = ta_load_cap_secret(pslb) 2503 let cib: *i64 = sys_mmap(16) as *i64 2504 let rib: *i64 = sys_mmap(16) as *i64 2505 let ccb: *i64 = sys_mmap(16) as *i64 2506 let scb: *i64 = sys_mmap(16) as *i64 2507 if oauth_param(body, bn, "client_id" as *u8, 9, cib) == 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"invalid_request\"}" as *u8) } 2508 if oauth_param(body, bn, "redirect_uri" as *u8, 12, rib) == 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"invalid_request\"}" as *u8) } 2509 if oauth_param(body, bn, "code_challenge" as *u8, 14, ccb) == 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"invalid_request\"}" as *u8) } 2510 let cip: *u8 = ((body as i64) + cib[0]) as *u8 2511 let rip: *u8 = ((body as i64) + rib[0]) as *u8 2512 let plb: *i64 = sys_mmap(16) as *i64 2513 if capt_mac_ok(secret, pslb[0], cip, cib[1], plb) == 0 { return ta_emit_lit(out, "HTTP/1.1 401 Unauthorized" as *u8, "{\"error\":\"invalid_client\"}" as *u8) } 2514 if capt_nonce_of(cip, cib[1]) != oauth_strhash(rip, rib[1]) { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"invalid_request\"}" as *u8) } 2515 var scp: *u8 = "nishi_search" as *u8 2516 var scl: i64 = 12 2517 if oauth_param(body, bn, "scope" as *u8, 5, scb) == 1 { scp = ((body as i64) + scb[0]) as *u8; scl = scb[1] } 2518 let ccp: *u8 = ((body as i64) + ccb[0]) as *u8 2519 let ccl: i64 = ccb[1] 2520 let now: i64 = sys_now_realtime_sec() 2521 var cnonce: i64 = (oauth_strhash(ccp, ccl) ^ (now * 131 + 7)) & TA_MAGIC_4294967295 // positive 32-bit -> capt_catn writes it 2522 if cnonce == 0 { cnonce = 1 } 2523 let s: i64 = oauth_slotv(cnonce, OC_SLOTS) 2524 oc_nonce[s] = cnonce 2525 oc_rhash[s] = oauth_strhash(rip, rib[1]) 2526 oc_exp[s] = now + 60 2527 var z: i64 = 0 2528 while z < ccl { if z < 47 { oc_bind[s * 160 + z] = ccp[z] } z = z + 1 } 2529 oc_bind[s * 160 + 47] = 0 as u8 2530 z = 0 2531 while z < scl { if z < 111 { oc_bind[s * 160 + 48 + z] = scp[z] } z = z + 1 } 2532 oc_bind[s * 160 + 48 + scl] = 0 as u8 2533 let code: *u8 = sys_mmap(TA_MAGIC_2048) 2534 let codn: i64 = capt_issue(secret, pslb[0], "oauth_code" as *u8, 10, now + 60, cnonce, code, TA_MAGIC_2048) 2535 // build Location = <decoded redirect_uri>?code=<code>&state=<state> 2536 let loc: *u8 = sys_mmap(TA_MAGIC_4096) 2537 var lo: i64 = oauth_urldec(rip, rib[1], loc) 2538 loc[lo] = 63 as u8; lo = lo + 1 2539 lo = ta_cat(loc, lo, "code=" as *u8) 2540 lo = ta_catb(loc, lo, code, codn) 2541 let stb: *i64 = sys_mmap(16) as *i64 2542 if oauth_param(body, bn, "state" as *u8, 5, stb) == 1 { 2543 lo = ta_cat(loc, lo, "&state=" as *u8) 2544 lo = ta_catb(loc, lo, ((body as i64) + stb[0]) as *u8, stb[1]) 2545 } 2546 var o: i64 = ta_cat(out, 0, "HTTP/1.1 302 Found\r\nLocation: " as *u8) 2547 o = ta_catb(out, o, loc, lo) 2548 o = ta_cat(out, o, "\r\nCache-Control: no-store\r\nConnection: close\r\nContent-Length: 0\r\n\r\n" as *u8) 2549 return o 2550} 2551// POST /oauth/token grant_type=authorization_code&code&redirect_uri&code_verifier -> verify code sig + one-time-use + 2552// redirect binding + PKCE(S256) -> mint a scoped access cap. RFC 6749 s5.1 JSON. 2553func ta_oauth_token(req: *u8, req_n: i64, out: *u8) -> i64 { 2554 oauth_state_init() 2555 let bo: i64 = ta_body_off(req, req_n) 2556 let body: *u8 = ((req as i64) + bo) as *u8 2557 let bn: i64 = req_n - bo 2558 let slb: *i64 = sys_mmap(16) as *i64 2559 let secret: *u8 = ta_load_cap_secret(slb) 2560 let gtb: *i64 = sys_mmap(16) as *i64 2561 let cdb: *i64 = sys_mmap(16) as *i64 2562 let rib: *i64 = sys_mmap(16) as *i64 2563 let cvb: *i64 = sys_mmap(16) as *i64 2564 if oauth_param(body, bn, "grant_type" as *u8, 10, gtb) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\"}" as *u8, 27) } 2565 if oauth_param(body, bn, "code" as *u8, 4, cdb) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\",\"error_description\":\"code required\"}" as *u8, 60) } 2566 if oauth_param(body, bn, "redirect_uri" as *u8, 12, rib) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\"}" as *u8, 27) } 2567 if oauth_param(body, bn, "code_verifier" as *u8, 13, cvb) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_request\",\"error_description\":\"PKCE code_verifier required\"}" as *u8, 76) } 2568 let cdp: *u8 = ((body as i64) + cdb[0]) as *u8 2569 let plb: *i64 = sys_mmap(16) as *i64 2570 if capt_mac_ok(secret, slb[0], cdp, cdb[1], plb) == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_grant\",\"error_description\":\"bad code signature\"}" as *u8, 64) } 2571 let cnonce: i64 = capt_nonce_of(cdp, cdb[1]) 2572 let s: i64 = oauth_slotv(cnonce, OC_SLOTS) 2573 let now: i64 = sys_now_realtime_sec() 2574 if oc_nonce[s] != cnonce { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_grant\",\"error_description\":\"code unknown/already used\"}" as *u8, 72) } 2575 if now >= oc_exp[s] { oc_nonce[s] = 0; return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_grant\",\"error_description\":\"code expired\"}" as *u8, 58) } 2576 let rip: *u8 = ((body as i64) + rib[0]) as *u8 2577 if oc_rhash[s] != oauth_strhash(rip, rib[1]) { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_grant\",\"error_description\":\"redirect_uri mismatch\"}" as *u8, 68) } 2578 // PKCE S256: base64url(sha256(code_verifier)) == stored code_challenge 2579 let cvp: *u8 = ((body as i64) + cvb[0]) as *u8 2580 let dg: *u8 = sys_mmap(64) 2581 sha256_digest(cvp, cvb[1], dg) 2582 let calc: *u8 = sys_mmap(128) 2583 let cln: i64 = oauth_b64url(dg, 32, calc) 2584 let chal: *u8 = ((oc_bind as i64) + s * 160) as *u8 2585 var chn: i64 = 0 2586 var cdone: i64 = 0 2587 while cdone == 0 { if chn >= 47 { cdone = 1 } else { if chal[chn] == (0 as u8) { cdone = 1 } else { chn = chn + 1 } } } 2588 var pkok: i64 = 1 2589 if cln != chn { pkok = 0 } 2590 var mi: i64 = 0 2591 while mi < cln { if calc[mi] != chal[mi] { pkok = 0 } mi = mi + 1 } 2592 if pkok == 0 { return oauth_resp(out, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"invalid_grant\",\"error_description\":\"PKCE verification failed\"}" as *u8, 70) } 2593 oc_nonce[s] = 0 // ONE-TIME USE: burn the code 2594 let scope: *u8 = ((oc_bind as i64) + s * 160 + 48) as *u8 2595 var scln: i64 = 0 2596 var sdone: i64 = 0 2597 while sdone == 0 { if scln >= 111 { sdone = 1 } else { if scope[scln] == (0 as u8) { sdone = 1 } else { scln = scln + 1 } } } 2598 let atok: *u8 = sys_mmap(TA_MAGIC_2048) 2599 let atn: i64 = capt_issue(secret, slb[0], scope, scln, now + TA_MAGIC_3600, (cnonce ^ TA_MAGIC_88161) & TA_MAGIC_4294967295, atok, TA_MAGIC_2048) 2600 let jb: *u8 = sys_mmap(TA_MAGIC_4096) 2601 var b: i64 = ta_cat(jb, 0, "{\"access_token\":\"" as *u8) 2602 b = ta_catb(jb, b, atok, atn) 2603 b = ta_cat(jb, b, "\",\"token_type\":\"Bearer\",\"expires_in\":3600,\"scope\":\"" as *u8) 2604 b = ta_catb(jb, b, scope, scln) 2605 b = ta_cat(jb, b, "\"}" as *u8) 2606 return oauth_resp(out, "200 OK" as *u8, "application/json" as *u8, jb, b) 2607} 2608 2609func ta_handle_pfx(prefix: *u8, req: *u8, req_n: i64, out: *u8) -> i64 { 2610 let mo: *i64 = sys_mmap(16) as *i64 2611 let po: *i64 = sys_mmap(16) as *i64 2612 if ta_reqline(req, req_n, mo, po) == 0 { return ta_emit_lit(out, "HTTP/1.1 400 Bad Request" as *u8, "{\"error\":\"bad_request_line\"}" as *u8) } 2613 let path: *u8 = ((req as i64) + po[0]) as *u8 2614 var plen: i64 = po[1] 2615 var qi: i64 = 0 2616 var qfound: i64 = 0 - 1 2617 while qi < po[1] { if qfound < 0 { if path[qi] == (63 as u8) { qfound = qi } } qi = qi + 1 } 2618 if qfound >= 0 { plen = qfound } 2619 let mp: *u8 = ((req as i64) + mo[0]) as *u8 2620 let ml: i64 = mo[1] 2621 let is_get: i64 = ta_streq_n(mp, ml, "GET" as *u8) 2622 let is_post: i64 = ta_streq_n(mp, ml, "POST" as *u8) 2623 if ta_streq_n(path, plen, "/api/tools" as *u8) == 1 { if is_get == 1 { return ta_tools(prefix, out) } } 2624 if ta_streq_n(path, plen, "/tools" as *u8) == 1 { if is_get == 1 { return ta_html_tools(out) } } 2625 if ta_streq_n(path, plen, "/mcp" as *u8) == 1 { if is_post == 1 { return ta_mcp(prefix, req, req_n, out) } return ta_cat(out, 0, "HTTP/1.1 405 Method Not Allowed\r\nAllow: POST, OPTIONS\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" as *u8) } 2626 if ta_streq_n(path, plen, "/api/cap/issue" as *u8) == 1 { if is_post == 1 { return ta_cap_issue(req, req_n, out) } } 2627 if ta_streq_n(path, plen, "/api/cap/revoke" as *u8) == 1 { if is_post == 1 { return ta_cap_revoke(req, req_n, out) } } 2628 if ta_streq_n(path, plen, "/api/cap/status" as *u8) == 1 { if is_get == 1 { return ta_cap_status(out) } } 2629 if ta_streq_n(path, plen, "/api/cap/consent-log" as *u8) == 1 { if is_get == 1 { return ta_cap_consent_log(out) } } 2630 if ta_streq_n(path, plen, "/.well-known/oauth-protected-resource" as *u8) == 1 { if is_get == 1 { return ta_oauth_prm(out) } } 2631 if ta_streq_n(path, plen, "/.well-known/oauth-authorization-server" as *u8) == 1 { if is_get == 1 { return ta_oauth_asmeta(out) } } 2632 if ta_streq_n(path, plen, "/oauth/register" as *u8) == 1 { if is_post == 1 { return ta_oauth_register(req, req_n, out) } } 2633 if ta_streq_n(path, plen, "/oauth/authorize" as *u8) == 1 { if is_get == 1 { return ta_oauth_authorize(req, req_n, out) } } 2634 if ta_streq_n(path, plen, "/oauth/approve" as *u8) == 1 { if is_post == 1 { return ta_oauth_approve(req, req_n, out) } } 2635 if ta_streq_n(path, plen, "/oauth/token" as *u8) == 1 { if is_post == 1 { return ta_oauth_token(req, req_n, out) } } 2636 if ta_streq_n(path, plen, "/api" as *u8) == 1 { if is_get == 1 { return ta_index(out) } } 2637 if ta_streq_n(path, plen, "/" as *u8) == 1 { if is_get == 1 { return ta_index(out) } } 2638 return ta_emit_lit(out, "HTTP/1.1 404 Not Found" as *u8, "{\"error\":\"not_found\"}" as *u8) 2639} 2640// production-prefix convenience wrapper (the real registry). 2641func ta_handle(req: *u8, req_n: i64, out: *u8) -> i64 { return ta_handle_pfx(TA_TOOLPFX, req, req_n, out) } 2642 2643func ta_claim_meta(jb:*u8,b:i64,jid:i64,elapsed:i64,published:i64,has_meta:i64)->i64{ 2644 // These callers have just closed result._meta/result/root (or result/root 2645 // for an allowlist refusal). Extend the existing metadata without replacing it. 2646 var p:i64=b-2-has_meta 2647 if p<0{return b} 2648 if has_meta==1{p=ta_cat(jb,p,",")}else{p=ta_cat(jb,p,",\"_meta\":{")} 2649 p=ta_cat(jb,p,"\"job\":");p=ta_catn(jb,p,jid) 2650 p=ta_cat(jb,p,",\"reservation_elapsed_ms\":");p=ta_catn(jb,p,elapsed) 2651 p=ta_cat(jb,p,",\"publication_verified\":") 2652 if published==1{p=ta_cat(jb,p,"true")}else{p=ta_cat(jb,p,"false")} 2653 return ta_cat(jb,p,"}}}") 2654}