e3376b88098e6d5a4d0bae49a6e9b37273a3b331
738 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e3376b8809 |
chore(backend): drop dead code left by the InfraService refactor
Remove symbols with no remaining consumer: - `gateway_name` property on Docker/MacosInfraService — nothing uses it; the launch flow reads `gateway().name` off the Gateway service. (+ its docker test.) - firecracker `infra_vm` EGRESS_PORT / SUPERVISE_PORT / GIT_HTTP_PORT — dead duplicates; launch.py uses supervisor.types / docker.egress / a local const. - macOS `INFRA_DB_VOLUME` back-compat alias — unused since the DB-volume test moved to `ORCHESTRATOR_DB_VOLUME`. - docker `infra.py`: the unused `ORCHESTRATOR_SOURCE_HASH_LABEL` import and the `__all__` re-exports of the orchestrator constants (consumers import them from `docker.orchestrator`, not `docker.infra`). - macOS `infra.py`: the unused `GatewayError` import + re-export. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
afb92ca155 |
refactor(backend): InfraService ABC + FirecrackerInfraService
Add a backend-neutral `InfraService` ABC (backend/infra_service.py) so all three backends present the same composer contract: `orchestrator()` / `gateway()` accessors, `ensure_running(*, startup_timeout) -> str` (the host control-plane URL), `stop()`, plus concrete `url()` / `is_healthy()` that delegate to the orchestrator. `DockerInfraService` and `MacosInfraService` now subclass it. Give firecracker the missing class: `FirecrackerInfraService` (backend/firecracker/infra.py) wraps the `infra_vm` substrate as the pair coordinator (adopt-or-boot-both under the singleton flock). `infra_vm` keeps only the plane-agnostic substrate; its `ensure_running` / `_adopt` / `InfraEndpoint` move to the class, and the coordinator helpers it now calls cross-module go public (`singleton_lock` / `expected_version` / `adoptable` / `record_booted_version`). Unify the return type on the way: `ensure_running` returns the URL string everywhere (was `str` for docker, two different `InfraEndpoint`s for macOS/firecracker), and those `InfraEndpoint`s are deleted — the services are the source of truth (callers read `gateway().address()` / `orchestrator().url()`). docker's `url` property becomes the inherited `url()`. backend.py / consolidated_launch / image_builder + tests updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cb2d778a8f |
refactor: remove leftovers from the orchestrator/gateway consolidation
Sweep for vestiges of the old combined-plane model and the pre-split shared rootfs. Two are load-bearing, the rest are stale docs/comments: - Bug: macOS `enumerate_active` only excluded the gateway container from the agent list, so after the split the orchestrator container (`bot-bottle-mac-orchestrator`, also `bot-bottle-`-prefixed) was enumerated as a phantom agent. Exclude both infra containers; test covers it. - Dead code: the gateway `bootstrap.py` still carried an `orchestrator` daemon spec + `_OPT_IN_DAEMONS` + a signing-key/JWT env branch, all for the old combined container where the gateway process could also run the control plane. No backend ever requests it now — removed; the key-stripping stays as defense-in-depth. Stale-comment reframes: "the/single infra container" -> the orchestrator + gateway pair (or the specific plane); "shared rootfs / bb_role init / one published rootfs" -> the per-plane rootfs + `role_init`; the deleted Dockerfile.infra references in Dockerfile.orchestrator/.gateway; and the macOS "one infra container ... same address" docstring + its now-false share-one-address test (the planes are distinct containers with distinct addresses). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d0d1da612e |
refactor(firecracker): split the combined rootfs into per-plane artifacts
Each infra VM now boots its own rootfs instead of a shared combined image, so
the exposed gateway VM no longer carries buildah + the control-plane code it
never runs (a fatter, less-isolated exposed surface — the opposite of the plane
split's intent). The combined image bought only "one artifact"; each VM already
kept a full copy of the shared rootfs, so nothing was saved at boot.
* orchestrator rootfs — control plane + buildah (Dockerfile.orchestrator.fc,
FROM orchestrator); the slim gateway rootfs boots bot-bottle-gateway:latest
directly. Dockerfile.infra.fc (the combined image) is deleted.
* `_infra_init` splits into `_orchestrator_init` / `_gateway_init` (shared
preamble via `_init_head`); each per-plane rootfs bakes only its role init,
so the `bb_role` cmdline branch is gone.
* infra_artifact is role-parametrized: a per-role package
(bot-bottle-firecracker-<role>), version hash, URL, cache dir, and candidate
subdir. `ensure_built` pulls both; `_expected_version` combines both markers.
* publish_infra builds the images once, then builds + publishes an
orchestrator and a gateway artifact under DIR/<role>/.
coverage.sh's candidate-dir plumbing is layout-agnostic (publish_infra --output
now fills DIR/<role>/, which ensure_artifact_gz reads). Full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
8d583789d2 |
refactor(firecracker): orchestrator under the Orchestrator service ABC
Pull the control plane's host-side logic out of `infra_vm` into `FirecrackerOrchestrator` (backend/firecracker/orchestrator.py): booting the orchestrator microVM on its link with the persistent registry volume (/dev/vdb), seeding the host-canonical signing key over SSH, waiting for /health, and the buildah SSH target. `url()` == `gateway_url()` (the gateway resolves the orchestrator by guest IP via bb_orch). This completes the trio — docker, macOS, firecracker — behind both the Gateway and Orchestrator ABCs. `infra_vm` stays the pair coordinator + shared substrate: `ensure_running` now mints nothing itself — it constructs both services (lazy import to break the cycle), calls `orchestrator.ensure_running()` first, then `gateway.connect_to_orchestrator(orch.gateway_url(), orch.mint_gateway_token())`. The plane-agnostic boot primitives graduate to public API (`_boot_vm`/ `_push_secret` -> `boot_vm`/`push_secret`) now that they're consumed only across modules; `InfraVm` loses its `orchestrator_url` (moved to the service) and `InfraEndpoint.orchestrator` is now the `FirecrackerOrchestrator` service. Container-lifecycle tests split into test_firecracker_orchestrator; the infra_vm tests keep the substrate + pair-coordinator coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5465379654 |
refactor(orchestrator): macOS orchestrator under the Orchestrator ABC
Extract the Apple-Container control-plane lifecycle out of `MacosInfraService` into `MacosOrchestrator` (backend/macos_container/orchestrator.py): the container run on the host-only control network, the container-only DB volume, the source-hash recreate gate, health polling against the resolved control-network address, and `probe_orchestrator_url`. `url()` == `gateway_url()` here (Apple has no container DNS, so one resolved address serves the CLI and the gateway). `MacosInfraService` now composes `orchestrator()` + `gateway()`: ensure the networks, build both images (each service self-builds via `ensure_built` — added to `MacosGateway` too), bring the orchestrator up first, then connect the gateway with the orchestrator-minted token. `ORCHESTRATOR_IMAGE` + the DB-volume constant move to the orchestrator module (with back-compat aliases where imported). Container-lifecycle tests split into test_macos_orchestrator; test_macos_infra now covers the composition. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cc4e29c3da |
refactor(orchestrator): introduce the Orchestrator service ABC (docker impl)
Mirror the Gateway work for the control plane. Add the backend-neutral `Orchestrator` service ABC in orchestrator/lifecycle.py — build the image/rootfs, `ensure_running` (start + health-gate), `is_running`/`is_healthy`/`stop`, `url()` (host-facing) vs `gateway_url()` (what the gateway resolves against), and a concrete `mint_gateway_token()` (the orchestrator holds the signing key, so it issues the gateway's token — #469). `DockerOrchestrator` (backend/docker/orchestrator.py) is the first impl, extracted out of `DockerInfraService`: the control-plane container run, the `--internal` control network, the source-hash recreate gate, health polling, and the orchestrator constants (name/label/network/image/dockerfile/hash-label). `DockerInfraService` now composes `orchestrator()` + `gateway()` — each builds its own image via `ensure_built`, the orchestrator comes up first, then the gateway is connected with the orchestrator-minted token. Its `url`/`is_healthy` delegate to the orchestrator service. Split the container-lifecycle tests into test_docker_orchestrator; test_docker_infra now covers the composition. macOS + firecracker follow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
57d32d2c5c |
refactor(orchestrator): rename in-guest core Orchestrator -> OrchestratorCore
Free the `Orchestrator` name for the incoming host-side control-plane service ABC (parallel to `Gateway`). The in-guest control-plane core (registry + broker, in orchestrator/service.py) becomes `OrchestratorCore`; server.py, __main__.py, the lazy facade, and the two service/server tests updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
07c975636d |
refactor(backend): verb-first names for the provisioning modules
Rename the module files to match their functions' verb-first names: `bottle_provision.py` -> `provision_bottle.py`, `gateway_provision.py` -> `provision_gateway.py` (and the test file to match). Imports, docstrings, and the git_gate/service.py pointer updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
96ab8e15a2 |
refactor(backend): rename teardown_consolidated -> deprovision_consolidated
The backends' public teardown entry point now pairs with `launch_consolidated` under the provision/deprovision verb used everywhere else (`deprovision_bottle`, `deprovision_git_gate`). Renamed across all three backends' consolidated_launch + launch modules and the tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a98f559bbe |
refactor(backend): split bottle vs gateway provisioning modules
Rename `consolidated_util` -> `bottle_provision` and align it with `gateway_provision` as the two provisioning domains: `bottle_provision` owns the bottle lifecycle (`provision_bottle` + `deprovision_bottle`, the latter renamed from `teardown_consolidated`), `gateway_provision` owns the git-gate state pushed through the transport (`provision_git_gate`/`deprovision_git_gate`). The three backends' consolidated_launch modules import from the new module; their own public `teardown_consolidated` wrappers (called by launch.py) keep their names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
343f3a0735 |
refactor(backend): move git-gate provisioning to a neutral module
`backend/docker/gateway_provision.py` had no docker-specific code left once `DockerGatewayTransport` moved out — `provision_git_gate` / `deprovision_git_gate` drive any `GatewayTransport`, and the guest-side paths they write (`/git-gate/creds/<id>`, `/git/<id>`, `/etc/git-gate/...`) are identical inside every backend's gateway. Yet the neutral `backend/consolidated_util.py` reached into the docker package to import them. Move it to `backend/gateway_provision.py`, a sibling of its only importer. The gateway *package* can't host it (git_gate already imports gateway.git_gate, so gateway importing git_gate would cycle), but the backend layer uses git_gate freely. Docstrings + the git_gate/service.py pointer updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e1fd8ef1b4 |
refactor(gateway): each backend's transport in its own gateway_transport.py
Give every backend a `gateway_transport.py` holding just its `GatewayTransport` impl, named consistently with the backend: `DockerGatewayTransport`, `MacosGatewayTransport` (was `AppleGatewayTransport`), and `FirecrackerGatewayTransport` (was `SshGatewayTransport`). - docker: split `DockerGatewayTransport` out of `gateway_provision.py`, which keeps only the backend-neutral provisioning logic (provision_git_gate / deprovision_git_gate) the other backends share. - macOS: `gateway_provision.py` held only the transport, so it's replaced by `gateway_transport.py`. - firecracker: move the transport out of `gateway.py`. Behaviour-preserving; importers + tests updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5bf9f052b9 |
refactor(firecracker): move gateway logic into the gateway service
Pull the gateway's host-side logic out of `infra_vm` and into `FirecrackerGateway`'s own methods, so the gateway is self-contained and `infra_vm` shrinks toward being just the pair coordinator (a step toward removing it). Now living in the gateway service: the gateway VM boot + `bb_orch` cmdline, the pre-minted JWT push, the mitmproxy CA fetch over SSH, the `SshGatewayTransport` exec/cp, and the lifecycle predicates (is_running/stop/address). `ensure_running` / `_adopt` construct the gateway and call `connect_to_orchestrator` (lazy import to break the cycle); the InfraEndpoint's gateway is now the `FirecrackerGateway` service. What deliberately stays shared in `infra_vm` (documented at the top of gateway.py): the plane-agnostic VM substrate the orchestrator VM also needs (`_boot_vm`, the stable SSH keypair, the secret-push retry, PID lifecycle), the pair coordinator (`ensure_running`: orchestrator-first health gate + singleton lock + adoption/version marker), and the single shared `bb_role`-branched init baked into the one published rootfs both VMs boot — the guest-side gateway startup can't live in a host method. These are the seam that moves to a neutral module when the Orchestrator service lands and `infra_vm` dissolves. CA fetch now raises GatewayError (was die/SystemExit) to match the ABC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d54c559a3a |
refactor(gateway): Firecracker gateway under the Gateway service ABC
Add `FirecrackerGateway`, the Firecracker implementation of the shared `Gateway` service — completing the trio (docker, macOS, firecracker) behind one uniform contract. The gateway here is a whole microVM, so the adapter composes `infra_vm`'s VM primitives (boot, SSH secret push, netpool links) into the ABC surface: `connect_to_orchestrator(url, token)` parses the orchestrator guest IP off the URL and boots the gateway VM seeding the pre-minted token; `address()` is the gateway link's guest IP; `ca_cert_pem()` fetches over SSH (adapting the running VM when this process didn't boot it); `provisioning_transport()` is the SSH exec/cp transport. Trust-model alignment with the other backends: minting moves out of `_push_gateway_jwt` to the host composition point — `ensure_running` mints the role-scoped `gateway` JWT and threads it through `boot_gateway(orch_ip, token)`, so the push step only writes the token the gateway presents (#469). `infra_vm` keeps driving the orchestrator+gateway pair as a singleton and gains gateway-only helpers (`gateway_running`/`stop_gateway`/`gateway_guest_ip`/ `adopted_gateway_vm`); the consolidated launch reads the gateway's transport + CA off the service. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c4ccd74f9b |
refactor(gateway): macOS gateway under the Gateway service ABC
Extract the Apple-Container gateway data plane out of `MacosInfraService` into `MacosGateway`, the macOS implementation of the shared `Gateway` service. `connect_to_orchestrator(url, gateway_token)` runs the triple-homed gateway container (egress + agent + control networks) carrying the mitmproxy CA and the pre-minted `gateway` token; `address()` / `ca_cert_pem()` / `provisioning_transport()` round out the contract. The infra service now composes the gateway: `ensure_running` mints the role-scoped `gateway` JWT (it holds the signing key; the gateway never does — #469) and hands it to `gateway().connect_to_orchestrator(...)`, and `ca_cert_pem` delegates to the service. The inlined `_ensure_gateway_container` + CA-read are gone. `AppleGatewayTransport` + the gateway container name/label now live with the gateway module, breaking the old infra→provision coupling. Splits the gateway-run + CA tests out of test_macos_infra into a dedicated test_macos_gateway; the infra tests mock `svc.gateway`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
05df21f210 |
refactor(gateway): introduce the Gateway service ABC (docker impl)
Replace the docker backend's ad-hoc gateway wiring with the shared `Gateway` service ABC (in the gateway package), the first of the two per-host service classes that supersede the per-backend infra glue. The contract is backend-neutral: `connect_to_orchestrator(url, gateway_token)` binds the gateway to its control plane and brings it up, `address()` reports the agent-facing proxy target, `ca_cert_pem()` vends the mitmproxy CA, and `provisioning_transport()` hands back the exec/cp seam git-gate provisioning stages per-bottle repos + deploy keys through. `GatewayProvisionError` + `GatewayTransport` move to the ABC so every backend shares them. Key trust-model change: the gateway no longer mints its own token. It never holds the signing key (#469), so the orchestrator mints the role-scoped `gateway` JWT and hands it in via `connect_to_orchestrator`; the gateway only injects it. `DockerGateway.ensure_running` becomes `connect_to_orchestrator` (stashing the URL + token as instance state), and the docker launch flow reads `address()` / `provisioning_transport()` off the service rather than re-deriving the container IP + transport itself. macOS + firecracker gateways move under the ABC in following commits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
18d9b81add |
feat(firecracker): split orchestrator and gateway into separate VMs (PRD 0070)
tracker-policy-pr / check-pr (pull_request) Successful in 12s
test / integration-docker (pull_request) Successful in 19s
lint / lint (push) Successful in 53s
test / unit (pull_request) Failing after 1m46s
test / integration-firecracker (pull_request) Failing after 2m42s
test / coverage (pull_request) Has been skipped
test / publish-infra (pull_request) Has been skipped
Now that #469 got the DB off the data plane, the Firecracker infra runs as two microVMs instead of one — mirroring the docker/macos plane split: * orchestrator VM (ORCH_IFACE) — control plane + buildah image builds; sole DB opener; host-seeded signing key. No gateway daemons. * gateway VM (new GW_IFACE) — egress / git-http / supervise data plane; mitmproxy CA + a host-minted `gateway` JWT (never the key). Reaches the orchestrator only over the one nft forward rule its link allows. Both boot the SAME shared infra rootfs; a `bb_role=` kernel-cmdline arg selects which plane a VM's PID-1 init starts, so there is still one published artifact. The gateway learns the orchestrator's address via `bb_orch=` on the cmdline (no IP baked into the artifact). Isolation is nearly free: agents were already nft-dropped except the DNAT'd gateway ports, so re-pointing that single DNAT rule at the gateway VM (`dnat to gw_guest`) severs every agent's L3 route to the control plane. The only added nft is the second infra link's mirror block (masquerade egress + forward accept, which subsumes gateway->orchestrator) in the shared shell script and the NixOS module. netpool gains GW_IFACE + gw_slot() (the /31 above the orch link); firecracker_vm.boot gains extra_boot_args for the role cmdline; infra_vm ensure_running() boots + adopts the pair (orchestrator first, then the gateway that resolves policy against it) and returns an InfraEndpoint mirroring the docker/macos shape. Builds stay in the orchestrator (PRD 0070 v1); the gateway is the slim unit. Unit-tested (test_firecracker_infra_vm rewritten for two VMs; gw_slot helper test added); the KVM boot / L3-isolation checks are validated on a Firecracker host. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
b676ce3156 |
feat(macos): split orchestrator and gateway into separate containers (PRD 0070)
test / integration-docker (pull_request) Successful in 18s
tracker-policy-pr / check-pr (pull_request) Successful in 22s
test / unit (pull_request) Failing after 42s
lint / lint (push) Successful in 57s
test / integration-firecracker (pull_request) Successful in 3m22s
test / coverage (pull_request) Has been skipped
test / publish-infra (pull_request) Has been skipped
The single Apple container existed only because two guests writing one bot-bottle.db over virtiofs would race incoherent fcntl locks; #469 removed that (the data plane no longer opens the DB), so the macOS backend now runs the planes as two containers like docker: * bot-bottle-mac-orchestrator — lean control plane on the host-only `bot-bottle-mac-control` network only (image Dockerfile.orchestrator, `-m bot_bottle.orchestrator`). Sole mounter of the container-only DB volume; holds the signing key. The CLI + gateway reach it at its control-network address. * bot-bottle-mac-infra — the gateway, triple-homed on the NAT egress net, the host-only agent net, and the control net. Resolves the orchestrator by IP (Apple has no container DNS) via BOT_BOTTLE_ORCHESTRATOR_URL; holds the CA + gateway JWT. Agents sit on the agent network only, so they have no route to the control plane. ensure_networks gains the control network; MacosInfraService brings up the orchestrator then the gateway; probe_orchestrator_url + ca_cert_pem target the right containers. pyright 0 errors; unit suite green (2274). Needs a real Apple-container host to validate the networking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d62d19a2e7 |
feat(docker): split orchestrator and gateway into separate containers (PRD 0070)
tracker-policy-pr / check-pr (pull_request) Successful in 6s
test / integration-docker (pull_request) Successful in 13s
test / unit (pull_request) Failing after 37s
lint / lint (push) Successful in 57s
test / integration-firecracker (pull_request) Successful in 3m19s
test / coverage (pull_request) Has been skipped
test / publish-infra (pull_request) Has been skipped
Now that #469 got the DB off the data plane, the docker backend runs the control plane and data plane as two containers instead of the combined `bot-bottle-infra` container: * bot-bottle-orchestrator — the lean control-plane container (image Dockerfile.orchestrator, `-m bot_bottle.orchestrator`). Joins the new `bot-bottle-orchestrator` control network (`--internal`) only, plus a host-loopback publish for the CLI. Sole opener of bot-bottle.db; holds the signing key; no CA, no gateway daemons. * bot-bottle-orch-gateway — the data-plane container (DockerGateway), dual-homed on the agent `bot-bottle-gateway` network AND the control network, so it resolves the orchestrator by name (http://bot-bottle-orchestrator:8099) while agents — never on the control network — have no route to the control plane (the L3 block, not just the JWT). Holds the gateway JWT + the mitmproxy CA. DockerInfraService now orchestrates the pair (builds gateway + orchestrator images, brings up the orchestrator then the gateway) and exposes gateway_name; the launch flow attributes agents against the gateway container. DockerGateway gains a control_network it joins after run. rotate_ca / the CA read target the gateway container. The combined Dockerfile.infra is no longer built by docker (firecracker/macOS still use it until their splits). pyright 0 errors; unit suite green (2273). Integration tests updated for the two-container shape but need a real-docker run to validate the networking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7e9ad8a78d |
Merge main into fix/db-off-data-plane-469
tracker-policy-pr / check-pr (pull_request) Successful in 13s
test / integration-docker (pull_request) Successful in 35s
test / unit (pull_request) Successful in 47s
lint / lint (push) Successful in 57s
test / integration-firecracker (pull_request) Successful in 3m35s
test / coverage (pull_request) Successful in 32s
test / publish-infra (pull_request) Has been skipped
Brings in main's 8 commits — #470 (backend-agnostic CI guards: BackendStatus enum, quiet status(), is_backend_available/is_backend_ready, tests/_backend.py skip_unless_backend) plus firecracker status() readiness checks (kvm/kernel/ dropbear/mke2fs). Reconciled #470's additions into the reorg'd backend structure: * BackendStatus enum + the status(*, quiet=) ABC signature -> backend/base.py * is_backend_available / is_backend_ready -> backend/selection.py * exposed all three through the lazy backend facade (__init__). Integration-test conflicts were our control_plane->orchestrator renames vs main's skip_unless_docker -> skip_unless_backend swap: kept our refactored import paths + main's guard. Repointed main's new is_backend_* tests to patch selection._backends (our moved home) instead of the facade. pyright: 0 errors. Full unit suite green (2272). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a68ee778f1 |
firecracker status(): open /dev/kvm O_RDWR; check kernel, dropbear, mke2fs
test / integration-docker (push) Successful in 15s
test / unit (push) Successful in 40s
Update Quality Badges / update-badges (push) Successful in 44s
lint / lint (push) Successful in 58s
test / integration-firecracker (push) Successful in 5m5s
test / coverage (push) Successful in 49s
test / publish-infra (push) Successful in 2m4s
Two reviewer findings addressed: 1. _kvm_accessible() now opens /dev/kvm with O_RDWR|O_CLOEXEC instead of read-only. VM creation requires write access; a read-only descriptor can satisfy KVM_GET_API_VERSION but fails at boot time. 2. status() now checks every hard prerequisite that require_firecracker() checks at launch: guest kernel image, static dropbear binary, and mke2fs. Previously a host with a configured TAP pool but missing artifacts could pass status() and then fail during launch. Regression coverage: TestFirecrackerKvmCheck gains test_kvm_open_rdwr_fails; TestFirecrackerArtifactCheck covers each missing artifact; existing TestFirecrackerStatus fixtures updated to stub the new checks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
de02d13ccf |
firecracker status(): add binary and KVM readiness checks
Adds `_firecracker_binary_ok()` (runs `firecracker --version`) and `_kvm_accessible()` (opens /dev/kvm, issues KVM_GET_API_VERSION ioctl) and gates `status()` on both, so the launch preflight reports missing binary or inaccessible KVM before the caller ever attempts a boot. Regression tests in TestFirecrackerBinaryCheck, TestFirecrackerKvmCheck, and TestFirecrackerStatusRuntime cover all branches. Updated the pre-existing TestFirecrackerStatus fixture to also stub the two new helpers so it still exercises only the tap-pool/nft logic it was designed for. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c740d1e145 |
fix: add missing type annotations and remove unused variables in new tests
Pyright flagged three inner class `status` methods missing a type annotation on the `quiet` parameter, and two unused variables (`written`, `real_status`) left over from an earlier draft of test_docker_quiet_true_suppresses_stderr. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
177721c286 |
test: cover is_backend_available, is_backend_ready, and status(quiet=True)
Add unit tests for the three new public symbols introduced by this PR: - TestIsBackendAvailable — delegates to has_backend; returns True/False correctly for available and unavailable backends - TestIsBackendReady — unknown names return False; known names return True/False based on status() return code; quiet flag is forwarded - TestBackendStatusQuiet — verifies the quiet=True branch in DockerBottleBackend, FirecrackerBottleBackend, and MacosContainerBottleBackend: return code is propagated and diagnostic stderr is suppressed Together these bring diff-coverage for the PR's changed lines to 100%. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
5bcf3db1f8 |
fix: align skip-guard tests with is_backend_ready and drop unused import
tests/_backend.py imported is_backend_available but never called it, causing a pyright reportUnusedImport error. Remove the unused import. test_backend_skip_guards.py was patching tests._backend.has_backend but _backend.py calls is_backend_ready — the mock never intercepted the real call, producing AttributeError at test runtime. Update all patch targets to is_backend_ready and add quiet=False to the assert_called_once_with assertion to match the actual call signature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
dcd658ece1 |
feat(backend): BackendStatus enum, quiet status(), is_backend_ready()
Add a module-level BackendStatus(IntEnum) with READY=0 to replace magic 0 comparisons. Extend BottleBackend.status() with a quiet parameter: quiet=False (default) prints diagnostics; quiet=True returns the code silently for programmatic checks. Add is_backend_available() (cheap PATH check, alias for has_backend) and is_backend_ready(name, *, quiet=False) (full status() check) to the backend package for callers that need to distinguish availability from readiness. Update tests/_backend.py guards to use is_backend_ready(quiet=False) so diagnostic output is printed during test discovery, giving the operator a concrete reason for each skip rather than a bare "prerequisites unavailable" message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
dfb56f8fe9 |
ci: reuse backend.has_backend / backend status for readiness
Review feedback: the hand-rolled `/dev/kvm` + docker-daemon capability probes duplicated logic the CLI already owns. Delegate instead. - tests/_backend.py skip guards now gate on `bot_bottle.backend.has_backend` (each backend's `is_available()` classmethod — the same probe behind `./cli.py backend status`), dropping the bespoke `Capability` probes. - Remove tests/backend_preflight.py; the docker integration job runs `./cli.py backend status --backend=docker` as its preflight (clear per-check summary, non-zero exit when unready), matching the firecracker job. The firecracker preflight reverts to its original binary/KVM checks (backend status doesn't cover those). - Unit test + docs updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
64adf23775 |
ci: backend-agnostic integration guards + per-backend preflight
Integration tests now select their backend from BOT_BOTTLE_BACKEND and
skip on the capability that backend actually needs, instead of gating
every backend on unrelated Docker availability.
Task 1 — backend-agnostic guards (tests/_backend.py):
- Capability probes: docker_capability() (reachable daemon) and
firecracker_capability() (accessible /dev/kvm + firecracker on PATH,
Docker-independent). backend_capability()/selected_backend() resolve
the target from BOT_BOTTLE_BACKEND (default docker).
- skip_unless_selected_backend_available() for backend-agnostic tests
(test_sandbox_escape) — runs through whichever backend is selected and
checks that backend's real capability.
- skip_unless_backend("docker") for Docker-implementation tests
(DockerBroker, DockerGateway, backend.docker.*) — they no-op under a
non-Docker run rather than testing internals that run doesn't target.
- Retires tests/_docker.py; the KVM job no longer needs SKIP_DOCKER_TESTS
to steer Docker-only classes.
Task 2 — explicit per-backend skip visibility:
- tests/backend_preflight.py prints a clear PASS/FAIL capability line and
exits non-zero when the selected backend is missing.
- Both integration jobs run it as a preflight, so absent infrastructure
is surfaced at the job level instead of hidden among unittest.skip
lines. The docker job replaces its soft "Show environment" step; the
firecracker job keeps its richer backend-status check.
Docs (tests/README.md, docs/ci.md) updated; unit coverage for the probes,
guards, and preflight in test_backend_skip_guards.py.
Closes #414
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
e7fe00e2f5 |
fix(firecracker): keep the host key canonical instead of clobbering the host token file
tracker-policy-pr / check-pr (pull_request) Successful in 16s
test / integration-docker (pull_request) Successful in 21s
lint / lint (push) Successful in 1m3s
test / unit (pull_request) Successful in 1m55s
test / integration-firecracker (pull_request) Successful in 3m29s
test / coverage (pull_request) Successful in 17s
test / publish-infra (pull_request) Has been skipped
Reimplements the dropped firecracker fix on the refactored code, addressing didericis-codex's blocking review on #471. The previous firecracker path let the VM generate its own signing key on the guest volume and had `_with_signing_key()` unconditionally overwrite the single host-wide orchestrator-token with it. That breaks a co-running Docker/macOS control plane: their orchestrators still verify with the old key while new host clients start signing `cli` tokens with the guest key, so supervise/teardown/policy calls against those backends begin returning 401 (cross-backend operation is supported). Keep the host token file the single source of truth. The launcher now pushes the host-canonical key into the freshly booted infra VM over SSH (atomic write, mirroring persist_env_var_secret) via `_push_signing_key()`; the VM's init waits for it and refuses to start the control plane — rather than run OPEN — if it never arrives. Nothing writes back to the host file, so other backends are untouched, and the best-effort silent path is gone (both the push and the guest fail loudly). Ported onto the reorg'd names (host_orchestrator_token, BOT_BOTTLE_ORCHESTRATOR_ TOKEN, orchestrator-token, bot_bottle.orchestrator_auth, bot_bottle.gateway. bootstrap). pyright: 0 errors. Full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
3efb014ace |
refactor(orchestrator): move registry into store/registry_store
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / integration-docker (pull_request) Successful in 18s
lint / lint (push) Failing after 58s
test / unit (pull_request) Successful in 2m14s
test / integration-firecracker (pull_request) Successful in 3m25s
test / coverage (pull_request) Successful in 35s
test / publish-infra (pull_request) Has been skipped
registry.py is a SQLite-backed store (its class is already RegistryStore), so it belongs with the other orchestrator-owned stores rather than at the package root. Moved orchestrator/registry.py -> orchestrator/store/ registry_store.py, joining queue_store / secret_store / config_store. Repointed the in-package importers (.registry -> .store.registry_store) and the tests, bumped the moved file's relative-import depths, renamed the test file to match, and listed it in the store package docstring. Full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ca1d341d4f |
refactor: unify component naming — control_plane/control_auth -> orchestrator
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / integration-docker (pull_request) Successful in 18s
test / unit (pull_request) Successful in 46s
lint / lint (push) Failing after 54s
test / integration-firecracker (pull_request) Successful in 3m21s
test / coverage (pull_request) Successful in 18s
test / publish-infra (pull_request) Has been skipped
The codebase used "control plane" both as an architectural role term AND
as an identifier alias for the orchestrator component, producing
duplicate names for one thing (control_plane_url vs orchestrator_url,
CONTROL_PLANE_PORT, host_control_plane_token, …). Going forward the
concrete component is always named for what it is — Gateway or
Orchestrator — and the plane vocabulary is reserved for prose (module
descriptions, the security argument).
Renamed (identifiers + the in-repo env/wire/file string values, all
setters/getters are in this repo so the change is atomic):
ControlPlaneServer -> OrchestratorServer
control_plane_url -> orchestrator_url
probe_control_plane_url -> probe_orchestrator_url
host_control_plane_token -> host_orchestrator_token
CONTROL_PLANE_PORT -> ORCHESTRATOR_PORT
CONTROL_PLANE_TOKEN_ENV/FILE -> ORCHESTRATOR_TOKEN_ENV/FILENAME
BOT_BOTTLE_CONTROL_PLANE_TOKEN-> BOT_BOTTLE_ORCHESTRATOR_TOKEN
control-plane-token (file) -> orchestrator-token
control_auth (module) -> orchestrator_auth (stays top-level;
the gateway imports it and must not
import the orchestrator/ package)
CONTROL_AUTH_HEADER -> ORCHESTRATOR_AUTH_HEADER
x-bot-bottle-control-auth -> x-bot-bottle-orchestrator-auth
CONTROL_AUTH_JWT_ENV -> ORCHESTRATOR_AUTH_JWT_ENV
BOT_BOTTLE_CONTROL_AUTH_JWT -> BOT_BOTTLE_ORCHESTRATOR_AUTH_JWT
_control_auth_headers -> _orchestrator_auth_headers
Prose plane-terms ("control plane", "data plane") are preserved,
including the test name test_data_plane_daemons_get_jwt_not_key (it
names the security invariant). Gateway and orchestrator verified to
agree on the renamed wire header; full unit suite green (2243).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
4166057abc |
refactor(orchestrator): rename control_plane module to server
test / integration-docker (pull_request) Successful in 17s
test / unit (pull_request) Successful in 50s
lint / lint (push) Failing after 1m0s
test / integration-firecracker (pull_request) Successful in 3m25s
test / coverage (pull_request) Successful in 16s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Failing after 12m7s
orchestrator/control_plane.py -> orchestrator/server.py. Within the orchestrator package the "control_plane" filename stutters (the orchestrator *is* the control plane), and `orchestrator.server` reads as "the orchestrator's HTTP server", pairing with service.py (domain logic) and matching gateway/supervisor/server.py. Scope is the module name only. The class ControlPlaneServer, the CONTROL_AUTH_HEADER constant, and the "control plane" architectural term (CONTROL_PLANE_PORT, host_control_plane_token, control_plane_url, …) are deliberately unchanged — that's the load-bearing control-plane/data-plane distinction. Test file renamed to match; full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
50a67c04bd |
refactor(cli): group subcommand handlers under cli/commands/
test / integration-docker (pull_request) Successful in 18s
tracker-policy-pr / check-pr (pull_request) Successful in 17s
test / unit (pull_request) Failing after 41s
lint / lint (push) Failing after 57s
test / integration-firecracker (pull_request) Successful in 3m26s
test / coverage (pull_request) Has been skipped
test / publish-infra (pull_request) Has been skipped
Move the eleven per-command modules (backend, cleanup, commit, edit, info, init, list, login, resume, start, supervise) into a new bot_bottle/cli/commands/ package, leaving the dispatcher (__init__), entrypoint (__main__), and shared helpers (_common, tui) at the cli root. Makes the command surface obvious at a glance and separates handlers from the plumbing that registers them. Updated the dispatcher's COMMANDS imports to .commands.*, bumped the moved files' relative-import depths (.._common, .. import tui, sibling .start unchanged), and repointed test references to bot_bottle.cli.commands.*. Full unit suite green (2243); CLI dispatch verified via `python -m bot_bottle.cli --help`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
82d02d2c4b |
refactor(gateway): move egress entrypoint into gateway/egress
tracker-policy-pr / check-pr (pull_request) Successful in 15s
test / integration-docker (pull_request) Successful in 17s
test / unit (pull_request) Successful in 46s
lint / lint (push) Failing after 58s
test / integration-firecracker (pull_request) Successful in 3m35s
test / coverage (pull_request) Successful in 16s
test / publish-infra (pull_request) Has been skipped
egress_entrypoint.sh is the mitmdump launcher for the egress data plane, so it belongs with the gateway egress service, not at the package root. Moved to bot_bottle/gateway/egress/entrypoint.sh (prefix stripped now that the package namespaces it). Only the Dockerfile.gateway COPY source changes; the runtime target (/app/egress-entrypoint.sh, referenced by bootstrap's egress DaemonSpec) is unchanged, and the infra images inherit it via FROM gateway. Updated the test that resolves the script path and the infra-artifact docstring example. Full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
74dc984cf8 |
refactor(gateway): rename bootstrap's _Supervisor to _DaemonManager
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / integration-docker (pull_request) Successful in 17s
lint / lint (push) Failing after 53s
test / unit (pull_request) Successful in 2m4s
test / integration-firecracker (pull_request) Successful in 3m32s
test / coverage (pull_request) Successful in 18s
test / publish-infra (pull_request) Has been skipped
The PID-1 process manager in gateway/bootstrap.py was named _Supervisor, which collided conceptually with the supervise permissions service (Supervisor / gateway.supervisor). It manages the _DaemonSpec child set, so _DaemonManager names what it does without the "supervise" echo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ce744a85c4 |
refactor(gateway): split data-plane files into egress/supervisor/git_gate services
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / integration-docker (pull_request) Successful in 17s
test / unit (pull_request) Successful in 49s
lint / lint (push) Failing after 2m49s
test / integration-firecracker (pull_request) Successful in 3m35s
test / coverage (pull_request) Successful in 18s
test / publish-infra (pull_request) Has been skipped
Group the gateway's data-plane modules into three service sub-packages mirroring the host-side trio (bot_bottle.egress / .supervisor / .git_gate): gateway/egress/ addon_core, addon, dlp_config, dlp_detectors gateway/supervisor/ server (was supervise_server) gateway/git_gate/ render, http_backend Prefix-stripped filenames now that the package namespaces them; each sub-package has a thin docstring __init__ (no eager imports, cheap leaf loads). The two cross-cutting files stay at the gateway root: policy_resolver (shared per-client lookup) and gateway_init, renamed to bootstrap now that gateway/ already namespaces it. Updated all importers (bot_bottle + tests), the in-VM/container `-m` launch strings, the Dockerfile.gateway addon shim + ENTRYPOINT, and the five gateway entries in scripts/critical-modules.txt. Full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a446551acb |
refactor(egress): make Egress a service class in an egress package
tracker-policy-pr / check-pr (pull_request) Successful in 10s
test / integration-docker (pull_request) Successful in 17s
test / unit (pull_request) Successful in 46s
lint / lint (push) Failing after 2m44s
test / integration-firecracker (pull_request) Successful in 3m35s
test / coverage (pull_request) Successful in 20s
test / publish-infra (pull_request) Has been skipped
Split the flat egress.py into an egress/ package: neutral DTOs (EgressRoute, EgressPlan) in plan.py, and the concrete Egress service class plus rendering/env helpers in service.py, behind a thin __getattr__ facade so leaf imports stay cheap. Egress drops ABC and becomes a concrete service the backends call: resolve_token_values / agent_env_entries / prepare are now methods. Backend launch paths (docker, firecracker, macos_container) call Egress().<method>(...) instead of the module-level functions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
450037b7e9 |
refactor(git-gate): make GitGate a service class in a git_gate package
test / integration-docker (pull_request) Successful in 15s
tracker-policy-pr / check-pr (pull_request) Successful in 10s
test / unit (pull_request) Successful in 47s
lint / lint (push) Failing after 58s
test / integration-firecracker (pull_request) Successful in 3m17s
test / coverage (pull_request) Successful in 20s
test / publish-infra (pull_request) Has been skipped
Turn the git_gate module into a package with GitGate as a concrete service class
(dropping the ABC), mirroring the Supervisor shape. The host-side git-gate
operations are now methods the backend drives:
git_gate/
__init__.py — thin __getattr__ facade (keeps `from bot_bottle.git_gate
import …` working; render/hook names lazily forwarded to
gateway.git_gate_render)
plan.py — GitGatePlan (the launch DTO the backend contract references)
service.py — GitGate: prepare / provision_dynamic_keys /
revoke_provisioned_keys / preflight_host_keys
provision.py — deploy-key lifecycle (was git_gate_provision.py)
host_key.py — host-key preflight (was git_gate_host_key.py)
The backend launch + base.py preflight now call GitGate() methods instead of the
free functions. The runtime rendering / in-gateway hook execution stay in
gateway/git_gate_render.py (data plane) — only the host-side service moved.
Because the facade forwards names lazily, the ~25 `from bot_bottle.git_gate
import GitGatePlan` call-sites are unchanged, and importing git_gate.plan (the
contract's dependency) no longer drags in the provisioning / forge-API code.
Full unit suite green (2243).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
3e62f31d8b |
refactor(backend): split the heavy backend/__init__ into base + selection, thin the package init
tracker-policy-pr / check-pr (pull_request) Successful in 12s
test / integration-docker (pull_request) Successful in 38s
test / unit (pull_request) Successful in 52s
lint / lint (push) Successful in 1m1s
test / integration-firecracker (pull_request) Successful in 3m33s
test / coverage (pull_request) Successful in 17s
test / publish-infra (pull_request) Has been skipped
backend/__init__.py eagerly imported the whole framework (manifest, egress,
git-gate, env, workspace, agent-provider), so importing ANY backend.* submodule
paid ~32 modules just to run the package init. Split it:
- backend/base.py — the abstract contract (BottleSpec, BottlePlan,
BottleCleanupPlan, ExecResult, ActiveAgent, Bottle,
BottleImages, BottleBackend). Carries the framework
imports; loaded only when a caller needs the contract.
- backend/selection.py — backend registry / selection / enumeration
(get_bottle_backend, _get_backends, _auto_select_backend,
has_backend, known_backend_names, enumerate_active_agents).
Concrete backends still imported lazily inside.
- backend/__init__.py — thin: a __getattr__ that resolves the public names
from those submodules on first access (+ a TYPE_CHECKING
block for checkers). Existing `from bot_bottle.backend
import X` and patch.object call-sites keep working.
`import bot_bottle.backend` drops from 32 -> 2 bot_bottle modules. Repointed the
backend-selection test's patch targets to the `selection` module (the functions
reference each other there now; the FirecrackerBottleBackend class-method
patches are unchanged — same class object).
Note: backend.docker.util is still heavy because backend/docker/__init__ eagerly
imports DockerBottleBackend — slimming the three sub-package inits is the
follow-up that makes the leaves cheap.
Full unit suite green (2243).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
b96a8b44e0 |
refactor(docker): rename OrchestratorService -> DockerInfraService, move to backend/docker
test / integration-docker (pull_request) Successful in 22s
lint / lint (push) Successful in 1m7s
test / unit (pull_request) Successful in 2m10s
test / integration-firecracker (pull_request) Successful in 3m35s
test / coverage (pull_request) Successful in 15s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Failing after 13m2s
OrchestratorService wasn't the orchestrator — it's the host-side lifecycle of the docker infra *container* (the one that runs the Orchestrator). It read as "the orchestrator as a service" and lived in orchestrator/lifecycle.py, while its siblings (macOS MacosInfraService, Firecracker infra_vm) live under their backend package. Rename it DockerInfraService and move it to backend/docker/infra.py alongside the docker backend, with its docker-only constants (INFRA_*/ORCHESTRATOR_* image + container names, daemon list, mount paths). orchestrator/lifecycle.py keeps only the backend-neutral pieces the other infra services share — DEFAULT_PORT, DEFAULT_STARTUP_TIMEOUT_SECONDS, OrchestratorStartError, source_hash — which macOS / firecracker / client still import from there. backend/docker/infra.py imports those (backend -> orchestrator is an allowed direction). Renamed the unit test to test_docker_infra.py. Full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d41236c376 |
refactor(util): make render_diff generic (caller supplies side titles); move to root util
tracker-policy-pr / check-pr (pull_request) Successful in 8s
test / integration-docker (pull_request) Successful in 36s
test / unit (pull_request) Successful in 43s
lint / lint (push) Successful in 56s
test / integration-firecracker (pull_request) Successful in 3m27s
test / coverage (pull_request) Successful in 21s
test / publish-infra (pull_request) Has been skipped
Generalize render_diff so it isn't supervise-flavored: the "(current)" / "(proposed)" side titles are no longer baked in — callers pass `before_title` and `after_label` (required). Move it from orchestrator/supervisor/util.py to the base bot_bottle.util alongside sha256_hex, and delete the now-empty supervisor/util.py. The supervise callsite (Orchestrator.supervise_respond) assigns before_title="current", after_label="proposed"; the facade no longer re-exports render_diff (callers import from bot_bottle.util). Behavior at the supervise callsite is unchanged. Full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
db6a151803 |
refactor(supervise): hash the proposed file inside Proposal.new; sha256_hex → root util
tracker-policy-pr / check-pr (pull_request) Successful in 16s
test / integration-docker (pull_request) Successful in 39s
test / unit (pull_request) Successful in 53s
lint / lint (push) Failing after 1m3s
test / integration-firecracker (pull_request) Successful in 3m35s
test / coverage (pull_request) Successful in 19s
test / publish-infra (pull_request) Has been skipped
Move sha256_hex out of orchestrator/supervisor/util.py to the root bot_bottle.util (it's a generic string hash, not supervise-specific), and have Proposal.new take the proposed file contents and compute current_file_hash itself instead of every caller passing sha256_hex(proposed_file). Every call site set current_file_hash to the hash of the proposed file, and nothing reads the field except the DB round-trip (schema + _row_to_proposal + from_dict, all untouched), so folding the hash into the factory is behavior-preserving and removes the boilerplate. Callers now pass just the file; the orchestrator no longer imports sha256_hex at all. Full unit suite green (2243). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8abccf7ffe |
refactor(supervise): make Supervisor a service class the orchestrator calls
tracker-policy-pr / check-pr (pull_request) Successful in 12s
test / integration-docker (pull_request) Successful in 19s
lint / lint (push) Failing after 55s
test / unit (pull_request) Successful in 2m8s
test / integration-firecracker (pull_request) Successful in 3m25s
test / coverage (pull_request) Successful in 39s
test / publish-infra (pull_request) Has been skipped
Turn the loose queue/audit functions in orchestrator/supervisor/queue.py into
methods on a concrete Supervisor service. The Orchestrator now owns an
injectable `self._supervisor` and calls `self._supervisor.write_proposal(...)`
etc., instead of module-level free functions — the supervise dependency is
explicit and mockable, and a Supervisor can be scoped to a `db_path` (defaults
to the host DB) so tests can point it at a temp database.
- Supervisor (in the package __init__) drops the ABC and gains write_proposal,
read_proposal, list_pending_proposals, list_all_pending_proposals,
write_response, read_response, archive_all_proposals, write_audit_entry,
read_audit_entries, plus the launch-time prepare().
- render_diff / sha256_hex are pure and stateless, so they move to
orchestrator/supervisor/util.py (re-exported from the facade) rather than
becoming methods.
- queue.py is deleted; service.py + the supervise tests call through a
Supervisor instance.
Full unit suite green (2243).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
27a122e24b |
refactor(supervise): drop obsolete queue helpers (archive_proposal, wait_for_response, audit_dir/path)
tracker-policy-pr / check-pr (pull_request) Successful in 10s
test / integration-docker (pull_request) Successful in 17s
lint / lint (push) Failing after 59s
test / unit (pull_request) Successful in 2m3s
test / integration-firecracker (pull_request) Successful in 3m28s
test / coverage (pull_request) Successful in 16s
test / publish-infra (pull_request) Has been skipped
Remove the supervise queue helpers that no live path uses anymore — they're
mechanisms the PRD-0070 / idempotent-poll migration superseded:
- archive_proposal (+ QueueStore.archive_proposal): the poll stopped archiving
on read (#469 review), so single-proposal archiving has no caller; decided
proposals drop off the pending list via their response row and are reaped in
bulk by archive_all_proposals on teardown/reconcile.
- wait_for_response: the data plane polls the queue over the control-plane RPC
now, so nothing block-waits on it.
- audit_dir / audit_log_path: file-based audit paths, replaced by the SQLite
AuditStore.
Also drops the tests that covered those obsolete helpers. Kept the prod-unused
read accessors list_pending_proposals and read_audit_entries: those aren't dead
mechanisms — they're the read side of live write paths that many tests use to
verify real queue/audit behavior.
Full unit suite green (2243).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
3e2cbcab88 |
refactor(supervise): fold Supervise into the facade as Supervisor; drop dead flat-fallbacks
tracker-policy-pr / check-pr (pull_request) Successful in 8s
test / integration-docker (pull_request) Successful in 12s
test / unit (pull_request) Successful in 43s
lint / lint (push) Failing after 59s
test / integration-firecracker (pull_request) Successful in 3m16s
test / coverage (pull_request) Successful in 16s
test / publish-infra (pull_request) Has been skipped
Move the `Supervise` lifecycle out of its own `orchestrator/supervisor/ supervise.py` and into the package `__init__`, renaming the class to `Supervisor`. Callers now import it from `bot_bottle.orchestrator.supervisor` alongside the queue surface it belongs with. Remove the dead `try/except ImportError` flat-import fallbacks from the package-only store modules (db_store, audit_store, config_store, queue_store) and image_cache. Those fallbacks existed for when the store files were flat-copied into the gateway; post-PRD-0070 the data plane never opens the DB, so these modules are only ever imported as part of the package. The two gateway data-plane files that may still be loaded flat (egress_addon_core, git_gate_render) keep their fallbacks. Full unit suite green (2251). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
44e2b5a897 |
refactor(supervise): split the supervise plane by tier; per-service store managers
test / integration-docker (pull_request) Successful in 13s
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / unit (pull_request) Successful in 42s
lint / lint (push) Failing after 54s
test / integration-firecracker (pull_request) Successful in 3m17s
test / coverage (pull_request) Successful in 17s
test / publish-infra (pull_request) Has been skipped
Give each service its own store package + manager, and cut the supervise module
along the control/data-plane boundary so nothing in the shared layer reaches up
into the orchestrator.
Stores, by owner:
- bot_bottle/store/ keeps only the shared base (DbStore, migrations) and the
concrete stores that aren't service-owned (audit_store, config_store).
- bot_bottle/orchestrator/store/ now houses the orchestrator-owned stores —
queue_store (supervise queue), secret_store, config_store — plus a new
orchestrator store_manager that migrates them (composing audit/config
downward from the base). The old shared store_manager is gone.
Supervise plane, by tier:
- bot_bottle/supervisor/ (NEUTRAL, importable by every tier including the
gateway): types.py (the Proposal/Response/AuditEntry wire types + the tool/
status/poll constants + the shared daemon constants moved out of
supervise.py) and plan.py (SupervisePlan, a pure DTO).
- bot_bottle/orchestrator/supervisor/ (orchestrator-only): queue.py (the
queue/audit I/O wrappers + render_diff + sha256_hex) and supervise.py (the
Supervise lifecycle that stages the DB via the store manager). Its __init__
re-exports the neutral vocabulary so orchestrator-side callers import from
one place.
The gateway now imports only bot_bottle.supervisor.types (never
bot_bottle.supervise), so the data plane holds no code dependency on the
orchestrator — it reaches the queue over the control-plane RPC. This removes
the circular import that moving queue_store under orchestrator introduced
(supervise -> orchestrator -> service -> supervise).
supervise_types.py -> supervisor/types.py; supervise.py deleted (split). Full
unit suite green (2251).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
f77023db1d |
refactor(gateway): move the data-plane daemons into a bot_bottle.gateway package
test / integration-docker (pull_request) Successful in 11s
test / unit (pull_request) Successful in 43s
lint / lint (push) Successful in 56s
test / integration-firecracker (pull_request) Successful in 3m19s
test / coverage (pull_request) Successful in 19s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Successful in 7s
Separate the gateway (data plane) from the orchestrator (control plane) at the
module level. The gateway runtime files move out of the package root — and the
backend-neutral Gateway lifecycle ABC + GATEWAY_* constants move out of
orchestrator/ — into a new bot_bottle/gateway/ package:
gateway/__init__.py (was orchestrator/gateway.py: Gateway ABC + consts
+ rotate_gateway_ca)
gateway/gateway_init.py (the PID-1 daemon supervisor)
gateway/egress_addon.py, egress_addon_core.py, egress_dlp_config.py,
dlp_detectors.py (the egress mitmproxy daemon)
gateway/git_http_backend.py (the git-http daemon)
gateway/git_gate_render.py (the git-gate pre-receive rendering)
gateway/supervise_server.py (the supervise MCP daemon)
gateway/policy_resolver.py (the data-plane control-plane RPC client)
orchestrator/ now holds only control-plane files. The shared plan/types/auth
layer (egress.py=EgressPlan, git_gate.py=GitGatePlan, supervise.py,
supervise_types.py, control_auth.py) and the launch-time git-gate provisioning
helpers stay at root, so orchestrator/ and backend/ still own them.
Because these daemons are invoked as `python3 -m bot_bottle.<name>`, loaded flat
by mitmproxy, and referenced in Dockerfile.gateway, the move updates more than
Python imports: the `-m` invocations (firecracker/macOS infra scripts), the
Dockerfile.gateway addon shim + ENTRYPOINT, gateway_init's _DAEMONS module
paths, and the git-gate CGI heredocs all now point at bot_bottle.gateway.*.
No behavior change; full unit suite green (2251).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
a845cba925 |
refactor(manifest): consolidate the manifest modules into a bot_bottle.manifest package
tracker-policy-pr / check-pr (pull_request) Failing after 11s
test / integration-docker (pull_request) Successful in 20s
lint / lint (push) Successful in 59s
test / unit (pull_request) Successful in 2m10s
test / integration-firecracker (pull_request) Successful in 3m29s
test / coverage (pull_request) Successful in 38s
test / publish-infra (pull_request) Has been skipped
Move the nine root-level manifest modules into a bot_bottle/manifest/ package, dropping the redundant manifest_ prefix: the facade (manifest.py) becomes the package __init__ and keeps re-exporting Manifest / ManifestIndex and the piece types, while manifest_<x>.py become manifest/<x>.py (agent, bottle, egress, extends, git, loader, schema, util). Because the facade stays the package __init__, the ~13 callers that import `from bot_bottle.manifest import ...` are unchanged; only direct-piece imports move to `bot_bottle.manifest.<name>`. Inside the package, intra-manifest imports drop the prefix and the non-manifest siblings (log, yaml_subset, agent_provider) move to `..`. No behavior change; full unit suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d7a58e52fd |
refactor(store): consolidate the SQLite store family into bot_bottle.store
lint / lint (push) Successful in 54s
test / integration-docker (pull_request) Successful in 38s
test / unit (pull_request) Successful in 45s
test / integration-firecracker (pull_request) Successful in 3m31s
test / coverage (pull_request) Successful in 18s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Failing after 6s
Move the DbStore base (db_store), the shared schema-migration base (migrations / TableMigrations), the concrete stores (audit_store, config_store, queue_store), and StoreManager out of the package root into a bot_bottle/store/ package, so persistence lives in one place rather than scattered across the root. Callers use direct submodule imports (from bot_bottle.store.store_manager import StoreManager). Inside the moved modules the relative imports point back up to their non-store siblings (..paths, ..supervise_types) while co-moved siblings stay package-local; the flat-import fallbacks are untouched. The orchestrator's own stores (config_store, secret_store, registry) stay in orchestrator/ and repoint to the moved DbStore / TableMigrations. No behavior change; full unit suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |