code wiki / _hdl_build / nx_collision_watchdog.nx

nx_collision_watchdog.nx source

↩ module page · 239 lines · 12120 B

1// nx_collision_watchdog.nx -- WMS-M3: the COLLISION / REGRESSION / FLOATER WATCHDOG. 2// 3// module: nishi-core.control_plane.collision_watchdog 4// capability: CORE_COMPUTE (a reusable self-monitoring scan capability) 5// 6// THE POINT: git is passive history; this is the LIVE OS for the work itself. The data-plane 7// substrate (WMS-R0 atomic framed-append, R1 registry SSOT, R2 reflog, RES-R6 journal) made the 8// channels never-tear. This organ is the WATCHDOG that READS those channels and the genealogy and 9// the known-issue catalogue, then RAISES + ROUTES three failure classes that the OLD torn-write 10// substrate could not reliably surface: 11// (1) TORN WRITES -- framing violations in any status log (MUST be 0 post-R0; a non-zero count 12// means a write somewhere bypassed the locked fa_appendz discipline). 13// (2) RED GATES / REGRESSIONS -- a "verdict=RED" line in an evidence log (a gate flipped red). 14// (3) FLOATING CAPABILITIES -- an organ that cannot trace UP to the spore (genealogy twin of 15// a floating queue row): an untraced node whose parents are "-" but it is 16// NOT the spore. Reuses rt_load+rt_trace (does NOT re-implement genealogy). 17// 18// Each raised issue is ROUTED via ki_recall (recall-or-red / no-unseen-repeat): a SEEN signature is 19// recognized + named (id != UNKNOWN); a genuinely novel one stays honest UNKNOWN. Every alert/evidence 20// write goes through the MANDATORY locked atomic fa_appendz (one assembled buffer -> one locked write, 21// the cn_emit_red discipline) -- NEVER a sequence of sys_write (that is the torn-line bug we killed). 22// 23// REUSE (registries-before-building): fa_cat/fa_catn/fa_appendz (nx_framed_append, WMS-R0b), ki_recall 24// + KI_HIT/KI_UNKNOWN (nx_known_issue_store, recall), rt_load/rt_trace/rt_find + RT_* (nx_root_trace, 25// genealogy). The torn-detect per-line head/tail invariant is the migration-gate idiom, lifted to scan 26// ARBITRARY channels (head/tail are DATA params, not hardcoded to NOTES). 27// ADDITIVE-ONLY: this organ READS existing logs/stores; it modifies no working organ. 28// Sovereign: only the named nx organs + raw syscalls -- no gcc/openssl/node/python. license_tier: ORIGINAL 29import "nx_framed_append.nx" // fa_cat / fa_catn / fa_appendz / fa_append (WMS-R0b locked write) 30import "nx_known_issue_store.nx" // ki_recall + KI_HIT / KI_UNKNOWN + KI_PREFIX (recall routing) 31import "nx_root_trace.nx" // rt_load / rt_trace / rt_find + RT_* (genealogy / floating) 32import "nx_syscalls.nx" 33const CW_MAGIC_1024: i64 = 1024 34 35const CW_REC_CAP: i64 = 512 // bounded alert/evidence record size 36const CW_ALERT_LOG: *u8 = "knowledge/status/watchdog_alerts.log" 37 38// --------------------------------------------------------------------------- 39// DETECTOR 1 -- torn writes in a status log (must be 0 post-R0). 40// A line is WELL-FORMED iff it contains EXACTLY ONE record head AND EXACTLY ONE record tail. 41// Any concurrent-interleave (two heads / two tails on one line, or a missing frame) breaks that. 42// head/tail are DATA params so this scans ANY channel (NOTES, examiner, custom), not one shape. 43// --------------------------------------------------------------------------- 44 45// count of pat occurrences within [a,b) of hay (the migration-gate slice_count idiom). 46func cw_slice_count(hay: *u8, a: i64, b: i64, pat: *u8, pl: i64) -> i64 { 47 var c: i64 = 0 48 var i: i64 = a 49 while i + pl <= b { 50 var k: i64 = 0 51 var hit: i64 = 1 52 while k < pl { if hay[i + k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } } 53 if hit == 1 { c = c + 1 } 54 i = i + 1 55 } 56 return c 57} 58 59// scan buf[0..n) line-by-line; a line is TORN unless it has exactly one head and exactly one tail. 60// returns torn-line count. Empty lines (llen==0) are skipped (not counted as torn). 61func cw_count_torn(buf: *u8, n: i64, head: *u8, tail: *u8) -> i64 { 62 let hl: i64 = fa_len(head) 63 let tl: i64 = fa_len(tail) 64 var torn: i64 = 0 65 var ls: i64 = 0 66 var i: i64 = 0 67 while i < n { 68 if buf[i] == (10 as u8) { 69 let llen: i64 = i - ls 70 if llen > 0 { 71 var ok: i64 = 1 72 if cw_slice_count(buf, ls, i, head, hl) != 1 { ok = 0 } 73 if cw_slice_count(buf, ls, i, tail, tl) != 1 { ok = 0 } 74 if ok == 0 { torn = torn + 1 } 75 } 76 ls = i + 1 77 } 78 i = i + 1 79 } 80 return torn 81} 82 83// read a path then count torn lines; returns -1 if unreadable. 84func cw_scan_torn_file(path: *u8, head: *u8, tail: *u8) -> i64 { 85 let szp: *i64 = sys_mmap(16) as *i64 86 let b: *u8 = sys_read_file(path, szp) 87 let sz: i64 = szp[0] 88 if sz <= 0 { return 0 - 1 } 89 return cw_count_torn(b, sz, head, tail) 90} 91 92// --------------------------------------------------------------------------- 93// DETECTOR 2 -- red gates / regressions in an evidence buffer. 94// counts lines under [0,n) containing "verdict=RED" (the regression signal every gate emits). 95// also copies the LAST red line into last_red (nul-str) so the rollup can route it. 96// --------------------------------------------------------------------------- 97func cw_count_red(buf: *u8, n: i64, last_red: *u8) -> i64 { 98 let pat: *u8 = "verdict=RED\x00" as *u8 99 let pl: i64 = 11 100 var reds: i64 = 0 101 var ls: i64 = 0 102 var i: i64 = 0 103 while i < n { 104 if buf[i] == (10 as u8) { 105 if cw_slice_count(buf, ls, i, pat, pl) >= 1 { 106 reds = reds + 1 107 var o: i64 = 0 108 var q: i64 = ls 109 while q < i { last_red[o] = buf[q]; o = o + 1; q = q + 1 } 110 last_red[o] = 0 as u8 111 } 112 ls = i + 1 113 } 114 i = i + 1 115 } 116 return reds 117} 118 119func cw_scan_red_file(path: *u8, last_red: *u8) -> i64 { 120 let szp: *i64 = sys_mmap(16) as *i64 121 let b: *u8 = sys_read_file(path, szp) 122 let sz: i64 = szp[0] 123 if sz <= 0 { return 0 - 1 } 124 return cw_count_red(b, sz, last_red) 125} 126 127// --------------------------------------------------------------------------- 128// DETECTOR 3 -- floating capabilities (the genealogy twin). 129// Reuses rt_trace (the proven lineage fixpoint): a FLOATING capability is an UNTRACED node 130// (traced[r]==0) whose parents are "-" but it is NOT the spore -- it claims no parent yet is not 131// god. Returns floating count EXCLUDING the spore (the only legal "-" root). Does NOT re-implement 132// genealogy; it composes the substrate. 133// --------------------------------------------------------------------------- 134func cw_count_floating(idbase: i64, parbase: i64, n: i64, spore: *u8, traced: *i64) -> i64 { 135 rt_trace(idbase, parbase, n, spore, traced) 136 var froots: i64 = 0 137 var r: i64 = 0 138 while r < n { 139 if traced[r] == 0 { 140 let pd: *u8 = rt_par_at(parbase, r) 141 if pd[0] == (45 as u8) { 142 // "-" parents and untraced -> floating UNLESS it is the spore (which traces, so 143 // it would not be untraced; but guard explicitly for absent-spore corner cases). 144 if rt_streq(rt_id_at(idbase, r), spore) == 0 { froots = froots + 1 } 145 } 146 } 147 r = r + 1 148 } 149 return froots 150} 151 152// convenience: load lineage.tsv then count floating capabilities. returns -1 if lineage missing. 153func cw_scan_floating(path: *u8, spore: *u8) -> i64 { 154 let idbase: i64 = sys_mmap(RT_MAX * RT_IDCAP) as i64 155 let parbase: i64 = sys_mmap(RT_MAX * RT_PARCAP) as i64 156 let n: i64 = rt_load(path, idbase, parbase) 157 if n <= 0 { return 0 - 1 } 158 let traced: *i64 = sys_mmap(RT_MAX * 8) as *i64 159 return cw_count_floating(idbase, parbase, n, spore, traced) 160} 161 162// --------------------------------------------------------------------------- 163// ROUTING -- recall-or-red on a raised issue. 164// Given a diagnostic string for a detected problem, consult the catalogue under `prefix`. 165// HIT -> fills outid/outrem/outstat with the NAMED known-issue (recognized, not UNKNOWN). 166// MISS -> writes "UNKNOWN" into outid (honest novel input -- no rubber-stamp). 167// returns KI_HIT / KI_UNKNOWN. 168// --------------------------------------------------------------------------- 169func cw_route(prefix: *u8, diag: *u8, dlen: i64, outid: *u8, outrem: *u8, outstat: *u8) -> i64 { 170 let r: i64 = ki_recall(prefix, diag, dlen, outid, outrem, outstat) 171 if r == KI_HIT { return KI_HIT } 172 // honest UNKNOWN: name it so routing is never silent ("can't identify" is still a routed verdict). 173 outid[0] = 85 as u8 // 'U' 174 outid[1] = 78 as u8 // 'N' 175 outid[2] = 75 as u8 // 'K' 176 outid[3] = 78 as u8 // 'N' 177 outid[4] = 79 as u8 // 'O' 178 outid[5] = 87 as u8 // 'W' 179 outid[6] = 78 as u8 // 'N' 180 outid[7] = 0 as u8 181 outrem[0] = 45 as u8; outrem[1] = 0 as u8 // "-" 182 outstat[0] = 79 as u8; outstat[1] = 80 as u8; outstat[2] = 69 as u8; outstat[3] = 78 as u8; outstat[4] = 0 as u8 // "OPEN" 183 return KI_UNKNOWN 184} 185 186// --------------------------------------------------------------------------- 187// THE LOCKED ALERT EMITTER -- eats its own dogfood: the WHOLE record is assembled into ONE buffer 188// and emitted with ONE fa_appendz call (the cn_emit_red discipline). NEVER a sys_write sequence. 189// "WD-ALERT epoch=<e> kind=<k> subject=<s> count=<c> routed_id=<id>" 190// returns fa_appendz's result (bytes>0 on success; -1/-2/-3 on failure). 191// --------------------------------------------------------------------------- 192func cw_emit_alert(path: *u8, epoch: i64, kind: *u8, subject: *u8, count: i64, routed_id: *u8) -> i64 { 193 let buf: *u8 = sys_mmap(CW_REC_CAP + 16) 194 var o: i64 = 0 195 o = fa_cat(buf, o, "WD-ALERT epoch=\x00" as *u8) 196 o = fa_catn(buf, o, epoch) 197 o = fa_cat(buf, o, " kind=\x00" as *u8) 198 o = fa_cat(buf, o, kind) 199 o = fa_cat(buf, o, " subject=\x00" as *u8) 200 o = fa_cat(buf, o, subject) 201 o = fa_cat(buf, o, " count=\x00" as *u8) 202 o = fa_catn(buf, o, count) 203 o = fa_cat(buf, o, " routed_id=\x00" as *u8) 204 o = fa_cat(buf, o, routed_id) 205 buf[o] = 0 as u8 206 return fa_appendz(path, buf, CW_REC_CAP) 207} 208 209func cw_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 210func cw_wn(v: i64) -> i64 { 211 let bb: *u8 = sys_mmap(28); var m: i64 = v 212 if m < 0 { sys_write(1, "-\x00" as *u8, 1); m = 0 - m } 213 let t: *u8 = sys_mmap(28); var k: i64 = 0 214 if m == 0 { t[0] = 48 as u8; k = 1 } 215 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 216 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 217 sys_write(1, bb, k); return 0 218} 219 220// LIB demonstration main: run the three detectors against the LIVE channels + genealogy and 221// report. This is observation, never failure -- exit 0 always. The GATE (separate organ) proves 222// the detectors with positive + negative + tamper controls; this main shows the capability runs. 223func main() -> i64 { 224 cw_w("=== COLLISION WATCHDOG: live scan (torn / red / floating) ===\n\x00" as *u8) 225 let lastred: *u8 = sys_mmap(CW_MAGIC_1024) 226 // Detector 1: torn lines in the conductor_notes channel (NOTES epoch= ... DRILL-CALLED frame). 227 let t: i64 = cw_scan_torn_file("knowledge/status/conductor_notes.log\x00" as *u8, 228 "NOTES epoch=\x00" as *u8, "DRILL-CALLED\x00" as *u8) 229 cw_w(" conductor_notes torn=\x00" as *u8); cw_wn(t); cw_w(" (want 0 post-R0; -1=absent)\n\x00" as *u8) 230 // Detector 2: red verdicts across the watchdog's own gate log (best-effort). 231 let rd: i64 = cw_scan_red_file("knowledge/status/collision_watchdog.log\x00" as *u8, lastred) 232 cw_w(" collision_watchdog red=\x00" as *u8); cw_wn(rd); cw_w(" (-1=absent)\n\x00" as *u8) 233 // Detector 3: floating capabilities against the real lineage tree. 234 let fl: i64 = cw_scan_floating(RT_LIN, RT_SPORE) 235 cw_w(" lineage floating=\x00" as *u8); cw_wn(fl); cw_w(" (want 0; -1=lineage-missing)\n\x00" as *u8) 236 cw_w(" WATCHDOG: live scan done (gate proves detection with controls)\n\x00" as *u8) 237 sys_exit(0) 238 return 0 239}