code wiki / (root) / nx_elder_registry.nx

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}