bake-gem-github 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec4e3e7088789843fbadf6a3b8a679593e5b24e328fce5301e63fe79a10dfdf7
4
- data.tar.gz: 77857229f0d478caba8181e9bdd1f5d81b2198d377cf37c9826c461531eab37d
3
+ metadata.gz: da7e92debf8a08450ce8af6da25ed171d4961726d5949b9a0c737f3849c613eb
4
+ data.tar.gz: 85459639500f5e4af61e4d305fe0442ddff853bb8530952385e89db39db6e69c
5
5
  SHA512:
6
- metadata.gz: 046244ece39b8e166674a1368781012123175f4bd2902ea4777da518f86b148f8a85bcd0c4eda889291bc7a6e00617992dd8f35c1fef8e156e883af1458abb37
7
- data.tar.gz: 0cea0e0fcbd5c3b2d8c04c432698b520a1ab9883781a5969649cb8e122cafe7657385501cc501028f3fd509de0f84d096216409af32ceecd1fef497110796a97
6
+ metadata.gz: 3919da7f3c6101666341e4d9af3b4176e400a2684b3f7cc996f8c04450b1398701512a5e5bbc9c00155d149178c1d17fc056353187a99789be8a4ab9c9a50ebb
7
+ data.tar.gz: 22dbd706b01561188cb1ccf8aece57b417b6e8a5edef01c22efb0ee883c41048602a356a5b0abc91cac81b24d600bd9d6e37c8d47fe1dd13a71865336cdc94e5
checksums.yaml.gz.sig CHANGED
Binary file
@@ -3,49 +3,70 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
- require_relative "../../../lib/bake/gem/github/publisher"
6
+ require "bake/gem/github/project"
7
7
 
8
8
  # Prepare a patch release and open its PR.
9
- def patch
10
- Bake::Gem::GitHub::Project.new(context.root).prepare(context, "patch")
9
+ # @parameter refresh [Boolean] Preserve and regenerate an existing release branch.
10
+ # @returns [String] The release PR URL.
11
+ def patch(refresh: false)
12
+ Bake::Gem::GitHub::Project.new(context.root).prepare(context, "patch", refresh: refresh)
11
13
  end
12
14
 
13
15
  # Prepare a minor release and open its PR.
14
- def minor
15
- Bake::Gem::GitHub::Project.new(context.root).prepare(context, "minor")
16
+ # @parameter refresh [Boolean] Preserve and regenerate an existing release branch.
17
+ # @returns [String] The release PR URL.
18
+ def minor(refresh: false)
19
+ Bake::Gem::GitHub::Project.new(context.root).prepare(context, "minor", refresh: refresh)
16
20
  end
17
21
 
18
22
  # Prepare a major release and open its PR.
19
- def major
20
- Bake::Gem::GitHub::Project.new(context.root).prepare(context, "major")
23
+ # @parameter refresh [Boolean] Preserve and regenerate an existing release branch.
24
+ # @returns [String] The release PR URL.
25
+ def major(refresh: false)
26
+ Bake::Gem::GitHub::Project.new(context.root).prepare(context, "major", refresh: refresh)
21
27
  end
22
28
 
23
29
  # Resolve and validate a merged PR, emitting a commit output for the publishing job.
30
+ # @parameter number [String] The merged PR number; defaults to `RELEASE_PR`.
31
+ # @returns [Hash | Nil] Release metadata, or nil for an ordinary PR.
24
32
  def resolve(number: ENV.fetch("RELEASE_PR"))
25
33
  result = Bake::Gem::GitHub::Project.new(context.root).inspect_release(number)
34
+
26
35
  if path = ENV["GITHUB_OUTPUT"]
27
36
  File.open(path, "a") do |file|
28
37
  file.puts "release=#{!result.nil?}"
29
38
  file.puts "commit=#{result.fetch(:commit)}" if result
30
39
  end
31
40
  end
32
- result
41
+
42
+ return result
33
43
  end
34
44
 
35
45
  # Build or restore the exact artifact for a merged release PR.
46
+ # @parameter number [String] The merged release PR number; defaults to `RELEASE_PR`.
47
+ # @returns [Hash] The built or restored release receipt.
36
48
  def build(number: ENV.fetch("RELEASE_PR"))
37
- Bake::Gem::GitHub::Publisher.new(context.root).build(number)
49
+ require "bake/gem/github/publisher"
50
+
51
+ return Bake::Gem::GitHub::Publisher.new(context.root).build(number)
38
52
  end
39
53
 
40
54
  # Verify, upload and finalize a merged release using its retained artifact.
55
+ # @parameter number [String] The merged release PR number; defaults to `RELEASE_PR`.
56
+ # @returns [Hash] The published release receipt.
41
57
  def publish(number: ENV.fetch("RELEASE_PR"))
42
- Bake::Gem::GitHub::Publisher.new(context.root).publish(number)
58
+ require "bake/gem/github/publisher"
59
+
60
+ return Bake::Gem::GitHub::Publisher.new(context.root).publish(number)
43
61
  end
44
62
 
45
63
  # Rerun the original publishing workflow, preserving event identity and artifact bytes.
64
+ # @parameter run [String] The original release-publish workflow run ID.
65
+ # @returns [Boolean] True when GitHub accepts the rerun request.
46
66
  def resume(run:)
47
67
  project = Bake::Gem::GitHub::Project.new(context.root)
48
68
  details = project.api("actions/runs/#{Integer(run)}")
49
69
  raise "Expected a release-publish workflow run." unless details.fetch("path") == ".github/workflows/release-publish.yaml"
50
- project.system("gh", "run", "rerun", run.to_s, "--repo", project.config.fetch("repository"), chdir: context.root)
70
+
71
+ return project.system("gh", "run", "rerun", run.to_s, "--repo", project.config.fetch("repository"), chdir: context.root)
51
72
  end
@@ -4,18 +4,23 @@
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
6
  # Inspect external settings before applying the generated policy.
7
+ # @returns [Hash] Desired and observed settings for review.
7
8
  def plan
8
9
  context.lookup("gem:github:doctor").call
9
10
  end
10
11
 
11
- # Apply the four managed rulesets using the current gh administrator credentials.
12
+ # Apply the four managed rulesets and configured environment reviewers using the current gh administrator credentials.
13
+ # @returns [Hash] The managed ruleset payloads after successful application.
12
14
  def apply
13
- require_relative "../../../lib/bake/gem/github/project"
15
+ require "bake/gem/github/project"
16
+
14
17
  Bake::Gem::GitHub::Project.new(context.root).apply
15
18
  end
16
19
 
17
20
  # Update generated files in the working tree using config/release.yaml and the installed templates.
21
+ # @returns [Array(String)] Changed paths relative to the repository root.
18
22
  def update
19
- require_relative "../../../lib/bake/gem/github/setup"
23
+ require "bake/gem/github/setup"
24
+
20
25
  Bake::Gem::GitHub::Setup.new(context.root).update
21
26
  end
data/bake/gem/github.rb CHANGED
@@ -3,29 +3,43 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
- # Generate local release workflows, policy, and documentation for review.
6
+ # Generate local release workflows, policy payloads, and configuration for review.
7
7
  # @parameter checks [Array(String)] Required CI check names, including matrix entries.
8
- # @parameter repository [String] Canonical owner/repository.
9
- # @parameter branch [String] Default branch name.
8
+ # @parameter repository [String] Canonical owner/repository; discovered through GitHub when omitted.
9
+ # @parameter branch [String] Default branch name; discovered through GitHub when omitted.
10
10
  # @parameter approvals [Integer] Number of approving reviews.
11
- # @parameter signing [Boolean] Require legacy certificate signing.
11
+ # @parameter reviewers [Array(String)] Publishing environment reviewers: user logins or organization/team names. Omit to leave environment settings unmanaged.
12
+ # @parameter signing [Boolean] Require certificate signing; when omitted, enable it if `release.cert` exists.
12
13
  # @parameter ruby [String] Ruby version for release workflows.
13
- def setup(checks:, repository: nil, branch: nil, approvals: 2, signing: nil, ruby: "3.4")
14
- require_relative "../../lib/bake/gem/github/setup"
14
+ # @returns [Array(String)] Generated paths relative to the repository root.
15
+ def setup(checks:, repository: nil, branch: nil, approvals: 2, reviewers: nil, signing: nil, ruby: "3.4")
16
+ require "bake/gem/github/setup"
15
17
  require "bake/gem/shell"
18
+
16
19
  helper = Object.new.extend(Bake::Gem::Shell)
17
20
  remote = if repository && branch
18
21
  {}
19
22
  else
20
23
  JSON.parse(helper.readlines("gh", "repo", "view", "--json", "nameWithOwner,defaultBranchRef", chdir: context.root).join)
21
24
  end
22
- options = {repository: repository || remote.fetch("nameWithOwner"), branch: branch || remote.fetch("defaultBranchRef").fetch("name"), checks: checks, approvals: approvals, ruby: ruby}
25
+
26
+ options = {
27
+ repository: repository || remote.fetch("nameWithOwner"),
28
+ branch: branch || remote.fetch("defaultBranchRef").fetch("name"),
29
+ checks: checks,
30
+ approvals: approvals,
31
+ reviewers: reviewers,
32
+ ruby: ruby,
33
+ }
23
34
  options[:signing] = signing unless signing.nil?
35
+
24
36
  Bake::Gem::GitHub::Setup.new(context.root).generate(**options)
25
37
  end
26
38
 
27
- # Show the desired rules, existing rules, environments, and RubyGems bootstrap values.
39
+ # Show the desired rules and environment reviewers, existing settings, and RubyGems bootstrap values.
40
+ # @returns [Hash] Desired and observed settings; RubyGems values describe the expected configuration.
28
41
  def doctor
29
- require_relative "../../lib/bake/gem/github/project"
42
+ require "bake/gem/github/project"
43
+
30
44
  Bake::Gem::GitHub::Project.new(context.root).doctor
31
45
  end
@@ -1,121 +1,122 @@
1
- # GitHub Releases
1
+ # Getting Started
2
2
 
3
- This guide explains how to set up reviewed Ruby gem releases with `bake-gem-github`, native GitHub rules, RubyGems Trusted Publishing, and retained release artifacts.
3
+ This guide explains how to configure reviewed Ruby gem releases and prepare the first release PR with `bake-gem-github`.
4
4
 
5
- ## Installation
5
+ ## How releases work
6
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`.
7
+ Maintainers prepare a release PR containing the version bump and generated release notes. CI regenerates those changes from the current base to verify the content. Native GitHub rules control approval and merging; after merge, GitHub Actions builds the exact merged commit and publishes its verified artifact to RubyGems.
8
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.
9
+ `bake-gem` provides version updates, release hooks, and clean builds. `bake-gem-github` adds PR preparation, GitHub policy, and remote publishing. The supported process uses one gemspec, stable three-part versions, merge or squash merging, and RubyGems.org.
10
10
 
11
- ## Setup and migration
11
+ ## Installation
12
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:
13
+ Add these dependencies to the maintenance group in `gems.rb`:
14
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
15
+ ``` ruby
16
+ group :maintenance, optional: true do
17
+ gem "bake-gem-github"
18
+ gem "agent-context"
19
+ end
19
20
  ```
20
21
 
21
- Setup adds three release workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. Setup does not replace other publishers: remove conflicting release workflows during migration.
22
-
23
- To adopt template fixes after upgrading the gem, start from a clean working tree, edit `config/release.yaml` as needed, and regenerate:
22
+ Install that group and its guidance:
24
23
 
25
24
  ``` bash
26
- bundle exec bake gem:github:setup:update
27
- git diff
25
+ bundle config set --local with maintenance
26
+ bundle install
27
+ bundle exec bake agent:context:install
28
28
  ```
29
29
 
30
- The task updates the managed workflows, policy payloads, configuration formatting, and maintainer instructions directly in the working tree and returns the changed paths. It does not stage, commit, or change remote settings. An agent or maintainer can review the diff and selectively keep changes, restoring repository-specific customizations from Git where needed. Commit or stash existing edits first: generated files are replaced by the current templates. Repeating an update produces no further changes; intentionally retained customizations will appear in later update diffs. Apply remote rulesets after the corresponding workflows are running.
30
+ The companion requires `bake-gem` 0.15 or later. Its generated release workflows install maintenance dependencies with `BUNDLE_WITH=maintenance`.
31
31
 
32
- Release workflows follow `bake modernize` action versions and use moving major tags where upstream provides them. These tags receive upstream updates automatically; full commit hashes select fixed revisions. The RubyGems credentials action uses its [documented `@main` reference](https://github.com/rubygems/configure-rubygems-credentials#trusted-publisher-recommended), since upstream does not provide a moving major tag. Repositories that require fixed revisions can customize these references.
32
+ Use a version constant in `lib/.../version.rb` and repeatable `after_gem_release_version_increment` hooks. Validation invokes these hooks from a clean base. Generation must not depend on changing network responses or the current time; review dependency updates that could alter generated content.
33
33
 
34
- 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.
34
+ ## Generate repository configuration
35
35
 
36
- 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:
36
+ Run setup from the repository root with the actual required CI job names, including supported matrix entries:
37
37
 
38
38
  ``` bash
39
- bundle exec bake gem:github:setup:apply
39
+ bundle exec bake gem:github:setup checks="3.3 on ubuntu,3.3 on macos,3.4 on ubuntu,3.4 on macos,4.0 on ubuntu,4.0 on macos,check,ruby on ubuntu,ruby on macos,validate"
40
+ bundle exec bake gem:github:setup:plan
40
41
  ```
41
42
 
42
- 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.
43
+ This example follows the standard `bake modernize` test, RuboCop, and coverage job names. Select the jobs produced by your repository. Experimental Ruby jobs are not required. Setup adds `Release validation` automatically.
43
44
 
44
- 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/).
45
+ Setup discovers the canonical GitHub repository and default branch through `gh`. It generates three release workflows, `config/release.yaml`, and four native ruleset payloads. Identical reruns do nothing; conflicting existing files stop generation before any file is written. Review and commit the files in a setup PR, and remove conflicting publishing workflows.
45
46
 
46
- If `release.cert` exists, setup enables legacy signing. Keep this public certificate in Git and install its matching **private key** as the Actions secret `GEM_SIGNING_KEY`. Use the `rubygems` environment or an organization secret available to the release repositories. 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.
47
+ The rules require two approvals by default, allow explicit administrator bypass, dismiss stale reviews, require approval of the last push, and require up-to-date CI. They protect default-branch history and release tags against deletion or replacement. These branch rules apply to **all PRs** into the default branch. An ordinary administrator approval counts as one review; bypass is a separate action.
47
48
 
48
- 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.
49
+ ## Configure publishing credentials
49
50
 
50
- ## Request and review
51
+ Create a `rubygems` GitHub environment restricted to the default branch. On RubyGems, an owner must configure a Trusted Publisher with the values printed by `gem:github:setup:plan`: the owner/repository, workflow filename **`release-publish.yaml`**, and environment **`rubygems`**. See [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) for the account setup.
51
52
 
52
- ``` bash
53
- # Local branch and commit only:
54
- bundle exec bake gem:release:branch:patch
53
+ Ownership, MFA, and signing bootstrap are manual setup steps. The plan reports expected RubyGems values; it does not verify ownership or publisher trust. Trusted Publishing supplies the publishing credential for each run, so a long-lived RubyGems API key is unnecessary.
55
54
 
56
- # From the current default branch: prepare, validate, push and open PR:
57
- bundle exec bake gem:github:release:patch
55
+ When `release.cert` exists, setup enables certificate signing. Commit the public certificate and install its matching private key as `GEM_SIGNING_KEY`, either in the `rubygems` environment or as an organization secret available to the repository. The publisher checks certificate validity, key matching, and package signatures. Use `signing=false` during setup to disable certificate signing.
56
+
57
+ Ensure another maintainer can administer the repository and recover its RubyGems account and signing key.
58
58
 
59
- # Remote request (also available in the Actions UI):
60
- gh workflow run release-prepare.yaml -f bump=patch
59
+ ## Authorize publishing
60
+
61
+ PR reviews approve the source changes. To require a release manager to authorize publication after merge, configure required reviewers on the `rubygems` environment. The publishing job waits for this separate approval before running.
62
+
63
+ Pass `reviewers=your-org/managers` to `gem:github:setup`, or add the reviewers to an existing `config/release.yaml`:
64
+
65
+ ``` yaml
66
+ reviewers:
67
+ - your-org/managers
61
68
  ```
62
69
 
63
- 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.
70
+ Replace `your-org/managers` with your organization and team slug, for example `socketry/managers`. Individual user logins are also supported. Reviewers need at least read access to the repository. Setup resolves their GitHub IDs without changing repository access or team membership.
64
71
 
65
- 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.
72
+ GitHub accepts one to six users or teams, and **one approval from any listed reviewer or team member is sufficient**. It does not support a minimum environment approval count. The default two PR approvals are independent of this publishing approval.
66
73
 
67
- 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.
74
+ Create the environment and restrict its deployment branch as described above before running plan or apply with reviewers configured. The plan previews the current and desired environment settings. Apply replaces its reviewer list while preserving its wait timer, self-review prevention, administrator bypass setting, and deployment branch restrictions. Custom deployment protection rules are managed separately and are not modified. Reapplying an identical reviewer list leaves the environment unchanged.
68
75
 
69
- ## Publish and verify
76
+ Omitting `reviewers` leaves environment settings unmanaged, including any existing reviewer requirement. An empty list is rejected. To remove an existing requirement, change the environment settings explicitly in GitHub.
70
77
 
71
- 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:
78
+ The RubyGems Trusted Publisher must explicitly require the `rubygems` environment; leaving that field blank would allow this trusted publisher to authenticate jobs without the environment approval. Keep administrator bypass enabled if administrators should be able to explicitly authorize publication without a reviewer. Environment approvals are available for public repositories on GitHub Free.
72
79
 
73
- - A Sigstore bundle submitted explicitly with `gem push --attestation` using RubyGems 4.0.21.
74
- - GitHub's native SLSA provenance covering both the gem and `release.json`. This signed receipt binds the gem digest to the exact release commit, even when the workflow's own default-branch revision is newer.
80
+ ## Enable the policy
75
81
 
76
- 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.
82
+ Merge the setup PR and confirm every selected CI job, including **Release validation**, runs. Review the plan again, then apply its rules using an administrator's `gh` login:
77
83
 
78
- The draft release description includes the exact version's notes from `releases.md` in the merged release checkout, followed by the PR URL, source commit, and gem digest. Notes are extracted using `bake-releases`; a missing or empty section leaves the metadata as the description. Retries preserve the existing release description.
84
+ ``` bash
85
+ bundle exec bake gem:github:setup:plan
86
+ bundle exec bake gem:github:setup:apply
87
+ ```
88
+
89
+ Apply updates the four managed rulesets and, when configured, the existing environment's reviewer list. It preserves unrelated rulesets. Other repository and organization protections still apply. Keep check names in `config/release.yaml` synchronized with the workflows, and apply updated rules after renamed jobs are available. Keep rebase merging and merge queues disabled for this process.
90
+
91
+ ## Prepare the first release PR
92
+
93
+ From an up-to-date default branch:
79
94
 
80
95
  ``` bash
81
- set -e
82
- for file in example-1.2.3.gem release.json; do
83
- gh attestation verify "$file" \
84
- --repo OWNER/REPOSITORY --bundle provenance.sigstore.json \
85
- --cert-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
86
- --source-ref refs/heads/main --deny-self-hosted-runners
87
- done
88
-
89
- jq -e --arg commit MERGED_SHA \
90
- --arg digest "$(shasum -a 256 example-1.2.3.gem | cut -d ' ' -f1)" \
91
- '.commit == $commit and .sha256 == $digest' release.json
92
-
93
- gem exec sigstore-cli:0.2.3 verify example-1.2.3.gem \
94
- --bundle example-1.2.3.gem.sigstore.json \
95
- --certificate-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
96
- --certificate-oidc-issuer https://token.actions.githubusercontent.com
96
+ bundle exec bake gem:github:release:patch
97
97
  ```
98
98
 
99
- Download `release.json` and `provenance.sigstore.json` alongside the gem. Verify both subjects before reading the receipt's source commit and digest. GitHub CLI's `--source-digest` checks the workflow revision, which may differ from the release commit recorded in the signed receipt. Replace `main` with the configured default branch in these commands.
99
+ The task prepares, validates, pushes, and opens the release PR. Review its version and release notes, wait for CI, and merge under the repository's approval policy. If environment reviewers are configured, a release manager then approves the publishing job in GitHub Actions. The publish workflow builds the merged release, verifies and preserves the artifact, publishes to RubyGems, and finalizes the version tag and GitHub release.
100
100
 
101
- Native GitHub records the merge and any bypass; the signed artifact receipt includes the PR and merging actor. This version does not export organization audit-log evidence or infer bypass reasons from review counts.
101
+ See [Preparing Releases](../preparing-releases/index) for remote requests and stale-content refresh, [Verifying Releases](../verifying-releases/index) for artifact checks, and [Recovering Releases](../recovering-releases/index) when a workflow stops partway through.
102
102
 
103
- ## Recovery
103
+ ## Update generated files
104
104
 
105
- Use **Re-run all jobs** on the original publishing run, or:
105
+ After upgrading the gem, start from a clean working tree and regenerate using your existing configuration:
106
106
 
107
107
  ``` bash
108
- bundle exec bake gem:github:release:resume run=RUN_ID
108
+ bundle exec bake gem:github:setup:update
109
+ git diff
109
110
  ```
110
111
 
111
- 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. Registry propagation is retried every ten seconds for up to one minute; a digest or attestation mismatch fails immediately.
112
+ This updates managed files in the working tree and returns their changed paths. Review the diff and selectively retain repository customizations before committing. The task does not stage, commit, or change remote settings. Repeated updates produce no further changes unless customizations differ from the templates. Apply changed rulesets after the corresponding workflows are running.
112
113
 
113
- Before uploading to RubyGems, the publisher stores the verified gem, receipt and both attestation bundles in a draft GitHub release targeting the merged commit. It publishes the draft after registry verification and tag creation. Actions artifacts are also retained for 90 days, but can disappear on rerun. Recovery falls back to the draft or published release and verifies the original bytes and attestations. Keep the draft until finalization succeeds. If asset preservation was interrupted and neither backup is complete, restore the missing original files before retrying; conflicting assets are never overwritten.
114
+ The release workflows follow `bake modernize` action versions and use moving major tags where available. The RubyGems credentials action uses its [documented `@main` reference](https://github.com/rubygems/configure-rubygems-credentials#trusted-publisher-recommended). Repositories that require fixed revisions can customize these references.
114
115
 
115
- 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. Older publishing runs execute their original code; adding this recovery support to the default branch does not change an already-triggered workflow.
116
+ ## Current scope
116
117
 
117
- ## Development and current limits
118
+ The process has published `bake-gem-github` through GitHub Actions. Each adopting repository still needs its own reviewed setup and successful release. Public single-gem repositories, ordinary stable versions, merge/squash, GitHub-hosted Linux runners, and RubyGems.org are the supported starting point.
118
119
 
119
- 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.
120
+ Native build matrices, reusable publisher workflows, merge queues, automated RubyGems ownership/MFA setup, cross-run artifact recovery, and organization-wide migration are outside the current setup tasks.
120
121
 
121
- Edit this guide and regenerate `context/` with `bake utopia:project:agent:context:update`. Consumers install the generated guidance using `agent-context`.
122
+ Edit source guides under `guides/` and regenerate the distributed guidance with `bundle exec bake utopia:project:agent:context:update`. Consumers install it through `agent-context`.
data/context/index.yaml CHANGED
@@ -2,9 +2,25 @@
2
2
  # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3
3
  ---
4
4
  description: Reviewable GitHub releases for Ruby gems.
5
- metadata: {}
5
+ metadata:
6
+ documentation_uri: https://socketry.github.io/bake-gem-github/
7
+ bug_tracker_uri: https://github.com/socketry/bake-gem-github/issues
8
+ changelog_uri: https://github.com/socketry/bake-gem-github/blob/main/releases.md
9
+ source_code_uri: https://github.com/socketry/bake-gem-github.git
6
10
  files:
7
11
  - path: getting-started.md
8
- title: GitHub Releases
9
- description: This guide explains how to set up reviewed Ruby gem releases with `bake-gem-github`,
10
- native GitHub rules, RubyGems Trusted Publishing, and retained release artifacts.
12
+ title: Getting Started
13
+ description: This guide explains how to configure reviewed Ruby gem releases and
14
+ prepare the first release PR with `bake-gem-github`.
15
+ - path: preparing-releases.md
16
+ title: Preparing Releases
17
+ description: This guide explains how to request a release PR, resume interrupted
18
+ preparation, and refresh generated content when the default branch changes.
19
+ - path: verifying-releases.md
20
+ title: Verifying Releases
21
+ description: This guide explains how publishing binds a gem to its reviewed source
22
+ and how to verify the downloaded artifact and attestations.
23
+ - path: recovering-releases.md
24
+ title: Recovering Releases
25
+ description: This guide explains how to resume an interrupted publishing workflow
26
+ using the original gem and its verification evidence.
@@ -0,0 +1,40 @@
1
+ # Preparing Releases
2
+
3
+ This guide explains how to request a release PR, resume interrupted preparation, and refresh generated content when the default branch changes.
4
+
5
+ Complete [Getting Started](../getting-started/index) first. Release preparation uses {ruby Bake::Gem::GitHub::Project#prepare} to coordinate the core Bake tasks and GitHub operations. Run local tasks from the repository root so gemspec paths resolve correctly.
6
+
7
+ ## Request a release
8
+
9
+ ``` bash
10
+ # Local branch and commit only:
11
+ bundle exec bake gem:release:branch:patch
12
+
13
+ # From the current default branch: prepare, validate, push and open PR:
14
+ bundle exec bake gem:github:release:patch
15
+
16
+ # Remote request (also available in the Actions UI):
17
+ gh workflow run release-prepare.yaml -f bump=patch
18
+ ```
19
+
20
+ Replace `patch` with `minor` or `major`. The wrapper fetches the default branch and tags, refuses a stale local checkout, and validates an existing release PR before returning its URL. A matching local or remote branch is reused if PR creation was interrupted. Multiple open release PRs or a different requested bump stop preparation. 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.
21
+
22
+ 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.
23
+
24
+ ## Resume interrupted preparation
25
+
26
+ If preparation stops after creating or pushing the release branch, return to the current default branch and repeat the same command. The existing branch is validated and reused, so retries do not create a second version bump or PR. Resolve any uncommitted changes before switching branches.
27
+
28
+ ## Refresh stale content
29
+
30
+ When new changes alter the generated release notes or other artifacts, rebasing the branch alone does not regenerate them. Validation reports the stale content. Explicitly refresh the same release:
31
+
32
+ ``` bash
33
+ git switch main
34
+ git pull --ff-only
35
+ bundle exec bake gem:github:release:patch refresh=true
36
+ # Or dispatch remotely:
37
+ gh workflow run release-prepare.yaml -f bump=patch -f refresh=true
38
+ ```
39
+
40
+ Refresh first pushes the complete previous release commit to `release-backups/vVERSION/OLD_SHA`, including manual edits. It then regenerates in a clean worktree from the current default branch, validates, and updates the existing release branch using an explicit `--force-with-lease`. A concurrent remote edit causes the push to fail. Existing local release branches are left intact. Review the backup against the refreshed PR; incorporate necessary manual changes into the default branch or generation hooks and refresh again. Keep the backup until that review is complete. Replace `main` and `patch` with your configured branch and original bump type.
@@ -0,0 +1,27 @@
1
+ # Recovering Releases
2
+
3
+ This guide explains how to resume an interrupted publishing workflow using the original gem and its verification evidence.
4
+
5
+ Use this when a workflow fails during preservation, upload, registry propagation, or tag/release finalization. For failures while creating the PR, see [Preparing Releases](../preparing-releases/index). Publishing recovery retains the original source and artifact bytes.
6
+
7
+ ## Rerun the original workflow
8
+
9
+ Use **Re-run all jobs** on the original publishing run, or:
10
+
11
+ ``` bash
12
+ bundle exec bake gem:github:release:resume run=RUN_ID
13
+ ```
14
+
15
+ Rerunning keeps the original event identity. GitHub may request publishing environment approval again. 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. Registry propagation is retried every ten seconds for up to one minute; a digest or attestation mismatch fails immediately.
16
+
17
+ ## Restore retained artifacts
18
+
19
+ Before uploading to RubyGems, the publisher stores the verified gem, receipt and both attestation bundles together in `release.tar`, uploaded as one draft-release asset before their individual assets. The draft targets the merged commit. It publishes the draft after registry verification and tag creation. Actions artifacts are also retained for 90 days, but can disappear on rerun. Recovery falls back to `release.tar` in the draft or published release, checking its digest and requiring exactly the four expected regular files before restoring them. It verifies the original bytes and attestations, then resumes any missing individual asset uploads. Older releases without an archive can still restore their four individual assets. Existing assets and backups are compared with the original files and never replaced with conflicting content.
20
+
21
+ ## Handle incomplete preservation
22
+
23
+ A rerun can recover an interrupted individual asset upload once `release.tar` is available, even if the Actions artifact has disappeared. An available Actions artifact can also resume an interrupted archive upload. If neither backup completed, restore the missing original files manually; the publisher stops before uploading to RubyGems. Keep the draft until finalization succeeds. A published version is never rebuilt to fill a missing backup.
24
+
25
+ ## Understand workflow reruns
26
+
27
+ 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. Older publishing runs execute their original code; adding this recovery support to the default branch does not change an already-triggered workflow.
@@ -0,0 +1,43 @@
1
+ # Verifying Releases
2
+
3
+ This guide explains how publishing binds a gem to its reviewed source and how to verify the downloaded artifact and attestations.
4
+
5
+ Use this when checking a completed release or confirming which source commit produced a package. [Getting Started](../getting-started/index) describes publisher configuration; [Recovering Releases](../recovering-releases/index) covers interrupted workflows.
6
+
7
+ ## What publishing verifies
8
+
9
+ 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:
10
+
11
+ - A Sigstore bundle submitted explicitly with `gem push --attestation` using RubyGems 4.0.21.
12
+ - GitHub's native SLSA provenance covering both the gem and `release.json`. This signed receipt binds the gem digest to the exact release commit, even when the workflow's own default-branch revision is newer.
13
+
14
+ 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.
15
+
16
+ The draft release description includes the exact version's notes from `releases.md` in the merged release checkout, followed by the PR URL, source commit, and gem digest. Notes are extracted using `bake-releases`; a missing or empty section leaves the metadata as the description. Retries preserve the existing release description.
17
+
18
+ ## Verify downloaded artifacts
19
+
20
+ Download the gem, its `.sigstore.json` bundle, `release.json`, and `provenance.sigstore.json` from the GitHub release. Replace the example package, owner/repository, and `MERGED_SHA` below with the release being checked. Run from the directory containing those files:
21
+
22
+ ``` bash
23
+ set -e
24
+ for file in example-1.2.3.gem release.json; do
25
+ gh attestation verify "$file" \
26
+ --repo OWNER/REPOSITORY --bundle provenance.sigstore.json \
27
+ --cert-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
28
+ --source-ref refs/heads/main --deny-self-hosted-runners
29
+ done
30
+
31
+ jq -e --arg commit MERGED_SHA \
32
+ --arg digest "$(shasum -a 256 example-1.2.3.gem | cut -d ' ' -f1)" \
33
+ '.commit == $commit and .sha256 == $digest' release.json
34
+
35
+ gem exec sigstore-cli:0.2.3 verify example-1.2.3.gem \
36
+ --bundle example-1.2.3.gem.sigstore.json \
37
+ --certificate-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
38
+ --certificate-oidc-issuer https://token.actions.githubusercontent.com
39
+ ```
40
+
41
+ Download `release.json` and `provenance.sigstore.json` alongside the gem. Verify both subjects before reading the receipt's source commit and digest. GitHub CLI's `--source-digest` checks the workflow revision, which may differ from the release commit recorded in the signed receipt. Replace `main` with the configured default branch in these commands.
42
+
43
+ Native GitHub records the merge and any bypass; the signed artifact receipt includes the PR and merging actor. This version does not export organization audit-log evidence or infer bypass reasons from review counts.
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "rubygems/package"
7
+
8
+ module Bake
9
+ module Gem
10
+ module GitHub
11
+ # Stores the original release files together so a single completed upload can recover them.
12
+ module Backup
13
+ # Write the release files to a tar archive.
14
+ # @parameter path [String] The destination archive path.
15
+ # @parameter files [Array(String)] Original release files, stored under their basenames.
16
+ # @returns [Nil] After writing and closing the archive.
17
+ def self.write(path, files)
18
+ File.open(path, "wb") do |output|
19
+ ::Gem::Package::TarWriter.new(output) do |archive|
20
+ files.each do |file|
21
+ archive.add_file(File.basename(file), 0644){|entry| entry.write(File.binread(file))}
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ # Read only the expected regular files; reject missing, duplicate, or unexpected entries before extraction.
28
+ # @parameter path [String] The archive to inspect without extracting filesystem paths.
29
+ # @parameter names [Array(String)] Exactly the permitted basenames for the gem, receipt, and two attestation files.
30
+ # @returns [Hash(String, String)] Binary file contents keyed by basename.
31
+ # @raises [RuntimeError] If entries are missing, duplicated, unexpected, or not regular files.
32
+ def self.read(path, names)
33
+ files = {}
34
+
35
+ File.open(path, "rb") do |input|
36
+ ::Gem::Package::TarReader.new(input) do |archive|
37
+ archive.each do |entry|
38
+ name = entry.full_name
39
+ raise "Unexpected release backup entry: #{name}" unless entry.file? && names.include?(name) && !files.key?(name)
40
+ files[name] = entry.read
41
+ end
42
+ end
43
+ end
44
+ raise "Release backup is incomplete." unless files.keys.sort == names.sort
45
+
46
+ return files
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end