Commit Graph

6 Commits

Author SHA1 Message Date
Will Pfleger
0a06c0b3cb feat(relay): log reads, logins and connections on access target (#8376)
🤖 Adds an access log for relay reads, logins, connections and HTTP
requests on the dedicated `buzz_relay::access` target. It writes to
stdout, not `audit_log`, so reads never take the audit advisory lock.
The log is shaped for volume: one line per HTTP request, one line per
WebSocket read, and one line per WebSocket connection.

## Lines per operation

| Operation | Lines |
|---|---|
| HTTP request on a matched route | 1 (`http`), except the skip list
below |
| HTTP request matching no route | 1 (`http`, `route="<unmatched>"`) at
status 400 and above, otherwise none |
| WebSocket connection | 1 (`close`, carrying the login) |
| WebSocket `REQ` / `COUNT` / NIP-AR sync | 1 each (`req` / `count` /
`sync`), plus the `buzz_ws_reads_total` counter |
| WebSocket NIP-42 login | 1 (`auth`) per decided or cut-off attempt,
capped after the decision as below |
| Audio login | 1 (`auth`, at its final admission check) |
| WebSocket `EVENT` | 1 (`Event ingested`, not on the access target) |

## HTTP requests
One middleware, `http_access_log`, writes every HTTP line. It sits
between `track_metrics` and `http_trace_layer`, so rebuilt errors,
body-limit refusals and localhost refusals all reach it. It writes the
line when the response is ready, which is not proof the body was
delivered. A request dropped before then logs `request=aborted` at WARN
with no `status`.
- Every line carries `route` (the matched route template, never the raw
path), `peer_addr` when the listener has one, `status`,
`time_to_response_ms` and `http_method` (the HTTP verb). The verb stays
on the line itself because the `http.request` span is absent under an
access-only filter and when a pending request is dropped.
- Handlers record what they learn on the request's `AccessSlot`: the
login (`method`, `pubkey`, `pubkey_verified`, `community`), the
decision, the filter summary, and the route's own fields. The decision
is `outcome` (`success` / `denied` / `failed` / `challenged`) with a
fixed `reason`, or `undecided` when the route recorded none. A refusal
is final: a later admission doesn't replace it, so a replayed NIP-98
proof that verifies and is then refused logs `failed` / `nip98_replay`.
- `outcome` describes the login. What the route did is in `status` and
the route's fields: a rejected `/events` write logs `outcome=success`
with `status=400`, `accepted=false` and its `reject_reason`.
- Route fields:
- `/query` and `/count`: the filter summary and `result_count`. An
incremental `/query` response (`{changes:[...]}`) counts its changes,
removals included. Artifact reads record their summary before the query
runs, so a read dropped mid-query keeps it.
- `/events`: `accepted`, `kind`, `event_id`, and on a refused write the
parse `category` / `line` / `column` or a fixed `reject_reason` (such as
`invalid` or `restricted`), never the ingest message.
- Invite mint: `invite_id`, `expires_at` and `max_uses`. Invite claim:
`joined`.
- Admin feedback attachment reads: `feedback_id` and
`attachment_sha256`. With `BUZZ_ADMIN_AUTH=disabled` they log
`method=network_trusted` with no key, and Host or Origin refusals log
their fixed reason.
  - Every other route logs only the login fields.
- No field can hold an invite code, a token, or a Blossom read hash.
Media routes log the `/media/{sha256_ext}` template.
- Skip list: NIP-11 and other non-upgrade requests on `/`, `/info`,
`/.well-known/nostr.json`, `/health`, `/_liveness`, `/_readiness`, and
the SPA and static files the relay serves when enabled (web assets,
invite landing pages, git web pages, the admin bundle) at any status.
Successful WebSocket upgrades (101) log no `http` line. Refusals before
the upgrade on `/` and the audio path log one, with the status actually
sent.
- Routes that aren't Nostr-key logins (the command-token disconnect
endpoint, the workflow webhook, the git server's internal callback) log
`outcome=undecided` with no key.
- Dev-mode `X-Pubkey` requests log `method=x_pubkey`,
`pubkey_verified=false`.
- A successful `/query` or `/count` that returned nothing
(`result_count=0`) logs at DEBUG: on staging, 93% of `/query` lines were
empty polls. Denied, failed, aborted and error reads, reads with no
`result_count`, writes and logins keep their levels.
- Other successful lines and git's credential-less first request
(`challenged`, with its 401) are INFO. Refusals, statuses of 400 and
above, and aborted requests are WARN. In Enforce mode the line records
the remapped status (for example 403). Denial bodies and headers are
unchanged.

## WebSocket reads
- `REQ` / `COUNT` / `sync` lines carry `pubkey`, `conn_id`, `sub_id`,
the filter summary, the outcome (`served` / `denied` / `aborted` /
`error` plus a fixed reason), and `events_queued` (REQ and sync) or
`count` (a COUNT that counted). A `REQ` is `served` only once its EOSE
is queued to the client. A read logs WARN only when it ends in `error`.
`served`, `denied` and `aborted` are INFO, since `aborted` means the
read ended before completing: the client disconnected or sent CLOSE, a
same-ID `REQ` replaced it, or the session was cancelled.
- A NIP-AR sync subscription writes its `sync` line when its initial set
completes, not when live delivery ends. Its summary is the registered
live filter (kinds and channels, never artifact predicates), and
`events_queued` counts the initial set.
- `REQ` lines stay at INFO even when empty at EOSE, because the
subscription can still deliver live events later.
- Filter summary, shared with HTTP `/query` and `/count`: `kinds`,
channel ids, authors and `p` values each list the first 10 distinct
values. The exact `kind_count` / `channel_count` / `author_count` /
`p_count` (summed per filter) appears only when the list is `*` or shows
fewer values than the filters held. A dimension that any filter leaves
open logs `*`. `since` / `until` / `limit` take the widest bound and are
absent when any filter leaves them open, so no field understates what
the read covered. `ids` is a count only. NIP-50 search logs `search:
true`, never the text. Only well-formed channel ids and pubkeys are
listed. An artifact read's summary uses only the standard dimensions of
each raw filter, so artifact predicates and client-defined tags (such as
`#1`) are never logged. The summary is skipped when the access target is
off. Under `=warn`, every WARN line, HTTP or WebSocket, still carries
it.
- Per connection, only the first 16 reads that are refused before they
run (`auth_required`, quota, concurrency, overload) or denied are
logged. The rest are counted as `reads_suppressed` on the close line. A
refusal that arrives after the close line has been written, from a read
still running when the client left, logs its own line, so every refusal
is either logged or counted, never both. Served reads are never capped.

## WebSocket read counter
`buzz_ws_reads_total{type="req"|"count"|"sync"}` counts every parsed
`REQ`, `COUNT` and NIP-AR sync frame before admission, exported at zero
from startup. It is a metric, so it counts at any `RUST_LOG`, including
production's current `error`.

## WebSocket logins
A NIP-42 login writes one `auth` line when it is decided. A successful
login logs INFO `outcome=success` at its commit point, decided under the
connection's close-reason lock, so a disconnect racing the login
(session expiry, admin disconnect, revocation, ban or roster removal)
either wins, and the login logs no success, or loses and closes the
connection right after the success line. A refused or failed login
writes one WARN `auth` line with `outcome`, a fixed `reason` from the
shared `AuthReason` code set, `pubkey` and `pubkey_verified`. Root
WebSocket `auth` lines carry `peer_addr`. AUTH frames after the login is
decided are capped at 16 per connection, and the rest are counted as
`auth_suppressed`. An attempt cut off by close or session expiry logs
`aborted`. Audio logins log one `auth` line at their final admission
check.

## Connections
The close line is the connection's only line. It is owned by the
upgraded socket, so it is written exactly once however the connection
ends: a normal close, a refusal while checking the community is active
(`refused=not_admitted`), the connection limit
(`refused=connection_limit`), or the handler being dropped. It carries
`conn_id`, `community`, `peer_addr`, `duration_ms`, the login,
`events_queued` / `bytes_queued` (frames the writer accepted and
flushed, not client delivery), and the two suppression counters.

## Error text
- Database errors log as `DbError::kind()` (SQLSTATE or variant),
including the command-executor event insert. Search errors, on both read
paths and in the search-cancel task, log as `SearchError::kind()` / the
same SQLx classifier. Workflow database and webhook errors log as their
codes, since webhook URLs can be secrets.
- Git NIP-98 failures log a fixed code. Git subprocess stderr moved to
DEBUG because hook output can contain message text.

## Operator notes
`.env.example` documents the target, its `event` values (`http`, `auth`,
`req`, `count`, `sync`, `close`), the skip list, the levels, and how to
turn it down: `buzz_relay::access=warn` keeps refusals, errors and
aborted HTTP requests, and of WebSocket reads only those that end in
`error`, and `=off` drops everything. It also lists the removed lines:
`NIP-42 auth successful/failed`, `WebSocket connection
established/closed`, `event=connect`, `HTTP bridge request`, `Event
ingested via pipeline`, `relay invite minted`, `relay member added via
invite`, `admin feedback attachment read`, and `addr` (now `peer_addr`).
JSON lines no longer repeat the current span as `span`, since `spans`
already ends with it. A line logged under an explicit `parent:` span
loses that parent's fields. The default OTel export filter sets
`buzz_relay::access=off`, so access lines aren't exported a second time
as span events.

`peer_addr` is the TCP peer. `BUZZ_CLIENT_IP_HEADER` optionally names a
request header (for example `cf-connecting-ip` behind Cloudflare or
`x-real-ip` behind nginx, matched case-insensitively). When set, `http`,
`auth` (root and audio), `close` and connection-limit lines also log
`client_ip`: the rightmost comma-separated entry of the header's last
field, only if it parses as an IP address. It never replaces
`peer_addr`, and it is only trustworthy when every request arrives
through a proxy that overwrites the header.

---------

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-11-03 17:48:59 -05:00
Brad Seiler
1fe120165f BUZZ-272: Deny workflow runs for banned owners (#8242)
## Summary

A workflow whose saved owner is banned from the community can no longer
create runs through any trigger: webhook, channel-event, cron, or signed
command trigger. Every one of those doors already calls
`WorkflowEngine::check_owner_authority`, which re-checks the owner's
channel role. That gate now also reads the owner's authoritative
restriction state. A ban denies the run, and a failed lookup fails
closed. Unbanning restores admission.

On the webhook door the denial is the same generic 404 as before, and no
run is queued. On the event and cron paths, a ban denial is logged at
WARN at most once per workflow every ten minutes, and at DEBUG in
between. A failed lookup and every other denial still warn every time.
The handler-local ban lookup in `api/bridge.rs` is removed because the
shared gate covers it.

This is a point-in-time check: a ban committed after the lookup can race
with run creation. It does not cancel existing runs. Community timeouts
do not block runs in this change.

This PR also carries the shared mesh receive fix needed for independent
validation (see Shared carry).

### Related

BUZZ-272.

- Invitations: [#8237](https://github.com/block/buzz/pull/8237).
- Route coverage: [#8239](https://github.com/block/buzz/pull/8239).
- Live-session revalidation:
[#8241](https://github.com/block/buzz/pull/8241).
- Staging runbook: [#8235](https://github.com/block/buzz/pull/8235).

### Testing

Postgres lane:
- `on_event_denies_run_while_owner_banned`: a banned owner's
channel-event trigger creates no run, and after unban it creates one.
- `cron_tick_skips_banned_owners_without_consuming_the_claim`: one tick
skips a banned owner and an agent whose human owner is banned, while an
unbanned workflow on the same tick still fires. After unban, a tick for
the same scheduled instant fires both, so the denial did not consume
their scheduled-fire claim.
- `on_event_denies_run_when_restriction_lookup_fails`: the event path
fails closed when the restriction read errors, and fires once the read
succeeds.
-
`webhook_admission_enforces_owner_ban_tenant_scope_and_lookup_failure`:
the HTTP door returns the same generic 404 for a wrong secret, a banned
owner, a forbidden role and a failed restriction lookup, with no run
created. It also covers another tenant's allowed control and unban.

Unit lane:
- `ban_denial_warns_once_per_interval_while_other_denials_always_warn`.

Results:
- At `81c367f48`, CI passed: Unit Tests 572/572, PostgreSQL Tests
1169/1169 and Relay E2E. A local-relay E2E also passed:
  - unbanned webhook: 202 and one run;
  - banned webhook: generic 404 and no run;
  - banned event trigger: no run;
  - after unban: one run.
- At `72c8d8bbc` (merged with main), CI passed: Unit Tests (including
all 171 buzz-workflow unit tests), PostgreSQL Tests 1227/1227 (all four
ban tests listed by name), and both `Build (linux/amd64|arm64)` image
jobs, which build with `--locked`. A Blox run at the same head also
passed `cargo metadata --locked`, fmt and clippy, and the buzz-workflow
unit tests passed there with no database reachable.
- Not covered: the signed trigger has no dedicated test. It calls the
same gate, which was confirmed by reading the code.

### Shared carry

#8237, #8239, #8241 and #8242 each carry the same mesh-receive fix, boot
and scraper regressions, and push-lease fixture correction. Whichever
lands first owns them, and the others drop them on their next main
merge.

### What got simpler

The ban check now lives in the one gate every run-creation door already
shares, so there is one fewer handler-local branch. Cron, event and
signed triggers get the same protection without per-door code. The cron
loop body is now `cron_tick(now)`, so a test can drive one tick without
the 60-second sleep.

buzz-workflow's infra-free unit tests ran in no CI lane. `just
test-unit` and its cargo fallback in `scripts/run-tests.sh` now run
them.

buzz-review-completed

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

---------

Signed-off-by: seiler <seiler@squareup.com>
Signed-off-by: Brad Seiler <seiler@squareup.com>
Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:27:24 -04:00
Kalvin C
8a2c9af2db feat(deletion): add durable whole-community deletion (#4425)
## Summary

Adds a durable, operator-controlled V1 for deleting an entire Buzz
community without deleting another tenant's data.

The workflow is exposed through `buzz-admin deletions`:

- `sweep` records independent fleet storage-taxonomy observations
- `submit`, `list`, `inspect`, and `approve` manage a deletion request
- `unblock` resumes a fail-closed request after an operator records
remediation identity and reason
- `run` and `drain` execute bounded work

Requests advance through a PostgreSQL-backed state machine and stop at
`retention_pending` after logical deletion has been independently
verified across PostgreSQL, object storage, and Redis.

This PR ships the engine and CLI, not a continuously running worker or
Kubernetes packaging. For V1, a cluster/VM administrator invokes
`/usr/local/bin/buzz-admin` from the existing relay image, for example
with `kubectl exec` or an equivalent container/VM exec path.

## What whole-community V1 removes

For the target community, V1 removes:

- rows from the allowlisted community-scoped PostgreSQL catalog,
including members, profiles, authored events and bodies, DMs, reactions,
mentions, memberships, tokens, workflows, moderation, audit, feedback,
and rate-limit state
- media sidecars and upload-attribution records under
`_meta/<community>/` and `_uploads/<community>/`
- Git repository pointers under `repos/<community>/`
- Redis keys under `buzz:<community>:*`

The community row survives as a permanent tombstone, and deletion
control-plane records remain as evidence of the request, approval,
execution, and result.

## Safety model

Deletion is not a broad `DELETE CASCADE` followed by optimistic cleanup.
The destructive boundaries are durable and fail closed.

### 1. Inventory and approval

- `submit` resolves the target and freezes the schema plus summary-only
storage inventory.
- Approval is bound to the exact request, community, and frozen
inventory digest.
- Unsupported manifest versions, malformed keys inside the target's
owned prefixes, live scoped-table/write-fence coverage drift,
frozen-inventory mismatch, and approval mismatch block execution rather
than guessing. Migration and catalog revision numbers are not
authorization gates; the executor validates the live safety shape
instead.
- Storage inventory is server-side prefix scoped to exactly:
  - `_meta/<community>/`
  - `_uploads/<community>/`
  - `repos/<community>/`
- The deletion path never lists the whole shared bucket and has no
arbitrary per-community object cap. Its listing work is proportional to
the target community's bindings, not total fleet storage.
- Fleet-wide taxonomy sweeps remain independent observability. They
report unknown writer shapes but do not gate deletion submission,
fencing, or destructive progress. Maintainers must add deletion taxonomy
coverage whenever a new community-owned object-key class is introduced;
writer-coverage tests bind the current media and Git writers to that
contract.

### 2. Quiesce, fence, and destructive freeze

- Writes continue through submission, inventory, and approval. They stop
when execution moves the target into `quiescing` and then establishes
the durable fence.
- Already-admitted external effects finish under heartbeated
serving-write leases; the exact admitted lease may renew while the
community is quiescing, but new lease acquisition is rejected. The
executor drains admitted leases before destructive work.
- Invite minting after quiescing begins fails as typed `AccessDenied`
(HTTP 503 at the relay boundary) before an invite can be persisted.
- Database triggers enforce the community write fence across the
complete catalog of community-scoped tables. Startup/readiness and
destructive execution validate that catalog so a newly added but
unfenced table cannot silently escape.
- **Named isolation assumption — fresh write snapshot.** Every writer
transaction that can reach a community-fenced relation must use
PostgreSQL `READ COMMITTED`; each guarded write therefore observes a
statement snapshot no older than acquisition of the community deletion
lock. `REPEATABLE READ` and `SERIALIZABLE` can retain a pre-fence
snapshot and are unsupported for writers. The writer pool refuses
non-`READ COMMITTED` sessions at connection setup, and both SQL fence
functions reject an explicit per-transaction isolation override with
SQLSTATE `25000`. Configuration-delivered bad isolation can surface
through SQLx as a pool-acquire timeout because every `after_connect`
attempt is rejected; the precise `community writes require READ
COMMITTED isolation` reason remains observable when the SQL guard is
reached. Read-only replica transactions are outside this assumption.
- Holding the shared advisory lock until the guarded write executes is a
separate liveness condition: under `READ COMMITTED`, releasing it early
does not permit resurrection because the trigger rechecks the fence, but
it can turn a fleet sweep into a statement-wide SQLSTATE `55000` abort.
- After the fence closes writers, storage is re-enumerated into chunked
side-table rows. Per-prefix counts and digests bind those concrete keys
to the destructive manifest.
- Manifest chunk insertion, update, and deletion are protected after
freeze. This closes the race where an unbound key could otherwise appear
after the manifest was committed.

### 3. Checkpointed destruction

- Target-owned object bindings are deleted from the frozen destructive
manifest in bounded batches with durable progress.
- The concrete key list lives in chunked side-table rows rather than one
request-row JSON value. It supports large communities, resumable
execution, and terminal cleanup.
- Missing objects are accepted as idempotent crash-window outcomes;
malformed ownership, changed evidence, and unexplained target-prefix
drift fail closed.
- PostgreSQL purging remains scoped by `community_id`, including the
guarded NIP-RS hard-delete path discovered with real Desktop kind
`30078` read-state data.
- Redis cleanup explicitly scans and `UNLINK`s only
`buzz:<community_id>:*`. Natural expiry is insufficient because some
keys, including tunnel generation counters used as fencing state, are
deliberately persistent.

### 4. Independent verification

- PostgreSQL logical absence is checked after purge.
- The three target-owned storage prefixes are freshly inventoried again
and must be empty.
- Redis requires two complete empty namespace scans.
- Only after all three stores pass does the request advance through
`logically_verified` to `retention_pending`.

## What V1 deliberately does not erase

### Shared content-addressed storage

Per-community deletion removes bindings, metadata, attribution records,
and Git pointers. It does **not** physically delete fleet-shared CAS
bytes that another community may still reference:

- media blobs and thumbnails
- Git manifests, packs, and indexes (`manifests/`, `packs/`, and `idx/`)

Safe reclamation requires a separate fleet-wide reachability and
retention GC. Unknown keys elsewhere in the shared bucket do not block
one community's deletion; malformed or unrecognized keys inside that
community's three owned prefixes still fail closed.

### External retained copies

The online logical-deletion proof does not erase object
versions/replicas, database backups/WAL, CDN copies, provider retention
copies, or observability exports. Those require their own retention and
purge controls.

### Member-only erasure

This PR erases a whole community. It does not implement the different
operation "erase one npub while preserving the community."

Removing membership or accepting NIP-09 is not member erasure. A
member-only workflow would need to find and selectively remove or redact
authored event content and pubkeys, profile data, DMs, reactions,
mentions, memberships/roles, tokens, workflows/subscriptions, upload
attribution, moderation/audit history, repository attribution, and
identity embedded in tags or JSON. It would also need explicit rules for
ownership transfer, surviving replies and thread metadata, audit-chain
integrity, immutable Git history, and shared-CAS reachability. That
requires a pubkey-level fence and selective graph rewrite; it is a
separate deletion product, not a safe extension of this whole-tenant
worker.

## In scope

- migration `0029_community_deletion.sql`: requests, approvals, leases,
manifest chunks, checkpoints, tombstones, and the universal write-fence
catalog
- durable executor leases, generations, heartbeats, retry/block state,
and resumable stage transitions
- operator-driven `sweep`, `submit`, `list`, `inspect`, `approve`,
`unblock`, `run`, and `drain` commands
- serving-path fences for database writes and external effects across
event ingest, media, Git, workflow, push, invites, mesh/tunnel, and
related paths
- target-prefix-only storage inventory, summary manifests, post-fence
destructive chunks, and bounded batch deletion
- exact community Redis namespace purge and two-pass absence
verification
- cross-community isolation, crash/resume, manifest-integrity,
writer-taxonomy, and schema/migration regressions
- desired-state `schema/schema.sql` support without requiring a SQLx
migration ledger

## Deferred / not covered

- dedicated Helm/chart worker Deployment, service account, secrets,
probes, resources, and network policy
- autonomous `buzz-admin deletions worker` poll loop and worker-only
health server
- least-privilege separation among migration, relay-serving, and
destructive execution roles
- fleet-wide shared-CAS physical GC
- backup/provider/CDN/observability retention completion
- member-only erasure
- provider-native conditional-delete improvements
- a general force-continue escape hatch; permanent safety failures
remain fail closed unless an operator remediates the cause and records
an audited `unblock`

The removed continuous-worker implementation remains deferred; no remote
follow-up branch is claimed by this PR.

## Validation

### Current PR head and repository state

Current pushed head: `359d8402ee15f049768f54156f67b953c7a7e2ed`, rebased
onto `cc9a2f783375e51a6e8d1f2f9d01d5f7e22813d1` (`origin/main` at push
time). The complete PR diff is now 47 files, 9,834 additions, and 517
deletions.

The bespoke source-scanner stack was removed to keep this PR scoped to
community deletion. Tyler/team requested the underlying fenced-write
safety behavior, not `ast-grep`,
`crates/buzz-db/tests/community_fenced_writes.rs`, its 27 fixtures, or
the new `scripts/lints/community_*.yml` rules. Those scanner-specific
files, dependencies, Hermit links, and runner wiring are absent from the
current tree. The production database write fence, startup/destructive
live-catalog validation, and deletion behavior remain.

Source validation on this exact SHA passed:

- `cargo fmt --all -- --check`
- `bash -n scripts/run-tests.sh`
- `cargo nextest run -p buzz-db --all-targets`: 102 passed, 173 skipped,
0 failed
- `cargo nextest run -p buzz-deletion --all-targets`: 10 passed, 9
skipped, 0 failed
- `cargo nextest run -p buzz-admin --all-targets`: 1 passed, 0 failed
- affected-package/all-target Clippy with warnings denied
- lockfile consistency
- Helm 3.16.4 lint and all 44 chart unit tests
- Helm region controls using that fixture: default
`BUZZ_S3_REGION=us-east-1`, explicit `eu-west-2` override, and
blank-region schema rejection

The prior Kubernetes battery below was run against
`928992237358a3294621ac0280830b77155abc04`. It remains useful evidence
for the patch-equivalent production deletion implementation, but it is
**not** claimed as exact-SHA evidence for current head
`359d8402ee15f049768f54156f67b953c7a7e2ed`; the current cleanup removes
only scanner/test/tooling infrastructure. CI restarted for the new head
after the rebase and is pending. Human review remains
`CHANGES_REQUESTED`.

### Prior-head live Kubernetes deletion and safety gates

The full program used one immutable image, real PostgreSQL, Redis,
MinIO, and a three-relay Kubernetes release:

- source: `928992237358a3294621ac0280830b77155abc04` (**prior head**)
- image: `buzz-e2e:sha-928992237358`
- immutable image digest:
`sha256:a1a204f4618ac22d9e210be5e5290645a15d79831ae30b0e44379357c8e4a895`
- evidence root:
`/tmp/buzz-e2e/20260807T033025Z-928992237358-full-gates/`
- evidence-manifest digest:
`82875c5bc9bea7370b796a7aef3457b3a1c8306c84c59e0f7388bbb5ad30e865`

Passed gates at that prior head:

- **Chart/operator region:** default `us-east-1`, explicit nondefault
propagation, blank-region schema rejection, live in-pod environment, and
an in-pod taxonomy sweep over 18 objects with zero unknown.
- **Fenced writers and lifecycle:** open-write/fence ordering;
100-attempt anti-starvation; invite, push matcher, and exhausted-reaper
bystander isolation; non-`READ-COMMITTED` rejection; manifest/tombstone
contracts; eight-failure stage block and audited `unblock`.
- **Destructive lifecycle:** submit → approve → run →
`retention_pending`; PostgreSQL tombstone and Redis/S3 verification
true; zero retries/errors; terminal reruns rejected with exit 5.
- **Fresh 10,001-object crash boundary:** exactly two chunks (10,000 +
1). The executor deleted chunk 0 from MinIO while its PostgreSQL stamp
was row-lock-blocked, was killed with `SIGKILL`, left one object and
both stamps absent, then resumed the same request under generation 2 to
zero objects and terminal state.
- **Independent dead-owner recovery:** a dedicated executor claimed
generation 1, blocked before effects, and was killed through containerd
with `SIGKILL` (no TERM cleanup). The request remained owned and
unreclaimable before lease expiry; a successor claimed generation 2
after 60 seconds and completed with two attempts and zero retries.
- **Three-pod socket isolation:** ordinary NIP-42 and joined
huddle-audio target witnesses on every replica received exact `1008 /
community deleted`; healthy-tenant witnesses on those pods remained
live; deleted-host reconnect returned HTTP 404.
- **Health/provenance:** all replicas independently returned ready and
retained the exact image digest before/after destructive runs and an
audio-enabled rolling restart; PostgreSQL, Redis, and MinIO were healthy
at close.

Instrument corrections were retained as evidence rather than counted as
product failures: a foreground PostgreSQL forward caused an initial
`PoolTimedOut`; Kubernetes pod deletion exercised graceful TERM rather
than dead-owner recovery; shell-background socket witnesses died with
their parent; and the first image build hit the corporate TLS proxy.
Detached forwarding/witnesses, containerd `SIGKILL`, and the configured
internal CA/Artifactory mirror produced the discriminating runs without
weakening product security.

### Prior-head cleanup

For the prior-head Kubernetes run, the Helm release was removed,
namespace absence was verified, run-owned Screen sessions were absent,
and that source worktree remained clean. The evidence manifest was
independently recomputed and every indexed artifact passed `shasum -a
256 -c`. The current `359d8402` source worktree is also clean after the
scanner-only cleanup and push.

---------

Signed-off-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Signed-off-by: cid <d9f92a72922bf45c17379a47d64dae84b6020397c2d5a52b5317d512068cd9d3@buzz.block.builderlab.xyz>
Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: cid <d9f92a72922bf45c17379a47d64dae84b6020397c2d5a52b5317d512068cd9d3@buzz.block.builderlab.xyz>
2026-08-12 09:25:58 -07:00
Wes
777babf393 feat(desktop): canonical <PubKey> component — hover to view/copy full keys, owner "you" labels (#1589)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
2026-07-07 13:35:15 -07:00
Tyler
a504ad6197 E1+E3: reduce relay ingest/fan-out DB round trips; ack p99 −7–16%, fd p99 −6–28%, p999 tails −29–53% vs PR #1453 tip (#1454)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-02 09:09:09 -04:00
Will Pfleger
d99ad131f1 refactor: rename sprout backend to buzz (#958)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Signed-off-by: Will Pfleger <wpfleger@squareup.com>
Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Will Pfleger <wpfleger96@gmail.com>
2026-06-10 19:29:51 -04:00