code wiki / _hdl_build / nx_evidence_beat.nx
nx_evidence_beat.nx source
↩ module page · 274 lines · 14981 B
1// nx_evidence_beat.nx -- THE CADENCE. Closes the seq1433 half that turns this workstream from a one-shot
2// into an actual loop.
3//
4// ★WHY THIS IS THE KEYSTONE, NOT A CHORE. Everything built this session expires on purpose: evstamps carry
5// an epoch and are refused past the TTL, bite records likewise. That expiry is the whole point -- a verdict
6// that outlives its evidence is the defect this workstream opened by fixing. But expiry without RENEWAL is
7// just a slower way to reach zero: within one TTL every domain silently falls back to UNPROVEN and the
8// board returns to exactly the state it started in. A measurement you never repeat is a measurement you
9// will eventually be lying about.
10//
11// WHAT IT DOES, per invocation:
12// for each domain declaring executable evidence (knowledge/compare/<d>.gates)
13// * if its evstamp is missing or older than the REFRESH threshold -> re-run nx_swcompare_evidence <d>
14// * if any of its declared gates lacks a fresh bite record -> re-run nx_bite_sweep <d>
15//
16// ★REFRESH AT HALF THE TTL, not at expiry. Renewing only once a record has already died guarantees a window
17// in which the board reads UNPROVEN for domains that are fine -- the instrument would manufacture its own
18// false negatives on a timer. Half-life renewal means a single missed beat is survivable.
19//
20// ★BOUNDED WORK BY CONSTRUCTION. Each refresh forks real gates and, for a sweep, rebuilds source repeatedly;
21// an unbounded beat would run for hours and get killed mid-flight, leaving a mutated tree behind. maxwork
22// caps the number of refreshes per invocation and the beat reports what it DEFERRED rather than silently
23// truncating -- a cadence that hides its own backlog is how "green" drifts away from "current".
24//
25// usage: nx_evidence_beat [maxwork] [--dry] (CWD = nxc2 root)
26// exit 0 = nothing overdue, or all attempted work succeeded · 1 = a refresh failed · 2 = usage
27// license_tier: ORIGINAL
28import "nx_syscalls.nx"
29import "nx_artifact_root.nx"
30const EB_MAGIC_4096: i64 = 4096
31const EB_MAGIC_4095: i64 = 4095
32const EB_MAGIC_604800: i64 = 604800
33const EB_MAGIC_1024: i64 = 1024
34
35const EB_DEF_MAXWORK: i64 = 3
36
37func w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
38func wn(v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} let b: *u8=sys_mmap(28); var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
39func wf(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 }
40func wnf(fd: i64, v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} let b: *u8=sys_mmap(28); var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(fd,b,k); return 0 }
41
42func eb_read(path: *u8, buf: *u8, cap: i64) -> i64 {
43 let fd: i64 = sys_openat_rd(path)
44 if fd < 0 { return 0 - 1 }
45 var tot: i64 = 0
46 var go: i64 = 1
47 while go == 1 {
48 let n: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, cap - tot)
49 if n <= 0 { go = 0 } else { tot = tot + n; if tot >= cap { go = 0 } }
50 }
51 sys_close(fd)
52 return tot
53}
54func eb_num_after(buf: *u8, n: i64, key: *u8, kl: i64) -> i64 {
55 var i: i64 = 0
56 while i <= n - kl {
57 var j: i64 = 0
58 var m: i64 = 1
59 while j < kl { if buf[i+j] != key[j] { m = 0; j = kl } else { j = j + 1 } }
60 if m == 1 {
61 var p: i64 = i + kl
62 var v: i64 = 0
63 var got: i64 = 0
64 while p < n { let c: i64 = buf[p] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48); got = 1; p = p + 1 } else { p = n } } else { p = n } }
65 if got == 1 { return v }
66 return 0 - 1
67 }
68 i = i + 1
69 }
70 return 0 - 1
71}
72func eb_cat(dst: *u8, off: i64, s: *u8) -> i64 {
73 var o: i64 = off
74 var i: i64 = 0
75 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 }
76 return o
77}
78func eb_catn(dst: *u8, off: i64, domain: *u8, dl: i64) -> i64 {
79 var o: i64 = off
80 var i: i64 = 0
81 while i < dl { dst[o] = domain[i]; o = o + 1; i = i + 1 }
82 return o
83}
84
85// fork <elf> <arg1> [arg2]; return exit code
86func eb_run2(elf: *u8, a1: *u8, a2: *u8, outpath: *u8) -> i64 {
87 let pid: i64 = sys_fork()
88 if pid == 0 {
89 let ofd: i64 = sys_openat_wr(outpath, 0x1a4)
90 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
91 let argv: *i64 = sys_mmap(48) as *i64
92 argv[0] = elf as i64
93 argv[1] = a1 as i64
94 if (a2 as i64) == 0 { argv[2] = 0 } else { argv[2] = a2 as i64; argv[3] = 0 }
95 let envp: *i64 = sys_mmap(16) as *i64
96 envp[0] = 0
97 sys_execve(elf, argv, envp)
98 sys_exit(127)
99 }
100 let stp: *i64 = sys_mmap(16) as *i64
101 sys_wait4(pid, stp, 0)
102 let sig: i64 = stp[0] & 0x7f
103 if sig != 0 { return 128 + sig }
104 return (stp[0] >> 8) & 0xff
105}
106
107func main(argc: i64, argv: *i64) -> i64 {
108 var maxwork: i64 = EB_DEF_MAXWORK
109 var dry: i64 = 0
110 if argc > 1 {
111 let a: *u8 = argv[1] as *u8
112 if a[0] == (45 as u8) { dry = 1 } else {
113 maxwork = 0
114 var i: i64 = 0
115 while a[i] != (0 as u8) { maxwork = maxwork*10 + ((a[i] as i64) - 48); i = i + 1 }
116 if maxwork <= 0 { maxwork = EB_DEF_MAXWORK }
117 }
118 }
119 if argc > 2 { let b: *u8 = argv[2] as *u8; if b[0] == (45 as u8) { dry = 1 } }
120
121 let now: i64 = sys_now_realtime_sec()
122 // ONE policy, read from the same file every other consumer reads (rule 17).
123 let pb: *u8 = sys_mmap(EB_MAGIC_4096)
124 let pn: i64 = eb_read("knowledge/evidence_policy.conf" as *u8, pb, EB_MAGIC_4095)
125 var ttl: i64 = EB_MAGIC_604800
126 if pn > 0 { let v: i64 = eb_num_after(pb, pn, "ttl_sec=" as *u8, 8); if v > 0 { ttl = v } }
127 let refresh: i64 = ttl / 2
128
129 w("=== nx_evidence_beat -- renew what is about to expire (half-life refresh) ===\n")
130 w(" ttl="); wn(ttl); w("s refresh_at="); wn(refresh); w("s maxwork="); wn(maxwork)
131 if dry == 1 { w(" MODE=dry-run") }
132 w("\n")
133
134 let evelf: *u8 = sys_mmap(512)
135 let swelf: *u8 = sys_mmap(512)
136 let haveev: i64 = ar_resolve("_offc/nx_swcompare_evidence.elf" as *u8, evelf)
137 let havesw: i64 = ar_resolve("_offc/nx_bite_sweep.elf" as *u8, swelf)
138 if haveev == 0 { w(" CANNOT RESOLVE nx_swcompare_evidence.elf -- promote it first\n"); sys_exit(2); return 2 }
139
140 let dbuf: *u8 = sys_mmap(1 << 16)
141 let fd: i64 = sys_openat_rd("knowledge/compare\x00" as *u8)
142 if fd < 0 { w(" cannot open knowledge/compare\n"); sys_exit(2); return 2 }
143 let outp: *u8 = "knowledge/status/evidence_beat_run.out\x00" as *u8
144 let dom: *u8 = sys_mmap(256)
145 let path: *u8 = sys_mmap(512)
146 var scanned: i64 = 0
147 var refreshed: i64 = 0
148 var swept: i64 = 0
149 var deferred: i64 = 0
150 var failed: i64 = 0
151 var work: i64 = 0
152
153 var go: i64 = 1
154 while go == 1 {
155 let nr: i64 = sys_getdents64(fd, dbuf, 1 << 16)
156 if nr <= 0 { go = 0 } else {
157 var pos: i64 = 0
158 while pos < nr {
159 let rec: *u8 = ((dbuf as i64) + pos) as *u8
160 let reclen: i64 = dirent_reclen(rec)
161 let name: *u8 = ((rec as i64) + 19) as *u8
162 var ln: i64 = 0
163 while name[ln] != (0 as u8) { ln = ln + 1 }
164 // only domains that DECLARE executable evidence: <d>.gates
165 var isg: i64 = 0
166 if ln > 6 {
167 let g: *u8 = ".gates\x00" as *u8
168 var m: i64 = 1
169 var q: i64 = 0
170 while q < 6 { if name[ln-6+q] != g[q] { m = 0; q = 6 } else { q = q + 1 } }
171 isg = m
172 }
173 if isg == 1 {
174 let dl: i64 = ln - 6
175 var c: i64 = 0
176 while c < dl { dom[c] = name[c]; c = c + 1 }
177 dom[dl] = 0 as u8
178 scanned = scanned + 1
179 // age of this domain's evstamp
180 var o: i64 = eb_cat(path, 0, "knowledge/status/evstamp_" as *u8)
181 o = eb_catn(path, o, dom, dl)
182 o = eb_cat(path, o, ".verdict" as *u8)
183 path[o] = 0 as u8
184 let sb: *u8 = sys_mmap(EB_MAGIC_1024)
185 let sn: i64 = eb_read(path, sb, 1023)
186 var age: i64 = ttl + 1
187 if sn > 0 { let ep: i64 = eb_num_after(sb, sn, "epoch=" as *u8, 6); if ep > 0 { if now >= ep { age = now - ep } } }
188 if age > refresh {
189 // ★THE DRY RUN MUST SPEND THE SAME BUDGET. The first cut incremented the work
190 // counter only on the real path, so --dry listed EVERY overdue domain as if it
191 // would all be done in one pass -- a preview that disagrees with the run it is
192 // previewing is worse than no preview, because it is trusted.
193 if work >= maxwork {
194 deferred = deferred + 1
195 w(" DEFER "); w(dom); w(" age="); wn(age); w("s (budget spent)\n")
196 } else {
197 work = work + 1
198 w(" REFRESH "); w(dom); w(" age="); wn(age); w("s -> ")
199 if dry == 1 { w("(dry)\n") } else {
200 let rc: i64 = eb_run2(evelf, dom, 0 as *u8, outp)
201 if rc == 0 { w("MEASURED-HONEST\n") } else { w("RED (exit "); wn(rc); w(")\n"); failed = failed + 1 }
202 refreshed = refreshed + 1
203 // NON-VACUITY RENEWAL: re-read the stamp we just wrote. If fewer gates have
204 // a live bite record than the domain declares, its non-vacuity has aged out
205 // and the sweep must run -- otherwise the beat renews the cheap half of the
206 // evidence and quietly lets the expensive half expire.
207 if havesw == 1 {
208 let s2: *u8 = sys_mmap(EB_MAGIC_1024)
209 let n2: i64 = eb_read(path, s2, 1023)
210 if n2 > 0 {
211 let rs: i64 = eb_num_after(s2, n2, "redseen=" as *u8, 8)
212 let dc: i64 = eb_num_after(s2, n2, "declared=" as *u8, 9)
213 if dc > 0 { if rs < dc {
214 if work < maxwork {
215 work = work + 1
216 w(" SWEEP "); w(dom); w(" redseen="); wn(rs); w("/"); wn(dc); w(" -> ")
217 // 6 -> 16, MEASURED 2026-08-01, not guessed. nx_x25519_kat_gate
218 // returned INCONCLUSIVE at 4 sites and again at 6-equivalent
219 // depth; the KILL landed at site 4 of the NUMERIC-CONSTANT
220 // operator only once the cap allowed 14. Constant-time crypto
221 // exposes few mutable comparisons and its early numeric literals
222 // are structural (array sizes -> uncompilable), so the first
223 // handful of sites are systematically barren for exactly the
224 // KAT/ORACLE class this beat most needs to score.
225 // ★A SITE CAP IS A SAMPLING DECISION: TOO SMALL AND THE SWEEP
226 // MANUFACTURES "INCONCLUSIVE" AND CALLS IT A MEASUREMENT.
227 // Cost is bounded -- the sweep stops at the FIRST kill, so a
228 // gate that bites early still costs the same 1-2 rebuilds.
229 let rc2: i64 = eb_run2(swelf, dom, "16\x00" as *u8, outp)
230 if rc2 == 0 { w("all gates BITE\n") } else { w("not all proven (exit "); wn(rc2); w(")\n") }
231 swept = swept + 1
232 } else { deferred = deferred + 1; w(" DEFER "); w(dom); w(" sweep (budget spent)\n") }
233 } }
234 }
235 }
236 }
237 }
238 }
239 }
240 if reclen <= 0 { pos = nr } else { pos = pos + reclen }
241 }
242 }
243 }
244 sys_close(fd)
245
246 w(" scanned="); wn(scanned); w(" refreshed="); wn(refreshed); w(" deferred="); wn(deferred); w(" failed="); wn(failed); w("\n")
247 if havesw == 0 { w(" NOTE nx_bite_sweep.elf unresolved -- non-vacuity records will not be renewed by this beat\n") }
248
249 let lfd: i64 = sys_openat_wr("knowledge/status/evidence_beat.log\x00" as *u8, 0x1a4)
250 if lfd >= 0 {
251 wf(lfd, "NX-EVIDENCE-BEAT scanned=" as *u8); wnf(lfd, scanned)
252 wf(lfd, " refreshed=" as *u8); wnf(lfd, refreshed)
253 wf(lfd, " deferred=" as *u8); wnf(lfd, deferred)
254 wf(lfd, " failed=" as *u8); wnf(lfd, failed)
255 wf(lfd, " ttl=" as *u8); wnf(lfd, ttl)
256 // ts= is the DEAD-MAN'S-SWITCH marker knowledge/registry/cron_heartbeats.tsv looks for. Emitting
257 // only `epoch=` would leave the watch permanently stale even while the beat ran perfectly -- the
258 // heartbeat would report a dead cadence that is actually alive, which is the same false-negative
259 // class as every other reader/writer dialect mismatch this session. Both are written: ts= for the
260 // watch, epoch= for anything reading this log the way the stamps are read.
261 wf(lfd, " ts=" as *u8); wnf(lfd, now)
262 wf(lfd, " epoch=" as *u8); wnf(lfd, now)
263 wf(lfd, "\n" as *u8)
264 // DEFERRED WORK IS NOT A FAILURE, but it must be VISIBLE: a beat that silently drops its backlog
265 // lets the board age out one domain at a time while every log line still reads GREEN.
266 if failed == 0 { wf(lfd, "VERDICT=GREEN evidence renewed within its half-life\n" as *u8) } else { wf(lfd, "VERDICT=RED a refresh failed -- a domain's evidence could not be re-measured\n" as *u8) }
267 sys_close(lfd)
268 }
269
270 if failed == 0 { w("VERDICT: verdict=GREEN (nothing overdue, or all attempted refreshes succeeded)\n"); sys_exit(0); return 0 }
271 w("VERDICT: verdict=RED (a refresh failed)\n")
272 sys_exit(1)
273 return 1
274}