Skip to content

[DEVOPS-12319] Publish SDK releases as draft then publish last, and fix SLSA provenance - #2098

Merged
abacchilb merged 4 commits into
developfrom
ab/DEVOPS-12319-draft-then-publish
Nov 3, 2026
Merged

abacchilb merged 4 commits into
developfrom
ab/DEVOPS-12319-draft-then-publish

Conversation

@abacchilb

@abacchilb abacchilb commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Description

Prepares the SDK release workflow for GitHub Immutable Releases, and fixes SLSA provenance, which has been missing from every release since v7.6.0.

Jira: DEVOPS-12319 (epic DEVOPS-12318). Turning on the repo setting is the follow-up DEVOPS-12321, after this merges.

Why

Once a release is published with immutable releases on, its tag can't be moved and its assets can't be added, replaced or removed. Today publish.yml uploads the wheel, sdist and SLSA file to a release that a maintainer has already published by hand, so turning on the setting as things stand would break every SDK release.

What changes

Release flow (publish.yml)

  • New check-draft-release job runs first. It fails within seconds if the release for the tag is missing or already published, instead of after the production tests.
  • The wheel, sdist and SLSA provenance are uploaded to the release while it is still a draft.
  • New publish-release job runs last. It publishes the draft (gh release edit --draft=false --verify-tag) only after the asset upload, the GitHub attestation and the PyPI publish have all succeeded.

Provenance

  • SLSA fixed. #2051 pinned slsa-github-generator to a commit SHA. The generator has to be called by tag (it uses the tag to fetch and verify its builder, and slsa-verifier checks it), so its upload has failed ever since: v7.7.0 through v7.17.0 have no multiple.intoto.jsonl. It is now called as @v2.1.0, with a comment explaining the exception to SHA pinning. Its own release upload is turned off; publish-python-package-to-release attaches the file to the draft instead.
  • GitHub artifact attestation added for the wheel and sdist (actions/attest-build-provenance, SHA-pinned v4.2.2), in a new attest_python job. gh attestation verify can check it.
  • Container SLSA is also called by tag now, and runs even when production tests are skipped (the same always() pattern pypi-publish uses). Before, it was skipped in that case.
  • PyPI attestations are unchanged; they have been published for every release.

Docs

  • CONTRIBUTING.md: maintainer release steps. Push the tag, save the release as a draft (do not publish it), then run the publish workflow.
  • README.md: the Provenance section covers gh attestation verify and slsa-verifier, and says which past releases have which kind of provenance.

Change for maintainers

Save the release as a draft instead of publishing it before running the workflow. The workflow publishes it at the end. If a run fails partway, the release stays a draft and the run can be retried.

Not changed

lbox-publish.yml still references the SLSA generator by SHA. It is only called from a job in publish.yml that is commented out.

Testing

  • actionlint reports nothing on the changed lines (only existing shellcheck notes on unchanged steps).
  • Not yet run end to end here. The design matches openfga/openfga's release workflow, whose releases are immutable and carry SLSA provenance (for example v1.22.1). It uses the same pieces: a draft release, generator_generic_slsa3.yml@v2.1.0 called by tag with upload-assets: false, gh release upload <tag> to the draft, and gh release edit <tag> --draft=false at the end. Their workflow also flagged two generator issues, both handled here:
    • The generator's built-in upload creates a duplicate draft once a repo has more than 100 releases. This repo has more than 100, which is why its upload is off here.
    • The generator needs contents: write even with upload-assets: false (slsa-github-generator#2044). Correction to an earlier version of this PR: it first granted contents: read, which would have failed. Fixed in the latest commit.
  • What is specific to this repo (the check-draft-release job and the workflow being dispatched from develop rather than triggered by a tag push) is checked by the next real release.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Document change

🤖 Generated with Claude Code


Note

Medium Risk
Changes the critical release pipeline (PyPI, GitHub release state, and supply-chain attestation); mistakes could block releases or leave drafts unpublished, though guards and docs reduce operator error.

Overview
Prepares the publish workflow for GitHub immutable releases by requiring a draft release for the tag up front (check-draft-release), uploading wheel, sdist, and SLSA file to that draft only after prod tests pass, and publishing the release last once GitHub asset upload, artifact attestation, and PyPI publish all succeed.

Provenance fixes: SLSA Python/container generators move from a pinned SHA to @v2.1.0 (tag required for generator/verifier), with upload-assets: false so multiple.intoto.jsonl is attached via gh release upload --clobber instead of the generator (avoids duplicate-draft issues on repos with 100+ releases). Adds attest_python using actions/attest-build-provenance for wheel/sdist. Container SLSA now runs when tests are skipped via always() on success.

Docs: CONTRIBUTING.md documents the maintainer flow (draft → workflow → auto-publish; retry failed jobs; manual publish if PyPI already has the version). README.md updates verification steps (gh attestation verify, simplified slsa-verifier) and notes which historical releases include which attestations.

Reviewed by Cursor Bugbot for commit c2d8c8f. Bugbot is set up for automated code reviews on this repo. Configure here.

abacchilb and others added 2 commits November 3, 2026 08:40
…lish last

Immutable releases reject asset uploads once a release is published. The
publish workflow now requires the release to be a draft, attaches the wheel
and sdist while it is a draft, and publishes the release after PyPI publish
succeeds.

Replace the SLSA generic generator with actions/attest-build-provenance. The
SLSA upload has failed on every release since v7.6.0, so v7.7.0 to v7.17.0
shipped without it. Update the README provenance section and add maintainer
release steps to CONTRIBUTING.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore the SLSA generic provenance (multiple.intoto.jsonl) and attach it to
the draft release with the wheel and sdist. The generator broke when it was
pinned to a SHA in #2051: it must be referenced by tag. Reference v2.1.0 by
tag and turn off its own release upload, which cannot target draft releases.

Also reference the container SLSA generator by tag, and let it run when
production tests are skipped. Restore the slsa-verifier example in the
README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generic generator declares contents: write for its upload job, so the
caller must grant it even with upload-assets: false
(slsa-framework/slsa-github-generator#2044).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c699ea4. Configure here.

Comment thread .github/workflows/publish.yml Resolved
Comment thread .github/workflows/publish.yml Resolved
Address Cursor Bugbot review on #2098:
- Upload to the draft release only after test-build passes or is skipped,
  so a failed test run (as on the first v7.17.0 run) leaves the draft empty.
- Use --clobber so Re-run failed jobs can retry a partial upload. The release
  is still a draft at this point.
- Set the SLSA provenance name explicitly and download it by that name rather
  than relying on the generator output.
- Document retrying with Re-run failed jobs in CONTRIBUTING.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@abacchilb
abacchilb merged commit 98f80bb into develop Nov 3, 2026
10 checks passed
@abacchilb
abacchilb deleted the ab/DEVOPS-12319-draft-then-publish branch November 3, 2026 14:23

This branch was successfully deployed

1 active deployment
Test-PyPI — c2d8c8f0 Deployed Nov 3, 2026 by abacchilb via test-pypi #2575
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants