diff --git a/.env.example b/.env.example
index b9bfcada0e..0f7bbba6f1 100644
--- a/.env.example
+++ b/.env.example
@@ -102,11 +102,10 @@ BUZZ_S3_ADDRESSING_STYLE=path
# BUZZ_MEDIA_MAX_CONCURRENT_UPLOADS=8
# BUZZ_MEDIA_MAX_CONCURRENT_UPLOADS_PER_PUBKEY=2
# BUZZ_MEDIA_UPLOADS_PER_MINUTE=30
-# Require Blossom t=get auth and relay membership for GET/HEAD /media/*.
-# Keep off until desktop/mobile/CLI clients that attach media read auth are deployed.
-# BUZZ_REQUIRE_MEDIA_GET_AUTH=false
-# Legacy alias accepted by the relay while rollout docs catch up:
-# BUZZ_REQUIRE_MEDIA_READ_AUTH=false
+# GET/HEAD /media/* always require Blossom t=get auth and relay membership.
+# BUZZ_REQUIRE_MEDIA_GET_AUTH and BUZZ_REQUIRE_MEDIA_READ_AUTH are no longer
+# read; setting either (including to false) changes nothing and the relay warns
+# about it at startup.
# -----------------------------------------------------------------------------
# Ephemeral Channels (TTL testing)
diff --git a/.github/workflows/auto-tag-on-release-pr-merge.yml b/.github/workflows/auto-tag-on-release-pr-merge.yml
index 7cf2199794..9d4f39f801 100644
--- a/.github/workflows/auto-tag-on-release-pr-merge.yml
+++ b/.github/workflows/auto-tag-on-release-pr-merge.yml
@@ -92,7 +92,7 @@ jobs:
echo "enabled=true"
echo "tag=${TAG_PREFIX}${VERSION}"
if [[ "$TAG_PREFIX" == desktop-v ]]; then
- echo "target_sha=${{ github.event.pull_request.merge_commit_sha }}"
+ echo "target_sha=${{ github.event.pull_request.head.sha }}"
echo "desktop=true"
else
echo "target_sha=$GITHUB_SHA"
@@ -113,6 +113,7 @@ jobs:
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
+ MERGED_AT: ${{ github.event.pull_request.merged_at }}
run: |
VERSION="${VERSION#desktop-v}"
export VERSION
@@ -147,7 +148,17 @@ jobs:
exit 1
fi
fi
- gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
+ if ! gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
-f ref="refs/tags/$TAG" \
-f sha="$TARGET_SHA" \
- --silent
+ --silent; then
+ # Ref creation is atomic. A concurrent retry may have won the race;
+ # accept that only when it created the exact immutable ref.
+ EXISTING_SHA="$(gh api "repos/$GITHUB_REPOSITORY/commits/$TAG" --jq .sha)"
+ if [ "$EXISTING_SHA" = "$TARGET_SHA" ]; then
+ echo "Tag $TAG was concurrently created at $TARGET_SHA"
+ exit 0
+ fi
+ echo "::error::Tag creation failed and $TAG resolves to $EXISTING_SHA (expected $TARGET_SHA)"
+ exit 1
+ fi
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a031e0a04d..bc7415a107 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -768,6 +768,19 @@ jobs:
env:
RELAY_URL: ws://localhost:3000
GIT_CREDENTIAL_NOSTR_BIN: ${{ github.workspace }}/target/ci/git-credential-nostr
+ - name: Media read-auth e2e
+ # Reads require kind:24242 `t=get` auth, so these binaries are the only
+ # coverage that a real relay rejects bare reads and honours host- and
+ # hash-scoped tokens. They were #[ignore]d and selected by no CI job, so
+ # the lane never ran; select it here, where MinIO and the seeded
+ # 'localhost:3000' community already exist.
+ # --no-fail-fast: without it cargo stops after the first failing binary,
+ # so one broken case hides every later binary's result.
+ run: |
+ cargo test -p buzz-test-client --no-fail-fast --test e2e_media --test e2e_media_extended --test e2e_media_video -- --ignored --nocapture
+ env:
+ RELAY_URL: ws://localhost:3000
+ RELAY_HTTP_URL: http://localhost:3000
- name: Upload relay logs
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
@@ -1023,7 +1036,7 @@ jobs:
git log -1 --format=%s | grep -qx smoke
echo "Host bash resolved and functional; git commit round-trip passed"
- name: Check (Tauri crate)
- run: cargo check --manifest-path desktop/src-tauri/Cargo.toml --target $env:TARGET
+ run: cargo check --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets --target $env:TARGET
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
- name: Test (Tauri crate)
diff --git a/.github/workflows/desktop-release-cache-proof.yml b/.github/workflows/desktop-release-cache-proof.yml
new file mode 100644
index 0000000000..cf9c8e7827
--- /dev/null
+++ b/.github/workflows/desktop-release-cache-proof.yml
@@ -0,0 +1,164 @@
+name: Desktop release cache tag-scope proof
+
+# Dispatch from a cache-proof-* tag at the same trusted-main SHA warmed by all
+# four canaries. Every job restores only and requires an exact cache hit.
+on:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ macos:
+ name: Prove macOS ${{ matrix.target }} cache visibility
+ if: github.repository == 'block/buzz'
+ runs-on: macos-latest
+ timeout-minutes: 15
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: aarch64-apple-darwin
+ features: mesh-llm
+ - target: x86_64-apple-darwin
+ features: default
+ steps:
+ - name: Require cache proof tag
+ run: '[[ "$GITHUB_REF" == refs/tags/cache-proof-* ]] || { echo "::error::Expected cache-proof-* tag; got $GITHUB_REF"; exit 1; }'
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+ - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
+ - name: Patch proof dependency graph
+ run: |
+ cd desktop && node scripts/set-version-from-tag.mjs "0.0.0-cache-proof"
+ cd src-tauri && cargo update --workspace
+ - name: Resolve native toolchain identity
+ id: native_toolchain
+ run: echo "id=$(scripts/desktop-native-toolchain-id.sh macos)" >> "$GITHUB_OUTPUT"
+ - name: Compute exact release cache key
+ id: rust_cache_key
+ env:
+ CACHE_TARGET: ${{ matrix.target }}
+ CACHE_FEATURES: ${{ matrix.features }}
+ NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
+ run: |
+ KEY=$(scripts/desktop-release-cache-key.py --platform "$RUNNER_OS" --target "$CACHE_TARGET" --features "$CACHE_FEATURES" --native-inputs "$NATIVE_TOOLCHAIN_ID")
+ echo "key=$KEY" >> "$GITHUB_OUTPUT"
+ - name: Restore exact default-branch cache from tag
+ id: rust_cache
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+ - name: Require exact cache hit
+ env:
+ CACHE_HIT: ${{ steps.rust_cache.outputs.cache-hit }}
+ CACHE_KEY: ${{ steps.rust_cache.outputs.cache-primary-key }}
+ EXPECTED_KEY: ${{ steps.rust_cache_key.outputs.key }}
+ run: '[[ "$CACHE_HIT" == true && "$CACHE_KEY" == "$EXPECTED_KEY" ]] || { echo "::error::Exact tag cache miss (hit=$CACHE_HIT restored=$CACHE_KEY expected=$EXPECTED_KEY)"; exit 1; }'
+
+ linux:
+ name: Prove Linux cache visibility
+ if: github.repository == 'block/buzz'
+ runs-on: ubuntu-latest
+ container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
+ timeout-minutes: 15
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Require cache proof tag and install release native tools
+ run: |
+ [[ "$GITHUB_REF" == refs/tags/cache-proof-* ]] || { echo "::error::Expected cache-proof-* tag; got $GITHUB_REF"; exit 1; }
+ apt-get update
+ apt-get install -y --no-install-recommends build-essential ca-certificates curl git libasound2-dev libayatana-appindicator3-dev libgtk-3-dev librsvg2-dev libssl-dev libwebkit2gtk-4.1-dev libxdo-dev patchelf pkg-config
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+ - run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
+ - name: Patch proof dependency graph
+ run: |
+ cd desktop && node scripts/set-version-from-tag.mjs "0.0.0-cache-proof"
+ cd src-tauri && cargo update --workspace
+ - name: Resolve native toolchain identity
+ id: native_toolchain
+ run: echo "id=$(scripts/desktop-native-toolchain-id.sh linux)" >> "$GITHUB_OUTPUT"
+ - name: Compute exact release cache key
+ id: rust_cache_key
+ env:
+ NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
+ run: |
+ KEY=$(scripts/desktop-release-cache-key.py --platform "$RUNNER_OS" --target x86_64-unknown-linux-gnu --features mesh-llm --native-inputs "$NATIVE_TOOLCHAIN_ID")
+ echo "key=$KEY" >> "$GITHUB_OUTPUT"
+ - name: Restore exact default-branch cache from tag
+ id: rust_cache
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+ - name: Require exact cache hit
+ env:
+ CACHE_HIT: ${{ steps.rust_cache.outputs.cache-hit }}
+ CACHE_KEY: ${{ steps.rust_cache.outputs.cache-primary-key }}
+ EXPECTED_KEY: ${{ steps.rust_cache_key.outputs.key }}
+ run: '[[ "$CACHE_HIT" == true && "$CACHE_KEY" == "$EXPECTED_KEY" ]] || { echo "::error::Exact tag cache miss (hit=$CACHE_HIT restored=$CACHE_KEY expected=$EXPECTED_KEY)"; exit 1; }'
+
+ windows:
+ name: Prove Windows cache visibility
+ if: github.repository == 'block/buzz'
+ runs-on: windows-latest
+ timeout-minutes: 15
+ steps:
+ - name: Require cache proof tag
+ shell: bash
+ run: '[[ "$GITHUB_REF" == refs/tags/cache-proof-* ]] || { echo "::error::Expected cache-proof-* tag; got $GITHUB_REF"; exit 1; }'
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+ - name: Patch proof dependency graph
+ shell: bash
+ run: |
+ cd desktop && node scripts/set-version-from-tag.mjs "0.0.0-cache-proof"
+ cd src-tauri && cargo update --workspace
+ - name: Resolve native toolchain identity
+ id: native_toolchain
+ shell: bash
+ run: echo "id=$(scripts/desktop-native-toolchain-id.sh windows)" >> "$GITHUB_OUTPUT"
+ - name: Compute exact release cache key
+ id: rust_cache_key
+ shell: bash
+ env:
+ NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
+ run: |
+ KEY=$(scripts/desktop-release-cache-key.py --platform "$RUNNER_OS" --target x86_64-pc-windows-msvc --features default --native-inputs "$NATIVE_TOOLCHAIN_ID")
+ echo "key=$KEY" >> "$GITHUB_OUTPUT"
+ - name: Restore exact default-branch cache from tag
+ id: rust_cache
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+ - name: Require exact cache hit
+ shell: bash
+ env:
+ CACHE_HIT: ${{ steps.rust_cache.outputs.cache-hit }}
+ CACHE_KEY: ${{ steps.rust_cache.outputs.cache-primary-key }}
+ EXPECTED_KEY: ${{ steps.rust_cache_key.outputs.key }}
+ run: '[[ "$CACHE_HIT" == true && "$CACHE_KEY" == "$EXPECTED_KEY" ]] || { echo "::error::Exact tag cache miss (hit=$CACHE_HIT restored=$CACHE_KEY expected=$EXPECTED_KEY)"; exit 1; }'
diff --git a/.github/workflows/desktop-release-candidate.yml b/.github/workflows/desktop-release-candidate.yml
index eddebea685..61ccc800af 100644
--- a/.github/workflows/desktop-release-candidate.yml
+++ b/.github/workflows/desktop-release-candidate.yml
@@ -6,6 +6,7 @@ on:
permissions:
contents: read
+ pull-requests: read
jobs:
validate:
@@ -20,6 +21,7 @@ jobs:
- name: Validate immutable desktop candidate
if: startsWith(github.event.pull_request.head.ref, 'version-bump/')
env:
+ GH_TOKEN: ${{ github.token }}
VERSION: ${{ github.event.pull_request.head.ref }}
run: |
VERSION="${VERSION#version-bump/}"
diff --git a/.github/workflows/linux-canary.yml b/.github/workflows/linux-canary.yml
index e1625f4ec8..d8b10032b2 100644
--- a/.github/workflows/linux-canary.yml
+++ b/.github/workflows/linux-canary.yml
@@ -7,8 +7,8 @@ name: Linux Canary
# Design notes vs. signed-macos-canary.yml:
# - fix-appimage.sh is run without signing env vars; the script detects
# their absence and skips re-signing, repacking only (documented inline).
-# - mold linker added (rui314/setup-mold) to reduce link time, matching
-# the Linux Rust CI jobs in ci.yml.
+# - Build tools match release.yml; cache keys derive the concrete linker and
+# native library identity rather than assuming the moving runner image.
# - pnpm store restore/save pattern mirrors ci.yml:149-196.
on:
workflow_dispatch:
@@ -83,18 +83,6 @@ jobs:
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
-
- # Rust cache covering both the workspace sidecar build and the Tauri
- # crate build. shared-key scoped to linux-canary-release so canary runs
- # warm each other without colliding with CI's debug-profile keys.
- - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- with:
- workspaces: |
- .
- desktop/src-tauri
- shared-key: linux-canary-release
-
- name: Install appimagetool
run: |
case "$(uname -m)" in
@@ -154,6 +142,38 @@ jobs:
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
cd src-tauri && cargo update --workspace
+ - name: Resolve native toolchain identity
+ id: native_toolchain
+ run: echo "id=$(scripts/desktop-native-toolchain-id.sh linux)" >> "$GITHUB_OUTPUT"
+
+ # Compute this after cargo update so the key describes the graph that is
+ # actually compiled. The helper normalizes only Buzz Desktop's release
+ # version, allowing a canary to warm an otherwise identical tag build.
+ - name: Compute exact release cache key
+ id: rust_cache_key
+ env:
+ NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
+ run: |
+ KEY=$(scripts/desktop-release-cache-key.py \
+ --platform "$RUNNER_OS" \
+ --target x86_64-unknown-linux-gnu \
+ --features mesh-llm \
+ --native-inputs "$NATIVE_TOOLCHAIN_ID")
+ echo "key=$KEY" >> "$GITHUB_OUTPUT"
+ echo "Release cache key: $KEY"
+
+ - name: Restore exact release Cargo cache
+ id: rust_cache
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+
- name: Generate non-updating bundle config
run: |
cat > desktop/src-tauri/tauri.canary.conf.json <<'JSON'
@@ -190,6 +210,24 @@ jobs:
fi
bash desktop/scripts/fix-appimage.sh "${APPIMAGES[0]}"
+ - name: Measure release Cargo cache inputs
+ if: always()
+ run: du -sh ~/.cargo/registry ~/.cargo/git target desktop/src-tauri/target 2>/dev/null || true
+
+ # Only this trusted, main-bound canary writes the cache. Excluding bundle
+ # output prevents installers from entering it.
+ - name: Save exact release Cargo cache
+ if: steps.rust_cache.outputs.cache-hit != 'true'
+ uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+
- name: Save pnpm store cache
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
diff --git a/.github/workflows/macos-intel-canary.yml b/.github/workflows/macos-intel-canary.yml
new file mode 100644
index 0000000000..35b05313c9
--- /dev/null
+++ b/.github/workflows/macos-intel-canary.yml
@@ -0,0 +1,126 @@
+name: macOS Intel Canary
+
+# Produces an unsigned Intel DMG from trusted main. Its release-equivalent
+# Cargo state warms the distinct x86_64 release target without signing or
+# publishing anything.
+on:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ name: Build macOS Intel canary
+ if: github.repository == 'block/buzz'
+ runs-on: macos-latest
+ timeout-minutes: 60
+ env:
+ TARGET: x86_64-apple-darwin
+ steps:
+ - name: Require main
+ env:
+ SOURCE_REF: ${{ github.ref }}
+ run: |
+ if [[ "$SOURCE_REF" != "refs/heads/main" ]]; then
+ echo "::error::Canary builds must run from main; got $SOURCE_REF"
+ exit 1
+ fi
+
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+
+ - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
+
+ - name: Add Rust target
+ run: rustup target add "$TARGET"
+
+ - name: Install desktop dependencies
+ run: just desktop-install-ci
+
+ - name: Derive and patch canary version
+ run: |
+ BASE_VERSION=$(node -p "require('./desktop/package.json').version")
+ VERSION="${BASE_VERSION%%-*}-intel-test.${GITHUB_RUN_NUMBER}"
+ cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
+ cd src-tauri && cargo update --workspace
+
+ - name: Resolve native toolchain identity
+ id: native_toolchain
+ run: echo "id=$(scripts/desktop-native-toolchain-id.sh macos)" >> "$GITHUB_OUTPUT"
+
+ - name: Compute exact release cache key
+ id: rust_cache_key
+ env:
+ NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
+ run: |
+ KEY=$(scripts/desktop-release-cache-key.py \
+ --platform "$RUNNER_OS" \
+ --target "$TARGET" \
+ --features default \
+ --native-inputs "$NATIVE_TOOLCHAIN_ID")
+ echo "key=$KEY" >> "$GITHUB_OUTPUT"
+ echo "Release cache key: $KEY"
+
+ - name: Restore exact release Cargo cache
+ id: rust_cache
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+
+ - name: Generate non-updating bundle config
+ run: |
+ cat > desktop/src-tauri/tauri.canary.conf.json <<'JSON'
+ {"bundle":{"createUpdaterArtifacts":false,"macOS":{"minimumSystemVersion":"10.15"}}}
+ JSON
+
+ - name: Build Intel sidecars
+ run: |
+ cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
+ ./scripts/bundle-sidecars.sh "$TARGET"
+
+ - name: Build unsigned Intel DMG
+ run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --bundles dmg --config src-tauri/tauri.canary.conf.json
+ env:
+ CMAKE_POLICY_VERSION_MINIMUM: "3.5"
+ MACOSX_DEPLOYMENT_TARGET: "10.15"
+ CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
+ TAURI_BUNDLER_DMG_IGNORE_CI: "true"
+
+ - name: Locate fresh Intel DMG
+ id: artifact
+ run: |
+ DMG=$(find "desktop/src-tauri/target/${TARGET}/release/bundle/dmg" -name '*.dmg' -type f | head -1)
+ [[ -n "$DMG" ]] || { echo "::error::No Intel DMG found"; exit 1; }
+ echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
+
+ - name: Upload Intel canary
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: buzz-macos-intel-canary-${{ github.sha }}
+ path: ${{ steps.artifact.outputs.dmg }}
+ if-no-files-found: error
+ retention-days: 7
+
+ - name: Measure release Cargo cache inputs
+ if: always()
+ run: du -sh ~/.cargo/registry ~/.cargo/git target desktop/src-tauri/target 2>/dev/null || true
+
+ - name: Save exact release Cargo cache
+ if: steps.rust_cache.outputs.cache-hit != 'true'
+ uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
diff --git a/.github/workflows/mesh-lifecycle.yml b/.github/workflows/mesh-lifecycle.yml
new file mode 100644
index 0000000000..4780083ba4
--- /dev/null
+++ b/.github/workflows/mesh-lifecycle.yml
@@ -0,0 +1,111 @@
+name: Mesh Lifecycle
+# Relay-driven mesh lifecycle smoke: membership → signed discovery notes →
+# relay-derived allowlist → join → CPU inference over QUIC → stranger denied
+# (relay membership rejection + no routed inference, with a differential
+# trusted-inference health proof so a dead serve node can't fake a denial).
+# Runs the full Buzz "shared compute" join story with three real mesh-llm
+# node processes on one runner, using the Buzz relay as the control plane
+# (no hand-carried invite tokens). Mirrors the shape mesh-llm's own CI uses
+# for its two-node smokes (tiny CPU model, one runner, real QUIC mesh).
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'crates/buzz-relay/examples/mesh_*.rs'
+ - 'crates/buzz-relay/Cargo.toml'
+ - 'crates/buzz-admin/**'
+ - 'crates/buzz-test-client/**'
+ - 'crates/buzz-ws-client/**'
+ - 'Cargo.lock'
+ - 'desktop/src-tauri/src/mesh_llm/**'
+ - 'scripts/ci-mesh-lifecycle-smoke.sh'
+ - 'scripts/start-relay-for-tests.sh'
+ - '.github/workflows/mesh-lifecycle.yml'
+ pull_request:
+ paths:
+ - 'crates/buzz-relay/examples/mesh_*.rs'
+ - 'crates/buzz-relay/Cargo.toml'
+ - 'crates/buzz-admin/**'
+ - 'crates/buzz-test-client/**'
+ - 'crates/buzz-ws-client/**'
+ - 'Cargo.lock'
+ - 'desktop/src-tauri/src/mesh_llm/**'
+ - 'scripts/ci-mesh-lifecycle-smoke.sh'
+ - 'scripts/start-relay-for-tests.sh'
+ - '.github/workflows/mesh-lifecycle.yml'
+ workflow_dispatch:
+
+concurrency:
+ group: mesh-lifecycle-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ lifecycle-smoke:
+ name: Relay-Driven Mesh Lifecycle Smoke
+ runs-on: ubuntu-24.04
+ timeout-minutes: 45
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+
+ - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
+
+ - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
+ with:
+ save-if: ${{ github.event_name != 'pull_request' }}
+
+ # The mesh-llm SDK downloads a signed native runtime (llama.cpp CPU
+ # build) on first init, and the serve node downloads the smoke model
+ # from HuggingFace on first run. Key on the lockfile so a mesh pin bump
+ # rolls the runtime cache; the model ref is stable.
+ - name: Restore mesh runtime + model caches
+ id: mesh-caches
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cache/mesh-llm/native-runtimes
+ ~/.cache/huggingface/hub
+ key: mesh-lifecycle-${{ runner.os }}-smollm2-135m-${{ hashFiles('Cargo.lock') }}
+ restore-keys: |
+ mesh-lifecycle-${{ runner.os }}-smollm2-135m-
+
+ - name: Start integration services
+ run: |
+ for attempt in 1 2 3; do
+ if docker compose up -d postgres redis minio minio-init; then
+ break
+ fi
+ if [ "$attempt" -eq 3 ]; then
+ echo "docker compose up failed after 3 attempts" >&2
+ exit 1
+ fi
+ echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2
+ sleep $((attempt * 5))
+ done
+
+ - name: Run relay-driven mesh lifecycle smoke
+ run: ./scripts/ci-mesh-lifecycle-smoke.sh 2>&1 | tee /tmp/mesh-lifecycle-harness.log
+
+ - name: Save mesh runtime + model caches
+ if: github.ref == 'refs/heads/main' && steps.mesh-caches.outputs.cache-hit != 'true'
+ uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cache/mesh-llm/native-runtimes
+ ~/.cache/huggingface/hub
+ key: mesh-lifecycle-${{ runner.os }}-smollm2-135m-${{ hashFiles('Cargo.lock') }}
+
+ - name: Upload relay + harness logs
+ if: failure()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: mesh-lifecycle-logs
+ path: |
+ /tmp/buzz-relay.log
+ /tmp/mesh-lifecycle-harness.log
+ if-no-files-found: ignore
diff --git a/.github/workflows/signed-macos-canary.yml b/.github/workflows/signed-macos-canary.yml
index b2971db946..04592be327 100644
--- a/.github/workflows/signed-macos-canary.yml
+++ b/.github/workflows/signed-macos-canary.yml
@@ -34,16 +34,6 @@ jobs:
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- # Rust cache covering both the workspace sidecar build and the Tauri
- # crate build. shared-key scoped to macos-canary-release so canary runs
- # warm each other without colliding with CI's debug-profile keys.
- - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- with:
- workspaces: |
- .
- desktop/src-tauri
- shared-key: macos-canary-release
-
- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
@@ -78,6 +68,38 @@ jobs:
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
cd src-tauri && cargo update --workspace
+ - name: Resolve native toolchain identity
+ id: native_toolchain
+ run: echo "id=$(scripts/desktop-native-toolchain-id.sh macos)" >> "$GITHUB_OUTPUT"
+
+ # Compute this after cargo update so the key describes the graph that is
+ # actually compiled. The helper normalizes only Buzz Desktop's release
+ # version, allowing a canary to warm an otherwise identical tag build.
+ - name: Compute exact release cache key
+ id: rust_cache_key
+ env:
+ NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
+ run: |
+ KEY=$(scripts/desktop-release-cache-key.py \
+ --platform "$RUNNER_OS" \
+ --target aarch64-apple-darwin \
+ --features mesh-llm \
+ --native-inputs "$NATIVE_TOOLCHAIN_ID")
+ echo "key=$KEY" >> "$GITHUB_OUTPUT"
+ echo "Release cache key: $KEY"
+
+ - name: Restore exact release Cargo cache
+ id: rust_cache
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+
- name: Generate non-updating bundle config
run: |
cat > desktop/src-tauri/tauri.canary.conf.json <<'JSON'
@@ -208,6 +230,24 @@ jobs:
if-no-files-found: error
retention-days: 7
+ - name: Measure release Cargo cache inputs
+ if: always()
+ run: du -sh ~/.cargo/registry ~/.cargo/git target desktop/src-tauri/target 2>/dev/null || true
+
+ # Only this trusted, main-bound canary writes the cache. Excluding bundle
+ # output prevents installers or signed artifacts from entering it.
+ - name: Save exact release Cargo cache
+ if: steps.rust_cache.outputs.cache-hit != 'true'
+ uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+
- name: Save pnpm store cache
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
diff --git a/.github/workflows/windows-canary.yml b/.github/workflows/windows-canary.yml
index 29f74fa0f6..7093efd2dc 100644
--- a/.github/workflows/windows-canary.yml
+++ b/.github/workflows/windows-canary.yml
@@ -46,24 +46,9 @@ jobs:
shell: bash
run: rustup target add "$TARGET"
- # Rust cache covering both the workspace sidecar build and the Tauri
- # crate build. shared-key scoped to windows-canary-release so canary
- # runs warm each other without colliding with CI's debug-profile key
- # (CI windows job does clippy/check, not --release).
- - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- with:
- workspaces: |
- .
- desktop/src-tauri
- shared-key: windows-canary-release
-
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 24.14.1
- # Disable setup-node's built-in cache: we manage the pnpm store cache
- # explicitly below (restore before install, save after) to mirror the
- # pattern used by ci.yml and to keep caching logic consistent across
- # all three canary workflows.
package-manager-cache: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
@@ -108,6 +93,40 @@ jobs:
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
cd src-tauri && cargo update --workspace
+ - name: Resolve native toolchain identity
+ id: native_toolchain
+ shell: bash
+ run: echo "id=$(scripts/desktop-native-toolchain-id.sh windows)" >> "$GITHUB_OUTPUT"
+
+ # Compute this after cargo update so the key describes the graph that is
+ # actually compiled. The helper normalizes only Buzz Desktop's release
+ # version, allowing a canary to warm an otherwise identical tag build.
+ - name: Compute exact release cache key
+ id: rust_cache_key
+ shell: bash
+ env:
+ NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
+ run: |
+ KEY=$(scripts/desktop-release-cache-key.py \
+ --platform "$RUNNER_OS" \
+ --target x86_64-pc-windows-msvc \
+ --features default \
+ --native-inputs "$NATIVE_TOOLCHAIN_ID")
+ echo "key=$KEY" >> "$GITHUB_OUTPUT"
+ echo "Release cache key: $KEY"
+
+ - name: Restore exact release Cargo cache
+ id: rust_cache
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+
- name: Generate non-updating bundle config
shell: bash
run: |
@@ -152,6 +171,25 @@ jobs:
if-no-files-found: error
retention-days: 7
+ - name: Measure release Cargo cache inputs
+ if: always()
+ shell: bash
+ run: du -sh ~/.cargo/registry ~/.cargo/git target desktop/src-tauri/target 2>/dev/null || true
+
+ # Only this trusted, main-bound canary writes the cache. Excluding bundle
+ # output prevents installers from entering it.
+ - name: Save exact release Cargo cache
+ if: steps.rust_cache.outputs.cache-hit != 'true'
+ uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ desktop/src-tauri/target
+ !desktop/src-tauri/target/**/release/bundle
+ key: ${{ steps.rust_cache_key.outputs.key }}
+
- name: Save pnpm store cache
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
diff --git a/.release/desktop-candidate.json b/.release/desktop-candidate.json
index 1ba64765ff..843ac0dd7d 100644
--- a/.release/desktop-candidate.json
+++ b/.release/desktop-candidate.json
@@ -1,8 +1,10 @@
{
- "schema": 1,
- "version": "0.5.4",
- "base_sha": "6de85fe31d781122756aecf954bae7d357a56b9a",
- "previous_tag": "desktop-v0.5.3",
- "tag": "desktop-v0.5.4",
- "commit_count": 40
+ "schema": 2,
+ "version": "0.5.8",
+ "base_sha": "6a17d035f79ad582ca3f4f3cdc38d376f2c4087f",
+ "previous_tag": "desktop-v0.5.7",
+ "previous_base_sha": "74b913cff8512c015dc6f1a7473b253fa803f954",
+ "previous_merge_sha": "13c9e900c84cac1e2c8eeb7551bd1510ecb544d3",
+ "tag": "desktop-v0.5.8",
+ "commit_count": 4
}
diff --git a/AGENTS.md b/AGENTS.md
index 7cd43061b2..2d3939bbb3 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -100,10 +100,11 @@ Run `just test` for integration tests if you touched `buzz-relay`,
formatting via `stage_fixed`. Pre-commit runs fix variants in parallel (Rust
fmt, Tauri Rust fmt, desktop biome fix, web biome fix, mobile dart format).
Auto-fixable issues are fixed and re-staged; unfixable lint issues block the
-commit. **Pre-push hooks** run clippy (workspace + Tauri) and fast unit tests
-in parallel (Rust, desktop JS, Tauri Rust, mobile Flutter) — no overlap with
-pre-commit. Builds are CI-only. Run `just fix-all` to auto-fix all formatting
-in one shot. Run `just ci` for the full local gate. Run `just hooks` to
+commit. **Pre-push hooks** run clippy (workspace + Tauri), desktop TypeScript
+typechecking (`tsc --noEmit`), and fast unit tests in parallel (Rust, desktop
+JS, Tauri Rust, mobile Flutter) — no overlap with pre-commit. Builds are
+CI-only. Run `just fix-all` to auto-fix all formatting in one shot. Run
+`just ci` for the full local gate. Run `just hooks` to
re-install hooks after env changes. Before agents run Git or hooks, activate the
repo's Hermit environment (`. ./bin/activate-hermit`); do not rewrite hook
commands to compensate for an unconfigured shell `PATH`.
@@ -507,6 +508,7 @@ reconnects preserve pending avatar verification work):
- `resetRenderScopedReactionHydration()` — reaction hydration cache
- `clearSearchHitEventCache()` — search result event cache
- `clearMarkdownNodeCache()` — markdown parse-node cache
+- `resetLinkPreviewTitleCache()` — link preview title cache (Buzz entity titles come from relay events)
**If you add a new module-level cache, Map, or class instance that holds
community-scoped data, you must add its reset to `resetCommunityState()`.**
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e30941a355..7ca9525074 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,172 @@
# Changelog
+## v0.5.8
+
+### Desktop and shared changes
+
+- feat(desktop): unify add agent flows ([#5015](https://github.com/block/buzz/pull/5015)) ([`02f640bc4559c48ac0c2ec595ef34dd2c294b0db`](https://github.com/block/buzz/commit/02f640bc4559c48ac0c2ec595ef34dd2c294b0db))
+- fix(buzz-agent): budget summarizer reasoning separately so it cannot starve the handoff summary ([#5248](https://github.com/block/buzz/pull/5248)) ([`c7b663680a29a837dbd2fdde810f239f3d303025`](https://github.com/block/buzz/commit/c7b663680a29a837dbd2fdde810f239f3d303025))
+
+### Other repository changes
+
+- Revert "fix(acp): reject unattended permission requests" ([#5323](https://github.com/block/buzz/pull/5323)) ([`6a17d035f79ad582ca3f4f3cdc38d376f2c4087f`](https://github.com/block/buzz/commit/6a17d035f79ad582ca3f4f3cdc38d376f2c4087f))
+- infra: bind development services to loopback ([#4871](https://github.com/block/buzz/pull/4871)) ([`65834d68d0d3441c4e628540d6d5c8b0a2e757c9`](https://github.com/block/buzz/commit/65834d68d0d3441c4e628540d6d5c8b0a2e757c9))
+
+[Compare desktop-v0.5.7...desktop-v0.5.8](https://github.com/block/buzz/compare/desktop-v0.5.7...desktop-v0.5.8)
+
+## v0.5.7
+
+### Desktop and shared changes
+
+- fix(desktop): isolate relay admission tests ([#5221](https://github.com/block/buzz/pull/5221)) ([`74b913cff8512c015dc6f1a7473b253fa803f954`](https://github.com/block/buzz/commit/74b913cff8512c015dc6f1a7473b253fa803f954))
+- fix(desktop): externalize boot
+