generated from coulomb/repo-seed
artifact refs and manifest fingerprinting
This commit is contained in:
65
src/guide_board/artifacts.py
Normal file
65
src/guide_board/artifacts.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""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)
|
||||
@@ -7,6 +7,7 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from guide_board.artifacts import build_artifact_manifest
|
||||
from guide_board.io import write_json
|
||||
from guide_board.planning import build_run_plan
|
||||
from guide_board.runners import run_step
|
||||
@@ -35,7 +36,16 @@ def run_assessment(
|
||||
for finding in findings:
|
||||
assert_valid(finding, "finding")
|
||||
|
||||
assessment_package = _assessment_package(run_id, plan, evidence, findings, created_at)
|
||||
artifact_manifest = build_artifact_manifest(run_dir, run_id, evidence)
|
||||
|
||||
assessment_package = _assessment_package(
|
||||
run_id,
|
||||
plan,
|
||||
evidence,
|
||||
findings,
|
||||
artifact_manifest,
|
||||
created_at,
|
||||
)
|
||||
assert_valid(assessment_package, "assessment-package")
|
||||
|
||||
run_metadata = {
|
||||
@@ -164,6 +174,7 @@ def _assessment_package(
|
||||
plan: dict[str, Any],
|
||||
evidence: list[dict[str, Any]],
|
||||
findings: list[dict[str, Any]],
|
||||
artifact_manifest: list[dict[str, Any]],
|
||||
created_at: str,
|
||||
) -> dict[str, Any]:
|
||||
summary = dict(Counter(item["result"] for item in evidence))
|
||||
@@ -179,7 +190,7 @@ def _assessment_package(
|
||||
"summary": summary,
|
||||
"findings": findings,
|
||||
"evidence_refs": [item["id"] for item in evidence],
|
||||
"artifact_manifest": [],
|
||||
"artifact_manifest": artifact_manifest,
|
||||
"waivers": [],
|
||||
"certification_boundary": "Guide Board produces preparation evidence only and does not issue certifications or audit assurance.",
|
||||
"created_at": created_at,
|
||||
|
||||
Reference in New Issue
Block a user