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}