using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; namespace ZB.MOM.WW.ScadaBridge.TemplateEngine; /// /// 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 — neither /// nor may be cleared after it has /// been set. The same one-way ratchet applies to alarms and scripts. This pins /// the design intent so a base template cannot retroactively re-allow derived /// overrides that were previously blocked (TemplateEngine-022). /// /// 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, ExecutionTimeoutSeconds, params/return overridable; Name fixed. /// - Lock flag applies to the entire member (attribute/alarm/script). /// public static class LockEnforcer { /// /// Validates that an attribute override does not violate lock or granularity rules. /// /// The parent template's attribute definition. /// The child template's proposed override. /// An error message string if a rule is violated; null if the override is permitted. 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; } /// /// Validates that an alarm override does not violate lock or granularity rules. /// /// The parent template's alarm definition. /// The child template's proposed override. /// An error message string if a rule is violated; null if the override is permitted. 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; } /// /// Validates that a script override does not violate lock or granularity rules. /// /// The parent template's script definition. /// The child template's proposed override. /// An error message string if a rule is violated; null if the override is permitted. 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; } /// /// Validates that a lock flag change is legal. /// Locking is allowed on unlocked members. Unlocking is never allowed. /// /// The current lock state of the member. /// The proposed lock state. /// Name of the member being changed, for error messages. /// An error message if unlocking is attempted; null if the lock change is valid. public static string? ValidateLockChange(bool originalIsLocked, bool proposedIsLocked, string memberName) { if (originalIsLocked && !proposedIsLocked) { return $"Member '{memberName}' is locked and cannot be unlocked."; } return null; } /// /// Validates that a (or alarm/script) /// flag change is legal. LockedInDerived follows the same one-way ratchet /// as IsLocked — once set on a base template, it cannot be cleared, /// otherwise derived templates that were previously blocked from overriding the /// field would become retroactively allowed (TemplateEngine-022). /// /// Current LockedInDerived state. /// Proposed LockedInDerived state. /// Name of the member being changed, for error messages. /// An error message if the locked-in-derived flag is being cleared; null if the change is valid. public static string? ValidateLockedInDerivedChange( bool originalLockedInDerived, bool proposedLockedInDerived, string memberName) { if (originalLockedInDerived && !proposedLockedInDerived) { return $"Member '{memberName}' is locked-in-derived and that lock cannot be cleared."; } return null; } }