feat(deploy): pending-deployment repository with supersession + purge

This commit is contained in:
Joseph Doherty
2026-06-26 12:19:54 -04:00
parent 81cb455f19
commit 290acfb1f0
3 changed files with 276 additions and 0 deletions
@@ -136,6 +136,44 @@ public interface IDeploymentManagerRepository
/// <returns>A task representing the asynchronous operation.</returns>
Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default);
// Notify-and-fetch: PendingDeployment staging store
/// <summary>
/// Stages a flattened instance config for an in-flight deployment. Supersession:
/// any prior pending row for the same <c>InstanceId</c> is removed first, so at most
/// one pending row exists per instance (safe because the per-instance operation lock
/// serializes same-instance deploys). Does NOT call <see cref="SaveChangesAsync"/> —
/// the caller commits, mirroring the other Add/Delete repository methods.
/// </summary>
/// <param name="pending">The pending deployment to stage.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task AddPendingDeploymentAsync(PendingDeployment pending, CancellationToken cancellationToken = default);
/// <summary>
/// Gets a pending deployment by its deployment ID (the fetch key).
/// </summary>
/// <param name="deploymentId">The deployment ID.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>The pending deployment, or null if not found.</returns>
Task<PendingDeployment?> GetPendingDeploymentByIdAsync(string deploymentId, 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
/// Add/Delete repository methods.
/// </summary>
/// <param name="deploymentId">The deployment ID to delete.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task DeletePendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default);
/// <summary>
/// Purges all pending deployments whose <c>ExpiresAtUtc</c> is at or before
/// <paramref name="nowUtc"/> (TTL maintenance). This is a self-contained maintenance
/// operation: it commits its own delete and returns the number of rows removed.
/// </summary>
/// <param name="nowUtc">The current UTC time; rows with <c>ExpiresAtUtc &lt;= nowUtc</c> are purged.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>The number of pending deployments purged.</returns>
Task<int> PurgeExpiredPendingDeploymentsAsync(DateTimeOffset nowUtc, CancellationToken cancellationToken = default);
// Instance lookups for deployment pipeline
/// <summary>
/// Gets an instance by ID.