BL-070 Shipped

Campaign session join link

game_sessions.join_token (UUID, before_validation, unique index, non-null). GET /join/:token handles both session tokens and campaign invite tokens: member → redirect to session; signed-in non-member → “not in this campaign” message; unauthenticated → magic link form with ?next=/join/:token hidden field so the signed-in redirect drops them straight to the session. MagicLinksController updated to accept and forward ?next= (relative paths only — open redirect guard). MagicLinkMailer#login accepts next_path: kwarg and embeds it in the token URL. DM sees a “Session join link” copy-button card on the session show page (clipboard Stimulus controller). get_game_session MCP tool returns join_url for DMs. 14 new tests (model + controller), full suite 491 green.