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}