4.7 KiB
PRD prd-new: Fold bot-bottle-orchestrator into this repo
- Status: Active
- Author: didericis
- Created: 2026-07-01
- Issue: #321
Summary
Move the bot-bottle-orchestrator binary into bot_bottle/orchestrator/ as a
first-class subpackage. pip install bot-bottle gets you everything; the
orchestrator's entry point becomes python -m bot_bottle.orchestrator run. The
cross-repo CLI contract becomes an internal boundary, and the forge integration
layer (GiteaClient, ScopedForge, SqliteForgeStateStore) is promoted to
bot_bottle/contrib/ where it belongs.
Problem
The orchestrator and bot-bottle are tightly coupled:
- It always deploys on the same host.
- It imports from
bot_bottlefor the forge/state layer. - Its runner shims (
start --headless,commit,resume) map 1:1 to CLI commands incli.py— a breaking CLI change silently breaks the orchestrator with no CI signal. - Two repos means two version pins, two CI pipelines, and two install steps every time the deploy environment is rebuilt.
Goals / Success Criteria
- All orchestrator modules live under
bot_bottle/orchestrator/and the package is importable asfrom bot_bottle.orchestrator import .... python -m bot_bottle.orchestrator runstarts the webhook server.python -m bot_bottle.orchestrator statusprints tracked runs.- The forge integration layer (
GiteaClient,GiteaForge,ScopedForge,ForgeState,SqliteForgeStateStore) lives inbot_bottle/contrib/and is covered by tests intests/unit/orchestrator/. - All orchestrator unit tests pass under bot-bottle's existing CI
(
python -m unittest discover -s tests/unit). - No functional change to the orchestrator's external behaviour: same HTTP surface, same webhook protocol, same env-var config, same CLI flags.
Non-goals
- Replacing
SubprocessBottleRunnerwith a direct programmatic runner — the subprocess shim stays; theBottleRunnerprotocol remains the internal abstraction point. - Merging the orchestrator's SQLite DB with any other bot-bottle state store.
- Archiving
bot-bottle-orchestrator(that happens after this ships and the deploy is updated; out of scope for this PR).
Design
Package layout
bot_bottle/
orchestrator/
__init__.py
__main__.py # python -m bot_bottle.orchestrator
bootstrap.py # wires contrib modules → orchestrator core
config.py
events.py
lifecycle.py
model.py
provenance.py
runner.py
sidecar.py
store.py
targeting.py
watchdog.py
webhook.py
contrib/
forge/
__init__.py
base.py # ScopedForge: read-anywhere / write-scoped wrapper
gitea/
client.py # GiteaClient (urllib.request), GiteaForge
forge_state.py # ForgeState dataclass + SqliteForgeStateStore
tests/unit/orchestrator/
__init__.py
_fakes.py
test_config.py
test_events.py
test_lifecycle.py
test_provenance.py
test_runner.py
test_sidecar.py
test_store.py
test_targeting.py
test_watchdog.py
test_webhook.py
Module moves
Every orchestrator/ source file moves verbatim into bot_bottle/orchestrator/.
Internal imports are already relative (from .config import Config) so no
changes are needed inside the orchestrator modules themselves.
bootstrap.py is the only file that changes meaningfully: the lazy bot_bottle
imports become direct relative imports (from ..contrib.gitea.client import …),
and the _require_bot_bottle() guard is removed since the package is always
present.
New contrib modules
bot_bottle/contrib/forge/base.py — ScopedForge
Wraps any forge object and enforces read-anywhere / write-scoped access: reads
pass through unconditionally; post_comment and update_description raise
PermissionError for issue/PR numbers outside the assigned set.
bot_bottle/contrib/gitea/client.py — GiteaClient, GiteaForge
GiteaClient is a thin urllib.request-only HTTP wrapper (no new Python
dependencies). GiteaForge composes a client and exposes the forge protocol:
is_org_member, read_issue, read_pr, read_comments, post_comment,
update_description.
bot_bottle/contrib/gitea/forge_state.py — ForgeState, SqliteForgeStateStore
ForgeState is a dataclass mirroring RunRecord field-for-field. SqliteForgeStateStore
backs it with SQLite (stdlib sqlite3): a single forge_state table with one
row per (owner, repo, issue_number).
Test migration
All orchestrator test files move to tests/unit/orchestrator/ with absolute
imports updated from orchestrator.X to bot_bottle.orchestrator.X. The unit
discovery command (-s tests/unit) picks them up automatically — no CI changes
required.