code wiki / _hdl_build / nx_supervised_dispatch_tamper.nx
nx_supervised_dispatch_tamper.nx
buildroot/runtime/_hdl_build/nx_supervised_dispatch_tamper.nx
about
nx_supervised_dispatch.nx -- WMS rung M5: OS / KERNEL SUPERVISOR BINDING.
module: nishi-core.autonomy.supervised_dispatch
capability: BIND_DISPATCH_TO_SUPERVISED_JOB (a dispatched rung becomes a real,
resource-LIMITED, reaped, RESTART-on-death supervised job)
WHAT THIS UNIFIES (the M5 payoff): M4 (nx_dispatch_lease) LEASES the next ready rung
but never RUNS it; M1 (nx_heartbeat_monitor) detects a stalled stream but never ACTS;
the supervisor policy (nx_supervisor) decides RESTART/ESCALATE but never forks. M5
is the missing seam: it FORKS the leased rung as a governed child (with a kernel
resource limit = prlimit64 RLIMIT_AS, the Linux analog of a Job-Object memory limit),
beats its heartbeat, and -- when M1 sees a missing heartbeat (a crashed job) -- it
RESTARTS/reassigns (a second supervised run). Conductor loop + kernel scheduler +
workstream manager collapse into ONE control plane.
REUSE / lineage (compose, do not reinvent -- check-registries-before-building):
- dl_dispatch / dl_try_lease / dl_release <- nx_dispatch_lease.nx (M4): lease the rung
- an_newcx / an_load / an_find / an_id_at <- nx_assign_core (surfaced via M4)
- hb_beat_at / hbm_scan / HB_STALLED/HB_ALIVE <- nx_heartbeat_monitor.nx (M1)
- fa_cat / fa_catn / fa_appendz <- nx_framed_append.nx (R0b): the locked
single-write status discipline (every record = ONE buffer -> ONE atomic write)
- the fork -> child-setup -> sys_wait4 status-decode model is the nx_container.nx idiom
(lo = status & 0xFF; exited iff lo==0; code = (status>>8)&0xFF) -- reimplemented HERE
(NOT imported) because nx_container.nx imports nx_syscalls_x86_64.nx and that would
surface a SECOND syscall table against M1/M4's nx_syscalls.nx = the double-import
rc=6 trap. M5 keeps a SINGLE syscall surface (nx_syscalls.nx via all three imports).
SOVEREIGN PAYLOAD (no /bin dependency): the supervised child does NOT execve a foreign
ELF. After fork it caps ITSELF (nx_prlimit) then runs an IN-ORGAN payload selector --
the same organ post-fork executing a payload function (the nx_container fork model minus
execve). payload=0 clean work+exit(0); payload=1 "crash" exit(70); payload=2 hang
(finite busy then exit WITHOUT beating = the never-beats stall the gate KILLs/detects).
SUPERVISOR POLICY (inlined, ~ nx_supervisor's 4-state restart-budget machine): a STALLED
job under budget -> RESTART (respawn + restarts++ + fresh beat); over budget -> ESCALATE
(bounded, no thrash storm); an ALIVE / clean-exited job -> NONE (the no-thrash neg-ctrl).
WRITE DISCIPLINE (the torn-line bug we must NOT reintroduce): every status record is
assembled into ONE buffer and emitted with a SINGLE locked fa_appendz (the cn_emit_*
discipline) -- never a sequence of sys_write() calls. Additive: new file only; one tiny
dependencies 3 imports · 1 importers
imports: nx_dispatch_lease.nxnx_heartbeat_monitor.nxnx_framed_append.nx
imported by: nx_supervised_dispatch_tamper_gate.nx
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 46 | const SD_MAGIC_100000: i64 = 100000 |
| 48 | const SD_REC_CAP: i64 = 256 // bounded status record size (no magic number) |
| 49 | const SD_SIGKILL: i64 = 9 // the crash signal for the controlled-kill test |
| 50 | const SD_MAXRESTART: i64 = 3 // restart budget; over -> ESCALATE (no thrash storm) |
| 51 | const SD_AS_LIMIT: i64 = 0x10000000 // 256 MiB address-space cap = the resource "limit" |
| 54 | const SD_ACTION_NONE: i64 = 0 // alive / clean-exited -> do nothing (no-thrash path) |
| 55 | const SD_ACTION_RESTART: i64 = 1 // stalled + under budget -> respawn |
| 56 | const SD_ACTION_ESCALATE: i64 = 3 // stalled + budget exhausted -> escalate (bounded) |
| 59 | const SD_RUNNING: i64 = 0 |
| 60 | const SD_EXITED_OK: i64 = 1 |
| 61 | const SD_CRASHED: i64 = 2 |
| 62 | const SD_ESCALATED: i64 = 3 |
| 65 | const SD_PAY_CLEAN: i64 = 0 // compute + exit(0): a job that finishes normally |
| 66 | const SD_PAY_CRASH: i64 = 1 // exit(70): a job that dies non-zero |
| 67 | const SD_PAY_HANG: i64 = 2 // finite busy, exit WITHOUT beating (the stall payload) |
| 71 | const SD_J_WS: i64 = 0 |
| 72 | const SD_J_PID: i64 = 1 |
| 73 | const SD_J_RESTARTS: i64 = 2 |
| 74 | const SD_J_STATE: i64 = 3 |
| 75 | const SD_J_LIMIT: i64 = 4 |
| 76 | const SD_J_EXIT: i64 = 5 |
functions
| 78 | func sd_job_new() -> *i64 |
| 85 | func sd_getpid() -> i64 { return __syscall(39, 0, 0, 0, 0, 0, 0) } |
| 92 | func sd_apply_limit(limit_as: i64) -> i64 |
| 114 | func sd_child_payload(hbpath: *u8, ws: i64, payload: i64, limit_as: i64, beats: i64, |
| 145 | func sd_spawn_supervised(hbpath: *u8, job: *i64, payload: i64, beat_epoch: i64) -> i64 |
| 168 | func sd_poll(job: *i64) -> i64 |
| 193 | func sd_dispatch_and_supervise(cx: *i64, dir: *u8, hbpath: *u8, job: *i64, |
| 214 | func sd_supervise_tick(hbpath: *u8, now: i64, thresh: i64, job: *i64, |
| 244 | func sd_emit(path: *u8, verb: *u8, ws: i64, pid: i64, restarts: i64, action: i64, |
| 264 | func sd_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } called by 1: main |
| 265 | func sd_n(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m; sys_write(1,"-\x00" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; 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}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 } called by 1: main |
| 271 | func main() -> i64 |