code wiki / (root) / nx_pub_lib.nx

nx_pub_lib.nx source

↩ module page · 1105 lines · 51664 B

1// nx_pub_lib.nx -- THE PUBLISHING PLANE CORE (multi-site, 2026-07-30). 2// 3// THE OPERATOR'S DIAGNOSIS: "how we publish in these workstreams to nishifamily.com is random and 4// arbitrary -- we want nishifamily and all our client sites to have SOTA capabilities so we and the 5// client can focus on work, not on where something was put." 6// 7// MEASURED FIRST (2026-07-30, nx_pub_desk census + live edge fetches), which is why this file exists: 8// nishifamily: FOUR different answers to "what pages does this site have" -- the nav/sitemap page 9// says 23 surfaces (hardcoded in nx_site_chrome), sitemap.xml says 96 urls, the registry says 157 10// rows, the docroot holds 135 pages (+61 .prev artifacts +11 debris). 54 pages are SERVED but in 11// NO registry. 12// andelinwest (a CLIENT): registry 0 rows, no sitemap.xml, no robots.txt (404), and 13// https://andelinwest.com/index.html.bak-v1 returns 200 with a whole previous homepage. 14// 15// THE ELITE PATTERN (news-org + docs-as-code + headless-CMS sweep, July 2026): ONE machine-readable 16// registry per site is the SSOT; every discovery artifact -- sitemap.xml, robots.txt, llms.txt, nav, 17// hubs, feeds -- is DERIVED from it and never hand-curated; and content reaches the docroot only 18// through a gate that maintains the registry. Astro content collections make it a build error, the 19// NYT Gateway makes it a publish refusal. Same law: THE DERIVED ARTIFACT MUST NOT BE AUTHORABLE. 20// 21// THIS FILE is the derivation core, site-agnostic. It reads TWO data sources and writes NO policy: 22// 1. the SITE TABLE (knowledge/pub_sites.conf, TSV): 23// site <TAB> docroot <TAB> regprefix <TAB> baseurl <TAB> title <TAB> disallow-csv 24// 2. the per-site REGISTRY plane at regprefix (the ecosystem row convention, status@col3): 25// id <TAB> title <TAB> owner <TAB> status <TAB> section <TAB> path <TAB> note 26// status vocabulary: live (indexable page) | asset (served, never in the sitemap) | 27// draft | withdrawn | redirect | debris 28// 29// WHAT IT FIXES vs the pd_sitemap it supersedes (both stay until nx_pub_desk migrates, F-debt filed): 30// * REAL XML ESCAPING. pd_esc maps & < > to '.' -- lossy by design for JSON/HTML attributes, but in 31// a <loc> it silently CORRUPTS any URL carrying them. Here &amp; &lt; &gt; &quot; &apos; are emitted. 32// * NO PER-ROW ALLOCATION. pd_sitemap calls sys_mmap INSIDE the row loop (a 512B path buffer per 33// registry row). That is the exact per-call-alloc-in-a-primitive class that leaked 8KB/call through 34// ss_hget across 205 sites. Every buffer here is hoisted above the loop. 35// * HONEST LOAD. sts_load_honest, not sts_load: a stale q:n silently truncates a registry, and a 36// truncated registry emits a SHORT SITEMAP that looks perfectly well-formed. Callers get the flags. 37// * MULTI-SITE BY CONSTRUCTION. No docroot, prefix, domain or disallow rule is compiled in (rule 11). 38// license_tier: ORIGINAL No hw writes (Rule 26). 39// DEPENDENCIES ARE runtime/-ONLY ON PURPOSE (measured 2026-07-30): import resolution reaches 40// _hdl_build/ -> runtime/ but NOT runtime/ -> _hdl_build/. This lib is imported by 41// runtime/nx_site_publish_lib.nx, so importing nx_sovjson_lib.nx (which lives only in _hdl_build/) 42// made every dependent fail with a bare "expand_imports failed". The five string helpers it supplied 43// are inlined below instead -- five lines, versus a shared lib this file structurally cannot reach. 44import "nx_store_seed_lib.nx" 45import "nx_seg_store.nx" 46import "nx_syscalls.nx" 47 48const PL_CAP: i64 = 1048576 49const PL_CONFCAP: i64 = 65536 50const PL_PATHCAP: i64 = 1024 51const PL_SPAN: i64 = 16 52const PL_STATB: i64 = 144 53const PL_MT_OFF: i64 = 88 54const PL_MODE: i64 = 420 55const PL_SECMAX: i64 = 128 56const PL_I64B: i64 = 8 57const PL_STDERR: i64 = 2 58const PL_NUMCAP: i64 = 32 59const PL_BASE10: i64 = 10 60const PL_TAB: i64 = 9 61const PL_NL: i64 = 10 62const PL_SLASH: i64 = 47 63const PL_HASH: i64 = 35 64const PL_COMMA: i64 = 44 65const PL_DASH: i64 = 45 66const PL_DOT: i64 = 46 67const PL_ZERO: i64 = 48 68const PL_SPACE: i64 = 32 69const PL_AMP: i64 = 38 70const PL_LT: i64 = 60 71const PL_GT: i64 = 62 72const PL_QUOTE: i64 = 34 73const PL_APOS: i64 = 39 74const PL_HTML_EXT: i64 = 5 75const PL_INDEX_LEN: i64 = 10 76// getdents64 record layout + scan envelope (DECLARED caps, never a silent truncation) 77const PL_DBUF: i64 = 65536 78const PL_DE_RLO: i64 = 16 79const PL_DE_RHI: i64 = 17 80const PL_DE_TYPE: i64 = 18 81const PL_DE_NAME: i64 = 19 82const PL_DT_DIR: i64 = 4 83const PL_BYTE: i64 = 256 84const PL_NAMES: i64 = 262144 85const PL_MAXN: i64 = 4096 86// tree-walk bounds (2026-08-01, debt 1785614931): the deepest REAL published surface today is 87// 3 dirs (compare/atlas/card); 8 is headroom. Beyond-depth REFUSES loudly, never truncates. 88const PL_MAXDEPTH: i64 = 8 89const PL_ENVB: i64 = 64 90const PL_NAME_PAD: i64 = 2 91const PL_I64B: i64 = 8 92const PL_DAY: i64 = 86400 93const PL_TWO_DIG: i64 = 10 94// civil-from-days (Howard Hinnant) -- integer calendar, no libc 95const PL_EPOCH_SHIFT: i64 = 719468 96const PL_ERA_DAYS: i64 = 146097 97const PL_CENT_DAYS: i64 = 36524 98const PL_QUAD_DAYS: i64 = 1460 99const PL_YEAR_DAYS: i64 = 365 100const PL_ERA_YEARS: i64 = 400 101const PL_LEAP4: i64 = 4 102const PL_LEAP100: i64 = 100 103const PL_MONTH_SLOPE: i64 = 153 104const PL_MP_BIAS: i64 = 2 105const PL_MP_SCALE: i64 = 5 106const PL_MP_CUT: i64 = 10 107const PL_MAR_OFF: i64 = 3 108const PL_DEC_WRAP: i64 = 9 109// registry columns (the ecosystem convention -- status@col3; ADD new columns at the end only, rule 19) 110const PL_C_ID: i64 = 0 111const PL_C_TITLE: i64 = 1 112const PL_C_OWNER: i64 = 2 113const PL_C_STATUS: i64 = 3 114const PL_C_SECTION: i64 = 4 115const PL_C_PATH: i64 = 5 116const PL_C_NOTE: i64 = 6 117// site-table columns 118const PL_S_SITE: i64 = 0 119const PL_S_DOCROOT: i64 = 1 120const PL_S_PREFIX: i64 = 2 121const PL_S_BASEURL: i64 = 3 122const PL_S_TITLE: i64 = 4 123const PL_S_DISALLOW: i64 = 5 124// gateway verdicts 125const PL_OK: i64 = 0 126const PL_REFUSED: i64 = 3 127 128func pl_vlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 129func pl_puts(s: *u8) -> i64 { sys_write(1, s, pl_vlen(s)); return 0 } 130func pl_werr(s: *u8) -> i64 { sys_write(PL_STDERR, s, pl_vlen(s)); return 0 } 131func pl_cat(d: *u8, o: i64, s: *u8) -> i64 { 132 var i: i64 = 0 133 var j: i64 = o 134 while s[i] != (0 as u8) { d[j] = s[i]; j = j + 1; i = i + 1 } 135 return j 136} 137func pl_catn(d: *u8, o: i64, v: i64) -> i64 { 138 var m: i64 = v 139 var j: i64 = o 140 if m < 0 { d[j] = PL_DASH as u8; j = j + 1; m = 0 - m } 141 let t: *u8 = sys_mmap(PL_NUMCAP) 142 var k: i64 = 0 143 if m == 0 { t[0] = PL_ZERO as u8; k = 1 } 144 while m > 0 { t[k] = (PL_ZERO + (m % PL_BASE10)) as u8; m = m / PL_BASE10; k = k + 1 } 145 var i: i64 = 0 146 while i < k { d[j] = t[k - 1 - i]; j = j + 1; i = i + 1 } 147 sys_munmap(t, PL_NUMCAP) 148 return j 149} 150 151// ---- row / column primitives over a newline-and-tab buffer ------------------------------------ 152func pl_le(q: *u8, i: i64, n: i64) -> i64 { 153 var e: i64 = i 154 var s: i64 = 1 155 while s == 1 { if e >= n { s = 0 } else { if q[e] == (PL_NL as u8) { s = 0 } else { e = e + 1 } } } 156 return e 157} 158func pl_col(q: *u8, ls: i64, le: i64, c: i64, out: *i64) -> i64 { 159 var col: i64 = 0 160 var p: i64 = ls 161 while col < c { 162 var s: i64 = 1 163 while s == 1 { if p >= le { return 0 } if q[p] == (PL_TAB as u8) { s = 0 } else { p = p + 1 } } 164 p = p + 1 165 col = col + 1 166 } 167 var e: i64 = p 168 var s2: i64 = 1 169 while s2 == 1 { if e >= le { s2 = 0 } else { if q[e] == (PL_TAB as u8) { s2 = 0 } else { e = e + 1 } } } 170 out[0] = p 171 out[1] = e 172 return 1 173} 174func pl_lit_eq(q: *u8, s: i64, e: i64, lit: *u8) -> i64 { 175 var i: i64 = 0 176 while s + i < e { if lit[i] == (0 as u8) { return 0 } if q[s+i] != lit[i] { return 0 } i = i + 1 } 177 if lit[i] != (0 as u8) { return 0 } 178 return 1 179} 180func pl_span_eq(a: *u8, s1: i64, e1: i64, b: *u8, s2: i64, e2: i64) -> i64 { 181 if e1 - s1 != e2 - s2 { return 0 } 182 var i: i64 = 0 183 while s1 + i < e1 { if a[s1+i] != b[s2+i] { return 0 } i = i + 1 } 184 return 1 185} 186func pl_ends(q: *u8, s: i64, n: i64, suf: *u8) -> i64 { 187 let sl: i64 = pl_vlen(suf) 188 if n < sl { return 0 } 189 var i: i64 = 0 190 while i < sl { if q[s + n - sl + i] != suf[i] { return 0 } i = i + 1 } 191 return 1 192} 193func pl_find(buf: *u8, n: i64, needle: *u8) -> i64 { 194 let nl: i64 = pl_vlen(needle) 195 if nl == 0 { return 0 } 196 var i: i64 = 0 197 while i + nl <= n { 198 var k: i64 = 0 199 var hit: i64 = 1 200 while k < nl { if buf[i+k] != needle[k] { hit = 0; k = nl } else { k = k + 1 } } 201 if hit == 1 { return 1 } 202 i = i + 1 203 } 204 return 0 205} 206// copy a span out as a nul-terminated C string in caller scratch 207func pl_span_cstr(q: *u8, s: i64, e: i64, out: *u8) -> i64 { 208 var o: i64 = 0 209 var i: i64 = s 210 while i < e { out[o] = q[i]; o = o + 1; i = i + 1 } 211 out[o] = 0 as u8 212 return o 213} 214func pl_span_put(d: *u8, o: i64, q: *u8, s: i64, e: i64) -> i64 { 215 var i: i64 = s 216 while i < e { d[o] = q[i]; o = o + 1; i = i + 1 } 217 return o 218} 219 220// ---- XML text escaping. NOT pd_esc: in a <loc> a lossy escape is silent CORRUPTION ------------ 221func pl_xesc(d: *u8, o: i64, q: *u8, s: i64, e: i64) -> i64 { 222 var i: i64 = s 223 while i < e { 224 let c: i64 = q[i] as i64 225 var done: i64 = 0 226 if c == PL_AMP { o = pl_cat(d, o, "&amp;" as *u8); done = 1 } 227 if c == PL_LT { if done == 0 { o = pl_cat(d, o, "&lt;" as *u8); done = 1 } } 228 if c == PL_GT { if done == 0 { o = pl_cat(d, o, "&gt;" as *u8); done = 1 } } 229 if c == PL_QUOTE { if done == 0 { o = pl_cat(d, o, "&quot;" as *u8); done = 1 } } 230 if c == PL_APOS { if done == 0 { o = pl_cat(d, o, "&apos;" as *u8); done = 1 } } 231 if done == 0 { 232 var cc: i64 = c 233 if cc < PL_SPACE { cc = PL_SPACE } 234 d[o] = cc as u8 235 o = o + 1 236 } 237 i = i + 1 238 } 239 return o 240} 241// markdown link-text escaping for llms.txt: ] and ) would break the link syntax 242func pl_mesc(d: *u8, o: i64, q: *u8, s: i64, e: i64) -> i64 { 243 var i: i64 = s 244 while i < e { 245 var c: i64 = q[i] as i64 246 if c == 93 { c = 41 } 247 if c == 91 { c = 40 } 248 if c < PL_SPACE { c = PL_SPACE } 249 d[o] = c as u8 250 o = o + 1 251 i = i + 1 252 } 253 return o 254} 255 256// ---- file io: read, and ATOMIC commit (tmp + rename; a browser never sees a torn artifact) ---- 257func pl_wfile(path: *u8, buf: *u8, n: i64) -> i64 { 258 let fd: i64 = sys_openat_wr(path, PL_MODE) 259 if fd < 0 { return 0 - 1 } 260 var o: i64 = 0 261 while o < n { 262 let r: i64 = sys_write(fd, ((buf as i64) + o) as *u8, n - o) 263 if r <= 0 { sys_close(fd); return 0 - 1 } 264 o = o + r 265 } 266 sys_close(fd) 267 return 0 268} 269func pl_rfile(path: *u8, buf: *u8, cap: i64) -> i64 { 270 let fd: i64 = sys_openat_rd(path) 271 if fd < 0 { return 0 - 1 } 272 var n: i64 = 0 273 var go: i64 = 1 274 while go == 1 { 275 let r: i64 = sys_read(fd, ((buf as i64) + n) as *u8, cap - 1 - n) 276 if r <= 0 { go = 0 } else { n = n + r } 277 if n >= cap - 1 { go = 0 } 278 } 279 sys_close(fd) 280 return n 281} 282func pl_commit(path: *u8, buf: *u8, n: i64) -> i64 { 283 let tmp: *u8 = sys_mmap(PL_PATHCAP) 284 var t: i64 = pl_cat(tmp, 0, path) 285 t = pl_cat(tmp, t, ".nxpub" as *u8) 286 tmp[t] = 0 as u8 287 if pl_wfile(tmp, buf, n) != 0 { sys_munmap(tmp, PL_PATHCAP); return 0 - 1 } 288 if sys_renameat(tmp, path) != 0 { sys_munmap(tmp, PL_PATHCAP); return 0 - 1 } 289 sys_munmap(tmp, PL_PATHCAP) 290 return 0 291} 292func pl_join(out: *u8, dir: *u8, name: *u8) -> i64 { 293 var o: i64 = pl_cat(out, 0, dir) 294 out[o] = PL_SLASH as u8 295 o = o + 1 296 o = pl_cat(out, o, name) 297 out[o] = 0 as u8 298 return o 299} 300// mtime (epoch sec) of a path, -1 if unstatable. The freshness channel is a MACHINE fact, never 301// a self-reported date -- and it doubles as the EXISTS test the sitemap law requires. 302func pl_mtime_path(path: *u8) -> i64 { 303 let sb: *u8 = sys_mmap(PL_STATB) 304 if sys_fstatat(path, sb) != 0 { sys_munmap(sb, PL_STATB); return 0 - 1 } 305 let mp: *i64 = ((sb as i64) + PL_MT_OFF) as *i64 306 let v: i64 = mp[0] 307 sys_munmap(sb, PL_STATB) 308 return v 309} 310// append yyyy-mm-dd for an epoch (civil-from-days, pure integer) 311func pl_iso(d: *u8, o: i64, epoch: i64) -> i64 { 312 var z: i64 = epoch / PL_DAY 313 z = z + PL_EPOCH_SHIFT 314 let era: i64 = z / PL_ERA_DAYS 315 let doe: i64 = z - era * PL_ERA_DAYS 316 let yoe: i64 = (doe - doe/PL_QUAD_DAYS + doe/PL_CENT_DAYS - doe/(PL_ERA_DAYS - 1)) / PL_YEAR_DAYS 317 var y: i64 = yoe + era * PL_ERA_YEARS 318 let doy: i64 = doe - (PL_YEAR_DAYS*yoe + yoe/PL_LEAP4 - yoe/PL_LEAP100) 319 let mp: i64 = (PL_MP_SCALE*doy + PL_MP_BIAS) / PL_MONTH_SLOPE 320 let dd: i64 = doy - (PL_MONTH_SLOPE*mp + PL_MP_BIAS)/PL_MP_SCALE + 1 321 var mm: i64 = mp + PL_MAR_OFF 322 if mp >= PL_MP_CUT { mm = mp - PL_DEC_WRAP } 323 if mm <= PL_MP_BIAS { y = y + 1 } 324 var oo: i64 = pl_catn(d, o, y) 325 d[oo] = PL_DASH as u8 326 oo = oo + 1 327 if mm < PL_TWO_DIG { d[oo] = PL_ZERO as u8; oo = oo + 1 } 328 oo = pl_catn(d, oo, mm) 329 d[oo] = PL_DASH as u8 330 oo = oo + 1 331 if dd < PL_TWO_DIG { d[oo] = PL_ZERO as u8; oo = oo + 1 } 332 oo = pl_catn(d, oo, dd) 333 return oo 334} 335 336// ---- THE URL CONTRACT -------------------------------------------------------------------------- 337// ONE canonical URL per resource, and it is the CLEAN one the edge already 301s to: 338// index.html -> / contact/index.html -> /contact/ X.html -> /X else verbatim 339// The DIRECTORY-INDEX case is not cosmetic: matching only the bare literal "index.html" emits 340// /contact/index for a nested index, which is a URL that does not exist -- a sitemap full of them 341// is worse than no sitemap. Matching the SUFFIX handles the root and every subdirectory with one rule. 342// Emitted into d at o, escaped for XML. Returns the new offset. 343func pl_clean_url(d: *u8, o: i64, q: *u8, s: i64, e: i64) -> i64 { 344 var end: i64 = e 345 if pl_ends(q, s, e - s, "index.html" as *u8) == 1 { end = e - PL_INDEX_LEN } else { 346 if pl_ends(q, s, e - s, ".html" as *u8) == 1 { end = e - PL_HTML_EXT } 347 } 348 return pl_xesc(d, o, q, s, end) 349} 350 351// ---- the SITE TABLE ---------------------------------------------------------------------------- 352// resolve one site row; spans[0..1]=docroot [2..3]=prefix [4..5]=baseurl [6..7]=title [8..9]=disallow. 353// conf bytes are returned in `conf` (caller-owned) because the spans point INTO it. 354func pl_site_lookup(confpath: *u8, site: *u8, conf: *u8, spans: *i64) -> i64 { 355 let n: i64 = pl_rfile(confpath, conf, PL_CONFCAP) 356 if n <= 0 { return 0 } 357 let c: *i64 = sys_mmap(PL_SPAN) as *i64 358 var found: i64 = 0 359 var i: i64 = 0 360 while i < n { 361 let le: i64 = pl_le(conf, i, n) 362 var skip: i64 = 0 363 if le <= i { skip = 1 } 364 if skip == 0 { if conf[i] == (PL_HASH as u8) { skip = 1 } } 365 if skip == 0 { 366 if pl_col(conf, i, le, PL_S_SITE, c) == 1 { 367 if pl_lit_eq(conf, c[0], c[1], site) == 1 { 368 var ok: i64 = 1 369 if pl_col(conf, i, le, PL_S_DOCROOT, c) == 1 { spans[0] = c[0]; spans[1] = c[1] } else { ok = 0 } 370 if pl_col(conf, i, le, PL_S_PREFIX, c) == 1 { spans[2] = c[0]; spans[3] = c[1] } else { ok = 0 } 371 if pl_col(conf, i, le, PL_S_BASEURL, c) == 1 { spans[4] = c[0]; spans[5] = c[1] } else { ok = 0 } 372 if pl_col(conf, i, le, PL_S_TITLE, c) == 1 { spans[6] = c[0]; spans[7] = c[1] } else { ok = 0 } 373 spans[8] = 0 374 spans[9] = 0 375 if pl_col(conf, i, le, PL_S_DISALLOW, c) == 1 { spans[8] = c[0]; spans[9] = c[1] } 376 if ok == 1 { found = 1; i = n } 377 } 378 } 379 } 380 if found == 0 { i = le + 1 } 381 } 382 sys_munmap(c as *u8, PL_SPAN) 383 return found 384} 385 386// ---- sitemap.xml: REGISTRY-DERIVED, canonical-only --------------------------------------------- 387// A URL enters iff status=live AND the file EXISTS on disk. Everything the 2026 guidance calls a 388// conflicting discovery signal -- withdrawn, redirect, draft, debris, registered-but-absent ghosts, 389// unregistered orphans -- is excluded BY CONSTRUCTION, because the loop can only read the registry. 390// Returns url count, or -1 on emit failure. flags[] carries the honest-load report. 391func pl_sitemap(docroot: *u8, prefix: *u8, baseurl: *u8, outpath: *u8, flags: *i64) -> i64 { 392 let reg: *u8 = sys_mmap(PL_CAP) 393 let rn: i64 = sts_load_honest(prefix, reg, PL_CAP, flags) 394 let out: *u8 = sys_mmap(PL_CAP) 395 // ALL scratch hoisted above the row loop -- a per-row sys_mmap is the leak class this file exists to end 396 let cs: *i64 = sys_mmap(PL_SPAN) as *i64 397 let cp: *i64 = sys_mmap(PL_SPAN) as *i64 398 let pathb: *u8 = sys_mmap(PL_PATHCAP) 399 let relb: *u8 = sys_mmap(PL_PATHCAP) 400 var o: i64 = 0 401 o = pl_cat(out, o, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" as *u8) 402 o = pl_cat(out, o, "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" as *u8) 403 var nurl: i64 = 0 404 var i: i64 = 0 405 while i < rn { 406 let le: i64 = pl_le(reg, i, rn) 407 var live: i64 = 0 408 if pl_col(reg, i, le, PL_C_STATUS, cs) == 1 { if pl_lit_eq(reg, cs[0], cs[1], "live" as *u8) == 1 { live = 1 } } 409 if live == 1 { 410 if pl_col(reg, i, le, PL_C_PATH, cp) == 1 { 411 pl_span_cstr(reg, cp[0], cp[1], relb) 412 pl_join(pathb, docroot, relb) 413 let mt: i64 = pl_mtime_path(pathb) 414 if mt > 0 { 415 o = pl_cat(out, o, " <url><loc>" as *u8) 416 o = pl_cat(out, o, baseurl) 417 o = pl_cat(out, o, "/" as *u8) 418 o = pl_clean_url(out, o, reg, cp[0], cp[1]) 419 o = pl_cat(out, o, "</loc><lastmod>" as *u8) 420 o = pl_iso(out, o, mt) 421 o = pl_cat(out, o, "</lastmod></url>\n" as *u8) 422 nurl = nurl + 1 423 } 424 } 425 } 426 i = le + 1 427 } 428 o = pl_cat(out, o, "</urlset>\n" as *u8) 429 let rc: i64 = pl_commit(outpath, out, o) 430 sys_munmap(reg, PL_CAP) 431 sys_munmap(out, PL_CAP) 432 sys_munmap(cs as *u8, PL_SPAN) 433 sys_munmap(cp as *u8, PL_SPAN) 434 sys_munmap(pathb, PL_PATHCAP) 435 sys_munmap(relb, PL_PATHCAP) 436 if rc != 0 { return 0 - 1 } 437 return nurl 438} 439 440// ---- robots.txt: points at the sitemap, disallows are DATA (the site table), never compiled in -- 441func pl_robots(baseurl: *u8, disallow: *u8, ds: i64, de: i64, outpath: *u8) -> i64 { 442 let out: *u8 = sys_mmap(PL_CONFCAP) 443 var o: i64 = 0 444 o = pl_cat(out, o, "User-agent: *\n" as *u8) 445 var n: i64 = 0 446 var i: i64 = ds 447 var seg: i64 = ds 448 while i <= de { 449 var atend: i64 = 0 450 if i == de { atend = 1 } else { if disallow[i] == (PL_COMMA as u8) { atend = 1 } } 451 if atend == 1 { 452 if i > seg { 453 o = pl_cat(out, o, "Disallow: /" as *u8) 454 o = pl_span_put(out, o, disallow, seg, i) 455 o = pl_cat(out, o, "\n" as *u8) 456 n = n + 1 457 } 458 seg = i + 1 459 } 460 i = i + 1 461 } 462 o = pl_cat(out, o, "Allow: /\n" as *u8) 463 o = pl_cat(out, o, "Sitemap: " as *u8) 464 o = pl_cat(out, o, baseurl) 465 o = pl_cat(out, o, "/sitemap.xml\n" as *u8) 466 let rc: i64 = pl_commit(outpath, out, o) 467 sys_munmap(out, PL_CONFCAP) 468 if rc != 0 { return 0 - 1 } 469 return n 470} 471 472// ---- llms.txt: the SAME registry, rendered for machine readers (llmstxt.org shape) ------------- 473// HONEST NOTE: Google states llms.txt has zero effect on crawling, indexing or ranking. It is emitted 474// because it costs one derivation off a registry we already maintain and it serves non-Google agent 475// readers a curated map instead of a scrape. It is NOT claimed as an SEO control. 476// Sections come out in first-appearance order (no sort; n is site-sized). 477func pl_llms(docroot: *u8, prefix: *u8, baseurl: *u8, title: *u8, outpath: *u8) -> i64 { 478 let reg: *u8 = sys_mmap(PL_CAP) 479 let fl: *i64 = sys_mmap(PL_SPAN) as *i64 480 let rn: i64 = sts_load_honest(prefix, reg, PL_CAP, fl) 481 let out: *u8 = sys_mmap(PL_CAP) 482 let cs: *i64 = sys_mmap(PL_SPAN) as *i64 483 let cp: *i64 = sys_mmap(PL_SPAN) as *i64 484 let ct: *i64 = sys_mmap(PL_SPAN) as *i64 485 let cn: *i64 = sys_mmap(PL_SPAN) as *i64 486 let cg: *i64 = sys_mmap(PL_SPAN) as *i64 487 let ch: *i64 = sys_mmap(PL_SPAN) as *i64 488 let secs: *i64 = sys_mmap(PL_SECMAX * PL_I64B * 2) as *i64 489 let pathb: *u8 = sys_mmap(PL_PATHCAP) 490 let relb: *u8 = sys_mmap(PL_PATHCAP) 491 var o: i64 = 0 492 o = pl_cat(out, o, "# " as *u8) 493 o = pl_cat(out, o, title) 494 o = pl_cat(out, o, "\n\n> Every published surface of " as *u8) 495 o = pl_cat(out, o, baseurl) 496 o = pl_cat(out, o, ", derived from the sovereign publishing registry. Each link is a live, canonical page; nothing here is hand-curated.\n" as *u8) 497 // pass 1: distinct sections, first-appearance order 498 var nsec: i64 = 0 499 var i: i64 = 0 500 while i < rn { 501 let le: i64 = pl_le(reg, i, rn) 502 var live: i64 = 0 503 if pl_col(reg, i, le, PL_C_STATUS, cs) == 1 { if pl_lit_eq(reg, cs[0], cs[1], "live" as *u8) == 1 { live = 1 } } 504 if live == 1 { 505 if pl_col(reg, i, le, PL_C_SECTION, cg) == 1 { 506 var seen: i64 = 0 507 var k: i64 = 0 508 while k < nsec { 509 if pl_span_eq(reg, secs[k+k], secs[k+k+1], reg, cg[0], cg[1]) == 1 { seen = 1; k = nsec } else { k = k + 1 } 510 } 511 if seen == 0 { 512 if nsec < PL_SECMAX { 513 secs[nsec+nsec] = cg[0] 514 secs[nsec+nsec+1] = cg[1] 515 nsec = nsec + 1 516 } 517 } 518 } 519 } 520 i = le + 1 521 } 522 // pass 2: one block per section 523 var nlink: i64 = 0 524 var s: i64 = 0 525 while s < nsec { 526 o = pl_cat(out, o, "\n## " as *u8) 527 o = pl_span_put(out, o, reg, secs[s+s], secs[s+s+1]) 528 o = pl_cat(out, o, "\n\n" as *u8) 529 var j: i64 = 0 530 while j < rn { 531 let le2: i64 = pl_le(reg, j, rn) 532 var live2: i64 = 0 533 if pl_col(reg, j, le2, PL_C_STATUS, cs) == 1 { if pl_lit_eq(reg, cs[0], cs[1], "live" as *u8) == 1 { live2 = 1 } } 534 if live2 == 1 { 535 var same: i64 = 0 536 if pl_col(reg, j, le2, PL_C_SECTION, ch) == 1 { 537 if pl_span_eq(reg, ch[0], ch[1], reg, secs[s+s], secs[s+s+1]) == 1 { same = 1 } 538 } 539 if same == 1 { 540 if pl_col(reg, j, le2, PL_C_PATH, cp) == 1 { 541 pl_span_cstr(reg, cp[0], cp[1], relb) 542 pl_join(pathb, docroot, relb) 543 if pl_mtime_path(pathb) > 0 { 544 o = pl_cat(out, o, "- [" as *u8) 545 if pl_col(reg, j, le2, PL_C_TITLE, ct) == 1 { o = pl_mesc(out, o, reg, ct[0], ct[1]) } 546 o = pl_cat(out, o, "](" as *u8) 547 o = pl_cat(out, o, baseurl) 548 o = pl_cat(out, o, "/" as *u8) 549 o = pl_clean_url(out, o, reg, cp[0], cp[1]) 550 o = pl_cat(out, o, ")" as *u8) 551 if pl_col(reg, j, le2, PL_C_NOTE, cn) == 1 { 552 if cn[1] > cn[0] { 553 o = pl_cat(out, o, ": " as *u8) 554 o = pl_mesc(out, o, reg, cn[0], cn[1]) 555 } 556 } 557 o = pl_cat(out, o, "\n" as *u8) 558 nlink = nlink + 1 559 } 560 } 561 } 562 } 563 j = le2 + 1 564 } 565 s = s + 1 566 } 567 let rc: i64 = pl_commit(outpath, out, o) 568 sys_munmap(reg, PL_CAP) 569 sys_munmap(out, PL_CAP) 570 sys_munmap(pathb, PL_PATHCAP) 571 sys_munmap(relb, PL_PATHCAP) 572 if rc != 0 { return 0 - 1 } 573 return nlink 574} 575 576// ---- docroot scan (ONE level) ------------------------------------------------------------------ 577// Names land in `arena` NUL-terminated, dirs carrying a trailing '/', offsets in offs[]. 578// ONE level on purpose: a publishing registry is the list of PUBLISHED SURFACES, not of every file 579// on disk. nishifamily's docroot holds 70k files, almost all generated leaf artifacts under 580// compare/atlas/... -- adopting those would turn the SSOT into a file listing and destroy its meaning. 581// envp[0]=dropped-beyond-cap envp[1]=.prev rollback artifacts seen. 582func pl_scan(docroot: *u8, arena: *u8, arena_cap: i64, offs: *i64, maxn: i64, envp: *i64) -> i64 { 583 envp[0] = 0 584 envp[1] = 0 585 let fd: i64 = sys_openat_rd(docroot) 586 if fd < 0 { return 0 - 1 } 587 let db: *u8 = sys_mmap(PL_DBUF) 588 var cnt: i64 = 0 589 var ao: i64 = 0 590 var nr: i64 = 1 591 while nr > 0 { 592 nr = sys_getdents64(fd, db, PL_DBUF) 593 if nr > 0 { 594 var o: i64 = 0 595 while o < nr { 596 let rl: i64 = (db[o+PL_DE_RLO] as i64) + ((db[o+PL_DE_RHI] as i64) * PL_BYTE) 597 if rl <= 0 { o = nr } else { 598 var nl: i64 = 0 599 while db[o+PL_DE_NAME+nl] != (0 as u8) { nl = nl + 1 } 600 var dot: i64 = 0 601 if nl >= 1 { if db[o+PL_DE_NAME] == (PL_DOT as u8) { dot = 1 } } 602 if dot == 0 { 603 if pl_ends(db, o+PL_DE_NAME, nl, ".prev" as *u8) == 1 { envp[1] = envp[1] + 1 } 604 if cnt >= maxn { envp[0] = envp[0] + 1 } else { 605 if ao + nl + PL_NAME_PAD >= arena_cap { envp[0] = envp[0] + 1 } else { 606 offs[cnt] = ao 607 var t: i64 = 0 608 while t < nl { arena[ao] = db[o+PL_DE_NAME+t]; ao = ao + 1; t = t + 1 } 609 if db[o+PL_DE_TYPE] == (PL_DT_DIR as u8) { arena[ao] = PL_SLASH as u8; ao = ao + 1 } 610 arena[ao] = 0 as u8 611 ao = ao + 1 612 cnt = cnt + 1 613 } 614 } 615 } 616 o = o + rl 617 } 618 } 619 } 620 } 621 sys_close(fd) 622 sys_munmap(db, PL_DBUF) 623 return cnt 624} 625 626// one refusal voice for every tree-scan cap: name the CAP, name the COUNT, commit NOTHING. 627// Returns -3 so the driver can tell a cap refusal from -1 (unreadable docroot / seed fail) 628// and -2 (lossy plane). Same discipline as the lossy-load guard in pl_adopt: refuse rather 629// than bake a loss, and never stamp the wrong cause over the right one. 630func pl_adopt_refuse(cap: *u8, n: i64) -> i64 { 631 pl_werr("PUB-ADOPT REFUSED: " as *u8) 632 pl_werr(cap) 633 pl_werr(" (affected=" as *u8) 634 let nb: *u8 = sys_mmap(PL_NUMCAP) 635 var no: i64 = pl_catn(nb, 0, n) 636 nb[no] = 0 as u8 637 pl_werr(nb) 638 sys_munmap(nb, PL_NUMCAP) 639 pl_werr("). A truncated adopt would register PART of the tree and the gateway would refuse the rest forever. NOTHING COMMITTED.\n" as *u8) 640 return 0 - 3 641} 642 643// ---- docroot TREE scan (2026-08-01) -- BOUNDED walk via an explicit worklist ------------------- 644// pl_scan above stays ONE level for the desk's top-level disk counts. ADOPTION cannot use it: 645// the one-level walk detects subdirectories and SKIPS them, so a page under code/ or world/ 646// could NEVER enter the registry, and the fail-closed gateway therefore refused every nested 647// page FOREVER while it was live and serving (debt 1785614931). This walk descends, emitting 648// each FILE under its full docroot-relative path ("code/research_map.html") -- the exact string 649// pl_reg_has and pl_check match on. Directories are walked, never emitted: a registry row is a 650// published surface, not a folder. BOUNDED EVERYWHERE; the envelope names each cap SEPARATELY 651// so the caller can refuse with the RIGHT cause (the two-causes lesson: refusing is right either 652// way, but naming the wrong cause sends the operator to the wrong fix): 653// envp[0] = file entries beyond maxn / the name arena (ENTRY cap) 654// envp[1] = .prev rollback artifacts seen (same meaning as pl_scan) 655// envp[2] = directories beyond PL_MAXDEPTH (DEPTH cap) 656// envp[3] = directories beyond the worklist (DIR cap) 657// envp[4] = relpaths too long to join under PL_PATHCAP (PATH cap) 658// envp[5] = subdirectories that could not be opened (UNREADABLE) 659// envp must be PL_ENVB bytes. Returns file count, or -1 iff the docroot itself will not open. 660func pl_scan_tree(docroot: *u8, arena: *u8, arena_cap: i64, offs: *i64, maxn: i64, envp: *i64) -> i64 { 661 var z: i64 = 0 662 while z < 6 { envp[z] = 0; z = z + 1 } 663 let dlen: i64 = pl_vlen(docroot) 664 let db: *u8 = sys_mmap(PL_DBUF) 665 let dira: *u8 = sys_mmap(PL_NAMES) 666 let diro: *i64 = sys_mmap(PL_MAXN * PL_I64B) as *i64 667 let dird: *i64 = sys_mmap(PL_MAXN * PL_I64B) as *i64 668 let pathb: *u8 = sys_mmap(PL_PATHCAP) 669 // worklist seed: the docroot itself -- empty relpath at arena offset 0, depth 0 670 diro[0] = 0 671 dird[0] = 0 672 dira[0] = 0 as u8 673 var dhead: i64 = 0 674 var dtail: i64 = 1 675 var dao: i64 = 1 676 var cnt: i64 = 0 677 var ao: i64 = 0 678 var rootfail: i64 = 0 679 while dhead < dtail { 680 let roff: i64 = diro[dhead] 681 let rdep: i64 = dird[dhead] 682 dhead = dhead + 1 683 let rlen: i64 = pl_vlen(((dira as i64) + roff) as *u8) 684 var fd: i64 = 0 - 1 685 if rlen == 0 { fd = sys_openat_rd(docroot) } else { 686 pl_join(pathb, docroot, ((dira as i64) + roff) as *u8) 687 fd = sys_openat_rd(pathb) 688 } 689 if fd < 0 { 690 if rlen == 0 { rootfail = 1 } else { envp[5] = envp[5] + 1 } 691 } else { 692 var nr: i64 = 1 693 while nr > 0 { 694 nr = sys_getdents64(fd, db, PL_DBUF) 695 if nr > 0 { 696 var o: i64 = 0 697 while o < nr { 698 let rl: i64 = (db[o+PL_DE_RLO] as i64) + ((db[o+PL_DE_RHI] as i64) * PL_BYTE) 699 if rl <= 0 { o = nr } else { 700 var nl: i64 = 0 701 while db[o+PL_DE_NAME+nl] != (0 as u8) { nl = nl + 1 } 702 var dot: i64 = 0 703 if nl >= 1 { if db[o+PL_DE_NAME] == (PL_DOT as u8) { dot = 1 } } 704 if dot == 0 { 705 var pre: i64 = 0 706 if rlen > 0 { pre = rlen + 1 } 707 let flen: i64 = pre + nl 708 if db[o+PL_DE_TYPE] == (PL_DT_DIR as u8) { 709 if rdep + 1 > PL_MAXDEPTH { envp[2] = envp[2] + 1 } else { 710 if dtail >= maxn { envp[3] = envp[3] + 1 } else { 711 if dao + flen + PL_NAME_PAD >= PL_NAMES { envp[3] = envp[3] + 1 } else { 712 if dlen + 1 + flen + PL_NAME_PAD >= PL_PATHCAP { envp[4] = envp[4] + 1 } else { 713 diro[dtail] = dao 714 dird[dtail] = rdep + 1 715 if rlen > 0 { 716 var q: i64 = 0 717 while q < rlen { dira[dao] = dira[roff + q]; dao = dao + 1; q = q + 1 } 718 dira[dao] = PL_SLASH as u8 719 dao = dao + 1 720 } 721 var t: i64 = 0 722 while t < nl { dira[dao] = db[o+PL_DE_NAME+t]; dao = dao + 1; t = t + 1 } 723 dira[dao] = 0 as u8 724 dao = dao + 1 725 dtail = dtail + 1 726 } 727 } 728 } 729 } 730 } else { 731 if pl_ends(db, o+PL_DE_NAME, nl, ".prev" as *u8) == 1 { envp[1] = envp[1] + 1 } 732 if cnt >= maxn { envp[0] = envp[0] + 1 } else { 733 if ao + flen + PL_NAME_PAD >= arena_cap { envp[0] = envp[0] + 1 } else { 734 if dlen + 1 + flen + PL_NAME_PAD >= PL_PATHCAP { envp[4] = envp[4] + 1 } else { 735 offs[cnt] = ao 736 if rlen > 0 { 737 var q2: i64 = 0 738 while q2 < rlen { arena[ao] = dira[roff + q2]; ao = ao + 1; q2 = q2 + 1 } 739 arena[ao] = PL_SLASH as u8 740 ao = ao + 1 741 } 742 var t2: i64 = 0 743 while t2 < nl { arena[ao] = db[o+PL_DE_NAME+t2]; ao = ao + 1; t2 = t2 + 1 } 744 arena[ao] = 0 as u8 745 ao = ao + 1 746 cnt = cnt + 1 747 } 748 } 749 } 750 } 751 } 752 o = o + rl 753 } 754 } 755 } 756 } 757 sys_close(fd) 758 } 759 } 760 sys_munmap(db, PL_DBUF) 761 sys_munmap(dira, PL_NAMES) 762 sys_munmap(diro as *u8, PL_MAXN * PL_I64B) 763 sys_munmap(dird as *u8, PL_MAXN * PL_I64B) 764 sys_munmap(pathb, PL_PATHCAP) 765 if rootfail == 1 { return 0 - 1 } 766 return cnt 767} 768// is this arena name already carried by some registry row's path column? 769func pl_reg_has(reg: *u8, rn: i64, arena: *u8, off: i64) -> i64 { 770 let c: *i64 = sys_mmap(PL_SPAN) as *i64 771 let nl: i64 = pl_vlen(((arena as i64) + off) as *u8) 772 var hit: i64 = 0 773 var i: i64 = 0 774 while i < rn { 775 let le: i64 = pl_le(reg, i, rn) 776 if pl_col(reg, i, le, PL_C_PATH, c) == 1 { 777 if pl_span_eq(reg, c[0], c[1], arena, off, off + nl) == 1 { hit = 1; i = rn } else { i = le + 1 } 778 } else { i = le + 1 } 779 } 780 sys_munmap(c as *u8, PL_SPAN) 781 return hit 782} 783// classify an unregistered docroot entry into the status vocabulary. Rollback/backup/temp artifacts 784// are DEBRIS (they must never be indexed and should never have been reachable); .html is a live page; 785// anything else is an asset (served, never in the sitemap). 786func pl_find_at(q: *u8, off: i64, n: i64, needle: *u8) -> i64 { 787 let nl: i64 = pl_vlen(needle) 788 if nl == 0 { return 0 } 789 var i: i64 = 0 790 while i + nl <= n { 791 var k: i64 = 0 792 var hit: i64 = 1 793 while k < nl { if q[off+i+k] != needle[k] { hit = 0; k = nl } else { k = k + 1 } } 794 if hit == 1 { return 1 } 795 i = i + 1 796 } 797 return 0 798} 799// classify an unregistered docroot entry into the status vocabulary. Rollback/backup/temp artifacts 800// are DEBRIS (they must never be indexed and should never have been reachable); .html is a live page; 801// anything else is an asset (served, never in the sitemap). 802func pl_classify(arena: *u8, off: i64) -> *u8 { 803 let n: i64 = pl_vlen(((arena as i64) + off) as *u8) 804 if pl_ends(arena, off, n, ".prev" as *u8) == 1 { return "debris" as *u8 } 805 if pl_ends(arena, off, n, ".nxtmp" as *u8) == 1 { return "debris" as *u8 } 806 if pl_find_at(arena, off, n, ".bak" as *u8) == 1 { return "debris" as *u8 } 807 if pl_find_at(arena, off, n, ".nxw" as *u8) == 1 { return "debris" as *u8 } 808 // DRAFT, NOT LIVE, and this is the whole safety of the migration. Adoption exists to make the 809 // registry describe reality; it must not also DECIDE that 54 never-reviewed pages should be 810 // handed to search engines. draft = registered (the gateway admits republishing it) but absent 811 // from sitemap.xml and llms.txt until an owner promotes the row to live. The 2026 guidance is 812 // explicit that thin or unreviewed pages in a sitemap are a conflicting discovery signal, and 813 // an adoption tool that silently indexed a docroot would be exactly that, at scale. 814 if pl_ends(arena, off, n, ".html" as *u8) == 1 { return "draft" as *u8 } 815 return "asset" as *u8 816} 817 818// ADOPT: bring every UNREGISTERED docroot entry -- the WHOLE TREE, depth-bounded -- into the 819// registry so the SSOT describes 820// reality before anything starts enforcing against it. Migration order matters: switching on the 821// gateway first would refuse pages that are already live and legitimate. IDEMPOTENT (rule 10) -- 822// an entry already carried by a row is skipped, so running this twice adds nothing. 823// Returns rows ADDED, or -1. counts[0]=draft counts[1]=asset counts[2]=debris counts[3]=skipped. 824func pl_adopt(docroot: *u8, prefix: *u8, counts: *i64) -> i64 { 825 let reg: *u8 = sys_mmap(PL_CAP) 826 let fl: *i64 = sys_mmap(PL_SPAN) as *i64 827 var rn: i64 = sts_load_honest(prefix, reg, PL_CAP, fl) 828 // ⚠REFUSE TO BAKE A LOSS. sts_seed rewrites the WHOLE plane from the buffer we just loaded, so 829 // if the load came back SHORT of the declared q:n -- rows a previous writer dropped -- committing 830 // would make that loss the next generation's truth and it becomes unrecoverable. Learned the hard 831 // way 2026-07-30: nx_debt refused this exact commit on the debt plane WHILE THIS CODE WAS BEING 832 // WRITTEN, and this function had no such guard. Any read-modify-write over a plane needs it. 833 // ⚠TWO CAUSES, OPPOSITE REMEDIES (sibling law, same day): "loaded fewer rows than declared" means 834 // EITHER real row loss (never rewrite -- you would bake it) OR that the READER hit its own buffer cap 835 // (the plane simply outgrew PL_CAP -- the data is fine, raise the cap or archive). Refusing is right 836 // either way, but telling the operator the WRONG cause sends them to the wrong fix, so name which one: 837 // a load that filled the buffer is a reader-cap verdict, not a loss verdict. 838 if fl[0] > fl[1] { 839 if rn >= PL_CAP - 1 { 840 pl_werr("PUB-ADOPT REFUSED: READER CAP, not data loss -- the registry outgrew PL_CAP. Raise it or archive rows; do NOT compact. declared " as *u8) 841 } 842 pl_werr("PUB-ADOPT REFUSED: lossy load (declared " as *u8) 843 let nb: *u8 = sys_mmap(PL_NUMCAP) 844 var no: i64 = pl_catn(nb, 0, fl[0]) 845 nb[no] = 0 as u8 846 pl_werr(nb) 847 no = pl_catn(nb, 0, fl[1]) 848 nb[no] = 0 as u8 849 pl_werr(" rows, reached " as *u8) 850 pl_werr(nb) 851 pl_werr("). Rewriting the plane would BAKE the loss. NOTHING COMMITTED.\n" as *u8) 852 return 0 - 2 853 } 854 let arena: *u8 = sys_mmap(PL_NAMES) 855 let offs: *i64 = sys_mmap(PL_MAXN * PL_I64B) as *i64 856 let envp: *i64 = sys_mmap(PL_ENVB) as *i64 857 // THE TREE, not one level (2026-08-01, debt 1785614931): pl_scan detects subdirectories and 858 // SKIPS them, so no nested page could ever enter the registry and the fail-closed gateway 859 // refused code/, world/, compare/ pages FOREVER -- adopt reported success and fixed nothing, 860 // which is why publishing kept escaping through direct docroot writes. The tree walk emits 861 // FULL relpaths, which are exactly the strings pl_reg_has and pl_check match on. 862 let cnt: i64 = pl_scan_tree(docroot, arena, PL_NAMES, offs, PL_MAXN, envp) 863 if cnt < 0 { return 0 - 1 } 864 // ⚠REFUSE, NEVER TRUNCATE -- and NAME the cap, for the same reason the lossy-load guard 865 // below names its cause: the right refusal with the wrong cause sends the operator to the 866 // wrong fix. A partial walk would register PART of the tree and the gateway would refuse 867 // the rest forever behind a green adopt log -- the exact defect this walk ends. 868 if envp[0] > 0 { return pl_adopt_refuse("ENTRY CAP -- more files than PL_MAXN/PL_NAMES carries; raise them DELIBERATELY or archive docroot debris first" as *u8, envp[0]) } 869 if envp[2] > 0 { return pl_adopt_refuse("DEPTH CAP -- tree deeper than PL_MAXDEPTH; a deeper publishing tree is a decision, not a default" as *u8, envp[2]) } 870 if envp[3] > 0 { return pl_adopt_refuse("DIR CAP -- more directories than the worklist carries (PL_MAXN/PL_NAMES)" as *u8, envp[3]) } 871 if envp[4] > 0 { return pl_adopt_refuse("PATH CAP -- a relpath will not fit PL_PATHCAP once joined to the docroot" as *u8, envp[4]) } 872 if envp[5] > 0 { return pl_adopt_refuse("UNREADABLE SUBDIRECTORY -- adopting around it would silently drop its pages" as *u8, envp[5]) } 873 counts[0] = 0 874 counts[1] = 0 875 counts[2] = 0 876 counts[3] = 0 877 // next id = highest existing numeric id + 1 (ids stay stable; rule 19) 878 let c0: *i64 = sys_mmap(PL_SPAN) as *i64 879 var maxid: i64 = 0 880 var i: i64 = 0 881 while i < rn { 882 let le: i64 = pl_le(reg, i, rn) 883 if pl_col(reg, i, le, PL_C_ID, c0) == 1 { 884 var v: i64 = 0 885 var k: i64 = c0[0] 886 while k < c0[1] { 887 let ch: i64 = reg[k] as i64 888 if ch >= PL_ZERO { if ch <= PL_ZERO + 9 { v = v * 10 + (ch - PL_ZERO) } } 889 k = k + 1 890 } 891 if v > maxid { maxid = v } 892 } 893 i = le + 1 894 } 895 var o: i64 = rn 896 var added: i64 = 0 897 var j: i64 = 0 898 while j < cnt { 899 let off: i64 = offs[j] 900 let nl: i64 = pl_vlen(((arena as i64) + off) as *u8) 901 var isdir: i64 = 0 902 if nl > 0 { if arena[off + nl - 1] == (PL_SLASH as u8) { isdir = 1 } } 903 if isdir == 0 { 904 if pl_reg_has(reg, rn, arena, off) == 1 { counts[3] = counts[3] + 1 } else { 905 let st: *u8 = pl_classify(arena, off) 906 maxid = maxid + 1 907 o = pl_catn(reg, o, maxid) 908 reg[o] = PL_TAB as u8 909 o = o + 1 910 o = pl_span_put(reg, o, arena, off, off + nl) 911 o = pl_cat(reg, o, "\tunassigned\t" as *u8) 912 o = pl_cat(reg, o, st) 913 o = pl_cat(reg, o, "\tUnsorted\t" as *u8) 914 o = pl_span_put(reg, o, arena, off, off + nl) 915 o = pl_cat(reg, o, "\tadopted from the docroot; title/section/owner need a human pass\n" as *u8) 916 added = added + 1 917 if pl_lit_eq(st, 0, pl_vlen(st), "draft" as *u8) == 1 { counts[0] = counts[0] + 1 } 918 if pl_lit_eq(st, 0, pl_vlen(st), "asset" as *u8) == 1 { counts[1] = counts[1] + 1 } 919 if pl_lit_eq(st, 0, pl_vlen(st), "debris" as *u8) == 1 { counts[2] = counts[2] + 1 } 920 } 921 } 922 j = j + 1 923 } 924 if added == 0 { return 0 } 925 let seeded: i64 = sts_seed(prefix, reg, o) 926 if seeded < 0 { return 0 - 1 } 927 return added 928} 929 930// ---- SELF-REGISTRATION: the emitter must declare what it wrote ---------------------------------- 931// THE DEFECT THIS CLOSES (found by the desk I built, on my own work): emit writes sitemap.xml, 932// robots.txt and llms.txt INTO the docroot and never registered them, so the publishing plane's own 933// output showed up as ORPHANS -- files the site serves that its registry does not describe. A plane 934// whose rule is 'nothing reaches the docroot unregistered' must not be the thing breaking that rule. 935// IDEMPOTENT (rule 10): a path already carried by any row is left completely alone, so re-emitting 936// never duplicates and never overwrites a human's curated title/owner/status. 937// Returns 1 if a row was added, 0 if already present, -1 refused, -2 lossy plane. 938func pl_register_asset(prefix: *u8, relpath: *u8, note: *u8) -> i64 { 939 let reg: *u8 = sys_mmap(PL_CAP) 940 let fl: *i64 = sys_mmap(PL_SPAN) as *i64 941 let rn: i64 = sts_load_honest(prefix, reg, PL_CAP, fl) 942 // same read-modify-write guard as pl_adopt: sts_seed rewrites the WHOLE plane, so a short load 943 // would bake the loss. Refuse rather than commit a truncated generation. 944 if fl[0] > fl[1] { sys_munmap(reg, PL_CAP); return 0 - 2 } 945 let cp: *i64 = sys_mmap(PL_SPAN) as *i64 946 let c0: *i64 = sys_mmap(PL_SPAN) as *i64 947 var maxid: i64 = 0 948 var found: i64 = 0 949 var i: i64 = 0 950 while i < rn { 951 let le: i64 = pl_le(reg, i, rn) 952 if pl_col(reg, i, le, PL_C_PATH, cp) == 1 { 953 if pl_lit_eq(reg, cp[0], cp[1], relpath) == 1 { found = 1 } 954 } 955 if pl_col(reg, i, le, PL_C_ID, c0) == 1 { 956 var v: i64 = 0 957 var k: i64 = c0[0] 958 while k < c0[1] { 959 let ch: i64 = reg[k] as i64 960 if ch >= PL_ZERO { if ch <= PL_ZERO + 9 { v = v * 10 + (ch - PL_ZERO) } } 961 k = k + 1 962 } 963 if v > maxid { maxid = v } 964 } 965 i = le + 1 966 } 967 if found == 1 { sys_munmap(reg, PL_CAP); return 0 } 968 var o: i64 = rn 969 o = pl_catn(reg, o, maxid + 1) 970 reg[o] = PL_TAB as u8 971 o = o + 1 972 o = pl_cat(reg, o, relpath) 973 o = pl_cat(reg, o, "\tpublishing-plane\tasset\tDiscovery\t" as *u8) 974 o = pl_cat(reg, o, relpath) 975 reg[o] = PL_TAB as u8 976 o = o + 1 977 o = pl_cat(reg, o, note) 978 reg[o] = PL_NL as u8 979 o = o + 1 980 let seeded: i64 = sts_seed(prefix, reg, o) 981 sys_munmap(reg, PL_CAP) 982 if seeded < 0 { return 0 - 1 } 983 return 1 984} 985 986// ---- MANAGEMENT COUNTS: the numbers the publishing desk renders --------------------------------- 987// Every figure on the board comes from HERE -- the registry plane and the docroot walk -- so the desk 988// cannot drift from reality the way a hand-maintained status page does. c[] = live asset draft 989// withdrawn redirect debris rows_total. 990func pl_status_counts(prefix: *u8, c: *i64) -> i64 { 991 var z: i64 = 0 992 while z < 7 { c[z] = 0; z = z + 1 } 993 let reg: *u8 = sys_mmap(PL_CAP) 994 let fl: *i64 = sys_mmap(PL_SPAN) as *i64 995 let rn: i64 = sts_load_honest(prefix, reg, PL_CAP, fl) 996 let cs: *i64 = sys_mmap(PL_SPAN) as *i64 997 var i: i64 = 0 998 while i < rn { 999 let le: i64 = pl_le(reg, i, rn) 1000 if le > i { 1001 c[6] = c[6] + 1 1002 if pl_col(reg, i, le, PL_C_STATUS, cs) == 1 { 1003 if pl_lit_eq(reg, cs[0], cs[1], "live" as *u8) == 1 { c[0] = c[0] + 1 } 1004 if pl_lit_eq(reg, cs[0], cs[1], "asset" as *u8) == 1 { c[1] = c[1] + 1 } 1005 if pl_lit_eq(reg, cs[0], cs[1], "draft" as *u8) == 1 { c[2] = c[2] + 1 } 1006 if pl_lit_eq(reg, cs[0], cs[1], "withdrawn" as *u8) == 1 { c[3] = c[3] + 1 } 1007 if pl_lit_eq(reg, cs[0], cs[1], "redirect" as *u8) == 1 { c[4] = c[4] + 1 } 1008 if pl_lit_eq(reg, cs[0], cs[1], "debris" as *u8) == 1 { c[5] = c[5] + 1 } 1009 } 1010 } 1011 i = le + 1 1012 } 1013 sys_munmap(reg, PL_CAP) 1014 sys_munmap(cs as *u8, PL_SPAN) 1015 return rn 1016} 1017// Is any registry path INSIDE this directory? A one-level docroot walk yields "contact/" while the 1018// registry holds "contact/index.html", so an exact-match orphan test calls a directory full of 1019// registered pages an ORPHAN -- and the same mismatch makes a census report that row as MISSING while 1020// the file plainly exists and is in the sitemap. Neither side is wrong; the COMPARISON was. A directory 1021// counts as registered when at least one row lives under it. 1022func pl_reg_has_prefix(reg: *u8, rn: i64, arena: *u8, off: i64) -> i64 { 1023 let c: *i64 = sys_mmap(PL_SPAN) as *i64 1024 let nl: i64 = pl_vlen(((arena as i64) + off) as *u8) 1025 var hit: i64 = 0 1026 var i: i64 = 0 1027 while i < rn { 1028 let le: i64 = pl_le(reg, i, rn) 1029 if pl_col(reg, i, le, PL_C_PATH, c) == 1 { 1030 if c[1] - c[0] > nl { 1031 var k: i64 = 0 1032 var same: i64 = 1 1033 while k < nl { if reg[c[0] + k] != arena[off + k] { same = 0; k = nl } else { k = k + 1 } } 1034 if same == 1 { hit = 1; i = rn } 1035 } 1036 } 1037 if hit == 0 { i = le + 1 } 1038 } 1039 sys_munmap(c as *u8, PL_SPAN) 1040 return hit 1041} 1042// d[] = pages dirs others prev_artifacts orphans. ORPHAN = on disk, in NO registry row -- the exact 1043// number that made the operator call publishing "random and arbitrary", so the desk states it plainly. 1044func pl_disk_counts(docroot: *u8, prefix: *u8, d: *i64) -> i64 { 1045 var z: i64 = 0 1046 while z < 5 { d[z] = 0; z = z + 1 } 1047 let arena: *u8 = sys_mmap(PL_NAMES) 1048 let offs: *i64 = sys_mmap(PL_MAXN * PL_I64B) as *i64 1049 let envp: *i64 = sys_mmap(PL_SPAN) as *i64 1050 let cnt: i64 = pl_scan(docroot, arena, PL_NAMES, offs, PL_MAXN, envp) 1051 if cnt < 0 { return 0 - 1 } 1052 d[3] = envp[1] 1053 let reg: *u8 = sys_mmap(PL_CAP) 1054 let fl: *i64 = sys_mmap(PL_SPAN) as *i64 1055 let rn: i64 = sts_load_honest(prefix, reg, PL_CAP, fl) 1056 var k: i64 = 0 1057 while k < cnt { 1058 let off: i64 = offs[k] 1059 let nl: i64 = pl_vlen(((arena as i64) + off) as *u8) 1060 var isdir: i64 = 0 1061 if nl > 0 { if arena[off + nl - 1] == (PL_SLASH as u8) { isdir = 1 } } 1062 if isdir == 1 { d[1] = d[1] + 1 } else { 1063 if pl_ends(arena, off, nl, ".html" as *u8) == 1 { d[0] = d[0] + 1 } else { d[2] = d[2] + 1 } 1064 } 1065 var regd: i64 = pl_reg_has(reg, rn, arena, off) 1066 if regd == 0 { if isdir == 1 { regd = pl_reg_has_prefix(reg, rn, arena, off) } } 1067 if regd == 0 { d[4] = d[4] + 1 } 1068 k = k + 1 1069 } 1070 sys_munmap(reg, PL_CAP) 1071 sys_munmap(arena, PL_NAMES) 1072 return cnt 1073} 1074 1075// ---- THE GATEWAY ------------------------------------------------------------------------------- 1076// A relpath may be published ONLY if the registry carries it with a publishable status. This is the 1077// half that makes "random and arbitrary" impossible rather than merely visible: pd_check states the 1078// same rule but its own header records that enforcement inside the publish organ was never wired. 1079// Returns PL_OK (0) or PL_REFUSED (3). Fail-CLOSED: an unreadable/empty registry refuses everything. 1080func pl_check(prefix: *u8, relpath: *u8) -> i64 { 1081 let reg: *u8 = sys_mmap(PL_CAP) 1082 let fl: *i64 = sys_mmap(PL_SPAN) as *i64 1083 let rn: i64 = sts_load_honest(prefix, reg, PL_CAP, fl) 1084 let cs: *i64 = sys_mmap(PL_SPAN) as *i64 1085 let cp: *i64 = sys_mmap(PL_SPAN) as *i64 1086 var rc: i64 = PL_REFUSED 1087 var i: i64 = 0 1088 while i < rn { 1089 let le: i64 = pl_le(reg, i, rn) 1090 if pl_col(reg, i, le, PL_C_PATH, cp) == 1 { 1091 if pl_lit_eq(reg, cp[0], cp[1], relpath) == 1 { 1092 if pl_col(reg, i, le, PL_C_STATUS, cs) == 1 { 1093 if pl_lit_eq(reg, cs[0], cs[1], "live" as *u8) == 1 { rc = PL_OK } 1094 if pl_lit_eq(reg, cs[0], cs[1], "asset" as *u8) == 1 { rc = PL_OK } 1095 if pl_lit_eq(reg, cs[0], cs[1], "draft" as *u8) == 1 { rc = PL_OK } 1096 } 1097 i = rn 1098 } else { i = le + 1 } 1099 } else { i = le + 1 } 1100 } 1101 sys_munmap(reg, PL_CAP) 1102 sys_munmap(cs as *u8, PL_SPAN) 1103 sys_munmap(cp as *u8, PL_SPAN) 1104 return rc 1105}