code wiki / _hdl_build / nx_worklog_sweep_gate.nx
nx_worklog_sweep_gate.nx source
↩ module page · 168 lines · 9693 B
1import "nx_gate_gn.nx"
2// nx_worklog_sweep_gate.nx -- hermetic gate for the CRASH-HEAL sweep (wli_sweep_behind_to). Builds a
3// fake project + cursors dir with four sessions and proves the sweep re-ingests exactly the stranded
4// tail of crashed sessions and nothing else:
5// Session A -- CRASHED: cursor parked after line 2; line 3 (Write /tmp/heal_A3.md) is stranded.
6// Session B -- CLEAN: cursor == size; its action (Read /tmp/leak_B1) must NEVER be re-captured.
7// Session C -- cursor present but transcript MISSING; must skip gracefully (no crash, no count).
8// Session D -- CRASHED: cursor after line 1; line 2 (Edit /tmp/heal_D2.md) is stranded.
9// T1 sweep heals exactly 2 (A.line3 + D.line2) -- ALL crashed sessions in ONE pass.
10// T2 heals ONLY the stranded tail: heal_A3/heal_D2 present; A1/A2/D1 (pre-cursor) NOT re-ingested.
11// T3 (NEG) clean session B is skipped: leak_B1 never appears; B's cursor is untouched.
12// T4 (NEG) missing-transcript C is skipped: C's cursor stays 0, the sweep never errors.
13// T5 (NEG / idempotence) a SECOND sweep heals 0 and the log does not grow -- safe on Stop+cron+boot.
14// Sovereign: imports nx_syscalls + the ingest lib only. license_tier: ORIGINAL
15import "nx_syscalls.nx"
16import "nx_worklog_ingest_lib.nx"
17import "nx_gate_verdict.nx"
18
19const SD: *u8 = "/tmp/nx_sweep_gate"
20const SPROJ: *u8 = "/tmp/nx_sweep_gate/proj"
21const SCUR: *u8 = "/tmp/nx_sweep_gate/cursors"
22const SW: *u8 = "/tmp/nx_sweep_gate/worklog.tsv"
23
24const TA: *u8 = "/tmp/nx_sweep_gate/proj/AAAAAAAA.jsonl"
25const TB: *u8 = "/tmp/nx_sweep_gate/proj/BBBBBBBB.jsonl"
26const TD: *u8 = "/tmp/nx_sweep_gate/proj/DDDDDDDD.jsonl"
27const CA: *u8 = "/tmp/nx_sweep_gate/cursors/AAAAAAAA.jsonl.cur"
28const CB: *u8 = "/tmp/nx_sweep_gate/cursors/BBBBBBBB.jsonl.cur"
29const CC: *u8 = "/tmp/nx_sweep_gate/cursors/CCCCCCCC.jsonl.cur"
30const CD: *u8 = "/tmp/nx_sweep_gate/cursors/DDDDDDDD.jsonl.cur"
31
32func gp(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
33func gstrlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
34func gtrunc(path: *u8, content: *u8) -> i64 {
35 let fd: i64 = sys_openat_wr(path, 420)
36 if fd < 0 { return 0 - 1 }
37 let n: i64 = gstrlen(content)
38 if n > 0 { sys_write(fd, content, n) }
39 sys_close(fd)
40 return 0
41}
42func gappend(path: *u8, content: *u8) -> i64 {
43 let fd: i64 = sys_openat_append(path, 420)
44 if fd < 0 { return 0 - 1 }
45 sys_write(fd, content, gstrlen(content))
46 sys_close(fd)
47 return 0
48}
49func gread(path: *u8, buf: *u8, cap: i64) -> i64 {
50 let fd: i64 = sys_openat_rd(path)
51 if fd < 0 { return 0 - 1 }
52 var total: i64 = 0
53 var nrd: i64 = sys_read(fd, buf, cap)
54 while nrd > 0 { total = total + nrd; if total >= cap { nrd = 0 } else { nrd = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) } }
55 sys_close(fd)
56 return total
57}
58func gfind(buf: *u8, n: i64, pat: *u8) -> i64 {
59 var pl: i64 = 0; while pat[pl] != (0 as u8) { pl = pl + 1 }
60 if pl == 0 { return 0 - 1 }
61 var i: i64 = 0
62 while i + pl <= n {
63 var j: i64 = 0; var ok: i64 = 1
64 while j < pl { if buf[i + j] != pat[j] { ok = 0; j = pl } else { j = j + 1 } }
65 if ok == 1 { return i }
66 i = i + 1
67 }
68 return 0 - 1
69}
70func gcount(buf: *u8, n: i64, c: i64) -> i64 { var cnt: i64 = 0; var i: i64 = 0; while i < n { if (buf[i] as i64) == c { cnt = cnt + 1 } i = i + 1 } return cnt }
71func gsize(path: *u8) -> i64 {
72 let st: *u8 = sys_mmap(256)
73 if sys_fstatat(path, st) != 0 { return 0 - 1 }
74 return (st[48] as i64) | ((st[49] as i64) << 8) | ((st[50] as i64) << 16) | ((st[51] as i64) << 24)
75}
76// truncate-to-empty WITHOUT writing -- sys_openat_wr carries O_TRUNC, so an open+close clears the file.
77// (Deliberately avoids gtrunc(path,"") -- a zero-length string literal is mis-pooled by nx_cc and
78// aliases the NEXT literal, so passing "" would write that literal's bytes. Confirmed via this gate.)
79func gclear(path: *u8) -> i64 {
80 let fd: i64 = sys_openat_wr(path, 420)
81 if fd >= 0 { sys_close(fd) }
82 return 0
83}
84
85func main(argc: i64, argv: *i64) -> i64 {
86 gp("=== nx_worklog_sweep_gate (crash-heal: re-ingest stranded tails of crashed sessions) ===\n" as *u8)
87 sys_mkdir(SD, 511)
88 sys_mkdir(SPROJ, 511)
89 sys_mkdir(SCUR, 511)
90 gclear(SW) // clean worklog every run -> deterministic
91
92 let a1: *u8 = "{\"message\":{\"content\":[{\"type\":\"tool_use\",\"id\":\"a1\",\"name\":\"Read\",\"input\":{\"file_path\":\"/tmp/A1\"}}]},\"type\":\"assistant\",\"timestamp\":\"a1\"}\n" as *u8
93 let a2: *u8 = "{\"message\":{\"content\":[{\"type\":\"tool_use\",\"id\":\"a2\",\"name\":\"Bash\",\"input\":{\"command\":\"echo A2\"}}]},\"type\":\"assistant\",\"timestamp\":\"a2\"}\n" as *u8
94 let a3: *u8 = "{\"message\":{\"content\":[{\"type\":\"tool_use\",\"id\":\"a3\",\"name\":\"Write\",\"input\":{\"file_path\":\"/tmp/heal_A3.md\"}}]},\"type\":\"assistant\",\"timestamp\":\"a3\"}\n" as *u8
95 let b1: *u8 = "{\"message\":{\"content\":[{\"type\":\"tool_use\",\"id\":\"b1\",\"name\":\"Read\",\"input\":{\"file_path\":\"/tmp/leak_B1\"}}]},\"type\":\"assistant\",\"timestamp\":\"b1\"}\n" as *u8
96 let d1: *u8 = "{\"message\":{\"content\":[{\"type\":\"tool_use\",\"id\":\"d1\",\"name\":\"Read\",\"input\":{\"file_path\":\"/tmp/D1\"}}]},\"type\":\"assistant\",\"timestamp\":\"d1\"}\n" as *u8
97 let d2: *u8 = "{\"message\":{\"content\":[{\"type\":\"tool_use\",\"id\":\"d2\",\"name\":\"Edit\",\"input\":{\"file_path\":\"/tmp/heal_D2.md\"}}]},\"type\":\"assistant\",\"timestamp\":\"d2\"}\n" as *u8
98
99 // Session A (CRASHED): write lines 1+2, park cursor at end-of-line-2, THEN append the stranded line 3.
100 gtrunc(TA, a1); gappend(TA, a2)
101 let curA: i64 = gstrlen(a1) + gstrlen(a2)
102 gappend(TA, a3)
103 wli_write_cursor(CA, curA)
104
105 // Session B (CLEAN): one line, cursor == full size -> nothing stranded.
106 gtrunc(TB, b1)
107 wli_write_cursor(CB, gstrlen(b1))
108
109 // Session C: cursor exists but NO transcript file -> missing.
110 wli_write_cursor(CC, 0)
111
112 // Session D (CRASHED): line 1 captured, line 2 stranded.
113 gtrunc(TD, d1)
114 let curD: i64 = gstrlen(d1)
115 gappend(TD, d2)
116 wli_write_cursor(CD, curD)
117
118 var pass: i64 = 0
119 var fail: i64 = 0
120 let wbuf: *u8 = sys_mmap(262144)
121
122 // T1: one sweep heals exactly the 2 stranded tails (A.line3 + D.line2).
123 let r1: i64 = wli_sweep_behind_to(SPROJ, SCUR, SW)
124 if r1 == 2 { pass = pass + 1; gp(" T1 heal-count==2 PASS\n" as *u8) } else { fail = fail + 1; gp(" T1 heal-count FAIL got=" as *u8); gn(r1); gp("\n" as *u8) }
125
126 // T2: ONLY the stranded tails were captured -- not the pre-cursor lines, not the clean session.
127 let w1: i64 = gread(SW, wbuf, 262144)
128 var t2: i64 = 1
129 if gfind(wbuf, w1, "/tmp/heal_A3.md" as *u8) < 0 { t2 = 0 } // A's stranded tail present
130 if gfind(wbuf, w1, "/tmp/heal_D2.md" as *u8) < 0 { t2 = 0 } // D's stranded tail present
131 if gfind(wbuf, w1, "/tmp/A1" as *u8) >= 0 { t2 = 0 } // A line1 was pre-cursor -> must NOT reappear
132 if gfind(wbuf, w1, "echo A2" as *u8) >= 0 { t2 = 0 } // A line2 pre-cursor -> must NOT reappear
133 if gfind(wbuf, w1, "/tmp/D1" as *u8) >= 0 { t2 = 0 } // D line1 pre-cursor -> must NOT reappear
134 if t2 == 1 { pass = pass + 1; gp(" T2 only-stranded-tail-healed PASS\n" as *u8) } else { fail = fail + 1; gp(" T2 tail-scope FAIL\n" as *u8) }
135
136 // T3 (NEG): clean session B never re-captured + its cursor untouched.
137 var t3: i64 = 1
138 if gfind(wbuf, w1, "/tmp/leak_B1" as *u8) >= 0 { t3 = 0 }
139 if wli_read_cursor(CB) != gstrlen(b1) { t3 = 0 }
140 if t3 == 1 { pass = pass + 1; gp(" T3 clean-session-skipped PASS\n" as *u8) } else { fail = fail + 1; gp(" T3 clean-session-leaked FAIL\n" as *u8) }
141
142 // T4 (NEG): missing-transcript C skipped gracefully -- cursor stays 0, no error path taken.
143 if wli_read_cursor(CC) == 0 { pass = pass + 1; gp(" T4 missing-transcript-skipped PASS\n" as *u8) } else { fail = fail + 1; gp(" T4 missing-transcript FAIL\n" as *u8) }
144
145 // T5 (NEG / idempotence): a SECOND sweep heals nothing and the log does not grow.
146 let nl1: i64 = gcount(wbuf, w1, 10)
147 let r2: i64 = wli_sweep_behind_to(SPROJ, SCUR, SW)
148 let w2: i64 = gread(SW, wbuf, 262144)
149 let nl2: i64 = gcount(wbuf, w2, 10)
150 if r2 == 0 { if nl2 == nl1 { pass = pass + 1; gp(" T5 idempotent-second-sweep(0) PASS\n" as *u8) } else { fail = fail + 1; gp(" T5 log-grew FAIL\n" as *u8) } } else { fail = fail + 1; gp(" T5 re-healed FAIL got=" as *u8); gn(r2); gp("\n" as *u8) }
151
152 // bonus rigor: A and D cursors advanced to full size (heal complete, now idempotent).
153 var t6: i64 = 1
154 if wli_read_cursor(CA) != gsize(TA) { t6 = 0 }
155 if wli_read_cursor(CD) != gsize(TD) { t6 = 0 }
156 if t6 == 1 { pass = pass + 1; gp(" T6 healed-cursors==size PASS\n" as *u8) } else { fail = fail + 1; gp(" T6 cursor-not-advanced FAIL\n" as *u8) }
157
158 gp("RESULT pass=" as *u8); gn(pass); gp(" fail=" as *u8); gn(fail)
159 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
160 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
161 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
162 let ctr__dry: *i64 = gv_ctr()
163 ctr__dry[0] = pass
164 ctr__dry[1] = pass + fail
165 let rc__dry: i64 = gv_verdict("WORKLOG-SWEEP-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
166 sys_exit(rc__dry)
167 return rc__dry
168}