Commit Graph

2813 Commits

Author SHA1 Message Date
Codex
944142f673 Merge owner preparation authority repair into Desktop deletion draft
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 17:19:03 +00:00
Codex
37e9b68c96 fix(db): restore owner deletion approval authority fence
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 17:17:08 +00:00
Codex
529064b123 Fence hosted deletion recovery by request and account generation
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 16:43:13 +00:00
Codex
2cc6c4ee42 Merge reconciled relay into published Desktop deletion draft
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 16:42:56 +00:00
Codex
225a11c858 Merge merged owner preparation foundation into relay deletion draft
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 16:42:29 +00:00
Luke Tornquist
bba75a71eb Automate owner deletion preparation (#7830)
## Why

Owner deletion admission stops at `submitted`. Manual inventory and
approval still stand between owner intent and the existing deletion
executor, preventing self-serve deletion.

## What

The existing `buzz-admin deletions drain` process prepares and
automatically approves operator-attested owner requests. Operator-origin
requests retain manual approval.

## How

The drain prioritizes approved work, then claims one owner submission
with the existing generation lease. It freezes inventory and records
digest-bound `owner_automatic` approval in one transaction before
entering the unchanged executor.

Before approval, the transaction rechecks archived state and current
ownership. It locks the deletion advisory fence, community, owner
memberships, and request in that order. A regression test pins
privileged abort during a live preparation lease.

What got simpler: one drain still owns preparation and execution. There
is no second worker, queue, lifecycle, or retry authority. The rebased
branch contains only this feature and its review fixes.

## Risk

Automatic approval permits irreversible deletion without a human grace
period. Operator attestation is not a cryptographic owner signature.
Provenance, locked authority checks, generation fences, and digest-bound
approval constrain the operation.

This remains work in progress. Broad test failures and incomplete mobile
checks are disclosed below; publication is not a merge-ready verdict.

## Testing

Published head: `e059ae9056b447ca3b5d0c2893da86550ef16f5c`. All seven
commit trees match tested source
`5605f27eaece1e809f746cc1839b55c7ceef8d80`; only attribution metadata
changed. Source-SHA results below are not new-head execution receipts.

At source commit `5605f27eaece1e809f746cc1839b55c7ceef8d80`, isolated
PostgreSQL tests showed stale archive and ownership fixtures fail closed
after the fix. Before the fix both reached automatic approval. Removing
abort's generation increment or stage transition made the new live-lease
regression fail; restored production passed.

`just test` failed in two ACP native-git fixtures and one agent
body-timeout test. The failing source files match the pinned base, but
there is no exact-base execution comparison proving those failures
unrelated. `just test-unit` also failed in the ACP fixtures, leaving 18
tests unrun. `just ci` stopped in Flutter tooling package resolution;
separately run non-mobile lanes do not constitute a full `just ci` pass.

No live tenant deletion or human testing was performed. A dedicated
populated `0052` → `0053` approval-backfill fixture remains a coverage
gap. Final-head hosted CI and security review are required.

## Bigger picture

Rebased directly onto main `12670bd0f037c66a682272bb81c46c3f254fad74`;
prerequisite #7818/#7827 are already merged. Main owns
`0052_channel_artifacts`, so this feature's automatic-approval migration
is now `0053`. Client/KGoose and quota work are separate PRs and are not
part of this diff.

Generated with Codex

---------

Signed-off-by: Codex <noreply@openai.com>
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-09-29 11:39:33 -04:00
Codex
4ba7f3f87d merge relay deletion review corrections
* private/community-delete-relay-followup:
  fix deletion admission and quota lifecycle proof

Signed-off-by: Codex <noreply@openai.com>
2026-09-29 10:48:03 +00:00
Codex
0d7798cb45 fix deletion admission and quota lifecycle proof
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 10:48:03 +00:00
Codex
b925d6a6dd fix community deletion recovery fencing
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 10:48:03 +00:00
Codex
a77fc33a8b Merge reconciled Relay into Desktop deletion flow
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 02:28:53 +00:00
Codex
bd95c4a984 fix(relay): return stable deletion lifecycle conflicts
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-29 02:27:25 +00:00
Codex
3b94325cd4 Merge owner deletion foundation and current main
Signed-off-by: Codex <noreply@openai.com>
2026-09-29 02:23:20 +00:00
Codex
99fa5aef4b chore: reserve migration 0054 for deletion quota
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-29 02:21:58 +00:00
Codex
c5edf47ca0 Merge current main into owner deletion foundation
Preserve current main migration 0052 and resequence the unchanged owner auto-approval migration as 0053.

Full verification gates remain deferred pending the recorded environment diagnosis.

Signed-off-by: Codex <codex@openai.com>
2026-09-29 01:01:30 +00:00
Bradley Axen
12670bd0f0 feat(relay): implement NIP-AR channel artifacts (#7919)
## Summary

Implements NIP-AR channel artifacts (spec merged in #7791) on the relay,
after a simplicity pass over the earlier two-agent implementation.

An artifact is an editable record (kind 45010) with a stable identity
(`d`), one home channel (`h`), and full-snapshot revisions chained by
`prev`:

- **Writes:** one transaction per write. It locks the artifact's head
row, requires `prev` to name the current revision, records the revision
in a ledger that survives retention, and advances the head. Two
competing edits can't both win. A stale edit gets `conflict: artifact
head changed`. Resubmitting an accepted event succeeds without applying
it twice.
- **Permissions:** writes pass the same gates as a kind 9 message in
`h`. A move also passes them in the source channel. The move stores the
destination snapshot and a relay-signed kind 45011 removal marker for
the source in the same transaction, so both channels recover by replay.
The marker never names the destination.
- **Moderation:** redaction is the ordinary kind 9005 removal. The
ledger keeps the ID so `prev` checks still work. Redacting the current
revision retires the artifact.
- **Reads:** HTTP `/query` and `/count` accept explicit `artifact:
current|history` filters with exact multi-character tag matching (e.g.
`#assignee`). Generic REQ and search return only current revisions. Kind
5 against artifacts is rejected. NIP-11 advertises the limits.
- **Spec:** NIP-AR updated to match: kind 9 permission checks, 9005
redaction and retirement, no creator-only delete rule.

A separate small commit routes `insert_channel_head_checked` through the
metered writer pool (`acquire_writer`), like every other event write in
that module.

### Related issue

Follows #7791. No separate issue found.

### Testing

- Clippy clean.
- Unit lane passes. Failures seen along the way came from agent env
leaking into `buzz-acp` tests (they pass in a clean env) and known
timing flakes (`keepalive_resets_idle_past_deadline`, two `buzz-agent`
databricks tests that passed on retry). This diff doesn't touch those
crates.
- Postgres lane passes, including new `artifact_postgres_tests` in
`buzz-db` and `buzz-relay`. Three presence tests need `REDIS_URL` set.
- Docker smoke against a fresh local relay, 29/29: create and idempotent
resubmit, update and stale conflict, current, `#assignee` and history
queries, generic REQ, kind 5 rejection, delete and restore, move with
source marker, private-channel isolation, and 9005 retirement.
- A pasteable curl walkthrough of the same flows was shared with the
reviewer.

Draft until the human hands-on check (AGENTS.md step 3) is confirmed.
The pre-push hook was skipped because every lane above had already run
on this tree. CI is the gate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Honey <8e307ae0076a4dab6b94b036ea3edc7e08f823a625269c1e6919e881a048b4d2@buzz.block.builderlab.xyz>
Signed-off-by: murderbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Co-authored-by: Honey <8e307ae0076a4dab6b94b036ea3edc7e08f823a625269c1e6919e881a048b4d2@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Smartie <fb3185faacfc3760b6b4fe0085a8878d8dd537f7a7671c7d3184fe1aa1b04df4@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
Co-authored-by: murderbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
2026-09-28 16:52:09 -06:00
crgallego
c96553eb13 fix(desktop): resolve unlisted project channel requests (#7619)
## Summary
- resolve project channel-creation approvals through the authoritative
home-channel selector
- permit owner-visible unlisted project homes when their channel and
repository binding match
- add regression coverage for the unlisted authoritative-home path

## Validation
- `just ci`
- focused project-home regression tests (10/10)
- full Desktop suite (6,451/6,451)
- `pnpm check`
- `pnpm build`

## Manual verification
After merging into a Desktop build, approve the pending LBX Copywriting
workroom request from the Litbox OS project.

Signed-off-by: crgallego <chris.cokerconstruction@gmail.com>
Co-authored-by: crgallego <chris.cokerconstruction@gmail.com>
2026-09-28 15:46:07 -07:00
Codex
f79fa813f8 Merge main into owner deletion auto-prepare
Preserve the single durable deletion drain and operator-attributed owner preparation while retaining the merged #7827 scheduler contract.

Signed-off-by: Codex <codex@openai.com>
2026-09-28 22:35:54 +00:00
Codex
9485cdfd62 test: abort quota fixture through store contract
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit 546b68640a)
Signed-off-by: Codex <noreply@openai.com>
2026-09-28 22:26:28 +00:00
Codex
96692c433d test: cover aborted owner quota reservation
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit 340ca56d36)
Signed-off-by: Codex <noreply@openai.com>
2026-09-28 22:26:27 +00:00
Codex
2b35ca0abc test: advance migration lock expectation to 0053
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit 0a4717d5a2)
Signed-off-by: Codex <noreply@openai.com>
2026-09-28 22:26:27 +00:00
Codex
d385c1f975 docs: describe authoritative owner quota projection
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit b50d7f833e)
Signed-off-by: Codex <noreply@openai.com>
2026-09-28 22:26:26 +00:00
Codex
546b68640a test: abort quota fixture through store contract
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 22:24:21 +00:00
Luke Tornquist
6918dd3a83 Schedule the deletion drain safely (#7827)
## Why

The deletion engine already exposes a one-shot `buzz-admin deletions
drain` command, but operators have no safe way to schedule it. A generic
workload wrapper could expose arbitrary commands or inherit relay
credentials, which would add a second control plane and widen the blast
radius.

## What

This PR adds an opt-in Kubernetes CronJob for the typed deletion drain
command. It is stacked on #7818, which adds owner-request admission.

## How

The chart keeps the job disabled by default. The CronJob uses `Forbid`,
zero Kubernetes retries, bounded runtime and history, and only the
database, Redis, and object-store settings required by the executor.
PostgreSQL leases, checkpoints, and retry timing remain the sole durable
retry authority.

The closed `_operator-jobs.tpl` helper centralizes safe CronJob
mechanics without creating an arbitrary command or environment registry.
This leaves the touched system simpler than a general job framework
would.

## Risk

The deployment blast radius is low because the job is disabled by
default. When enabled, it can run destructive deletion work that the
database has already approved, so the chart limits concurrency, retries,
credentials, and execution time.

## Testing

Rendered every `deploy/charts/buzz/ci/*.yaml` fixture with `helm
template`; all passed at `e68cb753bb6673aa1baa60046969bdb2d4738b62`.

Ran the full independent Desktop, Tauri, and web lanes; all passed at
`e68cb753bb6673aa1baa60046969bdb2d4738b62`. Flutter package resolution
made no progress within three minutes, so the mobile lane is not claimed
green.

## Bigger picture

A later stack layer must inventory and approve admitted owner requests.
Until then, this scheduler cannot make a new owner request destructive.

Generated with Codex

---------

Signed-off-by: tornquist <tornquist@squareup.com>
Signed-off-by: Codex <noreply@openai.com>
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
chart-v0.1.10
2026-09-28 18:23:42 -04:00
Codex
340ca56d36 test: cover aborted owner quota reservation
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 22:23:38 +00:00
Codex
0a4717d5a2 test: advance migration lock expectation to 0053
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 22:20:41 +00:00
Codex
b50d7f833e docs: describe authoritative owner quota projection
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 22:17:20 +00:00
Codex
a1c9794da8 fix: route deletion receipts through writer acquisition
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit 3a7f9ab38a)
Signed-off-by: Codex <noreply@openai.com>
2026-09-28 22:15:39 +00:00
Codex
0f733dad29 fix(desktop): retain ambiguous deletion recovery
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 22:15:27 +00:00
Codex
3a7f9ab38a fix: route deletion receipts through writer acquisition
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 22:13:45 +00:00
Codex
0720b0a863 feat(desktop): add hosted community deletion flow
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 22:06:22 +00:00
Codex
e024135d68 feat: add owner deletion receipt and quota reservation
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 21:58:29 +00:00
Tom Brow
662014d544 fix(mobile): stale community selection during mobile invite setup (#7951)
Buzz sets up starter channels when a person accepts an invite. If the
phone already had a saved community, the first attempt could fail. Retry
could then succeed.

A delayed read could replace the new active community with the old one.
The read now leaves the saved selection unchanged. The fix also delays
channel-status updates until the channel provider finishes
initialization.

### Before and after

Controlled Flutter app captures against local relays, with the same
delayed storage read. The before capture restores the pre-fix
active-community provider in the test harness. The after capture uses
the fixed provider. These are separate from the native iOS validation
below.

| Before: first attempt fails | After: first attempt succeeds |
| --- | --- |
| ![Before: starter channel setup error with Retry setup
button](https://github.com/user-attachments/assets/14d8ba45-6e53-40ae-bc10-f219f08d2f92)
| ![After: joined community with Continue to welcome channel
button](https://github.com/user-attachments/assets/7dc32d9d-3aae-4378-913c-8ac68cc7d930)
|

### Validation
- Regression tests reproduce the delayed-read ordering and passing
timing control; removing the channel sequencing safeguards makes their
regressions fail.
- Production app against local relays passes the forced race on the
first attempt.
- Native iOS simulator with an existing saved community accepted the
original invite using a fresh identity, joined on the first attempt, and
opened the welcome channel without Retry.

Signed-off-by: Tom Brow <tomb@block.xyz>
mobile-v0.19.0-rc.1 mobile-v0.18.0-rc.2
2026-09-28 14:44:43 -07:00
Elrond
3c8def0507 Merge main-refreshed operator cron base
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-09-28 16:56:49 -04:00
Elrond
7a26420b67 Merge main and retain upstream scroll readiness fix
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-09-28 16:56:20 -04:00
Elrond
d8c920ce5c Merge sidebar snapshot test repair from operator cron base
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-09-28 16:52:25 -04:00
Codex
5650ae7a5d test(desktop): record transient snapshot before fallback
Signed-off-by: Codex <noreply@openai.com>
2026-09-28 20:50:54 +00:00
Logan Johnson
f1e50be410 test(desktop): wait for channel head refresh before paging thread summary test (#7955)
🤖

## Summary
Main's Desktop Smoke E2E (4) job fails on `scroll-history.spec.ts`
("thread summary badge survives a retained older-history prepend"). This
change fixes the test's timing. It does not change app code.

When a channel opens, the app starts a live subscription. When that
subscription is ready, the app fetches the newest page again, and that
fetch replaces the whole loaded history. This test scrolled up before
that refresh happened. On slower CI runners, the refresh came after the
older page loaded but before it showed on screen. The older rows went
away, the reader stayed at the top, and no more pages loaded. All three
attempts failed that way.

The test now calls `waitForMockChannelHeadReady` before it scrolls, like
the other pagination tests in this file already do. That helper waits
for the live subscription and its refresh to finish.

### Related issue
Main CI failure at `15d44dcc`
(https://github.com/block/buzz/actions/runs/36470599751/job/109092076235).
The same failure was reported on #7827.

### Testing
- Added temporary logs to see the order of events. Locally, the refresh
ran after the older page had loaded: the loaded history went from 2
pages back to 1. The test passed only because it checked before the next
render. With the wait, the refresh runs first and nothing replaces the
loaded pages later.
- `playwright test --project=smoke tests/e2e/scroll-history.spec.ts`: 18
passed. This test with `--repeat-each=8`: 8 passed.

### Follow-up (not in this PR)
The app has the same race for real users. If someone scrolls up before
the live subscription is ready, or during a reconnect, the refresh can
drop the older history they loaded. If they are at the top of the list,
paging stops until they scroll down and back up.

Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-28 14:44:16 -06:00
Will Pfleger
9b1c4f98e2 🤖 perf: bound long-thread aux reads and make query deadlines terminal (#7854)
Long threads (over ~100 replies) time out in desktop. The aux hop that
loads reactions and other e-tag references builds its filter as one
`tags @> …` term per id, joined with `OR`. Past about 100 ids the
planner switches to walking each partition's primary key backward,
expecting to stop early. When few rows actually match, it scans the
whole community. With a dirty GIN pending list, a 188-reply thread never
finishes.

### What changes

- **Set-based e-tag reads (`buzz-db`).** The aux e-tag query now puts
the id set behind a `MATERIALIZED` CTE and matches with `ANY`. Each
e-tag read also runs under a transaction-local `statement_timeout` of 20
s.
- **Terminal timeout contract (`buzz-relay`).** When a read hits the
deadline, the client gets a terminal timeout (`CLOSED` for REQ, the
timeout error for COUNT and search), not a retryable failure.
- **Subscription lifecycle (`buzz-relay`).** The per-connection
subscription map lock is now the lifecycle lock. Claims, replacement,
client `CLOSE`, owner-checked timeout and search cleanup, revoke, and
connection teardown all change the map, the registry, topic refcounts
and terminal frames while holding it. As a result, a timed-out
subscription's `CLOSED` goes out before any replacement with the same id
can claim it, and claims made after teardown are refused.
- **Dropped terminal frame disconnects (`buzz-relay`).** If the `CLOSED`
for a retired subscription (owner-checked timeout or channel eviction)
can't be queued, the relay cancels the connection instead of leaving the
client holding a subscription that will never receive fan-out. A
client-initiated `CLOSE` ack is exempt.
- **COUNT under the same deadline (`buzz-db`).** The fast COUNT path
runs e-tag counts under the same transaction-local 20 s cap as SELECT,
so WS COUNT and HTTP `/count` produce the terminal timeout too.
- **Replica reads surface the deadline (`buzz-db`).** When a
replica-routed read is cancelled by `statement_timeout` (SQLSTATE
`57014`), the error is returned instead of re-running the query on the
writer, which would run the same slow statement. Other replica errors
still fall back to the writer.
`PLANS/REPLICA_FULL_READ_ROUTING_DESIGN.md` records the exception.
- **Deadline handling (desktop).** Only the relay's `query timed out`
counts as a query deadline. A client-side `request timed out` stays
retryable. For one-shot reads a deadline is terminal: the global query
retry (budget 1), thread-replies retry and reaction hydration don't
re-send the same slow query. Reaction ids held back by a deadline are
released when the channel is opened again, so reopening retries them
while same-channel re-renders don't. Live subscriptions, in both the
renderer and the native archive-sync socket, keep treating a deadline
`CLOSED` as transient: they back off and resubscribe with the same
filter instead of being dropped.
- **Deadline handling (mobile).** Mobile recognizes the deadline on both
transports: HTTP `503 {"error":"query timed out"}` and the WS `CLOSED`
reason.
- The root `ProviderScope` retry policy returns no retry for deadline
errors and keeps Riverpod's default backoff for everything else.
  - Background thread-summary recounts don't retry a deadline.
- Forum posts polling pauses while the last load settled on a deadline;
reopening the forum retries.
- A channel's newest-window load surfaces a deadline instead of falling
back to legacy WS history, and the channel list's batched queries return
"unavailable" on a deadline instead of fanning out to per-filter
fallbacks. An unavailable batch is not treated as empty. Known
last-message timestamps and unread state are kept, and unread catch-up
moves `lastMessageAt` forward (never backward) the same way live events
do, so a cold start with no known timestamp still gets one.
- A live subscription that gets a deadline `CLOSED` backs off and
resubscribes with the same filter. History requests still fail on it.
- Settled load failures show a Retry control, matching desktop's
thread-replies error card: a shared `LoadErrorView` on the channel
timeline, forum thread and forum posts list, and an icon button beside
the thread replies summary. While the thread query has no value, the
thread page shows the route snapshot plus live, cached and optimistic
replies, with the loading or error status still visible.
- Other re-sends (reconnect, foreground resume, live events, the unread
backstop, scroll) are not suppressed; each is bounded by the relay's 20
s statement deadline.
- **Long-thread seeder.** `just seed-long-thread [replies]` posts one
thread (default 187 replies) with some reactions to the local dev relay,
for reproducing long threads in the apps. It uses a throwaway key,
refuses non-loopback relays and ignores ambient `BUZZ_*` credentials.
Writes are paced to fit the stock relay rate limit.

**Lifecycle concurrency.** The lock serializes one connection's
subscription changes. Other connections can still be delayed by pressure
on the shared pubsub command queue, and by serial eviction loops waiting
behind a blocked connection. This PR does not claim strict isolation
between connections.

**Recovery vs. load.** Clients stop automatic retries of the same
one-shot read, but they don't remember deadlines across other triggers:
reconnect, resume, live events and the unread backstop can each send the
read again later, bounded by the 20 s statement deadline. A live
subscription that hits the deadline resubscribes with its original
filter under the existing 1–30 s backoff, and against a filter that
keeps timing out it retries at the 30 s cap indefinitely, so the worst
case is one bounded query per subscription every 30 s. `since` is
deliberately not advanced to now on resubscribe, because that would skip
events missed while the subscription was closed. Unchanged from `main`:
when mobile's channel history sync fails with cached messages available,
the cached messages are shown without the error or Retry control.


### Benchmark

Local reproduction on an isolated bench stack (not committed):
- PG16, migrations applied, one 188-reply thread plus 5M background
events in the same community.
- Each request replays the desktop thread-replies filter through the
relay, with `include_aux` on. Times are total ms for 3 runs, with a 60 s
client cap.

GIN pending-list state is controlled with `VACUUM` (clean) or by
refilling the pending list (dirty), and recorded per partition with
`pgstatginindex`. Dirty runs held 402 pending pages on every partition's
tags index at the start and end of each matrix. Clean runs held 0–1
pages (relay startup writes).

| build | GIN pending state | limit 10 | 50 | 100 | 200 (188 ids) |
|---|---|---|---|---|---|
| `main` | clean | 26 / 16 / 13 | 56 / 51 / 52 | 92 / 93 / 95 | 662 /
639 / 641 |
| `main` | dirty | 315 / 298 / 327 | 22141 / 15888 / 16769 | 28992 /
27819 / 28080 | timeout ×3 (>60 s) |
| this PR | clean | 35 / 21 / 14 | 57 / 49 / 48 | 82 / 80 / 75 | 153 /
150 / 135 |
| this PR | dirty | 290 / 254 / 251 | 1257 / 1204 / 1206 | 2432 / 2378 /
2382 | 4333 / 4706 / 5104 |

Baseline `origin/main` `b37e47721`; this PR at `5f2650f5f`, whose e-tag
read builders are identical to the current head. At head `1ad81d868`
against `origin/main` `15d44dcce`, limit 200 measured 145–179 ms clean
(`main` 683–715) and 4.41–4.51 s dirty (`main` timeout ×3).

Every completed cell returns the same signed event set: 209 events, 187
replies at limit 200. With `include_aux` off, every cell is ≤66 ms. The
thread-window loader (`select_aux`) is unchanged from `main`, and its
timings and captured plans match `main`'s.

Actual aux-hop statements captured from each relay binary with
`auto_explain` (analyze + buffers), execution ms:

| build | clean, 100 ids | clean, 188 ids | dirty, 100 ids | dirty, 188
ids |
|---|---|---|---|---|
| `main` (N-way `OR`) | 40 | 298 | 28785 | 62892 |
| this PR (`MATERIALIZED` fence + `ANY`) | 23 | 45 | 1992 | 3539 |

**What the fence changes**
- On `main`, the planner combines `ORDER BY created_at … LIMIT` with an
overestimated match count and walks each partition's primary key
backward, expecting to stop early. With few real matches it scans the
whole community: the dirty 188-id statement touches ~4.9M shared buffers
on `main` vs ~0.6M with the fence.
- The fence prevents that early-stop plan. Across the measured cases
(1–188 ids, both pending states), the planner used the GIN tags index.
The fence doesn't force GIN in general; a broad enough filter could
still get a different plan.

**Tradeoff:** the fence materializes every match before sorting. In this
bench small limits stayed within run-to-run noise of `main`. A single
hot id (5,000 reactions, limit 100) measured 30–34 ms unfenced vs 35 ms
fenced.

**Deadline:** each e-tag read (SELECT and COUNT) runs under a
transaction-local `statement_timeout` of 20 s. It only tightens: a
shorter operator timeout (`BUZZ_DB_STATEMENT_TIMEOUT_MS`) is kept. It's
a per-statement limit, not a per-request one, so a request that runs
several pages or hops can take longer than 20 s in total. The benchmark
does not force the deadline.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
2026-09-28 16:36:40 -04:00
Elrond
c18281b79e Merge commit '131ef21a253367db5e092fd6a392398e4b6eae93' into elrond/publish-7830-refresh
* commit '131ef21a253367db5e092fd6a392398e4b6eae93':
  test(desktop): await channel head before scroll summary check

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-09-28 16:08:33 -04:00
Codex
131ef21a25 test(desktop): await channel head before scroll summary check
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 20:06:50 +00:00
Jordan Mecom
cc36d25bec Add native HPKE encryption for nsec backups (#7849)
## Summary

Add a Rust API that encrypts an nsec into a backup envelope using RFC
9180 Base mode: DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, and AES-256-GCM.

---------

Signed-off-by: jm <jm@squareup.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 12:46:43 -07:00
Bradley Axen
548718e042 test(desktop): scope video menu e2e probes to emitted messages (#7953)
## Summary

Fixes the `Desktop Smoke E2E (4)` failure on `main`. The test at
`desktop/tests/e2e/video-attachment.spec.ts` ("right-click menus expose
distinct selectors for links, relay video, and off-relay video") fails
3/3 attempts on main and on every PR branch since ~Sept 25. Because
"Desktop" is a required check, that blocks merges.

**Cause:** the relay-video and off-relay-video probes picked their
player with `getByTestId("video-player").last()`. When mock messages
share a second, timeline ordering can put a different video last. The
locator then resolves to the wrong player, and the "Download video" menu
detaches before the click, so the test times out.

**Fix:** scope each probe to the `data-message-id` of the message
`emitVideoMessage` just returned. Test-only change, 16 lines.

## Attribution

This is a cherry-pick of `dfd5db4e0` by @tornquist, originally part of
#7827 (the deletion-drain cron, which is stacked on another feature
branch). It's pulled out on its own so main can go green without waiting
on that stack. Authorship is preserved.

## Testing

- `pnpm build:e2e && playwright test tests/e2e/video-attachment.spec.ts
--project=smoke`: 13/13 passed locally, including the target test.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: tornquist <tornquist@squareup.com>
Signed-off-by: murderbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Co-authored-by: tornquist <tornquist@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-28 19:46:14 +00:00
Elrond
9c175de395 Merge rebased operator CronJob base without changing owner preparation
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-09-28 15:15:15 -04:00
tornquist
38db86befc Scope video menu probes to emitted messages
Bind each context-menu interaction to the exact mock message returned by the emitter so same-second ordering cannot select the wrong video.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-28 15:12:21 -04:00
tornquist
cdef026926 Clarify deletion job workload identity
Distinguish the disabled Kubernetes API token mount from provider workload-identity credentials while retaining the dedicated service-account recommendation.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-28 15:12:21 -04:00
tornquist
c0a392a487 Bound operator CronJob names
Share a suffix-preserving 52-character name helper between the deletion drain and storage accounting CronJobs while retaining their existing short names.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-28 15:12:21 -04:00
tornquist
d791f86a3d Cover deletion drain chart contracts
Exercise both supported credential shapes through the CI render matrix and bind the typed job guards, schema boundary, pod identity, and default sidecar annotation.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-28 15:12:21 -04:00
tornquist
5c3af3b4c0 Correct the deletion drain runbook's operational claims
The runbook told operators to target `cronjob/<release>-buzz-deletion-drain`.
`buzz.fullname` collapses to the release name when it already contains the
chart name, so the documented name is wrong for the documented install:
`helm install buzz ...` renders `buzz-deletion-drain`. Discover the CronJob by
its component label instead, and describe the name rule rather than a single
guessed spelling.

Three more corrections:

`activeDeadlineSeconds` was presented as if a timed-out run were just another
retry. It is not recorded as one — shutdown releases the claim without
recording a retry, and only the object-store drain resumes mid-stage — so a
deadline landing repeatedly inside a non-resumable stage loops forever with a
rising `attempts`, a flat `retry_count`, and no block. Document how to spot
that from both the request and Kubernetes, how to size the deadline, and how
to recover.

`terminationGracePeriodSeconds` was presented as a clean handoff. Document
that a pod still working at the end of the window is SIGKILLed holding its
lease, and that recovery is lease expiry plus reclaim under a new generation.

An empty `serviceAccountName` was described as a neutral default. It inherits
the relay's service account; `automountServiceAccountToken: false` hides the
projected token but does not detach cloud IAM bindings resolved through the
node metadata path. Recommend a dedicated pre-created account when the
executor's IAM blast radius should be smaller than the relay's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-28 15:12:21 -04:00
tornquist
07312054db Reject reserved deletion job pod labels
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-28 15:12:21 -04:00
tornquist
e56f4d4c3a Add typed deletion drain operator CronJob
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-28 15:12:21 -04:00