import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const write = process.argv.includes("--write"); const absolute = (file) => path.join(root, file); const readJson = (file) => JSON.parse(fs.readFileSync(absolute(file), "utf8")); const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(absolute(file))).digest("hex"); const same = (left, right, label) => assert.deepEqual(left, right, label); const outputRoot = "tests/golden/WBV2-P8-001"; const currentDependencyPath = `${outputRoot}/dependency-inventory.current.json`; const reportPath = `${outputRoot}/recovery-baseline-report.json`; const statusPath = "docs/status/WBV2-P8-001.md"; const planPath = `${outputRoot}/recovery-task-plan.json`; const manifestPath = `${outputRoot}/manifest.json`; const parentManifest = readJson("tests/golden/WBV2-P8-000/manifest.json"); assert.equal(parentManifest.task, "WBV2-P8-000"); assert.equal(parentManifest.nextTask, "WBV2-P8-001"); for (const artifact of Object.values(parentManifest.artifacts ?? {})) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path); const sourceHashes = readJson("tests/golden/WBV2-P0-002/source-hashes.json"); const wbv2Drift = Object.entries(sourceHashes.sources).map(([file, expected]) => ({ file, expected, actual: sha256(file) })).filter((entry) => entry.expected !== entry.actual); assert.deepEqual(wbv2Drift.map((entry) => entry.file), ["docs/BLENDER_WEB_PARITY_TASK_PLAN_V2.json"]); const recoveryPlan = readJson("tests/golden/WBV2-P8-000/recovery-task-plan.json"); assert.equal(recoveryPlan.parentPlan.sha256, sha256("docs/BLENDER_WEB_PARITY_TASK_PLAN_V2.json")); for (const task of recoveryPlan.tasks.filter((task) => task.id.startsWith("WBV2-P") && !task.id.startsWith("WBV2-P8-"))) { const manifest = readJson(`tests/golden/${task.id}/manifest.json`); assert.equal(manifest.task, task.id); assert.equal(task.status, manifest.status === "blocked" ? "blocked" : "done", `${task.id} recovery status drift`); } fs.mkdirSync(absolute(outputRoot), { recursive: true }); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "wbv2-dependency-current-")); const generatedDependency = path.join(temporary, "dependency-inventory.json"); try { execFileSync(process.execPath, [absolute("tools/web/generate-dependency-inventory.mjs"), generatedDependency], { cwd: root, stdio: "pipe" }); const historical = readJson("tests/golden/M13-05C/dependency-inventory.json"); const current = JSON.parse(fs.readFileSync(generatedDependency, "utf8")); same(current.roots, historical.roots, "dependency roots drifted"); same(current.categoryCounts, historical.categoryCounts, "dependency category counts drifted"); same(current.sharedCount, historical.sharedCount, "dependency sharing drifted"); same(current.packages, historical.packages, "dependency closure drifted"); assert.equal(current.lockfile.sha256, historical.lockfile.sha256, "package-lock drifted"); fs.copyFileSync(generatedDependency, absolute(currentDependencyPath)); } finally { fs.rmSync(temporary, { recursive: true, force: true }); } const currentDependency = readJson(currentDependencyPath); const historicalDependency = readJson("tests/golden/M13-05C/dependency-inventory.json"); const report = { schemaVersion: 1, task: "WBV2-P8-001", operation: "REBASELINE_MUTABLE_INVENTORIES", status: "PASS", wbv2: { historicalInventory: "tests/golden/WBV2-P0-002/manifest.json", expectedMutableDriftOnly: true, drift: wbv2Drift }, dependency: { historicalInventory: "tests/golden/M13-05C/manifest.json", packageHashChanged: currentDependency.package.sha256 !== historicalDependency.package.sha256, lockfileHashStable: currentDependency.lockfile.sha256 === historicalDependency.lockfile.sha256, rootsStable: true, closureStable: true, packageCount: currentDependency.packages.length }, invariants: { historicalManifestsRewritten: false, capabilityClaimsAdded: 0, dependencyPackagesAdded: 0 }, nextTask: "WBV2-P8-002", }; if (!write) { process.stdout.write(`wbv2-recovery-baseline-ok wbv2Drift=${wbv2Drift.length} dependencyPackages=${currentDependency.packages.length} closureStable=1 next=WBV2-P8-002\n`); process.exit(0); } fs.writeFileSync(absolute(reportPath), `${JSON.stringify(report, null, 2)}\n`); const status = `# WBV2-P8-001 status\n\n- task: \`WBV2-P8-001\`; parent: \`WBV2-P8-000\`; status: \`done\`\n- nextTask: \`WBV2-P8-002\`\n\nThe WBV2 drift is limited to the mutable execution plan. The npm dependency graph and lockfile are unchanged; only package scripts changed. Historical P0 and M13 manifests remain untouched.\n`; fs.mkdirSync(absolute("docs/status"), { recursive: true }); fs.writeFileSync(absolute(statusPath), status); const nextPlan = structuredClone(recoveryPlan); nextPlan.parentRecoveryPlan = { path: "tests/golden/WBV2-P8-000/recovery-task-plan.json", sha256: sha256("tests/golden/WBV2-P8-000/recovery-task-plan.json") }; nextPlan.tasks = nextPlan.tasks.map((task) => task.id === "WBV2-P8-001" ? { ...task, status: "done", ready: false } : task.id === "WBV2-P8-002" ? { ...task, status: "in_progress", ready: true } : { ...task, ready: false }); fs.writeFileSync(absolute(planPath), `${JSON.stringify(nextPlan, null, 2)}\n`); execFileSync(process.execPath, [absolute("tools/web/check-task-context.mjs"), "--task", "WBV2-P8-001", "--write"], { cwd: root, stdio: "pipe" }); const checkerPath = "tools/web/check-wbv2-recovery-baseline.mjs"; const artifactPaths = { parentManifest: "tests/golden/WBV2-P8-000/manifest.json", historicalWbv2Manifest: "tests/golden/WBV2-P0-002/manifest.json", historicalDependencyManifest: "tests/golden/M13-05C/manifest.json", sourceHashes: "tests/golden/WBV2-P0-002/source-hashes.json", sourcePlan: "docs/BLENDER_WEB_PARITY_TASK_PLAN_V2.json", parentRecoveryPlan: "tests/golden/WBV2-P8-000/recovery-task-plan.json", recoveryPlan: planPath, dependencyInventory: currentDependencyPath, checker: checkerPath, package: "web/package.json", report: reportPath, status: statusPath, taskContext: `${outputRoot}/task-context.json` }; const artifacts = Object.fromEntries(Object.entries(artifactPaths).map(([name, file]) => [name, { path: file, sha256: sha256(file) }])); const manifest = { schemaVersion: 1, task: "WBV2-P8-001", parentTask: "WBV2-P8-000", operation: "REBASELINE_MUTABLE_INVENTORIES", status: "done", enablingTask: true, parityStateChange: false, artifacts, nextTask: "WBV2-P8-002" }; fs.writeFileSync(absolute(manifestPath), `${JSON.stringify(manifest, null, 2)}\n`); const nextCard = `# WBV2-P8-002: close atomic Chromium receipts\n\n- task: WBV2-P8-002\n- parent: WBV2-P8-001\n- status: in_progress\n- gap: workflow:close_atomic_chromium_receipts\n- ownerFamily: PARITY\n- targetImplementationClass: LOCAL_BOUNDED\n\n## 目标\n\nProduce manifest-bound Chromium receipts for all 45 family behaviors by reusing verified WBV2 desktop/WASM/save-reopen evidence and running the shared production protocol in Chromium.\n\n## 输入与范围\n\n- Queue: \`tests/golden/WBV2-P7-002/atomic-queue-desktop-report.json\`\n- Existing receipts: \`tests/golden/WBV2-P7-003/atomic-chromium-receipt-index.json\`\n- Chromium runner: \`web/tests/e2e/wbv2-atomic-recovery.spec.ts\`\n\n## 验收\n\nnpm --prefix web run test:e2e -- --workers=1 tests/e2e/wbv2-atomic-recovery.spec.ts\nnode tools/web/generate-wbv2-recovery-receipts.mjs --write\nnode tools/web/check-task-context.mjs --task WBV2-P8-002 --write\n\nMissing Chromium output, unsupported family mapping, source hash drift, or non-exact save/reopen must keep in_progress and must not advance nextTask.\n\n## 交付与回滚\n\n- Reports: \`tests/golden/WBV2-P8-002/\`\n- Status: \`docs/status/WBV2-P8-002.md\`\n- Manifest: \`tests/golden/WBV2-P8-002/manifest.json\`\n- Rollback: restore P8-001 as queue parent without modifying earlier evidence.\n`; fs.writeFileSync(absolute("docs/tasks/WBV2-P8-002.md"), nextCard); execFileSync(process.execPath, [absolute("tools/web/generate-v2-task-index.mjs"), "--plan-path", planPath], { cwd: root, stdio: "inherit" }); let queue = fs.readFileSync(absolute("docs/EXECUTION_QUEUE.md"), "utf8"); queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, "| 当前任务 | `WBV2-P8-002` |") .replace(/\| parent manifest \| `[^`]+` \|/u, "| parent manifest | `tests/golden/WBV2-P8-001/manifest.json` |") .replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, "| 任务卡 | [`tasks/WBV2-P8-002.md`](tasks/WBV2-P8-002.md) |"); fs.writeFileSync(absolute("docs/EXECUTION_QUEUE.md"), queue); process.stdout.write(`wbv2-recovery-baseline-written task=WBV2-P8-001 artifacts=${Object.keys(artifacts).length} next=WBV2-P8-002\n`);