Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6dbc946
fix(desktop): make missing-command error actionable for released buil…
wpfleger96 Aug 5, 2026
dc17965
fix(mobile): serialize channel sections sync (#3165)
brow Aug 5, 2026
067c085
Define private managed agent wire protocol (#4593)
wesbillman Aug 5, 2026
8a7eb8d
fix(agent): recover from unsupported image input instead of poisoning…
tlongwell-block Aug 5, 2026
997b8ca
fix(git): revoke access for banned relay members (#4608)
jmecom Aug 5, 2026
885bed3
fix(workflow): bind trigger author to the signed event (#4607)
jmecom Aug 5, 2026
ad538bf
fix(acp): reject unattended permission requests (#4609)
jmecom Aug 5, 2026
efe1893
fix(channels): restrict private-channel invitations (#4612)
jmecom Aug 5, 2026
4674750
fix(release): tag immutable desktop candidates (#4811)
wesbillman Aug 5, 2026
ff0b798
Polish mobile top navigation (#4778)
klopez4212 Aug 5, 2026
014562c
fix(desktop): allow shared agent mentions (#4913)
wesbillman Aug 5, 2026
2034e69
fix(desktop): remove join API token control (#4897)
klopez4212 Aug 5, 2026
f2ce575
Fix media attachment actions (#4849)
klopez4212 Aug 5, 2026
27b5114
Polish mobile bottom sheets and profile cards (#4911)
klopez4212 Aug 5, 2026
0c68429
Fix mobile message timeline bounce (#4862)
klopez4212 Aug 5, 2026
6df7eba
fix(buzz-agent): scope handoff cap per turn, not per session lifetime…
wpfleger96 Aug 5, 2026
6c40ce3
feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
wpfleger96 Aug 5, 2026
43cced3
feat(desktop): sync themes per community (#3653)
tellaho Aug 5, 2026
05150c1
feat(mobile): sync themes per community (#3767)
tellaho Aug 5, 2026
7334ad1
fix(desktop): route macos notification clicks (#4799)
kalvinnchau Aug 5, 2026
ccdaa16
docs(persona-pack): fix stale desktop import instructions (#4500)
SomSamantray Aug 5, 2026
ed4b3e7
fix(buzz-agent): recover from context-window 400s instead of sticking…
wpfleger96 Aug 5, 2026
719f973
feat(desktop): allow leaving your final community (#3621)
tellaho Aug 5, 2026
2ea9385
fix(reactions): support max-length custom emoji (#3833)
tellaho Aug 5, 2026
d42d60d
fix(desktop): rename generic attachment action from 'Attach image' to…
iroiro147 Aug 5, 2026
cda3397
style(messages): increase username contrast (#4948)
tellaho Aug 5, 2026
24c7995
fix(desktop): clamp thread panel to channel surface (#4965)
tellaho Aug 5, 2026
005fe54
fix(desktop): outline the selected community (#4969)
tellaho Aug 5, 2026
e14fff7
relay: fuzz WebSocket 1012 restart-close timing on graceful drain (BU…
bradseiler Aug 5, 2026
eb6a375
fix(desktop): enable message editing in Inbox (#2198)
brow Aug 5, 2026
06b60e6
fix(mobile): merge relay recounts with locally seen thread replies (#…
brow Aug 5, 2026
a7ea86c
fix(desktop): enable the content security policy (#4614)
jmecom Aug 5, 2026
4da7264
fix(acp): pace observer telemetry at 1/s with per-channel batch envel…
tlongwell-block Aug 6, 2026
5677e4c
test(desktop): match attachment button label (#4993)
tellaho Aug 6, 2026
16cc3de
fix(desktop): enforce owner-only access in internal builds (#4053)
brow Aug 6, 2026
4b4d547
Merge remote-tracking branch 'upstream/main' into upstream-sync-20260806
adrienlacombe Aug 6, 2026
1484d16
docs(agents): record the second migration renumber and how to catch it
adrienlacombe Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .github/workflows/auto-tag-on-release-pr-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
echo "enabled=true"
echo "tag=${TAG_PREFIX}${VERSION}"
if [[ "$TAG_PREFIX" == desktop-v ]]; then
echo "target_sha=${{ github.event.pull_request.merge_commit_sha }}"
echo "target_sha=${{ github.event.pull_request.head.sha }}"
echo "desktop=true"
else
echo "target_sha=$GITHUB_SHA"
Expand All @@ -112,6 +112,7 @@ jobs:
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
MERGED_AT: ${{ github.event.pull_request.merged_at }}
run: |
VERSION="${VERSION#desktop-v}"
export VERSION
Expand Down Expand Up @@ -146,7 +147,17 @@ jobs:
exit 1
fi
fi
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
if ! gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
-f ref="refs/tags/$TAG" \
-f sha="$TARGET_SHA" \
--silent
--silent; then
# Ref creation is atomic. A concurrent retry may have won the race;
# accept that only when it created the exact immutable ref.
EXISTING_SHA="$(gh api "repos/$GITHUB_REPOSITORY/commits/$TAG" --jq .sha)"
if [ "$EXISTING_SHA" = "$TARGET_SHA" ]; then
echo "Tag $TAG was concurrently created at $TARGET_SHA"
exit 0
fi
echo "::error::Tag creation failed and $TAG resolves to $EXISTING_SHA (expected $TARGET_SHA)"
exit 1
fi
2 changes: 2 additions & 0 deletions .github/workflows/desktop-release-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:

permissions:
contents: read
pull-requests: read

jobs:
validate:
Expand All @@ -20,6 +21,7 @@ jobs:
- name: Validate immutable desktop candidate
if: startsWith(github.event.pull_request.head.ref, 'version-bump/')
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ github.event.pull_request.head.ref }}
run: |
VERSION="${VERSION#version-bump/}"
Expand Down
44 changes: 32 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ place.
| `.github/workflows/upstream-sync-merge.yml` | new | The deterministic (01:30) sync stage — the one that preserves the merge parent. Plain git, no AI. Optional `SYNC_PUSH_TOKEN` secret: a branch pushed with `GITHUB_TOKEN` does not start new workflow runs, so set a PAT if CI stops firing on sync PRs |
| `.github/workflows/upstream-sync-ci-status.yml` | new | Labels an open sync PR `sync-ci-green`/`sync-ci-red` once checks settle, and re-requests the Copilot review that gh-aw's `reviewers:` fails to attach. Deliberately does not merge |
| `migrations/0027_wallet_binding_fts.sql`, `0028_wallet_binding_fts_kind_move.sql` | new, and **kept after the feature was removed** | Search exclusions for the withdrawn NIP-SW wallet binding. They have already run on live databases and sqlx checksums applied migrations, so deleting them breaks startup validation. What they leave behind — a `search_tsv` expression excluding a kind nobody publishes — is inert, and unwinding it would rewrite a generated column across the whole events table for nothing. **Never edit or delete an applied migration**; add a follow-on |
| `migrations/0029_channels_id_lookup_index.sql` | upstream's `0027_channels_id_lookup_index.sql`, **renumbered** | The fork holds 0027 and 0028, so upstream's own new migrations have to arrive above them. See [Upstream migrations arrive renumbered](#upstream-migrations-arrive-renumbered) — this is now a permanent pattern, not a one-off |
| `crates/buzz-db/src/migration.rs` | `migrations.len()` assertion is 29, not upstream's 27; upstream's channel-index assertion reads `migrations[28].version == 29`, and the highest applied version is `Some(29)` | Counts embedded migrations, so it moves whenever *either* side adds one. `0027` landed without bumping it and left the test failing on `main`; fixed in PR #9. Beyond the count, every upstream assertion that indexes `migrations[…]` past 25 or names a version above 26 has to be shifted by the fork's two — see the section below for why the test suite will *not* catch it if you forget |
| `migrations/0029_channels_id_lookup_index.sql`, `0030_long_reaction_payloads.sql` | upstream's `0027_channels_id_lookup_index.sql` and `0028_long_reaction_payloads.sql`, **renumbered**; contents byte-identical | The fork holds 0027 and 0028, so upstream's own new migrations have to arrive above them. Two syncs running, so treat this as the standing cost of the fork's migration block rather than a special case. See [Upstream migrations arrive renumbered](#upstream-migrations-arrive-renumbered) |
| `crates/buzz-db/src/migration.rs` | `migrations.len()` assertion is 30, not upstream's 28; upstream's channel-index assertion reads `migrations[28].version == 29`, its long-reaction assertion reads `migrations[29].version == 30`, and the highest applied version is `Some(30)` | Counts embedded migrations, so it moves whenever *either* side adds one. `0027` landed without bumping it and left the test failing on `main`; fixed in PR #9. Beyond the count, every upstream assertion that indexes `migrations[…]` past 25 or names a version above 26 has to be shifted by the fork's two — see the section below for why the test suite will *not* catch it if you forget |
| `.github/workflows/macos-canary.yml` | new; `push` trigger on `main` with desktop path filters | Unsigned macOS canary; upstream only has a *signed* one, which a fork cannot run. Builds automatically when `desktop/**`, `crates/**` or the root `Cargo.*` change, so the newest artifact always matches `main` — it was dispatch-only, and the sole artifact went 13 commits stale. Free: the repo is public, so GitHub-hosted macOS runners are unbilled. Stages the artifact and the usage notes under the product name read from `tauri.conf.json`, not a hardcoded one, so the brand rename below cannot publish a build under the old name. Sets `signingIdentity: "-"` in its inline config and runs **without** `--no-sign`, which would silently discard it; asserts the bundle signature of the `.app` inside the mounted DMG. Its **sidecar list must track upstream's non-Windows lanes**: `tauri.conf.json`'s `externalBin` is shared, and `scripts/bundle-sidecars.sh` exits 1 on a missing binary, so a sidecar upstream adds breaks this workflow without ever conflicting — `buzz-backend-kubernetes` (#4289) did exactly that in the 2026-08-03 sync |
| `Dockerfile` | `buzz-paymaster` added to the cargo build, the strip step, and both `COPY` stages | The sponsor ships in the relay's image so there is one publish pipeline and one immutable `:sha-<7>` tag for `deploy-aws.yml` to pin. Four one-line additions, each inside an existing parallel list, so a conflict resolves as *keep ours, take upstream's*. It is **not** the `ENTRYPOINT` — `infra/aws/paymaster.tf` overrides `entryPoint` |
| `.github/aw/actions-lock.json` | new | gh-aw action SHA pins |
Expand Down Expand Up @@ -245,9 +245,16 @@ database**, so the side with *applied history* keeps it — the fork. Same shape
collision, opposite resolution, because "already deployed" points at different
parties in the two cases.

`0027_channels_id_lookup_index.sql` (upstream #4647) was the first of these, in the
2026-08-05 sync, and it is worth knowing exactly how it fails because **nothing in
the test suite objects**:
It has now happened twice running — `0027_channels_id_lookup_index.sql` (upstream
#4647) in the 2026-08-05 sync, then `0028_long_reaction_payloads.sql` (upstream #3833)
in the 2026-08-06 sync, renumbered to `0029` and `0030`. Expect it on any sync that
touches `migrations/`, and note that the *second* collision is the more dangerous
shape: upstream's 0028 landed on the fork's 0028, so the two files sorted adjacent and
the tree looked plausible. **A new file under `migrations/` is the tripwire — check the
version integer before reading anything else in the diff.**

The first one is worth keeping in full because it shows exactly how the failure hides
— **nothing in the test suite objects**:

- `sqlx::migrate!` accepts duplicate versions at compile time. `MIGRATOR.iter()`
simply yields two entries with version 27.
Expand Down Expand Up @@ -284,13 +291,26 @@ missing this is the live relay failing to start — which is why a new file unde

**The renumber is not finished when the file is renamed.** Upstream's own assertions
about its new migration are written against upstream's index and version, and they
merge cleanly into a tree where both are wrong. For 0027→0029 that was
`migrations[26].version == 27` → `migrations[28].version == 29`, plus
`applied_versions(…).last() == Some(27)` → `Some(29)`. Grep the test module for the
old integer rather than trusting the diff — these lines arrive as context, not as
conflicts. And whenever the renumbered migration carries a `kind = …` literal or any
other value the fork also touches, re-read the [event-kind](#fork-local-event-kinds)
notes before assuming the two are independent.
merge cleanly into a tree where both are wrong:

| Sync | Renumber | Assertions that had to move with it |
|------|----------|--------------------------------------|
| 2026-08-05 | 0027 → 0029 | `migrations[26].version == 27` → `migrations[28].version == 29`; `applied_versions(…).last() == Some(27)` → `Some(29)` |
| 2026-08-06 | 0028 → 0030 | `migrations[27].version == 28` → `migrations[29].version == 30`; `migrations.len()` 28 → 30; `applied_versions(…).last()` → `Some(30)` |

Only `migrations.len()` arrives as a *conflict*; every indexed assertion arrives as
clean context, which is why the diff will not point you at them. Grep the test module
for the old integer instead. A cheap independent check that the rename actually took:

```bash
ls migrations/*.sql | sed 's|.*/||' | cut -d_ -f1 | sort | uniq -d
```

Any output is a duplicate version, and the unit tests will still be green.

And whenever the renumbered migration carries a `kind = …` literal or any other value
the fork also touches, re-read the [event-kind](#fork-local-event-kinds) notes before
assuming the two are independent.

### Upstream `buzz://` links that are deliberately *not* rebranded

Expand Down
21 changes: 17 additions & 4 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,10 @@ desktop-tauri-test: _ensure-sidecar-stubs
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 under both compile states (clean + internal).
# Runs the auto-connect compiled-flag test twice with independently supplied
# expected values; build.rs rerun-if-env-changed triggers recompilation.
# Verify compiled-flag behavior under both compile states (clean + capability set).
# Runs the auto-connect and owner-only access focused tests twice with
# independently supplied expected values; build.rs rerun-if-env-changed
# triggers recompilation.
desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
#!/usr/bin/env bash
set -euo pipefail
Expand All @@ -223,10 +224,22 @@ desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
env -u BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY \
BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=false \
cargo test compiled_flag_matches_expected -- --ignored --nocapture
echo "=== Internal build (flag set) → expect true ==="
env -u BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false \
cargo test --lib
env -u BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=false \
cargo test compiled_policy_matches_expected -- --ignored --nocapture
echo "=== Internal build (flags set) → expect true ==="
BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1 \
BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=true \
cargo test compiled_flag_matches_expected -- --ignored --nocapture
BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY=1 \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=true \
cargo test --lib
BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY=1 \
BUZZ_TEST_EXPECTED_AGENT_ACCESS_OWNER_ONLY=true \
cargo test compiled_policy_matches_expected -- --ignored --nocapture
echo "Both compiled states verified."

# Build the full desktop Tauri app locally (unsigned, for testing)
Expand Down
46 changes: 24 additions & 22 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,30 @@ or mobile GitHub Release.
### Desktop

1. Run `just release-desktop <version>` from a clean, up-to-date `main` checkout.
The script fetches the current `origin/main`, regenerates
`version-bump/<version>` as one
deterministic candidate commit, records the frozen base and proposed
`desktop-v<version>` tag in `.release/desktop-candidate.json`, updates every
desktop manifest and lockfile, writes a full-SHA changelog, and opens or
updates the PR.
2. Review the recorded base and candidate SHA, the complete changelog, and CI.
The required **Desktop Release Candidate** check validates the exact head.
A trusted repository member, owner, or collaborator must approve that exact
candidate head. Any regeneration or push changes the head, invalidates the
prior approval, and requires both the checks and approval to run again.
3. **Squash merge** the PR. The protected branch must still be exactly the
recorded base; otherwise regenerate the candidate from current `main`.
4. `auto-tag-on-release-pr-merge` verifies the frozen parent, full-tree identity,
required checks, and trusted approval on the exact candidate head, then tags
the squash commit as `desktop-v<version>`. An admin or ruleset bypass does not
authorize desktop tagging.
5. The tag triggers `release.yml`. It builds and stages Apple Silicon and Intel
macOS, Windows, and Linux artifacts; publishes the versioned release only
after the complete set succeeds; then updates the rolling updater manifest
last for stable versions. A failed platform leaves no partially published
versioned release.
The script creates one deterministic candidate commit and records both its
frozen base and the verified prior release ledger in candidate metadata.
2. Review the exact candidate SHA, complete changelog, and CI. Regenerating or
pushing the branch creates a new candidate and requires checks to run again.
3. **Squash merge** the PR after all protected-branch checks pass. The merge is
the human authorization event; an authorized owner/admin bypass is treated
the same way. Unrelated changes reaching `main` do not invalidate the
reviewed candidate.
4. `auto-tag-on-release-pr-merge` verifies the closed event against GitHub's PR
identity, validates candidate content, and proves every required check came
from its trusted producer and was successful when the PR merged. It creates
`desktop-v<version>` at the exact reviewed PR head—not the squash commit.
Retries accept that tag only at the same SHA and never move it. GitHub does
not expose when an individual check rerun was created, so an ordinary rerun
after merge deliberately makes tag verification fail closed; inspect that
run and create a new candidate version rather than retrying the blocked tag.
5. The tag triggers `release.yml`. It builds and stages all platform artifacts,
publishes the versioned release only after the complete set succeeds, then
updates the rolling updater manifest last for stable versions.

Because squash merging leaves immutable candidate tags on side history, the next
release uses validated prior candidate metadata as its ledger boundary. It
includes unrelated commits after the prior frozen base and excludes exactly the
prior release's recorded squash commit; tag ancestry is deliberately irrelevant.

### Relay

Expand Down
1 change: 1 addition & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ out of the box with `just setup` or `just relay`. Common overrides:
| `BUZZ_REQUIRE_AUTH_TOKEN` | `false` | When true, REST requires NIP-98 (no `X-Pubkey` fallback) |
| `BUZZ_REQUIRE_RELAY_MEMBERSHIP` | `false` | When true, only pubkeys in `relay_members` can connect |
| `BUZZ_REQUIRE_MEDIA_GET_AUTH` | `false` | When true, `GET`/`HEAD /media/*` require Blossom kind 24242 `t=get` auth plus relay membership. |
| `BUZZ_DRAIN_JITTER_MS` | `0` (off) | Per-connection upper bound, in ms, for the random delay before each live WebSocket gets its `1012 Service Restart` close on graceful shutdown. `0` closes every socket at once (the previous behavior). A positive value spreads closes uniformly over `[1, value]` ms to avoid a reconnect thundering herd on rolling deploys. Values above `20000` are capped to `20000` (`MAX_DRAIN_JITTER_MS`) to leave close-frame delivery headroom under the relay's 30s hard-drain timeout. Empty or whitespace-only is treated as unset (off); a non-integer fails startup loudly. |
| `BUZZ_AUDIT_ENABLED` | `true` | Tamper-evident event/media audit log. Set `false`/`0`/`off` to skip its DB pool and writes. Does not disable the separate moderation audit trail. |
| `BUZZ_AUTO_MIGRATE` | `false` | Opt in with `true`/`1`/`yes`/`on` to run embedded SQLx migrations on relay startup |
| `RELAY_OWNER_PUBKEY` | unset | Bootstrapped as `owner` in `relay_members` at first start |
Expand Down
2 changes: 1 addition & 1 deletion VISION.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The relay enforces all access control. Channel membership is the only gate.
| Type | Visibility | Join | Create |
|------|-----------|------|--------|
| **Open channels** | Searchable by all members | Self-join | Any member |
| **Private channels** | Hidden, invite-only | Invited by member | Any member |
| **Private channels** | Hidden, invite-only | Invited by an owner/admin | Any member |
| **DMs** | Participants only | N/A (up to 9) | Any member |
| **Guests** | Scoped to specific channels | Invited | N/A |

Expand Down
Loading
Loading