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}