"""Run the released LiveKit stages for a user-owned, non-publishable experiment.""" from __future__ import annotations import hashlib import json import sys from pathlib import Path from stage_evidence import run_stage RUN_DIR = Path("/run") INPUTS_DIR = Path("/inputs") def _sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as file: for block in iter(lambda: file.read(1024 * 1024), b""): digest.update(block) return digest.hexdigest() def verify_local_cache() -> None: lock = json.loads((RUN_DIR / "sources.lock.json").read_text(encoding="utf-8")) sources = lock.get("sources") if not isinstance(sources, list) or len(sources) != 1: raise ValueError("local source lock must contain exactly one cache inventory") files = sources[0].get("files") if isinstance(sources[0], dict) else None if not isinstance(files, list): raise ValueError("local source lock has no content-addressed file inventory") expected_paths: set[str] = set() for item in files: if not isinstance(item, dict): raise ValueError("local source inventory entry is invalid") relative = item.get("relative_path") expected_hash = item.get("sha256") expected_size = item.get("size_bytes") if ( not isinstance(relative, str) or not relative or Path(relative).is_absolute() or ".." in Path(relative).parts or not isinstance(expected_hash, str) or not isinstance(expected_size, int) ): raise ValueError("local source inventory entry is unsafe") path = INPUTS_DIR / relative if not path.is_file() or path.is_symlink(): raise ValueError(f"locked local cache file is missing: {relative}") if path.stat().st_size != expected_size or _sha256(path) != expected_hash: raise ValueError(f"locked local cache file changed: {relative}") expected_paths.add(relative) actual_paths = { path.relative_to(INPUTS_DIR).as_posix() for path in INPUTS_DIR.rglob("*") if path.is_file() } if actual_paths != expected_paths: raise ValueError("local cache contains unsealed files") def main(config_path: Path) -> int: request = json.loads((RUN_DIR / "request.json").read_text(encoding="utf-8")) payload = request.get("request", {}) if payload.get("run_kind") != "local_experiment": raise ValueError("local experiment wrapper requires run_kind=local_experiment") verify_local_cache() run_stage("provenance", ["python", "/opt/jr-wakeword/record-image-provenance.py"]) for stage in ("generate", "augment", "train", "export", "eval"): run_stage(stage, ["livekit-wakeword", stage, str(config_path)]) run_stage("finalize", ["python", "/opt/jr-wakeword/finalize-run.py", str(config_path)]) return 0 if __name__ == "__main__": raise SystemExit(main(Path(sys.argv[1])))