code wiki / _hdl_build / nx_treecanon_gate.nx

nx_treecanon_gate.nx source

↩ module page · 288 lines · 11911 B

1// nx_treecanon_gate.nx -- THE DIVERGENCE GATE THAT REFUSES (debt 1785622769, operator directive 2// 2026-08-01: "ONE canonical tree per generation with a lineage manifest ... a divergence gate that 3// REFUSES a build when the deploying tree's copy diverges from canonical"). 4// 5// MEASURED 2026-08-03, and it is why this exists: NAS buildroot vs laptop nxc2 diverge on 6// 8,258 files -- 3,705 NAS-newer, 1,838 laptop-newer, 739 NAS-only, 1,976 laptop-only. NEITHER 7// TREE IS A SUPERSET IN EITHER DIRECTION. A name-level diff saw only 969 of those, because the 8// dangerous class is files present in BOTH with DIFFERENT BYTES (5,543 of them). 9// ⇒ ★A NAME-LEVEL TREE DIFF IS NOT A DIVERGENCE CHECK. It cannot see the failure that actually 10// bites -- editing a file in one tree and compiling the other copy, which is precisely how the 11// unfreed-mmap fix had to be applied twice (the incident that opened this debt). 12// 13// SCOPE, DELIBERATE AND STATED: this gate does NOT try to reconcile the fork. Blocking on all 14// 8,258 would block every build, and mass-merging on a size screen would be DESTRUCTIVE -- byte 15// size is a SCREEN, not proof of newer (banked law: newer mtime + FEWER bytes = REVERT). It 16// enforces a CANON LIST from knowledge/tree_canon.conf (rule 11: the policy is DATA), starting at 17// the crown jewels and growing by measurement. Stopping the bleeding beats an unfinished cleanup. 18// 19// T0 LOADED both manifests parse and index (non-vacuity floor on the inputs) 20// T1 CANON READ the canon list loads and is non-empty (an empty list would pass vacuously) 21// T2 CANON CLEAN every canon path is present in BOTH trees and byte-identical -- REFUSES otherwise 22// T3 BITE the detector fires on a REAL divergent path and stays silent on a REAL identical 23// one, both discovered from the manifests at runtime (no hardcoded fixture) 24// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 25import "nx_syscalls.nx" 26import "nx_gate_verdict.nx" 27 28const TC_HBUCKETS: i64 = 65536 29const TC_MAXENT: i64 = 200000 30const TC_BUFCAP: i64 = 16777216 31const TC_CONFCAP: i64 = 262144 32const TC_HASH_SEED: i64 = 5381 33const TC_SPACE: i64 = 32 34const TC_NL: i64 = 10 35const TC_CR: i64 = 13 36const TC_HASHCH: i64 = 35 37const TC_BANG: i64 = 33 // '!' row prefix: the BUILD LANE freezes every build while that row 38 // diverges (nx_sov_build_run tree-canon admission); for THIS gate the 39 // identity check is the same either way, so the marker is stripped 40const TC_D0: i64 = 48 41const TC_D9: i64 = 57 42 43// two independent indexes: A (canonical/NAS) and B (the tree being compared) 44static tc_ha: *i64 = 0 as *i64 45static tc_hb: *i64 = 0 as *i64 46static tc_ka: *i64 = 0 as *i64 47static tc_kb: *i64 = 0 as *i64 48static tc_sa: *i64 = 0 as *i64 49static tc_sb: *i64 = 0 as *i64 50static tc_na: i64 = 0 51static tc_nb: i64 = 0 52 53func tc_readall(path: *u8, outn: *i64, cap: i64) -> *u8 { 54 let fd: i64 = sys_openat_rd(path) 55 if fd < 0 { outn[0] = 0 - 1; return 0 as *u8 } 56 let buf: *u8 = sys_mmap(cap) 57 var tot: i64 = 0 58 var go: i64 = 1 59 while go == 1 { 60 let r: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot - 1) 61 if r <= 0 { go = 0 } else { tot = tot + r } 62 if tot >= cap - 1 { go = 0 } 63 } 64 sys_close(fd) 65 buf[tot] = 0 as u8 66 outn[0] = tot 67 return buf 68} 69 70func tc_hash(s: *u8) -> i64 { 71 var h: i64 = TC_HASH_SEED 72 var i: i64 = 0 73 while s[i] != (0 as u8) { h = ((h * 33) + (s[i] as i64)) & 0x7fffffff; i = i + 1 } 74 return h & (TC_HBUCKETS - 1) 75} 76func tc_streq(a: *u8, b: *u8) -> i64 { 77 var i: i64 = 0 78 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 79 if b[i] != (0 as u8) { return 0 } 80 return 1 81} 82 83// parse "<bytes> <relpath>" in place; NUL-terminates the path at `end` 84func tc_parse(buf: *u8, start: i64, end: i64, szout: *i64) -> *u8 { 85 var i: i64 = start 86 var v: i64 = 0 87 var any: i64 = 0 88 var go: i64 = 1 89 while go == 1 { 90 if i >= end { go = 0 } else { 91 let c: i64 = buf[i] as i64 92 if c >= TC_D0 { if c <= TC_D9 { v = v * 10 + (c - TC_D0); any = 1; i = i + 1 } else { go = 0 } } else { go = 0 } 93 } 94 } 95 if any == 0 { return 0 as *u8 } 96 if i >= end { return 0 as *u8 } 97 if buf[i] != (TC_SPACE as u8) { return 0 as *u8 } 98 let p: i64 = i + 1 99 if p >= end { return 0 as *u8 } 100 buf[end] = 0 as u8 101 szout[0] = v 102 return ((buf as i64) + p) as *u8 103} 104 105// index one manifest into (htab, keys, sizes); returns row count 106func tc_index(buf: *u8, n: i64, htab: *i64, keys: *i64, sizes: *i64) -> i64 { 107 var i: i64 = 0 108 var ls: i64 = 0 109 var cnt: i64 = 0 110 let szbox: *i64 = sys_mmap(8) as *i64 111 while i <= n { 112 var eol: i64 = 0 113 if i == n { eol = 1 } 114 if i < n { if buf[i] == (TC_NL as u8) { eol = 1 } } 115 if eol == 1 { 116 var e: i64 = i 117 if e > ls { if buf[e-1] == (TC_CR as u8) { e = e - 1 } } 118 if e > ls { 119 let rel: *u8 = tc_parse(buf, ls, e, szbox) 120 if rel != (0 as *u8) { 121 if cnt < TC_MAXENT { 122 keys[cnt] = rel as i64 123 sizes[cnt] = szbox[0] 124 var h: i64 = tc_hash(rel) 125 var guard: i64 = 0 126 while guard < TC_HBUCKETS { 127 if htab[h] == 0 { htab[h] = cnt + 1; guard = TC_HBUCKETS } else { 128 h = (h + 1) & (TC_HBUCKETS - 1) 129 guard = guard + 1 130 } 131 } 132 cnt = cnt + 1 133 } 134 } 135 } 136 ls = i + 1 137 } 138 i = i + 1 139 } 140 return cnt 141} 142 143// look up a path; returns size, or -1 if absent 144func tc_lookup(rel: *u8, htab: *i64, keys: *i64, sizes: *i64) -> i64 { 145 var h: i64 = tc_hash(rel) 146 var guard: i64 = 0 147 while guard < TC_HBUCKETS { 148 let slot: i64 = htab[h] 149 if slot == 0 { return 0 - 1 } 150 let idx: i64 = slot - 1 151 if tc_streq(rel, keys[idx] as *u8) == 1 { return sizes[idx] } 152 h = (h + 1) & (TC_HBUCKETS - 1) 153 guard = guard + 1 154 } 155 return 0 - 1 156} 157 158// THE DETECTOR: 1 = this path DIVERGES (absent from either tree, or different bytes) 159func tc_diverges(rel: *u8) -> i64 { 160 let a: i64 = tc_lookup(rel, tc_ha, tc_ka, tc_sa) 161 let b: i64 = tc_lookup(rel, tc_hb, tc_kb, tc_sb) 162 if a < 0 { return 1 } 163 if b < 0 { return 1 } 164 if a != b { return 1 } 165 return 0 166} 167 168func main(argc: i64, argv: *i64) -> i64 { 169 let ctr: *i64 = gv_ctr() 170 gv_head("nx_treecanon gate -- a build must never compile a copy that diverges from canon" as *u8) 171 if argc < 4 { 172 gv_puts("usage: nx_treecanon_gate <manifestCANON> <manifestOTHER> <canon.conf>\n" as *u8) 173 gv_puts(" manifests come from: nx_treediff <dir> <outfile>\n" as *u8) 174 return 2 175 } 176 let pa: *u8 = argv[1] as *u8 177 let pb: *u8 = argv[2] as *u8 178 let pc: *u8 = argv[3] as *u8 179 180 tc_ha = sys_mmap(TC_HBUCKETS*8) as *i64 181 tc_hb = sys_mmap(TC_HBUCKETS*8) as *i64 182 tc_ka = sys_mmap(TC_MAXENT*8) as *i64 183 tc_kb = sys_mmap(TC_MAXENT*8) as *i64 184 tc_sa = sys_mmap(TC_MAXENT*8) as *i64 185 tc_sb = sys_mmap(TC_MAXENT*8) as *i64 186 187 let nbox: *i64 = sys_mmap(8) as *i64 188 let ba: *u8 = tc_readall(pa, nbox, TC_BUFCAP) 189 let la: i64 = nbox[0] 190 let bb: *u8 = tc_readall(pb, nbox, TC_BUFCAP) 191 let lb: i64 = nbox[0] 192 if la > 0 { tc_na = tc_index(ba, la, tc_ha, tc_ka, tc_sa) } 193 if lb > 0 { tc_nb = tc_index(bb, lb, tc_hb, tc_kb, tc_sb) } 194 gv_puts(" measured: canon tree rows=" as *u8); gv_num(tc_na) 195 gv_puts(" other tree rows=" as *u8); gv_num(tc_nb); gv_puts("\n" as *u8) 196 var t0: i64 = 0 197 if tc_na > 0 { if tc_nb > 0 { t0 = 1 } } 198 gv_check("T0 LOADED both manifests parsed and indexed" as *u8, t0, ctr) 199 200 // ---- global divergence tally: context, NOT the blocking check ---- 201 var div: i64 = 0 202 var same: i64 = 0 203 var i: i64 = 0 204 while i < tc_na { 205 let rel: *u8 = tc_ka[i] as *u8 206 let b2: i64 = tc_lookup(rel, tc_hb, tc_kb, tc_sb) 207 if b2 < 0 { div = div + 1 } else { 208 if b2 != tc_sa[i] { div = div + 1 } else { same = same + 1 } 209 } 210 i = i + 1 211 } 212 gv_puts(" measured: of the canon tree's rows, " as *u8); gv_num(div) 213 gv_puts(" diverge and " as *u8); gv_num(same) 214 gv_puts(" match in the other tree (CONTEXT ONLY -- not blocking)\n" as *u8) 215 216 // ---- the canon list ---- 217 let cb: *u8 = tc_readall(pc, nbox, TC_CONFCAP) 218 let lc: i64 = nbox[0] 219 var listed: i64 = 0 220 var bad: i64 = 0 221 if lc > 0 { 222 var p: i64 = 0 223 var ls: i64 = 0 224 while p <= lc { 225 var eol: i64 = 0 226 if p == lc { eol = 1 } 227 if p < lc { if cb[p] == (TC_NL as u8) { eol = 1 } } 228 if eol == 1 { 229 var e: i64 = p 230 if e > ls { if cb[e-1] == (TC_CR as u8) { e = e - 1 } } 231 if e > ls { 232 if cb[ls] != (TC_HASHCH as u8) { 233 cb[e] = 0 as u8 234 var rs: i64 = ls 235 if cb[rs] == (TC_BANG as u8) { rs = rs + 1 } 236 if rs < e { 237 let rel: *u8 = ((cb as i64) + rs) as *u8 238 listed = listed + 1 239 if tc_diverges(rel) == 1 { 240 bad = bad + 1 241 gv_puts(" DIVERGENT CANON: " as *u8); gv_puts(rel) 242 gv_puts(" canon=" as *u8); gv_num(tc_lookup(rel, tc_ha, tc_ka, tc_sa)) 243 gv_puts(" other=" as *u8); gv_num(tc_lookup(rel, tc_hb, tc_kb, tc_sb)) 244 gv_puts("\n" as *u8) 245 } 246 } 247 } 248 } 249 ls = p + 1 250 } 251 p = p + 1 252 } 253 } 254 gv_puts(" measured: canon paths listed=" as *u8); gv_num(listed) 255 gv_puts(" divergent=" as *u8); gv_num(bad); gv_puts("\n" as *u8) 256 var t1: i64 = 0 257 if listed > 0 { t1 = 1 } 258 gv_check("T1 CANON READ the canon list loaded and is non-empty (an empty list passes vacuously)" as *u8, t1, ctr) 259 var t2: i64 = 0 260 if bad == 0 { t2 = 1 } 261 gv_check("T2 CANON CLEAN every canon path present in BOTH trees and byte-identical" as *u8, t2, ctr) 262 263 // ---- T3 BITE: calibrate the detector on REAL rows found at runtime, no hardcoded fixture ---- 264 // A gate whose detector cannot fire is a voter with information content zero (council rule). 265 var badpath: *u8 = 0 as *u8 266 var goodpath: *u8 = 0 as *u8 267 i = 0 268 while i < tc_na { 269 let rel: *u8 = tc_ka[i] as *u8 270 let b3: i64 = tc_lookup(rel, tc_hb, tc_kb, tc_sb) 271 if b3 >= 0 { 272 if b3 != tc_sa[i] { if badpath == (0 as *u8) { badpath = rel } } 273 if b3 == tc_sa[i] { if goodpath == (0 as *u8) { goodpath = rel } } 274 } 275 i = i + 1 276 } 277 var fires_bad: i64 = 0 278 var fires_good: i64 = 1 279 if badpath != (0 as *u8) { fires_bad = tc_diverges(badpath) } 280 if goodpath != (0 as *u8) { fires_good = tc_diverges(goodpath) } 281 if badpath != (0 as *u8) { gv_puts(" bite fixture DIVERGENT: " as *u8); gv_puts(badpath); gv_puts("\n" as *u8) } 282 if goodpath != (0 as *u8) { gv_puts(" bite fixture IDENTICAL: " as *u8); gv_puts(goodpath); gv_puts("\n" as *u8) } 283 gv_bite("T3 BITE divergence detector fires on a real divergent path, silent on a real identical one" as *u8, fires_bad, fires_good, ctr) 284 285 let rc: i64 = gv_verdict("TREECANON-GATE" as *u8, ctr, 286 "canon paths are byte-identical across both trees; a build compiling a forked copy is refused" as *u8) 287 return rc 288}