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
109 lines
2.5 KiB
Python
109 lines
2.5 KiB
Python
"""Domain model: run records, forge events, provenance.
|
|
|
|
These are the orchestrator's own dataclasses. `RunRecord` mirrors
|
|
bot-bottle's `ForgeState` field-for-field so the bootstrap adapter can
|
|
translate between them with no loss; keeping our own copy is what lets
|
|
the core stay import-free of bot-bottle.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
# Run lifecycle. A bottle is launched (running), frozen on the done
|
|
# signal, and destroyed when the PR closes.
|
|
STATUS_RUNNING = "running"
|
|
STATUS_FROZEN = "frozen"
|
|
STATUS_DESTROYED = "destroyed"
|
|
|
|
|
|
@dataclass
|
|
class RunRecord:
|
|
"""One forge-targeted issue's bottle lifecycle record."""
|
|
|
|
owner: str
|
|
repo: str
|
|
issue_number: int
|
|
slug: str
|
|
agent_name: str
|
|
bottle_names: list[str] = field(default_factory=list)
|
|
backend_name: str = ""
|
|
agent_git_user: str = ""
|
|
pr_number: int | None = None
|
|
status: str = STATUS_RUNNING
|
|
last_checkin_at: str = ""
|
|
|
|
|
|
# --- Forge events (parsed webhook payloads) --------------------------------
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class IssueAssigned:
|
|
"""An issue gained an assignee — the trigger to consider a launch."""
|
|
|
|
owner: str
|
|
repo: str
|
|
issue_number: int
|
|
title: str
|
|
body: str
|
|
assignees: tuple[str, ...]
|
|
labels: tuple[str, ...]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CommentCreated:
|
|
"""A comment was posted on an issue or PR — a rehydrate trigger."""
|
|
|
|
owner: str
|
|
repo: str
|
|
issue_number: int
|
|
comment_id: int
|
|
author: str
|
|
body: str
|
|
is_pull: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PullRequestClosed:
|
|
"""A PR closed (merged or not) — the teardown trigger."""
|
|
|
|
owner: str
|
|
repo: str
|
|
pr_number: int
|
|
merged: bool
|
|
|
|
|
|
# Union of everything the webhook layer can emit.
|
|
ForgeEvent = IssueAssigned | CommentCreated | PullRequestClosed
|
|
|
|
|
|
# --- Provenance ------------------------------------------------------------
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ForgeOp:
|
|
"""One semantic forge operation the sidecar recorded."""
|
|
|
|
at: str # ISO timestamp
|
|
op: str # e.g. "post_comment", "read_pr", "signal_done"
|
|
target: int | None
|
|
detail: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Provenance:
|
|
"""The audit record for one run, served by the provenance API. Never
|
|
posted into the forge."""
|
|
|
|
slug: str
|
|
owner: str
|
|
repo: str
|
|
issue_number: int
|
|
agent_name: str
|
|
bottle_names: tuple[str, ...]
|
|
started_at: str
|
|
finished_at: str
|
|
exit_code: int | None
|
|
watchdog_fired: bool
|
|
ops: tuple[ForgeOp, ...]
|