code wiki / (root) / nx_clock_driver_sched_ar.nx

nx_clock_driver_sched_ar.nx source

↩ module page · 1512 lines · 90920 B

1// nx_clock_sched.nx -- RENAMED FROM nx_clock.nx 2026-07-31 (lib-reconcile). PERMANENT FIX for a 2// two-libraries-one-name collision: runtime/nx_clock.nx is the TIMING library (nx_clock_monotonic_ns, 3// 62 importers); THIS file is an unrelated tickless JOB SCHEDULER (clk_* registry + dispatcher) that 4// only shared the filename. nx_cc binds an import to the IMPORTER'S OWN DIRECTORY FIRST, so every 5// _hdl_build organ importing nx_clock.nx silently got the SCHEDULER -- _clk_probe.nx, whose whole 6// purpose is to prove nx_clock_monotonic_ns works, could not resolve it. The RENAME is the fix. 7// so we avoid issues ... dont have a million pulses and daemons"). Researched (knowledge/fetched/sched_*.raw: 8// clock-distribution = ONE oscillator -> a tree of DIVIDERS; PLL derives every frequency from ONE reference; 9// cron = ONE daemon reads ONE table of timed jobs; tickless = don't burn a constant tick when idle). The S-class 10// pattern is identical: ONE tick source + ONE durable JOB REGISTRY + ONE dispatcher. Every periodic capability 11// REGISTERS a job (name, interval-in-ticks) = a divider off the single clock -- it does NOT spin up its own 12// daemon/pulse loop. So N capabilities cost ONE loop, not N. This library is the registry + dispatcher; the single 13// tick source drives it (one external spark, like a crystal). license_tier: ORIGINAL 14import "nx_syscalls.nx" 15import "nx_clock_driver_boundary_ar.nx" 16import "nx_clock_caps.nx" 17import "nx_ioadmit_lib.nx" // ioa_measure / ioa_spawn_budget: THE I/O-storm ruler nx_build_admit uses -- composed here so the clock and the build gate can never disagree about whether the box is in a storm (2026-09-02) 18const CLK_MAGIC_1000000: i64 = 1000000 19 20const CLK_MAXJOBS: i64 = 512 // WAS 128 (2026-08-19): the desired plane hit 128/128 and every 21 // over-cap path was a SILENT drop -- a beat that vanishes at merge. 22 // 4x headroom; clk_register now refuses LOUDLY at the cap. Residual, 23 // named: the inline merge-at-cap skips stay quiet above 512. 24// DUTY-CYCLE CEILING FOR A COST-OVERRUNNING JOB (2026-08-06). Re-arming an overrunning job to exactly 25// its own runtime still leaves it consuming HALF the serial scheduler -- MEASURED: nx_worldgen_gate runs 26// 289s against a declared 120s period, so plain backoff took the blackout from ~96pct to ~50pct and 27// sitecheck stayed frozen through every run. Backing off to runtime*9 caps any single job at ~1/10 of 28// the scheduler. NOT A MAGIC NUMBER: it is the reciprocal of the duty-cycle budget (9 => <=10pct), and 29// it is the ONLY knob here, so raising it loosens the ceiling monotonically and nothing else moves. 30// (STAR)A SERIAL SCHEDULER MUST BOUND WHAT FRACTION OF ITSELF ONE JOB CAN OWN, BECAUSE EVERY OTHER JOB'S 31// LIVENESS IS THAT FRACTION'S COMPLEMENT. 32const CLK_HOGDUTY: i64 = 9 33// ---- PER-DISPATCH DEADLINE (2026-08-06): ADOPTION, NOT INVENTION ------------------------------- 34// nx_guarded_run.nx ALREADY solved "fork + wait4 WNOHANG + deadline + SIGKILL" and a dozen organs use 35// it (nx_tool_run, nx_sweep_core, nx_boot_revive, nx_web_crawl_step...). The clock simply never adopted 36// it and kept a bare BLOCKING sys_wait4(pid, st, 0) -- which is why one child could own the scheduler 37// indefinitely (debt 1786056459; proven live: 475s+ stall and the 1800s budget overrun by 42s). 38// (STAR)THE ESTATE HAD ALREADY SOLVED IT AND THE CLOCK DID NOT LOOK -- an ADOPTION gap, not a missing 39// primitive. Inlined rather than imported because nx_guarded_run.nx carries its own main() self-test. 40// nx_kill is the SANCTIONED wrapper: nx_syscalls notes the compiler BAKES bodies BY NAME, so a function 41// literally named sys_kill emits syscall 8; nx_kill passes rv64 129 which the sovereign table maps to 42// x86_64 62. Using the raw x86 62 here would have silently become lseek(8) and never killed anything. 43// DEADLINE = ONE FULL WINDOW: a single dispatch longer than the entire window is starving that window 44// BY DEFINITION, and it reaps nothing measured legitimate (worldgen 289s/286s, segguard ~700s). 45const CLK_SIGKILL: i64 = 9 46const CLK_DISPATCH_POLL_MS: i64 = 250 47// PER-JOB KILL DEADLINE. 900000ms = 900s = HALF the 1800s scheduling window (TLN_WINDOW_SECS). 48// WAS 1800000ms = the ENTIRE window, which is not a deadline at all: a single job could legally consume 49// the whole scheduler and the guard could not fire before the window ended anyway. A BOUND EQUAL TO THE 50// BUDGET CANNOT BIND. The call site below sells this as the fix for "the single line that let one child 51// own the clock" -- the bounded wait DID replace a bare blocking wait4, but the bound was then set to the 52// full budget, so the protection was nominal. 53// DERIVED, NOT CHOSEN: half the window means no single job can ever take more than half the scheduler, 54// and it carries 2.2x margin over the largest LEGITIMATE runtime measured on 2026-08-14 (max_exec across 55// three consecutive windows: 41s, 92s, and 408s for nx_compare_beat.elf). If that margin is ever wrong 56// the failure is VISIBLE, not silent: a job killed here is logged by clk_actlog with code 124 (the shell 57// timeout convention), so an over-tight bound surfaces as a named row rather than a mystery. 58// HONEST SCOPE: this bounds the PATHOLOGICAL case only. It does NOT fix the starvation measured that day 59// (b=11 -> 16 -> 23), which is cumulative time on a SERIAL dispatcher -- exec 761s -> 990s -> 1167s of an 60// 1800s window -- not one job monopolising. That needs bounded concurrency in clk_run_edf (debt 3952). 61const CLK_DISPATCH_DEADLINE_MS: i64 = 900000 62const CLK_NAMEW: i64 = CLK_CMD_CAP // bytes per name/organ slot -- DERIVED from the ONE shared 63 // command cap in nx_clock_caps.nx, the SAME const nx_clockjob's 64 // CJ_ROWCAP is derived from, so the reader can never hold less than 65 // the writer admits. WAS 128 (2026-08-03..08-22): every command 66 // longer than 127 bytes was SILENTLY CUT at merge/save/load -- 7 67 // live rows executed truncated argv (gateroster beat ran with 68 // deadline 1800 instead of 180000; admit/refusalshape/alertscore 69 // wrote knowledge/gateroster.c, knowledge/stat and kno). Before 70 // that it WAS 48 (debt 1784413227): 51/63-char paths fork-failed 71 // 127 on every tick. A SLOT NARROWER THAN WHAT THE WRITER ADMITS 72 // IS A SILENT TRUNCATOR WEARING A CONSTANT -- bind both ends to 73 // ONE imported const, never mirror a number. 74const CLK_ROWW: i64 = 2*CLK_NAMEW + 64 // per-row serialization budget, DERIVED, not chosen: 75 // 2 slots + 2 i64 decimals (<=20 bytes each) + 3 tabs + newline 76 // = 2*CLK_NAMEW + 44, held with 64 so the two numbers can never 77 // outgrow it (a hand-counted width beside a widened slot is the 78 // drift class this whole change retires) 79const CLK_REG: *u8 = "knowledge/sched/jobs.tsv" // RETIRED LEGACY PATH (2026-08-03, debt 1785792856): 80 // kept only so old fixtures parse; the live state SSOT is the 81 // clocksched- seg-store plane (clk_load_state / clk_save_plane below) 82 83// ---- STORM-AWARE DISPATCH FOR HEAVY BEATS (2026-09-02, /compare/loadgov LV14) ---------------------- 84// MEASURED THE SAME DAY: procchurn read cpu busy 24.5 percent with blocked >> running (I/O-bound), the 85// D-state roster carried nx_web_crawl_step (706 s), nx_secret_scan and nx_web_shard_compact -- all CLOCK 86// BEATS -- and every seat build was refused by admission for the same storm those beats were feeding. 87// This dispatcher had NO admission at all: a due job forked regardless of the box, so the beats that 88// cause a storm fired INTO it while the one lane that does check admission (the build queue) waited. 89// A SCHEDULER THAT CHECKS NOTHING IS THE STORM'S FEEDER, AND THE POLITE LANE PAYS FOR IT. 90// DESIGN, data-driven and fail-open: a job is HEAVY only if its NAME is listed in CLK_HEAVY_CONF (one 91// exact name per line; an unlisted job dispatches byte-for-byte as before, and an absent conf lists 92// nothing). A heavy job that is due while the shared ruler reports ZERO spawn budget (procs_blocked at 93// the storm line, the same conjunct nx_build_admit refuses on) is DEFERRED: re-armed to now plus a 94// fraction of its own period, counted, and announced on the in-flight beat as deferred-storm. It still 95// runs -- one storm cannot park it past CLK_DEFER_MAX_S per probe -- and EDF's own starvation report 96// still names it if the deferral compounds. An UNREADABLE ruler never defers: an axis that cannot see 97// must abstain, and here abstaining means the pre-change behaviour, never a stalled clock. 98// The instruments themselves (resmon, memvel, procchurn, sitecheck, netobs, tlsprobe, kaprobe) must 99// NEVER be listed: a detector deferred by the storm it detects is the axis-blind defect wearing a conf. 100const CLK_HEAVY_CONF: *u8 = "knowledge/status/clock_heavy.conf" 101const CLK_DEFER_DIV: i64 = 4 // re-arm at interval/4: a 1800 s beat re-checks every 450 s, so a storm costs it at most 3 probes before its own next period 102const CLK_DEFER_MIN_S: i64 = 60 // floor: the shortest re-check that is not a busy loop against /proc/stat 103const CLK_DEFER_MAX_S: i64 = 900 // cap: half a window, the same bound CLK_DISPATCH_DEADLINE_MS already places on one job 104// PURE: exact-whole-line membership of `name` in a newline-separated conf (CR-tolerant), the same 105// grammar the tools daemon's async_only_tools.conf reader uses, so a name with a suffix can never match. 106const CLKRS_CONF: *u8 = "knowledge/status/clock_reserved.conf" 107const CLKRS_NONE: i64 = 0 - 1 108const CLKRS_INVALID: i64 = 0 - 2 109const CLKRS_EINTR: i64 = 0 - 4 110const CLKRS_WNOHANG: i64 = 1 111const CLKRS_SIGNAL_EXIT_BASE: i64 = 128 // shell signal-exit convention 112const CLKRS_DISP: i64 = 0 113const CLKRS_STARVED: i64 = 1 114const CLKRS_LATE: i64 = 2 115const CLKRS_MAXMISSED: i64 = 3 116const CLKRS_STARVIDX: i64 = 4 117const CLKRS_MAXEXEC: i64 = 5 118const CLKRS_FOREGROUND_EXEC: i64 = 6 119const CLKRS_ERRORS: i64 = 7 120const CLKRS_N: i64 = 14 121const CLKRS_FATAL: i64 = 8 122const CLKRS_LOST_INDEX: i64 = 9 123const CLKRS_LOST_PID: i64 = 10 124const CLKRS_LOST_WAIT: i64 = 11 125const CLKRS_FOREGROUND_DISP: i64 = 12 126const CLKRS_KNOWN_OWNED: i64 = 13 127const CLKRS_FATAL_RC: i64 = 0 - 3 128const CLKRS_WAIT_RUNNING: i64 = 0 129const CLKRS_WAIT_REAPED: i64 = 1 130const CLKRS_WAIT_INTERRUPTED: i64 = 2 131const CLKRS_WAIT_LOST: i64 = 3 132const CLKRS_OUT_ERRORS: i64 = 12 133const CLKRS_OUT_FATAL: i64 = 13 134const CLKRS_OUT_LOST_INDEX: i64 = 14 135const CLKRS_OUT_LOST_PID: i64 = 15 136const CLKRS_OUT_LOST_WAIT: i64 = 16 137const CLKRS_OUT_KNOWN_OWNED: i64 = 17 138const CLKRS_OUT_N: i64 = 18 139 140func clk_heavy_listed(conf: *u8, n: i64, name: *u8) -> i64 { 141 if n <= 0 { return 0 } 142 var nl: i64 = 0 143 while name[nl] != (0 as u8) { nl = nl + 1 } 144 if nl == 0 { return 0 } 145 var i: i64 = 0 146 var hit: i64 = 0 147 while i < n { 148 var e: i64 = i 149 var eol: i64 = 0 150 while eol == 0 { 151 if e >= n { eol = 1 } else { if conf[e] == (10 as u8) { eol = 1 } else { e = e + 1 } } 152 } 153 var len: i64 = e - i 154 if len > 0 { if conf[i + len - 1] == (13 as u8) { len = len - 1 } } 155 if len == nl { if hit == 0 { 156 var k: i64 = 0 157 var same: i64 = 1 158 while k < nl { if conf[i + k] != name[k] { same = 0; k = nl } else { k = k + 1 } } 159 if same == 1 { hit = 1 } 160 } } 161 i = e + 1 162 } 163 return hit 164} 165// PURE: how long a deferred heavy job waits before EDF may pick it again -- a fraction of its own 166// period, clamped so a 60 s beat is not busy-polled and a daily beat is not parked for hours. 167func clk_defer_secs(interval: i64) -> i64 { 168 var d: i64 = interval / CLK_DEFER_DIV 169 if d < CLK_DEFER_MIN_S { d = CLK_DEFER_MIN_S } 170 if d > CLK_DEFER_MAX_S { d = CLK_DEFER_MAX_S } 171 return d 172} 173// PURE: the decision. heavy=1 AND budget==0 -> DEFER. budget<0 is UNOBSERVABLE (the ruler could not 174// read /proc/stat) and must NOT defer: abstain toward the pre-change behaviour. A light job never defers. 175func clk_storm_defer(heavy: i64, budget: i64) -> i64 { 176 if heavy != 1 { return 0 } 177 if budget == 0 { return 1 } 178 return 0 179} 180// The live measurement, composed from the shared ruler with its OWN calibration constants, so the 181// clock refuses on exactly the D-state line the build gate refuses on. ioa[0]=ncpu ioa[1]=procs_blocked. 182func clk_storm_budget(ioa: *i64) -> i64 { 183 if ioa_measure(ioa) != 0 { return IOA_UNREADABLE } 184 return ioa_spawn_budget(ioa[0], ioa[1], IOA_BLOCKED_PER_CPU, IOA_RESERVE_SLOTS) 185} 186 187func clk_slot(names: *u8, i: i64) -> *u8 { return ((names as i64) + i*CLK_NAMEW) as *u8 } 188func clk_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 } 189 190func clk_find(names: *u8, n: i64, name: *u8) -> i64 { var i: i64=0; while i<n { if clk_streq(clk_slot(names,i), name)==1 { return i } i=i+1 } return 0-1 } 191 192// REGISTER a periodic job = add a divider off the one clock. Idempotent: re-registering the same name is a no-op 193// (so capabilities can declare their job every boot without ever creating a duplicate pulse). returns 1 if added. 194func clk_register(names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, name: *u8, organ: *u8, interval: i64) -> i64 { 195 let n: i64 = np[0] 196 // A re-registration whose ORGAN or INTERVAL differs must LAND. This returned 0 for both 197 // "already present, identical" and "already present, DIFFERENT", so every correction was a 198 // silent no-op. Measured over the whole clockjobs- plane 2026-08-07: four names carry different 199 // commands across rows and the live beat was the OLD one in every case -- segguard still ran 200 // nx_segguard.sh while nx_segsweep.elf had been re-registered three times; lmexport, stalesweep 201 // and ddqbeat likewise still ran their .cron.sh. A shell-to-organ migration of four beats was 202 // written and never took effect, and nothing could see it because 0 was the only signal. 203 // An IDENTICAL re-declaration stays a no-op, so capabilities may still declare their job every 204 // boot without churn. Returns 0 unchanged, 2 updated. 205 // ★★★★★★ IDEMPOTENCE AND IMMUTABILITY ARE NOT THE SAME PROPERTY: AN IDEMPOTENT-BY-NAME 206 // REGISTRAR MAKES THE FIRST WRITE PERMANENT AND EVERY LATER CORRECTION A SILENT NO-OP. 207 let ex: i64 = clk_find(names, n, name) 208 if ex >= 0 { 209 var iv0: i64 = interval 210 if iv0 < 1 { iv0 = 1 } 211 var eo: *u8 = clk_slot(organs, ex) 212 var same: i64 = 1 213 var q: i64 = 0 214 var st: i64 = 0 215 while st == 0 { 216 let a: i64 = eo[q] as i64 217 let b: i64 = organ[q] as i64 218 if a != b { same = 0; st = 1 } 219 if st == 0 { 220 if a == 0 { st = 1 } 221 if a != 0 { 222 q = q + 1 223 if q >= CLK_NAMEW { st = 1 } 224 } 225 } 226 } 227 if intervals[ex] != iv0 { same = 0 } 228 if same == 1 { return 0 } 229 var w: i64 = 0 230 while w < CLK_NAMEW - 1 { 231 if organ[w] == (0 as u8) { eo[w] = 0 as u8; w = CLK_NAMEW } else { eo[w] = organ[w]; w = w + 1 } 232 } 233 if w == CLK_NAMEW - 1 { eo[w] = 0 as u8 } 234 intervals[ex] = iv0 235 next_due[ex] = iv0 236 return 2 237 } 238 if n >= CLK_MAXJOBS { 239 // A CAP REACHED IN SILENCE BECOMES A REGISTRATION NOBODY KNOWS WAS DROPPED (2026-08-19: 240 // measured 128/128 with every over-cap path a quiet return). Say WHICH row was refused. 241 let cm: *u8 = "CLOCK-CAP-DROP register refused at CLK_MAXJOBS: " as *u8 242 var cl: i64 = 0 243 while cm[cl] != (0 as u8) { cl = cl + 1 } 244 sys_write(1, cm, cl) 245 var nl2: i64 = 0 246 while name[nl2] != (0 as u8) { nl2 = nl2 + 1 } 247 sys_write(1, name, nl2) 248 sys_write(1, "\n" as *u8, 1) 249 return 0 250 } 251 var d: *u8 = clk_slot(names, n); var i: i64=0 252 while i<CLK_NAMEW-1 { if name[i]==(0 as u8){ d[i]=0 as u8; i=CLK_NAMEW } else { d[i]=name[i]; i=i+1 } } 253 if i==CLK_NAMEW-1 { d[i]=0 as u8 } 254 var e: *u8 = clk_slot(organs, n); var j: i64=0 255 while j<CLK_NAMEW-1 { if organ[j]==(0 as u8){ e[j]=0 as u8; j=CLK_NAMEW } else { e[j]=organ[j]; j=j+1 } } 256 if j==CLK_NAMEW-1 { e[j]=0 as u8 } 257 var iv: i64 = interval; if iv < 1 { iv = 1 } 258 intervals[n] = iv; next_due[n] = iv; np[0] = n + 1 259 return 1 260} 261 262// THE DISPATCHER: advance to logical tick `now`; mark + return how many jobs are DUE (next_due<=now), advancing 263// each due job's next_due by its interval (catch-up safe: a job never fires more than once for a missed window). 264func clk_tick(intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 { 265 var count: i64 = 0; var i: i64 = 0 266 while i < n { 267 if next_due[i] <= now { 268 fired[i] = 1; count = count + 1 269 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (no runaway on a skipped tick) 270 } else { fired[i] = 0 } 271 i = i + 1 272 } 273 return count 274} 275 276// THE FUNCTIONAL DISPATCH: advance to `now` and actually RUN each due job by fork+exec of its organ elf path 277// (the same fork+exec idiom nx_god_pulse / nx_aw_hostctl use). One dispatcher runs N jobs at their divided rates; 278// there is NO per-job daemon. Parent waits each child so a slow job can't be lost (a real scheduler can make this 279// bounded-concurrent / fire-and-forget). `organs` is a parallel slot array (organ[i] = an executable path). Returns 280// the number of jobs dispatched this tick. 281func clk_dispatch_run(organs: *u8, intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 { 282 var count: i64 = 0; var i: i64 = 0 283 while i < n { 284 if next_due[i] <= now { 285 fired[i] = 1 286 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (catch-up safe) 287 let path: *u8 = clk_slot(organs, i) 288 __syscall(90, path as i64, 0x1ed, 0, 0, 0, 0) // chmod 0755 first: recv-shipped organs aren't reliably +x (HC_MGMT_CMD chmods for the same reason) -> without this execve fails 127 = silent no-dispatch 289 let pid: i64 = sys_fork() 290 if pid == 0 { 291 let argv: *i64 = sys_mmap(32) as *i64; argv[0] = path as i64; argv[1] = 0 292 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 293 sys_execve(path, argv, envp) 294 sys_exit(127) 295 } 296 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0 297 sys_wait4(pid, st, 0) 298 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff 299 // HONEST count: a job whose organ failed to exec (child exit 127 = missing/broken organ) did NOT run, 300 // so it is not counted as dispatched (it gets fired[]=1 so a caller can flag it, mirroring the 301 // publisher's dead-letter). A crash (signalled) or any other exit means the organ DID run. 302 clk_actlog(path, code) 303 if sig != 0 { count = count + 1 } else { if code != 127 { count = count + 1 } } 304 } else { fired[i] = 0 } 305 i = i + 1 306 } 307 return count 308} 309 310// TICKLESS run (sched_tickless lesson): from start_tick, run up to maxbeats beats, but before each beat SLEEP 311// exactly until the MINIMUM next_due across all jobs -- skipping every idle tick -- then dispatch the due organs. 312// Advances next_due[] in place (the caller persists for resume). out[0..3] = beats, dispatches, skipped_idle, 313// final_tick. ONE implementation, shared by the driver (nx_clock_tickless) and its gate (no parallel copy). 314func clk_run_tickless(organs: *u8, intervals: *i64, next_due: *i64, n: i64, start_tick: i64, maxbeats: i64, tick_ms: i64, out: *i64) -> i64 { 315 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 316 var T: i64 = start_tick; var beats: i64 = 0; var disp: i64 = 0; var skipped: i64 = 0 317 while beats < maxbeats { 318 var minnd: i64 = next_due[0]; var i: i64 = 1 319 while i < n { if next_due[i] < minnd { minnd = next_due[i] } i = i + 1 } 320 if minnd <= T { minnd = T + 1 } 321 let skip: i64 = minnd - T 322 if skip > 1 { skipped = skipped + (skip - 1) } 323 let totms: i64 = skip * tick_ms 324 let ts: *i64 = sys_mmap(16) as *i64; ts[0] = totms / 1000; ts[1] = (totms - (totms/1000)*1000) * CLK_MAGIC_1000000 325 __syscall(35, ts as i64, 0, 0, 0, 0, 0) // nanosleep until the next due event (tickless) 326 T = minnd 327 disp = disp + clk_dispatch_run(organs, intervals, next_due, n, T, fired) 328 beats = beats + 1 329 } 330 out[0] = beats; out[1] = disp; out[2] = skipped; out[3] = T 331 return disp 332} 333 334// STATUS helpers (the consolidation payoff: ONE place shows every periodic job). clk_due_in = ticks until a job 335// fires (<=0 means due now). clk_twin_ok = 1 iff the job's organ elf actually exists (a 0 means it is registered 336// but un-blessed -> the dispatcher would honestly report it didn't run -> surface it in status, do not hide it). 337func clk_due_in(next_due_i: i64, now: i64) -> i64 { return next_due_i - now } 338func clk_twin_ok(organ: *u8) -> i64 { let fd: i64 = sys_openat_rd(organ); if fd < 0 { return 0 } sys_close(fd); return 1 } 339 340func clk_itoa(buf: *u8, o: i64, v: i64) -> i64 { var w: i64=o; var m: i64=v; if m==0{buf[w]=48 as u8;return w+1} if m<0{buf[w]=45 as u8;w=w+1;m=0-m} let t:*u8=sys_mmap(24); var k:i64=0; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var j:i64=0; while j<k{buf[w]=t[k-1-j];w=w+1;j=j+1} return w } 341 342// persist the registry (the ONE crontab): name<TAB>interval<TAB>next_due per line. atomic via tmp+rename. 343func clk_msgcat(d: *u8, o: i64, s: *u8) -> i64 { var x: i64=o; var i: i64=0; while s[i]!=(0 as u8){d[x]=s[i];x=x+1;i=i+1} return x } 344func clk_msgnum(d: *u8, o: i64, v: i64) -> i64 { 345 var x: i64 = o 346 var m: i64 = v 347 if m < 0 { d[x] = 45 as u8; x = x + 1; m = 0 - m } 348 if m == 0 { d[x] = 48 as u8; return x + 1 } 349 let t: *u8 = sys_mmap(24) 350 var k: i64 = 0 351 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 352 var j: i64 = 0 353 while j < k { d[x] = t[k - 1 - j]; x = x + 1; j = j + 1 } 354 sys_munmap(t, 24) 355 return x 356} 357// ADOPTION VISIBILITY (2026-08-07). The clock captured per-job stdout to logs/<job>.log but wrote 358// NOTHING to knowledge/status/actlog.jrnl -- the ledger nx_catalog reads to decide INVOKED. 359// MEASURED: 19 REGISTERED TOOLS ARE CLOCK-DRIVEN, so every one of them could ONLY EVER report 360// REGISTERED-DARK, callable and authorised and NEVER RUN, no matter how often it actually ran. The 361// estate adoption headline was structurally blind to its entire scheduled population -- not wrong 362// about a few organs, blind to a whole class. 363// The row carries the ORGAN command, NOT the job name, because nx_catalog matches the ORGAN via a 364// substring scan of the ledger and the two names differ (job raidwatch vs organ nx_raidwatch). 365// FAIL-OPEN BY CONSTRUCTION: if the ledger cannot be opened this returns immediately and the beat is 366// untouched. Telemetry must never be able to stop the clock -- the same rule the per-job log capture 367// already follows. Tabs and newline are written as BYTES so no source escape can be mangled. 368func clk_actlog(organ: *u8, code: i64) -> i64 { 369 var fd: i64 = sys_openat_append("knowledge/status/actlog.jrnl" as *u8, 420) 370 if fd < 0 { return 0 } 371 let ln: *u8 = sys_mmap(CLK_NAMEW + 128) // composes the ORGAN slot + <=56 fixed bytes; DERIVED from the slot so a widened command can never outgrow the ledger line 372 var o: i64 = clk_msgnum(ln, 0, sys_now_realtime_sec()) 373 ln[o] = 9 as u8; o = o + 1 374 o = clk_msgcat(ln, o, "clock" as *u8) 375 ln[o] = 9 as u8; o = o + 1 376 o = clk_msgcat(ln, o, organ) 377 ln[o] = 9 as u8; o = o + 1 378 o = clk_msgcat(ln, o, "run" as *u8) 379 ln[o] = 9 as u8; o = o + 1 380 if code == 0 { o = clk_msgcat(ln, o, "ok" as *u8) } else { o = clk_msgcat(ln, o, "fail" as *u8) } 381 ln[o] = 9 as u8; o = o + 1 382 o = clk_msgcat(ln, o, "clockjob lane=clock exit=" as *u8) 383 o = clk_msgnum(ln, o, code) 384 ln[o] = 10 as u8; o = o + 1 385 sys_write(fd, ln, o) 386 sys_close(fd) 387 sys_munmap(ln, CLK_NAMEW + 128) 388 return 0 389} 390func clk_save(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 { 391 // ---- RELOAD-MERGE BEFORE SAVE (2026-07-31, id 1785559048) ------------------------------------------- 392 // PROVEN CLOBBER, by controlled experiment not inference: a well-formed row appended to the registry via 393 // the sovereign write path returned OK and read back present IMMEDIATELY -- and was GONE 75 seconds later, 394 // with no error, no log and no rejection. Reproduced twice. Cause is right here: this function serialized 395 // ONLY the caller's in-memory set, so the registry could contain nothing but what THIS process happened to 396 // load, and every row added between our load and our save was erased by the atomic rename below. 397 // WHY IT MATTERED: it silently closed what was then the only working extension point for scheduling. 398 // (⚠A CLAIM HERE WAS STALE AND IS RETRACTED 2026-08-03: this comment used to assert the clockjobs- 399 // plane was "retired/dead". MEASURED FALSE -- the live tickless clock merges that plane every window 400 // and rows registered via `nx_store_put knowledge/store/clockjobs- put <actor> <name> <interval> 401 // <organ>` demonstrably arm and fire. The plane IS the sanctioned add lane.) 402 // THE REMEDY IS THE ONE sts_append_row ALREADY APPLIED TO THE SEG-STORE: re-read the file we are about to 403 // overwrite and keep whatever we did not know about. Rows WE hold win outright (ours carry the advanced 404 // next_due); a row we have never seen is appended verbatim, keeping its own next_due so it arms exactly 405 // when its author intended instead of being silently re-armed or dropped. 406 // u00e2u02dcu2026A WRITER THAT SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER. 407 // u00e2u02dcu2026AN EXTENSION POINT THAT SILENTLY DISCARDS EXTENSIONS IS A CLOSED SYSTEM WEARING OPEN DOCUMENTATION. 408 var nn: i64 = n 409 let rb: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW) 410 let nm2: *u8 = sys_mmap(CLK_NAMEW) 411 let og2: *u8 = sys_mmap(CLK_NAMEW) 412 let rfd: i64 = sys_openat_rd(path) 413 if rfd >= 0 { 414 var rn: i64 = 0 415 var rr: i64 = 1 416 while rr > 0 { 417 rr = sys_read(rfd, ((rb as i64) + rn) as *u8, CLK_MAXJOBS*CLK_ROWW - rn) 418 if rr > 0 { rn = rn + rr } 419 } 420 sys_close(rfd) 421 var p: i64 = 0 422 var ls: i64 = 0 423 while p <= rn { 424 var eol: i64 = 0 425 if p == rn { eol = 1 } else { if rb[p] == (10 as u8) { eol = 1 } } 426 if eol == 1 { 427 if p > ls { 428 var f: i64 = 0 429 var q: i64 = ls 430 var iv2: i64 = 0 431 var nd2: i64 = 0 432 var w2: i64 = 0 433 var g2: i64 = 0 434 while q < p { 435 if rb[q] == (9 as u8) { f = f + 1 } else { 436 if f == 0 { if w2 < CLK_NAMEW - 1 { nm2[w2] = rb[q]; w2 = w2 + 1 } } 437 else { if f == 1 { if rb[q] >= (48 as u8) { if rb[q] <= (57 as u8) { iv2 = iv2*10 + ((rb[q] - (48 as u8)) as i64) } } } 438 else { if f == 2 { if rb[q] >= (48 as u8) { if rb[q] <= (57 as u8) { nd2 = nd2*10 + ((rb[q] - (48 as u8)) as i64) } } } 439 else { if g2 < CLK_NAMEW - 1 { og2[g2] = rb[q]; g2 = g2 + 1 } } } } 440 } 441 q = q + 1 442 } 443 nm2[w2] = 0 as u8 444 og2[g2] = 0 as u8 445 if w2 > 0 { if g2 > 0 { if clk_find(names, nn, nm2) < 0 { if nn < CLK_MAXJOBS { 446 var d2: *u8 = clk_slot(names, nn) 447 var a2: i64 = 0 448 while a2 < w2 { d2[a2] = nm2[a2]; a2 = a2 + 1 } 449 d2[w2] = 0 as u8 450 var e2: *u8 = clk_slot(organs, nn) 451 var b2: i64 = 0 452 while b2 < g2 { e2[b2] = og2[b2]; b2 = b2 + 1 } 453 e2[g2] = 0 as u8 454 if iv2 < 1 { iv2 = 1 } 455 intervals[nn] = iv2 456 next_due[nn] = nd2 457 nn = nn + 1 458 } } } } 459 } 460 ls = p + 1 461 } 462 p = p + 1 463 } 464 } 465 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0 466 var i: i64 = 0 467 while i < nn { 468 let nm: *u8 = clk_slot(names, i); var k: i64=0; while nm[k]!=(0 as u8){ buf[o]=nm[k]; o=o+1; k=k+1 } 469 buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, intervals[i]); buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, next_due[i]); buf[o]=9 as u8; o=o+1 470 let og: *u8 = clk_slot(organs, i); var g: i64=0; while og[g]!=(0 as u8){ buf[o]=og[g]; o=o+1; g=g+1 } 471 buf[o]=10 as u8; o=o+1 472 i = i + 1 473 } 474 sys_mkdir("knowledge" as *u8, 0x1ed); sys_mkdir("knowledge/sched" as *u8, 0x1ed) 475 let tmp: *u8 = sys_mmap(256); var t: i64=0; let pp: *u8 = path; while pp[t]!=(0 as u8){ tmp[t]=pp[t]; t=t+1 } tmp[t]=46 as u8; tmp[t+1]=116 as u8; tmp[t+2]=109 as u8; tmp[t+3]=112 as u8; tmp[t+4]=0 as u8 // path + ".tmp" 476 let fd: i64 = sys_openat_wr(tmp, 0x1a4); if fd<0 { return 0 } sys_write(fd, buf, o); sys_close(fd) 477 __syscall(82, tmp as i64, path as i64, 0, 0, 0, 0) // atomic rename .tmp -> registry 478 return nn 479} 480 481// ---- PLANE-NATIVE STATE PERSISTENCE (2026-08-03, debts 1784828927 + 1784868625: the operator law is 482// planes, never tsv). The mutable schedule state (name·interval·next_due·organ per row, the thing the 483// clock resumes from) lives in a seg-store plane, written once per window via sts_seed. The plane is 484// CLOCK-EXCLUSIVE by doctrine (external adds ride the separate clockjobs- ADD plane, which the clock 485// is read-only on) -- but the same reload-merge that saved the tsv from the 1785559048 clobber is kept 486// here: rows we have never seen are preserved with their own next_due, because A WRITER THAT 487// SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER. -------------------------------- 488 489// load schedule state from the plane; same 4-col row grammar as the legacy file. returns #jobs. 490func clk_load_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 { 491 np[0] = 0 492 let data: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW) 493 let dn: i64 = sts_load(prefix, data, CLK_MAXJOBS*CLK_ROWW) 494 if dn <= 0 { return 0 } 495 var i: i64=0; var ls: i64=0; var n: i64=0 496 while i < dn { 497 if data[i] == (10 as u8) { 498 if i > ls { if n < CLK_MAXJOBS { 499 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls 500 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n) 501 var f: i64=0; var p: i64=0; var iv: i64=0; var nd: i64=0; var w: i64=0; var g: i64=0 502 while p < ll { 503 if line[p] == (9 as u8) { f = f + 1 } 504 else { 505 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } } 506 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } } 507 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) { 508 let dig: i64 = (line[p]-(48 as u8)) as i64 509 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig } 510 } } } } 511 } 512 p = p + 1 513 } 514 d[w] = 0 as u8; e[g] = 0 as u8 515 if w > 0 { if g > 0 { 516 intervals[n]=iv; next_due[n]=nd; n=n+1 517 } } 518 } } 519 ls = i + 1 520 } 521 i = i + 1 522 } 523 np[0] = n 524 // B-hunk (2026-08-22): this buffer is per-call and was never freed -- at CLK_ROWW=2112 that is a 525 // 1,081,344 B address-space leak per window on a daemon whose life is 120 windows. Everything it 526 // held was COPIED into the caller's slot arrays above, so the unmap is safe by construction. 527 sys_munmap(data, CLK_MAXJOBS*CLK_ROWW) 528 return n 529} 530 531// persist schedule state to the plane (reload-merge first, then ONE whole-plane sts_seed commit). 532// Rows WE hold win (ours carry the advanced next_due); unknown rows are preserved verbatim. 533func clk_save_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 { 534 var nn: i64 = n 535 let rnames: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW) 536 let rorgs: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW) 537 let riv: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 538 let rnd: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 539 let rnp: *i64 = sys_mmap(8) as *i64 540 clk_load_plane(prefix, rnames, rorgs, riv, rnd, rnp) 541 var ri: i64 = 0 542 while ri < rnp[0] { 543 if clk_find(names, nn, clk_slot(rnames, ri)) < 0 { if nn < CLK_MAXJOBS { 544 var d2: *u8 = clk_slot(names, nn); let sm: *u8 = clk_slot(rnames, ri) 545 var a2: i64 = 0 546 while a2 < CLK_NAMEW-1 { if sm[a2]==(0 as u8){ d2[a2]=0 as u8; a2=CLK_NAMEW } else { d2[a2]=sm[a2]; a2=a2+1 } } 547 if a2 == CLK_NAMEW-1 { d2[a2]=0 as u8 } 548 var e2: *u8 = clk_slot(organs, nn); let so: *u8 = clk_slot(rorgs, ri) 549 var b2: i64 = 0 550 while b2 < CLK_NAMEW-1 { if so[b2]==(0 as u8){ e2[b2]=0 as u8; b2=CLK_NAMEW } else { e2[b2]=so[b2]; b2=b2+1 } } 551 if b2 == CLK_NAMEW-1 { e2[b2]=0 as u8 } 552 intervals[nn] = riv[ri]; next_due[nn] = rnd[ri]; nn = nn + 1 553 } } 554 ri = ri + 1 555 } 556 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0 557 var i: i64 = 0 558 while i < nn { 559 let nm: *u8 = clk_slot(names, i); var k: i64=0; while nm[k]!=(0 as u8){ buf[o]=nm[k]; o=o+1; k=k+1 } 560 buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, intervals[i]); buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, next_due[i]); buf[o]=9 as u8; o=o+1 561 let og: *u8 = clk_slot(organs, i); var g: i64=0; while og[g]!=(0 as u8){ buf[o]=og[g]; o=o+1; g=g+1 } 562 buf[o]=10 as u8; o=o+1 563 i = i + 1 564 } 565 sts_seed(prefix, buf, o) 566 // B-hunk (2026-08-22): free the per-call buffers -- at the widened CLK_NAMEW/CLK_ROWW these 567 // mappings are ~3.2 MB per save (1-2 saves/window) of address space the GC-free substrate 568 // otherwise leaks until the 120-window life recycle. All contents are already committed/copied. 569 sys_munmap(rnames, CLK_MAXJOBS*CLK_NAMEW) 570 sys_munmap(rorgs, CLK_MAXJOBS*CLK_NAMEW) 571 sys_munmap(riv as *u8, CLK_MAXJOBS*8) 572 sys_munmap(rnd as *u8, CLK_MAXJOBS*8) 573 sys_munmap(rnp as *u8, 8) 574 sys_munmap(buf, CLK_MAXJOBS*CLK_ROWW) 575 return nn 576} 577 578// THE ONE STATE LOADER for the live clock: plane first; if the plane is empty AND a legacy tsv 579// exists, load it (the one-time migration path) -- the caller's next clk_save_plane completes the 580// cutover. Returns 0=loaded-from-plane, 1=migrated-from-legacy, -1=nothing anywhere. 581func clk_load_state(prefix: *u8, legacy: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 { 582 if clk_load_plane(prefix, names, organs, intervals, next_due, np) > 0 { return 0 } 583 if clk_load(legacy, names, organs, intervals, next_due, np) > 0 { return 1 } 584 return 0 - 1 585} 586 587// MERGE new job declarations from an nx_store PLANE (off-tsv, additive) into the in-memory registry. 588// THE CLOBBER FIX: the clock is READ-ONLY on this plane -- external adds go via 589// `nx_store_put <plane> put <actor> <name> <interval> <organ>` (upsert-by-name, additive) and can NEVER be 590// clobbered by the clock's own (now clock-private) tsv save. Plane row cols (TAB): 0=name 1=interval(sec) 2=organ. 591// A job already present (by name) is skipped (idempotent -- re-merge is a no-op). New jobs arm at base_tick+interval. 592// Returns the number of NEW jobs added this call. 593func clk_merge_store(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, base_tick: i64) -> i64 { 594 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW) 595 let dn: i64 = sts_load(prefix, buf, CLK_MAXJOBS*CLK_ROWW) 596 if dn <= 0 { return 0 } 597 // HOISTED: allocating this inside the row loop would leak a page per row (the nx_ts_lumadiff scar). 598 let msg: *u8 = sys_mmap(CLK_ROWW) 599 // DESIRED interval per job index, 0 = not declared this pass. mmap returns zeroed pages. 600 let want: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 601 // DESIRED ORGAN per job index (2026-08-07). This reconciler recorded only the desired INTERVAL, so a 602 // declaration could re-PERIOD a job but never re-TARGET it. MEASURED: I declared segguard 600s -> 603 // nx_segsweep.elf and the plane came back interval=600 organ=nx_segguard.sh -- the cadence moved and the 604 // command did not, which is strictly WORSE than either alone: it put the 717s shell script back on a 605 // 600s period it cannot meet. Caught only because I read the organ column back instead of trusting the 606 // interval that DID change. 607 // (STAR)A RECONCILER THAT SYNCS ONE FIELD OF A ROW WILL SILENTLY DESYNC THE OTHERS -- AND A PARTIAL 608 // RECONCILE IS MORE DANGEROUS THAN NONE, BECAUSE THE FIELD THAT MOVED PROVES IT WORKED. 609 let worg: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW) 610 // B-hunk: ONE pair of row-scratch buffers for the whole scan (see the hoist note at `msg` above). 611 let nmrow: *u8 = sys_mmap(CLK_NAMEW) 612 let ogrow: *u8 = sys_mmap(CLK_NAMEW) 613 var added: i64 = 0 614 var reper: i64 = 0 615 var i: i64=0; var ls: i64=0 616 while i < dn { 617 if buf[i] == (10 as u8) { 618 if i > ls { 619 let line: *u8 = ((buf as i64)+ls) as *u8; let ll: i64 = i-ls 620 let nm: *u8 = nmrow; let og: *u8 = ogrow // B-hunk: hoisted buffers (were per-row mmaps = a page per desired row per window; the HOISTED note at `msg` above already names the class) 621 var f: i64=0; var p: i64=0; var iv: i64=0; var w: i64=0; var g: i64=0 622 while p < ll { 623 if line[p]==(9 as u8) { f=f+1 } 624 else { 625 if f==0 { if w<CLK_NAMEW-1 { nm[w]=line[p]; w=w+1 } } 626 else { if f==1 { if line[p]>=(48 as u8) { if line[p]<=(57 as u8) { iv=iv*10+((line[p]-(48 as u8)) as i64) } } } 627 else { if f==2 { if g<CLK_NAMEW-1 { og[g]=line[p]; g=g+1 } } } } 628 } 629 p=p+1 630 } 631 nm[w]=0 as u8; og[g]=0 as u8 632 // MALFORMED-ROW GUARD (2026-08-03, debt 1784604739): a row lacking a name or an organ 633 // (e.g. a put that dropped an arg) must be SEEN, never silently skipped -- but never 634 // registered either (a nameless job / organless exec is garbage in the dispatcher). 635 if w == 0 { sts_werr("clk_merge_store: MALFORMED plane row (empty name) SKIPPED\n" as *u8) } 636 if w > 0 { if g == 0 { sts_werr("clk_merge_store: MALFORMED plane row (no organ) SKIPPED\n" as *u8) } } 637 if w > 0 { if g > 0 { 638 if iv < 1 { iv = 1 } 639 let n: i64 = np[0] 640 // ---- RECONCILE, NOT JUST ADD (2026-08-06) ---------------------------------------- 641 // MEASURED DEFECT: this merge was ADD-ONLY -- a row whose name already existed was 642 // silently skipped -- and clocksched- (the runtime state) is rewritten by the clock 643 // every window. So there was NO SUPPORTED PATH TO RE-PERIOD OR REMOVE A CLOCK JOB: 644 // you could add one and never change it again. That is not a hypothetical. Three jobs 645 // were stuck at pathological cadences -- bootstrap-gate at 60s and worldgen-gate at 646 // 120s are GATES (test suites) running 60-360x more often than every other gate in the 647 // plane (p384kat/tls12prf 21600, uigensitegate 86400), and together with intakekeep 648 // they are 60 of the ~273 dispatches/window = 22pct of ALL demand on a clock measured 649 // at 51 completed vs 273 demanded (5.4x oversubscribed, 31 jobs STARVED per window). 650 // Whoever added them could not fix them, and the starvation was blamed on scheduling 651 // policy for days while EDF was working exactly as designed. 652 // THE FIX IS THE ESTATE'S OWN PATTERN, USED TWICE ALREADY: cron.reg -> crontab via 653 // nx_cron_reconcile, and edge443 -> iptables via nx_edge443_reconcile.sh. The declared 654 // registry is the SSOT and the runtime state is RECONCILED toward it every window. 655 // clockjobs- is now DESIRED STATE, not an append-only inbox. 656 // SAFETY: re-arm to base_tick+iv rather than leaving the old deadline, so a re-period 657 // can never fire a herd (shortening) nor strand a job past its new period (lengthening). 658 // ★A REGISTRY YOU CAN ONLY APPEND TO IS A REGISTRY THAT ACCUMULATES ITS OWN MISTAKES. 659 let ex: i64 = clk_find(names, n, nm) 660 // RECORD the intent; do not apply it here. See the apply loop after this scan. 661 if ex >= 0 { want[ex] = iv 662 var wd: *u8 = clk_slot(worg, ex); var wk: i64 = 0 663 while wk < CLK_NAMEW-1 { if og[wk]==(0 as u8){wd[wk]=0 as u8;wk=CLK_NAMEW} else {wd[wk]=og[wk];wk=wk+1} } 664 if wk == CLK_NAMEW-1 { wd[wk] = 0 as u8 } 665 } 666 if ex < 0 { if n < CLK_MAXJOBS { 667 var d: *u8=clk_slot(names,n); var k: i64=0; while k<CLK_NAMEW-1 { if nm[k]==(0 as u8){d[k]=0 as u8;k=CLK_NAMEW} else {d[k]=nm[k];k=k+1} } if k==CLK_NAMEW-1 {d[k]=0 as u8} 668 var e: *u8=clk_slot(organs,n); var j: i64=0; while j<CLK_NAMEW-1 { if og[j]==(0 as u8){e[j]=0 as u8;j=CLK_NAMEW} else {e[j]=og[j];j=j+1} } if j==CLK_NAMEW-1 {e[j]=0 as u8} 669 if iv<1 { iv=1 } 670 intervals[n]=iv; next_due[n]=base_tick+iv; np[0]=n+1; added=added+1 671 } } 672 } } 673 } 674 ls=i+1 675 } 676 i=i+1 677 } 678 // APPLY ONCE PER JOB, AFTER EVERY ROW IS READ -- LAST DECLARATION WINS. 679 // Applying inside the row loop was the first cut and it was wrong: this plane ACCUMULATES rows 680 // (pubreconcile appears THREE times in it today), so a name carrying two differing declarations 681 // would flip-flop -- two writes and two log lines every window, forever, converging on exactly 682 // the same last-row-wins answer this loop reaches quietly. Collecting the intent and applying it 683 // once is both quieter and identical in outcome. 684 var wi: i64 = 0 685 while wi < np[0] { 686 if want[wi] > 0 { if intervals[wi] != want[wi] { 687 var mo: i64 = clk_msgcat(msg, 0, "clk_merge_store: REPERIOD " as *u8) 688 mo = clk_msgcat(msg, mo, clk_slot(names, wi)) 689 mo = clk_msgcat(msg, mo, " " as *u8) 690 mo = clk_itoa(msg, mo, intervals[wi]) 691 mo = clk_msgcat(msg, mo, "s -> " as *u8) 692 mo = clk_itoa(msg, mo, want[wi]) 693 mo = clk_msgcat(msg, mo, "s (declared in clockjobs-; re-armed at now+interval)\n" as *u8) 694 msg[mo] = 0 as u8 695 sts_werr(msg) 696 intervals[wi] = want[wi] 697 next_due[wi] = base_tick + want[wi] 698 reper = reper + 1 699 } } 700 // RE-TARGET: the organ is a field of the row too, and a declaration that names a different 701 // binary must MOVE the job, not just its cadence. Separate from the interval branch so a 702 // pure re-target (same period, new organ) is honoured. 703 if wi < np[0] { let wo: *u8 = clk_slot(worg, wi) 704 if wo[0] != (0 as u8) { if clk_streq(wo, clk_slot(organs, wi)) == 0 { 705 var mo2: i64 = clk_msgcat(msg, 0, "clk_merge_store: RETARGET " as *u8) 706 mo2 = clk_msgcat(msg, mo2, clk_slot(names, wi)) 707 mo2 = clk_msgcat(msg, mo2, " -> " as *u8) 708 mo2 = clk_msgcat(msg, mo2, wo) 709 mo2 = clk_msgcat(msg, mo2, " (declared in clockjobs-)\n" as *u8) 710 msg[mo2] = 0 as u8 711 sts_werr(msg) 712 var od: *u8 = clk_slot(organs, wi); var ok2: i64 = 0 713 while ok2 < CLK_NAMEW-1 { if wo[ok2]==(0 as u8){od[ok2]=0 as u8;ok2=CLK_NAMEW} else {od[ok2]=wo[ok2];ok2=ok2+1} } 714 if ok2 == CLK_NAMEW-1 { od[ok2] = 0 as u8 } 715 reper = reper + 1 716 } } } 717 wi = wi + 1 718 } 719 // RETURN ADDS *AND* RE-PERIODS (2026-08-06). This returned only `added`, so a caller could not tell 720 // that a RECONCILE had occurred -- reper was counted, logged and then dropped on the floor. The single 721 // caller discarded the value entirely, so widening it is safe and makes the reconcile actionable. 722 // (STAR)A COUNTER THAT IS COMPUTED, LOGGED AND NOT RETURNED IS A FACT THE CALLER CANNOT ACT ON. 723 // B-hunk: free the per-call transients (buf/msg/want/worg + the hoisted row scratch); everything 724 // they held was applied to the caller's arrays above. 725 sys_munmap(buf, CLK_MAXJOBS*CLK_ROWW) 726 sys_munmap(msg, CLK_ROWW) 727 sys_munmap(want as *u8, CLK_MAXJOBS*8) 728 sys_munmap(worg, CLK_MAXJOBS*CLK_NAMEW) 729 sys_munmap(nmrow, CLK_NAMEW) 730 sys_munmap(ogrow, CLK_NAMEW) 731 return added + reper 732} 733 734// ============================================================================================ 735// EDF / WALL-CLOCK SCHEDULING (2026-08-04, debt 1785872141 -- operator: "get the clock to sota") 736// 737// THE MEASURED DEFECT clk_run_tickless has BY CONSTRUCTION: its logical tick T advances ONLY by the 738// sleep amount (T = minnd), while REAL time also advances by however long the dispatched children 739// took (clk_dispatch_run forks and sys_wait4s EVERY job, serially, inside the beat). So logical time 740// drifts behind wall-clock time in proportion to dispatch load, and EVERY job's period silently 741// STRETCHES: measured 2026-08-04, evidencebeat (interval 21600 = "6 hours") fired ONCE in 21 HOURS 742// while the clock was demonstrably alive (tick current, window-end a=30 b=132). Even 60s netobs 743// slipped to 243s. The fast jobs pay the drift too, but a 6h job pays it 360x over. 744// u2605u2605u2605u2605u2605u2605 A SCHEDULER THAT ADVANCES ITS OWN CLOCK BY WHAT IT SLEPT -- NOT BY WHAT ELAPSED -- 745// MEASURES ITS OWN IDLENESS AND CALLS IT TIME. Every deadline it derives is then a lie under load. 746// 747// THE SOTA SHAPE (what real schedulers do; cron/systemd-timers/EDF literature all agree): 748// 1. deadlines are ABSOLUTE WALL-CLOCK instants, never a self-advanced counter 749// 2. re-read the clock AFTER every dispatch, so job runtime cannot be lost 750// 3. dispatch EARLIEST-DEADLINE-FIRST so the most-overdue job goes first (EDF is optimal for 751// meeting deadlines on one resource -- and it is exactly what stops slow-job starvation) 752// 4. catch-up without runaway: re-arm past now by WHOLE intervals, and REPORT missed periods 753// instead of pretending they happened 754// The decision math is factored into PURE functions below precisely so a gate can prove a 6-hour 755// period behaves correctly in MILLISECONDS with fabricated clock values -- a scheduler you can only 756// test by waiting 6 hours is a scheduler nobody tests. 757 758// deadlines below this are LEGACY LOGICAL TICKS (the old counter ran ~3e6; epochs are ~1.78e9), so the 759// one-time migration is unambiguous and needs no flag day. NEVER compare an epoch against a tick. 760const CLK_EPOCH_FLOOR: i64 = 1000000000 761 762// PURE: index of the overdue job with the EARLIEST deadline (EDF), or -1 if nothing is due. 763// Ties break toward the lower index = stable, so a tie can never rotate two jobs into each other's slot. 764// DEMAND vs CAPACITY (2026-08-06, measured on the live plane). EDF decides WHO runs next; it can never 765// decide HOW MANY CAN RUN. If the registry asks for more dispatches per window than the window can 766// complete, then EVERY ordering starves someone and the scheduler is merely choosing the victim -- so 767// the deficit has to be a NUMBER the clock PUBLISHES, not something inferred from a late heartbeat days 768// later. MEASURED THE DAY THIS SHIPPED: 45 jobs demanded ~273 dispatches per 1800s window while the 769// window completed 51 (~35s of real child time per dispatch) = ~5.4x oversubscribed, and 273 exceeds even 770// TLN_MAXDISPATCH=240, so the set could not be served at INFINITE speed. That is why netobs (60s period) 771// was firing at 284s+ intervals with its registry row and its elf both perfectly healthy. 772// CONSERVATIVE BY CONSTRUCTION: integer division FLOORS, and any job whose period exceeds the window 773// contributes 0. So this is a LOWER BOUND on demand -- if even this floor exceeds capacity, 774// oversubscription is PROVEN, never merely suspected. 775func clk_demand_per_window(intervals: *i64, n: i64, window_secs: i64) -> i64 { 776 var need: i64 = 0 777 var i: i64 = 0 778 while i < n { 779 if intervals[i] > 0 { need = need + (window_secs / intervals[i]) } 780 i = i + 1 781 } 782 return need 783} 784 785// PARALLEL-SAFE PICK (2026-08-06): identical to clk_edf_pick except it SKIPS jobs already IN FLIGHT. 786// THE HAZARD IT EXISTS FOR, and it is not hypothetical: a job is re-armed only AFTER its child is reaped, 787// so while a run is in progress its deadline is still in the past -- and a job whose runtime EXCEEDS its 788// period (worldgen: 289s measured against a 120s declared period) is overdue for the WHOLE of its own run. 789// Under bounded-parallel dispatch a picker without this mask would immediately dispatch a SECOND copy of 790// the exact organ already saturating the box, then a third, up to the slot cap. Two concurrent four-world 791// renders is strictly WORSE than the serial starvation parallelism was introduced to fix. 792// (STAR)PARALLELISM TURNS "ALWAYS OVERDUE" FROM A STARVATION BUG INTO A FORK BOMB -- THE BUSY MASK IS NOT 793// AN OPTIMISATION, IT IS THE CORRECTNESS CONDITION. Kept as a SEPARATE pure function so it is gate-provable 794// on fabricated inputs (T10/T11) before it is ever wired to a live dispatcher. 795func clk_edf_pick_free(deadlines: *i64, n: i64, now: i64, busy: *i64) -> i64 { 796 var best: i64 = 0 - 1 797 var bestd: i64 = 0 798 var i: i64 = 0 799 while i < n { 800 if busy[i] == 0 { 801 if deadlines[i] <= now { 802 if best < 0 { best = i; bestd = deadlines[i] } 803 else { if deadlines[i] < bestd { best = i; bestd = deadlines[i] } } 804 } 805 } 806 i = i + 1 807 } 808 return best 809} 810 811func clk_edf_pick(deadlines: *i64, n: i64, now: i64) -> i64 { 812 var best: i64 = 0 - 1 813 var bestd: i64 = 0 814 var i: i64 = 0 815 while i < n { 816 if deadlines[i] <= now { 817 if best < 0 { best = i; bestd = deadlines[i] } 818 else { if deadlines[i] < bestd { best = i; bestd = deadlines[i] } } 819 } 820 i = i + 1 821 } 822 return best 823} 824 825// PURE: soonest deadline across all jobs (what a tickless sleeper must sleep until). -1 if n==0. 826func clk_edf_next(deadlines: *i64, n: i64) -> i64 { 827 if n <= 0 { return 0 - 1 } 828 var m: i64 = deadlines[0] 829 var i: i64 = 1 830 while i < n { if deadlines[i] < m { m = deadlines[i] } i = i + 1 } 831 return m 832} 833 834// PURE: how many whole periods job i has MISSED at `now` (0 = on time / early). This is the honest 835// starvation measure -- a job 3 periods late is a 3, not a "fired". 836func clk_edf_missed(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 { 837 if deadlines[i] > now { return 0 } 838 var iv: i64 = intervals[i] 839 if iv < 1 { iv = 1 } 840 return (now - deadlines[i]) / iv 841} 842 843// PURE: re-arm job i past `now` by WHOLE intervals (catch-up safe: never fires twice for one missed 844// window, never drifts off-phase). Returns the periods skipped so the caller can REPORT them. 845func clk_edf_rearm(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 { 846 var iv: i64 = intervals[i] 847 if iv < 1 { iv = 1 } 848 var skipped: i64 = 0 849 while deadlines[i] <= now { deadlines[i] = deadlines[i] + iv; skipped = skipped + 1 } 850 if skipped > 0 { skipped = skipped - 1 } // the first advance is the fire itself, not a miss 851 return skipped 852} 853 854// PURE: one-time migration of legacy logical-tick deadlines to wall-clock instants. A tick value can 855// never be a valid epoch, so this is exact. Jobs arm at now+interval (their author's intent) rather 856// than firing a thundering herd at now. Returns how many rows were converted. 857func clk_edf_migrate(deadlines: *i64, intervals: *i64, n: i64, now: i64) -> i64 { 858 var c: i64 = 0 859 var i: i64 = 0 860 while i < n { 861 if deadlines[i] < CLK_EPOCH_FLOOR { 862 var iv: i64 = intervals[i] 863 if iv < 1 { iv = 1 } 864 deadlines[i] = now + iv 865 c = c + 1 866 } 867 i = i + 1 868 } 869 return c 870} 871 872// THE EDF WINDOW: wall-clock anchored, EDF-ordered, starvation-reporting. Runs until `maxdispatch` 873// jobs have been dispatched or `budget_secs` of REAL time is gone, sleeping only when nothing is due. 874// out[0]=dispatched out[1]=slept_secs out[2]=final_now out[3]=starved_jobs out[4]=max_lateness_secs 875// out[5]=exec_secs (real time inside children -- the number the old design silently threw away). 876// ---- IN-FLIGHT BEAT (2026-08-06) ------------------------------------------------------------- 877// PROVEN LIVE THIS SESSION, NOT HYPOTHESISED: the clock overran its own 1800s budget by 42s and 878// stopped dispatching for 475s+ while one child held it, because clk_dispatch_one waits with NO 879// deadline (debt 1786056459). sitecheck (30s period) froze; every beat in the estate went dead. 880// THE INSTRUMENT PROBLEM THAT INCIDENT EXPOSED: max_exec, starved, and the entire window-end report 881// are written AFTER clk_run_edf RETURNS. So the one failure mode that prevents returning is exactly 882// the one the report can never describe -- during the stall the newest window-end beat was 1800s old 883// and named a completely different window. 884// (STAR)AN INSTRUMENT THAT REPORTS ONLY AT COMPLETION CANNOT REPORT WHAT PREVENTS COMPLETION. 885// This beat is written BEFORE every dispatch and overwritten AFTER it, so a hang is visible the 886// moment it starts and NAMES the organ holding the scheduler. Cost is 2 file writes per dispatch, 887// negligible beside the fork+execve it brackets. 888func clk_ifw(s: *u8, dst: *u8, o: i64) -> i64 { var x: i64=o; var i: i64=0; while s[i]!=(0 as u8){dst[x]=s[i];x=x+1;i=i+1} return x } 889func clk_ifn(dst: *u8, o: i64, v: i64) -> i64 { var x: i64=o; var mm: i64=v; if mm<0{dst[x]=45 as u8;x=x+1;mm=0-mm} if mm==0{dst[x]=48 as u8;return x+1} let t:*u8=sys_mmap(24); var k:i64=0; while mm>0{t[k]=(48+(mm%10)) as u8;mm=mm/10;k=k+1} var j:i64=0; while j<k{dst[x]=t[k-1-j];x=x+1;j=j+1} return x } 890func clk_inflight(tag: *u8, organ: *u8, a: i64, b: i64) -> i64 { 891 let st: *u8 = sys_mmap(CLK_NAMEW + 128); var o: i64 = 0 // composes the ORGAN slot + <=97 fixed bytes; DERIVED from the slot (1024 was safe only by mmap page rounding once the slot widened) 892 o = clk_ifw("CLOCKBEAT " as *u8, st, o); o = clk_ifw(tag, st, o) 893 o = clk_ifw(" organ=" as *u8, st, o); o = clk_ifw(organ, st, o) 894 o = clk_ifw(" a=" as *u8, st, o); o = clk_ifn(st, o, a) 895 o = clk_ifw(" b=" as *u8, st, o); o = clk_ifn(st, o, b) 896 o = clk_ifw(" t=" as *u8, st, o); o = clk_ifn(st, o, sys_now_realtime_sec()) 897 st[o]=10 as u8; o=o+1; st[o]=0 as u8 898 let fd: i64 = sys_openat_wr("sites/nishifamily/clock_inflight.txt" as *u8, 0x1a4) 899 if fd >= 0 { sys_write(fd, st, o); sys_close(fd) } 900 return 0 901} 902 903func clk_run_edf_core(organs: *u8, names: *u8, intervals: *i64, deadlines: *i64, n: i64, maxdispatch: i64, budget_secs: i64, tick_ms: i64, out: *i64, reserved_mode: i64) -> i64 { 904 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 905 let t0: i64 = sys_now_realtime_sec() 906 var disp: i64 = 0 907 var slept: i64 = 0 908 var starved: i64 = 0 909 var maxlate: i64 = 0 910 var execs: i64 = 0 911 var maxexec: i64 = 0 912 var maxidx: i64 = 0 - 1 913 // STARVED AXIS, NAMED (2026-08-14). Mirrors maxexec/maxidx below, which already record the worst 914 // single dispatch AND which job owned it. That law was applied to the SLOW axis and never to the 915 // STARVED axis, so out[3] shipped as a bare count. 916 var maxmissed: i64 = 0 917 var starvidx: i64 = 0 - 1 918 // STORM-AWARE DISPATCH state (2026-09-02): the heavy list is read ONCE per window (sys_read_file sizes 919 // from the file, cannot short-read; an absent conf -> hn=0 -> nothing is heavy), the ruler's scratch is 920 // allocated ONCE, and the deferral count is published in out[10]. 921 var deferred: i64 = 0 922 var assisted: i64 = 0 923 var errors: i64 = 0 924 var fatal: i64 = 0 925 let reserved_result: *i64 = sys_mmap(CLKRS_N*8) as *i64 926 let ioa: *i64 = sys_mmap(16) as *i64 927 let hlen: *i64 = sys_mmap(16) as *i64 928 hlen[0] = 0 929 let hconf: *u8 = sys_read_file(CLK_HEAVY_CONF, hlen) 930 var hn: i64 = 0 931 if (hconf as i64) != 0 { hn = hlen[0] } 932 let rlen: *i64 = sys_mmap(8) as *i64 933 var rconf: *u8 = 0 as *u8 934 if reserved_mode == 1 { rconf = sys_read_file(CLKRS_CONF,rlen) } 935 var rn: i64 = 0 936 if (rconf as i64) != 0 { rn = rlen[0] } 937 let reserved: i64 = clk_reserved_resolve(rconf,rn,names,organs,intervals,n,hconf,hn) 938 if reserved == CLKRS_INVALID { errors = errors+1; sts_werr("CLOCK-RESERVE configuration refused; legacy serial service remains, no reservation active\n" as *u8) } 939 if reserved >= 0 { sts_werr("CLOCK-RESERVE active: one exact light job, at most one extra child; foreground dispatch ceiling excludes assisted service\n" as *u8) } 940 var run: i64 = 1 941 while run == 1 { 942 // RE-READ THE CLOCK EVERY ITERATION -- this single line is the fix: child runtime is now 943 // observed, not assumed away. 944 let now: i64 = sys_now_realtime_sec() 945 if now - t0 >= budget_secs { run = 0 } 946 if disp >= maxdispatch { run = 0 } 947 if run == 1 { 948 var k: i64 = clk_edf_pick(deadlines, n, now) 949 if reserved >= 0 { if deadlines[reserved] <= now { k = reserved } } 950 // STORM-AWARE DISPATCH (2026-09-02): a HEAVY job (listed in CLK_HEAVY_CONF) that is due while the 951 // shared I/O-storm ruler reports zero spawn budget is re-armed to now + clk_defer_secs(interval) 952 // and NOT forked this pass; k becomes -1 so the else-branch below sleeps until the next real 953 // deadline exactly as if nothing were due. Light jobs, an absent conf and an UNREADABLE ruler 954 // take the pre-change path byte-for-byte. The deferral is announced on the in-flight beat with 955 // b = procs_blocked, so the docroot witness names the storm that caused it. 956 if k >= 0 { if clk_heavy_listed(hconf, hn, clk_slot(names, k)) == 1 { 957 let bud: i64 = clk_storm_budget(ioa) 958 if clk_storm_defer(1, bud) == 1 { 959 deferred = deferred + 1 960 deadlines[k] = now + clk_defer_secs(intervals[k]) 961 clk_inflight("deferred-storm" as *u8, clk_slot(organs, k), ioa[1], k) 962 k = 0 - 1 963 } 964 } } 965 if k >= 0 { 966 let missed: i64 = clk_edf_missed(deadlines, intervals, k, now) 967 let late: i64 = now - deadlines[k] 968 if late > maxlate { maxlate = late } 969 if missed > 0 { 970 starved = starved + 1 971 // NAME THE STARVER, DO NOT MERELY COUNT IT. A window reported "STARVED 3" and nothing 972 // anywhere said WHICH 3, so a job persistently losing EDF was undiagnosable. 973 // MEASURED 2026-08-14: a window published b=3 STARVED while nx_resmon's leak axis was 974 // flipping to UNOBSERVABLE off a stale memvel snapshot -- and there was no way to tell 975 // whether memvel was one of the three. A COUNT WITHOUT A WORKLIST IS NOT ACTIONABLE. 976 // Worst-offender (most periods missed) rather than a list: it needs no allocation, it 977 // matches the maxexec/maxidx idiom already proven here, and the worst starver is the 978 // one whose period is actually unschedulable. 979 if missed > maxmissed { maxmissed = missed; starvidx = k } 980 } 981 clk_edf_rearm(deadlines, intervals, k, now) 982 let e0: i64 = now 983 clk_inflight("in-flight" as *u8, clk_slot(organs, k), e0, k) 984 var ran: i64 = 0 985 var ownexec: i64 = 0 986 if reserved >= 0 { 987 let dispatch_rc: i64 = clk_dispatch_reserved(organs,names,intervals,deadlines,k,reserved,CLK_DISPATCH_DEADLINE_MS,CLK_DISPATCH_POLL_MS,reserved_result) 988 ran = reserved_result[CLKRS_FOREGROUND_DISP] 989 errors = errors+reserved_result[CLKRS_ERRORS] 990 if dispatch_rc == CLKRS_FATAL_RC { fatal = 1; run = 0 } 991 ownexec = reserved_result[CLKRS_FOREGROUND_EXEC] 992 assisted = assisted+reserved_result[CLKRS_DISP] 993 starved = starved+reserved_result[CLKRS_STARVED] 994 if reserved_result[CLKRS_LATE] > maxlate { maxlate = reserved_result[CLKRS_LATE] } 995 if reserved_result[CLKRS_MAXMISSED] > maxmissed { maxmissed = reserved_result[CLKRS_MAXMISSED]; starvidx = reserved_result[CLKRS_STARVIDX] } 996 if reserved_result[CLKRS_MAXEXEC] > maxexec { maxexec = reserved_result[CLKRS_MAXEXEC]; maxidx = reserved } 997 } else { ran = clk_dispatch_one(organs,names,k) } 998 let e1: i64 = sys_now_realtime_sec() 999 if fatal == 0 { clk_inflight("idle" as *u8, clk_slot(organs, k), e1-e0, k) } 1000 else { clk_inflight("fatal-ownership-uncertain" as *u8,clk_slot(organs,k),reserved_result[CLKRS_LOST_PID],reserved_result[CLKRS_LOST_WAIT]) } 1001 // ---- COST-ADAPTIVE BACKOFF (2026-08-06) -------------------------------------- 1002 // PROVEN LIVE, NAMED BY THE IN-FLIGHT BEAT ABOVE: nx_worldgen_gate.sov.elf holds a 1003 // 120s-period slot for 500s+ per run. A job whose RUNTIME EXCEEDS ITS PERIOD is always 1004 // overdue, so EDF correctly picks it again immediately, and on a SERIAL dispatcher with 1005 // no deadline that is a PERMANENT DENIAL OF SERVICE to every other job -- sitecheck (30s) 1006 // and netobs (60s) both froze behind it and every beat in the estate went dead. 1007 // (STAR)A JOB WHOSE RUNTIME EXCEEDS ITS PERIOD CANNOT BE SCHEDULED AT THAT PERIOD -- THE 1008 // REGISTRY IS ASSERTING A COST NOBODY EVER MEASURED. So MEASURE IT AND BELIEVE THE 1009 // MEASUREMENT: re-arm such a job to now + its OWN observed runtime instead of now + 1010 // its declared interval. It still runs, it still makes progress, but it can never again 1011 // starve the whole registry, and the declared interval becomes a FLOOR not a fiction. 1012 // Chosen over SIGKILL deliberately: killing needs a raw signal syscall inside a forking 1013 // scheduler, and this tree has a live trap where literal syscall numbers are rv64->x86 1014 // translated. This fix needs NO new syscall and cannot mis-target a pid. 1015 if reserved < 0 { ownexec = e1-e0 } 1016 if ownexec > intervals[k] { deadlines[k] = e1 + ownexec * CLK_HOGDUTY } // dt is declared BELOW this point; use the operands already in scope 1017 let dt: i64 = e1 - e0 1018 execs = execs + dt 1019 // ★PUBLISH A MAX BEFORE YOU ACT ON A MEAN (2026-08-06). Aggregate exec_secs cannot separate 1020 // 'every child is a bit slow' from 'ONE child hung for most of the window' -- both produce the 1021 // same dispatch count and the same mean, and they have OPPOSITE remedies (raise capacity vs 1022 // kill one hog). clk_dispatch_one waits on the child with NO deadline (debt 1786056459), so the 1023 // hung case is not hypothetical. Record the worst SINGLE dispatch and WHICH job owned it. 1024 if ownexec > maxexec { maxexec = ownexec; maxidx = k } 1025 if ran == 1 { disp = disp + 1; fired[k] = 1 } 1026 } else { 1027 let nd: i64 = clk_edf_next(deadlines, n) 1028 if nd < 0 { run = 0 } else { 1029 var wait: i64 = nd - now 1030 if wait < 1 { wait = 1 } 1031 let left: i64 = budget_secs - (now - t0) 1032 if wait > left { wait = left } 1033 if wait < 1 { run = 0 } else { 1034 let ts: *i64 = sys_mmap(16) as *i64 1035 ts[0] = wait 1036 ts[1] = 0 1037 __syscall(35, ts as i64, 0, 0, 0, 0, 0) 1038 slept = slept + wait 1039 } 1040 } 1041 } 1042 } 1043 } 1044 out[0] = disp+assisted; out[1] = slept; out[2] = sys_now_realtime_sec() 1045 out[3] = starved; out[4] = maxlate; out[5] = execs 1046 out[6] = maxexec; out[7] = maxidx // worst single dispatch, and the job index that owned it 1047 out[8] = starvidx; out[9] = maxmissed // WORST STARVER: job index, and how many of its periods it missed 1048 if reserved_mode == 1 { out[11] = assisted } // service beside a foreground child; excluded from the foreground-start count ceiling 1049 out[10] = deferred // STORM-DEFERRED (2026-09-02): heavy dispatches this window declined on the shared I/O-storm ruler; the caller allocates 16 slots (128 bytes), so slot 10 is inside the mapping 1050 // CALLER CONTRACT: `out` must now be at least 10 i64 slots (80 bytes). nx_clock_tickless allocated 1051 // exactly 64 bytes = 8 slots, so writing out[8] there would have run off the end of the mapping -- 1052 // the caller was widened in the same change. Any new caller must allocate >= 80 bytes. 1053 if reserved_mode == 1 { 1054 out[CLKRS_OUT_ERRORS] = errors; out[CLKRS_OUT_FATAL] = fatal 1055 out[CLKRS_OUT_LOST_INDEX] = CLKRS_NONE 1056 out[CLKRS_OUT_LOST_PID] = 0; out[CLKRS_OUT_LOST_WAIT] = 0; out[CLKRS_OUT_KNOWN_OWNED] = 0 1057 if fatal != 0 { 1058 out[CLKRS_OUT_LOST_INDEX] = reserved_result[CLKRS_LOST_INDEX] 1059 out[CLKRS_OUT_LOST_PID] = reserved_result[CLKRS_LOST_PID] 1060 out[CLKRS_OUT_LOST_WAIT] = reserved_result[CLKRS_LOST_WAIT] 1061 out[CLKRS_OUT_KNOWN_OWNED] = reserved_result[CLKRS_KNOWN_OWNED] 1062 return CLKRS_FATAL_RC 1063 } 1064 } 1065 return disp+assisted 1066} 1067 1068// THE ARGV SPLIT, AS A PURE FUNCTION (2026-08-22, command-slot truncation fix). Copies the organ 1069// slot into `buf` (>= CLK_NAMEW+8 bytes), splits on single spaces IN PLACE, fills `argv` 1070// (>= 8*(CLK_ARGV_MAX+1) bytes) and returns the token count; argv[ac] is NULL. Factored out of 1071// clk_dispatch_one for the same reason the EDF math is factored into pure functions above: so a gate 1072// can prove an 18-token, 300-byte command survives the split WITHOUT a fork, and so the mutation 1073// site (CLK_NAMEW) is reachable from a tooth. 1074// OVERFLOW IS ANNOUNCED, NEVER SILENT: a command carrying more than CLK_ARGV_MAX tokens keeps the 1075// first CLK_ARGV_MAX and reports ARGV-OVERFLOW naming the job and the dropped count -- the old 1076// `if ac < 16` cap dropped every token past 15 with NO diagnostic, the same silent class as the 1077// 127-byte cut this change retires. Token-accept logic is otherwise byte-identical to the old code. 1078func clk_split_argv(raw0: *u8, buf: *u8, argv: *i64, label: *u8) -> i64 { 1079 var bl: i64 = 0 1080 var cdone: i64 = 0 1081 while cdone == 0 { 1082 if bl >= CLK_NAMEW - 1 { cdone = 1 } 1083 else { if raw0[bl] == (0 as u8) { cdone = 1 } else { buf[bl] = raw0[bl]; bl = bl + 1 } } 1084 } 1085 buf[bl] = 0 as u8 1086 argv[0] = buf as i64 1087 var ac: i64 = 1 1088 var dropped: i64 = 0 1089 var sp: i64 = 0 1090 while sp < bl { 1091 if buf[sp] == (32 as u8) { 1092 buf[sp] = 0 as u8 1093 if buf[sp+1] != (0 as u8) { 1094 if ac < CLK_ARGV_MAX { argv[ac] = ((buf as i64) + sp + 1); ac = ac + 1 } else { dropped = dropped + 1 } 1095 } 1096 } 1097 sp = sp + 1 1098 } 1099 argv[ac] = 0 1100 if dropped > 0 { 1101 let om: *u8 = sys_mmap(CLK_ROWW) 1102 var oo: i64 = clk_msgcat(om, 0, "clk_split_argv: ARGV-OVERFLOW job=" as *u8) 1103 oo = clk_msgcat(om, oo, label) 1104 oo = clk_msgcat(om, oo, " kept=" as *u8) 1105 oo = clk_itoa(om, oo, ac) 1106 oo = clk_msgcat(om, oo, " dropped=" as *u8) 1107 oo = clk_itoa(om, oo, dropped) 1108 oo = clk_msgcat(om, oo, " tail arguments NOT passed (CLK_ARGV_MAX in nx_clock_caps.nx bounds the vector; the bound is NAMED and this line is its announcement)\n" as *u8) 1109 om[oo] = 0 as u8 1110 sts_werr(om) 1111 sys_munmap(om, CLK_ROWW) 1112 } 1113 return ac 1114} 1115 1116// dispatch EXACTLY ONE job by index (the EDF loop picks the victim; this just runs it). Split out of 1117// clk_dispatch_run so ordering policy and exec mechanics are separable -- and so the EDF loop can 1118// re-read the clock between jobs. Returns 1 if the organ really ran, 0 if exec failed (127). 1119func clk_dispatch_one(organs: *u8, names: *u8, i: i64) -> i64 { 1120 // ARGV SUPPORT (2026-08-07). This built argv[0]=path, argv[1]=0 -- an organ could take NO ARGUMENTS. 1121 // (STAR)A DISPATCHER THAT CANNOT PASS ARGUMENTS MANUFACTURES SHELL SCRIPTS -- THE WRAPPERS ARE A 1122 // SYMPTOM OF THE CALLING CONVENTION, NOT OF LAZINESS. 1123 // The organ field is SPLIT ON SPACES into a real argv, so a row may read 1124 // fallbackharden<TAB>600<TAB>nx_actlog.elf harden knowledge/status/fallback.jrnl 1125 // BACK-COMPATIBLE BY CONSTRUCTION: a field with no space yields argv[0] only, and the slot is 1126 // CLK_NAMEW = CLK_CMD_CAP (nx_clock_caps.nx, shared with the writer nx_clockjob), so a command the 1127 // writer admits ALWAYS fits. The split itself is clk_split_argv above -- pure, gate-provable, 1128 // and it announces ARGV-OVERFLOW instead of silently dropping tokens. 1129 let buf: *u8 = sys_mmap(CLK_NAMEW + 8) 1130 let argv: *i64 = sys_mmap(8*(CLK_ARGV_MAX+1)) as *i64 1131 clk_split_argv(clk_slot(organs, i), buf, argv, clk_slot(names, i)) 1132 let path: *u8 = buf 1133 let path: *u8 = buf 1134 __syscall(90, path as i64, 0x1ed, 0, 0, 0, 0) // chmod +x the EXECUTABLE only, after the split 1135 let pid: i64 = sys_fork() 1136 if pid == 0 { 1137 // PER-JOB OUTPUT CAPTURE (2026-08-07). The SECOND service the .sh wrappers rendered, after argv: 1138 // nx_fallback_harden.cron.sh appends to logs/fallback_harden.log and its own header calls that 1139 // "the trend IS the telemetry". Replacing a wrapper with a bare organ row would have SILENTLY 1140 // LOST that log -- stripping a feature (rule 25) to remove a shell script. 1141 // (STAR)BEFORE REPLACING A WRAPPER, ENUMERATE EVERYTHING IT PROVIDES -- THE ARGUMENT YOU NOTICED 1142 // IS RARELY THE ONLY SERVICE IT RENDERS. 1143 // Every clock organ now gets, for free, the capture each wrapper hand-rolled: stdout AND stderr 1144 // append to logs/<jobname>.log. Named by JOB, not organ, so two jobs sharing a binary stay 1145 // distinguishable (clobbertest and frontdoor both run nx_atlas_frontdoor.elf). Fail-open: if the 1146 // log cannot be opened the child still execs -- telemetry must never be able to stop the beat. 1147 // Uses the sys_dup3 redirect idiom already proven in nx_guarded_run. 1148 let lp: *u8 = sys_mmap(CLK_NAMEW + 16); var lo: i64 = 0 // "logs/" + NAME slot + ".log" + NUL; DERIVED from the slot 1149 lo = clk_msgcat(lp, lo, "logs/" as *u8) 1150 lo = clk_msgcat(lp, lo, clk_slot(names, i)) 1151 lo = clk_msgcat(lp, lo, ".log" as *u8) 1152 lp[lo] = 0 as u8 1153 let ofd: i64 = sys_openat_append(lp, 0x1a4) 1154 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 1155 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 1156 sys_execve(path, argv, envp) 1157 sys_exit(127) 1158 } 1159 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0 1160 // BOUNDED WAIT (was a bare blocking wait4 -- the single line that let one child own the clock). 1161 let t0ms: i64 = sys_now_ms() 1162 var reaped: i64 = 0 1163 var timedout: i64 = 0 1164 while reaped == 0 { 1165 let w: i64 = sys_wait4(pid, st, 1) 1166 if w == pid { reaped = 1 } 1167 else { if w < 0 { reaped = 1 } 1168 else { if sys_now_ms() - t0ms >= CLK_DISPATCH_DEADLINE_MS { nx_kill(pid, CLK_SIGKILL); sys_wait4(pid, st, 0); reaped = 1; timedout = 1 } else { sys_sleep_ms(CLK_DISPATCH_POLL_MS) } } } 1169 } 1170 // A REAPED-ON-DEADLINE CHILD DID EXECUTE -- report it as dispatched so the duty ceiling above backs 1171 // it off from its OWN measured cost. Reporting 0 here would hide the hog from the very rule that tames it. 1172 // LIVE-PATH TELEMETRY (2026-08-07). clk_actlog was first added to clk_dispatch_run -- which is the 1173 // DEAD path: nx_clock_tickless calls clk_run_edf (it REPLACES clk_run_tickless), and clk_run_edf 1174 // dispatches through THIS function. The instrumented binary was verifiably the one running, and it 1175 // still logged nothing, because I had checked WHICH BINARY IS LIVE and never WHICH CODE PATH IS. 1176 // Logged on EVERY exit including the deadline kill, since a job that had to be killed is exactly 1177 // the one a reader needs to see. 124 mirrors the shell timeout convention. 1178 if timedout == 1 { clk_actlog(path, 124); return 1 } 1179 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff 1180 clk_actlog(path, code) 1181 if sig != 0 { return 1 } 1182 if code != 127 { return 1 } 1183 return 0 1184} 1185 1186// load the registry into the arrays. returns #jobs (also written to np[0]). 1187func clk_load(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 { 1188 np[0] = 0 1189 let lenp: *i64 = sys_mmap(8) as *i64 1190 let data: *u8 = sys_read_file(path, lenp) 1191 if (data as i64) == 0 { return 0 } 1192 let dn: i64 = lenp[0]; var i: i64=0; var ls: i64=0; var n: i64=0 1193 while i < dn { 1194 if data[i] == (10 as u8) { 1195 if i > ls { if n < CLK_MAXJOBS { 1196 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls 1197 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n) 1198 var f: i64=0; var p: i64=0; var iv: i64=0; var nd: i64=0; var w: i64=0; var g: i64=0 // f: 0=name 1=interval 2=next_due 3=organ 1199 while p < ll { 1200 if line[p] == (9 as u8) { f = f + 1 } 1201 else { 1202 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } } 1203 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } } 1204 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) { 1205 let dig: i64 = (line[p]-(48 as u8)) as i64 1206 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig } 1207 } } } } 1208 } 1209 p = p + 1 1210 } 1211 d[w] = 0 as u8; e[g] = 0 as u8 1212 intervals[n]=iv; next_due[n]=nd; n=n+1 1213 } } 1214 ls = i + 1 1215 } 1216 i = i + 1 1217 } 1218 np[0] = n 1219 return n 1220} 1221 1222// Reserved service uses the existing job registry and deadline arrays, never a second clock. 1223// Exactly one explicitly bound light job may run beside one foreground child. 1224// Pure, strict activation: one non-comment name<TAB>exact-command row; no wildcard or name-only grant. 1225func clk_reserved_resolve(conf: *u8, cn: i64, names: *u8, organs: *u8, intervals: *i64, n: i64, heavy: *u8, hn: i64) -> i64 { 1226 if cn <= 0 { return CLKRS_NONE } 1227 let name: *u8 = sys_mmap(CLK_NAMEW) 1228 let command: *u8 = sys_mmap(CLK_NAMEW) 1229 var rows: i64 = 0 1230 var p: i64 = 0 1231 while p < cn { 1232 var e: i64 = p 1233 while e < cn { if conf[e] == (10 as u8) { break } e = e + 1 } 1234 var end: i64 = e 1235 if end > p { if conf[end-1] == (13 as u8) { end = end - 1 } } 1236 if end > p { if conf[p] != (35 as u8) { 1237 rows = rows + 1 1238 if rows > 1 { return CLKRS_INVALID } 1239 var tab: i64 = 0 - 1 1240 var q: i64 = p 1241 while q < end { 1242 if conf[q] == (0 as u8) { return CLKRS_INVALID } 1243 if conf[q] == (9 as u8) { if tab >= 0 { return CLKRS_INVALID } tab = q } 1244 q = q + 1 1245 } 1246 if tab <= p { return CLKRS_INVALID } 1247 if tab+1 >= end { return CLKRS_INVALID } 1248 if tab-p >= CLK_NAMEW { return CLKRS_INVALID } 1249 if end-tab-1 >= CLK_NAMEW { return CLKRS_INVALID } 1250 q = p 1251 while q < tab { name[q-p] = conf[q]; q = q + 1 } 1252 name[tab-p] = 0 as u8 1253 q = tab+1 1254 while q < end { command[q-tab-1] = conf[q]; q = q + 1 } 1255 command[end-tab-1] = 0 as u8 1256 } } 1257 p = e + 1 1258 } 1259 if rows == 0 { return CLKRS_NONE } 1260 var found: i64 = CLKRS_NONE 1261 var i: i64 = 0 1262 while i < n { 1263 if clk_streq(clk_slot(names,i),name) == 1 { 1264 if found >= 0 { return CLKRS_INVALID } 1265 if clk_streq(clk_slot(organs,i),command) != 1 { return CLKRS_INVALID } 1266 if intervals[i] <= 0 { return CLKRS_INVALID } 1267 if clk_heavy_listed(heavy,hn,name) == 1 { return CLKRS_INVALID } 1268 found = i 1269 } 1270 i = i + 1 1271 } 1272 if found < 0 { return CLKRS_INVALID } 1273 // A second registry name must not conceal the same producer command. 1274 i = 0 1275 while i < n { 1276 if i != found { if clk_streq(clk_slot(organs,i),command) == 1 { return CLKRS_INVALID } } 1277 i = i+1 1278 } 1279 return found 1280} 1281// Pure service predicate. One occupied reservation cannot spawn another copy. 1282func clk_reserved_due(index: i64, foreground: i64, pid: i64, due: i64, now: i64, foreground_live: i64) -> i64 { 1283 if index < 0 { return 0 } 1284 if index == foreground { return 0 } 1285 if pid != 0 { return 0 } 1286 if foreground_live != 1 { return 0 } 1287 if due > now { return 0 } 1288 return 1 1289} 1290// Preserve argv, per-job append capture and executable preparation, but move child file work 1291// after fork so it cannot block the parent's wait/service loop before the child exists. 1292func clk_reserved_spawn(organs: *u8, names: *u8, i: i64) -> i64 { 1293 let pid: i64 = sys_fork() 1294 if pid != 0 { return pid } 1295 let buf: *u8 = sys_mmap(CLK_NAMEW+8) 1296 let argv: *i64 = sys_mmap(8*(CLK_ARGV_MAX+1)) as *i64 1297 clk_split_argv(clk_slot(organs,i),buf,argv,clk_slot(names,i)) 1298 __syscall(90,buf as i64,0x1ed,0,0,0,0) 1299 let lp: *u8 = sys_mmap(CLK_NAMEW+16) 1300 var lo: i64 = clk_msgcat(lp,0,"logs/" as *u8) 1301 lo = clk_msgcat(lp,lo,clk_slot(names,i)); lo = clk_msgcat(lp,lo,".log" as *u8); lp[lo] = 0 as u8 1302 let fd: i64 = sys_openat_append(lp,MODE_0644) 1303 if fd >= 0 { sys_dup3(fd,1,0); sys_dup3(fd,2,0); if fd > 2 { sys_close(fd) } } 1304 let envp: *i64 = sys_mmap(16) as *i64 1305 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 1306 sys_execve(buf,argv,envp) 1307 sys_exit(127); return 0 1308} 1309func clk_reserved_log(organs: *u8, names: *u8, i: i64, code: i64) -> i64 { 1310 let buf: *u8 = sys_mmap(CLK_NAMEW+8) 1311 let av: *i64 = sys_mmap(8*(CLK_ARGV_MAX+1)) as *i64 1312 clk_split_argv(clk_slot(organs,i),buf,av,clk_slot(names,i)) 1313 clk_actlog(buf,code) 1314 sys_munmap(buf,CLK_NAMEW+8); sys_munmap(av as *u8,8*(CLK_ARGV_MAX+1)) 1315 return 0 1316} 1317// Production syscall seams have no fixture flags or configurable fault behavior. 1318// TEST CLOSURE ONLY. Never merge this fixture seam into the serving scheduler. 1319const CFI_ECHILD: i64 = 0 - 10 1320func cfi_number(path: *u8) -> i64 { 1321 let n: *i64 = sys_mmap(8) as *i64 1322 let p: *u8 = sys_read_file(path,n) 1323 if (p as i64) == 0 { return 0 }; if n[0] != 8 { return 0 } 1324 return (p as *i64)[0] 1325} 1326func cfi_write(path: *u8, value: i64) -> i64 { 1327 let p: *i64 = sys_mmap(8) as *i64; p[0] = value 1328 let fd: i64 = sys_openat_wr(path,384) 1329 if fd < 0 { return 0 } 1330 let n: i64 = sys_write(fd,p as *u8,8); let rc: i64 = sys_close(fd) 1331 if n != 8 { return 0 }; if rc != 0 { return 0 } 1332 if cfi_number(path) != value { return 0 }; return 1 1333} 1334func cfi_mode() -> i64 { 1335 let n: *i64 = sys_mmap(8) as *i64 1336 let p: *u8 = sys_read_file("fault.mode" as *u8,n) 1337 if (p as i64) == 0 { return 0 }; if n[0] != 1 { return 0 } 1338 if p[0] == (70 as u8) { return 1 }; if p[0] == (69 as u8) { return 2 }; return 0 1339} 1340func cfi_fault_event() -> i64 { 1341 let lock: i64 = sys_openat_append("events.lock" as *u8,384) 1342 if lock < 0 { return 0 }; if sys_flock(lock,SYS_LOCK_EX) != 0 { sys_close(lock); return 0 } 1343 let msg: *u8 = sys_mmap(CLK_ROWW) 1344 var o: i64 = clk_msgcat(msg,0,"4 2 " as *u8) 1345 o = clk_msgnum(msg,o,sys_now_ms()); msg[o] = 10 as u8; o = o+1 1346 let fd: i64 = sys_openat_append("events.tsv" as *u8,384) 1347 var ok: i64 = 0 1348 if fd >= 0 { if sys_write(fd,msg,o) == o { ok = 1 }; if sys_close(fd) != 0 { ok = 0 } } 1349 if sys_flock(lock,SYS_LOCK_UN) != 0 { ok = 0 }; if sys_close(lock) != 0 { ok = 0 } 1350 return ok 1351} 1352 1353func clk_reserved_wait_owned(pid: i64, status: *i64, flags: i64) -> i64 { 1354 let mode: i64 = cfi_mode() 1355 if mode == 0 { return sys_wait4(pid,status,flags) } 1356 if cfi_write("fault.seam-active" as *u8,1) != 1 { return CFI_ECHILD } 1357 let lost: i64 = cfi_number("fault.lostpid" as *u8) 1358 if lost > 0 { if lost == pid { 1359 cfi_write("fault.forbidden-wait" as *u8,pid) 1360 return CFI_ECHILD 1361 } } 1362 var first: i64 = cfi_number("fault.firstpid" as *u8) 1363 if first == 0 { first = pid; if cfi_write("fault.firstpid" as *u8,pid) != 1 { return CFI_ECHILD } } 1364 let rc: i64 = sys_wait4(pid,status,flags) 1365 var selected: i64 = 0 1366 if mode == 1 { if first == pid { selected = 1 } } 1367 if mode == 2 { if first != pid { selected = 1 } } 1368 // No fake living child: the real wait must first report an actual reap. 1369 if lost == 0 { if selected == 1 { if rc == pid { 1370 if cfi_write("fault.lostpid" as *u8,pid) != 1 { return CFI_ECHILD } 1371 if cfi_fault_event() != 1 { return CFI_ECHILD } 1372 return CFI_ECHILD 1373 } } } 1374 return rc 1375} 1376func clk_reserved_signal_owned(pid: i64, signal: i64) -> i64 { 1377 let lost: i64 = cfi_number("fault.lostpid" as *u8) 1378 if lost > 0 { if lost == pid { 1379 cfi_write("fault.forbidden-signal" as *u8,pid) 1380 return CFI_ECHILD 1381 } } 1382 return nx_kill(pid,signal) 1383} 1384func clk_reserved_wait_kind(result: i64, pid: i64) -> i64 { 1385 if pid <= 0 { return CLKRS_WAIT_LOST } 1386 if result == pid { return CLKRS_WAIT_REAPED } 1387 if result == 0 { return CLKRS_WAIT_RUNNING } 1388 if result == CLKRS_EINTR { return CLKRS_WAIT_INTERRUPTED } 1389 return CLKRS_WAIT_LOST 1390} 1391func clk_reserved_halt_required(out: *i64) -> i64 { 1392 if out[CLKRS_OUT_FATAL] != 0 { return 1 } 1393 return 0 1394} 1395func clk_reserved_lost(ro: *i64, names: *u8, i: i64, pid: i64, result: i64, other_owned: i64) -> i64 { 1396 ro[CLKRS_ERRORS] = ro[CLKRS_ERRORS]+1 1397 if ro[CLKRS_FATAL] == 0 { 1398 ro[CLKRS_LOST_INDEX] = i; ro[CLKRS_LOST_PID] = pid; ro[CLKRS_LOST_WAIT] = result 1399 } 1400 ro[CLKRS_FATAL] = 1 1401 let msg: *u8 = sys_mmap(CLK_ROWW) 1402 var o: i64 = clk_msgcat(msg,0,"CLOCK-RESERVE FATAL ownership lost job=" as *u8) 1403 o = clk_msgcat(msg,o,clk_slot(names,i)); o = clk_msgcat(msg,o," pid=" as *u8); o = clk_msgnum(msg,o,pid) 1404 o = clk_msgcat(msg,o," wait_result=" as *u8); o = clk_msgnum(msg,o,result) 1405 o = clk_msgcat(msg,o," known_owned_to_drain=" as *u8); o = clk_msgnum(msg,o,other_owned) 1406 o = clk_msgcat(msg,o,"; no further forks or access to unowned PID; reconciliation required\n" as *u8) 1407 sys_write(2,msg,o); sys_munmap(msg,CLK_ROWW) 1408 return 0 1409} 1410// Owned slots are capabilities, not a guess that a numeric PID remains our child. 1411// A non-EINTR wait failure invalidates the slot once and globally inhibits new forks. 1412// Only still-owned children are drained. Unknown outcomes are never logged as success. 1413func clk_dispatch_reserved(organs: *u8, names: *u8, intervals: *i64, deadlines: *i64, i: i64, reserved: i64, deadline_ms: i64, poll_ms: i64, ro: *i64) -> i64 { 1414 var z: i64 = 0 1415 while z < CLKRS_N { ro[z] = 0; z = z+1 } 1416 ro[CLKRS_STARVIDX] = CLKRS_NONE; ro[CLKRS_LOST_INDEX] = CLKRS_NONE 1417 if deadline_ms <= 0 { ro[CLKRS_ERRORS] = 1; return 0 } 1418 if poll_ms <= 0 { ro[CLKRS_ERRORS] = 1; return 0 } 1419 let pid: i64 = clk_reserved_spawn(organs,names,i) 1420 if pid < 0 { ro[CLKRS_ERRORS] = 1; sts_werr("CLOCK-RESERVE foreground fork failed; no wait or signal issued\n" as *u8); return 0 } 1421 if pid == 0 { return 0 } 1422 let start: i64 = sys_now_ms() 1423 let st: *i64 = sys_mmap(16) as *i64 1424 let rst: *i64 = sys_mmap(16) as *i64 1425 var owned: i64 = 1 1426 var attempted: i64 = 0; var signaled: i64 = 0 1427 var rp: i64 = 0; var rowned: i64 = 0; var rs: i64 = 0 1428 var rattempted: i64 = 0; var rsignaled: i64 = 0 1429 while owned+rowned > 0 { 1430 let nowms: i64 = sys_now_ms() 1431 if owned == 1 { 1432 let result: i64 = clk_reserved_wait_owned(pid,st,CLKRS_WNOHANG) 1433 let kind: i64 = clk_reserved_wait_kind(result,pid) 1434 if kind == CLKRS_WAIT_REAPED { 1435 owned = 0 1436 ro[CLKRS_FOREGROUND_EXEC] = (nowms-start)/1000 1437 let sig: i64 = st[0] & 0x7f 1438 var code: i64 = (st[0] >> 8) & 0xff 1439 if sig != 0 { code = CLKRS_SIGNAL_EXIT_BASE+sig } 1440 if signaled == 1 { if sig == CLK_SIGKILL { code = 124 } } 1441 if sig != 0 { ro[CLKRS_FOREGROUND_DISP] = 1 } else { if code != 127 { ro[CLKRS_FOREGROUND_DISP] = 1 } } 1442 clk_reserved_log(organs,names,i,code) 1443 } else { 1444 if kind == CLKRS_WAIT_LOST { 1445 owned = 0 1446 clk_reserved_lost(ro,names,i,pid,result,rowned) 1447 } else { if kind == CLKRS_WAIT_RUNNING { if attempted == 0 { if nowms-start >= deadline_ms { 1448 let kr: i64 = clk_reserved_signal_owned(pid,CLK_SIGKILL); attempted = 1 1449 if kr == 0 { signaled = 1 } else { ro[CLKRS_ERRORS] = ro[CLKRS_ERRORS]+1 } 1450 sts_werr("CLOCK-RESERVE foreground deadline signal attempted; still owned until wait result, no repeat signal\n" as *u8) 1451 } } } } 1452 } 1453 } 1454 if rowned == 1 { 1455 let result: i64 = clk_reserved_wait_owned(rp,rst,CLKRS_WNOHANG) 1456 let kind: i64 = clk_reserved_wait_kind(result,rp) 1457 if kind == CLKRS_WAIT_REAPED { 1458 rowned = 0 1459 let dt: i64 = (nowms-rs)/1000 1460 if dt > ro[CLKRS_MAXEXEC] { ro[CLKRS_MAXEXEC] = dt } 1461 let sig: i64 = rst[0] & 0x7f 1462 var code: i64 = (rst[0] >> 8) & 0xff 1463 if sig != 0 { code = CLKRS_SIGNAL_EXIT_BASE+sig } 1464 if rsignaled == 1 { if sig == CLK_SIGKILL { code = 124 } } 1465 if sig != 0 { ro[CLKRS_DISP] = ro[CLKRS_DISP]+1 } else { if code != 127 { ro[CLKRS_DISP] = ro[CLKRS_DISP]+1 } } 1466 clk_reserved_log(organs,names,reserved,code) 1467 if dt > intervals[reserved] { deadlines[reserved] = sys_now_realtime_sec()+dt*CLK_HOGDUTY } 1468 rp = 0 1469 } else { 1470 if kind == CLKRS_WAIT_LOST { 1471 rowned = 0 1472 clk_reserved_lost(ro,names,reserved,rp,result,owned) 1473 rp = 0 1474 } else { if kind == CLKRS_WAIT_RUNNING { if rattempted == 0 { if nowms-rs >= deadline_ms { 1475 let kr: i64 = clk_reserved_signal_owned(rp,CLK_SIGKILL); rattempted = 1 1476 if kr == 0 { rsignaled = 1 } else { ro[CLKRS_ERRORS] = ro[CLKRS_ERRORS]+1 } 1477 sts_werr("CLOCK-RESERVE essential deadline signal attempted; still owned until wait result, no repeat signal\n" as *u8) 1478 } } } } 1479 } 1480 } 1481 if ro[CLKRS_FATAL] == 0 { 1482 let now: i64 = sys_now_realtime_sec() 1483 var due: i64 = now+1 1484 if reserved >= 0 { due = deadlines[reserved] } 1485 if clk_reserved_due(reserved,i,rp,due,now,owned) == 1 { 1486 let missed: i64 = clk_edf_missed(deadlines,intervals,reserved,now) 1487 let late: i64 = now-deadlines[reserved] 1488 if late > ro[CLKRS_LATE] { ro[CLKRS_LATE] = late } 1489 if missed > 0 { ro[CLKRS_STARVED] = ro[CLKRS_STARVED]+1 } 1490 if missed > ro[CLKRS_MAXMISSED] { ro[CLKRS_MAXMISSED] = missed; ro[CLKRS_STARVIDX] = reserved } 1491 clk_edf_rearm(deadlines,intervals,reserved,now) 1492 rp = clk_reserved_spawn(organs,names,reserved) 1493 if rp < 0 { rp = 0; ro[CLKRS_ERRORS] = ro[CLKRS_ERRORS]+1; sts_werr("CLOCK-RESERVE essential fork failed; no wait or signal issued\n" as *u8) } 1494 else { if rp > 0 { rowned = 1; rs = sys_now_ms(); rattempted = 0; rsignaled = 0; sts_werr("CLOCK-RESERVE essential dispatched beside foreground\n" as *u8) } } 1495 } 1496 } 1497 ro[CLKRS_KNOWN_OWNED] = owned+rowned 1498 if owned+rowned > 0 { sys_sleep_ms(poll_ms) } 1499 } 1500 sys_munmap(st as *u8,16); sys_munmap(rst as *u8,16) 1501 if ro[CLKRS_FATAL] != 0 { return CLKRS_FATAL_RC } 1502 return ro[CLKRS_FOREGROUND_DISP] 1503} 1504 1505func clk_run_edf(organs: *u8, names: *u8, intervals: *i64, deadlines: *i64, n: i64, maxdispatch: i64, budget_secs: i64, tick_ms: i64, out: *i64) -> i64 { 1506 return clk_run_edf_core(organs,names,intervals,deadlines,n,maxdispatch,budget_secs,tick_ms,out,0) 1507} 1508// Extended reserved contract: >=CLKRS_OUT_N i64 slots; out[11] assisted; 1509// named out[12..17] expose errors and fatal ownership evidence. Legacy wrapper extent is unchanged. 1510func clk_run_edf_reserved(organs: *u8, names: *u8, intervals: *i64, deadlines: *i64, n: i64, maxdispatch: i64, budget_secs: i64, tick_ms: i64, out: *i64) -> i64 { 1511 return clk_run_edf_core(organs,names,intervals,deadlines,n,maxdispatch,budget_secs,tick_ms,out,1) 1512}