code wiki / (root) / nx_organ_ship_review_lib_candidate_t371.nx

nx_organ_ship_review_lib_candidate_t371.nx source

↩ module page · 1102 lines · 65383 B

1// nx_organ_ship_lib.nx -- WHICH GATE PROVES THIS TARGET? The resolver the ship loop was missing. 2// 3// WHY (measured 2026-08-20). nx_organ_ship's PROVE stage looked for exactly ONE name, "<target>_gate", 4// and when that artifact did not exist it announced GATE=NONE and carried on. Shipping 5// nx_gate_roster_run therefore SKIPPED THE PROOF STAGE ENTIRELY -- its gate is nx_gate_roster_gate -- 6// and the loop reported SHIPPED having proven nothing about the binary it installed. 7// A SHIP LOOP THAT SILENTLY SKIPS ITS PROOF STAGE WHEN A NAME DOES NOT MATCH IS THE VACUOUS-TEST 8// DEFECT IN THE ONE ORGAN EVERY OTHER SHIP DEPENDS ON. 9// 10// THE REMEDY IS NOT A RENAME. Renaming a gate to satisfy a classifier is dodging by luck; the estate 11// settled that question once already for a sibling classifier (organ_kind.conf: "renaming a third time 12// would be dodging classifiers by luck"). So: RESOLVE the gate, and PUBLISH THE SEARCH. Every candidate 13// path probed is appended to `tried` in the order probed, so a miss can NAME what it looked for instead 14// of being a silent skip, and a hit can NAME the rule that found it. 15// 16// RESOLUTION ORDER -- declaration first, derivation second (rule 17): 17// 1 explicit the caller passed gate=<name>. A caller ASSERTION: if it is absent the loop REFUSES, 18// because a declared gate that is not there is a caller error, not a missing gate. 19// 2 conf row knowledge/organ_gate.conf, "<target> TAB <gatename>". The declaration path: a target 20// whose gate cannot be derived from its name gets a ROW, never a rename. 21// 3 convention <target>_gate -- what the estate mostly does. 22// 4 self the target ITSELF when it ends in _gate. Shipping a gate used to skip PROVE for the 23// same reason (nx_foo_gate_gate does not exist); a gate proves itself by running. 24// 5 strip1 <target minus its LAST underscore segment>_gate. This is the measured shape: 25// nx_gate_roster_run -> nx_gate_roster_gate. 26// EXACTLY ONE segment, deliberately. Each further strip walks toward a DIFFERENT 27// organ's gate, and A GATE WHOSE SUBJECT IS NOT THE TARGET PROVES NOTHING ABOUT THE 28// TARGET -- a resolver that reached far enough would manufacture a FALSE proof, which 29// is strictly worse than the silent skip it replaces. 30// 31// ROOT ORDER -- WHICH COPY OF THE RESOLVED GATE RUNS (added 2026-08-25). Each candidate is probed at 32// two roots: the promoted serving root and the build scratch. Promoted-first is right for a gate that 33// is a DIFFERENT organ from the target, because this invocation did not build it. It is WRONG for the 34// one candidate that IS the target just built, and for an in-process gate -- whose subject is compiled 35// INTO the gate -- that means reporting a tooth count from code the loop did not build. `fresh` names 36// the target so osl_try_fresh can flip the order for that candidate alone; osl_root_of reports which 37// root any resolved path came from, so the number always travels with its provenance. 38// 39// WHY A LIB AND NOT A FUNCTION INSIDE THE LOOP: nx_organ_ship's main() BUILDS before it proves, so a 40// gate that drove main() would fork real compiles. The roots are PARAMETERS here (dir + suffix pairs) 41// precisely so nx_organ_ship_gate can point them at /tmp/<gate>/ fixtures and test every rule without 42// touching the estate root -- a gate must never share its fixture with a production surface. 43// license_tier: ORIGINAL Read-only: opens candidate paths to test existence and closes them. No hw writes (Rule 26). 44import "nx_syscalls.nx" 45 46const OSL_NAMECAP: i64 = 256 47const OSL_PATHCAP: i64 = 512 48const OSL_TAB: i64 = 9 49const OSL_SP: i64 = 32 50const OSL_NL: i64 = 10 51const OSL_CR: i64 = 13 52const OSL_SEMI: i64 = 59 53const OSL_HASH: i64 = 35 54const OSL_US: i64 = 95 55// the rule that resolved a gate, reported as a NUMBER so callers cannot disagree about spelling 56const OSL_WHY_NONE: i64 = 0 57const OSL_WHY_EXPLICIT: i64 = 1 58const OSL_WHY_CONF: i64 = 2 59const OSL_WHY_CONVENTION: i64 = 3 60const OSL_WHY_SELF: i64 = 4 61const OSL_WHY_STRIP1: i64 = 5 62const OSL_GATE_SUFFIX: *u8 = "_gate" 63const OSL_CONF_DEFAULT: *u8 = "knowledge/organ_gate.conf" 64// WHICH ROOT AN ARTIFACT PATH CAME FROM, reported as a NUMBER for the same reason the WHY codes are: 65// so no caller can disagree with another about spelling. 66const OSL_ROOT_NONE: i64 = 0 67const OSL_ROOT_A: i64 = 1 68const OSL_ROOT_B: i64 = 2 69 70func osl_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 71func osl_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var oo: i64 = o; while s[i] != (0 as u8) { d[oo] = s[i]; oo = oo + 1; i = i + 1 } d[oo] = 0 as u8; return oo } 72func osl_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] == b[i] { if a[i] == (0 as u8) { return 1 } i = i + 1 } return 0 } 73// BOUNDED cat for the search transcript: `tried` is published in a refusal, so it must never be the 74// thing that overruns. NO SILENT CAP -- the caller sizes it from the candidate count, and osl_tried_cap 75// below is the arithmetic, not a guess. 76func osl_catb(d: *u8, o: i64, cap: i64, s: *u8) -> i64 { 77 var i: i64 = 0 78 var oo: i64 = o 79 while s[i] != (0 as u8) { if oo < cap - 1 { d[oo] = s[i]; oo = oo + 1 } i = i + 1 } 80 d[oo] = 0 as u8 81 return oo 82} 83// 5 rules x 2 roots = 10 probes, each at most one path plus a separator. DERIVED, not hand-picked: 84// a hand-counted cap beside a growing rule list is a second copy of the rule count that drifts. 85func osl_tried_cap() -> i64 { return 10 * (OSL_PATHCAP + 1) } 86 87func osl_starts(s: *u8, p: *u8) -> i64 { var i: i64 = 0; while p[i] != (0 as u8) { if s[i] != p[i] { return 0 } i = i + 1 } return 1 } 88// WHICH ROOT DID THIS PATH COME FROM? Tested LONGEST-PREFIX-FIRST, and that is load-bearing rather 89// than tidy: in production root A is "./" and root B is "./buildroot/_build/", so ROOT B STARTS WITH 90// ROOT A. A first-match-A test therefore reports every fresh-build path as a promoted one, and the 91// provenance line would confidently name the wrong artifact -- which is the exact defect this 92// reporting exists to end. The order is DERIVED from the two prefix lengths, never hand-picked, so it 93// stays correct if the roots are ever reconfigured or swapped. 94func osl_root_of(path: *u8, dirA: *u8, dirB: *u8) -> i64 { 95 let la: i64 = osl_slen(dirA) 96 let lb: i64 = osl_slen(dirB) 97 if la >= lb { 98 if osl_starts(path, dirA) == 1 { return OSL_ROOT_A } 99 if osl_starts(path, dirB) == 1 { return OSL_ROOT_B } 100 return OSL_ROOT_NONE 101 } 102 if osl_starts(path, dirB) == 1 { return OSL_ROOT_B } 103 if osl_starts(path, dirA) == 1 { return OSL_ROOT_A } 104 return OSL_ROOT_NONE 105} 106func osl_root_name(r: i64) -> *u8 { 107 if r == OSL_ROOT_A { return "promoted-serving-root" as *u8 } 108 if r == OSL_ROOT_B { return "fresh-build-root" as *u8 } 109 return "unknown-root" as *u8 110} 111 112func osl_ends_gate(s: *u8) -> i64 { 113 let l: i64 = osl_slen(s) 114 let gl: i64 = osl_slen(OSL_GATE_SUFFIX) 115 if l < gl { return 0 } 116 var k: i64 = 0 117 while k < gl { if s[l - gl + k] != OSL_GATE_SUFFIX[k] { return 0 } k = k + 1 } 118 return 1 119} 120 121// <target> minus its LAST underscore segment, plus _gate. nx_gate_roster_run -> nx_gate_roster_gate. 122func osl_strip1(target: *u8, dst: *u8) -> i64 { 123 dst[0] = 0 as u8 124 let l: i64 = osl_slen(target) 125 var cut: i64 = 0 - 1 126 var i: i64 = l - 1 127 while i > 0 { if target[i] == (OSL_US as u8) { cut = i; i = 0 } else { i = i - 1 } } 128 if cut <= 0 { return 0 } 129 var o: i64 = 0 130 while o < cut { dst[o] = target[o]; o = o + 1 } 131 dst[o] = 0 as u8 132 o = osl_cat(dst, o, OSL_GATE_SUFFIX) 133 return 1 134} 135 136// knowledge/organ_gate.conf: "<target> TAB <gatename>" (spaces accepted too). A leading semicolon or 137// hash byte is a comment. Returns 1 and fills dst with the declared gate NAME. 138// 139// ONE TARGET MAY DECLARE SEVERAL GATES (2026-08-23). The compiler is proven by one gate PER LANGUAGE 140// RUNG (nx_chkarith_gate, nx_optenforce_gate, nx_boundscheck_gate, nx_opt_eqsat_wire_gate) and a 141// resolver that read only the FIRST row left the other three to "the lane's own checklist" -- the 142// conf said so in its own comment. A LAW THAT HAS TO BE REMEMBERED AT SHIP TIME IS A LAW THAT GETS 143// SKIPPED; the fix is in the path: osl_conf_scan walks EVERY row for the target, the idx-th match 144// (0-based) fills dst, and the total match count comes back through countp so the ship loop can prove 145// every declared gate. osl_conf_gate is exactly scan(idx=0): every existing caller and every existing 146// tooth sees byte-identical behaviour. Only rows with a non-empty gate name count as declarations. 147func osl_conf_scan(conf: *u8, target: *u8, idx: i64, dst: *u8, countp: *i64) -> i64 { 148 dst[0] = 0 as u8 149 countp[0] = 0 150 let lenp: *i64 = sys_mmap(16) as *i64 151 lenp[0] = 0 152 let b: *u8 = sys_read_file(conf, lenp) 153 let n: i64 = lenp[0] 154 if n <= 0 { return 0 } 155 let tl: i64 = osl_slen(target) 156 var hit: i64 = 0 157 var seen: i64 = 0 158 var i: i64 = 0 159 while i < n { 160 var e: i64 = i 161 var fe: i64 = 0 162 while fe == 0 { if e >= n { fe = 1 } else { if b[e] == (OSL_NL as u8) { fe = 1 } else { e = e + 1 } } } 163 var skip: i64 = 0 164 if e <= i { skip = 1 } 165 if skip == 0 { if b[i] == (OSL_SEMI as u8) { skip = 1 } } 166 if skip == 0 { if b[i] == (OSL_HASH as u8) { skip = 1 } } 167 if skip == 0 { 168 var t: i64 = i 169 var ft: i64 = 0 170 while ft == 0 { 171 if t >= e { ft = 1 } else { 172 if b[t] == (OSL_TAB as u8) { ft = 1 } else { 173 if b[t] == (OSL_SP as u8) { ft = 1 } else { t = t + 1 } } } 174 } 175 if t - i == tl { 176 var same: i64 = 1 177 var k: i64 = 0 178 while k < tl { if b[i + k] != target[k] { same = 0; k = tl } else { k = k + 1 } } 179 if same == 1 { 180 var g: i64 = t 181 var fg: i64 = 0 182 while fg == 0 { 183 if g >= e { fg = 1 } else { 184 if b[g] == (OSL_TAB as u8) { g = g + 1 } else { 185 if b[g] == (OSL_SP as u8) { g = g + 1 } else { fg = 1 } } } 186 } 187 // measure the gate name's extent first; copy it into dst ONLY for the idx-th 188 // declaration, so a later row can never overwrite the one the caller asked for 189 var o: i64 = 0 190 var fo: i64 = 0 191 let gs: i64 = g 192 while fo == 0 { 193 if g >= e { fo = 1 } else { 194 if b[g] == (OSL_CR as u8) { fo = 1 } else { 195 if o < OSL_NAMECAP - 1 { o = o + 1 } 196 g = g + 1 } } 197 } 198 if o > 0 { 199 if seen == idx { 200 var c: i64 = 0 201 while c < o { dst[c] = b[gs + c]; c = c + 1 } 202 dst[o] = 0 as u8 203 hit = 1 204 } 205 seen = seen + 1 206 } 207 } 208 } 209 } 210 i = e + 1 211 } 212 sys_free_file(b, n) 213 countp[0] = seen 214 return hit 215} 216func osl_conf_gate(conf: *u8, target: *u8, dst: *u8) -> i64 { 217 let cp: *i64 = sys_mmap(16) as *i64 218 cp[0] = 0 219 return osl_conf_scan(conf, target, 0, dst, cp) 220} 221// the idx-th (0-based) declared gate for target; 0 when fewer than idx+1 rows declare one 222func osl_conf_gate_nth(conf: *u8, target: *u8, idx: i64, dst: *u8) -> i64 { 223 let cp: *i64 = sys_mmap(16) as *i64 224 cp[0] = 0 225 return osl_conf_scan(conf, target, idx, dst, cp) 226} 227// how many rows declare a gate for target (0 = none declared; comments and empty names never count) 228func osl_conf_gate_count(conf: *u8, target: *u8) -> i64 { 229 let cp: *i64 = sys_mmap(16) as *i64 230 cp[0] = 0 231 let d: *u8 = sys_mmap(OSL_NAMECAP) 232 osl_conf_scan(conf, target, 0 - 1, d, cp) 233 return cp[0] 234} 235 236// probe ONE candidate name at ONE (dir, suffix) root. The exact path probed is APPENDED to `tried` 237// whether it hits or misses -- the transcript is the point: a miss that cannot say what it looked for 238// is the silent skip wearing a new name. 239func osl_probe1(cand: *u8, dir: *u8, suf: *u8, tried: *u8, tcap: i64, tp: *i64, dst: *u8) -> i64 { 240 var o: i64 = osl_cat(dst, 0, dir) 241 o = osl_cat(dst, o, cand) 242 o = osl_cat(dst, o, suf) 243 tp[0] = osl_catb(tried, tp[0], tcap, " " as *u8) 244 tp[0] = osl_catb(tried, tp[0], tcap, dst) 245 let fd: i64 = sys_openat_rd(dst) 246 if fd < 0 { return 0 } 247 sys_close(fd) 248 return 1 249} 250 251// try one candidate NAME at both roots; 1 on hit with dst holding the artifact path. 252func osl_try(cand: *u8, dirA: *u8, sufA: *u8, dirB: *u8, sufB: *u8, tried: *u8, tcap: i64, tp: *i64, dst: *u8) -> i64 { 253 if osl_probe1(cand, dirA, sufA, tried, tcap, tp, dst) == 1 { return 1 } 254 if osl_probe1(cand, dirB, sufB, tried, tcap, tp, dst) == 1 { return 1 } 255 return 0 256} 257 258// THE FRESHNESS SWAP. `fresh` is the name the CALLER JUST BUILT in this invocation (0 = nothing was). 259// When the candidate IS that name, root B (the build scratch) holds code from THIS run while root A 260// holds the PREVIOUS promote -- and for an IN-PROCESS gate the subject is compiled INTO the gate, so 261// probing promoted-first runs a tooth set from code the loop did not build. 262// MEASURED 2026-08-25 from nx_organ_ship's OWN journal, one target, ONE BUILD SHA 2d9c5011..., two 263// opposite verdicts decided entirely by which root was probed first: 264// PROVE RED ./nx_uvunwrap_gate.elf (the previous promote) 265// PROVE GREEN ./buildroot/_build/nx_uvunwrap_gate.sov.elf (the artifact just built) 266// A SHIP LOOP THAT PROVES THE PREVIOUS BINARY IS A VERDICT ABOUT THE WRONG SUBJECT, and its 267// FLATTERING direction is the dangerous one: a regression introduced by this very build is invisible 268// because the old binary still passes. Both directions are in the journal -- nx_editstack_gate and 269// nx_domain_map_gate each went GREEN against ./<name>.elf moments after a build. 270// THE ORDER IS DELIBERATELY UNCHANGED FOR EVERY OTHER CANDIDATE. A gate whose name is not the target 271// was NOT built by this invocation, so its root-B artifact is a fossil of unknown vintage while its 272// root-A artifact is the one the estate actually deploys and runs -- there, promoted-first is right. 273// And the swap is a PREFERENCE, not a requirement: when only root A holds the artifact it still 274// resolves, because refusing there would turn a working ship into a false RED. 275func osl_try_fresh(cand: *u8, fresh: *u8, dirA: *u8, sufA: *u8, dirB: *u8, sufB: *u8, tried: *u8, tcap: i64, tp: *i64, dst: *u8) -> i64 { 276 if (fresh as i64) != 0 { if osl_streq(cand, fresh) == 1 { 277 if osl_probe1(cand, dirB, sufB, tried, tcap, tp, dst) == 1 { return 1 } 278 if osl_probe1(cand, dirA, sufA, tried, tcap, tp, dst) == 1 { return 1 } 279 return 0 280 } } 281 return osl_try(cand, dirA, sufA, dirB, sufB, tried, tcap, tp, dst) 282} 283 284// THE RESOLVER. Returns 1 with dst = the gate ARTIFACT PATH and why[0] = the rule that matched; 285// 0 with dst empty and why[0] = OSL_WHY_NONE, and `tried` naming every path probed either way. 286// 287// `fresh` only ever changes WHICH ROOT IS PROBED FIRST for the single candidate that equals it 288// (osl_try_fresh above). The rule order, the search transcript and every other candidate are 289// untouched. osl_gate_resolve below is EXACTLY this function with fresh=0, so every pre-existing 290// caller and tooth sees byte-identical behaviour -- ONE resolver body, never a second copy that drifts. 291func osl_gate_resolve_fresh(target: *u8, explicit: *u8, fresh: *u8, conf: *u8, dirA: *u8, sufA: *u8, dirB: *u8, sufB: *u8, tried: *u8, tcap: i64, dst: *u8, why: *i64) -> i64 { 292 let tp: *i64 = sys_mmap(16) as *i64 293 tp[0] = 0 294 tried[0] = 0 as u8 295 why[0] = OSL_WHY_NONE 296 dst[0] = 0 as u8 297 let cand: *u8 = sys_mmap(OSL_NAMECAP) 298 // 1 explicit: the caller ASSERTED this gate. Do not fall through to derivation -- silently proving 299 // a DIFFERENT gate than the one named would be the false-proof this resolver exists to refuse. 300 if (explicit as i64) != 0 { if explicit[0] != (0 as u8) { 301 osl_cat(cand, 0, explicit) 302 if osl_try_fresh(cand, fresh, dirA, sufA, dirB, sufB, tried, tcap, tp, dst) == 1 { why[0] = OSL_WHY_EXPLICIT; return 1 } 303 dst[0] = 0 as u8 304 return 0 305 } } 306 // 2 declaration 307 if osl_conf_gate(conf, target, cand) == 1 { 308 if osl_try_fresh(cand, fresh, dirA, sufA, dirB, sufB, tried, tcap, tp, dst) == 1 { why[0] = OSL_WHY_CONF; return 1 } 309 } 310 // 3 convention 311 var co: i64 = osl_cat(cand, 0, target) 312 co = osl_cat(cand, co, OSL_GATE_SUFFIX) 313 if osl_try_fresh(cand, fresh, dirA, sufA, dirB, sufB, tried, tcap, tp, dst) == 1 { why[0] = OSL_WHY_CONVENTION; return 1 } 314 // 4 the target IS a gate 315 if osl_ends_gate(target) == 1 { 316 osl_cat(cand, 0, target) 317 if osl_try_fresh(cand, fresh, dirA, sufA, dirB, sufB, tried, tcap, tp, dst) == 1 { why[0] = OSL_WHY_SELF; return 1 } 318 } 319 // 5 one-segment strip 320 if osl_strip1(target, cand) == 1 { 321 if osl_try_fresh(cand, fresh, dirA, sufA, dirB, sufB, tried, tcap, tp, dst) == 1 { why[0] = OSL_WHY_STRIP1; return 1 } 322 } 323 dst[0] = 0 as u8 324 return 0 325} 326// The pre-2026-08-25 entry point: resolve with NOTHING declared fresh. Kept so that no existing caller 327// has to change, and so its own gate can prove the two are equivalent rather than assert it. 328func osl_gate_resolve(target: *u8, explicit: *u8, conf: *u8, dirA: *u8, sufA: *u8, dirB: *u8, sufB: *u8, tried: *u8, tcap: i64, dst: *u8, why: *i64) -> i64 { 329 return osl_gate_resolve_fresh(target, explicit, 0 as *u8, conf, dirA, sufA, dirB, sufB, tried, tcap, dst, why) 330} 331 332func osl_why_name(w: i64) -> *u8 { 333 if w == OSL_WHY_EXPLICIT { return "explicit-gate-arg" as *u8 } 334 if w == OSL_WHY_CONF { return "organ_gate.conf-declaration" as *u8 } 335 if w == OSL_WHY_CONVENTION { return "convention-target_gate" as *u8 } 336 if w == OSL_WHY_SELF { return "self-the-target-is-a-gate" as *u8 } 337 if w == OSL_WHY_STRIP1 { return "one-segment-strip" as *u8 } 338 return "none" as *u8 339} 340 341// =========================================================================== 342// PART 2 (2026-08-25) -- IS THE RESOLVED GATE'S VERDICT ABOUT *THIS* RUN? 343// 344// The freshness swap above closed ONE case: the target IS its own gate, so stage 1 built the artifact 345// and probing the build root first runs it. The larger hole stayed open, and the lane that closed the 346// first named it: when the target is NOT a gate (ship nx_foo, resolving nx_foo_gate) THE SHIP LOOP 347// NEVER REBUILDS THE GATE. Edit nx_foo_lib.nx, ship nx_foo, and PROVE runs whatever nx_foo_gate binary 348// was last built -- and because an IN-PROCESS gate STATICALLY LINKS the lib, that verdict is a claim 349// about the tree AS OF THE GATE'S OWN BUILD, not about the edit being shipped. The estate has measured 350// the scale of it: 715 gates have a binary and only 176 are current with their own source. 351// 352// THE BOUND IS THE WHOLE DESIGN, not a caveat on it. Rebuilding every resolved gate on every ship 353// doubles compile cost for every lane on a box whose governor already refuses builds under load, and 354// MOST GATES DO NOT EMBED THEIR TARGET AT ALL -- an end-to-end gate fork/execs the deployed elf, so 355// rebuilding it changes nothing about what it proves. The rebuild is therefore spent only when the 356// gate's OWN SOURCE imports what this run built: <target>.nx or <target>_lib.nx. One cheap read. 357// 358// DECLARED SCOPE, because a bound that is not published reads as completeness: 359// * DIRECT IMPORT EDGES ONLY, AND THE TARGET NAME IS THE ONLY EVIDENCE THIS LOOP HAS. A gate that 360// reaches the target through an intermediate lib (gate -> nx_bar_lib.nx -> nx_foo_lib.nx), and -- 361// the commoner case -- a gate and a target that BOTH import some third lib which is what actually 362// got edited, both classify INDEPENDENT here and are NOT rebuilt. A FLOOR on the rebuild set, 363// never the transitive closure. 364// THE RESIDUAL IS NAMED WITH ITS MECHANISM, because a named absence is only worth naming if the 365// next lane can act on it: /api/build ALREADY RETURNS closure_sha256 for every target it compiles, 366// so the honest test is not an import scan at all -- it is "does this gate's CURRENT closure hash 367// differ from the one its artifact was built from", which needs one banked hash per built gate and 368// nothing else. Until that bank exists the import scan is the cheap approximation, and it is 369// deliberately the one that errs toward rebuilding. (nx_rebuild_plan walks the whole closure today 370// and is far too heavy to fork per ship.) 371// * COMMENTS ARE NOT CODE. A commented-out import is not a build edge, and a scanner that does not 372// skip comments measures the documentation rather than the code (banked, twice). 373// * EXACT MODULE NAMES. "nx_foo_other_lib.nx" must never answer for "nx_foo", so the module string is 374// compared by LENGTH AND BYTES. A substring test would follow the wrong organ's edit. 375// 376// WHY BOTH SOURCE ROOTS ARE READ, AND WHY THE ORDER IS NOT LOAD-BEARING HERE. nx_gatefresh's source 377// records that nx_sov_build_run probes buildroot/runtime/_hdl_build/ FIRST, so a basename present in 378// both dirs compiles from _hdl_build and the runtime copy is a SHADOW that never compiles; it carries 379// the law beside it (MATCH THE RESOLVER YOU ARE REPORTING ON). This scans in that same order AND reads 380// the shadow too, answering CONTAINS if EITHER copy names the target. Under a shadow the two copies can 381// disagree, and the only direction this decision is allowed to be wrong in is spending one extra 382// compile; being wrong the other way IS the stale-proof defect it exists to end. The count of readable 383// sources comes back through seenp so a shadow is ANNOUNCED instead of silently picked. 384// =========================================================================== 385 386const OSL_SRC_SUF: *u8 = ".nx" 387const OSL_LIB_SUFFIX: *u8 = "_lib" 388const OSL_KW_IMPORT: *u8 = "import" 389const OSL_CMT: *u8 = "//" 390const OSL_QUOTE: i64 = 34 391// the two source roots, IN THE BUILDER'S OWN ORDER (see the header note above) 392const OSL_SRC_DIR_HDL: *u8 = "./buildroot/runtime/_hdl_build/" 393const OSL_SRC_DIR_RT: *u8 = "./buildroot/runtime/" 394 395// WHAT A GATE'S BUILD CLOSURE HOLDS with respect to the target this run built. UNPROVEN is its OWN 396// state and must never collapse into INDEPENDENT: "I could not read the source" and "the source does 397// not import it" have opposite consequences for whether the verdict below is evidence. 398const OSL_CLO_UNPROVEN: i64 = 0 399const OSL_CLO_INDEPENDENT: i64 = 1 400const OSL_CLO_CONTAINS: i64 = 2 401 402// THE VINTAGE OF THE ARTIFACT A PROVE VERDICT CAME FROM, reported as a NUMBER for the same reason the 403// WHY and ROOT codes are: so no caller can disagree with another about spelling. Every state names its 404// own cause -- a single STALE bucket would leave the reader guessing which of three things happened. 405const OSL_VINT_UNKNOWN: i64 = 0 406const OSL_VINT_STAGE1: i64 = 1 407const OSL_VINT_REBUILT: i64 = 2 408const OSL_VINT_INDEPENDENT: i64 = 3 409const OSL_VINT_STALE_REFUSED: i64 = 4 410const OSL_VINT_STALE_NOARTIFACT: i64 = 5 411const OSL_VINT_UNKNOWN_NOSRC: i64 = 6 412const OSL_VINT_UNKNOWN_NONAME: i64 = 7 413const OSL_VINT_STALE_WRONGSRC: i64 = 8 414 415// =========================================================================== 416// PART 3 (2026-08-26) -- THE BANKED CLOSURE HASH DECIDES; THE IMPORT SCAN ONLY EVER ADDS. 417// 418// PART 2 named its own residual and named the fix in the same breath: "/api/build ALREADY RETURNS 419// closure_sha256 for every target it compiles, so the honest test is not an import scan at all -- it is 420// does this gate's CURRENT closure hash differ from the one its artifact was built from, which needs 421// one banked hash per built gate and nothing else." 422// 423// THE BANK ALREADY EXISTED AND NOTHING IN THIS LOOP HAD ASKED IT. /api/build writes <target>.provenance 424// at the serving root carrying closure_sha256=<64hex>, and nx_provcheck already recomputes the tree 425// closure through nx_closurehash and compares the two. So this is a WIRING, not a second ruler: no 426// closure walk and no sha256 is re-implemented here, and the comparison keeps exactly one owner. 427// 428// WHY THE HASH IS STRICTLY STRONGER THAN THE DIRECT-EDGE SCAN. A closure hash covers every TRANSITIVE 429// source, so the case PART 2 declared out of scope -- a gate and its target that BOTH import some third 430// lib, which is what actually got edited, neither importing the other directly -- lands inside the 431// gate's own closure and moves its hash. MEASURED 2026-08-26 on the live estate: nx_page_verify_gate 432// imports only nx_syscalls, nx_gate_verdict and nx_tool_run, so the edge scan calls it INDEPENDENT and 433// never rebuilds it, while its own bank reads 434// recorded=0960d15888fe52a0 ... now=1d07b01042d5621b ... verdict=DRIFTED 435// i.e. the teeth that would prove a ship of nx_page_verify are compiled from sources that have moved. 436// It also covers a blind spot the edge scan has by construction: nx_closurehash resolves through 437// runtime's SUBDIRECTORIES too, so a gate living in runtime/hub/ -- which osl_gate_closure can only 438// report UNPROVEN -- still gets a real answer from the bank. 439// 440// THE ORDERING RULE, AND IT IS THE WHOLE SAFETY ARGUMENT: THE HASH DECIDES, AND THE EDGE SCAN MAY ONLY 441// EVER SHORT-CIRCUIT TOWARD REBUILDING. osl_should_rebuild_prov returns 1 for every input on which the 442// incumbent osl_should_rebuild returns 1 -- it DELEGATES to it rather than restating it -- so NO SHIP 443// THAT REBUILT ITS GATE BEFORE STOPS DOING SO. The new test can add a rebuild and can never remove one. 444// A cheap approximation that is allowed to VETO an expensive proof is how a fast path becomes a false 445// green, and that direction is the one nobody audits. 446// 447// UNPROVEN NEVER FORCES A REBUILD, AND THAT IS THE PRODUCER'S OWN PUBLISHED ENVELOPE RATHER THAN A 448// CONVENIENCE. The build lane says it in its own source: the async worker does not write a sidecar yet, 449// "so a consumer MUST treat an absent sidecar as unknown, fall back to mtime, and never as stale." 450// Reading an absent bank as staleness would rebuild a gate on every ship whose gate was last built 451// asynchronously -- a permanent compile cost paid for no evidence at all. So UNPROVEN falls back to the 452// edge scan and is REPORTED as its own state rather than folded into either answer: "I could not look" 453// must never read as "current", and it must not read as "stale" either. 454// =========================================================================== 455 456// WHAT THE BANKED CLOSURE HASH SAYS ABOUT THE RESOLVED GATE'S ARTIFACT. Three states, because "the bank 457// disagrees with the tree" and "there is no bank to ask" have opposite consequences and opposite 458// remedies, and a single negative bucket would be read as the more alarming of the two. 459const OSL_PROV_UNPROVEN: i64 = 0 460const OSL_PROV_CURRENT: i64 = 1 461const OSL_PROV_DRIFTED: i64 = 2 462// nx_provcheck's PUBLISHED exit contract, named here so the call site never re-spells a bare integer 463// and so a change to that contract breaks in ONE place: 0 CURRENT, 1 DRIFTED, 2 UNRECORDED-or-NOSIDECAR, 464// 3 usage. Anything else -- 127 exec-missing, the -2/-3/-4 harness sentinels, -5 timeout -- is an 465// instrument that could not answer, which is UNPROVEN and must never be promoted into a verdict. 466const OSL_PC_EXIT_CURRENT: i64 = 0 467const OSL_PC_EXIT_DRIFTED: i64 = 1 468// THREE FURTHER VINTAGES. The bank is a DIFFERENT AXIS from the import scan and must not borrow its 469// names: STALE_PROV (we PROVED the artifact old) is not the same failure as STALE_REFUSED (the box 470// would not run the rebuild), and folding them would merge a fact about the gate with a fact about the 471// host -- two states with different remedies, which is the bucket-named-for-the-reader defect. 472const OSL_VINT_PROV_CURRENT: i64 = 9 473const OSL_VINT_REBUILT_PROV: i64 = 10 474const OSL_VINT_STALE_PROV: i64 = 11 475 476// 2 source roots x one path each plus a separator. DERIVED from the root count, like osl_tried_cap: 477// a hand-counted cap beside a growing root list is a second copy of that list that drifts. 478func osl_closure_tried_cap() -> i64 { return 2 * (OSL_PATHCAP + 1) } 479 480// THE NAME BEHIND AN ARTIFACT PATH -- the complement of osl_root_of, and the reason the ship loop can 481// ask anything at all about a gate it resolved: the resolver returns a PATH, and a source lookup needs 482// a NAME. Refuses (0) rather than guessing when the path is under neither root, does not end in that 483// root's suffix, or would overrun the name buffer -- NO SILENT CAP, because a truncated gate name would 484// resolve a DIFFERENT organ's source and the rebuild decision would follow it. 485func osl_name_of(path: *u8, dirA: *u8, sufA: *u8, dirB: *u8, sufB: *u8, dst: *u8) -> i64 { 486 dst[0] = 0 as u8 487 let r: i64 = osl_root_of(path, dirA, dirB) 488 if r == OSL_ROOT_NONE { return 0 } 489 var dir: *u8 = dirA 490 var suf: *u8 = sufA 491 if r == OSL_ROOT_B { dir = dirB; suf = sufB } 492 let pl: i64 = osl_slen(path) 493 let dl: i64 = osl_slen(dir) 494 let sl: i64 = osl_slen(suf) 495 if pl <= dl + sl { return 0 } 496 var k: i64 = 0 497 while k < sl { if path[pl - sl + k] != suf[k] { return 0 } k = k + 1 } 498 let nlen: i64 = pl - sl - dl 499 if nlen >= OSL_NAMECAP { return 0 } 500 var o: i64 = 0 501 while o < nlen { dst[o] = path[dl + o]; o = o + 1 } 502 dst[o] = 0 as u8 503 return 1 504} 505 506// ---- source scanning ------------------------------------------------------ 507// end of the line starting at `from` (index of the newline, or n). The answer lives in its OWN 508// variable and the cursor is only ever used to stop: a loop that breaks by clobbering its own cursor 509// cannot also report where it stopped, and that idiom has erased an answer four times in this estate. 510func osl_eol(buf: *u8, n: i64, from: i64) -> i64 { 511 var k: i64 = from 512 var e: i64 = 0 - 1 513 while k < n { if buf[k] == (OSL_NL as u8) { e = k; k = n } else { k = k + 1 } } 514 if e < 0 { return n } 515 return e 516} 517// index of the next double-quote before `end`, or -1 518func osl_qend(buf: *u8, end: i64, from: i64) -> i64 { 519 var k: i64 = from 520 var q: i64 = 0 - 1 521 while k < end { if buf[k] == (OSL_QUOTE as u8) { q = k; k = end } else { k = k + 1 } } 522 return q 523} 524// skip spaces, tabs and CRs. The byte is read ONCE into c: with no else between the tests, an earlier 525// branch's k = k + 1 makes the next branch test a DIFFERENT byte -- a banked silent-failure idiom. 526func osl_skipws(buf: *u8, n: i64, from: i64) -> i64 { 527 var k: i64 = from 528 var go: i64 = 1 529 while go == 1 { 530 go = 0 531 if k < n { 532 let c: i64 = buf[k] as i64 533 var ws: i64 = 0 534 if c == OSL_SP { ws = 1 } 535 if c == OSL_TAB { ws = 1 } 536 if c == OSL_CR { ws = 1 } 537 if ws == 1 { k = k + 1; go = 1 } 538 } 539 } 540 return k 541} 542func osl_at(buf: *u8, end: i64, at: i64, s: *u8) -> i64 { 543 let l: i64 = osl_slen(s) 544 if at + l > end { return 0 } 545 var k: i64 = 0 546 while k < l { if buf[at + k] != s[k] { return 0 } k = k + 1 } 547 return 1 548} 549// ONE LINE: is it a LIVE `import "<name>.nx"`? Everything this returns 0 for is a case where treating 550// the line as a build edge would spend a compile on the wrong organ or on nothing at all. 551func osl_line_imports(buf: *u8, ls: i64, le: i64, name: *u8, nl: i64) -> i64 { 552 var i: i64 = osl_skipws(buf, le, ls) 553 if i >= le { return 0 } 554 if osl_at(buf, le, i, OSL_CMT) == 1 { return 0 } 555 if osl_at(buf, le, i, OSL_KW_IMPORT) == 0 { return 0 } 556 // the keyword must END here, or `importer_of(x)` reads as an import. The length is DERIVED from the 557 // keyword literal, never hand-counted beside it. 558 var j: i64 = i + osl_slen(OSL_KW_IMPORT) 559 if j >= le { return 0 } 560 let c: i64 = buf[j] as i64 561 var ws: i64 = 0 562 if c == OSL_SP { ws = 1 } 563 if c == OSL_TAB { ws = 1 } 564 if ws == 0 { return 0 } 565 j = osl_skipws(buf, le, j) 566 if j >= le { return 0 } 567 if buf[j] != (OSL_QUOTE as u8) { return 0 } 568 j = j + 1 569 let q: i64 = osl_qend(buf, le, j) 570 if q < 0 { return 0 } 571 let sufl: i64 = osl_slen(OSL_SRC_SUF) 572 // LENGTH FIRST, then bytes: this is what stops nx_foo_other_lib.nx answering for nx_foo. 573 if q - j != nl + sufl { return 0 } 574 var k: i64 = 0 575 while k < nl { if buf[j + k] != name[k] { return 0 } k = k + 1 } 576 var m: i64 = 0 577 while m < sufl { if buf[j + nl + m] != OSL_SRC_SUF[m] { return 0 } m = m + 1 } 578 return 1 579} 580// does this SOURCE TEXT carry a live import of <name>.nx? 581func osl_imports(buf: *u8, n: i64, name: *u8) -> i64 { 582 if n <= 0 { return 0 } 583 let nl: i64 = osl_slen(name) 584 if nl <= 0 { return 0 } 585 var ls: i64 = 0 586 var hit: i64 = 0 587 while ls < n { 588 let le: i64 = osl_eol(buf, n, ls) 589 if hit == 0 { if osl_line_imports(buf, ls, le, name, nl) == 1 { hit = 1 } } 590 ls = le + 1 591 } 592 return hit 593} 594// CONTAINS iff this source imports the target ITSELF or the target's lib. Those are the two shapes a 595// ship actually rebuilds: `ship nx_foo` after editing nx_foo.nx or nx_foo_lib.nx. 596func osl_closure_of_buf(buf: *u8, n: i64, target: *u8) -> i64 { 597 if osl_imports(buf, n, target) == 1 { return OSL_CLO_CONTAINS } 598 // a name that cannot fit its _lib form is refused TOWARD REBUILDING: the only error this decision 599 // may make is spending a compile, never skipping one. 600 if osl_slen(target) + osl_slen(OSL_LIB_SUFFIX) >= OSL_NAMECAP { return OSL_CLO_CONTAINS } 601 let lib: *u8 = sys_mmap(OSL_NAMECAP) 602 var o: i64 = osl_cat(lib, 0, target) 603 o = osl_cat(lib, o, OSL_LIB_SUFFIX) 604 if osl_imports(buf, n, lib) == 1 { return OSL_CLO_CONTAINS } 605 return OSL_CLO_INDEPENDENT 606} 607// THE ONE CALL THE SHIP LOOP MAKES. Probes both source roots in the builder's order, APPENDS every 608// path probed to `tried` whether it hits or misses (a decision that cannot say what it read is the 609// silent skip wearing a new name), fills srcdst with the source that DECIDED, and reports through 610// seenp how many sources were readable so a SHADOW (both roots populated) can be announced. 611func osl_gate_closure(gate: *u8, target: *u8, dirA: *u8, dirB: *u8, suf: *u8, tried: *u8, tcap: i64, srcdst: *u8, seenp: *i64) -> i64 { 612 tried[0] = 0 as u8 613 srcdst[0] = 0 as u8 614 seenp[0] = 0 615 let tp: *i64 = sys_mmap(16) as *i64 616 tp[0] = 0 617 let path: *u8 = sys_mmap(OSL_PATHCAP) 618 let lenp: *i64 = sys_mmap(16) as *i64 619 var verdict: i64 = OSL_CLO_UNPROVEN 620 var r: i64 = 0 621 while r < 2 { 622 var d: *u8 = dirA 623 if r == 1 { d = dirB } 624 var o: i64 = osl_cat(path, 0, d) 625 o = osl_cat(path, o, gate) 626 o = osl_cat(path, o, suf) 627 tp[0] = osl_catb(tried, tp[0], tcap, " " as *u8) 628 tp[0] = osl_catb(tried, tp[0], tcap, path) 629 let buf: *u8 = sys_read_file(path, lenp) 630 if (buf as i64) != 0 { 631 seenp[0] = seenp[0] + 1 632 if verdict != OSL_CLO_CONTAINS { 633 osl_cat(srcdst, 0, path) 634 verdict = osl_closure_of_buf(buf, lenp[0], target) 635 } 636 sys_free_file(buf, lenp[0]) 637 } 638 r = r + 1 639 } 640 if verdict == OSL_CLO_CONTAINS { return OSL_CLO_CONTAINS } 641 if seenp[0] > 0 { return OSL_CLO_INDEPENDENT } 642 return OSL_CLO_UNPROVEN 643} 644// THE BOUND, as a predicate the gate can mutate. A rebuild is spent ONLY when the gate is a different 645// organ from the target AND its source imports what this run built. Without this, "rebuild every gate 646// on every ship" satisfies every other tooth -- which is why the INDEPENDENT case is a tooth of its own. 647func osl_should_rebuild(gate: *u8, target: *u8, clo: i64) -> i64 { 648 if osl_streq(gate, target) == 1 { return 0 } 649 if clo == OSL_CLO_CONTAINS { return 1 } 650 return 0 651} 652// IS THIS VERDICT EVIDENCE ABOUT THE RUN THAT PRODUCED IT? Exactly three vintages are: the artifact 653// stage 1 built, the artifact this run rebuilt, and a gate whose closure never contained the target at 654// all. Every other state is either STALE (an artifact older than the edit being shipped) or UNKNOWN 655// (undecidable) -- and both must read as NOT CURRENT, because an abstention that acquits is the 656// flattering failure nobody investigates. 657func osl_vint_current(v: i64) -> i64 { 658 if v == OSL_VINT_STAGE1 { return 1 } 659 if v == OSL_VINT_REBUILT { return 1 } 660 if v == OSL_VINT_INDEPENDENT { return 1 } 661 // THE BANK'S TWO CURRENT ANSWERS. OSL_VINT_STALE_PROV is deliberately ABSENT from this list: a gate 662 // whose recorded closure no longer matches the tree is the one case the loop may decline to rebuild 663 // and must still refuse to call current, because an abstention that acquits is the flattering 664 // failure nobody investigates. 665 if v == OSL_VINT_PROV_CURRENT { return 1 } 666 if v == OSL_VINT_REBUILT_PROV { return 1 } 667 return 0 668} 669func osl_vint_name(v: i64) -> *u8 { 670 if v == OSL_VINT_STAGE1 { return "FRESH-built-by-stage-1" as *u8 } 671 if v == OSL_VINT_REBUILT { return "FRESH-rebuilt-this-run" as *u8 } 672 if v == OSL_VINT_INDEPENDENT { return "INDEPENDENT-of-this-build" as *u8 } 673 if v == OSL_VINT_PROV_CURRENT { return "FRESH-closure-bank-CURRENT" as *u8 } 674 if v == OSL_VINT_REBUILT_PROV { return "FRESH-rebuilt-this-run-because-the-bank-DRIFTED" as *u8 } 675 if v == OSL_VINT_STALE_PROV { return "STALE-closure-bank-DRIFTED-and-the-rebuild-did-not-land" as *u8 } 676 if v == OSL_VINT_STALE_REFUSED { return "STALE-gate-rebuild-REFUSED" as *u8 } 677 if v == OSL_VINT_STALE_NOARTIFACT { return "STALE-fresh-artifact-absent-after-BUILT" as *u8 } 678 if v == OSL_VINT_STALE_WRONGSRC { return "STALE-gate-rebuild-compiled-WRONG-SOURCE" as *u8 } 679 if v == OSL_VINT_UNKNOWN_NOSRC { return "UNKNOWN-gate-source-absent" as *u8 } 680 if v == OSL_VINT_UNKNOWN_NONAME { return "UNKNOWN-gate-name-underivable" as *u8 } 681 return "UNKNOWN-cannot-decide" as *u8 682} 683func osl_closure_name(c: i64) -> *u8 { 684 if c == OSL_CLO_CONTAINS { return "CONTAINS-this-target" as *u8 } 685 if c == OSL_CLO_INDEPENDENT { return "INDEPENDENT-of-this-target" as *u8 } 686 return "UNPROVEN-no-source-readable" as *u8 687} 688 689// ---- PART 3 -- THE BANK, AS FUNCTIONS THE GATE CAN MUTATE ----------------- 690// Every function below is PURE: it takes the comparator's exit code, not a file and not a fork. The 691// fork of nx_provcheck lives in nx_organ_ship's main(), for the same reason the resolver's roots are 692// parameters -- a lib that forked would drag a real compile into its own gate, and a decision that can 693// only be exercised by running the estate is a decision no mutation test can reach. 694 695// nx_provcheck's exit code -> the bank's verdict. EVERYTHING that is not one of the two decided exits 696// is UNPROVEN, deliberately including 127 (comparator absent) and -5 (its deadline fired): when the 697// instrument cannot answer, this loop must degrade to EXACTLY the incumbent's behaviour, never to a 698// refusal and never to a green. 699func osl_prov_of_exit(rc: i64) -> i64 { 700 if rc == OSL_PC_EXIT_CURRENT { return OSL_PROV_CURRENT } 701 if rc == OSL_PC_EXIT_DRIFTED { return OSL_PROV_DRIFTED } 702 return OSL_PROV_UNPROVEN 703} 704func osl_prov_name(p: i64) -> *u8 { 705 if p == OSL_PROV_CURRENT { return "BANK-CURRENT-artifact-is-its-sources" as *u8 } 706 if p == OSL_PROV_DRIFTED { return "BANK-DRIFTED-closure-moved-since-this-artifact-was-built" as *u8 } 707 return "BANK-UNPROVEN-no-recorded-closure-to-compare" as *u8 708} 709 710// THE DECISION. Read it as three lines in priority order, because that order IS the safety property: 711// 1 the target is its own gate -> stage 1 already built it; a second build is pure waste. 712// 2 the bank says DRIFTED -> REBUILD. The hash decides, and it decides regardless of imports. 713// 3 otherwise -> DELEGATE to the incumbent edge rule, which can only ADD a rebuild. 714// Because line 3 is a delegation and not a restatement, this function returns 1 on every input where 715// osl_should_rebuild returns 1. That is the no-regression guarantee, and it is a property of the code 716// rather than a promise about it: there is no path on which a CONTAINS closure stops rebuilding. 717// (2026-09-05: a CURRENT bank alone still vetoes nothing here, because a bank cannot say WHICH file it describes; 718// the veto lives in osl_should_rebuild_pick below, which takes the artifact identity as its extra argument.) 719func osl_should_rebuild_prov(gate: *u8, target: *u8, clo: i64, prov: i64) -> i64 { 720 if osl_streq(gate, target) == 1 { return 0 } 721 if prov == OSL_PROV_DRIFTED { return 1 } 722 return osl_should_rebuild(gate, target, clo) 723} 724 725// WHICH VINTAGE A GATE THAT WAS *NOT* REBUILT CARRIES. It lives beside the decision so the two can 726// never disagree about what "not rebuilt" meant. Only a bank that actually said CURRENT earns the 727// stronger name; an UNPROVEN bank falls back to whatever the edge scan could establish and keeps the 728// incumbent's own wording; and a DRIFTED bank that reaches here at all means the rebuild did not 729// happen, so it must read STALE even though this loop chose to proceed. 730// PROV_CURRENT is well-founded and not merely optimistic: nx_provcheck can only answer CURRENT after 731// nx_closurehash RESOLVED AND READ every source in the closure, so that answer already carries the 732// source-found precondition that osl_gate_closure reports separately as UNPROVEN. 733func osl_vint_norebuild(prov: i64, clo: i64) -> i64 { 734 if prov == OSL_PROV_DRIFTED { return OSL_VINT_STALE_PROV } 735 if prov == OSL_PROV_CURRENT { return OSL_VINT_PROV_CURRENT } 736 if clo == OSL_CLO_UNPROVEN { return OSL_VINT_UNKNOWN_NOSRC } 737 return OSL_VINT_INDEPENDENT 738} 739 740// WHICH VINTAGE A SUCCESSFUL REBUILD CARRIES. Both are current; they differ only in WHICH instrument 741// demanded the rebuild, and that is worth keeping because it is the measurement of how much the bank 742// is actually buying over the edge scan. A single REBUILT bucket would hide exactly that number. 743func osl_vint_rebuilt(prov: i64) -> i64 { 744 if prov == OSL_PROV_DRIFTED { return OSL_VINT_REBUILT_PROV } 745 return OSL_VINT_REBUILT 746} 747 748// THE HIGHEST VINTAGE CODE, so that a census over "every vintage" is bound to the LIST rather than to 749// a number hand-copied beside it. This exists because the distinctness tooth in nx_organ_ship_gate was 750// written against the then-last code and would have silently stopped covering the list the moment part 751// 3 added three more -- the tooth would still have passed, over a subset, while its name went on 752// claiming every code. A bound that has to be remembered at the moment of extension is a bound that 753// drifts, and it drifts toward understating coverage, which is the direction nobody audits. 754func osl_vint_max() -> i64 { return OSL_VINT_STALE_PROV } 755 756// ---- WHICH ARTIFACT DOES THE BANK VOUCH FOR? (2026-09-05) ------------------------------------------- 757// nx_provcheck reads ./<gate>.provenance, the sidecar of the LAST BUILD -- root B, buildroot/_build/<gate>.sov.elf. 758// The resolver prefers root A (the promoted binary) for every gate the loop did not just build. When the two roots 759// hold DIFFERENT bytes, a CURRENT bank is a fact about root B while PROVE was about to run root A: one freshness 760// claim, two artifacts. MEASURED 2026-09-05 on nx_janitor_caps_gate: bank CURRENT, PROVE ran the 20,695 B promoted 761// binary (the pre-migration gate) while the banked artifact was 30,649 B -- and the receipt said 762// vintage=FRESH-closure-bank-CURRENT about a binary the bank had never described. 763// THE DECISION IS A PURE FUNCTION so its gate drives it in-process: the caller supplies which root the resolver 764// picked, both digests (empty when not computed or unreadable) and whether root B exists on disk. 765const OSL_PICK_KEEP: i64 = 0 // run what the resolver picked: root B already, or root A byte-identical to the banked artifact 766const OSL_PICK_SWITCH_B: i64 = 1 // root A differs from the banked root B: run root B, and say root A is BEHIND its own source 767const OSL_PICK_UNPROVEN: i64 = 2 // the bank cannot be matched to anything executable (root B absent -- a refused build eats the fossil and leaves the sidecar -- or a digest unreadable): UNPROVEN, never CURRENT 768func osl_pick_banked(resolved_root: i64, sha_a: *u8, sha_b: *u8, b_exists: i64) -> i64 { 769 if resolved_root != OSL_ROOT_A { return OSL_PICK_KEEP } 770 if b_exists == 0 { return OSL_PICK_UNPROVEN } 771 if sha_a[0] == (0 as u8) { return OSL_PICK_UNPROVEN } 772 if sha_b[0] == (0 as u8) { return OSL_PICK_UNPROVEN } 773 if osl_streq(sha_a, sha_b) == 1 { return OSL_PICK_KEEP } 774 return OSL_PICK_SWITCH_B 775} 776func osl_pick_name(p: i64) -> *u8 { 777 if p == OSL_PICK_KEEP { return "KEEP-RESOLVED" as *u8 } 778 if p == OSL_PICK_SWITCH_B { return "BANKED-BUILD-USED" as *u8 } 779 return "BANK-UNMATCHED" as *u8 780} 781// THE DECISION WITH THE ARTIFACT IN HAND (2026-09-05). osl_should_rebuild_prov deliberately lets a CURRENT bank veto 782// nothing, because a bank alone cannot say WHICH file it describes. This variant takes the pick (osl_pick_banked): 783// when the bank is CURRENT and the banked artifact is on disk and is the one PROVE will run (KEEP: identical to the 784// resolved binary, or SWITCH_B: about to be run instead of it), the rebuild the import scan demands can only 785// reproduce bytes already present, so it is skipped -- and a refused compile can no longer downgrade a proven 786// artifact to STALE. When the bank matches nothing executable (UNPROVEN) the incumbent rule decides, exactly as 787// before. Measured on the loop's own self-ship: bank CURRENT, import scan rebuilt, admission refused, PROVE ran the 788// stale promoted 43-tooth gate while the banked current 50-tooth build sat in root B. 789func osl_should_rebuild_pick(gate: *u8, target: *u8, clo: i64, prov: i64, pick: i64) -> i64 { 790 if osl_streq(gate, target) == 1 { return 0 } 791 if prov == OSL_PROV_DRIFTED { return 1 } 792 if prov == OSL_PROV_CURRENT { if pick != OSL_PICK_UNPROVEN { return 0 } } 793 return osl_should_rebuild(gate, target, clo) 794} 795 796// ---- AD2: THE HARNESS DISCLOSURE MANIFEST (2026-08-27) -------------------------------------------- 797// Harness configuration governs more variance than model choice and must be DISCLOSED; two results are 798// directly comparable only when produced under the same harness. This is the PRODUCER half; the 799// consumer half (gv_envelope_comparable: differing tokens REFUSE a comparison) ships in the gate base 800// class. The manifest is ROWS `component|path|id`, truncate-written to a caller-named path, and the 801// TOKEN is "h"+16 hex of FNV-1a over the manifest BYTES -- any component change (a ruler binary, a 802// conf, a budget) changes the token, and an ABSENT component is NAMED in its row (absence is part of 803// the identity, never skipped, because a harness that lost a guard is a different harness). 804const OSL_HM_BUF: i64 = 16384 805const OSL_HM_MODE: i64 = 420 806// FNV-1a 64-bit (offset basis 14695981039346656037 as a signed literal; prime 1099511628211). The same 807// recipe nx_autofix_auto.af_harness_calc uses for the fix loop's token; folding both onto this one 808// copy is the named follow-on (that organ shipped hours before this lib grew the shared home). 809const OSL_FNV_OFFSET: i64 = 0 - 3750763034362895579 810const OSL_FNV_PRIME: i64 = 1099511628211 811const OSL_HM_HEX: i64 = 16 812const OSL_HM_HEX_A: i64 = 87 813const OSL_HM_ZERO: i64 = 48 814 815func osl_fnv64(h0: i64, buf: *u8, n: i64) -> i64 { 816 var h: i64 = h0 817 var i: i64 = 0 818 while i < n { h = (h ^ (buf[i] as i64)) * OSL_FNV_PRIME; i = i + 1 } 819 return h 820} 821func osl_fnv64_file(h0: i64, path: *u8) -> i64 { 822 let lb: *i64 = sys_mmap(16) as *i64 823 let b: *u8 = sys_read_file(path, lb) 824 if (b as i64) == 0 { return h0 } 825 return osl_fnv64(h0, b, lb[0]) 826} 827func osl_hm_hex(h: i64, out: *u8) -> i64 { 828 out[0] = 104 as u8 829 var k: i64 = 0 830 while k < OSL_HM_HEX { 831 let nib: i64 = (h >> ((OSL_HM_HEX - 1 - k) * 4)) & 15 832 if nib < 10 { out[1 + k] = (OSL_HM_ZERO + nib) as u8 } else { out[1 + k] = (OSL_HM_HEX_A + nib) as u8 } 833 k = k + 1 834 } 835 out[1 + OSL_HM_HEX] = 0 as u8 836 return 1 + OSL_HM_HEX 837} 838func osl_hm_nl(d: *u8, o0: i64) -> i64 { 839 let nlb: *u8 = sys_mmap(2) 840 nlb[0] = 10 as u8 841 nlb[1] = 0 as u8 842 let o: i64 = osl_cat(d, o0, nlb) 843 sys_munmap(nlb, 2) 844 return o 845} 846// one component row: component|path|<h-token of the file bytes, or ABSENT (named, never skipped)> 847func osl_hm_row(d: *u8, o0: i64, comp: *u8, path: *u8) -> i64 { 848 var o: i64 = osl_cat(d, o0, comp) 849 o = osl_cat(d, o, "|" as *u8) 850 o = osl_cat(d, o, path) 851 o = osl_cat(d, o, "|" as *u8) 852 let fd: i64 = sys_openat_rd(path) 853 if fd < 0 { o = osl_cat(d, o, "ABSENT" as *u8) } else { 854 sys_close(fd) 855 let hx: *u8 = sys_mmap(24) 856 osl_hm_hex(osl_fnv64_file(OSL_FNV_OFFSET, path), hx) 857 o = osl_cat(d, o, hx) 858 sys_munmap(hx, 24) 859 } 860 return osl_hm_nl(d, o) 861} 862// one VALUE row (a budget or an argument): component|-|<decimal> 863func osl_hm_val(d: *u8, o0: i64, comp: *u8, v: i64) -> i64 { 864 var o: i64 = osl_cat(d, o0, comp) 865 o = osl_cat(d, o, "|-|" as *u8) 866 let t: *u8 = sys_mmap(32) 867 var m: i64 = v 868 var neg: i64 = 0 869 if m < 0 { neg = 1; m = 0 - m } 870 var k: i64 = 0 871 if m == 0 { t[0] = OSL_HM_ZERO as u8; k = 1 } 872 while m > 0 { t[k] = (OSL_HM_ZERO + (m % 10)) as u8; m = m / 10; k = k + 1 } 873 let t2: *u8 = sys_mmap(34) 874 var j: i64 = 0 875 if neg == 1 { t2[0] = 45 as u8; j = 1 } 876 var z: i64 = 0 877 while z < k { t2[j] = t[k - 1 - z]; j = j + 1; z = z + 1 } 878 t2[j] = 0 as u8 879 o = osl_cat(d, o, t2) 880 sys_munmap(t, 32) 881 sys_munmap(t2, 34) 882 return osl_hm_nl(d, o) 883} 884// THE MANIFEST: a row per component the ship's verdicts depend on, truncate-written to outpath; the 885// token (FNV over the manifest BYTES) lands in tok (>=18 bytes). `extra` may name ONE additional file 886// ("" = none) -- the gate's determinism and bite teeth turn on it. Returns the row count, <0 unwritable. 887func osl_harness_manifest(outpath: *u8, target_src_sha: *u8, gatepath: *u8, timeout_ms: i64, allow_loss: i64, extra: *u8, tok: *u8) -> i64 { 888 let d: *u8 = sys_mmap(OSL_HM_BUF) 889 var o: i64 = 0 890 var rows: i64 = 0 891 o = osl_cat(d, o, "# harness manifest -- the configuration this ship's verdicts were produced under" as *u8) 892 o = osl_hm_nl(d, o) 893 o = osl_hm_row(d, o, "builder" as *u8, "_offc/nx_sov_build_run.elf" as *u8); rows = rows + 1 894 o = osl_hm_row(d, o, "adopt-ruler" as *u8, "./nx_adoptgate.elf" as *u8); rows = rows + 1 895 o = osl_hm_row(d, o, "content-ruler" as *u8, "./nx_contentdiff.elf" as *u8); rows = rows + 1 896 o = osl_hm_row(d, o, "behave-ruler" as *u8, "./nx_behaveprobe.elf" as *u8); rows = rows + 1 897 o = osl_hm_row(d, o, "prove-gate" as *u8, gatepath); rows = rows + 1 898 o = osl_hm_row(d, o, "rigor-conf" as *u8, "knowledge/rigor.conf" as *u8); rows = rows + 1 899 o = osl_hm_row(d, o, "autofix-conf" as *u8, "knowledge/autofix.conf" as *u8); rows = rows + 1 900 o = osl_hm_row(d, o, "admit-conf" as *u8, "knowledge/build_admit.conf" as *u8); rows = rows + 1 901 o = osl_cat(d, o, "target-src-sha|-|" as *u8) 902 o = osl_cat(d, o, target_src_sha) 903 o = osl_hm_nl(d, o) 904 rows = rows + 1 905 o = osl_hm_val(d, o, "timeout-ms" as *u8, timeout_ms); rows = rows + 1 906 o = osl_hm_val(d, o, "allow-loss" as *u8, allow_loss); rows = rows + 1 907 if extra[0] != (0 as u8) { o = osl_hm_row(d, o, "extra" as *u8, extra); rows = rows + 1 } 908 osl_hm_hex(osl_fnv64(OSL_FNV_OFFSET, d, o), tok) 909 let fd: i64 = sys_openat_wr(outpath, OSL_HM_MODE) 910 if fd < 0 { return 0 - 1 } 911 sys_write(fd, d, o) 912 sys_close(fd) 913 return rows 914} 915 916 917// Exact loss-set approval binds complete evidence and both immutable artifacts. 918import "nx_tool_run.nx" 919import "nx_sha256.nx" 920struct OslLossSet { count:i64,record:NxBufOwned,digest:*u8 } 921func osls_len(s:*u8)->i64{var n:i64=0;while s[n]!=(0 as u8){n=n+1};return n} 922func osls_eq(a:*u8,b:*u8,n:i64)->i64{var i:i64=0;while i<n{if a[i]!=b[i]{return 0};i=i+1};return 1} 923func osls_prefix(a:*u8,n:i64,s:*u8)->i64{let m:i64=osls_len(s);if n<m{return 0};return osls_eq(a,s,m)} 924func osls_hex(s:*u8)->i64{if (s as i64)<=0{return 0};if osls_len(s)!=64{return 0};var i:i64=0;while i<64{let c:i64=s[i] as i64;if c<48{return 0};if c>57{if c<97||c>102{return 0}};i=i+1};return 1} 925// Decimal spans must be complete, checked and nonnegative; no ambiguous prefix parse. 926func osls_uint(s:*u8,n:i64)->i64{if n<=0{return -1};var v:i64=0;var i:i64=0;while i<n{let c:i64=s[i] as i64;if c<48||c>57{return -1};if v>(NX_BO_I64_MAX-(c-48))/10{return -1};v=v*10+c-48;i=i+1};return v} 927func osls_find(s:*u8,n:i64,key:*u8)->i64{let m:i64=osls_len(key);var i:i64=0;while i+m<=n{if osls_eq(s+i,key,m)==1{return i};i=i+1};return -1} 928func osls_token(s:*u8,n:i64,key:*u8,length:*i64)->i64{ 929 length[0]=0;let kn:i64=osls_len(key);var found:i64=-1;var i:i64=0 930 while i<n{ 931 while i<n{if s[i]!=(32 as u8){break};i=i+1};let start:i64=i 932 while i<n{if s[i]==(32 as u8){break};i=i+1} 933 if i-start>=kn{if osls_eq(s+start,key,kn)==1{if found>=0{return -1};found=start+kn;length[0]=i-found}} 934 };return found 935} 936func osls_field(s:*u8,n:i64,key:*u8)->i64{ 937 var keyp:*u8=key;if keyp[0]==(32 as u8){keyp=keyp+1} 938 var vn:i64=0;let at:i64=osls_token(s,n,keyp,&vn);if at<0{return -1};return osls_uint(s+at,vn) 939} 940func osls_report(buf:*u8,n:i64)->i64{ 941 if (buf as i64)<=0||n<=0{return -1};if buf[n-1]!=(10 as u8){return -1} 942 var pos:i64=0;var named:i64=0;var summary:i64=0;var total:i64=-1;var counted:i64=-1;var counts:i64=0;var verdict:i64=0 943 while pos<n{ 944 var end:i64=pos;while end<n{if buf[end]==(10 as u8){break};end=end+1} 945 let ln:i64=end-pos;let line:*u8=buf+pos 946 if osls_prefix(line,ln," LOST: ")==1{if ln<=8{return -1};named=named+1} 947 if osls_prefix(line,ln,"display_class=LOST ")==1{ 948 var cn:i64=0;let ci:i64=osls_token(line,ln,"display_class=",&cn);if ci<0||cn!=4{return -1};if osls_eq(line+ci,"LOST",4)!=1{return -1} 949 summary=summary+1;total=osls_field(line,ln," total=") 950 if osls_field(line,ln," shown=")!=total||osls_field(line,ln," omitted=")!=0{return -1} 951 } 952 if osls_prefix(line,ln,"runs_scanned=")==1{counts=counts+1;counted=osls_field(line,ln," lost_from_live=");if osls_field(line,ln,"runs_scanned=")<counted{return -1}} 953 if osls_prefix(line,ln,"NX-CONTENTDIFF ")==1{var vn:i64=0;let vp:i64=osls_token(line,ln,"verdict=",&vn);if vp<0||vn!=3{return -1};if osls_eq(line+vp,"RED",3)!=1{return -1};verdict=verdict+1;if end!=n-1{return -1}} 954 pos=end+1 955 } 956 if summary!=1||counts!=1||verdict!=1||named<=0||named!=total||counted!=total{return -1};return total 957} 958func osls_add(b:*NxBufOwned,s:*u8,n:i64)->i64{return nx_bo_append(b,s,n,0)} 959func osls_text(b:*NxBufOwned,s:*u8)->i64{return osls_add(b,s,osls_len(s))} 960func osls_build(live:*u8,candidate:*u8,report:*u8,n:i64,set:*OslLossSet)->i64{ 961 set.count=-1;set.record.buf=0 as *u8;set.record.len=0;set.record.cap=0;set.digest=0 as *u8 962 if osls_hex(live)==0||osls_hex(candidate)==0{return -1} 963 let count:i64=osls_report(report,n);if count<0{return -1} 964 var rc:i64=osls_text(&set.record,"NISHI-SHIP-LOSS-SET/1\nlive_sha256=") 965 if rc==0{rc=osls_add(&set.record,live,64)};if rc==0{rc=osls_text(&set.record,"\ncandidate_sha256=")} 966 if rc==0{rc=osls_add(&set.record,candidate,64)};if rc==0{rc=osls_text(&set.record,"\nreport=nx_contentdiff-all\n\n")} 967 if rc==0{rc=osls_add(&set.record,report,n)} 968 if rc!=0{nx_bo_release(&set.record);return rc} 969 set.digest=sys_mmap_try(65);if (set.digest as i64)<=0{nx_bo_release(&set.record);return -1} 970 let raw:*u8=sys_mmap_try(32);if (raw as i64)<=0{osls_close(set);return -1};sha256_digest(set.record.buf,set.record.len,raw) 971 let alphabet:*u8="0123456789abcdef";var i:i64=0;while i<32{let b:i64=raw[i] as i64;set.digest[i*2]=alphabet[b/16];set.digest[i*2+1]=alphabet[b%16];i=i+1};set.digest[64]=0 as u8;sys_munmap_direct(raw,32) 972 set.count=count;return 0 973} 974func osls_close(set:*OslLossSet)->i64{var rc:i64=nx_bo_release(&set.record);if (set.digest as i64)>0{let r:i64=sys_munmap_direct(set.digest,65);if r<0{rc=r};set.digest=0 as *u8};return rc} 975func osls_approve(set:*OslLossSet,approval:*u8,live:*u8,candidate:*u8)->i64{ 976 if set.count<=0{return 0};if osls_hex(approval)==0||osls_hex(live)==0||osls_hex(candidate)==0{return 0} 977 if osls_eq(set.digest,approval,64)==0{return 0} 978 let prefix:i64=osls_len("NISHI-SHIP-LOSS-SET/1\nlive_sha256=") 979 if osls_eq(set.record.buf+prefix,live,64)==0{return 0} 980 let off:i64=prefix+64+osls_len("\ncandidate_sha256=") 981 return osls_eq(set.record.buf+off,candidate,64) 982} 983// Verify bytes and fsync the SAME open descriptor before accepting an existing record. 984// This closes retry after a prior fsync failure; matching page-cache bytes alone are insufficient. 985func os_evidence_sync_existing(fd:i64,record:*NxBufOwned)->i64{ 986 let n:i64=sys_lseek(fd,0,2);if n!=record.len||n<=0{return -1};if sys_lseek(fd,0,0)!=0{return -1} 987 let b:*u8=sys_mmap_try(n);if (b as i64)<=0{return -1} 988 var off:i64=0;var rc:i64=0 989 while off<n{let r:i64=sys_read(fd,b+off,n-off);if r==TR_EINTR{continue};if r<=0{rc=-1;break};off=off+r} 990 if rc==0{if osls_eq(b,record.buf,n)!=1{rc=-1}} 991 if rc==0{var extra:u8=0 as u8;let eof:i64=sys_read(fd,&extra,1);if eof!=0{rc=-1}} 992 if rc==0{if sys_fsync(fd)<0{rc=-1}} 993 let freed:i64=sys_munmap_direct(b,n);if freed<0{rc=freed};return rc 994} 995// An existing exact record is reusable. A partial or changed record is never repaired in place. 996func os_evidence_retain(path:*u8,dir:*u8,record:*NxBufOwned)->i64{ 997 let fd:i64=sys_openat_exclusive(path,420) 998 if fd<0{ 999 if fd!=(-17){return -1} 1000 let existing:i64=sys_openat_rd(path);if existing<0{return -1} 1001 let checked:i64=os_evidence_sync_existing(existing,record);let closed:i64=sys_close(existing) 1002 if checked!=0||closed<0{return -1} 1003 }else{ 1004 var off:i64=0;while off<record.len{let w:i64=sys_write(fd,record.buf+off,record.len-off);if w==TR_EINTR{continue};if w<=0{sys_close(fd);return -1};off=off+w} 1005 let flushed:i64=sys_fsync(fd);let closed:i64=sys_close(fd);if flushed<0||closed<0{return -1} 1006 } 1007 let d:i64=sys_openat_directory(dir);if d<0{return -1};let synced:i64=sys_fsync(d);let dc:i64=sys_close(d);if synced<0||dc<0{return -1};return 0 1008} 1009 1010func osls_sync_existing(fd:i64,set:*OslLossSet)->i64{return os_evidence_sync_existing(fd,&set.record)} 1011func osls_retain(path:*u8,dir:*u8,set:*OslLossSet)->i64{return os_evidence_retain(path,dir,&set.record)} 1012 1013// A review record owns exact context and captures. Hash approval never reruns a workload. 1014import "nx_behaveprobe_review_candidate_t371.nx" 1015struct ObrWriter { out:*NxBufOwned,number:*u8,error:i64 } 1016func obr_add(w:*ObrWriter,s:*u8,n:i64)->i64{if w.error!=0{return w.error};w.error=nx_bo_append(w.out,s,n,0);return w.error} 1017func obr_text(w:*ObrWriter,s:*u8)->i64{return obr_add(w,s,osls_len(s))} 1018func obr_num(w:*ObrWriter,v:i64)->i64{if w.error!=0{return w.error};let n:i64=nxi_buf(w.number,0,v);return obr_add(w,w.number,n)} 1019func obr_frame(w:*ObrWriter,name:*u8,s:*u8,n:i64)->i64{ 1020 if n<0{w.error=NX_BO_RANGE;return w.error} 1021 obr_text(w,name);obr_text(w,"=");obr_num(w,n);obr_text(w,":");obr_add(w,s,n);return obr_text(w,"\n") 1022} 1023func obr_context(target:*u8,live:*u8,other:*u8,q:*NxBehaviorRequest,out:*NxBufOwned)->i64{ 1024 if nx_bo_state(out)!=0{return NX_BO_INVALID};if out.len!=0||out.cap!=0{return NX_BO_INVALID} 1025 if osls_hex(live)==0||osls_hex(other)==0{return NX_BO_INVALID} 1026 if q.arg_count<0||q.arg_count>NX_BO_I64_MAX/__size_of(i64)-2{return NX_BO_RANGE} 1027 if q.arg_count>0{if (q.args as i64)<=0{return NX_BO_INVALID}} 1028 if (target as i64)<=0||(q.cwd as i64)<=0||(q.live as i64)<=0||(q.other as i64)<=0{return NX_BO_INVALID} 1029 if q.timeout_ms<0||q.max_capture_bytes<0{return NX_BO_INVALID} 1030 let number:*u8=sys_mmap_try(NXI_BUF);if (number as i64)<=0{return NX_BO_ALLOCATION} 1031 var w:ObrWriter;w.out=out;w.number=number;w.error=0 1032 obr_text(&w,"NISHI-SHIP-BEHAVIOR-REVIEW/1\n") 1033 obr_frame(&w,"target",target,osls_len(target));obr_frame(&w,"live_sha256",live,64);obr_frame(&w,"candidate_sha256",other,64) 1034 obr_frame(&w,"live_path",q.live,osls_len(q.live));obr_frame(&w,"candidate_path",q.other,osls_len(q.other)) 1035 obr_frame(&w,"cwd",q.cwd,osls_len(q.cwd));obr_text(&w,"arg_count=");obr_num(&w,q.arg_count);obr_text(&w,"\n") 1036 var i:i64=0;while i<q.arg_count{if q.args[i]<=0{w.error=NX_BO_INVALID;break};let s:*u8=q.args[i] as *u8;obr_frame(&w,"arg",s,osls_len(s));i=i+1} 1037 obr_text(&w,"timeout_ms=");obr_num(&w,q.timeout_ms);obr_text(&w,"\nmax_capture_bytes=");obr_num(&w,q.max_capture_bytes);obr_text(&w,"\n") 1038 let freed:i64=sys_munmap_direct(number,NXI_BUF);if freed<0{return freed};return w.error 1039} 1040func obr_eligible(r:*NxBehaviorResult)->i64{ 1041 if r.state!=0||r.live_reaped!=1||r.other_reaped!=1{return 0} 1042 if r.live_rc!=0||r.other_rc!=0||r.live_exit!=0||r.other_exit!=0{return 0} 1043 if r.live_signal!=0||r.other_signal!=0||r.live_timeout!=0||r.other_timeout!=0{return 0} 1044 if r.live_setup!=0||r.other_setup!=0||r.live_exec!=0||r.other_exec!=0{return 0} 1045 if r.grade!=BP_G_STRONG{return 0};return 1 1046} 1047func obr_build(context:*NxBufOwned,target:*u8,r:*NxBehaviorResult,a:*NxBufOwned,b:*NxBufOwned,out:*NxBufOwned)->i64{ 1048 if nx_bo_state(context)!=0||nx_bo_state(a)!=0||nx_bo_state(b)!=0||nx_bo_state(out)!=0{return NX_BO_INVALID} 1049 if out.len!=0||out.cap!=0||context.len<=0{return NX_BO_INVALID} 1050 if r.live_bytes!=a.len||r.other_bytes!=b.len{return NX_BO_INVALID} 1051 let number:*u8=sys_mmap_try(NXI_BUF);if (number as i64)<=0{return NX_BO_ALLOCATION} 1052 var observed:NxBufOwned;observed.buf=0 as *u8;observed.len=0;observed.cap=0 1053 var w:ObrWriter;w.out=out;w.number=number;w.error=bp_build_result(target,"review-retained",r,&observed,0) 1054 obr_add(&w,context.buf,context.len) 1055 if obr_eligible(r)==1{obr_text(&w,"execution=complete-reaped-zero-exits\n")}else{obr_text(&w,"execution=unqualified\n")} 1056 obr_frame(&w,"observation",observed.buf,observed.len);obr_frame(&w,"live_output",a.buf,a.len);obr_frame(&w,"candidate_output",b.buf,b.len);obr_text(&w,"END\n") 1057 let released:i64=nx_bo_release(&observed);let freed:i64=sys_munmap_direct(number,NXI_BUF) 1058 if released<0{return released};if freed<0{return freed};return w.error 1059} 1060func obr_digest(record:*NxBufOwned,out:*u8)->i64{ 1061 if nx_bo_state(record)!=0||record.len<=0{return NX_BO_INVALID} 1062 let raw:*u8=sys_mmap_try(32);if (raw as i64)<=0{return NX_BO_ALLOCATION} 1063 sha256_digest(record.buf,record.len,raw);let alphabet:*u8="0123456789abcdef" 1064 var i:i64=0;while i<32{let v:i64=raw[i] as i64;out[i*2]=alphabet[v/16];out[i*2+1]=alphabet[v%16];i=i+1};out[64]=0 as u8 1065 return sys_munmap_direct(raw,32) 1066} 1067// Framing is parsed by exact label and checked byte length; output may contain any bytes. 1068func obr_next(record:*NxBufOwned,at:i64,name:*u8)->i64{ 1069 if at<0||at>record.len{return -1};let n:i64=osls_len(name) 1070 if n>record.len-at{return -1};if osls_eq(record.buf+at,name,n)!=1{return -1};var p:i64=at+n 1071 if p>=record.len{return -1};if record.buf[p]!=(61 as u8){return -1};p=p+1;let start:i64=p 1072 while p<record.len{if record.buf[p]==(58 as u8){break};p=p+1} 1073 if p>=record.len{return -1};let size:i64=osls_uint(record.buf+start,p-start);if size<0{return -1} 1074 p=p+1;if size>=record.len-p{return -1};p=p+size;if record.buf[p]!=(10 as u8){return -1};return p+1 1075} 1076func obr_approve(record:*NxBufOwned,context:*NxBufOwned,approval:*u8)->i64{ 1077 if nx_bo_state(record)!=0||nx_bo_state(context)!=0{return 0} 1078 if osls_hex(approval)==0||context.len<=0||record.len<context.len{return 0} 1079 if osls_eq(record.buf,context.buf,context.len)!=1{return 0} 1080 let marker:*u8="execution=complete-reaped-zero-exits\n";let n:i64=osls_len(marker);var pos:i64=context.len 1081 if n>record.len-pos{return 0};if osls_eq(record.buf+pos,marker,n)!=1{return 0};pos=pos+n 1082 pos=obr_next(record,pos,"observation");if pos<0{return 0} 1083 pos=obr_next(record,pos,"live_output");if pos<0{return 0} 1084 pos=obr_next(record,pos,"candidate_output");if pos<0{return 0} 1085 if record.len-pos!=4{return 0};if osls_eq(record.buf+pos,"END\n",4)!=1{return 0} 1086 let digest:*u8=sys_mmap_try(65);if (digest as i64)<=0{return 0};let rc:i64=obr_digest(record,digest) 1087 var accepted:i64=0;if rc==0{accepted=osls_eq(digest,approval,64)};let freed:i64=sys_munmap_direct(digest,65);if freed<0{return 0};return accepted 1088} 1089 1090// Measured regular-file read. Partial bytes remain owned for caller cleanup on refusal. 1091func os_evidence_read(path:*u8,out:*NxBufOwned)->i64{ 1092 if nx_bo_state(out)!=0{return NX_BO_INVALID};if out.len!=0||out.cap!=0{return NX_BO_INVALID} 1093 let fd:i64=sys_openat_rd(path);if fd<0{return fd} 1094 let n:i64=sys_lseek(fd,0,2);if n<=0{sys_close(fd);return NX_BO_INVALID} 1095 if sys_lseek(fd,0,0)!=0{sys_close(fd);return NX_BO_INVALID} 1096 let data:*u8=sys_mmap_try(n);if (data as i64)<=0{sys_close(fd);return NX_BO_ALLOCATION} 1097 out.buf=data;out.cap=n;out.len=0;var rc:i64=0 1098 while out.len<n{let got:i64=sys_read(fd,data+out.len,n-out.len);if got==TR_EINTR{continue};if got<=0{rc=TR_ERR_DRAIN;break};out.len=out.len+got} 1099 if rc==0{var extra:u8=0 as u8;let more:i64=sys_read(fd,&extra,1);if more!=0{rc=TR_ERR_DRAIN}} 1100 if rc==0{if sys_fsync(fd)<0{rc=TR_ERR_DRAIN}} 1101 let closed:i64=sys_close(fd);if closed<0{rc=closed};return rc 1102}