code wiki / _hdl_build / nx_clock_sched.nx
nx_clock_sched.nx source
↩ module page · 567 lines · 32476 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"
16const CLK_MAGIC_1000000: i64 = 1000000
17
18const CLK_MAXJOBS: i64 = 128
19const CLK_NAMEW: i64 = 128 // bytes per name/organ slot. WAS 48 (2026-08-03, debt 1784413227):
20 // every organ path longer than 47 bytes was SILENTLY TRUNCATED at
21 // register/load/save -- sitecheck (51-char abs path), worldgen-gate
22 // and bootstrap-gate (63-char _build paths) all fork-failed 127 on
23 // every tick, invisibly, because the dispatcher chmods+execs the
24 // truncated string. 128 holds every path shape the estate uses.
25const CLK_ROWW: i64 = 320 // per-row serialization budget (2 slots + 2 numbers + tabs)
26const CLK_REG: *u8 = "knowledge/sched/jobs.tsv" // RETIRED LEGACY PATH (2026-08-03, debt 1785792856):
27 // kept only so old fixtures parse; the live state SSOT is the
28 // clocksched- seg-store plane (clk_load_state / clk_save_plane below)
29
30func clk_slot(names: *u8, i: i64) -> *u8 { return ((names as i64) + i*CLK_NAMEW) as *u8 }
31func 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 }
32
33func 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 }
34
35// REGISTER a periodic job = add a divider off the one clock. Idempotent: re-registering the same name is a no-op
36// (so capabilities can declare their job every boot without ever creating a duplicate pulse). returns 1 if added.
37func clk_register(names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, name: *u8, organ: *u8, interval: i64) -> i64 {
38 let n: i64 = np[0]
39 if clk_find(names, n, name) >= 0 { return 0 }
40 if n >= CLK_MAXJOBS { return 0 }
41 var d: *u8 = clk_slot(names, n); var i: i64=0
42 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 } }
43 if i==CLK_NAMEW-1 { d[i]=0 as u8 }
44 var e: *u8 = clk_slot(organs, n); var j: i64=0
45 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 } }
46 if j==CLK_NAMEW-1 { e[j]=0 as u8 }
47 var iv: i64 = interval; if iv < 1 { iv = 1 }
48 intervals[n] = iv; next_due[n] = iv; np[0] = n + 1
49 return 1
50}
51
52// THE DISPATCHER: advance to logical tick `now`; mark + return how many jobs are DUE (next_due<=now), advancing
53// each due job's next_due by its interval (catch-up safe: a job never fires more than once for a missed window).
54func clk_tick(intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 {
55 var count: i64 = 0; var i: i64 = 0
56 while i < n {
57 if next_due[i] <= now {
58 fired[i] = 1; count = count + 1
59 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (no runaway on a skipped tick)
60 } else { fired[i] = 0 }
61 i = i + 1
62 }
63 return count
64}
65
66// THE FUNCTIONAL DISPATCH: advance to `now` and actually RUN each due job by fork+exec of its organ elf path
67// (the same fork+exec idiom nx_god_pulse / nx_aw_hostctl use). One dispatcher runs N jobs at their divided rates;
68// there is NO per-job daemon. Parent waits each child so a slow job can't be lost (a real scheduler can make this
69// bounded-concurrent / fire-and-forget). `organs` is a parallel slot array (organ[i] = an executable path). Returns
70// the number of jobs dispatched this tick.
71func clk_dispatch_run(organs: *u8, intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 {
72 var count: i64 = 0; var i: i64 = 0
73 while i < n {
74 if next_due[i] <= now {
75 fired[i] = 1
76 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (catch-up safe)
77 let path: *u8 = clk_slot(organs, i)
78 __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
79 let pid: i64 = sys_fork()
80 if pid == 0 {
81 let argv: *i64 = sys_mmap(32) as *i64; argv[0] = path as i64; argv[1] = 0
82 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
83 sys_execve(path, argv, envp)
84 sys_exit(127)
85 }
86 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0
87 sys_wait4(pid, st, 0)
88 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff
89 // HONEST count: a job whose organ failed to exec (child exit 127 = missing/broken organ) did NOT run,
90 // so it is not counted as dispatched (it gets fired[]=1 so a caller can flag it, mirroring the
91 // publisher's dead-letter). A crash (signalled) or any other exit means the organ DID run.
92 if sig != 0 { count = count + 1 } else { if code != 127 { count = count + 1 } }
93 } else { fired[i] = 0 }
94 i = i + 1
95 }
96 return count
97}
98
99// TICKLESS run (sched_tickless lesson): from start_tick, run up to maxbeats beats, but before each beat SLEEP
100// exactly until the MINIMUM next_due across all jobs -- skipping every idle tick -- then dispatch the due organs.
101// Advances next_due[] in place (the caller persists for resume). out[0..3] = beats, dispatches, skipped_idle,
102// final_tick. ONE implementation, shared by the driver (nx_clock_tickless) and its gate (no parallel copy).
103func clk_run_tickless(organs: *u8, intervals: *i64, next_due: *i64, n: i64, start_tick: i64, maxbeats: i64, tick_ms: i64, out: *i64) -> i64 {
104 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
105 var T: i64 = start_tick; var beats: i64 = 0; var disp: i64 = 0; var skipped: i64 = 0
106 while beats < maxbeats {
107 var minnd: i64 = next_due[0]; var i: i64 = 1
108 while i < n { if next_due[i] < minnd { minnd = next_due[i] } i = i + 1 }
109 if minnd <= T { minnd = T + 1 }
110 let skip: i64 = minnd - T
111 if skip > 1 { skipped = skipped + (skip - 1) }
112 let totms: i64 = skip * tick_ms
113 let ts: *i64 = sys_mmap(16) as *i64; ts[0] = totms / 1000; ts[1] = (totms - (totms/1000)*1000) * CLK_MAGIC_1000000
114 __syscall(35, ts as i64, 0, 0, 0, 0, 0) // nanosleep until the next due event (tickless)
115 T = minnd
116 disp = disp + clk_dispatch_run(organs, intervals, next_due, n, T, fired)
117 beats = beats + 1
118 }
119 out[0] = beats; out[1] = disp; out[2] = skipped; out[3] = T
120 return disp
121}
122
123// STATUS helpers (the consolidation payoff: ONE place shows every periodic job). clk_due_in = ticks until a job
124// fires (<=0 means due now). clk_twin_ok = 1 iff the job's organ elf actually exists (a 0 means it is registered
125// but un-blessed -> the dispatcher would honestly report it didn't run -> surface it in status, do not hide it).
126func clk_due_in(next_due_i: i64, now: i64) -> i64 { return next_due_i - now }
127func clk_twin_ok(organ: *u8) -> i64 { let fd: i64 = sys_openat_rd(organ); if fd < 0 { return 0 } sys_close(fd); return 1 }
128
129func 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 }
130
131// persist the registry (the ONE crontab): name<TAB>interval<TAB>next_due per line. atomic via tmp+rename.
132func clk_save(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 {
133 // ---- RELOAD-MERGE BEFORE SAVE (2026-07-31, id 1785559048) -------------------------------------------
134 // PROVEN CLOBBER, by controlled experiment not inference: a well-formed row appended to the registry via
135 // the sovereign write path returned OK and read back present IMMEDIATELY -- and was GONE 75 seconds later,
136 // with no error, no log and no rejection. Reproduced twice. Cause is right here: this function serialized
137 // ONLY the caller's in-memory set, so the registry could contain nothing but what THIS process happened to
138 // load, and every row added between our load and our save was erased by the atomic rename below.
139 // WHY IT MATTERED: it silently closed what was then the only working extension point for scheduling.
140 // (⚠A CLAIM HERE WAS STALE AND IS RETRACTED 2026-08-03: this comment used to assert the clockjobs-
141 // plane was "retired/dead". MEASURED FALSE -- the live tickless clock merges that plane every window
142 // and rows registered via `nx_store_put knowledge/store/clockjobs- put <actor> <name> <interval>
143 // <organ>` demonstrably arm and fire. The plane IS the sanctioned add lane.)
144 // THE REMEDY IS THE ONE sts_append_row ALREADY APPLIED TO THE SEG-STORE: re-read the file we are about to
145 // overwrite and keep whatever we did not know about. Rows WE hold win outright (ours carry the advanced
146 // next_due); a row we have never seen is appended verbatim, keeping its own next_due so it arms exactly
147 // when its author intended instead of being silently re-armed or dropped.
148 // u00e2u02dcu2026A WRITER THAT SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER.
149 // u00e2u02dcu2026AN EXTENSION POINT THAT SILENTLY DISCARDS EXTENSIONS IS A CLOSED SYSTEM WEARING OPEN DOCUMENTATION.
150 var nn: i64 = n
151 let rb: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW)
152 let nm2: *u8 = sys_mmap(CLK_NAMEW)
153 let og2: *u8 = sys_mmap(CLK_NAMEW)
154 let rfd: i64 = sys_openat_rd(path)
155 if rfd >= 0 {
156 var rn: i64 = 0
157 var rr: i64 = 1
158 while rr > 0 {
159 rr = sys_read(rfd, ((rb as i64) + rn) as *u8, CLK_MAXJOBS*CLK_ROWW - rn)
160 if rr > 0 { rn = rn + rr }
161 }
162 sys_close(rfd)
163 var p: i64 = 0
164 var ls: i64 = 0
165 while p <= rn {
166 var eol: i64 = 0
167 if p == rn { eol = 1 } else { if rb[p] == (10 as u8) { eol = 1 } }
168 if eol == 1 {
169 if p > ls {
170 var f: i64 = 0
171 var q: i64 = ls
172 var iv2: i64 = 0
173 var nd2: i64 = 0
174 var w2: i64 = 0
175 var g2: i64 = 0
176 while q < p {
177 if rb[q] == (9 as u8) { f = f + 1 } else {
178 if f == 0 { if w2 < CLK_NAMEW - 1 { nm2[w2] = rb[q]; w2 = w2 + 1 } }
179 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) } } }
180 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) } } }
181 else { if g2 < CLK_NAMEW - 1 { og2[g2] = rb[q]; g2 = g2 + 1 } } } }
182 }
183 q = q + 1
184 }
185 nm2[w2] = 0 as u8
186 og2[g2] = 0 as u8
187 if w2 > 0 { if g2 > 0 { if clk_find(names, nn, nm2) < 0 { if nn < CLK_MAXJOBS {
188 var d2: *u8 = clk_slot(names, nn)
189 var a2: i64 = 0
190 while a2 < w2 { d2[a2] = nm2[a2]; a2 = a2 + 1 }
191 d2[w2] = 0 as u8
192 var e2: *u8 = clk_slot(organs, nn)
193 var b2: i64 = 0
194 while b2 < g2 { e2[b2] = og2[b2]; b2 = b2 + 1 }
195 e2[g2] = 0 as u8
196 if iv2 < 1 { iv2 = 1 }
197 intervals[nn] = iv2
198 next_due[nn] = nd2
199 nn = nn + 1
200 } } } }
201 }
202 ls = p + 1
203 }
204 p = p + 1
205 }
206 }
207 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0
208 var i: i64 = 0
209 while i < nn {
210 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 }
211 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
212 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 }
213 buf[o]=10 as u8; o=o+1
214 i = i + 1
215 }
216 sys_mkdir("knowledge" as *u8, 0x1ed); sys_mkdir("knowledge/sched" as *u8, 0x1ed)
217 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"
218 let fd: i64 = sys_openat_wr(tmp, 0x1a4); if fd<0 { return 0 } sys_write(fd, buf, o); sys_close(fd)
219 __syscall(82, tmp as i64, path as i64, 0, 0, 0, 0) // atomic rename .tmp -> registry
220 return nn
221}
222
223// ---- PLANE-NATIVE STATE PERSISTENCE (2026-08-03, debts 1784828927 + 1784868625: the operator law is
224// planes, never tsv). The mutable schedule state (name·interval·next_due·organ per row, the thing the
225// clock resumes from) lives in a seg-store plane, written once per window via sts_seed. The plane is
226// CLOCK-EXCLUSIVE by doctrine (external adds ride the separate clockjobs- ADD plane, which the clock
227// is read-only on) -- but the same reload-merge that saved the tsv from the 1785559048 clobber is kept
228// here: rows we have never seen are preserved with their own next_due, because A WRITER THAT
229// SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER. --------------------------------
230
231// load schedule state from the plane; same 4-col row grammar as the legacy file. returns #jobs.
232func clk_load_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 {
233 np[0] = 0
234 let data: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW)
235 let dn: i64 = sts_load(prefix, data, CLK_MAXJOBS*CLK_ROWW)
236 if dn <= 0 { return 0 }
237 var i: i64=0; var ls: i64=0; var n: i64=0
238 while i < dn {
239 if data[i] == (10 as u8) {
240 if i > ls { if n < CLK_MAXJOBS {
241 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls
242 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n)
243 var f: i64=0; var p: i64=0; var iv: i64=0; var nd: i64=0; var w: i64=0; var g: i64=0
244 while p < ll {
245 if line[p] == (9 as u8) { f = f + 1 }
246 else {
247 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } }
248 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } }
249 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) {
250 let dig: i64 = (line[p]-(48 as u8)) as i64
251 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig }
252 } } } }
253 }
254 p = p + 1
255 }
256 d[w] = 0 as u8; e[g] = 0 as u8
257 if w > 0 { if g > 0 {
258 intervals[n]=iv; next_due[n]=nd; n=n+1
259 } }
260 } }
261 ls = i + 1
262 }
263 i = i + 1
264 }
265 np[0] = n
266 return n
267}
268
269// persist schedule state to the plane (reload-merge first, then ONE whole-plane sts_seed commit).
270// Rows WE hold win (ours carry the advanced next_due); unknown rows are preserved verbatim.
271func clk_save_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 {
272 var nn: i64 = n
273 let rnames: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW)
274 let rorgs: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW)
275 let riv: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
276 let rnd: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
277 let rnp: *i64 = sys_mmap(8) as *i64
278 clk_load_plane(prefix, rnames, rorgs, riv, rnd, rnp)
279 var ri: i64 = 0
280 while ri < rnp[0] {
281 if clk_find(names, nn, clk_slot(rnames, ri)) < 0 { if nn < CLK_MAXJOBS {
282 var d2: *u8 = clk_slot(names, nn); let sm: *u8 = clk_slot(rnames, ri)
283 var a2: i64 = 0
284 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 } }
285 if a2 == CLK_NAMEW-1 { d2[a2]=0 as u8 }
286 var e2: *u8 = clk_slot(organs, nn); let so: *u8 = clk_slot(rorgs, ri)
287 var b2: i64 = 0
288 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 } }
289 if b2 == CLK_NAMEW-1 { e2[b2]=0 as u8 }
290 intervals[nn] = riv[ri]; next_due[nn] = rnd[ri]; nn = nn + 1
291 } }
292 ri = ri + 1
293 }
294 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0
295 var i: i64 = 0
296 while i < nn {
297 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 }
298 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
299 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 }
300 buf[o]=10 as u8; o=o+1
301 i = i + 1
302 }
303 sts_seed(prefix, buf, o)
304 return nn
305}
306
307// THE ONE STATE LOADER for the live clock: plane first; if the plane is empty AND a legacy tsv
308// exists, load it (the one-time migration path) -- the caller's next clk_save_plane completes the
309// cutover. Returns 0=loaded-from-plane, 1=migrated-from-legacy, -1=nothing anywhere.
310func clk_load_state(prefix: *u8, legacy: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 {
311 if clk_load_plane(prefix, names, organs, intervals, next_due, np) > 0 { return 0 }
312 if clk_load(legacy, names, organs, intervals, next_due, np) > 0 { return 1 }
313 return 0 - 1
314}
315
316// MERGE new job declarations from an nx_store PLANE (off-tsv, additive) into the in-memory registry.
317// THE CLOBBER FIX: the clock is READ-ONLY on this plane -- external adds go via
318// `nx_store_put <plane> put <actor> <name> <interval> <organ>` (upsert-by-name, additive) and can NEVER be
319// clobbered by the clock's own (now clock-private) tsv save. Plane row cols (TAB): 0=name 1=interval(sec) 2=organ.
320// A job already present (by name) is skipped (idempotent -- re-merge is a no-op). New jobs arm at base_tick+interval.
321// Returns the number of NEW jobs added this call.
322func clk_merge_store(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, base_tick: i64) -> i64 {
323 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW)
324 let dn: i64 = sts_load(prefix, buf, CLK_MAXJOBS*CLK_ROWW)
325 if dn <= 0 { return 0 }
326 var added: i64 = 0
327 var i: i64=0; var ls: i64=0
328 while i < dn {
329 if buf[i] == (10 as u8) {
330 if i > ls {
331 let line: *u8 = ((buf as i64)+ls) as *u8; let ll: i64 = i-ls
332 let nm: *u8 = sys_mmap(CLK_NAMEW); let og: *u8 = sys_mmap(CLK_NAMEW)
333 var f: i64=0; var p: i64=0; var iv: i64=0; var w: i64=0; var g: i64=0
334 while p < ll {
335 if line[p]==(9 as u8) { f=f+1 }
336 else {
337 if f==0 { if w<CLK_NAMEW-1 { nm[w]=line[p]; w=w+1 } }
338 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) } } }
339 else { if f==2 { if g<CLK_NAMEW-1 { og[g]=line[p]; g=g+1 } } } }
340 }
341 p=p+1
342 }
343 nm[w]=0 as u8; og[g]=0 as u8
344 // MALFORMED-ROW GUARD (2026-08-03, debt 1784604739): a row lacking a name or an organ
345 // (e.g. a put that dropped an arg) must be SEEN, never silently skipped -- but never
346 // registered either (a nameless job / organless exec is garbage in the dispatcher).
347 if w == 0 { sts_werr("clk_merge_store: MALFORMED plane row (empty name) SKIPPED\n" as *u8) }
348 if w > 0 { if g == 0 { sts_werr("clk_merge_store: MALFORMED plane row (no organ) SKIPPED\n" as *u8) } }
349 if w > 0 { if g > 0 {
350 let n: i64 = np[0]
351 if clk_find(names, n, nm) < 0 { if n < CLK_MAXJOBS {
352 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}
353 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}
354 if iv<1 { iv=1 }
355 intervals[n]=iv; next_due[n]=base_tick+iv; np[0]=n+1; added=added+1
356 } }
357 } }
358 }
359 ls=i+1
360 }
361 i=i+1
362 }
363 return added
364}
365
366// ============================================================================================
367// EDF / WALL-CLOCK SCHEDULING (2026-08-04, debt 1785872141 -- operator: "get the clock to sota")
368//
369// THE MEASURED DEFECT clk_run_tickless has BY CONSTRUCTION: its logical tick T advances ONLY by the
370// sleep amount (T = minnd), while REAL time also advances by however long the dispatched children
371// took (clk_dispatch_run forks and sys_wait4s EVERY job, serially, inside the beat). So logical time
372// drifts behind wall-clock time in proportion to dispatch load, and EVERY job's period silently
373// STRETCHES: measured 2026-08-04, evidencebeat (interval 21600 = "6 hours") fired ONCE in 21 HOURS
374// while the clock was demonstrably alive (tick current, window-end a=30 b=132). Even 60s netobs
375// slipped to 243s. The fast jobs pay the drift too, but a 6h job pays it 360x over.
376// u2605u2605u2605u2605u2605u2605 A SCHEDULER THAT ADVANCES ITS OWN CLOCK BY WHAT IT SLEPT -- NOT BY WHAT ELAPSED --
377// MEASURES ITS OWN IDLENESS AND CALLS IT TIME. Every deadline it derives is then a lie under load.
378//
379// THE SOTA SHAPE (what real schedulers do; cron/systemd-timers/EDF literature all agree):
380// 1. deadlines are ABSOLUTE WALL-CLOCK instants, never a self-advanced counter
381// 2. re-read the clock AFTER every dispatch, so job runtime cannot be lost
382// 3. dispatch EARLIEST-DEADLINE-FIRST so the most-overdue job goes first (EDF is optimal for
383// meeting deadlines on one resource -- and it is exactly what stops slow-job starvation)
384// 4. catch-up without runaway: re-arm past now by WHOLE intervals, and REPORT missed periods
385// instead of pretending they happened
386// The decision math is factored into PURE functions below precisely so a gate can prove a 6-hour
387// period behaves correctly in MILLISECONDS with fabricated clock values -- a scheduler you can only
388// test by waiting 6 hours is a scheduler nobody tests.
389
390// deadlines below this are LEGACY LOGICAL TICKS (the old counter ran ~3e6; epochs are ~1.78e9), so the
391// one-time migration is unambiguous and needs no flag day. NEVER compare an epoch against a tick.
392const CLK_EPOCH_FLOOR: i64 = 1000000000
393
394// PURE: index of the overdue job with the EARLIEST deadline (EDF), or -1 if nothing is due.
395// Ties break toward the lower index = stable, so a tie can never rotate two jobs into each other's slot.
396func clk_edf_pick(deadlines: *i64, n: i64, now: i64) -> i64 {
397 var best: i64 = 0 - 1
398 var bestd: i64 = 0
399 var i: i64 = 0
400 while i < n {
401 if deadlines[i] <= now {
402 if best < 0 { best = i; bestd = deadlines[i] }
403 else { if deadlines[i] < bestd { best = i; bestd = deadlines[i] } }
404 }
405 i = i + 1
406 }
407 return best
408}
409
410// PURE: soonest deadline across all jobs (what a tickless sleeper must sleep until). -1 if n==0.
411func clk_edf_next(deadlines: *i64, n: i64) -> i64 {
412 if n <= 0 { return 0 - 1 }
413 var m: i64 = deadlines[0]
414 var i: i64 = 1
415 while i < n { if deadlines[i] < m { m = deadlines[i] } i = i + 1 }
416 return m
417}
418
419// PURE: how many whole periods job i has MISSED at `now` (0 = on time / early). This is the honest
420// starvation measure -- a job 3 periods late is a 3, not a "fired".
421func clk_edf_missed(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 {
422 if deadlines[i] > now { return 0 }
423 var iv: i64 = intervals[i]
424 if iv < 1 { iv = 1 }
425 return (now - deadlines[i]) / iv
426}
427
428// PURE: re-arm job i past `now` by WHOLE intervals (catch-up safe: never fires twice for one missed
429// window, never drifts off-phase). Returns the periods skipped so the caller can REPORT them.
430func clk_edf_rearm(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 {
431 var iv: i64 = intervals[i]
432 if iv < 1 { iv = 1 }
433 var skipped: i64 = 0
434 while deadlines[i] <= now { deadlines[i] = deadlines[i] + iv; skipped = skipped + 1 }
435 if skipped > 0 { skipped = skipped - 1 } // the first advance is the fire itself, not a miss
436 return skipped
437}
438
439// PURE: one-time migration of legacy logical-tick deadlines to wall-clock instants. A tick value can
440// never be a valid epoch, so this is exact. Jobs arm at now+interval (their author's intent) rather
441// than firing a thundering herd at now. Returns how many rows were converted.
442func clk_edf_migrate(deadlines: *i64, intervals: *i64, n: i64, now: i64) -> i64 {
443 var c: i64 = 0
444 var i: i64 = 0
445 while i < n {
446 if deadlines[i] < CLK_EPOCH_FLOOR {
447 var iv: i64 = intervals[i]
448 if iv < 1 { iv = 1 }
449 deadlines[i] = now + iv
450 c = c + 1
451 }
452 i = i + 1
453 }
454 return c
455}
456
457// THE EDF WINDOW: wall-clock anchored, EDF-ordered, starvation-reporting. Runs until `maxdispatch`
458// jobs have been dispatched or `budget_secs` of REAL time is gone, sleeping only when nothing is due.
459// out[0]=dispatched out[1]=slept_secs out[2]=final_now out[3]=starved_jobs out[4]=max_lateness_secs
460// out[5]=exec_secs (real time inside children -- the number the old design silently threw away).
461func clk_run_edf(organs: *u8, intervals: *i64, deadlines: *i64, n: i64, maxdispatch: i64, budget_secs: i64, tick_ms: i64, out: *i64) -> i64 {
462 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64
463 let t0: i64 = sys_now_realtime_sec()
464 var disp: i64 = 0
465 var slept: i64 = 0
466 var starved: i64 = 0
467 var maxlate: i64 = 0
468 var execs: i64 = 0
469 var run: i64 = 1
470 while run == 1 {
471 // RE-READ THE CLOCK EVERY ITERATION -- this single line is the fix: child runtime is now
472 // observed, not assumed away.
473 let now: i64 = sys_now_realtime_sec()
474 if now - t0 >= budget_secs { run = 0 }
475 if disp >= maxdispatch { run = 0 }
476 if run == 1 {
477 let k: i64 = clk_edf_pick(deadlines, n, now)
478 if k >= 0 {
479 let missed: i64 = clk_edf_missed(deadlines, intervals, k, now)
480 let late: i64 = now - deadlines[k]
481 if late > maxlate { maxlate = late }
482 if missed > 0 { starved = starved + 1 }
483 clk_edf_rearm(deadlines, intervals, k, now)
484 let e0: i64 = now
485 let ran: i64 = clk_dispatch_one(organs, k)
486 let e1: i64 = sys_now_realtime_sec()
487 execs = execs + (e1 - e0)
488 if ran == 1 { disp = disp + 1; fired[k] = 1 }
489 } else {
490 let nd: i64 = clk_edf_next(deadlines, n)
491 if nd < 0 { run = 0 } else {
492 var wait: i64 = nd - now
493 if wait < 1 { wait = 1 }
494 let left: i64 = budget_secs - (now - t0)
495 if wait > left { wait = left }
496 if wait < 1 { run = 0 } else {
497 let ts: *i64 = sys_mmap(16) as *i64
498 ts[0] = wait
499 ts[1] = 0
500 __syscall(35, ts as i64, 0, 0, 0, 0, 0)
501 slept = slept + wait
502 }
503 }
504 }
505 }
506 }
507 out[0] = disp; out[1] = slept; out[2] = sys_now_realtime_sec()
508 out[3] = starved; out[4] = maxlate; out[5] = execs
509 return disp
510}
511
512// dispatch EXACTLY ONE job by index (the EDF loop picks the victim; this just runs it). Split out of
513// clk_dispatch_run so ordering policy and exec mechanics are separable -- and so the EDF loop can
514// re-read the clock between jobs. Returns 1 if the organ really ran, 0 if exec failed (127).
515func clk_dispatch_one(organs: *u8, i: i64) -> i64 {
516 let path: *u8 = clk_slot(organs, i)
517 __syscall(90, path as i64, 0x1ed, 0, 0, 0, 0) // chmod +x: recv-shipped organs aren't reliably executable
518 let pid: i64 = sys_fork()
519 if pid == 0 {
520 let argv: *i64 = sys_mmap(32) as *i64; argv[0] = path as i64; argv[1] = 0
521 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
522 sys_execve(path, argv, envp)
523 sys_exit(127)
524 }
525 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0
526 sys_wait4(pid, st, 0)
527 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff
528 if sig != 0 { return 1 }
529 if code != 127 { return 1 }
530 return 0
531}
532
533// load the registry into the arrays. returns #jobs (also written to np[0]).
534func clk_load(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 {
535 np[0] = 0
536 let lenp: *i64 = sys_mmap(8) as *i64
537 let data: *u8 = sys_read_file(path, lenp)
538 if (data as i64) == 0 { return 0 }
539 let dn: i64 = lenp[0]; var i: i64=0; var ls: i64=0; var n: i64=0
540 while i < dn {
541 if data[i] == (10 as u8) {
542 if i > ls { if n < CLK_MAXJOBS {
543 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls
544 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n)
545 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
546 while p < ll {
547 if line[p] == (9 as u8) { f = f + 1 }
548 else {
549 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } }
550 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } }
551 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) {
552 let dig: i64 = (line[p]-(48 as u8)) as i64
553 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig }
554 } } } }
555 }
556 p = p + 1
557 }
558 d[w] = 0 as u8; e[g] = 0 as u8
559 intervals[n]=iv; next_due[n]=nd; n=n+1
560 } }
561 ls = i + 1
562 }
563 i = i + 1
564 }
565 np[0] = n
566 return n
567}