code wiki / wiki / nx_wiki_backlinks.nx

nx_wiki_backlinks.nx source

↩ module page · 350 lines · 14942 B

1// nx_wiki_backlinks.nx -- wiki R4: "what links here" reverse-link index. 2// 3// COMPOSES (zero new storage/parse substrate introduced): 4// nx_wiki_index_builder NxWikiDocStore: doc_count + per-rowid 5// (title, url, body) lookup -- the page corpus. 6// nx_syscalls sys_mmap for the reverse-map scratch. 7// 8// WHAT IT DOES: 9// Every wiki page body carries forward [[wikilink]] cross-references 10// (resolved/rendered by nx_wiki_doc_render). This organ INVERTS them: 11// given a target slug, it enumerates the source pages whose bodies link 12// TO it. That is the classic wiki "What links here" affordance, computed 13// from ground truth (the actual bodies in the doc store), never a 14// hand-maintained list. 15// 16// IDENTITY MODEL: 17// A page's identity ("slug") is its store URL field as-stored. The wiki 18// convention is a bare name (e.g. "alpha") or "/wiki/alpha"; a [[alpha]] 19// wikilink targets that same slug. nx_wiki_backlink_slug_matches compares 20// a link target against a stored slug allowing the optional "/wiki/" 21// prefix on either side, so both conventions resolve. 22// 23// [[cite:...]] links are SKIPPED -- those are supporting-source citations 24// handled by nx_wiki_cite_render, not page-to-page cross-references. 25// 26// Hygiene (NISHI_CODE_HYGIENE_STANDARD): M1 out-params not *T returns; 27// M3 every while has a hard iter cap; M5 every buffer index bounded; 28// M6 no pretend stubs; M7 named sizing constants; M8 verdicts propagated. 29// 30// Status: V1 (wiki R4). 2026-06-15. license_tier: ORIGINAL 31import "nx_syscalls.nx" 32import "nx_wiki_index_builder.nx" 33 34// ===== Sealed verdict surface (codes 2640-2659) ============================== 35const NX_WBL_OK: i64 = 0 36const NX_WBL_BAD_INPUT: i64 = 2640 37const NX_WBL_OVERFLOW: i64 = 2641 38const NX_WBL_LOOP_BUDGET: i64 = 2642 39 40// ===== Named sizing constants (M7) =========================================== 41const NX_WBL_MAX_LINKS_PER_PAGE: i64 = 512 // forward links scanned per body 42const NX_WBL_MAX_LINK_LEN: i64 = 256 // a single [[target]] name cap 43const NX_WBL_MAX_PAGES: i64 = 1000 // mirrors doc-store docs cap 44const NX_WBL_SCAN_BUDGET: i64 = 8000000 // per-body byte-scan cap (M3) 45const NX_WBL_WIKI_PREFIX_LEN: i64 = 6 // len("/wiki/") 46 47// Common ASCII (M7) 48const NX_WBL_LBRACKET: i64 = 0x5B // '[' 49const NX_WBL_RBRACKET: i64 = 0x5D // ']' 50const NX_WBL_COLON: i64 = 0x3A // ':' 51 52// ===== small str helpers ===================================================== 53func nx_wbl_slen(s: *u8) -> i64 { 54 var n: i64 = 0 55 while n < NX_WBL_SCAN_BUDGET { 56 if s[n] == (0 as u8) { return n } 57 n = n + 1 58 } 59 return n 60} 61 62// Strip a leading "/wiki/" from (p,n); returns the inner pointer via out_ptr 63// and inner length as the function result. Defensive: if shorter than the 64// prefix or no match, returns the input unchanged. 65func nx_wbl_strip_wiki(p: *u8, n: i64, out_ptr: *i64) -> i64 { 66 out_ptr[0] = p as i64 67 if n < NX_WBL_WIKI_PREFIX_LEN { return n } 68 if p[0] != (0x2F as u8) { return n } // '/' 69 if p[1] != (0x77 as u8) { return n } // 'w' 70 if p[2] != (0x69 as u8) { return n } // 'i' 71 if p[3] != (0x6B as u8) { return n } // 'k' 72 if p[4] != (0x69 as u8) { return n } // 'i' 73 if p[5] != (0x2F as u8) { return n } // '/' 74 out_ptr[0] = (p as i64) + NX_WBL_WIKI_PREFIX_LEN 75 return n - NX_WBL_WIKI_PREFIX_LEN 76} 77 78// Does a [[wikilink]] target (lp,ln) name the same page as stored slug (sp,sn)? 79// Compares with the optional "/wiki/" prefix stripped from BOTH sides, so 80// "alpha", "/wiki/alpha" all match each other. Case-sensitive on the bare name 81// (wiki slugs are canonical lowercase by convention). 82func nx_wiki_backlink_slug_matches(lp: *u8, ln: i64, sp: *u8, sn: i64) -> i64 { 83 if ln < 1 { return 0 } 84 if sn < 1 { return 0 } 85 let lib: *i64 = sys_mmap(8) as *i64 86 let sib: *i64 = sys_mmap(8) as *i64 87 let lin: i64 = nx_wbl_strip_wiki(lp, ln, lib) 88 let sin: i64 = nx_wbl_strip_wiki(sp, sn, sib) 89 if lin != sin { return 0 } 90 if lin < 1 { return 0 } 91 let la: *u8 = lib[0] as *u8 92 let sa: *u8 = sib[0] as *u8 93 var i: i64 = 0 94 var eq: i64 = 1 95 while i < lin { 96 if la[i] != sa[i] { eq = 0 } 97 i = i + 1 98 } 99 return eq 100} 101 102// ===== Forward-link extractor (the [[wikilink]] scanner, R4-reusable) ======== 103// 104// Walks body (p,n); for each [[name]] (NOT [[cite:...]]) records the name's 105// (offset,len) into the caller's parallel arrays offs[]/lens[] (capacity cap). 106// Returns the count found (>=0) or -verdict. This is the single forward-link 107// truth source the backlinks inversion, the TOC's link audit and the graph 108// edge builder all share -- no three divergent scanners. 109// 110// A name is "cite" iff it begins with "cite:". Those are skipped (not counted). 111func nx_wiki_forward_links(body: *u8, n: i64, 112 offs: *i64, lens: *i64, cap: i64, 113 out_count: *i64) -> i64 { 114 if (out_count as i64) == 0 { return 0 - NX_WBL_BAD_INPUT } 115 out_count[0] = 0 116 if (body as i64) == 0 { return NX_WBL_OK } 117 if n < 0 { return 0 - NX_WBL_BAD_INPUT } 118 var i: i64 = 0 119 var cnt: i64 = 0 120 var iter: i64 = 0 121 while i < n { 122 if iter >= NX_WBL_SCAN_BUDGET { return 0 - NX_WBL_LOOP_BUDGET } 123 iter = iter + 1 124 var advanced: i64 = 0 125 // Look for "[[" at i. 126 if i + 1 < n { 127 if body[i] == (NX_WBL_LBRACKET as u8) { 128 if body[i + 1] == (NX_WBL_LBRACKET as u8) { 129 let start_name: i64 = i + 2 130 // scan for the FIRST "]]" at/after start_name, within the 131 // per-link length cap. cur walks; on a "]]" hit we record 132 // end_name and stop via done. A clean single-exit loop -- 133 // no cap-guard clobbering the frozen end position. 134 var cur: i64 = start_name 135 var end_name: i64 = start_name 136 var found: i64 = 0 137 var done: i64 = 0 138 while done == 0 { 139 if cur >= n - 1 { done = 1 } 140 if cur - start_name >= NX_WBL_MAX_LINK_LEN { done = 1 } 141 if done == 0 { 142 if body[cur] == (NX_WBL_RBRACKET as u8) { 143 if body[cur + 1] == (NX_WBL_RBRACKET as u8) { 144 end_name = cur 145 found = 1 146 done = 1 147 } 148 } 149 if found == 0 { cur = cur + 1 } 150 } 151 } 152 if found == 1 { 153 let name_off: i64 = start_name 154 let name_n: i64 = end_name - start_name 155 // is it a [[cite:...]] ? (skip) 156 var is_cite: i64 = 0 157 if name_n >= 5 { 158 if body[name_off] == (0x63 as u8) { // 'c' 159 if body[name_off + 1] == (0x69 as u8) { // 'i' 160 if body[name_off + 2] == (0x74 as u8) { // 't' 161 if body[name_off + 3] == (0x65 as u8) { // 'e' 162 if body[name_off + 4] == (NX_WBL_COLON as u8) { is_cite = 1 } 163 } 164 } 165 } 166 } 167 } 168 if name_n > 0 { 169 if name_n <= NX_WBL_MAX_LINK_LEN { 170 if is_cite == 0 { 171 if cnt >= cap { return 0 - NX_WBL_OVERFLOW } 172 if cnt >= NX_WBL_MAX_LINKS_PER_PAGE { return 0 - NX_WBL_OVERFLOW } 173 offs[cnt] = name_off 174 lens[cnt] = name_n 175 cnt = cnt + 1 176 } 177 } 178 } 179 i = end_name + 2 180 advanced = 1 181 } 182 } 183 } 184 } 185 if advanced == 0 { i = i + 1 } 186 } 187 out_count[0] = cnt 188 return NX_WBL_OK 189} 190 191// ===== Reverse map: backlinks_of(store, slug) -> source rowids =============== 192// 193// For target slug (slug,slug_n): scan every page in the store; if a page's 194// body contains a forward [[wikilink]] resolving to this slug, record that 195// page's rowid into out_rowids[] (capacity cap). A page is recorded AT MOST 196// once even if it links the target several times. Returns the count of 197// distinct source pages (>=0) or -verdict. 198func nx_wiki_backlinks_of(store: *NxWikiDocStore, 199 slug: *u8, slug_n: i64, 200 out_rowids: *i64, cap: i64, 201 out_count: *i64) -> i64 { 202 if (out_count as i64) == 0 { return 0 - NX_WBL_BAD_INPUT } 203 out_count[0] = 0 204 if (store as i64) == 0 { return 0 - NX_WBL_BAD_INPUT } 205 if store.valid != 1 { return 0 - NX_WBL_BAD_INPUT } 206 if slug_n < 1 { return 0 - NX_WBL_BAD_INPUT } 207 208 let dc: i64 = nx_wiki_doc_store_count(store) 209 // per-page forward-link scratch (offsets+lens) 210 let foffs: *i64 = sys_mmap(NX_WBL_MAX_LINKS_PER_PAGE * 8) as *i64 211 let flens: *i64 = sys_mmap(NX_WBL_MAX_LINKS_PER_PAGE * 8) as *i64 212 let lc: *i64 = sys_mmap(8) as *i64 213 // lookup out-params 214 let tp: *i64 = sys_mmap(8) as *i64 215 let tn: *i64 = sys_mmap(8) as *i64 216 let up: *i64 = sys_mmap(8) as *i64 217 let un: *i64 = sys_mmap(8) as *i64 218 let bp: *i64 = sys_mmap(8) as *i64 219 let bn: *i64 = sys_mmap(8) as *i64 220 221 var cnt: i64 = 0 222 var r: i64 = 0 223 while r < dc { 224 if r >= NX_WBL_MAX_PAGES { return 0 - NX_WBL_LOOP_BUDGET } 225 let rc_lk: i64 = nx_wiki_doc_store_lookup(store, r, tp, tn, up, un, bp, bn) 226 if rc_lk == NX_WIB_OK { 227 let body: *u8 = bp[0] as *u8 228 let body_n: i64 = bn[0] 229 let rc_fl: i64 = nx_wiki_forward_links(body, body_n, foffs, flens, NX_WBL_MAX_LINKS_PER_PAGE, lc) 230 if rc_fl == NX_WBL_OK { 231 var k: i64 = 0 232 var hit: i64 = 0 233 while k < lc[0] { 234 if hit == 0 { 235 let lp: *u8 = (body as i64 + foffs[k]) as *u8 236 if nx_wiki_backlink_slug_matches(lp, flens[k], slug, slug_n) == 1 { hit = 1 } 237 } 238 k = k + 1 239 } 240 if hit == 1 { 241 if cnt >= cap { return 0 - NX_WBL_OVERFLOW } 242 out_rowids[cnt] = r 243 cnt = cnt + 1 244 } 245 } 246 } 247 r = r + 1 248 } 249 out_count[0] = cnt 250 return NX_WBL_OK 251} 252 253// ===== "What links here" panel render ======================================== 254// 255// Emits an <aside class="nx-backlinks"> listing every source page that links 256// to target slug, each as <a href="/wiki/<src-slug>"><src-title></a>. Empty 257// state when none. Writes NUL-terminated HTML into out (cap); returns bytes 258// written (excluding the NUL, >=0) or -verdict. 259// 260// Title/slug text is copied verbatim from the store (already-trusted internal 261// content per Cardinal 12); the served pipeline html-escapes upstream. 262 263func nx_wbl_emit(out: *u8, off: i64, cap: i64, s: *u8) -> i64 { 264 var i: i64 = 0 265 while s[i] != (0 as u8) { 266 if off + i >= cap { return 0 - NX_WBL_OVERFLOW } 267 out[off + i] = s[i] 268 i = i + 1 269 } 270 return off + i 271} 272func nx_wbl_emit_bytes(out: *u8, off: i64, cap: i64, src: *u8, n: i64) -> i64 { 273 var i: i64 = 0 274 while i < n { 275 if i >= NX_WBL_MAX_LINK_LEN { return 0 - NX_WBL_OVERFLOW } 276 if off + i >= cap { return 0 - NX_WBL_OVERFLOW } 277 out[off + i] = src[i] 278 i = i + 1 279 } 280 return off + i 281} 282 283func nx_wiki_backlinks_panel(store: *NxWikiDocStore, 284 slug: *u8, slug_n: i64, 285 out: *u8, cap: i64, 286 out_used: *i64) -> i64 { 287 if (out_used as i64) == 0 { return 0 - NX_WBL_BAD_INPUT } 288 out_used[0] = 0 289 if (out as i64) == 0 { return 0 - NX_WBL_BAD_INPUT } 290 if cap < 64 { return 0 - NX_WBL_BAD_INPUT } 291 if (store as i64) == 0 { return 0 - NX_WBL_BAD_INPUT } 292 if store.valid != 1 { return 0 - NX_WBL_BAD_INPUT } 293 294 let rowids: *i64 = sys_mmap(NX_WBL_MAX_PAGES * 8) as *i64 295 let bc: *i64 = sys_mmap(8) as *i64 296 let rc_b: i64 = nx_wiki_backlinks_of(store, slug, slug_n, rowids, NX_WBL_MAX_PAGES, bc) 297 if rc_b != NX_WBL_OK { return rc_b } 298 299 var o: i64 = 0 300 o = nx_wbl_emit(out, o, cap, "<aside class=\"nx-backlinks\"><h2>What links here</h2>" as *u8) 301 if o < 0 { return o } 302 303 if bc[0] == 0 { 304 o = nx_wbl_emit(out, o, cap, "<p class=\"nx-backlinks-empty\">No other pages link here yet.</p></aside>" as *u8) 305 if o < 0 { return o } 306 out[o] = 0 as u8 307 out_used[0] = o 308 return NX_WBL_OK 309 } 310 311 o = nx_wbl_emit(out, o, cap, "<ul>" as *u8) 312 if o < 0 { return o } 313 314 // lookup out-params for each source page's title + url(slug) 315 let tp: *i64 = sys_mmap(8) as *i64 316 let tn: *i64 = sys_mmap(8) as *i64 317 let up: *i64 = sys_mmap(8) as *i64 318 let un: *i64 = sys_mmap(8) as *i64 319 let bp: *i64 = sys_mmap(8) as *i64 320 let bn: *i64 = sys_mmap(8) as *i64 321 322 var i: i64 = 0 323 while i < bc[0] { 324 if i >= NX_WBL_MAX_PAGES { return 0 - NX_WBL_LOOP_BUDGET } 325 let rid: i64 = rowids[i] 326 let rc_lk: i64 = nx_wiki_doc_store_lookup(store, rid, tp, tn, up, un, bp, bn) 327 if rc_lk == NX_WIB_OK { 328 o = nx_wbl_emit(out, o, cap, "<li><a href=\"/wiki/" as *u8) 329 if o < 0 { return o } 330 // href slug = stored url with any leading "/wiki/" stripped (so the 331 // emitted href is canonical "/wiki/<slug>" not "/wiki//wiki/<slug>"). 332 let sib: *i64 = sys_mmap(8) as *i64 333 let sin: i64 = nx_wbl_strip_wiki(up[0] as *u8, un[0], sib) 334 o = nx_wbl_emit_bytes(out, o, cap, sib[0] as *u8, sin) 335 if o < 0 { return o } 336 o = nx_wbl_emit(out, o, cap, "\">" as *u8) 337 if o < 0 { return o } 338 o = nx_wbl_emit_bytes(out, o, cap, tp[0] as *u8, tn[0]) 339 if o < 0 { return o } 340 o = nx_wbl_emit(out, o, cap, "</a></li>" as *u8) 341 if o < 0 { return o } 342 } 343 i = i + 1 344 } 345 o = nx_wbl_emit(out, o, cap, "</ul></aside>" as *u8) 346 if o < 0 { return o } 347 out[o] = 0 as u8 348 out_used[0] = o 349 return NX_WBL_OK 350}