code wiki / _hdl_build / nx_job_launch_bound.nx

nx_job_launch_bound.nx source

↩ module page · 72 lines · 3689 B

1// nx_job_launch_bound.nx -- the never-brick bound on which organs may be ASYNC-LAUNCHED. 2// 3// WHY THIS EXISTS: nx_job_run refused every organ whose name did not contain "research_fetch", with 4// its own comment naming the broader set a filed rung. That single substring is why the API-first 5// doctrine still has no route that runs a general organ, and why every generator in the ecosystem 6// falls back to shell. Widening it is the fix; widening it WITHOUT a bound would be the brick. 7// 8// THE REAL SECURITY BOUNDARY IS NOT HERE. nx_job_run already requires the name to match a row in 9// tool_allowlist.conf -- a human vetted every launchable organ GREEN -- and already confines output 10// to /tmp with no "..". This lib adds the missing NEVER-BRICK half: refuse the CLASSES that either 11// outlive the call (daemons/servers never exit, so an async launch wedges the slot forever) or 12// reach the control plane (deploy/promote/restart/kill/mgmt/hostctl/supervisor). That is the same 13// reasoning /api/gate_run states for bounding itself to verifiers, applied to generators. 14// 15// DENY-BY-CLASS, not allow-by-one-name: a new generator is launchable the day it is allowlisted, 16// with no edit here, while a new daemon is refused by default. A rule nothing has to remember beats 17// a list somebody must maintain. 18// license_tier: ORIGINAL No hw writes (Rule 26). 19import "nx_syscalls.nx" 20 21// ---- SEGMENT-PREFIX, NOT `contains` (defect found by self-review 2026-07-31) ---- 22// A bare substring test is wrong in BOTH directions: 23// FALSE DENY -- "observe" contains "serve", "skill" contains "kill". nx_observe_* / nx_skill_* 24// would be refused: generators shoved back onto the shell by the lib meant to 25// free them, the exact failure this file exists to prevent. 26// FALSE ALLOW -- the obvious repair (demand '_' on both sides) then MISSES "nx_server", because 27// "serve" is followed by 'r'. A server that escapes this bound is the brick. 28// Rule: split on '_', refuse if ANY SEGMENT STARTS WITH a denied stem. Stems, so variants are 29// caught for free (supervisor, server, killer). 30const JLB_US: i64 = 95 31const JLB_NUL: i64 = 0 32 33// Terminates at the haystack NUL, so it cannot over-read -- the guard el_match lacks. 34func jlb_seg_starts(hay: *u8, s: i64, stem: *u8) -> i64 { 35 var j: i64 = 0 36 while stem[j] != (0 as u8) { 37 if hay[s+j] == (0 as u8) { return 0 } 38 if hay[s+j] != stem[j] { return 0 } 39 j = j + 1 40 } 41 return 1 42} 43 44func jlb_seg_prefix(name: *u8, stem: *u8) -> i64 { 45 if (name as i64) == 0 { return 0 } 46 var i: i64 = 0 47 var at_seg: i64 = 1 48 while 1 == 1 { 49 let c: i64 = name[i] as i64 50 if c == JLB_NUL { return 0 } 51 if at_seg == 1 { if jlb_seg_starts(name, i, stem) == 1 { return 1 } } 52 if c == JLB_US { at_seg = 1 } else { at_seg = 0 } 53 i = i + 1 54 } 55 return 0 56} 57 58// 1 = REFUSE this name for async launch. Fail-closed on an empty/NULL name. 59func jlb_denied(name: *u8) -> i64 { 60 if (name as i64) == 0 { return 1 } 61 if name[0] == (0 as u8) { return 1 } 62 if jlb_seg_prefix(name, "daemon" as *u8) == 1 { return 1 } 63 if jlb_seg_prefix(name, "serve" as *u8) == 1 { return 1 } 64 if jlb_seg_prefix(name, "supervis" as *u8) == 1 { return 1 } 65 if jlb_seg_prefix(name, "hostctl" as *u8) == 1 { return 1 } 66 if jlb_seg_prefix(name, "mgmt" as *u8) == 1 { return 1 } 67 if jlb_seg_prefix(name, "deploy" as *u8) == 1 { return 1 } 68 if jlb_seg_prefix(name, "promote" as *u8) == 1 { return 1 } 69 if jlb_seg_prefix(name, "restart" as *u8) == 1 { return 1 } 70 if jlb_seg_prefix(name, "kill" as *u8) == 1 { return 1 } 71 return 0 72}