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