Skip to content

Adopt Persistord 1.0.0-beta.3: upserts, timestamps, guild scope, managed webhooks - #91

Merged
HandyS11 merged 6 commits into
developfrom
chore/persistord-beta3
Sep 10, 2026
Merged

Adopt Persistord 1.0.0-beta.3: upserts, timestamps, guild scope, managed webhooks#91
HandyS11 merged 6 commits into
developfrom
chore/persistord-beta3

Conversation

@HandyS11

Copy link
Copy Markdown
Owner

Persistord 1.0.0-beta.3 ships the six things this bot was hand-rolling on top of Persistord.Core 1.0.0-beta2. This adopts them, in four reviewable commits.

What changed

1. chore: bump to beta.3, drop the tables nothing writes. beta.3 moved the Discord skeleton graph out of DiscordDbContext into DiscordGraphDbContext. The bot owns its Discord resources rather than mirroring them, so it stays on DiscordDbContext and Guilds/Channels/Users/Members/Roles leave the model — none has ever held a row. EventSubscriptions and PairedEntities go with them: production code only ever purged them, nothing ever wrote one. ClearAllTablesAsync replaces the hand-rolled factory reset (a SQLite-only defer_foreign_keys pragma, raw DELETE statements and an identifier guard for the Sonar gate).

2. refactor: upserts and timestamps. Fifteen read-then-insert-or-update blocks collapse onto UpsertAsync, taking six identical catch (DbUpdateException) race-recovery blocks with them. Every natural key used is already backed by a unique index, which is what makes that recovery correct. ICreatedAt/IUpdatedAt plus a TimestampInterceptor on the context factory replace fifteen manual clock stamps; the five entities that spelled the columns CreatedUtc/UpdatedUtc are renamed to match (rename only, no data touched). ClanStore and VendingStore take TimeProvider instead of IClock, so the persistence layer reads one clock. Tests move onto Persistord.Testing: SqliteTestDatabase replaces two copies of the in-memory fixture plus the shared-cache boilerplate inlined across twenty files.

3. refactor: IGuildScoped. Every mapped entity declares it, so the guild purge becomes one PurgeGuildAsync — dependents before principals, in a single transaction — instead of four untransacted ExecuteDelete calls plus a per-server RemoveAsync loop leaning on the RustServer cascade. A new guild-scoped table now joins the purge by declaring the interface rather than by someone remembering to add a line. GuildScopeConvention adds a GuildId index to the five per-server tables whose key leads with ServerId.

4. feat: managed chat webhooks. DiscordChatWebhookPoster re-discovered its webhook by name on every boot, so a rename orphaned it and the bot silently created a duplicate — the exact failure ManagedWebhook exists to prevent. Webhook id and token are now recorded per (guild, channel, kind); the name lookup stays as the fallback so a guild provisioned by an older build adopts its existing webhook instead of getting a second one.

Deliberate non-adoptions

  • ApplyGuildRoot — the bot has no Guilds table and never writes one; the cascading FK it wires would make a guild row a prerequisite for every insert. PurgeGuildAsync needs neither.
  • ApplyManagedModule — only ManagedWebhookConfiguration is applied. The bot's Provisioned* tables key their scope by a real foreign key to RustServers (load-bearing: it is why teardown must delete Discord resources before the server row), where ManagedResource.Scope is an opaque string. Mapping the other three would only add empty tables.
  • Persistord.Protection — the webhook token is protected by the bot's own ICredentialProtector, the one already covering player and FCM credentials. Persistord's [Protected] attribute on that column stays inert; wiring ApplyProtection later would have to drop the manual call in the same change, and migrating the existing columns would mean re-encrypting live ciphertext under a new purpose string.
  • ConnectionStore.UpsertStatusAsync keeps its own insert guard (it must swallow a vanished-parent FK violation and report "no change", which is not a natural-key race), and ServerService.ResolveOrCreateByEndpointAsync keeps its own recovery because its caller needs "was it created?" — UpsertResult.Changed reports whether the call wrote, which is true for any pending change in the context.

Migrations

Four, all additive or empty-table drops: DropMirrorAndDeadTables, StampedTimestamps (column renames), GuildScopeIndexes, ChatWebhooks.

Note

RustPlusBot.Domain now references Persistord.Core for the marker interfaces, which pulls EF Core in transitively — the interfaces do not ship in a dependency-free abstractions package.

Verification

dotnet build: clean. dotnet test: 1670 passed, 0 failed (up from 1661; the new tests cover the chat webhook store, the model shape Persistord's conventions assume, and that every mapped entity is guild-scoped). ReSharper ReformatAndReorder applied to every touched file.

🤖 Generated with Claude Code

HandyS11 and others added 5 commits September 10, 2026 16:40
beta.3 moves the Discord skeleton graph out of DiscordDbContext and into
DiscordGraphDbContext. The bot owns its Discord resources rather than
mirroring them, so it stays on DiscordDbContext and the five mirror tables
(Guilds/Channels/Users/Members/Roles) leave the model — they have never held
a row. EventSubscriptions and PairedEntities go with them: production code
only ever purged them, nothing ever wrote one.

ClearAllTablesAsync replaces the hand-rolled factory reset, which needed a
SQLite-only defer_foreign_keys pragma, raw DELETE statements and an
identifier guard to satisfy the Sonar gate. SnowflakeKeyConvention makes
GuildSettings' explicit ValueGeneratedNever redundant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every store that wrote a row was hand-rolling the same three things:
read-then-insert-or-update, a catch(DbUpdateException) that detaches, re-reads
the winner and re-applies the mutation, and a manual clock stamp. Persistord's
UpsertAsync does all three, so fifteen call sites collapse and six identical
race-recovery blocks disappear with them.

ICreatedAt/IUpdatedAt plus a TimestampInterceptor registered on the context
factory replace the manual stamps. That needed one naming decision: the
interfaces name the columns CreatedAt/UpdatedAt, so the five entities that
spelled them CreatedUtc/UpdatedUtc are renamed to match (a column rename, no
data touched). PostedUtc and LastSeenUtc stay hand-written — they record when
something happened in Discord or in game, not when the row was last written,
and VendingStore deliberately only moves PostedUtc when a message is reposted.
ClanStore and VendingStore now take TimeProvider rather than IClock, so the
persistence layer reads one clock.

Two shapes deliberately keep their own logic: ConnectionStore's insert guard
(it must swallow a vanished-parent FK violation and report "no change", which
is not a natural-key race) and ServerService.ResolveOrCreateByEndpointAsync
(its caller needs "was it created?", which UpsertResult.Changed does not
answer — Changed is true for any pending write in the context).

Tests move onto Persistord.Testing: SqliteTestDatabase replaces two copies of
the in-memory fixture and the shared-cache boilerplate inlined across twenty
files, and a FixedTimeProvider drives the interceptor where a test asserts on
a timestamp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every mapped entity carries a GuildId, so every one of them now declares
IGuildScoped. That turns the guild purge from a hand-maintained list — four
ExecuteDelete calls outside any transaction, plus a per-server RemoveAsync
loop leaning on the RustServer cascade — into one PurgeGuildAsync: dependents
before principals, in a single transaction, covering both the rows that
cascaded off RustServer and the ones that never had a foreign key to it. A new
guild-scoped table now joins the purge by declaring the interface instead of by
someone remembering to add a line.

Stopping the connection loops stays where it was, before any delete: a loop
whose server row has vanished faults on the connection-state foreign key at
its next status write and leaks its socket. The purge runs as SQL and does not
touch the change tracker, so the tracker is cleared after it.

ApplyGuildRoot is deliberately not used: the bot has no Guilds table and never
writes one, and the cascading foreign key it wires would make a guild row a
prerequisite for every insert. PurgeGuildAsync needs neither.

GuildScopeConvention adds a GuildId index to the five per-server tables whose
key leads with ServerId; the rest already lead with GuildId and are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DiscordChatWebhookPoster re-discovered its webhook by name on every boot, so
renaming one in Discord orphaned it and the bot silently created a duplicate
alongside it — the exact failure Persistord.Managed's ManagedWebhook exists to
prevent. The webhook id and token are now recorded per (guild, channel, kind)
and a restart posts through the webhook it already owns.

The name lookup stays as the fallback: a guild provisioned by an older build
has no record yet, and it must adopt its existing webhook rather than create a
second one. If the recorded webhook turns out to be unusable — deleted, or its
token revoked, which the DiscordWebhookClient constructor surfaces — the record
is dropped and that same fallback resolves a replacement.

Only ManagedWebhookConfiguration is applied, not ApplyManagedModule: the bot
owns its categories, channels and anchored messages through its own Provisioned*
tables, whose scope is a real foreign key to RustServers rather than
ManagedResource's opaque string, so mapping the other three would only add empty
tables. The token is protected by the bot's own ICredentialProtector, the one
that already covers player and FCM credentials; Persistord's [Protected]
attribute on that column stays inert, and wiring Persistord.Protection later
would have to drop the manual call in the same change.

Because ManagedResource is IGuildScoped and timestamped, the new table joins the
guild purge and the timestamp interceptor without any further wiring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 10, 2026 15:10

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.

🔵 Needs a closer look

It includes broad persistence refactors (upserts/timestamps/purges), schema migrations, and new managed webhook storage that warrant careful human validation beyond automated review.

Pull request overview

This PR upgrades the persistence layer to Persistord 1.0.0-beta.3, replacing several hand-rolled EF Core patterns (upserts, timestamp stamping, guild-scoped purges, and test DB plumbing) and introducing managed chat webhooks persisted per (guild, channel, kind) to prevent webhook duplication across restarts.

Changes:

  • Adopt Persistord beta.3 helpers: UpsertAsync, timestamp interception via TimeProvider, and PurgeGuildAsync/ClearAllTablesAsync.
  • Remove unused “mirror” tables and migrate timestamp columns (CreatedUtc/UpdatedUtcCreatedAt/UpdatedAt).
  • Add managed chat webhook persistence and update chat posting APIs to include guildId.
File summaries
File Description
tests/RustPlusBot.Persistence.Tests/Workspace/WorkspaceStoreWipePingTests.cs Update workspace tests for new fixtures/time provider
tests/RustPlusBot.Persistence.Tests/Workspace/WorkspaceStoreTests.cs Update workspace tests for new fixtures/time provider
tests/RustPlusBot.Persistence.Tests/Workspace/WorkspaceStoreByKeyTests.cs Update workspace-by-key tests for new fixtures/time provider
tests/RustPlusBot.Persistence.Tests/Wipes/WipeBaselineStoreTests.cs Switch tests to Persistord.Testing DB wrapper
tests/RustPlusBot.Persistence.Tests/VendingStoreTests.cs Update vending tests for TimeProvider + new fixture DB
tests/RustPlusBot.Persistence.Tests/Switches/SwitchStoreTests.cs Update switch tests for interceptor-driven timestamps
tests/RustPlusBot.Persistence.Tests/Switches/SmartSwitchSchemaTests.cs Update schema tests for CreatedAt rename
tests/RustPlusBot.Persistence.Tests/StorageMonitors/StorageMonitorStoreTests.cs Update storage monitor tests for CreatedAt rename
tests/RustPlusBot.Persistence.Tests/StorageMonitors/SmartStorageMonitorSchemaTests.cs Update schema tests for CreatedAt rename
tests/RustPlusBot.Persistence.Tests/SqliteContextFixture.cs Centralize SqliteTestDatabase + TimestampInterceptor setup
tests/RustPlusBot.Persistence.Tests/RustPlusBot.Persistence.Tests.csproj Add Persistord.Testing dependency
tests/RustPlusBot.Persistence.Tests/PersistenceRegistrationTests.cs Assert chat webhook store registration
tests/RustPlusBot.Persistence.Tests/FixedTimeProvider.cs Add mutable TimeProvider for timestamp assertions
tests/RustPlusBot.Persistence.Tests/Devices/PairedDeviceStoreTests.cs Update paired-device tests for new stores/fixture
tests/RustPlusBot.Persistence.Tests/Credentials/FcmRegistrationStoreTests.cs Update FCM tests for interceptor-driven UpdatedAt
tests/RustPlusBot.Persistence.Tests/Connections/ConnectionStoreTests.cs Update connection store tests for new fixture + no clock
tests/RustPlusBot.Persistence.Tests/Commands/MuteStoreTests.cs Update mute tests for new fixture DB wrapper
tests/RustPlusBot.Persistence.Tests/ClanStoreTests.cs Update clan tests for TimeProvider + timestamp rename
tests/RustPlusBot.Persistence.Tests/Chat/ChatWebhookStoreTests.cs New tests for persisted managed webhooks + protection behavior
tests/RustPlusBot.Persistence.Tests/BotDbContextTests.cs Add model-shape tests for Persistord conventions + IGuildScoped
tests/RustPlusBot.Persistence.Tests/Alarms/SmartAlarmSchemaTests.cs Update schema tests for CreatedAt rename
tests/RustPlusBot.Persistence.Tests/Alarms/AlarmStoreTests.cs Update alarm tests for interceptor-driven timestamps
tests/RustPlusBot.Features.Workspace.Tests/Teardown/ServerPurgeServiceTests.cs Update teardown tests to use SqliteTestDatabase
tests/RustPlusBot.Features.Workspace.Tests/Teardown/GuildPurgeServiceTests.cs Update purge tests to cover IGuildScoped tables
tests/RustPlusBot.Features.Workspace.Tests/RustPlusBot.Features.Workspace.Tests.csproj Add Persistord.Testing dependency
tests/RustPlusBot.Features.Workspace.Tests/Locating/TeamChatChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/SwitchChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/StorageMonitorChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/SetupChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/PlayerEventChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/MapChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/EventChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/CachingChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Workspace.Tests/Locating/AlarmChannelLocatorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Pairing.Tests/TestDb.cs Replace private in-memory connection with SqliteTestDatabase.Private
tests/RustPlusBot.Features.Pairing.Tests/ServerPairingCoordinatorTests.cs Update harness to hold SqliteTestDatabase instead of connection
tests/RustPlusBot.Features.Pairing.Tests/RustPlusBot.Features.Pairing.Tests.csproj Add Persistord.Testing dependency
tests/RustPlusBot.Features.Pairing.Tests/PairingSupervisorTests.cs Use SqliteTestDatabase.Options() for scoped contexts
tests/RustPlusBot.Features.Connections.Tests/TeamChatSenderTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/SwitchQueryTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/SwitchPrimingTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/StorageSweepTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/StorageMonitorPrimingTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/ServerQueryTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/RustPlusBot.Features.Connections.Tests.csproj Add Persistord.Testing dependency
tests/RustPlusBot.Features.Connections.Tests/MapImageQueryTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/ClanSupervisorTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/AlarmSweepTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Connections.Tests/AlarmPrimingTests.cs Replace keep-alive connection with SqliteTestDatabase.Shared
tests/RustPlusBot.Features.Chat.Tests/Hosting/ChatHostedServiceTests.cs Update webhook poster calls to include guildId
tests/RustPlusBot.Features.Chat.Tests/ChatRelayTests.cs Update webhook poster calls to include guildId
tests/RustPlusBot.Features.Chat.Tests/ChatRegistrationTests.cs Update webhook poster calls to include guildId
src/RustPlusBot.Persistence/Workspace/WorkspaceStore.cs Replace read-then-write with Persistord UpsertAsync + interceptor timestamps
src/RustPlusBot.Persistence/Vending/VendingStore.cs Use UpsertAsync and TimeProvider for PostedUtc semantics
src/RustPlusBot.Persistence/Switches/SwitchStore.cs Remove clock injection; rely on shared paired-device store
src/RustPlusBot.Persistence/StorageMonitors/StorageMonitorStore.cs Remove clock injection; rely on shared paired-device store
src/RustPlusBot.Persistence/RustPlusBot.Persistence.csproj Add Persistord.Managed dependency
src/RustPlusBot.Persistence/PersistenceServiceCollectionExtensions.cs Register TimeProvider + TimestampInterceptor + chat webhook store
src/RustPlusBot.Persistence/Migrations/BotDbContextModelSnapshot.cs Update model snapshot for dropped tables + managed webhooks + timestamps
src/RustPlusBot.Persistence/Migrations/20260910150057_ChatWebhooks.cs Add ManagedWebhooks table + unique index
src/RustPlusBot.Persistence/Migrations/20260910145600_GuildScopeIndexes.cs Add GuildId indexes for guild-scoped tables
src/RustPlusBot.Persistence/Migrations/20260910145042_StampedTimestamps.Designer.cs Add migration designer for timestamp renames
src/RustPlusBot.Persistence/Migrations/20260910145042_StampedTimestamps.cs Rename timestamp columns to CreatedAt/UpdatedAt
src/RustPlusBot.Persistence/Migrations/20260910143851_DropMirrorAndDeadTables.cs Drop unused mirror/dead tables
src/RustPlusBot.Persistence/Map/MapSettingsStore.cs Replace read-then-write with UpsertAsync
src/RustPlusBot.Persistence/Maintenance/DatabaseMaintenanceService.cs Replace manual wipe logic with Persistord ClearAllTablesAsync
src/RustPlusBot.Persistence/Devices/PairedDeviceStore.cs Replace insert-race recovery with UpsertAsync
src/RustPlusBot.Persistence/Credentials/FcmRegistrationStore.cs Replace manual upsert + timestamps with UpsertAsync + interceptor
src/RustPlusBot.Persistence/Credentials/CredentialStore.cs Replace read-then-write with UpsertAsync
src/RustPlusBot.Persistence/Connections/ConnectionStore.cs Remove manual UpdatedAt writes; rely on interceptor
src/RustPlusBot.Persistence/Configurations/PairedEntityConfiguration.cs Remove configuration for dropped PairedEntities table
src/RustPlusBot.Persistence/Configurations/GuildSettingsConfiguration.cs Rely on Persistord snowflake key convention
src/RustPlusBot.Persistence/Configurations/EventSubscriptionConfiguration.cs Remove configuration for dropped EventSubscriptions table
src/RustPlusBot.Persistence/Commands/MuteStore.cs Replace read-then-write with UpsertAsync
src/RustPlusBot.Persistence/Clans/ClanStore.cs Replace read-then-write with UpsertAsync + TimeProvider for LastSeenUtc
src/RustPlusBot.Persistence/Chat/IChatWebhookStore.cs New persistence interface for chat relay webhooks
src/RustPlusBot.Persistence/Chat/ChatWebhookStore.cs Persist managed webhooks with token protection + forget-on-unreadable
src/RustPlusBot.Persistence/BotDbContext.cs Remove unused sets; add ManagedWebhook set + apply ManagedWebhookConfiguration
src/RustPlusBot.Persistence/Alarms/AlarmStore.cs Replace insert-race recovery with UpsertAsync
src/RustPlusBot.Features.Workspace/Teardown/GuildPurgeService.cs Replace explicit deletes with Persistord PurgeGuildAsync
src/RustPlusBot.Features.Chat/Webhooks/IChatWebhookPoster.cs Add guildId parameter to poster API
src/RustPlusBot.Features.Chat/Webhooks/DiscordChatWebhookPoster.cs Record/reuse webhooks via IChatWebhookStore; fallback to name lookup
src/RustPlusBot.Features.Chat/Relaying/ChatRelay.cs Pass guildId through to webhook poster
src/RustPlusBot.Domain/Workspace/ProvisionedMessage.cs Add IGuildScoped + timestamp marker interfaces; doc updates
src/RustPlusBot.Domain/Workspace/ProvisionedChannel.cs Add IGuildScoped + ICreatedAt marker; doc updates
src/RustPlusBot.Domain/Workspace/ProvisionedCategory.cs Add IGuildScoped + ICreatedAt marker; doc updates
src/RustPlusBot.Domain/Vending/VendingStockNotification.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/Vending/VendingNotification.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/Vending/VendingListingTrack.cs Rename CreatedUtc→CreatedAt + implement ICreatedAt
src/RustPlusBot.Domain/Vending/VendingGridTrack.cs Rename CreatedUtc→CreatedAt + implement ICreatedAt
src/RustPlusBot.Domain/Servers/RustServer.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/RustPlusBot.Domain.csproj Reference Persistord.Core for marker interfaces
src/RustPlusBot.Domain/Map/ServerMapSettings.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/Guilds/GuildSettings.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/Events/EventSubscription.cs Remove dropped EventSubscription entity
src/RustPlusBot.Domain/Entities/PairedEntity.cs Remove dropped PairedEntity entity
src/RustPlusBot.Domain/Devices/PairedDeviceEntity.cs Add IGuildScoped + ICreatedAt marker; rename CreatedUtc→CreatedAt
src/RustPlusBot.Domain/Credentials/PlayerCredential.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/Credentials/FcmRegistration.cs Add IGuildScoped + IUpdatedAt marker; doc updates
src/RustPlusBot.Domain/Connections/ConnectionState.cs Add IGuildScoped + IUpdatedAt marker; doc updates
src/RustPlusBot.Domain/Commands/ServerCommandSettings.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/Clans/ClanState.cs Add IGuildScoped marker; move GuildId field position
src/RustPlusBot.Domain/Clans/ClanPlayerName.cs Rename UpdatedUtc→UpdatedAt + implement IUpdatedAt
src/RustPlusBot.Domain/Alarms/SmartAlarm.cs Rename CreatedUtc→CreatedAt + implement ICreatedAt
Directory.Packages.props Bump Persistord packages and add Persistord.Testing version
Review details

Files not reviewed (4)

  • src/RustPlusBot.Persistence/Migrations/20260910143851_DropMirrorAndDeadTables.Designer.cs: Generated file
  • src/RustPlusBot.Persistence/Migrations/20260910145042_StampedTimestamps.Designer.cs: Generated file
  • src/RustPlusBot.Persistence/Migrations/20260910145600_GuildScopeIndexes.Designer.cs: Generated file
  • src/RustPlusBot.Persistence/Migrations/20260910150057_ChatWebhooks.Designer.cs: Generated file
  • Files reviewed: 105/109 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/RustPlusBot.Persistence.Tests/Chat/ChatWebhookStoreTests.cs
Comment on lines +23 to +26
var (context, connection) = SqliteContextFixture.Create(new FixedTimeProvider(Now));
await using var _ = context;
await using var __ = connection;
var store = new FcmRegistrationStore(context, PassThroughProtector(), FixedClock());
var store = new FcmRegistrationStore(context, PassThroughProtector());

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in daaabb8, along with every other site in the suite. Same commit renames the locals that still said connection: the fixture hands back a SqliteTestDatabase now, not a SqliteConnection.

Disposal runs in reverse of declaration, so `using var _ = context;` followed by
`using var __ = database;` tore the database down first and left the context
outliving it. Harmless in practice — EF does not own a connection it was handed
— but it is backwards, and the suite already declared it the other way round in
about half its files.

The fixture's second element is a SqliteTestDatabase now, not a SqliteConnection,
so the locals that still called it `connection` are renamed to match what they
hold.

Addresses the two review comments on #91.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@HandyS11
HandyS11 merged commit 5ecc136 into develop Sep 10, 2026
3 checks passed
@HandyS11
HandyS11 deleted the chore/persistord-beta3 branch September 10, 2026 15:39
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