code wiki / _hdl_build / nx_dup_source_check.nx

nx_dup_source_check.nx source

↩ module page · 1261 lines · 75233 B

1// nx_dup_source_check.nx v3 -- ANTI-CLOBBER cross-tree source-dup detector. 2// 3// 🚨v3 EXISTS BECAUSE v2 WAS AN OOM BOMB THAT TOOK THE NAS DOWN (2026-07-20). Root cause, stated plainly: 4// v2 widened the scan from 2 hardcoded dirs to ALL discovered source trees (78 pairs) -- a good change -- but 5// REUSED v1's `exists()` helper UNCHANGED, and that helper did `sys_mmap(4096)` PER CALL and never unmapped. 6// v1 called it ~15k times (ONE pair) = ~60MB = survivable. v2 called it ~250k times = ~1GB LEAKED PER RUN, on a 7// cron. Result: NAS userspace starved -- kernel still SYN-ACKs but sshd gives no banner, HTTPS returns status=0, 8// even DSM stops answering. 9// 10// ★THE LESSON, so it is never repeated: CHANGING A CALLER'S FAN-OUT RE-COSTS EVERY CALLEE. A 2->78 pair change 11// is a 17x amplifier on an O(files) helper. Reviewing only the code you WROTE is not enough -- you must re-cost 12// the code you INHERITED AND AMPLIFIED. And note what the v2 selftest could NOT do: it was GREEN 4/4 because 13// fixture dirs hold 2-3 files, so it tested the ALGORITHM at scale and never the RESOURCE at scale. A scale-law 14// tooth that asserts only correctness-on-big-input is HALF a tooth; the other half is a RESOURCE bound. 15// 16// v3 FIX: every path buffer is allocated ONCE PER PAIR (or once per walk) and passed down, never per file. 17// Allocation profile is now O(pairs + dirs), not O(files), and it is DECLARED in the output envelope so the 18// property is inspectable rather than assumed. Detection semantics and the emitted " DUP basename: " line are 19// UNCHANGED (rule 19) -- nx_favela_census counts that exact substring out of knowledge/status/dup_source.log. 20// expect_exit:0 when clean. 21import "nx_syscalls.nx" 22import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 23const K_MAGIC_131072: i64 = 131072 24const K_MAGIC_4096: i64 = 4096 25// ★v4 (seq207): 1MiB per side for the CONTENT compare. A source over the cap is reported UNKNOWN, 26// never silently folded into "identical" -- partial coverage presented as complete is the L011 27// self-ceiling defect. Buffers are allocated ONCE PER RUN and passed down (see main). 28const K_CMP_CAP: i64 = 1048576 29// FINGERPRINT. This detector can only see trees it can OPEN, which means NAS-side trees. A working copy 30// on a laptop is a divergent duplicate it is structurally blind to -- and that blind spot bit us: the 31// laptop copy of nx_page_verify.nx sat 6092 bytes BEHIND the buildroot canonical and was missing the 32// entire recovered auto-connect-override, so any rebuild from that tree would have silently reverted a 33// landed fix. Emitting size+sum lets an off-NAS copy detect divergence in ONE call instead of one fetch 34// per organ. 35const FP_MULT: i64 = 131 36const FP_MASK: i64 = 1099511627775 37const FP_FILECAP: i64 = 1048576 38// PAGINATION, added after the very first real run was SILENTLY TRUNCATED by the transport: 1650 rows 39// went out, the tail was cut mid-alphabet, and the summary line -- the one thing that would have 40// revealed the cut -- was itself lost because it printed LAST. The total now prints FIRST, so a clipped 41// response can never look complete. 42const FP_PAGE_DEF: i64 = 300 43const FP_PAGE_MAX: i64 = 900 44// FRESHNESS. Size and sum say two copies DIFFER; only mtime says which way to reconcile, and getting 45// that backwards destroys work. Uses the already-proven stat channel (sys_fstatat/262, see 46// _freshness_gate.nx) rather than a second mechanism. st_mtim.tv_sec is at byte offset 88 = sp[11]. 47const FP_STATBUF: i64 = 144 48const FP_MTIME_SLOT: i64 = 11 49const FP_TEST_MTIME: i64 = 1750000000 50 51func dp_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 52// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 53// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 54// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 55// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 56func dp_wn(v: i64) -> i64 { nxi_out(v); return 0 } 57 58func ends_nx(nm: *u8) -> i64 { 59 var n: i64=0; while nm[n]!=(0 as u8){n=n+1} 60 if n < 3 { return 0 } 61 if nm[n-3]==(46 as u8) { if nm[n-2]==(110 as u8) { if nm[n-1]==(120 as u8) { return 1 } } } 62 return 0 63} 64 65func join_path(buf: *u8, dir: *u8, name: *u8) -> i64 { 66 var o: i64=0; var i: i64=0 67 while dir[i]!=(0 as u8) { buf[o]=dir[i]; o=o+1; i=i+1 } 68 buf[o]=47 as u8; o=o+1 69 i=0; while name[i]!=(0 as u8) { buf[o]=name[i]; o=o+1; i=i+1 } 70 buf[o]=0 as u8 71 return 0 72} 73 74// ★v3: takes a CALLER-OWNED scratch buffer. No allocation here -- this is the hot path (once per .nx file). 75func exists(dir: *u8, name: *u8, scratch: *u8) -> i64 { 76 join_path(scratch, dir, name) 77 let fd: i64=sys_openat_rd(scratch) 78 if fd >= 0 { sys_close(fd); return 1 } 79 return 0 80} 81 82func is_dot(nm: *u8) -> i64 { 83 if nm[0]==(46 as u8) { 84 if nm[1]==(0 as u8) { return 1 } 85 if nm[1]==(46 as u8) { if nm[2]==(0 as u8) { return 1 } } 86 } 87 return 0 88} 89 90func str_copy(dst: *u8, src: *u8) -> i64 { var i: i64=0; while src[i]!=(0 as u8) { dst[i]=src[i]; i=i+1 } dst[i]=0 as u8; return i } 91 92// Does `s` (with its NUL) fit in a slot of `cap` bytes? Stops at cap, so it costs O(cap) not O(len) 93// and cannot itself walk off the end of a pathological string. Returns 1 fits, 0 does not. 94// Exists because str_copy above is UNBOUNDED BY DESIGN (the right primitive for a sized destination) 95// -- the caller owes the bound, and two callers were not paying it. 96func path_fits(s: *u8, cap: i64) -> i64 { 97 var i: i64 = 0 98 while i < cap { if s[i] == (0 as u8) { return 1 } i = i + 1 } 99 return 0 100} 101 102func dp_streq(a: *u8, b: *u8) -> i64 { 103 var i: i64=0 104 while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } 105 if b[i]!=(0 as u8) { return 0 } 106 return 1 107} 108 109// ★F1127 -- DEFINE THE HAZARD BY THE MECHANISM, NOT THE RESEMBLANCE. 110// A duplicate basename can only cause the harm this organ warns about ("a rebuild resolves the stale 111// copy and silently regresses the service") if a BUILD CAN ACTUALLY RESOLVE IT. The build resolver 112// states its own search path when it fails to find a source: 113// "SOURCE-NOT-FOUND (probed runtime/_hdl_build/, runtime/, nxasm/, runtime/wiki/)" 114// So a twin living in bin/, _retired/ or a nested runtime/runtime/ is NEVER read by a build and cannot 115// clobber anything -- it is litter, not a hazard. Counting it as a hazard leaves the board permanently 116// RED with no action that could ever clear it, which trains readers to ignore the verdict. 117// Nothing is hidden: out-of-scope divergence is still PRINTED, just counted in its own class. 118// ⚠The probe-path concept only has meaning INSIDE the buildroot -- that is the only tree a build 119// resolves from. A scan of any other directory (notably the hermetic self-test fixtures) has no build 120// semantics at all, so everything there is in scope. Without this the detector's OWN self-test breaks, 121// which is precisely how this was caught: the gate refused the change before it could ship. 122func dp_in_buildroot(dir: *u8) -> i64 { 123 let p: *u8 = "buildroot/" as *u8 124 var i: i64 = 0 125 while p[i]!=(0 as u8) { if dir[i]!=p[i] { return 0 } i=i+1 } 126 return 1 127} 128 129// ★★★THE COMPILER'S PROBE ORDER -- what turns "divergent + newer" into a VERDICT instead of a riddle. 130// /api/build states it outright: "exists in BOTH buildroot/runtime/_hdl_build (RESOLVED FIRST) AND 131// buildroot/runtime". Lower rank wins. Without it this organ could only print "if the BUILD resolves the 132// OTHER directory, later work is being silently discarded" and leave the reader to finish the sentence -- 133// and on 2026-07-30 finishing it revealed FOUR organs compiling source up to 17.7 DAYS stale (nx_hls_parse, 134// nx_clock, nx_input_abstract, nx_site_lock_lib), buried among six pairs where the newer copy already wins 135// and nothing is wrong. Same two facts, opposite conclusions; only the probe order separates them. 136// ⚠keep this in step with dp_is_probed: a dir that is probed but unranked falls to 9 and reads as LAST. 137func dp_probe_rank(dir: *u8) -> i64 { 138 if dp_streq(dir, "buildroot/runtime/_hdl_build" as *u8)==1 { return 0 } 139 if dp_streq(dir, "buildroot/runtime" as *u8)==1 { return 1 } 140 if dp_streq(dir, "buildroot/runtime/wiki" as *u8)==1 { return 2 } 141 if dp_streq(dir, "buildroot/nxasm" as *u8)==1 { return 3 } 142 return 9 143} 144 145// ★1 = the copy that WINS the probe rank is the MAIN-LESS one, so the build is DEAD (nxasm rc=102). 146// 0 = either the rank-winner has main (the target builds; the OTHER copy is the unreachable one), or the 147// two dirs tie and no claim can be made. PURE ON PURPOSE: dp_probe_rank returns 9 for anything outside the 148// four ranked build dirs, so a /tmp fixture ALWAYS ties and can never reach the dead branch. Extracting the 149// decision lets the selftest bite it with synthetic ranks instead of leaving it dark -- a branch no fixture 150// can reach is a branch no gate is testing. 151func dp_deadwin(ra: i64, ha: i64, rb: i64, hb: i64) -> i64 { 152 if ra < rb { if ha == 0 { return 1 } return 0 } 153 if rb < ra { if hb == 0 { return 1 } return 0 } 154 return 0 155} 156 157func dp_is_probed(dir: *u8) -> i64 { 158 if dp_in_buildroot(dir)==0 { return 1 } 159 if dp_streq(dir, "buildroot/runtime" as *u8)==1 { return 1 } 160 if dp_streq(dir, "buildroot/runtime/_hdl_build" as *u8)==1 { return 1 } 161 if dp_streq(dir, "buildroot/runtime/wiki" as *u8)==1 { return 1 } 162 if dp_streq(dir, "buildroot/nxasm" as *u8)==1 { return 1 } 163 return 0 164} 165 166// read up to cap bytes. Returns byte count, or -1 unreadable, or -2 if the file EXCEEDS cap. 167// An over-cap file is NEVER folded into "identical" -- it is reported UNKNOWN (L011). 168func dp_atoi(s: *u8) -> i64 { 169 var v: i64 = 0 170 var i: i64 = 0 171 while s[i] != (0 as u8) { 172 let c: i64 = (s[i] as i64) & 255 173 if c >= 48 { if c <= 57 { v = v*10 + (c-48) } } 174 i = i + 1 175 } 176 return v 177} 178 179func dp_sum(buf: *u8, n: i64) -> i64 { 180 var h: i64 = 0 181 var i: i64 = 0 182 while i < n { 183 h = ((h * FP_MULT) + ((buf[i] as i64) & 255)) & FP_MASK 184 i = i + 1 185 } 186 return h 187} 188 189func dp_slurp(path: *u8, buf: *u8, cap: i64) -> i64 { 190 let fd: i64 = sys_openat_rd(path) 191 if fd < 0 { return 0 - 1 } 192 var total: i64 = 0 193 var go: i64 = 1 194 while go == 1 { 195 let n: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) 196 if n <= 0 { go = 0 } else { 197 total = total + n 198 if total >= cap { sys_close(fd); return 0 - 2 } 199 } 200 } 201 sys_close(fd) 202 return total 203} 204 205// 1 identical / 0 divergent / -1 unknown. Caller owns both compare buffers. 206func dp_same(pa: *u8, pb: *u8, ba: *u8, bb: *u8, cap: i64) -> i64 { 207 let na: i64 = dp_slurp(pa, ba, cap) 208 if na < 0 { return 0 - 1 } 209 let nb: i64 = dp_slurp(pb, bb, cap) 210 if nb < 0 { return 0 - 1 } 211 if na != nb { return 0 } 212 var i: i64 = 0 213 while i < na { if ba[i] != bb[i] { return 0 } i = i + 1 } 214 return 1 215} 216 217// ★★★v7 (2026-07-31, lib-reconcile): TARGET RESOLUTION AND IMPORT RESOLUTION ARE DIFFERENT RULES, AND 218// APPLYING THE FIRST TO A LIBRARY MANUFACTURES A FALSE WORK-LOSS ALARM WHOSE REMEDY CAUSES AN OUTAGE. 219// /api/build resolves a BUILD TARGET by dp_probe_rank (_hdl_build first), so for a file that IS a target, 220// "newer copy sits in the later dir" really does mean every build compiles stale source -- seq1343 221// nx_skullgen is exactly that and stays a hazard. But a LIBRARY is never a target: it reaches the compiler 222// only through another file's import, and an import resolves to the IMPORTER'S OWN DIRECTORY FIRST. 223// PROOF (measured 2026-07-31, artifact not claim): nx_clock.nx exists in BOTH dirs exporting DISJOINT 224// symbols -- runtime/ defines nx_clock_monotonic_ns, _hdl_build/ is the clk_* tickless scheduler -- and 225// nx_f32_llm_serve.nx, which exists ONLY in runtime/, compiles with nx_clock_monotonic_ns .globl-defined 226// in buildroot/_build/nx_f32_llm_serve.s:2511. A runtime importer therefore resolved the runtime copy 227// while a rank-0 _hdl_build copy of that basename existed. Reporting that pair as WORK-LOSS told the 228// operator to reconcile to ONE dir; doing so deletes runtime/nx_clock.nx and re-darkens the 174 organs 229// the 2026-07-30 fix recovered. An alarm whose remedy is the outage is worse than no alarm. 230// DISCRIMINATOR: a build target defines main(); a library omits it ON PURPOSE so it can be imported -- 231// nx_gate_verdict_lib.nx's header states exactly that reason. This reads the ARTIFACT, not the filename, 232// because name-based rules rot. 233// FAIL-CLOSED: unreadable or over-cap returns -1 and the caller keeps the LOUD hazard classification. 234// "I could not tell" must never downgrade an alarm -- that is how a real clobber gets filed as litter. 235// COST: one extra slurp per DIVERGENT pair (11 today) into the caller's existing buffer, never per file. 236// v2's OOM lesson is that changing a caller's fan-out re-costs every callee; this adds none. 237const DP_NDLCAP: i64 = 512 238static dp_ndl: *u8 239 240func dp_find_in(hay: *u8, hn: i64, ndl: *u8, nn: i64) -> i64 { 241 if nn == 0 { return 0 } 242 if nn > hn { return 0 } 243 var i: i64 = 0 244 while i + nn <= hn { 245 // column 0 on THIS side too, or a comment in the OTHER file fakes the overlap and the caller 246 // downgrades a real name collision to a merge -- see the note in dp_exports_overlap. 247 var atcol0: i64 = 1 248 if i > 0 { if hay[i-1] != (10 as u8) { atcol0 = 0 } } 249 if atcol0 == 1 { 250 var k: i64 = 0 251 var hit: i64 = 1 252 while k < nn { if hay[i+k] != ndl[k] { hit = 0; k = nn } else { k = k + 1 } } 253 if hit == 1 { return 1 } 254 } 255 i = i + 1 256 } 257 return 0 258} 259 260// index of a column-0 match, or -1. Same anchoring rule as dp_find_in; returns WHERE so the caller can 261// read the signature that follows. 262func dp_find_at(hay: *u8, hn: i64, ndl: *u8, nn: i64) -> i64 { 263 if nn == 0 { return 0 - 1 } 264 if nn > hn { return 0 - 1 } 265 var i: i64 = 0 266 while i + nn <= hn { 267 var atcol0: i64 = 1 268 if i > 0 { if hay[i-1] != (10 as u8) { atcol0 = 0 } } 269 if atcol0 == 1 { 270 var k: i64 = 0 271 var hit: i64 = 1 272 while k < nn { if hay[i+k] != ndl[k] { hit = 0; k = nn } else { k = k + 1 } } 273 if hit == 1 { return i } 274 } 275 i = i + 1 276 } 277 return 0 - 1 278} 279 280// parameter count of the definition starting at `start`, or -1 if unparseable. Signatures in this corpus 281// carry no nested parens, so commas at one level are the whole story: 0 params = empty parens. 282func dp_arity(buf: *u8, n: i64, start: i64) -> i64 { 283 var i: i64 = start 284 var go: i64 = 1 285 while go == 1 { 286 if i >= n { return 0 - 1 } 287 if buf[i] == (40 as u8) { go = 0 } else { i = i + 1 } 288 } 289 i = i + 1 290 var commas: i64 = 0 291 var any: i64 = 0 292 var go2: i64 = 1 293 while go2 == 1 { 294 if i >= n { return 0 - 1 } 295 let c: i64 = buf[i] as i64 296 if c == 41 { go2 = 0 } else { 297 if c == 44 { commas = commas + 1 } 298 if c != 32 { any = 1 } 299 i = i + 1 300 } 301 } 302 if any == 0 { return 0 } 303 return commas + 1 304} 305 306// 1 = the two copies share >=1 exported func name | 0 = DISJOINT (name collision) | -1 = unreadable 307func dp_exports_overlap(pa: *u8, pb: *u8, ba: *u8, bb: *u8, cap: i64) -> i64 { 308 let na: i64 = dp_slurp(pa, ba, cap) 309 if na <= 0 { return 0 - 1 } 310 let nb: i64 = dp_slurp(pb, bb, cap) 311 if nb <= 0 { return 0 - 1 } 312 if (dp_ndl as i64) == 0 { dp_ndl = sys_mmap(DP_NDLCAP) } 313 let ndl: *u8 = dp_ndl 314 var i: i64 = 0 315 while i + 5 <= na { 316 var isf: i64 = 0 317 if ba[i] == (102 as u8) { if ba[i+1] == (117 as u8) { if ba[i+2] == (110 as u8) { if ba[i+3] == (99 as u8) { if ba[i+4] == (32 as u8) { isf = 1 } } } } } 318 // COLUMN 0 ONLY. `func ` also occurs inside COMMENTS, and a shared comment mention would fake an 319 // OVERLAP -- the UNSAFE direction: a false collision costs one renamed file, a false overlap tells 320 // the operator to MERGE TWO PROGRAMS. Top-level definitions in this corpus are unindented, comments 321 // are not, so requiring column 0 removes the fake without needing a tokenizer. 322 if isf == 1 { if i > 0 { if ba[i-1] != (10 as u8) { isf = 0 } } } 323 if isf == 1 { 324 var k: i64 = 0 325 while k < 5 { ndl[k] = ba[i+k]; k = k + 1 } 326 var j: i64 = i + 5 327 var go: i64 = 1 328 while go == 1 { 329 if j >= na { go = 0 } else { 330 let c: i64 = ba[j] as i64 331 if c == 40 { go = 0 } else { if c == 32 { go = 0 } else { if c == 10 { go = 0 } else { 332 if k < DP_NDLCAP - 2 { ndl[k] = ba[j]; k = k + 1 } 333 j = j + 1 334 } } } 335 } 336 } 337 if k > 5 { 338 ndl[k] = 40 as u8 339 k = k + 1 340 if dp_find_in(bb, nb, ndl, k) == 1 { return 1 } 341 } 342 i = j 343 } else { i = i + 1 } 344 } 345 return 0 346} 347 348// 1 = a name exported by BOTH copies has a DIFFERENT parameter count | 0 = none | -1 = unreadable. 349// THE SILENT CLASS. A full name collision is loud the moment anything resolves the wrong copy; a PARTIAL 350// overlap binds QUIETLY and only the mismatched call misbehaves -- and nx_cc is fails-open on exactly that 351// (seq1012, 1785447657), so there is no diagnostic. Live instance: nx_input_abstract.nx, where runtime:88 352// is ia_init(p: *i64) and _hdl_build:68 is ia_init(s: *i64, nact: i64) while the rest is disjoint. 353func dp_sig_mismatch(pa: *u8, pb: *u8, ba: *u8, bb: *u8, cap: i64) -> i64 { 354 let na: i64 = dp_slurp(pa, ba, cap) 355 if na <= 0 { return 0 - 1 } 356 let nb: i64 = dp_slurp(pb, bb, cap) 357 if nb <= 0 { return 0 - 1 } 358 if (dp_ndl as i64) == 0 { dp_ndl = sys_mmap(DP_NDLCAP) } 359 let ndl: *u8 = dp_ndl 360 var i: i64 = 0 361 while i + 5 <= na { 362 var isf: i64 = 0 363 if ba[i] == (102 as u8) { if ba[i+1] == (117 as u8) { if ba[i+2] == (110 as u8) { if ba[i+3] == (99 as u8) { if ba[i+4] == (32 as u8) { isf = 1 } } } } } 364 if isf == 1 { if i > 0 { if ba[i-1] != (10 as u8) { isf = 0 } } } 365 if isf == 1 { 366 var k: i64 = 0 367 while k < 5 { ndl[k] = ba[i+k]; k = k + 1 } 368 var j: i64 = i + 5 369 var go: i64 = 1 370 while go == 1 { 371 if j >= na { go = 0 } else { 372 let c: i64 = ba[j] as i64 373 if c == 40 { go = 0 } else { if c == 32 { go = 0 } else { if c == 10 { go = 0 } else { 374 if k < DP_NDLCAP - 2 { ndl[k] = ba[j]; k = k + 1 } 375 j = j + 1 376 } } } 377 } 378 } 379 if k > 5 { 380 ndl[k] = 40 as u8 381 k = k + 1 382 let pos: i64 = dp_find_at(bb, nb, ndl, k) 383 if pos >= 0 { 384 let aa: i64 = dp_arity(ba, na, i) 385 let ab: i64 = dp_arity(bb, nb, pos) 386 if aa >= 0 { if ab >= 0 { if aa != ab { return 1 } } } 387 } 388 } 389 i = j 390 } else { i = i + 1 } 391 } 392 return 0 393} 394 395func dp_has_main(path: *u8, buf: *u8, cap: i64) -> i64 { 396 let n: i64 = dp_slurp(path, buf, cap) 397 if n <= 0 { return 0 - 1 } 398 let pat: *u8 = "func main(" as *u8 399 var pn: i64 = 0 400 while pat[pn] != (0 as u8) { pn = pn + 1 } 401 if pn > n { return 0 } 402 var i: i64 = 0 403 while i + pn <= n { 404 var k: i64 = 0 405 var hit: i64 = 1 406 while k < pn { if buf[i+k] != pat[k] { hit = 0; k = pn } else { k = k + 1 } } 407 if hit == 1 { return 1 } 408 i = i + 1 409 } 410 return 0 411} 412 413// ★v4 (seq207): a dup basename is only a REAL clobber hazard when the two copies DIVERGE. Identical 414// copies are litter (still LATENT -- the next one-sided edit diverges them). v3 compared basenames ONLY, 415// which is why 31 undifferentiated alarms sat unreconciled: nobody could tell which ones could actually 416// regress a service. stats[0]=divergent-IN-PROBE-PATH (real hazard) stats[1]=identical stats[2]=unknown 417// Compare buffers are CALLER-OWNED, allocated ONCE PER RUN: re-costing a callee you amplified is the v2 418// OOM lesson (2MiB per pair x 78 pairs = 156MB leaked). 419// ★★seq1343: NAMING WHICH COPY WINS IS NOT ENOUGH -- THE CASE THAT SILENTLY EATS WORK IS THE RESOLVED 420// COPY BEING THE OLDER ONE. Measured live 2026-07-30: nx_skullgen.nx resolved the 15481B Jul-27-15:03 421// copy while a 17685B Jul-27-21:56 copy -- the F1110 skull-v2 anatomy fix, 6h53m newer -- sat in a 422// shadow the build never read, so the fix for the renders-as-an-egg defect had never once compiled. 423// ★dp_mtime ALREADY EXISTED and was gate-proven (T8/T8b), and line 43 of this very file already said 424// "only mtime says which way to reconcile" -- it was simply never wired to the hazard line. Freshness 425// IS the triage: it turns each divergent pair from an investigation into one readable row. 426func dp_fresh(pa: *u8, pb: *u8, dirA: *u8, dirB: *u8, sb: *u8) -> i64 { 427 let ma: i64 = dp_mtime(pa, sb) 428 let mb: i64 = dp_mtime(pb, sb) 429 if ma < 0 { return 0 } 430 if mb < 0 { return 0 } 431 var d: i64 = ma - mb 432 if d < 0 { d = 0 - d } 433 var nw: i64 = 0 434 dp_w(" FRESHNESS: newer copy is in " as *u8) 435 if ma > mb { dp_w(dirA); nw = 1 } 436 if mb > ma { dp_w(dirB); nw = 2 } 437 if ma == mb { dp_w("(both carry the same mtime)" as *u8) } 438 dp_w(" gap " as *u8) 439 dp_wn(d) 440 dp_w("s -- crossed with the probe order below (seq1343)\n" as *u8) 441 return nw 442} 443 444func scan2(dirA: *u8, dirB: *u8, verbose: i64, stats: *i64, ca: *u8, cb: *u8, ccap: i64) -> i64 { 445 let fd: i64=sys_openat_rd(dirA) 446 if fd < 0 { return 0 } 447 let dbuf: *u8=sys_mmap(K_MAGIC_131072) 448 let pbuf: *u8=sys_mmap(K_MAGIC_4096) 449 let pa: *u8=sys_mmap(K_MAGIC_4096) 450 // one stat buffer per dir-pair, allocated with its siblings -- NEVER inside the file loop (v2 OOM lesson) 451 let stb: *u8=sys_mmap(K_MAGIC_4096) 452 var dups: i64=0 453 var go: i64=1 454 while go == 1 { 455 let nr: i64=sys_getdents64(fd, dbuf, K_MAGIC_131072) 456 if nr <= 0 { go = 0 } else { 457 var off: i64=0 458 while off < nr { 459 let rec: *u8=(dbuf as i64 + off) as *u8 460 let ty: i64=dirent_type(rec) 461 let nm: *u8=dirent_name(rec) 462 if ty != 4 { 463 if ends_nx(nm) == 1 { 464 if exists(dirB, nm, pbuf) == 1 { 465 dups = dups + 1 466 if verbose == 1 { dp_w(" DUP basename: "); dp_w(nm); dp_w(" (present in both "); dp_w(dirA); dp_w(" and "); dp_w(dirB); dp_w(")\ 467" as *u8) } 468 join_path(pa, dirA, nm) 469 let sm: i64 = dp_same(pa, pbuf, ca, cb, ccap) 470 if sm == 1 { 471 stats[1] = stats[1] + 1 472 // ★v11 SYNC-NOISE CONTROL, derived from this run's OWN data. 473 // Two BYTE-IDENTICAL copies cannot have been edited independently, so 474 // ANY mtime gap between them is pure bulk-sync noise. The largest such 475 // gap is therefore the floor below which the FRESHNESS direction above 476 // is NOT RESOLVABLE -- a measured control, never a chosen threshold. 477 // This matters because the estate has already proven that in a 478 // bulk-synced tree MTIME IS NOT EDIT HISTORY (7,047 of 7,133 .nx share 479 // an mtime inside a 5-minute window; it is why nx_gatestale rebuilds 480 // instead of comparing timestamps). Identical pairs are the perfect 481 // control for it because their answer is known a priori: no edit. 482 let ia: i64 = dp_mtime(pa, stb) 483 let ib: i64 = dp_mtime(pbuf, stb) 484 if ia >= 0 { 485 if ib >= 0 { 486 var gsn: i64 = ia - ib 487 if gsn < 0 { gsn = 0 - gsn } 488 if gsn > stats[8] { stats[8] = gsn } 489 } 490 } 491 if verbose == 1 { dp_w(" ^ identical bytes -- litter (latent: diverges on the next one-sided edit)\ 492" as *u8) } 493 } else { 494 if sm == 0 { 495 if dp_is_probed(dirA)*dp_is_probed(dirB) == 0 { 496 stats[3] = stats[3] + 1 497 if verbose == 1 { dp_w(" ^ divergent but OUT OF THE BUILD PROBE PATH -- a build never reads this tree, so it cannot clobber; litter, not a hazard\ 498" as *u8) } 499 } else { 500 var nwin: i64 = 0 501 if verbose == 1 { nwin = dp_fresh(pa, pbuf, dirA, dirB, stb) } 502 // v7: WHICH resolution rule applies is decided by target-vs-library, read from the 503 // ARTIFACT. Fail-closed: dp_has_main returns -1 when it cannot read, and -1 counts 504 // as a target so an unreadable pair keeps the LOUD hazard line instead of being 505 // quietly downgraded to litter. 506 let hm_a: i64 = dp_has_main(pa, ca, ccap) 507 let hm_b: i64 = dp_has_main(pbuf, cb, ccap) 508 var istgt: i64 = 0 509 if hm_a != 0 { istgt = 1 } 510 if hm_b != 0 { istgt = 1 } 511 // the newer copy LOSES iff it sits in the dir the compiler probes LATER -- a 512 // question that only MEANS anything for a target, so it is asked only for one. 513 var lost: i64 = 0 514 if istgt == 1 { 515 if nwin == 1 { if dp_probe_rank(dirA) > dp_probe_rank(dirB) { lost = 1 } } 516 if nwin == 2 { if dp_probe_rank(dirB) > dp_probe_rank(dirA) { lost = 1 } } 517 } 518 // ★v10: EXACTLY ONE copy has main => LIBRARY IN ONE DIR, TARGET IN THE OTHER. The 519 // library wins the target probe rank, so building this basename ALWAYS resolves the 520 // main-less copy and ALWAYS fails nxasm rc=102 'UNDEFINED label: main'. Measured on 521 // nx_hls_parse (id 1785520111): permanently RED, and no edit to either FILE can clear 522 // it -- only a rename can. This is the one dup shape that announces itself; the other 523 // four fail silently, which is exactly why it is worth naming rather than describing. 524 var lvt: i64 = 0 525 if hm_a == 1 { if hm_b == 0 { lvt = 1 } } 526 if hm_b == 1 { if hm_a == 0 { lvt = 1 } } 527 if lvt == 1 { stats[7] = stats[7] + 1 } 528 // ⚠THE SHAPE IS SYMMETRIC BUT THE CONSEQUENCE IS NOT, and v10's first message got this 529 // WRONG by claiming the build ALWAYS fails. It fails only when the copy that WINS the 530 // probe rank is the MAIN-LESS one. Measured: nx_hls_parse and nx_tissue lose that way 531 // (rc=102 / NOELF), while nx_gen's rank-winning copy DOES have main so it builds fine -- 532 // and the damage there is the opposite, the main-less R5 generator can never be built 533 // under its own name. Same collision, two different failures; say which one. 534 var deadwin: i64 = 0 535 if lvt == 1 { deadwin = dp_deadwin(dp_probe_rank(dirA), hm_a, dp_probe_rank(dirB), hm_b) } 536 if verbose == 1 { if lvt == 1 { if deadwin == 1 { 537 dp_w(" ^ LIBRARY-VS-TARGET COLLISION (BUILD IS DEAD): the copy that WINS the probe rank has NO main(), so building this basename fails nxasm rc=102 UNDEFINED label: main -- permanently, and no edit to either file clears it. Only a RENAME does\n" as *u8) 538 } else { 539 dp_w(" ^ LIBRARY-VS-TARGET COLLISION (BUILD OK, OTHER COPY UNREACHABLE): the rank-winning copy HAS main() so this target builds, but the main-less copy is a LIBRARY that can never be built or resolved under this name. RENAME it so it becomes reachable\n" as *u8) 540 } } } 541 // v8: disjoint exports = two PROGRAMS, not two versions. Asked for TARGETS too, 542 // because nx_gen and nx_tissue -- the costliest collisions found -- are targets. 543 let ov: i64 = dp_exports_overlap(pa, pbuf, ca, cb, ccap) 544 if ov == 0 { stats[5] = stats[5] + 1 } 545 if verbose == 1 { if ov == 0 { 546 dp_w(" ^ NAME COLLISION: the two copies share NO exported func name -- these are DIFFERENT PROGRAMS wearing one filename, not two versions of one module. Do NOT reconcile them to one dir (that DELETES a program); RENAME one so the name carries the identity\n" as *u8) 547 } } 548 // v9: only meaningful when the names DO overlap -- that is the partial case. 549 var sig: i64 = 0 550 if ov == 1 { sig = dp_sig_mismatch(pa, pbuf, ca, cb, ccap) } 551 if sig == 1 { stats[6] = stats[6] + 1 } 552 if verbose == 1 { if sig == 1 { 553 dp_w(" ^ SIGNATURE MISMATCH: both copies export the SAME name with a DIFFERENT parameter count. Worse than a full collision because it binds SILENTLY -- only the mismatched call misbehaves and nx_cc emits no diagnostic. Fix FIRST, and fix it by RENAMING, not by merging\n" as *u8) 554 } } 555 if istgt == 1 { stats[0] = stats[0] + 1 } else { stats[4] = stats[4] + 1 } 556 if verbose == 1 { if istgt == 0 { 557 dp_w(" ^ DIVERGENT LIBRARY (no main() in either copy) -- an import resolves to the IMPORTER'S OWN dir FIRST, so there is NO single losing copy and this is NOT work-loss. Reconcile ONLY if both copies export the SAME symbols; disjoint exports are a deliberate per-layer split (nx_clock.nx: runtime=monotonic clock, _hdl_build=tickless scheduler) and collapsing them re-darkens every importer of whichever copy you delete\n" as *u8) 558 } else { if lost == 1 { 559 dp_w(" ^ DIVERGENT + WORK-LOSS NOW: the NEWER copy sits in the dir the compiler probes SECOND, so EVERY build silently compiles the OLDER source\n" as *u8) 560 } else { 561 dp_w(" ^ DIVERGENT but the newer copy is the one resolved FIRST -- builds are correct today; litter, still reconcile (one one-sided edit flips it)\n" as *u8) 562 } } } 563 } 564 } else { 565 stats[2] = stats[2] + 1 566 if verbose == 1 { dp_w(" ^ UNKNOWN (unreadable or over the compare cap) -- NOT assumed identical\ 567" as *u8) } 568 } 569 } 570 } 571 } 572 } 573 off = off + dirent_reclen(rec) 574 } 575 } 576 } 577 sys_close(fd) 578 return dups 579} 580 581// v1/v2/v3 SIGNATURE PRESERVED VERBATIM (rule 19) -- selftest and any other caller keep working. 582func scan(dirA: *u8, dirB: *u8, verbose: i64) -> i64 { 583 // ★v7: 32->64 for the same reason as main's -- scan2 now writes st[4], and selftest reaches 584 // scan2 THROUGH here, so leaving this at 4 slots would make the SELF-TEST the out-of-bounds writer. 585 // ★v11: 64->128 for st[8], the sync-noise floor. Widened HERE FIRST, exactly as the v7/v10 notes 586 // demand, because selftest reaches scan2 through this function -- and the init below was only 587 // zeroing st[0..4] while scan2 already wrote st[5..7]; those three were correct solely because 588 // mmap zero-fills, which is a property of the allocator, not of this code. Now all nine are explicit. 589 let st: *i64 = sys_mmap(128) as *i64 590 st[0]=0; st[1]=0; st[2]=0; st[3]=0; st[4]=0; st[5]=0; st[6]=0; st[7]=0; st[8]=0 591 let ca: *u8 = sys_mmap(K_CMP_CAP) 592 let cb: *u8 = sys_mmap(K_CMP_CAP) 593 return scan2(dirA, dirB, verbose, st, ca, cb, K_CMP_CAP) 594} 595 596func wfile(path: *u8) -> i64 { let fd: i64=sys_openat_wr(path, 420); if fd>=0 { sys_write(fd, "x" as *u8, 1); sys_close(fd) } return 0 } 597// content-specific fixture writer for the v4 divergence tooth. Content is FIXED PER PATH so repeated 598// runs stay idempotent even though sys_openat_wr does not truncate. 599func wfile2(path: *u8, s: *u8) -> i64 { 600 let fd: i64=sys_openat_wr(path, 420) 601 if fd>=0 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd, s, n); sys_close(fd) } 602 return 0 603} 604 605// ***THE SANDBOX EXCLUSION (2026-09-03) -- why this census could never publish a trustworthy number. 606// buildroot/_build/ holds the mutation-testing sandboxes nx_gate_bite creates (afx_sb_<epoch>_<target>/) 607// beside the ordinary build fossils. MEASURED on the production artifact knowledge/status/dup_source.log 608// (14,954,968 B): 233 of the 256 discovered trees were those sandboxes, accumulating ~34/day since 609// 2026-08-28 and NEVER reaped. They exhausted the 256-slot tree table -- the envelope read 610// trees=256 tree_cap=256 cap_hit=1 -- so EVERY run refused as TRUNCATED, and they generated ~999 permil 611// of the 56,567 duplicate rows (partition: identical 55,756 + out-of-probe-path 795 + divergent-target 14 612// + divergent-library 2). The 16 genuine hazards were buried under a sandbox's shadow. 613// 614// PRUNING IS PROVABLY LOSSLESS FOR THE VERDICT, and the proof is read straight off scan2 above rather 615// than asserted: dp_is_probed returns 0 for ANY dir under buildroot/ that is not one of the four ranked 616// probe dirs, and scan2 routes every divergent pair having a non-probed side into stats[3] 617// (out-of-probe-path) BEFORE the hazard branch is reachable. So a pair involving one of these dirs can 618// only ever land in stats[1] (identical litter) or stats[3] -- it can NEVER reach stats[0], the ONLY 619// bucket the verdict keys on. A build never resolves a sandbox: the same fact from the compiler's side. 620// 621// IT IS AN EXCLUSION, NOT A TRUNCATION, AND THE DIFFERENCE IS THE WHOLE POINT: it deliberately does NOT 622// set cap[0]. cap[0] means -- a bound bit me and I do not know what I missed. This means -- I know 623// exactly what I skipped and why. It is COUNTED into cap[1] and PRINTED as dirs_skipped= in the 624// envelope, because AN EXCLUSION NOBODY CAN SEE IS INDISTINGUISHABLE FROM A SCAN THAT QUIETLY MISSED 625// SOMETHING -- the same law that made the depth cut announce below. 626func dp_is_build_scratch(dir: *u8) -> i64 { 627 if dp_streq(dir, "buildroot/_build" as *u8)==1 { return 1 } 628 return 0 629} 630 631// --- v12 status-file emitters. See dsx_publish for why this organ needs its own writer. --- 632func dsx_str(buf: *u8, pos: i64, s: *u8) -> i64 { 633 var p: i64 = pos 634 var i: i64 = 0 635 while s[i] != (0 as u8) { buf[p] = s[i]; p = p + 1; i = i + 1 } 636 return p 637} 638 639func dsx_nl(buf: *u8, pos: i64) -> i64 { buf[pos] = 10 as u8; return pos + 1 } 640 641// MSB-first, zero-alloc, and correct for 0 (div starts at 1, so the loop always emits one digit). 642func dsx_num(buf: *u8, pos: i64, v: i64) -> i64 { 643 var n: i64 = v 644 var p: i64 = pos 645 if n < 0 { buf[p] = 45 as u8; p = p + 1; n = 0 - n } 646 var div: i64 = 1 647 while n / div >= 10 { div = div * 10 } 648 while div > 0 { 649 buf[p] = (48 + ((n / div) % 10)) as u8 650 p = p + 1 651 div = div / 10 652 } 653 return p 654} 655 656func dsx_row(buf: *u8, pos: i64, k: *u8, v: i64) -> i64 { 657 var p: i64 = dsx_str(buf, pos, k) 658 p = dsx_str(buf, p, "=" as *u8) 659 p = dsx_num(buf, p, v) 660 return dsx_nl(buf, p) 661} 662 663// ***THE CENSUS COULD NOT PUBLISH ITSELF (2026-09-03). Its stdout artifact is 14,954,968 B while every 664// caller that could read it truncates FIRST: 163,840 B sync and 1,048,576 B async. The summary, the 665// partition and the verdict all sit at the END of that stream, behind ~14 MB of per-pair DUP rows, so 666// NO CALLER HAS EVER SEEN THIS ORGAN'S VERDICT -- its GREEN and its RED were equally invisible, which 667// is the same as having no verdict at all. 668// The fix is the SHAPE, not a bigger cap: a small truncate-written status file carrying the summary and 669// the partition, with verdict= as its LAST line so a positional reader (gv_last_line) still works. 670// sys_openat_wr DOES NOT TRUNCATE (see wfile2's own comment), so a shorter run would leave a stale tail 671// of the previous longer one -- a status file that lies by leftover. There is no openat_trunc anywhere 672// in the tree (swept 2026-09-03: matches=0 over 8,801 files, coverage_complete=1 corpus_complete=1), so 673// truncation is done the one sovereign way available: unlink FIRST, then create. That is idempotent and 674// yields EXACTLY the bytes written, never a mixture of two runs. 675// Returns the byte count, or -1 if the file could not be opened -- and main ANNOUNCES that, because a 676// status write that silently failed is the forgeable-heartbeat class. 677func dsx_publish(path: *u8, buf: *u8, n: i64) -> i64 { 678 sys_unlink(path) 679 let fd: i64 = sys_openat_wr(path, 420) 680 if fd < 0 { return 0 - 1 } 681 sys_write(fd, buf, n) 682 sys_close(fd) 683 return n 684} 685 686// BFS-discover dirs holding >=1 .nx. Buffers hoisted; allocation is O(1) per walk, not per entry. 687func discover(root: *u8, out: *u8, maxd: i64, maxdepth: i64, cap: *i64) -> i64 { 688 // BFS frontier capacity. This is the bound that was ACTUALLY BITING: the run reported 689 // trees=23 tree_cap=24 cap_hit=1, so the tree table (23<24) was not full and the depth cut was 690 // silent -- the only remaining setter was this queue at 256, which every directory under 691 // buildroot/ passes through (not just the ones holding .nx). The scan therefore refused as 692 // TRUNCATED on every run and its duplicate census could not be trusted in EITHER direction. 693 // 8192 is not a taste: it is validated by the run itself printing cap_hit=0, and any future 694 // breach is LOUD because the overflow arm below sets cap[0]. Cost is 8192*(256+8) = ~2.1MB for 695 // the whole walk, allocated ONCE (the O(pairs+dirs) profile this file's header defends). 696 let QCAP: i64 = 8192 697 let qbuf: *u8 = sys_mmap(QCAP*256) 698 let qdep: *i64 = sys_mmap(QCAP*8) as *i64 699 let dbuf: *u8 = sys_mmap(K_MAGIC_131072) 700 let child: *u8 = sys_mmap(K_MAGIC_4096) 701 var qh: i64 = 0 702 var qt: i64 = 0 703 cap[0] = 0 704 // v12: cap[1] is the DELIBERATE-EXCLUSION counter (dirs_skipped). Reset per walk, because the 705 // selftest calls discover() several times and a carried-over count would read as this run's. 706 cap[1] = 0 707 str_copy((qbuf as i64) as *u8, root); qdep[0]=0; qt=1 708 var nd: i64 = 0 709 while qh < qt { 710 let cur: *u8 = (qbuf as i64 + qh*256) as *u8 711 let cdep: i64 = qdep[qh] 712 qh = qh + 1 713 let fd: i64 = sys_openat_rd(cur) 714 if fd >= 0 { 715 var hasnx: i64 = 0 716 var go: i64 = 1 717 while go == 1 { 718 let nr: i64 = sys_getdents64(fd, dbuf, K_MAGIC_131072) 719 if nr <= 0 { go = 0 } else { 720 var off: i64 = 0 721 while off < nr { 722 let rec: *u8 = (dbuf as i64 + off) as *u8 723 let ty: i64 = dirent_type(rec) 724 let nm: *u8 = dirent_name(rec) 725 if ty == 4 { 726 if is_dot(nm) == 0 { 727 join_path(child, cur, nm) 728 // NAMED, COUNTED EXCLUSION -- see dp_is_build_scratch above. It 729 // deliberately does NOT set cap[0]: this subtree is skipped BY DECISION, 730 // so the census over what REMAINS is COMPLETE, not truncated. Conflating 731 // the two would put this organ back where it started -- refusing every 732 // run as TRUNCATED for a reason nobody could act on. 733 // join_path is hoisted because the test needs the child PATH, not the 734 // basename; it is free, since the enqueue below was computing it anyway. 735 if dp_is_build_scratch(child) == 1 { cap[1] = cap[1] + 1 } else { 736 if cdep < maxdepth { 737 if qt < QCAP { 738 // SLOT-BOUND CHECK (2026-08-16). `child` is a 4096-byte buffer 739 // and each queue slot is 256, but str_copy runs to the NUL with 740 // NO bound -- so a directory path longer than 255 bytes wrote 741 // straight past its slot. TWO symptoms, ONE defect: normally it 742 // lands inside the 65536-byte qbuf and SILENTLY CORRUPTS the 743 // next queue entry (no guard fires, the scan reports the wrong 744 // trees and still says GREEN); at qt near QCAP it leaves the 745 // arena and the ring detector prints ARENA-OVERRUN. The live 746 // estate hit the second form: 23 trees listed, then dead. 747 // MEASURED both directions on a 284-byte fixture: before, 5 748 // trees / cap_hit=0 / GREEN; after, 4 trees / cap_hit=1 / RED. 749 // Refusing here reuses this tool's OWN truncation signal, so the 750 // existing loud RED verdict fires instead of a wrong GREEN. 751 // A COPY WHOSE LENGTH IS UNBOUNDED IS A BUFFER SIZE THAT ONLY 752 // HOLDS WHILE THE INPUTS ARE POLITE. 753 if path_fits(child, 256) == 1 { 754 str_copy((qbuf as i64 + qt*256) as *u8, child) 755 qdep[qt] = cdep + 1 756 qt = qt + 1 757 } else { cap[0] = 1 } 758 } else { cap[0] = 1 } 759 } else { cap[0] = 1 } 760 } 761 // ^ closes the dp_is_build_scratch exclusion arm opened above. 762 // ^ the depth cut MUST announce. It previously skipped the subtree and 763 // left cap[0] alone, so a depth-truncated scan reported cap_hit=0 and 764 // read as COMPLETE -- the queue overflow beside it announced correctly, 765 // which made the asymmetry easy to miss. A cap reached in silence becomes 766 // a measurement nobody knows is partial; this organ's whole contract is 767 // that it REFUSES rather than publish a partial duplicate census. 768 } 769 } else { 770 if ends_nx(nm) == 1 { hasnx = 1 } 771 } 772 off = off + dirent_reclen(rec) 773 } 774 } 775 } 776 sys_close(fd) 777 if hasnx == 1 { 778 // same slot bound as the queue write above: `out` is maxd slots of 256 bytes, and the 779 // LAST slot has nothing after it to absorb an overrun. 780 if nd < maxd { 781 if path_fits(cur, 256) == 1 { str_copy((out as i64 + nd*256) as *u8, cur); nd = nd + 1 } 782 else { cap[0] = 1 } 783 } 784 else { cap[0] = 1 } 785 } 786 } 787 } 788 return nd 789} 790 791func selftest() -> i64 { 792 // T1 v1 REGRESSION: two-dir case still finds exactly one dup, ignores the non-dup 793 sys_mkdir("/tmp/nxdupA" as *u8, 0x1ed) 794 sys_mkdir("/tmp/nxdupB" as *u8, 0x1ed) 795 wfile("/tmp/nxdupA/foo.nx" as *u8) 796 wfile("/tmp/nxdupA/bar.nx" as *u8) 797 wfile("/tmp/nxdupB/foo.nx" as *u8) 798 let dups: i64=scan("/tmp/nxdupA" as *u8, "/tmp/nxdupB" as *u8, 0) 799 if dups != 1 { dp_w(" SELFTEST FAIL T1: expected 1 dup, got "); dp_wn(dups); dp_w("\ 800" as *u8); return 0 } 801 // T2 nested-tree tooth (the seq281 class v1 was blind to) 802 sys_mkdir("/tmp/nxdup3" as *u8, 0x1ed) 803 sys_mkdir("/tmp/nxdup3/_hdl_build" as *u8, 0x1ed) 804 sys_mkdir("/tmp/nxdup3/runtime" as *u8, 0x1ed) 805 wfile("/tmp/nxdup3/only_here.nx" as *u8) 806 wfile("/tmp/nxdup3/_hdl_build/organ.nx" as *u8) 807 wfile("/tmp/nxdup3/runtime/organ.nx" as *u8) 808 let tbl: *u8 = sys_mmap(16*256) 809 let cap: *i64 = sys_mmap(16) as *i64 810 let n: i64 = discover("/tmp/nxdup3" as *u8, tbl, 16, 5, cap) 811 if n != 3 { dp_w(" SELFTEST FAIL T2a: expected 3 trees, got "); dp_wn(n); dp_w("\ 812" as *u8); return 0 } 813 var found: i64 = 0 814 var i: i64 = 0 815 while i < n { 816 var j: i64 = i + 1 817 while j < n { found = found + scan((tbl as i64 + i*256) as *u8, (tbl as i64 + j*256) as *u8, 0); j = j + 1 } 818 i = i + 1 819 } 820 if found != 1 { dp_w(" SELFTEST FAIL T2b: nested dup not caught, got "); dp_wn(found); dp_w("\ 821" as *u8); return 0 } 822 // T3 NON-VACUOUS negative control: v1's hardcoded pair reports ZERO here 823 let v1blind: i64 = scan("/tmp/nxdup3" as *u8, "/tmp/nxdup3/_hdl_build" as *u8, 0) 824 if v1blind != 0 { dp_w(" SELFTEST FAIL T3: negative control expected 0, got "); dp_wn(v1blind); dp_w("\ 825" as *u8); return 0 } 826 // T4 truncation must be LOUD, never silent 827 let tbl2: *u8 = sys_mmap(4*256) 828 let cap2: *i64 = sys_mmap(16) as *i64 829 discover("/tmp/nxdup3" as *u8, tbl2, 1, 5, cap2) 830 if cap2[0] != 1 { dp_w(" SELFTEST FAIL T4: silent cap\ 831" as *u8); return 0 } 832 // ★T5 RESOURCE TOOTH -- the half of the scale law v2 was missing. 600 files in ONE dir: v2 would have 833 // allocated 600 x 4096 here (per-file mmap); v3 allocates TWO buffers for the whole pair. This asserts the 834 // hot loop is allocation-free by exercising it at a file count a fixture normally never reaches. 835 sys_mkdir("/tmp/nxdupR" as *u8, 0x1ed) 836 sys_mkdir("/tmp/nxdupS" as *u8, 0x1ed) 837 let nmb: *u8 = sys_mmap(256) 838 let dg: *u8 = sys_mmap(16) 839 // ⚠LM-030: a string literal may NOT be indexed in expression position (CONST[i] crashes nx_cc with 840 // "unexpected operator token kind=47"). Bind it to a local pointer FIRST, then index the local. 841 let pfx: *u8 = "/tmp/nxdupR/f" as *u8 842 var k: i64 = 0 843 while k < 600 { 844 // build /tmp/nxdupR/f<k>.nx -- ZERO allocations in this loop (nmb + dg are hoisted above), which is the 845 // whole point of the tooth: if the hot path allocated per iteration this fixture would show it. 846 var p: i64 = 0 847 while pfx[p]!=(0 as u8) { nmb[p]=pfx[p]; p=p+1 } 848 var t: i64 = k 849 var dn: i64 = 0 850 if t==0 { dg[0]=48 as u8; dn=1 } 851 while t>0 { dg[dn]=(48+(t%10)) as u8; t=t/10; dn=dn+1 } 852 var q: i64 = 0 853 while q<dn { nmb[p]=dg[dn-1-q]; p=p+1; q=q+1 } 854 nmb[p]=46 as u8; nmb[p+1]=110 as u8; nmb[p+2]=120 as u8; nmb[p+3]=0 as u8 855 wfile(nmb) 856 k = k + 1 857 } 858 let big: i64 = scan("/tmp/nxdupR" as *u8, "/tmp/nxdupS" as *u8, 0) 859 if big != 0 { dp_w(" SELFTEST FAIL T5: expected 0 dups vs empty dir, got "); dp_wn(big); dp_w("\ 860" as *u8); return 0 } 861 // ★T6 (v4/seq207) DIVERGENCE TOOTH, with its own negative control in the same fixture: the detector 862 // must tell a DIVERGENT dup (a real clobber hazard -- a rebuild can resolve the stale copy and 863 // silently regress a service) from an IDENTICAL one (litter). Both are the SAME basename in two 864 // trees, so a detector that only compares names cannot separate them and scores 2/0 here. 865 sys_mkdir("/tmp/nxdupD1" as *u8, 0x1ed) 866 sys_mkdir("/tmp/nxdupD2" as *u8, 0x1ed) 867 wfile2("/tmp/nxdupD1/same.nx" as *u8, "IDENTICAL-BYTES" as *u8) 868 wfile2("/tmp/nxdupD2/same.nx" as *u8, "IDENTICAL-BYTES" as *u8) 869 // ★v7: these two now carry a main(), because T6 is the TARGET divergence tooth and only a target 870 // resolves by probe rank. wfile2 does NOT truncate, so each replacement content is deliberately 871 // LONGER than the string it supersedes -- a shorter one would leave the old tail behind and the 872 // fixture would silently stop being what its name claims. 873 wfile2("/tmp/nxdupD1/diff.nx" as *u8, "func main() -> i64 { return 0 } VERSION-A" as *u8) 874 wfile2("/tmp/nxdupD2/diff.nx" as *u8, "func main() -> i64 { return 1 } VERSION-B-longer" as *u8) 875 // ★v7: 32->64. scan2 now writes stats[4]; at 4 slots THE SELF-TEST would be the OOB writer. 876 let st6: *i64 = sys_mmap(64) as *i64 877 st6[0]=0; st6[1]=0; st6[2]=0; st6[3]=0; st6[4]=0 878 let c6a: *u8 = sys_mmap(K_CMP_CAP) 879 let c6b: *u8 = sys_mmap(K_CMP_CAP) 880 let d6: i64 = scan2("/tmp/nxdupD1" as *u8, "/tmp/nxdupD2" as *u8, 0, st6, c6a, c6b, K_CMP_CAP) 881 if d6 != 2 { dp_w(" SELFTEST FAIL T6a: expected 2 dup basenames, got "); dp_wn(d6); dp_w("\ 882" as *u8); return 0 } 883 if st6[0] != 1 { dp_w(" SELFTEST FAIL T6b: expected exactly 1 DIVERGENT, got "); dp_wn(st6[0]); dp_w("\ 884" as *u8); return 0 } 885 if st6[1] != 1 { dp_w(" SELFTEST FAIL T6c: expected exactly 1 IDENTICAL, got "); dp_wn(st6[1]); dp_w("\ 886" as *u8); return 0 } 887 if st6[2] != 0 { dp_w(" SELFTEST FAIL T6d: expected 0 unknown, got "); dp_wn(st6[2]); dp_w("\ 888" as *u8); return 0 } 889 // ★T6f (v8) NEGATIVE CONTROL FOR THE COLLISION RULE, free from the T6 fixture: both diff.nx copies 890 // define main(), so their exports OVERLAP and they are two VERSIONS -- must NOT read as a collision. 891 if st6[5] != 0 { dp_w(" SELFTEST FAIL T6f: overlapping exports must NOT be a name collision, got "); dp_wn(st6[5]); dp_w("\n" as *u8); return 0 } 892 // ★T6e (v7) THE NEW RULE'S OWN TOOTH, negative control built in. T6 above proves a divergent TARGET 893 // still counts as a clobber hazard; this proves a divergent LIBRARY does not, and lands in the library 894 // bucket instead. BOTH halves are required -- a change that silenced both would sail through a 895 // one-sided test while quietly destroying the detector, which is the exact failure mode that let the 896 // false nx_clock.nx alarm stand. The fixtures carry NO main(), which is what makes them libraries, and 897 // they are the SAME LENGTH on purpose so the comparison must reach the byte loop rather than stopping 898 // at a size mismatch. 899 sys_mkdir("/tmp/nxdupL1" as *u8, 0x1ed) 900 sys_mkdir("/tmp/nxdupL2" as *u8, 0x1ed) 901 wfile2("/tmp/nxdupL1/lib.nx" as *u8, "func lib_alpha() -> i64 { return 1 }" as *u8) 902 wfile2("/tmp/nxdupL2/lib.nx" as *u8, "func lib_beta() -> i64 { return 22 }" as *u8) 903 let st7: *i64 = sys_mmap(64) as *i64 904 st7[0]=0; st7[1]=0; st7[2]=0; st7[3]=0; st7[4]=0 905 let c7a: *u8 = sys_mmap(K_CMP_CAP) 906 let c7b: *u8 = sys_mmap(K_CMP_CAP) 907 let d7: i64 = scan2("/tmp/nxdupL1" as *u8, "/tmp/nxdupL2" as *u8, 0, st7, c7a, c7b, K_CMP_CAP) 908 if d7 != 1 { dp_w(" SELFTEST FAIL T6e-a: expected 1 dup basename, got "); dp_wn(d7); dp_w("\n" as *u8); return 0 } 909 if st7[0] != 0 { dp_w(" SELFTEST FAIL T6e-b: a divergent LIBRARY must NOT be counted a clobber hazard, got "); dp_wn(st7[0]); dp_w("\n" as *u8); return 0 } 910 if st7[4] != 1 { dp_w(" SELFTEST FAIL T6e-c: expected exactly 1 divergent-library, got "); dp_wn(st7[4]); dp_w("\n" as *u8); return 0 } 911 // ★T6e-d (v8) THE COLLISION RULE ITSELF: lib_alpha vs lib_beta share NO exported name, so these are two 912 // PROGRAMS wearing one filename and must be flagged. With T6f above, both directions are now bitten. 913 if st7[5] != 1 { dp_w(" SELFTEST FAIL T6e-d: disjoint exports must be flagged a NAME COLLISION, got "); dp_wn(st7[5]); dp_w("\n" as *u8); return 0 } 914 // ★T6g (v9) SIGNATURE-MISMATCH TOOTH + negative control: T6 shares main() at the SAME arity and must 915 // NOT flag; this pair shares sig_go at DIFFERENT arity and MUST. Without both halves a constant-return 916 // dp_sig_mismatch would pass. 917 if st6[6] != 0 { dp_w(" SELFTEST FAIL T6g-a: equal arity must NOT be a signature mismatch, got "); dp_wn(st6[6]); dp_w("\n" as *u8); return 0 } 918 sys_mkdir("/tmp/nxdupS1" as *u8, 0x1ed) 919 sys_mkdir("/tmp/nxdupS2" as *u8, 0x1ed) 920 wfile2("/tmp/nxdupS1/sig.nx" as *u8, "func sig_go(a: *i64) -> i64 { return 1 }" as *u8) 921 wfile2("/tmp/nxdupS2/sig.nx" as *u8, "func sig_go(a: *i64, b: i64) -> i64 { return 2 }" as *u8) 922 let st8: *i64 = sys_mmap(64) as *i64 923 st8[0]=0; st8[1]=0; st8[2]=0; st8[3]=0; st8[4]=0; st8[5]=0; st8[6]=0 924 let c8a: *u8 = sys_mmap(K_CMP_CAP) 925 let c8b: *u8 = sys_mmap(K_CMP_CAP) 926 let d8: i64 = scan2("/tmp/nxdupS1" as *u8, "/tmp/nxdupS2" as *u8, 0, st8, c8a, c8b, K_CMP_CAP) 927 if d8 != 1 { dp_w(" SELFTEST FAIL T6g-b: expected 1 dup basename, got "); dp_wn(d8); dp_w("\n" as *u8); return 0 } 928 if st8[5] != 0 { dp_w(" SELFTEST FAIL T6g-c: a SHARED name must not read as a name collision, got "); dp_wn(st8[5]); dp_w("\n" as *u8); return 0 } 929 if st8[6] != 1 { dp_w(" SELFTEST FAIL T6g-d: differing arity must be flagged SIGNATURE MISMATCH, got "); dp_wn(st8[6]); dp_w("\n" as *u8); return 0 } 930 // ★T6h (v10) LIBRARY-VS-TARGET TOOTH + negative control: T6 has main() in BOTH copies and must NOT 931 // flag; this pair has main in exactly ONE and MUST. Both halves or a constant-return would pass. 932 if st6[7] != 0 { dp_w(" SELFTEST FAIL T6h-a: main() in BOTH copies must NOT flag library-vs-target, got "); dp_wn(st6[7]); dp_w("\n" as *u8); return 0 } 933 sys_mkdir("/tmp/nxdupT1" as *u8, 0x1ed) 934 sys_mkdir("/tmp/nxdupT2" as *u8, 0x1ed) 935 wfile2("/tmp/nxdupT1/mix.nx" as *u8, "func main() -> i64 { return 0 }" as *u8) 936 wfile2("/tmp/nxdupT2/mix.nx" as *u8, "func mix_helper(a: *i64) -> i64 { return 1 }" as *u8) 937 let st9: *i64 = sys_mmap(64) as *i64 938 st9[0]=0; st9[1]=0; st9[2]=0; st9[3]=0; st9[4]=0; st9[5]=0; st9[6]=0; st9[7]=0 939 let c9a: *u8 = sys_mmap(K_CMP_CAP) 940 let c9b: *u8 = sys_mmap(K_CMP_CAP) 941 let d9: i64 = scan2("/tmp/nxdupT1" as *u8, "/tmp/nxdupT2" as *u8, 0, st9, c9a, c9b, K_CMP_CAP) 942 if d9 != 1 { dp_w(" SELFTEST FAIL T6h-b: expected 1 dup basename, got "); dp_wn(d9); dp_w("\n" as *u8); return 0 } 943 if st9[7] != 1 { dp_w(" SELFTEST FAIL T6h-c: main in exactly ONE copy must flag LIBRARY-VS-TARGET, got "); dp_wn(st9[7]); dp_w("\n" as *u8); return 0 } 944 // ★T6i (v10.1) THE deadwin BRANCH, unit-tested with SYNTHETIC ranks because no fixture can reach it: 945 // dp_probe_rank returns 9 for anything outside the four ranked build dirs, so a /tmp pair always TIES 946 // and T6h above can only ever exercise the BUILD-OK side. Both directions plus the tie are asserted, so 947 // a dp_deadwin that returned a constant could not pass. A branch no fixture can reach is a branch no 948 // gate is testing -- this is the honest way to close that, and it is why dp_deadwin is a pure function. 949 if dp_deadwin(0, 0, 1, 1) != 1 { dp_w(" SELFTEST FAIL T6i-a: a rank-winner WITHOUT main must read DEAD\n" as *u8); return 0 } 950 if dp_deadwin(1, 1, 0, 0) != 1 { dp_w(" SELFTEST FAIL T6i-b: DEAD detection must not depend on argument order\n" as *u8); return 0 } 951 if dp_deadwin(0, 1, 1, 0) != 0 { dp_w(" SELFTEST FAIL T6i-c: a rank-winner WITH main builds fine (the nx_gen shape) and must NOT read DEAD\n" as *u8); return 0 } 952 if dp_deadwin(1, 0, 0, 1) != 0 { dp_w(" SELFTEST FAIL T6i-d: the mirror of T6i-c must also NOT read DEAD\n" as *u8); return 0 } 953 if dp_deadwin(9, 0, 9, 1) != 0 { dp_w(" SELFTEST FAIL T6i-e: TIED ranks support no claim and must NOT read DEAD\n" as *u8); return 0 } 954 // T7 the fingerprint must be ORDER-SENSITIVE. A plain byte total calls "ab" and "ba" equal, and a 955 // fingerprint blind to a transposition would bless a corrupted file as matching its canonical. 956 let fa: *u8 = sys_mmap(16) 957 let fb: *u8 = sys_mmap(16) 958 fa[0]=(97 as u8) 959 fa[1]=(98 as u8) 960 fb[0]=(98 as u8) 961 fb[1]=(97 as u8) 962 if dp_sum(fa,2) == dp_sum(fb,2) { dp_w(" SELFTEST FAIL T7: fingerprint is order-BLIND -- ab and ba produced the same value\ 963" as *u8); return 0 } 964 // T7b positive control. Without this, a fingerprint that returned a different value every call 965 // would sail through T7 while being useless for comparison -- the classic non-vacuity trap. 966 fb[0]=(97 as u8) 967 fb[1]=(98 as u8) 968 if dp_sum(fa,2) != dp_sum(fb,2) { dp_w(" SELFTEST FAIL T7b: identical content produced DIFFERENT fingerprints\ 969" as *u8); return 0 } 970 // T8 the freshness channel must round-trip a KNOWN value. Without this the column could return a 971 // constant, or garbage from the wrong struct offset, and every direction call built on it would be 972 // wrong in the one situation it exists for. 973 let tsb: *u8 = sys_mmap(FP_STATBUF) 974 let tms: *i64 = sys_mmap(64) as *i64 975 tms[0] = FP_TEST_MTIME 976 tms[1] = 0 977 tms[2] = FP_TEST_MTIME 978 tms[3] = 0 979 if sys_utimensat("/tmp/nxdupA/foo.nx" as *u8, tms) != 0 { dp_w(" SELFTEST FAIL T8: could not set a known mtime\ 980" as *u8); return 0 } 981 let gotm: i64 = dp_mtime("/tmp/nxdupA/foo.nx" as *u8, tsb) 982 if gotm != FP_TEST_MTIME { dp_w(" SELFTEST FAIL T8: mtime round-trip got "); dp_wn(gotm); dp_w(" want "); dp_wn(FP_TEST_MTIME); dp_w("\ 983" as *u8); return 0 } 984 // T8b an unstattable path must report -1, NOT 0. A zero would pass for a real epoch timestamp and 985 // silently rank a missing file as the oldest thing in the tree. 986 if dp_mtime("/tmp/nxdupA/does_not_exist.nx" as *u8, tsb) != (0 - 1) { dp_w(" SELFTEST FAIL T8b: a missing file did not report -1\ 987" as *u8); return 0 } 988 // T7c length must matter independently of content, or a truncation reads as a match. 989 fb[2]=(98 as u8) 990 if dp_sum(fb,2) == dp_sum(fb,3) { dp_w(" SELFTEST FAIL T7c: a truncated read produced the same fingerprint\ 991" as *u8); return 0 } 992 return 1 993} 994 995// Pass 1: count only. Cheap -- dirents only, no file is opened -- and it is what makes the total 996// honest before a single row is emitted. 997// -1 when the file cannot be stat''d. Never 0: a zero would read as a real epoch timestamp and make an 998// unstattable file look like the oldest thing in the tree. 999func dp_mtime(path: *u8, sb: *u8) -> i64 { 1000 if sys_fstatat(path, sb) != 0 { return 0 - 1 } 1001 let sp: *i64 = sb as *i64 1002 return sp[FP_MTIME_SLOT] 1003} 1004 1005func dp_fp_count(dir: *u8) -> i64 { 1006 let fd: i64 = sys_openat_rd(dir) 1007 if fd < 0 { return 0 - 1 } 1008 let dbuf: *u8 = sys_mmap(K_MAGIC_131072) 1009 var nf: i64 = 0 1010 var go: i64 = 1 1011 while go == 1 { 1012 let nr: i64 = sys_getdents64(fd, dbuf, K_MAGIC_131072) 1013 if nr <= 0 { go = 0 } else { 1014 var off: i64 = 0 1015 while off < nr { 1016 let rec: *u8 = (dbuf as i64 + off) as *u8 1017 if dirent_type(rec) != 4 { if ends_nx(dirent_name(rec)) == 1 { nf = nf + 1 } } 1018 off = off + dirent_reclen(rec) 1019 } 1020 } 1021 } 1022 sys_close(fd) 1023 return nf 1024} 1025 1026func dp_fingerprint(dir: *u8, offset: i64, limit: i64) -> i64 { 1027 let total: i64 = dp_fp_count(dir) 1028 if total < 0 { dp_w("FINGERPRINT-ERROR cannot open dir: " as *u8); dp_w(dir); dp_w("\n" as *u8); return 1 } 1029 var lim: i64 = limit 1030 if lim <= 0 { lim = FP_PAGE_DEF } 1031 if lim > FP_PAGE_MAX { lim = FP_PAGE_MAX } 1032 // The total goes out FIRST and unconditionally. Everything after it may be clipped by the 1033 // transport; this line cannot be, so shown-vs-total is always checkable by the caller. 1034 dp_w("-- FINGERPRINT dir=" as *u8) 1035 dp_w(dir) 1036 dp_w(" total=" as *u8) 1037 dp_wn(total) 1038 dp_w(" offset=" as *u8) 1039 dp_wn(offset) 1040 dp_w(" limit=" as *u8) 1041 dp_wn(lim) 1042 dp_w("\n" as *u8) 1043 let fd: i64 = sys_openat_rd(dir) 1044 if fd < 0 { dp_w("FINGERPRINT-ERROR cannot reopen dir\n" as *u8); return 1 } 1045 let dbuf: *u8 = sys_mmap(K_MAGIC_131072) 1046 let pth: *u8 = sys_mmap(K_MAGIC_4096) 1047 let cbuf: *u8 = sys_mmap(FP_FILECAP) 1048 let stb: *u8 = sys_mmap(FP_STATBUF) 1049 var idx: i64 = 0 1050 var shown: i64 = 0 1051 var nerr: i64 = 0 1052 var go: i64 = 1 1053 while go == 1 { 1054 let nr: i64 = sys_getdents64(fd, dbuf, K_MAGIC_131072) 1055 if nr <= 0 { go = 0 } else { 1056 var off: i64 = 0 1057 while off < nr { 1058 let rec: *u8 = (dbuf as i64 + off) as *u8 1059 let ty: i64 = dirent_type(rec) 1060 let nm: *u8 = dirent_name(rec) 1061 if ty != 4 { if ends_nx(nm) == 1 { 1062 if idx >= offset { if shown < lim { 1063 join_path(pth, dir, nm) 1064 let n: i64 = dp_slurp(pth, cbuf, FP_FILECAP) 1065 dp_w(nm) 1066 dp_w("\x09" as *u8) 1067 if n < 0 { 1068 // Never print a sum we could not compute. A zero here would read as a real 1069 // fingerprint and silently match another unreadable file. 1070 dp_w("ERR\x09ERR" as *u8) 1071 nerr = nerr + 1 1072 } else { 1073 dp_wn(n) 1074 dp_w("\x09" as *u8) 1075 dp_wn(dp_sum(cbuf, n)) 1076 } 1077 // Appended column (Rule 19: additive). Emitted for readable and unreadable 1078 // rows alike, so column count never varies between rows. 1079 dp_w("\x09" as *u8) 1080 dp_wn(dp_mtime(pth, stb)) 1081 dp_w("\n" as *u8) 1082 shown = shown + 1 1083 } } 1084 idx = idx + 1 1085 } } 1086 off = off + dirent_reclen(rec) 1087 } 1088 } 1089 } 1090 sys_close(fd) 1091 dp_w("-- END shown=" as *u8) 1092 dp_wn(shown) 1093 dp_w(" offset=" as *u8) 1094 dp_wn(offset) 1095 dp_w(" total=" as *u8) 1096 dp_wn(total) 1097 dp_w(" unreadable=" as *u8) 1098 dp_wn(nerr) 1099 dp_w(" more=" as *u8) 1100 if offset + shown < total { dp_wn(1) } else { dp_wn(0) } 1101 dp_w("\n" as *u8) 1102 return 0 1103} 1104 1105func main(argc: i64, argv: *i64) -> i64 { 1106 // Additive verb. With no arguments this organ behaves exactly as before (Rule 19): the full 1107 // cross-tree scan is untouched and remains the default. 1108 if argc > 2 { if dp_streq(argv[1] as *u8, "fingerprint" as *u8) == 1 { 1109 var fpoff: i64 = 0 1110 var fplim: i64 = 0 1111 if argc > 3 { fpoff = dp_atoi(argv[3] as *u8) } 1112 if argc > 4 { fplim = dp_atoi(argv[4] as *u8) } 1113 let frc: i64 = dp_fingerprint(argv[2] as *u8, fpoff, fplim) 1114 sys_exit(frc) 1115 return frc 1116 } } 1117 dp_w("nx_dup_source_check v3 (cross-tree source-dup detector -- scope DISCOVERED; allocation O(pairs) not O(files))\ 1118" as *u8) 1119 if selftest() != 1 { dp_w("verdict=RED (self-test of the detector failed)\ 1120" as *u8); sys_exit(1); return 1 } 1121 dp_w(" self-test OK (v1 regression + nested-tree tooth + non-vacuous negative control + loud-cap + 600-file RESOURCE tooth + v4 divergence-vs-identical tooth + v5 fingerprint order/identity/length teeth + v6 mtime round-trip and missing-file teeth)\ 1122" as *u8) 1123 // Tree-table size and walk depth. Both were bare literals (24 / 6) sized when the tree was 1124 // smaller; 23 of 24 slots were in use, i.e. ONE more source dir would have truncated the census 1125 // silently-in-effect. Neither is a taste value now: 1126 // MAXD -- headroom over the discovered count, and its overflow arm sets cap[0] (announces). 1127 // MAXDEPTH -- a CYCLE guard, not a tree-size estimate. A real source tree here is <8 deep 1128 // (deepest observed: buildroot/knowledge/store/wsaudit_fix/code = 5), so if 32 ever 1129 // binds you have a symlink loop, not a deep tree -- and it now ANNOUNCES either way. 1130 // The values are validated by the run printing cap_hit=0, never by assertion. 1131 let MAXD: i64 = 256 1132 let MAXDEPTH: i64 = 32 1133 let tbl: *u8 = sys_mmap(MAXD*256) 1134 // v12: cap[0]=truncation flag, cap[1]=dirs_skipped (the deliberate-exclusion counter). 16 B was 1135 // EXACTLY two i64 slots, i.e. already full at the moment a second bucket was added -- the same 1136 // one-slot-OOB trap this file's own stats[] comments warn about twice. Widened FIRST, per that 1137 // standing instruction, so the next bucket added here is not a silent neighbour-corrupting write. 1138 let cap: *i64 = sys_mmap(32) as *i64 1139 dp_w(" discovering source trees under buildroot/ ...\ 1140" as *u8) 1141 let nd: i64 = discover("buildroot" as *u8, tbl, MAXD, MAXDEPTH, cap) 1142 var i: i64 = 0 1143 while i < nd { dp_w(" source tree: "); dp_w((tbl as i64 + i*256) as *u8); dp_w("\ 1144" as *u8); i = i + 1 } 1145 dp_w(" source trees discovered: "); dp_wn(nd); dp_w("\ 1146" as *u8) 1147 var realdups: i64 = 0 1148 var pairs: i64 = 0 1149 // ★ONCE FOR THE WHOLE RUN, never per pair: 2MiB total. Per-pair would be 2MiB x 78 pairs = 156MB, 1150 // which is exactly the v2 OOM-bomb class documented in this file's header. 1151 // ★v7: WIDENED 32->64 BEFORE adding a bucket. stats[0..3] filled the old 32-byte (4-slot) 1152 // allocation EXACTLY, so writing the new library bucket at stats[4] without this would be a 1153 // one-slot out-of-bounds write -- the reg_index OOB class, silent until it corrupts a neighbour. 1154 let stats: *i64 = sys_mmap(128) as *i64 1155 stats[0] = 0 1156 stats[1] = 0 1157 stats[2] = 0 1158 stats[3] = 0 1159 stats[4] = 0 1160 stats[5] = 0 1161 stats[6] = 0 1162 // ⚠v10 USED stats[7], THE LAST SLOT of the 64-byte (8-slot) allocation, and left this instruction: 1163 // widen the mmap AND scan()'s buffer BEFORE adding a bucket. v11 does exactly that (64->128 both 1164 // here and in scan(), which is the path the SELFTEST reaches scan2 through). The same instruction 1165 // still stands for whoever adds stats[9]. 1166 stats[7] = 0 1167 stats[8] = 0 1168 let cmpa: *u8 = sys_mmap(K_CMP_CAP) 1169 let cmpb: *u8 = sys_mmap(K_CMP_CAP) 1170 i = 0 1171 while i < nd { 1172 var j: i64 = i + 1 1173 while j < nd { 1174 realdups = realdups + scan2((tbl as i64 + i*256) as *u8, (tbl as i64 + j*256) as *u8, 1, stats, cmpa, cmpb, K_CMP_CAP) 1175 pairs = pairs + 1 1176 j = j + 1 1177 } 1178 i = i + 1 1179 } 1180 dp_w(" cross-tree source-dup basenames found: "); dp_wn(realdups); dp_w("\ 1181" as *u8) 1182 dp_w(" LIBRARY-VS-TARGET="); dp_wn(stats[7]) 1183 dp_w(" <- one copy has main(), the other does not. Where the RANK-WINNING copy LACKS main the target is permanently rc=102 (nx_hls_parse, id 1785520111). Where it HAS main the target builds FINE and the main-less copy is the unreachable one (nx_gen: the R5 generator; nx_tissue: the voxel-grid lib -- its NOELF means never-deployed, NOT un-buildable). Only a RENAME clears either\n" as *u8) 1184 dp_w(" TRIAGE (seq207/v10): divergent-target="); dp_wn(stats[0]) 1185 dp_w(" <- REAL clobber hazards (TARGETS only -- these resolve by probe rank), fix FIRST | divergent-library="); dp_wn(stats[4]) 1186 dp_w(" (resolved per-importer from its OWN dir, so NOT work-loss) | NAME-COLLISIONS="); dp_wn(stats[5]) 1187 dp_w(" <- DIFFERENT PROGRAMS sharing one filename (disjoint exports): RENAME, never merge -- the 2026-07-21 dedupe deleted live capabilities (nx_http_health, nx_teacher) by treating exactly this as copies | SIGNATURE-MISMATCH="); dp_wn(stats[6]) 1188 dp_w(" <- same exported name, DIFFERENT arity: the SILENT class, binds without a diagnostic (nx_input_abstract ia_init) | identical="); dp_wn(stats[1]) 1189 dp_w(" litter (latent) | out-of-probe-path="); dp_wn(stats[3]); dp_w(" (divergent but a build never reads that tree -- litter, F1127) | unknown="); dp_wn(stats[2]); dp_w(" (never assumed identical)\ 1190" as *u8) 1191 // ★v11: publish the DERIVED sync-noise floor next to the freshness rows it qualifies. Without this 1192 // the FRESHNESS direction reads as fact, and most gaps in this tree are 4-22s -- indistinguishable 1193 // from the bulk-sync clustering. State the timescale next to the verdict. 1194 dp_w(" SYNC-NOISE FLOOR: "); dp_wn(stats[8]) 1195 dp_w("s = the LARGEST mtime gap between two BYTE-IDENTICAL copies in this run. Identical copies cannot have been edited independently, so that gap is PURE SYNC NOISE, measured from this run's own data and not chosen. ANY 'FRESHNESS: newer copy is in ...' row above whose gap is <= that floor is NOT RESOLVABLE by mtime -- the DIVERGENCE is still real (the bytes differ) and the clobber verdict below does NOT depend on mtime (it keys on probe rank), but the work-loss-NOW vs correct-today ORDERING does. Reconcile those by content or build evidence, never by the timestamp.\n" as *u8) 1196 dp_w(" ENVELOPE: pairs_compared="); dp_wn(pairs) 1197 dp_w(" trees="); dp_wn(nd) 1198 dp_w(" tree_cap="); dp_wn(MAXD) 1199 dp_w(" depth_cap="); dp_wn(MAXDEPTH) 1200 dp_w(" cap_hit="); dp_wn(cap[0]) 1201 dp_w(" dirs_skipped="); dp_wn(cap[1]) 1202 dp_w(" allocs=O(pairs+dirs) NOT O(files) [v2 regression guard: the per-file mmap in exists() leaked ~1GB/run and starved the host]") 1203 dp_w(" (counts are per-pair: one basename in three trees counts twice -- an upper bound on PAIRWISE hazards, not a distinct-basename count)\ 1204" as *u8) 1205 // ***v12 PUBLISH THE SUMMARY WHERE IT CAN ACTUALLY BE READ -- see dsx_publish above. 1206 // The verdict is decided HERE, ONCE, into a token, and the SAME token is written to the status 1207 // file that the stdout branches below re-state. One decision, so the published verdict and the 1208 // exit code cannot drift apart by construction -- which is exactly what a second, hand-written 1209 // verdict string would have reintroduced. 1210 var vtok: *u8 = "GREEN" as *u8 1211 if stats[0] != 0 { vtok = "RED-clobber-hazard" as *u8 } 1212 if cap[0] == 1 { vtok = "RED-TRUNCATED" as *u8 } 1213 let sbuf: *u8 = sys_mmap(K_MAGIC_4096) 1214 var sp: i64 = 0 1215 sp = dsx_str(sbuf, sp, "NX-DERIVED: regenerated artefact, not authored memory" as *u8); sp = dsx_nl(sbuf, sp) 1216 sp = dsx_str(sbuf, sp, "nx_dup_source_check v12 SUMMARY, truncate-written every run. The stdout artifact is ~14 MB and EVERY caller truncates before reaching its tail (163840 B sync, 1048576 B async), so the partition and the verdict live HERE. verdict= is the LAST line by contract, so a positional reader such as gv_last_line still works." as *u8); sp = dsx_nl(sbuf, sp) 1217 sp = dsx_row(sbuf, sp, "trees" as *u8, nd) 1218 sp = dsx_row(sbuf, sp, "tree_cap" as *u8, MAXD) 1219 sp = dsx_row(sbuf, sp, "depth_cap" as *u8, MAXDEPTH) 1220 sp = dsx_row(sbuf, sp, "pairs_compared" as *u8, pairs) 1221 sp = dsx_row(sbuf, sp, "cap_hit" as *u8, cap[0]) 1222 sp = dsx_row(sbuf, sp, "dirs_skipped" as *u8, cap[1]) 1223 sp = dsx_row(sbuf, sp, "dup_basenames" as *u8, realdups) 1224 sp = dsx_row(sbuf, sp, "divergent_target" as *u8, stats[0]) 1225 sp = dsx_row(sbuf, sp, "divergent_library" as *u8, stats[4]) 1226 sp = dsx_row(sbuf, sp, "name_collisions" as *u8, stats[5]) 1227 sp = dsx_row(sbuf, sp, "signature_mismatch" as *u8, stats[6]) 1228 sp = dsx_row(sbuf, sp, "library_vs_target" as *u8, stats[7]) 1229 sp = dsx_row(sbuf, sp, "identical" as *u8, stats[1]) 1230 sp = dsx_row(sbuf, sp, "out_of_probe_path" as *u8, stats[3]) 1231 sp = dsx_row(sbuf, sp, "unknown" as *u8, stats[2]) 1232 sp = dsx_row(sbuf, sp, "sync_noise_floor_s" as *u8, stats[8]) 1233 // A PARTITION IS A CLAIM: PRINT THE PARTS AND THE SUM SO A READER CAN RECONCILE IT WITHOUT RERUNNING. 1234 sp = dsx_row(sbuf, sp, "partition_sum" as *u8, stats[0]+stats[1]+stats[2]+stats[3]+stats[4]+stats[5]+stats[6]) 1235 sp = dsx_str(sbuf, sp, "partition_note=identical + out_of_probe_path + divergent_target + divergent_library + name_collisions + signature_mismatch + unknown. Compare against dup_basenames, remembering the counts are PER-PAIR: one basename in three trees counts twice." as *u8); sp = dsx_nl(sbuf, sp) 1236 sp = dsx_str(sbuf, sp, "verdict=" as *u8); sp = dsx_str(sbuf, sp, vtok); sp = dsx_nl(sbuf, sp) 1237 let swr: i64 = dsx_publish("knowledge/status/dup_source.status" as *u8, sbuf, sp) 1238 dp_w(" STATUS-FILE knowledge/status/dup_source.status bytes="); dp_wn(swr) 1239 if swr < 0 { dp_w(" <- COULD NOT WRITE. Announced, never silent: a status file that failed to publish must not be indistinguishable from one that had nothing to say.") } 1240 dp_w("\ 1241" as *u8) 1242 if cap[0] == 1 { 1243 dp_w("verdict=RED (TRUNCATED -- a bound was hit; this scan is INCOMPLETE, raise MAXD/MAXDEPTH before trusting it)\ 1244" as *u8) 1245 sys_exit(3); return 3 1246 } 1247 // ★F1127: the verdict keys on ACTIONABLE HAZARDS (divergent copies both sitting in trees a build 1248 // actually probes), not on the raw count of duplicate basenames. Identical litter and 1249 // out-of-probe-path divergence are still REPORTED above -- they are real housekeeping -- but they 1250 // are not clobber hazards, and a permanent RED that no action can clear teaches readers to ignore 1251 // the verdict. GREEN here means "nothing can silently regress a build", which is the claim the 1252 // organ actually supports; the litter counts remain visible for anyone who wants to tidy. 1253 if stats[0] == 0 { 1254 dp_w("verdict=GREEN (no clobber hazards: every remaining duplicate is identical litter or lives outside the build probe path, so no build can resolve a stale copy)\ 1255" as *u8) 1256 sys_exit(0); return 0 1257 } 1258 dp_w("verdict=RED (clobber hazard(s) present -- reconcile each basename to ONE canonical source dir; see debt seq207/seq281/seq284)\ 1259" as *u8) 1260 sys_exit(3); return 3 1261}