code wiki / (root) / nx_ivvguard.nx

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}