feat(web): keep unattended planner heartbeat active
This commit is contained in:
@@ -6,4 +6,4 @@
|
||||
- positive Main mutation, negative no-mutation, recovery and Chromium gates are recorded in the module receipt
|
||||
- dynamic source inventory: `3745` units; verified `523`; unverified `3222`; planning estimate `5200` (non-binding); this-segment binding delta: `6`
|
||||
- historical 6900-entry inventory remains read-only; full-family parity: `false`
|
||||
- nextTask: `NONE`
|
||||
- nextTask: `WBV2-P1162-000`
|
||||
|
||||
@@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
@@ -19,6 +20,9 @@ from pathlib import Path
|
||||
from wbv2_runtime import CONTROL_ROOT, INDEX, QUEUE, ROOT, read_json, terminal_pointer
|
||||
|
||||
|
||||
AUTO_PLANNER_STATUS = CONTROL_ROOT / "auto-planner.json"
|
||||
|
||||
|
||||
PREFERRED_BATCHES = [
|
||||
{
|
||||
"moduleId": "MODIFIER_READER_BATCH",
|
||||
@@ -130,6 +134,17 @@ PREFERRED_BATCHES = [
|
||||
"production": "tests/golden/{task}/shape-key-reader-production.json",
|
||||
"types": [],
|
||||
},
|
||||
{
|
||||
"moduleId": "MODIFIER_SHAPEKEY_READER_BATCH",
|
||||
"title": "verify shape key modifier reader dependency",
|
||||
"owner": "MODIFIER",
|
||||
"identifiers": ["MOD_shapekey"],
|
||||
"rna": ["ShapeKeyPoint"],
|
||||
"fixture": "tests/files/web/rigged_shape_scene.blend",
|
||||
"test": "web/tests/e2e/wbv2-shape-key-reader-production.spec.ts",
|
||||
"production": "tests/golden/{task}/shape-key-reader-production.json",
|
||||
"types": [],
|
||||
},
|
||||
{
|
||||
"moduleId": "LINE_STYLE_READER_BATCH",
|
||||
"title": "verify line style reader batch",
|
||||
@@ -267,6 +282,24 @@ def write_text(path: Path, value: str) -> None:
|
||||
path.write_text(value, encoding="utf-8")
|
||||
|
||||
|
||||
def write_planner_status(payload: dict) -> None:
|
||||
"""Persist a deduplicated planner heartbeat for unattended observability."""
|
||||
value = {"schemaVersion": 1, **payload}
|
||||
try:
|
||||
current = read_json(AUTO_PLANNER_STATUS)
|
||||
stable_current = {key: item for key, item in current.items() if key != "updatedAt"}
|
||||
stable_value = {key: item for key, item in value.items() if key != "updatedAt"}
|
||||
if stable_current == stable_value:
|
||||
return
|
||||
except (OSError, AttributeError):
|
||||
pass
|
||||
value["updatedAt"] = datetime.now(timezone.utc).isoformat()
|
||||
AUTO_PLANNER_STATUS.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = AUTO_PLANNER_STATUS.with_suffix(AUTO_PLANNER_STATUS.suffix + f".tmp-{os.getpid()}")
|
||||
temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
temporary.replace(AUTO_PLANNER_STATUS)
|
||||
|
||||
|
||||
def rel(path: Path) -> str:
|
||||
return path.relative_to(ROOT).as_posix()
|
||||
|
||||
@@ -331,6 +364,16 @@ def discover_existing_evidence_batch(inventory: dict) -> dict | None:
|
||||
}
|
||||
|
||||
|
||||
def pending_summary(inventory: dict) -> dict:
|
||||
pending = [unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED"]
|
||||
by_kind: dict[str, int] = {}
|
||||
by_family: dict[str, int] = {}
|
||||
for unit in pending:
|
||||
by_kind[unit.get("kind", "UNKNOWN")] = by_kind.get(unit.get("kind", "UNKNOWN"), 0) + 1
|
||||
by_family[unit.get("family", "UNKNOWN")] = by_family.get(unit.get("family", "UNKNOWN"), 0) + 1
|
||||
return {"count": len(pending), "byKind": by_kind, "byFamily": by_family, "sample": [unit.get("id") for unit in pending[:12]]}
|
||||
|
||||
|
||||
def run_node(*args: str) -> None:
|
||||
result = subprocess.run(["node", *args], cwd=ROOT, text=True, capture_output=True, check=False)
|
||||
output = result.stdout or result.stderr
|
||||
@@ -545,7 +588,14 @@ def select_batch(inventory: dict) -> dict:
|
||||
discovered = discover_existing_evidence_batch(inventory)
|
||||
if discovered:
|
||||
return discovered
|
||||
raise SystemExit("no configured unresolved Blender source batch remains")
|
||||
summary = pending_summary(inventory)
|
||||
write_planner_status({
|
||||
"status": "waiting_for_exact_evidence",
|
||||
"reason": "no_configured_unresolved_source_batch",
|
||||
"pending": summary,
|
||||
"nextAction": "add a real fixture/adapter or exact Chromium receipt; do not fabricate coverage",
|
||||
})
|
||||
raise SystemExit(f"no configured unresolved Blender source batch remains; pending={summary['count']} sample={summary['sample']}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -568,6 +618,15 @@ def main() -> int:
|
||||
batch = select_batch(inventory)
|
||||
contract = batch_contract(module_task, batch)
|
||||
batch["batchId"] = contract["batchId"]
|
||||
write_planner_status({
|
||||
"status": "ready",
|
||||
"reason": "source_backed_batch_selected",
|
||||
"task": module_task,
|
||||
"moduleId": batch["moduleId"],
|
||||
"batchId": contract["batchId"],
|
||||
"selectedRecordIds": contract["selectedRecordIds"],
|
||||
"nextAction": "scheduler runs the focused module gate",
|
||||
})
|
||||
if not args.write:
|
||||
print(json.dumps({"status": "ready", "rootTask": root_task, "nextTask": module_task, "moduleId": batch["moduleId"], "batchId": contract["batchId"], "batchSize": len(batch["units"]), "selectedRecordIds": contract["selectedRecordIds"], "sourceUnits": [unit["id"] for unit in batch["units"]], "adaptive": {"key": batch.get("adaptiveKey"), "history": batch.get("adaptiveHistory", []), "nextSize": batch.get("adaptiveNextSize", len(batch["units"]))}}, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
10
nextTask.md
10
nextTask.md
@@ -467,12 +467,12 @@ Recorded: 2026-08-29 Asia/Shanghai
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Current task when this file was written | `WBV2-P1161-001` |
|
||||
| Current task state | `done` |
|
||||
| Parent manifest | `tests/golden/WBV2-P1161-001/manifest.json` |
|
||||
| Current task card | `docs/tasks/WBV2-P1161-001.md` |
|
||||
| Current task when this file was written | `WBV2-P1162-001` |
|
||||
| Current task state | `in_progress` |
|
||||
| Parent manifest | `tests/golden/WBV2-P1162-000/manifest.json` |
|
||||
| Current task card | `docs/tasks/WBV2-P1162-001.md` |
|
||||
| Current task-context nextTask | `NONE` |
|
||||
| Focused command | `node tools/web/check-wbv2-module-execution.mjs` |
|
||||
| Focused command | `node tools/web/run-wbv2-module-task.mjs --task WBV2-P1162-001 --write` |
|
||||
|
||||
The snapshot is informational and may become stale. On every resume, the queue,
|
||||
parent manifest, and fresh `print-task-context` output win. Never infer a task
|
||||
|
||||
Reference in New Issue
Block a user