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}