nx_ivvguard.nx source
↩ module page · 334 lines · 16383 B
1// nx_ivvguard.nx -- the PURE safety predicates of the IV+V beat, extracted so they can be PROVEN BEFORE
2// any code that forks or deletes is written (2026-08-07).
3//
4// WHY THIS FILE EXISTS SEPARATELY. nx_ivv_beat.cron.sh is the only remaining clock wrapper that FORKS A
5// BINARY NAMED IN A DATA PLANE and DELETES FILES BY PREFIX GLOB. Two shell case-patterns are the only
6// things that make it safe to run at all: the exe must match dot-slash-nx-underscore-star-dot-elf or
7// dot-slash-underscore-star-dot-elf, and the pre-wipe path must begin with slash-tmp-slash.
8// (STAR)WHEN A BEAT TAKES ITS COMMANDS FROM DATA, THE ALLOWLIST IS NOT A FEATURE OF THE BEAT -- IT IS THE
9// ONLY THING THAT MAKES THE BEAT SAFE TO RUN AT ALL.
10// A subtle mis-anchor turns a monitoring beat into an execution primitive, and a wrong prefix test deletes
11// by glob outside the temp directory. So the predicates ship FIRST, as pure functions over fabricated
12// strings, gated with NEGATIVE cases that must REFUSE -- exactly the shape that let clk_edf_pick_free be
13// proven before it was ever wired to a live dispatcher.
14// license_tier: ORIGINAL No hw writes (Rule 26). Pure: no side effects.
15import "nx_syscalls.nx"
16
17func ivg_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
18
19func ivg_starts(s: *u8, p: *u8) -> i64 {
20 var i: i64 = 0
21 while p[i] != (0 as u8) { if s[i] != p[i] { return 0 } i = i + 1 }
22 return 1
23}
24
25func ivg_ends(s: *u8, p: *u8) -> i64 {
26 let sl: i64 = ivg_len(s)
27 let pl: i64 = ivg_len(p)
28 if sl < pl { return 0 }
29 var i: i64 = 0
30 while i < pl { if s[sl-pl+i] != p[i] { return 0 } i = i + 1 }
31 return 1
32}
33
34// EXEC ALLOWLIST. The name must START with the sanctioned prefix AND END with .elf. BOTH halves matter:
35// a prefix-only test admits any dot-slash-nx name at all, and a suffix-only test admits an absolute path
36// to somebody elses .elf. The shell glob requires both, so this does too.
37// ANCHORED AT BOTH ENDS ON PURPOSE: an unanchored contains-test would admit a path that merely MENTIONS
38// the sanctioned prefix somewhere in the middle.
39func ivg_ends_n(s: *u8, sl: i64, p: *u8) -> i64 {
40 let pl: i64 = ivg_len(p)
41 if sl < pl { return 0 }
42 var i: i64 = 0
43 while i < pl { if s[sl-pl+i] != p[i] { return 0 } i = i + 1 }
44 return 1
45}
46
47// THE EXE IS THE FIRST TOKEN, NOT THE WHOLE FIELD. Measured against the LIVE ivvreg- plane 2026-08-07:
48// the real rows carry ARGUMENTS -- dot-slash-nx_actlog.elf selftest /tmp/ccgate/ivv_al.jrnl -- so a
49// suffix test applied to the whole field asks whether the LAST ARGUMENT ends .elf, and REFUSES a valid
50// row. The first version of this file did exactly that, and its gate still passed 12/12, because every
51// fixture I fabricated was a bare exe path with no arguments.
52// (STAR)A GATE WHOSE FIXTURES ARE ALL FABRICATED PROVES THE PREDICATE AGAINST YOUR MENTAL MODEL OF THE
53// DATA, NOT AGAINST THE DATA. THE FIRST FIXTURE MUST BE A REAL ROW.
54// Splitting on the first space is safe here and is NOT a shell-quoting problem: the beat execve()s the
55// exe directly, so the tail is inert argv handed to the named binary -- there is no shell to re-expand it.
56func ivv_exe_len(cmd: *u8) -> i64 {
57 var i: i64 = 0
58 while cmd[i] != (0 as u8) { if cmd[i] == (32 as u8) { return i } i = i + 1 }
59 return i
60}
61
62func ivv_cmd_allowed(cmd: *u8) -> i64 {
63 let el: i64 = ivv_exe_len(cmd)
64 if el < 6 { return 0 }
65 if ivg_ends_n(cmd, el, ".elf" as *u8) == 0 { return 0 }
66 if ivg_starts(cmd, "./nx_" as *u8) == 1 { return 1 }
67 if ivg_starts(cmd, "./_" as *u8) == 1 { return 1 }
68 return 0
69}
70
71// WIPE CONFINEMENT. The delete is a PREFIX GLOB, so confinement is the whole guard. Rejects a bare
72// slash-tmp with no trailing slash (not inside the directory) and anything that merely CONTAINS the temp
73// path later in the string.
74// TRAVERSAL: a path containing a double-dot is REFUSED outright. The shell pattern does NOT do this --
75// a traversal segment still matches the glob and escapes -- so this is a DELIBERATE STRENGTHENING of the
76// original, not a port defect, and it is called out here so nobody "fixes" it back.
77// (STAR)A PREFIX TEST IS NOT A CONTAINMENT TEST WHILE TRAVERSAL SEGMENTS EXIST.
78func ivv_wipe_allowed(pre: *u8) -> i64 {
79 if ivg_starts(pre, "/tmp/" as *u8) == 0 { return 0 }
80 let n: i64 = ivg_len(pre)
81 if n < 6 { return 0 }
82 var i: i64 = 0
83 while i + 1 < n { if pre[i] == (46 as u8) { if pre[i+1] == (46 as u8) { return 0 } } i = i + 1 }
84 return 1
85}
86
87// NO-WIPE SENTINEL. The live plane uses a bare dash for "this row has nothing to clean". A dash is NOT a
88// path and must never reach ivv_wipe_allowed as one: that function correctly REFUSES a dash, so a caller
89// missing this check would REFUSE THE WHOLE ROW instead of simply skipping the wipe. Two of the four live
90// rows carry a dash, so this is load-bearing, not decorative.
91// (STAR)A SENTINEL VALUE IN A DATA PLANE IS PART OF THE SCHEMA -- A VALIDATOR THAT HAS NOT BEEN TOLD
92// ABOUT IT REJECTS VALID DATA AND CALLS IT SAFETY.
93func ivv_wipe_none(pre: *u8) -> i64 {
94 if pre[0] != (45 as u8) { return 0 }
95 if pre[1] != (0 as u8) { return 0 }
96 return 1
97}
98
99func ivv_marker_hit(out: *u8, n: i64, marker: *u8) -> i64 {
100 let ml: i64 = ivg_len(marker)
101 if ml <= 0 { return 0 }
102 if n < ml { return 0 }
103 var i: i64 = 0
104 while i + ml <= n {
105 var k: i64 = 0
106 var same: i64 = 1
107 while k < ml { if out[i+k] != marker[k] { same = 0; k = ml } else { k = k + 1 } }
108 if same == 1 { return 1 }
109 i = i + 1
110 }
111 return 0
112}
113
114// THE VERDICT. GREEN requires BOTH rc==0 AND the marker present. Either alone is DRIFT. That conjunction
115// is the point: a gate that exits 0 without printing its marker is exactly the fake-green class this beat
116// exists to catch, and a gate that prints the marker while failing is the mirror image.
117// (STAR)AN EXIT CODE AND A MARKER ARE TWO INDEPENDENT CLAIMS -- REQUIRING BOTH IS WHAT MAKES THE CHECK
118// NON-VACUOUS.
119// ================= THE TOOTH FLOOR =================
120// A marker like "passed 15/15 verdict=GREEN" is an EQUALITY, and an equality against a tooth count breaks
121// the moment the gate improves: nx_law_warden now runs 26 teeth, so that string can never match again even
122// when the gate is perfectly healthy. A monitor that goes RED after every improvement teaches its readers
123// to ignore it, which costs more than never having built it.
124// (STAR)A MARKER THAT ENCODES A COUNT BREAKS EVERY TIME THE THING IT WATCHES IMPROVES -- AND A MONITOR THAT
125// SCREAMS AFTER EVERY IMPROVEMENT IS TRAINED TO BE IGNORED.
126//
127// The naive repair is to match only the verdict= anchor and drop the count. That THROWS AWAY A REAL
128// PROTECTION: a gate that silently LOSES teeth is a defect class this estate has already been bitten by,
129// and an anchor-only check cannot see it -- 26 teeth becoming 3 still prints verdict=GREEN.
130//
131// So the count is kept and REINTERPRETED: the declared number is a FLOOR, not an equality. No new plane,
132// no new storage, no schema change -- the existing rows already carry the floor; they were simply being
133// read as the wrong relation. Improvements pass, regressions fail, and nobody has to rewrite expectations
134// by hand as gates grow.
135// (STAR)THE DATA NEEDED FOR THE STRONGER CHECK WAS ALREADY IN THE ROW; WHAT WAS WRONG WAS THE RELATION
136// APPLIED TO IT, NOT THE SCHEMA.
137func ivv_isdigit(c: u8) -> i64 {
138 if c < (48 as u8) { return 0 }
139 if c > (57 as u8) { return 0 }
140 return 1
141}
142
143// Locate a <digits>/<digits> token. span[0]=start span[1]=one-past-end. Returns 1 if found.
144func ivv_find_count(s: *u8, span: *i64) -> i64 {
145 var i: i64 = 0
146 while s[i] != (0 as u8) {
147 if ivv_isdigit(s[i]) == 1 {
148 var j: i64 = i
149 while ivv_isdigit(s[j]) == 1 { j = j + 1 }
150 var hit: i64 = 0
151 if s[j] == (47 as u8) {
152 if ivv_isdigit(s[j+1]) == 1 {
153 var k: i64 = j + 1
154 while ivv_isdigit(s[k]) == 1 { k = k + 1 }
155 span[0] = i
156 span[1] = k
157 hit = 1
158 }
159 }
160 if hit == 1 { return 1 }
161 i = j
162 }
163 if ivv_isdigit(s[i]) == 0 { i = i + 1 }
164 }
165 return 0
166}
167
168func ivv_find_sub_n(hay: *u8, n: i64, from: i64, needle: *u8, nl: i64) -> i64 {
169 if nl <= 0 { return from }
170 var i: i64 = from
171 while i + nl <= n {
172 var k: i64 = 0
173 var same: i64 = 1
174 while k < nl { if hay[i+k] != needle[k] { same = 0; k = nl } else { k = k + 1 } }
175 if same == 1 { return i }
176 i = i + 1
177 }
178 return 0 - 1
179}
180
181// Match `marker` against the captured output, tolerating a DIFFERENT count in the same position.
182// res[0]=observed passed res[1]=observed total res[2]=declared total (the floor)
183// A marker with no count token falls back to plain containment with res all -1, so rows that never carried
184// a count behave exactly as before -- this change cannot regress them.
185func ivv_match_counted(out: *u8, n: i64, marker: *u8, res: *i64) -> i64 {
186 res[0] = 0 - 1
187 res[1] = 0 - 1
188 res[2] = 0 - 1
189 let span: *i64 = sys_mmap(64) as *i64
190 if ivv_find_count(marker, span) == 0 { return ivv_marker_hit(out, n, marker) }
191
192 let ml: i64 = ivg_len(marker)
193 let pl: i64 = span[0]
194 let sl: i64 = ml - span[1]
195
196 let pbuf: *u8 = sys_mmap(pl + 16)
197 var a: i64 = 0
198 while a < pl { pbuf[a] = marker[a]; a = a + 1 }
199 pbuf[pl] = (0 as u8)
200
201 let sbuf: *u8 = sys_mmap(sl + 16)
202 var b: i64 = 0
203 while b < sl { sbuf[b] = marker[span[1]+b]; b = b + 1 }
204 sbuf[sl] = (0 as u8)
205
206 // the declared count is the FLOOR
207 var dv: i64 = 0
208 var di: i64 = span[0]
209 while ivv_isdigit(marker[di]) == 1 { dv = dv * 10 + ((marker[di] as i64) - 48); di = di + 1 }
210 var dt: i64 = 0
211 di = di + 1
212 while ivv_isdigit(marker[di]) == 1 { dt = dt * 10 + ((marker[di] as i64) - 48); di = di + 1 }
213 res[2] = dt
214
215 // ---- TRY EVERY OCCURRENCE OF THE PREFIX, NOT JUST THE FIRST (2026-08-07) ----
216 //
217 // THE DEFECT, traced byte-level by the seat that filed 1786135139 and reproduced here before this
218 // was touched. v1 took `ivv_find_sub_n(out, n, 0, pbuf, pl)` -- the FIRST "passed " in the output --
219 // and returned 0 the moment that one failed to validate. Since the shared base-class verdict
220 // refactor (109 source files now carry "verdict emission migrated onto the shared base class"),
221 // gates print the tally TWICE:
222 // ASSET-MERKLE-GATE passed 4/4 <- line 6, BARE, no suffix
223 // NX-ASSET-MERKLE-GATE passed 4/4 verdict=GREEN (...) <- line 7, the real marker
224 // The first parses its digits fine and then fails the (correct, deliberate) rule that the suffix
225 // must sit IMMEDIATELY after the count -- what follows `4/4` on line 6 is a newline. v1 gave up
226 // there and reported the marker ABSENT while it sat one line below, inside the very buffer it had
227 // been handed. MEASURED BLAST RADIUS: 12 of 95 rows UNPROVEN in the sweep, TEN OF THEM CONSECUTIVE
228 // ivv-asset_* gates -- ONE CAUSE WEARING TEN MASKS -- every one of which passes its own teeth.
229 //
230 // ★★★★★★A MATCHER THAT TAKES THE FIRST CANDIDATE AND GIVES UP CANNOT SURVIVE A PRODUCER THAT
231 // PRINTS ITS KEY TWICE -- AND A REFACTOR THAT ADDS A SUMMARY LINE IS EXACTLY THAT PRODUCER.
232 // ★★★★★THE SAME DEFECT, INDEPENDENTLY, IN TWO ORGANS ON ONE DAY: nx_idemp's id_tally read
233 // "passed" out of a tooth's PROSE and quit; this read it out of a BARE SUMMARY and quit. When a
234 // parsing bug recurs across authors, the shape is the lesson, not the instance.
235 //
236 // THE CHANGE IS MONOTONE AND THAT IS THE SAFETY ARGUMENT: every acceptance still requires the full
237 // contract -- prefix, digits, '/', digits, and the suffix IMMEDIATELY at the cursor. Nothing that
238 // v1 rejected on those grounds is accepted now; the only difference is that a candidate which
239 // fails is no longer treated as proof that no candidate exists. It can turn a false ABSENT into a
240 // PRESENT and can never do the reverse.
241 var from: i64 = 0
242 var found: i64 = 0
243 var fpv: i64 = 0
244 var ftv: i64 = 0
245 var scanning: i64 = 1
246 while scanning == 1 {
247 let p0: i64 = ivv_find_sub_n(out, n, from, pbuf, pl)
248 if p0 < 0 { scanning = 0 }
249 if p0 >= 0 {
250 var q: i64 = p0 + pl
251 var ok: i64 = 1
252 if q >= n { ok = 0 }
253 if ok == 1 { if ivv_isdigit(out[q]) == 0 { ok = 0 } }
254
255 var pv: i64 = 0
256 if ok == 1 {
257 var stop: i64 = 0
258 while stop == 0 {
259 if q >= n { stop = 1 }
260 if stop == 0 {
261 if ivv_isdigit(out[q]) == 1 { pv = pv * 10 + ((out[q] as i64) - 48); q = q + 1 }
262 if q < n { if ivv_isdigit(out[q]) == 0 { stop = 1 } }
263 }
264 }
265 if q >= n { ok = 0 }
266 if ok == 1 { if out[q] != (47 as u8) { ok = 0 } }
267 }
268
269 var tv: i64 = 0
270 if ok == 1 {
271 q = q + 1
272 if q >= n { ok = 0 }
273 if ok == 1 { if ivv_isdigit(out[q]) == 0 { ok = 0 } }
274 if ok == 1 {
275 var stop2: i64 = 0
276 while stop2 == 0 {
277 if q >= n { stop2 = 1 }
278 if stop2 == 0 {
279 if ivv_isdigit(out[q]) == 1 { tv = tv * 10 + ((out[q] as i64) - 48); q = q + 1 }
280 if q < n { if ivv_isdigit(out[q]) == 0 { stop2 = 1 } }
281 }
282 }
283 }
284 }
285
286 // UNCHANGED RULE: the suffix must sit IMMEDIATELY after the count, never merely somewhere
287 // later, or "passed 25/26 ... verdict=GREEN" from an unrelated line would satisfy the check.
288 if ok == 1 {
289 if sl > 0 {
290 if ivv_find_sub_n(out, n, q, sbuf, sl) != q { ok = 0 }
291 }
292 }
293
294 if ok == 1 { found = 1; fpv = pv; ftv = tv; scanning = 0 }
295 // ADVANCE PAST THIS CANDIDATE. Not `q` -- a failed parse leaves the cursor at an arbitrary
296 // place, and stepping from it can skip a real match. p0+1 is the only cursor whose meaning
297 // does not depend on how far the failed attempt happened to get.
298 if ok == 0 { from = p0 + 1 }
299 }
300 }
301 if found == 0 { return 0 }
302 res[0] = fpv
303 res[1] = ftv
304 return 1
305}
306
307// A count that has GROWN is an improvement; a count that has SHRUNK is tooth loss and must be drift.
308// An absent floor (-1, i.e. a marker with no count) can never fail this -- it is not evidence of loss.
309func ivv_floor_ok(observed_total: i64, declared_total: i64) -> i64 {
310 if declared_total < 0 { return 1 }
311 if observed_total < 0 { return 1 }
312 if observed_total >= declared_total { return 1 }
313 return 0
314}
315
316// The full v2 verdict. GREEN requires ALL of: exit 0, the marker matched around its count, every tooth
317// that ran actually passed, and no teeth lost since the row was written.
318// (STAR)A RESULT BUFFER REUSED ACROSS ITERATIONS REPORTS THE PREVIOUS ITEM'S MEASUREMENT AS THIS ITEM'S --
319// AND IT IS THE HARDEST KIND OF WRONG TO SPOT, BECAUSE THE NUMBER IS REAL DATA, JUST NOT ABOUT THIS ROW.
320// Measured 2026-08-07: v1 returned early on rc!=0 without touching res, so rows 1 and 3 of the live sweep
321// both reported "teeth observed=9 floor=9" -- row 2's figures. The parse now runs FIRST and unconditionally,
322// so res always describes the row being reported; the exit code is applied afterwards. This also makes the
323// report strictly more useful, since a FAILING gate now discloses how many teeth it actually ran.
324func ivv_is_green2(rc: i64, out: *u8, n: i64, marker: *u8, res: *i64) -> i64 {
325 let matched: i64 = ivv_match_counted(out, n, marker, res)
326 if rc != 0 { return 0 }
327 if matched == 0 { return 0 }
328 if res[1] >= 0 { if res[0] != res[1] { return 0 } }
329 return ivv_floor_ok(res[1], res[2])
330}
331
332func ivv_is_green(rc: i64, out: *u8, n: i64, marker: *u8) -> i64 {
333 if rc != 0 { return 0 }
334 return ivv_marker_hit(out, n, marker)
335}