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}