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}