code wiki / _hdl_build / nx_dev_api.nx

nx_dev_api.nx source

↩ module page · 410 lines · 19636 B

1// nx_dev_api.nx -- SOVEREIGN DEV/CI API: development itself becomes Nishi API calls (operator 2026-07-02: 2// "get this all setup as api calls in the nishi ecosystem ... so we dont use shitty wsl and shitty shell"). 3// The ONE operation no existing API exposed = BUILD (compile a .nx -> ELF). This closes it, so the WSL 4// door is retired: a client POSTs a build request over our own HTTP and reads the gate verdict back. 5// GET /api/dev -> index (routes) 6// POST /api/dev/build body organ=<name> -> fork/exec ./_offc/nx_sov_build_run.elf <name>, capture 7// stdout+stderr, return {organ,exit,output} 8// POST /api/dev/gate body organ=<name> -> build a *_gate + return GREEN/RED verdict + output 9// GET /api/dev/status -> the job-runner receipts tail (what ran, exit codes) 10// PURE ROUTER da_handle(req,req_n,out)->out_n (bytes in, bytes out) so the gate drives it in-process, 11// exactly like ta_handle/ma_handle. THE DEFENSIVE BOUNDARY (rule 12): the organ name is charset-validated 12// [a-z0-9_] only -> no path traversal, no args, FIXED-ARGV exec = injection structurally impossible. 13// AUTH: /api/dev EXECUTES code -> the mutating routes require an X-Nishi-Cap capability token granting the 14// tool "dev_build" (nx_cap_token: HMAC, unforgeable, least-authority, no ambient identity = confused-deputy 15// impossible). No cap / forged / expired / wrong-tool -> 401. Secret from a keyfile (prod), const fallback 16// (gates/dev stay deterministic) -- the tools-API convention. license_tier: ORIGINAL 17import "nx_syscalls.nx" 18import "nx_cap_token.nx" // capt_verify / CAPT_OK 19const DA_MAGIC_16384: i64 = 16384 20const DA_MAGIC_8192: i64 = 8192 21const DA_MAGIC_8000: i64 = 8000 22 23const DA_OUTCAP: i64 = 262144 24const DA_TMP: *u8 = "/tmp/da_build.out" 25const DA_HARNESS: *u8 = "./_offc/nx_sov_build_run.elf" 26const DA_CAP_TOOL: *u8 = "dev_build" as *u8 27const DA_CAP_KEYFILE: *u8 = "dev_cap_secret.key" as *u8 28// PLACEHOLDER (gates/dev only); production loads DA_CAP_KEYFILE (0600, provisioned once from the vault). 29const DA_CAP_SECRET: *u8 = "nishi-dev-cap-hmac-secret-v1-REPLACE-FROM-VAULT" as *u8 30 31func da_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 32func da_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 } 33func da_catn(d: *u8, o: i64, v: i64) -> i64 { 34 let t: *u8=sys_mmap(24); var m: i64=v; var k: i64=0 35 if m<0 { d[o]=45 as u8; return da_catn(d, o+1, 0-m) } 36 if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} 37 var w: i64=o; var i: i64=0; while i<k{d[w]=t[k-1-i];w=w+1;i=i+1} return w 38} 39// JSON string escape (defensive boundary: build output carries quotes/newlines/control bytes) 40func da_json_esc(d: *u8, o: i64, s: *u8, n: i64) -> i64 { 41 var i: i64=0 42 while i < n { 43 let c: i64 = (s[i] as i64) & 0xff 44 if c==34 { d[o]=92 as u8; d[o+1]=34 as u8; o=o+2 } 45 else { if c==92 { d[o]=92 as u8; d[o+1]=92 as u8; o=o+2 } 46 else { if c==10 { d[o]=92 as u8; d[o+1]=110 as u8; o=o+2 } 47 else { if c==13 { d[o]=92 as u8; d[o+1]=114 as u8; o=o+2 } 48 else { if c==9 { d[o]=92 as u8; d[o+1]=116 as u8; o=o+2 } 49 else { if c<32 { d[o]=32 as u8; o=o+1 } 50 else { d[o]=c as u8; o=o+1 } } } } } } 51 i=i+1 52 } 53 return o 54} 55 56// validate an organ name: [a-z0-9_] only, 1..64 chars. Returns len or -1 (rejects .. / space / args). 57func da_valid_name(s: *u8, n: i64, out: *u8) -> i64 { 58 if n < 1 { return 0 - 1 } 59 if n > 64 { return 0 - 1 } 60 var i: i64 = 0 61 while i < n { 62 let c: i64 = (s[i] as i64) & 0xff 63 var ok: i64 = 0 64 if c>=97 { if c<=122 { ok=1 } } 65 if c>=48 { if c<=57 { ok=1 } } 66 if c==95 { ok=1 } 67 if ok==0 { return 0 - 1 } 68 out[i] = s[i] 69 i = i + 1 70 } 71 out[n] = 0 as u8 72 return n 73} 74 75func da_body_off(req: *u8, n: i64) -> i64 { 76 var i: i64 = 0 77 while i + 3 < n { 78 if (req[i] as i64)==13 { if (req[i+1] as i64)==10 { if (req[i+2] as i64)==13 { if (req[i+3] as i64)==10 { return i+4 } } } } 79 i = i + 1 80 } 81 return 0 - 1 82} 83// extract form field `key=` value from body[boff..n) into out; returns len or -1 84func da_form_field(req: *u8, boff: i64, n: i64, key: *u8, out: *u8) -> i64 { 85 if boff < 0 { return 0 - 1 } 86 let kl: i64 = da_slen(key) 87 var i: i64 = boff 88 while i + kl < n { 89 var m: i64 = 1 90 var j: i64 = 0 91 while j < kl { if req[i+j] != key[j] { m=0; j=kl } else { j=j+1 } } 92 if m==1 { if req[i+kl]==(61 as u8) { 93 var o: i64 = 0 94 var p: i64 = i+kl+1 95 while p < n { let c: i64 = req[p] as i64; if c==38 { p=n } else { if c==10 { p=n } else { if c==13 { p=n } else { out[o]=req[p]; o=o+1; p=p+1 } } } } 96 out[o] = 0 as u8 97 return o 98 } } 99 i = i + 1 100 } 101 return 0 - 1 102} 103 104// bounded file read into a caller buffer. NO 4GiB mmap (unlike sys_read_file, which reserves 4GiB VA per call and 105// never munmaps -> ~13 per-request reads exhausted address space and made fork() fail). fd CLOSED. returns bytes. 106func da_read_small(path: *u8, out: *u8, cap: i64) -> i64 { 107 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } 108 var total: i64 = 0; var go: i64 = 1 109 while go == 1 { 110 let n: i64 = sys_read(fd, (out as i64 + total) as *u8, cap - total) 111 if n <= 0 { go = 0 } 112 if n > 0 { total = total + n } 113 if total >= cap { go = 0 } 114 } 115 sys_close(fd) 116 return total 117} 118// read a file's LAST `cap` bytes into out via lseek-to-tail (NO 4GiB mmap; fd CLOSED); returns bytes read. 119func da_read_tail(path: *u8, out: *u8, cap: i64) -> i64 { 120 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } 121 let size: i64 = sys_lseek(fd, 0, 2) // SEEK_END -> file size 122 var start: i64 = 0 123 if size > cap { start = size - cap } 124 sys_lseek(fd, start, 0) // SEEK_SET -> tail 125 var total: i64 = 0; var go: i64 = 1 126 while go == 1 { 127 let n: i64 = sys_read(fd, (out as i64 + total) as *u8, cap - total) 128 if n <= 0 { go = 0 } 129 if n > 0 { total = total + n } 130 if total >= cap { go = 0 } 131 } 132 sys_close(fd) 133 return total 134} 135 136// fork/exec the harness on <name>, capturing stdout+stderr to DA_TMP. Returns child exit code. 137func da_build_organ(name: *u8, capbuf: *u8, caplen: *i64) -> i64 { 138 let tf: i64 = sys_openat_wr(DA_TMP, 0x1a4) // truncate for a fresh capture 139 if tf >= 0 { sys_close(tf) } 140 let pid: i64 = sys_fork() 141 if pid == 0 { 142 let ofd: i64 = sys_openat_wr(DA_TMP, 0x1a4) 143 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 144 let argv: *i64 = sys_mmap(64) as *i64 145 let envp: *i64 = sys_mmap(16) as *i64 146 envp[0] = 0 147 argv[0] = DA_HARNESS as i64 148 argv[1] = name as i64 149 argv[2] = 0 150 sys_execve(DA_HARNESS, argv, envp) 151 sys_exit(127) 152 return 127 153 } 154 if pid < 0 { caplen[0]=0; return 0 - 1 } 155 let status: *i64 = sys_mmap(16) as *i64 156 sys_wait4(pid, status, 0) 157 caplen[0] = da_read_tail(DA_TMP, capbuf, DA_OUTCAP) 158 return (status[0] >> 8) & 0xff 159} 160 161// find NUL-terminated needle in hay[0..n); returns offset or -1 162func da_indexof(hay: *u8, n: i64, needle: *u8) -> i64 { 163 let nl: i64 = da_slen(needle); if nl==0 { return 0 } 164 var i: i64 = 0 165 while i + nl <= n { var j: i64=0; var ok: i64=1; while j<nl { if hay[i+j]!=needle[j]{ok=0;j=nl} else {j=j+1} } if ok==1{return i} i=i+1 } 166 return 0 - 1 167} 168// extract header value (key includes trailing colon) into out2[off,len] within the header region. 1/0. 169func da_header(req: *u8, req_n: i64, key: *u8, out2: *i64) -> i64 { 170 var hend: i64 = da_body_off(req, req_n) 171 if hend < 0 { hend = req_n } 172 let p: i64 = da_indexof(req, hend, key) 173 if p < 0 { return 0 } 174 var vs: i64 = p + da_slen(key) 175 var go: i64 = 1 176 while go==1 { if vs<hend { if req[vs]==(32 as u8) { vs=vs+1 } else { go=0 } } else { go=0 } } 177 var ve: i64 = vs 178 var go2: i64 = 1 179 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 } } 180 out2[0]=vs; out2[1]=ve-vs 181 return 1 182} 183// verify the request carries a cap granting DA_CAP_TOOL. Returns CAPT_OK(1) or a negative deny code / 0=no-cap. 184func da_authz(req: *u8, req_n: i64) -> i64 { 185 let kbuf: *u8 = sys_mmap(256) 186 let klen: i64 = da_read_small(DA_CAP_KEYFILE, kbuf, 240) 187 var secret: *u8 = DA_CAP_SECRET 188 var secretlen: i64 = da_slen(DA_CAP_SECRET) 189 if klen > 0 { secret = kbuf; secretlen = klen } 190 let cap2: *i64 = sys_mmap(16) as *i64 191 if da_header(req, req_n, "X-Nishi-Cap:" as *u8, cap2) != 1 { return 0 } 192 let capp: *u8 = ((req as i64) + cap2[0]) as *u8 193 let capl: i64 = cap2[1] 194 if capl <= 0 { return 0 } 195 return capt_verify(secret, secretlen, capp, capl, DA_CAP_TOOL, da_slen(DA_CAP_TOOL), sys_now_realtime_sec()) 196} 197 198func da_http_head(out: *u8, o: i64, status: *u8) -> i64 { 199 o = da_cat(out, o, "HTTP/1.1 " as *u8) 200 o = da_cat(out, o, status) 201 o = da_cat(out, o, "\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n" as *u8) 202 return o 203} 204 205func da_path_is(path: *u8, plen: i64, lit: *u8) -> i64 { 206 let ll: i64 = da_slen(lit) 207 if ll != plen { return 0 } 208 var i: i64 = 0 209 while i < plen { if path[i] != lit[i] { return 0 } i = i + 1 } 210 return 1 211} 212 213func da_index(out: *u8) -> i64 { 214 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 215 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"version\":2,\"routes\":[\"GET /api/dev\",\"POST /api/dev/build {organ}\",\"POST /api/dev/gate {organ}\",\"POST /api/dev/run {organ,arg1..arg3}\",\"GET /api/dev/status\"]}\n" as *u8) 216 return o 217} 218 219func da_do_build(req: *u8, body: i64, req_n: i64, out: *u8, is_gate: i64) -> i64 { 220 // AUTH FIRST: no valid dev_build capability -> 401, BEFORE parsing/execing anything 221 if da_authz(req, req_n) != CAPT_OK { 222 var oa: i64 = da_http_head(out, 0, "401 Unauthorized" as *u8) 223 oa = da_cat(out, oa, "{\"error\":\"dev_build capability required (X-Nishi-Cap); no ambient authority\"}\n" as *u8) 224 return oa 225 } 226 let raw: *u8 = sys_mmap(256) 227 let rn: i64 = da_form_field(req, body, req_n, "organ" as *u8, raw) 228 let name: *u8 = sys_mmap(128) 229 let vn: i64 = da_valid_name(raw, rn, name) 230 if vn < 0 { 231 var o2: i64 = da_http_head(out, 0, "400 Bad Request" as *u8) 232 o2 = da_cat(out, o2, "{\"error\":\"organ name required, [a-z0-9_] only\"}\n" as *u8) 233 return o2 234 } 235 let cap: *u8 = sys_mmap(DA_OUTCAP + 16) 236 let clen: *i64 = sys_mmap(16) as *i64 237 let exit: i64 = da_build_organ(name, cap, clen) 238 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 239 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"organ\":\"" as *u8) 240 o = da_json_esc(out, o, name, vn) 241 o = da_cat(out, o, "\",\"exit\":" as *u8) 242 o = da_catn(out, o, exit) 243 if is_gate==1 { if exit==0 { o = da_cat(out, o, ",\"verdict\":\"GREEN\"" as *u8) } else { o = da_cat(out, o, ",\"verdict\":\"RED\"" as *u8) } } 244 o = da_cat(out, o, ",\"output\":\"" as *u8) 245 o = da_json_esc(out, o, cap, clen[0]) 246 o = da_cat(out, o, "\"}\n" as *u8) 247 sys_munmap(cap, DA_OUTCAP + 16) // free the 262KB capture buffer -- in-process server: no per-request VA leak 248 return o 249} 250 251func da_do_status(out: *u8) -> i64 { 252 let tail: *u8 = sys_mmap(DA_MAGIC_16384) 253 let tn: i64 = da_read_tail("knowledge/status/job_runner.log" as *u8, tail, DA_MAGIC_8192) 254 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 255 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"status\":\"" as *u8) 256 o = da_json_esc(out, o, tail, tn) 257 o = da_cat(out, o, "\"}\n" as *u8) 258 return o 259} 260 261// validate ONE arg token: [a-z0-9_.=-] only, 0..64 chars (empty = present-but-blank -> skipped). Returns len or -1. 262// Rejects space / ';' / '/' / backtick / '..' -> with fixed-argv exec, argument injection is structurally impossible. 263func da_valid_arg(s: *u8, n: i64, out: *u8) -> i64 { 264 if n < 0 { return 0 - 1 } 265 if n > 64 { return 0 - 1 } 266 var i: i64 = 0 267 while i < n { 268 let c: i64 = (s[i] as i64) & 0xff 269 var ok: i64 = 0 270 if c>=97 { if c<=122 { ok=1 } } // a-z 271 if c>=48 { if c<=57 { ok=1 } } // 0-9 272 if c==95 { ok=1 } // _ 273 if c==46 { ok=1 } // . 274 if c==61 { ok=1 } // = 275 if c==45 { ok=1 } // - 276 if ok==0 { return 0 - 1 } 277 out[i] = s[i] 278 i = i + 1 279 } 280 out[n] = 0 as u8 281 return n 282} 283 284// fork/exec a PRE-BUILT organ ./_offc/<name>.elf with up to 3 validated args, capture stdout+stderr. Returns exit code. 285func da_run_organ(name: *u8, vn: i64, a1: *u8, a1n: i64, a2: *u8, a2n: i64, a3: *u8, a3n: i64, capbuf: *u8, caplen: *i64) -> i64 { 286 let path: *u8 = sys_mmap(256) 287 var po: i64 = da_cat(path, 0, "./_offc/" as *u8) 288 var i: i64 = 0; while i < vn { path[po]=name[i]; po=po+1; i=i+1 } 289 po = da_cat(path, po, ".elf" as *u8) 290 path[po] = 0 as u8 291 let tf: i64 = sys_openat_wr(DA_TMP, 0x1a4); if tf >= 0 { sys_close(tf) } 292 let pid: i64 = sys_fork() 293 if pid == 0 { 294 let ofd: i64 = sys_openat_wr(DA_TMP, 0x1a4) 295 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 296 let argv: *i64 = sys_mmap(64) as *i64 297 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 298 var ai: i64 = 1 299 argv[0] = path as i64 300 if a1n > 0 { argv[ai] = a1 as i64; ai=ai+1 } 301 if a2n > 0 { argv[ai] = a2 as i64; ai=ai+1 } 302 if a3n > 0 { argv[ai] = a3 as i64; ai=ai+1 } 303 argv[ai] = 0 304 sys_execve(path, argv, envp) 305 sys_exit(127) 306 return 127 307 } 308 if pid < 0 { caplen[0]=0; return 0 - 1 } 309 let status: *i64 = sys_mmap(16) as *i64 310 sys_wait4(pid, status, 0) 311 caplen[0] = da_read_tail(DA_TMP, capbuf, DA_OUTCAP) 312 return (status[0] >> 8) & 0xff 313} 314 315// is <name> on the run allowlist (knowledge/dev_run_allow.conf, one organ per line)? FAIL-CLOSED (no conf -> deny 316// ALL). The security boundary that makes /api/dev/run safe: a dev_build cap can only run EXPLICITLY-listed organs 317// (gates, exec_engine, sweeps) -- NOT the deploy/root organs (nx_aw_hostctl / nx_aw_sudo) => no privilege-escalation. 318func da_run_allowed(name: *u8, vn: i64) -> i64 { 319 let b: *u8 = sys_mmap(DA_MAGIC_8192) 320 let n: i64 = da_read_small("knowledge/dev_run_allow.conf" as *u8, b, DA_MAGIC_8000) 321 if n == 0 { return 0 } // no/empty allowlist -> deny (fail-closed) 322 var ls: i64 = 0 323 var i: i64 = 0 324 while i <= n { 325 var atend: i64 = 0 326 if i==n { atend=1 } else { if b[i]==(10 as u8) { atend=1 } } 327 if atend==1 { 328 var le: i64 = i 329 if le>ls { if b[le-1]==(13 as u8) { le=le-1 } } // strip trailing CR 330 if (le-ls)==vn { var m: i64=1; var j: i64=0; while j<vn { if b[ls+j]!=name[j] { m=0; j=vn } else { j=j+1 } } if m==1 { return 1 } } 331 ls = i+1 332 } 333 i = i + 1 334 } 335 return 0 336} 337 338// POST /api/dev/run organ=<name>[&arg1=..&arg2=..&arg3=..] -> run the PRE-BUILT _offc organ + capture output. 339// This is the WSL-replacement for RUN (the exec engine, gates-with-args, sweeps): drive any installed organ off-LAN. 340func da_do_run(req: *u8, body: i64, req_n: i64, out: *u8) -> i64 { 341 if da_authz(req, req_n) != CAPT_OK { 342 var oa: i64 = da_http_head(out, 0, "401 Unauthorized" as *u8) 343 oa = da_cat(out, oa, "{\"error\":\"dev_build capability required (X-Nishi-Cap); no ambient authority\"}\n" as *u8) 344 return oa 345 } 346 let raw: *u8 = sys_mmap(256) 347 let rn: i64 = da_form_field(req, body, req_n, "organ" as *u8, raw) 348 let name: *u8 = sys_mmap(128) 349 let vn: i64 = da_valid_name(raw, rn, name) 350 if vn < 0 { 351 var o2: i64 = da_http_head(out, 0, "400 Bad Request" as *u8) 352 o2 = da_cat(out, o2, "{\"error\":\"organ name required, [a-z0-9_] only\"}\n" as *u8) 353 return o2 354 } 355 if da_run_allowed(name, vn) != 1 { 356 var o3: i64 = da_http_head(out, 0, "403 Forbidden" as *u8) 357 o3 = da_cat(out, o3, "{\"error\":\"organ not on the run allowlist (knowledge/dev_run_allow.conf); fail-closed\"}\n" as *u8) 358 return o3 359 } 360 let r1: *u8=sys_mmap(96); let a1: *u8=sys_mmap(96); let af1: i64=da_form_field(req,body,req_n,"arg1" as *u8,r1); var a1n: i64=0 361 if af1>=0 { a1n=da_valid_arg(r1,af1,a1) } 362 let r2: *u8=sys_mmap(96); let a2: *u8=sys_mmap(96); let af2: i64=da_form_field(req,body,req_n,"arg2" as *u8,r2); var a2n: i64=0 363 if af2>=0 { a2n=da_valid_arg(r2,af2,a2) } 364 let r3: *u8=sys_mmap(96); let a3: *u8=sys_mmap(96); let af3: i64=da_form_field(req,body,req_n,"arg3" as *u8,r3); var a3n: i64=0 365 if af3>=0 { a3n=da_valid_arg(r3,af3,a3) } 366 if a1n<0 { if a2n<0 { a2n=0 } } // (any present-but-invalid arg -> 400 below) 367 if a1n<0 { var ob: i64=da_http_head(out, 0, "400 Bad Request" as *u8); ob=da_cat(out,ob,"{\"error\":\"arg must be [a-z0-9_.=-], <=64\"}\n" as *u8); return ob } 368 if a2n<0 { var ob: i64=da_http_head(out, 0, "400 Bad Request" as *u8); ob=da_cat(out,ob,"{\"error\":\"arg must be [a-z0-9_.=-], <=64\"}\n" as *u8); return ob } 369 if a3n<0 { var ob: i64=da_http_head(out, 0, "400 Bad Request" as *u8); ob=da_cat(out,ob,"{\"error\":\"arg must be [a-z0-9_.=-], <=64\"}\n" as *u8); return ob } 370 let cap: *u8 = sys_mmap(DA_OUTCAP + 16) 371 let clen: *i64 = sys_mmap(16) as *i64 372 let exit: i64 = da_run_organ(name, vn, a1, a1n, a2, a2n, a3, a3n, cap, clen) 373 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 374 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"ran\":\"" as *u8) 375 o = da_json_esc(out, o, name, vn) 376 o = da_cat(out, o, "\",\"exit\":" as *u8) 377 o = da_catn(out, o, exit) 378 o = da_cat(out, o, ",\"output\":\"" as *u8) 379 o = da_json_esc(out, o, cap, clen[0]) 380 o = da_cat(out, o, "\"}\n" as *u8) 381 sys_munmap(cap, DA_OUTCAP + 16) // free the 262KB capture buffer -- in-process server: no per-request VA leak 382 return o 383} 384 385// ---- the pure router ---- 386func da_handle(req: *u8, req_n: i64, out: *u8) -> i64 { 387 // method = req[0..mlen) up to the first space 388 var mi: i64 = 0 389 var mlen: i64 = req_n 390 while mi < req_n { if (req[mi] as i64)==32 { mlen=mi; mi=req_n } else { mi=mi+1 } } 391 // path = from after that space to the next space 392 var ps: i64 = mlen + 1 393 var pe: i64 = req_n 394 var pi: i64 = ps 395 while pi < req_n { if (req[pi] as i64)==32 { pe=pi; pi=req_n } else { pi=pi+1 } } 396 let path: *u8 = ((req as i64) + ps) as *u8 397 let plen: i64 = pe - ps 398 let is_post: i64 = 0 + (req[0]==(80 as u8)) // 'P' = POST 399 let body: i64 = da_body_off(req, req_n) 400 401 if da_path_is(path, plen, "/api/dev" as *u8)==1 { return da_index(out) } 402 if da_path_is(path, plen, "/api/dev/" as *u8)==1 { return da_index(out) } 403 if da_path_is(path, plen, "/api/dev/status" as *u8)==1 { return da_do_status(out) } 404 if is_post==1 { if da_path_is(path, plen, "/api/dev/build" as *u8)==1 { return da_do_build(req, body, req_n, out, 0) } } 405 if is_post==1 { if da_path_is(path, plen, "/api/dev/gate" as *u8)==1 { return da_do_build(req, body, req_n, out, 1) } } 406 if is_post==1 { if da_path_is(path, plen, "/api/dev/run" as *u8)==1 { return da_do_run(req, body, req_n, out) } } 407 var o: i64 = da_http_head(out, 0, "404 Not Found" as *u8) 408 o = da_cat(out, o, "{\"error\":\"no such dev route\"}\n" as *u8) 409 return o 410}