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}