|
| 1 | +# GitHub Releases |
| 2 | + |
| 3 | +This guide sets up reviewed Ruby gem releases with `bake-gem-github`, native GitHub rules, RubyGems Trusted Publishing, and retained release artifacts. |
| 4 | + |
| 5 | +## Installation |
| 6 | + |
| 7 | +Add `bake-gem-github` and `agent-context` to your maintenance bundle. This companion requires `bake-gem` 0.15 or later for branch preparation and regeneration validation. Install the maintenance group in CI with `BUNDLE_WITH=maintenance`. |
| 8 | + |
| 9 | +Use one gemspec, a stable three-part version in `lib/.../version.rb`, and repeatable `after_gem_release_version_increment` hooks. Hooks run from a clean base during validation. Commit dependency locks when practical; changing generation tools or using live network/time inputs can make old release content fail validation. |
| 10 | + |
| 11 | +## Setup and migration |
| 12 | + |
| 13 | +Run setup in each repository. It discovers the canonical repository and default branch through `gh` and generates reviewable local files. Supply the actual required CI job names, including supported matrix entries: |
| 14 | + |
| 15 | +``` bash |
| 16 | +bundle exec bake gem:github:setup checks="Test Ruby 3.3,Test Ruby 3.4,RuboCop" |
| 17 | +bundle exec bake agent:context:install |
| 18 | +bundle exec bake gem:github:setup:plan |
| 19 | +``` |
| 20 | + |
| 21 | +Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/RELEASING.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. For later policy changes, edit the configuration and review the generated workflow/policy changes explicitly. Setup does not replace other publishers: remove conflicting release workflows during migration. |
| 22 | + |
| 23 | +The default is two approvals with explicit administrator bypass, dismissed stale reviews, approval of the last push, strict up-to-date CI, and immutable default-branch history/release tags. These branch rules affect **all PRs** into the default branch. Ordinary administrator reviews count as one review. A human who dispatches a bot-authored PR is not its author under GitHub's native rules. |
| 24 | + |
| 25 | +Review `gem:github:setup:plan`, merge the setup PR, and confirm that **Release validation** and every selected check run. Then apply the four managed rulesets using an administrator's `gh` login: |
| 26 | + |
| 27 | +``` bash |
| 28 | +bundle exec bake gem:github:setup:apply |
| 29 | +``` |
| 30 | + |
| 31 | +This command changes remote rulesets and preserves unrelated rulesets. Existing rulesets with the four managed names are updated. Organization rules and other existing protections still apply. Check names in configuration must exactly match GitHub checks; a partial selection does not mean all CI is required. Keep rebase merging and merge queues disabled for the initial rollout. |
| 32 | + |
| 33 | +Create a `rubygems` GitHub environment restricted to the default branch. Do not add a second routine reviewer gate. On RubyGems, an owner must configure a Trusted Publisher with the owner/repository, workflow filename **`release-publish.yaml`**, and environment **`rubygems`** shown by `doctor`. Ownership/MFA and environment/signing bootstrap are deliberate manual steps in this first implementation; `doctor` prints desired and observed GitHub settings, not a claim that RubyGems ownership or publisher trust has been verified. See [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/). |
| 34 | + |
| 35 | +If `release.cert` exists, setup enables legacy signing. Keep this public certificate in Git and install its matching **private key** as the environment secret `GEM_SIGNING_KEY`. The workflow checks the certificate validity, key match, and resulting package signatures. To opt out explicitly, pass `signing=false` during setup. No long-lived RubyGems publishing key is required. |
| 36 | + |
| 37 | +Before enabling releases, confirm two people can administer the repository and recover the RubyGems account/signing key, and enough maintainers can satisfy the review policy. Pilot on one low-risk gem and prove publishing, administrator bypass, fork merges, and recovery before rolling out broadly. No organization-wide migration or live publisher setup is performed by these tasks. |
| 38 | + |
| 39 | +## Request and review |
| 40 | + |
| 41 | +``` bash |
| 42 | +# Local branch and commit only: |
| 43 | +bundle exec bake gem:release:branch:patch |
| 44 | + |
| 45 | +# From the current default branch: prepare, validate, push and open PR: |
| 46 | +bundle exec bake gem:github:release:patch |
| 47 | + |
| 48 | +# Remote request (also available in the Actions UI): |
| 49 | +gh workflow run release-prepare.yaml -f bump=patch |
| 50 | +``` |
| 51 | + |
| 52 | +Replace `patch` with `minor` or `major`. The wrapper fetches the default branch and tags, refuses a stale local checkout, and reports an existing release PR instead of opening another. GitHub's built-in token may require a writer to approve running workflows for its created PR; enable Actions' permission to create PRs. An organization-owned App token can be adopted later if automatic CI triggering is needed. |
| 53 | + |
| 54 | +All release changes belong in the PR. Core preparation commits additions and deletions from release hooks but never pushes, tags or publishes. Validation independently generates the expected tree from the current base. A changed base SHA alone is fine; changed generated notes are not. Ordinary PRs with no version change pass release validation and still build unsigned. |
| 55 | + |
| 56 | +If regeneration fails, prepare a new branch from the current default branch and review the new diff. Preserve manual release-branch edits separately. Automatic refresh/force-push is not implemented. A failure during preparation leaves the branch and generated changes available for inspection. |
| 57 | + |
| 58 | +## Publish and verify |
| 59 | + |
| 60 | +After merge/squash, the publishing workflow verifies GitHub's merged PR record and ancestry, then checks out the exact merged commit. Later development on the default branch is allowed. It regenerates against the merged commit's **first parent**, builds in a clean worktree, optionally certificate-signs, and creates two attestations over the final bytes: |
| 61 | + |
| 62 | +- A Sigstore bundle submitted explicitly with `gem push --attestation` using RubyGems 4.0.21. |
| 63 | +- GitHub SLSA provenance naming the exact checked-out commit, including when the workflow's own default-branch revision is newer. |
| 64 | + |
| 65 | +The workflow retains the gem, receipt and attestations before obtaining RubyGems publishing credentials. It verifies both attestations, checks the uploaded bytes and registry bundle, then pushes the specific version tag and creates the GitHub release. Existing tags/assets are checked and never overwritten. The old `after_gem_release` GitHub hook is not called by this pipeline, so there is one owner for release creation. |
| 66 | + |
| 67 | +``` bash |
| 68 | +gh attestation verify example-1.2.3.gem \ |
| 69 | + --repo OWNER/REPOSITORY \ |
| 70 | + --source-digest MERGED_SHA \ |
| 71 | + --signer-workflow OWNER/REPOSITORY/.github/workflows/release-publish.yaml |
| 72 | + |
| 73 | +gem exec sigstore-cli:0.2.3 verify example-1.2.3.gem \ |
| 74 | + --bundle example-1.2.3.gem.sigstore.json \ |
| 75 | + --certificate-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \ |
| 76 | + --certificate-oidc-issuer https://token.actions.githubusercontent.com |
| 77 | +``` |
| 78 | + |
| 79 | +Native GitHub records the merge and any bypass; the artifact receipt includes the PR and merging actor. This version does not export organization audit-log evidence or infer bypass reasons from review counts. |
| 80 | + |
| 81 | +## Recovery |
| 82 | + |
| 83 | +Use **Re-run all jobs** on the original publishing run, or: |
| 84 | + |
| 85 | +``` bash |
| 86 | +bundle exec bake gem:github:release:resume run=RUN_ID |
| 87 | +``` |
| 88 | + |
| 89 | +Rerunning keeps the original event identity. A retained artifact is downloaded and its source identity/digest checked. A matching registry version resumes tag/release finalization; different bytes or a conflicting tag stop. There is no automatic yank, retag, or rebuild of an already-published version. If failure occurs before artifact retention, rebuilding is safe because uploading has not started. |
| 90 | + |
| 91 | +Artifacts are retained for 90 days and copied to GitHub release assets on success. If the workflow artifact expires before finalization, manual recovery from the preserved bytes is required; do not dispatch a replacement build. GitHub concurrency does not guarantee a durable FIFO queue: rerun any publishing run displaced while pending. Resume reruns all jobs, including integrity checks; it does not repeat or second-guess the native review policy or a permitted administrator bypass. |
| 92 | + |
| 93 | +## Development and current limits |
| 94 | + |
| 95 | +The implementation has local repository and transport-fake tests. A real GitHub/RubyGems pilot remains necessary before enabling it across Socketry. Public single-gem repositories, ordinary stable versions, merge/squash, GitHub-hosted Linux runners, and RubyGems.org are the supported starting point. Native build matrices, reusable publisher workflows, merge queues, automated RubyGems ownership/MFA setup, cross-run artifact recovery, and organization-wide rollout are deferred. |
| 96 | + |
| 97 | +Edit this guide and regenerate `context/` with `bake utopia:project:agent:context:update`. Consumers install the generated guidance using `agent-context`. |
0 commit comments