From 8a93557be950b119ca86901810e594c7c9022285 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 13 Jul 2026 20:31:46 -0400 Subject: [PATCH 1/2] Remove reusable workflow self-ref check --- .github/workflows/run-agent-task.yml | 9 +-------- docs/agent-task-reusable-workflow.md | 20 +++++++++++-------- ...eusable-workflow-build-run-29295530010.yml | 15 ++++++++++++++ tests/agent-task-reusable-workflow.test.ts | 9 ++++++--- 4 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 fixtures/agent-task-reusable-workflow-build-run-29295530010.yml diff --git a/.github/workflows/run-agent-task.yml b/.github/workflows/run-agent-task.yml index fa7f03598..ae91c8ac0 100644 --- a/.github/workflows/run-agent-task.yml +++ b/.github/workflows/run-agent-task.yml @@ -163,19 +163,12 @@ jobs: credential_mode: ${{ steps.execute.outputs.credential_mode }} declared_artifacts_json: ${{ steps.execute.outputs.declared_artifacts_json }} steps: - - name: Validate coherent WP Codebox release tag - env: - WORKFLOW_REF: ${{ github.workflow_ref }} + - name: Validate WP Codebox release tag run: | if ! [[ "${WP_CODEBOX_RELEASE_REF}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "wp_codebox_release_ref must be an immutable vX.Y.Z release tag" >&2 exit 1 fi - expected_workflow_ref="Automattic/wp-codebox/.github/workflows/run-agent-task.yml@${WP_CODEBOX_RELEASE_REF}" - if [[ "${WORKFLOW_REF}" != "${expected_workflow_ref}" ]]; then - echo "The reusable workflow tag and wp_codebox_release_ref must match exactly" >&2 - exit 1 - fi - name: Checkout WP Codebox workflow helpers uses: actions/checkout@v4 diff --git a/docs/agent-task-reusable-workflow.md b/docs/agent-task-reusable-workflow.md index 086bd16c8..8d0284a85 100644 --- a/docs/agent-task-reusable-workflow.md +++ b/docs/agent-task-reusable-workflow.md @@ -39,18 +39,20 @@ with `wp_codebox_release_ref: v0.12.3`. The accepted format is exactly `vX.Y.Z`; branches, commit SHAs, moving major tags, prereleases, and arbitrary refs are rejected. -The workflow validates that its own `uses:` reference and -`wp_codebox_release_ref` match exactly before checkout. It always checks helpers -out from `Automattic/wp-codebox`, verifies the checked-out commit equals the -remote release tag commit, and verifies the checked-out `package.json` version -equals the requested tag without its `v` prefix. The caller cannot select a -different helper repository. +The workflow requires `wp_codebox_release_ref` to be an exact release tag. It +always checks helpers out from `Automattic/wp-codebox`, verifies the checked-out +commit equals the remote release tag commit, and verifies the checked-out +`package.json` version equals the requested tag without its `v` prefix. The +caller cannot select a different helper repository. GitHub nested workflows +expose the caller's `github.workflow_ref`, and the running workflow cannot +introspect its own `uses:` ref, so helper selection relies on the required input +and verified checkout rather than that caller context. This release-coherence contract fixes [#1759](https://github.com/Automattic/wp-codebox/issues/1759). ## Inputs -- `wp_codebox_release_ref`: required exact immutable WP Codebox release tag. It must match the `@vX.Y.Z` tag in the caller's `uses:` declaration exactly. +- `wp_codebox_release_ref`: required exact immutable WP Codebox release tag in `vX.Y.Z` form. - `external_package_source`: immutable descriptor with `repository`, full commit `revision`, one package-relative `.agent.json` `path`, and `digest`. Packages are supported only from publicly accessible GitHub repositories, fetched from canonical `https://github.com/OWNER/REPOSITORY.git` without credentials. `digest` is exactly `sha256-bytes-v1:` over the raw file bytes; filenames and JSON content are UTF-8-safe and are not normalized before hashing. - `EXTERNAL_PACKAGE_SOURCE_POLICY`: required reusable-workflow secret, supplied by the caller's operator-controlled secret configuration. Its strict version 1 JSON shape is `{"version":1,"repositories":{"owner/repository":["agents/example.agent.json"]}}`. Every entry is an exact standalone `.agent.json` path. The policy is validated in runner memory, is never part of task input, and is not uploaded. - `target_repo`: `OWNER/REPO` target repository. @@ -173,7 +175,9 @@ remain caller-workflow responsibilities. This is an intentional exposed-workflow `wp_codebox_release_ref` is a required v1 input. Existing callers must switch their `uses:` reference from a branch or other ref to an exact release tag and -pass that identical tag through this input. +pass that exact release tag through this input. Consumer contract tests can +compare both values where the caller workflow is available; the reusable +workflow itself cannot inspect the caller's `uses:` declaration at runtime. ## Upload safety limits diff --git a/fixtures/agent-task-reusable-workflow-build-run-29295530010.yml b/fixtures/agent-task-reusable-workflow-build-run-29295530010.yml new file mode 100644 index 000000000..5fbff5db1 --- /dev/null +++ b/fixtures/agent-task-reusable-workflow-build-run-29295530010.yml @@ -0,0 +1,15 @@ +# Regression fixture from Build run 29295530010. In a nested workflow GitHub +# exposed this caller context, not the reusable workflow's own uses ref. +# github.workflow_ref: Automattic/wp-build/.github/workflows/build.yml@trunk +name: Build consumer with foreign caller workflow ref + +on: workflow_dispatch + +jobs: + run-agent-task: + uses: Automattic/wp-codebox/.github/workflows/run-agent-task.yml@v0.12.4 + with: + wp_codebox_release_ref: v0.12.4 + external_package_source: '{"repository":"Automattic/example-agent-packages","revision":"0123456789abcdef0123456789abcdef01234567","path":"agents/example.agent.json","digest":"sha256-bytes-v1:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}' + target_repo: Automattic/example-target + prompt: Refresh the configured surface. diff --git a/tests/agent-task-reusable-workflow.test.ts b/tests/agent-task-reusable-workflow.test.ts index 187f10151..df7e2b10c 100644 --- a/tests/agent-task-reusable-workflow.test.ts +++ b/tests/agent-task-reusable-workflow.test.ts @@ -42,8 +42,7 @@ assert.match(workflow, /agent-task-upload/) assert.match(workflow, /if: always\(\)/) assert.match(workflow, /WP_CODEBOX_RELEASE_REF: \$\{\{ inputs\.wp_codebox_release_ref \}\}/) assert.match(workflow, /\^v\[0-9\]\+\\\.\[0-9\]\+\\\.\[0-9\]\+\$/) -assert.match(workflow, /WORKFLOW_REF: \$\{\{ github\.workflow_ref \}\}/) -assert.match(workflow, /The reusable workflow tag and wp_codebox_release_ref must match exactly/) +assert.doesNotMatch(workflow, /github\.workflow_ref|WORKFLOW_REF|expected_workflow_ref/) assert.match(workflow, /repository: Automattic\/wp-codebox/) assert.match(workflow, /ref: \$\{\{ inputs\.wp_codebox_release_ref \}\}/) assert.match(workflow, /Verify WP Codebox workflow helper release/) @@ -51,7 +50,7 @@ assert.match(workflow, /git ls-remote --exit-code --refs origin "refs\/tags\/\$\ assert.match(workflow, /checked_out_commit.*remote_tag_commit/) assert.match(workflow, /JSON\.parse\(readFileSync\("package\.json", "utf8"\)\)\.version/) assert.doesNotMatch(workflow, /steps\.[^.]+\.outputs\.ref/) -assert.match(workflow, /Validate coherent WP Codebox release tag/) +assert.match(workflow, /Validate WP Codebox release tag/) const parseConsumerReleaseTags = (consumer: string) => ({ workflow: consumer.match(/uses: Automattic\/wp-codebox\/\.github\/workflows\/run-agent-task\.yml@([^\s]+)/)?.[1], @@ -64,8 +63,11 @@ const isCoherentConsumer = (consumer: string) => { } const coherentConsumer = await readFile(new URL("../fixtures/agent-task-reusable-workflow-consumer.yml", import.meta.url), "utf8") const mismatchedConsumer = await readFile(new URL("../fixtures/agent-task-reusable-workflow-consumer-mismatched.yml", import.meta.url), "utf8") +const buildRunConsumer = await readFile(new URL("../fixtures/agent-task-reusable-workflow-build-run-29295530010.yml", import.meta.url), "utf8") assert.equal(isCoherentConsumer(coherentConsumer), true, "An exact matching workflow and helper release tag must succeed") assert.equal(isCoherentConsumer(mismatchedConsumer), false, "Mismatched workflow and helper release tags must fail") +assert.match(buildRunConsumer, /github\.workflow_ref: Automattic\/wp-build\/\.github\/workflows\/build\.yml@trunk/) +assert.equal(isCoherentConsumer(buildRunConsumer), true, "A foreign caller workflow_ref must not affect the paired release tags") for (const invalidRef of ["main", "v0", "v0.12", "v0.12.3-rc.1", "0123456789abcdef0123456789abcdef01234567"]) { assert.equal(isExactReleaseTag(invalidRef), false, `Non-release ref must fail: ${invalidRef}`) } @@ -79,6 +81,7 @@ assert.match(docs, /^# Agent Task Reusable Workflow/m) assert.match(docs, /Automattic\/wp-codebox\/.github\/workflows\/run-agent-task.yml@v0\.12\.3/) assert.match(docs, /wp_codebox_release_ref: v0\.12\.3/) assert.match(docs, /branches, commit SHAs, moving major tags, prereleases, and arbitrary\nrefs are rejected/) +assert.match(docs, /GitHub nested workflows\s+expose the caller's `github\.workflow_ref`, and the running workflow cannot\s+introspect its own `uses:` ref/) assert.match(docs, /external_package_source/) assert.match(docs, /runner_workspace/) assert.match(docs, /access_token_repos/) From 856993b245d0208505f150658cb91efb06b253f0 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 13 Jul 2026 20:32:43 -0400 Subject: [PATCH 2/2] Correct Build workflow-ref regression fixture --- fixtures/agent-task-reusable-workflow-build-run-29295530010.yml | 2 +- tests/agent-task-reusable-workflow.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fixtures/agent-task-reusable-workflow-build-run-29295530010.yml b/fixtures/agent-task-reusable-workflow-build-run-29295530010.yml index 5fbff5db1..d39170841 100644 --- a/fixtures/agent-task-reusable-workflow-build-run-29295530010.yml +++ b/fixtures/agent-task-reusable-workflow-build-run-29295530010.yml @@ -1,6 +1,6 @@ # Regression fixture from Build run 29295530010. In a nested workflow GitHub # exposed this caller context, not the reusable workflow's own uses ref. -# github.workflow_ref: Automattic/wp-build/.github/workflows/build.yml@trunk +# github.workflow_ref: Automattic/build-with-wordpress/.github/workflows/build.yml@trunk name: Build consumer with foreign caller workflow ref on: workflow_dispatch diff --git a/tests/agent-task-reusable-workflow.test.ts b/tests/agent-task-reusable-workflow.test.ts index df7e2b10c..1f7c32b46 100644 --- a/tests/agent-task-reusable-workflow.test.ts +++ b/tests/agent-task-reusable-workflow.test.ts @@ -66,7 +66,7 @@ const mismatchedConsumer = await readFile(new URL("../fixtures/agent-task-reusab const buildRunConsumer = await readFile(new URL("../fixtures/agent-task-reusable-workflow-build-run-29295530010.yml", import.meta.url), "utf8") assert.equal(isCoherentConsumer(coherentConsumer), true, "An exact matching workflow and helper release tag must succeed") assert.equal(isCoherentConsumer(mismatchedConsumer), false, "Mismatched workflow and helper release tags must fail") -assert.match(buildRunConsumer, /github\.workflow_ref: Automattic\/wp-build\/\.github\/workflows\/build\.yml@trunk/) +assert.match(buildRunConsumer, /github\.workflow_ref: Automattic\/build-with-wordpress\/\.github\/workflows\/build\.yml@trunk/) assert.equal(isCoherentConsumer(buildRunConsumer), true, "A foreign caller workflow_ref must not affect the paired release tags") for (const invalidRef of ["main", "v0", "v0.12", "v0.12.3-rc.1", "0123456789abcdef0123456789abcdef01234567"]) { assert.equal(isExactReleaseTag(invalidRef), false, `Non-release ref must fail: ${invalidRef}`)