code wiki / _hdl_build / nx_sitededup.nx

nx_sitededup.nx source

↩ module page · 532 lines · 28823 B

1// nx_sitededup.nx -- COLLAPSE DUPLICATE DOCUMENTS IN A SITE SEARCH SHARD. 2// 3// THE DEFECT, measured 2026-08-25 on a live scope=site SERP for "nishi search": of 30 results, 4// SIX were the SAME document under two different cids -- e.g. "VORTEX RISC-V GPGPU -- COMPLETE 5// PUBLICATION BIBLIOGRAPHY" as 7477184972746352550 AND 2839761502039440748, with IDENTICAL scores. 6// ROOT CAUSE: a cid is a content hash of the RAW STORED BYTES, and TWO DIFFERENT HASH FUNCTIONS write 7// the same doc:<cid> keyspace -- dp_cid (FNV-1a, nx_docportal_lib) and ci_hash (base-131 polynomial, 8// nx_corpus_ingest). Two captures of one page that differ only in a Date: header or a revision line 9// are therefore two different documents, and cross-path dedup is impossible by construction. 10// Ingest has since been fixed FORWARD (dp_ingest_policy strips capture headers and runs bd_fit_text 11// before hashing). That does nothing for rows already stored. This organ is the backward half. 12// 13// ---- WHY THE FIRST VERSION OF THIS ORGAN COULD NOT HAVE WORKED, AND HOW WE KNOW ---------------- 14// The first cut fingerprinted the RAW stored bytes. Its own gate then measured the real capture pair 15// -- one document, stored twice, the second copy carrying an HTTP header and a bumped revision line 16// -- at SIX bits of Hamming distance against the published k=3 bar. It would have collapsed NOTHING. 17// The published recipe has a step the fingerprint papers do not cover: NORMALISE THE TEXT BEFORE 18// SHINGLING, and specifically normalise DIGITS TO A CONSTANT, which is what kills counter, date, 19// revision and price near-duplicates. That is precisely our duplicate class. nx_textnorm_lib is that 20// step, it is a SHARED lib so the gate proves the same normalisation the actuator ships, and this 21// organ now reports the raw-vs-normalised comparison ON THE WHOLE POPULATION so the claim 22// "normalisation is what made this work" is a measurement rather than an assertion. 23// 24// WHY SUPPRESSION AND NOT DELETION: rule 13, additive-only. Nothing is removed. A duplicate is marked 25// by appending a pol:<cid> row with the public-search consent bit cleared -- the SAME row the search 26// path ALREADY reads per candidate (dss_search, DSS_POL_SEARCH), so this needs no change to any serve 27// organ and takes effect on the next query. It is reversible by appending a row with the bit set. 28// Every other owner flag on that row is preserved (sdd_clear_search_bit). 29// 30// WHY DURABLE AND NOT AT RANK TIME: the alternative is fingerprinting every candidate on every query. 31// Fingerprinting is a full pass over the document TEXT, and the serve path deliberately does not read 32// doc bodies during candidate filtering (the site: filter's own comment: "Cheap: ss_hget per candidate, 33// no doc reads"). Paying a text scan per candidate per query to rediscover a fact that does not change 34// between queries is the wrong side of the trade. This pass pays it once. 35// 36// COMPOSES, NEVER RE-IMPLEMENTS: nx_simhash for the fingerprint (Charikar 2002 / Manku 2007), 37// nx_textnorm_lib for the ONE copy of the normalisation decision, nx_sitededup_lib for the ONE copy 38// of the collapse decision, dss_mkpolkey for the policy row, and ccz_cat_num as the integer emitter. 39// 40// THE FINGERPRINT PLANE IS DELIBERATELY A NEW KEYSPACE. Persisted fingerprints go to fpn:<cid> 41// (tn_mkfpnkey), NOT to the existing fp:<cid> that ci_mkfpkey writes. fp: rows hold RAW fingerprints 42// written by the crawler; an fpn: row holds a NORMALISED one. They are different rulers and mixing 43// them in one keyspace is EXACTLY the defect this organ exists to repair -- committing it a second 44// time, in the same store, would be indefensible. Both rows can coexist and both are correct. 45// 46// BOUNDED, RESUMABLE-BY-DESIGN, DRY BY DEFAULT: `apply` writes only when handed the literal "commit". 47// The population bound is a conf row, and reaching it REFUSES rather than truncating -- a census that 48// quietly stopped counting reports a smaller number that reads like better news. 49// license_tier: ORIGINAL No hw writes (Rule 26). 50import "nx_corpus_ingest.nx" // dss_prefix / dss_mkkey / dss_mkpolkey / ss_* primitives 51import "nx_sitededup_lib.nx" // THE collapse decision -- shared with the gate 52import "nx_textnorm_lib.nx" // THE normalisation decision -- shared with the gate 53import "nx_itoa_lib.nx" // ccz_cat_num -- THE canonical integer emitter 54 55const SD_KEYCAP: i64 = 64 56const SD_VALCAP: i64 = 32 57const SD_WORD: i64 = 8 58const SD_MINUS: i64 = 45 59const SD_ZERO: i64 = 48 60const SD_NINE: i64 = 57 61const SD_DEC: i64 = 10 62const SD_URL_MINKEY: i64 = 5 63const SD_URL_MAXKEY: i64 = 60 64const SD_C_U: i64 = 117 65const SD_C_R: i64 = 114 66const SD_C_L: i64 = 108 67const SD_C_COLON: i64 = 58 68const SD_C_C: i64 = 99 69const SD_C_A: i64 = 97 70const SD_C_P: i64 = 112 71const SD_C_D: i64 = 100 72const SD_C_O: i64 = 111 73const SD_PREFIXCAP: i64 = 512 74const SD_BOX: i64 = 8 75 76func sd_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 77func sd_num(v: i64) -> i64 { 78 let b: *u8 = sys_mmap(NXI_BUF) 79 let n: i64 = ccz_cat_num(b, 0, v) 80 sys_write(1, b, n) 81 sys_munmap(b, NXI_BUF) 82 return 0 83} 84func sd_atoi(s: *u8) -> i64 { 85 var v: i64 = 0 86 var i: i64 = 0 87 while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= SD_ZERO { if c <= SD_NINE { v = v * SD_DEC + (c - SD_ZERO) } } i = i + 1 } 88 return v 89} 90// SIGN-AWARE, because a 64-bit simhash with bit 63 set is a NEGATIVE i64 and ccz_cat_num emits it 91// with a leading '-'. A parser that skipped the sign would read a stored fingerprint back as its own 92// negation and silently place the document in the wrong cluster. 93func sd_parse_i64(p: *u8, n: i64) -> i64 { 94 var v: i64 = 0 95 var neg: i64 = 0 96 var i: i64 = 0 97 if n > 0 { if p[0] == (SD_MINUS as u8) { neg = 1; i = 1 } } 98 while i < n { 99 let c: i64 = p[i] as i64 100 if c >= SD_ZERO { if c <= SD_NINE { v = v * SD_DEC + (c - SD_ZERO) } } 101 i = i + 1 102 } 103 if neg == 1 { return 0 - v } 104 return v 105} 106func sd_usage() -> i64 { 107 sd_puts("usage: nx_sitededup census <domain> <max> -- measure only, writes NOTHING\n" as *u8) 108 sd_puts(" nx_sitededup apply <domain> <max> [commit] -- default is a DRY RUN\n" as *u8) 109 sd_puts(" nx_sitededup pair <domain> <cidA> <cidB> -- RAW vs NORMALISED distance, on two real documents\n" as *u8) 110 return 2 111} 112 113// Fingerprint one cid. 114// box[0] = 1 when it had to be COMPUTED (so `apply` knows to persist it) 115// box[1] = 1 when there is no readable doc row at all 116// out[0] = the NORMALISED fingerprint -- the one the collapse decision uses 117// out[1] = the RAW fingerprint, and out[2] = 1 when out[1] is valid 118// The raw fingerprint is a free by-product of having the text in hand, and it is the evidence for 119// the whole normalisation claim. It is NOT available when the normalised value came from the fpn: 120// cache, and the caller must then ABSTAIN from the raw comparison rather than compute it over a 121// biased subset -- a partial control is worse than none, because it reads like a full one. 122func sd_fp_of(h: *i64, cid: i64, fkey: *u8, dkey: *u8, ap: *i64, al: *i64, box: *i64, nb: *i64, out: *i64) -> i64 { 123 box[0] = 0 124 box[1] = 0 125 out[0] = 0 126 out[1] = 0 127 out[2] = 0 128 tn_mkfpnkey(cid, fkey) 129 if ss_hget(h, fkey, ap, al) == 1 { if al[0] > 0 { 130 out[0] = sd_parse_i64(ap[0] as *u8, al[0]) 131 return 0 132 } } 133 dss_mkkey(cid, dkey) 134 if ss_hget(h, dkey, ap, al) == 1 { if al[0] > 0 { 135 box[0] = 1 136 let t: *u8 = ap[0] as *u8 137 let tl: i64 = al[0] 138 out[1] = nx_simhash_fingerprint(t, tl) 139 out[2] = 1 140 out[0] = tn_fingerprint_box(t, tl, nb) 141 return 0 142 } } 143 box[1] = 1 144 return 0 145} 146 147func main(argc: i64, argv: *i64) -> i64 { 148 if argc < 4 { return sd_usage() } 149 let verb: *u8 = argv[1] as *u8 150 let domain: *u8 = argv[2] as *u8 151 152 // ---- the threshold is DATA, and a missing row REFUSES --------------------------------------- 153 let k: i64 = sdd_conf_k() 154 let confmax: i64 = sdd_conf_maxdocs() 155 let showrows: i64 = sdd_conf_showrows() 156 let confwhich: i64 = sdd_conf_which() 157 if sdd_conf_missing(k) == 1 { sd_puts("REFUSED: hamming_max absent from knowledge/sitededup.conf (looked in the CWD and one directory up) -- there is no compiled-in default\n" as *u8); return 4 } 158 if sdd_conf_missing(confmax) == 1 { sd_puts("REFUSED: max_docs absent from knowledge/sitededup.conf\n" as *u8); return 4 } 159 if sdd_conf_missing(showrows) == 1 { sd_puts("REFUSED: show_rows absent from knowledge/sitededup.conf\n" as *u8); return 4 } 160 161 let prefix: *u8 = sys_mmap(SD_PREFIXCAP) 162 dss_prefix(domain, prefix) 163 let h: *i64 = ss_open(prefix) 164 if (h as i64) == 0 { sd_puts("REFUSED: shard absent: " as *u8); sd_puts(prefix); sd_puts("\n" as *u8); return 3 } 165 166 let fkey: *u8 = sys_mmap(SD_KEYCAP) 167 let dkey: *u8 = sys_mmap(SD_KEYCAP) 168 let box: *i64 = sys_mmap(SD_BOX * SD_WORD) as *i64 169 let nb: *i64 = sys_mmap(SD_BOX * SD_WORD) as *i64 170 let fout: *i64 = sys_mmap(SD_BOX * SD_WORD) as *i64 171 let ap: *i64 = sys_mmap(SD_WORD * 2) as *i64 172 let al: *i64 = sys_mmap(SD_WORD * 2) as *i64 173 174 // ---- verb: pair -- THE REAL-CONTROL DIAGNOSTIC ----------------------------------------------- 175 // Feed it two cids read off a LIVE SERP and it prints their true distance under BOTH rulers. 176 // This is how the threshold's false-positive rate gets measured against documents that actually 177 // exist, rather than against fixtures written by the same hand that chose the bar -- and it is 178 // the only way to see whether normalisation has pulled two GENUINELY DIFFERENT documents inside 179 // the bar, which is the one direction in which this whole lane can do harm. 180 if verb[0] == (SD_C_P as u8) { 181 let ca: i64 = sd_atoi(argv[3] as *u8) 182 var cb: i64 = 0 183 if argc >= 5 { cb = sd_atoi(argv[4] as *u8) } 184 sd_fp_of(h, ca, fkey, dkey, ap, al, box, nb, fout) 185 let amiss: i64 = box[1] 186 let na: i64 = fout[0] 187 let ra: i64 = fout[1] 188 let rav: i64 = fout[2] 189 let ahdr: i64 = nb[0] 190 let adig: i64 = nb[1] 191 sd_fp_of(h, cb, fkey, dkey, ap, al, box, nb, fout) 192 let bmiss: i64 = box[1] 193 let nbf: i64 = fout[0] 194 let rb: i64 = fout[1] 195 let rbv: i64 = fout[2] 196 let bhdr: i64 = nb[0] 197 let bdig: i64 = nb[1] 198 if amiss == 1 { sd_puts("NO-DOC cid=" as *u8); sd_num(ca); sd_puts("\n" as *u8); return 3 } 199 if bmiss == 1 { sd_puts("NO-DOC cid=" as *u8); sd_num(cb); sd_puts("\n" as *u8); return 3 } 200 let dn: i64 = nx_simhash_hamming(na, nbf) 201 sd_puts("PAIR a=" as *u8); sd_num(ca) 202 sd_puts(" b=" as *u8); sd_num(cb) 203 sd_puts(" threshold=" as *u8); sd_num(k) 204 sd_puts("\n normalised: hamming=" as *u8); sd_num(dn) 205 if dn <= k { sd_puts(" verdict=NEAR-DUPLICATE" as *u8) } else { sd_puts(" verdict=DISTINCT" as *u8) } 206 sd_puts("\n" as *u8) 207 if rav == 1 { if rbv == 1 { 208 let dr: i64 = nx_simhash_hamming(ra, rb) 209 sd_puts(" raw : hamming=" as *u8); sd_num(dr) 210 if dr <= k { sd_puts(" verdict=NEAR-DUPLICATE" as *u8) } else { sd_puts(" verdict=DISTINCT" as *u8) } 211 sd_puts("\n" as *u8) 212 if dr > k { if dn <= k { sd_puts(" NORMALISATION IS WHAT COLLAPSES THIS PAIR (raw missed it)\n" as *u8) } } 213 if dr <= k { if dn <= k { sd_puts(" both rulers agree: duplicate\n" as *u8) } } 214 if dn > k { sd_puts(" both rulers agree: distinct\n" as *u8) } 215 } } 216 if rav == 0 { sd_puts(" raw : UNAVAILABLE (normalised value came from the fpn: cache; the raw comparison needs the text)\n" as *u8) } 217 sd_puts(" normalisation did: a header_bytes_stripped=" as *u8); sd_num(ahdr) 218 sd_puts(" digits_folded=" as *u8); sd_num(adig) 219 sd_puts(" | b header_bytes_stripped=" as *u8); sd_num(bhdr) 220 sd_puts(" digits_folded=" as *u8); sd_num(bdig) 221 sd_puts("\n" as *u8) 222 return 0 223 } 224 225 let want: i64 = sd_atoi(argv[3] as *u8) 226 if want <= 0 { sd_puts("REFUSED: max must be > 0\n" as *u8); return 2 } 227 if want > confmax { sd_puts("REFUSED: max exceeds max_docs in knowledge/sitededup.conf (" as *u8); sd_num(confmax); sd_puts(") -- raise the conf row deliberately, with its complexity budget\n" as *u8); return 2 } 228 var docommit: i64 = 0 229 if argc >= 5 { let a4: *u8 = argv[4] as *u8; if a4[0] == (SD_C_C as u8) { docommit = 1 } } 230 var isapply: i64 = 0 231 if verb[0] == (SD_C_A as u8) { isapply = 1 } 232 if isapply == 0 { docommit = 0 } 233 234 // ---- arrays ------------------------------------------------------------------------------ 235 let cids: *i64 = sys_mmap(want * SD_WORD) as *i64 236 let fps: *i64 = sys_mmap(want * SD_WORD) as *i64 237 let rawfps: *i64 = sys_mmap(want * SD_WORD) as *i64 238 let parent: *i64 = sys_mmap(want * SD_WORD) as *i64 239 let eparent: *i64 = sys_mmap(want * SD_WORD) as *i64 240 let unsafe_of: *i64 = sys_mmap(want * SD_WORD) as *i64 241 let computed: *i64 = sys_mmap(want * SD_WORD) as *i64 242 let hist: *i64 = sys_mmap(SDD_HIST * SD_WORD) as *i64 243 var tsz: i64 = 1 244 while tsz < want * 2 { tsz = tsz * 2 } 245 let tab: *i64 = sys_mmap(tsz * SD_WORD) as *i64 246 let mask: i64 = tsz - 1 247 248 // ---- walk every doc: row, collecting DISTINCT cids ---------------------------------------- 249 // The store is append-only, so one key can appear in several segments. Counting ROWS instead of 250 // DOCUMENTS would pair a document with itself at distance 0 and then suppress it for existing 251 // twice in the log. The partition below reconciles rows against documents so the gap is visible. 252 // 253 // ---- WHY doc: AND NOT url:, MEASURED LIVE 2026-08-25 ----------------------------------------- 254 // The first version enumerated the population from url:<cid> rows and reported 589 documents. 255 // A single live query then returned total=611 MATCHING documents. A query cannot match more 256 // documents than the corpus contains, so the enumeration axis was under-counting the population 257 // -- documents reachable by search but invisible to the census, which is the worst possible 258 // direction for a dedup pass: it cannot suppress what it cannot see, and it would have reported 259 // a complete-looking duplicate rate over an incomplete corpus. 260 // ★ AN ENUMERATION AXIS IS A CLAIM ABOUT COVERAGE, AND A CONSUMER THAT SEES MORE THAN THE CENSUS 261 // DOES IS A DISPROOF OF THAT CLAIM. doc:<cid> IS the document keyspace (dss_mkkey) and is what 262 // the serve path resolves per hit; url: is a secondary index that not every ingest path writes. 263 // BOTH counts are printed below so the gap between the two axes stays visible instead of being 264 // silently fixed and forgotten. 265 var rows: i64 = 0 266 var urlrows: i64 = 0 267 var ndoc: i64 = 0 268 var overflow: i64 = 0 269 let ns: i64 = h[0] 270 var s: i64 = 0 271 while s < ns { 272 let kb: *u8 = h[1 + 8 * s] as *u8 273 if h[2 + 8 * s] >= 8 { 274 let m9: i64 = ss_r32(kb, 4) 275 var e9: i64 = 0 276 while e9 < m9 { 277 let eo: i64 = 8 + 4 * m9 + ss_r32(kb, 8 + 4 * e9) 278 if (kb[eo] as i64) == 1 { 279 let kl9: i64 = ss_r32(kb, eo + 1) 280 if kl9 >= SD_URL_MINKEY { if kl9 < SD_URL_MAXKEY { 281 var isdoc: i64 = 0 282 var isurl: i64 = 0 283 if kb[eo + 8] == (SD_C_COLON as u8) { 284 if kb[eo + 5] == (SD_C_D as u8) { if kb[eo + 6] == (SD_C_O as u8) { if kb[eo + 7] == (SD_C_C as u8) { isdoc = 1 } } } 285 if kb[eo + 5] == (SD_C_U as u8) { if kb[eo + 6] == (SD_C_R as u8) { if kb[eo + 7] == (SD_C_L as u8) { isurl = 1 } } } 286 } 287 if isurl == 1 { urlrows = urlrows + 1 } 288 if isdoc == 1 { 289 rows = rows + 1 290 var cid: i64 = 0 291 var ki: i64 = 4 292 while ki < kl9 { let kc: i64 = kb[eo + 5 + ki] as i64; if kc >= SD_ZERO { if kc <= SD_NINE { cid = cid * SD_DEC + (kc - SD_ZERO) } } ki = ki + 1 } 293 if cid > 0 { 294 if overflow == 0 { 295 let isnew: i64 = sdd_set_add(tab, mask, cid) 296 if isnew < 0 { overflow = 2 } 297 if isnew == 1 { 298 if ndoc >= want { overflow = 1 } else { cids[ndoc] = cid; ndoc = ndoc + 1 } 299 } 300 } 301 } 302 } 303 } } 304 } 305 e9 = e9 + 1 306 } 307 } 308 s = s + 1 309 } 310 if overflow == 1 { 311 sd_puts("REFUSED: distinct documents exceed max=" as *u8); sd_num(want) 312 sd_puts(" -- this run measured NOTHING; re-run with a larger max (ceiling " as *u8); sd_num(confmax) 313 sd_puts("). NOT truncated: a partial census published as a population is the defect this refuses to commit.\n" as *u8) 314 return 2 315 } 316 if overflow == 2 { sd_puts("REFUSED: distinct-cid table full -- internal bound, raise max\n" as *u8); return 2 } 317 318 // ---- fingerprint every document ----------------------------------------------------------- 319 var had_fp: i64 = 0 320 var made_fp: i64 = 0 321 var nodoc: i64 = 0 322 var n: i64 = 0 323 var nraw: i64 = 0 324 var hdrstrip: i64 = 0 325 var digfold: i64 = 0 326 var mkupx: i64 = 0 327 var i: i64 = 0 328 while i < ndoc { 329 sd_fp_of(h, cids[i], fkey, dkey, ap, al, box, nb, fout) 330 if box[1] == 1 { nodoc = nodoc + 1 } else { 331 if box[0] == 1 { made_fp = made_fp + 1 } else { had_fp = had_fp + 1 } 332 if nb[0] > 0 { hdrstrip = hdrstrip + 1 } 333 digfold = digfold + nb[1] 334 if nb[2] == 1 { mkupx = mkupx + 1 } 335 cids[n] = cids[i] 336 fps[n] = fout[0] 337 rawfps[n] = fout[1] 338 if fout[2] == 1 { nraw = nraw + 1 } 339 computed[n] = box[0] 340 n = n + 1 341 } 342 i = i + 1 343 } 344 345 // ---- cluster + the chained-component safety measurement ----------------------------------- 346 let edges: i64 = sdd_cluster(cids, fps, n, k, parent, hist, eparent) 347 let chained: i64 = sdd_mark_chained(fps, n, k, parent, unsafe_of) 348 349 var kept: i64 = 0 350 var supp: i64 = 0 351 var supp_exact_in_chain: i64 = 0 352 i = 0 353 while i < n { 354 if sdd_is_suppressed(i, parent, unsafe_of, eparent) == 1 { 355 supp = supp + 1 356 if unsafe_of[i] == 1 { supp_exact_in_chain = supp_exact_in_chain + 1 } 357 } else { kept = kept + 1 } 358 i = i + 1 359 } 360 361 // ---- report -------------------------------------------------------------------------------- 362 sd_puts("{\"tool\":\"nx_sitededup\",\"shard\":\"" as *u8); sd_puts(prefix) 363 sd_puts("\",\"conf_resolved_from\":" as *u8); sd_num(confwhich) 364 sd_puts(",\"doc_rows_walked\":" as *u8); sd_num(rows) 365 sd_puts(",\"url_rows_walked\":" as *u8); sd_num(urlrows) 366 sd_puts(",\"distinct_cids\":" as *u8); sd_num(ndoc) 367 sd_puts(",\"no_doc_row\":" as *u8); sd_num(nodoc) 368 sd_puts(",\"documents\":" as *u8); sd_num(n) 369 sd_puts(",\"fpn_rows_present\":" as *u8); sd_num(had_fp) 370 sd_puts(",\"fpn_computed_now\":" as *u8); sd_num(made_fp) 371 sd_puts(",\"norm_docs_header_stripped\":" as *u8); sd_num(hdrstrip) 372 sd_puts(",\"norm_digit_bytes_folded\":" as *u8); sd_num(digfold) 373 sd_puts(",\"norm_docs_markup_extracted\":" as *u8); sd_num(mkupx) 374 sd_puts(",\"threshold_bits\":" as *u8); sd_num(k) 375 sd_puts(",\"neardup_edges\":" as *u8); sd_num(edges) 376 sd_puts(",\"kept\":" as *u8); sd_num(kept) 377 sd_puts(",\"suppressed\":" as *u8); sd_num(supp) 378 sd_puts(",\"chained_documents\":" as *u8); sd_num(chained) 379 sd_puts(",\"suppressed_exact_twin_inside_chain\":" as *u8); sd_num(supp_exact_in_chain) 380 sd_puts(",\"chained_abstained\":" as *u8); sd_num(chained - supp_exact_in_chain) 381 sd_puts(",\"dup_permil\":" as *u8); sd_num(sdd_permil(supp, n)) 382 sd_puts(",\"committed\":" as *u8); sd_num(docommit) 383 sd_puts("}\n" as *u8) 384 385 // PARTITIONS MUST SUM, AND THE SUM MUST BE PRINTED. 386 if kept + supp == n { sd_puts("PARTITION-RECONCILES documents = kept + suppressed\n" as *u8) } else { sd_puts("PARTITION-LEAK -- do not trust these counts\n" as *u8) } 387 if had_fp + made_fp + nodoc == ndoc { sd_puts("PARTITION-RECONCILES distinct_cids = fpn_present + fpn_computed + no_doc\n" as *u8) } else { sd_puts("PARTITION-LEAK (fingerprint sources)\n" as *u8) } 388 389 // ---- THE NORMALISATION CONTROL, OVER THE WHOLE POPULATION ------------------------------------ 390 // Re-runs the identical clusterer over the RAW fingerprints of the SAME documents. The only thing 391 // that differs between the two numbers is the normalisation step, so the gap between them IS the 392 // measured value of normalising -- on this corpus, not on a fixture. 393 // It ABSTAINS unless every document contributed a raw fingerprint. A control computed over the 394 // subset that happened to miss the cache would be a biased sample wearing the name of a control. 395 if nraw == n { 396 if n > 0 { 397 let rparent: *i64 = sys_mmap(n * SD_WORD) as *i64 398 let reparent: *i64 = sys_mmap(n * SD_WORD) as *i64 399 let runsafe: *i64 = sys_mmap(n * SD_WORD) as *i64 400 let rhist: *i64 = sys_mmap(SDD_HIST * SD_WORD) as *i64 401 let redges: i64 = sdd_cluster(cids, rawfps, n, k, rparent, rhist, reparent) 402 sdd_mark_chained(rawfps, n, k, rparent, runsafe) 403 var rsupp: i64 = 0 404 i = 0 405 while i < n { 406 if sdd_is_suppressed(i, rparent, runsafe, reparent) == 1 { rsupp = rsupp + 1 } 407 i = i + 1 408 } 409 sd_puts("NORMALISATION-CONTROL (same clusterer, same bar, same " as *u8); sd_num(n) 410 sd_puts(" documents, raw text vs normalised text)\n" as *u8) 411 sd_puts(" raw : edges=" as *u8); sd_num(redges); sd_puts(" suppressed=" as *u8); sd_num(rsupp) 412 sd_puts(" permil=" as *u8); sd_num(sdd_permil(rsupp, n)); sd_puts("\n" as *u8) 413 sd_puts(" normalised: edges=" as *u8); sd_num(edges); sd_puts(" suppressed=" as *u8); sd_num(supp) 414 sd_puts(" permil=" as *u8); sd_num(sdd_permil(supp, n)); sd_puts("\n" as *u8) 415 sd_puts(" DELTA suppressed=" as *u8); sd_num(supp - rsupp) 416 sd_puts(" -- duplicates that ONLY normalisation can see\n" as *u8) 417 sys_munmap(rparent as *u8, n * SD_WORD) 418 sys_munmap(reparent as *u8, n * SD_WORD) 419 sys_munmap(runsafe as *u8, n * SD_WORD) 420 sys_munmap(rhist as *u8, SDD_HIST * SD_WORD) 421 } 422 } else { 423 sd_puts("NORMALISATION-CONTROL: ABSTAINED -- only " as *u8); sd_num(nraw) 424 sd_puts(" of " as *u8); sd_num(n) 425 sd_puts(" documents were fingerprinted from text this run (the rest came from the fpn: cache),\n" as *u8) 426 sd_puts(" and a control over the subset that happened to miss the cache is a biased sample, not a control.\n" as *u8) 427 } 428 429 // The histogram is the calibration evidence: k is only defensible while a clear gap separates 430 // the duplicate mass near 0 from the bulk of unrelated pairs. 431 sd_puts("HAMMING-HISTOGRAM, NORMALISED (all " as *u8); sd_num((n * (n - 1)) / 2); sd_puts(" pairs; last bucket is >=)\n" as *u8) 432 var b: i64 = 0 433 while b < SDD_HIST { 434 if hist[b] > 0 { 435 sd_puts(" d=" as *u8); sd_num(b) 436 if b == SDD_HIST - 1 { sd_puts("+" as *u8) } 437 sd_puts(" pairs=" as *u8); sd_num(hist[b]) 438 sd_puts("\n" as *u8) 439 } 440 b = b + 1 441 } 442 443 if chained > 0 { 444 sd_puts("CHAINED components hold " as *u8); sd_num(chained) 445 sd_puts(" document(s) (component diameter > threshold). Near-duplication is not transitive,\n" as *u8) 446 sd_puts(" so collapsing a whole chain could suppress a document that is NOT a near-duplicate of the\n" as *u8) 447 sd_puts(" survivor. Of those, " as *u8); sd_num(supp_exact_in_chain) 448 sd_puts(" are EXACT fingerprint twins and ARE collapsed: fingerprint equality IS\n" as *u8) 449 sd_puts(" transitive, so such a class has diameter 0 and its survivor is at distance 0 from every\n" as *u8) 450 sd_puts(" member it replaces -- a stronger guarantee than the one safe components already get.\n" as *u8) 451 sd_puts("ABSTAINED on the remaining " as *u8); sd_num(chained - supp_exact_in_chain) 452 sd_puts(" chained document(s). They stay searchable. Abstain, never destroy.\n" as *u8) 453 } 454 455 // ---- worklist + commit --------------------------------------------------------------------- 456 let w: *i64 = ss_begin() 457 let segsbox: *i64 = sys_mmap(SD_WORD * 2) as *i64 458 segsbox[0] = 0 459 let segidbox: *i64 = sys_mmap(SD_WORD * 2) as *i64 460 segidbox[0] = ss_next_segid(prefix) 461 let pkey: *u8 = sys_mmap(SD_KEYCAP) 462 let pval: *u8 = sys_mmap(SD_VALCAP) 463 let fval: *u8 = sys_mmap(SD_VALCAP) 464 let qp: *i64 = sys_mmap(SD_WORD * 2) as *i64 465 let ql: *i64 = sys_mmap(SD_WORD * 2) as *i64 466 467 var wrote_pol: i64 = 0 468 var wrote_fp: i64 = 0 469 var already: i64 = 0 470 var shown: i64 = 0 471 sd_puts("SUPPRESSION WORKLIST (cid -> survivor, distance)\n" as *u8) 472 i = 0 473 while i < n { 474 if sdd_is_suppressed(i, parent, unsafe_of, eparent) == 1 { 475 let r: i64 = sdd_survivor_of(i, parent, unsafe_of, eparent) 476 if shown < showrows { 477 shown = shown + 1 478 sd_puts(" SUPPRESS cid=" as *u8); sd_num(cids[i]) 479 sd_puts(" survivor=" as *u8); sd_num(cids[r]) 480 sd_puts(" d=" as *u8); sd_num(nx_simhash_hamming(fps[i], fps[r])) 481 if unsafe_of[i] == 1 { sd_puts(" rule=EXACT-TWIN-INSIDE-CHAINED-COMPONENT" as *u8) } 482 sd_puts("\n" as *u8) 483 } 484 // Read the CURRENT policy row. An absent row means the docportal default, which is 485 // PUBLIC + searchable (dp_default_policy) -- exactly what the serve path assumes. 486 dss_mkpolkey(cids[i], pkey) 487 var flags: i64 = 1 488 if ss_hget(h, pkey, qp, ql) == 1 { if ql[0] > 0 { flags = sd_parse_i64(qp[0] as *u8, ql[0]) } } 489 if sdd_search_allowed(flags) == 0 { already = already + 1 } else { 490 let nf: i64 = sdd_clear_search_bit(flags) 491 let pn: i64 = ccz_cat_num(pval, 0, nf) 492 wrote_pol = wrote_pol + 1 493 if docommit == 1 { 494 if ss_add(w, 1, pkey, pval, pn) < 0 { 495 if ss_commit(prefix, w, segidbox[0]) == 0 { segsbox[0] = segsbox[0] + 1 } 496 segidbox[0] = segidbox[0] + 1 497 w[1] = 0 498 ss_add(w, 1, pkey, pval, pn) 499 } 500 } 501 } 502 } 503 // Persist the NORMALISED fingerprint we just computed, into the fpn: plane. Capture once, ask 504 // forever: the next census reads the row instead of re-scanning the text. 505 if computed[i] == 1 { 506 tn_mkfpnkey(cids[i], fkey) 507 let fn2: i64 = ccz_cat_num(fval, 0, fps[i]) 508 wrote_fp = wrote_fp + 1 509 if docommit == 1 { 510 if ss_add(w, 1, fkey, fval, fn2) < 0 { 511 if ss_commit(prefix, w, segidbox[0]) == 0 { segsbox[0] = segsbox[0] + 1 } 512 segidbox[0] = segidbox[0] + 1 513 w[1] = 0 514 ss_add(w, 1, fkey, fval, fn2) 515 } 516 } 517 } 518 i = i + 1 519 } 520 if docommit == 1 { if ss_commit(prefix, w, segidbox[0]) == 0 { segsbox[0] = segsbox[0] + 1 } } 521 522 if supp > shown { sd_puts(" <== THIS LIST IS A PREFIX OF ITS OWN COUNT (" as *u8); sd_num(shown); sd_puts(" of " as *u8); sd_num(supp); sd_puts(" shown; raise show_rows)\n" as *u8) } 523 sd_puts("POL-ROWS pending=" as *u8); sd_num(wrote_pol) 524 sd_puts(" already_suppressed=" as *u8); sd_num(already) 525 sd_puts(" FPN-ROWS pending=" as *u8); sd_num(wrote_fp) 526 sd_puts(" segments_committed=" as *u8); sd_num(segsbox[0]) 527 sd_puts("\n" as *u8) 528 if docommit == 0 { sd_puts("DRY RUN -- nothing was written. Re-run `apply <domain> <max> commit` to persist.\n" as *u8) } 529 if docommit == 1 { sd_puts("REVERSIBLE: append a pol:<cid> row with bit 0 set to restore any document to search.\n" as *u8) } 530 sd_puts("SITEDEDUP-OK\n" as *u8) 531 return 0 532}