feat(web): keep unattended native batch planner productive
This commit is contained in:
@@ -24,6 +24,28 @@ AUTO_PLANNER_STATUS = CONTROL_ROOT / "auto-planner.json"
|
||||
|
||||
|
||||
PREFERRED_BATCHES = [
|
||||
{
|
||||
"moduleId": "COMPOSITOR_NATIVE_NODE_BATCH",
|
||||
"title": "verify native compositor transform blur alpha reader",
|
||||
"owner": "MEDIA",
|
||||
"identifiers": ["CompositorNodeTransform", "CompositorNodeBlur", "CompositorNodeAlphaOver", "CompositorNodeViewer"],
|
||||
"rna": [],
|
||||
"fixture": "tests/files/web/compositor_node_batch_scene.blend",
|
||||
"test": "web/tests/e2e/compositor-main-reader-production.spec.ts",
|
||||
"production": "tests/golden/{task}/compositor-main-reader-production.json",
|
||||
"types": ["CompositorNodeTransform", "CompositorNodeBlur", "CompositorNodeAlphaOver", "VIEWER"],
|
||||
},
|
||||
{
|
||||
"moduleId": "COMPOSITOR_NODE_READER_BATCH",
|
||||
"title": "verify compositor node reader batch",
|
||||
"owner": "MEDIA",
|
||||
"identifiers": [],
|
||||
"rna": ["CompositorNodeGlare", "CompositorNodeViewer"],
|
||||
"fixture": "tests/files/web/compositor_scene.blend",
|
||||
"test": "web/tests/e2e/compositor-main-reader-production.spec.ts",
|
||||
"production": "tests/golden/{task}/compositor-main-reader-production.json",
|
||||
"types": ["CompositorNodeGlare", "VIEWER"],
|
||||
},
|
||||
{
|
||||
"moduleId": "MODIFIER_READER_BATCH",
|
||||
"title": "verify modifier reader batch",
|
||||
@@ -310,6 +332,210 @@ def batch_contract(task: str, batch: dict) -> dict:
|
||||
return {"batchId": batch.get("batchId") or f"{task}:{batch['moduleId']}:{digest}", "selectedRecordIds": selected}
|
||||
|
||||
|
||||
def native_compositor_batch(inventory: dict) -> dict | None:
|
||||
"""Select a bounded node-reader batch that can generate its own fixture."""
|
||||
candidates = [
|
||||
unit for unit in inventory.get("units", [])
|
||||
if isinstance(unit, dict)
|
||||
and unit.get("coverageStatus") != "VERIFIED"
|
||||
and unit.get("kind") == "node"
|
||||
and isinstance(unit.get("identifier"), str)
|
||||
and unit["identifier"].startswith("CompositorNode")
|
||||
]
|
||||
if not candidates:
|
||||
return None
|
||||
# Native reader fixtures remain compact: some compositor node storage
|
||||
# layouts interact when more than five heterogeneous nodes share one
|
||||
# group. Keep the upper bound at the empirically stable shape; the
|
||||
# adaptive history may still reduce it after a focused gate failure.
|
||||
max_size = 5
|
||||
module_id = "AUTO_COMPOSITOR_NATIVE_NODE_READER"
|
||||
history = []
|
||||
for receipt_path in sorted(ROOT.glob("tests/golden/WBV2-P*-001/module-receipt.json"), key=lambda item: item.parent.name):
|
||||
receipt = read_json(receipt_path)
|
||||
if receipt.get("moduleId") != module_id:
|
||||
continue
|
||||
duration = receipt.get("efficiency", {}).get("totalDurationMs")
|
||||
if not isinstance(duration, (int, float)):
|
||||
duration = sum(item.get("durationMs", 0) for item in receipt.get("commands", []) if isinstance(item, dict))
|
||||
history.append({
|
||||
"durationMs": duration,
|
||||
"commands": len(receipt.get("commands", [])),
|
||||
"timeout": any("timeout" in str(item.get("output", "")).lower() for item in receipt.get("commands", []) if isinstance(item, dict)),
|
||||
"focusedGateFailure": receipt.get("status") != "PASS",
|
||||
})
|
||||
recent = history[-3:]
|
||||
current_size = min(len(candidates), max_size)
|
||||
if recent and any(item["timeout"] or item["durationMs"] > 900_000 or item["commands"] > 48 for item in recent):
|
||||
next_size = max(1, current_size // 2)
|
||||
elif recent and all(item["durationMs"] < 300_000 and item["commands"] < 24 and not item["focusedGateFailure"] for item in recent):
|
||||
next_size = min(max_size, current_size + 4)
|
||||
else:
|
||||
next_size = current_size
|
||||
units = candidates[:next_size]
|
||||
return {
|
||||
"moduleId": module_id,
|
||||
"title": "verify auto-generated native compositor reader batch",
|
||||
"owner": "NODES",
|
||||
"identifiers": [unit["identifier"] for unit in units],
|
||||
"rna": [],
|
||||
"test": "web/tests/e2e/compositor-main-reader-production.spec.ts",
|
||||
"production": "tests/golden/{task}/compositor-main-reader-production.json",
|
||||
"types": [unit["identifier"] for unit in units],
|
||||
"units": units,
|
||||
"candidateUnits": candidates,
|
||||
"nativeCompositorFixture": True,
|
||||
"adaptiveKey": f"{module_id}:LOCAL_EXACT:NODES",
|
||||
"adaptiveHistory": recent,
|
||||
"adaptiveCurrentSize": current_size,
|
||||
"adaptiveNextSize": next_size,
|
||||
"adaptiveMinSize": 1,
|
||||
"adaptiveMaxSize": max_size,
|
||||
}
|
||||
|
||||
|
||||
def ensure_native_compositor_fixture(batch: dict, contract: dict) -> None:
|
||||
"""Materialize a selected native node batch before its manifest references it."""
|
||||
if not batch.get("nativeCompositorFixture"):
|
||||
return
|
||||
digest = contract["batchId"].rsplit(":", 1)[-1]
|
||||
fixture = Path(f"tests/files/web/generated/wbv2-compositor-{digest}.blend")
|
||||
batch["fixture"] = fixture.as_posix()
|
||||
if (ROOT / fixture).is_file():
|
||||
return
|
||||
command = ["python3", "my-tools/generate_wbv2_compositor_fixture.py", "--output", fixture.as_posix()]
|
||||
for unit in batch["units"]:
|
||||
command.extend(["--node", unit["identifier"]])
|
||||
result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False)
|
||||
if result.returncode:
|
||||
raise SystemExit(f"native fixture preparation failed for {batch['moduleId']}:\n{result.stdout}{result.stderr}")
|
||||
|
||||
|
||||
def ensure_native_geometry_fixture(batch: dict, contract: dict) -> None:
|
||||
if not batch.get("nativeGeometryFixture"):
|
||||
return
|
||||
digest = contract["batchId"].rsplit(":", 1)[-1]
|
||||
fixture = Path(f"tests/files/web/generated/wbv2-geometry-{digest}.blend")
|
||||
batch["fixture"] = fixture.as_posix()
|
||||
if fixture.is_file() or (ROOT / fixture).is_file():
|
||||
return
|
||||
command = ["python3", "my-tools/generate_wbv2_geometry_fixture.py", "--output", fixture.as_posix()]
|
||||
for unit in batch["units"]:
|
||||
command.extend(["--node", unit["identifier"]])
|
||||
result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False)
|
||||
if result.returncode:
|
||||
raise SystemExit(f"native geometry fixture preparation failed for {batch['moduleId']}:\n{result.stdout}{result.stderr}")
|
||||
|
||||
|
||||
def native_geometry_batch(inventory: dict) -> dict | None:
|
||||
candidates = [
|
||||
unit for unit in inventory.get("units", [])
|
||||
if isinstance(unit, dict)
|
||||
and unit.get("coverageStatus") != "VERIFIED"
|
||||
and unit.get("kind") == "node"
|
||||
and isinstance(unit.get("identifier"), str)
|
||||
and unit["identifier"].startswith("GeometryNode")
|
||||
]
|
||||
if not candidates:
|
||||
return None
|
||||
max_size = 5
|
||||
units = candidates[:max_size]
|
||||
return {
|
||||
"moduleId": "AUTO_GEOMETRY_NATIVE_NODE_READER",
|
||||
"title": "verify auto-generated native geometry node reader batch",
|
||||
"owner": "NODES",
|
||||
"identifiers": [unit["identifier"] for unit in units],
|
||||
"rna": [],
|
||||
"types": [unit["identifier"] for unit in units],
|
||||
"units": units,
|
||||
"candidateUnits": candidates,
|
||||
"nativeGeometryFixture": True,
|
||||
"test": "web/tests/e2e/geometry-node-main-reader.spec.ts",
|
||||
"production": "tests/golden/{task}/geometry-node-reader-production.json",
|
||||
"adaptiveKey": "AUTO_GEOMETRY_NATIVE_NODE_READER:LOCAL_EXACT:NODES",
|
||||
"adaptiveHistory": [],
|
||||
"adaptiveCurrentSize": len(units),
|
||||
"adaptiveNextSize": len(units),
|
||||
"adaptiveMinSize": 1,
|
||||
"adaptiveMaxSize": max_size,
|
||||
}
|
||||
|
||||
|
||||
def recover_blocked_native_batch(current: str, index: dict, plan: dict, inventory: dict) -> bool:
|
||||
"""Shrink one failed auto-native batch to the last proven fixture shape.
|
||||
|
||||
This only applies to the active task's recorded BLOCKED receipt. The
|
||||
failed receipt remains immutable evidence; the task contract is repaired
|
||||
in place so the scheduler can retry the same bounded increment.
|
||||
"""
|
||||
record = next((item for item in plan.get("tasks", []) if item.get("id") == current), None)
|
||||
module = record.get("module") if isinstance(record, dict) else None
|
||||
receipt_path = ROOT / "tests/golden" / current / "module-receipt.json"
|
||||
receipt = read_json(receipt_path) if receipt_path.is_file() else {}
|
||||
if not isinstance(module, dict) or not str(module.get("moduleId", "")).startswith("AUTO_COMPOSITOR_NATIVE_NODE_READER"):
|
||||
return False
|
||||
if receipt.get("status") != "BLOCKED" or len(receipt.get("selectedRecordIds", [])) <= 5:
|
||||
return False
|
||||
by_id = {unit.get("id"): unit for unit in inventory.get("units", []) if isinstance(unit, dict)}
|
||||
selected_ids = list(receipt.get("selectedRecordIds", []))[:5]
|
||||
units = [by_id[unit_id] for unit_id in selected_ids if unit_id in by_id]
|
||||
if len(units) != len(selected_ids):
|
||||
return False
|
||||
batch = {
|
||||
"moduleId": str(module["moduleId"]),
|
||||
"title": "recover native compositor node reader batch",
|
||||
"owner": "NODES",
|
||||
"identifiers": [unit["identifier"] for unit in units],
|
||||
"rna": [],
|
||||
"types": [unit["identifier"] for unit in units],
|
||||
"units": units,
|
||||
"nativeCompositorFixture": True,
|
||||
"test": "web/tests/e2e/compositor-main-reader-production.spec.ts",
|
||||
"production": "tests/golden/{task}/compositor-main-reader-production.json",
|
||||
}
|
||||
contract = batch_contract(current, batch)
|
||||
ensure_native_compositor_fixture(batch, contract)
|
||||
fixture = batch["fixture"]
|
||||
source_paths = [unit["locations"][0]["path"] for unit in units]
|
||||
source_paths += [
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp",
|
||||
"web/tests/e2e/compositor-main-reader-production.spec.ts",
|
||||
fixture,
|
||||
"web/app/src/vendor/blender/web_engine.wasm",
|
||||
]
|
||||
types = json.dumps(batch["types"], separators=(",", ":"))
|
||||
ids = json.dumps(selected_ids, separators=(",", ":"))
|
||||
production = f"tests/golden/{current}/compositor-main-reader-production.json"
|
||||
command = (
|
||||
f"WBV2_BATCH_TASK={current} WBV2_BATCH_PRODUCTION={production} "
|
||||
f"WBV2_BATCH_FIXTURE={fixture} WBV2_BATCH_TYPES='{types}' WBV2_BATCH_UNITS='{ids}' "
|
||||
"WEB_TEST_PORT=5371 npm --prefix web run test:e2e -- --workers=1 tests/e2e/compositor-main-reader-production.spec.ts"
|
||||
)
|
||||
module.update({
|
||||
"batchId": contract["batchId"],
|
||||
"selectedRecordIds": selected_ids,
|
||||
"sourcePaths": source_paths,
|
||||
"positive": [f"{name} type is read from Blender Main" for name in batch["identifiers"]]
|
||||
+ ["shared RNA metadata is preserved", "save/reopen preserves the batch metadata"],
|
||||
"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",
|
||||
command,
|
||||
],
|
||||
"coverageBindings": selected_ids,
|
||||
})
|
||||
record["batchId"] = contract["batchId"]
|
||||
record["selectedRecordIds"] = selected_ids
|
||||
write_json(ROOT / index["source"]["path"], plan)
|
||||
run_node("tools/web/generate-v2-task-index.mjs", "--plan-path", index["source"]["path"])
|
||||
card_path = ROOT / "docs/tasks" / f"{current}.md"
|
||||
write_text(card_path, make_card(current, record.get("dependsOn", [""])[-1], "in_progress", batch, f"node tools/web/run-wbv2-module-task.mjs --task {current} --write", contract))
|
||||
run_node("tools/web/check-task-context.mjs", "--task", current, "--write")
|
||||
print(f"wbv2-blocked-batch-recovered task={current} batchId={contract['batchId']} selected={len(selected_ids)}")
|
||||
return True
|
||||
|
||||
|
||||
def discover_existing_evidence_batch(inventory: dict) -> dict | None:
|
||||
"""Find exact Chromium receipts that list currently-unbound records.
|
||||
|
||||
@@ -588,6 +814,12 @@ def select_batch(inventory: dict) -> dict:
|
||||
discovered = discover_existing_evidence_batch(inventory)
|
||||
if discovered:
|
||||
return discovered
|
||||
native = native_compositor_batch(inventory)
|
||||
if native:
|
||||
return native
|
||||
geometry = native_geometry_batch(inventory)
|
||||
if geometry:
|
||||
return geometry
|
||||
summary = pending_summary(inventory)
|
||||
write_planner_status({
|
||||
"status": "waiting_for_exact_evidence",
|
||||
@@ -609,15 +841,27 @@ def main() -> int:
|
||||
index = read_json(INDEX)
|
||||
plan_path = ROOT / index["source"]["path"]
|
||||
plan = json.loads(plan_path.read_text(encoding="utf-8"))
|
||||
inventory = read_json(CONTROL_ROOT / "dynamic-inventory.json")
|
||||
if recover_blocked_native_batch(current, index, plan, inventory):
|
||||
return 0
|
||||
terminal = current_manifest.get("status") == "done" and current_manifest.get("nextTask") is None and index.get("activeTask") is None
|
||||
if not terminal and not current_manifest.get("nextTask"):
|
||||
raise SystemExit(f"current task is not terminal: {current}")
|
||||
phase = max(int(item["id"].split("-")[1][1:]) for item in plan["tasks"]) + 1
|
||||
root_task, module_task = f"WBV2-P{phase}-000", f"WBV2-P{phase}-001"
|
||||
inventory = read_json(CONTROL_ROOT / "dynamic-inventory.json")
|
||||
batch = select_batch(inventory)
|
||||
contract = batch_contract(module_task, batch)
|
||||
batch["batchId"] = contract["batchId"]
|
||||
if batch.get("nativeCompositorFixture"):
|
||||
digest = contract["batchId"].rsplit(":", 1)[-1]
|
||||
batch["fixture"] = f"tests/files/web/generated/wbv2-compositor-{digest}.blend"
|
||||
if args.write:
|
||||
ensure_native_compositor_fixture(batch, contract)
|
||||
if batch.get("nativeGeometryFixture"):
|
||||
digest = contract["batchId"].rsplit(":", 1)[-1]
|
||||
batch["fixture"] = f"tests/files/web/generated/wbv2-geometry-{digest}.blend"
|
||||
if args.write:
|
||||
ensure_native_geometry_fixture(batch, contract)
|
||||
write_planner_status({
|
||||
"status": "ready",
|
||||
"reason": "source_backed_batch_selected",
|
||||
|
||||
65
my-tools/generate_wbv2_compositor_fixture.py
Normal file
65
my-tools/generate_wbv2_compositor_fixture.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a minimal native Blender compositor fixture for one WBV2 batch."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
NATIVE_BLENDER = ROOT / "build_blender_native/bin/blender"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", required=True, help="repository-relative .blend output under tests/files/web")
|
||||
parser.add_argument("--node", action="append", required=True, help="Blender CompositorNode type identifier")
|
||||
args = parser.parse_args()
|
||||
|
||||
output = Path(args.output)
|
||||
if output.is_absolute() or ".." in output.parts or output.suffix != ".blend" or not str(output).startswith("tests/files/web/"):
|
||||
parser.error("--output must be a repository-relative .blend path under tests/files/web/")
|
||||
nodes = list(dict.fromkeys(args.node))
|
||||
if not nodes or any(not node.startswith("CompositorNode") or not node.replace("_", "").isalnum() for node in nodes):
|
||||
parser.error("every --node must be a Blender CompositorNode identifier")
|
||||
if not NATIVE_BLENDER.is_file():
|
||||
raise SystemExit(f"native Blender fixture generator is unavailable: {NATIVE_BLENDER.relative_to(ROOT)}")
|
||||
|
||||
destination = ROOT / output
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
script = (
|
||||
"import bpy\n"
|
||||
f"destination = {str(destination)!r}\n"
|
||||
f"node_types = {nodes!r}\n"
|
||||
"scene = bpy.context.scene\n"
|
||||
"tree = bpy.data.node_groups.new('WBV2 Compositor Native Batch', 'CompositorNodeTree')\n"
|
||||
"scene.compositing_node_group = tree\n"
|
||||
"tree.interface.new_socket(name='Image', in_out='OUTPUT', socket_type='NodeSocketColor')\n"
|
||||
"source = tree.nodes.new('CompositorNodeRGB')\n"
|
||||
"source.name = 'WBV2 Batch Source'\n"
|
||||
"output = tree.nodes.new('NodeGroupOutput')\n"
|
||||
"output.name = 'WBV2 Batch Output'\n"
|
||||
"tree.links.new(source.outputs[0], output.inputs[0])\n"
|
||||
"for node_type in node_types:\n"
|
||||
" node = tree.nodes.new(node_type)\n"
|
||||
" node.name = 'WBV2 ' + node_type\n"
|
||||
"bpy.ops.wm.save_as_mainfile(filepath=destination)\n"
|
||||
)
|
||||
result = subprocess.run(
|
||||
[str(NATIVE_BLENDER), "--background", "--factory-startup", "--python-expr", script],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode or not destination.is_file():
|
||||
raise SystemExit(f"native compositor fixture generation failed ({result.returncode}):\n{result.stdout}{result.stderr}")
|
||||
print(json.dumps({"status": "PASS", "fixture": output.as_posix(), "nodes": nodes, "bytes": destination.stat().st_size}))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
69
my-tools/generate_wbv2_geometry_fixture.py
Normal file
69
my-tools/generate_wbv2_geometry_fixture.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a native Blender GeometryNodeTree fixture for one WBV2 batch."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
NATIVE_BLENDER = ROOT / "build_blender_native/bin/blender"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", required=True)
|
||||
parser.add_argument("--node", action="append", required=True)
|
||||
args = parser.parse_args()
|
||||
output = Path(args.output)
|
||||
if output.is_absolute() or ".." in output.parts or output.suffix != ".blend" or not str(output).startswith("tests/files/web/"):
|
||||
parser.error("--output must be a repository-relative .blend path under tests/files/web/")
|
||||
nodes = list(dict.fromkeys(args.node))
|
||||
if not nodes or any(not node.startswith("GeometryNode") or not node.replace("_", "").isalnum() for node in nodes):
|
||||
parser.error("every --node must be a Blender GeometryNode identifier")
|
||||
if not NATIVE_BLENDER.is_file():
|
||||
raise SystemExit(f"native Blender fixture generator is unavailable: {NATIVE_BLENDER.relative_to(ROOT)}")
|
||||
destination = ROOT / output
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
script = (
|
||||
"import bpy\n"
|
||||
f"destination = {str(destination)!r}\n"
|
||||
f"node_types = {nodes!r}\n"
|
||||
"mesh = bpy.data.meshes.new('WBV2 Geometry Mesh')\n"
|
||||
"mesh.from_pydata([(-1,-1,0),(1,-1,0),(0,1,0)], [], [(0,1,2)])\n"
|
||||
"obj = bpy.data.objects.new('WBV2 Geometry Object', mesh)\n"
|
||||
"bpy.context.collection.objects.link(obj)\n"
|
||||
"modifier = obj.modifiers.new('WBV2 Geometry Nodes', 'NODES')\n"
|
||||
"tree = bpy.data.node_groups.new('WBV2 Geometry Batch', 'GeometryNodeTree')\n"
|
||||
"modifier.node_group = tree\n"
|
||||
"tree.interface.new_socket(name='Geometry', in_out='INPUT', socket_type='NodeSocketGeometry')\n"
|
||||
"tree.interface.new_socket(name='Geometry', in_out='OUTPUT', socket_type='NodeSocketGeometry')\n"
|
||||
"input_node = tree.nodes.new('NodeGroupInput')\n"
|
||||
"output_node = tree.nodes.new('NodeGroupOutput')\n"
|
||||
"previous = input_node\n"
|
||||
"for node_type in node_types:\n"
|
||||
" node = tree.nodes.new(node_type)\n"
|
||||
" node.name = 'WBV2 ' + node_type\n"
|
||||
" geometry_input = next((socket for socket in node.inputs if socket.type == 'GEOMETRY'), None)\n"
|
||||
" geometry_output = next((socket for socket in node.outputs if socket.type == 'GEOMETRY'), None)\n"
|
||||
" previous_output = next((socket for socket in previous.outputs if socket.type == 'GEOMETRY'), None)\n"
|
||||
" if geometry_input and previous_output: tree.links.new(previous_output, geometry_input)\n"
|
||||
" previous = node\n"
|
||||
"previous_output = next((socket for socket in previous.outputs if socket.type == 'GEOMETRY'), None)\n"
|
||||
"if previous_output: tree.links.new(previous_output, output_node.inputs['Geometry'])\n"
|
||||
"bpy.context.view_layer.objects.active = obj\n"
|
||||
"obj.select_set(True)\n"
|
||||
"bpy.ops.wm.save_as_mainfile(filepath=destination)\n"
|
||||
)
|
||||
result = subprocess.run([str(NATIVE_BLENDER), "--background", "--factory-startup", "--python-expr", script], cwd=ROOT, text=True, capture_output=True, check=False)
|
||||
if result.returncode or not destination.is_file():
|
||||
raise SystemExit(f"native geometry fixture generation failed ({result.returncode}):\n{result.stdout}{result.stderr}")
|
||||
print(json.dumps({"status": "PASS", "fixture": output.as_posix(), "nodes": nodes, "bytes": destination.stat().st_size}))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -197,8 +197,12 @@ def main() -> int:
|
||||
"authorizationLog": str(AUTHORIZATION_LOG.relative_to(ROOT)),
|
||||
},
|
||||
)
|
||||
# Keep the planner heartbeat responsive while no exact
|
||||
# source-backed batch is available. Each short cycle
|
||||
# retries authorization and records the current pending
|
||||
# inventory instead of entering a long silent sleep.
|
||||
child = None
|
||||
time.sleep(max(args.interval, 60.0))
|
||||
time.sleep(args.interval)
|
||||
continue
|
||||
return 0 if reason == "no_configured_unresolved_source_batch" else 2
|
||||
if terminal_pointer(pointer) or (applies and state in {"complete", "blocked", "stopped"}):
|
||||
|
||||
Reference in New Issue
Block a user