fix(template-engine): resolve TemplateEngine-001/003/004/005, re-triage 002 — recursive composed flattening, fixed-field guard, alarm script refs, dead collision query
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
| Last reviewed | 2026-05-16 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `9c60592` |
|
||||
| Open findings | 14 |
|
||||
| Open findings | 10 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -52,7 +52,7 @@ of attributes vs. alarms vs. scripts throughout the resolve/flatten/derive paths
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs:211`, `src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs:535`, `src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs:609` |
|
||||
|
||||
**Description**
|
||||
@@ -77,7 +77,13 @@ the recursion already in `TemplateResolver.AddComposedMembers` and
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `<pending>`): replaced the hand-unrolled
|
||||
one/two-level composition loops in `ResolveComposedAttributes`,
|
||||
`ResolveComposedAlarms`, and `ResolveComposedScripts` with single recursive
|
||||
walks (`*Recursive` helpers) carrying the accumulated path prefix and a
|
||||
`visited` set, so composed members at arbitrary nesting depth are resolved.
|
||||
Regression tests: `Flatten_ThreeLevelComposition_AttributesAlarmsScriptsAllResolved`,
|
||||
`Flatten_NestedComposedAlarm_TriggerAttributePrefixed`.
|
||||
|
||||
### TemplateEngine-002 — Derived templates omit all base alarms; composed alarms cannot be overridden per slot
|
||||
|
||||
@@ -110,7 +116,22 @@ already do.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
_Unresolved (re-triaged 2026-05-16)._ Partially mis-stated and out of the
|
||||
current fix scope. Correction to the description: composed/inherited alarms
|
||||
are **not** dropped from the flattened deployment output — `FlatteningService`
|
||||
resolves alarms from the entire inheritance chain (`ResolveInheritedAlarms`
|
||||
walks `templateChain`, which includes the base of a derived template), so an
|
||||
instance of a derived template still receives the base template's alarms. The
|
||||
real, valid gap is narrower: there is no per-slot **alarm override**
|
||||
mechanism. The fix genuinely requires adding `IsInherited` / `LockedInDerived`
|
||||
fields to the `TemplateAlarm` entity, which lives in `ScadaLink.Commons`
|
||||
(a different module). Adding an alarm copy loop to `BuildDerivedTemplate`
|
||||
without those fields would be actively harmful: copied alarm rows on the
|
||||
derived template would shadow the live base alarm with stale data during
|
||||
flattening (`ResolveInheritedAlarms` has no `IsInherited` skip for alarms,
|
||||
unlike attributes/scripts). Resolving this safely is a cross-module change
|
||||
(`Commons` + `TemplateEngine`) and must be scheduled as a coordinated edit;
|
||||
left **Open** pending that.
|
||||
|
||||
### TemplateEngine-003 — `UpdateAttributeAsync` lets a non-locked attribute change its fixed DataType / DataSourceReference
|
||||
|
||||
@@ -118,7 +139,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.TemplateEngine/TemplateService.cs:285` |
|
||||
|
||||
**Description**
|
||||
@@ -147,7 +168,12 @@ apply block.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `<pending>`): removed the `&& existing.IsLocked`
|
||||
guard in `UpdateAttributeAsync` so the fixed-field granularity error is always
|
||||
honoured, and removed the unconditional `existing.DataType` /
|
||||
`existing.DataSourceReference` assignments from the apply block. Regression
|
||||
tests: `UpdateAttribute_UnlockedAttribute_DataTypeChangeRejected`,
|
||||
`UpdateAttribute_UnlockedAttribute_DataSourceReferenceChangeRejected`.
|
||||
|
||||
### TemplateEngine-004 — Alarm on-trigger script references are never resolved (empty placeholder)
|
||||
|
||||
@@ -155,7 +181,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs:695` |
|
||||
|
||||
**Description**
|
||||
@@ -179,7 +205,15 @@ and implement that consistently.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `<pending>`): implemented `ResolveAlarmScriptReferences`.
|
||||
Alarm resolution now records each resolved alarm's `OnTriggerScriptId` keyed by
|
||||
canonical name, and script resolution records each resolved `TemplateScript.Id`
|
||||
keyed by its canonical name (both honour composition path prefixes). Step 7
|
||||
joins the two maps to set `ResolvedAlarm.OnTriggerScriptCanonicalName`, so the
|
||||
revision hash, diff, and `SemanticValidator` on-trigger-script-exists check now
|
||||
all see the reference. Regression tests:
|
||||
`Flatten_AlarmOnTriggerScript_ResolvedToCanonicalName`,
|
||||
`Flatten_ComposedAlarmOnTriggerScript_ResolvedWithPrefix`.
|
||||
|
||||
### TemplateEngine-005 — Collision validation is skipped when creating a child template
|
||||
|
||||
@@ -187,7 +221,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.TemplateEngine/TemplateService.cs:56` |
|
||||
|
||||
**Description**
|
||||
@@ -210,7 +244,15 @@ that explicitly instead of leaving a no-op.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `<pending>`): deleted the dead `if
|
||||
(parentTemplateId.HasValue)` block and its unused `GetAllTemplatesAsync`
|
||||
read in `CreateTemplateAsync`. A create-time collision check on a child is a
|
||||
guaranteed no-op — a freshly created template has no members of its own, the
|
||||
parent's members were already collision-validated on every member-mutating
|
||||
call, and a new child cannot be an ancestor of its parent. Replaced the no-op
|
||||
with an explanatory comment documenting that collision detection is enforced
|
||||
on `AddAttribute`/`AddAlarm`/`AddScript`/`AddComposition` and on rename.
|
||||
Regression test: `CreateTemplate_WithParent_DoesNotRunDeadCollisionQuery`.
|
||||
|
||||
### TemplateEngine-006 — Forbidden-API enforcement is a naive substring scan (bypassable and false-positive prone)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user