Skip to content

DELETE /v1/rooms: cascade-revoke invites and share link#3

Merged
sxysun merged 1 commit into
mainfrom
room-revoke-cascade-invites
May 15, 2026
Merged

DELETE /v1/rooms: cascade-revoke invites and share link#3
sxysun merged 1 commit into
mainfrom
room-revoke-cascade-invites

Conversation

@sxysun
Copy link
Copy Markdown
Collaborator

@sxysun sxysun commented May 15, 2026

Problem

A room was shareable two ways — hmq_… capability tokens (auto-minted at room create) and the hms_… share link — but DELETE /v1/rooms/{id} only flipped revoked_at on the room itself. Every invite and the share link kept resolving at the auth layer; only the run path failed. From the owner's seat in the website this surfaced as "4 live invites" on /app/settings long after the rooms had been revoked, which is the opposite of what revocation should mean.

Change

Make DELETE /v1/rooms/{id} revoke the room and retire every credential pointing at it.

  • TenantRegistry.revoke_capabilities_for_room(tenant_id, room_id) -> int — sweeps every live _capability_tokens row whose constraints.room_id matches. Filter is Python-side because constraints is TEXT (JSON-encoded), so we don't introduce a jsonb-only dependency.
  • The DELETE handler in api/rooms.py revokes the room first (primary check still wins), then best-effort cascades:
    • revoke_capabilities_for_room for invites
    • disable_room_share_link (already existed) for the share link row
  • Cascade failures are logged but do not un-revoke the room. Worst case the owner sees an orphan and can revoke it manually from /app/settings. That's strictly better than leaving the room half-revoked.
  • Response shape gains invites_revoked and share_link_disabled so clients (the website, hmctl) can surface what got swept.

Docs

Top-of-file docstring on api/rooms.py now lays out the two sharing primitives in a side-by-side table:

  • storage row, mint trigger, who pays, whether bearer needs own tenant key, URL shape, revoke action
    …so the distinction is obvious from the source, not just from product context.

Tests

Three regression tests in tests/test_capability_tokens.py:

  • test_revoke_capabilities_for_room_only_targets_matching_room — confirms we sweep only invites for the named room and leave other rooms' invites live.
  • test_revoke_capabilities_for_room_idempotent — second call returns 0, doesn't double-revoke.
  • test_revoke_capabilities_for_room_unknown_room_returns_zero — guards against accidentally wide queries.

Full capability + room test suite green (70 tests, ~89s):

tests/test_capability_tokens.py    .......................... [37%]
tests/test_room_share_links.py     ..........                 [51%]
tests/test_room_share_links_api.py ...........                [67%]
tests/test_rooms.py                .......................   [100%]
======================== 70 passed in 89.30s ========================

Companion website change

hivemind-website@38ad246 already shipped the explanatory UI on both /app/settings (Room invites panel) and the per-room Share link panel, plus per-row room-name resolution and a "room revoked" pill for orphaned invites that exist today. Once this PR merges, new room revokes will auto-clean and the pill won't appear for fresh ones.

🤖 Generated with Claude Code

A room was shareable in two ways — hmq_… invites (auto-minted at room
create) and the hms_… share link — but DELETE /v1/rooms only flipped
revoked_at on the room itself. Every invite and the share link kept
working at the auth layer, which surfaced in the website as orphaned
"4 live invites" rows on /app/settings for rooms the owner had
already revoked.

Cascade both:
- TenantRegistry.revoke_capabilities_for_room sweeps every live
  capability whose constraints.room_id matches. Filter is Python-side
  (constraints is TEXT, not jsonb) so it stays storage-agnostic.
- The DELETE handler in api/rooms.py revokes the room first, then
  best-effort cascades the capabilities and disable_room_share_link.
  Cascade failures don't undo the room revoke — worst case the owner
  has to revoke an orphan from /app/settings.

Also adds a top-of-file docstring on api/rooms.py describing the two
sharing primitives (invite vs share link) side by side so the
distinction is obvious from the source, and three regression tests
for the capability cascade (targets only matching rooms, idempotent,
unknown room is a no-op).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sxysun sxysun merged commit 1161724 into main May 15, 2026
1 check passed
@sxysun sxysun deleted the room-revoke-cascade-invites branch May 15, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant