code wiki / _hdl_build / nx_build_registry_lib.nx
nx_build_registry_lib.nx source
↩ module page · 164 lines · 7516 B
1// nx_build_registry_lib.nx -- the BUILD-REGISTRAR library (Librarian SSOT of backend ORGANS); the
2// build-time twin of the publisher's pub_submit -> ledger. SEPARATE from the WMS stream-registry (does
3// NOT clobber it, per coordinate-don't-clobber). Storage = the WMS-R0b framed-append floor (fa_appendz:
4// O_APPEND + flock(LOCK_EX) + write-until-complete = atomic, torn-safe, NO TSV). Records:
5// "BR organ=<name> owner=<role> state=<state> END\n"
6// IDEMPOTENT by LATEST-WINS-on-read: re-submit appends, but distinct-organ count is stable and the
7// newest record's state is the live one (ledger-style dedup without rewrites). Pure lib, no main.
8// Consumers: nx_build_registry (R1 gate), nx_build_bypass_gate (R2 audit). license_tier: ORIGINAL
9import "nx_framed_append.nx"
10import "nx_syscalls.nx"
11
12const BR_CAP: i64 = 256 // per-record byte cap for fa_appendz
13const BR_FBUF: i64 = 262144 // store read buffer
14const BR_SLOT: i64 = 128 // per-field byte slot
15const BR_MAX: i64 = 1024 // max distinct organs
16
17func br_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
18func br_streq(a: *u8, b: *u8) -> i64 {
19 var i: i64 = 0
20 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
21 if b[i] != (0 as u8) { return 0 }
22 return 1
23}
24func br_read(path: *u8, buf: *u8, cap: i64) -> i64 {
25 let fd: i64 = sys_openat_rd(path)
26 if fd < 0 { return 0 - 1 }
27 var total: i64 = 0
28 var nrd: i64 = sys_read(fd, buf, cap)
29 while nrd > 0 {
30 total = total + nrd
31 if total >= cap { nrd = 0 } else { nrd = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) }
32 }
33 sys_close(fd)
34 return total
35}
36func br_find(buf: *u8, from: i64, to: i64, pat: *u8) -> i64 {
37 let pl: i64 = br_len(pat)
38 if pl == 0 { return 0 - 1 }
39 var i: i64 = from
40 while i + pl <= to {
41 var j: i64 = 0; var ok: i64 = 1
42 while j < pl { if buf[i + j] != pat[j] { ok = 0; j = pl } else { j = j + 1 } }
43 if ok == 1 { return i }
44 i = i + 1
45 }
46 return 0 - 1
47}
48// extract the space-delimited value of `key` within line [ls,le) into out (NUL-term). 1 if found.
49func br_extract(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 {
50 let pos: i64 = br_find(buf, ls, le, key)
51 if pos < 0 { return 0 }
52 var v: i64 = pos + br_len(key)
53 var o: i64 = 0
54 while v < le { if buf[v] == (32 as u8) { v = le } else { if o < BR_SLOT - 1 { out[o] = buf[v]; o = o + 1 } v = v + 1 } }
55 out[o] = 0 as u8
56 return 1
57}
58// THE REGISTRAR INTAKE with GENEALOGY OWNERSHIP (operator 2026-06-21: organs owned god->...->organ):
59// `parent` = the organ's anchor node in knowledge/registry/genesis_lineage.tsv (so it traces to god,
60// verified by nx_build_lineage_gate). One atomic framed append.
61func br_submit_lin(store: *u8, organ: *u8, owner: *u8, parent: *u8, state: *u8) -> i64 {
62 let rec: *u8 = sys_mmap(BR_CAP + 16)
63 var o: i64 = 0
64 o = fa_cat(rec, o, "BR organ=" as *u8)
65 o = fa_cat(rec, o, organ)
66 o = fa_cat(rec, o, " owner=" as *u8)
67 o = fa_cat(rec, o, owner)
68 o = fa_cat(rec, o, " parent=" as *u8)
69 o = fa_cat(rec, o, parent)
70 o = fa_cat(rec, o, " state=" as *u8)
71 o = fa_cat(rec, o, state)
72 o = fa_cat(rec, o, " END" as *u8)
73 rec[o] = 0 as u8
74 return fa_appendz(store, rec, BR_CAP)
75}
76// backward-compatible 4-arg: default genealogy anchor = DESC-ORGANS (the genesis node every organ
77// descends from). Existing callers keep working; their records now carry the lineage anchor.
78func br_submit(store: *u8, organ: *u8, owner: *u8, state: *u8) -> i64 {
79 return br_submit_lin(store, organ, owner, "DESC-ORGANS" as *u8, state)
80}
81// latest genealogy anchor (parent= node) for `organ` into out. 1 if the organ is registered.
82// ★THE 256 KiB STORE BUFFER IS FREED ON EVERY PATH OUT. This is a LIBRARY: build machinery calls these
83// readers repeatedly, and BR_FBUF is real VMA (well above NXA_SMALL_MAX), so an unfreed buffer per call
84// is the vm.max_map_count exhaustion nx_syscalls documents. Found by nx_mmapbal.
85// ⚠The per-line `ob` slots are NOT touched: BR_SLOT is 128, i.e. bump-arena memory where sys_munmap
86// returns 0 without freeing -- and in br_distinct those same pointers are RETAINED in names[], so
87// "tidying" them would alias every name to one buffer and collapse the distinct count to 1.
88// ★★★★★★A LEAK FIX THAT DOES NOT READ THE POINTER'S LIFETIME TURNS A LEAK INTO A CORRUPTION.
89func br_get_parent(store: *u8, organ: *u8, out: *u8) -> i64 {
90 let buf: *u8 = sys_mmap(BR_FBUF)
91 let n: i64 = br_read(store, buf, BR_FBUF)
92 out[0] = 0 as u8
93 if n <= 0 { sys_munmap(buf, BR_FBUF); return 0 }
94 var found: i64 = 0
95 var i: i64 = 0
96 while i < n {
97 let ls: i64 = i
98 var le: i64 = ls
99 var g: i64 = 1
100 while g == 1 { if le >= n { g = 0 } else { if buf[le] == (10 as u8) { g = 0 } else { le = le + 1 } } }
101 let ob: *u8 = sys_mmap(BR_SLOT)
102 if br_extract(buf, ls, le, "organ=" as *u8, ob) == 1 {
103 if br_streq(ob, organ) == 1 { br_extract(buf, ls, le, "parent=" as *u8, out); found = 1 }
104 }
105 i = le + 1
106 }
107 sys_munmap(buf, BR_FBUF)
108 return found
109}
110// distinct organs in the store (latest-wins => re-submit does NOT inflate this).
111func br_distinct(store: *u8) -> i64 {
112 let buf: *u8 = sys_mmap(BR_FBUF)
113 let n: i64 = br_read(store, buf, BR_FBUF)
114 if n <= 0 { sys_munmap(buf, BR_FBUF); return 0 }
115 let names: *i64 = sys_mmap(8 * BR_MAX) as *i64
116 var cnt: i64 = 0
117 var i: i64 = 0
118 while i < n {
119 let ls: i64 = i
120 var le: i64 = ls
121 var g: i64 = 1
122 while g == 1 { if le >= n { g = 0 } else { if buf[le] == (10 as u8) { g = 0 } else { le = le + 1 } } }
123 let ob: *u8 = sys_mmap(BR_SLOT)
124 if br_extract(buf, ls, le, "organ=" as *u8, ob) == 1 {
125 var found: i64 = 0
126 var k: i64 = 0
127 while k < cnt { if br_streq(names[k] as *u8, ob) == 1 { found = 1; k = cnt } else { k = k + 1 } }
128 if found == 0 { if cnt < BR_MAX { names[cnt] = ob as i64; cnt = cnt + 1 } }
129 }
130 i = le + 1
131 }
132 // names[] holds pointers into the per-line arena slots and is dead once cnt is computed; the slots
133 // themselves are arena-backed (BR_SLOT=128) so they are not freed here -- see the note on
134 // br_get_parent for why touching them would be a correctness bug, not a tidy-up.
135 sys_munmap(names as *u8, 8 * BR_MAX)
136 sys_munmap(buf, BR_FBUF)
137 return cnt
138}
139// latest state for `organ` into out (NUL-term). returns 1 if the organ is registered, else 0.
140func br_get_state(store: *u8, organ: *u8, out: *u8) -> i64 {
141 let buf: *u8 = sys_mmap(BR_FBUF)
142 let n: i64 = br_read(store, buf, BR_FBUF)
143 out[0] = 0 as u8
144 if n <= 0 { sys_munmap(buf, BR_FBUF); return 0 }
145 var found: i64 = 0
146 var i: i64 = 0
147 while i < n {
148 let ls: i64 = i
149 var le: i64 = ls
150 var g: i64 = 1
151 while g == 1 { if le >= n { g = 0 } else { if buf[le] == (10 as u8) { g = 0 } else { le = le + 1 } } }
152 let ob: *u8 = sys_mmap(BR_SLOT)
153 if br_extract(buf, ls, le, "organ=" as *u8, ob) == 1 {
154 if br_streq(ob, organ) == 1 { br_extract(buf, ls, le, "state=" as *u8, out); found = 1 }
155 }
156 i = le + 1
157 }
158 sys_munmap(buf, BR_FBUF)
159 return found
160}
161func br_registered(store: *u8, organ: *u8) -> i64 {
162 let tmp: *u8 = sys_mmap(BR_SLOT)
163 return br_get_state(store, organ, tmp)
164}