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}