refactor(manifest): remove empty EGRESS_ROLES and related plumbing
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:
@@ -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
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user