code wiki / _hdl_build / nx_gate_migrate.nx

nx_gate_migrate.nx source

↩ module page · 755 lines · 40281 B

1// nx_gate_migrate.nx -- D001 GATE-DRY MIGRATION VERIFIER (2026-07-22). 2// 3// THE DEBT: 1918 of 1996 gate organs (96%) hand-roll their verdict scaffold instead of inheriting 4// nx_gate_verdict. Every prior attempt to eat it reached for a BYTE-equivalence proof and refused, 5// because byte-identity is UNACHIEVABLE BY CONSTRUCTION here: gv_check emits " <name>: PASS" where a 6// hand-rolled row emits " <name> PASS", and gv_verdict reformats the summary line entirely. The wrong 7// oracle -- not a hard problem -- is why D001 never moved. 8// 9// THE SOUND ORACLE = JUDGE-EQUIVALENCE, 4 clauses, measured before vs after in the SAME environment: 10// (a) exit code identical -- the gate still passes/fails the same way 11// (b) judge verdict identical -- gg_line_green(last "verdict=" line, "GREEN") agrees. 12// We COMPOSE nx_gate_green (the ecosystem's ONE judge) rather than reimplement it: the migration 13// is sound exactly when the thing that JUDGES gates cannot tell the difference. 14// (c) PASS/FAIL vector identical -- same checks, same results, same counts 15// (d) evidence side-effect preserved -- OPTIONAL, only when the caller names the file; when 16// absent it is reported UNCHECKED, never silently assumed clean (self-ceiling law L011). 17// Byte-difference alone MUST NOT refuse -- that is the whole point (tooth T5). 18// 19// DIVISION OF LABOUR (deliberate): the EDIT is semantic and belongs to the gate's owner lane; the PROOF 20// is mechanizable and belongs here. So this is a VERIFIER with commit-or-restore, never an auto-editor. 21// never-brick: on ANY divergence, failed build, or harness error the ORIGINAL source is RESTORED and the 22// verdict is REFUSE. The pre-migration copy is kept (rule 13 additive) as <src>.premigrate. 23// 24// nx_gate_migrate verify <gate> <migrated-src> [evidence-file] 25// nx_gate_migrate anchor <gate> <anchored-src> -- seq585: adopt the canonical verdict= anchor 26// nx_gate_migrate probe <gate> 27// nx_gate_migrate selftest 28// 29// ANCHOR RUNG (seq585, MUST precede the DRY rung for the 953 unjudgeable gates): a gate that emits 30// PASS/FAIL + exit 0/1 but no "verdict=" line is machine-read as NOT-GREEN by gg_line_green (missing 31// anchor == failure, nx_gate_green.nx:56) -- a silent false negative. `anchor` verifies an 32// anchor-ADOPTION edit: before must have NO anchor in its output, after MUST have one, exit + PASS/FAIL 33// vector identical, and the emitted verdict must AGREE with the exit code (GREEN iff exit 0 -- an 34// anchor that lies about the exit is worse than no anchor). Same commit-or-restore as verify. 35// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 36import "nx_syscalls.nx" 37import "nx_gate_verdict.nx" 38import "nx_gate_green.nx" 39import "nx_tool_run.nx" 40 41const GM_CAP: i64 = 262144 42const GM_PATH: i64 = 512 43const GM_RES: i64 = 64 44const GM_MODE: i64 = 0x1a4 45// GM_BUILDER was "_offc/nx_buildonly.elf" -- a thin wrapper that is NOT staged in the NAS buildroot/_offc 46// (only its source exists), so every NAS-side build died at execve-127 = harness error. Call the canonical 47// builder directly: it is present in BOTH _offc trees, so the factory no longer depends on an unstaged shim. 48const GM_BUILDER: *u8 = "_offc/nx_sov_build_run.elf" 49const GM_BUILDONLY: *u8 = "--build-only" 50// A root is VALID iff the gate base class this organ migrates TOWARD lives in it. Named, not magic (rule 11). 51const GM_ROOTMARK: *u8 = "runtime/nx_gate_verdict.nx" 52const GM_ROOTALT: *u8 = "buildroot" 53// Builder output dir (cwd-relative, same as the builder itself); GM_ARTALT is the pre-wrapper fallback. 54const GM_ARTDIR: *u8 = "_build/" 55const GM_ARTALT: *u8 = "/tmp/" 56const GM_SANDBOX: *u8 = "_gatesandbox" 57// THE VERIFIER MUST NOT BE HANGABLE BY THE THING IT VERIFIES. Ported from ws=gate-dry-d001 (debt 58// 1785524287/1785524306), which measured a 20-gate batch making ZERO progress for THIRTY MINUTES on one 59// unbounded verify. gm_build/gm_run drive ~2800 UNKNOWN binaries, so an unbounded wait is a guaranteed 60// eventual stall; nx_tool_run documents the bounded twins in its OWN header and they were simply never 61// adopted here -- the adoption gap, in the one organ whose job is running unknown code. 62const GM_BUILD_MS: i64 = 300000 63const GM_RUN_MS: i64 = 120000 64const GM_USAGE: i64 = 2 65const GM_REFUSE: i64 = 3 66const GM_HARNESS: i64 = 4 67// one i64 scratch slot (tr_run_capture's outlen out-param). NOT a bare 16: sizing this by hand is the 68// exact class that produced the pipe2 fd-unpack hang -- name it once, never re-derive it at a call site. 69const GM_SLOT: i64 = 16 70const GM_UNCHECKED: i64 = 0 - 1 71// ENRICHMENT (2026-08-01, debts 1785562495 / 1785562671). BEFORE enumerated NO checks, AFTER enumerates 72// some, all passing. Not divergence -- it is the improvement the migration exists to deliver, and for 73// this population it is UNAVOIDABLE: nx_gate_verdict.nx:127 REFUSES GREEN unless ctr[1] > 0, so a 74// zero-check original CANNOT be expressed on the base class while keeping its exit code. Adding checks 75// failed clause (c); adding none makes gv_verdict return RED and fails (a) and (b). BOTH DOORS WERE 76// CLOSED -- the mechanical reason the composer-generated gates and the whole non-PASS vocabulary never 77// migrated. (Sibling census nx_gate_vocab_census.nx names the VOCABULARY TRAP; this is its second horn.) 78// Same shape as the original D001 blocker one layer in: byte-equivalence was unachievable BY 79// CONSTRUCTION, and so is vector-identity for a gate that enumerates nothing. 80// ACCEPTED ONLY when (a) exit and (b) judge verdict are PRESERVED and NO FAIL appears. One-way. 81const GM_ENRICHED: i64 = 2 82// ---- the res[] contract, shared by gm_clauses (writer) and gm_report (reader). Named because a 83// cross-function index contract expressed as bare integers is a silent-miscompare waiting to happen. 84const GM_RESN: i64 = 11 85const GM_R_EXIT: i64 = 0 86const GM_R_JUDGE: i64 = 1 87const GM_R_VEC: i64 = 2 88const GM_R_EVID: i64 = 3 89const GM_R_BYTES: i64 = 4 90const GM_R_PASSA: i64 = 5 91const GM_R_PASSB: i64 = 6 92const GM_R_FAILA: i64 = 7 93const GM_R_FAILB: i64 = 8 94const GM_R_GREENA: i64 = 9 95const GM_R_GREENB: i64 = 10 96// ---- anchor-verb res[] slots (an_clauses writer / its report reader share these names) 97const GM_A_EXIT: i64 = 0 98const GM_A_VEC: i64 = 1 99const GM_A_BEFORE_CLEAN: i64 = 2 100const GM_A_AFTER_HAS: i64 = 3 101const GM_A_AGREES: i64 = 4 102const GM_ARESN: i64 = 5 103 104func gm_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 105 106func gm_cat(dst: *u8, o: i64, s: *u8) -> i64 { 107 var i: i64 = 0 108 var oo: i64 = o 109 while s[i] != (0 as u8) { dst[oo] = s[i]; oo = oo + 1; i = i + 1 } 110 dst[oo] = 0 as u8 111 return oo 112} 113 114// count non-overlapping occurrences of a NUL-terminated needle in buf[0..n) 115func gm_count(buf: *u8, n: i64, needle: *u8) -> i64 { 116 let nl: i64 = gm_slen(needle) 117 if nl == 0 { return 0 } 118 var c: i64 = 0 119 var i: i64 = 0 120 while i + nl <= n { 121 var m: i64 = 1 122 var k: i64 = 0 123 while k < nl { if buf[i+k] != needle[k] { m = 0; k = nl } else { k = k + 1 } } 124 if m == 1 { c = c + 1; i = i + nl } else { i = i + 1 } 125 } 126 return c 127} 128 129func gm_write_file(path: *u8, buf: *u8, n: i64) -> i64 { 130 let fd: i64 = sys_openat_wr(path, GM_MODE) 131 if fd < 0 { return 0 - 1 } 132 var w: i64 = 0 133 var go: i64 = 1 134 while go == 1 { 135 if w >= n { go = 0 } else { 136 let r: i64 = sys_write(fd, ((buf as i64) + w) as *u8, n - w) 137 if r <= 0 { go = 0 } else { w = w + r } 138 } 139 } 140 sys_close(fd) 141 if w == n { return 0 } 142 return 0 - 1 143} 144 145// copy src -> dst byte-exact; returns bytes copied or -1 146func gm_copy(src: *u8, dst: *u8) -> i64 { 147 let b: *u8 = sys_mmap(GM_CAP) 148 let n: i64 = gg_read(src, b, GM_CAP) 149 if n <= 0 { return 0 - 1 } 150 if gm_write_file(dst, b, n) != 0 { return 0 - 1 } 151 return n 152} 153 154// size of a file in bytes (0 if absent) -- used for the evidence side-effect delta 155func gm_fsize(path: *u8) -> i64 { 156 let b: *u8 = sys_mmap(GM_CAP) 157 let n: i64 = gg_read(path, b, GM_CAP) 158 if n < 0 { return 0 } 159 return n 160} 161 162// ---------- ROOT RESOLUTION -- the tree identity must be DECLARED, never inferred ---------- 163// Every path in this organ is CWD-relative (runtime/_hdl_build/..., _offc/...), so WHICH TREE it edits is 164// decided entirely by where it happens to be launched -- and it never said which. That is exactly how the 165// first D001 campaign anchored 395 gates in the LOCAL nxc2 tree while the authoritative NAS warden gauge 166// never moved: two trees, one name, no declaration. Resolve a root, chdir to it, and PRINT the answer so a 167// caller can never again be wrong about which corpus was edited. 168// Fail-fast (rule 20): no corpus root => REFUSE, never silently operate on whatever the cwd happens to be. 169func gm_root_ensure() -> i64 { 170 let probe: *u8 = sys_mmap(GM_CAP) 171 if gg_read(GM_ROOTMARK, probe, GM_CAP) > 0 { gv_puts("GATE-MIGRATE root=cwd\n" as *u8); return 1 } 172 if sys_chdir(GM_ROOTALT) == 0 { 173 if gg_read(GM_ROOTMARK, probe, GM_CAP) > 0 { gv_puts("GATE-MIGRATE root=buildroot\n" as *u8); return 1 } 174 } 175 gv_puts("GATE-MIGRATE-FAIL no-corpus-root (runtime/nx_gate_verdict.nx absent in cwd and in buildroot)\n" as *u8) 176 return 0 177} 178 179// resolve <gate>.nx: _hdl_build first (bare-name builds resolve there), then runtime root 180func gm_resolve(gate: *u8, out: *u8) -> i64 { 181 var o: i64 = gm_cat(out, 0, "runtime/_hdl_build/" as *u8) 182 o = gm_cat(out, o, gate) 183 o = gm_cat(out, o, ".nx" as *u8) 184 let probe: *u8 = sys_mmap(GM_CAP) 185 if gg_read(out, probe, GM_CAP) > 0 { return 1 } 186 o = gm_cat(out, 0, "runtime/" as *u8) 187 o = gm_cat(out, o, gate) 188 o = gm_cat(out, o, ".nx" as *u8) 189 if gg_read(out, probe, GM_CAP) > 0 { return 1 } 190 return 0 191} 192 193// nx_sov_build_run --build-only emits <cwd>/_build/<target>.sov.elf. The old hardcoded "/tmp/" was a 194// stale convention from the nx_buildonly wrapper era: the gate COMPILED fine and then this resolver looked 195// for it somewhere it was never written, so every run reported execve-127 and the oracle read it as a 196// harness failure -- a built artifact judged missing. Probe the real builder output dir first, keep /tmp as 197// the back-compat fallback for any lane whose builder still writes there (rule 19). 198func gm_artifact(gate: *u8, out: *u8) -> i64 { 199 var o: i64 = gm_cat(out, 0, GM_ARTDIR) 200 o = gm_cat(out, o, gate) 201 o = gm_cat(out, o, ".sov.elf" as *u8) 202 let probe: *u8 = sys_mmap(GM_CAP) 203 if gg_read(out, probe, GM_CAP) > 0 { return o } 204 o = gm_cat(out, 0, GM_ARTALT) 205 o = gm_cat(out, o, gate) 206 o = gm_cat(out, o, ".sov.elf" as *u8) 207 return o 208} 209 210// build <gate> via the canonical builder; returns builder exit code 211func gm_build(gate: *u8) -> i64 { 212 let o: *u8 = sys_mmap(GM_CAP) 213 let ol: *i64 = sys_mmap(GM_SLOT) as *i64 214 // tr_run_capture takes a NUL-TERMINATED argv whose argv[0] is the PATH itself (nx_tool_run.nx:14). 215 // This differs from dep_run_capture (count-passing, argv[0]=first arg) as used by nx_buildonly.nx -- 216 // copying that convention here would exec the builder with the target in argv[0] and compile nothing. 217 let av: *i64 = sys_mmap(GM_SLOT) as *i64 218 av[0] = GM_BUILDER as i64 219 av[1] = gate as i64 220 av[2] = GM_BUILDONLY as i64 221 av[3] = 0 222 return tr_run_capture_to(GM_BUILDER, av, o, GM_CAP, ol, GM_BUILD_MS) 223} 224 225// run the built gate INSIDE THE SANDBOX, capture stdout+stderr; returns the gate's exit code. 226// ***THIS ORGAN RUNS ITS SUBJECT TWICE*** -- that is the whole point of judge-equivalence, and it is 227// also why migrating an EFFECTFUL gate executes its side effects twice. On 2026-08-06 that class of 228// run (via a coverage sweep) destroyed nishihost/tool_allowlist.conf and tools_cap_secret.key and 229// denied every capability in the estate. The denylist added afterwards is an ENUMERATION and therefore 230// a floor; this is the CONTAINMENT: cwd becomes _gatesandbox, one level under the buildroot, so a gate 231// reaching for ../<production-file> lands in the buildroot scratch tree instead of nishihost. 232// ⚠the artifact path is resolved AFTER the chdir, so a cwd-relative _build/ path must be rewritten to 233// ../_build/; the /tmp/ fallback is absolute and needs no rewrite. Getting this wrong would make every 234// subject unrunnable rather than unsafe -- loud, not silent, which is the correct failure direction. 235// ⚠HONEST LIMIT: a cwd sandbox cannot contain an ABSOLUTE path. It composes with the denylist, which 236// still gates this verb, and does not replace it. 237func gm_run(gate: *u8, out: *u8, outlen: *i64) -> i64 { 238 let ap: *u8 = sys_mmap(GM_PATH) 239 gm_artifact(gate, ap) 240 let sp: *u8 = sys_mmap(GM_PATH) 241 var o: i64 = 0 242 if ap[0] != (47 as u8) { o = gm_cat(sp, 0, "../" as *u8) } 243 o = gm_cat(sp, o, ap) 244 let av: *i64 = sys_mmap(64) as *i64 245 av[0] = sp as i64 246 av[1] = 0 247 return tr_run_capture_cwd(sp, av, out, GM_CAP, outlen, GM_RUN_MS, GM_SANDBOX) 248} 249 250// ---------- THE ORACLE (pure: no I/O, so it is hermetically gateable) ---------- 251// res[] slots are the GM_R_* consts above (GM_R_EXIT/JUDGE/VEC/EVID/BYTES + the PASS/FAIL/GREEN pairs); 252// the names ARE the contract -- do not re-document them as integers here, that is how the two drift apart. 253// dA/dB: evidence-file byte deltas; pass -1 for BOTH to mark clause (d) UNCHECKED. 254// returns 1 = ACCEPT (all applicable clauses hold), 0 = REFUSE 255func gm_clauses(outA: *u8, nA: i64, rcA: i64, outB: *u8, nB: i64, rcB: i64, dA: i64, dB: i64, res: *i64) -> i64 { 256 var i: i64 = 0 257 while i < GM_RESN { res[i] = 0; i = i + 1 } 258 259 if rcA == rcB { res[GM_R_EXIT] = 1 } 260 261 let gA: i64 = gg_line_green(outA, nA, "verdict=" as *u8, "GREEN" as *u8) 262 let gB: i64 = gg_line_green(outB, nB, "verdict=" as *u8, "GREEN" as *u8) 263 res[GM_R_GREENA] = gA 264 res[GM_R_GREENB] = gB 265 if gA == gB { res[GM_R_JUDGE] = 1 } 266 267 let pA: i64 = gm_count(outA, nA, "PASS" as *u8) 268 let pB: i64 = gm_count(outB, nB, "PASS" as *u8) 269 let fA: i64 = gm_count(outA, nA, "FAIL" as *u8) 270 let fB: i64 = gm_count(outB, nB, "FAIL" as *u8) 271 res[GM_R_PASSA] = pA 272 res[GM_R_PASSB] = pB 273 res[GM_R_FAILA] = fA 274 res[GM_R_FAILB] = fB 275 if pA == pB { if fA == fB { res[GM_R_VEC] = 1 } } 276 // VACUITY GUARD: when NEITHER side emits a PASS or FAIL token this clause compared NOTHING, and its 277 // IDENTICAL is agreement it never observed. Found in a real ACCEPT (pass=0 fail=0). It matters because 278 // the neg-control proving this oracle bites was caught SOLELY by clause (c). Declare UNCHECKED, the 279 // same honesty clause (d) already uses. ACCEPTANCE UNCHANGED -- this changes what the tool SAYS. 280 if pA == 0 { if pB == 0 { if fA == 0 { if fB == 0 { res[GM_R_VEC] = GM_UNCHECKED } } } } 281 // ENRICHMENT: 0 checks BEFORE -> N passing checks AFTER. A vector that SHRINKS, or that gains a 282 // FAIL, remains a divergence and is still refused -- growth is not a licence to regress. 283 if pA == 0 { if fA == 0 { if pB > 0 { if fB == 0 { res[GM_R_VEC] = GM_ENRICHED } } } } 284 285 // clause (d): UNCHECKED when the caller named no evidence file -> declared, never assumed clean 286 if dA < 0 { res[GM_R_EVID] = GM_UNCHECKED } else { if dA == dB { res[GM_R_EVID] = 1 } } 287 288 var same: i64 = 0 289 if nA == nB { 290 same = 1 291 var k: i64 = 0 292 while k < nA { if outA[k] != outB[k] { same = 0; k = nA } else { k = k + 1 } } 293 } 294 res[GM_R_BYTES] = same 295 296 var ok: i64 = 0 297 var vecok: i64 = 0 298 if res[GM_R_VEC] == 1 { vecok = 1 } 299 if res[GM_R_VEC] == GM_UNCHECKED { vecok = 1 } 300 if res[GM_R_VEC] == GM_ENRICHED { vecok = 1 } 301 if res[GM_R_EXIT] == 1 { if res[GM_R_JUDGE] == 1 { if vecok == 1 { ok = 1 } } } 302 if ok == 1 { if res[GM_R_EVID] == 0 { ok = 0 } } // (d) checked AND diverged -> refuse 303 return ok 304} 305 306// ---------- ANCHOR-ADOPTION oracle (pure, hermetically gateable). res[] = GM_A_* slots. 307// ACCEPT iff: exit identical + PASS/FAIL vector identical + BEFORE emitted no anchor + AFTER emits one 308// + the AFTER verdict AGREES with the exit code (gg sees GREEN exactly when exit==0). 309func an_clauses(outA: *u8, nA: i64, rcA: i64, outB: *u8, nB: i64, rcB: i64, res: *i64) -> i64 { 310 var i: i64 = 0 311 while i < GM_ARESN { res[i] = 0; i = i + 1 } 312 if rcA == rcB { res[GM_A_EXIT] = 1 } 313 let pA: i64 = gm_count(outA, nA, "PASS" as *u8) 314 let pB: i64 = gm_count(outB, nB, "PASS" as *u8) 315 let fA: i64 = gm_count(outA, nA, "FAIL" as *u8) 316 let fB: i64 = gm_count(outB, nB, "FAIL" as *u8) 317 if pA == pB { if fA == fB { res[GM_A_VEC] = 1 } } 318 if gm_count(outA, nA, "verdict=" as *u8) == 0 { res[GM_A_BEFORE_CLEAN] = 1 } 319 if gm_count(outB, nB, "verdict=" as *u8) > 0 { res[GM_A_AFTER_HAS] = 1 } 320 let gB: i64 = gg_line_green(outB, nB, "verdict=" as *u8, "GREEN" as *u8) 321 var wantg: i64 = 0 322 if rcB == 0 { wantg = 1 } 323 if gB == wantg { res[GM_A_AGREES] = 1 } 324 var ok: i64 = 1 325 i = 0 326 while i < GM_ARESN { if res[i] != 1 { ok = 0 } i = i + 1 } 327 return ok 328} 329 330func an_report(res: *i64, accepted: i64) -> i64 { 331 gv_puts(" exit-code............ " as *u8) 332 if res[GM_A_EXIT] == 1 { gv_puts("IDENTICAL\n" as *u8) } else { gv_puts("DIVERGED\n" as *u8) } 333 gv_puts(" PASS/FAIL vector..... " as *u8) 334 if res[GM_A_VEC] == 1 { gv_puts("IDENTICAL\n" as *u8) } else { gv_puts("DIVERGED\n" as *u8) } 335 gv_puts(" before anchor-free... " as *u8) 336 if res[GM_A_BEFORE_CLEAN] == 1 { gv_puts("YES\n" as *u8) } else { gv_puts("NO (already anchored?)\n" as *u8) } 337 gv_puts(" after emits anchor... " as *u8) 338 if res[GM_A_AFTER_HAS] == 1 { gv_puts("YES\n" as *u8) } else { gv_puts("NO\n" as *u8) } 339 gv_puts(" verdict==exit........ " as *u8) 340 if res[GM_A_AGREES] == 1 { gv_puts("AGREES\n" as *u8) } else { gv_puts("LIES (anchor contradicts exit)\n" as *u8) } 341 if accepted == 1 { gv_puts("\nNX-GATE-MIGRATE verdict=GREEN ANCHORED (gate is now machine-judgeable)\n" as *u8) } 342 else { gv_puts("\nNX-GATE-MIGRATE verdict=RED REFUSED (original restored)\n" as *u8) } 343 return 0 344} 345 346// anchor: same commit-or-restore driver shape as gm_verify, with the anchor oracle 347func gm_anchor(gate: *u8, ancsrc: *u8) -> i64 { 348 let src: *u8 = sys_mmap(GM_PATH) 349 if gm_resolve(gate, src) == 0 { gv_puts("GATE-MIGRATE-FAIL cannot resolve source for gate\n" as *u8); return GM_HARNESS } 350 let probe: *u8 = sys_mmap(GM_CAP) 351 let pn: i64 = gg_read(src, probe, GM_CAP) 352 if gm_count(probe, pn, "verdict=" as *u8) > 0 { 353 gv_puts("ALREADY-ANCHORED (source emits verdict=) -- nothing to do\n" as *u8) 354 return 0 355 } 356 let bak: *u8 = sys_mmap(GM_PATH) 357 var bo: i64 = gm_cat(bak, 0, src) 358 bo = gm_cat(bak, bo, ".premigrate" as *u8) 359 let outA: *u8 = sys_mmap(GM_CAP) 360 let outB: *u8 = sys_mmap(GM_CAP) 361 let lA: *i64 = sys_mmap(GM_SLOT) as *i64 362 let lB: *i64 = sys_mmap(GM_SLOT) as *i64 363 if gm_build(gate) != 0 { gv_puts("GATE-MIGRATE-FAIL original does not build\n" as *u8); return GM_HARNESS } 364 let rcA: i64 = gm_run(gate, outA, lA) 365 // A HANGING GATE IS NOT A MIGRATION CANDIDATE, and it MUST be refused HERE rather than reach the oracle. 366 // Ported from ws=gate-dry-d001 (1785524287) TOGETHER WITH the deadlines, because bounding the runs 367 // ALONE CREATES A WORSE BUG THAN IT FIXES: if the before-run times out the after-run almost certainly 368 // does too, and two timeouts compare EQUAL on every clause -- same exit (-5), same empty output, same 369 // zero PASS/FAIL vector -- so the oracle would hand back a confident GREEN ACCEPTED for a gate nobody 370 // has ever observed run. That is the vacuous-accept class: AGREEMENT BETWEEN TWO NON-OBSERVATIONS IS 371 // NOT EVIDENCE. Never land the timeout half of this pair without this half. 372 if rcA == TR_ERR_TIMEOUT { 373 gv_puts("GATE-MIGRATE-FAIL original HANGS past the run deadline -- not a migration candidate.\n" as *u8) 374 gv_puts(" Refusing here on purpose: two timeouts would compare identical and ACCEPT, certifying\n" as *u8) 375 gv_puts(" a migration of a gate that was never once observed to run.\n" as *u8) 376 return GM_HARNESS 377 } 378 if gm_copy(src, bak) < 0 { gv_puts("GATE-MIGRATE-FAIL cannot back up original\n" as *u8); return GM_HARNESS } 379 if gm_copy(ancsrc, src) < 0 { gm_copy(bak, src); gv_puts("GATE-MIGRATE-FAIL cannot stage anchored source (restored)\n" as *u8); return GM_HARNESS } 380 if gm_build(gate) != 0 { 381 gm_copy(bak, src) 382 gm_build(gate) 383 gv_puts(" anchored source DOES NOT BUILD -> restored\n" as *u8) 384 gv_puts("\nNX-GATE-MIGRATE verdict=RED REFUSED (original restored)\n" as *u8) 385 return GM_REFUSE 386 } 387 let rcB: i64 = gm_run(gate, outB, lB) 388 let res: *i64 = sys_mmap(GM_RES) as *i64 389 let ok: i64 = an_clauses(outA, lA[0], rcA, outB, lB[0], rcB, res) 390 gv_puts("nx_gate_migrate anchor " as *u8) 391 gv_puts(gate) 392 gv_puts("\n" as *u8) 393 an_report(res, ok) 394 if ok == 1 { return 0 } 395 gm_copy(bak, src) 396 gm_build(gate) 397 return GM_REFUSE 398} 399 400func gm_report(res: *i64, accepted: i64) -> i64 { 401 gv_puts(" (a) exit-code........ " as *u8) 402 if res[GM_R_EXIT] == 1 { gv_puts("IDENTICAL\n" as *u8) } else { gv_puts("DIVERGED\n" as *u8) } 403 gv_puts(" (b) judge verdict.... " as *u8) 404 if res[GM_R_JUDGE] == 1 { gv_puts("IDENTICAL (green=" as *u8); gv_num(res[GM_R_GREENA]); gv_puts(")\n" as *u8) } else { gv_puts("DIVERGED\n" as *u8) } 405 if res[GM_R_VEC] == GM_ENRICHED { gv_puts(" (c) PASS/FAIL vector. ENRICHED -- the original enumerated ZERO checks and the base class cannot emit GREEN with zero checks, so enumerating them is the ONLY expressible migration for this population. (a)+(b) are preserved and no FAIL was introduced; one-way, a shrink or a new FAIL still refuses. The raw token comparison on the next line therefore reads DIVERGED BY DESIGN: " as *u8) } else { gv_puts(" (c) PASS/FAIL vector. " as *u8) } 406 if res[GM_R_VEC] == 1 { gv_puts("IDENTICAL pass=" as *u8); gv_num(res[GM_R_PASSA]); gv_puts(" fail=" as *u8); gv_num(res[GM_R_FAILA]); gv_puts("\n" as *u8) } else { 407 if res[GM_R_VEC] < 0 { gv_puts("UNCHECKED (gate emits no PASS/FAIL tokens -- this clause compared nothing)\n" as *u8) } 408 else { gv_puts("DIVERGED\n" as *u8) } } 409 gv_puts(" (d) evidence effect.. " as *u8) 410 if res[GM_R_EVID] < 0 { gv_puts("UNCHECKED (no evidence file named)\n" as *u8) } else { 411 if res[GM_R_EVID] == 1 { gv_puts("PRESERVED\n" as *u8) } else { gv_puts("DIVERGED\n" as *u8) } } 412 gv_puts(" ... bytes............ " as *u8) 413 if res[GM_R_BYTES] == 1 { gv_puts("identical\n" as *u8) } else { gv_puts("DIFFER (expected; byte-proof would refuse this)\n" as *u8) } 414 if accepted == 1 { gv_puts("\nNX-GATE-MIGRATE verdict=GREEN ACCEPTED (judge-equivalence holds)\n" as *u8) } 415 else { gv_puts("\nNX-GATE-MIGRATE verdict=RED REFUSED (original restored)\n" as *u8) } 416 return 0 417} 418 419// ---------- verify: the commit-or-restore driver ---------- 420func gm_verify(gate: *u8, migsrc: *u8, evid: *u8) -> i64 { 421 let src: *u8 = sys_mmap(GM_PATH) 422 if gm_resolve(gate, src) == 0 { gv_puts("GATE-MIGRATE-FAIL cannot resolve source for gate\n" as *u8); return GM_HARNESS } 423 let bak: *u8 = sys_mmap(GM_PATH) 424 var bo: i64 = gm_cat(bak, 0, src) 425 bo = gm_cat(bak, bo, ".premigrate" as *u8) 426 427 let outA: *u8 = sys_mmap(GM_CAP) 428 let outB: *u8 = sys_mmap(GM_CAP) 429 let lA: *i64 = sys_mmap(GM_SLOT) as *i64 430 let lB: *i64 = sys_mmap(GM_SLOT) as *i64 431 432 // --- BEFORE --- 433 if gm_build(gate) != 0 { gv_puts("GATE-MIGRATE-FAIL original does not build -- not a migration candidate\n" as *u8); return GM_HARNESS } 434 var eA0: i64 = 0 - 1 435 var eA1: i64 = 0 - 1 436 if (evid as i64) != 0 { eA0 = gm_fsize(evid) } 437 let rcA: i64 = gm_run(gate, outA, lA) 438 // A HANGING GATE IS NOT A MIGRATION CANDIDATE, and it MUST be refused HERE rather than reach the oracle. 439 // Ported from ws=gate-dry-d001 (1785524287) TOGETHER WITH the deadlines, because bounding the runs 440 // ALONE CREATES A WORSE BUG THAN IT FIXES: if the before-run times out the after-run almost certainly 441 // does too, and two timeouts compare EQUAL on every clause -- same exit (-5), same empty output, same 442 // zero PASS/FAIL vector -- so the oracle would hand back a confident GREEN ACCEPTED for a gate nobody 443 // has ever observed run. That is the vacuous-accept class: AGREEMENT BETWEEN TWO NON-OBSERVATIONS IS 444 // NOT EVIDENCE. Never land the timeout half of this pair without this half. 445 if rcA == TR_ERR_TIMEOUT { 446 gv_puts("GATE-MIGRATE-FAIL original HANGS past the run deadline -- not a migration candidate.\n" as *u8) 447 gv_puts(" Refusing here on purpose: two timeouts would compare identical and ACCEPT, certifying\n" as *u8) 448 gv_puts(" a migration of a gate that was never once observed to run.\n" as *u8) 449 return GM_HARNESS 450 } 451 if (evid as i64) != 0 { eA1 = gm_fsize(evid) } 452 var dA: i64 = 0 - 1 453 if (evid as i64) != 0 { dA = eA1 - eA0 } 454 455 // --- SWAP (backup kept, rule 13) --- 456 if gm_copy(src, bak) < 0 { gv_puts("GATE-MIGRATE-FAIL cannot back up original\n" as *u8); return GM_HARNESS } 457 if gm_copy(migsrc, src) < 0 { gm_copy(bak, src); gv_puts("GATE-MIGRATE-FAIL cannot stage migrated source (restored)\n" as *u8); return GM_HARNESS } 458 459 // --- AFTER --- 460 let brc: i64 = gm_build(gate) 461 if brc != 0 { 462 gm_copy(bak, src) 463 gv_puts(" migrated source DOES NOT BUILD -> restored\n" as *u8) 464 gv_puts("\nNX-GATE-MIGRATE verdict=RED REFUSED (original restored)\n" as *u8) 465 return GM_REFUSE 466 } 467 var eB0: i64 = 0 - 1 468 var eB1: i64 = 0 - 1 469 if (evid as i64) != 0 { eB0 = gm_fsize(evid) } 470 let rcB: i64 = gm_run(gate, outB, lB) 471 if (evid as i64) != 0 { eB1 = gm_fsize(evid) } 472 var dB: i64 = 0 - 1 473 if (evid as i64) != 0 { dB = eB1 - eB0 } 474 475 let res: *i64 = sys_mmap(GM_RES) as *i64 476 let ok: i64 = gm_clauses(outA, lA[0], rcA, outB, lB[0], rcB, dA, dB, res) 477 gv_puts("nx_gate_migrate verify " as *u8) 478 gv_puts(gate) 479 gv_puts("\n" as *u8) 480 gm_report(res, ok) 481 if ok == 1 { return 0 } 482 gm_copy(bak, src) 483 gm_build(gate) 484 return GM_REFUSE 485} 486 487func gm_probe(gate: *u8) -> i64 { 488 let src: *u8 = sys_mmap(GM_PATH) 489 if gm_resolve(gate, src) == 0 { gv_puts("PROBE " as *u8); gv_puts(gate); gv_puts(" NO-SOURCE\n" as *u8); return GM_HARNESS } 490 let b: *u8 = sys_mmap(GM_CAP) 491 let n: i64 = gg_read(src, b, GM_CAP) 492 let inherits: i64 = gm_count(b, n, "gv_verdict" as *u8) 493 let builds: i64 = gm_build(gate) 494 let out: *u8 = sys_mmap(GM_CAP) 495 let ol: *i64 = sys_mmap(GM_SLOT) as *i64 496 var rc: i64 = 0 - 1 497 if builds == 0 { rc = gm_run(gate, out, ol) } 498 gv_puts("PROBE " as *u8) 499 gv_puts(gate) 500 gv_puts(" src_bytes=" as *u8) 501 gv_num(n) 502 gv_puts(" inherits_base=" as *u8) 503 if inherits > 0 { gv_puts("1" as *u8) } else { gv_puts("0" as *u8) } 504 gv_puts(" builds=" as *u8) 505 if builds == 0 { gv_puts("1" as *u8) } else { gv_puts("0" as *u8) } 506 gv_puts(" run_exit=" as *u8) 507 gv_num(rc) 508 gv_puts(" emits_verdict=" as *u8) 509 gv_num(gm_count(out, ol[0], "verdict=" as *u8)) 510 gv_puts("\n" as *u8) 511 if inherits > 0 { return 1 } 512 if builds != 0 { return 1 } 513 return 0 514} 515 516// ---------- selftest: hermetic, exercises the ORACLE on synthetic pairs (no shared prod state) ---------- 517func gm_selftest() -> i64 { 518 let ctr: *i64 = gv_ctr() 519 gv_head("nx_gate_migrate selftest -- the D001 judge-equivalence oracle (4 clauses)" as *u8) 520 let res: *i64 = sys_mmap(GM_RES) as *i64 521 522 // the REAL shapes: hand-rolled (no colon, domain verdict line) vs base class (colon, NX- summary) 523 let legacy: *u8 = "GATE x\n T1 thing PASS\n T2 other PASS\nK4GATE verdict=GREEN keystone=sv39 probe=4kpage\n" as *u8 524 let based: *u8 = "GATE x\n\n T1 thing: PASS\n T2 other: PASS\n\nNX-K4GATE passed 2/2 verdict=GREEN (keystone=sv39 probe=4kpage)\n" as *u8 525 let nl: i64 = gm_slen(legacy) 526 let nb: i64 = gm_slen(based) 527 528 let t1: i64 = gm_clauses(legacy, nl, 0, based, nb, 0, 0-1, 0-1, res) 529 gv_check("T1 real legacy-vs-base pair ACCEPTED (the migration D001 needs)" as *u8, t1, ctr) 530 531 var t5: i64 = 0 532 if res[GM_R_BYTES] == 0 { if t1 == 1 { t5 = 1 } } 533 gv_check("T5 ACCEPTED even though BYTES DIFFER (byte-proof would refuse: the whole point)" as *u8, t5, ctr) 534 535 // (a) exit divergence must refuse 536 var t2: i64 = 0 537 if gm_clauses(legacy, nl, 0, based, nb, 1, 0-1, 0-1, res) == 0 { t2 = 1 } 538 gv_check("T2 NEG exit-code divergence REFUSED" as *u8, t2, ctr) 539 540 // (b) judge divergence must refuse: same exit, but the migrated side judges RED 541 let redb: *u8 = "GATE x\n\n T1 thing: PASS\n T2 other: PASS\n\nNX-K4GATE passed 2/2 verdict=RED\n" as *u8 542 var t3: i64 = 0 543 if gm_clauses(legacy, nl, 0, redb, gm_slen(redb), 0, 0-1, 0-1, res) == 0 { t3 = 1 } 544 gv_check("T3 NEG judge divergence (GREEN->RED) REFUSED" as *u8, t3, ctr) 545 546 // (c) vector divergence must refuse: a check silently vanished 547 let lost: *u8 = "GATE x\n\n T1 thing: PASS\n\nNX-K4GATE passed 1/1 verdict=GREEN (n)\n" as *u8 548 var t4: i64 = 0 549 if gm_clauses(legacy, nl, 0, lost, gm_slen(lost), 0, 0-1, 0-1, res) == 0 { t4 = 1 } 550 gv_check("T4 NEG a check silently DISAPPEARING REFUSED (2 PASS vs 1)" as *u8, t4, ctr) 551 552 // NEG-CONTROL: a RED gate must stay RED and still be accepted (not GREEN-only reasoning) 553 let redl: *u8 = "GATE y\n T1 thing FAIL\nADGATE verdict=RED (t1=0)\n" as *u8 554 let redn: *u8 = "GATE y\n\n T1 thing: FAIL\n\nNX-ADGATE passed 0/1 verdict=RED\n" as *u8 555 var t6: i64 = 0 556 if gm_clauses(redl, gm_slen(redl), 1, redn, gm_slen(redn), 1, 0-1, 0-1, res) == 1 { t6 = 1 } 557 gv_check("T6 NEG-CONTROL RED gate stays RED and is ACCEPTED (oracle is not GREEN-only)" as *u8, t6, ctr) 558 559 // clause (d): evidence divergence refuses; absent evidence is declared UNCHECKED, never assumed 560 var t7: i64 = 0 561 if gm_clauses(legacy, nl, 0, based, nb, 0, 3, 5, res) == 0 { t7 = 1 } 562 gv_check("T7 evidence side-effect divergence (3 vs 5 bytes) REFUSED" as *u8, t7, ctr) 563 564 var t8: i64 = 0 565 gm_clauses(legacy, nl, 0, based, nb, 0, 0-1, 0-1, res) 566 if res[GM_R_EVID] < 0 { t8 = 1 } 567 gv_check("T8 absent evidence file reported UNCHECKED, not assumed-clean (L011 honesty)" as *u8, t8, ctr) 568 569 // ---- anchor-oracle teeth (seq585): the un-anchored shape is nx_aa_gate's real form 570 let noanch: *u8 = "AA gate\n T1 smooth PASS\n T2 det PASS\nAA-GATE GREEN -- ssaa\n" as *u8 571 let anched: *u8 = "AA gate\n T1 smooth PASS\n T2 det PASS\nAA-GATE verdict=GREEN -- ssaa\n" as *u8 572 let ares: *i64 = sys_mmap(GM_RES) as *i64 573 var t9: i64 = 0 574 if an_clauses(noanch, gm_slen(noanch), 0, anched, gm_slen(anched), 0, ares) == 1 { t9 = 1 } 575 gv_check("T9 anchor adoption ACCEPTED (no-anchor -> anchored, exit+vector held)" as *u8, t9, ctr) 576 577 // a LYING anchor: claims GREEN while the gate exits 1 -- worse than no anchor, must refuse 578 var t10: i64 = 0 579 if an_clauses(noanch, gm_slen(noanch), 1, anched, gm_slen(anched), 1, ares) == 0 { t10 = 1 } 580 gv_check("T10 NEG lying anchor (verdict=GREEN but exit 1) REFUSED" as *u8, t10, ctr) 581 582 let lostb: *u8 = "AA gate\n T1 smooth PASS\nAA-GATE verdict=GREEN -- ssaa\n" as *u8 583 var t11: i64 = 0 584 if an_clauses(noanch, gm_slen(noanch), 0, lostb, gm_slen(lostb), 0, ares) == 0 { t11 = 1 } 585 gv_check("T11 NEG anchor edit that drops a check REFUSED (vector 2 PASS vs 1)" as *u8, t11, ctr) 586 587 // RED gate anchored honestly: exit 1 both sides, verdict=RED (gg sees no GREEN) -> accept 588 let redna: *u8 = "AA gate\n T1 smooth FAIL\nAA-GATE RED\n" as *u8 589 let redan: *u8 = "AA gate\n T1 smooth FAIL\nAA-GATE verdict=RED\n" as *u8 590 var t12: i64 = 0 591 if an_clauses(redna, gm_slen(redna), 1, redan, gm_slen(redan), 1, ares) == 1 { t12 = 1 } 592 gv_check("T12 NEG-CONTROL RED gate anchored honestly (verdict=RED, exit 1) ACCEPTED" as *u8, t12, ctr) 593 594 // ---- ENRICHMENT teeth (2026-08-01): the zero-check population BOTH doors used to refuse. 595 // The composer-generated shape: ONE summary line, NO enumerated checks, exit 0, judged GREEN. 596 let zc: *u8 = "PIPEGATE all_ok=0 fail_at=3 steps=4 verdict=GREEN" as *u8 597 let enr: *u8 = "deployclass_gate T1 all-clean: PASS T2 abort point: PASS NX-PIPEGATE passed 2/2 verdict=GREEN (n)" as *u8 598 var t13: i64 = 0 599 if gm_clauses(zc, gm_slen(zc), 0, enr, gm_slen(enr), 0, 0-1, 0-1, res) == 1 { t13 = 1 } 600 gv_check("T13 ENRICHMENT 0 -> N passing checks ACCEPTED (base class cannot emit GREEN with zero checks, so this is the ONLY expressible migration for this population)" as *u8, t13, ctr) 601 602 let enrf: *u8 = "deployclass_gate T1 all-clean: PASS T2 abort point: FAIL NX-PIPEGATE passed 1/2 verdict=GREEN (n)" as *u8 603 var t14: i64 = 0 604 if gm_clauses(zc, gm_slen(zc), 0, enrf, gm_slen(enrf), 0, 0-1, 0-1, res) == 0 { t14 = 1 } 605 gv_check("T14 NEG enrichment that introduces a FAIL still REFUSED (growth is not a licence to regress)" as *u8, t14, ctr) 606 607 var t15: i64 = 0 608 if gm_clauses(enr, gm_slen(enr), 0, zc, gm_slen(zc), 0, 0-1, 0-1, res) == 0 { t15 = 1 } 609 gv_check("T15 NEG the REVERSE direction (N checks -> 0) REFUSED -- enrichment is ONE-WAY" as *u8, t15, ctr) 610 611 return gv_verdict("GATE-MIGRATE-GATE" as *u8, ctr, "judge-equivalence accepts byte-differing safe migrations, refuses exit/judge/vector/evidence divergence, RED stays RED; anchor rung: lying/lossy anchors refused, honest RED accepted" as *u8) 612} 613 614// ---- EFFECTFUL-GATE GUARD (2026-08-06) -------------------------------------------------------- 615// ***A BEHAVIOUR-PRESERVING JUDGE MUST RUN ITS SUBJECT -- SO MIGRATING AN EFFECTFUL GATE EXECUTES 616// ITS SIDE EFFECTS, TWICE.*** Purity was always a precondition of this organ and was never stated, 617// documented or enforced. MEASURED COST: nx_cap_grant_e2e_gate is a DESTRUCTIVE end-to-end test that 618// exercises the REAL capability system IN PLACE. Running it overwrote nishihost/tool_allowlist.conf 619// with a 52-byte fixture -- 750 GREEN rows destroyed, no .prev banked -- and left tools_cap_secret.key 620// ABSENT, so nx_tools_api fell back to its forgeable baked placeholder and EVERY capability in the 621// estate was denied. The sites stayed up; the entire MCP control plane did not. 622// ***AN END-TO-END GATE IS BY DEFINITION THE ONE THAT TOUCHES THE REAL SYSTEM*** -- the safest-sounding 623// word in the corpus marks the most dangerous gate to run, and nothing checked it. 624// FAIL-CLOSED, the same stance nx_gate_build_sweep takes on build admission: a guard that cannot read 625// its own list must REFUSE, because "could not check, so proceeded" is precisely how this happened. 626// The refusal always names the file AND the override, so it is a speed bump and never a dead end. 627const GM_EFFECT_A: *u8 = "knowledge/gate_effectful.conf" 628const GM_EFFECT_B: *u8 = "../knowledge/gate_effectful.conf" 629const GM_EFFECTFUL: i64 = 8 630const GM_EFFCAP: i64 = 65536 631const GM_HASH: i64 = 35 632 633func gm_eff_read(path: *u8, buf: *u8, cap: i64) -> i64 { 634 let fd: i64 = sys_openat_rd(path) 635 if fd < 0 { return 0 - 1 } 636 var n: i64 = 0 637 var go: i64 = 1 638 while go == 1 { 639 let r: i64 = sys_read(fd, ((buf as i64) + n) as *u8, cap - n) 640 if r <= 0 { go = 0 } else { n = n + r } 641 if n >= cap { go = 0 } 642 } 643 sys_close(fd) 644 return n 645} 646 647// -1 = list unreadable (caller MUST refuse) | 1 = listed effectful | 0 = not listed 648func gm_is_effectful(gate: *u8) -> i64 { 649 let b: *u8 = sys_mmap(GM_EFFCAP) 650 var n: i64 = gm_eff_read(GM_EFFECT_A, b, GM_EFFCAP - 1) 651 if n <= 0 { n = gm_eff_read(GM_EFFECT_B, b, GM_EFFCAP - 1) } 652 if n <= 0 { return 0 - 1 } 653 let gl: i64 = gm_slen(gate) 654 var i: i64 = 0 655 var found: i64 = 0 656 while i < n { 657 var e: i64 = i 658 var sc: i64 = 0 659 while sc == 0 { if e >= n { sc = 1 } else { if b[e] == (10 as u8) { sc = 1 } else { e = e + 1 } } } 660 if e > i { if b[i] != (GM_HASH as u8) { 661 var le: i64 = e 662 var trim: i64 = 1 663 while trim == 1 { 664 trim = 0 665 if le > i { 666 let c: i64 = b[le - 1] as i64 667 if c == 13 { le = le - 1; trim = 1 } 668 if c == 32 { le = le - 1; trim = 1 } 669 if c == 9 { le = le - 1; trim = 1 } 670 } 671 } 672 if le - i == gl { 673 var k: i64 = 0 674 var same: i64 = 1 675 while k < gl { if b[i + k] != gate[k] { same = 0; k = gl } else { k = k + 1 } } 676 if same == 1 { found = 1; i = n } 677 } 678 } } 679 if found == 0 { i = e + 1 } 680 } 681 return found 682} 683 684// the deliberate escape hatch: any argv token equal to allow-effectful 685func gm_eff_override(argc: i64, argv: *i64) -> i64 { 686 var i: i64 = 2 687 while i < argc { 688 let a: *u8 = argv[i] as *u8 689 if gm_count(a, gm_slen(a), "allow-effectful" as *u8) > 0 { return 1 } 690 i = i + 1 691 } 692 return 0 693} 694 695// 0 = proceed | nonzero = the exit code the caller must return 696func gm_eff_guard(gate: *u8, argc: i64, argv: *i64) -> i64 { 697 if gm_eff_override(argc, argv) == 1 { 698 gv_puts(" [effectful-guard] OVERRIDDEN by allow-effectful -- side effects WILL run, twice\n" as *u8) 699 return 0 700 } 701 let eff: i64 = gm_is_effectful(gate) 702 if eff == 0 - 1 { 703 gv_puts("GATE-MIGRATE REFUSED unreadable-effectful-list: cannot read knowledge/gate_effectful.conf (tried ./ and ../). This judge RUNS the subject TWICE to prove parity, so it must know whether the subject mutates production state BEFORE it does. Fail-closed by design. Ship the list, or pass allow-effectful to proceed deliberately.\n" as *u8) 704 return GM_EFFECTFUL 705 } 706 if eff == 1 { 707 gv_puts("GATE-MIGRATE REFUSED effectful-gate: this gate is listed in knowledge/gate_effectful.conf as mutating PRODUCTION state, and this verb RUNS it TWICE. On 2026-08-06 running nx_cap_grant_e2e_gate this way destroyed 750 tool_allowlist.conf rows and the cap signing key, denying every capability in the estate. Pass allow-effectful ONLY with an operator watching.\n" as *u8) 708 return GM_EFFECTFUL 709 } 710 return 0 711} 712 713func main(argc: i64, argv: *i64) -> i64 { 714 if argc < 2 { 715 gv_puts("usage: nx_gate_migrate {verify <gate> <migrated-src> [evidence-file] | anchor <gate> <anchored-src> | probe <gate> | selftest}\n" as *u8) 716 sys_exit(GM_USAGE) 717 return GM_USAGE 718 } 719 let verb: *u8 = argv[1] as *u8 720 if gm_count(verb, gm_slen(verb), "selftest" as *u8) > 0 { 721 let rc: i64 = gm_selftest() 722 sys_exit(rc) 723 return rc 724 } 725 // Every corpus-touching verb must know its tree before it resolves a single path; the selftest branch 726 // above is hermetic (synthetic buffers) and deliberately needs no root, so it stays reachable anywhere. 727 if gm_root_ensure() == 0 { sys_exit(GM_HARNESS); return GM_HARNESS } 728 if gm_count(verb, gm_slen(verb), "probe" as *u8) > 0 { 729 if argc < 3 { gv_puts("probe needs <gate>\n" as *u8); sys_exit(GM_USAGE); return GM_USAGE } 730 let rc2: i64 = gm_probe(argv[2] as *u8) 731 sys_exit(rc2) 732 return rc2 733 } 734 if gm_count(verb, gm_slen(verb), "anchor" as *u8) > 0 { 735 if argc < 4 { gv_puts("anchor needs <gate> <anchored-src>\n" as *u8); sys_exit(GM_USAGE); return GM_USAGE } 736 let eg: i64 = gm_eff_guard(argv[2] as *u8, argc, argv) 737 if eg != 0 { sys_exit(eg); return eg } 738 let rca: i64 = gm_anchor(argv[2] as *u8, argv[3] as *u8) 739 sys_exit(rca) 740 return rca 741 } 742 if gm_count(verb, gm_slen(verb), "verify" as *u8) > 0 { 743 if argc < 4 { gv_puts("verify needs <gate> <migrated-src>\n" as *u8); sys_exit(GM_USAGE); return GM_USAGE } 744 var ev: *u8 = 0 as *u8 745 if argc > 4 { ev = argv[4] as *u8 } 746 let eg3: i64 = gm_eff_guard(argv[2] as *u8, argc, argv) 747 if eg3 != 0 { sys_exit(eg3); return eg3 } 748 let rc3: i64 = gm_verify(argv[2] as *u8, argv[3] as *u8, ev) 749 sys_exit(rc3) 750 return rc3 751 } 752 gv_puts("GATE-MIGRATE-FAIL unknown verb\n" as *u8) 753 sys_exit(GM_USAGE) 754 return GM_USAGE 755}