74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Repair a stale continuation checkpoint from the authoritative task ledger.
|
|
|
|
Only the generated checkpoint projection is changed. Task statuses, counts and
|
|
the active ledger entry are never modified by this tool.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
STATE = ROOT / "docs/current/task-state.json"
|
|
CHECKPOINT = ROOT / "docs/current/continuation-state.json"
|
|
MODULE_BATCH = ROOT / "docs/current/module-batch-manifest.json"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--quiet", action="store_true")
|
|
args = parser.parse_args()
|
|
state = json.loads(STATE.read_text(encoding="utf-8"))
|
|
checkpoint = json.loads(CHECKPOINT.read_text(encoding="utf-8"))
|
|
active = state.get("active_task")
|
|
counts = state.get("counts", {})
|
|
statuses = state.get("tasks", {})
|
|
if not active or counts.get("in_progress") != 1 or statuses.get(active) != "in_progress":
|
|
if not args.quiet:
|
|
print("CHECKPOINT_REPAIR_NOT_APPLICABLE: ledger has no single in_progress active task")
|
|
return 2
|
|
previous = checkpoint.get("activeTask")
|
|
changed = False
|
|
if previous != active:
|
|
checkpoint["activeTask"] = active
|
|
changed = True
|
|
batch = None
|
|
try:
|
|
batch = json.loads(MODULE_BATCH.read_text(encoding="utf-8"))
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
batch = None
|
|
previous_batch_id = checkpoint.get("batchId")
|
|
if isinstance(batch, dict) and batch.get("batchId") and previous_batch_id != batch.get("batchId"):
|
|
checkpoint["batchManifest"] = "docs/current/module-batch-manifest.json"
|
|
checkpoint["batchId"] = batch.get("batchId")
|
|
checkpoint["batchSelection"] = batch.get("selected")
|
|
changed = True
|
|
if not changed:
|
|
if not args.quiet:
|
|
print(f"CHECKPOINT_ALREADY_ALIGNED: active={active}")
|
|
return 2
|
|
checkpoint["phase"] = "CONTINUE"
|
|
checkpoint["lastCheckpoint"] = "CONTINUE"
|
|
checkpoint["lastTransition"] = datetime.now(timezone.utc).isoformat()
|
|
checkpoint["checkpointRepair"] = {
|
|
"reason": "ledger_or_batch_projection_mismatch",
|
|
"previousActiveTask": previous,
|
|
"repairedActiveTask": active,
|
|
"previousBatchId": previous_batch_id,
|
|
"repairedBatchId": checkpoint.get("batchId"),
|
|
"repairedAt": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
temporary = CHECKPOINT.with_suffix(".repair.tmp")
|
|
temporary.write_text(json.dumps(checkpoint, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
temporary.replace(CHECKPOINT)
|
|
print(f"CHECKPOINT_REPAIRED: previous={previous} active={active}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|