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}