code wiki / _hdl_build / nx_clock_sched.nx
nx_clock_sched.nx source
↩ module page · 1207 lines · 77255 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_store_seed_lib.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.
106func clk_heavy_listed(conf: *u8, n: i64, name: *u8) -> i64 {
107 if n <= 0 { return 0 }
108 var nl: i64 = 0
109 while name[nl] != (0 as u8) { nl = nl + 1 }
110 if nl == 0 { return 0 }
111 var i: i64 = 0
112 var hit: i64 = 0
113 while i < n {
114 var e: i64 = i
115 var eol: i64 = 0
116 while eol == 0 {
117 if e >= n { eol = 1 } else { if conf[e] == (10 as u8) { eol = 1 } else { e = e + 1 } }
118 }
119 var len: i64 = e - i
120 if len > 0 { if conf[i + len - 1] == (13 as u8) { len = len - 1 } }
121 if len == nl { if hit == 0 {
122 var k: i64 = 0
123 var same: i64 = 1
124 while k < nl { if conf[i + k] != name[k] { same = 0; k = nl } else { k = k + 1 } }
125 if same == 1 { hit = 1 }
126 } }
127 i = e + 1
128 }
129 return hit
130}
131// PURE: how long a deferred heavy job waits before EDF may pick it again -- a fraction of its own
132// period, clamped so a 60 s beat is not busy-polled and a daily beat is not parked for hours.
133func clk_defer_secs(interval: i64) -> i64 {
134 var d: i64 = interval / CLK_DEFER_DIV
135 if d < CLK_DEFER_MIN_S { d = CLK_DEFER_MIN_S }
136 if d > CLK_DEFER_MAX_S { d = CLK_DEFER_MAX_S }
137 return d
138}
139// PURE: the decision. heavy=1 AND budget==0 -> DEFER. budget<0 is UNOBSERVABLE (the ruler could not
140// read /proc/stat) and must NOT defer: abstain toward the pre-change behaviour. A light job never defers.
141func clk_storm_defer(heavy: i64, budget: i64) -> i64 {
142 if heavy != 1 { return 0 }
143 if budget == 0 { return 1 }
144 return 0
145}
146// The live measurement, composed from the shared ruler with its OWN calibration constants, so the
147// clock refuses on exactly the D-state line the build gate refuses on. ioa[0]=ncpu ioa[1]=procs_blocked.
148func clk_storm_budget(ioa: *i64) -> i64 {
149 if ioa_measure(ioa) != 0 { return IOA_UNREADABLE }
150 return ioa_spawn_budget(ioa[0], ioa[1], IOA_BLOCKED_PER_CPU, IOA_RESERVE_SLOTS)
151}
152
153func clk_slot(names: *u8, i: i64) -> *u8 { return ((names as i64) + i*CLK_NAMEW) as *u8 }
154func 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 }
155
156func 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 }
157
158// REGISTER a periodic job = add a divider off the one clock. Idempotent: re-registering the same name is a no-op
159// (so capabilities can declare their job every boot without ever creating a duplicate pulse). returns 1 if added.
160func clk_register(names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, name: *u8, organ: *u8, interval: i64) -> i64 {
161 let n: i64 = np[0]
162 // A re-registration whose ORGAN or INTERVAL differs must LAND. This returned 0 for both
163 // "already present, identical" and "already present, DIFFERENT", so every correction was a
164 // silent no-op. Measured over the whole clockjobs- plane 2026-08-07: four names carry different
165 // commands across rows and the live beat was the OLD one in every case -- segguard still ran
166 // nx_segguard.sh while nx_segsweep.elf had been re-registered three times; lmexport, stalesweep
167 // and ddqbeat likewise still ran their .cron.sh. A shell-to-organ migration of four beats was
168 // written and never took effect, and nothing could see it because 0 was the only signal.
169 // An IDENTICAL re-declaration stays a no-op, so capabilities may still declare their job every
170 // boot without churn. Returns 0 unchanged, 2 updated.
171 // ★★★★★★ IDEMPOTENCE AND IMMUTABILITY ARE NOT THE SAME PROPERTY: AN IDEMPOTENT-BY-NAME
172 // REGISTRAR MAKES THE FIRST WRITE PERMANENT AND EVERY LATER CORRECTION A SILENT NO-OP.
173 let ex: i64 = clk_find(names, n, name)
174 if ex >= 0 {
175 var iv0: i64 = interval
176 if iv0 < 1 { iv0 = 1 }
177 var eo: *u8 = clk_slot(organs, ex)
178 var same: i64 = 1
179 var q: i64 = 0
180 var st: i64 = 0
181 while st == 0 {
182 let a: i64 = eo[q] as i64
183 let b: i64 = organ[q] as i64
184 if a != b { same = 0; st = 1 }
185 if st == 0 {
186 if a == 0 { st = 1 }
187 if a != 0 {
188 q = q + 1
189 if q >= CLK_NAMEW { st = 1 }
190 }
191 }
192 }
193 if intervals[ex] != iv0 { same = 0 }
194 if same == 1 { return 0 }
195 var w: i64 = 0
196 while w < CLK_NAMEW - 1 {
197 if organ[w] == (0 as u8) { eo[w] = 0 as u8; w = CLK_NAMEW } else { eo[w] = organ[w]; w = w + 1 }
198 }
199 if w == CLK_NAMEW - 1 { eo[w] = 0 as u8 }
200 intervals[ex] = iv0
201 next_due[ex] = iv0
202 return 2
203 }
204 if n >= CLK_MAXJOBS {
205 // A CAP REACHED IN SILENCE BECOMES A REGISTRATION NOBODY KNOWS WAS DROPPED (2026-08-19:
206 // measured 128/128 with every over-cap path a quiet return). Say WHICH row was refused.
207 let cm: *u8 = "CLOCK-CAP-DROP register refused at CLK_MAXJOBS: " as *u8
208 var cl: i64 = 0
209 while cm[cl] != (0 as u8) { cl = cl + 1 }
210 sys_write(1, cm, cl)
211 var nl2: i64 = 0
212 while name[nl2] != (0 as u8) { nl2 = nl2 + 1 }
213 sys_write(1, name, nl2)
214 sys_write(1, "\n" as *u8, 1)
215 return 0
216 }
217 var d: *u8 = clk_slot(names, n); var i: i64=0
218 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 } }
219 if i==CLK_NAMEW-1 { d[i]=0 as u8 }
220 var e: *u8 = clk_slot(organs, n); var j: i64=0
221 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 } }
222 if j==CLK_NAMEW-1 { e[j]=0 as u8 }
223 var iv: i64 = interval; if iv < 1 { iv = 1 }
224 intervals[n] = iv; next_due[n] = iv; np[0] = n + 1
225 return 1
226}
227
228// THE DISPATCHER: advance to logical tick `now`; mark + return how many jobs are DUE (next_due<=now), advancing
229// each due job's next_due by its interval (catch-up safe: a job never fires more than once for a missed window).
230func clk_tick(intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 {
231 var count: i64 = 0; var i: i64 = 0
232 while i < n {
233 if next_due[i] <= now {
234 fired[i] = 1; count = count + 1
235 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (no runaway on a skipped tick)
236 } else { fired[i] = 0 }
237 i = i + 1
238 }
239 return count
240}
241
242// THE FUNCTIONAL DISPATCH: advance to `now` and actually RUN each due job by fork+exec of its organ elf path
243// (the same fork+exec idiom nx_god_pulse / nx_aw_hostctl use). One dispatcher runs N jobs at their divided rates;
244// there is NO per-job daemon. Parent waits each child so a slow job can't be lost (a real scheduler can make this
245// bounded-concurrent / fire-and-forget). `organs` is a parallel slot array (organ[i] = an executable path). Returns
246// the number of jobs dispatched this tick.
247func clk_dispatch_run(organs: *u8, intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 {
248 var count: i64 = 0; var i: i64 = 0
249 while i < n {
250 if next_due[i] <= now {
251 fired[i] = 1
252 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (catch-up safe)
253 let path: *u8 = clk_slot(organs, i)
254 __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
255 let pid: i64 = sys_fork()
256 if pid == 0 {
257 let argv: *i64 = sys_mmap(32) as *i64; argv[0] = path as i64; argv[1] = 0
258 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
259 sys_execve(path, argv, envp)
260 sys_exit(127)
261 }
262 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0
263 sys_wait4(pid, st, 0)
264 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff
265 // HONEST count: a job whose organ failed to exec (child exit 127 = missing/broken organ) did NOT run,
266 // so it is not counted as dispatched (it gets fired[]=1 so a caller can flag it, mirroring the
267 // publisher's dead-letter). A crash (signalled) or any other exit means the organ DID run.
268 // NO JOB NAME ON THIS PATH, BY CONSTRUCTION (2026-09-18): clk_dispatch_run holds no names array and
269 // its signature is shared with nx_clock_run and nx_clock_dispatch_gate, so it passes null and its
270 // row stays byte-identical to the old shape. The LIVE path (clk_run_edf -> clk_dispatch_one) names the job.
271 clk_actlog(path, code, 0 as *u8)
272 if sig != 0 { count = count + 1 } else { if code != 127 { count = count + 1 } }
273 } else { fired[i] = 0 }
274 i = i + 1
275 }
276 return count
277}
278
279// TICKLESS run (sched_tickless lesson): from start_tick, run up to maxbeats beats, but before each beat SLEEP
280// exactly until the MINIMUM next_due across all jobs -- skipping every idle tick -- then dispatch the due organs.
281// Advances next_due[] in place (the caller persists for resume). out[0..3] = beats, dispatches, skipped_idle,
282// final_tick. ONE implementation, shared by the driver (nx_clock_tickless) and its gate (no parallel copy).
283func clk_run_tickless(organs: *u8, intervals: *i64, next_due: *i64, n: i64, start_tick: i64, maxbeats: i64, tick_ms: i64, out: *i64) -> i64 {
284 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
285 var T: i64 = start_tick; var beats: i64 = 0; var disp: i64 = 0; var skipped: i64 = 0
286 while beats < maxbeats {
287 var minnd: i64 = next_due[0]; var i: i64 = 1
288 while i < n { if next_due[i] < minnd { minnd = next_due[i] } i = i + 1 }
289 if minnd <= T { minnd = T + 1 }
290 let skip: i64 = minnd - T
291 if skip > 1 { skipped = skipped + (skip - 1) }
292 let totms: i64 = skip * tick_ms
293 let ts: *i64 = sys_mmap(16) as *i64; ts[0] = totms / 1000; ts[1] = (totms - (totms/1000)*1000) * CLK_MAGIC_1000000
294 __syscall(35, ts as i64, 0, 0, 0, 0, 0) // nanosleep until the next due event (tickless)
295 T = minnd
296 disp = disp + clk_dispatch_run(organs, intervals, next_due, n, T, fired)
297 beats = beats + 1
298 }
299 out[0] = beats; out[1] = disp; out[2] = skipped; out[3] = T
300 return disp
301}
302
303// STATUS helpers (the consolidation payoff: ONE place shows every periodic job). clk_due_in = ticks until a job
304// fires (<=0 means due now). clk_twin_ok = 1 iff the job's organ elf actually exists (a 0 means it is registered
305// but un-blessed -> the dispatcher would honestly report it didn't run -> surface it in status, do not hide it).
306func clk_due_in(next_due_i: i64, now: i64) -> i64 { return next_due_i - now }
307func clk_twin_ok(organ: *u8) -> i64 { let fd: i64 = sys_openat_rd(organ); if fd < 0 { return 0 } sys_close(fd); return 1 }
308
309func 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 }
310
311// persist the registry (the ONE crontab): name<TAB>interval<TAB>next_due per line. atomic via tmp+rename.
312func 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 }
313func clk_msgnum(d: *u8, o: i64, v: i64) -> i64 {
314 var x: i64 = o
315 var m: i64 = v
316 if m < 0 { d[x] = 45 as u8; x = x + 1; m = 0 - m }
317 if m == 0 { d[x] = 48 as u8; return x + 1 }
318 let t: *u8 = sys_mmap(24)
319 var k: i64 = 0
320 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
321 var j: i64 = 0
322 while j < k { d[x] = t[k - 1 - j]; x = x + 1; j = j + 1 }
323 sys_munmap(t, 24)
324 return x
325}
326// ADOPTION VISIBILITY (2026-08-07). The clock captured per-job stdout to logs/<job>.log but wrote
327// NOTHING to knowledge/status/actlog.jrnl -- the ledger nx_catalog reads to decide INVOKED.
328// MEASURED: 19 REGISTERED TOOLS ARE CLOCK-DRIVEN, so every one of them could ONLY EVER report
329// REGISTERED-DARK, callable and authorised and NEVER RUN, no matter how often it actually ran. The
330// estate adoption headline was structurally blind to its entire scheduled population -- not wrong
331// about a few organs, blind to a whole class.
332// The row carries the ORGAN command, NOT the job name, because nx_catalog matches the ORGAN via a
333// substring scan of the ledger and the two names differ (job raidwatch vs organ nx_raidwatch).
334// FAIL-OPEN BY CONSTRUCTION: if the ledger cannot be opened this returns immediately and the beat is
335// untouched. Telemetry must never be able to stop the clock -- the same rule the per-job log capture
336// already follows. Tabs and newline are written as BYTES so no source escape can be mangled.
337//
338// JOB NAME ON THE ROW (2026-09-18). The row named only the ORGAN, so a clock row that runs
339// `nx_plan_run.elf <plan>` logged nx_plan_run.elf and nothing else, and the close ruler (nx_rungclose) --
340// which judges a rung OPERATED when its agent, a clock ROW NAME such as twincheck or searchship-dry, has
341// run since the rung landed -- could never witness ANY plan-run agent. The row now appends ` job=<row
342// name>` as the LAST token of its LAST field: `... clockjob lane=clock exit=<code> job=<name>`. ADDITIVE:
343// still six TAB fields, the ORGAN column byte-identical, every pre-existing byte in place.
344// THE LINE BUDGET grows by exactly one NAME slot, DERIVED, never chosen: the ORGAN (<= one slot) + the
345// JOB name (<= one slot) + <= 88 fixed bytes (two i64 decimals at <= 20 each, the literal words, five
346// TABs, " job=" and the LF), held with 128 so the fixed part can never outgrow it.
347const CLK_ACTLOGW: i64 = CLK_NAMEW + CLK_NAMEW + 128
348const CLK_TOKEN_MIN_BYTE: i64 = 33 // the first printable ASCII byte that is not a space; every byte below it is a space or a control byte
349const CLK_TOKEN_DEL_BYTE: i64 = 127 // ASCII DEL, the one control byte above the printable range
350
351// A JOB NAME IS WRITTEN ONLY WHEN IT CAN TRAVEL AS ONE TOKEN. Returns its length (1..CLK_NAMEW-1), or 0
352// for a null, empty, slot-overflowing or non-token name -- and on 0 the row carries NO job= field, the
353// pre-2026-09-18 shape byte for byte. WHY ABSENCE AND NOT A PLACEHOLDER: the reader matches `job=<agent>`
354// token-bounded, so an empty value, a value a SPACE splits in two, or a TAB/CR/LF that breaks the row
355// grammar could each hand it a witness for a job that never ran; a missing field can only read as "not
356// witnessed", the direction that under-claims. The scan is BOUNDED BY THE SLOT, so a name with no NUL
357// inside its slot can never run the line past CLK_ACTLOGW. Bytes >= 128 pass (a UTF-8 name is a token).
358func clk_job_token_len(job: *u8) -> i64 {
359 if (job as i64) == 0 { return 0 }
360 var n: i64 = 0
361 var st: i64 = 0
362 while st == 0 {
363 if n >= CLK_NAMEW - 1 { st = 1 }
364 else {
365 let c: i64 = job[n] as i64
366 if c == 0 { st = 1 }
367 else {
368 if c < CLK_TOKEN_MIN_BYTE { return 0 }
369 if c == CLK_TOKEN_DEL_BYTE { return 0 }
370 n = n + 1
371 }
372 }
373 }
374 if job[n] != (0 as u8) { return 0 }
375 return n
376}
377
378// THE LEDGER ROW, AS A PURE FUNCTION (2026-09-18). Composes one actlog.jrnl row into `ln` (>= CLK_ACTLOGW
379// bytes) and returns its length. Split out of clk_actlog for the reason clk_split_argv was: a gate can
380// prove the row's shape WITHOUT writing the production ledger, and it proves it on the SAME code the
381// dispatcher runs -- one implementation, no mirrored copy to drift.
382// SHAPE: <epoch> TAB clock TAB <organ> TAB run TAB ok|fail TAB clockjob lane=clock exit=<code>[ job=<name>] LF
383func clk_actlog_row(ln: *u8, now: i64, organ: *u8, code: i64, job: *u8) -> i64 {
384 var o: i64 = clk_msgnum(ln, 0, now)
385 ln[o] = 9 as u8; o = o + 1
386 o = clk_msgcat(ln, o, "clock" as *u8)
387 ln[o] = 9 as u8; o = o + 1
388 o = clk_msgcat(ln, o, organ)
389 ln[o] = 9 as u8; o = o + 1
390 o = clk_msgcat(ln, o, "run" as *u8)
391 ln[o] = 9 as u8; o = o + 1
392 if code == 0 { o = clk_msgcat(ln, o, "ok" as *u8) } else { o = clk_msgcat(ln, o, "fail" as *u8) }
393 ln[o] = 9 as u8; o = o + 1
394 o = clk_msgcat(ln, o, "clockjob lane=clock exit=" as *u8)
395 o = clk_msgnum(ln, o, code)
396 if clk_job_token_len(job) > 0 {
397 o = clk_msgcat(ln, o, " job=" as *u8)
398 o = clk_msgcat(ln, o, job)
399 }
400 ln[o] = 10 as u8; o = o + 1
401 return o
402}
403
404// `job` is the clock ROW NAME (clk_slot(names, i)); null or empty writes the row without a job= field.
405func clk_actlog(organ: *u8, code: i64, job: *u8) -> i64 {
406 var fd: i64 = sys_openat_append("knowledge/status/actlog.jrnl" as *u8, 420)
407 if fd < 0 { fd = sys_openat_append("/volume1/homes/elderwesto/nishihost/knowledge/status/actlog.jrnl" as *u8, 420) }
408 if fd < 0 { return 0 }
409 let ln: *u8 = sys_mmap(CLK_ACTLOGW)
410 let o: i64 = clk_actlog_row(ln, sys_now_realtime_sec(), organ, code, job)
411 sys_write(fd, ln, o)
412 sys_close(fd)
413 sys_munmap(ln, CLK_ACTLOGW)
414 return 0
415}
416func clk_save(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 {
417 // ---- RELOAD-MERGE BEFORE SAVE (2026-07-31, id 1785559048) -------------------------------------------
418 // PROVEN CLOBBER, by controlled experiment not inference: a well-formed row appended to the registry via
419 // the sovereign write path returned OK and read back present IMMEDIATELY -- and was GONE 75 seconds later,
420 // with no error, no log and no rejection. Reproduced twice. Cause is right here: this function serialized
421 // ONLY the caller's in-memory set, so the registry could contain nothing but what THIS process happened to
422 // load, and every row added between our load and our save was erased by the atomic rename below.
423 // WHY IT MATTERED: it silently closed what was then the only working extension point for scheduling.
424 // (⚠A CLAIM HERE WAS STALE AND IS RETRACTED 2026-08-03: this comment used to assert the clockjobs-
425 // plane was "retired/dead". MEASURED FALSE -- the live tickless clock merges that plane every window
426 // and rows registered via `nx_store_put knowledge/store/clockjobs- put <actor> <name> <interval>
427 // <organ>` demonstrably arm and fire. The plane IS the sanctioned add lane.)
428 // THE REMEDY IS THE ONE sts_append_row ALREADY APPLIED TO THE SEG-STORE: re-read the file we are about to
429 // overwrite and keep whatever we did not know about. Rows WE hold win outright (ours carry the advanced
430 // next_due); a row we have never seen is appended verbatim, keeping its own next_due so it arms exactly
431 // when its author intended instead of being silently re-armed or dropped.
432 // u00e2u02dcu2026A WRITER THAT SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER.
433 // u00e2u02dcu2026AN EXTENSION POINT THAT SILENTLY DISCARDS EXTENSIONS IS A CLOSED SYSTEM WEARING OPEN DOCUMENTATION.
434 var nn: i64 = n
435 let rb: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW)
436 let nm2: *u8 = sys_mmap(CLK_NAMEW)
437 let og2: *u8 = sys_mmap(CLK_NAMEW)
438 let rfd: i64 = sys_openat_rd(path)
439 if rfd >= 0 {
440 var rn: i64 = 0
441 var rr: i64 = 1
442 while rr > 0 {
443 rr = sys_read(rfd, ((rb as i64) + rn) as *u8, CLK_MAXJOBS*CLK_ROWW - rn)
444 if rr > 0 { rn = rn + rr }
445 }
446 sys_close(rfd)
447 var p: i64 = 0
448 var ls: i64 = 0
449 while p <= rn {
450 var eol: i64 = 0
451 if p == rn { eol = 1 } else { if rb[p] == (10 as u8) { eol = 1 } }
452 if eol == 1 {
453 if p > ls {
454 var f: i64 = 0
455 var q: i64 = ls
456 var iv2: i64 = 0
457 var nd2: i64 = 0
458 var w2: i64 = 0
459 var g2: i64 = 0
460 while q < p {
461 if rb[q] == (9 as u8) { f = f + 1 } else {
462 if f == 0 { if w2 < CLK_NAMEW - 1 { nm2[w2] = rb[q]; w2 = w2 + 1 } }
463 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) } } }
464 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) } } }
465 else { if g2 < CLK_NAMEW - 1 { og2[g2] = rb[q]; g2 = g2 + 1 } } } }
466 }
467 q = q + 1
468 }
469 nm2[w2] = 0 as u8
470 og2[g2] = 0 as u8
471 if w2 > 0 { if g2 > 0 { if clk_find(names, nn, nm2) < 0 { if nn < CLK_MAXJOBS {
472 var d2: *u8 = clk_slot(names, nn)
473 var a2: i64 = 0
474 while a2 < w2 { d2[a2] = nm2[a2]; a2 = a2 + 1 }
475 d2[w2] = 0 as u8
476 var e2: *u8 = clk_slot(organs, nn)
477 var b2: i64 = 0
478 while b2 < g2 { e2[b2] = og2[b2]; b2 = b2 + 1 }
479 e2[g2] = 0 as u8
480 if iv2 < 1 { iv2 = 1 }
481 intervals[nn] = iv2
482 next_due[nn] = nd2
483 nn = nn + 1
484 } } } }
485 }
486 ls = p + 1
487 }
488 p = p + 1
489 }
490 }
491 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0
492 var i: i64 = 0
493 while i < nn {
494 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 }
495 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
496 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 }
497 buf[o]=10 as u8; o=o+1
498 i = i + 1
499 }
500 sys_mkdir("knowledge" as *u8, 0x1ed); sys_mkdir("knowledge/sched" as *u8, 0x1ed)
501 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"
502 let fd: i64 = sys_openat_wr(tmp, 0x1a4); if fd<0 { return 0 } sys_write(fd, buf, o); sys_close(fd)
503 __syscall(82, tmp as i64, path as i64, 0, 0, 0, 0) // atomic rename .tmp -> registry
504 return nn
505}
506
507// ---- PLANE-NATIVE STATE PERSISTENCE (2026-08-03, debts 1784828927 + 1784868625: the operator law is
508// planes, never tsv). The mutable schedule state (name·interval·next_due·organ per row, the thing the
509// clock resumes from) lives in a seg-store plane, written once per window via sts_seed. The plane is
510// CLOCK-EXCLUSIVE by doctrine (external adds ride the separate clockjobs- ADD plane, which the clock
511// is read-only on) -- but the same reload-merge that saved the tsv from the 1785559048 clobber is kept
512// here: rows we have never seen are preserved with their own next_due, because A WRITER THAT
513// SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER. --------------------------------
514
515// load schedule state from the plane; same 4-col row grammar as the legacy file. returns #jobs.
516func clk_load_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 {
517 np[0] = 0
518 let data: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW)
519 let dn: i64 = sts_load(prefix, data, CLK_MAXJOBS*CLK_ROWW)
520 if dn <= 0 { return 0 }
521 var i: i64=0; var ls: i64=0; var n: i64=0
522 while i < dn {
523 if data[i] == (10 as u8) {
524 if i > ls { if n < CLK_MAXJOBS {
525 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls
526 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n)
527 var f: i64=0; var p: i64=0; var iv: i64=0; var nd: i64=0; var w: i64=0; var g: i64=0
528 while p < ll {
529 if line[p] == (9 as u8) { f = f + 1 }
530 else {
531 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } }
532 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } }
533 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) {
534 let dig: i64 = (line[p]-(48 as u8)) as i64
535 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig }
536 } } } }
537 }
538 p = p + 1
539 }
540 d[w] = 0 as u8; e[g] = 0 as u8
541 if w > 0 { if g > 0 {
542 intervals[n]=iv; next_due[n]=nd; n=n+1
543 } }
544 } }
545 ls = i + 1
546 }
547 i = i + 1
548 }
549 np[0] = n
550 // B-hunk (2026-08-22): this buffer is per-call and was never freed -- at CLK_ROWW=2112 that is a
551 // 1,081,344 B address-space leak per window on a daemon whose life is 120 windows. Everything it
552 // held was COPIED into the caller's slot arrays above, so the unmap is safe by construction.
553 sys_munmap(data, CLK_MAXJOBS*CLK_ROWW)
554 return n
555}
556
557// persist schedule state to the plane (reload-merge first, then ONE whole-plane sts_seed commit).
558// Rows WE hold win (ours carry the advanced next_due); unknown rows are preserved verbatim.
559func clk_save_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 {
560 var nn: i64 = n
561 let rnames: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW)
562 let rorgs: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW)
563 let riv: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
564 let rnd: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
565 let rnp: *i64 = sys_mmap(8) as *i64
566 clk_load_plane(prefix, rnames, rorgs, riv, rnd, rnp)
567 var ri: i64 = 0
568 while ri < rnp[0] {
569 if clk_find(names, nn, clk_slot(rnames, ri)) < 0 { if nn < CLK_MAXJOBS {
570 var d2: *u8 = clk_slot(names, nn); let sm: *u8 = clk_slot(rnames, ri)
571 var a2: i64 = 0
572 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 } }
573 if a2 == CLK_NAMEW-1 { d2[a2]=0 as u8 }
574 var e2: *u8 = clk_slot(organs, nn); let so: *u8 = clk_slot(rorgs, ri)
575 var b2: i64 = 0
576 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 } }
577 if b2 == CLK_NAMEW-1 { e2[b2]=0 as u8 }
578 intervals[nn] = riv[ri]; next_due[nn] = rnd[ri]; nn = nn + 1
579 } }
580 ri = ri + 1
581 }
582 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0
583 var i: i64 = 0
584 while i < nn {
585 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 }
586 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
587 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 }
588 buf[o]=10 as u8; o=o+1
589 i = i + 1
590 }
591 sts_seed(prefix, buf, o)
592 // B-hunk (2026-08-22): free the per-call buffers -- at the widened CLK_NAMEW/CLK_ROWW these
593 // mappings are ~3.2 MB per save (1-2 saves/window) of address space the GC-free substrate
594 // otherwise leaks until the 120-window life recycle. All contents are already committed/copied.
595 sys_munmap(rnames, CLK_MAXJOBS*CLK_NAMEW)
596 sys_munmap(rorgs, CLK_MAXJOBS*CLK_NAMEW)
597 sys_munmap(riv as *u8, CLK_MAXJOBS*8)
598 sys_munmap(rnd as *u8, CLK_MAXJOBS*8)
599 sys_munmap(rnp as *u8, 8)
600 sys_munmap(buf, CLK_MAXJOBS*CLK_ROWW)
601 return nn
602}
603
604// THE ONE STATE LOADER for the live clock: plane first; if the plane is empty AND a legacy tsv
605// exists, load it (the one-time migration path) -- the caller's next clk_save_plane completes the
606// cutover. Returns 0=loaded-from-plane, 1=migrated-from-legacy, -1=nothing anywhere.
607func clk_load_state(prefix: *u8, legacy: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 {
608 if clk_load_plane(prefix, names, organs, intervals, next_due, np) > 0 { return 0 }
609 if clk_load(legacy, names, organs, intervals, next_due, np) > 0 { return 1 }
610 return 0 - 1
611}
612
613// MERGE new job declarations from an nx_store PLANE (off-tsv, additive) into the in-memory registry.
614// THE CLOBBER FIX: the clock is READ-ONLY on this plane -- external adds go via
615// `nx_store_put <plane> put <actor> <name> <interval> <organ>` (upsert-by-name, additive) and can NEVER be
616// clobbered by the clock's own (now clock-private) tsv save. Plane row cols (TAB): 0=name 1=interval(sec) 2=organ.
617// A job already present (by name) is skipped (idempotent -- re-merge is a no-op). New jobs arm at base_tick+interval.
618// Returns the number of NEW jobs added this call.
619func clk_merge_store(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, base_tick: i64) -> i64 {
620 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW)
621 let dn: i64 = sts_load(prefix, buf, CLK_MAXJOBS*CLK_ROWW)
622 if dn <= 0 { return 0 }
623 // HOISTED: allocating this inside the row loop would leak a page per row (the nx_ts_lumadiff scar).
624 let msg: *u8 = sys_mmap(CLK_ROWW)
625 // DESIRED interval per job index, 0 = not declared this pass. mmap returns zeroed pages.
626 let want: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
627 // DESIRED ORGAN per job index (2026-08-07). This reconciler recorded only the desired INTERVAL, so a
628 // declaration could re-PERIOD a job but never re-TARGET it. MEASURED: I declared segguard 600s ->
629 // nx_segsweep.elf and the plane came back interval=600 organ=nx_segguard.sh -- the cadence moved and the
630 // command did not, which is strictly WORSE than either alone: it put the 717s shell script back on a
631 // 600s period it cannot meet. Caught only because I read the organ column back instead of trusting the
632 // interval that DID change.
633 // (STAR)A RECONCILER THAT SYNCS ONE FIELD OF A ROW WILL SILENTLY DESYNC THE OTHERS -- AND A PARTIAL
634 // RECONCILE IS MORE DANGEROUS THAN NONE, BECAUSE THE FIELD THAT MOVED PROVES IT WORKED.
635 let worg: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW)
636 // B-hunk: ONE pair of row-scratch buffers for the whole scan (see the hoist note at `msg` above).
637 let nmrow: *u8 = sys_mmap(CLK_NAMEW)
638 let ogrow: *u8 = sys_mmap(CLK_NAMEW)
639 var added: i64 = 0
640 var reper: i64 = 0
641 var i: i64=0; var ls: i64=0
642 while i < dn {
643 if buf[i] == (10 as u8) {
644 if i > ls {
645 let line: *u8 = ((buf as i64)+ls) as *u8; let ll: i64 = i-ls
646 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)
647 var f: i64=0; var p: i64=0; var iv: i64=0; var w: i64=0; var g: i64=0
648 while p < ll {
649 if line[p]==(9 as u8) { f=f+1 }
650 else {
651 if f==0 { if w<CLK_NAMEW-1 { nm[w]=line[p]; w=w+1 } }
652 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) } } }
653 else { if f==2 { if g<CLK_NAMEW-1 { og[g]=line[p]; g=g+1 } } } }
654 }
655 p=p+1
656 }
657 nm[w]=0 as u8; og[g]=0 as u8
658 // MALFORMED-ROW GUARD (2026-08-03, debt 1784604739): a row lacking a name or an organ
659 // (e.g. a put that dropped an arg) must be SEEN, never silently skipped -- but never
660 // registered either (a nameless job / organless exec is garbage in the dispatcher).
661 if w == 0 { sts_werr("clk_merge_store: MALFORMED plane row (empty name) SKIPPED\n" as *u8) }
662 if w > 0 { if g == 0 { sts_werr("clk_merge_store: MALFORMED plane row (no organ) SKIPPED\n" as *u8) } }
663 if w > 0 { if g > 0 {
664 if iv < 1 { iv = 1 }
665 let n: i64 = np[0]
666 // ---- RECONCILE, NOT JUST ADD (2026-08-06) ----------------------------------------
667 // MEASURED DEFECT: this merge was ADD-ONLY -- a row whose name already existed was
668 // silently skipped -- and clocksched- (the runtime state) is rewritten by the clock
669 // every window. So there was NO SUPPORTED PATH TO RE-PERIOD OR REMOVE A CLOCK JOB:
670 // you could add one and never change it again. That is not a hypothetical. Three jobs
671 // were stuck at pathological cadences -- bootstrap-gate at 60s and worldgen-gate at
672 // 120s are GATES (test suites) running 60-360x more often than every other gate in the
673 // plane (p384kat/tls12prf 21600, uigensitegate 86400), and together with intakekeep
674 // they are 60 of the ~273 dispatches/window = 22pct of ALL demand on a clock measured
675 // at 51 completed vs 273 demanded (5.4x oversubscribed, 31 jobs STARVED per window).
676 // Whoever added them could not fix them, and the starvation was blamed on scheduling
677 // policy for days while EDF was working exactly as designed.
678 // THE FIX IS THE ESTATE'S OWN PATTERN, USED TWICE ALREADY: cron.reg -> crontab via
679 // nx_cron_reconcile, and edge443 -> iptables via nx_edge443_reconcile.sh. The declared
680 // registry is the SSOT and the runtime state is RECONCILED toward it every window.
681 // clockjobs- is now DESIRED STATE, not an append-only inbox.
682 // SAFETY: re-arm to base_tick+iv rather than leaving the old deadline, so a re-period
683 // can never fire a herd (shortening) nor strand a job past its new period (lengthening).
684 // ★A REGISTRY YOU CAN ONLY APPEND TO IS A REGISTRY THAT ACCUMULATES ITS OWN MISTAKES.
685 let ex: i64 = clk_find(names, n, nm)
686 // RECORD the intent; do not apply it here. See the apply loop after this scan.
687 if ex >= 0 { want[ex] = iv
688 var wd: *u8 = clk_slot(worg, ex); var wk: i64 = 0
689 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} }
690 if wk == CLK_NAMEW-1 { wd[wk] = 0 as u8 }
691 }
692 if ex < 0 { if n < CLK_MAXJOBS {
693 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}
694 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}
695 if iv<1 { iv=1 }
696 intervals[n]=iv; next_due[n]=base_tick+iv; np[0]=n+1; added=added+1
697 } }
698 } }
699 }
700 ls=i+1
701 }
702 i=i+1
703 }
704 // APPLY ONCE PER JOB, AFTER EVERY ROW IS READ -- LAST DECLARATION WINS.
705 // Applying inside the row loop was the first cut and it was wrong: this plane ACCUMULATES rows
706 // (pubreconcile appears THREE times in it today), so a name carrying two differing declarations
707 // would flip-flop -- two writes and two log lines every window, forever, converging on exactly
708 // the same last-row-wins answer this loop reaches quietly. Collecting the intent and applying it
709 // once is both quieter and identical in outcome.
710 var wi: i64 = 0
711 while wi < np[0] {
712 if want[wi] > 0 { if intervals[wi] != want[wi] {
713 var mo: i64 = clk_msgcat(msg, 0, "clk_merge_store: REPERIOD " as *u8)
714 mo = clk_msgcat(msg, mo, clk_slot(names, wi))
715 mo = clk_msgcat(msg, mo, " " as *u8)
716 mo = clk_itoa(msg, mo, intervals[wi])
717 mo = clk_msgcat(msg, mo, "s -> " as *u8)
718 mo = clk_itoa(msg, mo, want[wi])
719 mo = clk_msgcat(msg, mo, "s (declared in clockjobs-; re-armed at now+interval)\n" as *u8)
720 msg[mo] = 0 as u8
721 sts_werr(msg)
722 intervals[wi] = want[wi]
723 next_due[wi] = base_tick + want[wi]
724 reper = reper + 1
725 } }
726 // RE-TARGET: the organ is a field of the row too, and a declaration that names a different
727 // binary must MOVE the job, not just its cadence. Separate from the interval branch so a
728 // pure re-target (same period, new organ) is honoured.
729 if wi < np[0] { let wo: *u8 = clk_slot(worg, wi)
730 if wo[0] != (0 as u8) { if clk_streq(wo, clk_slot(organs, wi)) == 0 {
731 var mo2: i64 = clk_msgcat(msg, 0, "clk_merge_store: RETARGET " as *u8)
732 mo2 = clk_msgcat(msg, mo2, clk_slot(names, wi))
733 mo2 = clk_msgcat(msg, mo2, " -> " as *u8)
734 mo2 = clk_msgcat(msg, mo2, wo)
735 mo2 = clk_msgcat(msg, mo2, " (declared in clockjobs-)\n" as *u8)
736 msg[mo2] = 0 as u8
737 sts_werr(msg)
738 var od: *u8 = clk_slot(organs, wi); var ok2: i64 = 0
739 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} }
740 if ok2 == CLK_NAMEW-1 { od[ok2] = 0 as u8 }
741 reper = reper + 1
742 } } }
743 wi = wi + 1
744 }
745 // RETURN ADDS *AND* RE-PERIODS (2026-08-06). This returned only `added`, so a caller could not tell
746 // that a RECONCILE had occurred -- reper was counted, logged and then dropped on the floor. The single
747 // caller discarded the value entirely, so widening it is safe and makes the reconcile actionable.
748 // (STAR)A COUNTER THAT IS COMPUTED, LOGGED AND NOT RETURNED IS A FACT THE CALLER CANNOT ACT ON.
749 // B-hunk: free the per-call transients (buf/msg/want/worg + the hoisted row scratch); everything
750 // they held was applied to the caller's arrays above.
751 sys_munmap(buf, CLK_MAXJOBS*CLK_ROWW)
752 sys_munmap(msg, CLK_ROWW)
753 sys_munmap(want as *u8, CLK_MAXJOBS*8)
754 sys_munmap(worg, CLK_MAXJOBS*CLK_NAMEW)
755 sys_munmap(nmrow, CLK_NAMEW)
756 sys_munmap(ogrow, CLK_NAMEW)
757 return added + reper
758}
759
760// ============================================================================================
761// EDF / WALL-CLOCK SCHEDULING (2026-08-04, debt 1785872141 -- operator: "get the clock to sota")
762//
763// THE MEASURED DEFECT clk_run_tickless has BY CONSTRUCTION: its logical tick T advances ONLY by the
764// sleep amount (T = minnd), while REAL time also advances by however long the dispatched children
765// took (clk_dispatch_run forks and sys_wait4s EVERY job, serially, inside the beat). So logical time
766// drifts behind wall-clock time in proportion to dispatch load, and EVERY job's period silently
767// STRETCHES: measured 2026-08-04, evidencebeat (interval 21600 = "6 hours") fired ONCE in 21 HOURS
768// while the clock was demonstrably alive (tick current, window-end a=30 b=132). Even 60s netobs
769// slipped to 243s. The fast jobs pay the drift too, but a 6h job pays it 360x over.
770// u2605u2605u2605u2605u2605u2605 A SCHEDULER THAT ADVANCES ITS OWN CLOCK BY WHAT IT SLEPT -- NOT BY WHAT ELAPSED --
771// MEASURES ITS OWN IDLENESS AND CALLS IT TIME. Every deadline it derives is then a lie under load.
772//
773// THE SOTA SHAPE (what real schedulers do; cron/systemd-timers/EDF literature all agree):
774// 1. deadlines are ABSOLUTE WALL-CLOCK instants, never a self-advanced counter
775// 2. re-read the clock AFTER every dispatch, so job runtime cannot be lost
776// 3. dispatch EARLIEST-DEADLINE-FIRST so the most-overdue job goes first (EDF is optimal for
777// meeting deadlines on one resource -- and it is exactly what stops slow-job starvation)
778// 4. catch-up without runaway: re-arm past now by WHOLE intervals, and REPORT missed periods
779// instead of pretending they happened
780// The decision math is factored into PURE functions below precisely so a gate can prove a 6-hour
781// period behaves correctly in MILLISECONDS with fabricated clock values -- a scheduler you can only
782// test by waiting 6 hours is a scheduler nobody tests.
783
784// deadlines below this are LEGACY LOGICAL TICKS (the old counter ran ~3e6; epochs are ~1.78e9), so the
785// one-time migration is unambiguous and needs no flag day. NEVER compare an epoch against a tick.
786const CLK_EPOCH_FLOOR: i64 = 1000000000
787
788// PURE: index of the overdue job with the EARLIEST deadline (EDF), or -1 if nothing is due.
789// Ties break toward the lower index = stable, so a tie can never rotate two jobs into each other's slot.
790// DEMAND vs CAPACITY (2026-08-06, measured on the live plane). EDF decides WHO runs next; it can never
791// decide HOW MANY CAN RUN. If the registry asks for more dispatches per window than the window can
792// complete, then EVERY ordering starves someone and the scheduler is merely choosing the victim -- so
793// the deficit has to be a NUMBER the clock PUBLISHES, not something inferred from a late heartbeat days
794// later. MEASURED THE DAY THIS SHIPPED: 45 jobs demanded ~273 dispatches per 1800s window while the
795// window completed 51 (~35s of real child time per dispatch) = ~5.4x oversubscribed, and 273 exceeds even
796// TLN_MAXDISPATCH=240, so the set could not be served at INFINITE speed. That is why netobs (60s period)
797// was firing at 284s+ intervals with its registry row and its elf both perfectly healthy.
798// CONSERVATIVE BY CONSTRUCTION: integer division FLOORS, and any job whose period exceeds the window
799// contributes 0. So this is a LOWER BOUND on demand -- if even this floor exceeds capacity,
800// oversubscription is PROVEN, never merely suspected.
801func clk_demand_per_window(intervals: *i64, n: i64, window_secs: i64) -> i64 {
802 var need: i64 = 0
803 var i: i64 = 0
804 while i < n {
805 if intervals[i] > 0 { need = need + (window_secs / intervals[i]) }
806 i = i + 1
807 }
808 return need
809}
810
811// PARALLEL-SAFE PICK (2026-08-06): identical to clk_edf_pick except it SKIPS jobs already IN FLIGHT.
812// THE HAZARD IT EXISTS FOR, and it is not hypothetical: a job is re-armed only AFTER its child is reaped,
813// so while a run is in progress its deadline is still in the past -- and a job whose runtime EXCEEDS its
814// period (worldgen: 289s measured against a 120s declared period) is overdue for the WHOLE of its own run.
815// Under bounded-parallel dispatch a picker without this mask would immediately dispatch a SECOND copy of
816// the exact organ already saturating the box, then a third, up to the slot cap. Two concurrent four-world
817// renders is strictly WORSE than the serial starvation parallelism was introduced to fix.
818// (STAR)PARALLELISM TURNS "ALWAYS OVERDUE" FROM A STARVATION BUG INTO A FORK BOMB -- THE BUSY MASK IS NOT
819// AN OPTIMISATION, IT IS THE CORRECTNESS CONDITION. Kept as a SEPARATE pure function so it is gate-provable
820// on fabricated inputs (T10/T11) before it is ever wired to a live dispatcher.
821func clk_edf_pick_free(deadlines: *i64, n: i64, now: i64, busy: *i64) -> i64 {
822 var best: i64 = 0 - 1
823 var bestd: i64 = 0
824 var i: i64 = 0
825 while i < n {
826 if busy[i] == 0 {
827 if deadlines[i] <= now {
828 if best < 0 { best = i; bestd = deadlines[i] }
829 else { if deadlines[i] < bestd { best = i; bestd = deadlines[i] } }
830 }
831 }
832 i = i + 1
833 }
834 return best
835}
836
837func clk_edf_pick(deadlines: *i64, n: i64, now: i64) -> i64 {
838 var best: i64 = 0 - 1
839 var bestd: i64 = 0
840 var i: i64 = 0
841 while i < n {
842 if deadlines[i] <= now {
843 if best < 0 { best = i; bestd = deadlines[i] }
844 else { if deadlines[i] < bestd { best = i; bestd = deadlines[i] } }
845 }
846 i = i + 1
847 }
848 return best
849}
850
851// PURE: soonest deadline across all jobs (what a tickless sleeper must sleep until). -1 if n==0.
852func clk_edf_next(deadlines: *i64, n: i64) -> i64 {
853 if n <= 0 { return 0 - 1 }
854 var m: i64 = deadlines[0]
855 var i: i64 = 1
856 while i < n { if deadlines[i] < m { m = deadlines[i] } i = i + 1 }
857 return m
858}
859
860// PURE: how many whole periods job i has MISSED at `now` (0 = on time / early). This is the honest
861// starvation measure -- a job 3 periods late is a 3, not a "fired".
862func clk_edf_missed(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 {
863 if deadlines[i] > now { return 0 }
864 var iv: i64 = intervals[i]
865 if iv < 1 { iv = 1 }
866 return (now - deadlines[i]) / iv
867}
868
869// PURE: re-arm job i past `now` by WHOLE intervals (catch-up safe: never fires twice for one missed
870// window, never drifts off-phase). Returns the periods skipped so the caller can REPORT them.
871func clk_edf_rearm(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 {
872 var iv: i64 = intervals[i]
873 if iv < 1 { iv = 1 }
874 var skipped: i64 = 0
875 while deadlines[i] <= now { deadlines[i] = deadlines[i] + iv; skipped = skipped + 1 }
876 if skipped > 0 { skipped = skipped - 1 } // the first advance is the fire itself, not a miss
877 return skipped
878}
879
880// PURE: one-time migration of legacy logical-tick deadlines to wall-clock instants. A tick value can
881// never be a valid epoch, so this is exact. Jobs arm at now+interval (their author's intent) rather
882// than firing a thundering herd at now. Returns how many rows were converted.
883func clk_edf_migrate(deadlines: *i64, intervals: *i64, n: i64, now: i64) -> i64 {
884 var c: i64 = 0
885 var i: i64 = 0
886 while i < n {
887 if deadlines[i] < CLK_EPOCH_FLOOR {
888 var iv: i64 = intervals[i]
889 if iv < 1 { iv = 1 }
890 deadlines[i] = now + iv
891 c = c + 1
892 }
893 i = i + 1
894 }
895 return c
896}
897
898// THE EDF WINDOW: wall-clock anchored, EDF-ordered, starvation-reporting. Runs until `maxdispatch`
899// jobs have been dispatched or `budget_secs` of REAL time is gone, sleeping only when nothing is due.
900// out[0]=dispatched out[1]=slept_secs out[2]=final_now out[3]=starved_jobs out[4]=max_lateness_secs
901// out[5]=exec_secs (real time inside children -- the number the old design silently threw away).
902// ---- IN-FLIGHT BEAT (2026-08-06) -------------------------------------------------------------
903// PROVEN LIVE THIS SESSION, NOT HYPOTHESISED: the clock overran its own 1800s budget by 42s and
904// stopped dispatching for 475s+ while one child held it, because clk_dispatch_one waits with NO
905// deadline (debt 1786056459). sitecheck (30s period) froze; every beat in the estate went dead.
906// THE INSTRUMENT PROBLEM THAT INCIDENT EXPOSED: max_exec, starved, and the entire window-end report
907// are written AFTER clk_run_edf RETURNS. So the one failure mode that prevents returning is exactly
908// the one the report can never describe -- during the stall the newest window-end beat was 1800s old
909// and named a completely different window.
910// (STAR)AN INSTRUMENT THAT REPORTS ONLY AT COMPLETION CANNOT REPORT WHAT PREVENTS COMPLETION.
911// This beat is written BEFORE every dispatch and overwritten AFTER it, so a hang is visible the
912// moment it starts and NAMES the organ holding the scheduler. Cost is 2 file writes per dispatch,
913// negligible beside the fork+execve it brackets.
914func 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 }
915func 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 }
916func clk_inflight(tag: *u8, organ: *u8, a: i64, b: i64) -> i64 {
917 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)
918 o = clk_ifw("CLOCKBEAT " as *u8, st, o); o = clk_ifw(tag, st, o)
919 o = clk_ifw(" organ=" as *u8, st, o); o = clk_ifw(organ, st, o)
920 o = clk_ifw(" a=" as *u8, st, o); o = clk_ifn(st, o, a)
921 o = clk_ifw(" b=" as *u8, st, o); o = clk_ifn(st, o, b)
922 o = clk_ifw(" t=" as *u8, st, o); o = clk_ifn(st, o, sys_now_realtime_sec())
923 st[o]=10 as u8; o=o+1; st[o]=0 as u8
924 let fd: i64 = sys_openat_wr("sites/nishifamily/clock_inflight.txt" as *u8, 0x1a4)
925 if fd >= 0 { sys_write(fd, st, o); sys_close(fd) }
926 return 0
927}
928
929func clk_run_edf(organs: *u8, names: *u8, intervals: *i64, deadlines: *i64, n: i64, maxdispatch: i64, budget_secs: i64, tick_ms: i64, out: *i64) -> i64 {
930 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
931 let t0: i64 = sys_now_realtime_sec()
932 var disp: i64 = 0
933 var slept: i64 = 0
934 var starved: i64 = 0
935 var maxlate: i64 = 0
936 var execs: i64 = 0
937 var maxexec: i64 = 0
938 var maxidx: i64 = 0 - 1
939 // STARVED AXIS, NAMED (2026-08-14). Mirrors maxexec/maxidx below, which already record the worst
940 // single dispatch AND which job owned it. That law was applied to the SLOW axis and never to the
941 // STARVED axis, so out[3] shipped as a bare count.
942 var maxmissed: i64 = 0
943 var starvidx: i64 = 0 - 1
944 // STORM-AWARE DISPATCH state (2026-09-02): the heavy list is read ONCE per window (sys_read_file sizes
945 // from the file, cannot short-read; an absent conf -> hn=0 -> nothing is heavy), the ruler's scratch is
946 // allocated ONCE, and the deferral count is published in out[10].
947 var deferred: i64 = 0
948 let ioa: *i64 = sys_mmap(16) as *i64
949 let hlen: *i64 = sys_mmap(16) as *i64
950 hlen[0] = 0
951 let hconf: *u8 = sys_read_file(CLK_HEAVY_CONF, hlen)
952 var hn: i64 = 0
953 if (hconf as i64) != 0 { hn = hlen[0] }
954 var run: i64 = 1
955 while run == 1 {
956 // RE-READ THE CLOCK EVERY ITERATION -- this single line is the fix: child runtime is now
957 // observed, not assumed away.
958 let now: i64 = sys_now_realtime_sec()
959 if now - t0 >= budget_secs { run = 0 }
960 if disp >= maxdispatch { run = 0 }
961 if run == 1 {
962 var k: i64 = clk_edf_pick(deadlines, n, now)
963 // STORM-AWARE DISPATCH (2026-09-02): a HEAVY job (listed in CLK_HEAVY_CONF) that is due while the
964 // shared I/O-storm ruler reports zero spawn budget is re-armed to now + clk_defer_secs(interval)
965 // and NOT forked this pass; k becomes -1 so the else-branch below sleeps until the next real
966 // deadline exactly as if nothing were due. Light jobs, an absent conf and an UNREADABLE ruler
967 // take the pre-change path byte-for-byte. The deferral is announced on the in-flight beat with
968 // b = procs_blocked, so the docroot witness names the storm that caused it.
969 if k >= 0 { if clk_heavy_listed(hconf, hn, clk_slot(names, k)) == 1 {
970 let bud: i64 = clk_storm_budget(ioa)
971 if clk_storm_defer(1, bud) == 1 {
972 deferred = deferred + 1
973 deadlines[k] = now + clk_defer_secs(intervals[k])
974 clk_inflight("deferred-storm" as *u8, clk_slot(organs, k), ioa[1], k)
975 k = 0 - 1
976 }
977 } }
978 if k >= 0 {
979 let missed: i64 = clk_edf_missed(deadlines, intervals, k, now)
980 let late: i64 = now - deadlines[k]
981 if late > maxlate { maxlate = late }
982 if missed > 0 {
983 starved = starved + 1
984 // NAME THE STARVER, DO NOT MERELY COUNT IT. A window reported "STARVED 3" and nothing
985 // anywhere said WHICH 3, so a job persistently losing EDF was undiagnosable.
986 // MEASURED 2026-08-14: a window published b=3 STARVED while nx_resmon's leak axis was
987 // flipping to UNOBSERVABLE off a stale memvel snapshot -- and there was no way to tell
988 // whether memvel was one of the three. A COUNT WITHOUT A WORKLIST IS NOT ACTIONABLE.
989 // Worst-offender (most periods missed) rather than a list: it needs no allocation, it
990 // matches the maxexec/maxidx idiom already proven here, and the worst starver is the
991 // one whose period is actually unschedulable.
992 if missed > maxmissed { maxmissed = missed; starvidx = k }
993 }
994 clk_edf_rearm(deadlines, intervals, k, now)
995 let e0: i64 = now
996 clk_inflight("in-flight" as *u8, clk_slot(organs, k), e0, k)
997 let ran: i64 = clk_dispatch_one(organs, names, k)
998 let e1: i64 = sys_now_realtime_sec()
999 clk_inflight("idle" as *u8, clk_slot(organs, k), e1 - e0, k)
1000 // ---- COST-ADAPTIVE BACKOFF (2026-08-06) --------------------------------------
1001 // PROVEN LIVE, NAMED BY THE IN-FLIGHT BEAT ABOVE: nx_worldgen_gate.sov.elf holds a
1002 // 120s-period slot for 500s+ per run. A job whose RUNTIME EXCEEDS ITS PERIOD is always
1003 // overdue, so EDF correctly picks it again immediately, and on a SERIAL dispatcher with
1004 // no deadline that is a PERMANENT DENIAL OF SERVICE to every other job -- sitecheck (30s)
1005 // and netobs (60s) both froze behind it and every beat in the estate went dead.
1006 // (STAR)A JOB WHOSE RUNTIME EXCEEDS ITS PERIOD CANNOT BE SCHEDULED AT THAT PERIOD -- THE
1007 // REGISTRY IS ASSERTING A COST NOBODY EVER MEASURED. So MEASURE IT AND BELIEVE THE
1008 // MEASUREMENT: re-arm such a job to now + its OWN observed runtime instead of now +
1009 // its declared interval. It still runs, it still makes progress, but it can never again
1010 // starve the whole registry, and the declared interval becomes a FLOOR not a fiction.
1011 // Chosen over SIGKILL deliberately: killing needs a raw signal syscall inside a forking
1012 // scheduler, and this tree has a live trap where literal syscall numbers are rv64->x86
1013 // translated. This fix needs NO new syscall and cannot mis-target a pid.
1014 if e1 - e0 > intervals[k] { deadlines[k] = e1 + (e1 - e0) * CLK_HOGDUTY } // dt is declared BELOW this point; use the operands already in scope
1015 let dt: i64 = e1 - e0
1016 execs = execs + dt
1017 // ★PUBLISH A MAX BEFORE YOU ACT ON A MEAN (2026-08-06). Aggregate exec_secs cannot separate
1018 // 'every child is a bit slow' from 'ONE child hung for most of the window' -- both produce the
1019 // same dispatch count and the same mean, and they have OPPOSITE remedies (raise capacity vs
1020 // kill one hog). clk_dispatch_one waits on the child with NO deadline (debt 1786056459), so the
1021 // hung case is not hypothetical. Record the worst SINGLE dispatch and WHICH job owned it.
1022 if dt > maxexec { maxexec = dt; maxidx = k }
1023 if ran == 1 { disp = disp + 1; fired[k] = 1 }
1024 } else {
1025 let nd: i64 = clk_edf_next(deadlines, n)
1026 if nd < 0 { run = 0 } else {
1027 var wait: i64 = nd - now
1028 if wait < 1 { wait = 1 }
1029 let left: i64 = budget_secs - (now - t0)
1030 if wait > left { wait = left }
1031 if wait < 1 { run = 0 } else {
1032 let ts: *i64 = sys_mmap(16) as *i64
1033 ts[0] = wait
1034 ts[1] = 0
1035 __syscall(35, ts as i64, 0, 0, 0, 0, 0)
1036 slept = slept + wait
1037 }
1038 }
1039 }
1040 }
1041 }
1042 out[0] = disp; out[1] = slept; out[2] = sys_now_realtime_sec()
1043 out[3] = starved; out[4] = maxlate; out[5] = execs
1044 out[6] = maxexec; out[7] = maxidx // worst single dispatch, and the job index that owned it
1045 out[8] = starvidx; out[9] = maxmissed // WORST STARVER: job index, and how many of its periods it missed
1046 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
1047 // CALLER CONTRACT: `out` must now be at least 10 i64 slots (80 bytes). nx_clock_tickless allocated
1048 // exactly 64 bytes = 8 slots, so writing out[8] there would have run off the end of the mapping --
1049 // the caller was widened in the same change. Any new caller must allocate >= 80 bytes.
1050 return disp
1051}
1052
1053// THE ARGV SPLIT, AS A PURE FUNCTION (2026-08-22, command-slot truncation fix). Copies the organ
1054// slot into `buf` (>= CLK_NAMEW+8 bytes), splits on single spaces IN PLACE, fills `argv`
1055// (>= 8*(CLK_ARGV_MAX+1) bytes) and returns the token count; argv[ac] is NULL. Factored out of
1056// clk_dispatch_one for the same reason the EDF math is factored into pure functions above: so a gate
1057// can prove an 18-token, 300-byte command survives the split WITHOUT a fork, and so the mutation
1058// site (CLK_NAMEW) is reachable from a tooth.
1059// OVERFLOW IS ANNOUNCED, NEVER SILENT: a command carrying more than CLK_ARGV_MAX tokens keeps the
1060// first CLK_ARGV_MAX and reports ARGV-OVERFLOW naming the job and the dropped count -- the old
1061// `if ac < 16` cap dropped every token past 15 with NO diagnostic, the same silent class as the
1062// 127-byte cut this change retires. Token-accept logic is otherwise byte-identical to the old code.
1063func clk_split_argv(raw0: *u8, buf: *u8, argv: *i64, label: *u8) -> i64 {
1064 var bl: i64 = 0
1065 var cdone: i64 = 0
1066 while cdone == 0 {
1067 if bl >= CLK_NAMEW - 1 { cdone = 1 }
1068 else { if raw0[bl] == (0 as u8) { cdone = 1 } else { buf[bl] = raw0[bl]; bl = bl + 1 } }
1069 }
1070 buf[bl] = 0 as u8
1071 argv[0] = buf as i64
1072 var ac: i64 = 1
1073 var dropped: i64 = 0
1074 var sp: i64 = 0
1075 while sp < bl {
1076 if buf[sp] == (32 as u8) {
1077 buf[sp] = 0 as u8
1078 if buf[sp+1] != (0 as u8) {
1079 if ac < CLK_ARGV_MAX { argv[ac] = ((buf as i64) + sp + 1); ac = ac + 1 } else { dropped = dropped + 1 }
1080 }
1081 }
1082 sp = sp + 1
1083 }
1084 argv[ac] = 0
1085 if dropped > 0 {
1086 let om: *u8 = sys_mmap(CLK_ROWW)
1087 var oo: i64 = clk_msgcat(om, 0, "clk_split_argv: ARGV-OVERFLOW job=" as *u8)
1088 oo = clk_msgcat(om, oo, label)
1089 oo = clk_msgcat(om, oo, " kept=" as *u8)
1090 oo = clk_itoa(om, oo, ac)
1091 oo = clk_msgcat(om, oo, " dropped=" as *u8)
1092 oo = clk_itoa(om, oo, dropped)
1093 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)
1094 om[oo] = 0 as u8
1095 sts_werr(om)
1096 sys_munmap(om, CLK_ROWW)
1097 }
1098 return ac
1099}
1100
1101// dispatch EXACTLY ONE job by index (the EDF loop picks the victim; this just runs it). Split out of
1102// clk_dispatch_run so ordering policy and exec mechanics are separable -- and so the EDF loop can
1103// re-read the clock between jobs. Returns 1 if the organ really ran, 0 if exec failed (127).
1104func clk_dispatch_one(organs: *u8, names: *u8, i: i64) -> i64 {
1105 // ARGV SUPPORT (2026-08-07). This built argv[0]=path, argv[1]=0 -- an organ could take NO ARGUMENTS.
1106 // (STAR)A DISPATCHER THAT CANNOT PASS ARGUMENTS MANUFACTURES SHELL SCRIPTS -- THE WRAPPERS ARE A
1107 // SYMPTOM OF THE CALLING CONVENTION, NOT OF LAZINESS.
1108 // The organ field is SPLIT ON SPACES into a real argv, so a row may read
1109 // fallbackharden<TAB>600<TAB>nx_actlog.elf harden knowledge/status/fallback.jrnl
1110 // BACK-COMPATIBLE BY CONSTRUCTION: a field with no space yields argv[0] only, and the slot is
1111 // CLK_NAMEW = CLK_CMD_CAP (nx_clock_caps.nx, shared with the writer nx_clockjob), so a command the
1112 // writer admits ALWAYS fits. The split itself is clk_split_argv above -- pure, gate-provable,
1113 // and it announces ARGV-OVERFLOW instead of silently dropping tokens.
1114 let buf: *u8 = sys_mmap(CLK_NAMEW + 8)
1115 let argv: *i64 = sys_mmap(8*(CLK_ARGV_MAX+1)) as *i64
1116 clk_split_argv(clk_slot(organs, i), buf, argv, clk_slot(names, i))
1117 let path: *u8 = buf
1118 let path: *u8 = buf
1119 __syscall(90, path as i64, 0x1ed, 0, 0, 0, 0) // chmod +x the EXECUTABLE only, after the split
1120 let pid: i64 = sys_fork()
1121 if pid == 0 {
1122 // PER-JOB OUTPUT CAPTURE (2026-08-07). The SECOND service the .sh wrappers rendered, after argv:
1123 // nx_fallback_harden.cron.sh appends to logs/fallback_harden.log and its own header calls that
1124 // "the trend IS the telemetry". Replacing a wrapper with a bare organ row would have SILENTLY
1125 // LOST that log -- stripping a feature (rule 25) to remove a shell script.
1126 // (STAR)BEFORE REPLACING A WRAPPER, ENUMERATE EVERYTHING IT PROVIDES -- THE ARGUMENT YOU NOTICED
1127 // IS RARELY THE ONLY SERVICE IT RENDERS.
1128 // Every clock organ now gets, for free, the capture each wrapper hand-rolled: stdout AND stderr
1129 // append to logs/<jobname>.log. Named by JOB, not organ, so two jobs sharing a binary stay
1130 // distinguishable (clobbertest and frontdoor both run nx_atlas_frontdoor.elf). Fail-open: if the
1131 // log cannot be opened the child still execs -- telemetry must never be able to stop the beat.
1132 // Uses the sys_dup3 redirect idiom already proven in nx_guarded_run.
1133 let lp: *u8 = sys_mmap(CLK_NAMEW + 16); var lo: i64 = 0 // "logs/" + NAME slot + ".log" + NUL; DERIVED from the slot
1134 lo = clk_msgcat(lp, lo, "logs/" as *u8)
1135 lo = clk_msgcat(lp, lo, clk_slot(names, i))
1136 lo = clk_msgcat(lp, lo, ".log" as *u8)
1137 lp[lo] = 0 as u8
1138 let ofd: i64 = sys_openat_append(lp, 0x1a4)
1139 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
1140 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
1141 sys_execve(path, argv, envp)
1142 sys_exit(127)
1143 }
1144 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0
1145 // BOUNDED WAIT (was a bare blocking wait4 -- the single line that let one child own the clock).
1146 let t0ms: i64 = sys_now_ms()
1147 var reaped: i64 = 0
1148 var timedout: i64 = 0
1149 while reaped == 0 {
1150 let w: i64 = sys_wait4(pid, st, 1)
1151 if w == pid { reaped = 1 }
1152 else { if w < 0 { reaped = 1 }
1153 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) } } }
1154 }
1155 // A REAPED-ON-DEADLINE CHILD DID EXECUTE -- report it as dispatched so the duty ceiling above backs
1156 // it off from its OWN measured cost. Reporting 0 here would hide the hog from the very rule that tames it.
1157 // LIVE-PATH TELEMETRY (2026-08-07). clk_actlog was first added to clk_dispatch_run -- which is the
1158 // DEAD path: nx_clock_tickless calls clk_run_edf (it REPLACES clk_run_tickless), and clk_run_edf
1159 // dispatches through THIS function. The instrumented binary was verifiably the one running, and it
1160 // still logged nothing, because I had checked WHICH BINARY IS LIVE and never WHICH CODE PATH IS.
1161 // Logged on EVERY exit including the deadline kill, since a job that had to be killed is exactly
1162 // the one a reader needs to see. 124 mirrors the shell timeout convention.
1163 // Both rows carry the job's clock ROW NAME (2026-09-18): the organ column keeps argv[0], the name
1164 // rides as the last token, so a reader can tell WHICH row ran when many rows share one organ.
1165 if timedout == 1 { clk_actlog(path, 124, clk_slot(names, i)); return 1 }
1166 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff
1167 clk_actlog(path, code, clk_slot(names, i))
1168 if sig != 0 { return 1 }
1169 if code != 127 { return 1 }
1170 return 0
1171}
1172
1173// load the registry into the arrays. returns #jobs (also written to np[0]).
1174func clk_load(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 {
1175 np[0] = 0
1176 let lenp: *i64 = sys_mmap(8) as *i64
1177 let data: *u8 = sys_read_file(path, lenp)
1178 if (data as i64) == 0 { return 0 }
1179 let dn: i64 = lenp[0]; var i: i64=0; var ls: i64=0; var n: i64=0
1180 while i < dn {
1181 if data[i] == (10 as u8) {
1182 if i > ls { if n < CLK_MAXJOBS {
1183 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls
1184 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n)
1185 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
1186 while p < ll {
1187 if line[p] == (9 as u8) { f = f + 1 }
1188 else {
1189 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } }
1190 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } }
1191 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) {
1192 let dig: i64 = (line[p]-(48 as u8)) as i64
1193 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig }
1194 } } } }
1195 }
1196 p = p + 1
1197 }
1198 d[w] = 0 as u8; e[g] = 0 as u8
1199 intervals[n]=iv; next_due[n]=nd; n=n+1
1200 } }
1201 ls = i + 1
1202 }
1203 i = i + 1
1204 }
1205 np[0] = n
1206 return n
1207}