Files
workinf_Blender_Wasm/tools/web/authorize-wbv2-p1140.mjs
mes123456 f3e21c6b98
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 migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

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

104 lines
11 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 parentTask = "WBV2-P1139-001";
const rootTask = "WBV2-P1140-000";
const moduleTask = "WBV2-P1140-001";
const moduleId = "ACTION_GROUP_READER";
const planPath = "tests/golden/WBV2-P1100-000/module-execution-plan.json";
const parentManifestPath = `tests/golden/${parentTask}/manifest.json`;
const dynamicPath = "tests/golden/WBV2/dynamic-inventory.json";
const sourcePaths = [
"blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp",
"web/protocol/scene-ir.ts",
"web/app/src/workers/web-engine.worker.ts",
"web/app/src/engine-client/WebEngineClient.ts",
"web/tests/e2e/wbv2-action-group-reader-production.spec.ts",
"web/app/src/vendor/blender/web_engine.wasm",
];
const commands = [
"source tools/web/emscripten-env.sh && cmake --build build_web_blender6 --target web_engine -- -j8 && cp build_web_blender6/bin/web_engine.wasm web/app/src/vendor/blender/web_engine.wasm && cp build_web_blender6/bin/web_engine.wasm web/app/public/vendor/blender/web_engine.wasm",
"npm --prefix web run typecheck",
"npm --prefix web run build",
"WEB_TEST_PORT=5356 npm --prefix web run test:e2e -- --workers=1 tests/e2e/wbv2-action-group-reader-production.spec.ts",
];
const queuePath = "docs/EXECUTION_QUEUE.md";
const queue = read(queuePath);
const queueTask = queue.match(/\| 当前任务 \| `([^`]+)` \|/u)?.[1];
assert.ok(queueTask === parentTask || queueTask === rootTask || queueTask === moduleTask);
const parent = json(parentManifestPath);
assert.equal(parent.status, "done");
assert.ok(parent.nextTask === null || parent.nextTask === rootTask);
const plan = json(planPath);
assert.ok(plan.status === "COMPLETED_MODULE_EXECUTION" || plan.status === "ACTIVE_MODULE_EXECUTION_QUEUE");
const dynamic = json(dynamicPath);
const dynamicSummary = { path: dynamicPath, sourceTreeSha256: dynamic.authority.sourceTreeSha256, unitCount: dynamic.summary.unitCount, planningEstimate: dynamic.policy.planningEstimate, hardTarget: dynamic.policy.hardTarget, verifiedUnits: dynamic.coverage.verifiedUnits, unverifiedUnits: dynamic.coverage.unverifiedUnits };
const module = {
moduleId,
sourceBacked: true,
capabilities: ["CAP-ACTION-GROUP-READER", "CAP-ACTION-GROUP-RECOVERY", "CAP-ACTION-GROUP-FAIL-CLOSED"],
sourcePaths,
reuseReceipts: [{ manifest: "tests/golden/WBV2-P1112-000/manifest.json", report: "tests/golden/WBV2-P1112-000/module-receipt.json" }],
positive: ["ActionGroup Main reader exposes group and expanded metadata", "three grouped location F-Curves remain grouped", "save/reopen preserves group metadata"],
negative: ["malformed blend is rejected without Main mutation", "missing animation target is fail-closed", "unsupported group payload is not synthesized"],
recovery: ["undo", "redo", "save/reopen", "worker termination and deterministic group state"],
commands,
fullFamilyParity: false,
};
const rootRecord = {
id: rootTask, phase: "1140", title: "authorize_action_group_reader_module", kind: "enabling", owner: "ANIMATION", dependsOn: [parentTask], parityId: null,
implementationClass: "QUEUE_ONLY", parallelGroup: "module-root-1140", gate: "MODULE", card: `docs/tasks/${rootTask}.md`,
behavior: "Authorize a source-backed ActionGroup reader and recovery module after P1139 Texture.", outputs: ["successor authorization", "module plan", "manifest", "status"],
acceptance: ["P1139 evidence remains hash-bound", "ActionGroup has explicit Blender source mapping", "inventory remains source-derived", "one source-backed module is active"],
commands: ["node tools/web/check-execution-control-plane.mjs"], status: "done", ready: false, queueMutationAllowed: true,
};
const moduleRecord = {
id: moduleTask, phase: "1140", title: "verify_action_group_reader_module", kind: "module", owner: "ANIMATION", dependsOn: [rootTask], parityId: moduleId,
implementationClass: "LOCAL_EXACT", parallelGroup: "module-action_group_reader", gate: "MODULE", card: `docs/tasks/${moduleTask}.md`,
behavior: "Verify ActionGroup metadata through Blender Main reader, protocol and Chromium recovery.", outputs: ["module receipt", "negative matrix", "manifest", "status"],
acceptance: ["source and protocol hashes are bound", "positive Main reader is proven", "negative no-mutation and recovery are proven", "Chromium and save/reopen evidence are exact", "full-family parity remains explicit"],
status: "in_progress", ready: true, queueMutationAllowed: true, module,
};
plan.status = "ACTIVE_MODULE_EXECUTION_QUEUE";
if (!plan.modules.some((entry) => entry.id === moduleId)) plan.modules.push({ id: moduleId, task: moduleTask, owner: "ANIMATION", sourceBacked: true, capabilities: module.capabilities, reusedReceipts: module.reuseReceipts, fullFamilyParity: false });
if (!plan.tasks.some((entry) => entry.id === rootTask)) plan.tasks.push(rootRecord);
if (!plan.tasks.some((entry) => entry.id === moduleTask)) plan.tasks.push(moduleRecord);
writeJson(planPath, plan);
run("tools/web/generate-v2-task-index.mjs", "--plan-path", abs(planPath));
parent.nextTask = rootTask;
parent.continuation = { authorized: true, root: rootTask, scope: moduleId, priorModule: parentTask };
parent.artifacts.plan.sha256 = sha(planPath);
writeJson(parentManifestPath, parent);
writeText(`docs/tasks/${rootTask}.md`, `# ${rootTask}: authorize ActionGroup reader\n\n- task: ${rootTask}\n- parent: ${parentTask}\n- status: done\n- ownerFamily: ANIMATION\n- targetImplementationClass: QUEUE_ONLY\n- Implementation entrypoint: \`node tools/web/authorize-wbv2-p1140.mjs\`\n\n## 目标\n\n授权一个 source-backed ActionGroup reader/recovery 模块。\n\n## 输入与范围\n\n- RNA: \`blender-5.2.0/source/blender/makesrna/intern/rna_action.cc\` (ActionGroup)\n- Reader: \`blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp\`\n- Chromium: \`web/tests/e2e/wbv2-action-group-reader-production.spec.ts\`\n\n## 验收\n\n- P1139 evidence stays hash-bound; one source-backed module active; malformed or hash-drift input remains \`in_progress\`.\n- \`node tools/web/check-execution-control-plane.mjs\`\n\n## 交付与回滚\n\n- 交付 P1140 manifests/status/receipt;回滚恢复 P1139 终态指针。\n`);
writeText(`docs/status/${rootTask}.md`, `# ${rootTask} status\n\n- task: \`${rootTask}\`; parent: \`${parentTask}\`; status: \`done\`\n- operation: \`AUTHORIZE_SOURCE_BACKED_MODULE\`; module: \`${moduleId}\`\n- P1139 evidence reused; historical 6900-entry inventory remains immutable\n- dynamic source inventory: \`${dynamicSummary.unitCount}\` units, verified \`${dynamicSummary.verifiedUnits}\`, unverified \`${dynamicSummary.unverifiedUnits}\`, planning estimate \`${dynamicSummary.planningEstimate}\` (non-binding)\n- nextTask: \`${moduleTask}\`\n`);
let updatedQueue = read(queuePath).replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${rootTask}\` |`).replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`${parentManifestPath}\` |`).replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${rootTask}.md\`](tasks/${rootTask}.md) |`);
writeText(queuePath, updatedQueue);
run("tools/web/check-task-context.mjs", "--task", rootTask, "--write");
const rootReportPath = `tests/golden/${rootTask}/successor-authorization-report.json`;
const rootStatusPath = `docs/status/${rootTask}.md`;
const rootContextPath = `tests/golden/${rootTask}/task-context.json`;
writeJson(rootReportPath, { schemaVersion: 1, task: rootTask, operation: "AUTHORIZE_SOURCE_BACKED_MODULE", status: "PASS", parent: { task: parentTask, manifest: parentManifestPath, manifestSha256: sha(parentManifestPath) }, successor: { task: moduleTask, moduleId, sourceBacked: true, noRepeatedImplementation: true }, inventory: { ...plan.inventory, boundCaseDelta: 0 }, dynamicInventory: { ...dynamicSummary, boundUnitDelta: 0 }, nextTask: moduleTask });
writeJson(`tests/golden/${rootTask}/manifest.json`, { schemaVersion: 1, task: rootTask, parentTask, operation: "AUTHORIZE_SOURCE_BACKED_MODULE", status: "done", enablingTask: true, parityStateChange: false, runtime: "MODULE_EXECUTION_QUEUE_BRIDGE", artifacts: { parentManifest: { path: parentManifestPath, sha256: sha(parentManifestPath) }, plan: { path: planPath, sha256: sha(planPath) }, report: { path: rootReportPath, sha256: sha(rootReportPath) }, status: { path: rootStatusPath, sha256: sha(rootStatusPath) }, taskContext: { path: rootContextPath, sha256: sha(rootContextPath) } }, assertions: { sourceBackedSuccessor: true, priorEvidencePreserved: true, inventoryTotal: 6900, effectiveCompleted: 290, effectivePending: 6609, boundCaseDelta: 0, dynamicInventory: dynamicSummary, fullHistoricalReproduction: false }, nextTask: moduleTask });
updatedQueue = read(queuePath).replace(/\| 当前任务 \| `[^`]+` \|/u, `| 当前任务 | \`${moduleTask}\` |`).replace(/\| parent manifest \| `[^`]+` \|/u, `| parent manifest | \`tests/golden/${rootTask}/manifest.json\` |`).replace(/\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|/u, `| 任务卡 | [\`tasks/${moduleTask}.md\`](tasks/${moduleTask}.md) |`);
writeText(queuePath, updatedQueue);
writeText(`docs/tasks/${moduleTask}.md`, `# ${moduleTask}: verify ActionGroup reader\n\n- task: ${moduleTask}\n- parent: ${rootTask}\n- status: in_progress\n- gap: workflow:action_group_reader\n- ownerFamily: ANIMATION\n- targetImplementationClass: LOCAL_EXACT\n- moduleId: ${moduleId}\n- Implementation entrypoint: \`node tools/web/run-wbv2-module-task.mjs --task ${moduleTask} --write\`\n\n## 目标\n\n验证 Blender Main 的 ActionGroup 元数据在 Chromium recovery/save-reopen 中保持一致。\n\n## 输入与范围\n\n${sourcePaths.map((file) => `- \`${file}\``).join("\n")}\n- RNA: \`blender-5.2.0/source/blender/makesrna/intern/rna_action.cc\` (ActionGroup)\n- Fixture: \`tests/files/web/generated/M16-GAP-00161-operator-anim.channels_group.blend\`\n- Production test: \`web/tests/e2e/wbv2-action-group-reader-production.spec.ts\`\n\n## 验收\n\n- Positive: ${module.positive.join(", ")}\n- Negative: ${module.negative.join(", ")}\n- Recovery: ${module.recovery.join(", ")}\n- Commands:\n${commands.map((command) => `- \`${command}\``).join("\n")}\n- JS simulation is forbidden; all behavior must come from Blender Main/WASM.\n\n## 交付与回滚\n\n- Receipt: \`tests/golden/${moduleTask}/module-receipt.json\`; Manifest: \`tests/golden/${moduleTask}/manifest.json\`; Status: \`docs/status/${moduleTask}.md\`.\n`);
run("tools/web/check-task-context.mjs", "--task", moduleTask, "--write");
run("tools/web/check-execution-control-plane.mjs");
process.stdout.write(`wbv2-successor-authorized root=${rootTask} next=${moduleTask} module=${moduleId}\n`);