code wiki / _hdl_build / nx_cron_reconcile.nx
nx_cron_reconcile.nx source
↩ module page · 227 lines · 10129 B
1// nx_cron_reconcile.nx -- SOVEREIGN crontab reconciler (operator directive 2026-07-16: "make the
2// capabilities native to the nishi ecosystem, don't make me do it"). The nishi-managed block of a crontab
3// becomes DATA: cron.reg declares exactly the nishi rows that must exist; this organ rewrites the crontab
4// so its nishi rows (any line containing /nishihost/) equal EXACTLY the declared set, leaving every
5// non-nishi row BYTE-EXACT. Dead rows (retired reconcilers) vanish by construction; new rows = a registry
6// line. Runs as root with ZERO operator action by piggybacking on the existing root cron row
7// (nx_edge443_reconcile.sh -- the same proven pattern as the vsz_watchdog on the rail).
8//
9// NEVER-BRICK BY CONSTRUCTION (this writes a root-owned BOOT FILE):
10// R1 only lines containing /nishihost/ are ever touched -- the filter IS the blast-radius bound
11// R2 refuse (exit 3, no write) if the registry is missing/empty -- an absent SSOT must never wipe rows
12// R3 refuse (exit 5, no write) if ANY declared row lacks /nishihost/ -- the organ cannot inject
13// non-nishi rows even if the registry is corrupted/hostile
14// R4 refuse (exit 4, no write) if the crontab is unreadable or EMPTY -- a boot file is never empty
15// R5 idempotent: byte-identical result -> NO-CHANGE, zero writes (safe at any frequency)
16// R6 atomic: write <crontab>.nxnew then renameat over -- no torn boot file ever exists on disk
17// After a real change, SIGHUP every `crond` (reload -- standard, non-destructive; if crond ignores it,
18// stale in-memory entries keep harmlessly firing removed paths until its next restart = no regression).
19//
20// usage: nx_cron_reconcile <crontab-path> <reg-path> (gate runs it on COPIES; prod = /etc/crontab)
21// exit: 0 ok (CHANGED or NO-CHANGE) | 2 usage | 3 reg-refuse | 4 crontab-refuse | 5 reg-taint | 6 write-fail
22// license_tier: ORIGINAL module: nishi-core.ops.cron_reconcile
23import "nx_syscalls.nx"
24const CR_MAGIC_65536: i64 = 65536
25
26const CR_CTCAP: i64 = 65536
27const CR_REGCAP: i64 = 16384
28const CR_MARK: *u8 = "/nishihost/" as *u8
29
30func cr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
31func cr_p(s: *u8) -> i64 { sys_write(1, s, cr_slen(s)); return 0 }
32func cr_decw(v: i64, out: *u8) -> i64 {
33 var o: i64 = 0
34 var m: i64 = v
35 if m < 0 { out[0] = 45 as u8; o = 1; m = 0 - m }
36 if m == 0 { out[o] = 48 as u8; return o + 1 }
37 var nd: i64 = 1
38 var mm: i64 = m
39 while mm >= 10 { nd = nd + 1; mm = mm / 10 }
40 var i: i64 = nd - 1
41 while m > 0 { out[o + i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 }
42 return o + nd
43}
44func cr_pn(v: i64) -> i64 { let b: *u8 = sys_mmap(28); let n: i64 = cr_decw(v, b); sys_write(1, b, n); sys_munmap(b, 28); return 0 }
45func cr_contains(hay: *u8, hlen: i64, needle: *u8) -> i64 {
46 let nlen: i64 = cr_slen(needle)
47 if hlen < nlen { return 0 }
48 var s: i64 = 0
49 let last: i64 = hlen - nlen
50 while s <= last {
51 var m: i64 = 0
52 var eq: i64 = 1
53 while m < nlen { if hay[s + m] != needle[m] { eq = 0; m = nlen } m = m + 1 }
54 if eq == 1 { return 1 }
55 s = s + 1
56 }
57 return 0
58}
59func cr_line_end(buf: *u8, pos: i64, end: i64) -> i64 {
60 var p: i64 = pos
61 while p < end { if (buf[p] as i64) == 10 { return p } p = p + 1 }
62 return end
63}
64func cr_read(path: *u8, buf: *u8, cap: i64) -> i64 {
65 let fd: i64 = sys_openat_rd(path)
66 if fd < 0 { return 0 - 1 }
67 let n: i64 = sys_read(fd, buf, cap - 1)
68 sys_close(fd)
69 return n
70}
71
72func main(argc: i64, argv: *i64) -> i64 {
73 if argc < 3 { cr_p("usage: nx_cron_reconcile <crontab-path> <reg-path>\n" as *u8); return 2 }
74 let ctpath: *u8 = argv[1] as *u8
75 let regpath: *u8 = argv[2] as *u8
76 let ct: *u8 = sys_mmap(CR_CTCAP)
77 let reg: *u8 = sys_mmap(CR_REGCAP)
78 let out: *u8 = sys_mmap(CR_CTCAP + CR_REGCAP)
79 let np: *u8 = sys_mmap(512)
80
81 // R4: crontab must exist and be non-empty
82 let cn: i64 = cr_read(ctpath, ct, CR_CTCAP)
83 if cn <= 0 { cr_p("REFUSED: crontab unreadable/empty (a boot file is never empty) -- no write\n" as *u8); return 4 }
84 // R2: registry must exist with >=1 declared row; R3: every row must carry the nishi mark
85 let rn: i64 = cr_read(regpath, reg, CR_REGCAP)
86 if rn <= 0 { cr_p("REFUSED: cron.reg missing/empty -- an absent SSOT never wipes rows\n" as *u8); return 3 }
87 var decl: i64 = 0
88 var pos: i64 = 0
89 while pos < rn {
90 let le: i64 = cr_line_end(reg, pos, rn)
91 if le > pos { if (reg[pos] as i64) != 35 { if (reg[pos] as i64) != 13 {
92 if cr_contains((((reg as i64) + pos)) as *u8, le - pos, CR_MARK) == 0 {
93 cr_p("REFUSED: declared row lacks /nishihost/ (registry taint -- this organ cannot manage non-nishi rows)\n" as *u8)
94 return 5
95 }
96 decl = decl + 1
97 } } }
98 pos = le + 1
99 }
100 if decl == 0 { cr_p("REFUSED: cron.reg has no declared rows -- no write\n" as *u8); return 3 }
101
102 // build: non-nishi crontab lines byte-exact, then the declared block
103 var w: i64 = 0
104 var removed: i64 = 0
105 pos = 0
106 while pos < cn {
107 let le2: i64 = cr_line_end(ct, pos, cn)
108 if cr_contains((((ct as i64) + pos)) as *u8, le2 - pos, CR_MARK) == 1 { removed = removed + 1 }
109 else {
110 var k: i64 = pos
111 while k < le2 { out[w] = ct[k]; w = w + 1; k = k + 1 }
112 out[w] = 10 as u8
113 w = w + 1
114 }
115 pos = le2 + 1
116 }
117 pos = 0
118 while pos < rn {
119 let le3: i64 = cr_line_end(reg, pos, rn)
120 if le3 > pos { if (reg[pos] as i64) != 35 { if (reg[pos] as i64) != 13 {
121 var k2: i64 = pos
122 while k2 < le3 { out[w] = reg[k2]; w = w + 1; k2 = k2 + 1 }
123 out[w] = 10 as u8
124 w = w + 1
125 } } }
126 pos = le3 + 1
127 }
128
129 // R5: idempotence -- byte-identical means zero writes
130 var same: i64 = 0
131 if w == cn {
132 same = 1
133 var ci: i64 = 0
134 while ci < cn { if out[ci] != ct[ci] { same = 0; ci = cn } ci = ci + 1 }
135 }
136 if same == 1 {
137 cr_p("NO-CHANGE (crontab already matches cron.reg; declared=" as *u8)
138 cr_pn(decl)
139 cr_p(")\n" as *u8)
140 return 0
141 }
142
143 // R6: atomic write -- <path>.nxnew then rename over
144 var npw: i64 = 0
145 var pi: i64 = 0
146 while ctpath[pi] != (0 as u8) { np[npw] = ctpath[pi]; npw = npw + 1; pi = pi + 1 }
147 let sfx: *u8 = ".nxnew" as *u8
148 var si: i64 = 0
149 while sfx[si] != (0 as u8) { np[npw] = sfx[si]; npw = npw + 1; si = si + 1 }
150 np[npw] = 0 as u8
151 let wfd: i64 = sys_openat_wr(np, 420)
152 if wfd < 0 { cr_p("WRITE-FAIL: cannot create .nxnew (permission?) -- crontab untouched\n" as *u8); return 6 }
153 var off: i64 = 0
154 while off < w {
155 let wr: i64 = sys_write(wfd, (((out as i64) + off)) as *u8, w - off)
156 if wr <= 0 { sys_close(wfd); cr_p("WRITE-FAIL: short write on .nxnew -- crontab untouched\n" as *u8); return 6 }
157 off = off + wr
158 }
159 sys_close(wfd)
160 if sys_renameat(np, ctpath) != 0 { cr_p("WRITE-FAIL: rename over crontab failed -- crontab untouched\n" as *u8); return 6 }
161 cr_p("CHANGED: nishi-rows removed=" as *u8)
162 cr_pn(removed)
163 cr_p(" declared=" as *u8)
164 cr_pn(decl)
165 cr_p(" bytes " as *u8)
166 cr_pn(cn)
167 cr_p("->" as *u8)
168 cr_pn(w)
169 cr_p("\n" as *u8)
170
171 // SIGHUP crond so the in-memory schedule reloads (safe standard reload; no-op if none found)
172 let dfd: i64 = sys_openat_rd("/proc\x00" as *u8)
173 if dfd >= 0 {
174 let dbuf: *u8 = sys_mmap(CR_MAGIC_65536)
175 let cpath: *u8 = sys_mmap(64)
176 let cbuf: *u8 = sys_mmap(32)
177 var hup: i64 = 0
178 var reading: i64 = 1
179 while reading == 1 {
180 let dn: i64 = sys_getdents64(dfd, dbuf, CR_MAGIC_65536)
181 if dn <= 0 { reading = 0 }
182 else {
183 var doff: i64 = 0
184 while doff < dn {
185 let recp: *u8 = (((dbuf as i64) + doff)) as *u8
186 let reclen: i64 = dirent_reclen(recp)
187 let nm: *u8 = dirent_name(recp)
188 if nm[0] >= (48 as u8) { if nm[0] <= (57 as u8) {
189 var qi: i64 = 0
190 let pre: *u8 = "/proc/" as *u8
191 var ppi: i64 = 0
192 while pre[ppi] != (0 as u8) { cpath[ppi] = pre[ppi]; ppi = ppi + 1 }
193 while nm[qi] != (0 as u8) { cpath[ppi] = nm[qi]; ppi = ppi + 1; qi = qi + 1 }
194 let suf: *u8 = "/comm" as *u8
195 var ssi: i64 = 0
196 while suf[ssi] != (0 as u8) { cpath[ppi] = suf[ssi]; ppi = ppi + 1; ssi = ssi + 1 }
197 cpath[ppi] = 0 as u8
198 let cfd: i64 = sys_openat_rd(cpath)
199 if cfd >= 0 {
200 let cl: i64 = sys_read(cfd, cbuf, 31)
201 sys_close(cfd)
202 // comm == "crond\n"
203 if cl >= 5 { if cbuf[0]==(99 as u8) { if cbuf[1]==(114 as u8) { if cbuf[2]==(111 as u8) { if cbuf[3]==(110 as u8) { if cbuf[4]==(100 as u8) {
204 var tail_ok: i64 = 0
205 if cl == 5 { tail_ok = 1 }
206 if cl == 6 { if (cbuf[5] as i64) == 10 { tail_ok = 1 } }
207 if tail_ok == 1 {
208 var pid: i64 = 0
209 var di: i64 = 0
210 while nm[di] != (0 as u8) { pid = pid * 10 + ((nm[di] as i64) - 48); di = di + 1 }
211 nx_kill(pid, 1)
212 hup = hup + 1
213 }
214 } } } } } }
215 }
216 } }
217 doff = doff + reclen
218 }
219 }
220 }
221 sys_close(dfd)
222 cr_p("crond HUP'd: " as *u8)
223 cr_pn(hup)
224 cr_p("\n" as *u8)
225 }
226 return 0
227}