code wiki / _hdl_build / nx_gen_cid_gate.nx

nx_gen_cid_gate.nx source

↩ module page · 162 lines · 8083 B

1// nx_gen_cid_gate.nx -- REFEREE for the GALLERY CID CANON (debt 1785881333 / 1785881657). 2// 3// WHAT IT PROTECTS. The gallery CID is NOT a hash of the pixels, despite the name: it is a hash of a 4// CANONICAL FORM built from the PNG's tEXt fields. Twice on 2026-08-04 that canon was missing a 5// field that genuinely changes the image, and the consequence each time was SILENT DATA LOSS -- the 6// second render collided onto the first cid and gp_one's openat_wr truncated the earlier blob: 7// * resolution: 512x512 (md5 a9fa671a) then 768x1024 under the SAME cid -> the 512 image was gone. 8// * cfg_scale: cfg 1.0 (md5 f10849c2) vs 5.0 (md5 6471d466), two different pictures, ONE cid. 9// Fixed by adding `size` and then `args` (the exact generator-facing argument string) to the canon. 10// 11// ★WHY A GATE AND NOT JUST THE FIX: the second collision existed the whole time the first was being 12// celebrated as fixed. Nothing was checking, so a narrower trigger survived. This gate encodes the 13// INVARIANT rather than the two instances: ANY tEXt field that differs must move the cid. 14// 15// ★HERMETIC AND GPU-FREE. It builds tiny synthetic PNGs in memory and drives the REAL 16// png_insert_genrec + nx_store_ingest_compute_cid -- the same code the daemon runs. No render, no 17// engine, no network, so it is safe on any cadence. (A gate that needs a GPU is a gate nobody runs.) 18// 19// license_tier: ORIGINAL expect_exit: 0 20// module: nishi-core.gen.cidgate 21import "nx_syscalls.nx" 22import "nx_png_textw.nx" 23import "nx_store_ingest.nx" 24import "nx_gate_verdict.nx" 25 26const CG_BUF: i64 = 262144 27 28func cw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 29func cn(v: i64) -> i64 { 30 let t: *u8 = sys_mmap(32); let b: *u8 = sys_mmap(32) 31 var m: i64 = v; var k: i64 = 0 32 if m == 0 { t[0] = 48 as u8; k = 1 } 33 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 34 var i: i64 = 0 35 while i < k { b[i] = t[k - 1 - i]; i = i + 1 } 36 sys_write(1, b, k); return 0 37} 38 39// minimal but STRUCTURALLY VALID PNG: signature + IHDR + IEND. png_insert_genrec splices the tEXt 40// chunks in before IEND, exactly as it does for a real render. 41func cg_be32(b: *u8, o: i64, v: i64) -> i64 { 42 b[o] = ((v >> 24) & 0xff) as u8; b[o+1] = ((v >> 16) & 0xff) as u8 43 b[o+2] = ((v >> 8) & 0xff) as u8; b[o+3] = (v & 0xff) as u8 44 return o + 4 45} 46func cg_stub(dst: *u8, w: i64, h: i64) -> i64 { 47 dst[0]=137 as u8; dst[1]=80 as u8; dst[2]=78 as u8; dst[3]=71 as u8 48 dst[4]=13 as u8; dst[5]=10 as u8; dst[6]=26 as u8; dst[7]=10 as u8 49 var o: i64 = 8 50 o = cg_be32(dst, o, 13) 51 dst[o]=73 as u8; dst[o+1]=72 as u8; dst[o+2]=68 as u8; dst[o+3]=82 as u8 // IHDR 52 o = o + 4 53 o = cg_be32(dst, o, w) 54 o = cg_be32(dst, o, h) 55 dst[o]=8 as u8; dst[o+1]=6 as u8; dst[o+2]=0 as u8; dst[o+3]=0 as u8; dst[o+4]=0 as u8 56 o = o + 5 57 o = cg_be32(dst, o, 0) 58 o = cg_be32(dst, o, 0) 59 dst[o]=73 as u8; dst[o+1]=69 as u8; dst[o+2]=78 as u8; dst[o+3]=68 as u8 // IEND 60 o = o + 4 61 o = cg_be32(dst, o, 0) 62 return o 63} 64 65// build a genrec'd PNG and return its CID text in cidout (69 bytes) 66func cg_cid(seed: *u8, model: *u8, sampler: *u8, steps: *u8, host: *u8, prompt: *u8, size: *u8, args: *u8, cidout: *u8) -> i64 { 67 let stub: *u8 = sys_mmap(CG_BUF) 68 let sn: i64 = cg_stub(stub, 64, 64) 69 let withtext: *u8 = sys_mmap(CG_BUF) 70 let wlen: i64 = png_insert_genrec(stub, sn, seed, model, sampler, steps, host, prompt, size, args, withtext) 71 if wlen < 0 { return 0 - 1 } 72 let canon: *u8 = sys_mmap(65536) 73 let rk: *u8 = sys_mmap(128) 74 return nx_store_ingest_compute_cid(withtext, wlen, cidout, canon, rk) 75} 76 77func cg_same(a: *u8, b: *u8) -> i64 { 78 var i: i64 = 0 79 while i < 69 { if a[i] != b[i] { return 0 } i = i + 1 } 80 return 1 81} 82 83func main(argc: i64, argv: *i64) -> i64 { 84 var pass: i64 = 0 85 var total: i64 = 0 86 let S: *u8 = "1234\x00" as *u8 87 let M: *u8 = "z_image\x00" as *u8 88 let SA: *u8 = "euler\x00" as *u8 89 let ST: *u8 = "8\x00" as *u8 90 let H: *u8 = "laptop\x00" as *u8 91 let P: *u8 = "a lighthouse\x00" as *u8 92 let A1: *u8 = "a lighthouse <args>{\"cfg_scale\":1.0}\x00" as *u8 93 let A2: *u8 = "a lighthouse <args>{\"cfg_scale\":5.0}\x00" as *u8 94 let c1: *u8 = sys_mmap(128) 95 let c2: *u8 = sys_mmap(128) 96 97 cw("GEN-CID-GATE (canon must separate anything that changes the image)\n" as *u8) 98 99 // T1 DETERMINISM: identical inputs -> identical cid (else every tooth below is noise) 100 total = total + 1 101 if cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1) > 0 { 102 if cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c2) > 0 { 103 if cg_same(c1,c2) == 1 { pass = pass + 1; cw("T1 deterministic OK\n" as *u8) } else { cw("T1 deterministic FAIL\n" as *u8) } 104 } 105 } 106 107 // T2 ★THE RESOLUTION CASE (the original data loss): size differs -> cid MUST differ 108 total = total + 1 109 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1) 110 cg_cid(S,M,SA,ST,H,P,"768x1024\x00" as *u8,A1,c2) 111 if cg_same(c1,c2) == 0 { pass = pass + 1; cw("T2 size separates OK\n" as *u8) } else { cw("T2 size COLLIDES -- DATA LOSS FAIL\n" as *u8) } 112 113 // T3 ★THE cfg CASE (survived the first fix): engine args differ -> cid MUST differ 114 total = total + 1 115 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1) 116 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A2,c2) 117 if cg_same(c1,c2) == 0 { pass = pass + 1; cw("T3 args/cfg separates OK\n" as *u8) } else { cw("T3 args COLLIDES -- DATA LOSS FAIL\n" as *u8) } 118 119 // T4 seed still separates (the oldest guarantee -- a regression here breaks every gallery) 120 total = total + 1 121 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1) 122 cg_cid("9999\x00" as *u8,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c2) 123 if cg_same(c1,c2) == 0 { pass = pass + 1; cw("T4 seed separates OK\n" as *u8) } else { cw("T4 seed COLLIDES FAIL\n" as *u8) } 124 125 // T5 prompt still separates 126 total = total + 1 127 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1) 128 cg_cid(S,M,SA,ST,H,"a harbour\x00" as *u8,"512x512\x00" as *u8,A1,c2) 129 if cg_same(c1,c2) == 0 { pass = pass + 1; cw("T5 prompt separates OK\n" as *u8) } else { cw("T5 prompt COLLIDES FAIL\n" as *u8) } 130 131 // T6 ★NEG-CONTROL: the cid must NOT be a hash of the pixels either -- two DIFFERENT stub sizes 132 // with the SAME declared metadata still collide, which is WHY the metadata must be complete. 133 // This states the model honestly instead of implying content-addressing we do not have. 134 total = total + 1 135 let s1: *u8 = sys_mmap(CG_BUF) 136 let n1: i64 = cg_stub(s1, 64, 64) 137 let s2: *u8 = sys_mmap(CG_BUF) 138 let n2: i64 = cg_stub(s2, 128, 128) 139 let w1: *u8 = sys_mmap(CG_BUF) 140 let w2: *u8 = sys_mmap(CG_BUF) 141 let l1: i64 = png_insert_genrec(s1, n1, S,M,SA,ST,H,P,"512x512\x00" as *u8,A1, w1) 142 let l2: i64 = png_insert_genrec(s2, n2, S,M,SA,ST,H,P,"512x512\x00" as *u8,A1, w2) 143 let cA: *u8 = sys_mmap(128) 144 let cB: *u8 = sys_mmap(128) 145 let ca: *u8 = sys_mmap(65536) 146 let rka: *u8 = sys_mmap(128) 147 nx_store_ingest_compute_cid(w1, l1, cA, ca, rka) 148 nx_store_ingest_compute_cid(w2, l2, cB, ca, rka) 149 if cg_same(cA,cB) == 1 { pass = pass + 1; cw("T6 NEG-CONTROL cid is canon-based, NOT pixel-based (documented, not assumed) OK\n" as *u8) } 150 else { cw("T6 unexpected: differing pixels changed the cid -- the model in the comments is wrong FAIL\n" as *u8) } 151 152 cw("GENCIDGATE " as *u8); cn(pass); cw("/" as *u8); cn(total) 153 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 154 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 155 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 156 let ctr__dry: *i64 = gv_ctr() 157 ctr__dry[0] = pass 158 ctr__dry[1] = total 159 let rc__dry: i64 = gv_verdict("GEN-CID-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 160 sys_exit(rc__dry) 161 return rc__dry 162}