code wiki / (root) / nx_sts_cas_gate.nx

nx_sts_cas_gate.nx source

↩ module page · 237 lines · 9828 B

1// nx_sts_cas_gate.nx -- does an UNLOCKED sts_seed writer LIE ABOUT SUCCEEDING? 2// 3// nx_sts_lock_gate already proves the lost-row race exists (1 of 6 rows survive unlocked). It counts 4// SURVIVING ROWS, and that is the one thing it cannot use to tell the two outcomes apart: 5// 6// SILENT CLOBBER : sts_seed returns a row count, the caller believes it wrote, and the row is gone. 7// LOUD REFUSAL : sts_seed returns negative, the caller KNOWS it did not write, and can retry. 8// 9// Both leave fewer rows in the plane. Only one of them is a data-loss defect; the other is ordinary 10// backpressure. So this gate measures the thing that actually matters: 11// 12// SILENT LOSS = (writers that were TOLD they succeeded) - (rows actually present) 13// 14// Pre-guard that is 5 of 6: six writers each get a success return, one row exists. Every one of those 15// five callers has already moved on, logged success, and returned a row id nobody will ever read. 16// A CAS/generation guard cannot make more rows survive -- the writes genuinely conflict -- but it can 17// drive SILENT LOSS to ZERO, which converts an invisible corruption into a retryable error. 18// 19// NON-VACUITY: if every writer succeeds AND every row survives, nothing raced and the gate says 20// VACUOUS rather than GREEN. A concurrency gate that never triggers the race proves nothing. 21// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 22import "nx_store_seed_lib.nx" 23import "nx_seg_store.nx" 24import "nx_syscalls.nx" 25import "nx_gate_verdict.nx" 26 27const G_N: i64 = 6 28const G_CAP: i64 = 262144 29const G_WIDEN_MS: i64 = 150 30const G_NL: i64 = 10 31const G_EXIT_SHIFT: i64 = 8 32const G_EXIT_MASK: i64 = 255 33 34func g_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 35func g_num(v: i64) -> i64 { 36 if v == 0 { g_p("0" as *u8); return 0 } 37 var x: i64 = v 38 if x < 0 { g_p("-" as *u8); x = 0 - x } 39 let b: *u8 = sys_mmap(32) 40 var i: i64 = 0 41 while x > 0 { b[i] = ((x % 10) + 48) as u8; x = x / 10; i = i + 1 } 42 while i > 0 { i = i - 1; sys_write(1, ((b as i64) + i) as *u8, 1) } 43 return 0 44} 45 46func g_rows(prefix: *u8) -> i64 { 47 let buf: *u8 = sys_mmap(G_CAP) 48 let n: i64 = sts_load(prefix, buf, G_CAP) 49 var c: i64 = 0 50 var i: i64 = 0 51 while i < n { if buf[i] == (G_NL as u8) { c = c + 1 } i = i + 1 } 52 sys_munmap(buf, G_CAP) 53 return c 54} 55 56// THE UNIVERSAL UNLOCKED CALL SHAPE, verbatim: load -> mutate -> seed, no lock anywhere. 57// Returns 0 if sts_seed CLAIMED success, 1 if it refused. That return becomes the exit status, 58// which is how the parent counts claims without any shared memory. 59func g_worker(prefix: *u8, id: i64) -> i64 { 60 let buf: *u8 = sys_mmap(G_CAP) 61 var n: i64 = sts_load(prefix, buf, G_CAP) 62 if n < 0 { n = 0 } 63 sys_sleep_ms(G_WIDEN_MS) 64 buf[n] = 114 as u8 65 n = n + 1 66 buf[n] = (48 + id) as u8 67 n = n + 1 68 buf[n] = G_NL as u8 69 n = n + 1 70 let rc: i64 = sts_seed(prefix, buf, n) 71 if rc < 0 { return 1 } 72 return 0 73} 74 75// claims[0] = how many writers were TOLD they succeeded. Returns rows actually present. 76func g_arm(prefix: *u8, claims: *i64) -> i64 { 77 let empty: *u8 = sys_mmap(16) 78 sts_seed(prefix, empty, 0) 79 var k: i64 = 0 80 while k < G_N { 81 let pid: i64 = sys_fork() 82 if pid == 0 { 83 let r: i64 = g_worker(prefix, k) 84 sys_exit_group(r) 85 } 86 k = k + 1 87 } 88 let st: *i64 = sys_mmap(16) as *i64 89 var reaped: i64 = 0 90 var ok: i64 = 0 91 while reaped < G_N { 92 st[0] = 0 93 if sys_wait4(0 - 1, st, 0) > 0 { 94 let code: i64 = (st[0] / (1 * 256)) % 256 95 if code == 0 { ok = ok + 1 } 96 reaped = reaped + 1 97 } else { reaped = G_N } 98 } 99 claims[0] = ok 100 return g_rows(prefix) 101} 102 103// ---- THE SAME QUESTION, ASKED OF sts_append_fast ------------------------------------------------ 104// sts_append_fast is the O(1) path the library ACTIVELY RECOMMENDS -- measured 4489x cheaper than a 105// re-seed -- and it is a read-modify-write too: it ss_get's q:n to learn n, then writes q:<n> and 106// q:n=n+1. Two appenders both read n and both write THE SAME q:<n> key. Reads are NEWEST-WINS, so the 107// loser's row is not merely absent, it is OVERWRITTEN; and both then set q:n=n+1, so THE COUNT AGREES 108// WITH THE LOSS and no integrity check downstream can ever notice. Speed is not safety. 109// The workers sleep BEFORE the call so all six line up and read q:n in the same instant -- the window 110// itself is inside sts_append_fast and cannot be widened from out here. 111func g_worker_fast(prefix: *u8, id: i64) -> i64 { 112 let row: *u8 = sys_mmap(16) 113 row[0] = 102 as u8 114 row[1] = (48 + id) as u8 115 sys_sleep_ms(G_WIDEN_MS) 116 let rc: i64 = sts_append_fast(prefix, row, 2) 117 if rc < 0 { return 1 } 118 return 0 119} 120 121func g_arm_fast(prefix: *u8, claims: *i64) -> i64 { 122 let empty: *u8 = sys_mmap(16) 123 sts_seed(prefix, empty, 0) 124 var k: i64 = 0 125 while k < G_N { 126 let pid: i64 = sys_fork() 127 if pid == 0 { 128 let r: i64 = g_worker_fast(prefix, k) 129 sys_exit_group(r) 130 } 131 k = k + 1 132 } 133 let st: *i64 = sys_mmap(16) as *i64 134 var reaped: i64 = 0 135 var ok: i64 = 0 136 while reaped < G_N { 137 st[0] = 0 138 if sys_wait4(0 - 1, st, 0) > 0 { 139 let code: i64 = (st[0] / (1 * 256)) % 256 140 if code == 0 { ok = ok + 1 } 141 reaped = reaped + 1 142 } else { reaped = G_N } 143 } 144 claims[0] = ok 145 return g_rows(prefix) 146} 147 148func main(argc: i64, argv: *i64) -> i64 { 149 g_p("nx_sts_cas_gate -- does an unlocked sts_seed writer LIE about succeeding?\n\n" as *u8) 150 let p: *u8 = "knowledge/store/stscasgate-" as *u8 151 let cl: *i64 = sys_mmap(16) as *i64 152 let rows: i64 = g_arm(p, cl) 153 let claimed: i64 = cl[0] 154 let silent: i64 = claimed - rows 155 156 g_p(" T1 " as *u8); g_num(G_N); g_p(" unlocked concurrent writers (load -> mutate -> seed)\n" as *u8) 157 g_p(" writers TOLD they succeeded : " as *u8); g_num(claimed); g_p("\n" as *u8) 158 g_p(" rows actually in the plane : " as *u8); g_num(rows); g_p("\n" as *u8) 159 g_p(" SILENT LOSS (told-yes, gone): " as *u8); g_num(silent); g_p("\n\n" as *u8) 160 161 var vac: i64 = 0 162 if claimed == G_N { if rows == G_N { vac = 1 } } 163 if vac == 1 { 164 g_p(" VACUOUS -- every writer succeeded AND every row survived, so nothing raced.\n" as *u8) 165 g_p(" Do NOT read this as evidence of safety; widen G_WIDEN_MS or raise G_N.\n" as *u8) 166 } 167 168 var pass: i64 = 0 169 if vac == 0 { 170 if silent <= 0 { 171 pass = 1 172 g_p(" T2 NO SILENT LOSS: every writer that was told it wrote, wrote. Refused writers\n" as *u8) 173 g_p(" were told NO and can retry -- backpressure, not corruption.\n" as *u8) 174 } else { 175 g_p(" T2 SILENT LOSS PRESENT: " as *u8); g_num(silent) 176 g_p(" writers were told they succeeded and their rows do not exist.\n" as *u8) 177 g_p(" Each one has already moved on holding a row id nobody will ever read.\n" as *u8) 178 } 179 } 180 181 // ---- T3/T4: sts_append_fast, the O(1) path ------------------------------------------------- 182 let pf: *u8 = "knowledge/store/stscasfast-" as *u8 183 let clf: *i64 = sys_mmap(16) as *i64 184 let rowsf: i64 = g_arm_fast(pf, clf) 185 let claimedf: i64 = clf[0] 186 let silentf: i64 = claimedf - rowsf 187 g_p(" T3 " as *u8); g_num(G_N); g_p(" unlocked concurrent sts_append_fast writers\n" as *u8) 188 g_p(" writers TOLD they succeeded : " as *u8); g_num(claimedf); g_p("\n" as *u8) 189 g_p(" rows actually in the plane : " as *u8); g_num(rowsf); g_p("\n" as *u8) 190 g_p(" SILENT LOSS (told-yes, gone): " as *u8); g_num(silentf); g_p("\n\n" as *u8) 191 if silentf > 0 { 192 pass = 0 193 g_p(" T4 sts_append_fast SILENTLY LOSES: " as *u8); g_num(silentf) 194 g_p(" writers were told they wrote; their rows were OVERWRITTEN and q:n agrees with the loss.\n" as *u8) 195 } else { 196 g_p(" T4 sts_append_fast: no silent loss -- refused writers were told NO.\n" as *u8) 197 } 198 199 // ---- T5: THE PORTED TRUNCATION GUARD MUST ACTUALLY FIRE ---------------------------------------- 200 // sts_load drops bytes once o >= cap and returns a byte count that looks perfectly healthy. The 201 // authoring tree made that LOUD on 2026-07-30; the guard never reached the tree that compiles and 202 // deploys, so every organ built here truncated silently until 2026-08-06. A guard that cannot be 203 // SHOWN to fire has not been tested, so force it: seed a plane far larger than the cap we then 204 // hand the loader. Expect a SHORT return AND an STS-LOAD TRUNCATED line on stderr. 205 let pt: *u8 = "knowledge/store/stscastrunc-" as *u8 206 let seed: *u8 = sys_mmap(4096) 207 var so: i64 = 0 208 var si: i64 = 0 209 while si < 40 { 210 seed[so] = 114 as u8 211 so = so + 1 212 seed[so] = (48 + (si % 10)) as u8 213 so = so + 1 214 seed[so] = G_NL as u8 215 so = so + 1 216 si = si + 1 217 } 218 sts_seed(pt, seed, so) 219 let full: *u8 = sys_mmap(G_CAP) 220 let nfull: i64 = sts_load(pt, full, G_CAP) 221 let tiny: *u8 = sys_mmap(4096) 222 let ntiny: i64 = sts_load(pt, tiny, 16) 223 g_p(" T5 truncation guard -- full_load=" as *u8); g_num(nfull) 224 g_p(" bytes, tiny_cap_load=" as *u8); g_num(ntiny) 225 g_p(" bytes (cap 16); expect an STS-LOAD TRUNCATED line on stderr\n" as *u8) 226 if ntiny >= nfull { 227 pass = 0 228 g_p(" VACUOUS: the tiny cap did NOT truncate, so this tooth proved nothing.\n" as *u8) 229 } 230 231 let ctr: *i64 = gv_ctr() 232 ctr[0] = pass 233 ctr[1] = 1 234 let rc: i64 = gv_verdict("STS-CAS-GATE" as *u8, ctr, "unlocked sts_seed never claims a write it lost" as *u8) 235 sys_exit(rc) 236 return rc 237}