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}