feat(egress-proxy): retarget remediation at egress-proxy (PRD 0017 chunk 3)
Finishes PRD 0017. The `cred-proxy-block` MCP tool is renamed and
its remediation apply path is repointed at egress-proxy.
- `claude_bottle/supervise.py` — `TOOL_CRED_PROXY_BLOCK` →
`TOOL_EGRESS_PROXY_BLOCK`; `COMPONENT_FOR_TOOL` maps the new
tool ID to `egress-proxy` for audit-log routing.
- `claude_bottle/supervise_server.py` — tool definition renamed
+ description rewritten: "Call when egress-proxy refused your
HTTPS request ... Read the current routes.yaml from /etc/
claude-bottle/current-config/routes.yaml, compose a modified
version, pass the full new file plus a justification." The
syntactic validator dispatches on the new tool ID.
- `claude_bottle/backend/docker/egress_proxy_apply.py` — renamed
from `cred_proxy_apply.py`. Reads routes.yaml from
/etc/egress-proxy/routes.yaml via `docker exec cat`; validates
via `egress_proxy_addon_core.load_routes` (so both sides use
the same parser); writes via `docker cp`; SIGHUPs egress-proxy
with `docker kill --signal HUP`. `EgressProxyApplyError`
replaces `CredProxyApplyError`.
- `claude_bottle/cli/dashboard.py` — wires the new apply +
`discover_egress_proxy_slugs` helper; the operator-initiated
`routes edit <bottle>` verb now writes to egress-proxy with
`.yaml` suffix. Stale follow-up comment about path-aware
filtering removed — PRD 0017 settled that question.
- `tests/integration/test_supervise_sidecar.py` — restores the
approval round-trip test (chunk 2 had switched it to a reject
path because no cred-proxy existed). Approval stubs
`apply_routes_change` so the test focuses on the supervise
queue/response plumbing rather than docker-exec into a real
egress-proxy sidecar (that's covered separately).
- `tests/unit/test_egress_proxy_apply.py` — rewritten against
the new validator; covers JSON shape, missing routes key,
partial-auth-pair rejection (the addon-core parser catches
these before SIGHUP).
- PRDs 0010 + 0014 — status headers updated to
Superseded / Retargeted with a callout block pointing at PRD
0017's migration section. Historical text preserved.
384 unit + integration tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -45,19 +45,19 @@ from claude_bottle.supervise_server import (
|
||||
|
||||
|
||||
class TestValidation(unittest.TestCase):
|
||||
def test_cred_proxy_block_requires_valid_json(self):
|
||||
def test_egress_proxy_block_requires_valid_json(self):
|
||||
with self.assertRaises(_RpcError) as cm:
|
||||
validate_proposed_file(_sv.TOOL_CRED_PROXY_BLOCK, "{not json")
|
||||
validate_proposed_file(_sv.TOOL_EGRESS_PROXY_BLOCK, "{not json")
|
||||
self.assertEqual(ERR_INVALID_PARAMS, cm.exception.code)
|
||||
self.assertIn("not valid JSON", cm.exception.message)
|
||||
|
||||
def test_cred_proxy_block_requires_routes_array(self):
|
||||
def test_egress_proxy_block_requires_routes_array(self):
|
||||
with self.assertRaises(_RpcError):
|
||||
validate_proposed_file(_sv.TOOL_CRED_PROXY_BLOCK, '{"other": []}')
|
||||
validate_proposed_file(_sv.TOOL_EGRESS_PROXY_BLOCK, '{"other": []}')
|
||||
|
||||
def test_cred_proxy_block_accepts_valid_routes(self):
|
||||
def test_egress_proxy_block_accepts_valid_routes(self):
|
||||
validate_proposed_file(
|
||||
_sv.TOOL_CRED_PROXY_BLOCK,
|
||||
_sv.TOOL_EGRESS_PROXY_BLOCK,
|
||||
'{"routes": [{"path": "/x/", "upstream": "https://example.com"}]}',
|
||||
)
|
||||
|
||||
@@ -175,7 +175,7 @@ class TestHandleToolsList(unittest.TestCase):
|
||||
names = [t["name"] for t in result["tools"]] # type: ignore[index]
|
||||
self.assertEqual(
|
||||
sorted([
|
||||
_sv.TOOL_CRED_PROXY_BLOCK,
|
||||
_sv.TOOL_EGRESS_PROXY_BLOCK,
|
||||
_sv.TOOL_PIPELOCK_BLOCK,
|
||||
_sv.TOOL_CAPABILITY_BLOCK,
|
||||
]),
|
||||
@@ -225,7 +225,7 @@ class TestHandleToolsCall(unittest.TestCase):
|
||||
try:
|
||||
result = handle_tools_call(
|
||||
{
|
||||
"name": _sv.TOOL_CRED_PROXY_BLOCK,
|
||||
"name": _sv.TOOL_EGRESS_PROXY_BLOCK,
|
||||
"arguments": {
|
||||
"routes": '{"routes": []}',
|
||||
"justification": "need a route",
|
||||
@@ -269,7 +269,7 @@ class TestHandleToolsCall(unittest.TestCase):
|
||||
with self.assertRaises(_RpcError):
|
||||
handle_tools_call(
|
||||
{
|
||||
"name": _sv.TOOL_CRED_PROXY_BLOCK,
|
||||
"name": _sv.TOOL_EGRESS_PROXY_BLOCK,
|
||||
"arguments": {"routes": '{"routes": []}'},
|
||||
},
|
||||
self.config,
|
||||
@@ -280,7 +280,7 @@ class TestHandleToolsCall(unittest.TestCase):
|
||||
try:
|
||||
handle_tools_call(
|
||||
{
|
||||
"name": _sv.TOOL_CRED_PROXY_BLOCK,
|
||||
"name": _sv.TOOL_EGRESS_PROXY_BLOCK,
|
||||
"arguments": {
|
||||
"routes": '{"routes": []}',
|
||||
"justification": "x",
|
||||
@@ -367,7 +367,7 @@ class TestHttpEndToEnd(unittest.TestCase):
|
||||
self.assertEqual("2.0", result["jsonrpc"])
|
||||
self.assertEqual(1, result["id"])
|
||||
names = [t["name"] for t in result["result"]["tools"]] # type: ignore[index]
|
||||
self.assertIn(_sv.TOOL_CRED_PROXY_BLOCK, names)
|
||||
self.assertIn(_sv.TOOL_EGRESS_PROXY_BLOCK, names)
|
||||
|
||||
def test_unknown_method_returns_jsonrpc_error(self):
|
||||
result = self._post_jsonrpc(
|
||||
|
||||
Reference in New Issue
Block a user