code wiki / (root) / nx_asset_meta_gate.nx

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}