code wiki / (root) / nx_clockjobs_gate.nx

nx_clockjobs_gate.nx source

↩ module page · 198 lines · 10052 B

1// nx_clockjobs_gate.nx -- is the scheduler's registry WELL-FORMED, or is it carrying silent no-ops? 2// 3// WHY THIS EXISTS (debt 1785601721). clk_load in nx_clock_sched.nx parses clock_jobs.tsv with NO 4// validation of any kind. Every failure mode below is silent -- the row becomes a "job" that dispatches 5// nothing, forever, and no instrument reports it: 6// * fewer than 4 tab fields -> organ = "" -> dispatch is a no-op, scheduled for eternity 7// * non-digit chars in a number -> SKIPPED, so an interval of "3o0" parses as 30 (wrong cadence, 8// plausible value, no error) -- the silent-coercion class 9// * interval = 0 -> a job that is always due 10// * rows past CLK_MAXJOBS -> SILENTLY DROPPED (the truncating-cap class: the registry says 11// one thing, the scheduler runs another) 12// 13// A MALFORMED ROW MUST BE REFUSED AT ADMISSION, NOT PARSED INTO A SILENT NO-OP AT DISPATCH. This gate 14// is the DETECTION half of that: it cannot stop a bad write, but it makes one impossible to miss. The 15// admission half (a validating writer) is the next rung and is deliberately separate -- a reader-side 16// guard that is honest about being read-only beats a writer swap rushed under a live scheduler. 17// 18// SCOPE, STATED HONESTLY: this validates the REGISTRY FILE. It does NOT prove the named organ exists on 19// disk, and it does NOT prove a job ever dispatched -- fail=0 is not evidence that a beat moved what it 20// names. Those are separate claims and separate gates. 21// license_tier: ORIGINAL expect_exit: 0 22import "nx_syscalls.nx" 23import "nx_gate_verdict.nx" 24import "nx_store_seed_lib.nx" 25 26// MUST TRACK nx_clock_sched.nx:18. Mirrored rather than imported because that file is the SCHEDULER 27// library and importing it here would drag the dispatcher into a read-only gate. If the dispatcher's 28// cap changes and this is not updated, T3 measures the wrong bound -- so the number is named, not inline. 29const CJ_MAXJOBS: i64 = 128 30// ⚠⚠SUBJECT REPOINTED 2026-08-14 -- THIS GATE HAD BEEN VALIDATING A FOSSIL. It read "clock_jobs.tsv", 31// which nx_clock_tickless MIGRATED to the clocksched- plane ("the tsv is never written again") and then 32// deleted. The file has not existed for days, so the readable-registry tooth failed on every hourly run: 33// the gate was PERMANENTLY RED, and its RED said nothing about the scheduler. ★★★★★★A GATE WHOSE SUBJECT 34// MOVED REPORTS FOREVER ON A FILE NOBODY WRITES, AND A PERMANENTLY-RED DETECTOR IS ONE EVERYONE LEARNS 35// TO IGNORE -- the fail-closed design was RIGHT, the path was stale. 36// ⚠THE TWO PLANES HAVE DIFFERENT SHAPES, so this is not an interchangeable swap: 37// clocksched- (LIVE, what the scheduler dispatches) = name TAB interval TAB next_due TAB organ (3 tabs) 38// clockjobs- (DESIRED, what external writers put) = name TAB interval TAB organ (2 tabs) 39// This parser wants 3 tabs and this gate's subject is "the registry the scheduler RUNS", so it validates 40// the LIVE plane. Validating the desired plane is a separate rung with a separate parser -- named, not done. 41const CJ_PLANE: *u8 = "knowledge/store/clocksched-" as *u8 42const CJ_TAB: i64 = 9 43const CJ_NL: i64 = 10 44 45func cj_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 46func cj_p(s: *u8) -> i64 { let n: i64 = cj_len(s); sys_write(1, s, n); return 0 } 47func cj_pn(v: i64) -> i64 { 48 var m: i64 = v 49 if m < 0 { cj_p("-" as *u8); m = 0 - m } 50 let t: *u8 = sys_mmap(24) 51 var k: i64 = 0 52 if m == 0 { t[0] = 48 as u8; k = 1 } 53 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 54 let o: *u8 = sys_mmap(24) 55 var i: i64 = 0 56 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 57 sys_write(1, o, k) 58 return 0 59} 60 61// Is every byte in [lo,hi) a decimal digit, and is the span non-empty? 62func cj_all_digits(buf: *u8, lo: i64, hi: i64) -> i64 { 63 if hi <= lo { return 0 } 64 var i: i64 = lo 65 var ok: i64 = 1 66 while i < hi { 67 let c: i64 = buf[i] as i64 68 if c < 48 { ok = 0 } 69 if c > 57 { ok = 0 } 70 i = i + 1 71 } 72 return ok 73} 74 75// Decimal value of [lo,hi). Caller must have checked cj_all_digits first. 76func cj_val(buf: *u8, lo: i64, hi: i64) -> i64 { 77 var v: i64 = 0 78 var i: i64 = lo 79 while i < hi { v = v * 10 + ((buf[i] as i64) - 48); i = i + 1 } 80 return v 81} 82 83func main() -> i64 { 84 cj_p("=== nx_clockjobs_gate -- is the scheduler registry well-formed? (debt 1785601721) ===\n" as *u8) 85 86 let lenp: *i64 = sys_mmap(8) as *i64 87 // sts_load_fit SIZES ITS OWN BUFFER (8 MiB doubling to 1 GiB, then REFUSES with null rather than 88 // handing back a partial board), so there is no cap for this gate to guess and a short read cannot 89 // masquerade as a clean registry -- the same contract shape as the sys_read_file it replaces. 90 // ⚠IMPRECISION DECLARED, NOT HIDDEN: sts_load_fit composes sts_load, which trusts the plane's q:n 91 // count key. It is therefore BUFFER-honest but not COUNT-honest -- a low or stale q:n would still 92 // under-report rows. Closing that needs sts_load_honest (flags[2] = rows reachable beyond q:n) and 93 // is the next rung, named here so no reader mistakes this scan for exhaustive. 94 let data: *u8 = sts_load_fit(CJ_PLANE, lenp) 95 if (data as i64) == 0 { 96 cj_p(" REGISTRY UNREADABLE: " as *u8); cj_p(CJ_PLANE); cj_p("\n" as *u8) 97 cj_p(" Reporting zero malformed rows here would be indistinguishable from a clean registry,\n" as *u8) 98 cj_p(" so this is RED: the instrument measured NOTHING.\n" as *u8) 99 let ctr0: *i64 = gv_ctr() 100 gv_head("nx_clockjobs_gate -- scheduler registry well-formedness" as *u8) 101 gv_check("T1 the registry was readable (a blind scan must never render as a clean one)" as *u8, 0, ctr0) 102 let rc0: i64 = gv_verdict("CLOCKJOBS-GATE" as *u8, ctr0, "registry unreadable" as *u8) 103 sys_exit(rc0) 104 return rc0 105 } 106 107 let dn: i64 = lenp[0] 108 var rows: i64 = 0 109 var bad: i64 = 0 110 var i: i64 = 0 111 var ls: i64 = 0 112 113 while i <= dn { 114 var eol: i64 = 0 115 if i == dn { eol = 1 } 116 if i < dn { if (data[i] as i64) == CJ_NL { eol = 1 } } 117 if eol == 1 { 118 if i > ls { 119 rows = rows + 1 120 var t1: i64 = 0 - 1 121 var t2: i64 = 0 - 1 122 var t3: i64 = 0 - 1 123 var ntab: i64 = 0 124 var p: i64 = ls 125 while p < i { 126 if (data[p] as i64) == CJ_TAB { 127 ntab = ntab + 1 128 if ntab == 1 { t1 = p } 129 if ntab == 2 { t2 = p } 130 if ntab == 3 { t3 = p } 131 } 132 p = p + 1 133 } 134 var rowbad: i64 = 0 135 if ntab != 3 { 136 rowbad = 1 137 cj_p(" ROW " as *u8); cj_pn(rows) 138 cj_p(" MALFORMED: " as *u8); cj_pn(ntab); cj_p(" tabs, want 3 (name/interval/next_due/organ)\n" as *u8) 139 } 140 if ntab == 3 { 141 if t1 <= ls { 142 rowbad = 1 143 cj_p(" ROW " as *u8); cj_pn(rows); cj_p(" MALFORMED: empty NAME\n" as *u8) 144 } 145 if cj_all_digits(data, t1 + 1, t2) == 0 { 146 rowbad = 1 147 cj_p(" ROW " as *u8); cj_pn(rows) 148 cj_p(" MALFORMED: INTERVAL is not all digits -- clk_load SKIPS non-digits, so this\n" as *u8) 149 cj_p(" parses to a plausible WRONG cadence rather than erroring\n" as *u8) 150 } 151 if cj_all_digits(data, t1 + 1, t2) == 1 { 152 if cj_val(data, t1 + 1, t2) == 0 { 153 rowbad = 1 154 cj_p(" ROW " as *u8); cj_pn(rows); cj_p(" MALFORMED: INTERVAL=0 (always due)\n" as *u8) 155 } 156 } 157 if cj_all_digits(data, t2 + 1, t3) == 0 { 158 rowbad = 1 159 cj_p(" ROW " as *u8); cj_pn(rows); cj_p(" MALFORMED: NEXT_DUE is not all digits\n" as *u8) 160 } 161 if i <= (t3 + 1) { 162 rowbad = 1 163 cj_p(" ROW " as *u8); cj_pn(rows) 164 cj_p(" MALFORMED: empty ORGAN -- this row dispatches NOTHING, forever, silently\n" as *u8) 165 } 166 } 167 if rowbad == 1 { bad = bad + 1 } 168 } 169 ls = i + 1 170 } 171 i = i + 1 172 } 173 174 cj_p(" rows read = " as *u8); cj_pn(rows); cj_p("\n" as *u8) 175 cj_p(" malformed rows = " as *u8); cj_pn(bad); cj_p("\n" as *u8) 176 cj_p(" dispatcher cap = " as *u8); cj_pn(CJ_MAXJOBS); cj_p(" (rows beyond it are SILENTLY DROPPED by clk_load)\n" as *u8) 177 178 var t3ok: i64 = 0 179 if rows <= CJ_MAXJOBS { t3ok = 1 } 180 if t3ok == 0 { 181 cj_p(" OVER CAP: the registry declares more jobs than the scheduler will load. The file and the\n" as *u8) 182 cj_p(" running schedule DISAGREE, and nothing else reports it.\n" as *u8) 183 } 184 185 var t1ok: i64 = 0 186 if rows > 0 { t1ok = 1 } 187 var t2ok: i64 = 0 188 if bad == 0 { t2ok = 1 } 189 190 let ctr: *i64 = gv_ctr() 191 gv_head("nx_clockjobs_gate -- scheduler registry well-formedness" as *u8) 192 gv_check("T1 the scan read real rows (zero rows would render like a clean registry)" as *u8, t1ok, ctr) 193 gv_check("T2 every row is well-formed (4 fields, numeric interval>0, numeric next_due, non-empty organ)" as *u8, t2ok, ctr) 194 gv_check("T3 row count is within the dispatcher cap (beyond it, clk_load drops rows silently)" as *u8, t3ok, ctr) 195 let rc: i64 = gv_verdict("CLOCKJOBS-GATE" as *u8, ctr, "registry validated row-by-row; a malformed row is a permanent silent no-op" as *u8) 196 sys_exit(rc) 197 return rc 198}