Rename TOOL_ALLOW to TOOL_EGRESS_ALLOW
The constant and its MCP tool name ("allow" → "egress-allow") were the
only supervise tools without an egress-scoped identifier, despite the
tool being egress-only (routes.yaml payload, COMPONENT_FOR_TOOL maps
it to "egress", always grouped with TOOL_EGRESS_BLOCK). The rename
brings it in line with TOOL_EGRESS_BLOCK and TOOL_EGRESS_TOKEN_ALLOW,
and adds TOOL_EGRESS_ALLOW and TOOL_EGRESS_BLOCK to __all__ (both were
previously absent).
This commit is contained in:
@@ -51,7 +51,7 @@ from ..supervise import (
|
|||||||
STATUS_MODIFIED,
|
STATUS_MODIFIED,
|
||||||
STATUS_REJECTED,
|
STATUS_REJECTED,
|
||||||
TOOL_CAPABILITY_BLOCK,
|
TOOL_CAPABILITY_BLOCK,
|
||||||
TOOL_ALLOW,
|
TOOL_EGRESS_ALLOW,
|
||||||
TOOL_EGRESS_BLOCK,
|
TOOL_EGRESS_BLOCK,
|
||||||
TOOL_GITLEAKS_ALLOW,
|
TOOL_GITLEAKS_ALLOW,
|
||||||
TOOL_EGRESS_TOKEN_ALLOW,
|
TOOL_EGRESS_TOKEN_ALLOW,
|
||||||
@@ -145,7 +145,7 @@ def _detail_lines(
|
|||||||
def _suffix_for_tool(tool: str) -> str:
|
def _suffix_for_tool(tool: str) -> str:
|
||||||
if tool == TOOL_CAPABILITY_BLOCK:
|
if tool == TOOL_CAPABILITY_BLOCK:
|
||||||
return ".dockerfile"
|
return ".dockerfile"
|
||||||
if tool in (TOOL_ALLOW, TOOL_EGRESS_BLOCK):
|
if tool in (TOOL_EGRESS_ALLOW, TOOL_EGRESS_BLOCK):
|
||||||
return ".yaml"
|
return ".yaml"
|
||||||
if tool in (TOOL_GITLEAKS_ALLOW, TOOL_EGRESS_TOKEN_ALLOW):
|
if tool in (TOOL_GITLEAKS_ALLOW, TOOL_EGRESS_TOKEN_ALLOW):
|
||||||
return ".txt"
|
return ".txt"
|
||||||
@@ -177,7 +177,7 @@ def approve(
|
|||||||
# diff_before, diff_after = apply_capability_change(
|
# diff_before, diff_after = apply_capability_change(
|
||||||
# qp.proposal.bottle_slug, file_to_apply,
|
# qp.proposal.bottle_slug, file_to_apply,
|
||||||
# )
|
# )
|
||||||
if qp.proposal.tool in (TOOL_ALLOW, TOOL_EGRESS_BLOCK):
|
if qp.proposal.tool in (TOOL_EGRESS_ALLOW, TOOL_EGRESS_BLOCK):
|
||||||
diff_before, diff_after = apply_routes_change(
|
diff_before, diff_after = apply_routes_change(
|
||||||
qp.proposal.bottle_slug,
|
qp.proposal.bottle_slug,
|
||||||
file_to_apply,
|
file_to_apply,
|
||||||
|
|||||||
@@ -50,14 +50,14 @@ SUPERVISE_PORT = 9100
|
|||||||
|
|
||||||
TOOL_CAPABILITY_BLOCK = "capability-block"
|
TOOL_CAPABILITY_BLOCK = "capability-block"
|
||||||
TOOL_EGRESS_BLOCK = "egress-block"
|
TOOL_EGRESS_BLOCK = "egress-block"
|
||||||
TOOL_ALLOW = "allow"
|
TOOL_EGRESS_ALLOW = "egress-allow"
|
||||||
TOOL_GITLEAKS_ALLOW = "gitleaks-allow"
|
TOOL_GITLEAKS_ALLOW = "gitleaks-allow"
|
||||||
# Written directly by the egress addon (not an agent-facing MCP tool) when an
|
# Written directly by the egress addon (not an agent-facing MCP tool) when an
|
||||||
# outbound DLP token block is routed to the operator for override (PRD 0062).
|
# outbound DLP token block is routed to the operator for override (PRD 0062).
|
||||||
TOOL_EGRESS_TOKEN_ALLOW = "egress-token-allow"
|
TOOL_EGRESS_TOKEN_ALLOW = "egress-token-allow"
|
||||||
TOOL_LIST_EGRESS_ROUTES = "list-egress-routes"
|
TOOL_LIST_EGRESS_ROUTES = "list-egress-routes"
|
||||||
TOOLS: tuple[str, ...] = (
|
TOOLS: tuple[str, ...] = (
|
||||||
TOOL_ALLOW,
|
TOOL_EGRESS_ALLOW,
|
||||||
TOOL_CAPABILITY_BLOCK,
|
TOOL_CAPABILITY_BLOCK,
|
||||||
TOOL_EGRESS_BLOCK,
|
TOOL_EGRESS_BLOCK,
|
||||||
TOOL_GITLEAKS_ALLOW,
|
TOOL_GITLEAKS_ALLOW,
|
||||||
@@ -80,7 +80,7 @@ EGRESS_INTROSPECT_URL = "http://_egress.local/allowlist"
|
|||||||
# here — those changes are captured by git history + the rebuild record
|
# here — those changes are captured by git history + the rebuild record
|
||||||
# laid down in PRD 0016.
|
# laid down in PRD 0016.
|
||||||
COMPONENT_FOR_TOOL: dict[str, str] = {
|
COMPONENT_FOR_TOOL: dict[str, str] = {
|
||||||
TOOL_ALLOW: "egress",
|
TOOL_EGRESS_ALLOW: "egress",
|
||||||
TOOL_EGRESS_BLOCK: "egress",
|
TOOL_EGRESS_BLOCK: "egress",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +559,8 @@ __all__ = [
|
|||||||
"EGRESS_FORWARD_PROXY",
|
"EGRESS_FORWARD_PROXY",
|
||||||
"EGRESS_INTROSPECT_URL",
|
"EGRESS_INTROSPECT_URL",
|
||||||
"TOOL_CAPABILITY_BLOCK",
|
"TOOL_CAPABILITY_BLOCK",
|
||||||
|
"TOOL_EGRESS_ALLOW",
|
||||||
|
"TOOL_EGRESS_BLOCK",
|
||||||
"TOOL_GITLEAKS_ALLOW",
|
"TOOL_GITLEAKS_ALLOW",
|
||||||
"TOOL_EGRESS_TOKEN_ALLOW",
|
"TOOL_EGRESS_TOKEN_ALLOW",
|
||||||
"TOOL_LIST_EGRESS_ROUTES",
|
"TOOL_LIST_EGRESS_ROUTES",
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ TOOL_DEFINITIONS: list[dict[str, object]] = [
|
|||||||
"allowlist. Returns JSON with one entry per allowed host, "
|
"allowlist. Returns JSON with one entry per allowed host, "
|
||||||
"each carrying its matches rules (if any) and whether "
|
"each carrying its matches rules (if any) and whether "
|
||||||
"the proxy injects Authorization for the route. Use this "
|
"the proxy injects Authorization for the route. Use this "
|
||||||
"before composing an `allow` or `egress-block` proposal so "
|
"before composing an `egress-allow` or `egress-block` proposal so "
|
||||||
"the new routes file extends the live one rather than "
|
"the new routes file extends the live one rather than "
|
||||||
"replacing it."
|
"replacing it."
|
||||||
),
|
),
|
||||||
@@ -159,7 +159,7 @@ TOOL_DEFINITIONS: list[dict[str, object]] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": _sv.TOOL_ALLOW,
|
"name": _sv.TOOL_EGRESS_ALLOW,
|
||||||
"description": (
|
"description": (
|
||||||
"Request operator approval to change the bottle's egress "
|
"Request operator approval to change the bottle's egress "
|
||||||
"allowlist. Pass the full proposed routes.yaml content, not "
|
"allowlist. Pass the full proposed routes.yaml content, not "
|
||||||
@@ -276,7 +276,7 @@ TOOL_DEFINITIONS: list[dict[str, object]] = [
|
|||||||
# Map each proposal tool to the input field that carries the agent's
|
# Map each proposal tool to the input field that carries the agent's
|
||||||
# payload (stored in Proposal.proposed_file).
|
# payload (stored in Proposal.proposed_file).
|
||||||
PROPOSED_FILE_FIELD: dict[str, str] = {
|
PROPOSED_FILE_FIELD: dict[str, str] = {
|
||||||
_sv.TOOL_ALLOW: "routes_yaml",
|
_sv.TOOL_EGRESS_ALLOW: "routes_yaml",
|
||||||
_sv.TOOL_CAPABILITY_BLOCK: "dockerfile",
|
_sv.TOOL_CAPABILITY_BLOCK: "dockerfile",
|
||||||
_sv.TOOL_EGRESS_BLOCK: "routes_yaml",
|
_sv.TOOL_EGRESS_BLOCK: "routes_yaml",
|
||||||
}
|
}
|
||||||
@@ -295,7 +295,7 @@ def validate_proposed_file(tool: str, content: str) -> None:
|
|||||||
# Dockerfiles are too varied to validate syntactically beyond
|
# Dockerfiles are too varied to validate syntactically beyond
|
||||||
# non-empty. The operator reads the diff in the TUI.
|
# non-empty. The operator reads the diff in the TUI.
|
||||||
pass
|
pass
|
||||||
elif tool in (_sv.TOOL_ALLOW, _sv.TOOL_EGRESS_BLOCK):
|
elif tool in (_sv.TOOL_EGRESS_ALLOW, _sv.TOOL_EGRESS_BLOCK):
|
||||||
try:
|
try:
|
||||||
load_routes(content)
|
load_routes(content)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ class TestToolConstants(unittest.TestCase):
|
|||||||
def test_tools_tuple_matches_individual_constants(self):
|
def test_tools_tuple_matches_individual_constants(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
(
|
(
|
||||||
supervise.TOOL_ALLOW,
|
supervise.TOOL_EGRESS_ALLOW,
|
||||||
TOOL_CAPABILITY_BLOCK,
|
TOOL_CAPABILITY_BLOCK,
|
||||||
supervise.TOOL_EGRESS_BLOCK,
|
supervise.TOOL_EGRESS_BLOCK,
|
||||||
TOOL_GITLEAKS_ALLOW,
|
TOOL_GITLEAKS_ALLOW,
|
||||||
@@ -341,7 +341,7 @@ class TestToolConstants(unittest.TestCase):
|
|||||||
def test_component_map_has_egress_entries(self):
|
def test_component_map_has_egress_entries(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
{
|
{
|
||||||
supervise.TOOL_ALLOW: "egress",
|
supervise.TOOL_EGRESS_ALLOW: "egress",
|
||||||
supervise.TOOL_EGRESS_BLOCK: "egress",
|
supervise.TOOL_EGRESS_BLOCK: "egress",
|
||||||
},
|
},
|
||||||
supervise.COMPONENT_FOR_TOOL,
|
supervise.COMPONENT_FOR_TOOL,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ FIXED = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
|
|||||||
def _proposal(slug: str = "dev", tool: str = TOOL_CAPABILITY_BLOCK) -> Proposal:
|
def _proposal(slug: str = "dev", tool: str = TOOL_CAPABILITY_BLOCK) -> Proposal:
|
||||||
payloads = {
|
payloads = {
|
||||||
TOOL_CAPABILITY_BLOCK: "FROM python:3.13\n",
|
TOOL_CAPABILITY_BLOCK: "FROM python:3.13\n",
|
||||||
supervise.TOOL_ALLOW: "routes:\n - host: example.com\n",
|
supervise.TOOL_EGRESS_ALLOW: "routes:\n - host: example.com\n",
|
||||||
supervise.TOOL_EGRESS_BLOCK: "routes:\n - host: example.com\n",
|
supervise.TOOL_EGRESS_BLOCK: "routes:\n - host: example.com\n",
|
||||||
TOOL_GITLEAKS_ALLOW: "file: tests/test_fixture.py\nline: 3\n",
|
TOOL_GITLEAKS_ALLOW: "file: tests/test_fixture.py\nline: 3\n",
|
||||||
TOOL_EGRESS_TOKEN_ALLOW: "host: api.example.com\ndetector: token\n",
|
TOOL_EGRESS_TOKEN_ALLOW: "host: api.example.com\ndetector: token\n",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class TestValidation(unittest.TestCase):
|
|||||||
|
|
||||||
def test_egress_routes_yaml_is_validated(self):
|
def test_egress_routes_yaml_is_validated(self):
|
||||||
validate_proposed_file(
|
validate_proposed_file(
|
||||||
_sv.TOOL_ALLOW,
|
_sv.TOOL_EGRESS_ALLOW,
|
||||||
"routes:\n - host: example.com\n",
|
"routes:\n - host: example.com\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ class TestHandleToolsList(unittest.TestCase):
|
|||||||
names = [t["name"] for t in result["tools"]] # type: ignore[index]
|
names = [t["name"] for t in result["tools"]] # type: ignore[index]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted([
|
sorted([
|
||||||
_sv.TOOL_ALLOW,
|
_sv.TOOL_EGRESS_ALLOW,
|
||||||
_sv.TOOL_CAPABILITY_BLOCK,
|
_sv.TOOL_CAPABILITY_BLOCK,
|
||||||
_sv.TOOL_EGRESS_BLOCK,
|
_sv.TOOL_EGRESS_BLOCK,
|
||||||
_sv.TOOL_LIST_EGRESS_ROUTES,
|
_sv.TOOL_LIST_EGRESS_ROUTES,
|
||||||
@@ -181,7 +181,7 @@ class TestHandleToolsList(unittest.TestCase):
|
|||||||
self.assertNotIn("required", schema) # type: ignore[operator]
|
self.assertNotIn("required", schema) # type: ignore[operator]
|
||||||
|
|
||||||
def test_egress_tools_take_routes_yaml_and_justification(self):
|
def test_egress_tools_take_routes_yaml_and_justification(self):
|
||||||
for tool_name in (_sv.TOOL_ALLOW, _sv.TOOL_EGRESS_BLOCK):
|
for tool_name in (_sv.TOOL_EGRESS_ALLOW, _sv.TOOL_EGRESS_BLOCK):
|
||||||
with self.subTest(tool_name=tool_name):
|
with self.subTest(tool_name=tool_name):
|
||||||
tool = next(t for t in TOOL_DEFINITIONS if t["name"] == tool_name)
|
tool = next(t for t in TOOL_DEFINITIONS if t["name"] == tool_name)
|
||||||
schema = tool["inputSchema"]
|
schema = tool["inputSchema"]
|
||||||
@@ -244,7 +244,7 @@ class TestHandleToolsCall(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
result = handle_tools_call(
|
result = handle_tools_call(
|
||||||
{
|
{
|
||||||
"name": _sv.TOOL_ALLOW,
|
"name": _sv.TOOL_EGRESS_ALLOW,
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"routes_yaml": "routes:\n - host: example.com\n",
|
"routes_yaml": "routes:\n - host: example.com\n",
|
||||||
"justification": "need example.com",
|
"justification": "need example.com",
|
||||||
@@ -451,7 +451,7 @@ class TestHttpEndToEnd(unittest.TestCase):
|
|||||||
self.assertEqual(1, result["id"])
|
self.assertEqual(1, result["id"])
|
||||||
names = [t["name"] for t in result["result"]["tools"]] # type: ignore[index]
|
names = [t["name"] for t in result["result"]["tools"]] # type: ignore[index]
|
||||||
self.assertIn(_sv.TOOL_CAPABILITY_BLOCK, names)
|
self.assertIn(_sv.TOOL_CAPABILITY_BLOCK, names)
|
||||||
self.assertIn(_sv.TOOL_ALLOW, names)
|
self.assertIn(_sv.TOOL_EGRESS_ALLOW, names)
|
||||||
self.assertIn(_sv.TOOL_EGRESS_BLOCK, names)
|
self.assertIn(_sv.TOOL_EGRESS_BLOCK, names)
|
||||||
|
|
||||||
def test_unknown_method_returns_jsonrpc_error(self):
|
def test_unknown_method_returns_jsonrpc_error(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user