nx_elder_registry.nx source
↩ module page · 337 lines · 13413 B
1// nx_elder_registry.nx -- Elder AI service registry (bits-up).
2//
3// Sovereign service registry for the Stage B Elder AI off-Docker
4// port. Holds the (service_name, port, exec_path, args_blob,
5// healthcheck_url, supervisor_state) tuple per service; the
6// platform-specific launcher (PowerShell on Windows; nx_container
7// on Linux) reads the registry to know what to spawn.
8//
9// V1 mechanics: pure-NishiLang registry with canary-bracketed
10// records. String fields are caller-owned *u8 blobs with byte
11// lengths -- the registry doesn't copy strings, it holds pointers
12// (matches H10 nx_modality_router opacity pattern). Supervisor
13// state per service is a sealed enum mirroring nx_supervisor.nx
14// (HEALTHY / BACKING_OFF / ESCALATING / TERMINATED).
15//
16// Composition path:
17// - register_service(name, port, exec, args, healthcheck_url)
18// once per service at supervisor bootstrap
19// - lookup_by_name(name) at routing time -- caller hashes name
20// to a stable index OR linearly scans (V1 is linear; Elder AI
21// has ~80 services, scan is fine)
22// - lookup_by_port(port) for reverse-lookup (debugging,
23// health-page generation)
24// - mark_state(name, state) when the launcher reports a child
25// crashed / exited / recovered
26//
27// Cardinal honesty: spawning a Win32 process requires CreateProcessW
28// from kernel32.dll. That OS boundary is irreducible -- the
29// substrate cannot fork() on Windows. This file owns ONLY the
30// registry data structure and the supervisor-state bookkeeping.
31// The PowerShell launcher (or future nx_win_spawn primitive) reads
32// the registry's exposed accessors and does the actual CreateProcessW
33// call. Substrate stays bits-up; OS boundary stays thin.
34//
35// genealogy_id: docker_compose_v3_2021 + erlang_otp_supervisor_1996 +
36// cardinal_2026-05-21_elder_ai_windows_native_docker_replacement +
37// cardinal_2026-05-20_bits_up_nishi_not_linux
38// lineage_id: substrate_elder_registry_v1
39//
40// nx_capability_manifest:
41// variant_class: elder_registry
42// variant_id: elder_registry_v1_linear_scan
43// requires_isa: [rv32i, rv64imac, x86_64, cortex_m, armv7a, aarch64, wasm32]
44// requires_syscalls: [mmap]
45// requires_ram_min_b: 16384
46// tier_floor: NX_TIER_INF_MOBILE
47// tier_ceiling: NX_TIER_INF_HPC
48// cost_model:
49// flops_per_n: 1.0 // O(n_services) linear scan
50// bytes_per_n: 96.0 // per-service record
51// syscalls_per_n: 0.0
52// adversary_class: THREAT_AI_ADVERSARY
53//
54// nx_safety_envelope:
55// intended_use: "Service registry for sovereign Elder AI port;
56// bits-up; composes with nx_supervisor state machine"
57// sil_target: SIL2
58// evidence: [canary_bracketed, supervisor_state_sealed_enum,
59// duplicate_name_refused, port_uniqueness]
60// verdict: NOT_YET_EVALUATED
61
62import "nx_syscalls.nx"
63const NX_MAGIC_65535: i64 = 65535
64
65// ===== Constants =================================================
66const NX_ELDER_MAX_SERVICES: i64 = 256
67
68// Supervisor state (mirrors nx_supervisor.nx but redefined locally
69// to avoid the existing import which targets x86_64-only syscalls).
70const NX_ELDER_STATE_REGISTERED: i64 = 0 // known but not yet started
71const NX_ELDER_STATE_STARTING: i64 = 1 // launcher invoked, awaiting healthy
72const NX_ELDER_STATE_HEALTHY: i64 = 2 // healthcheck OK
73const NX_ELDER_STATE_BACKING_OFF: i64 = 3 // crashed, waiting before restart
74const NX_ELDER_STATE_ESCALATING: i64 = 4 // exceeded max_restarts
75const NX_ELDER_STATE_TERMINATED: i64 = 5 // explicit stop
76const NX_ELDER_STATE_N_STATES: i64 = 6
77
78func nx_elder_state_is_valid(s: i64) -> i64 {
79 if s < 0 { return 0 }
80 if s >= NX_ELDER_STATE_N_STATES { return 0 }
81 return 1
82}
83
84// Verdicts.
85const NX_ELDER_OK: i64 = 0
86const NX_ELDER_BAD_INPUT: i64 = 1
87const NX_ELDER_NOT_FOUND: i64 = 2
88const NX_ELDER_DUPLICATE_NAME: i64 = 3
89const NX_ELDER_DUPLICATE_PORT: i64 = 4
90const NX_ELDER_FULL: i64 = 5
91const NX_ELDER_BAD_STATE: i64 = 6
92const NX_ELDER_TAMPER: i64 = 7
93const NX_ELDER_N_VERDICTS: i64 = 8
94
95func nx_elder_verdict_is_valid(v: i64) -> i64 {
96 if v < 0 { return 0 }
97 if v >= NX_ELDER_N_VERDICTS { return 0 }
98 return 1
99}
100
101// Canary magic.
102const NX_ELDER_SVC_CANARY_PRE: i64 = 0x456C6453766350A1 // "EldSvcP!"
103const NX_ELDER_SVC_CANARY_POST: i64 = 0x53766345314434A2 // "SvcE1D4 "
104const NX_ELDER_REG_CANARY_PRE: i64 = 0x456C645265675001 // "EldRegP."
105const NX_ELDER_REG_CANARY_POST: i64 = 0x52656745314435A3 // "RegE1D5."
106
107// ===== Structs ====================================================
108struct NxElderService {
109 canary_pre: i64,
110 service_id: i64, // caller-assigned numeric id
111 name: *u8, // opaque pointer
112 name_len: i64,
113 port: i64,
114 exec_path: *u8, // opaque pointer (e.g. "python.exe")
115 exec_path_len: i64,
116 args_blob: *u8, // opaque pointer (caller-encoded; PowerShell parses)
117 args_blob_len: i64,
118 healthcheck_url: *u8, // opaque pointer (e.g. "http://localhost:8423/health")
119 healthcheck_len: i64,
120 state: i64, // NX_ELDER_STATE_*
121 restart_count: i64, // for the supervisor backoff math
122 canary_post: i64,
123}
124
125struct NxElderRegistry {
126 canary_pre: i64,
127 max_services: i64,
128 n_services: i64,
129 services: *i64, // *i64 array of *NxElderService pointers
130 canary_post: i64,
131}
132
133// ===== Validity =================================================
134func nx_elder_service_is_valid(s: *NxElderService) -> i64 {
135 if (s as i64) == 0 { return 0 }
136 if s.canary_pre != NX_ELDER_SVC_CANARY_PRE { return 0 }
137 if s.canary_post != NX_ELDER_SVC_CANARY_POST { return 0 }
138 if s.port <= 0 { return 0 }
139 if s.port > NX_MAGIC_65535 { return 0 }
140 if s.name_len <= 0 { return 0 }
141 if s.exec_path_len <= 0 { return 0 }
142 if nx_elder_state_is_valid(s.state) != 1 { return 0 }
143 if s.restart_count < 0 { return 0 }
144 return 1
145}
146
147func nx_elder_registry_is_valid(r: *NxElderRegistry) -> i64 {
148 if (r as i64) == 0 { return 0 }
149 if r.canary_pre != NX_ELDER_REG_CANARY_PRE { return 0 }
150 if r.canary_post != NX_ELDER_REG_CANARY_POST { return 0 }
151 if r.max_services <= 0 { return 0 }
152 if r.max_services > NX_ELDER_MAX_SERVICES { return 0 }
153 if r.n_services < 0 { return 0 }
154 if r.n_services > r.max_services { return 0 }
155 return 1
156}
157
158// ===== Constructor =================================================
159func nx_elder_registry_new(max_services: i64) -> *NxElderRegistry {
160 if max_services <= 0 { return (0 as i64) as *NxElderRegistry }
161 if max_services > NX_ELDER_MAX_SERVICES { return (0 as i64) as *NxElderRegistry }
162
163 let r: *NxElderRegistry = (sys_mmap(48)) as *NxElderRegistry
164 r.canary_pre = NX_ELDER_REG_CANARY_PRE
165 r.max_services = max_services
166 r.n_services = 0
167 r.services = (sys_mmap(max_services * 8)) as *i64
168 r.canary_post = NX_ELDER_REG_CANARY_POST
169
170 var i: i64 = 0
171 while i < max_services {
172 r.services[i] = 0
173 i = i + 1
174 }
175 return r
176}
177
178// ===== String comparison (byte-equal) =================================================
179func _elder_bytes_equal(a: *u8, a_len: i64, b: *u8, b_len: i64) -> i64 {
180 if a_len != b_len { return 0 }
181 var i: i64 = 0
182 while i < a_len {
183 if a[i] != b[i] { return 0 }
184 i = i + 1
185 }
186 return 1
187}
188
189// ===== Internal helpers =================================================
190func _elder_find_by_name(r: *NxElderRegistry, name: *u8, name_len: i64) -> i64 {
191 var i: i64 = 0
192 while i < r.n_services {
193 let p: i64 = r.services[i]
194 if p != 0 {
195 let s: *NxElderService = p as *NxElderService
196 if _elder_bytes_equal(s.name, s.name_len, name, name_len) == 1 {
197 return i
198 }
199 }
200 i = i + 1
201 }
202 return 0 - 1
203}
204
205func _elder_find_by_port(r: *NxElderRegistry, port: i64) -> i64 {
206 var i: i64 = 0
207 while i < r.n_services {
208 let p: i64 = r.services[i]
209 if p != 0 {
210 let s: *NxElderService = p as *NxElderService
211 if s.port == port { return i }
212 }
213 i = i + 1
214 }
215 return 0 - 1
216}
217
218// ===== Register =================================================
219func nx_elder_register(r: *NxElderRegistry, service_id: i64, name: *u8, name_len: i64, port: i64, exec_path: *u8, exec_path_len: i64, args_blob: *u8, args_blob_len: i64, healthcheck_url: *u8, healthcheck_len: i64) -> i64 {
220 if nx_elder_registry_is_valid(r) != 1 { return 0 - NX_ELDER_TAMPER }
221 if (name as i64) == 0 { return 0 - NX_ELDER_BAD_INPUT }
222 if name_len <= 0 { return 0 - NX_ELDER_BAD_INPUT }
223 if (exec_path as i64) == 0 { return 0 - NX_ELDER_BAD_INPUT }
224 if exec_path_len <= 0 { return 0 - NX_ELDER_BAD_INPUT }
225 if port <= 0 { return 0 - NX_ELDER_BAD_INPUT }
226 if port > NX_MAGIC_65535 { return 0 - NX_ELDER_BAD_INPUT }
227 if healthcheck_len < 0 { return 0 - NX_ELDER_BAD_INPUT }
228 if args_blob_len < 0 { return 0 - NX_ELDER_BAD_INPUT }
229
230 if _elder_find_by_name(r, name, name_len) >= 0 { return 0 - NX_ELDER_DUPLICATE_NAME }
231 if _elder_find_by_port(r, port) >= 0 { return 0 - NX_ELDER_DUPLICATE_PORT }
232 if r.n_services >= r.max_services { return 0 - NX_ELDER_FULL }
233
234 let s: *NxElderService = (sys_mmap(120)) as *NxElderService
235 s.canary_pre = NX_ELDER_SVC_CANARY_PRE
236 s.service_id = service_id
237 s.name = name
238 s.name_len = name_len
239 s.port = port
240 s.exec_path = exec_path
241 s.exec_path_len = exec_path_len
242 s.args_blob = args_blob
243 s.args_blob_len = args_blob_len
244 s.healthcheck_url = healthcheck_url
245 s.healthcheck_len = healthcheck_len
246 s.state = NX_ELDER_STATE_REGISTERED
247 s.restart_count = 0
248 s.canary_post = NX_ELDER_SVC_CANARY_POST
249
250 let slot: i64 = r.n_services
251 r.services[slot] = s as i64
252 r.n_services = r.n_services + 1
253 return slot
254}
255
256// ===== Lookups =================================================
257func nx_elder_lookup_by_name(r: *NxElderRegistry, name: *u8, name_len: i64) -> *NxElderService {
258 if nx_elder_registry_is_valid(r) != 1 { return (0 as i64) as *NxElderService }
259 let slot: i64 = _elder_find_by_name(r, name, name_len)
260 if slot < 0 { return (0 as i64) as *NxElderService }
261 return (r.services[slot]) as *NxElderService
262}
263
264func nx_elder_lookup_by_port(r: *NxElderRegistry, port: i64) -> *NxElderService {
265 if nx_elder_registry_is_valid(r) != 1 { return (0 as i64) as *NxElderService }
266 let slot: i64 = _elder_find_by_port(r, port)
267 if slot < 0 { return (0 as i64) as *NxElderService }
268 return (r.services[slot]) as *NxElderService
269}
270
271func nx_elder_lookup_by_index(r: *NxElderRegistry, idx: i64) -> *NxElderService {
272 if nx_elder_registry_is_valid(r) != 1 { return (0 as i64) as *NxElderService }
273 if idx < 0 { return (0 as i64) as *NxElderService }
274 if idx >= r.n_services { return (0 as i64) as *NxElderService }
275 return (r.services[idx]) as *NxElderService
276}
277
278// ===== Supervisor-state mutation =================================================
279func nx_elder_mark_state(r: *NxElderRegistry, name: *u8, name_len: i64, new_state: i64) -> i64 {
280 if nx_elder_registry_is_valid(r) != 1 { return 0 - NX_ELDER_TAMPER }
281 if nx_elder_state_is_valid(new_state) != 1 { return 0 - NX_ELDER_BAD_STATE }
282 let slot: i64 = _elder_find_by_name(r, name, name_len)
283 if slot < 0 { return 0 - NX_ELDER_NOT_FOUND }
284 let s: *NxElderService = (r.services[slot]) as *NxElderService
285 if nx_elder_service_is_valid(s) != 1 { return 0 - NX_ELDER_TAMPER }
286 s.state = new_state
287 if new_state == NX_ELDER_STATE_BACKING_OFF {
288 s.restart_count = s.restart_count + 1
289 }
290 if new_state == NX_ELDER_STATE_HEALTHY {
291 // Reset restart count once healthy (matches Erlang OTP healthy window).
292 s.restart_count = 0
293 }
294 return NX_ELDER_OK
295}
296
297// ===== Accessors =================================================
298func nx_elder_service_port(s: *NxElderService) -> i64 {
299 if nx_elder_service_is_valid(s) != 1 { return 0 - 1 }
300 return s.port
301}
302
303func nx_elder_service_state(s: *NxElderService) -> i64 {
304 if nx_elder_service_is_valid(s) != 1 { return 0 - 1 }
305 return s.state
306}
307
308func nx_elder_service_id(s: *NxElderService) -> i64 {
309 if nx_elder_service_is_valid(s) != 1 { return 0 - 1 }
310 return s.service_id
311}
312
313func nx_elder_service_restart_count(s: *NxElderService) -> i64 {
314 if nx_elder_service_is_valid(s) != 1 { return 0 - 1 }
315 return s.restart_count
316}
317
318func nx_elder_n_services(r: *NxElderRegistry) -> i64 {
319 if nx_elder_registry_is_valid(r) != 1 { return 0 - 1 }
320 return r.n_services
321}
322
323// Count services currently in HEALTHY state.
324func nx_elder_n_healthy(r: *NxElderRegistry) -> i64 {
325 if nx_elder_registry_is_valid(r) != 1 { return 0 - 1 }
326 var count: i64 = 0
327 var i: i64 = 0
328 while i < r.n_services {
329 let p: i64 = r.services[i]
330 if p != 0 {
331 let s: *NxElderService = p as *NxElderService
332 if s.state == NX_ELDER_STATE_HEALTHY { count = count + 1 }
333 }
334 i = i + 1
335 }
336 return count
337}