code wiki / (root) / nx_nxwreap_gate.nx

nx_nxwreap_gate.nx source

↩ module page · 407 lines · 21821 B

1// nx_nxwreap_gate.nx -- BITE-PROOF for the leaked-write-guard-scratch reaper. 2// 3// THE LOAD-BEARING PROPERTY IS FAIL-SAFE, AND ONE TOOTH CARRIES IT: a scratch file whose pid is 4// still LIVE is NEVER reaped, however old. Everything else this gate asserts could be satisfied by a 5// reaper that unlinks every file it sees -- 6// A GUARD THAT REFUSES NOTHING PASSES EVERY POSITIVE TEST EVER WRITTEN FOR IT, 7// which is the exact mirror of the deny-guard defect this estate has already paid for. So the live- 8// owner fixture is the POSITIVE CONTROL: a file that MUST survive, and whose survival no 9// count-based check can fake. 10// 11// THE SECOND ANTI-VACUITY TOOTH IS THE DRY RUN. Every counter this organ prints would be identical 12// whether apply=0 wrote nothing or deleted everything, so the dry-run teeth assert THE STATE OF THE 13// DISK, not the report. A LEAKING CALL ANSWERS IDENTICAL BYTES. 14// 15// FIXTURES LIVE IN /tmp/nx_nxwreap_gate AND NOWHERE ELSE. A gate that shares a fixture with a 16// production tree measures the tree instead of the code -- and this one UNLINKS FILES, so pointing 17// it at knowledge/ or buildroot/ would destroy real work. The directory is created at SETUP, because 18// a teardown does not run when a run crashes, and it is PURGED at setup so the gate is idempotent: 19// the live fixture's name contains the running pid and therefore differs on every run, so a gate 20// that only recreated its own names would accumulate a stranger every time and lie about the count 21// from the second run onward. 22// 23// THE THRESHOLD USED FOR THE FIXTURE SCANS IS PINNED HERE (NG_MAXAGE), NOT READ FROM THE CONF. A 24// gate whose subject's behaviour moves when someone edits production config is measuring the config. 25// The conf/default/derivation arithmetic is proved SEPARATELY, as pure teeth. 26// 27// license_tier: ORIGINAL expect_exit: 0 28import "nx_syscalls.nx" 29import "nx_gate_verdict.nx" 30import "nx_nxwreap_lib.nx" 31 32const NG_DIR: *u8 = "/tmp/nx_nxwreap_gate" as *u8 33const NG_ABSENTDIR: *u8 = "/tmp/nx_nxwreap_gate_no_such_directory" as *u8 34const NG_SEP: *u8 = "/" as *u8 35const NG_BODY: *u8 = "scratch fixture body\n" as *u8 36 37// A pid at the top of the Linux pid_max range. It is ASSERTED DEAD by its own tooth rather than 38// assumed: if a box ever hands this pid to a live process the gate goes RED and names the reason, 39// instead of silently proving nothing. 40const NG_DEAD_PID: i64 = 4194303 41const NG_MAXAGE: i64 = 120 42const NG_OLD_AGE: i64 = 600 43const NG_YOUNG_AGE: i64 = 5 44const NG_ANCIENT: i64 = 999999 45const NG_PATH: i64 = 1024 46const NG_TS_BYTES: i64 = 32 47const NG_TS_ATV: i64 = 0 48const NG_TS_ATN: i64 = 1 49const NG_TS_MTV: i64 = 2 50const NG_TS_MTN: i64 = 3 51const NG_PIDBUF: i64 = 64 52 53// The fixture population, DERIVED from the setup below rather than typed as a second copy of its own 54// shape: 2 dot-entries + 3 reap-or-refuse scratch + 2 malformed + 1 plain + 1 directory + 1 live. 55const NG_DOTS: i64 = 2 56const NG_EXPECT_ENTRIES: i64 = 10 57const NG_EXPECT_REAP: i64 = 2 58const NG_EXPECT_AFTER: i64 = 8 59const NG_LONGDIGITS: *u8 = "x.nx.nxw12345678901" as *u8 60 61func gt_eq(a: i64, b: i64) -> i64 { if a == b { return 1 } return 0 } 62func gt_gt(a: i64, b: i64) -> i64 { if a > b { return 1 } return 0 } 63func gt_ge(a: i64, b: i64) -> i64 { if a >= b { return 1 } return 0 } 64func gt_and(a: i64, b: i64) -> i64 { if a == 1 { if b == 1 { return 1 } } return 0 } 65 66func gt_pid(s: *u8) -> i64 { return nw_pid_of(s, ccz_slen(s)) } 67 68// <stem><pid> into buf, NUL-terminated. The dead pid appears ONCE in this file, as NG_DEAD_PID; a 69// hand-typed \"...nxw4194303\" literal beside it would be a second copy of the same constant and the 70// two would drift the moment one is changed. 71func gt_nm(buf: *u8, stem: *u8, pid: i64) -> i64 { 72 var o: i64 = 0 73 o = ccz_cat_str(buf, o, stem) 74 o = ccz_cat_num(buf, o, pid) 75 buf[o] = 0 as u8 76 return o 77} 78 79func gt_pth(buf: *u8, name: *u8) -> i64 { 80 var o: i64 = 0 81 o = ccz_cat_str(buf, o, NG_DIR) 82 o = ccz_cat_str(buf, o, NG_SEP) 83 o = ccz_cat_str(buf, o, name) 84 buf[o] = 0 as u8 85 return o 86} 87 88func gt_write(path: *u8) -> i64 { 89 let fd: i64 = sys_openat_wr(path, MODE_0644) 90 if fd < 0 { return 0 - 1 } 91 sys_write(fd, NG_BODY, ccz_slen(NG_BODY)) 92 sys_close(fd) 93 return 0 94} 95 96func gt_touch(path: *u8, e: i64) -> i64 { 97 let t: *i64 = sys_mmap(NG_TS_BYTES) as *i64 98 t[NG_TS_ATV] = e 99 t[NG_TS_ATN] = 0 100 t[NG_TS_MTV] = e 101 t[NG_TS_MTN] = 0 102 let r: i64 = sys_utimensat(path, t) 103 sys_munmap(t as *u8, NG_TS_BYTES) 104 return r 105} 106 107func gt_make(path: *u8, e: i64) -> i64 { 108 if gt_write(path) < 0 { return 0 - 1 } 109 return gt_touch(path, e) 110} 111 112// Remove every REGULAR entry from the fixture directory. This is what makes the gate idempotent, and 113// it is safe ONLY because NG_DIR is a /tmp path this gate owns. The directory fixture survives (it is 114// not a regular file), which is exactly the entry the next run needs to still be there. 115func gt_purge(dir: *u8) -> i64 { 116 let fd: i64 = sys_openat_rd(dir) 117 if fd < 0 { return 0 } 118 let dbuf: *u8 = sys_mmap(NW_DENTBUF) 119 let p: *u8 = sys_mmap(NG_PATH) 120 var killed: i64 = 0 121 var run: i64 = 1 122 while run == 1 { 123 let n: i64 = sys_getdents64(fd, dbuf, NW_DENTBUF) 124 if n <= 0 { run = 0 } else { 125 var off: i64 = 0 126 while off < n { 127 let rec: *u8 = ((dbuf as i64 + off) as *u8) 128 let reclen: i64 = dirent_reclen(rec) 129 if reclen <= 0 { off = n } else { 130 if dirent_type(rec) == DT_REG { 131 gt_pth(p, dirent_name(rec)) 132 if lr_unlink(p) == 0 { killed = killed + 1 } 133 } 134 off = off + reclen 135 } 136 } 137 } 138 } 139 sys_close(fd) 140 sys_munmap(dbuf, NW_DENTBUF) 141 sys_munmap(p, NG_PATH) 142 return killed 143} 144 145func main(argc: i64, argv: *i64) -> i64 { 146 let ctr: *i64 = gv_ctr() 147 gv_head("nx_nxwreap_gate -- does the scratch reaper remove ONLY provably abandoned write temps, and does a dry run really write nothing" as *u8) 148 149 let now: i64 = sys_now_realtime_sec() 150 let selfpid: i64 = lr_selfpid() 151 let pidbuf: *u8 = sys_mmap(NG_PIDBUF) 152 let pathbuf: *u8 = sys_mmap(NW_PATHBUF) 153 let info: *i64 = sys_mmap(NW_I_SLOTS * NW_I64_BYTES) as *i64 154 155 // ---- SETUP ------------------------------------------------------------------------------- 156 sys_mkdir(NG_DIR, MODE_0755) 157 let purged: i64 = gt_purge(NG_DIR) 158 159 let n_live: *u8 = sys_mmap(NG_PATH) 160 let n_old: *u8 = sys_mmap(NG_PATH) 161 let n_young: *u8 = sys_mmap(NG_PATH) 162 let n_inner: *u8 = sys_mmap(NG_PATH) 163 let n_ghost: *u8 = sys_mmap(NG_PATH) 164 let n_dirl: *u8 = sys_mmap(NG_PATH) 165 gt_nm(n_live, "live.nx.nxw" as *u8, selfpid) 166 gt_nm(n_old, "dead_old.nx.nxw" as *u8, NG_DEAD_PID) 167 gt_nm(n_young, "dead_young.nx.nxw" as *u8, NG_DEAD_PID) 168 gt_nm(n_inner, "inner.nx.laneFprobe.nxw" as *u8, NG_DEAD_PID) 169 gt_nm(n_ghost, "ghost.nx.nxw" as *u8, NG_DEAD_PID) 170 gt_nm(n_dirl, "dirlike.nx.nxw" as *u8, NG_DEAD_PID) 171 let n_bad: *u8 = "bad.nx.nxwZZ" as *u8 172 let n_bare: *u8 = "bare.nx.nxw" as *u8 173 let n_plain: *u8 = "plain.nx" as *u8 174 175 let p_live: *u8 = sys_mmap(NG_PATH) 176 let p_old: *u8 = sys_mmap(NG_PATH) 177 let p_young: *u8 = sys_mmap(NG_PATH) 178 let p_inner: *u8 = sys_mmap(NG_PATH) 179 let p_ghost: *u8 = sys_mmap(NG_PATH) 180 let p_dirl: *u8 = sys_mmap(NG_PATH) 181 let p_bad: *u8 = sys_mmap(NG_PATH) 182 let p_bare: *u8 = sys_mmap(NG_PATH) 183 let p_plain: *u8 = sys_mmap(NG_PATH) 184 gt_pth(p_live, n_live) 185 gt_pth(p_old, n_old) 186 gt_pth(p_young, n_young) 187 gt_pth(p_inner, n_inner) 188 gt_pth(p_ghost, n_ghost) 189 gt_pth(p_dirl, n_dirl) 190 gt_pth(p_bad, n_bad) 191 gt_pth(p_bare, n_bare) 192 gt_pth(p_plain, n_plain) 193 194 gt_make(p_live, now - NG_ANCIENT) 195 gt_make(p_old, now - NG_OLD_AGE) 196 gt_make(p_young, now - NG_YOUNG_AGE) 197 gt_make(p_inner, now - NG_OLD_AGE) 198 gt_make(p_bad, now - NG_OLD_AGE) 199 gt_make(p_bare, now - NG_OLD_AGE) 200 gt_make(p_plain, now - NG_OLD_AGE) 201 sys_mkdir(p_dirl, MODE_0755) 202 // p_ghost is DELIBERATELY never created -- it is the VANISHED fixture. 203 204 gv_puts(" setup: dir=" as *u8) 205 gv_puts(NG_DIR) 206 gv_puts(" purged_regular_entries=" as *u8) 207 gv_num(purged) 208 gv_puts(" selfpid=" as *u8) 209 gv_num(selfpid) 210 gv_puts(" dead_pid=" as *u8) 211 gv_num(NG_DEAD_PID) 212 gv_puts(" threshold_s=" as *u8) 213 gv_num(NG_MAXAGE) 214 gv_puts("\n measured ages: live=" as *u8) 215 gv_num(lr_age_s(p_live, now)) 216 gv_puts(" dead_old=" as *u8) 217 gv_num(lr_age_s(p_old, now)) 218 gv_puts(" dead_young=" as *u8) 219 gv_num(lr_age_s(p_young, now)) 220 gv_puts("\n\n" as *u8) 221 222 // ---- THE NAME CONTRACT: pid parsing, and every refusal NAMED -------------------------------- 223 gv_check("T1 pid-parsed-from-the-trailing-digit-run" as *u8, 224 gt_eq(gt_pid("nx_mgmt_api.nx.nxw12848" as *u8), 12848), ctr) 225 // A DISCRIMINATING fixture, not merely a realistic one. `a.nxw12.nx.nxw34` carries TWO markers: 226 // a FIRST-match parser reads the suffix as `12.nx.nxw34`, hits a non-digit and returns MALFORMED, 227 // where the shipped LAST-match parser returns 34. The real observed name below has only ONE 228 // marker, so it could never have told the two implementations apart -- a control that cannot 229 // discriminate proves nothing, however true it looks. 230 gv_check("T2 pid-parsed-from-the-LAST-marker-not-the-first (two-marker discriminating fixture)" as *u8, 231 gt_eq(gt_pid("a.nxw12.nx.nxw34" as *u8), 34), ctr) 232 gv_check("T2b real observed scratch name with an inner dotted segment parses" as *u8, 233 gt_eq(gt_pid("nx_opaque_login_smoke.nx.laneFprobe.nxw8847" as *u8), 8847), ctr) 234 gv_check("T3 neg-control-name-with-no-marker-refused-BY-NAME" as *u8, 235 gt_eq(gt_pid("nx_organ_ship.nx" as *u8), NW_PID_NOT_NXW), ctr) 236 gv_check("T4 neg-control-nondigit-suffix-refused-BY-NAME" as *u8, 237 gt_eq(gt_pid("x.nx.nxwZZ" as *u8), NW_PID_MALFORMED), ctr) 238 gv_check("T5 neg-control-empty-digit-run-refused-BY-NAME" as *u8, 239 gt_eq(gt_pid("x.nx.nxw" as *u8), NW_PID_MALFORMED), ctr) 240 gv_check("T6 neg-control-overlong-digit-run-refused-BY-NAME (no i64 overflow on a hostile name)" as *u8, 241 gt_eq(gt_pid(NG_LONGDIGITS), NW_PID_MALFORMED), ctr) 242 // pid 0 is the kernel swapper -- a REAL pid. A refusal spelled 0 would be read as a live process. 243 var r_neg: i64 = 0 244 if NW_PID_NOT_NXW < 0 { if NW_PID_MALFORMED < 0 { if NW_PID_NOT_NXW != NW_PID_MALFORMED { r_neg = 1 } } } 245 gv_check("T7 neg-control-no-refusal-is-ever-pid-zero-and-the-two-are-distinguishable" as *u8, r_neg, ctr) 246 247 // ---- OWNER LIVENESS ------------------------------------------------------------------------ 248 gv_check("T8 self-pid-reads-ALIVE (the process running this gate)" as *u8, 249 gt_eq(nw_pid_alive(selfpid, pidbuf), 1), ctr) 250 // ASSERT THE FIXTURE REACHED THE CONDITION BEFORE ASSERTING THE OUTCOME. 251 gv_check("T9 fixture-dead-pid-is-really-dead (else every reap tooth below is vacuous)" as *u8, 252 gt_eq(nw_pid_alive(NG_DEAD_PID, pidbuf), 0), ctr) 253 gv_check("T10 neg-control-nonpositive-pid-reads-ALIVE-failsafe-so-it-is-never-reaped" as *u8, 254 gt_and(gt_eq(nw_pid_alive(0, pidbuf), 1), gt_eq(nw_pid_alive(0 - 1, pidbuf), 1)), ctr) 255 256 // ---- THE THRESHOLD IS DERIVED, AND THE DERIVATION IS CHECKED ARITHMETICALLY ----------------- 257 gv_check("T11 ship-loop-bound-derives-to-900s-from-nx_organ_ship-OS_TIMEOUT_MS" as *u8, 258 gt_eq(nw_ship_bound_s(), 900), ctr) 259 gv_check("T12 default-threshold-is-at-least-the-ship-loop-bound (lowering it turns this RED)" as *u8, 260 gt_ge(NW_DEFAULT_MAX_AGE_SEC, nw_ship_bound_s()), ctr) 261 gv_check("T13 default-threshold-equals-the-sibling-jobclaim-calibration-3600" as *u8, 262 gt_eq(NW_DEFAULT_MAX_AGE_SEC, 3600), ctr) 263 let srcp: *i64 = sys_mmap(NW_I64_BYTES) as *i64 264 let resolved: i64 = nw_maxage(srcp) 265 gv_puts(" resolved threshold=" as *u8) 266 gv_num(resolved) 267 gv_puts(" source=" as *u8) 268 gv_puts(nw_maxage_src_name(srcp[0])) 269 gv_puts("\n" as *u8) 270 gv_check("T14 resolved-threshold-is-positive-and-names-its-source" as *u8, 271 gt_and(gt_gt(resolved, 0), gt_ge(srcp[0], 0)), ctr) 272 273 // ---- FIXTURES REACHED THEIR CONDITIONS ----------------------------------------------------- 274 gv_check("T15 fixture-live-scratch-exists-on-disk" as *u8, gt_eq(lr_exists(p_live), 1), ctr) 275 gv_check("T16 fixture-live-scratch-is-OLDER-than-the-threshold (so T19 cannot pass by youth)" as *u8, 276 gt_gt(lr_age_s(p_live, now), NG_MAXAGE), ctr) 277 gv_check("T17 fixture-dead-old-is-OLDER-than-the-threshold" as *u8, 278 gt_gt(lr_age_s(p_old, now), NG_MAXAGE), ctr) 279 gv_check("T18 fixture-dead-young-is-YOUNGER-than-the-threshold" as *u8, 280 gt_eq(gt_gt(lr_age_s(p_young, now), NG_MAXAGE), 0), ctr) 281 282 // ---- CLASSIFICATION. Captured into locals FIRST, because the apply pass below destroys two of 283 // these fixtures and the bite teeth need both sides of the comparison. 284 let c_live: i64 = nw_classify(NG_DIR, n_live, ccz_slen(n_live), DT_REG, now, NG_MAXAGE, pathbuf, pidbuf, info) 285 let c_old: i64 = nw_classify(NG_DIR, n_old, ccz_slen(n_old), DT_REG, now, NG_MAXAGE, pathbuf, pidbuf, info) 286 let c_young: i64 = nw_classify(NG_DIR, n_young, ccz_slen(n_young), DT_REG, now, NG_MAXAGE, pathbuf, pidbuf, info) 287 let c_inner: i64 = nw_classify(NG_DIR, n_inner, ccz_slen(n_inner), DT_REG, now, NG_MAXAGE, pathbuf, pidbuf, info) 288 let c_bad: i64 = nw_classify(NG_DIR, n_bad, ccz_slen(n_bad), DT_REG, now, NG_MAXAGE, pathbuf, pidbuf, info) 289 let c_plain: i64 = nw_classify(NG_DIR, n_plain, ccz_slen(n_plain), DT_REG, now, NG_MAXAGE, pathbuf, pidbuf, info) 290 let c_ghost: i64 = nw_classify(NG_DIR, n_ghost, ccz_slen(n_ghost), DT_REG, now, NG_MAXAGE, pathbuf, pidbuf, info) 291 let c_dirl: i64 = nw_classify(NG_DIR, n_dirl, ccz_slen(n_dirl), DT_DIR, now, NG_MAXAGE, pathbuf, pidbuf, info) 292 293 gv_check("T19 LOAD-BEARING live-owner-scratch-classified-REFUSED-LIVE-OWNER-though-ANCIENT" as *u8, 294 gt_eq(c_live, NW_D_LIVE_OWNER), ctr) 295 gv_check("T20 dead-owner-old-scratch-classified-REAP" as *u8, gt_eq(c_old, NW_D_REAP), ctr) 296 gv_check("T21 dead-owner-young-scratch-classified-REFUSED-TOO-YOUNG" as *u8, 297 gt_eq(c_young, NW_D_TOO_YOUNG), ctr) 298 gv_check("T22 inner-marker-name-classified-REAP-via-the-LAST-marker" as *u8, 299 gt_eq(c_inner, NW_D_REAP), ctr) 300 gv_check("T23 malformed-name-classified-REFUSED-MALFORMED-NAME-not-pid-zero" as *u8, 301 gt_eq(c_bad, NW_D_MALFORMED), ctr) 302 gv_check("T24 non-scratch-name-classified-NOT-A-SCRATCH-NAME" as *u8, 303 gt_eq(c_plain, NW_D_NOT_NXW), ctr) 304 gv_check("T25 absent-scratch-classified-REFUSED-VANISHED-not-REAP" as *u8, 305 gt_eq(c_ghost, NW_D_VANISHED), ctr) 306 gv_check("T26 directory-wearing-a-scratch-name-classified-REFUSED-NOT-A-REGULAR-FILE" as *u8, 307 gt_eq(c_dirl, NW_D_NOT_REGULAR), ctr) 308 309 // ---- THE DRY RUN. Its counters are identical whether it wrote nothing or deleted everything, 310 // so the teeth that matter interrogate THE DISK. 311 let dry: *i64 = nw_ctr() 312 let rc_dry: i64 = nw_scan(NG_DIR, now, NG_MAXAGE, 0, dry) 313 nw_print_partition(dry) 314 gv_subjects("fixture entries examined by the dry scan" as *u8, dry[NW_C_TOTAL], ctr) 315 gv_check("T27 dry-scan-read-the-directory (rc 0, not the could-not-look code)" as *u8, 316 gt_eq(rc_dry, 0), ctr) 317 gv_check("T28 dry-scan-examined-every-entry-including-the-two-dot-entries" as *u8, 318 gt_eq(dry[NW_C_TOTAL], NG_EXPECT_ENTRIES), ctr) 319 gv_check("T29 dry-scan-partition-SUMS-to-the-population" as *u8, 320 gt_eq(nw_sum(dry), dry[NW_C_TOTAL]), ctr) 321 gv_check("T30 dry-scan-found-exactly-the-two-reapable-fixtures" as *u8, 322 gt_eq(dry[NW_C_REAP], NG_EXPECT_REAP), ctr) 323 // Split into named locals rather than one nested expression: each refusal class is a SEPARATE 324 // bucket, and a single collapsed boolean would not say which of them moved. 325 let b_live: i64 = gt_eq(dry[NW_C_LIVE_OWNER], 1) 326 let b_young: i64 = gt_eq(dry[NW_C_TOO_YOUNG], 1) 327 let b_malf: i64 = gt_eq(dry[NW_C_MALFORMED], 2) 328 let b_dir: i64 = gt_eq(dry[NW_C_NOT_REGULAR], 1) 329 let b_plain: i64 = gt_eq(dry[NW_C_NOT_NXW], NG_DOTS + 1) 330 let b_a: i64 = gt_and(b_live, b_young) 331 let b_b: i64 = gt_and(b_malf, b_dir) 332 let b_c: i64 = gt_and(b_b, b_plain) 333 gv_check("T31 dry-scan-bucketed-the-refusals-separately (live 1, young 1, malformed 2, dir 1, plain-and-dots 3)" as *u8, 334 gt_and(b_a, b_c), ctr) 335 // ANTI-VACUITY: a dry run that actually deleted would pass every count-based check above. 336 // CAPTURED into a local because the apply pass below destroys this file, and B7 needs BOTH sides 337 // of the comparison. A bite whose `bad` argument is a literal 1 is half a control. 338 let survived_dry: i64 = gt_and(gt_eq(lr_exists(p_old), 1), gt_eq(lr_exists(p_inner), 1)) 339 gv_check("T32 ANTI-VACUITY dry-run-left-BOTH-reapable-files-ON-DISK" as *u8, survived_dry, ctr) 340 gv_check("T33 dry-run-unlinked-nothing-and-recorded-both-as-would-reap" as *u8, 341 gt_and(gt_and(gt_eq(dry[NW_C_UNLINKED], 0), gt_eq(dry[NW_C_UNLINK_FAIL], 0)), 342 gt_eq(dry[NW_C_WOULD], NG_EXPECT_REAP)), ctr) 343 gv_check("T34 dry-scan-outcome-axis-SUMS-to-the-reap-bucket" as *u8, 344 gt_eq(nw_axis_sum(dry), dry[NW_C_REAP]), ctr) 345 346 // ---- THE APPLY RUN ------------------------------------------------------------------------- 347 let app: *i64 = nw_ctr() 348 let rc_app: i64 = nw_scan(NG_DIR, now, NG_MAXAGE, 1, app) 349 nw_print_partition(app) 350 gv_check("T35 apply-scan-read-the-directory" as *u8, gt_eq(rc_app, 0), ctr) 351 gv_check("T36 apply-removed-BOTH-reapable-files" as *u8, 352 gt_and(gt_eq(lr_exists(p_old), 0), gt_eq(lr_exists(p_inner), 0)), ctr) 353 gv_check("T37 LOAD-BEARING apply-PRESERVED-the-live-owned-scratch-however-old" as *u8, 354 gt_eq(lr_exists(p_live), 1), ctr) 355 gv_check("T38 apply-preserved-the-too-young-scratch" as *u8, gt_eq(lr_exists(p_young), 1), ctr) 356 gv_check("T39 apply-preserved-BOTH-malformed-names" as *u8, 357 gt_and(gt_eq(lr_exists(p_bad), 1), gt_eq(lr_exists(p_bare), 1)), ctr) 358 gv_check("T40 apply-preserved-the-non-scratch-file" as *u8, gt_eq(lr_exists(p_plain), 1), ctr) 359 gv_check("T41 apply-preserved-the-directory-wearing-a-scratch-name" as *u8, 360 gt_eq(lr_exists(p_dirl), 1), ctr) 361 gv_check("T42 apply-unlinked-count-equals-the-reap-decision-with-no-failures" as *u8, 362 gt_and(gt_and(gt_eq(app[NW_C_UNLINKED], NG_EXPECT_REAP), gt_eq(app[NW_C_UNLINK_FAIL], 0)), 363 gt_eq(app[NW_C_WOULD], 0)), ctr) 364 gv_check("T43 apply-scan-partition-SUMS-to-the-population" as *u8, 365 gt_eq(nw_sum(app), app[NW_C_TOTAL]), ctr) 366 gv_check("T44 apply-scan-outcome-axis-SUMS-to-the-reap-bucket" as *u8, 367 gt_eq(nw_axis_sum(app), app[NW_C_REAP]), ctr) 368 369 // ---- IDEMPOTENCE. A reaper that is not safe to run twice is not safe to put on a beat. ------ 370 let ag: *i64 = nw_ctr() 371 nw_scan(NG_DIR, now, NG_MAXAGE, 1, ag) 372 gv_check("T45 second-apply-finds-nothing-left-to-reap-and-unlinks-nothing" as *u8, 373 gt_and(gt_eq(ag[NW_C_REAP], 0), gt_eq(ag[NW_C_UNLINKED], 0)), ctr) 374 gv_check("T46 second-apply-still-sees-every-preserved-entry" as *u8, 375 gt_eq(ag[NW_C_TOTAL], NG_EXPECT_AFTER), ctr) 376 377 // ---- I COULD NOT LOOK IS NOT NOTHING TO DO ------------------------------------------------- 378 let bad: *i64 = nw_ctr() 379 let rc_bad: i64 = nw_scan(NG_ABSENTDIR, now, NG_MAXAGE, 1, bad) 380 gv_check("T47 neg-control-unopenable-dir-returns-minus-one-not-zero" as *u8, 381 gt_eq(rc_bad, 0 - 1), ctr) 382 gv_check("T48 neg-control-unopenable-dir-examined-nothing-and-claimed-no-coverage" as *u8, 383 gt_and(gt_eq(bad[NW_C_TOTAL], 0), gt_eq(bad[NW_C_DIRS], 0)), ctr) 384 385 // ---- BITE: every rule must FIRE on the bad input and stay SILENT on the good one ------------ 386 gv_bite("B1 bite-marker-refusal" as *u8, 387 gt_eq(gt_pid("plain.nx" as *u8), NW_PID_NOT_NXW), 388 gt_eq(gt_pid("plain.nx.nxw7" as *u8), NW_PID_NOT_NXW), ctr) 389 gv_bite("B2 bite-malformed-suffix-refusal" as *u8, 390 gt_eq(gt_pid("plain.nx.nxwQ" as *u8), NW_PID_MALFORMED), 391 gt_eq(gt_pid("plain.nx.nxw7" as *u8), NW_PID_MALFORMED), ctr) 392 gv_bite("B3 bite-live-owner-guard" as *u8, 393 gt_eq(c_live, NW_D_LIVE_OWNER), gt_eq(c_old, NW_D_LIVE_OWNER), ctr) 394 gv_bite("B4 bite-too-young-guard" as *u8, 395 gt_eq(c_young, NW_D_TOO_YOUNG), gt_eq(c_old, NW_D_TOO_YOUNG), ctr) 396 gv_bite("B5 bite-not-a-regular-file-guard" as *u8, 397 gt_eq(c_dirl, NW_D_NOT_REGULAR), gt_eq(c_old, NW_D_NOT_REGULAR), ctr) 398 gv_bite("B6 bite-vanished-guard" as *u8, 399 gt_eq(c_ghost, NW_D_VANISHED), gt_eq(c_old, NW_D_VANISHED), ctr) 400 // The dry-run guard itself: the SAME file survives a dry pass and does NOT survive an apply pass. 401 // Nothing about the printed counters can distinguish those two runs -- only the disk can. 402 gv_bite("B7 bite-dry-run-guard (the SAME file survives a dry pass and does NOT survive an apply pass)" as *u8, 403 survived_dry, gt_eq(lr_exists(p_old), 1), ctr) 404 405 return gv_verdict("NXWREAP-GATE" as *u8, ctr, 406 "a live-owned scratch survives every pass, a dry pass writes nothing, and the partition sums" as *u8) 407}