fix(template-engine): resolve TemplateEngine-011,013,014 — remove dead converter, duplicate-id-safe cycle detection, unified deletion logic; TemplateEngine-012 deferred
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
| Last reviewed | 2026-05-16 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `9c60592` |
|
||||
| Open findings | 4 |
|
||||
| Open findings | 0 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -474,7 +474,7 @@ behaviour change; no regression test (documentation-only).
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.TemplateEngine/Flattening/RevisionHashService.cs:136` |
|
||||
|
||||
**Description**
|
||||
@@ -497,7 +497,18 @@ ordering of the `Hashable*` records (ideally enforced by a test).
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `pending commit`): deleted the dead
|
||||
`SortedPropertiesConverterFactory` (and removed it from `CanonicalJsonOptions`),
|
||||
and replaced the misleading "sorted keys / consistent ordering" comments with an
|
||||
explicit DETERMINISM CONTRACT note on the `RevisionHashService` class summary —
|
||||
`System.Text.Json` serializes properties in CLR declaration order and does not
|
||||
sort, so stable hashes rely on the private `Hashable*` records declaring their
|
||||
properties alphabetically (collections are explicitly sorted by `CanonicalName`).
|
||||
That manual ordering is now guarded by a regression test:
|
||||
`RevisionHashServiceTests.HashableRecords_PropertiesDeclaredAlphabetically`
|
||||
(reflects over the nested `Hashable*` types and asserts ordinal-alphabetical
|
||||
property declaration order), so adding a property out of order now fails the build's
|
||||
test gate instead of silently changing every revision hash.
|
||||
|
||||
### TemplateEngine-012 — `DataType` enum naming diverges from the design doc
|
||||
|
||||
@@ -505,7 +516,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Open |
|
||||
| Status | Deferred |
|
||||
| Location | `src/ScadaLink.TemplateEngine/Validation/SemanticValidator.cs:18` |
|
||||
|
||||
**Description**
|
||||
@@ -523,9 +534,30 @@ are numeric.
|
||||
Update `docs/requirements/Component-TemplateEngine.md` to list the actual enum members,
|
||||
or rename the enum to match the doc if "Integer" is the intended canonical name.
|
||||
|
||||
**Re-triage**
|
||||
|
||||
Verified against the source: the `DataType` enum is declared in
|
||||
`src/ScadaLink.Commons/Types/Enums/DataType.cs` (`Boolean, Int32, Float, Double,
|
||||
String, DateTime, Binary`) — **not** in the TemplateEngine module — and is consumed
|
||||
across modules (`TemplateAttribute` entity, management command contracts). The only
|
||||
in-module file the finding cites, `SemanticValidator.cs:18`, is confirmed **correct**:
|
||||
`NumericDataTypes` already hard-codes the real enum names. Both remediation options
|
||||
in the recommendation therefore land **outside** this module's resolution boundary
|
||||
(`src/ScadaLink.TemplateEngine/**`): renaming the enum touches `ScadaLink.Commons`
|
||||
(and every consumer of `DataType`), and the alternative — updating the design doc —
|
||||
touches `docs/requirements/Component-TemplateEngine.md`. There is no in-module code
|
||||
defect to fix. Re-triaged from Open to Deferred: the fix is a one-line design-doc
|
||||
correction (list the actual seven enum members instead of "Boolean, Integer, Float,
|
||||
String") that must be made by an agent owning the docs / Commons scope.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Deferred 2026-05-16 (no commit): no in-module fix possible — see Re-triage. The
|
||||
TemplateEngine code is correct as-is. FLAGGED for the docs owner: correct the
|
||||
Attribute data-type list in `docs/requirements/Component-TemplateEngine.md` to match
|
||||
`ScadaLink.Commons` `DataType` (`Boolean, Int32, Float, Double, String, DateTime,
|
||||
Binary`). Renaming the enum is not recommended (cross-module churn for no behavioural
|
||||
gain); the doc is the authoritative thing to fix.
|
||||
|
||||
### TemplateEngine-013 — `ToDictionary(t => t.Id)` throws on duplicate IDs; cycle detectors overload Id 0 as a sentinel
|
||||
|
||||
@@ -533,7 +565,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.TemplateEngine/CycleDetector.cs:30`, `src/ScadaLink.TemplateEngine/CycleDetector.cs:38` |
|
||||
|
||||
**Description**
|
||||
@@ -556,7 +588,23 @@ special.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `pending commit`): added `CycleDetector.BuildLookup`,
|
||||
a duplicate-tolerant Id-keyed lookup (`Dictionary` + `TryAdd`, first occurrence wins)
|
||||
that replaces every `allTemplates.ToDictionary(t => t.Id)` in the static helpers —
|
||||
`CycleDetector` (all three methods), `TemplateResolver.ResolveAllMembers`, and
|
||||
`CollisionDetector.DetectCollisions` — so a list containing two templates with the
|
||||
same `Id` (e.g. not-yet-saved templates carrying `Id 0`) no longer throws an
|
||||
unhandled `ArgumentException`. Separately, the `0`-as-"no-parent" sentinel was
|
||||
removed: `DetectInheritanceCycle` now walks the parent chain via the `int?`
|
||||
`ParentTemplateId` (`HasValue` gates continuation), and `DetectCrossGraphCycle`
|
||||
gates the proposed parent/composed edges on `HasValue` rather than `!= 0`, so a
|
||||
template with a real `Id` of 0 is treated like any other node and cycles through it
|
||||
are detected. Regression tests:
|
||||
`CycleDetectorTests.DetectInheritanceCycle_DuplicateIdsInList_DoesNotThrow`,
|
||||
`DetectCompositionCycle_DuplicateIdsInList_DoesNotThrow`,
|
||||
`DetectCrossGraphCycle_DuplicateIdsInList_DoesNotThrow`,
|
||||
`DetectInheritanceCycle_RealIdZero_StillDetectsCycle`,
|
||||
`DetectInheritanceCycle_ParentChainThroughIdZero_DetectsCycle`.
|
||||
|
||||
### TemplateEngine-014 — Template-deletion constraint logic is duplicated and divergent
|
||||
|
||||
@@ -564,7 +612,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.TemplateEngine/TemplateService.cs:109`, `src/ScadaLink.TemplateEngine/Services/TemplateDeletionService.cs:27` |
|
||||
|
||||
**Description**
|
||||
@@ -586,4 +634,17 @@ vice versa) so the constraint logic lives in exactly one place.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `pending commit`): `TemplateService.DeleteTemplateAsync`
|
||||
no longer re-implements the deletion-constraint rules — it now delegates the
|
||||
constraint check and the delete to `TemplateDeletionService.DeleteTemplateAsync`
|
||||
(the surviving single implementation, which already accumulates every blocking
|
||||
reason rather than returning on the first failing category). `TemplateService`
|
||||
retains only its audit-logging side effect: after a successful delete it writes the
|
||||
`Delete` audit entry and calls `SaveChangesAsync` (the deletion service is unaware of
|
||||
auditing and persists the delete itself, so the audit entry needs its own save).
|
||||
The constraint logic now lives in exactly one place, so a future rule change cannot
|
||||
drift between two implementations. Behavioural change: `DeleteTemplateAsync` now
|
||||
reports all blocking reasons and uses `TemplateDeletionService`'s phrasing — the
|
||||
affected `TemplateServiceTests` delete tests were updated to the unified messages,
|
||||
and a regression test `DeleteTemplate_MultipleConstraints_ReportsAllNotJustFirst`
|
||||
verifies all three constraint categories are surfaced together.
|
||||
|
||||
Reference in New Issue
Block a user