code wiki / _hdl_build / nx_toolflags.nx

nx_toolflags.nx source

↩ module page · 358 lines · 18485 B

1// nx_toolflags.nx -- SAFETY-FLAG DERIVER (2026-08-06). Emits knowledge/tool_flags.conf so tools/list can 2// stop publishing a FABRICATED safety profile for tools whose flags were never looked up. 3// 4// THE DEFECT, MEASURED on the live tools/list: 694 of 869 tools (79.9%) carry the identical quad 5// ro=0 destr=1 idem=0 open=1. That is not a measurement -- it is a hardcoded constant written by 6// ma_schema_append_row at registration time. It does not merely omit, it LIES: nx_pm_board, a READ-ONLY 7// dashboard, is published destructiveHint=true, so an agent that honours annotations refuses a safe read. 8// (The constant errs CONSERVATIVE, so this is an INFORMATION defect, not an unsafe one -- but a fabricated 9// value ends the enquiry where a blank would have invited a lookup.) 10// 11// MATCHES THE KNOWN GOOD: the marker sets and the conservatism below are nx_schema_backfill's 12// sb_derive_flags (shipped 2026-08-06) COPIED DELIBERATELY, not reinvented -- same syscalls, same 13// "DESTRUCTIVE is narrower than writes" split, same refusal to claim `idempotent` (nothing in the source 14// declares it, so it stays 0, the conservative direction). Two consumers is not yet a shared lib (rule 15 15// says three); if a third appears, extract sb_derive_flags to a lib and delete this copy. 16// 17// THE TWEAK, AND WHY IT IS A SAFETY FIX: sb_derive_flags scans ONE FILE. An organ that writes through an 18// imported helper reaches sys_openat_wr in the LIB, not in its own source, so a single-file scan calls it 19// READ-ONLY -- wrong in the UNSAFE direction. This organ scans the organ's own source AND one level of its 20// `import "x.nx"` closure, and REPORTS `depth0_readonly` vs `depth1_readonly`: the difference IS the count 21// of tools the single-file scan would have mislabelled read-only. The number is published either way, so 22// the tweak can never be claimed without evidence. 23// 24// HONEST: no source, or a read that FILLED the buffer (so a marker past the cap may have been missed), 25// => NOT DERIVED, conservative default kept, and COUNTED. Blank beats bad. 26// nx_toolflags -> writes knowledge/tool_flags.conf, prints a JSON census on stdout 27// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 28import "nx_mgmt_api.nx" 29import "nx_syscalls.nx" 30 31const TF_ALCAP: i64 = 1 << 18 32const TF_SRCCAP: i64 = 262144 33const TF_NAMECAP: i64 = 128 34const TF_PATHCAP: i64 = 512 35// declared: at most this many imports per organ are followed (one level, no recursion) 36const TF_MAXIMP: i64 = 32 37const TF_OUT: *u8 = "knowledge/tool_flags.conf" as *u8 38 39static TF_D0RO: i64 = 0 40static TF_D1RO: i64 = 0 41 42func tfw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 43func tfn(v: i64) -> i64 { 44 let b: *u8 = sys_mmap(32) 45 var x: i64 = v 46 var i: i64 = 31 47 if x == 0 { b[i] = 48 as u8; i = i - 1 } 48 while x > 0 { b[i] = ((48 + (x - ((x / 10) * 10))) as u8); x = x / 10; i = i - 1 } 49 sys_write(1, ((b as i64) + i + 1) as *u8, 31 - i) 50 return 0 51} 52func tf_ends(s: *u8, sl: i64, suf: *u8, fl: i64) -> i64 { 53 if sl < fl { return 0 } 54 var i: i64 = 0 55 while i < fl { if s[sl-fl+i] != suf[i] { return 0 } i = i + 1 } 56 return 1 57} 58// A CALL, not a DEFINITION. This distinction is the whole ballgame for the import closure: EVERY organ 59// imports nx_syscalls.nx, which DEFINES `func sys_openat_wr(...)`, `func sys_unlink...` and the rest. A 60// naive substring scan therefore marks the entire estate a writer the moment imports are folded in -- 61// MEASURED: depth-1 read-only collapsed to 114 of 844 and the derived quad converged right back onto the 62// cautious constant it was supposed to replace, so the derivation bought nothing. 63// ★ A DEFINITION AND A CALL ARE THE SAME BYTES; ONLY THEIR LEFT CONTEXT TELLS THEM APART. 64// Occurrences immediately preceded by "func " are declarations and are SKIPPED. No filename is special- 65// cased -- the rule is structural, so it holds for any future syscall-shim library too. 66func tf_has(buf: *u8, n: i64, needle: *u8) -> i64 { 67 var m: i64 = 0 68 while needle[m] != (0 as u8) { m = m + 1 } 69 if m == 0 { return 0 } 70 var i: i64 = 0 71 while i + m <= n { 72 var j: i64 = 0 73 var hit: i64 = 1 74 while j < m { if buf[i+j] != needle[j] { hit = 0; j = m } else { j = j + 1 } } 75 if hit == 1 { 76 var pfx: i64 = 0 77 if i >= 5 { 78 if (buf[i-5] as i64) == 102 { pfx = pfx + 1 } 79 if (buf[i-4] as i64) == 117 { pfx = pfx + 1 } 80 if (buf[i-3] as i64) == 110 { pfx = pfx + 1 } 81 if (buf[i-2] as i64) == 99 { pfx = pfx + 1 } 82 if (buf[i-1] as i64) == 32 { pfx = pfx + 1 } 83 } 84 if pfx < 5 { return 1 } 85 } 86 i = i + 1 87 } 88 return 0 89} 90func tf_wflag(fd: i64, v: i64) -> i64 { if v == 1 { ma_write_str(fd, "1" as *u8) } else { ma_write_str(fd, "0" as *u8) } return 0 } 91 92// Scan ONE file's bytes for the marker sets. box[0]=writes box[1]=destr box[2]=net ; returns 1 scanned 93// conclusively, 0 = unreadable or buffer-filled (inconclusive -> caller must NOT claim derived). 94// STRIP // COMMENTS BEFORE SCANNING (2026-08-07). THE precision defect, diagnosed from ONE concrete 95// case rather than guessed at: nx_capsearch only READS two confs and writes stdout, yet came out ro=0. 96// Its ONLY import is nx_syscalls.nx -- and EVERY write/destructive/network marker in that file sits 97// inside a COMMENT ("sys_mkdir / sys_renameat are not baked...", "sys_unlinkat ... REFERENCE it", 98// "Socket-option constants used by nx_http_server / nx_https_server"). Essentially every organ imports 99// the syscall shim, so the depth-1 closure was inheriting "writer" FROM PROSE. That is also why making 100// the scan definition-aware moved read-only by exactly ZERO: those occurrences were never definitions, 101// they were commentary. 102// ★★★★★★ A SCANNER THAT DOES NOT SKIP COMMENTS MEASURES THE DOCUMENTATION, NOT THE CODE. 103// Declared limitation, deliberately not fixed here: a marker inside a STRING LITERAL still counts. That 104// is rare and errs conservative (over-warns); comments were both common and systematic. 105func tf_strip_comments(src: *u8, n: i64, out: *u8) -> i64 { 106 var o: i64 = 0 107 var i: i64 = 0 108 var incmt: i64 = 0 109 while i < n { 110 let c: i64 = src[i] as i64 111 if incmt == 1 { 112 if c == 10 { incmt = 0; out[o] = 10 as u8; o = o + 1 } 113 } else { 114 var starts: i64 = 0 115 if c == 47 { if i + 1 < n { if src[i+1] == (47 as u8) { starts = 1 } } } 116 if starts == 1 { incmt = 1; i = i + 1 } else { out[o] = src[i]; o = o + 1 } 117 } 118 i = i + 1 119 } 120 return o 121} 122 123func tf_scan_one(path: *u8, box: *i64) -> i64 { 124 let fd: i64 = sys_openat_rd(path) 125 if fd < 0 { return 0 } 126 let raw: *u8 = sys_mmap(TF_SRCCAP) 127 let rn: i64 = sys_read(fd, raw, TF_SRCCAP - 1) 128 sys_close(fd) 129 if rn <= 0 { return 0 } 130 if rn >= TF_SRCCAP - 1 { return 0 } 131 let sb: *u8 = sys_mmap(TF_SRCCAP) 132 let sn: i64 = tf_strip_comments(raw, rn, sb) 133 if tf_has(sb, sn, "sys_openat_wr" as *u8) == 1 { box[0] = 1 } 134 if tf_has(sb, sn, "sys_openat_append" as *u8) == 1 { box[0] = 1 } 135 if tf_has(sb, sn, "sys_mkdir" as *u8) == 1 { box[0] = 1 } 136 if tf_has(sb, sn, "sts_put" as *u8) == 1 { box[0] = 1 } 137 if tf_has(sb, sn, "ss_append" as *u8) == 1 { box[0] = 1 } 138 // DESTRUCTIVE is NARROWER than "writes": appending a row is not unlinking a file. 139 if tf_has(sb, sn, "sys_unlink" as *u8) == 1 { box[1] = 1 } 140 if tf_has(sb, sn, "sys_rename" as *u8) == 1 { box[1] = 1 } 141 if tf_has(sb, sn, "sys_rmdir" as *u8) == 1 { box[1] = 1 } 142 if tf_has(sb, sn, "sys_ftruncate" as *u8) == 1 { box[1] = 1 } 143 if tf_has(sb, sn, "sys_socket" as *u8) == 1 { box[2] = 1 } 144 if tf_has(sb, sn, "sys_connect" as *u8) == 1 { box[2] = 1 } 145 if tf_has(sb, sn, "nx_https_" as *u8) == 1 { box[2] = 1 } 146 if tf_has(sb, sn, "nx_tls" as *u8) == 1 { box[2] = 1 } 147 if tf_has(sb, sn, "hf_fetch" as *u8) == 1 { box[2] = 1 } 148 return 1 149} 150 151// Collect up to TF_MAXIMP `import "x.nx"` names from path into imps (TF_PATHCAP-strided). Returns count. 152func tf_imports(path: *u8, imps: *u8) -> i64 { 153 let fd: i64 = sys_openat_rd(path) 154 if fd < 0 { return 0 } 155 let sb: *u8 = sys_mmap(TF_SRCCAP) 156 let sn: i64 = sys_read(fd, sb, TF_SRCCAP - 1) 157 sys_close(fd) 158 if sn <= 0 { return 0 } 159 var cnt: i64 = 0 160 var i: i64 = 0 161 while i + 8 < sn { 162 var m: i64 = 1 163 if sb[i] != (105 as u8) { m = 0 } 164 if m == 1 { if sb[i+1] != (109 as u8) { m = 0 } } 165 if m == 1 { if sb[i+2] != (112 as u8) { m = 0 } } 166 if m == 1 { if sb[i+3] != (111 as u8) { m = 0 } } 167 if m == 1 { if sb[i+4] != (114 as u8) { m = 0 } } 168 if m == 1 { if sb[i+5] != (116 as u8) { m = 0 } } 169 if m == 1 { if sb[i+6] != (32 as u8) { m = 0 } } 170 if m == 1 { if sb[i+7] != (34 as u8) { m = 0 } } 171 if m == 1 { 172 var p: i64 = i + 8 173 var o: i64 = 0 174 var go: i64 = 1 175 while go == 1 { 176 if p >= sn { go = 0 } else { 177 if sb[p] == (34 as u8) { go = 0 } else { 178 if cnt < TF_MAXIMP { if o < TF_PATHCAP - 1 { imps[cnt*TF_PATHCAP + o] = sb[p]; o = o + 1 } } 179 p = p + 1 180 } 181 } 182 } 183 if o > 0 { if cnt < TF_MAXIMP { imps[cnt*TF_PATHCAP + o] = 0 as u8; cnt = cnt + 1 } } 184 i = p 185 } 186 i = i + 1 187 } 188 return cnt 189} 190 191// Resolve <stem>.nx under _hdl_build then runtime; 1 if a readable path landed in out. 192func tf_resolve(stem: *u8, stl: i64, out: *u8) -> i64 { 193 var o: i64 = sd_cat(out, 0, "buildroot/runtime/_hdl_build/" as *u8) 194 var c: i64 = 0 195 while c < stl { out[o] = stem[c]; o = o + 1; c = c + 1 } 196 out[o] = 0 as u8 197 var fd: i64 = sys_openat_rd(out) 198 if fd >= 0 { sys_close(fd); return 1 } 199 o = sd_cat(out, 0, "buildroot/runtime/" as *u8) 200 c = 0 201 while c < stl { out[o] = stem[c]; o = o + 1; c = c + 1 } 202 out[o] = 0 as u8 203 fd = sys_openat_rd(out) 204 if fd >= 0 { sys_close(fd); return 1 } 205 return 0 206} 207 208func main() -> i64 { 209 let al: *u8 = sys_mmap(TF_ALCAP) 210 let fd: i64 = sys_openat_rd("tool_allowlist.conf" as *u8) 211 if fd < 0 { tfw("{\"organ\":\"nx_toolflags\",\"refused\":\"tool_allowlist.conf unreadable\"}\n" as *u8); sys_exit(2); return 2 } 212 let n: i64 = sys_read(fd, al, TF_ALCAP - 1) 213 sys_close(fd) 214 if n <= 0 { tfw("{\"organ\":\"nx_toolflags\",\"refused\":\"tool_allowlist.conf empty\"}\n" as *u8); sys_exit(2); return 2 } 215 if n >= TF_ALCAP - 1 { tfw("{\"organ\":\"nx_toolflags\",\"refused\":\"allowlist read filled the buffer -- refusing a partial census\"}\n" as *u8); sys_exit(2); return 2 } 216 al[n] = 0 as u8 217 218 sys_unlinkat(TF_OUT) 219 let of: i64 = sys_openat_append(TF_OUT, 420) 220 if of < 0 { tfw("{\"organ\":\"nx_toolflags\",\"refused\":\"cannot create knowledge/tool_flags.conf\"}\n" as *u8); sys_exit(2); return 2 } 221 ma_write_str(of, "# tool_flags.conf -- DERIVED MCP safety annotations, emitted by nx_toolflags from each organ's own\n" as *u8) 222 ma_write_str(of, "# source AND one level of its import closure (a writer reached through a lib is NOT read-only).\n" as *u8) 223 ma_write_str(of, "# Read by nx_tools_api ONLY for rows whose schema output-desc marks them registration-synthesized,\n" as *u8) 224 ma_write_str(of, "# i.e. whose flags were the hardcoded constant and never a measurement. idempotent stays UNDECLARED.\n" as *u8) 225 ma_write_str(of, "# TAB: <name>\\t<readOnly>\\t<destructive>\\t<idempotent>\\t<openWorld>\n" as *u8) 226 227 var scanned: i64 = 0 228 var derived: i64 = 0 229 var nosrc: i64 = 0 230 let nm: *u8 = sys_mmap(TF_NAMECAP) 231 let elf: *u8 = sys_mmap(TF_PATHCAP) 232 let pth: *u8 = sys_mmap(TF_PATHCAP) 233 let ipth: *u8 = sys_mmap(TF_PATHCAP) 234 let imps: *u8 = sys_mmap(TF_MAXIMP * TF_PATHCAP) 235 let b0: *i64 = sys_mmap(64) as *i64 236 let b1: *i64 = sys_mmap(64) as *i64 237 var i: i64 = 0 238 while i < n { 239 var le: i64 = i 240 var sc: i64 = 1 241 while sc == 1 { if le >= n { sc = 0 } else { if al[le] == (10 as u8) { sc = 0 } else { le = le + 1 } } } 242 var ok: i64 = 1 243 if le <= i { ok = 0 } 244 if ok == 1 { if al[i] == (35 as u8) { ok = 0 } } 245 if ok == 1 { 246 var nmn: i64 = 0 247 var p: i64 = i 248 var s2: i64 = 1 249 while s2 == 1 { 250 if p >= le { s2 = 0; ok = 0 } else { 251 if al[p] == (9 as u8) { s2 = 0 } else { 252 if nmn < TF_NAMECAP - 1 { nm[nmn] = al[p]; nmn = nmn + 1 } 253 p = p + 1 254 } 255 } 256 } 257 nm[nmn] = 0 as u8 258 var en: i64 = 0 259 if ok == 1 { 260 p = p + 1 261 var s3: i64 = 1 262 while s3 == 1 { 263 if p >= le { s3 = 0 } else { 264 if al[p] == (9 as u8) { s3 = 0 } else { 265 if en < TF_PATHCAP - 1 { elf[en] = al[p]; en = en + 1 } 266 p = p + 1 267 } 268 } 269 } 270 } 271 elf[en] = 0 as u8 272 if nmn == 0 { ok = 0 } 273 if en == 0 { ok = 0 } 274 if ok == 1 { 275 scanned = scanned + 1 276 var bs: i64 = 0 277 var k: i64 = 0 278 while elf[k] != (0 as u8) { if elf[k] == (47 as u8) { bs = k + 1 } k = k + 1 } 279 var stl: i64 = k - bs 280 let base: *u8 = ((elf as i64) + bs) as *u8 281 if tf_ends(base, stl, ".sov.elf.new" as *u8, 12) == 1 { stl = stl - 12 } else { 282 if tf_ends(base, stl, ".elf.new" as *u8, 8) == 1 { stl = stl - 8 } else { 283 if tf_ends(base, stl, ".sov.elf" as *u8, 8) == 1 { stl = stl - 8 } else { 284 if tf_ends(base, stl, ".elf" as *u8, 4) == 1 { stl = stl - 4 } else { 285 if tf_ends(base, stl, ".new" as *u8, 4) == 1 { stl = stl - 4 } 286 } 287 } 288 } 289 } 290 let stem: *u8 = sys_mmap(TF_PATHCAP) 291 var c3: i64 = 0 292 while c3 < stl { stem[c3] = base[c3]; c3 = c3 + 1 } 293 stem[stl] = 46 as u8 294 stem[stl+1] = 110 as u8 295 stem[stl+2] = 120 as u8 296 stem[stl+3] = 0 as u8 297 var got: i64 = 0 298 if stl > 0 { got = tf_resolve(stem, stl + 3, pth) } 299 if got == 0 { nosrc = nosrc + 1 } else { 300 b0[0] = 0; b0[1] = 0; b0[2] = 0 301 let conc: i64 = tf_scan_one(pth, b0) 302 if conc == 0 { nosrc = nosrc + 1 } else { 303 // depth-0 verdict (what the single-file known good would say) 304 var ro0: i64 = 0 305 if b0[0] == 0 { ro0 = 1 } 306 if ro0 == 1 { TF_D0RO = TF_D0RO + 1 } 307 // depth-1: fold in one level of the import closure 308 b1[0] = b0[0]; b1[1] = b0[1]; b1[2] = b0[2] 309 let nimp: i64 = tf_imports(pth, imps) 310 var q: i64 = 0 311 while q < nimp { 312 let iname: *u8 = ((imps as i64) + q * TF_PATHCAP) as *u8 313 var il: i64 = 0 314 while iname[il] != (0 as u8) { il = il + 1 } 315 if tf_resolve(iname, il, ipth) == 1 { tf_scan_one(ipth, b1) } 316 q = q + 1 317 } 318 var ro1: i64 = 0 319 if b1[0] == 0 { ro1 = 1 } 320 if ro1 == 1 { TF_D1RO = TF_D1RO + 1 } 321 // MIXED DEPTH, CHOSEN BY WHICH DIRECTION THE ERROR HURTS -- not by taste. 322 // readOnly <- DEPTH-1 (import closure). Claiming read-only wrongly is the UNSAFE 323 // error: an agent calls a writer freely. Stay conservative even though it costs 324 // information (measured: 522 -> 114 read-only). 325 // destructive/openWorld <- DEPTH-0 (the organ's OWN source). Depth-1 marked nearly 326 // everything destructive because fat libs (nx_mgmt_api.nx et al) contain unlink 327 // somewhere and are imported for one unrelated helper -- that is the INFORMATION 328 // destroyer, and it is exactly the fabricated-constant failure we came to fix: 329 // a warning that fires on everything carries no signal. Under-warning here is 330 // bounded because readOnly stays conservative, so a tool not marked read-only is 331 // ALREADY "may write" -- destructive only adds "and may unlink/rename". 332 // ★ PICK THE CONSERVATIVE SIDE PER FLAG, NOT PER SCAN: one depth for all four fields 333 // forces a choice between a lie and a blindfold. 334 ma_write_str(of, nm) 335 ma_write_str(of, "\t" as *u8); tf_wflag(of, ro1) 336 ma_write_str(of, "\t" as *u8); tf_wflag(of, b0[1]) 337 ma_write_str(of, "\t0" as *u8) 338 ma_write_str(of, "\t" as *u8); tf_wflag(of, b0[2]) 339 ma_write_str(of, "\n" as *u8) 340 derived = derived + 1 341 } 342 } 343 } 344 } 345 i = le + 1 346 } 347 sys_close(of) 348 349 tfw("{\"organ\":\"nx_toolflags\",\"out\":\"knowledge/tool_flags.conf\",\"scanned\":" as *u8); tfn(scanned) 350 tfw(",\"derived\":" as *u8); tfn(derived) 351 tfw(",\"skipped_inconclusive\":" as *u8); tfn(nosrc) 352 tfw(",\"depth0_readonly\":" as *u8); tfn(TF_D0RO) 353 tfw(",\"depth1_readonly\":" as *u8); tfn(TF_D1RO) 354 tfw(",\"mislabelled_readonly_by_single_file_scan\":" as *u8); tfn(TF_D0RO - TF_D1RO) 355 tfw(",\"note\":\"depth1 folds one level of the import closure: the difference IS the count the known-good single-file scan would have called read-only in the UNSAFE direction. idempotent is UNDECLARED (0), never guessed.\"}\n" as *u8) 356 sys_exit(0) 357 return 0 358}