code wiki / _hdl_build / nx_wiki_structure_regen.nx

nx_wiki_structure_regen.nx source

↩ module page · 613 lines · 31453 B

1// nx_wiki_structure_regen.nx -- IMS arc rung A4b: the RE-PUBLISH-ON-PULSE engine (full-auto cron driver). 2// 3// WHAT IT DOES (one pulse): reads the DATA-DRIVEN managed-page registry knowledge/registry/wiki_pages.tsv, 4// and for every managed page that HAS an emitter it (a) RE-EMITS the page (fork+exec the sovereign build- 5// runner on the emitter organ -> web_assets/<emit_file>), (b) builds the WOULD-BE corpus and runs the A1 6// orphan/dead-link MONITOR over it -- if re-emitting would leave any managed page with 0 inbound links 7// (orphan) or introduce a dead internal link, the offending page is marked REFUSE_ORPHAN and SKIPPED, (c) 8// runs the A2 publish GUARD (pg_decide) -- a REJECT skips the page with the exact reason, (d) if apply_flag 9// == 0 records a DRY-RUN plan row and PUSHES NOTHING; if apply_flag == 1 it APPLIES via the A4a versioned 10// publish (snapshot-then-guarded-push, multi-rollback-point). Pages with emitter "-" are reported manual- 11// only (counted, never silently dropped, never published). 12// 13// COMPOSITION (every safety property routes through a PROVEN organ -- nothing reinvented): 14// nx_ims_monitor nx_ims_orphans / nx_ims_dead_links / nx_ims_collect_targets (A1: the SOLE orphan 15// + link-rot authority; the regen NEVER rolls its own link scanner). 16// nx_wiki_publish_guard pg_decide (A2: the SOLE pre-publish integrity authority, fail-closed). 17// nx_wiki_versioned_publish vpub (A4a: snapshot-then-guarded-publish; the ONLY path that can write live, 18// reached ONLY when apply_flag==1; do_push_flag is wired to apply_flag so a 19// dry-run can NEVER push -- ADDITIVE-ONLY + multi-rollback-point by construction). 20// nx_wiki_index_builder NxWikiDocStore (the in-memory corpus the monitor reads). 21// nx_syscalls fork/exec/wait for the emitter; sys_read_file for bodies; fixed-epoch stamp. 22// 23// REFUSAL ATTRIBUTION (mechanical, so the gate can assert EXACT identities): 24// R-DEAD a page whose WOULD-BE body is the SOURCE of a dead internal link -> RC_DEAD_LINK (it introduced 25// the rot) -> would_publish=0. 26// R-ORPH a page whose re-emit DROPPED an outbound link (present in its baseline body, absent in its would- 27// be body) where the dropped target is now an ORPHAN in the would-be corpus -> RC_REFUSE_ORPHAN 28// (its change orphaned a managed page) -> would_publish=0. Also: an orphaned page itself is held 29// back (RC_REFUSE_ORPHAN) -- we never publish a page into a corpus where it is unreachable. 30// 31// NO WALL-CLOCK in the publish record: the caller passes a FIXED epoch (the versioned record is determin- 32// istic/reproducible). (The emitter's own footer uses sys_now_realtime_sec for the freshness STAMP -- that 33// is the page's "generated at" text, separate from the deterministic version-index epoch we control here.) 34// 35// Hygiene: M3 every while has a hard iter cap; M5 every buffer index bounded; M7 named constants; M8 36// verdicts propagated. ("loop" and "match" are reserved words -- never used as idents.) license_tier: ORIGINAL 37import "nx_syscalls.nx" 38import "nx_ims_monitor.nx" 39import "nx_wiki_publish_guard.nx" 40import "nx_wiki_versioned_publish.nx" 41import "nx_wiki_index_builder.nx" 42const REG_MAGIC_1048576: i64 = 1048576 43const REG_MAGIC_4096: i64 = 4096 44 45// ===== sealed surface ============================================================================= 46const REG_OK: i64 = 0 47const REG_BAD_INPUT: i64 = 0 - 4960 48const REG_REGISTRY_READ: i64 = 0 - 4961 // could not read wiki_pages.tsv 49const REG_OVERFLOW: i64 = 0 - 4962 // more rows than REG_MAX_PAGES 50 51// per-page orphan_check result codes (recorded in the plan; the gate asserts these exactly). 52const RC_CLEAN: i64 = 0 // re-emit leaves no orphan / no dead link attributable to it 53const RC_REFUSE_ORPHAN: i64 = 1 // would orphan a managed page (R-ORPH) -> skipped, not published 54const RC_DEAD_LINK: i64 = 2 // would introduce a dead internal link (R-DEAD) -> skipped 55const RC_MANUAL_ONLY: i64 = 3 // no emitter -> not regenerated, not published (reported) 56const RC_NOT_RUN: i64 = 4 // emitter run failed -> not published 57 58// would_publish verdict. 59const WP_NO: i64 = 0 60const WP_YES: i64 = 1 61 62// ===== named sizing constants (M7) ================================================================ 63const REG_MAX_PAGES: i64 = 64 // managed registry rows cap (corpus is small) 64const REG_SLUG_CAP: i64 = 256 // a single slug / emitter / file field cap 65const REG_LINE_CAP: i64 = 1024 // a single registry line cap 66const REG_MAX_LINKS_PP: i64 = 512 // outbound links scanned per page (mirrors A1) 67const REG_BODY_POOL: i64 = 8388608 // 8 MB doc-store body pool (real pages ~13 KB; generous) 68const REG_STR_POOL: i64 = 131072 // 128 KB url/title pool 69const REG_PATH_CAP: i64 = 1024 // web_assets/<file> path cap 70const REG_READ_CAP: i64 = 2097152 // 2 MB bounded read cap (pages ~13 KB; avoids 4 GB reservations) 71const REG_TAB: i64 = 0x09 // '\t' 72const REG_HASH: i64 = 0x23 // '#' 73const REG_NL: i64 = 0x0A // '\n' 74 75const REG_BUILD_RUNNER: *u8 = "_offc/nx_sov_build_run.elf" 76const REG_WEB_ASSETS: *u8 = "web_assets/" 77const REG_WIKI_PREFIX: *u8 = "/wiki/" // for building each slug's served url 78const REG_REGISTRY_PATH: *u8 = "knowledge/registry/wiki_pages.tsv" 79 80// ===== the parsed registry (parallel arrays; one entry per managed page) ========================== 81struct RegRegistry { 82 n: i64, // number of pages 83 slug_p: *i64, // slug_p[i] -> NUL-terminated slug 84 emit_p: *i64, // emit_p[i] -> NUL-terminated emitter organ ("-" if none) 85 file_p: *i64, // file_p[i] -> NUL-terminated web_assets file ("-" if none) 86 is_root: *i64, // is_root[i] (1/0) 87 has_emit: *i64 // has_emit[i] (1 iff emitter != "-") 88} 89 90// ===== the per-page plan record (what each pulse WOULD do) ======================================== 91struct RegPlan { 92 n: i64, 93 slug_p: *i64, // slug_p[i] -> NUL slug 94 emitted: *i64, // emitted bytes (0 if not re-emitted) 95 guard: *i64, // pg_decide verdict (PG_ALLOW / PG_REJECT_*), or 0 if not guarded 96 orphan_chk: *i64, // one of RC_* 97 would_pub: *i64, // WP_YES / WP_NO 98 pushed: *i64 // 1 iff a push was actually invoked for this page (the safety witness) 99} 100 101// ===== tiny helpers (reg_ namespace) ============================================================= 102func reg_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 103 104// NUL-terminated equals? 105func reg_eq(a: *u8, b: *u8) -> i64 { 106 var i: i64 = 0 107 while i < REG_SLUG_CAP { 108 if a[i] != b[i] { return 0 } 109 if a[i] == (0 as u8) { return 1 } 110 i = i + 1 111 } 112 return 0 113} 114 115// copy a counted region (src,off,len) into a fresh NUL-terminated buffer; returns the buffer. 116func reg_dup(src: *u8, off: i64, len: i64) -> *u8 { 117 let out: *u8 = sys_mmap(len + 1) 118 var i: i64 = 0 119 while i < len { out[i] = src[off + i]; i = i + 1 } 120 out[len] = 0 as u8 121 return out 122} 123 124// concat NUL-terminated s into dst at off; returns new off (no terminator). 125func reg_cat(dst: *u8, off: i64, s: *u8) -> i64 { 126 var o: i64 = off; var k: i64 = 0 127 while s[k] != (0 as u8) { dst[o] = s[k]; o = o + 1; k = k + 1 } 128 return o 129} 130 131// BOUNDED file read: mmap exactly `cap` bytes (NOT the 4 GB sys_read_file reserves) and read up to cap. 132// ROOT-CAUSE FIX for the fork-ENOMEM gotcha: sys_read_file reserves 4 GiB virtual per call, so calling it 133// many times (registry + 2 reads per page) exhausts VA and a later fork() of the build-runner fails with 134// ENOMEM (observed: the 4th emitter silently not spawning). A bounded cap keeps every read cheap. Wiki 135// pages are ~13 KB; REG_READ_CAP is generous headroom. NUL-terminates. Returns byte count, or -1 on open 136// fail / 0 on empty. Fills out_ptr with the buffer pointer. 137func reg_read_bounded(path: *u8, cap: i64, out_ptr: *i64) -> i64 { 138 out_ptr[0] = 0 139 let fd: i64 = sys_openat_rd(path) 140 if fd < 0 { return 0 - 1 } 141 let buf: *u8 = sys_mmap(cap + 16) 142 var total: i64 = 0 143 var go: i64 = 1 144 var iter: i64 = 0 145 while go == 1 { 146 if iter >= REG_MAGIC_1048576 { go = 0 } // hard loop cap (M3) 147 iter = iter + 1 148 if go == 1 { 149 let tail: *u8 = (buf as i64 + total) as *u8 150 let n: i64 = sys_read(fd, tail, cap - total) 151 if n <= 0 { go = 0 } 152 if n > 0 { total = total + n } 153 if total >= cap { go = 0 } 154 } 155 } 156 sys_close(fd) 157 let term: *u8 = (buf as i64 + total) as *u8 158 term[0] = 0 as u8 159 out_ptr[0] = buf as i64 160 return total 161} 162 163// ===== registry parse ============================================================================ 164// Reads knowledge/registry/wiki_pages.tsv; skips blank + '#' comment lines; parses up to 4 TAB fields 165// (slug, emitter, emit_file, is_root) per row into reg. Returns REG_OK or a negative verdict. 166func reg_load(reg: *RegRegistry) -> i64 { 167 reg.n = 0 168 reg.slug_p = sys_mmap(REG_MAX_PAGES * 8) as *i64 169 reg.emit_p = sys_mmap(REG_MAX_PAGES * 8) as *i64 170 reg.file_p = sys_mmap(REG_MAX_PAGES * 8) as *i64 171 reg.is_root = sys_mmap(REG_MAX_PAGES * 8) as *i64 172 reg.has_emit= sys_mmap(REG_MAX_PAGES * 8) as *i64 173 174 let rbox: *i64 = sys_mmap(16) as *i64 175 let total: i64 = reg_read_bounded(REG_REGISTRY_PATH, REG_READ_CAP, rbox) 176 if total <= 0 { return REG_REGISTRY_READ } 177 let buf: *u8 = rbox[0] as *u8 178 179 var i: i64 = 0 180 var iter: i64 = 0 181 while i < total { 182 if iter >= REG_MAX_PAGES * REG_MAGIC_4096 { return REG_OVERFLOW } 183 iter = iter + 1 184 // find this line's newline index le (or total if the last line has no trailing newline). 185 var j: i64 = i 186 var le: i64 = total 187 var found_nl: i64 = 0 188 while j < total { 189 if found_nl == 0 { 190 if buf[j] == (REG_NL as u8) { le = j; found_nl = 1 } 191 } 192 j = j + 1 193 } 194 let lstart: i64 = i 195 let lend: i64 = le 196 // process line [lstart, lend) if non-empty and not a comment 197 if lend > lstart { 198 if buf[lstart] != (REG_HASH as u8) { 199 // split up to 4 TAB fields 200 let f_off: *i64 = sys_mmap(8 * 8) as *i64 201 let f_len: *i64 = sys_mmap(8 * 8) as *i64 202 var nf: i64 = 0 203 var fs: i64 = lstart 204 var p: i64 = lstart 205 while p <= lend { 206 var issep: i64 = 0 207 if p == lend { issep = 1 } 208 else { if buf[p] == (REG_TAB as u8) { issep = 1 } } 209 if issep == 1 { 210 if nf < 8 { 211 f_off[nf] = fs 212 f_len[nf] = p - fs 213 nf = nf + 1 214 } 215 fs = p + 1 216 } 217 p = p + 1 218 } 219 // need at least slug + emitter + file + is_root (4 fields) 220 if nf >= 4 { 221 if reg.n < REG_MAX_PAGES { 222 let r: i64 = reg.n 223 let slug: *u8 = reg_dup(buf, f_off[0], f_len[0]) 224 let emit: *u8 = reg_dup(buf, f_off[1], f_len[1]) 225 let efil: *u8 = reg_dup(buf, f_off[2], f_len[2]) 226 let isr: *u8 = reg_dup(buf, f_off[3], f_len[3]) 227 reg.slug_p[r] = slug as i64 228 reg.emit_p[r] = emit as i64 229 reg.file_p[r] = efil as i64 230 var rootv: i64 = 0 231 if isr[0] == (49 as u8) { rootv = 1 } // '1' 232 reg.is_root[r] = rootv 233 var he: i64 = 1 234 if reg_eq(emit, "-" as *u8) == 1 { he = 0 } 235 reg.has_emit[r] = he 236 reg.n = r + 1 237 } else { return REG_OVERFLOW } 238 } 239 } 240 } 241 if found_nl == 0 { i = total } // last line had no trailing newline -> done 242 else { i = le + 1 } 243 } 244 return REG_OK 245} 246 247// ===== re-emit one page ========================================================================== 248// fork+exec the sovereign build-runner on the emitter organ. The runner COMPILES the emitter and RUNS it; 249// the emitter writes web_assets/<emit_file> (relative to cwd) and stamps epoch= in its footer. Returns the 250// runner exit code (0 = ok), or -1 if it could not be spawned. (Reuse of the exact mechanism the whole 251// stack uses to build+run a sovereign organ; we do NOT re-implement the compiler.) 252func reg_emit(emitter: *u8) -> i64 { 253 let pid: i64 = sys_fork() 254 if pid == 0 { 255 let argv: *i64 = sys_mmap(64) as *i64 256 argv[0] = REG_BUILD_RUNNER as i64 257 argv[1] = emitter as i64 258 argv[2] = 0 259 let envp: *i64 = sys_mmap(16) as *i64 260 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 261 sys_execve(REG_BUILD_RUNNER, argv, envp) 262 sys_exit(127) 263 } 264 if pid < 0 { return 0 - 1 } 265 let st: *i64 = sys_mmap(16) as *i64 266 sys_wait4(pid, st, 0) 267 return (st[0] >> 8) & 0xff 268} 269 270// read web_assets/<file> -> (ptr via out_ptr, len returned). len<0 on read fail. 271func reg_read_asset(file: *u8, out_ptr: *i64) -> i64 { 272 let path: *u8 = sys_mmap(REG_PATH_CAP) 273 var o: i64 = reg_cat(path, 0, REG_WEB_ASSETS) 274 o = reg_cat(path, o, file) 275 path[o] = 0 as u8 276 return reg_read_bounded(path, REG_READ_CAP, out_ptr) 277} 278 279// read web_assets/<slug>.html (for manual-only pages whose file == slug.html on the served root). 280func reg_read_asset_slug(slug: *u8, out_ptr: *i64) -> i64 { 281 let file: *u8 = sys_mmap(REG_PATH_CAP) 282 var o: i64 = reg_cat(file, 0, slug) 283 o = reg_cat(file, o, ".html" as *u8) 284 file[o] = 0 as u8 285 return reg_read_asset(file, out_ptr) 286} 287 288// does outbound-link set of (body,n) contain a target normalising to slug? 1/0. 289func reg_body_links_to(body: *u8, n: i64, slug: *u8) -> i64 { 290 let offs: *i64 = sys_mmap(REG_MAX_LINKS_PP * 8) as *i64 291 let lens: *i64 = sys_mmap(REG_MAX_LINKS_PP * 8) as *i64 292 let lc: *i64 = sys_mmap(8) as *i64 293 let rc: i64 = nx_ims_collect_targets(body, n, offs, lens, REG_MAX_LINKS_PP, lc) 294 if rc != NX_IMS_OK { return 0 } 295 let slug_n: i64 = reg_len(slug) 296 var k: i64 = 0 297 while k < lc[0] { 298 if k >= REG_MAX_LINKS_PP { k = lc[0] } 299 if k < lc[0] { 300 let lp: *u8 = (body as i64 + offs[k]) as *u8 301 if nx_ims_slug_eq(lp, lens[k], slug, slug_n) == 1 { return 1 } 302 } 303 k = k + 1 304 } 305 return 0 306} 307 308// ===== DECISION CORE (shared by the live runner AND the gate) ===================================== 309// Given a populated would-be corpus store + the registry + (optionally) per-page BASELINE bodies, compute 310// the orphan_check + would_publish for every emitter page, applying R-DEAD and R-ORPH. base_ptr[i] / 311// base_len[i] hold each emitter page's PRIOR (baseline) body for the drop-diff (base_ptr[i]==0 => no 312// baseline available => only the dead-link + own-orphan rules apply to it). Fills plan (already sized). 313// READ-ONLY: pushes nothing. Returns REG_OK or -verdict. 314func reg_decide(store: *NxWikiDocStore, reg: *RegRegistry, 315 base_ptr: *i64, base_len: *i64, 316 plan: *RegPlan) -> i64 { 317 let dc: i64 = nx_wiki_doc_store_count(store) 318 319 // resolve root rowids from the registry (is_root==1). 320 let roots: *i64 = sys_mmap(NX_IMS_MAX_ROOTS * 8) as *i64 321 var nroots: i64 = 0 322 var ri: i64 = 0 323 while ri < reg.n { 324 if ri >= REG_MAX_PAGES { ri = reg.n } 325 if ri < reg.n { 326 if reg.is_root[ri] == 1 { 327 if nroots < NX_IMS_MAX_ROOTS { 328 let rr: i64 = nx_ims_resolve_root(store, reg.slug_p[ri] as *u8, reg_len(reg.slug_p[ri] as *u8)) 329 if rr >= 0 { roots[nroots] = rr; nroots = nroots + 1 } 330 } 331 } 332 } 333 ri = ri + 1 334 } 335 336 // run the A1 monitor over the would-be corpus. 337 let orph: *i64 = sys_mmap(NX_IMS_MAX_PAGES * 8) as *i64 338 let oc: *i64 = sys_mmap(8) as *i64 339 let or: i64 = nx_ims_orphans(store, roots, nroots, orph, NX_IMS_MAX_PAGES, oc) 340 if or != NX_IMS_OK { return REG_BAD_INPUT } 341 let dsrc: *i64 = sys_mmap(NX_IMS_MAX_PAGES * 8) as *i64 342 let doff: *i64 = sys_mmap(NX_IMS_MAX_PAGES * 8) as *i64 343 let dlen: *i64 = sys_mmap(NX_IMS_MAX_PAGES * 8) as *i64 344 let ddc: *i64 = sys_mmap(8) as *i64 345 let dr: i64 = nx_ims_dead_links(store, dsrc, doff, dlen, NX_IMS_MAX_PAGES, ddc) 346 if dr != NX_IMS_OK { return REG_BAD_INPUT } 347 348 // per emitter page, decide. 349 var p: i64 = 0 350 while p < plan.n { 351 if p >= REG_MAX_PAGES { p = plan.n } 352 if p < plan.n { 353 if reg.has_emit[p] == 1 { 354 if plan.orphan_chk[p] != RC_NOT_RUN { 355 // resolve this page's rowid in the store (by slug). 356 let myrow: i64 = nx_ims_resolve_root(store, reg.slug_p[p] as *u8, reg_len(reg.slug_p[p] as *u8)) 357 var verdict: i64 = RC_CLEAN 358 359 // R-DEAD: is this page the SOURCE of any dead internal link? 360 var di: i64 = 0 361 while di < ddc[0] { 362 if di >= NX_IMS_MAX_PAGES { di = ddc[0] } 363 if di < ddc[0] { 364 if dsrc[di] == myrow { if myrow >= 0 { verdict = RC_DEAD_LINK } } 365 } 366 di = di + 1 367 } 368 369 // own-orphan: is THIS page an orphan in the would-be corpus? 370 if verdict == RC_CLEAN { 371 var oi: i64 = 0 372 while oi < oc[0] { 373 if oi >= NX_IMS_MAX_PAGES { oi = oc[0] } 374 if oi < oc[0] { 375 if orph[oi] == myrow { if myrow >= 0 { verdict = RC_REFUSE_ORPHAN } } 376 } 377 oi = oi + 1 378 } 379 } 380 381 // R-ORPH: did this page DROP an outbound link (in baseline, absent now) whose target is 382 // now an orphan? (this page's change orphaned a managed page.) 383 if verdict == RC_CLEAN { 384 if base_ptr[p] != 0 { 385 // for each orphan T, if baseline(this) linked T but would-be(this) does not -> flag. 386 let myb_p: i64 = base_ptr[p] 387 let myb_n: i64 = base_len[p] 388 // would-be body of this page (from the store). 389 let tp: *i64 = sys_mmap(8) as *i64; let tn: *i64 = sys_mmap(8) as *i64 390 let up: *i64 = sys_mmap(8) as *i64; let un: *i64 = sys_mmap(8) as *i64 391 let bp: *i64 = sys_mmap(8) as *i64; let bn: *i64 = sys_mmap(8) as *i64 392 var have_now: i64 = 0 393 if myrow >= 0 { 394 if nx_wiki_doc_store_lookup(store, myrow, tp, tn, up, un, bp, bn) == NX_WIB_OK { have_now = 1 } 395 } 396 if have_now == 1 { 397 var oi2: i64 = 0 398 while oi2 < oc[0] { 399 if oi2 >= NX_IMS_MAX_PAGES { oi2 = oc[0] } 400 if oi2 < oc[0] { 401 // orphan T's slug 402 let t2p: *i64 = sys_mmap(8) as *i64; let t2n: *i64 = sys_mmap(8) as *i64 403 let u2p: *i64 = sys_mmap(8) as *i64; let u2n: *i64 = sys_mmap(8) as *i64 404 let b2p: *i64 = sys_mmap(8) as *i64; let b2n: *i64 = sys_mmap(8) as *i64 405 if nx_wiki_doc_store_lookup(store, orph[oi2], t2p, t2n, u2p, u2n, b2p, b2n) == NX_WIB_OK { 406 // normalise the orphan url to a bare slug 407 let nb: *i64 = sys_mmap(8) as *i64 408 let nlen: i64 = nx_ims_norm_slug(u2p[0] as *u8, u2n[0], nb) 409 let oslug: *u8 = reg_dup(nb[0] as *u8, 0, nlen) 410 // baseline linked it AND would-be does NOT -> dropped -> flag 411 let was: i64 = reg_body_links_to(myb_p as *u8, myb_n, oslug) 412 let now: i64 = reg_body_links_to(bp[0] as *u8, bn[0], oslug) 413 if was == 1 { if now == 0 { verdict = RC_REFUSE_ORPHAN } } 414 } 415 } 416 oi2 = oi2 + 1 417 } 418 } 419 } 420 } 421 422 plan.orphan_chk[p] = verdict 423 if verdict == RC_CLEAN { plan.would_pub[p] = WP_YES } 424 else { plan.would_pub[p] = WP_NO } 425 } 426 } 427 } 428 p = p + 1 429 } 430 return REG_OK 431} 432 433// ===== guard pass + (apply) publish ============================================================== 434// After the orphan decision, run the A2 guard over each emitter page's would-be body; a REJECT overrides 435// would_publish to NO with the guard reason. corpus = the managed slug set (parallel arrays) for the dead- 436// internal-link guard check. If apply_flag==1 AND the page is still would_publish==YES, APPLY via A4a vpub 437// (do_push_flag := apply_flag, so apply_flag==0 can NEVER push). prefix = archive store prefix; epoch is the 438// FIXED deterministic version stamp. Returns REG_OK or -verdict. 439func reg_guard_and_apply(reg: *RegRegistry, plan: *RegPlan, store: *NxWikiDocStore, 440 cs_ptr: *i64, cs_len: *i64, ncorpus: i64, 441 prefix: *u8, apply_flag: i64, epoch: i64) -> i64 { 442 var p: i64 = 0 443 while p < plan.n { 444 if p >= REG_MAX_PAGES { p = plan.n } 445 if p < plan.n { 446 if reg.has_emit[p] == 1 { 447 if plan.orphan_chk[p] != RC_NOT_RUN { 448 // would-be body of this page from the store 449 let myrow: i64 = nx_ims_resolve_root(store, reg.slug_p[p] as *u8, reg_len(reg.slug_p[p] as *u8)) 450 if myrow >= 0 { 451 let tp: *i64 = sys_mmap(8) as *i64; let tn: *i64 = sys_mmap(8) as *i64 452 let up: *i64 = sys_mmap(8) as *i64; let un: *i64 = sys_mmap(8) as *i64 453 let bp: *i64 = sys_mmap(8) as *i64; let bn: *i64 = sys_mmap(8) as *i64 454 if nx_wiki_doc_store_lookup(store, myrow, tp, tn, up, un, bp, bn) == NX_WIB_OK { 455 let verdict: i64 = pg_decide(prefix, bp[0] as *u8, bn[0], cs_ptr, cs_len, ncorpus) 456 plan.guard[p] = verdict 457 if verdict != PG_ALLOW { plan.would_pub[p] = WP_NO } 458 } 459 } 460 // APPLY (only when armed AND still publishable). do_push_flag := apply_flag. 461 if apply_flag == 1 { 462 if plan.would_pub[p] == WP_YES { 463 // build web_assets/<file> path for vpub to read the bytes from. 464 let path: *u8 = sys_mmap(REG_PATH_CAP) 465 var o: i64 = reg_cat(path, 0, REG_WEB_ASSETS) 466 o = reg_cat(path, o, reg.file_p[p] as *u8) 467 path[o] = 0 as u8 468 let pres: *PubResult = sys_mmap(64) as *PubResult 469 let vout: *i64 = sys_mmap(8) as *i64 470 let vr: i64 = vpub_ex(prefix, reg.slug_p[p] as *u8, path, 471 cs_ptr, cs_len, ncorpus, epoch, apply_flag, pres, vout) 472 if vr == VP_OK { plan.pushed[p] = pres.push_invoked } 473 else { plan.would_pub[p] = WP_NO } 474 } 475 } 476 } 477 } 478 } 479 p = p + 1 480 } 481 return REG_OK 482} 483 484// ===== TOP-LEVEL: regen(apply_flag) ============================================================== 485// One pulse. apply_flag==0 -> DRY-RUN (re-emit + decide + guard, PUSH NOTHING, fill plan). apply_flag==1 486// -> additionally APPLY publishable pages via A4a vpub (snapshot+guard+push). prefix = archive store prefix 487// (production: WAR_PREFIX). epoch = FIXED deterministic version stamp. Fills plan (caller-supplied, sized 488// >= REG_MAX_PAGES). Returns REG_OK or a negative verdict. The CALLER decides apply_flag -- this organ never 489// pushes when apply_flag==0, by construction (do_push_flag is wired to apply_flag inside reg_guard_and_apply). 490func regen_ex(prefix: *u8, apply_flag: i64, epoch: i64, reg: *RegRegistry, plan: *RegPlan) -> i64 { 491 let lr: i64 = reg_load(reg) 492 if lr != REG_OK { return lr } 493 494 // init plan arrays. 495 plan.n = reg.n 496 plan.slug_p = sys_mmap(REG_MAX_PAGES * 8) as *i64 497 plan.emitted = sys_mmap(REG_MAX_PAGES * 8) as *i64 498 plan.guard = sys_mmap(REG_MAX_PAGES * 8) as *i64 499 plan.orphan_chk = sys_mmap(REG_MAX_PAGES * 8) as *i64 500 plan.would_pub = sys_mmap(REG_MAX_PAGES * 8) as *i64 501 plan.pushed = sys_mmap(REG_MAX_PAGES * 8) as *i64 502 503 // build the would-be corpus store + capture baselines (the CURRENT asset BEFORE re-emit). 504 let store: *NxWikiDocStore = sys_mmap(512) as *NxWikiDocStore 505 nx_wiki_doc_store_init(store, REG_MAX_PAGES, REG_STR_POOL, REG_STR_POOL, REG_BODY_POOL) 506 let base_ptr: *i64 = sys_mmap(REG_MAX_PAGES * 8) as *i64 507 let base_len: *i64 = sys_mmap(REG_MAX_PAGES * 8) as *i64 508 509 // managed slug set (the corpus the dead-link guard + monitor judge against). 510 let cs_ptr: *i64 = sys_mmap(REG_MAX_PAGES * 8) as *i64 511 let cs_len: *i64 = sys_mmap(REG_MAX_PAGES * 8) as *i64 512 513 var i: i64 = 0 514 while i < reg.n { 515 if i >= REG_MAX_PAGES { i = reg.n } 516 if i < reg.n { 517 plan.slug_p[i] = reg.slug_p[i] 518 plan.emitted[i] = 0 519 plan.guard[i] = 0 520 plan.would_pub[i] = WP_NO 521 plan.pushed[i] = 0 522 base_ptr[i] = 0 523 base_len[i] = 0 524 cs_ptr[i] = reg.slug_p[i] 525 cs_len[i] = reg_len(reg.slug_p[i] as *u8) 526 527 // build the served url /wiki/<slug>.html for this page's store row. 528 let url: *u8 = sys_mmap(REG_PATH_CAP) 529 var uo: i64 = reg_cat(url, 0, REG_WIKI_PREFIX) 530 uo = reg_cat(url, uo, reg.slug_p[i] as *u8) 531 uo = reg_cat(url, uo, ".html" as *u8) 532 url[uo] = 0 as u8 533 534 if reg.has_emit[i] == 0 { 535 // manual-only: reported, NOT regenerated, NOT published. (Still part of the corpus universe 536 // so links TO it resolve -- read its live served asset if one exists locally, else an empty 537 // body placeholder so it is a known corpus member.) 538 plan.orphan_chk[i] = RC_MANUAL_ONLY 539 plan.would_pub[i] = WP_NO 540 // add to store with whatever body we can find (the slug-named asset), else empty. 541 let mp: *i64 = sys_mmap(8) as *i64 542 let mlen: i64 = reg_read_asset_slug(reg.slug_p[i] as *u8, mp) 543 if mlen > 0 { 544 nx_wiki_doc_store_add(store, url, uo, url, uo, mp[0] as *u8, mlen) 545 } else { 546 nx_wiki_doc_store_add(store, url, uo, url, uo, "" as *u8, 0) 547 } 548 } else { 549 // capture BASELINE body (current asset before re-emit). 550 let pb: *i64 = sys_mmap(8) as *i64 551 let blen: i64 = reg_read_asset(reg.file_p[i] as *u8, pb) 552 if blen > 0 { base_ptr[i] = pb[0]; base_len[i] = blen } 553 554 // RE-EMIT -> web_assets/<file>. 555 let erc: i64 = reg_emit(reg.emit_p[i] as *u8) 556 if erc != 0 { 557 plan.orphan_chk[i] = RC_NOT_RUN 558 plan.would_pub[i] = WP_NO 559 // still add baseline (or empty) so the corpus has the row. 560 if blen > 0 { nx_wiki_doc_store_add(store, url, uo, url, uo, pb[0] as *u8, blen) } 561 else { nx_wiki_doc_store_add(store, url, uo, url, uo, "" as *u8, 0) } 562 } else { 563 // read the WOULD-BE body and add to the corpus store. 564 let wp: *i64 = sys_mmap(8) as *i64 565 let wlen: i64 = reg_read_asset(reg.file_p[i] as *u8, wp) 566 if wlen > 0 { 567 plan.emitted[i] = wlen 568 plan.orphan_chk[i] = RC_CLEAN // provisional; reg_decide finalises 569 nx_wiki_doc_store_add(store, url, uo, url, uo, wp[0] as *u8, wlen) 570 } else { 571 plan.orphan_chk[i] = RC_NOT_RUN 572 plan.would_pub[i] = WP_NO 573 nx_wiki_doc_store_add(store, url, uo, url, uo, "" as *u8, 0) 574 } 575 } 576 } 577 } 578 i = i + 1 579 } 580 581 // DECIDE (orphan/dead) over the would-be corpus. 582 let dcr: i64 = reg_decide(store, reg, base_ptr, base_len, plan) 583 if dcr != REG_OK { return dcr } 584 585 // GUARD + (apply). do_push_flag is wired to apply_flag inside. 586 let gr: i64 = reg_guard_and_apply(reg, plan, store, cs_ptr, cs_len, reg.n, prefix, apply_flag, epoch) 587 if gr != REG_OK { return gr } 588 return REG_OK 589} 590 591// production convenience: prefix = WAR_PREFIX (the live durable archive store). 592func regen(apply_flag: i64, epoch: i64, reg: *RegRegistry, plan: *RegPlan) -> i64 { 593 return regen_ex(WAR_PREFIX, apply_flag, epoch, reg, plan) 594} 595 596// ===== human-readable names (for the live runner / logs) ========================================= 597func rc_name(c: i64) -> *u8 { 598 if c == RC_CLEAN { return "CLEAN" as *u8 } 599 if c == RC_REFUSE_ORPHAN { return "REFUSE_ORPHAN" as *u8 } 600 if c == RC_DEAD_LINK { return "REFUSE_DEAD_LINK" as *u8 } 601 if c == RC_MANUAL_ONLY { return "MANUAL_ONLY" as *u8 } 602 if c == RC_NOT_RUN { return "EMIT_FAILED" as *u8 } 603 return "?" as *u8 604} 605func guard_name(v: i64) -> *u8 { 606 if v == PG_ALLOW { return "ALLOW" as *u8 } 607 if v == PG_REJECT_NO_FRESHNESS { return "REJECT_NO_FRESHNESS" as *u8 } 608 if v == PG_REJECT_DANGLING_CITE { return "REJECT_DANGLING_CITE" as *u8 } 609 if v == PG_REJECT_DEAD_LINK { return "REJECT_DEAD_LINK" as *u8 } 610 if v == PG_REJECT_PLACEHOLDER { return "REJECT_PLACEHOLDER" as *u8 } 611 if v == 0 { return "(not-guarded)" as *u8 } 612 return "?" as *u8 613}