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}