Commit Graph
143 Commits
Author SHA1 Message Date
83a285f1b1 ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524)
## Summary

Official Linux desktop packages (`.deb` / AppImage) are built without
`--features mesh-llm`, so they ship the `mesh_llm_stubs` backend and
Settings → Compute always fails with `mesh-llm feature not enabled`.
This PR adds the feature flag to the two Linux build commands:

- `release.yml` → `release-linux` job
- `linux-canary.yml` → canary build

That's the whole diff — 2 lines. Fixes #3788 (Linux); see also #3841
(dup with UI-gating PR #3914) and the Windows twin #2836/#3223.

## Why no native prebuild step (unlike the macOS job)

The macOS job carries Metal llama prebuild/cache steps from #798. Linux
doesn't need an equivalent:

- `mesh-llm-host-runtime` is compiled with `dynamic-native-runtime` and
installs the recommended runtime on first use (verified by sha256
checksum over HTTPS; upstream's signature verification path is not yet
implemented — default policy is `RequireChecksum`, per
`mesh-llm-runtime-install/src/lib.rs`)
(`desktop/src-tauri/src/mesh_llm/mod.rs` —
`initialize_mesh_native_runtime`), so release builds work on clean
machines without bundling llama.cpp.
- Upstream publishes Linux x86_64/aarch64 runtime bundles for the pinned
`v0.74.0` line, and `scripts/ensure-mesh-native-runtime.sh` already maps
`meshllm-native-runtime-linux-x86_64-cpu` / `linux-aarch64-cpu` for
local/e2e use.
- The unmerged branch `micn/mesh-node-download` (`96f29417a`) treats
even the macOS prebuild steps as removable dead weight for the same
reason.

## Background

The omission is historical drift, not a decision: Linux packaging
predates the mesh feature flag (#693), mesh became opt-in for
build-cost/reliability reasons (#823, #1183), and #1221 re-enabled it
for releases by editing only the macOS build line. `release-linux` and
the later `linux-canary` copy were never revisited.

The mesh shutdown hard-exit/relaunch path is gated `all(mesh-llm,
target_os = "macos")` because ggml/Metal destructors abort on macOS;
ordinary mesh shutdown (`shutdown_mesh_runtime`) is cross-platform, so
Linux falls through to the generic path.

## Validation

- [x] `./bin/cargo check --manifest-path desktop/src-tauri/Cargo.toml
--features mesh-llm` green at base `2c0ac2467` (feature graph compiles
at the pinned v0.74.0 line)
- [ ] Linux canary run with this change: AppImage/.deb build succeeds
and binary contains real `mesh_llm` symbols (not `mesh_llm_stubs`)
- [ ] Installed package: cold-start → Settings → Compute → runtime
download → serve → clean shutdown

The last two need a Linux run/host. **Note (from review):**
`linux-canary.yml` is `workflow_dispatch`-only and its `Require main`
step rejects non-main refs, so the canary cannot run on this branch
pre-merge — and `.github/workflows/**` matches no ci.yml paths-filter,
so this PR's own CI does not exercise the changed lines. Validation
sequencing is therefore merge → dispatch linux-canary on main →
live-package pass, with a trivial 2-line revert as the escape hatch.

Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-03 10:14:01 -04:00
6530b58a61 feat(k8s): Kubernetes backend plugin + desktop deploy path (#4289)
# Kubernetes backend plugin (crates/buzz-backend-kubernetes) + desktop
deploy path

Implements docs/remote-agents.md (merged @ 28ae6cd21) as ONE PR: the
provider
binary, the desktop changes that make it work, the harness inactivity
reaper,
the Sprig image, and the conformance/live-test suites.

Channel: buzz-remote-agents (29414326-dba7-402d-b384-b1b34d63a2e6),
thread c42b70ef.

## What's here (by lane)
- **crates/buzz-backend-kubernetes** (Dawn): stdin/stdout JSON provider,
info +
  deploy; pure classify.rs (one match arm per spec state-machine row);
reconcile/GC with ownership-marker gate + same-clock orphan check;
per-attempt
immutable Secrets; three-tier env with clear-then-write authoritative
tier.
- **Desktop** (Mari): KD3 launch block from resolved descriptor, KD5
pre-secret
negotiation gate (resolve-once → stage-and-digest → info → protocol gate
→
deploy), KD1 Windows extension strip, bundling (externalBin + Justfile +
release/canary workflows + stub loops), tauri.windows.conf.json platform
  override (Decision B: no Windows artifact).
- **buzz-acp** (Max): KD4 BUZZ_ACP_EXIT_AFTER_INACTIVITY reaper
(pool-independent;
reset only at accepted dispatch; in-flight turn/heartbeat defers, never
resets);
BUZZ_ACP_EXIT_AFTER_INACTIVITY + BUZZ_ACP_NO_PRESENCE reserved. KD8 fix.
- **Image + tests** (Perci): Dockerfile.sprig (digest-pinned bases, exec
buzz-acp
PID 1, relay-scoped credential config), image contract script, provider
conformance suites (golden wire fixtures shared with desktop tests),
live-local
  runbook (namespace-scoped, shared-cluster safe).
- **Docs** (Sami, first commit): citation re-pin c1bca1b56 → 28ae6cd21
(44/49
were already byte-exact; 3 offsets fixed) + I3 presence-bound correction
(below).

## Named spec deviations (deliberate, each with rationale)
1. **No baked default image yet.** ghcr.io/block/buzz-sprig is
unpublished
(verified: anonymous pull 403 vs control 200). Omitted `image` returns
an
   in-band field-required error instead of a default.
2. **Image override STRICTER than spec §Image:** digest-only
(`name@sha256:<64hex>`); ALL tags rejected; `name:tag@digest`
normalized.
With no baked default the override is the only path, so tag-acceptance
would
make mutability the v1 norm. Strictness is reversible; a moved tag under
an
nsec is not. Baked digest default + tag re-acceptance = follow-up with
image
   publish.
3. **imagePullSecrets not in schema (v1).** Explicit user images may
rely on
namespace-preprovisioned pull credentials — the substrate boundary.
Field
added only if the publish decision proves it necessary. 9-field budget
intact.
4. **Decision A closed: writable empty workspace.** Nest projection =
named
   follow-up; no image-side scaffolding.
5. **Decision D overridden by Tyler (event b55398d8):** provider ships
bundled
with the desktop like buzz-acp/buzz-agent; spec §Distribution's separate
   release workflow deleted for v1.
6. **I3/vision presence bound corrected 90s → 180s.** PRESENCE_TTL_SECS
moved in
#3783 during this spec's base→merge window; the number was inherited,
not
chosen. Spec :206/:216/:928 + inline quote + VISION_REMOTE_AGENTS.md:59
corrected. ← Tyler: the vision is your document; this edit is flagged
for
   your explicit eyes.
7. **Spec citations are pinned to 28ae6cd21** (main at spec merge) and
resolve
   there, not at this PR's head — this PR's own lanes move
crates/buzz-acp/src/lib.rs by ~100 lines (19 citations across
KD4/KD6/KD7/
§Stop/§Launch data). Known Defects rows fixed BY this PR retire on
merge;
   the section documents main as of the pin.
8. **KD7 grace tension declared:** pod terminationGracePeriodSeconds=60
vs
KD7's measured ~87s shutdown tail at parallelism 10 (~197s at cap 32).
   KD7 is ruled out of scope, so L1-3's "enough grace for full graceful
shutdown" is NOT met at default config — deliberate, resolved by the KD7
   follow-up, not silently.

## Question for Tyler
Will ghcr.io/block/buzz-sprig publish PUBLIC? If private-by-policy,
§Image needs
an imagePullSecrets story before the baked-default follow-up can land.

## Out of scope (named follow-ups)
KD6 exit-code contract + KD7 shutdown budget (gate OnFailure), OnFailure
restart
policy, Windows provider binary, PVCs/nest projection, mesh
deployability,
sprig image publish workflow + baked multi-arch digest default.

## Reproduce locally (four traps that cost us real time)

**1. Git hooks inherit the invoking shell's PATH — pin the shell, not
just your
verification commands.** `rust-toolchain.toml` pins `1.95.0`, but the
rustup shim
that honors that pin lives in `~/.cargo/bin`. If Homebrew's cargo is
earlier on
PATH, `cargo` in this repo is 1.89.0, which cannot build the workspace
at all:

```
$ /opt/homebrew/bin/cargo check -p buzz-db
error: rustc 1.89.0 is not supported by the following packages:
  sqlx@0.9.0 requires rustc 1.94.0
  ...                                                    # exit 101
```

Verifying with `PATH="$HOME/.cargo/bin:$PATH" cargo test` does *not*
protect the
push: lefthook's `pre-push` → `just test-unit` re-resolves `cargo` from
the
shell's own PATH, so a green local run is followed by a hook failure on
a crate
you never touched. Export the PATH for the whole shell, not per-command.
This
bit twice.

**2. Line-scope your mutations, or the mutation edits its own
detector.** When
mutation-testing the respond-to guard, a whole-file `sed` on the mode
literal
touches 5 sites — the guard *and* the fixtures/assertions that test it.
The
mutation and its detector move together and the suite stays green, which
reads
as "this code is dead" when it actually means "you deleted the
experiment":

```
# WRONG — 5 sites, guard and tests mutate together
$ sed -i '' 's/"allowlist"/"allowlist-DISABLED"/g' src/env.rs
test result: ok. 145 passed; 0 failed          # false survivor

# RIGHT — 1 site, anchored to the guard's own definition line
$ sed -i '' '/^const RESPOND_TO_ALLOWLIST/s/"allowlist"/"allowlist-DISABLED"/' src/env.rs
failures:
    env::tests::allowlist_mode_with_an_empty_list_is_refused
    env::tests::an_allowlist_entry_that_is_not_64_hex_is_refused
test result: FAILED. 143 passed; 2 failed      # real kill
```

Restore by copying a pristine file back and confirming `git diff --stat`
is
empty, not by re-running an inverse `sed`.

**3. A completeness guard is not a correctness guard.** The shared wire
fixture
`tests/fixtures/provider-wire/deploy-full-launch.request.json` passed
every test
we had while containing four classes of invented data (wrong
`respond_to`
encoding, an env key no emitter writes, allowlist entries that fail the
harness's own 64-hex rule, a `launch.env` key from no descriptor layer).
The
provider's tests could not have caught this: its types are deliberately
indifferent to these values (`Option<String>`, `Vec<String>`, arbitrary
map), so
"the provider parses it" was never evidence that the desktop emits it.
The fix
was not a stronger provider assertion but a rule about provenance —
"recorded"
means executed-and-transcribed, and the desktop's whole-object equality
test is
the only enforcement that can exist. See the fixture README.

**4. Every drift this arc was a value that agreed with itself.** Five
invented
values were found, and not one was caught by an assertion failing — each
was
caught by someone asking where a value came from. A named constant
referenced
symbolically on both the fixture and assertion side. A `sed` that
mutated its
own detector. Six probe rows that all died at the same unrelated error.
A
descriptor struct literal compared against a fixture built from that
literal
(`launch.args: ["run","--session"]`, which the resolver actually returns
as
`["acp"]`). The general defense is not more assertions but provenance: a
stub is
a control that varies nothing, and the more faithful it looks the better
it
hides. Ask what executed, not what passed.

*Fixture-test determinism caveat (post-verification, Quinn + Dawn).* The
desktop's whole-object fixture test calls the real resolver, which
consults a
process-global harness registry whose own docs require
`registry_test_lock`
for any test touching it. The fixture test holds no lock and is
nonetheless
deterministic — but by containment, not by ordering. Measured, not
derived:
planting a definition with `id: "goose"` directly into the registry
(bypassing
the loader) changes the resolved descriptor from `args: ["acp"]` to
`args: ["--poisoned"]`, so `resolve_effective_harness_descriptor`
**does**
reach the registry for this id — it does not short-circuit on the
builtin
table first. Two controls discriminate: an empty registry and a registry
poisoned under a *different* id both return `["acp"]`. What actually
protects
the test is that the registry has exactly one writer
(`update_loaded_harness_registry`, reached only via
`warm_harness_registry_from_dir`) — but that writer concatenates **two**
sources of unequal strength (`custom_harnesses.rs:319-326`). Custom
files
pass through `load_custom_harnesses`, whose `check_id_collision` rejects
the
reserved builtin id `goose` case-insensitively at the loader — and that
leg
is tested (`load_applies_id_collision_check` writes a real `goose.json`
and
asserts the loader drops it). Preset definitions
(`preset_harness_definitions`, `presets.rs:177-193`) are a bare `.map`
over
`PRESET_HARNESSES` with **no collision check** — exhaustive call-site
enumeration at `60007fda4` finds four production `check_id_collision`
sites,
none on the preset path. That leg holds only because `goose` is not in
the
preset table today (intersection of TIER1 and preset ids is empty) —
executed, not just read: adding a preset with `id: "goose"`,
`args: ["--poisoned"]` and warming via the normal preset-only path
(`warm_harness_registry_from_dir(None)`, no custom dir, no direct
writer)
flips the fixture's emitted `launch.args` from `["acp"]` to
`["--poisoned"]`
at `60007fda4`, command/env/policy_env unchanged. So: no test in the
suite
can put a `goose` entry in the registry
via the custom path, and no preset currently carries one, so no
interleaving
can perturb this fixture — containment with one checked leg and one
coincidental one. A future fixture built on a **non-builtin** runtime id
has
no containment at all — it would be order-dependent against whatever
registry-writing test ran last and must take the lock.

*Late instance, found while reviewing the mode guard.* The guard
exact-matches
`respond_to` untrimmed and case-sensitively, which is only correct if
clap's
`ValueEnum` derive is case-sensitive. `config.rs` gives two answers: the
derive
at `:448-453` carries no `ignore_case`, while the crate's own tests call
`RespondTo::from_str(s, true)` — `ignore_case = true`. Reading the
source
supports either. Measured on the built binary instead: `owner-only`
starts,
`OWNER-ONLY` / `Owner-Only` / `ALLOWLIST` / `NOBODY` all exit rc=2
`invalid
value`. Case-sensitive at the CLI, so the guard is right — and right for
a
reason the source does not state. The `from_str(_, true)` tests exercise
a
different surface and are not evidence about the CLI.

*Corollary, and the sharper half.* When a test helper **reimplements**
production instead of calling it, the helper is a fork — and a fork can
be
right while production is wrong, or wrong in the same way, and the suite
reports green either way. Both `BUZZ_ACP_ALLOWED_*` gates are forked
like this:
production compares **strings** while the helpers compare **post-parse
enums**
(`config.rs:2623`) or re-derive the split
(`buzz-cli/.../channels.rs:1296`).
Production and the helper each carry their *own* copy of the empty-entry
filter
(`:1025` and `:1300`), so fixing one says nothing about the other.
Measured on
`buzz-cli`, restoring byte-exact between runs:

| tree | result |
|---|---|
| baseline | 274 passed |
| drop the empty-filter in **production** only (the real fix) | **274
passed** — no signal |
| drop it in the **test helper** only | **273 passed, 1 failed**
(`channels.rs:1338`) |

Two independent defects, stacked, and worse together than either alone:
production can be fixed with no test ever noticing, *and* the helper
cannot be
corrected without a false alarm demanding the bug back. The root cause
is one
bit of type information — `check_allowed_channel_add_policy(allowed_raw:
&str,
..)` cannot represent "unset", while production reads `env::var(..) ->
Result`,
where unset and `""` are different states. A helper whose parameter type
can't
represent all of production's input states isn't testing production's
states —
it's testing a subset it silently chose. Same family as the
struct-literal
descriptor and the fixture drift: the test and the thing it tests
agreeing with
each other, rather than the test measuring the thing. Neither defect is
in this
PR's diff (`git diff --name-only 28ae6cd21 <head> -- crates/buzz-cli` is
empty); both are now filed as NIP-34 issues on this repo: the fail-open
+
fork-helper defect at issue event `0524a4113f2d97fd…` and the respond-to
self-lock at `e32837498969b5e7…` (filed 2026-08-02 after Quinn measured
that
no prior filing existed — zero hits on GitHub `block/buzz` open *or*
closed
and zero on the relay's kind:1621 issues, against working positive
controls). The prescription was itself
mutation-tested before being written down: repairing the fork's
signature
(`Option<&str>` + assertion → `None`) still let the reintroduced
production
bug ship 274-green — an expressive fork is still a fork; it never
executes
production. So the `buzz-cli` fix has **three parts and one explicit
keep**:
drop the production filter; **delete** the helper and point its tests at
the
real `cmd_set_add_policy` (which self-discriminates by error variant —
`Usage` = refused, `Network(BadScheme)` = passed the gate — no relay
needed);
serialize the env-var tests behind one
**`tokio::sync::Mutex::const_new`**
lock taken with `.lock().await`, including the pre-existing `:1362`
integration test (the fork was silently buying test isolation — without
the
lock, parallel runs flake nondeterministically; a `std::sync::Mutex`
held
across `.await` trips `clippy::await_holding_lock` under `-D warnings`);
and
**keep** the then-dead `!allowed.is_empty()` clause with a comment
saying
why. It is unreachable-false (`split(',')` never yields an empty vec),
but it
is the only thing that keeps the reintroduced production bug detectable
—
mutation-tested: on a tree that deletes the clause, reintroducing the
empty-filter bug survives 275/0, because `""`/`","`/`" "` refuse either
way
and the filter goes semantically inert. Dead code can be load-bearing
for
tests: "provably unreachable" is an argument about behavior, never about
coverage. When a helper forks production, the fix has to delete the
fork:
any change that leaves two implementations standing can only ever be
verified against the one the tests call. *Final shape:* the keep and the
broad lock are both artifacts of the fork surviving in some form. The
extraction variant (Dawn, mutation-tested at `60007fda4`) removes the
tension: extract one `check_channel_add_policy_allowed(Option<&str>,
&str)`
that **production calls**, with the `Option` placed at the env boundary
where the `Result<String, VarError>` bit actually lives. 5/6 mutants
killed; the empty-filter survivor is proven **equivalent** (exhaustive
6174-pair check, 0 divergences, with a diverging negative control;
independently re-derived by a second generator — different tokens and
shape — 0 divergences on admitted policies, 500 on a non-admitted
control),
not a coverage hole — on a one-implementation tree there is no fork left
to
witness, so no dead clause needs keeping. One scope line on that
equivalence: it is **caller-conditional**, a property of the only
current
caller, not of the gate function — `cmd_set_add_policy`'s own match at
`:1027-1034` admits only three policies before the gate runs; a second
caller reaching the gate with arbitrary strings resurrects m1 as a real
hole. The lock does not disappear, it
narrows (Dawn's own correction, caught by Mari): lock exactly the tests
that mutate the process env — three-plus-one on a fork tree, two on the
extraction tree — behind one `tokio::sync::Mutex`, and the lock is part
of
the assertion, not hygiene: with it deleted, the gate test fails 8/8
runs
deterministically by receiving `Network(BadScheme)` where it expects
`Usage` — the unset test's `remove_var` clobbers the other's `set_var`,
and
**the gate test passes straight through the gate**, a false negative on
the
exact authz assertion the test exists to make. State it as an outcome:
these two tests must not observe each other's env writes. 276/0 stable
across 5 parallel runs, clippy `-D warnings` clean; independently
verified
(patch applied to a second worktree: result blob `d67e584be` matches the
patch index, full mutant matrix reproduces row for row). One new row no
earlier
prescription covered: collapsing unset into `Some("")` fails **closed**
—
an unconfigured deployment refuses every policy — killed by the unset
test.
Patch: `OUTBOX/BUZZ_CLI_ADD_POLICY_GATE_EXTRACT_FIX.patch`. The filed
issue
(`0524a411…`) carries the fork-shape prescription; whoever picks it up
should prefer the extraction shape, drop the dead-clause keep with it,
and
keep part 3 outcome-shaped: serialize whichever tests mutate the env.

## Verification (final HEAD `60007fda4`)
- Full touched-package suites at each integration merge (log in plan
file).
  At candidate parent `00e5b5fe9`: buzz-backend-kubernetes 154,
buzz-acp 673, desktop tauri 2100+3, pnpm 3908, workspace clippy/fmt/tsc
  all clean. The only delta to `60007fda4` is one character in
  `scripts/test-k8s-sprig-image-live.sh` (heredoc escape so the readlink
probe evaluates pod-side, not host-side at render); `crates/` tree hash
  is byte-identical at both SHAs, so the Rust receipts attach by tree
  identity. buzz-backend-kubernetes suite re-run in-shell at
  `HEAD == 60007fda4`: 154 passed.
- Adversarial one-HEAD gate (Sami): guard matrix 12/12, predicate
mutants
  7/7, doomed-invocation finding closed end-to-end; tree-hash carry to
  `60007fda4` confirmed (crates/buzz-backend-kubernetes blob unchanged).
- Live-local pass per TESTING.md + skill-buzz-testing (Perci, at
`60007fda4`): explicit `docker-desktop` context, digest-qualified image
  imported into node containerd `k8s.io` namespace, pull policy `Never`;
  pod printed `DIGEST_ABI_OK`, `resolved_spec` and `image_id` both the
  exact requested digest, script exit 0. Dedicated per-run namespace,
  ownership labels on every object, scoped cleanup verified empty after.
- Implementation review (Wren) at `60007fda4`: 9.6 minimalness /
  9.4 elegance / 9.3 correctness, no blocker.
- `origin/eva/k8s-backend` == `60007fda4` (ls-remote verified; SHA
  identity is byte identity).

---------

Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Tyler <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz>
Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz>
Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
2026-08-02 14:39:27 -04:00
5765fc74b7 fix(relay): allow open relays to set their NIP-11 workspace icon (kind:9033) (#3998)
## Problem

The desktop deliberately shows the workspace icon editor on open relays
(#2640, gate: `canEditIcon` in
`desktop/src/features/communities/ui/EditCommunityDialog.tsx`) and
defers to the relay-side kind:9033 check — which required an admin/owner
row in `relay_members`. For a community with **no admin/owner row at
all** (the `ensure_configured_community` path, which never writes an
owner), every 9033 was refused and the icon was permanently unsettable.

**Correction from review (thanks @Dawn):** the original version of this
PR claimed nobody holds a role on an open relay. That's false —
`main.rs` bootstraps `RELAY_OWNER_PUBKEY` as owner regardless of
`BUZZ_REQUIRE_RELAY_MEMBERSHIP`, so a production open relay like
bb-block *does* have an owner row, and the old gate was refusing
everyone except that owner. The first revision of this diff would have
silently widened that owner-only control to any NIP-42-authenticated
sender.

## Fix — steward-wins

`may_set_workspace_profile(sender_role, membership_enforced,
community_has_steward)`:

| Relay mode | Community has admin/owner row? | Who may set the icon |
|---|---|---|
| Closed (`require_relay_membership=true`) | any | admin or owner
(unchanged) |
| Open | yes (e.g. bb-block) | admin or owner (unchanged posture) |
| Open | no (genuinely rosterless) | any NIP-42-authenticated sender |

- New DB helper `has_admin_or_owner(community)`
(`crates/buzz-db/src/relay_members.rs`); the call site only queries it
on open relays.
- The rosterless admit logs a `warn!` with the sender pubkey — 9033
writes no audit row and publishes no announcement event (unlike
9030/9031), so this is the only durable attribution.
- Kinds 9030–9032, NIP-42 auth, `AdminUsers` scope, ban gate, and icon
validation are all untouched.
- Doc comment fixed: cited nonexistent `canEditCommunityProfile`; real
symbol is `canEditIcon`.

## Test coverage — closing the mutation gap

Dawn's mutation testing showed the original unit tests pinned only the
helper's truth table: inverting the flag at the call site or deleting
the gate entirely survived the full suite.

- Unit tests now cover the 3-arg truth table (closed
steward-independent, open-with-steward stays steward-only,
rosterless-open admits).
- Two `#[ignore]`d Postgres integration tests drive
`handle_relay_admin_event` with a real `AppState` (open rosterless admit
→ steward appears → roleless refused again; closed relay member
refused). Wired into the Backend Integration CI job as a dedicated
nextest step.
- **Both of Dawn's mutants verified killed** at this head: flag
inversion fails 1 unit test; gate deletion fails both integration tests
(`Ok(())` where `Rejected` expected).

## CI wrinkle found and fixed: pre-existing schema drift

The first Backend Integration run of the new 9033 tests failed with
`column "icon" of relation "communities" does not exist` — migration
`0003_community_icon.sql` added the column, but `schema/schema.sql` (the
desired-state file that CI job applies via pgschema) was never updated.
Pre-existing drift, invisible until a test in that job actually wrote
the column. Fixed in `297148f62` (3-line addition to
`schema/schema.sql`).

## Receipts (at `1b4b52db8` code / `297148f62` head)

- `cargo test -p buzz-relay`: 835 pass, 1 fail —
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo`,
pre-existing (fails identically at the old base and on clean main);
`telemetry::trace_context_lookup_does_not_enable_callsites` is a known
order-dependent flake, passes in isolation.
- `cargo test -p buzz-db`: 94 pass.
- Both ignored integration tests pass live against local Postgres.
- `cargo fmt --all -- --check`: clean.
- Live-local pass per TESTING.md at this head (release build, relay on
:3199, real WS + NIP-42 via nak):
- open rosterless: roleless key sets icon → NIP-11 serves it; `warn!`
with sender pubkey in the relay log
- open + owner row inserted: fresh roleless key refused ("must be admin
or owner"); owner sets icon
- closed relay (owner bootstrapped, `BUZZ_RELAY_PRIVATE_KEY` set): plain
member refused, owner sets icon, `javascript:` URL rejected, empty icon
clears (NIP-11 → null)

---------

Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-01 12:03:31 -04:00
cb9701cd30 feat(relay): accept kind:30621 multi-repo projects at ingest (#3171)
Buzz renders one card per `kind:30617`, so a project spanning several
repositories has no representation.
[NIP-MP](https://github.com/block/buzz/pull/3163) defines `kind:30621`
as an addressable container holding a group's name, description, channel
binding, and member coordinates. This adds the kind to `buzz-core` and
its structural validation to the relay ingest path.

## Event shape

```json
{
  "kind": 30621,
  "tags": [
    ["d", "platform"],
    ["name", "Platform"],
    ["description", "Relay, desktop, and mobile."],
    ["a", "30617:<owner-a-hex>:buzz"],
    ["a", "30617:<owner-b-hex>:buzz-infra"],
    ["buzz-channel", "<channel-uuid>"],
    ["buzz-visibility", "listed"]
  ]
}
```

## Validation at ingest

| Rule | Behavior |
|------|----------|
| `d` tag | exactly one, non-empty (length already bounded by the
generic `D_TAG_MAX_LEN` check) |
| member `a` tag arity | exactly 2 or 3 elements per NIP-01's `a` tag
grammar; a 4th element has no defined meaning and is rejected |
| member `a` tag coordinate | must parse as
`30617:<lowercase-64-hex-owner>:<non-empty-d>` |
| duplicate members | rejected on exact string match of the canonical
coordinate |
| member cap | 64, counted over raw `a` tags |
| metadata cardinality | at most one each of `name`, `description`,
`buzz-channel`, `buzz-visibility` |
| metadata length | `name` ≤ 256 bytes, `description` ≤ 2048 bytes,
`buzz-channel` ≤ 256 bytes, `buzz-visibility` ≤ 256 bytes |
| zero members | valid |
| unknown tags | ignored |

Rejection order is normative so a client can predict which rule fires:
`d`-cardinality → `d`-empty → member-cap → member-arity → coordinate
parse → member-duplicate → metadata cardinality → metadata length.

## Design notes

**No membership authorization.** Members are `a` tags, so one project
may name repositories owned by different pubkeys — the entire point of
the kind. That is safe because membership grants nothing: push policy
reads a repository's own `kind:30617` (`api/git/policy.rs`) and never a
project. `buzz-channel` is a metadata reference, not a routing
directive, so projects are classified global-only.

**Owner-only editing is free.** NIP-33 addressing keys replacement on
`(pubkey, kind, d)`, so one signer can never overwrite another's
project. No relay-side permission check exists or is needed, and
`test_project_same_d_under_two_authors_are_independent` pins it.

**Duplicates are rejected, not deduped.** A relay cannot rewrite tags
inside a signed event without invalidating its id and signature, so the
alternative to rejection is a stored duplicate-member head that every
consumer must apply a first-wins rule to.

**The cap is checked before the duplicate set is built.** Counting raw
`a` tags rather than distinct coordinates means an event naming one
coordinate thousands of times is refused on count, instead of being
bounded only by the relay frame limit.

**No side-effect handler.** Generic NIP-33 replacement and generic
NIP-09 coordinate soft-delete already cover replacement and deletion;
`kind:30621` needs no entry in `is_side_effect_kind`.

## Generic NIP-09 fix carried along

`soft_delete_by_coordinate` (`crates/buzz-db/src/event.rs`) previously
deleted the live coordinate head regardless of the tombstone's own
`created_at`, so a delayed or replayed `a`-tag deletion signed between
two versions destroyed the newer replacement. NIP-09 scopes an `a`-tag
deletion to versions at or before the deletion request, so the `UPDATE`
now carries `created_at <= $5` and `handle_a_tag_deletion` threads the
deletion event's `created_at` through.

The bug predates `kind:30621` and affected every
parameterized-replaceable kind on the generic path — `kind:30617`
repository announcements included — so the fix lands there rather than
as a project special case. `events.created_at` is immutable per row, so
the predicate guarantees a tombstone can never erase a version newer
than itself; the UPDATE re-evaluates its WHERE clause after any lock
wait. Under READ COMMITTED, a same-coordinate replacement racing the
deletion may cause the deletion to evaluate before the new head lands,
returning `Ok(false)` — but that outcome is state-identical to the
deletion having arrived first, a valid Nostr ordering Nostr never fixes.
The return value feeds only a debug log. No coordinate-level lock is
needed.

## Coverage

32 unit tests in `crates/buzz-relay/src/handlers/ingest.rs` pin the
envelope contract (accept: minimal, cross-owner, zero-member, same repo
`d` under two owners, colon-bearing repo `d`, cap boundary, unknown
tags, relay hint on member `a` tag, max-length metadata, stranger-owned
member, uninterpreted metadata values, non-empty content; reject: every
rule above plus valueless `d`/`a` tags). A fixture-driven test
(`project_envelope_validates_all_shared_fixtures`) runs every case in
the shared `NIP-MP.fixtures.json` oracle (11 accept + 20 reject) against
`validate_project_envelope`, so any future change that breaks a case
turns the test suite red.

6 `#[ignore]`d e2e tests in
`crates/buzz-test-client/tests/e2e_project.rs` cover behavior that only
exists past storage — coordinate round-trip, newer-wins replacement, two
authors sharing a `d`, an `a`-tag tombstone that removes the project
while leaving referenced `kind:30617`s intact, and a tombstone
timestamped between V1 and V2 that must leave V2 live. The negative e2e
case asserts on the rejection message so a refusal for an unrelated
reason cannot satisfy it; that is what proves the validator is reachable
from the live write path rather than merely correct in isolation. The
new e2e binary is wired into the Relay E2E job.

The timestamp predicate is additionally pinned at the storage layer by
`coordinate_delete_spares_head_newer_than_the_deletion` in
`crates/buzz-db/src/lib.rs`, which asserts both directions: a stale
tombstone deletes nothing and leaves the newer head readable, and a
tombstone at the head's own timestamp still deletes it. This test is
wired into the Backend Integration job.

Related: #3163 (the NIP-MP spec and shared conformance fixtures).
Independent — either can merge first.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-31 16:22:57 -04:00
54c8ef30a9 fix(release): require exact-head approval for desktop tags (#3973)
## Summary
- require an exact-head trusted approval before desktop auto-tagging
- remove rule-suite authorization that `GITHUB_TOKEN` cannot access
- pin review pagination to `page=1` and test the deployed `gh` control
flow

## Why
The previous verifier unconditionally queried repository rule-suite
endpoints with `github.token`. Those endpoints require Administration:
read, which Actions `GITHUB_TOKEN` cannot receive. Its paginated list
request also duplicated page one when no explicit page was supplied.

This deliberately removes admin-bypass authorization rather than
introducing a second credential during release recovery. Desktop release
PRs must now have GitHub's overall `APPROVED` decision and a
MEMBER/OWNER/COLLABORATOR approval attached to the exact candidate SHA.

## Validation
- `scripts/test-desktop-release-authorization.sh`
- `scripts/test-release-ref-contract.sh`
- `bash -n scripts/verify-desktop-release-merge.sh
scripts/verify-desktop-release-authorization.sh
scripts/test-desktop-release-authorization.sh
scripts/test-release-ref-contract.sh`
- `git diff --check origin/main...HEAD`

The new flow test uses a stub `gh` executable, asserts the exact
`page=1` request, fails any rule-suite API call, and rejects stale-SHA,
untrusted-author, changes-requested review, and non-approved
aggregate-decision cases.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-31 11:33:54 -07:00
db7e84d4f8 fix(release): make desktop tagging squash-safe (#3965)
## Summary
- validate desktop release candidates before merge and keep the
repository squash-only
- tag the squash commit only after proving frozen-base parent and
complete-tree identity with the validated PR head
- accept either an exact-head approval or the durable Default-ruleset
bypass record as release authorization
- remove the unusable App-backed preparation workflow; retain `just
release-desktop`

## Ruleset follow-up
After this PR merges, update Default ruleset `13596885` to:
- enable strict required status checks
- dismiss stale reviews on push and require approval after the last push
- require the integration-bound `Desktop Release Candidate` check

The next desktop release should be cut only after that settings update.

## Verification
At commit `d8c254db427eedbcffac1a6e078e90d1d0f5e151` with a clean
worktree:
- `scripts/test-release-ref-contract.sh`
- `scripts/test-desktop-release-candidate.sh`
- `bash -n scripts/verify-desktop-release-merge.sh
scripts/prepare-desktop-release.sh scripts/test-release-ref-contract.sh`
- `git diff --check`

The bypass test fixture is the captured rule-suite shape from real
squash merge PR #2864 / suite `3520068134`.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-31 11:03:51 -07:00
052174a148 fix(release): make immutable desktop release operable (#3943)
## Summary

- document `Prepare Desktop Release` as the canonical desktop release
entry point
- describe the frozen candidate, exact-head approval, and true
merge-commit contract
- document all platform outputs and complete release App/signing
configuration
- link the release runbook from the README
- allow stable reruns to repair the rolling updater manifest after the
versioned release has already published

## Release blocker

The live repository cannot currently complete this flow: repository
settings disable merge commits and the `main` ruleset allows only
squash, while `scripts/verify-desktop-release-merge.sh` requires a
two-parent merge whose second parent is the approved candidate. Those
settings must allow merge commits before a desktop release PR is merged.

## Validation

- `bash scripts/test-desktop-release-candidate.sh`
- `bash scripts/test-release-ref-contract.sh`
- `git diff --check`
- verified live repository merge settings, `main` ruleset, release tag
ruleset, Actions variable names, and secret names with GitHub API
- independent review by Princess Donut; incorporated all findings,
including the rolling-manifest retry gap and unsigned Windows labeling

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-31 09:39:14 -07:00
Will PflegerandGitHub 114d40d9d3 feat(relay): gate kind 30178 team-catalog reads behind the shared tag (#3358)
Team catalog projections (`kind:30178`) embed every member's system
prompt, so they need the same read gate personas already have: only the
author sees an unshared event. The gate was hardcoded to `kind:30175` at
six read surfaces plus the SQL pushdown, so rather than adding a second
special case it becomes kind-generic over `SHARED_GATED_KINDS = {30175,
30178}`.

## Kind 30178

New parameterized-replaceable kind, addressed by `(pubkey_o, 30178,
team_id)`. It embeds sanitized member projections instead of referencing
`kind:30175` heads — a foreign reader of a shared team could not
otherwise hydrate members whose own persona events are unshared or, for
built-ins, absent entirely. `kind:30176`'s wire body is untouched, so
device sync keeps its contract.

## Kind-generic shared gate

`buzz_core::kind` replaces `is_persona_shared_kind` /
`is_unshared_persona_event` / `persona_event_is_shared` with
`SHARED_GATED_KINDS` and the kind-agnostic `is_shared_gated_kind` /
`is_unshared_gated_event` / `event_is_shared`. Every read surface
consults the set:

| Surface | File |
|---|---|
| REQ historical delivery + `ids` lookup |
`crates/buzz-relay/src/handlers/req.rs` |
| Live fan-out | `crates/buzz-relay/src/handlers/event.rs` |
| COUNT fallback | `crates/buzz-relay/src/handlers/count.rs` |
| NIP-98 HTTP `/query`, `/count`, `/search` |
`crates/buzz-relay/src/api/bridge.rs` |
| Pre-`LIMIT` SQL pushdown | `crates/buzz-db/src/event.rs` |

The SQL clause generalizes from `kind != 30175` to `kind NOT IN (...)`
bound from `SHARED_GATED_KINDS`, still applied before `ORDER BY … LIMIT`
so a page of newer private events cannot starve an older shared one off
the candidate set. `EventQuery::persona_reader` is renamed
`shared_gated_reader` and `needs_persona_filtering` to
`needs_shared_gate_filtering` to match.

Because the `buzz-core` rename has consumers outside the relay, the four
desktop call sites of `persona_event_is_shared` travel with it:
`desktop/src-tauri/src/commands/personas/pending.rs`,
`desktop/src-tauri/src/event_sync.rs`, and two in
`desktop/src-tauri/src/managed_agents/persona_events.rs`. Each call is
unchanged apart from the name — the persona `shared` projection behaves
exactly as before.

## Ingest validation

`validate_persona_envelope` splits into two reusable pieces —
`validate_shared_tag` (exactly-two-element `["shared","true"]`, at most
one occurrence) and `single_bounded_d_tag` (exactly one `d` tag,
non-empty, `<=64` chars, no ASCII control characters or whitespace).
`validate_team_catalog_envelope` composes both; personas additionally
keep the slug grammar `^[a-z0-9][a-z0-9_-]{0,63}$`.

`kind:30178` deliberately does **not** get the slug grammar. Team ids
are UUIDs or built-in identifiers such as `builtin-team:welcome`, and
the colon is not slug-legal; rewriting ids to fit would break NIP-33
addressing against the team's own `kind:30176` head. The non-empty and
exactly-one checks are load-bearing regardless — without them generic
NIP-33 storage maps a missing `d` onto `(pubkey_o, 30178, "")` and every
team overwrites its predecessor.

The exact two-element `shared` shape is enforced because the SQL
visibility clause is JSONB containment (`tags @>
'[["shared","true"]]'`), which would match a three-element superset such
as `["shared","true","extra"]`.

`kind:30178` is also added to the `Scope::UsersWrite` allowlist and to
`is_global_only_kind`, so a stray `h` tag cannot channel-scope an
owner-authored definition.

## Deferred

`kind:30176` is deliberately not a gate member. Its writers never emit
`shared`, so catalog opt-in semantics do not describe it — it needs
owner-private reads driven by an authenticated principal set, tracked as
a separate follow-up.

## Tests

- 19 new `ingest.rs` unit tests covering the 30178 envelope (UUID and
colon `d` tags, 64-char boundary, non-ASCII bound,
empty/valueless/duplicate/missing `d`, embedded newline, `shared`
false/three-element/duplicate, scope and global-only membership).
- Persona regressions for the valueless `["d"]` shapes, since the
`d`-tag helper is shared by both validators.
- Existing `kind.rs` gate tests generalized and extended to assert the
gate applies to 30178 as it does to 30175.
- New `crates/buzz-test-client/tests/e2e_team_catalog.rs`: 9 WS-level
tests over a live relay covering author reads of unshared heads, foreign
omission from REQ, `ids`-lookup denial, COUNT existence-leak, share and
unshare transitions, and the mixed-kind filter case.
- `.github/workflows/ci.yml` adds `--test e2e_team_catalog` to the Relay
E2E job so the new suite runs.

## Docs

`docs/nips/NIP-AP.md` gains a "Team catalog projection: kind:30178"
section and an "Ingest validation: kind:30178" subsection, records the
gate as kind-generic, documents 30178 deletion vs. unshare semantics,
and adds a security note that sharing a team exposes every member's
instructions even when that member's own `kind:30175` head is unshared.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-30 17:53:30 -04:00
1dfd89ea67 feat(release): make desktop releases immutable (#3568)
## Summary
- add a manual desktop release preparer that regenerates one
version-only candidate from current `origin/main`
- validate deterministic complete changelog accounting, candidate
authorship, allowed files, exact-head approval, required checks, and
two-parent merge topology before tagging the reviewed candidate
- move desktop tags/releases from `v*` to `desktop-v*` while preserving
relay, chart, push-chart, and mobile behavior
- stage all four platform outputs in Actions artifacts and grant GitHub
release write access only to one final all-platform-gated publisher
- publish the versioned release only after complete artifact assembly;
update stable `latest.json` last; never promote prereleases or published
rebuild outputs

## Safety properties
- desktop tags point to the reviewed candidate SHA, not the merge commit
- release builds remain tag-bound and reverify tag == checked-out HEAD
- one final writer fails closed on artifact basename collisions
- per-tag concurrency serializes publication without cancellation
- published reruns do not replace immutable versioned assets or promote
signatures from a rebuild
- candidate branches use an explicit remote OID lease when regenerated

## Validation
- `scripts/test-desktop-release-candidate.sh`
- `scripts/test-release-ref-contract.sh`
- `scripts/test-mobile-release-contract.sh`
- changed workflow YAML parsing (Ruby Psych)
- changed shell syntax (`bash -n`)
- `git diff --check`
- push hooks: branch-skew, Rust workspace tests (1,853 passed), desktop
Tauri tests (3 passed)

## Coordinated companion
- squareup/buzz-releases#79 updates the manually entered desktop
source-tag contract to stable-only `desktop-v*`
- merge the private contract companion before the first namespaced
desktop release

## Rollout blockers (no settings changed here)
Before the first candidate/release:
1. enable merge commits in repository settings
2. allow `merge` in ruleset `13596885`
3. require approval after the last push in ruleset `13596885`
4. include `refs/tags/desktop-v*` explicitly in release ruleset
`14378754`
5. prove the non-publishing candidate/merge/tag/artifact validation path
before any production release

Do not test the old workflow with a prerelease: it can still mutate the
production rolling updater release.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-30 11:27:56 -07:00
581baa6254 chore(ci): bump Linux AppImage build container to ubuntu:24.04 (#3602)
## What and why

The Buzz AppImage is built on `ubuntu:22.04`, which links WebKitGTK
against FreeType 2.11.1. Because `libfreetype.so.6` is on the
linuxdeploy community excludelist, the bundled WebKit loads the
**host's** FreeType at runtime instead of the bundled one.

FreeType 2.13.0 (released 2023-02-09) added `FT_Bool read_variable` to
`FT_ColorStopIterator`, growing the struct from 16 to 20 bytes. Any host
running FreeType ≥ 2.13 (Fedora 42+, Ubuntu 24.04+) has a struct-layout
mismatch with the 22.04-compiled WebKit. The mismatched offsets corrupt
color-stop index arithmetic inside Skia's COLRv1 renderer, producing the
assertion abort in issues #2548 and #2982:

```
stl_vector.h:1123: Assertion '__n < this->size()' failed.
... colrv1_configure_skpaint(FT_Face, ...) ...
```

## Fix

Bump the build container to `ubuntu:24.04` (noble), which ships FreeType
**2.13.2**. Noble's struct layout matches every crash-affected host. The
ABI mismatch disappears and the crash is eliminated at root.

WebKitGTK also advances from **2.50.4** (jammy backport) to **2.52.3**
(noble backport).

## Glibc floor change

| Build base | glibc floor | Oldest supported AppImage distro |
|---|---|---|
| ubuntu:22.04 (before) | 2.35 | Ubuntu 22.04 LTS, Debian 12 |
| ubuntu:24.04 (after) | 2.39 | Ubuntu 24.04 LTS, Fedora 40+ |

Ubuntu 22.04 LTS and Debian 12 users lose AppImage support. Both
distributions continue to receive first-class `.deb` / `.rpm` packages,
which use the system WebKit and are unaffected. The crash-affected users
(Fedora 42/44, Ubuntu 24.04+) all have glibc ≥ 2.39.

## Changes

- `.github/workflows/linux-canary.yml:24` — container pin updated to
`ubuntu:24.04@sha256:4fbb8e6a…`
- `.github/workflows/release.yml:479` — same container pin updated
- `.github/workflows/release.yml:501` — comment version string updated
from 22.04 to 24.04

`fix-appimage.sh` and `desktop/src-tauri/**` are untouched. The #3573
fontconfig stopgap remains active; retirement is a separate follow-on PR
once this fix is verified on a shipped build.

## Sequencing

`docs/linux-rendering-troubleshooting.md` (introduced in #3573) will
receive a glibc-floor callout section once #3573 merges — adding it here
would conflict with #3573's open branch.

Context: #2548, #2982.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-29 16:38:44 -04:00
9227bdf58a fix(ci): ratchet file sizes against the base tree (#3352)
## Summary

- replace the whole-tree file-size gate with a stateless differential
ratchet
- allow inherited files over 1,000 lines to hold or shrink, but never
grow
- delete the 44-entry numeric override ledger and run the same policy
across Desktop, Web, and Mobile CI
- fail closed when the local base cannot be resolved and cover policy,
Git status parsing, and base resolution in unit tests

This removes the shared mutable policy state that caused unrelated PRs
to fail after neighboring merges. It does **not** by itself prevent two
stale green PRs from becoming invalid when combined; that requires merge
queue or up-to-date branch enforcement.

### Related issue

None found. This follows the design discussion in the linked Buzz
channel.

### Testing

- `node --test scripts/check-file-sizes-core.test.mjs` (6/6)
- Desktop, Web, and Mobile ratchet entrypoints
- `just desktop-check`
- `just web-check`
- Mobile analysis
- `git diff --check`

The repository pre-push suite also exposed an unrelated existing Mobile
widget failure in `ChannelDetailPage keeps follow mode off while a tall
newest message stays visible`; it reproduces in isolation and this
branch does not touch Mobile widget behavior.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-28 22:17:36 +00:00
Will PflegerandGitHub 826bed4821 chore(ci): bump desktop smoke E2E timeout to 30 minutes (#3409)
Three main-branch runs today had shards killed at exactly 20m17s
("exceeded the maximum execution time of 20m0s"); the killed shard was
actively passing tests seconds before the cap. Shard runtime has grown
to the limit. 30 matches the other desktop jobs in the same workflow.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 17:59:02 -04:00
3a4bf513df Publish symbol-bearing debug relay images (#3250)
## Why
Native profilers such as `ddprof` need symbols to resolve optimized Buzz
relay stacks, while ordinary deployments should keep the current compact
stripped image.

## What
- Build optimized relay binaries with line-table debug information and
derive the stripped release binaries from the same ELF files
- Publish matching multi-arch `debug-*` tags while preserving existing
stripped tags and runtime behavior
- Document the debug image as an optimized symbol-bearing release, not a
debug-mode build

## Risk Assessment
Medium — this changes the relay image release workflow and adds a second
image variant, but existing tags remain stripped and use the same
runtime base, user, entrypoint, and optimized machine code.

## References
- Follows Envoy's optimized unstripped `debug-*` image pattern:
https://github.com/envoyproxy/envoy/blob/main/distribution/binary/BUILD
- Built both Docker targets locally; verified matching GNU build IDs and
`.text` hashes, with DWARF and symbol sections present only in the debug
variant
- Pre-push checks passed: branch skew, org policy, desktop checks/tests,
Rust tests, mobile tests, Tauri tests, and workspace tests

Generated with Amp

Signed-off-by: David Grochowski <dgrochowski@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-07-27 23:35:45 -04:00
d500c2d5cf feat(invites): add use-limited invite links (#3141)
## Summary

- add database-backed v2 invite links with optional maximum-use limits
and atomic final-slot redemption
- preserve v1 invite compatibility while adding
exhausted/expired/invalid client handling across desktop, web, and
mobile
- emit structured claim-outcome logs with community, invite ID, outcome,
maximum uses, and post-claim count

## Verification

- `cargo fmt --all -- --check`
- `cargo test -p buzz-db` (85 passed, 134 Postgres-dependent ignored)
- `cargo clippy -p buzz-db --all-targets -- -D warnings`
- desktop `npm run typecheck`
- push hook: desktop checks/tests, desktop Tauri tests, Rust tests, and
branch-skew passed
- Postgres integration tests were previously reviewed green at the
pre-rebase tree; local rerun on this session was unavailable because
Postgres/Docker were not running
- mobile push-hook check could not start because Flutter is unavailable
locally

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: npub1c4alndp82zyt9veaklm5d965quss79vlhk9awv7qu5erwhmf42qqlvc25c <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz>
Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-07-27 15:19:39 -07:00
1a56b7cc9e feat(mobile): add worktree-aware debug identities (#2858)
**Category:** improvement  
**User Impact:** Developers can identify which worktree produced a
mobile debug app, keep a bounded set of worktree builds installed side
by side, and preserve each worktree app's login and local state while
switching branches.

**Problem:** Mobile debug builds from every checkout currently appear as
the same “Buzz” app and share one application identity, so the running
source is ambiguous and one worktree build replaces another. A
branch-keyed identity would avoid replacement but create stale installs
and fresh app state on every branch switch.

**Solution:** Give each linked worktree a stable Debug-only application
identity derived from its sanitized directory name. Show the sanitized
branch name (or short commit SHA when detached) in the display label,
persist generated native overrides for direct IDE builds, and leave
Release/Profile identities unchanged. Worktree defaults remain lower
precedence than a developer's iOS `AppOverrides.xcconfig`. `just
mobile-clean` provides a safe cleanup path for suffixed worktree
installs while preserving production Buzz.

<details>
<summary>File changes</summary>

**.github/workflows/ci.yml**  
Runs the expanded worktree override contract when relevant mobile or
native configuration changes.

**AGENTS.md**  
Documents worktree-aware mobile development and cleanup for contributors
and agents.

**Justfile**  
Generates overrides before mobile development and Android debug builds,
and exposes `just mobile-clean`.

**mobile/README.md**  
Explains stable per-worktree identities, branch/SHA labels, direct IDE
usage, cleanup, and Release/Profile guarantees.

**mobile/android/.gitignore**  
Ignores generated worktree properties.

**mobile/android/app/build.gradle.kts**  
Loads and validates generated properties, then applies the application
ID suffix and display label to Android Debug only.

**mobile/android/app/src/main/AndroidManifest.xml**  
Resolves the Android app label through an overridable string resource.

**mobile/ios/.gitignore**  
Ignores generated iOS worktree settings.

**mobile/ios/Flutter/Debug.xcconfig**  
Loads generated worktree defaults before developer `AppOverrides`, so
personal signing overrides retain precedence.

**mobile/ios/Flutter/Release.xcconfig**  
Pins the production display name and bundle identifier for
Release/Profile builds.

**mobile/ios/Runner/Info.plist**  
Resolves the visible iOS app name from build settings.

**scripts/mobile-worktree-overrides.sh**  
Detects linked worktrees, derives a stable directory-keyed identity,
sanitizes branch/SHA display context, writes native Debug overrides, and
removes stale overrides in the main checkout.

**scripts/mobile-worktree-clean.sh**  
Lists or removes suffixed Buzz worktree installs from booted iOS
simulators and connected Android emulators without matching production
IDs; supports `--dry-run`.

**scripts/test-mobile-worktree-overrides.sh**  
Covers worktree detection, branch-switch identity stability, detached
HEAD fallback, special-character sanitization, iOS override precedence,
brace-aware Release/Profile purity, cleanup safety, ignores, and command
integration.

</details>

## Reproduction steps

1. From a linked worktree, activate the repository toolchain and run
`just mobile-dev`.
2. Inspect the running app: its label should be `Buzz
(<sanitized-branch>)`, while its application ID suffix is derived from
the worktree directory.
3. Switch branches in the same worktree, rerun the override script, and
confirm the application ID remains stable while the display label
updates. In detached HEAD, confirm the label uses a short SHA.
4. Build Debug from a second worktree and confirm both apps remain
installed side by side with independent state.
5. Build from Xcode after setting `AppOverrides.xcconfig` and confirm
developer overrides still win over generated worktree defaults.
6. Run `just mobile-clean --dry-run`, then `just mobile-clean`, and
confirm suffixed worktree installs are targeted while the production app
is preserved.
7. Build Release/Profile and confirm the production name and application
identity remain unchanged.
8. Run `scripts/test-mobile-worktree-overrides.sh`, `just mobile-check`,
`just mobile-test`, and `just mobile-build-android`.

## Screenshots / demos

| iOS — labeled app switcher | iOS — side-by-side installs |
| --- | --- |
| <img width="360" alt="Buzz worktree label in the iOS app switcher"
src="https://github.com/user-attachments/assets/4bcae067-7ce5-4333-bb11-2803c4107663"
/> | <img width="360" alt="Buzz production and worktree debug apps
installed side by side on iOS"
src="https://github.com/user-attachments/assets/08a107b5-fdf2-463a-8a4c-81d41d7bf5e7"
/> |

| Android — side-by-side installs | Android — labeled app switcher |
| --- | --- |
| <img width="360" alt="Buzz production and worktree debug apps
installed side by side on Android"
src="https://github.com/user-attachments/assets/4f5841a1-adae-42da-ae84-47c09ec85fb9"
/> | <img width="360" alt="Buzz worktree label in the Android app
switcher"
src="https://github.com/user-attachments/assets/0546ff51-efcc-4cb6-a4bd-2a3af26cd60f"
/> |

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
2026-07-26 13:47:35 -07:00
Daniel CadenasandGitHub cc6c4d3471 fix(desktop): make Linux AppImage GStreamer work on non-Debian distros (#2176) 2026-07-25 19:49:30 -07:00
b78a684cfa ci: add Windows and Linux canary workflows with caching (#2642)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-24 09:19:31 -07:00
Tom BrowGitHubnpub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marzenpub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9enpub1re830n24qhgstulznk5dxdluccspxkxxdq643lm4q3zwwwjgsuqqmcmdrm7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
21573b6cb9 chore(mobile): lighter-weight release process (#2144)
Signed-off-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1re830n24qhgstulznk5dxdluccspxkxxdq643lm4q3zwwwjgsuqqmcmdrm <1e4f17cd5505d105f3e29da8d337fcc6201358c6683558ff750444e73a488700@buzz.block.builderlab.xyz>
Signed-off-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
Co-authored-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1re830n24qhgstulznk5dxdluccspxkxxdq643lm4q3zwwwjgsuqqmcmdrm <1e4f17cd5505d105f3e29da8d337fcc6201358c6683558ff750444e73a488700@buzz.block.builderlab.xyz>
Co-authored-by: 7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
2026-07-23 15:05:59 -07:00
659e6a8d65 fix(ci): stop moving protected Sprig rolling tag (#2221)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-07-23 10:33:06 -07:00
c96ae0ea97 ci(desktop): add signed macOS canary build (#2419)
Signed-off-by: npub1rf6fvdj6ut0c4kcmjv4p5mmgh89nj58n69uu3fz3cvk3jn500hqs7emz79 <1a7496365ae2df8adb1b932a1a6f68b9cb3950f3d179c8a451c32d194e8f7dc1@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1rf6fvdj6ut0c4kcmjv4p5mmgh89nj58n69uu3fz3cvk3jn500hqs7emz79 <1a7496365ae2df8adb1b932a1a6f68b9cb3950f3d179c8a451c32d194e8f7dc1@sprout-oss.stage.blox.sqprod.co>
2026-07-22 21:43:13 +00:00
dd64d721d3 fix(ci): don't rebuild relay tests on every run (#2203)
Signed-off-by: npub1ft62tztwwm2x9xamk25smmuaj4sfckdkldksruf2x2jwqalffkrq0g7arr <4af4a5896e76d4629bbbb2a90def9d95609c59b6fb6d01f12a32a4e077e94d86@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1ft62tztwwm2x9xamk25smmuaj4sfckdkldksruf2x2jwqalffkrq0g7arr <4af4a5896e76d4629bbbb2a90def9d95609c59b6fb6d01f12a32a4e077e94d86@sprout-oss.stage.blox.sqprod.co>
2026-07-21 15:23:38 -07:00
Jordan MecomandGitHub 13c11f24ea [codex] Fix missing release tag detection (#2191) 2026-07-20 15:30:10 -07:00
Jordan MecomandGitHub 0b297edbdf [codex] create release tags with dedicated App (#2186) 2026-07-20 22:12:14 +00:00
thomaspblockandGitHub ed432f91d9 feat(desktop): complete project git workflows (#2119) 2026-07-19 22:09:56 +02:00
ca384d082d fix: recover community access visibility (#2074)
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@sprout-oss.stage.blox.sqprod.co>
2026-07-18 19:01:03 +00:00
92040e5513 fix(ci): use git diff for path filtering (#2029)
Signed-off-by: Cameron Hotchkies <chotchkies@block.xyz>
Co-authored-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@sprout-oss.stage.blox.sqprod.co>
2026-07-17 10:07:22 -07:00
Jordan MecomandGitHub 73fc0ec6cf [codex] Enforce shared relay admission limits (BUZZ-SEC-019) (#1917) 2026-07-17 16:25:09 +00:00
Jordan MecomandGitHub 2310b03f9d [codex] Bind release publishers to immutable tags (BUZZ-SEC-039) (#1914) 2026-07-17 08:52:57 -07:00
648cbf3610 feat: add invite QR and mobile direct join (#1957)
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
2026-07-16 13:40:24 -07:00
Will PflegerandGitHub 8969745549 fix(desktop): enforce observer archive policy reconciliation on internal builds (#1923)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-16 15:47:35 -04:00
acdf4f2671 ci: build the relay once and cache it by source hash (#1941)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
2026-07-15 21:43:18 -07:00
Michael NealeandGitHub 32957692eb fix(desktop): load downloaded mesh runtime on signed macOS builds (#1932) 2026-07-15 21:28:17 -04:00
Michael NealeandGitHub 54638ff4bb mesh: upgrade runtime, enforce membership, add shared compute provider (#1656) 2026-07-15 10:10:29 -04:00
ab5cbc3692 ci(mobile): compile Android debug APK (#1816)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-07-14 13:59:34 -07:00
1c006822e4 feat(push): add public APNs gateway (#1770)
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
2026-07-14 11:17:21 -04:00
a653406309 ci(desktop): surface flaky E2E tests instead of retry-masking them (#1838)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-13 22:14:40 -04:00
9b47c8548f Add optional standalone pairing relay to Helm chart (#1799)
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
2026-07-13 11:36:04 -04:00
bea507d8aa chore(ci): lint desktop Tauri crate in CI (#1801)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-13 11:21:10 -04:00
6037615694 fix(release): mark workspace safe for git in containerized Linux release job (#1793)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-13 00:44:39 -04:00
eb0417aa88 fix(release): install AppImage bundling system deps for Linux (#1737)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-10 18:30:01 -04:00
bbf80aa73f fix(release): run Linux release job under bash not dash (#1733)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-10 17:00:25 -04:00
Will PflegerandGitHub 563dab92e4 fix(linux): make AppImage work on Mesa 25+ / GLib 2.88 distros (#1567) 2026-07-08 13:18:48 -04:00
f437e43d09 benchmarks: Harbor Terminal-Bench harness for Buzz agent-team orchestration (harbor-buzz-orchestra) (#1504)
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-08 11:32:11 -04:00
klopez4212andGitHub 5b1a53ceba Update DMG background and label size (#1629) 2026-07-08 15:28:32 +01:00
cfa2089831 refactor(shell): drop bundled PortableGit, add BUZZ_SHELL override + dialect hint (#1536)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-06 12:54:50 -04:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
a255a77358 chore(deps): update all non-major dependencies (#1163)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 14:19:20 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
19d98319a8 chore(deps): update cashapp/activate-hermit digest to cea9af7 (#1522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 14:14:53 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8dea817fa9 chore(deps): update actions/cache digest to caa2961 (#1341)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 14:14:38 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
4b01040afb chore(deps): pin dependencies (#1162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 14:14:06 +00:00
klopez4212andGitHub bbf8d8912a Customize macOS DMG installer (#1496) 2026-07-03 08:56:20 -07:00