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}