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}