From adb5e75ec36e0bb9d29d765dba7ffd8af15b1bff Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 16 May 2026 22:32:30 -0400 Subject: [PATCH] =?UTF-8?q?fix(template-engine):=20resolve=20TemplateEngin?= =?UTF-8?q?e-011,013,014=20=E2=80=94=20remove=20dead=20converter,=20duplic?= =?UTF-8?q?ate-id-safe=20cycle=20detection,=20unified=20deletion=20logic;?= =?UTF-8?q?=20TemplateEngine-012=20deferred?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code-reviews/TemplateEngine/findings.md | 79 ++++++++++++++++--- .../CollisionDetector.cs | 5 +- src/ScadaLink.TemplateEngine/CycleDetector.cs | 46 +++++++---- .../Flattening/RevisionHashService.cs | 32 ++++---- .../TemplateResolver.cs | 5 +- .../TemplateService.cs | 63 +++------------ .../CycleDetectorTests.cs | 73 +++++++++++++++++ .../Flattening/RevisionHashServiceTests.cs | 32 ++++++++ .../TemplateServiceTests.cs | 37 ++++++++- 9 files changed, 274 insertions(+), 98 deletions(-) diff --git a/code-reviews/TemplateEngine/findings.md b/code-reviews/TemplateEngine/findings.md index a0dec5a..f2de185 100644 --- a/code-reviews/TemplateEngine/findings.md +++ b/code-reviews/TemplateEngine/findings.md @@ -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. diff --git a/src/ScadaLink.TemplateEngine/CollisionDetector.cs b/src/ScadaLink.TemplateEngine/CollisionDetector.cs index d6bfb7c..6cf910e 100644 --- a/src/ScadaLink.TemplateEngine/CollisionDetector.cs +++ b/src/ScadaLink.TemplateEngine/CollisionDetector.cs @@ -27,7 +27,10 @@ public static class CollisionDetector Template template, IReadOnlyList