code wiki / _hdl_build / nx_memplane_lib.nx

nx_memplane_lib.nx source

↩ module page · 408 lines · 16330 B

1// nx_memplane_lib.nx -- shared primitives for the memory-plane organs. 2// 3// WHY THIS EXISTS (two reasons, both measured) 4// ------------------------------------------- 5// 1. RULE 15. Six organs -- memcliff, memhealth, memfix, memorph, memfind, memroot -- each carried 6// its own copy of: the getdents64 directory walk, the name table + open-addressing index, the 7// bounded file read, the path join, and the markdown/wiki link scanner. That is a pattern in six 8// services, copied by hand each time. 9// 10// 2. THE COPIES DRIFTED, AND THE DRIFT WAS THE BUG. Each defence below was learned once, banked in 11// ONE organ, and then absent from the next one written: 12// * DUMP EXCLUSION -- memhealth, memorph and memfind all skip the aggregate dumps. memroot did 13// not, so its first live run walked MEMORY.md -> reference-orphan-catalogue -> everything the 14// catalogue listed and reported ROOTED 2178 / FLOATING 0 / GREEN, contradicting an already 15// measured 581 orphans. A false GREEN on the one metric that would have exposed the problem. 16// * NESTED-ARRAY REJECTION -- a scanner that stops at the first `]]` reads `[[433,94,2],[434,90,2]]` 17// out of a code block as a wiki link. 25 of memhealth's first 427 "dangling" links were this. 18// * FRONTMATTER-ONLY PARENT -- `parent:` appears in prose ("kill children before parent:"), and a 19// grep counting those said 9 declared edges when the truth was 0. 20// ★★★★★★ A LAW BANKED IN ONE ORGAN IS NOT BANKED IN THE NEXT. Putting the defence in a shared lib 21// is the only version of "we learned that" that survives the next author. 22// 23// 3. IT IS ALSO WHAT MAKES GATES SOVEREIGN. Every gate for these organs was a `sh` script run by WSL 24// from PowerShell -- Linux shell + Windows host + third-party runtime on the program path, in 25// violation of the estate's own bits-up doctrine. A gate cannot import a monolith that owns 26// `main()`, so shell was the only way to exercise them. With the logic in a lib, the gate becomes 27// an ORGAN that imports it and asserts IN-PROCESS: no shell, no fixture scripts, no CRLF or 28// backtick-escape corruption (both of which cost real debugging time this session). 29// 30// Lineage: the getdents64 + name-slot walk is the proven nx_ws_index_lib / nx_sov_tree_audit idiom. 31// Sovereign: imports only nx_syscalls. license_tier: ORIGINAL 32import "nx_syscalls.nx" 33 34const MP_SLOT: i64 = 128 // per-name byte slot 35const MP_HASH: i64 = 32768 // name index slots (power of two, > 2x corpus) 36const MP_MAXF: i64 = 8192 // max memory nodes 37 38// ---- bytes ------------------------------------------------------------------------------------ 39 40func mp_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 41 42func mp_streq(a: *u8, b: *u8) -> i64 { 43 var i: i64 = 0 44 var r: i64 = 1 45 var go: i64 = 1 46 while go == 1 { 47 if a[i] != b[i] { r = 0; go = 0 } else { 48 if a[i] == (0 as u8) { go = 0 } else { i = i + 1 } 49 } 50 } 51 return r 52} 53 54func mp_cat(buf: *u8, off: i64, s: *u8) -> i64 { 55 var o: i64 = off 56 var i: i64 = 0 57 while s[i] != (0 as u8) { buf[o] = s[i]; o = o + 1; i = i + 1 } 58 return o 59} 60 61func mp_catn(buf: *u8, off: i64, v: i64) -> i64 { 62 var o: i64 = off 63 var m: i64 = v 64 if m == 0 { buf[o] = 48 as u8; return o + 1 } 65 if m < 0 { buf[o] = 45 as u8; o = o + 1; m = 0 - m } 66 let t: *u8 = sys_mmap(32) 67 var k: i64 = 0 68 while m > 0 { t[k] = ((48 + (m % 10)) as u8); m = m / 10; k = k + 1 } 69 var i: i64 = 0 70 while i < k { buf[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } 71 return o 72} 73 74func mp_write_all(fd: i64, buf: *u8, n: i64) -> i64 { 75 var done: i64 = 0 76 var go: i64 = 1 77 while go == 1 { 78 if done >= n { go = 0 } else { 79 let w: i64 = sys_write(fd, ((buf as i64) + done) as *u8, n - done) 80 if w <= 0 { go = 0 } else { done = done + w } 81 } 82 } 83 return done 84} 85func mp_say(buf: *u8, n: i64) -> i64 { return mp_write_all(1, buf, n) } 86 87func mp_alnum(c: u8) -> i64 { 88 if c >= (65 as u8) { if c <= (90 as u8) { return 1 } } 89 if c >= (97 as u8) { if c <= (122 as u8) { return 1 } } 90 if c >= (48 as u8) { if c <= (57 as u8) { return 1 } } 91 return 0 92} 93 94// ---- files ------------------------------------------------------------------------------------ 95 96func mp_join(dst: *u8, dir: *u8, name: *u8) -> i64 { 97 var o: i64 = 0 98 var i: i64 = 0 99 while dir[i] != (0 as u8) { dst[o] = dir[i]; o = o + 1; i = i + 1 } 100 dst[o] = 47 as u8; o = o + 1 101 i = 0 102 while name[i] != (0 as u8) { dst[o] = name[i]; o = o + 1; i = i + 1 } 103 dst[o] = 0 as u8 104 return o 105} 106 107func mp_readf(p: *u8, buf: *u8, cap: i64) -> i64 { 108 let fd: i64 = sys_openat_rd(p) 109 if fd < 0 { return 0 - 1 } 110 var total: i64 = 0 111 var go: i64 = 1 112 while go == 1 { 113 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) 114 if r <= 0 { go = 0 } else { 115 total = total + r 116 if total >= cap { go = 0 } 117 } 118 } 119 sys_close(fd) 120 return total 121} 122 123func mp_is_md(name: *u8) -> i64 { 124 let n: i64 = mp_len(name) 125 if n < 4 { return 0 } 126 if name[n - 3] != (46 as u8) { return 0 } 127 if name[n - 2] != (109 as u8) { return 0 } 128 if name[n - 1] != (100 as u8) { return 0 } 129 return 1 130} 131 132func mp_base(p: *u8) -> *u8 { 133 let n: i64 = mp_len(p) 134 var i: i64 = n 135 while i > 0 { 136 if p[i - 1] == (47 as u8) { return ((p as i64) + i) as *u8 } 137 if p[i - 1] == (92 as u8) { return ((p as i64) + i) as *u8 } 138 i = i - 1 139 } 140 return p 141} 142 143// ★ THE FALSE-GREEN DEFENCE, IN ONE PLACE. An aggregate dump is a concatenation of the corpus: it 144// matches every query, cites every file, and will therefore win any ranking and manufacture lineage 145// for anything it lists. Every organ that walks or ranks the plane must skip these, and putting the 146// list here is what stops the next organ forgetting. 147func mp_is_dump(name: *u8) -> i64 { 148 if mp_streq(name, "MEMORY.overflow.md" as *u8) == 1 { return 1 } 149 if mp_streq(name, "reference-index-recent-refs-dormant.md" as *u8) == 1 { return 1 } 150 if mp_streq(name, "reference-orphan-catalogue.md" as *u8) == 1 { return 1 } 151 if mp_streq(name, "reference-memory-lineage.md" as *u8) == 1 { return 1 } 152 return 0 153} 154 155// ★★★★★★ A NAME LIST ALWAYS LAGS THE NEXT GENERATOR. THE ARTEFACT MUST DECLARE ITSELF. 156// The same defect fired THREE times in one session, each time because an organ guarded against the 157// dumps it knew about and a NEW one appeared: 158// nx_memroot -- false FLOATING 0 / GREEN on its first run 159// nx_memorph -- live orphans collapsed 581 -> 1 during conversion, and the gate passed anyway 160// nx_memhealth -- live orphans collapsed 581 -> 1 again, caught only by a final verification 161// Every occurrence was reference-memory-lineage.md, a file that did not exist when those guards were 162// written. Enumerating dumps cannot fix that class; only self-declaration can. 163// 164// So a generated artefact now stamps MP_DERIVED_MARK in its head, and any organ holding the CONTENT 165// asks the file rather than consulting a list. mp_is_dump survives only for the legacy artefacts that 166// predate the marker and are never regenerated. New generators need no organ to be updated -- which 167// is the whole point, since the updating is what kept being forgotten. 168const MP_DERIVED_MARK: *u8 = "<!-- NX-DERIVED: regenerated artefact, not authored memory -->" 169 170func mp_is_derived(buf: *u8, n: i64) -> i64 { 171 let pat: *u8 = MP_DERIVED_MARK 172 let pl: i64 = mp_len(pat) 173 var lim: i64 = n - pl 174 if lim > 600 { lim = 600 } // head only: a MENTION deep in prose is not a declaration 175 var i: i64 = 0 176 while i < lim { 177 var j: i64 = 0 178 var ok: i64 = 1 179 while j < pl { if buf[i + j] != pat[j] { ok = 0; j = pl } else { j = j + 1 } } 180 if ok == 1 { return 1 } 181 i = i + 1 182 } 183 return 0 184} 185 186// ---- name table + open-addressing index -------------------------------------------------------- 187 188func mp_nameptr(names: *u8, i: i64) -> *u8 { return ((names as i64) + i * MP_SLOT) as *u8 } 189 190func mp_hashr(p: *u8, n: i64) -> i64 { 191 var h: i64 = 2166136261 192 var i: i64 = 0 193 while i < n { 194 h = h ^ (p[i] as i64) 195 h = (h * 16777619) & 1073741823 196 i = i + 1 197 } 198 return h 199} 200 201func mp_eqr(a: *u8, p: *u8, n: i64) -> i64 { 202 var i: i64 = 0 203 while i < n { if a[i] != p[i] { return 0 } i = i + 1 } 204 if a[n] != (0 as u8) { return 0 } 205 return 1 206} 207 208func mp_put(tbl: *i64, names: *u8, idx: i64) -> i64 { 209 let nm: *u8 = mp_nameptr(names, idx) 210 var h: i64 = mp_hashr(nm, mp_len(nm)) & (MP_HASH - 1) 211 var go: i64 = 1 212 while go == 1 { 213 if tbl[h] == 0 { tbl[h] = idx + 1; go = 0 } else { h = (h + 1) & (MP_HASH - 1) } 214 } 215 return 0 216} 217 218func mp_get(tbl: *i64, names: *u8, p: *u8, n: i64) -> i64 { 219 var h: i64 = mp_hashr(p, n) & (MP_HASH - 1) 220 var r: i64 = 0 - 1 221 var go: i64 = 1 222 while go == 1 { 223 if tbl[h] == 0 { go = 0 } else { 224 let idx: i64 = tbl[h] - 1 225 if mp_eqr(mp_nameptr(names, idx), p, n) == 1 { r = idx; go = 0 } else { h = (h + 1) & (MP_HASH - 1) } 226 } 227 } 228 return r 229} 230 231// enumerate every *.md in `dir` into the name table. returns count, or -1 if the dir cannot open. 232func mp_scan(dir: *u8, names: *u8, tbl: *i64, cap: i64) -> i64 { 233 let fd: i64 = sys_openat_rd(dir) 234 if fd < 0 { return 0 - 1 } 235 let gbuf: *u8 = sys_mmap(65536) 236 var cnt: i64 = 0 237 var nread: i64 = __syscall(217, fd, gbuf, 65536, 0, 0, 0) 238 while nread > 0 { 239 var off: i64 = 0 240 while off < nread { 241 let reclen: i64 = (gbuf[off + 16] as i64) | ((gbuf[off + 17] as i64) << 8) 242 if reclen <= 0 { off = nread } else { 243 let nm: *u8 = ((gbuf as i64) + off + 19) as *u8 244 if mp_is_md(nm) == 1 { 245 if cnt < cap { 246 let dst: *u8 = mp_nameptr(names, cnt) 247 var c: i64 = 0 248 while nm[c] != (0 as u8) { if c < MP_SLOT - 1 { dst[c] = nm[c] } c = c + 1 } 249 dst[c] = 0 as u8 250 cnt = cnt + 1 251 } 252 } 253 off = off + reclen 254 } 255 } 256 nread = __syscall(217, fd, gbuf, 65536, 0, 0, 0) 257 } 258 sys_close(fd) 259 var i: i64 = 0 260 while i < MP_HASH { tbl[i] = 0; i = i + 1 } 261 i = 0 262 while i < cnt { mp_put(tbl, names, i); i = i + 1 } 263 return cnt 264} 265 266// ---- the link scanner -------------------------------------------------------------------------- 267// Finds the next `](target.md)` or `[[target]]` at or after `from`. 268// out[0]=start of target, out[1]=end (exclusive), out[2]=1 if wiki form, out[3]=position to resume. 269// Returns 1 when a VALID link was found, 0 when the buffer is exhausted. 270// 271// ★ REJECTS NESTED BRACKETS AND PATH SEPARATORS. A scanner that merely stops at the first `]]` reads 272// `[[433,94,2],[434,90,2]]` out of a code block and invents a dangling target; and `](../../x.md)` 273// points outside the corpus so it is not a sibling edge. Both rules live here so no organ re-learns 274// them. 275func mp_link_next(buf: *u8, n: i64, from: i64, out: *i64) -> i64 { 276 var p: i64 = from 277 while p + 2 < n { 278 var s: i64 = 0 - 1 279 var e: i64 = 0 - 1 280 var wiki: i64 = 0 281 if buf[p] == (93 as u8) { if buf[p + 1] == (40 as u8) { 282 s = p + 2 283 var q: i64 = s 284 var g: i64 = 1 285 while g == 1 { 286 if q >= n { g = 0 } else { if buf[q] == (41 as u8) { e = q; g = 0 } else { q = q + 1 } } 287 } 288 } } 289 if buf[p] == (91 as u8) { if buf[p + 1] == (91 as u8) { 290 s = p + 2 291 var q2: i64 = s 292 var g2: i64 = 1 293 while g2 == 1 { 294 if q2 + 1 >= n { g2 = 0 } else { 295 if buf[q2] == (93 as u8) { if buf[q2 + 1] == (93 as u8) { e = q2; g2 = 0; wiki = 1 } else { q2 = q2 + 1 } } else { q2 = q2 + 1 } 296 } 297 } 298 } } 299 if e > s { 300 let ln: i64 = e - s 301 var ok: i64 = 1 302 if ln <= 0 { ok = 0 } 303 if ln >= MP_SLOT - 4 { ok = 0 } 304 if ok == 1 { 305 let base: *u8 = ((buf as i64) + s) as *u8 306 var ci: i64 = 0 307 while ci < ln { 308 let cc: u8 = base[ci] 309 if cc == (91 as u8) { ok = 0 } 310 if cc == (93 as u8) { ok = 0 } 311 if cc == (47 as u8) { ok = 0 } 312 if cc == (92 as u8) { ok = 0 } 313 ci = ci + 1 314 } 315 } 316 // a markdown link must actually name a .md; a wiki link implies it 317 if ok == 1 { if wiki == 0 { 318 let b2: *u8 = ((buf as i64) + s) as *u8 319 var ismd: i64 = 0 320 if ln >= 3 { if b2[ln - 3] == (46 as u8) { if b2[ln - 2] == (109 as u8) { if b2[ln - 1] == (100 as u8) { ismd = 1 } } } } 321 if ismd == 0 { ok = 0 } 322 } } 323 if ok == 1 { 324 out[0] = s 325 out[1] = e 326 out[2] = wiki 327 out[3] = e 328 return 1 329 } 330 } 331 p = p + 1 332 } 333 return 0 334} 335 336// resolve a link target (adding ".md" for the wiki form) to a file index, or -1 337func mp_resolve(buf: *u8, out: *i64, names: *u8, tbl: *i64, scratch: *u8) -> i64 { 338 let s: i64 = out[0] 339 let e: i64 = out[1] 340 let wiki: i64 = out[2] 341 var pl: i64 = 0 342 var c: i64 = s 343 while c < e { scratch[pl] = buf[c]; pl = pl + 1; c = c + 1 } 344 if wiki == 1 { 345 scratch[pl] = 46 as u8; pl = pl + 1 346 scratch[pl] = 109 as u8; pl = pl + 1 347 scratch[pl] = 100 as u8; pl = pl + 1 348 } 349 scratch[pl] = 0 as u8 350 return mp_get(tbl, names, scratch, pl) 351} 352 353// ★ FRONTMATTER ONLY. `parent:` occurs in prose -- "(parent: 3-way same-day pattern)", a struct-field 354// comment, "kill children before parent:" -- and a grep counting those reported 9 declared edges when 355// the truth was 0. The edge must START A LINE inside the head of the file to count. 356func mp_declparent(buf: *u8, n: i64, out: *u8) -> i64 { 357 let pat: *u8 = "parent:" as *u8 358 let pl: i64 = mp_len(pat) 359 var lim: i64 = n - pl 360 if lim > 1200 { lim = 1200 } 361 var i: i64 = 0 362 while i < lim { 363 var atline: i64 = 0 364 if i == 0 { atline = 1 } 365 if i > 0 { if buf[i - 1] == (10 as u8) { atline = 1 } } 366 if i > 1 { if buf[i - 1] == (32 as u8) { if buf[i - 2] == (10 as u8) { atline = 1 } } } 367 if atline == 1 { 368 var j: i64 = 0 369 var ok: i64 = 1 370 while j < pl { if buf[i + j] != pat[j] { ok = 0; j = pl } else { j = j + 1 } } 371 if ok == 1 { 372 var p: i64 = i + pl 373 var go: i64 = 1 374 while go == 1 { 375 if p >= n { go = 0 } else { 376 let c: u8 = buf[p] 377 if c == (32 as u8) { p = p + 1 } else { 378 if c == (34 as u8) { p = p + 1 } else { 379 if c == (91 as u8) { p = p + 1 } else { go = 0 } 380 } 381 } 382 } 383 } 384 var o: i64 = 0 385 var g2: i64 = 1 386 while g2 == 1 { 387 if p >= n { g2 = 0 } else { 388 if o >= MP_SLOT - 5 { g2 = 0 } else { 389 let c2: u8 = buf[p] 390 var stop: i64 = 0 391 if c2 == (10 as u8) { stop = 1 } 392 if c2 == (13 as u8) { stop = 1 } 393 if c2 == (34 as u8) { stop = 1 } 394 if c2 == (93 as u8) { stop = 1 } 395 if c2 == (32 as u8) { stop = 1 } 396 if stop == 1 { g2 = 0 } else { out[o] = c2; o = o + 1; p = p + 1 } 397 } 398 } 399 } 400 out[o] = 0 as u8 401 return o 402 } 403 } 404 i = i + 1 405 } 406 out[0] = 0 as u8 407 return 0 408}