refactor(pipelock): start/stop become methods on PipelockProxy
test / run tests/run_tests.py (pull_request) Successful in 31s

ProxyPlan -> PipelockProxyPlan, with two additional fields populated
in launch: internal_network, egress_network (default ""). prepare
fills yaml_path + slug; launch uses dataclasses.replace to populate
the networks before calling start.

pipelock_start -> PipelockProxy.start(plan). Reads yaml_path, slug,
internal_network, egress_network off the plan. Returns the resolved
container name.

pipelock_stop -> PipelockProxy.stop(proxy_target). Takes the resolved
container name directly (the value that start returned); no longer
needs to know about slugs or naming conventions.

Backend launch passes the running container name (state["pipelock"])
to stop. Test for stop's idempotency uses pipelock_container_name to
construct the proxy_target.
This commit is contained in:
2026-05-11 10:57:07 -04:00
parent c2cdb7777d
commit ff962d2893
6 changed files with 107 additions and 101 deletions
+4 -3
View File
@@ -1,7 +1,8 @@
"""Integration: the cleanup primitives the start-flow trap depends on
are idempotent. The original orphan-network bug was a trap-ordering
issue; the fix moved the install earlier. The trap is only safe if
network_remove and pipelock_stop are no-ops against missing resources."""
network_remove and PipelockProxy.stop are no-ops against missing
resources."""
import os
import subprocess
@@ -12,7 +13,7 @@ from claude_bottle.backend.docker.network import (
network_create_internal,
network_remove,
)
from claude_bottle.pipelock import pipelock_stop
from claude_bottle.pipelock import PipelockProxy, pipelock_container_name
from tests._docker import skip_unless_docker
@@ -68,7 +69,7 @@ class TestOrphanCleanup(unittest.TestCase):
def test_pipelock_stop_missing_sidecar(self):
# Should not raise.
pipelock_stop(f"missing-{self.slug}")
PipelockProxy().stop(pipelock_container_name(f"missing-{self.slug}"))
if __name__ == "__main__":
+1 -1
View File
@@ -38,7 +38,7 @@ class TestPipelockSidecarSmoke(unittest.TestCase):
)
def test_smoke(self):
yaml_path = self.work_dir / "pipelock.yaml"
PipelockProxy().prepare(fixture_minimal(), "dev", yaml_path)
PipelockProxy().prepare(fixture_minimal(), "dev", "demo", yaml_path)
create = subprocess.run(
[
+4 -4
View File
@@ -23,7 +23,7 @@ class TestPipelockProxyPrepare(unittest.TestCase):
def test_minimal(self):
yaml_path = self.out_dir / "min.yaml"
self.proxy.prepare(fixture_minimal(), "dev", yaml_path)
self.proxy.prepare(fixture_minimal(), "dev", "demo", yaml_path)
content = yaml_path.read_text()
self.assertIn("mode: strict", content)
self.assertIn("enforce: true", content)
@@ -41,7 +41,7 @@ class TestPipelockProxyPrepare(unittest.TestCase):
def test_ssh_blocks(self):
yaml_path = self.out_dir / "ssh.yaml"
self.proxy.prepare(fixture_with_ssh(), "dev", yaml_path)
self.proxy.prepare(fixture_with_ssh(), "dev", "demo", yaml_path)
content = yaml_path.read_text()
self.assertIn("trusted_domains:", content)
self.assertIn("github.com", content)
@@ -65,7 +65,7 @@ class TestPipelockProxyPrepare(unittest.TestCase):
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
})
yaml_path = self.out_dir / "secret.yaml"
self.proxy.prepare(manifest, "dev", yaml_path)
self.proxy.prepare(manifest, "dev", "demo", yaml_path)
content = yaml_path.read_text()
self.assertNotIn("literal-value-should-not-appear", content)
self.assertNotIn("MY_SECRET", content)
@@ -73,7 +73,7 @@ class TestPipelockProxyPrepare(unittest.TestCase):
def test_file_mode_is_600(self):
yaml_path = self.out_dir / "min.yaml"
self.proxy.prepare(fixture_minimal(), "dev", yaml_path)
self.proxy.prepare(fixture_minimal(), "dev", "demo", yaml_path)
mode = os.stat(yaml_path).st_mode & 0o777
self.assertEqual(0o600, mode)