fix(supervise): stage current-config routes file as routes.yaml
The supervise sidecar mounted a snapshot named routes.json into the agent at /etc/claude-bottle/current-config/routes.json, but the egress-proxy-block tool description (and the live proxy file the apply step writes) say routes.yaml. The agent couldn't find the file at the documented path, composed proposals against stale or empty current state, and reported "routes wasn't updated on disk" because it was looking at the wrong filename. Rename the staged file to routes.yaml so the tool description, the staged snapshot, and the live proxy file all agree on the name. Content stays JSON-in-a-yaml-extension (per PRD 0017 chunk 1's decision: every JSON document is valid YAML, stdlib parsers handle it on both ends). Note: the staged file is still a one-shot snapshot taken at bottle prep time. It does NOT auto-update when the operator approves an egress-proxy-block. Agents that want to verify their proposal took effect should retry the request that triggered the block — a successful upstream response is the real signal. Fixing the snapshot-staleness UX is a separate follow-up. Tests migrated from routes.json → routes.yaml. 364 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -278,7 +278,7 @@ def _run_agent_container(plan: DockerBottlePlan, internal_network: str) -> str:
|
||||
docker_args.extend(["-e", name])
|
||||
|
||||
# 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
|
||||
# stage_dir/current-config/ populated at prepare time.
|
||||
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
|
||||
# these (read-only) from CURRENT_CONFIG_DIR_IN_AGENT and proposes
|
||||
# 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_DOCKERFILE = "Dockerfile"
|
||||
|
||||
@@ -437,7 +443,7 @@ class SupervisePlan:
|
||||
`queue_dir` is the host directory bind-mounted into the sidecar
|
||||
at /run/supervise/queue. `current_config_dir` is the host
|
||||
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
|
||||
proposal. `internal_network` is empty at prepare time; the
|
||||
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:
|
||||
# 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
|
||||
# Dockerfile-ish blob. Match the production dispatch in
|
||||
# PROPOSED_FILE_FIELD.
|
||||
|
||||
@@ -297,9 +297,9 @@ class TestDiffAndHash(unittest.TestCase):
|
||||
self.assertEqual("", render_diff("a\nb\n", "a\nb\n"))
|
||||
|
||||
def test_render_diff_shows_changes(self):
|
||||
diff = render_diff("a\nb\nc\n", "a\nB\nc\n", label="routes.json")
|
||||
self.assertIn("routes.json (current)", diff)
|
||||
self.assertIn("routes.json (proposed)", diff)
|
||||
diff = render_diff("a\nb\nc\n", "a\nB\nc\n", label="routes.yaml")
|
||||
self.assertIn("routes.yaml (current)", diff)
|
||||
self.assertIn("routes.yaml (proposed)", 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.assertEqual(
|
||||
'{"routes": [{"path": "/x/"}]}\n',
|
||||
(plan.current_config_dir / "routes.json").read_text(),
|
||||
(plan.current_config_dir / "routes.yaml").read_text(),
|
||||
)
|
||||
self.assertEqual(
|
||||
"example.com\n",
|
||||
@@ -382,7 +382,7 @@ class TestSupervisePrepare(unittest.TestCase):
|
||||
plan = _StubSupervise().prepare("dev", self.stage_dir)
|
||||
self.assertEqual(
|
||||
'{"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