code wiki / _hdl_build / nx_treediff.nx

nx_treediff.nx source

↩ module page · 218 lines · 10051 B

1// nx_treediff.nx -- CROSS-TREE SOURCE DIVERGENCE CENSUS (laptop SSOT vs NAS buildroot). 2// 3// THE GAP IT CLOSES (filed seq1429, hit FOUR times in one session 2026-07-30): 4// seq768 the build-admission enforcement was in NEITHER tree 5// seq1393 five compiler defects existed ONLY in the buildroot copy 6// seq1419 the deployed compiler was fine; the buildroot copy was the divergent one 7// seq1429 nx_office_daemon/nx_survey_daemon were stale in buildroot while the SSOT was migrated 8// nx_dup_source_check finds duplicate basenames WITHIN buildroot. NOTHING compared the buildroot 9// against the laptop SSOT, so every one of those was found by hand, by accident, after the damage. 10// 11// WHY IT MATTERS BEYOND TIDINESS: /api/build compiles from BUILDROOT. A fix that lives only in the 12// SSOT never reaches a running binary; a fix that lives only in buildroot is destroyed by the next 13// sync from the SSOT. BOTH DIRECTIONS SILENTLY LOSE WORK, which is why divergence must be a REPORT, 14// not an archaeology exercise. 15// 16// DESIGN: this half runs ON THE NAS and emits a manifest of `<bytes> <relpath>` for one tree, in a 17// stable sorted-by-path order. The caller diffs two manifests (one per tree). Byte SIZE is a 18// deliberately cheap discriminator -- it caught all four of today's instances -- and it needs no hash 19// primitive on either side. A size match is NOT proof of identity; it is a screen that turns an 20// unbounded search into a short candidate list, and it says so in its own output rather than implying 21// certainty it does not have. 22// 23// FAIL-CLOSED + HONEST ENVELOPE: every cap is declared in-band (files scanned, cap hit, depth). A 24// scanner that silently truncates its own coverage is the self-ceiling defect (law L011) -- so if a 25// cap is reached the verdict SAYS SO and reports PARTIAL rather than presenting a floor as a total. 26// DIALECT: plain-if, no empty literals, <=6 params, consts above use. 27import "nx_syscalls.nx" 28 29const TD_MAX_FILES: i64 = 40000 // upper bound on emitted rows; declared, never silent 30const TD_MAX_DEPTH: i64 = 8 // recursion depth cap 31const TD_PATH_CAP: i64 = 1024 32const TD_NAME_CAP: i64 = 256 33const TD_DIRENT_BUF: i64 = 65536 34 35const TD_OUTBUF: i64 = 4194304 // 4 MiB manifest buffer (~19.8k rows x ~60B, with headroom) 36 37static td_count: *i64 // [0]=rows emitted [1]=dirs walked [2]=1 if a cap was hit [3]=1 if outbuf full 38// MANIFEST SINK (2026-07-30). The first cut wrote rows to STDOUT, which capped the census at whatever 39// the MCP tools/call response allows: the first real run returned only 4447 of ~19800 NAS rows 40// (168600 B), so the measured "655 differing files" was a FLOOR bounded by a TRANSPORT limit rather 41// than by the corpus. A census whose coverage is decided by its caller's payload cap is the same 42// self-ceiling defect it is supposed to detect. With an output path the rows go to a file (read back 43// with nx_fs) and stdout carries only the envelope, so the response size is constant regardless of 44// tree size -- the same shape as the codewiki _async=1 fix. 45static td_out: *u8 46static td_out_n: *i64 47 48func td_puts(s: *u8) -> i64 { 49 var n: i64 = 0 50 while s[n] != (0 as u8) { n = n + 1 } 51 sys_write(1, s, n) 52 return 0 53} 54func td_putn(v: i64) -> i64 { 55 let b: *u8 = sys_mmap(32) 56 var m: i64 = v 57 if m == 0 { b[0] = 48 as u8; sys_write(1, b, 1); return 0 } 58 if m < 0 { td_puts("-" as *u8); m = 0 - m } 59 let t: *u8 = sys_mmap(32) 60 var k: i64 = 0 61 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 62 var i: i64 = 0 63 while i < k { b[i] = t[k - 1 - i]; i = i + 1 } 64 sys_write(1, b, k) 65 return 0 66} 67func td_cat(d: *u8, o: i64, s: *u8) -> i64 { 68 var a: i64 = o 69 var i: i64 = 0 70 while s[i] != (0 as u8) { d[a] = s[i]; a = a + 1; i = i + 1 } 71 d[a] = 0 as u8 72 return a 73} 74func td_ends_nx(nm: *u8) -> i64 { 75 var n: i64 = 0 76 while nm[n] != (0 as u8) { n = n + 1 } 77 if n < 3 { return 0 } 78 if nm[n-3] != (46 as u8) { return 0 } 79 if nm[n-2] != (110 as u8) { return 0 } 80 if nm[n-1] != (120 as u8) { return 0 } 81 return 1 82} 83// file size via fstatat; -1 when unreadable (reported, never silently 0) 84func td_size(path: *u8) -> i64 { 85 let stb: *u8 = sys_mmap(160) 86 if sys_fstatat(path, stb) != 0 { return 0 - 1 } 87 let szp: *i64 = ((stb as i64) + 48) as *i64 // st_size @ +48 (x86_64 struct stat) 88 return szp[0] 89} 90 91// append "<bytes> <relpath>\n" -- to the manifest buffer when an output path was given, else stdout. 92// FAIL-CLOSED ON OVERFLOW: a full buffer sets td_count[3] so the envelope reports PARTIAL. It must 93// never wrap or silently drop rows; a census that under-reports without saying so is the defect. 94func td_row(sz: i64, rel: *u8) -> i64 { 95 if (td_out as i64) == 0 { 96 td_putn(sz); td_puts(" " as *u8); td_puts(rel); td_puts("\n" as *u8) 97 return 0 98 } 99 let nb: *u8 = sys_mmap(64) 100 var k: i64 = 0 101 var m: i64 = sz 102 if m < 0 { nb[0] = 45 as u8; k = 1; m = 0 - m } 103 if m == 0 { nb[k] = 48 as u8; k = k + 1 } 104 let t: *u8 = sys_mmap(32) 105 var j: i64 = 0 106 while m > 0 { t[j] = (48 + (m % 10)) as u8; m = m / 10; j = j + 1 } 107 while j > 0 { nb[k] = t[j - 1]; k = k + 1; j = j - 1 } 108 var rl: i64 = 0 109 while rel[rl] != (0 as u8) { rl = rl + 1 } 110 let need: i64 = k + 1 + rl + 1 111 var o: i64 = td_out_n[0] 112 if o + need >= TD_OUTBUF { td_count[3] = 1; return 0 } 113 var i: i64 = 0 114 while i < k { td_out[o] = nb[i]; o = o + 1; i = i + 1 } 115 td_out[o] = 32 as u8; o = o + 1 116 i = 0 117 while i < rl { td_out[o] = rel[i]; o = o + 1; i = i + 1 } 118 td_out[o] = 10 as u8; o = o + 1 119 td_out_n[0] = o 120 return 0 121} 122 123func td_walk(dir: *u8, rel: *u8, depth: i64) -> i64 { 124 if depth > TD_MAX_DEPTH { td_count[2] = 1; return 0 } 125 let fd: i64 = sys_openat_rd(dir) 126 if fd < 0 { return 0 } 127 td_count[1] = td_count[1] + 1 128 let buf: *u8 = sys_mmap(TD_DIRENT_BUF) 129 var go: i64 = 1 130 while go == 1 { 131 let n: i64 = sys_getdents64(fd, buf, TD_DIRENT_BUF) 132 if n <= 0 { go = 0 } 133 if n > 0 { 134 var off: i64 = 0 135 while off < n { 136 // the dirent_* helpers take the RECORD POINTER, not (buf, offset) -- caught by the 137 // compiler's arity check, which is exactly the seal that makes this class fail closed. 138 let rec: *u8 = ((buf as i64) + off) as *u8 139 let rl: i64 = dirent_reclen(rec) 140 let ty: i64 = dirent_type(rec) 141 let nm: *u8 = dirent_name(rec) 142 var skip: i64 = 0 143 if nm[0] == (46 as u8) { skip = 1 } // . .. and dotfiles 144 if skip == 0 { 145 let sub: *u8 = sys_mmap(TD_PATH_CAP) 146 var o: i64 = td_cat(sub, 0, dir) 147 o = td_cat(sub, o, "/" as *u8) 148 o = td_cat(sub, o, nm) 149 let srel: *u8 = sys_mmap(TD_PATH_CAP) 150 var r: i64 = td_cat(srel, 0, rel) 151 if rel[0] != (0 as u8) { r = td_cat(srel, r, "/" as *u8) } 152 r = td_cat(srel, r, nm) 153 if ty == 4 { td_walk(sub, srel, depth + 1) } 154 if ty != 4 { 155 if td_ends_nx(nm) == 1 { 156 if td_count[0] >= TD_MAX_FILES { td_count[2] = 1 } else { 157 td_row(td_size(sub), srel) 158 td_count[0] = td_count[0] + 1 159 } 160 } 161 } 162 } 163 off = off + rl 164 } 165 } 166 } 167 sys_close(fd) 168 return 0 169} 170 171func main(argc: i64, argv: *i64) -> i64 { 172 if argc < 2 { 173 td_puts("usage: nx_treediff <dir> [outfile] -- '<bytes> <relpath>' per .nx file, for cross-tree\n" as *u8) 174 td_puts(" comparison. With [outfile] the manifest is WRITTEN THERE (read it back with nx_fs)\n" as *u8) 175 td_puts(" and stdout carries only the envelope, so a transport payload cap can never bound\n" as *u8) 176 td_puts(" coverage. Without it, rows go to stdout (fine for small trees).\n" as *u8) 177 sys_exit(2) 178 return 2 179 } 180 td_count = sys_mmap(32) as *i64 181 td_count[0] = 0 182 td_count[1] = 0 183 td_count[2] = 0 184 td_count[3] = 0 185 td_out_n = sys_mmap(16) as *i64 186 td_out_n[0] = 0 187 var outp: *u8 = 0 as *u8 188 if argc > 2 { outp = argv[2] as *u8; td_out = sys_mmap(TD_OUTBUF) } 189 let root: *u8 = argv[1] as *u8 190 td_walk(root, "" as *u8, 0) 191 if (td_out as i64) != 0 { 192 let fd: i64 = sys_openat_wr(outp, 0x1a4) 193 if fd < 0 { 194 td_puts("# TREEDIFF RED -- cannot open outfile: " as *u8); td_puts(outp); td_puts("\n" as *u8) 195 sys_exit(3); return 3 196 } 197 sys_write(fd, td_out, td_out_n[0]) 198 sys_close(fd) 199 td_puts("# manifest written: " as *u8); td_puts(outp) 200 td_puts(" bytes=" as *u8); td_putn(td_out_n[0]); td_puts("\n" as *u8) 201 } 202 // ENVELOPE IN-BAND: a scanner that hides its coverage presents a floor as a total (law L011). 203 td_puts("# TREEDIFF-MANIFEST files=" as *u8); td_putn(td_count[0]) 204 td_puts(" dirs=" as *u8); td_putn(td_count[1]) 205 td_puts(" file_cap=" as *u8); td_putn(TD_MAX_FILES) 206 td_puts(" depth_cap=" as *u8); td_putn(TD_MAX_DEPTH) 207 td_puts(" cap_hit=" as *u8); td_putn(td_count[2]) 208 td_puts(" outbuf_full=" as *u8); td_putn(td_count[3]) 209 var partial: i64 = 0 210 if td_count[2] == 1 { partial = 1 } 211 if td_count[3] == 1 { partial = 1 } 212 if partial == 1 { td_puts(" verdict=PARTIAL (coverage truncated -- raise a cap deliberately, never read this as a total)" as *u8) } 213 if partial == 0 { td_puts(" verdict=COMPLETE" as *u8) } 214 td_puts("\n# NOTE: size is a SCREEN, not proof of identity -- equal sizes can still differ. It is cheap,\n" as *u8) 215 td_puts("# needs no hash primitive on either side, and caught all four 2026-07-30 divergences.\n" as *u8) 216 sys_exit(0) 217 return 0 218}