From 7f6d1be58f386f9f8f92331c30f051a769fc946f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Jul 2026 01:33:50 -0700 Subject: [PATCH 1/2] ci(release): migrate Discrawl to shared pipeline --- .github/release-allowed-signers | 1 + .github/workflows/release-unified.yml | 39 +++++++ .github/workflows/release.yml | 127 ---------------------- .goreleaser.yaml | 6 - CHANGELOG.md | 6 +- Makefile | 91 ++++++++++++++++ docs/RELEASING.md | 151 ++++---------------------- scripts/release-signed.sh | 65 +---------- scripts/release_signing_test.go | 20 ++-- 9 files changed, 168 insertions(+), 338 deletions(-) create mode 100644 .github/release-allowed-signers create mode 100644 .github/workflows/release-unified.yml delete mode 100644 .github/workflows/release.yml create mode 100644 Makefile diff --git a/.github/release-allowed-signers b/.github/release-allowed-signers new file mode 100644 index 00000000..500e102b --- /dev/null +++ b/.github/release-allowed-signers @@ -0,0 +1 @@ +steipete@gmail.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII9XsaCcr8TInPnHcuTVfvXXcsoUFrOE7menfbEIHFW9 steipete@gmail.com diff --git a/.github/workflows/release-unified.yml b/.github/workflows/release-unified.yml new file mode 100644 index 00000000..bf050e3a --- /dev/null +++ b/.github/workflows/release-unified.yml @@ -0,0 +1,39 @@ +name: Release (unified) + +on: + workflow_dispatch: + inputs: + version: + description: SemVer to release + required: true + type: string + +permissions: {} + +jobs: + release: + permissions: + actions: read + checks: read + contents: write + pull-requests: write + statuses: read + uses: openclaw/release-workflows/.github/workflows/release-go-cli.yml@v1 + with: + version: ${{ inputs.version }} + repository-type: openclaw + homebrew-tap: openclaw/homebrew-tap + homebrew-formula: discrawl + archive-files: '["CHANGELOG.md","LICENSE","README.md"]' + checksum-filename: checksums.txt + nfpm: auto + stable-identifier: org.openclaw.discrawl + require-signed-tag: true + darwin-universal: disabled + secrets: + MACOS_SIGNING_P12: ${{ secrets.MACOS_SIGNING_P12 }} + MACOS_SIGNING_P12_PASSWORD: ${{ secrets.MACOS_SIGNING_P12_PASSWORD }} + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} + ASC_PRIVATE_KEY_P8: ${{ secrets.ASC_PRIVATE_KEY_P8 }} + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 807d9aaa..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: release - -on: - release: - types: - - published - workflow_dispatch: - inputs: - tag: - description: "Release tag to verify and send to Homebrew (e.g. v0.1.0)" - required: true - type: string - -permissions: - contents: read - -jobs: - verify-macos: - strategy: - fail-fast: false - matrix: - include: - - runs-on: macos-15 - arch: arm64 - - runs-on: macos-15-intel - arch: amd64 - runs-on: ${{ matrix.runs-on }} - env: - RELEASE_TAG: ${{ inputs.tag || github.event.release.tag_name }} - GH_TOKEN: ${{ github.token }} - steps: - - name: Checkout trusted verifier - uses: actions/checkout@v7.0.1 - with: - ref: main - - - name: Download signed macOS artifact - shell: bash - run: | - set -euo pipefail - version="${RELEASE_TAG#v}" - mkdir -p artifacts - gh release download "$RELEASE_TAG" \ - --repo "$GITHUB_REPOSITORY" \ - --dir artifacts \ - --pattern "checksums.txt" \ - --pattern "discrawl_${version}_darwin_${{ matrix.arch }}.tar.gz" - - - name: Verify Developer ID signature - shell: bash - run: | - set -euo pipefail - version="${RELEASE_TAG#v}" - ./scripts/verify-macos-release.sh \ - "$RELEASE_TAG" \ - "artifacts/discrawl_${version}_darwin_${{ matrix.arch }}.tar.gz" \ - artifacts/checksums.txt - - update-homebrew-tap: - runs-on: ubuntu-latest - needs: verify-macos - steps: - - name: Resolve release tag - env: - EVENT_NAME: ${{ github.event_name }} - RELEASE_TAG: ${{ github.event.release.tag_name }} - TAG_INPUT: ${{ inputs.tag }} - run: | - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - release_tag="$TAG_INPUT" - else - release_tag="$RELEASE_TAG" - fi - - if [[ ! "$release_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-.+A-Za-z0-9]+)?$ ]]; then - echo "::error::Invalid release tag" - exit 1 - fi - - echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV" - - - name: Dispatch tap formula update - env: - GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} - run: | - if [ -z "$GH_TOKEN" ]; then - echo "::error::Set HOMEBREW_TAP_TOKEN with workflow access to openclaw/homebrew-tap" - exit 1 - fi - - request_id="discrawl-${RELEASE_TAG}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - expected_title="Update discrawl for ${RELEASE_TAG} (${request_id})" - - gh workflow run update-formula.yml \ - --repo openclaw/homebrew-tap \ - --ref main \ - -f formula=discrawl \ - -f tag="$RELEASE_TAG" \ - -f repository=openclaw/discrawl \ - -f artifact_template="{formula}_{version}_{target}.tar.gz" \ - -f request_id="$request_id" - - run_id="" - for _ in {1..30}; do - run_id=$(gh run list \ - --repo openclaw/homebrew-tap \ - --workflow update-formula.yml \ - --branch main \ - --event workflow_dispatch \ - --limit 20 \ - --json databaseId,displayTitle \ - --jq ".[] | select(.displayTitle == \"$expected_title\") | .databaseId" | head -n1) - if [ -n "$run_id" ]; then - break - fi - sleep 5 - done - - if [ -z "$run_id" ]; then - echo "::error::Could not find tap workflow run with title: $expected_title" - exit 1 - fi - - gh run watch "$run_id" \ - --repo openclaw/homebrew-tap \ - --exit-status \ - --interval 10 diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8fe2c5bd..5da4635d 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -13,12 +13,6 @@ builds: - CGO_ENABLED=0 ldflags: - -s -w -X github.com/openclaw/discrawl/internal/cli.version={{ .Version }} - hooks: - post: - - cmd: ./scripts/codesign-release-binary.sh "{{ .Target }}" "{{ .Path }}" - output: true - env: - - DISCRAWL_CODESIGN_REQUIRED={{ if .IsSnapshot }}0{{ else }}1{{ end }} targets: - darwin_amd64 - darwin_arm64 diff --git a/CHANGELOG.md b/CHANGELOG.md index f2cbdc16..108e7c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## 0.11.10 - Unreleased +## 0.11.10 - 2026-07-27 + +### Maintenance + +- Move official releases to the shared signed, notarized, independently verified GitHub Actions pipeline while preserving archive names, contents, checksums, and Homebrew delivery. ## 0.11.9 - 2026-07-26 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..3176df00 --- /dev/null +++ b/Makefile @@ -0,0 +1,91 @@ +BINARY ?= bin/discrawl +VERSION ?= dev +ARTIFACT_DIR ?= dist + +.DEFAULT_GOAL := help + +.PHONY: help build test test-race test-coverage fmt lint tidy-check smoke check snapshot release verify-release release-artifacts release-snapshot generate-sqlc clean + +help: + @printf '%s\n' \ + 'Available targets:' \ + ' help Print available targets (default).' \ + ' build Build the CLI into $(BINARY).' \ + ' test Run the full Go test suite.' \ + ' fmt Check Go formatting.' \ + ' lint Run the static-analysis and vulnerability gates.' \ + ' check Run every local gate enforced by CI.' \ + ' snapshot Build credential-free release artifacts.' \ + ' release Refuse local publishing and print the official CI command.' \ + ' verify-release Verify downloaded release artifacts (VERSION=vX.Y.Z).' \ + ' release-artifacts Alias for release.' \ + ' release-snapshot Alias for snapshot.' \ + ' generate-sqlc Regenerate sqlc output.' \ + ' clean Remove local build output.' + +build: + @binary="$(BINARY)"; mkdir -p "$$(dirname -- "$$binary")"; \ + GOWORK=off go build -ldflags "-X github.com/openclaw/discrawl/internal/cli.version=$(VERSION)" -o "$$binary" ./cmd/discrawl + +test: + GOWORK=off go test -count=1 ./... + +test-race: + GOWORK=off go test -count=1 -race ./... + +test-coverage: + GOWORK=off go test -count=1 ./... -coverprofile=coverage.out + @grep -v '^github.com/openclaw/discrawl/internal/store/storedb/' coverage.out > coverage.filtered.out + @total="$$(go tool cover -func=coverage.filtered.out | awk '/^total:/ { sub(/%$$/, "", $$3); print $$3 }')"; \ + awk -v total="$$total" 'BEGIN { if (total == "" || total + 0 < 85.0) { printf("coverage %s%% is below 85%%\n", total == "" ? "missing" : total); exit 1 } printf("coverage %.1f%%\n", total + 0) }' + +fmt: + @changed="$$(GOWORK=off go run mvdan.cc/gofumpt@v0.10.0 -l .)"; \ + if [ -n "$$changed" ]; then printf 'gofumpt wants changes in:\n%s\n' "$$changed"; exit 1; fi + +lint: + GOWORK=off go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 run + GOWORK=off go vet ./... + GOWORK=off go run honnef.co/go/tools/cmd/staticcheck@v0.7.0 ./... + GOWORK=off go run github.com/securego/gosec/v2/cmd/gosec@v2.27.1 -exclude=G101,G115,G202,G301,G304 ./... + GOWORK=off go run golang.org/x/vuln/cmd/govulncheck@v1.5.0 ./... + @output_file="$$(mktemp)"; trap 'rm -f "$$output_file"' EXIT; \ + GOWORK=off go run golang.org/x/tools/cmd/deadcode@v0.47.0 -test ./... >"$$output_file"; \ + if [ -s "$$output_file" ]; then cat "$$output_file"; exit 1; fi + +tidy-check: + GOWORK=off go mod verify + GOWORK=off go mod tidy + git diff --exit-code -- go.mod go.sum + +smoke: build + @test -n "$$($(BINARY) --version)" + @$(BINARY) metadata --json | grep -q '"schema_version"' + @$(BINARY) help tui | grep -q 'Usage: discrawl tui' + +check: tidy-check fmt lint test-coverage test-race smoke snapshot + +snapshot: + GOWORK=off goreleaser release --snapshot --clean --skip=publish + +release: + @./scripts/release-signed.sh + +verify-release: + @test -n "$(VERSION)" && [ "$(VERSION)" != dev ] || (echo "usage: make verify-release VERSION=vX.Y.Z [ARTIFACT_DIR=dist]" >&2; exit 2) + @set -e; version="$(VERSION)"; release_version="$${version#v}"; \ + for arch in amd64 arm64; do \ + ./scripts/verify-macos-release.sh "$$version" \ + "$(ARTIFACT_DIR)/discrawl_$${release_version}_darwin_$${arch}.tar.gz" \ + "$(ARTIFACT_DIR)/checksums.txt"; \ + done + +release-artifacts: release + +release-snapshot: snapshot + +generate-sqlc: + ./scripts/generate-sqlc.sh + +clean: + rm -rf -- bin dist coverage.out coverage.filtered.out diff --git a/docs/RELEASING.md b/docs/RELEASING.md index e0a43ead..e8d9a2f2 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -1,151 +1,44 @@ --- -summary: "Release checklist for discrawl (GitHub release binaries via GoReleaser + Homebrew tap update)" +summary: "Official Discrawl releases through the shared GitHub Actions pipeline" --- # Releasing `discrawl` -Always do all steps below. No partial releases. +`.github/workflows/release-unified.yml` is the only official release path. It calls `openclaw/release-workflows@v1` from protected `main`, requires the existing SSH-signed version tag, builds the exact GoReleaser matrix, signs and notarizes both thin macOS binaries as `org.openclaw.discrawl`, verifies the complete asset inventory independently on arm64 and Intel macOS, publishes `checksums.txt`, and waits for the `openclaw/homebrew-tap` handoff to succeed. -Assumptions: -- Repo: `openclaw/discrawl` -- Binary: `discrawl` -- GoReleaser config: `.goreleaser.yaml` -- Homebrew tap repo: `~/Projects/homebrew-tap` -- Official releases run from macOS through the shared managed-keychain helper +The public compatibility contract remains: -## 0) Prereqs +- `discrawl_VERSION_{darwin,linux}_{amd64,arm64}.tar.gz` +- `discrawl_VERSION_windows_{amd64,arm64}.zip` +- `checksums.txt` +- `CHANGELOG.md`, `LICENSE`, `README.md`, and `discrawl` inside every platform archive +- OpenClaw Foundation Team ID `FWJYW4S8P8` and code identifier `org.openclaw.discrawl` -- Clean working tree on `main` -- Go toolchain from `go.mod` -- GitHub CLI authenticated -- CI green on `main` -- OpenClaw Foundation Developer ID Application identity available through the managed release keychain -- A `NOTARYTOOL_KEYCHAIN_PROFILE` runtime value whose credentials are stored in the operator's login keychain +The shared pipeline also publishes verifier control assets (`ASSET-INVENTORY.json`, `SIGNING-MANIFEST.json`, and `RELEASE-NOTES.md`). -## 1) Verify build + tests +## Release -```sh -go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 run -go test -count=1 ./... -coverprofile=coverage.out -go tool cover -func=coverage.out | tail -n 1 -go test -count=1 -race ./... -go build -o /tmp/discrawl ./cmd/discrawl -gh run list -L 5 --branch main -``` - -Coverage floor: `85%+` - -## 2) Finalize changelog and release notes - -Replace the current `Unreleased` heading in `CHANGELOG.md` with the release -version and date. Use that exact section as the GitHub Release body and append a -link to the full changelog at the tagged commit. - -Example: - -- `## 0.2.0 - 2026-03-08` - -## 3) Commit, tag, push +Prepare a dated changelog section and land it on protected `main`. The signing key configured by `user.signingkey` must be the SSH key listed for your principal in `.github/release-allowed-signers`. Create and push an annotated SSH-signed tag whose commit is reachable from `main`, verify it explicitly against the repository allowlist, then dispatch the workflow: ```sh -git checkout main -git pull --ff-only origin main -git commit -am "chore(release): vX.Y.Z" -git tag -s vX.Y.Z -m "Release X.Y.Z" -git tag -v vX.Y.Z -git push origin main +git -c gpg.format=ssh tag -s vX.Y.Z -m "Release X.Y.Z" +git -c gpg.format=ssh -c gpg.ssh.allowedSignersFile=.github/release-allowed-signers tag -v vX.Y.Z git push origin vX.Y.Z +gh workflow run release-unified.yml --repo openclaw/discrawl -f version=X.Y.Z ``` -## 4) Publish signed and notarized release artifacts - -From the clean checkout whose `HEAD` exactly matches the signed tag, publish all -GoReleaser artifacts through the shared secret-safe keychain helper: - -```sh -./scripts/release-signed.sh vX.Y.Z -``` - -The script uses an existing GitHub token environment variable or the -authenticated GitHub CLI without writing credentials to disk. Set -`NOTARYTOOL_KEYCHAIN_PROFILE` only in the private release environment; never -commit a real profile name or its credentials. - -The GoReleaser hook signs and notarizes both thin macOS binaries. Each binary is -signed into a temporary candidate with hardened runtime and a trusted timestamp, -submitted to Apple as an ephemeral ZIP with `notarytool --wait`, and accepted -only after the response reports `Accepted` and an online ticket check passes. -Failed signing or notarization leaves the GoReleaser output untouched. Snapshot -builds and all non-macOS targets remain credential-free. Production builds fail -closed unless the notary profile is set and the macOS artifacts use identifier -`org.openclaw.discrawl` and OpenClaw Foundation Team ID `FWJYW4S8P8`. - -Publishing the GitHub Release triggers `.github/workflows/release.yml`, which -downloads both macOS archives on native runners, verifies their checksums and -Developer ID signatures, hardened-runtime metadata, stable requirement, and -Apple notarization tickets, then dispatches the Homebrew update. Raw CLI -binaries cannot carry stapled tickets, so this verification requires network -access to Apple's ticket service. - -## 5) Verify GitHub Release - -```sh -gh run list -L 5 --workflow release.yml -gh release view vX.Y.Z --json url,body,assets -``` - -Before closeout, confirm the Release body contains the exact `X.Y.Z` -changelog section and a full-changelog link. If it is missing or stale, prepare -the corrected body in a reviewed file and run: - -```sh -gh release edit vX.Y.Z --notes-file /tmp/discrawl-release-notes.md -``` - -Confirm checksums plus assets exist for: - -- `darwin_amd64` -- `darwin_arm64` -- `linux_amd64` -- `linux_arm64` -- `windows_amd64` -- `windows_arm64` - -## 6) Verify Homebrew tap update +Watch the exact run through publication and Homebrew handoff. The release is complete only when the GitHub Release contains the full asset set, both native macOS verification jobs pass, and the tap's `update-formula.yml` run is green. -`discrawl` ships a binary formula in `~/Projects/homebrew-tap/Formula/discrawl.rb` that points at the GitHub release archives. +## Local diagnostics -The release workflow dispatches `openclaw/homebrew-tap`'s -`update-formula.yml` and waits for it to finish. Verify the formula version and -per-platform checksums landed, then test the installed binary. Do not manually -duplicate a successful automated update. +Local publishing is disabled. `make release` and the compatibility alias `make release-artifacts` refuse and print the official workflow command. -Useful commands: +Use these credential-free or read-only diagnostics: ```sh -curl -L -o /tmp/discrawl-darwin-arm64.tgz https://github.com/openclaw/discrawl/releases/download/vX.Y.Z/discrawl_X.Y.Z_darwin_arm64.tar.gz -shasum -a 256 /tmp/discrawl-darwin-arm64.tgz -brew uninstall discrawl || true -brew install openclaw/tap/discrawl -discrawl --version -brew info openclaw/tap/discrawl +make check +make snapshot +make verify-release VERSION=vX.Y.Z ARTIFACT_DIR=/path/to/downloaded-assets ``` -## 7) Close out the release - -Only after the GitHub Release, release notes, assets, and Homebrew formula are -verified, add the next patch section at the top of `CHANGELOG.md`. For example, -after `0.11.4`: - -- `## 0.11.5 - Unreleased` - -Commit that closeout with a conventional `chore:` message and push `main`. - -## Notes - -- Build-time version stamping comes from `-X github.com/openclaw/discrawl/internal/cli.version={{ .Version }}` -- If release workflow needs a rerun: - -```sh -gh workflow run release.yml -f tag=vX.Y.Z -``` +`make snapshot` builds without release credentials. `make verify-release` rechecks downloaded Darwin archives against `checksums.txt`, the stable Foundation designated requirement, hardened runtime, architecture, embedded version, and Apple's online notarization ticket. diff --git a/scripts/release-signed.sh b/scripts/release-signed.sh index 8eb9245b..b52fd05d 100755 --- a/scripts/release-signed.sh +++ b/scripts/release-signed.sh @@ -1,65 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) -VERSION=${1:-} -HELPER=${MAC_RELEASE_HELPER:-$HOME/Projects/agent-scripts/skills/release-mac-app/scripts/mac-release} - -[[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]] || { - echo "usage: $0 vX.Y.Z" >&2 - exit 2 -} -[[ "$(uname -s)" == Darwin ]] || { - echo "signed releases must run on macOS" >&2 - exit 1 -} -[[ -n "${NOTARYTOOL_KEYCHAIN_PROFILE:-}" ]] || { - echo "NOTARYTOOL_KEYCHAIN_PROFILE is required for official macOS release notarization" >&2 - exit 1 -} -[[ -x "$HELPER" ]] || { - echo "managed-keychain release helper not found: $HELPER" >&2 - exit 1 -} -command -v goreleaser >/dev/null || { - echo "goreleaser is required" >&2 - exit 1 -} - -head_commit=$(git -C "$ROOT" rev-parse HEAD) -tag_commit=$(git -C "$ROOT" rev-parse "refs/tags/$VERSION^{commit}" 2>/dev/null) || { - echo "release tag does not exist locally: $VERSION" >&2 - exit 1 -} -[[ "$head_commit" == "$tag_commit" ]] || { - echo "HEAD does not match release tag $VERSION" >&2 - exit 1 -} -[[ -z "$(git -C "$ROOT" status --porcelain --untracked-files=normal)" ]] || { - echo "release checkout is not clean" >&2 - exit 1 -} -git -C "$ROOT" tag -v "$VERSION" >/dev/null 2>&1 || { - echo "release tag is not signed by a trusted git signing key: $VERSION" >&2 - exit 1 -} - -release_auth=${GITHUB_TOKEN:-${GH_TOKEN:-}} -if [[ -z "$release_auth" ]]; then - command -v gh >/dev/null || { - echo "GITHUB_TOKEN, GH_TOKEN, or an authenticated gh CLI is required for GoReleaser publishing" >&2 - exit 1 - } - release_auth=$(gh auth token) -fi -[[ -n "$release_auth" ]] || { - echo "GitHub authentication is unavailable" >&2 - exit 1 -} -export GITHUB_TOKEN=$release_auth -unset release_auth - -cd "$ROOT" -exec "$HELPER" codesign-run -- \ - env DISCRAWL_CODESIGN_REQUIRED=1 \ - goreleaser release --clean --config "$ROOT/.goreleaser.yaml" +echo "local releases are disabled because this path cannot enforce the shared verifier and publication chain" >&2 +echo "official releases must use: gh workflow run release-unified.yml --repo openclaw/discrawl -f version=X.Y.Z" >&2 +exit 1 diff --git a/scripts/release_signing_test.go b/scripts/release_signing_test.go index c070c4c7..bc65d5b5 100644 --- a/scripts/release_signing_test.go +++ b/scripts/release_signing_test.go @@ -209,7 +209,7 @@ func TestCodesignReleaseBinaryPreservesArtifactOnNotaryRejection(t *testing.T) { } } -func TestReleaseSignedFailsClosedWithoutNotaryProfile(t *testing.T) { +func TestReleaseSignedRedirectsToUnifiedWorkflow(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("release-signed.sh is a POSIX shell script") } @@ -219,22 +219,16 @@ func TestReleaseSignedFailsClosedWithoutNotaryProfile(t *testing.T) { t.Skip("bash is not available") } - tempDir := t.TempDir() - stubDir := filepath.Join(tempDir, "bin") - if err := os.Mkdir(stubDir, 0o755); err != nil { - t.Fatal(err) - } - writeExecutable(t, filepath.Join(stubDir, "uname"), "#!/bin/sh\nprintf 'Darwin\\n'\n") - - cmd := exec.CommandContext(t.Context(), bash, "./release-signed.sh", "v0.0.0") + cmd := exec.CommandContext(t.Context(), bash, "./release-signed.sh") cmd.Dir = signingScriptDir(t) - cmd.Env = signingTestEnv("PATH=" + stubDir + string(os.PathListSeparator) + os.Getenv("PATH")) + cmd.Env = signingTestEnv() output, err := cmd.CombinedOutput() if err == nil { - t.Fatalf("release entrypoint accepted a missing notary profile: %s", output) + t.Fatalf("local release entrypoint unexpectedly succeeded: %s", output) } - if !strings.Contains(string(output), "NOTARYTOOL_KEYCHAIN_PROFILE is required") { - t.Fatalf("unexpected release entrypoint failure: %s", output) + want := "gh workflow run release-unified.yml --repo openclaw/discrawl -f version=X.Y.Z" + if !strings.Contains(string(output), want) { + t.Fatalf("local release entrypoint did not print %q: %s", want, output) } } From f60f078cba936140d48e16545096754344d052a8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Jul 2026 01:38:09 -0700 Subject: [PATCH 2/2] test: isolate SIGTERM watcher from credentials --- cmd/discrawl/main_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/discrawl/main_test.go b/cmd/discrawl/main_test.go index 6d3127dd..ccf86540 100644 --- a/cmd/discrawl/main_test.go +++ b/cmd/discrawl/main_test.go @@ -56,6 +56,8 @@ func TestMainCancelsWatchOnSIGTERM(t *testing.T) { cfg.CacheDir = filepath.Join(dir, "cache") cfg.LogDir = filepath.Join(dir, "logs") cfg.Desktop.Path = filepath.Join(dir, "discord") + cfg.Discord.TokenSource = "none" + cfg.Share.AutoUpdate = false requireNoError(t, os.MkdirAll(cfg.Desktop.Path, 0o755)) requireNoError(t, config.Write(cfgPath, cfg))