code wiki / _hdl_build / nx_ws_crash_gate.nx
nx_ws_crash_gate.nx source
↩ module page · 270 lines · 10777 B
1// nx_ws_crash_gate.nx -- WMS-R10: the END-TO-END CRASH INTEGRATION TEST (the capstone).
2//
3// This is the scenario that STARTED the whole arc -- "claude froze and crashed, find out where we
4// were" -- proven a NON-EVENT. It composes every durability rung at once:
5// R0/R0b framed-append floor : concurrent workers record transitions to the REFLOG with zero tear
6// R2 transition ledger : the append-only reflog = the durable source of truth
7// R8 ws_put_locked : concurrent workers also update the LIVE registry, no lost updates
8// R9 ledger_rebuild : after the crash WIPES the registry, the reflog rebuilds it
9//
10// FLOW: NWORKERS fork; worker w writes NTRANS transitions for workstream w to BOTH the reflog
11// (ledger_append) AND the live registry (ws_put_locked), ending at a deterministic terminal state.
12// Then we simulate a CRASH that loses the derived registry (a fresh, empty registry prefix) while the
13// append-only reflog survives -- the realistic failure mode. Recovery = ledger_rebuild(reflog -> new
14// registry). The end-to-end claim: EVERY workstream's state is recovered, NOTHING is lost.
15//
16// ASSERTIONS (no false green):
17// T1 floor-held : reflog has exactly NWORKERS*NTRANS lines AND flagged==0 (R0 held under
18// concurrency -> the history we recover from is complete and untorn).
19// T2 live-consistent : the concurrently-built LIVE registry has all NWORKERS workstreams at their
20// correct terminal state (R8 lost no update across the shared manifest RMW).
21// T3 recovery-complete : after the crash (wiped registry), ledger_rebuild restores all NWORKERS
22// workstreams with correct terminal states (recovered==N AND correct==N).
23// T4 load-bearing(NEG) : a wiped registry that is NOT rebuilt has ZERO of the N workstreams -- so
24// the recovery is what saves them; if this showed N, the test would be vacuous.
25// GREEN(0) iff T1..T4. Self-log via fa_appendz (dogfoods R0b). Evidence -> ws_crash_gate.log.
26// Sovereign: nx_ws_repair + nx_ws_cas + nx_ws_ledger + nx_workstream_store + nx_framed_append + nx_syscalls.
27// license_tier: ORIGINAL
28import "nx_ws_repair.nx"
29import "nx_ws_cas.nx"
30import "nx_ws_ledger.nx"
31import "nx_workstream_store.nx"
32import "nx_framed_append.nx"
33import "nx_syscalls.nx"
34
35const CW_N: i64 = 12 // concurrent workers (= workstreams 0..11)
36const CW_K: i64 = 8 // transitions per worker (12*8 = 96 < 256 manifest cap)
37const CW_MAXWS: i64 = 64
38const CW_REC: i64 = 256
39const CW_LOG: *u8 = "knowledge/status/ws_crash_gate.log"
40
41// deterministic terminal state for workstream w (spread over the 5 ledger states 0..4)
42func cw_term(w: i64) -> i64 { return (w * 3 + 2) % 5 }
43
44// stdout-only writers
45func so(s: *u8) -> i64 {
46 var n: i64 = 0
47 while s[n] != (0 as u8) { n = n + 1 }
48 sys_write(1, s, n)
49 return 0
50}
51func son(v: i64) -> i64 {
52 let bb: *u8 = sys_mmap(28)
53 var m: i64 = v
54 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
55 let t: *u8 = sys_mmap(28)
56 var k: i64 = 0
57 if m == 0 { t[0] = 48 as u8; k = 1 }
58 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
59 var i: i64 = 0
60 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
61 sys_write(1, bb, k)
62 return 0
63}
64
65// buffer assembly
66func cw_cat(dst: *u8, off: i64, s: *u8) -> i64 {
67 var i: i64 = 0
68 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
69 return off + i
70}
71func cw_catn(dst: *u8, off: i64, v: i64) -> i64 {
72 var m: i64 = v
73 var o: i64 = off
74 if m == 0 { dst[o] = 48 as u8; return o + 1 }
75 let t: *u8 = sys_mmap(28)
76 var k: i64 = 0
77 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
78 var i: i64 = 0
79 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
80 return o + k
81}
82func cw_atoi(s: *u8) -> i64 {
83 var v: i64 = 0
84 var i: i64 = 0
85 while s[i] != (0 as u8) {
86 let c: i64 = s[i] as i64
87 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
88 i = i + 1
89 }
90 return v
91}
92func cw_path(out: *u8, tag: *u8, epoch: i64, suffix: *u8) -> i64 {
93 var o: i64 = 0
94 o = cw_cat(out, o, "/tmp/" as *u8)
95 o = cw_cat(out, o, tag)
96 o = cw_catn(out, o, epoch)
97 o = cw_cat(out, o, suffix)
98 out[o] = 0 as u8
99 return o
100}
101
102// state field (field 2) int of registry record for ws under prefix; -1 if not present.
103func cw_get_state(prefix: *u8, ws: i64) -> i64 {
104 let key: *u8 = sys_mmap(128)
105 var o: i64 = 0
106 o = cw_cat(key, o, "ws:" as *u8)
107 o = cw_catn(key, o, ws)
108 key[o] = 0 as u8
109 let pq: *i64 = sys_mmap(16) as *i64
110 let lq: *i64 = sys_mmap(16) as *i64
111 if ws_get_p(prefix, key, pq, lq) != WS_FOUND { return 0 - 1 }
112 let sb: *u8 = sys_mmap(64)
113 ws_field(pq[0] as *u8, lq[0], 2, sb)
114 return cw_atoi(sb)
115}
116
117// one worker: CW_K transitions for workstream w to BOTH the reflog AND the live registry.
118func cw_worker(reflog: *u8, regp: *u8, w: i64) -> i64 {
119 let pid: i64 = __syscall(39, 0, 0, 0, 0, 0, 0) // getpid (actor)
120 let key: *u8 = sys_mmap(128)
121 var ko: i64 = 0
122 ko = cw_cat(key, ko, "ws:" as *u8)
123 ko = cw_catn(key, ko, w)
124 key[ko] = 0 as u8
125 let val: *u8 = sys_mmap(256)
126 var i: i64 = 0
127 while i < CW_K {
128 var nw: i64 = (i + 1) % 5
129 if i == CW_K - 1 { nw = cw_term(w) }
130 ledger_append(reflog, w, i % 5, nw, pid, i, w * 1000 + i)
131 var vo: i64 = 0
132 vo = cw_catn(val, vo, w)
133 vo = cw_cat(val, vo, "\tE-LIVE\t" as *u8)
134 vo = cw_catn(val, vo, nw)
135 vo = cw_cat(val, vo, "\t0\t-\t-\t-" as *u8)
136 val[vo] = 0 as u8
137 ws_put_locked(regp, key, val)
138 i = i + 1
139 }
140 return 0
141}
142
143// fork CW_N workers over (reflog, regp), wait all.
144func cw_spawn(reflog: *u8, regp: *u8) -> i64 {
145 let pids: *i64 = sys_mmap(8 * (CW_N + 4)) as *i64
146 var w: i64 = 0
147 while w < CW_N {
148 let pid: i64 = sys_fork()
149 if pid == 0 {
150 cw_worker(reflog, regp, w)
151 sys_exit(0)
152 }
153 pids[w] = pid
154 w = w + 1
155 }
156 let st: *i64 = sys_mmap(16) as *i64
157 w = 0
158 while w < CW_N {
159 sys_wait4(pids[w], st, 0)
160 w = w + 1
161 }
162 return 0
163}
164
165// count workstreams 0..CW_N-1 whose state under prefix == cw_term(w)
166func cw_count_correct(prefix: *u8) -> i64 {
167 var ok: i64 = 0
168 var w: i64 = 0
169 while w < CW_N {
170 if cw_get_state(prefix, w) == cw_term(w) { ok = ok + 1 }
171 w = w + 1
172 }
173 return ok
174}
175// count workstreams 0..CW_N-1 PRESENT (state >= 0) under prefix
176func cw_count_present(prefix: *u8) -> i64 {
177 var p: i64 = 0
178 var w: i64 = 0
179 while w < CW_N {
180 if cw_get_state(prefix, w) >= 0 { p = p + 1 }
181 w = w + 1
182 }
183 return p
184}
185
186func main() -> i64 {
187 let epoch: i64 = sys_now_realtime_sec()
188 let reflog: *u8 = sys_mmap(128)
189 let regp_live: *u8 = sys_mmap(128)
190 let regp_rec: *u8 = sys_mmap(128)
191 let regp_norec: *u8 = sys_mmap(128)
192 cw_path(reflog, "cwlog" as *u8, epoch, ".log" as *u8)
193 cw_path(regp_live, "cwlive" as *u8, epoch, "-" as *u8)
194 cw_path(regp_rec, "cwrec" as *u8, epoch, "-" as *u8)
195 cw_path(regp_norec,"cwnr" as *u8, epoch, "-" as *u8)
196
197 // ===== WORK PHASE: concurrent writers -> reflog + live registry =====
198 cw_spawn(reflog, regp_live)
199
200 // ----- T1 floor-held: reflog complete + untorn -----
201 let state: *i64 = sys_mmap(8 * CW_MAXWS) as *i64
202 var si: i64 = 0
203 while si < CW_MAXWS { state[si] = 0 - 1; si = si + 1 }
204 let outs: *i64 = sys_mmap(32) as *i64
205 ledger_replay(reflog, state, outs, CW_MAXWS)
206 let lines: i64 = outs[0]
207 let flagged: i64 = outs[2]
208 let want_lines: i64 = CW_N * CW_K
209 var t1: i64 = 0
210 if lines == want_lines { if flagged == 0 { t1 = 1 } }
211
212 // ----- T2 live-registry consistent (R8 lost no update) -----
213 let live_correct: i64 = cw_count_correct(regp_live)
214 var t2: i64 = 0
215 if live_correct == CW_N { t2 = 1 }
216
217 // ===== CRASH: the derived registry is lost. RECOVER from the reflog. =====
218 let outs2: *i64 = sys_mmap(32) as *i64
219 ledger_rebuild(reflog, regp_rec, CW_MAXWS, outs2)
220 let recovered: i64 = outs2[3]
221 let rec_correct: i64 = cw_count_correct(regp_rec)
222 var t3: i64 = 0
223 if recovered == CW_N { if rec_correct == CW_N { t3 = 1 } }
224
225 // ----- T4 NEG load-bearing: a wiped registry NOT rebuilt has nothing -----
226 let norec_present: i64 = cw_count_present(regp_norec)
227 var t4: i64 = 0
228 if norec_present == 0 { t4 = 1 }
229
230 var green: i64 = 1
231 if t1 == 0 { green = 0 }
232 if t2 == 0 { green = 0 }
233 if t3 == 0 { green = 0 }
234 if t4 == 0 { green = 0 }
235
236 so("WMS-R10 end-to-end crash integration gate (R0+R8+R9 capstone)\n" as *u8)
237 so(" T1 floor-held reflog_lines=" as *u8); son(lines); so("/" as *u8); son(want_lines)
238 so(" flagged=" as *u8); son(flagged)
239 if t1 == 1 { so(" PASS\n" as *u8) } else { so(" FAIL\n" as *u8) }
240 so(" T2 live-consistent correct=" as *u8); son(live_correct); so("/" as *u8); son(CW_N)
241 if t2 == 1 { so(" PASS\n" as *u8) } else { so(" FAIL\n" as *u8) }
242 so(" T3 recovery-complete recovered=" as *u8); son(recovered)
243 so(" correct=" as *u8); son(rec_correct); so("/" as *u8); son(CW_N)
244 if t3 == 1 { so(" PASS\n" as *u8) } else { so(" FAIL\n" as *u8) }
245 so(" T4 load-bearing (NEG) norebuild_present=" as *u8); son(norec_present); so("/" as *u8); son(CW_N)
246 if t4 == 1 { so(" PASS(recovery-is-load-bearing)\n" as *u8) } else { so(" FAIL(vacuous)\n" as *u8) }
247 so("verdict=" as *u8)
248 if green == 1 { so("GREEN\n" as *u8) } else { so("RED\n" as *u8) }
249
250 // single assembled evidence record -> fa_appendz (dogfoods R0b)
251 let rec: *u8 = sys_mmap(CW_REC + 16)
252 var o: i64 = 0
253 o = cw_cat(rec, o, "WMS-R10-CRASH epoch=" as *u8); o = cw_catn(rec, o, epoch)
254 o = cw_cat(rec, o, " reflog_lines=" as *u8); o = cw_catn(rec, o, lines)
255 o = cw_cat(rec, o, " flagged=" as *u8); o = cw_catn(rec, o, flagged)
256 o = cw_cat(rec, o, " live=" as *u8); o = cw_catn(rec, o, live_correct)
257 o = cw_cat(rec, o, " recovered=" as *u8); o = cw_catn(rec, o, recovered)
258 o = cw_cat(rec, o, " rec_correct=" as *u8); o = cw_catn(rec, o, rec_correct)
259 o = cw_cat(rec, o, " t1=" as *u8); o = cw_catn(rec, o, t1)
260 o = cw_cat(rec, o, " t2=" as *u8); o = cw_catn(rec, o, t2)
261 o = cw_cat(rec, o, " t3=" as *u8); o = cw_catn(rec, o, t3)
262 o = cw_cat(rec, o, " t4=" as *u8); o = cw_catn(rec, o, t4)
263 o = cw_cat(rec, o, " verdict=" as *u8)
264 if green == 1 { o = cw_cat(rec, o, "GREEN" as *u8) } else { o = cw_cat(rec, o, "RED" as *u8) }
265 rec[o] = 0 as u8
266 fa_appendz(CW_LOG, rec, CW_REC)
267
268 if green == 1 { return 0 }
269 return 1
270}