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}