44 lines
1.6 KiB
Python
44 lines
1.6 KiB
Python
from dataclasses import dataclass, asdict
|
|
from typing import List, Dict, Any, Optional
|
|
import json
|
|
import hashlib
|
|
|
|
from .contracts import Environment
|
|
|
|
|
|
@dataclass
|
|
class ReproBundle:
|
|
"""A compact, content-addressed snapshot describing everything needed to
|
|
deterministically replay a run (code commit, environment, dataset refs,
|
|
model ref, and a minimal run manifest).
|
|
|
|
This is intentionally small and deterministic so it can be cheaply
|
|
exchanged between peers and used to compute Merkle-like bundle hashes.
|
|
"""
|
|
code_commit: str
|
|
environment: Environment
|
|
dataset_refs: List[Dict[str, Any]]
|
|
model_ref: Dict[str, Any]
|
|
run_manifest: Dict[str, Any]
|
|
container_hash: Optional[str] = None
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
# Convert to a canonical dict suitable for hashing/serialization.
|
|
return {
|
|
"code_commit": self.code_commit,
|
|
"environment": self.environment.to_dict(),
|
|
"container_hash": self.container_hash,
|
|
"dataset_refs": sorted(self.dataset_refs, key=lambda d: d.get("id") or ""),
|
|
"model_ref": self.model_ref,
|
|
"run_manifest": self.run_manifest,
|
|
}
|
|
|
|
def compute_bundle_hash(self) -> str:
|
|
"""Compute a deterministic SHA-256 digest over the canonical bundle
|
|
representation. Uses JSON with sorted keys so identical bundles always
|
|
produce the same digest.
|
|
"""
|
|
payload = self.to_dict()
|
|
serialized = json.dumps(payload, sort_keys=True, default=str).encode("utf-8")
|
|
return hashlib.sha256(serialized).hexdigest()
|