code wiki / _hdl_build / nx_gatescan.nx

nx_gatescan.nx source

↩ module page · 324 lines · 15214 B

1// nx_gatescan.nx -- FIND THE ORGANS NOTHING IS CHECKING. 2// 3// ===== WHY THIS EXISTS ============================================ 4// 5// Two defects were found by luck in this ecosystem within one session: 6// 7// nx_pow10 -- no gate of its own. Its header claimed an interpolation 8// error of <=0.3%; measured, it is 0.66%. That figure had 9// been inherited into a FOOD-SAFETY margin note. 10// nx_arrhenius -- no gate AND nothing imported it. It returned exactly 1.0 11// for every input, i.e. "temperature does not affect 12// reaction rate", from a file whose own comment claimed it 13// carefully managed precision across the reciprocal step. 14// 15// Both were caught because someone happened to look. That is not a control. 16// A session must remember; a tool cannot forget. This organ turns 17// "no gate + no callers = an unverified intention" from a law into a scan. 18// 19// ===== THE MEASUREMENT ============================================ 20// 21// Composes the existing graph engine rather than rebuilding it: the whole- 22// tree walk and accurate import edges already exist and are proven, so this 23// adds exactly one idea on top of them. 24// 25// HAS_GATE -- a node named <base>_test.nx or <base>_gate.nx exists. 26// CONSUMERS -- importers that are NOT that organ's own gate. 27// 28// ★THE SECOND HALF IS THE LOAD-BEARING ONE. Raw fan-in (Ca) counts the gate 29// as an importer, so the moment you write a gate for a dead organ its Ca 30// becomes 1 and a naive orphan check calls it "used". That is exactly what 31// would have hidden nx_arrhenius the instant it was gated. Consumers are 32// therefore counted with gate/test importers EXCLUDED. 33// 34// ===== RISK IS RANKED, NOT LISTED ================================= 35// 36// A flat list of ungated files invites working the easy end. The two classes 37// are not equally dangerous and the ranking says so: 38// 39// UNGATED-LOAD-BEARING (no gate, N consumers) -- an error here propagates 40// into N callers and is attributed to whichever one notices first. 41// This is the nx_pow10 class. Risk RISES with fan-in. 42// DEAD-RISK (no gate, no consumers) -- harmless today because nothing 43// calls it, dangerous the moment somebody wires it up believing the 44// header. This is the nx_arrhenius class. 45// 46// So severity = fan-in for the first class, and a fixed floor for the 47// second: never zero, because "nothing calls it" is a reason to distrust it, 48// not a reason to ignore it. 49// 50// nx_gatescan <root-dir> [min-consumers] [topk] 51// default root runtime, min-consumers 1 (load-bearing only; pass 0 52// to include organs nothing calls), topk 40 53// 54// license_tier: ORIGINAL 55import "nx_syscalls.nx" 56import "nx_eco_graph.nx" 57import "nx_import_scan.nx" 58import "nx_gatelib.nx" 59const GS_MAGIC_4096: i64 = 4096 60const GS_MAGIC_1024: i64 = 1024 61 62const GS_MAXNODE: i64 = 24000 63const GS_MAXEDGE: i64 = 300000 64const GS_ARENA: i64 = 4194304 65const GS_HASH: i64 = 65536 66const GS_FILECAP: i64 = 262144 67 68// ⚠DEBT, NAMED RATHER THAN HIDDEN: the getdents recursion below is the THIRD 69// copy of the same tree walk in this tree (nx_eco_graph_build's egb_walk and 70// nx_dr_tree's tr_walk are the others). It is duplicated here because both 71// of those live in modules that own a main(), so neither can be imported by 72// a second entry point -- the walk was never extracted into a library. The 73// right fix is a shared walk lib, which touches every consumer and needs its 74// own re-gating, so it is filed instead of smuggled into this change. An 75// organ whose whole purpose is finding unchecked code should not quietly add 76// unchecked duplication while doing it. 77 78// A dead organ still scores this much, so it can never rank as "no risk". 79// 80// ⚠⚠THIS FLOOR HID A REAL DEFECT AND THE FIX IS NOT TO RAISE IT. Extracting 81// nx_gatelib created an ungated organ with two consumers -- exactly the class 82// this tool exists to find -- and it did not appear anywhere in the report, 83// because severity 2 ranks BELOW the floor of 3 handed to files nothing calls 84// at all. It sat under five thousand dead scratch files. 85// 86// A load-bearing organ with two consumers matters MORE than a dead one with 87// none, so the two classes cannot share one scale: comparing "reach" against 88// "nothing calls it" is comparing different quantities. They are now ranked 89// SEPARATELY and both printed, with the load-bearing list first, so a small 90// but live finding can never be buried under a large dead one. 91const GS_DEAD_FLOOR: i64 = 3 92 93func gw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 94func gn(v: i64) -> i64 { let b: *u8 = sys_mmap(24); var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } let t: *u8 = sys_mmap(24); 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 { b[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, b, k); return 0 } 95 96// Recursive walk: interns every .nx basename as a node and adds its imports 97// as edges. st[0] = files scanned. 98 99// Does `name` (length n) end with `suf`? Used to recognise gate files by 100// convention -- both suffixes are in live use in this tree. 101 102// Is this node itself a gate/test file rather than an organ? 103 104// Write <base><suffix> into out, where base is name minus its ".nx". 105func gs_make_gate_name(name: *u8, n: i64, suffix: *u8, out: *u8) -> i64 { 106 let stem: i64 = n - 3 107 var i: i64 = 0 108 while i < stem { out[i] = name[i]; i = i + 1 } 109 let m: i64 = gl_len(suffix) 110 var j: i64 = 0 111 while j < m { out[stem + j] = suffix[j]; j = j + 1 } 112 out[stem + m] = 0 as u8 113 return stem + m 114} 115 116// ★★THE NAMING RULE COST ME A WRONG HEADLINE, SO IT IS WORTH STATING. 117// 118// The first cut of this scan asked only whether <base>_test.nx or 119// <base>_gate.nx existed, and reported 1019 ungated load-bearing organs. 120// Spot-checking the top rows before filing showed the number was inflated: 121// nx_sha256 IS gated, by nx_sha256_kat_test.nx; nx_tls13 is gated by a dozen 122// files like nx_tls13_alpn_test.nx. This tree's real convention is 123// <organ>_<aspect>_test.nx, and a strict suffix match cannot see it. 124// 125// So a gate for organ X is any file that starts with X's stem FOLLOWED BY AN 126// UNDERSCORE and ends in _test.nx or _gate.nx. The underscore is what keeps 127// the rule honest in the other direction: without it nx_str would be claimed 128// as gated by nx_strategy_test.nx, which tests a different organ entirely. 129// 130// Attribution runs from the GATE side rather than the organ side: strip the 131// suffix, then walk the underscore split points longest-first and attribute 132// the gate to the most specific organ that exists. That is O(gates x parts) 133// with hash lookups instead of O(organs x nodes) of string comparison, and 134// longest-first means nx_foo_bar_test.nx credits nx_foo_bar when that organ 135// exists and only falls back to nx_foo when it does not. 136 137func main(argc: i64, argv: *i64) -> i64 { 138 var root: *u8 = "runtime" as *u8 139 // ★DEFAULT IS 1, NOT 0, AND THAT IS THE FIX FOR THE BURIED FINDING. 140 // With 0 the board fills with five thousand dead scratch files and a 141 // live two-consumer library ranks beneath all of them. Load-bearing is 142 // what a reader should see first; pass 0 explicitly to include the dead. 143 var min_consumers: i64 = 1 144 var topk: i64 = 40 145 if argc >= 2 { root = argv[1] as *u8 } 146 if argc >= 3 { 147 let s: *u8 = argv[2] as *u8 148 var v: i64 = 0 149 var i: i64 = 0 150 while s[i] != (0 as u8) { let d: i64 = s[i] as i64; if d >= 48 { if d <= 57 { v = v * 10 + (d - 48) } } i = i + 1 } 151 min_consumers = v 152 } 153 if argc >= 4 { 154 let s2: *u8 = argv[3] as *u8 155 var v2: i64 = 0 156 var i2: i64 = 0 157 while s2[i2] != (0 as u8) { let d2: i64 = s2[i2] as i64; if d2 >= 48 { if d2 <= 57 { v2 = v2 * 10 + (d2 - 48) } } i2 = i2 + 1 } 158 if v2 > 0 { topk = v2 } 159 } 160 161 let g: *EcoGraph = eg_new(GS_MAXNODE, GS_MAXEDGE, GS_ARENA, GS_HASH) 162 let files: *i64 = sys_mmap(16) 163 files[0] = 0 164 let path: *u8 = sys_mmap(GS_MAGIC_4096) 165 let filebuf: *u8 = sys_mmap(GS_FILECAP) 166 let aoff: *i64 = sys_mmap(GS_MAGIC_1024 * 8) 167 let alen: *i64 = sys_mmap(GS_MAGIC_1024 * 8) 168 var rn: i64 = 0 169 while root[rn] != (0 as u8) { path[rn] = root[rn]; rn = rn + 1 } 170 gl_walk(g, path, rn, filebuf, aoff, alen, files, 1, 0 as *u8, 0 as *i64, 0 as *i64, 0) 171 eg_finalize(g) 172 173 // PASS A: attribute every gate file to the most specific organ it covers. 174 let gatedmap: *u8 = sys_mmap(g.node_count + 8) 175 var z: i64 = 0 176 while z < g.node_count { gatedmap[z] = 0 as u8; z = z + 1 } 177 let nbuf: *u8 = sys_mmap(512) 178 var gi: i64 = 0 179 var gatefiles: i64 = 0 180 while gi < g.node_count { 181 let gn2: *u8 = ((g.arena as i64) + g.node_off[gi]) as *u8 182 let gl: i64 = gl_len(gn2) 183 if gl_is_gate_name(gn2, gl) == 1 { 184 gatefiles = gatefiles + 1 185 let ao: i64 = gl_attribute(g, gn2, gl, nbuf) 186 if ao >= 0 { gatedmap[ao] = 1 as u8 } 187 } 188 gi = gi + 1 189 } 190 191 gw("=== NISHI GATESCAN -- organs nothing is checking ===\n" as *u8) 192 gw("root=" as *u8); gw(root) 193 gw(" files=" as *u8); gn(files[0]) 194 gw(" nodes=" as *u8); gn(g.node_count) 195 gw(" edges=" as *u8); gn(g.edge_count) 196 gw(" gatefiles=" as *u8); gn(gatefiles) 197 gw("\n" as *u8) 198 199 // Bounded top-K, ranked by severity. Declared, never silent (scale law). 200 let bk_idx: *i64 = sys_mmap((topk + 2) * 8) 201 let bk_sev: *i64 = sys_mmap((topk + 2) * 8) 202 let bk_con: *i64 = sys_mmap((topk + 2) * 8) 203 var bn: i64 = 0 204 205 var ungated_loadbearing: i64 = 0 206 var dead_risk: i64 = 0 207 var organs: i64 = 0 208 var gated: i64 = 0 209 210 let gname: *u8 = sys_mmap(512) 211 var v: i64 = 0 212 while v < g.node_count { 213 let nm: *u8 = ((g.arena as i64) + g.node_off[v]) as *u8 214 let nlen: i64 = gl_len(nm) 215 if gl_is_gate_name(nm, nlen) == 0 { 216 organs = organs + 1 217 218 // --- HAS_GATE: exact convention, or any <organ>_<aspect>_test/gate 219 // attributed to this organ in pass A. 220 var has_gate: i64 = 0 221 if gatedmap[v] == (1 as u8) { has_gate = 1 } 222 if has_gate == 0 { 223 gs_make_gate_name(nm, nlen, "_test.nx" as *u8, gname) 224 if eg_find(g, gname, gl_len(gname)) >= 0 { has_gate = 1 } 225 } 226 if has_gate == 0 { 227 gs_make_gate_name(nm, nlen, "_gate.nx" as *u8, gname) 228 if eg_find(g, gname, gl_len(gname)) >= 0 { has_gate = 1 } 229 } 230 231 // --- CONSUMERS: importers that are not gate/test files. 232 // Counting raw Ca here would let a freshly-written gate disguise 233 // a dead organ as a used one, which is the whole failure mode. 234 var consumers: i64 = 0 235 var p: i64 = g.in_head[v] 236 let e: i64 = g.in_head[v + 1] 237 while p < e { 238 let src: i64 = g.in_list[p] 239 let sn: *u8 = ((g.arena as i64) + g.node_off[src]) as *u8 240 if gl_is_gate_name(sn, gl_len(sn)) == 0 { consumers = consumers + 1 } 241 p = p + 1 242 } 243 244 if has_gate == 1 { gated = gated + 1 } 245 if has_gate == 0 { 246 // Severity: propagation reach for the load-bearing class, a 247 // floor for the dead class. Never zero -- "nothing calls it" 248 // is a reason to distrust an organ, not to ignore it. 249 var sev: i64 = GS_DEAD_FLOOR 250 if consumers > 0 { sev = consumers; ungated_loadbearing = ungated_loadbearing + 1 } 251 if consumers == 0 { dead_risk = dead_risk + 1 } 252 // DEAD-RISK is counted but never competes with load-bearing 253 // for board space -- different quantities, separate scales. 254 if consumers > 0 { 255 if consumers >= min_consumers { 256 // Bounded top-K, descending by severity. PLACE THEN 257 // BUBBLE, never shift-then-write: the first cut of this 258 // shifted entries down and then skipped the write on the 259 // path that found the insertion point, which duplicated 260 // one row across the whole board. Swapping cannot lose 261 // or duplicate an entry by construction. 262 var start: i64 = 0 - 1 263 if bn < topk { 264 start = bn 265 bk_sev[bn] = sev; bk_idx[bn] = v; bk_con[bn] = consumers 266 bn = bn + 1 267 } 268 if bn >= topk { 269 if start < 0 { 270 if sev > bk_sev[topk - 1] { 271 start = topk - 1 272 bk_sev[start] = sev; bk_idx[start] = v; bk_con[start] = consumers 273 } 274 } 275 } 276 var q: i64 = start 277 while q > 0 { 278 if bk_sev[q - 1] < bk_sev[q] { 279 let ts: i64 = bk_sev[q - 1]; bk_sev[q - 1] = bk_sev[q]; bk_sev[q] = ts 280 let ti: i64 = bk_idx[q - 1]; bk_idx[q - 1] = bk_idx[q]; bk_idx[q] = ti 281 let tc: i64 = bk_con[q - 1]; bk_con[q - 1] = bk_con[q]; bk_con[q] = tc 282 q = q - 1 283 } else { q = 0 } 284 } 285 } 286 } 287 } 288 } 289 v = v + 1 290 } 291 292 gw("organs=" as *u8); gn(organs) 293 gw(" gated=" as *u8); gn(gated) 294 gw(" UNGATED-LOAD-BEARING=" as *u8); gn(ungated_loadbearing) 295 gw(" DEAD-RISK(no gate,no consumers)=" as *u8); gn(dead_risk) 296 gw("\n" as *u8) 297 298 var shown: i64 = bn 299 if shown > topk { shown = topk } 300 gw("-- ranked by severity (top " as *u8); gn(shown) 301 gw(" of " as *u8); gn(ungated_loadbearing) 302 gw(" ungated; bound DECLARED not silent) --\n" as *u8) 303 var r: i64 = 0 304 while r < shown { 305 let nm2: *u8 = ((g.arena as i64) + g.node_off[bk_idx[r]]) as *u8 306 gw(" sev=" as *u8); gn(bk_sev[r]) 307 if bk_con[r] == 0 { gw(" DEAD-RISK " as *u8) } else { gw(" UNGATED-LOAD-BEARING" as *u8) } 308 gw(" consumers=" as *u8); gn(bk_con[r]) 309 gw(" " as *u8); gw(nm2) 310 gw("\n" as *u8) 311 r = r + 1 312 } 313 314 // A scan finding is a HYPOTHESIS. It says nothing checks this organ; it 315 // does not say the organ is wrong. Whoever acts on a row still has to 316 // read the file -- nx_pow10 turned out to be subtly wrong and 317 // nx_arrhenius completely dead, and only reading them established that. 318 gw("\nNOTE: each row is a HYPOTHESIS (nothing checks this organ), not a defect.\n" as *u8) 319 gw(" Verify by reading before filing. verdict=" as *u8) 320 if ungated_loadbearing + dead_risk == 0 { gw("GREEN\n" as *u8); sys_exit(0); return 0 } 321 gw("FINDINGS\n" as *u8) 322 sys_exit(0) 323 return 0 324}