Recover model provenance from verified backup receipt
GeoIntel release gates / Compile, test, contracts and builds (push) Failing after 29s
GeoIntel release gates / Python and npm vulnerability policy (push) Failing after 53s
GeoIntel release gates / GIS image, SBOM and container scan (push) Failing after 5m38s

This commit is contained in:
Jens
2026-08-24 03:22:45 +02:00
parent 36a3c84699
commit 3627a05bfe
2 changed files with 140 additions and 0 deletions
@@ -63,6 +63,8 @@ def _require_equal(observed: Any, expected: Any, field: str) -> None:
def inspect_evidence(args: argparse.Namespace) -> dict[str, Any]:
if args.evidence_receipt_path:
return inspect_recovery_receipt(args)
paths = {
"model": _required_file(args.model_path, "--model-path"),
"checkpoint": _required_file(args.checkpoint_path, "--checkpoint-path"),
@@ -112,6 +114,89 @@ def inspect_evidence(args: argparse.Namespace) -> dict[str, Any]:
return evidence
def _checksum_manifest_entries(path: Path) -> dict[str, str]:
entries: dict[str, str] = {}
for line in path.read_text(encoding="utf-8").splitlines():
checksum, separator, name = line.partition(" ")
if separator and len(checksum) == 64:
entries[name.strip()] = checksum.lower()
return entries
def _storage_manifest_entries(path: Path) -> dict[str, tuple[int, str]]:
entries: dict[str, tuple[int, str]] = {}
lines = path.read_text(encoding="utf-8").splitlines()
if not lines or lines[0] != "relative_path\tsize_bytes\tmtime_ns\tsha256":
raise ValueError("backup storage manifest has an unexpected header")
for line in lines[1:]:
fields = line.split("\t")
if len(fields) == 4:
entries[fields[0]] = (int(fields[1]), fields[3].lower())
return entries
def inspect_recovery_receipt(args: argparse.Namespace) -> dict[str, Any]:
receipt_path = _required_file(args.evidence_receipt_path, "--evidence-receipt-path")
storage_manifest_path = _required_file(
args.backup_storage_manifest_path, "--backup-storage-manifest-path"
)
backup_checksums_path = _required_file(args.backup_checksums_path, "--backup-checksums-path")
receipt = _json_object(receipt_path, "evidence receipt")
_require_equal(receipt.get("status"), "ready_to_apply", "evidence receipt status")
_require_equal(receipt.get("claim_boundary"), CLAIM_BOUNDARY, "evidence receipt claim boundary")
evidence = receipt.get("evidence")
if not isinstance(evidence, dict):
raise ValueError("evidence receipt does not contain an evidence object")
paths = evidence.get("paths")
checksums = evidence.get("checksums")
if not isinstance(paths, dict) or not isinstance(checksums, dict):
raise ValueError("evidence receipt paths/checksums are incomplete")
backup_checksums = _checksum_manifest_entries(backup_checksums_path)
_require_equal(
backup_checksums.get(storage_manifest_path.name),
_sha256_file(storage_manifest_path),
"backup storage manifest SHA-256",
)
storage_entries = _storage_manifest_entries(storage_manifest_path)
for name in ("checkpoint", "training_summary", "training_args", "training_results"):
original_path = Path(str(paths.get(name, "")))
try:
relative_path = original_path.relative_to("/app/storage").as_posix()
except ValueError as exc:
raise ValueError(f"receipt {name} path is outside /app/storage: {original_path}") from exc
entry = storage_entries.get(relative_path)
if entry is None:
raise ValueError(f"backup storage manifest does not inventory receipt path: {relative_path}")
_require_equal(entry[1], checksums.get(name), f"backup {name} SHA-256")
live_paths = {
"model": _required_file(args.model_path, "--model-path"),
"base_model": _required_file(args.base_model_path, "--base-model-path"),
"dataset_summary": _required_file(args.dataset_summary_path, "--dataset-summary-path"),
"dataset_yaml": _required_file(args.dataset_yaml_path, "--dataset-yaml-path"),
}
for name, live_path in live_paths.items():
_require_equal(_sha256_file(live_path), checksums.get(name), f"live {name} SHA-256")
paths[name] = str(live_path)
_require_equal(checksums.get("checkpoint"), checksums.get("model"), "receipt checkpoint/model SHA-256")
evidence["recovery_receipt"] = {
"path": str(receipt_path),
"sha256": _sha256_file(receipt_path),
"backup_storage_manifest_path": str(storage_manifest_path),
"backup_storage_manifest_sha256": _sha256_file(storage_manifest_path),
"backup_checksums_path": str(backup_checksums_path),
"backup_checksums_sha256": _sha256_file(backup_checksums_path),
"missing_artifacts_not_recreated": [
"checkpoint",
"training_summary",
"training_args",
"training_results",
],
}
return evidence
def _snapshot_key(*, model_id: str, model_sha256: str) -> str:
return f"runtime-model-{model_id}-{model_sha256[:24]}"
@@ -358,6 +443,9 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
parser.add_argument("--task-type", default="object_detection")
parser.add_argument("--source-version", required=True)
parser.add_argument("--framework-version", required=True)
parser.add_argument("--evidence-receipt-path")
parser.add_argument("--backup-storage-manifest-path")
parser.add_argument("--backup-checksums-path")
parser.add_argument("--apply", action="store_true")
parser.add_argument("--json", action="store_true")
return parser.parse_args(argv)