nx_research_journal_gate.nx source
↩ module page · 315 lines · 16602 B
1// nx_research_journal_gate.nx -- the REFEREE for RES-R6 (crash-resumable journal).
2//
3// Proves nx_research_journal makes a research run RESUME from a crash point by
4// REPLAYING the journal and SKIPPING already-completed phases -- AND proves the
5// test can actually DETECT failure (the mandatory NEGATIVE CONTROL: the old,
6// journal-less path re-runs everything, skipping nothing).
7//
8// GOOD lane (journaled):
9// run 1 ("crash"): execute FETCH (3 sources) + phase_done, EXTRACT (3
10// sources) + phase_done, then VERIFY begin + 1 source_done, then STOP --
11// a crash MID-VERIFY (VERIFY never gets its phase_done). work_run1 counts
12// every phase actually executed.
13// run 2 ("restart"): rj_replay(GOOD) -> resume high-water = EXTRACT (VERIFY
14// is NOT complete, so it is correctly NOT skipped). For FETCH..SYNTH:
15// rj_should_skip -> FETCH+EXTRACT SKIPPED (counted), VERIFY+SYNTH executed.
16// PROOF of resume = good_skipped >= 2 AND completed phases re-executed = 0.
17//
18// BAD lane (NEGATIVE CONTROL -- old path, NO journal):
19// same crash, but restart has no journal -> rj_replay on the absent path
20// returns 0 -> resume high-water 0 -> EVERY phase re-executes -> bad_skipped
21// == 0 and bad_refetched == FULL_RUN. If bad_skipped != 0 the detector is
22// worthless -> RED (no false green).
23//
24// TAMPER x2:
25// (a) oversized record (rec_len+1 > cap=8) must be REJECTED with -2.
26// (b) a TORN "RJ ... VERIFY ... DONE" fragment hand-written as a SEQUENCE of
27// separate sys_write()s (no single-write framing) into a victim journal
28// that ALSO holds a real FETCH+EXTRACT phase_done -> rj_replay must NOT
29// let the torn VERIFY-DONE advance the resume point past EXTRACT. Proves
30// a crash that tore the last line can never cause skipped-but-incomplete
31// work.
32//
33// Output: rows -> stdout + evidence log knowledge/status/research_journal_gate.log,
34// then a final verdict line. Exit 0 GREEN / 1 RED. Sovereign: only nx_syscalls
35// + nx_framed_append + nx_research_journal.
36// license_tier: ORIGINAL
37import "nx_syscalls.nx"
38import "nx_framed_append.nx"
39import "nx_research_journal.nx"
40
41const NSRC: i64 = 3 // sources per FETCH/EXTRACT/VERIFY phase
42const SID: i64 = 7 // the research session id under test
43const FULL_RUN: i64 = 4 // FETCH+EXTRACT+VERIFY+SYNTH = 4 phase executions
44// journal scratch paths are now PID-NAMESPACED at runtime (built in main: gpath/bpath/tpath) so concurrent
45// gate runs are isolated -- the fixed /tmp/rj_*.jrnl constants were the intermittent-RED flake under parallel sweeps.
46
47// dual-sink writer: stdout (fd 1) AND the evidence log fd (lifted from WMS-R0 gate)
48func gp(logfd: i64, s: *u8) -> i64 {
49 var n: i64 = 0
50 while s[n] != (0 as u8) { n = n + 1 }
51 sys_write(1, s, n)
52 if logfd > 0 { sys_write(logfd, s, n) }
53 return 0
54}
55func gn(logfd: i64, v: i64) -> i64 {
56 let bb: *u8 = sys_mmap(28)
57 var m: i64 = v
58 if m < 0 { sys_write(1, "-\x00" as *u8, 1); if logfd > 0 { sys_write(logfd, "-\x00" as *u8, 1) } m = 0 - m }
59 let t: *u8 = sys_mmap(28)
60 var k: i64 = 0
61 if m == 0 { t[0] = 48 as u8; k = 1 }
62 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
63 var i: i64 = 0
64 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
65 sys_write(1, bb, k)
66 if logfd > 0 { sys_write(logfd, bb, k) }
67 return 0
68}
69
70// build "/tmp/<base>_<pid><suffix>" into out -- PID-NAMESPACED scratch journal so CONCURRENT gate runs never
71// share /tmp state (the de-flake: the fixed /tmp/rj_*.jrnl paths corrupted each other under parallel sweeps).
72func g_pidp(base: *u8, suffix: *u8, pid: i64, out: *u8) -> i64 {
73 var o: i64 = 0
74 while base[o] != (0 as u8) { out[o] = base[o]; o = o + 1 }
75 out[o] = 95 as u8; o = o + 1 // '_'
76 let t: *u8 = sys_mmap(28)
77 var m: i64 = pid
78 var k: i64 = 0
79 if m == 0 { t[0] = 48 as u8; k = 1 }
80 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
81 var i: i64 = 0
82 while i < k { out[o] = t[k - 1 - i]; o = o + 1; i = i + 1 }
83 var s: i64 = 0
84 while suffix[s] != (0 as u8) { out[o] = suffix[s]; o = o + 1; s = s + 1 }
85 out[o] = 0 as u8
86 return o
87}
88
89// getpid (__syscall 39) is BROKEN on this backend (-25 for EVERY process, and -25 rendered no digits at all ->
90// /tmp/rj_good_.jrnl shared by every run = the flake). Claim a process-unique POSITIVE i64 token ATOMICALLY via
91// O_EXCL: first creator of /tmp/nxuq_<n> owns <n>; EEXIST retries n+1. Seeded by monotonic us. Collision-free.
92func g_uniq() -> i64 {
93 var n: i64 = sys_now_us()
94 if n < 0 { n = 0 - n }
95 var tries: i64 = 0
96 while tries < 1000000 {
97 let p: *u8 = sys_mmap(64)
98 var o: i64 = 0
99 let pre: *u8 = "/tmp/nxuq_\x00" as *u8
100 while pre[o] != (0 as u8) { p[o] = pre[o]; o = o + 1 }
101 let t: *u8 = sys_mmap(28); var m: i64 = n; var k: i64 = 0
102 if m == 0 { t[0] = 48 as u8; k = 1 }
103 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
104 var i: i64 = 0
105 while i < k { p[o] = t[k - 1 - i]; o = o + 1; i = i + 1 }
106 p[o] = 0 as u8
107 let fd: i64 = __syscall(SYS_OPENAT, AT_FDCWD, p, 0xc1, 0x1a4, 0, 0) // O_CREAT|O_EXCL|O_WRONLY
108 if fd >= 0 { sys_close(fd); return n }
109 n = n + 1
110 tries = tries + 1
111 }
112 return n
113}
114
115// Execute one phase = the "real research work" signal. Logs phase_begin, NSRC
116// source_done records, then phase_done. Returns 1 (one phase of work executed).
117func do_phase(path: *u8, phase: i64) -> i64 {
118 rj_phase_begin(path, SID, phase)
119 var s: i64 = 0
120 while s < NSRC {
121 rj_source_done(path, SID, phase, s)
122 s = s + 1
123 }
124 rj_phase_done(path, SID, phase)
125 return 1
126}
127
128// SYNTH is a single-step phase (no per-source loop).
129func do_synth(path: *u8) -> i64 {
130 rj_phase_begin(path, SID, RJ_SYNTH)
131 rj_phase_done(path, SID, RJ_SYNTH)
132 return 1
133}
134
135func main() -> i64 {
136 let logfd: i64 = sys_openat_append("knowledge/status/research_journal_gate.log\x00" as *u8, 0x1a4)
137 gp(logfd, "RESEARCH-JOURNAL epoch=\x00" as *u8)
138 gn(logfd, sys_now_realtime_sec())
139 gp(logfd, " sid=\x00" as *u8); gn(logfd, SID)
140 gp(logfd, " nsrc=\x00" as *u8); gn(logfd, NSRC)
141 gp(logfd, "\n\x00" as *u8)
142
143 // UNIQUE-NAMESPACED journal scratch paths so CONCURRENT gate runs are isolated (the de-flake; getpid is broken)
144 let pid: i64 = g_uniq()
145 let gpath: *u8 = sys_mmap(128); g_pidp("/tmp/rj_good\x00" as *u8, ".jrnl\x00" as *u8, pid, gpath)
146 let bpath: *u8 = sys_mmap(128); g_pidp("/tmp/rj_bad\x00" as *u8, ".jrnl\x00" as *u8, pid, bpath)
147 let tpath: *u8 = sys_mmap(128); g_pidp("/tmp/rj_torn\x00" as *u8, ".jrnl\x00" as *u8, pid, tpath)
148 let tampath: *u8 = sys_mmap(128); g_pidp("/tmp/rj_tamper\x00" as *u8, ".log\x00" as *u8, pid, tampath)
149
150 // fresh journals each run (truncate the good + torn paths; the bad path stays absent)
151 let gtr: i64 = sys_openat_wr(gpath, 0x1a4)
152 if gtr > 0 { sys_close(gtr) }
153 let ttr: i64 = sys_openat_wr(tpath, 0x1a4)
154 if ttr > 0 { sys_close(ttr) }
155
156 // ============ GOOD LANE: journaled, crash-resumable ============
157 // --- run 1: execute through EXTRACT, then CRASH mid-VERIFY ---
158 var work_run1: i64 = 0
159 work_run1 = work_run1 + do_phase(gpath, RJ_FETCH) // FETCH complete
160 work_run1 = work_run1 + do_phase(gpath, RJ_EXTRACT) // EXTRACT complete
161 // start VERIFY but crash before phase_done: BEGIN + 1 source_done only
162 rj_phase_begin(gpath, SID, RJ_VERIFY)
163 rj_source_done(gpath, SID, RJ_VERIFY, 0)
164 work_run1 = work_run1 + 1 // VERIFY partially executed
165 // (process "crashes" here -- no SYNTH, no VERIFY phase_done)
166
167 // --- run 2: restart, REPLAY, resume from crash point ---
168 let out: *i64 = sys_mmap(32) as *i64
169 let resume_from: i64 = rj_replay(gpath, SID, out) // expect EXTRACT(=2)
170 let good_wf: i64 = out[2]
171 var good_skipped: i64 = 0
172 var good_refetched: i64 = 0 // re-executions of an ALREADY-COMPLETE phase (must be 0)
173 var good_executed: i64 = 0
174 var ph: i64 = RJ_FETCH
175 while ph <= RJ_SYNTH {
176 if rj_should_skip(ph, resume_from) == 1 {
177 good_skipped = good_skipped + 1
178 } else {
179 // execute the phase (resume work)
180 if ph == RJ_SYNTH { do_synth(gpath) } else { do_phase(gpath, ph) }
181 good_executed = good_executed + 1
182 // if this phase was already complete per replay, that is a re-fetch BUG
183 if ph <= resume_from { good_refetched = good_refetched + 1 }
184 }
185 ph = ph + 1
186 }
187 // after resume, the whole run is complete iff SYNTH ran (highest phase done)
188 let out2: *i64 = sys_mmap(32) as *i64
189 let final_hw: i64 = rj_replay(gpath, SID, out2)
190 var good_finished: i64 = 0
191 if final_hw == RJ_SYNTH { good_finished = 1 }
192
193 // ============ BAD LANE: NEGATIVE CONTROL (no journal) ============
194 // run 1 "crashes" the same way -- but NOTHING is journaled. On restart the
195 // replay reads an ABSENT journal -> high-water 0 -> resume nothing -> the old
196 // path re-runs EVERY phase.
197 let outb: *i64 = sys_mmap(32) as *i64
198 let bad_resume: i64 = rj_replay(bpath, SID, outb) // expect 0 (absent file)
199 var bad_skipped: i64 = 0
200 var bad_refetched: i64 = 0
201 ph = RJ_FETCH
202 while ph <= RJ_SYNTH {
203 if rj_should_skip(ph, bad_resume) == 1 {
204 bad_skipped = bad_skipped + 1
205 } else {
206 bad_refetched = bad_refetched + 1 // every phase re-executed from scratch
207 }
208 ph = ph + 1
209 }
210
211 // ============ TAMPER (a): oversized record rejected with -2 ============
212 let trec: *u8 = sys_mmap(64)
213 var ti: i64 = 0
214 while ti < 32 { trec[ti] = 65 as u8; ti = ti + 1 } // 32 'A's, way over cap=8
215 let tamper_oversize: i64 = fa_append(tampath, trec, 32, 8)
216
217 // ============ TAMPER (b): torn VERIFY-DONE must NOT advance resume ============
218 // Write a REAL FETCH+EXTRACT (atomic) into the torn journal, then hand-write a
219 // TORN "RJ ... VERIFY ... ev=DONE" line as a SEQUENCE of separate writes with
220 // NO single-write framing (the exact bug WMS-R0 fixes). A crash could leave
221 // such a fragment. rj_replay must IGNORE it (not well-formed / interleavable)
222 // so the resume high-water stays at EXTRACT, never jumping to VERIFY.
223 do_phase(tpath, RJ_FETCH)
224 do_phase(tpath, RJ_EXTRACT)
225 // torn fragment: open O_APPEND, emit the record as many little writes, and --
226 // critically -- do NOT terminate it with the framed single '\n'+sentinel in a
227 // single write; we deliberately interleave a second record-head, producing a
228 // line that fails rj_wellformed.
229 let tfd: i64 = sys_openat_append(tpath, 0x1a4)
230 if tfd > 0 {
231 sys_write(tfd, "RJ sid=\x00" as *u8, 7)
232 sys_write(tfd, "7\x00" as *u8, 1)
233 sys_write(tfd, " ph=\x00" as *u8, 4)
234 sys_write(tfd, "3\x00" as *u8, 1) // VERIFY
235 sys_write(tfd, " src=\x00" as *u8, 5)
236 sys_write(tfd, "-1\x00" as *u8, 2)
237 sys_write(tfd, " ev=\x00" as *u8, 4)
238 // INTERLEAVE a second record head before the END sentinel -> torn line
239 sys_write(tfd, "2 RJ sid=7 ph=3 src=-1 ev=2 ts=0\x00" as *u8, 32)
240 sys_write(tfd, "\n\x00" as *u8, 1) // newline WITHOUT a clean " END" tail
241 sys_close(tfd)
242 }
243 let out_t: *i64 = sys_mmap(32) as *i64
244 let torn_resume: i64 = rj_replay(tpath, SID, out_t) // must stay at EXTRACT(=2)
245 var tamper_torn_advanced: i64 = 0
246 if torn_resume > RJ_EXTRACT { tamper_torn_advanced = 1 } // 1 = FOOLED (bad)
247
248 // ---------------- rows ----------------
249 gp(logfd, "RJ row=good_resume_from got=\x00" as *u8); gn(logfd, resume_from)
250 gp(logfd, " want=\x00" as *u8); gn(logfd, RJ_EXTRACT)
251 if resume_from == RJ_EXTRACT { gp(logfd, " verdict=PASS\n\x00" as *u8) } else { gp(logfd, " verdict=FAIL\n\x00" as *u8) }
252
253 gp(logfd, "RJ row=good_skipped got=\x00" as *u8); gn(logfd, good_skipped)
254 gp(logfd, " want>=2 (FETCH+EXTRACT) refetched=\x00" as *u8); gn(logfd, good_refetched)
255 if good_skipped >= 2 { if good_refetched == 0 { gp(logfd, " verdict=PASS\n\x00" as *u8) } else { gp(logfd, " verdict=FAIL\n\x00" as *u8) } } else { gp(logfd, " verdict=FAIL\n\x00" as *u8) }
256
257 gp(logfd, "RJ row=good_executed_on_resume got=\x00" as *u8); gn(logfd, good_executed)
258 gp(logfd, " want=2 (VERIFY+SYNTH) finished=\x00" as *u8); gn(logfd, good_finished)
259 if good_executed == 2 { if good_finished == 1 { gp(logfd, " verdict=PASS\n\x00" as *u8) } else { gp(logfd, " verdict=FAIL\n\x00" as *u8) } } else { gp(logfd, " verdict=FAIL\n\x00" as *u8) }
260 gp(logfd, "RJ row=good_wf_lines got=\x00" as *u8); gn(logfd, good_wf); gp(logfd, "\n\x00" as *u8)
261
262 gp(logfd, "RJ row=neg_control bad_resume=\x00" as *u8); gn(logfd, bad_resume)
263 gp(logfd, " bad_skipped=\x00" as *u8); gn(logfd, bad_skipped)
264 gp(logfd, " bad_refetched=\x00" as *u8); gn(logfd, bad_refetched)
265 gp(logfd, " want_skipped=0 want_refetched=\x00" as *u8); gn(logfd, FULL_RUN)
266 if bad_skipped == 0 { if bad_refetched == FULL_RUN { gp(logfd, " verdict=PASS(no-journal-redoes-all)\n\x00" as *u8) } else { gp(logfd, " verdict=FAIL\n\x00" as *u8) } } else { gp(logfd, " verdict=FAIL(skipped-without-journal)\n\x00" as *u8) }
267
268 gp(logfd, "RJ row=tamper_oversize got=\x00" as *u8); gn(logfd, tamper_oversize)
269 gp(logfd, " want=-2\x00" as *u8)
270 if tamper_oversize == (0 - 2) { gp(logfd, " verdict=PASS\n\x00" as *u8) } else { gp(logfd, " verdict=FAIL\n\x00" as *u8) }
271
272 gp(logfd, "RJ row=tamper_torn torn_resume=\x00" as *u8); gn(logfd, torn_resume)
273 gp(logfd, " want<=\x00" as *u8); gn(logfd, RJ_EXTRACT)
274 if tamper_torn_advanced == 0 { gp(logfd, " verdict=PASS(torn-ignored)\n\x00" as *u8) } else { gp(logfd, " verdict=FAIL(torn-advanced-resume)\n\x00" as *u8) }
275
276 // ---------------- verdict ----------------
277 var green: i64 = 1
278 if resume_from != RJ_EXTRACT { green = 0 } // resume point computed correctly
279 if good_skipped < 2 { green = 0 } // FETCH+EXTRACT skipped on resume
280 if good_refetched != 0 { green = 0 } // completed phases NOT re-executed
281 if good_executed != 2 { green = 0 } // VERIFY+SYNTH actually run
282 if good_finished != 1 { green = 0 } // run completes after resume
283 if bad_skipped != 0 { green = 0 } // NEG: no-journal skips nothing
284 if bad_refetched != FULL_RUN { green = 0 } // NEG: no-journal redoes all
285 if tamper_oversize != (0 - 2) { green = 0 } // oversized rejected
286 if tamper_torn_advanced != 0 { green = 0 } // torn line did NOT advance resume
287
288 gp(logfd, "RESEARCH-JOURNAL verdict=\x00" as *u8)
289 if green == 1 { gp(logfd, "GREEN\x00" as *u8) } else { gp(logfd, "RED\x00" as *u8) }
290 gp(logfd, " resume_from=\x00" as *u8); gn(logfd, resume_from)
291 gp(logfd, " good_skipped=\x00" as *u8); gn(logfd, good_skipped)
292 gp(logfd, " good_refetched=\x00" as *u8); gn(logfd, good_refetched)
293 gp(logfd, " good_executed=\x00" as *u8); gn(logfd, good_executed)
294 gp(logfd, " good_finished=\x00" as *u8); gn(logfd, good_finished)
295 gp(logfd, " bad_skipped=\x00" as *u8); gn(logfd, bad_skipped)
296 gp(logfd, " bad_refetched=\x00" as *u8); gn(logfd, bad_refetched)
297 gp(logfd, " tamper_oversize=\x00" as *u8); gn(logfd, tamper_oversize)
298 gp(logfd, " tamper_torn_advanced=\x00" as *u8); gn(logfd, tamper_torn_advanced)
299 if green == 0 {
300 gp(logfd, " reason=\x00" as *u8)
301 if resume_from != RJ_EXTRACT { gp(logfd, "wrong-resume-point \x00" as *u8) }
302 if good_skipped < 2 { gp(logfd, "did-not-skip-completed \x00" as *u8) }
303 if good_refetched != 0 { gp(logfd, "REFETCHED-COMPLETED \x00" as *u8) }
304 if good_executed != 2 { gp(logfd, "wrong-resume-execution \x00" as *u8) }
305 if good_finished != 1 { gp(logfd, "did-not-finish \x00" as *u8) }
306 if bad_skipped != 0 { gp(logfd, "neg-control-skipped-without-journal \x00" as *u8) }
307 if bad_refetched != FULL_RUN { gp(logfd, "neg-control-did-not-rerun-all \x00" as *u8) }
308 if tamper_oversize != (0 - 2) { gp(logfd, "oversize-not-rejected \x00" as *u8) }
309 if tamper_torn_advanced != 0 { gp(logfd, "torn-line-advanced-resume \x00" as *u8) }
310 }
311 gp(logfd, "\n\x00" as *u8)
312 if logfd > 0 { sys_close(logfd) }
313 if green == 1 { return 0 }
314 return 1
315}