Skip to content

fix(deletion): require sole owner at admission - #7966

Merged
TheSentinel454 merged 6 commits into
mainfrom
elrond/owner-deletion-sole-owner-followup
Sep 30, 2026
Merged

TheSentinel454 merged 6 commits into
mainfrom
elrond/owner-deletion-sole-owner-followup

Conversation

@TheSentinel454

@TheSentinel454 TheSentinel454 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #7830: reject owner-origin deletion admission for a legacy co-owned community before creating a request. Under the existing community-row lock, admission locks all current owner memberships in pubkey order and requires exactly one matching asserted owner. Existing-request idempotent replay remains unchanged; completion retains its independent post-lock revalidation.

New optional operator field: community_id (ab0cd713, 766eb07f). Owner-origin POST /operator/communities/delete now accepts an optional community_id that binds the request to the host's community:

  • Fresh submission: checked after sole-owner authority is proven, so a non-owner still gets 404 community_not_found. A mismatch returns 409 community_id_mismatch and writes no request row.
  • Replay: checked against the stored request, and only when the stored request is for the same host. A mismatch returns 409 community_id_mismatch, and the stored request is unchanged. A known UUID sent with a different host goes through the existing convergence check and returns 409 deletion_request_conflict.
  • Omitted: behaves as before. admit_owner_request takes expected_community_id: Option<Uuid> rather than a second public entry point, and existing callers pass None.
  • docs/operator-community-deletion.md documents the ordering.

Also folds in the non-blocking review nits from #7969 (wpfleger96, at e21151f4):

  • limit_reached has a stable code. Create (provision_community) and transfer-in now return 409 with code: "limit_reached", matching the other coded lifecycle errors. The error message keeps its limit_reached: prefix, so clients that match on the message still work. The provision limit test now asserts the code, and a new PostgreSQL test covers a transfer to an owner at the limit (coded 409, no membership change).
  • Docs: an ack-version mismatch is a 400, not a 409. docs/operator-community-deletion.md and the delete_community handler doc now say a changed host or owner under a known UUID is 409 deletion_request_conflict, and an unsupported acknowledgement version is rejected first with 400 unsupported_acknowledgement_version.
  • Docs: active-cap overrides above the lifetime cap can't be reached. The max_communities_per_owner doc and the quota section now say MAX_LIFETIME_COMMUNITIES_PER_OWNER (20) counts live ownership, so BUZZ_MAX_COMMUNITIES_PER_OWNER above 20 can't be reached.
  • Legolas P2s:
    • The UUID-conflict docs now list every 409 case, including a stored ack version that doesn't match and a UUID held by an operator-origin request.
    • The runbook now says a legacy co-owned community is rejected as 404 community_not_found and should be converged with a transfer first.
    • The preparation drift test now pins DeletionSafety plus "sole-owner authority drifted".
  • Guard wording: the delete_community handler doc now says "sole current owner", matching the admission check.

What got simpler: admission and automatic preparation now enforce the same sole-owner authority, so no accepted-but-unexecutable co-owned request needs a new recovery path. deletion_api_error is renamed to coded_api_error, since it now carries non-deletion codes too. There's one helper for coded operator errors instead of a second one.

Rebased onto main after #7969 merged (d7a35afa). The sole-owner commit applied cleanly.

Earlier focused Blox verification (at the pre-rebase head 161e9857): the new admission regression was red before the change and green after it. Five temporary, restored mutants each failed its matching production-seam regression. At the current head 766eb07f, the relay api::operator:: suite passes 23/0 on Blox. Each new community_id guard, removed one at a time, fails its own regression test. CI owns the full suites.

Remaining debt: the separate P2-A purge/completion membership/community lock-order cycle is unchanged.

Generated with Codex

@github-actions

github-actions Bot commented Sep 29, 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: 0ee609379004a894e99b449885c37a044f41919e...766eb07fd91eb76a879fdc72ba6535fb7a017787
  • 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 defects found in the authorized PR range.

Findings

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

Notes

  • Static read-only review; tests and repository code were not executed as instructed.

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

codex and others added 2 commits September 30, 2026 10:52
Signed-off-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Give create and transfer-in limit_reached rejections a stable code,
keeping the message prefix for older clients. Document that an unsupported
acknowledgement version returns 400 before the tuple comparison, that
active-cap overrides above the lifetime cap are unreachable, and the
sole-current-owner guard on the delete handler.

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
@TheSentinel454
TheSentinel454 force-pushed the elrond/owner-deletion-sole-owner-followup branch from c593003 to ea6fba8 Compare September 30, 2026 14:52
TheSentinel454 pushed a commit that referenced this pull request Sep 30, 2026
…rd in test

Address Legolas P2s on #7966: the UUID conflict docs name every non-converging
case, the runbook explains that legacy co-owned communities are rejected as
community_not_found, and the preparation drift test matches the sole-owner
authority guard instead of any owner deletion error.

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
…rd in test

Address Legolas P2s on #7966: the UUID conflict docs name every non-converging
case, the runbook explains that legacy co-owned communities are rejected as
community_not_found, and the preparation drift test matches the sole-owner
authority guard instead of any owner deletion error.

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
@TheSentinel454
TheSentinel454 force-pushed the elrond/owner-deletion-sole-owner-followup branch from 579e2dd to a0fc97d Compare September 30, 2026 15:00
Elrond and others added 3 commits September 30, 2026 11:04
…nities

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Codex <noreply@openai.com>
…e host

A known request UUID resent for a different host is a request conflict,
not a community_id mismatch, even when community_id names that other
host. The replay mismatch check now applies only to a stored request for
the same host, and the fresh-path check runs after sole-owner authority so
non-owners see the same 404 as an unknown host.

Fold admit_owner_request_with_community_id into admit_owner_request with
an expected_community_id parameter, and pin the UUID-collision, matching
replay and non-owner cases in operator tests. Update the runbook.

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
@TheSentinel454
TheSentinel454 marked this pull request as ready for review September 30, 2026 18:17
@TheSentinel454
TheSentinel454 requested a review from a team as a code owner September 30, 2026 18:17
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 30, 2026

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Automated multi-lane review at head 766eb07f, against base d7a35afa. Two independent code reviews plus an end-to-end verification run found no blocking issues. Thanks for closing the admission/completion gap from #7830.

What was checked

  • Lock order. Admission locks the community row, then every owner membership in pubkey order, and checks both the owner count and the asserted pubkey against those locked rows, not an earlier read (crates/buzz-db/src/store/deletion.rs:920-950). Preparation and ownership transfer take the community lock before owner rows in the same order. Admission never waits on the deletion advisory lock and reads existing requests without row locks, so it does not form a new wait cycle with the abort path.
  • Error precedence. Unsupported acknowledgement version → 400 before the UUID lookup; a known UUID on a different host → 409 deletion_request_conflict without resolving that host; a fresh UUID proves sole-owner authority before the community_id check, so a non-owner still gets 404. Replay reads only the stored request, so it does not reveal whether some other community currently exists.
  • Rename. No deletion_api_error references remain at this head; every former caller now uses coded_api_error.
  • Guards are load-bearing. Removing each of these one at a time turned exactly one PostgreSQL test red (70/71), and restoring it returned 71/71: the sole-owner count check, the community_id mismatch check on a fresh submission, the same check on replay, and the same-host scoping of the replay check. The 48 deletion and 23 operator PostgreSQL tests pass unmodified.
  • Live HTTP run. Against a local relay with real PostgreSQL and Redis and signed NIP-98 requests, the runbook recovery worked end to end: a legacy co-owned deletion is rejected with 404 and no intent row, then after unarchive, self-transfer, and re-archive, the same request UUID is accepted with 202 submitted. Wrong community_id values returned 409 on both fresh submission and replay.

MINOR — the combined replay error isn't documented

On a same-host replay, the community_id check runs before the owner, operator-origin, and stored-acknowledgement checks. So a replay that has a wrong community_id and a changed owner gets 409 community_id_mismatch. docs/operator-community-deletion.md:159-164 and the delete_community doc (crates/buzz-relay/src/api/operator.rs:471-476) say a changed owner under a known UUID is 409 deletion_request_conflict, with no exception noted. Both are 409s and neither mutates anything, so this is not an authorization issue. Either state that community_id_mismatch wins for a same-host replay, or move the ID check after the rest of the replay tuple matches, and add a regression test for the combined case.

MINOR — PR description is missing the community_id binding

ab0cd7130 and 766eb07fd add the optional community_id field and its same-host replay scoping, but the description doesn't mention either. Worth adding, since it's a new request field in the operator API.

@TheSentinel454
TheSentinel454 merged commit 53a1210 into main Sep 30, 2026
147 of 150 checks passed
@TheSentinel454
TheSentinel454 deleted the elrond/owner-deletion-sole-owner-followup branch September 30, 2026 19:04
tlongwell-block pushed a commit that referenced this pull request Sep 30, 2026
Main added sole-owner admission for owner-requested community deletion
(#7966). It changes deletion.rs, which this branch also changes, but in a
different part of the file: main edits owner admission and its tests, this
branch adds the two personal read tables to the purge lists. The merge is
textually clean and needs no follow-on edit.

This branch's diff against main is unchanged by the merge: the same 31
files, each with the same added and removed lines.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Sep 30, 2026
…ty-from-device

* origin/main:
  feat(desktop): add Admin Console Actions tab for direct staff actions (#7904)
  fix(deletion): require sole owner at admission (#7966)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>

This branch was successfully deployed

1 active deployment
codex-review — 766eb07f Deployed Sep 30, 2026 by TheSentinel454 via Run Codex Security Review #6245
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