code wiki / _hdl_build / nx_atlas_graphrec_lib.nx

nx_atlas_graphrec_lib.nx source

↩ module page · 225 lines · 10594 B

1// nx_atlas_graphrec.nx -- NATIVE GRAPH-ADJACENCY RECOMBINATION over the eco graph (F225a). 2// 3// WHY (measured 2026-07-30, debt seq1489): nx_atlas_discover sources candidates from the 4// commontask catalog -- ~48 task rows yielding 27 classified organs -- while the eco graph 5// holds 16,580 nodes. A ~614x substrate gap, and the saturation shows in discover's OWN 6// output: 40 proposals with declared=1, trusted=7, GUESSED=20. Half of every proposal is a 7// kind INFERRED from the organ name because the real catalog pairs are exhausted. A 8// recombinator emitting 50pct guesses is not discovering, it is confabulating. 9// Corroborated externally (July 2026): agent skill-selection accuracy undergoes a PHASE 10// TRANSITION as library size grows -- routing over a FLAT catalog degrades combinatorially. 11// The fix is the SUBSTRATE, not the volume. 12// 13// THIS ORGAN DOES NOT REPLACE nx_atlas_discover. Its header rationale still holds: "the eco 14// graph sees STATIC import edges; plans compose tools at RUNTIME -- edges the graph cannot 15// see." So this is the SIBLING mechanism: discover mines RUNTIME composability from the 16// catalog, graphrec mines STRUCTURAL adjacency from the graph. Distinct ids (GRC vs DSC). 17// 18// METHOD -- classic link prediction (common neighbours) on the undirected import graph: 19// candidate (a,b) iff NO direct edge a<->b AND |N(a) INTERSECT N(b)| >= minscore. 20// A missing edge between two modules with many shared neighbours is the standard signal that 21// the edge SHOULD exist -- i.e. a real composition opportunity, DERIVED not guessed. 22// HUB SUPPRESSION: a witness whose degree exceeds GR_HUB_THR is skipped. Hubs (nx_syscalls 23// and friends) are imported by nearly everything, so they are evidence of nothing; counting 24// them would make every pair look related and rediscover the same saturation in a new costume. 25// This is also what bounds the cost: sum over witnesses of deg^2 instead of all-pairs. 26// 27// PROVENANCE: every row is source=graph. There is no name-guessing path in this organ AT ALL, 28// so its guess rate is 0 BY CONSTRUCTION -- that is the metric seq1489 says must fall. 29// Proposals stay status=proposed: promotion still requires a plan run to PROVE the combo. 30// 31// REFUSAL, NOT A REMINDER: an unloadable or empty graph EXITS 2 rather than printing 32// "0 proposals", which would read as "nothing to recombine" -- the exact lie to avoid. 33// NO SILENT CAPS: witnesses skipped as hubs and pair-table overflow are both REPORTED. 34// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 35import "nx_eco_graph.nx" 36import "nx_store_seed_lib.nx" 37import "nx_syscalls.nx" 38const GR_MAGIC_2654435761: i64 = 2654435761 39 40const GR_OUT: i64 = 262144 41const GR_HUB_THR: i64 = 24 42// ENDPOINT hub cap (added after the FIRST live run exposed the defect): suppressing hubs as 43// WITNESSES is not enough -- a hub must also be barred as a CANDIDATE ENDPOINT. nx_syscalls.nx 44// (Ca=14084) took 6 of the first top-20 slots purely because a universal import shares 45// neighbours with everything. Those rows are hub artifacts, not composition opportunities. 46// Infrastructure everyone already imports is not a recombination candidate, by definition. 47const GR_ENDPOINT_THR: i64 = 512 48const GR_NBUF: i64 = 64 49const GR_PAIRCAP: i64 = 1048576 50const GR_PAIRMASK: i64 = 1048575 51const GR_TOPN: i64 = 40 52const GR_MINSCORE: i64 = 3 53const GR_NAMEMAX: i64 = 128 54const GR_STDERR: i64 = 2 55const GR_ROWS: i64 = 65536 56// OWN PLANE BY DEFAULT, and that is a correctness decision, not a preference. sts_seed commits 57// the WHOLE plane, so seeding knowledge/store/discovery- here would CLOBBER every DSC row 58// nx_atlas_discover wrote -- the exact data-loss class nx_jrnlguard/planeguard exist to catch. 59// Two producers, two planes; a consumer can read both. Override with argv[4] if you mean to. 60const GR_OUTP_DEF: *u8 = "knowledge/store/discovery-graph-" as *u8 61 62func gr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 63 64func gr_cat(o: *u8, at: i64, s: *u8) -> i64 { 65 var a: i64 = at 66 var i: i64 = 0 67 while s[i] != (0 as u8) { o[a] = s[i]; a = a + 1; i = i + 1 } 68 return a 69} 70 71func gr_catn(o: *u8, at: i64, v: i64) -> i64 { 72 if v == 0 { o[at] = 48 as u8; return at + 1 } 73 let tmp: *u8 = sys_mmap(32) 74 var n: i64 = 0 75 var x: i64 = v 76 while x > 0 { tmp[n] = ((x % 10) + 48) as u8; x = x / 10; n = n + 1 } 77 var a: i64 = at 78 while n > 0 { n = n - 1; o[a] = tmp[n]; a = a + 1 } 79 return a 80} 81 82// node name (NUL-terminated in the graph arena, written by eg_intern) 83func gr_name(g: *EcoGraph, idx: i64) -> *u8 { 84 let base: i64 = g.arena as i64 85 return (base + g.node_off[idx]) as *u8 86} 87 88func gr_deg(g: *EcoGraph, i: i64) -> i64 { return eg_ce(g, i) + eg_ca(g, i) } 89 90// collect N(i) = out-neighbours ++ in-neighbours into buf, bounded by GR_NBUF. returns count. 91func gr_nbrs(g: *EcoGraph, i: i64, buf: *i64) -> i64 { 92 var n: i64 = 0 93 var p: i64 = g.out_head[i] 94 let pe: i64 = g.out_head[i + 1] 95 while p < pe { if n < GR_NBUF { buf[n] = g.out_list[p]; n = n + 1 } p = p + 1 } 96 var q: i64 = g.in_head[i] 97 let qe: i64 = g.in_head[i + 1] 98 while q < qe { if n < GR_NBUF { buf[n] = g.in_list[q]; n = n + 1 } q = q + 1 } 99 return n 100} 101 102// is there a DIRECT edge a->b or b->a? (already composed -> never a proposal) 103func gr_has_edge(g: *EcoGraph, a: i64, b: i64) -> i64 { 104 var p: i64 = g.out_head[a] 105 let pe: i64 = g.out_head[a + 1] 106 while p < pe { if g.out_list[p] == b { return 1 } p = p + 1 } 107 var q: i64 = g.out_head[b] 108 let qe: i64 = g.out_head[b + 1] 109 while q < qe { if g.out_list[q] == a { return 1 } q = q + 1 } 110 return 0 111} 112 113func gr_hash_key(k: i64) -> i64 { 114 var h: i64 = k 115 h = (h ^ (h >> 33)) * GR_MAGIC_2654435761 116 h = h & 0x7fffffff 117 return h & GR_PAIRMASK 118} 119 120// ---- THE SCORING CORE, extracted so a GATE can prove it -------------------------------------- 121// This logic was inline in main(), which meant the ONLY way to exercise it was to run the whole 122// organ against a real 17k-node graph -- so any gate over it would have been vacuous, and I 123// shipped the organ without one. A primitive's gate must own its proof, so the code that DECIDES 124// what gets proposed now lives in a function callable with a SYNTHETIC graph. 125// Fills ta/tb/ts (descending by score) and returns the count written. 126// stats: [0]=witnesses [1]=hubs_skipped [2]=pair_overflow [3]=considered [4]=hub_endpoints 127func gr_propose(g: *EcoGraph, minscore: i64, topn: i64, ta: *i64, tb: *i64, ts: *i64, stats: *i64) -> i64 { 128 if g.finalized != 1 { eg_finalize(g) } 129 let n: i64 = g.node_count 130 let pk: *i64 = sys_mmap(GR_PAIRCAP * 8) as *i64 131 let pc: *i64 = sys_mmap(GR_PAIRCAP * 8) as *i64 132 let nb: *i64 = sys_mmap(GR_NBUF * 8) as *i64 133 var overflow: i64 = 0 134 var hubs_skipped: i64 = 0 135 var witnesses: i64 = 0 136 var w: i64 = 0 137 while w < n { 138 let d: i64 = gr_deg(g, w) 139 if d >= 2 { 140 if d > GR_HUB_THR { hubs_skipped = hubs_skipped + 1 } else { 141 witnesses = witnesses + 1 142 let cnt: i64 = gr_nbrs(g, w, nb) 143 var i: i64 = 0 144 while i < cnt { 145 var j: i64 = i + 1 146 while j < cnt { 147 var a: i64 = nb[i] 148 var c: i64 = nb[j] 149 if a != c { 150 if a > c { let t: i64 = a; a = c; c = t } 151 let key: i64 = a * n + c 152 var h: i64 = gr_hash_key(key) 153 var probes: i64 = 0 154 var placed: i64 = 0 155 // EXIT on placement -- a probe loop that keeps spinning after it has 156 // placed runs 64x per pair over ~18M pairs, which is not a micro-cost. 157 while placed == 0 { 158 if probes >= 64 { placed = 2 } else { 159 if pc[h] == 0 { pk[h] = key; pc[h] = 1; placed = 1 } else { 160 if pk[h] == key { pc[h] = pc[h] + 1; placed = 1 } else { 161 h = (h + 1) & GR_PAIRMASK 162 probes = probes + 1 163 } 164 } 165 } 166 } 167 if placed == 2 { overflow = overflow + 1 } 168 } 169 j = j + 1 170 } 171 i = i + 1 172 } 173 } 174 } 175 w = w + 1 176 } 177 var have: i64 = 0 178 var considered: i64 = 0 179 var hub_endpoints: i64 = 0 180 var s: i64 = 0 181 while s < GR_PAIRCAP { 182 let sc: i64 = pc[s] 183 if sc >= minscore { 184 let key: i64 = pk[s] 185 let a: i64 = key / n 186 let c: i64 = key % n 187 if a != c { 188 considered = considered + 1 189 var endpoint_ok: i64 = 1 190 if gr_deg(g, a) > GR_ENDPOINT_THR { endpoint_ok = 0 } 191 if gr_deg(g, c) > GR_ENDPOINT_THR { endpoint_ok = 0 } 192 if endpoint_ok == 0 { hub_endpoints = hub_endpoints + 1 } 193 if endpoint_ok == 1 { if gr_has_edge(g, a, c) == 0 { 194 var slot: i64 = 0 - 1 195 if have < topn { slot = have; have = have + 1 } else { 196 if sc > ts[topn - 1] { slot = topn - 1 } 197 } 198 if slot >= 0 { 199 ta[slot] = a; tb[slot] = c; ts[slot] = sc 200 var k: i64 = slot 201 var go: i64 = 1 202 while go == 1 { 203 if k <= 0 { go = 0 } else { 204 if ts[k - 1] < ts[k] { 205 let xa: i64 = ta[k - 1]; let xb: i64 = tb[k - 1]; let xs: i64 = ts[k - 1] 206 ta[k - 1] = ta[k]; tb[k - 1] = tb[k]; ts[k - 1] = ts[k] 207 ta[k] = xa; tb[k] = xb; ts[k] = xs 208 k = k - 1 209 } else { go = 0 } 210 } 211 } 212 } 213 } } 214 } 215 } 216 s = s + 1 217 } 218 stats[0] = witnesses 219 stats[1] = hubs_skipped 220 stats[2] = overflow 221 stats[3] = considered 222 stats[4] = hub_endpoints 223 return have 224} 225