import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; 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 abs = (file) => path.join(root, file); const read = (file) => fs.readFileSync(abs(file), "utf8"); const json = (file) => JSON.parse(read(file)); const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex"); const writeJson = (file, value) => { fs.mkdirSync(path.dirname(abs(file)), { recursive: true }); fs.writeFileSync(abs(file), `${JSON.stringify(value, null, 2)}\n`); }; const p24ParentPath = "tests/golden/WBV2-P24-002/manifest.json"; const p24ReportPath = "tests/golden/WBV2-P24-002/object-move-receipt-report.json"; const inventoryPath = "tests/golden/WBV2-P0-002/parity-map.json"; const apiPath = "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp"; const protocolPath = "web/protocol/web-engine.ts"; const clientPath = "web/app/src/engine-client/WebEngineClient.ts"; const p24Out = "tests/golden/WBV2-P24-003"; const p25Out = "tests/golden/WBV2-P25-000"; const p24Parent = json(p24ParentPath); const p24Report = json(p24ReportPath); const inventory = json(inventoryPath); assert.equal(p24Parent.status, "done"); assert.equal(p24Parent.nextTask, "WBV2-P24-003"); assert.equal(p24Report.status, "PASS"); assert.equal(inventory.summary.inventoryEntries, 6900); assert.equal(inventory.summary.pendingLegacyEntries, 6618); assert.match(read(apiPath), /moveObjectToCollection/u); assert.match(read(protocolPath), /moveObjectToCollection/u); assert.match(read(clientPath), /applyCommand/u); const p24Card = `# WBV2-P24-003: switch to implementation-first execution\n\n- task: WBV2-P24-003\n- parent: WBV2-P24-002\n- status: done\n- gap: workflow:implementation_first_mode_switch\n- ownerFamily: PROGRAM\n- targetImplementationClass: SOURCE_BACKED\n\n## 目标\n\nClose the blocked receipt gate without changing parity counts, then authorize a source-backed implementation wave for the remaining WebEngine capability families.\n\n## 输入与范围\n\n- Existing move receipt: \`tests/golden/WBV2-P24-002/object-move-receipt-report.json\`\n- Main dispatcher: \`${apiPath}\`\n- Protocol surface: \`${protocolPath}\`\n- Implementation entrypoint: \`node tools/web/generate-wbv2-implementation-root.mjs --write\`\n\n## 验收\n\n- P24 evidence is preserved and not rerun\n- implementation-first mode is explicit\n- original 6900 inventory and provenance remain read-only\n- effective inventory remains 290 completed and 6609 pending\n- detailed Chromium, malformed, undo/redo, save/reopen, and deterministic verification is deferred to the verification wave\n- malformed, unsupported, hash-drift, or false-completion input never advances parity\n- \`node tools/web/check-task-context.mjs --task WBV2-P24-003 --write\`\n\n## 交付与回滚\n\n- Reports: \`tests/golden/WBV2-P24-003/\`\n- Manifest: \`tests/golden/WBV2-P24-003/manifest.json\`\n- Rollback: restore P24-002 as queue parent and leave P25 unactivated.\n`; fs.mkdirSync(abs(p24Out), { recursive: true }); fs.writeFileSync(abs("docs/tasks/WBV2-P24-003.md"), p24Card); const p24StatusPath = "docs/status/WBV2-P24-003.md"; fs.mkdirSync(abs("docs/status"), { recursive: true }); fs.writeFileSync(abs(p24StatusPath), `# WBV2-P24-003 status\n\n- task: \`WBV2-P24-003\`; parent: \`WBV2-P24-002\`; status: \`done\`\n- mode: \`IMPLEMENTATION_FIRST\`; P24 move source and receipt are retained without rerun\n- original inventory: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending\n- effective inventory carried forward: \`290\` completed, \`6609\` pending; completion overlay unchanged\n- full Chromium receipts and parity claims are deferred to the verification wave\n- nextTask: \`WBV2-P25-000\`\n`); const p24ReportOutPath = `${p24Out}/implementation-mode-report.json`; writeJson(p24ReportOutPath, { schemaVersion: 1, task: "WBV2-P24-003", operation: "SWITCH_TO_IMPLEMENTATION_FIRST_MODE", status: "PASS", parent: { task: p24Parent.task, manifest: p24ParentPath, manifestSha256: sha(p24ParentPath), receipt: p24ReportPath, receiptSha256: sha(p24ReportPath) }, mode: { name: "IMPLEMENTATION_FIRST", implementationWave: true, verificationWave: "DEFERRED", browserEvidence: "DEFERRED" }, inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayChanged: false, provenanceReadOnly: true }, implementationInputs: { mainApi: apiPath, protocol: protocolPath, client: clientPath, existingMoveBehaviorReused: true }, policy: { noRepeatedBehaviorRuns: true, noNewCompletionClaims: true, fullHistoricalReproduction: false, authorizedRebaselineOnly: true }, nextTask: "WBV2-P25-000", }); const task = (id, title, owner, parent, status, ready, parityId, behavior, implementationClass = "SOURCE_BACKED") => ({ id, phase: "P25", title, kind: id.endsWith("000") ? "enabling" : "feature", owner, dependsOn: [parent], parityId, implementationClass, parallelGroup: `p25-${id.slice(-3)}`, gate: id.endsWith("000") ? "G1" : "IMPLEMENTATION", card: `docs/tasks/${id}.md`, behavior, outputs: ["implementation-report", "manifest", "status"], acceptance: ["real source or WebEngine Main path is wired", "unsupported input fails closed", "implementation smoke is reproducible", "verification remains deferred until the verification wave"], status, ready, queueMutationAllowed: id.endsWith("000"), }); const tasks = [ task("WBV2-P24-000", "authorize_object_move_collection_continuation", "PROGRAM", "WBV2-P23-003", "done", false, null, "Retained source-backed Object Move-to-Collection continuation."), task("WBV2-P24-001", "select_object_move_collection_batch", "PARITY", "WBV2-P24-000", "done", false, "OBJECT_OPERATIONS.MOVE_COLLECTION", "Retained the unique moveObjectToCollection candidate without rerun."), task("WBV2-P24-002", "object_move_collection_engine_receipt", "OBJECT", "WBV2-P24-001", "done", false, "OBJECT_OPERATIONS.MOVE_COLLECTION", "Retained the existing compiled WASM Main move receipt."), task("WBV2-P24-003", "switch_to_implementation_first_mode", "PROGRAM", "WBV2-P24-002", "done", false, null, "Authorize implementation-first execution with verification deferred."), task("WBV2-P25-000", "authorize_source_backed_implementation_wave", "PROGRAM", "WBV2-P24-003", "done", false, null, "Create a capability-family implementation queue from the existing source tree.", "SOURCE_BACKED"), task("WBV2-P25-001", "implement_main_command_registry_and_dispatch", "ENGINE", "WBV2-P25-000", "in_progress", true, "ENGINE.COMMAND_REGISTRY", "Create and consume one source-of-truth registry for Main-authoritative WebEngine commands.", "SOURCE_BACKED"), task("WBV2-P25-002", "implement_core_scene_object_surface", "OBJECT", "WBV2-P25-001", "planned", false, "OBJECT_OPERATIONS.CORE_SCENE", "Complete create, duplicate, delete, rename, transform, parent, collection and visibility paths."), task("WBV2-P25-003", "implement_mesh_authoring_surface", "MESH", "WBV2-P25-002", "planned", false, "MESH.AUTHORING", "Complete mesh edit, UV, material slots, vertex colors and weights paths."), task("WBV2-P25-004", "implement_nonmesh_geometry_surface", "GEOMETRY", "WBV2-P25-003", "planned", false, "GEOMETRY.NON_MESH", "Complete curve, surface, font, volume, metaball and grease-pencil paths."), task("WBV2-P25-005", "implement_animation_constraint_render_surface", "ANIMATION", "WBV2-P25-004", "planned", false, "ANIMATION.RENDER", "Complete keyframes, F-curves, NLA, constraints, cameras, lights, world and sculpt paths."), task("WBV2-P25-006", "implement_io_media_storage_surface", "IO", "WBV2-P25-005", "planned", false, "IO.MEDIA_STORAGE", "Complete image, library, font, blend save/reopen and asset/resource routing paths."), task("WBV2-P25-007", "implement_frontend_workbench_surface", "FRONTEND", "WBV2-P25-006", "planned", false, "FRONTEND.WORKBENCH", "Expose implemented command families through the worker, client and workbench UI."), task("WBV2-P25-008", "run_implementation_wave_smoke", "RELEASE", "WBV2-P25-007", "planned", false, null, "Run one bounded build and browser-start smoke before opening the verification wave."), ]; const cards = { "WBV2-P25-000": ["authorize_source_backed_implementation_wave", "PROGRAM", "WBV2-P24-003", "Create a bounded implementation-first queue from existing source-backed Main, protocol, worker, client and frontend surfaces.", [apiPath, protocolPath, clientPath], "node tools/web/generate-wbv2-implementation-root.mjs --write"], "WBV2-P25-001": ["implement_main_command_registry_and_dispatch", "ENGINE", "WBV2-P25-000", "Create one typed registry for all WebEngine edit commands and consume it at the client/Main dispatch boundary, while keeping existing command behavior unchanged.", [apiPath, protocolPath, clientPath], "node tools/web/generate-wbv2-main-command-registry.mjs --write"], "WBV2-P25-002": ["implement_core_scene_object_surface", "OBJECT", "WBV2-P25-001", "Wire the core scene/object capability family end to end through Main, protocol, worker, client and workbench entry points.", [apiPath, protocolPath, clientPath], "node tools/web/generate-wbv2-implementation-slice.mjs --task WBV2-P25-002 --write"], "WBV2-P25-003": ["implement_mesh_authoring_surface", "MESH", "WBV2-P25-002", "Wire mesh authoring, UV, materials, vertex color and weight commands through the existing source-backed path.", [apiPath, protocolPath], "node tools/web/generate-wbv2-implementation-slice.mjs --task WBV2-P25-003 --write"], "WBV2-P25-004": ["implement_nonmesh_geometry_surface", "GEOMETRY", "WBV2-P25-003", "Wire curve, surface, font, volume, metaball and grease-pencil command families through Main and the web bridge.", [apiPath, protocolPath], "node tools/web/generate-wbv2-implementation-slice.mjs --task WBV2-P25-004 --write"], "WBV2-P25-005": ["implement_animation_constraint_render_surface", "ANIMATION", "WBV2-P25-004", "Wire animation, constraints, NLA, camera, light, world and sculpt command families.", [apiPath, protocolPath], "node tools/web/generate-wbv2-implementation-slice.mjs --task WBV2-P25-005 --write"], "WBV2-P25-006": ["implement_io_media_storage_surface", "IO", "WBV2-P25-005", "Wire image, library, font, blend persistence and resource routing paths using existing source implementations.", [apiPath, protocolPath], "node tools/web/generate-wbv2-implementation-slice.mjs --task WBV2-P25-006 --write"], "WBV2-P25-007": ["implement_frontend_workbench_surface", "FRONTEND", "WBV2-P25-006", "Expose implemented command families through worker/client state and the workbench controls without duplicating protocol logic.", ["web/app/src", "web/protocol/web-engine.ts"], "node tools/web/generate-wbv2-implementation-slice.mjs --task WBV2-P25-007 --write"], "WBV2-P25-008": ["run_implementation_wave_smoke", "RELEASE", "WBV2-P25-007", "Run only bounded build and browser-start smoke; leave full production verification for the next wave.", ["web/package.json", "web/app/package.json"], "node tools/web/generate-wbv2-implementation-smoke.mjs --write"], }; for (const [id, [title, owner, parent, goal, inputs, command]] of Object.entries(cards)) { const parity = tasks.find((candidate) => candidate.id === id)?.parityId ?? `workflow:${title}`; const card = `# ${id}: ${title}\n\n- task: ${id}\n- parent: ${parent}\n- status: ${id === "WBV2-P25-001" ? "in_progress" : id === "WBV2-P25-000" ? "done" : "planned"}\n- gap: ${parity}\n- ownerFamily: ${owner}\n- targetImplementationClass: SOURCE_BACKED\n\n## 目标\n\n${goal}\n\n## 输入与范围\n\n${inputs.map((input) => `- Focused source: \`${input}\``).join("\n")}\n- Implementation entrypoint: \`${command}\`\n\n## 验收\n\n- real source or WebEngine Main implementation is present and connected\n- worker/client/frontend route uses the typed protocol without a duplicate shim\n- malformed or unsupported input fails closed without claiming parity\n- implementation smoke is reproducible; detailed Chromium receipt, undo/redo, save/reopen and deterministic verification remain deferred\n- \`node tools/web/check-task-context.mjs --task ${id} --write\`\n\n## 交付与回滚\n\n- Reports: \`tests/golden/${id}/\`\n- Status: \`docs/status/${id}.md\`\n- Manifest: \`tests/golden/${id}/manifest.json\`\n- Rollback: restore ${parent} as queue parent and leave the implementation slice in_progress.\n`; fs.writeFileSync(abs(`docs/tasks/${id}.md`), card); } const planPath = `${p25Out}/implementation-task-plan.json`; const plan = { schemaVersion: 1, planId: "BLENDER_WEB_IMPLEMENTATION_FIRST_WAVE_P25", status: "ACTIVE_SOURCE_BACKED_IMPLEMENTATION_QUEUE", authority: "user_authorized_implementation_first_execution", sourcePlan: "docs/BLENDER_WEB_PARITY_TASK_PLAN_V2.json", executionPointer: "docs/EXECUTION_QUEUE.md", runtimePolicy: { blenderSource: "blender-5.2.0", mainAuthority: "blender_main", browserPolicy: "chromium_only_for_later_verification", verificationPolicy: "deferred_until_implementation_wave_closes", reusePolicy: "source_hash_behavior_hash_and_dedupe_key" }, inventoryPolicy: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayMutation: "forbidden_during_implementation_wave", provenance: "read_only" }, tasks, }; writeJson(planPath, plan); const p25ReportPath = `${p25Out}/implementation-root-report.json`; writeJson(p25ReportPath, { schemaVersion: 1, task: "WBV2-P25-000", operation: "AUTHORIZE_SOURCE_BACKED_IMPLEMENTATION_WAVE", status: "PASS", parent: { task: "WBV2-P24-003", manifest: `${p24Out}/manifest.json`, mode: "IMPLEMENTATION_FIRST" }, scope: { plannedTasks: tasks.filter((candidate) => candidate.phase === "P25").map((candidate) => candidate.id), firstTask: "WBV2-P25-001", capabilityFamilies: tasks.filter((candidate) => candidate.phase === "P25" && candidate.parityId).map((candidate) => candidate.parityId) }, implementation: { sourceApi: apiPath, protocol: protocolPath, client: clientPath, reuseExistingMoveReceipt: true, noRepeatedBehaviorRuns: true }, verification: { status: "DEFERRED", chromiumReceipts: false, completionOverlay: false, fullHistoricalReproduction: false }, inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609 }, nextTask: "WBV2-P25-001", }); const p25StatusPath = "docs/status/WBV2-P25-000.md"; fs.writeFileSync(abs(p25StatusPath), "# WBV2-P25-000 status\n\n- task: `WBV2-P25-000`; parent: `WBV2-P24-003`; status: `done`\n- mode: `IMPLEMENTATION_FIRST`; source-backed capability-family queue authorized\n- first implementation task: `WBV2-P25-001`\n- verification receipts and completion overlays: `DEFERRED`\n- inventory unchanged: `6900` total, `281` legacy completed, `6618` legacy pending, `290/6609` effective carried forward\n- nextTask: `WBV2-P25-001`\n"); const p24ManifestPath = `${p24Out}/manifest.json`; writeJson(p24ManifestPath, { schemaVersion: 1, task: "WBV2-P24-003", parentTask: "WBV2-P24-002", operation: "SWITCH_TO_IMPLEMENTATION_FIRST_MODE", status: "done", enablingTask: true, parityStateChange: false, runtime: "READ_ONLY_IMPLEMENTATION_MODE_SWITCH", artifacts: { parentManifest: { path: p24ParentPath, sha256: sha(p24ParentPath) }, parentReceipt: { path: p24ReportPath, sha256: sha(p24ReportPath) }, inventory: { path: inventoryPath, sha256: sha(inventoryPath) }, mainApi: { path: apiPath, sha256: sha(apiPath) }, protocol: { path: protocolPath, sha256: sha(protocolPath) }, client: { path: clientPath, sha256: sha(clientPath) }, report: { path: p24ReportOutPath, sha256: sha(p24ReportOutPath) }, status: { path: p24StatusPath, sha256: sha(p24StatusPath) }, }, assertions: { implementationFirst: true, verificationDeferred: true, inventoryTotal: 6900, baseInventoryPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayChanged: false, historicalReproduction: false, duplicateValidationAvoided: true }, nextTask: "WBV2-P25-000", }); // Generate the v2 index after all cards and the P25 plan exist. This is the only // queue catalog writer; offsets and active state are never edited by hand. execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(planPath)], { cwd: root, stdio: "inherit" }); const p25ManifestPath = `${p25Out}/manifest.json`; writeJson(p25ManifestPath, { schemaVersion: 1, task: "WBV2-P25-000", parentTask: "WBV2-P24-003", operation: "AUTHORIZE_SOURCE_BACKED_IMPLEMENTATION_WAVE", status: "done", enablingTask: true, parityStateChange: false, runtime: "SOURCE_BACKED_IMPLEMENTATION_QUEUE", artifacts: { parentManifest: { path: p24ManifestPath, sha256: sha(p24ManifestPath) }, parentStatus: { path: p24StatusPath, sha256: sha(p24StatusPath) }, plan: { path: planPath, sha256: sha(planPath) }, report: { path: p25ReportPath, sha256: sha(p25ReportPath) }, rootCard: { path: "docs/tasks/WBV2-P25-000.md", sha256: sha("docs/tasks/WBV2-P25-000.md") }, rootStatus: { path: p25StatusPath, sha256: sha(p25StatusPath) }, taskIndex: { path: "tests/golden/WBV2/task-index.json", sha256: sha("tests/golden/WBV2/task-index.json") }, }, assertions: { implementationFirst: true, sourceBacked: true, plannedCapabilityFamilies: 7, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false }, nextTask: "WBV2-P25-001", }); let queue = read("docs/EXECUTION_QUEUE.md"); queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, "| 当前任务 | `WBV2-P25-001` |") .replace(/\| parent manifest \| `[^`]+` \|/u, "| parent manifest | `tests/golden/WBV2-P25-000/manifest.json` |") .replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, "| 任务卡 | [`tasks/WBV2-P25-001.md`](tasks/WBV2-P25-001.md) |"); fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue); execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", "WBV2-P24-003", "--write"], { cwd: root, stdio: "inherit" }); execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", "WBV2-P25-001", "--write"], { cwd: root, stdio: "inherit" }); let next = read("nextTask.md"); next = next.replace(/\| Current task when this file was written \| `[^`]+` \|/u, "| Current task when this file was written | `WBV2-P25-001` |") .replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |") .replace(/\| Parent manifest \| `[^`]+` \|/u, "| Parent manifest | `tests/golden/WBV2-P25-000/manifest.json` |") .replace(/\| Current task card \| `[^`]+` \|/u, "| Current task card | `docs/tasks/WBV2-P25-001.md` |") .replace(/\| Current task-context nextTask \| `[^`]+` \|/u, "| Current task-context nextTask | `WBV2-P25-002` |") .replace(/\| Focused command \| `[^`]+` \|/u, "| Focused command | `node tools/web/generate-wbv2-main-command-registry.mjs --write` |"); fs.writeFileSync(abs("nextTask.md"), next); console.log("wbv2-implementation-root-written mode=IMPLEMENTATION_FIRST current=WBV2-P25-001 inventory=6900 effectivePending=6609 verification=DEFERRED");