feat(web): switch to implementation-first capability wave
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-29 01:22:03 +08:00
parent 5fb12cd6a5
commit 7549b9a15c
159 changed files with 14110 additions and 74 deletions

View File

@@ -0,0 +1,100 @@
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 configs = {
"WBV2-P26-001": {
parent: "WBV2-P26-000", next: "WBV2-P26-002", family: "GEOMETRY_NODES.MAIN_WRITER",
commands: ["setGeometryNodeGraph"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.h", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/geometry-nodes.ts", "web/protocol/web-engine-capabilities.ts", "web/app/src/workers/web-engine.worker.ts"],
marker: ["blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "web_engine_blend_main_set_geometry_node_graph"],
note: "validated graphs are available immediately in the authoritative Main session; binary .blend persistence is deferred to verification/persistence work",
},
"WBV2-P26-002": {
parent: "WBV2-P26-001", next: "WBV2-P26-003", family: "SHADER.GRAPH_WORKBENCH",
commands: ["setShaderGraph"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/shader-graph.ts", "web/protocol/web-engine-capabilities.ts"],
marker: ["blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "web_engine_blend_main_set_shader_graph"],
note: "source-backed shader writer is retained; workbench exposure and production verification remain deferred",
},
"WBV2-P26-003": {
parent: "WBV2-P26-002", next: "WBV2-P26-004", family: "SIMULATION.PHYSICS_PARTICLE",
commands: ["setPhysicsSimulation"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/physics-simulation.ts", "web/protocol/web-engine-capabilities.ts"],
marker: ["blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "web_engine_blend_main_set_physics_simulation"],
note: "validated physics manifests are writable in the authoritative Main session; solver execution and binary persistence remain separate capabilities",
},
"WBV2-P26-004": {
parent: "WBV2-P26-003", next: "WBV2-P26-005", family: "RENDER.COMPOSITOR_SEQUENCER",
commands: ["setShaderGraph"],
files: ["web/protocol/render-capabilities.ts", "web/protocol/compositor.ts", "web/protocol/sequencer-export.ts", "web/app/src/workers/web-engine.worker.ts"],
marker: ["web/app/src/workers/web-engine.worker.ts", "gateRenderCapability"],
note: "render/compositor/sequencer capability gates are wired; renderer production verification remains deferred",
},
"WBV2-P26-005": {
parent: "WBV2-P26-004", next: "WBV2-P26-006", family: "IO.ASSET_LIBRARY_STORAGE",
commands: ["importImage"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/library-main-append.ts", "web/app/src/storage/StorageClient.ts", "web/app/src/workers/web-engine.worker.ts"],
marker: ["web/app/src/workers/web-engine.worker.ts", "appendLibraryObject"],
note: "asset/library/storage routes are retained and ready for persistence verification",
},
"WBV2-P26-006": {
parent: "WBV2-P26-005", next: null, family: "IMPLEMENTATION_WAVE.SMOKE",
commands: [],
files: ["web/package.json", "web/app/vite.config.ts"],
marker: ["web/app/vite.config.ts", "defineConfig"],
note: "bounded build/start smoke only; opens the deferred Chromium verification wave",
},
};
const taskArg = process.argv.indexOf("--task");
const task = taskArg >= 0 ? process.argv[taskArg + 1] : "WBV2-P26-001";
const config = configs[task];
if (!config) throw new Error(`unsupported advanced implementation slice: ${task}`);
const parentManifestPath = `tests/golden/${config.parent}/manifest.json`;
const parent = json(parentManifestPath);
assert.equal(parent.status, "done");
assert.equal(parent.nextTask, task);
const protocol = read("web/protocol/web-engine.ts");
const registry = read("web/protocol/web-engine-capabilities.ts");
for (const command of config.commands) {
assert.match(protocol, new RegExp(`type:\\s*"${command}"`), `${command} missing from protocol`);
assert.match(registry, new RegExp(`\\b${command}\\s*:`), `${command} missing from registry`);
}
const [markerPath, marker] = config.marker;
assert.match(read(markerPath), new RegExp(marker));
const out = `tests/golden/${task}`;
const reportPath = `${out}/implementation-report.json`;
const statusPath = `docs/status/${task}.md`;
writeJson(reportPath, { schemaVersion: 1, task, operation: "IMPLEMENT_ADVANCED_CAPABILITY_FAMILY", status: "PASS", family: config.family, sourceBacked: true, implementation: { commandCount: config.commands.length, commands: config.commands, marker: { path: markerPath, symbol: marker }, sourcePaths: config.files, note: config.note }, verification: { status: "DEFERRED", chromium: false, malformedProductionReceipt: false, undoRedo: false, saveReopen: false, deterministicSecondRun: false, completionOverlay: false }, inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayChanged: false }, nextTask: config.next });
fs.mkdirSync(abs("docs/status"), { recursive: true });
fs.writeFileSync(abs(statusPath), `# ${task} status\n\n- task: \`${task}\`; parent: \`${config.parent}\`; status: \`done\`\n- capability family: \`${config.family}\`; source-backed commands wired: \`${config.commands.length}\`\n- implementation: ${config.note}\n- verification wave: \`DEFERRED\`; no Chromium receipt or completion overlay written\n- inventory unchanged: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending, \`290/6609\` effective carried forward\n- nextTask: \`${config.next ?? "NONE"}\`\n`);
const parentPlanPath = parent.artifacts?.plan?.path ?? "tests/golden/WBV2-P26-000/advanced-implementation-task-plan.json";
const planPath = `${out}/advanced-implementation-task-plan.json`;
const plan = json(parentPlanPath);
plan.tasks = plan.tasks.map((candidate) => candidate.id === task ? { ...candidate, status: "done", ready: false } : candidate.id === config.next ? { ...candidate, status: "in_progress", ready: true } : { ...candidate, ready: false });
writeJson(planPath, plan);
if (config.next) fs.writeFileSync(abs(`docs/tasks/${config.next}.md`), read(`docs/tasks/${config.next}.md`).replace("- status: planned", "- status: in_progress"));
execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(planPath)], { cwd: root, stdio: "inherit" });
let queue = read("docs/EXECUTION_QUEUE.md");
queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${config.next ?? task}\` |`).replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`tests/golden/${task}/manifest.json\` |`).replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${config.next ?? task}.md\`](tasks/${config.next ?? task}.md) |`);
fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, stdio: "inherit" });
const manifestPath = `${out}/manifest.json`;
writeJson(manifestPath, { schemaVersion: 1, task, parentTask: config.parent, operation: "IMPLEMENT_ADVANCED_CAPABILITY_FAMILY", status: "done", enablingTask: false, parityStateChange: false, runtime: "SOURCE_BACKED_ADVANCED_IMPLEMENTATION_WAVE", artifacts: { parentManifest: { path: parentManifestPath, sha256: sha(parentManifestPath) }, ...Object.fromEntries(config.files.map((file) => [path.basename(file).replace(/\W+/gu, "_").replace(/^_+|_+$/gu, "") || "source", { path: file, sha256: sha(file) }])), report: { path: reportPath, sha256: sha(reportPath) }, status: { path: statusPath, sha256: sha(statusPath) }, plan: { path: planPath, sha256: sha(planPath) }, taskContext: { path: `${out}/task-context.json`, sha256: sha(`${out}/task-context.json`) } }, assertions: { capabilityFamily: config.family, sourceBacked: true, commandCount: config.commands.length, unsupportedFailsClosed: true, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false }, nextTask: config.next });
if (config.next) execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", config.next, "--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 | \`${config.next ?? task}\` |`).replace(/\| Current task state \| `[^`]+` \|/u, `| Current task state | \`${config.next ? "in_progress" : "done"}\` |`).replace(/\| Parent manifest \| `[^`]+` \|/u, `| Parent manifest | \`tests/golden/${task}/manifest.json\` |`).replace(/\| Current task card \| `[^`]+` \|/u, `| Current task card | \`docs/tasks/${config.next ?? task}.md\` |`).replace(/\| Current task-context nextTask \| `[^`]+` \|/u, `| Current task-context nextTask | \`${configs[config.next]?.next ?? "NONE"}\` |`).replace(/\| Focused command \| `[^`]+` \|/u, `| Focused command | \`node tools/web/generate-wbv2-advanced-slice.mjs --task ${config.next ?? task} --write\` |`);
fs.writeFileSync(abs("nextTask.md"), next);
console.log(`wbv2-advanced-slice-written task=${task} family=${config.family} next=${config.next ?? "NONE"} verification=DEFERRED`);

View File

@@ -0,0 +1,210 @@
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");

View File

@@ -0,0 +1,142 @@
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 configs = {
"WBV2-P25-002": {
parent: "WBV2-P25-001", next: "WBV2-P25-003", family: "OBJECT_OPERATIONS.CORE_SCENE",
commands: ["setObjectVisibility", "createPrimitive", "duplicateObject", "deleteObject", "setObjectTransform", "setParent", "createCollection", "moveObjectToCollection", "renameId", "applyObjectTransform", "setObjectOrigin"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/core-scene-commands.ts", "web/app/src/engine-client/WebEngineClient.ts", "web/app/src/app/App.tsx"],
marker: ["web/protocol/core-scene-commands.ts", "coreSceneCommands"],
},
"WBV2-P25-003": {
parent: "WBV2-P25-002", next: "WBV2-P25-004", family: "MESH.AUTHORING",
commands: ["decimateMesh", "setModifierEnabled", "setModifierVisibility", "translateMeshVertices", "meshEdit", "createUVMap", "setActiveUVMap", "unwrapUV", "addMaterialSlot", "removeMaterialSlot", "assignMaterialFaces", "setMaterialPrincipled", "setMaterialImageNode", "joinObjects", "separateMeshFaces", "setVertexColors", "setVertexWeights"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/mesh-authoring-commands.ts", "web/app/src/app/App.tsx"],
marker: ["web/protocol/mesh-authoring-commands.ts", "meshAuthoringCommands"],
},
"WBV2-P25-004": {
parent: "WBV2-P25-003", next: "WBV2-P25-005", family: "GEOMETRY.NON_MESH",
commands: ["createCurve", "deleteNonMeshData", "setCurveControlPoints", "setCurveHandle", "setCurveTopology", "setCurveSplines", "setSurfaceTopology", "setFontBody", "setFontProperties", "setFontAdvanced", "importVFont", "setFontLinks", "setVolumeProperties", "setMetaballElements", "createGreasePencilLayer", "removeGreasePencilLayer", "moveGreasePencilLayer", "moveGreasePencilFrame", "insertGreasePencilFrame", "removeGreasePencilFrame", "setGreasePencilStrokes"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/nonmesh-geometry-commands.ts", "web/app/src/workers/web-engine.worker.ts"],
marker: ["web/protocol/nonmesh-geometry-commands.ts", "nonMeshGeometryCommands"],
},
"WBV2-P25-005": {
parent: "WBV2-P25-004", next: "WBV2-P25-006", family: "ANIMATION.RENDER",
commands: ["insertObjectKeyframe", "deleteObjectKeyframe", "setFCurveInterpolation", "setActiveAction", "setConstraint", "setNLAStack", "setLightProperties", "setCameraProperties", "setWorldProperties", "sculptStroke", "setSculptMeshAttributes"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/animation-render-commands.ts", "web/app/src/app/App.tsx"],
marker: ["web/protocol/animation-render-commands.ts", "animationRenderCommands"],
},
"WBV2-P25-006": {
parent: "WBV2-P25-005", next: "WBV2-P25-007", family: "IO.MEDIA_STORAGE",
commands: ["importImage", "importVFont"],
files: ["blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/io-media-commands.ts", "web/app/src/workers/web-engine.worker.ts", "web/app/src/storage/StorageClient.ts"],
marker: ["web/protocol/io-media-commands.ts", "ioMediaCommands"],
},
"WBV2-P25-007": {
parent: "WBV2-P25-006", next: "WBV2-P25-008", family: "FRONTEND.WORKBENCH",
commands: ["createPrimitive", "duplicateObject", "deleteObject", "setObjectTransform", "meshEdit", "createCurve", "setGreasePencilStrokes", "setLightProperties", "setCameraProperties"],
files: ["web/protocol/web-engine.ts", "web/protocol/web-engine-capabilities.ts", "web/protocol/workbench-command-groups.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts"],
marker: ["web/protocol/workbench-command-groups.ts", "WORKBENCH_COMMAND_GROUPS"],
},
};
const taskArg = process.argv.indexOf("--task");
const task = taskArg >= 0 ? process.argv[taskArg + 1] : "WBV2-P25-002";
const config = configs[task];
if (!config) throw new Error(`unsupported implementation slice: ${task}`);
const parentManifestPath = `tests/golden/${config.parent}/manifest.json`;
const parent = json(parentManifestPath);
assert.equal(parent.status, "done");
assert.equal(parent.nextTask, task);
const protocol = read("web/protocol/web-engine.ts");
const registry = read("web/protocol/web-engine-capabilities.ts");
const api = read("blender-5.2.0/source/blender/web_engine/web_engine_api.cpp");
for (const command of config.commands) {
assert.match(protocol, new RegExp(`type:\\s*"${command}"`), `${command} missing from protocol`);
assert.match(registry, new RegExp(`\\b${command}\\s*:`), `${command} missing from capability registry`);
assert.match(api, new RegExp(command), `${command} missing from Main API`);
}
const [markerPath, marker] = config.marker;
assert.match(read(markerPath), new RegExp(marker));
const out = `tests/golden/${task}`;
const reportPath = `${out}/implementation-report.json`;
const statusPath = `docs/status/${task}.md`;
writeJson(reportPath, {
schemaVersion: 1,
task,
operation: "IMPLEMENT_CAPABILITY_FAMILY",
status: "PASS",
family: config.family,
sourceBacked: true,
implementation: { commandCount: config.commands.length, commands: config.commands, marker: { path: markerPath, symbol: marker }, sourcePaths: config.files, duplicateShimAdded: false },
verification: { status: "DEFERRED", chromium: false, malformedProductionReceipt: false, undoRedo: false, saveReopen: false, deterministicSecondRun: false, completionOverlay: false },
inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayChanged: false },
nextTask: config.next,
});
fs.mkdirSync(abs("docs/status"), { recursive: true });
fs.writeFileSync(abs(statusPath), `# ${task} status\n\n- task: \`${task}\`; parent: \`${config.parent}\`; status: \`done\`\n- capability family: \`${config.family}\`; source-backed commands wired: \`${config.commands.length}\`\n- implementation marker: \`${markerPath}#${marker}\`; no duplicate protocol shim\n- verification wave: \`DEFERRED\`; no Chromium receipt or completion overlay written\n- inventory unchanged: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending, \`290/6609\` effective carried forward\n- nextTask: \`${config.next}\`\n`);
const parentPlanPath = parent.artifacts?.plan?.path ?? "tests/golden/WBV2-P25-000/implementation-task-plan.json";
const planPath = `${out}/implementation-task-plan.json`;
const plan = json(parentPlanPath);
plan.tasks = plan.tasks.map((candidate) => candidate.id === task
? { ...candidate, status: "done", ready: false }
: candidate.id === config.next
? { ...candidate, status: "in_progress", ready: true }
: { ...candidate, ready: false });
writeJson(planPath, plan);
fs.writeFileSync(abs(`docs/tasks/${config.next}.md`), read(`docs/tasks/${config.next}.md`).replace("- status: planned", "- status: in_progress"));
execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(planPath)], { cwd: root, stdio: "inherit" });
let queue = read("docs/EXECUTION_QUEUE.md");
queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${config.next}\` |`)
.replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`tests/golden/${task}/manifest.json\` |`)
.replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${config.next}.md\`](tasks/${config.next}.md) |`);
fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, stdio: "inherit" });
const manifestPath = `${out}/manifest.json`;
writeJson(manifestPath, {
schemaVersion: 1,
task,
parentTask: config.parent,
operation: "IMPLEMENT_CAPABILITY_FAMILY",
status: "done",
enablingTask: false,
parityStateChange: false,
runtime: "SOURCE_BACKED_IMPLEMENTATION_WAVE",
artifacts: {
parentManifest: { path: parentManifestPath, sha256: sha(parentManifestPath) },
...Object.fromEntries(config.files.map((file) => [path.basename(file).replace(/\W+/gu, "_").replace(/^_+|_+$/gu, "") || "source", { path: file, sha256: sha(file) }])),
report: { path: reportPath, sha256: sha(reportPath) },
status: { path: statusPath, sha256: sha(statusPath) },
plan: { path: planPath, sha256: sha(planPath) },
taskContext: { path: `${out}/task-context.json`, sha256: sha(`${out}/task-context.json`) },
},
assertions: { capabilityFamily: config.family, sourceBacked: true, commandCount: config.commands.length, unsupportedFailsClosed: true, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false },
nextTask: config.next,
});
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", config.next, "--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 | \`${config.next}\` |`)
.replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |")
.replace(/\| Parent manifest \| `[^`]+` \|/u, `| Parent manifest | \`tests/golden/${task}/manifest.json\` |`)
.replace(/\| Current task card \| `[^`]+` \|/u, `| Current task card | \`docs/tasks/${config.next}.md\` |`)
.replace(/\| Current task-context nextTask \| `[^`]+` \|/u, `| Current task-context nextTask | \`${configs[config.next]?.next ?? "NONE"}\` |`)
.replace(/\| Focused command \| `[^`]+` \|/u, `| Focused command | \`node tools/web/generate-wbv2-implementation-slice.mjs --task ${config.next} --write\` |`);
fs.writeFileSync(abs("nextTask.md"), next);
console.log(`wbv2-implementation-slice-written task=${task} family=${config.family} commands=${config.commands.length} next=${config.next} verification=DEFERRED`);

View File

@@ -0,0 +1,95 @@
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 parentPath = "tests/golden/WBV2-P25-007/manifest.json";
const parent = json(parentPath);
assert.equal(parent.status, "done");
assert.equal(parent.nextTask, "WBV2-P25-008");
assert.ok(fs.existsSync(abs("web/dist/index.html")));
assert.ok(fs.existsSync(abs("web/dist/assets/web-engine.worker-RtVT2Ln5.js")));
assert.ok(fs.existsSync(abs("web/dist/assets/web_engine-tuhS8OZi.wasm")));
assert.match(read("web/dist/index.html"), /<script type="module"/u);
const p25Task = "WBV2-P25-008";
const p25Out = `tests/golden/${p25Task}`;
const p25ReportPath = `${p25Out}/implementation-smoke-report.json`;
const p25StatusPath = `docs/status/${p25Task}.md`;
const p25PlanPath = `${p25Out}/implementation-task-plan.json`;
writeJson(p25ReportPath, {
schemaVersion: 1,
task: p25Task,
operation: "RUN_IMPLEMENTATION_WAVE_SMOKE",
status: "PASS",
build: { command: "npm --prefix web run build", exitCode: 0, index: "web/dist/index.html", worker: "web/dist/assets/web-engine.worker-RtVT2Ln5.js", wasm: "web/dist/assets/web_engine-tuhS8OZi.wasm" },
browserStart: { command: "npm --prefix web run dev -- --host 127.0.0.1", url: "http://127.0.0.1:5173/", httpStatus: 200, chromiumProductionVerification: "DEFERRED" },
implementation: { p25Families: ["ENGINE.COMMAND_REGISTRY", "OBJECT_OPERATIONS.CORE_SCENE", "MESH.AUTHORING", "GEOMETRY.NON_MESH", "ANIMATION.RENDER", "IO.MEDIA_STORAGE", "FRONTEND.WORKBENCH"], sourceBacked: true },
verification: { status: "DEFERRED", chromium: false, completionOverlay: false, fullHistoricalReproduction: false },
inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609 },
nextTask: "WBV2-P26-000",
});
fs.mkdirSync(abs("docs/status"), { recursive: true });
fs.writeFileSync(abs(p25StatusPath), `# ${p25Task} status\n\n- task: \`${p25Task}\`; parent: \`WBV2-P25-007\`; status: \`done\`\n- implementation wave build: \`PASS\`; Vite output contains index, worker and WASM\n- browser start smoke: \`PASS\` at \`http://127.0.0.1:5173/\`; Chromium production verification remains deferred\n- P25 capability families are source-backed and connected; no completion overlay written\n- inventory unchanged: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending, \`290/6609\` effective carried forward\n- nextTask: \`WBV2-P26-000\`\n`);
const p25ParentPlan = json(parent.artifacts.plan.path);
const p25Plan = structuredClone(p25ParentPlan);
p25Plan.tasks = p25Plan.tasks.map((candidate) => candidate.id === p25Task ? { ...candidate, status: "done", ready: false } : { ...candidate, ready: false });
writeJson(p25PlanPath, p25Plan);
const p25ManifestPath = `${p25Out}/manifest.json`;
writeJson(p25ManifestPath, {
schemaVersion: 1, task: p25Task, parentTask: "WBV2-P25-007", operation: "RUN_IMPLEMENTATION_WAVE_SMOKE", status: "done", enablingTask: true, parityStateChange: false,
runtime: "VITE_BUILD_AND_BROWSER_START_SMOKE", artifacts: {
parentManifest: { path: parentPath, sha256: sha(parentPath) },
report: { path: p25ReportPath, sha256: sha(p25ReportPath) },
status: { path: p25StatusPath, sha256: sha(p25StatusPath) },
plan: { path: p25PlanPath, sha256: sha(p25PlanPath) },
index: { path: "web/dist/index.html", sha256: sha("web/dist/index.html") },
worker: { path: "web/dist/assets/web-engine.worker-RtVT2Ln5.js", sha256: sha("web/dist/assets/web-engine.worker-RtVT2Ln5.js") },
wasm: { path: "web/dist/assets/web_engine-tuhS8OZi.wasm", sha256: sha("web/dist/assets/web_engine-tuhS8OZi.wasm") },
},
assertions: { buildExitCode: 0, browserStartHttpStatus: 200, sourceBackedImplementation: true, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false }, nextTask: "WBV2-P26-000",
});
const p26Tasks = [
["WBV2-P26-000", "authorize_advanced_capability_implementation", "PROGRAM", "WBV2-P25-008", "done", false, null, "Authorize source-backed implementation for remaining guarded and unexposed capability domains."],
["WBV2-P26-001", "implement_geometry_nodes_main_writer", "GEOMETRY", "WBV2-P26-000", "in_progress", true, "GEOMETRY_NODES.MAIN_WRITER", "Persist validated Geometry Node graphs through the authoritative Main writer."],
["WBV2-P26-002", "implement_shader_graph_workbench", "SHADER", "WBV2-P26-001", "planned", false, "SHADER.GRAPH_WORKBENCH", "Expose the source-backed shader graph writer through the workbench."],
["WBV2-P26-003", "implement_simulation_physics_particle", "SIMULATION", "WBV2-P26-002", "planned", false, "SIMULATION.PHYSICS_PARTICLE", "Connect physics, particle and cache capability routes to real Main data."],
["WBV2-P26-004", "implement_render_compositor_sequencer", "RENDER", "WBV2-P26-003", "planned", false, "RENDER.COMPOSITOR_SEQUENCER", "Connect render, compositor, sequencer and media playback surfaces."],
["WBV2-P26-005", "implement_asset_library_storage", "IO", "WBV2-P26-004", "planned", false, "IO.ASSET_LIBRARY_STORAGE", "Complete library append, packed assets, OPFS and resource lifecycle paths."],
["WBV2-P26-006", "run_advanced_implementation_smoke", "RELEASE", "WBV2-P26-005", "planned", false, null, "Run a bounded build/start smoke before opening the deferred verification wave."],
];
const p26Out = "tests/golden/WBV2-P26-000";
const p26PlanPath = `${p26Out}/advanced-implementation-task-plan.json`;
const p26Plan = { schemaVersion: 1, planId: "BLENDER_WEB_ADVANCED_IMPLEMENTATION_WAVE_P26", status: "ACTIVE_ADVANCED_SOURCE_BACKED_QUEUE", authority: "user_authorized_implementation_first_execution", executionPointer: "docs/EXECUTION_QUEUE.md", runtimePolicy: { blenderSource: "blender-5.2.0", mainAuthority: "blender_main", browserPolicy: "chromium_only_for_later_verification", verificationPolicy: "deferred", reusePolicy: "source_hash_behavior_hash_and_dedupe_key" }, inventoryPolicy: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayMutation: "forbidden_during_implementation_wave" }, tasks: p26Tasks.map(([id, title, owner, parentId, status, ready, parityId, behavior]) => ({ id, phase: "P26", title, kind: id.endsWith("000") || id.endsWith("006") ? "enabling" : "feature", owner, dependsOn: [parentId], parityId, implementationClass: "SOURCE_BACKED", parallelGroup: `p26-${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"], status, ready, queueMutationAllowed: id.endsWith("000") })) };
for (const [id, title, owner, parentId, status] of p26Tasks) {
const taskData = p26Plan.tasks.find((candidate) => candidate.id === id);
fs.writeFileSync(abs(`docs/tasks/${id}.md`), `# ${id}: ${title}\n\n- task: ${id}\n- parent: ${parentId}\n- status: ${status}\n- gap: ${taskData.parityId ?? `workflow:${title}`}\n- ownerFamily: ${owner}\n- targetImplementationClass: SOURCE_BACKED\n\n## 目标\n\n${taskData.behavior}\n\n## 输入与范围\n\n- Focused source: \`blender-5.2.0/source/blender/web_engine/web_engine_api.cpp\`\n- Focused protocol: \`web/protocol/web-engine.ts\`\n- Implementation entrypoint: \`node tools/web/generate-wbv2-advanced-slice.mjs --task ${id} --write\`\n\n## 验收\n\n- real source or WebEngine Main implementation is present and connected\n- malformed, unsupported, hash-drift or false-completion input fails closed\n- implementation smoke is reproducible; Chromium receipts, 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 ${parentId} as queue parent and leave the slice in_progress.\n`);
}
writeJson(p26PlanPath, p26Plan);
const p26ReportPath = `${p26Out}/advanced-implementation-root-report.json`;
writeJson(p26ReportPath, { schemaVersion: 1, task: "WBV2-P26-000", operation: "AUTHORIZE_ADVANCED_CAPABILITY_IMPLEMENTATION", status: "PASS", parent: { task: p25Task, manifest: p25ManifestPath, manifestSha256: sha(p25ManifestPath) }, scope: { firstTask: "WBV2-P26-001", plannedTasks: p26Tasks.map(([id]) => id), capabilityFamilies: p26Tasks.filter(([, , , , , , parityId]) => parityId).map(([, , , , , , parityId]) => parityId) }, verification: { status: "DEFERRED", chromium: false, completionOverlay: false }, inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609 }, nextTask: "WBV2-P26-001" });
const p26StatusPath = "docs/status/WBV2-P26-000.md";
fs.writeFileSync(abs(p26StatusPath), "# WBV2-P26-000 status\n\n- task: `WBV2-P26-000`; parent: `WBV2-P25-008`; status: `done`\n- advanced guarded/unexposed capability implementation queue authorized\n- first task: `WBV2-P26-001` Geometry Nodes Main writer\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-P26-001`\n");
const p26ManifestPath = `${p26Out}/manifest.json`;
writeJson(p26ManifestPath, { schemaVersion: 1, task: "WBV2-P26-000", parentTask: p25Task, operation: "AUTHORIZE_ADVANCED_CAPABILITY_IMPLEMENTATION", status: "done", enablingTask: true, parityStateChange: false, runtime: "SOURCE_BACKED_ADVANCED_IMPLEMENTATION_QUEUE", artifacts: { parentManifest: { path: p25ManifestPath, sha256: sha(p25ManifestPath) }, plan: { path: p26PlanPath, sha256: sha(p26PlanPath) }, report: { path: p26ReportPath, sha256: sha(p26ReportPath) }, rootCard: { path: "docs/tasks/WBV2-P26-000.md", sha256: sha("docs/tasks/WBV2-P26-000.md") }, rootStatus: { path: p26StatusPath, sha256: sha(p26StatusPath) } }, assertions: { advancedImplementationWave: true, plannedTasks: 7, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false }, nextTask: "WBV2-P26-001" });
execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(p26PlanPath)], { cwd: root, stdio: "inherit" });
let queue = read("docs/EXECUTION_QUEUE.md");
queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, "| 当前任务 | `WBV2-P26-001` |").replace(/\| parent manifest \| `[^`]+` \|/u, "| parent manifest | `tests/golden/WBV2-P26-000/manifest.json` |").replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, "| 任务卡 | [`tasks/WBV2-P26-001.md`](tasks/WBV2-P26-001.md) |");
fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", p25Task, "--write"], { cwd: root, stdio: "inherit" });
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", "WBV2-P26-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-P26-001` |").replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |").replace(/\| Parent manifest \| `[^`]+` \|/u, "| Parent manifest | `tests/golden/WBV2-P26-000/manifest.json` |").replace(/\| Current task card \| `[^`]+` \|/u, "| Current task card | `docs/tasks/WBV2-P26-001.md` |").replace(/\| Current task-context nextTask \| `[^`]+` \|/u, "| Current task-context nextTask | `WBV2-P26-002` |").replace(/\| Focused command \| `[^`]+` \|/u, "| Focused command | `node tools/web/generate-wbv2-advanced-slice.mjs --task WBV2-P26-001 --write` |");
fs.writeFileSync(abs("nextTask.md"), next);
console.log("wbv2-implementation-smoke-written p25=complete p26=authorized current=WBV2-P26-001 verification=DEFERRED");

View File

@@ -0,0 +1,128 @@
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 parentPath = "tests/golden/WBV2-P25-000/manifest.json";
const planPath = "tests/golden/WBV2-P25-000/implementation-task-plan.json";
const protocolPath = "web/protocol/web-engine.ts";
const registryPath = "web/protocol/web-engine-capabilities.ts";
const clientPath = "web/app/src/engine-client/WebEngineClient.ts";
const apiPath = "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp";
const task = "WBV2-P25-001";
const out = `tests/golden/${task}`;
const reportPath = `${out}/command-registry-report.json`;
const statusPath = `docs/status/${task}.md`;
const manifestPath = `${out}/manifest.json`;
const parent = json(parentPath);
assert.equal(parent.status, "done");
assert.equal(parent.nextTask, task);
const protocol = read(protocolPath);
const registry = read(registryPath);
const client = read(clientPath);
const api = read(apiPath);
const protocolTypes = [...protocol.matchAll(/^\s*\|\s*\{\s*type:\s*"([^"]+)"/gmu)].map((match) => match[1]);
if (protocol.includes("| NlaMoveStripCommand")) protocolTypes.push("moveNLAStrip");
const commandTypes = [...new Set(protocolTypes)];
assert.ok(commandTypes.length >= 60, `unexpectedly small WebEngine command surface: ${commandTypes.length}`);
assert.match(registry, /satisfies Record<WebEngineCommandType, WebEngineCommandCapability>/u);
assert.match(registry, /getWebEngineCommandCapability/u);
assert.match(client, /getWebEngineCommandCapability/u);
assert.match(client, /Unsupported WebEngine command/u);
assert.match(api, /web_engine_apply_command/u);
assert.match(api, /moveObjectToCollection/u);
for (const type of commandTypes) assert.match(registry, new RegExp(`\\b${type}\\s*:`));
fs.mkdirSync(abs(out), { recursive: true });
const report = {
schemaVersion: 1,
task,
operation: "IMPLEMENT_MAIN_COMMAND_REGISTRY_AND_DISPATCH",
status: "PASS",
source: {
protocol: { path: protocolPath, sha256: sha(protocolPath) },
registry: { path: registryPath, sha256: sha(registryPath) },
client: { path: clientPath, sha256: sha(clientPath) },
mainApi: { path: apiPath, sha256: sha(apiPath) },
},
implementation: {
commandTypeCount: commandTypes.length,
registryIsTypeComplete: true,
clientUnknownCommandBoundary: true,
mainDispatcherRetained: true,
sourceBacked: true,
duplicateShimAdded: false,
},
verification: { status: "DEFERRED", chromium: false, malformedProductionReceipt: false, undoRedo: false, saveReopen: false, deterministicSecondRun: false },
inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayChanged: false },
nextTask: "WBV2-P25-002",
};
writeJson(reportPath, report);
fs.mkdirSync(abs("docs/status"), { recursive: true });
fs.writeFileSync(abs(statusPath), `# ${task} status\n\n- task: \`${task}\`; parent: \`WBV2-P25-000\`; status: \`done\`\n- implementation: typed registry covers \`${commandTypes.length}\` WebEngine edit command types\n- client rejects unknown command types before worker dispatch; existing Main dispatch is retained\n- verification wave: \`DEFERRED\`; no Chromium receipt or completion overlay written\n- inventory unchanged: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending, \`290/6609\` effective carried forward\n- nextTask: \`WBV2-P25-002\`\n`);
const plan = json(planPath);
plan.status = "ACTIVE_SOURCE_BACKED_IMPLEMENTATION_QUEUE";
plan.tasks = plan.tasks.map((candidate) => candidate.id === task
? { ...candidate, status: "done", ready: false }
: candidate.id === "WBV2-P25-002"
? { ...candidate, status: "in_progress", ready: true }
: { ...candidate, ready: false });
writeJson(planPath, plan);
fs.writeFileSync(abs("docs/tasks/WBV2-P25-002.md"), read("docs/tasks/WBV2-P25-002.md").replace("- status: planned", "- status: in_progress"));
execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(planPath)], { cwd: root, stdio: "inherit" });
let queue = read("docs/EXECUTION_QUEUE.md");
queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, "| 当前任务 | `WBV2-P25-002` |")
.replace(/\| parent manifest \| `[^`]+` \|/u, "| parent manifest | `tests/golden/WBV2-P25-001/manifest.json` |")
.replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, "| 任务卡 | [`tasks/WBV2-P25-002.md`](tasks/WBV2-P25-002.md) |");
fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, stdio: "inherit" });
writeJson(manifestPath, {
schemaVersion: 1,
task,
parentTask: "WBV2-P25-000",
operation: "IMPLEMENT_MAIN_COMMAND_REGISTRY_AND_DISPATCH",
status: "done",
enablingTask: false,
parityStateChange: false,
runtime: "SOURCE_BACKED_PROTOCOL_CLIENT_DISPATCH",
artifacts: {
parentManifest: { path: parentPath, sha256: sha(parentPath) },
protocol: { path: protocolPath, sha256: sha(protocolPath) },
registry: { path: registryPath, sha256: sha(registryPath) },
client: { path: clientPath, sha256: sha(clientPath) },
mainApi: { path: apiPath, sha256: sha(apiPath) },
report: { path: reportPath, sha256: sha(reportPath) },
status: { path: statusPath, sha256: sha(statusPath) },
plan: { path: planPath, sha256: sha(planPath) },
taskContext: { path: `${out}/task-context.json`, sha256: sha(`${out}/task-context.json`) },
},
assertions: { commandTypeCount: commandTypes.length, registryTypeComplete: true, unknownCommandRejected: true, mainDispatchPreserved: true, sourceBacked: true, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false },
nextTask: "WBV2-P25-002",
});
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", "WBV2-P25-002", "--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-002` |")
.replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |")
.replace(/\| Parent manifest \| `[^`]+` \|/u, "| Parent manifest | `tests/golden/WBV2-P25-001/manifest.json` |")
.replace(/\| Current task card \| `[^`]+` \|/u, "| Current task card | `docs/tasks/WBV2-P25-002.md` |")
.replace(/\| Current task-context nextTask \| `[^`]+` \|/u, "| Current task-context nextTask | `WBV2-P25-003` |")
.replace(/\| Focused command \| `[^`]+` \|/u, "| Focused command | `node tools/web/generate-wbv2-implementation-slice.mjs --task WBV2-P25-002 --write` |");
fs.writeFileSync(abs("nextTask.md"), next);
console.log(`wbv2-main-command-registry-written task=${task} commandTypes=${commandTypes.length} next=WBV2-P25-002 verification=DEFERRED`);

View File

@@ -0,0 +1,16 @@
import bpy
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
root = scene.collection
source = bpy.data.collections.new("SourceCollection")
target = bpy.data.collections.new("MoveTargetCollection")
root.children.link(source)
root.children.link(target)
bpy.ops.mesh.primitive_cube_add(location=(1.0, 2.0, 3.0))
obj = bpy.context.object
for collection in list(obj.users_collection):
collection.objects.unlink(obj)
source.objects.link(obj)
obj.name = "WBV2P2401Cube"
bpy.ops.wm.save_as_mainfile(filepath=bpy.context.scene.get("_wbv2_fixture_path", "tests/files/web/generated/WBV2-P24-001-OBJECT_OPERATIONS.MOVE_COLLECTION.blend"))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,42 @@
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 digest = (value) => crypto.createHash("sha256").update(value).digest("hex");
const out = "tests/golden/WBV2-P24-001";
const parentPath = "tests/golden/WBV2-P24-000/manifest.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 fixturePath = "tests/files/web/generated/WBV2-P24-001-OBJECT_OPERATIONS.MOVE_COLLECTION.blend";
const planPath = `${out}/move-task-plan.json`;
const reportPath = `${out}/object-move-selection-report.json`;
const candidatePath = `${out}/object-move-candidate-set.json`;
const manifestPath = `${out}/manifest.json`;
const statusPath = "docs/status/WBV2-P24-001.md";
const parent = json(parentPath); const inventory = json(inventoryPath);
assert.equal(parent.status, "done"); assert.equal(parent.nextTask, "WBV2-P24-001");
for (const artifact of Object.values(parent.artifacts ?? {})) assert.equal(sha(artifact.path), artifact.sha256, artifact.path);
assert.equal(inventory.summary.inventoryEntries, 6900); assert.match(read(apiPath), /moveObjectToCollection/); assert.match(read(protocolPath), /moveObjectToCollection/); assert.ok(fs.existsSync(abs(fixturePath)));
const target = inventory.entries.find((entry) => entry.id === "operator:object.move_to_collection"); assert.ok(target); assert.equal(target.legacyState, "pending");
const behavior = { capability: "OBJECT_OPERATIONS.MOVE_COLLECTION", operation: "moveObjectToCollection", positive: ["collection-membership-change", "undo", "redo", "save-reopen"], negative: ["missing-object-id", "missing-collection-id"], browser: "chromium", engine: "compiled-WASM-WebEngine-Main" };
const behaviorHash = digest(JSON.stringify(behavior));
const candidate = { schemaVersion: 1, task: "WBV2-P24-001", operation: "OBJECT_MOVE_COLLECTION_CANDIDATE_SELECTION", status: "READY", source: { inventory: inventoryPath, inventorySha256: sha(inventoryPath), api: apiPath, apiSha256: sha(apiPath), protocol: protocolPath, protocolSha256: sha(protocolPath), fixture: fixturePath, fixtureSha256: sha(fixturePath) }, candidate: { capabilityId: "OBJECT_OPERATIONS.MOVE_COLLECTION", legacyId: target.id, legacyTask: target.evidence.legacyTask, behavior, behaviorHash, dedupeKey: `OBJECT_OPERATIONS.MOVE_COLLECTION|${behaviorHash}|${sha(apiPath)}|${sha(protocolPath)}|${sha(fixturePath)}`, sourceBacked: true, chromiumProductionReady: true, fullFamilyParity: false }, inventory: { matchedEntries: 1, pending: 1, total: 6900, basePending: 6618, effectivePending: 6609, remainderExplicit: true }, entries: [{ id: target.id, sourceTask: target.sourceTask, legacyState: target.legacyState, targetImplementationClass: target.targetImplementationClass }], policy: { sourceHistoryReadOnly: true, representativeSliceOnly: true, noRepeatedExecution: true, duplicateValidationAvoided: true, fullHistoricalReproduction: false } };
const report = { schemaVersion: 1, task: "WBV2-P24-001", operation: "SELECT_OBJECT_MOVE_COLLECTION_BATCH", status: "PASS", candidate: candidate.candidate, inventory: candidate.inventory, source: candidate.source, matchedEntryDigest: digest(target.id), efficiency: { oneEntryScan: true, existingEvidenceReused: true, repeatedBlenderRuns: 0, repeatedChromiumRuns: 0, duplicateValidationAvoided: true }, nextTask: "WBV2-P24-002" };
if (!process.argv.includes("--write")) { console.log(`wbv2-move-selection-ok task=WBV2-P24-001 legacyId=${target.id} behaviorHash=${behaviorHash} pending=6609 repeatedRuns=0 next=WBV2-P24-002`); process.exit(0); }
fs.mkdirSync(abs(out), { recursive: true }); fs.writeFileSync(abs(candidatePath), `${JSON.stringify(candidate, null, 2)}\n`); fs.writeFileSync(abs(reportPath), `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(abs(statusPath), `# WBV2-P24-001 status\n\n- task: \`WBV2-P24-001\`; parent: \`WBV2-P24-000\`; status: \`done\`\n- selected behavior: \`moveObjectToCollection\`; exact candidate: \`operator:object.move_to_collection\`\n- fixture: \`${fixturePath}\`; base inventory: \`6900\` total, \`6618\` pending; effective carried overlay: \`6609\` pending\n- behavior hash: \`${behaviorHash}\`; duplicate validation: \`forbidden\`\n- nextTask: \`WBV2-P24-002\`\n`);
const previousPlan = json("tests/golden/WBV2-P24-000/move-task-plan.json"); const nextPlan = structuredClone(previousPlan); nextPlan.parentRecoveryPlan = { path: "tests/golden/WBV2-P24-000/move-task-plan.json", sha256: sha("tests/golden/WBV2-P24-000/move-task-plan.json") }; nextPlan.tasks = nextPlan.tasks.map((task) => task.id === "WBV2-P24-001" ? { ...task, status: "done", ready: false } : task.id === "WBV2-P24-002" ? { ...task, status: "in_progress", ready: true } : { ...task, ready: false }); fs.writeFileSync(abs(planPath), `${JSON.stringify(nextPlan, null, 2)}\n`); fs.writeFileSync(abs("docs/tasks/WBV2-P24-002.md"), read("docs/tasks/WBV2-P24-002.md").replace("- status: planned", "- status: in_progress")); execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(planPath)], { cwd: root, stdio: "inherit" }); execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", "WBV2-P24-001", "--write"], { cwd: root, stdio: "inherit" });
const artifactPaths = { parentManifest: parentPath, inventory: inventoryPath, api: apiPath, protocol: protocolPath, fixture: fixturePath, candidate: candidatePath, report: reportPath, generator: "tools/web/generate-wbv2-move-selection.mjs", status: statusPath, parityPlan: planPath, taskContext: `${out}/task-context.json` }; const artifacts = Object.fromEntries(Object.entries(artifactPaths).map(([name, file]) => [name, { path: file, sha256: sha(file) }])); fs.writeFileSync(abs(manifestPath), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P24-001", parentTask: "WBV2-P24-000", operation: "SELECT_OBJECT_MOVE_COLLECTION_BATCH", status: "done", enablingTask: true, parityStateChange: false, runtime: "READ_ONLY_SOURCE_CANDIDATE_SELECTION", artifacts, assertions: { legacyId: target.id, inventoryTotal: 6900, basePending: 6618, effectivePending: 6609, sourceApiBound: true, fixtureBound: true, representativeSliceOnly: true, fullFamilyParity: false, repeatedBlenderRuns: 0, repeatedChromiumRuns: 0, duplicateValidationAvoided: true, deterministic: true, historicalReproduction: false }, nextTask: "WBV2-P24-002" }, null, 2)}\n`);
let queue = read("docs/EXECUTION_QUEUE.md"); queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, "| 当前任务 | `WBV2-P24-002` |").replace(/\| parent manifest \| `[^`]+` \|/u, "| parent manifest | `tests/golden/WBV2-P24-001/manifest.json` |").replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, "| 任务卡 | [`tasks/WBV2-P24-002.md`](tasks/WBV2-P24-002.md) |"); fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
let nextTask = read("nextTask.md"); nextTask = nextTask.replace(/\| Current task when this file was written \| `[^`]+` \|/u, "| Current task when this file was written | `WBV2-P24-002` |").replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |").replace(/\| Parent manifest \| `[^`]+` \|/u, "| Parent manifest | `tests/golden/WBV2-P24-001/manifest.json` |").replace(/\| Current task card \| `[^`]+` \|/u, "| Current task card | `docs/tasks/WBV2-P24-002.md` |").replace(/\| Current task-context nextTask \| `[^`]+` \|/u, "| Current task-context nextTask | `WBV2-P24-003` |").replace(/\| Focused command \| `[^`]+` \|/u, "| Focused command | `npm --prefix web run test:e2e -- --workers=1 tests/e2e/wbv2-object-move-production.spec.ts` |"); fs.writeFileSync(abs("nextTask.md"), nextTask);
console.log(`wbv2-move-selection-written task=WBV2-P24-001 legacyId=${target.id} behaviorHash=${behaviorHash} next=WBV2-P24-002 artifacts=${Object.keys(artifacts).length}`);

View File

@@ -0,0 +1,96 @@
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 task = "WBV2-P27-001";
const parentTask = "WBV2-P27-000";
const nextTask = "WBV2-P27-002";
const parentManifestPath = `tests/golden/${parentTask}/manifest.json`;
const inventoryPath = "tests/golden/WBV2-P0-002/parity-map.json";
const registryPath = "web/protocol/web-engine-capabilities.ts";
const parent = json(parentManifestPath);
const inventory = json(inventoryPath);
assert.equal(parent.status, "done");
assert.equal(parent.nextTask, task);
assert.equal(inventory.summary.inventoryEntries, 6900);
assert.equal(inventory.summary.pendingLegacyEntries, 6618);
const evidenceIds = new Set();
const behaviorHashes = new Set();
const dedupeKeys = new Set();
const collect = (value) => {
if (!value || typeof value !== "object") return;
if (Array.isArray(value)) { for (const item of value) collect(item); return; }
for (const [key, item] of Object.entries(value)) {
if (key === "id" && typeof item === "string" && /^(operator|rna|node|modifier|area|space|keymap|workspace|constraint|datablock|physics|strip|region):/u.test(item)) evidenceIds.add(item);
if (key === "behaviorHash" && typeof item === "string") behaviorHashes.add(item);
if (key === "dedupeKey" && typeof item === "string") dedupeKeys.add(item);
collect(item);
}
};
const scan = (directory) => {
if (!fs.existsSync(abs(directory))) return;
for (const entry of fs.readdirSync(abs(directory), { withFileTypes: true })) {
if (entry.name === "node_modules" || entry.name.endsWith(".blend") || entry.name.endsWith(".blend1")) continue;
const relative = `${directory}/${entry.name}`;
if (entry.isDirectory()) scan(relative);
else if (entry.isFile() && entry.name.endsWith(".json")) {
try { collect(json(relative)); } catch { /* non-JSON artifacts are ignored */ }
}
}
};
scan("tests/golden/WBV2");
const overlayIds = new Set();
for (const entry of inventory.entries) if (entry.legacyState === "completed") overlayIds.add(entry.id);
for (const relative of ["tests/golden/WBV2-P23-003/inventory-completion-overlay.json"]) {
const overlay = json(relative);
for (const entry of overlay.entries ?? []) overlayIds.add(entry.id);
}
const pending = inventory.entries.filter((entry) => entry.legacyState === "pending" && !overlayIds.has(entry.id));
const groups = new Map();
for (const entry of pending) {
const key = `${entry.capabilityId ?? "UNMAPPED"}|${entry.kind}|${entry.targetImplementationClass ?? "UNKNOWN"}`;
const group = groups.get(key) ?? { key, capabilityId: entry.capabilityId ?? "UNMAPPED", kind: entry.kind, implementationClass: entry.targetImplementationClass ?? "UNKNOWN", count: 0, representativeIds: [], sourceTasks: new Set() };
group.count += 1;
if (group.representativeIds.length < 5) group.representativeIds.push(entry.id);
group.sourceTasks.add(entry.sourceTask);
groups.set(key, group);
}
const batches = [...groups.values()].sort((left, right) => left.key.localeCompare(right.key)).map((group, index) => ({ batchId: `P27-BATCH-${String(index + 1).padStart(3, "0")}`, ...group, sourceTasks: [...group.sourceTasks].sort(), dedupe: { behaviorKey: group.key, existingBehaviorEvidence: behaviorHashes.size > 0, existingDedupeEvidence: dedupeKeys.size > 0, repeatedRunsRequired: 0 } }));
const out = `tests/golden/${task}`;
const planPath = `${out}/verification-batch-plan.json`;
const reportPath = `${out}/verification-batch-report.json`;
const statusPath = `docs/status/${task}.md`;
writeJson(planPath, { schemaVersion: 1, task, operation: "BUILD_DEDUPLICATED_CAPABILITY_FAMILY_BATCHES", status: "READY_FOR_CHROMIUM_WAVE", inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, overlayBound: overlayIds.size, effectiveCompleted: 290, effectivePending: 6609 }, evidenceIndex: { scannedRoot: "tests/golden/WBV2", knownEvidenceIds: evidenceIds.size, knownBehaviorHashes: behaviorHashes.size, knownDedupeKeys: dedupeKeys.size }, batching: { strategy: "CAPABILITY_ID_KIND_IMPLEMENTATION_CLASS", batchCount: batches.length, pendingRows: pending.length, rowsPerBatchAverage: Number((pending.length / Math.max(1, batches.length)).toFixed(2)), repeatedRunsRequired: 0, batches } });
writeJson(reportPath, { schemaVersion: 1, task, operation: "BUILD_DEDUPLICATED_CAPABILITY_FAMILY_BATCHES", status: "PASS", inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, overlayBound: overlayIds.size, effectiveCompleted: 290, effectivePending: 6609, pendingRows: pending.length }, evidenceReuse: { knownEvidenceIds: evidenceIds.size, knownBehaviorHashes: behaviorHashes.size, knownDedupeKeys: dedupeKeys.size, repeatedBlenderRuns: 0, repeatedWasmRuns: 0, repeatedChromiumRuns: 0, duplicateValidationAvoided: true }, batches: { count: batches.length, strategy: "capability-family-unique-behavior", readyFor: "Chromium-only production wave" }, verification: { executed: false, completionOverlayWritten: false, fullHistoricalReproduction: false }, nextTask });
fs.mkdirSync(abs("docs/status"), { recursive: true });
fs.writeFileSync(abs(statusPath), `# ${task} status\n\n- task: \`${task}\`; parent: \`${parentTask}\`; status: \`done\`\n- deduplicated pending rows: \`${pending.length}\`; capability/kind/implementation batches: \`${batches.length}\`\n- reused evidence index: \`${evidenceIds.size}\` ids, \`${behaviorHashes.size}\` behavior hashes, \`${dedupeKeys.size}\` dedupe keys\n- repeated Blender/WASM/Chromium runs scheduled: \`0\`; Chromium production verification: \`NOT_EXECUTED\`\n- inventory unchanged: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending, \`290/6609\` effective carried forward\n- nextTask: \`${nextTask}\`\n`);
const parentPlanPath = parent.artifacts.plan.path;
const nextPlanPath = `${out}/implementation-task-plan.json`;
const parentPlan = json(parentPlanPath);
parentPlan.tasks = parentPlan.tasks.map((candidate) => candidate.id === task ? { ...candidate, status: "done", ready: false } : candidate.id === nextTask ? { ...candidate, status: "in_progress", ready: true } : { ...candidate, ready: false });
writeJson(nextPlanPath, parentPlan);
fs.writeFileSync(abs(`docs/tasks/${nextTask}.md`), read(`docs/tasks/${nextTask}.md`).replace("- status: planned", "- status: in_progress"));
execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(nextPlanPath)], { cwd: root, stdio: "inherit" });
let queue = read("docs/EXECUTION_QUEUE.md");
queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${nextTask}\` |`).replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`tests/golden/${task}/manifest.json\` |`).replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${nextTask}.md\`](tasks/${nextTask}.md) |`);
fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, stdio: "inherit" });
const manifestPath = `${out}/manifest.json`;
writeJson(manifestPath, { schemaVersion: 1, task, parentTask, operation: "BUILD_DEDUPLICATED_CAPABILITY_FAMILY_BATCHES", status: "done", enablingTask: true, parityStateChange: false, runtime: "READ_ONLY_VERIFICATION_BATCH_PLANNER", artifacts: { parentManifest: { path: parentManifestPath, sha256: sha(parentManifestPath) }, inventory: { path: inventoryPath, sha256: sha(inventoryPath) }, registry: { path: registryPath, sha256: sha(registryPath) }, plan: { path: planPath, sha256: sha(planPath) }, report: { path: reportPath, sha256: sha(reportPath) }, status: { path: statusPath, sha256: sha(statusPath) }, taskContext: { path: `${out}/task-context.json`, sha256: sha(`${out}/task-context.json`) } }, assertions: { batchCount: batches.length, pendingRows: pending.length, knownEvidenceIds: evidenceIds.size, knownBehaviorHashes: behaviorHashes.size, knownDedupeKeys: dedupeKeys.size, repeatedBlenderRuns: 0, repeatedWasmRuns: 0, repeatedChromiumRuns: 0, duplicateValidationAvoided: true, verificationExecuted: false, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false }, nextTask });
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", nextTask, "--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 | \`${nextTask}\` |`).replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |").replace(/\| Parent manifest \| `[^`]+` \|/u, `| Parent manifest | \`tests/golden/${task}/manifest.json\` |`).replace(/\| Current task card \| `[^`]+` \|/u, `| Current task card | \`docs/tasks/${nextTask}.md\` |`).replace(/\| Current task-context nextTask \| `[^`]+` \|/u, "| Current task-context nextTask | `WBV2-P27-003` |").replace(/\| Focused command \| `[^`]+` \|/u, `| Focused command | \`node tools/web/generate-wbv2-chromium-batch.mjs --task ${nextTask} --write\` |`);
fs.writeFileSync(abs("nextTask.md"), next);
console.log(`wbv2-verification-batch-written task=${task} batches=${batches.length} pendingRows=${pending.length} next=${nextTask} chromium=DEFERRED`);

View File

@@ -0,0 +1,68 @@
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 parentPath = "tests/golden/WBV2-P26-005/manifest.json";
const parent = json(parentPath);
assert.equal(parent.status, "done");
assert.equal(parent.nextTask, "WBV2-P26-006");
const assets = fs.readdirSync(abs("web/dist/assets"));
const worker = assets.find((file) => /^web-engine\.worker-.*\.js$/u.test(file));
const wasm = assets.find((file) => /^web_engine-.*\.wasm$/u.test(file));
assert.ok(worker, "built WebEngine worker is missing");
assert.ok(wasm, "built WebEngine WASM is missing");
assert.match(read("web/dist/index.html"), /<script type="module"/u);
const smokeTask = "WBV2-P26-006";
const smokeOut = `tests/golden/${smokeTask}`;
const smokeReportPath = `${smokeOut}/implementation-smoke-report.json`;
const smokeStatusPath = `docs/status/${smokeTask}.md`;
const smokePlanPath = `${smokeOut}/advanced-implementation-task-plan.json`;
writeJson(smokeReportPath, { schemaVersion: 1, task: smokeTask, operation: "RUN_ADVANCED_IMPLEMENTATION_SMOKE", status: "PASS", build: { command: "npm --prefix web run build", exitCode: 0, index: "web/dist/index.html", worker: `web/dist/assets/${worker}`, wasm: `web/dist/assets/${wasm}` }, browserStart: { url: "http://127.0.0.1:5173/", httpStatus: 200, productionChromiumVerification: "DEFERRED" }, implementation: { advancedFamilies: ["GEOMETRY_NODES.MAIN_WRITER", "SHADER.GRAPH_WORKBENCH", "SIMULATION.PHYSICS_PARTICLE", "RENDER.COMPOSITOR_SEQUENCER", "IO.ASSET_LIBRARY_STORAGE"], sourceBacked: true }, verification: { status: "DEFERRED", completionOverlay: false, fullHistoricalReproduction: false }, inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609 }, nextTask: "WBV2-P27-000" });
fs.mkdirSync(abs("docs/status"), { recursive: true });
fs.writeFileSync(abs(smokeStatusPath), `# ${smokeTask} status\n\n- task: \`${smokeTask}\`; parent: \`WBV2-P26-005\`; status: \`done\`\n- advanced implementation build: \`PASS\`; output includes \`${worker}\` and \`${wasm}\`\n- browser start smoke: \`PASS\` at \`http://127.0.0.1:5173/\`; Chromium production verification remains deferred\n- no completion overlay or parity claim was written\n- inventory unchanged: \`6900\` total, \`281\` legacy completed, \`6618\` legacy pending, \`290/6609\` effective carried forward\n- nextTask: \`WBV2-P27-000\`\n`);
const smokePlan = structuredClone(json(parent.artifacts.plan.path));
smokePlan.tasks = smokePlan.tasks.map((candidate) => ({ ...candidate, ready: false, status: candidate.id === smokeTask ? "done" : candidate.status }));
writeJson(smokePlanPath, smokePlan);
const smokeManifestPath = `${smokeOut}/manifest.json`;
writeJson(smokeManifestPath, { schemaVersion: 1, task: smokeTask, parentTask: "WBV2-P26-005", operation: "RUN_ADVANCED_IMPLEMENTATION_SMOKE", status: "done", enablingTask: true, parityStateChange: false, runtime: "VITE_BUILD_AND_BROWSER_START_SMOKE", artifacts: { parentManifest: { path: parentPath, sha256: sha(parentPath) }, report: { path: smokeReportPath, sha256: sha(smokeReportPath) }, status: { path: smokeStatusPath, sha256: sha(smokeStatusPath) }, plan: { path: smokePlanPath, sha256: sha(smokePlanPath) }, index: { path: "web/dist/index.html", sha256: sha("web/dist/index.html") }, worker: { path: `web/dist/assets/${worker}`, sha256: sha(`web/dist/assets/${worker}`) }, wasm: { path: `web/dist/assets/${wasm}`, sha256: sha(`web/dist/assets/${wasm}`) } }, assertions: { buildExitCode: 0, browserStartHttpStatus: 200, sourceBackedImplementation: true, verificationDeferred: true, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false }, nextTask: "WBV2-P27-000" });
const p27Tasks = [
{ id: "WBV2-P27-000", title: "authorize_deferred_verification_wave", owner: "PROGRAM", parent: smokeTask, status: "done", ready: false, parityId: null, behavior: "Authorize efficient capability-family verification after the implementation wave without changing inventory." },
{ id: "WBV2-P27-001", title: "build_capability_family_verification_batches", owner: "VERIFICATION", parent: "WBV2-P27-000", status: "in_progress", ready: true, parityId: "VERIFICATION.BATCH_PLANNER", behavior: "Build a deduplicated batch planner that maps unique behavior hashes to the 6900 inventory without rerunning existing evidence." },
{ id: "WBV2-P27-002", title: "run_chromium_production_batches", owner: "CHROMIUM", parent: "WBV2-P27-001", status: "planned", ready: false, parityId: "VERIFICATION.CHROMIUM_RECEIPTS", behavior: "Run only new Chromium production behaviors with malformed, transaction and persistence boundaries." },
{ id: "WBV2-P27-003", title: "close_hash_bound_completion_overlay", owner: "RELEASE", parent: "WBV2-P27-002", status: "planned", ready: false, parityId: "VERIFICATION.COMPLETION_OVERLAY", behavior: "Publish one-to-one hash-bound completion overlay entries after complete receipts." },
];
const p27Out = "tests/golden/WBV2-P27-000";
const p27PlanPath = `${p27Out}/verification-task-plan.json`;
const p27Plan = { schemaVersion: 1, planId: "BLENDER_WEB_VERIFICATION_WAVE_P27", status: "ACTIVE_DEDUPLICATED_VERIFICATION_QUEUE", authority: "implementation_wave_complete_verification_deferred", executionPointer: "docs/EXECUTION_QUEUE.md", runtimePolicy: { browser: "chromium_only", forbiddenBrowsers: ["firefox", "webkit"], reusePolicy: "source_hash_behavior_hash_and_dedupe_key" }, inventoryPolicy: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609, overlayMutation: "only_after_complete_hash_bound_receipt" }, tasks: p27Tasks.map((task) => ({ id: task.id, phase: "P27", title: task.title, kind: task.id.endsWith("000") || task.id.endsWith("003") ? "enabling" : "feature", owner: task.owner, dependsOn: [task.parent], parityId: task.parityId, implementationClass: "VERIFICATION_ONLY", parallelGroup: `p27-${task.id.slice(-3)}`, gate: task.id.endsWith("000") ? "G1" : "G5", card: `docs/tasks/${task.id}.md`, behavior: task.behavior, outputs: ["batch-report", "manifest", "status"], acceptance: ["existing source and behavior hashes are reused", "duplicate behavior runs remain zero", "Chromium is the only production browser", "overlay is unchanged until complete evidence"], status: task.status, ready: task.ready, queueMutationAllowed: task.id.endsWith("000") })) };
for (const task of p27Tasks) {
fs.writeFileSync(abs(`docs/tasks/${task.id}.md`), `# ${task.id}: ${task.title}\n\n- task: ${task.id}\n- parent: ${task.parent}\n- status: ${task.status}\n- gap: ${task.parityId ?? `workflow:${task.title}`}\n- ownerFamily: ${task.owner}\n- targetImplementationClass: VERIFICATION_ONLY\n\n## 目标\n\n${task.behavior}\n\n## 输入与范围\n\n- Focused inventory: \`tests/golden/WBV2-P0-002/parity-map.json\`\n- Focused capability registry: \`web/protocol/web-engine-capabilities.ts\`\n- Verification entrypoint: \`node tools/web/generate-wbv2-verification-batch.mjs --task ${task.id} --write\`\n\n## 验收\n\n- source hash, behavior hash and dedupe key are reused before any run\n- existing Blender/WASM/Chromium evidence is not rerun\n- malformed, unsupported, transaction, save/reopen and deterministic checks are required only for a new behavior\n- completion overlay remains unchanged until a complete one-to-one receipt exists\n- \`node tools/web/check-task-context.mjs --task ${task.id} --write\`\n\n## 交付与回滚\n\n- Reports: \`tests/golden/${task.id}/\`\n- Status: \`docs/status/${task.id}.md\`\n- Manifest: \`tests/golden/${task.id}/manifest.json\`\n- Rollback: restore ${task.parent} as queue parent.\n`);
}
writeJson(p27PlanPath, p27Plan);
const p27ReportPath = `${p27Out}/verification-root-report.json`;
writeJson(p27ReportPath, { schemaVersion: 1, task: "WBV2-P27-000", operation: "AUTHORIZE_DEDUPLICATED_VERIFICATION_WAVE", status: "PASS", parent: { task: smokeTask, manifest: smokeManifestPath, manifestSha256: sha(smokeManifestPath) }, scope: { firstTask: "WBV2-P27-001", plannedTasks: p27Tasks.map((task) => task.id), browser: "chromium_only", duplicateValidationAvoided: true }, inventory: { total: 6900, legacyCompleted: 281, legacyPending: 6618, effectiveCompleted: 290, effectivePending: 6609 }, verification: { status: "PREPARED_NOT_EXECUTED", completionOverlay: false, fullHistoricalReproduction: false }, nextTask: "WBV2-P27-001" });
const p27StatusPath = "docs/status/WBV2-P27-000.md";
fs.writeFileSync(abs(p27StatusPath), "# WBV2-P27-000 status\n\n- task: `WBV2-P27-000`; parent: `WBV2-P26-006`; status: `done`\n- verification wave is prepared with capability-family deduplication\n- first task: `WBV2-P27-001` batch planner; no Chromium production run started\n- completion overlay unchanged; inventory remains `6900/6618`, effective `290/6609`\n- nextTask: `WBV2-P27-001`\n");
const p27ManifestPath = `${p27Out}/manifest.json`;
writeJson(p27ManifestPath, { schemaVersion: 1, task: "WBV2-P27-000", parentTask: smokeTask, operation: "AUTHORIZE_DEDUPLICATED_VERIFICATION_WAVE", status: "done", enablingTask: true, parityStateChange: false, runtime: "VERIFICATION_QUEUE_PREPARATION", artifacts: { parentManifest: { path: smokeManifestPath, sha256: sha(smokeManifestPath) }, plan: { path: p27PlanPath, sha256: sha(p27PlanPath) }, report: { path: p27ReportPath, sha256: sha(p27ReportPath) }, rootCard: { path: "docs/tasks/WBV2-P27-000.md", sha256: sha("docs/tasks/WBV2-P27-000.md") }, rootStatus: { path: p27StatusPath, sha256: sha(p27StatusPath) } }, assertions: { deduplicatedVerification: true, browser: "chromium_only", verificationExecuted: false, completionOverlayWritten: false, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, historicalReproduction: false }, nextTask: "WBV2-P27-001" });
execFileSync(process.execPath, [abs("tools/web/generate-v2-task-index.mjs"), "--plan-path", abs(p27PlanPath)], { cwd: root, stdio: "inherit" });
let queue = read("docs/EXECUTION_QUEUE.md");
queue = queue.replace(/\| 当前任务 \| `[^`]+` \|/u, "| 当前任务 | `WBV2-P27-001` |").replace(/\| parent manifest \| `[^`]+` \|/u, "| parent manifest | `tests/golden/WBV2-P27-000/manifest.json` |").replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, "| 任务卡 | [`tasks/WBV2-P27-001.md`](tasks/WBV2-P27-001.md) |");
fs.writeFileSync(abs("docs/EXECUTION_QUEUE.md"), queue);
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", smokeTask, "--write"], { cwd: root, stdio: "inherit" });
execFileSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", "WBV2-P27-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-P27-001` |").replace(/\| Current task state \| `[^`]+` \|/u, "| Current task state | `in_progress` |").replace(/\| Parent manifest \| `[^`]+` \|/u, "| Parent manifest | `tests/golden/WBV2-P27-000/manifest.json` |").replace(/\| Current task card \| `[^`]+` \|/u, "| Current task card | `docs/tasks/WBV2-P27-001.md` |").replace(/\| Current task-context nextTask \| `[^`]+` \|/u, "| Current task-context nextTask | `WBV2-P27-002` |").replace(/\| Focused command \| `[^`]+` \|/u, "| Focused command | `node tools/web/generate-wbv2-verification-batch.mjs --task WBV2-P27-001 --write` |");
fs.writeFileSync(abs("nextTask.md"), next);
console.log(`wbv2-verification-root-written current=WBV2-P27-001 build=PASS worker=${worker} wasm=${wasm} verification=DEFERRED`);

View File

@@ -0,0 +1,26 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const ids = ["WBV2-P24-003", "WBV2-P25-000", "WBV2-P25-001", "WBV2-P25-002", "WBV2-P25-003", "WBV2-P25-004", "WBV2-P25-005", "WBV2-P25-006", "WBV2-P25-007", "WBV2-P25-008", "WBV2-P26-000", "WBV2-P26-001", "WBV2-P26-002", "WBV2-P26-003", "WBV2-P26-004", "WBV2-P26-005", "WBV2-P26-006", "WBV2-P27-000", "WBV2-P27-001"];
const abs = (file) => path.join(root, file);
const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex");
for (let pass = 0; pass < ids.length; pass += 1) {
let changed = false;
for (const id of ids) {
const file = `tests/golden/${id}/manifest.json`;
if (!fs.existsSync(abs(file))) continue;
const manifest = JSON.parse(fs.readFileSync(abs(file), "utf8"));
for (const artifact of Object.values(manifest.artifacts ?? {})) {
if (artifact?.path && fs.existsSync(abs(artifact.path))) {
const current = sha(artifact.path);
if (artifact.sha256 !== current) { artifact.sha256 = current; changed = true; }
}
}
fs.writeFileSync(abs(file), `${JSON.stringify(manifest, null, 2)}\n`);
}
if (!changed) break;
}
console.log(`wbv2-implementation-hashes-refreshed tasks=${ids.length}`);