From db9a931ed6ef7e80deaf6c502ff32d8684ef0bb2 Mon Sep 17 00:00:00 2001 From: Toutsu Date: Wed, 20 May 2026 12:48:25 +0300 Subject: [PATCH] fix(shared): filter due proposals by source_platform to prevent cross-platform race Both Telegram and Discord deadline services were querying ALL due proposals without filtering by source_platform. If the Telegram service reached a Discord proposal first, it finalized the DB state but skipped message handling. The Discord service then saw status != 'Voting' and never updated the Discord vote message. Fix: GetDueProposalIdsAsync now accepts a sourcePlatform parameter and filters at the DB level. Each service only processes its own platform's proposals. Co-Authored-By: Claude Opus 4.7 --- .../RescheduleSession/RescheduleVotingDeadlineService.cs | 2 +- .../Sessions/DiscordRescheduleVotingDeadlineService.cs | 2 +- .../Sessions/RescheduleSession/RescheduleVotingFinalizer.cs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs index ffd21e8..e35c9e0 100644 --- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs +++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs @@ -46,7 +46,7 @@ public sealed class RescheduleVotingDeadlineService( { try { - var proposalIds = await finalizer.GetDueProposalIdsAsync(ct); + var proposalIds = await finalizer.GetDueProposalIdsAsync("Telegram", ct); foreach (var proposalId in proposalIds) { diff --git a/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs b/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs index 2f1b4be..bb88411 100644 --- a/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs +++ b/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs @@ -34,7 +34,7 @@ public sealed class DiscordRescheduleVotingDeadlineService( { try { - var proposalIds = await finalizer.GetDueProposalIdsAsync(ct); + var proposalIds = await finalizer.GetDueProposalIdsAsync("Discord", ct); foreach (var id in proposalIds) { await TryFinalizeAsync(id, ct); diff --git a/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs b/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs index 8942b4b..bb00f4a 100644 --- a/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs +++ b/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs @@ -26,7 +26,7 @@ public sealed class RescheduleVotingFinalizer( ISystemClock clock, ILogger logger) { - public async Task> GetDueProposalIdsAsync(CancellationToken ct) + public async Task> GetDueProposalIdsAsync(string sourcePlatform, CancellationToken ct) { await using var connection = await dataSource.OpenConnectionAsync(ct); var proposalIds = (await connection.QueryAsync( @@ -36,10 +36,11 @@ public sealed class RescheduleVotingFinalizer( WHERE status = 'Voting' AND voting_deadline_at IS NOT NULL AND voting_deadline_at <= @Now + AND source_platform = @SourcePlatform ORDER BY voting_deadline_at LIMIT 25 """, - new { Now = clock.UtcNow.UtcDateTime })).ToList(); + new { Now = clock.UtcNow.UtcDateTime, SourcePlatform = sourcePlatform })).ToList(); return proposalIds; }