From c67d9736fda1b12ec5b0692a4f471745913f2ad7 Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Mon, 19 Oct 2026 01:41:44 +1300 Subject: [PATCH] R3b [fw]: delete legacy workflow-script/v1 composite and cp-service-token/v1 context (dbc5efc6) Control plane 0.0.5.1890 no longer falls back to either. Invariant tests now assert both stay gone. Claude-Session: https://claude.ai/code/session_01N218bERyNeLhbLmDRDvbWF --- .../workflow-script/v1/harness.yaml | 35 --- .../contexts/cp-service-token/v1/harness.yaml | 30 --- .../contexts/cp-service-token/v1/init.sh | 39 --- .../test_no_legacy_script_composite.py | 32 +++ .../test_workflow_script_composite_layers.py | 35 --- ...test_workflow_script_composite_resolves.py | 227 ------------------ 6 files changed, 32 insertions(+), 366 deletions(-) delete mode 100644 harnesses/composites/workflow-script/v1/harness.yaml delete mode 100644 harnesses/contexts/cp-service-token/v1/harness.yaml delete mode 100755 harnesses/contexts/cp-service-token/v1/init.sh create mode 100644 tests/invariants/test_no_legacy_script_composite.py delete mode 100644 tests/invariants/test_workflow_script_composite_layers.py delete mode 100644 tests/invariants/test_workflow_script_composite_resolves.py diff --git a/harnesses/composites/workflow-script/v1/harness.yaml b/harnesses/composites/workflow-script/v1/harness.yaml deleted file mode 100644 index f1dc995..0000000 --- a/harnesses/composites/workflow-script/v1/harness.yaml +++ /dev/null @@ -1,35 +0,0 @@ -kind: composite -name: workflow-script -version: 1 -description: "Internal composite for the flag-on kind: script path (typed-workflows WF-COMP-31 / workflow-variables WV-19). Brings the shared-variables filesystem context AND the AU-51 CP write credential (cp-service-token/v1 sets CP_SERVICE_TOKEN_FILE in the entrypoint process env before shell_runner runs). NOT a model-bearing composite — the kind: script compiled shape is cli=\"shell\", params.cmd=; this composite provides the harness layers that path needs and nothing else." - -# Order matters only for HC-6 env last-writer-wins / HC-8 git identity. The two -# layers here have disjoint env vars (shared-variables sets WORKFLOW_VARIABLES_* -# paths, cp-service-token sets CP_SERVICE_TOKEN_FILE) and no overlapping git -# identity, so the listed order has no observable merge effect — both layers -# reach the script-node container either way. -layers: - # spec/workflow-variables.md WV-19 / spec/harness.md HD-14 — agent-facing - # /variables/ context (CLAUDE.md mounted at /opt/harness/context/shared-variables/CLAUDE.md) - # plus WORKFLOW_VARIABLES_ROOT=/variables and templated WORKFLOW_VARIABLES_SELF=. - # Provides the [shared-variables] tag used by the dispatcher's extra_layers injection. - - context: shared-variables/v1 - # spec/typed-workflows.md WF-COMP-31 "AU-51 credential env seam" — this layer's - # init.sh restages the ESO-mounted CP-service-token secret (root-owned mode 0400) - # to an agent-owned 0600 copy at /run/agent/cp-service-token/token and exports - # CP_SERVICE_TOKEN_FILE pointing at it. Verified: env.CP_SERVICE_TOKEN_FILE is set - # on the entrypoint process environment before shell_runner runs (entrypoint reads - # os.environ via env=os.environ.copy() in subprocess.run at entrypoint/runners/shell.py:73, - # so the same credential channel an agent node gets reaches a script node unchanged). - - context: cp-service-token/v1 - # spec/typed-workflows.md WF-COMP-34 (bug 3bee78fa, 2026-10-02) — read-only - # project-repo clone for harness-path kind: script nodes. Layered LAST so - # its clone runs after the two AU-51 wiring layers and before the script - # starts (init.sh order: shared-variables -> cp-service-token -> repo-readonly). - # When AGENT_REPO_URL is unset (the pre-fix runtime, or any template that - # does not declare agent_repo) this layer is a no-op; the V2 guard - # (entrypoint/entrypoint.py:901-906) does NOT trip because the unset case - # is a no-op there too. Layered-in instead of layering agent-repo/v1 - # because that context's fork management and push side effects are wrong - # for a read-only script node (see bug item body). - - context: repo-readonly/v1 diff --git a/harnesses/contexts/cp-service-token/v1/harness.yaml b/harnesses/contexts/cp-service-token/v1/harness.yaml deleted file mode 100644 index 116e115..0000000 --- a/harnesses/contexts/cp-service-token/v1/harness.yaml +++ /dev/null @@ -1,30 +0,0 @@ -kind: context -name: cp-service-token -version: 1 -description: "CP-internal service bearer token — authenticates agent-container write calls back to the control plane (AU-51/AU-51b, bug b3a96acc)" -requires: [] -provides: [] - -# AU-51b: the ESO-mounted secret is root-owned mode 0400 (init.sh runs as -# root, before the gosu drop to the agent user, uid 1000) — unreadable by -# the agent process directly. Confirmed live 2026-09-03: a real agent -# session hit exactly this ("cp-cli invocation can't proceed... token is -# root-owned mode 0400 so it's not readable by the agent user"). Mirrors -# anthropic-cloud-paul-oauth/v1's pattern: init.sh restages a 0600 -# agent-owned copy outside the ESO mount. CP_SERVICE_TOKEN_FILE points at -# the restaged copy, not the raw mount. -# -# The value at this path MUST match the CP's CP_INTERNAL_BEARER_TOKEN -# (same account: cp-decompose-service-token, account_id -# 1d963673-6ac9-4f85-875a-2ce5323e76ad, owner (cp, cp)). -env: - CP_SERVICE_TOKEN_FILE: /run/agent/cp-service-token/token - -scripts: - init: "./init.sh" - -secrets_required: - - name: cp-service-token - account_ref: "1d963673-6ac9-4f85-875a-2ce5323e76ad" - mount_path: /run/agent/secrets/cp-service-token - mode: "0400" diff --git a/harnesses/contexts/cp-service-token/v1/init.sh b/harnesses/contexts/cp-service-token/v1/init.sh deleted file mode 100755 index 238c69e..0000000 --- a/harnesses/contexts/cp-service-token/v1/init.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -# cp-service-token init — restage the ESO-mounted secret to an agent-owned -# copy. -# -# Threat model: keep the ESO mount root-only (mode 0400) so the agent user -# cannot directly `cat` /run/agent/secrets/cp-service-token/*. init.sh runs -# as root (in uid-wrapper.sh, before the gosu drop) and writes a restaged -# copy at /run/agent/cp-service-token/token with mode 0600 owned by the -# agent user. This mirrors anthropic-cloud-paul-oauth/v1/init.sh's pattern. -# -# Bug discovered live 2026-09-03: without this restaging step, cp-cli -# (invoked by the agent user, uid 1000) could not read the raw 0400 -# root-owned mount at all — "cp_cli invocation can't proceed... token is -# root-owned mode 0400 so it's not readable by the agent user". The context -# originally set CP_SERVICE_TOKEN_FILE directly at the raw ESO mount path, -# which is the mistake this init.sh fixes. - -set -euo pipefail - -ESO_TOKEN="/run/agent/secrets/cp-service-token/value" - -if [ ! -r "$ESO_TOKEN" ]; then - echo "ERROR: $ESO_TOKEN not readable. Check ESO ExternalSecret acct-1d963673." >&2 - exit 1 -fi - -AGENT_USER="${AGENT_USER:-agent}" - -DEST_DIR="/run/agent/cp-service-token" -mkdir -p "$DEST_DIR" - -DEST_FILE="$DEST_DIR/token" -cp "$ESO_TOKEN" "$DEST_FILE" - -chown "$AGENT_USER:" "$DEST_DIR" "$DEST_FILE" 2>/dev/null || true -chmod 0700 "$DEST_DIR" -chmod 0600 "$DEST_FILE" - -echo "cp-service-token: restaged into $DEST_FILE (0600 $AGENT_USER:$AGENT_USER)" diff --git a/tests/invariants/test_no_legacy_script_composite.py b/tests/invariants/test_no_legacy_script_composite.py new file mode 100644 index 0000000..828ae6f --- /dev/null +++ b/tests/invariants/test_no_legacy_script_composite.py @@ -0,0 +1,32 @@ +"""Track R / R3b: the legacy AU-51 bearer composite and layer stay deleted. + +``workflow-script/v1`` (the legacy script composite) and the +``cp-service-token/v1`` context were removed once the control plane stopped +falling back to them (agent-runtimes 0.0.5.1890). Nothing may re-introduce +either, and no live composite may layer the bearer context. +""" + +from pathlib import Path + +import yaml + +_ROOT = Path(__file__).resolve().parents[2] +_HARNESSES = _ROOT / "harnesses" + + +def test_legacy_script_composite_directory_is_gone(): + assert not (_HARNESSES / "composites" / "workflow-script").exists() + + +def test_cp_service_token_context_directory_is_gone(): + assert not (_HARNESSES / "contexts" / "cp-service-token").exists() + + +def test_no_composite_layers_cp_service_token(): + offenders = [] + for path in sorted((_HARNESSES / "composites").glob("*/v*/harness.yaml")): + doc = yaml.safe_load(path.read_text()) or {} + for layer in doc.get("layers") or []: + if str(layer.get("context", "")).startswith("cp-service-token"): + offenders.append(str(path.relative_to(_ROOT))) + assert offenders == [] diff --git a/tests/invariants/test_workflow_script_composite_layers.py b/tests/invariants/test_workflow_script_composite_layers.py deleted file mode 100644 index 28489cb..0000000 --- a/tests/invariants/test_workflow_script_composite_layers.py +++ /dev/null @@ -1,35 +0,0 @@ -"""workflow-script/v1 composite: exact layer order and credential env export. - -Script nodes get their CP write credential only through this composite -(the legacy WF-COMP-26 mount is removed), so the layering is load-bearing. -""" -from __future__ import annotations - -from pathlib import Path - -import yaml - -_ROOT = Path(__file__).resolve().parents[2] / "harnesses" - - -def _load(kind_dir: str, name: str) -> dict: - return yaml.safe_load((_ROOT / kind_dir / name / "v1" / "harness.yaml").read_text()) - - -def test_workflow_script_layers_shared_variables_cp_service_token_repo_readonly(): - doc = _load("composites", "workflow-script") - assert doc["layers"] == [ - {"context": "shared-variables/v1"}, - {"context": "cp-service-token/v1"}, - {"context": "repo-readonly/v1"}, - ] - - -def test_workflow_script_env_exports_cp_service_token_file(): - doc = _load("composites", "workflow-script") - env: dict = dict(doc.get("env") or {}) - for layer in doc["layers"]: - (kind, ref), = layer.items() - name = ref.split("/")[0] - env.update(_load(kind + "s", name).get("env") or {}) - assert env["CP_SERVICE_TOKEN_FILE"] == "/run/agent/cp-service-token/token" diff --git a/tests/invariants/test_workflow_script_composite_resolves.py b/tests/invariants/test_workflow_script_composite_resolves.py deleted file mode 100644 index 81e57b4..0000000 --- a/tests/invariants/test_workflow_script_composite_resolves.py +++ /dev/null @@ -1,227 +0,0 @@ -"""Workflow-script harness composition invariant (spec/typed-workflows.md WF-COMP-31 -+ spec/workflow-variables.md WV-19 + spec/harness.md HD-14). - -The two new framework layers — ``harnesses/contexts/shared-variables/v1/`` and -``harnesses/composites/workflow-script/v1/`` — must exist on the tree, must parse, -and the composite must resolve every layer it references to a real file on disk. -This test guards against the most common regression: a missing or renamed layer -that would only surface at the dispatcher's ``compose_harness`` call, where it -costs an entire task's worth of wasted dispatch round-trip. - -Spec: /home/paul/dev/claude/projects/agent-runtimes/spec/workflow-variables.md - (WV-19, WV-20, WF-COMP-31), spec/harness.md (HD-14). - -Not committed by the authoring agent per its instructions — written here so it -can run locally against the current tree. -""" -from __future__ import annotations - -from pathlib import Path - -import pytest -import yaml - -_HARNESSES_ROOT = Path(__file__).resolve().parents[2] / "harnesses" - -# Composite layer entries use the singular kind as the dict key. The kind maps -# to a top-level directory by adding "s". -_KIND_TO_DIR = { - "context": "contexts", - "capability": "capabilities", - "composite": "composites", -} - - -def _load_layer(kind_dir: str, name: str) -> dict: - p = _HARNESSES_ROOT / kind_dir / name / "v1" / "harness.yaml" - return yaml.safe_load(p.read_text(encoding="utf-8")) - - -def test_shared_variables_layer_exists_and_parses(): - """``harnesses/contexts/shared-variables/v1/harness.yaml`` must exist, - parse as YAML, and carry ``provides: [shared-variables]`` (the HD-14 tag). - - Spec: spec/workflow-variables.md WV-19; spec/harness.md HD-14. - """ - parsed = _load_layer("contexts", "shared-variables") - assert parsed["kind"] == "context" - assert parsed["name"] == "shared-variables" - assert parsed["version"] == 1 - assert parsed["provides"] == ["shared-variables"], ( - "WV-19: provides MUST contain the 'shared-variables' tag — HD-14's " - "extra_layers dedup key, and the tag the dispatcher's payload-marker " - "injection matches against." - ) - # WV-20: this layer's OWN env: declaration sets the two WORKFLOW_VARIABLES_* - # vars (NOT the entrypoint, NOT the container backend). - env = parsed["env"] - assert env["WORKFLOW_VARIABLES_ROOT"] == "/variables" - assert "WORKFLOW_VARIABLES_SELF" in env, ( - "WV-20: WORKFLOW_VARIABLES_SELF must be set by the layer's env: " - "(templated per-node at harness-composition time on the dispatcher)" - ) - # Agent-facing context: a CLAUDE.md must be mounted under /opt/harness/context/. - files = parsed.get("context_files", []) - targets = [f["target"] for f in files] - assert any(t.startswith("/opt/harness/context/shared-variables/") for t in targets), ( - "WV-19: agent-facing context must be mounted under /opt/harness/context/" - "(the HC-7 namespacing point) so the agent sees it at startup." - ) - - -def test_shared_variables_claude_md_present_and_substantial(): - """The agent-facing CLAUDE.md must be a non-trivial markdown document - (HC-7 says it is read by the agent, not a placeholder). - - Spec: spec/workflow-variables.md WV-19; spec/harness.md HC-7. - """ - md = _HARNESSES_ROOT / "contexts" / "shared-variables" / "v1" / "CLAUDE.md" - assert md.exists(), "WV-19: shared-variables/v1 must ship a CLAUDE.md" - text = md.read_text(encoding="utf-8") - assert text.count("\n") >= 30, ( - f"WV-19: the CLAUDE.md must explain the /variables/ contract to the " - f"agent — got {text.count(chr(10))} lines, expected >=30." - ) - # Spot-check the four high-value pieces of content the agent MUST see: - assert "/variables/" in text, "must explain the filesystem root" - assert "WORKFLOW_VARIABLES_SELF" in text, "must document the env vars" - assert "wfvar" in text, "must point at the CLI helper (WV-21)" - assert "reserved" in text.lower(), "must list the reserved-name families (WV-6)" - - -def test_workflow_script_composite_resolves(): - """``harnesses/composites/workflow-script/v1/harness.yaml`` must declare - exactly the two layers the spec names (WF-COMP-31) and every referenced - file must exist on disk. - - Spec: spec/typed-workflows.md WF-COMP-31 ("Internal composite layered from - ``shared-variables/v1`` + ``cp-service-token/v1``"). - """ - parsed = _load_layer("composites", "workflow-script") - assert parsed["kind"] == "composite" - assert parsed["name"] == "workflow-script" - assert parsed["version"] == 1 - assert isinstance(parsed["layers"], list) and len(parsed["layers"]) >= 2, ( - "WF-COMP-31: the composite must layer at least shared-variables/v1 + " - "cp-service-token/v1; missing one breaks the credential env seam." - ) - - refs = [] - for layer in parsed["layers"]: - assert isinstance(layer, dict) and len(layer) == 1, ( - f"each layer entry must be a single-key dict (kind: ref), got {layer}" - ) - kind_name, ref = next(iter(layer.items())) - assert kind_name in _KIND_TO_DIR, f"unknown layer kind {kind_name!r}" - assert "/" in ref, f"layer ref must be '/v', got {ref!r}" - name, ver = ref.split("/") - assert ver.startswith("v") and ver[1:].isdigit(), ( - f"layer ref version must match v, got {ver!r}" - ) - kind_dir = _KIND_TO_DIR[kind_name] - layer_path = _HARNESSES_ROOT / kind_dir / name / ver / "harness.yaml" - assert layer_path.exists(), ( - f"WF-COMP-31: composite references a layer that does not exist on " - f"the tree: {layer_path.relative_to(_HARNESSES_ROOT)}" - ) - layer_data = yaml.safe_load(layer_path.read_text(encoding="utf-8")) - assert layer_data["name"] == name, ( - f"name mismatch in {layer_path}: file says {layer_data['name']!r}, " - f"ref says {name!r}" - ) - refs.append((kind_name, name, ver, layer_data)) - - # WF-COMP-31: the two SPECIFIED layers must both be present. - ref_names = {(kind, name) for kind, name, _, _ in refs} - assert ("context", "shared-variables") in ref_names, ( - "WF-COMP-31: workflow-script/v1 MUST layer shared-variables/v1 " - "(WV-19 — the agent-facing /variables/ contract)" - ) - assert ("context", "cp-service-token") in ref_names, ( - "WF-COMP-31 / AU-51 credential env seam: workflow-script/v1 MUST " - "layer cp-service-token/v1 so CP_SERVICE_TOKEN_FILE is in the " - "entrypoint process env before shell_runner runs." - ) - - -def test_workflow_script_composite_brings_no_model_or_endpoint_layer(): - """``kind: script`` nodes run via the shell runner — the composite MUST - NOT bring agent-only layers (no model, endpoint, CLAUDE.md context that - drives the agent runner, etc.). This is a structural regression gate: a - future refactor that adds e.g. ``anthropic-cloud-paul-oauth/v1`` here - would silently widen the script-node attack surface. - - Spec: spec/typed-workflows.md WF-COMP-31 ("the flag-on kind: script - harness ... MUST NOT bring agent-only layers"). - """ - parsed = _load_layer("composites", "workflow-script") - forbidden_substrings = ( - # cloud-auth contexts the agent runner needs - "anthropic-cloud", "minimax", "qwen-code", "deepseek-code", - "alibaba-model-studio", "fable-low-effort", "z-ai", - # the model-bearing capability / context layers - "code-methodology", "spec-writing", "planning", "integration", - # harness composites that bundle a model - "code-", "planning-", "spec-writing-", "best-practices-", - "sonnet-", "opus-", "minimax-", "haiku-", "airouter-", "fable-", - ) - layer_names = [ - next(iter(layer.values())).split("/")[0] - for layer in parsed["layers"] - ] - bad = [ - n for n in layer_names - if any(sub in n for sub in forbidden_substrings) - ] - assert not bad, ( - f"WF-COMP-31: workflow-script/v1 MUST NOT bring agent-only layers; " - f"found: {bad}" - ) - - -def test_cp_service_token_layer_already_sets_cp_service_token_file_env(): - """WF-COMP-31 'AU-51 credential env seam': cp-service-token/v1 must set - ``CP_SERVICE_TOKEN_FILE`` in its env: declaration (the layer's init.sh - restages the ESO-mounted secret to an agent-readable 0600 copy at the - path CP_SERVICE_TOKEN_FILE points at). Verified here at the YAML level - rather than at runtime — the spec's claim is structural. - - Spec: spec/typed-workflows.md WF-COMP-31; spec/harness.md AU-51/AU-51b - (bug b3a96acc). - """ - parsed = _load_layer("contexts", "cp-service-token") - env = parsed["env"] - assert "CP_SERVICE_TOKEN_FILE" in env, ( - "WF-COMP-31 'AU-51 credential env seam': cp-service-token/v1 must " - "declare CP_SERVICE_TOKEN_FILE in its env: block; without it the " - "shell_runner subprocess does not inherit the AU-51 credential." - ) - fpath = env["CP_SERVICE_TOKEN_FILE"] - assert fpath.endswith("/token"), ( - f"CP_SERVICE_TOKEN_FILE must point at the restaged 0600 file, not " - f"the raw 0400 ESO mount — got {fpath!r}" - ) - - -def test_workflow_script_composite_forge_credentials_are_read_only(): - """Any layer in workflow-script/v1 that carries a forge credential MUST - declare ``write_targets: []`` (HC-11): a script node may read the project - repo but never push. The write-capable gitea-ssh/v1 layer must not be - present (bug 45a48685). - - Spec: spec/harness.md HC-11, spec/typed-workflows.md WF-COMP-34. - """ - parsed = _load_layer("composites", "workflow-script") - from tests.invariants.forge_credentials import _layer_has_forge_credentials - - for layer in parsed["layers"]: - kind_name, ref = next(iter(layer.items())) - name = ref.split("/")[0] - assert name != "gitea-ssh", "workflow-script/v1 must not layer the write-capable gitea-ssh/v1 (bug 45a48685)" - kind_dir = _KIND_TO_DIR[kind_name] - sub = _load_layer(kind_dir, name) - if _layer_has_forge_credentials(sub): - assert sub.get("write_targets") == [], ( - f"workflow-script/v1's layer {name} carries a forge credential " - f"but does not declare write_targets: [] (HC-11 read-only)." - )