code wiki / _hdl_build / nx_tooldiff.nx
nx_tooldiff.nx source
↩ module page · 209 lines · 12263 B
1// nx_tooldiff.nx -- toolsplane (seq162 DETECTION half #2: the tool_allowlist ROW-SET shrink detector).
2// Sibling of nx_route_diff (which covers /api routes): THIS catches any GREEN tool row VANISHING (or being
3// de-GREENed) vs a captured baseline -- the allowlist-clobber class (a hub re-assemble, stale-mirror deploy,
4// or bad edit silently killing MCP capability for every session). Transactional use: `snapshot` before a
5// deploy/registry edit, `check` after -> a MISSING name = a capability regression (exit 3 + restore hint).
6// READ-ONLY over the live registry; baseline + verdict land in the evidence layer (deploy_checks row
7// tool-shrink -> nx_deploy_ready surfaces it). ENFORCEMENT/auto-rollback stays mgmt-owned, same stance as
8// nx_route_diff (a cron cannot know intent; the deploy transaction can -- filed rung).
9// nx_tooldiff snapshot | check
10// ENVELOPE (declared in output): TD_MAXTOOLS rows, TD_REGCAP registry read. exit: 0 GREEN | 3 RED-SHRINK |
11// 4 read-fail | 2 usage.
12// RULE-11 (seq274 / law-warden L001, own-house-first 2026-07-20): v1 of this organ shipped with inline
13// literals (16000/16384/32768/262144). REWRITTEN, not patched -- every bound is a NAMED CONST below, so a
14// future session tuning a limit edits a declaration instead of burying a new magic number in the body.
15// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
16import "nx_syscalls.nx"
17
18const TD_REGCAP: i64 = 262144
19const TD_BLOBCAP: i64 = 16384
20const TD_BASECAP: i64 = 16384
21const TD_BASEREAD: i64 = 16000
22const TD_WBUFMAX: i64 = 16300
23const TD_REPCAP: i64 = 32768
24const TD_REPMAX: i64 = 32000
25// ★★CAP RAISED + TRUNCATION MADE VISIBLE (2026-07-30). This was 512 and the live GREEN allowlist had
26// reached EXACTLY 512, so td_extract stopped at the cap and every row past it was invisible.
27// MEASURED CONSEQUENCE: nx_tooldiff reported `REMOVED: nx_eco_graph_build` = RED-SHRINK = a capability
28// REGRESSION -- while the row sat at tool_allowlist.conf:550, present and GREEN the whole time. The output
29// even printed `current=512` next to `TD_MAXTOOLS=512`, which is the tell, but declared the cap only in a
30// PARENTHETICAL and then presented the truncated scan as the verdict.
31// ★A FALSE CAPABILITY-REGRESSION ALARM IS WORSE THAN NO ALARM: it sends an operator hunting a phantom
32// vanished tool, and it trains everyone to ignore the one check that would catch a REAL allowlist clobber
33// (seq162). Third instance of this class in one session, after nx_debt_hygiene and nx_dbthyg.
34const TD_MAXTOOLS: i64 = 4096
35const TD_PTR: i64 = 8
36const TD_SLACK: i64 = 16
37const TD_NL: i64 = 10
38const TD_TAB: i64 = 9
39const TD_HASH: i64 = 35
40const TD_SNAPCH: i64 = 115
41const TD_MODE: i64 = 420
42const TD_STDOUT: i64 = 1
43const TD_STDERR: i64 = 2
44const TD_EXIT_USAGE: i64 = 2
45const TD_EXIT_SHRINK: i64 = 3
46const TD_EXIT_FAIL: i64 = 4
47
48func td_w(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
49func td_b(rep: *u8, pos: i64, s: *u8) -> i64 { var p: i64 = pos; var i: i64 = 0; while s[i] != (0 as u8) { if p < TD_REPMAX { rep[p] = s[i]; p = p + 1 } i = i + 1 } return p }
50func td_bn(rep: *u8, pos: i64, v: i64) -> i64 { var p: i64 = pos; var m: i64 = v; if m < 0 { if p < TD_REPMAX { rep[p] = 45 as u8; p = p + 1 } m = 0 - m } let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { if p < TD_REPMAX { rep[p] = t[k - 1 - i]; p = p + 1 } i = i + 1 } return p }
51func td_reads(path: *u8, buf: *u8, cap: i64) -> i64 {
52 let fd: i64 = sys_openat_rd(path)
53 if fd < 0 { return 0 - 1 }
54 var n: i64 = 0
55 var go: i64 = 1
56 while go == 1 { let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } }
57 sys_close(fd)
58 return n
59}
60func td_span_eq_z(a: *u8, ls: i64, le: i64, z: *u8) -> i64 {
61 var i: i64 = ls
62 var j: i64 = 0
63 while i < le { if z[j] == (0 as u8) { return 0 } if a[i] != z[j] { return 0 } i = i + 1; j = j + 1 }
64 if z[j] != (0 as u8) { return 0 }
65 return 1
66}
67func td_in_baseline(bbuf: *u8, bn: i64, z: *u8) -> i64 {
68 var ls: i64 = 0
69 var i: i64 = 0
70 var hit: i64 = 0
71 while i <= bn {
72 var isend: i64 = 0
73 if i == bn { isend = 1 } else { if bbuf[i] == (TD_NL as u8) { isend = 1 } }
74 if isend == 1 { if i > ls { if td_span_eq_z(bbuf, ls, i, z) == 1 { hit = 1; i = bn } } ls = i + 1 }
75 i = i + 1
76 }
77 return hit
78}
79func td_line_in_cur(bbuf: *u8, ls: i64, le: i64, blob: *u8, offs: *i64, cnt: i64) -> i64 {
80 var j: i64 = 0
81 var hit: i64 = 0
82 while j < cnt { if td_span_eq_z(bbuf, ls, le, (blob as i64 + offs[j]) as *u8) == 1 { hit = 1; j = cnt } else { j = j + 1 } }
83 return hit
84}
85func td_extract(buf: *u8, n: i64, blob: *u8, offs: *i64, maxr: i64) -> i64 {
86 var cnt: i64 = 0
87 var bp: i64 = 0
88 var i: i64 = 0
89 while i < n {
90 var le: i64 = i
91 var s: i64 = 1
92 while s == 1 { if le >= n { s = 0 } else { if buf[le] == (TD_NL as u8) { s = 0 } else { le = le + 1 } } }
93 if le > i { if buf[i] != (TD_HASH as u8) {
94 var ne: i64 = i
95 var s2: i64 = 1
96 while s2 == 1 { if ne >= le { s2 = 0 } else { if buf[ne] == (TD_TAB as u8) { s2 = 0 } else { ne = ne + 1 } } }
97 if ne > i { if ne < le {
98 var ps: i64 = ne + 1
99 var pe: i64 = ps
100 var s3: i64 = 1
101 while s3 == 1 { if pe >= le { s3 = 0 } else { if buf[pe] == (TD_TAB as u8) { s3 = 0 } else { pe = pe + 1 } } }
102 if pe < le {
103 var gs: i64 = pe + 1
104 var ge: i64 = gs
105 var s4: i64 = 1
106 while s4 == 1 { if ge >= le { s4 = 0 } else { if buf[ge] == (TD_TAB as u8) { s4 = 0 } else { ge = ge + 1 } } }
107 if td_span_eq_z(buf, gs, ge, "GREEN" as *u8) == 1 {
108 if cnt < maxr {
109 offs[cnt] = bp
110 var k: i64 = i
111 while k < ne { if bp < TD_WBUFMAX { blob[bp] = buf[k]; bp = bp + 1 } k = k + 1 }
112 blob[bp] = 0 as u8
113 bp = bp + 1
114 cnt = cnt + 1
115 }
116 }
117 }
118 } }
119 } }
120 i = le + 1
121 }
122 return cnt
123}
124func main(argc: i64, argv: *i64) -> i64 {
125 if argc < 2 { td_w(TD_STDERR, "usage: nx_tooldiff snapshot | check\n" as *u8); sys_exit(TD_EXIT_USAGE); return TD_EXIT_USAGE }
126 let verb: *u8 = argv[1] as *u8
127 let reg: *u8 = "tool_allowlist.conf" as *u8
128 let baseline: *u8 = "knowledge/status/tool_baseline.txt" as *u8
129 let basetmp: *u8 = "knowledge/status/tool_baseline.txt.tmp" as *u8
130 let cbuf: *u8 = sys_mmap(TD_REGCAP + TD_SLACK)
131 let cn: i64 = td_reads(reg, cbuf, TD_REGCAP)
132 if cn <= 0 { td_w(TD_STDOUT, "TOOL-DIFF verdict=READ-FAIL tool_allowlist.conf unreadable\n" as *u8); sys_exit(TD_EXIT_FAIL); return TD_EXIT_FAIL }
133 let blob: *u8 = sys_mmap(TD_BLOBCAP)
134 let offs: *i64 = sys_mmap(TD_MAXTOOLS * TD_PTR) as *i64
135 let cur: i64 = td_extract(cbuf, cn, blob, offs, TD_MAXTOOLS)
136 if cur <= 0 { td_w(TD_STDOUT, "TOOL-DIFF verdict=READ-FAIL no GREEN rows parsed\n" as *u8); sys_exit(TD_EXIT_FAIL); return TD_EXIT_FAIL }
137 let rep: *u8 = sys_mmap(TD_REPCAP)
138 var p: i64 = 0
139 let v0: i64 = verb[0]
140 if v0 == TD_SNAPCH {
141 var wp: i64 = 0
142 let wbuf: *u8 = sys_mmap(TD_BASECAP)
143 var i: i64 = 0
144 while i < cur { var j: i64 = offs[i]; while blob[j] != (0 as u8) { if wp < TD_WBUFMAX { wbuf[wp] = blob[j]; wp = wp + 1 } j = j + 1 } if wp < TD_WBUFMAX { wbuf[wp] = TD_NL as u8; wp = wp + 1 } i = i + 1 }
145 let tfd: i64 = sys_openat_wr(basetmp, TD_MODE)
146 if tfd < 0 { td_w(TD_STDOUT, "TOOL-DIFF verdict=WRITE-FAIL\n" as *u8); sys_exit(TD_EXIT_FAIL); return TD_EXIT_FAIL }
147 sys_write(tfd, wbuf, wp); sys_close(tfd); sys_renameat(basetmp, baseline)
148 p = td_b(rep, p, "TOOL-DIFF verb=snapshot tools=" as *u8); p = td_bn(rep, p, cur)
149 p = td_b(rep, p, " -> baseline knowledge/status/tool_baseline.txt VERDICT=GREEN (baseline captured)\n" as *u8)
150 sys_write(TD_STDOUT, rep, p)
151 sys_exit(0); return 0
152 }
153 let bbuf: *u8 = sys_mmap(TD_BASECAP)
154 let bn: i64 = td_reads(baseline, bbuf, TD_BASEREAD)
155 if bn < 0 {
156 var wp2: i64 = 0
157 let wbuf2: *u8 = sys_mmap(TD_BASECAP)
158 var i2: i64 = 0
159 while i2 < cur { var j2: i64 = offs[i2]; while blob[j2] != (0 as u8) { if wp2 < TD_WBUFMAX { wbuf2[wp2] = blob[j2]; wp2 = wp2 + 1 } j2 = j2 + 1 } if wp2 < TD_WBUFMAX { wbuf2[wp2] = TD_NL as u8; wp2 = wp2 + 1 } i2 = i2 + 1 }
160 let tfd2: i64 = sys_openat_wr(basetmp, TD_MODE)
161 if tfd2 >= 0 { sys_write(tfd2, wbuf2, wp2); sys_close(tfd2); sys_renameat(basetmp, baseline) }
162 p = td_b(rep, p, "TOOL-DIFF verb=check tools=" as *u8); p = td_bn(rep, p, cur)
163 p = td_b(rep, p, " no-prior-baseline -> INITIALIZED VERDICT=GREEN\n" as *u8)
164 sys_write(TD_STDOUT, rep, p)
165 let lfdi: i64 = sys_openat_append("knowledge/status/tool_diff.log" as *u8, TD_MODE)
166 if lfdi >= 0 { sys_write(lfdi, rep, p); sys_close(lfdi) }
167 sys_exit(0); return 0
168 }
169 var basec: i64 = 0
170 var removed: i64 = 0
171 p = td_b(rep, p, "TOOL-DIFF verb=check current=" as *u8); p = td_bn(rep, p, cur)
172 // ★TRUNCATION IN THE NUMBERS. `cap_hit=1` means this scan reached TD_MAXTOOLS and rows beyond it were
173 // never read -- in which case every REMOVED name below may be an artifact of the cap, not a regression.
174 // Emitting it as a NUMBER beside the counts is the whole fix: the old output printed the cap in a
175 // parenthetical and the reader had to notice current==cap themselves.
176 p = td_b(rep, p, " cap_hit=" as *u8)
177 if cur >= TD_MAXTOOLS { p = td_b(rep, p, "1-REMOVED-LIST-UNTRUSTWORTHY" as *u8) } else { p = td_b(rep, p, "0" as *u8) }
178 p = td_b(rep, p, " (caps: TD_MAXTOOLS=4096 tools, TD_REGCAP=256KB registry read, TD_BASEREAD=16000B baseline; GREEN rows only -- a de-GREENed row counts as removed)\nREMOVED (in baseline, GONE now): " as *u8)
179 var ls: i64 = 0
180 var i3: i64 = 0
181 while i3 <= bn {
182 var isend: i64 = 0
183 if i3 == bn { isend = 1 } else { if bbuf[i3] == (TD_NL as u8) { isend = 1 } }
184 if isend == 1 { if i3 > ls { basec = basec + 1; if td_line_in_cur(bbuf, ls, i3, blob, offs, cur) == 0 { removed = removed + 1; var k: i64 = ls; while k < i3 { if p < TD_REPMAX { rep[p] = bbuf[k]; p = p + 1 } k = k + 1 } p = td_b(rep, p, " " as *u8) } } ls = i3 + 1 }
185 i3 = i3 + 1
186 }
187 p = td_b(rep, p, "\nADDED (new since baseline): " as *u8)
188 var added: i64 = 0
189 var i4: i64 = 0
190 while i4 < cur { let z: *u8 = (blob as i64 + offs[i4]) as *u8; if td_in_baseline(bbuf, bn, z) == 0 { added = added + 1; p = td_b(rep, p, z); p = td_b(rep, p, " " as *u8) } i4 = i4 + 1 }
191 p = td_b(rep, p, "\nSUMMARY baseline=" as *u8); p = td_bn(rep, p, basec)
192 p = td_b(rep, p, " current=" as *u8); p = td_bn(rep, p, cur)
193 p = td_b(rep, p, " added=" as *u8); p = td_bn(rep, p, added)
194 p = td_b(rep, p, " removed=" as *u8); p = td_bn(rep, p, removed)
195 if removed > 0 {
196 p = td_b(rep, p, "\nVERDICT=RED-SHRINK (" as *u8); p = td_bn(rep, p, removed)
197 p = td_b(rep, p, " tool row(s) vanished/de-GREENed = capability regression; restore the row via /api/tools/register update=yes or bank .prev, then re-snapshot)\n" as *u8)
198 } else {
199 p = td_b(rep, p, "\nVERDICT=GREEN (no tool shrink)\n" as *u8)
200 }
201 sys_write(TD_STDOUT, rep, p)
202 let lfd: i64 = sys_openat_append("knowledge/status/tool_diff.log" as *u8, TD_MODE)
203 if lfd >= 0 { sys_write(lfd, rep, p); sys_close(lfd) }
204 let tfd3: i64 = sys_openat_wr("knowledge/status/tool_diff.out.tmp" as *u8, TD_MODE)
205 if tfd3 >= 0 { sys_write(tfd3, rep, p); sys_close(tfd3); sys_renameat("knowledge/status/tool_diff.out.tmp" as *u8, "knowledge/status/tool_diff.out" as *u8) }
206 if removed > 0 { sys_exit(TD_EXIT_SHRINK); return TD_EXIT_SHRINK }
207 sys_exit(0)
208 return 0
209}