"""State store interface + an in-memory implementation. The orchestrator persists one `RunRecord` per forge-targeted issue. At runtime `bootstrap` supplies an adapter over bot-bottle's `SqliteForgeStateStore`; the in-memory store here backs tests and a `--no-bot-bottle` dry mode. """ from __future__ import annotations from typing import Protocol from .model import RunRecord class StateStore(Protocol): """Thin CRUD surface. Mirrors bot-bottle's `ForgeStateStore` so the bootstrap adapter is a straight pass-through.""" def upsert(self, record: RunRecord) -> None: ... def get(self, owner: str, repo: str, issue_number: int) -> RunRecord | None: ... def delete(self, owner: str, repo: str, issue_number: int) -> None: ... def all(self) -> list[RunRecord]: ... class InMemoryStateStore: """Dict-backed `StateStore`, keyed by (owner, repo, issue_number).""" def __init__(self) -> None: self._by_key: dict[tuple[str, str, int], RunRecord] = {} def upsert(self, record: RunRecord) -> None: self._by_key[(record.owner, record.repo, record.issue_number)] = record def get(self, owner: str, repo: str, issue_number: int) -> RunRecord | None: return self._by_key.get((owner, repo, issue_number)) def delete(self, owner: str, repo: str, issue_number: int) -> None: self._by_key.pop((owner, repo, issue_number), None) def all(self) -> list[RunRecord]: return sorted( self._by_key.values(), key=lambda r: (r.owner, r.repo, r.issue_number), )