code wiki / _hdl_build / nx_gate_dry_apply.nx
nx_gate_dry_apply.nx source
↩ module page · 707 lines · 32284 B
1// nx_gate_dry_apply.nx -- THE D001 CANDIDATE GENERATOR (2026-07-31, ws=gate-dry-d001).
2//
3// WHAT IT IS: a MECHANICAL REWRITER that emits a MINIMAL-FORM migration candidate for one gate. It is
4// explicitly NOT a correct rewriter and does not need to be -- nx_gate_migrate verify is the safety net,
5// and that net is independently gate-proven to BITE on real files (a check-dropping edit was refused on
6// clause (c) while clauses (a) and (b) both waved it through, and the original was restored byte-intact).
7// So the division of labour is: this organ GUESSES, the verifier DECIDES, and a wrong guess costs nothing
8// because the verifier restores the original itself.
9//
10// THE MINIMAL FORM, and why it is the mechanizable one: leave EVERY check row byte-untouched and replace
11// ONLY the hand-rolled verdict tail. Clause (c) -- the PASS/FAIL vector, which is the hardest clause to
12// satisfy -- then cannot diverge BY CONSTRUCTION rather than by the author having hand-checked it. And the
13// verdict tail is precisely the debt D001 names ("2601 gates roll their own verdict emission"), so the
14// minimal form eats the debt in full. Full gv_check adoption is a separate, optional, later rung.
15//
16// SAFE BY REFUSAL (the nx_map_delegate discipline): every structural landmark must be found EXACTLY, and
17// the counter idiom must match one of the three known shapes. Anything else -> SKIP, emit nothing, exit 1.
18// A SKIP is a fine outcome and must never be dressed up as a migration.
19//
20// SHAPES RECOGNISED (measured over the local buildroot: 950 of 1390 candidates carry a counter idiom):
21// A tot[0] / tot[1] -- an mmap'd pair, pass in [0] and fail in [1] (nx_brand_snap_gate)
22// B var pass / var fail -- two scalars, pass and fail counted separately
23// C var pass / var tot -- pass counted, total counted
24//
25// usage: nx_gate_dry_apply <gate> <out-candidate-path>
26// then: nx_gate_migrate verify <gate> <out-candidate-path>
27// exit 0 = candidate written · 1 = SKIP (shape not recognised) · 2 = usage · 4 = harness/root failure
28// license_tier: ORIGINAL No hw writes (Rule 26).
29import "nx_syscalls.nx"
30import "nx_gate_verdict.nx"
31
32const DA_CAP: i64 = 262144
33const DA_PATH: i64 = 512
34const DA_OUT: i64 = 393216
35const DA_MODE: i64 = 420
36const DA_SKIP: i64 = 1
37const DA_USAGE: i64 = 2
38const DA_HARNESS: i64 = 4
39const DA_NOTE_MAX: i64 = 240
40
41func da_exists(path: *u8) -> i64 {
42 let fd: i64 = sys_openat_rd(path)
43 if fd < 0 { return 0 }
44 sys_close(fd)
45 return 1
46}
47
48func da_root_ensure() -> i64 {
49 if da_exists("runtime/nx_gate_verdict.nx" as *u8) == 1 { gv_puts("DRY-APPLY root=cwd\n" as *u8); return 1 }
50 if sys_chdir("buildroot" as *u8) == 0 {
51 if da_exists("runtime/nx_gate_verdict.nx" as *u8) == 1 { gv_puts("DRY-APPLY root=buildroot\n" as *u8); return 1 }
52 }
53 gv_puts("DRY-APPLY-FAIL no-corpus-root -- refusing to rewrite whatever the cwd happens to be.\n" as *u8)
54 return 0
55}
56
57func da_cat(dst: *u8, o: i64, s: *u8) -> i64 {
58 var i: i64 = 0
59 var p: i64 = o
60 while s[i] != (0 as u8) { dst[p] = s[i]; p = p + 1; i = i + 1 }
61 dst[p] = 0 as u8
62 return p
63}
64
65func da_slurp(path: *u8, buf: *u8, cap: i64) -> i64 {
66 let fd: i64 = sys_openat_rd(path)
67 if fd < 0 { return 0 - 1 }
68 var total: i64 = 0
69 var run: i64 = 1
70 while run == 1 {
71 if total >= cap { run = 0 } else {
72 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total)
73 if r <= 0 { run = 0 } else { total = total + r }
74 }
75 }
76 sys_close(fd)
77 return total
78}
79
80func da_write(path: *u8, buf: *u8, n: i64) -> i64 {
81 let fd: i64 = sys_openat_wr(path, DA_MODE)
82 if fd < 0 { return 0 - 1 }
83 var w: i64 = 0
84 var run: i64 = 1
85 while run == 1 {
86 if w >= n { run = 0 } else {
87 let r: i64 = sys_write(fd, ((buf as i64) + w) as *u8, n - w)
88 if r <= 0 { run = 0 } else { w = w + r }
89 }
90 }
91 sys_close(fd)
92 if w == n { return 0 }
93 return 0 - 1
94}
95
96func da_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
97
98// offset of the FIRST occurrence of needle at or after `from`, else -1
99func da_find(buf: *u8, n: i64, needle: *u8, from: i64) -> i64 {
100 let nl: i64 = da_slen(needle)
101 if nl == 0 { return 0 - 1 }
102 var i: i64 = from
103 while i + nl <= n {
104 var m: i64 = 1
105 var k: i64 = 0
106 while k < nl { if buf[i + k] != needle[k] { m = 0; k = nl } else { k = k + 1 } }
107 if m == 1 { return i }
108 i = i + 1
109 }
110 return 0 - 1
111}
112
113// offset of the LAST occurrence, else -1
114func da_find_last(buf: *u8, n: i64, needle: *u8) -> i64 {
115 var best: i64 = 0 - 1
116 var at: i64 = da_find(buf, n, needle, 0)
117 while at >= 0 {
118 best = at
119 at = da_find(buf, n, needle, at + 1)
120 }
121 return best
122}
123
124// start-of-line offset containing `off`
125func da_line_start(buf: *u8, off: i64) -> i64 {
126 var i: i64 = off
127 while i > 0 {
128 if buf[i - 1] == (10 as u8) { return i }
129 i = i - 1
130 }
131 return 0
132}
133
134// end-of-line offset (index of the '\n') for the line containing `off`; n if none
135func da_line_end(buf: *u8, n: i64, off: i64) -> i64 {
136 var i: i64 = off
137 while i < n {
138 if buf[i] == (10 as u8) { return i }
139 i = i + 1
140 }
141 return n
142}
143
144// insertion point for the new import = just past the newline of the LAST `import "` line
145func da_import_point(buf: *u8, n: i64) -> i64 {
146 let at: i64 = da_find_last(buf, n, "import \"" as *u8)
147 if at < 0 { return 0 - 1 }
148 let e: i64 = da_line_end(buf, n, at)
149 if e >= n { return 0 - 1 }
150 return e + 1
151}
152
153// derive the gv_verdict display name: strip a leading "nx_", uppercase, '_' -> '-'
154func da_name(gate: *u8, out: *u8) -> i64 {
155 var i: i64 = 0
156 if gate[0] == (110 as u8) { if gate[1] == (120 as u8) { if gate[2] == (95 as u8) { i = 3 } } }
157 // A large slice of the corpus is named with a leading underscore (_atomic_rewrite_gate, _mmu_gate).
158 // Mapping '_' -> '-' unconditionally would emit "NX--ATOMIC-REWRITE-GATE", a double dash that looks
159 // like a typo in every report the judge aggregates. Drop leading underscores before converting.
160 while gate[i] == (95 as u8) { i = i + 1 }
161 var p: i64 = 0
162 while gate[i] != (0 as u8) {
163 var c: i64 = gate[i] as i64
164 if c == 95 { c = 45 }
165 if c >= 97 { if c <= 122 { c = c - 32 } }
166 out[p] = c as u8
167 p = p + 1
168 i = i + 1
169 }
170 out[p] = 0 as u8
171 return p
172}
173
174// Recover the ORIGINAL domain note so rule 25 is honoured (rewrite better, never strip): take the text
175// that followed "verdict=GREEN" inside the same string literal, up to the closing quote. Escapes and
176// quotes are dropped rather than re-encoded -- a note is prose, and a mangled note must never be able to
177// break the emitted source. Empty result -> the caller falls back to a fixed honest note.
178func da_note(buf: *u8, n: i64, gat: i64, out: *u8) -> i64 {
179 var i: i64 = gat + 13
180 let e: i64 = da_line_end(buf, n, gat)
181 var p: i64 = 0
182 while i < e {
183 let c: i64 = buf[i] as i64
184 if c == 34 { i = e }
185 else {
186 // An escape is TWO characters. Skipping only the backslash leaks the escaped letter into
187 // the note -- caught in the wild, where ` verdict=GREEN\n` yielded the note "n". Skip both,
188 // so a gate whose verdict line carries no prose falls through to the honest default note
189 // instead of a one-character fragment that LOOKS like recovered domain detail.
190 if c == 92 { i = i + 2 }
191 else {
192 if p < DA_NOTE_MAX {
193 if c >= 32 { out[p] = c as u8; p = p + 1 }
194 }
195 i = i + 1
196 }
197 }
198 }
199 // trim leading separators/space so the note reads cleanly inside gv_verdict's parentheses
200 var s: i64 = 0
201 var go: i64 = 1
202 while go == 1 {
203 if s >= p { go = 0 }
204 else {
205 let c2: i64 = out[s] as i64
206 if c2 == 32 { s = s + 1 } else { if c2 == 45 { s = s + 1 } else { if c2 == 40 { s = s + 1 } else { go = 0 } } }
207 }
208 }
209 var q: i64 = 0
210 while s + q < p { out[q] = out[s + q]; q = q + 1 }
211 out[q] = 0 as u8
212 // A note made only of punctuation carries NO domain information -- observed in the wild as "===",
213 // the leftover of a banner line. Emitting it would dress a FAILED extraction up as recovered detail,
214 // which is worse than admitting the default: it looks like rule-25 compliance while delivering none.
215 // Require at least one letter; otherwise report empty and let the caller use the honest default.
216 var hasalpha: i64 = 0
217 var z: i64 = 0
218 while z < q {
219 let cc: i64 = out[z] as i64
220 if cc >= 65 { if cc <= 90 { hasalpha = 1 } }
221 if cc >= 97 { if cc <= 122 { hasalpha = 1 } }
222 z = z + 1
223 }
224 if hasalpha == 0 { out[0] = 0 as u8; return 0 }
225 return q
226}
227
228func main(argc: i64, argv: *i64) -> i64 {
229 if argc < 3 {
230 gv_puts("usage: nx_gate_dry_apply <gate> <out-candidate-path>\n" as *u8)
231 gv_puts(" emits a MINIMAL-FORM migration candidate; prove it with nx_gate_migrate verify.\n" as *u8)
232 return DA_USAGE
233 }
234 if da_root_ensure() == 0 { return DA_HARNESS }
235 let gate: *u8 = argv[1] as *u8
236 let outp: *u8 = argv[2] as *u8
237
238 let src: *u8 = sys_mmap(DA_PATH)
239 var o: i64 = da_cat(src, 0, "runtime/_hdl_build/" as *u8)
240 o = da_cat(src, o, gate)
241 o = da_cat(src, o, ".nx" as *u8)
242 let buf: *u8 = sys_mmap(DA_CAP)
243 var n: i64 = da_slurp(src, buf, DA_CAP)
244 if n <= 0 {
245 o = da_cat(src, 0, "runtime/" as *u8)
246 o = da_cat(src, o, gate)
247 o = da_cat(src, o, ".nx" as *u8)
248 n = da_slurp(src, buf, DA_CAP)
249 }
250 if n <= 0 { gv_puts("DRY-APPLY NO-SOURCE " as *u8); gv_puts(gate); gv_puts("\n" as *u8); return DA_HARNESS }
251
252 if da_find(buf, n, "nx_gate_verdict" as *u8, 0) >= 0 {
253 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate); gv_puts(" -- already inherits the base class\n" as *u8)
254 return DA_SKIP
255 }
256
257 let gat: i64 = da_find_last(buf, n, "verdict=GREEN" as *u8)
258 if gat < 0 {
259 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate); gv_puts(" -- no verdict=GREEN tail (anchor rung, not this one)\n" as *u8)
260 return DA_SKIP
261 }
262 let imp: i64 = da_import_point(buf, n)
263 if imp < 0 {
264 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate); gv_puts(" -- no import block to extend\n" as *u8)
265 return DA_SKIP
266 }
267 var tail: i64 = da_line_start(buf, gat)
268 if tail <= imp {
269 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate); gv_puts(" -- verdict tail precedes the imports (unexpected layout)\n" as *u8)
270 return DA_SKIP
271 }
272
273 // ---- MULTI-LINE GUARD: move the cut UP to the `if` that OPENS the verdict block ----
274 // MEASURED, not assumed: of the gates still unmigrated, only 6 carry an `if <a> <op> <b>` ON the
275 // verdict=GREEN line, while 196 carry NO `if` there at all. Those are the multi-line shape
276 // if pass == N {
277 // ...verdict=GREEN...; sys_exit(0); return 0
278 // }
279 // ...verdict=RED...; sys_exit(1); return 1
280 // Cutting at the GREEN line copies the STILL-OPEN `if` and deletes the RED arm and main's closing
281 // brace -- precisely the damage this applier did to nx_swarm_admit_gate before the brace guard
282 // existed. Cutting at the `if` LINE instead removes the ENTIRE construct, both arms together,
283 // which is correct because gv_verdict emits GREEN and RED itself.
284 //
285 // The heuristic is deliberately loose (a line containing both `if ` and `{`) because it does not
286 // have to be sound on its own: the brace-DELTA guard re-checks the finished candidate and REFUSES
287 // anything unbalanced, and nx_gate_migrate verify then re-checks behaviour. A loose proposal behind
288 // two fail-closed checks is safe; a clever proposal with no check is not.
289 var vle: i64 = tail
290 var vg: i64 = 1
291 while vg == 1 {
292 if vle >= n { vg = 0 }
293 else {
294 if buf[vle] as i64 == 10 { vg = 0 }
295 else { vle = vle + 1 }
296 }
297 }
298 if da_find(buf, vle, "if " as *u8, tail) < 0 {
299 var up: i64 = tail
300 var steps: i64 = 0
301 var hunting: i64 = 1
302 while hunting == 1 {
303 if steps >= 6 { hunting = 0 }
304 else {
305 if up <= (imp + 2) { hunting = 0 }
306 else {
307 let ps0: i64 = da_line_start(buf, up - 2)
308 var pe0: i64 = ps0
309 var g2: i64 = 1
310 while g2 == 1 {
311 if pe0 >= n { g2 = 0 }
312 else {
313 if buf[pe0] as i64 == 10 { g2 = 0 }
314 else { pe0 = pe0 + 1 }
315 }
316 }
317 let hasif0: i64 = da_find(buf, pe0, "if " as *u8, ps0)
318 if hasif0 >= 0 {
319 let hasbr0: i64 = da_find(buf, pe0, "{" as *u8, ps0)
320 if hasbr0 >= 0 {
321 if ps0 > imp { tail = ps0 }
322 hunting = 0
323 }
324 }
325 up = ps0
326 steps = steps + 1
327 }
328 }
329 }
330 }
331
332 // ---- EVIDENCE-SIDE-EFFECT GUARD --------------------------------------------------------------
333 // This applier works by DELETING everything from the verdict line to end-of-main and re-emitting a
334 // gv_verdict tail. That is safe for pure printing, but some gates also write an append-only evidence
335 // log in exactly that region (observed on _eoe_gate: g_fp(lfd,...) followed by sys_close(lfd)).
336 // Deleting those writes destroys the gate's evidence trail.
337 // WHY THE ORACLE CANNOT BE RELIED ON TO CATCH IT: clause (d) is the evidence clause, and it reports
338 // UNCHECKED unless the CALLER names an evidence file -- which the batch driver does not. So an
339 // evidence-destroying rewrite can pass (a), (b) and (c) and be ACCEPTED. The safety net has a hole
340 // exactly here, so the generator must not walk into it. REFUSE rather than rely on a clause that is
341 // switched off by default.
342 var apmode: i64 = 0
343 if da_find(buf, n, "sys_close(" as *u8, tail) >= 0 {
344 apmode = 1
345 // ---- APPEND MODE (2026-08-01): DO NOT DELETE THE EVIDENCE, CUT ONLY THE FINAL RETURN ----
346 // The refusal above was RIGHT about the danger and WRONG to stop. 129 gates -- the largest
347 // actionable family in the R4 backlog -- were unmigratable purely because their verdict tail
348 // also writes a durable log, and this applier's only mode was DELETE-FROM-HERE.
349 //
350 // The safe move is not to classify which statements are evidence and which are verdict; that
351 // needs a heuristic ("gw(1,..) is console, gw(lf,..) is log") that WILL be wrong somewhere, and
352 // a wrong transform here silently destroys an audit trail. Instead: KEEP EVERY BYTE of the tail
353 // and move the cut to the FINAL return statement. The gate's own console line and its log write
354 // both survive untouched; only the bare return is replaced by the gv_verdict emission.
355 //
356 // Cost: the gate prints its verdict twice -- its own wording, then the contract line. That is
357 // redundant, not wrong, and it is INFORMATION-PRESERVING. A duplicated line is a far cheaper
358 // defect than a deleted evidence trail.
359 // Gain: it now inherits the base class, so nx_gate_green can read it and a harness.jrnl frame
360 // exists -- which is the whole point of D001.
361 //
362 // Exit code: gv_verdict derives rc from ctr[0] vs ctr[1], so a gate that used to `return 0`
363 // unconditionally now returns the TRUTH. On a passing gate the two agree, which is what
364 // nx_gate_migrate verify compares; on a failing one the new code is strictly more honest.
365 // The brace-delta guard below still protects this path: if the final return sits inside a
366 // guard block, the cut unbalances the braces and the whole candidate is REFUSED.
367 let rp: i64 = da_find_last(buf, n, "\n return " as *u8)
368 if rp < 0 {
369 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate)
370 gv_puts(" -- verdict tail writes a durable log AND has no top-level final return to cut,\n" as *u8)
371 gv_puts(" so there is no way to append the contract without deleting evidence. REFUSED.\n" as *u8)
372 return DA_SKIP
373 }
374 if (rp + 1) <= imp {
375 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate)
376 gv_puts(" -- final return precedes the import block (unexpected layout)\n" as *u8)
377 return DA_SKIP
378 }
379 tail = rp + 1
380
381 // ---- walk the cut UP over one-line early-return guards ----
382 // CAUGHT BY READING THE FIRST CANDIDATE, not by review: cutting at the FINAL return leaves a
383 // preceding one-liner like
384 // if passed == BG_ROWS { return 0 }
385 // in place, so a PASSING gate returns BEFORE reaching gv_verdict and the contract is emitted
386 // only on FAILURE -- exactly backwards, and BRACE-BALANCED, so the delta guard cannot see it.
387 // That guard line IS part of the hand-rolled verdict emission, so it must be cut with it; the
388 // evidence-log statements sit ABOVE it and are still preserved untouched.
389 var moved: i64 = 1
390 while moved == 1 {
391 moved = 0
392 if tail > (imp + 2) {
393 let ps: i64 = da_line_start(buf, tail - 2)
394 if ps > imp {
395 let hasif: i64 = da_find(buf, tail, "if " as *u8, ps)
396 if hasif >= 0 {
397 let hasret: i64 = da_find(buf, tail, "return" as *u8, ps)
398 if hasret >= 0 { tail = ps; moved = 1 }
399 }
400 }
401 }
402 }
403 }
404
405 // ---- counter idiom detection. Ordered most-specific first; unknown shape = SKIP, never a guess. ----
406 var pexpr: *u8 = 0 as *u8
407 var texpr: *u8 = 0 as *u8
408 if da_find(buf, n, "tot[0]" as *u8, 0) >= 0 {
409 if da_find(buf, n, "tot[1]" as *u8, 0) >= 0 {
410 pexpr = "tot[0]" as *u8
411 texpr = "tot[0] + tot[1]" as *u8
412 }
413 }
414 if (pexpr as i64) == 0 {
415 if da_find(buf, n, "var pass: i64" as *u8, 0) >= 0 {
416 if da_find(buf, n, "var fail: i64" as *u8, 0) >= 0 {
417 pexpr = "pass" as *u8
418 texpr = "pass + fail" as *u8
419 }
420 }
421 }
422 // Shape D was found by MEASURING the skips rather than guessing at them: of the gates that carry
423 // "var pass: i64" but were declined, the dominant partner is "var total: i64", and it is the LARGEST
424 // family in the whole corpus -- 499 gates, against 137 for pass/fail, 109 for pass/tot and 30 for
425 // tot[0]/tot[1]. Omitting it was most of the applier's missing recall.
426 // NOTE on ordering: "var total: i64" cannot be confused with "var tot: i64" by substring search
427 // (the literal includes the colon), so these three branches are mutually exclusive by construction.
428 if (pexpr as i64) == 0 {
429 if da_find(buf, n, "var pass: i64" as *u8, 0) >= 0 {
430 if da_find(buf, n, "var total: i64" as *u8, 0) >= 0 {
431 pexpr = "pass" as *u8
432 texpr = "total" as *u8
433 }
434 }
435 }
436 if (pexpr as i64) == 0 {
437 if da_find(buf, n, "var pass: i64" as *u8, 0) >= 0 {
438 if da_find(buf, n, "var tot: i64" as *u8, 0) >= 0 {
439 pexpr = "pass" as *u8
440 texpr = "tot" as *u8
441 }
442 }
443 }
444 // Shape E: the total is a LITERAL, not a variable -- "var pass: i64" counted up and then compared
445 // against a hard number, e.g. `if pass == 4 { ... }`. Measured at 296 gates with no total/fail/tot
446 // variable at all, 222 of them carrying a verdict tail, so it is the second-largest family and it
447 // looks like "no counter idiom" to any check that only hunts for a second variable.
448 // The literal is read from the gate's OWN comparison, so the total can never disagree with the
449 // gate's own definition of complete -- and if it somehow did, clause (a) would refuse the migration.
450 let litbuf: *u8 = sys_mmap(64)
451 if (pexpr as i64) == 0 {
452 if da_find(buf, n, "var pass: i64" as *u8, 0) >= 0 {
453 var lat: i64 = da_find(buf, n, "if pass == " as *u8, 0)
454 var adv: i64 = 11
455 if lat < 0 {
456 lat = da_find(buf, n, "if pass==" as *u8, 0)
457 adv = 9
458 }
459 if lat >= 0 {
460 var d: i64 = lat + adv
461 var q2: i64 = 0
462 var go2: i64 = 1
463 while go2 == 1 {
464 if d >= n { go2 = 0 }
465 else {
466 if q2 >= 32 { go2 = 0 }
467 else {
468 let ch: i64 = buf[d] as i64
469 if ch >= 48 {
470 if ch <= 57 { litbuf[q2] = buf[d]; q2 = q2 + 1; d = d + 1 }
471 else { go2 = 0 }
472 } else { go2 = 0 }
473 }
474 }
475 }
476 litbuf[q2] = 0 as u8
477 if q2 > 0 {
478 pexpr = "pass" as *u8
479 texpr = litbuf
480 }
481 }
482 }
483 }
484
485 // In APPEND MODE the cut point IS the verdict guard, so that guard's operands are AUTHORITATIVE and
486 // shapes A-E must not win. PROVEN NECESSARY, not defensive: on nx_browser_gate shape D matched the
487 // names pass/total from somewhere else in the file while the real guard was `if passed == BG_ROWS`,
488 // so the candidate referenced two variables that do not exist at that point and DID NOT BUILD.
489 // nx_gate_migrate verify caught it and restored the original -- the safety net held -- but the
490 // generator should not have emitted it. Discard the earlier guess and let shape F read the truth.
491 if apmode == 1 { pexpr = 0 as *u8 }
492 if (pexpr as i64) == 0 {
493 // ---- Shape F (2026-08-01): READ THE OPERANDS FROM THE GATE'S OWN GUARD ----
494 // MEASURED over the 322 unmigrated gates that carry a verdict=GREEN tail, not guessed:
495 // ok == 1 -> 87 · fails == 0 -> 36 · pass == ttl -> 20 · green == 1 -> 3 · long tail after.
496 // Shapes A-E hardcode the NAMES (pass/tot/total/fail), so a gate that merely SPELLS its
497 // counter differently reads as "no counter idiom" while its comparison sits right there on
498 // the line. Rather than bolt on five more names, take both operands from the gate's OWN
499 // comparison -- the same principle shape E already uses for its literal, so the total can
500 // never disagree with the gate's own definition of complete.
501 //
502 // REFUSED BY CONSTRUCTION: a right-hand side of literal 0 (the `fails == 0` family, 36
503 // gates). gv_verdict scores ctr[1]==0 as RED by design -- zero teeth is never GREEN -- so
504 // mapping fails/0 straight onto it would INVERT every one of those gates from GREEN to RED.
505 // They need a DERIVED boolean, which is a separate rung, not a wider regex here.
506 //
507 // Only the ONE-LINER guard is reachable: for a multi-line guard the `if` is not on this
508 // line, so no match is found -- and those are refused by the brace-delta guard anyway.
509 var le: i64 = tail
510 var g3: i64 = 1
511 while g3 == 1 {
512 if le >= n { g3 = 0 }
513 else {
514 if buf[le] as i64 == 10 { g3 = 0 }
515 else { le = le + 1 }
516 }
517 }
518 let ifp: i64 = da_find(buf, le, "if " as *u8, tail)
519 if ifp >= 0 {
520 let abuf: *u8 = sys_mmap(64)
521 let bbuf: *u8 = sys_mmap(64)
522 var d3: i64 = ifp + 3
523 var s1: i64 = 1
524 while s1 == 1 {
525 if d3 >= le { s1 = 0 }
526 else {
527 if buf[d3] as i64 == 32 { d3 = d3 + 1 }
528 else { s1 = 0 }
529 }
530 }
531 var qa: i64 = 0
532 var s2: i64 = 1
533 while s2 == 1 {
534 if d3 >= le { s2 = 0 }
535 else {
536 if qa >= 60 { s2 = 0 }
537 else {
538 let ca: i64 = buf[d3] as i64
539 var isa: i64 = 0
540 if ca >= 97 { if ca <= 122 { isa = 1 } }
541 if ca >= 65 { if ca <= 90 { isa = 1 } } // UPPERCASE: totals are often CONSTANTS (BG_ROWS)
542 if ca == 95 { isa = 1 }
543 if ca >= 48 { if ca <= 57 { isa = 1 } }
544 if isa == 1 { abuf[qa] = buf[d3]; qa = qa + 1; d3 = d3 + 1 }
545 else { s2 = 0 }
546 }
547 }
548 }
549 abuf[qa] = 0 as u8
550 var s3: i64 = 1
551 while s3 == 1 {
552 if d3 >= le { s3 = 0 }
553 else {
554 if buf[d3] as i64 == 32 { d3 = d3 + 1 }
555 else { s3 = 0 }
556 }
557 }
558 var okeq: i64 = 0
559 if d3 + 1 < le {
560 if buf[d3] as i64 == 61 {
561 if buf[d3 + 1] as i64 == 61 { okeq = 1; d3 = d3 + 2 }
562 }
563 }
564 if okeq == 1 {
565 var s4: i64 = 1
566 while s4 == 1 {
567 if d3 >= le { s4 = 0 }
568 else {
569 if buf[d3] as i64 == 32 { d3 = d3 + 1 }
570 else { s4 = 0 }
571 }
572 }
573 var qb: i64 = 0
574 var s5: i64 = 1
575 while s5 == 1 {
576 if d3 >= le { s5 = 0 }
577 else {
578 if qb >= 60 { s5 = 0 }
579 else {
580 let cb: i64 = buf[d3] as i64
581 var isb: i64 = 0
582 if cb >= 97 { if cb <= 122 { isb = 1 } }
583 if cb >= 65 { if cb <= 90 { isb = 1 } } // UPPERCASE: `if passed == BG_ROWS`
584 if cb == 95 { isb = 1 }
585 if cb >= 48 { if cb <= 57 { isb = 1 } }
586 if isb == 1 { bbuf[qb] = buf[d3]; qb = qb + 1; d3 = d3 + 1 }
587 else { s5 = 0 }
588 }
589 }
590 }
591 bbuf[qb] = 0 as u8
592 var zrhs: i64 = 0
593 if qb == 1 {
594 if bbuf[0] as i64 == 48 { zrhs = 1 }
595 }
596 if zrhs == 1 {
597 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate)
598 gv_puts(" -- guard is <x> == 0 (the fails==0 family, 36 gates). gv_verdict scores\n" as *u8)
599 gv_puts(" ctr[1]==0 as RED by design, so a direct operand map would INVERT this gate\n" as *u8)
600 gv_puts(" from GREEN to RED. Needs a derived boolean -- a separate rung.\n" as *u8)
601 return DA_SKIP
602 }
603 if qa > 0 {
604 if qb > 0 {
605 pexpr = abuf
606 texpr = bbuf
607 }
608 }
609 }
610 }
611 }
612 if (pexpr as i64) == 0 {
613 gv_puts("DRY-APPLY SKIP " as *u8); gv_puts(gate); gv_puts(" -- counter idiom not recognised (A tot[0]/tot[1], B pass/fail, C pass/tot, D pass/total, E pass/literal, F own-guard operands)\n" as *u8)
614 return DA_SKIP
615 }
616
617 let nm: *u8 = sys_mmap(DA_PATH)
618 da_name(gate, nm)
619 let note: *u8 = sys_mmap(DA_PATH)
620 let nl: i64 = da_note(buf, n, gat, note)
621
622 let out: *u8 = sys_mmap(DA_OUT)
623 var p: i64 = 0
624 var i: i64 = 0
625 while i < imp { out[p] = buf[i]; p = p + 1; i = i + 1 }
626 p = da_cat(out, p, "import \"nx_gate_verdict.nx\"\n" as *u8)
627 i = imp
628 while i < tail { out[p] = buf[i]; p = p + 1; i = i + 1 }
629 p = da_cat(out, p, " // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check\n" as *u8)
630 p = da_cat(out, p, " // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled\n" as *u8)
631 p = da_cat(out, p, " // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.\n" as *u8)
632 p = da_cat(out, p, " let ctr__dry: *i64 = gv_ctr()\n" as *u8)
633 p = da_cat(out, p, " ctr__dry[0] = " as *u8)
634 p = da_cat(out, p, pexpr)
635 p = da_cat(out, p, "\n ctr__dry[1] = " as *u8)
636 p = da_cat(out, p, texpr)
637 p = da_cat(out, p, "\n let rc__dry: i64 = gv_verdict(\"" as *u8)
638 p = da_cat(out, p, nm)
639 p = da_cat(out, p, "\" as *u8, ctr__dry, \"" as *u8)
640 if nl > 0 { p = da_cat(out, p, note) }
641 else { p = da_cat(out, p, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) }
642 p = da_cat(out, p, "\" as *u8)\n" as *u8)
643 p = da_cat(out, p, " sys_exit(rc__dry)\n" as *u8)
644 p = da_cat(out, p, " return rc__dry\n}\n" as *u8)
645
646 // ---- FAIL-CLOSED STRUCTURAL GUARD (added 2026-08-01 after this applier DAMAGED a real gate) ----
647 // The cut point is the line holding "verdict=GREEN". When that emission is NESTED inside a guard --
648 // if pass == 3 { GREEN; exit 0 } RED; exit 1
649 // -- cutting at the GREEN line copies the still-OPEN `if`, then deletes BOTH the RED path and main's
650 // closing brace. The result compiled anyway (nx_cc fails open on an unclosed body at EOF) and
651 // nx_gate_migrate verify called it GREEN, because a gate that PASSES today never exercises its RED
652 // path -- so the deleted branch was never compared. That is a gate which goes SILENT exactly when it
653 // should fail: the worst possible direction for an instrument to break.
654 //
655 // Comparing DELTAS, not absolute balance: the original is a file that compiles, so it is the
656 // reference. Only a tail is replaced, so the brace delta MUST be preserved exactly. A naive count is
657 // sound here precisely because it is applied identically to both sides -- any skew from braces inside
658 // strings or comments cancels. REFUSE, never emit-and-hope: an unbalanced candidate is not a
659 // migration, it is damage with a plausible shape.
660 var ob: i64 = 0
661 var cb: i64 = 0
662 var k: i64 = 0
663 while k < n {
664 let c: i64 = buf[k] as i64
665 if c == 123 { ob = ob + 1 }
666 if c == 125 { cb = cb + 1 }
667 k = k + 1
668 }
669 var ob2: i64 = 0
670 var cb2: i64 = 0
671 k = 0
672 while k < p {
673 let c2: i64 = out[k] as i64
674 if c2 == 123 { ob2 = ob2 + 1 }
675 if c2 == 125 { cb2 = cb2 + 1 }
676 k = k + 1
677 }
678 if (ob - cb) != (ob2 - cb2) {
679 gv_puts("DRY-APPLY REFUSE " as *u8); gv_puts(gate)
680 gv_puts(" -- candidate brace delta differs from the original (orig " as *u8)
681 gv_num(ob); gv_puts("/" as *u8); gv_num(cb)
682 gv_puts(", candidate " as *u8); gv_num(ob2); gv_puts("/" as *u8); gv_num(cb2)
683 gv_puts(").\n The verdict tail is NESTED inside a guard, so the cut deleted a branch and/or a\n" as *u8)
684 gv_puts(" closing brace. NOTHING WRITTEN -- the gate source is untouched. This layout needs the\n" as *u8)
685 gv_puts(" cut point moved to the enclosing `if` line, which this applier does not yet do.\n" as *u8)
686 return DA_SKIP
687 }
688
689 if da_write(outp, out, p) != 0 {
690 gv_puts("DRY-APPLY-FAIL cannot write candidate\n" as *u8)
691 return DA_HARNESS
692 }
693 gv_puts("DRY-APPLY CANDIDATE " as *u8)
694 gv_puts(gate)
695 gv_puts(" shape=" as *u8)
696 gv_puts(pexpr)
697 gv_puts(" bytes=" as *u8)
698 gv_num(p)
699 gv_puts(" -> " as *u8)
700 gv_puts(outp)
701 gv_puts("\n NOT A MIGRATION YET. Prove it: nx_gate_migrate verify " as *u8)
702 gv_puts(gate)
703 gv_puts(" " as *u8)
704 gv_puts(outp)
705 gv_puts("\n" as *u8)
706 return 0
707}