code wiki / _hdl_build / nx_capgraph_lib.nx
nx_capgraph_lib.nx source
↩ module page · 440 lines · 15876 B
1// nx_capgraph_lib.nx -- MANY-AXIS CAPABILITY GRAPH, pure core. No main.
2//
3// WHY (operator 2026-07-31): "a many axis capabilities graph system ... sota as of july 2026 ...
4// instead of our broken flat percent we are using to track growth towards sota so we can start
5// properly mapping forward progress".
6//
7// MEASURED DISEASE (this session, not asserted). nx_capaxes_lib made the SHAPE measurable and its six
8// laws are correct, but the wiring is DEGENERATE: nx_capaxes_derive sets evidence=0 for EVERY domain by
9// construction, so all 28 domains headline MIN=0 today and would still headline 0 after real work lands.
10// A ruler whose every reading is 0 has NO DYNAMIC RANGE and cannot map forward progress -- so the rank
11// silently falls back to leverage, and leverage IS the coverage claim = the flat percent again, wearing
12// a vector's clothes. Second defect: there is no GRAPH. A flat 6-vector per domain has no nodes, no
13// prerequisite edges, no rollup, so a capability standing on a broken prerequisite still reads healthy.
14//
15// SOTA GROUNDING (July 2026, external -- the comparator nx_cap_census correctly refuses to fake):
16// ADeLe / general scales (Nature 2026; arXiv 2503.06378; Microsoft Research + CFI)
17// -- rate DEMAND on 18 dimensions at ORDINAL levels 0..5, and define a subject's ABILITY on a
18// dimension as the demand level at which its success probability crosses 50%. Ability is DERIVED
19// from pass/fail evidence, never claimed; demand-vs-ability then PREDICTS new-task success (~88%).
20// Item Response Theory / adaptive testing for LLM eval (arXiv 2511.04689, 2505.15055, 2510.00844)
21// -- raw accuracy treats all items as equally informative; that IS the flat-percent disease.
22// HELM (Stanford CRFM, arXiv 2211.09110) -- scenario x metric MATRIX, deliberately NO single aggregate.
23// Prerequisite-DAG evaluation with weakest-path propagation (SaaSBench arXiv 2605.17526)
24// -- downstream criteria are SKIPPED when prerequisites are unmet, and the overall strength of a
25// claim mapping is determined by the WEAKEST PATH through the dependency graph.
26//
27// WHAT THIS ADDS TO nx_capaxes_lib (which stays the axis-vector authority; L1/L2/L3 are preserved):
28//
29// LAW L4 -- WEAKEST PATH. A node can NEVER exceed the minimum effective level of its prerequisites.
30// This is L1 (headline=MIN) generalised from a flat vector to a DAG. A capability standing on a TOY
31// prerequisite is a TOY capability no matter what its own axes say, because the prerequisite is what a
32// user hits first. UNMEASURED prerequisites are SKIPPED, never propagated (L3 holds across edges too).
33//
34// LAW L5 -- ORDINAL LEVELS, NOT PERCENTAGES. Each level is defined by WHAT IT TAKES TO PASS, so it
35// cannot be gamed by breadth the way a percentage can, and a level-up is a countable event -- which is
36// exactly what mapping forward progress requires. The ladder is derived from laws this ecosystem
37// already banked by measurement, not from taste:
38// 0 ABSENT nothing exists
39// 1 TOY exists, demo-only
40// 2 WORKS real inputs, happy path
41// 3 GATED a gate proves it AND the gate is proven able to fail (non-vacuity law)
42// 4 ADOPTED wired at the live chokepoint, has a production caller
43// (A GATED FN WITH NO PRODUCTION CALLER IS STILL THE BASELINE)
44// 5 SOTA beats a declared EXTERNAL reference on a declared metric
45//
46// LAW L6 -- EVIDENCE CAPS THE CLAIM. A node's level is capped by its evidence class, so a claim can
47// never outrun its proof. This is the structural cure for gamed-on-coverage-at-below-toy-quality:
48// claiming 5 with demo-only evidence yields 1, not 5. No reviewer memory required.
49//
50// LAW L7 -- PREDICTION IS THE PRODUCT. Ability >= demand, per axis, is a FORWARD question a percentage
51// can never answer. cg_predict names the BINDING axis of a task the system would fail.
52//
53// license_tier: ORIGINAL No hw writes (Rule 26).
54import "nx_capaxes_lib.nx"
55
56const CG_UNMEASURED: i64 = 0 - 1
57const CG_LMAX: i64 = 5
58const CG_MAXN: i64 = 256
59const CG_MAXAX: i64 = 16
60const CG_MAXE: i64 = 2048
61const CG_NAMEW: i64 = 40
62
63const CG_EV_NONE: i64 = 0
64const CG_EV_DEMO: i64 = 1
65const CG_EV_REAL: i64 = 2
66const CG_EV_GATE: i64 = 3
67const CG_EV_LIVE: i64 = 4
68const CG_EV_EXT: i64 = 5
69
70static cg_naxes: i64
71static cg_nn: i64
72static cg_ne: i64
73static cg_lv: *i64
74static cg_ev: *i64
75static cg_own: *i64
76static cg_eff: *i64
77static cg_effax: *i64
78static cg_ef: *i64
79static cg_et: *i64
80static cg_mark: *i64
81static cg_names: *u8
82static cg_axnames: *u8
83
84func cg_streq(a: *u8, b: *u8) -> i64 {
85 var i: i64 = 0
86 var same: i64 = 1
87 var done: i64 = 0
88 while done == 0 {
89 let ca: i64 = a[i] as i64
90 let cb: i64 = b[i] as i64
91 if ca != cb {
92 same = 0
93 done = 1
94 } else {
95 if ca == 0 { done = 1 }
96 else { i = i + 1 }
97 }
98 }
99 return same
100}
101
102func cg_level_name(l: i64) -> *u8 {
103 if l == 0 { return "ABSENT" as *u8 }
104 if l == 1 { return "TOY" as *u8 }
105 if l == 2 { return "WORKS" as *u8 }
106 if l == 3 { return "GATED" as *u8 }
107 if l == 4 { return "ADOPTED" as *u8 }
108 if l == 5 { return "SOTA" as *u8 }
109 return "UNMEASURED" as *u8
110}
111
112func cg_ev_name(e: i64) -> *u8 {
113 if e == CG_EV_NONE { return "none" as *u8 }
114 if e == CG_EV_DEMO { return "demo" as *u8 }
115 if e == CG_EV_REAL { return "real-inputs" as *u8 }
116 if e == CG_EV_GATE { return "gate-nonvacuous" as *u8 }
117 if e == CG_EV_LIVE { return "live-caller" as *u8 }
118 if e == CG_EV_EXT { return "external-battery" as *u8 }
119 return "unknown" as *u8
120}
121
122func cg_init(naxes: i64) {
123 cg_naxes = naxes
124 if cg_naxes > CG_MAXAX { cg_naxes = CG_MAXAX }
125 cg_nn = 0
126 cg_ne = 0
127 cg_lv = sys_mmap(CG_MAXN * CG_MAXAX * 8 + 64) as *i64
128 cg_ev = sys_mmap(CG_MAXN * 8 + 64) as *i64
129 cg_own = sys_mmap(CG_MAXN * 8 + 64) as *i64
130 cg_eff = sys_mmap(CG_MAXN * 8 + 64) as *i64
131 cg_effax = sys_mmap(CG_MAXN * CG_MAXAX * 8 + 64) as *i64
132 cg_ef = sys_mmap(CG_MAXE * 8 + 64) as *i64
133 cg_et = sys_mmap(CG_MAXE * 8 + 64) as *i64
134 cg_mark = sys_mmap(CG_MAXN * 8 + 64) as *i64
135 cg_names = sys_mmap(CG_MAXN * CG_NAMEW + 64) as *u8
136 cg_axnames = sys_mmap(CG_MAXAX * CG_NAMEW + 64) as *u8
137}
138
139func cg_name_put(slot: i64, src: *u8) {
140 var w: i64 = 0
141 while src[w] != (0 as u8) {
142 if w < CG_NAMEW - 1 {
143 let dst: *u8 = (slot + w) as *u8
144 dst[0] = src[w]
145 }
146 w = w + 1
147 }
148 if w > CG_NAMEW - 1 { w = CG_NAMEW - 1 }
149 let term: *u8 = (slot + w) as *u8
150 term[0] = 0 as u8
151}
152
153func cg_axis_set(a: i64, nm: *u8) {
154 if a < 0 { return }
155 if a >= cg_naxes { return }
156 cg_name_put((cg_axnames as i64) + a * CG_NAMEW, nm)
157}
158
159func cg_axis_name(a: i64) -> *u8 {
160 if a < 0 { return "unknown" as *u8 }
161 if a >= cg_naxes { return "unknown" as *u8 }
162 return ((cg_axnames as i64) + a * CG_NAMEW) as *u8
163}
164
165func cg_node_name(i: i64) -> *u8 {
166 if i < 0 { return "unknown" as *u8 }
167 if i >= cg_nn { return "unknown" as *u8 }
168 return ((cg_names as i64) + i * CG_NAMEW) as *u8
169}
170
171func cg_addnode(nm: *u8, evclass: i64) -> i64 {
172 if cg_nn >= CG_MAXN { return 0 - 1 }
173 let id: i64 = cg_nn
174 cg_name_put((cg_names as i64) + id * CG_NAMEW, nm)
175 cg_ev[id] = evclass
176 var a: i64 = 0
177 while a < cg_naxes {
178 cg_lv[id * CG_MAXAX + a] = CG_UNMEASURED
179 a = a + 1
180 }
181 cg_nn = cg_nn + 1
182 return id
183}
184
185func cg_addedge(prereq: i64, dependent: i64) -> i64 {
186 if cg_ne >= CG_MAXE { return 0 - 1 }
187 if prereq < 0 { return 0 - 1 }
188 if dependent < 0 { return 0 - 1 }
189 if prereq >= cg_nn { return 0 - 1 }
190 if dependent >= cg_nn { return 0 - 1 }
191 cg_ef[cg_ne] = prereq
192 cg_et[cg_ne] = dependent
193 cg_ne = cg_ne + 1
194 return 0
195}
196
197func cg_cap(claim: i64, evclass: i64) -> i64 {
198 if claim == CG_UNMEASURED { return CG_UNMEASURED }
199 var c: i64 = claim
200 if c > CG_LMAX { c = CG_LMAX }
201 if c < 0 { c = 0 }
202 if c > evclass { c = evclass }
203 return c
204}
205
206func cg_set(node: i64, axis: i64, claim: i64) {
207 if node < 0 { return }
208 if node >= cg_nn { return }
209 if axis < 0 { return }
210 if axis >= cg_naxes { return }
211 cg_lv[node * CG_MAXAX + axis] = cg_cap(claim, cg_ev[node])
212}
213
214func cg_get(node: i64, axis: i64) -> i64 {
215 if node < 0 { return CG_UNMEASURED }
216 if node >= cg_nn { return CG_UNMEASURED }
217 if axis < 0 { return CG_UNMEASURED }
218 if axis >= cg_naxes { return CG_UNMEASURED }
219 return cg_lv[node * CG_MAXAX + axis]
220}
221
222func cg_own_headline(node: i64) -> i64 {
223 var best: i64 = CG_UNMEASURED
224 var a: i64 = 0
225 while a < cg_naxes {
226 let v: i64 = cg_lv[node * CG_MAXAX + a]
227 if v != CG_UNMEASURED {
228 if best == CG_UNMEASURED { best = v }
229 else { if v < best { best = v } }
230 }
231 a = a + 1
232 }
233 return best
234}
235
236func cg_starved_axis(node: i64) -> i64 {
237 var bi: i64 = 0 - 1
238 var a: i64 = 0
239 while a < cg_naxes {
240 let v: i64 = cg_lv[node * CG_MAXAX + a]
241 if v != CG_UNMEASURED {
242 if bi < 0 { bi = a }
243 else { if v < cg_lv[node * CG_MAXAX + bi] { bi = a } }
244 }
245 a = a + 1
246 }
247 return bi
248}
249
250// L4 -- WEAKEST PATH, propagated PER AXIS. Collapsing the graph to one scalar before propagating would
251// flatten every axis to the node's cross-axis minimum and destroy the multi-axis profile -- measured as a
252// real defect in the first build of this gate (T8b RED). Keeping the MATRIX and propagating each axis
253// independently is the HELM discipline (scenario x metric, never a premature aggregate) combined with the
254// prerequisite-DAG weakest-path rule. The scalar headline is derived LAST, from the per-axis result.
255func cg_relax() {
256 var i: i64 = 0
257 while i < cg_nn {
258 cg_own[i] = cg_own_headline(i)
259 var a: i64 = 0
260 while a < cg_naxes {
261 cg_effax[i * CG_MAXAX + a] = cg_lv[i * CG_MAXAX + a]
262 a = a + 1
263 }
264 i = i + 1
265 }
266 var pass: i64 = 0
267 while pass < cg_nn {
268 var e: i64 = 0
269 while e < cg_ne {
270 let p: i64 = cg_ef[e]
271 let d: i64 = cg_et[e]
272 var a2: i64 = 0
273 while a2 < cg_naxes {
274 let pv: i64 = cg_effax[p * CG_MAXAX + a2]
275 if pv != CG_UNMEASURED {
276 let dv: i64 = cg_effax[d * CG_MAXAX + a2]
277 if dv == CG_UNMEASURED { cg_effax[d * CG_MAXAX + a2] = pv }
278 else { if pv < dv { cg_effax[d * CG_MAXAX + a2] = pv } }
279 }
280 a2 = a2 + 1
281 }
282 e = e + 1
283 }
284 pass = pass + 1
285 }
286 i = 0
287 while i < cg_nn {
288 var best: i64 = CG_UNMEASURED
289 var a3: i64 = 0
290 while a3 < cg_naxes {
291 let v: i64 = cg_effax[i * CG_MAXAX + a3]
292 if v != CG_UNMEASURED {
293 if best == CG_UNMEASURED { best = v }
294 else { if v < best { best = v } }
295 }
296 a3 = a3 + 1
297 }
298 cg_eff[i] = best
299 i = i + 1
300 }
301}
302
303func cg_effective_axis(node: i64, axis: i64) -> i64 {
304 if node < 0 { return CG_UNMEASURED }
305 if node >= cg_nn { return CG_UNMEASURED }
306 if axis < 0 { return CG_UNMEASURED }
307 if axis >= cg_naxes { return CG_UNMEASURED }
308 return cg_effax[node * CG_MAXAX + axis]
309}
310
311func cg_effective(node: i64) -> i64 {
312 if node < 0 { return CG_UNMEASURED }
313 if node >= cg_nn { return CG_UNMEASURED }
314 return cg_eff[node]
315}
316
317func cg_descendants(root: i64) -> i64 {
318 if root < 0 { return 0 }
319 if root >= cg_nn { return 0 }
320 var i: i64 = 0
321 while i < cg_nn {
322 cg_mark[i] = 0
323 i = i + 1
324 }
325 cg_mark[root] = 1
326 var pass: i64 = 0
327 while pass < cg_nn {
328 var e: i64 = 0
329 while e < cg_ne {
330 let p: i64 = cg_ef[e]
331 let d: i64 = cg_et[e]
332 if cg_mark[p] == 1 { cg_mark[d] = 1 }
333 e = e + 1
334 }
335 pass = pass + 1
336 }
337 var c: i64 = 0
338 i = 0
339 while i < cg_nn {
340 if cg_mark[i] == 1 { c = c + 1 }
341 i = i + 1
342 }
343 return c - 1
344}
345
346func cg_rock(node: i64) -> i64 {
347 let e: i64 = cg_effective(node)
348 if e == CG_UNMEASURED { return 0 }
349 let deficit: i64 = CG_LMAX - e
350 if deficit <= 0 { return 0 }
351 return deficit * (1 + cg_descendants(node))
352}
353
354// Rock with EXPLICIT leverage. When the graph has no declared edges yet, downstream mass is 0 for every
355// node and the plain rock TIES EVERYTHING -- measured on the first live run, where all 28 domains scored
356// exactly 5 and the ranking carried no information. Leverage lets the caller supply the STAKE: how large
357// a claim is riding on the missing evidence. This preserves the original insight -- the domain asserting
358// the MOST with the LEAST proof is the biggest liability -- while never letting the claim touch the LEVEL
359// itself, which T10c guards.
360func cg_rock_lev(node: i64, leverage: i64) -> i64 {
361 let e: i64 = cg_effective(node)
362 if e == CG_UNMEASURED { return 0 }
363 let deficit: i64 = CG_LMAX - e
364 if deficit <= 0 { return 0 }
365 var lev: i64 = leverage
366 if lev < 1 { lev = 1 }
367 return deficit * (1 + cg_descendants(node)) * lev
368}
369
370func cg_predict(node: i64, demand: *i64) -> i64 {
371 var worst: i64 = 0 - 1
372 var gap: i64 = 0
373 var a: i64 = 0
374 while a < cg_naxes {
375 let d: i64 = demand[a]
376 if d != CG_UNMEASURED {
377 var abil: i64 = cg_effective_axis(node, a)
378 if abil == CG_UNMEASURED { abil = 0 }
379 if abil < d {
380 let this_gap: i64 = d - abil
381 if this_gap > gap {
382 gap = this_gap
383 worst = a
384 }
385 }
386 }
387 a = a + 1
388 }
389 return worst
390}
391
392func cg_grounded(node: i64) -> i64 {
393 if node < 0 { return 0 }
394 if node >= cg_nn { return 0 }
395 if cg_ev[node] <= CG_EV_NONE { return 0 }
396 return 1
397}
398
399// A gate result that lives only in stdout is INVISIBLE to this ecosystem's evidence machinery -- the
400// rollup, the honesty gate and the evidence audit all read knowledge/status/<name>.log. A GREEN gate
401// nothing can read is, to every ruler here, an UNMEASURED capability: the exact PROVEN-BUT-UNWIRED
402// class this graph was built to expose. It would have been absurd for the capability-measuring organs
403// to be invisible to the measurement system. Append-only, so history accumulates (rule 13).
404// DELEGATES to the base layer. The implementation moved DOWN to nx_capaxes_lib once nx_capaxes_gate
405// turned out to have the same invisibility defect: keeping a second copy here would have been a
406// divergent-dup waiting to happen (two emitters, one format, drifting apart silently). This wrapper
407// stays so the three capgraph gates keep their call site unchanged.
408func cg_gate_log(path: *u8, tag: *u8, passed: i64, total: i64) {
409 cax_gate_log(path, tag, passed, total)
410}
411
412func cg_row(node: i64) {
413 cax_puts(cg_node_name(node))
414 cax_puts(" " as *u8)
415 if cg_grounded(node) == 0 {
416 cax_puts("UNGROUNDED evidence=none -- refusing to emit a level (L2)\n" as *u8)
417 return
418 }
419 let own: i64 = cg_own[node]
420 let eff: i64 = cg_eff[node]
421 cax_kv("own" as *u8, own)
422 cax_puts("(" as *u8)
423 cax_puts(cg_level_name(own))
424 cax_puts(") " as *u8)
425 cax_kv("effective" as *u8, eff)
426 cax_puts("(" as *u8)
427 cax_puts(cg_level_name(eff))
428 cax_puts(") " as *u8)
429 if eff < own {
430 cax_puts("CAPPED-BY-PREREQ " as *u8)
431 }
432 cax_puts("starved=" as *u8)
433 cax_puts(cg_axis_name(cg_starved_axis(node)))
434 cax_puts(" " as *u8)
435 cax_kv("gates" as *u8, cg_descendants(node))
436 cax_kv("rock" as *u8, cg_rock(node))
437 cax_puts("ev=" as *u8)
438 cax_puts(cg_ev_name(cg_ev[node]))
439 cax_puts("\n" as *u8)
440}