Skip to content

Subsystem 1b-ii: live connection lifecycle, failover & #info status - #6

Merged
HandyS11 merged 14 commits into
developfrom
feat/live-connection
Jun 15, 2026
Merged

Subsystem 1b-ii: live connection lifecycle, failover & #info status#6
HandyS11 merged 14 commits into
developfrom
feat/live-connection

Conversation

@HandyS11

Copy link
Copy Markdown
Owner

Summary

Builds the live Rust+ connection layer on top of 1b-i's credential pool: one live socket per (guild, server) driven by the pool's Active player credential, with restart-survival, auto-failover, heartbeat health, a ManageGuild swap control on #info, and a live-status #info embed.

New RustPlusBot.Features.Connections project (mirrors Features.Pairing):

  • IRustSocketSource / RustPlusSocketSource — integration shim over RustPlusApi 2.0.0-beta.1 (construction-tested only; auth-rejection error mapping is a documented VERIFY for live tuning).
  • ConnectionSupervisor (singleton) — one connect → heartbeat → reconnect/failover loop per server; auto-promotes the next Standby on auth-reject, marks the dead credential Invalid + DMs its owner, capped exponential backoff on unreachable, restart-survival via StartAllAsync. Publishes ConnectionStatusChangedEvent only on a real state change.
  • ConnectionHostedServiceStartAll on startup + consumes ServerRegisteredEvent; StopAll on shutdown.
  • ConnectionComponentModule — ManageGuild #info swap-select handler.

Supporting changes:

  • ConnectionState reshaped (drop IsHealthy; add Status enum + PlayerCount) — now the restart-surviving source of truth. Migration LiveConnections.
  • New IConnectionStore (Persistence): connection-state + pool ops (GetActiveCredential / ListPool / Promote / MarkInvalid / ListConnectableServers / UpsertStatus with a changed-flag).
  • IUserDmSender extracted into the Discord project; DiscordOwnerNotifier refactored onto it.
  • Workspace ServerInfoMessageRenderer rewritten for live status + the swap select (workspace:info:swap:{serverId}); WorkspaceHostedService gained a second consumer for ConnectionStatusChangedEvent → re-renders #info.
  • ConnectionOptions (timeouts/backoff/heartbeat) bound + validated ValidateOnStart; AddConnections() wired in the Host.

Deferred to 1b-iii (intentionally not built): IP-change re-identification, RustServer-removal FK/cascade cleanup, account disconnect/remove flow (FcmRegistrationStatus.Disabled).

Test Plan

  • dotnet build RustPlusBot.slnx → 0 warnings / 0 errors (strict analyzers)
  • dotnet test RustPlusBot.slnx → 104 passed (Connections 10, Workspace 33, Persistence 37, Pairing 20, Abstractions 4)
  • ReSharper format gate dotnet jb cleanupcode --profile=ReformatAndReorder applied
  • dotnet ef migrations has-pending-model-changes → no drift
  • Verify the RustPlusApi adapter's auth-rejection mapping against a live server (documented VERIFY in RustPlusSocketSource)

🤖 Generated with Claude Code

HandyS11 and others added 13 commits June 15, 2026 15:53
Replace IsHealthy (bool) with Status (ConnectionStatus enum) and add
nullable PlayerCount; generate LiveConnections migration and schema test.
… pool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Notifier

Introduces IUserDmSender (Discord project) as a reusable DM primitive and
replaces DiscordOwnerNotifier's direct DiscordSocketClient usage with it;
fixes PairingRegistrationTests to supply a substitute IUserDmSender.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…over, restart-survival)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives IConnectionSupervisor: StartAllAsync on startup, reacts to
ServerRegisteredEvent to call EnsureConnectionAsync, StopAllAsync on
shutdown. Added InternalsVisibleTo DynamicProxyGenAssembly2 to allow
NSubstitute to proxy the internal IConnectionSupervisor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ConnectionComponentModule handling workspace:info:swap:* select menus
- Add ConnectionServiceCollectionExtensions.AddConnections() DI extension
- Wire ConnectionOptions validation and AddConnections() into Program.cs
- Add project reference for Connections in Host.csproj
- Add ConnectionRegistrationTests verifying IConnectionSupervisor and IConnectionStore resolve

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 15, 2026 15:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Builds a new live Rust+ connection subsystem (one socket per (guild, server)) with persisted connection state, automatic reconnect/failover, and Workspace #info live-status rendering (including a ManageGuild swap-select for the active credential).

Changes:

  • Adds RustPlusBot.Features.Connections (socket source seam, supervisor loop, hosted service, and swap interaction module).
  • Introduces persistence support for live connection status + credential pool ops via IConnectionStore and a LiveConnections migration.
  • Updates Workspace #info rendering to show live connection status/player info and to refresh on ConnectionStatusChangedEvent; refactors owner DM sending behind IUserDmSender.

Reviewed changes

Copilot reviewed 48 out of 49 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/RustPlusBot.Persistence.Tests/PersistenceRegistrationTests.cs Verifies IConnectionStore DI registration
tests/RustPlusBot.Persistence.Tests/Connections/ConnectionStoreTests.cs Tests connection store pool + status ops
tests/RustPlusBot.Persistence.Tests/Connections/ConnectionStateSchemaTests.cs Verifies EF schema round-trip for new fields
tests/RustPlusBot.Features.Workspace.Tests/Messages/RendererTests.cs Updates #info rendering tests for live status
tests/RustPlusBot.Features.Workspace.Tests/Hosting/WorkspaceConnectionStatusTests.cs Tests workspace refresh on status-change event
tests/RustPlusBot.Features.Pairing.Tests/PairingRegistrationTests.cs Adds IUserDmSender test DI seam
tests/RustPlusBot.Features.Pairing.Tests/DiscordOwnerNotifierTests.cs Tests notifier uses IUserDmSender
tests/RustPlusBot.Features.Connections.Tests/TestDb.cs SQLite test DB helper for Connections tests
tests/RustPlusBot.Features.Connections.Tests/SeamSmokeTests.cs Smoke test for fake socket source seam
tests/RustPlusBot.Features.Connections.Tests/RustPlusSocketSourceTests.cs Construction test for RustPlusApi adapter
tests/RustPlusBot.Features.Connections.Tests/RustPlusBot.Features.Connections.Tests.csproj New Connections test project
tests/RustPlusBot.Features.Connections.Tests/Fakes/FakeRustSocketSource.cs Scripted fake socket source for supervisor tests
tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs End-to-end supervisor lifecycle/failover tests
tests/RustPlusBot.Features.Connections.Tests/ConnectionRegistrationTests.cs Verifies Connections DI resolves
tests/RustPlusBot.Features.Connections.Tests/ConnectionHostedServiceTests.cs Tests hosted service starts/stops supervisor
src/RustPlusBot.Persistence/PersistenceServiceCollectionExtensions.cs Registers IConnectionStore in persistence DI
src/RustPlusBot.Persistence/Migrations/BotDbContextModelSnapshot.cs Updates snapshot for status/player count
src/RustPlusBot.Persistence/Migrations/20260615134626_LiveConnections.Designer.cs New migration designer output
src/RustPlusBot.Persistence/Migrations/20260615134626_LiveConnections.cs Migration: rename IsHealthyStatus, add PlayerCount
src/RustPlusBot.Persistence/Connections/IConnectionStore.cs New persistence API for connection state + pool ops
src/RustPlusBot.Persistence/Connections/ConnectionStore.cs EF implementation of connection store
src/RustPlusBot.Host/RustPlusBot.Host.csproj References Connections feature project
src/RustPlusBot.Host/Program.cs Binds/validates ConnectionOptions, wires AddConnections()
src/RustPlusBot.Features.Workspace/WorkspaceComponentIds.cs Adds swap-select custom-id prefix
src/RustPlusBot.Features.Workspace/RustPlusBot.Features.Workspace.csproj Adds proxy visibility for NSubstitute
src/RustPlusBot.Features.Workspace/Messages/ServerInfoMessageRenderer.cs Renders live status + swap select in #info
src/RustPlusBot.Features.Workspace/Localization/LocalizationCatalog.cs Adds localization keys for new #info fields
src/RustPlusBot.Features.Workspace/Hosting/WorkspaceHostedService.cs Adds consumer loop for connection-status events
src/RustPlusBot.Features.Pairing/Notifications/DiscordOwnerNotifier.cs Refactors notifier to use IUserDmSender
src/RustPlusBot.Features.Connections/Supervisor/IConnectionSupervisor.cs New supervisor abstraction
src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs Core connect/heartbeat/reconnect/failover loop
src/RustPlusBot.Features.Connections/RustPlusBot.Features.Connections.csproj New Connections feature project
src/RustPlusBot.Features.Connections/Modules/ConnectionComponentModule.cs Implements ManageGuild swap-select handler
src/RustPlusBot.Features.Connections/Listening/SocketConnectOutcome.cs Connect outcome classification enum
src/RustPlusBot.Features.Connections/Listening/RustPlusSocketSource.cs RustPlusApi-backed socket adapter
src/RustPlusBot.Features.Connections/Listening/IRustSocketSource.cs Factory seam for socket creation
src/RustPlusBot.Features.Connections/Listening/IRustServerConnection.cs Abstraction for a live server socket
src/RustPlusBot.Features.Connections/Listening/HeartbeatResult.cs Heartbeat result classification
src/RustPlusBot.Features.Connections/Hosting/ConnectionHostedService.cs Hosted service driving supervisor lifecycle
src/RustPlusBot.Features.Connections/ConnectionServiceCollectionExtensions.cs DI registration for Connections feature
src/RustPlusBot.Features.Connections/ConnectionOptions.cs Config options for timeouts/backoff/heartbeat
src/RustPlusBot.Domain/Connections/ConnectionStatus.cs New status enum persisted in connection state
src/RustPlusBot.Domain/Connections/ConnectionState.cs Reshapes state: adds Status + PlayerCount
src/RustPlusBot.Discord/Notifications/IUserDmSender.cs New DM-sending abstraction
src/RustPlusBot.Discord/Notifications/DiscordUserDmSender.cs Discord.Net implementation of DM sender
src/RustPlusBot.Discord/DiscordServiceCollectionExtensions.cs Registers IUserDmSender in Discord DI
src/RustPlusBot.Abstractions/Events/ConnectionStatusChangedEvent.cs New event for #info refresh
RustPlusBot.slnx Adds Connections projects to solution
Directory.Packages.props Adds RustPlusApi 2.0.0-beta.1 package version
Files not reviewed (1)
  • src/RustPlusBot.Persistence/Migrations/20260615134626_LiveConnections.Designer.cs: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +63 to +74
public async Task EnsureConnectionAsync(ulong guildId, Guid serverId, CancellationToken cancellationToken = default)
{
var key = (guildId, serverId);
await StopConnectionAsync(key).ConfigureAwait(false);
if (_shutdown.IsCancellationRequested)
{
return;
}

var cts = CancellationTokenSource.CreateLinkedTokenSource(_shutdown.Token);
_connections[key] = new Handle(cts, Task.Run(() => RunAsync(key, cts.Token), CancellationToken.None));
}
Comment on lines +12 to +21
public IRustServerConnection Create(string ip, int port, ulong steamId, string playerToken)
{
if (!int.TryParse(playerToken, CultureInfo.InvariantCulture, out var token))
{
throw new ArgumentException($"playerToken is not a valid numeric token: '{playerToken}'.",
nameof(playerToken));
}

return new RustPlusServerConnection(ip, port, steamId, token, logger);
}
Comment on lines +30 to +36
if (!Guid.TryParse(serverIdRaw, out var serverId)
|| selectedValues.Length == 0
|| !Guid.TryParse(selectedValues[0], out var credentialId))
{
await RespondAsync("That selection wasn't valid.", ephemeral: true).ConfigureAwait(false);
return;
}
@HandyS11
HandyS11 force-pushed the feat/live-connection branch from c45796a to d1eeec1 Compare June 15, 2026 16:14
@HandyS11
HandyS11 merged commit e035493 into develop Jun 15, 2026
3 checks passed
@HandyS11
HandyS11 deleted the feat/live-connection branch June 15, 2026 17:25
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.

2 participants