Remove the supervise flag; supervise every bottle
Issue #249: in practice the per-bottle `supervise` flag was never turned off — all bottles should be supervised. Remove the manifest flag and make the supervise sidecar unconditional, mirroring egress. - Reject `supervise:` as a removed bottle key with a migration hint. - Drop the `supervise` field from ManifestBottle and the extends merge. - prepare_supervise always returns a SupervisePlan; the plan type is now non-optional and the per-backend `is None` guards are gone, so the supervise daemon, current-config mount, aliases, and MCP registration always render. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcU7nerbg8cVj9R4EkpfLJ
This commit is contained in:
+27
-33
@@ -40,13 +40,11 @@ STAGE = Path("/tmp/cb-stage")
|
||||
STATE = Path("/tmp/cb-state")
|
||||
|
||||
|
||||
def _manifest(*, supervise: bool, with_git: bool, with_egress: bool) -> ManifestIndex:
|
||||
def _manifest(*, with_git: bool, with_egress: bool) -> ManifestIndex:
|
||||
"""Minimal manifest with the toggles the chunk-1 matrix needs.
|
||||
The renderer only reads from the plan, not the manifest, so this
|
||||
is just here to back BottleSpec."""
|
||||
bottle: dict[str, object] = {}
|
||||
if supervise:
|
||||
bottle["supervise"] = True
|
||||
if with_git:
|
||||
bottle["git-gate"] = {"repos": {
|
||||
"upstream": {
|
||||
@@ -111,10 +109,11 @@ def _plan(
|
||||
*,
|
||||
with_git: bool = False,
|
||||
with_egress: bool = False,
|
||||
supervise: bool = False,
|
||||
) -> DockerBottlePlan:
|
||||
"""Build a fully-resolved DockerBottlePlan. Toggles cover the
|
||||
matrix the renderer's conditional-service logic branches on."""
|
||||
matrix the renderer's conditional-service logic branches on.
|
||||
Every bottle is supervised (issue #249), so the supervise plan
|
||||
is always present."""
|
||||
upstreams: tuple[GitGateUpstream, ...] = ()
|
||||
if with_git:
|
||||
upstreams = (GitGateUpstream(
|
||||
@@ -136,7 +135,7 @@ def _plan(
|
||||
roles=(),
|
||||
),)
|
||||
|
||||
index = _manifest(supervise=supervise, with_git=with_git, with_egress=with_egress)
|
||||
index = _manifest(with_git=with_git, with_egress=with_egress)
|
||||
spec = BottleSpec(
|
||||
manifest=index,
|
||||
agent_name="demo",
|
||||
@@ -151,7 +150,7 @@ def _plan(
|
||||
forwarded_env={"CLAUDE_CODE_OAUTH_TOKEN": "x"},
|
||||
git_gate_plan=_git_gate_plan(upstreams),
|
||||
egress_plan=_egress_plan(routes),
|
||||
supervise_plan=_supervise_plan() if supervise else None,
|
||||
supervise_plan=_supervise_plan(),
|
||||
use_runsc=False,
|
||||
agent_provision=AgentProvisionPlan(
|
||||
template="claude",
|
||||
@@ -220,10 +219,8 @@ class TestAgentAlwaysPresent(unittest.TestCase):
|
||||
proxy = [e for e in s["environment"] if e.startswith("HTTPS_PROXY=")][0]
|
||||
self.assertEqual("HTTPS_PROXY=http://egress:9099", proxy)
|
||||
|
||||
def test_agent_no_proxy_adds_supervise_when_enabled(self):
|
||||
s = bottle_plan_to_compose(
|
||||
_plan(supervise=True)
|
||||
)["services"]["agent"]
|
||||
def test_agent_no_proxy_includes_supervise(self):
|
||||
s = bottle_plan_to_compose(_plan())["services"]["agent"]
|
||||
no_proxy = [e for e in s["environment"] if e.startswith("NO_PROXY=")][0]
|
||||
self.assertIn("supervise", no_proxy)
|
||||
|
||||
@@ -259,22 +256,18 @@ class TestAgentAlwaysPresent(unittest.TestCase):
|
||||
def test_agent_depends_only_on_sidecars(self):
|
||||
# Bundle shape: the init supervisor owns intra-bundle daemon
|
||||
# ordering, so the agent waits on the bundle container alone.
|
||||
for kwargs in [{}, {"with_git": True, "with_egress": True, "supervise": True}]:
|
||||
for kwargs in [{}, {"with_git": True, "with_egress": True}]:
|
||||
with self.subTest(**kwargs):
|
||||
s = bottle_plan_to_compose(_plan(**kwargs))["services"]["agent"]
|
||||
self.assertEqual(["sidecars"], s["depends_on"])
|
||||
|
||||
def test_agent_current_config_mount_only_with_supervise(self):
|
||||
with_sv = bottle_plan_to_compose(_plan(supervise=True))["services"]["agent"]
|
||||
def test_agent_current_config_always_mounted(self):
|
||||
# Every bottle is supervised (issue #249), so the read-only
|
||||
# current-config mount is always present in the agent.
|
||||
agent = bottle_plan_to_compose(_plan())["services"]["agent"]
|
||||
self.assertTrue(any(
|
||||
v["target"] == "/etc/bot-bottle/current-config"
|
||||
for v in with_sv.get("volumes", [])
|
||||
))
|
||||
without_sv = bottle_plan_to_compose(_plan(supervise=False))["services"]["agent"]
|
||||
# Either no volumes key at all, or no current-config target.
|
||||
self.assertFalse(any(
|
||||
v["target"] == "/etc/bot-bottle/current-config"
|
||||
for v in without_sv.get("volumes", [])
|
||||
for v in agent.get("volumes", [])
|
||||
))
|
||||
|
||||
|
||||
@@ -292,7 +285,7 @@ class TestSidecarBundleShape(unittest.TestCase):
|
||||
self.assertEqual({"sidecars", "agent"}, set(spec["services"].keys()))
|
||||
|
||||
def test_emits_two_services_full_matrix(self):
|
||||
spec = self._render(with_git=True, with_egress=True, supervise=True)
|
||||
spec = self._render(with_git=True, with_egress=True)
|
||||
# Still two services — the bundle absorbs git-gate/egress/supervise.
|
||||
self.assertEqual({"sidecars", "agent"}, set(spec["services"].keys()))
|
||||
|
||||
@@ -315,16 +308,16 @@ class TestSidecarBundleShape(unittest.TestCase):
|
||||
self.assertIn("egress", aliases)
|
||||
|
||||
def test_internal_aliases_omit_inactive_sidecars(self):
|
||||
# With no git-gate / supervise, those names are NOT aliased
|
||||
# — keeps the alias list honest about what's actually
|
||||
# listening inside the bundle.
|
||||
# With no git-gate, that name is NOT aliased — keeps the alias
|
||||
# list honest about what's actually listening inside the bundle.
|
||||
# supervise is always present (issue #249).
|
||||
sc = self._render()["services"]["sidecars"]
|
||||
aliases = set(sc["networks"]["internal"]["aliases"])
|
||||
self.assertNotIn("git-gate", aliases)
|
||||
self.assertNotIn("supervise", aliases)
|
||||
self.assertIn("supervise", aliases)
|
||||
|
||||
def test_internal_aliases_include_active_sidecars(self):
|
||||
sc = self._render(with_git=True, supervise=True)["services"]["sidecars"]
|
||||
sc = self._render(with_git=True)["services"]["sidecars"]
|
||||
aliases = set(sc["networks"]["internal"]["aliases"])
|
||||
self.assertIn("git-gate", aliases)
|
||||
self.assertIn("supervise", aliases)
|
||||
@@ -336,10 +329,11 @@ class TestSidecarBundleShape(unittest.TestCase):
|
||||
for line in sc["environment"]
|
||||
if line.startswith("BOT_BOTTLE_SIDECAR_DAEMONS=")
|
||||
}
|
||||
self.assertEqual({"egress"}, daemons)
|
||||
# egress + supervise are always present (issue #249).
|
||||
self.assertEqual({"egress,supervise"}, daemons)
|
||||
|
||||
def test_daemons_csv_expands_with_optional_sidecars(self):
|
||||
sc = self._render(with_git=True, supervise=True)["services"]["sidecars"]
|
||||
sc = self._render(with_git=True)["services"]["sidecars"]
|
||||
for line in sc["environment"]:
|
||||
if line.startswith("BOT_BOTTLE_SIDECAR_DAEMONS="):
|
||||
csv = line.split("=", 1)[1]
|
||||
@@ -347,7 +341,7 @@ class TestSidecarBundleShape(unittest.TestCase):
|
||||
else:
|
||||
self.fail("BOT_BOTTLE_SIDECAR_DAEMONS not in env")
|
||||
self.assertEqual(
|
||||
["egress", "git-gate", "supervise"],
|
||||
["egress", "supervise", "git-gate"],
|
||||
csv.split(","),
|
||||
)
|
||||
|
||||
@@ -376,7 +370,7 @@ class TestSidecarBundleShape(unittest.TestCase):
|
||||
self.assertNotIn("EGRESS_TOKEN_0", env_strings)
|
||||
|
||||
def test_supervise_env_present_when_active(self):
|
||||
sc = self._render(supervise=True)["services"]["sidecars"]
|
||||
sc = self._render()["services"]["sidecars"]
|
||||
env_strings = sc["environment"]
|
||||
self.assertIn(f"SUPERVISE_BOTTLE_SLUG={SLUG}", env_strings)
|
||||
self.assertTrue(any(e.startswith("SUPERVISE_QUEUE_DIR=") for e in env_strings))
|
||||
@@ -388,7 +382,7 @@ class TestSidecarBundleShape(unittest.TestCase):
|
||||
self.assertIn("/home/mitmproxy/.mitmproxy/mitmproxy-ca.pem", targets)
|
||||
|
||||
def test_volumes_union_full_matrix(self):
|
||||
sc = self._render(with_git=True, with_egress=True, supervise=True)[
|
||||
sc = self._render(with_git=True, with_egress=True)[
|
||||
"services"]["sidecars"]
|
||||
targets = {v["target"] for v in sc["volumes"]}
|
||||
self.assertIn("/home/mitmproxy/.mitmproxy/mitmproxy-ca.pem", targets)
|
||||
@@ -403,7 +397,7 @@ class TestSidecarBundleShape(unittest.TestCase):
|
||||
self.assertNotIn("extra_hosts", sc)
|
||||
|
||||
def test_agent_depends_on_bundle_only(self):
|
||||
sc = self._render(with_git=True, with_egress=True, supervise=True)[
|
||||
sc = self._render(with_git=True, with_egress=True)[
|
||||
"services"]["agent"]
|
||||
self.assertEqual(["sidecars"], sc["depends_on"])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user