forked from ChelseaKR/sprout
-
Notifications
You must be signed in to change notification settings - Fork 0
161 lines (153 loc) · 6.71 KB
/
Copy pathrelease.yml
File metadata and controls
161 lines (153 loc) · 6.71 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
161
name: release
on:
workflow_dispatch:
inputs:
tag:
description: "Existing signed stable SemVer tag (vX.Y.Z)"
required: true
type: string
permissions:
contents: read
concurrency:
group: release
cancel-in-progress: false
jobs:
# The shared trust-boundary primitive: it owns tag authorization only (annotated,
# signed by an allowed signer, stable SemVer, on main) and returns immutable
# identifiers this workflow's own build/publish jobs pin to. It is consumed from the
# publicly readable copy in the org profile repository, not from a copy a
# non-collaborator cannot resolve — a caller can only reuse a workflow it can read,
# so the public copy is the one that keeps working regardless of this repo's
# visibility. Same commit, same behaviour; SHA-pinned so the trust boundary cannot
# move under us.
authorize:
uses: ChelseaKR/.github/.github/workflows/release-authorize.yml@315a513ff3b4e7c5c0628428909052d947f4f1ab
permissions:
contents: read
with:
tag: ${{ inputs.tag }}
verify:
needs: authorize
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.authorize.outputs.release-commit }}
persist-credentials: false
# No caching here either: this whole workflow triggers on `push: tags`, i.e. a
# release event, so a cache poisoned by an earlier, less-trusted run could be
# consumed here even though this job itself holds no write credentials (zizmor
# `cache-poisoning`, high severity — found 2026-07-05, applies workflow-wide on a
# tag-push trigger, not just to the job holding OIDC/attestation permissions).
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: false
- name: Re-run the full gate set at the tagged commit
# RELEASE_TAG makes `make eval` (inside `make verify`) append this release's scores to
# docs/audits/eval-history.jsonl and run the consecutive-decline drift gate (EXP-13),
# gating on whatever ledger is committed at the tagged commit. This job has
# `contents: read` only and cannot push, so the actual append-and-commit happens the
# same way `docs/audits/eval-baseline.json` already does: the maintainer runs
# `make eval RELEASE_TAG=<tag>` locally and commits the updated ledger before tagging;
# this step is the fail-closed re-verification at the tagged commit, not the writer.
env:
TAG: ${{ needs.authorize.outputs.release-tag }}
run: uv sync --locked --extra serve && uv run sprout ingest && make verify RELEASE_TAG="${TAG}"
- name: Require tag, package, and changelog versions to match
env:
TAG: ${{ needs.authorize.outputs.release-tag }}
run: |
set -euo pipefail
VERSION="${TAG#v}"
PACKAGE_VERSION="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
test "${VERSION}" = "${PACKAGE_VERSION}"
grep -Fq "## [${VERSION}]" CHANGELOG.md
build:
needs: [authorize, verify]
runs-on: ubuntu-latest
permissions:
id-token: write
attestations: write
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.authorize.outputs.release-commit }}
persist-credentials: false
# No caching in the OIDC signing/publish job: this job holds id-token: write +
# attestations: write, and a poisoned/stale cache is the standard's canonical
# cache-poisoning exposure for a provenance job (SEC-30, CICD-24). The `verify`
# job above has no credentials and may cache freely.
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: false
- name: Build sdist + wheel
run: uv build
- name: Install the built package so the SBOM reflects real, non-empty deps
run: uv sync --locked --no-dev
- name: Generate SBOM (CycloneDX)
run: uvx cyclonedx-py environment --output-format json --outfile sbom.cdx.json
- name: Upload SBOM artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sbom
path: sbom.cdx.json
- name: Upload verified distributions
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: distributions
path: dist/*
if-no-files-found: error
- name: Attest build provenance (SLSA)
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: "dist/*"
publish-pypi:
needs: [authorize, build]
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: distributions
path: dist
- name: Recheck immutable tag object before PyPI publication
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.authorize.outputs.release-tag }}
TAG_OBJECT_SHA: ${{ needs.authorize.outputs.tag-object-sha }}
run: |
set -euo pipefail
LIVE_TAG_OBJECT="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq .object.sha)"
test "${LIVE_TAG_OBJECT}" = "${TAG_OBJECT_SHA}"
- name: Publish to PyPI through Trusted Publishing
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
publish-release:
needs: [authorize, build, publish-pypi]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: distributions
path: dist
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sbom
path: dist
- name: Recheck immutable tag object and create the release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.authorize.outputs.release-tag }}
TAG_OBJECT_SHA: ${{ needs.authorize.outputs.tag-object-sha }}
run: |
set -euo pipefail
LIVE_TAG_OBJECT="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq .object.sha)"
test "${LIVE_TAG_OBJECT}" = "${TAG_OBJECT_SHA}"
gh release create "${TAG}" dist/* \
--title "sprout ${TAG}" --generate-notes