import { createHash } from "node:crypto"; import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; import { resolve } from "node:path"; const colorRoot = process.env.DADA_COLOR_ASSET_ROOT ?? resolve(homedir(), "Desktop", "sticker_colour", "单模板归档", "styles"); const dynamicRoot = process.env.DADA_DYNAMIC_ASSET_ROOT ?? resolve(homedir(), "Desktop", "sticker_interactive", "单模板归档", "templates"); const colorIds = ["COLOR001", "COLOR002", "COLOR008", "COLOR016"]; const dynamicIds = ["DYN001", "DYN002", "DYN003", "DYN004", "DYN007", "DYN008", "DYN011", "DYN012", "DYN015", "DYN016"]; const expectedColorStyles = { COLOR001: "style_01", COLOR002: "style_02", COLOR008: "style_08", COLOR016: "style_16" }; const expectedDynamicSources = { DYN001: "l_POI01", DYN002: "l_POI02", DYN003: "l_POI03", DYN004: "l_POI04", DYN007: "diaoyu", DYN008: "l_shijian2", DYN011: "l_shijian6", DYN012: "l_shijian7", DYN015: "0721userna", DYN016: "l_username00", }; const requiredDynamicKeys = { DYN001: ["title"], DYN002: ["city_en", "title"], DYN003: ["city", "title"], DYN004: ["city", "latitude", "longitude"], DYN007: ["nickname"], DYN008: ["hour", "minute", "month"], DYN011: ["day", "month", "year"], DYN012: ["hour", "minute"], DYN015: ["nickname"], DYN016: ["nickname"], }; const paths = [ ...colorIds.map((id) => resolve(colorRoot, id, "metadata.json")), ...dynamicIds.map((id) => resolve(dynamicRoot, id, "metadata.json")), ]; if (!paths.every(existsSync)) throw new Error("WP4-04 normative metadata is unavailable."); const hash = (bytes) => createHash("sha256").update(bytes).digest("hex").toUpperCase(); const before = paths.map((path) => ({ bytes: readFileSync(path), mtime: statSync(path).mtimeMs })); const metadata = before.map((entry) => JSON.parse(entry.bytes.toString("utf8"))); for (const id of colorIds) { const item = metadata.find((entry) => entry.canonical_id === id); if (!item || item.family !== "color_card" || item.resource_class !== "parameter_renderer" || item.runtime?.stable_style_id !== expectedColorStyles[id]) throw new Error(`Color metadata verification failed for ${id}.`); } for (const id of dynamicIds) { const item = metadata.find((entry) => entry.canonical_id === id); if (!item || item.family !== "interactive_sticker" || item.resource_class !== "dynamic_resource" || item.source_candidate_id !== expectedDynamicSources[id] || item.runtime?.has_dynamic_binding !== true || !requiredDynamicKeys[id].every((key) => item.dynamic_keys.includes(key)) || ![...(item.files?.prefab ?? []), ...(item.files?.images ?? []), ...(item.files?.lua ?? [])] .every((relativePath) => existsSync(resolve(dynamicRoot, id, relativePath)))) { throw new Error(`Dynamic metadata verification failed for ${id}.`); } } const dyn012 = metadata.find((entry) => entry.canonical_id === "DYN012"); const missingDin = dyn012.runtime.external_runtime_fonts?.some((entry) => entry.manifest_filename === "DIN_MediumAlternate.otf"); if (!missingDin) throw new Error("DYN012 missing-font evidence is unavailable."); const unchanged = paths.every((path, index) => statSync(path).mtimeMs === before[index].mtime && hash(readFileSync(path)) === hash(before[index].bytes)); if (!unchanged) throw new Error("Normative source changed during read-only verification."); const result = { color_ids: colorIds, color_metadata_sha256: hash(Buffer.concat(before.slice(0, colorIds.length).map((entry) => entry.bytes))), dyn012_original_font: "DIN_MediumAlternate.otf", dyn012_substitute_font: "FONT081 Lexend Deca", dynamic_ids: dynamicIds, dynamic_metadata_sha256: hash(Buffer.concat(before.slice(colorIds.length).map((entry) => entry.bytes))), lua_prefab_execution: false, source_access: "read_only_normative_metadata", source_unchanged: unchanged, status: "passed", }; if (process.env.DADA_WP4_04_SOURCE_EVIDENCE) writeFileSync(process.env.DADA_WP4_04_SOURCE_EVIDENCE, `${JSON.stringify(result, null, 2)}\n`); console.log(JSON.stringify(result, null, 2));