"""Provenance assembly + serialization. Provenance is the run's audit record: the `RunRecord` metadata plus the sidecar's semantic operation log. It is exposed through the provenance API (see `webhook.ProvenanceHandler`) and deliberately never posted back into the forge — a mutable PR comment is not an audit record. This module only assembles and serializes; retention/signing of the record is a control-plane concern out of scope here. """ from __future__ import annotations from typing import Any from .model import ForgeOp, Provenance, RunRecord def ops_from_log(entries: list[dict[str, Any]]) -> tuple[ForgeOp, ...]: return tuple( ForgeOp( at=str(e.get("at", "")), op=str(e.get("op", "")), target=e.get("target"), detail=str(e.get("detail", "")), ) for e in entries ) def build_provenance( record: RunRecord, *, ops: tuple[ForgeOp, ...], started_at: str, finished_at: str, exit_code: int | None, watchdog_fired: bool, ) -> Provenance: return Provenance( slug=record.slug, owner=record.owner, repo=record.repo, issue_number=record.issue_number, agent_name=record.agent_name, bottle_names=tuple(record.bottle_names), started_at=started_at, finished_at=finished_at, exit_code=exit_code, watchdog_fired=watchdog_fired, ops=ops, ) def provenance_to_dict(p: Provenance) -> dict[str, Any]: return { "slug": p.slug, "owner": p.owner, "repo": p.repo, "issue_number": p.issue_number, "agent": p.agent_name, "bottles": list(p.bottle_names), "started_at": p.started_at, "finished_at": p.finished_at, "exit_code": p.exit_code, "watchdog_fired": p.watchdog_fired, "ops": [ {"at": o.at, "op": o.op, "target": o.target, "detail": o.detail} for o in p.ops ], }