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}