fix(m9/T24a): scope move-guard native-alarm scan to source-site templates (Ordinal); purpose-built include; add guard-4 + repo tests
This commit is contained in:
@@ -90,6 +90,18 @@ public interface ISiteRepository
|
||||
/// <returns>A task that resolves to the distinct referencing <see cref="Instance"/> entities (empty when none).</returns>
|
||||
Task<IReadOnlyList<Instance>> GetInstancesReferencingDataConnectionAsync(int dataConnectionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a site's instances with their
|
||||
/// <see cref="Instance.NativeAlarmSourceOverrides"/> eagerly loaded. Purpose-built
|
||||
/// for the move-data-connection guard, which must scan source-site instance
|
||||
/// overrides for name-based connection references without paying the eager-load
|
||||
/// cost on the hot-path <see cref="GetInstancesBySiteIdAsync"/>.
|
||||
/// </summary>
|
||||
/// <param name="siteId">The site primary key to filter by.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task that resolves to the site's <see cref="Instance"/> entities with overrides loaded (empty when none).</returns>
|
||||
Task<IReadOnlyList<Instance>> GetInstancesWithNativeAlarmOverridesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Saves all pending changes to the database.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task that resolves to the number of state entries written to the database.</returns>
|
||||
|
||||
@@ -132,9 +132,15 @@ public class SiteRepository : ISiteRepository
|
||||
{
|
||||
return await _dbContext.Instances
|
||||
.Where(i => i.SiteId == siteId)
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<Instance>> GetInstancesWithNativeAlarmOverridesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _dbContext.Instances
|
||||
.Where(i => i.SiteId == siteId)
|
||||
.Include(i => i.NativeAlarmSourceOverrides)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -1426,17 +1426,40 @@ public class ManagementActor : ReceiveActor
|
||||
// instances may override that name
|
||||
// (InstanceNativeAlarmSourceOverride.ConnectionNameOverride). Moving a
|
||||
// connection changes which physical connection that name resolves to on
|
||||
// the source site, so any such reference to the moving name is a blocker.
|
||||
// Detect and report only — never auto-rewrite.
|
||||
// the SOURCE site, so any such reference *on the source site* to the
|
||||
// moving name is a blocker. Detect and report only — never auto-rewrite.
|
||||
//
|
||||
// Scoping: templates are site-agnostic and ConnectionName resolves
|
||||
// against the DEPLOYING site's connection pool at flatten time, so the
|
||||
// template scan is restricted to templates actually instantiated on the
|
||||
// SOURCE site. A template referencing the same connection NAME but only
|
||||
// instantiated on another site is NOT affected by this move (that site
|
||||
// keeps its own connection of the same name), so a global template scan
|
||||
// would be a false positive that over-blocks legitimate moves whenever
|
||||
// connection names are reused across sites (the common case).
|
||||
//
|
||||
// Comparison is StringComparison.Ordinal to match the
|
||||
// flattening/deployment pipeline (FlatteningService / FlatteningPipeline
|
||||
// resolve connection names case-sensitively) — block exactly the
|
||||
// references the runtime would actually fail to resolve.
|
||||
var referenceBlockers = new List<string>();
|
||||
|
||||
// Source-site instances with their native-alarm-source overrides eagerly
|
||||
// loaded (purpose-built load so the hot-path GetInstancesBySiteIdAsync
|
||||
// stays lean). Reused below for both the override scan and to derive the
|
||||
// set of templates to scan.
|
||||
var sourceInstances = await repo.GetInstancesWithNativeAlarmOverridesBySiteIdAsync(sourceSiteId);
|
||||
|
||||
// Only templates instantiated on the SOURCE site can be broken by the move.
|
||||
var templateRepo = sp.GetRequiredService<ITemplateEngineRepository>();
|
||||
var templates = await templateRepo.GetAllTemplatesAsync();
|
||||
foreach (var template in templates)
|
||||
var sourceTemplateIds = sourceInstances.Select(i => i.TemplateId).Distinct();
|
||||
foreach (var templateId in sourceTemplateIds)
|
||||
{
|
||||
var template = await templateRepo.GetTemplateByIdAsync(templateId);
|
||||
if (template is null) continue;
|
||||
foreach (var source in template.NativeAlarmSources)
|
||||
{
|
||||
if (string.Equals(source.ConnectionName, conn.Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(source.ConnectionName, conn.Name, StringComparison.Ordinal))
|
||||
{
|
||||
referenceBlockers.Add(
|
||||
$"template '{template.Name}' native-alarm-source '{source.Name}'");
|
||||
@@ -1446,12 +1469,11 @@ public class ManagementActor : ReceiveActor
|
||||
|
||||
// Instance-level overrides on the SOURCE site that name this connection
|
||||
// would orphan once the connection leaves the site.
|
||||
var sourceInstances = await repo.GetInstancesBySiteIdAsync(sourceSiteId);
|
||||
foreach (var instance in sourceInstances)
|
||||
{
|
||||
foreach (var ovr in instance.NativeAlarmSourceOverrides)
|
||||
{
|
||||
if (string.Equals(ovr.ConnectionNameOverride, conn.Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(ovr.ConnectionNameOverride, conn.Name, StringComparison.Ordinal))
|
||||
{
|
||||
referenceBlockers.Add(
|
||||
$"instance '{instance.UniqueName}' (ID {instance.Id}) native-alarm-source override '{ovr.SourceCanonicalName}'");
|
||||
|
||||
Reference in New Issue
Block a user