From 16ee4b0297d2f8a34c7794450dbdc55ff091cf0b Mon Sep 17 00:00:00 2001 From: Varun Sahni Date: Fri, 31 Jul 2026 11:39:57 +0530 Subject: [PATCH 1/3] fix: let mentionable relay agents through the managed-list gate Closes #2987 ## Problem External (non-desktop-managed) agents publishing kind:10100 profiles became un-mentionable because isAgentIdentityInManagedList dropped every non-managed agent before shouldHideAgentFromMentions could check invocability via respond_to allowlist. The same gate blocked adding such agents to channels from the MembersSidebar. ## Changes - useMentions.ts: let mentionableAgentPubkeys through the managed-list gate so shouldHideAgentFromMentions can evaluate invocability - MembersSidebar.tsx: compute mentionableAgentPubkeys from relay agents and shared channels, then let them through the add-member gate; add mentionableAgentPubkeys to addSearchResults deps (Biome useExhaustiveDependencies) ## Testing - biome check on changed files: clean - tsc --noEmit: clean - agentAutocompleteEligibility.test.mjs: 18/18 pass Signed-off-by: Varun Sahni --- .../features/channels/ui/MembersSidebar.tsx | 31 ++++++++++++++++++- .../src/features/messages/lib/useMentions.ts | 5 ++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/desktop/src/features/channels/ui/MembersSidebar.tsx b/desktop/src/features/channels/ui/MembersSidebar.tsx index c6349546a2..318e4d8b4b 100644 --- a/desktop/src/features/channels/ui/MembersSidebar.tsx +++ b/desktop/src/features/channels/ui/MembersSidebar.tsx @@ -5,10 +5,13 @@ import { invalidateChannelState, useAddChannelMembersMutation, useChannelMembersQuery, + useChannelsQuery, } from "@/features/channels/hooks"; import { attachManagedAgentToChannel } from "@/features/agents/channelAgents"; import { coalesceAgentAutocompleteCandidates, + getMentionableAgentPubkeys, + getSharedChannelIds, isAgentIdentityInManagedList, } from "@/features/agents/lib/agentAutocompleteEligibility"; import { useIsArchivedPredicate } from "@/features/identity-archive/hooks"; @@ -253,6 +256,30 @@ export function MembersSidebar({ }); const userSearchResults = useFlattenedUserSearchResults(userSearchQuery.data); const isArchivedDiscovery = useIsArchivedPredicate(); + const channelsQuery = useChannelsQuery(); + const sharedChannelIds = React.useMemo( + () => getSharedChannelIds(channelsQuery.data), + [channelsQuery.data], + ); + const mentionableAgentPubkeys = React.useMemo( + () => + getMentionableAgentPubkeys({ + currentPubkey, + managedAgentPubkeys: new Set( + (managedAgentsQuery.data ?? []).map((agent) => + normalizePubkey(agent.pubkey), + ), + ), + relayAgents: relayAgentsQuery.data, + sharedChannelIds, + }), + [ + currentPubkey, + managedAgentsQuery.data, + relayAgentsQuery.data, + sharedChannelIds, + ], + ); const addSearchResults = React.useMemo(() => { if (!canAddMembers || normalizedDeferredSearchQuery.length === 0) { return []; @@ -282,7 +309,8 @@ export function MembersSidebar({ )) || memberPubkeys.has(pubkey) || isArchivedDiscovery(pubkey) || - !isAgentIdentityInManagedList(candidate, managedAgentPubkeys) + (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys) && + !mentionableAgentPubkeys.has(pubkey)) ) { return; } @@ -369,6 +397,7 @@ export function MembersSidebar({ relayAgentsQuery.data, userSearchResults, rawMembers, + mentionableAgentPubkeys, ]); const isAddSearchLoading = userSearchQuery.isLoading || diff --git a/desktop/src/features/messages/lib/useMentions.ts b/desktop/src/features/messages/lib/useMentions.ts index 0c73b75339..07bab0385a 100644 --- a/desktop/src/features/messages/lib/useMentions.ts +++ b/desktop/src/features/messages/lib/useMentions.ts @@ -246,7 +246,10 @@ export function useMentions( if (isArchivedDiscovery(pubkey)) { return; } - if (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)) { + if ( + !isAgentIdentityInManagedList(candidate, managedAgentPubkeys) && + !mentionableAgentPubkeys.has(normalizePubkey(candidate.pubkey)) + ) { return; } if ( From fb65ec81fba85440a73732439ce36db20b72414f Mon Sep 17 00:00:00 2001 From: Varun Sahni Date: Fri, 31 Jul 2026 11:40:12 +0530 Subject: [PATCH 2/3] chore: keep workflow files at PR-base versions Signed-off-by: Varun Sahni --- .../auto-tag-on-release-pr-merge.yml | 43 +-- .github/workflows/ci.yml | 27 +- .github/workflows/docker.yml | 75 ++-- .github/workflows/linux-canary.yml | 2 +- .github/workflows/release.yml | 322 ++++++++++-------- 5 files changed, 206 insertions(+), 263 deletions(-) diff --git a/.github/workflows/auto-tag-on-release-pr-merge.yml b/.github/workflows/auto-tag-on-release-pr-merge.yml index a69eafb404..db34fddc2c 100644 --- a/.github/workflows/auto-tag-on-release-pr-merge.yml +++ b/.github/workflows/auto-tag-on-release-pr-merge.yml @@ -4,7 +4,7 @@ name: Auto-tag on Release PR Merge # prefix; the main chart lane also auto-detects a Chart.yaml version bump so # a chart feature PR can publish its own new version when merged: # -# version-bump/ → tag desktop-v → release.yml (desktop app) +# version-bump/ → tag v → release.yml (desktop app) # relay-release/ → tag relay-v → docker.yml (relay image) # chart-release/ → tag chart-v → helm-chart.yml (main helm chart) # push-chart-release/ → tag push-chart-v → push-gateway-helm-chart.yml @@ -35,11 +35,6 @@ permissions: jobs: auto-tag: - permissions: - contents: read - pull-requests: read - checks: read - statuses: read if: > github.event.pull_request.merged == true && github.event.pull_request.head.repo.full_name == github.repository @@ -62,7 +57,7 @@ jobs: case "$BRANCH" in version-bump/*) VERSION="${BRANCH#version-bump/}" - TAG_PREFIX="desktop-v" ;; + TAG_PREFIX="v" ;; relay-release/*) VERSION="${BRANCH#relay-release/}" TAG_PREFIX="relay-v" ;; @@ -90,34 +85,9 @@ jobs: { echo "enabled=true" echo "tag=${TAG_PREFIX}${VERSION}" - if [[ "$TAG_PREFIX" == desktop-v ]]; then - echo "target_sha=${{ github.event.pull_request.head.sha }}" - echo "desktop=true" - else - echo "target_sha=$GITHUB_SHA" - echo "desktop=false" - fi } >> "$GITHUB_OUTPUT" echo "Tagging ${TAG_PREFIX}${VERSION}" - - - name: Verify immutable reviewed desktop candidate - if: steps.release.outputs.desktop == 'true' - env: - GH_TOKEN: ${{ github.token }} - VERSION: ${{ steps.release.outputs.tag }} - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} - PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} - PR_BASE_REF: ${{ github.event.pull_request.base.ref }} - PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} - PR_PUSHER: ${{ github.event.pull_request.head.user.login }} - MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} - run: | - VERSION="${VERSION#desktop-v}" - export VERSION - scripts/verify-desktop-release-merge.sh - - name: Create release tagger token if: steps.release.outputs.enabled == 'true' id: release-tagger @@ -132,22 +102,21 @@ jobs: env: GH_TOKEN: ${{ steps.release-tagger.outputs.token }} TAG: ${{ steps.release.outputs.tag }} - TARGET_SHA: ${{ steps.release.outputs.target_sha }} run: | set -euo pipefail # Check gh's exit status, not its output. A missing ref returns a 404 # JSON body on stdout, which must not be mistaken for an existing tag. if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --silent 2>/dev/null; then EXISTING_SHA="$(gh api "repos/$GITHUB_REPOSITORY/commits/$TAG" --jq .sha)" - if [ "$EXISTING_SHA" = "$TARGET_SHA" ]; then - echo "Tag $TAG already exists at $TARGET_SHA — skipping tag creation" + if [ "$EXISTING_SHA" = "$GITHUB_SHA" ]; then + echo "Tag $TAG already exists at $GITHUB_SHA — skipping tag creation" exit 0 else - echo "::error::Tag $TAG already exists at $EXISTING_SHA (expected $TARGET_SHA)" + echo "::error::Tag $TAG already exists at $EXISTING_SHA (expected $GITHUB_SHA)" exit 1 fi fi gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \ -f ref="refs/tags/$TAG" \ - -f sha="$TARGET_SHA" \ + -f sha="$GITHUB_SHA" \ --silent diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc594e16ad..acdf5237bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,21 +48,15 @@ jobs: - 'scripts/run-tests.sh' - 'justfile' desktop: - - 'scripts/check-file-sizes-core.mjs' - - 'scripts/check-file-sizes-core.test.mjs' - 'desktop/**' - '!desktop/src-tauri/**' - 'pnpm-lock.yaml' desktop-rust: - 'desktop/src-tauri/**' web: - - 'scripts/check-file-sizes-core.mjs' - - 'scripts/check-file-sizes-core.test.mjs' - 'web/**' - 'pnpm-lock.yaml' mobile: - - 'scripts/check-file-sizes-core.mjs' - - 'scripts/check-file-sizes-core.test.mjs' - 'mobile/**' - 'scripts/mobile-release.sh' - 'scripts/mobile-worktree-overrides.sh' @@ -76,16 +70,12 @@ jobs: - '.github/workflows/ci.yml' - name: Release workflow source contract run: scripts/test-release-ref-contract.sh - - name: Desktop release candidate contract - run: scripts/test-desktop-release-candidate.sh - name: Mobile release contract run: | scripts/test-mobile-release-contract.sh scripts/test-mobile-release-candidate-publisher.sh - name: Mobile worktree identity contract run: scripts/test-mobile-worktree-overrides.sh - - name: File size ratchet unit tests - run: node --test scripts/check-file-sizes-core.test.mjs rust-lint: name: Rust Lint @@ -140,8 +130,6 @@ jobs: contents: read steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - with: - fetch-depth: 2 - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 @@ -225,7 +213,7 @@ jobs: desktop-smoke-e2e: name: Desktop Smoke E2E (${{ matrix.shard }}) runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 20 needs: [changes] if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true' strategy: @@ -352,7 +340,6 @@ jobs: cargo build --profile ci -p buzz-relay -p git-credential-nostr cargo nextest archive \ --cargo-profile ci \ - -p buzz-db \ -p buzz-relay \ -p buzz-test-client \ --lib \ @@ -684,11 +671,11 @@ jobs: done cat /tmp/buzz-relay.log exit 1 - - name: Invite security tests + - name: Invite claim security tests run: | cargo nextest run \ --archive-file target/ci/backend-integration-tests.tar.zst \ - -E '(package(buzz-db) and test(/relay_invite::tests/)) or (package(buzz-relay) and test(/api::invites::tests/))' \ + -E 'package(buzz-relay) and test(claim_)' \ --run-ignored ignored-only env: DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz @@ -739,7 +726,7 @@ jobs: ./scripts/start-relay-for-tests.sh --no-build - name: Relay E2E tests run: | - cargo test -p buzz-test-client --test e2e_persona --test e2e_team_catalog --test e2e_nostr_interop -- --ignored --nocapture + cargo test -p buzz-test-client --test e2e_persona --test e2e_nostr_interop -- --ignored --nocapture cargo test -p buzz-test-client --test e2e_relay invite -- --ignored --nocapture cargo test -p buzz-test-client --test e2e_relay nip43_membership_snapshots_are_rejected -- --ignored --nocapture env: @@ -763,8 +750,6 @@ jobs: contents: read steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - with: - fetch-depth: 2 - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 - name: Get pnpm store directory id: pnpm-cache @@ -798,8 +783,6 @@ jobs: contents: read steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - with: - fetch-depth: 2 - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 - name: Compute Hermit cache key id: hermit-bin-hash @@ -838,8 +821,6 @@ jobs: with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('mobile/pubspec.lock') }} - - name: File size ratchet - run: node mobile/scripts/check-file-sizes.mjs - name: Format check run: cd mobile && dart format --output=none --set-exit-if-changed . - name: Analyze diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 52f21b28bc..31080652ea 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,8 +1,6 @@ name: Docker image -# Builds and publishes the public Buzz relay images as ghcr.io/block/buzz. -# Normal tags contain stripped binaries; matching debug-* tags contain the same -# optimized binaries with line-table debug information for native profilers. +# Builds and publishes the public Buzz relay image as ghcr.io/block/buzz. # # Strategy: each architecture builds on its native runner (ubuntu-24.04 for # amd64, ubuntu-24.04-arm for arm64), pushes to GHCR by digest, then a final @@ -17,10 +15,8 @@ name: Docker image # # Triggers: # - push to main → :main + :sha-<7> -# + :debug-main + :debug-sha-<7> # - push tags relay-v*.*.* → :{version} + :{major}.{minor} + :{major} -# + matching :debug-* tags -# (+ :latest/:debug-latest for stable releases) +# (+ :latest for stable, NOT for prereleases) # - pull_request → build only (no push), cache stays warm # - workflow_dispatch → manual relay-tag rescue at the tag itself # @@ -99,6 +95,10 @@ jobs: runner: ubuntu-24.04-arm arch: arm64 + outputs: + # Used downstream by `merge` to stitch the manifest. + version: ${{ steps.meta.outputs.version }} + steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -163,13 +163,12 @@ jobs: org.opencontainers.image.description=WebSocket relay server for the Buzz communications platform org.opencontainers.image.licenses=Apache-2.0 - - name: Build and push release image by digest - id: build-release + - name: Build and push by digest + id: build uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . file: ./Dockerfile - target: runtime platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} # Push by digest, not by tag — the merge job assembles the tags @@ -181,49 +180,25 @@ jobs: cache-to: | ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref={0}-buildcache:{1},mode=max,compression=zstd', env.IMAGE_NAME, matrix.arch) || '' }} - - name: Build and push debug image by digest - id: build-debug - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 - with: - context: . - file: ./Dockerfile - target: runtime-debug - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} - cache-from: | - type=registry,ref=${{ env.IMAGE_NAME }}-buildcache:${{ matrix.arch }} - - - name: Export release and debug digests + - name: Export digest if: github.event_name != 'pull_request' env: - RELEASE_DIGEST: ${{ steps.build-release.outputs.digest }} - DEBUG_DIGEST: ${{ steps.build-debug.outputs.digest }} + DIGEST: ${{ steps.build.outputs.digest }} run: | - mkdir -p /tmp/digests-release /tmp/digests-debug - touch "/tmp/digests-release/${RELEASE_DIGEST#sha256:}" - touch "/tmp/digests-debug/${DEBUG_DIGEST#sha256:}" - - - name: Upload release digest - if: github.event_name != 'pull_request' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: digests-release-${{ matrix.arch }} - path: /tmp/digests-release/* - if-no-files-found: error - retention-days: 1 + mkdir -p /tmp/digests + touch "/tmp/digests/${DIGEST#sha256:}" - - name: Upload debug digest + - name: Upload digest if: github.event_name != 'pull_request' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: digests-debug-${{ matrix.arch }} - path: /tmp/digests-debug/* + name: digests-${{ matrix.arch }} + path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge: - name: Merge ${{ matrix.variant }} multi-arch manifest + name: Merge multi-arch manifest if: github.event_name != 'pull_request' runs-on: ubuntu-24.04 needs: build @@ -233,21 +208,13 @@ jobs: packages: write # push the merged manifest id-token: write # OIDC for provenance attestation on the manifest attestations: write - strategy: - fail-fast: false - matrix: - include: - - variant: release - tag_prefix: "" - - variant: debug - tag_prefix: debug- steps: - name: Download all per-arch digests uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: /tmp/digests - pattern: digests-${{ matrix.variant }}-* + pattern: digests-* merge-multiple: true - name: Set up Docker Buildx @@ -270,12 +237,9 @@ jobs: # the build job's `meta` step for why match=^relay-v(.*)$, why # value=${{ inputs.version }} carries the rescue-dispatch version, # and why :latest is left to flavor.latest=auto. - flavor: | - latest=auto - prefix=${{ matrix.tag_prefix }},onlatest=true tags: | type=ref,event=branch,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }} - type=sha,prefix=${{ matrix.tag_prefix }}sha-,format=short,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }} + type=sha,prefix=sha-,format=short,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }} type=semver,pattern={{version}},match=^relay-v(.*)$,value=${{ inputs.version }} type=semver,pattern={{major}}.{{minor}},match=^relay-v(.*)$,value=${{ inputs.version }} type=semver,pattern={{major}},match=^relay-v(.*)$,value=${{ inputs.version }} @@ -320,12 +284,11 @@ jobs: - name: Summary env: IMAGE_NAME: ${{ env.IMAGE_NAME }} - VARIANT: ${{ matrix.variant }} MERGED_DIGEST: ${{ steps.manifest.outputs.digest }} META_TAGS: ${{ steps.meta.outputs.tags }} run: | { - echo "### Published \`${IMAGE_NAME}\` (${VARIANT})" + echo "### Published \`${IMAGE_NAME}\`" echo echo "**Digest:** \`${MERGED_DIGEST}\`" echo diff --git a/.github/workflows/linux-canary.yml b/.github/workflows/linux-canary.yml index 9806443378..18d476e400 100644 --- a/.github/workflows/linux-canary.yml +++ b/.github/workflows/linux-canary.yml @@ -21,7 +21,7 @@ jobs: name: Build Linux canary if: github.repository == 'block/buzz' runs-on: ubuntu-latest - container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90 + container: ubuntu:22.04@sha256:0e0a0fc6d18feda9db1590da249ac93e8d5abfea8f4c3c0c849ce512b5ef8982 timeout-minutes: 60 permissions: contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07951ef81d..c613924e57 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,14 @@ name: Release -concurrency: - group: desktop-release-${{ github.ref }} - cancel-in-progress: false - on: push: tags: - - 'desktop-v[0-9]*' + - 'v[0-9]*' + workflow_dispatch: + inputs: + version: + description: "Semver version matching the v-prefixed dispatch tag" + required: true jobs: # Shared setup: verify the immutable release tag, determine the version, and @@ -18,14 +19,23 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 permissions: - contents: read + contents: write outputs: version: ${{ steps.version.outputs.version }} source_sha: ${{ steps.source.outputs.source_sha }} steps: - name: Determine version id: version - run: echo "version=${GITHUB_REF_NAME#desktop-v}" >> "$GITHUB_OUTPUT" + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_VERSION: ${{ inputs.version }} + run: | + if [[ "$EVENT_NAME" == "push" ]]; then + VERSION="${GITHUB_REF_NAME#v}" + else + VERSION="$INPUT_VERSION" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Validate version env: @@ -46,9 +56,42 @@ jobs: env: VERSION: ${{ steps.version.outputs.version }} run: | - scripts/verify-release-ref.sh desktop-v "$VERSION" + scripts/verify-release-ref.sh v "$VERSION" echo "source_sha=$(git rev-parse 'HEAD^{commit}')" >> "$GITHUB_OUTPUT" + - name: Create versioned GitHub release + env: + VERSION: ${{ steps.version.outputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + RELEASE_SHA=$(git rev-parse HEAD) + NOTES="" + if [[ -f CHANGELOG.md ]]; then + NOTES=$(awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found && !/^\$/" CHANGELOG.md) + fi + if [[ -z "$NOTES" ]]; then + NOTES="Buzz Desktop v${VERSION}" + fi + PRERELEASE_FLAGS=() + if [[ "$VERSION" =~ -(test|alpha|beta|rc)([.-]|$) ]]; then + PRERELEASE_FLAGS=(--prerelease --latest=false) + fi + gh release create "v${VERSION}" \ + --target "$RELEASE_SHA" \ + --title "Buzz Desktop v${VERSION}" \ + --notes "$NOTES" \ + "${PRERELEASE_FLAGS[@]}" + + - name: Create rolling auto-update release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create buzz-desktop-latest \ + --prerelease \ + --title "Buzz Desktop Auto-Update" \ + --notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \ + 2>/dev/null || true + release: name: Release if: github.repository == 'block/buzz' @@ -56,7 +99,7 @@ jobs: needs: setup timeout-minutes: 60 permissions: - contents: read + contents: write id-token: write # required by block/apple-codesign-action for OIDC outputs: archive_name: ${{ steps.artifacts.outputs.archive_name }} @@ -71,7 +114,7 @@ jobs: persist-credentials: false - name: Verify tag-bound release source - run: scripts/verify-release-ref.sh desktop-v "$VERSION" + run: scripts/verify-release-ref.sh v "$VERSION" - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 @@ -229,19 +272,13 @@ jobs: fi echo "dmg=$DMG" >> "$GITHUB_OUTPUT" - # Find the updater .tar.gz and .sig. Give each architecture a unique - # release basename before artifacts are merged by the final writer. + # Find the updater .tar.gz and .sig ARCHIVE=$(find "$BUNDLE_DIR/macos" -name '*.tar.gz' ! -name '*.sig' -type f | head -1) SIG="${ARCHIVE}.sig" if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos" exit 1 fi - RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_aarch64.app.tar.gz" - mv "$ARCHIVE" "$RENAMED" - mv "$SIG" "${RENAMED}.sig" - ARCHIVE="$RENAMED" - SIG="${RENAMED}.sig" echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT" echo "sig=$SIG" >> "$GITHUB_OUTPUT" @@ -252,15 +289,23 @@ jobs: env: SIG_PATH: ${{ steps.artifacts.outputs.sig }} - - name: Stage Apple Silicon release artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: desktop-release-macos-arm64 - if-no-files-found: error - path: | - ${{ steps.artifacts.outputs.dmg }} - ${{ steps.artifacts.outputs.archive }} - ${{ steps.artifacts.outputs.sig }} + - name: Upload arm64 DMG to versioned GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DMG_PATH: ${{ steps.artifacts.outputs.dmg }} + run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber + + - name: Upload updater archive to rolling release + if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version) + run: | + gh release upload buzz-desktop-latest \ + "$ARCHIVE_PATH" \ + "$SIG_PATH" \ + --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }} + SIG_PATH: ${{ steps.artifacts.outputs.sig }} release-macos-x64: name: Release macOS (Intel) @@ -269,7 +314,7 @@ jobs: needs: setup timeout-minutes: 60 permissions: - contents: read + contents: write id-token: write # required by block/apple-codesign-action for OIDC outputs: archive_name: ${{ steps.artifacts.outputs.archive_name }} @@ -285,7 +330,7 @@ jobs: persist-credentials: false - name: Verify tag-bound release source - run: scripts/verify-release-ref.sh desktop-v "$VERSION" + run: scripts/verify-release-ref.sh v "$VERSION" - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 @@ -398,11 +443,6 @@ jobs: echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos" exit 1 fi - RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_x64.app.tar.gz" - mv "$ARCHIVE" "$RENAMED" - mv "$SIG" "${RENAMED}.sig" - ARCHIVE="$RENAMED" - SIG="${RENAMED}.sig" echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT" echo "sig=$SIG" >> "$GITHUB_OUTPUT" @@ -413,26 +453,34 @@ jobs: env: SIG_PATH: ${{ steps.artifacts.outputs.sig }} - - name: Stage Intel macOS release artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: desktop-release-macos-x64 - if-no-files-found: error - path: | - ${{ steps.unsigned.outputs.dmg }} - ${{ steps.artifacts.outputs.archive }} - ${{ steps.artifacts.outputs.sig }} + - name: Upload Intel DMG to versioned GitHub release + run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DMG_PATH: ${{ steps.unsigned.outputs.dmg }} + + - name: Upload updater archive to rolling release + if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version) + run: | + gh release upload buzz-desktop-latest \ + "$ARCHIVE_PATH" \ + "$SIG_PATH" \ + --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }} + SIG_PATH: ${{ steps.artifacts.outputs.sig }} release-linux: name: Release Linux if: github.repository == 'block/buzz' runs-on: ubuntu-latest # Digest-pinned like the SHA-pinned actions below; Renovate keeps it fresh. - container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90 + container: ubuntu:22.04@sha256:0e0a0fc6d18feda9db1590da249ac93e8d5abfea8f4c3c0c849ce512b5ef8982 needs: setup timeout-minutes: 60 permissions: - contents: read + contents: write env: # AppImage tools (linuxdeploy, appimagetool) are themselves AppImages. # Containers lack FUSE, so we must use the extract-and-run fallback. @@ -450,7 +498,7 @@ jobs: env: DEBIAN_FRONTEND: noninteractive run: | - # Must run first: bare ubuntu:24.04 ships without curl, wget, git, or + # Must run first: bare ubuntu:22.04 ships without curl, wget, git, or # ca-certificates. activate-hermit bootstraps via curl+HTTPS (needs # both), and actions/checkout falls back to a REST tarball without git. # Running as root — no sudo needed. @@ -507,7 +555,7 @@ jobs: - name: Verify tag-bound release source env: VERSION: ${{ needs.setup.outputs.version }} - run: scripts/verify-release-ref.sh desktop-v "$VERSION" + run: scripts/verify-release-ref.sh v "$VERSION" - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 @@ -641,16 +689,29 @@ jobs: SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }} # NOTE: .deb is NOT auto-updatable (Tauri updater constraint — only AppImage supports it on Linux) - - name: Stage Linux release artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: desktop-release-linux-x64 - if-no-files-found: error - path: | - ${{ steps.linux-artifacts.outputs.deb }} - ${{ steps.linux-artifacts.outputs.appimage }} - ${{ steps.linux-artifacts.outputs.archive }} - ${{ steps.linux-artifacts.outputs.sig }} + - name: Upload Linux artifacts to versioned GitHub release + env: + VERSION: ${{ needs.setup.outputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEB_PATH: ${{ steps.linux-artifacts.outputs.deb }} + APPIMAGE_PATH: ${{ steps.linux-artifacts.outputs.appimage }} + run: | + gh release upload "v$VERSION" \ + "$DEB_PATH" \ + "$APPIMAGE_PATH" \ + --clobber + + - name: Upload updater archive to rolling release + if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version) + run: | + gh release upload buzz-desktop-latest \ + "$ARCHIVE_PATH" \ + "$SIG_PATH" \ + --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ARCHIVE_PATH: ${{ steps.linux-artifacts.outputs.archive }} + SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }} release-windows: name: Release Windows @@ -658,7 +719,7 @@ jobs: needs: setup timeout-minutes: 60 permissions: - contents: read + contents: write outputs: archive_name: ${{ steps.artifacts.outputs.archive_name }} sig: ${{ steps.read-sig.outputs.sig }} @@ -674,7 +735,7 @@ jobs: - name: Verify tag-bound release source shell: bash - run: scripts/verify-release-ref.sh desktop-v "$VERSION" + run: scripts/verify-release-ref.sh v "$VERSION" - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 with: @@ -684,7 +745,7 @@ jobs: with: node-version: 24.14.1 # Disable dependency caching: a writable cache in this release workflow - # (contents: read, feeds a signed installer) is a poisoning vector. pnpm + # (contents: write, feeds a signed installer) is a poisoning vector. pnpm # install runs uncached below. package-manager-cache: false @@ -766,14 +827,25 @@ jobs: env: SIG_PATH: ${{ steps.artifacts.outputs.sig }} - - name: Stage Windows release artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: desktop-release-windows-x64 - if-no-files-found: error - path: | - ${{ steps.artifacts.outputs.exe }} - ${{ steps.artifacts.outputs.sig }} + - name: Upload Windows installer to versioned GitHub release + shell: bash + run: gh release upload "v${VERSION}" "$EXE_PATH" --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + EXE_PATH: ${{ steps.artifacts.outputs.exe }} + + - name: Upload updater archive to rolling release + if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version) + shell: bash + run: | + gh release upload buzz-desktop-latest \ + "$ARCHIVE_PATH" \ + "$SIG_PATH" \ + --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }} + SIG_PATH: ${{ steps.artifacts.outputs.sig }} assemble-manifest: name: Assemble multi-platform latest.json @@ -781,11 +853,7 @@ jobs: if: | always() && needs.setup.result == 'success' && - needs.release.result == 'success' && - needs.release-macos-x64.result == 'success' && - needs.release-linux.result == 'success' && - needs.release-windows.result == 'success' && - github.ref == format('refs/tags/desktop-v{0}', needs.setup.outputs.version) + github.ref == format('refs/tags/v{0}', needs.setup.outputs.version) runs-on: ubuntu-latest needs: [setup, release, release-macos-x64, release-linux, release-windows] timeout-minutes: 10 @@ -802,26 +870,7 @@ jobs: persist-credentials: false - name: Verify tag-bound release source - run: scripts/verify-release-ref.sh desktop-v "$VERSION" - - - name: Download staged release artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: desktop-release-* - path: staged-by-platform - - - name: Flatten staged artifacts without basename collisions - run: | - set -euo pipefail - mkdir staged - while IFS= read -r -d '' file; do - name="$(basename "$file")" - [[ ! -e "staged/$name" ]] || { - echo "::error::release artifact basename collision: $name" - exit 1 - } - cp "$file" "staged/$name" - done < <(find staged-by-platform -type f -print0) + run: scripts/verify-release-ref.sh v "$VERSION" - name: Write signature files env: @@ -850,7 +899,7 @@ jobs: write_sig "$RESULT_LINUX" linux-x86_64 "$SIG_LINUX" write_sig "$RESULT_WIN" windows-x86_64 "$SIG_WIN" - - name: Verify draft release has every updater archive + - name: Verify archive URLs are accessible env: RESULT_ARM64: ${{ needs.release.result }} RESULT_X64: ${{ needs.release-macos-x64.result }} @@ -862,19 +911,39 @@ jobs: ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }} run: | set -euo pipefail - assets=$(find staged -type f -exec basename {} \;) - for spec in \ - "$RESULT_ARM64:$ARCHIVE_ARM64" \ - "$RESULT_X64:$ARCHIVE_X64" \ - "$RESULT_LINUX:$ARCHIVE_LINUX" \ - "$RESULT_WIN:$ARCHIVE_WIN"; do - result="${spec%%:*}" - archive="${spec#*:}" - if [[ "$result" == success ]]; then - [[ -n "$archive" ]] || { echo "::error::successful platform has no archive"; exit 1; } - grep -Fxq "$archive" <<<"$assets" || { echo "::error::draft release missing $archive"; exit 1; } + BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest" + ARCHIVES=() + + add_archive() { + local result="$1" platform="$2" archive="$3" + if [[ "$result" == "success" ]]; then + [[ -n "$archive" ]] || { echo "::error::Missing archive name for successful platform: $platform"; exit 1; } + ARCHIVES+=("$archive") + fi + } + + add_archive "$RESULT_ARM64" darwin-aarch64 "$ARCHIVE_ARM64" + add_archive "$RESULT_X64" darwin-x86_64 "$ARCHIVE_X64" + add_archive "$RESULT_LINUX" linux-x86_64 "$ARCHIVE_LINUX" + add_archive "$RESULT_WIN" windows-x86_64 "$ARCHIVE_WIN" + + for name in "${ARCHIVES[@]}"; do + echo "Checking $BASE/$name ..." + success=false + for attempt in 1 2 3; do + if curl -fsI "$BASE/$name" > /dev/null 2>&1; then + success=true + break + fi + echo "Attempt $attempt failed for $name, retrying in 10s..." + sleep 10 + done + if [ "$success" != "true" ]; then + echo "::error::Archive not accessible after 3 attempts: $BASE/$name" + exit 1 fi done + echo "All archive URLs verified." - name: Generate unified latest.json env: @@ -888,7 +957,7 @@ jobs: ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }} run: | set -euo pipefail - BASE="https://github.com/block/buzz/releases/download/desktop-v${VERSION}" + BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest" TRIPLES=() add_triple() { @@ -908,45 +977,6 @@ jobs: bash desktop/scripts/generate-oss-latest-json.sh "$VERSION" "${TRIPLES[@]}" > latest.json cat latest.json - - name: Create or verify versioned draft + - name: Upload latest.json to rolling release run: | - set -euo pipefail - NOTES_FILE="${RUNNER_TEMP}/release-notes.md" - awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found" CHANGELOG.md > "$NOTES_FILE" - [[ -s "$NOTES_FILE" ]] || { echo "::error::missing non-empty changelog block for v${VERSION}"; exit 1; } - PRERELEASE_FLAGS=() - if [[ "$VERSION" == *-* ]]; then - PRERELEASE_FLAGS=(--prerelease --latest=false) - fi - if gh release view "desktop-v${VERSION}" >/dev/null 2>&1; then - EXISTING_SHA=$(gh release view "desktop-v${VERSION}" --json targetCommitish --jq .targetCommitish) - IS_DRAFT=$(gh release view "desktop-v${VERSION}" --json isDraft --jq .isDraft) - [[ "$EXISTING_SHA" == "${{ needs.setup.outputs.source_sha }}" ]] || { - echo "::error::existing release targets $EXISTING_SHA, not the immutable source"; exit 1; - } - if [[ "$IS_DRAFT" != true ]]; then - echo "already_published=true" >> "$GITHUB_ENV" - fi - else - gh release create "desktop-v${VERSION}" \ - --draft \ - --target "${{ needs.setup.outputs.source_sha }}" \ - --title "Buzz Desktop v${VERSION}" \ - --notes-file "$NOTES_FILE" \ - "${PRERELEASE_FLAGS[@]}" - fi - - - name: Upload complete artifact set to versioned draft - if: env.already_published != 'true' - run: | - mapfile -t files < <(find staged -type f -print) - [[ "${#files[@]}" -gt 0 ]] || { echo "::error::no staged release artifacts"; exit 1; } - gh release upload "desktop-v${VERSION}" "${files[@]}" --clobber - - - name: Publish complete versioned release - if: env.already_published != 'true' - run: gh release edit "desktop-v${VERSION}" --draft=false - - - name: Upload latest.json to rolling release last - if: ${{ env.already_published != 'true' && !contains(needs.setup.outputs.version, '-') }} - run: gh release upload buzz-desktop-latest latest.json --clobber + gh release upload buzz-desktop-latest latest.json --clobber From 3fca3d7b3a2af9ececd73fd68c4c371bca00b133 Mon Sep 17 00:00:00 2001 From: Varun Sahni Date: Fri, 31 Jul 2026 11:40:19 +0530 Subject: [PATCH 3/3] chore: drop workflow file absent from PR base Signed-off-by: Varun Sahni --- .github/workflows/prepare-desktop-release.yml | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 .github/workflows/prepare-desktop-release.yml diff --git a/.github/workflows/prepare-desktop-release.yml b/.github/workflows/prepare-desktop-release.yml deleted file mode 100644 index 7cc480b93b..0000000000 --- a/.github/workflows/prepare-desktop-release.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Prepare Desktop Release - -on: - workflow_dispatch: - inputs: - version: - description: Semver to prepare (for example 0.5.1) - required: true - -env: - RELEASE_AUTOMATION_NAME: Carl - RELEASE_AUTOMATION_EMAIL: c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz - -jobs: - prepare: - if: github.repository == 'block/buzz' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Create short-lived release preparer token - id: preparer - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - client-id: ${{ vars.BUZZ_RELEASE_TAGGER_CLIENT_ID }} - private-key: ${{ secrets.BUZZ_RELEASE_TAGGER_PRIVATE_KEY }} - permission-contents: write - permission-pull-requests: write - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: 0 - token: ${{ steps.preparer.outputs.token }} - - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 - - name: Prepare immutable candidate and open or update PR - env: - GH_TOKEN: ${{ steps.preparer.outputs.token }} - VERSION: ${{ inputs.version }} - run: scripts/prepare-desktop-release.sh "$VERSION"