#!/usr/bin/env python3 """Prepare bounded font and animation contracts from the supplied native entries.""" import copy import json from wbv2_autopilot import Autopilot, ROOT, contained, queue_context, write_json WORKFLOWS = { "WEBUI-FONT-ASSETS": { "slug": "font-assets", "test": "font-assets-ui-production.spec.ts", "reader": "font-main-snapshots.py", "fixture": "nonmesh_scene.blend", "units": ["web-command:setFontAdvanced", "web-command:importVFont", "web-command:setFontLinks"], "negative": ["web-command:setFontLinks"], "history": ["web-command:setFontLinks"], "source": ["blender-5.2.0/source/blender/makesrna/intern/rna_curve.cc"], "assets": ["blender-5.2.0/release/datafiles/bfont.pfb", "web/app/src/fonts/external-vfont-import.ts"], "workflow": "Edit font layout, import a packed browser font and link it to text; reject missing links and preserve native Main through history and reopening", }, "ACTION-MARKER-AUTHORING": { "slug": "action-marker", "test": "action-marker-ui-production.spec.ts", "reader": "action-main-snapshots.py", "fixture": "animation_scene.blend", "units": ["web-command:createAction", "web-command:removeAction", "web-command:setKeyframe", "web-command:setFCurveKeyframes", "web-command:setTimelineMarkers"], "negative": ["web-command:setTimelineMarkers"], "history": ["web-command:setTimelineMarkers"], "source": ["blender-5.2.0/source/blender/makesrna/intern/rna_action.cc", "blender-5.2.0/source/blender/makesrna/intern/rna_timeline.cc"], "assets": [], "workflow": "Create and remove Actions, edit keyframes and their handles, and add, rename, move and delete timeline markers through native Main", }, } def main(): runner = Autopilot() context = queue_context(ROOT) jobs = {job["id"]: job for job in runner.jobs(context)} results = [] for identifier, spec in WORKFLOWS.items(): if identifier not in jobs: raise ValueError("workflow is not in the authorized backlog") test = "web/tests/e2e/" + spec["test"] reader = "web/tests/fixtures/" + spec["reader"] references = spec["source"] + ["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_blend_reader.cpp"] dependencies = sorted(set(jobs[identifier]["ownedPaths"] + references + spec["assets"] + [ test, reader, "web/tests/fixtures/native-ui-workflow.ts", "tests/files/web/" + spec["fixture"], "web/app/src/engine-client/WebEngineClient.ts", "web/app/src/workers/web-engine.worker.ts", "web/protocol/scene-ir.ts", "web/protocol/web-engine.ts", "web/protocol/animation-render-commands.ts", "web/protocol/nonmesh-geometry-commands.ts", "web/app/vite.config.ts", "web/app/src/capabilities/blender-feature-route-catalog.json", "web/playwright.config.ts", "web/package.json", "web/tsconfig.json", "my-tools/build_local_webengine.py", "tools/web/sync-web-engine-runtime.mjs", "tools/web/incremental-web-build.mjs", "tools/web/run-incremental-typecheck.mjs", "tools/web/web-build-fingerprint.mjs", ".emscripten-web", "build_web_blender6/CMakeCache.txt", "my-tools/prepare_wbv2_authoring_batch.py", ])) behavior = ["positive", "negative", "undo_redo", "save_reopen", "determinism"] result = { "status": "ready", "summary": "Prepared local native authoring workflow; independent behavioral validation is mandatory", "workflow": spec["workflow"], "sourceUnits": spec["units"], "routeIds": spec["units"], "sourceReferences": references, "changedFiles": [test, reader, "web/tests/fixtures/native-ui-workflow.ts", "web/app/src/app/App.tsx"] + (["blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"] if identifier == "ACTION-MARKER-AUTHORING" else []), "dependencies": dependencies, "artifacts": ["build_blender_native/bin/blender", "build_web_blender6/bin/web_engine.wasm", "build_web_blender6/bin/web_engine.js", "web/app/public/vendor/blender/web_engine.wasm", "web/app/public/vendor/blender/web_engine.js", "web/app/public/wasm-module-manifest.json"], "checks": [ {"id": "wasm-source-build", "argv": ["python3", "my-tools/build_local_webengine.py"], "cwd": ".", "kinds": ["source_build"], "timeoutSeconds": 1200, "continueOnFailure": True}, {"id": "web-build", "argv": ["node", "tools/web/incremental-web-build.mjs"], "cwd": ".", "kinds": ["typecheck"], "dependsOn": ["wasm-source-build"], "timeoutSeconds": 300, "continueOnFailure": True}, {"id": "authoring-lint", "argv": ["node", "node_modules/eslint/bin/eslint.js", "app/src/app/App.tsx"], "cwd": "web", "kinds": ["lint"], "timeoutSeconds": 60, "continueOnFailure": True}, {"id": "chromium-authoring", "argv": ["env", "BLENDER_BIN=" + str(ROOT / "build_blender_native/bin/blender"), "WEB_TEST_PORT=5357", "node", "node_modules/@playwright/test/cli.js", "test", "--config", "playwright.config.ts", "tests/e2e/" + spec["test"], "--workers=1", "--max-failures=1"], "cwd": "web", "kinds": ["chromium", *behavior], "dependsOn": ["wasm-source-build", "web-build"], "timeoutSeconds": 180, "continueOnFailure": True}, ], "cases": [{"id": spec["slug"] + "-" + kind, "kind": kind, "checkId": "chromium-authoring", "receipt": "unallocated/" + kind + ".json", "unitIds": spec["negative"] if kind == "negative" else spec["history"] if kind == "undo_redo" else spec["units"]} for kind in behavior], } for relative in dependencies + result["artifacts"]: if not contained(ROOT, relative).is_file(): raise ValueError("missing local workflow prerequisite: " + relative) probe = runner.directory / "contract-admission" / identifier validated = copy.deepcopy(result) for case in validated["cases"]: case["receipt"] = str((probe / "cases" / (case["id"] + ".json")).relative_to(ROOT)) runner.validate_result(validated, probe) destination = ROOT / "my-tools/local-workflows" / (identifier + ".json") write_json(destination, {"schemaVersion": 1, "task": context["task"], "job": identifier, "parentSha256": context["parentSha256"], "acceptance": "pending independent local validation", "result": result}) results.append({"job": identifier, "contract": str(destination.relative_to(ROOT)), "checks": len(result["checks"]), "cases": len(result["cases"])}) print(json.dumps({"prepared": results, "modelRequests": 0})) if __name__ == "__main__": main()