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}