nx_substrate_evolve.nx source
↩ module page · 506 lines · 20464 B
1// nx_substrate_evolve.nx -- generational substrate audit + watch-list.
2//
3// Walks the substrate as evolutionary generations:
4//
5// Gen 0 -- "God" : axioms (nx_axioms.nx, NX_AX_* constants)
6// Gen 1 -- "Adam" : foundational primitives that depend only
7// on syscalls + axioms (runtime.nx, nx_ascii.nx,
8// nx_dirent.nx, nx_fcntl.nx, ...)
9// Gen 2..N -- "Children": primitives whose only-import frontier sits
10// in earlier generations.
11//
12// For each generation we emit:
13// - members (the .nx files at this generation)
14// - genealogy completeness (% with genealogy_id + lineage_id + axioms)
15// - the WATCH LIST for the next generation: gaps / duplicates /
16// queued items that the NEXT gen must address.
17//
18// This makes the algorithm-evolving loop a generational lineage:
19// Gen N inherits axioms from God, builds on Gen N-1, and emits
20// concrete obligations for Gen N+1.
21//
22// Output JSONL to stdout:
23// {"gen":N,"members":[...],"complete":K,"total":M,
24// "watch_next":[{"kind":"...","what":"..."}, ...]}
25// {"summary":true,"n_generations":N,"total_files":M, ...}
26//
27// genealogy_id: dawkins_1976_meme + kuhn_1962_paradigm
28// + classical_topological_sort
29// lineage_id: generational_lineage_audit
30// axioms: NX_AX_ORD_LEAST_UPPER_BOUND (generation number is well-
31// defined: smallest n such that all deps are in Gen < n)
32// + NX_AX_REL_TRANSITIVITY (depends-on is transitive)
33
34// nx_safety_envelope:
35// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
36// sil_target: SIL1
37// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
38// verdict: NOT_YET_EVALUATED
39
40import "syscalls.nx"
41import "runtime.nx"
42import "nx_axioms.nx"
43import "nx_dirent.nx"
44import "nx_fcntl.nx"
45import "nx_lex.nx"
46import "nx_ascii.nx"
47
48const NX_EV_MAX_FILES: i64 = 400
49const NX_EV_MAX_IMPORTS: i64 = 32
50const NX_EV_MAX_NAME: i64 = 96
51const NX_EV_RECORD_BYTES: i64 = 56 // 7 i64 fields
52const NX_EV_DIR_BUF: i64 = 16384
53const NX_EV_GEN_UNKNOWN: i64 = -1
54
55struct EvolveRecord {
56 name: *u8, // filename (no path, no .nx)
57 n_imports: i64,
58 import_idx: i64, // offset into imports_to[] array
59 has_genealogy: i64,
60 has_lineage: i64,
61 has_axioms: i64,
62 generation: i64,
63}
64
65// ===== filename pattern + module extraction =============================
66
67func ev_name_matches(name: *u8, name_len: i64) -> i64 {
68 if name_len < 6 { return 0 } // nx_*.nx minimum
69 if name[0] != 110 { return 0 } // 'n'
70 if name[1] != 120 { return 0 } // 'x'
71 if name[2] != 95 { return 0 } // '_'
72 if name[name_len - 3] != 46 { return 0 } // '.'
73 if name[name_len - 2] != 110 { return 0 } // 'n'
74 if name[name_len - 1] != 120 { return 0 } // 'x'
75 // reject _test.nx
76 if name_len >= 8 {
77 if name[name_len - 8] == 95 {
78 if name[name_len - 7] == 116 {
79 if name[name_len - 6] == 101 {
80 if name[name_len - 5] == 115 {
81 if name[name_len - 4] == 116 {
82 return 0
83 }
84 }
85 }
86 }
87 }
88 }
89 return 1
90}
91
92// Convert filename "nx_geom.nx" to module key "nx_geom" (null-terminated).
93func ev_module_from_filename(name: *u8, name_len: i64, out: *u8) -> i64 {
94 let mod_len: i64 = name_len - 3 // strip ".nx"
95 var i: i64 = 0
96 while i < mod_len {
97 out[i] = name[i]
98 i = i + 1
99 }
100 out[mod_len] = 0
101 return mod_len
102}
103
104// ===== file header scan ================================================
105//
106// Reads the file content and:
107// - extracts every `import "..."` line into module-name list
108// - sets has_genealogy / has_lineage / has_axioms based on presence
109// of those tags in header comments
110//
111// Returns 0 on success, -1 on read error.
112
113func ev_line_contains(buf: *u8, start: i64, end: i64, needle: *u8) -> i64 {
114 let n_len: i64 = strlen(needle)
115 if end - start < n_len { return 0 }
116 var i: i64 = start
117 while i <= end - n_len {
118 let buf_at_i: *u8 = ((buf as i64) + i) as *u8
119 if strneq(buf_at_i, needle, n_len) == 1 { return 1 }
120 i = i + 1
121 }
122 return 0
123}
124
125func ev_scan_header(file_path: *u8, rec: *EvolveRecord,
126 imports_to: *i64, all_modules: *u8,
127 n_modules: i64) -> i64 {
128 let out_len: *i64 = (sys_mmap(8)) as *i64
129 out_len[0] = 0
130 let buf: *u8 = sys_read_file(file_path, out_len)
131 if (buf as i64) == 0 { return -1 }
132 let n: i64 = out_len[0]
133
134 rec.n_imports = 0
135 rec.has_genealogy = 0
136 rec.has_lineage = 0
137 rec.has_axioms = 0
138
139 // Walk line by line.
140 var pos: i64 = 0
141 while pos < n {
142 // find end of line (proper done-flag pattern)
143 var eol: i64 = pos
144 var done_line: i64 = 0
145 while done_line == 0 {
146 if eol >= n { done_line = 1 }
147 if done_line == 0 {
148 if buf[eol] == 10 { done_line = 1 }
149 if done_line == 0 { eol = eol + 1 }
150 }
151 }
152 let line_len: i64 = eol - pos
153
154 // Check for `import "<name>"`
155 if line_len > 10 {
156 // match "import "
157 if buf[pos] == 105 { // 'i'
158 if buf[pos+1] == 109 { // 'm'
159 if buf[pos+2] == 112 { // 'p'
160 if buf[pos+3] == 111 { // 'o'
161 if buf[pos+4] == 114 { // 'r'
162 if buf[pos+5] == 116 { // 't'
163 if buf[pos+6] == 32 { // ' '
164 // find opening " (done-flag)
165 var p: i64 = pos + 7
166 var done_q: i64 = 0
167 while done_q == 0 {
168 if p >= eol { done_q = 1 }
169 if done_q == 0 {
170 if buf[p] == 34 { done_q = 1 }
171 if done_q == 0 { p = p + 1 }
172 }
173 }
174 if p < eol {
175 if buf[p] == 34 {
176 p = p + 1
177 let mod_start: i64 = p
178 var done_m: i64 = 0
179 while done_m == 0 {
180 if p >= eol { done_m = 1 }
181 if done_m == 0 {
182 if buf[p] == 34 { done_m = 1 }
183 if done_m == 0 {
184 if buf[p] == 46 { done_m = 1 }
185 if done_m == 0 { p = p + 1 }
186 }
187 }
188 }
189 let mod_end: i64 = p
190 // record this import (with done-flag for inner loop)
191 if rec.n_imports < NX_EV_MAX_IMPORTS {
192 var found_idx: i64 = -1
193 var mi: i64 = 0
194 var done_search: i64 = 0
195 while done_search == 0 {
196 if mi >= n_modules { done_search = 1 }
197 if done_search == 0 {
198 let cand_addr: i64 = (all_modules as i64) + mi * NX_EV_MAX_NAME
199 let cand: *u8 = cand_addr as *u8
200 let cand_len: i64 = strlen(cand)
201 if cand_len == mod_end - mod_start {
202 let buf_at_mod: *u8 = ((buf as i64) + mod_start) as *u8
203 if strneq(cand, buf_at_mod, cand_len) == 1 {
204 found_idx = mi
205 done_search = 1
206 }
207 }
208 if done_search == 0 { mi = mi + 1 }
209 }
210 }
211 if found_idx >= 0 {
212 imports_to[rec.import_idx + rec.n_imports] = found_idx
213 rec.n_imports = rec.n_imports + 1
214 }
215 }
216 }
217 }
218 }
219 }
220 }
221 }
222 }
223 }
224 }
225 }
226
227 // Check tag lines `// genealogy_id:`, `// lineage_id:`, `// axioms:`
228 if line_len > 4 {
229 if buf[pos] == 47 { // '/'
230 if buf[pos+1] == 47 { // '//'
231 // look for tag keywords
232 if rec.has_genealogy == 0 {
233 if ev_line_contains(buf, pos, eol, "genealogy_id:" as *u8) == 1 {
234 rec.has_genealogy = 1
235 }
236 }
237 if rec.has_lineage == 0 {
238 if ev_line_contains(buf, pos, eol, "lineage_id:" as *u8) == 1 {
239 rec.has_lineage = 1
240 }
241 }
242 if rec.has_axioms == 0 {
243 if ev_line_contains(buf, pos, eol, "axioms:" as *u8) == 1 {
244 rec.has_axioms = 1
245 }
246 }
247 }
248 }
249 }
250
251 pos = eol + 1
252 }
253 return 0
254}
255
256// ===== directory walk ====================================================
257
258func ev_collect_modules(dir_path: *u8, all_modules: *u8, n_out: *i64) -> i64 {
259 let fd: i64 = nx_openat(NX_AT_FDCWD, dir_path,
260 NX_O_RDONLY | NX_O_DIRECTORY, 0)
261 if fd < 0 { return -1 }
262 let buf: *u8 = sys_mmap(NX_EV_DIR_BUF)
263 let dr_raw: *u8 = sys_mmap(NX_DIRENT_BYTES)
264 let dr: *NxDirent = dr_raw as *NxDirent
265 let mod_buf: *u8 = sys_mmap(NX_EV_MAX_NAME)
266 n_out[0] = 0
267
268 var batch: i64 = nx_dirent_read(fd, buf, NX_EV_DIR_BUF)
269 while batch > 0 {
270 var off: i64 = 0
271 while off < batch {
272 let next_off: i64 = nx_dirent_iter(buf, off, batch, dr)
273 if next_off <= 0 { off = batch + 1 }
274 if off <= batch {
275 let nm_len: i64 = nx_dirent_name_len(dr)
276 if ev_name_matches(dr.name, nm_len) == 1 {
277 if n_out[0] < NX_EV_MAX_FILES {
278 let dst_addr: i64 = (all_modules as i64) + n_out[0] * NX_EV_MAX_NAME
279 let dst: *u8 = dst_addr as *u8
280 ev_module_from_filename(dr.name, nm_len, dst)
281 n_out[0] = n_out[0] + 1
282 }
283 }
284 off = next_off
285 }
286 }
287 batch = nx_dirent_read(fd, buf, NX_EV_DIR_BUF)
288 }
289 sys_close(fd)
290 return 0
291}
292
293// ===== topological generation assignment =================================
294//
295// generation(M) = 0 if M imports nothing (or only axioms/syscalls)
296// = 1 + max(generation(D) for D in imports(M)) otherwise
297//
298// We compute generations via iterative refinement (no full toposort
299// needed; just keep assigning until stable). For acyclic substrate
300// this converges quickly.
301
302func ev_assign_generations(records: *EvolveRecord, n_records: i64,
303 imports_to: *i64) -> i64 {
304 var i: i64 = 0
305 while i < n_records {
306 let r: *EvolveRecord = ((records as i64) + i * NX_EV_RECORD_BYTES) as *EvolveRecord
307 r.generation = NX_EV_GEN_UNKNOWN
308 i = i + 1
309 }
310 // Records with no imports get generation 0.
311 var changed: i64 = 1
312 var iter: i64 = 0
313 while changed == 1 {
314 changed = 0
315 if iter > 50 { changed = 0 } // safety bound
316 if iter <= 50 {
317 var idx: i64 = 0
318 while idx < n_records {
319 let r: *EvolveRecord = ((records as i64) + idx * NX_EV_RECORD_BYTES) as *EvolveRecord
320 if r.generation == NX_EV_GEN_UNKNOWN {
321 if r.n_imports == 0 {
322 r.generation = 0
323 changed = 1
324 }
325 if r.n_imports > 0 {
326 var max_gen: i64 = -1
327 var all_known: i64 = 1
328 var j: i64 = 0
329 while j < r.n_imports {
330 let dep_idx: i64 = imports_to[r.import_idx + j]
331 let d_addr: i64 = (records as i64) + dep_idx * NX_EV_RECORD_BYTES
332 let d: *EvolveRecord = d_addr as *EvolveRecord
333 if d.generation == NX_EV_GEN_UNKNOWN { all_known = 0 }
334 if d.generation > max_gen { max_gen = d.generation }
335 j = j + 1
336 }
337 if all_known == 1 {
338 r.generation = max_gen + 1
339 changed = 1
340 }
341 }
342 }
343 idx = idx + 1
344 }
345 }
346 iter = iter + 1
347 }
348 return iter
349}
350
351// ===== emit per-generation report =======================================
352
353func ev_emit_generation(records: *EvolveRecord, n_records: i64, gen: i64) -> i64 {
354 var n_members: i64 = 0
355 var n_with_geneal: i64 = 0
356 var n_with_lineage: i64 = 0
357 var n_with_axioms: i64 = 0
358 var i: i64 = 0
359 while i < n_records {
360 let r: *EvolveRecord = ((records as i64) + i * NX_EV_RECORD_BYTES) as *EvolveRecord
361 if r.generation == gen {
362 n_members = n_members + 1
363 n_with_geneal = n_with_geneal + r.has_genealogy
364 n_with_lineage = n_with_lineage + r.has_lineage
365 n_with_axioms = n_with_axioms + r.has_axioms
366 }
367 i = i + 1
368 }
369 if n_members == 0 { return 0 }
370
371 print("{\"gen\":" as *u8)
372 print_i64(gen)
373 print(",\"n_members\":" as *u8)
374 print_i64(n_members)
375 print(",\"with_genealogy\":" as *u8)
376 print_i64(n_with_geneal)
377 print(",\"with_lineage\":" as *u8)
378 print_i64(n_with_lineage)
379 print(",\"with_axioms\":" as *u8)
380 print_i64(n_with_axioms)
381 print(",\"members\":[" as *u8)
382
383 var n_emitted: i64 = 0
384 var j: i64 = 0
385 while j < n_records {
386 let r: *EvolveRecord = ((records as i64) + j * NX_EV_RECORD_BYTES) as *EvolveRecord
387 if r.generation == gen {
388 if n_emitted > 0 { print("," as *u8) }
389 print("\"" as *u8)
390 print(r.name)
391 print("\"" as *u8)
392 n_emitted = n_emitted + 1
393 }
394 j = j + 1
395 }
396 print("],\"watch_next\":[" as *u8)
397
398 // Watch list: every member of this generation missing genealogy
399 // becomes a watch item for the next generation.
400 var n_watch: i64 = 0
401 var k: i64 = 0
402 while k < n_records {
403 let r: *EvolveRecord = ((records as i64) + k * NX_EV_RECORD_BYTES) as *EvolveRecord
404 if r.generation == gen {
405 if r.has_genealogy == 0 {
406 if n_watch > 0 { print("," as *u8) }
407 print("{\"kind\":\"missing_genealogy\",\"module\":\"" as *u8)
408 print(r.name)
409 print("\"}" as *u8)
410 n_watch = n_watch + 1
411 }
412 }
413 k = k + 1
414 }
415 println("]}" as *u8)
416 return n_members
417}
418
419// ===== entry ============================================================
420
421func main() -> i64 {
422 let all_modules: *u8 = sys_mmap(NX_EV_MAX_FILES * NX_EV_MAX_NAME)
423 let n_modules: *i64 = (sys_mmap(8)) as *i64
424 let imports_to: *i64 = (sys_mmap(NX_EV_MAX_FILES * NX_EV_MAX_IMPORTS * 8)) as *i64
425
426 if ev_collect_modules("nxc2/runtime" as *u8, all_modules, n_modules) != 0 {
427 println("error: cannot open runtime directory" as *u8)
428 return 1
429 }
430
431 let n: i64 = n_modules[0]
432 let records_raw: *u8 = sys_mmap(n * NX_EV_RECORD_BYTES)
433 let records: *EvolveRecord = records_raw as *EvolveRecord
434
435 // Initialize records: each module's name, import_idx into shared array.
436 var i: i64 = 0
437 while i < n {
438 let r: *EvolveRecord = ((records as i64) + i * NX_EV_RECORD_BYTES) as *EvolveRecord
439 r.name = ((all_modules as i64) + i * NX_EV_MAX_NAME) as *u8
440 r.import_idx = i * NX_EV_MAX_IMPORTS
441 r.generation = NX_EV_GEN_UNKNOWN
442 i = i + 1
443 }
444
445 // Scan each module for imports + tags.
446 let path_buf: *u8 = sys_mmap(512)
447 var j: i64 = 0
448 while j < n {
449 let r: *EvolveRecord = ((records as i64) + j * NX_EV_RECORD_BYTES) as *EvolveRecord
450 // Build path: "nxc2/runtime/" + module + ".nx"
451 let prefix: *u8 = "nxc2/runtime/" as *u8
452 let plen: i64 = strlen(prefix)
453 var k: i64 = 0
454 while k < plen { path_buf[k] = prefix[k]; k = k + 1 }
455 let mod_len: i64 = strlen(r.name)
456 var k2: i64 = 0
457 while k2 < mod_len { path_buf[plen + k2] = r.name[k2]; k2 = k2 + 1 }
458 path_buf[plen + mod_len] = 46 // '.'
459 path_buf[plen + mod_len + 1] = 110 // 'n'
460 path_buf[plen + mod_len + 2] = 120 // 'x'
461 path_buf[plen + mod_len + 3] = 0
462 ev_scan_header(path_buf, r, imports_to, all_modules, n)
463 j = j + 1
464 }
465
466 // Assign generations.
467 ev_assign_generations(records, n, imports_to)
468
469 // Find max generation.
470 var max_gen: i64 = 0
471 var idx: i64 = 0
472 while idx < n {
473 let r: *EvolveRecord = ((records as i64) + idx * NX_EV_RECORD_BYTES) as *EvolveRecord
474 if r.generation > max_gen { max_gen = r.generation }
475 idx = idx + 1
476 }
477
478 // Emit one JSONL line per generation, in order.
479 var total_emitted: i64 = 0
480 var g: i64 = 0
481 while g <= max_gen {
482 let emitted: i64 = ev_emit_generation(records, n, g)
483 total_emitted = total_emitted + emitted
484 g = g + 1
485 }
486
487 // Unresolved (cyclic or missing deps) get generation = -1.
488 var n_unresolved: i64 = 0
489 var u: i64 = 0
490 while u < n {
491 let r: *EvolveRecord = ((records as i64) + u * NX_EV_RECORD_BYTES) as *EvolveRecord
492 if r.generation == NX_EV_GEN_UNKNOWN { n_unresolved = n_unresolved + 1 }
493 u = u + 1
494 }
495
496 print("{\"summary\":true,\"n_generations\":" as *u8)
497 print_i64(max_gen + 1)
498 print(",\"total_files\":" as *u8)
499 print_i64(n)
500 print(",\"emitted\":" as *u8)
501 print_i64(total_emitted)
502 print(",\"unresolved\":" as *u8)
503 print_i64(n_unresolved)
504 println("}" as *u8)
505 return 0
506}