mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
## Summary The PR critical path on main push [run 37635224099](https://github.com/block/buzz/actions/runs/37635224099) (`e097c4299`) was about 42 minutes: - **Desktop Core** took 41 min. It ran frontend checks, two clippy graphs, the Tauri suite, the mesh-llm suite and every compiled-flag state one after another. The compiled-flag step alone took 16m51. - **Relay artifact producer → Desktop E2E Integration** took about 31 min. The producer built the relay binaries (5m07), the backend integration archive (4m41) and the PostgreSQL archive (8m18) one after another. - **Desktop Smoke E2E** took 19–24 min per shard across 4 shards, with 82 min of tests in total. This PR attacks all three. ### 1. Run each compiled-flag state once (`Justfile`) | Previous step | Cost | Why it's redundant | |---|---|---| | Clean state: full `buzz_lib` suite (`--lib`) | 3m27 run | `desktop-tauri-test` runs the same suite in the same compile state one step earlier. When `BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY` is unset, the expected value already defaults to `false` in non-owner-only builds (`runtime/test_fixtures.rs`, `agents_tests.rs`). | | Internal state: auto-connect and owner-only built separately | 1m22 + 1m06 + 56s across three recompiles | Internal release packaging always sets both capabilities together, so building owner-only without auto-connect tests a configuration that never ships. The extra 56s happens because a bare `cargo test <filter>` builds every target, not just the `--lib` harness. | | Demo state: `cargo test --workspace` | ~30s more than `--lib` | `buzz-terminal` and the `tests/*.rs` integration tests don't read any `BUZZ_BUILD_*` variable or `buzz_lib`, so they ran the same binaries a second time. | A fourth cost was hidden: every recipe invocation spent ~1m20 recompiling `buzz-desktop` to the *same* unit hash `desktop-tauri-test` had just built. `_ensure-sidecar-stubs` ran `touch` on every sidecar stub on each run. `tauri-build` emits `rerun-if-changed` for each `externalBin`, so the new mtimes reran `build.rs` and recompiled the crate. Stubs are now created only when they're missing. This also speeds up any local `desktop-tauri-*` recipe run back-to-back. After this change: - **Clean state:** runs only the two compiled-flag assertions (`compiled_flag_matches_expected`, `compiled_policy_matches_expected`) with `expected=false`. No recompile. - **Internal state:** one compile with `BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1` and `BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY=1`. It runs both assertions with `expected=true`, then the full `buzz_lib` suite. The suite now runs with auto-connect enabled as well, which matches the shipped internal build more closely than before. - **Auto-connect-only state (assertions only):** auto-connect `true`, owner-only `false`. The two capabilities are documented as independent. Without this state, a `build.rs` block that read the other capability's variable would pass, because every other state sets both or neither. - **Demo state:** one compile with only `BUZZ_BUILD_DEMO_SLUG`. It asserts the slug and asserts both capabilities stay `false`, then runs the full `buzz_lib` suite. - The recipe clears the three toggled `BUZZ_BUILD_*` capability variables and any caller `BUZZ_TEST_EXPECTED_*` values up front. Each state exports its own values in a subshell. Every invocation uses `--lib`, so each state compiles one test harness. - Each ignored-assertion run requires exactly one passing test per name filter. Previously, a renamed or un-ignored assertion matched nothing and passed silently. - The 32-character demo-name rejection check is unchanged. The full-suite reruns in the internal and demo states are kept on purpose. They are the only coverage that the whole suite holds up in those two shipped build shapes. Estimate: the compiled-flag work goes from about 17 min to about 11. ### 2. Split Desktop Core into parallel, path-gated lanes (`_ci-desktop.yml`) - **Desktop JS:** `desktop-check`, `desktop-test` and `desktop-build`. These compile no Rust, but some node:test cases read relay and DB crate sources to pin shared constants (for example `relayReconnectReplay.test.mjs`). So the job runs on desktop *and* Rust changes, in parallel with the Tauri lanes. `lefthook.yml` notes this as a deliberate local deviation. - **Desktop Tauri (core | mesh | compiled-flags):** a matrix of parallel Rust lanes. - `core` and `compiled-flags` share the existing `desktop-core` Rust cache key, so they start warm. `compiled-flags` restores it read-only. - `mesh` keeps its own cache because `mesh-llm` pulls a different dependency graph. - **Smoke E2E and the Windows frontend build** compile no Rust and read no crate sources, so they no longer run for Rust-only changes. - **Smoke E2E goes from 4 to 10 shards.** Playwright shards by test count, not duration. A simulation from the per-test durations in run 37635224099 reproduces the observed 4-shard split exactly. With 10 shards, the slowest shard runs about 11 min of tests. - **The `Desktop` gate is stricter.** Each selected job must succeed and each unselected job must be skipped, so a mis-gated job can't pass silently. The required check context (`Desktop`) is unchanged. `scripts/ci-required-gates.test.mjs` pins each job's `if:` to the gate's selection expression, and checks which jobs a Rust-only, desktop-only, Tauri, push, and empty selection pick. - **Path-filter fixes:** - The `desktop` filter now includes `_ci-desktop.yml`, `ci.yml` and `Justfile` (which holds the desktop recipes), so edits to them run the frontend lanes. - The `rust` filter now matches `Justfile`. It still named the pre-rename `justfile`, so a `Justfile`-only change selected no Rust suites. - `scripts/ci-selection.test.mjs` gains `Justfile` and desktop-workflow scenarios, and the shared-workflow scenario now expects `desktop`. - **Dead step removed:** the failure-only Playwright artifact upload in the old Core job never had anything to upload, because that job never ran Playwright. ### 3. Build relay artifacts in parallel (`_ci-relay.yml`) - **The producer is now a 3-way matrix** (`relay-binaries`, `backend-integration-tests`, `postgres-tests`). - Each lane has its own exact-source cache entry and uploads `desktop-e2e-relay-<artifact>`. - Consumers download `desktop-e2e-relay-*` into `target/ci`, so their paths are unchanged. - The required `Desktop E2E Relay` context still reads the producer job's aggregate result. - **Rust cache saving:** all lanes restore the existing Rust cache key, but only the widest lane (`postgres-tests`) saves it. Otherwise the fastest lane could overwrite it with a narrower target dir. - **Desktop E2E Integration goes from 2 to 3 shards.** Shard labels now use `strategy.job-total`. ### 4. Bound degraded apt steps (`scripts/ci-apt-retry.sh`) - With more shards, more runs hit the occasional degraded Ubuntu mirror. On this PR, a Tauri lane ran `apt-get install` for 27 minutes: apt's `Acquire` timeouts kept firing, but apt kept re-queuing the requests and never failed. Separately, three Smoke shards stalled silently in `playwright install-deps` (no output after `InRelease`) until the job timeout. Healthy apt steps across four recent runs finished in at most 238s. - Every apt-backed step in Desktop and Integration now runs through `scripts/ci-apt-retry.sh`. The script writes the apt options to `apt.conf.d`, so Playwright's own apt-get calls get them too. It kills each attempt after 7 minutes (90s for `apt-get update`). Between attempts it stops any `apt-get` or `dpkg` the attempt left running, repairs dpkg under its own 60s deadline, and retries up to 3 times. The stop matters for `playwright install-deps`, which runs `sudo sh -c "apt-get update && apt-get install ..."`: sudo puts its command in a new process group, so the deadline's signal stops `sh` and apt-get survives holding the dpkg lock (seen on Smoke shard 8 of run 37696815557, and reproduced with real sudo). - Downloaded `.deb`s are kept between attempts, so a retry resumes where the last one stopped. Against a mirror that stays degraded, this bounds each wrapped command at about 25 minutes rather than recovering it. The Tauri lanes wrap two commands, `apt-get update` (about 8.5 minutes worst case) and `apt-get install` (about 25), so about 34 minutes together. Job timeouts are sized to fit that bound plus the job's own work: Tauri 55m (lanes take up to ~15m), Integration 40m, and Smoke 45m. A degraded mirror therefore ends with the wrapper's error instead of a silent job timeout. - **The retry has already fired on this PR.** In [run 37703795342](https://github.com/block/buzz/actions/runs/37703795342), `playwright install-deps` on Integration shard 1/3 hit the 420s attempt deadline. The second attempt started about 1.4s later, downloaded and configured the real packages, and succeeded. The step took 526s (8m46), and that shard took 17m24 while the other two took about 5m. Healthy installs have taken at most 238s, so 420s leaves headroom, and a slow mirror costs one attempt instead of the job. - Between attempts the script stops **every** `apt-get`/`dpkg` on the machine, not only the ones it started. That's intentional: on a hosted runner any other one would hold the same dpkg lock. It assumes an ephemeral runner, and the script says not to use it on a self-hosted runner or a developer machine. - `scripts/ci-apt-retry.test.mjs` covers the following with a fake `sudo`: success, retry, the attempt budget, a stalled attempt, a stalled dpkg repair, an apt-get that escapes the attempt's process group (on a retry and on the final attempt), and usage. It needs Linux (GNU `timeout`, `flock`, `setsid`, procps), so it skips on other platforms. CI runs it on Ubuntu. ### What got simpler - **Compiled-flag recipe:** two full-suite runs instead of three, six cargo invocations instead of eight, and sidecar stubs no longer invalidate the desktop build cache. - **Path selection:** each desktop job declares only the paths that actually affect it. The stale `justfile` filter and a dead artifact step are gone. - **Apt options:** set once in `apt.conf.d` by one script, instead of as repeated `-o Acquire...` flags on each apt call. - **Shard counts:** the shard count is written once per matrix, not repeated in names and commands. ### Cost - **More runners:** a full run uses more runner jobs. Smoke E2E goes from 4 to 10 jobs, Desktop Core from 1 to 4, the relay producer from 1 to 3, and Integration from 2 to 3. - **Fewer runners on single-area PRs:** desktop-only PRs skip the Rust lanes, and Rust-only PRs skip Smoke E2E and the Windows build. - **One extra Rust cache entry:** `desktop-mesh`. ### Related issue None found. ### Testing Blox, compiled-flag recipe at `77b9874b`: - [x] Recipe passes with output redirected to a file. The log keeps all 4 state headers, the ignored assertions ran 2/2/2/3, and both full suites passed (`3423 passed`). - [x] A second back-to-back run shows no clean-state `Compiling buzz-desktop`. - [x] Mutation tests all fail as required: - `build.rs` auto-connect reads the owner-only var: rc=101. - The reverse swap: rc=101. - Rename `compiled_policy_matches_expected`: rc=1. - Wrong owner-only expectation: rc=101. - [ ] `just ci` Workflows: - [x] `actionlint` on `_ci-desktop.yml`, `_ci-relay.yml` and `ci.yml`: clean. - [x] `scripts/test-ci-required-context-isolation.sh`: passes. - [x] GitHub Actions: runs [37701003713](https://github.com/block/buzz/actions/runs/37701003713) and [37703795342](https://github.com/block/buzz/actions/runs/37703795342) took 27m09 and 28m51. Before this PR, PR runs had a median of 40.0m (n=21) and main pushes took about 42m. These are observations across mixed runner images, not a measured causal speedup. - [x] Selection, gate and apt suites (74/74 on Linux), `actionlint` and `bash -n` on Blox at tree `db9725fe`. --------- Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
1349 lines
68 KiB
Makefile
1349 lines
68 KiB
Makefile
# Buzz — development task runner
|
|
|
|
set dotenv-load := true
|
|
|
|
desktop_dir := "desktop"
|
|
desktop_tauri_manifest := "desktop/src-tauri/Cargo.toml"
|
|
web_dir := "web"
|
|
|
|
# Opt-in mesh-llm. Off by default so `just dev`/`just staging`/`just production`
|
|
# skip ~420 extra crates + the llama.cpp native runtime build and stay fast to
|
|
# iterate on. Turn on to test mesh compute features: `just mesh=1 dev` /
|
|
# `just mesh=1 staging` / `just mesh=1 production`.
|
|
mesh := ""
|
|
|
|
# Reset only the current standalone desktop instance before launch.
|
|
# Usage: `just fresh=1 desktop-standalone`.
|
|
fresh := ""
|
|
|
|
# List all available tasks
|
|
default:
|
|
@just --list
|
|
|
|
# ─── Dev Environment ─────────────────────────────────────────────────────────
|
|
|
|
# Install required dev tools via Hermit and create .env (safe to re-run)
|
|
bootstrap:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
# Hermit's bin/ symlinks auto-download pinned tool versions on first use.
|
|
# Running each tool once triggers the download if not already cached.
|
|
echo "Ensuring toolchain via Hermit..."
|
|
cargo --version &
|
|
node --version &
|
|
pnpm --version &
|
|
wait
|
|
if ! command -v docker &>/dev/null; then
|
|
echo "Error: Docker is required but not installed."
|
|
echo "Install it from https://docs.docker.com/get-docker/"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f .env ]]; then
|
|
cp .env.example .env
|
|
echo "Created .env from .env.example — review it before running just dev."
|
|
fi
|
|
./scripts/ensure-local-relay-key.sh .env
|
|
|
|
# Start Docker services, run migrations, install desktop deps
|
|
setup: bootstrap
|
|
./scripts/dev-setup.sh
|
|
|
|
# Install git hooks via lefthook (dispatches from the shared .git/hooks dir so all
|
|
# linked worktrees inherit the same hooks without a worktree-relative .hooks path)
|
|
hooks:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# Use the Hermit-pinned lefthook (bin/lefthook self-downloads on first use):
|
|
# works with no pre-installed lefthook and guarantees the pinned version
|
|
# rather than whatever happens to be on PATH.
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
# --path-format=absolute guarantees an absolute path from every invocation context:
|
|
# without it, --git-common-dir returns ".git" from the main checkout and a
|
|
# relative hooksPath would break linked-worktree dispatch just like .hooks did.
|
|
HOOKS_DIR="$(git rev-parse --path-format=absolute --git-common-dir)/hooks"
|
|
git config --local core.hooksPath "$HOOKS_DIR"
|
|
lefthook install --force
|
|
|
|
# Wipe development state and recreate a clean environment. Installed Buzz is preserved.
|
|
[confirm("This will DELETE all development data and preserve installed Buzz. Continue? (y/N)")]
|
|
reset:
|
|
./scripts/dev-reset.sh --yes
|
|
|
|
# Stop all dev services (keep data)
|
|
down:
|
|
docker compose down
|
|
|
|
# Show dev service status
|
|
ps:
|
|
docker compose ps
|
|
|
|
# Tail all service logs
|
|
logs *ARGS:
|
|
docker compose logs -f {{ARGS}}
|
|
|
|
# ─── Build & Check ───────────────────────────────────────────────────────────
|
|
|
|
# Build the Rust workspace
|
|
build:
|
|
cargo build --workspace
|
|
|
|
# Build the Rust workspace in release mode
|
|
build-release:
|
|
cargo build --workspace --release
|
|
|
|
# Run repo lint, formatting, and repository policy checks
|
|
check: fmt-check clippy desktop-check desktop-tauri-fmt-check desktop-tauri-clippy web-check mobile-check security-review-check file-size-check
|
|
|
|
# Validate the trusted security-review workflow support and renderer contract.
|
|
security-review-check:
|
|
node --check .github/scripts/codex-security-review.js
|
|
node --test .github/scripts/codex-security-review.test.js
|
|
actionlint .github/workflows/codex-security-review.yml
|
|
|
|
# Run the repository-wide differential file-size ratchet and its policy tests.
|
|
# The ratchet inspects only files changed from the merge base, so this stays
|
|
# cheap enough to run unconditionally without duplicating path filters.
|
|
file-size-check:
|
|
node --test scripts/check-file-sizes-core.test.mjs
|
|
node desktop/scripts/check-file-sizes.mjs
|
|
node web/scripts/check-file-sizes.mjs
|
|
node mobile/scripts/check-file-sizes.mjs
|
|
|
|
# Format all Rust code
|
|
fmt:
|
|
cargo fmt --all
|
|
|
|
# Check formatting without modifying files
|
|
fmt-check:
|
|
cargo fmt --all -- --check
|
|
|
|
# Run clippy with warnings as errors
|
|
clippy:
|
|
cargo clippy --workspace --all-targets -- -D warnings
|
|
|
|
# Install JS dependencies (pnpm workspace — installs all packages from root)
|
|
desktop-install:
|
|
pnpm install
|
|
|
|
# Install JS dependencies reproducibly for CI (pnpm workspace)
|
|
desktop-install-ci:
|
|
pnpm install --frozen-lockfile
|
|
|
|
# Run desktop lint and format checks
|
|
desktop-check:
|
|
cd {{desktop_dir}} && pnpm check
|
|
|
|
# Fix desktop lint and format issues
|
|
desktop-fix:
|
|
cd {{desktop_dir}} && pnpm exec biome check --write .
|
|
|
|
# Run desktop TS helper unit tests
|
|
desktop-test:
|
|
cd {{desktop_dir}} && pnpm test
|
|
|
|
# Run desktop TypeScript checks
|
|
desktop-typecheck:
|
|
cd {{desktop_dir}} && pnpm typecheck
|
|
|
|
# Build desktop frontend assets
|
|
desktop-build:
|
|
cd {{desktop_dir}} && pnpm build
|
|
|
|
# Format desktop Tauri Rust code
|
|
desktop-tauri-fmt:
|
|
cargo fmt --manifest-path {{desktop_tauri_manifest}} --all
|
|
|
|
# Check desktop Tauri Rust formatting
|
|
desktop-tauri-fmt-check:
|
|
cargo fmt --manifest-path {{desktop_tauri_manifest}} --all -- --check
|
|
|
|
# Format all code (Rust + Tauri Rust + Dart)
|
|
fmt-all: fmt desktop-tauri-fmt mobile-fmt
|
|
|
|
# Fix all formatting and lint issues
|
|
fix-all: fmt desktop-tauri-fmt desktop-fix web-fix mobile-fix
|
|
|
|
# Ensure sidecar placeholder binaries exist (Tauri validates externalBin at compile time)
|
|
# Sidecar binary list must stay in sync with desktop-release-build below.
|
|
_ensure-sidecar-stubs:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
mkdir -p desktop/src-tauri/binaries
|
|
SIDECARS=(buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz)
|
|
if [[ "$TARGET" != *windows* ]]; then
|
|
SIDECARS+=(buzz-backend-kubernetes)
|
|
fi
|
|
# Create missing stubs only: tauri-build reruns build.rs (and recompiles
|
|
# buzz-desktop) whenever an externalBin's mtime changes, so touching
|
|
# existing files would invalidate every cached desktop test build.
|
|
for bin in "${SIDECARS[@]}"; do
|
|
stub="desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
[[ -e "$stub" ]] || : > "$stub"
|
|
done
|
|
|
|
# Ensure Docker dev services (Postgres, Redis, etc.) are running and healthy
|
|
_ensure-services:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-postgres 2>/dev/null || echo "not_found")
|
|
redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-redis 2>/dev/null || echo "not_found")
|
|
if [[ "$pg" == "healthy" && "$redis" == "healthy" ]]; then
|
|
echo "Services already healthy"
|
|
exit 0
|
|
fi
|
|
echo "Starting services..."
|
|
docker compose up -d || true
|
|
echo -n "Waiting for services"
|
|
for i in $(seq 1 40); do
|
|
pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-postgres 2>/dev/null || echo "not_found")
|
|
redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-redis 2>/dev/null || echo "not_found")
|
|
if [[ "$pg" == "healthy" && "$redis" == "healthy" ]]; then
|
|
echo " ready"
|
|
exit 0
|
|
fi
|
|
echo -n "."
|
|
sleep 3
|
|
done
|
|
echo " timed out"
|
|
exit 1
|
|
|
|
# Apply database migrations and seed the local dev community if the dev database is running
|
|
_ensure-migrations: _ensure-services
|
|
cargo run -p buzz-admin -- migrate
|
|
./scripts/seed-local-community.sh
|
|
|
|
# Run clippy on the desktop Tauri Rust crate
|
|
# Features are additive, so a single invocation lints only one cfg graph.
|
|
# Both graphs ship (release-windows builds without mesh-llm), so lint both:
|
|
# the default graph covers the `#[cfg(not(feature = "mesh-llm"))]` arms and
|
|
# the feature-enabled graph covers the mesh code.
|
|
desktop-tauri-clippy: _ensure-sidecar-stubs
|
|
cargo clippy --manifest-path {{desktop_tauri_manifest}} --workspace --all-targets -- -D warnings
|
|
cargo clippy --manifest-path {{desktop_tauri_manifest}} --workspace --all-targets --features mesh-llm -- -D warnings
|
|
|
|
# Check the desktop Tauri Rust crate compiles
|
|
desktop-tauri-check: _ensure-sidecar-stubs
|
|
cargo check --manifest-path {{desktop_tauri_manifest}}
|
|
|
|
# Run desktop Tauri Rust unit tests
|
|
desktop-tauri-test: _ensure-sidecar-stubs
|
|
cd desktop/src-tauri && cargo test --workspace
|
|
|
|
# Run the desktop Tauri lib tests with the mesh-llm feature graph
|
|
desktop-tauri-test-mesh: _ensure-sidecar-stubs
|
|
cargo test --manifest-path {{desktop_tauri_manifest}} --features mesh-llm --lib
|
|
|
|
# Run the native terminal latency gate explicitly on a known-idle host.
|
|
# This is intentionally excluded from shared CI: scheduler contention makes a
|
|
# wall-clock assertion flaky, and the release profile is the shipped shape.
|
|
desktop-terminal-performance-test:
|
|
cargo test --manifest-path desktop/src-tauri/crates/buzz-terminal/Cargo.toml --release --test latency g3_renderer_acquire_stays_within_frame_budget -- --ignored --exact --nocapture
|
|
|
|
# Verify compiled-flag behavior in each compile state.
|
|
# The clean (OSS) state's full suite already runs in desktop-tauri-test, so
|
|
# only its flag assertions run here. The internal state sets both release
|
|
# capabilities together, as internal release packaging does, and reruns the
|
|
# buzz_lib suite. An auto-connect-only state asserts the two capabilities stay
|
|
# independent (a swapped build.rs variable fails it). The demo state sets only
|
|
# the demo slug, asserts neither capability leaks on, and reruns the suite.
|
|
# Every invocation uses --lib so each state compiles a single test harness;
|
|
# build.rs rerun-if-env-changed triggers the recompile between states. The
|
|
# workspace member crates and integration tests do not read these build
|
|
# variables, so they are not rerun.
|
|
desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cd desktop/src-tauri
|
|
unset BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY BUZZ_BUILD_DEMO_SLUG
|
|
unset BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY BUZZ_TEST_EXPECTED_DEMO_SLUG
|
|
# Run ignored assertions by name; libtest passes a filter that matches
|
|
# nothing, so require exactly one passing test per filter.
|
|
run_ignored() {
|
|
local out
|
|
out="$(cargo test --lib -- --ignored --nocapture "$@" 2>&1 | tee >(cat >&2))"
|
|
if ! grep -qE "^test result: ok\. $# passed;" <<<"$out"; then
|
|
echo "expected exactly $# ignored compiled-flag assertions to pass: $*" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
flag_tests=(compiled_flag_matches_expected compiled_policy_matches_expected)
|
|
echo "=== Clean build (no flag) → expect false ==="
|
|
(
|
|
export BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=false
|
|
export BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false
|
|
run_ignored "${flag_tests[@]}"
|
|
)
|
|
echo "=== Internal build (flags set) → expect true ==="
|
|
(
|
|
export BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1
|
|
export BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY=1
|
|
export BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=true
|
|
export BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=true
|
|
run_ignored "${flag_tests[@]}"
|
|
cargo test --lib
|
|
)
|
|
echo "=== Auto-connect only → capabilities stay independent ==="
|
|
(
|
|
export BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1
|
|
export BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=true
|
|
export BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false
|
|
run_ignored "${flag_tests[@]}"
|
|
)
|
|
echo "=== Maximum accepted demo name reaches Rust build validation ==="
|
|
DEMO_CONFIG="$(node ../scripts/demo-build-config.mjs "$(printf 'x%.0s' {1..31})" /dev/null 1234567812345678)"
|
|
DEMO_SLUG="$(node -e 'console.log(JSON.parse(process.argv[1]).slug)' "$DEMO_CONFIG")"
|
|
(
|
|
export BUZZ_BUILD_DEMO_SLUG="$DEMO_SLUG"
|
|
export BUZZ_TEST_EXPECTED_DEMO_SLUG="$DEMO_SLUG"
|
|
export BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=false
|
|
export BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false
|
|
run_ignored compiled_demo_slug_matches_expected "${flag_tests[@]}"
|
|
cargo test --lib
|
|
)
|
|
if node ../scripts/demo-build-config.mjs "$(printf 'x%.0s' {1..32})" /dev/null 1234567812345678; then
|
|
echo "A 32-character demo name unexpectedly passed JavaScript validation" >&2
|
|
exit 1
|
|
fi
|
|
echo "All compiled states and the accepted/rejected demo-name boundary verified."
|
|
|
|
# Build the full desktop Tauri app locally (unsigned, for testing)
|
|
# Sidecar binary list must stay in sync with _ensure-sidecar-stubs above.
|
|
# pnpm install is unconditional here: release builds must start from a clean dep tree.
|
|
desktop-release-build target="aarch64-apple-darwin":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
TARGET={{target}}
|
|
mkdir -p desktop/src-tauri/binaries
|
|
touch "desktop/src-tauri/binaries/buzz-acp-$TARGET"
|
|
touch "desktop/src-tauri/binaries/buzz-agent-$TARGET"
|
|
if [[ "$TARGET" != *windows* ]]; then
|
|
touch "desktop/src-tauri/binaries/buzz-backend-kubernetes-$TARGET"
|
|
fi
|
|
touch "desktop/src-tauri/binaries/buzz-dev-mcp-$TARGET"
|
|
touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET"
|
|
touch "desktop/src-tauri/binaries/buzz-$TARGET"
|
|
pnpm install
|
|
cd {{desktop_dir}} && pnpm tauri build --features mesh-llm --target {{target}}
|
|
|
|
# Build an unsigned named macOS demo DMG with isolated app and runtime identities.
|
|
desktop-demo-build demo_name target="aarch64-apple-darwin":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
TARGET={{target}}
|
|
[[ "$(uname -s)" == "Darwin" && "$TARGET" == *-apple-darwin ]] || { echo "Demo DMGs require a macOS Apple target" >&2; exit 2; }
|
|
CONFIG_PATH="$(mktemp "${TMPDIR:-/tmp}/buzz-demo-config.XXXXXX")"
|
|
trap 'rm -f "$CONFIG_PATH"' EXIT
|
|
DEMO_BUILD_ID="$(node -e 'console.log(require("node:crypto").randomBytes(8).toString("hex"))')"
|
|
DEMO_CONFIG="$(node desktop/scripts/demo-build-config.mjs {{quote(demo_name)}} "$CONFIG_PATH" "$DEMO_BUILD_ID")"
|
|
read_config() { node -e 'console.log(JSON.parse(process.argv[1])[process.argv[2]])' "$DEMO_CONFIG" "$1"; }
|
|
PRODUCT_NAME="$(read_config productName)"
|
|
DMG_VOLUME_NAME="$(read_config dmgVolumeName)"
|
|
DMG_FILE_STEM="$(read_config dmgFileStem)"
|
|
DEMO_SLUG="$(read_config slug)"
|
|
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"
|
|
pnpm install
|
|
cd {{desktop_dir}}
|
|
BUZZ_BUILD_DEMO_SLUG="$DEMO_SLUG" pnpm tauri build --features mesh-llm --target "$TARGET" --bundles app --config "$CONFIG_PATH"
|
|
cd ..
|
|
VERSION="$(node -p "require('./desktop/package.json').version")"
|
|
DMG_ARCH="${TARGET%%-*}"; [[ "$DMG_ARCH" == "x86_64" ]] && DMG_ARCH=x64
|
|
APP_PATH="desktop/src-tauri/target/$TARGET/release/bundle/macos/$PRODUCT_NAME.app"
|
|
PLIST="$APP_PATH/Contents/Info.plist"
|
|
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $PRODUCT_NAME" "$PLIST"
|
|
/usr/libexec/PlistBuddy -c "Set :CFBundleName $PRODUCT_NAME" "$PLIST"
|
|
codesign --force --deep --sign - "$APP_PATH"
|
|
VOL_NAME="$DMG_VOLUME_NAME" ./desktop/scripts/package-macos-dmg.sh "$APP_PATH" "desktop/src-tauri/target/$TARGET/release/bundle/dmg/${DMG_FILE_STEM}_${VERSION}_${DMG_ARCH}.dmg"
|
|
|
|
# Run desktop checks suitable for CI / pre-push
|
|
desktop-ci: desktop-check desktop-test desktop-tauri-fmt-check desktop-build desktop-tauri-check desktop-tauri-test
|
|
|
|
# Seed deterministic channel data for desktop Playwright tests
|
|
desktop-e2e-seed: _ensure-migrations
|
|
./scripts/setup-desktop-test-data.sh
|
|
|
|
# Run desktop browser smoke tests
|
|
desktop-e2e-smoke:
|
|
cd {{desktop_dir}} && pnpm test:e2e:smoke
|
|
|
|
# Run desktop relay-backed e2e tests
|
|
desktop-e2e-integration: _ensure-migrations
|
|
cd {{desktop_dir}} && pnpm test:e2e:integration
|
|
|
|
# Run the deterministic desktop correctness smoke against an isolated local relay
|
|
desktop-release-smoke:
|
|
./scripts/run-desktop-release-smoke.sh
|
|
|
|
# Run only the e2e specs changed vs origin/main (both projects) before pushing
|
|
desktop-e2e-pre-push: _ensure-migrations
|
|
git fetch origin main
|
|
cd {{desktop_dir}} && pnpm build:e2e && pnpm exec playwright test --only-changed=origin/main
|
|
|
|
# Run all checks suitable for CI / pre-push (no infra needed)
|
|
ci: check test-unit desktop-test desktop-build desktop-tauri-check desktop-tauri-test web-build mobile-test
|
|
|
|
# ─── Test ─────────────────────────────────────────────────────────────────────
|
|
|
|
# Run all tests (unit + integration)
|
|
test:
|
|
./scripts/run-tests.sh all
|
|
|
|
# Run unit tests only (no infra needed)
|
|
test-unit:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
./scripts/test-ensure-local-relay-key.sh
|
|
if command -v cargo-nextest &>/dev/null; then
|
|
cargo nextest run -p buzz-core -p buzz-auth --lib
|
|
cargo nextest run -p buzz-audit --lib
|
|
# Cross-pod disconnect payload tests (NIP-FI and community archive
|
|
# fence); all infra-free.
|
|
cargo nextest run -p buzz-pubsub --lib -E 'test(/^conn_control::tests::/)'
|
|
# buzz-auth NIP-FI verifier doctests. The sealed-authority
|
|
# `compile_fail` doctests prove the default-feature public API alone
|
|
# cannot forge the issuer→JWKS authority; nextest does not run
|
|
# doctests, hence this separate step. The verifier's regression suite
|
|
# lives in the in-crate `#[cfg(test)] mod tests`, so `--lib` above
|
|
# already runs it.
|
|
cargo test -p buzz-auth --doc
|
|
cargo nextest run -p buzz-voice --lib
|
|
cargo nextest run -p buzz-cli
|
|
# buzz-sdk builder/validation unit tests: pure event-builder and input
|
|
# validation (e.g. the canvas writer-discipline/skew guard and the
|
|
# canvas_write_survived predicate), no infra. `--lib` runs all unit
|
|
# tests without the rustdoc dependency-resolution flake the full-package
|
|
# invocation hits. Enumerated explicitly because nothing in CI runs
|
|
# `cargo test --workspace` — membership buys clippy/check, not tests.
|
|
cargo nextest run -p buzz-sdk --lib
|
|
# buzz-acp owns the relay-to-agent trust boundary. Run its tests here so
|
|
# forged relay events cannot regain a path into agent routing unnoticed.
|
|
cargo nextest run -p buzz-acp
|
|
# Exercise the real MCP server for modern and legacy clients.
|
|
cargo nextest run -p buzz-dev-mcp
|
|
# buzz-db migrator/lint tests: pure SQL-parsing unit tests (no infra).
|
|
# They guard the embedded-migrator invariant (the complete checked-in
|
|
# additive migration set; legacy cutover/backfill remains an operator
|
|
# script, not startup state) and the tenant-scoping lints. The
|
|
# Postgres-backed buzz-db tests are
|
|
# #[ignore]d, so --lib runs only the infra-free set. Without this gate a
|
|
# stray file in migrations/ or a broken lint ships green.
|
|
cargo nextest run -p buzz-db --lib
|
|
# buzz-db source-policy tests: infra-free scans of the crate sources
|
|
# that enforce the event-write admission chokepoint, metric/provenance
|
|
# contracts, and their fixtures. They live in an integration-test
|
|
# binary, so `--lib` above does not run them.
|
|
cargo nextest run -p buzz-db --test observability_source
|
|
# buzz-db `AdmittedTx` doctests. The `compile_fail` cases prove code
|
|
# outside the crate can neither construct an admitted transaction nor
|
|
# pass a raw `sqlx::Transaction` to an event-write helper; nextest
|
|
# does not run doctests, hence this separate step.
|
|
cargo test -p buzz-db --doc
|
|
# Storage accounting crosses three crates whose focused regression
|
|
# suites are otherwise absent from the infra-free unit lane.
|
|
cargo nextest run -p buzz-media --lib \
|
|
-E 'test(=bucket_index::tests::bucket_snapshot_json_round_trip_preserves_community_keys)'
|
|
# buzz-admin: storage-snapshot worker plus the community archive
|
|
# command parser, validation, and propagation-evidence tests.
|
|
cargo nextest run -p buzz-admin \
|
|
-E 'test(storage_snapshot) + test(/^communities::tests::/) + test(/^tests::communities_/)'
|
|
# Multi-tenant conformance gate (buzz-conformance): the independent
|
|
# replay checker + golden fixtures. No infra — pure in-process trace
|
|
# replay — so it belongs in the unit job. Run all targets (lib + the
|
|
# tests/replay_fixtures.rs integration test), not just --lib.
|
|
cargo nextest run -p buzz-conformance
|
|
# Gateway unit and black-box HTTP tests are infra-free. Postgres-backed
|
|
# contract/race tests run in the dedicated CI job below.
|
|
cargo nextest run -p buzz-push-gateway
|
|
cargo nextest run -p buzz-push-gateway --features personal-dev-app-attest
|
|
# Kubernetes backend provider: the decision layers (state machine, GC
|
|
# planner, env precedence, naming, wire) are pure functions with a fake
|
|
# substrate, so they belong in the unit job. Enumerated explicitly
|
|
# because nothing in CI runs `cargo test --workspace` — workspace
|
|
# membership alone buys clippy/check, not a single executed test.
|
|
cargo nextest run -p buzz-backend-kubernetes
|
|
# Feature-flag crate coverage: run once with LaunchDarkly enabled.
|
|
# This includes all default tests plus the cfg(feature="launchdarkly")
|
|
# tests, avoiding duplicate default-only execution in the unit lane.
|
|
cargo nextest run -p buzz-feature-flags --features launchdarkly
|
|
# buzz-agent: two infra-free concerns run together by executing the
|
|
# whole crate (lib + integration tests), because nothing in CI runs
|
|
# `cargo test --workspace`, so without this stanza neither the crate's
|
|
# library tests nor its integration tests execute remotely.
|
|
# * model-capabilities corpus (lib): the Rust half of the
|
|
# cross-language drift guard. `model_capabilities.rs` embeds
|
|
# scripts/model-capabilities.json + scripts/normative-corpus.json via
|
|
# include_str! and replays the full locked corpus as pure in-process
|
|
# tests; without it a manifest edit that diverges Rust from the
|
|
# corpus ships green.
|
|
# * OAuth auth coordinator (lib concurrency matrix + databricks
|
|
# integration tests): lock single-flight, cooldown, cross-process
|
|
# crash recovery — infra-free via a stub OIDC provider and an
|
|
# injected browser opener, no network or Postgres.
|
|
cargo nextest run -p buzz-agent
|
|
# Admin API auth-boundary tests (api::admin in buzz-relay): the NIP-98
|
|
# duplicate-tag rejections, the Host/Origin replay-ordering causal pair,
|
|
# the admin.localhost origin/advertisement/canonical-URL pins, and the
|
|
# host-oracle/credential-first checks. These are the regression guard for
|
|
# the /api/admin/v1 moderation auth surface. Enumerated explicitly because
|
|
# nothing in CI runs `cargo test --workspace`, `just test-unit` did not
|
|
# enumerate `buzz-relay --lib`, and Backend Integration selects only the
|
|
# #[ignore]d Postgres suites — so these non-ignored tests ran in no lane
|
|
# and a red one could ship green (exactly how a broken admin test slipped
|
|
# past every gate once). Scoped to api::admin, not the whole buzz-relay
|
|
# --lib, because api::media has non-ignored tests that require Postgres.
|
|
# DB-backed api::admin tests are #[ignore]d and run in the PostgreSQL
|
|
# lane; the non-ignored ones reject before touching the database. Any
|
|
# new non-ignored test here must stay DB-free: without a database, a
|
|
# DB fallthrough only "passes" by waiting out the ~30s sqlx acquire
|
|
# timeout.
|
|
# The second clause adds the relay's pure authorization-decision tests:
|
|
# the NIP-29 channel membership grid (handlers::channel_authz), the
|
|
# moderation capability grid (handlers::moderation_authz), and the pure
|
|
# helpers in handlers::side_effects. They ran in NO lane before —
|
|
# `test(/^api::admin::/)` never matched them, and the PostgreSQL lane
|
|
# pairs `--run-ignored ignored-only` with a `postgres_tests::`
|
|
# default-filter — so a red one shipped green, exactly the gap the
|
|
# api::admin clause above was added to close.
|
|
# Deliberately scoped to these three modules instead of all of
|
|
# `handlers::`: the wider set is mostly Postgres-backed, and five of its
|
|
# non-postgres_tests cases only "pass" without a database by waiting out
|
|
# the ~30s sqlx acquire timeout, so they do not belong in the infra-free
|
|
# unit job either. The REQ subscription-lifecycle tests are picked by
|
|
# exact name for the same reason: the rest of handlers::req needs a DB.
|
|
# The third clause adds the NIP-FI HTTP ingress and its router/config
|
|
# neighbours: nip_fi_http, nip_fi_config, router, api::parse_query_tests,
|
|
# and the Git transport off_mode_precedence_tests. All are infra-free
|
|
# and finish in well under a second with no DATABASE_URL, so none wait
|
|
# out the sqlx acquire timeout. `--bin buzz-relay` adds main.rs's
|
|
# `tests::` module (JWKS refresh cadence) and `composition_tests::`
|
|
# (JWKS source + refresh-loop composition), which `--lib` cannot reach
|
|
# because they live in the binary target; the nested
|
|
# `tests::postgres_tests::` stays in the PostgreSQL lane.
|
|
# The fourth clause adds the startup step timers (startup_steps) and the
|
|
# env- and log-capture tests in telemetry and config. All three modules
|
|
# are infra-free; config parses env into a struct and never connects.
|
|
# `^config::` is anchored so it does not also select nip_fi_config.
|
|
cargo nextest run -p buzz-relay --lib --bin buzz-relay \
|
|
-E 'test(/^api::admin::/) + test(/^handlers::channel_authz::/) + test(/^handlers::moderation_authz::/) + test(/^handlers::side_effects::tests::/) + test(/^storage_sweep::tests::/) + test(/^nip_fi_core::tests::/) + test(/^nip_fi_http::tests::/) + test(/^nip_fi_config::tests::/) + test(/^readiness::tests::/) + test(/^router::tests::/) + test(/^api::parse_query_tests::/) + test(/^api::git::transport::off_mode_precedence_tests::/) + test(/^audio::join::tests::/) + test(/^audio::handler::tests::/) + test(/^nip_fi_gate::tests::/) + test(/^nip_fi_session::tests::/) + test(/^nip_fi_shadow::tests::/) + test(/^nip_fi_shadow_session::tests::/) + test(/^startup_steps::tests::/) + test(/^telemetry::tests::/) + test(/^config::tests::/) + test(=state::tests::neither_a_confirmed_inactive_community_nor_a_failed_lookup_admits_the_socket) + test(=state::tests::periodic_revalidation_disconnects_inside_the_fenced_callback) + test(=state::tests::bare_community_disconnect_fails_closed_when_the_fence_is_unavailable) + test(=connection::tests::send_loop_sends_policy_close_when_community_is_archived) + test(=state::tests::community_disconnect_then_nip_fi_keeps_community_deleted_reason) + test(=state::tests::disconnect_community_wins_reason_losing_nip_fi_does_not_enqueue_frame) + test(=handlers::req::tests::timed_out_historical_read_deregisters_before_closed) + test(=handlers::req::tests::superseded_timeout_leaves_replacement_intact) + test(=handlers::req::tests::search_claim_retires_live_and_yields_to_replacement) + test(=handlers::req::tests::concurrent_claims_and_stale_teardowns_keep_the_last_owner) + test(=handlers::req::tests::timeout_closed_is_emitted_before_a_replacement_can_claim) + test(=handlers::req::tests::revoke_then_replacement_keeps_replacement_whole) + test(=handlers::req::tests::claims_after_connection_cleanup_are_refused) + test(=handlers::req::tests::dropped_terminal_frame_cancels_connection) + test(=handlers::req::tests::revoke_dropped_terminal_frame_cancels_connection) + (kind(bin) & (test(/^tests::/) + test(/^composition_tests::/) + test(/^env_filter_tests::/)) - test(/^tests::postgres_tests::/))'
|
|
# Note on audio::join::tests scope: the full suite is infra-free (no DB,
|
|
# no Redis). The infra-free audio/FI regression witnesses — bootstrap
|
|
# ordering barrier, CommitConfirmed arm, pending-close invisibility,
|
|
# abnormal-stream-close fanout, and never-ready-sink writer witnesses —
|
|
# are all selected by audio::join::tests and audio::handler::tests.
|
|
# DB-backed audio join tests use #[ignore] and run in the postgres lane.
|
|
# NIP-FI (S3/S4) relay witnesses: the wholly-new nip_fi_upgrade and
|
|
# api::nip_fi modules,
|
|
# the auth metrics contract module, plus the exact NIP-FI tests added,
|
|
# or whose assertions changed, in mixed modules (audio::room,
|
|
# connection, handlers::*, state). nip_fi_config and router are
|
|
# selected whole by the command above. Mixed modules are listed by exact
|
|
# name so main's unselected tests (several wait out the ~30s sqlx
|
|
# acquire timeout) stay out. NIP-FI tests that need Postgres live in
|
|
# postgres_tests and
|
|
# run in the PostgreSQL lane. Keep scripts/run-tests.sh in step.
|
|
cargo nextest run -p buzz-relay --lib -E '
|
|
test(/^nip_fi_upgrade::/)
|
|
+ test(/^metrics::contract_tests::/)
|
|
+ test(=audio::room::tests::roster_revisions_are_ordered_and_snapshot_is_authoritative)
|
|
+ test(=connection::tests::auth_lifecycle_reconciles_every_terminal_and_never_leaks_gauge)
|
|
+ test(=audio::room::tests::b1_pending_peer_removed_before_commit_emits_no_delta)
|
|
+ test(=audio::room::tests::b2_commit_peer_emits_exactly_one_joined_delta_and_marks_visible)
|
|
+ test(=audio::room::tests::b3_commit_peer_revision_is_monotone_between_concurrent_events)
|
|
+ test(=audio::room::tests::f7a_pending_peer_excluded_from_snapshot_until_committed)
|
|
+ test(=connection::tests::b2_cancelled_connection_event_frame_not_dispatched)
|
|
+ test(=connection::tests::b3_expiry_denial_precedes_close_through_send_loop)
|
|
+ test(=connection::tests::b3_root_pairing_denial_precedes_close_through_send_loop)
|
|
+ test(=connection::tests::cancellation_during_select_with_fi_denial_routes_through_bounded_path)
|
|
+ test(=connection::tests::cancelled_never_ready_sink_with_queued_fi_denial_exits_within_timeout)
|
|
+ test(=connection::tests::deadline_exp_is_earliest_selects_exp)
|
|
+ test(=connection::tests::deadline_max_connection_lifetime_is_earliest_selects_partition)
|
|
+ test(=connection::tests::deadline_no_lifetime_returns_upstream_only)
|
|
+ test(=connection::tests::expiry_notice_queued_on_ctrl_before_cancel)
|
|
+ test(=connection::tests::f3_root_outer_wrapper_delivers_denial_on_bootstrap_cancellation)
|
|
+ test(=connection::tests::f3_root_pre_built_expired_gate_terminates_connection)
|
|
+ test(=handlers::auth::tests::b2_pre_cancelled_connection_never_becomes_authenticated)
|
|
+ test(=handlers::auth::tests::fi_ban_check_error_emits_terminal_authorization_unavailable)
|
|
+ test(=handlers::auth::tests::fi_root_authorization_denied_rows_emit_identical_frames)
|
|
+ test(=handlers::auth::tests::fi_invalid_nip42_proof_emits_terminal_evidence_rejected)
|
|
+ test(=handlers::auth::tests::handle_auth_pairing_mismatch_runs_full_root_denial_path)
|
|
+ test(=handlers::auth::tests::shadow_root_auth_records_pairing_denial)
|
|
+ test(=handlers::auth::tests::shadow_root_invalid_nip42_retires_without_record)
|
|
+ test(=handlers::auth::tests::nip42_denial_class_separates_internal_failure_from_bad_evidence)
|
|
+ test(=handlers::event::tests::fanout_access::owner_only_kinds_keep_only_the_owner)
|
|
+ test(=handlers::event::tests::pubsub_fanout::pubsub_owner_only_kinds_reach_only_the_owner)
|
|
+ test(=handlers::event::tests::pubsub_fanout::dispatch_owner_only_kinds_reach_only_the_owner)
|
|
+ test(=handlers::req::tests::p1a_huddle_liveness_req_barrier_expiry_blocks_query_and_emission)
|
|
+ test(=state::tests::f3_cancellation_during_check_terminates_socket_without_waiting_for_check)
|
|
+ test(=state::tests::on_not_run_runs_once_on_each_deny_arm_and_never_on_admit)
|
|
+ test(=state::tests::conn_manager_disconnect_nip_fi_ignores_unproven_connection)
|
|
+ test(=state::tests::conn_manager_disconnect_nip_fi_is_issuer_scoped)
|
|
+ test(=state::tests::conn_manager_disconnect_nip_fi_sets_authorization_denied_reason)
|
|
+ test(=state::tests::nip_fi_disconnect_audio_is_issuer_scoped)
|
|
+ test(=state::tests::nip_fi_disconnect_closes_proven_audio_socket_and_sends_policy_close_reason)
|
|
+ test(=state::tests::nip_fi_disconnect_closes_target_audio_only_and_preserves_collocated_peer)
|
|
+ test(=state::tests::nip_fi_disconnect_does_not_close_different_pubkey_audio_socket)
|
|
+ test(=state::tests::nip_fi_disconnect_does_not_close_unproven_audio_socket)
|
|
+ test(=state::tests::community_disconnect_then_nip_fi_keeps_community_deleted_reason)
|
|
+ test(=state::tests::disconnect_community_wins_reason_losing_nip_fi_does_not_enqueue_frame)
|
|
+ test(=state::tests::manager_disconnect_sets_reason_enqueues_frame_then_cancels)
|
|
+ test(/^api::nip_fi::/)'
|
|
# boot_lifecycle spawns the real relay binary and asserts its startup
|
|
# lifecycle, including that buzz_startup_phase_* reaches /metrics. Its
|
|
# non-ignored tests need no Postgres or Redis; the Postgres cases are
|
|
# #[ignore]d and stay in the PostgreSQL lane. Keep
|
|
# scripts/run-tests.sh in step.
|
|
cargo nextest run -p buzz-relay --test boot_lifecycle
|
|
# ACP author-gate and queue tests protect the trust boundary between
|
|
# relay events and agent prompts. They are infra-free; ignored lifecycle
|
|
# tests remain excluded and run in their dedicated integration lanes.
|
|
cargo nextest run -p buzz-acp --lib
|
|
else
|
|
./scripts/run-tests.sh unit
|
|
fi
|
|
|
|
# Run integration tests only (starts services if needed)
|
|
test-integration:
|
|
./scripts/run-tests.sh integration
|
|
|
|
# Regenerate the model-capability normative corpus from the production Rust
|
|
# resolver. The corpus is a golden snapshot, never hand-edited: this runs the
|
|
# `#[ignore]`d writer test in buzz-agent, which serializes `resolve()` over the
|
|
# inputs-only question table to scripts/normative-corpus.json. Run this after
|
|
# any model-capabilities.json edit, then commit the regenerated file. The
|
|
# `corpus_matches_generated_snapshot` gate fails CI if the committed file drifts.
|
|
regen-model-corpus:
|
|
cargo test -p buzz-agent --lib model_capabilities::tests::regen_corpus_file -- --ignored --exact
|
|
|
|
# Buzz shared compute e2e: current desktop discovery/admission logic and
|
|
# Playwright UI coverage.
|
|
mesh-e2e:
|
|
cargo test --manifest-path {{desktop_dir}}/src-tauri/Cargo.toml --features mesh-llm mesh_llm --lib
|
|
cd {{desktop_dir}} && pnpm test:e2e:smoke -- mesh-compute.spec.ts
|
|
|
|
# Reset only development state, seed deterministic local channels, and launch
|
|
# the mesh-enabled desktop with the repository's public Tyler test identity.
|
|
# This is for local verification only; never point this identity at staging/prod.
|
|
[confirm("This will reset development data, preserve installed Buzz, then launch a seeded mesh dev app. Continue? (y/N)")]
|
|
mesh-dev-fresh:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
./scripts/dev-reset.sh --yes
|
|
./scripts/setup-desktop-test-data.sh
|
|
export BUZZ_PRIVATE_KEY="3dbaebadb5dfd777ff25149ee230d907a15a9e1294b40b830661e65bb42f6c03"
|
|
export BUZZ_REQUIRE_RELAY_MEMBERSHIP=true
|
|
export BUZZ_ALLOW_NIP_OA_AUTH=true
|
|
export RELAY_OWNER_PUBKEY="e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34"
|
|
export BUZZ_RELAY_PRIVATE_KEY="0000000000000000000000000000000000000000000000000000000000000001"
|
|
export BUZZ_RECONCILE_CHANNELS=true
|
|
export BUZZ_RESET_WEBVIEW_STATE=1
|
|
exec just mesh=1 dev
|
|
|
|
# Real serve->client->inference on this machine (not CI).
|
|
mesh-e2e-hardware:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cargo run -p buzz-mesh-smoke --example mesh_serve_client_smoke
|
|
|
|
# Three isolated node processes: trusted member joins and infers; stranger is rejected.
|
|
# Uses temp homes and explicit mesh owner keystores. Never reads the Buzz Keychain.
|
|
mesh-e2e-admission:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cargo run -p buzz-mesh-smoke --example mesh_admission_smoke
|
|
|
|
# Full hardware confidence suite: routing, owner admission, and real agent inference.
|
|
mesh-e2e-confidence:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cargo build --release -p buzz-agent -p buzz-dev-mcp
|
|
cargo run -p buzz-mesh-smoke --example mesh_serve_client_smoke
|
|
cargo run -p buzz-mesh-smoke --example mesh_admission_smoke
|
|
cargo run -p buzz-mesh-smoke --example mesh_agent_e2e
|
|
|
|
# Take desktop screenshots using the mock bridge
|
|
desktop-screenshot *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
pnpm -C {{desktop_dir}} build:e2e
|
|
cd {{desktop_dir}}
|
|
if ! curl -sf http://127.0.0.1:4173/ >/dev/null 2>&1; then
|
|
python3 -m http.server 4173 -d dist >/dev/null 2>&1 &
|
|
trap "kill $! 2>/dev/null || true" EXIT
|
|
for i in $(seq 1 20); do curl -sf http://127.0.0.1:4173/ >/dev/null && break; sleep 0.5; done
|
|
fi
|
|
node tests/helpers/screenshot.mjs {{ARGS}}
|
|
|
|
# ─── Run ──────────────────────────────────────────────────────────────────────
|
|
|
|
# Start the relay server (auto-starts Docker services if needed)
|
|
relay: bootstrap _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
set -o allexport
|
|
source .env
|
|
set +o allexport
|
|
cargo run -p buzz-relay
|
|
|
|
# Post one root + N replies (and a few reactions) to the local dev relay, for eyeballing long threads
|
|
seed-long-thread replies="187":
|
|
./scripts/seed-long-thread.sh {{replies}}
|
|
|
|
# Start the relay with the built web UI served from it
|
|
relay-web: bootstrap _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
set -o allexport
|
|
source .env
|
|
set +o allexport
|
|
[[ -d node_modules ]] || pnpm install
|
|
pnpm -C web build
|
|
BUZZ_WEB_DIR=./web/dist cargo run -p buzz-relay
|
|
|
|
# Build and run the private admin dashboard
|
|
admin: bootstrap _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
set -o allexport
|
|
source .env
|
|
set +o allexport
|
|
[[ -d node_modules ]] || pnpm install
|
|
pnpm -C admin-web build
|
|
export BUZZ_ADMIN_HOST="${BUZZ_ADMIN_HOST:-admin.localhost:3000}"
|
|
export BUZZ_ADMIN_WEB_DIR="${BUZZ_ADMIN_WEB_DIR:-{{justfile_directory()}}/admin-web/dist}"
|
|
# Default to disabled auth locally: a NIP-07 signer extension can't be
|
|
# assumed in dev. Disabled mode serves every moderation read (reports,
|
|
# feedback, restrictions, community directory, member profiles, any stored
|
|
# message by ID) to anyone who can reach the relay port and refuses writes
|
|
# and staffing, so it binds the relay to 127.0.0.1 (port from .env)
|
|
# unless the mode is exactly nip98: a padded or misspelled value stays
|
|
# local. Override per run with BUZZ_ADMIN_AUTH=nip98 (plus
|
|
# RELAY_OPERATOR_PUBKEYS or RELAY_OWNER_PUBKEY) to exercise the
|
|
# authenticated path; that keeps BUZZ_BIND_ADDR as configured.
|
|
export BUZZ_ADMIN_AUTH="${BUZZ_ADMIN_AUTH:-disabled}"
|
|
if [[ "$BUZZ_ADMIN_AUTH" != nip98 ]]; then
|
|
bind_addr="${BUZZ_BIND_ADDR:-0.0.0.0:3000}"
|
|
relay_port="${bind_addr##*:}"; [[ -n "$relay_port" ]] || relay_port=3000
|
|
export BUZZ_BIND_ADDR="127.0.0.1:${relay_port}"
|
|
fi
|
|
echo "Admin dashboard: http://${BUZZ_ADMIN_HOST}/reports"
|
|
echo "Auth mode: ${BUZZ_ADMIN_AUTH} (set BUZZ_ADMIN_AUTH=nip98 to require a signed operator)"
|
|
echo "Relay bind: ${BUZZ_BIND_ADDR:-0.0.0.0:3000}"
|
|
cargo run -p buzz-relay
|
|
|
|
# Seed deterministic reports and product feedback for local admin dashboard review
|
|
admin-seed: _ensure-migrations
|
|
./scripts/seed-admin-dashboard.sh
|
|
|
|
# Run focused relay and browser checks for the admin dashboard
|
|
admin-check: fmt-check
|
|
cargo check -p buzz-relay --all-targets
|
|
cargo test -p buzz-relay api::admin
|
|
cargo test -p buzz-relay router::tests
|
|
pnpm -C admin-web check
|
|
pnpm -C admin-web test:e2e
|
|
|
|
# Start the relay server in release mode
|
|
relay-release: bootstrap _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
set -o allexport
|
|
source .env
|
|
set +o allexport
|
|
cargo run -p buzz-relay --release
|
|
|
|
|
|
# Run the desktop Tauri app in dev mode with a local relay (ports and identity derived from worktree)
|
|
dev *ARGS: bootstrap _ensure-sidecar-stubs _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
set -o allexport
|
|
source .env
|
|
set +o allexport
|
|
bind_addr="${BUZZ_BIND_ADDR:-0.0.0.0:3000}"
|
|
relay_port="${bind_addr##*:}"; [[ -n "$relay_port" ]] || relay_port=3000
|
|
health_port="${BUZZ_HEALTH_PORT:-8080}"
|
|
metrics_port="${BUZZ_METRICS_PORT:-9102}"
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
for spec in "relay:$relay_port" "health:$health_port" "metrics:$metrics_port"; do
|
|
name="${spec%%:*}"; port="${spec##*:}"
|
|
if lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
echo "Error: $name port $port is already in use; refusing to launch desktop against a stale relay." >&2
|
|
lsof -nP -iTCP:"$port" -sTCP:LISTEN >&2 || true
|
|
echo "Stop the process above (often a stale buzz-relay) and rerun: just dev" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
cargo build -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr -p buzz-relay
|
|
# Docker Desktop's forwarded MinIO port can stall under the deployment
|
|
# probe's 32 concurrent writers. Keep the gate enabled in local dev, using
|
|
# the bounded profile already used by the relay test launcher.
|
|
export BUZZ_GIT_PROBE_WRITERS="${BUZZ_GIT_PROBE_WRITERS:-8}"
|
|
export BUZZ_GIT_PROBE_ROUNDS="${BUZZ_GIT_PROBE_ROUNDS:-2}"
|
|
./target/debug/buzz-relay &
|
|
RELAY_PID=$!
|
|
cleanup() {
|
|
[[ -n "${INSTANCE_ID:-}" ]] && ../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true
|
|
kill "$RELAY_PID" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
relay_ready=false
|
|
for _ in $(seq 1 120); do
|
|
if ! kill -0 "$RELAY_PID" 2>/dev/null; then
|
|
echo "Error: buzz-relay exited during startup; refusing to launch desktop." >&2
|
|
wait "$RELAY_PID" || true
|
|
exit 1
|
|
fi
|
|
if curl --silent --fail --max-time 1 "http://127.0.0.1:${health_port}/_readiness" >/dev/null; then
|
|
relay_ready=true
|
|
break
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
if [[ "$relay_ready" != true ]]; then
|
|
echo "Error: buzz-relay did not become healthy within 60 seconds; refusing to launch desktop." >&2
|
|
exit 1
|
|
fi
|
|
cd {{desktop_dir}}
|
|
[[ -d node_modules ]] || pnpm install
|
|
source ../scripts/instance-env.sh
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
echo "Starting on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
FEATURES=(); [[ -n "{{mesh}}" ]] && FEATURES=(--features mesh-llm)
|
|
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run only the desktop app. No relay, database, Docker, migrations, or .env are needed.
|
|
# The app opens normally and asks for a community before making a relay connection.
|
|
desktop-standalone *ARGS: _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
cargo build -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
|
for bin in buzz-acp buzz-agent buzz-backend-kubernetes buzz-dev-mcp git-credential-nostr buzz; do
|
|
cp "${TARGET_DIR}/debug/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
done
|
|
cd {{desktop_dir}}
|
|
[[ -d node_modules ]] || pnpm install
|
|
unset BUZZ_PRIVATE_KEY BUZZ_SHARE_IDENTITY
|
|
if [[ -n "{{fresh}}" ]]; then
|
|
export BUZZ_RESET_WEBVIEW_STATE=1
|
|
fi
|
|
source ../scripts/instance-env.sh
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
export BUZZ_DEV_KEYRING_SERVICE="buzz-desktop-dev.${BUZZ_INSTANCE_SLUG:-main}"
|
|
if [[ -n "{{fresh}}" ]]; then
|
|
../scripts/reset-desktop-standalone-state.sh "$INSTANCE_ID" "$BUZZ_DEV_KEYRING_SERVICE"
|
|
fi
|
|
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
|
echo "Starting standalone desktop on Vite port ${BUZZ_VITE_PORT}; no relay services were started"
|
|
FEATURES=()
|
|
if [[ -n "{{mesh}}" ]]; then
|
|
FEATURES=(--features mesh-llm)
|
|
fi
|
|
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run the desktop app against the internal staging relay (installs deps + builds agent tools automatically)
|
|
staging *ARGS: bootstrap _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
pnpm install # unconditional: staging must always start with a clean dep tree
|
|
cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
|
|
FEATURES=()
|
|
if [[ -n "{{mesh}}" ]]; then
|
|
FEATURES=(--features mesh-llm)
|
|
fi
|
|
# Replace 0-byte sidecar stubs with real binaries so tauri dev picks them up.
|
|
# buzz: the CLI sidecar. buzz-backend-kubernetes: provider discovery scans the
|
|
# exe dir for executable buzz-backend-* files, so the non-executable stub that
|
|
# tauri dev copies next to the exe would hide the provider from "Run on".
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
|
STAGING_SIDECARS=(buzz)
|
|
if [[ "$TARGET" != *windows* ]]; then
|
|
STAGING_SIDECARS+=(buzz-backend-kubernetes)
|
|
fi
|
|
for bin in "${STAGING_SIDECARS[@]}"; do
|
|
cp "${TARGET_DIR}/release/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
done
|
|
cd {{desktop_dir}}
|
|
export BUZZ_RELAY_URL="wss://buzz.test.blockstaging.build"
|
|
source ../scripts/instance-env.sh
|
|
# Ctrl+C kills the Tauri app before its in-process sweep finishes, leaking
|
|
# agent workers. Reap this instance's agents on exit as a backstop.
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
|
echo "Starting staging on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run the desktop app against the production relay (installs deps + builds agent tools automatically)
|
|
production *ARGS: bootstrap _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
pnpm install # unconditional: production must always start with a clean dep tree
|
|
cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
|
|
FEATURES=()
|
|
if [[ -n "{{mesh}}" ]]; then
|
|
FEATURES=(--features mesh-llm)
|
|
fi
|
|
# Replace 0-byte sidecar stubs with real binaries so tauri dev picks them up.
|
|
# buzz: the CLI sidecar. buzz-backend-kubernetes: provider discovery scans the
|
|
# exe dir for executable buzz-backend-* files, so the non-executable stub that
|
|
# tauri dev copies next to the exe would hide the provider from "Run on".
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
|
PRODUCTION_SIDECARS=(buzz)
|
|
if [[ "$TARGET" != *windows* ]]; then
|
|
PRODUCTION_SIDECARS+=(buzz-backend-kubernetes)
|
|
fi
|
|
for bin in "${PRODUCTION_SIDECARS[@]}"; do
|
|
cp "${TARGET_DIR}/release/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
done
|
|
cd {{desktop_dir}}
|
|
export BUZZ_RELAY_URL="wss://buzz.block.builderlab.xyz"
|
|
source ../scripts/instance-env.sh
|
|
# Ctrl+C kills the Tauri app before its in-process sweep finishes, leaking
|
|
# agent workers. Reap this instance's agents on exit as a backstop.
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
|
echo "Starting production on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run the desktop frontend dev server (port derived from worktree)
|
|
desktop-dev:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cd {{desktop_dir}}
|
|
[[ -d node_modules ]] || pnpm install
|
|
source ../scripts/instance-env.sh
|
|
echo "Starting frontend dev server on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
pnpm exec vite --port "${BUZZ_VITE_PORT}" --strictPort
|
|
|
|
# ─── Web ─────────────────────────────────────────────────────────────────────
|
|
|
|
# Run the web frontend dev server (port derived from worktree to avoid collisions)
|
|
web:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
[[ -d node_modules ]] || pnpm install
|
|
source scripts/instance-env.sh
|
|
export VITE_PORT=$((BUZZ_VITE_PORT + 100))
|
|
export VITE_RELAY_URL="${BUZZ_RELAY_URL}"
|
|
echo "Starting web dev server on port ${VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
cd {{web_dir}}
|
|
pnpm exec vite --port "${VITE_PORT}" --strictPort
|
|
|
|
# Run web lint and format checks
|
|
web-check:
|
|
cd {{web_dir}} && pnpm check
|
|
|
|
# Fix web lint and format issues
|
|
web-fix:
|
|
cd {{web_dir}} && pnpm exec biome check --write .
|
|
|
|
# Run web TypeScript checks
|
|
web-typecheck:
|
|
cd {{web_dir}} && pnpm typecheck
|
|
|
|
# Build web frontend assets
|
|
web-build:
|
|
cd {{web_dir}} && pnpm build
|
|
|
|
# Run web browser smoke tests
|
|
web-e2e-smoke:
|
|
cd {{web_dir}} && pnpm test:e2e:smoke
|
|
|
|
# ─── Mobile ──────────────────────────────────────────────────────────────────
|
|
|
|
mobile_dir := "mobile"
|
|
|
|
# Install mobile Flutter dependencies
|
|
mobile-install:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter pub get
|
|
|
|
# Format all Dart code
|
|
mobile-fmt:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format .
|
|
|
|
# Fix mobile formatting and run analysis
|
|
mobile-fix:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format . && flutter analyze
|
|
|
|
# Run mobile lint and format checks
|
|
mobile-check:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format --output=none --set-exit-if-changed . && flutter analyze
|
|
|
|
# Run mobile tests
|
|
mobile-test:
|
|
/bin/bash ./scripts/test-mobile-gateway-recipes.sh
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter test --dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter test test/shared/push/push_unconfigured_build_test.dart
|
|
|
|
# Regenerate the emoji dataset asset from desktop's emoji-mart install.
|
|
# Output is committed — rerun after bumping @emoji-mart/data.
|
|
mobile-emoji-data:
|
|
node {{mobile_dir}}/scripts/generate-emoji-data.mjs
|
|
|
|
# Compile an unsigned Android debug APK (worktree-aware debug identity)
|
|
mobile-build-android:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
./scripts/mobile-worktree-overrides.sh
|
|
set -- build apk --debug --no-pub
|
|
if [[ -n "${BUZZ_PUSH_GATEWAY_URL:-}" ]]; then
|
|
set -- "$@" --dart-define="BUZZ_PUSH_GATEWAY_URL=${BUZZ_PUSH_GATEWAY_URL}"
|
|
fi
|
|
unset GIT_DIR GIT_WORK_TREE
|
|
cd {{mobile_dir}}
|
|
flutter "$@"
|
|
|
|
# Run the mobile app on iOS simulator (worktree-aware debug identity)
|
|
mobile-dev:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
if ! pgrep -x Simulator &>/dev/null; then
|
|
open -a Simulator
|
|
sleep 3
|
|
fi
|
|
./scripts/mobile-worktree-overrides.sh
|
|
gateway_url="${BUZZ_PUSH_GATEWAY_URL:-}"
|
|
overrides_file="{{mobile_dir}}/ios/Flutter/AppOverrides.xcconfig"
|
|
if [[ -z "$gateway_url" && -f "$overrides_file" ]]; then
|
|
gateway_url="$(sed -nE 's/^[[:space:]]*BUZZ_PUSH_GATEWAY_URL[[:space:]]*=[[:space:]]*(.*[^[:space:]])[[:space:]]*$/\1/p' "$overrides_file" | tail -n 1 | sed 's/\$()//g')"
|
|
fi
|
|
set -- run
|
|
if [[ -n "$gateway_url" ]]; then
|
|
set -- "$@" --dart-define="BUZZ_PUSH_GATEWAY_URL=${gateway_url}"
|
|
fi
|
|
cd {{mobile_dir}}
|
|
unset GIT_DIR GIT_WORK_TREE
|
|
flutter "$@"
|
|
|
|
# Uninstall stale worktree-suffixed Buzz debug installs (production apps kept)
|
|
mobile-clean:
|
|
./scripts/mobile-worktree-clean.sh
|
|
|
|
# ─── Database ─────────────────────────────────────────────────────────────────
|
|
|
|
# Apply database migrations
|
|
migrate: _ensure-migrations
|
|
|
|
# ─── Utilities ────────────────────────────────────────────────────────────────
|
|
|
|
# Remove build artifacts
|
|
clean:
|
|
cargo clean
|
|
cargo clean --manifest-path desktop/src-tauri/Cargo.toml
|
|
|
|
# Check the Rust workspace compiles without producing binaries
|
|
check-compile:
|
|
cargo check --workspace --all-targets
|
|
|
|
# ─── Release ─────────────────────────────────────────────────────────────────
|
|
|
|
# Read the current desktop version from package.json
|
|
get-current-version:
|
|
@node -p "require('./desktop/package.json').version"
|
|
|
|
# Read the current relay version from its crate manifest
|
|
get-current-relay-version:
|
|
@grep -m1 '^version = ' crates/buzz-relay/Cargo.toml | sed -E 's/version = "(.*)"/\1/'
|
|
|
|
# Compute next minor version (e.g., 0.3.0 → 0.4.0)
|
|
get-next-minor-version:
|
|
@python3 -c "v='$(just get-current-version)'.split('.'); print(f'{v[0]}.{int(v[1])+1}.0')"
|
|
|
|
# Compute next patch version (e.g., 0.3.0 → 0.3.1)
|
|
get-next-patch-version:
|
|
@python3 -c "v='$(just get-current-version)'.split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')"
|
|
|
|
# Compute next relay patch version (e.g., 0.3.0 → 0.3.1)
|
|
get-next-relay-patch-version:
|
|
@python3 -c "v='$(just get-current-relay-version)'.split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')"
|
|
|
|
# Update version in desktop package manifests and regenerate lockfiles
|
|
bump-desktop-version version:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# desktop/package.json
|
|
cd desktop && npm pkg set "version={{ version }}" && cd ..
|
|
# desktop/src-tauri/tauri.conf.json
|
|
node -e "
|
|
const fs = require('fs');
|
|
const p = 'desktop/src-tauri/tauri.conf.json';
|
|
const c = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
c.version = '{{ version }}';
|
|
fs.writeFileSync(p, JSON.stringify(c, null, 2) + '\n');
|
|
"
|
|
# JSON.stringify expands arrays/objects in a way biome rejects; reformat to match.
|
|
(cd desktop && pnpm exec biome format --write src-tauri/tauri.conf.json)
|
|
# desktop/src-tauri/Cargo.toml — only first version line (under [package])
|
|
node -e "
|
|
const fs = require('fs');
|
|
const p = 'desktop/src-tauri/Cargo.toml';
|
|
let t = fs.readFileSync(p, 'utf8');
|
|
t = t.replace(/^version = \".*\"/m, 'version = \"{{ version }}\"');
|
|
fs.writeFileSync(p, t);
|
|
"
|
|
# Regenerate lockfiles
|
|
pnpm install --lockfile-only
|
|
cargo update -p buzz-desktop --manifest-path desktop/src-tauri/Cargo.toml
|
|
echo "Bumped desktop manifests to {{ version }} and regenerated lockfiles"
|
|
|
|
# Bump the relay crate version and regenerate the lockfile
|
|
bump-relay-version version:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# buzz-relay carries its own `version =` (not version.workspace), so the
|
|
# replace targets the package version line only.
|
|
perl -i -pe 's/^version = ".*"/version = "{{ version }}"/' crates/buzz-relay/Cargo.toml
|
|
cargo update -p buzz-relay
|
|
echo "Bumped buzz-relay to {{ version }} and regenerated Cargo.lock"
|
|
|
|
# Open or update the desktop release PR from an immutable origin/main snapshot
|
|
release-desktop *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
ARG="{{ ARGS }}"
|
|
if [[ -z "$ARG" || "$ARG" == "patch" ]]; then
|
|
VERSION=$(just get-next-patch-version)
|
|
else
|
|
VERSION="$ARG"
|
|
fi
|
|
scripts/prepare-desktop-release.sh "$VERSION"
|
|
|
|
# Open or update the relay release PR (ghcr.io/block/buzz image)
|
|
release-relay *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
ARG="{{ ARGS }}"
|
|
if [[ -z "$ARG" || "$ARG" == "patch" ]]; then
|
|
VERSION=$(just get-next-relay-patch-version)
|
|
else
|
|
VERSION="$ARG"
|
|
fi
|
|
just _release-pr relay "$VERSION"
|
|
|
|
# Shared release-PR engine for desktop and relay. Mobile publishes immutable
|
|
# candidate tags directly from remote main instead of using metadata-only PRs.
|
|
_release-pr lane version:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
VERSION="{{ version }}"
|
|
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
|
|
echo "Error: '$VERSION' is not valid semver (expected X.Y.Z)"
|
|
exit 1
|
|
fi
|
|
# Lane-specific identifiers. The bump command runs after the branch switch.
|
|
case "{{ lane }}" in
|
|
desktop)
|
|
BRANCH_PREFIX="version-bump"
|
|
TAG_FETCH='v*'
|
|
TAG_MATCH='v[0-9]*'
|
|
TAG_EXCLUDE='*-*'
|
|
TAG_PREFIX="v"
|
|
CHANGELOG="CHANGELOG.md"
|
|
ADD_FILES=(desktop/package.json desktop/src-tauri/tauri.conf.json desktop/src-tauri/Cargo.toml desktop/src-tauri/Cargo.lock pnpm-lock.yaml CHANGELOG.md)
|
|
LOG_PATHS=(desktop/ crates/buzz-core/ crates/buzz-persona/ crates/buzz-sdk/ crates/buzz-agent/)
|
|
ARTIFACT="Buzz Desktop" ;;
|
|
relay)
|
|
BRANCH_PREFIX="relay-release"
|
|
TAG_FETCH='relay-v*'
|
|
TAG_MATCH='relay-v[0-9]*'
|
|
TAG_EXCLUDE='relay-v*-*'
|
|
TAG_PREFIX="relay-v"
|
|
CHANGELOG="crates/buzz-relay/CHANGELOG.md"
|
|
ADD_FILES=(crates/buzz-relay/Cargo.toml Cargo.lock crates/buzz-relay/CHANGELOG.md)
|
|
LOG_PATHS=(crates/buzz-relay/ crates/buzz-core/ crates/buzz-db/ crates/buzz-auth/ crates/buzz-pubsub/ crates/buzz-search/ crates/buzz-audit/ crates/buzz-media/ crates/buzz-sdk/ crates/buzz-workflow/ crates/buzz-conformance/ migrations/)
|
|
ARTIFACT="Buzz Relay" ;;
|
|
*)
|
|
echo "Error: unknown release lane '{{ lane }}'"
|
|
exit 1 ;;
|
|
esac
|
|
echo "Preparing ${ARTIFACT} release v${VERSION}..."
|
|
# Must run on main with a clean, up-to-date tree.
|
|
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
|
|
if [[ "$CURRENT_BRANCH" != "main" ]]; then
|
|
echo "Error: must be on main branch (currently on '$CURRENT_BRANCH')"
|
|
exit 1
|
|
fi
|
|
git fetch origin refs/heads/main:refs/remotes/origin/main --no-tags
|
|
# Release tags are remote-owned state; sync only this lane's tags so stale
|
|
# local tags from older histories do not make release preflight fail.
|
|
git fetch origin "+refs/tags/${TAG_FETCH}:refs/tags/${TAG_FETCH}"
|
|
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then
|
|
echo "Error: local main is not up-to-date with origin/main. Run 'git pull' first."
|
|
exit 1
|
|
fi
|
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
echo "Error: working tree is dirty. Commit or stash changes first."
|
|
exit 1
|
|
fi
|
|
# Switch to the release branch (create, or reset to main if it exists).
|
|
BRANCH="${BRANCH_PREFIX}/${VERSION}"
|
|
if git rev-parse --verify "refs/heads/$BRANCH" >/dev/null 2>&1; then
|
|
echo "Branch '$BRANCH' already exists — resetting to origin/main..."
|
|
git switch "$BRANCH"
|
|
git reset --hard origin/main
|
|
elif git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
|
|
echo "Branch '$BRANCH' exists on remote — checking out and resetting to origin/main..."
|
|
git switch -c "$BRANCH" --track "origin/$BRANCH"
|
|
git reset --hard origin/main
|
|
else
|
|
git switch -c "$BRANCH"
|
|
fi
|
|
# Lane-specific bump (the one diverging step).
|
|
case "{{ lane }}" in
|
|
desktop) just bump-desktop-version "$VERSION" ;;
|
|
relay) just bump-relay-version "$VERSION" ;;
|
|
esac
|
|
# Generate the changelog from commits since this lane's last release tag.
|
|
LAST_TAG=$(git describe --tags --abbrev=0 --match "$TAG_MATCH" --exclude "$TAG_EXCLUDE" 2>/dev/null || echo "")
|
|
REPO=$(git remote get-url origin | sed -E 's|.*github\.com[:/]||; s|\.git$||')
|
|
format_log() {
|
|
local range="$1"
|
|
git log "$range" --format="%h %H %s" --no-merges -- "${LOG_PATHS[@]}" | while IFS=' ' read -r short full rest; do
|
|
local pr subject
|
|
pr=$(printf '%s' "$rest" | grep -oE '\(#[0-9]+\)$' | grep -oE '[0-9]+' || true)
|
|
if [[ -n "$pr" ]]; then
|
|
subject=$(printf '%s' "$rest" | sed -E 's/ \(#[0-9]+\)$//')
|
|
printf -- '- %s ([#%s](https://github.com/%s/pull/%s)) ([`%s`](https://github.com/%s/commit/%s))\n' \
|
|
"$subject" "$pr" "$REPO" "$pr" "$short" "$REPO" "$full"
|
|
else
|
|
printf -- '- %s ([`%s`](https://github.com/%s/commit/%s))\n' \
|
|
"$rest" "$short" "$REPO" "$full"
|
|
fi
|
|
done
|
|
}
|
|
TMPFILE=$(mktemp)
|
|
{
|
|
echo "# Changelog"
|
|
echo ""
|
|
echo "## ${TAG_PREFIX}${VERSION}"
|
|
echo ""
|
|
if [[ -n "$LAST_TAG" ]]; then
|
|
format_log "${LAST_TAG}..HEAD"
|
|
else
|
|
echo "- Initial release"
|
|
fi
|
|
echo ""
|
|
if [[ -f "$CHANGELOG" ]]; then
|
|
tail -n +2 "$CHANGELOG"
|
|
fi
|
|
} > "$TMPFILE"
|
|
mkdir -p "$(dirname "$CHANGELOG")"
|
|
mv "$TMPFILE" "$CHANGELOG"
|
|
# Commit.
|
|
git add "${ADD_FILES[@]}"
|
|
RELEASE_MSG="chore(release): release ${ARTIFACT} version ${VERSION}"
|
|
if [[ "$(git log -1 --format='%s' 2>/dev/null)" == "$RELEASE_MSG" ]]; then
|
|
git commit --amend --no-edit
|
|
else
|
|
git commit -m "$RELEASE_MSG"
|
|
fi
|
|
# Push and open/update the PR.
|
|
git push --force-with-lease -u origin "$BRANCH"
|
|
PR_BODY="## ${ARTIFACT} release v${VERSION}"$'\n\n'
|
|
if [[ -n "$LAST_TAG" ]]; then
|
|
PR_BODY+="### Changes since ${LAST_TAG}:"$'\n\n'
|
|
CHANGELOG_BODY=$(format_log "${LAST_TAG}..HEAD~1")
|
|
MAX_LOG=62000
|
|
if (( ${#CHANGELOG_BODY} > MAX_LOG )); then
|
|
TRUNCATED=$(printf '%s' "$CHANGELOG_BODY" | awk -v max="$MAX_LOG" \
|
|
'BEGIN{n=0} {line_len=length($0)+1; if(n+line_len>max) exit; n+=line_len; print}')
|
|
SHOWN=$(printf '%s\n' "$TRUNCATED" | grep -c '^-' || true)
|
|
TOTAL=$(printf '%s\n' "$CHANGELOG_BODY" | grep -c '^-' || true)
|
|
SKIPPED=$(( TOTAL - SHOWN ))
|
|
CHANGELOG_BODY="${TRUNCATED}"$'\n'"_… and ${SKIPPED} more commits — [compare ${LAST_TAG}…${TAG_PREFIX}${VERSION}](https://github.com/${REPO}/compare/${LAST_TAG}...${TAG_PREFIX}${VERSION})_"
|
|
fi
|
|
PR_BODY+="${CHANGELOG_BODY}"$'\n\n'
|
|
else
|
|
PR_BODY+="Initial release."$'\n\n'
|
|
fi
|
|
PR_BODY+="**To release:** merge this PR. The tag and build will happen automatically."
|
|
PR_TITLE="chore(release): release ${ARTIFACT} version ${VERSION}"
|
|
EXISTING_PR=$(gh pr list --head "$BRANCH" --json url --jq '.[0].url' 2>/dev/null || true)
|
|
if [[ -n "$EXISTING_PR" ]]; then
|
|
gh pr edit "$BRANCH" --title "$PR_TITLE" --body "$PR_BODY"
|
|
PR_URL="$EXISTING_PR"
|
|
echo ""
|
|
echo "Updated existing release PR: ${PR_URL}"
|
|
else
|
|
PR_URL=$(gh pr create --title "$PR_TITLE" --body "$PR_BODY")
|
|
echo ""
|
|
echo "Release PR opened: ${PR_URL}"
|
|
fi
|
|
echo "Merge it to trigger the release build."
|
|
|
|
# ─── Agent Harness ────────────────────────────────────────────────────────────
|
|
|
|
# Run a goose agent connected to a Buzz relay (foreground)
|
|
goose relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$BUZZ_PRIVATE_KEY":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
source ./scripts/_goose-env.sh "{{relay}}" "{{key}}" "{{agents}}" "{{heartbeat}}" "{{prompt}}"
|
|
exec env "${env_args[@]}" ./target/release/buzz-acp
|
|
|
|
# Run a goose agent in the background (screen session named 'goose-agent-N')
|
|
goose-bg relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$BUZZ_PRIVATE_KEY":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
source ./scripts/_goose-env.sh "{{relay}}" "{{key}}" "{{agents}}" "{{heartbeat}}" "{{prompt}}"
|
|
screen -dmS goose-agent-{{agents}} bash -c "$(printf '%q ' env "${env_args[@]}") ./target/release/buzz-acp"
|
|
echo "Agent running in screen session 'goose-agent-{{agents}}'. Attach with: screen -r goose-agent-{{agents}}"
|
|
|
|
# ─── Benchmarking ─────────────────────────────────────────────────────────────
|
|
|
|
# Run the Buzz orchestra benchmark — TB defaults to leaderboard-eligible k=5; Buzz task layers default to regression k=1 and workflow k=3. Stands up its own Docker stack; --gui opens a live spectator desktop app; other flags pass to benchmark.py (--dataset/--path, --layer, --include-task, --attempts, --manifest, --dry-run, ...)
|
|
benchmark *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
uv run --project benchmarks/harbor-buzz-orchestra/testbed \
|
|
benchmarks/harbor-buzz-orchestra/scripts/benchmark.py {{ARGS}}
|
|
|
|
# Run the benchmark adapter + testbed gate exactly as CI does (pytest + ruff, pinned ruff from pyproject)
|
|
benchmark-check:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cd "{{justfile_directory()}}/benchmarks/harbor-buzz-orchestra"
|
|
# CI installs the dev extra with pip, so pyproject — not uv.lock — decides
|
|
# which ruff lints. Read the pin from there so this recipe cannot drift
|
|
# from the workflow (a floating specifier once meant CI failed on RUF100
|
|
# while the locked local ruff passed).
|
|
ruff_pin="$(grep -oE 'ruff==[0-9.]+' pyproject.toml | head -1 | cut -d= -f3)"
|
|
for project in . testbed; do
|
|
(
|
|
cd "$project"
|
|
echo "── harbor-buzz-orchestra/$project (ruff $ruff_pin)"
|
|
uv run --frozen pytest -q
|
|
uvx "ruff@$ruff_pin" check .
|
|
uvx "ruff@$ruff_pin" format --check .
|
|
)
|
|
done
|
|
# The task verifiers live in the sibling benchmarks/buzz-dataset, so they
|
|
# need the harness config passed explicitly to stay linted.
|
|
echo "── buzz-dataset (ruff $ruff_pin)"
|
|
uvx "ruff@$ruff_pin" check --config pyproject.toml ../buzz-dataset
|
|
uvx "ruff@$ruff_pin" format --check --config pyproject.toml ../buzz-dataset
|
|
|
|
# Stop the benchmark Docker stack (state and channels are kept)
|
|
benchmark-down:
|
|
docker compose --project-name buzz-benchmark down
|