code wiki / _hdl_build / nx_qrels_bench.nx

nx_qrels_bench.nx source

↩ module page · 773 lines · 45601 B

1// nx_qrels_bench.nx -- THE PER-DOCUMENT RELEVANCE RULER (Q7 runner, 2026-09-04). 2// 3// WHY IT EXISTS. The estate's incumbent web ruler judges by EXPECTED HOST, and that is not a detail -- 4// it is why it could not see any defect found on 2026-09-04. "Icing (aeronautics)" at rank 1 for the 5// query "ice cream" sits on en.wikipedia.org and therefore scored a PERFECT hit@1, while the stored 6// document does not contain the word "cream" anywhere. A HOST IS NOT AN ANSWER. This runner scores 7// against knowledge/search_qrels.conf, whose judgements are per URL. 8// 9// COMPOSED, NOT REIMPLEMENTED, ON BOTH SIDES: 10// retrieval -- dss_search over the SAME code path /search serves, so the number describes the live 11// engine rather than a reimplementation of it. Deliberately NO HTTP: an organ that fetches 12// its own daemon measures the transport too, and would queue behind the serve it is grading. 13// metrics -- nx_ir_eval (ie_ndcg_at_k, ie_mrr, ie_hits_at_k, ie_precision_at_k, ie_ap), the estate's 14// Q16.16 integer-exact floor. A second metric implementation is the duplicate-ruler defect. 15// 16// SCOPE, INHERITED FROM THE QRELS FILE AND NOT WEAKENED HERE: that set is a BOOTSTRAP -- its candidates 17// were pooled from the engine's own required-term arm, so it measures whether known-good documents rank UP 18// and it CANNOT prove recall over documents that arm never retrieved. The recall figure is therefore recall 19// over the JUDGED POOL, and it is emitted as judged_recall_permil so nobody reads it as the other thing. 20// 21// GRADE 0 IS A JUDGEMENT, NOT A GAP. A result absent from the file scores 0 deliberately: the file records 22// every document examined per query, so "unlisted" means "examined and judged irrelevant" -- which is 23// exactly how a stem-variant-only match (icing for ice, warring for wars) is meant to score. 24import "nx_docportal_search_seg.nx" 25import "nx_ir_eval.nx" 26import "nx_gate_verdict.nx" 27 28const QB_PATH: *u8 = "knowledge/search_qrels.conf" 29const QB_PATH_UP: *u8 = "../knowledge/search_qrels.conf" // same file seen from a buildroot CWD 30const QB_DOMAIN: *u8 = "web" 31const QB_MAXQ: i64 = 64 32const QB_MAXREL: i64 = 1024 33const QB_K: i64 = 10 // DSS_MAXR is 30, so scoring @5 would discard two thirds of the page 34const QB_TOPN: i64 = 30 35const QB_QBUF: i64 = 128 36const QB_UBUF: i64 = 512 37const QB_PATHCAP: i64 = 1024 38const QB_BOX: i64 = 16 39const QB_PIPE: i64 = 124 40const QB_NL: i64 = 10 41const QB_CR: i64 = 13 // CRLF-generated data files leave this as the last byte of a field 42const QB_HASH: i64 = 35 43const QB_D0: i64 = 48 44const QB_D9: i64 = 57 45const QB_B10: i64 = 10 46const QB_PERMIL: i64 = 1000 47const QB_Q16: i64 = 65536 // nx_ir_eval returns Q16.16 fixed point, NOT permil (mrr=65536 == 1.0) 48const QB_FAMCAP: i64 = 32 // a family is a COARSE signal class (lexical/human/neural), not a label 49const QB_LC_AUTO: i64 = 0 50const QB_LC_HUMAN: i64 = 1 51const QB_SP: i64 = 32 52const QB_CONTENT_MIN: i64 = 3 // S4: a CONTENT term. 1-2 char tokens in these queries are 53 // function words, and counting them would let a rewrite score 54 // alignment for preserving "of" while dropping "carburettor". 55const QB_CROSSOVER_TOKENS: i64 = 10000000 // S1: the published BM25 scale crossover, in corpus tokens 56const QB_EPOCH_FLOOR: i64 = 1750000000 // S2: below this is not a date, it is an UNSET CLOCK. A run 57 // stamped 0 must fail benchmark identity rather than publish a 58 // score that claims to have been taken at the epoch. 59 60func qb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 61func qb_eqz(a: *u8, b: *u8) -> i64 { 62 var i: i64 = 0 63 var ok: i64 = 1 64 var go: i64 = 1 65 while go == 1 { 66 if a[i] == (0 as u8) { go = 0 } else { 67 if a[i] != b[i] { ok = 0; go = 0 } else { i = i + 1 } 68 } 69 } 70 if ok == 1 { if b[i] != (0 as u8) { ok = 0 } } 71 return ok 72} 73func qb_cpn(dst: *u8, cap: i64, src: *u8, n: i64) -> i64 { 74 var k: i64 = n 75 if k > cap - 1 { k = cap - 1 } 76 var i: i64 = 0 77 while i < k { dst[i] = src[i]; i = i + 1 } 78 dst[k] = 0 as u8 79 return k 80} 81// END OF LINE via an explicit flag, never by clobbering the cursor: a loop that exits by overwriting its 82// own index cannot also report where it stopped, and that idiom has cost this estate four defects. 83// END OF LINE, CR-TRIMMED. The CR is not a nicety: the judged set was generated on Windows, so every 84// record ends \r\n, and a scanner that stops only at \n leaves the CR as the LAST BYTE OF THE URL FIELD. 85// MEASURED, and it is the reason this ruler read a perfect zero on its first green run: retrieved and 86// judged urls printed CHARACTER-FOR-CHARACTER IDENTICAL in the diagnostic and compared UNEQUAL, because 87// gv_puts renders a trailing CR as a carriage return -- invisible in a log, decisive in a memcmp. The 88// index fields were all correct (rqi0=0 rgr0=2 qi=0 nr=174) and only streq=0 disagreed, which is what 89// localised it to the string and not to the bookkeeping. 90// ★★★★★★TWO STRINGS THAT PRINT IDENTICALLY CAN STILL DIFFER, AND A DIAGNOSTIC THAT ONLY PRINTS THEM WILL 91// AGREE WITH THE WRONG ANSWER -- PRINT THE LENGTH AND THE COMPARISON'S OWN VERDICT, NOT JUST THE VALUE. 92// Trimming here rather than at the generator is deliberate: a data file may arrive from any host, and a 93// parser that tolerates CRLF cannot be broken again by whoever next edits the set on a different OS. 94func qb_eol(buf: *u8, n: i64, i: i64) -> i64 { 95 var e: i64 = i 96 var go: i64 = 1 97 while go == 1 { 98 if e >= n { go = 0 } else { if buf[e] == (QB_NL as u8) { go = 0 } else { e = e + 1 } } 99 } 100 if e > i { if buf[e - 1] == (QB_CR as u8) { e = e - 1 } } 101 return e 102} 103// offset of the next '|' at or after i, bounded by e 104func qb_pipe(buf: *u8, e: i64, i: i64) -> i64 { 105 var p: i64 = i 106 var go: i64 = 1 107 while go == 1 { 108 if p >= e { go = 0 } else { if buf[p] == (QB_PIPE as u8) { go = 0 } else { p = p + 1 } } 109 } 110 return p 111} 112func qb_atoi(buf: *u8, s: i64, e: i64) -> i64 { 113 var v: i64 = 0 114 var i: i64 = s 115 while i < e { 116 let c: i64 = buf[i] as i64 117 if c >= QB_D0 { if c <= QB_D9 { v = v * QB_B10 + (c - QB_D0) } } 118 i = i + 1 119 } 120 return v 121} 122// WHOLE-WORD CONTAINMENT. Needed by S4 because "dog" must not be satisfied by "dogs": the whole point of 123// a lexical alignment figure is that it counts the terms that genuinely survived, and a substring test 124// would score a rewrite for a word it did not keep. Boundaries are non-alphanumeric on both sides. 125func qb_isword(c: i64) -> i64 { 126 if c >= 48 { if c <= 57 { return 1 } } 127 if c >= 65 { if c <= 90 { return 1 } } 128 if c >= 97 { if c <= 122 { return 1 } } 129 return 0 130} 131func qb_hasword(hay: *u8, needle: *u8) -> i64 { 132 let hn: i64 = qb_slen(hay) 133 let nn: i64 = qb_slen(needle) 134 if nn == 0 { return 0 } 135 if nn > hn { return 0 } 136 var i: i64 = 0 137 while i <= hn - nn { 138 var j: i64 = 0 139 var same: i64 = 1 140 while j < nn { if hay[i + j] != needle[j] { same = 0; j = nn } else { j = j + 1 } } 141 if same == 1 { 142 var lok: i64 = 1 143 var rok: i64 = 1 144 if i > 0 { if qb_isword(hay[i - 1] as i64) == 1 { lok = 0 } } 145 if i + nn < hn { if qb_isword(hay[i + nn] as i64) == 1 { rok = 0 } } 146 if lok == 1 { if rok == 1 { return 1 } } 147 } 148 i = i + 1 149 } 150 return 0 151} 152// S1 CONTRACT SYMBOL cs_crossover -- WHICH SIDE OF THE PUBLISHED BM25 SCALE CROSSOVER THIS CORPUS SITS ON. 153// The finding it encodes: BM25 overtakes agentic and dense retrieval at roughly 10M corpus tokens and 154// leads at every larger tier. That is load-bearing for this estate, because it says a lexical engine is 155// the RIGHT default at our scale rather than a placeholder for a model we have not built. 156// THREE STATES, NOT TWO. -1 is UNDECIDABLE and it is returned whenever either input is unmeasured, so an 157// unknown corpus size can never be silently reported as BELOW -- abstain, never acquit. The threshold is 158// a parameter rather than a constant precisely so the negative control can plant a corpus on either side. 159// ⚠SCOPE CAVEAT, CARRIED BESIDE THE NUMBER AS THE CONTRACT REQUIRES: the crossover is reported by its 160// authors over their own corpora and task mix; it is a published landmark, not a law of this index. 161// cs_crossover LIVES IN nx_intlog.nx since 2026-09-14 (the S1 contract symbol, reached through nx_docportal_search_seg's 162// import) with the same three states; a second copy here collided with it on rebuild -- ONE ruler. 163// S6 CONTRACT SYMBOL qb_tier -- THE LABEL TIER OF A JUDGED SET. 1 when the set carries at least one HUMAN 164// label, 0 when it is auto-only. It is deliberately SEPARATE from the publish decision, because the tier 165// is a property of the DATA while publishability also depends on WHICH RANKER this run measured; folding 166// them would make a property of the labels depend on the subject under test. It is on the critical path, 167// not a name written to satisfy a watch contract: the publish decision below consumes its return value. 168func qb_tier(rlc: *i64, nr: i64) -> i64 { 169 var i: i64 = 0 170 while i < nr { 171 if rlc[i] == QB_LC_HUMAN { return 1 } 172 i = i + 1 173 } 174 return 0 175} 176// S2 CONTRACT SYMBOL qb_benchver -- BENCHMARK IDENTITY. A retrieval score without the benchmark it was 177// taken on and the date it was taken is not comparable with anything, and it is the shape in which stale 178// numbers survive: "nDCG 581" outlives the index, the qrels revision and the scorer that produced it. 179// Returns 1 only when BOTH are present. The epoch floor is what makes this non-vacuous -- an unset clock 180// reads as 0 and would otherwise pass as "a date". 181func qb_benchver(bench: *u8, epoch: i64) -> i64 { 182 if qb_slen(bench) == 0 { return 0 } 183 if epoch < QB_EPOCH_FLOOR { return 0 } 184 return 1 185} 186// S4 CONTRACT SYMBOL qb_lexalign -- LEXICAL ALIGNMENT of a rewrite, in permil, computed with NO MODEL. 187// Of the original query CONTENT terms, what fraction survived into the rewrite AND actually occurs in a 188// judged-relevant document? Both conjuncts are required on purpose: surviving into the rewrite alone 189// rewards a rewrite that keeps a term the corpus does not contain, and occurring in the document alone 190// rewards a rewrite that dropped it. Published evidence for why this is gated at all: LLM query rewriting 191// made the STRONGEST model worse on every metric with every technique, so a rewrite must EARN its place. 192// Returns 0 when there are no content terms -- a figure over an empty denominator is not a measurement. 193func qb_lexalign(orig: *u8, rewrite: *u8, doctext: *u8) -> i64 { 194 let n: i64 = qb_slen(orig) 195 var total: i64 = 0 196 var kept: i64 = 0 197 var i: i64 = 0 198 while i < n { 199 // SEPARATOR SKIP AND TOKEN SCAN VIA AN EXPLICIT FLAG, NEVER A SENTINEL WRITTEN INTO THE CURSOR. 200 // The first cut of this function exited both loops with i = n + n and then subtracted n back -- 201 // which is the idiom this estate has recorded FOUR separate defects against, because a loop that 202 // exits by clobbering its own index cannot also report where it stopped. qb_eol above already 203 // uses the flag form; matching it is the whole point of having one. 204 var go: i64 = 1 205 while go == 1 { 206 if i >= n { go = 0 } else { if orig[i] == (QB_SP as u8) { i = i + 1 } else { go = 0 } } 207 } 208 var e: i64 = i 209 go = 1 210 while go == 1 { 211 if e >= n { go = 0 } else { if orig[e] == (QB_SP as u8) { go = 0 } else { e = e + 1 } } 212 } 213 if e > i { 214 if e - i >= QB_CONTENT_MIN { 215 let tok: *u8 = sys_mmap(QB_QBUF) 216 qb_cpn(tok, QB_QBUF, ((orig as i64) + i) as *u8, e - i) 217 total = total + 1 218 if qb_hasword(rewrite, tok) == 1 { 219 if qb_hasword(doctext, tok) == 1 { kept = kept + 1 } 220 } 221 sys_munmap(tok, QB_QBUF) 222 } 223 } 224 i = e + 1 225 } 226 if total == 0 { return 0 } 227 return (kept * QB_PERMIL) / total 228} 229// S6 (2026-09-04): THE PUBLISH DECISION, DELIBERATELY A PURE FUNCTION OF ALREADY-MEASURED VALUES so a 230// tooth can drive it with PLANTED inputs without touching the index, the conf or the network -- the 231// controls below therefore cannot pass while the live run takes some other path, because there is only 232// one of it. It answers ONE question: may this run publish an ABSOLUTE relevance metric? It says no in 233// two cases, and the caller NAMES which one fired. 234// CIRCULAR judge_family == ranker_family. A judged set graded by the same signal class the ranker 235// ranks by measures TRANSCRIPTION, not relevance, and it flatters precisely the change that 236// leans hardest on that signal. MEASURED ON THIS ESTATE 2026-09-04, which is why this exists: 237// the set was graded "every query term present as a whole word" -- lexical -- against a 238// lexical BM25 ranker. It graded a porn-spam page RELEVANT for the query "hot dog" and a 239// Streamlit tutorial HIGHLY relevant, and a scorer change that raised its mean by 28 permil 240// simultaneously made a user-visible result worse. The number rose while the engine got 241// worse. That is what circularity buys, and no amount of extra queries fixes it. 242// AUTO-ONLY the set carries no human label at all. An automatic judgement is a research object, not a 243// substitute for a human qrel, so an absolute figure drawn only from auto labels is withheld. 244// THE FAMILY IS COARSE ON PURPOSE. "lexical-termpresence" and "lexical-bm25" are different STRINGS and the 245// same FAMILY, so an exact match on the full label would have cleared the very case this was built to 246// catch. Circularity is a property of the signal class, never of the spelling. 247func qb_publish_ok(judgefam: *u8, rankerfam: *u8, tier: i64) -> i64 { 248 if qb_slen(judgefam) == 0 { return 0 } 249 if qb_slen(rankerfam) == 0 { return 0 } 250 if qb_eqz(judgefam, rankerfam) == 1 { return 0 } 251 if tier <= 0 { return 0 } 252 return 1 253} 254// THE PARSER. Records: q|<query>|<class>|<pool> and rel|<query>|<grade>|<url>[|<label_class>] 255// plus the S6 provenance header rows judge|<family> and ranker|<family>. 256// Comments and blanks are skipped AND COUNTED -- a parser that silently drops rows turns a shrinking 257// judged set into a rising score, which is the flattering direction nobody audits. 258func qb_load(buf: *u8, n: i64, qtext: *u8, nq: *i64, 259 rqi: *i64, rgr: *i64, rurl: *u8, rlc: *i64, nr: *i64, skipped: *i64, 260 judgefam: *u8, rankerfam: *u8, benchstr: *u8) -> i64 { 261 nq[0] = 0 262 nr[0] = 0 263 skipped[0] = 0 264 judgefam[0] = 0 as u8 265 rankerfam[0] = 0 as u8 266 benchstr[0] = 0 as u8 267 var i: i64 = 0 268 while i < n { 269 let e: i64 = qb_eol(buf, n, i) 270 if e > i { 271 if buf[i] != (QB_HASH as u8) { 272 let p1: i64 = qb_pipe(buf, e, i) 273 if p1 < e { 274 let kind: *u8 = sys_mmap(QB_BOX) 275 qb_cpn(kind, QB_BOX, ((buf as i64) + i) as *u8, p1 - i) 276 let p2: i64 = qb_pipe(buf, e, p1 + 1) 277 if qb_eqz(kind, "q") == 1 { 278 if nq[0] < QB_MAXQ { 279 let dst: *u8 = ((qtext as i64) + nq[0] * QB_QBUF) as *u8 280 qb_cpn(dst, QB_QBUF, ((buf as i64) + p1 + 1) as *u8, p2 - p1 - 1) 281 nq[0] = nq[0] + 1 282 } 283 } else { if qb_eqz(kind, "rel") == 1 { 284 let p3: i64 = qb_pipe(buf, e, p2 + 1) 285 let p4: i64 = qb_pipe(buf, e, p3 + 1) 286 if nr[0] < QB_MAXREL { 287 // resolve the query name to its index; a rel row for an unknown query is 288 // DROPPED AND COUNTED rather than attached to whatever was parsed last. 289 let qn: *u8 = sys_mmap(QB_QBUF) 290 qb_cpn(qn, QB_QBUF, ((buf as i64) + p1 + 1) as *u8, p2 - p1 - 1) 291 var qi: i64 = 0 - 1 292 var t: i64 = 0 293 while t < nq[0] { 294 if qb_eqz(qn, ((qtext as i64) + t * QB_QBUF) as *u8) == 1 { qi = t } 295 t = t + 1 296 } 297 sys_munmap(qn, QB_QBUF) 298 if qi >= 0 { 299 rqi[nr[0]] = qi 300 rgr[nr[0]] = qb_atoi(buf, p2 + 1, p3) 301 qb_cpn(((rurl as i64) + nr[0] * QB_UBUF) as *u8, QB_UBUF, 302 ((buf as i64) + p3 + 1) as *u8, p4 - p3 - 1) 303 // LABEL CLASS is the optional 5th field and ABSENT MEANS auto. That 304 // default is a MEASUREMENT, not a convenience: every row in the shipped 305 // set was emitted by a lexical rule, so auto is what those rows ARE. 306 // p4 == e when the field is absent, so the url extent above is unchanged 307 // for every pre-existing row -- this is additive, per the contract rule. 308 var lc: i64 = QB_LC_AUTO 309 if p4 < e { 310 let lcb: *u8 = sys_mmap(QB_BOX) 311 qb_cpn(lcb, QB_BOX, ((buf as i64) + p4 + 1) as *u8, e - p4 - 1) 312 if qb_eqz(lcb, "human") == 1 { lc = QB_LC_HUMAN } 313 sys_munmap(lcb, QB_BOX) 314 } 315 rlc[nr[0]] = lc 316 nr[0] = nr[0] + 1 317 } else { skipped[0] = skipped[0] + 1 } 318 } 319 } else { if qb_eqz(kind, "judge") == 1 { 320 qb_cpn(judgefam, QB_FAMCAP, ((buf as i64) + p1 + 1) as *u8, p2 - p1 - 1) 321 } else { if qb_eqz(kind, "ranker") == 1 { 322 qb_cpn(rankerfam, QB_FAMCAP, ((buf as i64) + p1 + 1) as *u8, p2 - p1 - 1) 323 } else { if qb_eqz(kind, "bench") == 1 { 324 qb_cpn(benchstr, QB_QBUF, ((buf as i64) + p1 + 1) as *u8, p2 - p1 - 1) 325 } else { skipped[0] = skipped[0] + 1 } } } } } 326 sys_munmap(kind, QB_BOX) 327 } else { skipped[0] = skipped[0] + 1 } 328 } 329 } 330 i = e + 1 331 } 332 return 0 333} 334// grade for (query index, url), 0 when unlisted -- which the header explains is a JUDGEMENT 335func qb_grade(qi: i64, url: *u8, rqi: *i64, rgr: *i64, rurl: *u8, nr: i64) -> i64 { 336 var i: i64 = 0 337 var g: i64 = 0 338 while i < nr { 339 if rqi[i] == qi { 340 if qb_eqz(url, ((rurl as i64) + i * QB_UBUF) as *u8) == 1 { g = rgr[i] } 341 } 342 i = i + 1 343 } 344 return g 345} 346static qb_mode_ablate: i64 347// THE SCORING ARM, EXTRACTED 2026-09-04 SO THERE IS EXACTLY ONE OF IT. S3 needs the judged set scored 348// TWICE -- once with stem expansion enabled and once without -- and the only honest way to compare two 349// arms is to run the SAME ruler over both. A second copy of this loop would be the duplicate-ruler defect 350// in its purest form: two scorers that agree today and drift apart on the first edit anybody forgets to 351// mirror. out[] carries the five aggregates the caller needs; the per-query lines are printed by design, 352// because the contract requires BOTH ARMS TO PRINT THEIR OWN NUMBERS rather than only a verdict. 353func qb_arm(h: *i64, qtext: *u8, nq: i64, rqi: *i64, rgr: *i64, rurl: *u8, nr: i64, out: *i64) -> i64 { 354 let cids: *i64 = sys_mmap(QB_TOPN * 8) as *i64 355 let scores: *i64 = sys_mmap(QB_TOPN * 8) as *i64 356 let gains: *i64 = sys_mmap(QB_TOPN * 8) as *i64 357 let rels: *i64 = sys_mmap(QB_TOPN * 8) as *i64 358 let ukey: *u8 = sys_mmap(QB_PATHCAP) 359 let up: *i64 = sys_mmap(QB_BOX) as *i64 360 let ul: *i64 = sys_mmap(QB_BOX) as *i64 361 var sum_ndcg: i64 = 0 362 var sum_mrr: i64 = 0 363 var sum_p: i64 = 0 364 var scored: i64 = 0 365 var rank1: i64 = 0 366 var qi: i64 = 0 367 while qi < nq { 368 let q: *u8 = ((qtext as i64) + qi * QB_QBUF) as *u8 369 let n: i64 = dss_search(QB_DOMAIN, q, qb_slen(q), cids, scores, QB_TOPN) 370 var j: i64 = 0 371 while j < QB_TOPN { gains[j] = 0; rels[j] = 0; j = j + 1 } 372 var got: i64 = 0 373 if n > 0 { 374 var r: i64 = 0 375 while r < n { 376 dss_mkurlkey(cids[r], ukey) 377 var g: i64 = 0 378 if ss_hget(h, ukey, up, ul) == 1 { if ul[0] > 0 { 379 let ub: *u8 = sys_mmap(QB_UBUF) 380 qb_cpn(ub, QB_UBUF, up[0] as *u8, ul[0]) 381 g = qb_grade(qi, ub, rqi, rgr, rurl, nr) 382 // DIAGNOSTIC, and it exists because every gain came back 0 on the first green run: 383 // 17 queries retrieved 30 results each and NOT ONE url matched a judged url, which is 384 // either a real miss or a string-shape mismatch, and a verdict cannot tell those apart. 385 // ★PRINT THE VALUES, NOT JUST PASS/FAIL -- a gate that reports a boolean cannot say why. 386 // Bounded to the first query's first two results so it cannot flood a beat log. 387 if qi == 0 { if r < 2 { 388 gv_puts(" DIAG retrieved[") 389 gv_num(r) 390 gv_puts("] len=") 391 gv_num(ul[0]) 392 gv_puts(" url=") 393 gv_puts(ub) 394 gv_puts("\n DIAG judged[0] url=") 395 gv_puts(rurl) 396 // The two urls printed IDENTICAL and the grade was still 0, so the strings are not 397 // the defect and the index fields are. Print them: rqi[0] is the query this row was 398 // bound to at parse time, rgr[0] its grade, and streq the comparison's own answer. 399 gv_puts("\n DIAG rqi0=") 400 gv_num(rqi[0]) 401 gv_puts(" rgr0=") 402 gv_num(rgr[0]) 403 gv_puts(" qi=") 404 gv_num(qi) 405 gv_puts(" nr=") 406 gv_num(nr) 407 gv_puts(" streq=") 408 gv_num(qb_eqz(ub, rurl)) 409 gv_puts(" grade=") 410 gv_num(g) 411 gv_puts("\n") 412 } } 413 sys_munmap(ub, QB_UBUF) 414 } } 415 gains[r] = g 416 if g > 0 { rels[r] = 1 } 417 r = r + 1 418 } 419 got = n 420 } 421 if got > 0 { 422 let nd: i64 = ie_ndcg_at_k(gains, got, QB_K) 423 let mr: i64 = ie_mrr(rels, got) 424 let pr: i64 = ie_precision_at_k(rels, got, QB_K) 425 sum_ndcg = sum_ndcg + nd 426 sum_mrr = sum_mrr + mr 427 sum_p = sum_p + pr 428 scored = scored + 1 429 if gains[0] > 0 { rank1 = rank1 + 1 } 430 gv_puts(" q=") 431 gv_puts(q) 432 gv_puts(" n=") 433 gv_num(got) 434 gv_puts(" ndcg=") 435 gv_num(nd) 436 gv_puts(" mrr=") 437 gv_num(mr) 438 gv_puts(" p@k=") 439 gv_num(pr) 440 gv_puts(" g1=") 441 gv_num(gains[0]) 442 gv_puts("\n") 443 } else { 444 // A QUERY THAT RETRIEVED NOTHING IS NOT A ZERO, IT IS AN ABSTENTION: averaging it in would 445 // let an index outage read as a relevance regression, which sends the next reader at the 446 // wrong subject entirely. 447 gv_puts(" q=") 448 gv_puts(q) 449 gv_puts(" NO-RESULTS -- excluded from the mean, not scored 0\n") 450 } 451 qi = qi + 1 452 } 453 out[0] = sum_ndcg 454 out[1] = sum_mrr 455 out[2] = sum_p 456 out[3] = scored 457 out[4] = rank1 458 return scored 459} 460// S3 CONTRACT SYMBOL qb_expand_ab -- THE STEM-EXPANSION ABLATION. The estate spent a day repairing the 461// ACCOUNTING of query-time stem expansion without once asking whether expanding beats not expanding. 462// Published evidence says that question must be asked: query rewriting made the strongest model WORSE on 463// every metric with every technique, so a rewrite has to EARN its wiring. 464// RETURNS: 1 expansion wins, 0 expansion loses, 2 tie, -1 undecidable (an arm scored nothing). 465// ⚠THE ARMS SHARE ONE RULER AND ONE LABEL SET, so this comparison is sound even while the ABSOLUTE 466// figures from that set are unpublishable under S6 -- a difference over one biased ruler is still a 467// difference. The bias direction is stated in the caller: a lexical judge under-credits exactly the 468// non-whole-word variants expansion exists to match, so this test is HARSH on expansion by construction. 469func qb_expand_ab(h: *i64, qtext: *u8, nq: i64, rqi: *i64, rgr: *i64, rurl: *u8, nr: i64, 470 outA: *i64, outB: *i64) -> i64 { 471 dss_set_expand_off(0) 472 qb_arm(h, qtext, nq, rqi, rgr, rurl, nr, outA) 473 dss_set_expand_off(1) 474 qb_arm(h, qtext, nq, rqi, rgr, rurl, nr, outB) 475 dss_set_expand_off(0) 476 if outA[3] <= 0 { return 0 - 1 } 477 if outB[3] <= 0 { return 0 - 1 } 478 let a: i64 = (outA[0] * QB_PERMIL) / (outA[3] * QB_Q16) 479 let b: i64 = (outB[0] * QB_PERMIL) / (outB[3] * QB_Q16) 480 if a > b { return 1 } 481 if a < b { return 0 } 482 return 2 483} 484func qb_main() -> i64 { 485 gv_head("nx_qrels_bench -- per-DOCUMENT relevance over the live index, scored by the estate's own nx_ir_eval") 486 let ctr: *i64 = gv_ctr() 487 let szb: *i64 = sys_mmap(QB_BOX) as *i64 488 // TWO-ROOT RESOLVE, AND IT ANNOUNCES WHICH ONE ANSWERED. A bare knowledge/ path is CWD-RELATIVE, and 489 // this estate runs the same elf from two different working directories: the build/run lane chdirs into 490 // buildroot/ while the daemons run from the nishihost root, so the IDENTICAL literal names two 491 // different trees depending on who forked it. MEASURED ON THIS ORGAN'S FIRST RUN: it compiled, ran, 492 // and returned passed 0/1 RED at this exact tooth because knowledge/search_qrels.conf resolved under 493 // buildroot/ where the file does not exist. ★A RESOLVER THAT RETURNS BYTES WITHOUT SAYING WHERE THEY 494 // CAME FROM REPRODUCES THE ORIGINAL DEFECT ONE LAYER UP, so qrels_root is emitted with every run and 495 // a MISS is reported as a miss rather than as an empty judged set scoring a perfect zero. 496 // ALIGN THE CWD ONCE, DO NOT RESOLVE EACH PATH SEPARATELY. The first cut read the qrels from a second 497 // candidate root and left everything else alone -- and the very next tooth, web-shard-opened, FAILED 498 // for the SAME reason, because dss_prefix builds the seg-store path relative to the CWD too. Patching 499 // one literal would have fixed the symptom I could see and left the identical defect in every path 500 // this organ touches. ★A PER-PATH WORKAROUND FOR A CWD PROBLEM IS THE DUPLICATE-RULER DEFECT WEARING A 501 // FILENAME: the wrong thing is the working directory, so correct THAT and every path follows. 502 // The probe is the qrels file itself because it is the one artifact this organ cannot run without. 503 var buf: *u8 = sys_read_file(QB_PATH, szb) 504 var whichroot: i64 = 1 505 if (buf as i64) == 0 { 506 // started under buildroot/ (the build-and-run lane chdirs there); the serving root is one up, 507 // and it carries BOTH knowledge/search_qrels.conf AND knowledge/store/, so one chdir aligns them. 508 sys_chdir("..") 509 buf = sys_read_file(QB_PATH, szb) 510 whichroot = 2 511 } 512 if (buf as i64) == 0 { whichroot = 0 } 513 gv_kv("qrels_root", whichroot) 514 gv_kv("qrels_bytes", szb[0]) 515 gv_check("qrels-file-readable", (buf as i64) != 0, ctr) 516 if (buf as i64) == 0 { return gv_verdict("nx_qrels_bench", ctr, "the judged set is the subject; without it this organ has nothing to measure and says so rather than scoring an empty set") } 517 518 let qtext: *u8 = sys_mmap(QB_MAXQ * QB_QBUF) 519 let rqi: *i64 = sys_mmap(QB_MAXREL * 8) as *i64 520 let rgr: *i64 = sys_mmap(QB_MAXREL * 8) as *i64 521 let rurl: *u8 = sys_mmap(QB_MAXREL * QB_UBUF) 522 let nqb: *i64 = sys_mmap(QB_BOX) as *i64 523 let nrb: *i64 = sys_mmap(QB_BOX) as *i64 524 let skb: *i64 = sys_mmap(QB_BOX) as *i64 525 let rlc: *i64 = sys_mmap(QB_MAXREL * 8) as *i64 526 let judgefam: *u8 = sys_mmap(QB_FAMCAP) 527 let rankerfam: *u8 = sys_mmap(QB_FAMCAP) 528 let benchstr: *u8 = sys_mmap(QB_QBUF) 529 qb_load(buf, szb[0], qtext, nqb, rqi, rgr, rurl, rlc, nrb, skb, judgefam, rankerfam, benchstr) 530 let nq: i64 = nqb[0] 531 let nr: i64 = nrb[0] 532 gv_kv("queries_loaded", nq) 533 gv_kv("judgements_loaded", nr) 534 gv_kv("rows_skipped", skb[0]) 535 // BIND THE AGGREGATE TO ITS DENOMINATOR: a tooth that passes on the empty set is not a tooth. 536 gv_check("qrels-carries-queries", nq > 0, ctr) 537 gv_check("qrels-carries-judgements", nr > 0, ctr) 538 539 // S6 LABEL PROVENANCE. Counted, printed, and PARTITIONED -- a class census whose parts do not sum to 540 // the population is a leak, and here the leak would be a silently unclassified label deciding whether 541 // an absolute figure may be published at all. 542 var nhuman: i64 = 0 543 var nauto: i64 = 0 544 var li: i64 = 0 545 while li < nr { 546 if rlc[li] == QB_LC_HUMAN { nhuman = nhuman + 1 } else { nauto = nauto + 1 } 547 li = li + 1 548 } 549 gv_puts(" judge_family=") 550 gv_puts(judgefam) 551 gv_puts(" ranker_family=") 552 gv_puts(rankerfam) 553 gv_puts(" 554") 555 gv_kv("labels_human", nhuman) 556 gv_kv("labels_auto", nauto) 557 let tier: i64 = qb_tier(rlc, nr) 558 gv_kv("label_tier", tier) 559 // S2 BENCHMARK IDENTITY. The clock is read from the host and NOT trusted blindly: an unset clock 560 // reads 0 and must fail identity rather than stamp a score with the epoch. 561 let ts: *i64 = sys_mmap(QB_BOX) as *i64 562 ts[0] = 0 563 sys_clock_gettime_real(ts) 564 let runepoch: i64 = ts[0] 565 gv_puts(" benchmark=") 566 gv_puts(benchstr) 567 gv_puts(" 568") 569 gv_kv("run_epoch", runepoch) 570 let bver: i64 = qb_benchver(benchstr, runepoch) 571 gv_kv("benchmark_identity_ok", bver) 572 var publish: i64 = 0 573 if qb_publish_ok(judgefam, rankerfam, tier) == 1 { if bver == 1 { publish = 1 } } 574 gv_kv("publish_absolute_metrics", publish) 575 gv_check("judge-family-declared", qb_slen(judgefam) > 0, ctr) 576 gv_check("ranker-family-declared", qb_slen(rankerfam) > 0, ctr) 577 gv_check("label-class-partition-sums", nhuman + nauto == nr, ctr) 578 // THE CONTROLS RUN THROUGH THE SAME PURE FUNCTION THE LIVE DECISION USES, with planted inputs. 579 // The third one is a POSITIVE control and it is the load-bearing one: a guard that refuses 580 // EVERYTHING passes every negative test, so without it these teeth would stay green on a decision 581 // function that had been broken to always return 0. 582 gv_check("refuses-when-judge-family-equals-ranker-family", qb_publish_ok("lexical", "lexical", 1) == 0, ctr) 583 gv_check("refuses-absolute-metrics-from-auto-only-labels", qb_publish_ok("human", "lexical", 0) == 0, ctr) 584 gv_check("neg-control-distinct-families-with-human-labels-publishes", qb_publish_ok("human", "lexical", 1) == 1, ctr) 585 // S6 qb_tier is on the critical path above; this binds its return to the independently counted census. 586 var tierexp: i64 = 0 587 if nhuman > 0 { tierexp = 1 } 588 gv_check("tier-agrees-with-the-independently-counted-human-labels", tier == tierexp, ctr) 589 // S2 CONTROLS. The third is the positive one -- without it these two would stay green on a benchver 590 // that had been broken to always return 0. 591 gv_check("refuses-a-score-with-no-benchmark-string", qb_benchver("", runepoch) == 0, ctr) 592 gv_check("refuses-a-score-stamped-by-an-unset-clock", qb_benchver("nishi-search-qrels", 0) == 0, ctr) 593 gv_check("neg-control-complete-benchmark-identity-passes", qb_benchver("nishi-search-qrels", QB_EPOCH_FLOOR) == 1, ctr) 594 // S4 CONTROLS. The declared bite is the first one. The rest exist because a single bite cannot tell a 595 // real alignment measure from a function that returns 0, and because BOTH conjuncts must be shown to 596 // be load-bearing -- a measure that only checks the rewrite would score the fourth case 1000. 597 gv_check("lexalign-a-rewrite-that-drops-every-content-term-scores-zero", 598 qb_lexalign("machine learning", "zzz qqq", "a machine learning document") == 0, ctr) 599 gv_check("lexalign-full-survival-scores-1000", 600 qb_lexalign("machine learning", "machine learning", "a machine learning document") == QB_PERMIL, ctr) 601 gv_check("neg-control-lexalign-partial-is-strictly-between", 602 qb_lexalign("machine learning", "machine", "a machine learning document") == 500, ctr) 603 gv_check("lexalign-requires-the-term-in-the-document-not-only-in-the-rewrite", 604 qb_lexalign("machine learning", "machine learning", "a machine document") == 500, ctr) 605 // The hot dog case, as a unit test: a rewrite keeping "dog" earns nothing from a document that only 606 // says "dogs". This is the whole reason alignment counts WHOLE WORDS. 607 gv_check("lexalign-counts-whole-words-so-dogs-does-not-satisfy-dog", 608 qb_lexalign("dog", "dog", "many dogs here") == 0, ctr) 609 // S1 CONTROLS. The contract's declared negative control is that a PLANTED corpus on the other side 610 // must print the other side, so both directions are planted here through the same pure function the 611 // live report calls. The third asserts the abstention, which is the state the live shard is in. 612 gv_check("crossover-a-corpus-above-the-threshold-reads-ABOVE", 613 cs_crossover(50000000, 10000000) == 1, ctr) 614 gv_check("neg-control-crossover-a-planted-corpus-below-reads-BELOW", 615 cs_crossover(9999999, 10000000) == 0, ctr) 616 gv_check("crossover-an-unmeasured-corpus-abstains-rather-than-reading-BELOW", 617 cs_crossover(0, 10000000) == (0 - 1), ctr) 618 // S5 CONTROLS. The anti-vacuity property is ARITHMETIC, not an assertion we remembered to write: at a 619 // query-term frequency of 1 the saturation qtfsat_q10(1) is exactly 1024 (nx_intlog, the ONE ruler since S7 retired the 620 // 3-argument twin this file used to call), so it is exactly the identity and every 621 // query in this judged set scores identically under both scorers. The second tooth proves it is not 622 // merely the identity everywhere, which is what would make the first vacuous. 623 gv_check("bm25q-a-single-occurrence-query-term-is-scored-identically", 624 idf_bm25q(1000, 1) == 1000, ctr) 625 gv_check("bm25q-a-repeated-query-term-saturates-above-its-base-weight-but-below-linear", 626 idf_bm25q(1000, 3) > 1000, ctr) 627 gv_check("neg-control-bm25q-saturates-strictly-below-linear-growth", 628 idf_bm25q(1000, 3) < 3000, ctr) 629 630 let pfx: *u8 = sys_mmap(QB_PATHCAP) 631 dss_prefix(QB_DOMAIN, pfx) 632 let h: *i64 = dss_open_maybe_cached(pfx) 633 gv_check("web-shard-opened", (h as i64) != 0, ctr) 634 let sums: *i64 = sys_mmap(QB_BOX * 8) as *i64 635 if qb_mode_ablate != 0 { 636 // S3 ABLATION. FOUR ARMS: the A/B pair, then a CONTROL pair run with expansion disabled on BOTH 637 // sides. The control is the tooth that matters -- without it, an A/B that came back identical 638 // would be indistinguishable from a toggle that never took effect, which is precisely how a 639 // vacuous ablation reports "no difference" and gets believed. 640 let outA: *i64 = sys_mmap(QB_BOX * 8) as *i64 641 let outB: *i64 = sys_mmap(QB_BOX * 8) as *i64 642 gv_puts(" S3 ABLATION -- arm A is expansion ENABLED, arm B is expansion DISABLED; both print per-query lines below 643") 644 let win: i64 = qb_expand_ab(h, qtext, nq, rqi, rgr, rurl, nr, outA, outB) 645 if outA[3] > 0 { gv_kv("armA_expansion_on_ndcg_permil", (outA[0] * QB_PERMIL) / (outA[3] * QB_Q16)) } 646 if outA[3] > 0 { gv_kv("armA_expansion_on_rank1", outA[4]) } 647 if outB[3] > 0 { gv_kv("armB_expansion_off_ndcg_permil", (outB[0] * QB_PERMIL) / (outB[3] * QB_Q16)) } 648 if outB[3] > 0 { gv_kv("armB_expansion_off_rank1", outB[4]) } 649 // THE ABLATION VERDICT IS GATED BY THE SAME RULE AS THE ABSOLUTE METRICS, AND IT HAS TO BE. 650 // MEASURED 2026-09-04 on the first real run: arm B (expansion OFF) scored 912 permil ndcg and 651 // 17 of 17 rank-1-relevant against arm A's 553 and 8. That reads like a 65% win and it is NOT 652 // actionable, because the judged pool was drawn from this engine's own whole-word required-term 653 // arm -- and disabling expansion makes the ranker behave like exactly that arm. The ruler 654 // rewards precisely what the change does, which is circularity in its purest form and is the 655 // very condition S6 refuses on. Publishing "expansion_wins" here unqualified would hand the next 656 // reader a number engineered to be believed. 657 // ★A DRAMATIC RESULT FROM A CIRCULAR RULER IS MORE DANGEROUS THAN A NULL ONE, BECAUSE ITS SIZE 658 // IS MISTAKEN FOR ITS STRENGTH. 659 if publish == 1 { gv_kv("expansion_wins", win) } 660 else { 661 gv_kv("expansion_wins_UNPUBLISHABLE", win) 662 gv_puts(" ⚠S3 IS NOT SETTLED BY THIS RUN. The judged pool came from the engine's own whole-word required-term arm, so an expansion-off arm is scored by a ruler that shares its definition -- the arms differ, but the comparison is circular. What settles it is a human-labelled subset, which is exactly the label_class=human field S6 added; until one exists this verdict is evidence about the RULER, not about expansion. 663") 664 } 665 gv_puts(" S3 VERDICT -- ") 666 if win == 1 { gv_puts("expansion EARNS its wiring on this judged set") } 667 else { if win == 0 { gv_puts("expansion LOSES on this judged set and its done-rule says do not keep it wired") } 668 else { if win == 2 { gv_puts("TIE -- expansion changes nothing measurable here, which is not a win and does not earn the cost") } 669 else { gv_puts("UNDECIDABLE -- an arm scored no queries at all") } } } 670 gv_puts(" 671") 672 let c1: *i64 = sys_mmap(QB_BOX * 8) as *i64 673 let c2: *i64 = sys_mmap(QB_BOX * 8) as *i64 674 dss_set_expand_off(1) 675 qb_arm(h, qtext, nq, rqi, rgr, rurl, nr, c1) 676 qb_arm(h, qtext, nq, rqi, rgr, rurl, nr, c2) 677 dss_set_expand_off(0) 678 gv_check("neg-control-two-arms-both-with-expansion-off-score-identically", 679 c1[0] == c2[0], ctr) 680 // Written as explicit branches rather than a bitwise OR of two comparisons: this dialect's | is 681 // bitwise and the operand types of a comparison are not something to assume in a tooth whose whole 682 // job is to catch a toggle that silently did nothing. 683 var toggled: i64 = 0 684 if outA[0] != outB[0] { toggled = 1 } 685 if c1[0] == outB[0] { toggled = 1 } 686 gv_check("neg-control-the-expansion-toggle-actually-took-effect-somewhere", toggled == 1, ctr) 687 sums[0] = outA[0] 688 sums[1] = outA[1] 689 sums[2] = outA[2] 690 sums[3] = outA[3] 691 sums[4] = outA[4] 692 } else { 693 qb_arm(h, qtext, nq, rqi, rgr, rurl, nr, sums) 694 } 695 let sum_ndcg: i64 = sums[0] 696 let sum_mrr: i64 = sums[1] 697 let sum_p: i64 = sums[2] 698 let scored: i64 = sums[3] 699 let rank1: i64 = sums[4] 700 gv_values_head() 701 gv_kv("queries_scored", scored) 702 gv_kv("queries_no_results", nq - scored) 703 if scored > 0 { 704 // UNIT CORRECTION, CAUGHT BY READING THE VALUE RATHER THAN THE LABEL. nx_ir_eval returns Q16.16 705 // FIXED POINT, not permil -- the tell was mrr=65536 on a perfect query, which is exactly 2^16 and 706 // cannot be a per-mille figure. The first cut emitted those raw under keys ending _permil, which 707 // published 36264 where the true value is 553. ★★★★★A NUMBER UNDER THE WRONG UNIT IS WORSE THAN NO 708 // NUMBER: it is precise, quotable, and wrong by a factor of 65.5, and nothing about its shape says 709 // so. Converting here (not renaming the key) keeps these comparable with nx_recall_eval, which 710 // already publishes permil over the same lib -- two rulers on one scale, never two scales. 711 if publish == 1 { 712 gv_kv("mean_ndcg_permil", (sum_ndcg * QB_PERMIL) / (scored * QB_Q16)) 713 gv_kv("mean_mrr_permil", (sum_mrr * QB_PERMIL) / (scored * QB_Q16)) 714 gv_kv("mean_precision_at_k_permil", (sum_p * QB_PERMIL) / (scored * QB_Q16)) 715 gv_kv("rank1_relevant", rank1) 716 gv_kv("rank1_of", scored) 717 } else { 718 // REFUSED, AND THE RULE THAT FIRED IS NAMED. A refusal that will not say which conjunct 719 // failed is a false-alarm generator, and the reader always guesses the most alarming one. 720 gv_puts(" PUBLISH REFUSED -- ") 721 if qb_slen(judgefam) == 0 { 722 gv_puts("no judge_family declared, so the provenance of these labels is unknown and an absolute figure cannot be attributed") 723 } else { if qb_slen(rankerfam) == 0 { 724 gv_puts("no ranker_family declared, so circularity against the ranker cannot be ruled out") 725 } else { if bver == 0 { 726 gv_puts("benchmark identity incomplete -- a retrieval score with no benchmark string or no run date is not comparable with anything and is exactly how a stale number outlives the index that produced it") 727 } else { if qb_eqz(judgefam, rankerfam) == 1 { 728 gv_puts("judge_family equals ranker_family, so this set measures TRANSCRIPTION of the ranker own signal class rather than relevance") 729 } else { 730 gv_puts("no human label in the judged set, and an automatic judgement is not a substitute for a human qrel") 731 } } } } 732 gv_puts(" 733") 734 // THE DIFFERENCE BETWEEN TWO RUNS OVER ONE LABEL SET REMAINS SOUND EVEN WHEN THE LEVEL IS NOT, 735 // so the figures are emitted rather than destroyed -- an ablation needs them and deleting them 736 // would make every A/B on this estate unmeasurable. They are keyed UNPUBLISHABLE so no reader 737 // can lift one into a headline without carrying the word that says it is not one. The BIAS HAS 738 // A DIRECTION and it must be stated: a lexical judge under-credits exactly the mechanisms that 739 // match non-whole-word variants, so a stem-expansion arm is penalised by construction here. 740 gv_kv("diag_mean_ndcg_permil_UNPUBLISHABLE", (sum_ndcg * QB_PERMIL) / (scored * QB_Q16)) 741 gv_kv("diag_mean_mrr_permil_UNPUBLISHABLE", (sum_mrr * QB_PERMIL) / (scored * QB_Q16)) 742 gv_kv("diag_mean_precision_at_k_permil_UNPUBLISHABLE", (sum_p * QB_PERMIL) / (scored * QB_Q16)) 743 gv_kv("diag_rank1_relevant_UNPUBLISHABLE", rank1) 744 gv_kv("rank1_of", scored) 745 } 746 } 747 gv_kv("k", QB_K) 748 // S1 LIVE REPORT. corpus_docs is EXACT from the store. corpus_tokens is NOT, and it is reported as 749 // unmeasured rather than estimated: this scorer normalises with a precomputed per-doc normq10 and 750 // computes avgdl over the CANDIDATE SET per query, so no corpus-wide token total is persisted -- 751 // the scorer's own comment already names that gap as "the store-rung". An estimate here would be a 752 // constant wearing the shape of a measurement, and the whole purpose of this rung is to say which 753 // side of a published threshold the corpus is on, which an estimate cannot honestly settle. 754 let corpus_docs: i64 = ss_doc_count(h) 755 gv_kv("corpus_docs", corpus_docs) 756 gv_kv("corpus_tokens_measured", 0) 757 let side: i64 = cs_crossover(0, QB_CROSSOVER_TOKENS) 758 gv_kv("bm25_crossover_tokens", QB_CROSSOVER_TOKENS) 759 gv_kv("bm25_crossover_side", side) 760 gv_puts(" bm25_crossover: UNDECIDABLE -- corpus_tokens is not persisted by this index, so the side is abstained rather than guessed; the blocking rung is a corpus-wide token total in the store 761") 762 // The scored-count tooth is what stops this reporting a clean mean over two queries. 763 gv_check("scored-most-of-the-set", scored * 2 > nq, ctr) 764 gv_check("some-query-has-a-relevant-rank1", rank1 > 0, ctr) 765 return gv_verdict("nx_qrels_bench", ctr, "retrieval via dss_search on the live serve path and metrics via nx_ir_eval, so neither half is reimplemented here; recall over the JUDGED POOL only, never the corpus") 766} 767func main(argc: i64, argv: *i64) -> i64 { 768 // The ablation is a VERB, not a per-beat tooth, and that is deliberate: it runs the judged set FOUR 769 // times, so wiring it into the default path would make every beat four times slower to answer a 770 // question that only changes when the scorer changes. 771 if argc > 1 { if qb_eqz(argv[1] as *u8, "ablate") == 1 { qb_mode_ablate = 1 } } 772 return qb_main() 773}