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
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ jobs:
--repo "$GITHUB_REPOSITORY" \
--signer-workflow "$GITHUB_REPOSITORY/.github/workflows/release.yml" \
--source-digest "$GITHUB_SHA" --source-ref "$GITHUB_REF"
- name: Publish release assets
- name: Create draft release with verified assets
shell: bash
env:
GH_TOKEN: ${{ github.token }}
ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }}
IS_PRERELEASE: ${{ steps.archive.outputs.prerelease }}
run: |
release_flags=(--verify-tag --generate-notes)
release_flags=(--draft --verify-tag --generate-notes)
case "$IS_PRERELEASE" in
true) release_flags+=(--prerelease --latest=false) ;;
false) ;;
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Use `uv lock --upgrade-package NAME` for a deliberate upgrade. Update the `maida

## Tagged release provenance

The release workflow tests the full stable or release-candidate tag, archives its committed source, and publishes `maida-assert.tar.gz`, `SHA256SUMS`, and `provenance.jsonl`. It verifies the attestation against the workflow, source commit and tag before publication. Release candidates use the same checks and provenance as stable releases, with GitHub's prerelease flag and `latest=false`. Protect release tags and require review of the release workflow; do not overwrite full tags or existing releases.
The release workflow tests the full stable or release-candidate tag, archives its committed source, and creates a draft GitHub release containing `maida-assert.tar.gz`, `SHA256SUMS`, and `provenance.jsonl`. It verifies the attestation against the workflow, source commit and tag before creating the draft. Both stable and release-candidate tags remain drafts until you review the tag, notes, assets and provenance and manually publish the release; immutable release protection takes effect on publication. Release candidates use the same checks and provenance as stable releases, with GitHub's prerelease flag and `latest=false`. Protect release tags and require review of the release workflow; do not overwrite full tags or existing releases.

To verify an archive, set its reviewed tag and commit, then run in an empty directory:

Expand Down
9 changes: 5 additions & 4 deletions tests/test_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def git(*args):
assert archive.read_bytes() == contents


def test_release_attests_and_verifies_before_publishing():
def test_release_attests_and_verifies_before_creating_draft():
workflow = yaml.safe_load((ROOT / ".github/workflows/release.yml").read_text())
assert workflow.get("on", workflow.get(True)) == {"push": {"tags": ["v*.*.*"]}}
assert workflow["permissions"] == {"contents": "read"}
Expand All @@ -86,7 +86,7 @@ def test_release_attests_and_verifies_before_publishing():
build = next(i for i, s in enumerate(steps) if s.get("id") == "archive")
attest = next(i for i, s in enumerate(steps) if s.get("id") == "attest")
verify = next(i for i, s in enumerate(steps) if s.get("name") == "Verify provenance")
publish = next(i for i, s in enumerate(steps) if s.get("name") == "Publish release assets")
publish = next(i for i, s in enumerate(steps) if s.get("name") == "Create draft release with verified assets")
assert build < attest < verify < publish
assert steps[attest]["with"]["subject-path"] == "${{ runner.temp }}/release/maida-assert.tar.gz"
assert "--source-digest" in steps[verify]["run"]
Expand All @@ -98,9 +98,9 @@ def test_release_attests_and_verifies_before_publishing():


@pytest.mark.parametrize("classification", ["true", "false", "", "invalid"])
def test_publish_marks_only_release_candidates_as_prereleases(tmp_path, classification):
def test_draft_marks_only_release_candidates_as_prereleases(tmp_path, classification):
workflow = yaml.safe_load((ROOT / ".github/workflows/release.yml").read_text())
step = next(s for s in workflow["jobs"]["release"]["steps"] if s.get("name") == "Publish release assets")
step = next(s for s in workflow["jobs"]["release"]["steps"] if s.get("name") == "Create draft release with verified assets")
release = tmp_path / "release"
release.mkdir()
archive = release / "maida-assert.tar.gz"
Expand Down Expand Up @@ -134,6 +134,7 @@ def test_publish_marks_only_release_candidates_as_prereleases(tmp_path, classifi
args = json.loads(arguments.read_text())
assert args[:3] == ["release", "create", tag]
assert "--verify-tag" in args
assert "--draft" in args
assert ("--prerelease" in args) is (classification == "true")
assert ("--latest=false" in args) is (classification == "true")
assert args[-3:] == ["maida-assert.tar.gz", "SHA256SUMS", "provenance.jsonl"]
Expand Down
Loading