code wiki / _hdl_build / nx_capreg_librarian.nx

nx_capreg_librarian.nx source

↩ module page · 284 lines · 13009 B

1// nx_capreg_librarian.nx -- the LIBRARIAN's registry survival + collision repair (operator-flagged debt: 2// "CAPREG index collision needs the Librarian dedup / atomic allocation fix" -- and the command-prompt 3// crash that wiped /tmp PROVED the durability gap: the live registry died, only the NTFS journal at 4// knowledge/status/cap_registry_durable.log survived). Three capabilities, one module: 5// 1. REGEN -- cl_regen(journal, live): rebuild the clean live registry FROM the append-only journal. 6// Byte-identical re-snapshot copies dedup away; TRUE collisions (same idx, different capability -- 7// the two-parallel-sessions bug at idx 212-227) are RENUMBERED past the global max with an explicit 8// `remapped_from=` marker (additive: history preserved, nothing deleted, the journal untouched). 9// 2. ALLOC -- cl_next_idx(live, journal): max idx across BOTH files + 1. Register modules call this 10// instead of hardcoding an index = the atomic-allocation fix (collisions can't recur in one lane; 11// cross-lane races are repaired by the next regen, which is the backstop by construction). 12// 3. DUAL-WRITE -- cl_register_dual(...): every registration appends to the live registry AND the 13// NTFS journal in one call. A /tmp wipe now loses NOTHING (regen restores live from journal). 14// RACI: the LIBRARIAN owns this (GATHER/REGISTER verb -- the registry is the gathered source the 15// self-model regenerates from). LAWS: struct-free, integer-only, flat ifs. license_tier: ORIGINAL 16import "nx_syscalls.nx" 17import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 18const K_MAGIC_1000000007: i64 = 1000000007 19const K_MAGIC_32768: i64 = 32768 20 21// atomic rename via DIRECT x86 renameat=264 (4 args, no flags). sys_renameat issues rv64 renameat2=276 22// whose translate row postdates the frozen known-good cc -> 276 hits x86 tee = EINVAL (probe-proven 23// rc=-22 by _cl_probe2; same NXASM-SYSCALL-XLATE-GAP class as the filed mkdirat/unlinkat cases). 24func cl_rename(oldpath: *u8, newpath: *u8) -> i64 { 25 return __syscall(264, 0 - 100, oldpath, 0 - 100, newpath, 0, 0) 26} 27 28func cl_w(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 29// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 30// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 31// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 32// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 33func cl_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 34// append nul-terminated s into dst at j; returns the new end (dst stays nul-terminated) 35func cl_cats(dst: *u8, j: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){ dst[j+n]=s[n]; n=n+1 } dst[j+n]=0 as u8; return j+n } 36 37// does the line starting at i match "CAPREG idx="? (the journal's entry marker) 38func cl_is_entry(b: *u8, i: i64, n: i64) -> i64 { 39 let m: *u8 = "CAPREG idx=" as *u8 40 var k: i64 = 0 41 while m[k] != (0 as u8) { 42 if i + k >= n { return 0 } 43 if b[i+k] != m[k] { return 0 } 44 k = k + 1 45 } 46 return 1 47} 48// index of the line's '\n' (or n if the file ends without one) 49func cl_line_end(b: *u8, i: i64, n: i64) -> i64 { 50 var e: i64 = i 51 while e < n { if b[e] == (10 as u8) { return e } e = e + 1 } 52 return n 53} 54// parse the decimal run at i; -1 if no digit there 55func cl_parse_int(b: *u8, i: i64, n: i64) -> i64 { 56 var v: i64 = 0 - 1 57 var j: i64 = i 58 while j < n { 59 let c: i64 = b[j] as i64 60 if c < 48 { return v } 61 if c > 57 { return v } 62 if v < 0 { v = 0 } 63 v = v * 10 + (c - 48) 64 j = j + 1 65 } 66 return v 67} 68// first index past the decimal run at i 69func cl_digits_end(b: *u8, i: i64, n: i64) -> i64 { 70 var j: i64 = i 71 while j < n { 72 let c: i64 = b[j] as i64 73 if c < 48 { return j } 74 if c > 57 { return j } 75 j = j + 1 76 } 77 return j 78} 79// content hash of bytes j..e (the entry text AFTER the idx digits): same capability re-snapshotted 80// hashes equal -> dedup; different capability at the same idx hashes different -> collision 81func cl_hash_range(b: *u8, j: i64, e: i64) -> i64 { 82 var h: i64 = 7 83 var k: i64 = j 84 while k < e { h = (h * 131 + (b[k] as i64)) % K_MAGIC_1000000007; k = k + 1 } 85 return h 86} 87 88// max CAPREG idx in a registry file (0 if missing/empty) -- the allocator's scan 89func cl_max_idx(path: *u8) -> i64 { 90 let lenp: *i64 = sys_mmap(16) as *i64 91 let b: *u8 = sys_read_file(path, lenp) 92 let n: i64 = lenp[0] 93 if n <= 0 { return 0 } 94 var mx: i64 = 0 95 var i: i64 = 0 96 while i < n { 97 if cl_is_entry(b, i, n) == 1 { 98 let v: i64 = cl_parse_int(b, i + 11, n) 99 if v > mx { mx = v } 100 } 101 i = cl_line_end(b, i, n) + 1 102 } 103 return mx 104} 105 106// THE ATOMIC ALLOCATOR: next free idx = max(live, journal) + 1. Register modules call this instead of 107// hardcoding -- the root-cause fix for the cross-session collision. 108func cl_next_idx(livepath: *u8, journalpath: *u8) -> i64 { 109 var mx: i64 = cl_max_idx(livepath) 110 let mj: i64 = cl_max_idx(journalpath) 111 if mj > mx { mx = mj } 112 return mx + 1 113} 114 115// DUAL-WRITE registration: one validated CAPREG line appended to BOTH the live registry and the NTFS 116// journal. The crash-survival fix: live is now a cache, the journal is the truth. 117func cl_register_dual(livepath: *u8, journalpath: *u8, idx: i64, layer: i64, status: i64, name: *u8) -> i64 { 118 var pass: i64 = 0 119 var f: i64 = 0 120 while f < 2 { 121 var fd: i64 = 0 - 1 122 if f == 0 { fd = sys_openat_append(livepath, 0x1a4) } 123 if f == 1 { fd = sys_openat_append(journalpath, 0x1a4) } 124 if fd >= 0 { 125 cl_w(fd, "CAPREG idx=" as *u8); cl_wn(fd, idx) 126 cl_w(fd, " layer=" as *u8); cl_wn(fd, layer) 127 cl_w(fd, " status=" as *u8); cl_wn(fd, status) 128 cl_w(fd, " name=" as *u8); cl_w(fd, name); cl_w(fd, "\n" as *u8) 129 sys_close(fd) 130 pass = pass + 1 131 } 132 f = f + 1 133 } 134 if pass == 2 { return 1 } 135 return 0 136} 137 138// one journal entry -> the clean output (helper kept flat: the lane has a filed 4-deep nested-if 139// miscompile, so the per-entry decision lives here, not inlined under the scan loop; and kept to 6 140// args -- the toolchain family has a filed >6-stack-args miscompile, tables ride inside st). 141// st[0]=nt st[1]=maxidx st[2]=tidx-table st[3]=thsh-table. Emits unless the (idx, content-hash) pair 142// was already emitted; a reused idx with NEW content is renumbered past max with remapped_from. 143func cl_emit_entry(out: i64, b: *u8, i: i64, e: i64, n: i64, st: *i64) -> i64 { 144 let tidx: *i64 = st[2] as *i64 145 let thsh: *i64 = st[3] as *i64 146 let idx: i64 = cl_parse_int(b, i + 11, n) 147 let j: i64 = cl_digits_end(b, i + 11, n) 148 let h: i64 = cl_hash_range(b, j, e) 149 var used: i64 = 0 150 var exact: i64 = 0 151 var k: i64 = 0 152 while k < st[0] { 153 if tidx[k] == idx { used = 1; if thsh[k] == h { exact = 1 } } 154 k = k + 1 155 } 156 if exact == 1 { return 0 } 157 var assigned: i64 = idx 158 if used == 1 { st[1] = st[1] + 1; assigned = st[1] } 159 tidx[st[0]] = idx; thsh[st[0]] = h; st[0] = st[0] + 1 160 cl_w(out, "CAPREG idx=" as *u8); cl_wn(out, assigned) 161 sys_write(out, (b as i64 + j) as *u8, e - j) 162 if assigned != idx { cl_w(out, " remapped_from=" as *u8); cl_wn(out, idx) } 163 cl_w(out, "\n" as *u8) 164 return 1 165} 166 167// THE REGEN: journal -> clean live registry. Dedups byte-identical re-snapshots, renumbers true 168// collisions past the global max (with `remapped_from=` so history stays traceable), writes the new 169// live file atomically (.new + rename -> a concurrent reader never sees a torn registry). 170// Returns the number of unique entries written (-1 on read/write failure). 171func cl_regen(journalpath: *u8, livepath: *u8) -> i64 { 172 let lenp: *i64 = sys_mmap(16) as *i64 173 let b: *u8 = sys_read_file(journalpath, lenp) 174 let n: i64 = lenp[0] 175 if n <= 0 { return 0 - 1 } 176 // pass 1: the global max original idx (renumbers start past EVERYTHING) 177 var maxidx: i64 = 0 178 var i: i64 = 0 179 while i < n { 180 if cl_is_entry(b, i, n) == 1 { 181 let v: i64 = cl_parse_int(b, i + 11, n) 182 if v > maxidx { maxidx = v } 183 } 184 i = cl_line_end(b, i, n) + 1 185 } 186 // seen-table: original idx + content hash per unique entry (linear scan -- registry is small) 187 let tidx: *i64 = sys_mmap(K_MAGIC_32768) as *i64 188 let thsh: *i64 = sys_mmap(K_MAGIC_32768) as *i64 189 let st: *i64 = sys_mmap(64) as *i64 190 st[0] = 0; st[1] = maxidx; st[2] = tidx as i64; st[3] = thsh as i64 191 // atomic publish: write .new then rename over live 192 let tmp: *u8 = sys_mmap(256) 193 var tl: i64 = cl_cats(tmp, 0, livepath) 194 tl = cl_cats(tmp, tl, ".new" as *u8) 195 let out: i64 = sys_openat_wr(tmp, 0x1a4) 196 if out < 0 { return 0 - 1 } 197 // pass 2: emit unique entries, renumber collisions 198 i = 0 199 while i < n { 200 let e: i64 = cl_line_end(b, i, n) 201 if cl_is_entry(b, i, n) == 1 { cl_emit_entry(out, b, i, e, n, st) } 202 i = e + 1 203 } 204 sys_close(out) 205 if cl_rename(tmp, livepath) < 0 { return 0 - 1 } 206 return st[0] 207} 208 209// INGEST-LIVE: append live-registry entries the journal lacks INTO the journal. The legacy register 210// modules append to /tmp live ONLY (pre-dual-write) -- without this, a canonicalize would regenerate 211// live FROM the journal and silently DROP their registrations. Preloads the seen-table from the 212// journal, then routes live's lines through cl_emit_entry with the journal as the output: absentees 213// append (idx collisions renumber, exactly like regen), known lines skip. Returns entries ingested. 214func cl_ingest_live(livepath: *u8, journalpath: *u8) -> i64 { 215 let llen: *i64 = sys_mmap(16) as *i64 216 let lb: *u8 = sys_read_file(livepath, llen) 217 let ln: i64 = llen[0] 218 if ln <= 0 { return 0 } 219 let jlen: *i64 = sys_mmap(16) as *i64 220 let jb: *u8 = sys_read_file(journalpath, jlen) 221 let jn: i64 = jlen[0] 222 // preload the seen-table + global max from the journal 223 let tidx: *i64 = sys_mmap(K_MAGIC_32768) as *i64 224 let thsh: *i64 = sys_mmap(K_MAGIC_32768) as *i64 225 let st: *i64 = sys_mmap(64) as *i64 226 st[0] = 0; st[1] = 0; st[2] = tidx as i64; st[3] = thsh as i64 227 var i: i64 = 0 228 while i < jn { 229 let e: i64 = cl_line_end(jb, i, jn) 230 if cl_is_entry(jb, i, jn) == 1 { 231 let idx: i64 = cl_parse_int(jb, i + 11, jn) 232 let j: i64 = cl_digits_end(jb, i + 11, jn) 233 tidx[st[0]] = idx; thsh[st[0]] = cl_hash_range(jb, j, e); st[0] = st[0] + 1 234 if idx > st[1] { st[1] = idx } 235 } 236 i = e + 1 237 } 238 // renumber base must clear BOTH files' originals (cl_regen gets this from its pass-1 over all 239 // input; here live is a second input, so fold its max in too) 240 let lmx: i64 = cl_max_idx(livepath) 241 if lmx > st[1] { st[1] = lmx } 242 let pre: i64 = st[0] 243 let out: i64 = sys_openat_append(journalpath, 0x1a4) 244 if out < 0 { return 0 - 1 } 245 i = 0 246 while i < ln { 247 let e2: i64 = cl_line_end(lb, i, ln) 248 if cl_is_entry(lb, i, ln) == 1 { cl_emit_entry(out, lb, i, e2, ln, st) } 249 i = e2 + 1 250 } 251 sys_close(out) 252 return st[0] - pre 253} 254 255// CANONICALIZE: the one-time legacy repair made durable. Regen renumbers collisions relative to the 256// journal's CURRENT max -- so as long as collided lines sit in the journal, their assigned idx would 257// DRIFT on every regen (proven by the KAT: GAMMA 12 -> 14 after one more registration). The fix: after 258// a regen, the CLEAN set replaces the journal (old journal archived additively at <journal>.raw -- 259// history sacred, nothing deleted). Post-canonicalize the journal has one idx per entry by 260// construction -> every future regen is a stable pure-dedup, zero renumbers, zero drift. 261func cl_canonicalize(journalpath: *u8, livepath: *u8) -> i64 { 262 let nu: i64 = cl_regen(journalpath, livepath) 263 if nu < 0 { return 0 - 1 } 264 // archive the raw journal (first canonicalize only: keep the oldest raw, don't clobber it) 265 let raw: *u8 = sys_mmap(256) 266 var rl: i64 = cl_cats(raw, 0, journalpath) 267 rl = cl_cats(raw, rl, ".raw" as *u8) 268 let probe: i64 = sys_openat_rd(raw) 269 if probe >= 0 { sys_close(probe) } 270 if probe < 0 { cl_rename(journalpath, raw) } 271 // the clean live set becomes the new journal (atomic: .new + rename) 272 let lenp: *i64 = sys_mmap(16) as *i64 273 let lb: *u8 = sys_read_file(livepath, lenp) 274 if lenp[0] <= 0 { return 0 - 1 } 275 let tmp: *u8 = sys_mmap(256) 276 var tl: i64 = cl_cats(tmp, 0, journalpath) 277 tl = cl_cats(tmp, tl, ".new" as *u8) 278 let out: i64 = sys_openat_wr(tmp, 0x1a4) 279 if out < 0 { return 0 - 1 } 280 sys_write(out, lb, lenp[0]) 281 sys_close(out) 282 if cl_rename(tmp, journalpath) < 0 { return 0 - 1 } 283 return nu 284}