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,14 +27,13 @@ from tests._docker import skip_unless_docker
|
||||
|
||||
|
||||
def _manifest() -> ManifestIndex:
|
||||
"""Bottle with supervise on so the bundle exercises egress +
|
||||
supervise. Git is off because a meaningful git-gate test needs
|
||||
a real upstream and SSH keys — out of scope for a bundle smoke."""
|
||||
"""Minimal bottle so the bundle exercises egress + supervise
|
||||
(every bottle is supervised, issue #249). Git is off because a
|
||||
meaningful git-gate test needs a real upstream and SSH keys —
|
||||
out of scope for a bundle smoke."""
|
||||
return ManifestIndex.from_json_obj({
|
||||
"bottles": {
|
||||
"dev": {
|
||||
"supervise": True,
|
||||
},
|
||||
"dev": {},
|
||||
},
|
||||
"agents": {
|
||||
"demo": {"skills": [], "prompt": "", "bottle": "dev"},
|
||||
|
||||
+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"])
|
||||
|
||||
|
||||
@@ -50,11 +50,8 @@ def _plan(
|
||||
agent_prompt: str = "",
|
||||
skills: list[str] | None = None,
|
||||
agent_provision: AgentProvisionPlan | None = None,
|
||||
supervise: bool = False,
|
||||
) -> DockerBottlePlan:
|
||||
bottle_json: dict = {"agent_provider": {"template": "claude"}} # type: ignore
|
||||
if supervise:
|
||||
bottle_json["supervise"] = True
|
||||
index = ManifestIndex.from_json_obj({
|
||||
"bottles": {"dev": bottle_json},
|
||||
"agents": {
|
||||
@@ -70,13 +67,11 @@ def _plan(
|
||||
manifest=index, agent_name="demo",
|
||||
copy_cwd=False, user_cwd="/tmp/x",
|
||||
)
|
||||
supervise_plan = None
|
||||
if supervise:
|
||||
supervise_plan = SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
)
|
||||
supervise_plan = SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
)
|
||||
return DockerBottlePlan(
|
||||
spec=spec,
|
||||
manifest=manifest,
|
||||
@@ -314,17 +309,10 @@ class TestClaudeUiProvision(unittest.TestCase):
|
||||
|
||||
|
||||
class TestClaudeSuperviseMcp(unittest.TestCase):
|
||||
def test_noop_when_supervise_disabled(self):
|
||||
bottle = _make_bottle()
|
||||
ClaudeAgentProvider().provision_supervise_mcp(
|
||||
_plan(supervise=False), bottle, _URL,
|
||||
)
|
||||
bottle.exec.assert_not_called()
|
||||
|
||||
def test_runs_claude_mcp_add_as_node(self):
|
||||
bottle = _make_bottle()
|
||||
ClaudeAgentProvider().provision_supervise_mcp(
|
||||
_plan(supervise=True), bottle, _URL,
|
||||
_plan(), bottle, _URL,
|
||||
)
|
||||
bottle.exec.assert_called_once()
|
||||
script = bottle.exec.call_args.args[0]
|
||||
@@ -340,7 +328,7 @@ class TestClaudeSuperviseMcp(unittest.TestCase):
|
||||
exec_result=ExecResult(returncode=1, stdout="", stderr="boom"),
|
||||
)
|
||||
ClaudeAgentProvider().provision_supervise_mcp(
|
||||
_plan(supervise=True), bottle, _URL,
|
||||
_plan(), bottle, _URL,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -50,11 +50,8 @@ def _plan(
|
||||
agent_prompt: str = "",
|
||||
skills: list[str] | None = None,
|
||||
agent_provision: AgentProvisionPlan | None = None,
|
||||
supervise: bool = False,
|
||||
) -> DockerBottlePlan:
|
||||
bottle_json: dict = {"agent_provider": {"template": "codex"}} # type: ignore
|
||||
if supervise:
|
||||
bottle_json["supervise"] = True
|
||||
index = ManifestIndex.from_json_obj({
|
||||
"bottles": {"dev": bottle_json},
|
||||
"agents": {
|
||||
@@ -70,13 +67,11 @@ def _plan(
|
||||
manifest=index, agent_name="demo",
|
||||
copy_cwd=False, user_cwd="/tmp/x",
|
||||
)
|
||||
supervise_plan = None
|
||||
if supervise:
|
||||
supervise_plan = SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
)
|
||||
supervise_plan = SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
)
|
||||
return DockerBottlePlan(
|
||||
spec=spec,
|
||||
manifest=manifest,
|
||||
@@ -277,17 +272,10 @@ class TestCodexProvision(unittest.TestCase):
|
||||
|
||||
|
||||
class TestCodexSuperviseMcp(unittest.TestCase):
|
||||
def test_noop_when_supervise_disabled(self):
|
||||
bottle = _make_bottle()
|
||||
CodexAgentProvider().provision_supervise_mcp(
|
||||
_plan(supervise=False), bottle, _URL,
|
||||
)
|
||||
bottle.exec.assert_not_called()
|
||||
|
||||
def test_runs_codex_mcp_add_as_node(self):
|
||||
bottle = _make_bottle()
|
||||
CodexAgentProvider().provision_supervise_mcp(
|
||||
_plan(supervise=True), bottle, _URL,
|
||||
_plan(), bottle, _URL,
|
||||
)
|
||||
bottle.exec.assert_called_once()
|
||||
script = bottle.exec.call_args.args[0]
|
||||
@@ -302,7 +290,7 @@ class TestCodexSuperviseMcp(unittest.TestCase):
|
||||
exec_result=ExecResult(returncode=1, stdout="", stderr="boom"),
|
||||
)
|
||||
CodexAgentProvider().provision_supervise_mcp(
|
||||
_plan(supervise=True), bottle, _URL,
|
||||
_plan(), bottle, _URL,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
||||
from bot_bottle.contrib.pi.agent_provider import PiAgentProvider
|
||||
from bot_bottle.egress import EgressPlan
|
||||
from bot_bottle.git_gate import GitGatePlan
|
||||
from bot_bottle.supervise import SupervisePlan
|
||||
from bot_bottle.manifest import ManifestIndex
|
||||
|
||||
|
||||
@@ -77,7 +78,11 @@ def _plan(
|
||||
routes=(),
|
||||
token_env_map={},
|
||||
),
|
||||
supervise_plan=None,
|
||||
supervise_plan=SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
),
|
||||
use_runsc=False,
|
||||
agent_provision=agent_provision or AgentProvisionPlan(
|
||||
template="pi", command="pi", prompt_mode="append_system_prompt",
|
||||
|
||||
@@ -16,6 +16,7 @@ from bot_bottle.backend.docker import launch as launch_mod
|
||||
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
||||
from bot_bottle.egress import EgressPlan
|
||||
from bot_bottle.git_gate import GitGatePlan
|
||||
from bot_bottle.supervise import SupervisePlan
|
||||
from bot_bottle.manifest import ManifestIndex
|
||||
|
||||
|
||||
@@ -55,7 +56,11 @@ def _plan(tmp: str) -> DockerBottlePlan:
|
||||
routes=(),
|
||||
token_env_map={},
|
||||
),
|
||||
supervise_plan=None,
|
||||
supervise_plan=SupervisePlan(
|
||||
slug=_SLUG,
|
||||
queue_dir=stage / "supervise" / "queue",
|
||||
current_config_dir=stage / "supervise" / "current-config",
|
||||
),
|
||||
agent_provision=AgentProvisionPlan(
|
||||
template="claude",
|
||||
command="claude",
|
||||
|
||||
@@ -21,6 +21,7 @@ from bot_bottle.backend.docker import launch as launch_mod
|
||||
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
||||
from bot_bottle.egress import EgressPlan
|
||||
from bot_bottle.git_gate import GitGatePlan
|
||||
from bot_bottle.supervise import SupervisePlan
|
||||
from bot_bottle.manifest import ManifestIndex
|
||||
|
||||
_INDEX = ManifestIndex.from_json_obj({
|
||||
@@ -56,7 +57,11 @@ def _plan(tmp: str) -> DockerBottlePlan:
|
||||
routes=(),
|
||||
token_env_map={},
|
||||
),
|
||||
supervise_plan=None,
|
||||
supervise_plan=SupervisePlan(
|
||||
slug="test-teardown-00001",
|
||||
queue_dir=stage / "supervise" / "queue",
|
||||
current_config_dir=stage / "supervise" / "current-config",
|
||||
),
|
||||
agent_provision=AgentProvisionPlan(
|
||||
template="claude",
|
||||
command="claude",
|
||||
|
||||
@@ -21,6 +21,7 @@ from bot_bottle.backend import Bottle, BottleSpec, ExecResult
|
||||
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
||||
from bot_bottle.egress import EgressPlan
|
||||
from bot_bottle.git_gate import GitGatePlan
|
||||
from bot_bottle.supervise import SupervisePlan
|
||||
from bot_bottle.manifest import ManifestIndex
|
||||
|
||||
|
||||
@@ -79,7 +80,11 @@ def _plan(*, git_user: dict | None = None, # type: ignore
|
||||
routes=(),
|
||||
token_env_map={},
|
||||
),
|
||||
supervise_plan=None,
|
||||
supervise_plan=SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
),
|
||||
use_runsc=False,
|
||||
agent_provision=AgentProvisionPlan(
|
||||
template="claude",
|
||||
|
||||
@@ -15,6 +15,7 @@ from bot_bottle.backend.macos_container import launch
|
||||
from bot_bottle.backend.macos_container.bottle_plan import MacosContainerBottlePlan
|
||||
from bot_bottle.egress import EgressPlan
|
||||
from bot_bottle.git_gate import GitGatePlan
|
||||
from bot_bottle.supervise import SupervisePlan
|
||||
from bot_bottle.manifest import ManifestIndex
|
||||
|
||||
_MANIFEST = ManifestIndex.from_json_obj({
|
||||
@@ -27,7 +28,6 @@ def _plan(
|
||||
*,
|
||||
stage_dir: Path,
|
||||
git: bool = False,
|
||||
supervise: bool = False,
|
||||
agent_git_gate_url: str = "",
|
||||
agent_supervise_url: str = "",
|
||||
) -> MacosContainerBottlePlan:
|
||||
@@ -67,10 +67,8 @@ def _plan(
|
||||
)
|
||||
else:
|
||||
git_gate_plan = SimpleNamespace(upstreams=())
|
||||
supervise_plan = (
|
||||
SimpleNamespace(queue_dir=Path("/state/supervise/queue"))
|
||||
if supervise else None
|
||||
)
|
||||
# Every bottle is supervised (issue #249).
|
||||
supervise_plan = SimpleNamespace(queue_dir=Path("/state/supervise/queue"))
|
||||
agent_provision = SimpleNamespace(
|
||||
guest_env={"LITERAL": "value"},
|
||||
provisioned_env={"CODEX_HOME": "/run/codex-home"},
|
||||
@@ -101,7 +99,7 @@ class TestMacosContainerLaunchArgv(unittest.TestCase):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def test_sidecar_argv_uses_egress_network_first_and_explicit_dns(self):
|
||||
plan = _plan(stage_dir=self.stage_dir, supervise=True)
|
||||
plan = _plan(stage_dir=self.stage_dir)
|
||||
with patch.object(launch.os, "environ", {
|
||||
"BOT_BOTTLE_MACOS_CONTAINER_DNS": "9.9.9.9",
|
||||
}):
|
||||
@@ -172,7 +170,7 @@ class TestMacosContainerLaunchArgv(unittest.TestCase):
|
||||
def test_git_gate_daemons_are_ready_gated(self):
|
||||
plan = _plan(stage_dir=self.stage_dir, git=True)
|
||||
self.assertEqual(
|
||||
("egress", "git-gate", "git-http"),
|
||||
("egress", "supervise", "git-gate", "git-http"),
|
||||
launch._sidecar_daemons(plan),
|
||||
)
|
||||
self.assertIn(
|
||||
@@ -181,7 +179,7 @@ class TestMacosContainerLaunchArgv(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_stamp_agent_urls_includes_git_http_when_git_gate_exists(self):
|
||||
plan = _plan(stage_dir=self.stage_dir, git=True, supervise=True)
|
||||
plan = _plan(stage_dir=self.stage_dir, git=True)
|
||||
with patch.object(launch.dataclasses, "replace") as replace:
|
||||
launch._stamp_agent_urls(plan, "192.168.128.2")
|
||||
replace.assert_called_once_with(
|
||||
@@ -272,7 +270,10 @@ def _build_plan(stage_dir: Path) -> MacosContainerBottlePlan:
|
||||
stage_dir=stage_dir,
|
||||
git_gate_plan=cast(GitGatePlan, SimpleNamespace(upstreams=())),
|
||||
egress_plan=cast(EgressPlan, SimpleNamespace()),
|
||||
supervise_plan=None,
|
||||
supervise_plan=cast(
|
||||
SupervisePlan,
|
||||
SimpleNamespace(queue_dir=Path("/state/supervise/queue")),
|
||||
),
|
||||
agent_provision=AgentProvisionPlan(
|
||||
template="claude",
|
||||
command="claude",
|
||||
|
||||
@@ -116,7 +116,6 @@ class TestAgentGitUserOverlay(unittest.TestCase):
|
||||
idx = ManifestIndex.from_json_obj({
|
||||
"bottles": {"dev": {
|
||||
"env": {"FOO": "bar"},
|
||||
"supervise": True,
|
||||
"git-gate": {"user": {"name": "B"}},
|
||||
}},
|
||||
"agents": {"impl": {
|
||||
@@ -127,7 +126,6 @@ class TestAgentGitUserOverlay(unittest.TestCase):
|
||||
b = idx.load_for_agent("impl").bottle
|
||||
self.assertEqual("a", b.git_user.name)
|
||||
self.assertEqual({"FOO": "bar"}, dict(b.env))
|
||||
self.assertTrue(b.supervise)
|
||||
|
||||
|
||||
class TestAgentGitUserRejections(unittest.TestCase):
|
||||
|
||||
@@ -42,38 +42,26 @@ class TestExtendsBasic(unittest.TestCase):
|
||||
# same way they did before the resolver landed.
|
||||
m = _build(dev={
|
||||
"env": {"FOO": "bar"},
|
||||
"supervise": True,
|
||||
})
|
||||
b = m.bottles["dev"]
|
||||
self.assertEqual({"FOO": "bar"}, dict(b.env))
|
||||
self.assertTrue(b.supervise)
|
||||
|
||||
def test_child_inherits_parent_fields_unchanged(self):
|
||||
m = _build(
|
||||
base={
|
||||
"env": {"BASE": "1"},
|
||||
"supervise": True,
|
||||
},
|
||||
child={"extends": "base"},
|
||||
)
|
||||
c = m.bottles["child"]
|
||||
self.assertEqual({"BASE": "1"}, dict(c.env))
|
||||
self.assertTrue(c.supervise)
|
||||
|
||||
def test_child_overrides_supervise_scalar(self):
|
||||
m = _build(
|
||||
base={"supervise": True},
|
||||
off={"extends": "base", "supervise": False},
|
||||
)
|
||||
self.assertTrue(m.bottles["base"].supervise)
|
||||
self.assertFalse(m.bottles["off"].supervise)
|
||||
|
||||
def test_parent_resolved_once_for_multiple_children(self):
|
||||
# Two children sharing one parent: both inherit; the parent
|
||||
# is resolved once + cached. (Cache behavior is internal; we
|
||||
# observe correctness on both children.)
|
||||
m = _build(
|
||||
base={"env": {"BASE": "1"}, "supervise": True},
|
||||
base={"env": {"BASE": "1"}},
|
||||
a={"extends": "base", "env": {"A": "1"}},
|
||||
b={"extends": "base", "env": {"B": "1"}},
|
||||
)
|
||||
@@ -366,7 +354,6 @@ class TestExtendsChain(unittest.TestCase):
|
||||
m = _build(
|
||||
grandparent={
|
||||
"env": {"GP": "1"},
|
||||
"supervise": True,
|
||||
},
|
||||
parent={
|
||||
"extends": "grandparent",
|
||||
@@ -381,8 +368,6 @@ class TestExtendsChain(unittest.TestCase):
|
||||
{"GP": "1", "P": "1", "C": "1"},
|
||||
dict(m.bottles["child"].env),
|
||||
)
|
||||
# supervise threads through unchanged.
|
||||
self.assertTrue(m.bottles["child"].supervise)
|
||||
|
||||
def test_intermediate_can_override(self):
|
||||
m = _build(
|
||||
|
||||
@@ -19,6 +19,7 @@ from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
||||
from bot_bottle.backend.smolmachines.bottle_plan import SmolmachinesBottlePlan
|
||||
from bot_bottle.egress import EgressPlan, EgressRoute
|
||||
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
||||
from bot_bottle.supervise import SupervisePlan
|
||||
from bot_bottle.manifest import Manifest, ManifestIndex
|
||||
|
||||
|
||||
@@ -77,6 +78,15 @@ def _egress_plan(tmp: str) -> EgressPlan:
|
||||
)
|
||||
|
||||
|
||||
def _supervise_plan(tmp: str) -> SupervisePlan:
|
||||
stage = Path(tmp)
|
||||
return SupervisePlan(
|
||||
slug="test-00001",
|
||||
queue_dir=stage / "supervise" / "queue",
|
||||
current_config_dir=stage / "supervise" / "current-config",
|
||||
)
|
||||
|
||||
|
||||
def _agent_provision(tmp: str) -> AgentProvisionPlan:
|
||||
return AgentProvisionPlan(
|
||||
template="claude",
|
||||
@@ -99,7 +109,7 @@ def _docker_plan(spec: BottleSpec, manifest: Manifest, tmp: str) -> DockerBottle
|
||||
stage_dir=stage,
|
||||
git_gate_plan=_git_gate_plan(tmp),
|
||||
egress_plan=_egress_plan(tmp),
|
||||
supervise_plan=None,
|
||||
supervise_plan=_supervise_plan(tmp),
|
||||
agent_provision=_agent_provision(tmp),
|
||||
slug="test-00001",
|
||||
forwarded_env={},
|
||||
@@ -115,7 +125,7 @@ def _smolmachines_plan(spec: BottleSpec, manifest: Manifest, tmp: str) -> Smolma
|
||||
stage_dir=stage,
|
||||
git_gate_plan=_git_gate_plan(tmp),
|
||||
egress_plan=_egress_plan(tmp),
|
||||
supervise_plan=None,
|
||||
supervise_plan=_supervise_plan(tmp),
|
||||
agent_provision=_agent_provision(tmp),
|
||||
slug="test-00001",
|
||||
bundle_subnet="10.99.0.0/24",
|
||||
|
||||
@@ -86,7 +86,6 @@ def _plan(
|
||||
stage_dir: Path | None = None,
|
||||
egress_routes: tuple[EgressRoute, ...] = (),
|
||||
egress_ca_path: Path = Path(),
|
||||
supervise: bool = False,
|
||||
bundle_ip: str = "192.168.50.2",
|
||||
agent_git_gate_host: str = "127.0.0.1:55555",
|
||||
agent_supervise_url: str = "http://127.0.0.1:55556/",
|
||||
@@ -108,8 +107,6 @@ def _plan(
|
||||
git_gate_json["user"] = git_user
|
||||
if git_gate_json:
|
||||
bottle_json["git-gate"] = git_gate_json
|
||||
if supervise:
|
||||
bottle_json["supervise"] = True
|
||||
index = ManifestIndex.from_json_obj({
|
||||
"bottles": {"dev": bottle_json},
|
||||
"agents": {
|
||||
@@ -127,13 +124,11 @@ def _plan(
|
||||
copy_cwd=copy_cwd,
|
||||
user_cwd=user_cwd,
|
||||
)
|
||||
supervise_plan = None
|
||||
if supervise:
|
||||
supervise_plan = SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
)
|
||||
supervise_plan = SupervisePlan(
|
||||
slug="demo-abc12",
|
||||
queue_dir=Path("/tmp/queue"),
|
||||
current_config_dir=Path("/tmp/current-config"),
|
||||
)
|
||||
return SmolmachinesBottlePlan(
|
||||
spec=spec,
|
||||
manifest=manifest,
|
||||
@@ -405,7 +400,7 @@ class TestBundleLaunchSpec(unittest.TestCase):
|
||||
spec = _bundle_launch_spec(plan, "net", "127.0.0.16")
|
||||
|
||||
self.assertEqual(
|
||||
"egress,git-gate,git-http",
|
||||
"egress,supervise,git-gate,git-http",
|
||||
spec.daemons_csv,
|
||||
)
|
||||
self.assertIn(9420, spec.ports_to_publish)
|
||||
|
||||
Reference in New Issue
Block a user