code wiki / _hdl_build / nx_job_run.nx

nx_job_run.nx source

↩ module page · 195 lines · 9918 B

1// nx_job_run.nx v3 -- GUARDED ASYNC JOB RUNNER. Eats the async-fetch >15s edge-timeout debt the 2// good-form way: not a cron workaround, not an arbitrary executor. 3// SAFE BY CONSTRUCTION (rule 26 never-brick / rule 12 boundary-defense) -- FOUR fail-closed gates: 4// (1) CLASS: deny-by-class via nx_job_launch_bound -- WIDENED 2026-07-31, and this is exactly the 5// "filed rung" the previous line named for itself. The old rule launched ONLY organs whose 6// name contained *research_fetch*, which meant the ecosystem had NO route to run a general 7// organ and every generator fell back to shell. Now: generators, rollups, censuses and page 8// emitters are launchable; DAEMONS and SERVERS are refused (they never exit, so an async 9// launch wedges the slot forever) and so is the CONTROL PLANE (deploy/promote/restart/kill/ 10// mgmt/hostctl/supervisor). Fail-closed on an empty name. Deny-by-class, not allow-by-one- 11// substring: a new generator is launchable the day it is allowlisted with no edit here, while 12// a new daemon is refused by default -- a rule nothing must remember beats a list somebody 13// must maintain. Proven by nx_job_launch_bound_gate 15/15 GREEN, including back-compat teeth 14// for every organ the old *research_fetch* rule allowed. 15// (2) VETTED: the name must resolve to a GREEN elf in tool_allowlist.conf -- the SAME trust 16// boundary nx_plan_run uses; NEVER an arbitrary caller-supplied path. 17// (3) NO-PIN-BYPASS: refuses pinned rows -- can never run a pinned elf with non-pinned args. 18// (4) SCRATCH-ONLY: out-file confined to /tmp/ with no '..' -- writes land only on disposable scratch. 19// Mechanism (mirrors nx_hostctl cmd_buildrun): fork -> child redirects stdout+stderr to out-file via 20// sys_dup3 + execve the resolved elf with the caller's [args...]; PARENT returns IMMEDIATELY with a 21// job handle (does NOT wait4) -> child is orphaned + reaped by init, runs to completion async while 22// /mcp already answered -> a slow research-fetch no longer times out the edge. 23// nx_job_run <tool-name(*research_fetch*)> <out-file:/tmp/...> [args...] -> JOB-STARTED pid=.. elf=.. out=.. 24// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 25import "nx_syscalls.nx" 26import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 27import "nx_job_launch_bound.nx" 28const JR_MAGIC_1024: i64 = 1024 29 30const JR_OMODE: i64 = 0x1a4 31const JR_STDOUT: i64 = 1 32const JR_STDERR: i64 = 2 33const JR_MAXARGV: i64 = 32 34const JR_NL: i64 = 10 35const JR_TAB: i64 = 9 36const JR_HASH: i64 = 35 37 38func jr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 39func jr_puts(s: *u8) -> i64 { sys_write(JR_STDOUT, s, jr_slen(s)); return 0 } 40func jr_werr(s: *u8) -> i64 { sys_write(JR_STDERR, s, jr_slen(s)); return 0 } 41// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 42// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 43// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 44// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 45func jr_putn(v: i64) -> i64 { nxi_out(v); return 0 } 46func jr_slice_eq(buf: *u8, start: i64, end: i64, s: *u8) -> i64 { 47 var i: i64 = start 48 var j: i64 = 0 49 while i < end { 50 if s[j] == (0 as u8) { return 0 } 51 if buf[i] != s[j] { return 0 } 52 i = i + 1 53 j = j + 1 54 } 55 if s[j] != (0 as u8) { return 0 } 56 return 1 57} 58func jr_contains(hay: *u8, needle: *u8) -> i64 { 59 if needle[0] == (0 as u8) { return 1 } 60 var i: i64 = 0 61 while hay[i] != (0 as u8) { 62 var j: i64 = 0 63 var m: i64 = 1 64 while m == 1 { 65 if needle[j] == (0 as u8) { return 1 } 66 if hay[i+j] == (0 as u8) { m = 0 } else { if hay[i+j] != needle[j] { m = 0 } else { j = j + 1 } } 67 } 68 i = i + 1 69 } 70 return 0 71} 72func jr_is_tmp(p: *u8) -> i64 { 73 if p[0] != (47 as u8) { return 0 } 74 if p[1] != (116 as u8) { return 0 } 75 if p[2] != (109 as u8) { return 0 } 76 if p[3] != (112 as u8) { return 0 } 77 if p[4] != (47 as u8) { return 0 } 78 return 1 79} 80func jr_has_dotdot(p: *u8) -> i64 { 81 var i: i64 = 0 82 while p[i] != (0 as u8) { 83 if p[i] == (46 as u8) { if p[i+1] == (46 as u8) { return 1 } } 84 i = i + 1 85 } 86 return 0 87} 88 89func main(argc: i64, argv: *i64) -> i64 { 90 if argc < 3 { jr_werr("usage: nx_job_run <tool-name(allowlisted GREEN, non-daemon)> <out-file:/tmp/...> [args...]\n" as *u8); sys_exit(1); return 1 } 91 let name: *u8 = argv[1] as *u8 92 let outp: *u8 = argv[2] as *u8 93 94 if jlb_denied(name) == 1 { jr_werr("REFUSED: daemons, servers, supervisors and control-plane organs (deploy/promote/restart/kill/mgmt/hostctl) are never async-launchable -- they outlive the call or mutate the live plane. Generators are launchable once vetted GREEN in tool_allowlist.conf.\n" as *u8); sys_exit(6); return 6 } 95 if jr_is_tmp(outp) == 0 { jr_werr("REFUSED: out-file must be under /tmp/ (write-surface defense)\n" as *u8); sys_exit(2); return 2 } 96 if jr_has_dotdot(outp) == 1 { jr_werr("REFUSED: out-file must not contain '..'\n" as *u8); sys_exit(2); return 2 } 97 98 let lenp: *i64 = sys_mmap(8) as *i64 99 lenp[0] = 0 100 let buf: *u8 = sys_read_file("tool_allowlist.conf" as *u8, lenp) 101 let n: i64 = lenp[0] 102 if n <= 0 { jr_werr("REFUSED: tool registry unreadable\n" as *u8); sys_exit(3); return 3 } 103 104 let elfbuf: *u8 = sys_mmap(JR_MAGIC_1024) 105 var found: i64 = 0 106 var was_pinned: i64 = 0 107 var i: i64 = 0 108 while i < n { 109 var le: i64 = i 110 var s: i64 = 1 111 while s == 1 { if le >= n { s = 0 } else { if buf[le] == (JR_NL as u8) { s = 0 } else { le = le + 1 } } } 112 if le > i { if buf[i] != (JR_HASH as u8) { 113 var ne: i64 = i 114 var s2: i64 = 1 115 while s2 == 1 { if ne >= le { s2 = 0 } else { if buf[ne] == (JR_TAB as u8) { s2 = 0 } else { ne = ne + 1 } } } 116 if ne > i { if found == 0 { if jr_slice_eq(buf, i, ne, name) == 1 { 117 var ps: i64 = ne + 1 118 var pe: i64 = ps 119 var s3: i64 = 1 120 while s3 == 1 { if pe >= le { s3 = 0 } else { if buf[pe] == (JR_TAB as u8) { s3 = 0 } else { pe = pe + 1 } } } 121 var gs: i64 = pe + 1 122 var ge: i64 = gs 123 var s4: i64 = 1 124 while s4 == 1 { if ge >= le { s4 = 0 } else { if buf[ge] == (JR_TAB as u8) { s4 = 0 } else { ge = ge + 1 } } } 125 if jr_slice_eq(buf, gs, ge, "GREEN" as *u8) == 1 { 126 if ge >= le { 127 if pe > ps { 128 var eo: i64 = 0 129 var k: i64 = ps 130 while k < pe { elfbuf[eo] = buf[k]; eo = eo + 1; k = k + 1 } 131 elfbuf[eo] = 0 as u8 132 found = 1 133 } 134 } else { was_pinned = 1 } 135 } 136 } } } 137 } } 138 i = le + 1 139 } 140 141 if found == 0 { 142 if was_pinned == 1 { jr_werr("REFUSED: '" as *u8); jr_werr(name); jr_werr("' is PINNED; nx_job_run only launches UNPINNED GREEN tools (anti-pin-bypass)\n" as *u8); sys_exit(5); return 5 } 143 jr_werr("REFUSED: '" as *u8); jr_werr(name); jr_werr("' is not an unpinned GREEN tool in the registry\n" as *u8); sys_exit(4); return 4 144 } 145 146 // ★EXISTENCE CHECK BEFORE THE FORK (2026-08-01, debt 1785623939). Every guard above validates 147 // the NAME -- registered, GREEN, unpinned, not a daemon -- and NOTHING validated that the elf 148 // the row points at still EXISTS. After execve fails in the child there is no way to report it: 149 // the parent has already returned JOB-STARTED and never wait4's. Reproduced live: a tool row 150 // registered against a `.sov.elf.new` STAGING artifact, which /api/promote then consumed, 151 // printed a confident `JOB-STARTED pid=8938` and simply never wrote its out-file. ABSENCE OF 152 // OUTPUT WAS THE ONLY SIGNAL -- the same reported-green-wrote-nothing shape that cost a whole 153 // session elsewhere today. 154 // ★★★★★A LAUNCHER THAT VALIDATES THE NAME BUT NOT THE ARTIFACT REPORTS SUCCESS FOR A PROCESS 155 // THAT CANNOT START. Fail LOUD here, where a caller can still see it, instead of silently in a 156 // child nobody reaps. 157 let jr_probe: i64 = sys_openat_rd(elfbuf) 158 if jr_probe < 0 { 159 jr_werr("REFUSED: registry row for '" as *u8); jr_werr(name) 160 jr_werr("' points at a MISSING elf: " as *u8); jr_werr(elfbuf) 161 jr_werr("\n the row is dangling -- most often it was registered against a .sov.elf.new STAGING\n" as *u8) 162 jr_werr(" artifact that a later /api/promote consumed. Rebuild the target to regenerate it, or\n" as *u8) 163 jr_werr(" re-register the name against the PROMOTED .elf. BUILD -> PROMOTE -> THEN REGISTER.\n" as *u8) 164 sys_exit(7); return 7 165 } 166 sys_close(jr_probe) 167 168 let pid: i64 = sys_fork() 169 if pid == 0 { 170 let of: i64 = sys_openat_wr(outp, JR_OMODE) 171 if of >= 0 { sys_dup3(of, JR_STDOUT, 0); sys_dup3(of, JR_STDERR, 0) } 172 let av: *i64 = sys_mmap(JR_MAXARGV*8) as *i64 173 av[0] = elfbuf as i64 174 var a: i64 = 3 175 var kk: i64 = 1 176 while a < argc { if kk < JR_MAXARGV - 1 { av[kk] = argv[a]; kk = kk + 1 } a = a + 1 } 177 av[kk] = 0 178 let envp: *i64 = sys_mmap(16) as *i64 179 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 180 envp[1] = 0 181 sys_execve(elfbuf, av, envp) 182 sys_exit(127) 183 } 184 jr_puts("JOB-STARTED pid=" as *u8) 185 jr_putn(pid) 186 jr_puts(" tool=" as *u8) 187 jr_puts(name) 188 jr_puts(" elf=" as *u8) 189 jr_puts(elfbuf) 190 jr_puts(" out=" as *u8) 191 jr_puts(outp) 192 jr_puts("\n" as *u8) 193 sys_exit(0) 194 return 0 195}