code wiki / _hdl_build / nx_gen_ingest.nx
nx_gen_ingest.nx source
↩ module page · 242 lines · 11987 B
1// nx_gen_ingest.nx -- ingest an ON-DISK PNG into the gallery: GENREC tEXt -> CID -> blob -> sidecar.
2//
3// WHY THIS EXISTS. The operator asked, this session: *"are these gens pointable so right now i can
4// see them in gallery"*. The measured answer was NO, and it stayed no while three sovereign images
5// were produced: `grep -c sovgen /volume1/ai/gen/cids.tsv` returned 0. The sovereign renderer wrote
6// PNGs to /volume1/ai/images/sovgen/ -- a directory the gallery does not read. Nothing was broken;
7// the images were simply never handed to the index.
8// ★★★★★ AN ARTEFACT THAT IS NOT IN THE INDEX DOES NOT EXIST TO THE PRODUCT, HOWEVER REAL THE FILE IS.
9//
10// nx_gen_pipeline already does exactly this ingest, but its entry point takes BASE64 because it was
11// written for a GPU worker that answers in JSON. The sovereign path has raw PNG bytes on disk, so
12// this organ reuses that file's blob-path and sidecar-append helpers rather than restating them --
13// the gallery's contract must have ONE definition or the two writers will disagree about where an
14// image lives, which is a corruption nobody would see until a link 404s.
15//
16// Usage:
17// nx_gen_ingest <blobdir> <sidecar|-> <idx|-> <blob|-> <view|-> <model> <seed> <prompt> <png> ...
18// Production values (measured from the live store, not assumed):
19// blobdir /volume1/ai/gen/blob-
20// idx /volume1/ai/galx/knowledge/index/galx_cid.idx
21// blob /volume1/ai/galx/knowledge/index/galx_cid.blob
22// Pass `-` for the sidecar to skip the legacy TSV entirely -- which is the point: the gen->gallery
23// path now writes the SOVEREIGN store directly (nx_galx_cidput), so no 70-byte text append and no
24// 34MB recompile of 235,445 rows stands between a render and the gallery.
25// The gate points everything at /tmp -- ★ A TEST THAT WRITES TO THE PRODUCTION STORE IS NOT A TEST.
26// license_tier: ORIGINAL
27
28import "nx_syscalls.nx"
29import "nx_strconv.nx"
30import "nx_canon_cid.nx"
31import "nx_png_textw.nx"
32import "nx_store_ingest.nx"
33import "nx_gen_pipeline.nx"
34import "nx_galx_cidput.nx"
35import "nx_galx_viewadd_lib.nx"
36const GI_MAGIC_4096: i64 = 4096
37const GI_MAGIC_1024: i64 = 1024
38
39func gi_puts(s: *u8) -> i64 {
40 var n: i64 = 0
41 while s[n] != (0 as u8) { n = n + 1 }
42 return sys_write(1, s, n)
43}
44
45// Read a whole file into a fresh mapping. Returns byte count, or negative.
46// ⚠ Returns -2 on a SHORT read rather than a partial buffer: a truncated PNG would otherwise be
47// ingested under a CID that no future read of the same file could reproduce.
48func gi_slurp(path: *u8, out: *u8, cap: i64) -> i64 {
49 let fd: i64 = sys_openat_rd(path)
50 if fd < 0 { return 0 - 1 }
51 var n: i64 = 0
52 var go: i64 = 1
53 while go == 1 {
54 let r: i64 = sys_read(fd, ((out as i64) + n) as *u8, cap - n)
55 if r <= 0 { go = 0 } else { n = n + r }
56 if n >= cap { go = 0 }
57 }
58 sys_close(fd)
59 if n >= cap { return 0 - 2 }
60 return n
61}
62
63func gi_be32(b: *u8, off: i64) -> i64 {
64 return ((b[off] as i64) << 24) | ((b[off + 1] as i64) << 16)
65 | ((b[off + 2] as i64) << 8) | (b[off + 3] as i64)
66}
67
68// "<w>x<h>" from the IHDR. The size string is DERIVED from the file, never passed in -- a caller
69// that mislabels the dimensions would write a GENREC that disagrees with its own pixels.
70func gi_size(png: *u8, n: i64, out: *u8) -> i64 {
71 if n < 33 { return 0 - 1 }
72 if png[12] != (0x49 as u8) { return 0 - 1 } // 'I' of IHDR
73 var o: i64 = nx_strconv_format_i64(gi_be32(png, 16), out)
74 out[o] = 0x78 // 'x'
75 o = o + 1
76 o = o + nx_strconv_format_i64(gi_be32(png, 20), ((out as i64) + o) as *u8)
77 out[o] = 0
78 return o
79}
80
81// basename, so the report reads as images rather than as directories
82func gi_base(p: *u8) -> *u8 {
83 var n: i64 = 0
84 var last: i64 = 0
85 while p[n] != (0 as u8) {
86 if p[n] == (0x2F as u8) { last = n + 1 }
87 n = n + 1
88 }
89 return ((p as i64) + last) as *u8
90}
91
92const GI_CAP: i64 = 33554432
93
94func main(argc: i64, argv: *i64) -> i64 {
95 if argc < 10 {
96 gi_puts("usage: nx_gen_ingest <blobdir> <sidecar|-> <idx|-> <blob|-> <model> <seed> <prompt> <png> [<png> ...]\n" as *u8)
97 return 2
98 }
99 let blobdir: *u8 = argv[1] as *u8
100 let sidecar: *u8 = argv[2] as *u8
101 let cidx: *u8 = argv[3] as *u8
102 let cblob: *u8 = argv[4] as *u8
103 let cview: *u8 = argv[5] as *u8
104 let model: *u8 = argv[6] as *u8
105 let seed: *u8 = argv[7] as *u8
106 let prompt: *u8 = argv[8] as *u8
107 // ⚠THE ENUMERATOR IS A SEPARATE INDEX AND IT IS THE ONE THE USER SEES. Resolver-only ingest
108 // yields an image fetchable by cid and invisible in the grid -- exactly what shipped once.
109 var do_view: i64 = 1
110 if cview[0] == (0x2D as u8) { if cview[1] == (0 as u8) { do_view = 0 } }
111 // "-" disables a sink. The legacy TSV is opt-IN now, not opt-out.
112 var do_side: i64 = 1
113 if sidecar[0] == (0x2D as u8) { if sidecar[1] == (0 as u8) { do_side = 0 } }
114 var do_native: i64 = 1
115 if cidx[0] == (0x2D as u8) { if cidx[1] == (0 as u8) { do_native = 0 } }
116
117 let raw: *u8 = sys_mmap(GI_CAP + GI_MAGIC_4096)
118 let wt: *u8 = sys_mmap(GI_CAP + GI_MAGIC_4096)
119 let cid: *u8 = sys_mmap(128)
120 let path: *u8 = sys_mmap(GI_MAGIC_1024)
121 let size: *u8 = sys_mmap(64)
122 let chash: *u8 = sys_mmap(128)
123 let args: *u8 = sys_mmap(512)
124
125 var n_new: i64 = 0
126 var n_dup: i64 = 0
127 var n_err: i64 = 0
128 var n_idx: i64 = 0
129 var n_view: i64 = 0
130
131 var a: i64 = 9
132 while a < argc {
133 let png: *u8 = argv[a] as *u8
134 gi_puts(gi_base(png))
135 gi_puts(" " as *u8)
136 let rn: i64 = gi_slurp(png, raw, GI_CAP)
137 if rn < 0 {
138 gi_puts("UNREADABLE (or larger than the 32MB cap)\n" as *u8)
139 n_err = n_err + 1
140 } else {
141 if gi_size(raw, rn, size) < 0 {
142 // Refuse rather than ingest something that is not a PNG: the gallery serves these
143 // bytes to a browser, and the CID would make a non-image permanent.
144 gi_puts("NOT A PNG (no IHDR)\n" as *u8)
145 n_err = n_err + 1
146 } else {
147 // ⚠⚠ THE STORE'S CID IS A HASH OF THE HARVESTED GENREC FACTORS, NOT OF THE PIXELS.
148 // That is sound for the GPU-worker path, which increments the seed per image, so
149 // its factors differ per image. It is NOT sound for ingesting arbitrary files:
150 // ingesting two DIFFERENT sovereign renders (69,600 B and 68,863 B) under one seed
151 // produced ONE CID, and the second image was silently dropped as a duplicate.
152 // ★★★★★★ CONTENT ADDRESSING THAT HASHES THE LABEL INSTEAD OF THE CONTENT SILENTLY
153 // DISCARDS EVERY COLLISION AS A "DUPLICATE" -- and the dedup counter reports it as
154 // a success. The dedup path is exactly where the loss becomes invisible.
155 // ⇒ fold the TRUE content hash into the factors, so the record identifies the bytes.
156 // ⚠PRECISELY WHAT THIS HASH COVERS: the source file AS INGESTED, before the GENREC
157 // chunk is inserted. The stored blob is those bytes PLUS the chunk, so re-hashing
158 // the served image does NOT reproduce it -- you must hash the original, or strip
159 // the injected tEXt first. Stating the wrong subject here would hand the next
160 // reader a "verification" that fails for a file that is perfectly intact.
161 cid_of(raw, rn, chash)
162 let pfx: *u8 = "sovereign-gen-path content=" as *u8
163 var ao: i64 = 0
164 while pfx[ao] != (0 as u8) { args[ao] = pfx[ao]; ao = ao + 1 }
165 var ci: i64 = 0
166 while chash[ci] != (0 as u8) { args[ao] = chash[ci]; ao = ao + 1; ci = ci + 1 }
167 args[ao] = 0
168
169 let wl: i64 = png_insert_genrec(raw, rn, seed, model,
170 "nishi-sovereign" as *u8, "8" as *u8,
171 "nishios" as *u8, prompt, size,
172 args, wt)
173 if wl < 0 {
174 gi_puts("GENREC INJECT FAILED\n" as *u8)
175 n_err = n_err + 1
176 } else {
177 let rc: i64 = nx_store_ingest_ingest_cid(wt, wl, "nxc1" as *u8, cid)
178 if rc < 0 {
179 gi_puts("STORE INGEST FAILED\n" as *u8)
180 n_err = n_err + 1
181 } else {
182 gp_blob_path(blobdir, cid, path)
183 let fd: i64 = sys_openat_wr(path, 0x1a4)
184 if fd >= 0 { sys_write(fd, wt, wl); sys_close(fd) }
185 // rc==1 is new, rc==0 is already in the store. Appending on a dup would
186 // grow the sidecar without bound and make /img/<cid> ambiguous.
187 if rc == 1 {
188 if do_side == 1 { gp_sidecar_append(sidecar, cid, path) }
189 n_new = n_new + 1
190 } else { n_dup = n_dup + 1 }
191 gi_puts(size)
192 gi_puts(" " as *u8)
193 gi_puts(cid)
194 if rc == 1 { gi_puts(" NEW" as *u8) } else { gi_puts(" dup" as *u8) }
195
196 // ---- the sovereign store, directly. This is what the gallery READS. ----
197 // Done even for rc==0: a blob can exist in the factor store while the
198 // cid->path index has never heard of it, which is precisely the state that
199 // left 109 images 404ing. The put is idempotent, so re-asserting is free.
200 if do_native == 1 {
201 let pr: i64 = nx_galx_cidput(cidx, cblob, cid, path)
202 if pr == CP_INSERTED { gi_puts(" idx:INSERTED" as *u8); n_idx = n_idx + 1 }
203 else {
204 if pr == CP_PRESENT { gi_puts(" idx:present" as *u8) }
205 else {
206 gi_puts(" idx:FAILED" as *u8)
207 n_err = n_err + 1
208 }
209 }
210 }
211 // ---- THE ENUMERATOR. Separate index, and the only one the grid reads. ----
212 if do_view == 1 {
213 let vr: i64 = nx_galx_view_prepend(cview, cid)
214 if vr == NXV_ADDED { gi_puts(" view:ADDED" as *u8); n_view = n_view + 1 }
215 else {
216 if vr == NXV_PRESENT { gi_puts(" view:present" as *u8) }
217 else { gi_puts(" view:FAILED" as *u8); n_err = n_err + 1 }
218 }
219 }
220 gi_puts("\n" as *u8)
221 }
222 }
223 }
224 }
225 a = a + 1
226 }
227
228 gi_puts("ingested_new=" as *u8)
229 let b: *u8 = sys_mmap(32)
230 sys_write(1, b, nx_strconv_format_i64(n_new, b))
231 gi_puts(" duplicates=" as *u8)
232 sys_write(1, b, nx_strconv_format_i64(n_dup, b))
233 gi_puts(" idx_inserted=" as *u8)
234 sys_write(1, b, nx_strconv_format_i64(n_idx, b))
235 gi_puts(" view_added=" as *u8)
236 sys_write(1, b, nx_strconv_format_i64(n_view, b))
237 gi_puts(" errors=" as *u8)
238 sys_write(1, b, nx_strconv_format_i64(n_err, b))
239 gi_puts("\n" as *u8)
240 if n_err > 0 { return 1 }
241 return 0
242}