fix(deployment): delete NotDeployed instances centrally without a site round-trip

A NotDeployed instance has no live Instance Actor at any site, so Delete is a pure
central-side record cleanup. Transport-imported instances always land NotDeployed
(often against an uncommissioned/unreachable site); a site round-trip made them
undeletable until the site came online. Short-circuit the site call for NotDeployed.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-09 16:22:29 -04:00
parent 83294ab44e
commit ab6077708f
2 changed files with 50 additions and 0 deletions
@@ -563,6 +563,37 @@ public class DeploymentService
instance.UniqueName, _options.OperationLockTimeout, cancellationToken);
var commandId = Guid.NewGuid().ToString("N");
// A NotDeployed instance has no live Instance Actor at any site, so Delete is
// a pure central-side record cleanup -- there is nothing to tear down at the
// site and no reason to require the site be reachable. Transport-imported
// instances always land NotDeployed (often against an uncommissioned or
// unreachable site), so a site round-trip here would make them undeletable
// until their site comes online. Skip the site entirely and remove the record.
if (instance.State == InstanceState.NotDeployed)
{
try
{
await _repository.DeleteInstanceAsync(instanceId, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to delete NotDeployed instance {Instance} centrally", instance.UniqueName);
return Result<InstanceLifecycleResponse>.Failure(
$"Delete failed: the central record for '{instance.UniqueName}' could not be removed: {ex.Message}.");
}
await TryLogAuditAsync(user, "Delete", "Instance", instanceId.ToString(),
instance.UniqueName,
new { CommandId = commandId, Success = true, SiteRoundTripSkipped = true },
cancellationToken);
return Result<InstanceLifecycleResponse>.Success(new InstanceLifecycleResponse(
commandId, instance.UniqueName, Success: true, ErrorMessage: null, DateTimeOffset.UtcNow));
}
var siteId = await ResolveSiteIdentifierAsync(instance.SiteId, cancellationToken);
var command = new DeleteInstanceCommand(commandId, instance.UniqueName, DateTimeOffset.UtcNow);