mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(release): accept trusted extended-stable validation evidence (#119264)
* fix(release): accept trusted extended-stable validation evidence * fix(release): document trusted candidate promotion * docs(release): clarify validation evidence recovery
This commit is contained in:
@@ -252,22 +252,29 @@ on pinned current `main` as the exact command and validation contract.
|
||||
and workflow validation. Run focused checks and freeze the untagged tip SHA.
|
||||
2. From that branch, run npm preflight with the SHA as `tag`,
|
||||
`preflight_only=true`, and `npm_dist_tag=extended-stable`; save the run ID.
|
||||
3. Run complete Full Release Validation from and against the canonical branch
|
||||
with `release_profile=stable`; save its run ID and successful `run_attempt`.
|
||||
Any branch change invalidates both gates.
|
||||
3. Run complete Full Release Validation against the canonical branch with
|
||||
`release_profile=stable`; save its run ID and successful `run_attempt`.
|
||||
Prefer the trusted main-pinned harness, which attests the immutable target
|
||||
SHA in its v3 manifest. Any candidate branch change invalidates both gates.
|
||||
4. Require the tip still equals the frozen SHA, then create signed `vYYYY.M.P`.
|
||||
Never move or delete a final tag; later source changes need a new patch.
|
||||
5. Require the saved validation run to be complete, successful, and bound to
|
||||
the canonical branch, tag SHA, and attempt. Reject `release-ci/*` and narrow
|
||||
reruns.
|
||||
5. Require the saved validation run to be complete and successful, bind its
|
||||
manifest target SHA and attempt to the tag, and accept a direct run from the
|
||||
canonical branch, a direct current-`main` run whose workflow SHA is still
|
||||
reachable from main, or a trusted main-pinned `release-ci/*` harness. Reject
|
||||
narrow reruns.
|
||||
6. Dispatch `plugin-npm-release.yml` from the same branch with
|
||||
`publish_scope=all-publishable`, the full release SHA as `ref`, and
|
||||
`npm_dist_tag=extended-stable`. Require complete exact-version and selector
|
||||
readback, then save the successful plugin run ID.
|
||||
7. Publish core from the same branch with the tag, `npm_dist_tag=extended-stable`,
|
||||
all three run IDs, and
|
||||
`full_release_validation_run_attempt=<saved-attempt>`. Require the prepared
|
||||
tarball and every run to match the branch and release SHA.
|
||||
7. Publish core with the tag, `npm_dist_tag=extended-stable`, all three run IDs,
|
||||
and `full_release_validation_run_attempt=<saved-attempt>`. Normally dispatch
|
||||
from the canonical branch. For a workflow-only recovery after the candidate
|
||||
is immutable, dispatch trusted current `main` with
|
||||
`release_candidate_branch=extended-stable/YYYY.M.33`; it still publishes the
|
||||
tag checkout and accepts canonical-branch, current-main, or trusted-pinned
|
||||
validation evidence; the prepared tarball and every evidence identity must
|
||||
still match the candidate SHA.
|
||||
8. From a clean current-`main` checkout, run
|
||||
`node --import tsx scripts/openclaw-npm-postpublish-verify.ts YYYY.M.P`.
|
||||
Verify signatures, provenance, inventories, exact versions, and selectors.
|
||||
|
||||
@@ -32,6 +32,11 @@ on:
|
||||
description: Successful Plugin NPM Release run id for the exact extended-stable branch and release SHA
|
||||
required: false
|
||||
type: string
|
||||
release_candidate_branch:
|
||||
description: Canonical extended-stable branch when a trusted main workflow promotes its immutable tag
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
npm_dist_tag:
|
||||
description: npm dist-tag to publish to
|
||||
required: true
|
||||
@@ -709,6 +714,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
ref: ${{ inputs.release_candidate_branch != '' && format('refs/tags/{0}', inputs.tag) || github.sha }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
persist-credentials: false
|
||||
@@ -718,13 +724,14 @@ jobs:
|
||||
BYPASS_EXTENDED_STABLE_GUARD: ${{ inputs.bypass_extended_stable_guard }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
NPM_WORKFLOW_REF: ${{ github.ref }}
|
||||
NPM_WORKFLOW_REF: ${{ inputs.release_candidate_branch != '' && format('refs/heads/{0}', inputs.release_candidate_branch) || github.ref }}
|
||||
run: node scripts/openclaw-npm-extended-stable-release.mjs validate-request
|
||||
|
||||
- name: Require trusted workflow ref for publish
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
RELEASE_CANDIDATE_BRANCH: ${{ inputs.release_candidate_branch }}
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
WORKFLOW_SHA: ${{ github.workflow_sha }}
|
||||
run: |
|
||||
@@ -732,12 +739,29 @@ jobs:
|
||||
tideclaw_alpha_publish=false
|
||||
extended_stable_publish=false
|
||||
sha_pinned_release_publish=false
|
||||
release_candidate_branch="${RELEASE_CANDIDATE_BRANCH:-}"
|
||||
if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha" && "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
|
||||
tideclaw_alpha_publish=true
|
||||
fi
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" && "${WORKFLOW_REF}" == refs/heads/extended-stable/* ]]; then
|
||||
extended_stable_publish=true
|
||||
fi
|
||||
if [[ -n "${release_candidate_branch}" ]]; then
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" != "extended-stable" || "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
|
||||
echo "release_candidate_branch is only valid for an extended-stable publish dispatched from main." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "${RELEASE_TAG}" =~ ^v([0-9]{4})\.([1-9][0-9]*)\.[1-9][0-9]*$ ]]; then
|
||||
echo "release_candidate_branch requires an exact final release tag." >&2
|
||||
exit 1
|
||||
fi
|
||||
expected_candidate_branch="extended-stable/${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.33"
|
||||
if [[ "${release_candidate_branch}" != "${expected_candidate_branch}" ]]; then
|
||||
echo "release_candidate_branch must be ${expected_candidate_branch} for ${RELEASE_TAG}." >&2
|
||||
exit 1
|
||||
fi
|
||||
extended_stable_publish=true
|
||||
fi
|
||||
if [[ "${WORKFLOW_REF}" =~ ^refs/tags/release-publish/([a-f0-9]{12})-[1-9][0-9]*$ ]]; then
|
||||
workflow_sha_prefix="${BASH_REMATCH[1]}"
|
||||
[[ "${WORKFLOW_SHA}" =~ ^[a-f0-9]{40}$ && "${WORKFLOW_SHA:0:12}" == "${workflow_sha_prefix}" ]] || {
|
||||
@@ -857,6 +881,16 @@ jobs:
|
||||
# tarball, so real publishes do not need the repository's full history.
|
||||
fetch-depth: ${{ inputs.preflight_run_id != '' && 1 || 0 }}
|
||||
|
||||
- name: Checkout trusted validation verifier
|
||||
if: ${{ inputs.full_release_validation_run_id != '' }}
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
ref: ${{ github.workflow_sha }}
|
||||
path: trusted-workflow
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate Tideclaw alpha publish target
|
||||
if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/')
|
||||
env:
|
||||
@@ -902,7 +936,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
EXPECTED_EXTENDED_STABLE_BRANCH: ${{ github.ref_name }}
|
||||
EXPECTED_EXTENDED_STABLE_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }}
|
||||
RUN_KIND: preflight
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -911,37 +945,32 @@ jobs:
|
||||
RUN_JSON="$(gh run view "$PREFLIGHT_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,headSha,event,conclusion,url)"
|
||||
printf '%s' "$RUN_JSON" | node scripts/openclaw-npm-extended-stable-release.mjs verify-run
|
||||
|
||||
- name: Verify full release validation run metadata
|
||||
id: full_run
|
||||
- name: Download full release validation manifest
|
||||
if: ${{ inputs.full_release_validation_run_id != '' }}
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ inputs.full_release_validation_run_attempt }}
|
||||
path: full-release-validation
|
||||
repository: ${{ github.repository }}
|
||||
run-id: ${{ inputs.full_release_validation_run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Verify full release validation evidence
|
||||
if: ${{ inputs.full_release_validation_run_id != '' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
|
||||
FULL_RELEASE_VALIDATION_RUN_ATTEMPT: ${{ inputs.full_release_validation_run_attempt }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
EXPECTED_EXTENDED_STABLE_BRANCH: ${{ github.ref_name }}
|
||||
RUN_KIND: validation
|
||||
EXPECTED_WORKFLOW_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }}
|
||||
STRICT_VALIDATOR_FILE: ${{ github.workspace }}/trusted-workflow/scripts/release-ci-summary.mjs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"
|
||||
export EXPECTED_RELEASE_SHA
|
||||
run_file="${RUNNER_TEMP}/full-release-validation-run.json"
|
||||
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" > "$run_file"
|
||||
jq '{
|
||||
workflowName: .name,
|
||||
headBranch: .head_branch,
|
||||
headSha: .head_sha,
|
||||
event,
|
||||
status,
|
||||
conclusion,
|
||||
url: .html_url
|
||||
}' "$run_file" | node scripts/openclaw-npm-extended-stable-release.mjs verify-run
|
||||
run_attempt="$(jq -r '.run_attempt // ""' "$run_file")"
|
||||
if [[ "$run_attempt" != "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT" ]]; then
|
||||
echo "Full Release Validation run ${FULL_RELEASE_VALIDATION_RUN_ID} attempt mismatch: expected ${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}, got ${run_attempt:-<missing>}." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "attempt=$run_attempt" >> "$GITHUB_OUTPUT"
|
||||
EXPECTED_SHA="$(git rev-parse HEAD)"
|
||||
MANIFEST_FILE="full-release-validation/full-release-validation-manifest.json"
|
||||
export EXPECTED_SHA MANIFEST_FILE
|
||||
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" | \
|
||||
node trusted-workflow/scripts/validate-full-release-validation-evidence.mjs
|
||||
|
||||
- name: Verify plugin npm release run metadata
|
||||
if: ${{ inputs.npm_dist_tag == 'extended-stable' }}
|
||||
@@ -949,7 +978,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PLUGIN_NPM_RUN_ID: ${{ inputs.plugin_npm_run_id }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
EXPECTED_EXTENDED_STABLE_BRANCH: ${{ github.ref_name }}
|
||||
EXPECTED_EXTENDED_STABLE_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }}
|
||||
RUN_KIND: plugin
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1011,16 +1040,6 @@ jobs:
|
||||
|
||||
download_preflight_artifact
|
||||
|
||||
- name: Download full release validation manifest
|
||||
if: ${{ inputs.full_release_validation_run_id != '' }}
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ steps.full_run.outputs.attempt }}
|
||||
path: full-release-validation
|
||||
repository: ${{ github.repository }}
|
||||
run-id: ${{ inputs.full_release_validation_run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Validate release tag and package metadata
|
||||
if: ${{ inputs.preflight_run_id == '' }}
|
||||
env:
|
||||
@@ -1167,20 +1186,14 @@ jobs:
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
EXPECTED_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
|
||||
FULL_RELEASE_VALIDATION_RUN_ATTEMPT: ${{ steps.full_run.outputs.attempt }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"
|
||||
MANIFEST_FILE="full-release-validation/full-release-validation-manifest.json"
|
||||
if [[ ! -f "$MANIFEST_FILE" ]]; then
|
||||
echo "Full release validation manifest is missing." >&2
|
||||
ls -la full-release-validation >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
export EXPECTED_RELEASE_SHA MANIFEST_FILE
|
||||
node scripts/openclaw-npm-extended-stable-release.mjs verify-manifest
|
||||
RERUN_GROUP="$(jq -r '.rerunGroup // ""' "$MANIFEST_FILE")"
|
||||
RUN_RELEASE_SOAK="$(jq -r '.runReleaseSoak // ""' "$MANIFEST_FILE")"
|
||||
PERFORMANCE_BLOCKING="$(jq -r '.controls.performanceBlocking // false' "$MANIFEST_FILE")"
|
||||
@@ -1202,7 +1215,7 @@ jobs:
|
||||
BYPASS_EXTENDED_STABLE_GUARD: ${{ inputs.bypass_extended_stable_guard }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
NPM_WORKFLOW_REF: ${{ github.ref }}
|
||||
NPM_WORKFLOW_REF: ${{ inputs.release_candidate_branch != '' && format('refs/heads/{0}', inputs.release_candidate_branch) || github.ref }}
|
||||
run: node scripts/openclaw-npm-extended-stable-release.mjs validate-request
|
||||
|
||||
- name: Capture previous extended-stable selector
|
||||
@@ -1255,8 +1268,7 @@ jobs:
|
||||
env:
|
||||
BYPASS_EXTENDED_STABLE_GUARD: ${{ inputs.bypass_extended_stable_guard }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_SHA: ${{ github.sha }}
|
||||
EXTENDED_STABLE_BRANCH: ${{ github.ref_name }}
|
||||
EXTENDED_STABLE_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
|
||||
TARBALL_NAME: ${{ steps.preflight_provenance.outputs.tarball_name }}
|
||||
@@ -1270,13 +1282,14 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
release_version="${RELEASE_TAG#v}"
|
||||
release_sha="$(git rev-parse HEAD)"
|
||||
{
|
||||
echo "## npm extended-stable publication"
|
||||
echo "- Package: openclaw@${release_version}"
|
||||
echo "- Extended-stable guard bypass: ${BYPASS_EXTENDED_STABLE_GUARD}"
|
||||
echo "- Extended-stable branch: ${EXTENDED_STABLE_BRANCH}"
|
||||
echo "- Release tag: ${RELEASE_TAG}"
|
||||
echo "- Release SHA: ${RELEASE_SHA}"
|
||||
echo "- Release SHA: ${release_sha}"
|
||||
echo "- npm preflight run: ${PREFLIGHT_RUN_ID}"
|
||||
echo "- Full Release Validation run: ${FULL_RELEASE_VALIDATION_RUN_ID}"
|
||||
echo "- Tarball: ${TARBALL_NAME:-unavailable}"
|
||||
|
||||
@@ -137,6 +137,32 @@ gh workflow run openclaw-npm-release.yml \
|
||||
-f plugin_npm_run_id=<plugin-npm-run-id>
|
||||
```
|
||||
|
||||
If the immutable candidate has already passed its saved preflight and Full
|
||||
Release Validation but core publication needs a workflow-only recovery, dispatch
|
||||
the trusted current-`main` workflow instead. Keep the same tag and evidence
|
||||
identities; do not move the tag or republish plugins:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-npm-release.yml \
|
||||
--ref main \
|
||||
-f tag=vYYYY.M.P \
|
||||
-f preflight_only=false \
|
||||
-f npm_dist_tag=extended-stable \
|
||||
-f release_candidate_branch=extended-stable/YYYY.M.33 \
|
||||
-f preflight_run_id=<npm-preflight-run-id> \
|
||||
-f full_release_validation_run_id=<full-validation-run-id> \
|
||||
-f full_release_validation_run_attempt=<full-validation-run-attempt> \
|
||||
-f plugin_npm_run_id=<plugin-npm-run-id>
|
||||
```
|
||||
|
||||
This recovery path checks out and publishes the immutable tag and requires the
|
||||
canonical branch implied by that tag. It accepts Full Release Validation
|
||||
evidence from the canonical candidate branch directly, from current `main`
|
||||
directly when its workflow SHA is reachable from current `main`, or from the
|
||||
trusted main-pinned harness. Every accepted form must attest the immutable
|
||||
tag's SHA. Use it only when the candidate source and recorded evidence are
|
||||
unchanged.
|
||||
|
||||
For non-production rehearsal only, add
|
||||
`-f bypass_extended_stable_guard=true` to preflight and publish. It bypasses the
|
||||
month guard only, never canonical-ref, SHA/tag/version equality, provenance,
|
||||
|
||||
@@ -21,6 +21,7 @@ type Workflow = {
|
||||
bypass_extended_stable_guard?: { default?: boolean; type?: string };
|
||||
npm_dist_tag?: { options?: string[] };
|
||||
plugin_npm_run_id?: { required?: boolean; type?: string };
|
||||
release_candidate_branch?: { default?: string; required?: boolean; type?: string };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -43,7 +44,7 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
it("bounds every git fetch operation", () => {
|
||||
const source = readFileSync(workflowPath, "utf8");
|
||||
const gitFetchLines = source.split("\n").filter((line) => line.includes("git fetch"));
|
||||
expect(gitFetchLines).toHaveLength(6);
|
||||
expect(gitFetchLines).toHaveLength(7);
|
||||
expect(
|
||||
gitFetchLines.every((line) => line.includes("timeout --signal=TERM --kill-after=10s 120s")),
|
||||
).toBe(true);
|
||||
@@ -128,6 +129,41 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
expect(summary.run).toContain("Extended-stable guard bypass: ${BYPASS_EXTENDED_STABLE_GUARD}");
|
||||
});
|
||||
|
||||
it("lets main promote only the canonical immutable extended-stable candidate", () => {
|
||||
const parsed = workflow();
|
||||
const releaseDocs = readFileSync("docs/reference/RELEASING.md", "utf8");
|
||||
const input = parsed.on?.workflow_dispatch?.inputs?.release_candidate_branch;
|
||||
expect(input).toMatchObject({ default: "", required: false, type: "string" });
|
||||
|
||||
const validate = step(parsed.jobs?.validate_publish_request, "Validate npm release request");
|
||||
expect(validate.env?.NPM_WORKFLOW_REF).toBe(
|
||||
"${{ inputs.release_candidate_branch != '' && format('refs/heads/{0}', inputs.release_candidate_branch) || github.ref }}",
|
||||
);
|
||||
const checkout = step(parsed.jobs?.validate_publish_request, "Checkout");
|
||||
expect(checkout.with?.ref).toBe(
|
||||
"${{ inputs.release_candidate_branch != '' && format('refs/tags/{0}', inputs.tag) || github.sha }}",
|
||||
);
|
||||
|
||||
const trustedRef = step(
|
||||
parsed.jobs?.validate_publish_request,
|
||||
"Require trusted workflow ref for publish",
|
||||
);
|
||||
expect(trustedRef.env?.RELEASE_CANDIDATE_BRANCH).toBe("${{ inputs.release_candidate_branch }}");
|
||||
expect(trustedRef.run).toContain('release_candidate_branch="${RELEASE_CANDIDATE_BRANCH:-}"');
|
||||
expect(trustedRef.run).toContain('"${WORKFLOW_REF}" != "refs/heads/main"');
|
||||
expect(trustedRef.run).toContain(
|
||||
'expected_candidate_branch="extended-stable/${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.33"',
|
||||
);
|
||||
|
||||
const recheck = step(parsed.jobs?.publish_openclaw_npm, "Recheck npm release request");
|
||||
expect(recheck.env?.NPM_WORKFLOW_REF).toBe(validate.env?.NPM_WORKFLOW_REF);
|
||||
expect(releaseDocs).toContain("--ref main");
|
||||
expect(releaseDocs).toContain("-f release_candidate_branch=extended-stable/YYYY.M.33");
|
||||
expect(releaseDocs).toContain("canonical candidate branch directly");
|
||||
expect(releaseDocs).toContain("workflow SHA is reachable from current `main`");
|
||||
expect(releaseDocs).toContain("trusted main-pinned harness");
|
||||
});
|
||||
|
||||
it("accepts arbitrary SHA preflight targets and exercises every publishable plugin package", () => {
|
||||
const parsed = workflow();
|
||||
const preflight = parsed.jobs?.preflight_openclaw_npm;
|
||||
@@ -192,26 +228,35 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
expect(save.with?.key).toBe("${{ steps.dist_build_cache.outputs.cache-primary-key }}");
|
||||
});
|
||||
|
||||
it("authenticates exact extended-stable run and Full Validation identities", () => {
|
||||
it("uses the trusted Full Validation evidence verifier", () => {
|
||||
const parsed = workflow();
|
||||
const raw = readFileSync(workflowPath, "utf8");
|
||||
expect(raw).toContain("--json workflowName,headBranch,headSha,event,conclusion,url");
|
||||
const fullValidationRun = step(
|
||||
const verifier = step(
|
||||
parsed.jobs?.publish_openclaw_npm,
|
||||
"Verify full release validation run metadata",
|
||||
"Checkout trusted validation verifier",
|
||||
);
|
||||
expect(fullValidationRun.env?.FULL_RELEASE_VALIDATION_RUN_ATTEMPT).toBe(
|
||||
expect(verifier.with?.ref).toBe("${{ github.workflow_sha }}");
|
||||
expect(verifier.with?.path).toBe("trusted-workflow");
|
||||
|
||||
const fullValidation = step(
|
||||
parsed.jobs?.publish_openclaw_npm,
|
||||
"Verify full release validation evidence",
|
||||
);
|
||||
expect(fullValidation.env?.EXPECTED_WORKFLOW_BRANCH).toBe(
|
||||
"${{ inputs.release_candidate_branch || github.ref_name }}",
|
||||
);
|
||||
expect(fullValidation.env?.FULL_RELEASE_VALIDATION_RUN_ATTEMPT).toBe(
|
||||
"${{ inputs.full_release_validation_run_attempt }}",
|
||||
);
|
||||
expect(fullValidationRun.run).toContain(
|
||||
expect(fullValidation.run).toContain(
|
||||
"actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}",
|
||||
);
|
||||
expect(fullValidationRun.run).toContain(
|
||||
'"$run_attempt" != "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT"',
|
||||
expect(fullValidation.run).toContain(
|
||||
"trusted-workflow/scripts/validate-full-release-validation-evidence.mjs",
|
||||
);
|
||||
expect(fullValidationRun.run).toContain('echo "attempt=$run_attempt" >> "$GITHUB_OUTPUT"');
|
||||
expect(raw.match(/openclaw-npm-extended-stable-release\.mjs verify-run/g)).toHaveLength(3);
|
||||
expect(raw).toContain("openclaw-npm-extended-stable-release.mjs verify-manifest");
|
||||
expect(raw.match(/openclaw-npm-extended-stable-release\.mjs verify-run/g)).toHaveLength(2);
|
||||
expect(raw).not.toContain("openclaw-npm-extended-stable-release.mjs verify-manifest");
|
||||
});
|
||||
|
||||
it("requires and authenticates the plugin npm run before an extended-stable core publish", () => {
|
||||
@@ -250,6 +295,11 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
expect(summary.if).toContain("always()");
|
||||
expect(summary.run).toContain("openclaw-npm-extended-stable-release.mjs repair-command");
|
||||
expect(summary.run).toContain('EXPECTED_VERSION="$RELEASE_TAG"');
|
||||
expect(summary.env?.EXTENDED_STABLE_BRANCH).toBe(
|
||||
"${{ inputs.release_candidate_branch || github.ref_name }}",
|
||||
);
|
||||
expect(summary.env?.RELEASE_SHA).toBeUndefined();
|
||||
expect(summary.run).toContain('release_sha="$(git rev-parse HEAD)"');
|
||||
expect(publish?.environment).toBe("npm-release");
|
||||
});
|
||||
|
||||
|
||||
@@ -4215,7 +4215,7 @@ describe("package artifact reuse", () => {
|
||||
"Download full release validation manifest",
|
||||
);
|
||||
const npmPublishJob = workflowJob(OPENCLAW_NPM_RELEASE_WORKFLOW, "publish_openclaw_npm");
|
||||
const npmRun = workflowStep(npmPublishJob, "Verify full release validation run metadata");
|
||||
const npmRun = workflowStep(npmPublishJob, "Verify full release validation evidence");
|
||||
const npmManifest = workflowStep(npmPublishJob, "Download full release validation manifest");
|
||||
|
||||
expect(releaseRun).toMatchObject({
|
||||
@@ -4236,18 +4236,15 @@ describe("package artifact reuse", () => {
|
||||
"run-id": "${{ inputs.full_release_validation_run_id }}",
|
||||
});
|
||||
|
||||
expect(npmRun).toMatchObject({
|
||||
id: "full_run",
|
||||
env: {
|
||||
FULL_RELEASE_VALIDATION_RUN_ID: "${{ inputs.full_release_validation_run_id }}",
|
||||
FULL_RELEASE_VALIDATION_RUN_ATTEMPT: "${{ inputs.full_release_validation_run_attempt }}",
|
||||
},
|
||||
expect(npmRun.env).toMatchObject({
|
||||
FULL_RELEASE_VALIDATION_RUN_ID: "${{ inputs.full_release_validation_run_id }}",
|
||||
FULL_RELEASE_VALIDATION_RUN_ATTEMPT: "${{ inputs.full_release_validation_run_attempt }}",
|
||||
});
|
||||
expect(npmRun.run).toContain(
|
||||
"actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}",
|
||||
);
|
||||
expect(npmManifest.with).toMatchObject({
|
||||
name: "full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ steps.full_run.outputs.attempt }}",
|
||||
name: "full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ inputs.full_release_validation_run_attempt }}",
|
||||
"run-id": "${{ inputs.full_release_validation_run_id }}",
|
||||
});
|
||||
});
|
||||
@@ -4263,7 +4260,7 @@ describe("package artifact reuse", () => {
|
||||
const publishOrchestration = workflowStep(publishJob, "Dispatch publish workflows");
|
||||
const npmPublishJob = workflowJob(OPENCLAW_NPM_RELEASE_WORKFLOW, "publish_openclaw_npm");
|
||||
const npmCheckout = workflowStep(npmPublishJob, "Checkout");
|
||||
const npmFullRun = workflowStep(npmPublishJob, "Verify full release validation run metadata");
|
||||
const npmFullRun = workflowStep(npmPublishJob, "Verify full release validation evidence");
|
||||
const npmDownload = workflowStep(npmPublishJob, "Download full release validation manifest");
|
||||
const npmTarget = workflowStep(npmPublishJob, "Verify full release validation target");
|
||||
|
||||
@@ -4295,18 +4292,14 @@ describe("package artifact reuse", () => {
|
||||
"${{ needs.resolve_release_target.outputs.full_release_validation_run_attempt }}",
|
||||
);
|
||||
expect(publishOrchestration.run).toContain('"${validation_target_sha}" != "${TARGET_SHA}"');
|
||||
expect(npmFullRun.id).toBe("full_run");
|
||||
expect(npmFullRun.env?.FULL_RELEASE_VALIDATION_RUN_ATTEMPT).toBe(
|
||||
"${{ inputs.full_release_validation_run_attempt }}",
|
||||
);
|
||||
expect(npmDownload.with?.name).toBe(
|
||||
"full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ steps.full_run.outputs.attempt }}",
|
||||
"full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ inputs.full_release_validation_run_attempt }}",
|
||||
);
|
||||
expect(npmTarget.env).toMatchObject({
|
||||
FULL_RELEASE_VALIDATION_RUN_ID: "${{ inputs.full_release_validation_run_id }}",
|
||||
FULL_RELEASE_VALIDATION_RUN_ATTEMPT: "${{ steps.full_run.outputs.attempt }}",
|
||||
});
|
||||
expect(npmTarget.run).toContain(
|
||||
expect(npmTarget.env?.FULL_RELEASE_VALIDATION_RUN_ID).toBeUndefined();
|
||||
expect(npmTarget.run).not.toContain(
|
||||
"node scripts/openclaw-npm-extended-stable-release.mjs verify-manifest",
|
||||
);
|
||||
expect(npmCheckout.with?.["fetch-depth"]).toBe(
|
||||
|
||||
Reference in New Issue
Block a user