perf(web): add idempotent batch verification fast paths
This commit is contained in:
@@ -13,6 +13,8 @@ commit and push.
|
||||
a missing scheduler and stops on a terminal or blocked checkpoint.
|
||||
- `show_blender_web_scheduler.py`: delegates live RUNNING/IDLE/BLOCKED state to
|
||||
`tools/web/print-wbv2-live-status.mjs`.
|
||||
- `validate_incremental_context.py`: validates only the active batch metadata;
|
||||
unchanged inputs return `incremental-context-ok ... context=REUSED`.
|
||||
- `start_unattended.sh`: launches the scheduler and watchdog with `nohup` and
|
||||
`setsid` for process-level continuity.
|
||||
- `check_continuation_state.py` and `validate_web_context.py`: read-only WBV2
|
||||
@@ -60,6 +62,12 @@ For machine-readable live state:
|
||||
python3 my-tools/show_blender_web_scheduler.py --json
|
||||
```
|
||||
|
||||
Implementation cycles use a bounded fast path: each module manifest carries a
|
||||
stable `batchId` and selected record IDs, context metadata compares
|
||||
`bytes`/`mtime_ns` before hashing, and the outer scheduler owns planner and
|
||||
continuation rotation. Full API, format, Chromium, performance and release
|
||||
gates remain an explicit final-audit operation.
|
||||
|
||||
`--commit --push` is enabled by the launcher and can be disabled for a local
|
||||
dry run with `WBV2_AUTO_COMMIT=0 WBV2_AUTO_PUSH=0`.
|
||||
|
||||
|
||||
@@ -168,6 +168,17 @@ PREFERRED_BATCHES = [
|
||||
"production": "tests/golden/{task}/line-style-reader-production.json",
|
||||
"types": [],
|
||||
},
|
||||
{
|
||||
"moduleId": "FCURVE_MODIFIER_SUBTYPE_READER_BATCH",
|
||||
"title": "verify FCurve modifier subtype reader batch",
|
||||
"owner": "ANIMATION",
|
||||
"identifiers": [],
|
||||
"rna": ["FModifierEnvelope", "FModifierGenerator", "FModifierLimits", "FModifierNoise", "FModifierSmooth", "FModifierStepped"],
|
||||
"fixture": "tests/files/web/fcurve_modifier_subtype_batch_scene.blend",
|
||||
"test": "web/tests/e2e/wbv2-fcurve-modifier-production.spec.ts",
|
||||
"production": "tests/golden/{task}/fcurve-modifier-production.json",
|
||||
"types": [],
|
||||
},
|
||||
{
|
||||
"moduleId": "MODIFIER_SOURCE_DEPENDENCY_REUSE",
|
||||
"title": "reuse verified modifier receipts for shared source utilities",
|
||||
@@ -260,6 +271,12 @@ def rel(path: Path) -> str:
|
||||
return path.relative_to(ROOT).as_posix()
|
||||
|
||||
|
||||
def batch_contract(task: str, batch: dict) -> dict:
|
||||
selected = [unit["id"] for unit in batch.get("units", [])]
|
||||
digest = hashlib.sha256(json.dumps(selected, ensure_ascii=False, separators=(",", ":")).encode()).hexdigest()[:16]
|
||||
return {"batchId": batch.get("batchId") or f"{task}:{batch['moduleId']}:{digest}", "selectedRecordIds": selected}
|
||||
|
||||
|
||||
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
|
||||
@@ -298,7 +315,7 @@ def snapshot(text: str, task: str, parent: str, next_task: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def make_card(task: str, parent: str, status: str, batch: dict, entrypoint: str) -> str:
|
||||
def make_card(task: str, parent: str, status: str, batch: dict, entrypoint: str, contract: dict) -> str:
|
||||
fixture_paths = batch.get("fixtures") or ([batch["fixture"]] if batch.get("fixture") else [])
|
||||
fixture_text = ", ".join(f"`{fixture}`" for fixture in fixture_paths)
|
||||
if not fixture_text:
|
||||
@@ -312,6 +329,9 @@ def make_card(task: str, parent: str, status: str, batch: dict, entrypoint: str)
|
||||
- gap: capability:{batch['moduleId']}
|
||||
- ownerFamily: {batch['owner']}
|
||||
- targetImplementationClass: QUEUE_ONLY
|
||||
- batchId: {contract['batchId']}
|
||||
- selectedRecordIds: {json.dumps(contract['selectedRecordIds'], ensure_ascii=False, separators=(',', ':'))}
|
||||
- commandBudget: max=48; read/exploration max=16
|
||||
- Implementation entrypoint: `{entrypoint}`
|
||||
|
||||
## 目标
|
||||
@@ -345,6 +365,9 @@ def make_card(task: str, parent: str, status: str, batch: dict, entrypoint: str)
|
||||
- gap: capability:{batch['moduleId']}
|
||||
- ownerFamily: {batch['owner']}
|
||||
- targetImplementationClass: LOCAL_EXACT
|
||||
- batchId: {contract['batchId']}
|
||||
- selectedRecordIds: {json.dumps(contract['selectedRecordIds'], ensure_ascii=False, separators=(',', ':'))}
|
||||
- commandBudget: max=48; read/exploration max=16
|
||||
- Implementation entrypoint: `{entrypoint}`
|
||||
|
||||
## 目标
|
||||
@@ -388,6 +411,9 @@ def make_card(task: str, parent: str, status: str, batch: dict, entrypoint: str)
|
||||
- gap: capability:{batch['moduleId']}
|
||||
- ownerFamily: {batch['owner']}
|
||||
- targetImplementationClass: LOCAL_EXACT
|
||||
- batchId: {contract['batchId']}
|
||||
- selectedRecordIds: {json.dumps(contract['selectedRecordIds'], ensure_ascii=False, separators=(',', ':'))}
|
||||
- commandBudget: max=48; read/exploration max=16
|
||||
- Implementation entrypoint: `{entrypoint}`
|
||||
|
||||
## 目标
|
||||
@@ -435,7 +461,33 @@ def select_batch(inventory: dict) -> dict:
|
||||
if all(selected):
|
||||
unresolved = [unit for unit in selected if unit.get("coverageStatus") != "VERIFIED"]
|
||||
if unresolved:
|
||||
return {**candidate, "units": unresolved, "candidateUnits": selected}
|
||||
key = f"{candidate['moduleId']}:LOCAL_EXACT:{candidate['owner']}"
|
||||
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") != candidate["moduleId"]:
|
||||
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:]
|
||||
min_size = 1
|
||||
max_size = 8 if len(unresolved) > 16 or len(candidate.get("fixtures", [])) > 1 else 16
|
||||
current_size = min(len(unresolved), max_size)
|
||||
if recent and any(item["timeout"] or item["durationMs"] > 900_000 or item["commands"] > 48 for item in recent):
|
||||
next_size = max(min_size, 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
|
||||
chosen = unresolved[:max(min_size, min(max_size, len(unresolved), next_size))]
|
||||
return {**candidate, "units": chosen, "candidateUnits": selected, "adaptiveKey": key, "adaptiveHistory": recent, "adaptiveCurrentSize": current_size, "adaptiveNextSize": len(chosen), "adaptiveMinSize": min_size, "adaptiveMaxSize": max_size}
|
||||
raise SystemExit("no configured unresolved Blender source batch remains")
|
||||
|
||||
|
||||
@@ -457,8 +509,10 @@ def main() -> int:
|
||||
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 not args.write:
|
||||
print(json.dumps({"status": "ready", "rootTask": root_task, "nextTask": module_task, "moduleId": batch["moduleId"], "batchSize": len(batch["units"]), "sourceUnits": [unit["id"] for unit in batch["units"]]}, ensure_ascii=False))
|
||||
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
|
||||
|
||||
current_status = ROOT / "docs/status" / f"{current}.md"
|
||||
@@ -497,19 +551,20 @@ def main() -> int:
|
||||
f"WBV2_BATCH_TASK={module_task} {production_env} {fixture_env} WBV2_BATCH_TYPES='{batch_types}' WBV2_BATCH_UNITS='{batch_units}' WEB_TEST_PORT=5371 npm --prefix web run test:e2e -- --workers=1 tests/e2e/{Path(batch['test']).name}",
|
||||
]
|
||||
root_record = {"id": root_task, "phase": str(phase), "title": "authorize_modifier_reader_batch", "kind": "enabling", "owner": "PROGRAM", "dependsOn": [current], "parityId": None, "implementationClass": "QUEUE_ONLY", "parallelGroup": f"module-root-{phase}", "gate": "MODULE", "card": f"docs/tasks/{root_task}.md", "behavior": "Authorize one shared source-backed modifier reader batch.", "outputs": ["successor authorization", "module plan", "manifest", "status"], "acceptance": ["prior evidence remains hash-bound", "batch source mapping is explicit", "inventory remains source-derived"], "commands": ["node tools/web/check-execution-control-plane.mjs"], "status": "done", "ready": False, "queueMutationAllowed": True}
|
||||
module_record = {"id": module_task, "phase": str(phase), "title": batch["title"], "kind": "module", "owner": batch["owner"], "dependsOn": [root_task], "parityId": batch["moduleId"], "implementationClass": "LOCAL_EXACT", "parallelGroup": f"module-{batch['moduleId'].lower()}", "gate": "MODULE", "card": f"docs/tasks/{module_task}.md", "behavior": f"Verify the {batch['moduleId']} batch through Blender Main, WASM and Chromium recovery.", "outputs": ["aggregate module receipt", "coverage bindings", "manifest", "status"], "acceptance": ["source and protocol hashes are bound", "every selected Main reader type is proven", "negative no-mutation and recovery are proven", "Chromium and save/reopen evidence are exact"], "status": "in_progress", "ready": True, "queueMutationAllowed": True, "module": {"moduleId": batch["moduleId"], "sourceBacked": True, "capabilities": [f"CAP-{batch['moduleId']}-READER", f"CAP-{batch['moduleId']}-RECOVERY", f"CAP-{batch['moduleId']}-FAIL-CLOSED"], "sourcePaths": source_paths, "reuseReceipts": batch.get("reuseReceipts", [{"manifest": "tests/golden/WBV2-P1107-000/manifest.json", "report": "tests/golden/WBV2-P1107-000/module-receipt.json"}]), "dependencyReuse": batch.get("dependencyReuse", []), "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"], "negative": ["malformed blend is rejected without Main mutation", "unsupported batch data fails closed", "non-finite metadata is not synthesized"], "recovery": ["save/reopen", "worker termination", "deterministic second read"], "commands": commands, "fullFamilyParity": False, "evidencePaths": [batch["production"].format(task=module_task)], "coverageBindings": [unit["id"] for unit in batch["units"]]}}
|
||||
module_record = {"id": module_task, "phase": str(phase), "title": batch["title"], "kind": "module", "owner": batch["owner"], "dependsOn": [root_task], "parityId": batch["moduleId"], "implementationClass": "LOCAL_EXACT", "parallelGroup": f"module-{batch['moduleId'].lower()}", "gate": "MODULE", "card": f"docs/tasks/{module_task}.md", "behavior": f"Verify the {batch['moduleId']} batch through Blender Main, WASM and Chromium recovery.", "outputs": ["aggregate module receipt", "coverage bindings", "manifest", "status"], "acceptance": ["source and protocol hashes are bound", "every selected Main reader type is proven", "negative no-mutation and recovery are proven", "Chromium and save/reopen evidence are exact"], "status": "in_progress", "ready": True, "queueMutationAllowed": True, "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "module": {"moduleId": batch["moduleId"], "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "sourceBacked": True, "capabilities": [f"CAP-{batch['moduleId']}-READER", f"CAP-{batch['moduleId']}-RECOVERY", f"CAP-{batch['moduleId']}-FAIL-CLOSED"], "sourcePaths": source_paths, "reuseReceipts": batch.get("reuseReceipts", [{"manifest": "tests/golden/WBV2-P1107-000/manifest.json", "report": "tests/golden/WBV2-P1107-000/module-receipt.json"}]), "dependencyReuse": batch.get("dependencyReuse", []), "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"], "negative": ["malformed blend is rejected without Main mutation", "unsupported batch data fails closed", "non-finite metadata is not synthesized"], "recovery": ["save/reopen", "worker termination", "deterministic second read"], "commands": commands, "fullFamilyParity": False, "evidencePaths": [batch["production"].format(task=module_task)], "coverageBindings": contract["selectedRecordIds"]}}
|
||||
|
||||
module_record["module"]["commandBudget"] = {"maxCommands": 48, "maxReadCommands": 16}
|
||||
root_out = CONTROL_ROOT.parent / root_task
|
||||
root_plan_path = root_out / "module-execution-plan.json"
|
||||
root_plan = {**plan, "planId": f"BLENDER_WEB_MODULE_EXECUTION_P{phase}", "status": "ACTIVE_MODULE_QUEUE", "tasks": [*plan["tasks"], root_record, module_record], "modules": [*plan.get("modules", []), {"id": batch["moduleId"], "task": module_task, "owner": batch["owner"], "sourceBacked": True, "capabilities": module_record["module"]["capabilities"], "sourcePaths": source_paths, "reuseReceipts": module_record["module"]["reuseReceipts"], "fullFamilyParity": False}]}
|
||||
root_plan = {**plan, "planId": f"BLENDER_WEB_MODULE_EXECUTION_P{phase}", "status": "ACTIVE_MODULE_QUEUE", "tasks": [*plan["tasks"], root_record, module_record], "modules": [*plan.get("modules", []), {"id": batch["moduleId"], "task": module_task, "owner": batch["owner"], "sourceBacked": True, "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "capabilities": module_record["module"]["capabilities"], "sourcePaths": source_paths, "reuseReceipts": module_record["module"]["reuseReceipts"], "fullFamilyParity": False}]}
|
||||
write_json(root_plan_path, root_plan)
|
||||
root_card_path, module_card_path = ROOT / "docs/tasks" / f"{root_task}.md", ROOT / "docs/tasks" / f"{module_task}.md"
|
||||
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"))
|
||||
write_text(root_card_path, make_card(root_task, current, "done", batch, f"python3 my-tools/authorize_next_wbv2_task.py --write", contract))
|
||||
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", contract))
|
||||
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"]), "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_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)}, "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "module": {"moduleId": batch["moduleId"], "task": module_task, "batchSize": len(batch["units"]), "candidateBatchSize": len(batch.get("candidateUnits", batch["units"])), "sourceUnits": contract["selectedRecordIds"], "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", "adaptiveKey": batch.get("adaptiveKey"), "adaptiveHistory": batch.get("adaptiveHistory", []), "adaptiveNextSize": batch.get("adaptiveNextSize", len(batch["units"]))}, "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})
|
||||
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", "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "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})
|
||||
|
||||
run_node("tools/web/generate-v2-task-index.mjs", "--plan-path", rel(root_plan_path))
|
||||
queue_text = QUEUE.read_text(encoding="utf-8")
|
||||
|
||||
@@ -39,6 +39,7 @@ def main() -> int:
|
||||
"schedulerWrapperStatus": read_json(CONTROL_ROOT / "my-tools-scheduler.json"),
|
||||
}
|
||||
status["progress"] = project_progress()
|
||||
status["fastPath"] = fast_path_status(status)
|
||||
if args.json:
|
||||
print(json.dumps(status, ensure_ascii=False, separators=(",", ":")))
|
||||
if not args.watch:
|
||||
@@ -64,7 +65,7 @@ def main() -> int:
|
||||
print(f" supervisor: pid={supervisor.get('pid') or 'none'} alive={supervisor.get('alive', False)} state={supervisor.get('state') or 'unknown'}")
|
||||
print(f" latest handoff: {status.get('latestHandoff') or 'none'}")
|
||||
print(f" local HEAD: {status.get('localHead') or 'unknown'}")
|
||||
for line in format_progress(status["progress"]):
|
||||
for line in format_progress(status["progress"], status["fastPath"]):
|
||||
print(line)
|
||||
print(f" message: {status.get('message', '')}")
|
||||
if not args.watch:
|
||||
@@ -180,12 +181,42 @@ def project_progress() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def format_progress(progress: dict) -> list[str]:
|
||||
def fast_path_status(status: dict) -> dict:
|
||||
scheduler = status.get("scheduler", {}) if isinstance(status, dict) else {}
|
||||
latest_batch = scheduler.get("latestBatch") if isinstance(scheduler.get("latestBatch"), dict) else {}
|
||||
cache = _read_json(CONTROL_ROOT / "context-cache.json")
|
||||
ledger = _read_json(CONTROL_ROOT / "failure-ledger.json")
|
||||
return {
|
||||
"batch": {
|
||||
"batchId": latest_batch.get("batchId"),
|
||||
"selectedRecordCount": len(latest_batch.get("selectedRecordIds", [])),
|
||||
"completed": bool(latest_batch.get("completed")),
|
||||
},
|
||||
"contextCache": {
|
||||
"status": cache.get("status", "MISSING"),
|
||||
"task": cache.get("task"),
|
||||
"schemaVersion": cache.get("schemaVersion"),
|
||||
"inputCount": len(cache.get("inputs", {})) if isinstance(cache.get("inputs"), dict) else 0,
|
||||
},
|
||||
"failureLedger": {
|
||||
"entryCount": len(ledger.get("entries", [])) if isinstance(ledger.get("entries"), list) else 0,
|
||||
"hydratedLogs": len(ledger.get("hydratedLogs", [])) if isinstance(ledger.get("hydratedLogs"), list) else 0,
|
||||
},
|
||||
"commandBudget": {"maxCommands": 48, "maxReadCommands": 16},
|
||||
"verificationPolicy": "incremental-context-only-until-final-audit",
|
||||
}
|
||||
|
||||
|
||||
def format_progress(progress: dict, fast_path: dict | None = None) -> list[str]:
|
||||
estimate = progress.get("estimatedRemainingHours")
|
||||
estimate_text = f"{estimate} h" if estimate is not None else "n/a"
|
||||
total_estimate = progress.get("estimatedTotalHours")
|
||||
total_text = f"{total_estimate} h" if total_estimate is not None else "n/a"
|
||||
plan = progress.get("taskPlan", {})
|
||||
fast_path = fast_path or {}
|
||||
batch = fast_path.get("batch", {})
|
||||
context_cache = fast_path.get("contextCache", {})
|
||||
ledger = fast_path.get("failureLedger", {})
|
||||
return [
|
||||
"",
|
||||
"Project migration progress",
|
||||
@@ -197,6 +228,9 @@ def format_progress(progress: dict) -> list[str]:
|
||||
f" stage medians: {progress.get('stageMedianDurationMs') or 'n/a'} ms (1=build, 2=typecheck, 3=web build, 4=Chromium)",
|
||||
f" estimate: remaining={estimate_text}, total={total_text}",
|
||||
f" estimate basis: {progress.get('estimateBasis', 'n/a')}",
|
||||
f" fast path: batch={batch.get('batchId') or 'none'} selected={batch.get('selectedRecordCount', 0)} completed={batch.get('completed', False)}",
|
||||
f" context cache: {context_cache.get('status', 'MISSING')} task={context_cache.get('task') or 'none'} inputs={context_cache.get('inputCount', 0)}; failure ledger entries={ledger.get('entryCount', 0)} hydratedLogs={ledger.get('hydratedLogs', 0)}",
|
||||
f" command budget: max={fast_path.get('commandBudget', {}).get('maxCommands', 48)}, read/exploration max={fast_path.get('commandBudget', {}).get('maxReadCommands', 16)}; verification={fast_path.get('verificationPolicy', 'n/a')}",
|
||||
]
|
||||
|
||||
|
||||
|
||||
67
my-tools/validate_incremental_context.py
Normal file
67
my-tools/validate_incremental_context.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate only the active task's context metadata and focused pointer.
|
||||
|
||||
The normal path compares stat metadata first. The task-context checker is
|
||||
invoked only when an input is missing, changed, or lacks the required hash.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from wbv2_runtime import ROOT, queue_pointer
|
||||
|
||||
|
||||
def stat_metadata(path: Path) -> tuple[int, str] | None:
|
||||
try:
|
||||
value = path.stat()
|
||||
except OSError:
|
||||
return None
|
||||
return value.st_size, str(value.st_mtime_ns)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--task", help="task id; defaults to the authoritative queue pointer")
|
||||
args = parser.parse_args()
|
||||
task = args.task or queue_pointer().get("task")
|
||||
if not task:
|
||||
parser.error("queue has no current task")
|
||||
context_path = ROOT / "tests/golden" / task / "task-context.json"
|
||||
try:
|
||||
context = json.loads(context_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
context = {}
|
||||
metadata = context.get("inputMetadata") if isinstance(context, dict) else None
|
||||
unchanged = isinstance(metadata, dict) and bool(metadata)
|
||||
if unchanged:
|
||||
for relative, expected in metadata.items():
|
||||
current = stat_metadata(ROOT / relative)
|
||||
if (
|
||||
not isinstance(expected, dict)
|
||||
or current is None
|
||||
or current[0] != expected.get("bytes")
|
||||
or current[1] != expected.get("mtime_ns")
|
||||
or not isinstance(expected.get("sha256"), str)
|
||||
):
|
||||
unchanged = False
|
||||
break
|
||||
if unchanged:
|
||||
print(f"incremental-context-ok task={task} context=REUSED metadata={len(metadata)}")
|
||||
return 0
|
||||
command = ["node", "tools/web/check-task-context.mjs", "--task", task, "--write"]
|
||||
result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False)
|
||||
output = result.stdout or result.stderr
|
||||
if output:
|
||||
print(output, end="" if output.endswith("\n") else "\n")
|
||||
if result.returncode:
|
||||
return result.returncode
|
||||
print(f"incremental-context-ok task={task} context=REFRESHED")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user