Skip to content

feat(relay): add admin HTTP routes for member restriction management - #7302

Merged
wpfleger96 merged 12 commits into
mainfrom
hayt/admin-restrictions
Sep 21, 2026
Merged

wpfleger96 merged 12 commits into
mainfrom
hayt/admin-restrictions

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Sep 3, 2026

Copy link
Copy Markdown
Member

What

Three admin HTTP routes under GET|DELETE /api/admin/v1/members/...:

  • GET /members/restrictions?communityId=<uuid>[&limit={1-200}][&cursor=<token>] — lists active bans and timeouts with stable keyset pagination
  • DELETE /members/{pubkey}/ban?communityId=<uuid> — lifts an active ban; 409 if no active restriction
  • DELETE /members/{pubkey}/timeout?communityId=<uuid> — lifts an active timeout; 409 if no active restriction

Auth

All three routes pass through the existing NIP-98 authorization gate. Both DELETEs call require_mutation_principal; GET follows the established read-authorization pattern.

Atomicity

Both DELETE routes use transactional DB helpers (unban_member_with_audit / untimeout_member_with_audit) that perform the conditional lift UPDATE and the moderation_actions INSERT in one SQL transaction. If the audit INSERT fails the lift rolls back; the restriction stays active.

Expired-ban predicate

unban_member WHERE clause requires banned AND (ban_expires_at IS NULL OR ban_expires_at > now()) — matches all read paths. An already-expired ban returns false → 409.

Pagination (GET /members/restrictions)

GET /members/restrictions returns { "items": [...], "nextCursor": "<token>"|null }.

  • limit — page size 1–200 (default 200), enforced as a SQL LIMIT. Returns 400 for values outside range.
  • cursor — opaque continuation token from a prior page's nextCursor. Omit for the first page.
  • Order: ORDER BY updated_at DESC, pubkey DESC. The pubkey tie-breaker ensures a deterministic cursor even when multiple rows share the same updated_at.
  • Cursor encoding: base64url of {updated_at_micros}_{pubkey_hex}. Treat as opaque.
  • nextCursor is null only when the returned page is smaller than limit. An exactly-full final page emits a non-null cursor; the subsequent request returns an empty page with null.
  • The existing list_community_restrictions DB method (unbounded) is kept intact for the embedded dashboard bridge consumer (GET /moderation/restricted), whose bare-array response contract must not change.

Tests

All Postgres-gated tests are in the postgres_tests module (api/admin/mod.rs):

  • list_restrictions_returns_active_bans_and_timeouts — seeds a ban + timeout, GET returns both; nextCursor is null when all records fit in one page
  • list_restrictions_pagination_exhaustive — seeds 201 rows: 199 with distinct timestamps (1s apart, newest first) plus a tied pair sharing an identical older timestamp (now()-1000s), pinned to sort positions 200/201 so the default-200 page boundary splits the tie. Asserts: (1) limit=201 → 400; (2) a no-limit request returns exactly 200 items + non-null nextCursor (falsifiable binding of default=200 and SQL cap — reverting the route default to the shared helper's 50 fails this assertion); (3) continuation walk to exhaustion with exactly-once coverage over all 201 pubkeys, exercising the tie-breaker at the page boundary.
  • unban_member_returns_204_clears_ban_and_inserts_audit — 204, ban cleared, timeout survives (restriction independence), audit row verified, tenant isolation confirmed
  • untimeout_member_returns_204_clears_timeout_and_inserts_audit — 204, timeout cleared, ban survives (restriction independence), second-community timeout survives (community_id predicate), audit row verified
  • unban_member_returns_409_for_expired_ban — expired ban → 409, no audit row
  • unban_with_audit_rolls_back_lift_when_audit_insert_fails (buzz-db) — forced CHECK constraint failure proves Err + ban still active (rollback proof)

@wpfleger96
wpfleger96 requested a review from a team as a code owner September 3, 2026 21:02
@wpfleger96
wpfleger96 deployed to codex-review September 3, 2026 21:02 — with GitHub Actions Active
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated, security-focused review generated by Codex.
Use it as a supplement to human review; false positives are possible.

Scope

  • Exact PR diff: a61239f0d8036aff58176f5c0ce7f080c66e21b7...319c97874b4f1c9c51bd75e70da93bb91e7fa652
  • Model: gpt-5.6-sol

💡 Click "edited" above to see earlier reviews for this PR.


Review Summary

Overall Risk: NONE

No concrete security, correctness, or reliability issues were identified in the authorized pull request range.

Findings

No concrete security, correctness, or reliability findings were identified.

Notes

  • Review was limited to read-only static inspection; repository scripts, builds, and tests were not executed as requested.

Generated by Codex Security Review |
Requested by: @wpfleger96 |
Workflow run

@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 3, 2026
@wpfleger96
wpfleger96 deployed to codex-review September 3, 2026 21:54 — with GitHub Actions Active
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 3, 2026
Hayt and others added 3 commits September 3, 2026 18:27
Adds three routes to the /api/admin/v1 plane that expose the existing
ban/timeout DB layer (moderation.rs) via HTTP — completing the admin
surface for un-ban, un-timeout, and restriction listing:

  GET  /members/restrictions?communityId={uuid}
  DELETE /members/{pubkey}/ban?communityId={uuid}
  DELETE /members/{pubkey}/timeout?communityId={uuid}

The DB methods (list_community_restrictions, unban_community_member,
untimeout_community_member) and the community_bans schema already exist
from Phase 1; these routes are the only missing piece. The WebSocket
paths (kind 9041 / 9043) continue to work unchanged.

Route contract:
- All three require admin auth (GET: read-only, accepted in both nip98
  and disabled modes; DELETE: require_mutation_principal).
- communityId resolves directly to CommunityId::from_uuid — no
  host-lookup needed; the admin plane is already operator-scoped.
- Mutations write an audit row (action: unban / untimeout) and return
  204 on success, 409 when no active restriction exists for the target.
- Pubkey path params are validated as 64-char hex and reject with 404
  on malformed input (consistent with the operators route convention).

Response shape: MemberRestrictionRecord maps BanRecord fields to a
camelCase JSON envelope with Vec<u8> pubkeys hex-encoded as strings.
BanRecord remains a pure DB row type with no Serialize derive.

Tests:
- restriction_record_converts_ban_record_pubkeys_to_hex: pure unit test
  pinning the hex-encoding and field mapping of BanRecord → JSON.
- list_restrictions_rejects_missing_credential: GET without credential
  returns 401 (no DB access).
- unban_member_rejects_missing_credential: DELETE /ban without
  credential returns 401 (no DB access).
- untimeout_member_rejects_missing_credential: DELETE /timeout without
  credential returns 401 (no DB access).
- unban_member_returns_409_when_no_active_ban: signed DELETE against a
  community with no ban row returns 409 [requires Postgres].
- untimeout_member_returns_409_when_no_active_timeout: signed DELETE
  against a community with no timeout row returns 409 [requires Postgres].

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Three issues corrected in the admin restriction management routes:

IMPORTANT 1 — audit must be atomic with the lift.
Both DELETEs previously committed the ban/timeout lift (unban_community_member /
untimeout_community_member) and then inserted the audit row in a separate
autocommit. An audit-insert failure after a successful lift produced a dishonest
state: enforcement changed, NIP-98 replay consumed, and a fresh retry would get
409 — an audit-contract violation (VISION_MODERATION.md).

Fix: two new transactional helpers in buzz-db — unban_member_with_audit and
untimeout_member_with_audit — run the conditional-lift UPDATE and the
moderation_actions INSERT in one SQL transaction. If the INSERT fails, the UPDATE
rolls back and the restriction stays active. The HTTP handlers now call the
single transactional method instead of two separate autocommits.

The Db trait exposes two new wrapper methods:
  unban_community_member_with_audit
  untimeout_community_member_with_audit

MINOR — expired-ban predicate.
unban_member (both the free function and its Db wrapper) used
WHERE banned = true rather than the active-ban predicate used by all read
paths (banned AND (ban_expires_at IS NULL OR ban_expires_at > now())).
An expired ban would return 204 + audit instead of the agreed 409.

Fix: corrected WHERE clause in unban_member and both transactional helpers.

IMPORTANT 2 — tests now bind the success contracts.
New Postgres-gated tests (all #[ignore = "requires Postgres"]):

  In buzz-relay api::admin:
  - list_restrictions_returns_active_bans_and_timeouts: seeds ban+timeout,
    GET returns both as JSON with correct fields.
  - unban_member_returns_204_clears_ban_and_inserts_audit: seeds active ban,
    DELETE returns 204, ban cleared, audit row has actor/target/authority,
    other community's ban untouched (tenant isolation).
  - untimeout_member_returns_204_clears_timeout_and_inserts_audit: same
    pattern for timeout.
  - unban_member_returns_409_for_expired_ban: expired ban → 409, no audit row
    inserted (rollback evidence via HTTP path).

  In buzz-db store/moderation:
  - unban_with_audit_rolls_back_lift_when_audit_insert_fails: calls
    unban_member_with_audit with an invalid actor_authority to trigger the DB
    CHECK constraint on moderation_actions.actor_authority, verifies Err
    returned and ban remains active (direct rollback proof).

Non-blocking: GET /members/restrictions is unbounded (no pagination). Acceptable
for the rostered-admin surface now; noted as known follow-up in PR body.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ependence and community isolation

Restriction independence: seed the target with both a ban and an active timeout;
assert that unban clears only the ban (timeout survives), and that untimeout clears
only the timeout (ban survives). A regression widening either UPDATE to clear both
restrictions would fail the new co-existing-restriction assertion.

Community isolation for untimeout: seed the same pubkey with an active timeout in
a second community; assert it stays active after the untimeout. A regression
dropping the community_id = $1 predicate would clear the second-community timeout
and fail the new cross-community assertion.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 force-pushed the hayt/admin-restrictions branch from af24d21 to ec81e97 Compare September 3, 2026 22:27
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 3, 2026
@wpfleger96
wpfleger96 deployed to codex-review September 3, 2026 22:27 — with GitHub Actions Active
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 3, 2026
…dant migrate in tests

Seven clippy::unnecessary_map_or errors in tests added by this PR:
- map_or(true,  |r| !r.banned)                   → is_none_or(|r| !r.banned)
- map_or(false, |t| t > ...)   (×3)               → is_some_and(|t| t > ...)
- map_or(false, |r| r.banned)  (×2)               → is_some_and(|r| r.banned)
- map_or(true,  |r| r.muted_until.map_or(true,…)) → is_none_or(…is_none_or…)

Six db.migrate() calls removed from the new Postgres-backed tests. The
postgres-test framework (postgres-test-setup.sh) builds the per-test database
from schema/schema.sql via pgschema, not by running sqlx migrations. Calling
db.migrate() on a desired-state clone fails with 42710 "type channel_type
already exists" because migration 0001 attempts to re-create types already
present in the schema. The per-test clone is fully migrated before the test
binary runs; no explicit migration call is required.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 3, 2026
…test fixtures

The 4 tests that insert ban/timeout fixtures via db.ban_community_member or
db.timeout_community_member were constructing a community_uuid = Uuid::new_v4()
and calling db.ensure_configured_community(&host) — which inserts a community
row with a DB-generated UUID that is NOT community_uuid. Inserting into
community_bans then triggers enforce_community_write_fence, which queries
communities WHERE id = community_uuid, finds nothing, and raises
'community ... is missing'.

Fix: capture the EnsuredCommunityRecord returned by ensure_configured_community
and use its .id (a CommunityId backed by the DB-assigned UUID) as the
community throughout each test. Shadow community_uuid with *community.as_uuid()
where the UUID is needed in URL format strings.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 deployed to codex-review September 4, 2026 15:17 — with GitHub Actions Active
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 4, 2026
@TheSentinel454

Copy link
Copy Markdown
Contributor

P1 — bound and paginate the new restriction-list endpoint

Verified at exact head c7106d808716eeddaa75b52e4df9f449d7093863.

GET /api/admin/v1/members/restrictions calls list_community_restrictions (crates/buzz-relay/src/api/admin/mod.rs:1159-1182), whose DB query orders all active restrictions and then uses unbounded fetch_all without a LIMIT (crates/buzz-db/src/store/moderation.rs:626-647). A roster-authorized caller can repeatedly make PostgreSQL scan and the relay materialize every active restriction for a community.

This conflicts with the repository rule to “Bound every resource, loop, and process tree” (AGENTS.md:194-202). The PR body acknowledges this deferral, but no follow-up issue is linked.

Please add:

  1. A capped limit with a maximum of 200 and stable keyset/cursor pagination.
  2. The bound in the SQL query itself, rather than slicing after fetch_all.
  3. Continuation metadata in the HTTP response.
  4. A production-route PostgreSQL regression test that seeds more than the cap, proves no page exceeds it, and proves pagination returns every row exactly once.

updated_at alone is not a unique cursor. Use a deterministic tie-breaker (for example (updated_at, pubkey)) in both ORDER BY and the keyset predicate so equal timestamps cannot duplicate or omit rows.

Severity: P1. This endpoint is NIP-98 roster-gated, so this is a remotely repeatable availability cost rather than a demonstrated confidentiality or data-loss issue. The authorization, tenant predicates, transactional lift-and-audit behavior, active/expired semantics, and independent ban/timeout handling otherwise looked sound at this SHA.

Add a capped `limit` query param (default and max 200), stable
keyset pagination (`ORDER BY updated_at DESC, pubkey DESC` with a
compound keyset predicate), and a `{ items, nextCursor }` response
envelope to `GET /api/admin/v1/members/restrictions`.

The tie-breaker on `pubkey` (BYTEA) makes the cursor deterministic
when multiple rows share the same `updated_at`. The opaque cursor
token is base64url of `{updated_at_micros}_{pubkey_hex}`.

The existing `list_community_restrictions` (unbounded) is kept
intact for the embedded dashboard bridge consumer
(`GET /moderation/restricted`), which returns a bare JSON array
whose response contract must not change.

New PostgreSQL regression test seeds 5 rows, forces two to share an
identical `updated_at` to exercise the tie-breaker, walks pages of
size 2 to exhaustion, and asserts exactly-once coverage.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 4, 2026
@wpfleger96
wpfleger96 deployed to codex-review September 4, 2026 19:38 — with GitHub Actions Active
The route-level default must be 200 (the max) — the shared `limit()`
helper defaults to 50, which is wrong for this route.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 4, 2026
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 4, 2026
Upgrade list_restrictions_pagination_exhaustive to be falsifiable
on the default/max cap:

- Seed 201 rows: 199 with distinct timestamps (newest first) + a
  tied pair at an identical older timestamp pinned to sort positions
  200-201, straddling the default 200-row page boundary.
- First route request omits limit: asserts exactly 200 items + a
  non-null nextCursor. Reverting the one-liner to limit(query.limit)?
  produces 50 instead of 200, failing this assertion.
- Asserts limit=201 -> 400.
- Walks remaining pages; asserts exactly-once coverage over all 201
  pubkeys. The tied pair at positions 200-201 deterministically
  exercises the tie-breaker at the page boundary.

Also fix RestrictionsPage.next_cursor field doc: null only when the
returned page is smaller than limit; an exactly full final page emits
a non-null cursor and the next request returns the empty terminal page.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 deployed to codex-review September 4, 2026 20:13 — with GitHub Actions Active
…n test

The tied-pair comment incorrectly claimed 0xE1/0xE2 sort "below"
0x01..0xC7 under pubkey DESC (they sort above). The older timestamp
is what pins them to positions 200/201. Correct and clarify the
comment; note the pair's internal order under pubkey DESC.

PR body Tests section updated to describe the shipped 201-row /
default-200 boundary test.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@github-actions github-actions Bot added codex-security-review-current The posted Codex security review matches its recorded range. and removed codex-security-review-current The posted Codex security review matches its recorded range. labels Sep 4, 2026
@wpfleger96
wpfleger96 deployed to codex-review September 4, 2026 20:25 — with GitHub Actions Active
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 4, 2026

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Source review clear

Reviewed head 86a5ebb506f57b4bc400488204ff34f6f2e2ebb0 against base 75f101d8b4f5b4b26f9891b73b87c67fedac25a0. No actionable code, product, or security blocker found. This is a review comment, not approval.

  • Contract and authority: these routes extend the existing deployment-admin HTTP plane. The shared NIP-98 gate binds method and full query-bearing URL, resolves deployment Operator/Moderator authority, and retains Host/Origin and replay checks. Both DELETEs reject disabled-mode mutations; community-local roles do not grant deployment access.
  • State and compatibility: traced both lifts through the scoped conditional UPDATE and atomic audit INSERT, schema action/authority vocabularies, and the admission/write consumers of the same restriction rows. Ban and timeout remain independent; absent/expired restrictions do not produce a lift audit. The existing community command path and /moderation/restricted bare-array response remain compatible. Keyset order, strict continuation predicate, timestamp precision, and the documented extra empty page after an exactly-full final page agree. Pagination is not a snapshot across concurrent changes.
  • Validation limits: reviewed the changed source and tests, exact-base product/architecture/testing guidance, and independent authorization and DB review lanes. No PR code was checked out, built, tested, imported, or executed; runtime and CI outcomes are not certified by this review. Client UI and lift-notification behavior were not changed here. Additional route-specific malformed-cursor/disabled-mode tests and concurrent-lift coverage would strengthen the suite, but source review found no concrete defect to block on.

Hayt and others added 2 commits September 21, 2026 11:51
* origin/main: (81 commits)
  fix(mobile): avoid opening empty threads on message tap (#7756)
  fix(workflows): make deletion persistent and retryable (#7735)
  fix(mobile): preserve thread replies through refresh failures (#7757)
  fix(mobile): keep iOS message menu actions responsive after rebuilds (#7758)
  fix(relay): exclude ephemeral activity from message quota (#7736)
  release: push gateway chart 0.3.1 (#7749)
  fix(push): label plaintext push gateway service as HTTP (#7717)
  Replace personal and internal data in desktop test fixtures (#7748)
  Add mobile VISION (#7710)
  fix(mobile): keep relay sessions stable during push lease updates (#7745)
  fix(desktop): keep managed agent avatars usable across communities (#7732)
  fix(mobile): fail open when age checks are unavailable (#7714)
  fix(ci): don't run desktop tests for purely mobile client changes (#7709)
  fix(mobile): temporarily disable age gating (#7708)
  feat(db): expose connection setup metrics (#7286)
  Isolate S3 storage metrics from the relay (#7543)
  fix(web): route mobile invite downloads to app stores (#7702)
  feat(mobile): show build number with version in settings (#7697)
  release(mobile-infra): buzz-push-gateway 0.3.0 (#7685)
  Add authenticated WebSocket recovery telemetry (#7546)
  ...

Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
The pagination loop in the restrictions cursor test used a `loop { match
... None => break }` pattern that clippy -D warnings flags as
`clippy::while_let_loop`. Rewrote it as a `let ... else { break }` form,
which is idiomatic and clippy-clean.

This was the root cause of the four red CI lanes (Rust Lint + Windows
Rust on both the PR and a secondary run) at the prior head.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 21, 2026
…et_loop

Loop body and termination semantics are identical to the prior
loop/let-else form; this is the idiomatic Rust pattern clippy
recommends.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 21, 2026

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Source review clear

Reviewed head 319c97874b4f1c9c51bd75e70da93bb91e7fa652 against base a61239f0d8036aff58176f5c0ce7f080c66e21b7, carrying forward the prior source clearance at 86a5ebb506f57b4bc400488204ff34f6f2e2ebb0. No actionable regression found. This is a review comment, not approval.

  • Delta: one main merge plus two test-only lint fixes. The net branch change is a pagination-test loop rewritten as while let; cursor advancement, termination guard, page bound, and exhaustive/tie-break assertions remain intact. Production code in the two PR-owned files is unchanged since the prior clearance, not production code throughout the merged repository.
  • Integration: checked main’s changed NIP-98 verifier, ban-enforcement state transition, test-state setup, schema, and legacy restriction consumers against the existing contract. Deployment authority and disabled-mode mutation rejection, community-scoped atomic lift/audit, independent ban/timeout state, bounded keyset pagination, and the legacy bare-array response remain compatible. Pagination remains intentionally non-snapshot; no new UI or lift-notification requirement is imposed.
  • Evidence and limits: source-only review on the pinned Blox host with exact-base product/testing/architecture guidance and an independent metadata lane. No PR code was checked out, built, tested, imported, or executed. The exact-head CI snapshot at 2026-09-21 18:02:44Z had 54 successful and 26 skipped checks, with none failing or pending. Existing optional coverage suggestions are not promoted into new blockers.

@wpfleger96
wpfleger96 merged commit 77729ab into main Sep 21, 2026
80 checks passed
@wpfleger96
wpfleger96 deleted the hayt/admin-restrictions branch September 21, 2026 18:53
wpfleger96 pushed a commit that referenced this pull request Sep 21, 2026
…n-surface

* origin/main:
  feat(relay): add admin HTTP routes for member restriction management (#7302)
  fix(relay): fire kick live side effects at convergence; persist target; fence re-add race with held lock (#7298)
  feat(relay): add atomic complete read-state snapshots (#7572)
  fix(desktop): register macOS badges for new and existing installs (#7783)
  fix(mobile): avoid opening empty threads on message tap (#7756)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
shellz-n-stuff added a commit that referenced this pull request Sep 21, 2026
…ecurity

* origin/main: (22 commits)
  feat(relay): add admin HTTP routes for member restriction management (#7302)
  fix(relay): fire kick live side effects at convergence; persist target; fence re-add race with held lock (#7298)
  feat(relay): add atomic complete read-state snapshots (#7572)
  fix(desktop): register macOS badges for new and existing installs (#7783)
  fix(mobile): avoid opening empty threads on message tap (#7756)
  fix(workflows): make deletion persistent and retryable (#7735)
  fix(mobile): preserve thread replies through refresh failures (#7757)
  fix(mobile): keep iOS message menu actions responsive after rebuilds (#7758)
  fix(relay): exclude ephemeral activity from message quota (#7736)
  release: push gateway chart 0.3.1 (#7749)
  fix(push): label plaintext push gateway service as HTTP (#7717)
  Replace personal and internal data in desktop test fixtures (#7748)
  Add mobile VISION (#7710)
  fix(mobile): keep relay sessions stable during push lease updates (#7745)
  fix(desktop): keep managed agent avatars usable across communities (#7732)
  fix(mobile): fail open when age checks are unavailable (#7714)
  fix(ci): don't run desktop tests for purely mobile client changes (#7709)
  fix(mobile): temporarily disable age gating (#7708)
  feat(db): expose connection setup metrics (#7286)
  Isolate S3 storage metrics from the relay (#7543)
  ...

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
TheSentinel454 added a commit that referenced this pull request Sep 21, 2026
…ness-overload

* origin/main: (74 commits)
  feat(relay): add admin HTTP routes for member restriction management (#7302)
  fix(relay): fire kick live side effects at convergence; persist target; fence re-add race with held lock (#7298)
  feat(relay): add atomic complete read-state snapshots (#7572)
  fix(desktop): register macOS badges for new and existing installs (#7783)
  fix(mobile): avoid opening empty threads on message tap (#7756)
  fix(workflows): make deletion persistent and retryable (#7735)
  fix(mobile): preserve thread replies through refresh failures (#7757)
  fix(mobile): keep iOS message menu actions responsive after rebuilds (#7758)
  fix(relay): exclude ephemeral activity from message quota (#7736)
  release: push gateway chart 0.3.1 (#7749)
  fix(push): label plaintext push gateway service as HTTP (#7717)
  Replace personal and internal data in desktop test fixtures (#7748)
  Add mobile VISION (#7710)
  fix(mobile): keep relay sessions stable during push lease updates (#7745)
  fix(desktop): keep managed agent avatars usable across communities (#7732)
  fix(mobile): fail open when age checks are unavailable (#7714)
  fix(ci): don't run desktop tests for purely mobile client changes (#7709)
  fix(mobile): temporarily disable age gating (#7708)
  feat(db): expose connection setup metrics (#7286)
  Isolate S3 storage metrics from the relay (#7543)
  ...

Signed-off-by: tornquist <tornquist@squareup.com>
wpfleger96 pushed a commit that referenced this pull request Sep 21, 2026
…rcement

* origin/main: (87 commits)
  feat(relay): add admin HTTP routes for member restriction management (#7302)
  fix(relay): fire kick live side effects at convergence; persist target; fence re-add race with held lock (#7298)
  feat(relay): add atomic complete read-state snapshots (#7572)
  fix(desktop): register macOS badges for new and existing installs (#7783)
  fix(mobile): avoid opening empty threads on message tap (#7756)
  fix(workflows): make deletion persistent and retryable (#7735)
  fix(mobile): preserve thread replies through refresh failures (#7757)
  fix(mobile): keep iOS message menu actions responsive after rebuilds (#7758)
  fix(relay): exclude ephemeral activity from message quota (#7736)
  release: push gateway chart 0.3.1 (#7749)
  fix(push): label plaintext push gateway service as HTTP (#7717)
  Replace personal and internal data in desktop test fixtures (#7748)
  Add mobile VISION (#7710)
  fix(mobile): keep relay sessions stable during push lease updates (#7745)
  fix(desktop): keep managed agent avatars usable across communities (#7732)
  fix(mobile): fail open when age checks are unavailable (#7714)
  fix(ci): don't run desktop tests for purely mobile client changes (#7709)
  fix(mobile): temporarily disable age gating (#7708)
  feat(db): expose connection setup metrics (#7286)
  Isolate S3 storage metrics from the relay (#7543)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

# Conflicts:
#	crates/buzz-auth/src/nip98.rs
#	crates/buzz-relay/Cargo.toml
wpfleger96 added a commit that referenced this pull request Sep 21, 2026
Two fixes required after merging origin/main:

1. crates/buzz-relay/src/api/bridge.rs: restore pub(crate) visibility on
   verify_bridge_auth and verify_bridge_auth_with_options. PR #7302 added
   git/settings.rs as a new crate-internal caller; our F2 commit had narrowed
   both to private, breaking the build. Docstring updated to reflect the
   broader crate-visible contract.

2. crates/buzz-auth/src/nip98.rs: correct payload tag no-content contract.
   Our F1 implementation treated a one-element ["payload"] tag (no content)
   as "no binding" (None), but main's test contract and the NIP-98 spec
   treat a present-but-empty tag as a structurally invalid claim that must
   be rejected. Updated implementation to reject empty/missing hash on a
   present tag. Removed the incorrect payload_tag_no_content_treated_as_absent
   test; main's payload_tag_without_hash_rejected_with_body covers this case.
   Updated section comment to reflect the corrected contract.

Both tests that now fail (demo_join_forwarded_arm_round_trips_echo,
trace_context_lookup_does_not_enable_callsites) are pre-existing failures
present before this PR's changes — verified against prior merge base.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Sep 22, 2026
…-history

* origin/main:
  feat(relay): add admin HTTP routes for member restriction management (#7302)
  fix(relay): fire kick live side effects at convergence; persist target; fence re-add race with held lock (#7298)
  feat(relay): add atomic complete read-state snapshots (#7572)
  fix(desktop): register macOS badges for new and existing installs (#7783)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
brow added a commit that referenced this pull request Sep 22, 2026
…-dev

* origin/main:
  fix(desktop): bound startup request bursts and recover quota refusals (#7790)
  fix(audit): frame hash inputs with TLV (#7492)
  fix(admin): allow cold storage worker DB startup (#7770)
  feat(relay): add admin HTTP routes for member restriction management (#7302)
  fix(relay): fire kick live side effects at convergence; persist target; fence re-add race with held lock (#7298)
  feat(relay): add atomic complete read-state snapshots (#7572)
  fix(desktop): register macOS badges for new and existing installs (#7783)
  fix(mobile): avoid opening empty threads on message tap (#7756)
  fix(workflows): make deletion persistent and retryable (#7735)
  fix(mobile): preserve thread replies through refresh failures (#7757)
  fix(mobile): keep iOS message menu actions responsive after rebuilds (#7758)
  fix(relay): exclude ephemeral activity from message quota (#7736)
  release: push gateway chart 0.3.1 (#7749)
  fix(push): label plaintext push gateway service as HTTP (#7717)
  Replace personal and internal data in desktop test fixtures (#7748)
  Add mobile VISION (#7710)
  fix(mobile): keep relay sessions stable during push lease updates (#7745)

Signed-off-by: Tom Brow <tomb@block.xyz>

This branch was successfully deployed

1 active deployment
codex-review 319c9787 Deployed Sep 21, 2026 by wpfleger96 via Run Codex Security Review #4953
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex-security-review-current The posted Codex security review matches its recorded range.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants