From 57d32d2c5c820ff0efbead2bb4bfa812d5b04668 Mon Sep 17 00:00:00 2001 From: didericis Date: Sat, 25 Jul 2026 14:29:32 -0400 Subject: [PATCH] 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 --- bot_bottle/orchestrator/__init__.py | 8 ++++---- bot_bottle/orchestrator/__main__.py | 4 ++-- bot_bottle/orchestrator/registration.py | 4 ++-- bot_bottle/orchestrator/server.py | 10 +++++----- bot_bottle/orchestrator/service.py | 6 +++--- bot_bottle/orchestrator/supervisor.py | 2 +- bot_bottle/supervisor/types.py | 2 +- tests/unit/test_orchestrator_server.py | 10 +++++----- tests/unit/test_orchestrator_service.py | 12 ++++++------ 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/bot_bottle/orchestrator/__init__.py b/bot_bottle/orchestrator/__init__.py index 7597a69..485d2f6 100644 --- a/bot_bottle/orchestrator/__init__.py +++ b/bot_bottle/orchestrator/__init__.py @@ -14,7 +14,7 @@ backend-neutral "consolidation core" that needs no VM packaging: lifecycle contract (idempotent singleton). One gateway shared by all bottles instead of one per bottle; backend impls live under `backend/*/gateway`. - * `service` — the `Orchestrator`: owns the registry, brokers the + * `service` — the `OrchestratorCore`: owns the registry, brokers the launch lifecycle (launch/teardown), manages the shared gateway, attributes. * `server` — the HTTP control-plane RPC (launch / teardown / @@ -42,7 +42,7 @@ if TYPE_CHECKING: ) from .docker_broker import DockerBroker, DockerBrokerError from ..gateway import Gateway, GatewayError - from .service import Orchestrator + from .service import OrchestratorCore from .server import OrchestratorServer, dispatch, make_server @@ -64,7 +64,7 @@ _LAZY: dict[str, str] = { "DockerBrokerError": ".docker_broker", "Gateway": "..gateway", "GatewayError": "..gateway", - "Orchestrator": ".service", + "OrchestratorCore": ".service", "OrchestratorServer": ".server", "dispatch": ".server", "make_server": ".server", @@ -96,7 +96,7 @@ __all__ = [ "DockerBrokerError", "Gateway", "GatewayError", - "Orchestrator", + "OrchestratorCore", "OrchestratorServer", "dispatch", "make_server", diff --git a/bot_bottle/orchestrator/__main__.py b/bot_bottle/orchestrator/__main__.py index 6fad836..20fd4ec 100644 --- a/bot_bottle/orchestrator/__main__.py +++ b/bot_bottle/orchestrator/__main__.py @@ -21,7 +21,7 @@ from .broker import LaunchBroker, StubBroker from .server import make_server from .docker_broker import DockerBroker from .store.registry_store import RegistryStore, default_db_path -from .service import Orchestrator +from .service import OrchestratorCore def main(argv: list[str] | None = None) -> int: @@ -52,7 +52,7 @@ def main(argv: list[str] | None = None) -> int: # anything; 'docker' runs real containers (firecracker drops in later). secret = secrets.token_bytes(32) broker: LaunchBroker = DockerBroker(secret) if args.broker == "docker" else StubBroker(secret) - orchestrator = Orchestrator(registry, broker, secret) + orchestrator = OrchestratorCore(registry, broker, secret) server = make_server(orchestrator, host=args.host, port=args.port) bound_host, bound_port = server.server_address[0], server.server_address[1] diff --git a/bot_bottle/orchestrator/registration.py b/bot_bottle/orchestrator/registration.py index fd88610..bbf3555 100644 --- a/bot_bottle/orchestrator/registration.py +++ b/bot_bottle/orchestrator/registration.py @@ -2,7 +2,7 @@ Bridges the existing per-bottle `prepare` output to the consolidated registry: turns a prepared bottle's egress plan into the backend-neutral -inputs `Orchestrator.launch_bottle` takes — the egress **policy** blob and +inputs `OrchestratorCore.launch_bottle` takes — the egress **policy** blob and launch **metadata**. The policy blob is the exact routes YAML the per-bottle egress daemon used @@ -26,7 +26,7 @@ from ..egress import EgressPlan, egress_render_routes @dataclass(frozen=True) class RegistrationInputs: - """What `Orchestrator.launch_bottle` needs to register a bottle, derived + """What `OrchestratorCore.launch_bottle` needs to register a bottle, derived from its prepared plan. `policy` is served verbatim by the gateway's `/resolve`; `metadata` is opaque forward-compat state — it carries the human slug so the console / supervise can show a name, not just the diff --git a/bot_bottle/orchestrator/server.py b/bot_bottle/orchestrator/server.py index a7ec7a4..dbaecd6 100644 --- a/bot_bottle/orchestrator/server.py +++ b/bot_bottle/orchestrator/server.py @@ -45,7 +45,7 @@ Both attribute the caller by `(source_ip, identity_token)` exactly like `POST /bottles` / `DELETE` drive the full launch lifecycle: they mint (or tear down) the bottle in the registry AND broker the backend-native launch via the orchestrator. Register/deregister without a launch are internal to -`Orchestrator`, not exposed here. +`OrchestratorCore`, not exposed here. Routing/handling is the pure function `dispatch()` so it is unit-testable without a socket; `Handler` / `OrchestratorServer` / `make_server` are a @@ -66,7 +66,7 @@ from urllib.parse import urlsplit from ..orchestrator_auth import ROLE_CLI, ROLES, verify from ..paths import ORCHESTRATOR_TOKEN_ENV from ..supervisor.types import TOOLS -from .service import Orchestrator +from .service import OrchestratorCore # JSON body payload type (parsed request / rendered response). Json = dict[str, object] @@ -109,7 +109,7 @@ def _parse_json_object(body: bytes) -> Json: def dispatch( # pylint: disable=too-many-return-statements,too-many-branches - orch: Orchestrator, method: str, path: str, body: bytes, *, role: str | None = ROLE_CLI, + orch: OrchestratorCore, method: str, path: str, body: bytes, *, role: str | None = ROLE_CLI, ) -> tuple[int, Json]: """Route one control-plane request to a (status, payload) pair. Pure — no I/O beyond the orchestrator — so it is fully testable without a socket. @@ -411,7 +411,7 @@ class OrchestratorServer(socketserver.ThreadingMixIn, http.server.HTTPServer): daemon_threads = True allow_reuse_address = True - def __init__(self, address: tuple[str, int], orchestrator: Orchestrator) -> None: + def __init__(self, address: tuple[str, int], orchestrator: OrchestratorCore) -> None: self.orchestrator = orchestrator self._signing_key = os.environ.get(ORCHESTRATOR_TOKEN_ENV, "").strip() if not self._signing_key: @@ -437,7 +437,7 @@ class OrchestratorServer(socketserver.ThreadingMixIn, http.server.HTTPServer): def make_server( - orchestrator: Orchestrator, host: str = "127.0.0.1", port: int = 0 + orchestrator: OrchestratorCore, host: str = "127.0.0.1", port: int = 0 ) -> OrchestratorServer: """Build (but do not start) a control-plane server. `port=0` binds an ephemeral port — read `server.server_address` for the actual one.""" diff --git a/bot_bottle/orchestrator/service.py b/bot_bottle/orchestrator/service.py index 5b7e109..a43d9b1 100644 --- a/bot_bottle/orchestrator/service.py +++ b/bot_bottle/orchestrator/service.py @@ -1,6 +1,6 @@ """The per-host orchestrator core (PRD 0070). -`Orchestrator` is the single backend-neutral object the control plane talks +`OrchestratorCore` is the single backend-neutral object the control plane talks to: it owns the registry (runtime state) and brokers agent launches. It never branches on backend — the `LaunchBroker` abstracts the backend-native launch, so this same object drives docker / firecracker / apple once a real @@ -54,7 +54,7 @@ _RESPOND_STATUS = { _APPLY_TOOLS = (TOOL_EGRESS_ALLOW, TOOL_EGRESS_BLOCK) -class Orchestrator: +class OrchestratorCore: """Owns the registry + brokers launches, and manages the single consolidated per-host gateway. Backend-neutral (broker and gateway abstract the backend-native pieces).""" @@ -390,4 +390,4 @@ class Orchestrator: return {"configured": False} -__all__ = ["Orchestrator"] +__all__ = ["OrchestratorCore"] diff --git a/bot_bottle/orchestrator/supervisor.py b/bot_bottle/orchestrator/supervisor.py index 7db7839..9b7c4be 100644 --- a/bot_bottle/orchestrator/supervisor.py +++ b/bot_bottle/orchestrator/supervisor.py @@ -1,6 +1,6 @@ """The orchestrator's supervise service. -`Supervisor` is the service object the `Orchestrator` owns and calls: it wraps +`Supervisor` is the service object the `OrchestratorCore` owns and calls: it wraps the supervise queue + audit stores (queue proposals, record operator responses, write audit entries, stage the DB at launch). Binding those operations to one object — optionally scoped to a `db_path` — makes the orchestrator's supervise diff --git a/bot_bottle/supervisor/types.py b/bot_bottle/supervisor/types.py index cd6c391..abf6aed 100644 --- a/bot_bottle/supervisor/types.py +++ b/bot_bottle/supervisor/types.py @@ -43,7 +43,7 @@ STATUSES: tuple[str, ...] = (STATUS_APPROVED, STATUS_MODIFIED, STATUS_REJECTED) # proposal has no operator decision yet (`PENDING`) or no queued proposal with # that id exists for the calling bottle (`UNKNOWN`). They are never a # `Response.status` — only the poll wire contract (see -# `Orchestrator.supervise_poll_response`) — so they are deliberately outside +# `OrchestratorCore.supervise_poll_response`) — so they are deliberately outside # `STATUSES`. POLL_STATUS_PENDING = "pending" POLL_STATUS_UNKNOWN = "unknown" diff --git a/tests/unit/test_orchestrator_server.py b/tests/unit/test_orchestrator_server.py index e3dfb1b..495cc99 100644 --- a/tests/unit/test_orchestrator_server.py +++ b/tests/unit/test_orchestrator_server.py @@ -23,7 +23,7 @@ from bot_bottle.orchestrator_auth import ROLE_CLI, ROLE_GATEWAY, mint from bot_bottle.orchestrator.broker import StubBroker from bot_bottle.orchestrator.server import dispatch, make_server from bot_bottle.orchestrator.store.registry_store import BottleRecord, RegistryStore -from bot_bottle.orchestrator.service import Orchestrator +from bot_bottle.orchestrator.service import OrchestratorCore from bot_bottle.orchestrator.store.store_manager import StoreManager from bot_bottle.orchestrator.supervisor import ( Proposal, @@ -36,11 +36,11 @@ def _body(obj: object) -> bytes: return json.dumps(obj).encode() -def _orchestrator(db_path: Path) -> Orchestrator: +def _orchestrator(db_path: Path) -> OrchestratorCore: store = RegistryStore(db_path) store.migrate() secret = secrets.token_bytes(16) - return Orchestrator(store, StubBroker(secret), secret) + return OrchestratorCore(store, StubBroker(secret), secret) class TestDispatch(unittest.TestCase): @@ -408,7 +408,7 @@ class TestDispatchSupervise(unittest.TestCase): self.store.migrate() StoreManager(db).migrate() secret = secrets.token_bytes(16) - self.orch = Orchestrator(self.store, StubBroker(secret), secret) + self.orch = OrchestratorCore(self.store, StubBroker(secret), secret) def tearDown(self) -> None: self._env.stop() @@ -475,7 +475,7 @@ class TestDispatchSuperviseAgentRpc(unittest.TestCase): self.store.migrate() StoreManager(db).migrate() secret = secrets.token_bytes(16) - self.orch = Orchestrator(self.store, StubBroker(secret), secret) + self.orch = OrchestratorCore(self.store, StubBroker(secret), secret) def tearDown(self) -> None: self._env.stop() diff --git a/tests/unit/test_orchestrator_service.py b/tests/unit/test_orchestrator_service.py index 8ba2589..7f9b12f 100644 --- a/tests/unit/test_orchestrator_service.py +++ b/tests/unit/test_orchestrator_service.py @@ -1,4 +1,4 @@ -"""Unit tests for the Orchestrator launch lifecycle (PRD 0070).""" +"""Unit tests for the OrchestratorCore launch lifecycle (PRD 0070).""" from __future__ import annotations @@ -13,7 +13,7 @@ from unittest.mock import patch from bot_bottle.orchestrator.broker import LaunchBroker, LaunchRequest, StubBroker from bot_bottle.orchestrator.store.registry_store import RegistryStore -from bot_bottle.orchestrator.service import Orchestrator +from bot_bottle.orchestrator.service import OrchestratorCore from bot_bottle.orchestrator.store.secret_store import new_env_var_secret from bot_bottle.orchestrator.store.store_manager import StoreManager from bot_bottle.orchestrator.supervisor import ( @@ -42,7 +42,7 @@ class TestOrchestrator(unittest.TestCase): self.store = RegistryStore(Path(self._tmp.name) / "r.db") self.store.migrate() self.broker = StubBroker(self.secret) - self.orch = Orchestrator(self.store, self.broker, self.secret) + self.orch = OrchestratorCore(self.store, self.broker, self.secret) def tearDown(self) -> None: self._tmp.cleanup() @@ -137,7 +137,7 @@ class TestOrchestrator(unittest.TestCase): self.assertIsNone(self.orch.resolve("10.243.0.3", "wrong-token")) def test_launch_rolls_back_registry_on_broker_failure(self) -> None: - orch = Orchestrator(self.store, _FailingBroker(self.secret), self.secret) + orch = OrchestratorCore(self.store, _FailingBroker(self.secret), self.secret) with self.assertRaises(RuntimeError): orch.launch_bottle("10.243.0.9") self.assertEqual([], self.store.all()) # no orphan @@ -168,7 +168,7 @@ class TestOrchestratorSupervise(unittest.TestCase): self.store.migrate() StoreManager(db).migrate() secret = secrets.token_bytes(16) - self.orch = Orchestrator(self.store, StubBroker(secret), secret) + self.orch = OrchestratorCore(self.store, StubBroker(secret), secret) def tearDown(self) -> None: self._env.stop() @@ -360,7 +360,7 @@ class TestOrchestratorReconcile(unittest.TestCase): self.store = RegistryStore(self.db) self.store.migrate() self.broker = StubBroker(self.secret) - self.orch = Orchestrator(self.store, self.broker, self.secret) + self.orch = OrchestratorCore(self.store, self.broker, self.secret) def tearDown(self) -> None: self._tmp.cleanup()