ci: migrate release pipeline onto shared reusable workflows#125
Conversation
Replace the bespoke auto-release.yml / release.yml / ci.yml with thin callers of the open-cli-collective/.github @v1 reusable workflows and composite actions, driven by a new packaging/identity.yml manifest. - packaging/identity.yml: declares binary (nrq), tag scheme, archives, and the homebrew (canonical nrq + newrelic-cli alias) / winget / chocolatey / linux channels, plus a keychain_probe (config show --json) that ports the darwin Keychain pre-publish gate. - .goreleaser.yml: add homebrew_casks (skip_upload: true — goreleaser renders the cask, the reusable homebrew-alias step is the single atomic writer for canonical + alias), release.replace_existing_artifacts, and a fail-on-dirty go mod tidy before-hook. The cask keeps both on_macos and on_linux blocks (matching the prior hand-rolled cask). - ci.yml: adopt the composite shape (build matrix -> build aggregate, go-test, go-lint, identity-check, pr-title). The static-release build guard is preserved as a dedicated static-release-guard job so a CGO=0 linux regression still fails the build. This drops the heredoc Casks/nrq.rb writer and the deprecated Formula/newrelic-cli.rb regeneration; the reusable homebrew job writes the nrq cask + newrelic-cli alias atomically. The Codecov upload step is dropped to match the standard composite CI shape (it was non-blocking). snap was already removed in #120. Closes #116
The new ci.yml build aggregate produces a required 'build' check context. release-build-check.yml's job was also id 'build', which would create a duplicate/ambiguous check context for branch protection (and this workflow is path-filtered, so it doesn't run on every PR). Rename the job to release-build-check so the two contexts are distinct.
|
Prior major is resolved. No new blockers/majors. If |
TDD coverage assessmentThis PR is a CI/CD-workflow and goreleaser-config migration — no application Go source is touched. The right lens is: do the validation gates provide adequate confidence that the migrated pipeline behaves identically to what it replaces? Gates present and sufficient:
One gap worth tracking (non-blocking): The Codecov upload is dropped intentionally (non-blocking, aligned with family standard). If line-coverage visibility matters for nrq specifically, a follow-up to re-add it via the standard composite shape is reasonable, but it is not a correctness concern for this migration. Verdict: Coverage is adequate for the change type. Every behavior change — the cask writer replacement, the Keychain probe migration, the CGO static-linux sentinel, the goreleaser config additions — has a corresponding validation gate (local snapshot proof, |
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: a20f7d7
Summary
| Reviewer | Findings |
|---|---|
| harness-engineering:harness-architecture-reviewer | 4 |
| harness-engineering:harness-enforcement-reviewer | 2 |
harness-engineering:harness-architecture-reviewer (4 findings)
.github/workflows/auto-release.yml:8
Gate 1 (path filter for **.go, go.mod, go.sum) has been removed from the caller and the PR comment claims it lives in the shared reusable workflow. This cannot be verified from the diff. If open-cli-collective/.github's auto-release.yml@v1 does not enforce an equivalent path filter, every push to main (docs, CI, packaging) will spin up a release runner. Gate 2 (feat:/fix: prefix) still prevents accidental tag creation, so no spurious release occurs, but the dual-gate efficiency design documented in CLAUDE.md is silently degraded. Confirm the shared workflow implements the path filter before merging.
.github/workflows/ci.yml:44
The old test job ran
go test -raceas a required CI gate. The new job delegates togo-test@v1whose implementation is not visible in this diff. If the shared composite action omits-race, race condition detection is silently dropped — a meaningful regression for a project that exercises shared mutable state in keyring/credstore paths. Confirm the shared action passes-race, or add an explicit flag override here.
💡 Suggestion - .goreleaser.yml:5
go mod tidy && git diff --exit-code go.mod go.sumruns as a before-hook during goreleaser. If tidy modifies go.mod/go.sum at release time, the release fails with no automated recovery path — a dangling tag may have already been created before goreleaser ran. Confirm whether the reusable release workflow has retry or cleanup logic for this failure mode.
💡 Suggestion - packaging/identity.yml:32
The keychain_probe assertions (.backend: keychain, .backend_source: auto, .credential_ref: newrelic-cli/default) are correct, but their enforcement depends entirely on the assertion semantics of the shared identity-check@v1 action, which is opaque here. If the shared action uses substring or loose matching rather than strict equality, the gate degrades silently. Verify the shared action performs exact-equality JQ assertions.
harness-engineering:harness-enforcement-reviewer (2 findings)
💡 Suggestion - .github/workflows/ci.yml:7
The
pull_requesttrigger no longer scopes tobranches: [main]. Previously CI only ran on PRs targeting main; now it runs on PRs targeting any branch. This will cause duplicate CI runs on stacked feature branches and appears unintentional given the rest of the project's conventions.
💡 Suggestion - .github/workflows/release.yml:1
Both release.yml and auto-release.yml reference the shared workflow via the mutable
@v1floating tag rather than a pinned commit SHA. A force-push or tag move in open-cli-collective/.github would silently change behavior in a workflow that receives CHOCOLATEY_API_KEY, WINGET_GITHUB_TOKEN, LINUX_PACKAGES_DISPATCH_TOKEN, and TAP_GITHUB_TOKEN. Pinning to a specific commit SHA provides a stronger supply-chain guarantee for secrets-bearing callers.
6 info-level observations excluded. Run with --verbose to include.
2 PR discussion threads considered.
Completed in 2m 36s | $0.62 | sonnet | daemon 0.2.121 | Glorfindel
| Field | Value |
|---|---|
| Model | sonnet |
| Reviewers | hybrid-synthesis, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer |
| Engine | claude · sonnet |
| Reviewed by | pr-review-daemon · monit-pr-reviewer |
| Duration | 2m 36s wall · 4m 45s compute (Reviewers: 1m 32s · Synthesis: 1m 01s) |
| Cost | $0.62 |
| Tokens | 115.8k in / 14.9k out |
| Turns | 4 |
Per-workstream usage
| Workstream | Model | In | Out | Cache read | Cache create | Cost |
|---|---|---|---|---|---|---|
| hybrid-synthesis | sonnet | 41.3k | 3.8k | 18.6k | 22.7k (1h) | $0.16 |
| harness-engineering:harness-architecture-reviewer | sonnet | 24.8k | 3.1k | 2.1k | 22.7k (1h) | $0.15 |
| harness-engineering:harness-enforcement-reviewer | sonnet | 24.8k | 3.8k | 2.1k | 22.7k (1h) | $0.16 |
| harness-engineering:harness-knowledge-reviewer | sonnet | 24.8k | 4.3k | 2.1k | 22.7k (1h) | $0.16 |
Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.
| # lives in the shared reusable workflow; this caller just wires triggers/secrets. | ||
| # The release gate (Go-code paths + feat/fix commit) and the version.txt + | ||
| # GITHUB_RUN_NUMBER tag scheme are handled by the reusable workflow. | ||
| on: |
There was a problem hiding this comment.
🟡 Medium (harness-engineering:harness-architecture-reviewer): Gate 1 (path filter for **.go, go.mod, go.sum) has been removed from the caller and the PR comment claims it lives in the shared reusable workflow. This cannot be verified from the diff. If open-cli-collective/.github's auto-release.yml@v1 does not enforce an equivalent path filter, every push to main (docs, CI, packaging) will spin up a release runner. Gate 2 (feat:/fix: prefix) still prevents accidental tag creation, so no spurious release occurs, but the dual-gate efficiency design documented in CLAUDE.md is silently degraded. Confirm the shared workflow implements the path filter before merging.
Reply to this thread when addressed.
There was a problem hiding this comment.
Verified in the @v1 source: the reusable auto-release.yml exposes a release-paths input defaulting to **.go,go.mod,go.sum,version.txt and passes it (with before/after SHA) to the auto-release@v1 composite, which gates on whether the push range touched a release-path AND the commit-message prefix. Both gates are preserved — a push touching only docs/CI/packaging won't spin a release. slck uses the same default; nrq matches.
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| - uses: open-cli-collective/.github/actions/go-test@v1 | ||
| with: |
There was a problem hiding this comment.
🟡 Medium (harness-engineering:harness-architecture-reviewer): The old test job ran go test -race as a required CI gate. The new job delegates to go-test@v1 whose implementation is not visible in this diff. If the shared composite action omits -race, race condition detection is silently dropped — a meaningful regression for a project that exercises shared mutable state in keyring/credstore paths. Confirm the shared action passes -race, or add an explicit flag override here.
Reply to this thread when addressed.
There was a problem hiding this comment.
Race detection is preserved. go-test@v1 runs make test, and nrq's Makefile test target is go test -race ./... (test-cover is go test -race -coverprofile=…). The composite delegates to the repo Makefile, so -race still runs as the required CI gate — no regression.
| branches: [main] | ||
| pull_request: | ||
| branches: [main] | ||
|
|
There was a problem hiding this comment.
🔵 Low (harness-engineering:harness-enforcement-reviewer): The pull_request trigger no longer scopes to branches: [main]. Previously CI only ran on PRs targeting main; now it runs on PRs targeting any branch. This will cause duplicate CI runs on stacked feature branches and appears unintentional given the rest of the project's conventions.
Reply to this thread when addressed.
There was a problem hiding this comment.
Intentional — this matches the shared CI composite template (codereview-cli + the merged slck#176 use the same bare pull_request: trigger). With concurrency cancel-in-progress and squash-merge-to-main convention, the practical effect is the same; standardizing the trigger across the family is part of the rollout. Not scoping to a single branch is the documented ci.md §7 shape.
| @@ -1,412 +1,39 @@ | |||
| name: Release | |||
There was a problem hiding this comment.
🔵 Low (harness-engineering:harness-enforcement-reviewer): Both release.yml and auto-release.yml reference the shared workflow via the mutable @v1 floating tag rather than a pinned commit SHA. A force-push or tag move in open-cli-collective/.github would silently change behavior in a workflow that receives CHOCOLATEY_API_KEY, WINGET_GITHUB_TOKEN, LINUX_PACKAGES_DISPATCH_TOKEN, and TAP_GITHUB_TOKEN. Pinning to a specific commit SHA provides a stronger supply-chain guarantee for secrets-bearing callers.
Reply to this thread when addressed.
There was a problem hiding this comment.
Intentional design: the @v1 moving major tag is how the family gets centralized, in-lockstep updates to the shared workflows (one re-point in .github vs N caller edits). SHA-pinning all secrets-bearing callers is a real supply-chain hardening, but it's a family-wide decision tracked separately in .github#10 (SHA-pin third-party + internal action refs), not something to do per-repo in this migration.
|
|
||
| project_name: newrelic-cli | ||
|
|
||
| before: |
There was a problem hiding this comment.
🔵 Low (harness-engineering:harness-architecture-reviewer): go mod tidy && git diff --exit-code go.mod go.sum runs as a before-hook during goreleaser. If tidy modifies go.mod/go.sum at release time, the release fails with no automated recovery path — a dangling tag may have already been created before goreleaser ran. Confirm whether the reusable release workflow has retry or cleanup logic for this failure mode.
Reply to this thread when addressed.
There was a problem hiding this comment.
The fail-on-dirty tidy is intentional: a release MUST build from a committed, tidy module graph — a mutating tidy at release time is a real problem to surface, not paper over. On the recovery side, the reusable release.yml is idempotent: the auto-release tag step is collision-safe (tag-at-HEAD is a no-op, tag-elsewhere fails loud), and goreleaser uses replace_existing_artifacts, so a fixed re-run of the same tag overwrites cleanly. In practice go.mod/go.sum are committed and tidy (the static-release-guard + local snapshot build exercise the same graph), so the hook is a guard, not an expected failure path.
| command: ["config", "show", "--json"] | ||
| output: json | ||
| assertions: | ||
| .backend: keychain |
There was a problem hiding this comment.
🔵 Low (harness-engineering:harness-architecture-reviewer): The keychain_probe assertions (.backend: keychain, .backend_source: auto, .credential_ref: newrelic-cli/default) are correct, but their enforcement depends entirely on the assertion semantics of the shared identity-check@v1 action, which is opaque here. If the shared action uses substring or loose matching rather than strict equality, the gate degrades silently. Verify the shared action performs exact-equality JQ assertions.
Reply to this thread when addressed.
There was a problem hiding this comment.
Confirmed strict equality. The reusable darwin-gate runs the probe command and asserts each jq path with got=$(jq -r "$key"); [ "$got" = "$want" ] — exact string equality, fail-loud on mismatch (no substring/loose matching). Verified against the @v1 darwin-gate.sh. The probe assertions here will enforce backend=keychain / backend_source=auto / credential_ref=newrelic-cli/default exactly.
|
All six inline findings are either verified-correct against the @v1 shared-action source (path-filter gate preserved, |
|
No architectural drift from the last clean review. The daemon findings are consistent with the shared standard or preserved behavior:
Admin-merge is reasonable assuming CI is green and branch protection is updated to include |
What
Migrates nrq's release pipeline onto the
open-cli-collective/.github@v1reusable workflows + composite actions, driven by a newpackaging/identity.ymlmanifest. Rollout step 9 — the second alias-cask pilot (nrqcanonical +newrelic-clialias), following the merged slck#176.Closes #116.
Changes
packaging/identity.yml(new) — binarynrq, tag scheme, archive template, and the homebrew (canonicalnrq+newrelic-clialias) / winget / chocolatey (nrq-cli) / linux channels.keychain_probeusesconfig show --json(the §2 carve-out —--output jsonwas restricted to{table, plain}in feat(view): remove -o json from resource reads; closed-set + carve-outs #110) and assertsbackend=keychain/auto/credential_ref=newrelic-cli/default..goreleaser.yml— addhomebrew_casks(skip_upload: true, notoken:),release.replace_existing_artifacts: true, and a fail-on-dirtygo mod tidybefore-hook (nrq had no before block).auto-release.yml/release.yml— replaced with thin@v1callers; channel secrets wired through.ci.yml— composite shape: build matrix →buildaggregate,go-test,go-lint,identity-check,pr-title.Intentional behavior changes (please note)
Casks/nrq.rbheredoc and the deprecatedFormula/newrelic-cli.rbregeneration are replaced by goreleaserhomebrew_casks+ the reusablehomebrew-aliasstep (single atomic tap writer for canonical + alias). The cask stays dual-platform (on_macos+on_linux), matching the prior cask — Linuxbrew users keep working.Casks/newrelic-cli.rbin the tap had drifted to a broken state (version 1.0.11,newrelic-cli_v*URLs,newrelic-clibinary — all wrong; the archives arenrq_v*, the binary isnrq). The companion tap commit (homebrew-tap@a70ca21) regenerates it as a correct one-line-token alias of the canonicalnrq.rband deletes the deprecatedFormula/newrelic-cli.rb, sobrew install open-cli-collective/tap/newrelic-cliresolves to the corrected alias cask immediately. (cli-common#36 still owns the broader gro/gmail-ro Formula-remnant sweep.)static-release-guardis a required check — preserves the CGO=0-linux regression sentinel that currently lives inside the requiredtestjob. The sharedgo-build@v1matrix builds with default CGO and would not catch a static-linux regression, so this credstore sentinel is kept as a dedicated job. Preserves existing blocking semantics; does not expand the standard floor.testjob'scodecov-actionstep (non-blocking,fail_ci_if_error: false) is not part of the standard composite CI shape. Dropped to align nrq with the family; re-add as a follow-up if desired.Proof (no gratuitous real release)
goreleaser check✓ andgoreleaser release --snapshot --clean✓ with the tap token unset — all platforms + nfpms + checksums + rendereddist/homebrew/Casks/nrq.rb.homebrew-alias/alias.sh render <cask> nrq newrelic-clidiffers from canonical by exactly one line (cask "nrq"→cask "newrelic-cli"); url/version/sha256/binary "nrq"verbatim.make build/ tests green /golangci-lint run0 issues.identity-check validate✓ andrelease-preflight check-config✓ locally.Out of scope (deliberate)
release-build-check.yml(PR-timegoreleaser buildvalidator) — KEEP; the reusable release.yml has no PR-time equivalent, and nrq added this after the v1.0.39–44 silent-breakage incident.chocolatey-publish.yml/winget-publish.yml(manual dispatch) +test-chocolatey.yml/test-winget.yml— left as-is.Branch protection
Current required checks are
test/lint. After CI is green here, branch protection must addbuild/pr-title/identity-check/static-release-guard.