"""Artifact manifest helpers.""" from __future__ import annotations import hashlib import mimetypes from datetime import datetime, timezone from pathlib import Path from typing import Any from guide_board.schema import assert_valid def build_artifact_manifest( run_dir: Path, run_id: str, evidence: list[dict[str, Any]], ) -> list[dict[str, Any]]: artifacts: list[dict[str, Any]] = [] seen: set[str] = set() for item in evidence: producer = item["check_id"] for artifact_ref in item.get("artifact_refs", []): if not isinstance(artifact_ref, str) or artifact_ref in seen: continue seen.add(artifact_ref) path = (run_dir / artifact_ref).resolve() try: path.relative_to(run_dir.resolve()) except ValueError: continue if not path.exists() or not path.is_file(): continue artifact = { "id": f"artifact:{_safe_id(artifact_ref)}", "run_id": run_id, "path": artifact_ref, "media_type": _media_type(path), "producer": producer, "checksum": f"sha256:{_sha256(path)}", "created_at": datetime.now(timezone.utc).isoformat(), "retention_class": "raw", } assert_valid(artifact, "raw-artifact") artifacts.append(artifact) return artifacts def _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 _media_type(path: Path) -> str: guessed, _ = mimetypes.guess_type(path.name) if guessed: return guessed return "application/octet-stream" def _safe_id(value: str) -> str: return "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in value)