code wiki / _hdl_build / nx_memfix.nx

nx_memfix.nx source

↩ module page · 244 lines · 10969 B

1// nx_memfix.nx -- repair PROVABLY-SAFE dangling links in the laptop-local memory corpus. 2// 3// CONVERTED TO nx_memplane_lib 2026-08-06. It now owns only what is specific to it: the normalised 4// name index and the two repair classes. The scanner, name table, walk and dump list come from the 5// lib, because six hand-copies of those drifted and the drift was the bug. 6// 7// SEPARATE ORGAN FROM nx_memhealth ON PURPOSE: the instrument stays read-only, so a repair bug can 8// never corrupt the measurement that would reveal it. 9// 10// ★★★★★★ A WRONG LINK IS WORSE THAN A DEAD ONE -- A DEAD LINK FAILS LOUDLY WHEN FOLLOWED, A WRONG 11// LINK SILENTLY DELIVERS A DIFFERENT MEMORY. So repair is restricted to the classes where the answer 12// is unique and mechanical, and everything else is reported, never guessed: 13// A DOUBLE-EXTENSION -- target ends ".md.md" and stripping one ".md" names an existing file (5) 14// B NORMALISED-EXACT -- differs from exactly ONE file by case/punctuation/WHITESPACE (19/26 occ) 15// ★ class B is not renames: it is LINKS WRAPPED ACROSS TWO LINES, where the author's markdown 16// wrapped and the newline landed inside the target. The intent is unambiguous and the file is 17// right there; only the whitespace is wrong. 18// C unique-substring (29/53) · D ambiguous (14/26) · E absent (121/292) -- ALL REPORT-ONLY. 19// Class E cannot be repaired at all: those files were DELETED, not renamed (only 2 of 37 legacy 20// names resolve), so "repairing" them would fabricate the doctrine they cite. 21// 22// ⚠ DUMPS ARE SKIPPED (added at conversion). The derived artefacts are regenerated wholly on each 23// run, so rewriting links inside them is churn that the next regeneration discards -- and it would 24// count toward the repair total, inflating a number that is supposed to mean "authored corpus fixed". 25// 26// SAFETY: dry-run by default; a .bak-memfix copy written BEFORE each file is touched; atomic 27// tmp+renameat install; refuses any target whose normalised form matches more than one file; the 28// replacement is always the literal name of a file that exists right now. 29// 30// usage: nx_memfix [--dir <memdir>] [--apply] [--list] 31// exit: 0 = clean or repaired; 1 = a write failed 32// Sovereign: imports nx_memplane_lib. license_tier: ORIGINAL 33import "nx_memplane_lib.nx" 34 35const MF_DIR: *u8 = "/mnt/c/Users/elder/.claude/projects/C--Users-elder/memory" 36const MF_FBUF: i64 = 1048576 37const MF_OBUF: i64 = 2097152 38const MF_MSG: i64 = 8192 39 40// normalised hash of a byte range: every non-alphanumeric dropped, case folded. This is what makes a 41// newline inside a wrapped link invisible to the comparison. 42func mf_hashnorm(p: *u8, n: i64) -> i64 { 43 var h: i64 = 2166136261 44 var i: i64 = 0 45 while i < n { 46 let b: u8 = p[i] 47 var c: i64 = 0 - 1 48 if b >= (65 as u8) { if b <= (90 as u8) { c = (b as i64) + 32 } } 49 if b >= (97 as u8) { if b <= (122 as u8) { c = b as i64 } } 50 if b >= (48 as u8) { if b <= (57 as u8) { c = b as i64 } } 51 if c >= 0 { 52 h = h ^ c 53 h = (h * 16777619) & 1073741823 54 } 55 i = i + 1 56 } 57 return h 58} 59 60// normalised index: key = normalised hash, value = file index + 1, or -1 once AMBIGUOUS. 61// Ambiguity is recorded permanently: two files sharing a normalised form make BOTH unrepairable, 62// because "unique" is the entire safety argument. 63func mf_nput(nk: *i64, nv: *i64, h: i64, idx: i64) -> i64 { 64 var p: i64 = h & (MP_HASH - 1) 65 var go: i64 = 1 66 while go == 1 { 67 if nk[p] == 0 { nk[p] = h; nv[p] = idx + 1; go = 0 } else { 68 if nk[p] == h { nv[p] = 0 - 1; go = 0 } else { p = (p + 1) & (MP_HASH - 1) } 69 } 70 } 71 return 0 72} 73 74func mf_nget(nk: *i64, nv: *i64, h: i64) -> i64 { 75 var p: i64 = h & (MP_HASH - 1) 76 var r: i64 = 0 - 1 77 var go: i64 = 1 78 while go == 1 { 79 if nk[p] == 0 { go = 0 } else { 80 if nk[p] == h { r = nv[p]; go = 0 } else { p = (p + 1) & (MP_HASH - 1) } 81 } 82 } 83 if r <= 0 { return 0 - 1 } 84 return r - 1 85} 86 87func main(argc: i64, argv: *i64) -> i64 { 88 var dir: *u8 = MF_DIR 89 var apply: i64 = 0 90 var list: i64 = 0 91 var ai: i64 = 1 92 while ai < argc { 93 let a: *u8 = argv[ai] as *u8 94 if mp_streq(a, "--apply" as *u8) == 1 { apply = 1 } 95 if mp_streq(a, "--list" as *u8) == 1 { list = 1 } 96 if mp_streq(a, "--dir" as *u8) == 1 { if ai + 1 < argc { dir = argv[ai + 1] as *u8; ai = ai + 1 } } 97 ai = ai + 1 98 } 99 100 let msg: *u8 = sys_mmap(MF_MSG) 101 let names: *u8 = sys_mmap(MP_MAXF * MP_SLOT) 102 let tbl: *i64 = sys_mmap(8 * MP_HASH) as *i64 103 let nk: *i64 = sys_mmap(8 * MP_HASH) as *i64 104 let nv: *i64 = sys_mmap(8 * MP_HASH) as *i64 105 let fbuf: *u8 = sys_mmap(MF_FBUF) 106 let obuf: *u8 = sys_mmap(MF_OBUF) 107 let path: *u8 = sys_mmap(4096) 108 let path2: *u8 = sys_mmap(4096) 109 let probe: *u8 = sys_mmap(1024) 110 let out: *i64 = sys_mmap(64) as *i64 111 112 let cnt: i64 = mp_scan(dir, names, tbl, MP_MAXF) 113 if cnt < 0 { 114 var e: i64 = mp_cat(msg, 0, "nx_memfix: REFUSED -- cannot open the memory dir.\n" as *u8) 115 mp_say(msg, e) 116 return 1 117 } 118 var i: i64 = 0 119 while i < MP_HASH { nk[i] = 0; nv[i] = 0; i = i + 1 } 120 i = 0 121 while i < cnt { 122 let nm: *u8 = mp_nameptr(names, i) 123 mf_nput(nk, nv, mf_hashnorm(nm, mp_len(nm)), i) 124 i = i + 1 125 } 126 127 var repaired: i64 = 0 128 var filesfixed: i64 = 0 129 var failed: i64 = 0 130 var f: i64 = 0 131 while f < cnt { 132 let fname: *u8 = mp_nameptr(names, f) 133 if mp_is_dump(fname) == 0 { 134 mp_join(path, dir, fname) 135 let total: i64 = mp_readf(path, fbuf, MF_FBUF) 136 if total > 0 { 137 var o: i64 = 0 138 var pos: i64 = 0 139 var hits: i64 = 0 140 var go: i64 = 1 141 while go == 1 { 142 if mp_link_next(fbuf, total, pos, out) == 1 { 143 let s: i64 = out[0] 144 let e: i64 = out[1] 145 let wiki: i64 = out[2] 146 // copy everything up to the target verbatim 147 var c: i64 = pos 148 while c < s { obuf[o] = fbuf[c]; o = o + 1; c = c + 1 } 149 var wrote: i64 = 0 150 if mp_resolve(fbuf, out, names, tbl, probe) < 0 { 151 // DANGLING. class A: strip a duplicated ".md" 152 let pl: i64 = mp_len(probe) 153 var fix: i64 = 0 - 1 154 if pl >= 6 { 155 if probe[pl - 6] == (46 as u8) { if probe[pl - 5] == (109 as u8) { if probe[pl - 4] == (100 as u8) { 156 let cand: i64 = mp_get(tbl, names, probe, pl - 3) 157 if cand >= 0 { fix = cand } 158 } } } 159 } 160 // class B: unique normalised match 161 if fix < 0 { 162 let hit: i64 = mf_nget(nk, nv, mf_hashnorm(probe, pl)) 163 if hit >= 0 { fix = hit } 164 } 165 if fix >= 0 { 166 let canon: *u8 = mp_nameptr(names, fix) 167 let cl: i64 = mp_len(canon) 168 var emit: i64 = cl 169 if wiki == 1 { emit = cl - 3 } // wiki text carries no ".md" 170 var c3: i64 = 0 171 while c3 < emit { obuf[o] = canon[c3]; o = o + 1; c3 = c3 + 1 } 172 wrote = 1 173 hits = hits + 1 174 repaired = repaired + 1 175 if list == 1 { 176 var d: i64 = mp_cat(msg, 0, " FIX " as *u8) 177 d = mp_cat(msg, d, fname) 178 d = mp_cat(msg, d, "\n -> " as *u8) 179 d = mp_cat(msg, d, canon) 180 d = mp_cat(msg, d, "\n" as *u8) 181 mp_say(msg, d) 182 } 183 } 184 } 185 if wrote == 0 { 186 var c4: i64 = s 187 while c4 < e { obuf[o] = fbuf[c4]; o = o + 1; c4 = c4 + 1 } 188 } 189 pos = e 190 } else { 191 var c5: i64 = pos 192 while c5 < total { obuf[o] = fbuf[c5]; o = o + 1; c5 = c5 + 1 } 193 go = 0 194 } 195 } 196 197 if hits > 0 { 198 filesfixed = filesfixed + 1 199 if apply == 1 { 200 // preserve first: a .bak-memfix copy BEFORE the live file is touched 201 var bl: i64 = mp_cat(path2, 0, path) 202 bl = mp_cat(path2, bl, ".bak-memfix" as *u8) 203 path2[bl] = 0 as u8 204 let bfd: i64 = sys_openat_wr(path2, 420) 205 if bfd < 0 { failed = failed + 1 } else { 206 mp_write_all(bfd, fbuf, total) 207 sys_close(bfd) 208 var tl: i64 = mp_cat(path2, 0, path) 209 tl = mp_cat(path2, tl, ".mftmp" as *u8) 210 path2[tl] = 0 as u8 211 let wfd: i64 = sys_openat_wr(path2, 420) 212 if wfd < 0 { failed = failed + 1 } else { 213 let w2: i64 = mp_write_all(wfd, obuf, o) 214 sys_close(wfd) 215 if w2 != o { failed = failed + 1 } else { 216 if sys_renameat(path2, path) != 0 { failed = failed + 1 } 217 } 218 } 219 } 220 } 221 } 222 } 223 } 224 f = f + 1 225 } 226 227 var m: i64 = mp_cat(msg, 0, "nx_memfix: " as *u8) 228 m = mp_catn(msg, m, cnt) 229 m = mp_cat(msg, m, " .md files; " as *u8) 230 m = mp_catn(msg, m, repaired) 231 m = mp_cat(msg, m, " provably-safe link repairs across " as *u8) 232 m = mp_catn(msg, m, filesfixed) 233 m = mp_cat(msg, m, " files" as *u8) 234 if apply == 0 { m = mp_cat(msg, m, " (DRY RUN -- nothing written; pass --apply)" as *u8) } 235 if failed > 0 { 236 m = mp_cat(msg, m, "; *** " as *u8) 237 m = mp_catn(msg, m, failed) 238 m = mp_cat(msg, m, " WRITES FAILED ***" as *u8) 239 } 240 m = mp_cat(msg, m, ".\n" as *u8) 241 mp_say(msg, m) 242 if failed > 0 { return 1 } 243 return 0 244}