code wiki / _hdl_build / nx_gen_cid_gate.nx
nx_gen_cid_gate.nx source
↩ module page · 155 lines · 7613 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"
24
25const CG_BUF: i64 = 262144
26
27func cw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
28func cn(v: i64) -> i64 {
29 let t: *u8 = sys_mmap(32); let b: *u8 = sys_mmap(32)
30 var m: i64 = v; var k: i64 = 0
31 if m == 0 { t[0] = 48 as u8; k = 1 }
32 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
33 var i: i64 = 0
34 while i < k { b[i] = t[k - 1 - i]; i = i + 1 }
35 sys_write(1, b, k); return 0
36}
37
38// minimal but STRUCTURALLY VALID PNG: signature + IHDR + IEND. png_insert_genrec splices the tEXt
39// chunks in before IEND, exactly as it does for a real render.
40func cg_be32(b: *u8, o: i64, v: i64) -> i64 {
41 b[o] = ((v >> 24) & 0xff) as u8; b[o+1] = ((v >> 16) & 0xff) as u8
42 b[o+2] = ((v >> 8) & 0xff) as u8; b[o+3] = (v & 0xff) as u8
43 return o + 4
44}
45func cg_stub(dst: *u8, w: i64, h: i64) -> i64 {
46 dst[0]=137 as u8; dst[1]=80 as u8; dst[2]=78 as u8; dst[3]=71 as u8
47 dst[4]=13 as u8; dst[5]=10 as u8; dst[6]=26 as u8; dst[7]=10 as u8
48 var o: i64 = 8
49 o = cg_be32(dst, o, 13)
50 dst[o]=73 as u8; dst[o+1]=72 as u8; dst[o+2]=68 as u8; dst[o+3]=82 as u8 // IHDR
51 o = o + 4
52 o = cg_be32(dst, o, w)
53 o = cg_be32(dst, o, h)
54 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
55 o = o + 5
56 o = cg_be32(dst, o, 0)
57 o = cg_be32(dst, o, 0)
58 dst[o]=73 as u8; dst[o+1]=69 as u8; dst[o+2]=78 as u8; dst[o+3]=68 as u8 // IEND
59 o = o + 4
60 o = cg_be32(dst, o, 0)
61 return o
62}
63
64// build a genrec'd PNG and return its CID text in cidout (69 bytes)
65func cg_cid(seed: *u8, model: *u8, sampler: *u8, steps: *u8, host: *u8, prompt: *u8, size: *u8, args: *u8, cidout: *u8) -> i64 {
66 let stub: *u8 = sys_mmap(CG_BUF)
67 let sn: i64 = cg_stub(stub, 64, 64)
68 let withtext: *u8 = sys_mmap(CG_BUF)
69 let wlen: i64 = png_insert_genrec(stub, sn, seed, model, sampler, steps, host, prompt, size, args, withtext)
70 if wlen < 0 { return 0 - 1 }
71 let canon: *u8 = sys_mmap(65536)
72 let rk: *u8 = sys_mmap(128)
73 return nx_store_ingest_compute_cid(withtext, wlen, cidout, canon, rk)
74}
75
76func cg_same(a: *u8, b: *u8) -> i64 {
77 var i: i64 = 0
78 while i < 69 { if a[i] != b[i] { return 0 } i = i + 1 }
79 return 1
80}
81
82func main(argc: i64, argv: *i64) -> i64 {
83 var pass: i64 = 0
84 var total: i64 = 0
85 let S: *u8 = "1234\x00" as *u8
86 let M: *u8 = "z_image\x00" as *u8
87 let SA: *u8 = "euler\x00" as *u8
88 let ST: *u8 = "8\x00" as *u8
89 let H: *u8 = "laptop\x00" as *u8
90 let P: *u8 = "a lighthouse\x00" as *u8
91 let A1: *u8 = "a lighthouse <args>{\"cfg_scale\":1.0}\x00" as *u8
92 let A2: *u8 = "a lighthouse <args>{\"cfg_scale\":5.0}\x00" as *u8
93 let c1: *u8 = sys_mmap(128)
94 let c2: *u8 = sys_mmap(128)
95
96 cw("GEN-CID-GATE (canon must separate anything that changes the image)\n" as *u8)
97
98 // T1 DETERMINISM: identical inputs -> identical cid (else every tooth below is noise)
99 total = total + 1
100 if cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1) > 0 {
101 if cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c2) > 0 {
102 if cg_same(c1,c2) == 1 { pass = pass + 1; cw("T1 deterministic OK\n" as *u8) } else { cw("T1 deterministic FAIL\n" as *u8) }
103 }
104 }
105
106 // T2 ★THE RESOLUTION CASE (the original data loss): size differs -> cid MUST differ
107 total = total + 1
108 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1)
109 cg_cid(S,M,SA,ST,H,P,"768x1024\x00" as *u8,A1,c2)
110 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) }
111
112 // T3 ★THE cfg CASE (survived the first fix): engine args differ -> cid MUST differ
113 total = total + 1
114 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1)
115 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A2,c2)
116 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) }
117
118 // T4 seed still separates (the oldest guarantee -- a regression here breaks every gallery)
119 total = total + 1
120 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1)
121 cg_cid("9999\x00" as *u8,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c2)
122 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) }
123
124 // T5 prompt still separates
125 total = total + 1
126 cg_cid(S,M,SA,ST,H,P,"512x512\x00" as *u8,A1,c1)
127 cg_cid(S,M,SA,ST,H,"a harbour\x00" as *u8,"512x512\x00" as *u8,A1,c2)
128 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) }
129
130 // T6 ★NEG-CONTROL: the cid must NOT be a hash of the pixels either -- two DIFFERENT stub sizes
131 // with the SAME declared metadata still collide, which is WHY the metadata must be complete.
132 // This states the model honestly instead of implying content-addressing we do not have.
133 total = total + 1
134 let s1: *u8 = sys_mmap(CG_BUF)
135 let n1: i64 = cg_stub(s1, 64, 64)
136 let s2: *u8 = sys_mmap(CG_BUF)
137 let n2: i64 = cg_stub(s2, 128, 128)
138 let w1: *u8 = sys_mmap(CG_BUF)
139 let w2: *u8 = sys_mmap(CG_BUF)
140 let l1: i64 = png_insert_genrec(s1, n1, S,M,SA,ST,H,P,"512x512\x00" as *u8,A1, w1)
141 let l2: i64 = png_insert_genrec(s2, n2, S,M,SA,ST,H,P,"512x512\x00" as *u8,A1, w2)
142 let cA: *u8 = sys_mmap(128)
143 let cB: *u8 = sys_mmap(128)
144 let ca: *u8 = sys_mmap(65536)
145 let rka: *u8 = sys_mmap(128)
146 nx_store_ingest_compute_cid(w1, l1, cA, ca, rka)
147 nx_store_ingest_compute_cid(w2, l2, cB, ca, rka)
148 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) }
149 else { cw("T6 unexpected: differing pixels changed the cid -- the model in the comments is wrong FAIL\n" as *u8) }
150
151 cw("GENCIDGATE " as *u8); cn(pass); cw("/" as *u8); cn(total)
152 if pass == total { cw(" verdict=GREEN\n" as *u8); return 0 }
153 cw(" verdict=RED\n" as *u8)
154 return 1
155}