import { createHash } from "node:crypto"; import { spawnSync } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import { WP4_07_RED_RESOURCE_VERSION, WP4_07_SOURCE_HASHES, assertWp407Fixture } from "../../tests/visual-performance/wp4-07-fixture.mjs"; export const WP4_07_REQUIRED_WP5_TASKS = Object.freeze( Array.from({ length: 7 }, (_, index) => `TASK-WP5-0${index + 1}`), ); export const WP4_07_FINAL_WP5_BRANCH = "codex/wp5-07"; export function validateWp407FrozenInputs() { const mismatches = []; for (const [path, expected] of Object.entries(WP4_07_SOURCE_HASHES)) { if (!existsSync(path)) { mismatches.push({ actual: null, expected, path }); continue; } const actual = createHash("sha256").update(readFileSync(path)).digest("hex").toUpperCase(); if (actual !== expected) mismatches.push({ actual, expected, path }); } if (mismatches.length > 0) { const error = new Error("WP4_07_FROZEN_SOURCE_CHANGED"); error.details = mismatches; throw error; } return assertWp407Fixture(); } export function inspectWp5TaskLineage(heads, commits) { const candidate_branch = [...WP4_07_REQUIRED_WP5_TASKS] .reverse() .map((taskId) => taskId.replace("TASK-WP5-", "codex/wp5-")) .find((branch) => /^[0-9a-f]{40}$/.test(heads[branch] ?? "")) ?? null; const task_shas = Object.fromEntries(WP4_07_REQUIRED_WP5_TASKS.flatMap((taskId) => { const commit = commits.find((entry) => entry.subject.includes(taskId)); return commit && /^[0-9a-f]{40}$/.test(commit.sha) ? [[taskId, commit.sha]] : []; })); const missing_tasks = WP4_07_REQUIRED_WP5_TASKS.filter((taskId) => !task_shas[taskId]); return { candidate_baseline_branch: candidate_branch, candidate_baseline_sha: candidate_branch ? heads[candidate_branch] : null, complete: candidate_branch === WP4_07_FINAL_WP5_BRANCH && missing_tasks.length === 0, final_branch_sha: heads[WP4_07_FINAL_WP5_BRANCH] ?? null, missing_tasks, required_final_branch: WP4_07_FINAL_WP5_BRANCH, required_tasks: WP4_07_REQUIRED_WP5_TASKS, task_shas, }; } export function readWp5RemoteGate() { const result = spawnSync("git", ["ls-remote", "--heads", "origin", "codex/wp5-*"], { encoding: "utf8", timeout: 30_000 }); if ((result.status ?? 1) !== 0) { const error = new Error("WP4_07_GITEA_GATE_UNREADABLE"); error.details = { exit_code: result.status ?? 1 }; throw error; } const heads = Object.fromEntries(result.stdout.trim().split(/\r?\n/).filter(Boolean).map((line) => { const [sha, reference] = line.split(/\s+/); return [reference.replace("refs/heads/", ""), sha]; })); const candidateBranch = [...WP4_07_REQUIRED_WP5_TASKS] .reverse() .map((taskId) => taskId.replace("TASK-WP5-", "codex/wp5-")) .find((branch) => /^[0-9a-f]{40}$/.test(heads[branch] ?? "")); let commits = []; if (candidateBranch) { const fetch = spawnSync("git", ["fetch", "--quiet", "--no-tags", "origin", `refs/heads/${candidateBranch}`], { encoding: "utf8", timeout: 60_000 }); if ((fetch.status ?? 1) !== 0) { const error = new Error("WP4_07_GITEA_BASELINE_FETCH_FAILED"); error.details = { branch: candidateBranch, exit_code: fetch.status ?? 1 }; throw error; } const log = spawnSync("git", ["log", "--format=%H%x09%s", heads[candidateBranch]], { encoding: "utf8", timeout: 30_000 }); if ((log.status ?? 1) !== 0) { const error = new Error("WP4_07_GITEA_BASELINE_HISTORY_UNREADABLE"); error.details = { branch: candidateBranch, exit_code: log.status ?? 1 }; throw error; } commits = log.stdout.trim().split(/\r?\n/).filter(Boolean).map((line) => { const separator = line.indexOf("\t"); return { sha: line.slice(0, separator), subject: line.slice(separator + 1) }; }); } return { heads, ...inspectWp5TaskLineage(heads, commits), }; } export function validateWp5FinalManifest(path) { if (!path || !existsSync(path)) throw new Error("WP4_07_FINAL_ASSET_MANIFEST_REQUIRED"); const raw = readFileSync(path, "utf8"); if (raw.includes(WP4_07_RED_RESOURCE_VERSION) || raw.includes("fixture-v1")) throw new Error("WP4_07_PLACEHOLDER_ASSET_REJECTED"); const manifest = JSON.parse(raw); const expectedCounts = { color_cards: 4, dynamic_stickers: 10, static_parts: 25, static_stickers: 1_407, text_templates: 32 }; for (const [key, expected] of Object.entries(expectedCounts)) { if (manifest.counts?.[key] !== expected) throw new Error(`WP4_07_FINAL_MANIFEST_COUNT_MISMATCH:${key}`); } if (!manifest.release_version || String(manifest.release_version).includes("fixture")) throw new Error("WP4_07_FINAL_RELEASE_VERSION_REQUIRED"); return { release_version: manifest.release_version, sha256: createHash("sha256").update(raw).digest("hex").toUpperCase() }; }