Skip to content
Merged
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
252 changes: 252 additions & 0 deletions .github/workflows/release-spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
name: release spec

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
release_ref:
description: Git ref to build the release from.
required: true
default: refs/heads/main
type: string
release_version:
description: SemVer version for release metadata (without v prefix).
required: true
default: 0.0.0-dryrun
type: string
attest_to_rekor:
description: Upload Cosign signatures to the public Rekor log.
required: true
default: false
type: boolean

permissions:
contents: write
id-token: write

jobs:
test:
name: "IntentProof Release: Test Spec"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_ref || github.ref }}

- uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Run spec verification
run: npm test

generate:
name: "IntentProof Release: Generate Integrity Manifest"
needs: test
runs-on: ubuntu-latest
outputs:
release_ref: ${{ steps.release.outputs.release_ref }}
release_version: ${{ steps.release.outputs.release_version }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_ref || github.ref }}

- uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm

- name: Resolve release metadata
id: release
env:
INPUT_RELEASE_REF: ${{ inputs.release_ref }}
INPUT_RELEASE_VERSION: ${{ inputs.release_version }}
run: |
set -euo pipefail
semver_tag='^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'

if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
release_ref="$INPUT_RELEASE_REF"
release_version="$INPUT_RELEASE_VERSION"
else
release_ref="${GITHUB_REF}"
release_version="${GITHUB_REF_NAME#v}"
if [[ ! "$release_ref" =~ $semver_tag ]]; then
echo "tag ref must match vMAJOR.MINOR.PATCH: ${release_ref}" >&2
exit 1
fi
fi

{
echo "release_ref=${release_ref}"
echo "release_version=${release_version}"
} >> "$GITHUB_OUTPUT"

- name: Install dependencies
run: npm ci

- name: Generate and verify Ed25519-signed manifest
env:
SPEC_INTEGRITY_PRIVATE_KEY: ${{ secrets.SPEC_INTEGRITY_PRIVATE_KEY }}
run: |
set -euo pipefail
test -n "${SPEC_INTEGRITY_PRIVATE_KEY:-}"
npx ts-node integrity/generate_manifest.ts
npx ts-node integrity/verify_manifest.ts

- uses: actions/upload-artifact@v7
with:
name: spec-integrity-manifest
path: |
integrity/manifest.v1.json
integrity/manifest.v1.json.sig
well-known-keys/spec-integrity.pem

cosign:
name: "IntentProof Release: Cosign Integrity Manifest"
needs: generate
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.generate.outputs.release_ref }}

- uses: actions/download-artifact@v8
with:
name: spec-integrity-manifest
path: .

- uses: sigstore/cosign-installer@v3

- name: Cosign-sign manifest
env:
ATTEST_TO_REKOR: ${{ github.event_name != 'workflow_dispatch' || inputs.attest_to_rekor }}
run: |
set -euo pipefail
manifest="integrity/manifest.v1.json"
test -f "$manifest"
tlog_args=()
if [[ "$ATTEST_TO_REKOR" != "true" ]]; then
tlog_args+=(--tlog-upload=false)
fi
cosign sign-blob --yes \
"${tlog_args[@]}" \
--bundle "${manifest}.cosign.sigstore.json" \
--output-signature "${manifest}.cosign.sig" \
"$manifest"

- name: Verify Cosign signature
if: github.event_name != 'workflow_dispatch' || inputs.attest_to_rekor
run: |
set -euo pipefail
manifest="integrity/manifest.v1.json"
cosign verify-blob \
--certificate-identity-regexp 'https://github.com/IntentProof/intentproof-spec/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
--bundle "${manifest}.cosign.sigstore.json" \
--signature "${manifest}.cosign.sig" \
"$manifest"

- uses: actions/upload-artifact@v7
with:
name: spec-integrity-cosign
path: |
integrity/manifest.v1.json.cosign.sig
integrity/manifest.v1.json.cosign.sigstore.json

bundle:
name: "IntentProof Release: Build Spec Bundle"
needs:
- generate
- cosign
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.generate.outputs.release_ref }}

- uses: actions/download-artifact@v8
with:
name: spec-integrity-manifest
path: .

- uses: actions/download-artifact@v8
with:
name: spec-integrity-cosign
path: integrity

- name: Build reproducible spec tarball
env:
RELEASE_VERSION: ${{ needs.generate.outputs.release_version }}
run: |
set -euo pipefail
mkdir -p dist
tar \
--sort=name \
--mtime='UTC 1970-01-01' \
--owner=0 \
--group=0 \
--numeric-owner \
-czf "dist/intentproof-spec-${RELEASE_VERSION}.tar.gz" \
schema \
golden \
compatibility \
integrity/manifest.v1.json \
integrity/manifest.v1.json.sig \
integrity/manifest.v1.json.cosign.sig \
integrity/manifest.v1.json.cosign.sigstore.json \
well-known-keys/spec-integrity.pem

- uses: actions/upload-artifact@v7
with:
name: spec-release-bundle
path: dist/*.tar.gz

upload-release:
name: "IntentProof Release: Publish Spec Release Artifacts"
needs:
- generate
- cosign
- bundle
if: github.event_name != 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v8
with:
name: spec-integrity-manifest
path: release-artifacts

- uses: actions/download-artifact@v8
with:
name: spec-integrity-cosign
path: release-artifacts/integrity

- uses: actions/download-artifact@v8
with:
name: spec-release-bundle
path: release-artifacts

- name: Upload artifacts to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
mapfile -t files < <(find release-artifacts -type f | sort)
test "${#files[@]}" -gt 0
if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
gh release create "$RELEASE_TAG" --generate-notes
fi
gh release upload "$RELEASE_TAG" "${files[@]}" --clobber
Loading