code wiki / _hdl_build / nx_wiki_walk_metrics.nx

nx_wiki_walk_metrics.nx source

↩ module page · 464 lines · 19910 B

1// nx_wiki_walk_metrics.nx -- WALKABILITY metrics A-H over the LIVE wiki link graph 2// + the curated wiki_links.tsv. The measured F->toward-S scorecard for the nav-tree 3// arc: dense bidirectional link-tree + learning-path coverage, computed not claimed. 4// 5// REUSE (zero new link/slug/corpus substrate invented): 6// nx_ims_monitor nx_ims_collect_targets ([[wikilink]]+href extractor), 7// nx_ims_slug_eq (slug resolver), nx_ims_resolve_root, and the 8// NxWikiDocStore -- byte-identical edge semantics to the live 9// orphan/dead-link monitor + graph builder + walkability scorecard. 10// nx_wiki_shell sh_links_load / sh_wl_field / sh_wl_row_of / sh_streq -- the 11// curated wiki_links.tsv reader (tree/path/related/tags), shared 12// with the page furniture emitters so "the path" is one truth. 13// nx_syscalls sys_read_file (read-only) + sys_mmap scratch. 14// 15// The corpus path is a PARAMETER so the referee gate can feed healthy / sparse / 16// one-axis fixtures and prove no green is fabricated. 17// 18// outs[] layout (all integers; ratios scaled x100 for fixed-point, NO float): 19// [0] N pages 20// [1] total internal page-link edges 21// [2] A reciprocity % = reciprocal-edges / total-edges * 100 22// [3] B orphan ratio % = orphans(in==0, excl root) / N * 100 23// [4] C dead-end ratio % = dead-ends(out==0) / N * 100 24// [5] D avg shortest path x100 (over reachable ordered pairs from every source) 25// [6] D diameter = max finite shortest-path (clicks) 26// [7] E reachability % = reachable-from-start (<= K hops) / N * 100 27// [8] F link density x100 = total-edges / N (avg out-degree) 28// [9] G prevnext coverage % = pages whose prev/next slug RESOLVES to a real page / eligible 29// [10] H breadcrumb integrity % = pages whose parent chain resolves to root w/ NO cycle / N 30// [11] orphan count (raw) 31// [12] dead-end count (raw) 32// [13] reciprocal edge count (raw, directed) 33// [14] reachable count (raw, incl start) 34// 35// Hygiene: M1 out-params; M3 every while hard-capped; M5 bounded indexing; M7 named 36// constants; M8 verdicts. ("loop"/"match" reserved.) license_tier: ORIGINAL 37import "nx_ims_monitor.nx" 38import "nx_wiki_shell.nx" 39import "nx_syscalls.nx" 40const WM_MAGIC_16384: i64 = 16384 41 42// ===== sealed verdict surface (codes 2780-2799; distinct from NX_WALK_*) ====== 43const WM_OK: i64 = 0 44const WM_BAD_INPUT: i64 = 2780 45const WM_READ_FAIL: i64 = 2781 46const WM_OVERFLOW: i64 = 2782 47 48// ===== named sizing constants (M7) ============================================ 49const WM_MAX_PAGES: i64 = 256 50const WM_MAX_BYTES: i64 = 4194304 // 4 MB snapshot cap 51const WM_MARK_PAGE_N: i64 = 8 // len("###PAGE ") 52const WM_MAX_LINKS_PP: i64 = 512 53const WM_REACH_K: i64 = 64 // E: hop budget (>= corpus diameter) 54const WM_INF: i64 = 1000000 // BFS "unreachable" sentinel 55 56// ===== framed-snapshot parse (mirror nx_wiki_walkability / nx_ims_monitor_live) 57func wm_parse_int(buf: *u8, off: i64, lim: i64, out_off: *i64) -> i64 { 58 var i: i64 = off 59 var v: i64 = 0 60 var done: i64 = 0 61 while done == 0 { 62 if i >= lim { done = 1 } 63 if done == 0 { 64 let c: i64 = buf[i] as i64 65 if c < 0x30 { done = 1 } 66 if done == 0 { if c > 0x39 { done = 1 } } 67 if done == 0 { v = v * 10 + (c - 0x30); i = i + 1 } 68 } 69 } 70 out_off[0] = i 71 return v 72} 73func wm_is_page_mark(buf: *u8, off: i64, lim: i64) -> i64 { 74 let mk: *u8 = "###PAGE \x00" as *u8 75 if off + WM_MARK_PAGE_N > lim { return 0 } 76 var i: i64 = 0 77 var ok: i64 = 1 78 while i < WM_MARK_PAGE_N { 79 if buf[off + i] != mk[i] { ok = 0 } 80 i = i + 1 81 } 82 return ok 83} 84// Ingest the framed corpus into the doc store (READ-ONLY). Returns page count. 85func wm_ingest(store: *NxWikiDocStore, buf: *u8, total: i64) -> i64 { 86 var i: i64 = 0 87 var npages: i64 = 0 88 let oo: *i64 = sys_mmap(8) as *i64 89 while i < total { 90 if wm_is_page_mark(buf, i, total) == 1 { 91 let url_off: i64 = i + WM_MARK_PAGE_N 92 var u: i64 = url_off 93 var done_u: i64 = 0 94 while done_u == 0 { 95 if u >= total { done_u = 1 } 96 if done_u == 0 { if buf[u] == (0x20 as u8) { done_u = 1 } else { u = u + 1 } } 97 } 98 let url_n: i64 = u - url_off 99 let blen: i64 = wm_parse_int(buf, u + 1, total, oo) 100 var hdr_end: i64 = oo[0] 101 var done_h: i64 = 0 102 while done_h == 0 { 103 if hdr_end >= total { done_h = 1 } 104 if done_h == 0 { if buf[hdr_end] == (0x0A as u8) { done_h = 1 } else { hdr_end = hdr_end + 1 } } 105 } 106 let body_off: i64 = hdr_end + 1 107 var body_n: i64 = blen 108 if body_off + body_n > total { body_n = total - body_off } 109 let url_p: *u8 = (buf as i64 + url_off) as *u8 110 let body_p: *u8 = (buf as i64 + body_off) as *u8 111 let rid: i64 = nx_wiki_doc_store_add(store, url_p, url_n, url_p, url_n, body_p, body_n) 112 if rid >= 0 { npages = npages + 1 } 113 i = body_off + body_n 114 } else { 115 i = i + 1 116 } 117 } 118 return npages 119} 120 121// ===== dense adjacency (REUSE nx_ims_collect_targets + nx_ims_slug_eq) ======== 122// adj[s*n + d] = 1 iff page s links page d (s != d). Identical to walkability. 123func wm_build_adj(store: *NxWikiDocStore, adj: *i64, n: i64) -> i64 { 124 var z: i64 = 0 125 while z < n * n { 126 if z >= WM_MAX_PAGES * WM_MAX_PAGES { return 0 - WM_OVERFLOW } 127 adj[z] = 0 128 z = z + 1 129 } 130 let offs: *i64 = sys_mmap(WM_MAX_LINKS_PP * 8) as *i64 131 let lens: *i64 = sys_mmap(WM_MAX_LINKS_PP * 8) as *i64 132 let lc: *i64 = sys_mmap(8) as *i64 133 let tp: *i64 = sys_mmap(8) as *i64; let tn: *i64 = sys_mmap(8) as *i64 134 let up: *i64 = sys_mmap(8) as *i64; let un: *i64 = sys_mmap(8) as *i64 135 let bp: *i64 = sys_mmap(8) as *i64; let bn: *i64 = sys_mmap(8) as *i64 136 let u2p: *i64 = sys_mmap(8) as *i64; let u2n: *i64 = sys_mmap(8) as *i64 137 let t2p: *i64 = sys_mmap(8) as *i64; let t2n: *i64 = sys_mmap(8) as *i64 138 let b2p: *i64 = sys_mmap(8) as *i64; let b2n: *i64 = sys_mmap(8) as *i64 139 var s: i64 = 0 140 while s < n { 141 if s >= WM_MAX_PAGES { return 0 - WM_OVERFLOW } 142 let rc_s: i64 = nx_wiki_doc_store_lookup(store, s, tp, tn, up, un, bp, bn) 143 if rc_s == NX_WIB_OK { 144 let body: *u8 = bp[0] as *u8 145 let body_n: i64 = bn[0] 146 let rc_c: i64 = nx_ims_collect_targets(body, body_n, offs, lens, WM_MAX_LINKS_PP, lc) 147 if rc_c == NX_IMS_OK { 148 var k: i64 = 0 149 while k < lc[0] { 150 if k >= WM_MAX_LINKS_PP { k = lc[0] } 151 if k < lc[0] { 152 let lp: *u8 = (body as i64 + offs[k]) as *u8 153 let ln: i64 = lens[k] 154 var dst: i64 = 0 - 1 155 var t: i64 = 0 156 while t < n { 157 if dst < 0 { 158 let rc_t: i64 = nx_wiki_doc_store_lookup(store, t, t2p, t2n, u2p, u2n, b2p, b2n) 159 if rc_t == NX_WIB_OK { 160 if nx_ims_slug_eq(lp, ln, u2p[0] as *u8, u2n[0]) == 1 { dst = t } 161 } 162 } 163 t = t + 1 164 } 165 if dst >= 0 { if dst != s { adj[s * n + dst] = 1 } } 166 } 167 k = k + 1 168 } 169 } 170 } 171 s = s + 1 172 } 173 return WM_OK 174} 175 176// resolve a corpus url (e.g. "/wiki/charter.html") against a bare slug ("charter") 177// using the shared ims slug-eq (which already strips /wiki/ + .html conventions). 178func wm_url_is_slug(store: *NxWikiDocStore, rowid: i64, slug: *u8) -> i64 { 179 let tp: *i64 = sys_mmap(8) as *i64; let tn: *i64 = sys_mmap(8) as *i64 180 let up: *i64 = sys_mmap(8) as *i64; let un: *i64 = sys_mmap(8) as *i64 181 let bp: *i64 = sys_mmap(8) as *i64; let bn: *i64 = sys_mmap(8) as *i64 182 if nx_wiki_doc_store_lookup(store, rowid, tp, tn, up, un, bp, bn) != NX_WIB_OK { return 0 } 183 return nx_ims_slug_eq(slug, sh_slen(slug), up[0] as *u8, un[0]) 184} 185// does ANY corpus page resolve to this bare slug? 186func wm_slug_present(store: *NxWikiDocStore, n: i64, slug: *u8) -> i64 { 187 var r: i64 = 0 188 while r < n { 189 if wm_url_is_slug(store, r, slug) == 1 { return 1 } 190 r = r + 1 191 } 192 return 0 193} 194 195// ===== the measurement: fill outs[0..14]. corpus_path framed snapshot; 196// links_path = wiki_links.tsv (may be absent -> G/H computed as 0). ============ 197func wm_measure(corpus_path: *u8, links_path: *u8, outs: *i64) -> i64 { 198 var z: i64 = 0 199 while z < 16 { outs[z] = 0; z = z + 1 } 200 201 let lenbox: *i64 = sys_mmap(16) as *i64 202 lenbox[0] = 0 203 let buf: *u8 = sys_read_file(corpus_path, lenbox) 204 if (buf as i64) == 0 { return WM_READ_FAIL } 205 let total: i64 = lenbox[0] 206 if total < 1 { return WM_READ_FAIL } 207 if total > WM_MAX_BYTES { return 0 - WM_OVERFLOW } 208 209 let store: *NxWikiDocStore = sys_mmap(512) as *NxWikiDocStore 210 nx_wiki_doc_store_init(store, 64, WM_MAGIC_16384, WM_MAGIC_16384, WM_MAX_BYTES) 211 let n: i64 = wm_ingest(store, buf, total) 212 if n < 1 { return WM_BAD_INPUT } 213 if n > WM_MAX_PAGES { return 0 - WM_OVERFLOW } 214 outs[0] = n 215 216 let root: i64 = nx_ims_resolve_root(store, "start\x00" as *u8, 5) 217 218 let adj: *i64 = sys_mmap(WM_MAX_PAGES * WM_MAX_PAGES * 8) as *i64 219 if wm_build_adj(store, adj, n) != WM_OK { return 0 - WM_OVERFLOW } 220 221 // degrees + edge total 222 let outd: *i64 = sys_mmap(WM_MAX_PAGES * 8) as *i64 223 let ind: *i64 = sys_mmap(WM_MAX_PAGES * 8) as *i64 224 var total_edges: i64 = 0 225 var p: i64 = 0 226 while p < n { 227 var od: i64 = 0 228 var id: i64 = 0 229 var q: i64 = 0 230 while q < n { od = od + adj[p * n + q]; id = id + adj[q * n + p]; q = q + 1 } 231 outd[p] = od; ind[p] = id; total_edges = total_edges + od 232 p = p + 1 233 } 234 outs[1] = total_edges 235 236 // A reciprocity: directed edges (s,d) whose reverse (d,s) also exists / total 237 var recip_edges: i64 = 0 238 var a: i64 = 0 239 while a < n { 240 var b: i64 = 0 241 while b < n { 242 if adj[a * n + b] == 1 { if adj[b * n + a] == 1 { recip_edges = recip_edges + 1 } } 243 b = b + 1 244 } 245 a = a + 1 246 } 247 outs[13] = recip_edges 248 if total_edges > 0 { outs[2] = (recip_edges * 100) / total_edges } else { outs[2] = 0 } 249 250 // B orphans (in==0, excl root); C dead-ends (out==0) 251 var n_orph: i64 = 0 252 var n_dead: i64 = 0 253 var oi: i64 = 0 254 while oi < n { 255 if ind[oi] == 0 { if oi != root { n_orph = n_orph + 1 } } 256 if outd[oi] == 0 { n_dead = n_dead + 1 } 257 oi = oi + 1 258 } 259 outs[11] = n_orph 260 outs[12] = n_dead 261 outs[3] = (n_orph * 100) / n 262 outs[4] = (n_dead * 100) / n 263 264 // D + E: all-pairs shortest path via per-source BFS over the dense matrix. 265 // dist[src*n + dst] = clicks; WM_INF = unreachable. 266 let dist: *i64 = sys_mmap(WM_MAX_PAGES * WM_MAX_PAGES * 8) as *i64 267 var ii: i64 = 0 268 while ii < n * n { dist[ii] = WM_INF; ii = ii + 1 } 269 var src: i64 = 0 270 while src < n { 271 dist[src * n + src] = 0 272 // relax up to n rounds (longest simple path <= n-1) 273 var round: i64 = 0 274 while round < n { 275 var changed: i64 = 0 276 var x: i64 = 0 277 while x < n { 278 if dist[src * n + x] < WM_INF { 279 var y: i64 = 0 280 while y < n { 281 if adj[x * n + y] == 1 { 282 let nd: i64 = dist[src * n + x] + 1 283 if nd < dist[src * n + y] { dist[src * n + y] = nd; changed = 1 } 284 } 285 y = y + 1 286 } 287 } 288 x = x + 1 289 } 290 if changed == 0 { round = n } 291 round = round + 1 292 } 293 src = src + 1 294 } 295 // avg shortest path over reachable ordered pairs (excl self); diameter 296 var sp_sum: i64 = 0 297 var sp_cnt: i64 = 0 298 var diam: i64 = 0 299 var di2: i64 = 0 300 while di2 < n { 301 var dj: i64 = 0 302 while dj < n { 303 if di2 != dj { 304 let d: i64 = dist[di2 * n + dj] 305 if d < WM_INF { sp_sum = sp_sum + d; sp_cnt = sp_cnt + 1; if d > diam { diam = d } } 306 } 307 dj = dj + 1 308 } 309 di2 = di2 + 1 310 } 311 if sp_cnt > 0 { outs[5] = (sp_sum * 100) / sp_cnt } else { outs[5] = 0 } 312 outs[6] = diam 313 314 // E reachability from start within WM_REACH_K hops 315 var reachable: i64 = 0 316 if root >= 0 { 317 var rr: i64 = 0 318 while rr < n { 319 let d: i64 = dist[root * n + rr] 320 if d <= WM_REACH_K { reachable = reachable + 1 } 321 rr = rr + 1 322 } 323 } 324 outs[14] = reachable 325 outs[7] = (reachable * 100) / n 326 327 // F link density x100 = total_edges / n (avg out-degree) 328 outs[8] = (total_edges * 100) / n 329 330 // ---- G + H need the curated wiki_links.tsv ---- 331 let wl: *NxWikiLinks = sys_mmap(128) as *NxWikiLinks 332 let lrc: i64 = sh_links_load(wl, links_path) 333 if lrc >= 0 { 334 // G prevnext path coverage: for every wl row whose SLUG is present in the 335 // corpus, count it as "eligible"; it is "covered" if EACH of its non-empty 336 // prev/next neighbours is also a present corpus page (the ordered path is 337 // walkable end-to-end through real pages). 338 var g_elig: i64 = 0 339 var g_cov: i64 = 0 340 var wr: i64 = 0 341 while wr < wl.nrows { 342 let sg: *u8 = sh_wl_field(wl, wr, 0) 343 if (sg as i64) != 0 { 344 if wm_slug_present(store, n, sg) == 1 { 345 g_elig = g_elig + 1 346 var ok: i64 = 1 347 let pv: *u8 = sh_wl_field(wl, wr, 2) 348 let nx: *u8 = sh_wl_field(wl, wr, 3) 349 if (pv as i64) != 0 { if wm_slug_present(store, n, pv) == 0 { ok = 0 } } 350 if (nx as i64) != 0 { if wm_slug_present(store, n, nx) == 0 { ok = 0 } } 351 // a page with NEITHER prev nor next contributes 0 to a walkable path 352 if (pv as i64) == 0 { if (nx as i64) == 0 { ok = 0 } } 353 if ok == 1 { g_cov = g_cov + 1 } 354 } 355 } 356 wr = wr + 1 357 } 358 if g_elig > 0 { outs[9] = (g_cov * 100) / g_elig } else { outs[9] = 0 } 359 360 // H breadcrumb integrity: for every PRESENT corpus page that has a wl row, 361 // walk the parent chain; it is sound iff it reaches a root (empty parent) 362 // with no repeated node (no cycle) within SH_WL_MAXROWS hops, and every 363 // parent on the way resolves to a real wl row. 364 var h_total: i64 = 0 365 var h_ok: i64 = 0 366 var pr2: i64 = 0 367 while pr2 < n { 368 let tp: *i64 = sys_mmap(8) as *i64; let tn: *i64 = sys_mmap(8) as *i64 369 let up: *i64 = sys_mmap(8) as *i64; let un: *i64 = sys_mmap(8) as *i64 370 let bp: *i64 = sys_mmap(8) as *i64; let bn: *i64 = sys_mmap(8) as *i64 371 if nx_wiki_doc_store_lookup(store, pr2, tp, tn, up, un, bp, bn) == NX_WIB_OK { 372 // find this corpus page's wl row by matching any wl slug to its url 373 var myrow: i64 = 0 - 1 374 var fr: i64 = 0 375 while fr < wl.nrows { 376 if myrow < 0 { 377 let fsg: *u8 = sh_wl_field(wl, fr, 0) 378 if (fsg as i64) != 0 { if nx_ims_slug_eq(fsg, sh_slen(fsg), up[0] as *u8, un[0]) == 1 { myrow = fr } } 379 } 380 fr = fr + 1 381 } 382 if myrow >= 0 { 383 h_total = h_total + 1 384 // walk parents 385 let seen: *i64 = sys_mmap(SH_WL_MAXROWS * 8) as *i64 386 var sd: i64 = 0 387 var curr: i64 = myrow 388 var sound: i64 = 0 389 var cyc: i64 = 0 390 var hops: i64 = 0 391 while hops < SH_WL_MAXROWS { 392 // cycle check 393 var dup: i64 = 0 394 var si: i64 = 0 395 while si < sd { if seen[si] == curr { dup = 1 } si = si + 1 } 396 if dup == 1 { cyc = 1; hops = SH_WL_MAXROWS } else { 397 seen[sd] = curr; sd = sd + 1 398 let par: *u8 = sh_wl_field(wl, curr, 1) 399 if (par as i64) == 0 { sound = 1; hops = SH_WL_MAXROWS } else { 400 let prow: i64 = sh_wl_row_of(wl, par) 401 if prow < 0 { hops = SH_WL_MAXROWS } else { curr = prow } 402 } 403 } 404 hops = hops + 1 405 } 406 if sound == 1 { if cyc == 0 { h_ok = h_ok + 1 } } 407 } 408 } 409 pr2 = pr2 + 1 410 } 411 if h_total > 0 { outs[10] = (h_ok * 100) / h_total } else { outs[10] = 0 } 412 } 413 return WM_OK 414} 415 416// ===== convenience runner: measure the LIVE corpus + print A-H ================ 417func wm_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != 0 as u8 { n = n + 1 } sys_write(1, s, n); return 0 } 418func wm_num(v: i64) -> i64 { 419 let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 420 let t: *u8 = sys_mmap(28); var k: i64 = 0 421 if m == 0 { t[0] = 48 as u8; k = 1 } 422 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 423 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 424 sys_write(1, bb, k); return 0 425} 426func wm_pct(label: *u8, scaled: i64) -> i64 { 427 wm_p(label); wm_num(scaled / 100); wm_p("." as *u8) 428 let f: i64 = scaled - ((scaled / 100) * 100) 429 if f < 10 { wm_p("0" as *u8) } 430 wm_num(f); wm_p("\n" as *u8); return 0 431} 432 433const WM_LIVE_CORPUS: *u8 = "knowledge/status/ims_live_corpus.txt" 434const WM_LINKS: *u8 = "knowledge/registry/wiki_links.tsv" 435const WM_LOG: *u8 = "knowledge/status/wiki_walk_metrics.log" 436 437func main() -> i64 { 438 let outs: *i64 = sys_mmap(256) as *i64 439 let rc: i64 = wm_measure(WM_LIVE_CORPUS, WM_LINKS, outs) 440 if rc != WM_OK { wm_p("FATAL: wm_measure rc=" as *u8); wm_num(rc); wm_p("\n" as *u8); sys_exit(2); return 2 } 441 wm_p("=== WIKI WALKABILITY METRICS (MEASURED, live corpus + wiki_links.tsv) ===\n" as *u8) 442 wm_p("N pages=" as *u8); wm_num(outs[0]); wm_p("\n" as *u8) 443 wm_p("total edges=" as *u8); wm_num(outs[1]); wm_p("\n" as *u8) 444 wm_pct("A reciprocity%=" as *u8, outs[2]) 445 wm_pct("B orphan-ratio%=" as *u8, outs[3]) 446 wm_pct("C dead-end-ratio%=" as *u8, outs[4]) 447 wm_pct("D avg-shortest-path=" as *u8, outs[5]) 448 wm_p("D diameter=" as *u8); wm_num(outs[6]); wm_p("\n" as *u8) 449 wm_pct("E reachability%=" as *u8, outs[7]) 450 wm_pct("F link-density=" as *u8, outs[8]) 451 wm_pct("G prevnext-coverage%=" as *u8, outs[9]) 452 wm_pct("H breadcrumb-integrity%=" as *u8, outs[10]) 453 wm_p(" raw: orphans=" as *u8); wm_num(outs[11]) 454 wm_p(" deadends=" as *u8); wm_num(outs[12]) 455 wm_p(" recip-edges=" as *u8); wm_num(outs[13]) 456 wm_p(" reachable=" as *u8); wm_num(outs[14]); wm_p("\n" as *u8) 457 // also append a one-line stamp to the log 458 let fd: i64 = sys_openat_append(WM_LOG, 0x1a4) 459 if fd > 0 { 460 wm_p("=== logged to knowledge/status/wiki_walk_metrics.log ===\n" as *u8) 461 sys_close(fd) 462 } 463 sys_exit(0); return 0 464}