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}