Files
buzz/.github/workflows/ci.yml
Tom Brow 01b6174a1c fix(ci): don't run desktop tests for purely mobile client changes (#7709)
Mobile-only PRs currently trigger desktop CI because the desktop test
filter's glob inadvertently matches every path outside Tauri.

Fix that filter so that pure mobile changes skip desktop builds, tests,
and their relay artifact producer.

### Verification

- 12 regression tests exercise the exact pinned paths-filter action on
mobile, desktop, Tauri, backend, mixed, workflow, and documentation
changes.
- Restoring the bad glob makes the mobile-only regression test fail.
- The filter tests, existing required-context isolation contract,
workflow syntax validation, and Biome pass locally.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
2026-09-17 11:27:55 -07:00

411 lines
16 KiB
YAML

name: CI
on:
push:
branches: [main, release]
pull_request:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
BUZZ_TEST_POSTGRES_PASSWORD: buzz_dev
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.cache/ms-playwright
jobs:
changes:
name: Detect Changed Paths
runs-on: ubuntu-latest
timeout-minutes: 2
permissions:
contents: read
pull-requests: read
outputs:
rust: ${{ steps.filter.outputs.rust }}
desktop: ${{ steps.filter.outputs.desktop }}
desktop-rust: ${{ steps.filter.outputs.desktop-rust }}
web: ${{ steps.filter.outputs.web }}
mobile: ${{ steps.filter.outputs.mobile }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 2
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
token: ''
filters: |
rust:
- 'crates/**'
- 'migrations/**'
- 'schema/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- '.config/nextest.toml'
- 'scripts/postgres-test-*.sh'
- 'scripts/reconcile-schema-after-pgschema.sql'
- 'bin/pgschema'
- 'bin/.pgschema-*.pkg'
- 'scripts/check-postgres-test-discovery.py'
- 'scripts/test-postgres-test-discovery.sh'
- 'scripts/test-postgres-test-wrapper.sh'
- 'deny.toml'
- '.github/workflows/ci.yml'
- '.github/workflows/_ci-*.yml'
- 'scripts/run-tests.sh'
- 'scripts/model-capabilities.json'
- 'scripts/normative-corpus.json'
- 'justfile'
desktop:
- 'scripts/model-capabilities.json'
- 'scripts/normative-corpus.json'
- 'desktop/**'
- 'pnpm-lock.yaml'
desktop-rust:
- 'desktop/src-tauri/**'
web:
- 'web/**'
- 'pnpm-lock.yaml'
mobile:
- 'mobile/**'
- 'scripts/mobile-release.sh'
- 'scripts/mobile-worktree-overrides.sh'
- 'scripts/mobile-worktree-clean.sh'
- 'scripts/publish-mobile-release-candidate.sh'
- 'scripts/release-rulesets.sh'
- 'scripts/test-mobile-release-contract.sh'
- 'scripts/test-mobile-release-candidate-publisher.sh'
- 'scripts/test-mobile-worktree-overrides.sh'
- '.github/workflows/mobile-release-candidate.yml'
- '.github/workflows/ci.yml'
- '.github/workflows/_ci-*.yml'
- name: Validate PostgreSQL test discovery
if: github.event_name == 'push' || steps.filter.outputs.rust == 'true'
run: |
scripts/test-postgres-test-discovery.sh
scripts/test-postgres-test-wrapper.sh
- name: Release workflow source contract
run: scripts/test-release-ref-contract.sh
- name: Relay image eligibility contract
run: scripts/test-relay-image-eligibility-workflow.sh
- name: Desktop release candidate contract
run: scripts/test-desktop-release-candidate.sh
- name: OSS desktop promotion contract
run: |
scripts/test-oss-desktop-promotion.sh
scripts/test-oss-desktop-promotion-behavior.sh
- name: Mobile release contract
run: |
scripts/test-mobile-release-contract.sh
scripts/test-mobile-release-candidate-publisher.sh
- name: Desktop instance environment contract
run: scripts/test-desktop-instance-detection.sh
- name: Mobile worktree identity contract
run: scripts/test-mobile-worktree-overrides.sh
- name: Codex security review contract
run: just security-review-check
- name: Rust cache contract
run: |
scripts/test-rust-cache-contract.sh
scripts/test-rust-cache-contract-regressions.sh
- name: CI required-context isolation contract
run: scripts/test-ci-required-context-isolation.sh
- name: CI path selection regression tests
run: node --test scripts/ci-selection.test.mjs
- name: File size policy
run: just file-size-check
dead-token-guard:
name: Dead Token Reference Guard
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Check for dead API token references in client code
run: |
# Fail if dead API token patterns reappear in desktop, mobile, docs, or config.
# Relay crates are excluded — they still use token auth internally.
PATTERNS='TokenScope|MintTokenResponse|hasApiToken|spr_tok_'
PATHS='desktop/src/ desktop/tests/ mobile/test/ mobile/lib/ .env.example'
EXCLUDES='--exclude-dir=node_modules --exclude-dir=.dart_tool'
if grep -rn $EXCLUDES -E "$PATTERNS" $PATHS 2>/dev/null; then
echo "::error::Dead API token references found in client code. See above."
exit 1
fi
echo "No dead token references found."
rust:
name: Rust
needs: [changes]
if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.desktop-rust == 'true'
uses: ./.github/workflows/_ci-rust.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
desktop_rust: ${{ needs.changes.outputs.desktop-rust == 'true' }}
lane: required
rust-cross-compile-domain:
name: Rust Cross-Compile
needs: [changes]
if: github.event_name == 'push' || needs.changes.outputs.rust == 'true'
uses: ./.github/workflows/_ci-rust.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
desktop_rust: ${{ needs.changes.outputs.desktop-rust == 'true' }}
lane: cross-compile
desktop-domain:
name: Desktop Domain
needs: [changes]
if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true'
uses: ./.github/workflows/_ci-desktop.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
desktop: ${{ needs.changes.outputs.desktop == 'true' }}
desktop_rust: ${{ needs.changes.outputs.desktop-rust == 'true' }}
relay-artifacts-domain:
name: Relay Artifact Producer
needs: [changes]
if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true'
uses: ./.github/workflows/_ci-relay.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
desktop: ${{ needs.changes.outputs.desktop == 'true' }}
desktop_rust: ${{ needs.changes.outputs.desktop-rust == 'true' }}
lane: artifacts
postgres-domain:
name: PostgreSQL Domain
needs: [changes, relay-artifacts-domain]
if: github.event_name == 'push' || needs.changes.outputs.rust == 'true'
uses: ./.github/workflows/_ci-relay.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
desktop: ${{ needs.changes.outputs.desktop == 'true' }}
desktop_rust: ${{ needs.changes.outputs.desktop-rust == 'true' }}
lane: postgres
desktop-macos-domain:
name: Desktop macOS Domain
needs: [changes]
if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true'
uses: ./.github/workflows/_ci-desktop-macos.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
desktop: ${{ needs.changes.outputs.desktop == 'true' }}
desktop_rust: ${{ needs.changes.outputs.desktop-rust == 'true' }}
relay-domain:
name: Relay and PostgreSQL
needs: [changes, relay-artifacts-domain]
if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true'
uses: ./.github/workflows/_ci-relay.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
desktop: ${{ needs.changes.outputs.desktop == 'true' }}
desktop_rust: ${{ needs.changes.outputs.desktop-rust == 'true' }}
lane: required
clients:
name: Clients
needs: [changes]
if: github.event_name == 'push' || needs.changes.outputs.web == 'true' || needs.changes.outputs.mobile == 'true'
uses: ./.github/workflows/_ci-clients.yml
with:
web: ${{ needs.changes.outputs.web == 'true' }}
mobile: ${{ needs.changes.outputs.mobile == 'true' }}
lane: required
mobile-swift-domain:
name: Mobile Swift Domain
needs: [changes]
if: needs.changes.outputs.mobile == 'true'
uses: ./.github/workflows/_ci-clients.yml
with:
web: ${{ needs.changes.outputs.web == 'true' }}
mobile: ${{ needs.changes.outputs.mobile == 'true' }}
lane: mobile-swift
security-domain:
name: Security Domain
needs: [changes]
if: github.event_name == 'push' || needs.changes.outputs.rust == 'true'
uses: ./.github/workflows/_ci-security.yml
with:
rust: ${{ needs.changes.outputs.rust == 'true' }}
rust-lint:
name: Rust Lint
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.desktop-rust == 'true')
needs: [changes, rust]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Rust Lint result
env:
RESULT: ${{ needs.rust.outputs.rust_lint_result }}
run: test "$RESULT" = success
unit-tests:
name: Unit Tests
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.rust == 'true')
needs: [changes, rust]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Unit Tests result
env:
RESULT: ${{ needs.rust.outputs.unit_tests_result }}
run: test "$RESULT" = success
windows-rust:
name: Windows Rust (x86_64-pc-windows-msvc)
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.desktop-rust == 'true')
needs: [changes, rust]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Windows Rust result
env:
RESULT: ${{ needs.rust.outputs.windows_rust_result }}
run: test "$RESULT" = success
desktop:
name: Desktop
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true')
needs: [changes, desktop-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Desktop result
env:
RESULT: ${{ needs.desktop-domain.outputs.desktop_result }}
run: test "$RESULT" = success
desktop-build-macos:
name: Desktop Build (macOS)
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true')
needs: [changes, desktop-macos-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Desktop Build (macOS) result
env:
RESULT: ${{ needs.desktop-macos-domain.outputs.desktop_macos_result }}
run: test "$RESULT" = success
desktop-e2e-relay:
name: Desktop E2E Relay
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true')
needs: [changes, relay-artifacts-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Desktop E2E Relay result
env:
RESULT: ${{ needs.relay-artifacts-domain.outputs.desktop_e2e_relay_result }}
run: test "$RESULT" = success
desktop-e2e-integration:
name: Desktop E2E Integration
if: always() && needs.changes.result == 'success' && needs.relay-artifacts-domain.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true')
needs: [changes, relay-artifacts-domain, relay-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Desktop E2E Integration result
env:
RESULT: ${{ needs.relay-domain.outputs.desktop_e2e_integration_result }}
run: test "$RESULT" = success
backend-integration:
name: Backend Integration (relay e2e)
if: always() && needs.changes.result == 'success' && needs.relay-artifacts-domain.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.rust == 'true')
needs: [changes, relay-artifacts-domain, relay-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Backend Integration result
env:
RESULT: ${{ needs.relay-domain.outputs.backend_integration_result }}
run: test "$RESULT" = success
postgres-tests:
name: PostgreSQL Tests
if: always() && needs.changes.result == 'success' && needs.relay-artifacts-domain.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.rust == 'true')
needs: [changes, relay-artifacts-domain, postgres-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check PostgreSQL Tests result
env:
RESULT: ${{ needs.postgres-domain.outputs.postgres_tests_result }}
run: test "$RESULT" = success
relay-e2e:
name: Relay E2E
if: always() && needs.changes.result == 'success' && needs.relay-artifacts-domain.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.rust == 'true')
needs: [changes, relay-artifacts-domain, relay-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Relay E2E result
env:
RESULT: ${{ needs.relay-domain.outputs.relay_e2e_result }}
run: test "$RESULT" = success
web:
name: Web
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.web == 'true')
needs: [changes, clients]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Web result
env:
RESULT: ${{ needs.clients.outputs.web_result }}
run: test "$RESULT" = success
mobile:
name: Mobile
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.mobile == 'true')
needs: [changes, clients]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Mobile result
env:
RESULT: ${{ needs.clients.outputs.mobile_result }}
run: test "$RESULT" = success
security:
name: Security
if: always() && needs.changes.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.rust == 'true')
needs: [changes, security-domain]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Security result
env:
RESULT: ${{ needs.security-domain.outputs.security_result }}
run: test "$RESULT" = success