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}