From 70a22fa21055bfb841365c558a3d5235d07411ce Mon Sep 17 00:00:00 2001 From: didericis Date: Sun, 10 May 2026 23:59:38 -0400 Subject: [PATCH] refactor: rename platform abstraction to backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Across the package: - claude_bottle/platform/ -> claude_bottle/backend/ - platform/docker/platform.py -> backend/docker/backend.py - class BottlePlatform -> BottleBackend - class DockerBottlePlatform -> DockerBottleBackend - get_bottle_platform() -> get_bottle_backend() - env var CLAUDE_BOTTLE_PLATFORM -> CLAUDE_BOTTLE_BACKEND - dict _PLATFORMS -> _BACKENDS "Backend" is shorter and more established as the term for a pluggable strategy-pattern implementation. "Platform" was vague (could mean OS, hardware, cloud) and mildly redundant — Docker is itself a platform. The previous PRD section claiming "the Backend protocol was rejected" referred to a low-level run/exec/cp/network_connect protocol; the name was never the reason. The PRD is updated to describe that rejected design by shape rather than by name. The bottle/agent concepts and the manifest schema are unchanged. --- .../{platform => backend}/__init__.py | 63 ++++++++++--------- .../{platform => backend}/docker/__init__.py | 11 ++-- .../platform.py => backend/docker/backend.py} | 12 ++-- .../{platform => backend}/docker/bottle.py | 2 +- .../docker/bottle_cleanup_plan.py | 4 +- .../docker/bottle_plan.py | 4 +- .../{platform => backend}/docker/network.py | 0 .../{platform => backend}/docker/util.py | 2 +- claude_bottle/cli/cleanup.py | 8 +-- claude_bottle/cli/list.py | 4 +- claude_bottle/cli/start.py | 8 +-- docs/prds/0003-bottle-factory-abstraction.md | 58 ++++++++--------- tests/test_orphan_cleanup.py | 2 +- 13 files changed, 91 insertions(+), 87 deletions(-) rename claude_bottle/{platform => backend}/__init__.py (66%) rename claude_bottle/{platform => backend}/docker/__init__.py (68%) rename claude_bottle/{platform/docker/platform.py => backend/docker/backend.py} (97%) rename claude_bottle/{platform => backend}/docker/bottle.py (97%) rename claude_bottle/{platform => backend}/docker/bottle_cleanup_plan.py (87%) rename claude_bottle/{platform => backend}/docker/bottle_plan.py (95%) rename claude_bottle/{platform => backend}/docker/network.py (100%) rename claude_bottle/{platform => backend}/docker/util.py (98%) diff --git a/claude_bottle/platform/__init__.py b/claude_bottle/backend/__init__.py similarity index 66% rename from claude_bottle/platform/__init__.py rename to claude_bottle/backend/__init__.py index 893eb5b..ea67c52 100644 --- a/claude_bottle/platform/__init__.py +++ b/claude_bottle/backend/__init__.py @@ -1,7 +1,7 @@ -"""Per-platform bottle factories. +"""Per-backend bottle factories. A bottle is a running, isolated environment with claude inside. Each -platform exposes four methods: +backend exposes five methods: prepare(spec, stage_dir=...) -> BottlePlan Resolves names, validates host-side prerequisites, and writes @@ -19,8 +19,11 @@ platform exposes four methods: cleanup(plan) -> None Actually removes everything described by the cleanup plan. -Selection is driven by CLAUDE_BOTTLE_PLATFORM (default "docker"). Per -PRD 0003 the manifest does not carry a platform field; the host + list_active() -> None + Print every currently-running bottle on this backend to stderr. + +Selection is driven by CLAUDE_BOTTLE_BACKEND (default "docker"). Per +PRD 0003 the manifest does not carry a backend field; the host environment picks. """ @@ -38,8 +41,8 @@ from ..manifest import Manifest @dataclass(frozen=True) class BottleSpec: - """CLI-supplied intent. Platform-agnostic — each platform's prepare - step consumes it and produces its own platform-specific plan. + """CLI-supplied intent. Backend-agnostic — each backend's prepare + step consumes it and produces its own backend-specific plan. Resolved values (image names, container name, scratch paths, runsc availability) live on the plan, not the spec.""" @@ -52,8 +55,8 @@ class BottleSpec: @dataclass(frozen=True) class BottlePlan(ABC): - """Base output of a platform's prepare step. Concrete subclasses - (e.g. DockerBottlePlan) add platform-specific resolved fields and + """Base output of a backend's prepare step. Concrete subclasses + (e.g. DockerBottlePlan) add backend-specific resolved fields and implement `print`.""" spec: BottleSpec @@ -66,8 +69,8 @@ class BottlePlan(ABC): @dataclass(frozen=True) class BottleCleanupPlan(ABC): - """Base output of a platform's prepare_cleanup step. Concrete - subclasses (e.g. DockerBottleCleanupPlan) carry platform-specific + """Base output of a backend's prepare_cleanup step. Concrete + subclasses (e.g. DockerBottleCleanupPlan) carry backend-specific lists of resources to be removed and implement `print` + `empty`.""" @abstractmethod @@ -82,7 +85,7 @@ class BottleCleanupPlan(ABC): class Bottle(ABC): - """Handle to a running bottle. Yielded by a platform's launch step. + """Handle to a running bottle. Yielded by a backend's launch step. `exec_claude` runs `claude` inside the bottle and blocks until the session ends. `cp_in` copies a host path into the bottle. `close` @@ -101,9 +104,9 @@ class Bottle(ABC): def close(self) -> None: ... -class BottlePlatform(ABC): - """Abstract base for selectable bottle platforms. Concrete subclasses - (e.g. DockerBottlePlatform) own their own prepare/launch impls. +class BottleBackend(ABC): + """Abstract base for selectable bottle backends. Concrete subclasses + (e.g. DockerBottleBackend) own their own prepare/launch impls. Symmetric with the BottlePlan → DockerBottlePlan hierarchy.""" name: str @@ -128,37 +131,37 @@ class BottlePlatform(ABC): @abstractmethod def list_active(self) -> None: - """Print every currently-running bottle on this platform to + """Print every currently-running bottle on this backend to stderr (name + status).""" -# Import concrete platform classes AFTER the base types are defined, so -# each platform module can pull BottleSpec / BottlePlan / BottlePlatform +# Import concrete backend classes AFTER the base types are defined, so +# each backend module can pull BottleSpec / BottlePlan / BottleBackend # via `from . import ...` without hitting a partially-initialized module. -from .docker import DockerBottlePlatform # noqa: E402 +from .docker import DockerBottleBackend # noqa: E402 -_PLATFORMS: dict[str, BottlePlatform] = { - "docker": DockerBottlePlatform(), +_BACKENDS: dict[str, BottleBackend] = { + "docker": DockerBottleBackend(), } -def get_bottle_platform() -> BottlePlatform: - """Resolve the bottle platform for the active environment. Dies with - a pointer at the known platforms if CLAUDE_BOTTLE_PLATFORM names an +def get_bottle_backend() -> BottleBackend: + """Resolve the bottle backend for the active environment. Dies with + a pointer at the known backends if CLAUDE_BOTTLE_BACKEND names an unimplemented one.""" - name = os.environ.get("CLAUDE_BOTTLE_PLATFORM", "docker") - if name not in _PLATFORMS: - known = ", ".join(sorted(_PLATFORMS)) - die(f"unknown CLAUDE_BOTTLE_PLATFORM={name!r}; known platforms: {known}") - return _PLATFORMS[name] + name = os.environ.get("CLAUDE_BOTTLE_BACKEND", "docker") + if name not in _BACKENDS: + known = ", ".join(sorted(_BACKENDS)) + die(f"unknown CLAUDE_BOTTLE_BACKEND={name!r}; known backends: {known}") + return _BACKENDS[name] __all__ = [ "Bottle", + "BottleBackend", "BottleCleanupPlan", "BottlePlan", - "BottlePlatform", "BottleSpec", - "get_bottle_platform", + "get_bottle_backend", ] diff --git a/claude_bottle/platform/docker/__init__.py b/claude_bottle/backend/docker/__init__.py similarity index 68% rename from claude_bottle/platform/docker/__init__.py rename to claude_bottle/backend/docker/__init__.py index f3c4cf1..7af34a0 100644 --- a/claude_bottle/platform/docker/__init__.py +++ b/claude_bottle/backend/docker/__init__.py @@ -1,28 +1,29 @@ -"""Docker bottle platform. +"""Docker bottle backend. The bulk of the implementation lives in sibling modules: - util: thin Docker subprocess wrappers + - network: Docker network plumbing - bottle_plan: DockerBottlePlan - bottle_cleanup_plan: DockerBottleCleanupPlan - bottle: DockerBottle handle - - platform: DockerBottlePlatform + - backend: DockerBottleBackend This file only re-exports the public names so -`from claude_bottle.platform.docker import DockerBottlePlatform` keeps +`from claude_bottle.backend.docker import DockerBottleBackend` keeps working. """ from __future__ import annotations +from .backend import DockerBottleBackend from .bottle import DockerBottle from .bottle_cleanup_plan import DockerBottleCleanupPlan from .bottle_plan import DockerBottlePlan -from .platform import DockerBottlePlatform __all__ = [ "DockerBottle", + "DockerBottleBackend", "DockerBottleCleanupPlan", "DockerBottlePlan", - "DockerBottlePlatform", ] diff --git a/claude_bottle/platform/docker/platform.py b/claude_bottle/backend/docker/backend.py similarity index 97% rename from claude_bottle/platform/docker/platform.py rename to claude_bottle/backend/docker/backend.py index 9434efa..c425874 100644 --- a/claude_bottle/platform/docker/platform.py +++ b/claude_bottle/backend/docker/backend.py @@ -1,4 +1,4 @@ -"""DockerBottlePlatform — the Docker implementation of BottlePlatform. +"""DockerBottleBackend — the Docker implementation of BottleBackend. Methods: .prepare(spec, stage_dir=...) -> DockerBottlePlan @@ -22,7 +22,7 @@ from ... import skills as skills_mod from ... import ssh as ssh_mod from ...env_resolve import env_resolve from ...log import die, info -from .. import BottleCleanupPlan, BottlePlan, BottlePlatform, BottleSpec +from .. import BottleBackend, BottleCleanupPlan, BottlePlan, BottleSpec from . import network as network_mod from . import util as docker_mod from .bottle import DockerBottle @@ -34,8 +34,8 @@ from .bottle_plan import DockerBottlePlan _REPO_DIR = str(Path(__file__).resolve().parent.parent.parent.parent) -class DockerBottlePlatform(BottlePlatform): - """Docker platform implementation. Selected by CLAUDE_BOTTLE_PLATFORM +class DockerBottleBackend(BottleBackend): + """Docker backend implementation. Selected by CLAUDE_BOTTLE_BACKEND (default).""" name = "docker" @@ -131,7 +131,7 @@ class DockerBottlePlatform(BottlePlatform): def launch(self, plan: BottlePlan) -> Iterator[DockerBottle]: """Build, launch, and provision a Docker bottle. Teardown on exit.""" assert isinstance(plan, DockerBottlePlan), ( - f"DockerBottlePlatform.launch expects DockerBottlePlan, " + f"DockerBottleBackend.launch expects DockerBottlePlan, " f"got {type(plan).__name__}" ) @@ -358,7 +358,7 @@ class DockerBottlePlatform(BottlePlatform): Containers first; networks would refuse to delete while containers are still attached.""" assert isinstance(plan, DockerBottleCleanupPlan), ( - f"DockerBottlePlatform.cleanup expects DockerBottleCleanupPlan, " + f"DockerBottleBackend.cleanup expects DockerBottleCleanupPlan, " f"got {type(plan).__name__}" ) for name in plan.containers: diff --git a/claude_bottle/platform/docker/bottle.py b/claude_bottle/backend/docker/bottle.py similarity index 97% rename from claude_bottle/platform/docker/bottle.py rename to claude_bottle/backend/docker/bottle.py index 32a3770..a93e64d 100644 --- a/claude_bottle/platform/docker/bottle.py +++ b/claude_bottle/backend/docker/bottle.py @@ -1,5 +1,5 @@ """DockerBottle — concrete Bottle handle yielded by -DockerBottlePlatform.launch. +DockerBottleBackend.launch. Holds the container name plus the in-container prompt path so exec_claude can transparently add --append-system-prompt-file when a diff --git a/claude_bottle/platform/docker/bottle_cleanup_plan.py b/claude_bottle/backend/docker/bottle_cleanup_plan.py similarity index 87% rename from claude_bottle/platform/docker/bottle_cleanup_plan.py rename to claude_bottle/backend/docker/bottle_cleanup_plan.py index 1cfa766..fd54ad4 100644 --- a/claude_bottle/platform/docker/bottle_cleanup_plan.py +++ b/claude_bottle/backend/docker/bottle_cleanup_plan.py @@ -1,7 +1,7 @@ """DockerBottleCleanupPlan — concrete subclass of BottleCleanupPlan. Holds the tuples of container and network names that -DockerBottlePlatform.cleanup will remove. The y/N preflight reads +DockerBottleBackend.cleanup will remove. The y/N preflight reads these via `print`; the CLI short-circuits via `empty`. """ @@ -16,7 +16,7 @@ from .. import BottleCleanupPlan @dataclass(frozen=True) class DockerBottleCleanupPlan(BottleCleanupPlan): - """Resources DockerBottlePlatform.cleanup will remove. Produced by + """Resources DockerBottleBackend.cleanup will remove. Produced by `prepare_cleanup` from a snapshot of `docker ps -a` + `docker network ls`; sorted so the y/N output is stable.""" diff --git a/claude_bottle/platform/docker/bottle_plan.py b/claude_bottle/backend/docker/bottle_plan.py similarity index 95% rename from claude_bottle/platform/docker/bottle_plan.py rename to claude_bottle/backend/docker/bottle_plan.py index f729c9c..187987e 100644 --- a/claude_bottle/platform/docker/bottle_plan.py +++ b/claude_bottle/backend/docker/bottle_plan.py @@ -1,7 +1,7 @@ """DockerBottlePlan — concrete subclass of BottlePlan. Carries the Docker-specific resolved fields produced by -DockerBottlePlatform.prepare. The launch step consumes it without +DockerBottleBackend.prepare. The launch step consumes it without further resolution; show_plan-style rendering is the `print` method. """ @@ -18,7 +18,7 @@ from .. import BottlePlan @dataclass(frozen=True) class DockerBottlePlan(BottlePlan): """Docker-specific resolved fields produced by - DockerBottlePlatform.prepare. Inherits `spec` and `stage_dir` from + DockerBottleBackend.prepare. Inherits `spec` and `stage_dir` from BottlePlan.""" slug: str diff --git a/claude_bottle/platform/docker/network.py b/claude_bottle/backend/docker/network.py similarity index 100% rename from claude_bottle/platform/docker/network.py rename to claude_bottle/backend/docker/network.py diff --git a/claude_bottle/platform/docker/util.py b/claude_bottle/backend/docker/util.py similarity index 98% rename from claude_bottle/platform/docker/util.py rename to claude_bottle/backend/docker/util.py index 221e8a2..75318e8 100644 --- a/claude_bottle/platform/docker/util.py +++ b/claude_bottle/backend/docker/util.py @@ -1,4 +1,4 @@ -"""Docker host-side primitives used by DockerBottlePlatform: probing +"""Docker host-side primitives used by DockerBottleBackend: probing for docker on PATH, slugifying agent names, checking image/container existence, and building images.""" diff --git a/claude_bottle/cli/cleanup.py b/claude_bottle/cli/cleanup.py index 1228d9e..cfaafa1 100644 --- a/claude_bottle/cli/cleanup.py +++ b/claude_bottle/cli/cleanup.py @@ -5,14 +5,14 @@ from __future__ import annotations import sys -from ..platform import get_bottle_platform +from ..backend import get_bottle_backend from ..log import info from ._common import read_tty_line def cmd_cleanup(_argv: list[str]) -> int: - platform = get_bottle_platform() - plan = platform.prepare_cleanup() + backend = get_bottle_backend() + plan = backend.prepare_cleanup() if plan.empty: info("no claude-bottle resources to clean up") @@ -26,6 +26,6 @@ def cmd_cleanup(_argv: list[str]) -> int: info("aborted") return 0 - platform.cleanup(plan) + backend.cleanup(plan) info("done") return 0 diff --git a/claude_bottle/cli/list.py b/claude_bottle/cli/list.py index 5ce8079..74f52ee 100644 --- a/claude_bottle/cli/list.py +++ b/claude_bottle/cli/list.py @@ -4,7 +4,7 @@ from __future__ import annotations import argparse -from ..platform import get_bottle_platform +from ..backend import get_bottle_backend from ..manifest import Manifest from ._common import PROG, USER_CWD @@ -20,5 +20,5 @@ def cmd_list(argv: list[str]) -> int: print(name) return 0 - get_bottle_platform().list_active() + get_bottle_backend().list_active() return 0 diff --git a/claude_bottle/cli/start.py b/claude_bottle/cli/start.py index ba01baa..81b2c5e 100644 --- a/claude_bottle/cli/start.py +++ b/claude_bottle/cli/start.py @@ -11,7 +11,7 @@ import sys import tempfile from pathlib import Path -from ..platform import BottleSpec, get_bottle_platform +from ..backend import BottleSpec, get_bottle_backend from ..log import info from ..manifest import Manifest from ._common import PROG, USER_CWD, read_tty_line @@ -38,8 +38,8 @@ def cmd_start(argv: list[str]) -> int: stage_dir = Path(tempfile.mkdtemp(prefix="claude-bottle-stage.")) try: - platform = get_bottle_platform() - plan = platform.prepare(spec, stage_dir=stage_dir) + backend = get_bottle_backend() + plan = backend.prepare(spec, stage_dir=stage_dir) plan.print(remote_control=args.remote_control) if dry_run: @@ -53,7 +53,7 @@ def cmd_start(argv: list[str]) -> int: info("aborted by user") return 0 - with platform.launch(plan) as bottle: + with backend.launch(plan) as bottle: info( "attaching interactive claude session " "(Ctrl-D or 'exit' to leave; container will be removed)" diff --git a/docs/prds/0003-bottle-factory-abstraction.md b/docs/prds/0003-bottle-factory-abstraction.md index 0a8e080..35b41e0 100644 --- a/docs/prds/0003-bottle-factory-abstraction.md +++ b/docs/prds/0003-bottle-factory-abstraction.md @@ -6,10 +6,10 @@ ## Summary -Introduce a per-platform factory function that owns the end-to-end +Introduce a per-backend factory function that owns the end-to-end lifecycle of a "bottle" (a running, isolated environment with claude inside). The first and only implementation lands as -`create_docker_bottle`. No second platform ships in this PRD. +`create_docker_bottle`. No second backend ships in this PRD. ## Problem @@ -33,11 +33,11 @@ Today, "how to launch a bottle" is spread across roughly six modules and exists only because the current code can't decide on its own. The shape that fits the project's actual goals (isolated agent runs -across multiple platforms) is "one factory per platform," not "one +across multiple backends) is "one factory per backend," not "one container-runtime SDK with N drivers." A previous draft of this PRD -considered a low-level `Backend` protocol (`run`, `exec`, `cp`, -`network_connect`, ...) and rejected it as the wrong layer — it would -have forced fly.io to pretend it's Docker. +considered a low-level runtime-primitive protocol (`run`, `exec`, +`cp`, `network_connect`, ...) and rejected it as the wrong layer — +it would have forced fly.io to pretend it's Docker. ## Goals / Success Criteria @@ -57,7 +57,7 @@ The feature works when all of the following are observable: The feature is **done** when all of the following ship: -- A new `claude_bottle/bottles/` package exists with +- A new `claude_bottle/backend/` package exists with `__init__.py` (factory selection) and `docker.py` (`create_docker_bottle`). - `create_docker_bottle` returns a context manager yielding a `Bottle` @@ -65,23 +65,23 @@ The feature is **done** when all of the following ship: and teardown on context exit. - Every existing `subprocess.run(["docker", ...])` call in `cli/start.py`, `pipelock.py`, `network.py`, `ssh.py`, and - `skills.py` either moves into `bottles/docker.py` or is called from + `skills.py` either moves into `backend/docker.py` or is called from it. No top-level CLI code references `docker` directly. - `bottles[].runtime` is removed from the manifest schema, the dataclass in `manifest.py`, the example manifest, and any README / docs references. `require_runsc()` in `claude_bottle/docker.py` is deleted. -- A single env var, `CLAUDE_BOTTLE_PLATFORM` (default `"docker"`), +- A single env var, `CLAUDE_BOTTLE_BACKEND` (default `"docker"`), selects the factory. Unknown values die at startup with a list of - known platforms. + known backends. - The y/N preflight in `cli.py` includes the resolved Docker runtime alongside the allowlist summary. ## Non-goals -- No second platform implementation. `create_container_bottle` and +- No second backend implementation. `create_container_bottle` and `create_flyio_bottle` are not in this PRD. The factory dict in - `bottles/__init__.py` ships with one entry. + `backend/__init__.py` ships with one entry. - No retries, async, or streaming exec. The current code is synchronous `subprocess.run`; the `Bottle` handle matches. - No behavior change beyond the runsc auto-detect. Pipelock topology, @@ -89,14 +89,14 @@ The feature is **done** when all of the following ship: provisioning all stay byte-identical. - No `--require-runsc` CLI escape hatch. If a user later wants "fail rather than silently downgrade," that's a follow-up. -- No `bottles[].platform` manifest field. Platform is a property of +- No `bottles[].backend` manifest field. Backend is a property of the host environment, not the bottle definition (at least for now). ## Scope ### In scope -- New `claude_bottle/bottles/` package containing `__init__.py` and +- New `claude_bottle/backend/` package containing `__init__.py` and `docker.py`. - The `Bottle` Protocol definition and `create_docker_bottle` factory. - Moving Docker-specific subprocess calls into the factory. @@ -113,9 +113,9 @@ The feature is **done** when all of the following ship: - Apple `container` and fly.io factories (separate PRDs, deferred until the Docker factory is the only thing shipping). -- Generalizing the pipelock sidecar to other platforms. Pipelock +- Generalizing the pipelock sidecar to other backends. Pipelock topology is, after this PRD, an implementation detail private to - `bottles/docker.py`. + `backend/docker.py`. - Rewriting `pipelock.py`'s YAML generation. The allowlist→YAML translation stays where it is and is called by the Docker factory. - Changes to `env_resolve.py`, `manifest.py` (beyond the `runtime` @@ -126,13 +126,13 @@ The feature is **done** when all of the following ship: ### New services / components -A new package, `claude_bottle/bottles/`: +A new package, `claude_bottle/backend/`: -- **`claude_bottle/bottles/__init__.py`** — Defines the `Bottle` +- **`claude_bottle/backend/__init__.py`** — Defines the `Bottle` Protocol and `get_bottle_factory()`. The factory registry is a - module-level dict mapping platform name → factory function. - Selection reads `CLAUDE_BOTTLE_PLATFORM` (default `"docker"`). - Unknown values call `die()` with the list of known platforms. + module-level dict mapping backend name → factory function. + Selection reads `CLAUDE_BOTTLE_BACKEND` (default `"docker"`). + Unknown values call `die()` with the list of known backends. ```python class Bottle(Protocol): @@ -145,7 +145,7 @@ A new package, `claude_bottle/bottles/`: ... ``` -- **`claude_bottle/bottles/docker.py`** — `create_docker_bottle(...)`, +- **`claude_bottle/backend/docker.py`** — `create_docker_bottle(...)`, the only factory implementation in this PRD. Owns: - probing for `runsc` availability (`docker info --format '{{json .Runtimes}}'`), @@ -178,19 +178,19 @@ A new package, `claude_bottle/bottles/`: consumes. - **`claude_bottle/pipelock.py`** — keep all the allowlist resolution and YAML generation. Remove `pipelock_start` / `pipelock_stop` (or - inline them into `bottles/docker.py` — decide during + inline them into `backend/docker.py` — decide during implementation). Pipelock-the-sidecar becomes a Docker-factory internal concept. - **`claude_bottle/network.py`** — same call-sites moved into - `bottles/docker.py`. The module either becomes a thin set of pure + `backend/docker.py`. The module either becomes a thin set of pure name-derivation helpers (`network_name_for_slug`, etc.) or folds - entirely into `bottles/docker.py`. Decide during implementation. + entirely into `backend/docker.py`. Decide during implementation. - **`claude_bottle/ssh.py`** and **`claude_bottle/skills.py`** — the `docker cp` and `docker exec` calls move into / are called from - `bottles/docker.py`. The host-side file-tree generation stays put. + `backend/docker.py`. The host-side file-tree generation stays put. - **`claude-bottle.example.json`** — remove the `runtime` field from any example bottle. -- **`README.md`** — note `CLAUDE_BOTTLE_PLATFORM` and the runsc +- **`README.md`** — note `CLAUDE_BOTTLE_BACKEND` and the runsc auto-detect; remove any mention of `runtime: "runsc"` as a manifest field. @@ -241,9 +241,9 @@ they're about to run under before approving. - **Where the pipelock sidecar lifecycle lives.** Two reasonable splits: (a) `pipelock.py` keeps `pipelock_start` / `pipelock_stop` - and `bottles/docker.py` calls them; (b) the sidecar + and `backend/docker.py` calls them; (b) the sidecar `docker create/cp/network connect/start` sequence moves entirely - into `bottles/docker.py` and `pipelock.py` shrinks to the YAML + + into `backend/docker.py` and `pipelock.py` shrinks to the YAML + allowlist helpers. (a) keeps git blame intact and is the smaller diff; (b) makes pipelock-as-an-implementation-detail more obvious. Decide during implementation. diff --git a/tests/test_orphan_cleanup.py b/tests/test_orphan_cleanup.py index 3b9ae79..462c40a 100644 --- a/tests/test_orphan_cleanup.py +++ b/tests/test_orphan_cleanup.py @@ -7,7 +7,7 @@ import os import subprocess import unittest -from claude_bottle.platform.docker.network import ( +from claude_bottle.backend.docker.network import ( network_create_egress, network_create_internal, network_remove,