Part II: Fold bot-bottle-orchestrator into this repo #322

Open
didericis-claude wants to merge 7 commits from fold-orchestrator-subpackage into main
Collaborator

Closes #321.

PRD: prd-new-fold-orchestrator-subpackage.md

Summary

  • Move all orchestrator modules into bot_bottle/orchestrator/ as a first-class subpackage
  • Create bot_bottle/contrib/forge/base.py (ScopedForge), bot_bottle/contrib/gitea/client.py (GiteaClient, GiteaForge), and bot_bottle/contrib/gitea/forge_state.py (ForgeState, SqliteForgeStateStore) — the forge integration layer that belongs in the main package
  • Migrate all orchestrator unit tests to tests/unit/orchestrator/
  • Entry point becomes python -m bot_bottle.orchestrator run; no change to HTTP surface, webhook protocol, or env-var config
Closes #321. PRD: [prd-new-fold-orchestrator-subpackage.md](https://gitea.dideric.is/didericis/bot-bottle/src/branch/fold-orchestrator-subpackage/docs/prds/prd-new-fold-orchestrator-subpackage.md) ## Summary - Move all orchestrator modules into `bot_bottle/orchestrator/` as a first-class subpackage - Create `bot_bottle/contrib/forge/base.py` (`ScopedForge`), `bot_bottle/contrib/gitea/client.py` (`GiteaClient`, `GiteaForge`), and `bot_bottle/contrib/gitea/forge_state.py` (`ForgeState`, `SqliteForgeStateStore`) — the forge integration layer that belongs in the main package - Migrate all orchestrator unit tests to `tests/unit/orchestrator/` - Entry point becomes `python -m bot_bottle.orchestrator run`; no change to HTTP surface, webhook protocol, or env-var config
didericis-claude added 1 commit 2026-07-01 13:15:03 -04:00
didericis added 2 commits 2026-07-01 13:18:43 -04:00
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
docs: mark fold-orchestrator PRD as Active
lint / lint (push) Successful in 2m4s
test / unit (pull_request) Successful in 56s
test / integration (pull_request) Successful in 22s
test / coverage (pull_request) Failing after 1m7s
df1f0e8f70
didericis added 1 commit 2026-07-01 15:35:41 -04:00
test: add coverage for orchestrator + gitea client (diff gate 77% → 98%)
lint / lint (push) Failing after 2m5s
test / unit (pull_request) Successful in 53s
test / integration (pull_request) Successful in 24s
test / coverage (pull_request) Successful in 1m12s
57290da1e8
Three new unit test modules:
- tests/unit/test_contrib_gitea_client.py — GiteaClient (urllib mocked)
  and GiteaForge delegation
- tests/unit/orchestrator/test_main.py — __main__ run/status commands
- tests/unit/orchestrator/test_bootstrap.py — _token, BotBottleStateStore,
  _to_forge_state/_to_record, make_forge, make_sidecar, build

Augments to existing suites:
- test_events: non-"created" comment action ignored
- test_lifecycle: _iso_now callable, untracked-issue comment ignored,
  untracked-PR closed ignored (covers _find_by_pr return-None path)
- test_runner: destroy command, _default_run via subprocess mock
- test_sidecar: _jsonable dataclass/list branches, OpLog.read on missing
  file, drain_done_events on corrupted file, socket _Handler invalid-JSON
  and empty-line paths, serve() with pre-existing socket path
- test_watchdog: _loop body covered by patching _TICK_SECS to 0.01s
- test_webhook: unknown GET path returns 404

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
didericis-claude changed title from PRD: Fold bot-bottle-orchestrator into this repo to Part II: Fold bot-bottle-orchestrator into this repo 2026-07-01 15:44:52 -04:00
didericis added 1 commit 2026-07-01 15:47:38 -04:00
fix: resolve pylint/pyright issues in new test files
lint / lint (push) Successful in 2m7s
test / unit (pull_request) Successful in 57s
test / integration (pull_request) Successful in 17s
test / coverage (pull_request) Successful in 1m4s
71699b3ecd
- test_contrib_gitea_client: remove unused Any import, fix _mock_response
  to use return_value instead of lambda (unknown lambda type), narrow
  HTTPError hdrs type, add type annotations to fake_urlopen helpers,
  suppress protected-access for _request tests
- test_bootstrap: annotate **kw as **kw: object, use dict literal,
  unpack server_address via index to avoid tuple type mismatch
- test_main: remove unused MagicMock import
- test_watchdog: guard store.get() result before accessing .status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
didericis added 1 commit 2026-07-01 15:48:15 -04:00
refactor(orchestrator): swap SubprocessBottleRunner → ProgrammaticBottleRunner
lint / lint (push) Failing after 2m15s
test / unit (pull_request) Successful in 51s
test / integration (pull_request) Successful in 21s
test / coverage (pull_request) Successful in 1m7s
d5fb159857
BottleRunner Protocol tightened: start() → str, freeze/resume/destroy → None.
RunResult removed. lifecycle.py unpacks the slug directly. FakeRunner and
test_runner updated to match. Config.bot_bottle_cli dropped (nothing uses it).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
didericis added 1 commit 2026-07-01 16:28:02 -04:00
fix: resolve pylint/pyright issues in runner, sidecar, and test_runner
lint / lint (push) Successful in 2m1s
test / unit (pull_request) Successful in 50s
test / integration (pull_request) Successful in 17s
test / coverage (pull_request) Successful in 1m3s
18e610c7a8
runner.py: use 'from bot_bottle import api' (satisfies R0402) with
type: ignore and pylint disable for the cross-branch dependency on
bot_bottle.api (added in PR #318, which merges before this one).
sidecar.py: add pylint disable for intentional broad-exception-caught.
test_runner.py: annotate _make_api_stub(**overrides: object) -> Any and
type stub variable as Any to allow attribute assignment without
type: ignore per-line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Collaborator

Scope additions — fold in from the PRD 0070 consolidation work

The per-host orchestrator (PRD 0070, shipped as the #367–#379 stack) and this forge orchestrator should be one unified orchestrator — one python -m bot_bottle.orchestrator process that does both per-host bottle management (registry / attribution / /resolve / gateway) and the forge webhook + run-loop. Both currently land in bot_bottle/orchestrator/ with the same entry point, so unifying them requires refactoring (dispatch: bare = control-plane serve, run = forge loop, etc.). Folding the following into this PR's scope:

  1. Rename the sidecars image → gateway. Finish the sidecar→gateway retirement deferred in #364 (kept the names then to avoid rebuild churn): Dockerfile.sidecars, bot-bottle-sidecars, BOT_BOTTLE_SIDECAR_IMAGEgateway. (Independent of the unification.)

  2. Dedicated lean orchestrator image. Its own Dockerfile.orchestrator (python-slim + control plane, no mitmproxy), dropping the current shortcut of running the bot-bottle-sidecars bundle image with the repo bind-mounted. Keeps the control/data-plane image boundary (orchestrator carries no byte-parsing machinery).

  3. iroh transport for the remote web UI. Bundle iroh into the orchestrator image as a separate proxy process (terminates the p2p link, forwards to the stdlib Python control plane over 127.0.0.1) so the Python side stays zero-runtime-deps. A remote web UI then reaches the orchestrator with no inbound firewall port and node-key auth for free.

  4. Long-poll / pull-model launcher. A host-side launcher that pulls launch intents from the orchestrator (host→container), so the web UI can drive launches without any container→host traffic (the NixOS firewall drops container→host on the docker bridge — see the 0070 cut-over notes). The poll response is the structured launch request.

DB-as-volume was considered and dropped — 0067's storage work is merged and the docker bind-mount is fine; only a firecracker-VM orchestrator would revisit it.

Context / prior art: the whole 0070 consolidation stack (containerized orchestrator + shared multi-tenant gateway, test_sandbox_escape green on real docker) is the monster PR that this unifies with.

## Scope additions — fold in from the PRD 0070 consolidation work The per-host orchestrator (PRD 0070, shipped as the #367–#379 stack) and this forge orchestrator **should be one unified orchestrator** — one `python -m bot_bottle.orchestrator` process that does both per-host bottle management (registry / attribution / `/resolve` / gateway) **and** the forge webhook + run-loop. Both currently land in `bot_bottle/orchestrator/` with the same entry point, so unifying them **requires refactoring** (dispatch: bare = control-plane serve, `run` = forge loop, etc.). Folding the following into this PR's scope: 1. **Rename the `sidecars` image → `gateway`.** Finish the sidecar→gateway retirement deferred in #364 (kept the names then to avoid rebuild churn): `Dockerfile.sidecars`, `bot-bottle-sidecars`, `BOT_BOTTLE_SIDECAR_IMAGE` → `gateway`. (Independent of the unification.) 2. **Dedicated lean orchestrator image.** Its own `Dockerfile.orchestrator` (python-slim + control plane, **no mitmproxy**), dropping the current shortcut of running the `bot-bottle-sidecars` bundle image with the repo bind-mounted. Keeps the control/data-plane image boundary (orchestrator carries no byte-parsing machinery). 3. **iroh transport for the remote web UI.** Bundle iroh into the orchestrator image as a **separate proxy process** (terminates the p2p link, forwards to the stdlib Python control plane over `127.0.0.1`) so the Python side stays zero-runtime-deps. A remote web UI then reaches the orchestrator with **no inbound firewall port** and node-key auth for free. 4. **Long-poll / pull-model launcher.** A host-side launcher that **pulls** launch intents from the orchestrator (host→container), so the web UI can drive launches without any container→host traffic (the NixOS firewall drops container→host on the docker bridge — see the 0070 cut-over notes). The poll response *is* the structured launch request. DB-as-volume was considered and **dropped** — 0067's storage work is merged and the docker bind-mount is fine; only a firecracker-VM orchestrator would revisit it. Context / prior art: the whole 0070 consolidation stack (containerized orchestrator + shared multi-tenant gateway, `test_sandbox_escape` green on real docker) is the monster PR that this unifies with.
Some checks are pending
lint / lint (push) Successful in 2m1s
test / unit (pull_request) Successful in 50s
test / integration (pull_request) Successful in 17s
test / coverage (pull_request) Successful in 1m3s
This pull request has changes conflicting with the target branch.
  • bot_bottle/orchestrator/__init__.py
  • bot_bottle/orchestrator/__main__.py
  • bot_bottle/orchestrator/lifecycle.py
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fold-orchestrator-subpackage:fold-orchestrator-subpackage
git checkout fold-orchestrator-subpackage
Sign in to join this conversation.