code wiki / _hdl_build / nx_staghyg_gate.nx

nx_staghyg_gate.nx source

↩ module page · 351 lines · 21691 B

1// nx_staghyg_gate.nx -- prove the staging-hygiene guard REFUSES a backwards promote, does NOT refuse a 2// legitimate one, and -- the tooth this gate earned the hard way -- does NOT refuse an ORDINARY EDIT. 3// 4// Inherits the nx_gate_verdict base class (D001/L009: gv_ctr/gv_head/gv_check/gv_bite/gv_verdict) so 5// nx_gate_green can judge it and it records a harness frame. 6// 7// DETERMINISTIC BY CONSTRUCTION: every fixture mtime is set explicitly via sys_utimensat and `now` is 8// passed to sg_classify as a parameter, so there are NO sleeps and NO filesystem-resolution races. 9// Each tooth writes its OWN file names, so nothing depends on whether the write helper truncates. 10// 11// THE cfg BELOW IS THE SHIPPED POLICY, NOT A CONVENIENT ONE. An earlier version of this gate passed 12// 10/10 against thresholds the shipped conf did not use, which proves the mechanism and says nothing 13// about the rule operators actually meet. T4 and T8 prove it FIRES; T11 proves it STAYS SILENT on a 14// one-run edit -- and T11 exists because the first tuning refused the very build that introduced it. 15// A guard that fires on routine work is not strict, it is training people to bypass it. 16// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 17import "nx_syscalls.nx" 18import "nx_gate_verdict.nx" 19import "nx_staging_guard.nx" 20 21const GT_NOW: i64 = 1800000000 22const GT_DAY: i64 = 86400 23 24func gt_wfd(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 } 25func gt_wnfd(fd: i64, v: i64) -> i64 { 26 var m: i64 = v 27 let t: *u8 = sys_mmap(32) 28 var k: i64 = 0 29 if m == 0 { t[0] = 48 as u8; k = 1 } 30 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 31 let o: *u8 = sys_mmap(32) 32 var i: i64 = 0 33 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 34 sys_write(fd, o, k) 35 sys_munmap(t, 32) 36 sys_munmap(o, 32) 37 return 0 38} 39 40// Write `n` distinct printable runs (prefix + index), newline-separated, then `pad` bare newlines. 41// A newline is non-printable to cdl_runlen, so it both SEPARATES runs and PADS bytes without adding any 42// run -- which is how a fixture is made BIGGER while carrying FEWER of the live runs. Indices 43// [oddstart, oddstart+oddcount) get a different prefix: that is the mutation dial, and its SIZE is what 44// separates an ordinary edit (1 run) from a real regression (many). 45func gt_tokens(path: *u8, prefix: *u8, n: i64, pad: i64, odd: *i64) -> i64 { 46 let fd: i64 = sys_openat_wr(path, 420) 47 if fd < 0 { return 0 } 48 var i: i64 = 0 49 while i < n { 50 var mut: i64 = 0 51 if i >= odd[0] { if i < (odd[0] + odd[1]) { mut = 1 } } 52 if mut == 1 { gt_wfd(fd, "NXSG_MUTATED_RUN_" as *u8) } 53 if mut == 0 { gt_wfd(fd, prefix) } 54 gt_wnfd(fd, 1000 + i) 55 sys_write(fd, "\n" as *u8, 1) 56 i = i + 1 57 } 58 var p: i64 = 0 59 while p < pad { sys_write(fd, "\n" as *u8, 1); p = p + 1 } 60 sys_close(fd) 61 return 1 62} 63 64func gt_odd(o: *i64, start: i64, count: i64) -> i64 { o[0] = start; o[1] = count; return 0 } 65 66func gt_fill(dst: *u8, s: *u8) -> i64 { 67 var i: i64 = 0 68 while s[i] != (0 as u8) { dst[i] = s[i]; i = i + 1 } 69 return i 70} 71 72func gt_set_mtime(path: *u8, sec: i64) -> i64 { 73 let ts: *i64 = sys_mmap(64) as *i64 74 ts[0] = sec; ts[1] = 0; ts[2] = sec; ts[3] = 0 75 return sys_utimensat(path, ts) 76} 77 78func main() -> i64 { 79 let ctr: *i64 = gv_ctr() 80 let out: *i64 = sys_mmap(8 * SG_OUT_SLOTS) as *i64 81 let none: *i64 = sys_mmap(32) as *i64 82 gt_odd(none, 0 - 1, 0) 83 let odd6: *i64 = sys_mmap(32) as *i64 84 gt_odd(odd6, 7, 6) 85 let odd1: *i64 = sys_mmap(32) as *i64 86 gt_odd(odd1, 7, 1) 87 let odd3: *i64 = sys_mmap(32) as *i64 88 gt_odd(odd3, 0, 3) 89 90 // THE SHIPPED POLICY (knowledge/staging_hygiene.conf), stated explicitly so a conf drift shows up 91 // as a FAILING TOOTH here rather than as a silently different rule in production. 92 let cfg: *i64 = sys_mmap(8 * SG_C_SLOTS) as *i64 93 cfg[SG_C_MAXAGE] = 14 94 cfg[SG_C_SHRINKTOL] = 20 95 cfg[SG_C_CAPLOSS] = 50 96 cfg[SG_C_CAPLOSSABS] = 5 97 cfg[SG_C_CAPLOSSMIN] = 2 98 cfg[SG_C_MINTOK] = 12 99 cfg[SG_C_MAXTOK] = 256 100 101 gv_head("nx_staghyg_gate -- refuses backwards promotes, allows forward ones, ignores ordinary edits" as *u8) 102 103 // ---- T1 FORWARD: newer, carries every live run -> ALLOW ---------------------------------------- 104 gt_tokens("/tmp/nxsg_t1_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 105 gt_tokens("/tmp/nxsg_t1_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 500, none) 106 gt_set_mtime("/tmp/nxsg_t1_live" as *u8, GT_NOW - GT_DAY) 107 gt_set_mtime("/tmp/nxsg_t1_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 108 let c1: i64 = sg_classify("/tmp/nxsg_t1_live" as *u8, "/tmp/nxsg_t1_live.new" as *u8, GT_NOW, cfg, out) 109 var t1: i64 = 0 110 if c1 == SG_FORWARD { if sg_allows(c1) == 1 { t1 = 1 } } 111 gv_check("T1 forward promote ALLOWED (newer, no run loss)" as *u8, t1, ctr) 112 113 // ---- T2 BACKDATE: staged PREDATES live -> REFUSE ----------------------------------------------- 114 gt_tokens("/tmp/nxsg_t2_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 115 gt_tokens("/tmp/nxsg_t2_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 116 gt_set_mtime("/tmp/nxsg_t2_live" as *u8, GT_NOW - GT_DAY) 117 gt_set_mtime("/tmp/nxsg_t2_live.new" as *u8, GT_NOW - (2 * GT_DAY)) 118 let c2: i64 = sg_classify("/tmp/nxsg_t2_live" as *u8, "/tmp/nxsg_t2_live.new" as *u8, GT_NOW, cfg, out) 119 var t2: i64 = 0 120 if c2 == SG_BACKDATE { if sg_allows(c2) == 0 { t2 = 1 } } 121 gv_check("T2 BACKDATE refused (staged predates the live binary)" as *u8, t2, ctr) 122 123 // ---- T3 SHRINK WITH LOSS: newer, materially smaller AND 30 of 40 live runs gone -> REFUSE -------- 124 // Since 2026-08-18 the shrink screen DEFERS to the token ruler whenever the ruler could run (see 125 // sg_classify: the compiler's whole-program DCE makes every honest rebuild 20-70% smaller, so size 126 // alone can no longer separate "pruned dead code" from "lost capability"). This fixture loses runs, 127 // so the ruler is what refuses it: the class is CAPLOSS and the promote is still refused. 128 gt_tokens("/tmp/nxsg_t3_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 129 gt_tokens("/tmp/nxsg_t3_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 10, 0, none) 130 gt_set_mtime("/tmp/nxsg_t3_live" as *u8, GT_NOW - GT_DAY) 131 gt_set_mtime("/tmp/nxsg_t3_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 132 let c3: i64 = sg_classify("/tmp/nxsg_t3_live" as *u8, "/tmp/nxsg_t3_live.new" as *u8, GT_NOW, cfg, out) 133 var t3: i64 = 0 134 if c3 == SG_CAPLOSS { if sg_allows(c3) == 0 { t3 = 1 } } 135 gv_check("T3 SHRINK+LOSS refused as CAPLOSS (smaller AND 30 of 40 live runs gone)" as *u8, t3, ctr) 136 137 // ---- T3b THE DCE SHAPE: newer, 72% smaller, EVERY live run kept, run count held -> ALLOW --------- 138 // This is what every rebuild looks like after nx_cc's reachability pass (B1, 2026-08-18): the code 139 // that no path reaches is gone, every string the program can print is still there. A guard that 140 // refused this would refuse the whole estate's next promote, and a refusal that fires on routine 141 // work is a bypass waiting to be typed by reflex (T11's own lesson). Live is padded with 2000 bare 142 // newlines (bytes, not runs) so the byte shrink is large while the run ruler sees no loss at all. 143 gt_tokens("/tmp/nxsg_t3b_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 2000, none) 144 gt_tokens("/tmp/nxsg_t3b_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 145 gt_set_mtime("/tmp/nxsg_t3b_live" as *u8, GT_NOW - GT_DAY) 146 gt_set_mtime("/tmp/nxsg_t3b_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 147 let c3b: i64 = sg_classify("/tmp/nxsg_t3b_live" as *u8, "/tmp/nxsg_t3b_live.new" as *u8, GT_NOW, cfg, out) 148 var t3b: i64 = 0 149 if c3b == SG_FORWARD { if sg_allows(c3b) == 1 { if out[SG_O_SHRINKPERMIL] > cfg[SG_C_SHRINKTOL] { if out[SG_O_MISSING] == 0 { t3b = 1 } } } } 150 gv_check("T3b DCE-SHAPE shrink ALLOWED: past the size screen, zero live runs absent, count held" as *u8, t3b, ctr) 151 152 // ---- T3c SHRINK THE RULER CANNOT VOUCH FOR: an EMPTY staged file -> REFUSE (fail-closed) --------- 153 // The screen still bites where the ruler is blind: nothing to read means nothing can vouch, and a 154 // 0-byte .new is the exact artefact a COMPILE-FAIL leaves behind. Size is then the ONLY evidence 155 // and it says backwards, so this stays a refusal. 156 gt_tokens("/tmp/nxsg_t3c_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 157 let t3cfd: i64 = sys_openat_wr("/tmp/nxsg_t3c_live.new" as *u8, 420) 158 if t3cfd >= 0 { sys_close(t3cfd) } 159 gt_set_mtime("/tmp/nxsg_t3c_live" as *u8, GT_NOW - GT_DAY) 160 gt_set_mtime("/tmp/nxsg_t3c_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 161 let c3c: i64 = sg_classify("/tmp/nxsg_t3c_live" as *u8, "/tmp/nxsg_t3c_live.new" as *u8, GT_NOW, cfg, out) 162 var t3c: i64 = 0 163 if c3c == SG_SHRINK { if sg_allows(c3c) == 0 { t3c = 1 } } 164 gv_check("T3c SHRINK with an unreadable/empty staged file is still REFUSED (ruler cannot vouch)" as *u8, t3c, ctr) 165 166 // ---- T4 CAPLOSS: NEWER *and* BIGGER, yet every live run is gone -> REFUSE ----------------------- 167 // mtime says upgrade. size says upgrade. Only the run ruler sees the loss -- and 7 artifacts on the 168 // live deploy root are in exactly this state. 169 gt_tokens("/tmp/nxsg_t4_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 170 gt_tokens("/tmp/nxsg_t4_live.new" as *u8, "NXSG_OTHER_CAPRUN_" as *u8, 40, 2000, none) 171 gt_set_mtime("/tmp/nxsg_t4_live" as *u8, GT_NOW - GT_DAY) 172 gt_set_mtime("/tmp/nxsg_t4_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 173 let c4: i64 = sg_classify("/tmp/nxsg_t4_live" as *u8, "/tmp/nxsg_t4_live.new" as *u8, GT_NOW, cfg, out) 174 var t4: i64 = 0 175 if c4 == SG_CAPLOSS { if sg_allows(c4) == 0 { t4 = 1 } } 176 if out[SG_O_NEWSZ] <= out[SG_O_LIVESZ] { t4 = 0 } 177 gv_check("T4 CAPLOSS refused THOUGH NEWER AND BIGGER (size+mtime both say upgrade)" as *u8, t4, ctr) 178 gv_puts(" live=" as *u8); gv_num(out[SG_O_LIVESZ]) 179 gv_puts("b staged=" as *u8); gv_num(out[SG_O_NEWSZ]) 180 gv_puts("b caploss_permil=" as *u8); gv_num(out[SG_O_LOSTPERMIL]) 181 gv_puts(" absent=" as *u8); gv_num(out[SG_O_MISSING]) 182 gv_puts("/" as *u8); gv_num(out[SG_O_CHECKED]); gv_puts("\n" as *u8) 183 184 // ---- T5 ORPHAN: no live counterpart -> nothing to lose -> ALLOW -------------------------------- 185 gt_tokens("/tmp/nxsg_t5_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 186 gt_set_mtime("/tmp/nxsg_t5_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 187 let c5: i64 = sg_classify("/tmp/nxsg_t5_absent" as *u8, "/tmp/nxsg_t5_live.new" as *u8, GT_NOW, cfg, out) 188 var t5: i64 = 0 189 if c5 == SG_ORPHAN { if sg_allows(c5) == 1 { t5 = 1 } } 190 gv_check("T5 ORPHAN allowed (no live binary, no capability to lose)" as *u8, t5, ctr) 191 192 // ---- T6 NONE: nothing staged -> say NONE, never invent a refusal -------------------------------- 193 let c6: i64 = sg_classify("/tmp/nxsg_t1_live" as *u8, "/tmp/nxsg_t6_absent" as *u8, GT_NOW, cfg, out) 194 var t6: i64 = 0 195 if c6 == SG_NONE { t6 = 1 } 196 gv_check("T6 NONE when nothing is staged" as *u8, t6, ctr) 197 198 // ---- T7 EXPIRED: NEWER than live but past the window -> REFUSE --------------------------------- 199 gt_tokens("/tmp/nxsg_t7_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 200 gt_tokens("/tmp/nxsg_t7_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 500, none) 201 gt_set_mtime("/tmp/nxsg_t7_live" as *u8, GT_NOW - (40 * GT_DAY)) 202 gt_set_mtime("/tmp/nxsg_t7_live.new" as *u8, GT_NOW - (30 * GT_DAY)) 203 let c7: i64 = sg_classify("/tmp/nxsg_t7_live" as *u8, "/tmp/nxsg_t7_live.new" as *u8, GT_NOW, cfg, out) 204 var t7: i64 = 0 205 if c7 == SG_EXPIRED { if sg_allows(c7) == 0 { t7 = 1 } } 206 gv_check("T7 EXPIRED refused (newer than live, but past the staging window)" as *u8, t7, ctr) 207 208 // ---- T8 BITE PROOF: 6 changed runs must FIRE; the identical fixture must stay SILENT ----------- 209 gt_tokens("/tmp/nxsg_t8_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 210 gt_tokens("/tmp/nxsg_t8_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 500, odd6) 211 gt_set_mtime("/tmp/nxsg_t8_live" as *u8, GT_NOW - GT_DAY) 212 gt_set_mtime("/tmp/nxsg_t8_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 213 let c8: i64 = sg_classify("/tmp/nxsg_t8_live" as *u8, "/tmp/nxsg_t8_live.new" as *u8, GT_NOW, cfg, out) 214 var bad_fires: i64 = 0 215 if c8 == SG_CAPLOSS { bad_fires = 1 } 216 var good_fires: i64 = 0 217 if c1 == SG_CAPLOSS { good_fires = 1 } 218 gv_bite("T8 caploss detector (6 changed runs vs identical)" as *u8, bad_fires, good_fires, ctr) 219 220 // ---- T11 THE ANTI-BYPASS TOOTH: ONE changed run is an ORDINARY EDIT and must be ALLOWED -------- 221 // Born from a live false positive: the first tuning (10 permil, no absolute bar) scored a one-line 222 // rename at 24 permil and REFUSED the build that contained it. Detection was right; policy was not. 223 // If this tooth ever goes RED the thresholds have drifted back into refusing routine work, which is 224 // how an override becomes reflex and a guard quietly stops being one. 225 gt_tokens("/tmp/nxsg_t11_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 0, none) 226 gt_tokens("/tmp/nxsg_t11_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 40, 500, odd1) 227 gt_set_mtime("/tmp/nxsg_t11_live" as *u8, GT_NOW - GT_DAY) 228 gt_set_mtime("/tmp/nxsg_t11_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 229 let c11: i64 = sg_classify("/tmp/nxsg_t11_live" as *u8, "/tmp/nxsg_t11_live.new" as *u8, GT_NOW, cfg, out) 230 var t11: i64 = 0 231 if sg_allows(c11) == 1 { t11 = 1 } 232 gv_check("T11 ANTI-BYPASS: a ONE-run ordinary edit is ALLOWED, not refused" as *u8, t11, ctr) 233 gv_puts(" class=" as *u8); gv_puts(sg_class_name(c11)) 234 gv_puts(" caploss_permil=" as *u8); gv_num(out[SG_O_LOSTPERMIL]) 235 gv_puts(" absent=" as *u8); gv_num(out[SG_O_MISSING]) 236 gv_puts(" (bars: " as *u8); gv_num(cfg[SG_C_CAPLOSS]) 237 gv_puts("permil OR " as *u8); gv_num(cfg[SG_C_CAPLOSSABS]); gv_puts(" absent)\n" as *u8) 238 239 // ---- T12 SAME SIZE, DIFFERENT CONTENT is DETECTED as such ------------------------------------- 240 // The mutation-class blind spot, made visible. An ==/!= operator swap preserves the byte count AND 241 // every printable run, so nothing else in this guard can see it; SG_O_IDENTICAL is the only field 242 // that separates "a harmless re-stage of the same bytes" from "something changed that I cannot see". 243 var t12: i64 = 0 244 if out[SG_O_IDENTICAL] == 0 { 245 let c12: i64 = sg_classify("/tmp/nxsg_t1_live" as *u8, "/tmp/nxsg_t1_live" as *u8, GT_NOW, cfg, out) 246 if out[SG_O_IDENTICAL] == 1 { if c12 == SG_FORWARD { t12 = 1 } } 247 } 248 gv_check("T12 byte-identity distinguishes a re-stage from an unseen change" as *u8, t12, ctr) 249 250 // ---- T15 RENAME must be ALLOWED / T16 REMOVAL must still be REFUSED --------------------------- 251 // The exact shape that refused this guard's own author three times: 3 tokens absent out of 21 = 252 // 142 permil, over the 50-permil bar and over the 2-absent floor -- but the 3 were RENAMED, not 253 // removed. T15 reproduces it (21 tokens, first 3 renamed: lost 3, GAINED 3, run count unchanged). 254 // T16 is its BITE: the same 3-token loss with NOTHING added and the run count DOWN must still be 255 // refused, so the escape can never be mistaken for a hole. A tooth that only proves the escape 256 // fires would be exactly the vacuity this session found in _mmu_gate -- the pair is the point. 257 gt_tokens("/tmp/nxsg_t15_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 21, 0, none) 258 gt_tokens("/tmp/nxsg_t15_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 21, 400, odd3) 259 gt_set_mtime("/tmp/nxsg_t15_live" as *u8, GT_NOW - GT_DAY) 260 gt_set_mtime("/tmp/nxsg_t15_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 261 let c15: i64 = sg_classify("/tmp/nxsg_t15_live" as *u8, "/tmp/nxsg_t15_live.new" as *u8, GT_NOW, cfg, out) 262 var t15: i64 = 0 263 if sg_allows(c15) == 1 { t15 = 1 } 264 gv_check("T15 RENAME allowed: 3 of 21 runs absent (142 permil) but 3 gained and run count held" as *u8, t15, ctr) 265 gv_puts(" class=" as *u8); gv_puts(sg_class_name(c15)) 266 gv_puts(" permil=" as *u8); gv_num(out[SG_O_LOSTPERMIL]) 267 gv_puts(" absent=" as *u8); gv_num(out[SG_O_MISSING]) 268 gv_puts(" gained=" as *u8); gv_num(out[SG_O_GAINED]) 269 gv_puts(" runs " as *u8); gv_num(out[SG_O_LIVERUNS]) 270 gv_puts("->" as *u8); gv_num(out[SG_O_CANDRUNS]); gv_puts("\ 271" as *u8) 272 273 gt_tokens("/tmp/nxsg_t16_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 21, 0, none) 274 gt_tokens("/tmp/nxsg_t16_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 18, 900, none) 275 gt_set_mtime("/tmp/nxsg_t16_live" as *u8, GT_NOW - GT_DAY) 276 gt_set_mtime("/tmp/nxsg_t16_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 277 let c16: i64 = sg_classify("/tmp/nxsg_t16_live" as *u8, "/tmp/nxsg_t16_live.new" as *u8, GT_NOW, cfg, out) 278 var t16: i64 = 0 279 if sg_allows(c16) == 0 { t16 = 1 } 280 gv_check("T16 BITE: the same 3-run loss with NOTHING gained and the count DOWN is still REFUSED" as *u8, t16, ctr) 281 gv_puts(" class=" as *u8); gv_puts(sg_class_name(c16)) 282 gv_puts(" permil=" as *u8); gv_num(out[SG_O_LOSTPERMIL]) 283 gv_puts(" absent=" as *u8); gv_num(out[SG_O_MISSING]) 284 gv_puts(" gained=" as *u8); gv_num(out[SG_O_GAINED]) 285 gv_puts(" runs " as *u8); gv_num(out[SG_O_LIVERUNS]) 286 gv_puts("->" as *u8); gv_num(out[SG_O_CANDRUNS]); gv_puts("\ 287" as *u8) 288 289 // ---- T14 SMALL-BINARY ANTI-BYPASS: one edit in an EIGHT-run binary must be ALLOWED ------------ 290 // T11 proves a one-run edit is allowed in a 40-run fixture (25 permil). It did NOT catch the real 291 // false positive, because the victim had only EIGHT sampled runs, where ONE edit is 125 permil -- 292 // over the 50-permil bar. THE TOOTH WAS RIGHT AND THE FIXTURE WAS TOO BIG. This one reproduces the 293 // actual shape that refused nx_offc_install, so the size-dependence cannot come back unnoticed. 294 gt_tokens("/tmp/nxsg_t14_live" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 8, 0, none) 295 gt_tokens("/tmp/nxsg_t14_live.new" as *u8, "NXSG_LIVE_CAPRUN_" as *u8, 8, 400, odd1) 296 gt_set_mtime("/tmp/nxsg_t14_live" as *u8, GT_NOW - GT_DAY) 297 gt_set_mtime("/tmp/nxsg_t14_live.new" as *u8, GT_NOW - (GT_DAY / 2)) 298 let c14: i64 = sg_classify("/tmp/nxsg_t14_live" as *u8, "/tmp/nxsg_t14_live.new" as *u8, GT_NOW, cfg, out) 299 var t14: i64 = 0 300 if sg_allows(c14) == 1 { t14 = 1 } 301 gv_check("T14 SMALL-BINARY anti-bypass: 1 absent run of 8 (125 permil) is an ordinary edit, ALLOWED" as *u8, t14, ctr) 302 gv_puts(" class=" as *u8); gv_puts(sg_class_name(c14)) 303 gv_puts(" caploss_permil=" as *u8); gv_num(out[SG_O_LOSTPERMIL]) 304 gv_puts(" absent=" as *u8); gv_num(out[SG_O_MISSING]) 305 gv_puts("/" as *u8); gv_num(out[SG_O_CHECKED]) 306 gv_puts(" (permil bar needs >=" as *u8); gv_num(cfg[SG_C_CAPLOSSMIN]); gv_puts(" absent to fire)\n" as *u8) 307 308 // ---- T13 THE REFERENCE PREDICATE, BITE-PROVEN -------------------------------------------------- 309 // `expire apply` RENAMES staged artifacts, and tool_allowlist.conf can point a REGISTERED tool at a 310 // <name>.sov.elf.new -- 45 rows do. Renaming one turns a live tool into exit 127 NOT-FOUND, so expiry 311 // must PROVE UNREFERENCED before it MOVES. This is exactly the predicate that path calls: it must FIRE 312 // on a name the registry references and stay SILENT on one it does not. Measured on the live registry 313 // the same day: 68,391 bytes read, 70 artifacts expiry-eligible, 0 of them referenced -- but a zero 314 // measured in the field proves the SILENT direction only, which is why the FIRING direction is proven 315 // here on a fixture instead of being assumed from a happy number. 316 let ab: *u8 = sys_mmap(4096) 317 let abn: i64 = gt_fill(ab, "nx_alpha\t/x/nx_alpha.elf\tGREEN\nnx_beta\t/x/nx_beta.sov.elf.new\tGREEN\n" as *u8) 318 let refhit: i64 = sg_contains(ab, abn, "nx_beta.sov.elf.new" as *u8, 19) 319 let refmiss: i64 = sg_contains(ab, abn, "nx_gamma.sov.elf.new" as *u8, 20) 320 gv_bite("T13 reference predicate (registry-referenced vs not)" as *u8, refhit, refmiss, ctr) 321 322 // ---- T9 the conf file is ACTUALLY read (not decorative) ---------------------------------------- 323 // knowledge/staging_hygiene.conf sets caploss_refuse_permil=50 while the bootstrap default is 25, 324 // so reading 50 back can ONLY happen if the file was opened and parsed. 325 let lcfg: *i64 = sys_mmap(8 * SG_C_SLOTS) as *i64 326 let hadconf: i64 = sg_conf_load(lcfg) 327 var t9: i64 = 0 328 if hadconf == 1 { if lcfg[SG_C_CAPLOSS] == 50 { t9 = 1 } } 329 gv_check("T9 knowledge/staging_hygiene.conf is read (caploss 50 overrides default 25)" as *u8, t9, ctr) 330 331 // ---- T10 sg_allows is fail-closed for anything it does not recognise --------------------------- 332 var t10: i64 = 0 333 if sg_allows(SG_FORWARD) == 1 { 334 if sg_allows(SG_ORPHAN) == 1 { 335 if sg_allows(SG_BACKDATE) == 0 { 336 if sg_allows(SG_EXPIRED) == 0 { 337 if sg_allows(SG_SHRINK) == 0 { 338 if sg_allows(SG_CAPLOSS) == 0 { 339 if sg_allows(9999) == 0 { t10 = 1 } 340 } 341 } 342 } 343 } 344 } 345 } 346 gv_check("T10 sg_allows fail-closed (unknown class REFUSED)" as *u8, t10, ctr) 347 348 let rc: i64 = gv_verdict("STAGHYG-GATE" as *u8, ctr, "refuses backwards promotes, allows forward ones (including the DCE-shape shrink the token ruler vouches for), ignores ordinary edits; per-tooth lines above are the record" as *u8) 349 sys_exit(rc) 350 return rc 351}