Skip to content

Commit 1f18e91

Browse files
avrabeclaude
andauthored
feat(release): standardised gale release pipeline — signed wasm + rivet compliance (#72)
Standardised gale release pipeline mirroring synth/relay: release.yml (tag-triggered, signed wasm dist + CycloneDX SBOM + SHA256SUMS cosign + SLSA provenance), compliance.yml (rivet compliance report -> release), release-wasm.yml demoted to manual. CI core green; only red is the pre-existing llvm-lto pull-ENOSPC (gale#73, unrelated to these workflow files). Dormant until the first v* tag, which is the validation oracle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 068f4e6 commit 1f18e91

3 files changed

Lines changed: 282 additions & 16 deletions

File tree

.github/workflows/compliance.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Compliance report
2+
3+
# Publishes gale-<tag>-compliance-report.tar.gz to each GitHub Release so
4+
# pulseengine.eu's fetch-reports surfaces gale's rivet traceability/compliance
5+
# evidence on the Reports page. Mirrors pulseengine/synth's compliance.yml.
6+
#
7+
# Decoupled from release.yml on purpose: triggers on release publish, is
8+
# additive, and cannot affect the release flow if it fails.
9+
#
10+
# NOTE: rivet-version pins the rivet binary used to validate/export gale's
11+
# artifacts. gale is authored against rivet 0.15.0 (see CLAUDE.md / the
12+
# .rivet schemas); bump it in lockstep if a future schema needs a newer rivet.
13+
14+
on:
15+
release:
16+
types: [published]
17+
workflow_dispatch:
18+
inputs:
19+
tag:
20+
description: 'Existing release tag to (re)generate the report for (e.g. v0.1.0)'
21+
required: true
22+
type: string
23+
24+
permissions:
25+
contents: write
26+
27+
jobs:
28+
compliance:
29+
name: Generate compliance report
30+
runs-on: ubuntu-latest
31+
steps:
32+
- name: Resolve tag
33+
id: tag
34+
env:
35+
REL_TAG: ${{ github.event.release.tag_name }}
36+
INPUT_TAG: ${{ inputs.tag }}
37+
run: |
38+
set -euo pipefail
39+
TAG="${INPUT_TAG:-$REL_TAG}"
40+
case "$TAG" in v*) ;; *) echo "::error::tag '$TAG' must start with v"; exit 1 ;; esac
41+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
42+
43+
- uses: actions/checkout@v6
44+
with:
45+
ref: ${{ steps.tag.outputs.tag }}
46+
47+
- name: Generate compliance report
48+
id: report
49+
uses: pulseengine/rivet/.github/actions/compliance@v0.16.1
50+
with:
51+
theme: dark
52+
rivet-version: v0.15.0
53+
include-data-formats: true
54+
report-label: ${{ steps.tag.outputs.tag }}
55+
archive: 'true'
56+
archive-name: gale-${{ steps.tag.outputs.tag }}-compliance-report
57+
58+
- name: Upload report to release
59+
env:
60+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61+
ARCHIVE_PATH: ${{ steps.report.outputs.archive-path }}
62+
TAG: ${{ steps.tag.outputs.tag }}
63+
run: |
64+
set -euo pipefail
65+
if [ -z "${ARCHIVE_PATH:-}" ] || [ ! -f "$ARCHIVE_PATH" ]; then
66+
echo "::error::compliance action produced no archive-path"; exit 1
67+
fi
68+
gh release upload "$TAG" "$ARCHIVE_PATH" --clobber
69+
echo "::notice title=Compliance report::uploaded $(basename "$ARCHIVE_PATH")"

.github/workflows/release-wasm.yml

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
# Build + attach the wasm-cross-LTO module artifacts to gale releases.
2-
# See docs/wasm-module-distribution.md. The manifest is the trust anchor;
3-
# sigil signing rides once the sigil release flow lands (TODO below).
4-
name: Release wasm modules
1+
# Manual wasm-module build (workflow_dispatch only).
2+
#
3+
# Release-time wasm building + attaching + signing is now owned by release.yml
4+
# (the standardised creator: builds the same modules via build-wasm-dist.sh,
5+
# then ships them under a cosign-signed SHA256SUMS + SLSA provenance + SBOM).
6+
# This workflow is kept for on-demand rebuilds outside a release — it uploads
7+
# the dist as a run artifact, it does NOT touch any GitHub Release. The
8+
# release: published trigger was removed so it no longer double-builds /
9+
# double-attaches alongside release.yml.
10+
name: Release wasm modules (manual)
511

612
on:
7-
release:
8-
types: [published]
913
workflow_dispatch:
1014

1115
permissions:
@@ -17,7 +21,7 @@ jobs:
1721
runs-on: ubuntu-22.04
1822
container:
1923
image: ghcr.io/zephyrproject-rtos/ci-base:v0.29.0
20-
options: --user root
24+
options: --user root --volume /mnt:/mnt
2125
env:
2226
HOME: /root
2327
steps:
@@ -33,8 +37,10 @@ jobs:
3337
run: |
3438
. /root/.cargo/env
3539
# Pinned versions — bump deliberately; the manifest records them.
36-
cargo install loom-cli --git https://github.com/pulseengine/loom --tag v1.1.11 --locked
37-
cargo install synth-cli --git https://github.com/pulseengine/synth --tag v0.11.37 --locked
40+
# Keep in lockstep with release.yml (synth v0.11.48 ships #359/#372,
41+
# required to build the msgq module).
42+
cargo install loom-cli --git https://github.com/pulseengine/loom --tag v1.1.14 --locked
43+
cargo install synth-cli --git https://github.com/pulseengine/synth --tag v0.11.48 --locked
3844
3945
- name: Install arm toolchain (objcopy for the import renames)
4046
run: |
@@ -52,14 +58,7 @@ jobs:
5258
# release/signing flow is wired; until then the sha256 manifest is
5359
# attached unsigned and the release notes must say so.
5460

55-
- name: Attach to release
56-
if: github.event_name == 'release'
57-
env:
58-
GH_TOKEN: ${{ github.token }}
59-
run: gh release upload "${GITHUB_REF_NAME}" dist/* --clobber
60-
6161
- name: Upload artifact (dispatch runs)
62-
if: github.event_name == 'workflow_dispatch'
6362
uses: actions/upload-artifact@v7
6463
with:
6564
name: gale-wasm-dist

.github/workflows/release.yml

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
name: Release
2+
3+
# Tag-triggered creator for a standardised PulseEngine release. gale ships no
4+
# CLI binary — its release "binaries" are the wasm-cross-LTO kernel modules
5+
# (gale-wasm-{sem,mutex,msgq}-<tag>-cortex-m4f.o + .wasm + manifest). This
6+
# workflow builds them, then publishes them with the standardised provenance
7+
# bundle. Pattern mirrors pulseengine/synth and pulseengine/relay release.yml:
8+
# cosign-signed SHA256SUMS.txt, CycloneDX SBOM, SLSA build provenance,
9+
# build-env capture.
10+
#
11+
# Standardised release assets:
12+
# gale-wasm-<mod>-<tag>.wasm / .wat # loom-dissolved modules (evidence)
13+
# gale-wasm-<mod>-<tag>-cortex-m4f.o # synth ET_REL relocatable objects
14+
# gale-wasm-manifest-<tag>.json # sha256 + pinned toolchain (trust anchor)
15+
# gale-<bare-ver>.cdx.json # CycloneDX SBOM (gale-ffi)
16+
# SHA256SUMS.txt{,.sig,.pem,.cosign.bundle} # one cosign-signed checksum file
17+
# build-env.txt # toolchain provenance
18+
# The rivet compliance report is added separately by compliance.yml (decoupled,
19+
# additive, on release publish).
20+
#
21+
# Verification one-liner (paste in release notes):
22+
# cosign verify-blob \
23+
# --certificate-identity-regexp \
24+
# 'https://github.com/pulseengine/gale/.github/workflows/release.yml@.*' \
25+
# --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
26+
# --bundle SHA256SUMS.txt.cosign.bundle SHA256SUMS.txt
27+
# gh attestation verify gale-wasm-msgq-<tag>-cortex-m4f.o --repo pulseengine/gale
28+
29+
concurrency:
30+
group: release-${{ github.ref }}
31+
cancel-in-progress: false
32+
33+
on:
34+
push:
35+
tags:
36+
- "v*"
37+
workflow_dispatch:
38+
inputs:
39+
tag:
40+
description: "Existing release tag to (re)build (e.g. v0.1.0)"
41+
required: true
42+
type: string
43+
44+
permissions:
45+
contents: read
46+
47+
jobs:
48+
# ── Build the wasm-cross-LTO modules (gale's release "binaries") ──────────
49+
# ci-base has the Zephyr SDK (arm-zephyr-eabi-objcopy for the import renames);
50+
# loom + synth are pinned to the toolchain that builds ALL THREE modules
51+
# (synth v0.11.48 ships #359/#372, required for msgq).
52+
build-wasm-dist:
53+
name: "wasm dist (sem + mutex + msgq, cortex-m4f)"
54+
runs-on: ubuntu-22.04
55+
container:
56+
image: ghcr.io/zephyrproject-rtos/ci-base:v0.29.0
57+
options: --user root --volume /mnt:/mnt
58+
env:
59+
HOME: /root
60+
steps:
61+
- uses: actions/checkout@v6
62+
with:
63+
ref: ${{ inputs.tag || github.ref }}
64+
65+
- name: Install Rust + wasm32 target
66+
run: |
67+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
68+
. /root/.cargo/env
69+
rustup target add wasm32-unknown-unknown
70+
71+
- name: Install pinned loom + synth
72+
run: |
73+
. /root/.cargo/env
74+
# Pinned — bump deliberately; the manifest records the exact versions.
75+
# synth v0.11.48 ships #372 (arm i64) + #359 (native-pointer .bss),
76+
# both required to build the msgq module.
77+
cargo install loom-cli --git https://github.com/pulseengine/loom --tag v1.1.14 --locked
78+
cargo install synth-cli --git https://github.com/pulseengine/synth --tag v0.11.48 --locked
79+
80+
- name: Install arm toolchain (objcopy for the import renames)
81+
run: |
82+
pip3 install west
83+
west sdk install -t arm-zephyr-eabi || true
84+
echo "$HOME/zephyr-sdk/arm-zephyr-eabi/bin" >> "$GITHUB_PATH" || true
85+
86+
- name: Build distribution
87+
env:
88+
INPUT_TAG: ${{ inputs.tag }}
89+
run: |
90+
set -euo pipefail
91+
. /root/.cargo/env
92+
export TC=arm-zephyr-eabi
93+
VERSION="${INPUT_TAG:-${GITHUB_REF#refs/tags/}}"
94+
scripts/build-wasm-dist.sh dist "$VERSION"
95+
ls -la dist/
96+
97+
- uses: actions/upload-artifact@v7
98+
with:
99+
name: wasm-dist
100+
path: dist/
101+
retention-days: 7
102+
103+
# ── Create the GitHub Release: SBOM, checksums, provenance, signing ───────
104+
create-release:
105+
name: Create GitHub Release
106+
needs: [build-wasm-dist]
107+
runs-on: ubuntu-latest
108+
permissions:
109+
contents: write # release-asset upload
110+
id-token: write # cosign keyless + provenance OIDC
111+
attestations: write # SLSA build provenance
112+
steps:
113+
- uses: actions/checkout@v6
114+
with:
115+
ref: ${{ inputs.tag || github.ref }}
116+
117+
- uses: dtolnay/rust-toolchain@stable
118+
119+
- name: Download wasm dist
120+
uses: actions/download-artifact@v8
121+
with:
122+
name: wasm-dist
123+
path: release-assets
124+
125+
- name: Generate gale-ffi SBOM (CycloneDX)
126+
env:
127+
INPUT_TAG: ${{ inputs.tag }}
128+
run: |
129+
set -euo pipefail
130+
VERSION="${INPUT_TAG:-${GITHUB_REF#refs/tags/}}"
131+
BARE="${VERSION#v}"
132+
cargo install --locked cargo-cyclonedx
133+
# gale-ffi is the crate whose verified decision functions are
134+
# dissolved into the shipped wasm modules.
135+
cargo cyclonedx --manifest-path ffi/Cargo.toml --format json --spec-version 1.5
136+
SBOM_SRC=$(find ffi -maxdepth 2 -name '*.cdx.json' | head -1)
137+
test -n "$SBOM_SRC" && test -f "$SBOM_SRC"
138+
cp "$SBOM_SRC" "release-assets/gale-${BARE}.cdx.json"
139+
ls -la release-assets/
140+
141+
- name: Generate SHA256 checksums
142+
run: |
143+
set -euo pipefail
144+
cd release-assets
145+
sha256sum ./* > SHA256SUMS.txt
146+
cat SHA256SUMS.txt
147+
148+
- name: Generate SLSA build provenance
149+
uses: actions/attest-build-provenance@v4
150+
with:
151+
subject-path: "release-assets/gale-wasm-*-cortex-m4f.o"
152+
153+
- name: Install cosign
154+
uses: sigstore/cosign-installer@v3
155+
with:
156+
cosign-release: 'v2.4.1'
157+
158+
- name: Sign SHA256SUMS with cosign (keyless OIDC)
159+
run: |
160+
set -euo pipefail
161+
cd release-assets
162+
cosign sign-blob \
163+
--yes \
164+
--bundle SHA256SUMS.txt.cosign.bundle \
165+
--output-signature SHA256SUMS.txt.sig \
166+
--output-certificate SHA256SUMS.txt.pem \
167+
SHA256SUMS.txt
168+
echo "::notice::SHA256SUMS signed via Sigstore keyless flow."
169+
170+
- name: Capture build environment
171+
run: |
172+
set -euo pipefail
173+
{
174+
echo "rustc: $(rustc --version)"
175+
echo "cargo: $(cargo --version)"
176+
echo "cosign: $(cosign version 2>&1 | head -1)"
177+
echo "runner: $(uname -srm)"
178+
} > release-assets/build-env.txt
179+
cat release-assets/build-env.txt
180+
181+
- name: Create or update GitHub Release
182+
env:
183+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
184+
INPUT_TAG: ${{ inputs.tag }}
185+
run: |
186+
set -euo pipefail
187+
VERSION="${INPUT_TAG:-${GITHUB_REF#refs/tags/}}"
188+
if gh release view "$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
189+
echo "::notice::Release $VERSION exists; uploading assets"
190+
gh release upload "$VERSION" --repo "$GITHUB_REPOSITORY" --clobber release-assets/*
191+
else
192+
echo "::notice::Creating Release $VERSION"
193+
gh release create "$VERSION" \
194+
--repo "$GITHUB_REPOSITORY" \
195+
--title "gale $VERSION" \
196+
--generate-notes \
197+
release-assets/*
198+
fi

0 commit comments

Comments
 (0)