code wiki / _hdl_build / nx_wiki_structure_regen.nx

nx_wiki_structure_regen.nx source

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