test: complete WP4-07 visual and performance budgets
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 2m34s

This commit is contained in:
suyx
2026-08-04 12:55:25 +08:00
parent 15f7afbe4d
commit c0a8bd6d43
23 changed files with 516 additions and 122 deletions
+22 -13
View File
@@ -53,6 +53,10 @@ export const WP4_07_DYNAMIC_VALUES = Object.freeze({
const timestamp = "2026-07-27T04:00:00.000Z";
const redResourceVersion = "wp4-07-red-contract-v1";
export const WP4_07_REAL_RESOURCE_VERSIONS = Object.freeze({
complex: "p0a-complex-v1",
static: "p0a-static-v1",
});
const palette = ["#111111", "#F2F400", "#1769AA", "#C92A24", "#FFFFFF"];
const textTemplateIds = [
"FLOWER001", "FLOWER003", "FLOWER005", "FLOWER008", "H003", "H004",
@@ -100,9 +104,14 @@ function common(index, type, templateOrAssetId, resourceVersion) {
};
}
export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
function releaseVersions(input = redResourceVersion) {
return typeof input === "string" ? { complex: input, static: input } : input;
}
export function createWp407CanvasFixture(resourceVersions = redResourceVersion) {
const versions = releaseVersions(resourceVersions);
const text = textTemplateIds.map((templateId, offset) => ({
...common(offset + 1, "text_template", templateId, resourceVersion),
...common(offset + 1, "text_template", templateId, versions.complex),
content: offset === 0 ? "DADA\n视觉预算" : `固定文字 ${String(offset + 1).padStart(2, "0")}`,
font_size: 48,
position: offset === 0 ? { x: 0.5, y: 0.5 } : position(offset, 4, 0.09),
@@ -123,7 +132,7 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
},
}));
const stickers = Array.from({ length: 24 }, (_, offset) => ({
...common(offset + 13, "static_sticker", `STK${String(offset + 1).padStart(3, "0")}`, resourceVersion),
...common(offset + 13, "static_sticker", `STK${String(offset + 1).padStart(3, "0")}`, versions.static),
opacity: 0.84 + (offset % 4) * 0.04,
position: position(offset, 6, 0.39),
rotation: (offset % 5 - 2) * 6,
@@ -131,7 +140,7 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
style_parameters: { flip_horizontal: offset % 7 === 0 },
}));
const colors = colorCards.map(([cardId, styleId], offset) => ({
...common(offset + 37, "color_card", cardId, resourceVersion),
...common(offset + 37, "color_card", cardId, versions.complex),
colors: [...palette],
position: { x: 0.16 + offset * 0.22, y: 0.83 },
scale: { x: 1.25, y: 1.25 },
@@ -139,7 +148,7 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
style_parameters: { palette_algorithm_version: "mmcq-v1" },
}));
const dynamics = dynamicIds.map((dynamicId, offset) => ({
...common(offset + 41, "dynamic_sticker", dynamicId, resourceVersion),
...common(offset + 41, "dynamic_sticker", dynamicId, versions.complex),
dynamic_fields: { ...WP4_07_DYNAMIC_VALUES },
formatted_value: dynamicId === "DYN012" ? "12:00 PM" : "@dada_fixture",
position: { x: 0.09 + (offset % 5) * 0.205, y: 0.9 + Math.floor(offset / 5) * 0.06 },
@@ -162,8 +171,8 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
};
}
export function wp407FixtureContract() {
const canvas = createWp407CanvasFixture();
export function wp407FixtureContract(resourceVersions = redResourceVersion) {
const canvas = createWp407CanvasFixture(resourceVersions);
return {
canvas,
dynamic_values: WP4_07_DYNAMIC_VALUES,
@@ -174,12 +183,12 @@ export function wp407FixtureContract() {
};
}
export function wp407FixtureSha256() {
return createHash("sha256").update(JSON.stringify(wp407FixtureContract())).digest("hex").toUpperCase();
export function wp407FixtureSha256(resourceVersions = redResourceVersion) {
return createHash("sha256").update(JSON.stringify(wp407FixtureContract(resourceVersions))).digest("hex").toUpperCase();
}
export function assertWp407Fixture() {
const fixture = wp407FixtureContract();
export function assertWp407Fixture(resourceVersions = redResourceVersion) {
const fixture = wp407FixtureContract(resourceVersions);
const counts = Object.fromEntries(["text_template", "static_sticker", "color_card", "dynamic_sticker"].map((type) => [
type,
fixture.canvas.elements.filter((element) => element.type === type).length,
@@ -195,12 +204,12 @@ export function assertWp407Fixture() {
if (fixture.environment.viewport.width !== 1920 || fixture.environment.viewport.height !== 1080 || fixture.environment.device_scale_factor !== 1) {
throw new Error("candidate viewport or DPR changed");
}
return { counts, fixture_sha256: wp407FixtureSha256() };
return { counts, fixture_sha256: wp407FixtureSha256(resourceVersions) };
}
export const WP4_07_RED_RESOURCE_VERSION = redResourceVersion;
export const WP4_07_REQUIRED_FONT_IDS = Object.freeze([
"FONT005", "FONT008", "FONT011", "FONT022", "FONT027", "FONT039", "FONT043", "FONT046", "FONT052", "FONT081",
"FONT005", "FONT008", "FONT011", "FONT021", "FONT022", "FONT027", "FONT039", "FONT043", "FONT046", "FONT052", "FONT081",
"15974853bc3294ef68e7e6d58fe74fd7", "46f8336813e4c48d06a1aef294fdccf6",
"53ca6b704728520da50c145eabb2e635", "cca5efc0e02fb1bf62349bd68ef30fc1",
"dd25b35dcb7ba4476cbaa9a9592e39e2", "e4210c9872f0c279b35273f230809821",
+17 -6
View File
@@ -10,7 +10,10 @@ test("WP5 lineage recognizes tasks already merged into later remote heads", () =
const gate = inspectWp5TaskLineage({
"codex/wp5-03": sha("3"),
"codex/wp5-04": sha("4"),
}, [commit(4), commit(3), commit(2), commit(1)]);
}, {
"codex/wp5-03": [commit(3), commit(2), commit(1)],
"codex/wp5-04": [commit(4), commit(2), commit(1)],
});
assert.equal(gate.complete, false);
assert.equal(gate.candidate_baseline_branch, "codex/wp5-04");
@@ -19,13 +22,21 @@ test("WP5 lineage recognizes tasks already merged into later remote heads", () =
assert.equal(gate.task_shas["TASK-WP5-02"], sha("2"));
});
test("WP5 lineage becomes complete only at a wp5-07 head containing every task", () => {
const heads = { "codex/wp5-07": sha("7") };
const complete = inspectWp5TaskLineage(heads, Array.from({ length: 7 }, (_, index) => commit(7 - index)));
const incomplete = inspectWp5TaskLineage(heads, [commit(7), commit(6), commit(5), commit(4), commit(3), commit(2)]);
test("WP5 lineage accepts independently pushed terminal branches without rewriting their heads", () => {
const heads = Object.fromEntries(Array.from({ length: 5 }, (_, index) => [`codex/wp5-0${index + 3}`, sha(String(index + 3))]));
const histories = {
"codex/wp5-03": [commit(3), commit(2), commit(1)],
"codex/wp5-04": [commit(4), commit(2), commit(1)],
"codex/wp5-05": [commit(5), commit(4), commit(2), commit(1)],
"codex/wp5-06": [commit(6), commit(5), commit(4), commit(2), commit(1)],
"codex/wp5-07": [commit(7), commit(4), commit(2), commit(1)],
};
const complete = inspectWp5TaskLineage(heads, histories);
const incomplete = inspectWp5TaskLineage(heads, { ...histories, "codex/wp5-03": [commit(2), commit(1)] });
assert.equal(complete.complete, true);
assert.deepEqual(complete.missing_tasks, []);
assert.deepEqual(complete.terminal_branch_shas, heads);
assert.equal(incomplete.complete, false);
assert.deepEqual(incomplete.missing_tasks, ["TASK-WP5-01"]);
assert.deepEqual(incomplete.missing_tasks, ["TASK-WP5-03"]);
});
@@ -0,0 +1,131 @@
import { createHash } from "node:crypto";
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { basename, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { WP4_07_REAL_RESOURCE_VERSIONS } from "./wp4-07-fixture.mjs";
const dynamicFontTemplates = Object.freeze({
"15974853bc3294ef68e7e6d58fe74fd7": "DYN002",
"46f8336813e4c48d06a1aef294fdccf6": "DYN016",
"53ca6b704728520da50c145eabb2e635": "DYN007",
cca5efc0e02fb1bf62349bd68ef30fc1: "DYN015",
dd25b35dcb7ba4476cbaa9a9592e39e2: "DYN001",
e4210c9872f0c279b35273f230809821: "DYN011",
f4bfd4132df2d6be97ceabadf3853505: "DYN008",
});
const dynamicImages = Object.freeze({
"DYN001-image28": ["DYN001", "image28.png"],
"DYN002-image29": ["DYN002", "image29.png"],
"DYN003-image30": ["DYN003", "image30.png"],
"DYN004-image32": ["DYN004", "image32.png"],
"DYN008-backendui0": ["DYN008", "backendui0.png"],
"DYN011-backendui0": ["DYN011", "backendui0.png"],
"DYN015-imager2": ["DYN015", "imager2_2.png"],
"DYN016-image21": ["DYN016", "image21.png"],
});
function sha256(path) {
return createHash("sha256").update(readFileSync(path)).digest("hex").toUpperCase();
}
function walkFiles(directory) {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name);
return entry.isDirectory() ? walkFiles(path) : [path];
});
}
function firstFile(directory, predicate) {
const path = walkFiles(directory).find(predicate);
if (!path) throw new Error(`WP4_07_REAL_ASSET_FILE_MISSING:${basename(directory)}`);
return path;
}
function inside(root, path) {
const delta = relative(resolve(root), resolve(path));
return delta !== ".." && !delta.startsWith(`..${sep}`) && !isAbsolute(delta);
}
function contentType(path) {
const extension = extname(path).toLowerCase();
if (extension === ".png") return "image/png";
if (extension === ".otf") return "font/otf";
if (extension === ".woff") return "font/woff";
if (extension === ".woff2") return "font/woff2";
return "font/ttf";
}
function assetRecord(assetId, path, sourceReference, expectedSha256) {
if (!existsSync(path) || !statSync(path).isFile()) throw new Error(`WP4_07_REAL_ASSET_UNAVAILABLE:${assetId}`);
const actualSha256 = sha256(path);
if (expectedSha256 && actualSha256 !== expectedSha256) throw new Error(`WP4_07_REAL_ASSET_HASH_MISMATCH:${assetId}`);
return {
asset_id: assetId,
bytes: statSync(path).size,
content_type: contentType(path),
path,
sha256: actualSha256,
source_reference: sourceReference,
};
}
export function loadWp407RealAssets() {
const manifestPath = resolve(process.env.DADA_WP4_07_FINAL_ASSET_MANIFEST ?? "");
if (!process.env.DADA_WP4_07_FINAL_ASSET_MANIFEST || !existsSync(manifestPath)) throw new Error("WP4_07_FINAL_ASSET_MANIFEST_REQUIRED");
const handoffPath = resolve(process.env.DADA_COMPLEX_ASSET_MANIFEST ?? join(homedir(), "Desktop", "sticker_web_handoff", "sticker_web_catalog_manifest.json"));
const staticRoot = resolve(process.env.DADA_STATIC_STICKER_ROOT ?? join(homedir(), "Desktop", "贴纸素材"));
const backgroundPath = resolve(process.env.DADA_WP4_07_BACKGROUND_PATH ?? join(homedir(), "Documents", "贴纸脚本", "time_01_input_20260716.png"));
if (!existsSync(handoffPath) || !existsSync(staticRoot)) throw new Error("WP4_07_REAL_ARCHIVE_ROOT_REQUIRED");
const manifestRaw = readFileSync(manifestPath, "utf8");
const manifest = JSON.parse(manifestRaw);
const handoff = JSON.parse(readFileSync(handoffPath, "utf8"));
const collectionRoots = Object.fromEntries(handoff.collections.map((collection) => [collection.id, resolve(collection.root)]));
const fontRoot = collectionRoots.font_panel;
const dynamicRoot = collectionRoots.interactive_stickers;
if (!fontRoot || !dynamicRoot) throw new Error("WP4_07_REAL_ARCHIVE_COLLECTION_REQUIRED");
const publicAssets = new Map();
for (const item of manifest.assets.font_panel_items) {
const packageDirectory = resolve(fontRoot, item.canonical_resource_reference.path);
if (!inside(fontRoot, packageDirectory)) throw new Error(`WP4_07_UNSAFE_FONT_REFERENCE:${item.canonical_id}`);
const path = firstFile(resolve(packageDirectory, "font_files"), (candidate) => /\.(?:otf|ttf|woff2?|ztf)$/i.test(candidate));
publicAssets.set(item.canonical_id, assetRecord(item.canonical_id, path, `font_panel/${item.canonical_id}/${basename(path)}`));
}
for (const item of manifest.assets.static_stickers) {
const path = resolve(staticRoot, item.relative_path);
if (!inside(staticRoot, path)) throw new Error(`WP4_07_UNSAFE_STATIC_REFERENCE:${item.stable_id}`);
publicAssets.set(item.stable_id, assetRecord(item.stable_id, path, `static/${item.stable_id}`, item.sha256));
}
for (const [assetId, templateId] of Object.entries(dynamicFontTemplates)) {
const directory = resolve(dynamicRoot, "templates", templateId, "fonts", assetId);
const path = firstFile(directory, (candidate) => /\.(?:otf|ttf|woff2?|ztf)$/i.test(candidate));
publicAssets.set(assetId, assetRecord(assetId, path, `interactive/${templateId}/fonts/${assetId}/${basename(path)}`));
}
for (const [assetId, [templateId, filename]] of Object.entries(dynamicImages)) {
const path = resolve(dynamicRoot, "templates", templateId, "resource", filename);
publicAssets.set(assetId, assetRecord(assetId, path, `interactive/${templateId}/resource/${filename}`));
}
const background = assetRecord("private-background", backgroundPath, "private/background/time_01_input_20260716.png");
const manifestSha256 = createHash("sha256").update(manifestRaw).digest("hex").toUpperCase();
return {
background,
evidence: {
asset_count: publicAssets.size,
assets: [...publicAssets.values()].map(({ asset_id, bytes, content_type, sha256: hash, source_reference }) => ({
asset_id, bytes, content_type, sha256: hash, source_reference,
})),
background: { bytes: background.bytes, content_type: background.content_type, sha256: background.sha256, source_reference: background.source_reference },
manifest_sha256: manifestSha256,
release_version: manifest.release_version,
resource_versions: WP4_07_REAL_RESOURCE_VERSIONS,
source_mutations: 0,
},
publicAssets,
};
}