fix: task templates use ingress CP_URL (https://agents.oreillyit.nz/api), not svc.cluster.local:8100

The deploy repo NetworkPolicy admits :8100 only from named apps and the traefik/cms-proxy
namespaces; agent/script pods time out, so state-tx / cp-cli calls silently lost transitions.
Invariant now rejects the in-cluster form and pins the ingress URL.

Claude-Session: https://claude.ai/code/session_01N218bERyNeLhbLmDRDvbWF
This commit is contained in:
Paul O'Reilly
2026-10-01 18:14:30 +13:00
parent 782eadb615
commit e51146fc30
8 changed files with 64 additions and 9 deletions

View File

@@ -60,3 +60,58 @@ def test_state_tx_node_declares_from_states(name):
assert doc["output"]["state"] not in states
# The template's own input state must be an acceptable from-state.
assert doc["input"]["state"] in states
# ---------------------------------------------------------------------------
# CP_URL must be the ingress form, never the in-cluster Service on :8100.
#
# The deploy repo's networkpolicy-cp-internal-traefik-only.yaml admits :8100 only
# from dashboard / git-planner-backend / cert-expiry-checker and the traefik and
# cms-proxy namespaces. Agent/script pods are dropped, so a template pointing
# CP_URL at svc.cluster.local:8100 times out (30s) and, with on_fail: continue,
# silently loses the transition. The workflow scripts join CP_URL + "/v1/...",
# so the /api ingress path prefix is preserved.
# ---------------------------------------------------------------------------
CP_URL_INGRESS = "https://agents.oreillyit.nz/api"
_FORBIDDEN_CP_URL_MARKERS = ("svc.cluster.local:8100", "controlplane.agent-runtimes.svc")
_ALL_TEMPLATES = sorted(_DIR.glob("*.yaml"))
def _cp_url_values(path: Path) -> list[str]:
doc = yaml.safe_load(path.read_text())
return [
(n.get("runtime_env") or {})["CP_URL"]
for n in (doc.get("nodes") or [])
if "CP_URL" in (n.get("runtime_env") or {})
]
def test_cp_url_templates_discovered():
names = {p.name for p in _ALL_TEMPLATES if _cp_url_values(p)}
assert {
"test-validate@1.yaml",
"frozen-fixture-emit@1.yaml",
"bug-ancestor-auto-advance@1.yaml",
"scope-decompose-sonnet@1.yaml",
"scope-decompose-sonnet@2.yaml",
"bug-verify-sonnet@1.yaml",
"bug-verify-sonnet@2.yaml",
} <= names
@pytest.mark.parametrize("path", _ALL_TEMPLATES, ids=lambda p: p.name)
def test_cp_url_is_ingress_not_in_cluster_service(path):
for value in _cp_url_values(path):
assert not any(m in value for m in _FORBIDDEN_CP_URL_MARKERS), (
f"{path.name}: CP_URL={value!r} targets the in-cluster CP service; agent pods "
"are blocked on :8100 by NetworkPolicy"
)
assert value == CP_URL_INGRESS, f"{path.name}: CP_URL={value!r}"
@pytest.mark.parametrize("path", _ALL_TEMPLATES, ids=lambda p: p.name)
def test_no_template_mentions_in_cluster_cp_service(path):
text = path.read_text()
assert not any(m in text for m in _FORBIDDEN_CP_URL_MARKERS)