code wiki / _hdl_build / nx_js_hydrate_index_gate.nx
nx_js_hydrate_index_gate.nx source
↩ module page · 100 lines · 6030 B
1// nx_js_hydrate_index_gate.nx -- CLOSES THE LOOP: a page whose INLINE JS builds content -> hydrate
2// (js_render_page, async-drained) -> extract text -> INDEX into a seg_store shard -> the JS-built token
3// is SEARCHABLE (ss_term finds it), whereas indexing the RAW static HTML does NOT. Proves hydration makes
4// JS content indexable/searchable -- the payoff of the whole JS-execution arc, end-to-end, HERMETIC.
5//
6// D001 MIGRATION 2026-08-22 -- WHY THIS FILE CHANGED. It hand-rolled its own pass/total counters and
7// printed "=== GREEN (4/4) ===" with NO canonical verdict= anchor, so /api/promote REFUSED it by name and
8// nx_gate_green could not judge it: its outcome has been unreadable FROM OUTSIDE for its whole life.
9// It now inherits nx_gate_verdict, so declared == executed BY CONSTRUCTION (a hand-rolled counter can
10// print 5/4 when a tooth silently stops running) and the EXIT CODE CARRIES the verdict.
11// AND THE EMPTY-SET HOLE IS CLOSED: T2 and T4 are ABSENCE assertions -- "the JS token is NOT in the
12// static shard", "'loading' is GONE from the hydrated one" -- and BOTH PASS TRIVIALLY IF NOTHING WAS
13// INDEXED AT ALL. gv_subjects binds them to their denominators, so a hydration that silently produced
14// zero bytes now SKIPs (evidence=none) instead of reporting a clean 4/4.
15// license_tier: ORIGINAL
16import "nx_syscalls.nx"
17import "nx_gate_verdict.nx" // gv_ctr/gv_head/gv_check/gv_subjects/gv_verdict -- the ONE verdict lib
18import "nx_js_eval.nx" // js_render_page
19import "nx_html_to_text.nx" // hydrated/raw HTML -> text
20import "nx_seg_store.nx" // ss_begin/add/commit/open/term/manifest_cap
21
22func r_clen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
23
24func next_segid(prefix: *u8) -> i64 {
25 let segs: *i64 = sys_mmap(4096 * 8) as *i64
26 let nseg: i64 = ss_manifest_cap(prefix, segs, 4096)
27 var segid: i64 = 1; var si: i64 = 0
28 // ROOT FIX seq1730 (id 1785450987): segs[] holds POINTERS to seg-<id> name strings
29 // (nx_seg_store.nx:1234 stores segs[cnt] = name as i64), NOT ids -- so segs[si] + 1 produced
30 // MMAP_ADDRESS+1 (~1.4e14), the pointer-shaped poison that PINS a plane forever: every later
31 // epoch id sorts BELOW it in supersede order and its rows are silently shadowed while rc=0.
32 // Parse the DIGITS, as nx_web_shard_compact.nx:55-58 already does on this SAME array.
33 while si < nseg {
34 let sg_nm: *u8 = segs[si] as *u8
35 var sg_v: i64 = 0
36 var sg_ci: i64 = 0
37 while sg_nm[sg_ci] != (0 as u8) { let sg_c: i64 = sg_nm[sg_ci] as i64; if sg_c >= 48 { if sg_c <= 57 { sg_v = sg_v * 10 + (sg_c - 48) } } sg_ci = sg_ci + 1 }
38 if sg_v >= segid { segid = sg_v + 1 }
39 si = si + 1
40 }
41 return segid
42}
43// index `text` as one doc into the shard at `prefix` (fresh segment; re-run-safe).
44func index_doc(prefix: *u8, key: *u8, text: *u8, tlen: i64) -> i64 {
45 let segid: i64 = next_segid(prefix)
46 let w: *i64 = ss_begin()
47 ss_add(w, 1, key, text, tlen)
48 ss_commit(prefix, w, segid)
49 return 0
50}
51// how many docs in the shard contain `term` (0 = not indexed / not searchable).
52func term_count(prefix: *u8, term: *u8) -> i64 {
53 let h: *i64 = ss_open(prefix)
54 if (h as i64) == 0 { return 0 }
55 let kp: *i64 = sys_mmap(64 * 8) as *i64
56 let kl: *i64 = sys_mmap(64 * 8) as *i64
57 return ss_term(h, term, kp, kl, 64)
58}
59
60func main() -> i64 {
61 let ctr: *i64 = gv_ctr()
62 gv_head("=== nx_js_hydrate_index_gate: JS-built content -> hydrate -> index -> SEARCHABLE ===" as *u8)
63
64 // A page whose INLINE SCRIPT replaces "LOADING" with a distinctive token only JS produces.
65 let html: *u8 = "<html><body><div id=\"app\">LOADING</div><script>document.getElementById('app').innerHTML='<p>jshydratedtoken99 kyokahydrated</p>';</script></body></html>\x00" as *u8
66 let hlen: i64 = r_clen(html)
67
68 // STATIC path = what a non-JS crawler indexes (raw HTML -> text). Contains "loading", NOT the token.
69 let stat: *u8 = sys_mmap(65536)
70 let statlen: i64 = nx_html_to_text(html, hlen, stat, 65536)
71 // HYDRATED path = run the JS, serialize, then text. Contains the token, NOT "loading".
72 let hydr: *u8 = sys_mmap(262144)
73 let hyl: i64 = js_render_page(html, hlen, hydr, 262144)
74 let hydtext: *u8 = sys_mmap(65536)
75 let hydtlen: i64 = nx_html_to_text(hydr, hyl, hydtext, 65536)
76
77 index_doc("/tmp/hydidx-static-\x00" as *u8, "doc:s\x00" as *u8, stat, statlen)
78 index_doc("/tmp/hydidx-hydr-\x00" as *u8, "doc:h\x00" as *u8, hydtext, hydtlen)
79
80 let ch: i64 = term_count("/tmp/hydidx-hydr-\x00" as *u8, "jshydratedtoken99\x00" as *u8)
81 let cs: i64 = term_count("/tmp/hydidx-static-\x00" as *u8, "jshydratedtoken99\x00" as *u8)
82 let sl: i64 = term_count("/tmp/hydidx-static-\x00" as *u8, "loading\x00" as *u8)
83 let hl2: i64 = term_count("/tmp/hydidx-hydr-\x00" as *u8, "loading\x00" as *u8)
84
85 // THE DENOMINATORS. T2 and T4 assert ABSENCE and would BOTH pass on an empty index, so bind them to
86 // the populations they claim to be absent FROM before asserting anything about them.
87 gv_subjects("static text bytes indexed" as *u8, statlen, ctr)
88 gv_subjects("hydrated text bytes indexed" as *u8, hydtlen, ctr)
89
90 // T1 THE PAYOFF: the JS-built token is searchable after hydration.
91 gv_check("T1 hydrated: JS token SEARCHABLE" as *u8, (ch >= 1) as i64, ctr)
92 // T2 a static crawler MISSES it (the whole reason this arc exists).
93 gv_check("T2 static: JS token ABSENT" as *u8, (cs == 0) as i64, ctr)
94 // T3 without this control T2 also passes whenever the static shard is simply broken.
95 gv_check("T3 neg-control: static shard works (finds 'loading')" as *u8, (sl >= 1) as i64, ctr)
96 // T4 hydration REPLACED (not appended): 'loading' is gone from the hydrated doc.
97 gv_check("T4 hydration replaced 'loading'" as *u8, (hl2 == 0) as i64, ctr)
98
99 return gv_verdict("JS-HYDRATE-INDEX-GATE" as *u8, ctr, "JS-built token is indexed and searchable; a static crawler misses it" as *u8)
100}