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

@@ -33,7 +33,7 @@ nodes:
cmd: scripts/workflow/parent-link-check
on_fail: refuse_dispatch
runtime_env:
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
WORKFLOW_ITEM_UUID: "${item.uuid}"
PARENT_LINK_TAG: auto-resolve-bugs
PARENT_LINK_RELATION: parent
@@ -45,7 +45,7 @@ nodes:
depends_on: [parent-link-check]
on_fail: escalate
runtime_env:
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
WORKFLOW_ITEM_UUID: "${item.uuid}"
TAG_SET_ADD: ai-resolve
# Advance the bug; R2 then dispatches bug-verify-sonnet@1 at verify-pending.
@@ -55,7 +55,7 @@ nodes:
depends_on: [tag-ai-resolve]
on_fail: escalate
runtime_env:
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
STATE_TX_NEW_STATE: verify-pending
STATE_TX_FROM_STATES: "pending,open"
# NOTE (script-node credentials): kind: script nodes are harness-exempt

View File

@@ -36,7 +36,7 @@ nodes:
runtime_env:
AGENT_REPO_URL: "git@gitea.oreillyit.nz-ai-enablement:skynet/agent-runtimes-agents.git"
AGENT_BRANCH: "bug-verify/${item.uuid}"
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
prompt: |
The agent-runtimes project repo is cloned at /workspace/project.
Change to that directory before doing any work.

View File

@@ -30,7 +30,7 @@ nodes:
runtime_env:
AGENT_REPO_URL: "git@gitea.oreillyit.nz-ai-enablement:skynet/agent-runtimes-agents.git"
AGENT_BRANCH: "bug-verify/${item.uuid}"
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
prompt: |
The agent-runtimes project repo is cloned at /workspace/project.
Change to that directory before doing any work.

View File

@@ -22,6 +22,6 @@ nodes:
cmd: scripts/workflow/state-tx
on_fail: continue
runtime_env:
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
STATE_TX_NEW_STATE: test-skeleton-ready
STATE_TX_FROM_STATES: scoped-tagged

View File

@@ -27,7 +27,7 @@ nodes:
runtime_env:
AGENT_REPO_URL: "git@gitea.oreillyit.nz-ai-enablement:skynet/agent-runtimes-agents.git"
AGENT_BRANCH: "spec/${item.uuid}"
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
# AR-38: this stage's deliverable is work items POSTed to the CP, not a
# repo diff — the empty-deliverable audit would fail every success.
AGENT_EMPTY_DELIVERABLE_CHECK: "false"

View File

@@ -26,7 +26,7 @@ nodes:
runtime_env:
AGENT_REPO_URL: "git@gitea.oreillyit.nz-ai-enablement:skynet/agent-runtimes-agents.git"
AGENT_BRANCH: "spec/${item.uuid}"
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
# The deliverable is a directory of plan files on disk — NOT a git diff.
AGENT_EMPTY_DELIVERABLE_CHECK: "false"
prompt: |

View File

@@ -30,6 +30,6 @@ nodes:
cmd: scripts/workflow/state-tx
on_fail: continue
runtime_env:
CP_URL: "http://controlplane.agent-runtimes.svc.cluster.local:8100"
CP_URL: "https://agents.oreillyit.nz/api"
STATE_TX_NEW_STATE: test-validated
STATE_TX_FROM_STATES: test-write-pending

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)