feat(mgmt): secured-write approve relays to site MxGateway write with CAS race guard (T14b)
This commit is contained in:
+27
@@ -79,4 +79,31 @@ public class SecuredWriteRepository : ISecuredWriteRepository
|
||||
.Take(take)
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> TryMarkApprovedAsync(
|
||||
long id,
|
||||
string verifierUser,
|
||||
string? verifierComment,
|
||||
DateTime decidedAtUtc,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// Single-statement compare-and-swap: the conditional WHERE Status='Pending'
|
||||
// makes the Pending->Approved transition atomic at the row level, so two
|
||||
// verifiers approving concurrently produce exactly one rowsAffected==1 (the
|
||||
// winner) and one rowsAffected==0 (the loser). Parameterised via
|
||||
// ExecuteSqlInterpolatedAsync — same raw-SQL conditional-update pattern as
|
||||
// SiteCallAuditRepository's upsert-on-newer-status path.
|
||||
var rowsAffected = await _context.Database.ExecuteSqlInterpolatedAsync(
|
||||
$@"UPDATE dbo.PendingSecuredWrites
|
||||
SET Status = 'Approved',
|
||||
VerifierUser = {verifierUser},
|
||||
VerifierComment = {verifierComment},
|
||||
DecidedAtUtc = {decidedAtUtc}
|
||||
WHERE Id = {id}
|
||||
AND Status = 'Pending';",
|
||||
ct);
|
||||
|
||||
return rowsAffected == 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user