From 265119d601d2ecaaa0583b14eb58a8897d1d7e83 Mon Sep 17 00:00:00 2001 From: didericis Date: Thu, 16 Jul 2026 19:01:45 -0400 Subject: [PATCH] fix(supervise): docker gateway shares the one host DB (Step 2d) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docker gateway container ran the supervise daemon but bind-mounted no DB and set no SUPERVISE_DB_PATH — so the daemon wrote proposals to a container-local, ephemeral SQLite file, disconnected from the host DB the orchestrator (and now the operator, over HTTP) uses. Same split-DB bug firecracker had; Dockerfile.gateway even documents the mount (`/run/supervise/bot-bottle.db bind-mounted at run time`) that ensure_running never provided. Bind-mount the host DB dir into the gateway at /run/supervise and set SUPERVISE_DB_PATH, so the daemon queues into the same file the orchestrator container opens (its BOT_BOTTLE_ROOT bind-mount) and the operator reaches over the control plane. One DB per host, shared by bind-mounts — docker now on the exact same supervise path as firecracker. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck --- bot_bottle/orchestrator/gateway.py | 20 ++++++++++++++++++++ tests/unit/test_orchestrator_gateway.py | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/bot_bottle/orchestrator/gateway.py b/bot_bottle/orchestrator/gateway.py index f501f39..a01bef6 100644 --- a/bot_bottle/orchestrator/gateway.py +++ b/bot_bottle/orchestrator/gateway.py @@ -23,6 +23,13 @@ import time from pathlib import Path from ..docker_cmd import run_docker +from ..paths import host_db_path +from ..supervise import DB_PATH_IN_CONTAINER + +# The host DB dir is bind-mounted here so the gateway's supervise daemon +# writes its queued proposals into the ONE host DB (the same file the +# orchestrator container opens and the operator reaches over HTTP). +_SUPERVISE_DB_DIR_IN_CONTAINER = os.path.dirname(DB_PATH_IN_CONTAINER) # The gateway's mitmproxy writes its CA a beat after the container starts, so # reads poll for it rather than assuming it's there on a fresh launch. @@ -54,6 +61,14 @@ GATEWAY_DOCKERFILE = "Dockerfile.gateway" _REPO_ROOT = Path(__file__).resolve().parents[2] +def _host_db_dir() -> str: + """The host DB directory (created if missing), for the gateway's + supervise-DB bind-mount.""" + db_dir = host_db_path().parent + db_dir.mkdir(parents=True, exist_ok=True) + return str(db_dir) + + class GatewayError(Exception): """The shared gateway failed to build/start/stop (non-zero `docker` exit).""" @@ -192,6 +207,11 @@ class DockerGateway(Gateway): # Persist the self-generated CA so it survives restarts (agents # trust it) — see GATEWAY_CA_VOLUME. "--volume", f"{GATEWAY_CA_VOLUME}:{MITMPROXY_HOME}", + # Share the one host DB: the supervise daemon queues proposals + # into the same file the orchestrator (and the operator, over + # HTTP) reads — no second, disconnected DB in the container. + "--volume", f"{_host_db_dir()}:{_SUPERVISE_DB_DIR_IN_CONTAINER}", + "--env", f"SUPERVISE_DB_PATH={DB_PATH_IN_CONTAINER}", ] for port in self._host_port_bindings: argv += ["--publish", f"0.0.0.0:{port}:{port}"] diff --git a/tests/unit/test_orchestrator_gateway.py b/tests/unit/test_orchestrator_gateway.py index 09a53f4..76b34e6 100644 --- a/tests/unit/test_orchestrator_gateway.py +++ b/tests/unit/test_orchestrator_gateway.py @@ -91,6 +91,13 @@ class TestDockerGateway(unittest.TestCase): self.assertEqual(self.sc.network, runs[0][runs[0].index("--network") + 1]) # Persists its CA on a named volume so agents keep trusting it. self.assertTrue(any("mitmproxy" in a for a in runs[0])) + # Shares the ONE host DB: the supervise daemon queues into the same + # file the orchestrator + operator (over HTTP) use. + self.assertTrue(any( + a.startswith("SUPERVISE_DB_PATH=") and a.endswith("/run/supervise/bot-bottle.db") + for a in runs[0])) + self.assertTrue(any( + a.endswith(":/run/supervise") for a in runs[0])) def test_ensure_running_creates_network_when_missing(self) -> None: calls: list[list[str]] = []