code wiki / _hdl_build / nx_toolsafety_lib.nx
nx_toolsafety_lib.nx
buildroot/runtime/_hdl_build/nx_toolsafety_lib.nx
about
nx_toolsafety_lib.nx -- THE ONE MCP SAFETY-FLAG DERIVER (2026-08-25). Write reach, destructive reach,
network reach and exec reach for an organ, derived from its TRANSITIVE import closure, with line
comments AND string literals stripped, and syscall DEFINITIONS skipped.
WHY A LIB, AND WHY NOW. Two derivers already shipped and DISAGREED BY CONSTRUCTION. nx_schema_backfill
(sb_derive_flags) scans ONE FILE, so an organ that writes through an imported helper reaches the write
syscall in the LIB and is published readOnly=1 -- wrong in the UNSAFE direction. nx_toolflags folds ONE
level of the import closure and strips comments, and its own header set the condition for this
extraction in advance: two consumers is not yet a lib, but if a third appears, extract it and delete
the copy. The third consumer arrived (the drift control), so this is that extraction.
WHEN TWO ORGANS MUST AGREE, MAKE DISAGREEMENT IMPOSSIBLE BY CONSTRUCTION: THERE IS ONLY ONE OF IT.
THREE CHANGES FROM THE INCUMBENTS, EACH FOR A REASON THAT WAS MEASURED, NOT PREFERRED:
1. TRANSITIVE closure, not one level. A helper that reaches a writer through a helper of its own is
two hops away and one hop cannot see it. The depth LADDER is published per subject, so the
contribution of the extra depth is evidence rather than a claim about what it bought.
2. STRING LITERALS ARE STRIPPED TOO. The incumbent declared this a rare limitation erring
conservative. It stops being rare the moment the deriver is a LIB that every consumer imports:
a marker table written as literals would put write, destructive and network markers into the
closure of every organ that composed it. That is why the table is a CONF and this source names
no marker at all -- but stripping literals is what makes the property hold for any OTHER scanner
too, including the ones already shipped.
3. sys_read_file, so there is no source-size cap left to be inconclusive about. Both incumbents cap
at 256 KiB and honestly refuse past it; the primitive that closes that hole already exists, sizes
its buffer from the file and cannot short-read.
FAIL-CLOSED EVERYWHERE. Unreadable subject, unreadable marker table, an empty marker class, or a walk
that hits its budget all return INCONCLUSIVE. None of them may return "nothing matched", because
nothing-matched is byte-identical to "this organ is read-only", which is the permissive direction and
the entire defect this ruler exists to stop.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 1 imports · 3 importers
imports: nx_syscalls.nx
imported by: nx_schema_backfill.nxnx_schemadrift.nxnx_schemadrift_gate.nx
structs
| none |
consts
| 34 | const TS_MARKCONF: *u8 = "knowledge/tool_safety_markers.conf" as *u8 |
| 37 | const TS_CLASSES: i64 = 4 |
| 38 | const TS_CW: i64 = 0 |
| 39 | const TS_CD: i64 = 1 |
| 40 | const TS_CN: i64 = 2 |
| 41 | const TS_CX: i64 = 3 |
| 44 | const TS_MAXMARK: i64 = 64 |
| 45 | const TS_MARKCAP: i64 = 64 |
| 46 | const TS_PATHCAP: i64 = 512 |
| 50 | const TS_MEMOSLOTS: i64 = 8192 |
| 51 | const TS_IMPBLK: i64 = 1536 |
| 53 | const TS_MAXWALK: i64 = 1024 |
| 54 | const TS_LF: i64 = 10 |
| 55 | const TS_TAB: i64 = 9 |
| 56 | const TS_HASH_OFF: i64 = 1469598103934665603 |
| 57 | const TS_HASH_PRIME: i64 = 1099511628211 |
| 59 | const TS_HASH_MUL: i64 = 131 |
| 60 | const TS_CH_SLASH: i64 = 47 |
| 61 | const TS_CH_QUOTE: i64 = 34 |
| 62 | const TS_CH_BSLASH: i64 = 92 |
| 63 | const TS_CH_SPACE: i64 = 32 |
| 64 | const TS_CH_HASH: i64 = 35 |
| 65 | const TS_CH_F: i64 = 102 |
| 66 | const TS_CH_U: i64 = 117 |
| 67 | const TS_CH_N: i64 = 110 |
| 68 | const TS_CH_C: i64 = 99 |
| 69 | const TS_CH_I: i64 = 105 |
| 70 | const TS_CH_M: i64 = 109 |
| 71 | const TS_CH_P: i64 = 112 |
| 72 | const TS_CH_O: i64 = 111 |
| 73 | const TS_CH_R: i64 = 114 |
| 74 | const TS_CH_T: i64 = 116 |
| 75 | const TS_FUNCPFX: i64 = 5 |
| 76 | const TS_IMPPFX: i64 = 8 |
| 77 | const TS_ROOT_HDL: *u8 = "buildroot/runtime/_hdl_build/" as *u8 |
| 78 | const TS_ROOT_RT: *u8 = "buildroot/runtime/" as *u8 |
| 80 | const TS_DEPTH_ALL: i64 = 0 - 1 |
functions
| 113 | func ts_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 115 | func ts_cat(d: *u8, o: i64, s: *u8) -> i64 called by 1: ts_resolve |
| 136 | func ts_hash(s: *u8) -> i64 |
| 150 | func ts_streq(a: *u8, b: *u8) -> i64 |
| 164 | func ts_load_markers() -> i64 |
| 231 | func ts_marker_count(cls: i64) -> i64 |
| 237 | func ts_marker_at(cls: i64, idx: i64) -> *u8 called by 1: ts_scan_buf |
| 249 | func ts_strip(src: *u8, n: i64, out: *u8) -> i64 |
| 282 | func ts_has(buf: *u8, n: i64, needle: *u8) -> i64 |
| 307 | func ts_scan_buf(buf: *u8, n: i64, box: *i64) -> i64 |
| 325 | func ts_imports_buf(buf: *u8, n: i64, out: *u8) -> i64 called by 1: ts_slot |
| 358 | func ts_resolve(mod: *u8, out: *u8) -> i64 |
| 372 | func ts_memo_init() -> i64 |
| 392 | func ts_slot(path: *u8) -> i64 |
| 464 | func ts_reach(path: *u8, maxdepth: i64, box: *i64) -> i64 |
| 578 | func ts_quad(path: *u8, quad: *i64, deep: *i64, own: *i64) -> i64 |
| 590 | func ts_set_trace(v: i64) -> i64 { TS_TRACE = v; return v } called by 1: main |
| 595 | func ts_write_src(out: *u8) -> i64 called by 1: main |
| 606 | func ts_stat_files() -> i64 { return TS_STAT_FILES } called by 1: main |
| 607 | func ts_stat_bytes() -> i64 { return TS_STAT_BYTES } called by 1: main |
| 608 | func ts_memo_full() -> i64 { return TS_MEMO_FULL } called by 1: main |