code wiki / (root) / nx_stale_check.nx

nx_stale_check.nx source

↩ module page · 229 lines · 14844 B

1// nx_stale_check.nx -- SAFE detect-only companion to nx_restage: is a build tree's _offc/<target>.elf 2// current vs its source? Rebuilds <target> --build-only + byte-compares to the DEPLOYED elf. DETECT-ONLY 3// (never writes _offc) so it can never break a tool -- the safe half of the deploy-drift loop. 4// GUARDS the _cli/stub trap (LESSON 2026-07-17: a same-basename .nx can be a do-nothing SMOKE stub while 5// the real tool was built from <target>_cli.nx -- a naive byte-diff then FALSE-flags STALE and restaging 6// the stub breaks the tool): if the fresh elf is far SMALLER than the deployed one, reports AMBIGUOUS 7// (source likely wrong) rather than STALE. 8// nx_stale_check <target> [broot=buildroot] 9// -> CURRENT (byte-identical) | STALE (differ, comparable size) | AMBIGUOUS (fresh<<deployed = stub src) 10// | NOSOURCE | BUILDFAIL | NOELF 11// argv[1]=target [A-Za-z0-9_], argv[2]=broot. Exit 0 CURRENT | 1 STALE | 2 AMBIGUOUS/error | 3 usage. 12// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 13import "nx_tool_run.nx" 14const K_MAGIC_262144: i64 = 262144 15const K_MAGIC_8388608: i64 = 8388608 16const K_MAGIC_4096: i64 = 4096 17 18func sp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19func spe(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(2,s,n); return 0 } 20func snum(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} let t: *u8=sys_mmap(28); 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} var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 } 21func s_cat(dst: *u8, off: i64, src: *u8) -> i64 { var o: i64=off; var j: i64=0; while src[j]!=(0 as u8){dst[o]=src[j];o=o+1;j=j+1} return o } 22func s_san(name: *u8) -> i64 { 23 var i: i64 = 0 24 while name[i] != (0 as u8) { 25 let c: i64 = name[i] as i64 26 var ok: i64 = 0 27 if c >= 48 { if c <= 57 { ok = 1 } } 28 if c >= 65 { if c <= 90 { ok = 1 } } 29 if c >= 97 { if c <= 122 { ok = 1 } } 30 if c == 95 { ok = 1 } 31 if ok == 0 { return 0 } 32 i = i + 1 33 if i >= 64 { return 0 } 34 } 35 if i == 0 { return 0 } 36 return 1 37} 38// read whole file into buf (cap); returns byte count or -1 if unreadable. 39func s_read(path: *u8, buf: *u8, cap: i64) -> i64 { 40 let fd: i64 = sys_openat_rd(path) 41 if fd < 0 { return 0 - 1 } 42 var n: i64 = 0 43 var go: i64 = 1 44 while go == 1 { let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } } 45 sys_close(fd) 46 return n 47} 48func s_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 } 49 50// ---- CAPABILITY-PRESENCE (2026-07-31, debts 1785517485 / 1785518612 / 1785525614) ---- 51// MECHANISES THE CHECK THE DIRECTION GUARD BELOW ALREADY PRESCRIBES BY HAND: "look for behaviour or 52// output strings its source cannot produce". That guard's signal is SIZE ONLY, and the warden proves 53// the blind spot: nx_law_warden rebuilds to 185172B vs a 161951B deployment, so `fn < dn` is FALSE, 54// it falls through to STALE, and it prescribes the rebuild -- while the DEPLOYED binary is the only 55// copy of gatedry/helperdup/scancap (L009/L010/L011), which exist in NO source in either tree. 56// A BINARY CAN GROW WHILE LOSING CAPABILITY, so size can never separate the two populations. 57// Both images are already resident (fb/fn, db/dn) so this costs no extra IO. 58const SC_MINRUN: i64 = 12 // shortest printable run treated as a capability string 59const SC_MAXSTR: i64 = 4000 // bounded work; coverage is DECLARED, never presented as complete (L011) 60const SC_SHOW: i64 = 3 // exemplar misses printed so a human can judge, not just a count 61const SC_ASCII_LO: i64 = 32 62const SC_ASCII_HI: i64 = 126 63const SC_EXIT_CAPLOSS: i64 = 4 // distinct from STALE(1)/AMBIGUOUS+DEPLOYED-AHEAD(2)/usage(3) 64func sc_print(c: i64) -> i64 { if c < SC_ASCII_LO { return 0 } if c > SC_ASCII_HI { return 0 } return 1 } 65func sc_has(hay: *u8, hn: i64, ndl: *u8, at: i64, len: i64) -> i64 { 66 if len <= 0 { return 1 } 67 if len > hn { return 0 } 68 var i: i64 = 0 69 while i + len <= hn { 70 var k: i64 = 0 71 var hit: i64 = 1 72 while k < len { if hay[i+k] != ndl[at+k] { hit = 0; k = len } else { k = k + 1 } } 73 if hit == 1 { return 1 } 74 i = i + 1 75 } 76 return 0 77} 78 79func main(argc: i64, argv: *i64) -> i64 { 80 if argc < 2 { spe("usage: nx_stale_check <target> [buildroot]\n" as *u8); sys_exit(3); return 3 } 81 let target: *u8 = argv[1] as *u8 82 var broot: *u8 = "buildroot" as *u8 83 if argc >= 3 { broot = argv[2] as *u8 } 84 if s_san(target) == 0 { spe("nx_stale_check: REFUSED bad target\n" as *u8); sys_exit(3); return 3 } 85 if sys_chdir(broot) != 0 { spe("nx_stale_check: cannot chdir buildroot\n" as *u8); sys_exit(2); return 2 } 86 87 // source present? runtime/<t>.nx or runtime/_hdl_build/<t>.nx 88 let s1: *u8 = sys_mmap(256); var o1: i64 = s_cat(s1, 0, "runtime/" as *u8); o1 = s_cat(s1, o1, target); o1 = s_cat(s1, o1, ".nx" as *u8); s1[o1] = 0 as u8 89 let s2: *u8 = sys_mmap(256); var o2: i64 = s_cat(s2, 0, "runtime/_hdl_build/" as *u8); o2 = s_cat(s2, o2, target); o2 = s_cat(s2, o2, ".nx" as *u8); s2[o2] = 0 as u8 90 if s_exists(s1) == 0 { if s_exists(s2) == 0 { sp("NOSOURCE " as *u8); sp(target); sp(" (no runtime/" as *u8); sp(target); sp(".nx)\n" as *u8); sys_exit(2); return 2 } } 91 92 // build --build-only -> /tmp/<t>.sov.elf 93 let av: *i64 = sys_mmap(64) as *i64 94 av[0] = "_offc/nx_sov_build_run.elf" as i64 95 av[1] = target as i64 96 av[2] = "--build-only" as i64 97 av[3] = 0 98 let cap: i64 = K_MAGIC_262144 99 let out: *u8 = sys_mmap(cap) 100 let ol: *i64 = sys_mmap(16) as *i64 101 let rc: i64 = tr_run_capture("_offc/nx_sov_build_run.elf" as *u8, av, out, cap, ol) 102 let fresh: *u8 = sys_mmap(256); var of: i64 = s_cat(fresh, 0, "/tmp/" as *u8); of = s_cat(fresh, of, target); of = s_cat(fresh, of, ".sov.elf" as *u8); fresh[of] = 0 as u8 103 // seq1554 FIX -- the SAME matched-pair resolution the deployed-elf lookup below already does. 104 // The build lane emits to buildroot/_build/<t>.sov.elf on this host, not /tmp (the /api/build 105 // failure diag probes BOTH). Probing ONE location made this guard answer BUILDFAIL for EVERY 106 // target, so the instrument that gates a risky promote was DEAD -- and its error read like a 107 // verdict rather than an abstention. A guard that cannot find what it guards is not a guard. 108 if s_exists(fresh) == 0 { var of2: i64 = s_cat(fresh, 0, "_build/" as *u8); of2 = s_cat(fresh, of2, target); of2 = s_cat(fresh, of2, ".sov.elf" as *u8); fresh[of2] = 0 as u8 } 109 if rc != 0 { sp("BUILDFAIL " as *u8); sp(target); sp("\n" as *u8); sys_exit(2); return 2 } 110 111 let dep: *u8 = sys_mmap(256); var od: i64 = s_cat(dep, 0, "_offc/" as *u8); od = s_cat(dep, od, target); od = s_cat(dep, od, ".elf" as *u8); dep[od] = 0 as u8 112 // seq259 FIX: a GUARD-SUPERVISED DAEMON deploys to the nishihost ROOT, not the buildroot _offc/ 113 // toolchain dir -- so the whole daemon fleet used to return NOELF and this detector SILENTLY 114 // ABSTAINED (and a NOELF misread as BUILDFAIL is exactly how debt seq120 sat wrong for 2 days). 115 // Try _offc/ first, then ../<t>.elf (nishihost root); the compared path is REPORTED on stderr. 116 if s_exists(dep) == 0 { var od2: i64 = s_cat(dep, 0, "../" as *u8); od2 = s_cat(dep, od2, target); od2 = s_cat(dep, od2, ".elf" as *u8); dep[od2] = 0 as u8 } 117 // seq1554 THIRD FORM: guard-supervised daemons are deployed to the nishihost root WITHOUT the 118 // .elf suffix (measured: the live supervisor is `nishihost/nx_hostctl`, no extension, alongside 119 // nx_hostctl.prev / .old). Probing only the two SUFFIXED forms made this detector abstain NOELF 120 // on exactly the fleet whose promotes are most dangerous. Same meta-defect as the notation miss 121 // that hid the .193 launcher: ONE ARTIFACT, SEVERAL NAMING CONVENTIONS, a probe that knew one. 122 if s_exists(dep) == 0 { var od3: i64 = s_cat(dep, 0, "../" as *u8); od3 = s_cat(dep, od3, target); dep[od3] = 0 as u8 } 123 124 let fcap: i64 = K_MAGIC_8388608 // 8MB per elf 125 let fb: *u8 = sys_mmap(fcap) 126 let db: *u8 = sys_mmap(fcap) 127 let fn: i64 = s_read(fresh, fb, fcap) 128 let dn: i64 = s_read(dep, db, fcap) 129 if fn < 0 { sp("BUILDFAIL " as *u8); sp(target); sp(" (no fresh elf at /tmp/" as *u8); sp(target); sp(".sov.elf NOR _build/" as *u8); sp(target); sp(".sov.elf)\n" as *u8); sys_exit(2); return 2 } 130 if dn < 0 { sp("NOELF " as *u8); sp(target); sp(" (no deployed elf at _offc/" as *u8); sp(target); sp(".elf NOR nishihost-root " as *u8); sp(target); sp(".elf)\n" as *u8); sys_exit(2); return 2 } 131 spe("cmp=" as *u8); spe(dep); spe("\n" as *u8) 132 133 // byte-identical? 134 var same: i64 = 1 135 if fn != dn { same = 0 } else { 136 var i: i64 = 0 137 while i < fn { if fb[i] != db[i] { same = 0; i = fn } else { i = i + 1 } } 138 } 139 if same == 1 { sp("CURRENT " as *u8); sp(target); sp(" (" as *u8); snum(fn); sp("B match)\n" as *u8); sys_exit(0); return 0 } 140 141 // STUB-TRAP GUARD: fresh far smaller than deployed => the same-basename source is likely a stub, 142 // the real tool was built from a differently-named source. Report AMBIGUOUS not STALE. 143 if fn * 2 < dn { if fn < K_MAGIC_4096 { 144 sp("AMBIGUOUS " as *u8); sp(target); sp(" (fresh " as *u8); snum(fn); sp("B << deployed " as *u8); snum(dn); sp("B: runtime/" as *u8); sp(target); sp(".nx is likely a STUB; real source elsewhere e.g. " as *u8); sp(target); sp("_cli.nx -- do NOT restage from this target)\n" as *u8) 145 sys_exit(2); return 2 146 } } 147 148 // ★DIRECTION GUARD (added 2026-07-26; debt seq1008 + F1145). THE DEFECT THIS CURES: 149 // a byte-compare detects that source and deployment DISAGREE but is structurally blind to WHICH 150 // ONE IS AHEAD -- and the old code called every disagreement STALE, whose remedy text says 151 // "rebuild+restage". For a binary that is AHEAD of its source (a shipped feature whose source was 152 // lost) that remedy DESTROYS the feature. That is not hypothetical: nx_page_verify shipped an 153 // our-domain auto-connect-override whose source was absent from BOTH trees; a fresh build was 154 // ~2KB SMALLER than the deployment, far outside the stub guard (fn*2 < dn is false at ~572KB vs 155 // ~575KB), so this organ would have said STALE and prescribed the exact action that silently 156 // deletes the override -- turning every our-domain verification into a :443 coin flip. 157 // SIGNAL: fresh SMALLER than deployed => the deployment carries code this source does not emit. 158 // HONEST FLOOR (law L011): size direction is a HEURISTIC, not proof -- a refactor can legitimately 159 // shrink a binary. So this REFUSES TO PRESCRIBE rather than claiming a diagnosis: it withholds the 160 // dangerous remedy and names the check a human must run. Under-claiming here is the safe error; 161 // over-claiming is how the feature got deleted. 162 // u2605CAPABILITY-PRESENCE GATE -- runs BEFORE the size heuristic, because CONTENT EVIDENCE BEATS A 163 // SIZE GUESS. Scans printable runs in the DEPLOYED image and asks whether each survives into the 164 // FRESH build. This is the mechanised form of the manual step the DIRECTION GUARD below asks a 165 // human to perform, and it catches the case size cannot: fresh BIGGER yet capability-poorer. 166 let fo: *i64 = sys_mmap(SC_SHOW * 8) as *i64 167 let flen: *i64 = sys_mmap(SC_SHOW * 8) as *i64 168 var checked: i64 = 0 169 var missed: i64 = 0 170 var shown: i64 = 0 171 var p: i64 = 0 172 while p < dn { 173 if sc_print(db[p] as i64) == 0 { p = p + 1 } else { 174 var e: i64 = p 175 var go: i64 = 1 176 while go == 1 { if e < dn { if sc_print(db[e] as i64) == 1 { e = e + 1 } else { go = 0 } } else { go = 0 } } 177 let rl: i64 = e - p 178 if rl >= SC_MINRUN { if checked < SC_MAXSTR { 179 checked = checked + 1 180 if sc_has(fb, fn, db, p, rl) == 0 { 181 missed = missed + 1 182 if shown < SC_SHOW { fo[shown] = p; flen[shown] = rl; shown = shown + 1 } 183 } 184 } } 185 p = e 186 } 187 } 188 var ccomp: i64 = 1 189 if checked >= SC_MAXSTR { ccomp = 0 } 190 if missed > 0 { 191 sp("CAPABILITY-LOSS " as *u8); sp(target); sp(" (fresh " as *u8); snum(fn); sp("B vs deployed " as *u8); snum(dn) 192 sp("B; " as *u8); snum(missed); sp(" of " as *u8); snum(checked) 193 sp(" deployed strings are ABSENT from the rebuild -- REBUILDING WOULD DELETE SHIPPED CAPABILITY) 194" as *u8) 195 sp(" DO NOT RESTAGE. Recover the source for what is listed below, rebuild, then confirm CURRENT. 196" as *u8) 197 var q: i64 = 0 198 while q < shown { 199 sp(" MISSING-FROM-REBUILD: " as *u8) 200 sys_write(1, (db as i64 + fo[q]) as *u8, flen[q]) 201 sp(" 202" as *u8) 203 q = q + 1 204 } 205 // the envelope rides ON the number, never beside it (L011 self-ceiling) 206 sp(" strings_checked=" as *u8); snum(checked); sp(" cap=" as *u8); snum(SC_MAXSTR) 207 sp(" minrun=" as *u8); snum(SC_MINRUN) 208 sp(" coverage_complete=" as *u8); snum(ccomp); sp(" 209" as *u8) 210 sys_exit(SC_EXIT_CAPLOSS); return SC_EXIT_CAPLOSS 211 } 212 213 if fn < dn { 214 sp("DEPLOYED-AHEAD " as *u8); sp(target) 215 sp(" (fresh " as *u8); snum(fn); sp("B < deployed " as *u8); snum(dn) 216 sp("B: the DEPLOYED binary carries code this source does not emit)\n" as *u8) 217 sp(" DO NOT REBUILD/RESTAGE YET -- a rebuild would DELETE whatever the deployment has and\n" as *u8) 218 sp(" source does not. First find what is missing: run the live binary and look for behaviour\n" as *u8) 219 sp(" or output strings its source cannot produce (that is how seq1008 was caught), and check\n" as *u8) 220 sp(" knowledge/registry/ for a conf whose header names this organ as a consumer.\n" as *u8) 221 sp(" If the gap is real: RECOVER the source first, rebuild, then confirm CURRENT.\n" as *u8) 222 sp(" HEURISTIC: size direction only. A legitimate shrinking refactor also lands here -- this\n" as *u8) 223 sp(" withholds the remedy, it does not diagnose. Confirm before acting either way.\n" as *u8) 224 sys_exit(2); return 2 225 } 226 227 sp("STALE " as *u8); sp(target); sp(" (fresh " as *u8); snum(fn); sp("B != deployed " as *u8); snum(dn); sp("B; source is AHEAD or same-size-divergent; rebuild+restage via nx_restage " as *u8); sp(target); sp(", then VERIFY output)" as *u8); sp(" capability_check=OK strings_checked=" as *u8); snum(checked); sp(" coverage_complete=" as *u8); snum(ccomp); sp("\n" as *u8) 228 sys_exit(1); return 1 229}