diff --git a/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs b/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs index e018283..edc32ed 100644 --- a/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs +++ b/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs @@ -54,6 +54,17 @@ public class FlatteningService try { + // Step 0: Validate LockedInDerived isn't violated by any chain. + var lockError = ValidateLockedInDerived(templateChain); + if (lockError != null) + return Result.Failure(lockError); + foreach (var composedChain in composedTemplateChains.Values) + { + lockError = ValidateLockedInDerived(composedChain); + if (lockError != null) + return Result.Failure(lockError); + } + // Step 1: Resolve attributes from inheritance chain (most-derived-first wins for same name) var attributes = ResolveInheritedAttributes(templateChain); @@ -124,7 +135,10 @@ public class FlatteningService { var result = new Dictionary(StringComparer.Ordinal); - // Walk from base (last) to most-derived (first) so derived values win + // Walk from base (last) to most-derived (first) so derived values win. + // IsInherited rows on a derived template are placeholders that should + // not shadow the live base value; they only contribute a row when the + // base lacks one. for (int i = templateChain.Count - 1; i >= 0; i--) { var template = templateChain[i]; @@ -132,9 +146,13 @@ public class FlatteningService foreach (var attr in template.Attributes) { - // If a parent defined this attribute as locked, derived cannot change the value - if (result.TryGetValue(attr.Name, out var existing) && existing.IsLocked) - continue; + if (result.TryGetValue(attr.Name, out var existing)) + { + if (existing.IsLocked) + continue; + if (attr.IsInherited) + continue; + } result[attr.Name] = new ResolvedAttribute { @@ -152,6 +170,42 @@ public class FlatteningService return result; } + /// + /// Reports any LockedInDerived violations across the chain — i.e., a base + /// attribute/script marked LockedInDerived that a downstream derived + /// template overrides (IsInherited=false). Returns null on success or an + /// error message describing the first offending entries. + /// + private static string? ValidateLockedInDerived(IReadOnlyList