fix(template-engine): resolve TemplateEngine-006..010 — code-region-aware API/brace scanning, composed-alarm override validation, N+1 fix, doc correction
This commit is contained in:
@@ -3,6 +3,7 @@ using ScadaLink.Commons.Interfaces.Repositories;
|
||||
using ScadaLink.Commons.Interfaces.Services;
|
||||
using ScadaLink.Commons.Types;
|
||||
using ScadaLink.Commons.Types.Enums;
|
||||
using ScadaLink.TemplateEngine;
|
||||
|
||||
namespace ScadaLink.TemplateEngine.Services;
|
||||
|
||||
@@ -13,7 +14,11 @@ namespace ScadaLink.TemplateEngine.Services;
|
||||
/// - Override non-locked attribute values
|
||||
/// - Cannot add or remove attributes (only override existing ones)
|
||||
/// - Per-attribute connection binding (bulk assignment support)
|
||||
/// - Enabled/disabled state with optimistic concurrency
|
||||
/// - Enabled/disabled state. Concurrent edits are last-write-wins — there is no
|
||||
/// version token or conflict detection on instance state, matching the design
|
||||
/// decision (Component-TemplateEngine.md: "Concurrent editing uses
|
||||
/// last-write-wins — no pessimistic locking or conflict detection"). Optimistic
|
||||
/// concurrency in the system applies to deployment status records, not here.
|
||||
/// - Audit logging
|
||||
/// </summary>
|
||||
public class InstanceService
|
||||
@@ -170,10 +175,11 @@ public class InstanceService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a per-instance alarm override. The alarm must exist on the
|
||||
/// template and must not be locked. For HiLo alarms, the override JSON
|
||||
/// merges into the inherited TriggerConfiguration setpoint-by-setpoint;
|
||||
/// for binary trigger types, it replaces the whole config.
|
||||
/// Sets a per-instance alarm override. The alarm must exist in the
|
||||
/// instance's effective alarm set (direct, inherited, or composed) and
|
||||
/// must not be locked. For HiLo alarms, the override JSON merges into the
|
||||
/// inherited TriggerConfiguration setpoint-by-setpoint; for binary trigger
|
||||
/// types, it replaces the whole config.
|
||||
/// </summary>
|
||||
public async Task<Result<InstanceAlarmOverride>> SetAlarmOverrideAsync(
|
||||
int instanceId,
|
||||
@@ -187,17 +193,25 @@ public class InstanceService
|
||||
if (instance == null)
|
||||
return Result<InstanceAlarmOverride>.Failure($"Instance with ID {instanceId} not found.");
|
||||
|
||||
// Verify alarm exists in the template and is not locked. Only direct
|
||||
// template alarms are checked here — composed-member overrides go
|
||||
// through but are silently ignored at runtime if the name doesn't
|
||||
// match (same behavior as attribute overrides).
|
||||
var templateAlarms = await _repository.GetAlarmsByTemplateIdAsync(instance.TemplateId, cancellationToken);
|
||||
var templateAlarm = templateAlarms.FirstOrDefault(a => a.Name == alarmCanonicalName);
|
||||
if (templateAlarm != null && templateAlarm.IsLocked)
|
||||
{
|
||||
// Verify the alarm exists in the instance's effective alarm set and is
|
||||
// not locked. The effective set is resolved via TemplateResolver so that
|
||||
// composed (path-qualified) and inherited alarms are found — a lookup
|
||||
// against the template's direct alarms alone would miss them, silently
|
||||
// accepting an override for a non-existent name or bypassing the lock
|
||||
// rule for a composed alarm. Mirrors SetAttributeOverrideAsync.
|
||||
var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
|
||||
var resolvedAlarm = TemplateResolver
|
||||
.ResolveAllMembers(instance.TemplateId, allTemplates)
|
||||
.FirstOrDefault(m => m.MemberType == "Alarm" && m.CanonicalName == alarmCanonicalName);
|
||||
|
||||
if (resolvedAlarm == null)
|
||||
return Result<InstanceAlarmOverride>.Failure(
|
||||
$"Alarm '{alarmCanonicalName}' does not exist in template {instance.TemplateId}. " +
|
||||
"Cannot override an unknown alarm.");
|
||||
|
||||
if (resolvedAlarm.IsLocked)
|
||||
return Result<InstanceAlarmOverride>.Failure(
|
||||
$"Alarm '{alarmCanonicalName}' is locked and cannot be overridden.");
|
||||
}
|
||||
|
||||
var existingOverride = await _repository.GetAlarmOverrideAsync(
|
||||
instanceId, alarmCanonicalName, cancellationToken);
|
||||
|
||||
Reference in New Issue
Block a user