From 156fa482e292e35784e8621ebffe7fc14c0a5c91 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 09:47:43 -0500 Subject: [PATCH 1/5] Release through the shared Go release workflow release.yml becomes a thin caller of simtabi/.github's go-release.yml on macos-latest (the darwin targets need cgo) under the release environment. A release builds on a pushed vX.Y.Z tag or on request, replacing the dispatch-only, confirm=release-go workflow; the shared workflow's notes check replaces the gh release edit step. The guards pin the triggers, the shared call, its inputs and the channels. --- .github/workflows/release.yml | 162 ++++++-------------------- src/cmd/vigil/releasenotes_test.go | 129 ++++++++++++++------ src/internal/release/workflow_test.go | 75 +++++++++--- 3 files changed, 183 insertions(+), 183 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27bdd81..ca819eb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,140 +1,50 @@ -name: Release - -# Every release is tag-driven and publishes BOTH the source code and the -# binaries: GoReleaser cross-compiles the full matrix, builds the archives, -# packages, and a reproducible source tarball, generates SBOMs and checksums, -# creates the GitHub Release (body from the tagged CHANGELOG section), and -# updates the Homebrew/Scoop/Winget manifests. A build-provenance attestation is -# then signed for every artifact via the checksums file. +# Release: build vigil's binaries and publish them on its GitHub release, and nowhere else (no +# `go install` path, Homebrew, Scoop, Winget, image or any other channel; owner, 2026-11-13). +# The pipeline is simtabi/.github's shared go-release.yml; this file only says when, and where. # -# Go releases are git only until the owner says otherwise for this project, so -# nothing here starts on a tag push: pushing a vX.Y.Z tag records a version and -# publishes nothing. A release is dispatched by hand against the tag, with an -# explicit confirmation, and refuses to run without both: +# git push origin vX.Y.Z builds and publishes vX.Y.Z +# gh workflow run release.yml --repo simtabi/vigil -f tag=vX.Y.Z the same, for a tag already pushed # -# gh workflow run release.yml --repo simtabi/vigil --ref vX.Y.Z -f confirm=release-go +# Push a version tag only for final code: tags are final, and a pushed tag publishes. The shared +# workflow reads src/.goreleaser.yaml (dist: .cache/goreleaser, never build/), takes the notes from +# the tag's CHANGELOG.md section and reads the published body back, and pushes the src/vX.Y.Z +# module tag, which matches no trigger here. # -# It stays tag-driven: GoReleaser reads the version from the ref it is run -# against, and the first step refuses a ref that is not a v[0-9]* tag. -# src/cmd/vigil's TestReleaseRunsOnlyOnAConfirmedDispatch fails if an automatic -# trigger comes back or the refusal stops being the first step. -# -# The Go module lives in src/ (github.com/simtabi/vigil/src), so GoReleaser runs -# from the repository root with --config src/.goreleaser.yaml and writes its -# scratch dist to .cache/goreleaser/, never into build/. +# macOS, not Linux: vigil's input engine (src/internal/activity/input_darwin.go) links +# CoreGraphics/IOKit via cgo, so the darwin targets need the macOS SDK and can't be cross-compiled +# CGO-free. The arm64 runner builds darwin natively and cross-builds the CGO-free +# linux/windows/bsd matrix in one job. The job runs under the `release` environment, as before. +# src/cmd/vigil's TestReleaseRunsOnAVersionTagOrOnRequest and src/internal/release's +# TestReleaseInputsMatchTheRepository fail if a trigger is added or an input drifts. +name: Release + on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-*' workflow_dispatch: inputs: - confirm: - description: Type release-go to publish binaries for the tag this run is dispatched on + tag: + description: 'An existing vX.Y.Z tag to build and publish' required: true type: string -# Releases must never be cancelled mid-publish by a newer tag. +permissions: {} + +# One run per tag, never cancelled: two runs must not publish the same release. concurrency: - group: release-${{ github.ref }} + group: release-${{ inputs.tag || github.ref_name }} cancel-in-progress: false -permissions: - contents: read - -# Our own modules are fetched from git, never through proxy.golang.org or -# sum.golang.org, so the build itself is not what feeds the public proxy a tag. -env: - GOPRIVATE: github.com/simtabi/* - GONOSUMDB: github.com/simtabi/* - jobs: - goreleaser: - # macOS, not Linux: vigil's input engine (src/internal/activity/input_darwin.go) - # links CoreGraphics/IOKit via cgo, so the darwin targets need the macOS SDK - # and can't be cross-compiled CGO-free. The arm64 runner builds darwin - # natively and cross-builds the CGO-free linux/windows/bsd matrix in one job. - runs-on: macos-latest - environment: release - timeout-minutes: 60 + release: permissions: - contents: write # create the release and upload assets - id-token: write # keyless signing for the provenance attestation - attestations: write # write the build-provenance attestation - steps: - - name: Refuse without confirmation or a release tag - env: - CONFIRM: ${{ inputs.confirm }} - run: | - if [ "${CONFIRM}" != "release-go" ]; then - echo "::error::confirm must be release-go; nothing was published" - exit 1 - fi - case "${GITHUB_REF}" in - refs/tags/v[0-9]*) ;; - *) echo "::error::dispatch this on a vX.Y.Z tag (--ref vX.Y.Z), not ${GITHUB_REF}"; exit 1 ;; - esac - - - uses: actions/checkout@v7 - with: - fetch-depth: 0 # full history + tags for GoReleaser - - - uses: actions/setup-go@v7 - with: - go-version-file: src/go.mod - cache: true - cache-dependency-path: src/go.sum - - # syft powers GoReleaser's SBOM generation. - - uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 - - # `go install` needs a tag named after the module's subdirectory: the - # module is github.com/simtabi/vigil/src, so Go resolves src/vX.Y.Z and - # ignores vX.Y.Z. OSS GoReleaser rejects the prefixed tag as non-semver, - # so the release runs off vX.Y.Z and mirrors src/vX.Y.Z onto the same - # commit here, where it cannot be forgotten. This workflow starts on no - # tag push, so pushing src/... starts no second run. A re-run finds the - # tag already there and leaves it alone: a public tag is never moved. - - name: Mirror the tag Go resolves the module through - run: | - alias_tag="src/${GITHUB_REF_NAME}" - if git rev-parse -q --verify "refs/tags/${alias_tag}" >/dev/null; then - echo "${alias_tag} already exists" - else - git tag "${alias_tag}" "${GITHUB_REF_NAME}^{commit}" - git push origin "refs/tags/${alias_tag}" - echo "pushed ${alias_tag}" - fi - - - name: Extract CHANGELOG section for this tag - run: ./src/scripts/extract-changelog.sh "${GITHUB_REF_NAME}" > release_notes.md - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@a4f02ab5355a589de60d917a659ce617ed19b3de # v7.3.0 - with: - version: latest - args: release --clean --config src/.goreleaser.yaml --release-notes=release_notes.md - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} - # Write deploy keys on simtabi/homebrew-tap and simtabi/scoop-bucket. When set, GoReleaser - # pushes with them and the token is not used for those two. - HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }} - SCOOP_BUCKET_DEPLOY_KEY: ${{ secrets.SCOOP_BUCKET_DEPLOY_KEY }} - # Scoop/Winget publishing is skipped until the bucket/fork exist. Set - # repo variable SKIP_WINDOWS_PKGS to "false" to enable it. - SKIP_WINDOWS_PKGS: ${{ vars.SKIP_WINDOWS_PKGS || 'true' }} - # Scoop's own switch; empty falls back to SKIP_WINDOWS_PKGS (see the GoReleaser config). - SKIP_SCOOP: ${{ vars.SKIP_SCOOP }} - - # Set the body again from the same CHANGELOG section, so the release - # carries its notes even if a GoReleaser change stops honouring - # --release-notes, which is how ssh-manager v3.2.0 and v3.4.0 went out empty. - - name: Set the release body from CHANGELOG.md - env: - GH_TOKEN: ${{ github.token }} - run: gh release edit "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" --notes-file release_notes.md - - # Sign a build-provenance attestation for every released artifact. The - # checksums file lists them all, so `gh attestation verify - # --repo /` works for any downloaded binary or archive. - - name: Attest build provenance - uses: actions/attest-build-provenance@v4 - with: - subject-checksums: .cache/goreleaser/checksums.txt + contents: write + id-token: write + attestations: write + uses: simtabi/.github/.github/workflows/go-release.yml@20630147e3cb8c378c8b9c8ee9b6a371cd517581 # simtabi/.github#28 head; re-pin to its merge commit + with: + tag: ${{ inputs.tag || github.ref_name }} + runs-on: macos-latest + environment: release diff --git a/src/cmd/vigil/releasenotes_test.go b/src/cmd/vigil/releasenotes_test.go index 23ab2a9..699062f 100644 --- a/src/cmd/vigil/releasenotes_test.go +++ b/src/cmd/vigil/releasenotes_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "regexp" + "sort" "strings" "testing" ) @@ -30,12 +31,13 @@ func repoRoot(t *testing.T) string { } } -// Every release carries its CHANGELOG section as the body. release.yml hands -// GoReleaser that section with --release-notes, but GoReleaser reads the flag -// inside its changelog pipe, and `changelog: disable: true` skips the pipe -// whole: ssh-manager v3.2.0 and v3.4.0 were published with empty bodies that -// way, and the run reported success. So the config must not disable the -// changelog, and the workflow also sets the body itself after GoReleaser. +// Every release carries its CHANGELOG section as the body. The shared simtabi/.github +// go-release.yml hands GoReleaser that section with --release-notes, then reads the published body +// back and fails the run if it is shorter than the section; it replaced this workflow's own +// `gh release edit --notes-file` repair on 2026-11-13. GoReleaser reads the flag inside its +// changelog pipe, and `changelog: disable: true` skips the pipe whole: ssh-manager v3.2.0 and +// v3.4.0 were published with empty bodies that way, and the run reported success. So the config +// must not disable the changelog, and the caller must not point the notes at another file. func TestReleasesCarryTheirChangelogSection(t *testing.T) { root := repoRoot(t) cfg, err := os.ReadFile(filepath.Join(root, "src", ".goreleaser.yaml")) @@ -49,50 +51,101 @@ func TestReleasesCarryTheirChangelogSection(t *testing.T) { if err != nil { t.Fatal(err) } - for _, want := range []string{"--release-notes=", "gh release edit", "--notes-file"} { - if !strings.Contains(string(wf), want) { - t.Errorf("release.yml lacks %q: the release body must be set from the CHANGELOG section", want) - } + if !strings.Contains(string(wf), "uses: simtabi/.github/.github/workflows/go-release.yml@") { + t.Error("release.yml does not call the shared go-release.yml, which sets and verifies the body") + } + if m := regexp.MustCompile(`(?m)^\s+changelog:\s*(\S+)`).FindStringSubmatch(string(wf)); m != nil && m[1] != "CHANGELOG.md" { + t.Errorf("release.yml takes the notes from %s; they come from CHANGELOG.md", m[1]) + } + if _, err := os.Stat(filepath.Join(root, "CHANGELOG.md")); err != nil { + t.Error("CHANGELOG.md is missing; the release notes come from it") } } -// automaticTrigger matches a trigger that starts a workflow without a person -// dispatching it, at any indentation under `on:`. -var automaticTrigger = regexp.MustCompile(`(?m)^[ \t]*(push|schedule|pull_request|pull_request_target|release|workflow_run)\s*:`) - -// Go releases are git only until the owner says otherwise, so release.yml must -// never start on its own: no push, schedule, pull_request or release trigger, -// and a dispatch whose first step refuses to publish without confirm=release-go -// on a vX.Y.Z tag. A tag push that started GoReleaser is how an unwanted -// release began elsewhere on 2026-11-02. -func TestReleaseRunsOnlyOnAConfirmedDispatch(t *testing.T) { +// release.yml publishes a release on a pushed version tag, or on request for a tag already +// pushed, and does nothing else: its triggers are exactly those two, and its one job calls the +// shared simtabi/.github go-release.yml (owner, 2026-11-13). A branch push, a schedule, a pull +// request or a release event must never start it. It replaces the dispatch-only, confirm=release-go +// guard of 2026-11-02. +// +// This reads the workflow's indentation, two spaces per level, as the file is written. A +// reformatted file fails here rather than passing unread. +func TestReleaseRunsOnAVersionTagOrOnRequest(t *testing.T) { raw, err := os.ReadFile(filepath.Join(repoRoot(t), ".github", "workflows", "release.yml")) if err != nil { t.Fatal(err) } wf := string(raw) - if m := automaticTrigger.FindString(wf); m != "" { - t.Errorf("release.yml has an automatic trigger (%q); it must run on workflow_dispatch only", strings.TrimSpace(m)) + + on := yamlBlock(wf, "", "on") + if on == "" { + t.Fatal("release.yml has no top-level on: block") + } + if got := strings.Join(yamlKeys(on, " "), ","); got != "push,workflow_dispatch" { + t.Fatalf("release.yml triggers [%s], want exactly push (version tags) and workflow_dispatch", got) } - for _, want := range []string{"workflow_dispatch:", "confirm:", "required: true", `"${CONFIRM}" != "release-go"`, "refs/tags/v[0-9]*"} { - if !strings.Contains(wf, want) { - t.Errorf("release.yml lacks %q: a release needs an explicit, confirmed dispatch on a version tag", want) + + push := yamlBlock(on, " ", "push") + if got := strings.Join(yamlKeys(push, " "), ","); got != "tags" { + t.Fatalf("release.yml's push filters [%s], want tags only, never a branch", got) + } + tags := regexp.MustCompile(`(?m)^ - '([^']*)'\s*$`).FindAllStringSubmatch(yamlBlock(push, " ", "tags"), -1) + if len(tags) == 0 { + t.Fatal("release.yml's push has no quoted tag filters") + } + for _, m := range tags { + if !strings.HasPrefix(m[1], "v[0-9]+.[0-9]+.[0-9]+") { + t.Errorf("tag filter %q is not a version tag", m[1]) } } - // The refusal must be the first step, before anything is checked out, - // built or published. - _, steps, ok := strings.Cut(wf, "steps:") - if !ok { - t.Fatal("release.yml has no steps") - } - first := steps - if items := regexp.MustCompile(`(?m)^\s*- `).FindAllStringIndex(steps, 2); len(items) == 2 { - first = steps[:items[1][0]] - } - for _, want := range []string{`"release-go"`, "refs/tags/v[0-9]*", "exit 1"} { - if !strings.Contains(first, want) { - t.Errorf("the first release step must refuse without confirm=release-go on a vX.Y.Z tag; it lacks %q:\n%s", want, first) + tagInput := yamlBlock(yamlBlock(yamlBlock(on, " ", "workflow_dispatch"), " ", "inputs"), " ", "tag") + if !regexp.MustCompile(`(?m)^ required: true\s*$`).MatchString(tagInput) { + t.Fatal("release.yml has no required tag input on workflow_dispatch") + } + + jobs := yamlBlock(wf, "", "jobs") + if got := strings.Join(yamlKeys(jobs, " "), ","); got != "release" { + t.Fatalf("release.yml jobs [%s], want the one release job", got) + } + job := yamlBlock(jobs, " ", "release") + if !regexp.MustCompile(`(?m)^ uses: simtabi/\.github/\.github/workflows/go-release\.yml@[0-9a-f]{40}\b`).MatchString(job) { + t.Fatalf("the release job must call the shared go-release.yml pinned to a commit:\n%s", job) + } + if !strings.Contains(job, " tag: ${{ inputs.tag || github.ref_name }}\n") { + t.Error("the release job must pass tag: ${{ inputs.tag || github.ref_name }}") + } +} + +// yamlBlock returns the lines nested under the key at the given indent, or "" when it is absent. +func yamlBlock(text, indent, key string) string { + var out []string + in := false + for _, line := range strings.Split(text, "\n") { + if strings.HasPrefix(line, indent+key+":") { + in = true + continue + } + if in { + trimmed := strings.TrimSpace(line) + if trimmed != "" && !strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(line, indent+" ") { + break + } + out = append(out, line) + } + } + return strings.Join(out, "\n") +} + +// yamlKeys lists the mapping keys at exactly the given indent, sorted. +func yamlKeys(text, indent string) []string { + re := regexp.MustCompile(`^` + indent + `([A-Za-z_][A-Za-z0-9_-]*):`) + var out []string + for _, line := range strings.Split(text, "\n") { + if m := re.FindStringSubmatch(line); m != nil { + out = append(out, m[1]) } } + sort.Strings(out) + return out } diff --git a/src/internal/release/workflow_test.go b/src/internal/release/workflow_test.go index 831d348..b247356 100644 --- a/src/internal/release/workflow_test.go +++ b/src/internal/release/workflow_test.go @@ -217,9 +217,9 @@ func TestNoContainerImage(t *testing.T) { // the elided `…/cmd/vigil` form the docs used for it. var goInstall = regexp.MustCompile("go install [^\\s`]*(github\\.com/|cmd/vigil)") -// Releases are on hold, so the docs offer building from source only: no -// `go install` of this module, which would also serve whatever the Go proxy -// cached for a tag rather than the code in the checkout. +// Binaries ship on the GitHub release only, so the docs offer the release +// download or a build from source: no `go install` of this module, which would +// serve whatever the Go proxy cached for a tag rather than the released code. func TestDocsOfferNoGoInstall(t *testing.T) { root := repoRoot(t) files := []string{filepath.Join(root, "README.md"), filepath.Join(root, "CONTRIBUTING.md")} @@ -244,7 +244,7 @@ func TestDocsOfferNoGoInstall(t *testing.T) { for _, f := range files { if m := goInstall.FindString(readFile(t, f)); m != "" { rel, _ := filepath.Rel(root, f) - t.Errorf("%s offers %q; while releases are on hold the docs build from source", rel, m) + t.Errorf("%s offers %q; the docs offer the GitHub release or a build from source", rel, m) } } } @@ -270,26 +270,39 @@ func TestGoInstallPatternMatches(t *testing.T) { } } -// The module is github.com/simtabi/vigil/src, so Go resolves a version through -// the src/vX.Y.Z tag and ignores vX.Y.Z. release.yml mirrors that alias onto the -// release tag's commit before GoReleaser runs, and never moves one that exists. -func TestReleaseMirrorsTheSrcTag(t *testing.T) { +// The module is github.com/simtabi/vigil/src, so Go resolves a version through the src/vX.Y.Z tag +// and ignores vX.Y.Z. The shared simtabi/.github go-release.yml mirrors that alias onto the release +// tag's commit before GoReleaser runs, and never moves one that exists, as long as the caller +// leaves module-alias-prefix at its default, src/. The caller must also keep this repository's +// config, Go version file and changelog, run on macOS (the darwin targets need cgo and the macOS +// SDK) and under the release environment, as the workflow it replaced did. +func TestReleaseInputsMatchTheRepository(t *testing.T) { root := repoRoot(t) wf := readFile(t, root, ".github", "workflows", "release.yml") - mirror := strings.Index(wf, `alias_tag="src/${GITHUB_REF_NAME}"`) - run := strings.Index(wf, "goreleaser/goreleaser-action@") - if mirror < 0 { - t.Fatal("release.yml does not mirror src/vX.Y.Z onto the release tag") + if !strings.Contains(wf, "uses: simtabi/.github/.github/workflows/go-release.yml@") { + t.Fatal("release.yml does not call the shared go-release.yml; this guard reads its inputs") } - if run < 0 || mirror > run { - t.Error("release.yml must mirror the src/ tag before GoReleaser runs") + defaults := map[string]string{ + "module-alias-prefix": "src/", + "goreleaser-config": "src/.goreleaser.yaml", + "go-version-file": "src/go.mod", + "changelog": "CHANGELOG.md", } - if !strings.Contains(wf, `git rev-parse -q --verify "refs/tags/${alias_tag}"`) { - t.Error("release.yml must leave an existing src/ tag alone: a public tag is never moved") + required := map[string]string{ + "runs-on": "macos-latest", + "environment": "release", } - for _, want := range []string{"--config src/.goreleaser.yaml", "go-version-file: src/go.mod", "subject-checksums: .cache/goreleaser/checksums.txt"} { - if !strings.Contains(wf, want) { - t.Errorf("release.yml lacks %q: the module and GoReleaser's dist moved", want) + input := func(name string) []string { + return regexp.MustCompile(`(?m)^ ` + name + `:\s*['"]?([^'"\s#]*)`).FindStringSubmatch(wf) + } + for name, want := range defaults { + if m := input(name); m != nil && m[1] != want { + t.Errorf("release.yml sets %s to %q; this repository needs the shared default %q", name, m[1], want) + } + } + for name, want := range required { + if m := input(name); m == nil || m[1] != want { + t.Errorf("release.yml must set %s: %s", name, want) } } @@ -303,3 +316,27 @@ func TestReleaseMirrorsTheSrcTag(t *testing.T) { t.Error("src/.goreleaser.yaml writes into build/, which holds one folder per OS and nothing else") } } + +// otherChannels are the GoReleaser blocks that publish somewhere other than the GitHub release: +// package managers, images and arbitrary publishers (~/.claude/rules/go.md, owner, 2026-11-13). +// nfpms stays: it builds the .deb/.rpm/.apk files that ship as release assets. +var otherChannels = []string{ + "brews", "homebrew_casks", "scoops", "winget", "chocolateys", "aurs", "aur_sources", + "snapcrafts", "flatpaks", "nix", "dockers", "dockers_v2", "docker_manifests", "docker_signs", + "kos", "krews", "npms", "cloudsmiths", "furies", "publishers", "blobs", "uploads", "artifactories", +} + +// The GitHub release is the only channel. The Homebrew cask, Scoop manifest and Winget manifest +// this config published until 2026-11-13 are gone from it and must not come back; the shared +// workflow refuses such a config at release time, and this fails first, on the pull request. +func TestGoReleaserPublishesNowhereElse(t *testing.T) { + cfg := readFile(t, repoRoot(t), "src", ".goreleaser.yaml") + if !regexp.MustCompile(`(?m)^release:`).MatchString(cfg) { + t.Fatal("src/.goreleaser.yaml has no release block; the GitHub release is the channel") + } + for _, k := range otherChannels { + if regexp.MustCompile(`(?m)^` + k + `:`).MatchString(cfg) { + t.Errorf("src/.goreleaser.yaml publishes through %q; binaries go to the GitHub release only", k) + } + } +} From 9820567eaab0fe9a883a4326758f6d74da361f2c Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 09:47:43 -0500 Subject: [PATCH 2/5] Stop publishing to Homebrew, Scoop and Winget Binaries ship on the GitHub release only (owner, 2026-11-13). The homebrew_casks, scoops and winget blocks go. simtabi/homebrew-tap and simtabi/scoop-bucket hold no vigil entry. --- src/.goreleaser.yaml | 82 ++++---------------------------------------- 1 file changed, 7 insertions(+), 75 deletions(-) diff --git a/src/.goreleaser.yaml b/src/.goreleaser.yaml index 65ee0c7..4162ba7 100644 --- a/src/.goreleaser.yaml +++ b/src/.goreleaser.yaml @@ -8,8 +8,8 @@ project_name: vigil # # GoReleaser writes into a scratch dist, .cache/goreleaser/ (gitignored), never # into build/. build/ holds one folder per OS and nothing else; `make cross` and -# `make dist` write it with src/scripts/build-all.sh. A dispatched release -# uploads straight from this dist, so release assets stay flat. +# `make dist` write it with src/scripts/build-all.sh. A release (simtabi/.github's +# go-release.yml) uploads straight from this dist, so release assets stay flat. dist: .cache/goreleaser before: @@ -136,79 +136,11 @@ sboms: - id: archive artifacts: archive -homebrew_casks: - - name: vigil - ids: [archive] - repository: - owner: simtabi - name: homebrew-tap - token: "{{ .Env.TAP_GITHUB_TOKEN }}" - # Write deploy key on simtabi/homebrew-tap (secret HOMEBREW_TAP_DEPLOY_KEY): reaches that - # one repository only. Set, git pushes with it; unset, the URL is empty and the token - # path above runs. Measured with GoReleaser 2.18.3. - git: - url: '{{ if envOrDefault "HOMEBREW_TAP_DEPLOY_KEY" "" }}git@github.com:simtabi/homebrew-tap.git{{ end }}' - private_key: '{{ envOrDefault "HOMEBREW_TAP_DEPLOY_KEY" "" }}' - directory: Casks - homepage: "https://opensource.simtabi.com/products/vigil" - description: "Keep Microsoft Teams active on a configurable schedule." - hooks: - post: - install: | - if OS.mac? - system_command "/usr/bin/xattr", - args: ["-dr", "com.apple.quarantine", "#{staged_path}/vigil"], - sudo: false - end - -scoops: - - name: vigil - ids: [archive] - repository: - owner: simtabi - name: scoop-bucket - token: "{{ .Env.TAP_GITHUB_TOKEN }}" - # Write deploy key on simtabi/scoop-bucket (secret SCOOP_BUCKET_DEPLOY_KEY): reaches that - # one repository only. Set, git pushes with it; unset, the URL is empty and the token - # path above runs. Measured with GoReleaser 2.18.3. - git: - url: '{{ if envOrDefault "SCOOP_BUCKET_DEPLOY_KEY" "" }}git@github.com:simtabi/scoop-bucket.git{{ end }}' - private_key: '{{ envOrDefault "SCOOP_BUCKET_DEPLOY_KEY" "" }}' - directory: bucket - homepage: "https://opensource.simtabi.com/products/vigil" - description: "Keep Microsoft Teams active on a configurable schedule." - license: MIT - # Scoop publishes on its own switch: SKIP_SCOOP, else SKIP_WINDOWS_PKGS, else skipped. `or` takes the - # first non-empty value, so a variable CI sets to empty falls through instead of publishing - # (envOrDefault returns an empty value as set; measured with GoReleaser 2.18.3). - skip_upload: '{{ or (envOrDefault "SKIP_SCOOP" "") (envOrDefault "SKIP_WINDOWS_PKGS" "") "true" }}' - -# Winget (Microsoft's default Windows package manager). Publishes a manifest to -# a simtabi/winget-pkgs fork and opens a PR to microsoft/winget-pkgs. Package id -# is Simtabi.vigil. Gated behind SKIP_WINDOWS_PKGS until the fork exists. -winget: - - name: vigil - publisher: Simtabi - ids: [archive] - license: MIT - homepage: "https://opensource.simtabi.com/products/vigil" - short_description: "Keep Microsoft Teams active on a configurable schedule." - description: "Keep Microsoft Teams active on a configurable schedule." - publisher_url: "https://simtabi.com" - release_notes_url: "https://github.com/simtabi/vigil/releases/tag/{{ .Tag }}" - repository: - owner: simtabi - name: winget-pkgs - branch: "vigil-{{ .Version }}" - token: "{{ .Env.TAP_GITHUB_TOKEN }}" - pull_request: - enabled: true - draft: false - base: - owner: microsoft - name: winget-pkgs - branch: master - skip_upload: '{{ envOrDefault "SKIP_WINDOWS_PKGS" "true" }}' +# The GitHub release is the only channel (owner, 2026-11-13): no homebrew_casks, scoops, winget +# or any other publishing block. vigil has no release on GitHub now and no entry in +# simtabi/homebrew-tap or simtabi/scoop-bucket, so nothing there is left to freeze. +# simtabi/.github's go-release.yml refuses this config if one of those blocks comes back, and +# src/internal/release's TestGoReleaserPublishesNowhereElse fails first. release: github: From 89bc33fd5da268ceb653cbc209abff391c08a38f Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 09:47:43 -0500 Subject: [PATCH 3/5] Document the GitHub release as the one channel README and installation offer the release download, checked against checksums.txt, then a build from source. docs/release.md gives both ways to release, and says the Release workflow stays disabled until the owner enables it. --- CHANGELOG.md | 55 +++++++++++++-------- Makefile | 2 +- README.md | 31 +++++++++--- docs/installation.md | 48 +++++++++++++----- docs/release.md | 102 ++++++++++++++++++++++----------------- docs/updating.md | 19 ++++---- src/scripts/build-all.sh | 4 +- 7 files changed, 163 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3990831..838e440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,19 +15,25 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). release download fails. ### Changed -- **The `Release` workflow is disabled in Actions while binary releases are on - hold.** The tags `v0.1.0` through `v0.7.0` carry an older `release.yml` that a - manual dispatch would run with no confirmation step, and a disabled workflow - runs on no ref. `docs/release.md` gives the enable, dispatch and disable - commands for an approved release. -- **Binary releases are on hold; the release workflow runs only on a confirmed - manual dispatch.** A tag push no longer starts GoReleaser. A release is - dispatched on a `v[0-9]*` tag with `-f confirm=release-go` - (`gh workflow run release.yml --repo simtabi/vigil --ref vX.Y.Z -f confirm=release-go`), - and the first step refuses anything else before a single artifact is built. - `TestReleaseRunsOnlyOnAConfirmedDispatch` fails if `release.yml` regains an - automatic trigger or loses the confirmation, or if the refusal stops being the - first step. +- **Binaries ship on the GitHub release only, built on a version tag or on + request.** A pushed `vX.Y.Z` tag (or `vX.Y.Z-rc.N` pre-release) builds and + publishes the release, and + `gh workflow run release.yml --repo simtabi/vigil -f tag=vX.Y.Z` does the same + for a tag already pushed. `release.yml` is now a thin caller of simtabi/.github's + shared `go-release.yml`, run on `macos-latest` (the darwin targets need cgo) under + the `release` environment; the shared workflow checks the tag, takes the notes + from this file and reads the published body back, mirrors `src/vX.Y.Z`, runs + GoReleaser and attests provenance. This replaces the dispatch-only, + `-f confirm=release-go` workflow of this cycle, which never shipped, and its own + `gh release edit --notes-file` step. `TestReleaseRunsOnAVersionTagOrOnRequest` + and `TestReleaseInputsMatchTheRepository` fail if a trigger is added, the job + stops calling the shared workflow, or the runner, environment, alias tag, config + or changelog input drifts. +- **The `Release` workflow stays disabled in Actions until the owner enables it.** + It was disabled on 2026-11-08 because a dispatch with `--ref` runs the workflow a + tag carries. The only version tag on GitHub now, `v0.1.0`, carries a dispatch-only + workflow that refuses to run without `-f confirm=release-go`; `docs/release.md` + gives the enable command. - **The Go module lives in `src/`.** `go.mod`, `go.sum`, `cmd/`, `internal/`, `tui/`, `tools/iconz`, `scripts/`, `.goreleaser.yaml` and `.golangci.yml` moved under `src/`, and the module path is now `github.com/simtabi/vigil/src`, as the @@ -66,14 +72,21 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). `brand.md` and `cli.md` give the new paths. They still say binary releases are on hold and build from source; `updating.md` no longer offers re-running `go install` and says that channel is frozen at 0.7.0. -- **Building from source is the only documented install path.** The README and - `installation.md` no longer offer the install scripts, Homebrew, Scoop, deb/rpm, - release downloads or `go install`; they give a clone, `make build`, - `vigil self install`, how to pin a tag and update, and say the 0.1.0-0.7.0 - release archives stay where they are but no new release is published. - `updating.md` says `vigil upgrade` finds nothing newer than 0.7.0 while - releases are on hold, and that it refuses to replace a source build with an - older release unless `--force` is given. +- **Two install paths, in order: the GitHub release, then a build from source.** + The README and `installation.md` give the release download checked against + `checksums.txt` (and `gh attestation verify`), then a clone, `make build`, + `vigil self install`, how to pin a tag and update. No Homebrew, Scoop, Winget or + `go install` path is offered. `updating.md` says `vigil upgrade` reads the latest + GitHub release, and that it refuses to replace a source build with an older + release unless `--force` is given. + +### Removed +- **Homebrew cask, Scoop and Winget publishing.** `src/.goreleaser.yaml` loses its + `homebrew_casks`, `scoops` and `winget` blocks, and `release.yml` the tap and + bucket deploy keys, `TAP_GITHUB_TOKEN` and the `SKIP_SCOOP` / + `SKIP_WINDOWS_PKGS` switches that fed them. `simtabi/homebrew-tap` and + `simtabi/scoop-bucket` hold no vigil entry, so nothing there is affected. + `TestGoReleaserPublishesNowhereElse` fails if a publishing block returns. ### Fixed - **`vigil upgrade` no longer replaces a newer build with an older release.** diff --git a/Makefile b/Makefile index e69523f..d48f964 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ # as macos and .exe on Windows. `make check-build` (TestBuildLayout) fails on # anything else. # -# Nothing here publishes. Releases are dispatched by hand (docs/release.md). +# Nothing here publishes. A release builds on a pushed vX.Y.Z tag or on request (docs/release.md). SHELL := /usr/bin/env bash # `make` alone builds, as it did before the help target existed. diff --git a/README.md b/README.md index bdedb8a..9207edb 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,25 @@ schedule, keeps the session non-idle with one of two **engines**: ## Install -vigil is built from source: binary releases are on hold, so there is no -download, install-script, package-manager or `go install` channel to use. You -need Go (the version `src/go.mod` names), `git` and `make`; macOS also needs the -Xcode Command Line Tools, because the input backend uses cgo. +Download the binary for your platform from the +[GitHub releases](https://github.com/simtabi/vigil/releases) (`vigil__`, +e.g. `vigil_macos_universal`, `vigil_linux_amd64`), check it against the release's +`checksums.txt`, and let it install itself: + +```bash +gh release download --repo simtabi/vigil --pattern vigil_macos_universal --pattern checksums.txt +shasum -a 256 -c checksums.txt --ignore-missing +chmod +x vigil_macos_universal && ./vigil_macos_universal self install +vigil doctor +``` + +The GitHub release is the only channel: there is no package-manager or +`go install` path. GitHub lists no vigil release yet (checked 2026-11-13); until +the first one under the current release workflow is published, build from source. + +To build from source you need Go (the version `src/go.mod` names), `git` and +`make`; macOS also needs the Xcode Command Line Tools, because the input backend +uses cgo. ```bash git clone https://github.com/simtabi/vigil ~/src/vigil @@ -72,8 +87,8 @@ vigil doctor ``` Then run `vigil config wizard` (or `vigil config init`) and `vigil install`. -Windows, pinning a version, updating, and the archives published for 0.1.0 to -0.7.0 are in [docs/installation.md](docs/installation.md). +Windows, verifying the attestation, pinning a version, updating, and the release +asset names are in [docs/installation.md](docs/installation.md). The **input** engine must be installed with `--scope user` (default, GUI session); a system-wide service (`--scope system`) is intended for the `graph` @@ -109,8 +124,8 @@ The full configuration walkthrough is in [docs/configuration.md](docs/configurat ## Updating -No new releases are published while binary releases are on hold, so update a -source build from git: +A release install updates with `vigil upgrade` ([docs/updating.md](docs/updating.md)). +Update a source build from git: ```bash git -C ~/src/vigil pull --ff-only diff --git a/docs/installation.md b/docs/installation.md index bac0811..4fe7b82 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,10 +2,36 @@ ## Install -vigil is built from source. Binary releases are on hold until the maintainer -decides otherwise, so this page offers no download, install-script, -package-manager or `go install` path. You need Go (the version `src/go.mod` -names), `git` and `make`. +Two ways, in this order: download the binary from the GitHub release, or build +from source. This page offers no package-manager or `go install` path. + +### Download a release + +Download the binary for your platform from the +[GitHub releases](https://github.com/simtabi/vigil/releases) (`vigil__`, +e.g. `vigil_macos_universal`, `vigil_linux_amd64`), check it against the release's +`checksums.txt`, and let it install itself: + +```bash +gh release download --repo simtabi/vigil --pattern vigil_macos_universal --pattern checksums.txt +shasum -a 256 -c checksums.txt --ignore-missing +chmod +x vigil_macos_universal && ./vigil_macos_universal self install +vigil doctor +``` + +The GitHub release is the only channel: there is no package-manager or +`go install` path. GitHub lists no vigil release yet (checked 2026-11-13); until +the first one under the current release workflow is published, build from source. + +Every asset in `checksums.txt` carries a build-provenance attestation: + +```bash +gh attestation verify vigil_macos_universal --repo simtabi/vigil +``` + +### Build from source + +You need Go (the version `src/go.mod` names), `git` and `make`. ```bash git clone https://github.com/simtabi/vigil ~/src/vigil @@ -46,16 +72,12 @@ Do not use `vigil upgrade` on a source build; see [updating.md](updating.md). `vigil version` reports the version `git describe` gave the build, so a build from a branch is still identifiable. -### Earlier binary releases +### Release assets -0.1.0 to 0.7.0 were published as archives on -[GitHub Releases](https://github.com/simtabi/vigil/releases). Those stay where -they are and an existing install keeps working, but no new release is published -while binary releases are on hold: build from source to get anything newer. The -install scripts in `src/scripts/` remain in the tree, but they download the latest -published release, so they cannot install anything newer than 0.7.0. For -reference, those archives are named `vigil__` (macOS uses the -`macos` token): +Releases are published on [GitHub Releases](https://github.com/simtabi/vigil/releases) +only; no Homebrew, Scoop or Winget manifest is published. The install scripts in +`src/scripts/` download the latest published release. The archives are named +`vigil__` (macOS uses the `macos` token): | Machine | Archive | |---------|---------| diff --git a/docs/release.md b/docs/release.md index 7708ba4..a79d5f2 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,48 +1,58 @@ # Release process -Releases are tag-driven, and binary releases are on hold. The git tag is the -single source of version truth; `vigil version` is stamped at build time via -ldflags (`github.com/simtabi/vigil/src/internal/cli.version/commit/date`). A `vX.Y.Z` tag records a version; -binaries are built from it only when the release workflow is dispatched by hand -with a confirmation (below). Until binary releases resume, users build from -source ([installation](installation.md#install)). +Releases are tag-driven. The git tag is the single source of version truth; +`vigil version` is stamped at build time via ldflags +(`github.com/simtabi/vigil/src/internal/cli.version/commit/date`). Binaries ship +on the GitHub release only: no Homebrew, Scoop, Winget, `go install`, image or +any other channel. A release builds in one of two ways: + +```bash +git push origin vX.Y.Z # builds and publishes vX.Y.Z +gh workflow run release.yml --repo simtabi/vigil -f tag=vX.Y.Z # the same, for a tag already pushed +``` + +Because a pushed `vX.Y.Z` tag publishes, push one only for final code. A tag is +final: it is never moved, deleted or reused. Test builds use `vX.Y.Z-rc.N`, which +publishes a GitHub pre-release. ## Cutting a release 1. Move `[Unreleased]` items in `CHANGELOG.md` under a new `## [X.Y.Z]` section with the date. -2. Commit on `main`. -3. Tag and push: +2. Land it on `main` through a pull request. A final version with no + `## [X.Y.Z]` section is refused. +3. Tag the merge commit and push the tag. The push builds and publishes the + release: ```bash - git tag vX.Y.Z - git push origin vX.Y.Z + git -C ~/src/vigil tag vX.Y.Z + git -C ~/src/vigil push origin vX.Y.Z ``` - Pushing a tag publishes nothing: no workflow starts on a tag push. -4. Only when the owner has approved a binary release for this version, turn the - `Release` workflow (`.github/workflows/release.yml`) on, dispatch it on the - tag, and turn it off again once the run has started: +4. To build a release for a tag already on GitHub (a first release of an older + tag, or a re-run after a failed one), request it instead: ```bash - gh workflow enable release.yml --repo simtabi/vigil - gh workflow run release.yml --repo simtabi/vigil --ref vX.Y.Z -f confirm=release-go - gh workflow disable release.yml --repo simtabi/vigil + gh workflow run release.yml --repo simtabi/vigil -f tag=vX.Y.Z ``` - `vX.Y.Z` must be a real tag already on GitHub. The command publishes for real - (GitHub Release, assets, attestation), so run it only for an approved - version. The first step refuses to run without `confirm=release-go`, or on - any ref that is not a `v[0-9]*` tag, before anything is built. + This runs `main`'s `release.yml` and builds the named tag. Do not add + `--ref vX.Y.Z`: that runs the copy of `release.yml` the tag carries, which for + `v0.1.0` is the older dispatch-only workflow. + +**The `Release` workflow is still disabled in Actions** (`disabled_manually` +since 2026-11-08, when binary releases were on hold), so neither a tag push nor a +request runs it until the owner enables it: + +```bash +gh workflow enable release.yml --repo simtabi/vigil +gh workflow list --repo simtabi/vigil --all # Release: active +``` -**The `Release` workflow is disabled in Actions while binary releases are on -hold** (since 2026-11-08). A dispatch runs the copy of `release.yml` that the tag -carries, and the tags `v0.1.0` through `v0.7.0` carry an older one that accepts -a manual dispatch and publishes with no confirmation step. A tag cannot be -changed, so the workflow's enabled state is the only control that covers them: -a disabled workflow runs on no ref. Check it with -`gh workflow list --repo simtabi/vigil --all`, which shows `Release` as -`disabled_manually`. +It was disabled because a dispatch with `--ref` runs the workflow the tag +carries. Measured 2026-11-13, the only version tag on GitHub is `v0.1.0`, and its +`release.yml` is dispatch-only and refuses to run without `-f confirm=release-go`, +so enabling the workflow exposes no unconfirmed release path. ## Repository layout @@ -107,7 +117,7 @@ Archive names are **version-less** (`vigil__.{tar.gz,zip}`) to keep th self-update contract stable. macOS ships a **universal** binary (`vigil_macos_universal`, Apple Silicon + Intel). macOS artifacts use the `macos` token (Go's `darwin` is hard to recognize); self-update sets the updater's OS to -`macos` accordingly. GitHub release assets stay flat: a dispatched release runs +`macos` accordingly. GitHub release assets stay flat: a release runs GoReleaser from the repository root with `--config src/.goreleaser.yaml`, which writes its scratch dist to `.cache/goreleaser/` (gitignored), never into `build/`, and uploads from there. @@ -115,16 +125,18 @@ and uploads from there. ## Workflows ``` - gh workflow run release.yml --repo simtabi/vigil --ref vX.Y.Z -f confirm=release-go + git push origin vX.Y.Z or gh workflow run release.yml --repo simtabi/vigil -f tag=vX.Y.Z │ ▼ - release.yml (macOS runner) - ├─ refuse unless confirm=release-go on a v[0-9]* tag + release.yml ─► simtabi/.github go-release.yml (macos-latest, environment release) + ├─ refuse anything but a vX.Y.Z[-pre] tag already on GitHub + ├─ refuse a GoReleaser config holding any other publishing block + ├─ notes from the CHANGELOG section (a final version without one is refused) ├─ mirror src/vX.Y.Z onto the tag ├─ GoReleaser --config src/.goreleaser.yaml ─► .cache/goreleaser/ │ binaries + archives + deb/rpm/apk + source tarball + SBOMs - │ + checksums.txt ─► GitHub Release, tap/bucket manifests - ├─ release body from the CHANGELOG section + │ + checksums.txt ─► GitHub Release + ├─ read the release body back; fail if shorter than the section └─ build-provenance attestation over .cache/goreleaser/checksums.txt pull request ─► ci.yml snapshot ─► build-binaries.yml ─► build/archives/* as run artifacts @@ -136,9 +148,11 @@ and uploads from there. a `mac` job (darwin arm64/amd64 + universal, cgo). Each runs `src/scripts/build-all.sh`, verifies `build/checksums.txt` and the `build/` layout, and uploads `build/archives/*`. -- **`release.yml`** — on a confirmed `workflow_dispatch` against a `vX.Y.Z` - tag only (a tag push starts nothing): GoReleaser cross-compiles and publishes - the GitHub Release, the body is set from the CHANGELOG section, and the +- **`release.yml`** — on a pushed `vX.Y.Z` tag or a `workflow_dispatch` naming + one, a thin caller of simtabi/.github's shared `go-release.yml` with + `runs-on: macos-latest` (the darwin targets need cgo) and + `environment: release`: GoReleaser cross-compiles and publishes the GitHub + Release, its body is the CHANGELOG section (read back and checked), and the artifacts get a build-provenance attestation. - **`ci.yml`** `snapshot` job — on every pull request and on demand: calls `build-binaries` so **ready-to-run binaries are available** as run artifacts @@ -157,11 +171,13 @@ archive name, the flat inner-binary name, **bare-filename** `checksums.txt`, and ## First-release prerequisites 1. Make the repo public. -2. Create `simtabi/homebrew-tap` and `simtabi/scoop-bucket` repositories. -3. Add the write deploy keys `HOMEBREW_TAP_DEPLOY_KEY` (on `simtabi/homebrew-tap`) and `SCOOP_BUCKET_DEPLOY_KEY` (on `simtabi/scoop-bucket`), each a repository secret that reaches its one repository and nothing else (the deploy-key guide in - `imanimanyara/github-apps` `docs/operations.md`). `TAP_GITHUB_TOKEN` is only - a fallback, and the Winget pull request's token. -4. Push the first `vX.Y.Z` tag, then dispatch the release on it as above. +2. Enable the `Release` workflow (see [Cutting a release](#cutting-a-release)). +3. No publishing secrets are needed: the release uses the run's own + `GITHUB_TOKEN`. The tap, bucket and Winget credentials + (`HOMEBREW_TAP_DEPLOY_KEY`, `SCOOP_BUCKET_DEPLOY_KEY`, `TAP_GITHUB_TOKEN`) are + no longer read. +4. Push the first `vX.Y.Z` tag, or request the release of an existing one, as + above. Every release carries a real, human-readable description sourced from the CHANGELOG — never a bare "see changelog" stub. Keep GitHub Actions current via diff --git a/docs/updating.md b/docs/updating.md index 8991b38..0aff994 100644 --- a/docs/updating.md +++ b/docs/updating.md @@ -1,12 +1,11 @@ # Updating -> **Binary releases are on hold.** No new GitHub release is being published, so -> `vigil upgrade` finds nothing newer than 0.7.0, the last published release. -> To get anything newer, build from source and update from git (below). The -> self-update mechanism described on this page is unchanged and still in the -> binary; it will pick up releases again if they resume. +> Binaries ship on the [GitHub release](https://github.com/simtabi/vigil/releases) +> only. A release install updates with `vigil upgrade` (below), which reads the +> latest GitHub release. GitHub lists no vigil release yet (checked 2026-11-13), +> so until the first one is published, update a source build from git. -## From source (the current way to update) +## From source ```bash git -C ~/src/vigil pull --ff-only @@ -69,16 +68,16 @@ the version you are already running. ## Package-manager installs -No package-manager channel is updated while binary releases are on hold. If you -installed through a package manager, `vigil upgrade` refuses and points you -to the right tool: +No package-manager channel is published: the GitHub release is the only one. If +a binary sits under a package manager's path, `vigil upgrade` still refuses and +points you to that tool: | Installed via | Update with | |---------------|-------------| | Homebrew | `brew upgrade vigil` | | Scoop | `scoop update vigil` | | deb/rpm | `apt upgrade` / `dnf upgrade` | -| `go install` (0.7.0 and earlier) | frozen at 0.7.0: switch to a [source build](#from-source-the-current-way-to-update) | +| `go install` (0.7.0 and earlier) | frozen at 0.7.0: switch to a release or a [source build](#from-source) | The interactive TUI shows an "update available" banner only for standalone installs. diff --git a/src/scripts/build-all.sh b/src/scripts/build-all.sh index da71dd0..fc5a61d 100755 --- a/src/scripts/build-all.sh +++ b/src/scripts/build-all.sh @@ -2,8 +2,8 @@ # # Cross-compile every target in build/targets.txt into build/, one folder per OS. # It builds on this machine and publishes nothing; GoReleaser -# (src/.goreleaser.yaml) is the release path, and releases are dispatched by -# hand only (docs/release.md). +# (src/.goreleaser.yaml) is the release path, run by release.yml on a pushed +# vX.Y.Z tag or on request (docs/release.md). # # build/ holds one folder per OS and nothing else: # From 537e81187030b657a415beb6458a5f216e009898 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 09:56:25 -0500 Subject: [PATCH 4/5] Read release.yml with LF line endings in the trigger guard A Windows checkout carries CRLF, so the exact-line match for the tag input failed there while passing on macOS and Linux. --- src/cmd/vigil/releasenotes_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cmd/vigil/releasenotes_test.go b/src/cmd/vigil/releasenotes_test.go index 699062f..9aa30d8 100644 --- a/src/cmd/vigil/releasenotes_test.go +++ b/src/cmd/vigil/releasenotes_test.go @@ -75,7 +75,8 @@ func TestReleaseRunsOnAVersionTagOrOnRequest(t *testing.T) { if err != nil { t.Fatal(err) } - wf := string(raw) + // A Windows checkout may carry CRLF line endings; the structure is read line by line. + wf := strings.ReplaceAll(string(raw), "\r\n", "\n") on := yamlBlock(wf, "", "on") if on == "" { From 07c152cd19902962c3f4386e14acbbb00963b790 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 10:15:55 -0500 Subject: [PATCH 5/5] Pin the shared release workflow to simtabi/.github#28's merge --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca819eb..7d7fedf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,7 @@ jobs: contents: write id-token: write attestations: write - uses: simtabi/.github/.github/workflows/go-release.yml@20630147e3cb8c378c8b9c8ee9b6a371cd517581 # simtabi/.github#28 head; re-pin to its merge commit + uses: simtabi/.github/.github/workflows/go-release.yml@941f7b7ecce5dc8af6daedfc7496dd4367c70877 # simtabi/.github#28 with: tag: ${{ inputs.tag || github.ref_name }} runs-on: macos-latest