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}