271 lines
11 KiB
Python
271 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate a Working Leo production-apply receipt.
|
|
|
|
This validator is intentionally offline. It reads the retained authorized apply
|
|
runbook plus a receipt JSON file and checks that the receipt proves the
|
|
post-apply state expected by the runbook. It never opens a database connection,
|
|
never runs SQL, and never posts to Telegram.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
REPORT_DIR = Path("docs/reports/leo-working-state-20260709")
|
|
DEFAULT_RUNBOOK = REPORT_DIR / "working-leo-authorized-apply-runbook-current.json"
|
|
DEFAULT_RECEIPT = REPORT_DIR / "working-leo-production-apply-receipt-current.json"
|
|
DEFAULT_OUT = REPORT_DIR / "working-leo-production-apply-receipt-validation-current.json"
|
|
DEFAULT_MARKDOWN_OUT = REPORT_DIR / "working-leo-production-apply-receipt-validation-current.md"
|
|
|
|
EMPTY_PLACEHOLDERS = {"", "todo", "tbd", "n/a", "none", "null", "placeholder"}
|
|
|
|
|
|
def _load_json(path: Path) -> dict[str, Any]:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def _is_nonempty(value: Any) -> bool:
|
|
if value is None:
|
|
return False
|
|
if isinstance(value, str):
|
|
return value.strip().lower() not in EMPTY_PLACEHOLDERS
|
|
if isinstance(value, dict | list):
|
|
return bool(value)
|
|
return True
|
|
|
|
|
|
def _as_list(value: Any) -> list[Any]:
|
|
if isinstance(value, list):
|
|
return value
|
|
if value is None:
|
|
return []
|
|
return [value]
|
|
|
|
|
|
def _resolve(path: str, *, repo_root: Path) -> Path:
|
|
candidate = Path(path)
|
|
return candidate if candidate.is_absolute() else repo_root / candidate
|
|
|
|
|
|
def _paths_exist(receipt: dict[str, Any], field: str, *, repo_root: Path) -> tuple[bool, list[str]]:
|
|
values = _as_list(receipt.get(field))
|
|
missing = [str(value) for value in values if not _resolve(str(value), repo_root=repo_root).exists()]
|
|
return bool(values) and not missing, missing
|
|
|
|
|
|
def _counts_match(expected: dict[str, Any], actual: Any) -> bool:
|
|
if not isinstance(actual, dict):
|
|
return False
|
|
try:
|
|
normalized_actual = {str(key): int(value) for key, value in actual.items()}
|
|
normalized_expected = {str(key): int(value) for key, value in expected.items()}
|
|
except (TypeError, ValueError):
|
|
return False
|
|
return normalized_actual == normalized_expected
|
|
|
|
|
|
def _direct_claim_score_passes(path_value: Any, *, repo_root: Path) -> tuple[bool, str]:
|
|
values = _as_list(path_value)
|
|
if not values:
|
|
return False, "missing direct_claim_score_path"
|
|
path = _resolve(str(values[0]), repo_root=repo_root)
|
|
if not path.exists():
|
|
return False, f"missing direct claim score artifact: {path}"
|
|
text = path.read_text(encoding="utf-8")
|
|
if path.suffix == ".json":
|
|
data = json.loads(text)
|
|
score = data.get("score", {})
|
|
if score.get("pass") is True and score.get("passes") == score.get("expected_prompt_count") == 6:
|
|
return True, "json score pass"
|
|
return False, "json score is not 6/6 pass"
|
|
if "Overall pass for scored coverage: `True`" in text and "Prompts scored: `6/6`" in text:
|
|
return True, "markdown score pass"
|
|
return False, "score artifact does not show 6/6 pass"
|
|
|
|
|
|
def _service_readback_ok(value: Any) -> bool:
|
|
if not isinstance(value, dict):
|
|
return False
|
|
return (
|
|
value.get("ActiveState") == "active"
|
|
and value.get("SubState") == "running"
|
|
and _is_nonempty(value.get("MainPID"))
|
|
and _is_nonempty(value.get("NRestarts"))
|
|
)
|
|
|
|
|
|
def _cleanup_readback_ok(value: Any) -> bool:
|
|
if not isinstance(value, dict):
|
|
return False
|
|
checks = value.get("checks") if isinstance(value.get("checks"), dict) else value
|
|
leftovers = checks.get("leftover_rehearsal_clone_or_staging_databases", [])
|
|
return checks.get("cleanup_ok") is True and leftovers == []
|
|
|
|
|
|
def validate_receipt(receipt_path: Path, runbook_path: Path = DEFAULT_RUNBOOK, *, repo_root: Path = Path(".")) -> dict[str, Any]:
|
|
repo_root = repo_root.resolve()
|
|
runbook = _load_json(runbook_path)
|
|
required_fields = list(runbook["production_apply_receipt_required_fields"])
|
|
expected_counts = dict(runbook["expected_after_apply_counts"])
|
|
|
|
if not receipt_path.exists():
|
|
return {
|
|
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
"status": "missing_receipt",
|
|
"valid": False,
|
|
"receipt_path": str(receipt_path),
|
|
"runbook_path": str(runbook_path),
|
|
"missing_fields": required_fields,
|
|
"errors": [f"receipt file does not exist: {receipt_path}"],
|
|
"claim_ceiling": "No production apply receipt was validated.",
|
|
}
|
|
|
|
receipt = _load_json(receipt_path)
|
|
missing_fields = [field for field in required_fields if field not in receipt]
|
|
errors: list[str] = []
|
|
warnings: list[str] = []
|
|
checks: dict[str, bool] = {}
|
|
|
|
checks["all_required_fields_present"] = not missing_fields
|
|
if missing_fields:
|
|
errors.append(f"missing required fields: {', '.join(missing_fields)}")
|
|
|
|
for field in ("explicit_authorization_record", "operator_identity", "git_commit_sha"):
|
|
checks[f"{field}_present"] = _is_nonempty(receipt.get(field))
|
|
if not checks[f"{field}_present"]:
|
|
errors.append(f"{field} is missing or placeholder")
|
|
|
|
git_sha = str(receipt.get("git_commit_sha", ""))
|
|
checks["git_commit_sha_shape"] = bool(re.fullmatch(r"[0-9a-f]{7,40}", git_sha))
|
|
if not checks["git_commit_sha_shape"]:
|
|
errors.append("git_commit_sha must be a 7-40 char lowercase hex SHA")
|
|
|
|
authorization = receipt.get("explicit_authorization_record")
|
|
checks["authorization_record_not_false"] = not (
|
|
isinstance(authorization, dict) and authorization.get("authorized") is False
|
|
)
|
|
if not checks["authorization_record_not_false"]:
|
|
errors.append("explicit_authorization_record explicitly says authorized=false")
|
|
|
|
for field in (
|
|
"preflight_output_paths",
|
|
"apply_output_paths",
|
|
"postflight_output_paths",
|
|
"telegram_or_handler_regression_paths",
|
|
"rollback_packet_paths",
|
|
):
|
|
ok, missing = _paths_exist(receipt, field, repo_root=repo_root)
|
|
checks[f"{field}_exist"] = ok
|
|
if missing:
|
|
errors.append(f"{field} contains missing path(s): {', '.join(missing)}")
|
|
elif not _as_list(receipt.get(field)):
|
|
errors.append(f"{field} must be a non-empty path list")
|
|
|
|
checks["expected_counts_match_runbook"] = _counts_match(expected_counts, receipt.get("expected_after_apply_counts"))
|
|
if not checks["expected_counts_match_runbook"]:
|
|
errors.append("expected_after_apply_counts does not match the authorized runbook")
|
|
|
|
checks["actual_counts_match_expected"] = _counts_match(expected_counts, receipt.get("actual_after_apply_counts"))
|
|
if not checks["actual_counts_match_expected"]:
|
|
errors.append("actual_after_apply_counts does not match the authorized runbook expected counts")
|
|
|
|
score_ok, score_note = _direct_claim_score_passes(receipt.get("direct_claim_score_path"), repo_root=repo_root)
|
|
checks["direct_claim_score_passes"] = score_ok
|
|
if not score_ok:
|
|
errors.append(score_note)
|
|
|
|
checks["service_readback_ok"] = _service_readback_ok(receipt.get("service_readback"))
|
|
if not checks["service_readback_ok"]:
|
|
errors.append("service_readback must show active/running service with MainPID and NRestarts")
|
|
|
|
checks["cleanup_readback_ok"] = _cleanup_readback_ok(receipt.get("cleanup_readback"))
|
|
if not checks["cleanup_readback_ok"]:
|
|
errors.append("cleanup_readback must show cleanup_ok=true and no rehearsal/clone/staging DB leftovers")
|
|
|
|
checks["known_residual_risks_list"] = isinstance(receipt.get("known_residual_risks"), list)
|
|
if not checks["known_residual_risks_list"]:
|
|
errors.append("known_residual_risks must be a list")
|
|
elif not receipt["known_residual_risks"]:
|
|
warnings.append("known_residual_risks is empty; verify that this is intentional")
|
|
|
|
valid = all(checks.values()) and not errors
|
|
return {
|
|
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
"status": "valid" if valid else "invalid",
|
|
"valid": valid,
|
|
"receipt_path": str(receipt_path),
|
|
"runbook_path": str(runbook_path),
|
|
"checks": checks,
|
|
"missing_fields": missing_fields,
|
|
"errors": errors,
|
|
"warnings": warnings,
|
|
"claim_ceiling": (
|
|
"This validates a retained production-apply receipt against the authorized runbook. It does not run SQL, "
|
|
"does not mutate production, and does not create authorization."
|
|
),
|
|
}
|
|
|
|
|
|
def write_markdown(path: Path, result: dict[str, Any]) -> None:
|
|
lines = [
|
|
"# Working Leo Production Apply Receipt Validation",
|
|
"",
|
|
f"Generated UTC: `{result['generated_at_utc']}`",
|
|
f"Status: `{result['status']}`",
|
|
f"Valid: `{result['valid']}`",
|
|
f"Receipt: `{result['receipt_path']}`",
|
|
f"Runbook: `{result['runbook_path']}`",
|
|
"",
|
|
"## Checks",
|
|
"",
|
|
]
|
|
for key, value in sorted(result.get("checks", {}).items()):
|
|
lines.append(f"- `{key}`: `{value}`")
|
|
lines.extend(["", "## Errors", ""])
|
|
errors = result.get("errors", [])
|
|
lines.extend(f"- {item}" for item in errors) if errors else lines.append("- none")
|
|
lines.extend(["", "## Warnings", ""])
|
|
warnings = result.get("warnings", [])
|
|
lines.extend(f"- {item}" for item in warnings) if warnings else lines.append("- none")
|
|
lines.extend(["", "## Claim Ceiling", "", result["claim_ceiling"], ""])
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("\n".join(lines), encoding="utf-8")
|
|
|
|
|
|
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--receipt", type=Path, default=DEFAULT_RECEIPT)
|
|
parser.add_argument("--runbook", type=Path, default=DEFAULT_RUNBOOK)
|
|
parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
|
|
parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = parse_args(argv)
|
|
result = validate_receipt(args.receipt, args.runbook)
|
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
|
args.out.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
write_markdown(args.markdown_out, result)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"out": str(args.out),
|
|
"markdown_out": str(args.markdown_out),
|
|
"status": result["status"],
|
|
"valid": result["valid"],
|
|
},
|
|
indent=2,
|
|
sort_keys=True,
|
|
)
|
|
)
|
|
return 0 if result["valid"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|