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>
This commit is contained in:
2026-07-24 13:25:10 -04:00
parent a845cba925
commit f77023db1d
47 changed files with 87 additions and 87 deletions
+8 -8
View File
@@ -10,7 +10,7 @@ from pathlib import Path
from unittest import mock
from bot_bottle.git_gate import GIT_GATE_TIMEOUT_SECS
from bot_bottle.git_http_backend import GitHttpHandler, MAX_BODY_BYTES
from bot_bottle.gateway.git_http_backend import GitHttpHandler, MAX_BODY_BYTES
# The git-http backend is resolver-only: every request is attributed to a
@@ -199,7 +199,7 @@ class TestGitHttpBackend(unittest.TestCase):
subprocess.CompletedProcess(["git"], 0, backend_response, b""),
]
with mock.patch(
"bot_bottle.git_http_backend.subprocess.run",
"bot_bottle.gateway.git_http_backend.subprocess.run",
side_effect=calls,
) as run:
request = urllib.request.Request(
@@ -265,7 +265,7 @@ class TestGitHttpBackend(unittest.TestCase):
subprocess.CompletedProcess(["git"], 0, backend_response, b""),
]
with mock.patch(
"bot_bottle.git_http_backend.subprocess.run",
"bot_bottle.gateway.git_http_backend.subprocess.run",
side_effect=calls,
) as run:
req = urllib.request.Request(
@@ -309,7 +309,7 @@ class TestGitHttpBackend(unittest.TestCase):
denial = b"git-gate: upstream fetch failed; refusing to serve stale data\n"
with mock.patch(
"bot_bottle.git_http_backend.subprocess.run",
"bot_bottle.gateway.git_http_backend.subprocess.run",
return_value=subprocess.CompletedProcess(
["hook"], 1, b"", denial,
),
@@ -355,7 +355,7 @@ class TestGitHttpBackend(unittest.TestCase):
self.addCleanup(server.server_close)
with mock.patch(
"bot_bottle.git_http_backend.subprocess.run",
"bot_bottle.gateway.git_http_backend.subprocess.run",
return_value=subprocess.CompletedProcess(
["hook"], 2, b"", b"",
),
@@ -402,7 +402,7 @@ class TestGitHttpBackend(unittest.TestCase):
self.addCleanup(server.server_close)
with mock.patch(
"bot_bottle.git_http_backend.subprocess.run",
"bot_bottle.gateway.git_http_backend.subprocess.run",
side_effect=PermissionError(13, "Permission denied"),
):
buf = io.StringIO()
@@ -461,7 +461,7 @@ class TestMalformedStatusHeader(unittest.TestCase):
def _get_with_backend_response(self, cgi_response: bytes) -> int:
with mock.patch(
"bot_bottle.git_http_backend.subprocess.run",
"bot_bottle.gateway.git_http_backend.subprocess.run",
return_value=mock.Mock(returncode=0, stdout=cgi_response),
):
req = urllib.request.Request(
@@ -545,7 +545,7 @@ class TestContentLengthBounds(unittest.TestCase):
# With a valid Content-Length the handler proceeds into
# git http-backend; that will fail (no real git repo) but the
# status won't be 400 or 413.
with mock.patch("bot_bottle.git_http_backend.subprocess.run") as run:
with mock.patch("bot_bottle.gateway.git_http_backend.subprocess.run") as run:
run.return_value = mock.Mock(
returncode=0,
stdout=(