Migrate pipeline from CircleCI to GitHub Actions - #140
Conversation
phelma
left a comment
There was a problem hiding this comment.
Code Review: #140 - Migrate pipeline from CircleCI to GitHub Actions
Verdict: COMMENT
This CircleCI→GitHub Actions cutover conforms tightly to the Variant A family plan §4. Across correctness, security, safety, standards, and code-quality lenses no critical, major, or minor defects were found: the workflows, Rakefile blocks, gemspec, README, GPG-key move, and CircleCI decommission all match the prescribed shapes, and the release job is correctly parameterised to the repo's deleted release.sh (version:bump[minor], no documentation:update). Only two non-blocking observations remain, one of which is against plan-prescribed code.
Cross-Cutting Themes
- Correct release-job parameterisation (flagged by: correctness, safety, standards) — three lenses independently confirmed the release job mirrors the deleted
release.sh(minor bump,git pull, no docs step). Nodocumentation:updatetask exists in the Rakefile, so the common failure mode of copying the template's docs step verbatim was correctly avoided.
Strengths
- ✅ No workflow injection: all attacker-influenced PR data (title, number, URL, head SHA) is bound via
env:and referenced as shell variables, never interpolated intorun:. The only${{ }}in arun:isjob.status. - ✅ Least-privilege tokens: top-level
contents: read, elevated per-job only where needed. - ✅ Correct trust boundary: the secret-bearing PR
prereleasejob is guarded to same-repo human PRs, andmerge-pull-requestdoes notneeds:it. - ✅
--match-head-commit "$HEAD_SHA"preserves merge integrity. - ✅ Rakefile guards fail fast on a locked clone (missing passphrase / git-crypt ciphertext sentinel) rather than uploading garbage secrets.
- ✅
prerelease:publishwraps the temporaryversion.rbrewrite in begin/ensure, restoring the file and removing the built gem even on failure. - ✅ Decommission is complete and clean: no
circlecireferences remain outsidedocs//Gemfile.lock; only the two portablescripts/ci/commonscripts are retained;config/secrets/cikeeps only the git-crypt material.
General Findings
- 🔵 code-quality (plan concern):
prerelease:publishuses the replacement-string formsource.sub(version_pattern, "\\1#{version}\\3"), which couples correctness to backslash-escape semantics of the interpolated version. Safe today (numeric facts only). This is the plan's verbatim §4.4 step 8 code, so it is a plan concern, not a diff defect. - 🔵 correctness (false positive): the check/test jobs use
./go test:unitwhile the deletedtest.shinvoked./go spec. Verified benign —test:unitis the existing RSpec task and the family-canonical name; nospectask exists. Counted nowhere.
Review generated by /accelerator:review-pr
| - context | ||
| - shared_examples | ||
| - it | ||
| - define_repository_tasks |
There was a problem hiding this comment.
🔵 Standards (suggestion)
This adds define_repository_tasks to Metrics/BlockLength's AllowedMethods to accommodate the now-larger RakeGithub.define_repository_tasks block (the token-resolution and passphrase-guard logic). The family plan §4 prescribes exact file shapes and includes no .rubocop.yml change; §4.4 only asks that the Rakefile stay clean against the 80-char rule. If this relaxation is genuinely required to pass library:check, it is reasonable — but note it in the PR description as a deliberate deviation so future maintainers can trace it, since it permanently relaxes a lint rule for the repo.
Part of PP-709.
Cutover to GitHub Actions per the Variant A family plan (gem pilot).
Includes decommission — merging this PR completes the repo's migration.
releaseenvironment gate.github/rake_slack; dependabot auto-merge jobrake_githubsecrets/environments;rake_circle_cidropped.circleci/,scripts/ci/, the CI SSH deploykey pair and its
keys:deploy/deploy_keysprovisioning, and the storedCircleCI/GitHub API credentials (
config/secrets/{circle_ci,github}/)Deliberate decisions (not defects)
This cutover reproduces the CircleCI pipeline's behaviour, warts included;
fixing inherited hazards is post-migration work. In particular:
./go releasepublishes to RubyGems before the version-bump commit ispushed — pre-existing ordering inside the untouched release logic.
mainwith no approval gate; onlyfull releases are gated (
environment: release).merge does not trigger a release build — on CircleCI the merge commit
carried
[skip ci], so this matches. Updates ship with the nexthuman-triggered release.
releasejob pullsmainat approval time, so a delayed approvalpublishes main as it stands then, not the SHA this run tested — parity with
the old
release.sh(which also pulled;prerelease.shdid not, so theprerelease job has no pull).
asdf_install@v1is our own action (infrablocks/github-actions); we arehappy tracking its major version tag.
build system (
./go/rake) and CI stays lean — it just triggers tasks andsupplies secrets/context.
Gemfile.lockcarries transitive major bumps — the unavoidable resolutionof the targeted
bundle lock --update, not scope creep.autocorrects existing code (e.g.
Style/ArgumentsForwarding) — requiredby the
library:checkverification gate, not drive-by refactoring.pipeline:prepare) authenticates with the operator's ambientghlogin (GITHUB_TOKENfallback) instead of a stored PAT — a deliberateparity deviation; the stored token in
config/secrets/github/config.yamlis deleted with the rest of the CircleCI-era credentials.
PR-CI prerelease publish (deliberate, permanent)
pr.yamlhas aprereleasejob that publishes a namespaced pre-release ofthis gem to RubyGems from the PR branch — a permanent CI feature, not
migration-only. This is a deliberate deviation from CircleCI (which published
nothing pre-merge): it proves the publish path before merge instead of
discovering it broken on
main. The version is<committed-version>.pr<PR>.<run>.<attempt>(via the newprerelease:publishRakefile task), so it can never collide with
main'sversion:bump[pre]sequence; the task builds the gem and pushes it straight to RubyGems, then
restores
version.rb, so nothing is committed, tagged, or pushed(
gem releaseis not used — it aborts on the uncommitted version rewrite).The job is skipped for fork
and Dependabot PRs (they hold no secrets), and
merge-pull-requestdoes notdepend on it. PR pre-release versions accumulate permanently on RubyGems —
accepted.
Do not merge manually — the pipeline merges once checks are green.
Disabling the CircleCI project and deleting the
CircleCIdeploy key aredeferred to the end-of-migration sweep.
🏭 This PR was opened by Foundry, Atomic's AI software development
factory. Implementation, review, and fixes are performed by AI agents;
merges happen automatically once the review and checks gates pass.
This task migrates a Ruby gem's CI from CircleCI to GitHub Actions.
migratemigrate-gem2026-07-22T17-48-59-192Zatomic-foundry-pr · foundry-pipeline: migrate · foundry-task: migrate-gem · foundry-run: 2026-07-22T17-48-59-192Z