#!/usr/bin/env python3 """Validate the persistent continuous-execution checkpoint. This is intentionally read-only. It cannot start a model/agent turn; it gives an external scheduler or the next resumed turn a deterministic continuation decision without changing task state. """ from __future__ import annotations import argparse import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] CHECKPOINT = ROOT / "docs/current/continuation-state.json" TASK_STATE = ROOT / "docs/current/task-state.json" MODULE_BATCH = ROOT / "docs/current/module-batch-manifest.json" def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--quiet", action="store_true") parser.add_argument("--json", action="store_true", help="emit a machine-readable result") args = parser.parse_args() checkpoint = json.loads(CHECKPOINT.read_text(encoding="utf-8")) task_state = json.loads(TASK_STATE.read_text(encoding="utf-8")) active = task_state.get("active_task") counts = task_state.get("counts", {}) errors: list[str] = [] if checkpoint.get("mode") != "continuous": errors.append("continuation mode is not continuous") if checkpoint.get("scope") != "full-project": errors.append("continuation scope is not full-project") allowed_scopes = {"all-task-cards-all-modules-all-required-y-gates"} if checkpoint.get("implementationStrategy", {}).get("mode") == "feature-first-deferred-validation": allowed_scopes.add("all-business-workflows-and-feature-routes") if checkpoint.get("completionScope") not in allowed_scopes: errors.append("completion scope is not the full project") if checkpoint.get("noPartialCompletion") is not True: errors.append("noPartialCompletion is not true") if checkpoint.get("autoAdvance") is not True: errors.append("autoAdvance is not true") if checkpoint.get("mustContinue") is not True: errors.append("mustContinue is not true") if checkpoint.get("noFinalBeforeTerminate") is not True: errors.append("noFinalBeforeTerminate is not true") if checkpoint.get("phaseTransitionPolicy") != "immediate": errors.append("phase transition policy is not immediate") if checkpoint.get("confirmationPolicy") != "never_for_in_scope": errors.append("confirmation policy is not never_for_in_scope") if checkpoint.get("pausePolicy") != "never_between_phases": errors.append("pause policy allows a phase pause") if checkpoint.get("waitingAllowed") is not False: errors.append("waitingAllowed must be false") if checkpoint.get("awaitConfirmation") is not False: errors.append("awaitConfirmation must be false") if not isinstance(checkpoint.get("automaticActions"), list) or not checkpoint.get("automaticActions"): errors.append("automaticActions is empty") required_actions = {"source edits", "native builds", "asset sync", "context rotation", "ledger sync", "task claim", "single retry after timeout", "recovery-card creation"} if not required_actions.issubset(set(checkpoint.get("automaticActions", []))): errors.append("automaticActions is missing required in-scope actions") allowed_pauses = {"missing credentials", "irreversible destructive action outside the requested repository scope", "new architecture decision that cannot be inferred from source"} if set(checkpoint.get("pauseOnlyFor", [])) != allowed_pauses: errors.append("pauseOnlyFor does not match the declared exception boundary") if not checkpoint.get("termination"): errors.append("termination policy is empty") batch_policy = checkpoint.get("batchPolicy") if not isinstance(batch_policy, dict): errors.append("batchPolicy is missing") else: if batch_policy.get("mode") != "module+tier+family": errors.append("batchPolicy mode is not module+tier+family") if int(batch_policy.get("minRecords", 0)) < 2: errors.append("batchPolicy minRecords must be at least 2") if int(batch_policy.get("defaultRecords", 0)) < int(batch_policy.get("minRecords", 0)): errors.append("batchPolicy defaultRecords is below minRecords") if int(batch_policy.get("maxRecords", 0)) < int(batch_policy.get("defaultRecords", 0)): errors.append("batchPolicy maxRecords is below defaultRecords") if batch_policy.get("manifest") != "docs/current/module-batch-manifest.json": errors.append("batchPolicy manifest is not the canonical module batch manifest") if checkpoint.get("batchManifest") != "docs/current/module-batch-manifest.json": errors.append("batchManifest is not the canonical module batch manifest") if active and not MODULE_BATCH.exists(): errors.append("module-batch-manifest.json is missing for active task") elif active and MODULE_BATCH.exists(): try: batch = json.loads(MODULE_BATCH.read_text(encoding="utf-8")) if batch.get("taskId") not in (None, active): errors.append("module batch manifest taskId does not match active task") if batch.get("status") == "ready": selected = batch.get("selected") if isinstance(batch.get("selected"), dict) else {} min_records = int(batch_policy.get("minRecords", 2)) if isinstance(batch_policy, dict) else 2 if int(selected.get("recordCount", 0)) < min_records: if batch.get("singletonFallback") is not True: errors.append("module batch manifest has fewer records than batchPolicy minRecords without singletonFallback") if checkpoint.get("batchId") not in (None, batch.get("batchId")): errors.append("continuation batchId does not match module batch manifest") except (OSError, json.JSONDecodeError, TypeError, ValueError): errors.append("module-batch-manifest.json is invalid") if checkpoint.get("activeTask") != active: errors.append(f"active task mismatch: checkpoint={checkpoint.get('activeTask')} ledger={active}") if counts.get("in_progress", 0) != 1: errors.append(f"expected exactly one in_progress task, got {counts.get('in_progress', 0)}") phases = {"LOAD_STATE", "VALIDATE", "PREPARE_CONTEXT", "READ_MANIFEST", "IMPLEMENT_BATCH", "RECORD_RESULT", "ROTATE_CONTEXT", "SYNC_STATE", "PREPARE_NEXT", "CONTINUE"} if checkpoint.get("phase") not in phases: errors.append(f"invalid continuation phase: {checkpoint.get('phase')}") if errors: if args.json: print(json.dumps({"schema": "freecad-web-continuation-check/v1", "ok": False, "activeTask": active, "errors": errors}, ensure_ascii=False)) return 1 if not args.quiet: for error in errors: print(f"CONTINUATION_STATE_INVALID: {error}") return 1 if args.json: print(json.dumps({"schema": "freecad-web-continuation-check/v1", "ok": True, "activeTask": active, "phase": checkpoint.get("phase"), "mustContinue": True, "noFinalBeforeTerminate": True, "resumeCommand": checkpoint.get("resumeCommand", "")}, ensure_ascii=False)) elif not args.quiet: print(f"CONTINUATION_STATE_READY: active={active} phase={checkpoint.get('phase')} mustContinue=true noFinalBeforeTerminate=true") print(f"RESUME_COMMAND: {checkpoint.get('resumeCommand', '')}") return 0 if __name__ == "__main__": raise SystemExit(main())