code wiki / _hdl_build / nx_atlas_graphrec.nx
nx_atlas_graphrec.nx source
↩ module page · 341 lines · 16328 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
39const GR_MAGIC_1024: i64 = 1024
40
41const GR_OUT: i64 = 262144
42const GR_HUB_THR: i64 = 24
43// ENDPOINT hub cap (added after the FIRST live run exposed the defect): suppressing hubs as
44// WITNESSES is not enough -- a hub must also be barred as a CANDIDATE ENDPOINT. nx_syscalls.nx
45// (Ca=14084) took 6 of the first top-20 slots purely because a universal import shares
46// neighbours with everything. Those rows are hub artifacts, not composition opportunities.
47// Infrastructure everyone already imports is not a recombination candidate, by definition.
48const GR_ENDPOINT_THR: i64 = 512
49const GR_NBUF: i64 = 64
50const GR_PAIRCAP: i64 = 1048576
51const GR_PAIRMASK: i64 = 1048575
52const GR_TOPN: i64 = 40
53const GR_MINSCORE: i64 = 3
54const GR_NAMEMAX: i64 = 128
55const GR_STDERR: i64 = 2
56const GR_ROWS: i64 = 65536
57// OWN PLANE BY DEFAULT, and that is a correctness decision, not a preference. sts_seed commits
58// the WHOLE plane, so seeding knowledge/store/discovery- here would CLOBBER every DSC row
59// nx_atlas_discover wrote -- the exact data-loss class nx_jrnlguard/planeguard exist to catch.
60// Two producers, two planes; a consumer can read both. Override with argv[4] if you mean to.
61const GR_OUTP_DEF: *u8 = "knowledge/store/discovery-graph-" as *u8
62
63func gr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
64
65func gr_cat(o: *u8, at: i64, s: *u8) -> i64 {
66 var a: i64 = at
67 var i: i64 = 0
68 while s[i] != (0 as u8) { o[a] = s[i]; a = a + 1; i = i + 1 }
69 return a
70}
71
72func gr_catn(o: *u8, at: i64, v: i64) -> i64 {
73 if v == 0 { o[at] = 48 as u8; return at + 1 }
74 let tmp: *u8 = sys_mmap(32)
75 var n: i64 = 0
76 var x: i64 = v
77 while x > 0 { tmp[n] = ((x % 10) + 48) as u8; x = x / 10; n = n + 1 }
78 var a: i64 = at
79 while n > 0 { n = n - 1; o[a] = tmp[n]; a = a + 1 }
80 return a
81}
82
83// node name (NUL-terminated in the graph arena, written by eg_intern)
84func gr_name(g: *EcoGraph, idx: i64) -> *u8 {
85 let base: i64 = g.arena as i64
86 return (base + g.node_off[idx]) as *u8
87}
88
89func gr_deg(g: *EcoGraph, i: i64) -> i64 { return eg_ce(g, i) + eg_ca(g, i) }
90
91// collect N(i) = out-neighbours ++ in-neighbours into buf, bounded by GR_NBUF. returns count.
92func gr_nbrs(g: *EcoGraph, i: i64, buf: *i64) -> i64 {
93 var n: i64 = 0
94 var p: i64 = g.out_head[i]
95 let pe: i64 = g.out_head[i + 1]
96 while p < pe { if n < GR_NBUF { buf[n] = g.out_list[p]; n = n + 1 } p = p + 1 }
97 var q: i64 = g.in_head[i]
98 let qe: i64 = g.in_head[i + 1]
99 while q < qe { if n < GR_NBUF { buf[n] = g.in_list[q]; n = n + 1 } q = q + 1 }
100 return n
101}
102
103// is there a DIRECT edge a->b or b->a? (already composed -> never a proposal)
104func gr_has_edge(g: *EcoGraph, a: i64, b: i64) -> i64 {
105 var p: i64 = g.out_head[a]
106 let pe: i64 = g.out_head[a + 1]
107 while p < pe { if g.out_list[p] == b { return 1 } p = p + 1 }
108 var q: i64 = g.out_head[b]
109 let qe: i64 = g.out_head[b + 1]
110 while q < qe { if g.out_list[q] == a { return 1 } q = q + 1 }
111 return 0
112}
113
114func gr_hash_key(k: i64) -> i64 {
115 var h: i64 = k
116 h = (h ^ (h >> 33)) * GR_MAGIC_2654435761
117 h = h & 0x7fffffff
118 return h & GR_PAIRMASK
119}
120
121func main(argc: i64, argv: *i64) -> i64 {
122 let o: *u8 = sys_mmap(GR_OUT)
123 var b: i64 = 0
124 if argc < 2 {
125 b = gr_cat(o, b, "usage: nx_atlas_graphrec <graph-store-prefix> [minscore] [topn]\n link prediction over the eco graph: proposes UNLINKED pairs with many shared neighbours.\n" as *u8)
126 sys_write(1, o, b)
127 return 1
128 }
129 let prefix: *u8 = argv[1] as *u8
130 var minscore: i64 = GR_MINSCORE
131 if argc > 2 {
132 let ms: *u8 = argv[2] as *u8
133 var v: i64 = 0
134 var i: i64 = 0
135 while ms[i] != (0 as u8) { let c: i64 = ms[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 }
136 if v > 0 { minscore = v }
137 }
138 var topn: i64 = GR_TOPN
139 if argc > 3 {
140 let tn: *u8 = argv[3] as *u8
141 var v2: i64 = 0
142 var j: i64 = 0
143 while tn[j] != (0 as u8) { let c: i64 = tn[j] as i64; if c >= 48 { if c <= 57 { v2 = v2 * 10 + (c - 48) } } j = j + 1 }
144 if v2 > 0 { if v2 <= GR_TOPN { topn = v2 } }
145 }
146
147 var outp: *u8 = GR_OUTP_DEF
148 if argc > 4 { outp = argv[4] as *u8 }
149
150 let g: *EcoGraph = eg_load(prefix)
151 if (g as i64) == 0 {
152 b = gr_cat(o, b, "GRAPHREC REFUSED -- eco graph would not load from prefix: " as *u8)
153 b = gr_cat(o, b, prefix)
154 b = gr_cat(o, b, "\n Refusing rather than printing '0 proposals', which reads as 'nothing to recombine'.\n Build/save one first: nx_eco_graph_build <root> <query> <save-prefix>\n" as *u8)
155 sys_write(GR_STDERR, o, b)
156 return 2
157 }
158 let n: i64 = g.node_count
159 if n < 2 {
160 b = gr_cat(o, b, "GRAPHREC REFUSED -- graph has fewer than 2 nodes (node_count=" as *u8)
161 b = gr_catn(o, b, n)
162 b = gr_cat(o, b, ")\n" as *u8)
163 sys_write(GR_STDERR, o, b)
164 return 2
165 }
166 if g.finalized != 1 { eg_finalize(g) }
167
168 // ---- accumulate common-neighbour counts, hub-suppressed ------------------------------
169 let pk: *i64 = sys_mmap(GR_PAIRCAP * 8) as *i64
170 let pc: *i64 = sys_mmap(GR_PAIRCAP * 8) as *i64
171 let nb: *i64 = sys_mmap(GR_NBUF * 8) as *i64
172 var overflow: i64 = 0
173 var hubs_skipped: i64 = 0
174 var witnesses: i64 = 0
175 var w: i64 = 0
176 while w < n {
177 let d: i64 = gr_deg(g, w)
178 if d >= 2 {
179 if d > GR_HUB_THR { hubs_skipped = hubs_skipped + 1 } else {
180 witnesses = witnesses + 1
181 let cnt: i64 = gr_nbrs(g, w, nb)
182 var i: i64 = 0
183 while i < cnt {
184 var j: i64 = i + 1
185 while j < cnt {
186 var a: i64 = nb[i]
187 var c: i64 = nb[j]
188 if a != c {
189 if a > c { let t: i64 = a; a = c; c = t }
190 let key: i64 = a * n + c
191 var h: i64 = gr_hash_key(key)
192 var probes: i64 = 0
193 var placed: i64 = 0
194 // EXIT on placement -- a probe loop that keeps spinning after it has
195 // placed runs 64x per pair over ~18M pairs, which is not a micro-cost.
196 while placed == 0 {
197 if probes >= 64 { placed = 2 } else {
198 if pc[h] == 0 { pk[h] = key; pc[h] = 1; placed = 1 } else {
199 if pk[h] == key { pc[h] = pc[h] + 1; placed = 1 } else {
200 h = (h + 1) & GR_PAIRMASK
201 probes = probes + 1
202 }
203 }
204 }
205 }
206 if placed == 2 { overflow = overflow + 1 }
207 }
208 j = j + 1
209 }
210 i = i + 1
211 }
212 }
213 }
214 w = w + 1
215 }
216
217 // ---- select the top-N unlinked pairs -------------------------------------------------
218 let ta: *i64 = sys_mmap((GR_TOPN + 2) * 8) as *i64
219 let tb: *i64 = sys_mmap((GR_TOPN + 2) * 8) as *i64
220 let ts: *i64 = sys_mmap((GR_TOPN + 2) * 8) as *i64
221 var have: i64 = 0
222 var considered: i64 = 0
223 var hub_endpoints: i64 = 0
224 var s: i64 = 0
225 while s < GR_PAIRCAP {
226 let sc: i64 = pc[s]
227 if sc >= minscore {
228 let key: i64 = pk[s]
229 let a: i64 = key / n
230 let c: i64 = key % n
231 if a != c {
232 considered = considered + 1
233 var da: i64 = gr_deg(g, a)
234 var dc: i64 = gr_deg(g, c)
235 var endpoint_ok: i64 = 1
236 if da > GR_ENDPOINT_THR { endpoint_ok = 0 }
237 if dc > GR_ENDPOINT_THR { endpoint_ok = 0 }
238 if endpoint_ok == 0 { hub_endpoints = hub_endpoints + 1 }
239 if endpoint_ok == 1 { if gr_has_edge(g, a, c) == 0 {
240 // insertion into a small DESCENDING top-N: append (or displace the weakest),
241 // then bubble the new row up while it outranks its predecessor.
242 var slot: i64 = 0 - 1
243 if have < topn {
244 slot = have
245 have = have + 1
246 } else {
247 if sc > ts[topn - 1] { slot = topn - 1 }
248 }
249 if slot >= 0 {
250 ta[slot] = a; tb[slot] = c; ts[slot] = sc
251 var k: i64 = slot
252 var go: i64 = 1
253 while go == 1 {
254 if k <= 0 { go = 0 } else {
255 if ts[k - 1] < ts[k] {
256 let xa: i64 = ta[k - 1]; let xb: i64 = tb[k - 1]; let xs: i64 = ts[k - 1]
257 ta[k - 1] = ta[k]; tb[k - 1] = tb[k]; ts[k - 1] = ts[k]
258 ta[k] = xa; tb[k] = xb; ts[k] = xs
259 k = k - 1
260 } else { go = 0 }
261 }
262 }
263 }
264 } }
265 }
266 }
267 s = s + 1
268 }
269
270 b = gr_cat(o, b, "=== ATLAS GRAPHREC -- native graph-adjacency recombination (F225a) ===\n graph: " as *u8)
271 b = gr_cat(o, b, prefix)
272 b = gr_cat(o, b, "\n nodes=" as *u8); b = gr_catn(o, b, n)
273 b = gr_cat(o, b, " edges=" as *u8); b = gr_catn(o, b, g.edge_count)
274 b = gr_cat(o, b, " witnesses=" as *u8); b = gr_catn(o, b, witnesses)
275 b = gr_cat(o, b, " hubs_skipped=" as *u8); b = gr_catn(o, b, hubs_skipped)
276 b = gr_cat(o, b, "\n minscore=" as *u8); b = gr_catn(o, b, minscore)
277 b = gr_cat(o, b, " candidates_considered=" as *u8); b = gr_catn(o, b, considered)
278 b = gr_cat(o, b, " emitted=" as *u8); b = gr_catn(o, b, have)
279 // NO SILENT CAPS
280 b = gr_cat(o, b, " hub_endpoints_suppressed=" as *u8); b = gr_catn(o, b, hub_endpoints)
281 b = gr_cat(o, b, "\n bounds: pair_table_overflow=" as *u8); b = gr_catn(o, b, overflow)
282 b = gr_cat(o, b, " witness_hub_thr=" as *u8); b = gr_catn(o, b, GR_HUB_THR)
283 b = gr_cat(o, b, " endpoint_hub_thr=" as *u8); b = gr_catn(o, b, GR_ENDPOINT_THR)
284 b = gr_cat(o, b, " nbr_buf=" as *u8); b = gr_catn(o, b, GR_NBUF)
285 b = gr_cat(o, b, "\n provenance: source=graph for EVERY row -- this organ has no name-guessing path,\n so its guess rate is 0 BY CONSTRUCTION (cf. nx_atlas_discover 20 guessed / 40).\n\n" as *u8)
286
287 // Rows are built in the SAME pass that prints them, so the plane and stdout can never
288 // disagree about what was proposed.
289 let rows: *u8 = sys_mmap(GR_ROWS)
290 var ro: i64 = 0
291 var emitted_rows: i64 = 0
292 var r: i64 = 0
293 while r < have {
294 if ts[r] > 0 {
295 let na: *u8 = gr_name(g, ta[r])
296 let nbn: *u8 = gr_name(g, tb[r])
297 b = gr_cat(o, b, " GRC" as *u8); b = gr_catn(o, b, r + 1)
298 b = gr_cat(o, b, " " as *u8)
299 b = gr_cat(o, b, na)
300 b = gr_cat(o, b, "->" as *u8)
301 b = gr_cat(o, b, nbn)
302 b = gr_cat(o, b, " cn=" as *u8); b = gr_catn(o, b, ts[r])
303 b = gr_cat(o, b, " proposed graph unlinked-shared-neighbours\n" as *u8)
304 // plane row: GRC<n> <A>-><B> <cn> proposed graph <evidence> <try-hint>
305 if ro < GR_ROWS - GR_MAGIC_1024 {
306 ro = gr_cat(rows, ro, "GRC" as *u8); ro = gr_catn(rows, ro, r + 1)
307 ro = gr_cat(rows, ro, "\x09" as *u8)
308 ro = gr_cat(rows, ro, na); ro = gr_cat(rows, ro, "->" as *u8); ro = gr_cat(rows, ro, nbn)
309 ro = gr_cat(rows, ro, "\x09" as *u8); ro = gr_catn(rows, ro, ts[r])
310 ro = gr_cat(rows, ro, "\x09proposed\x09graph\x09unlinked-shared-neighbours cn=" as *u8)
311 ro = gr_catn(rows, ro, ts[r])
312 ro = gr_cat(rows, ro, "\x09try: plan 10 " as *u8); ro = gr_cat(rows, ro, na)
313 ro = gr_cat(rows, ro, " / 20 " as *u8); ro = gr_cat(rows, ro, nbn)
314 ro = gr_cat(rows, ro, "\n" as *u8)
315 emitted_rows = emitted_rows + 1
316 }
317 }
318 r = r + 1
319 }
320 // COMMIT: never write an EMPTY plane -- an empty discovery plane reads as "no opportunities
321 // exist", which is a different claim from "this run found none above threshold".
322 if emitted_rows > 0 {
323 if sts_seed(outp, rows, ro) < 0 {
324 b = gr_cat(o, b, "\n PLANE COMMIT FAILED: " as *u8)
325 b = gr_cat(o, b, outp)
326 b = gr_cat(o, b, "\n" as *u8)
327 sys_write(1, o, b)
328 return 1
329 }
330 b = gr_cat(o, b, "\n COMMITTED " as *u8); b = gr_catn(o, b, emitted_rows)
331 b = gr_cat(o, b, " rows -> " as *u8); b = gr_cat(o, b, outp)
332 b = gr_cat(o, b, " (status=proposed; promotion still requires a plan run to PROVE the combo)\n" as *u8)
333 } else {
334 b = gr_cat(o, b, "\n NO rows committed -- plane left untouched (this run found none above threshold,\n which is NOT the same claim as 'no opportunities exist').\n" as *u8)
335 }
336 if have == 0 {
337 b = gr_cat(o, b, " (no unlinked pair reached minscore -- raise topn/lower minscore, or the graph is saturated)\n" as *u8)
338 }
339 sys_write(1, o, b)
340 return 0
341}