feat(egress-proxy): retarget remediation flow (PRD 0017 chunk 3) #30
@@ -278,7 +278,7 @@ def _run_agent_container(plan: DockerBottlePlan, internal_network: str) -> str:
|
|||||||
docker_args.extend(["-e", name])
|
docker_args.extend(["-e", name])
|
||||||
|
|
||||||
# PRD 0013: read-only current-config mount so the agent can read
|
# PRD 0013: read-only current-config mount so the agent can read
|
||||||
# routes.json / allowlist / Dockerfile before composing a
|
# routes.yaml / allowlist / Dockerfile before composing a
|
||||||
# supervise tool-call proposal. Mounted from the per-bottle
|
# supervise tool-call proposal. Mounted from the per-bottle
|
||||||
# stage_dir/current-config/ populated at prepare time.
|
# stage_dir/current-config/ populated at prepare time.
|
||||||
if plan.supervise_plan is not None:
|
if plan.supervise_plan is not None:
|
||||||
|
|||||||
@@ -425,7 +425,13 @@ def sha256_hex(content: str) -> str:
|
|||||||
# Filenames inside the per-bottle current-config dir. The agent reads
|
# Filenames inside the per-bottle current-config dir. The agent reads
|
||||||
# these (read-only) from CURRENT_CONFIG_DIR_IN_AGENT and proposes
|
# these (read-only) from CURRENT_CONFIG_DIR_IN_AGENT and proposes
|
||||||
# modified versions back via the three MCP tools.
|
# modified versions back via the three MCP tools.
|
||||||
CURRENT_CONFIG_ROUTES = "routes.json"
|
# Filename of the staged egress-proxy routes file inside the agent's
|
||||||
|
# read-only current-config mount. JSON content under a `.yaml`
|
||||||
|
# extension to match the live file the egress-proxy sidecar reads
|
||||||
|
# (`/etc/egress-proxy/routes.yaml`) — the egress-proxy-block tool
|
||||||
|
# description points at this exact path, and the apply step writes
|
||||||
|
# the new content to the matching live path.
|
||||||
|
CURRENT_CONFIG_ROUTES = "routes.yaml"
|
||||||
CURRENT_CONFIG_ALLOWLIST = "allowlist"
|
CURRENT_CONFIG_ALLOWLIST = "allowlist"
|
||||||
CURRENT_CONFIG_DOCKERFILE = "Dockerfile"
|
CURRENT_CONFIG_DOCKERFILE = "Dockerfile"
|
||||||
|
|
||||||
@@ -437,7 +443,7 @@ class SupervisePlan:
|
|||||||
`queue_dir` is the host directory bind-mounted into the sidecar
|
`queue_dir` is the host directory bind-mounted into the sidecar
|
||||||
at /run/supervise/queue. `current_config_dir` is the host
|
at /run/supervise/queue. `current_config_dir` is the host
|
||||||
directory bind-mounted (read-only) into the *agent* container at
|
directory bind-mounted (read-only) into the *agent* container at
|
||||||
/etc/claude-bottle/current-config, holding routes.json + allowlist
|
/etc/claude-bottle/current-config, holding routes.yaml + allowlist
|
||||||
+ Dockerfile so the agent can read them before composing a
|
+ Dockerfile so the agent can read them before composing a
|
||||||
proposal. `internal_network` is empty at prepare time; the
|
proposal. `internal_network` is empty at prepare time; the
|
||||||
backend's launch step fills it via dataclasses.replace before
|
backend's launch step fills it via dataclasses.replace before
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ FIXED = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
|
|||||||
|
|
||||||
|
|
||||||
def _proposal(slug: str = "dev", tool: str = TOOL_EGRESS_PROXY_BLOCK) -> Proposal:
|
def _proposal(slug: str = "dev", tool: str = TOOL_EGRESS_PROXY_BLOCK) -> Proposal:
|
||||||
# Per-tool payload shape: cred-proxy gets routes.json, pipelock
|
# Per-tool payload shape: cred-proxy gets routes.yaml, pipelock
|
||||||
# gets a failed URL (PR #25 follow-up), capability gets a
|
# gets a failed URL (PR #25 follow-up), capability gets a
|
||||||
# Dockerfile-ish blob. Match the production dispatch in
|
# Dockerfile-ish blob. Match the production dispatch in
|
||||||
# PROPOSED_FILE_FIELD.
|
# PROPOSED_FILE_FIELD.
|
||||||
|
|||||||
@@ -297,9 +297,9 @@ class TestDiffAndHash(unittest.TestCase):
|
|||||||
self.assertEqual("", render_diff("a\nb\n", "a\nb\n"))
|
self.assertEqual("", render_diff("a\nb\n", "a\nb\n"))
|
||||||
|
|
||||||
def test_render_diff_shows_changes(self):
|
def test_render_diff_shows_changes(self):
|
||||||
diff = render_diff("a\nb\nc\n", "a\nB\nc\n", label="routes.json")
|
diff = render_diff("a\nb\nc\n", "a\nB\nc\n", label="routes.yaml")
|
||||||
self.assertIn("routes.json (current)", diff)
|
self.assertIn("routes.yaml (current)", diff)
|
||||||
self.assertIn("routes.json (proposed)", diff)
|
self.assertIn("routes.yaml (proposed)", diff)
|
||||||
self.assertIn("-b", diff)
|
self.assertIn("-b", diff)
|
||||||
self.assertIn("+B", diff)
|
self.assertIn("+B", diff)
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ class TestSupervisePrepare(unittest.TestCase):
|
|||||||
self.assertTrue(plan.current_config_dir.is_dir())
|
self.assertTrue(plan.current_config_dir.is_dir())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'{"routes": [{"path": "/x/"}]}\n',
|
'{"routes": [{"path": "/x/"}]}\n',
|
||||||
(plan.current_config_dir / "routes.json").read_text(),
|
(plan.current_config_dir / "routes.yaml").read_text(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"example.com\n",
|
"example.com\n",
|
||||||
@@ -382,7 +382,7 @@ class TestSupervisePrepare(unittest.TestCase):
|
|||||||
plan = _StubSupervise().prepare("dev", self.stage_dir)
|
plan = _StubSupervise().prepare("dev", self.stage_dir)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'{"routes": []}\n',
|
'{"routes": []}\n',
|
||||||
(plan.current_config_dir / "routes.json").read_text(),
|
(plan.current_config_dir / "routes.yaml").read_text(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user