314dc03b0d
Moves the orchestrator into bot_bottle/orchestrator/ so one install gets everything. Entry point is now `python -m bot_bottle.orchestrator run`. - Add bot_bottle/orchestrator/ with all 14 modules (verbatim move; internal imports were already relative, so no changes inside orchestrator modules) - Rewrite bootstrap.py: remove the lazy bot_bottle import guard, use direct relative imports from ..contrib.* - Add bot_bottle/contrib/forge/base.py: ScopedForge (read-anywhere / write-scoped) - Add bot_bottle/contrib/gitea/client.py: GiteaClient + GiteaForge (urllib.request only) - Add bot_bottle/contrib/gitea/forge_state.py: ForgeState + SqliteForgeStateStore - Add tests/unit/orchestrator/ (82 tests: 63 migrated + 19 new for contrib modules) Closes #321
72 lines
1.9 KiB
Python
72 lines
1.9 KiB
Python
"""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
|
|
],
|
|
}
|