Files
workinf_Blender_Wasm/tools/web/authorize-wbv2-p1425.mjs
mes123456 d1e6b63d54
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
chore: checkpoint web Blender feature-gap work, docs and tests
- update native web_engine sources and rebuilt wasm artifacts
- add task cards, status notes and tooling scripts
- add web e2e specs, tutorial UI sources and tutorial assets
- sync golden task manifests and audit records

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-11 10:46:55 +08:00

431 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 writeText = (file, value) => { fs.mkdirSync(path.dirname(abs(file)), { recursive: true }); fs.writeFileSync(abs(file), value); };
const run = (entry, ...args) => execFileSync(process.execPath, [abs(entry), ...args], { cwd: root, stdio: "inherit" });
const sourcePlanPath = "tests/golden/WBV2-P1424-001/recovery-20261020/execution-plan.json";
const planPath = "tests/golden/WBV2-P1425-000/domain-parity-plan.json";
const parentTask = "WBV2-P1424-001";
const bridgeTask = "WBV2-P1424-900";
const rootTask = "WBV2-P1425-000";
const moduleTask = "WBV2-P1425-001";
const moduleId = "OBJECT_ADD_ROUTE";
const queuePath = "docs/EXECUTION_QUEUE.md";
const parentManifestPath = `tests/golden/${parentTask}/manifest.json`;
const bridgeManifestPath = `tests/golden/${bridgeTask}/manifest.json`;
const planDocPath = "docs/plans/CREATE_MODEL_HIERARCHY_PARITY_PLAN.md";
const preservedContinuation = "tests/golden/WBV2-P1424-001/continuation-20261103-goal-numpy.json";
// Repair the partial first run if it touched the preserved task head; the original
// task must keep its in_progress status and null nextTask byte-for-byte.
const parentManifest = read(parentManifestPath);
const polluted = ` "status": "in_progress",\n "enablingTask": true,\n "nextTask": "${rootTask}",\n "supersession": [^\n]*,\n`;
const pristine = ` "status": "in_progress",\n "nextTask": null,\n`;
const restored = parentManifest.replace(new RegExp(polluted, "u"), pristine);
assert.ok(!restored.includes(`"nextTask": "${rootTask}"`), "failed to restore the preserved P1424-001 manifest");
if (restored !== parentManifest) writeText(parentManifestPath, restored);
assert.equal(fs.statSync(abs(parentManifestPath)).size, 212423, "P1424-001 manifest size changed; refuse to continue");
const inventory = json("tests/golden/WBV2/dynamic-inventory.json");
const createIdentifiers = new Set([
"OBJECT_OT_add", "OBJECT_OT_add_named", "OBJECT_OT_camera_add", "OBJECT_OT_light_add",
"OBJECT_OT_empty_add", "OBJECT_OT_armature_add",
]);
const coverageBindings = inventory.units
.filter((unit) => {
const id = unit.identifier ?? "";
return /^MESH_OT_primitive_(circle|cone|cube|cylinder|grid|ico_sphere|monkey|plane|uv_sphere)_add$/u.test(id)
|| createIdentifiers.has(id);
})
.map((unit) => unit.id);
assert.ok(coverageBindings.length >= 14, `create coverage bindings too small: ${coverageBindings.length}`);
const moduleContract = {
moduleId,
sourceBacked: true,
capabilities: [
"CAP-OBJECT-ADD-MENU",
"CAP-MESH-PRIMITIVE-ADD",
"CAP-MESH-PRIMITIVE-PARAMETERS",
"CAP-OPERATOR-CHANNEL-NATIVE-CONTEXT",
],
sourcePaths: [
"blender-5.2.0/source/blender/web_engine/web_engine_python_bootstrap.py",
"blender-5.2.0/source/blender/web_engine/web_engine_gpu_ui.cpp",
"blender-5.2.0/source/blender/web_engine/web_engine_api.cpp",
"web/app/src/app/App.tsx",
"web/app/src/engine-client/WebEngineClient.ts",
"web/protocol/web-engine.ts",
"web/app/src/capabilities/blender-feature-route-catalog.json",
],
reuseReceipts: [
{ manifest: "tests/golden/WBV2-P18-002/manifest.json", report: "tests/golden/WBV2-P18-002/object-create-production.json" },
],
positive: [
"Add Circle/Grid/Monkey and existing mesh primitives through the native operator channel",
"Create camera/light/empty/armature through the same Add menu route",
"location/rotation/scale/align/enter_editmode are applied by the native operator",
],
negative: [
"unsupported primitive identifier fails with a structured error and no Main mutation",
"stale baseRevision and wrong mode/area fail closed without an undo entry",
],
recovery: ["undo", "redo", "save/reopen", "worker restart"],
commands: [
"source tools/web/emscripten-env.sh && cmake --build build_web_blender6 --target web_engine -- -j8",
"node tools/web/sync-web-engine-runtime.mjs",
],
coverageBindings,
fullFamilyParity: false,
};
const bridgeRecord = {
id: bridgeTask,
phase: "1424",
title: "supersede_stalled_task_for_domain_parity",
kind: "bridge",
owner: "PROGRAM",
dependsOn: [parentTask],
parityId: null,
implementationClass: "QUEUE_ONLY",
parallelGroup: "domain-parity-bridge",
gate: "QUEUE",
card: `docs/tasks/${bridgeTask}.md`,
behavior: "Record the user-authorized supersession of stalled WBV2-P1424-001 and authorize the P1425 domain-parity epoch without modifying the preserved task manifest.",
outputs: ["supersession record", "bridge manifest", "status"],
acceptance: ["P1424-001 manifest and continuation remain preserved", "bridge manifest points to P1425-000"],
commands: ["node tools/web/check-execution-control-plane.mjs"],
status: "done",
ready: false,
queueMutationAllowed: true,
supersedes: parentTask,
};
const rootRecord = {
id: rootTask,
phase: "1425",
title: "authorize_create_model_hierarchy_parity_epoch",
kind: "bridge",
owner: "PROGRAM",
dependsOn: [bridgeTask],
parityId: null,
implementationClass: "QUEUE_ONLY",
parallelGroup: "domain-parity-root-1425",
gate: "QUEUE",
card: `docs/tasks/${rootTask}.md`,
behavior: "Authorize the create/modeling/hierarchy full-parity epoch while preserving the stalled P1424-001 task record.",
outputs: ["domain parity plan", "successor authorization", "manifest", "status"],
acceptance: [
"P1424-001 manifest and continuation remain intact and in_progress",
"plan/index/queue pointer are consistent",
"one source-backed module is active",
],
commands: ["node tools/web/check-execution-control-plane.mjs"],
status: "done",
ready: false,
queueMutationAllowed: true,
supersedes: parentTask,
userAuthorization: {
approvedAt: "2026-11-10",
scope: "创建模型 / 模型化 / 模型层级 三域全量对标桌面 Blender",
planDocument: planDocPath,
decision: "授权桥接并保留 P1424-001 记录",
},
};
const moduleRecord = {
id: moduleTask,
phase: "1425",
title: "implement_object_add_route",
kind: "module",
owner: "OBJECT",
dependsOn: [rootTask],
parityId: moduleId,
implementationClass: "LOCAL_EXACT",
parallelGroup: "module-object_add_route",
gate: "MODULE",
card: `docs/tasks/${moduleTask}.md`,
behavior: "Route the Web Add menu through the native desktop operator channel for mesh primitives and basic object helpers.",
outputs: ["module contract", "Add menu wiring", "positive/negative/recovery cases", "manifest", "status"],
acceptance: [
"every bound operator unit has a native Main mutation case",
"unsupported/stale/wrong-context cases fail closed",
"undo/redo and save/reopen preserve identical semantics",
"full-family parity remains explicit",
],
commands: moduleContract.commands,
status: "in_progress",
ready: true,
queueMutationAllowed: true,
module: moduleContract,
};
const plan = fs.existsSync(abs(planPath)) ? json(planPath) : JSON.parse(JSON.stringify(json(sourcePlanPath)));
plan.planId = "BLENDER_WEB_CREATE_MODEL_HIERARCHY_PARITY_P1425";
plan.status = "ACTIVE_MODULE_EXECUTION_QUEUE";
plan.authority = {
...(plan.authority ?? {}),
userAuthorization: {
approvedAt: "2026-11-10",
scope: "创建模型 / 模型化 / 模型层级 三域全量对标桌面 Blender",
planDocument: planDocPath,
supersedes: parentTask,
preservedTask: parentTask,
bridge: bridgeTask,
},
};
plan.tasks = plan.tasks.filter((task) => ![bridgeTask, rootTask, moduleTask].includes(task.id));
plan.modules = plan.modules.filter((module) => module.id !== moduleId);
const stalled = plan.tasks.find((task) => task.id === parentTask);
assert.ok(stalled, `${parentTask} record is missing`);
stalled.status = "blocked";
stalled.ready = false;
stalled.supersededBy = bridgeTask;
stalled.supersessionReason = "User approved the three-domain full-parity plan on 2026-11-10; P1424-001 manifest, continuation and acceptance state are preserved.";
stalled.preservedContinuation = preservedContinuation;
plan.tasks.push(bridgeRecord, rootRecord, moduleRecord);
plan.modules.push({
id: moduleId,
task: moduleTask,
owner: "OBJECT",
sourceBacked: true,
capabilities: moduleContract.capabilities,
reusedReceipts: moduleContract.reuseReceipts,
fullFamilyParity: false,
});
writeJson(planPath, plan);
writeText(planDocPath, fs.readFileSync(path.join(process.env.HOME ?? "", ".claude/plans/cosmic-greeting-marble.md"), "utf8"));
writeText(`docs/tasks/${bridgeTask}.md`, `# ${bridgeTask}: supersede stalled task for domain parity
- task: ${bridgeTask}
- parent: ${parentTask}
- status: done
- ownerFamily: PROGRAM
- targetImplementationClass: QUEUE_ONLY
## 目标
记录用户 2026-11-10 授权的队列桥接:保留 \`${parentTask}\` 的 manifest、continuation、\`in_progress\` 状态和全部未闭合证据,不修改该文件;由本桥接指向三域全量对标 epoch 根任务 \`${rootTask}\`。
## 输入与范围
- 保留任务:\`${parentManifestPath}\`(期望 212423 bytes,原样)
- 保留 continuation:\`${preservedContinuation}\`
- 新计划:\`${planPath}\`
## 验收
- P1424-001 manifest 字节级不变;桥接 manifest 为 done 且 nextTask 指向 \`${rootTask}\`。
- 负例:P1424-001 manifest 大小变化、continuation 缺失或队列已被再次切换时拒绝继续。
- 校验:\`node tools/web/check-execution-control-plane.mjs\`
## 交付与回滚
- 交付桥接 manifest/status/report;回滚把队列恢复为 \`${parentTask}\` 并删除桥接指针,不删除任何证据。
`);
writeText(`docs/tasks/${rootTask}.md`, `# ${rootTask}: authorize create/modeling/hierarchy parity epoch
- task: ${rootTask}
- parent: ${bridgeTask}
- status: done
- ownerFamily: PROGRAM
- targetImplementationClass: QUEUE_ONLY
- Implementation entrypoint: \`node tools/web/authorize-wbv2-p1425.mjs\`
## 目标
以用户 2026-11-10 批准的三域全量对标计划为依据,授权新的 module epoch,并把队列指向首个模块 \`${moduleTask}\`。\`${parentTask}\` 的 manifest、continuation、in_progress 状态和全部未闭合证据原样保留,不删除、不伪装完成。
## 输入与范围
- 用户批准计划:\`${planDocPath}\`
- 源队列计划:\`${sourcePlanPath}\`
- 新计划:\`${planPath}\`
- 保留任务:\`${parentTask}\`(\`${parentManifestPath}\`)
- 桥接 manifest:\`${bridgeManifestPath}\`
## 验收
- P1424-001 manifest 仍为 \`in_progress\` 且字节级未变。
- 新计划中恰好一个 ready/in_progress 模块;索引 activeTask 与队列指针一致。
- 负例:P1424-001 缺失、重复授权或索引不一致时脚本必须拒绝对队列做第二次切换。
- \`node tools/web/check-execution-control-plane.mjs\`
## 交付与回滚
- 交付 plan、manifest、status、report、task-context;校验 \`node tools/web/check-execution-control-plane.mjs\`。
- 回滚:恢复队列指针为 \`${parentTask}\`;新计划文件保留但不得作为 active 队列。
`);
writeText(`docs/tasks/${moduleTask}.md`, `# ${moduleTask}: implement Add menu native operator route
- task: ${moduleTask}
- parent: ${rootTask}
- status: in_progress
- gap: workflow:object_add_route
- ownerFamily: OBJECT
- targetImplementationClass: LOCAL_EXACT
- moduleId: ${moduleId}
- Implementation entrypoint: \`node tools/web/run-wbv2-module-task.mjs --task ${moduleTask} --write\`
## 目标
让 WASM 本地模式的 Add 菜单通过现有通用桌面 operator 通道(\`desktop_operator_catalog/schema/desktop_operator\` + \`gpuUIInvoke\`)创建 Mesh 全部基础体,包括缺失的 Circle/Grid/Monkey,并支持 location/rotation/scale/align/enter_editmode 参数;复用 Operator Palette、\`WebEngineClient\` 与原生 Main,不新增并行执行器。
## 输入与范围
- 原生通道:\`blender-5.2.0/source/blender/web_engine/web_engine_python_bootstrap.py\`、\`web_engine_gpu_ui.cpp\`、\`web_engine_api.cpp\`
- 前端:\`web/app/src/app/App.tsx\`(Add 菜单、F3、快捷条)、\`web/app/src/engine-client/WebEngineClient.ts\`
- 协议/路由:\`web/protocol/web-engine.ts\`、\`web/app/src/capabilities/blender-feature-route-catalog.json\`
- 绑定的源码单元:${coverageBindings.join(", ")}
- 已复用证据:\`tests/golden/WBV2-P18-002/manifest.json\`、\`tests/golden/WBV2-P18-002/object-create-production.json\`
## 失败边界
不支持的基础体/参数、错误 mode/area、陈旧 baseRevision、modal 占用必须返回结构化错误,Main revision 不变且不产生 undo 记录;linked/只读对象不得被创建路径绕过。
## 验收
- 正例:Add Circle/Grid/Monkey 及既有基础体经原生通道创建,Main mutation 与 snapshot 反映对象。
- 负例:非法标识、非法参数、陈旧 revision、错误上下文均 no-mutation。
- 恢复:undo/redo、save/reopen、Worker restart 语义一致。
- 命令:实现期只做增量编译与 runtime 同步;全部功能完成后由用户在 Chromium 验证。结果标 \`NOT_CLAIMED\`。
- \`cmake --build build_web_blender6 --target web_engine -- -j8\`
- \`node tools/web/sync-web-engine-runtime.mjs\`
## 交付与回滚
- 交付 module receipt、manifest、status、continuation;失败保持 \`in_progress\`,保留已有构件与证据。
`);
const reportPath = `tests/golden/${rootTask}/successor-authorization-report.json`;
writeJson(`tests/golden/${bridgeTask}/supersession-report.json`, {
schemaVersion: 1,
task: bridgeTask,
operation: "SUPERSEDE_STALLED_TASK_FOR_DOMAIN_PARITY",
status: "PASS",
preserved: {
task: parentTask,
manifest: parentManifestPath,
manifestBytes: fs.statSync(abs(parentManifestPath)).size,
manifestSha256: sha(parentManifestPath),
status: "in_progress",
continuation: preservedContinuation,
continuationSha256: sha(preservedContinuation),
acceptance: "NOT_CLAIMED",
},
userAuthorization: rootRecord.userAuthorization,
successor: { task: rootTask, plan: planPath },
nextTask: rootTask,
});
writeText(`docs/status/${bridgeTask}.md`, `# ${bridgeTask} status
- task: \`${bridgeTask}\`; parent: \`${parentTask}\`; status: \`done\`
- operation: \`SUPERSEDE_STALLED_TASK_FOR_DOMAIN_PARITY\`
- preserved: \`${parentTask}\` manifest \`${fs.statSync(abs(parentManifestPath)).size}\` bytes, status \`in_progress\`, continuance \`${preservedContinuation}\`
- successor: \`${rootTask}\`; plan: \`${planPath}\`
- nextTask: \`${rootTask}\`
`);
writeJson(bridgeManifestPath, {
schemaVersion: 1,
task: bridgeTask,
parentTask,
operation: "SUPERSEDE_STALLED_TASK_FOR_DOMAIN_PARITY",
status: "done",
enablingTask: true,
parityStateChange: false,
artifacts: {
preservedManifest: { path: parentManifestPath, sha256: sha(parentManifestPath) },
preservedContinuation: { path: preservedContinuation, sha256: sha(preservedContinuation) },
report: { path: `tests/golden/${bridgeTask}/supersession-report.json`, sha256: sha(`tests/golden/${bridgeTask}/supersession-report.json`) },
status: { path: `docs/status/${bridgeTask}.md`, sha256: sha(`docs/status/${bridgeTask}.md`) },
},
assertions: {
preservedTaskStatus: "in_progress",
preservedAcceptance: "NOT_CLAIMED",
preservedManifestBytes: fs.statSync(abs(parentManifestPath)).size,
supersedes: parentTask,
userApprovedAt: rootRecord.userAuthorization.approvedAt,
},
nextTask: rootTask,
});
writeJson(reportPath, {
schemaVersion: 1,
task: rootTask,
operation: "AUTHORIZE_DOMAIN_PARITY_EPOCH",
status: "PASS",
parent: { task: bridgeTask, manifest: bridgeManifestPath },
preserved: { task: parentTask, manifest: parentManifestPath, status: "in_progress", continuation: preservedContinuation, acceptance: "NOT_CLAIMED" },
successor: { task: moduleTask, moduleId, sourceBacked: true, coverageBindings: coverageBindings.length },
plan: { path: planPath, sha256: sha(planPath) },
userAuthorization: rootRecord.userAuthorization,
nextTask: moduleTask,
});
writeText(`docs/status/${rootTask}.md`, `# ${rootTask} status
- task: \`${rootTask}\`; parent: \`${bridgeTask}\`; status: \`done\`
- operation: \`AUTHORIZE_DOMAIN_PARITY_EPOCH\`; module: \`${moduleId}\`
- preserved: \`${parentTask}\` manifest remains \`in_progress\` (\`${fs.statSync(abs(parentManifestPath)).size}\` bytes); continuation \`${preservedContinuation}\`; acceptance \`NOT_CLAIMED\`
- new plan: \`${planPath}\`; bound source units: \`${coverageBindings.length}\`
- nextTask: \`${moduleTask}\`
`);
run("tools/web/generate-v2-task-index.mjs", "--plan-path", abs(planPath));
let queue = read(queuePath);
queue = queue
.replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${moduleTask}\` |`)
.replace(/\| 里程碑 \| [^|]+ \|/u, `| 里程碑 | 三域全量对标(创建模型 / 模型化 / 模型层级) |`)
.replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`tests/golden/${rootTask}/manifest.json\` |`)
.replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${moduleTask}.md\`](tasks/${moduleTask}.md) |`);
writeText(queuePath, queue);
run("tools/web/check-task-context.mjs", "--task", rootTask, "--write");
writeJson(`tests/golden/${rootTask}/manifest.json`, {
schemaVersion: 1,
task: rootTask,
parentTask: bridgeTask,
operation: "AUTHORIZE_DOMAIN_PARITY_EPOCH",
status: "done",
enablingTask: true,
parityStateChange: false,
artifacts: {
parentManifest: { path: bridgeManifestPath, sha256: sha(bridgeManifestPath) },
plan: { path: planPath, sha256: sha(planPath) },
report: { path: reportPath, sha256: sha(reportPath) },
status: { path: `docs/status/${rootTask}.md`, sha256: sha(`docs/status/${rootTask}.md`) },
taskContext: { path: `tests/golden/${rootTask}/task-context.json`, sha256: sha(`tests/golden/${rootTask}/task-context.json`) },
},
assertions: {
supersedes: parentTask,
bridge: bridgeTask,
preservedTaskStatus: "in_progress",
preservedManifestBytes: fs.statSync(abs(parentManifestPath)).size,
preservedContinuation,
userApprovedAt: rootRecord.userAuthorization.approvedAt,
moduleId,
coverageBindings: coverageBindings.length,
sourceBackedSuccessor: true,
fullFamilyParity: false,
},
nextTask: moduleTask,
});
run("tools/web/check-task-context.mjs", "--task", moduleTask, "--write");
run("tools/web/check-execution-control-plane.mjs");
process.stdout.write(`wbv2-domain-parity-authorized bridge=${bridgeTask} root=${rootTask} next=${moduleTask} module=${moduleId} preserved=${parentTask}\n`);