feat(web): reuse verified batches in unattended flow
This commit is contained in:
@@ -299,11 +299,13 @@ def make_card(task: str, parent: str, status: str, batch: dict, entrypoint: str)
|
||||
|
||||
|
||||
def select_batch(inventory: dict) -> dict:
|
||||
units = {unit.get("identifier"): unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED"}
|
||||
units = {unit.get("identifier"): unit for unit in inventory.get("units", []) if isinstance(unit, dict)}
|
||||
for candidate in PREFERRED_BATCHES:
|
||||
selected = [units.get(name) for name in candidate["identifiers"] + candidate["rna"]]
|
||||
if all(selected):
|
||||
return {**candidate, "units": selected}
|
||||
unresolved = [unit for unit in selected if unit.get("coverageStatus") != "VERIFIED"]
|
||||
if unresolved:
|
||||
return {**candidate, "units": unresolved, "candidateUnits": selected}
|
||||
raise SystemExit("no configured unresolved Blender source batch remains")
|
||||
|
||||
|
||||
@@ -357,7 +359,7 @@ def main() -> int:
|
||||
write_text(root_card_path, make_card(root_task, current, "done", batch, f"python3 my-tools/authorize_next_wbv2_task.py --write"))
|
||||
write_text(module_card_path, make_card(module_task, root_task, "in_progress", batch, f"node tools/web/run-wbv2-module-task.mjs --task {module_task} --write"))
|
||||
root_report_path, root_status_path, root_manifest_path = root_out / "successor-authorization-report.json", ROOT / "docs/status" / f"{root_task}.md", root_out / "manifest.json"
|
||||
write_json(root_report_path, {"schemaVersion": 1, "task": root_task, "operation": "AUTHORIZE_SOURCE_BACKED_MODULE_BATCH", "taskClass": "QUEUE_ENABLEMENT", "status": "PASS", "parent": {"task": current, "manifest": rel(current_manifest_path), "manifestSha256": sha(current_manifest_path)}, "module": {"moduleId": batch["moduleId"], "task": module_task, "batchSize": len(batch["units"]), "sourceUnits": [unit["id"] for unit in batch["units"]], "sourceBacked": True}, "inventory": {"unitCount": inventory["summary"]["unitCount"], "verifiedUnits": inventory["coverage"]["verifiedUnits"], "unverifiedUnits": inventory["coverage"]["unverifiedUnits"], "sourceTreeSha256": inventory["authority"]["sourceTreeSha256"], "boundUnitDelta": 0}, "nextTask": module_task})
|
||||
write_json(root_report_path, {"schemaVersion": 1, "task": root_task, "operation": "AUTHORIZE_SOURCE_BACKED_MODULE_BATCH", "taskClass": "QUEUE_ENABLEMENT", "status": "PASS", "parent": {"task": current, "manifest": rel(current_manifest_path), "manifestSha256": sha(current_manifest_path)}, "module": {"moduleId": batch["moduleId"], "task": module_task, "batchSize": len(batch["units"]), "candidateBatchSize": len(batch.get("candidateUnits", batch["units"])), "sourceUnits": [unit["id"] for unit in batch["units"]], "sourceBacked": True}, "inventory": {"unitCount": inventory["summary"]["unitCount"], "verifiedUnits": inventory["coverage"]["verifiedUnits"], "unverifiedUnits": inventory["coverage"]["unverifiedUnits"], "sourceTreeSha256": inventory["authority"]["sourceTreeSha256"], "boundUnitDelta": 0}, "efficiency": {"existingBindingsReused": len(batch.get("candidateUnits", batch["units"])) - len(batch["units"]), "selectionPolicy": "all-candidate-units-then-unresolved-only"}, "nextTask": module_task})
|
||||
write_text(root_status_path, f"# {root_task} status\n\n- task: `{root_task}`; parent: `{current}`; status: `done`\n- taskClass: `QUEUE_ENABLEMENT`; no modifier-family parity completion claimed\n- source-backed batch authorized: `{batch['moduleId']}` ({len(batch['units'])} units)\n- dynamic inventory: `{inventory['summary']['unitCount']}` units; verified `{inventory['coverage']['verifiedUnits']}`; unverified `{inventory['coverage']['unverifiedUnits']}`; bound delta `0`\n- nextTask: `{module_task}`\n")
|
||||
write_json(root_manifest_path, {"schemaVersion": 1, "task": root_task, "parentTask": current, "operation": "AUTHORIZE_SOURCE_BACKED_MODULE_BATCH", "status": "done", "enablingTask": True, "parityStateChange": False, "runtime": "MODULE_EXECUTION_QUEUE_BRIDGE", "artifacts": {"parentManifest": {"path": rel(current_manifest_path), "sha256": sha(current_manifest_path)}, "plan": {"path": rel(root_plan_path), "sha256": sha(root_plan_path)}, "report": {"path": rel(root_report_path), "sha256": sha(root_report_path)}, "status": {"path": rel(root_status_path), "sha256": sha(root_status_path)}}, "assertions": {"sourceBackedSuccessor": True, "moduleId": batch["moduleId"], "batchSize": len(batch["units"]), "inventoryTotal": 6900, "effectiveCompleted": 290, "effectivePending": 6609, "dynamicUnitCount": inventory["summary"]["unitCount"], "dynamicVerifiedUnits": inventory["coverage"]["verifiedUnits"], "dynamicUnverifiedUnits": inventory["coverage"]["unverifiedUnits"], "boundUnitDelta": 0, "fullHistoricalReproduction": False}, "nextTask": module_task})
|
||||
|
||||
@@ -377,7 +379,7 @@ def main() -> int:
|
||||
# wastes budget and can fail solely because the parent manifest grew; only
|
||||
# the active implementation batch needs a packet.
|
||||
run_node("tools/web/check-task-context.mjs", "--task", module_task, "--write")
|
||||
print(f"wbv2-successor-authorized root={root_task} next={module_task} module={batch['moduleId']} batchSize={len(batch['units'])} inventoryDelta=0")
|
||||
print(f"wbv2-successor-authorized root={root_task} next={module_task} module={batch['moduleId']} batchSize={len(batch['units'])} reused={len(batch.get('candidateUnits', batch['units'])) - len(batch['units'])} inventoryDelta=0")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ SCHEDULER_POLL_MS="${WBV2_POLL_MS:-1000}"
|
||||
SCHEDULER_PUSH_RETRIES="${WBV2_PUSH_RETRIES:-6}"
|
||||
WATCHDOG_INTERVAL="${WBV2_WATCHDOG_INTERVAL:-10}"
|
||||
WATCHDOG_RESTART_DELAY="${WBV2_WATCHDOG_RESTART_DELAY:-5}"
|
||||
AUTO_AUTHORIZE="${WBV2_AUTO_AUTHORIZE:-1}"
|
||||
|
||||
# Keep repository evidence on disk while placing compiler/transient caches in
|
||||
# RAM. Eight compile jobs is the repository's stability limit.
|
||||
@@ -44,6 +45,10 @@ start_scheduler() {
|
||||
|
||||
start_watchdog() {
|
||||
local pid_file="$CONTROL_DIR/my-tools-watchdog.pid"
|
||||
local auto_authorize_args=()
|
||||
if test "$AUTO_AUTHORIZE" != "0"; then
|
||||
auto_authorize_args+=(--auto-authorize)
|
||||
fi
|
||||
if test -f "$pid_file"; then
|
||||
local watchdog_pid
|
||||
watchdog_pid="$(tr -d '[:space:]' < "$pid_file")"
|
||||
@@ -58,6 +63,7 @@ start_watchdog() {
|
||||
--restart-delay "$WATCHDOG_RESTART_DELAY" \
|
||||
--max-iterations "$SCHEDULER_MAX_ITERATIONS" \
|
||||
--push-retries "$SCHEDULER_PUSH_RETRIES" \
|
||||
"${auto_authorize_args[@]}" \
|
||||
--commit --push \
|
||||
> "$CONTROL_DIR/my-tools-watchdog.stdout.log" 2>&1 < /dev/null &
|
||||
printf 'watchdog launch pid: %s\n' "$!"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Watch and safely restart the Blender/WBV2 scheduler process.
|
||||
"""Watch, authorize, and safely restart the Blender/WBV2 scheduler process.
|
||||
|
||||
The watchdog only restarts an unexpectedly missing scheduler. It never edits
|
||||
the queue, claims tasks, retries blocked checkpoints, or deletes evidence.
|
||||
The watchdog delegates terminal queue advancement to the source-backed
|
||||
authorizer. It never edits the queue directly, retries blocked checkpoints, or
|
||||
deletes evidence.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -22,6 +23,7 @@ from wbv2_runtime import ROOT, SCHEDULER_CHECKPOINT, SCHEDULER_LOCK, lock_owner,
|
||||
WATCHDOG_LOCK = ROOT / "tests/golden/WBV2/my-tools-watchdog.lock"
|
||||
WATCHDOG_PID = ROOT / "tests/golden/WBV2/my-tools-watchdog.pid"
|
||||
WATCHDOG_STATUS = ROOT / "tests/golden/WBV2/my-tools-watchdog.json"
|
||||
AUTHORIZATION_LOG = ROOT / "tests/golden/WBV2/my-tools-authorization.log"
|
||||
|
||||
|
||||
def now() -> str:
|
||||
@@ -81,6 +83,24 @@ def checkpoint_task(value: dict) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def authorize_terminal_queue() -> tuple[bool, str]:
|
||||
"""Ask the repository authorizer for one source-backed successor batch."""
|
||||
result = subprocess.run(
|
||||
["python3", str(ROOT / "my-tools/authorize_next_wbv2_task.py"), "--write"],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
AUTHORIZATION_LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||
AUTHORIZATION_LOG.write_text(
|
||||
f"$ python3 my-tools/authorize_next_wbv2_task.py --write\n{result.stdout}{result.stderr}",
|
||||
encoding="utf-8",
|
||||
)
|
||||
output = f"{result.stdout}{result.stderr}".strip()
|
||||
return result.returncode == 0, output
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--interval", type=float, default=10.0)
|
||||
@@ -90,6 +110,8 @@ def main() -> int:
|
||||
parser.add_argument("--commit", action="store_true", default=os.getenv("WBV2_AUTO_COMMIT", "1") != "0")
|
||||
parser.add_argument("--push", action="store_true", default=os.getenv("WBV2_AUTO_PUSH", "1") != "0")
|
||||
parser.add_argument("--push-retries", type=int, default=6)
|
||||
parser.add_argument("--auto-authorize", dest="auto_authorize", action="store_true", default=os.getenv("WBV2_AUTO_AUTHORIZE", "1") != "0", help="authorize a source-backed successor when the queue reaches a terminal task")
|
||||
parser.add_argument("--no-auto-authorize", dest="auto_authorize", action="store_false")
|
||||
args = parser.parse_args()
|
||||
if args.interval <= 0 or args.restart_delay < 0 or args.max_restarts < 0 or args.max_iterations < 0:
|
||||
parser.error("interval/delay/limits must be non-negative; interval must be positive")
|
||||
@@ -121,6 +143,38 @@ def main() -> int:
|
||||
saved_checkpoint = checkpoint()
|
||||
state = saved_checkpoint.get("state")
|
||||
applies = checkpoint_task(saved_checkpoint) == pointer.get("task")
|
||||
if terminal_pointer(pointer) and args.auto_authorize:
|
||||
authorized, output = authorize_terminal_queue()
|
||||
if authorized:
|
||||
write_json(
|
||||
WATCHDOG_STATUS,
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"status": "authorized",
|
||||
"reason": "source_backed_successor",
|
||||
"task": pointer.get("task"),
|
||||
"updatedAt": now(),
|
||||
"restartCount": restarts,
|
||||
"authorizationLog": str(AUTHORIZATION_LOG.relative_to(ROOT)),
|
||||
},
|
||||
)
|
||||
child = None
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
reason = "no_configured_unresolved_source_batch" if "no configured unresolved Blender source batch remains" in output else "successor_authorization_failed"
|
||||
write_json(
|
||||
WATCHDOG_STATUS,
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"status": "stopped" if reason == "no_configured_unresolved_source_batch" else "blocked",
|
||||
"reason": reason,
|
||||
"task": pointer.get("task"),
|
||||
"updatedAt": now(),
|
||||
"restartCount": restarts,
|
||||
"authorizationLog": str(AUTHORIZATION_LOG.relative_to(ROOT)),
|
||||
},
|
||||
)
|
||||
return 0 if reason == "no_configured_unresolved_source_batch" else 2
|
||||
if terminal_pointer(pointer) or (applies and state in {"complete", "blocked", "stopped"}):
|
||||
write_json(
|
||||
WATCHDOG_STATUS,
|
||||
|
||||
Reference in New Issue
Block a user