172 lines
12 KiB
Python
172 lines
12 KiB
Python
#!/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())
|