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}