code wiki / _hdl_build / nx_atlas_gapscan.nx

nx_atlas_gapscan.nx source

↩ module page · 399 lines · 16438 B

1// nx_atlas_gapscan.nx -- F225/F225a STRUCTURAL GAP SCAN over the REAL from-god graph: under-connected node 2// pairs scored on the contract axes ([[project-nishi-atlas-loop-integration-2026-07-18]]: "walk the graph for 3// under-connected node pairs x maturity-gap x momentum"). Sibling of nx_atlas_discover (recombines the ~28-row 4// commontask CATALOG -> 12 mcp-live units, silently capped at 48 organs / 40 first-past-the-post proposals) and 5// of nx_atlas_recombine (dmkt PRODUCT recombination, one-best-partner-per-anchor by capability richness). 6// This organ is the MATURITY-GAP axis: it scans all candidate pairs and ranks by true top-K. 7// 8// Four defects of the catalog-mode sibling are fixed BY CONSTRUCTION here: 9// (1) SILENT CAP -> every bound is DECLARED in the envelope line (scale law: no silent truncation). 10// (2) O(n^2 x E) exclusion rescan -> per-node wing/ca/ce are HOISTED once (O(n)); the pair body is pure 11// arithmetic and the O(deg) edge probe runs ONLY for pairs that already clear minscore. 12// (3) "top proposals" that were actually FIRST-40 -> a real bounded top-K insertion, descending by score. 13// (4) toy substrate -> candidates come from the graph, islands and test scaffolding excluded. 14// 15// nx_atlas_gapscan [store-prefix] [out-prefix] [minscore] [topk] 16// defaults: knowledge/store/ecograph_full knowledge/store/gapscan- 5 40 17// out row: DSC<n> <A>-><B> <score> proposed atlas-graph <evidence> try: plan 10 <A> then 20 <B> 18// 19// NOVELTY = absence of an import edge in EITHER direction (the graph's own ground truth), so an 20// already-composed pair can never be proposed -- the F225 neg-control holds structurally, not by a filter. 21// HUB EXCLUSION (measured, not assumed): the first live run over 16651 nodes returned a top-K saturated by 22// nx_syscalls.nx (Ca=13539) -- a leaf imported by ~80% of the tree carries NO under-connection signal, so 23// pairing it with anything is noise, not novelty. Nodes at or above AR_HUB_CA are therefore not candidates, 24// and an ANCHOR CAP bounds how many emitted rows may share one node so a single anchor cannot crowd the set. 25// Both bounds are reported in the envelope. 26// Wing identity is an FNV hash of the name prefix through the 2nd underscore; a hash collision can only COST 27// a cross-wing bonus (never invent one), so the failure mode is a missed proposal, never a false one. 28// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 29import "nx_eco_graph.nx" 30import "nx_store_seed_lib.nx" 31import "nx_seg_store.nx" 32import "nx_syscalls.nx" 33 34const AR_OUTCAP: i64 = 1048576 35const AR_MAXCAND: i64 = 8192 36const AR_TOPK_MAX: i64 = 512 37const AR_TOPK_DEF: i64 = 40 38const AR_MINSCORE_DEF: i64 = 5 39const AR_HASHMOD: i64 = 1000003 40const AR_MSGCAP: i64 = 640 41const AR_NL: i64 = 10 42const AR_TAB: i64 = 9 43const AR_STDOUT: i64 = 1 44const AR_STDERR: i64 = 2 45const AR_EXIT_IO: i64 = 1 46const AR_ZERO: i64 = 48 47const AR_NINE: i64 = 57 48const AR_B10: i64 = 10 49const AR_USCORE: i64 = 95 50const AR_OVER: i64 = 4 51// scoring weights -- echoed in the envelope line so every emitted score is auditable arithmetic 52const AR_W_CROSSWING: i64 = 3 53const AR_W_MOMENTUM: i64 = 2 54const AR_W_MATGAP: i64 = 2 55const AR_W_BRIDGE: i64 = 1 56// thresholds 57const AR_MOMENTUM_CA: i64 = 2 58const AR_MATURE_CA: i64 = 5 59const AR_UNDER_CA: i64 = 1 60const AR_CAND_CE: i64 = 2 61const AR_HUB_CA: i64 = 500 62const AR_MAX_ANCHOR: i64 = 2 63// SHARED SUBSTRATE: measured on the live graph, the four axes above max out at 6 and 8.9M pairs tie there -- 64// too little dynamic range to RANK anything. Common out-neighbours (both import X, but not each other) is the 65// classic link-prediction signal and the discriminator this scan was missing: it means "adjacent problems, 66// same substrate, never composed". Capped so a mega-import organ cannot buy rank. 67const AR_W_SHARED: i64 = 1 68const AR_SHARED_CAP: i64 = 8 69 70func ar_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 71func ar_werr(s: *u8) -> i64 { sys_write(AR_STDERR, s, ar_slen(s)); return 0 } 72func ar_atoi(s: *u8) -> i64 { 73 var v: i64 = 0 74 var i: i64 = 0 75 while s[i] != (0 as u8) { 76 let c: i64 = s[i] as i64 77 if c >= AR_ZERO { if c <= AR_NINE { v = v * AR_B10 + (c - AR_ZERO) } } 78 i = i + 1 79 } 80 return v 81} 82// ---- node-name helpers over the interned arena ---- 83func ar_nlen(g: *EcoGraph, idx: i64) -> i64 { 84 let off: i64 = g.node_off[idx] 85 var i: i64 = 0 86 while g.arena[off+i] != (0 as u8) { i = i + 1 } 87 return i 88} 89func ar_name_has(g: *EcoGraph, idx: i64, s: *u8) -> i64 { 90 let sn: i64 = ar_slen(s) 91 if sn == 0 { return 1 } 92 let off: i64 = g.node_off[idx] 93 let n: i64 = ar_nlen(g, idx) 94 var i: i64 = 0 95 while i + sn <= n { 96 var hit: i64 = 1 97 var j: i64 = 0 98 while j < sn { if g.arena[off+i+j] != s[j] { hit = 0; j = sn } else { j = j + 1 } } 99 if hit == 1 { return 1 } 100 i = i + 1 101 } 102 return 0 103} 104func ar_catname(g: *EcoGraph, idx: i64, d: *u8, o: i64) -> i64 { 105 let off: i64 = g.node_off[idx] 106 var oo: i64 = o 107 var i: i64 = 0 108 while g.arena[off+i] != (0 as u8) { d[oo] = g.arena[off+i]; oo = oo + 1; i = i + 1 } 109 return oo 110} 111// wing = name prefix through the 2nd underscore (nx_tls12_prf.nx -> "nx_tls12") 112func ar_wing_len(g: *EcoGraph, idx: i64) -> i64 { 113 let off: i64 = g.node_off[idx] 114 let n: i64 = ar_nlen(g, idx) 115 var u: i64 = 0 116 var r: i64 = n 117 var i: i64 = 0 118 while i < n { 119 if g.arena[off+i] == (AR_USCORE as u8) { u = u + 1; if u == 2 { r = i; i = n } } 120 i = i + 1 121 } 122 return r 123} 124// direct import edge a->b? O(out-degree) via the CSR adjacency built by eg_finalize/eg_load. 125func ar_has_edge(g: *EcoGraph, a: i64, b: i64) -> i64 { 126 var p: i64 = g.out_head[a] 127 let e: i64 = g.out_head[a+1] 128 var f: i64 = 0 129 while p < e { 130 if g.out_list[p] == b { f = 1; p = e } else { p = p + 1 } 131 } 132 return f 133} 134// |out(a) INTERSECT out(b)| -- shared substrate. Out-degrees are small (mean ~2.5 over the live graph), so 135// this stays cheap even though it runs for every pair that clears the base score. 136func ar_shared(g: *EcoGraph, a: i64, b: i64) -> i64 { 137 var c: i64 = 0 138 var p: i64 = g.out_head[a] 139 let ea: i64 = g.out_head[a+1] 140 let qb: i64 = g.out_head[b] 141 let eb: i64 = g.out_head[b+1] 142 while p < ea { 143 let d: i64 = g.out_list[p] 144 var q: i64 = qb 145 while q < eb { 146 if g.out_list[q] == d { c = c + 1; q = eb } else { q = q + 1 } 147 } 148 p = p + 1 149 } 150 return c 151} 152// candidate = load-bearing, non-island, non-scaffolding, non-ubiquitous-infrastructure 153func ar_is_cand(g: *EcoGraph, i: i64) -> i64 { 154 let ca: i64 = g.ca_arr[i] 155 let ce: i64 = g.ce_arr[i] 156 if ca == 0 { if ce == 0 { return 0 } } 157 if ca >= AR_HUB_CA { return 0 } 158 if ar_name_has(g, i, "_gate" as *u8) == 1 { return 0 } 159 if ar_name_has(g, i, "_kat" as *u8) == 1 { return 0 } 160 if ar_name_has(g, i, "_test" as *u8) == 1 { return 0 } 161 if ca >= 1 { return 1 } 162 if ce >= AR_CAND_CE { return 1 } 163 return 0 164} 165// ---- PURE scoring core (no graph handle -> directly gate-testable; the O(n^2) inner body) ---- 166func ar_score(wha: i64, whb: i64, caa: i64, cab: i64, cea: i64, ceb: i64) -> i64 { 167 var sc: i64 = 0 168 if wha != whb { sc = sc + AR_W_CROSSWING } 169 if caa >= AR_MOMENTUM_CA { if cab >= AR_MOMENTUM_CA { sc = sc + AR_W_MOMENTUM } } 170 var hi: i64 = caa 171 var lo: i64 = cab 172 if cab > caa { hi = cab; lo = caa } 173 if hi >= AR_MATURE_CA { if lo <= AR_UNDER_CA { sc = sc + AR_W_MATGAP } } 174 if cea >= 1 { if ceb >= 1 { sc = sc + AR_W_BRIDGE } } 175 return sc 176} 177// ---- PURE bounded top-K, descending by score (replaces first-past-the-post) ---- 178func ar_topk_ins(ta: *i64, tb: *i64, tsc: *i64, tn: *i64, k: i64, a: i64, b: i64, sc: i64) -> i64 { 179 var n: i64 = tn[0] 180 if n >= k { 181 if sc <= tsc[k-1] { return 0 } 182 n = k - 1 183 } 184 var pos: i64 = n 185 var go: i64 = 1 186 while go == 1 { 187 if pos == 0 { go = 0 } else { 188 if tsc[pos-1] < sc { 189 ta[pos] = ta[pos-1] 190 tb[pos] = tb[pos-1] 191 tsc[pos] = tsc[pos-1] 192 pos = pos - 1 193 } else { go = 0 } 194 } 195 } 196 ta[pos] = a 197 tb[pos] = b 198 tsc[pos] = sc 199 if tn[0] < k { tn[0] = tn[0] + 1 } 200 return 1 201} 202 203func main(argc: i64, argv: *i64) -> i64 { 204 var stp: *u8 = "knowledge/store/ecograph_full" as *u8 205 var outp: *u8 = "knowledge/store/gapscan-" as *u8 206 var minsc: i64 = AR_MINSCORE_DEF 207 var topk: i64 = AR_TOPK_DEF 208 if argc > 1 { stp = argv[1] as *u8 } 209 if argc > 2 { outp = argv[2] as *u8 } 210 if argc > 3 { minsc = ar_atoi(argv[3] as *u8) } 211 if argc > 4 { topk = ar_atoi(argv[4] as *u8) } 212 if topk < 1 { topk = AR_TOPK_DEF } 213 if topk > AR_TOPK_MAX { topk = AR_TOPK_MAX } 214 215 let g: *EcoGraph = eg_load(stp) 216 if (g as i64) == 0 { ar_werr("graph store not found / unseeded (fail-closed)\n" as *u8); sys_exit(AR_EXIT_IO); return AR_EXIT_IO } 217 let n: i64 = g.node_count 218 if n <= 0 { ar_werr("graph EMPTY (fail-closed)\n" as *u8); sys_exit(AR_EXIT_IO); return AR_EXIT_IO } 219 220 // ---- candidate selection (O(n)) ---- 221 let cand: *i64 = sys_mmap(AR_MAXCAND*8) as *i64 222 var nc: i64 = 0 223 var trunc: i64 = 0 224 var islands: i64 = 0 225 var hubs: i64 = 0 226 var i: i64 = 0 227 while i < n { 228 let ca0: i64 = g.ca_arr[i] 229 let ce0: i64 = g.ce_arr[i] 230 if ca0 == 0 { if ce0 == 0 { islands = islands + 1 } } 231 if ca0 >= AR_HUB_CA { hubs = hubs + 1 } 232 if ar_is_cand(g, i) == 1 { 233 if nc < AR_MAXCAND { cand[nc] = i; nc = nc + 1 } else { trunc = 1 } 234 } 235 i = i + 1 236 } 237 if nc < 2 { ar_werr("fewer than 2 candidates in graph (fail-closed)\n" as *u8); sys_exit(AR_EXIT_IO); return AR_EXIT_IO } 238 239 // ---- HOIST per-candidate wing hash + degrees ONCE (the O(n^2 x E) fix) ---- 240 let wh: *i64 = sys_mmap(AR_MAXCAND*8) as *i64 241 let cca: *i64 = sys_mmap(AR_MAXCAND*8) as *i64 242 let cce: *i64 = sys_mmap(AR_MAXCAND*8) as *i64 243 var k: i64 = 0 244 while k < nc { 245 let idx: i64 = cand[k] 246 let off: i64 = g.node_off[idx] 247 let wl: i64 = ar_wing_len(g, idx) 248 let wp: *u8 = ((g.arena as i64) + off) as *u8 249 wh[k] = eg_hash(wp, wl, AR_HASHMOD) 250 cca[k] = g.ca_arr[idx] 251 cce[k] = g.ce_arr[idx] 252 k = k + 1 253 } 254 255 // ---- unordered pair sweep: cheap arithmetic first, O(deg) edge probe only above minscore ---- 256 // internal K is oversampled so the anchor-diversity filter below still yields ~topk rows 257 var ik: i64 = topk * AR_OVER 258 if ik > AR_TOPK_MAX { ik = AR_TOPK_MAX } 259 let ta: *i64 = sys_mmap(AR_TOPK_MAX*8) as *i64 260 let tb: *i64 = sys_mmap(AR_TOPK_MAX*8) as *i64 261 let tsc: *i64 = sys_mmap(AR_TOPK_MAX*8) as *i64 262 let tn: *i64 = sys_mmap(8) as *i64 263 tn[0] = 0 264 var pairs: i64 = 0 265 var scored: i64 = 0 266 var skipconn: i64 = 0 267 var ai: i64 = 0 268 while ai < nc { 269 var bi: i64 = ai + 1 270 while bi < nc { 271 pairs = pairs + 1 272 let sc: i64 = ar_score(wh[ai], wh[bi], cca[ai], cca[bi], cce[ai], cce[bi]) 273 if sc >= minsc { 274 scored = scored + 1 275 let na: i64 = cand[ai] 276 let nb: i64 = cand[bi] 277 var conn: i64 = 0 278 if ar_has_edge(g, na, nb) == 1 { conn = 1 } 279 if ar_has_edge(g, nb, na) == 1 { conn = 1 } 280 if conn == 1 { skipconn = skipconn + 1 } else { 281 var sh: i64 = ar_shared(g, na, nb) 282 if sh > AR_SHARED_CAP { sh = AR_SHARED_CAP } 283 let fsc: i64 = sc + sh * AR_W_SHARED 284 ar_topk_ins(ta, tb, tsc, tn, ik, na, nb, fsc) 285 } 286 } 287 bi = bi + 1 288 } 289 ai = ai + 1 290 } 291 let nsel: i64 = tn[0] 292 if nsel == 0 { ar_werr("no unconnected pair above minscore (graph saturated at this threshold)\n" as *u8); sys_exit(0); return 0 } 293 294 // ---- emit with ANCHOR-DIVERSITY cap so one node cannot crowd the proposal set ---- 295 let acnt: *u8 = sys_mmap(n + 2) 296 var z: i64 = 0 297 while z < n { acnt[z] = 0 as u8; z = z + 1 } 298 let out: *u8 = sys_mmap(AR_OUTCAP) 299 var oo: i64 = 0 300 var emitted: i64 = 0 301 var dropdiv: i64 = 0 302 var r: i64 = 0 303 while r < nsel { 304 if emitted < topk { 305 let na: i64 = ta[r] 306 let nb: i64 = tb[r] 307 var okd: i64 = 1 308 if (acnt[na] as i64) >= AR_MAX_ANCHOR { okd = 0 } 309 if (acnt[nb] as i64) >= AR_MAX_ANCHOR { okd = 0 } 310 if okd == 0 { dropdiv = dropdiv + 1 } else { 311 let pa: i64 = (acnt[na] as i64) + 1 312 let pb: i64 = (acnt[nb] as i64) + 1 313 acnt[na] = pa as u8 314 acnt[nb] = pb as u8 315 oo = ss_cat(out, oo, "DSC" as *u8) 316 oo = ss_catn(out, oo, emitted) 317 out[oo] = AR_TAB as u8 318 oo = oo + 1 319 oo = ar_catname(g, na, out, oo) 320 oo = ss_cat(out, oo, "->" as *u8) 321 oo = ar_catname(g, nb, out, oo) 322 out[oo] = AR_TAB as u8 323 oo = oo + 1 324 oo = ss_catn(out, oo, tsc[r]) 325 oo = ss_cat(out, oo, "\x09proposed\x09atlas-graph\x09ca=" as *u8) 326 oo = ss_catn(out, oo, g.ca_arr[na]) 327 oo = ss_cat(out, oo, "/" as *u8) 328 oo = ss_catn(out, oo, g.ca_arr[nb]) 329 oo = ss_cat(out, oo, " ce=" as *u8) 330 oo = ss_catn(out, oo, g.ce_arr[na]) 331 oo = ss_cat(out, oo, "/" as *u8) 332 oo = ss_catn(out, oo, g.ce_arr[nb]) 333 oo = ss_cat(out, oo, " shared=" as *u8) 334 oo = ss_catn(out, oo, ar_shared(g, na, nb)) 335 oo = ss_cat(out, oo, " unconnected-both-ways\x09try: plan 10 " as *u8) 336 oo = ar_catname(g, na, out, oo) 337 oo = ss_cat(out, oo, " then 20 " as *u8) 338 oo = ar_catname(g, nb, out, oo) 339 out[oo] = AR_NL as u8 340 oo = oo + 1 341 emitted = emitted + 1 342 } 343 } 344 r = r + 1 345 } 346 if sts_seed(outp, out, oo) < 0 { ar_werr("discovery plane commit error\n" as *u8); sys_exit(AR_EXIT_IO); return AR_EXIT_IO } 347 348 // ---- DECLARED ENVELOPE (scale law: the tool states its own bounds; silent capping is forbidden) ---- 349 let msg: *u8 = sys_mmap(AR_MSGCAP) 350 var mo: i64 = ss_cat(msg, 0, "GAPSCAN proposals=" as *u8) 351 mo = ss_catn(msg, mo, emitted) 352 mo = ss_cat(msg, mo, " nodes=" as *u8) 353 mo = ss_catn(msg, mo, n) 354 mo = ss_cat(msg, mo, " islands=" as *u8) 355 mo = ss_catn(msg, mo, islands) 356 mo = ss_cat(msg, mo, " hubs_excluded=" as *u8) 357 mo = ss_catn(msg, mo, hubs) 358 mo = ss_cat(msg, mo, " candidates=" as *u8) 359 mo = ss_catn(msg, mo, nc) 360 mo = ss_cat(msg, mo, "/" as *u8) 361 mo = ss_catn(msg, mo, AR_MAXCAND) 362 mo = ss_cat(msg, mo, " truncated=" as *u8) 363 mo = ss_catn(msg, mo, trunc) 364 mo = ss_cat(msg, mo, " pairs=" as *u8) 365 mo = ss_catn(msg, mo, pairs) 366 mo = ss_cat(msg, mo, " above_minscore=" as *u8) 367 mo = ss_catn(msg, mo, scored) 368 mo = ss_cat(msg, mo, " skipped_connected=" as *u8) 369 mo = ss_catn(msg, mo, skipconn) 370 mo = ss_cat(msg, mo, " selected=" as *u8) 371 mo = ss_catn(msg, mo, nsel) 372 mo = ss_cat(msg, mo, " dropped_anchor_cap=" as *u8) 373 mo = ss_catn(msg, mo, dropdiv) 374 mo = ss_cat(msg, mo, " topk=" as *u8) 375 mo = ss_catn(msg, mo, topk) 376 mo = ss_cat(msg, mo, " minscore=" as *u8) 377 mo = ss_catn(msg, mo, minsc) 378 mo = ss_cat(msg, mo, " hub_ca_cut=" as *u8) 379 mo = ss_catn(msg, mo, AR_HUB_CA) 380 mo = ss_cat(msg, mo, " anchor_cap=" as *u8) 381 mo = ss_catn(msg, mo, AR_MAX_ANCHOR) 382 mo = ss_cat(msg, mo, " weights=crosswing" as *u8) 383 mo = ss_catn(msg, mo, AR_W_CROSSWING) 384 mo = ss_cat(msg, mo, ",momentum" as *u8) 385 mo = ss_catn(msg, mo, AR_W_MOMENTUM) 386 mo = ss_cat(msg, mo, ",matgap" as *u8) 387 mo = ss_catn(msg, mo, AR_W_MATGAP) 388 mo = ss_cat(msg, mo, ",bridge" as *u8) 389 mo = ss_catn(msg, mo, AR_W_BRIDGE) 390 mo = ss_cat(msg, mo, ",shared" as *u8) 391 mo = ss_catn(msg, mo, AR_W_SHARED) 392 mo = ss_cat(msg, mo, "x" as *u8) 393 mo = ss_catn(msg, mo, AR_SHARED_CAP) 394 msg[mo] = AR_NL as u8 395 mo = mo + 1 396 sys_write(AR_STDOUT, msg, mo) 397 sys_exit(0) 398 return 0 399}