refactor(manifest): remove empty EGRESS_ROLES and related plumbing
test / unit (pull_request) Successful in 30s
test / integration (pull_request) Successful in 45s

EGRESS_ROLES, EGRESS_SINGLETON_ROLES, and PROVIDER_EGRESS_ROLES were
all empty frozensets after the codex_auth and claude_code_oauth roles
were removed. Delete the constants and all validation code that iterated
over them (the singleton-role loop and provider-role check in
_validate_egress_routes, the EGRESS_ROLES membership test in
EgressRoute.from_dict). EgressRoute.from_dict now rejects any role
string unconditionally; _validate_egress_routes loses its
agent_provider_template parameter entirely.

Assisted-by: Claude Code
This commit was merged in pull request #115.
This commit is contained in:
2026-06-02 01:57:55 +00:00
parent 8a038dcceb
commit 650f3aa93e
2 changed files with 19 additions and 69 deletions
+2 -3
View File
@@ -69,9 +69,8 @@ class EgressRoute:
under `token_env`. Routes that share a `token_ref` coalesce to under `token_env`. Routes that share a `token_ref` coalesce to
one `token_env` slot. one `token_env` slot.
`roles` carries the manifest route's optional role markers (see `roles` carries the manifest route's role tuple (reserved for
`manifest.EGRESS_ROLES`). The launch step reads these for future use; always empty today).
side effects like the claude-code OAuth placeholder env.
`tls_passthrough` signals that pipelock must not TLS-MITM this `tls_passthrough` signals that pipelock must not TLS-MITM this
host — either because the manifest declared `pipelock.tls_passthrough: host — either because the manifest declared `pipelock.tls_passthrough:
+17 -66
View File
@@ -175,17 +175,6 @@ class GitEntry:
# token-not-Bearer quirk (go-gitea/gitea#16734). # token-not-Bearer quirk (go-gitea/gitea#16734).
EGRESS_AUTH_SCHEMES = ("Bearer", "token") EGRESS_AUTH_SCHEMES = ("Bearer", "token")
# Per-route role markers. Both former roles (claude_code_oauth,
# codex_auth) have been removed — provider auth is now provisioner-owned
# via agent_provider.auth_token / forward_host_credentials. The field
# and validation plumbing remain for future roles.
EGRESS_ROLES: frozenset[str] = frozenset()
EGRESS_SINGLETON_ROLES: frozenset[str] = frozenset()
PROVIDER_EGRESS_ROLES: dict[str, frozenset[str]] = {
"claude": frozenset(),
"codex": frozenset(),
}
@dataclass(frozen=True) @dataclass(frozen=True)
class AgentProvider: class AgentProvider:
@@ -420,7 +409,8 @@ class EgressRoute:
manifest's `auth` block is omitted both fields are empty strings — manifest's `auth` block is omitted both fields are empty strings —
no Authorization is written, no token forwarded. no Authorization is written, no token forwarded.
`Role` is an optional tuple of named markers (see EGRESS_ROLES). `Role` is reserved for future use; all role strings are currently
rejected by the validator.
Validation rules (enforced in `from_dict`): Validation rules (enforced in `from_dict`):
- `host` required, non-empty. - `host` required, non-empty.
@@ -429,10 +419,7 @@ class EgressRoute:
`token_ref` as non-empty strings; an empty `auth: {}` is an `token_ref` as non-empty strings; an empty `auth: {}` is an
error rather than a synonym for "no auth" (omit `auth` for error rather than a synonym for "no auth" (omit `auth` for
that case). that case).
- `role` optional. String or list of strings drawn from - `role` optional, reserved — any non-empty value is rejected.
EGRESS_ROLES. Singleton roles (see
EGRESS_SINGLETON_ROLES) may appear on at most one
route per bottle.
""" """
Host: str Host: str
@@ -530,12 +517,11 @@ class EgressRoute:
f"{label} role must be a string or a list of strings " f"{label} role must be a string or a list of strings "
f"(was {type(role_raw).__name__})" f"(was {type(role_raw).__name__})"
) )
for r in roles: if roles:
if r not in EGRESS_ROLES: raise ManifestError(
raise ManifestError( f"{label} role {roles[0]!r} is not accepted; "
f"{label} role {r!r} is not one of " f"the 'role' field is reserved for future use"
f"{', '.join(sorted(EGRESS_ROLES))}" )
)
pipelock = ( pipelock = (
PipelockRoutePolicy.from_dict(bottle_name, idx, d["pipelock"]) PipelockRoutePolicy.from_dict(bottle_name, idx, d["pipelock"])
@@ -570,9 +556,7 @@ class EgressConfig:
routes: tuple[EgressRoute, ...] = () routes: tuple[EgressRoute, ...] = ()
@classmethod @classmethod
def from_dict( def from_dict(cls, bottle_name: str, raw: object) -> "EgressConfig":
cls, bottle_name: str, raw: object, *, agent_provider_template: str = "claude",
) -> "EgressConfig":
d = _as_json_object(raw, f"bottle '{bottle_name}' egress") d = _as_json_object(raw, f"bottle '{bottle_name}' egress")
routes_raw = d.get("routes") routes_raw = d.get("routes")
routes: tuple[EgressRoute, ...] = () routes: tuple[EgressRoute, ...] = ()
@@ -587,9 +571,7 @@ class EgressConfig:
EgressRoute.from_dict(bottle_name, i, entry) EgressRoute.from_dict(bottle_name, i, entry)
for i, entry in enumerate(routes_list) for i, entry in enumerate(routes_list)
) )
_validate_egress_routes( _validate_egress_routes(bottle_name, routes)
bottle_name, routes, agent_provider_template=agent_provider_template,
)
for k in d: for k in d:
if k != "routes": if k != "routes":
raise ManifestError( raise ManifestError(
@@ -680,10 +662,7 @@ class Bottle:
) )
egress = ( egress = (
EgressConfig.from_dict( EgressConfig.from_dict(name, d["egress"])
name, d["egress"],
agent_provider_template=agent_provider.template,
)
if "egress" in d if "egress" in d
else EgressConfig() else EgressConfig()
) )
@@ -1047,21 +1026,15 @@ def _is_ip_literal(value: str) -> bool:
def _validate_egress_routes( def _validate_egress_routes(
bottle_name: str, bottle_name: str,
routes: tuple[EgressRoute, ...], routes: tuple[EgressRoute, ...],
*,
agent_provider_template: str = "claude",
) -> None: ) -> None:
"""Cross-validation for `bottle.egress.routes`: """Cross-validation for `bottle.egress.routes`: hosts must be unique.
- Hosts must be unique within the bottle. The proxy matches by The proxy matches by exact-host (v1); duplicate hosts leave the
exact-host (v1, prefix matching is on path_allowlist only); route choice ambiguous so we reject them up front.
duplicate hosts leave the route choice ambiguous.
- Singleton roles (see EGRESS_SINGLETON_ROLES) may appear
on at most one route per bottle.
No cross-validation against `bottle.git` is performed. git-gate No cross-validation against `bottle.git` is performed. git-gate
(SSH push/fetch) and egress (HTTPS) broker different (SSH push/fetch) and egress (HTTPS) broker different protocols;
protocols; declaring both for the same host is a legitimate declaring both for the same host is a legitimate dev setup."""
dev setup."""
seen_hosts: dict[str, None] = {} seen_hosts: dict[str, None] = {}
for r in routes: for r in routes:
key = r.Host.lower() key = r.Host.lower()
@@ -1071,25 +1044,6 @@ def _validate_egress_routes(
f"{r.Host!r}; each host must be unique on the proxy." f"{r.Host!r}; each host must be unique on the proxy."
) )
seen_hosts[key] = None seen_hosts[key] = None
for role in EGRESS_SINGLETON_ROLES:
with_role = [r for r in routes if role in r.Role]
if len(with_role) > 1:
hosts = ", ".join(r.Host for r in with_role)
raise ManifestError(
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."
)
allowed_roles = PROVIDER_EGRESS_ROLES[agent_provider_template]
for route in routes:
for role in route.Role:
if role not in allowed_roles:
raise ManifestError(
f"bottle '{bottle_name}' egress route for host "
f"{route.Host!r} has role {role!r}, but provider "
f"{agent_provider_template!r} only accepts roles "
f"{', '.join(sorted(allowed_roles)) or '(none)'}"
)
def _validate_unique_git_names(bottle_name: str, git: tuple[GitEntry, ...]) -> None: def _validate_unique_git_names(bottle_name: str, git: tuple[GitEntry, ...]) -> None:
@@ -1300,10 +1254,7 @@ def _merge_bottles(
merged_supervise = ( merged_supervise = (
child.supervise if "supervise" in child_raw else parent.supervise child.supervise if "supervise" in child_raw else parent.supervise
) )
_validate_egress_routes( _validate_egress_routes(name, merged_egress.routes)
name, merged_egress.routes,
agent_provider_template=merged_agent_provider.template,
)
return Bottle( return Bottle(
env=merged_env, env=merged_env,