code wiki / _hdl_build / nx_memguard.nx
nx_memguard.nx source
↩ module page · 500 lines · 20505 B
1// ⛔⛔ DISPROVEN 2026-08-06 -- BUILT, MEASURED, AND RETRACTED. NOT DEPLOYED, NOT HOOK-WIRED.
2// The ELF was deliberately removed from _offc. Source kept as a recorded negative result, because the
3// idea is attractive enough that someone will otherwise build it again.
4//
5// WHAT IT WAS MEANT TO DO: derive a query from a new memory's own name+description and warn if the
6// corpus already holds that doctrine -- prevention at write time, where post-hoc detection has a
7// proven ceiling.
8//
9// WHY IT DOES NOT WORK (clean replay + control, both failed):
10// * replay: fed the ORIGINAL July store-law frontmatter, it did NOT surface the June file that says
11// the same thing. It returned project-nofloat-models-vision and netobs-observability instead.
12// * control: a birdsong-synthesis note fired 4/6 against a FETCHER memory. A guard that fires on
13// everything is indistinguishable from no guard, except it also teaches the reader to ignore it.
14//
15// ⚠MY FIRST "VALIDATION" WAS CONTAMINATED and nearly shipped: I pointed it at the July file AFTER
16// stubbing it, and the stub's description literally contains the canonical filename. It scored 17/20
17// and looked like a triumph. ★ A FIXTURE THAT NAMES ITS OWN ANSWER VALIDATES NOTHING.
18//
19// ★★★★★★ THE ROOT CAUSE, MEASURED, AND IT INDICTS MORE THAN THIS ORGAN:
20// IDF ASSUMES A DIVERSE CORPUS. IN A SINGLE-PROJECT CORPUS THE TOPIC WORDS *ARE* THE COMMON WORDS,
21// SO RARITY WEIGHTING SELECTS FOR THE INCIDENTAL.
22// Document frequency over the 2,178-file corpus (common cutoff = 108):
23// sovereign 1178 · data 1108 · files 803 · store 638 · tsv 438 · txt 388 · ssot 257 · seg 228
24// Every word that IDENTIFIES the store law is corpus-common and gets dropped; the survivors were
25// "planes" (105) and "queues" (39) -- incidental words that then matched unrelated files. Lowering the
26// word-length floor 5 -> 3 to admit "tsv"/"txt" changed NOTHING, because those are common too.
27//
28// ⇒ Rarity weighting works for distinguishing specific technical artefacts (coulombic, dendrite) and
29// fails for a project's CENTRAL concepts -- which is exactly where duplicated doctrine lives. The same
30// blind spot limits nx_memhealth's duplicate ladder. nx_memfind is unaffected ONLY because a human
31// picks its query terms; that human judgement is the part that cannot currently be automated.
32//
33// ---- original design notes below ----
34//
35// nx_memguard.nx -- at WRITE time, ask "does this memory already exist?" and name the candidates.
36//
37// WHY THIS AND NOT MORE DETECTION
38// ------------------------------
39// Measured 2026-08-06, the same doctrine written four times in 90 seconds; the store law written
40// twice by two sessions three weeks apart; nge-browser-first written twice by ONE session. Every one
41// is the same root cause: a session wrote a memory without checking whether it already existed.
42//
43// Post-hoc detection has a PROVEN ceiling. nx_memhealth's ladder (slug prefix -> rarity-weighted
44// overlap -> series rule -> co-authorship filter) cannot see the store-law pair at all, because those
45// two files share ZERO rare tokens:
46// ★★★★★★ THE VERY PROPERTY THAT MAKES A REDISCOVERY A REDISCOVERY -- INDEPENDENT WORDING -- IS THE
47// PROPERTY THAT DEFEATS LEXICAL DETECTION. Two authors who would have used the same words would
48// have found each other's file; that they did not is WHY the duplicate exists.
49//
50// But that ceiling belongs to PAIRWISE SIMILARITY, not to search. The distinction is the whole design:
51// * pairwise: "do these two files share vocabulary?" -- store-law scored 0/0 and is invisible;
52// * query: "what existing memory is ABOUT this topic?" -- ranked search returned BOTH store-law
53// files at the top, because a file's own name and description are the author's summary
54// of the TOPIC, not a sample of its vocabulary.
55// ★ SEARCH BY THE AUTHOR'S OWN SUMMARY FINDS WHAT VOCABULARY OVERLAP CANNOT.
56//
57// So this organ derives a query from the new file's `name:` + `description:` frontmatter and ranks
58// the rest of the corpus against it, exactly as nx_memfind does (same proven, gated scorer). It is
59// ADVISORY: it names candidates and always exits 0, because a false alarm that blocks a write is
60// worse than a duplicate -- the author is the only one who can say whether two memories are the same.
61//
62// usage: nx_memguard.elf <file.md> [--dir <memdir>] [--n <k>] [--quiet]
63// exit: 0 always (advisory); prints nothing when nothing looks similar
64// Sovereign: imports only nx_syscalls. license_tier: ORIGINAL
65import "nx_syscalls.nx"
66
67const MG_DIR: *u8 = "/mnt/c/Users/elder/.claude/projects/C--Users-elder/memory"
68const MG_MAXF: i64 = 8192
69const MG_SLOT: i64 = 128
70const MG_FBUF: i64 = 1048576
71const MG_GBUF: i64 = 65536
72const MG_MSG: i64 = 32768
73const MG_MAXT: i64 = 24 // derived query terms (bitmask is i64)
74const MG_MINW: i64 = 3 // shortest word worth querying. 5 was tried and FAILED: the two
75 // highest-signal shared tokens in the store-law pair are "tsv" and
76 // "txt" -- 3 chars each -- so the threshold discarded exactly the
77 // evidence and left generic survivors (planes, queues, staging).
78 // ★ A MINIMUM-LENGTH FILTER SILENTLY DELETES THE MOST DISCRIMINATING
79 // TOKENS IN ANY DOMAIN WHOSE JARGON IS SHORT.
80const MG_WARN: i64 = 3 // this many shared TOPICAL terms before we say anything
81const MG_COMMON: i64 = 20 // a term in more than 1/20 of the corpus carries no topic signal
82
83func mg_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
84
85func mg_streq(a: *u8, b: *u8) -> i64 {
86 var i: i64 = 0
87 var r: i64 = 1
88 var go: i64 = 1
89 while go == 1 {
90 if a[i] != b[i] { r = 0; go = 0 } else {
91 if a[i] == (0 as u8) { go = 0 } else { i = i + 1 }
92 }
93 }
94 return r
95}
96
97func mg_cat(buf: *u8, off: i64, s: *u8) -> i64 {
98 var o: i64 = off
99 var i: i64 = 0
100 while s[i] != (0 as u8) { buf[o] = s[i]; o = o + 1; i = i + 1 }
101 return o
102}
103
104func mg_catn(buf: *u8, off: i64, v: i64) -> i64 {
105 var o: i64 = off
106 var m: i64 = v
107 if m == 0 { buf[o] = 48 as u8; return o + 1 }
108 if m < 0 { buf[o] = 45 as u8; o = o + 1; m = 0 - m }
109 let t: *u8 = sys_mmap(32)
110 var k: i64 = 0
111 while m > 0 { t[k] = ((48 + (m % 10)) as u8); m = m / 10; k = k + 1 }
112 var i: i64 = 0
113 while i < k { buf[o] = t[k - 1 - i]; o = o + 1; i = i + 1 }
114 return o
115}
116
117func mg_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
118 var done: i64 = 0
119 var go: i64 = 1
120 while go == 1 {
121 if done >= n { go = 0 } else {
122 let w: i64 = sys_write(fd, ((buf as i64) + done) as *u8, n - done)
123 if w <= 0 { go = 0 } else { done = done + w }
124 }
125 }
126 return done
127}
128func mg_say(buf: *u8, n: i64) -> i64 { return mg_write_all(1, buf, n) }
129
130func mg_getdents(fd: i64, buf: *u8, count: i64) -> i64 { return __syscall(217, fd, buf, count, 0, 0, 0) }
131
132func mg_join(dst: *u8, dir: *u8, name: *u8) -> i64 {
133 var o: i64 = 0
134 var i: i64 = 0
135 while dir[i] != (0 as u8) { dst[o] = dir[i]; o = o + 1; i = i + 1 }
136 dst[o] = 47 as u8; o = o + 1
137 i = 0
138 while name[i] != (0 as u8) { dst[o] = name[i]; o = o + 1; i = i + 1 }
139 dst[o] = 0 as u8
140 return o
141}
142
143func mg_readf(p: *u8, buf: *u8, cap: i64) -> i64 {
144 let fd: i64 = sys_openat_rd(p)
145 if fd < 0 { return 0 }
146 var total: i64 = 0
147 var go: i64 = 1
148 while go == 1 {
149 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total)
150 if r <= 0 { go = 0 } else {
151 total = total + r
152 if total >= cap { go = 0 }
153 }
154 }
155 sys_close(fd)
156 return total
157}
158
159func mg_is_md(name: *u8) -> i64 {
160 let n: i64 = mg_len(name)
161 if n < 4 { return 0 }
162 if name[n - 3] != (46 as u8) { return 0 }
163 if name[n - 2] != (109 as u8) { return 0 }
164 if name[n - 1] != (100 as u8) { return 0 }
165 return 1
166}
167
168func mg_lower(c: u8) -> i64 {
169 if c >= (65 as u8) { if c <= (90 as u8) { return (c as i64) + 32 } }
170 return c as i64
171}
172
173func mg_alnum(c: u8) -> i64 {
174 if c >= (65 as u8) { if c <= (90 as u8) { return 1 } }
175 if c >= (97 as u8) { if c <= (122 as u8) { return 1 } }
176 if c >= (48 as u8) { if c <= (57 as u8) { return 1 } }
177 return 0
178}
179
180// WORD-BOUNDARY match, not substring.
181// ⚠MEASURED FAILURE OF THE FIRST CUT: plain substring matching made "store" hit *restore*, *storage*,
182// *stored*; "model" hit *models*; "files" hit *profiles*. Combined with counting COMMON words toward
183// the alarm, a birdsong-synthesis control fired 12/15 against a fetcher memory. A guard that fires on
184// everything is indistinguishable from no guard, except that it also trains the reader to ignore it.
185func mg_has(buf: *u8, n: i64, pat: *u8) -> i64 {
186 let pl: i64 = mg_len(pat)
187 if pl == 0 { return 0 }
188 if pl > n { return 0 }
189 var i: i64 = 0
190 let lim: i64 = n - pl
191 while i <= lim {
192 var j: i64 = 0
193 var ok: i64 = 1
194 while j < pl {
195 if mg_lower(buf[i + j]) != mg_lower(pat[j]) { ok = 0; j = pl } else { j = j + 1 }
196 }
197 if ok == 1 {
198 var bounded: i64 = 1
199 if i > 0 { if mg_alnum(buf[i - 1]) == 1 { bounded = 0 } }
200 if i + pl < n { if mg_alnum(buf[i + pl]) == 1 { bounded = 0 } }
201 if bounded == 1 { return 1 }
202 }
203 i = i + 1
204 }
205 return 0
206}
207
208func mg_nameptr(names: *u8, i: i64) -> *u8 { return ((names as i64) + i * MG_SLOT) as *u8 }
209
210func mg_log2(v: i64) -> i64 {
211 var n: i64 = 0
212 var m: i64 = v
213 while m > 1 { m = m / 2; n = n + 1 }
214 return n
215}
216
217// basename of a path
218func mg_base(p: *u8) -> *u8 {
219 let n: i64 = mg_len(p)
220 var i: i64 = n
221 while i > 0 {
222 if p[i - 1] == (47 as u8) { return ((p as i64) + i) as *u8 }
223 if p[i - 1] == (92 as u8) { return ((p as i64) + i) as *u8 }
224 i = i - 1
225 }
226 return p
227}
228
229func main(argc: i64, argv: *i64) -> i64 {
230 var dir: *u8 = MG_DIR
231 var target: *u8 = 0 as *u8
232 var topn: i64 = 3
233 var quiet: i64 = 0
234 var ai: i64 = 1
235 while ai < argc {
236 let a: *u8 = argv[ai] as *u8
237 var used: i64 = 0
238 if mg_streq(a, "--dir" as *u8) == 1 { if ai + 1 < argc { dir = argv[ai + 1] as *u8; ai = ai + 1; used = 1 } }
239 if mg_streq(a, "--quiet" as *u8) == 1 { quiet = 1; used = 1 }
240 if mg_streq(a, "--n" as *u8) == 1 {
241 if ai + 1 < argc {
242 let s: *u8 = argv[ai + 1] as *u8
243 var v: i64 = 0
244 var k: i64 = 0
245 while s[k] != (0 as u8) {
246 if s[k] >= (48 as u8) { if s[k] <= (57 as u8) { v = v * 10 + ((s[k] as i64) - 48) } }
247 k = k + 1
248 }
249 if v > 0 { topn = v }
250 ai = ai + 1
251 used = 1
252 }
253 }
254 if used == 0 { if target == (0 as *u8) { target = a } }
255 ai = ai + 1
256 }
257 let msg: *u8 = sys_mmap(MG_MSG)
258 if target == (0 as *u8) {
259 var u: i64 = mg_cat(msg, 0, "usage: nx_memguard <file.md> [--dir D] [--n K] [--quiet]\n" as *u8)
260 mg_say(msg, u)
261 return 0
262 }
263
264 // the target must be a memory file we are meant to guard
265 let tbase: *u8 = mg_base(target)
266 if mg_is_md(tbase) == 0 { return 0 }
267 if mg_streq(tbase, "MEMORY.md" as *u8) == 1 { return 0 }
268 if mg_streq(tbase, "reference-orphan-catalogue.md" as *u8) == 1 { return 0 }
269 if mg_streq(tbase, "reference-index-recent-refs-dormant.md" as *u8) == 1 { return 0 }
270 if mg_streq(tbase, "MEMORY.overflow.md" as *u8) == 1 { return 0 }
271
272 let tbuf: *u8 = sys_mmap(MG_FBUF)
273 let tlen: i64 = mg_readf(target, tbuf, MG_FBUF)
274 if tlen <= 0 { return 0 }
275
276 // ---- derive the query from the AUTHOR'S OWN SUMMARY: the name + description frontmatter,
277 // falling back to the filename. This is the whole trick: a description states the TOPIC, while
278 // the body merely samples one author's vocabulary for it.
279 var hs: i64 = 0
280 var he: i64 = tlen
281 if he > 1400 { he = 1400 }
282 let qbuf: *u8 = sys_mmap(8192)
283 var ql: i64 = 0
284 // filename words always join the query
285 var bi: i64 = 0
286 while tbase[bi] != (0 as u8) { qbuf[ql] = tbase[bi]; ql = ql + 1; bi = bi + 1 }
287 qbuf[ql] = 32 as u8; ql = ql + 1
288 // plus everything on the description: line
289 let dpat: *u8 = "description:" as *u8
290 let dpl: i64 = mg_len(dpat)
291 var i: i64 = hs
292 while i + dpl < he {
293 var j: i64 = 0
294 var ok: i64 = 1
295 while j < dpl { if tbuf[i + j] != dpat[j] { ok = 0; j = dpl } else { j = j + 1 } }
296 if ok == 1 {
297 var p: i64 = i + dpl
298 while p < he {
299 if tbuf[p] == (10 as u8) { p = he } else {
300 if ql < 8000 { qbuf[ql] = tbuf[p]; ql = ql + 1 }
301 p = p + 1
302 }
303 }
304 i = he
305 } else { i = i + 1 }
306 }
307 qbuf[ql] = 0 as u8
308
309 // ---- split into distinct terms of length >= MG_MINW ----
310 let terms: *u8 = sys_mmap(MG_MAXT * 64)
311 var nt: i64 = 0
312 var s2: i64 = 0
313 var p2: i64 = 0
314 while p2 <= ql {
315 var isw: i64 = 0
316 if p2 < ql { isw = mg_alnum(qbuf[p2]) }
317 if isw == 1 {
318 if s2 < 0 { s2 = p2 }
319 } else {
320 let wl: i64 = p2 - s2
321 if wl >= MG_MINW {
322 if nt < MG_MAXT {
323 if wl < 60 {
324 // reject duplicates
325 var dup: i64 = 0
326 var t2: i64 = 0
327 while t2 < nt {
328 let ex: *u8 = ((terms as i64) + t2 * 64) as *u8
329 if mg_len(ex) == wl {
330 var m2: i64 = 1
331 var c3: i64 = 0
332 while c3 < wl { if mg_lower(qbuf[s2 + c3]) != (ex[c3] as i64) { m2 = 0; c3 = wl } else { c3 = c3 + 1 } }
333 if m2 == 1 { dup = 1 }
334 }
335 t2 = t2 + 1
336 }
337 if dup == 0 {
338 let dst: *u8 = ((terms as i64) + nt * 64) as *u8
339 var c4: i64 = 0
340 while c4 < wl { dst[c4] = mg_lower(qbuf[s2 + c4]) as u8; c4 = c4 + 1 }
341 dst[wl] = 0 as u8
342 nt = nt + 1
343 }
344 }
345 }
346 }
347 s2 = p2 + 1
348 }
349 p2 = p2 + 1
350 }
351 if nt < 2 { return 0 }
352
353 // ---- scan the corpus ----
354 let names: *u8 = sys_mmap(MG_MAXF * MG_SLOT)
355 let hitmask: *i64 = sys_mmap(8 * MG_MAXF) as *i64
356 let namemask: *i64 = sys_mmap(8 * MG_MAXF) as *i64
357 let score: *i64 = sys_mmap(8 * MG_MAXF) as *i64
358 let df: *i64 = sys_mmap(8 * MG_MAXT) as *i64
359 let fbuf: *u8 = sys_mmap(MG_FBUF)
360 let gbuf: *u8 = sys_mmap(MG_GBUF)
361 let path: *u8 = sys_mmap(4096)
362
363 let dfd: i64 = sys_openat_rd(dir)
364 if dfd < 0 { return 0 }
365 var cnt: i64 = 0
366 var nread: i64 = mg_getdents(dfd, gbuf, MG_GBUF)
367 while nread > 0 {
368 var off: i64 = 0
369 while off < nread {
370 let reclen: i64 = (gbuf[off + 16] as i64) | ((gbuf[off + 17] as i64) << 8)
371 if reclen <= 0 { off = nread } else {
372 let nm: *u8 = ((gbuf as i64) + off + 19) as *u8
373 if mg_is_md(nm) == 1 {
374 if cnt < MG_MAXF {
375 let dst: *u8 = mg_nameptr(names, cnt)
376 var c: i64 = 0
377 while nm[c] != (0 as u8) { if c < MG_SLOT - 1 { dst[c] = nm[c] } c = c + 1 }
378 dst[c] = 0 as u8
379 hitmask[cnt] = 0
380 namemask[cnt] = 0
381 score[cnt] = 0
382 cnt = cnt + 1
383 }
384 }
385 off = off + reclen
386 }
387 }
388 nread = mg_getdents(dfd, gbuf, MG_GBUF)
389 }
390 sys_close(dfd)
391
392 var t: i64 = 0
393 while t < nt { df[t] = 0; t = t + 1 }
394
395 var f: i64 = 0
396 while f < cnt {
397 let fname: *u8 = mg_nameptr(names, f)
398 var skip: i64 = 0
399 if mg_streq(fname, tbase) == 1 { skip = 1 } // never match itself
400 if mg_streq(fname, "MEMORY.md" as *u8) == 1 { skip = 1 }
401 if mg_streq(fname, "MEMORY.overflow.md" as *u8) == 1 { skip = 1 }
402 if mg_streq(fname, "reference-index-recent-refs-dormant.md" as *u8) == 1 { skip = 1 }
403 if mg_streq(fname, "reference-orphan-catalogue.md" as *u8) == 1 { skip = 1 }
404 if skip == 0 {
405 mg_join(path, dir, fname)
406 let total: i64 = mg_readf(path, fbuf, MG_FBUF)
407 if total > 0 {
408 let nl: i64 = mg_len(fname)
409 t = 0
410 while t < nt {
411 let term: *u8 = ((terms as i64) + t * 64) as *u8
412 if mg_has(fbuf, total, term) == 1 {
413 hitmask[f] = hitmask[f] | (1 << t)
414 df[t] = df[t] + 1
415 if mg_has(fname, nl, term) == 1 { namemask[f] = namemask[f] | (1 << t) }
416 }
417 t = t + 1
418 }
419 }
420 }
421 f = f + 1
422 }
423
424 // ★★★★★ RARITY MUST GATE ADMISSION, NOT ONLY WEIGHTING.
425 // The first cut computed IDF and then let ANY three matched terms raise the alarm. A term present
426 // in a large fraction of the corpus says nothing about the topic, so "must", "model", "source",
427 // "rules" carried a birdsong note to a 12/15 match against a fetcher memory. Terms appearing in
428 // more than 1/MG_COMMON of the corpus are now dropped from the query outright.
429 let topical: *i64 = sys_mmap(8 * MG_MAXT) as *i64
430 var ntop: i64 = 0
431 t = 0
432 while t < nt {
433 topical[t] = 0
434 if df[t] > 0 { if df[t] * MG_COMMON <= cnt { topical[t] = 1; ntop = ntop + 1 } }
435 t = t + 1
436 }
437 // Too little topical signal to make a claim -- SAY NOTHING rather than guess.
438 if ntop < MG_WARN { return 0 }
439
440 f = 0
441 while f < cnt {
442 var sc: i64 = 0
443 var matched: i64 = 0
444 t = 0
445 while t < nt {
446 if topical[t] == 1 {
447 if (hitmask[f] & (1 << t)) != 0 {
448 matched = matched + 1
449 var w: i64 = mg_log2((cnt * 100) / (1 + df[t]))
450 if w < 1 { w = 1 }
451 if (namemask[f] & (1 << t)) != 0 { w = w * 2 }
452 sc = sc + w
453 }
454 }
455 t = t + 1
456 }
457 if matched >= MG_WARN { score[f] = matched * 10000 + sc } else { score[f] = 0 }
458 f = f + 1
459 }
460 nt = ntop
461
462 // ---- report, but only if something genuinely looks like the same topic ----
463 var shown: i64 = 0
464 var head: i64 = 0
465 while shown < topn {
466 var best: i64 = 0 - 1
467 var bs: i64 = 0
468 f = 0
469 while f < cnt {
470 if score[f] > bs { bs = score[f]; best = f }
471 f = f + 1
472 }
473 if best < 0 { shown = topn } else {
474 score[best] = 0
475 if head == 0 {
476 head = 1
477 if quiet == 0 {
478 var h: i64 = mg_cat(msg, 0, "*** nx_memguard: BEFORE ADDING THIS MEMORY, CHECK IT IS NOT ALREADY WRITTEN. " as *u8)
479 h = mg_cat(msg, h, tbase)
480 h = mg_cat(msg, h, " shares its topic with existing memories: ***\n" as *u8)
481 mg_say(msg, h)
482 }
483 }
484 var d: i64 = mg_cat(msg, 0, " " as *u8)
485 d = mg_catn(msg, d, bs / 10000)
486 d = mg_cat(msg, d, "/" as *u8)
487 d = mg_catn(msg, d, nt)
488 d = mg_cat(msg, d, " topic terms " as *u8)
489 d = mg_cat(msg, d, mg_nameptr(names, best))
490 d = mg_cat(msg, d, "\n" as *u8)
491 mg_say(msg, d)
492 shown = shown + 1
493 }
494 }
495 if head == 1 {
496 var z: i64 = mg_cat(msg, 0, " If one of these is the same doctrine, UPDATE IT instead -- the corpus already holds the same law written twice by two sessions three weeks apart.\n" as *u8)
497 mg_say(msg, z)
498 }
499 return 0
500}