feat(mgmt): awaiting-invite link resume — self-serve link of invite-only rooms (#111) #119

Merged
robocub merged 1 commit from feat/111-awaiting-invite-resume into master 2026-07-13 17:45:16 +00:00
Collaborator

Implements the #111 remainder per the approved design (issue comment 1226) with the operator's decisions folded in: strict same-sender matching · #89a cooldown bypass on match · configurable TTL ([limits] invite_intent_ttl_secs, default 30 min) · one intent per requester (explicit replacement) · unified auto-resume for both tiers · list as the always-current status surface.

Flow: self-serve link of an un-joinable room arms a single-use awaiting-invite intent → the requester invites the bot from that room within the window → the invite gate accepts it (exact sender+room match; ban check still first) → the link resumes to the normal pending-approval code in the origin DM.

Safety: failed resumes reap the join (forced probe-created semantics, #83); intent creation sits behind every existing link gate; single-use; capped (1/requester, 32 global); fully audit-logged (link_awaiting_invite / invite_accepted:link_intent / link_resumed). The matched path skips the greeting task, whose #85 anti-spoof leave would otherwise undo the join — note: that same #85 leave appears to break the manual operator invite-then-relink flow for >2-member rooms; the intent path sidesteps it (worth its own look later).

Verified: 124 unit tests + clippy -D warnings green; live mgmt_link_invite_only PASS first try (16.7 s) — including the cooldown bypass with mgmt_cooldown_secs armed — plus regressions mgmt_self_serve_link / mgmt_invite_offline / mgmt_dm_e2ee / fed_link_v12 all PASS (mgmt_dm_pending_reap known-fail = tracked #104 infra). Suite → 27 scenarios. Docs updated (self-serve walkthrough, hosted-instance).

Held for operator review before merge — this reshapes an open-registration gate; the operator approved the design but has not seen the diff.

🤖 Generated with Claude Code

Implements the #111 remainder per the approved design (issue comment 1226) with the operator's decisions folded in: strict same-sender matching · #89a cooldown bypass on match · configurable TTL (`[limits] invite_intent_ttl_secs`, default 30 min) · one intent per requester (explicit replacement) · **unified auto-resume for both tiers** · `list` as the always-current status surface. **Flow:** self-serve `link` of an un-joinable room arms a single-use awaiting-invite intent → the requester invites the bot from that room within the window → the invite gate accepts it (exact sender+room match; ban check still first) → the link resumes to the normal pending-approval code in the origin DM. **Safety:** failed resumes reap the join (forced probe-created semantics, #83); intent creation sits behind every existing `link` gate; single-use; capped (1/requester, 32 global); fully audit-logged (`link_awaiting_invite` / `invite_accepted:link_intent` / `link_resumed`). The matched path skips the greeting task, whose #85 anti-spoof leave would otherwise undo the join — note: that same #85 leave appears to break the *manual* operator invite-then-relink flow for >2-member rooms; the intent path sidesteps it (worth its own look later). **Verified:** 124 unit tests + clippy `-D warnings` green; **live `mgmt_link_invite_only` PASS first try (16.7 s)** — including the cooldown bypass with `mgmt_cooldown_secs` armed — plus regressions `mgmt_self_serve_link` / `mgmt_invite_offline` / `mgmt_dm_e2ee` / `fed_link_v12` all PASS (`mgmt_dm_pending_reap` known-fail = tracked #104 infra). Suite → 27 scenarios. Docs updated (self-serve walkthrough, hosted-instance). **Held for operator review before merge** — this reshapes an open-registration gate; the operator approved the design but has not seen the diff. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(mgmt): awaiting-invite link resume — self-serve link of invite-only rooms (#111)
All checks were successful
CI / test (push) Successful in 9m21s
CI / test (pull_request) Successful in 9m39s
eea87d87b9
A self-serve `link` of a room the bot cannot join now arms a single-use
awaiting-invite intent (keyed (requester, room_id), TTL `[limits]
invite_intent_ttl_secs`, default 30 min, 1 per requester, global cap 32).
An invite from EXACTLY that requester for EXACTLY that room is accepted
through the invite gate — bypassing the DMs-only rule, the global budget,
and the #89a cooldown (the user's own onboarding DM-join stamps it moments
earlier; the intent is single-use and velocity-gated at creation) — and the
link resumes automatically: probe (with forced probe-created semantics so a
failed resume still reaps the join, #83) → pending Discord approval in the
origin DM. Unified across tiers: an operator's matched invite resumes too.

Avoid-confusion status surface: `list` now shows the sender's in-flight
state — pending Discord approvals (with code + time left, own links only)
and awaiting-invite intents. The matched-intent path skips the greeting
task, whose #85 anti-spoof would have left the just-joined group room.

probe_room_prereqs now returns a structured ProbeFailure (Join vs Other) so
handle_link can arm the intent exactly on join failures; its post-probe tail
is factored into finish_link, shared with the resume path.

New scenario `mgmt_link_invite_only` (suite → 27) proves the chain live,
including the cooldown bypass (mgmt_cooldown_secs armed). The
mgmt_self_serve_link list-scoping assert now REQUIRES the user's own
pending-link status line. Docs: self-serve walkthrough + hosted-instance
describe the invite-after-link flow.

Verified: 124 unit tests + clippy -D warnings green; live
mgmt_link_invite_only PASS (16.7s first try), regressions
mgmt_self_serve_link / mgmt_invite_offline / mgmt_dm_e2ee / fed_link_v12
PASS, mgmt_dm_pending_reap known-fail (#104, tracked infra issue).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
robocub merged commit 393ccc404c into master 2026-07-13 17:45:16 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dark/nether-voicebridge!119
No description provided.