refactor(sidecars): bundle is the only shape (PRD 0024 chunk 5)
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 43s

The CLAUDE_BOTTLE_SIDECAR_BUNDLE feature flag is gone. Every
bottle ships with the agent + bundle pair — no opt-in, no legacy
four-sidecar fallback.

Changes:

- Renderer (compose.py): bottle_plan_to_compose unconditionally
  emits {agent, sidecars}. Deleted _pipelock_service,
  _git_gate_service, _egress_service, _supervise_service helpers.
  _agent_service.depends_on collapses to ["sidecars"].

- sidecar_bundle.py: deleted sidecar_bundle_enabled (the flag
  parser). SIDECAR_BUNDLE_IMAGE + container-name helper stay.

- pipelock_apply.py: docker cp + docker restart now target
  sidecar_bundle_container_name(slug). Bundle restart bounces
  all four daemons together (per-daemon reload is the eventual
  feature, not v1).

- Per-sidecar modules trimmed:
  - egress.py: dropped EGRESS_IMAGE, EGRESS_DOCKERFILE,
    build_egress_image, egress_url. Kept EGRESS_PORT, CA paths,
    egress_container_name (still used by the renderer's network
    aliases).
  - git_gate.py: dropped GIT_GATE_IMAGE, GIT_GATE_DOCKERFILE,
    build_git_gate_image. Kept git_gate_host + GIT_GATE_PORT.
  - supervise.py: dropped SUPERVISE_IMAGE, SUPERVISE_DOCKERFILE,
    build_supervise_image, supervise_url.

- Deleted Dockerfile.{egress,git-gate,supervise}. The bundle's
  Dockerfile.sidecars is the only sidecar image now.

- test_compose.py: deleted TestPipelockAlwaysPresent,
  TestConditionalGitGate, TestConditionalEgress,
  TestConditionalSupervise, TestFullMatrix (legacy-shape only),
  TestSidecarBundleFlag (flag is gone). TestSidecarBundleShape
  drops its patch.dict wrapper. TestAgentAlwaysPresent's
  depends_on cases collapse to one.

- test_pipelock_apply.py: bringup container name uses
  sidecar_bundle_container_name(slug) to match the production
  target.

- README.md Architecture section rewritten to describe the
  agent + bundle pair.

Net: -626 lines.

Test status: 498 unit + 27 integration + 1 skipped (chunk-4
pending — superseded by this chunk's rewrite). Locally verified
end-to-end bottle launch produces exactly 2 containers
(claude-bottle-<slug> + claude-bottle-sidecars-<slug>).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 01:37:21 -04:00
parent 9348d4b343
commit 62f6f8db34
12 changed files with 117 additions and 743 deletions
+11 -2
View File
@@ -38,7 +38,6 @@ from claude_bottle.backend.docker.pipelock import (
PIPELOCK_IMAGE,
PIPELOCK_PORT,
DockerPipelockProxy,
pipelock_container_name,
pipelock_tls_init,
)
from claude_bottle.backend.docker.pipelock_apply import (
@@ -47,6 +46,9 @@ from claude_bottle.backend.docker.pipelock_apply import (
fetch_current_allowlist,
fetch_current_yaml,
)
from claude_bottle.backend.docker.sidecar_bundle import (
sidecar_bundle_container_name,
)
from claude_bottle.yaml_subset import parse_yaml_subset
from tests._docker import skip_unless_docker
from tests.fixtures import fixture_minimal
@@ -107,7 +109,14 @@ class TestPipelockApply(unittest.TestCase):
self.egress_net = network_create_egress(self.slug)
ca_cert_host, ca_key_host = pipelock_tls_init(state_dir)
self.sidecar_name = pipelock_container_name(self.slug)
# apply_allowlist_change targets sidecar_bundle_container_name
# (chunk 5 flipped the bundle to the only shape). Bringing the
# standalone pipelock up under that name keeps this test
# exercising the real production code path; the bundle's
# other three daemons aren't running here, but the
# apply/fetch code only touches /etc/pipelock.yaml + the
# pipelock binary, so the lighter setup is fine.
self.sidecar_name = sidecar_bundle_container_name(self.slug)
subprocess.run(
["docker", "create",
"--name", self.sidecar_name,