From e51146fc30f63665f3f5719c42d5f422b905901e Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Thu, 1 Oct 2026 18:14:30 +1300 Subject: [PATCH] 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 --- .../workflow/bug-ancestor-auto-advance@1.yaml | 6 +- .../workflow/bug-verify-sonnet@1.yaml | 2 +- .../workflow/bug-verify-sonnet@2.yaml | 2 +- .../workflow/frozen-fixture-emit@1.yaml | 2 +- .../workflow/scope-decompose-sonnet@1.yaml | 2 +- .../workflow/scope-decompose-sonnet@2.yaml | 2 +- task-templates/workflow/test-validate@1.yaml | 2 +- tests/invariants/test_state_tx_runtime_env.py | 55 +++++++++++++++++++ 8 files changed, 64 insertions(+), 9 deletions(-) diff --git a/task-templates/workflow/bug-ancestor-auto-advance@1.yaml b/task-templates/workflow/bug-ancestor-auto-advance@1.yaml index 271c692..fc90d33 100644 --- a/task-templates/workflow/bug-ancestor-auto-advance@1.yaml +++ b/task-templates/workflow/bug-ancestor-auto-advance@1.yaml @@ -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 diff --git a/task-templates/workflow/bug-verify-sonnet@1.yaml b/task-templates/workflow/bug-verify-sonnet@1.yaml index 9300c00..fb34437 100644 --- a/task-templates/workflow/bug-verify-sonnet@1.yaml +++ b/task-templates/workflow/bug-verify-sonnet@1.yaml @@ -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. diff --git a/task-templates/workflow/bug-verify-sonnet@2.yaml b/task-templates/workflow/bug-verify-sonnet@2.yaml index 4ef022f..9363c93 100644 --- a/task-templates/workflow/bug-verify-sonnet@2.yaml +++ b/task-templates/workflow/bug-verify-sonnet@2.yaml @@ -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. diff --git a/task-templates/workflow/frozen-fixture-emit@1.yaml b/task-templates/workflow/frozen-fixture-emit@1.yaml index 0800f6d..416c9c5 100644 --- a/task-templates/workflow/frozen-fixture-emit@1.yaml +++ b/task-templates/workflow/frozen-fixture-emit@1.yaml @@ -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 diff --git a/task-templates/workflow/scope-decompose-sonnet@1.yaml b/task-templates/workflow/scope-decompose-sonnet@1.yaml index 3c75fbd..cd020d8 100644 --- a/task-templates/workflow/scope-decompose-sonnet@1.yaml +++ b/task-templates/workflow/scope-decompose-sonnet@1.yaml @@ -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" diff --git a/task-templates/workflow/scope-decompose-sonnet@2.yaml b/task-templates/workflow/scope-decompose-sonnet@2.yaml index ca8babf..188e278 100644 --- a/task-templates/workflow/scope-decompose-sonnet@2.yaml +++ b/task-templates/workflow/scope-decompose-sonnet@2.yaml @@ -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: | diff --git a/task-templates/workflow/test-validate@1.yaml b/task-templates/workflow/test-validate@1.yaml index fe8af8e..c66660b 100644 --- a/task-templates/workflow/test-validate@1.yaml +++ b/task-templates/workflow/test-validate@1.yaml @@ -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 diff --git a/tests/invariants/test_state_tx_runtime_env.py b/tests/invariants/test_state_tx_runtime_env.py index 176c468..c8e954a 100644 --- a/tests/invariants/test_state_tx_runtime_env.py +++ b/tests/invariants/test_state_tx_runtime_env.py @@ -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)