#!/usr/bin/env python3 """Activate the source-unit route completion phase after product workflows.""" from __future__ import annotations import hashlib import json import re import subprocess from pathlib import Path from wbv2_runtime import CONTROL_ROOT, INDEX, QUEUE, ROOT CURRENT = "WBV2-P1422-001" ROOT_TASK = "WBV2-P1423-000" TASK = "WBV2-P1423-001" def read_json(path: Path) -> dict: return json.loads(path.read_text(encoding="utf-8")) def write_json(path: Path, value: object) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def write_text(path: Path, value: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(value, encoding="utf-8") def sha(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() TASKS = [ { "id": "SERVER-OPERATOR-JOB", "module": "RUNTIME", "title": "Execute background-safe Blender operators through server jobs", "commands": ["submitServerOperatorJob", "cancelServerOperatorJob", "readServerOperatorResult"], "ownedPaths": ["web/protocol/server-operator-job.ts", "web/protocol/blender-operator.ts", "web/app/src/engine-client/WebEngineClient.ts", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "tools/web/server-operator-job.py", "tools/web/server-job-process.mjs"], "implementation": "Add a bounded, hash-bound server operator job contract for the SERVER_NATIVE route. Run Blender background operators in an isolated workspace with typed properties, cancellation, resource budgets and result binding; reject UI/window/file-browser context.", }, { "id": "OPERATOR-ROUTE-PALETTE", "module": "WORKSPACE", "title": "Expose every classified operator route in the browser palette", "commands": ["listFeatureRoutes", "invokeFeatureRoute"], "ownedPaths": ["web/protocol/blender-operator.ts", "web/protocol/web-engine.ts", "web/app/src/app/App.tsx", "web/app/src/engine-client/WebEngineClient.ts", "web/app/src/workers/web-engine.worker.ts", "web/app/src/capabilities/blender-feature-route-catalog.json"], "implementation": "Load the complete source-unit route catalog, merge WEB_MAIN operators with server-job and browser-equivalent entries in F3, and show an explicit route/reason badge. Invoke only typed native or server-job routes.", }, { "id": "OPERATOR-MACRO-COMPOSER", "module": "RUNTIME", "title": "Decompose and execute operator macros as native command plans", "commands": ["listBlenderMacros", "invokeBlenderMacro"], "ownedPaths": ["web/protocol/blender-operator.ts", "web/protocol/web-engine.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Represent the 58 macro units as bounded, source-backed command plans. Reuse existing Main transactions where possible, otherwise submit a server-native plan; reject context-dependent macro steps before mutation.", }, { "id": "RNA-REFLECTION-EXTENDED", "module": "RUNTIME", "title": "Extend RNA reflection to arrays, enums and nested collections", "commands": ["readRNAPropertySchema", "setRNAArrayProperty", "setRNAEnumProperty", "readRNACollection"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/scene-ir.ts", "web/app/src/app/App.tsx", "web/app/src/engine-client/WebEngineClient.ts", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "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"], "implementation": "Expand the bounded RNA route beyond primitive scalars to validated arrays, enums and nested collection reads/writes, keeping pointer, ID-link, context-only and arbitrary execution paths fail-closed.", }, { "id": "FEATURE-COVERAGE-DASHBOARD", "module": "WORKSPACE", "title": "Publish complete source-unit route coverage and gap dashboard", "commands": ["featureRouteCoverage", "featureRouteGaps"], "ownedPaths": ["web/app/src/app/App.tsx", "web/app/src/capabilities/blender-feature-route-catalog.json", "my-tools/show_blender_web_scheduler.py", "docs/WEB_FULL_FEATURE_GAP_INVENTORY.md"], "implementation": "Add a route-aware dashboard and scheduler summary for all 3745 units, broken down by WEB_MAIN, SERVER_NATIVE, BROWSER_EQUIVALENT and UNAVAILABLE, with explicit unverified behavioral evidence and no false completion claims.", }, ] def main() -> int: backlog_path = CONTROL_ROOT / "web-product-backlog.json" backlog = read_json(backlog_path) existing = {task.get("id") for task in backlog.get("tasks", [])} for task in TASKS: item = {**task, "attempts": 0, "status": "pending", "resultPath": f"tests/golden/WBV2/product-migration/{task['id']}-result.json"} if task["id"] not in existing: backlog.setdefault("tasks", []).append(item) else: for current in backlog["tasks"]: if current.get("id") == task["id"]: current.update(item) by_module = {} for item in backlog["tasks"]: by_module.setdefault(item.get("module"), []).append(item["id"]) for module in backlog.get("workflowModules", []): added = by_module.get(module.get("id"), []) if added: module["taskIds"] = list(dict.fromkeys([*module.get("taskIds", []), *added])) module["status"] = "partial" backlog.update({"previousQueueTask": CURRENT, "rootTask": ROOT_TASK, "queueTask": TASK, "strategy": "implementation_first_route_complete", "routeCatalog": "web/app/src/capabilities/blender-feature-route-catalog.json"}) write_json(backlog_path, backlog) current_manifest_path = ROOT / "tests/golden" / CURRENT / "manifest.json" current_manifest = read_json(current_manifest_path) current_manifest["nextTask"] = ROOT_TASK write_json(current_manifest_path, current_manifest) current_status_path = ROOT / "docs/status" / f"{CURRENT}.md" if current_status_path.is_file(): text = re.sub(r"- nextTask: `[^`]+`", f"- nextTask: `{ROOT_TASK}`", current_status_path.read_text(encoding="utf-8")) write_text(current_status_path, text) if isinstance(current_manifest.get("artifacts", {}).get("status"), dict): current_manifest["artifacts"]["status"]["sha256"] = sha(current_status_path) write_json(current_manifest_path, current_manifest) root_dir = ROOT / "tests/golden" / ROOT_TASK plan_path = root_dir / "operator-route-implementation-plan.json" base_plan = read_json(ROOT / "tests/golden/WBV2-P1422-000/product-implementation-plan.json") root_record = {"id": ROOT_TASK, "phase": "1423", "title": "authorize source-unit route completion", "kind": "enabling", "owner": "PROGRAM", "dependsOn": [CURRENT], "implementationClass": "QUEUE_ONLY", "card": f"docs/tasks/{ROOT_TASK}.md", "commands": [], "status": "done", "ready": False, "queueMutationAllowed": True} task_record = {"id": TASK, "phase": "1423", "title": "complete source-unit route completion", "kind": "product-module", "owner": "WEB_PRODUCT", "dependsOn": [ROOT_TASK], "implementationClass": "PRODUCT_WORKFLOW", "card": f"docs/tasks/{TASK}.md", "commands": ["python3 my-tools/run_web_product_migration.py"], "status": "in_progress", "ready": True, "queueMutationAllowed": True, "productBacklog": "tests/golden/WBV2/web-product-backlog.json", "module": {"sourcePaths": list(dict.fromkeys(path for item in TASKS for path in item["ownedPaths"]))}} write_json(plan_path, {**base_plan, "planId": "BLENDER_WEB_ROUTE_COMPLETION_P1423", "status": "ACTIVE_ROUTE_COMPLETION", "tasks": [*base_plan.get("tasks", []), root_record, task_record]}) write_text(ROOT / "docs/tasks" / f"{ROOT_TASK}.md", f"# {ROOT_TASK}: authorize source-unit route completion\n\n- task: {ROOT_TASK}\n- parent: {CURRENT}\n- status: done\n- nextTask: {TASK}\n- route catalog: `web/app/src/capabilities/blender-feature-route-catalog.json`\n") write_text(ROOT / "docs/tasks" / f"{TASK}.md", f"""# {TASK}: complete source-unit route completion - task: {TASK} - parent: {ROOT_TASK} - status: in_progress - targetImplementationClass: PRODUCT_WORKFLOW - implementation entrypoint: `python3 my-tools/run_web_product_migration.py` - node tools/web/print-task-context.mjs - route catalog: `web/app/src/capabilities/blender-feature-route-catalog.json` ## 目标 Complete execution routes for every classified Blender source unit. WEB_MAIN uses typed Main/WASM, SERVER_NATIVE uses bounded background Blender jobs, BROWSER_EQUIVALENT uses browser workflows, and UNAVAILABLE remains explicit. ## 输入与范围 - `docs/WEB_FULL_FEATURE_GAP_INVENTORY.md` - `web/app/src/capabilities/blender-feature-route-catalog.json` - pending tasks in `tests/golden/WBV2/web-product-backlog.json` - each task's owned source/protocol/Worker/UI paths ## 验收 - every route is typed, bounded and source-backed - no JavaScript simulation replaces Blender Main or a declared server job - browser-only context is represented by an explicit equivalent route - verification remains incremental and deferred to user operation ## 失败边界 - unsupported context, unsafe RNA pointer, missing server capability or malformed job fails closed - failed turns remain pending and are retried - route catalog never marks behavioral verification as complete by classification alone ## 交付与回滚 - result artifacts: `tests/golden/WBV2/product-migration/*-result.json` - aggregate report: `tests/golden/{TASK}/product-implementation-report.json` - rollback: stop the product runner and retain the last completed route task """) root_report = root_dir / "operator-route-authorization.json" write_json(root_report, {"schemaVersion": 1, "task": ROOT_TASK, "operation": "AUTHORIZE_SOURCE_UNIT_ROUTE_COMPLETION", "status": "PASS", "routeCatalog": "web/app/src/capabilities/blender-feature-route-catalog.json", "pendingTaskCount": len(TASKS), "nextTask": TASK}) root_status = ROOT / "docs/status" / f"{ROOT_TASK}.md" write_text(root_status, f"# {ROOT_TASK} status\n\n- status: `done`\n- nextTask: `{TASK}`\n- route catalog: `web/app/src/capabilities/blender-feature-route-catalog.json`\n") root_manifest = root_dir / "manifest.json" write_json(root_manifest, {"schemaVersion": 1, "task": ROOT_TASK, "parentTask": CURRENT, "operation": "AUTHORIZE_SOURCE_UNIT_ROUTE_COMPLETION", "status": "done", "nextTask": TASK, "artifacts": {"parentManifest": {"path": f"tests/golden/{CURRENT}/manifest.json", "sha256": sha(current_manifest_path)}, "backlog": {"path": str(backlog_path.relative_to(ROOT)), "sha256": sha(backlog_path)}, "plan": {"path": str(plan_path.relative_to(ROOT)), "sha256": sha(plan_path)}, "routeCatalog": {"path": "web/app/src/capabilities/blender-feature-route-catalog.json", "sha256": sha(ROOT / "web/app/src/capabilities/blender-feature-route-catalog.json")}}}) queue = QUEUE.read_text(encoding="utf-8") queue = re.sub(r"\| 当前任务 \| `[^`]+` \|", f"| 当前任务 | `{TASK}` |", queue, count=1) queue = re.sub(r"\| parent manifest \| `[^`]+` \|", f"| parent manifest | `tests/golden/{ROOT_TASK}/manifest.json` |", queue, count=1, flags=re.IGNORECASE) queue = re.sub(r"\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|", f"| 任务卡 | [`tasks/{TASK}.md`](tasks/{TASK}.md) |", queue, count=1) write_text(QUEUE, queue) subprocess.run(["node", "tools/web/generate-v2-task-index.mjs", "--plan-path", str(plan_path.relative_to(ROOT))], cwd=ROOT, check=True) subprocess.run(["node", "tools/web/check-task-context.mjs", "--task", TASK, "--write"], cwd=ROOT, check=True) print(f"operator-route-phase-activated task={TASK} pending={len(TASKS)}") return 0 if __name__ == "__main__": raise SystemExit(main())