refactor(cred_proxy): flat routes, role-driven provisioning (PRD 0010)
test / unit (pull_request) Successful in 14s
test / integration (pull_request) Successful in 22s

Replace bottle.tokens (with Kind enum and hardcoded per-kind
route/auth tables) with bottle.cred_proxy.routes — each route
declares its own path, upstream, auth_scheme, token_ref, and
optional role[]. The manifest is now the source of truth for the
proxy's runtime route table; adding an upstream is a manifest edit,
not a code change.

Agent-side rewrites move from per-kind dispatch to per-role tags
on routes:
  anthropic-base-url -> set ANTHROPIC_BASE_URL=<proxy><path>
  npm-registry       -> write ~/.npmrc registry=
  git-insteadof      -> write ~/.gitconfig [url] insteadOf, keyed
                        off route.upstream (suppressed when
                        bottle.git brokers the same host)
  tea-login          -> add a ~/.config/tea/config.yml login

Roles are a list (string accepted as sugar). A gitea route
typically carries ["git-insteadof", "tea-login"]. Singleton roles
(anthropic-base-url, npm-registry) appear on at most one route.

token_env slots are assigned per distinct TokenRef in declaration
order — two routes sharing a token_ref (e.g. github API + git
endpoints) share a slot.

Drops: TOKEN_KINDS, _KIND_ROUTES, _KIND_AUTH_SCHEME, _TOKEN_DEFAULT_HOST,
cred_proxy_route_path_for_gitea, the kind field on CredProxyUpstream,
and the kind-based hardcoding in pipelock_token_hosts (now derives
from route.UpstreamHost).

Legacy bottle.tokens manifests now die with a hint pointing at
bottle.cred_proxy.routes + this PRD. Tests rewritten end-to-end.
Docs + example.json + the dev ~/claude-bottle.json updated to match.
This commit is contained in:
2026-05-13 21:49:55 -04:00
parent 27b2d78b11
commit fcbbc4484d
15 changed files with 798 additions and 695 deletions
+3 -3
View File
@@ -57,7 +57,7 @@ class TestStartGuards(unittest.TestCase):
def test_missing_internal_network_dies(self):
upstream = CredProxyUpstream(
kind="anthropic", path="/anthropic/",
path="/anthropic/",
upstream="https://api.anthropic.com",
auth_scheme="Bearer", token_env="CRED_PROXY_TOKEN_0",
token_ref="T",
@@ -67,7 +67,7 @@ class TestStartGuards(unittest.TestCase):
def test_missing_routes_file_dies(self):
upstream = CredProxyUpstream(
kind="anthropic", path="/anthropic/",
path="/anthropic/",
upstream="https://api.anthropic.com",
auth_scheme="Bearer", token_env="CRED_PROXY_TOKEN_0",
token_ref="T",
@@ -84,7 +84,7 @@ class TestStartGuards(unittest.TestCase):
# URL set + CA path empty/missing is a wiring bug: either both
# populated (production) or both empty (test escape hatch).
upstream = CredProxyUpstream(
kind="anthropic", path="/anthropic/",
path="/anthropic/",
upstream="https://api.anthropic.com",
auth_scheme="Bearer", token_env="CRED_PROXY_TOKEN_0",
token_ref="T",