code wiki / _hdl_build / nx_ad_core.nx
nx_ad_core.nx source
↩ module page · 174 lines · 7697 B
1// nx_ad_core.nx -- AUTORUN BEAT CORE (mainless; X-Q-007 "beat list as DATA").
2// The 6h slow beat's TARGETS come from conf rows, not code:
3// T <basename> one durable-lane target per row (order preserved, cap 8)
4// No T rows -> the historical default (nx_math_rung_run) so the existing
5// conf keeps working unchanged. adc_beat runs each target sequentially via
6// _offc/nx_sov_build_run.elf (rebuild-from-source = the evidence renewal),
7// appends one AUTORUN-TGT row per target and ONE AUTORUN-BEAT row keeping
8// the historical rung_rc= field (= worst rc) plus targets=<n>.
9// license_tier: ORIGINAL
10
11import "nx_syscalls.nx"
12import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
13const ADC_MAGIC_8192: i64 = 8192
14
15const ADC_MAXTGT: i64 = 8
16const ADC_NAMECAP: i64 = 64
17
18// ATOMIC single-instance guard -- shared so the daemon AND its dup-guard GATE use the
19// SAME mechanism (DRY). flock(LOCK_EX|LOCK_NB) on a /tmp ext4 lockfile: atomic (no
20// TOCTOU) + kernel-released-on-process-death (no stale-lock problem). Returns the held
21// fd (>=0; caller keeps it open for life) or -1 if another holder has it. Replaces the
22// racy read-pidfile+/proc-cmdline check whose fork->pidfile-write window let two daemons
23// coexist (the bug hit 2026-06-13). /tmp (ext4) because flock on /mnt/c drvfs is unreliable.
24// Atomic single-instance guard via O_CREAT|O_EXCL openat: only ONE concurrent creator
25// wins (the rest get EEXIST) -- this closes the TOCTOU race the old pidfile+/proc check had.
26// NOTE: flock is NOT usable here -- rv64 32 (flock) is MISSING from the compiler's
27// rv64->x86-64 syscall table, so it silently emits as x86-64 dup2 (no lock); nx_flock.nx
28// is latent-broken for the same reason (verdict NOT_YET_EVALUATED). openat IS in the table.
29// Caller keeps the returned fd open for life; staleness (crash-leftover file) is reclaimed
30// by the daemon via its /proc-liveness check + unlink.
31func ad_acquire_lock(path: *u8) -> i64 {
32 let fd: i64 = __syscall(SYS_OPENAT, AT_FDCWD, path as i64, 193, 420, 0, 0) // O_CREAT(64)|O_EXCL(128)|O_WRONLY(1), 0644
33 if fd < 0 { return 0 - 1 } // EEXIST or error -> not acquired (another holder owns it)
34 return fd
35}
36
37func adc_w(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
38// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
39// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
40// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
41// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
42func adc_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
43
44func adc_read(path: *u8, buf: *u8, cap: i64) -> i64 {
45 let fd: i64 = sys_openat_rd(path)
46 if fd < 0 { return 0 }
47 var n: i64 = 0
48 var r: i64 = sys_read(fd, buf, cap - 1)
49 while r > 0 { n = n + r; r = sys_read(fd, buf + n, cap - 1 - n) }
50 sys_close(fd)
51 return n
52}
53
54// parse T rows into arena (ADC_NAMECAP bytes per slot); returns count
55// (0 conf rows -> 1 default target). Target charset [a-z0-9_] (boundary law).
56func adc_targets(confpath: *u8, arena: *u8) -> i64 {
57 let buf: *u8 = sys_mmap(ADC_MAGIC_8192)
58 let n: i64 = adc_read(confpath, buf, ADC_MAGIC_8192)
59 var cnt: i64 = 0
60 var i: i64 = 0
61 while i < n {
62 var le: i64 = i
63 var scan: i64 = 1
64 while scan == 1 {
65 if le >= n { scan = 0 } else {
66 if buf[le] == (10 as u8) { scan = 0 } else { le = le + 1 }
67 }
68 }
69 if buf[i] == (84 as u8) {
70 if i + 1 < le {
71 if buf[i + 1] == (32 as u8) {
72 if cnt < ADC_MAXTGT {
73 let dst: *u8 = (arena as i64 + cnt * ADC_NAMECAP) as *u8
74 var k: i64 = 0
75 var q: i64 = i + 2
76 var ok: i64 = 1
77 while q < le {
78 let ch: i64 = buf[q] as i64
79 var okc: i64 = 0
80 if ch >= 97 { if ch <= 122 { okc = 1 } }
81 if ch >= 48 { if ch <= 57 { okc = 1 } }
82 if ch == 95 { okc = 1 }
83 if okc == 0 { ok = 0; q = le } else {
84 if k < ADC_NAMECAP - 1 { dst[k] = ch as u8; k = k + 1 }
85 q = q + 1
86 }
87 }
88 dst[k] = 0 as u8
89 if ok == 1 { if k > 0 { cnt = cnt + 1 } }
90 }
91 }
92 }
93 }
94 i = le + 1
95 }
96 if cnt == 0 {
97 let d0: *u8 = arena
98 var j: i64 = 0
99 let dflt: *u8 = "nx_math_rung_run" as *u8
100 while dflt[j] != (0 as u8) { d0[j] = dflt[j]; j = j + 1 }
101 d0[j] = 0 as u8
102 cnt = 1
103 }
104 return cnt
105}
106
107// run one durable-lane target, stdout/err -> outpath (append); rc/128+sig
108func adc_run_one(name: *u8, outpath: *u8) -> i64 {
109 let pid: i64 = sys_fork()
110 if pid == 0 {
111 let runner: *u8 = "_offc/nx_sov_build_run.elf" as *u8
112 let argv: *i64 = sys_mmap(32) as *i64
113 argv[0] = runner as i64
114 argv[1] = name as i64
115 argv[2] = 0
116 let envp: *i64 = sys_mmap(16) as *i64
117 envp[0] = 0
118 let ofd: i64 = sys_openat_append(outpath, 0x1a4)
119 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
120 sys_execve(runner, argv, envp)
121 sys_exit(127)
122 }
123 let st: *i64 = sys_mmap(16) as *i64
124 sys_wait4(pid, st, 0)
125 let sig: i64 = st[0] & 0x7f
126 if sig != 0 { return 128 + sig }
127 return (st[0] >> 8) & 0xff
128}
129
130// one full beat: every conf target through the lane; TGT rows + BEAT row.
131// returns worst rc (0 = all green).
132func adc_beat(confpath: *u8, outpath: *u8, logpath: *u8) -> i64 {
133 let arena: *u8 = sys_mmap(ADC_MAXTGT * ADC_NAMECAP)
134 let cnt: i64 = adc_targets(confpath, arena)
135 // DAEMON-SIL-R1 self-measurement (you cannot improve what you do not measure):
136 // time the beat + count regressions. improvements_landed/driven are emitted
137 // as 0 here BY DESIGN -- this daemon only RE-PROVES (rank-D); they become real
138 // when DAEMON-SIL-R3 wires the drive loop. Emitting them now makes the honest
139 // D baseline visible + trackable. All additive: existing fields/flow unchanged.
140 let t_start: i64 = sys_now_realtime_sec()
141 let lfd: i64 = sys_openat_append(logpath, 0x1a4)
142 var worst: i64 = 0
143 var regressions: i64 = 0
144 var i: i64 = 0
145 while i < cnt {
146 let nm: *u8 = (arena as i64 + i * ADC_NAMECAP) as *u8
147 let rc: i64 = adc_run_one(nm, outpath)
148 if rc > worst { worst = rc }
149 if rc != 0 { regressions = regressions + 1 }
150 if lfd >= 0 {
151 adc_w(lfd, "AUTORUN-TGT name=" as *u8)
152 adc_w(lfd, nm)
153 adc_w(lfd, " rc=" as *u8)
154 adc_wn(lfd, rc)
155 adc_w(lfd, "\n" as *u8)
156 }
157 i = i + 1
158 }
159 if lfd >= 0 {
160 adc_w(lfd, "AUTORUN-BEAT epoch=" as *u8)
161 adc_wn(lfd, sys_now_realtime_sec())
162 adc_w(lfd, " rung_rc=" as *u8)
163 adc_wn(lfd, worst)
164 adc_w(lfd, " targets=" as *u8)
165 adc_wn(lfd, cnt)
166 adc_w(lfd, " improvements_landed=0 driven=0 regressions=" as *u8)
167 adc_wn(lfd, regressions)
168 adc_w(lfd, " beat_cost_s=" as *u8)
169 adc_wn(lfd, sys_now_realtime_sec() - t_start)
170 if worst == 0 { adc_w(lfd, " verdict=GREEN\n" as *u8) } else { adc_w(lfd, " verdict=ATTENTION\n" as *u8) }
171 sys_close(lfd)
172 }
173 return worst
174}