Simam3DZeroGPU / validate_manifest.py
junaid-simamdigital's picture
Initial ZeroGPU Simam3D runtime
763bffb verified
Raw History Blame Contribute Delete
3.64 kB
"""Validate a Simam3D run manifest and its exported artifact fingerprints."""
from __future__ import annotations
import hashlib
import json
import sys
from pathlib import Path
def file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def validate_manifest(manifest_path: str | Path) -> tuple[bool, list[str]]:
manifest_path = Path(manifest_path)
errors: list[str] = []
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
return False, [f"manifest unreadable: {exc}"]
if manifest.get("project") != "Simam3D":
errors.append("project is not Simam3D")
if int(manifest.get("manifest_schema_version", 0)) < 3:
errors.append("manifest schema is older than provenance-contract schema 3")
if manifest.get("status") == "failed":
required = {"run_id", "backend", "runtime", "stage", "error", "input_sha256"}
missing = sorted(key for key in required if key not in manifest)
if missing:
errors.append(f"failure manifest missing fields: {', '.join(missing)}")
if not isinstance(manifest.get("error"), dict):
errors.append("failure manifest error field is invalid")
elif not all(manifest["error"].get(key) for key in ("type", "message")):
errors.append("failure manifest error type/message is incomplete")
claims = manifest.get("claims")
if not isinstance(claims, dict) or claims.get("failure_report_only") is not True:
errors.append("failure manifest must declare failure_report_only")
return not errors, errors
claims = manifest.get("claims")
required_claims = {
"observed_input_geometry",
"generated_novel_views",
"metric_scale_reconstruction",
"learned_gaussian_optimization",
"reveal_content_is_verified",
}
if not isinstance(claims, dict) or not required_claims.issubset(claims):
errors.append("claims provenance contract is missing or incomplete")
outputs = manifest.get("outputs")
hashes = manifest.get("output_sha256")
if not isinstance(outputs, list) or not isinstance(hashes, dict):
return False, errors + ["outputs/output_sha256 fields are missing or invalid"]
for raw_output in outputs:
output = Path(str(raw_output))
if not output.is_absolute() and not output.is_file():
sibling = manifest_path.parent / output.name
if sibling.is_file():
output = sibling
if not output.is_file():
errors.append(f"missing artifact: {raw_output}")
continue
expected = hashes.get(output.name)
if not expected:
errors.append(f"missing hash: {output.name}")
continue
actual = file_sha256(output)
if actual != expected:
errors.append(f"hash mismatch: {output.name}")
return not errors, errors
def main(argv: list[str] | None = None) -> int:
argv = argv or sys.argv[1:]
if len(argv) != 1:
print("usage: python validate_manifest.py PATH_TO_RUN_JSON", file=sys.stderr)
return 2
valid, errors = validate_manifest(argv[0])
if valid:
print("VALID: all Simam3D artifacts match manifest fingerprints")
return 0
print("INVALID")
for error in errors:
print(f"- {error}")
return 1
if __name__ == "__main__":
raise SystemExit(main())