nx_asset_meta_gate.nx source
↩ module page · 360 lines · 18386 B
1// nx_asset_meta_gate.nx -- KAT + TEETH for nx_asset_meta + nx_asset_autotag (R3 of the universal
2// organization-tooling arc). ONE gate, both organs.
3//
4// PROVES the catalog can know WHAT each asset IS and WHICH media are NEAR-duplicates:
5//
6// (a) METADATA (KAT, REAL extraction):
7// - a REAL PNG byte buffer (signature + IHDR, width=320 height=200) -> am_enrich_image populates
8// width="320" / height="200" / encoding_format="image/png" extracted FROM THE BYTES;
9// - those fields ROUND-TRIP through R0's ar_encode -> ar_decode -> ar_get byte-faithfully;
10// - a DOC record -> am_enrich_doc populates dc:title + dc:subject, round-tripping the same way;
11// - TEETH: non-PNG bytes -> am_enrich_image refuses (AM_NOT_PNG), inventing NO dimensions.
12//
13// (b) NEAR-DUP via perceptual hash:
14// - two NEAR-identical grayscale images (a small perturbation) -> at_neardup GROUPS them
15// (Hamming < threshold);
16// - two CLEARLY-DIFFERENT images -> NOT grouped (TEETH: no false-merge);
17// - identical bytes -> Hamming 0 AND same CID (ar_cid).
18//
19// (c) THE WHOLE POINT -- perceptual hash catches a near-dup that exact-CID does NOT:
20// a 1-pixel-changed image has a DIFFERENT CID (ar_cid differs) but a near-zero Hamming distance
21// -> grouped. Both asserted (CID differs AND phash groups).
22//
23// Verdict logged to knowledge/status/asset_meta_gate.log (append-only; ADDITIVE law #13).
24// expect_exit: 0 license_tier: ORIGINAL
25import "nx_syscalls.nx"
26import "nx_asset_record.nx"
27import "nx_asset_meta.nx"
28import "nx_asset_autotag.nx"
29import "nx_gate_verdict.nx"
30
31// ---- gate I/O (logfd THREADED as a param: compiler supports const globals but not reassigning a
32// module-level var inside a function -- the R0/R2 gate idiom) ----
33func g_puts(logfd: i64, s: *u8) -> i64 {
34 var n: i64 = 0
35 while s[n] != (0 as u8) { n = n + 1 }
36 sys_write(1, s, n)
37 if logfd > 0 { sys_write(logfd, s, n) }
38 return 0
39}
40func g_putn(logfd: i64, v: i64) -> i64 {
41 let bb: *u8 = sys_mmap(28)
42 var m: i64 = v
43 if m < 0 { g_puts(logfd, "-\x00" as *u8); m = 0 - m }
44 let t: *u8 = sys_mmap(28)
45 var k: i64 = 0
46 if m == 0 { t[0] = 48 as u8; k = 1 }
47 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
48 var i: i64 = 0
49 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
50 sys_write(1, bb, k)
51 if logfd > 0 { sys_write(logfd, bb, k) }
52 return 0
53}
54func g_streq(a: *u8, b: *u8) -> i64 {
55 var i: i64 = 0
56 while 1 == 1 {
57 if a[i] != b[i] { return 0 }
58 if a[i] == (0 as u8) { return 1 }
59 i = i + 1
60 }
61 return 1
62}
63// empty-string sentinel for omitted optional fields (mirrors R0/R2's E()).
64func E() -> *u8 { return "\x00" as *u8 }
65
66// build a synthetic 320x200-shaped PNG: 8-byte signature + IHDR (len=13, "IHDR", width/height BE,
67// bit_depth=8 color_type=2 RGB) + 4 CRC bytes (we don't verify CRC; nx_png_header doesn't either).
68// Returns the byte length. Width/height are written big-endian per the IHDR layout.
69func gate_make_png(raw: *u8, w: i64, h: i64) -> i64 {
70 var i: i64 = 0
71 while i < 64 { raw[i] = 0 as u8; i = i + 1 }
72 raw[0] = 0x89 as u8; raw[1] = 0x50 as u8; raw[2] = 0x4E as u8; raw[3] = 0x47 as u8
73 raw[4] = 0x0D as u8; raw[5] = 0x0A as u8; raw[6] = 0x1A as u8; raw[7] = 0x0A as u8
74 raw[11] = 13 as u8 // IHDR length (BE; high bytes already 0)
75 raw[12] = 0x49 as u8; raw[13] = 0x48 as u8; raw[14] = 0x44 as u8; raw[15] = 0x52 as u8 // "IHDR"
76 raw[16] = ((w >> 24) & 255) as u8; raw[17] = ((w >> 16) & 255) as u8
77 raw[18] = ((w >> 8) & 255) as u8; raw[19] = (w & 255) as u8 // width BE
78 raw[20] = ((h >> 24) & 255) as u8; raw[21] = ((h >> 16) & 255) as u8
79 raw[22] = ((h >> 8) & 255) as u8; raw[23] = (h & 255) as u8 // height BE
80 raw[24] = 8 as u8 // bit_depth
81 raw[25] = 2 as u8 // color_type RGB
82 raw[26] = 0 as u8; raw[27] = 0 as u8; raw[28] = 0 as u8 // compression/filter/interlace
83 return 8 + 8 + 13 + 4 // = 33 bytes
84}
85
86// fill a w*h grayscale buffer with a deterministic horizontal gradient (rich adjacent-pixel structure
87// so dHash produces a meaningful, differentiable fingerprint). pixel(x,y) = (x*7 + y*3) & 255.
88func gate_fill_gradient(g: *u8, w: i64, h: i64) -> i64 {
89 var y: i64 = 0
90 while y < h {
91 var x: i64 = 0
92 while x < w {
93 g[y * w + x] = ((x * 7 + y * 3) & 255) as u8
94 x = x + 1
95 }
96 y = y + 1
97 }
98 return 0
99}
100// copy n bytes src->dst.
101func gate_memcpy(dst: *u8, src: *u8, n: i64) -> i64 {
102 var i: i64 = 0
103 while i < n { dst[i] = src[i]; i = i + 1 }
104 return 0
105}
106
107func main() -> i64 {
108 let logfd: i64 = sys_openat_append("knowledge/status/asset_meta_gate.log\x00" as *u8, 0x1a4)
109 g_puts(logfd, "=== ASSET-META-GATE (R3: metadata enrichment + perceptual near-dup) ===\n\x00" as *u8)
110
111 var pass: i64 = 0
112 var total: i64 = 0
113
114 // =========================================================================================
115 // (a) METADATA -- REAL extraction from PNG bytes + Dublin Core round-trip.
116 // =========================================================================================
117 g_puts(logfd, "-- (a) METADATA enrichment (real PNG extraction + Dublin Core round-trip) --\n\x00" as *u8)
118
119 // build a real PNG (width=320, height=200) and REAL-extract its facts.
120 let png: *u8 = sys_mmap(64)
121 let pnglen: i64 = gate_make_png(png, 320, 200)
122 // field set for an image record; start from the core/type via ar_fields, then am_enrich_image
123 // EXTENDS it. (keys/vals sized generously; kv[0] = running field count.)
124 let keys: *i64 = sys_mmap(8 * 32) as *i64
125 let vals: *i64 = sys_mmap(8 * 32) as *i64
126 let kv: *i64 = sys_mmap(16) as *i64
127 kv[0] = 0
128 // seed minimal core: type=image, title (so the record is realistic + we prove enrich is additive).
129 ar_addf(keys, vals, kv, "type\x00" as *u8, "image\x00" as *u8)
130 ar_addf(keys, vals, kv, "title\x00" as *u8, "Family Photo\x00" as *u8)
131 // REAL extraction -> width/height/encoding_format buffers (must OUTLIVE the field set).
132 let wbuf: *u8 = sys_mmap(24)
133 let hbuf: *u8 = sys_mmap(24)
134 let fbuf: *u8 = sys_mmap(24)
135 let nf_after: i64 = am_enrich_image(png, pnglen, keys, vals, kv, wbuf, hbuf, fbuf)
136 g_puts(logfd, " am_enrich_image extracted: width=\x00" as *u8); g_puts(logfd, wbuf)
137 g_puts(logfd, " height=\x00" as *u8); g_puts(logfd, hbuf)
138 g_puts(logfd, " encoding_format=\x00" as *u8); g_puts(logfd, fbuf); g_puts(logfd, "\n\x00" as *u8)
139
140 // (a1) KAT: extracted values equal the expected PNG dimensions + format.
141 total = total + 1
142 g_puts(logfd, " (a1) KAT image facts == 320 / 200 / image/png (real-extracted): \x00" as *u8)
143 var a1: i64 = 0
144 if nf_after > 0 {
145 a1 = 1
146 if g_streq(wbuf, "320\x00" as *u8) == 0 { a1 = 0 }
147 if g_streq(hbuf, "200\x00" as *u8) == 0 { a1 = 0 }
148 if g_streq(fbuf, "image/png\x00" as *u8) == 0 { a1 = 0 }
149 }
150 if a1 == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
151
152 // (a2) ROUND-TRIP: encode the enriched field set -> decode -> ar_get returns the same values.
153 let enc: *u8 = sys_mmap(8192)
154 let elen: i64 = ar_encode(keys, vals, kv[0], enc)
155 let dk: *i64 = sys_mmap(8 * 32) as *i64
156 let dv: *i64 = sys_mmap(8 * 32) as *i64
157 let nfd: i64 = ar_decode(enc, elen, dk, dv, 32)
158 total = total + 1
159 g_puts(logfd, " (a2) fields round-trip ar_encode->ar_decode->ar_get [w=\x00" as *u8)
160 g_puts(logfd, ar_get(dk, dv, nfd, "width\x00" as *u8)); g_puts(logfd, " h=\x00" as *u8)
161 g_puts(logfd, ar_get(dk, dv, nfd, "height\x00" as *u8)); g_puts(logfd, " fmt=\x00" as *u8)
162 g_puts(logfd, ar_get(dk, dv, nfd, "encoding_format\x00" as *u8)); g_puts(logfd, " type=\x00" as *u8)
163 g_puts(logfd, ar_get(dk, dv, nfd, "type\x00" as *u8)); g_puts(logfd, "]: \x00" as *u8)
164 var a2: i64 = 0
165 if nfd > 0 {
166 a2 = 1
167 if g_streq(ar_get(dk, dv, nfd, "width\x00" as *u8), "320\x00" as *u8) == 0 { a2 = 0 }
168 if g_streq(ar_get(dk, dv, nfd, "height\x00" as *u8), "200\x00" as *u8) == 0 { a2 = 0 }
169 if g_streq(ar_get(dk, dv, nfd, "encoding_format\x00" as *u8), "image/png\x00" as *u8) == 0 { a2 = 0 }
170 if g_streq(ar_get(dk, dv, nfd, "type\x00" as *u8), "image\x00" as *u8) == 0 { a2 = 0 }
171 }
172 if a2 == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
173
174 // (a3) DOC record: am_enrich_doc populates dc:title + dc:subject, round-tripping the same way.
175 let dkeys: *i64 = sys_mmap(8 * 32) as *i64
176 let dvals: *i64 = sys_mmap(8 * 32) as *i64
177 let dkv: *i64 = sys_mmap(16) as *i64
178 dkv[0] = 0
179 ar_addf(dkeys, dvals, dkv, "type\x00" as *u8, "doc\x00" as *u8)
180 am_enrich_doc("2025 Tax Return\x00" as *u8, "taxes,finance,2025\x00" as *u8, dkeys, dvals, dkv)
181 let denc: *u8 = sys_mmap(8192)
182 let delen: i64 = ar_encode(dkeys, dvals, dkv[0], denc)
183 let ddk: *i64 = sys_mmap(8 * 32) as *i64
184 let ddv: *i64 = sys_mmap(8 * 32) as *i64
185 let dnfd: i64 = ar_decode(denc, delen, ddk, ddv, 32)
186 total = total + 1
187 g_puts(logfd, " (a3) DOC dc:title+dc:subject round-trip [title=\x00" as *u8)
188 g_puts(logfd, ar_get(ddk, ddv, dnfd, "title\x00" as *u8)); g_puts(logfd, " subject=\x00" as *u8)
189 g_puts(logfd, ar_get(ddk, ddv, dnfd, "subject\x00" as *u8)); g_puts(logfd, "]: \x00" as *u8)
190 var a3: i64 = 0
191 if dnfd > 0 {
192 a3 = 1
193 if g_streq(ar_get(ddk, ddv, dnfd, "title\x00" as *u8), "2025 Tax Return\x00" as *u8) == 0 { a3 = 0 }
194 if g_streq(ar_get(ddk, ddv, dnfd, "subject\x00" as *u8), "taxes,finance,2025\x00" as *u8) == 0 { a3 = 0 }
195 }
196 if a3 == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
197
198 // (a4) TEETH: non-PNG bytes -> am_enrich_image REFUSES (AM_NOT_PNG), invents no dimensions.
199 let notpng: *u8 = sys_mmap(64)
200 var z: i64 = 0
201 while z < 64 { notpng[z] = (65 + (z % 5)) as u8; z = z + 1 } // arbitrary ASCII, no PNG signature
202 let tkeys: *i64 = sys_mmap(8 * 8) as *i64
203 let tvals: *i64 = sys_mmap(8 * 8) as *i64
204 let tkv: *i64 = sys_mmap(16) as *i64
205 tkv[0] = 0
206 let twb: *u8 = sys_mmap(24)
207 let thb: *u8 = sys_mmap(24)
208 let tfb: *u8 = sys_mmap(24)
209 let tret: i64 = am_enrich_image(notpng, 64, tkeys, tvals, tkv, twb, thb, tfb)
210 total = total + 1
211 g_puts(logfd, " (a4) TEETH non-PNG -> AM_NOT_PNG, 0 fields invented (ret=\x00" as *u8)
212 g_putn(logfd, tret); g_puts(logfd, " fields=\x00" as *u8); g_putn(logfd, tkv[0]); g_puts(logfd, "): \x00" as *u8)
213 if tret == AM_NOT_PNG { if tkv[0] == 0 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) } } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
214
215 // =========================================================================================
216 // (b) NEAR-DUP via perceptual hash.
217 // =========================================================================================
218 g_puts(logfd, "-- (b) perceptual near-dup (group near-identical, NOT clearly-different) --\n\x00" as *u8)
219
220 let W: i64 = 32
221 let H: i64 = 32
222 let npx: i64 = W * H
223
224 // imgA: gradient. imgB: copy of A with a SMALL perturbation (near-identical). imgC: a clearly
225 // DIFFERENT pattern (vertical bands inverted from A's gradient).
226 let imgA: *u8 = sys_mmap(npx)
227 let imgB: *u8 = sys_mmap(npx)
228 let imgC: *u8 = sys_mmap(npx)
229 gate_fill_gradient(imgA, W, H)
230 gate_memcpy(imgB, imgA, npx)
231 // LOCALIZED perturbation: strongly brighten one small block (rows 0-7, cols 0-3) so it flips a
232 // FEW dHash bits (the relative-order comparisons land differently in those downscale cells) yet
233 // leaves A~B well under threshold -- this exercises the near-but-NOT-identical fingerprint case
234 // (a non-zero sub-threshold Hamming), not a degenerate Hamming-0. clamps at 255.
235 var py: i64 = 0
236 while py < 8 {
237 var px: i64 = 0
238 while px < 4 {
239 let idx: i64 = py * W + px
240 var nv: i64 = (imgB[idx] as i64) + 120
241 if nv > 255 { nv = 255 }
242 imgB[idx] = nv as u8
243 px = px + 1
244 }
245 py = py + 1
246 }
247 // clearly different image: a high-contrast checkerboard (uncorrelated with the gradient's order).
248 var cy: i64 = 0
249 while cy < H {
250 var cx: i64 = 0
251 while cx < W {
252 if ((cx + cy) % 2) == 0 { imgC[cy * W + cx] = 0 as u8 } else { imgC[cy * W + cx] = 255 as u8 }
253 cx = cx + 1
254 }
255 cy = cy + 1
256 }
257
258 let hA: i64 = at_phash(imgA, W, H)
259 let hB: i64 = at_phash(imgB, W, H)
260 let hC: i64 = at_phash(imgC, W, H)
261 let hAB: i64 = at_hamming(hA, hB)
262 let hAC: i64 = at_hamming(hA, hC)
263 g_puts(logfd, " dHash Hamming: A~B(near-dup)=\x00" as *u8); g_putn(logfd, hAB)
264 g_puts(logfd, " A~C(different)=\x00" as *u8); g_putn(logfd, hAC); g_puts(logfd, "\n\x00" as *u8)
265
266 // threshold = 10 (generous near-dup band for 64-bit dHash; A~B must fall under, A~C must not).
267 let THRESH: i64 = 10
268 let hashes: *i64 = sys_mmap(8 * 3) as *i64
269 hashes[0] = hA; hashes[1] = hB; hashes[2] = hC
270 let groups: *i64 = sys_mmap(8 * 3) as *i64
271 let ngroups: i64 = at_neardup(hashes, 3, THRESH, groups)
272 g_puts(logfd, " at_neardup groups: A=\x00" as *u8); g_putn(logfd, groups[0])
273 g_puts(logfd, " B=\x00" as *u8); g_putn(logfd, groups[1])
274 g_puts(logfd, " C=\x00" as *u8); g_putn(logfd, groups[2])
275 g_puts(logfd, " distinct=\x00" as *u8); g_putn(logfd, ngroups); g_puts(logfd, "\n\x00" as *u8)
276
277 // (b1) near-identical A,B grouped (Hamming < threshold AND same group id).
278 total = total + 1
279 g_puts(logfd, " (b1) near-identical A,B GROUPED (hamming<thresh + same group): \x00" as *u8)
280 var b1: i64 = 0
281 if hAB < THRESH { if at_in_same_group(groups, 0, 1) == 1 { b1 = 1 } }
282 if b1 == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
283
284 // (b2) TEETH: clearly-different C NOT merged with A (no false-merge), and 2 distinct groups total.
285 total = total + 1
286 g_puts(logfd, " (b2) TEETH different C NOT grouped with A + exactly 2 distinct groups: \x00" as *u8)
287 var b2: i64 = 0
288 if hAC >= THRESH { if at_in_same_group(groups, 0, 2) == 0 { if ngroups == 2 { b2 = 1 } } }
289 if b2 == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
290
291 // (b3) identical bytes -> Hamming 0 AND same CID (ar_cid over the raw buffers).
292 let hAA: i64 = at_hamming(hA, at_phash(imgA, W, H))
293 let cidA: *u8 = sys_mmap(128)
294 let cidA2: *u8 = sys_mmap(128)
295 ar_cid(imgA, npx, cidA)
296 ar_cid(imgA, npx, cidA2)
297 total = total + 1
298 g_puts(logfd, " (b3) identical bytes -> Hamming 0 (=\x00" as *u8); g_putn(logfd, hAA)
299 g_puts(logfd, ") AND same CID: \x00" as *u8)
300 var b3: i64 = 0
301 if hAA == 0 { if g_streq(cidA, cidA2) == 1 { b3 = 1 } }
302 if b3 == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
303
304 // =========================================================================================
305 // (c) THE WHOLE POINT: a 1-pixel-changed image has a DIFFERENT CID but a near-zero Hamming
306 // distance -> the perceptual hash groups what exact-CID dedup MISSES.
307 // =========================================================================================
308 g_puts(logfd, "-- (c) phash catches what CID misses (1-px change: diff CID, near-zero Hamming) --\n\x00" as *u8)
309 let imgA1: *u8 = sys_mmap(npx)
310 gate_memcpy(imgA1, imgA, npx)
311 imgA1[123] = (imgA[123] + 1) as u8 // ONE pixel changed by 1
312
313 let cidOrig: *u8 = sys_mmap(128)
314 let cid1px: *u8 = sys_mmap(128)
315 ar_cid(imgA, npx, cidOrig)
316 ar_cid(imgA1, npx, cid1px)
317 let hA1: i64 = at_phash(imgA1, W, H)
318 let hamA_A1: i64 = at_hamming(hA, hA1)
319
320 // group the original + the 1-px-changed image.
321 let h2: *i64 = sys_mmap(8 * 2) as *i64
322 h2[0] = hA; h2[1] = hA1
323 let g2: *i64 = sys_mmap(8 * 2) as *i64
324 let ng2: i64 = at_neardup(h2, 2, THRESH, g2)
325
326 g_puts(logfd, " CID(orig) = \x00" as *u8); g_puts(logfd, cidOrig); g_puts(logfd, "\n\x00" as *u8)
327 g_puts(logfd, " CID(1px) = \x00" as *u8); g_puts(logfd, cid1px); g_puts(logfd, "\n\x00" as *u8)
328 g_puts(logfd, " CID differs=\x00" as *u8)
329 var ciddiff: i64 = 1
330 if g_streq(cidOrig, cid1px) == 1 { ciddiff = 0 }
331 g_putn(logfd, ciddiff)
332 g_puts(logfd, " phash Hamming(orig,1px)=\x00" as *u8); g_putn(logfd, hamA_A1)
333 g_puts(logfd, " grouped=\x00" as *u8); g_putn(logfd, at_in_same_group(g2, 0, 1)); g_puts(logfd, "\n\x00" as *u8)
334
335 // (c) BOTH must hold: exact-CID says "different" (ciddiff==1) yet perceptual hash says "near-dup"
336 // (Hamming < threshold AND grouped + collapses to 1 group). This is the capability exact-CID lacks.
337 total = total + 1
338 g_puts(logfd, " (c) CID DIFFERS yet phash GROUPS (the near-dup CID misses): \x00" as *u8)
339 var c1: i64 = 0
340 if ciddiff == 1 {
341 if hamA_A1 < THRESH {
342 if at_in_same_group(g2, 0, 1) == 1 {
343 if ng2 == 1 { c1 = 1 }
344 }
345 }
346 }
347 if c1 == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) }
348
349 // =========================================================================================
350 g_puts(logfd, "ASSET-META-GATE passed \x00" as *u8); g_putn(logfd, pass); g_puts(logfd, "/\x00" as *u8); g_putn(logfd, total)
351 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
352 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
353 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
354 let ctr__dry: *i64 = gv_ctr()
355 ctr__dry[0] = pass
356 ctr__dry[1] = total
357 let rc__dry: i64 = gv_verdict("ASSET-META-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
358 sys_exit(rc__dry)
359 return rc__dry
360}