fix(reconcile): expiry-aware pending staging — expired rows no longer block self-heal

This commit is contained in:
Joseph Doherty
2026-06-26 17:23:26 -04:00
parent 6538216b0c
commit fd22f5ce0a
5 changed files with 156 additions and 21 deletions
@@ -164,12 +164,15 @@ public interface IDeploymentManagerRepository
/// reconcile from the other node, or an in-flight deploy), the handler reads that existing row
/// so a second concurrently-missing node can fetch the same pending config with its (multi-use,
/// TTL-bound) token instead of being omitted from the gap. Defensive against >1 row: returns
/// the most recently created one.
/// the most recently created one. When <paramref name="nowUtc"/> is supplied, EXPIRED rows
/// (<c>ExpiresAtUtc &lt;= nowUtc</c>) are filtered out so the caller never receives a token the
/// config-fetch endpoint would 404.
/// </summary>
/// <param name="instanceId">The instance whose pending deployment to read.</param>
/// <param name="nowUtc">When supplied, only rows with <c>ExpiresAtUtc &gt; nowUtc</c> are considered (expired rows excluded).</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>The pending deployment for the instance, or null if none is staged.</returns>
Task<PendingDeployment?> GetPendingDeploymentByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
Task<PendingDeployment?> GetPendingDeploymentByInstanceIdAsync(int instanceId, DateTimeOffset? nowUtc = null, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes a pending deployment by its deployment ID. No-op if not found. Does NOT
/// call <see cref="SaveChangesAsync"/> — the caller commits, mirroring the other