Phase 2 WP-1–13+23: Template Engine CRUD, composition, overrides, locking, collision detection, acyclicity
- WP-23: ITemplateEngineRepository full EF Core implementation - WP-1: Template CRUD with deletion constraints (instances, children, compositions) - WP-2–4: Attribute, alarm, script definitions with lock flags and override granularity - WP-5: Shared script CRUD with syntax validation - WP-6–7: Composition with recursive nesting and canonical naming - WP-8–11: Override granularity, locking rules, inheritance/composition scope - WP-12: Naming collision detection on canonical names (recursive) - WP-13: Graph acyclicity (inheritance + composition cycles) Core services: TemplateService, SharedScriptService, TemplateResolver, LockEnforcer, CollisionDetector, CycleDetector. 358 tests pass.
This commit is contained in:
109
src/ScadaLink.TemplateEngine/LockEnforcer.cs
Normal file
109
src/ScadaLink.TemplateEngine/LockEnforcer.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using ScadaLink.Commons.Entities.Templates;
|
||||
|
||||
namespace ScadaLink.TemplateEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Enforces locking rules for template member overrides.
|
||||
///
|
||||
/// Locking rules:
|
||||
/// - Locked members cannot be overridden downstream (child templates or compositions).
|
||||
/// - Any level can lock an unlocked member (intermediate locking).
|
||||
/// - Once locked, a member stays locked — it cannot be unlocked downstream.
|
||||
///
|
||||
/// Override granularity:
|
||||
/// - Attributes: Value and Description overridable; DataType and DataSourceReference fixed.
|
||||
/// - Alarms: Priority, TriggerConfiguration, Description, OnTriggerScript overridable; Name and TriggerType fixed.
|
||||
/// - Scripts: Code, TriggerConfiguration, MinTimeBetweenRuns, params/return overridable; Name fixed.
|
||||
/// - Lock flag applies to the entire member (attribute/alarm/script).
|
||||
/// </summary>
|
||||
public static class LockEnforcer
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that an attribute override does not violate lock or granularity rules.
|
||||
/// </summary>
|
||||
public static string? ValidateAttributeOverride(
|
||||
TemplateAttribute original,
|
||||
TemplateAttribute proposed)
|
||||
{
|
||||
if (original.IsLocked)
|
||||
{
|
||||
return $"Attribute '{original.Name}' is locked and cannot be overridden.";
|
||||
}
|
||||
|
||||
// DataType is fixed — cannot change
|
||||
if (proposed.DataType != original.DataType)
|
||||
{
|
||||
return $"Attribute '{original.Name}': DataType cannot be overridden (fixed).";
|
||||
}
|
||||
|
||||
// DataSourceReference is fixed — cannot change
|
||||
if (proposed.DataSourceReference != original.DataSourceReference)
|
||||
{
|
||||
return $"Attribute '{original.Name}': DataSourceReference cannot be overridden (fixed).";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that an alarm override does not violate lock or granularity rules.
|
||||
/// </summary>
|
||||
public static string? ValidateAlarmOverride(
|
||||
TemplateAlarm original,
|
||||
TemplateAlarm proposed)
|
||||
{
|
||||
if (original.IsLocked)
|
||||
{
|
||||
return $"Alarm '{original.Name}' is locked and cannot be overridden.";
|
||||
}
|
||||
|
||||
// Name is fixed
|
||||
if (proposed.Name != original.Name)
|
||||
{
|
||||
return $"Alarm '{original.Name}': Name cannot be overridden (fixed).";
|
||||
}
|
||||
|
||||
// TriggerType is fixed
|
||||
if (proposed.TriggerType != original.TriggerType)
|
||||
{
|
||||
return $"Alarm '{original.Name}': TriggerType cannot be overridden (fixed).";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a script override does not violate lock or granularity rules.
|
||||
/// </summary>
|
||||
public static string? ValidateScriptOverride(
|
||||
TemplateScript original,
|
||||
TemplateScript proposed)
|
||||
{
|
||||
if (original.IsLocked)
|
||||
{
|
||||
return $"Script '{original.Name}' is locked and cannot be overridden.";
|
||||
}
|
||||
|
||||
// Name is fixed
|
||||
if (proposed.Name != original.Name)
|
||||
{
|
||||
return $"Script '{original.Name}': Name cannot be overridden (fixed).";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a lock flag change is legal.
|
||||
/// Locking is allowed on unlocked members. Unlocking is never allowed.
|
||||
/// </summary>
|
||||
public static string? ValidateLockChange(bool originalIsLocked, bool proposedIsLocked, string memberName)
|
||||
{
|
||||
if (originalIsLocked && !proposedIsLocked)
|
||||
{
|
||||
return $"Member '{memberName}' is locked and cannot be unlocked.";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user