code wiki / _hdl_build / nx_autorun_daemon.nx
nx_autorun_daemon.nx source
↩ module page · 173 lines · 8710 B
1// nx_autorun_daemon.nx -- THE SLOW BEAT (was: math-only; X-Q-007 made the
2// target list DATA): each beat re-proves every conf-listed target through
3// the durable lane. Kept OFF the 30-min Conductor pulse on purpose (rule
4// 21: the pulse stays cheap and reads evidence; THIS daemon renews it).
5// - interval from knowledge/status/autorun_daemon.conf (I row)
6// - targets from the SAME conf (T <basename> rows, re-read EVERY beat so
7// conf edits apply without re-arm; no T rows = the historical
8// nx_math_rung_run default) -- policy in nx_ad_core (gate-driven)
9// - beat output -> autorun_daemon.out; AUTORUN-TGT row per target + ONE
10// AUTORUN-BEAT row (rung_rc= kept as worst-rc for old readers)
11// - self-daemonizes; duplicate guard via autorun_daemon.pid + /proc cmdline
12// Sovereign (syscalls only, no .sh). Run from nxc2 root. license_tier: ORIGINAL
13// (nx_syscalls arrives via nx_ad_core -- a direct second import alongside it
14// produced a deterministic NXASM rc=6 on this file; dedup fixed it)
15import "nx_ad_core.nx"
16const SYS_MAGIC_4096: i64 = 4096
17const SYS_MAGIC_4080: i64 = 4080
18func _p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
19func _pn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 }
20func _fp(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 }
21func _fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
22func ad_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
23func ad_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i }
24func ad_read_all(path: *u8, buf: *u8, cap: i64) -> i64 {
25 let fd: i64 = sys_openat_rd(path)
26 if fd < 0 { return 0 }
27 var n: i64 = 0
28 var go: i64 = 1
29 while go == 1 { let base: i64 = buf as i64; let r: i64 = sys_read(fd, (base + n) as *u8, cap - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } }
30 sys_close(fd)
31 return n
32}
33func ad_contains(hay: *u8, n: i64, pat: *u8) -> i64 {
34 let pl: i64 = ad_len(pat)
35 if pl == 0 { return 0 }
36 var i: i64 = 0
37 while i + pl <= n {
38 var k: i64 = 0
39 var hit: i64 = 1
40 while k < pl { if hay[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
41 if hit == 1 { return 1 }
42 i = i + 1
43 }
44 return 0
45}
46func ad_sleep(secs: i64) -> i64 {
47 let ts: *i64 = sys_mmap(16) as *i64
48 ts[0] = secs
49 ts[1] = 0
50 return __syscall(SYS_CLOCK_NANOSLEEP, 0, 0, ts, 0, 0, 0)
51}
52func ad_interval() -> i64 {
53 let buf: *u8 = sys_mmap(SYS_MAGIC_4096)
54 let n: i64 = ad_read_all("knowledge/status/autorun_daemon.conf" as *u8, buf, SYS_MAGIC_4080)
55 var i: i64 = 0
56 while i < n {
57 var le: i64 = i
58 var scan: i64 = 1
59 while scan == 1 {
60 if le >= n { scan = 0 }
61 else { if buf[le] == (10 as u8) { scan = 0 } else { le = le + 1 } }
62 }
63 if buf[i] == (73 as u8) {
64 var v: i64 = 0
65 var q: i64 = i + 2
66 while q < le {
67 if buf[q] >= (48 as u8) { if buf[q] <= (57 as u8) { v = v * 10 + ((buf[q] as i64) - 48) } }
68 q = q + 1
69 }
70 return v
71 }
72 i = le + 1
73 }
74 return 0
75}
76func ad_already_running() -> i64 {
77 let pbuf: *u8 = sys_mmap(64)
78 let pn: i64 = ad_read_all("knowledge/status/autorun_daemon.pid" as *u8, pbuf, 32)
79 if pn <= 0 { return 0 }
80 let path: *u8 = sys_mmap(256)
81 var o: i64 = 0
82 o = ad_cat(path, o, "/proc/" as *u8)
83 var i: i64 = 0
84 while i < pn { if pbuf[i] >= (48 as u8) { if pbuf[i] <= (57 as u8) { path[o] = pbuf[i]; o = o + 1 } } i = i + 1 }
85 o = ad_cat(path, o, "/cmdline" as *u8); path[o] = 0 as u8
86 let cbuf: *u8 = sys_mmap(512)
87 let cn: i64 = ad_read_all(path, cbuf, 500)
88 if cn <= 0 { return 0 }
89 return ad_contains(cbuf, cn, "nx_autorun_daemon" as *u8)
90}
91// ---- EVENT-DRIVEN trigger (replaces the time-based sleep): a sovereign FIFO.
92// The daemon blocks in read() on the FIFO -> ZERO compute until a producer pokes
93// it (nx_autorun_poke, a git post-commit, or the loop after authoring). inotify is
94// dead on /mnt/c (drvfs, empirically confirmed), so a FIFO on ext4 /tmp is the
95// reliable channel. "event driven not time": no clock, beats only on real events.
96const SYS_MKNODAT: i64 = 259 // x86-64 (the daemon runs on the WSL x86-64 host)
97const S_IFIFO_0666: i64 = 4534 // S_IFIFO (0o010000=4096) | 0o666 (438)
98const O_RDWR_FLAG: i64 = 2 // open the FIFO read+write so the daemon is always
99 // also a writer -> blocking read() never sees EOF
100
101// read the event-channel FIFO path from conf (F row); default if absent (rule 11).
102func ad_fifo_path(out: *u8) -> i64 {
103 let buf: *u8 = sys_mmap(SYS_MAGIC_4096)
104 let n: i64 = ad_read_all("knowledge/status/autorun_daemon.conf" as *u8, buf, SYS_MAGIC_4080)
105 var i: i64 = 0
106 while i < n {
107 var le: i64 = i
108 var scan: i64 = 1
109 while scan == 1 { if le >= n { scan = 0 } else { if buf[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } }
110 if buf[i] == (70 as u8) {
111 var o: i64 = 0
112 var q: i64 = i + 2
113 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } }
114 out[o] = 0 as u8
115 if o > 0 { return 1 }
116 }
117 i = le + 1
118 }
119 let d: i64 = ad_cat(out, 0, "/tmp/nx_autorun.fifo" as *u8); out[d] = 0 as u8
120 return 0
121}
122
123// ensure the FIFO exists (idempotent; EEXIST harmless) and open it blocking R/W.
124func ad_open_event(path: *u8) -> i64 {
125 let mkr: i64 = __syscall(SYS_MKNODAT, AT_FDCWD, path as i64, S_IFIFO_0666, 0, 0, 0)
126 return __syscall(SYS_OPENAT, AT_FDCWD, path as i64, O_RDWR_FLAG, 0, 0, 0)
127}
128
129func ad_beat() -> i64 {
130 return adc_beat("knowledge/status/autorun_daemon.conf" as *u8,
131 "knowledge/status/autorun_daemon.out" as *u8,
132 "knowledge/status/autorun_daemon.log" as *u8)
133}
134func main() -> i64 {
135 let fifo: *u8 = sys_mmap(256)
136 ad_fifo_path(fifo)
137 // ATOMIC dup-guard (was: racy ad_already_running pidfile+/proc check -- a TOCTOU hole
138 // between fork and pidfile-write let two daemons coexist, hit 2026-06-13). O_EXCL create
139 // of a /tmp lockfile is ATOMIC: only ONE concurrent creator wins, the rest get EEXIST.
140 // Crash-staleness (the lockfile persists after a kill/crash) is reclaimed via the SAME
141 // /proc-liveness check the old guard used: pidfile owner alive -> real duplicate, exit;
142 // owner dead -> stale -> unlink + recreate (preserves the original auto-recovery).
143 let lockpath: *u8 = "/tmp/nx_autorun_daemon.lock" as *u8
144 var lockfd: i64 = ad_acquire_lock(lockpath)
145 if lockfd < 0 {
146 if ad_already_running() == 1 { _p("nx_autorun_daemon: ALREADY-RUNNING (pidfile live) -- not starting a duplicate\n" as *u8); sys_exit(0); return 0 }
147 __syscall(263, AT_FDCWD, lockpath as i64, 0, 0, 0, 0) // unlinkat: reclaim stale lock (x86-64 263 passthrough)
148 lockfd = ad_acquire_lock(lockpath)
149 if lockfd < 0 { _p("nx_autorun_daemon: ALREADY-RUNNING (reclaim race) -- not starting a duplicate\n" as *u8); sys_exit(0); return 0 }
150 }
151 let pid: i64 = sys_fork()
152 if pid != 0 {
153 _p("nx_autorun_daemon: ARMED pid=" as *u8); _pn(pid); _p(" mode=EVENT fifo=" as *u8); _p(fifo); _p(" (no timer; beats fire on poke -> knowledge/status/autorun_daemon.log)\n" as *u8)
154 let pfd: i64 = sys_openat_wr("knowledge/status/autorun_daemon.pid" as *u8, 0x1a4)
155 if pfd >= 0 { _fn(pfd, pid); sys_close(pfd) }
156 sys_exit(0)
157 return 0
158 }
159 nx_setsid()
160 let efd: i64 = ad_open_event(fifo)
161 if efd < 0 { sys_exit(1); return 1 }
162 // EVENT LOOP: block on the FIFO; each poke (>=1 byte) drives exactly one
163 // re-prove beat (adc_beat writes the AUTORUN-TGT rows + the AUTORUN-BEAT row).
164 // The blocking read is the whole point -- no busy-poll, no clock (rule 21).
165 let rbuf: *u8 = sys_mmap(64)
166 var go: i64 = 1
167 while go == 1 {
168 let r: i64 = sys_read(efd, rbuf, 64)
169 if r <= 0 { go = 0 } else { ad_beat() }
170 }
171 sys_exit(0)
172 return 0
173}