code wiki / _hdl_build / nx_wiki_walkability.nx
nx_wiki_walkability.nx source
↩ module page · 462 lines · 19004 B
1// nx_wiki_walkability.nx -- IMS arc: MEASURED walkability scorecard over the
2// REAL published wiki link graph (READ-ONLY baseline, no assertions).
3//
4// PURPOSE: produce the honest navigation baseline the operator asked for --
5// NUMBERS from the link graph, not claims. Over the live corpus it computes:
6// 1. N pages, total internal page-links, avg/min/max OUT-degree + IN-degree.
7// 2. ORPHANS (in==0, excluding root) + DEAD-ENDS (out==0), with slugs.
8// 3. RECIPROCAL page pairs (A->B AND B->A) vs ONE-WAY pairs.
9// 4. Reachability from start: reachable count, max clicks-to-reach (BFS
10// depth), and STRANDED pages (unreachable from start).
11//
12// READ-ONLY BY CONSTRUCTION: reads ONE framed corpus snapshot via sys_read_file
13// (the exact A1 transport) and computes in memory. Writes NOTHING to the corpus
14// -- the only output is the scorecard to stdout + a status log.
15//
16// COMPOSES (DRY -- zero new corpus/link/slug substrate invented):
17// nx_ims_monitor nx_ims_collect_targets (the ONE unified outbound
18// [[wikilink]]+href extractor), nx_ims_slug_eq (the
19// ONE slug resolver), nx_ims_resolve_root, and the
20// NxWikiDocStore it composes -- so this scorecard's
21// notion of "a link/edge" is byte-identical to the
22// orphan/dead-link monitor and the graph builder.
23// nx_syscalls sys_read_file (read-only) + sys_mmap scratch.
24//
25// The adjacency is built ONCE into a dense N*N edge bitmatrix (self-edges
26// dropped -- a page linking itself is not navigation), then every metric is a
27// pure read of that matrix. N is small (corpus cap), so N*N is trivial.
28//
29// Hygiene: M1 out-params; M3 every while hard-capped; M5 bounded indexing;
30// M6 real semantics; M7 named constants; M8 verdicts. ("loop"/"match" reserved.)
31// license_tier: ORIGINAL
32import "nx_ims_monitor.nx"
33import "nx_syscalls.nx"
34const NX_MAGIC_8192: i64 = 8192
35const NX_MAGIC_16384: i64 = 16384
36const NX_MAGIC_1000000: i64 = 1000000
37
38// ===== Sealed verdict surface (codes 2760-2779) ==============================
39const NX_WALK_OK: i64 = 0
40const NX_WALK_BAD_INPUT: i64 = 2760
41const NX_WALK_OVERFLOW: i64 = 2761
42
43// ===== Named sizing constants (M7) ===========================================
44const WALK_MAX_PAGES: i64 = 256 // corpus cap for the dense matrix
45const WALK_MAX_BYTES: i64 = 4194304 // 4 MB snapshot cap
46const WALK_MARK_PAGE_N: i64 = 8 // len("###PAGE ")
47const WALK_MAX_LINKS_PP: i64 = 512 // outbound targets scanned per page
48
49// ===== tiny stdout+log writers (mirror the A1 live runner) ===================
50func wlp(fd: i64, s: *u8) -> i64 {
51 var n: i64 = 0
52 while s[n] != (0 as u8) { n = n + 1 }
53 sys_write(1, s, n)
54 if fd > 0 { sys_write(fd, s, n) }
55 return 0
56}
57func wln(fd: i64, v: i64) -> i64 {
58 var m: i64 = v
59 if m < 0 { wlp(fd, "-\x00" as *u8); m = 0 - m }
60 let t: *u8 = sys_mmap(28)
61 var k: i64 = 0
62 if m == 0 { t[0] = 48 as u8; k = 1 }
63 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
64 let bb: *u8 = sys_mmap(28)
65 var i: i64 = 0
66 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
67 sys_write(1, bb, k)
68 if fd > 0 { sys_write(fd, bb, k) }
69 return 0
70}
71// print a value scaled by 100 as "X.YY" (integer fixed-point, no float)
72func wln2(fd: i64, scaled: i64) -> i64 {
73 wln(fd, scaled / 100)
74 wlp(fd, ".\x00" as *u8)
75 let frac: i64 = scaled - ((scaled / 100) * 100)
76 if frac < 10 { wlp(fd, "0\x00" as *u8) }
77 wln(fd, frac)
78 return 0
79}
80func wlb(fd: i64, p: *u8, n: i64) -> i64 {
81 var i: i64 = 0
82 while i < n {
83 if i >= NX_MAGIC_8192 { i = n }
84 if i < n {
85 sys_write(1, (p as i64 + i) as *u8, 1)
86 if fd > 0 { sys_write(fd, (p as i64 + i) as *u8, 1) }
87 }
88 i = i + 1
89 }
90 return 0
91}
92
93// ===== framed-snapshot parse helpers (mirror nx_ims_monitor_live) ============
94func walk_parse_int(buf: *u8, off: i64, lim: i64, out_off: *i64) -> i64 {
95 var i: i64 = off
96 var v: i64 = 0
97 var done: i64 = 0
98 while done == 0 {
99 if i >= lim { done = 1 }
100 if done == 0 {
101 let c: i64 = buf[i] as i64
102 if c < 0x30 { done = 1 }
103 if done == 0 { if c > 0x39 { done = 1 } }
104 if done == 0 { v = v * 10 + (c - 0x30); i = i + 1 }
105 }
106 }
107 out_off[0] = i
108 return v
109}
110func walk_is_page_mark(buf: *u8, off: i64, lim: i64) -> i64 {
111 let mk: *u8 = "###PAGE \x00" as *u8
112 if off + WALK_MARK_PAGE_N > lim { return 0 }
113 var i: i64 = 0
114 var ok: i64 = 1
115 while i < WALK_MARK_PAGE_N {
116 if buf[off + i] != mk[i] { ok = 0 }
117 i = i + 1
118 }
119 return ok
120}
121
122// Ingest the framed corpus snapshot into the doc store (READ-ONLY). Returns the
123// page count (>=0) or -verdict. Identical framing/logic to the A1 live runner so
124// the SAME corpus is measured.
125func walk_ingest(store: *NxWikiDocStore, buf: *u8, total: i64) -> i64 {
126 var i: i64 = 0
127 var npages: i64 = 0
128 let oo: *i64 = sys_mmap(8) as *i64
129 while i < total {
130 if walk_is_page_mark(buf, i, total) == 1 {
131 let url_off: i64 = i + WALK_MARK_PAGE_N
132 var u: i64 = url_off
133 var done_u: i64 = 0
134 while done_u == 0 {
135 if u >= total { done_u = 1 }
136 if done_u == 0 {
137 if buf[u] == (0x20 as u8) { done_u = 1 }
138 if done_u == 0 { u = u + 1 }
139 }
140 }
141 let url_n: i64 = u - url_off
142 let blen: i64 = walk_parse_int(buf, u + 1, total, oo)
143 var hdr_end: i64 = oo[0]
144 var done_h: i64 = 0
145 while done_h == 0 {
146 if hdr_end >= total { done_h = 1 }
147 if done_h == 0 {
148 if buf[hdr_end] == (0x0A as u8) { done_h = 1 }
149 if done_h == 0 { hdr_end = hdr_end + 1 }
150 }
151 }
152 let body_off: i64 = hdr_end + 1
153 var body_n: i64 = blen
154 if body_off + body_n > total { body_n = total - body_off }
155 let url_p: *u8 = (buf as i64 + url_off) as *u8
156 let body_p: *u8 = (buf as i64 + body_off) as *u8
157 let rid: i64 = nx_wiki_doc_store_add(store, url_p, url_n, url_p, url_n, body_p, body_n)
158 if rid >= 0 { npages = npages + 1 }
159 i = body_off + body_n
160 } else {
161 i = i + 1
162 }
163 }
164 return npages
165}
166
167// ===== dense adjacency matrix ================================================
168//
169// adj[s*N + d] = 1 iff page s has an outbound internal PAGE-link to page d,
170// s != d (self-links dropped: not navigation). Built ONCE from the shared
171// nx_ims_collect_targets + nx_ims_slug_eq. Returns NX_WALK_OK or -verdict.
172func walk_build_adj(store: *NxWikiDocStore, adj: *i64, n: i64) -> i64 {
173 var z: i64 = 0
174 while z < n * n {
175 if z >= WALK_MAX_PAGES * WALK_MAX_PAGES { return 0 - NX_WALK_OVERFLOW }
176 adj[z] = 0
177 z = z + 1
178 }
179 let offs: *i64 = sys_mmap(WALK_MAX_LINKS_PP * 8) as *i64
180 let lens: *i64 = sys_mmap(WALK_MAX_LINKS_PP * 8) as *i64
181 let lc: *i64 = sys_mmap(8) as *i64
182 let tp: *i64 = sys_mmap(8) as *i64
183 let tn: *i64 = sys_mmap(8) as *i64
184 let up: *i64 = sys_mmap(8) as *i64
185 let un: *i64 = sys_mmap(8) as *i64
186 let bp: *i64 = sys_mmap(8) as *i64
187 let bn: *i64 = sys_mmap(8) as *i64
188 let u2p: *i64 = sys_mmap(8) as *i64
189 let u2n: *i64 = sys_mmap(8) as *i64
190 let t2p: *i64 = sys_mmap(8) as *i64
191 let t2n: *i64 = sys_mmap(8) as *i64
192 let b2p: *i64 = sys_mmap(8) as *i64
193 let b2n: *i64 = sys_mmap(8) as *i64
194
195 var s: i64 = 0
196 while s < n {
197 if s >= WALK_MAX_PAGES { return 0 - NX_WALK_OVERFLOW }
198 let rc_s: i64 = nx_wiki_doc_store_lookup(store, s, tp, tn, up, un, bp, bn)
199 if rc_s == NX_WIB_OK {
200 let body: *u8 = bp[0] as *u8
201 let body_n: i64 = bn[0]
202 let rc_c: i64 = nx_ims_collect_targets(body, body_n, offs, lens, WALK_MAX_LINKS_PP, lc)
203 if rc_c == NX_IMS_OK {
204 var k: i64 = 0
205 while k < lc[0] {
206 if k >= WALK_MAX_LINKS_PP { k = lc[0] }
207 if k < lc[0] {
208 let lp: *u8 = (body as i64 + offs[k]) as *u8
209 let ln: i64 = lens[k]
210 var dst: i64 = 0 - 1
211 var t: i64 = 0
212 while t < n {
213 if dst < 0 {
214 let rc_t: i64 = nx_wiki_doc_store_lookup(store, t, t2p, t2n, u2p, u2n, b2p, b2n)
215 if rc_t == NX_WIB_OK {
216 if nx_ims_slug_eq(lp, ln, u2p[0] as *u8, u2n[0]) == 1 { dst = t }
217 }
218 }
219 t = t + 1
220 }
221 if dst >= 0 {
222 if dst != s { adj[s * n + dst] = 1 } // dedup + drop self
223 }
224 }
225 k = k + 1
226 }
227 }
228 }
229 s = s + 1
230 }
231 return NX_WALK_OK
232}
233
234func main() -> i64 {
235 let logfd: i64 = sys_openat_append("knowledge/status/wiki_walkability.log\x00" as *u8, 0x1a4)
236 wlp(logfd, "=== WIKI WALKABILITY SCORECARD (MEASURED, read-only) ===\n\x00" as *u8)
237
238 // ---- read the framed snapshot (READ-ONLY, the SAME A1 corpus) ----
239 let lenbox: *i64 = sys_mmap(16) as *i64
240 lenbox[0] = 0
241 let buf: *u8 = sys_read_file("knowledge/status/ims_live_corpus.txt\x00" as *u8, lenbox)
242 if (buf as i64) == 0 { wlp(logfd, "FATAL: cannot read ims_live_corpus.txt\n\x00" as *u8); sys_exit(2) }
243 let total: i64 = lenbox[0]
244 wlp(logfd, "snapshot bytes=\x00" as *u8); wln(logfd, total); wlp(logfd, "\n\x00" as *u8)
245 if total < 1 { wlp(logfd, "FATAL: empty snapshot\n\x00" as *u8); sys_exit(2) }
246 if total > WALK_MAX_BYTES { wlp(logfd, "FATAL: snapshot too large\n\x00" as *u8); sys_exit(2) }
247
248 let store: *NxWikiDocStore = sys_mmap(512) as *NxWikiDocStore
249 nx_wiki_doc_store_init(store, 64, NX_MAGIC_16384, NX_MAGIC_16384, WALK_MAX_BYTES)
250 let n: i64 = walk_ingest(store, buf, total)
251 if n < 1 { wlp(logfd, "FATAL: 0 pages ingested\n\x00" as *u8); sys_exit(2) }
252 if n > WALK_MAX_PAGES { wlp(logfd, "FATAL: too many pages\n\x00" as *u8); sys_exit(2) }
253
254 // ---- resolve root = start ----
255 let root: i64 = nx_ims_resolve_root(store, "start\x00" as *u8, 5)
256
257 // ---- build the dense adjacency ONCE ----
258 let adj: *i64 = sys_mmap(WALK_MAX_PAGES * WALK_MAX_PAGES * 8) as *i64
259 let rc_a: i64 = walk_build_adj(store, adj, n)
260 if rc_a != NX_WALK_OK { wlp(logfd, "FATAL: adj build failed rc=\x00" as *u8); wln(logfd, rc_a); wlp(logfd, "\n\x00" as *u8); sys_exit(2) }
261
262 // ---- per-page out/in degree ----
263 let outd: *i64 = sys_mmap(WALK_MAX_PAGES * 8) as *i64
264 let ind: *i64 = sys_mmap(WALK_MAX_PAGES * 8) as *i64
265 var total_edges: i64 = 0
266 var p: i64 = 0
267 while p < n {
268 var od: i64 = 0
269 var id: i64 = 0
270 var q: i64 = 0
271 while q < n {
272 od = od + adj[p * n + q] // p -> q
273 id = id + adj[q * n + p] // q -> p
274 q = q + 1
275 }
276 outd[p] = od
277 ind[p] = id
278 total_edges = total_edges + od
279 p = p + 1
280 }
281
282 // degree min/max/avg (avg scaled x100)
283 var omin: i64 = NX_MAGIC_1000000
284 var omax: i64 = 0
285 var imin: i64 = NX_MAGIC_1000000
286 var imax: i64 = 0
287 var pp: i64 = 0
288 while pp < n {
289 if outd[pp] < omin { omin = outd[pp] }
290 if outd[pp] > omax { omax = outd[pp] }
291 if ind[pp] < imin { imin = ind[pp] }
292 if ind[pp] > imax { imax = ind[pp] }
293 pp = pp + 1
294 }
295 let avg_scaled: i64 = (total_edges * 100) / n // == avg out == avg in (sum identical)
296
297 // lookup out-params for printing slugs
298 let tp: *i64 = sys_mmap(8) as *i64; let tn: *i64 = sys_mmap(8) as *i64
299 let up: *i64 = sys_mmap(8) as *i64; let un: *i64 = sys_mmap(8) as *i64
300 let bp: *i64 = sys_mmap(8) as *i64; let bn: *i64 = sys_mmap(8) as *i64
301
302 // ---- per-page table ----
303 wlp(logfd, "pages N=\x00" as *u8); wln(logfd, n)
304 wlp(logfd, " root(start) rowid=\x00" as *u8); wln(logfd, root); wlp(logfd, "\n\x00" as *u8)
305 wlp(logfd, "--- PER-PAGE DEGREE (internal page-links; self-links dropped) ---\n\x00" as *u8)
306 var pr: i64 = 0
307 while pr < n {
308 nx_wiki_doc_store_lookup(store, pr, tp, tn, up, un, bp, bn)
309 wlp(logfd, " [\x00" as *u8); wln(logfd, pr); wlp(logfd, "] \x00" as *u8)
310 wlb(logfd, up[0] as *u8, un[0])
311 wlp(logfd, " out=\x00" as *u8); wln(logfd, outd[pr])
312 wlp(logfd, " in=\x00" as *u8); wln(logfd, ind[pr]); wlp(logfd, "\n\x00" as *u8)
313 pr = pr + 1
314 }
315
316 // ---- 1. aggregate metrics ----
317 wlp(logfd, "--- 1. GRAPH METRICS ---\n\x00" as *u8)
318 wlp(logfd, " total internal page-links (edges)=\x00" as *u8); wln(logfd, total_edges); wlp(logfd, "\n\x00" as *u8)
319 wlp(logfd, " avg OUT-links/page=\x00" as *u8); wln2(logfd, avg_scaled)
320 wlp(logfd, " (min=\x00" as *u8); wln(logfd, omin); wlp(logfd, " max=\x00" as *u8); wln(logfd, omax); wlp(logfd, ")\n\x00" as *u8)
321 wlp(logfd, " avg IN-links/page=\x00" as *u8); wln2(logfd, avg_scaled)
322 wlp(logfd, " (min=\x00" as *u8); wln(logfd, imin); wlp(logfd, " max=\x00" as *u8); wln(logfd, imax); wlp(logfd, ")\n\x00" as *u8)
323
324 // ---- 2. orphans (in==0, excl root) + dead-ends (out==0) ----
325 wlp(logfd, "--- 2. ORPHANS (in==0, excl root) + DEAD-ENDS (out==0) ---\n\x00" as *u8)
326 var n_orph: i64 = 0
327 var n_dead: i64 = 0
328 wlp(logfd, " ORPHANS:\x00" as *u8)
329 var oi: i64 = 0
330 while oi < n {
331 if ind[oi] == 0 {
332 if oi != root {
333 nx_wiki_doc_store_lookup(store, oi, tp, tn, up, un, bp, bn)
334 wlp(logfd, " \x00" as *u8); wlb(logfd, up[0] as *u8, un[0])
335 n_orph = n_orph + 1
336 }
337 }
338 oi = oi + 1
339 }
340 wlp(logfd, " (count=\x00" as *u8); wln(logfd, n_orph); wlp(logfd, ")\n\x00" as *u8)
341 wlp(logfd, " DEAD-ENDS:\x00" as *u8)
342 var di: i64 = 0
343 while di < n {
344 if outd[di] == 0 {
345 nx_wiki_doc_store_lookup(store, di, tp, tn, up, un, bp, bn)
346 wlp(logfd, " \x00" as *u8); wlb(logfd, up[0] as *u8, un[0])
347 n_dead = n_dead + 1
348 }
349 di = di + 1
350 }
351 wlp(logfd, " (count=\x00" as *u8); wln(logfd, n_dead); wlp(logfd, ")\n\x00" as *u8)
352
353 // ---- 3. reciprocal vs one-way pairs ----
354 // For each unordered pair (a<b): both = adj[a,b] && adj[b,a]; oneway = exactly one.
355 wlp(logfd, "--- 3. PAIR DIRECTIONALITY (unordered page pairs) ---\n\x00" as *u8)
356 var recip: i64 = 0
357 var oneway: i64 = 0
358 var a: i64 = 0
359 while a < n {
360 var b: i64 = a + 1
361 while b < n {
362 let ab: i64 = adj[a * n + b]
363 let ba: i64 = adj[b * n + a]
364 if ab == 1 {
365 if ba == 1 { recip = recip + 1 }
366 }
367 if ab + ba == 1 { oneway = oneway + 1 }
368 b = b + 1
369 }
370 a = a + 1
371 }
372 wlp(logfd, " RECIPROCAL pairs (A<->B both ways)=\x00" as *u8); wln(logfd, recip); wlp(logfd, "\n\x00" as *u8)
373 wlp(logfd, " ONE-WAY pairs (A->B only)=\x00" as *u8); wln(logfd, oneway); wlp(logfd, "\n\x00" as *u8)
374 // enumerate the reciprocal pairs explicitly (small N)
375 wlp(logfd, " reciprocal detail:\x00" as *u8)
376 a = 0
377 while a < n {
378 var b2: i64 = a + 1
379 while b2 < n {
380 if adj[a * n + b2] == 1 {
381 if adj[b2 * n + a] == 1 {
382 nx_wiki_doc_store_lookup(store, a, tp, tn, up, un, bp, bn)
383 wlp(logfd, " \x00" as *u8); wlb(logfd, up[0] as *u8, un[0])
384 wlp(logfd, "<->\x00" as *u8)
385 nx_wiki_doc_store_lookup(store, b2, tp, tn, up, un, bp, bn)
386 wlb(logfd, up[0] as *u8, un[0])
387 }
388 }
389 b2 = b2 + 1
390 }
391 a = a + 1
392 }
393 wlp(logfd, "\n\x00" as *u8)
394
395 // ---- 4. reachability from start (BFS) ----
396 // dist[] = clicks-to-reach from root; -1 = unvisited. Iterative relaxation
397 // over the dense matrix, capped at N rounds (longest simple path <= N-1).
398 wlp(logfd, "--- 4. REACHABILITY FROM start (BFS over the live graph) ---\n\x00" as *u8)
399 let dist: *i64 = sys_mmap(WALK_MAX_PAGES * 8) as *i64
400 var zz: i64 = 0
401 while zz < n { dist[zz] = 0 - 1; zz = zz + 1 }
402 var reachable: i64 = 0
403 var maxdepth: i64 = 0
404 if root >= 0 {
405 dist[root] = 0
406 reachable = 1
407 // relax up to N rounds: round r settles all nodes at distance r+1
408 var round: i64 = 0
409 while round < n {
410 var changed: i64 = 0
411 var s2: i64 = 0
412 while s2 < n {
413 if dist[s2] >= 0 {
414 var d2: i64 = 0
415 while d2 < n {
416 if adj[s2 * n + d2] == 1 {
417 if dist[d2] < 0 {
418 dist[d2] = dist[s2] + 1
419 if dist[d2] > maxdepth { maxdepth = dist[d2] }
420 reachable = reachable + 1
421 changed = 1
422 }
423 }
424 d2 = d2 + 1
425 }
426 }
427 s2 = s2 + 1
428 }
429 if changed == 0 { round = n } // fixpoint -> stop
430 round = round + 1
431 }
432 }
433 wlp(logfd, " reachable from start (incl start)=\x00" as *u8); wln(logfd, reachable)
434 wlp(logfd, " of \x00" as *u8); wln(logfd, n); wlp(logfd, "\n\x00" as *u8)
435 wlp(logfd, " max clicks-to-reach (graph depth)=\x00" as *u8); wln(logfd, maxdepth); wlp(logfd, "\n\x00" as *u8)
436 // per-page depth
437 wlp(logfd, " depth-from-start:\x00" as *u8)
438 var ri: i64 = 0
439 while ri < n {
440 nx_wiki_doc_store_lookup(store, ri, tp, tn, up, un, bp, bn)
441 wlp(logfd, " \x00" as *u8); wlb(logfd, up[0] as *u8, un[0]); wlp(logfd, "=\x00" as *u8); wln(logfd, dist[ri])
442 ri = ri + 1
443 }
444 wlp(logfd, "\n\x00" as *u8)
445 // stranded = unreachable
446 var stranded: i64 = 0
447 wlp(logfd, " STRANDED (NOT reachable from start):\x00" as *u8)
448 var si: i64 = 0
449 while si < n {
450 if dist[si] < 0 {
451 nx_wiki_doc_store_lookup(store, si, tp, tn, up, un, bp, bn)
452 wlp(logfd, " \x00" as *u8); wlb(logfd, up[0] as *u8, un[0])
453 stranded = stranded + 1
454 }
455 si = si + 1
456 }
457 wlp(logfd, " (count=\x00" as *u8); wln(logfd, stranded); wlp(logfd, ")\n\x00" as *u8)
458
459 wlp(logfd, "=== SCORECARD DONE (read-only; corpus untouched) ===\n\x00" as *u8)
460 sys_exit(0)
461 return 0
462}