feat(forge): forge library layer for native integration (PRD chunks 1-3, 5)
Implements the bot-bottle side of the forge-native PRD that is self-contained in this repo (the forge sidecar and orchestrate command belong to the separate bot-bottle-orchestrator, a PRD non-goal): - contrib/forge/base.py: Forge ABC + ScopedForge enforcing the read-anywhere / write-scoped model (writes rejected outside the assigned issue/PRs via ForgeScopeError). - contrib/gitea/client.py: GiteaClient (stdlib-only HTTP, mirrors the deploy-key provisioner) + GiteaForge. Token held by the caller (the sidecar), not injected by cred-proxy. - contrib/gitea/forge_state.py: ForgeState dataclass + atomic read/write/delete/all under ~/.bot-bottle/forge/<owner>/<repo>/. - contrib/gitea/provenance.py: build_provenance_footer — collapsed markdown audit footer; watchdog/gitleaks/egress rendering. - cli/resume.py: `resume --headless --prompt` reusing the shipped assume_yes + headless_prompt launch core (the new half of chunk 1). 47 new unit tests; pylint 9.98/10, pyright clean. Forge sidecar (chunk 4), orchestrate command (chunk 6), and forge_env plumbing are deferred: their only consumer is the separate orchestrator and they are untestable in isolation here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WL77TgFxKbs3cidGMG9dz7
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""Provenance footer (PRD forge-native-integration, chunk 5).
|
||||
|
||||
Every orchestrator-posted comment ends with this footer — non-optional
|
||||
and not configurable off. It renders the run's audit trail (agent,
|
||||
bottle, timing, exit, gitleaks, done-signal source, egress) as a
|
||||
collapsed markdown block the reviewer sees at the moment of the merge
|
||||
decision.
|
||||
|
||||
The function is pure: the orchestrator, which holds the run context,
|
||||
supplies the values. In particular `egress_routes` is the pre-rendered
|
||||
list of allowed-route lines the orchestrator computed from the run's
|
||||
resolved egress policy — this module does not parse backend-specific
|
||||
egress state. (The PRD sketch named an `egress_log_path`; passing the
|
||||
already-rendered lines keeps the footer builder pure and fully testable
|
||||
and leaves egress-state parsing where the data lives.)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def _parse(ts: str) -> datetime | None:
|
||||
try:
|
||||
return datetime.fromisoformat(ts)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
def _format_duration(started_at: str, finished_at: str) -> str:
|
||||
start = _parse(started_at)
|
||||
end = _parse(finished_at)
|
||||
if start is None or end is None:
|
||||
return "unknown"
|
||||
secs = int((end - start).total_seconds())
|
||||
if secs < 0:
|
||||
return "unknown"
|
||||
if secs < 60:
|
||||
return f"{secs}s"
|
||||
return f"{secs // 60}m {secs % 60}s"
|
||||
|
||||
|
||||
def build_provenance_footer(
|
||||
slug: str,
|
||||
*,
|
||||
agent_name: str,
|
||||
bottle_names: tuple[str, ...],
|
||||
started_at: str,
|
||||
finished_at: str,
|
||||
exit_code: int,
|
||||
watchdog_fired: bool = False,
|
||||
gitleaks_clean: bool | None = None,
|
||||
egress_routes: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Return a markdown string for appending to a Gitea comment body.
|
||||
|
||||
`watchdog_fired=True` marks runs where the agent did not signal
|
||||
completion, so reviewers know the audit trail may be incomplete.
|
||||
`gitleaks_clean=None` renders the gitleaks row as "not run".
|
||||
`egress_routes` is omitted entirely when None/empty.
|
||||
"""
|
||||
bottle_label = ", ".join(f"`{b}`" for b in bottle_names) if bottle_names else "—"
|
||||
exit_cell = f"{exit_code} {'✓' if exit_code == 0 else '✗'}"
|
||||
|
||||
if gitleaks_clean is None:
|
||||
gitleaks_cell = "— not run"
|
||||
elif gitleaks_clean:
|
||||
gitleaks_cell = "✓ no secrets detected"
|
||||
else:
|
||||
gitleaks_cell = "✗ secrets detected"
|
||||
|
||||
if watchdog_fired:
|
||||
done_cell = "watchdog — agent did not signal"
|
||||
else:
|
||||
done_cell = "sidecar `signal_done`"
|
||||
|
||||
lines = [
|
||||
"<details><summary>🔬 Run provenance</summary>",
|
||||
"",
|
||||
"| Field | Value |",
|
||||
"|---|---|",
|
||||
f"| agent | `{agent_name}` |",
|
||||
f"| bottle | {bottle_label} |",
|
||||
f"| slug | `{slug}` |",
|
||||
f"| started | {started_at} |",
|
||||
f"| duration | {_format_duration(started_at, finished_at)} |",
|
||||
f"| exit | {exit_cell} |",
|
||||
f"| gitleaks | {gitleaks_cell} |",
|
||||
f"| done signal | {done_cell} |",
|
||||
]
|
||||
|
||||
if egress_routes:
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"**Egress** (deny-by-default; {len(egress_routes)} "
|
||||
f"route{'s' if len(egress_routes) != 1 else ''} allowed)"
|
||||
)
|
||||
for route in egress_routes:
|
||||
lines.append(f"- {route}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("</details>")
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user