code wiki / (root) / nx_mf_hold.nx

nx_mf_hold.nx source

↩ module page · 640 lines · 26830 B

1// nx_mf_hold.nx -- ADVANCE A SYNC WATERMARK EXCEPT WHERE IT WAS NEVER EARNED. 2// 3// WHY IT EXISTS (measured 2026-08-16, treecanon flow-back lane): 4// sync_nas_ahead.ps1 ended with `if ($Apply) { Copy-Item $cur $base -Force }` -- it advanced the 5// baseline manifest to the CURRENT NAS manifest WHOLESALE. For a REFUSE that is correct: the file 6// was examined, declined, and durably queued in conflicts.log for conflicts.ps1 to rank. But a 7// FETCH FAILURE `continue`d BEFORE that ledger append, so it was recorded NOWHERE -- and the 8// watermark then advanced past a file the lane had never even looked at, marking it synced 9// permanently and silently. 10// => A TRANSIENT FAILURE THAT IS NEITHER RETRIED NOR RECORDED IS A PERMANENT ONE. 11// 12// SCOPE CORRECTION WORTH KEEPING, because the first version of this header got it wrong: the 13// watermark answers "what changed on the NAS", NOT "what is still unreconciled". Those are different 14// questions with different readers, and conflicts.log/conflicts.ps1 already answer the second (837 15// distinct files / 1185 events, measured the same day). Holding every refusal here would re-fetch all 16// 837 on every hourly run to rebuild a worklist that already exists -- a duplicate ruler that also 17// hammers the NAS. BEFORE BUILDING A QUEUE, CHECK WHETHER THE REFUSAL LEDGER IS ALREADY ONE. 18// 19// WHAT IT DOES 20// out = current, EXCEPT that any path named in holdlist keeps its BASELINE row -- or is OMITTED 21// entirely when the baseline has no row for it, so it re-surfaces next run as B-ONLY rather than 22// being silently blessed. 23// PER-PATH, deliberately. Holding the WHOLE watermark whenever anything was held would let one 24// stuck file pin it forever and turn every later run into a full re-fetch: one silence traded for 25// one flood. 26// 27// SELF-TESTS ON EVERY RUN AND REFUSES TO WRITE IF ANY TOOTH FAILS. Precedent in this same lane: 28// nx_normdiff withholds its verdict when its supersede predicate regresses, so the failure mode is 29// "no adopt", never "wrong adopt". Emitting a CORRUPT baseline is strictly worse than emitting none, 30// because a corrupt watermark is indistinguishable from a converged one. 31// 32// ROW FORMAT: "<64hex> <bytes> <relpath>\n" -- what nx_treehash writes and nx_hashdiverge parses. 33// IMPRECISION ACCEPTED AND NAMED: relpath is the bytes between the second space and the newline, so 34// a relpath CONTAINING A SPACE parses short. nx_treehash never emits one. The parse is deliberately 35// the SAME SHAPE as nx_hashdiverge's rather than a cleverer one, because two parsers that disagree 36// under the same input is the duplicate-ruler defect wearing a bugfix. 37// 38// sys_openat_wr has NO O_TRUNC, so a shorter rewrite over a longer file leaves a live tail -- on a 39// BASELINE manifest that is corrupt trailing rows that read as real. There is no sys_openat_trunc 40// (proven absent over buildroot/runtime, corpus_complete=1), so: UNLINK FIRST. 41// 42// NO CAPS THAT HAVE TO BE GUESSED. Every table and buffer is DERIVED from the input it must hold: 43// the output can never exceed current+baseline bytes (each emitted row comes from exactly one of 44// them), and each hash table is sized to a power of two at MH_LOAD_NUM x its own row upper bound. 45// A guessed ceiling is a defect generator in both directions and raising it only moves the guess. 46// 47// BITE-PROVEN BOTH DIRECTIONS 2026-08-16, two mutants each killing a different tooth class: 48// copy-current-instead-of-baseline -> 6/8, kills ONLY the anti-vacuity pair (all six count teeth 49// still pass, which is exactly why that pair exists) 50// hold-every-path -> 5/8, kills the positive control (a guard that refuses 51// everything passes every negative test) 52// 53// DIALECT: plain-if, <=6 params, consts above use, tables via static POINTERS (a BSS static array 54// crashes the module at startup -- banked gotcha). 55// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26). 56import "nx_syscalls.nx" 57 58const MH_SHA_CHARS: i64 = 64 59const MH_NUMBUF: i64 = 64 60const MH_FNV_OFF: i64 = 1469598103934665603 61const MH_FNV_PRIME: i64 = 1099511628211 62// 64 sha + space + at least one size digit + space + at least one path byte + newline. 63const MH_MIN_MF_ROW: i64 = MH_SHA_CHARS + 4 64// shortest possible holdlist line: one path byte + newline. 65const MH_MIN_HOLD_ROW: i64 = 2 66// open-addressed tables stay under 25% load, so a probe walk is short and cannot degrade to a scan. 67const MH_LOAD_NUM: i64 = 4 68const MH_MIN_SLOTS: i64 = 16 69const MH_FIXTURE_BYTES: i64 = 4096 70const MH_OK: i64 = 0 71const MH_PARTITION_FAIL: i64 = 1 72const MH_USAGE: i64 = 2 73const MH_UNREADABLE: i64 = 3 74const MH_SELFTEST_FAIL: i64 = 4 75 76static mh_bkey: *i64 77static mh_bsha: *i64 78static mh_bsz: *i64 79static mh_bslots: *i64 80static mh_hkey: *i64 81static mh_hseen: *i64 82static mh_hslots: *i64 83static mh_c: *i64 84static mh_out: *u8 85static mh_out_n: *i64 86static mh_outcap: *i64 87static mh_num: *u8 88static mh_rev: *u8 89static mh_bad: *i64 90static mh_t: *i64 91 92func mh_puts(s: *u8) -> i64 { 93 var n: i64 = 0 94 while s[n] != (0 as u8) { n = n + 1 } 95 sys_write(1, s, n) 96 return 0 97} 98func mh_putn(v: i64) -> i64 { 99 var m: i64 = v 100 if m == 0 { mh_num[0] = 48 as u8; sys_write(1, mh_num, 1); return 0 } 101 if m < 0 { mh_puts("-" as *u8); m = 0 - m } 102 var k: i64 = 0 103 while m > 0 { mh_rev[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 104 var i: i64 = 0 105 while i < k { mh_num[i] = mh_rev[k - 1 - i]; i = i + 1 } 106 sys_write(1, mh_num, k) 107 return 0 108} 109func mh_slen(s: *u8) -> i64 { 110 var n: i64 = 0 111 while s[n] != (0 as u8) { n = n + 1 } 112 return n 113} 114func mh_streq(a: *u8, b: *u8) -> i64 { 115 var i: i64 = 0 116 var go: i64 = 1 117 var eq: i64 = 1 118 while go == 1 { 119 if a[i] != b[i] { eq = 0; go = 0 } else { 120 if a[i] == (0 as u8) { go = 0 } else { i = i + 1 } 121 } 122 } 123 return eq 124} 125func mh_hash(s: *u8) -> i64 { 126 var h: i64 = MH_FNV_OFF 127 var i: i64 = 0 128 while s[i] != (0 as u8) { 129 h = h ^ (s[i] as i64) 130 h = h * MH_FNV_PRIME 131 i = i + 1 132 } 133 if h < 0 { h = 0 - h } 134 return h 135} 136// smallest power of two >= v, never below MH_MIN_SLOTS. Derived sizing, so there is no ceiling to tune. 137func mh_pow2ge(v: i64) -> i64 { 138 var p: i64 = MH_MIN_SLOTS 139 while p < v { p = p * 2 } 140 return p 141} 142 143// ---- baseline table: relpath -> (sha, size) ---- 144func mh_bput(rel: *u8, sha: *u8, sz: *u8) -> i64 { 145 let mask: i64 = mh_bslots[0] - 1 146 var s: i64 = mh_hash(rel) & mask 147 var probes: i64 = 0 148 var go: i64 = 1 149 while go == 1 { 150 // A FULL OPEN-ADDRESSED TABLE HANGS RATHER THAN TRUNCATES -- announce, never spin. 151 if probes > mh_bslots[0] { mh_bad[0] = 1; return 0 } 152 if mh_bkey[s] == 0 { 153 mh_bkey[s] = rel as i64 154 mh_bsha[s] = sha as i64 155 mh_bsz[s] = sz as i64 156 go = 0 157 } else { 158 if mh_streq(mh_bkey[s] as *u8, rel) == 1 { go = 0 } else { s = (s + 1) & mask; probes = probes + 1 } 159 } 160 } 161 return 0 162} 163func mh_bfind(rel: *u8) -> i64 { 164 let mask: i64 = mh_bslots[0] - 1 165 var s: i64 = mh_hash(rel) & mask 166 var probes: i64 = 0 167 while probes <= mh_bslots[0] { 168 if mh_bkey[s] == 0 { return 0 - 1 } 169 if mh_streq(mh_bkey[s] as *u8, rel) == 1 { return s } 170 s = (s + 1) & mask 171 probes = probes + 1 172 } 173 return 0 - 1 174} 175 176// ---- hold set: relpath -> seen-in-current flag ---- 177func mh_hput(rel: *u8) -> i64 { 178 let mask: i64 = mh_hslots[0] - 1 179 var s: i64 = mh_hash(rel) & mask 180 var probes: i64 = 0 181 var go: i64 = 1 182 while go == 1 { 183 if probes > mh_hslots[0] { mh_bad[0] = 1; return 0 } 184 if mh_hkey[s] == 0 { 185 mh_hkey[s] = rel as i64 186 mh_hseen[s] = 0 187 mh_c[4] = mh_c[4] + 1 188 go = 0 189 } else { 190 if mh_streq(mh_hkey[s] as *u8, rel) == 1 { go = 0 } else { s = (s + 1) & mask; probes = probes + 1 } 191 } 192 } 193 return 0 194} 195func mh_hfind(rel: *u8) -> i64 { 196 let mask: i64 = mh_hslots[0] - 1 197 var s: i64 = mh_hash(rel) & mask 198 var probes: i64 = 0 199 while probes <= mh_hslots[0] { 200 if mh_hkey[s] == 0 { return 0 - 1 } 201 if mh_streq(mh_hkey[s] as *u8, rel) == 1 { return s } 202 s = (s + 1) & mask 203 probes = probes + 1 204 } 205 return 0 - 1 206} 207 208func mh_emit_row(sha: *u8, sz: *u8, rel: *u8) -> i64 { 209 var o: i64 = mh_out_n[0] 210 let need: i64 = MH_SHA_CHARS + 1 + mh_slen(sz) + 1 + mh_slen(rel) + 1 211 if o + need > mh_outcap[0] { mh_bad[0] = 2; return 0 } 212 var i: i64 = 0 213 while i < MH_SHA_CHARS { mh_out[o] = sha[i]; o = o + 1; i = i + 1 } 214 mh_out[o] = 32 as u8; o = o + 1 215 i = 0 216 while sz[i] != (0 as u8) { mh_out[o] = sz[i]; o = o + 1; i = i + 1 } 217 mh_out[o] = 32 as u8; o = o + 1 218 i = 0 219 while rel[i] != (0 as u8) { mh_out[o] = rel[i]; o = o + 1; i = i + 1 } 220 mh_out[o] = 10 as u8; o = o + 1 221 mh_out_n[0] = o 222 return 0 223} 224 225// Parse a treehash manifest IN PLACE. side 0 = load into the baseline table, side 1 = stream current 226// and decide per row. Same field walk as nx_hashdiverge's hd_scan, on purpose. 227func mh_scan_mf(buf: *u8, n: i64, side: i64) -> i64 { 228 var p: i64 = 0 229 while p < n { 230 let base: i64 = buf as i64 231 let sha: *u8 = (base + p) as *u8 232 if p + MH_SHA_CHARS + 2 > n { return 0 } 233 buf[p + MH_SHA_CHARS] = 0 as u8 234 var q: i64 = p + MH_SHA_CHARS + 1 235 let szs: *u8 = (base + q) as *u8 236 var gq: i64 = 1 237 while gq == 1 { 238 if q >= n { gq = 0 } else { 239 if buf[q] == (32 as u8) { gq = 0 } else { q = q + 1 } 240 } 241 } 242 if q >= n { return 0 } 243 buf[q] = 0 as u8 244 var r: i64 = q + 1 245 let rel: *u8 = (base + r) as *u8 246 var gr: i64 = 1 247 while gr == 1 { 248 if r >= n { gr = 0 } else { 249 if buf[r] == (10 as u8) { gr = 0 } else { r = r + 1 } 250 } 251 } 252 if r >= n { return 0 } 253 buf[r] = 0 as u8 254 if side == 0 { 255 mh_c[6] = mh_c[6] + 1 256 mh_bput(rel, sha, szs) 257 } 258 if side == 1 { 259 mh_c[0] = mh_c[0] + 1 260 let h: i64 = mh_hfind(rel) 261 if h < 0 { 262 mh_emit_row(sha, szs, rel) 263 mh_c[1] = mh_c[1] + 1 264 } else { 265 mh_hseen[h] = 1 266 let b: i64 = mh_bfind(rel) 267 if b < 0 { 268 // held but the baseline never knew it: OMIT, so it returns as B-ONLY next run. 269 mh_c[3] = mh_c[3] + 1 270 } else { 271 mh_emit_row(mh_bsha[b] as *u8, mh_bsz[b] as *u8, mh_bkey[b] as *u8) 272 mh_c[2] = mh_c[2] + 1 273 } 274 } 275 } 276 p = r + 1 277 } 278 return 0 279} 280 281// One path per line; blank lines and '#' comments ignored. Cursor and terminator are SEPARATE 282// variables -- writing the exit sentinel into the cursor erases where the line ended. 283func mh_scan_hold(buf: *u8, n: i64) -> i64 { 284 var p: i64 = 0 285 while p < n { 286 let base: i64 = buf as i64 287 var e: i64 = p 288 var go: i64 = 1 289 while go == 1 { 290 if e >= n { go = 0 } else { 291 if buf[e] == (10 as u8) { go = 0 } else { e = e + 1 } 292 } 293 } 294 // A HOLDLIST IS EXTERNAL INPUT AND ARRIVES CRLF WHEN A WINDOWS CALLER WRITES IT. .NET's 295 // WriteAllLines emits \r\n, so every key would carry a trailing CR -- and a hash lookup cannot 296 // report "almost", it reports NOT FOUND. The watermark would then advance past exactly the 297 // paths being held, which is the defect this organ exists to prevent, reintroduced silently. 298 // MEASURED 2026-08-16 on the first real wiring: hold_rows=3 hold_not_in_current=3 299 // held_omitted=0 for three paths provably present in the manifest. Only the third-state 300 // counter made it visible; a two-state answer would have read as a clean no-op. 301 // * VALIDATE AT THE BOUNDARY: THE PRODUCER OF THIS FILE RUNS ON A DIFFERENT OS. 302 var t: i64 = e 303 if t > p { 304 if buf[t - 1] == (13 as u8) { t = t - 1 } 305 } 306 if t < n { buf[t] = 0 as u8 } 307 let line: *u8 = (base + p) as *u8 308 if line[0] != (0 as u8) { 309 if line[0] != (35 as u8) { mh_hput(line) } 310 } 311 p = e + 1 312 } 313 return 0 314} 315 316// A held path the current manifest does not carry is REPORTED, never silently dropped: it usually 317// means the file was deleted on the far side while a conflict about it was still open. 318func mh_sweep_hold() -> i64 { 319 var s: i64 = 0 320 while s < mh_hslots[0] { 321 if mh_hkey[s] != 0 { 322 if mh_hseen[s] == 0 { mh_c[5] = mh_c[5] + 1 } 323 } 324 s = s + 1 325 } 326 return 0 327} 328 329// Read a file and hand back a buffer that is ALWAYS newline-terminated, so the last row can never be 330// the one silently dropped by a parser that needs a trailing separator. 331func mh_slurp(path: *u8, lenout: *i64) -> *u8 { 332 let l: *i64 = sys_mmap(16) as *i64 333 let raw: *u8 = sys_read_file(path, l) 334 if (raw as i64) == 0 { lenout[0] = 0; return 0 as *u8 } 335 var n: i64 = l[0] 336 let b: *u8 = sys_mmap(n + 2) 337 var i: i64 = 0 338 while i < n { b[i] = raw[i]; i = i + 1 } 339 if n > 0 { 340 if b[n - 1] != (10 as u8) { b[n] = 10 as u8; n = n + 1 } 341 } 342 lenout[0] = n 343 return b 344} 345 346// brows/hrows are UPPER BOUNDS derived from input bytes, so the tables cannot overflow by construction. 347func mh_alloc(brows: i64, hrows: i64) -> i64 { 348 mh_bslots[0] = mh_pow2ge(brows * MH_LOAD_NUM) 349 mh_hslots[0] = mh_pow2ge(hrows * MH_LOAD_NUM) 350 mh_bkey = sys_mmap(mh_bslots[0] * 8) as *i64 351 mh_bsha = sys_mmap(mh_bslots[0] * 8) as *i64 352 mh_bsz = sys_mmap(mh_bslots[0] * 8) as *i64 353 mh_hkey = sys_mmap(mh_hslots[0] * 8) as *i64 354 mh_hseen = sys_mmap(mh_hslots[0] * 8) as *i64 355 var z: i64 = 0 356 while z < 8 { mh_c[z] = 0; z = z + 1 } 357 mh_out_n[0] = 0 358 return 0 359} 360 361func mh_cat(dst: *u8, off: i64, s: *u8) -> i64 { 362 var o: i64 = off 363 var i: i64 = 0 364 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 } 365 return o 366} 367func mh_fill(dst: *u8, off: i64, ch: i64, cnt: i64) -> i64 { 368 var o: i64 = off 369 var i: i64 = 0 370 while i < cnt { dst[o] = ch as u8; o = o + 1; i = i + 1 } 371 return o 372} 373// A fixture row whose sha is 64 copies of ONE character: it can never collide with a real hash, and a 374// failure dump says at a glance which side the surviving row came from. 375func mh_row(dst: *u8, off: i64, shachar: i64, sz: *u8, path: *u8) -> i64 { 376 var o: i64 = mh_fill(dst, off, shachar, MH_SHA_CHARS) 377 dst[o] = 32 as u8; o = o + 1 378 o = mh_cat(dst, o, sz) 379 dst[o] = 32 as u8; o = o + 1 380 o = mh_cat(dst, o, path) 381 dst[o] = 10 as u8; o = o + 1 382 return o 383} 384func mh_holdline(dst: *u8, off: i64, path: *u8) -> i64 { 385 var o: i64 = mh_cat(dst, off, path) 386 dst[o] = 10 as u8; o = o + 1 387 return o 388} 389// The SAME line as a Windows caller writes it. One fixture entry uses this so the CRLF path is 390// exercised by the ordinary teeth rather than by a special case nobody runs. 391func mh_holdline_crlf(dst: *u8, off: i64, path: *u8) -> i64 { 392 var o: i64 = mh_cat(dst, off, path) 393 dst[o] = 13 as u8; o = o + 1 394 dst[o] = 10 as u8; o = o + 1 395 return o 396} 397func mh_find_sub(hay: *u8, hn: i64, needle: *u8) -> i64 { 398 let nl: i64 = mh_slen(needle) 399 if nl == 0 { return 0 - 1 } 400 if nl > hn { return 0 - 1 } 401 var i: i64 = 0 402 while i <= hn - nl { 403 var k: i64 = 0 404 var ok: i64 = 1 405 while k < nl { 406 if hay[i + k] != needle[k] { ok = 0; k = nl } else { k = k + 1 } 407 } 408 if ok == 1 { return i } 409 i = i + 1 410 } 411 return 0 - 1 412} 413// declared == executed by construction: both counters move on the same call, so a tooth that stops 414// running lowers the denominator too instead of quietly reading green. 415func mh_check(name: *u8, cond: i64) -> i64 { 416 mh_t[1] = mh_t[1] + 1 417 if cond == 1 { mh_t[0] = mh_t[0] + 1 } else { 418 mh_puts(" SELFTEST FAIL: " as *u8); mh_puts(name); mh_puts("\n" as *u8) 419 } 420 return cond 421} 422 423// Fixtures are ASSEMBLED AT RUNTIME and never read from disk: a check that shares a fixture with a 424// production file measures the file. Buffers are rebuilt for the second scenario because mh_scan_mf 425// terminates fields IN PLACE -- re-parsing an already-parsed buffer would read a mangled manifest and 426// the tooth would be testing the mangling. 427func mh_selftest() -> i64 { 428 mh_t[0] = 0 429 mh_t[1] = 0 430 431 let bb: *u8 = sys_mmap(MH_FIXTURE_BYTES) 432 let cb: *u8 = sys_mmap(MH_FIXTURE_BYTES) 433 let hb: *u8 = sys_mmap(MH_FIXTURE_BYTES) 434 var bn: i64 = 0 435 bn = mh_row(bb, bn, 97, "100" as *u8, "alpha.nx" as *u8) 436 bn = mh_row(bb, bn, 98, "200" as *u8, "beta.nx" as *u8) 437 bn = mh_row(bb, bn, 99, "300" as *u8, "gamma.nx" as *u8) 438 var cn: i64 = 0 439 cn = mh_row(cb, cn, 97, "100" as *u8, "alpha.nx" as *u8) 440 cn = mh_row(cb, cn, 122, "250" as *u8, "beta.nx" as *u8) 441 cn = mh_row(cb, cn, 99, "300" as *u8, "gamma.nx" as *u8) 442 cn = mh_row(cb, cn, 100, "400" as *u8, "delta.nx" as *u8) 443 var hn: i64 = 0 444 // beta is written CRLF ON PURPOSE: the real caller is a Windows script, so the ordinary held-path 445 // teeth below must pass through the CRLF path rather than only ever seeing LF. 446 hn = mh_holdline_crlf(hb, hn, "beta.nx" as *u8) 447 hn = mh_holdline(hb, hn, "delta.nx" as *u8) 448 hn = mh_holdline(hb, hn, "zeta.nx" as *u8) 449 450 mh_alloc(8, 8) 451 mh_outcap[0] = MH_FIXTURE_BYTES * 2 452 mh_out = sys_mmap(mh_outcap[0]) 453 mh_out_n[0] = 0 454 mh_scan_hold(hb, hn) 455 mh_scan_mf(bb, bn, 0) 456 mh_scan_mf(cb, cn, 1) 457 mh_sweep_hold() 458 459 mh_check("partition-reconciles current == advanced + held + omitted" as *u8, 460 (mh_c[0] == mh_c[1] + mh_c[2] + mh_c[3]) as i64) 461 mh_check("advanced-exactly-the-two-unheld-rows" as *u8, (mh_c[1] == 2) as i64) 462 mh_check("held-exactly-one" as *u8, (mh_c[2] == 1) as i64) 463 464 // ANTI-VACUITY PAIR. An implementation that simply copies `current` passes every count tooth 465 // above. Only these two refute it, and they must BOTH hold at once: the baseline row present AND 466 // the current row absent. Either alone is satisfied by a trivial wrong answer. 467 let ex: *u8 = sys_mmap(MH_FIXTURE_BYTES) 468 var en: i64 = mh_row(ex, 0, 98, "200" as *u8, "beta.nx" as *u8) 469 ex[en] = 0 as u8 470 mh_check("held-row-carries-the-BASELINE-bytes" as *u8, (mh_find_sub(mh_out, mh_out_n[0], ex) >= 0) as i64) 471 let ex2: *u8 = sys_mmap(MH_FIXTURE_BYTES) 472 var e2: i64 = mh_row(ex2, 0, 122, "250" as *u8, "beta.nx" as *u8) 473 ex2[e2] = 0 as u8 474 mh_check("held-row-does-NOT-carry-the-CURRENT-bytes" as *u8, (mh_find_sub(mh_out, mh_out_n[0], ex2) < 0) as i64) 475 476 let ex3: *u8 = sys_mmap(MH_FIXTURE_BYTES) 477 var e3: i64 = mh_cat(ex3, 0, "delta.nx" as *u8) 478 ex3[e3] = 0 as u8 479 mh_check("held-without-a-baseline-row-is-OMITTED-so-it-resurfaces" as *u8, 480 ((mh_c[3] == 1) && (mh_find_sub(mh_out, mh_out_n[0], ex3) < 0)) as i64) 481 mh_check("neg-control-held-path-absent-from-current-is-REPORTED-not-dropped" as *u8, (mh_c[5] == 1) as i64) 482 // All three holdlist lines PARSED, and exactly one (zeta) failed to match the manifest. If the 483 // CRLF line were mis-keyed, beta would miss too and this would read 2 -- which is precisely how 484 // the live defect presented before it was fixed. 485 mh_check("crlf-holdline-is-not-silently-missed" as *u8, 486 ((mh_c[4] == 3) && (mh_c[5] == 1)) as i64) 487 488 // POSITIVE CONTROL. A guard that holds EVERYTHING passes every negative tooth above while being 489 // completely broken, so one scenario must prove the organ still ADVANCES. 490 let bb2: *u8 = sys_mmap(MH_FIXTURE_BYTES) 491 let cb2: *u8 = sys_mmap(MH_FIXTURE_BYTES) 492 var bn2: i64 = 0 493 bn2 = mh_row(bb2, bn2, 97, "100" as *u8, "alpha.nx" as *u8) 494 bn2 = mh_row(bb2, bn2, 98, "200" as *u8, "beta.nx" as *u8) 495 var cn2: i64 = 0 496 cn2 = mh_row(cb2, cn2, 97, "100" as *u8, "alpha.nx" as *u8) 497 cn2 = mh_row(cb2, cn2, 122, "250" as *u8, "beta.nx" as *u8) 498 mh_alloc(8, 8) 499 mh_out_n[0] = 0 500 mh_scan_hold(hb, 0) 501 mh_scan_mf(bb2, bn2, 0) 502 mh_scan_mf(cb2, cn2, 1) 503 mh_check("positive-control-empty-holdlist-advances-every-row" as *u8, 504 ((mh_c[1] == 2) && ((mh_c[2] == 0) && (mh_c[3] == 0))) as i64) 505 506 mh_puts("# MFHOLD selftest " as *u8); mh_putn(mh_t[0]) 507 mh_puts("/" as *u8); mh_putn(mh_t[1]); mh_puts("\n" as *u8) 508 if mh_t[0] == mh_t[1] { return 1 } 509 return 0 510} 511 512func main(argc: i64, argv: *i64) -> i64 { 513 mh_num = sys_mmap(MH_NUMBUF) 514 mh_rev = sys_mmap(MH_NUMBUF) 515 mh_c = sys_mmap(128) as *i64 516 mh_t = sys_mmap(32) as *i64 517 mh_bad = sys_mmap(16) as *i64 518 mh_out_n = sys_mmap(16) as *i64 519 mh_outcap = sys_mmap(16) as *i64 520 mh_bslots = sys_mmap(16) as *i64 521 mh_hslots = sys_mmap(16) as *i64 522 mh_bad[0] = 0 523 524 if argc < 5 { 525 mh_puts("usage: nx_mf_hold <baseline.mf> <current.mf> <holdlist> <out.mf>\n" as *u8) 526 mh_puts(" Emits <current> EXCEPT that every path in <holdlist> keeps its <baseline> row,\n" as *u8) 527 mh_puts(" or is OMITTED when the baseline has none so it resurfaces as B-ONLY next run.\n" as *u8) 528 mh_puts(" For a sync watermark: advance only where the reconcile was actually EARNED, so a\n" as *u8) 529 mh_puts(" file the run never examined is offered again instead of being blessed as synced.\n" as *u8) 530 mh_puts(" Manifests are nx_treehash rows. Self-tests every run and refuses to write on fail.\n" as *u8) 531 sys_exit(MH_USAGE) 532 return MH_USAGE 533 } 534 535 // THE SELFTEST RUNS BEFORE ANYTHING IS READ. A merge organ that cannot prove its own merge must 536 // not be handed a real watermark. Its tables are small and short-lived; the process exits below. 537 if mh_selftest() == 0 { 538 mh_puts("# MFHOLD RED -- selftest failed, REFUSING to write. The old baseline is untouched.\n" as *u8) 539 sys_exit(MH_SELFTEST_FAIL) 540 return MH_SELFTEST_FAIL 541 } 542 543 let lb: *i64 = sys_mmap(16) as *i64 544 let bbuf: *u8 = mh_slurp(argv[1] as *u8, lb) 545 if (bbuf as i64) == 0 { 546 mh_puts("# MFHOLD RED -- cannot read baseline manifest\n" as *u8) 547 sys_exit(MH_UNREADABLE); return MH_UNREADABLE 548 } 549 let lc: *i64 = sys_mmap(16) as *i64 550 let cbuf: *u8 = mh_slurp(argv[2] as *u8, lc) 551 if (cbuf as i64) == 0 { 552 mh_puts("# MFHOLD RED -- cannot read current manifest\n" as *u8) 553 sys_exit(MH_UNREADABLE); return MH_UNREADABLE 554 } 555 let lh: *i64 = sys_mmap(16) as *i64 556 let hbuf: *u8 = mh_slurp(argv[3] as *u8, lh) 557 if (hbuf as i64) == 0 { 558 // AN ABSENT HOLDLIST IS NOT AN EMPTY ONE. Treating "could not read" as "nothing to hold" 559 // would advance the whole watermark on exactly the failure this organ exists to prevent. 560 mh_puts("# MFHOLD RED -- cannot read holdlist. An unreadable holdlist is not an empty one;\n" as *u8) 561 mh_puts("# pass a zero-byte file to mean 'nothing was held'. Refusing to write.\n" as *u8) 562 sys_exit(MH_UNREADABLE); return MH_UNREADABLE 563 } 564 565 // Upper bounds from bytes: every row costs at least its minimum, so rows can never exceed this. 566 let bub: i64 = (lb[0] / MH_MIN_MF_ROW) + 1 567 let hub: i64 = (lh[0] / MH_MIN_HOLD_ROW) + 1 568 mh_alloc(bub, hub) 569 // Each emitted row is copied from current or from baseline, so their sum is a true ceiling. 570 mh_outcap[0] = lb[0] + lc[0] + MH_NUMBUF 571 mh_out = sys_mmap(mh_outcap[0]) 572 mh_out_n[0] = 0 573 574 mh_scan_hold(hbuf, lh[0]) 575 mh_scan_mf(bbuf, lb[0], 0) 576 mh_scan_mf(cbuf, lc[0], 1) 577 mh_sweep_hold() 578 579 let emitted: i64 = mh_c[1] + mh_c[2] 580 let sums: i64 = mh_c[1] + mh_c[2] + mh_c[3] 581 582 mh_puts("=== nx_mf_hold -- advance the watermark only where it was earned ===\n" as *u8) 583 mh_puts("# MFHOLD baseline_rows=" as *u8); mh_putn(mh_c[6]) 584 mh_puts(" current_rows=" as *u8); mh_putn(mh_c[0]) 585 mh_puts(" hold_rows=" as *u8); mh_putn(mh_c[4]) 586 mh_puts(" advanced=" as *u8); mh_putn(mh_c[1]) 587 mh_puts(" held=" as *u8); mh_putn(mh_c[2]) 588 mh_puts(" held_omitted=" as *u8); mh_putn(mh_c[3]) 589 mh_puts(" hold_not_in_current=" as *u8); mh_putn(mh_c[5]) 590 mh_puts(" emitted=" as *u8); mh_putn(emitted) 591 mh_puts("\n" as *u8) 592 593 if mh_bad[0] == 1 { 594 mh_puts("# MFHOLD RED -- a hash table filled. Refusing to write a partial watermark.\n" as *u8) 595 sys_exit(MH_PARTITION_FAIL); return MH_PARTITION_FAIL 596 } 597 if mh_bad[0] == 2 { 598 mh_puts("# MFHOLD RED -- output ceiling reached, which should be unreachable because it is\n" as *u8) 599 mh_puts("# derived from baseline+current bytes. Refusing to write a truncated watermark.\n" as *u8) 600 sys_exit(MH_PARTITION_FAIL); return MH_PARTITION_FAIL 601 } 602 // A PARTITION IS A CLAIM: CHECK THE PARTS SUM, AND PRINT THE CHECK. 603 if sums != mh_c[0] { 604 mh_puts("# MFHOLD RED -- partition does NOT reconcile: advanced+held+omitted=" as *u8) 605 mh_putn(sums) 606 mh_puts(" but current_rows=" as *u8); mh_putn(mh_c[0]) 607 mh_puts(". Refusing to write.\n" as *u8) 608 sys_exit(MH_PARTITION_FAIL); return MH_PARTITION_FAIL 609 } 610 mh_puts("# MFHOLD partition RECONCILES: advanced+held+omitted == current_rows == " as *u8) 611 mh_putn(mh_c[0]); mh_puts("\n" as *u8) 612 613 // sys_openat_wr carries no O_TRUNC: without this unlink a shorter watermark leaves the tail of the 614 // previous one live, and those trailing rows read as real manifest entries. 615 sys_unlinkat(argv[4] as *u8) 616 let fd: i64 = sys_openat_wr(argv[4] as *u8, MODE_0644) 617 if fd < 0 { 618 mh_puts("# MFHOLD RED -- cannot open outfile\n" as *u8) 619 sys_exit(MH_UNREADABLE); return MH_UNREADABLE 620 } 621 let wrote: i64 = sys_write(fd, mh_out, mh_out_n[0]) 622 sys_close(fd) 623 // ANNOUNCE THE PUBLISH so "did it land" is one number, never a hunt. 624 mh_puts("# MFHOLD wrote=" as *u8); mh_putn(wrote) 625 mh_puts(" of=" as *u8); mh_putn(mh_out_n[0]) 626 mh_puts(" path=" as *u8); mh_puts(argv[4] as *u8); mh_puts("\n" as *u8) 627 if wrote != mh_out_n[0] { 628 mh_puts("# MFHOLD RED -- short write; the watermark on disk is NOT the one measured above.\n" as *u8) 629 sys_exit(MH_PARTITION_FAIL); return MH_PARTITION_FAIL 630 } 631 if mh_c[2] + mh_c[3] == 0 { 632 mh_puts("# verdict=ADVANCED-IN-FULL (nothing was held: every path reconciled)\n" as *u8) 633 } 634 if mh_c[2] + mh_c[3] != 0 { 635 mh_puts("# verdict=ADVANCED-WITH-HOLDS -- the held paths stay divergent on purpose and WILL be\n" as *u8) 636 mh_puts("# offered again next run. That is the point: an unexamined file must not read as a sync.\n" as *u8) 637 } 638 sys_exit(MH_OK) 639 return MH_OK 640}