feat(template-engine): resolve TemplateEngine-002 — per-slot alarm override for derived templates

Adds IsInherited/LockedInDerived to the TemplateAlarm entity (mirroring the
attribute/script override model), an EF migration, base-alarm copy-on-derive,
inherited-alarm flattening skip, and LockedInDerived override-rejection validation.
This commit is contained in:
Joseph Doherty
2026-05-16 20:12:24 -04:00
parent bc548e1447
commit 305b42ea6d
9 changed files with 1700 additions and 21 deletions

View File

@@ -398,6 +398,16 @@ public class TemplateService
if (parentMember != null && parentMember.IsLocked)
return Result<TemplateAlarm>.Failure(
$"Alarm '{existing.Name}' is locked in parent and cannot be overridden.");
// Derived templates may not override alarms the base marked LockedInDerived.
if (template.IsDerived)
{
var baseTemplate = await _repository.GetTemplateByIdAsync(template.ParentTemplateId.Value, cancellationToken);
var baseAlarm = baseTemplate?.Alarms.FirstOrDefault(a => a.Name == existing.Name);
if (baseAlarm != null && baseAlarm.LockedInDerived)
return Result<TemplateAlarm>.Failure(
$"Alarm '{existing.Name}' is locked by base template '{baseTemplate!.Name}' and cannot be overridden.");
}
}
// Validate fixed fields
@@ -411,6 +421,10 @@ public class TemplateService
existing.Description = proposed.Description;
existing.OnTriggerScriptId = proposed.OnTriggerScriptId;
existing.IsLocked = proposed.IsLocked;
if (template?.IsDerived == true)
existing.IsInherited = proposed.IsInherited;
else
existing.LockedInDerived = proposed.LockedInDerived;
// Name and TriggerType are NOT updated (fixed)
await _repository.UpdateTemplateAlarmAsync(existing, cancellationToken);
@@ -818,6 +832,21 @@ public class TemplateService
});
}
foreach (var alarm in baseTemplate.Alarms)
{
derived.Alarms.Add(new TemplateAlarm(alarm.Name)
{
Description = alarm.Description,
PriorityLevel = alarm.PriorityLevel,
IsLocked = alarm.IsLocked,
TriggerType = alarm.TriggerType,
TriggerConfiguration = alarm.TriggerConfiguration,
OnTriggerScriptId = alarm.OnTriggerScriptId,
IsInherited = true,
LockedInDerived = false,
});
}
foreach (var script in baseTemplate.Scripts)
{
derived.Scripts.Add(new TemplateScript(script.Name, script.Code)