Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/compliance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Compliance report

# Publishes gale-<tag>-compliance-report.tar.gz to each GitHub Release so
# pulseengine.eu's fetch-reports surfaces gale's rivet traceability/compliance
# evidence on the Reports page. Mirrors pulseengine/synth's compliance.yml.
#
# Decoupled from release.yml on purpose: triggers on release publish, is
# additive, and cannot affect the release flow if it fails.
#
# NOTE: rivet-version pins the rivet binary used to validate/export gale's
# artifacts. gale is authored against rivet 0.15.0 (see CLAUDE.md / the
# .rivet schemas); bump it in lockstep if a future schema needs a newer rivet.

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Existing release tag to (re)generate the report for (e.g. v0.1.0)'
required: true
type: string

permissions:
contents: write

jobs:
compliance:
name: Generate compliance report
runs-on: ubuntu-latest
steps:
- name: Resolve tag
id: tag
env:
REL_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
TAG="${INPUT_TAG:-$REL_TAG}"
case "$TAG" in v*) ;; *) echo "::error::tag '$TAG' must start with v"; exit 1 ;; esac
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@v6
with:
ref: ${{ steps.tag.outputs.tag }}

- name: Generate compliance report
id: report
uses: pulseengine/rivet/.github/actions/compliance@v0.16.1
with:
theme: dark
rivet-version: v0.15.0
include-data-formats: true
report-label: ${{ steps.tag.outputs.tag }}
archive: 'true'
archive-name: gale-${{ steps.tag.outputs.tag }}-compliance-report

- name: Upload report to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARCHIVE_PATH: ${{ steps.report.outputs.archive-path }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
set -euo pipefail
if [ -z "${ARCHIVE_PATH:-}" ] || [ ! -f "$ARCHIVE_PATH" ]; then
echo "::error::compliance action produced no archive-path"; exit 1
fi
gh release upload "$TAG" "$ARCHIVE_PATH" --clobber
echo "::notice title=Compliance report::uploaded $(basename "$ARCHIVE_PATH")"
33 changes: 16 additions & 17 deletions .github/workflows/release-wasm.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
# Build + attach the wasm-cross-LTO module artifacts to gale releases.
# See docs/wasm-module-distribution.md. The manifest is the trust anchor;
# sigil signing rides once the sigil release flow lands (TODO below).
name: Release wasm modules
# Manual wasm-module build (workflow_dispatch only).
#
# Release-time wasm building + attaching + signing is now owned by release.yml
# (the standardised creator: builds the same modules via build-wasm-dist.sh,
# then ships them under a cosign-signed SHA256SUMS + SLSA provenance + SBOM).
# This workflow is kept for on-demand rebuilds outside a release — it uploads
# the dist as a run artifact, it does NOT touch any GitHub Release. The
# release: published trigger was removed so it no longer double-builds /
# double-attaches alongside release.yml.
name: Release wasm modules (manual)

on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: write

jobs:
build-wasm-dist:
name: "wasm dist (sem + mutex, cortex-m4f)"
name: "wasm dist (sem + mutex + msgq, cortex-m4f)"
runs-on: ubuntu-22.04
container:
image: ghcr.io/zephyrproject-rtos/ci-base:v0.29.0
options: --user root
options: --user root --volume /mnt:/mnt
env:
HOME: /root
steps:
Expand All @@ -33,8 +37,10 @@ jobs:
run: |
. /root/.cargo/env
# Pinned versions — bump deliberately; the manifest records them.
cargo install loom-cli --git https://github.com/pulseengine/loom --tag v1.1.11 --locked
cargo install synth-cli --git https://github.com/pulseengine/synth --tag v0.11.37 --locked
# Keep in lockstep with release.yml (synth v0.11.48 ships #359/#372,
# required to build the msgq module).
cargo install loom-cli --git https://github.com/pulseengine/loom --tag v1.1.14 --locked
cargo install synth-cli --git https://github.com/pulseengine/synth --tag v0.11.48 --locked

- name: Install arm toolchain (objcopy for the import renames)
run: |
Expand All @@ -52,14 +58,7 @@ jobs:
# release/signing flow is wired; until then the sha256 manifest is
# attached unsigned and the release notes must say so.

- name: Attach to release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${GITHUB_REF_NAME}" dist/* --clobber

- name: Upload artifact (dispatch runs)
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v7
with:
name: gale-wasm-dist
Expand Down
198 changes: 198 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
name: Release

# Tag-triggered creator for a standardised PulseEngine release. gale ships no
# CLI binary — its release "binaries" are the wasm-cross-LTO kernel modules
# (gale-wasm-{sem,mutex,msgq}-<tag>-cortex-m4f.o + .wasm + manifest). This
# workflow builds them, then publishes them with the standardised provenance
# bundle. Pattern mirrors pulseengine/synth and pulseengine/relay release.yml:
# cosign-signed SHA256SUMS.txt, CycloneDX SBOM, SLSA build provenance,
# build-env capture.
#
# Standardised release assets:
# gale-wasm-<mod>-<tag>.wasm / .wat # loom-dissolved modules (evidence)
# gale-wasm-<mod>-<tag>-cortex-m4f.o # synth ET_REL relocatable objects
# gale-wasm-manifest-<tag>.json # sha256 + pinned toolchain (trust anchor)
# gale-<bare-ver>.cdx.json # CycloneDX SBOM (gale-ffi)
# SHA256SUMS.txt{,.sig,.pem,.cosign.bundle} # one cosign-signed checksum file
# build-env.txt # toolchain provenance
# The rivet compliance report is added separately by compliance.yml (decoupled,
# additive, on release publish).
#
# Verification one-liner (paste in release notes):
# cosign verify-blob \
# --certificate-identity-regexp \
# 'https://github.com/pulseengine/gale/.github/workflows/release.yml@.*' \
# --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
# --bundle SHA256SUMS.txt.cosign.bundle SHA256SUMS.txt
# gh attestation verify gale-wasm-msgq-<tag>-cortex-m4f.o --repo pulseengine/gale

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to (re)build (e.g. v0.1.0)"
required: true
type: string

permissions:
contents: read

jobs:
# ── Build the wasm-cross-LTO modules (gale's release "binaries") ──────────
# ci-base has the Zephyr SDK (arm-zephyr-eabi-objcopy for the import renames);
# loom + synth are pinned to the toolchain that builds ALL THREE modules
# (synth v0.11.48 ships #359/#372, required for msgq).
build-wasm-dist:
name: "wasm dist (sem + mutex + msgq, cortex-m4f)"
runs-on: ubuntu-22.04
container:
image: ghcr.io/zephyrproject-rtos/ci-base:v0.29.0
options: --user root --volume /mnt:/mnt
env:
HOME: /root
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.tag || github.ref }}

- name: Install Rust + wasm32 target
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
. /root/.cargo/env
rustup target add wasm32-unknown-unknown

- name: Install pinned loom + synth
run: |
. /root/.cargo/env
# Pinned — bump deliberately; the manifest records the exact versions.
# synth v0.11.48 ships #372 (arm i64) + #359 (native-pointer .bss),
# both required to build the msgq module.
cargo install loom-cli --git https://github.com/pulseengine/loom --tag v1.1.14 --locked
cargo install synth-cli --git https://github.com/pulseengine/synth --tag v0.11.48 --locked

- name: Install arm toolchain (objcopy for the import renames)
run: |
pip3 install west
west sdk install -t arm-zephyr-eabi || true
echo "$HOME/zephyr-sdk/arm-zephyr-eabi/bin" >> "$GITHUB_PATH" || true

- name: Build distribution
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
. /root/.cargo/env
export TC=arm-zephyr-eabi
VERSION="${INPUT_TAG:-${GITHUB_REF#refs/tags/}}"
scripts/build-wasm-dist.sh dist "$VERSION"
ls -la dist/

- uses: actions/upload-artifact@v7
with:
name: wasm-dist
path: dist/
retention-days: 7

# ── Create the GitHub Release: SBOM, checksums, provenance, signing ───────
create-release:
name: Create GitHub Release
needs: [build-wasm-dist]
runs-on: ubuntu-latest
permissions:
contents: write # release-asset upload
id-token: write # cosign keyless + provenance OIDC
attestations: write # SLSA build provenance
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.tag || github.ref }}

- uses: dtolnay/rust-toolchain@stable

- name: Download wasm dist
uses: actions/download-artifact@v8
with:
name: wasm-dist
path: release-assets

- name: Generate gale-ffi SBOM (CycloneDX)
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
VERSION="${INPUT_TAG:-${GITHUB_REF#refs/tags/}}"
BARE="${VERSION#v}"
cargo install --locked cargo-cyclonedx
# gale-ffi is the crate whose verified decision functions are
# dissolved into the shipped wasm modules.
cargo cyclonedx --manifest-path ffi/Cargo.toml --format json --spec-version 1.5
SBOM_SRC=$(find ffi -maxdepth 2 -name '*.cdx.json' | head -1)
test -n "$SBOM_SRC" && test -f "$SBOM_SRC"
cp "$SBOM_SRC" "release-assets/gale-${BARE}.cdx.json"
ls -la release-assets/

- name: Generate SHA256 checksums
run: |
set -euo pipefail
cd release-assets
sha256sum ./* > SHA256SUMS.txt
cat SHA256SUMS.txt

- name: Generate SLSA build provenance
uses: actions/attest-build-provenance@v4
with:
subject-path: "release-assets/gale-wasm-*-cortex-m4f.o"

- name: Install cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'

- name: Sign SHA256SUMS with cosign (keyless OIDC)
run: |
set -euo pipefail
cd release-assets
cosign sign-blob \
--yes \
--bundle SHA256SUMS.txt.cosign.bundle \
--output-signature SHA256SUMS.txt.sig \
--output-certificate SHA256SUMS.txt.pem \
SHA256SUMS.txt
echo "::notice::SHA256SUMS signed via Sigstore keyless flow."

- name: Capture build environment
run: |
set -euo pipefail
{
echo "rustc: $(rustc --version)"
echo "cargo: $(cargo --version)"
echo "cosign: $(cosign version 2>&1 | head -1)"
echo "runner: $(uname -srm)"
} > release-assets/build-env.txt
cat release-assets/build-env.txt

- name: Create or update GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
VERSION="${INPUT_TAG:-${GITHUB_REF#refs/tags/}}"
if gh release view "$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "::notice::Release $VERSION exists; uploading assets"
gh release upload "$VERSION" --repo "$GITHUB_REPOSITORY" --clobber release-assets/*
else
echo "::notice::Creating Release $VERSION"
gh release create "$VERSION" \
--repo "$GITHUB_REPOSITORY" \
--title "gale $VERSION" \
--generate-notes \
release-assets/*
fi
Loading