code wiki / _hdl_build / nx_gate_roster_run.nx
nx_gate_roster_run.nx source
↩ module page · 721 lines · 37407 B
1// nx_gate_roster_run.nx -- THE DATA-DRIVEN GATE ROSTER BEAT: run every gate named in a conf, on the
2// clock, with a deadline, and journal each verdict -- so a promoted gate nobody runs becomes a gate
3// whose verdict is COLLECTED, by adding ONE LINE OF DATA rather than a rebuild.
4//
5// WHY (campaign lane B, 2026-08-18): nx_gateadjudicate measured 3,235 gate sources on the NAS,
6// 919 promoted, and 780 DEPLOYED-DARK -- promoted binaries that no clock row, roster organ, .gates map,
7// gatereg row or MCP call ever runs. The estate's law: "a gate nobody runs does not degrade to
8// neutral -- it degrades to a false sense of coverage, because its existence is counted and its
9// verdict is not." The wiring vehicle that already exists, nx_gfx_gates_beat, is CORRECT in shape
10// (bounded runner, per-gate journal line, ABSENT reported never skipped) but its roster is EIGHT
11// NAMES IN CODE, so admitting a ninth gate is a rebuild. Rule 11/17: rosters are DATA. This organ is
12// that beat with the roster moved to a conf; nx_gfx_gates_beat is the precedent, not a duplicate --
13// its eight rows can migrate here whenever its owner chooses.
14//
15// ROSTER CONF (default knowledge/gateroster.conf), one gate per line:
16// <gate-name> runs ./<gate-name>.elf from the estate root
17// ; comment / blank ignored (also lines whose first byte is the hash mark)
18// A gate is ADMITTED to the conf only after a TRIAL run has come back GREEN or SKIP -- adoption is not
19// blind wiring, and this organ has a `trial` verb for exactly that:
20// nx_gate_roster_run REFUSED (exit 2): argless mutates NOTHING and prints
21// usage. The beat must NAME its verb -- every proof tool
22// in the estate probes an organ argless, so an argless
23// default that runs the beat makes this organ unprovable
24// and lets a staged binary write production state.
25// nx_gate_roster_run beat [conf] [journal] [ms] the beat. A conf OR journal other than the
26// production defaults writes its heartbeat BESIDE its own
27// journal (<journal>.stamp, announced) and never to the
28// cron-watched production stamp.
29// nx_gate_roster_run trial <listfile> [journal] [ms] run every gate in <listfile> ONCE, journal
30// under tag "trial"; exit 0 always (a trial
31// reports, it does not judge the fleet)
32// nx_gate_roster_run admit <journal> <deadline_ms> [conf] [suspectlist=<conf>.suspect]
33// The journal->conf bridge, so admission is MECHANICAL, never hand-filtered: take the LATEST
34// row per gate; GREEN/SKIP with ms < deadline_ms -> merge-ADD into the conf (additive, rule 13:
35// admission never removes -- a beat RED is an alarm to read, not a row to erase); GREEN with
36// ms >= deadline_ms -> SUSPECT, written to [suspectlist] and NOT admitted. The suspect class
37// exists because journals written before 2026-08-18 came from a runner that returned exit 0
38// for a watchdog-KILLED child, so a legacy at-deadline GREEN is a timeout wearing a pass; the
39// fixed runner reports 137 and those rows say TIMEOUT, making this class empty going forward.
40// deadline_ms is REQUIRED (it is the value the trial ran with, from its own header line) --
41// defaulting it here would silently misclassify a journal run at a different deadline.
42// JOURNAL (default knowledge/status/gateroster.jrnl, O_APPEND, one writer):
43// <epoch> TAB <tag> TAB <gate> TAB <verdict> TAB exit=<rc> TAB ms=<elapsed> TAB <reason>
44// <reason> (2026-08-19) is the LAST non-empty line of the gate's own output, tabs/CRs spaced, capped at
45// GRR_REASON_MAX -- THE REASON TRAVELS WITH THE COUNT: a failing list without reasons costs a re-run of
46// every gate to learn why (measured: 150 DARK-FAILING rows, zero causes). Additive per rule 19 -- every
47// reader indexes fields 0..5 and is untouched.
48// VERDICTS: GREEN(rc 0) . SKIP(rc 3, tolerated: gv_need preconditions) . RED(other rc) .
49// ABSENT(no ./<gate>.elf) . TIMEOUT(rc 137 = the watchdog killed it at <ms>)
50// Composes gk_run_capture_ms -- the estate's bounded runner; the pipe-deadline and watchdog lessons
51// live THERE. Runs at nice 10 (a roster is background work; it must never become the load it
52// measures). Every gate's output is captured and DISCARDED here except its exit code: the gate's own
53// journal (gv_journal -> knowledge/status/harness.jrnl) is the durable detail; this file is the
54// roster's one-line summary, the same split nx_gfx_gates_beat uses.
55// exit (beat): 0 = no RED/ABSENT/TIMEOUT, 1 otherwise. exit 2 = usage / conf unreadable.
56// license_tier: ORIGINAL Read-only over the estate except its journal. No hw writes (Rule 26).
57import "nx_syscalls.nx"
58import "nx_gatekit_lib.nx"
59import "nx_estate_path.nx"
60
61const GRR_MS_DEFAULT: i64 = 60000
62const GRR_CAP: i64 = 65536
63const GRR_MODE: i64 = 420 // 0644
64const GRR_NICE: i64 = 10
65const GRR_SIGKILL_RC: i64 = 137 // 128+SIGKILL: what gk_run_capture_ms returns for a child its watchdog killed
66const GRR_MAXG: i64 = 4096
67const GRR_SLOT: i64 = 128
68const GRR_LINE: i64 = 1400 // one journal line (epoch/tag/gate/verdict/exit/ms/reason)
69const GRR_REASON_MAX: i64 = 240 // reason field cap; a truncated reason still names the cause
70const GRR_MSG: i64 = 8192 // report buffers (admit summary, beat header/footer)
71const GRR_CONF: *u8 = "knowledge/gateroster.conf"
72const GRR_JRNL: *u8 = "knowledge/status/gateroster.jrnl"
73// LIVENESS HEARTBEAT for nx_cron_watch. Until 2026-08-20 this beat wrote NO stamp, so the surface that
74// runs EVERY NAS-side detector was the one surface no liveness instrument could see: if it stopped,
75// nothing in the estate would report it and 630 gates would keep being counted as covered.
76const GRR_STAMP: *u8 = "knowledge/status/gateroster.stamp"
77// ---- THE PASS FITS ITS SCHEDULER, AND THE STALEST GATE GOES FIRST (2026-09-18) ----
78// MEASURED: the clock dispatcher KILLS any job at CLK_DISPATCH_DEADLINE_MS (900000, nx_clock_sched.nx, logged
79// exit 124), and one serial pass over ~790 gates with a 180000 ms per-gate deadline does not fit: the last
80// COMPLETE pass was stamped ts=1789417451 (778 rows); the passes after it wrote 1,502 and 694 rows and were cut
81// at the kill, so the conf's TAIL (nx_intlog_gate: 2 production rows in the whole journal) never ran again
82// and no heartbeat was written for four days. A BEAT KILLED BEFORE ITS TAIL STARVES THE SAME GATES EVERY DAY,
83// AND BECAUSE IT DIES BEFORE THE STAMP, ITS OWN LIVENESS SIGNAL GOES DARK EXACTLY WHEN IT IS FAILING.
84// Two changes, both on the beat verb only (trial is unchanged):
85// ORDER least-recently-run first, read from this beat's OWN journal (a gate with no row runs first; ties
86// keep conf order), so a beat cut short still advances the stalest gates and no cursor file exists
87// to corrupt -- the order is re-derived from evidence every beat.
88// BUDGET the conf may state the scheduler's kill bound as DATA: a line `@dispatch_deadline_ms <ms>`. The
89// beat then stops STARTING gates once the elapsed time plus the per-gate deadline would pass that
90// bound, so every gate it starts can finish before the kill; the rest are DEFERRED and named. At
91// least one gate always runs. No directive = no budget (the old behaviour, byte for byte).
92// The heartbeat is written by every beat that ends by itself and carries ran=, deferred=, never_run= and
93// oldest_age_s= (the roster's TRUE freshness), after the fields its readers already parse.
94const GRR_DIRECTIVE_DISPATCH: *u8 = "@dispatch_deadline_ms"
95const GRR_TAG_BEAT: *u8 = "roster"
96
97func grr_isid(c: i64) -> i64 {
98 if c >= 97 { if c <= 122 { return 1 } }
99 if c >= 65 { if c <= 90 { return 1 } }
100 if c >= 48 { if c <= 57 { return 1 } }
101 if c == 95 { return 1 }
102 if c == 45 { return 1 }
103 if c == 46 { return 1 }
104 if c == 47 { return 1 }
105 return 0
106}
107
108func grr_slot(t: *u8, i: i64) -> *u8 { return ((t as i64) + i * GRR_SLOT) as *u8 }
109
110// read a roster file into the name table; returns names, or -1 unreadable, -2 table full
111func grr_load(path: *u8, tbl: *u8) -> i64 {
112 let lenp: *i64 = sys_mmap(16) as *i64
113 let b: *u8 = sys_read_file(path, lenp)
114 if lenp[0] <= 0 { return 0 - 1 }
115 let n: i64 = lenp[0]
116 var cnt: i64 = 0
117 var i: i64 = 0
118 while i < n {
119 let e: i64 = gk_eol(b, i, n)
120 var c0: i64 = i
121 var fc: i64 = 0
122 while fc == 0 {
123 if c0 >= e { fc = 1 }
124 if fc == 0 { if b[c0] == (32 as u8) { c0 = c0 + 1 } else { fc = 1 } }
125 }
126 var skip: i64 = 0
127 if c0 >= e { skip = 1 }
128 if skip == 0 { if b[c0] == (59 as u8) { skip = 1 } }
129 if skip == 0 { if b[c0] == (35 as u8) { skip = 1 } }
130 if skip == 0 {
131 var t: i64 = c0
132 var ft: i64 = 0
133 while ft == 0 {
134 if t >= e { ft = 1 }
135 if ft == 0 { if grr_isid(b[t] as i64) == 1 { t = t + 1 } else { ft = 1 } }
136 }
137 if t > c0 {
138 if cnt >= GRR_MAXG { sys_free_file(b, n); return 0 - 2 }
139 let dst: *u8 = grr_slot(tbl, cnt)
140 var q: i64 = 0
141 while q < t - c0 { if q < GRR_SLOT - 1 { dst[q] = b[c0 + q] } q = q + 1 }
142 if q > GRR_SLOT - 1 { q = GRR_SLOT - 1 }
143 dst[q] = 0 as u8
144 cnt = cnt + 1
145 }
146 }
147 i = e + 1
148 }
149 sys_free_file(b, n)
150 return cnt
151}
152
153// the conf's `@dispatch_deadline_ms <ms>` directive: the number, or -1 when the conf states none (or is unreadable)
154func grr_directive_ms(path: *u8) -> i64 {
155 let lenp: *i64 = sys_mmap(16) as *i64
156 let b: *u8 = sys_read_file(path, lenp)
157 if lenp[0] <= 0 { return 0 - 1 }
158 let n: i64 = lenp[0]
159 let key: *u8 = GRR_DIRECTIVE_DISPATCH
160 let kl: i64 = gk_len(key)
161 let endp: *i64 = sys_mmap(16) as *i64
162 var found: i64 = 0 - 1
163 var i: i64 = 0
164 while i < n {
165 let e: i64 = gk_eol(b, i, n)
166 if e - i > kl { if grr_runeq(b, i, i + kl, key) == 1 {
167 var p: i64 = i + kl
168 while p < e { if b[p] == (32 as u8) { p = p + 1 } else { break } }
169 if p < e { let v: i64 = grr_num(b, p, endp); if endp[0] > p { found = v } }
170 } }
171 i = e + 1
172 }
173 sys_free_file(b, n)
174 return found
175}
176
177// the newest epoch of a `tag` row naming each roster gate, from the beat's own journal: last[i] = -1 when the
178// journal carries none for gate i. Returns the journal rows read, or -1 when it is unreadable (every last[] = -1).
179func grr_lastrun(jrnl: *u8, tag: *u8, tbl: *u8, n: i64, last: *i64) -> i64 {
180 var k: i64 = 0
181 while k < n { last[k] = 0 - 1; k = k + 1 }
182 let lenp: *i64 = sys_mmap(16) as *i64
183 let b: *u8 = sys_read_file(jrnl, lenp)
184 if lenp[0] <= 0 { return 0 - 1 }
185 let bn: i64 = lenp[0]
186 let endp: *i64 = sys_mmap(16) as *i64
187 var rows: i64 = 0
188 var i: i64 = 0
189 while i < bn {
190 let e: i64 = gk_eol(b, i, bn)
191 // fields: epoch TAB tag TAB gate TAB ...
192 var t1: i64 = i
193 while t1 < e { if b[t1] == (9 as u8) { break } t1 = t1 + 1 }
194 var t2: i64 = t1 + 1
195 while t2 < e { if b[t2] == (9 as u8) { break } t2 = t2 + 1 }
196 var t3: i64 = t2 + 1
197 while t3 < e { if b[t3] == (9 as u8) { break } t3 = t3 + 1 }
198 if t3 < e { if grr_runeq(b, t1 + 1, t2, tag) == 1 {
199 let ep: i64 = grr_num(b, i, endp)
200 if endp[0] == t1 {
201 rows = rows + 1
202 var j: i64 = 0
203 while j < n {
204 if grr_runeq(b, t2 + 1, t3, grr_slot(tbl, j)) == 1 { if ep > last[j] { last[j] = ep } j = n } else { j = j + 1 }
205 }
206 }
207 } }
208 i = e + 1
209 }
210 sys_free_file(b, bn)
211 return rows
212}
213
214// the run order: indices of tbl by last[] ascending (never-run first), ties in conf order -- a stable insertion sort
215func grr_order(last: *i64, n: i64, ord: *i64) -> i64 {
216 var i: i64 = 0
217 while i < n {
218 var j: i64 = i
219 while j > 0 { if last[ord[j - 1]] > last[i] { ord[j] = ord[j - 1]; j = j - 1 } else { break } }
220 ord[j] = i
221 i = i + 1
222 }
223 return n
224}
225
226// run one gate; append its journal line; returns 0 GREEN/SKIP, 1 RED/ABSENT/TIMEOUT
227func grr_one(name: *u8, tag: *u8, jfd: i64, ms: i64) -> i64 {
228 let p: *u8 = sys_mmap(512)
229 var o: i64 = gk_cat(p, 0, "./" as *u8)
230 o = gk_cat(p, o, name)
231 // a roster row may already carry .elf; do not double it
232 var has_elf: i64 = 0
233 let l: i64 = gk_len(name)
234 if l > 4 { if name[l - 4] == (46 as u8) { if name[l - 3] == (101 as u8) { if name[l - 2] == (108 as u8) { if name[l - 1] == (102 as u8) { has_elf = 1 } } } } }
235 if has_elf == 0 { o = gk_cat(p, o, ".elf" as *u8) }
236 p[o] = 0 as u8
237 var absent: i64 = 1
238 let fd: i64 = sys_openat_rd(p)
239 if fd >= 0 { absent = 0; sys_close(fd) }
240 var ec: i64 = 127
241 let ob: *u8 = sys_mmap(GRR_CAP)
242 let ol: *i64 = sys_mmap(16) as *i64
243 ol[0] = 0
244 let t0: i64 = sys_now_ms()
245 if absent == 0 {
246 ec = gk_run_capture_ms(p, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8, ms, ob, GRR_CAP, ol)
247 }
248 let el: i64 = sys_now_ms() - t0
249 // the REASON: last non-empty output line, sanitized (tabs/CRs -> space) and capped
250 let reason: *u8 = sys_mmap(GRR_REASON_MAX + 8)
251 reason[0] = 0 as u8
252 if ol[0] > 0 {
253 var re: i64 = ol[0]
254 var fend: i64 = 0
255 while fend == 0 {
256 if re <= 0 { fend = 1 }
257 if fend == 0 { if ob[re - 1] == (10 as u8) { re = re - 1 } else { if ob[re - 1] == (13 as u8) { re = re - 1 } else { fend = 1 } } }
258 }
259 var rsb: i64 = re
260 var fstart: i64 = 0
261 while fstart == 0 {
262 if rsb <= 0 { fstart = 1 }
263 if fstart == 0 { if ob[rsb - 1] == (10 as u8) { fstart = 1 } else { rsb = rsb - 1 } }
264 }
265 var rk: i64 = 0
266 while rsb < re { if rk < GRR_REASON_MAX {
267 var c: u8 = ob[rsb]
268 if c == (9 as u8) { c = 32 as u8 }
269 if c == (13 as u8) { c = 32 as u8 }
270 reason[rk] = c
271 rk = rk + 1 } rsb = rsb + 1 }
272 reason[rk] = 0 as u8
273 }
274 var vname: *u8 = "RED" as *u8
275 if ec == 0 { vname = "GREEN" as *u8 }
276 if ec == 3 { vname = "SKIP" as *u8 }
277 if absent == 1 { vname = "ABSENT" as *u8 }
278 // TIMEOUT is the WATCHDOG'S verdict, not an elapsed-time guess: since gk_wait_code (2026-08-18) a
279 // killed child returns 128+SIGKILL, so key on that. (The old `elapsed >= ms && rc != 0` rule labelled a
280 // slow RED that exited 1 at the deadline as TIMEOUT -- measured on nx_planecap_gate.)
281 if absent == 0 { if ec == GRR_SIGKILL_RC { vname = "TIMEOUT" as *u8 } }
282 let ln: *u8 = sys_mmap(GRR_LINE)
283 var lo: i64 = gk_catn(ln, 0, sys_now_realtime_sec())
284 ln[lo] = 9 as u8; lo = lo + 1
285 lo = gk_cat(ln, lo, tag)
286 ln[lo] = 9 as u8; lo = lo + 1
287 lo = gk_cat(ln, lo, name)
288 ln[lo] = 9 as u8; lo = lo + 1
289 lo = gk_cat(ln, lo, vname)
290 ln[lo] = 9 as u8; lo = lo + 1
291 lo = gk_cat(ln, lo, "exit=" as *u8)
292 lo = gk_catn(ln, lo, ec)
293 ln[lo] = 9 as u8; lo = lo + 1
294 lo = gk_cat(ln, lo, "ms=" as *u8)
295 lo = gk_catn(ln, lo, el)
296 ln[lo] = 9 as u8; lo = lo + 1
297 lo = gk_cat(ln, lo, reason)
298 ln[lo] = 10 as u8; lo = lo + 1
299 if jfd >= 0 { gk_write_all(jfd, ln, lo) }
300 gk_say(ln, lo)
301 if ec == 0 { return 0 }
302 if ec == 3 { return 0 }
303 return 1
304}
305
306// parse a non-negative decimal out of s starting at off; returns value, writes end to endp
307func grr_num(s: *u8, off: i64, endp: *i64) -> i64 {
308 var v: i64 = 0
309 var i: i64 = off
310 var f: i64 = 0
311 while f == 0 {
312 if s[i] >= (48 as u8) { if s[i] <= (57 as u8) { v = v * 10 + ((s[i] as i64) - 48); i = i + 1 } else { f = 1 } } else { f = 1 }
313 }
314 endp[0] = i
315 return v
316}
317
318// does the byte run b[s..e) equal the NUL-terminated literal? Length is DERIVED from the literal --
319// a hand-counted length beside a string spelled as character codes is two rule-11 defects at once,
320// and the build ratchet refused exactly that shape here before this helper existed (2026-08-18).
321func grr_runeq(b: *u8, rs: i64, re: i64, lit: *u8) -> i64 {
322 let l: i64 = gk_len(lit)
323 if re - rs != l { return 0 }
324 var k: i64 = 0
325 while k < l { if b[rs + k] != lit[k] { return 0 } k = k + 1 }
326 return 1
327}
328
329// admit: journal -> conf (additive) + suspect list. See header. exit 0 done, 2 unreadable/usage.
330func grr_admit(jrnl: *u8, dls: *u8, argc: i64, argv: *i64) -> i64 {
331 var conf: *u8 = GRR_CONF
332 if argc >= 5 { conf = argv[4] as *u8 }
333 // the suspect list defaults to a SIBLING of the conf (<conf>.suspect), never a fixed production path:
334 // a caller that points admit at a fixture conf must not have its suspects land in production
335 // (the fixture-sharing law), and a bounded 4-arg runner cannot always pass the fifth argument.
336 let sdef: *u8 = sys_mmap(GRR_SLOT * 4)
337 var so: i64 = gk_cat(sdef, 0, conf)
338 so = gk_cat(sdef, so, ".suspect" as *u8)
339 sdef[so] = 0 as u8
340 var susp: *u8 = sdef
341 if argc >= 6 { susp = argv[5] as *u8 }
342 let endp: *i64 = sys_mmap(16) as *i64
343 let deadline: i64 = grr_num(dls, 0, endp)
344 let msg: *u8 = sys_mmap(GRR_MSG)
345 var o: i64 = 0
346 if deadline <= 0 {
347 o = gk_cat(msg, 0, "nx_gate_roster_run admit: REFUSED -- deadline_ms must be a positive number (the value the trial ran with)\n" as *u8)
348 gk_say(msg, o)
349 return 2
350 }
351 let lenp: *i64 = sys_mmap(16) as *i64
352 let b: *u8 = sys_read_file(jrnl, lenp)
353 if lenp[0] <= 0 {
354 o = gk_cat(msg, 0, "nx_gate_roster_run admit: journal unreadable: " as *u8)
355 o = gk_cat(msg, o, jrnl)
356 o = gk_cat(msg, o, "\n" as *u8)
357 gk_say(msg, o)
358 return 2
359 }
360 let n: i64 = lenp[0]
361 // latest-row-wins tables (append-only journal: later rows overwrite earlier entries by gate)
362 let gname: *u8 = sys_mmap(GRR_MAXG * GRR_SLOT)
363 let gverd: *i64 = sys_mmap(GRR_MAXG * 8) as *i64 // 1 GREEN 2 SKIP 3 RED 4 ABSENT 5 TIMEOUT 0 unknown
364 let gms: *i64 = sys_mmap(GRR_MAXG * 8) as *i64
365 var ng: i64 = 0
366 var rows: i64 = 0
367 var overflow: i64 = 0
368 var i: i64 = 0
369 while i < n {
370 let e: i64 = gk_eol(b, i, n)
371 // fields: epoch TAB tag TAB gate TAB verdict TAB exit=N TAB ms=N
372 var f: i64 = 0
373 var fs: i64 = i
374 var gs: i64 = 0
375 var ge: i64 = 0
376 var vs: i64 = 0
377 var ve: i64 = 0
378 var ms: i64 = 0 - 1
379 var t: i64 = i
380 while t <= e {
381 var atend: i64 = 0
382 if t == e { atend = 1 }
383 if atend == 0 { if b[t] == (9 as u8) { atend = 1 } }
384 if atend == 1 {
385 if f == 2 { gs = fs; ge = t }
386 if f == 3 { vs = fs; ve = t }
387 if f == 5 {
388 let msp: *u8 = "ms=" as *u8
389 let mspl: i64 = gk_len(msp)
390 if t - fs > mspl { ms = grr_num(b, fs + mspl, endp) }
391 }
392 f = f + 1
393 fs = t + 1
394 }
395 t = t + 1
396 }
397 if ge > gs {
398 if ve > vs {
399 rows = rows + 1
400 var verd: i64 = 0
401 if grr_runeq(b, vs, ve, "GREEN" as *u8) == 1 { verd = 1 }
402 if grr_runeq(b, vs, ve, "SKIP" as *u8) == 1 { verd = 2 }
403 if grr_runeq(b, vs, ve, "RED" as *u8) == 1 { verd = 3 }
404 if grr_runeq(b, vs, ve, "ABSENT" as *u8) == 1 { verd = 4 }
405 if grr_runeq(b, vs, ve, "TIMEOUT" as *u8) == 1 { verd = 5 }
406 // find or append the gate
407 var idx: i64 = 0 - 1
408 var j: i64 = 0
409 while j < ng {
410 var eq: i64 = 1
411 var k: i64 = 0
412 let sl: *u8 = grr_slot(gname, j)
413 while k < ge - gs { if sl[k] != b[gs + k] { eq = 0; k = ge } else { k = k + 1 } }
414 if k <= ge - gs { if sl[ge - gs] != (0 as u8) { eq = 0 } }
415 if eq == 1 { idx = j; j = ng } else { j = j + 1 }
416 }
417 if idx < 0 {
418 if ng >= GRR_MAXG { overflow = 1 }
419 if overflow == 0 {
420 let dst: *u8 = grr_slot(gname, ng)
421 var q: i64 = 0
422 while q < ge - gs { if q < GRR_SLOT - 1 { dst[q] = b[gs + q] } q = q + 1 }
423 if q > GRR_SLOT - 1 { q = GRR_SLOT - 1 }
424 dst[q] = 0 as u8
425 idx = ng
426 ng = ng + 1
427 }
428 }
429 if idx >= 0 { gverd[idx] = verd; gms[idx] = ms }
430 }
431 }
432 i = e + 1
433 }
434 sys_free_file(b, n)
435 if overflow == 1 {
436 o = gk_cat(msg, 0, "nx_gate_roster_run admit: REFUSED -- journal names more gates than GRR_MAXG; a truncated admission silently drops gates\n" as *u8)
437 gk_say(msg, o)
438 return 2
439 }
440 // load the existing conf so admission is a MERGE (additive), never a clobber
441 let have: *u8 = sys_mmap(GRR_MAXG * GRR_SLOT)
442 var nh: i64 = grr_load(conf, have)
443 if nh < 0 { nh = 0 }
444 // classify + build outputs
445 let cb: *u8 = sys_mmap(GRR_MAXG * GRR_SLOT)
446 var co: i64 = 0
447 let sb: *u8 = sys_mmap(GRR_MAXG * GRR_SLOT)
448 var so: i64 = gk_cat(sb, 0, "; NX-DERIVED by nx_gate_roster_run admit: legacy at-deadline GREEN rows (runner-kill class) -- re-trial these on the fixed runner\n" as *u8)
449 var admitted: i64 = 0
450 var already: i64 = 0
451 var suspects: i64 = 0
452 var rejected: i64 = 0
453 var gi: i64 = 0
454 while gi < ng {
455 let nm: *u8 = grr_slot(gname, gi)
456 var cls: i64 = 0 // 1 admit, 2 suspect, 3 reject
457 if gverd[gi] == 2 { cls = 1 }
458 if gverd[gi] == 1 {
459 cls = 1
460 if gms[gi] >= deadline { cls = 2 }
461 }
462 if cls == 0 { cls = 3 }
463 if cls == 1 {
464 var dup: i64 = 0
465 var hj: i64 = 0
466 while hj < nh { if gk_streq(grr_slot(have, hj), nm) == 1 { dup = 1; hj = nh } else { hj = hj + 1 } }
467 if dup == 1 { already = already + 1 }
468 if dup == 0 {
469 co = gk_cat(cb, co, nm)
470 cb[co] = 10 as u8
471 co = co + 1
472 admitted = admitted + 1
473 }
474 }
475 if cls == 2 {
476 so = gk_cat(sb, so, nm)
477 sb[so] = 10 as u8
478 so = so + 1
479 suspects = suspects + 1
480 }
481 if cls == 3 { rejected = rejected + 1 }
482 gi = gi + 1
483 }
484 // append the new admissions to the conf (create it with a header if absent)
485 if admitted > 0 {
486 if nh == 0 {
487 let hdr: *u8 = "; gateroster.conf -- gates the beat runs; rows are ADMITTED by `nx_gate_roster_run admit` from trial journals (GREEN/SKIP under deadline). Additive: never hand-remove without naming why.\n" as *u8
488 let hfd: i64 = sys_openat_append(conf, GRR_MODE)
489 if hfd >= 0 { gk_write_all(hfd, hdr, gk_len(hdr)); sys_close(hfd) }
490 }
491 let afd: i64 = sys_openat_append(conf, GRR_MODE)
492 if afd < 0 {
493 o = gk_cat(msg, 0, "nx_gate_roster_run admit: cannot open conf for append: " as *u8)
494 o = gk_cat(msg, o, conf)
495 o = gk_cat(msg, o, "\n" as *u8)
496 gk_say(msg, o)
497 return 2
498 }
499 cb[co] = 0 as u8
500 gk_write_all(afd, cb, co)
501 sys_close(afd)
502 }
503 sb[so] = 0 as u8
504 let sw: i64 = gk_write(susp, sb)
505 o = gk_cat(msg, 0, "nx_gate_roster_run admit: journal_rows=" as *u8); o = gk_catn(msg, o, rows)
506 o = gk_cat(msg, o, " gates=" as *u8); o = gk_catn(msg, o, ng)
507 o = gk_cat(msg, o, " admitted_new=" as *u8); o = gk_catn(msg, o, admitted)
508 o = gk_cat(msg, o, " already_in_conf=" as *u8); o = gk_catn(msg, o, already)
509 o = gk_cat(msg, o, " suspects=" as *u8); o = gk_catn(msg, o, suspects)
510 o = gk_cat(msg, o, " rejected=" as *u8); o = gk_catn(msg, o, rejected)
511 o = gk_cat(msg, o, " (admitted+already+suspects+rejected=" as *u8)
512 o = gk_catn(msg, o, admitted + already + suspects + rejected)
513 o = gk_cat(msg, o, " == gates: " as *u8)
514 if admitted + already + suspects + rejected == ng { o = gk_cat(msg, o, "partition RECONCILES)" as *u8) }
515 if admitted + already + suspects + rejected != ng { o = gk_cat(msg, o, "PARTITION LEAK)" as *u8) }
516 o = gk_cat(msg, o, "\n conf=" as *u8); o = gk_cat(msg, o, conf)
517 o = gk_cat(msg, o, " suspects_wrote=" as *u8); o = gk_catn(msg, o, sw)
518 o = gk_cat(msg, o, " of=" as *u8); o = gk_cat(msg, o, susp)
519 o = gk_cat(msg, o, "\n" as *u8)
520 gk_say(msg, o)
521 return 0
522}
523
524func main(argc: i64, argv: *i64) -> i64 {
525 // the pass budget is measured from the moment the process starts: the scheduler's kill counts from there too
526 let t_start: i64 = sys_now_ms()
527 // ARGLESS IS NOT THE BEAT (2026-08-20). This organ's argless default USED to be the FULL PRODUCTION
528 // BEAT: every row of knowledge/gateroster.conf, appending knowledge/status/gateroster.jrnl and
529 // writing the cron-watched heartbeat. Every proof tool in the estate probes an organ ARGLESS --
530 // nx_behaveprobe runs LIVE and STAGED that way, nx_adoptgate drives nx_behaveprobe, nx_organ_ship
531 // composes nx_adoptgate -- so the default meant proving this organ ran the daily beat TWICE and let
532 // a STAGED, unpromoted binary write production state.
533 // AN ORGAN WHOSE ARGLESS DEFAULT IS A PRODUCTION MUTATION CANNOT BE SAFELY PROBED, AND EVERY PROOF
534 // TOOL IN THE ESTATE PROBES ARGLESS.
535 // Fixed at the SHAPE rather than by asking callers to remember: argless prints usage and mutates
536 // NOTHING. CHECKED BEFORE CHANGING, NOT ASSUMED: all four live clock rows already name their verb
537 // explicitly in the clocksched- plane (`beat <conf> <journal> <ms>`, two `trial ...` rows and one
538 // `admit ...`), so the beat and its retrial/admit siblings are untouched by this.
539 if argc < 2 {
540 let u0: *u8 = "nx_gate_roster_run: REFUSED -- argless does NOTHING, deliberately. The production beat mutates the roster journal AND the cron-watched heartbeat, and every proof tool in the estate probes an organ with no arguments; an argless default that runs the beat makes this organ unprovable and lets a staged binary write production state. Name the verb.\nusage: nx_gate_roster_run beat [conf] [journal] [ms] | trial <listfile> [journal] [ms] | admit <journal> <deadline_ms> [conf] [suspectlist]\n" as *u8
541 gk_say(u0, gk_len(u0))
542 return 2
543 }
544 var verb: *u8 = argv[1] as *u8
545 var conf: *u8 = GRR_CONF
546 var jrnl: *u8 = GRR_JRNL
547 var ms: i64 = GRR_MS_DEFAULT
548 var tag: *u8 = "roster" as *u8
549 var trial: i64 = 0
550 if gk_streq(verb, "admit" as *u8) == 1 {
551 if argc < 4 {
552 let ua: *u8 = "usage: nx_gate_roster_run admit <journal> <deadline_ms> [conf] [suspectlist]\n" as *u8
553 gk_say(ua, gk_len(ua))
554 return 2
555 }
556 return grr_admit(argv[2] as *u8, argv[3] as *u8, argc, argv)
557 }
558 if gk_streq(verb, "trial" as *u8) == 1 { trial = 1; tag = "trial" as *u8 }
559 if trial == 0 { if gk_streq(verb, "beat" as *u8) == 0 {
560 let u0: *u8 = "usage: nx_gate_roster_run [beat [conf] [journal] [ms]] | trial <listfile> [journal] [ms]\n" as *u8
561 gk_say(u0, gk_len(u0)); return 2 } }
562 if trial == 1 { if argc < 3 { let u: *u8 = "usage: nx_gate_roster_run trial <listfile> [journal] [ms]\n" as *u8; gk_say(u, gk_len(u)); return 2 } conf = argv[2] as *u8 }
563 if trial == 0 { if argc >= 3 { conf = argv[2] as *u8 } }
564 if argc >= 4 { jrnl = argv[3] as *u8 }
565 if argc >= 5 {
566 let a4: *u8 = argv[4] as *u8
567 var v: i64 = 0
568 var i: i64 = 0
569 while a4[i] != (0 as u8) { if a4[i] >= (48 as u8) { if a4[i] <= (57 as u8) { v = v * 10 + ((a4[i] as i64) - 48) } } i = i + 1 }
570 if v > 0 { ms = v }
571 }
572 if ep_anchor() < 0 {
573 let u2: *u8 = "nx_gate_roster_run: UNOBSERVABLE -- no estate root on this host\n" as *u8
574 gk_say(u2, gk_len(u2))
575 return 2
576 }
577 sys_setpriority(GRR_NICE)
578 let tbl: *u8 = sys_mmap(GRR_MAXG * GRR_SLOT)
579 let n: i64 = grr_load(conf, tbl)
580 let msg: *u8 = sys_mmap(GRR_MSG)
581 var o: i64 = 0
582 if n == (0 - 1) { o = gk_cat(msg, 0, "nx_gate_roster_run: roster unreadable: " as *u8); o = gk_cat(msg, o, conf); o = gk_cat(msg, o, "\n" as *u8); gk_say(msg, o); return 2 }
583 if n == (0 - 2) { o = gk_cat(msg, 0, "nx_gate_roster_run: REFUSED -- roster exceeds GRR_MAXG rows; a truncated roster silently drops gates\n" as *u8); gk_say(msg, o); return 2 }
584 let jfd: i64 = sys_openat_append(jrnl, GRR_MODE)
585 o = gk_cat(msg, 0, "nx_gate_roster_run: " as *u8); o = gk_cat(msg, o, tag)
586 o = gk_cat(msg, o, " rows=" as *u8); o = gk_catn(msg, o, n)
587 o = gk_cat(msg, o, " conf=" as *u8); o = gk_cat(msg, o, conf)
588 o = gk_cat(msg, o, " journal=" as *u8); o = gk_cat(msg, o, jrnl)
589 o = gk_cat(msg, o, " deadline_ms=" as *u8); o = gk_catn(msg, o, ms)
590 o = gk_cat(msg, o, " nice=" as *u8); o = gk_catn(msg, o, GRR_NICE)
591 o = gk_cat(msg, o, "\n" as *u8)
592 gk_say(msg, o)
593 // ORDER and BUDGET, the beat only: least-recently-run first, and no gate started that could outlive the
594 // scheduler's kill. A trial runs its list once, in list order, exactly as before.
595 let last: *i64 = sys_mmap((n + 1) * 8) as *i64
596 let ord: *i64 = sys_mmap((n + 1) * 8) as *i64
597 var k0: i64 = 0
598 while k0 < n { ord[k0] = k0; last[k0] = 0 - 1; k0 = k0 + 1 }
599 var jrows: i64 = 0 - 1
600 var dispatch: i64 = 0 - 1
601 if trial == 0 {
602 jrows = grr_lastrun(jrnl, GRR_TAG_BEAT, tbl, n, last)
603 grr_order(last, n, ord)
604 dispatch = grr_directive_ms(conf)
605 o = gk_cat(msg, 0, "nx_gate_roster_run: order=least-recently-run-first journal_rows_read=" as *u8)
606 o = gk_catn(msg, o, jrows)
607 if dispatch > 0 {
608 o = gk_cat(msg, o, " dispatch_deadline_ms=" as *u8); o = gk_catn(msg, o, dispatch)
609 o = gk_cat(msg, o, " pass_budget_ms=" as *u8); o = gk_catn(msg, o, dispatch - ms)
610 o = gk_cat(msg, o, " (the conf's scheduler kill bound minus the per-gate deadline: a gate is started only if it can finish before the kill)" as *u8)
611 } else { o = gk_cat(msg, o, " pass_budget=none (the conf states no @dispatch_deadline_ms)" as *u8) }
612 o = gk_cat(msg, o, "\n" as *u8)
613 gk_say(msg, o)
614 }
615 var bad: i64 = 0
616 var green: i64 = 0
617 var ran: i64 = 0
618 var deferred: i64 = 0
619 var i: i64 = 0
620 while i < n {
621 let gi: i64 = ord[i]
622 var go: i64 = 1
623 // >= and never >: a gate whose deadline lands ON the kill bound is still killed there, so it must not start
624 if dispatch > 0 { if ran > 0 { if sys_now_ms() - t_start + ms >= dispatch { go = 0 } } }
625 if go == 1 {
626 let r: i64 = grr_one(grr_slot(tbl, gi), tag, jfd, ms)
627 if r == 0 { green = green + 1 }
628 bad = bad + r
629 ran = ran + 1
630 last[gi] = sys_now_realtime_sec()
631 } else { deferred = deferred + 1 }
632 i = i + 1
633 }
634 if jfd >= 0 { sys_close(jfd) }
635 // the roster's TRUE freshness after this beat: how many gates have never run, and the oldest last run
636 let nowe: i64 = sys_now_realtime_sec()
637 var never: i64 = 0
638 var oldest: i64 = 0 - 1
639 var q0: i64 = 0
640 while q0 < n {
641 if last[q0] < 0 { never = never + 1 } else { if nowe - last[q0] > oldest { oldest = nowe - last[q0] } }
642 q0 = q0 + 1
643 }
644 if deferred > 0 {
645 o = gk_cat(msg, 0, "nx_gate_roster_run: DEFERRED " as *u8); o = gk_catn(msg, o, deferred)
646 o = gk_cat(msg, o, " gate(s) to the next beat: the pass budget was spent, and the stalest ran first\n" as *u8)
647 gk_say(msg, o)
648 }
649 // ---- HEARTBEAT: written because the PASS COMPLETED, and deliberately NOT gated on the verdict.
650 // nx_cron_watch is a dead-man's switch for the BEAT, not a health check for the gates -- their
651 // verdicts are already collected per row in the journal above.
652 // A LIVENESS SIGNAL MADE CONDITIONAL ON HEALTH GOES SILENT EXACTLY WHEN THINGS ARE FAILING,
653 // WHICH IS THE ONE MOMENT IT HAS TO SPEAK.
654 // A 630-row roster is essentially never all-green, so a GREEN-only stamp would never be written at
655 // all and the watch would read STALE forever -- a stuck alarm, which is worse than no watch.
656 // TRIAL DOES NOT STAMP: a hand-run must never forge the beat's heartbeat, or the dead-man's switch
657 // reports fresh because some seat happened to run a trial.
658 if trial == 0 {
659 var so: i64 = gk_cat(msg, 0, "ts=" as *u8)
660 so = gk_catn(msg, so, sys_now_realtime_sec())
661 so = gk_cat(msg, so, " rows=" as *u8); so = gk_catn(msg, so, n)
662 so = gk_cat(msg, so, " green_or_skip=" as *u8); so = gk_catn(msg, so, green)
663 so = gk_cat(msg, so, " red_absent_timeout=" as *u8); so = gk_catn(msg, so, bad)
664 so = gk_cat(msg, so, " ran=" as *u8); so = gk_catn(msg, so, ran)
665 so = gk_cat(msg, so, " deferred=" as *u8); so = gk_catn(msg, so, deferred)
666 so = gk_cat(msg, so, " never_run=" as *u8); so = gk_catn(msg, so, never)
667 so = gk_cat(msg, so, " oldest_age_s=" as *u8); so = gk_catn(msg, so, oldest)
668 so = gk_cat(msg, so, "\n" as *u8)
669 // gk_cat/gk_catn return an OFFSET and do NOT terminate, and `msg` still holds the header built
670 // above -- so without this byte gk_write's gk_len() would run past the stamp and append the
671 // previous message's tail. A BUFFER REUSED WITHOUT TERMINATION CARRIES THE LAST MESSAGE'S TAIL.
672 msg[so] = 0 as u8
673 // ---- ONLY THE PRODUCTION BEAT MAY WRITE THE PRODUCTION HEARTBEAT (2026-08-20) ----
674 // GRR_STAMP is a CONSTANT path, so before this ANY caller that passed a fixture conf or a
675 // scratch journal still stamped production: `beat /tmp/x.conf /tmp/x.jrnl` forged the
676 // cron-watched liveness signal for a run that touched no production row.
677 // A LIVENESS SIGNAL ANY CALLER CAN WRITE IS A DEAD-MAN'S SWITCH ANY CALLER CAN HOLD DOWN.
678 // THIS ORGAN ALREADY KNEW THE LAW AND HAD WIRED IT INTO ONE VERB ONLY: `admit` defaults its
679 // suspect list to a SIBLING of the conf so a fixture run cannot write a production artifact,
680 // and the comment above says a hand-run must never forge the beat's heartbeat -- while the
681 // beat verb wrote a fixed production path regardless of what it had just measured.
682 // MEASURED BEFORE THE FIX: the live stamp read rows=1 while knowledge/gateroster.conf held
683 // hundreds of rows, i.e. the last writer of the production heartbeat was NOT the production
684 // beat -- and nx_cron_watch was reading that as FRESH.
685 // A non-default run still stamps, BESIDE ITS OWN JOURNAL and announced, because writing
686 // nothing at all would trade a forged heartbeat for an invisible one.
687 var stamp: *u8 = GRR_STAMP
688 var offbeat: i64 = 0
689 if gk_streq(conf, GRR_CONF) == 0 { offbeat = 1 }
690 if gk_streq(jrnl, GRR_JRNL) == 0 { offbeat = 1 }
691 if offbeat == 1 {
692 let sp: *u8 = sys_mmap(GRR_SLOT * 8)
693 var spo: i64 = gk_cat(sp, 0, jrnl)
694 spo = gk_cat(sp, spo, ".stamp" as *u8)
695 sp[spo] = 0 as u8
696 stamp = sp
697 let m2: *u8 = sys_mmap(GRR_MSG)
698 var wo: i64 = gk_cat(m2, 0, "nx_gate_roster_run: NOT the production beat (conf or journal is not the default) -- heartbeat written beside its own journal at " as *u8)
699 wo = gk_cat(m2, wo, stamp)
700 wo = gk_cat(m2, wo, " and deliberately NOT to " as *u8)
701 wo = gk_cat(m2, wo, GRR_STAMP)
702 wo = gk_cat(m2, wo, "\n" as *u8)
703 gk_say(m2, wo)
704 }
705 if gk_write(stamp, msg) < 0 {
706 let hw: *u8 = "nx_gate_roster_run: WARN heartbeat NOT written -- the liveness watch will read STALE\n" as *u8
707 gk_say(hw, gk_len(hw))
708 }
709 }
710 o = gk_cat(msg, 0, "nx_gate_roster_run: " as *u8); o = gk_cat(msg, o, tag)
711 o = gk_cat(msg, o, " done rows=" as *u8); o = gk_catn(msg, o, n)
712 o = gk_cat(msg, o, " ran=" as *u8); o = gk_catn(msg, o, ran)
713 o = gk_cat(msg, o, " deferred=" as *u8); o = gk_catn(msg, o, deferred)
714 o = gk_cat(msg, o, " green_or_skip=" as *u8); o = gk_catn(msg, o, green)
715 o = gk_cat(msg, o, " red_absent_timeout=" as *u8); o = gk_catn(msg, o, bad)
716 if trial == 1 { o = gk_cat(msg, o, " (trial: reported, not judged)\nverdict=TRIAL\n" as *u8); gk_say(msg, o); return 0 }
717 if bad > 0 { o = gk_cat(msg, o, "\nverdict=RED\n" as *u8); gk_say(msg, o); return 1 }
718 o = gk_cat(msg, o, "\nverdict=GREEN\n" as *u8)
719 gk_say(msg, o)
720 return 0
721}