import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; const root = path.resolve(new URL(".", import.meta.url).pathname, "../.."); const reportPath = path.join(root, "tests/golden/WBV2/full-source-parity-validation-report.json"); const logRoot = path.join(root, "tests/golden/WBV2/full-source-parity-validation-logs"); fs.mkdirSync(logRoot, { recursive: true }); const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); const commands = [ ["context", "node", ["tools/web/check-task-context.mjs"]], ["governance", "node", ["tools/web/check-context-governance.mjs"]], ["inventory", "npm", ["--prefix", "web", "run", "test:wbv2-inventory"]], ["dynamic-inventory", "node", ["tools/web/check-source-dynamic-inventory.mjs"]], ["completeness", "node", ["tools/web/audit-wbv2-feature-completeness.mjs"]], ["feature-smoke-evidence", "node", ["tools/web/check-wbv2-feature-smoke-plan.mjs"]], ["submodule-smoke-evidence", "node", ["tools/web/check-wbv2-submodule-smoke-plan.mjs"]], ["typecheck", "npm", ["--prefix", "web", "run", "typecheck"]], ["lint", "npm", ["--prefix", "web", "run", "lint"]], ["unit", "node", ["tools/web/run-unit-tests.mjs"]], ["local-deps", "npm", ["--prefix", "web", "run", "check:local-deps"]], ["source-sync", "node", ["tools/web/check-web-engine-source-sync.mjs"]], ["deployment-contract", "node", ["tools/web/check-deployment-contract.mjs"]], ["deployment-http", "node", ["tools/web/check-deployment-http.mjs"]], ["deployment-cache", "node", ["tools/web/check-deployment-cache.mjs"]], ["v1-acceptance", "npm", ["--prefix", "web", "run", "release:v1-acceptance"]], ["v1-acceptance-evidence", "npm", ["--prefix", "web", "run", "test:v1-acceptance-evidence"]], ["v1-acceptance-release-binding", "npm", ["--prefix", "web", "run", "test:v1-acceptance-release-binding"]], ["full-e2e", "npm", ["--prefix", "web", "run", "test:e2e", "--", "--workers=1"]], ["release-browser", "npm", ["--prefix", "web", "run", "test:browser"]], ["release-performance", "npm", ["--prefix", "web", "run", "test:release-performance"]], ["release-package", "npm", ["--prefix", "web", "run", "test:release-package"]], ["offline-reproducibility", "npm", ["--prefix", "web", "run", "release:offline"]], ["deployment-runbook", "npm", ["--prefix", "web", "run", "test:deployment-runbook"]], ["upgrade-runbook", "npm", ["--prefix", "web", "run", "test:upgrade-runbook"]], ["rollback-runbook", "npm", ["--prefix", "web", "run", "test:rollback-runbook"]], ["operations-rehearsal", "npm", ["--prefix", "web", "run", "test:operations-rehearsal"]], ["deployed-site", "node", ["tools/web/check-deployed-web-blender.mjs"]], ]; const records = []; for (const [id, command, args] of commands) { const started = Date.now(); const result = spawnSync(command, args, { cwd: root, encoding: "utf8", env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", BLENDER_BIN: process.env.BLENDER_BIN ?? "build_blender_native/bin/blender" }, maxBuffer: 64 * 1024 * 1024 }); const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim(); const logPath = path.join(logRoot, `${String(records.length + 1).padStart(2, "0")}-${id}.log`); fs.writeFileSync(logPath, `${output}\n`); records.push({ id, command: [command, ...args].join(" "), exitCode: result.status ?? 1, durationMs: Date.now() - started, log: { path: path.relative(root, logPath).replaceAll(path.sep, "/"), sha256: sha(logPath), bytes: fs.statSync(logPath).size } }); process.stdout.write(`[${records.length}/${commands.length}] ${id} exit=${records.at(-1).exitCode} durationMs=${records.at(-1).durationMs}\n`); } const inventory = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/WBV2/dynamic-inventory.json"), "utf8")); const catalog = JSON.parse(fs.readFileSync(path.join(root, "web/app/src/capabilities/blender-feature-route-catalog.json"), "utf8")); const sourceParityStatus = inventory.coverage?.unverifiedUnits === 0 && catalog.unclassifiedCount === 0 ? "READY" : "PARTIAL"; const webProductStatus = records.filter((item) => ["context", "governance", "inventory", "dynamic-inventory", "completeness", "feature-smoke-evidence", "submodule-smoke-evidence", "typecheck", "lint", "unit", "local-deps", "source-sync", "full-e2e", "release-browser"].includes(item.id)).every((item) => item.exitCode === 0) ? "READY" : "BLOCKED"; const releaseStatus = records.filter((item) => ["deployment-contract", "deployment-http", "deployment-cache", "release-package", "offline-reproducibility", "deployment-runbook", "upgrade-runbook", "rollback-runbook", "operations-rehearsal", "deployed-site"].includes(item.id)).every((item) => item.exitCode === 0) ? "READY" : "BLOCKED"; const report = { schemaVersion: 1, reportId: "WBV2-FULL-SOURCE-PARITY-VALIDATION-001", generatedAt: new Date().toISOString(), sourceParityStatus, webProductStatus, releaseStatus, inventory: { unitCount: inventory.summary.unitCount, verifiedUnits: inventory.coverage.verifiedUnits, unverifiedUnits: inventory.coverage.unverifiedUnits, sourceTreeSha256: catalog.sourceTreeSha256, classifiedCount: catalog.classifiedCount, unclassifiedCount: catalog.unclassifiedCount }, records, policy: `route classification is not behavioral completion; ${inventory.coverage.unverifiedUnits} unverified source units remain explicit` }; fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stdout.write(`web-source-parity-validation sourceParity=${sourceParityStatus} webProduct=${webProductStatus} release=${releaseStatus} report=${reportPath}\n`); process.exit(records.every((item) => item.exitCode === 0) ? 0 : 1);