-
Notifications
You must be signed in to change notification settings - Fork 0
160 lines (156 loc) · 6.97 KB
/
Copy pathrelease.yml
File metadata and controls
160 lines (156 loc) · 6.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
name: Release
on:
workflow_dispatch:
inputs:
candidate_sha:
description: Exact independently reviewed main commit to release
required: true
type: string
permissions:
contents: read
jobs:
verify:
if: github.ref == 'refs/heads/main' && github.sha == inputs.candidate_sha
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
release_version: ${{ steps.metadata.outputs.release_version }}
artifact_digest: ${{ steps.release_artifact.outputs.artifact-digest }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ inputs.candidate_sha }}
fetch-depth: 0
persist-credentials: false
- name: Bind workflow and release to the exact protected-main candidate
env:
CANDIDATE_SHA: ${{ inputs.candidate_sha }}
run: |
set -euo pipefail
test "$GITHUB_REF" = "refs/heads/main"
test "$GITHUB_SHA" = "$CANDIDATE_SHA"
[[ "$CANDIDATE_SHA" =~ ^[0-9a-f]{40}$ ]]
test "$(git rev-parse HEAD)" = "$CANDIDATE_SHA"
git fetch --no-tags origin main
test "$(git rev-parse origin/main)" = "$CANDIDATE_SHA"
git show --no-patch --format='%B' "$CANDIDATE_SHA" | \
grep -Eq '^Signed-off-by: .+ <[^>]+>$'
- name: Scan the pristine candidate before installing dependencies
run: |
set -euo pipefail
python3 scripts/check_public_hygiene.py --root .
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.11.32"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install only frozen verification dependencies
run: uv sync --frozen --extra dev --no-install-project
- name: Verify version and all release-critical behavior
id: metadata
run: |
set -euo pipefail
version="$(tr -d ' \t\r\n' < VERSION)"
test -n "$version"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
hermes_version="$(python -c 'from pathlib import Path; print(next(line.split(":", 1)[1].strip() for line in Path("plugins/substrate/plugin.yaml").read_text().splitlines() if line.startswith("version: ")))')"
test "$hermes_version" = "0.4.0"
# The five host adapters are frozen at 0.4.0 (byte-identical to the
# v0.4.0 assets); only the repo-level release version advances.
for manifest in \
plugins/claude-code/.claude-plugin/plugin.json \
plugins/claude-cowork/.claude-plugin/plugin.json \
plugins/codex/.codex-plugin/plugin.json \
plugins/grok-bot/plugin.json \
plugins/openclaw/openclaw.plugin.json; do
test "$(python -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$manifest")" = "0.4.0"
done
uv run --no-sync ruff check .
uv run --no-sync python -m compileall -q plugins scripts
uv run --no-sync python -m pytest -q
printf 'release_version=%s\n' "$version" >> "$GITHUB_OUTPUT"
- name: Build and verify exact release bytes twice
env:
HERMES_PLUGIN_SOURCE_COMMIT: ${{ inputs.candidate_sha }}
run: |
set -euo pipefail
uv run --no-sync python scripts/build_release.py
mkdir -p "$RUNNER_TEMP/first-dist"
cp dist/*.zip "$RUNNER_TEMP/first-dist/"
rm -rf dist
uv run --no-sync python scripts/build_release.py
for archive in substrate.zip claude-code.zip claude-cowork.zip codex.zip grok-bot.zip openclaw.zip; do
cmp "$RUNNER_TEMP/first-dist/$archive" "dist/$archive"
done
uv run --no-sync python scripts/build_release.py --check
- name: Upload exact verified release bytes
id: release_artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: release-${{ inputs.candidate_sha }}
path: dist/*
if-no-files-found: error
compression-level: 0
retention-days: 1
publish:
needs: verify
if: github.ref == 'refs/heads/main' && github.sha == inputs.candidate_sha
runs-on: ubuntu-latest
environment: public-release
permissions:
actions: read
contents: write
id-token: write
attestations: write
steps:
- name: Download exact verified release bytes
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: release-${{ inputs.candidate_sha }}
path: dist
- name: Verify downloaded release set
run: |
set -euo pipefail
test -n "${{ needs.verify.outputs.artifact_digest }}"
test "$(find dist -maxdepth 1 -type f -printf '%f\n' | sort | tr '\n' ' ')" = \
"SHA256SUMS claude-code.zip claude-cowork.zip codex.zip grok-bot.zip openclaw.zip substrate.zip "
(cd dist && sha256sum -c SHA256SUMS)
- uses: actions/attest-build-provenance@e3fe62ef559997059fe8380e7d2b4c909e2d65f4 # pinned
with:
subject-path: "dist/*"
- name: Create immutable tag and GitHub release
env:
CANDIDATE_SHA: ${{ inputs.candidate_sha }}
RELEASE_VERSION: ${{ needs.verify.outputs.release_version }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
test "$GITHUB_REF" = "refs/heads/main"
test "$GITHUB_SHA" = "$CANDIDATE_SHA"
tag="v$RELEASE_VERSION"
if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" >/dev/null 2>&1; then
test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" --jq .object.sha)" = "$CANDIDATE_SHA"
else
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
-f ref="refs/tags/$tag" -f sha="$CANDIDATE_SHA" >/dev/null
fi
if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "release already exists; refusing to mutate immutable assets" >&2
exit 1
fi
gh release create "$tag" \
dist/substrate.zip \
dist/claude-code.zip \
dist/claude-cowork.zip \
dist/codex.zip \
dist/grok-bot.zip \
dist/openclaw.zip \
dist/SHA256SUMS \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--title "substrate-plugins $tag" \
--notes "Substrate memory plugin set $tag: durable v5 Hermes substrate 0.4.0 (write-ahead spool, memory_remember/memory_forget, true session boundaries, subagent capture, onboard.py device login) plus the unchanged 0.4.0 Claude Code, Claude Cowork, Codex CLI, Grok Bot, and OpenClaw host adapters (byte-identical to v0.4.0). See README.md, CHANGELOG.md, and COMPATIBILITY.md."