Skip to content

Commit 4f9e4eb

Browse files
committed
fix(ci): harden release evidence publication
1 parent ef0c28e commit 4f9e4eb

7 files changed

Lines changed: 926 additions & 490 deletions

.github/workflows/openclaw-release-evidence-from-full-validation.yml

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ on:
1212
required: true
1313
type: string
1414
release_ref:
15-
description: Optional public OpenClaw release ref, tag, branch, or SHA
16-
required: false
17-
default: ""
15+
description: Exact public OpenClaw release ref, tag, or SHA
16+
required: true
1817
type: string
1918
package_spec:
20-
description: Optional package spec covered by this evidence, for example openclaw@2026.4.27-beta.1
21-
required: false
22-
default: ""
19+
description: Exact package spec covered by this evidence, for example openclaw@2026.4.27-beta.1
20+
required: true
2321
type: string
2422
notes:
2523
description: Optional release-manager notes to append to the evidence markdown
@@ -31,7 +29,7 @@ on:
3129
- openclaw_full_release_validation_completed
3230

3331
concurrency:
34-
group: openclaw-release-evidence-from-full-validation-${{ github.event_name == 'repository_dispatch' && github.event.client_payload.release_id || inputs.release_id }}
32+
group: openclaw-release-evidence-${{ github.event_name == 'repository_dispatch' && github.event.client_payload.release_id || inputs.release_id }}
3533
cancel-in-progress: false
3634

3735
env:
@@ -45,19 +43,21 @@ env:
4543

4644
jobs:
4745
write_evidence:
46+
if: github.ref == 'refs/heads/main'
4847
runs-on: ubuntu-latest
4948
permissions:
5049
actions: read
5150
contents: write
5251
steps:
5352
- name: Checkout release repo
54-
uses: actions/checkout@v6
53+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
5554
with:
5655
fetch-depth: 0
57-
persist-credentials: false
56+
ref: main
57+
token: ${{ github.token }}
5858

5959
- name: Setup Node.js
60-
uses: actions/setup-node@v6
60+
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
6161
with:
6262
node-version: ${{ env.NODE_VERSION }}
6363

@@ -69,7 +69,7 @@ jobs:
6969
7070
- name: Generate release evidence
7171
env:
72-
GH_TOKEN: ${{ github.token }}
72+
GH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_PUSH_TOKEN }}
7373
run: |
7474
set -euo pipefail
7575
node scripts/openclaw-release-evidence-from-full-validation.mjs \
@@ -90,31 +90,64 @@ jobs:
9090
} >> "$GITHUB_STEP_SUMMARY"
9191
9292
- name: Commit evidence to main
93-
env:
94-
PUSH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_PUSH_TOKEN }}
9593
run: |
9694
set -euo pipefail
9795
evidence_path="evidence/${RELEASE_ID}"
98-
if [[ -z "${PUSH_TOKEN// }" ]]; then
99-
echo "::error::OPENCLAW_RELEASES_PRIVATE_PUSH_TOKEN is required to push release evidence directly to main."
100-
exit 1
101-
fi
96+
node scripts/openclaw-release-evidence-contract.mjs verify-evidence \
97+
"${evidence_path}/release-evidence.json" "$RELEASE_ID" "$RELEASE_REF" "$PACKAGE_SPEC" \
98+
--require-full-validation
10299
git config user.name "openclaw-release-bot"
103100
git config user.email "release-bot@openclaw.ai"
104101
git add "${evidence_path}"
102+
generated_tree="$(git write-tree)"
103+
expected_tree="$(git rev-parse "${generated_tree}:${evidence_path}")"
104+
changed=0
105105
if git diff --cached --quiet; then
106106
echo "No evidence changes to commit."
107-
exit 0
107+
else
108+
git commit -m "Evidence: record ${RELEASE_ID}"
109+
changed=1
108110
fi
109-
git commit -m "Evidence: record ${RELEASE_ID}"
110-
git remote set-url origin "https://x-access-token:${PUSH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
111-
for attempt in 1 2 3 4 5; do
112-
git fetch origin main
113-
if git rebase origin/main && git push origin HEAD:main; then
114-
exit 0
111+
112+
if [[ "$changed" == "1" ]]; then
113+
pushed=0
114+
for attempt in 1 2 3 4 5; do
115+
git fetch origin main
116+
if git rebase origin/main; then
117+
if [[ "$(git rev-parse "HEAD:${evidence_path}")" != "$expected_tree" ]]; then
118+
echo "::error::Rebase changed the generated evidence directory."
119+
exit 1
120+
fi
121+
if git push origin HEAD:main; then
122+
pushed=1
123+
break
124+
fi
125+
fi
126+
git rebase --abort || true
127+
sleep $((attempt * 5))
128+
done
129+
if [[ "$pushed" != "1" ]]; then
130+
echo "::error::Could not push evidence to main after retries."
131+
exit 1
115132
fi
116-
git rebase --abort || true
117-
sleep $((attempt * 5))
118-
done
119-
echo "::error::Could not push evidence to main after retries."
120-
exit 1
133+
fi
134+
135+
git fetch origin main
136+
git merge-base --is-ancestor HEAD origin/main || {
137+
echo "::error::Published evidence commit is not reachable from origin/main."
138+
exit 1
139+
}
140+
remote_tree="$(git rev-parse "origin/main:${evidence_path}")"
141+
if [[ "$expected_tree" != "$remote_tree" ]]; then
142+
echo "::error::Remote evidence directory differs from the generated files."
143+
exit 1
144+
fi
145+
remote_json="$RUNNER_TEMP/openclaw-release-evidence/remote-release-evidence.json"
146+
git show "origin/main:${evidence_path}/release-evidence.json" > "$remote_json"
147+
cmp "${evidence_path}/release-evidence.json" "$remote_json" || {
148+
echo "::error::Remote evidence bytes differ from the generated JSON."
149+
exit 1
150+
}
151+
node scripts/openclaw-release-evidence-contract.mjs verify-evidence \
152+
"$remote_json" "$RELEASE_ID" "$RELEASE_REF" "$PACKAGE_SPEC" \
153+
--require-full-validation

.github/workflows/openclaw-release-evidence.yml

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ on:
88
required: true
99
type: string
1010
release_ref:
11-
description: Optional public OpenClaw release ref, tag, branch, or SHA
12-
required: false
13-
default: ""
11+
description: Exact public OpenClaw release ref, tag, or SHA
12+
required: true
1413
type: string
1514
package_spec:
16-
description: Optional package spec covered by this evidence, for example openclaw@2026.4.27-beta.1
17-
required: false
18-
default: ""
15+
description: Exact package spec covered by this evidence, for example openclaw@2026.4.27-beta.1
16+
required: true
1917
type: string
2018
runs:
2119
description: "One per line: <label> <owner/repo> <run-id> <blocking|advisory>"
@@ -34,22 +32,27 @@ concurrency:
3432
env:
3533
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
3634
NODE_VERSION: "24.x"
35+
PACKAGE_SPEC: ${{ inputs.package_spec }}
36+
RELEASE_ID: ${{ inputs.release_id }}
37+
RELEASE_REF: ${{ inputs.release_ref }}
3738

3839
jobs:
3940
write_evidence:
41+
if: github.ref == 'refs/heads/main'
4042
runs-on: ubuntu-latest
4143
permissions:
4244
actions: read
4345
contents: write
4446
steps:
4547
- name: Checkout release repo
46-
uses: actions/checkout@v6
48+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
4749
with:
4850
fetch-depth: 0
49-
persist-credentials: false
51+
ref: main
52+
token: ${{ github.token }}
5053

5154
- name: Setup Node.js
52-
uses: actions/setup-node@v6
55+
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
5356
with:
5457
node-version: ${{ env.NODE_VERSION }}
5558

@@ -69,48 +72,79 @@ jobs:
6972
run: |
7073
set -euo pipefail
7174
node scripts/openclaw-release-evidence.mjs \
72-
--release-id "${{ inputs.release_id }}" \
73-
--release-ref "${{ inputs.release_ref }}" \
74-
--package-spec "${{ inputs.package_spec }}" \
75+
--release-id "$RELEASE_ID" \
76+
--release-ref "$RELEASE_REF" \
77+
--package-spec "$PACKAGE_SPEC" \
7578
--runs-file "$RUNNER_TEMP/openclaw-release-evidence/runs.txt" \
7679
--notes-file "$RUNNER_TEMP/openclaw-release-evidence/notes.md"
7780
7881
- name: Append run summary
7982
run: |
8083
set -euo pipefail
81-
evidence_md="evidence/${{ inputs.release_id }}/release-evidence.md"
84+
evidence_md="evidence/${RELEASE_ID}/release-evidence.md"
8285
{
8386
cat "$evidence_md"
8487
echo
8588
echo "Evidence path: \`$evidence_md\`"
8689
} >> "$GITHUB_STEP_SUMMARY"
8790
8891
- name: Commit evidence to main
89-
env:
90-
PUSH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_PUSH_TOKEN }}
9192
run: |
9293
set -euo pipefail
93-
evidence_path="evidence/${{ inputs.release_id }}"
94-
if [[ -z "${PUSH_TOKEN// }" ]]; then
95-
echo "::error::OPENCLAW_RELEASES_PRIVATE_PUSH_TOKEN is required to push release evidence directly to main."
96-
exit 1
97-
fi
94+
evidence_path="evidence/${RELEASE_ID}"
95+
node scripts/openclaw-release-evidence-contract.mjs verify-evidence \
96+
"${evidence_path}/release-evidence.json" "$RELEASE_ID" "$RELEASE_REF" "$PACKAGE_SPEC"
9897
git config user.name "openclaw-release-bot"
9998
git config user.email "release-bot@openclaw.ai"
10099
git add "${evidence_path}"
100+
generated_tree="$(git write-tree)"
101+
expected_tree="$(git rev-parse "${generated_tree}:${evidence_path}")"
102+
changed=0
101103
if git diff --cached --quiet; then
102104
echo "No evidence changes to commit."
103-
exit 0
105+
else
106+
git commit -m "Evidence: record ${RELEASE_ID}"
107+
changed=1
104108
fi
105-
git commit -m "Evidence: record ${{ inputs.release_id }}"
106-
git remote set-url origin "https://x-access-token:${PUSH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
107-
for attempt in 1 2 3 4 5; do
108-
git fetch origin main
109-
if git rebase origin/main && git push origin HEAD:main; then
110-
exit 0
109+
110+
if [[ "$changed" == "1" ]]; then
111+
pushed=0
112+
for attempt in 1 2 3 4 5; do
113+
git fetch origin main
114+
if git rebase origin/main; then
115+
if [[ "$(git rev-parse "HEAD:${evidence_path}")" != "$expected_tree" ]]; then
116+
echo "::error::Rebase changed the generated evidence directory."
117+
exit 1
118+
fi
119+
if git push origin HEAD:main; then
120+
pushed=1
121+
break
122+
fi
123+
fi
124+
git rebase --abort || true
125+
sleep $((attempt * 5))
126+
done
127+
if [[ "$pushed" != "1" ]]; then
128+
echo "::error::Could not push evidence to main after retries."
129+
exit 1
111130
fi
112-
git rebase --abort || true
113-
sleep $((attempt * 5))
114-
done
115-
echo "::error::Could not push evidence to main after retries."
116-
exit 1
131+
fi
132+
133+
git fetch origin main
134+
git merge-base --is-ancestor HEAD origin/main || {
135+
echo "::error::Published evidence commit is not reachable from origin/main."
136+
exit 1
137+
}
138+
remote_tree="$(git rev-parse "origin/main:${evidence_path}")"
139+
if [[ "$expected_tree" != "$remote_tree" ]]; then
140+
echo "::error::Remote evidence directory differs from the generated files."
141+
exit 1
142+
fi
143+
remote_json="$RUNNER_TEMP/openclaw-release-evidence/remote-release-evidence.json"
144+
git show "origin/main:${evidence_path}/release-evidence.json" > "$remote_json"
145+
cmp "${evidence_path}/release-evidence.json" "$remote_json" || {
146+
echo "::error::Remote evidence bytes differ from the generated JSON."
147+
exit 1
148+
}
149+
node scripts/openclaw-release-evidence-contract.mjs verify-evidence \
150+
"$remote_json" "$RELEASE_ID" "$RELEASE_REF" "$PACKAGE_SPEC"

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ Each evidence directory contains:
5858
- `index.json`
5959
- `runs/<label>.json`
6060

61-
Evidence records include release ref provenance, npm package metadata, run URLs,
62-
workflow names, refs, SHAs, pass/fail state, timing summaries, artifact names,
63-
artifact sizes, and selected release performance summaries.
61+
Evidence records include release ref provenance, current npm and public GitHub
62+
release state, run URLs, workflow names, refs, SHAs, pass/fail state, timing
63+
summaries, artifact names, artifact sizes, and selected release performance
64+
summaries.
6465

6566
Evidence records do not store raw logs, provider payloads, live-channel
6667
transcripts, signing material, credentials, environment dumps, or downloaded
@@ -103,8 +104,16 @@ fail the release by itself.
103104
### Full Validation Ingest
104105

105106
`OpenClaw Release Evidence From Full Validation` takes a completed
106-
`openclaw/openclaw` full-validation run id, reads that parent run's logs,
107-
extracts child run ids, and writes the same evidence directory shape.
107+
`openclaw/openclaw` full-validation run id and reads its attempt-qualified
108+
`full-release-validation-<run-id>-<attempt>` manifest artifact. The ingest
109+
rejects missing, duplicate, expired, malformed, or identity-mismatched
110+
artifacts; child run ids and evidence-reuse provenance come only from the
111+
validated manifest.
112+
113+
Both evidence workflows require an exact package spec and release ref. They
114+
commit with the workflow's same-repository `github.token`, then verify that the
115+
commit is reachable from `origin/main`, the remote evidence directory is
116+
byte-identical, and its JSON still satisfies the release identity contract.
108117

109118
Manual ingest example:
110119

0 commit comments

Comments
 (0)