nx_artifact_root.nx source
↩ module page · 106 lines · 4776 B
1// nx_artifact_root.nx -- ARTIFACT ROOT RESOLUTION, shared. One resolver, one roots table, for every organ
2// that has to turn a by-convention path into a file that actually exists on THIS host.
3//
4// WHY THIS IS A SHARED LIB AND NOT A LOCAL HELPER: two independent lanes paid for the same defect within a
5// day of each other, which is the signal that it is a CLASS, not a bug.
6// * evidence lane (2026-07-30): a `.gates` row names `_offc/nx_X_gate.elf` and a matrix row names
7// `runtime/nx_X.nx`. BOTH resolve on the laptop and NEITHER on the NAS -- /api/promote installs binaries
8// FLAT at the serving root, and sources live under `buildroot/`. MEASURED: 9 of 9 real warden axes read
9// UNGROUNDED on the NAS while every symbol was present in the tree the whole time.
10// * seq1339 (sibling): the maturity rollup reads evidence logs at one root while gates emit 1164 of them
11// at another, leaving ~85% of real evidence invisible and whole lanes reading UNMEASURED.
12// Same shape both times: an organ decided "not here" when the honest answer was "look somewhere else".
13//
14// ★THE LAW THIS ENCODES: a path convention that differs per host is a DATA fact, not a code constant.
15// The roots live in a conf (rule 11/17) so a new deployment layout is a config edit, not a recompile of
16// every consumer.
17//
18// RESOLUTION ORDER, and why all three steps are needed:
19// 1. the path EXACTLY as written -- so nothing that works today changes behaviour
20// 2. each root + the FULL relative path -- sources keep their directory part (`buildroot/` + `runtime/x.nx`)
21// 3. each root + the BASENAME -- binaries lose theirs (`` + `x.elf`, because promote installs flat)
22// First hit wins. A miss returns 0 and leaves `out` equal to the input, so the caller's error message stays
23// truthful about what it was actually looking for.
24// license_tier: ORIGINAL
25import "nx_syscalls.nx"
26
27const AR_CONF_MAX: i64 = 8192
28
29static ar_buf: *u8
30static ar_n: i64
31static ar_loaded: i64
32
33func ar_read(path: *u8, buf: *u8, cap: i64) -> i64 {
34 let fd: i64 = sys_openat_rd(path)
35 if fd < 0 { return 0 - 1 }
36 var tot: i64 = 0
37 var go: i64 = 1
38 while go == 1 {
39 let n: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, cap - tot)
40 if n <= 0 { go = 0 } else { tot = tot + n; if tot >= cap { go = 0 } }
41 }
42 sys_close(fd)
43 return tot
44}
45
46func ar_exists(p: *u8) -> i64 {
47 let fd: i64 = sys_openat_rd(p)
48 if fd >= 0 { sys_close(fd); return 1 }
49 return 0
50}
51
52// Resolve against an EXPLICIT roots conf. Taking the conf path as a parameter is deliberate: a resolver
53// that can only ever read one hardcoded file cannot be tested without editing production config, and an
54// untestable resolver is exactly the kind of component that silently rots into the defect above.
55func ar_resolve_with(inp: *u8, out: *u8, confpath: *u8) -> i64 {
56 var n: i64 = 0
57 while inp[n] != (0 as u8) { n = n + 1 }
58 var i: i64 = 0
59 while i <= n { out[i] = inp[i]; i = i + 1 }
60 if ar_exists(inp) == 1 { return 1 }
61 let cbuf: *u8 = sys_mmap(AR_CONF_MAX)
62 let cn: i64 = ar_read(confpath, cbuf, AR_CONF_MAX - 1)
63 if cn <= 0 { return 0 }
64 var base: i64 = 0
65 var b: i64 = 0
66 while b < n { if inp[b] == (47 as u8) { base = b + 1 } b = b + 1 }
67 var ls: i64 = 0
68 var p: i64 = 0
69 while p <= cn {
70 var eol: i64 = 0
71 if p == cn { eol = 1 } else { if cbuf[p] == (10 as u8) { eol = 1 } }
72 if eol == 1 {
73 if p > ls { if cbuf[ls] != (35 as u8) { if p - ls >= 5 {
74 var isroot: i64 = 1
75 let rk: *u8 = "root="
76 var q: i64 = 0
77 while q < 5 { if cbuf[ls+q] != rk[q] { isroot = 0; q = 5 } else { q = q + 1 } }
78 if isroot == 1 {
79 var o: i64 = 0
80 var k: i64 = ls + 5
81 while k < p { out[o] = cbuf[k]; o = o + 1; k = k + 1 }
82 let rootlen: i64 = o
83 var c: i64 = 0
84 while c < n { out[o] = inp[c]; o = o + 1; c = c + 1 }
85 out[o] = 0 as u8
86 if ar_exists(out) == 1 { return 1 }
87 o = rootlen
88 c = base
89 while c < n { out[o] = inp[c]; o = o + 1; c = c + 1 }
90 out[o] = 0 as u8
91 if ar_exists(out) == 1 { return 1 }
92 }
93 } } }
94 ls = p + 1
95 }
96 p = p + 1
97 }
98 var j: i64 = 0
99 while j <= n { out[j] = inp[j]; j = j + 1 }
100 return 0
101}
102
103// The ecosystem default. Consumers call this; the roots table is one file for the whole host.
104func ar_resolve(inp: *u8, out: *u8) -> i64 {
105 return ar_resolve_with(inp, out, "knowledge/evidence_roots.conf" as *u8)
106}