nx_mf_hold.nx
buildroot/runtime/nx_mf_hold.nx
about
nx_mf_hold.nx -- ADVANCE A SYNC WATERMARK EXCEPT WHERE IT WAS NEVER EARNED.
WHY IT EXISTS (measured 2026-08-16, treecanon flow-back lane):
sync_nas_ahead.ps1 ended with `if ($Apply) { Copy-Item $cur $base -Force }` -- it advanced the
baseline manifest to the CURRENT NAS manifest WHOLESALE. For a REFUSE that is correct: the file
was examined, declined, and durably queued in conflicts.log for conflicts.ps1 to rank. But a
FETCH FAILURE `continue`d BEFORE that ledger append, so it was recorded NOWHERE -- and the
watermark then advanced past a file the lane had never even looked at, marking it synced
permanently and silently.
=> A TRANSIENT FAILURE THAT IS NEITHER RETRIED NOR RECORDED IS A PERMANENT ONE.
SCOPE CORRECTION WORTH KEEPING, because the first version of this header got it wrong: the
watermark answers "what changed on the NAS", NOT "what is still unreconciled". Those are different
questions with different readers, and conflicts.log/conflicts.ps1 already answer the second (837
distinct files / 1185 events, measured the same day). Holding every refusal here would re-fetch all
837 on every hourly run to rebuild a worklist that already exists -- a duplicate ruler that also
hammers the NAS. BEFORE BUILDING A QUEUE, CHECK WHETHER THE REFUSAL LEDGER IS ALREADY ONE.
WHAT IT DOES
out = current, EXCEPT that any path named in holdlist keeps its BASELINE row -- or is OMITTED
entirely when the baseline has no row for it, so it re-surfaces next run as B-ONLY rather than
being silently blessed.
PER-PATH, deliberately. Holding the WHOLE watermark whenever anything was held would let one
stuck file pin it forever and turn every later run into a full re-fetch: one silence traded for
one flood.
SELF-TESTS ON EVERY RUN AND REFUSES TO WRITE IF ANY TOOTH FAILS. Precedent in this same lane:
nx_normdiff withholds its verdict when its supersede predicate regresses, so the failure mode is
"no adopt", never "wrong adopt". Emitting a CORRUPT baseline is strictly worse than emitting none,
because a corrupt watermark is indistinguishable from a converged one.
ROW FORMAT: "<64hex> <bytes> <relpath>\n" -- what nx_treehash writes and nx_hashdiverge parses.
IMPRECISION ACCEPTED AND NAMED: relpath is the bytes between the second space and the newline, so
a relpath CONTAINING A SPACE parses short. nx_treehash never emits one. The parse is deliberately
the SAME SHAPE as nx_hashdiverge's rather than a cleverer one, because two parsers that disagree
under the same input is the duplicate-ruler defect wearing a bugfix.
sys_openat_wr has NO O_TRUNC, so a shorter rewrite over a longer file leaves a live tail -- on a
BASELINE manifest that is corrupt trailing rows that read as real. There is no sys_openat_trunc
(proven absent over buildroot/runtime, corpus_complete=1), so: UNLINK FIRST.
dependencies 1 imports · 0 importers
imports: nx_syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 58 | const MH_SHA_CHARS: i64 = 64 |
| 59 | const MH_NUMBUF: i64 = 64 |
| 60 | const MH_FNV_OFF: i64 = 1469598103934665603 |
| 61 | const MH_FNV_PRIME: i64 = 1099511628211 |
| 63 | const MH_MIN_MF_ROW: i64 = MH_SHA_CHARS + 4 |
| 65 | const MH_MIN_HOLD_ROW: i64 = 2 |
| 67 | const MH_LOAD_NUM: i64 = 4 |
| 68 | const MH_MIN_SLOTS: i64 = 16 |
| 69 | const MH_FIXTURE_BYTES: i64 = 4096 |
| 70 | const MH_OK: i64 = 0 |
| 71 | const MH_PARTITION_FAIL: i64 = 1 |
| 72 | const MH_USAGE: i64 = 2 |
| 73 | const MH_UNREADABLE: i64 = 3 |
| 74 | const MH_SELFTEST_FAIL: i64 = 4 |
functions
| 92 | func mh_puts(s: *u8) -> i64 |
| 98 | func mh_putn(v: i64) -> i64 |
| 109 | func mh_slen(s: *u8) -> i64 |
| 114 | func mh_streq(a: *u8, b: *u8) -> i64 |
| 125 | func mh_hash(s: *u8) -> i64 |
| 137 | func mh_pow2ge(v: i64) -> i64 called by 1: mh_alloc |
| 144 | func mh_bput(rel: *u8, sha: *u8, sz: *u8) -> i64 |
| 163 | func mh_bfind(rel: *u8) -> i64 |
| 177 | func mh_hput(rel: *u8) -> i64 |
| 195 | func mh_hfind(rel: *u8) -> i64 |
| 208 | func mh_emit_row(sha: *u8, sz: *u8, rel: *u8) -> i64 |
| 227 | func mh_scan_mf(buf: *u8, n: i64, side: i64) -> i64 |
| 283 | func mh_scan_hold(buf: *u8, n: i64) -> i64 |
| 318 | func mh_sweep_hold() -> i64 |
| 331 | func mh_slurp(path: *u8, lenout: *i64) -> *u8 |
| 347 | func mh_alloc(brows: i64, hrows: i64) -> i64 |
| 361 | func mh_cat(dst: *u8, off: i64, s: *u8) -> i64 |
| 367 | func mh_fill(dst: *u8, off: i64, ch: i64, cnt: i64) -> i64 called by 1: mh_row |
| 375 | func mh_row(dst: *u8, off: i64, shachar: i64, sz: *u8, path: *u8) -> i64 |
| 384 | func mh_holdline(dst: *u8, off: i64, path: *u8) -> i64 |
| 391 | func mh_holdline_crlf(dst: *u8, off: i64, path: *u8) -> i64 |
| 397 | func mh_find_sub(hay: *u8, hn: i64, needle: *u8) -> i64 |
| 415 | func mh_check(name: *u8, cond: i64) -> i64 |
| 427 | func mh_selftest() -> i64 |
| 512 | func main(argc: i64, argv: *i64) -> i64 |