Skip to content

Commit 2198c74

Browse files
committed
refactor(release): scope stable foundation to npm
1 parent 5c8c299 commit 2198c74

10 files changed

Lines changed: 117 additions & 2036 deletions

.github/workflows/macos-release.yml

Lines changed: 4 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,10 @@ on:
1313
default: true
1414
type: boolean
1515
public_release_branch:
16-
description: Public branch that contains the release tag commit; main, release/YYYY.M.PATCH, stable/YYYY.M.33, or tideclaw/alpha/YYYY-MM-DD-HHMMZ
16+
description: Public branch that contains the release tag commit, usually main or release/YYYY.M.PATCH
1717
required: false
1818
default: main
1919
type: string
20-
release_sha:
21-
description: Exact 40-character release tag commit authenticated by npm preflight evidence
22-
required: true
23-
type: string
24-
verifier_run_id:
25-
description: Completed npm preflight verifier producer run id
26-
required: true
27-
type: string
28-
verifier_run_attempt:
29-
description: Completed npm preflight verifier producer run attempt
30-
required: true
31-
type: string
32-
verifier_artifact_name:
33-
description: Attempt-qualified tag-preflight verifier artifact name
34-
required: true
35-
type: string
36-
verifier_payload_sha256:
37-
description: SHA-256 of the exact tag-preflight verifier payload bytes
38-
required: true
39-
type: string
4020

4121
concurrency:
4222
group: macos-release-${{ inputs.tag }}
@@ -50,112 +30,8 @@ jobs:
5030
validate_macos_release_request:
5131
runs-on: ubuntu-latest
5232
permissions:
53-
actions: read
5433
contents: read
5534
steps:
56-
- name: Authenticate npm tag-preflight verifier evidence
57-
env:
58-
GH_TOKEN: ${{ github.token }}
59-
RELEASE_TAG: ${{ inputs.tag }}
60-
RELEASE_SHA: ${{ inputs.release_sha }}
61-
PUBLIC_RELEASE_BRANCH: ${{ inputs.public_release_branch }}
62-
VERIFIER_RUN_ID: ${{ inputs.verifier_run_id }}
63-
VERIFIER_RUN_ATTEMPT: ${{ inputs.verifier_run_attempt }}
64-
VERIFIER_ARTIFACT_NAME: ${{ inputs.verifier_artifact_name }}
65-
VERIFIER_PAYLOAD_SHA256: ${{ inputs.verifier_payload_sha256 }}
66-
run: |
67-
set -euo pipefail
68-
if [[ ! "$RELEASE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
69-
echo "release_sha must be 40 lowercase hexadecimal characters." >&2
70-
exit 1
71-
fi
72-
if [[ ! "$VERIFIER_RUN_ID" =~ ^[1-9][0-9]*$ || ! "$VERIFIER_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then
73-
echo "Verifier producer run id and attempt must be positive decimal strings." >&2
74-
exit 1
75-
fi
76-
if [[ ! "$VERIFIER_PAYLOAD_SHA256" =~ ^[0-9a-f]{64}$ ]]; then
77-
echo "Verifier payload digest must be 64 lowercase hexadecimal characters." >&2
78-
exit 1
79-
fi
80-
if [[ "$PUBLIC_RELEASE_BRANCH" != "main" && ! "$PUBLIC_RELEASE_BRANCH" =~ ^release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ && ! "$PUBLIC_RELEASE_BRANCH" =~ ^stable/[0-9]{4}\.[1-9][0-9]*\.33$ && ! "$PUBLIC_RELEASE_BRANCH" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
81-
echo "public_release_branch must be main, release/YYYY.M.PATCH, stable/YYYY.M.33, or tideclaw/alpha/YYYY-MM-DD-HHMMZ, got ${PUBLIC_RELEASE_BRANCH}." >&2
82-
exit 1
83-
fi
84-
85-
evidence_root="$RUNNER_TEMP/openclaw-macos-tag-preflight"
86-
rm -rf "$evidence_root"
87-
mkdir -p "$evidence_root/payload"
88-
producer_json="$evidence_root/producer.json"
89-
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${VERIFIER_RUN_ID}" > "$producer_json"
90-
jq -e \
91-
--arg id "$VERIFIER_RUN_ID" \
92-
--arg attempt "$VERIFIER_RUN_ATTEMPT" \
93-
--arg repository "$GITHUB_REPOSITORY" \
94-
'(.id | tostring) == $id and
95-
(.run_attempt | tostring) == $attempt and
96-
.repository.full_name == $repository and
97-
.event == "workflow_dispatch" and
98-
.path == ".github/workflows/openclaw-npm-release.yml" and
99-
.status == "completed" and
100-
.conclusion == "success"' \
101-
"$producer_json" >/dev/null
102-
103-
artifacts_json="$evidence_root/artifacts.json"
104-
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${VERIFIER_RUN_ID}/artifacts?per_page=100" > "$artifacts_json"
105-
artifact_count="$(jq --arg name "$VERIFIER_ARTIFACT_NAME" '[.artifacts[] | select(.name == $name and .expired == false)] | length' "$artifacts_json")"
106-
if [[ "$artifact_count" != "1" ]]; then
107-
echo "Expected exactly one unexpired verifier artifact named ${VERIFIER_ARTIFACT_NAME}; found ${artifact_count}." >&2
108-
exit 1
109-
fi
110-
gh run download "$VERIFIER_RUN_ID" --repo "$GITHUB_REPOSITORY" \
111-
--name "$VERIFIER_ARTIFACT_NAME" --dir "$evidence_root/payload"
112-
mapfile -t payload_files < <(find "$evidence_root/payload" -type f -print)
113-
verifier_payload="$evidence_root/payload/release-operation-verifier-v1.json"
114-
if [[ "${#payload_files[@]}" != "1" || "${payload_files[0]}" != "$verifier_payload" ]]; then
115-
echo "Verifier artifact must contain exactly one release-operation-verifier-v1.json and no extra payload." >&2
116-
exit 1
117-
fi
118-
if [[ "$(sha256sum "$verifier_payload" | awk '{print $1}')" != "$VERIFIER_PAYLOAD_SHA256" ]]; then
119-
echo "Verifier payload digest mismatch." >&2
120-
exit 1
121-
fi
122-
123-
PRODUCER_JSON="$producer_json" VERIFIER_PAYLOAD="$verifier_payload" node <<'NODE'
124-
const { readFileSync } = require("node:fs");
125-
const producer = JSON.parse(readFileSync(process.env.PRODUCER_JSON, "utf8"));
126-
const payload = JSON.parse(readFileSync(process.env.VERIFIER_PAYLOAD, "utf8"));
127-
const fail = (message) => { throw new Error(message); };
128-
const closed = (value, keys, label) => {
129-
if (value === null || typeof value !== "object" || Array.isArray(value)) fail(`${label} must be an object`);
130-
const actual = Object.keys(value).sort();
131-
const expected = [...keys].sort();
132-
if (JSON.stringify(actual) !== JSON.stringify(expected)) fail(`${label} has unknown or missing fields`);
133-
};
134-
const sha = /^[0-9a-f]{40}$/;
135-
const digest = /^[0-9a-f]{64}$/;
136-
closed(payload, ["schemaVersion", "ok", "operation", "releaseVersion", "releaseClass", "releaseSelector", "policyMode", "policySource", "execution", "target"], "verifier payload");
137-
closed(payload.policySource, ["sha", "blobs"], "policySource");
138-
closed(payload.policySource.blobs, ["releaseVersionPolicySha256", "stableReleaseLinesModuleSha256", "verifyReleaseOperationSha256", "stableLinesSha256"], "policySource.blobs");
139-
closed(payload.execution, ["event", "workflowPath", "executionRef", "runHeadSha", "runId", "runAttempt"], "execution");
140-
closed(payload.target, ["targetRef", "targetSha", "releaseTag", "authorizedSourceRef", "authorizedSourceTipSha", "targetReachableFromAuthorizedSource"], "target");
141-
if (payload.schemaVersion !== 1 || payload.ok !== true || payload.operation !== "tag-preflight") fail("expected successful tag-preflight verifier v1 evidence");
142-
if (payload.policyMode === "legacy") {
143-
if (payload.releaseSelector !== null) fail("legacy verifier evidence must use a null selector");
144-
} else if (payload.policyMode !== "strict" || !["alpha", "beta", "daily", "stable"].includes(payload.releaseSelector)) {
145-
fail("invalid verifier selector policy");
146-
}
147-
const selectorClass = payload.releaseClass === "stable-base" || payload.releaseClass === "stable-patch" ? "stable" : payload.releaseClass;
148-
if (payload.policyMode === "strict" && selectorClass !== payload.releaseSelector) fail("verifier selector and release class mismatch");
149-
if (!sha.test(payload.policySource.sha) || !digest.test(payload.policySource.blobs.releaseVersionPolicySha256) || !digest.test(payload.policySource.blobs.stableReleaseLinesModuleSha256) || !digest.test(payload.policySource.blobs.verifyReleaseOperationSha256)) fail("invalid verifier policy source digests");
150-
const stableDigest = payload.policySource.blobs.stableLinesSha256;
151-
if ((selectorClass === "stable" && !digest.test(stableDigest)) || (selectorClass !== "stable" && stableDigest !== null)) fail("stable-lines digest applicability mismatch");
152-
const expectedSourceRef = `refs/heads/${process.env.PUBLIC_RELEASE_BRANCH}`;
153-
if (payload.execution.event !== "workflow_dispatch" || payload.execution.workflowPath !== ".github/workflows/openclaw-npm-release.yml" || payload.execution.runId !== process.env.VERIFIER_RUN_ID || payload.execution.runAttempt !== process.env.VERIFIER_RUN_ATTEMPT || payload.execution.runHeadSha !== producer.head_sha || payload.execution.executionRef !== expectedSourceRef) fail("verifier execution does not equal authenticated npm preflight run");
154-
if (payload.releaseVersion !== process.env.RELEASE_TAG.slice(1) || payload.target.targetRef !== `refs/tags/${process.env.RELEASE_TAG}` || payload.target.targetSha !== process.env.RELEASE_SHA || payload.target.releaseTag !== process.env.RELEASE_TAG || payload.target.authorizedSourceRef !== expectedSourceRef || !sha.test(payload.target.authorizedSourceTipSha) || payload.target.targetReachableFromAuthorizedSource !== true) fail("tag-preflight target does not equal macOS dispatch target");
155-
const expectedArtifact = `release-operation-verifier-v1-tag-preflight-${payload.releaseVersion}-${process.env.VERIFIER_RUN_ID}-${process.env.VERIFIER_RUN_ATTEMPT}`;
156-
if (process.env.VERIFIER_ARTIFACT_NAME !== expectedArtifact) fail("verifier artifact name is not attempt-qualified for the payload");
157-
NODE
158-
15935
- name: Validate tag input format
16036
env:
16137
RELEASE_TAG: ${{ inputs.tag }}
@@ -169,22 +45,9 @@ jobs:
16945
- name: Checkout selected tag
17046
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
17147
with:
172-
ref: ${{ inputs.release_sha }}
48+
ref: refs/tags/${{ inputs.tag }}
17349
fetch-depth: 0
17450

175-
- name: Verify release tag still resolves to authenticated SHA
176-
env:
177-
RELEASE_TAG: ${{ inputs.tag }}
178-
RELEASE_SHA: ${{ inputs.release_sha }}
179-
run: |
180-
set -euo pipefail
181-
git fetch --force --no-tags origin "+refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}"
182-
observed_sha="$(git rev-parse "refs/tags/${RELEASE_TAG}^{commit}")"
183-
if [[ "$observed_sha" != "$RELEASE_SHA" || "$(git rev-parse HEAD^{commit})" != "$RELEASE_SHA" ]]; then
184-
echo "Release tag moved after authenticated npm preflight evidence." >&2
185-
exit 1
186-
fi
187-
18851
- name: Setup Node environment
18952
uses: ./.github/actions/setup-node-env
19053
with:
@@ -209,8 +72,8 @@ jobs:
20972
PUBLIC_RELEASE_BRANCH: ${{ inputs.public_release_branch }}
21073
run: |
21174
set -euo pipefail
212-
if [[ "${PUBLIC_RELEASE_BRANCH}" != "main" && ! "${PUBLIC_RELEASE_BRANCH}" =~ ^release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ && ! "${PUBLIC_RELEASE_BRANCH}" =~ ^stable/[0-9]{4}\.[1-9][0-9]*\.33$ && ! "${PUBLIC_RELEASE_BRANCH}" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
213-
echo "public_release_branch must be main, release/YYYY.M.PATCH, stable/YYYY.M.33, or tideclaw/alpha/YYYY-MM-DD-HHMMZ, got ${PUBLIC_RELEASE_BRANCH}." >&2
75+
if [[ "${PUBLIC_RELEASE_BRANCH}" != "main" && ! "${PUBLIC_RELEASE_BRANCH}" =~ ^release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
76+
echo "public_release_branch must be main or release/YYYY.M.PATCH, got ${PUBLIC_RELEASE_BRANCH}." >&2
21477
exit 1
21578
fi
21679
RELEASE_SHA=$(git rev-parse HEAD)

0 commit comments

Comments
 (0)