code wiki / _hdl_build / nx_split_resolve.nx

nx_split_resolve.nx source

↩ module page · 327 lines · 16253 B

1// nx_split_resolve.nx -- SPLIT-BRAIN resolver: byte-provable RENAME+REPOINT for a DIVERGENT lib that 2// exists (with DIFFERENT content) in two dirs, where different consumers silently resolve different 3// copies. Retirement (nx_consol_apply_mc) REFUSES these (the copy is live for someone). The sound 4// resolution is to give each copy a DISTINCT name IN PLACE (so its own relative imports still resolve) 5// and repoint every consumer to the copy that reproduces ITS build BYTE-FOR-BYTE. Commit ONLY if every 6// buildable consumer's artifact is byte-identical to its pre-change build, else restore every source + 7// undo both renames (never-brick). Keeps BOTH versions (Rule 25: make the dependency explicit). 8// 9// TRANSITIVE case: a direct importer that is itself a LIB (no main, does not build standalone) cannot be 10// byte-verified directly. It is repointed by DIRECTORY RULE (the copy in its own dir) and verified 11// TRANSITIVELY -- a buildable consumer that imports it will change if it was repointed wrong. A coverage 12// guard REFUSES if a lib-importer is imported by no buildable consumer in the list (unverifiable). 13// argv: <lib> <dir1> <tag1> <dir2> <tag2> <buildonly-elf> <consumer-srcpath>... 14// e.g. nx_split_resolve nx_bom runtime rt runtime/_hdl_build hb _offc/nx_buildonly.elf <5 consumer paths> 15// exit: 0 RESOLVED | 1 REFUSED (all restored) | 2 usage/precondition | 3 filesystem move error 16// license_tier: ORIGINAL No hw writes (Rule 26): edits .nx source + rebuilds only. 17import "nx_seat_drive_lib.nx" 18import "nx_seg_store.nx" 19import "nx_deploy_lib.nx" 20import "nx_syscalls.nx" 21 22const SR_CAP: i64 = 4194304 23const SR_MAXC: i64 = 64 24 25func sr_strlen(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { i = i + 1 } return i } 26func sr_read(path: *u8, buf: *u8, cap: i64) -> i64 { 27 let fd: i64 = sys_openat_rd(path) 28 if fd < 0 { return 0 - 1 } 29 var n: i64 = 0 30 var r: i64 = sys_read(fd, buf, cap - 1) 31 while r > 0 { n = n + r; if n >= cap - 1 { r = 0 } else { r = sys_read(fd, buf + n, cap - 1 - n) } } 32 sys_close(fd) 33 return n 34} 35func sr_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { if an != bn { return 0 } var i: i64 = 0; while i < an { if a[i] != b[i] { return 0 } i = i + 1 } return 1 } 36func sr_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] == b[i] { if a[i] == (0 as u8) { return 1 } i = i + 1 } return 0 } 37// build the quoted import filename token: "<lib>.nx" (tag=0) or "<lib>_<tag>.nx". real quotes, byte-wise. 38func sr_mktok(out: *u8, lib: *u8, tag: *u8) -> i64 { 39 var o: i64 = 0 40 out[o] = 34 as u8; o = o + 1 41 let ll: i64 = sr_strlen(lib) 42 var i: i64 = 0 43 while i < ll { out[o] = lib[i]; o = o + 1; i = i + 1 } 44 if (tag as i64) != 0 { 45 out[o] = 95 as u8; o = o + 1 46 let tl: i64 = sr_strlen(tag) 47 i = 0 48 while i < tl { out[o] = tag[i]; o = o + 1; i = i + 1 } 49 } 50 out[o] = 46 as u8; o = o + 1 51 out[o] = 110 as u8; o = o + 1 52 out[o] = 120 as u8; o = o + 1 53 out[o] = 34 as u8; o = o + 1 54 out[o] = 0 as u8 55 return o 56} 57// copy src->out, replacing EVERY occurrence of oldt with newt. return outlen, or -1 if oldt not present. 58func sr_replace(src: *u8, n: i64, oldt: *u8, ol: i64, newt: *u8, nl: i64, out: *u8) -> i64 { 59 var oi: i64 = 0 60 var i: i64 = 0 61 var found: i64 = 0 62 while i < n { 63 var mch: i64 = 0 64 if i + ol <= n { 65 mch = 1 66 var j: i64 = 0 67 while j < ol { if src[i + j] != oldt[j] { mch = 0; j = ol } else { j = j + 1 } } 68 } 69 if mch == 1 { 70 var k: i64 = 0 71 while k < nl { out[oi] = newt[k]; oi = oi + 1; k = k + 1 } 72 i = i + ol 73 found = found + 1 74 } else { out[oi] = src[i]; oi = oi + 1; i = i + 1 } 75 } 76 if found == 0 { return 0 - 1 } 77 return oi 78} 79func sr_contains(buf: *u8, n: i64, tok: *u8, tl: i64) -> i64 { 80 var i: i64 = 0 81 while i + tl <= n { 82 var m: i64 = 1 83 var j: i64 = 0 84 while j < tl { if buf[i + j] != tok[j] { m = 0; j = tl } else { j = j + 1 } } 85 if m == 1 { return 1 } 86 i = i + 1 87 } 88 return 0 89} 90// strip dir + trailing .nx -> basename (build target). out null-terminated. return len. 91func sr_basename(path: *u8, out: *u8) -> i64 { 92 let L: i64 = sr_strlen(path) 93 var st: i64 = 0 94 var i: i64 = 0 95 while i < L { if path[i] == (47 as u8) { st = i + 1 } i = i + 1 } 96 var en: i64 = L 97 if L >= 3 { if path[L - 1] == (120 as u8) { if path[L - 2] == (110 as u8) { if path[L - 3] == (46 as u8) { en = L - 3 } } } } 98 var o: i64 = 0 99 var k: i64 = st 100 while k < en { out[o] = path[k]; o = o + 1; k = k + 1 } 101 out[o] = 0 as u8 102 return o 103} 104// dir part of a path (everything before the last '/'). out null-terminated. return len. 105func sr_dirname(path: *u8, out: *u8) -> i64 { 106 let L: i64 = sr_strlen(path) 107 var cut: i64 = 0 108 var i: i64 = 0 109 while i < L { if path[i] == (47 as u8) { cut = i } i = i + 1 } 110 var o: i64 = 0 111 while o < cut { out[o] = path[o]; o = o + 1 } 112 out[o] = 0 as u8 113 return o 114} 115// build target tname (unlink artifact first so a failed build leaves no stale artifact). return bytes. 116func sr_build(buildelf: *u8, tname: *u8, artpath: *u8, buf: *u8) -> i64 { 117 sys_unlinkat(artpath) 118 let av: *i64 = sys_mmap(16) as *i64 119 av[0] = tname as i64 120 dep_run_capture(buildelf, av, 1, "/tmp/sr_build.log" as *u8) 121 return sr_read(artpath, buf, SR_CAP) 122} 123// helper: write /tmp/<base>.<suffix> into out. return len. 124func sr_tmpp(out: *u8, base: *u8, suffix: *u8) -> i64 { 125 var q: i64 = sd_cat(out, 0, "/tmp/" as *u8); q = sd_cat(out, q, base); q = sd_cat(out, q, suffix); return q 126} 127 128func main(argc: i64, argv: *i64) -> i64 { 129 if argc < 8 { 130 sd_w("usage: nx_split_resolve <lib> <dir1> <tag1> <dir2> <tag2> <buildonly-elf> <consumer-srcpath>...\n" as *u8) 131 sys_exit(2); return 2 132 } 133 let lib: *u8 = argv[1] as *u8 134 let dir1: *u8 = argv[2] as *u8 135 let tag1: *u8 = argv[3] as *u8 136 let dir2: *u8 = argv[4] as *u8 137 let tag2: *u8 = argv[5] as *u8 138 let buildelf: *u8 = argv[6] as *u8 139 let nc: i64 = argc - 7 140 if nc > SR_MAXC { sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=too-many-consumers fix=raise-SR_MAXC-or-batch. NOTHING-CHANGED\n" as *u8); sys_exit(2); return 2 } 141 142 let p1: *u8 = sys_mmap(512) 143 let p2: *u8 = sys_mmap(512) 144 let n1: *u8 = sys_mmap(512) 145 let n2: *u8 = sys_mmap(512) 146 var o: i64 = 0 147 o = sd_cat(p1, 0, dir1); o = sd_cat(p1, o, "/" as *u8); o = sd_cat(p1, o, lib); o = sd_cat(p1, o, ".nx" as *u8) 148 o = sd_cat(p2, 0, dir2); o = sd_cat(p2, o, "/" as *u8); o = sd_cat(p2, o, lib); o = sd_cat(p2, o, ".nx" as *u8) 149 o = sd_cat(n1, 0, dir1); o = sd_cat(n1, o, "/" as *u8); o = sd_cat(n1, o, lib); o = sd_cat(n1, o, "_" as *u8); o = sd_cat(n1, o, tag1); o = sd_cat(n1, o, ".nx" as *u8) 150 o = sd_cat(n2, 0, dir2); o = sd_cat(n2, o, "/" as *u8); o = sd_cat(n2, o, lib); o = sd_cat(n2, o, "_" as *u8); o = sd_cat(n2, o, tag2); o = sd_cat(n2, o, ".nx" as *u8) 151 152 let oldtok: *u8 = sys_mmap(256) 153 let ntok1: *u8 = sys_mmap(256) 154 let ntok2: *u8 = sys_mmap(256) 155 sr_mktok(oldtok, lib, 0 as *u8) 156 sr_mktok(ntok1, lib, tag1) 157 sr_mktok(ntok2, lib, tag2) 158 let oll: i64 = sr_strlen(oldtok) 159 let nl1: i64 = sr_strlen(ntok1) 160 let nl2: i64 = sr_strlen(ntok2) 161 162 let srcbuf: *u8 = sys_mmap(SR_CAP) 163 let outbuf: *u8 = sys_mmap(SR_CAP) 164 let artbuf: *u8 = sys_mmap(SR_CAP) 165 let befbuf: *u8 = sys_mmap(SR_CAP) 166 let base: *u8 = sys_mmap(256) 167 let dbase: *u8 = sys_mmap(256) 168 let ldir: *u8 = sys_mmap(512) 169 let covtok: *u8 = sys_mmap(256) 170 let artp: *u8 = sys_mmap(512) 171 let srsrc: *u8 = sys_mmap(512) 172 let srbef: *u8 = sys_mmap(512) 173 let matched: *i64 = sys_mmap(8 * SR_MAXC) as *i64 174 let blt: *i64 = sys_mmap(8 * SR_MAXC) as *i64 175 176 // PHASE 1: snapshot each consumer's ORIGINAL source; CLASSIFY buildable (build standalone -> witness) 177 // vs lib (repoint-only). No lib touched yet -> any ABORT here is clean. Non-building = lib, NOT abort. 178 var nbuild: i64 = 0 179 var c: i64 = 0 180 while c < nc { 181 let srcpath: *u8 = argv[7 + c] as *u8 182 sr_basename(srcpath, base) 183 sr_tmpp(artp, base, ".sov.elf" as *u8) 184 sr_tmpp(srsrc, base, ".srsrc" as *u8) 185 sr_tmpp(srbef, base, ".srbefore" as *u8) 186 let sn: i64 = sr_read(srcpath, srcbuf, SR_CAP) 187 if sn <= 0 { sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=consumer-source-unreadable:" as *u8); sd_w(srcpath); sd_w(" fix=check-the-srcpath. NOTHING-CHANGED\n" as *u8); sys_exit(2); return 2 } 188 ss_writefile(srsrc, srcbuf, sn) 189 let bn: i64 = sr_build(buildelf, base, artp, artbuf) 190 if bn > 0 { blt[c] = 1; ss_writefile(srbef, artbuf, bn); nbuild = nbuild + 1 } else { blt[c] = 0 } 191 matched[c] = 0 192 c = c + 1 193 } 194 if nbuild == 0 { 195 sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=no-consumer-builds-standalone(no-witness-to-prove-byte-safety) fix=at-least-one-listed-consumer-must-be-a-buildable-organ(not-all-libs);-add-a-buildable-organ-that-imports-the-lib. NOTHING-CHANGED\n" as *u8) 196 sys_exit(2); return 2 197 } 198 199 // COVERAGE GUARD + DIR-RULE: every LIB importer must (a) be imported by >=1 buildable witness (else 200 // its repoint is unverifiable), and (b) live in dir1 or dir2 so its correct copy is known. Assign its 201 // tag by directory now (verified transitively later). 202 c = 0 203 while c < nc { 204 if blt[c] == 0 { 205 let srcpath: *u8 = argv[7 + c] as *u8 206 sr_basename(srcpath, base) 207 sr_mktok(covtok, base, 0 as *u8) 208 let covl: i64 = sr_strlen(covtok) 209 var covered: i64 = 0 210 var d: i64 = 0 211 while d < nc { 212 if blt[d] == 1 { 213 sr_basename(argv[7 + d] as *u8, dbase) 214 sr_tmpp(srsrc, dbase, ".srsrc" as *u8) 215 let dn: i64 = sr_read(srsrc, befbuf, SR_CAP) 216 if dn > 0 { if sr_contains(befbuf, dn, covtok, covl) == 1 { covered = 1; d = nc } } 217 } 218 d = d + 1 219 } 220 if covered == 0 { 221 sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=lib-importer-not-imported-by-any-buildable-witness:" as *u8); sd_w(base) 222 sd_w(" fix=it-does-not-build-standalone-AND-no-listed-buildable-organ-imports-it-so-its-repoint-cannot-be-byte-verified;-add-a-buildable-organ-that-(transitively)-imports-it,-or-remove-it. NOTHING-CHANGED\n" as *u8) 223 sys_exit(2); return 2 224 } 225 sr_dirname(srcpath, ldir) 226 if sr_streq(ldir, dir1) == 1 { matched[c] = 1 } else { 227 if sr_streq(ldir, dir2) == 1 { matched[c] = 2 } else { 228 sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=lib-importer-not-in-dir1-or-dir2(cannot-place):" as *u8); sd_w(base) 229 sd_w(" fix=this-lib-lives-in-a-third-dir;-the-directory-rule-cannot-decide-which-copy-it-resolves;-list-only-libs-under-dir1/dir2. NOTHING-CHANGED\n" as *u8) 230 sys_exit(2); return 2 231 } 232 } 233 } 234 c = c + 1 235 } 236 237 // PHASE 2: both copies must exist and genuinely DIFFER, then rename each IN PLACE. 238 let l1: i64 = sr_read(p1, srcbuf, SR_CAP) 239 let l2: i64 = sr_read(p2, outbuf, SR_CAP) 240 if l1 <= 0 { sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=copy1-not-found:" as *u8); sd_w(p1); sd_w(" fix=check-dir1/lib. NOTHING-CHANGED\n" as *u8); sys_exit(2); return 2 } 241 if l2 <= 0 { sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=copy2-not-found:" as *u8); sd_w(p2); sd_w(" fix=check-dir2/lib. NOTHING-CHANGED\n" as *u8); sys_exit(2); return 2 } 242 if sr_eq(srcbuf, l1, outbuf, l2) == 1 { 243 sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=the-two-copies-are-BYTE-IDENTICAL(not-a-split-brain) fix=this-is-a-DEAD-shadow-not-a-divergence:-use-nx_consol_apply_mc-to-retire-one-copy. NOTHING-CHANGED\n" as *u8) 244 sys_exit(2); return 2 245 } 246 if sys_renameat(p1, n1) != 0 { sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=rename-copy1-failed(filesystem) fix=check-write-perms-on-dir1. NOTHING-CHANGED\n" as *u8); sys_exit(3); return 3 } 247 if sys_renameat(p2, n2) != 0 { 248 sys_renameat(n1, p1) 249 sd_w("NX-SPLIT-RESOLVE verdict=ABORTED reason=rename-copy2-failed(copy1-rename-UNDONE) fix=check-write-perms-on-dir2. NOTHING-CHANGED\n" as *u8); sys_exit(3); return 3 250 } 251 252 // PHASE 3a: repoint every LIB importer by its directory-rule tag (from ORIGINAL source). Must happen 253 // BEFORE the buildable witnesses build, so their builds pull the correctly-repointed libs. 254 c = 0 255 while c < nc { 256 if blt[c] == 0 { 257 let srcpath: *u8 = argv[7 + c] as *u8 258 sr_basename(srcpath, base) 259 sr_tmpp(srsrc, base, ".srsrc" as *u8) 260 let sn: i64 = sr_read(srsrc, srcbuf, SR_CAP) 261 var el: i64 = 0 262 if matched[c] == 1 { el = sr_replace(srcbuf, sn, oldtok, oll, ntok1, nl1, outbuf) } else { el = sr_replace(srcbuf, sn, oldtok, oll, ntok2, nl2, outbuf) } 263 if el > 0 { ss_writefile(srcpath, outbuf, el) } 264 sd_w("SR-LIB-REPOINT " as *u8); sd_w(base); sd_w(" -> " as *u8); if matched[c] == 1 { sd_w(tag1) } else { sd_w(tag2) }; sd_w("\n" as *u8) 265 } 266 c = c + 1 267 } 268 269 // PHASE 3b: repoint each BUILDABLE witness to the copy that reproduces its build byte-for-byte (try 270 // tag1 then tag2). This byte-match also transitively PROVES every lib it imports was repointed right. 271 var fail: i64 = 0 272 c = 0 273 while c < nc { 274 if blt[c] == 1 { 275 let srcpath: *u8 = argv[7 + c] as *u8 276 sr_basename(srcpath, base) 277 sr_tmpp(artp, base, ".sov.elf" as *u8) 278 sr_tmpp(srsrc, base, ".srsrc" as *u8) 279 sr_tmpp(srbef, base, ".srbefore" as *u8) 280 let sn: i64 = sr_read(srsrc, srcbuf, SR_CAP) 281 let bn: i64 = sr_read(srbef, befbuf, SR_CAP) 282 var mt: i64 = 0 283 var el: i64 = sr_replace(srcbuf, sn, oldtok, oll, ntok1, nl1, outbuf) 284 if el > 0 { 285 ss_writefile(srcpath, outbuf, el) 286 let an: i64 = sr_build(buildelf, base, artp, artbuf) 287 if an > 0 { if sr_eq(artbuf, an, befbuf, bn) == 1 { mt = 1 } } 288 } 289 if mt == 0 { 290 el = sr_replace(srcbuf, sn, oldtok, oll, ntok2, nl2, outbuf) 291 if el > 0 { 292 ss_writefile(srcpath, outbuf, el) 293 let an2: i64 = sr_build(buildelf, base, artp, artbuf) 294 if an2 > 0 { if sr_eq(artbuf, an2, befbuf, bn) == 1 { mt = 2 } } 295 } 296 } 297 matched[c] = mt 298 if mt == 0 { fail = fail + 1; sd_w("SR-UNMATCHED " as *u8); sd_w(base); sd_w("\n" as *u8) } 299 else { sd_w("SR-MATCHED " as *u8); sd_w(base); sd_w(" -> " as *u8); if mt == 1 { sd_w(tag1) } else { sd_w(tag2) }; sd_w("\n" as *u8) } 300 } 301 c = c + 1 302 } 303 304 if fail == 0 { 305 sd_w("NX-SPLIT-RESOLVE verdict=RESOLVED reason=all-" as *u8) 306 let ob: *u8 = sys_mmap(32); sd_num(ob, 0, nbuild); sd_w(ob) 307 sd_w("-buildable-witness(es)-byte-identical(libs-verified-transitively);-split-brain-eliminated new-names=" as *u8) 308 sd_w(n1); sd_w("," as *u8); sd_w(n2); sd_w("\n" as *u8) 309 sys_exit(0); return 0 310 } 311 // REVERT: restore EVERY consumer source (buildable and lib), undo both renames. 312 c = 0 313 while c < nc { 314 let srcpath: *u8 = argv[7 + c] as *u8 315 sr_basename(srcpath, base) 316 sr_tmpp(srsrc, base, ".srsrc" as *u8) 317 let sn: i64 = sr_read(srsrc, srcbuf, SR_CAP) 318 if sn > 0 { ss_writefile(srcpath, srcbuf, sn) } 319 c = c + 1 320 } 321 sys_renameat(n1, p1) 322 sys_renameat(n2, p2) 323 sd_w("NX-SPLIT-RESOLVE verdict=REFUSED reason=" as *u8) 324 let ob2: *u8 = sys_mmap(32); sd_num(ob2, 0, fail); sd_w(ob2) 325 sd_w("-buildable-witness(es)-did-NOT-reproduce-their-build-with-EITHER-renamed-copy(see-SR-UNMATCHED) fix=they-resolve-a-THIRD-copy-or-a-lib-was-mis-placed:-widen-the-dir-set-or-list-the-real-copies;-ALL-sources+names-RESTORED(safe)\n" as *u8) 326 sys_exit(1); return 1 327}