code wiki / _hdl_build / _wiki_search_gate.nx
_wiki_search_gate.nx source
↩ module page · 214 lines · 11038 B
1// _wiki_search_gate.nx -- THE RANKED-SEARCH GATE (wiki R3).
2//
3// Re-proves, from a REAL run (no fabricated GREEN), that the wiki's search is
4// (1) genuinely BM25-RANKED -- ordered by term density, not mere presence -- and
5// (2) UNIFIED with the no-link-rot archive -- content that lives only in the
6// content-addressed archive (never in the live doc store) is still found.
7//
8// Rows (judged by the WIKISEARCH marker; FAIL any -> RED, never silenced):
9// rank_ok docA mentions "sovereign" 5x, docB 1x (both same length-class).
10// Real BM25 must rank docA STRICTLY ABOVE docB -- assert on the
11// ranked rowid ORDER (ground truth), not a bare rc==const. A
12// presence/AND ranker would tie them; BM25 separates them by tf.
13// archive_ok A page whose body contains the UNIQUE term "zarquontoken" is
14// archived into the wikiarchive store but is NOT in the live doc
15// store. Searching "zarquontoken" must FIND it via the seg_store
16// term index folded in by nx_wbs_gather_archive (kind=ARCHIVE).
17// neg_ok Bogus "zzqnosuchterm" -> 0 hits across doc store AND archive.
18//
19// Determinism: a FRESH archive prefix keyed by the wall-clock epoch (empty store
20// each run => idempotent re-run, Rule 10). The PRODUCTION archive prefix is
21// knowledge/store/wikiarchive- (nx_wiki_archive). nx_sites_daemon UNTOUCHED.
22//
23// Verdict line (stdout + knowledge/status/wiki_search_gate.log):
24// WIKISEARCH results=<n> rank_ok=<0|1> archive_ok=<0|1> neg_ok=<0|1> verdict=GREEN|RED
25//
26// Pure NishiLang, NO SQL, NO .sh/.py/.js, no new .tsv/.conf. license_tier: ORIGINAL
27import "nx_syscalls.nx"
28import "nx_seg_store.nx"
29import "wiki/nx_wiki_archive.nx"
30import "wiki/nx_wiki_index_builder.nx"
31import "wiki/nx_wiki_bm25_search.nx"
32
33const WSG_LOG: *u8 = "knowledge/status/wiki_search_gate.log"
34
35// ---- io helpers ----
36func wsg_w(fd: i64, s: *u8) -> i64 {
37 var n: i64 = 0
38 while s[n] != (0 as u8) { n = n + 1 }
39 sys_write(fd, s, n)
40 return 0
41}
42func wsg_n(fd: i64, v: i64) -> i64 {
43 let bb: *u8 = sys_mmap(28)
44 var m: i64 = v
45 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
46 let t: *u8 = sys_mmap(28)
47 var k: i64 = 0
48 if m == 0 { t[0] = 48 as u8; k = 1 }
49 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
50 var i: i64 = 0
51 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
52 sys_write(fd, bb, k)
53 return 0
54}
55// write to BOTH stdout (gate evidence) and the durable log
56func wsg_w2(lfd: i64, s: *u8) -> i64 { wsg_w(1, s); if lfd >= 0 { wsg_w(lfd, s) } return 0 }
57func wsg_n2(lfd: i64, v: i64) -> i64 { wsg_n(1, v); if lfd >= 0 { wsg_n(lfd, v) } return 0 }
58func wsg_p(s: *u8) -> i64 { wsg_w(1, s); return 0 }
59func wsg_pn(v: i64) -> i64 { wsg_n(1, v); return 0 }
60func wsg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
61
62// concat NUL-terminated s into dst at off; returns new off
63func wsg_cat(dst: *u8, off: i64, s: *u8) -> i64 {
64 var i: i64 = 0
65 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
66 return off + i
67}
68func wsg_catn(dst: *u8, off: i64, v: i64) -> i64 {
69 var m: i64 = v
70 var o: i64 = off
71 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m }
72 let t: *u8 = sys_mmap(28)
73 var k: i64 = 0
74 if m == 0 { t[0] = 48 as u8; k = 1 }
75 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
76 var i: i64 = 0
77 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
78 return o + k
79}
80
81func main() -> i64 {
82 wsg_p("WIKISEARCH-GATE: start (BM25 ranked search + no-rot archive unification)\n" as *u8)
83
84 // ---- FRESH per-run archive prefix: knowledge/store/wikiarchive-search-<epoch>- ----
85 let prefix: *u8 = sys_mmap(256)
86 var po: i64 = 0
87 po = wsg_cat(prefix, po, "knowledge/store/wikiarchive-search-" as *u8)
88 po = wsg_catn(prefix, po, sys_now_realtime_sec())
89 po = wsg_cat(prefix, po, "-" as *u8)
90 prefix[po] = 0 as u8
91 wsg_p(" archive prefix = " as *u8); wsg_p(prefix); wsg_p("\n" as *u8)
92
93 // ===== BUILD a LIVE doc store with DISCRIMINATING term frequencies =====
94 // docA: "sovereign" x5 (substantive) docB: "sovereign" x1 (thin mention),
95 // same length-class so the SEPARATION is driven by tf, not by length. Four
96 // filler docs (docC..docF) do NOT contain "sovereign", so across the 6-doc
97 // corpus "sovereign" is a genuinely DISCRIMINATING term (df=2 of N=6 => the
98 // BM25 IDF stays positive, the regime where tf-density ranking is meaningful
99 // -- not a term so ubiquitous that IDF goes negative). Real BM25, honest
100 // corpus: docA must outrank docB on term density.
101 let store: *NxWikiDocStore = sys_mmap(256) as *NxWikiDocStore
102 let rc_si: i64 = nx_wiki_doc_store_init(store, 16, 65536, 65536, 1048576)
103 if rc_si != 0 { wsg_p(" FATAL doc_store_init rc="); wsg_pn(rc_si); wsg_p("\n" as *u8) }
104
105 let bodyA: *u8 = "sovereign sovereign sovereign sovereign sovereign substrate runtime engine kernel" as *u8
106 let bodyB: *u8 = "sovereign wikipedia aggregator portal directory listing index summary overview page" as *u8
107 let bodyC: *u8 = "weather climate rainfall temperature humidity forecast barometer seasonal pattern chart" as *u8
108 let bodyD: *u8 = "recipe cooking baking flour sugar butter oven timer kitchen utensil mixing whisk bowl" as *u8
109 let bodyE: *u8 = "garden planting soil seed water sunlight harvest compost mulch trowel spade fence trellis" as *u8
110 let bodyF: *u8 = "music melody rhythm harmony tempo chord scale octave instrument string drum flute brass" as *u8
111 let rA: i64 = nx_wiki_doc_store_add(store, "Sovereign Substrate" as *u8, 20, "/wiki/sovereign-substrate" as *u8, 25, bodyA, wsg_slen(bodyA))
112 let rB: i64 = nx_wiki_doc_store_add(store, "Aggregator Portal" as *u8, 17, "/wiki/aggregator-portal" as *u8, 23, bodyB, wsg_slen(bodyB))
113 let rC: i64 = nx_wiki_doc_store_add(store, "Weather" as *u8, 7, "/wiki/weather" as *u8, 13, bodyC, wsg_slen(bodyC))
114 let rD: i64 = nx_wiki_doc_store_add(store, "Recipe" as *u8, 6, "/wiki/recipe" as *u8, 12, bodyD, wsg_slen(bodyD))
115 let rE: i64 = nx_wiki_doc_store_add(store, "Garden" as *u8, 6, "/wiki/garden" as *u8, 12, bodyE, wsg_slen(bodyE))
116 let rF: i64 = nx_wiki_doc_store_add(store, "Music" as *u8, 5, "/wiki/music" as *u8, 11, bodyF, wsg_slen(bodyF))
117 wsg_p(" doc store: docA(rowid="); wsg_pn(rA); wsg_p(",sovereign x5) docB(rowid="); wsg_pn(rB); wsg_p(",sovereign x1) + 4 filler (no 'sovereign'), N="); wsg_pn(nx_wiki_doc_store_count(store)); wsg_p("\n" as *u8)
118
119 // ===== ARCHIVE a page whose body has a UNIQUE term, NOT in the doc store =====
120 // "zarquontoken" appears ONLY in this archived blob. If search finds it, the
121 // archive term index (ss_term) was genuinely folded into ranking.
122 let s_uniq: *u8 = "zarquon-archive-page" as *u8
123 let bodyZ: *u8 = "# Zarquon Archive\nThis archived page contains the unique token zarquontoken nowhere in the live doc store.\n" as *u8
124 let w: *i64 = ss_begin()
125 let cidZ: *u8 = sys_mmap(80)
126 let raz: i64 = war_archive_page(w, s_uniq, bodyZ, wsg_slen(bodyZ), cidZ)
127 let cz: i64 = ss_commit(prefix, w, 0)
128 wsg_p(" archived zarquon page (war rc="); wsg_pn(raz); wsg_p(" commit rc="); wsg_pn(cz); wsg_p(" cid="); wsg_p(cidZ); wsg_p(")\n" as *u8)
129
130 // ===== ranked-result scratch =====
131 let out: *NxWikiRanked = sys_mmap(128) as *NxWikiRanked
132 nx_wiki_ranked_init(out, 64)
133
134 // ----- query "sovereign": expect ranked DOC hits, docA above docB -----
135 let qt1: *i64 = sys_mmap(8 * 2) as *i64
136 let ql1: *i64 = sys_mmap(8 * 2) as *i64
137 qt1[0] = "sovereign" as *u8 as i64
138 ql1[0] = 9
139 nx_wiki_bm25_rank(store, prefix, qt1 as **u8, ql1, 1, out, 20)
140 let n1: i64 = nx_wiki_ranked_count(out)
141 // find the rank POSITION of docA(rowid rA) and docB(rowid rB) among DOC hits
142 var posA: i64 = 0 - 1
143 var posB: i64 = 0 - 1
144 var scoreA: i64 = 0
145 var scoreB: i64 = 0
146 var i: i64 = 0
147 while i < n1 {
148 if nx_wiki_ranked_kind_at(out, i) == NX_WBS_SRC_DOC {
149 let rid: i64 = nx_wiki_ranked_rowid_at(out, i)
150 if rid == rA { posA = i; scoreA = nx_wiki_ranked_score_at(out, i) }
151 if rid == rB { posB = i; scoreB = nx_wiki_ranked_score_at(out, i) }
152 }
153 i = i + 1
154 }
155 // rank_ok: both present, docA strictly ABOVE docB (lower index = higher rank)
156 // AND docA's BM25 score strictly exceeds docB's (density, not presence).
157 var rank_ok: i64 = 0
158 if posA >= 0 { if posB >= 0 { if posA < posB { if scoreA > scoreB { rank_ok = 1 } } } }
159 wsg_p(" query 'sovereign': results="); wsg_pn(n1);
160 wsg_p(" posA="); wsg_pn(posA); wsg_p(" posB="); wsg_pn(posB);
161 wsg_p(" scoreA="); wsg_pn(scoreA); wsg_p(" scoreB="); wsg_pn(scoreB);
162 wsg_p(" rank_ok="); wsg_pn(rank_ok); wsg_p("\n" as *u8)
163
164 // ----- query "zarquontoken": must be FOUND, and via the ARCHIVE -----
165 let qt2: *i64 = sys_mmap(8 * 2) as *i64
166 let ql2: *i64 = sys_mmap(8 * 2) as *i64
167 qt2[0] = "zarquontoken" as *u8 as i64
168 ql2[0] = 12
169 nx_wiki_bm25_rank(store, prefix, qt2 as **u8, ql2, 1, out, 20)
170 let n2: i64 = nx_wiki_ranked_count(out)
171 // archive_ok: at least one hit, and it is an ARCHIVE-kind row (proves the
172 // seg_store term index supplied content the live doc store never had).
173 var archive_ok: i64 = 0
174 var arch_hits: i64 = 0
175 i = 0
176 while i < n2 {
177 if nx_wiki_ranked_kind_at(out, i) == NX_WBS_SRC_ARCHIVE { arch_hits = arch_hits + 1 }
178 i = i + 1
179 }
180 if arch_hits >= 1 { archive_ok = 1 }
181 wsg_p(" query 'zarquontoken': results="); wsg_pn(n2); wsg_p(" archive_hits="); wsg_pn(arch_hits); wsg_p(" archive_ok="); wsg_pn(archive_ok); wsg_p("\n" as *u8)
182
183 // ----- query "zzqnosuchterm": 0 hits anywhere -----
184 let qt3: *i64 = sys_mmap(8 * 2) as *i64
185 let ql3: *i64 = sys_mmap(8 * 2) as *i64
186 qt3[0] = "zzqnosuchterm" as *u8 as i64
187 ql3[0] = 13
188 nx_wiki_bm25_rank(store, prefix, qt3 as **u8, ql3, 1, out, 20)
189 let n3: i64 = nx_wiki_ranked_count(out)
190 var neg_ok: i64 = 0
191 if n3 == 0 { neg_ok = 1 }
192 wsg_p(" query 'zzqnosuchterm': results="); wsg_pn(n3); wsg_p(" neg_ok="); wsg_pn(neg_ok); wsg_p("\n" as *u8)
193
194 // ===== VERDICT (judged by the marker; results=<n> reports the ranked query) =====
195 var green: i64 = 1
196 if rank_ok != 1 { green = 0 }
197 if archive_ok != 1 { green = 0 }
198 if neg_ok != 1 { green = 0 }
199
200 let lfd: i64 = sys_openat_append(WSG_LOG, 420)
201 wsg_w2(lfd, "WIKISEARCH results=" as *u8); wsg_n2(lfd, n1)
202 wsg_w2(lfd, " rank_ok=" as *u8); wsg_n2(lfd, rank_ok)
203 wsg_w2(lfd, " archive_ok=" as *u8); wsg_n2(lfd, archive_ok)
204 wsg_w2(lfd, " neg_ok=" as *u8); wsg_n2(lfd, neg_ok)
205 if green == 1 { wsg_w2(lfd, " verdict=GREEN\n" as *u8) } else { wsg_w2(lfd, " verdict=RED\n" as *u8) }
206 if lfd >= 0 { sys_close(lfd) }
207
208 if green == 1 {
209 sys_exit(0)
210 return 0
211 }
212 sys_exit(1)
213 return 1
214}