nx_hashdiverge.nx source
↩ module page · 326 lines · 13728 B
1// nx_hashdiverge.nx -- CONTENT-EXACT CROSS-TREE DIVERGENCE. The comparer half of nx_treehash.
2//
3// WHY IT EXISTS (measured 2026-08-06, tree-canon-single-writer lane):
4// nx_treediverge compares nx_treediff manifests, which carry SIZE. Its own usage line says the
5// verdicts are "(by BYTES, a FLOOR)" and it is right. The hourly treediverge beat therefore
6// reported a size floor as if it were the fork, for days, and one of the files it could not see
7// was an ESCAPED nx_gate_bite MUTANT in buildroot: _hdl_build/nx_media_extract.nx held
8// `if hit != 0` where the SSOT held `if hit == 0` -- one byte, 4389 bytes on BOTH sides, so every
9// plain-http: URL was silently dropped from media harvest and no size gauge could ever notice.
10// => A MUTATION-CLASS DEFECT IS SIZE-PRESERVING BY CONSTRUCTION. A size screen is not merely
11// approximate against it, it is STRUCTURALLY BLIND to the whole class. Of 18060 files present in
12// both trees, only 5 differed at equal size -- and 1 of those 5 was the mutant.
13//
14// TWO IMPROVEMENTS OVER THE ORGAN IT SUCCEEDS, both learned by being bitten in the same session:
15// 1. DETAIL GOES TO A FILE, ENVELOPE GOES TO STDOUT. Running nx_treediverge over these trees
16// returned 163,936 characters and was cut off at capture_cap mid-row, so the envelope -- the
17// only line carrying the counts -- was the part that got dropped. A comparer whose SUMMARY is
18// the first casualty of its own verbosity cannot be wired to a beat. Rows go to [outfile].
19// 2. THE SAME-SIZE CLASS IS COUNTED SEPARATELY. It is the only class a size screen cannot reach,
20// so it is the number that says whether the cheap gauge upstream is still trustworthy.
21//
22// CLASSES: IDENT (sha equal) / DIFFSZ (sha differs, size differs -- a size screen sees these) /
23// SAMESZ (sha differs, size EQUAL -- the blind spot) / A-ONLY / B-ONLY.
24// An UNREADABLE row from nx_treehash is 64 '-', which is not hex and never equals itself, so an
25// unreadable file always classifies as divergent rather than silently matching another unreadable.
26//
27// DIALECT: plain-if, <=6 params, consts above use. Tables are mmap'd through static POINTERS --
28// a BSS static ARRAY silently crashes the module at startup (banked gotcha).
29// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26).
30import "nx_syscalls.nx"
31
32const HD_SLOTS: i64 = 65536 // power of two; ~18k rows = 28% load
33const HD_MASK: i64 = 65535
34const HD_SHA_CHARS: i64 = 64
35const HD_OUTBUF: i64 = 8388608
36const HD_NUMBUF: i64 = 64
37const HD_FNV_OFF: i64 = 1469598103934665603
38const HD_FNV_PRIME: i64 = 1099511628211
39const HD_SAMPLE_CAP: i64 = 40 // stdout sample rows; the FULL list always goes to [outfile]
40
41static hd_key: *i64 // rel pointer per slot (0 = empty)
42static hd_sha: *i64
43static hd_sz: *i64
44static hd_seen: *i64
45static hd_c: *i64 // [0]=ident [1]=samesz [2]=diffsz [3]=aonly [4]=bonly [5]=arows [6]=brows
46static hd_out: *u8
47static hd_out_n: *i64
48static hd_num: *u8
49static hd_rev: *u8
50static hd_shown: *i64
51
52func hd_puts(s: *u8) -> i64 {
53 var n: i64 = 0
54 while s[n] != (0 as u8) { n = n + 1 }
55 sys_write(1, s, n)
56 return 0
57}
58func hd_putn(v: i64) -> i64 {
59 var m: i64 = v
60 if m == 0 { hd_num[0] = 48 as u8; sys_write(1, hd_num, 1); return 0 }
61 if m < 0 { hd_puts("-" as *u8); m = 0 - m }
62 var k: i64 = 0
63 while m > 0 { hd_rev[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
64 var i: i64 = 0
65 while i < k { hd_num[i] = hd_rev[k - 1 - i]; i = i + 1 }
66 sys_write(1, hd_num, k)
67 return 0
68}
69func hd_slen(s: *u8) -> i64 {
70 var n: i64 = 0
71 while s[n] != (0 as u8) { n = n + 1 }
72 return n
73}
74func hd_streq(a: *u8, b: *u8) -> i64 {
75 var i: i64 = 0
76 var go: i64 = 1
77 var eq: i64 = 1
78 while go == 1 {
79 if a[i] != b[i] { eq = 0; go = 0 } else {
80 if a[i] == (0 as u8) { go = 0 } else { i = i + 1 }
81 }
82 }
83 return eq
84}
85// FNV-1a over the relpath.
86func hd_hash(s: *u8) -> i64 {
87 var h: i64 = HD_FNV_OFF
88 var i: i64 = 0
89 while s[i] != (0 as u8) {
90 h = h ^ (s[i] as i64)
91 h = h * HD_FNV_PRIME
92 i = i + 1
93 }
94 if h < 0 { h = 0 - h }
95 return h
96}
97// 64-char sha compare (fixed width, so no strlen walk).
98func hd_shaeq(a: *u8, b: *u8) -> i64 {
99 var i: i64 = 0
100 while i < HD_SHA_CHARS { if a[i] != b[i] { return 0 } i = i + 1 }
101 return 1
102}
103
104func hd_put(rel: *u8, sha: *u8, sz: *u8) -> i64 {
105 var s: i64 = hd_hash(rel) & HD_MASK
106 var go: i64 = 1
107 while go == 1 {
108 if hd_key[s] == 0 {
109 hd_key[s] = rel as i64
110 hd_sha[s] = sha as i64
111 hd_sz[s] = sz as i64
112 hd_seen[s] = 0
113 go = 0
114 } else {
115 if hd_streq(hd_key[s] as *u8, rel) == 1 { go = 0 } else { s = (s + 1) & HD_MASK }
116 }
117 }
118 return 0
119}
120// returns slot index, or -1 when absent
121func hd_find(rel: *u8) -> i64 {
122 var s: i64 = hd_hash(rel) & HD_MASK
123 var go: i64 = 1
124 while go == 1 {
125 if hd_key[s] == 0 { return 0 - 1 }
126 if hd_streq(hd_key[s] as *u8, rel) == 1 { return s }
127 s = (s + 1) & HD_MASK
128 }
129 return 0 - 1
130}
131
132func hd_emit(cls: *u8, rel: *u8, x: *u8, y: *u8) -> i64 {
133 var o: i64 = hd_out_n[0]
134 let need: i64 = 8 + hd_slen(rel) + 1 + HD_SHA_CHARS + 1 + HD_SHA_CHARS + 4
135 if o + need >= HD_OUTBUF { return 0 }
136 var i: i64 = 0
137 while cls[i] != (0 as u8) { hd_out[o] = cls[i]; o = o + 1; i = i + 1 }
138 hd_out[o] = 32 as u8; o = o + 1
139 i = 0
140 while rel[i] != (0 as u8) { hd_out[o] = rel[i]; o = o + 1; i = i + 1 }
141 if (x as i64) != 0 {
142 hd_out[o] = 32 as u8; o = o + 1
143 i = 0
144 while i < HD_SHA_CHARS { hd_out[o] = x[i]; o = o + 1; i = i + 1 }
145 }
146 if (y as i64) != 0 {
147 hd_out[o] = 32 as u8; o = o + 1
148 i = 0
149 while i < HD_SHA_CHARS { hd_out[o] = y[i]; o = o + 1; i = i + 1 }
150 }
151 hd_out[o] = 10 as u8; o = o + 1
152 hd_out_n[0] = o
153 return 0
154}
155// SAMESZ is the class a size screen cannot reach, so it is the one worth printing to stdout.
156func hd_sample(cls: *u8, rel: *u8) -> i64 {
157 if hd_shown[0] >= HD_SAMPLE_CAP { return 0 }
158 hd_puts(" " as *u8); hd_puts(cls); hd_puts(" " as *u8); hd_puts(rel); hd_puts("\n" as *u8)
159 hd_shown[0] = hd_shown[0] + 1
160 return 0
161}
162
163// Parse a treehash manifest IN PLACE: rows are "<64hex> <bytes> <relpath>\n". Terminates each field
164// by overwriting its separator, then calls hd_put (side A) or classifies (side B).
165// side 0 = load into the table, side 1 = stream and classify.
166func hd_scan(buf: *u8, n: i64, side: i64) -> i64 {
167 var p: i64 = 0
168 while p < n {
169 let base: i64 = buf as i64
170 let sha: *u8 = (base + p) as *u8
171 if p + HD_SHA_CHARS + 2 > n { return 0 }
172 buf[p + HD_SHA_CHARS] = 0 as u8
173 var q: i64 = p + HD_SHA_CHARS + 1
174 let szs: *u8 = (base + q) as *u8
175 var gq: i64 = 1
176 while gq == 1 {
177 if q >= n { gq = 0 } else {
178 if buf[q] == (32 as u8) { gq = 0 } else { q = q + 1 }
179 }
180 }
181 if q >= n { return 0 }
182 buf[q] = 0 as u8
183 var r: i64 = q + 1
184 let rel: *u8 = (base + r) as *u8
185 var gr: i64 = 1
186 while gr == 1 {
187 if r >= n { gr = 0 } else {
188 if buf[r] == (10 as u8) { gr = 0 } else { r = r + 1 }
189 }
190 }
191 if r >= n { return 0 }
192 buf[r] = 0 as u8
193 if side == 0 {
194 hd_c[5] = hd_c[5] + 1
195 hd_put(rel, sha, szs)
196 }
197 if side == 1 {
198 hd_c[6] = hd_c[6] + 1
199 let s: i64 = hd_find(rel)
200 if s < 0 {
201 hd_c[4] = hd_c[4] + 1
202 hd_emit("B-ONLY" as *u8, rel, sha, 0 as *u8)
203 } else {
204 hd_seen[s] = 1
205 let asha: *u8 = hd_sha[s] as *u8
206 let asz: *u8 = hd_sz[s] as *u8
207 if hd_shaeq(asha, sha) == 1 { hd_c[0] = hd_c[0] + 1 } else {
208 if hd_streq(asz, szs) == 1 {
209 hd_c[1] = hd_c[1] + 1
210 hd_emit("SAMESZ" as *u8, rel, asha, sha)
211 hd_sample("SAMESZ" as *u8, rel)
212 } else {
213 hd_c[2] = hd_c[2] + 1
214 hd_emit("DIFFSZ" as *u8, rel, asha, sha)
215 }
216 }
217 }
218 }
219 p = r + 1
220 }
221 return 0
222}
223
224func main(argc: i64, argv: *i64) -> i64 {
225 hd_num = sys_mmap(HD_NUMBUF)
226 hd_rev = sys_mmap(HD_NUMBUF)
227 if argc < 4 {
228 hd_puts("usage: nx_hashdiverge <manifestA> <manifestB> <outfile>\n" as *u8)
229 hd_puts(" manifests come from: nx_treehash <dir> <outfile> (one per tree)\n" as *u8)
230 hd_puts(" CONTENT-exact. Classes: IDENT / SAMESZ (the size-screen blind spot) / DIFFSZ /\n" as *u8)
231 hd_puts(" A-ONLY / B-ONLY. Rows go to <outfile>; stdout carries only the envelope + a sample,\n" as *u8)
232 hd_puts(" so the counts can never be the part that a payload cap truncates.\n" as *u8)
233 sys_exit(2)
234 return 2
235 }
236 hd_key = sys_mmap(HD_SLOTS * 8) as *i64
237 hd_sha = sys_mmap(HD_SLOTS * 8) as *i64
238 hd_sz = sys_mmap(HD_SLOTS * 8) as *i64
239 hd_seen = sys_mmap(HD_SLOTS * 8) as *i64
240 hd_c = sys_mmap(128) as *i64
241 hd_out_n = sys_mmap(16) as *i64
242 hd_shown = sys_mmap(16) as *i64
243 hd_out = sys_mmap(HD_OUTBUF)
244 hd_out_n[0] = 0
245 hd_shown[0] = 0
246 var z: i64 = 0
247 while z < 8 { hd_c[z] = 0; z = z + 1 }
248
249 let la: *i64 = sys_mmap(16) as *i64
250 let ba: *u8 = sys_read_file(argv[1] as *u8, la)
251 if (ba as i64) == 0 {
252 hd_puts("# HASHDIVERGE RED -- cannot read manifestA\n" as *u8)
253 sys_exit(3); return 3
254 }
255 let lb: *i64 = sys_mmap(16) as *i64
256 let bb: *u8 = sys_read_file(argv[2] as *u8, lb)
257 if (bb as i64) == 0 {
258 hd_puts("# HASHDIVERGE RED -- cannot read manifestB\n" as *u8)
259 sys_exit(3); return 3
260 }
261 hd_puts("=== nx_hashdiverge -- CONTENT-exact cross-tree divergence ===\n" as *u8)
262 hd_scan(ba, la[0], 0)
263 hd_scan(bb, lb[0], 1)
264 // leftovers in the table were never matched by B
265 var s: i64 = 0
266 while s < HD_SLOTS {
267 if hd_key[s] != 0 {
268 if hd_seen[s] == 0 {
269 hd_c[3] = hd_c[3] + 1
270 hd_emit("A-ONLY" as *u8, hd_key[s] as *u8, hd_sha[s] as *u8, 0 as *u8)
271 }
272 }
273 s = s + 1
274 }
275 let fd: i64 = sys_openat_wr(argv[3] as *u8, 0x1a4)
276 if fd < 0 {
277 hd_puts("# HASHDIVERGE RED -- cannot open outfile\n" as *u8)
278 sys_exit(3); return 3
279 }
280 sys_write(fd, hd_out, hd_out_n[0])
281 sys_close(fd)
282 let inboth: i64 = hd_c[0] + hd_c[1] + hd_c[2]
283 let divergent: i64 = hd_c[1] + hd_c[2] + hd_c[3] + hd_c[4]
284 hd_puts("# HASHDIVERGE arows=" as *u8); hd_putn(hd_c[5])
285 hd_puts(" brows=" as *u8); hd_putn(hd_c[6])
286 hd_puts(" inboth=" as *u8); hd_putn(inboth)
287 hd_puts(" ident=" as *u8); hd_putn(hd_c[0])
288 hd_puts(" samesz=" as *u8); hd_putn(hd_c[1])
289 hd_puts(" diffsz=" as *u8); hd_putn(hd_c[2])
290 hd_puts(" aonly=" as *u8); hd_putn(hd_c[3])
291 hd_puts(" bonly=" as *u8); hd_putn(hd_c[4])
292 hd_puts(" divergent=" as *u8); hd_putn(divergent)
293 hd_puts("\n# rows written: " as *u8); hd_puts(argv[3] as *u8)
294 hd_puts(" bytes=" as *u8); hd_putn(hd_out_n[0]); hd_puts("\n" as *u8)
295 // A CONVERGENCE PROOF OVER THE EMPTY SET IS THE MOST DANGEROUS GREEN THERE IS: it is textually
296 // identical to a real proof, and it is produced by having looked at NOTHING.
297 // MEASURED 2026-08-16: nx_treehash hashes *.nx ONLY, so pointing it at a tree of .matrix/.verdict
298 // files yields a manifest with ZERO rows and its own verdict=COMPLETE. Feeding two such manifests
299 // to this organ printed "verdict=CONVERGED (every path present in both trees is byte-identical)"
300 // -- a confident no-divergence answer about two trees from which not one file was ever read. The
301 // producer was behaving correctly and so was the arithmetic (0 divergent of 0 compared); the defect
302 // is that ZERO COMPARISONS AND ZERO DISAGREEMENTS ARE INDISTINGUISHABLE IN A TWO-STATE VERDICT.
303 // AN INSTRUMENT THAT CANNOT SEE MUST ABSTAIN, NOT ACQUIT -- so empty input gets its own state.
304 // Exit 3 matches this organ's existing RED-exit convention; CONVERGED and DIVERGENT keep exit 0 so
305 // no existing caller's contract moves (rule 19). That DIVERGENT also exits 0 is a separate,
306 // pre-existing weakness -- named here rather than silently changed underneath a caller.
307 var unproven: i64 = 0
308 if hd_c[5] == 0 { unproven = 1 }
309 if hd_c[6] == 0 { unproven = 1 }
310 if unproven == 1 {
311 hd_puts("# verdict=UNPROVEN -- a manifest is EMPTY, so NOTHING was compared. This is not convergence.\n" as *u8)
312 hd_puts("# CHECK THE PRODUCER'S SCOPE FIRST: nx_treehash hashes *.nx ONLY, so a tree of .matrix,\n" as *u8)
313 hd_puts("# .conf or .verdict files legitimately yields zero rows -- the manifest is empty because\n" as *u8)
314 hd_puts("# the filter excluded everything, NOT because the two trees agree.\n" as *u8)
315 sys_exit(3)
316 return 3
317 }
318 if divergent == 0 { hd_puts("# verdict=CONVERGED (every path present in both trees is byte-identical)\n" as *u8) }
319 if divergent != 0 {
320 hd_puts("# verdict=DIVERGENT -- direction is a PER-FILE measurement, never a per-tree policy.\n" as *u8)
321 hd_puts("# SAMESZ is the class no size screen can reach: a mutation-class defect preserves size\n" as *u8)
322 hd_puts("# by construction. Body-diff every SAMESZ row before assuming which side is ahead.\n" as *u8)
323 }
324 sys_exit(0)
325 return 0
326}