refactor(manifest): rename egress_proxy key to egress
Now that `bottle.egress` (the old allowlist/dlp_action block) is
gone, the longer `egress_proxy:` disambiguator isn't needed. The
manifest field reads more naturally as just `egress:` with the
same nested `routes: [...]` shape.
Renamed:
- Manifest YAML key: `egress_proxy:` → `egress:`
- Bottle dataclass attr: `bottle.egress_proxy` → `bottle.egress`
- `_BOTTLE_KEYS` entry, schema docstring, and all
user-facing error message labels (`egress.routes[N]`,
`egress has unknown key …`, etc.).
Kept (these refer to the egress-proxy SIDECAR, not the manifest
field):
- File names: `egress_proxy.py`, `egress_proxy_apply.py`,
`egress_proxy_addon.py`, `egress_proxy_addon_core.py`.
- Class names: `EgressProxyConfig`, `EgressProxyRoute`,
`EgressProxyPlan`, `EgressProxy`, `DockerEgressProxy`.
- Helper names: `egress_proxy_manifest_routes`,
`egress_proxy_routes_for_bottle`,
`egress_proxy_token_env_map`, etc.
- Constants: `EGRESS_PROXY_HOSTNAME`, `EGRESS_PROXY_ROLES`,
`EGRESS_PROXY_AUTH_SCHEMES`, `EGRESS_PROXY_FORWARD_PROXY`,
`EGRESS_PROXY_INTROSPECT_URL`, `EGRESS_PROXY_PORT`, etc.
- Container name prefix `claude-bottle-egress-proxy-*`, the
`egress-proxy` docker network alias, the
`egress-proxy-block` + `list-egress-proxy-routes` MCP tool
IDs, the `egress-proxy` audit-log component label.
Local bottle migrated (`~/.claude-bottle/bottles/dev.md` already
updated). The legacy `egress_proxy` key isn't surfaced anywhere
anymore; the generic unknown-key validator catches typos with a
"did you mean: egress, env, git, supervise" hint.
409 unit + integration tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -155,7 +155,7 @@ def launch(
|
||||
stack.callback(git_gate.stop, git_gate_name)
|
||||
|
||||
# Egress-proxy (PRD 0017). One sidecar per bottle when
|
||||
# bottle.egress_proxy.routes is non-empty. Must come up AFTER
|
||||
# bottle.egress.routes is non-empty. Must come up AFTER
|
||||
# pipelock — egress-proxy routes its outbound HTTPS through
|
||||
# pipelock (HTTPS_PROXY in environ + the pipelock CA in its
|
||||
# trust store) so the egress allowlist + body scanner sit on
|
||||
|
||||
@@ -122,13 +122,13 @@ def resolve_plan(
|
||||
# actionable hint. Fail fast here with a cleanup pointer instead.
|
||||
# Only probe sidecars this launch will actually try to create:
|
||||
# pipelock always; git-gate when bottle.git is non-empty;
|
||||
# egress-proxy when bottle.egress_proxy.routes is non-empty.
|
||||
# egress-proxy when bottle.egress.routes is non-empty.
|
||||
sidecar_probes: list[tuple[str, str]] = [
|
||||
("pipelock", pipelock_container_name(slug)),
|
||||
]
|
||||
if bottle.git:
|
||||
sidecar_probes.append(("git-gate", git_gate_container_name(slug)))
|
||||
if bottle.egress_proxy.routes:
|
||||
if bottle.egress.routes:
|
||||
sidecar_probes.append(("egress-proxy", egress_proxy_container_name(slug)))
|
||||
if bottle.supervise:
|
||||
sidecar_probes.append(("supervise", supervise_container_name(slug)))
|
||||
|
||||
@@ -147,7 +147,7 @@ DEFAULT_ALLOWLIST: tuple[str, ...] = (
|
||||
def egress_proxy_manifest_routes(
|
||||
bottle: Bottle,
|
||||
) -> tuple[EgressProxyRoute, ...]:
|
||||
"""Lift each `bottle.egress_proxy.routes[]` manifest entry into a
|
||||
"""Lift each `bottle.egress.routes[]` manifest entry into a
|
||||
resolved EgressProxyRoute. Order is preserved so route lookup at
|
||||
the proxy is stable.
|
||||
|
||||
@@ -163,7 +163,7 @@ def egress_proxy_manifest_routes(
|
||||
addon enforces."""
|
||||
out: list[EgressProxyRoute] = []
|
||||
slot_for_token: dict[str, str] = {}
|
||||
for r in bottle.egress_proxy.routes:
|
||||
for r in bottle.egress.routes:
|
||||
if r.AuthScheme and r.TokenRef:
|
||||
token_env = slot_for_token.get(r.TokenRef)
|
||||
if token_env is None:
|
||||
@@ -199,7 +199,7 @@ def egress_proxy_routes_for_bottle(
|
||||
|
||||
Operators that want to allow an arbitrary host that isn't in
|
||||
DEFAULT_ALLOWLIST declare it directly in
|
||||
`bottle.egress_proxy.routes` as a bare-pass entry
|
||||
`bottle.egress.routes` as a bare-pass entry
|
||||
(`- host: <name>`). The legacy `bottle.egress.allowlist`
|
||||
folding is gone — egress_proxy is the single allowlist surface."""
|
||||
out: list[EgressProxyRoute] = list(egress_proxy_manifest_routes(bottle))
|
||||
@@ -279,7 +279,7 @@ def egress_proxy_resolve_token_values(
|
||||
die(
|
||||
f"egress-proxy: host env var '{token_ref}' is unset. Set it "
|
||||
f"before launching, or remove the corresponding auth block "
|
||||
f"from bottle.egress_proxy.routes."
|
||||
f"from bottle.egress.routes."
|
||||
)
|
||||
if not value:
|
||||
die(
|
||||
@@ -298,7 +298,7 @@ class EgressProxy(ABC):
|
||||
concrete subclasses."""
|
||||
|
||||
def prepare(self, bottle: Bottle, slug: str, stage_dir: Path) -> EgressProxyPlan:
|
||||
"""Lift `bottle.egress_proxy.routes` into resolved routes,
|
||||
"""Lift `bottle.egress.routes` into resolved routes,
|
||||
render the routes file (mode 600) under `stage_dir`, and
|
||||
return the plan. Pure host-side, no docker subprocess. The
|
||||
token-env map records the mapping the launch step uses to
|
||||
|
||||
+17
-17
@@ -14,7 +14,7 @@ the system prompt, for bottles the body is human documentation
|
||||
Bottle schema (frontmatter):
|
||||
env: { <NAME>: <env-entry>, ... }
|
||||
git: [ <git-entry>, ... ]
|
||||
egress_proxy: { routes: [ <egress-route>, ... ] }
|
||||
egress: { routes: [ <egress-route>, ... ] }
|
||||
|
||||
Agent schema (frontmatter):
|
||||
bottle: <bottle-name> # required
|
||||
@@ -196,7 +196,7 @@ class EgressProxyRoute:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, bottle_name: str, idx: int, raw: object) -> "EgressProxyRoute":
|
||||
label = f"bottle '{bottle_name}' egress_proxy.routes[{idx}]"
|
||||
label = f"bottle '{bottle_name}' egress.routes[{idx}]"
|
||||
d = _as_json_object(raw, label)
|
||||
host = d.get("host")
|
||||
if not isinstance(host, str) or not host:
|
||||
@@ -308,7 +308,7 @@ class EgressProxyRoute:
|
||||
@dataclass(frozen=True)
|
||||
class EgressProxyConfig:
|
||||
"""Per-bottle egress-proxy configuration. Today this is just the
|
||||
route table; the nesting under `egress_proxy:` leaves room for
|
||||
route table; the nesting under `egress:` leaves room for
|
||||
per-bottle proxy settings (port override, log level, etc.) in
|
||||
follow-ups."""
|
||||
|
||||
@@ -316,13 +316,13 @@ class EgressProxyConfig:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, bottle_name: str, raw: object) -> "EgressProxyConfig":
|
||||
d = _as_json_object(raw, f"bottle '{bottle_name}' egress_proxy")
|
||||
d = _as_json_object(raw, f"bottle '{bottle_name}' egress")
|
||||
routes_raw = d.get("routes")
|
||||
routes: tuple[EgressProxyRoute, ...] = ()
|
||||
if routes_raw is not None:
|
||||
if not isinstance(routes_raw, list):
|
||||
die(
|
||||
f"bottle '{bottle_name}' egress_proxy.routes must be an array "
|
||||
f"bottle '{bottle_name}' egress.routes must be an array "
|
||||
f"(was {type(routes_raw).__name__})"
|
||||
)
|
||||
routes_list = cast(list[object], routes_raw)
|
||||
@@ -334,7 +334,7 @@ class EgressProxyConfig:
|
||||
for k in d:
|
||||
if k != "routes":
|
||||
die(
|
||||
f"bottle '{bottle_name}' egress_proxy has unknown key {k!r}; "
|
||||
f"bottle '{bottle_name}' egress has unknown key {k!r}; "
|
||||
f"only 'routes' is accepted"
|
||||
)
|
||||
return cls(routes=routes)
|
||||
@@ -344,7 +344,7 @@ class EgressProxyConfig:
|
||||
class Bottle:
|
||||
env: Mapping[str, str] = field(default_factory=_empty_str_dict)
|
||||
git: tuple[GitEntry, ...] = ()
|
||||
egress_proxy: EgressProxyConfig = field(default_factory=EgressProxyConfig)
|
||||
egress: EgressProxyConfig = field(default_factory=EgressProxyConfig)
|
||||
# Opt-in per-bottle stuck-recovery sidecar (PRD 0013). When true,
|
||||
# the launch step brings up a supervise sidecar that exposes three
|
||||
# MCP tools to the agent (cred-proxy-block, pipelock-block,
|
||||
@@ -402,14 +402,14 @@ class Bottle:
|
||||
if "tokens" in d:
|
||||
die(
|
||||
f"bottle '{name}' has a 'tokens' field. The shape was reworked: "
|
||||
f"each route now lives under 'egress_proxy.routes' with explicit "
|
||||
f"each route now lives under 'egress.routes' with explicit "
|
||||
f"host / path_allowlist / auth. See docs/prds/0017-egress-proxy-via-mitmproxy.md."
|
||||
)
|
||||
|
||||
if "cred_proxy" in d:
|
||||
die(
|
||||
f"bottle '{name}' has a 'cred_proxy' field, which has been removed "
|
||||
f"(PRD 0017). Rename to 'egress_proxy' and migrate each route:\n"
|
||||
f"(PRD 0017). Rename to 'egress' and migrate each route:\n"
|
||||
f" - 'path' + 'upstream' (cred-proxy URL prefix + upstream URL)\n"
|
||||
f" → 'host' (just the upstream hostname)\n"
|
||||
f" - 'auth_scheme' + 'token_ref' (flat)\n"
|
||||
@@ -422,9 +422,9 @@ class Bottle:
|
||||
f"See docs/prds/0017-egress-proxy-via-mitmproxy.md."
|
||||
)
|
||||
|
||||
egress_proxy = (
|
||||
EgressProxyConfig.from_dict(name, d["egress_proxy"])
|
||||
if "egress_proxy" in d
|
||||
egress = (
|
||||
EgressProxyConfig.from_dict(name, d["egress"])
|
||||
if "egress" in d
|
||||
else EgressProxyConfig()
|
||||
)
|
||||
|
||||
@@ -436,7 +436,7 @@ class Bottle:
|
||||
)
|
||||
|
||||
return cls(
|
||||
env=env, git=git, egress_proxy=egress_proxy,
|
||||
env=env, git=git, egress=egress,
|
||||
supervise=supervise_raw,
|
||||
)
|
||||
|
||||
@@ -715,7 +715,7 @@ def _validate_egress_proxy_routes(
|
||||
bottle_name: str,
|
||||
routes: tuple[EgressProxyRoute, ...],
|
||||
) -> None:
|
||||
"""Cross-validation for `bottle.egress_proxy.routes`:
|
||||
"""Cross-validation for `bottle.egress.routes`:
|
||||
|
||||
- Hosts must be unique within the bottle. The proxy matches by
|
||||
exact-host (v1, prefix matching is on path_allowlist only);
|
||||
@@ -732,7 +732,7 @@ def _validate_egress_proxy_routes(
|
||||
key = r.Host.lower()
|
||||
if key in seen_hosts:
|
||||
die(
|
||||
f"bottle '{bottle_name}' egress_proxy.routes has duplicate host "
|
||||
f"bottle '{bottle_name}' egress.routes has duplicate host "
|
||||
f"{r.Host!r}; each host must be unique on the proxy."
|
||||
)
|
||||
seen_hosts[key] = None
|
||||
@@ -741,7 +741,7 @@ def _validate_egress_proxy_routes(
|
||||
if len(with_role) > 1:
|
||||
hosts = ", ".join(r.Host for r in with_role)
|
||||
die(
|
||||
f"bottle '{bottle_name}' egress_proxy.routes has {len(with_role)} "
|
||||
f"bottle '{bottle_name}' egress.routes has {len(with_role)} "
|
||||
f"routes with role {role!r} (hosts: {hosts}); this role drives a "
|
||||
f"single launch-step side effect — pick one."
|
||||
)
|
||||
@@ -772,7 +772,7 @@ _FILENAME_RX = re.compile(r"^[a-z][a-z0-9-]*$")
|
||||
# sets dies with a "did you mean" pointer — typos shouldn't silently
|
||||
# ghost into an empty config.
|
||||
_BOTTLE_KEYS = frozenset(
|
||||
{"env", "git", "egress_proxy", "supervise"}
|
||||
{"env", "git", "egress", "supervise"}
|
||||
)
|
||||
_AGENT_KEYS_REQUIRED = frozenset({"bottle"})
|
||||
_AGENT_KEYS_OPTIONAL = frozenset({"skills"})
|
||||
|
||||
@@ -101,7 +101,7 @@ def pipelock_seed_phrase_detection_enabled(bottle: Bottle) -> bool:
|
||||
seeds; the patterns that matter — gh*_, sk-ant-, AKIA, etc. —
|
||||
keep firing)."""
|
||||
return not any(
|
||||
r.Host == "api.anthropic.com" for r in bottle.egress_proxy.routes
|
||||
r.Host == "api.anthropic.com" for r in bottle.egress.routes
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from claude_bottle.manifest import Manifest
|
||||
|
||||
def _bottle(routes):
|
||||
return Manifest.from_json_obj({
|
||||
"bottles": {"dev": {"egress_proxy": {"routes": routes}}},
|
||||
"bottles": {"dev": {"egress": {"routes": routes}}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
}).bottles["dev"]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Unit: manifest parsing for `bottle.egress_proxy.routes[]` (PRD 0017).
|
||||
"""Unit: manifest parsing for `bottle.egress.routes[]` (PRD 0017).
|
||||
|
||||
The route shape is new: `host` (required), optional `path_allowlist`,
|
||||
optional nested `auth: { scheme, token_ref }`. Validation rules per
|
||||
@@ -13,7 +13,7 @@ from claude_bottle.manifest import EgressProxyRoute, Manifest
|
||||
|
||||
def _bottle(routes):
|
||||
return Manifest.from_json_obj({
|
||||
"bottles": {"dev": {"egress_proxy": {"routes": routes}}},
|
||||
"bottles": {"dev": {"egress": {"routes": routes}}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
}).bottles["dev"]
|
||||
|
||||
@@ -21,8 +21,8 @@ def _bottle(routes):
|
||||
class TestMinimalRoute(unittest.TestCase):
|
||||
def test_host_only(self):
|
||||
b = _bottle([{"host": "api.example.com"}])
|
||||
self.assertEqual(1, len(b.egress_proxy.routes))
|
||||
r = b.egress_proxy.routes[0]
|
||||
self.assertEqual(1, len(b.egress.routes))
|
||||
r = b.egress.routes[0]
|
||||
self.assertEqual("api.example.com", r.Host)
|
||||
self.assertEqual((), r.PathAllowlist)
|
||||
self.assertEqual("", r.AuthScheme)
|
||||
@@ -44,7 +44,7 @@ class TestMinimalRoute(unittest.TestCase):
|
||||
class TestPathAllowlist(unittest.TestCase):
|
||||
def test_optional(self):
|
||||
b = _bottle([{"host": "x.example"}])
|
||||
self.assertEqual((), b.egress_proxy.routes[0].PathAllowlist)
|
||||
self.assertEqual((), b.egress.routes[0].PathAllowlist)
|
||||
|
||||
def test_must_be_array(self):
|
||||
with self.assertRaises(Die):
|
||||
@@ -65,14 +65,14 @@ class TestPathAllowlist(unittest.TestCase):
|
||||
}])
|
||||
self.assertEqual(
|
||||
("/didericis/", "/users/didericis"),
|
||||
b.egress_proxy.routes[0].PathAllowlist,
|
||||
b.egress.routes[0].PathAllowlist,
|
||||
)
|
||||
|
||||
|
||||
class TestAuth(unittest.TestCase):
|
||||
def test_omitted_means_no_auth(self):
|
||||
b = _bottle([{"host": "github.com"}])
|
||||
r = b.egress_proxy.routes[0]
|
||||
r = b.egress.routes[0]
|
||||
self.assertEqual("", r.AuthScheme)
|
||||
self.assertEqual("", r.TokenRef)
|
||||
|
||||
@@ -81,7 +81,7 @@ class TestAuth(unittest.TestCase):
|
||||
"host": "api.github.com",
|
||||
"auth": {"scheme": "Bearer", "token_ref": "GH_PAT"},
|
||||
}])
|
||||
r = b.egress_proxy.routes[0]
|
||||
r = b.egress.routes[0]
|
||||
self.assertEqual("Bearer", r.AuthScheme)
|
||||
self.assertEqual("GH_PAT", r.TokenRef)
|
||||
|
||||
@@ -118,7 +118,7 @@ class TestAuth(unittest.TestCase):
|
||||
"host": "git.example",
|
||||
"auth": {"scheme": "token", "token_ref": "GITEA_PAT"},
|
||||
}])
|
||||
self.assertEqual("token", b.egress_proxy.routes[0].AuthScheme)
|
||||
self.assertEqual("token", b.egress.routes[0].AuthScheme)
|
||||
|
||||
def test_unknown_auth_key_rejected(self):
|
||||
with self.assertRaises(Die):
|
||||
@@ -131,7 +131,7 @@ class TestAuth(unittest.TestCase):
|
||||
class TestRole(unittest.TestCase):
|
||||
def test_omitted_means_no_roles(self):
|
||||
b = _bottle([{"host": "x.example"}])
|
||||
self.assertEqual((), b.egress_proxy.routes[0].Role)
|
||||
self.assertEqual((), b.egress.routes[0].Role)
|
||||
|
||||
def test_string_normalizes_to_tuple(self):
|
||||
b = _bottle([{
|
||||
@@ -140,7 +140,7 @@ class TestRole(unittest.TestCase):
|
||||
"auth": {"scheme": "Bearer", "token_ref": "T"},
|
||||
}])
|
||||
self.assertEqual(("claude_code_oauth",),
|
||||
b.egress_proxy.routes[0].Role)
|
||||
b.egress.routes[0].Role)
|
||||
|
||||
def test_list_supported(self):
|
||||
b = _bottle([{
|
||||
@@ -149,7 +149,7 @@ class TestRole(unittest.TestCase):
|
||||
"auth": {"scheme": "Bearer", "token_ref": "T"},
|
||||
}])
|
||||
self.assertEqual(("claude_code_oauth",),
|
||||
b.egress_proxy.routes[0].Role)
|
||||
b.egress.routes[0].Role)
|
||||
|
||||
def test_unknown_role_rejected(self):
|
||||
# The role enum is locked down — typos shouldn't silently
|
||||
@@ -199,7 +199,7 @@ class TestRouteValidation(unittest.TestCase):
|
||||
|
||||
def test_empty_routes_allowed(self):
|
||||
b = _bottle([])
|
||||
self.assertEqual((), b.egress_proxy.routes)
|
||||
self.assertEqual((), b.egress.routes)
|
||||
|
||||
def test_no_egress_proxy_block_means_empty(self):
|
||||
# The bottle dataclass defaults to an empty EgressProxyConfig.
|
||||
@@ -207,14 +207,14 @@ class TestRouteValidation(unittest.TestCase):
|
||||
"bottles": {"dev": {}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
}).bottles["dev"]
|
||||
self.assertEqual((), b.egress_proxy.routes)
|
||||
self.assertEqual((), b.egress.routes)
|
||||
|
||||
|
||||
class TestConfigShape(unittest.TestCase):
|
||||
def test_unknown_egress_proxy_key_rejected(self):
|
||||
with self.assertRaises(Die):
|
||||
Manifest.from_json_obj({
|
||||
"bottles": {"dev": {"egress_proxy": {"wat": []}}},
|
||||
"bottles": {"dev": {"egress": {"wat": []}}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "",
|
||||
"bottle": "dev"}},
|
||||
})
|
||||
|
||||
@@ -22,7 +22,7 @@ def _write(p: Path, text: str) -> None:
|
||||
|
||||
_BOTTLE_DEV = """
|
||||
---
|
||||
egress_proxy:
|
||||
egress:
|
||||
routes:
|
||||
- host: api.anthropic.com
|
||||
auth:
|
||||
@@ -85,7 +85,7 @@ class TestBottleFileParses(_ResolveCase):
|
||||
_write(self.home_cb / "agents" / "implementer.md", _AGENT_IMPL)
|
||||
m = self.resolve()
|
||||
self.assertIn("dev", m.bottles)
|
||||
routes = m.bottles["dev"].egress_proxy.routes
|
||||
routes = m.bottles["dev"].egress.routes
|
||||
self.assertEqual(2, len(routes))
|
||||
self.assertEqual("api.anthropic.com", routes[0].Host)
|
||||
self.assertEqual("Bearer", routes[0].AuthScheme)
|
||||
@@ -132,7 +132,7 @@ class TestCwdAgentOverridesHome(_ResolveCase):
|
||||
m = self.resolve()
|
||||
self.assertIn("CWD-OVERRIDE-PROMPT", m.agents["implementer"].prompt)
|
||||
# Home bottle still present
|
||||
self.assertEqual(2, len(m.bottles["dev"].egress_proxy.routes))
|
||||
self.assertEqual(2, len(m.bottles["dev"].egress.routes))
|
||||
|
||||
|
||||
class TestCwdBottlesIgnored(_ResolveCase):
|
||||
@@ -147,7 +147,7 @@ class TestCwdBottlesIgnored(_ResolveCase):
|
||||
self.cwd_cb / "bottles" / "dev.md",
|
||||
"""
|
||||
---
|
||||
egress_proxy:
|
||||
egress:
|
||||
routes:
|
||||
- host: attacker.example.com
|
||||
auth:
|
||||
@@ -160,7 +160,7 @@ class TestCwdBottlesIgnored(_ResolveCase):
|
||||
# Home value wins because cwd bottles are ignored entirely.
|
||||
self.assertEqual(
|
||||
"api.anthropic.com",
|
||||
m.bottles["dev"].egress_proxy.routes[0].Host,
|
||||
m.bottles["dev"].egress.routes[0].Host,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ def _bottle(spec):
|
||||
|
||||
|
||||
def _routes(routes):
|
||||
return {"egress_proxy": {"routes": routes}}
|
||||
return {"egress": {"routes": routes}}
|
||||
|
||||
|
||||
class TestEffectiveAllowlist(unittest.TestCase):
|
||||
|
||||
@@ -109,7 +109,7 @@ class TestBuildConfig(unittest.TestCase):
|
||||
# up to route claude through pipelock.
|
||||
from claude_bottle.manifest import Manifest
|
||||
bottle = Manifest.from_json_obj({
|
||||
"bottles": {"dev": {"egress_proxy": {"routes": [
|
||||
"bottles": {"dev": {"egress": {"routes": [
|
||||
{"host": "api.anthropic.com",
|
||||
"auth": {"scheme": "Bearer", "token_ref": "T"}},
|
||||
]}}},
|
||||
@@ -158,7 +158,7 @@ class TestRenderAndWrite(unittest.TestCase):
|
||||
"MY_SECRET": "literal-value-should-not-appear",
|
||||
"ANOTHER": "?prompt-message",
|
||||
},
|
||||
"egress_proxy": {"routes": [{"host": "github.com"}]},
|
||||
"egress": {"routes": [{"host": "github.com"}]},
|
||||
}
|
||||
},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
@@ -204,7 +204,7 @@ class TestRenderAndWrite(unittest.TestCase):
|
||||
def test_render_emits_seed_phrase_off_for_anthropic_route(self):
|
||||
from claude_bottle.manifest import Manifest
|
||||
bottle = Manifest.from_json_obj({
|
||||
"bottles": {"dev": {"egress_proxy": {"routes": [
|
||||
"bottles": {"dev": {"egress": {"routes": [
|
||||
{"host": "api.anthropic.com",
|
||||
"auth": {"scheme": "Bearer", "token_ref": "T"}},
|
||||
]}}},
|
||||
|
||||
@@ -252,7 +252,7 @@ class TestRealisticBottleFile(unittest.TestCase):
|
||||
|
||||
def test_dev_bottle(self):
|
||||
out = _y("""
|
||||
egress_proxy:
|
||||
egress:
|
||||
routes:
|
||||
- host: api.anthropic.com
|
||||
auth:
|
||||
@@ -270,25 +270,21 @@ class TestRealisticBottleFile(unittest.TestCase):
|
||||
IdentityFile: ~/.ssh/gitea.pem
|
||||
ExtraHosts:
|
||||
gitea.dideric.is: 100.78.141.42
|
||||
egress:
|
||||
allowlist:
|
||||
- example.com
|
||||
""")
|
||||
# Spot-check the deep parts; the structure is large.
|
||||
self.assertEqual(2, len(out["egress_proxy"]["routes"]))
|
||||
self.assertEqual(2, len(out["egress"]["routes"]))
|
||||
self.assertEqual(
|
||||
["/didericis/"],
|
||||
out["egress_proxy"]["routes"][1]["path_allowlist"],
|
||||
out["egress"]["routes"][1]["path_allowlist"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"Bearer",
|
||||
out["egress_proxy"]["routes"][0]["auth"]["scheme"],
|
||||
out["egress"]["routes"][0]["auth"]["scheme"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"100.78.141.42",
|
||||
out["git"][0]["ExtraHosts"]["gitea.dideric.is"],
|
||||
)
|
||||
self.assertEqual(["example.com"], out["egress"]["allowlist"])
|
||||
|
||||
|
||||
class TestFrontmatter(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user