code wiki / _hdl_build / nx_workstream_audit.nx
nx_workstream_audit.nx source
↩ module page · 364 lines · 17668 B
1// nx_workstream_audit.nx -- WMS-R4 LIB: the BOOT-TIME COMPLETENESS / CRASH AUDIT.
2//
3// WMS-R1 (nx_workstream_store) proves INTERNAL consistency: every id in ws:ids has a retrievable
4// seg-store segment with a real empire. R4 proves EXTERNAL completeness -- it cross-checks the
5// registry against the FILESYSTEM (memory .md files + code .nx organs) and the assignment_queue,
6// turning silently-lost workstreams into three LOUD verdicts (the crash-recovery durability proof):
7// orphan -- a registry row whose memory_link / code_link does NOT resolve to a real file
8// (the registry points at a lost/moved artifact).
9// untracked -- a real .nx code organ that NO registry row's code_link references (code exists
10// with no registry entry -- the hidden-workstream case = R4's neg-control hook).
11// unregistered -- a queue id (assignment_queue col0) that ws_get_p == WS_UNKNOWN (prose/queue-only,
12// never registered).
13//
14// COMPOSITION (anti-reinvention, rule 15): registry reads come from nx_workstream_store
15// (ws_manifest_p / ws_get_p / ws_field) read-only; the FS walk reuses the struct-free raw-offset
16// getdents64 idiom + dirent_reclen/dirent_type/dirent_name + dotlike-skip + last-'.' extension scan
17// PROVEN in nx_nas_book_census (the NxDirent struct path crashes signal-11 under nx_cc_sovereign --
18// LANDMINE avoided; we use nx_syscalls.nx ONLY). memory + code + queue are FLAT scans, so no work-stack
19// is needed -- a single depth-1 getdents64 loop (simpler than the census tree walk).
20//
21// CRITICAL drvfs caveat (nx_nas_book_census line 277): getdents64 returns DT_UNKNOWN(0) for regular
22// files on WSL drvfs -- so the file candidate test accepts dtype 8 (DT_REG) AND 0 (DT_UNKNOWN).
23//
24// CRITICAL code_link path-shift hazard (verified): seed code_links like "runtime/nx_geo.nx" /
25// "runtime/nx_drv_proto_emit.nx" / "runtime/nx_root_trace.nx" do NOT exist at those literal paths --
26// the files live in runtime/_hdl_build/. A naive "literal path missing -> orphan" check would emit
27// ~3 FALSE orphans. So the code-existence check MIRRORS harness resolution: try the literal link,
28// else fall back to the basename under runtime/ then runtime/_hdl_build/. A "-" link = intentionally
29// none (not an orphan).
30//
31// Sovereign: imports only nx_workstream_store (R1 store, read-only) + nx_seg_store + nx_syscalls.
32// Additive-only: this LIB does NOT modify R1's store. license_tier: ORIGINAL
33import "nx_workstream_store.nx"
34import "nx_seg_store.nx"
35import "nx_syscalls.nx"
36const WA_MAGIC_4096: i64 = 4096
37const WA_MAGIC_1024: i64 = 1024
38const WA_MAGIC_1048640: i64 = 1048640
39const WA_MAGIC_1048576: i64 = 1048576
40
41// ---- data-driven config (rule 11: no magic numbers / paths buried in code) ----
42const WA_MEM_DIR: *u8 = "/mnt/c/Users/elder/.claude/projects/C--Users-elder/memory/"
43const WA_QUEUE: *u8 = "knowledge/registry/assignment_queue.tsv"
44const WA_CODE_DIR_A: *u8 = "runtime/"
45const WA_CODE_DIR_B: *u8 = "runtime/_hdl_build/"
46const WA_FLD_MEMORY: i64 = 4
47const WA_FLD_CODE: i64 = 5
48const WA_ITER_CAP: i64 = 200000 // hard bound on getdents records scanned (JPL rule 2)
49
50// ---- string helpers (mirror nx_nas_book_census bc_* / store ws_len) ----
51func wa_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
52
53func wa_streq(a: *u8, b: *u8) -> i64 {
54 var i: i64 = 0
55 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
56 if b[i] != (0 as u8) { return 0 }
57 return 1
58}
59
60// last path segment (after the last '/') of a NUL-term path -> basename ptr (into the same buffer).
61func wa_basename(path: *u8) -> *u8 {
62 let n: i64 = wa_slen(path)
63 var last: i64 = 0 - 1
64 var i: i64 = 0
65 while i < n { if path[i] == (47 as u8) { last = i } i = i + 1 }
66 return (path as i64 + last + 1) as *u8
67}
68
69// fd>=0 existence probe (close immediately). 1 present, 0 absent. The proven file-exists idiom.
70func wa_path_exists(path: *u8) -> i64 {
71 let fd: i64 = sys_openat_rd(path)
72 if fd < 0 { return 0 }
73 sys_close(fd)
74 return 1
75}
76
77// join dir + name into out (NUL-term). dir already ends with '/' (or not -- we do NOT add one here;
78// callers that need a separator pass a dir literal ending in '/'). Returns out length.
79func wa_join(dir: *u8, name: *u8, out: *u8) -> i64 {
80 var w: i64 = 0
81 var i: i64 = 0
82 while dir[i] != (0 as u8) { out[w] = dir[i]; w = w + 1; i = i + 1 }
83 var j: i64 = 0
84 while name[j] != (0 as u8) { out[w] = name[j]; w = w + 1; j = j + 1 }
85 out[w] = 0 as u8
86 return w
87}
88
89// resolve a code_link the way the harness does: literal, else basename under runtime/ then
90// runtime/_hdl_build/. "-" => 0 (intentionally none, NOT an orphan). 1 if resolvable, 0 otherwise.
91func wa_code_link_resolves(link: *u8) -> i64 {
92 if link[0] == (45 as u8) { if link[1] == (0 as u8) { return 0 } } // "-"
93 if wa_path_exists(link) == 1 { return 1 }
94 let base: *u8 = wa_basename(link)
95 let buf: *u8 = sys_mmap(WA_MAGIC_4096)
96 wa_join(WA_CODE_DIR_A, base, buf)
97 if wa_path_exists(buf) == 1 { return 1 }
98 wa_join(WA_CODE_DIR_B, base, buf)
99 if wa_path_exists(buf) == 1 { return 1 }
100 return 0
101}
102
103// resolve a memory_link: "-" => 0 (none, not an orphan); else memory_link basename must exist under
104// mem_dir (mem_dir ends with '/'). 1 if resolvable, 0 otherwise.
105func wa_mem_link_resolves(link: *u8, mem_dir: *u8) -> i64 {
106 if link[0] == (45 as u8) { if link[1] == (0 as u8) { return 0 } } // "-"
107 let base: *u8 = wa_basename(link)
108 let buf: *u8 = sys_mmap(WA_MAGIC_4096)
109 wa_join(mem_dir, base, buf)
110 return wa_path_exists(buf)
111}
112
113// ---- ORPHAN pass: for each registry id, a non-"-" memory_link MUST resolve to a real file under
114// mem_dir AND a non-"-" code_link MUST resolve (basename-aware). Returns count of orphans, fills
115// orphan_out[] with offending ids (mmap'd NUL-term copies, LOUD). 0 == none. ----
116func ws_audit_orphans_p(prefix: *u8, mem_dir: *u8, orphan_out: *i64, cap: i64) -> i64 {
117 let ids: *i64 = sys_mmap(8 * WA_MAGIC_4096) as *i64
118 let n: i64 = ws_manifest_p(prefix, ids, WA_MAGIC_4096)
119 if n < 0 { return 0 - 1 }
120 // SCALE FIX (post-crash hardening 2026-06-14): snapshot the registry ONCE via ss_open; the
121 // per-id lookup below is ss_hget (in-memory, zero file IO). The old per-id ws_get_p mmap'd
122 // ~100KB/call and starved the bump-allocator at ~1162 ids. Mirrors ws_audit_complete_p.
123 let h: *i64 = ss_open(prefix)
124 let pq: *i64 = sys_mmap(16) as *i64
125 let lq: *i64 = sys_mmap(16) as *i64
126 let keybuf: *u8 = sys_mmap(256)
127 let mlink: *u8 = sys_mmap(WA_MAGIC_1024)
128 let clink: *u8 = sys_mmap(WA_MAGIC_1024)
129 var orph: i64 = 0
130 var i: i64 = 0
131 while i < n {
132 let id: *u8 = ids[i] as *u8
133 // key = "ws:" + id
134 keybuf[0] = 119 as u8; keybuf[1] = 115 as u8; keybuf[2] = 58 as u8 // 'w','s',':'
135 var t: i64 = 0
136 while id[t] != (0 as u8) { keybuf[3 + t] = id[t]; t = t + 1 }
137 keybuf[3 + t] = 0 as u8
138 var bad: i64 = 0
139 if ss_hget(h, keybuf, pq, lq) == 1 {
140 ws_field(pq[0] as *u8, lq[0], WA_FLD_MEMORY, mlink)
141 ws_field(pq[0] as *u8, lq[0], WA_FLD_CODE, clink)
142 // memory_link non-"-" must resolve
143 var mneed: i64 = 1
144 if mlink[0] == (45 as u8) { if mlink[1] == (0 as u8) { mneed = 0 } }
145 if mneed == 1 { if wa_mem_link_resolves(mlink, mem_dir) == 0 { bad = 1 } }
146 // code_link non-"-" must resolve (basename-aware -- avoids the path-shift false orphans)
147 var cneed: i64 = 1
148 if clink[0] == (45 as u8) { if clink[1] == (0 as u8) { cneed = 0 } }
149 if cneed == 1 { if wa_code_link_resolves(clink) == 0 { bad = 1 } }
150 }
151 if bad == 1 {
152 if orph < cap {
153 let ob: *u8 = sys_mmap(256)
154 var z: i64 = 0
155 while id[z] != (0 as u8) { ob[z] = id[z]; z = z + 1 }
156 ob[z] = 0 as u8
157 orphan_out[orph] = ob as i64
158 }
159 orph = orph + 1
160 }
161 i = i + 1
162 }
163 return orph
164}
165
166// (wa_code_referenced REMOVED 2026-06-14: it re-scanned the WHOLE registry per organ via leaking
167// per-key ss_get -> O(N_organs x N_ids) blowup that wedged the audit at scale. ws_audit_untracked_p
168// now snapshots the referenced-basename set ONCE via ss_open/ss_hget; see its body. Note kept so the
169// footgun isn't reintroduced.)
170
171// 1 iff name ends with ".nx" (the code-organ extension test; last-'.' scan idiom).
172func wa_is_nx(name: *u8) -> i64 {
173 let n: i64 = wa_slen(name)
174 if n < 3 { return 0 }
175 if name[n - 3] != (46 as u8) { return 0 } // '.'
176 if name[n - 2] != (110 as u8) { return 0 } // 'n'
177 if name[n - 1] != (120 as u8) { return 0 } // 'x'
178 return 1
179}
180
181// ---- UNTRACKED pass: walk code_dir (struct-free getdents64), for each *.nx organ NOT referenced by
182// ANY registry row's code_link (basename match), report it. Returns count, fills untracked_out[].
183// This is the NEG-CONTROL hook: a code file with no registry row is CAUGHT here. ----
184func ws_audit_untracked_p(prefix: *u8, code_dir: *u8, untracked_out: *i64, cap: i64) -> i64 {
185 // --- SCALE FIX (post-crash hardening 2026-06-14): build the set of code_link basenames
186 // referenced by ANY registry row ONCE, from a single store snapshot (ss_open + ss_hget). The
187 // old path called wa_code_referenced PER organ, each re-scanning all ~1162 ids via leaking
188 // per-key ss_get -> O(N_organs x N_ids) ~2.6M file-backed mmaps over 2259 organs (minutes +
189 // OOM-edge). Now O(N_ids + N_organs) with all registry IO done once. Semantics identical. ---
190 let ids: *i64 = sys_mmap(8 * WA_MAGIC_4096) as *i64
191 let nid: i64 = ws_manifest_p(prefix, ids, WA_MAGIC_4096)
192 let refset: *i64 = sys_mmap(8 * WA_MAGIC_4096) as *i64
193 var nref: i64 = 0
194 if nid > 0 {
195 let h: *i64 = ss_open(prefix)
196 let pqr: *i64 = sys_mmap(16) as *i64
197 let lqr: *i64 = sys_mmap(16) as *i64
198 let kb: *u8 = sys_mmap(256)
199 let clink: *u8 = sys_mmap(WA_MAGIC_1024)
200 var ri: i64 = 0
201 while ri < nid {
202 let id: *u8 = ids[ri] as *u8
203 kb[0] = 119 as u8; kb[1] = 115 as u8; kb[2] = 58 as u8
204 var kt: i64 = 0
205 while id[kt] != (0 as u8) { kb[3 + kt] = id[kt]; kt = kt + 1 }
206 kb[3 + kt] = 0 as u8
207 if ss_hget(h, kb, pqr, lqr) == 1 {
208 ws_field(pqr[0] as *u8, lqr[0], WA_FLD_CODE, clink)
209 var csk: i64 = 0
210 if clink[0] == (45 as u8) { if clink[1] == (0 as u8) { csk = 1 } } // "-"
211 if csk == 0 {
212 let cbase: *u8 = wa_basename(clink)
213 if nref < WA_MAGIC_4096 {
214 let sb: *u8 = sys_mmap(256)
215 var zc: i64 = 0
216 while cbase[zc] != (0 as u8) { sb[zc] = cbase[zc]; zc = zc + 1 }
217 sb[zc] = 0 as u8
218 refset[nref] = sb as i64
219 nref = nref + 1
220 }
221 }
222 }
223 ri = ri + 1
224 }
225 }
226 let dfd: i64 = sys_openat_rd(code_dir)
227 if dfd < 0 { return 0 - 1 }
228 let dirbuf: *u8 = sys_mmap(WA_MAGIC_1048640) // getdents64 batch buffer (1 MiB)
229 var untr: i64 = 0
230 var iters: i64 = 0
231 var done: i64 = 0
232 while done == 0 {
233 let nb: i64 = sys_getdents64(dfd, dirbuf, WA_MAGIC_1048576)
234 if nb <= 0 { done = 1 }
235 else {
236 var off: i64 = 0
237 while off < nb {
238 if iters >= WA_ITER_CAP { off = nb; done = 1 }
239 else {
240 iters = iters + 1
241 let rec: *u8 = (dirbuf as i64 + off) as *u8
242 let rl: i64 = dirent_reclen(rec)
243 if rl <= 0 { off = nb }
244 else {
245 let nm: *u8 = dirent_name(rec)
246 let dt: i64 = dirent_type(rec)
247 // dotlike skip
248 var skip: i64 = 0
249 if nm[0] == (46 as u8) {
250 if nm[1] == (0 as u8) { skip = 1 }
251 else { if nm[1] == (46 as u8) { if nm[2] == (0 as u8) { skip = 1 } } }
252 }
253 // file candidate: DT_REG(8) OR DT_UNKNOWN(0) (drvfs), and a *.nx name
254 var iscand: i64 = 0
255 if dt == 8 { iscand = 1 }
256 if dt == 0 { iscand = 1 }
257 if skip == 0 { if iscand == 1 { if wa_is_nx(nm) == 1 {
258 // in-memory membership against the pre-built referenced set (zero IO)
259 var referenced: i64 = 0
260 var mr: i64 = 0
261 while mr < nref {
262 if wa_streq(refset[mr] as *u8, nm) == 1 { referenced = 1; mr = nref }
263 else { mr = mr + 1 }
264 }
265 if referenced == 0 {
266 if untr < cap {
267 let ub: *u8 = sys_mmap(256)
268 var z: i64 = 0
269 while nm[z] != (0 as u8) { ub[z] = nm[z]; z = z + 1 }
270 ub[z] = 0 as u8
271 untracked_out[untr] = ub as i64
272 }
273 untr = untr + 1
274 }
275 } } }
276 off = off + rl
277 }
278 }
279 }
280 }
281 }
282 sys_close(dfd)
283 return untr
284}
285
286// ---- UNREGISTERED pass: read assignment_queue.tsv, for each non-comment row's col0 id that
287// ws_get_p(ws:<id>) == WS_UNKNOWN, report it. Returns count, fills unreg_out[].
288// Line model: each iteration is positioned at a line START; we extract col0 (up to TAB/newline),
289// then advance `i` to the byte AFTER the next newline (or to n at EOF). '#'-leading lines are skipped.
290func ws_audit_unregistered_p(prefix: *u8, queue_path: *u8, unreg_out: *i64, cap: i64) -> i64 {
291 let lenq: *i64 = sys_mmap(16) as *i64
292 let buf: *u8 = sys_read_file(queue_path, lenq)
293 if buf as i64 == 0 { return 0 - 1 }
294 let n: i64 = lenq[0]
295 // SCALE FIX (post-crash hardening 2026-06-14): snapshot the registry ONCE; the per-row lookup
296 // below is ss_hget (zero file IO). The old per-row ws_get_p leaked ~100KB/call over the queue.
297 let h: *i64 = ss_open(prefix)
298 let pq: *i64 = sys_mmap(16) as *i64
299 let lq: *i64 = sys_mmap(16) as *i64
300 let idbuf: *u8 = sys_mmap(256)
301 let keybuf: *u8 = sys_mmap(256)
302 var unreg: i64 = 0
303 var i: i64 = 0
304 var iters: i64 = 0
305 while i < n {
306 if iters >= WA_ITER_CAP { i = n }
307 else {
308 iters = iters + 1
309 let is_comment: i64 = (buf[i] == (35 as u8)) as i64 // '#'
310 // extract col0 id = bytes up to first TAB or newline (only if not a comment)
311 var o: i64 = 0
312 if is_comment == 0 {
313 var go: i64 = 1
314 while go == 1 {
315 if i >= n { go = 0 }
316 else {
317 if buf[i] == (9 as u8) { go = 0 }
318 else { if buf[i] == (10 as u8) { go = 0 } else { if o < 255 { idbuf[o] = buf[i]; o = o + 1 } i = i + 1 } }
319 }
320 }
321 idbuf[o] = 0 as u8
322 }
323 // advance i to the byte AFTER the next newline (consumes the rest of this line).
324 // skip=1 until we step PAST a newline; at EOF the while just terminates on i>=n.
325 var skip: i64 = 1
326 while skip == 1 {
327 if i >= n { skip = 0 }
328 else { if buf[i] == (10 as u8) { i = i + 1; skip = 0 } else { i = i + 1 } }
329 }
330 if is_comment == 0 { if o > 0 {
331 keybuf[0] = 119 as u8; keybuf[1] = 115 as u8; keybuf[2] = 58 as u8
332 var t: i64 = 0
333 while idbuf[t] != (0 as u8) { keybuf[3 + t] = idbuf[t]; t = t + 1 }
334 keybuf[3 + t] = 0 as u8
335 if ss_hget(h, keybuf, pq, lq) == WS_UNKNOWN {
336 if unreg < cap {
337 let rb: *u8 = sys_mmap(256)
338 var z: i64 = 0
339 while idbuf[z] != (0 as u8) { rb[z] = idbuf[z]; z = z + 1 }
340 rb[z] = 0 as u8
341 unreg_out[unreg] = rb as i64
342 }
343 unreg = unreg + 1
344 }
345 } }
346 }
347 }
348 return unreg
349}
350
351// ---- roll-up: total lost-things = orphans + untracked + unregistered. The orphan count is the
352// hard lost-things invariant (0 == GREEN, the registry points at nothing lost). untracked/unregistered
353// are returned to the caller too (informational on production; the GATE proves the DETECTORS work). ----
354func ws_audit_complete_r4_p(prefix: *u8, mem_dir: *u8, code_dir: *u8, queue_path: *u8) -> i64 {
355 let scratch: *i64 = sys_mmap(8 * WA_MAGIC_4096) as *i64
356 var total: i64 = 0
357 let o: i64 = ws_audit_orphans_p(prefix, mem_dir, scratch, WA_MAGIC_4096)
358 if o > 0 { total = total + o }
359 let u: i64 = ws_audit_untracked_p(prefix, code_dir, scratch, WA_MAGIC_4096)
360 if u > 0 { total = total + u }
361 let r: i64 = ws_audit_unregistered_p(prefix, queue_path, scratch, WA_MAGIC_4096)
362 if r > 0 { total = total + r }
363 return total
364}