211 lines
19 KiB
JavaScript
211 lines
19 KiB
JavaScript
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");
|