code wiki / (root) / nx_segopen_incr_gate.nx

nx_segopen_incr_gate.nx source

↩ module page · 512 lines · 27743 B

1// nx_segopen_incr_gate.nx -- THE REFEREE FOR ss_open3's INCREMENTAL, WARM-PRESERVING REOPEN (search plan rung L1 2// ss_open_incr, 2026-09-02). THE CLAIM UNDER TEST: when the manifest is a strict prefix-extension of the handle a 3// caller already holds, ss_open3(prefix, 1, prev, 1) reuses prev's segment mappings, live maps and key table, loads 4// only the new rows, clears exactly the old marks the new rows shadow, and produces a handle whose every live map 5// and every key->newest-row answer is BYTE-IDENTICAL to a FULL open of the same manifest. Any admission failure 6// (no table retained, a leading name differs, a leading .docs size differs on disk, the manifest shrank) must fall 7// back to FULL, ANNOUNCE its reason, and leave prev untouched. 8// FIXTURE, built at runtime in /tmp/<gate>/ (never knowledge/store, which the segguard beat sweeps): three rows 9// with overlapping keys -- A puts doc:1..40; B re-puts doc:11..20 and tombstones doc:21..25; C re-puts doc:16..18, 10// tombstones doc:31..35 and adds doc:41..50 -- so the incremental step must clear marks in BOTH older rows (3 in B, 11// 5 in A = 8) through a put AND a tombstone, and leave A's already-dead doc:21..25 alone. The expected mark counts 12// are computed from the fixture, never read back from the subject: A 25 -> 20, B 10 -> 7, C 13 of 18. 13// EVERY OUTCOME TOOTH IS PAIRED WITH A FIXTURE-REACHED TOOTH: mapping identity (the moved .docs pointer IS prev's), 14// prev gutted after the move, the value read through the moved mapping AFTER prev is closed. 15// GREEN iff every tooth passes; neg-controls named neg-control-* so the gate-law census can count them. 16// license_tier: ORIGINAL expect_exit: 0 17import "nx_seg_store.nx" 18import "nx_syscalls.nx" 19import "nx_gate_verdict.nx" 20 21const SI_DIR: *u8 = "/tmp/nx_segopen_incr_gate" 22const SI_PREFIX: *u8 = "/tmp/nx_segopen_incr_gate/soi-" 23const SI_ERR_PREV: *u8 = "/tmp/nx_segopen_incr_gate/stderr-prev.txt" 24const SI_ERR_INCR: *u8 = "/tmp/nx_segopen_incr_gate/stderr-incr.txt" 25const SI_ERR_FULL: *u8 = "/tmp/nx_segopen_incr_gate/stderr-full.txt" 26const SI_ERR_NEG1: *u8 = "/tmp/nx_segopen_incr_gate/stderr-neg-notable.txt" 27const SI_ERR_NEG2: *u8 = "/tmp/nx_segopen_incr_gate/stderr-neg-prefix.txt" 28const SI_ERR_NEG3: *u8 = "/tmp/nx_segopen_incr_gate/stderr-neg-size.txt" 29const SI_ERR_NEG4: *u8 = "/tmp/nx_segopen_incr_gate/stderr-neg-shrank.txt" 30const SI_ERR_FOLD: *u8 = "/tmp/nx_segopen_incr_gate/stderr-fold.txt" 31const SI_ERR_FOLDFULL: *u8 = "/tmp/nx_segopen_incr_gate/stderr-fold-full.txt" 32const SI_ERR_FS: *u8 = "/tmp/nx_segopen_incr_gate/stderr-fold-ship.txt" 33const SI_ERR_UN: *u8 = "/tmp/nx_segopen_incr_gate/stderr-uncovered.txt" 34const SI_ERR_UNFULL: *u8 = "/tmp/nx_segopen_incr_gate/stderr-uncovered-full.txt" 35const SI_ERR_REO: *u8 = "/tmp/nx_segopen_incr_gate/stderr-reorder.txt" 36const SI_ERR_REOFULL: *u8 = "/tmp/nx_segopen_incr_gate/stderr-reorder-full.txt" 37const SI_D_LIVE: i64 = 27 // D = latest of A u B: 35 puts, minus C re-puts 16..18 and tombstones 31..35 38const SI_D_LIVE_AFTER_E: i64 = 24 // E re-puts doc:1..3 39const SI_E_LO: i64 = 1 40const SI_E_HI: i64 = 3 41const SI_E_RECS: i64 = 3 42const SI_FOLD_ENTRIES: i64 = 58 // 40 + 18 key entries over [D,C] 43const SI_MODE_DIR: i64 = 493 // 0755 44const SI_MODE_FILE: i64 = 420 // 0644 45const SI_SEGCAP: i64 = 65536 46const SI_KIND_LIVE: i64 = 1 47const SI_KIND_TOMB: i64 = 2 48const SI_KEYCAP: i64 = 64 49const SI_VALCAP: i64 = 64 50const SI_PATHCAP: i64 = 512 51const SI_MANCAP: i64 = 4096 52const SI_SAVED_FD: i64 = 20 // where fd 2 is parked while an open's stderr is captured 53const SI_A_LO: i64 = 1 // row A: doc:1..40 live 54const SI_A_HI: i64 = 40 55const SI_B_LO: i64 = 11 // row B: doc:11..20 re-put, doc:21..25 tombstoned -> 15 records 56const SI_B_HI: i64 = 20 57const SI_B_TLO: i64 = 21 58const SI_B_THI: i64 = 25 59const SI_C_LO: i64 = 16 // row C: doc:16..18 re-put, doc:31..35 tombstoned, doc:41..50 new -> 18 records 60const SI_C_HI: i64 = 18 61const SI_C_TLO: i64 = 31 62const SI_C_THI: i64 = 35 63const SI_C_NLO: i64 = 41 64const SI_C_NHI: i64 = 50 65const SI_A_RECS: i64 = 40 66const SI_B_RECS: i64 = 15 67const SI_C_RECS: i64 = 18 68const SI_A_LIVE_PREV: i64 = 25 // 40 - 10 re-put by B - 5 tombstoned by B 69const SI_B_LIVE_PREV: i64 = 10 70const SI_A_LIVE_AFTER: i64 = 20 // 25 - 5 tombstoned by C 71const SI_B_LIVE_AFTER: i64 = 7 // 10 - 3 re-put by C 72const SI_C_LIVE: i64 = 13 // 3 re-puts + 10 new; the 5 tombstones carry no mark 73const SI_SHADOWED: i64 = 8 // 3 in B + 5 in A 74const SI_ENTRIES: i64 = 73 // 40 + 15 + 18 key entries the table comparison must cover 75const SI_SUBJECTS: i64 = 16 // opens driven: prev, incr, full, no-table x2, size, reorder x3, shrank x3, fold x2, fold-ship, uncovered x2 76 77func si_key(out: *u8, i: i64) -> i64 { 78 var o: i64 = 0 79 o = ss_cat(out, o, "doc:" as *u8) 80 o = ss_catn(out, o, i) 81 out[o] = 0 as u8 82 return o 83} 84func si_manifest_path(prefix: *u8, out: *u8) -> i64 { 85 var o: i64 = ss_cat(out, 0, prefix) 86 o = ss_cat(out, o, "manifest.txt" as *u8) 87 out[o] = 0 as u8 88 return o 89} 90func si_reset(prefix: *u8) -> i64 { 91 let mf: *u8 = sys_mmap(SI_PATHCAP) 92 si_manifest_path(prefix, mf) 93 let e: *u8 = sys_mmap(16) 94 ss_writefile(mf, e, 0) 95 return 0 96} 97func si_marks(h: *i64, s: i64, n: i64) -> i64 { 98 let ns: i64 = h[0] 99 let lm: *u8 = h[1 + 8 * ns + s] as *u8 100 if (lm as i64) == 0 { return 0 - 1 } 101 var c: i64 = 0 102 var i: i64 = 0 103 while i < n { if lm[i] == (1 as u8) { c = c + 1 } i = i + 1 } 104 return c 105} 106func si_seg_path(prefix: *u8, segname: *u8, suffix: *u8, out: *u8) -> i64 { 107 var o: i64 = ss_cat(out, 0, prefix) 108 o = ss_cat(out, o, segname) 109 o = ss_cat(out, o, suffix) 110 out[o] = 0 as u8 111 return o 112} 113func si_contains(hay: *u8, hn: i64, needle: *u8) -> i64 { 114 var nn: i64 = 0 115 while needle[nn] != (0 as u8) { nn = nn + 1 } 116 if hn < nn { return 0 } 117 var i: i64 = 0 118 while i + nn <= hn { 119 var j: i64 = 0 120 var ok: i64 = 1 121 while j < nn { if hay[i + j] != needle[j] { ok = 0; break } j = j + 1 } 122 if ok == 1 { return 1 } 123 i = i + 1 124 } 125 return 0 126} 127// run ONE ss_open3 with fd 2 redirected to errpath, restore fd 2, return the handle 128func si_open_captured(prefix: *u8, errpath: *u8, prev: *i64, retain: i64) -> *i64 { 129 let efd: i64 = sys_openat_wr(errpath, SI_MODE_FILE) 130 sys_dup3(2, SI_SAVED_FD, 0) 131 if efd >= 0 { sys_dup3(efd, 2, 0) } 132 let h: *i64 = ss_open3(prefix, 1, prev, retain) 133 sys_dup3(SI_SAVED_FD, 2, 0) 134 if efd >= 0 { sys_close(efd) } 135 return h 136} 137func si_err_has(errpath: *u8, needle: *u8) -> i64 { 138 let n: *i64 = sys_mmap(16) as *i64 139 let b: *u8 = ss_readall(errpath, n) 140 var len: i64 = n[0] 141 if len < 0 { len = 0 } 142 return si_contains(b, len, needle) 143} 144func si_add_range(wr: *i64, kind: i64, lo: i64, hi: i64, tag: *u8) -> i64 { 145 let key: *u8 = sys_mmap(SI_KEYCAP) 146 let val: *u8 = sys_mmap(SI_VALCAP) 147 var i: i64 = lo 148 var added: i64 = 0 149 while i <= hi { 150 si_key(key, i) 151 var vn: i64 = 0 152 if kind == SI_KIND_LIVE { vn = ss_cat(val, 0, tag); vn = ss_catn(val, vn, i) } 153 val[vn] = 0 as u8 154 if ss_add(wr, kind, key, val, vn) == 0 { added = added + 1 } 155 i = i + 1 156 } 157 sys_munmap(key, SI_KEYCAP) 158 sys_munmap(val, SI_VALCAP) 159 return added 160} 161// 1 iff ss_hget finds key LIVE through this handle with exactly the expected value bytes 162func si_val_is(h: *i64, key: *u8, expect: *u8) -> i64 { 163 let po: *i64 = sys_mmap(16) as *i64 164 let lo: *i64 = sys_mmap(16) as *i64 165 let r: i64 = ss_hget(h, key, po, lo) 166 if r != 1 { return 0 } 167 let v: *u8 = po[0] as *u8 168 let vl: i64 = lo[0] 169 var i: i64 = 0 170 while expect[i] != (0 as u8) { if i >= vl { return 0 } if v[i] != expect[i] { return 0 } i = i + 1 } 171 if i != vl { return 0 } 172 return 1 173} 174func si_hget_kind(h: *i64, key: *u8) -> i64 { 175 let po: *i64 = sys_mmap(16) as *i64 176 let lo: *i64 = sys_mmap(16) as *i64 177 return ss_hget(h, key, po, lo) 178} 179// byte-compare row s's live map between two handles over the map's own extent; adds to compared, returns mismatches 180func si_lm_diff(ha: *i64, hb: *i64, s: i64, compared: *i64) -> i64 { 181 let na: i64 = ha[0] 182 let nb: i64 = hb[0] 183 let la: *u8 = ha[1 + 8 * na + s] as *u8 184 let lb: *u8 = hb[1 + 8 * nb + s] as *u8 185 let ext: i64 = ha[4 + 8 * s] / 9 + 16 186 if hb[4 + 8 * s] != ha[4 + 8 * s] { return ext } 187 if (la as i64) == 0 { return ext } 188 if (lb as i64) == 0 { return ext } 189 var mm: i64 = 0 190 var i: i64 = 0 191 while i < ext { if la[i] != lb[i] { mm = mm + 1 } i = i + 1 } 192 compared[0] = compared[0] + ext 193 return mm 194} 195// every key entry of every row asked of BOTH retained tables: newest row must agree; adds to compared, returns mismatches 196func si_table_diff(ha: *i64, hb: *i64, compared: *i64) -> i64 { 197 let ta: *i64 = sys_mmap(32) as *i64 198 let tb: *i64 = sys_mmap(32) as *i64 199 if ss_open_table(ha, ta) == 0 { return 0 - 1 } 200 if ss_open_table(hb, tb) == 0 { return 0 - 1 } 201 let ns: i64 = ha[0] 202 var mm: i64 = 0 203 var s: i64 = 0 204 while s < ns { 205 let kb: *u8 = ha[1 + 8 * s] as *u8 206 let ksz: i64 = ha[2 + 8 * s] 207 if ksz >= 8 { 208 let m: i64 = ss_r32(kb, 4) 209 var e: i64 = 0 210 while e < m { 211 let eo: i64 = 8 + 4 * m + ss_r32(kb, 8 + 4 * e) 212 let ra: i64 = ssl_lookup(ha, ta[0] as *i64, ta[1] as *i64, ta[2], kb, eo) 213 let rb: i64 = ssl_lookup(hb, tb[0] as *i64, tb[1] as *i64, tb[2], kb, eo) 214 if ra != rb { mm = mm + 1 } 215 compared[0] = compared[0] + 1 216 e = e + 1 217 } 218 } 219 s = s + 1 220 } 221 return mm 222} 223// rewrite the manifest as the rows of h in the given order (a neg-control fixture; restored afterwards) 224func si_write_manifest(prefix: *u8, h: *i64, order: *i64, n: i64) -> i64 { 225 let mf: *u8 = sys_mmap(SI_PATHCAP) 226 si_manifest_path(prefix, mf) 227 let b: *u8 = sys_mmap(SI_MANCAP) 228 var o: i64 = 0 229 var i: i64 = 0 230 while i < n { 231 let nm: *u8 = ss_open_segname(h, order[i]) 232 if (nm as i64) == 0 { return 0 - 1 } 233 o = ss_cat(b, o, nm) 234 b[o] = 10 as u8 235 o = o + 1 236 i = i + 1 237 } 238 ss_writefile(mf, b, o) 239 sys_munmap(b, SI_MANCAP) 240 sys_munmap(mf, SI_PATHCAP) 241 return n 242} 243 244func si_write_manifest_names(prefix: *u8, names: *i64, order: *i64, n: i64) -> i64 { 245 let mf: *u8 = sys_mmap(SI_PATHCAP) 246 si_manifest_path(prefix, mf) 247 let b: *u8 = sys_mmap(SI_MANCAP) 248 var o: i64 = 0 249 var i: i64 = 0 250 while i < n { 251 o = ss_cat(b, o, names[order[i]] as *u8) 252 b[o] = 10 as u8 253 o = o + 1 254 i = i + 1 255 } 256 ss_writefile(mf, b, o) 257 sys_munmap(b, SI_MANCAP) 258 sys_munmap(mf, SI_PATHCAP) 259 return n 260} 261func main(argc: i64, argv: *i64) -> i64 { 262 gv_head("nx_segopen_incr_gate -- ss_open3 incremental reopen is byte-identical to a FULL open and refuses honestly" as *u8) 263 let ctr: *i64 = gv_ctr() 264 gv_subjects("shard opens driven" as *u8, SI_SUBJECTS, ctr) 265 266 sys_mkdir(SI_DIR, SI_MODE_DIR) 267 si_reset(SI_PREFIX) 268 let key: *u8 = sys_mmap(SI_KEYCAP) 269 270 // ---- row A ---- 271 let wa: *i64 = ss_begin_cap(SI_SEGCAP) 272 let addedA: i64 = si_add_range(wa, SI_KIND_LIVE, SI_A_LO, SI_A_HI, "A" as *u8) 273 gv_check("fixture-setup-row-A-40-puts-added" as *u8, addedA == SI_A_RECS, ctr) 274 gv_check("fixture-setup-row-A-committed" as *u8, ss_commit(SI_PREFIX, wa, ss_next_segid(SI_PREFIX)) == 0, ctr) 275 // ---- row B ---- 276 let wb: *i64 = ss_begin_cap(SI_SEGCAP) 277 var addedB: i64 = si_add_range(wb, SI_KIND_LIVE, SI_B_LO, SI_B_HI, "B" as *u8) 278 addedB = addedB + si_add_range(wb, SI_KIND_TOMB, SI_B_TLO, SI_B_THI, "B" as *u8) 279 gv_check("fixture-setup-row-B-10-puts-5-tombstones-added" as *u8, addedB == SI_B_RECS, ctr) 280 gv_check("fixture-setup-row-B-committed" as *u8, ss_commit(SI_PREFIX, wb, ss_next_segid(SI_PREFIX)) == 0, ctr) 281 282 // ---- prev: the handle a daemon parent would already hold (FULL, table retained) ---- 283 let prev: *i64 = si_open_captured(SI_PREFIX, SI_ERR_PREV, 0 as *i64, 1) 284 gv_check("prev-open-sees-two-rows" as *u8, prev[0] == 2, ctr) 285 gv_check("prev-open-mode-is-FULL" as *u8, ss_open_mode(prev) == SS3_MODE_FULL, ctr) 286 let tprev: *i64 = sys_mmap(32) as *i64 287 gv_check("prev-open-retained-its-key-table" as *u8, ss_open_table(prev, tprev) == 1, ctr) 288 gv_check("prev-row-A-marks-25-live-of-40" as *u8, si_marks(prev, 0, SI_A_RECS) == SI_A_LIVE_PREV, ctr) 289 gv_check("prev-row-B-marks-10-live-of-15" as *u8, si_marks(prev, 1, SI_B_RECS) == SI_B_LIVE_PREV, ctr) 290 gv_check("neg-control-an-open-with-no-prev-announces-nothing" as *u8, si_err_has(SI_ERR_PREV, "SSOPEN" as *u8) == 0, ctr) 291 let pa_docs: i64 = prev[3] 292 let pa_lm: i64 = prev[1 + 8 * 2] 293 gv_check("fixture-reached-prev-row-A-docs-mapped" as *u8, pa_docs != 0, ctr) 294 295 // ---- row C ships: the manifest becomes a strict prefix-extension ---- 296 let wc: *i64 = ss_begin_cap(SI_SEGCAP) 297 var addedC: i64 = si_add_range(wc, SI_KIND_LIVE, SI_C_LO, SI_C_HI, "C" as *u8) 298 addedC = addedC + si_add_range(wc, SI_KIND_TOMB, SI_C_TLO, SI_C_THI, "C" as *u8) 299 addedC = addedC + si_add_range(wc, SI_KIND_LIVE, SI_C_NLO, SI_C_NHI, "C" as *u8) 300 gv_check("fixture-setup-row-C-3-reputs-5-tombstones-10-new-added" as *u8, addedC == SI_C_RECS, ctr) 301 gv_check("fixture-setup-row-C-committed" as *u8, ss_commit(SI_PREFIX, wc, ss_next_segid(SI_PREFIX)) == 0, ctr) 302 303 // ---- the reopen under test ---- 304 let hi: *i64 = si_open_captured(SI_PREFIX, SI_ERR_INCR, prev, 1) 305 gv_check("incr-open-sees-three-rows" as *u8, hi[0] == 3, ctr) 306 gv_check("incr-open-mode-is-INCR" as *u8, ss_open_mode(hi) == SS3_MODE_INCR, ctr) 307 gv_check("incr-open-announces-reused-2-built-1-shadowed-8" as *u8, si_err_has(SI_ERR_INCR, "SSOPEN INCR reused=2 built=1 shadowed=8" as *u8) == 1, ctr) 308 gv_check("incr-row-A-docs-mapping-IS-prev's-mapping-not-a-remap" as *u8, hi[3] == pa_docs, ctr) 309 gv_check("incr-row-A-live-map-IS-prev's-map-corrected-in-place" as *u8, hi[1 + 8 * 3] == pa_lm, ctr) 310 gv_check("incr-prev-row-A-pointers-gutted-so-closing-prev-cannot-free-them" as *u8, prev[3] == 0, ctr) 311 gv_check("incr-prev-live-map-slot-gutted" as *u8, prev[1 + 8 * 2] == 0, ctr) 312 gv_check("incr-prev-table-slot-gutted" as *u8, ss_open_table(prev, tprev) == 0, ctr) 313 gv_check("incr-row-A-marks-20-live-after-C-tombstoned-5" as *u8, si_marks(hi, 0, SI_A_RECS) == SI_A_LIVE_AFTER, ctr) 314 gv_check("incr-row-B-marks-7-live-after-C-reput-3" as *u8, si_marks(hi, 1, SI_B_RECS) == SI_B_LIVE_AFTER, ctr) 315 gv_check("incr-row-C-marks-13-live-of-18" as *u8, si_marks(hi, 2, SI_C_RECS) == SI_C_LIVE, ctr) 316 ss_close(prev) 317 si_key(key, 1) 318 gv_check("incr-reads-row-A-value-through-the-moved-mapping-AFTER-prev-closed" as *u8, si_val_is(hi, key, "A1" as *u8) == 1, ctr) 319 si_key(key, 16) 320 gv_check("incr-newest-row-wins-doc16-reads-C16" as *u8, si_val_is(hi, key, "C16" as *u8) == 1, ctr) 321 si_key(key, 33) 322 gv_check("incr-tombstone-in-new-row-shadows-old-put-doc33-reads-tombstoned" as *u8, si_hget_kind(hi, key) == 0, ctr) 323 si_key(key, 23) 324 gv_check("incr-already-dead-doc23-stays-tombstoned" as *u8, si_hget_kind(hi, key) == 0, ctr) 325 si_key(key, 45) 326 gv_check("incr-new-key-doc45-reads-C45" as *u8, si_val_is(hi, key, "C45" as *u8) == 1, ctr) 327 328 // ---- the reference: a FULL open of the same manifest ---- 329 let hf: *i64 = si_open_captured(SI_PREFIX, SI_ERR_FULL, 0 as *i64, 1) 330 gv_check("full-open-mode-is-FULL" as *u8, ss_open_mode(hf) == SS3_MODE_FULL, ctr) 331 gv_check("full-row-A-marks-20" as *u8, si_marks(hf, 0, SI_A_RECS) == SI_A_LIVE_AFTER, ctr) 332 gv_check("full-row-B-marks-7" as *u8, si_marks(hf, 1, SI_B_RECS) == SI_B_LIVE_AFTER, ctr) 333 gv_check("full-row-C-marks-13" as *u8, si_marks(hf, 2, SI_C_RECS) == SI_C_LIVE, ctr) 334 let cmp: *i64 = sys_mmap(16) as *i64 335 cmp[0] = 0 336 var lm_mm: i64 = 0 337 var r: i64 = 0 338 while r < 3 { lm_mm = lm_mm + si_lm_diff(hi, hf, r, cmp); r = r + 1 } 339 gv_check("equivalence-live-maps-compared-over-every-row-nonzero-bytes" as *u8, cmp[0] > 0, ctr) 340 gv_check("equivalence-live-maps-BYTE-IDENTICAL-incr-vs-full" as *u8, lm_mm == 0, ctr) 341 let cmp2: *i64 = sys_mmap(16) as *i64 342 cmp2[0] = 0 343 let tb_mm: i64 = si_table_diff(hi, hf, cmp2) 344 gv_check("equivalence-table-compared-every-key-entry-73" as *u8, cmp2[0] == SI_ENTRIES, ctr) 345 gv_check("equivalence-table-newest-row-answers-IDENTICAL-incr-vs-full" as *u8, tb_mm == 0, ctr) 346 347 // ---- neg-control 1: a prev opened WITHOUT retain has no table -> FULL, announced, prev untouched ---- 348 let h0: *i64 = ss_open3(SI_PREFIX, 1, 0 as *i64, 0) 349 gv_check("neg-control-fixture-reached-retain-0-open-holds-no-table" as *u8, ss_open_table(h0, tprev) == 0, ctr) 350 let hx: *i64 = si_open_captured(SI_PREFIX, SI_ERR_NEG1, h0, 1) 351 gv_check("neg-control-no-table-falls-back-to-FULL" as *u8, ss_open_mode(hx) == SS3_MODE_FULL, ctr) 352 gv_check("neg-control-no-table-announces-its-reason" as *u8, si_err_has(SI_ERR_NEG1, "SSOPEN FULL reason=no-table" as *u8) == 1, ctr) 353 gv_check("neg-control-no-table-leaves-prev-untouched" as *u8, h0[3] != 0, ctr) 354 ss_close(hx) 355 ss_close(h0) 356 357 // ---- neg-control 3: a leading row's .docs changed size on disk -> FULL docs-size-mismatch (prev untouched) ---- 358 let pdocs: *u8 = sys_mmap(SI_PATHCAP) 359 let phid: *u8 = sys_mmap(SI_PATHCAP) 360 si_seg_path(SI_PREFIX, ss_open_segname(hf, 0), ".docs" as *u8, pdocs) 361 si_seg_path(SI_PREFIX, ss_open_segname(hf, 0), ".docs.hidden" as *u8, phid) 362 let mrc: i64 = sys_renameat(pdocs, phid) 363 let one: *u8 = sys_mmap(16) 364 one[0] = 1 as u8 365 ss_writefile(pdocs, one, 1) 366 gv_check("neg-control-fixture-reached-row-A-docs-replaced-by-1-byte" as *u8, mrc == 0, ctr) 367 let hz: *i64 = si_open_captured(SI_PREFIX, SI_ERR_NEG3, hf, 1) 368 gv_check("size-changed-row-is-RELOADED-siblings-reused-mode-INCR" as *u8, ss_open_mode(hz) == SS3_MODE_INCR, ctr) 369 gv_check("size-changed-row-announces-reused-2-built-1" as *u8, si_err_has(SI_ERR_NEG3, "SSOPEN INCR reused=2 built=1" as *u8) == 1, ctr) 370 gv_check("size-changed-row-announces-vanished-1-the-old-A" as *u8, si_err_has(SI_ERR_NEG3, "INCR-FOLD vanished=1" as *u8) == 1, ctr) 371 gv_check("size-changed-row-A-was-NOT-reused-its-old-mapping-stays-with-prev" as *u8, hf[3] != 0, ctr) 372 gv_check("size-changed-row-siblings-B-C-moved-out-of-prev" as *u8, hf[3 + 8 * 1] == 0, ctr) 373 ss_close(hz) 374 gv_check("fixture-restored-row-A-docs" as *u8, sys_renameat(phid, pdocs) == 0, ctr) 375 ss_close(hf) 376 377 // canonical names of [A,B,C], captured from hi while it still owns them: every restore below writes these 378 let nmABC: *i64 = sys_mmap(32) as *i64 379 nmABC[0] = ss_open_segname(hi, 0) as i64 380 nmABC[1] = ss_open_segname(hi, 1) as i64 381 nmABC[2] = ss_open_segname(hi, 2) as i64 382 let ordABC: *i64 = sys_mmap(32) as *i64 383 ordABC[0] = 0 384 ordABC[1] = 1 385 ordABC[2] = 2 386 // ---- REORDER [B,A,C] (rung L1b): C is a common suffix, A and B vanish and come back as NEW rows in the 387 // new order, so the fold path serves it and must equal a FULL open of [B,A,C] (A now shadows B) ---- 388 let hr0: *i64 = ss_open3(SI_PREFIX, 1, 0 as *i64, 1) 389 let order: *i64 = sys_mmap(32) as *i64 390 order[0] = 1 391 order[1] = 0 392 order[2] = 2 393 gv_check("reorder-fixture-reached-manifest-rewritten-B-A-C" as *u8, si_write_manifest(SI_PREFIX, hr0, order, 3) == 3, ctr) 394 let hr: *i64 = si_open_captured(SI_PREFIX, SI_ERR_REO, hr0, 1) 395 gv_check("reorder-is-served-incrementally" as *u8, ss_open_mode(hr) == SS3_MODE_INCR, ctr) 396 gv_check("reorder-announces-reused-1-built-2" as *u8, si_err_has(SI_ERR_REO, "SSOPEN INCR reused=1 built=2" as *u8) == 1, ctr) 397 gv_check("reorder-announces-vanished-2" as *u8, si_err_has(SI_ERR_REO, "INCR-FOLD vanished=2" as *u8) == 1, ctr) 398 ss_close(hr0) 399 let hrf: *i64 = si_open_captured(SI_PREFIX, SI_ERR_REOFULL, 0 as *i64, 1) 400 let cmpr: *i64 = sys_mmap(16) as *i64 401 cmpr[0] = 0 402 var rmm: i64 = 0 403 var rr: i64 = 0 404 while rr < 3 { rmm = rmm + si_lm_diff(hr, hrf, rr, cmpr); rr = rr + 1 } 405 gv_check("reorder-live-maps-compared-nonzero-bytes" as *u8, cmpr[0] > 0, ctr) 406 gv_check("reorder-live-maps-BYTE-IDENTICAL-to-FULL" as *u8, rmm == 0, ctr) 407 let cmpr2: *i64 = sys_mmap(16) as *i64 408 cmpr2[0] = 0 409 gv_check("reorder-table-answers-IDENTICAL-to-FULL" as *u8, si_table_diff(hr, hrf, cmpr2) == 0, ctr) 410 gv_check("reorder-table-compared-every-entry-73" as *u8, cmpr2[0] == SI_ENTRIES, ctr) 411 si_key(key, 23) 412 gv_check("reorder-A-now-newer-than-B-doc23-reads-A23" as *u8, si_val_is(hr, key, "A23" as *u8) == 1, ctr) 413 ss_close(hr) 414 ss_close(hrf) 415 gv_check("reorder-fixture-restored-A-B-C-from-canonical-names" as *u8, si_write_manifest_names(SI_PREFIX, nmABC, ordABC, 3) == 3, ctr) 416 417 // ---- PURE REMOVAL [A,B] (C gone, nothing replaces it): C's keys vanish uncovered, their next-newest entries 418 // in A and B become live again and the table cannot name them -> rebuilt in place, announced as FULL ---- 419 let hs0: *i64 = ss_open3(SI_PREFIX, 1, 0 as *i64, 1) 420 gv_check("removal-fixture-prev-is-A-B-C-again" as *u8, hs0[0] == 3, ctr) 421 gv_check("removal-fixture-reached-manifest-shrunk-to-two-rows" as *u8, si_write_manifest_names(SI_PREFIX, nmABC, ordABC, 2) == 2, ctr) 422 let hs: *i64 = si_open_captured(SI_PREFIX, SI_ERR_NEG4, hs0, 1) 423 gv_check("removal-falls-back-to-FULL-rebuilt-in-place" as *u8, ss_open_mode(hs) == SS3_MODE_FULL, ctr) 424 gv_check("removal-announces-uncovered-vanished-keys" as *u8, si_err_has(SI_ERR_NEG4, "reason=uncovered-vanished-keys" as *u8) == 1, ctr) 425 gv_check("removal-consumed-prev-rows-A-B-moved" as *u8, hs0[3] == 0, ctr) 426 ss_close(hs0) 427 let hsf: *i64 = si_open_captured(SI_PREFIX, SI_ERR_UNFULL, 0 as *i64, 1) 428 let cmps: *i64 = sys_mmap(16) as *i64 429 cmps[0] = 0 430 var smm: i64 = 0 431 rr = 0 432 while rr < 2 { smm = smm + si_lm_diff(hs, hsf, rr, cmps); rr = rr + 1 } 433 gv_check("removal-live-maps-BYTE-IDENTICAL-to-a-fresh-FULL-open" as *u8, smm == 0, ctr) 434 gv_check("removal-row-A-marks-25-again-C-shadowing-gone" as *u8, si_marks(hs, 0, SI_A_RECS) == SI_A_LIVE_PREV, ctr) 435 ss_close(hs) 436 ss_close(hsf) 437 let hrest2: *i64 = ss_open3(SI_PREFIX, 1, 0 as *i64, 0) 438 // the manifest now names only [A,B]; put C back so the fold below starts from [A,B,C] 439 let sp3: *i64 = sys_mmap(8) as *i64 440 let n3: i64 = ss_manifest_dyn(SI_PREFIX, sp3) 441 let segs3: *i64 = sp3[0] as *i64 442 gv_check("removal-fixture-manifest-reads-two-rows" as *u8, n3 == 2, ctr) 443 ss_close(hrest2) 444 445 // ---- RANGE FOLD (rung L1b): rows A,B fold into merged D at position 0, C survives as the suffix ---- 446 // D is written exactly as the compactor writes it: the latest entry per key of A u B, tombstones kept. 447 let wd: *i64 = ss_begin_cap(SI_SEGCAP) 448 var addedD: i64 = si_add_range(wd, SI_KIND_LIVE, SI_A_LO, SI_B_LO - 1, "A" as *u8) 449 addedD = addedD + si_add_range(wd, SI_KIND_LIVE, SI_B_LO, SI_B_HI, "B" as *u8) 450 addedD = addedD + si_add_range(wd, SI_KIND_TOMB, SI_B_TLO, SI_B_THI, "B" as *u8) 451 addedD = addedD + si_add_range(wd, SI_KIND_LIVE, SI_B_THI + 1, SI_A_HI, "A" as *u8) 452 gv_check("fold-fixture-D-holds-40-merged-records" as *u8, addedD == SI_A_RECS, ctr) 453 // restore [A,B,C] first (ss_commit appends to the CURRENT manifest), then commit D -> [A,B,C,D] 454 si_write_manifest_names(SI_PREFIX, nmABC, ordABC, 3) 455 gv_check("fold-fixture-D-committed" as *u8, ss_commit(SI_PREFIX, wd, ss_next_segid(SI_PREFIX)) == 0, ctr) 456 let sp4: *i64 = sys_mmap(8) as *i64 457 let n4: i64 = ss_manifest_dyn(SI_PREFIX, sp4) 458 let segs4: *i64 = sp4[0] as *i64 459 gv_check("fold-fixture-manifest-names-four-rows-before-the-fold" as *u8, n4 == 4, ctr) 460 // the compactor's manifest: [D, C] 461 let ord2: *i64 = sys_mmap(32) as *i64 462 ord2[0] = 3 463 ord2[1] = 2 464 gv_check("fold-fixture-reached-manifest-D-C" as *u8, si_write_manifest_names(SI_PREFIX, segs4, ord2, 2) == 2, ctr) 465 let pc_docs: i64 = hi[3 + 8 * 2] 466 let hfold: *i64 = si_open_captured(SI_PREFIX, SI_ERR_FOLD, hi, 1) 467 gv_check("fold-open-sees-two-rows" as *u8, hfold[0] == 2, ctr) 468 gv_check("fold-open-mode-is-INCR" as *u8, ss_open_mode(hfold) == SS3_MODE_INCR, ctr) 469 gv_check("fold-announces-reused-1-built-1" as *u8, si_err_has(SI_ERR_FOLD, "SSOPEN INCR reused=1 built=1" as *u8) == 1, ctr) 470 gv_check("fold-announces-vanished-2" as *u8, si_err_has(SI_ERR_FOLD, "INCR-FOLD vanished=2" as *u8) == 1, ctr) 471 gv_check("fold-survivor-C-docs-mapping-IS-prev's-mapping" as *u8, hfold[3 + 8 * 1] == pc_docs, ctr) 472 gv_check("fold-prev-survivor-slot-gutted" as *u8, hi[3 + 8 * 2] == 0, ctr) 473 gv_check("fold-row-D-marks-27-live-of-40" as *u8, si_marks(hfold, 0, SI_A_RECS) == SI_D_LIVE, ctr) 474 gv_check("fold-row-C-marks-13-unchanged" as *u8, si_marks(hfold, 1, SI_C_RECS) == SI_C_LIVE, ctr) 475 let hff: *i64 = si_open_captured(SI_PREFIX, SI_ERR_FOLDFULL, 0 as *i64, 1) 476 let cmpf: *i64 = sys_mmap(16) as *i64 477 cmpf[0] = 0 478 var fmm: i64 = 0 479 rr = 0 480 while rr < 2 { fmm = fmm + si_lm_diff(hfold, hff, rr, cmpf); rr = rr + 1 } 481 gv_check("fold-live-maps-compared-nonzero-bytes" as *u8, cmpf[0] > 0, ctr) 482 gv_check("fold-live-maps-BYTE-IDENTICAL-to-FULL" as *u8, fmm == 0, ctr) 483 let cmpf2: *i64 = sys_mmap(16) as *i64 484 cmpf2[0] = 0 485 gv_check("fold-table-answers-IDENTICAL-to-FULL" as *u8, si_table_diff(hfold, hff, cmpf2) == 0, ctr) 486 gv_check("fold-table-compared-every-entry-58" as *u8, cmpf2[0] == SI_FOLD_ENTRIES, ctr) 487 si_key(key, 5) 488 gv_check("fold-doc5-reads-A5-through-D" as *u8, si_val_is(hfold, key, "A5" as *u8) == 1, ctr) 489 si_key(key, 16) 490 gv_check("fold-doc16-reads-C16-survivor-newest" as *u8, si_val_is(hfold, key, "C16" as *u8) == 1, ctr) 491 si_key(key, 23) 492 gv_check("fold-doc23-tombstoned-inside-D" as *u8, si_hget_kind(hfold, key) == 0, ctr) 493 si_key(key, 33) 494 gv_check("fold-doc33-tombstoned-by-C" as *u8, si_hget_kind(hfold, key) == 0, ctr) 495 ss_close(hff) 496 497 // ---- FOLD THEN SHIP: E re-puts doc:1..3 -> [D,C,E], the prefix case on top of a folded handle ---- 498 let we: *i64 = ss_begin_cap(SI_SEGCAP) 499 gv_check("fold-ship-fixture-E-3-reputs-added" as *u8, si_add_range(we, SI_KIND_LIVE, SI_E_LO, SI_E_HI, "E" as *u8) == SI_E_RECS, ctr) 500 gv_check("fold-ship-fixture-E-committed" as *u8, ss_commit(SI_PREFIX, we, ss_next_segid(SI_PREFIX)) == 0, ctr) 501 let hfs: *i64 = si_open_captured(SI_PREFIX, SI_ERR_FS, hfold, 1) 502 gv_check("fold-ship-open-mode-is-INCR" as *u8, ss_open_mode(hfs) == SS3_MODE_INCR, ctr) 503 gv_check("fold-ship-announces-reused-2-built-1" as *u8, si_err_has(SI_ERR_FS, "SSOPEN INCR reused=2 built=1" as *u8) == 1, ctr) 504 gv_check("fold-ship-announces-vanished-0" as *u8, si_err_has(SI_ERR_FS, "INCR-FOLD vanished=0" as *u8) == 1, ctr) 505 gv_check("fold-ship-row-D-marks-24-after-E-reput-3" as *u8, si_marks(hfs, 0, SI_A_RECS) == SI_D_LIVE_AFTER_E, ctr) 506 si_key(key, 2) 507 gv_check("fold-ship-doc2-reads-E2" as *u8, si_val_is(hfs, key, "E2" as *u8) == 1, ctr) 508 ss_close(hfold) 509 ss_close(hfs) 510 ss_close(hi) 511 return gv_verdict("nx_segopen_incr_gate" as *u8, ctr, "in-process over nx_seg_store ss_open3: a prefix, suffix or range-fold reopen reuses every surviving row's mappings, map and table entries, tombstones the vanished rows, inserts the new rows in manifest order, equals a FULL open byte-for-byte, and an uncovered removal rebuilds in place announced as FULL" as *u8) 512}