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}