Skip to content

Commit 1acf7bd

Browse files
committed
Separate workflow helpers from release assets
1 parent 0227ca7 commit 1acf7bd

4 files changed

Lines changed: 49 additions & 25 deletions

File tree

.github/workflows/run-agent-task.yml

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ name: Run Agent Task (reusable)
77
description: Exact immutable WP Codebox release tag used for this workflow invocation, in vX.Y.Z form.
88
type: string
99
required: true
10+
wp_codebox_workflow_ref:
11+
description: Optional full commit SHA for workflow helpers; defaults to wp_codebox_release_ref.
12+
type: string
13+
default: ""
1014
external_package_source:
1115
description: "JSON descriptor for exactly one standalone .agent.json file: repository, immutable revision, file path, and sha256-bytes-v1 digest."
1216
type: string
@@ -155,6 +159,7 @@ jobs:
155159
runs-on: ubuntu-latest
156160
env:
157161
WP_CODEBOX_RELEASE_REF: ${{ inputs.wp_codebox_release_ref }}
162+
WP_CODEBOX_WORKFLOW_REF: ${{ inputs.wp_codebox_workflow_ref || inputs.wp_codebox_release_ref }}
158163
permissions:
159164
contents: write
160165
pull-requests: write
@@ -173,24 +178,31 @@ jobs:
173178
echo "wp_codebox_release_ref must be an immutable vX.Y.Z release tag" >&2
174179
exit 1
175180
fi
181+
if ! [[ "${WP_CODEBOX_WORKFLOW_REF}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$|^[0-9a-f]{40}$ ]]; then
182+
echo "wp_codebox_workflow_ref must be an immutable vX.Y.Z release tag or full commit SHA" >&2
183+
exit 1
184+
fi
176185
177186
- name: Checkout WP Codebox workflow helpers
178187
uses: actions/checkout@v4
179188
with:
180189
repository: Automattic/wp-codebox
181-
ref: ${{ inputs.wp_codebox_release_ref }}
190+
ref: ${{ inputs.wp_codebox_workflow_ref || inputs.wp_codebox_release_ref }}
182191
path: .wp-codebox-workflow
183192

184-
- name: Verify WP Codebox workflow helper release
193+
- name: Verify WP Codebox workflow helper revision
185194
working-directory: .wp-codebox-workflow
186195
run: |
187-
git ls-remote --exit-code --refs origin "refs/tags/${WP_CODEBOX_RELEASE_REF}" >/dev/null
188-
remote_tag_object="$(git ls-remote --refs origin "refs/tags/${WP_CODEBOX_RELEASE_REF}" | cut -f1)"
189-
remote_tag_commit="$(git ls-remote origin "refs/tags/${WP_CODEBOX_RELEASE_REF}^{}" | cut -f1)"
190-
remote_tag_commit="${remote_tag_commit:-${remote_tag_object}}"
191196
checked_out_commit="$(git rev-parse HEAD)"
192-
if [[ "${checked_out_commit}" != "${remote_tag_commit}" ]]; then
193-
echo "Checked-out WP Codebox helpers do not match the remote release tag commit" >&2
197+
expected_helper_commit="${WP_CODEBOX_WORKFLOW_REF}"
198+
if [[ "${WP_CODEBOX_WORKFLOW_REF}" == v* ]]; then
199+
git ls-remote --exit-code --refs origin "refs/tags/${WP_CODEBOX_WORKFLOW_REF}" >/dev/null
200+
remote_tag_object="$(git ls-remote --refs origin "refs/tags/${WP_CODEBOX_WORKFLOW_REF}" | cut -f1)"
201+
remote_tag_commit="$(git ls-remote origin "refs/tags/${WP_CODEBOX_WORKFLOW_REF}^{}" | cut -f1)"
202+
expected_helper_commit="${remote_tag_commit:-${remote_tag_object}}"
203+
fi
204+
if [[ "${checked_out_commit}" != "${expected_helper_commit}" ]]; then
205+
echo "Checked-out WP Codebox helpers do not match wp_codebox_workflow_ref" >&2
194206
exit 1
195207
fi
196208
EXPECTED_VERSION="${WP_CODEBOX_RELEASE_REF#v}" node --input-type=module -e '

contracts/run-agent-task-reusable-workflow-interface.v1.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"workflow": ".github/workflows/run-agent-task.yml",
44
"inputs": {
55
"wp_codebox_release_ref": { "required": true, "type": "string" },
6+
"wp_codebox_workflow_ref": { "required": false, "type": "string", "default": "" },
67
"external_package_source": { "required": true, "type": "string" },
78
"runtime_sources": { "required": false, "type": "string", "default": "[]" },
89
"workload_id": { "required": false, "type": "string", "default": "agent-task" },

docs/agent-task-reusable-workflow.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,22 @@ public native package, invokes the package-declared agent through the native cha
3131
in a credential-free verification environment, and returns actual runtime and
3232
publication data.
3333
34-
## Helper Release Tag
34+
## Helper Revision
3535
36-
Callers must invoke the reusable workflow from an exact WP Codebox release tag
36+
By default, callers invoke the reusable workflow from an exact WP Codebox release tag
3737
and pass that same tag as `wp_codebox_release_ref`. For example, callers use
3838
`Automattic/wp-codebox/.github/workflows/run-agent-task.yml@v0.12.3` together
3939
with `wp_codebox_release_ref: v0.12.3`. The accepted format is exactly
4040
`vX.Y.Z`; branches, commit SHAs, moving major tags, prereleases, and arbitrary
41-
refs are rejected.
42-
43-
The workflow requires `wp_codebox_release_ref` to be an exact release tag. It
44-
always checks helpers out from `Automattic/wp-codebox`, verifies the checked-out
45-
commit equals the remote release tag commit, and verifies the checked-out
46-
`package.json` version equals the requested tag without its `v` prefix. The
41+
refs are rejected for the packaged release.
42+
43+
The workflow requires `wp_codebox_release_ref` to be an exact release tag. An
44+
accepted workflow commit may also pass the same full SHA as `wp_codebox_workflow_ref`
45+
to execute helper changes independently of packaged runtime assets. When omitted,
46+
the helper ref defaults to the release tag. The workflow checks helpers out from
47+
`Automattic/wp-codebox`, verifies the checked-out commit equals the selected helper
48+
revision, and verifies the checked-out `package.json` version equals the requested
49+
release tag without its `v` prefix. The
4750
caller cannot select a different helper repository. GitHub nested workflows
4851
expose the caller's `github.workflow_ref`, and the running workflow cannot
4952
introspect its own `uses:` ref, so helper selection relies on the required input
@@ -54,6 +57,7 @@ This release-coherence contract fixes [#1759](https://github.com/Automattic/wp-c
5457
## Inputs
5558

5659
- `wp_codebox_release_ref`: required exact immutable WP Codebox release tag in `vX.Y.Z` form.
60+
- `wp_codebox_workflow_ref`: optional full immutable helper commit SHA. It defaults to `wp_codebox_release_ref` for existing callers.
5761
- `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:<lowercase-sha256>` over the raw file bytes; filenames and JSON content are UTF-8-safe and are not normalized before hashing.
5862
- `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.
5963

tests/agent-task-reusable-workflow.test.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const publicWorkflowSurface = workflow.slice(0, workflow.indexOf("jobs:"))
1515
assert.match(workflow, /^name: Run Agent Task \(reusable\)$/m)
1616
assert.match(workflow, /workflow_call:/)
1717
assert.match(workflow, /wp_codebox_release_ref:/)
18+
assert.match(workflow, /wp_codebox_workflow_ref:/)
1819
assert.match(workflow, /external_package_source:/)
1920
assert.match(workflow, /runtime_sources:/)
2021
assert.match(workflow, /EXTERNAL_PACKAGE_SOURCE_POLICY:/)
@@ -43,25 +44,29 @@ assert.match(workflow, /prepare-agent-task-upload\.mjs/)
4344
assert.match(workflow, /agent-task-upload/)
4445
assert.match(workflow, /if: always\(\)/)
4546
assert.match(workflow, /WP_CODEBOX_RELEASE_REF: \$\{\{ inputs\.wp_codebox_release_ref \}\}/)
47+
assert.match(workflow, /WP_CODEBOX_WORKFLOW_REF: \$\{\{ inputs\.wp_codebox_workflow_ref \|\| inputs\.wp_codebox_release_ref \}\}/)
4648
assert.match(workflow, /\^v\[0-9\]\+\\\.\[0-9\]\+\\\.\[0-9\]\+\$/)
47-
assert.doesNotMatch(workflow, /github\.workflow_ref|WORKFLOW_REF|expected_workflow_ref/)
49+
assert.doesNotMatch(workflow, /github\.workflow_ref|expected_workflow_ref/)
4850
assert.match(workflow, /repository: Automattic\/wp-codebox/)
49-
assert.match(workflow, /ref: \$\{\{ inputs\.wp_codebox_release_ref \}\}/)
50-
assert.match(workflow, /Verify WP Codebox workflow helper release/)
51-
assert.match(workflow, /git ls-remote --exit-code --refs origin "refs\/tags\/\$\{WP_CODEBOX_RELEASE_REF\}"/)
52-
assert.match(workflow, /checked_out_commit.*remote_tag_commit/)
51+
assert.match(workflow, /ref: \$\{\{ inputs\.wp_codebox_workflow_ref \|\| inputs\.wp_codebox_release_ref \}\}/)
52+
assert.match(workflow, /Verify WP Codebox workflow helper revision/)
53+
assert.match(workflow, /git ls-remote --exit-code --refs origin "refs\/tags\/\$\{WP_CODEBOX_WORKFLOW_REF\}"/)
54+
assert.match(workflow, /checked_out_commit.*expected_helper_commit/)
5355
assert.match(workflow, /JSON\.parse\(readFileSync\("package\.json", "utf8"\)\)\.version/)
5456
assert.doesNotMatch(workflow, /steps\.[^.]+\.outputs\.ref/)
5557
assert.match(workflow, /Validate WP Codebox release tag/)
5658

57-
const parseConsumerReleaseTags = (consumer: string) => ({
59+
const parseConsumerRefs = (consumer: string) => ({
5860
workflow: consumer.match(/uses: Automattic\/wp-codebox\/\.github\/workflows\/run-agent-task\.yml@([^\s]+)/)?.[1],
59-
helpers: consumer.match(/wp_codebox_release_ref: ([^\s]+)/)?.[1],
61+
release: consumer.match(/wp_codebox_release_ref: ([^\s]+)/)?.[1],
62+
helpers: consumer.match(/wp_codebox_workflow_ref: ([^\s]+)/)?.[1],
6063
})
6164
const isExactReleaseTag = (value: string | undefined) => /^v\d+\.\d+\.\d+$/.test(value ?? "")
65+
const isFullCommit = (value: string | undefined) => /^[0-9a-f]{40}$/.test(value ?? "")
6266
const isCoherentConsumer = (consumer: string) => {
63-
const { workflow: workflowTag, helpers: helperTag } = parseConsumerReleaseTags(consumer)
64-
return isExactReleaseTag(workflowTag) && workflowTag === helperTag
67+
const { workflow: workflowRef, release, helpers } = parseConsumerRefs(consumer)
68+
if (!isExactReleaseTag(release)) return false
69+
return helpers ? isFullCommit(workflowRef) && workflowRef === helpers : workflowRef === release
6570
}
6671
const coherentConsumer = await readFile(new URL("../fixtures/agent-task-reusable-workflow-consumer.yml", import.meta.url), "utf8")
6772
const mismatchedConsumer = await readFile(new URL("../fixtures/agent-task-reusable-workflow-consumer-mismatched.yml", import.meta.url), "utf8")
@@ -70,6 +75,8 @@ assert.equal(isCoherentConsumer(coherentConsumer), true, "An exact matching work
7075
assert.equal(isCoherentConsumer(mismatchedConsumer), false, "Mismatched workflow and helper release tags must fail")
7176
assert.match(buildRunConsumer, /github\.workflow_ref: Automattic\/build-with-wordpress\/\.github\/workflows\/build\.yml@trunk/)
7277
assert.equal(isCoherentConsumer(buildRunConsumer), true, "A foreign caller workflow_ref must not affect the paired release tags")
78+
const commit = "0123456789abcdef0123456789abcdef01234567"
79+
assert.equal(isCoherentConsumer(`uses: Automattic/wp-codebox/.github/workflows/run-agent-task.yml@${commit}\nwith:\n wp_codebox_release_ref: v0.12.29\n wp_codebox_workflow_ref: ${commit}\n`), true, "An accepted workflow commit may provide helpers independently of packaged release assets")
7380
for (const invalidRef of ["main", "v0", "v0.12", "v0.12.3-rc.1", "0123456789abcdef0123456789abcdef01234567"]) {
7481
assert.equal(isExactReleaseTag(invalidRef), false, `Non-release ref must fail: ${invalidRef}`)
7582
}

0 commit comments

Comments
 (0)