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}