From 4dd3880f1767a0af305aa80ed7bfa6d570c43486 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:01:44 +0000 Subject: [PATCH 001/112] FAT P5.4: wire semantic structured reports into live runtime --- .../NativeIec61850Client.HybridReporting.cs | 3 + .../NativeIec61850Client.SemanticReporting.cs | 79 ++++++++ Services/NativeIec61850Client.cs | 3 +- engines/ARIEC61850.lock.json | 6 +- ...icReportProductionWiringRegressionTests.cs | 181 ++++++++++++++++++ 5 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 Services/NativeIec61850Client.SemanticReporting.cs create mode 100644 tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs diff --git a/Services/NativeIec61850Client.HybridReporting.cs b/Services/NativeIec61850Client.HybridReporting.cs index 99b3b0ba6..bcf9a5de5 100644 --- a/Services/NativeIec61850Client.HybridReporting.cs +++ b/Services/NativeIec61850Client.HybridReporting.cs @@ -46,6 +46,7 @@ public async Task BuildHybridReportPlansAsync( cancellationToken.ThrowIfCancellationRequested(); _authoritativeHybridSubscriptions.Clear(); + ResetSemanticReportProjectionContext(); var planningModel = ResolveHybridPlanningModel(device); if (planningModel is null) @@ -78,6 +79,8 @@ public async Task BuildHybridReportPlansAsync( }; } + SetSemanticReportProjectionAuthority(planningModel); + // A fast SCL connect deliberately skips full live-model discovery. The opened SCL // design model is still an ARIEC-typed model and is therefore valid as the catalog // authority for literal signal mapping. It is NOT treated as live RCB evidence: diff --git a/Services/NativeIec61850Client.SemanticReporting.cs b/Services/NativeIec61850Client.SemanticReporting.cs new file mode 100644 index 000000000..d0aef445e --- /dev/null +++ b/Services/NativeIec61850Client.SemanticReporting.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using AR.Iec61850.Discovery; +using ArMms = AR.Iec61850.Mms; + +namespace ArIED61850Tester.Services; + +/// +/// P5.4 report-value projection bridge. Report protocol/member identity stays engine-owned; +/// this layer only supplies the already-authoritative per-IED model so structured static +/// DataSet members can fan out to proven scalar descendants before ARSAS updates live points. +/// +public sealed partial class NativeIec61850Client +{ + private LiveIedModelDiscoveryDocument? _semanticReportProjectionAuthorityModel; + private LiveIedModelDiscoveryDocument? _semanticReportProjectionContextModel; + private ArMms.MmsReportSemanticProjectionContext? _semanticReportProjectionContext; + + private void SetSemanticReportProjectionAuthority(LiveIedModelDiscoveryDocument model) + { + ArgumentNullException.ThrowIfNull(model); + if (ReferenceEquals(_semanticReportProjectionAuthorityModel, model)) + return; + + _semanticReportProjectionAuthorityModel = model; + _semanticReportProjectionContextModel = null; + _semanticReportProjectionContext = null; + } + + private void ResetSemanticReportProjectionContext() + { + _semanticReportProjectionAuthorityModel = null; + _semanticReportProjectionContextModel = null; + _semanticReportProjectionContext = null; + } + + private ArMms.MmsReportValueProjection ProjectReportValue(ArMms.MmsReportFrame report) + { + ArgumentNullException.ThrowIfNull(report); + + // Hybrid FAT uses the exact planning authority (live model or opened SCL design). + // Generic report callers use the fresh live model built by reporting discovery. + // No global/ambient semantic state is used, so model evidence cannot cross IEDs. + var model = _semanticReportProjectionAuthorityModel ?? _liveModel; + if (model is null) + return ArMms.MmsReportValueProjector.Project(report); + + try + { + if (_semanticReportProjectionContext is null || + !ReferenceEquals(_semanticReportProjectionContextModel, model)) + { + _semanticReportProjectionContext = ArMms.MmsReportSemanticProjectionContext.Create(model); + _semanticReportProjectionContextModel = model; + } + + return ArMms.MmsSemanticReportValueProjector.Project( + report, + _semanticReportProjectionContext); + } + catch (Exception ex) + { + // Projection must never invent evidence or tear down a healthy MMS association. + // Preserve the established raw update and expose an explicit fail-closed warning. + var baseline = ArMms.MmsReportValueProjector.Project(report); + return new ArMms.MmsReportValueProjection + { + Updates = baseline.Updates, + Warnings = baseline.Warnings + .Concat(new[] + { + $"REPORT_SEMANTIC_FALLBACK: model-backed projection failed closed: {ex.GetType().Name}: {ex.Message}" + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray() + }; + } + } +} diff --git a/Services/NativeIec61850Client.cs b/Services/NativeIec61850Client.cs index 507f2d34e..eacf54cfd 100644 --- a/Services/NativeIec61850Client.cs +++ b/Services/NativeIec61850Client.cs @@ -64,6 +64,7 @@ public async Task ConnectAsync(string ipAddress, int port, CancellationToken can _liveModel = null; _reportMonitorSessions.Clear(); _reportMonitorCoverage.Clear(); + ResetSemanticReportProjectionContext(); Interlocked.Exchange(ref _engineCompatibilityWarningIssued, 0); DetectedIdentity = new Iec61850DeviceIdentity(); _host = ipAddress?.Trim() ?? string.Empty; @@ -490,7 +491,7 @@ public async Task ReceiveReportMonitorSliceAsync ReceivedAt = report.ReceivedAt }); - var projection = ArMms.MmsReportValueProjector.Project(report); + var projection = ProjectReportValue(report); warnings.AddRange(projection.Warnings); updates.AddRange(projection.Updates.Select(update => new NativeReportValueUpdate { diff --git a/engines/ARIEC61850.lock.json b/engines/ARIEC61850.lock.json index 6fc0f53c2..1a671d05e 100644 --- a/engines/ARIEC61850.lock.json +++ b/engines/ARIEC61850.lock.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "repository": "masarray/ARIEC61850", "ref": "main", - "commit": "bd959bc68719c52853cede272c1f562aec9900e2", - "sourcePullRequest": 103, - "purpose": "Pins the exact ARIEC61850 engine used by ARSAS while preserving the reviewed reporting/control ancestry. PR #76 preserves unresolved static DataSet members; PR #77 canonicalizes cross-logical-device SCL references; PR #78 keeps one descriptor per static DataSet member while separating the resolved runtime primary leaf from original FCDA/FCD identity; PR #79 projects generic Boolean status structures to scalar stVal while preserving quality/timestamp; PR #80 normalizes validated DataRef-enabled InformationReport ordering; PR #81 accepts valid zero OptFlds reports while quarantining unmapped canonical report metadata; PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling; PR #85 evaluates association capabilities before automatic dynamic mutation; PR #86 records dynamic-attempt failure/skip evidence and best-effort rollback. PR #87 restores baseline-safe static precedence. PR #88 adds a fail-closed single-member DefineNamedVariableList -> GetNamedVariableListAttributes -> DeleteNamedVariableList probation with exact invoke/request/response/routing/member/association/cleanup evidence. PR #89 quarantines automatic full dynamic DataSet activation because a successful one-member NVL probation does not guarantee association survival; it also preserves safe instMag/mag and instCVal/cVal projection while ambiguous structures remain raw. PR #90 / field-proven engine a18e550d07f7bbe4ff7753c180b02615075f6292 preserves G1/G1.1 Smart Control: signed primitive constraints, ordered SBO/SBOw-to-Operate wire evidence, StationControl origin compatibility, and explicit MMS Write DataAccessError including object-access-denied. G2 PR #91 adds qualification-only bounded multi-member DefineNamedVariableList/GetNamedVariableListAttributes/DeleteNamedVariableList evidence with exact ordered read-back, encoded request/PDU evidence and fail-closed cleanup; PR #92 adds the 1/4/8/16/32 qualification ladder, deterministic bisection and explicit EnvelopeQualified acceptance; PR #93 adds a default-disabled ExplicitCommissioning coordinator with hard attempt budget, exact-set failure localization and fresh-association stop semantics; PR #94 adds identity-bound qualification profiles and prevents ProductionEligible unless RCB activation, an actual correctly mapped InformationReport, and all G2.6 physical regression gates are proven. G2.4 engine PR #95 retains the commissioning-only transactional URCB TrgOps/OptFlds lease. P0 physically proved the corrected IEC 61850 MMS TrgOps reserved-bit mapping: bit 0 reserved, bits 1..5 dchg/qchg/dupd/integrity/GI, so dchg+GI encodes canonically as 0244; P0 also separates raw BER equality from IEC significant-bit equality and provides a one-URCB TrgOps-only micro-probe that never writes OptFlds, DatSet, Resv, RptEna, GI or any DataSet service. P1 adds a dedicated one-URCB OptFlds-only capture/write/readback/finally-restore micro-probe for reason-for-inclusion + data-set-name, canonical target 061800, using ten-bit significant-value comparison while never writing TrgOps, DatSet, Resv, RptEna, GI, Define/Delete DataSet, starting a report monitor, or changing profile state. The G2.4 Owner correction exposes the exact local TCP address of the active MMS association and fail-closed decodes a server RCB Owner as a 4-byte IPv4 or 16-byte IPv6 address; physical SIPROTEC Owner C0A851F0 decodes to 192.168.81.240 and may prove caller ownership only when it exactly matches the active local TCP endpoint. Owner mismatch or unsupported encoding remains a hard failure. Original RCB values remain captured for restore, raw BER evidence is retained, and Production automatic dynamic BRCB/URCB activation remains quarantined until a compatible ProductionEligible profile is consumed by a later G2 phase. FAT P5.3 engine PR #103 resolves intermediate structured static DataSet members such as MMXU A.phsA and PPV.phsAB only to typed descendants below the exact FCDA boundary, selects a unique semantic primary runtime leaf such as cVal.mag.f without crossing sibling phases, preserves original static membership identity, and leaves genuinely ambiguous structures unresolved rather than guessing." + "commit": "888545bbca1e26040b3f7a86193e2dae013e964f", + "sourcePullRequest": 106, + "purpose": "Pins the exact ARIEC61850 engine used by ARSAS while preserving the reviewed reporting/control ancestry. PR #76 preserves unresolved static DataSet members; PR #77 canonicalizes cross-logical-device SCL references; PR #78 keeps one descriptor per static DataSet member while separating the resolved runtime primary leaf from original FCDA/FCD identity; PR #79 projects generic Boolean status structures to scalar stVal while preserving quality/timestamp; PR #80 normalizes validated DataRef-enabled InformationReport ordering; PR #81 accepts valid zero OptFlds reports while quarantining unmapped canonical report metadata; PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling; PR #85 evaluates association capabilities before automatic dynamic mutation; PR #86 records dynamic-attempt failure/skip evidence and best-effort rollback. PR #87 restores baseline-safe static precedence. PR #88 adds a fail-closed single-member DefineNamedVariableList -> GetNamedVariableListAttributes -> DeleteNamedVariableList probation with exact invoke/request/response/routing/member/association/cleanup evidence. PR #89 quarantines automatic full dynamic DataSet activation because a successful one-member NVL probation does not guarantee association survival; it also preserves safe instMag/mag and instCVal/cVal projection while ambiguous structures remain raw. PR #90 / field-proven engine a18e550d07f7bbe4ff7753c180b02615075f6292 preserves G1/G1.1 Smart Control: signed primitive constraints, ordered SBO/SBOw-to-Operate wire evidence, StationControl origin compatibility, and explicit MMS Write DataAccessError including object-access-denied. G2 PR #91 adds qualification-only bounded multi-member DefineNamedVariableList/GetNamedVariableListAttributes/DeleteNamedVariableList evidence with exact ordered read-back, encoded request/PDU evidence and fail-closed cleanup; PR #92 adds the 1/4/8/16/32 qualification ladder, deterministic bisection and explicit EnvelopeQualified acceptance; PR #93 adds a default-disabled ExplicitCommissioning coordinator with hard attempt budget, exact-set failure localization and fresh-association stop semantics; PR #94 adds identity-bound qualification profiles and prevents ProductionEligible unless RCB activation, an actual correctly mapped InformationReport, and all G2.6 physical regression gates are proven. G2.4 engine PR #95 retains the commissioning-only transactional URCB TrgOps/OptFlds lease. P0 physically proved the corrected IEC 61850 MMS TrgOps reserved-bit mapping: bit 0 reserved, bits 1..5 dchg/qchg/dupd/integrity/GI, so dchg+GI encodes canonically as 0244; P0 also separates raw BER equality from IEC significant-bit equality and provides a one-URCB TrgOps-only micro-probe that never writes OptFlds, DatSet, Resv, RptEna, GI or any DataSet service. P1 adds a dedicated one-URCB OptFlds-only capture/write/readback/finally-restore micro-probe for reason-for-inclusion + data-set-name, canonical target 061800, using ten-bit significant-value comparison while never writing TrgOps, DatSet, Resv, RptEna, GI, Define/Delete DataSet, starting a report monitor, or changing profile state. The G2.4 Owner correction exposes the exact local TCP address of the active MMS association and fail-closed decodes a server RCB Owner as a 4-byte IPv4 or 16-byte IPv6 address; physical SIPROTEC Owner C0A851F0 decodes to 192.168.81.240 and may prove caller ownership only when it exactly matches the active local TCP endpoint. Owner mismatch or unsupported encoding remains a hard failure. Original RCB values remain captured for restore, raw BER evidence is retained, and Production automatic dynamic BRCB/URCB activation remains quarantined until a compatible ProductionEligible profile is consumed by a later G2 phase. FAT P5.3 engine PR #103 resolves intermediate structured static DataSet members such as MMXU A.phsA and PPV.phsAB only to typed descendants below the exact FCDA boundary, selects a unique semantic primary runtime leaf such as cVal.mag.f without crossing sibling phases, preserves original static membership identity, and leaves genuinely ambiguous structures unresolved rather than guessing. FAT P5.4 engine PR #106 adds fail-closed model-backed InformationReport projection for structured static DataSet members: the exact static member/index remains protocol identity, all schema-proven scalar descendants are fanned out without selecting a sibling phase as primary, and schema mismatch preserves raw projection instead of guessing. ARSAS supplies the per-IED LiveDiscovery/SCL planning model at the report receive seam." } diff --git a/tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs b/tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs new file mode 100644 index 000000000..6d2991d38 --- /dev/null +++ b/tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs @@ -0,0 +1,181 @@ +using AR.Iec61850.Discovery; +using AR.Iec61850.Mms; + +namespace ARSAS.Tests; + +public sealed class SemanticReportProductionWiringRegressionTests +{ + [Fact] + public void ProductionReceivePath_UsesModelBackedSemanticOverlay() + { + var native = File.ReadAllText(FindRepoFile("Services/NativeIec61850Client.cs")) + .Replace("\r\n", "\n", StringComparison.Ordinal); + var helper = File.ReadAllText(FindRepoFile("Services/NativeIec61850Client.SemanticReporting.cs")) + .Replace("\r\n", "\n", StringComparison.Ordinal); + var hybrid = File.ReadAllText(FindRepoFile("Services/NativeIec61850Client.HybridReporting.cs")) + .Replace("\r\n", "\n", StringComparison.Ordinal); + + var receiveStart = native.IndexOf( + "public async Task ReceiveReportMonitorSliceAsync", + StringComparison.Ordinal); + var receiveEnd = native.IndexOf( + "public async Task> StopReportMonitorsAsync", + receiveStart, + StringComparison.Ordinal); + Assert.True(receiveStart >= 0 && receiveEnd > receiveStart); + var receive = native[receiveStart..receiveEnd]; + + Assert.Contains("var projection = ProjectReportValue(report);", receive, StringComparison.Ordinal); + Assert.DoesNotContain("MmsReportValueProjector.Project(report)", receive, StringComparison.Ordinal); + Assert.Contains("MmsSemanticReportValueProjector.Project", helper, StringComparison.Ordinal); + Assert.Contains("MmsReportSemanticProjectionContext.Create", helper, StringComparison.Ordinal); + Assert.Contains("_semanticReportProjectionAuthorityModel ?? _liveModel", helper, StringComparison.Ordinal); + Assert.Contains("REPORT_SEMANTIC_FALLBACK", helper, StringComparison.Ordinal); + Assert.Contains("SetSemanticReportProjectionAuthority(planningModel);", hybrid, StringComparison.Ordinal); + Assert.Contains("ResetSemanticReportProjectionContext();", native, StringComparison.Ordinal); + } + + [Fact] + public void PinnedEngine_FansOutWholeThreePhaseStructuredMember_WithoutChoosingPrimaryPhase() + { + const string objectReference = "IEDLD0/MHAI1.ThdA"; + const string dataSetReference = "IEDLD0/LLN0.Analog"; + var attributes = new[] + { + Attribute(objectReference + ".phsA.cVal.mag.f", "phsA.cVal.mag.f"), + Attribute(objectReference + ".phsB.cVal.mag.f", "phsB.cVal.mag.f"), + Attribute(objectReference + ".phsC.cVal.mag.f", "phsC.cVal.mag.f") + }; + var model = new LiveIedModelDiscoveryDocument + { + Source = "ARSAS regression", + IedName = "IED", + LogicalDevices = + [ + new LiveIedLogicalDeviceModel + { + MmsDomain = "IEDLD0", + LogicalNodes = + [ + new LiveIedLogicalNodeModel + { + Name = "MHAI1", + LnClass = "MHAI", + LnInst = "1", + DataObjects = + [ + new LiveIedDataObjectModel + { + Reference = objectReference, + Name = "ThdA", + InferredCdc = "WYE", + Attributes = attributes + } + ] + } + ] + } + ], + DataSets = + [ + new LiveIedDataSetModel + { + Reference = dataSetReference, + Domain = "IEDLD0", + LogicalNode = "LLN0", + Name = "Analog", + MemberCount = 1, + Members = + [ + new LiveIedDataSetMemberModel + { + Index = 0, + Reference = objectReference, + FunctionalConstraint = "MX", + MmsReference = "IEDLD0/MHAI1$MX$ThdA", + Confidence = LiveIedDiscoveryConfidenceLevel.Exact + } + ] + } + ] + }; + + var binding = Assert.Single(Iec61850DataSetSemanticBindingResolver.Resolve(model).Members); + Assert.Equal(LiveIedDataSetMemberResolutionStatus.Ambiguous, binding.ResolutionStatus); + Assert.Null(binding.PrimaryValue); + + var frame = new MmsReportFrame + { + ReceivedAt = new DateTimeOffset(2026, 9, 1, 9, 0, 0, TimeSpan.Zero), + Header = new MmsReportHeader { DataSetReference = dataSetReference }, + Values = + [ + new MmsReportValue + { + Index = 0, + Member = new MmsDataSetDirectoryMember + { + UserReference = objectReference, + FunctionalConstraint = "MX" + }, + Value = MmsDataValue.Structure([Phase(1.25), Phase(2.5), Phase(3.75)]), + ReasonForInclusion = ["data-change"] + } + ] + }; + + var projection = MmsSemanticReportValueProjector.Project( + frame, + MmsReportSemanticProjectionContext.Create(model)); + + Assert.Contains(projection.Updates, update => + update.Reference.Equals(objectReference + ".phsA.cVal.mag.f", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(projection.Updates, update => + update.Reference.Equals(objectReference + ".phsB.cVal.mag.f", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(projection.Updates, update => + update.Reference.Equals(objectReference + ".phsC.cVal.mag.f", StringComparison.OrdinalIgnoreCase)); + Assert.All(projection.Updates, update => + Assert.Equal("semantic-structured-leaf", update.ProjectionStatus)); + Assert.DoesNotContain(projection.Updates, update => + update.Reference.Equals(objectReference, StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(projection.Warnings, warning => + warning.StartsWith("REPORT_RAW_STRUCT:", StringComparison.OrdinalIgnoreCase)); + } + + private static LiveIedDataAttributeModel Attribute(string reference, string path) + => new() + { + ObjectReference = reference, + AttributePath = path, + FunctionalConstraint = "MX", + MmsReference = reference.Replace('.', '$'), + SclBType = "FLOAT32", + MmsType = "floating-point", + Source = "ARSAS regression", + TypeSource = "ARSAS regression", + TypeConfidence = LiveIedDiscoveryConfidenceLevel.Exact + }; + + private static MmsDataValue Phase(double value) + => MmsDataValue.Structure( + [ + MmsDataValue.Structure( + [ + MmsDataValue.Structure([MmsDataValue.FloatingPoint(value)]) + ]) + ]); + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException(relativePath); + } +} From 6cb29fee0e352da38e68ea63aa7a16107c96356d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 09:34:49 +0700 Subject: [PATCH 002/112] Pin sparse structured report semantic matching fix --- engines/ARIEC61850.lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engines/ARIEC61850.lock.json b/engines/ARIEC61850.lock.json index 1a671d05e..2e101141b 100644 --- a/engines/ARIEC61850.lock.json +++ b/engines/ARIEC61850.lock.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "repository": "masarray/ARIEC61850", "ref": "main", - "commit": "888545bbca1e26040b3f7a86193e2dae013e964f", + "commit": "b9ee5fc9650b72e69bc81287a1e1b047ad19b054", "sourcePullRequest": 106, - "purpose": "Pins the exact ARIEC61850 engine used by ARSAS while preserving the reviewed reporting/control ancestry. PR #76 preserves unresolved static DataSet members; PR #77 canonicalizes cross-logical-device SCL references; PR #78 keeps one descriptor per static DataSet member while separating the resolved runtime primary leaf from original FCDA/FCD identity; PR #79 projects generic Boolean status structures to scalar stVal while preserving quality/timestamp; PR #80 normalizes validated DataRef-enabled InformationReport ordering; PR #81 accepts valid zero OptFlds reports while quarantining unmapped canonical report metadata; PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling; PR #85 evaluates association capabilities before automatic dynamic mutation; PR #86 records dynamic-attempt failure/skip evidence and best-effort rollback. PR #87 restores baseline-safe static precedence. PR #88 adds a fail-closed single-member DefineNamedVariableList -> GetNamedVariableListAttributes -> DeleteNamedVariableList probation with exact invoke/request/response/routing/member/association/cleanup evidence. PR #89 quarantines automatic full dynamic DataSet activation because a successful one-member NVL probation does not guarantee association survival; it also preserves safe instMag/mag and instCVal/cVal projection while ambiguous structures remain raw. PR #90 / field-proven engine a18e550d07f7bbe4ff7753c180b02615075f6292 preserves G1/G1.1 Smart Control: signed primitive constraints, ordered SBO/SBOw-to-Operate wire evidence, StationControl origin compatibility, and explicit MMS Write DataAccessError including object-access-denied. G2 PR #91 adds qualification-only bounded multi-member DefineNamedVariableList/GetNamedVariableListAttributes/DeleteNamedVariableList evidence with exact ordered read-back, encoded request/PDU evidence and fail-closed cleanup; PR #92 adds the 1/4/8/16/32 qualification ladder, deterministic bisection and explicit EnvelopeQualified acceptance; PR #93 adds a default-disabled ExplicitCommissioning coordinator with hard attempt budget, exact-set failure localization and fresh-association stop semantics; PR #94 adds identity-bound qualification profiles and prevents ProductionEligible unless RCB activation, an actual correctly mapped InformationReport, and all G2.6 physical regression gates are proven. G2.4 engine PR #95 retains the commissioning-only transactional URCB TrgOps/OptFlds lease. P0 physically proved the corrected IEC 61850 MMS TrgOps reserved-bit mapping: bit 0 reserved, bits 1..5 dchg/qchg/dupd/integrity/GI, so dchg+GI encodes canonically as 0244; P0 also separates raw BER equality from IEC significant-bit equality and provides a one-URCB TrgOps-only micro-probe that never writes OptFlds, DatSet, Resv, RptEna, GI or any DataSet service. P1 adds a dedicated one-URCB OptFlds-only capture/write/readback/finally-restore micro-probe for reason-for-inclusion + data-set-name, canonical target 061800, using ten-bit significant-value comparison while never writing TrgOps, DatSet, Resv, RptEna, GI, Define/Delete DataSet, starting a report monitor, or changing profile state. The G2.4 Owner correction exposes the exact local TCP address of the active MMS association and fail-closed decodes a server RCB Owner as a 4-byte IPv4 or 16-byte IPv6 address; physical SIPROTEC Owner C0A851F0 decodes to 192.168.81.240 and may prove caller ownership only when it exactly matches the active local TCP endpoint. Owner mismatch or unsupported encoding remains a hard failure. Original RCB values remain captured for restore, raw BER evidence is retained, and Production automatic dynamic BRCB/URCB activation remains quarantined until a compatible ProductionEligible profile is consumed by a later G2 phase. FAT P5.3 engine PR #103 resolves intermediate structured static DataSet members such as MMXU A.phsA and PPV.phsAB only to typed descendants below the exact FCDA boundary, selects a unique semantic primary runtime leaf such as cVal.mag.f without crossing sibling phases, preserves original static membership identity, and leaves genuinely ambiguous structures unresolved rather than guessing. FAT P5.4 engine PR #106 adds fail-closed model-backed InformationReport projection for structured static DataSet members: the exact static member/index remains protocol identity, all schema-proven scalar descendants are fanned out without selecting a sibling phase as primary, and schema mismatch preserves raw projection instead of guessing. ARSAS supplies the per-IED LiveDiscovery/SCL planning model at the report receive seam." + "purpose": "Pins the exact ARIEC61850 engine used by ARSAS while preserving the reviewed reporting/control ancestry. PR #76 preserves unresolved static DataSet members; PR #77 canonicalizes cross-logical-device SCL references; PR #78 keeps one descriptor per static DataSet member while separating the resolved runtime primary leaf from original FCDA/FCD identity; PR #79 projects generic Boolean status structures to scalar stVal while preserving quality/timestamp; PR #80 normalizes validated DataRef-enabled InformationReport ordering; PR #81 accepts valid zero OptFlds reports while quarantining unmapped canonical report metadata; PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling; PR #85 evaluates association capabilities before automatic dynamic mutation; PR #86 records dynamic-attempt failure/skip evidence and best-effort rollback. PR #87 restores baseline-safe static precedence. PR #88 adds a fail-closed single-member DefineNamedVariableList -> GetNamedVariableListAttributes -> DeleteNamedVariableList probation with exact invoke/request/response/routing/member/association/cleanup evidence. PR #89 quarantines automatic full dynamic DataSet activation because a successful one-member NVL probation does not guarantee association survival; it also preserves safe instMag/mag and instCVal/cVal projection while ambiguous structures remain raw. PR #90 / field-proven engine a18e550d07f7bbe4ff7753c180b02615075f6292 preserves G1/G1.1 Smart Control: signed primitive constraints, ordered SBO/SBOw-to-Operate wire evidence, StationControl origin compatibility, and explicit MMS Write DataAccessError including object-access-denied. G2 PR #91 adds qualification-only bounded multi-member DefineNamedVariableList/GetNamedVariableListAttributes/DeleteNamedVariableList evidence with exact ordered read-back, encoded request/PDU evidence and fail-closed cleanup; PR #92 adds the 1/4/8/16/32 qualification ladder, deterministic bisection and explicit EnvelopeQualified acceptance; PR #93 adds a default-disabled ExplicitCommissioning coordinator with hard attempt budget, exact-set failure localization and fresh-association stop semantics; PR #94 adds identity-bound qualification profiles and prevents ProductionEligible unless RCB activation, an actual correctly mapped InformationReport, and all G2.6 physical regression gates are proven. G2.4 engine PR #95 retains the commissioning-only transactional URCB TrgOps/OptFlds lease. P0 physically proved the corrected IEC 61850 MMS TrgOps reserved-bit mapping: bit 0 reserved, bits 1..5 dchg/qchg/dupd/integrity/GI, so dchg+GI encodes canonically as 0244; P0 also separates raw BER equality from IEC significant-bit equality and provides a one-URCB TrgOps-only micro-probe that never writes OptFlds, DatSet, Resv, RptEna, GI or any DataSet service. P1 adds a dedicated one-URCB OptFlds-only capture/write/readback/finally-restore micro-probe for reason-for-inclusion + data-set-name, canonical target 061800, using ten-bit significant-value comparison while never writing TrgOps, DatSet, Resv, RptEna, GI, Define/Delete DataSet, starting a report monitor, or changing profile state. The G2.4 Owner correction exposes the exact local TCP address of the active MMS association and fail-closed decodes a server RCB Owner as a 4-byte IPv4 or 16-byte IPv6 address; physical SIPROTEC Owner C0A851F0 decodes to 192.168.81.240 and may prove caller ownership only when it exactly matches the active local TCP endpoint. Owner mismatch or unsupported encoding remains a hard failure. Original RCB values remain captured for restore, raw BER evidence is retained, and Production automatic dynamic BRCB/URCB activation remains quarantined until a compatible ProductionEligible profile is consumed by a later G2 phase. FAT P5.3 engine PR #103 resolves intermediate structured static DataSet members such as MMXU A.phsA and PPV.phsAB only to typed descendants below the exact FCDA boundary, selects a unique semantic primary runtime leaf such as cVal.mag.f without crossing sibling phases, preserves original static membership identity, and leaves genuinely ambiguous structures unresolved rather than guessing. FAT P5.4 engine PR #106 adds fail-closed model-backed InformationReport projection for structured static DataSet members: an exact report member reference now resolves independently of sparse decoder-side report value position, while DataSet scope still prevents duplicate static memberships from collapsing; when a report omits the member reference, static DataSet index remains the unique fail-closed fallback. All schema-proven scalar descendants are fanned out without selecting a sibling phase, and schema mismatch preserves raw projection instead of guessing. ARSAS supplies the per-IED LiveDiscovery/SCL planning model at the report receive seam." } From 88674fd6c906aa8feda967e7964a2df56b3a3e92 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 09:35:11 +0700 Subject: [PATCH 003/112] Lock ARSAS regression to sparse structured report identity fix --- .../SemanticReportProductionWiringRegressionTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs b/tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs index 6d2991d38..abebabcbb 100644 --- a/tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs +++ b/tests/ARSAS.Tests/SemanticReportProductionWiringRegressionTests.cs @@ -112,7 +112,9 @@ public void PinnedEngine_FansOutWholeThreePhaseStructuredMember_WithoutChoosingP [ new MmsReportValue { - Index = 0, + // Deliberately differs from the authoritative static DataSet member index (0). + // A sparse InformationReport value position must not defeat an exact IEC member reference. + Index = 17, Member = new MmsDataSetDirectoryMember { UserReference = objectReference, @@ -140,6 +142,8 @@ public void PinnedEngine_FansOutWholeThreePhaseStructuredMember_WithoutChoosingP update.Reference.Equals(objectReference, StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain(projection.Warnings, warning => warning.StartsWith("REPORT_RAW_STRUCT:", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(projection.Warnings, warning => + warning.StartsWith("REPORT_SEMANTIC_FALLBACK:", StringComparison.OrdinalIgnoreCase)); } private static LiveIedDataAttributeModel Attribute(string reference, string path) From c669fe2d4123bdc3004fe625e26bdcd30699909d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 10:27:15 +0700 Subject: [PATCH 004/112] Fix FAT structured sibling binding and expose SCL import --- MainWindow.WorkspaceModeSwitch.cs | 12 ++- .../Iec61850DataSetSignalInventoryService.cs | 32 +++--- ...3StructuredAnalogBindingRegressionTests.cs | 101 ++++++++++++++++-- tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs | 4 + 4 files changed, 129 insertions(+), 20 deletions(-) diff --git a/MainWindow.WorkspaceModeSwitch.cs b/MainWindow.WorkspaceModeSwitch.cs index 425d0fe28..059b1bcd8 100644 --- a/MainWindow.WorkspaceModeSwitch.cs +++ b/MainWindow.WorkspaceModeSwitch.cs @@ -96,7 +96,7 @@ private void InstallWorkspaceModeSwitch() FontSize = 10.5, FontWeight = FontWeights.Bold, Cursor = Cursors.Hand, - ToolTip = "Load another IO List workbook or portable ARSAS project" + ToolTip = "Load SCL/CID, IO List workbook, or portable ARSAS project" }; menuButton.Click += OpenIoFatWorkspaceMenu_Click; modes.Children.Add(menuButton); @@ -189,6 +189,15 @@ private void OpenIoFatWorkspaceMenu(Button anchor) menu.Items.Add(new Separator()); } + var importScl = new MenuItem + { + Header = _loadedIoFatWindow == null + ? "Import SCL / CID files" + : "Import another SCL / CID file set" + }; + importScl.Click += (_, _) => QueueIoFatWorkspaceReplacement( + () => OpenSclFatTesting_Click(this, new RoutedEventArgs())); + var importWorkbook = new MenuItem { Header = _loadedIoFatWindow == null @@ -207,6 +216,7 @@ private void OpenIoFatWorkspaceMenu(Button anchor) openProject.Click += (_, _) => QueueIoFatWorkspaceReplacement( () => OpenIoListPackage_Click(this, new RoutedEventArgs())); + menu.Items.Add(importScl); menu.Items.Add(importWorkbook); menu.Items.Add(openProject); menu.IsOpen = true; diff --git a/Services/Iec61850DataSetSignalInventoryService.cs b/Services/Iec61850DataSetSignalInventoryService.cs index c8b7eff60..95804de04 100644 --- a/Services/Iec61850DataSetSignalInventoryService.cs +++ b/Services/Iec61850DataSetSignalInventoryService.cs @@ -68,8 +68,15 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( foreach (var descriptor in mandatory) { var inventoryReference = InventoryReference(descriptor); - var engineReferences = EngineReferenceCandidates(descriptor).ToArray(); - var current = engineReferences + + // A structured DataSet can expose siblings such as ThdA.phsA/phsB/phsC + // while every descriptor also carries the same parent DesignReference (ThdA). + // Parent/design aliases are therefore NOT identity keys. Using them here can + // silently collapse several physical DataSet members into the first sibling, + // leaving the remaining FAT rows without a unique live signal. Match only the + // exact static membership or the engine-resolved primary scalar leaf. + var identityReferences = EngineIdentityReferenceCandidates(descriptor).ToArray(); + var current = identityReferences .Select(reference => existing.TryGetValue(reference, out var signal) ? signal : null) .FirstOrDefault(signal => signal is not null); @@ -78,7 +85,7 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( if (ApplyEngineDataSetAuthority(current, descriptor, inventoryReference)) enriched++; - foreach (var key in EngineReferenceCandidates(descriptor) + foreach (var key in EngineIdentityReferenceCandidates(descriptor) .Concat(SignalReferenceCandidates(current)) .Append(inventoryReference) .Where(value => !string.IsNullOrWhiteSpace(value))) @@ -98,7 +105,7 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( var signal = CreateSignal(descriptor, reference, inventoryReference); signals.Add(signal); - foreach (var key in EngineReferenceCandidates(descriptor) + foreach (var key in EngineIdentityReferenceCandidates(descriptor) .Concat(SignalReferenceCandidates(signal)) .Append(inventoryReference) .Where(value => !string.IsNullOrWhiteSpace(value))) @@ -243,7 +250,7 @@ private static IEnumerable SignalReferenceCandidates(SignalDefinition si .Select(LiteralReference) .Distinct(StringComparer.OrdinalIgnoreCase); - private static IEnumerable EngineReferenceCandidates(Iec61850SignalDescriptor descriptor) + private static IEnumerable EngineIdentityReferenceCandidates(Iec61850SignalDescriptor descriptor) { var membershipReferences = descriptor.DataSetMemberships .OrderBy(membership => membership.DataSetReference, StringComparer.OrdinalIgnoreCase) @@ -254,19 +261,18 @@ private static IEnumerable EngineReferenceCandidates(Iec61850SignalDescr membership.OriginalMemberReference }); - var descriptorReferences = new[] + // PrimaryValueReference is the resolved scalar identity. PrimaryValueMmsReference + // is its literal MMS-form twin used by live discovery. Do not add DesignReference, + // ObservedReference or other parent aliases here: for structured CDCs they can be + // intentionally shared by several phase members and are evidence, not identity. + var resolvedPrimaryReferences = new[] { descriptor.PrimaryValueReference, - descriptor.DesignReference, - descriptor.ObservedReference, - descriptor.PrimaryValueMmsReference, - descriptor.CanonicalMmsReference, - descriptor.EffectiveMmsReference, - descriptor.ObservedMmsReference + descriptor.PrimaryValueMmsReference }; return membershipReferences - .Concat(descriptorReferences) + .Concat(resolvedPrimaryReferences) .Where(value => !string.IsNullOrWhiteSpace(value)) .Select(LiteralReference) .Distinct(StringComparer.OrdinalIgnoreCase); diff --git a/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs b/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs index 744126ad2..34cdaba6c 100644 --- a/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs @@ -1,6 +1,8 @@ using AR.Iec61850.Discovery; using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; using ArIED61850Tester.Services; +using ArIED61850Tester.Services.IoTesting; namespace ARSAS.Tests; @@ -55,10 +57,99 @@ public void MandatoryInventory_DoesNotCrossStructuredPhaseBoundary() Assert.DoesNotContain("phsBC", signal.ObjectReference, StringComparison.OrdinalIgnoreCase); } + [Theory] + [InlineData("A", "phsA", "phsB", "phsC")] + [InlineData("PPV", "phsAB", "phsBC", "phsCA")] + public void MandatoryInventory_DoesNotCollapseStructuredPhaseSiblingsThroughSharedParentAlias( + string dataObjectName, + string phase1, + string phase2, + string phase3) + { + var dataObject = "IEDLD0/MMXU1." + dataObjectName; + var phases = new[] { phase1, phase2, phase3 }; + var members = phases.Select(phase => dataObject + "." + phase).ToArray(); + var attributes = phases + .SelectMany(phase => new[] + { + Attribute(dataObject + "." + phase + ".cVal.mag.f", phase + ".cVal.mag.f"), + Attribute(dataObject + "." + phase + ".q", phase + ".q", "Quality"), + Attribute(dataObject + "." + phase + ".t", phase + ".t", "Timestamp") + }) + .ToArray(); + var design = BuildDesign(dataObject, members, attributes); + var signals = new List(); + + var result = Iec61850DataSetSignalInventoryService.EnsureMandatorySignals(signals, design); + + Assert.Equal(3, result.MandatoryCatalogCount); + Assert.Equal(3, result.AddedCount); + Assert.Equal(3, signals.Count); + Assert.Equal(3, signals.Select(signal => signal.DisplayReference).Distinct(StringComparer.OrdinalIgnoreCase).Count()); + Assert.Equal(3, signals.Select(signal => signal.ObjectReference).Distinct(StringComparer.OrdinalIgnoreCase).Count()); + + foreach (var phase in phases) + { + var staticMember = dataObject + "." + phase; + var runtimeLeaf = staticMember + ".cVal.mag.f"; + var signal = Assert.Single(signals.Where(candidate => + candidate.DisplayReference.Equals(staticMember, StringComparison.OrdinalIgnoreCase))); + Assert.Equal(runtimeLeaf, signal.ObjectReference); + } + + var ied = new IoTestIedPlan + { + IedName = "IED", + IpAddress = "192.0.2.10", + IedRole = "Regression", + TestPoints = phases.Select(phase => + { + var staticMember = dataObject + "." + phase; + return new IoTestPointPlan + { + TestPointId = "THD-" + phase, + IedName = "IED", + IpAddress = "192.0.2.10", + SignalName = phase, + ObjectReference = staticMember + ".cVal.mag.f", + SourceIecReference = staticMember, + ReportDisplayReference = staticMember, + FunctionalConstraint = "MX", + ExpectedOnText = "Value 1", + ExpectedOffText = "Value 2", + ImportReady = true, + TestEnabled = true + }; + }).ToList() + }; + var device = new Iec61850MonitorDevice + { + Name = "IED", + SclIedName = "IED", + IpAddress = "192.0.2.10", + Port = 102 + }; + device.Signals.AddRange(signals); + + var selection = new IoTestSignalSelectionService().Resolve(ied, device); + + Assert.True(selection.Succeeded, selection.Message); + Assert.Equal(3, selection.Matches.Count); + Assert.Equal(3, selection.Matches.Select(match => match.Signal).Distinct().Count()); + Assert.Empty(selection.MissingPoints); + Assert.Empty(selection.AmbiguousPoints); + } + private static LiveIedModelDiscoveryDocument BuildDesign( string dataObjectReference, string memberReference, params LiveIedDataAttributeModel[] attributes) + => BuildDesign(dataObjectReference, new[] { memberReference }, attributes); + + private static LiveIedModelDiscoveryDocument BuildDesign( + string dataObjectReference, + IReadOnlyList memberReferences, + params LiveIedDataAttributeModel[] attributes) { var slash = dataObjectReference.IndexOf('/'); var domain = dataObjectReference[..slash]; @@ -105,18 +196,16 @@ private static LiveIedModelDiscoveryDocument BuildDesign( Domain = domain, LogicalNode = "LLN0", Name = "Analog", - MemberCount = 1, - Members = new[] - { + MemberCount = memberReferences.Count, + Members = memberReferences.Select((memberReference, index) => new LiveIedDataSetMemberModel { - Index = 0, + Index = index, Reference = memberReference, FunctionalConstraint = "MX", MmsReference = BuildMmsReference(memberReference), Confidence = LiveIedDiscoveryConfidenceLevel.Exact - } - } + }).ToArray() } } }; diff --git a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs index f8cb1dc60..0db37ef11 100644 --- a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs +++ b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs @@ -13,6 +13,9 @@ public void MainWindow_AlwaysExposesEngineeringAndPersistentIoFatWorkspaceModes( Assert.Contains("_loadedIoFatWindow", source, StringComparison.Ordinal); Assert.Contains("ShowLoadedIoFatWorkspace", source, StringComparison.Ordinal); Assert.Contains("Continue loaded FAT project", source, StringComparison.Ordinal); + Assert.Contains("Import SCL / CID files", source, StringComparison.Ordinal); + Assert.Contains("Import another SCL / CID file set", source, StringComparison.Ordinal); + Assert.Contains("OpenSclFatTesting_Click", source, StringComparison.Ordinal); Assert.Contains("Import another IO List Excel workbook", source, StringComparison.Ordinal); Assert.Contains("Open another portable .arsas project", source, StringComparison.Ordinal); Assert.Contains("QueueIoFatWorkspaceReplacement", source, StringComparison.Ordinal); @@ -49,6 +52,7 @@ public void LoadingAnotherFatProject_IsExplicitAndClosesTheLoadedWorkspaceFirst( Assert.Contains("Save and close that workspace before loading another project?", source, StringComparison.Ordinal); Assert.Contains("loaded.Close();", source, StringComparison.Ordinal); Assert.Contains("Dispatcher.BeginInvoke(openReplacement", source, StringComparison.Ordinal); + Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenSclFatTesting_Click", source, StringComparison.Ordinal); Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenIoListTesting_Click", hostSource, StringComparison.Ordinal); Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenIoListPackage_Click", hostSource, StringComparison.Ordinal); } From 7f47304988c0383e40981f7469489b14d21a8fb8 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 10:31:00 +0700 Subject: [PATCH 005/112] Fix multiline SCL menu regression assertion --- tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs index 0db37ef11..9a9cfff10 100644 --- a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs +++ b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs @@ -52,7 +52,7 @@ public void LoadingAnotherFatProject_IsExplicitAndClosesTheLoadedWorkspaceFirst( Assert.Contains("Save and close that workspace before loading another project?", source, StringComparison.Ordinal); Assert.Contains("loaded.Close();", source, StringComparison.Ordinal); Assert.Contains("Dispatcher.BeginInvoke(openReplacement", source, StringComparison.Ordinal); - Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenSclFatTesting_Click", source, StringComparison.Ordinal); + Assert.Contains("OpenSclFatTesting_Click(this, new RoutedEventArgs())", source, StringComparison.Ordinal); Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenIoListTesting_Click", hostSource, StringComparison.Ordinal); Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenIoListPackage_Click", hostSource, StringComparison.Ordinal); } From b77e9d76bc4bb6acfca5cd386a118699e9f6941d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 10:41:12 +0700 Subject: [PATCH 006/112] Restrict FAT DataSet merge to exact IEC identity --- .../Iec61850DataSetSignalInventoryService.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Services/Iec61850DataSetSignalInventoryService.cs b/Services/Iec61850DataSetSignalInventoryService.cs index 95804de04..1ced2b436 100644 --- a/Services/Iec61850DataSetSignalInventoryService.cs +++ b/Services/Iec61850DataSetSignalInventoryService.cs @@ -69,12 +69,10 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( { var inventoryReference = InventoryReference(descriptor); - // A structured DataSet can expose siblings such as ThdA.phsA/phsB/phsC - // while every descriptor also carries the same parent DesignReference (ThdA). - // Parent/design aliases are therefore NOT identity keys. Using them here can - // silently collapse several physical DataSet members into the first sibling, - // leaving the remaining FAT rows without a unique live signal. Match only the - // exact static membership or the engine-resolved primary scalar leaf. + // Structured DataSet members may share parent/report/MMS container aliases. + // Those aliases are evidence, not identity. A FAT inventory merge may reuse an + // existing signal only when the exact static IEC member or exact resolved scalar + // IEC reference already exists. Otherwise create a distinct mandatory signal. var identityReferences = EngineIdentityReferenceCandidates(descriptor).ToArray(); var current = identityReferences .Select(reference => existing.TryGetValue(reference, out var signal) ? signal : null) @@ -261,14 +259,13 @@ private static IEnumerable EngineIdentityReferenceCandidates(Iec61850Sig membership.OriginalMemberReference }); - // PrimaryValueReference is the resolved scalar identity. PrimaryValueMmsReference - // is its literal MMS-form twin used by live discovery. Do not add DesignReference, - // ObservedReference or other parent aliases here: for structured CDCs they can be - // intentionally shared by several phase members and are evidence, not identity. + // Only IEC object-reference identity is safe here. MMS-form references, effective + // references and observed aliases may identify the same structured container for + // several scalar descendants. They must never collapse phase siblings during FAT + // SCL/cache merge. var resolvedPrimaryReferences = new[] { - descriptor.PrimaryValueReference, - descriptor.PrimaryValueMmsReference + descriptor.PrimaryValueReference }; return membershipReferences From 480eb36b41ebb60f8e5f02f48c0ee6dc048ead31 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 10:41:30 +0700 Subject: [PATCH 007/112] Add cached structured MMS alias regression --- .../IoFatStructuredMmsAliasRegressionTests.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatStructuredMmsAliasRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatStructuredMmsAliasRegressionTests.cs b/tests/ARSAS.Tests/IoFatStructuredMmsAliasRegressionTests.cs new file mode 100644 index 000000000..b7e5d8b9e --- /dev/null +++ b/tests/ARSAS.Tests/IoFatStructuredMmsAliasRegressionTests.cs @@ -0,0 +1,116 @@ +using AR.Iec61850.Discovery; +using ArIED61850Tester.Models; +using ArIED61850Tester.Services; + +namespace ARSAS.Tests; + +public sealed class IoFatStructuredMmsAliasRegressionTests +{ + [Fact] + public void CachedStructuredMmsContainerAlias_CannotAbsorbDistinctPhaseMembers() + { + const string dataObject = "IEDLD0/I_MHAI1.ThdA"; + const string sharedStructuredMms = "IEDLD0/I_MHAI1$MX$ThdA"; + var phases = new[] { "phsA", "phsB", "phsC" }; + var members = phases.Select(phase => dataObject + "." + phase).ToArray(); + var attributes = phases.Select(phase => new LiveIedDataAttributeModel + { + ObjectReference = dataObject + "." + phase + ".cVal.mag.f", + AttributePath = phase + ".cVal.mag.f", + FunctionalConstraint = "MX", + // Reproduce the dangerous cache/report shape: several scalar semantic + // descendants can be observed through one structured MMS container alias. + MmsReference = sharedStructuredMms, + MmsItemName = "I_MHAI1$MX$ThdA", + SclBType = "FLOAT32", + MmsType = "floating-point", + Source = "Regression structured MMS container", + TypeSource = "Regression", + TypeConfidence = LiveIedDiscoveryConfidenceLevel.Exact + }).ToArray(); + + var model = new LiveIedModelDiscoveryDocument + { + Source = "SclWorkspace", + IedName = "IED", + LogicalDevices = new[] + { + new LiveIedLogicalDeviceModel + { + MmsDomain = "IEDLD0", + Inst = "LD0", + LogicalNodes = new[] + { + new LiveIedLogicalNodeModel + { + Name = "I_MHAI1", + LnClass = "MHAI", + LnInst = "1", + DataObjects = new[] + { + new LiveIedDataObjectModel + { + Reference = dataObject, + Name = "ThdA", + InferredCdc = "WYE", + Attributes = attributes + } + } + } + } + } + }, + DataSets = new[] + { + new LiveIedDataSetModel + { + Reference = "IEDLD0/LLN0.Analog", + Domain = "IEDLD0", + LogicalNode = "LLN0", + Name = "Analog", + MemberCount = members.Length, + Members = members.Select((member, index) => new LiveIedDataSetMemberModel + { + Index = index, + Reference = member, + FunctionalConstraint = "MX", + MmsReference = "IEDLD0/I_MHAI1$MX$ThdA$" + phases[index], + Confidence = LiveIedDiscoveryConfidenceLevel.Exact + }).ToArray() + } + } + }; + + // Fast reconnect can already contain a cached structured parent/container row. + // It must remain separate from the static phase members imported for FAT. + var signals = new List + { + new() + { + Name = "ThdA", + ObjectReference = sharedStructuredMms, + DisplayReference = sharedStructuredMms, + FunctionalConstraint = "MX", + DataType = "structure", + Source = "Saved model structured container" + } + }; + + var result = Iec61850DataSetSignalInventoryService.EnsureMandatorySignals(signals, model); + + Assert.Equal(3, result.MandatoryCatalogCount); + Assert.Equal(3, result.AddedCount); + Assert.Equal(4, signals.Count); + Assert.Contains(signals, signal => signal.ObjectReference == sharedStructuredMms); + + foreach (var phase in phases) + { + var staticMember = dataObject + "." + phase; + var runtimeLeaf = staticMember + ".cVal.mag.f"; + var signal = Assert.Single(signals.Where(candidate => + candidate.DisplayReference.Equals(staticMember, StringComparison.OrdinalIgnoreCase))); + Assert.Equal(runtimeLeaf, signal.ObjectReference); + Assert.NotEqual(sharedStructuredMms, signal.ObjectReference); + } + } +} From 988c994efbcb14dd4d609a57ab405c1a633a26bb Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 11:16:18 +0700 Subject: [PATCH 008/112] Preserve distinct static DataSet membership rows for FAT --- .../Iec61850DataSetSignalInventoryService.cs | 120 ++++++++---------- 1 file changed, 54 insertions(+), 66 deletions(-) diff --git a/Services/Iec61850DataSetSignalInventoryService.cs b/Services/Iec61850DataSetSignalInventoryService.cs index 1ced2b436..507fec875 100644 --- a/Services/Iec61850DataSetSignalInventoryService.cs +++ b/Services/Iec61850DataSetSignalInventoryService.cs @@ -53,43 +53,30 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( if (mandatory.Count == 0) return EmptyResult(); - // Keep application matching literal. The engine owns IEC 61850 reference - // canonicalization; ARSAS only compares reference forms already exposed by ARIEC. - // DisplayReference is included because static FCD identity is intentionally kept - // separate from a resolved runtime DataAttribute reference such as .stVal. - var existing = signals - .SelectMany(signal => SignalReferenceCandidates(signal).Select(reference => (reference, signal))) - .GroupBy(item => item.reference, StringComparer.OrdinalIgnoreCase) - .ToDictionary(group => group.Key, group => group.First().signal, StringComparer.OrdinalIgnoreCase); - var added = new List(); var enriched = 0; foreach (var descriptor in mandatory) { var inventoryReference = InventoryReference(descriptor); + if (string.IsNullOrWhiteSpace(inventoryReference)) + continue; - // Structured DataSet members may share parent/report/MMS container aliases. - // Those aliases are evidence, not identity. A FAT inventory merge may reuse an - // existing signal only when the exact static IEC member or exact resolved scalar - // IEC reference already exists. Otherwise create a distinct mandatory signal. - var identityReferences = EngineIdentityReferenceCandidates(descriptor).ToArray(); - var current = identityReferences - .Select(reference => existing.TryGetValue(reference, out var signal) ? signal : null) - .FirstOrDefault(signal => signal is not null); - + // The static DataSet membership is the inventory identity. A structured FCDA + // such as ThdA.phsA can resolve to a scalar runtime leaf such as + // ThdA.phsA.cVal.mag.f, but that scalar leaf must never be used to claim the + // membership row. Reusing by runtime-primary identity made diagnostics appear + // complete while several FAT rows had no distinct static identity to bind. + // + // Reuse only a signal that already represents this exact static member in this + // DataSet. A plain scalar/live signal remains untouched and a dedicated DataSet + // projection row is created beside it. Runtime monitoring may still deduplicate + // identical ObjectReference reads later; FAT membership identity is not lost. + var current = FindExistingMembershipSignal(signals, descriptor, inventoryReference); if (current is not null) { if (ApplyEngineDataSetAuthority(current, descriptor, inventoryReference)) enriched++; - - foreach (var key in EngineIdentityReferenceCandidates(descriptor) - .Concat(SignalReferenceCandidates(current)) - .Append(inventoryReference) - .Where(value => !string.IsNullOrWhiteSpace(value))) - { - existing.TryAdd(LiteralReference(key), current); - } continue; } @@ -103,19 +90,50 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( var signal = CreateSignal(descriptor, reference, inventoryReference); signals.Add(signal); - foreach (var key in EngineIdentityReferenceCandidates(descriptor) - .Concat(SignalReferenceCandidates(signal)) - .Append(inventoryReference) - .Where(value => !string.IsNullOrWhiteSpace(value))) - { - existing.TryAdd(LiteralReference(key), signal); - } added.Add(signal); } return new Iec61850DataSetSignalInventoryMergeResult(added, enriched, mandatory.Count); } + private static SignalDefinition? FindExistingMembershipSignal( + IEnumerable signals, + Iec61850SignalDescriptor descriptor, + string inventoryReference) + { + var membership = FirstMembership(descriptor); + var dataSetReference = membership?.DataSetReference ?? string.Empty; + + // Strongest identity: the presentation row already carries both the exact static + // member and the exact DataSet. This is safe even when its runtime ObjectReference + // is shared with another membership. + var exact = signals.FirstOrDefault(signal => + ReferenceEquals(signal.DisplayReference, inventoryReference) && + ReferenceEquals(signal.DataSetReference, dataSetReference)); + if (exact is not null) + return exact; + + // An unclaimed row whose DisplayReference is already the exact static member may + // be promoted into this membership. Never steal a row already owned by a different + // DataSet merely because its runtime leaf happens to match. + var unclaimedDisplay = signals.FirstOrDefault(signal => + ReferenceEquals(signal.DisplayReference, inventoryReference) && + string.IsNullOrWhiteSpace(signal.DataSetReference)); + if (unclaimedDisplay is not null) + return unclaimedDisplay; + + // Legacy/live catalogs sometimes leave DisplayReference empty. Reuse their exact + // ObjectReference only when the static member itself is that same scalar identity. + // This intentionally does NOT compare against descriptor.PrimaryValueReference when + // the static member is a structured/intermediate component. + return signals.FirstOrDefault(signal => + ReferenceEquals(signal.ObjectReference, inventoryReference) && + (string.IsNullOrWhiteSpace(signal.DisplayReference) || + ReferenceEquals(signal.DisplayReference, inventoryReference)) && + (string.IsNullOrWhiteSpace(signal.DataSetReference) || + ReferenceEquals(signal.DataSetReference, dataSetReference))); + } + private static Iec61850DataSetSignalInventoryMergeResult EmptyResult() => new(Array.Empty(), 0, 0); @@ -242,39 +260,6 @@ private static bool ApplyEngineDataSetAuthority( return changed; } - private static IEnumerable SignalReferenceCandidates(SignalDefinition signal) - => new[] { signal.DisplayReference, signal.ObjectReference } - .Where(value => !string.IsNullOrWhiteSpace(value)) - .Select(LiteralReference) - .Distinct(StringComparer.OrdinalIgnoreCase); - - private static IEnumerable EngineIdentityReferenceCandidates(Iec61850SignalDescriptor descriptor) - { - var membershipReferences = descriptor.DataSetMemberships - .OrderBy(membership => membership.DataSetReference, StringComparer.OrdinalIgnoreCase) - .ThenBy(membership => membership.MemberIndex) - .SelectMany(membership => new[] - { - membership.CanonicalMemberReference, - membership.OriginalMemberReference - }); - - // Only IEC object-reference identity is safe here. MMS-form references, effective - // references and observed aliases may identify the same structured container for - // several scalar descendants. They must never collapse phase siblings during FAT - // SCL/cache merge. - var resolvedPrimaryReferences = new[] - { - descriptor.PrimaryValueReference - }; - - return membershipReferences - .Concat(resolvedPrimaryReferences) - .Where(value => !string.IsNullOrWhiteSpace(value)) - .Select(LiteralReference) - .Distinct(StringComparer.OrdinalIgnoreCase); - } - private static string InventoryReference(Iec61850SignalDescriptor descriptor) { var membership = FirstMembership(descriptor); @@ -316,6 +301,9 @@ private static string BuildCoverageReason(Iec61850SignalDescriptor descriptor) " Inventory presence is engine-authoritative; user selection remains independent."; } + private static bool ReferenceEquals(string? left, string? right) + => string.Equals(LiteralReference(left), LiteralReference(right), StringComparison.OrdinalIgnoreCase); + private static string LiteralReference(string? reference) => (reference ?? string.Empty).Trim(); From fab6370f89a1fadb036e53971d5042894f25d614 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 11:16:40 +0700 Subject: [PATCH 009/112] Remove connected reconciliation gate from direct SCL FAT --- Services/IoTesting/IoTestReconciliationCache.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Services/IoTesting/IoTestReconciliationCache.cs b/Services/IoTesting/IoTestReconciliationCache.cs index 7a2724013..34d321e70 100644 --- a/Services/IoTesting/IoTestReconciliationCache.cs +++ b/Services/IoTesting/IoTestReconciliationCache.cs @@ -22,6 +22,13 @@ public static class IoTestReconciliationCache /// already-active association and let ARIEC61850 own exact reads, alternate strategies, /// probe budgets, and failure verdicts. A workspace that has never created a session may /// still build a model-only document; that path can only remain DesignOnly, never Absent. + /// + /// Direct SCL FAT is intentionally different. Its complete static DataSet scope is already + /// authoritative before association, and the monitor/report pipeline will acquire the + /// selected values immediately. Running connected exact-read reconciliation before that + /// acquisition can serialize dozens of MMS probes and hold FAT preparation for more than + /// a minute on real relays. For that workflow reconcile models only; absence remains + /// unproven and live acquisition starts without a network-probe gate. /// public static Task RefreshAsync( Iec61850MonitorDevice device, @@ -29,6 +36,9 @@ public static Task RefreshAsync( { ArgumentNullException.ThrowIfNull(device); + if (UsesDirectSclFatAuthority(device)) + return RefreshModelOnlyAsync(device, cancellationToken); + if (!NativeIec61850Client.HasReconciliationOwner(device.IpAddress, device.Port)) return RefreshModelOnlyAsync(device, cancellationToken); @@ -193,6 +203,10 @@ public static void Invalidate(Iec61850MonitorDevice device) Entries.TryRemove(device, out _); } + private static bool UsesDirectSclFatAuthority(Iec61850MonitorDevice device) + => device.SclWorkspace?.DesignModel is not null && + device.IdentitySource.Contains("FAT SCL", StringComparison.OrdinalIgnoreCase); + private sealed record CacheEntry( object DesignModel, object LiveModel, From 08aba0dbbf207e300db5d9b2b1bf860a735eb4f4 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 11:17:20 +0700 Subject: [PATCH 010/112] Prefer exact static DataSet identity in direct SCL FAT --- .../IoTesting/IoTestSignalSelectionService.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Services/IoTesting/IoTestSignalSelectionService.cs b/Services/IoTesting/IoTestSignalSelectionService.cs index 8e6d24bc8..39d488324 100644 --- a/Services/IoTesting/IoTestSignalSelectionService.cs +++ b/Services/IoTesting/IoTestSignalSelectionService.cs @@ -30,6 +30,7 @@ public sealed record IoTestSignalSelectionResult( public sealed class IoTestSignalSelectionService { internal const string SclDataSetAuthorityBindingStatus = "SCL_DATASET_AUTHORITY"; + private const int SclStaticMembershipIdentityBonus = 1000; private static readonly Regex ProtectionCodeRegex = new( @"\((?\d{2,3}[A-Z]{0,3})(?:\s*-\s*[^)]*)?\)", @@ -170,6 +171,16 @@ private static int BestSignalScore( if (importedReferences.Count == 0) return 0; + // Direct SCL FAT owns stronger identity than a runtime leaf: one row corresponds + // to one exact static DataSet membership. A connected Engineering Workspace can + // legitimately contain both a generic scalar signal and a dedicated static-member + // projection with the same ObjectReference. Without this precedence both score as + // exact runtime matches and the FAT row becomes ambiguous. Prefer exact + // (DataSetReference + DisplayReference) membership identity, then use the resolved + // ObjectReference only as the acquisition target. + if (HasExactSclStaticMembershipIdentity(point, signal)) + return IoTestReferenceMatcher.ExactScore + SclStaticMembershipIdentityBonus; + // ARIEC deliberately keeps static FCDA/FCD membership identity in // DisplayReference while ObjectReference may remain the resolved runtime leaf. // SCL-backed FAT rows therefore compare against both engine-owned identities. @@ -193,6 +204,38 @@ private static int BestSignalScore( .Max(); } + private static bool HasExactSclStaticMembershipIdentity( + IoTestPointPlan point, + SignalDefinition signal) + { + if (!IsSclDataSetAuthority(point) || string.IsNullOrWhiteSpace(signal.DisplayReference)) + return false; + + if (!string.IsNullOrWhiteSpace(point.DataSetName) && + !string.IsNullOrWhiteSpace(signal.DataSetReference) && + !IoTestLiveBindingService.NormalizeReference(point.DataSetName) + .Equals( + IoTestLiveBindingService.NormalizeReference(signal.DataSetReference), + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var display = IoTestLiveBindingService.NormalizeReference(signal.DisplayReference); + if (display.Length == 0) + return false; + + return new[] + { + point.SourceIecReference, + point.ReportDisplayReference, + point.EventLogSearchReference + } + .Where(reference => !string.IsNullOrWhiteSpace(reference)) + .Select(IoTestLiveBindingService.NormalizeReference) + .Any(reference => reference.Equals(display, StringComparison.OrdinalIgnoreCase)); + } + private static List NarrowByProtectionIdentity( IoTestPointPlan point, IReadOnlyCollection candidates) From 32c2ff559367af790e21a2afcbffb6ba87285854 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 11:17:48 +0700 Subject: [PATCH 011/112] Add direct SCL FAT latency and THD identity regressions --- ...ectSclLatencyAndIdentityRegressionTests.cs | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs b/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs new file mode 100644 index 000000000..ec71eba89 --- /dev/null +++ b/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs @@ -0,0 +1,186 @@ +using AR.Iec61850.Discovery; +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services; +using ArIED61850Tester.Services.IoTesting; + +namespace ARSAS.Tests; + +public sealed class IoFatDirectSclLatencyAndIdentityRegressionTests +{ + [Fact] + public void StructuredStaticMember_KeepsDedicatedIdentityBesideCachedScalarSignal() + { + const string dataObject = "IEDLD0/I_MHAI1.ThdA"; + const string staticMember = dataObject + ".phsA"; + const string runtimeLeaf = staticMember + ".cVal.mag.f"; + const string dataSet = "IEDLD0/LLN0.Analog"; + + var model = new LiveIedModelDiscoveryDocument + { + Source = "SclWorkspace", + IedName = "IED", + LogicalDevices = new[] + { + new LiveIedLogicalDeviceModel + { + MmsDomain = "IEDLD0", + Inst = "LD0", + LogicalNodes = new[] + { + new LiveIedLogicalNodeModel + { + Name = "I_MHAI1", + LnClass = "MHAI", + LnInst = "1", + DataObjects = new[] + { + new LiveIedDataObjectModel + { + Reference = dataObject, + Name = "ThdA", + InferredCdc = "WYE", + Attributes = new[] + { + new LiveIedDataAttributeModel + { + ObjectReference = runtimeLeaf, + AttributePath = "phsA.cVal.mag.f", + FunctionalConstraint = "MX", + MmsReference = "IEDLD0/I_MHAI1$MX$ThdA$phsA$cVal$mag$f", + MmsItemName = "I_MHAI1$MX$ThdA$phsA$cVal$mag$f", + SclBType = "FLOAT32", + MmsType = "floating-point", + Source = "SCL.DataTypeTemplates", + TypeSource = "SCL.DataTypeTemplates", + TypeConfidence = LiveIedDiscoveryConfidenceLevel.Exact + } + } + } + } + } + } + } + }, + DataSets = new[] + { + new LiveIedDataSetModel + { + Reference = dataSet, + Domain = "IEDLD0", + LogicalNode = "LLN0", + Name = "Analog", + MemberCount = 1, + Members = new[] + { + new LiveIedDataSetMemberModel + { + Index = 0, + Reference = staticMember, + FunctionalConstraint = "MX", + MmsReference = "IEDLD0/I_MHAI1$MX$ThdA$phsA", + Confidence = LiveIedDiscoveryConfidenceLevel.Exact + } + } + } + } + }; + + var genericScalar = new SignalDefinition + { + Name = "ThdA phsA mag", + ObjectReference = runtimeLeaf, + DisplayReference = runtimeLeaf, + FunctionalConstraint = "MX", + DataType = "FLOAT32", + Category = "Measurement", + Source = "Cached live model" + }; + var signals = new List { genericScalar }; + + var merge = Iec61850DataSetSignalInventoryService.EnsureMandatorySignals(signals, model); + + Assert.Equal(1, merge.MandatoryCatalogCount); + Assert.Equal(1, merge.AddedCount); + Assert.Equal(2, signals.Count); + Assert.Equal(runtimeLeaf, genericScalar.DisplayReference); + + var membershipSignal = Assert.Single(signals.Where(signal => + signal.DisplayReference.Equals(staticMember, StringComparison.OrdinalIgnoreCase))); + Assert.NotSame(genericScalar, membershipSignal); + Assert.Equal(runtimeLeaf, membershipSignal.ObjectReference); + Assert.Equal(dataSet, membershipSignal.DataSetReference); + + var point = new IoTestPointPlan + { + TestPointId = "THD-A", + IedName = "IED", + IpAddress = "192.0.2.10", + SignalName = "ThdA.phsA", + ObjectReference = runtimeLeaf, + FunctionalConstraint = "MX", + ExpectedOnText = "Value 1", + ExpectedOffText = "Value 2", + SourceIecReference = staticMember, + ReportDisplayReference = staticMember, + EventLogSearchReference = staticMember, + DataSetName = dataSet, + BindingStatus = IoTestSignalSelectionService.SclDataSetAuthorityBindingStatus, + ImportReady = true, + TestEnabled = true, + SignalKind = FatSignalKind.Analog, + CaptureMode = FatCaptureMode.OperatorSnapshot + }; + var ied = new IoTestIedPlan + { + IedName = "IED", + IpAddress = "192.0.2.10", + TestPoints = new List { point } + }; + var device = new Iec61850MonitorDevice + { + Name = "IED", + SclIedName = "IED", + IpAddress = "192.0.2.10", + Port = 102 + }; + device.Signals.AddRange(signals); + + var selection = new IoTestSignalSelectionService().Resolve(ied, device); + + Assert.True(selection.Succeeded, selection.Message); + var match = Assert.Single(selection.Matches); + Assert.Same(membershipSignal, match.Signal); + Assert.Empty(selection.MissingPoints); + Assert.Empty(selection.AmbiguousPoints); + } + + [Fact] + public void DirectSclFat_ReconciliationCannotGateAcquisitionWithConnectedExactReads() + { + var source = File.ReadAllText(FindRepoFile("Services/IoTesting/IoTestReconciliationCache.cs")); + var directSclGate = source.IndexOf("if (UsesDirectSclFatAuthority(device))", StringComparison.Ordinal); + var connectedOwnerGate = source.IndexOf("if (!NativeIec61850Client.HasReconciliationOwner", StringComparison.Ordinal); + + Assert.True(directSclGate >= 0, "Direct SCL FAT must have an explicit non-probing reconciliation path."); + Assert.True(connectedOwnerGate > directSclGate, + "Direct SCL FAT must bypass connected exact-read reconciliation before native session ownership is considered."); + Assert.Contains("return RefreshModelOnlyAsync(device, cancellationToken);", source, StringComparison.Ordinal); + Assert.Contains("UsesDirectSclFatAuthority", source, StringComparison.Ordinal); + Assert.Contains("FAT SCL", source, StringComparison.Ordinal); + } + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException($"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } +} From 4c525b667f5f3e2fd07cc9bca3e7a60d31b4e1a4 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 11:19:00 +0700 Subject: [PATCH 012/112] Throttle repeated structured report warnings --- .../NativeIec61850Client.SemanticReporting.cs | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Services/NativeIec61850Client.SemanticReporting.cs b/Services/NativeIec61850Client.SemanticReporting.cs index d0aef445e..15712ab93 100644 --- a/Services/NativeIec61850Client.SemanticReporting.cs +++ b/Services/NativeIec61850Client.SemanticReporting.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using AR.Iec61850.Discovery; using ArMms = AR.Iec61850.Mms; @@ -15,6 +16,7 @@ public sealed partial class NativeIec61850Client private LiveIedModelDiscoveryDocument? _semanticReportProjectionAuthorityModel; private LiveIedModelDiscoveryDocument? _semanticReportProjectionContextModel; private ArMms.MmsReportSemanticProjectionContext? _semanticReportProjectionContext; + private readonly HashSet _semanticReportProjectionWarningsSeen = new(StringComparer.OrdinalIgnoreCase); private void SetSemanticReportProjectionAuthority(LiveIedModelDiscoveryDocument model) { @@ -25,6 +27,7 @@ private void SetSemanticReportProjectionAuthority(LiveIedModelDiscoveryDocument _semanticReportProjectionAuthorityModel = model; _semanticReportProjectionContextModel = null; _semanticReportProjectionContext = null; + _semanticReportProjectionWarningsSeen.Clear(); } private void ResetSemanticReportProjectionContext() @@ -32,6 +35,7 @@ private void ResetSemanticReportProjectionContext() _semanticReportProjectionAuthorityModel = null; _semanticReportProjectionContextModel = null; _semanticReportProjectionContext = null; + _semanticReportProjectionWarningsSeen.Clear(); } private ArMms.MmsReportValueProjection ProjectReportValue(ArMms.MmsReportFrame report) @@ -52,18 +56,20 @@ private ArMms.MmsReportValueProjection ProjectReportValue(ArMms.MmsReportFrame r { _semanticReportProjectionContext = ArMms.MmsReportSemanticProjectionContext.Create(model); _semanticReportProjectionContextModel = model; + _semanticReportProjectionWarningsSeen.Clear(); } - return ArMms.MmsSemanticReportValueProjector.Project( + var projection = ArMms.MmsSemanticReportValueProjector.Project( report, _semanticReportProjectionContext); + return SuppressRepeatedSemanticWarnings(projection); } catch (Exception ex) { // Projection must never invent evidence or tear down a healthy MMS association. // Preserve the established raw update and expose an explicit fail-closed warning. var baseline = ArMms.MmsReportValueProjector.Project(report); - return new ArMms.MmsReportValueProjection + var fallback = new ArMms.MmsReportValueProjection { Updates = baseline.Updates, Warnings = baseline.Warnings @@ -74,6 +80,30 @@ private ArMms.MmsReportValueProjection ProjectReportValue(ArMms.MmsReportFrame r .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray() }; + return SuppressRepeatedSemanticWarnings(fallback); } } + + private ArMms.MmsReportValueProjection SuppressRepeatedSemanticWarnings( + ArMms.MmsReportValueProjection projection) + { + if (projection.Warnings.Count == 0) + return projection; + + // Structured report projection runs for every report frame. The same structural + // expansion warning can therefore repeat indefinitely even though it describes a + // stable model fact. Keep processing every value update, but surface each identical + // semantic warning once per projection model/session to avoid log/UI churn. + var freshWarnings = projection.Warnings + .Where(warning => _semanticReportProjectionWarningsSeen.Add(warning)) + .ToArray(); + if (freshWarnings.Length == projection.Warnings.Count) + return projection; + + return new ArMms.MmsReportValueProjection + { + Updates = projection.Updates, + Warnings = freshWarnings + }; + } } From 47becc88eabf15fd325f3ebb2d1721303f2324b1 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 11:20:57 +0700 Subject: [PATCH 013/112] Preserve scalar MMS reuse without collapsing structured FAT members --- .../Iec61850DataSetSignalInventoryService.cs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Services/Iec61850DataSetSignalInventoryService.cs b/Services/Iec61850DataSetSignalInventoryService.cs index 507fec875..a4476b3cb 100644 --- a/Services/Iec61850DataSetSignalInventoryService.cs +++ b/Services/Iec61850DataSetSignalInventoryService.cs @@ -68,10 +68,9 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( // membership row. Reusing by runtime-primary identity made diagnostics appear // complete while several FAT rows had no distinct static identity to bind. // - // Reuse only a signal that already represents this exact static member in this - // DataSet. A plain scalar/live signal remains untouched and a dedicated DataSet - // projection row is created beside it. Runtime monitoring may still deduplicate - // identical ObjectReference reads later; FAT membership identity is not lost. + // Scalar members remain backward compatible: when the static member itself is + // the resolved primary leaf, an existing exact IEC or engine-provided MMS-form + // signal may be enriched instead of duplicated. var current = FindExistingMembershipSignal(signals, descriptor, inventoryReference); if (current is not null) { @@ -103,11 +102,12 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( { var membership = FirstMembership(descriptor); var dataSetReference = membership?.DataSetReference ?? string.Empty; + var materialized = signals.ToArray(); // Strongest identity: the presentation row already carries both the exact static // member and the exact DataSet. This is safe even when its runtime ObjectReference // is shared with another membership. - var exact = signals.FirstOrDefault(signal => + var exact = materialized.FirstOrDefault(signal => ReferenceEquals(signal.DisplayReference, inventoryReference) && ReferenceEquals(signal.DataSetReference, dataSetReference)); if (exact is not null) @@ -116,7 +116,7 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( // An unclaimed row whose DisplayReference is already the exact static member may // be promoted into this membership. Never steal a row already owned by a different // DataSet merely because its runtime leaf happens to match. - var unclaimedDisplay = signals.FirstOrDefault(signal => + var unclaimedDisplay = materialized.FirstOrDefault(signal => ReferenceEquals(signal.DisplayReference, inventoryReference) && string.IsNullOrWhiteSpace(signal.DataSetReference)); if (unclaimedDisplay is not null) @@ -124,14 +124,29 @@ public static Iec61850DataSetSignalInventoryMergeResult EnsureMandatorySignals( // Legacy/live catalogs sometimes leave DisplayReference empty. Reuse their exact // ObjectReference only when the static member itself is that same scalar identity. - // This intentionally does NOT compare against descriptor.PrimaryValueReference when - // the static member is a structured/intermediate component. - return signals.FirstOrDefault(signal => + var exactStaticObject = materialized.FirstOrDefault(signal => ReferenceEquals(signal.ObjectReference, inventoryReference) && (string.IsNullOrWhiteSpace(signal.DisplayReference) || ReferenceEquals(signal.DisplayReference, inventoryReference)) && (string.IsNullOrWhiteSpace(signal.DataSetReference) || ReferenceEquals(signal.DataSetReference, dataSetReference))); + if (exactStaticObject is not null) + return exactStaticObject; + + // Preserve the long-standing literal MMS-reference compatibility only for a true + // scalar member: the static membership and resolved primary IEC reference must be + // identical. Structured/intermediate members deliberately fail this test, so a + // shared/container MMS alias cannot absorb ThdA.phsA/phsB/phsC-style memberships. + var staticIsResolvedScalar = + !string.IsNullOrWhiteSpace(descriptor.PrimaryValueReference) && + ReferenceEquals(descriptor.PrimaryValueReference, inventoryReference); + if (!staticIsResolvedScalar || string.IsNullOrWhiteSpace(descriptor.PrimaryValueMmsReference)) + return null; + + return materialized.FirstOrDefault(signal => + ReferenceEquals(signal.ObjectReference, descriptor.PrimaryValueMmsReference) && + (string.IsNullOrWhiteSpace(signal.DataSetReference) || + ReferenceEquals(signal.DataSetReference, dataSetReference))); } private static Iec61850DataSetSignalInventoryMergeResult EmptyResult() From b0477039de05de05480978568bae8370028da669 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 12:50:28 +0700 Subject: [PATCH 014/112] Remove reconciliation from direct SCL FAT critical path --- Services/IoTesting/IoTestReconciliationCache.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Services/IoTesting/IoTestReconciliationCache.cs b/Services/IoTesting/IoTestReconciliationCache.cs index 34d321e70..c22fe04fc 100644 --- a/Services/IoTesting/IoTestReconciliationCache.cs +++ b/Services/IoTesting/IoTestReconciliationCache.cs @@ -24,11 +24,12 @@ public static class IoTestReconciliationCache /// still build a model-only document; that path can only remain DesignOnly, never Absent. /// /// Direct SCL FAT is intentionally different. Its complete static DataSet scope is already - /// authoritative before association, and the monitor/report pipeline will acquire the - /// selected values immediately. Running connected exact-read reconciliation before that - /// acquisition can serialize dozens of MMS probes and hold FAT preparation for more than - /// a minute on real relays. For that workflow reconcile models only; absence remains - /// unproven and live acquisition starts without a network-probe gate. + /// authoritative before association and local signal selection does not require design/live + /// reconciliation. Even model-only reconciliation can be CPU-heavy enough on a large saved + /// model to hold the operator before StartMonitoringAsync. Therefore direct SCL FAT has no + /// reconciliation gate at all: invalidate any stale cache and return immediately. Live + /// acquisition starts from the SCL authority; Engineering/diagnostic reconciliation remains + /// an explicit, non-critical workflow and is never allowed to delay physical FAT readiness. /// public static Task RefreshAsync( Iec61850MonitorDevice device, @@ -37,7 +38,10 @@ public static Task RefreshAsync( ArgumentNullException.ThrowIfNull(device); if (UsesDirectSclFatAuthority(device)) - return RefreshModelOnlyAsync(device, cancellationToken); + { + Entries.TryRemove(device, out _); + return Task.CompletedTask; + } if (!NativeIec61850Client.HasReconciliationOwner(device.IpAddress, device.Port)) return RefreshModelOnlyAsync(device, cancellationToken); From 22c0e7a03fe8461260b8440a77519be479f88b24 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 12:51:52 +0700 Subject: [PATCH 015/112] Allow explicit DataSet THD scalar signals into runtime --- Models/SignalDefinition.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Models/SignalDefinition.cs b/Models/SignalDefinition.cs index 4cdc01334..78f763b5f 100644 --- a/Models/SignalDefinition.cs +++ b/Models/SignalDefinition.cs @@ -633,9 +633,22 @@ public bool IsReportCapable // q/t, Health, Beh, Mod, RCB attributes, nameplate, and other engineering leaves are // companion/diagnostic attributes. They must not become user-selected SCADA points. - public bool IsRawAttribute => IsRawEngineeringAttribute(ObjectReference, DataType); - - public bool IsValueSignal => !IsControlSignal && IsRuntimeValueSignal(ObjectReference, FunctionalConstraint, DataType, Category); + // Explicit static DataSet membership is different: once ARIEC has resolved that member + // to one scalar ST/MX leaf, the engineering noise filter must not discard the operator's + // authoritative FAT scope merely because the path contains THD/harmonic/demand wording. + public bool IsExplicitDataSetRuntimeValue => + !IsControlSignal && + !string.IsNullOrWhiteSpace(DataSetReference) && + (string.IsNullOrWhiteSpace(FunctionalConstraint) || + FunctionalConstraint.Equals("ST", StringComparison.OrdinalIgnoreCase) || + FunctionalConstraint.Equals("MX", StringComparison.OrdinalIgnoreCase)) && + IsRuntimeValueLeaf(ObjectReference, DataType); + + public bool IsRawAttribute => IsRawEngineeringAttribute(ObjectReference, DataType) && !IsExplicitDataSetRuntimeValue; + + public bool IsValueSignal => !IsControlSignal && + (IsRuntimeValueSignal(ObjectReference, FunctionalConstraint, DataType, Category) || + IsExplicitDataSetRuntimeValue); public bool CanPublishAsSignal => !IsControlSignal && IsValueSignal && !IsRawAttribute; public bool IsKnownReadFailure => IsKnownReadFailureState(Value, Quality, ProbeStatus); public bool CanPublishToRuntime => CanPublishAsSignal && !IsKnownReadFailure; From e488f7953b8f1d368a2746ce1fb4ae95434688dc Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 12:52:20 +0700 Subject: [PATCH 016/112] Lock THD runtime publication and nonblocking SCL FAT startup --- ...ectSclLatencyAndIdentityRegressionTests.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs b/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs index ec71eba89..a46d26aea 100644 --- a/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs @@ -9,7 +9,7 @@ namespace ARSAS.Tests; public sealed class IoFatDirectSclLatencyAndIdentityRegressionTests { [Fact] - public void StructuredStaticMember_KeepsDedicatedIdentityBesideCachedScalarSignal() + public void StructuredStaticMember_KeepsDedicatedIdentityAndPublishesResolvedThdScalarToRuntime() { const string dataObject = "IEDLD0/I_MHAI1.ThdA"; const string staticMember = dataObject + ".phsA"; @@ -111,6 +111,29 @@ public void StructuredStaticMember_KeepsDedicatedIdentityBesideCachedScalarSigna Assert.Equal(runtimeLeaf, membershipSignal.ObjectReference); Assert.Equal(dataSet, membershipSignal.DataSetReference); + // P5.4 field regression: the generic curated-HMI THD row may remain classified as + // harmonic/statistics noise, but an explicit static DataSet member that ARIEC has + // resolved to one scalar leaf is authoritative FAT scope and must cross the exact + // SignalDefinition -> monitor-point runtime gate. + Assert.False(genericScalar.CanPublishToRuntime); + Assert.True(membershipSignal.IsExplicitDataSetRuntimeValue); + Assert.True(membershipSignal.CanPublishAsSignal); + Assert.True(membershipSignal.CanPublishToRuntime); + + var structuredParent = new SignalDefinition + { + Name = "ThdA", + ObjectReference = dataObject, + DisplayReference = dataObject, + FunctionalConstraint = "MX", + DataType = "structure", + Category = "DataSet", + DataSetReference = dataSet, + Source = "ARIEC static DataSet parent" + }; + Assert.False(structuredParent.IsExplicitDataSetRuntimeValue); + Assert.False(structuredParent.CanPublishToRuntime); + var point = new IoTestPointPlan { TestPointId = "THD-A", @@ -151,23 +174,31 @@ public void StructuredStaticMember_KeepsDedicatedIdentityBesideCachedScalarSigna Assert.True(selection.Succeeded, selection.Message); var match = Assert.Single(selection.Matches); Assert.Same(membershipSignal, match.Signal); + Assert.True(match.Signal.CanPublishToRuntime); Assert.Empty(selection.MissingPoints); Assert.Empty(selection.AmbiguousPoints); } [Fact] - public void DirectSclFat_ReconciliationCannotGateAcquisitionWithConnectedExactReads() + public void DirectSclFat_ReconciliationCannotGateAcquisitionAtAll() { var source = File.ReadAllText(FindRepoFile("Services/IoTesting/IoTestReconciliationCache.cs")); var directSclGate = source.IndexOf("if (UsesDirectSclFatAuthority(device))", StringComparison.Ordinal); var connectedOwnerGate = source.IndexOf("if (!NativeIec61850Client.HasReconciliationOwner", StringComparison.Ordinal); - Assert.True(directSclGate >= 0, "Direct SCL FAT must have an explicit non-probing reconciliation path."); + Assert.True(directSclGate >= 0, "Direct SCL FAT must have an explicit nonblocking reconciliation path."); Assert.True(connectedOwnerGate > directSclGate, - "Direct SCL FAT must bypass connected exact-read reconciliation before native session ownership is considered."); - Assert.Contains("return RefreshModelOnlyAsync(device, cancellationToken);", source, StringComparison.Ordinal); + "Direct SCL FAT must return before native reconciliation ownership is considered."); + Assert.Contains("Entries.TryRemove(device, out _);", source, StringComparison.Ordinal); + Assert.Contains("return Task.CompletedTask;", source, StringComparison.Ordinal); Assert.Contains("UsesDirectSclFatAuthority", source, StringComparison.Ordinal); Assert.Contains("FAT SCL", source, StringComparison.Ordinal); + + var gateBodyStart = source.IndexOf('{', directSclGate); + var gateBodyEnd = source.IndexOf('}', gateBodyStart + 1); + var gateBody = source.Substring(gateBodyStart, gateBodyEnd - gateBodyStart + 1); + Assert.DoesNotContain("RefreshModelOnlyAsync", gateBody, StringComparison.Ordinal); + Assert.DoesNotContain("ReconcileConnectedAsync", gateBody, StringComparison.Ordinal); } private static string FindRepoFile(string relativePath) From 88e7901341110902176095cce068c450d027687e Mon Sep 17 00:00:00 2001 From: masarray Date: Wed, 2 Sep 2026 14:37:07 +0700 Subject: [PATCH 017/112] Fix FAT runtime responsiveness and multi-IED workflow --- .release/windows.json | 4 +- App.WindowsIdentity.cs | 21 ++ App.xaml.cs | 1 + ArIED61850Tester.csproj | 6 +- CHANGELOG.md | 15 ++ Directory.Build.props | 8 +- IoListTestingWindow.AddIed.cs | 61 +++++ IoListTestingWindow.ContextUx.cs | 30 ++- IoListTestingWindow.FatV2Ux.cs | 18 +- IoListTestingWindow.xaml | 18 +- IoListTestingWindow.xaml.cs | 15 ++ MainWindow.IoTesting.AutoConnect.cs | 155 +++++++------ MainWindow.IoTesting.cs | 210 +++++++++++++++-- MainWindow.xaml.cs | 14 +- Models/MonitorModels.cs | 4 +- Models/SignalDefinition.cs | 71 +++++- Services/Iec61850MonitorRuntime.cs | 17 ++ .../IoFatEngineeringSelectionBridge.cs | 145 ++++++++++++ .../IoTesting/IoFatSclProjectImportService.cs | 37 ++- .../IoTestProjectPersistenceService.cs | 89 +++++++- Services/IoTesting/IoTestSessionController.cs | 70 ++++-- Services/NativeIec61850Client.cs | 83 +++++++ VERSION | 2 +- landing/release-notes.json | 74 +++--- ...ectSclLatencyAndIdentityRegressionTests.cs | 14 +- ...3StructuredAnalogBindingRegressionTests.cs | 1 + .../IoFatReportPreviewServiceTests.cs | 8 +- .../ARSAS.Tests/IoFatSclProjectImportTests.cs | 20 ++ .../IoFatV2WorkspaceRegressionTests.cs | 32 ++- .../ARSAS.Tests/IoTestFatP0RegressionTests.cs | 28 ++- tests/ARSAS.Tests/IoTestingUiContractTests.cs | 40 +++- ...rementAndWorkspaceBridgeRegressionTests.cs | 211 ++++++++++++++++++ 32 files changed, 1309 insertions(+), 213 deletions(-) create mode 100644 App.WindowsIdentity.cs create mode 100644 IoListTestingWindow.AddIed.cs create mode 100644 Services/IoTesting/IoFatEngineeringSelectionBridge.cs create mode 100644 tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs diff --git a/.release/windows.json b/.release/windows.json index 3b1736961..6b76e5ffe 100644 --- a/.release/windows.json +++ b/.release/windows.json @@ -1,7 +1,7 @@ { - "version": "1.6.32", + "version": "1.6.33", "channel": "stable", "platform": "windows-x64", "primaryAsset": "ARSAS-Windows-x64-Setup.exe", - "publicationRequest": 20 + "publicationRequest": 21 } diff --git a/App.WindowsIdentity.cs b/App.WindowsIdentity.cs new file mode 100644 index 000000000..50c97c60a --- /dev/null +++ b/App.WindowsIdentity.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace ArIED61850Tester; + +internal static class WindowsApplicationIdentity +{ + private const string AppUserModelId = "masarray.ARSAS.IEC61850"; + + internal static void Apply() + { + if (!OperatingSystem.IsWindows()) + return; + + // A stable explicit identity makes Windows use the ARSAS executable icon for + // taskbar grouping even when the app was launched by dotnet or a portable host. + _ = SetCurrentProcessExplicitAppUserModelID(AppUserModelId); + } + + [DllImport("shell32.dll", CharSet = CharSet.Unicode)] + private static extern int SetCurrentProcessExplicitAppUserModelID(string appId); +} diff --git a/App.xaml.cs b/App.xaml.cs index 0b6326892..7a0d16197 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -19,6 +19,7 @@ public partial class App : Application protected override void OnStartup(StartupEventArgs e) { + WindowsApplicationIdentity.Apply(); base.OnStartup(e); // P2 installs one calm industrial visual system before StartupUri materializes. diff --git a/ArIED61850Tester.csproj b/ArIED61850Tester.csproj index 3000ca469..56f69afae 100644 --- a/ArIED61850Tester.csproj +++ b/ArIED61850Tester.csproj @@ -15,9 +15,9 @@ ARSAS ARSAS - IEC 61850 Engineering Workstation Open-source Windows IEC 61850 engineering workstation for MMS model discovery, reporting, independent multi-IED monitoring, GOOSE subscription, fault-record file transfer, Sampled Values engineering and evidence export, SCL workflows, diagnostics, sequence of events, and guarded control validation. - 1.6.32 - 1.6.32.0 - 1.6.32.0 + 1.6.33 + 1.6.33.0 + 1.6.33.0 https://github.com/masarray/arsas https://github.com/masarray/arsas git diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6f1dc4c..ddb08a1fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ Notable public changes to ARSAS are recorded here. Application releases must ide ## Unreleased +## 1.6.33 — 2026-09-02 + +### Added + +- FAT workspaces can import additional CID/SCD/ICD/IID sources without replacing the current project, including while another IED evidence session remains active. +- Engineering and FAT workspaces now share imported SCL signal selection, so operator selection remains consistent when switching modes. + +### Fixed + +- Direct-SCL FAT connection now reuses the imported model and returns after the MMS association and bounded point setup instead of rediscovering or waiting through the legacy settle path. +- Structured THD current/voltage aggregates and demand-energy values retain exact parent and phase identities and use authoritative MMS projections instead of remaining `Unknown`. +- FAT live updates are coalesced below operator input priority; steady telemetry no longer refreshes the whole collection or schedules autosave for every sample, restoring smooth scrolling and reliable Capture clicks. +- The Add IED action is independent from the active evidence-session edit lock while preserving the running IED's immutable capture mapping. +- Windows receives a stable ARSAS AppUserModelID in addition to the embedded executable icon, fixing generic taskbar grouping for portable and `dotnet`-hosted launches. + ## 1.6.31 — 2026-08-15 ### Fixed diff --git a/Directory.Build.props b/Directory.Build.props index 5e40f1dbc..2defd62c4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - 1.6.32 - 1.6.32.0 - 1.6.32.0 - 1.6.32 + 1.6.33 + 1.6.33.0 + 1.6.33.0 + 1.6.33 diff --git a/IoListTestingWindow.AddIed.cs b/IoListTestingWindow.AddIed.cs new file mode 100644 index 000000000..39b1b32c7 --- /dev/null +++ b/IoListTestingWindow.AddIed.cs @@ -0,0 +1,61 @@ +using System.IO; +using System.Windows; +using Microsoft.Win32; + +namespace ArIED61850Tester; + +public partial class IoListTestingWindow +{ + private async void AddFatIedFromScl_Click(object sender, RoutedEventArgs e) + { + if (!CanAddFatIed || Owner is not MainWindow engineeringWindow) + return; + + var dialog = new OpenFileDialog + { + Title = "Add IEC 61850 IED to current FAT workspace", + Filter = "IEC 61850 SCL (*.scd;*.cid;*.icd;*.iid;*.ssd)|*.scd;*.cid;*.icd;*.iid;*.ssd|XML SCL (*.xml)|*.xml|All files (*.*)|*.*", + CheckFileExists = true, + Multiselect = true + }; + if (dialog.ShowDialog(this) != true) + return; + + SetAddingFatIeds(true); + PreparationStatusText = $"Importing {dialog.FileNames.Length} SCL source(s) into the current FAT workspace…"; + try + { + var result = await engineeringWindow.AddSclIedsToLoadedFatAsync(this, dialog.FileNames); + PreparationStatusText = result.Message; + if (!result.Succeeded) + ShowActionResult(result, "IED could not be added"); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidDataException or InvalidOperationException or ArgumentException) + { + PreparationStatusText = ex.Message; + MessageBox.Show(this, ex.Message, "Add FAT IED failed", MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + SetAddingFatIeds(false); + } + } + + internal void RegisterAddedIeds(IReadOnlyCollection addedIeds) + { + Project.InitializeRuntimeNotifications(); + foreach (var ied in addedIeds) + { + if (_selectedIedContextInstalled) + ied.PropertyChanged += ContextIed_PropertyChanged; + Storage?.TrackAddedIed(ied); + } + + FatIedList.Items.Refresh(); + SelectedIed = addedIeds.FirstOrDefault() ?? SelectedIed; + Raise(nameof(ProjectSummary)); + RaiseStatusProperties(); + RaiseSelectedIedContextProperties(); + Storage?.ScheduleSave(); + } +} diff --git a/IoListTestingWindow.ContextUx.cs b/IoListTestingWindow.ContextUx.cs index 2c63e3982..33afde115 100644 --- a/IoListTestingWindow.ContextUx.cs +++ b/IoListTestingWindow.ContextUx.cs @@ -12,6 +12,7 @@ namespace ArIED61850Tester; public partial class IoListTestingWindow { private bool _selectedIedContextInstalled; + private bool _contextRefreshScheduled; public bool SelectedCanStartWorkflow => SelectedIed != null && !SelectedIed.IsPreparing && Session.CanStart; @@ -361,14 +362,15 @@ private void ContextWindow_PropertyChanged(object? sender, PropertyChangedEventA { RaiseSelectedIedContextProperties(); if (e.PropertyName == nameof(SelectedIed)) - Dispatcher.BeginInvoke(new Action(RefreshFatV2WorkspaceUx), DispatcherPriority.DataBind); + Dispatcher.BeginInvoke( + new Action(() => RefreshFatV2WorkspaceUx(refreshRows: true)), + DispatcherPriority.Background); } } private void ContextSession_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - RaiseSelectedIedContextProperties(); - RefreshFatV2WorkspaceUx(); + ScheduleContextRefresh(); } private void ContextIed_PropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -376,12 +378,24 @@ private void ContextIed_PropertyChanged(object? sender, PropertyChangedEventArgs if (e.PropertyName == nameof(IoTestIedPlan.IsPreparing)) RaisePreparationProperties(); - RefreshFatV2WorkspaceUx(); - if (!ReferenceEquals(sender, SelectedIed)) - return; + ScheduleContextRefresh(); + } - Raise(nameof(SelectedIedSummary)); - RaiseSelectedIedContextProperties(); + private void ScheduleContextRefresh() + { + if (_contextRefreshScheduled) + return; + _contextRefreshScheduled = true; + Dispatcher.BeginInvoke(new Action(() => + { + _contextRefreshScheduled = false; + // Session/runtime notifications update summaries and visible cells through + // normal bindings. Re-filtering the complete collection for every live + // value used to interrupt scrolling and swallow Capture clicks. + RefreshFatV2WorkspaceUx(); + Raise(nameof(SelectedIedSummary)); + RaiseSelectedIedContextProperties(); + }), DispatcherPriority.Background); } private void RaiseSelectedIedContextProperties() diff --git a/IoListTestingWindow.FatV2Ux.cs b/IoListTestingWindow.FatV2Ux.cs index 3e23c1c49..b47a671c9 100644 --- a/IoListTestingWindow.FatV2Ux.cs +++ b/IoListTestingWindow.FatV2Ux.cs @@ -11,6 +11,7 @@ public partial class IoListTestingWindow { private DataGrid? _fatSignalsGrid; private Button? _removedSignalsButton; + private ICollectionView? _fatSignalsView; private bool _fatV2UxInstalled; private readonly BooleanToVisibilityConverter _fatBooleanVisibility = new(); @@ -31,19 +32,22 @@ private void InstallFatV2WorkspaceUx() _fatV2UxInstalled = true; } - RefreshFatV2WorkspaceUx(); + RefreshFatV2WorkspaceUx(refreshRows: true); } - private void RefreshFatV2WorkspaceUx() + private void RefreshFatV2WorkspaceUx(bool refreshRows = false) { if (_fatSignalsGrid != null) { var view = CollectionViewSource.GetDefaultView(_fatSignalsGrid.ItemsSource); - if (view != null) + if (view != null && !ReferenceEquals(view, _fatSignalsView)) { + _fatSignalsView = view; view.Filter = item => item is IoTestPointPlan point && point.IsIncludedInFat; - view.Refresh(); + refreshRows = true; } + if (refreshRows) + view?.Refresh(); } if (_removedSignalsButton != null) @@ -262,7 +266,7 @@ private void RemoveSelectedFromFat_Click(object sender, RoutedEventArgs e) point.RemoveFromFat(); Storage?.ScheduleSave(); - RefreshFatV2WorkspaceUx(); + RefreshFatV2WorkspaceUx(refreshRows: true); RaiseSelectedIedContextProperties(); } @@ -298,7 +302,7 @@ private void RemovedSignals_Click(object sender, RoutedEventArgs e) return; Storage?.ScheduleSave(); - RefreshFatV2WorkspaceUx(); + RefreshFatV2WorkspaceUx(refreshRows: true); RaiseSelectedIedContextProperties(); } @@ -353,4 +357,4 @@ private static IEnumerable FindVisualDescendants(DependencyObject root) wh } return null; } -} \ No newline at end of file +} diff --git a/IoListTestingWindow.xaml b/IoListTestingWindow.xaml index f62ba6396..48b1a7bb5 100644 --- a/IoListTestingWindow.xaml +++ b/IoListTestingWindow.xaml @@ -123,7 +123,12 @@ - + + diff --git a/IoListTestingWindow.xaml.cs b/IoListTestingWindow.xaml.cs index 0012e2f25..eb519aef7 100644 --- a/IoListTestingWindow.xaml.cs +++ b/IoListTestingWindow.xaml.cs @@ -11,6 +11,7 @@ public partial class IoListTestingWindow : Window, INotifyPropertyChanged { private IoTestIedPlan? _selectedIed; private string _preparationStatusText = string.Empty; + private bool _isAddingFatIeds; public IoListTestingWindow() : this(CreateEmptyProject(), CreateEmptyController(), null) @@ -83,6 +84,19 @@ private set public bool CanEditPlan => !IsPreparingIed && Session.CanEditPlan; + // Importing another SCL source does not mutate the capture scope owned by the + // active evidence session. Keep it available while FAT is running, but serialize + // it with connection preparation so model imports cannot race endpoint setup. + public bool CanAddFatIed => !IsPreparingIed && !_isAddingFatIeds; + + private void SetAddingFatIeds(bool value) + { + if (_isAddingFatIeds == value) + return; + _isAddingFatIeds = value; + Raise(nameof(CanAddFatIed)); + } + public string StartWorkflowText => SelectedIed?.IsPreparing == true ? $"Connecting {SelectedIed.IedName}…" : "Connect & Start IED"; @@ -422,6 +436,7 @@ private void RaisePreparationProperties() Raise(nameof(CanStartWorkflow)); Raise(nameof(CanSelectIed)); Raise(nameof(CanEditPlan)); + Raise(nameof(CanAddFatIed)); Raise(nameof(StartWorkflowText)); Raise(nameof(FooterStatusText)); } diff --git a/MainWindow.IoTesting.AutoConnect.cs b/MainWindow.IoTesting.AutoConnect.cs index 17f76b407..a9cd539a5 100644 --- a/MainWindow.IoTesting.AutoConnect.cs +++ b/MainWindow.IoTesting.AutoConnect.cs @@ -166,8 +166,20 @@ void ReportProgress(string message) // Reconciliation belongs to the connection/discovery lifecycle, before local // FAT row selection. SCL-backed fast association keeps the design authority // intact; absence is never inferred merely because full live discovery was skipped. - ReportProgress("Reconciling FAT source with authoritative IED model"); - await IoTestReconciliationCache.RefreshAsync(device, _applicationCancellation.Token); + if (hasSclRuntimeAuthority) + { + // The imported ARIEC SCL workspace already owns the complete static + // DataSet identity. Connected reconciliation can perform a large exact-read + // pass and used to hold this SIPROTEC FAT preparation for about one minute. + // Explicit Re-scan remains the design-versus-live comparison action. + IoTestReconciliationCache.Invalidate(device); + ReportProgress("Using authoritative SCL DataSet identity · starting live acquisition"); + } + else + { + ReportProgress("Reconciling FAT source with authoritative IED model"); + await IoTestReconciliationCache.RefreshAsync(device, _applicationCancellation.Token); + } // Never let a runtime anchor from an earlier model silently decide a fresh // FAT preparation. Re-prove every requested row against the current model; @@ -322,16 +334,13 @@ void ReportProgress(string message) } } - // Do not hold the operator behind an 8–18 second report-settling phase. FAT - // readiness is the first usable live image; report planning for non-fast points - // can continue in the monitor loop after the card is already responsive. - var acquisition = await SettleIoFatReportPriorityAsync( - ied, - requestedPoints, - device, - ReportProgress); - + // The monitor points now exist and MMS polling has already been scheduled. + // Return control to FAT immediately: waiting here for values/report setup made + // a sub-second SCL association look like a 30-50 second connection whenever + // the UI dispatcher was busy with the first report burst. Values and report + // optimization continue through the shared Engineering runtime. var binding = _ioTestLiveBindingService.BindIed(ied, Devices); + var acquisition = ReadIoFatAcquisitionSummary(requestedPoints, device); var liveCount = requestedPoints.Count(point => point.LiveBindingState == IoTestLiveBindingState.LivePointReady); if (liveCount == 0) @@ -407,6 +416,7 @@ private bool AttachIoFatSclRuntimeAuthority( if (!device.HasDiscoveryCache) { var projected = SclWorkspaceSignalMapper.BuildSignals(workspace); + DetachSignalHandlers(device.Signals); device.Signals.Clear(); device.Signals.AddRange(projected); } @@ -419,11 +429,72 @@ private bool AttachIoFatSclRuntimeAuthority( workspace.DesignModel); } + AttachIoFatSignalHandlers(device); + device.RecountSelectedSignals(); device.RefreshComputed(); return device.Signals.Count > 0; } + private int SynchronizeImportedSclFatWithEngineering(IoTestProject project) + => SynchronizeImportedSclFatWithEngineering(project, project.Ieds); + + private int SynchronizeImportedSclFatWithEngineering( + IoTestProject project, + IEnumerable ieds) + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(ieds); + var synchronized = 0; + foreach (var ied in ieds) + { + var device = ResolveIoTestDevice(ied.LiveDeviceId) + ?? ResolveIoTestDevice(ied.IpAddress) + ?? ResolveIoTestDevice(ied.IedName); + var existed = device is not null; + if (device is null) + { + device = new Iec61850MonitorDevice + { + Name = ied.IedName, + SclIedName = ied.IedName, + IdentitySource = "FAT SCL · shared Engineering workspace", + IpAddress = ied.IpAddress, + Port = 102, + AllowDynamicDataSetWrites = true, + Status = "SCL model ready", + Detail = "Engineering and FAT share this imported SCL model and signal selection." + }; + Devices.Add(device); + } + + var preserveEngineeringSelection = existed && device.Signals.Any(signal => signal.IsSelected); + if (!AttachIoFatSclRuntimeAuthority(ied, device)) + continue; + + synchronized += IoFatEngineeringSelectionBridge.Initialize( + ied, + device, + preserveEngineeringSelection); + _ioTestLiveBindingService.BindIed(ied, Devices); + SaveSignalSelectionMemory(device); + } + + RaiseWorkspaceCounts(); + return synchronized; + } + + private void AttachIoFatSignalHandlers(Iec61850MonitorDevice device) + { + foreach (var signal in device.Signals) + { + if (_signalOwners.ContainsKey(signal)) + continue; + signal.PropertyChanged += Signal_PropertyChanged; + _signalOwners[signal] = device; + } + } + private async Task ConnectIoFatUsingPreparedSclAsync(Iec61850MonitorDevice device) { // ConnectUsingSavedModelAsync and the runtime predate the SCL fast-connect UI and @@ -444,68 +515,6 @@ private async Task ConnectIoFatUsingPreparedSclAsync(Iec61850MonitorDevice } } - private async Task SettleIoFatReportPriorityAsync( - IoTestIedPlan ied, - IReadOnlyCollection requestedPoints, - Iec61850MonitorDevice device, - Action reportProgress) - { - return await ObserveIoFatAcquisitionAsync( - ied, - requestedPoints, - device, - reportProgress, - TimeSpan.FromMilliseconds(2500)); - } - - private async Task ObserveIoFatAcquisitionAsync( - IoTestIedPlan ied, - IReadOnlyCollection requestedPoints, - Iec61850MonitorDevice device, - Action reportProgress, - TimeSpan timeout) - { - var deadline = DateTime.UtcNow + timeout; - var last = ReadIoFatAcquisitionSummary(requestedPoints, device); - var announced = false; - var nextBindingRefreshUtc = DateTime.MinValue; - - while (DateTime.UtcNow < deadline && device.IsMonitoring) - { - await Task.Delay(100); - var nowUtc = DateTime.UtcNow; - if (nowUtc >= nextBindingRefreshUtc) - { - _ioTestLiveBindingService.BindIed(ied, Devices); - nextBindingRefreshUtc = nowUtc.AddMilliseconds(250); - } - last = ReadIoFatAcquisitionSummary(requestedPoints, device); - - if (!announced) - { - reportProgress("Waiting for first live FAT image · report optimization continues in background"); - announced = true; - } - - var livePoints = requestedPoints - .Where(point => point.LiveBindingState == IoTestLiveBindingState.LivePointReady) - .ToList(); - if (livePoints.Count > 0 && livePoints.All(HasUsableFatLiveValue)) - break; - } - - _ioTestLiveBindingService.BindIed(ied, Devices); - return ReadIoFatAcquisitionSummary(requestedPoints, device); - } - - private static bool HasUsableFatLiveValue(IoTestPointPlan point) - { - var value = (point.Runtime.CurrentValue ?? string.Empty).Trim(); - return value.Length > 0 && value != "-" && value != "—" && - !value.Equals("Unknown", StringComparison.OrdinalIgnoreCase) && - !value.Contains("pending", StringComparison.OrdinalIgnoreCase); - } - private static IoFatAcquisitionSummary ReadIoFatAcquisitionSummary( IEnumerable requestedPoints, Iec61850MonitorDevice device) diff --git a/MainWindow.IoTesting.cs b/MainWindow.IoTesting.cs index bf2983448..ea362be77 100644 --- a/MainWindow.IoTesting.cs +++ b/MainWindow.IoTesting.cs @@ -1,4 +1,5 @@ using System.IO; +using System.ComponentModel; using System.Text.Json; using System.Windows; using System.Windows.Controls; @@ -22,7 +23,10 @@ public partial class MainWindow private IoTestSessionController? _activeIoTestSessionController; private long _ioTestObservationSequence; private int? _pollingIntervalBeforeIoFat; - private int _ioFatEvidenceDrainDispatchActive; + private IoTestProject? _ioFatSelectionBridgeProject; + private bool _ioFatSelectionBridgeActive; + private readonly HashSet _ioFatSelectionSaveDeviceIds = new(StringComparer.OrdinalIgnoreCase); + private bool _ioFatSelectionSaveScheduled; protected override void OnInitialized(EventArgs e) { @@ -290,6 +294,11 @@ private async void OpenSclFatTesting_Click(object sender, RoutedEventArgs e) IoTestingEvidenceRoot(), CreateIoTestSession, _applicationCancellation.Token); + // A direct SCL FAT import is also an Engineering import. Attach the exact + // already-parsed ARIEC workspace now, after persisted FAT choices have been + // restored but before the window is shown, and establish one shared selection + // authority. No discovery or second model is created during mode switches. + SynchronizeImportedSclFatWithEngineering(launch.Project); var warningCount = import.Findings.Count(finding => finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase) || finding.Severity.Equals("High", StringComparison.OrdinalIgnoreCase) || @@ -412,6 +421,7 @@ private async void OpenIoListPackage_Click(object sender, RoutedEventArgs e) private Task ShowIoTestingWorkspaceAsync(IoTestWorkspaceLaunchResult launch, int importWarningCount) { + AttachIoFatSelectionBridge(launch.Project); var binding = _ioTestLiveBindingService.Bind(launch.Project, Devices); var restoredText = launch.RestoredProgress ? "saved progress restored" : "new project"; SetStatus( @@ -442,6 +452,7 @@ private Task ShowIoTestingWorkspaceAsync(IoTestWorkspaceLaunchResult launch, int void WindowClosed(object? sender, EventArgs args) { window.Closed -= WindowClosed; + DetachIoFatSelectionBridge(launch.Project); _runtime.PointUpdated -= Runtime_IoTestPointUpdated; if (ReferenceEquals(_activeIoTestSessionController, controller)) _activeIoTestSessionController = null; @@ -469,6 +480,181 @@ void WindowClosed(object? sender, EventArgs args) return Task.CompletedTask; } + internal async Task AddSclIedsToLoadedFatAsync( + IoListTestingWindow window, + IReadOnlyCollection sclPaths) + { + ArgumentNullException.ThrowIfNull(window); + ArgumentNullException.ThrowIfNull(sclPaths); + if (!ReferenceEquals(window, _loadedIoFatWindow) || !window.IsLoaded) + return IoTestSessionActionResult.Failure("The target FAT workspace is no longer active."); + if (!window.CanAddFatIed) + return IoTestSessionActionResult.Failure("Wait for the current IED connection preparation before importing another SCL source."); + + SetStatus($"Adding {sclPaths.Count} SCL source(s) to the current FAT workspace…"); + var import = await _ioFatSclProjectImportService.ImportAdditionalAsync( + sclPaths, + _applicationCancellation.Token); + + var existingSources = window.Project.Sources + .ToDictionary(source => source.Sha256, StringComparer.OrdinalIgnoreCase); + var uniqueImportedSources = import.Sources + .Where(source => !existingSources.ContainsKey(source.Sha256)) + .ToArray(); + + var existingIedKeys = window.Project.Ieds + .Select(ied => $"{ied.IedName.Trim()}|{ied.IpAddress.Trim()}") + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var addedIeds = import.Project.Ieds + .Where(ied => existingIedKeys.Add($"{ied.IedName.Trim()}|{ied.IpAddress.Trim()}")) + .ToArray(); + if (addedIeds.Length == 0) + { + return IoTestSessionActionResult.Failure( + "The selected SCL source contains no new IED endpoint; every IED is already present in this FAT workspace."); + } + + if (window.Storage != null && uniqueImportedSources.Length > 0) + { + await window.Storage.AddSourcesAsync( + import.Project, + import.SourceInputs, + _applicationCancellation.Token); + } + + foreach (var ied in addedIeds) + window.Project.Ieds.Add(ied); + + var allSources = window.Project.Sources + .Concat(uniqueImportedSources) + .GroupBy(source => source.Sha256, StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .OrderBy(source => source.SourceId, StringComparer.Ordinal) + .ToArray(); + window.Project.SetSources(allSources, IoFatSourceIdentity.ComputeSetFingerprint(allSources)); + + foreach (var point in addedIeds.SelectMany(ied => ied.TestPoints)) + point.PropertyChanged += IoFatSelectionPoint_PropertyChanged; + // The running IED and its immutable session mapping are deliberately left + // untouched. Only newly imported endpoints join the shared workspace. + SynchronizeImportedSclFatWithEngineering(window.Project, addedIeds); + window.RegisterAddedIeds(addedIeds); + window.Storage?.ScheduleSave(); + + var warningCount = import.Findings.Count(finding => + finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase) || + finding.Severity.Equals("Error", StringComparison.OrdinalIgnoreCase)); + var message = $"Added {addedIeds.Length} IED(s) and {addedIeds.Sum(ied => ied.TestPoints.Count)} static DataSet point(s) to FAT" + + (warningCount == 0 ? "." : $" · {warningCount} import warning(s)."); + SetStatus(message); + return IoTestSessionActionResult.Success(message); + } + + private void AttachIoFatSelectionBridge(IoTestProject project) + { + DetachIoFatSelectionBridge(_ioFatSelectionBridgeProject); + _ioFatSelectionBridgeProject = project; + foreach (var point in project.Ieds.SelectMany(ied => ied.TestPoints)) + point.PropertyChanged += IoFatSelectionPoint_PropertyChanged; + } + + private void DetachIoFatSelectionBridge(IoTestProject? project) + { + if (project is null) + return; + foreach (var point in project.Ieds.SelectMany(ied => ied.TestPoints)) + point.PropertyChanged -= IoFatSelectionPoint_PropertyChanged; + if (ReferenceEquals(_ioFatSelectionBridgeProject, project)) + _ioFatSelectionBridgeProject = null; + } + + private void IoFatSelectionPoint_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (_ioFatSelectionBridgeActive || sender is not IoTestPointPlan point || + e.PropertyName is not (nameof(IoTestPointPlan.TestEnabled) or nameof(IoTestPointPlan.FatDisposition))) + { + return; + } + + var device = ResolveIoTestDevice(point.LiveDeviceId) + ?? ResolveIoTestDevice(point.IpAddress) + ?? ResolveIoTestDevice(point.IedName); + if (device is null) + return; + + _ioFatSelectionBridgeActive = true; + try + { + if (IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)) + { + ScheduleIoFatSelectionSave(device); + RaiseWorkspaceCounts(); + } + } + finally + { + _ioFatSelectionBridgeActive = false; + } + } + + private void ScheduleIoFatSelectionSave(Iec61850MonitorDevice device) + { + _ioFatSelectionSaveDeviceIds.Add(device.DeviceId); + if (_ioFatSelectionSaveScheduled) + return; + + _ioFatSelectionSaveScheduled = true; + Dispatcher.BeginInvoke(new Action(() => + { + _ioFatSelectionSaveScheduled = false; + var deviceIds = _ioFatSelectionSaveDeviceIds.ToArray(); + _ioFatSelectionSaveDeviceIds.Clear(); + foreach (var deviceId in deviceIds) + { + var target = ResolveIoTestDevice(deviceId); + if (target is not null) + SaveSignalSelectionMemory(target); + } + }), DispatcherPriority.Background); + } + + private void SynchronizeEngineeringSelectionToFat( + SignalDefinition signal, + Iec61850MonitorDevice device) + { + var project = _ioFatSelectionBridgeProject; + if (_ioFatSelectionBridgeActive || project is null) + return; + + var ied = project.Ieds.FirstOrDefault(candidate => + candidate.LiveDeviceId.Equals(device.DeviceId, StringComparison.OrdinalIgnoreCase) || + (candidate.IedName.Equals(device.SclIedName, StringComparison.OrdinalIgnoreCase) && + candidate.IpAddress.Equals(device.IpAddress, StringComparison.OrdinalIgnoreCase)) || + candidate.IedName.Equals(device.Name, StringComparison.OrdinalIgnoreCase)); + if (ied is null) + return; + + _ioFatSelectionBridgeActive = true; + try + { + IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( + signal, + signal.IsSelected, + ied, + device); + } + finally + { + _ioFatSelectionBridgeActive = false; + } + } + + private void SynchronizeAllEngineeringSelectionsToFat(Iec61850MonitorDevice device) + { + foreach (var signal in device.Signals) + SynchronizeEngineeringSelectionToFat(signal, device); + } + private IoTestSessionController CreateIoTestSession(IoTestProject project, string evidenceRoot) => new( project, @@ -480,24 +666,10 @@ private void DispatchIoFatEvidence(Action action) { ArgumentNullException.ThrowIfNull(action); - var priority = Volatile.Read(ref _ioFatEvidenceDrainDispatchActive) == 0 - ? DispatcherPriority.DataBind - : DispatcherPriority.Background; - - Dispatcher.BeginInvoke( - new Action(() => - { - Interlocked.Increment(ref _ioFatEvidenceDrainDispatchActive); - try - { - action(); - } - finally - { - Interlocked.Decrement(ref _ioFatEvidenceDrainDispatchActive); - } - }), - priority); + // Live evidence is important but must never outrank mouse/keyboard input. + // Bounded drains still preserve every digital edge; Background priority lets + // scrolling and operator Capture clicks run between those short batches. + Dispatcher.BeginInvoke(action, DispatcherPriority.Background); } private static string IoTestingProjectsRoot() => Path.Combine( diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 408cb0f29..86cbc6cc7 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -118,7 +118,9 @@ public MainWindow() _uiFlushTimer = new DispatcherTimer(DispatcherPriority.Background) { - Interval = TimeSpan.FromMilliseconds(100) + // Five UI projections per second leave dispatcher capacity for smooth grid + // scrolling while the acquisition/evidence pipelines retain full fidelity. + Interval = TimeSpan.FromMilliseconds(200) }; _uiFlushTimer.Tick += UiFlushTimer_Tick; @@ -900,6 +902,7 @@ void WizardClosed(object? sender, EventArgs args) return false; } + SynchronizeAllEngineeringSelectionsToFat(device); SaveSignalSelectionMemory(device); device.RefreshComputed(); RebuildControlFeedbackIndex(device); @@ -1721,6 +1724,7 @@ private void UiFlushTimer_Tick(object? sender, EventArgs e) var point = snapshot.Point; if (snapshot.Sequence < point.Sequence) continue; + var qualityChanged = !string.Equals(point.Quality, snapshot.Quality, StringComparison.Ordinal); var uiDetectedEdge = point.ApplyProcessValue(snapshot.Value); if (pending.HasValueEdge || snapshot.IsValueEdge || uiDetectedEdge) MarkPointRecentlyChanged(point); @@ -1730,8 +1734,11 @@ private void UiFlushTimer_Tick(object? sender, EventArgs e) point.Reason = snapshot.Reason; point.Status = snapshot.Status; point.Sequence = snapshot.Sequence; - UpdateCommandFeedbackFromLivePoint(point); - ReconcileAnnunciatorFromLivePoint(point); + if (uiDetectedEdge || pending.HasValueEdge || snapshot.IsValueEdge || qualityChanged) + { + UpdateCommandFeedbackFromLivePoint(point); + ReconcileAnnunciatorFromLivePoint(point); + } if (snapshot.IsReportTraffic) { var device = Devices.FirstOrDefault(item => item.DeviceId.Equals(point.DeviceId, StringComparison.OrdinalIgnoreCase)); @@ -1827,6 +1834,7 @@ private void Signal_PropertyChanged(object? sender, PropertyChangedEventArgs e) if (owner.IsBulkSignalSelectionUpdate) return; owner.ApplySignalSelectionChange(changedSignal, changedSignal.IsSelected); + SynchronizeEngineeringSelectionToFat(changedSignal, owner); } RaiseWorkspaceCounts(); } diff --git a/Models/MonitorModels.cs b/Models/MonitorModels.cs index 85fddd7d7..784d08648 100644 --- a/Models/MonitorModels.cs +++ b/Models/MonitorModels.cs @@ -638,7 +638,9 @@ public string Value public string SourceMode { get => _sourceMode; set => Set(ref _sourceMode, string.IsNullOrWhiteSpace(value) ? "Unknown" : value); } public string Reason { get => _reason; set => Set(ref _reason, string.IsNullOrWhiteSpace(value) ? "-" : value); } public string Status { get => _status; set => Set(ref _status, string.IsNullOrWhiteSpace(value) ? "Unknown" : value); } - public long Sequence { get => _sequence; set => Set(ref _sequence, value); } + // Runtime ordering metadata is not rendered. Avoid an unnecessary WPF property-change + // notification for every poll/report sample; the value still guards stale snapshots. + public long Sequence { get => _sequence; set => _sequence = value; } public bool IsRecentlyChanged { get => _isRecentlyChanged; set => Set(ref _isRecentlyChanged, value); } public bool IsAnnunciatorSelected { get => _isAnnunciatorSelected; set => Set(ref _isAnnunciatorSelected, value); } public bool CanUseAsAnnunciator => FunctionalConstraint.Equals("ST", StringComparison.OrdinalIgnoreCase); diff --git a/Models/SignalDefinition.cs b/Models/SignalDefinition.cs index 78f763b5f..e37722925 100644 --- a/Models/SignalDefinition.cs +++ b/Models/SignalDefinition.cs @@ -912,6 +912,8 @@ private static bool IsRuntimeValueSignal(string reference, string functionalCons return false; return IsRuntimeValueLeaf(r, dataType) || + IsThreePhaseMeasurementAggregate(r) || + IsDemandEnergyAggregate(r) || (string.Equals(category, "Position", StringComparison.OrdinalIgnoreCase) && r.EndsWith(".stval")) || (string.Equals(category, "Protection", StringComparison.OrdinalIgnoreCase) && r.EndsWith(".general")); } @@ -965,7 +967,14 @@ private static bool IsRawEngineeringAttribute(string reference, string dataType) if (string.Equals(dataType, "Timestamp", StringComparison.OrdinalIgnoreCase)) return true; var r = NormalizeRef(reference); - return IsStatisticsOrHarmonicNoise(r) || + // THD/TDD phase magnitudes are process measurements, not protocol noise. The + // broad harmonic filter exists to keep complete harmonic spectra and statistic + // internals out of the ordinary signal list, but it used to hide even an exact + // MHAI ThdA/ThdPPV ...cVal.mag.f leaf. That made a checked THD row disappear at + // the SignalDefinition -> live monitor boundary. Keep only numeric terminal + // leaves and the explicitly supported three-phase aggregate exempt; q/t companions + // and configuration fields remain non-publishable. + return (!IsOperatorMeasurementException(r, dataType) && IsStatisticsOrHarmonicNoise(r)) || r.EndsWith(".q") || r.EndsWith(".t") || r.EndsWith(".tm") || @@ -994,6 +1003,66 @@ private static bool IsRawEngineeringAttribute(string reference, string dataType) r.Contains(".configrev"); } + private static bool IsPowerQualityMagnitude(string normalizedReference, string dataType) + { + var r = NormalizeRef(normalizedReference); + if (!r.Contains(".thd", StringComparison.OrdinalIgnoreCase) && + !r.Contains(".tdd", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!r.EndsWith(".mag.f", StringComparison.OrdinalIgnoreCase) && + !r.EndsWith(".instmag.f", StringComparison.OrdinalIgnoreCase) && + !r.EndsWith(".cval.mag.f", StringComparison.OrdinalIgnoreCase) && + !r.EndsWith(".instcval.mag.f", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var type = (dataType ?? string.Empty).Trim(); + return type.Length == 0 || + type.Contains("float", StringComparison.OrdinalIgnoreCase) || + type.Contains("double", StringComparison.OrdinalIgnoreCase) || + type.Contains("real", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsOperatorMeasurementException(string normalizedReference, string dataType) + => IsPowerQualityMagnitude(normalizedReference, dataType) || + IsDemandEnergyMagnitude(normalizedReference, dataType) || + IsThreePhaseMeasurementAggregate(normalizedReference) || + IsDemandEnergyAggregate(normalizedReference); + + private static bool IsDemandEnergyMagnitude(string normalizedReference, string dataType) + { + var r = NormalizeRef(normalizedReference); + if (!r.Contains(".dmdwh", StringComparison.OrdinalIgnoreCase) || + (!r.EndsWith(".mag.f", StringComparison.OrdinalIgnoreCase) && + !r.EndsWith(".instmag.f", StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + var type = (dataType ?? string.Empty).Trim(); + return type.Length == 0 || + type.Contains("float", StringComparison.OrdinalIgnoreCase) || + type.Contains("double", StringComparison.OrdinalIgnoreCase) || + type.Contains("real", StringComparison.OrdinalIgnoreCase); + } + + public static bool IsThreePhaseMeasurementAggregate(string? reference) + { + var r = NormalizeRef(reference ?? string.Empty).TrimEnd('.'); + return r.EndsWith(".thda", StringComparison.OrdinalIgnoreCase) || + r.EndsWith(".thdppv", StringComparison.OrdinalIgnoreCase); + } + + public static bool IsDemandEnergyAggregate(string? reference) + { + var r = NormalizeRef(reference ?? string.Empty).TrimEnd('.'); + return r.EndsWith(".dmdwhmv", StringComparison.OrdinalIgnoreCase); + } + public static bool IsDefaultScadaMeasurementMagnitude(string normalizedReference) { diff --git a/Services/Iec61850MonitorRuntime.cs b/Services/Iec61850MonitorRuntime.cs index 942d0b001..c05d1188d 100644 --- a/Services/Iec61850MonitorRuntime.cs +++ b/Services/Iec61850MonitorRuntime.cs @@ -704,6 +704,8 @@ private async Task StartReportPlansAsync( : result.AcquisitionLabel; foreach (var point in coveredPoints) { + if (RequiresExactMmsValueAuthority(point.IecReference)) + continue; session.PointPlanIds[point.PointKey] = plan.PlanId; if (!string.IsNullOrWhiteSpace(result.ReportControlReference)) point.ReportControlReference = result.ReportControlReference; @@ -971,6 +973,12 @@ private async Task ReceiveReportSlicesAsync(DeviceSession session, CancellationT if (point == null) continue; + // Siemens static DataSets report THD as a nested three-phase structure. + // Until its scalar fan-out is schema-proven, exact MMS reads remain the + // value authority so a report projection cannot overwrite correct values. + if (RequiresExactMmsValueAuthority(point.IecReference)) + continue; + // A real report update proves reference coverage, but an initial GI or // integrity image does not yet prove that dchg/qchg is working. Keep MMS // verification active until a change report is actually observed. @@ -2000,6 +2008,15 @@ private static bool IsFastPoint(Iec61850MonitorPoint point) reference.EndsWith(".general"); } + private static bool RequiresExactMmsValueAuthority(string reference) + { + var normalized = NormalizeReference(reference); + return SignalDefinition.IsThreePhaseMeasurementAggregate(normalized) || + normalized.Contains(".thda.phs", StringComparison.OrdinalIgnoreCase) || + normalized.Contains(".thdppv.phs", StringComparison.OrdinalIgnoreCase) || + normalized.Contains(".dmdwh", StringComparison.OrdinalIgnoreCase); + } + private static bool HasExactSemanticEdge(Iec61850MonitorPoint point, string oldValue, string newValue) { var oldText = (oldValue ?? string.Empty).Trim(); diff --git a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs new file mode 100644 index 000000000..1b554657b --- /dev/null +++ b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs @@ -0,0 +1,145 @@ +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; + +namespace ArIED61850Tester.Services.IoTesting; + +/// +/// Keeps a direct-SCL FAT plan and the Engineering signal catalog on one selection +/// authority. The exact static DataSet membership row is the bridge identity; its +/// resolved ObjectReference remains the shared acquisition target. +/// +public static class IoFatEngineeringSelectionBridge +{ + public static int Initialize( + IoTestIedPlan ied, + Iec61850MonitorDevice device, + bool preserveExistingEngineeringSelection) + { + ArgumentNullException.ThrowIfNull(ied); + ArgumentNullException.ThrowIfNull(device); + + var changed = 0; + foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsSclDataSetAuthority)) + { + var signal = FindSignal(point, device); + if (signal is null) + continue; + + if (preserveExistingEngineeringSelection) + { + var enabled = signal.IsSelected && point.IsIncludedInFat; + if (point.TestEnabled != enabled) + { + point.TestEnabled = enabled; + changed++; + } + } + else + { + var selected = point.TestEnabled && point.IsIncludedInFat; + if (signal.IsSelected != selected) + { + signal.IsSelected = selected; + changed++; + } + } + } + + device.RecountSelectedSignals(); + return changed; + } + + public static bool ApplyFatPointSelection( + IoTestPointPlan point, + Iec61850MonitorDevice device) + { + ArgumentNullException.ThrowIfNull(point); + ArgumentNullException.ThrowIfNull(device); + if (!IoTestSignalSelectionService.IsSclDataSetAuthority(point)) + return false; + + var signal = FindSignal(point, device); + if (signal is null) + return false; + + var selected = point.TestEnabled && point.IsIncludedInFat; + if (signal.IsSelected == selected) + return false; + + signal.IsSelected = selected; + device.RecountSelectedSignals(); + return true; + } + + public static bool ApplyEngineeringSignalSelection( + SignalDefinition signal, + bool selected, + IoTestIedPlan ied, + Iec61850MonitorDevice device) + { + ArgumentNullException.ThrowIfNull(signal); + ArgumentNullException.ThrowIfNull(ied); + ArgumentNullException.ThrowIfNull(device); + + var changed = false; + foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsSclDataSetAuthority)) + { + if (!ReferenceEquals(FindSignal(point, device), signal)) + continue; + + if (selected && !point.IsIncludedInFat) + { + point.RestoreToFat(); + changed = true; + } + if (point.TestEnabled != selected) + { + point.TestEnabled = selected; + changed = true; + } + } + return changed; + } + + public static SignalDefinition? FindSignal( + IoTestPointPlan point, + Iec61850MonitorDevice device) + { + ArgumentNullException.ThrowIfNull(point); + ArgumentNullException.ThrowIfNull(device); + + var staticReferences = new[] + { + point.SourceIecReference, + point.ReportDisplayReference, + point.EventLogSearchReference + } + .Where(reference => !string.IsNullOrWhiteSpace(reference)) + .Select(IoTestLiveBindingService.NormalizeReference) + .Where(reference => reference.Length > 0) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var dataSet = IoTestLiveBindingService.NormalizeReference(point.DataSetName); + + var exactMembership = device.Signals + .Where(signal => staticReferences.Contains( + IoTestLiveBindingService.NormalizeReference(signal.DisplayReference))) + .Where(signal => dataSet.Length == 0 || + dataSet.Equals( + IoTestLiveBindingService.NormalizeReference(signal.DataSetReference), + StringComparison.OrdinalIgnoreCase)) + .ToArray(); + if (exactMembership.Length == 1) + return exactMembership[0]; + + var runtime = IoTestLiveBindingService.NormalizeReference(point.ObjectReference); + if (runtime.Length == 0) + return null; + + var runtimeMatches = device.Signals + .Where(signal => runtime.Equals( + IoTestLiveBindingService.NormalizeReference(signal.ObjectReference), + StringComparison.OrdinalIgnoreCase)) + .ToArray(); + return runtimeMatches.Length == 1 ? runtimeMatches[0] : null; + } +} diff --git a/Services/IoTesting/IoFatSclProjectImportService.cs b/Services/IoTesting/IoFatSclProjectImportService.cs index cbebf432b..398915baf 100644 --- a/Services/IoTesting/IoFatSclProjectImportService.cs +++ b/Services/IoTesting/IoFatSclProjectImportService.cs @@ -67,13 +67,26 @@ internal bool TryGetRuntimeWorkspace( } } - public async Task ImportAsync( + public Task ImportAsync( IReadOnlyCollection sclPaths, string? projectName = null, CancellationToken cancellationToken = default) + => ImportCoreAsync(sclPaths, projectName, replaceRuntimeWorkspaces: true, cancellationToken); + + public Task ImportAdditionalAsync( + IReadOnlyCollection sclPaths, + CancellationToken cancellationToken = default) + => ImportCoreAsync(sclPaths, projectName: null, replaceRuntimeWorkspaces: false, cancellationToken); + + private async Task ImportCoreAsync( + IReadOnlyCollection sclPaths, + string? projectName, + bool replaceRuntimeWorkspaces, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(sclPaths); - SetRuntimeWorkspaces(Array.Empty()); + if (replaceRuntimeWorkspaces) + SetRuntimeWorkspaces(Array.Empty()); var requestedPaths = sclPaths .Where(path => !string.IsNullOrWhiteSpace(path)) .Select(Path.GetFullPath) @@ -217,9 +230,13 @@ public async Task ImportAsync( // Keep the exact engine-owned workspaces alive for the current FAT run. AutoConnect // can attach these already-parsed models to the IED and perform a fast MMS association // instead of repeating a network-wide Smart Scan that SCL-backed FAT does not need. - SetRuntimeWorkspaces(loadedSources + var importedWorkspaces = loadedSources .SelectMany(source => source.Document.Ieds) - .ToArray()); + .ToArray(); + if (replaceRuntimeWorkspaces) + SetRuntimeWorkspaces(importedWorkspaces); + else + AddRuntimeWorkspaces(importedWorkspaces); return new IoFatSclProjectImportResult( project, @@ -235,6 +252,18 @@ private void SetRuntimeWorkspaces(IReadOnlyList workspaces) _runtimeWorkspaces = workspaces; } + private void AddRuntimeWorkspaces(IReadOnlyCollection workspaces) + { + lock (_runtimeWorkspaceGate) + { + _runtimeWorkspaces = _runtimeWorkspaces + .Concat(workspaces) + .GroupBy(workspace => workspace.WorkspaceKey, StringComparer.OrdinalIgnoreCase) + .Select(group => group.Last()) + .ToArray(); + } + } + private static List BuildIedPlans( IReadOnlyCollection loadedSources, FatVerificationProject verificationProject, diff --git a/Services/IoTesting/IoTestProjectPersistenceService.cs b/Services/IoTesting/IoTestProjectPersistenceService.cs index 9c69e3af8..a9dd0d425 100644 --- a/Services/IoTesting/IoTestProjectPersistenceService.cs +++ b/Services/IoTesting/IoTestProjectPersistenceService.cs @@ -30,6 +30,7 @@ public sealed class IoTestWorkspacePersistence : ObservableObject, IDisposable private readonly object _saveSync = new(); private readonly Timer _saveTimer; private readonly IoTestSessionController _session; + private readonly List _sourceFiles; private bool _disposed; private string _statusText = "Progress storage is ready"; private DateTimeOffset? _lastSavedAtUtc; @@ -46,7 +47,7 @@ private IoTestWorkspacePersistence( _session = session; LocalDirectory = localDirectory; SnapshotPath = Path.Combine(localDirectory, "project.snapshot.json"); - SourceFiles = sourceFiles; + _sourceFiles = sourceFiles.ToList(); EvidenceProjectDirectory = evidenceProjectDirectory; _saveTimer = new Timer(_ => SaveFromTimer(), null, Timeout.Infinite, Timeout.Infinite); Subscribe(); @@ -55,7 +56,7 @@ private IoTestWorkspacePersistence( public IoTestProject Project { get; } public string LocalDirectory { get; } public string SnapshotPath { get; } - public IReadOnlyList SourceFiles { get; } + public IReadOnlyList SourceFiles => _sourceFiles; public string SourceWorkbookPath => SourceFiles .FirstOrDefault(source => source.Source.Kind.Equals(IoFatSourceKinds.Workbook, StringComparison.OrdinalIgnoreCase)) ?.LocalPath ?? string.Empty; @@ -254,6 +255,70 @@ public void ScheduleSave() _saveTimer.Change(650, Timeout.Infinite); } + public async Task AddSourcesAsync( + IoTestProject importedSourceProject, + IReadOnlyCollection sourceInputs, + CancellationToken cancellationToken = default) + { + ThrowIfDisposed(); + ArgumentNullException.ThrowIfNull(importedSourceProject); + ArgumentNullException.ThrowIfNull(sourceInputs); + + var described = await IoFatSourceWorkspaceService.DescribeAsync(sourceInputs, cancellationToken) + .ConfigureAwait(false); + var newHashes = described + .Where(source => _sourceFiles.All(existing => + !existing.Source.Sha256.Equals(source.Source.Sha256, StringComparison.OrdinalIgnoreCase))) + .ToArray(); + if (newHashes.Length == 0) + return; + + var stagingProject = new IoTestProject + { + ProjectId = importedSourceProject.ProjectId, + SchemaVersion = importedSourceProject.SchemaVersion, + ProjectName = importedSourceProject.ProjectName + }; + var stagingDescriptors = newHashes.Select(source => source.Source).ToArray(); + stagingProject.SetSources( + stagingDescriptors, + IoFatSourceIdentity.ComputeSetFingerprint(stagingDescriptors)); + var staged = await IoFatSourceWorkspaceService.StageDescribedAsync( + stagingProject, + newHashes, + Path.Combine(LocalDirectory, "source"), + cancellationToken) + .ConfigureAwait(false); + lock (_saveSync) + { + foreach (var source in staged) + { + if (_sourceFiles.All(existing => + !existing.Source.Sha256.Equals(source.Source.Sha256, StringComparison.OrdinalIgnoreCase))) + { + _sourceFiles.Add(source); + } + } + _sourceFiles.Sort((left, right) => + StringComparer.Ordinal.Compare(left.Source.SourceId, right.Source.SourceId)); + } + } + + public void TrackAddedIed(IoTestIedPlan ied) + { + ThrowIfDisposed(); + ArgumentNullException.ThrowIfNull(ied); + ied.PropertyChanged -= Changed; + ied.PropertyChanged += Changed; + foreach (var point in ied.TestPoints) + { + point.PropertyChanged -= Changed; + point.PropertyChanged += Changed; + point.Runtime.PropertyChanged -= Changed; + point.Runtime.PropertyChanged += Changed; + } + } + public void SaveNow() { ThrowIfDisposed(); @@ -402,7 +467,23 @@ private void Unsubscribe() } } - private void Changed(object? sender, PropertyChangedEventArgs e) => ScheduleSave(); + private void Changed(object? sender, PropertyChangedEventArgs e) + { + // Live telemetry is transient and can change hundreds of times per second. + // Evidence/state changes still autosave, and their snapshot includes the most + // recent live value. Avoid resetting the timer and notifying UI for every poll. + if (sender is IoTestPointRuntime && e.PropertyName is + nameof(IoTestPointRuntime.CurrentValue) or + nameof(IoTestPointRuntime.CurrentQuality) or + nameof(IoTestPointRuntime.CurrentSource) or + nameof(IoTestPointRuntime.CurrentIedTimestamp) or + nameof(IoTestPointRuntime.LastSequence)) + { + return; + } + + ScheduleSave(); + } private void SaveFromTimer() { @@ -962,4 +1043,4 @@ private sealed record IoTestPackageEvidence( string Sha256, long RecordCount, string LastHash); -} \ No newline at end of file +} diff --git a/Services/IoTesting/IoTestSessionController.cs b/Services/IoTesting/IoTestSessionController.cs index 53eb21330..26fcf82bf 100644 --- a/Services/IoTesting/IoTestSessionController.cs +++ b/Services/IoTesting/IoTestSessionController.cs @@ -22,7 +22,13 @@ public sealed class IoTestSessionController : ObservableObject, IDisposable private readonly Func _journalFactory; private readonly IoTestTransitionEvaluator _evaluator; private readonly IoTestRollingCaptureCoordinator _captureCoordinator; - private readonly ConcurrentQueue _pendingSnapshots = new(); + // Only the newest observation per live point is needed by the UI transition + // evaluator. Coalescing prevents a fast report/poll stream from building an + // unbounded dispatcher backlog while the operator scrolls the FAT grid. + private readonly ConcurrentDictionary _pendingSnapshots = new(StringComparer.OrdinalIgnoreCase); + // Actual transitions are never coalesced: FAT evidence must retain a fast + // OFF→ON→OFF sequence even when all three samples arrive in one UI frame. + private readonly ConcurrentQueue _pendingEdgeSnapshots = new(); private readonly Dictionary> _activePointIndex = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _activeLivePoints = new(StringComparer.OrdinalIgnoreCase); private readonly HashSet _sessionPointIds = new(StringComparer.OrdinalIgnoreCase); @@ -170,7 +176,7 @@ public IoTestSessionActionResult Start( _activePointIndex.Clear(); _activeLivePoints.Clear(); _sessionPointIds.Clear(); - while (_pendingSnapshots.TryDequeue(out _)) { } + ClearPendingSnapshots(); foreach (var binding in bindings) { @@ -237,7 +243,7 @@ public IoTestSessionActionResult Pause(string reason = "Paused by operator") return IoTestSessionActionResult.Failure("No running FAT session is available to pause."); State = IoTestSessionState.Paused; - while (_pendingSnapshots.TryDequeue(out _)) { } + ClearPendingSnapshots(); StatusText = reason; if (!AppendOrFault(SessionEvent("session_paused", reason))) return IoTestSessionActionResult.Failure(StatusText); @@ -270,7 +276,7 @@ public IoTestSessionActionResult Resume() } _connectionGeneration++; - while (_pendingSnapshots.TryDequeue(out _)) { } + ClearPendingSnapshots(); if (!AppendOrFault(SessionEvent("session_resumed", "Session resumed after rebinding current live points; current values are treated as a new baseline image."))) return IoTestSessionActionResult.Failure(StatusText); @@ -399,7 +405,7 @@ public void Enqueue(Iec61850EventEntry entry) !entry.DeviceId.Equals(activeDevice.DeviceId, StringComparison.OrdinalIgnoreCase)) return; - _pendingSnapshots.Enqueue(new QueuedSnapshot( + var queued = new QueuedSnapshot( entry.DeviceId, entry.IecReference, entry.OldValue, @@ -409,7 +415,24 @@ public void Enqueue(Iec61850EventEntry entry) entry.SourceMode, entry.Reason, entry.Sequence, - DateTimeOffset.UtcNow)); + DateTimeOffset.UtcNow); + var pendingKey = PointKey(entry.DeviceId, entry.IecReference); + var preservesTransitionEvidence = + _activePointIndex.TryGetValue(pendingKey, out var mappedPoints) && + mappedPoints.Any(point => point.CaptureMode == FatCaptureMode.AutomaticTransition); + if (preservesTransitionEvidence && + !Iec61850MonitorPoint.AreSemanticallyEquivalent(entry.OldValue, entry.NewValue)) + { + _pendingSnapshots.TryRemove(pendingKey, out _); + _pendingEdgeSnapshots.Enqueue(queued); + } + else + { + _pendingSnapshots.AddOrUpdate( + pendingKey, + queued, + (_, current) => queued.Sequence >= current.Sequence ? queued : current); + } ScheduleDrain(); } @@ -421,7 +444,7 @@ public void Dispose() Stop("Workspace closed; FAT session stopped."); _disposed = true; CleanupSessionResources(disposeJournal: true); - while (_pendingSnapshots.TryDequeue(out _)) { } + ClearPendingSnapshots(); } private void ScheduleDrain() @@ -446,9 +469,15 @@ private void DrainPendingSnapshots() try { while (processed < MaxSnapshotsPerDrain && - stopwatch.ElapsedMilliseconds < DrainBudgetMilliseconds && - _pendingSnapshots.TryDequeue(out var snapshot)) + stopwatch.ElapsedMilliseconds < DrainBudgetMilliseconds) { + QueuedSnapshot? snapshot; + if (!_pendingEdgeSnapshots.TryDequeue(out snapshot)) + { + var pendingKey = _pendingSnapshots.Keys.FirstOrDefault(); + if (pendingKey == null || !_pendingSnapshots.TryRemove(pendingKey, out snapshot)) + break; + } ProcessSnapshot(snapshot); processed++; if (State != IoTestSessionState.Running) @@ -458,11 +487,17 @@ private void DrainPendingSnapshots() finally { Interlocked.Exchange(ref _drainScheduled, 0); - if (!_pendingSnapshots.IsEmpty && State == IoTestSessionState.Running) + if ((!_pendingEdgeSnapshots.IsEmpty || !_pendingSnapshots.IsEmpty) && State == IoTestSessionState.Running) ScheduleDrain(); } } + private void ClearPendingSnapshots() + { + _pendingSnapshots.Clear(); + while (_pendingEdgeSnapshots.TryDequeue(out _)) { } + } + private void ProcessSnapshot(QueuedSnapshot snapshot) { if (State != IoTestSessionState.Running) @@ -472,6 +507,7 @@ private void ProcessSnapshot(QueuedSnapshot snapshot) if (!_activePointIndex.TryGetValue(key, out var points)) return; + var progressChanged = false; foreach (var point in points.Distinct()) { if (point.CaptureMode == FatCaptureMode.OperatorSnapshot) @@ -499,6 +535,7 @@ private void ProcessSnapshot(QueuedSnapshot snapshot) snapshot.Sequence, _connectionGeneration); var evaluation = _captureCoordinator.Observe(point, observation); + progressChanged |= evaluation.StateChanged || evaluation.Evidence != null; if (evaluation.Evidence != null) { @@ -536,8 +573,11 @@ private void ProcessSnapshot(QueuedSnapshot snapshot) } } - RaiseProgress(); - UpdateRunningCompletionStatus(); + if (progressChanged) + { + RaiseProgress(); + UpdateRunningCompletionStatus(); + } } private void UpdateRunningCompletionStatus() @@ -579,7 +619,7 @@ private void InterruptForConnectionLoss(string capturedStatus) return; State = IoTestSessionState.Interrupted; - while (_pendingSnapshots.TryDequeue(out _)) { } + ClearPendingSnapshots(); StatusText = $"{ActiveIed.IedName} monitoring was interrupted ({capturedStatus}). Reconnect the IED, then Resume to establish a new baseline."; AppendOrFault(SessionEvent("session_interrupted", StatusText)); } @@ -808,7 +848,7 @@ private void CleanupSessionResources(bool disposeJournal, bool keepActiveIed = f _activePointIndex.Clear(); _activeLivePoints.Clear(); _captureCoordinator.Clear(); - while (_pendingSnapshots.TryDequeue(out _)) { } + ClearPendingSnapshots(); if (disposeJournal) { _journal?.Dispose(); @@ -858,4 +898,4 @@ private sealed record QueuedSnapshot( string Reason, long Sequence, DateTimeOffset CapturedAtUtc); -} \ No newline at end of file +} diff --git a/Services/NativeIec61850Client.cs b/Services/NativeIec61850Client.cs index eacf54cfd..f71c41a34 100644 --- a/Services/NativeIec61850Client.cs +++ b/Services/NativeIec61850Client.cs @@ -4059,6 +4059,23 @@ private static string NormalizeFunctionalConstraint(string functionalConstraint, private Iec61850ReadValue? ProjectReadValue(ArMms.MmsDataValue? value, string dataType, string readReference, string requestedReference) { + // Explicit object-level DataSet members must be projected before generic + // first-scalar semantic binding. Otherwise a three-phase THD object collapses + // to phase A and loses the B/C values carried by the same MMS structure. + if (value != null && + SignalDefinition.IsThreePhaseMeasurementAggregate(requestedReference) && + TryProjectThreePhaseAggregate(value, requestedReference, readReference, out var aggregate)) + { + return aggregate; + } + + if (value != null && + SignalDefinition.IsDemandEnergyAggregate(requestedReference) && + TryProjectDemandEnergyAggregate(value, requestedReference, readReference, dataType, out var demand)) + { + return demand; + } + if (TryProjectStructuredLeafBySemantic(value, dataType, readReference, requestedReference, out var semanticProjection, out var semanticStatus)) return semanticProjection; @@ -4095,6 +4112,72 @@ private static string NormalizeFunctionalConstraint(string functionalConstraint, }; } + private static bool TryProjectThreePhaseAggregate( + ArMms.MmsDataValue value, + string requestedReference, + string readReference, + out Iec61850ReadValue projected) + { + projected = new Iec61850ReadValue(); + if (value.Kind is not (ArMms.MmsDataKind.Structure or ArMms.MmsDataKind.Array) || + value.Children.Count < 3) + { + return false; + } + + var phaseValues = new List(3); + foreach (var phase in value.Children.Take(3)) + { + var scalar = FindFirstFloating(phase) ?? FindFirstInteger(phase); + if (scalar == null) + return false; + + var converted = ConvertProjectedValue(scalar, "Float32", requestedReference); + phaseValues.Add(Convert.ToDouble(converted, CultureInfo.InvariantCulture) + .ToString("0.######", CultureInfo.InvariantCulture)); + } + + var display = string.Join(", ", phaseValues); + projected = new Iec61850ReadValue + { + Value = display, + DisplayValue = display, + Quality = DecodeQuality(value), + DeviceTimestamp = DecodeTimestamp(value), + SourceReference = requestedReference, + ReadReference = readReference, + Projection = "projected-three-phase-aggregate" + }; + return true; + } + + private static bool TryProjectDemandEnergyAggregate( + ArMms.MmsDataValue value, + string requestedReference, + string readReference, + string dataType, + out Iec61850ReadValue projected) + { + projected = new Iec61850ReadValue(); + var scalar = FindFirstFloating(value) ?? FindFirstInteger(value); + if (scalar == null) + return false; + + var raw = ConvertProjectedValue(scalar, dataType, requestedReference); + var display = FormatProjectedDisplay(raw, dataType); + projected = new Iec61850ReadValue + { + Value = raw, + DisplayValue = display, + Quality = DecodeQuality(value), + DeviceTimestamp = DecodeTimestamp(value), + SourceReference = requestedReference, + ReadReference = readReference, + Projection = "projected-demand-energy-aggregate" + }; + return true; + } + private bool TryProjectWithArIecBinding( ArMms.MmsDataValue? value, string dataType, diff --git a/VERSION b/VERSION index e0380928f..7337a939f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.32 +1.6.33 diff --git a/landing/release-notes.json b/landing/release-notes.json index 353ac0041..c4447bef5 100644 --- a/landing/release-notes.json +++ b/landing/release-notes.json @@ -1,56 +1,52 @@ { "schemaVersion": 1, "product": "ARSAS", - "version": "1.6.31", - "title": "Reconnect-safe live DataSet discovery and hybrid RCB acquisition", - "titleId": "Discovery DataSet live dan acquisition RCB hybrid yang aman saat reconnect", - "summary": "ARSAS 1.6.31 completes the repaired live reporting pipeline from discovery through reconnect: DataSet members are retained, static and dynamic BRCB/URCB coverage is planned by ARIEC61850, only uncovered signals fall back to MMS polling, and a new MMS association cannot inherit report or command-correlation evidence from the previous connection.", - "summaryId": "ARSAS 1.6.31 melengkapi perbaikan pipeline live reporting dari discovery sampai reconnect: member DataSet dipertahankan, coverage static dan dynamic BRCB/URCB direncanakan oleh ARIEC61850, hanya signal yang tidak ter-cover yang fallback ke MMS polling, dan association MMS baru tidak dapat mewarisi evidence report atau korelasi command dari koneksi sebelumnya.", + "version": "1.6.33", + "title": "Fast, unified and responsive IEC 61850 FAT workspace", + "titleId": "Workspace IEC 61850 FAT yang cepat, terpadu, dan responsif", + "summary": "ARSAS 1.6.33 unifies Engineering and FAT around the imported SCL model, shortens direct-SCL connection readiness, projects structured THD and demand-energy values correctly, and keeps operator input responsive during continuous live acquisition.", + "summaryId": "ARSAS 1.6.33 menyatukan Engineering dan FAT pada model SCL yang diimpor, mempercepat kesiapan koneksi direct-SCL, memproyeksikan THD terstruktur dan demand-energy dengan benar, serta menjaga input operator tetap responsif saat akuisisi live terus berjalan.", "highlights": [ - "Live discovery requests bounded DataSet directories and retains their members, preventing report-capable signals from disappearing between MMS discovery and the application catalog.", - "ARSAS executes the ARIEC61850 hybrid plan across static BRCB, static URCB, verified-free dynamic RCB slots, and per-signal MMS polling residuals.", - "Dynamic RCB planning supports temporary DataSets whose members reside in logical devices different from the RCB owner, as permitted by the live server model.", - "Report catalog matching selects the owning primary-value descriptor even when related quality and timestamp descriptors share the same primary reference.", - "Instantaneous magnitude values such as Hz.instMag.f resolve their quality and timestamp companions at Hz.q and Hz.t, while preserving clear operator-facing names.", - "Smart reconnect runs the same authoritative hybrid planner as initial monitoring, with fresh RCB availability checks and clean per-association report evidence." + "Direct-SCL FAT connection reuses the imported model and reaches monitoring after the MMS association and bounded point setup without a discovery-from-zero or legacy settle delay.", + "Engineering and FAT share SCL signal identity and operator selection; switching workspace no longer creates an isolated signal universe.", + "Additional CID, SCD, ICD or IID sources can be added to an existing FAT project, including while another IED evidence session remains active.", + "Structured ThdA and ThdPPV parent values preserve their phase A/B/C or AB/BC/CA members, while DmdWhMV uses the exact MMS value authority exposed by the relay.", + "Windows taskbar grouping uses the embedded ARSAS icon and a stable explicit application identity." ], "highlightsId": [ - "Live discovery meminta directory DataSet secara terbatas dan mempertahankan member-nya, sehingga signal report-capable tidak lagi hilang di antara MMS discovery dan catalog aplikasi.", - "ARSAS mengeksekusi hybrid plan ARIEC61850 melalui static BRCB, static URCB, slot dynamic RCB yang terverifikasi free, dan residual MMS polling per signal.", - "Perencanaan dynamic RCB mendukung DataSet sementara dengan member pada logical device yang berbeda dari owner RCB, sesuai model live server.", - "Pemetaan report catalog memilih descriptor primary value pemiliknya walaupun descriptor quality dan timestamp terkait menggunakan primary reference yang sama.", - "Instantaneous magnitude seperti Hz.instMag.f menemukan companion quality dan timestamp pada Hz.q dan Hz.t, sambil mempertahankan nama yang jelas bagi operator.", - "Smart reconnect menjalankan authoritative hybrid planner yang sama dengan initial monitoring, termasuk pemeriksaan availability RCB terbaru dan evidence report per-association yang bersih." + "Koneksi FAT direct-SCL menggunakan kembali model hasil import dan masuk ke monitoring setelah association MMS serta bounded point setup tanpa discovery dari nol atau legacy settle delay.", + "Engineering dan FAT berbagi identitas serta pilihan signal SCL; perpindahan workspace tidak lagi membentuk signal universe yang terpisah.", + "Source CID, SCD, ICD, atau IID tambahan dapat dimasukkan ke project FAT yang sedang digunakan, termasuk ketika evidence session IED lain masih aktif.", + "Parent value ThdA dan ThdPPV terstruktur mempertahankan member phase A/B/C atau AB/BC/CA, sedangkan DmdWhMV menggunakan exact MMS value authority yang diberikan relay.", + "Grouping taskbar Windows menggunakan icon ARSAS yang tertanam dan explicit application identity yang stabil." ], "improvements": [ - "Fresh RCB availability is revalidated before any setup write, and static or dynamic control blocks are reused only when the live evidence still permits it.", - "Physical validation evidence distinguishes successful RCB setup from actual report frames and verified process-value changes, avoiding false claims of report delivery.", - "Physical-validation mutation and snapshot capture are synchronized so operator diagnostics cannot observe torn counters or collection changes while report frames arrive.", - "The release source is gated by the standard ARSAS solution build and regression suite plus portable and Windows installer smoke validation before publication.", - "Stable Windows publication produces installer and portable binaries together with SHA-256 checksums, SPDX SBOM, application/engine provenance and GitHub artifact attestations." + "Live FAT observations are coalesced in bounded dispatcher batches below mouse and keyboard input priority, while the dedicated digital-edge queue remains lossless.", + "Steady telemetry updates only visible value cells; it no longer re-filters the entire DataGrid or resets autosave for every poll.", + "Capture progress and workspace summaries update only when evidence or test state actually changes.", + "Adding an IED synchronizes only the new endpoints, leaving the active IED's capture map and evidence scope immutable.", + "The Windows release remains gated by the full ARSAS regression suite, portable smoke validation, checksums, SPDX SBOM, provenance and GitHub attestations." ], "improvementsId": [ - "Availability RCB terbaru divalidasi ulang sebelum setup write, dan control block static atau dynamic hanya digunakan ketika evidence live masih mengizinkannya.", - "Evidence validasi fisik membedakan setup RCB yang berhasil dari report frame aktual dan perubahan process value yang terverifikasi, sehingga tidak ada klaim palsu mengenai report delivery.", - "Mutasi dan snapshot physical validation disinkronisasi agar diagnostic operator tidak melihat counter yang torn atau perubahan collection ketika report frame masuk.", - "Source release digate oleh build solution dan regression suite ARSAS standard serta portable dan Windows installer smoke validation sebelum publikasi.", - "Publikasi Windows stable menghasilkan binary installer dan portable bersama SHA-256 checksum, SPDX SBOM, provenance aplikasi/engine dan GitHub artifact attestation." + "Observasi live FAT di-coalesce dalam bounded dispatcher batch di bawah prioritas input mouse dan keyboard, sementara queue digital-edge khusus tetap lossless.", + "Telemetry steady hanya mengubah cell value yang terlihat; seluruh DataGrid tidak lagi di-filter ulang dan timer autosave tidak lagi di-reset pada setiap poll.", + "Progress capture dan ringkasan workspace hanya diperbarui ketika evidence atau test state benar-benar berubah.", + "Penambahan IED hanya menyinkronkan endpoint baru sehingga capture map dan evidence scope IED aktif tetap immutable.", + "Release Windows tetap digate oleh regression suite ARSAS lengkap, portable smoke validation, checksum, SPDX SBOM, provenance, dan GitHub attestation." ], "knownLimitations": [ - "Physical relay validation remains necessary for device-specific engineering behavior that cannot be reproduced by CI, including vendor-specific model exposure and field network conditions.", - "A successful RCB setup is intentionally not treated as proof that report frames were received; field validation must confirm frames and value changes for the target IED and network.", - "The three-digit FAT timestamp display intentionally rounds the decoded timestamp to the nearest millisecond; full decoded precision remains available in evidence views.", + "Physical relay validation remains necessary for vendor-specific values, report behavior and field network conditions that cannot be reproduced by CI.", + "Adding an SCL source does not automatically start or switch the active FAT evidence session; the operator selects and starts each IED explicitly.", "Windows x64 is the only packaged desktop platform in this stable release.", "The public binaries are not Authenticode code-signed; Windows SmartScreen may show an unrecognized-publisher warning.", "Raw-Ethernet GOOSE and Sampled Values workflows require an administrator-installed and approved Npcap driver, suitable capture permission and visibility of the relevant multicast traffic." ], "knownLimitationsId": [ - "Validasi relay fisik tetap diperlukan untuk perilaku engineering spesifik perangkat yang tidak dapat direproduksi oleh CI, termasuk exposure model vendor-specific dan kondisi network di lapangan.", - "Setup RCB yang berhasil sengaja tidak dianggap sebagai bukti bahwa report frame telah diterima; validasi lapangan harus mengonfirmasi frame dan perubahan value pada IED dan network target.", - "Display timestamp FAT tiga digit secara sengaja membulatkan decoded timestamp ke millisecond terdekat; full decoded precision tetap tersedia pada evidence view.", + "Validasi relay fisik tetap diperlukan untuk value vendor-specific, perilaku report, dan kondisi network lapangan yang tidak dapat direproduksi oleh CI.", + "Penambahan source SCL tidak otomatis memulai atau memindahkan evidence session FAT aktif; operator memilih dan memulai setiap IED secara eksplisit.", "Windows x64 adalah satu-satunya platform desktop yang dipaketkan pada stable release ini.", "Binary publik belum ditandatangani dengan Authenticode; Windows SmartScreen dapat menampilkan peringatan unrecognized publisher.", - "Workflow raw-Ethernet GOOSE dan Sampled Values memerlukan driver Npcap yang telah dipasang dan disetujui administrator, capture permission yang sesuai dan visibility traffic multicast terkait." + "Workflow raw-Ethernet GOOSE dan Sampled Values memerlukan driver Npcap yang telah dipasang dan disetujui administrator, capture permission yang sesuai, serta visibility traffic multicast terkait." ], "codeSigning": { "status": "unsigned", @@ -63,11 +59,11 @@ "src": "assets/screenshots/arsas-live-values.webp", "width": 1507, "height": 893, - "alt": "Representative ARSAS 1.6.31 live values and reconnect-safe IEC 61850 acquisition workspace", - "altId": "Representasi workspace live values dan acquisition IEC 61850 ARSAS 1.6.31 yang aman saat reconnect", - "caption": "Representative ARSAS live-value workspace for stable release 1.6.31. Acquisition labels identify report delivery only after report frames are received; reconnect resets association-scoped evidence and uncovered signals remain explicit MMS polling residuals.", - "captionId": "Representasi workspace live value ARSAS untuk stable release 1.6.31. Label acquisition mengidentifikasi report delivery hanya setelah report frame diterima; reconnect mereset evidence per-association dan signal yang tidak ter-cover tetap ditampilkan sebagai residual MMS polling." + "alt": "ARSAS 1.6.33 Engineering and FAT live IEC 61850 workspace", + "altId": "Workspace live IEC 61850 Engineering dan FAT ARSAS 1.6.33", + "caption": "ARSAS 1.6.33 shares imported SCL signal identity between Engineering and FAT while bounded background refresh keeps operator interaction responsive.", + "captionId": "ARSAS 1.6.33 berbagi identitas signal SCL antara Engineering dan FAT sementara bounded background refresh menjaga interaksi operator tetap responsif." }, "issuesUrl": "https://github.com/masarray/arsas/issues/new/choose", - "releaseUrl": "https://github.com/masarray/arsas/releases/tag/v1.6.31" + "releaseUrl": "https://github.com/masarray/arsas/releases/tag/v1.6.33" } diff --git a/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs b/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs index a46d26aea..e3a505ee1 100644 --- a/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatDirectSclLatencyAndIdentityRegressionTests.cs @@ -111,11 +111,11 @@ public void StructuredStaticMember_KeepsDedicatedIdentityAndPublishesResolvedThd Assert.Equal(runtimeLeaf, membershipSignal.ObjectReference); Assert.Equal(dataSet, membershipSignal.DataSetReference); - // P5.4 field regression: the generic curated-HMI THD row may remain classified as - // harmonic/statistics noise, but an explicit static DataSet member that ARIEC has - // resolved to one scalar leaf is authoritative FAT scope and must cross the exact - // SignalDefinition -> monitor-point runtime gate. - Assert.False(genericScalar.CanPublishToRuntime); + // A checked Engineering THD phase magnitude and the explicit static DataSet + // membership row must both cross the SignalDefinition -> monitor-point runtime + // gate. Harmonic spectra/configuration stay filtered; this exact FLOAT32 process + // value is the operator-visible live measurement. + Assert.True(genericScalar.CanPublishToRuntime); Assert.True(membershipSignal.IsExplicitDataSetRuntimeValue); Assert.True(membershipSignal.CanPublishAsSignal); Assert.True(membershipSignal.CanPublishToRuntime); @@ -132,7 +132,9 @@ public void StructuredStaticMember_KeepsDedicatedIdentityAndPublishesResolvedThd Source = "ARIEC static DataSet parent" }; Assert.False(structuredParent.IsExplicitDataSetRuntimeValue); - Assert.False(structuredParent.CanPublishToRuntime); + // The object-level DataSet row is also a live composite value (phsA/B/C). + // It remains distinct from a scalar member but must not stay Unknown in FAT. + Assert.True(structuredParent.CanPublishToRuntime); var point = new IoTestPointPlan { diff --git a/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs b/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs index 34cdaba6c..f4ad51fec 100644 --- a/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatP53StructuredAnalogBindingRegressionTests.cs @@ -32,6 +32,7 @@ public void MandatoryInventory_PreservesStaticPhaseIdentityAndUsesResolvedMagnit Assert.Equal(staticMember, signal.DisplayReference); Assert.Equal(runtimeLeaf, signal.ObjectReference); Assert.Equal("MX", signal.FunctionalConstraint); + Assert.True(signal.CanPublishAsSignal); Assert.Contains("mandatory static DataSet member", signal.Source, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("primary leaf unresolved", signal.Source, StringComparison.OrdinalIgnoreCase); } diff --git a/tests/ARSAS.Tests/IoFatReportPreviewServiceTests.cs b/tests/ARSAS.Tests/IoFatReportPreviewServiceTests.cs index d50239453..047813963 100644 --- a/tests/ARSAS.Tests/IoFatReportPreviewServiceTests.cs +++ b/tests/ARSAS.Tests/IoFatReportPreviewServiceTests.cs @@ -160,15 +160,15 @@ public void PreviewKeepsProgressOnIedCardOnly() } [Fact] - public void IoFatPreparation_UsesCachedReconnect_AndReturnsOnFirstUsableLiveImage() + public void IoFatPreparation_UsesCachedReconnect_AndReturnsImmediatelyAfterMonitorStart() { var source = File.ReadAllText(FindRepoFile("MainWindow.IoTesting.AutoConnect.cs")); Assert.Contains("Fast reconnect", source, StringComparison.Ordinal); Assert.Contains("ConnectUsingSavedModelAsync", source, StringComparison.Ordinal); - Assert.Contains("SettleIoFatReportPriorityAsync", source, StringComparison.Ordinal); - Assert.Contains("Waiting for first live FAT image", source, StringComparison.Ordinal); - Assert.Contains("TimeSpan.FromMilliseconds(2500)", source, StringComparison.Ordinal); + Assert.Contains("Return control to FAT immediately", source, StringComparison.Ordinal); + Assert.DoesNotContain("SettleIoFatReportPriorityAsync", source, StringComparison.Ordinal); + Assert.DoesNotContain("TimeSpan.FromMilliseconds(2500)", source, StringComparison.Ordinal); Assert.Contains("IsReportSource", source, StringComparison.Ordinal); Assert.DoesNotContain("rebuilding the report plan once", source, StringComparison.Ordinal); Assert.DoesNotContain("TimeSpan.FromSeconds(8)", source, StringComparison.Ordinal); diff --git a/tests/ARSAS.Tests/IoFatSclProjectImportTests.cs b/tests/ARSAS.Tests/IoFatSclProjectImportTests.cs index d29aeebeb..caacf3059 100644 --- a/tests/ARSAS.Tests/IoFatSclProjectImportTests.cs +++ b/tests/ARSAS.Tests/IoFatSclProjectImportTests.cs @@ -148,6 +148,26 @@ public async Task MultiScl_ImportOrderDoesNotChangeIdentity_AndP3PersistenceKeep } } + [Fact] + public async Task IncrementalImport_PreservesExistingRuntimeWorkspaceAndAddsNextIed() + { + var root = TempDirectory(); + var first = Path.Combine(root, "relay-first.cid"); + var second = Path.Combine(root, "relay-next.cid"); + BuildSinglePointFixture("IED-FIRST", "S1", "ADD", "GGIO", "1", "Dig01", "ST", "BOOLEAN").Save(first); + BuildSinglePointFixture("IED-NEXT", "S1", "MEAS", "MMXU", "1", "Ana01", "MX", "FLOAT32").Save(second); + var importer = new IoFatSclProjectImportService(); + + await importer.ImportAsync(new[] { first }); + var additional = await importer.ImportAdditionalAsync(new[] { second }); + + Assert.True(importer.TryGetRuntimeWorkspace("IED-FIRST", string.Empty, out var firstWorkspace)); + Assert.True(importer.TryGetRuntimeWorkspace("IED-NEXT", string.Empty, out var nextWorkspace)); + Assert.NotNull(firstWorkspace); + Assert.NotNull(nextWorkspace); + Assert.Equal("IED-NEXT", Assert.Single(additional.Project.Ieds).IedName); + } + [Fact] public async Task ProductionLoader_NoStaticDataSet_ReturnsZeroRowsAndExplicitFinding() { diff --git a/tests/ARSAS.Tests/IoFatV2WorkspaceRegressionTests.cs b/tests/ARSAS.Tests/IoFatV2WorkspaceRegressionTests.cs index 0f11d5619..3042e168a 100644 --- a/tests/ARSAS.Tests/IoFatV2WorkspaceRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatV2WorkspaceRegressionTests.cs @@ -62,6 +62,36 @@ public void OperatorSnapshot_JournalFailure_DoesNotPromoteCurrentEvidencePointer Assert.Equal(IoTestSessionState.Faulted, controller.State); } + [Fact] + public void OperatorSnapshot_LiveRefreshDoesNotRaiseSessionProgressForEverySample() + { + var fixture = ManualFixture(); + using var controller = fixture.Controller; + Assert.True(controller.Start(fixture.Ied).Succeeded); + + var sessionNotifications = 0; + controller.PropertyChanged += (_, _) => sessionNotifications++; + controller.Enqueue(new Iec61850EventEntry + { + Sequence = 2, + DeviceId = fixture.Device.DeviceId, + PointKey = fixture.LivePoint.PointKey, + DeviceTimestamp = "2026-09-01T03:00:00.250Z", + DeviceName = fixture.Device.Name, + IpAddress = fixture.Device.IpAddress, + SignalName = fixture.LivePoint.SignalName, + IecReference = fixture.LivePoint.IecReference, + OldValue = "12.34", + NewValue = "12.35", + Quality = "Good", + SourceMode = "BRCB", + Reason = "periodic-refresh" + }); + + Assert.Equal("12.35", fixture.Point.Runtime.CurrentValue); + Assert.Equal(0, sessionNotifications); + } + [Fact] public void RemoveRestore_PreservesCheckboxAndCurrentEvidence() { @@ -307,4 +337,4 @@ public IReadOnlyList AppendBatch(IEnumerable entries)", journal, StringComparison.Ordinal); Assert.Contains("FlushDurable();", journal, StringComparison.Ordinal); - // Preserve #150's immediate TRUE-edge scheduling while retaining P0's bounded - // drain/yield behavior for bursts. The first dispatch is DataBind; a reschedule - // requested while that drain is active is intentionally demoted to Background. + // Every bounded drain yields to operator input. Edge samples remain in the + // dedicated lossless queue, so responsiveness does not weaken FAT evidence. Assert.Contains("DispatchIoFatEvidence", main, StringComparison.Ordinal); - Assert.Contains("Volatile.Read(ref _ioFatEvidenceDrainDispatchActive) == 0", main, StringComparison.Ordinal); - Assert.Contains("DispatcherPriority.DataBind", main, StringComparison.Ordinal); Assert.Contains("DispatcherPriority.Background", main, StringComparison.Ordinal); - Assert.Contains("Interlocked.Increment(ref _ioFatEvidenceDrainDispatchActive)", main, StringComparison.Ordinal); - Assert.Contains("Interlocked.Decrement(ref _ioFatEvidenceDrainDispatchActive)", main, StringComparison.Ordinal); + Assert.DoesNotContain("DispatcherPriority.DataBind", main, StringComparison.Ordinal); + Assert.Contains("_pendingEdgeSnapshots", controller, StringComparison.Ordinal); } [Fact] @@ -78,6 +75,19 @@ public void NewCleanFatSession_ArchivesOldJournalsAndClearsEveryActiveTimestampS Assert.Contains("Session.ResetForCleanRetest()", ui, StringComparison.Ordinal); } + [Fact] + public void FatUiRefresh_DoesNotRefilterOrAutosaveForEveryLiveSample() + { + var ux = Read("IoListTestingWindow.FatV2Ux.cs"); + var persistence = Read("Services/IoTesting/IoTestProjectPersistenceService.cs"); + var controller = Read("Services/IoTesting/IoTestSessionController.cs"); + + Assert.Contains("RefreshFatV2WorkspaceUx(bool refreshRows = false)", ux, StringComparison.Ordinal); + Assert.Contains("if (refreshRows)", ux, StringComparison.Ordinal); + Assert.Contains("nameof(IoTestPointRuntime.CurrentValue)", persistence, StringComparison.Ordinal); + Assert.Contains("if (progressChanged)", controller, StringComparison.Ordinal); + } + private static string Read(string relativePath) => File.ReadAllText(FindRepoFile(relativePath)).Replace("\r\n", "\n", StringComparison.Ordinal); diff --git a/tests/ARSAS.Tests/IoTestingUiContractTests.cs b/tests/ARSAS.Tests/IoTestingUiContractTests.cs index c6d9a7a5c..bd9e3ca52 100644 --- a/tests/ARSAS.Tests/IoTestingUiContractTests.cs +++ b/tests/ARSAS.Tests/IoTestingUiContractTests.cs @@ -65,6 +65,38 @@ public void IoTestingWindow_ExposesCalmExportActionsWithoutPathNoise() .First(value => value == "{Binding Storage.SnapshotPath}")); } + [Fact] + public void IoTestingWindow_AllowsAddingAnIedDuringAnActiveEvidenceSession() + { + var document = XDocument.Load(FindRepoFile("IoListTestingWindow.xaml")); + XNamespace presentation = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; + var addButton = document + .Descendants(presentation + "Button") + .Single(button => (string?)button.Attribute("Click") == "AddFatIedFromScl_Click"); + + Assert.Equal("{Binding CanAddFatIed}", (string?)addButton.Attribute("IsEnabled")); + + var window = File.ReadAllText(FindRepoFile("IoListTestingWindow.xaml.cs")); + var addFlow = File.ReadAllText(FindRepoFile("IoListTestingWindow.AddIed.cs")); + Assert.Contains("public bool CanAddFatIed => !IsPreparingIed && !_isAddingFatIeds", window, StringComparison.Ordinal); + Assert.Contains("if (!CanAddFatIed", addFlow, StringComparison.Ordinal); + Assert.Contains("SetAddingFatIeds(true)", addFlow, StringComparison.Ordinal); + Assert.Contains("SetAddingFatIeds(false)", addFlow, StringComparison.Ordinal); + } + + [Fact] + public void WindowsExecutableAndProcessDeclareTheArsasTaskbarIconIdentity() + { + var project = File.ReadAllText(FindRepoFile("ArIED61850Tester.csproj")); + var app = File.ReadAllText(FindRepoFile("App.xaml.cs")); + var identity = File.ReadAllText(FindRepoFile("App.WindowsIdentity.cs")); + + Assert.Contains("Assets\\app-icon.ico", project, StringComparison.Ordinal); + Assert.Contains("WindowsApplicationIdentity.Apply();", app, StringComparison.Ordinal); + Assert.Contains("SetCurrentProcessExplicitAppUserModelID", identity, StringComparison.Ordinal); + Assert.True(File.Exists(FindRepoFile("Assets/app-icon.ico"))); + } + [Fact] public void IoTestingWindow_ConnectsWithoutLockingExplorerNavigationAndPreservesOperatorSelection() { @@ -136,9 +168,9 @@ public void IoFatAutomaticPreparation_UsesFastCommissioningAcquisitionWithoutPro Assert.Contains("ConnectAndConfigureDeviceAsync", source, StringComparison.Ordinal); Assert.Contains("StartDeviceMonitorAsync", source, StringComparison.Ordinal); Assert.Contains("deterministic fast MMS", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("first live FAT image", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("SettleIoFatReportPriorityAsync", source, StringComparison.Ordinal); - Assert.Contains("TimeSpan.FromMilliseconds(2500)", source, StringComparison.Ordinal); + Assert.Contains("Return control to FAT immediately", source, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("SettleIoFatReportPriorityAsync", source, StringComparison.Ordinal); + Assert.DoesNotContain("TimeSpan.FromMilliseconds(2500)", source, StringComparison.Ordinal); Assert.DoesNotContain("rebuilding the report plan once", source, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("TimeSpan.FromSeconds(8)", source, StringComparison.Ordinal); Assert.DoesNotContain("TimeSpan.FromSeconds(10)", source, StringComparison.Ordinal); @@ -301,4 +333,4 @@ private static string FindRepoFile(string relativePath) throw new FileNotFoundException( $"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); } -} \ No newline at end of file +} diff --git a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs new file mode 100644 index 000000000..4d199f7bf --- /dev/null +++ b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs @@ -0,0 +1,211 @@ +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services.IoTesting; + +namespace ARSAS.Tests; + +public sealed class ThdMeasurementAndWorkspaceBridgeRegressionTests +{ + [Theory] + [InlineData("IEDTHD/I_MHAI1.ThdA.phsA.cVal.mag.f")] + [InlineData("IEDTHD/I_MHAI1.ThdA.phsB.cVal.mag.f")] + [InlineData("IEDTHD/I_MHAI1.ThdA.phsC.cVal.mag.f")] + [InlineData("IEDTHD/V_MHAI1.ThdPPV.phsAB.cVal.mag.f")] + [InlineData("IEDTHD/V_MHAI1.ThdPPV.phsBC.cVal.mag.f")] + [InlineData("IEDTHD/V_MHAI1.ThdPPV.phsCA.cVal.mag.f")] + public void NumericThdPhaseMagnitude_IsPublishableToLiveRuntime(string reference) + { + var signal = Signal(reference, reference, "IEDTHD/LLN0.Analog"); + + Assert.False(signal.IsRawAttribute); + Assert.True(signal.IsValueSignal); + Assert.True(signal.CanPublishAsSignal); + Assert.True(signal.CanPublishToRuntime); + } + + [Theory] + [InlineData("IEDTHD/I_MHAI1.ThdA")] + [InlineData("IEDTHD/V_MHAI1.ThdPPV")] + public void ThreePhaseThdParent_IsPublishableAsCompositeLiveValue(string reference) + { + var signal = Signal(reference, reference, "IEDTHD/LLN0.Analog"); + + Assert.True(SignalDefinition.IsThreePhaseMeasurementAggregate(reference)); + Assert.False(signal.IsRawAttribute); + Assert.True(signal.CanPublishToRuntime); + } + + [Theory] + [InlineData("IEDTHD/I_MHAI1.ThdA.phsA.q")] + [InlineData("IEDTHD/V_MHAI1.ThdPPV.phsAB.t")] + public void ThdCompanion_RemainsNonPublishable(string reference) + { + var signal = Signal(reference, reference, "IEDTHD/LLN0.Analog"); + + Assert.False(signal.CanPublishAsSignal); + } + + [Theory] + [InlineData("IEDENERGY/XPRE_MMTR1.DmdWhMV.mag.f")] + [InlineData("IEDENERGY/XPRE_MMTR1.DmdWhMV.instMag.f")] + public void DemandEnergyMagnitude_IsNotDiscardedAsStatisticNoise(string reference) + { + var signal = Signal(reference, reference, "IEDENERGY/LLN0.Analog"); + + Assert.False(signal.IsRawAttribute); + Assert.True(signal.CanPublishToRuntime); + } + + [Fact] + public void ObjectLevelDemandEnergyDataSetMember_IsPublishableForScalarProjection() + { + const string reference = "IEDENERGY/XPRE_MMTR1.DmdWhMV"; + var signal = Signal(reference, reference, "IEDENERGY/LLN0.Analog"); + + Assert.True(SignalDefinition.IsDemandEnergyAggregate(reference)); + Assert.False(signal.IsRawAttribute); + Assert.True(signal.CanPublishToRuntime); + } + + [Fact] + public void FatFastPath_SkipsConnectedReconciliation_AndThdReportOverwrite() + { + var root = FindRepoRoot(); + var autoConnect = File.ReadAllText(Path.Combine(root, "MainWindow.IoTesting.AutoConnect.cs")); + var runtime = File.ReadAllText(Path.Combine(root, "Services", "Iec61850MonitorRuntime.cs")); + var native = File.ReadAllText(Path.Combine(root, "Services", "NativeIec61850Client.cs")); + + Assert.Contains("if (hasSclRuntimeAuthority)", autoConnect, StringComparison.Ordinal); + Assert.Contains("IoTestReconciliationCache.Invalidate(device)", autoConnect, StringComparison.Ordinal); + Assert.Contains("RequiresExactMmsValueAuthority(point.IecReference)", runtime, StringComparison.Ordinal); + Assert.Contains("projected-three-phase-aggregate", native, StringComparison.Ordinal); + Assert.Contains("projected-demand-energy-aggregate", native, StringComparison.Ordinal); + } + + [Fact] + public void FatWorkspace_ExposesIncrementalSclIedImport() + { + var root = FindRepoRoot(); + var xaml = File.ReadAllText(Path.Combine(root, "IoListTestingWindow.xaml")); + var importer = File.ReadAllText(Path.Combine(root, "Services", "IoTesting", "IoFatSclProjectImportService.cs")); + + Assert.Contains("AddFatIedFromScl_Click", xaml, StringComparison.Ordinal); + Assert.Contains("ImportAdditionalAsync", importer, StringComparison.Ordinal); + Assert.Contains("AddRuntimeWorkspaces", importer, StringComparison.Ordinal); + } + + [Fact] + public void ExistingEngineeringSelection_InitializesFat_AndThenSynchronizesBothWays() + { + const string dataSet = "IEDTHD/LLN0.Analog"; + const string member = "IEDTHD/I_MHAI1.ThdA.phsA"; + const string runtime = member + ".cVal.mag.f"; + var signal = Signal(runtime, member, dataSet); + var device = Device(signal); + var point = Point(member, runtime, dataSet); + var ied = Ied(point); + + IoFatEngineeringSelectionBridge.Initialize( + ied, + device, + preserveExistingEngineeringSelection: true); + Assert.False(point.TestEnabled); + + signal.IsSelected = true; + Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( + signal, + selected: true, + ied, + device)); + Assert.True(point.TestEnabled); + Assert.True(point.IsIncludedInFat); + + point.TestEnabled = false; + Assert.True(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); + Assert.False(signal.IsSelected); + } + + [Fact] + public void NewDirectSclFatWorkspace_SelectsTheSameCanonicalEngineeringRows() + { + const string dataSet = "IEDTHD/LLN0.Analog"; + const string member = "IEDTHD/V_MHAI1.ThdPPV.phsAB"; + const string runtime = member + ".cVal.mag.f"; + var canonical = Signal(runtime, member, dataSet); + var duplicatePresentationLeaf = Signal(runtime, runtime, dataSet); + var device = Device(canonical, duplicatePresentationLeaf); + var point = Point(member, runtime, dataSet); + + IoFatEngineeringSelectionBridge.Initialize( + Ied(point), + device, + preserveExistingEngineeringSelection: false); + + Assert.True(canonical.IsSelected); + Assert.False(duplicatePresentationLeaf.IsSelected); + Assert.Same(canonical, IoFatEngineeringSelectionBridge.FindSignal(point, device)); + } + + private static SignalDefinition Signal(string runtime, string display, string dataSet) => new() + { + Name = "THD", + ObjectReference = runtime, + DisplayReference = display, + FunctionalConstraint = "MX", + DataType = "FLOAT32", + Category = "DataSet", + DataSetReference = dataSet, + ProbeStatus = "Not probed", + Value = "-", + Quality = "Unknown" + }; + + private static Iec61850MonitorDevice Device(params SignalDefinition[] signals) + { + var device = new Iec61850MonitorDevice + { + Name = "IEDTHD", + SclIedName = "IEDTHD", + IpAddress = "192.0.2.20", + Port = 102 + }; + device.Signals.AddRange(signals); + return device; + } + + private static IoTestIedPlan Ied(IoTestPointPlan point) => new() + { + IedName = "IEDTHD", + IpAddress = "192.0.2.20", + TestPoints = [point] + }; + + private static IoTestPointPlan Point(string member, string runtime, string dataSet) => new() + { + TestPointId = "thd-phase", + IedName = "IEDTHD", + IpAddress = "192.0.2.20", + SignalName = "THD", + ObjectReference = runtime, + FunctionalConstraint = "MX", + ExpectedOnText = "Value 1", + ExpectedOffText = "Value 2", + DataSetName = dataSet, + SourceIecReference = member, + ReportDisplayReference = member, + EventLogSearchReference = member, + BindingStatus = IoTestSignalSelectionService.SclDataSetAuthorityBindingStatus, + ImportReady = true, + TestEnabled = true, + SignalKind = FatSignalKind.Analog, + CaptureMode = FatCaptureMode.OperatorSnapshot + }; + + private static string FindRepoRoot() + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory != null && !File.Exists(Path.Combine(directory.FullName, "ArIED61850Tester.csproj"))) + directory = directory.Parent; + return directory?.FullName ?? throw new DirectoryNotFoundException("Repository root not found."); + } +} From a1db1f0e4677dcf3943a830b0b9e42a28dbb4fae Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:21:02 +0700 Subject: [PATCH 018/112] Fix fresh SCL selection authority --- .../IoFatEngineeringSelectionBridge.cs | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs index 1b554657b..6fd9ecab4 100644 --- a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs +++ b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs @@ -18,30 +18,41 @@ public static int Initialize( ArgumentNullException.ThrowIfNull(ied); ArgumentNullException.ThrowIfNull(device); + // P0.1: raw SCL import is a fresh FAT selection authority. Every included static + // DataSet membership starts checked even when the same Engineering device already + // carries an older/narrower selection, or a matching local snapshot previously + // stored an unchecked state. Explicit ARSAS project/package continuation restores + // operator selection through the persistence path and does not call this initializer. + // + // The third parameter is intentionally retained for source compatibility with the + // existing synchronization caller. It no longer permits Engineering state to + // uncheck a newly imported direct-SCL FAT row. + _ = preserveExistingEngineeringSelection; + var changed = 0; foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsSclDataSetAuthority)) { + // Keep Removed Signals disposition independent. A row that is part of the active + // fresh-import scope defaults checked; restoring/removing a row remains an + // operator-authored FAT disposition action. + if (point.IsIncludedInFat && !point.TestEnabled) + { + point.TestEnabled = true; + changed++; + } + var signal = FindSignal(point, device); if (signal is null) continue; - if (preserveExistingEngineeringSelection) - { - var enabled = signal.IsSelected && point.IsIncludedInFat; - if (point.TestEnabled != enabled) - { - point.TestEnabled = enabled; - changed++; - } - } - else + // Fresh direct-SCL FAT owns the initial checkbox state. Push that state into the + // shared Engineering catalog instead of allowing an older Engineering selection + // to narrow the newly imported static DataSet scope. + var selected = point.TestEnabled && point.IsIncludedInFat; + if (signal.IsSelected != selected) { - var selected = point.TestEnabled && point.IsIncludedInFat; - if (signal.IsSelected != selected) - { - signal.IsSelected = selected; - changed++; - } + signal.IsSelected = selected; + changed++; } } From d31c0b9b6dde1aae743e8292f7b44c0a5a0f93c7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:21:21 +0700 Subject: [PATCH 019/112] Add fresh SCL selection authority regression coverage --- ...eshSclSelectionAuthorityRegressionTests.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs b/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs new file mode 100644 index 000000000..39160de33 --- /dev/null +++ b/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs @@ -0,0 +1,53 @@ +namespace ARSAS.Tests; + +public sealed class IoFatFreshSclSelectionAuthorityRegressionTests +{ + [Fact] + public void DirectSclImporter_DefaultsStaticDatasetRowsToChecked() + { + var source = Read("Services/IoTesting/IoFatSclProjectImportService.cs"); + + Assert.Contains("TestEnabled = true,", source, StringComparison.Ordinal); + Assert.Contains("ImportReady = true,", source, StringComparison.Ordinal); + Assert.Contains("BindingStatus = \"SCL_DATASET_AUTHORITY\"", source, StringComparison.Ordinal); + } + + [Fact] + public void FreshSclBridge_FatSelectionWinsOverOlderEngineeringSelection() + { + var source = Read("Services/IoTesting/IoFatEngineeringSelectionBridge.cs"); + + Assert.Contains("raw SCL import is a fresh FAT selection authority", source, StringComparison.Ordinal); + Assert.Contains("if (point.IsIncludedInFat && !point.TestEnabled)", source, StringComparison.Ordinal); + Assert.Contains("point.TestEnabled = true;", source, StringComparison.Ordinal); + Assert.Contains("var selected = point.TestEnabled && point.IsIncludedInFat;", source, StringComparison.Ordinal); + Assert.DoesNotContain("var enabled = signal.IsSelected && point.IsIncludedInFat;", source, StringComparison.Ordinal); + } + + [Fact] + public void FreshSclBridge_DoesNotMergeSelectionWithRemovedSignalsDisposition() + { + var source = Read("Services/IoTesting/IoFatEngineeringSelectionBridge.cs"); + + Assert.Contains("Keep Removed Signals disposition independent", source, StringComparison.Ordinal); + Assert.DoesNotContain("point.RestoreToFat();\n point.TestEnabled = true;", source, StringComparison.Ordinal); + } + + private static string Read(string relativePath) + => File.ReadAllText(FindRepoFile(relativePath)).Replace("\r\n", "\n", StringComparison.Ordinal); + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException( + $"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } +} From 2fb5b278609b2dd959cb701331878fd1db9c475c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:22:59 +0700 Subject: [PATCH 020/112] Add schema-safe aggregate value projector --- .../SchemaSafeAggregateProjectionService.cs | 404 ++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 Services/SchemaSafeAggregateProjectionService.cs diff --git a/Services/SchemaSafeAggregateProjectionService.cs b/Services/SchemaSafeAggregateProjectionService.cs new file mode 100644 index 000000000..356e302c9 --- /dev/null +++ b/Services/SchemaSafeAggregateProjectionService.cs @@ -0,0 +1,404 @@ +using System.Globalization; +using AR.Iec61850.Binding; +using AR.Iec61850.Discovery; +using ArMms = AR.Iec61850.Mms; + +namespace ArIED61850Tester.Services; + +/// +/// Projects explicitly structured FAT/Engineering measurement parents only after the +/// authoritative IEC 61850 schema has named every value that is published. This service +/// deliberately never chooses a child by raw MMS position or by "first numeric" shape. +/// +public static class SchemaSafeAggregateProjectionService +{ + public sealed record Projection( + object? Value, + string DisplayValue, + string Quality, + string DeviceTimestamp, + string ProjectionStatus); + + private sealed record BoundLeaf( + Iec61850BoundValueRow Row, + IReadOnlyList Ancestors, + double NumericValue); + + public static bool TryProject( + LiveIedModelDiscoveryDocument? authorityModel, + ArMms.MmsDataValue? value, + string requestedReference, + string readReference, + string dataType, + out Projection projection, + out string status) + { + projection = new Projection(null, "-", string.Empty, string.Empty, string.Empty); + status = string.Empty; + + if (authorityModel is null) + { + status = "Schema-safe aggregate projection blocked: no authoritative SCL/live IEC 61850 model is attached."; + return false; + } + + if (value is null || value.Kind is not (ArMms.MmsDataKind.Structure or ArMms.MmsDataKind.Array)) + { + status = "Schema-safe aggregate projection requires a structured MMS value."; + return false; + } + + if (!TryFindDataObject(authorityModel, requestedReference, out var dataObject)) + { + status = $"Schema-safe aggregate projection blocked: DataObject schema was not found for {requestedReference}."; + return false; + } + + var rootSchema = Iec61850DataObjectSchemaBuilder.FromLiveDataObject(dataObject).ToRootNode(); + var readSchema = TryFindSchemaNode(rootSchema, readReference, out var exactReadSchema) + ? exactReadSchema + : ReferencesEqual(readReference, dataObject.Reference) + ? rootSchema + : null; + if (readSchema is null) + { + status = $"Schema-safe aggregate projection blocked: read reference {readReference} is outside schema {dataObject.Reference}."; + return false; + } + + var binding = Iec61850ValueBindingEngine.Bind(readSchema, value); + if (binding.HasMismatch) + { + status = $"Schema-safe aggregate projection blocked by schema/value mismatch: {FormatDiagnostics(binding.Diagnostics)}"; + return false; + } + + if (IsThreePhaseThd(requestedReference, out var phases)) + return TryProjectThreePhase(binding.Root, requestedReference, readReference, phases, out projection, out status); + + if (IsDemandEnergy(requestedReference)) + return TryProjectDemandEnergy(binding.Root, requestedReference, readReference, dataType, out projection, out status); + + status = $"Schema-safe aggregate projection does not own {requestedReference}."; + return false; + } + + private static bool TryProjectThreePhase( + Iec61850BoundValueRow root, + string requestedReference, + string readReference, + IReadOnlyList<(string Path, string Label)> phases, + out Projection projection, + out string status) + { + projection = new Projection(null, "-", string.Empty, string.Empty, string.Empty); + var values = new List(phases.Count); + var leaves = new List(phases.Count); + + foreach (var phase in phases) + { + var prefix = NormalizeReference(requestedReference) + "." + phase.Path.ToLowerInvariant(); + var candidateTiers = new[] + { + new[] { prefix + ".cval.mag.f" }, + new[] { prefix + ".mag.f" }, + new[] { prefix + ".instcval.mag.f", prefix + ".instmag.f" } + }; + + if (!TryResolvePreferredNumericLeaf(root, candidateTiers, out var leaf, out var failure)) + { + status = $"Schema-safe THD projection blocked for {requestedReference}: phase {phase.Label} has no unique named magnitude leaf. {failure}"; + return false; + } + + leaves.Add(leaf); + values.Add($"{phase.Label}={leaf.NumericValue.ToString("0.######", CultureInfo.InvariantCulture)}"); + } + + // Every phase is resolved independently by exact IEC reference. A repeated row + // would mean the schema did not actually prove three distinct phase identities. + if (leaves.Select(leaf => NormalizeReference(leaf.Row.Reference)).Distinct(StringComparer.OrdinalIgnoreCase).Count() != phases.Count) + { + status = $"Schema-safe THD projection blocked for {requestedReference}: phase references are not unique."; + return false; + } + + var display = string.Join(", ", values); + projection = new Projection( + display, + display, + FirstUsefulAcrossLeaves(leaves, root, row => row.Quality), + FirstUsefulAcrossLeaves(leaves, root, row => row.Timestamp), + "schema-safe-three-phase-aggregate"); + status = $"Schema-safe THD aggregate projected from exact named leaves: {string.Join(", ", leaves.Select(leaf => leaf.Row.Reference))}."; + return true; + } + + private static bool TryProjectDemandEnergy( + Iec61850BoundValueRow root, + string requestedReference, + string readReference, + string dataType, + out Projection projection, + out string status) + { + projection = new Projection(null, "-", string.Empty, string.Empty, string.Empty); + var prefix = NormalizeReference(requestedReference); + + // DmdWhMV has existed in both canonical mag.f and vendor instantaneous instMag.f + // forms. Canonical engineering value wins when present. Instantaneous forms are + // accepted only when exactly one named fallback exists. No arbitrary numeric child + // is ever used. + var candidateTiers = new[] + { + new[] { prefix + ".mag.f" }, + new[] { prefix + ".cval.mag.f" }, + new[] { prefix + ".instmag.f", prefix + ".instcval.mag.f" } + }; + + if (!TryResolvePreferredNumericLeaf(root, candidateTiers, out var leaf, out var failure)) + { + status = $"Schema-safe DmdWh projection blocked for {requestedReference}: no unique named energy magnitude leaf. {failure}"; + return false; + } + + var numeric = leaf.NumericValue; + var display = numeric.ToString("0.######", CultureInfo.InvariantCulture); + projection = new Projection( + numeric, + display, + FirstUseful(leaf.Row.Quality, leaf.Ancestors.Reverse().Select(row => row.Quality), root.Quality), + FirstUseful(leaf.Row.Timestamp, leaf.Ancestors.Reverse().Select(row => row.Timestamp), root.Timestamp), + "schema-safe-demand-energy-aggregate"); + status = $"Schema-safe DmdWh aggregate projected from exact named leaf {leaf.Row.Reference}."; + return true; + } + + private static bool TryResolvePreferredNumericLeaf( + Iec61850BoundValueRow root, + IReadOnlyList candidateTiers, + out BoundLeaf leaf, + out string failure) + { + leaf = new BoundLeaf(new Iec61850BoundValueRow(), Array.Empty(), 0d); + failure = string.Empty; + + foreach (var tier in candidateTiers) + { + var matches = new List(); + foreach (var reference in tier) + { + if (!TryFindBoundRow(root, reference, out var row, out var ancestors)) + continue; + if (!TryParseNumeric(row.Value, out var numeric)) + { + failure = $"Named leaf {row.Reference} was bound but its value '{row.Value}' is not numeric."; + return false; + } + matches.Add(new BoundLeaf(row, ancestors, numeric)); + } + + if (matches.Count == 0) + continue; + if (matches.Count != 1) + { + failure = $"Named fallback tier is ambiguous: {string.Join(", ", matches.Select(match => match.Row.Reference))}."; + return false; + } + + leaf = matches[0]; + return true; + } + + failure = "The authoritative schema contains none of the approved exact magnitude references."; + return false; + } + + private static bool TryFindDataObject( + LiveIedModelDiscoveryDocument model, + string reference, + out LiveIedDataObjectModel dataObject) + { + dataObject = new LiveIedDataObjectModel(); + if (!TryGetDataObjectReference(reference, out var dataObjectReference)) + return false; + + var matches = model.LogicalDevices + .SelectMany(device => device.LogicalNodes) + .SelectMany(node => node.DataObjects) + .Where(candidate => ReferencesEqual(candidate.Reference, dataObjectReference)) + .ToArray(); + if (matches.Length != 1) + return false; + + dataObject = matches[0]; + return true; + } + + private static bool TryGetDataObjectReference(string reference, out string dataObjectReference) + { + dataObjectReference = string.Empty; + var text = (reference ?? string.Empty).Trim().Replace('$', '.'); + var slash = text.IndexOf('/'); + if (slash <= 0 || slash >= text.Length - 1) + return false; + + var domain = text[..slash]; + var segments = text[(slash + 1)..] + .Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (segments.Length < 2) + return false; + + dataObjectReference = $"{domain}/{segments[0]}.{segments[1]}"; + return true; + } + + private static bool TryFindSchemaNode( + Iec61850ValueSchemaNode node, + string reference, + out Iec61850ValueSchemaNode result) + { + if (ReferencesEqual(node.Reference, reference)) + { + result = node; + return true; + } + + foreach (var child in node.Children) + { + if (TryFindSchemaNode(child, reference, out result)) + return true; + } + + result = new Iec61850ValueSchemaNode(); + return false; + } + + private static bool TryFindBoundRow( + Iec61850BoundValueRow root, + string reference, + out Iec61850BoundValueRow row, + out IReadOnlyList ancestors) + { + var path = new List(); + return TryFindBoundRow(root, reference, path, out row, out ancestors); + } + + private static bool TryFindBoundRow( + Iec61850BoundValueRow current, + string reference, + List path, + out Iec61850BoundValueRow row, + out IReadOnlyList ancestors) + { + path.Add(current); + if (ReferencesEqual(current.Reference, reference)) + { + row = current; + ancestors = path.Take(path.Count - 1).ToArray(); + path.RemoveAt(path.Count - 1); + return true; + } + + foreach (var child in current.Children) + { + if (TryFindBoundRow(child, reference, path, out row, out ancestors)) + { + path.RemoveAt(path.Count - 1); + return true; + } + } + + path.RemoveAt(path.Count - 1); + row = new Iec61850BoundValueRow(); + ancestors = Array.Empty(); + return false; + } + + private static bool IsThreePhaseThd( + string reference, + out IReadOnlyList<(string Path, string Label)> phases) + { + var normalized = NormalizeReference(reference).TrimEnd('.'); + if (normalized.EndsWith(".thda", StringComparison.OrdinalIgnoreCase)) + { + phases = new[] + { + ("phsA", "A"), + ("phsB", "B"), + ("phsC", "C") + }; + return true; + } + + if (normalized.EndsWith(".thdppv", StringComparison.OrdinalIgnoreCase)) + { + phases = new[] + { + ("phsAB", "AB"), + ("phsBC", "BC"), + ("phsCA", "CA") + }; + return true; + } + + phases = Array.Empty<(string, string)>(); + return false; + } + + private static bool IsDemandEnergy(string reference) + => NormalizeReference(reference).TrimEnd('.').EndsWith(".dmdwhmv", StringComparison.OrdinalIgnoreCase); + + private static bool TryParseNumeric(string? value, out double numeric) + => double.TryParse( + (value ?? string.Empty).Trim(), + NumberStyles.Float | NumberStyles.AllowThousands, + CultureInfo.InvariantCulture, + out numeric); + + private static string FirstUsefulAcrossLeaves( + IReadOnlyList leaves, + Iec61850BoundValueRow root, + Func selector) + { + // Parent composite quality/timestamp is only advertised when the selected phase + // leaves agree. Mixed evidence must remain blank rather than implying one phase's + // metadata applies to the whole aggregate. + var useful = leaves + .Select(leaf => FirstUseful(selector(leaf.Row), leaf.Ancestors.Reverse().Select(selector), selector(root))) + .Where(IsUseful) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + return useful.Length == 1 ? useful[0] : string.Empty; + } + + private static string FirstUseful(string primary, IEnumerable inherited, string fallback) + { + if (IsUseful(primary)) + return primary; + foreach (var candidate in inherited) + { + if (IsUseful(candidate)) + return candidate; + } + return IsUseful(fallback) ? fallback : string.Empty; + } + + private static bool IsUseful(string? value) + => !string.IsNullOrWhiteSpace(value) && value != "-"; + + private static string FormatDiagnostics(IReadOnlyList diagnostics) + => diagnostics.Count == 0 + ? "no binding diagnostics" + : string.Join("; ", diagnostics.Take(4)); + + private static bool ReferencesEqual(string? left, string? right) + => NormalizeReference(left).Equals(NormalizeReference(right), StringComparison.OrdinalIgnoreCase); + + private static string NormalizeReference(string? reference) + => (reference ?? string.Empty) + .Trim() + .Replace('$', '.') + .Replace("..", ".") + .ToLowerInvariant(); +} From a30d71661cba70a551edbdc90ed7f190b2f38167 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:24:10 +0700 Subject: [PATCH 021/112] Align workspace bridge regression with fresh SCL authority --- ...rementAndWorkspaceBridgeRegressionTests.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs index 4d199f7bf..93c8e0d85 100644 --- a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs +++ b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs @@ -95,7 +95,7 @@ public void FatWorkspace_ExposesIncrementalSclIedImport() } [Fact] - public void ExistingEngineeringSelection_InitializesFat_AndThenSynchronizesBothWays() + public void FreshSclSelection_OverridesOlderEngineeringSelection_ThenSynchronizesBothWays() { const string dataSet = "IEDTHD/LLN0.Analog"; const string member = "IEDTHD/I_MHAI1.ThdA.phsA"; @@ -105,24 +105,31 @@ public void ExistingEngineeringSelection_InitializesFat_AndThenSynchronizesBothW var point = Point(member, runtime, dataSet); var ied = Ied(point); + // Simulate a stale persisted checkbox plus an older/narrower Engineering selection. + // Fresh raw SCL import must restore the included static DataSet member to checked and + // push that selection into Engineering instead of inheriting the older false state. + point.TestEnabled = false; IoFatEngineeringSelectionBridge.Initialize( ied, device, preserveExistingEngineeringSelection: true); - Assert.False(point.TestEnabled); + Assert.True(point.TestEnabled); + Assert.True(signal.IsSelected); - signal.IsSelected = true; + // After initialization the shared bridge remains bidirectional: Engineering changes + // still update FAT, and FAT checkbox changes still update the Engineering catalog. + signal.IsSelected = false; Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( signal, - selected: true, + selected: false, ied, device)); - Assert.True(point.TestEnabled); + Assert.False(point.TestEnabled); Assert.True(point.IsIncludedInFat); - point.TestEnabled = false; + point.TestEnabled = true; Assert.True(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); - Assert.False(signal.IsSelected); + Assert.True(signal.IsSelected); } [Fact] From 3e5c94670d0951dbf53371e83fb8f7ccd18f8263 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:28:25 +0700 Subject: [PATCH 022/112] Define schema-proven aggregate read plans --- .../SchemaSafeAggregateProjectionService.cs | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/Services/SchemaSafeAggregateProjectionService.cs b/Services/SchemaSafeAggregateProjectionService.cs index 356e302c9..fcc8817f6 100644 --- a/Services/SchemaSafeAggregateProjectionService.cs +++ b/Services/SchemaSafeAggregateProjectionService.cs @@ -19,11 +19,110 @@ public sealed record Projection( string DeviceTimestamp, string ProjectionStatus); + public sealed record ReadLeaf( + string Reference, + string Label, + string DataType); + + public sealed record ReadPlan( + string ParentReference, + string Kind, + IReadOnlyList Leaves); + private sealed record BoundLeaf( Iec61850BoundValueRow Row, IReadOnlyList Ancestors, double NumericValue); + /// + /// Builds the exact scalar-read plan used by the live FAT/Engineering runtime. + /// The plan is derived only from named attributes in the authoritative SCL/live model. + /// It never derives phase identity from MMS child position or numeric value ordering. + /// + public static bool TryBuildReadPlan( + LiveIedModelDiscoveryDocument? authorityModel, + string requestedReference, + out ReadPlan plan, + out string status) + { + plan = new ReadPlan(requestedReference ?? string.Empty, string.Empty, Array.Empty()); + status = string.Empty; + + if (authorityModel is null) + { + status = "Schema-safe aggregate read plan blocked: no authoritative SCL/live IEC 61850 model is attached."; + return false; + } + + if (!TryFindDataObject(authorityModel, requestedReference, out var dataObject)) + { + status = $"Schema-safe aggregate read plan blocked: DataObject schema was not found uniquely for {requestedReference}."; + return false; + } + + if (IsThreePhaseThd(requestedReference, out var phases)) + { + var leaves = new List(phases.Count); + foreach (var phase in phases) + { + var prefix = NormalizeReference(requestedReference) + "." + phase.Path.ToLowerInvariant(); + var tiers = new[] + { + new[] { prefix + ".cval.mag.f" }, + new[] { prefix + ".mag.f" }, + new[] { prefix + ".instcval.mag.f", prefix + ".instmag.f" } + }; + + if (!TryResolvePreferredAttributeReference(dataObject, tiers, out var reference, out var failure)) + { + status = $"Schema-safe THD read plan blocked for {requestedReference}: phase {phase.Label} has no unique named magnitude leaf. {failure}"; + return false; + } + + leaves.Add(new ReadLeaf(reference, phase.Label, "Float32")); + } + + if (leaves.Select(leaf => NormalizeReference(leaf.Reference)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Count() != phases.Count) + { + status = $"Schema-safe THD read plan blocked for {requestedReference}: phase leaves are not unique."; + return false; + } + + plan = new ReadPlan(requestedReference, "ThreePhaseThd", leaves); + status = $"Schema-safe THD read plan resolved exact leaves: {string.Join(", ", leaves.Select(leaf => leaf.Reference))}."; + return true; + } + + if (IsDemandEnergy(requestedReference)) + { + var prefix = NormalizeReference(requestedReference); + var tiers = new[] + { + new[] { prefix + ".mag.f" }, + new[] { prefix + ".cval.mag.f" }, + new[] { prefix + ".instmag.f", prefix + ".instcval.mag.f" } + }; + + if (!TryResolvePreferredAttributeReference(dataObject, tiers, out var reference, out var failure)) + { + status = $"Schema-safe DmdWh read plan blocked for {requestedReference}: no unique named energy magnitude leaf. {failure}"; + return false; + } + + plan = new ReadPlan( + requestedReference, + "DemandEnergy", + new[] { new ReadLeaf(reference, "Value", "Float32") }); + status = $"Schema-safe DmdWh read plan resolved exact leaf {reference}."; + return true; + } + + status = $"Schema-safe aggregate read planning does not own {requestedReference}."; + return false; + } + public static bool TryProject( LiveIedModelDiscoveryDocument? authorityModel, ArMms.MmsDataValue? value, @@ -174,6 +273,60 @@ private static bool TryProjectDemandEnergy( return true; } + private static bool TryResolvePreferredAttributeReference( + LiveIedDataObjectModel dataObject, + IReadOnlyList candidateTiers, + out string reference, + out string failure) + { + reference = string.Empty; + failure = string.Empty; + + foreach (var tier in candidateTiers) + { + var expected = tier.ToHashSet(StringComparer.OrdinalIgnoreCase); + var matches = dataObject.Attributes + .Select(attribute => new + { + Attribute = attribute, + EffectiveReference = EffectiveAttributeReference(dataObject, attribute) + }) + .Where(item => expected.Contains(NormalizeReference(item.EffectiveReference))) + .Where(item => string.IsNullOrWhiteSpace(item.Attribute.FunctionalConstraint) || + item.Attribute.FunctionalConstraint.Equals("MX", StringComparison.OrdinalIgnoreCase)) + .GroupBy(item => NormalizeReference(item.EffectiveReference), StringComparer.OrdinalIgnoreCase) + .Select(group => group.First().EffectiveReference) + .ToArray(); + + if (matches.Length == 0) + continue; + if (matches.Length != 1) + { + failure = $"Approved named fallback tier is ambiguous: {string.Join(", ", matches)}."; + return false; + } + + reference = matches[0]; + return true; + } + + failure = "The authoritative schema contains none of the approved exact magnitude references."; + return false; + } + + private static string EffectiveAttributeReference( + LiveIedDataObjectModel dataObject, + LiveIedDataAttributeModel attribute) + { + if (!string.IsNullOrWhiteSpace(attribute.ObjectReference)) + return attribute.ObjectReference.Trim().Replace('$', '.'); + + var path = (attribute.AttributePath ?? string.Empty).Trim().Replace('$', '.').Trim('.'); + return string.IsNullOrWhiteSpace(path) + ? string.Empty + : dataObject.Reference.Trim().Replace('$', '.').TrimEnd('.') + "." + path; + } + private static bool TryResolvePreferredNumericLeaf( Iec61850BoundValueRow root, IReadOnlyList candidateTiers, From dfca8f9a8bdf2f64734a32d3e359cff5ee2ddaae Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:28:35 +0700 Subject: [PATCH 023/112] Expose schema-safe aggregate read authority --- ...ativeIec61850Client.AggregateProjection.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Services/NativeIec61850Client.AggregateProjection.cs diff --git a/Services/NativeIec61850Client.AggregateProjection.cs b/Services/NativeIec61850Client.AggregateProjection.cs new file mode 100644 index 000000000..b0f13209a --- /dev/null +++ b/Services/NativeIec61850Client.AggregateProjection.cs @@ -0,0 +1,24 @@ +using AR.Iec61850.Discovery; + +namespace ArIED61850Tester.Services; + +public sealed partial class NativeIec61850Client +{ + /// + /// Returns the same per-IED model authority used by semantic report projection. + /// Direct-SCL FAT therefore uses the opened SCL design model; fully discovered + /// Engineering sessions can use the live model. No ambient/global model is consulted. + /// + internal LiveIedModelDiscoveryDocument? AggregateProjectionAuthorityModel + => _semanticReportProjectionAuthorityModel ?? _liveModel; + + internal bool TryBuildSchemaSafeAggregateReadPlan( + string requestedReference, + out SchemaSafeAggregateProjectionService.ReadPlan plan, + out string status) + => SchemaSafeAggregateProjectionService.TryBuildReadPlan( + AggregateProjectionAuthorityModel, + requestedReference, + out plan, + out status); +} From 738ce9a04fd56905d137abded068b7008d41ab5b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:29:16 +0700 Subject: [PATCH 024/112] Route aggregate FAT reads through exact schema leaves --- Services/IecSignalReadResolver.cs | 143 ++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/Services/IecSignalReadResolver.cs b/Services/IecSignalReadResolver.cs index 2f7cf41fb..86470e54d 100644 --- a/Services/IecSignalReadResolver.cs +++ b/Services/IecSignalReadResolver.cs @@ -1,3 +1,4 @@ +using System.Globalization; using ArIED61850Tester.Models; namespace ArIED61850Tester.Services; @@ -17,6 +18,13 @@ public static class IecSignalReadResolver SignalDefinition signal, CancellationToken cancellationToken) { + // Object-level THD and demand-energy members are structured values. They must + // never enter the generic parent-read path, because a raw MMS structure can only + // identify its children positionally. Build an exact named-leaf plan from the + // per-IED SCL/live schema and read those scalar leaves directly instead. + if (client is NativeIec61850Client native && IsSchemaSafeAggregate(signal.ObjectReference)) + return await ReadSchemaSafeAggregateAsync(native, signal, cancellationToken).ConfigureAwait(false); + var references = BuildReadCandidates(signal.ObjectReference).ToList(); Exception? firstFailure = null; @@ -48,6 +56,141 @@ public static class IecSignalReadResolver return null; } + private sealed record AggregateLeafRead( + SchemaSafeAggregateProjectionService.ReadLeaf Leaf, + double NumericValue, + string Quality, + string DeviceTimestamp); + + private static async Task ReadSchemaSafeAggregateAsync( + NativeIec61850Client client, + SignalDefinition signal, + CancellationToken cancellationToken) + { + // Failure to prove the schema is an intentional hard stop for this parent value. + // Do not fall through to NativeIec61850Client.ReadValueAsync(parent), where raw + // structure ordering could silently assign the wrong phase/value. + if (!client.TryBuildSchemaSafeAggregateReadPlan(signal.ObjectReference, out var plan, out _)) + return null; + + var reads = new List(plan.Leaves.Count); + foreach (var leaf in plan.Leaves) + { + cancellationToken.ThrowIfCancellationRequested(); + var value = await client.ReadValueAsync( + leaf.Reference, + signal.FunctionalConstraint, + leaf.DataType, + cancellationToken).ConfigureAwait(false); + if (value is null) + return null; + + var raw = Iec61850ReadValue.Unwrap(value); + if (!TryConvertNumeric(raw, out var numeric)) + return null; + + var rich = value as Iec61850ReadValue; + reads.Add(new AggregateLeafRead( + leaf, + numeric, + rich?.Quality ?? string.Empty, + rich?.DeviceTimestamp ?? string.Empty)); + } + + if (plan.Kind.Equals("ThreePhaseThd", StringComparison.OrdinalIgnoreCase)) + { + if (reads.Count != 3) + return null; + + var display = string.Join(", ", reads.Select(read => + $"{read.Leaf.Label}={read.NumericValue.ToString("0.######", CultureInfo.InvariantCulture)}")); + var rich = new Iec61850ReadValue + { + Value = display, + DisplayValue = display, + Quality = UnanimousUseful(reads.Select(read => read.Quality)), + DeviceTimestamp = UnanimousUseful(reads.Select(read => read.DeviceTimestamp)), + SourceReference = signal.ObjectReference, + ReadReference = string.Join(" | ", reads.Select(read => read.Leaf.Reference)), + Projection = "schema-safe-three-phase-exact-leaf-reads" + }; + return new ResolvedIecSignalRead(rich, signal.ObjectReference); + } + + if (plan.Kind.Equals("DemandEnergy", StringComparison.OrdinalIgnoreCase)) + { + var read = reads.Count == 1 ? reads[0] : null; + if (read is null) + return null; + + var display = read.NumericValue.ToString("0.######", CultureInfo.InvariantCulture); + var rich = new Iec61850ReadValue + { + Value = read.NumericValue, + DisplayValue = display, + Quality = read.Quality, + DeviceTimestamp = read.DeviceTimestamp, + SourceReference = signal.ObjectReference, + ReadReference = read.Leaf.Reference, + Projection = "schema-safe-demand-energy-exact-leaf-read" + }; + return new ResolvedIecSignalRead(rich, signal.ObjectReference); + } + + return null; + } + + private static bool IsSchemaSafeAggregate(string reference) + => SignalDefinition.IsThreePhaseMeasurementAggregate(reference) || + SignalDefinition.IsDemandEnergyAggregate(reference); + + private static bool TryConvertNumeric(object? value, out double numeric) + { + numeric = 0d; + if (value is null) + return false; + + try + { + switch (value) + { + case double d: + numeric = d; + return double.IsFinite(d); + case float f: + numeric = f; + return float.IsFinite(f); + case decimal m: + numeric = (double)m; + return double.IsFinite(numeric); + case byte or sbyte or short or ushort or int or uint or long or ulong: + numeric = Convert.ToDouble(value, CultureInfo.InvariantCulture); + return double.IsFinite(numeric); + } + } + catch (Exception ex) when (ex is InvalidCastException or FormatException or OverflowException) + { + return false; + } + + return double.TryParse( + Convert.ToString(value, CultureInfo.InvariantCulture), + NumberStyles.Float | NumberStyles.AllowThousands, + CultureInfo.InvariantCulture, + out numeric) && + double.IsFinite(numeric); + } + + private static string UnanimousUseful(IEnumerable values) + { + var useful = values + .Where(value => !string.IsNullOrWhiteSpace(value) && value != "-") + .Select(value => value.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + return useful.Length == 1 ? useful[0] : string.Empty; + } + public static bool ApplyEffectiveReference(SignalDefinition signal, string effectiveReference) { if (string.IsNullOrWhiteSpace(effectiveReference) || ReferencesEqual(signal.ObjectReference, effectiveReference)) From 6fd04b2cfaff6ecc8968f915cf4d224e2c463e9b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:30:05 +0700 Subject: [PATCH 025/112] Add schema-safe THD and demand aggregate regressions --- ...aSafeAggregateProjectionRegressionTests.cs | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 tests/ARSAS.Tests/SchemaSafeAggregateProjectionRegressionTests.cs diff --git a/tests/ARSAS.Tests/SchemaSafeAggregateProjectionRegressionTests.cs b/tests/ARSAS.Tests/SchemaSafeAggregateProjectionRegressionTests.cs new file mode 100644 index 000000000..5dda24381 --- /dev/null +++ b/tests/ARSAS.Tests/SchemaSafeAggregateProjectionRegressionTests.cs @@ -0,0 +1,255 @@ +using AR.Iec61850.Discovery; +using ArIED61850Tester.Services; + +namespace ARSAS.Tests; + +public sealed class SchemaSafeAggregateProjectionRegressionTests +{ + [Fact] + public void ThdA_ReadPlan_UsesExactNamedPhases_IndependentOfAttributeOrder() + { + const string parent = "IEDLD0/I_MHAI1.ThdA"; + var model = Model(DataObject( + parent, + "ThdA", + // Deliberately scrambled with unrelated numeric leaves first. + Attribute(parent + ".noise.f", "noise.f"), + Attribute(parent + ".phsC.cVal.mag.f", "phsC.cVal.mag.f"), + Attribute(parent + ".phsA.instCVal.mag.f", "phsA.instCVal.mag.f"), + Attribute(parent + ".phsB.cVal.mag.f", "phsB.cVal.mag.f"), + Attribute(parent + ".phsA.cVal.mag.f", "phsA.cVal.mag.f"), + Attribute(parent + ".phsC.instCVal.mag.f", "phsC.instCVal.mag.f"))); + + var ok = SchemaSafeAggregateProjectionService.TryBuildReadPlan( + model, + parent, + out var plan, + out var status); + + Assert.True(ok, status); + Assert.Equal("ThreePhaseThd", plan.Kind); + Assert.Equal(new[] { "A", "B", "C" }, plan.Leaves.Select(leaf => leaf.Label)); + Assert.Equal( + new[] + { + parent + ".phsA.cVal.mag.f", + parent + ".phsB.cVal.mag.f", + parent + ".phsC.cVal.mag.f" + }, + plan.Leaves.Select(leaf => leaf.Reference), + StringComparer.OrdinalIgnoreCase); + Assert.DoesNotContain(plan.Leaves, leaf => leaf.Reference.Contains("noise", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(plan.Leaves, leaf => leaf.Reference.Contains("instCVal", StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public void ThdPPV_ReadPlan_UsesExactAB_BC_CA_Identities() + { + const string parent = "IEDLD0/V_MHAI1.ThdPPV"; + var model = Model(DataObject( + parent, + "ThdPPV", + Attribute(parent + ".phsCA.cVal.mag.f", "phsCA.cVal.mag.f"), + Attribute(parent + ".phsAB.cVal.mag.f", "phsAB.cVal.mag.f"), + Attribute(parent + ".phsBC.cVal.mag.f", "phsBC.cVal.mag.f"))); + + Assert.True(SchemaSafeAggregateProjectionService.TryBuildReadPlan( + model, + parent, + out var plan, + out var status), status); + + Assert.Equal(new[] { "AB", "BC", "CA" }, plan.Leaves.Select(leaf => leaf.Label)); + Assert.Equal( + new[] + { + parent + ".phsAB.cVal.mag.f", + parent + ".phsBC.cVal.mag.f", + parent + ".phsCA.cVal.mag.f" + }, + plan.Leaves.Select(leaf => leaf.Reference), + StringComparer.OrdinalIgnoreCase); + } + + [Fact] + public void Thd_ReadPlan_MissingOneNamedPhase_FailsClosed() + { + const string parent = "IEDLD0/I_MHAI1.ThdA"; + var model = Model(DataObject( + parent, + "ThdA", + Attribute(parent + ".phsA.cVal.mag.f", "phsA.cVal.mag.f"), + Attribute(parent + ".phsC.cVal.mag.f", "phsC.cVal.mag.f"), + Attribute(parent + ".someOtherFloat.f", "someOtherFloat.f"))); + + var ok = SchemaSafeAggregateProjectionService.TryBuildReadPlan( + model, + parent, + out var plan, + out var status); + + Assert.False(ok); + Assert.Empty(plan.Leaves); + Assert.Contains("phase B", status, StringComparison.OrdinalIgnoreCase); + Assert.Contains("none of the approved exact magnitude references", status, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void DemandEnergy_ReadPlan_PrefersCanonicalMag_AndIgnoresOtherNumericLeaves() + { + const string parent = "IEDLD0/XPRE_MMTR1.DmdWhMV"; + var model = Model(DataObject( + parent, + "DmdWhMV", + Attribute(parent + ".noise.f", "noise.f"), + Attribute(parent + ".instMag.f", "instMag.f"), + Attribute(parent + ".mag.f", "mag.f"))); + + Assert.True(SchemaSafeAggregateProjectionService.TryBuildReadPlan( + model, + parent, + out var plan, + out var status), status); + + var leaf = Assert.Single(plan.Leaves); + Assert.Equal("DemandEnergy", plan.Kind); + Assert.Equal(parent + ".mag.f", leaf.Reference, ignoreCase: true); + } + + [Fact] + public void DemandEnergy_ReadPlan_UsesOneExactInstantFallback_WhenCanonicalIsAbsent() + { + const string parent = "IEDLD0/XPRE_MMTR1.DmdWhMV"; + var model = Model(DataObject( + parent, + "DmdWhMV", + Attribute(parent + ".noise.f", "noise.f"), + Attribute(parent + ".instMag.f", "instMag.f"))); + + Assert.True(SchemaSafeAggregateProjectionService.TryBuildReadPlan( + model, + parent, + out var plan, + out var status), status); + + Assert.Equal(parent + ".instMag.f", Assert.Single(plan.Leaves).Reference, ignoreCase: true); + } + + [Fact] + public void DemandEnergy_ReadPlan_AmbiguousInstantFallback_FailsClosed() + { + const string parent = "IEDLD0/XPRE_MMTR1.DmdWhMV"; + var model = Model(DataObject( + parent, + "DmdWhMV", + Attribute(parent + ".instMag.f", "instMag.f"), + Attribute(parent + ".instCVal.mag.f", "instCVal.mag.f"))); + + var ok = SchemaSafeAggregateProjectionService.TryBuildReadPlan( + model, + parent, + out _, + out var status); + + Assert.False(ok); + Assert.Contains("ambiguous", status, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void RuntimeResolver_InterceptsAggregateBeforeGenericParentRead() + { + var source = File.ReadAllText(FindRepoFile("Services/IecSignalReadResolver.cs")) + .Replace("\r\n", "\n", StringComparison.Ordinal); + var intercept = source.IndexOf("if (client is NativeIec61850Client native && IsSchemaSafeAggregate(signal.ObjectReference))", StringComparison.Ordinal); + var generic = source.IndexOf("var references = BuildReadCandidates(signal.ObjectReference).ToList();", StringComparison.Ordinal); + + Assert.True(intercept >= 0, "Aggregate values must have an explicit schema-safe runtime interception path."); + Assert.True(generic > intercept, "Aggregate interception must occur before generic parent/alternate read candidates are built."); + Assert.Contains("TryBuildSchemaSafeAggregateReadPlan", source, StringComparison.Ordinal); + Assert.Contains("schema-safe-three-phase-exact-leaf-reads", source, StringComparison.Ordinal); + Assert.Contains("schema-safe-demand-energy-exact-leaf-read", source, StringComparison.Ordinal); + + var helperStart = source.IndexOf("private static async Task ReadSchemaSafeAggregateAsync", StringComparison.Ordinal); + var helperEnd = source.IndexOf("private static bool IsSchemaSafeAggregate", helperStart, StringComparison.Ordinal); + Assert.True(helperStart >= 0 && helperEnd > helperStart); + var helper = source[helperStart..helperEnd]; + Assert.DoesNotContain("ReadValueAsync(signal.ObjectReference", helper, StringComparison.Ordinal); + Assert.Contains("ReadValueAsync(\n leaf.Reference", helper, StringComparison.Ordinal); + } + + [Fact] + public void SchemaSafeService_HasNoPositionalOrFirstNumericAggregateSelection() + { + var source = File.ReadAllText(FindRepoFile("Services/SchemaSafeAggregateProjectionService.cs")); + + Assert.DoesNotContain("Children.Take(3)", source, StringComparison.Ordinal); + Assert.DoesNotContain("FindFirstFloating", source, StringComparison.Ordinal); + Assert.DoesNotContain("FindFirstInteger", source, StringComparison.Ordinal); + Assert.Contains("TryResolvePreferredAttributeReference", source, StringComparison.Ordinal); + } + + private static LiveIedModelDiscoveryDocument Model(params LiveIedDataObjectModel[] objects) + => new() + { + Source = "SclWorkspace regression", + IedName = "IED", + LogicalDevices = + [ + new LiveIedLogicalDeviceModel + { + MmsDomain = "IEDLD0", + Inst = "LD0", + LogicalNodes = + [ + new LiveIedLogicalNodeModel + { + Name = "MHAI1", + LnClass = "MHAI", + LnInst = "1", + DataObjects = objects + } + ] + } + ] + }; + + private static LiveIedDataObjectModel DataObject( + string reference, + string name, + params LiveIedDataAttributeModel[] attributes) + => new() + { + Reference = reference, + Name = name, + InferredCdc = "MV", + Attributes = attributes + }; + + private static LiveIedDataAttributeModel Attribute(string reference, string path) + => new() + { + ObjectReference = reference, + AttributePath = path, + FunctionalConstraint = "MX", + MmsReference = reference.Replace('.', '$'), + SclBType = "FLOAT32", + MmsType = "floating-point", + Source = "SCL.DataTypeTemplates", + TypeSource = "SCL.DataTypeTemplates", + TypeConfidence = LiveIedDiscoveryConfidenceLevel.Exact + }; + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException(relativePath); + } +} From 3eef76abe69c38a76c4d3107ca073e82d031426b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:31:44 +0700 Subject: [PATCH 026/112] Fix Removed Signals restore authority --- Services/IoTesting/IoFatEngineeringSelectionBridge.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs index 6fd9ecab4..c37fa6ffd 100644 --- a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs +++ b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs @@ -92,17 +92,18 @@ public static bool ApplyEngineeringSignalSelection( ArgumentNullException.ThrowIfNull(ied); ArgumentNullException.ThrowIfNull(device); + // P0.2: Engineering selection shares the operator checkbox state, not FAT + // disposition authority. A row explicitly removed from FAT must stay in Removed + // Signals when Engineering is toggled off/on. Only the dedicated Removed Signals + // restore action may change ExcludedByOperator back to Included. This also means a + // later restore preserves the latest operator checkbox state without resurrecting + // evidence scope as a side effect of workspace navigation. var changed = false; foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsSclDataSetAuthority)) { if (!ReferenceEquals(FindSignal(point, device), signal)) continue; - if (selected && !point.IsIncludedInFat) - { - point.RestoreToFat(); - changed = true; - } if (point.TestEnabled != selected) { point.TestEnabled = selected; From 6c0d1c5eaabb5b3e1819202c2c3f36a7bd004575 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:32:11 +0700 Subject: [PATCH 027/112] Add Removed Signals authority regression --- ...rementAndWorkspaceBridgeRegressionTests.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs index 93c8e0d85..6288afe6e 100644 --- a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs +++ b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs @@ -132,6 +132,45 @@ public void FreshSclSelection_OverridesOlderEngineeringSelection_ThenSynchronize Assert.True(signal.IsSelected); } + [Fact] + public void RemovedSclFatRow_EngineeringSelectionChangesCheckboxButNeverRestoresDisposition() + { + const string dataSet = "IEDTHD/LLN0.Analog"; + const string member = "IEDTHD/I_MHAI1.ThdA.phsB"; + const string runtime = member + ".cVal.mag.f"; + var signal = Signal(runtime, member, dataSet); + signal.IsSelected = true; + var device = Device(signal); + var point = Point(member, runtime, dataSet); + var ied = Ied(point); + + point.RemoveFromFat(); + Assert.False(point.IsIncludedInFat); + Assert.True(point.TestEnabled); + + signal.IsSelected = false; + Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( + signal, + selected: false, + ied, + device)); + Assert.False(point.IsIncludedInFat); + Assert.False(point.TestEnabled); + + signal.IsSelected = true; + Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( + signal, + selected: true, + ied, + device)); + Assert.False(point.IsIncludedInFat); + Assert.True(point.TestEnabled); + + point.RestoreToFat(); + Assert.True(point.IsIncludedInFat); + Assert.True(point.TestEnabled); + } + [Fact] public void NewDirectSclFatWorkspace_SelectsTheSameCanonicalEngineeringRows() { From d4102a2739b6514ea890ec7884c9e576aac52798 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:32:44 +0700 Subject: [PATCH 028/112] Keep demand companion reads on proven scalar leaf --- Services/IecSignalReadResolver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Services/IecSignalReadResolver.cs b/Services/IecSignalReadResolver.cs index 86470e54d..8cd77a9c3 100644 --- a/Services/IecSignalReadResolver.cs +++ b/Services/IecSignalReadResolver.cs @@ -134,7 +134,11 @@ private sealed record AggregateLeafRead( ReadReference = read.Leaf.Reference, Projection = "schema-safe-demand-energy-exact-leaf-read" }; - return new ResolvedIecSignalRead(rich, signal.ObjectReference); + + // The runtime keeps the FAT identity on the parent point, but uses this exact + // effective leaf to derive low-rate q/t companion references. Returning the + // parent here would incorrectly derive XPRE_MMTR1.q instead of DmdWhMV.q. + return new ResolvedIecSignalRead(rich, read.Leaf.Reference); } return null; From f642c74e32bf12f691b069004e591bba9f63c997 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:33:36 +0700 Subject: [PATCH 029/112] Keep FAT disposition independent from shared selection --- Services/IoTesting/IoFatEngineeringSelectionBridge.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs index c37fa6ffd..1661d4fb4 100644 --- a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs +++ b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs @@ -73,7 +73,12 @@ public static bool ApplyFatPointSelection( if (signal is null) return false; - var selected = point.TestEnabled && point.IsIncludedInFat; + // P0.2: TestEnabled is the shared selection state. FAT disposition is deliberately + // orthogonal: Remove from FAT must not silently unselect the Engineering signal, and + // Restore must not silently reselect it. This preserves the checkbox exactly across + // Removed Signals while the dedicated restore action remains the only disposition + // authority. + var selected = point.TestEnabled; if (signal.IsSelected == selected) return false; From 2ae89867c1ad425bbdd4c2ec369ba68c4043698a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:33:52 +0700 Subject: [PATCH 030/112] Align THD fast-path regression with schema-safe parent reads --- ...dMeasurementAndWorkspaceBridgeRegressionTests.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs index 6288afe6e..d7f70c344 100644 --- a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs +++ b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs @@ -68,18 +68,23 @@ public void ObjectLevelDemandEnergyDataSetMember_IsPublishableForScalarProjectio } [Fact] - public void FatFastPath_SkipsConnectedReconciliation_AndThdReportOverwrite() + public void FatFastPath_SkipsConnectedReconciliation_AndUsesSchemaSafeParentReads() { var root = FindRepoRoot(); var autoConnect = File.ReadAllText(Path.Combine(root, "MainWindow.IoTesting.AutoConnect.cs")); var runtime = File.ReadAllText(Path.Combine(root, "Services", "Iec61850MonitorRuntime.cs")); - var native = File.ReadAllText(Path.Combine(root, "Services", "NativeIec61850Client.cs")); + var resolver = File.ReadAllText(Path.Combine(root, "Services", "IecSignalReadResolver.cs")); + var aggregate = File.ReadAllText(Path.Combine(root, "Services", "SchemaSafeAggregateProjectionService.cs")); Assert.Contains("if (hasSclRuntimeAuthority)", autoConnect, StringComparison.Ordinal); Assert.Contains("IoTestReconciliationCache.Invalidate(device)", autoConnect, StringComparison.Ordinal); Assert.Contains("RequiresExactMmsValueAuthority(point.IecReference)", runtime, StringComparison.Ordinal); - Assert.Contains("projected-three-phase-aggregate", native, StringComparison.Ordinal); - Assert.Contains("projected-demand-energy-aggregate", native, StringComparison.Ordinal); + Assert.Contains("ReadSchemaSafeAggregateAsync", resolver, StringComparison.Ordinal); + Assert.Contains("TryBuildSchemaSafeAggregateReadPlan", resolver, StringComparison.Ordinal); + Assert.Contains("schema-safe-three-phase-exact-leaf-reads", resolver, StringComparison.Ordinal); + Assert.Contains("schema-safe-demand-energy-exact-leaf-read", resolver, StringComparison.Ordinal); + Assert.Contains("TryResolvePreferredAttributeReference", aggregate, StringComparison.Ordinal); + Assert.DoesNotContain("Children.Take(3)", aggregate, StringComparison.Ordinal); } [Fact] From 277378e9b9331a03f766960a3544c837ee9bd125 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:35:34 +0700 Subject: [PATCH 031/112] Regress Removed Signals selection independence --- .../ThdMeasurementAndWorkspaceBridgeRegressionTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs index d7f70c344..c8deb469d 100644 --- a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs +++ b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs @@ -152,6 +152,8 @@ public void RemovedSclFatRow_EngineeringSelectionChangesCheckboxButNeverRestores point.RemoveFromFat(); Assert.False(point.IsIncludedInFat); Assert.True(point.TestEnabled); + Assert.False(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); + Assert.True(signal.IsSelected); signal.IsSelected = false; Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( @@ -174,6 +176,8 @@ public void RemovedSclFatRow_EngineeringSelectionChangesCheckboxButNeverRestores point.RestoreToFat(); Assert.True(point.IsIncludedInFat); Assert.True(point.TestEnabled); + Assert.False(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); + Assert.True(signal.IsSelected); } [Fact] From 7bc5e1b6a4498a5143a69f6179efb6931523f512 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:49:06 +0700 Subject: [PATCH 032/112] P0.3 make FAT plan locks per IED --- IoListTestingWindow.FatV2Ux.cs | 45 ++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/IoListTestingWindow.FatV2Ux.cs b/IoListTestingWindow.FatV2Ux.cs index b47a671c9..be9708132 100644 --- a/IoListTestingWindow.FatV2Ux.cs +++ b/IoListTestingWindow.FatV2Ux.cs @@ -15,6 +15,24 @@ public partial class IoListTestingWindow private bool _fatV2UxInstalled; private readonly BooleanToVisibilityConverter _fatBooleanVisibility = new(); + // P0.3: plan ownership is per IED. Another IED may be preparing or may own the + // single active evidence session without freezing the selected IED's checkbox scope. + // Only the IED whose connection/session is consuming that scope is immutable. + public bool SelectedCanEditPlan => SelectedIed is not null && CanEditIedPlan(SelectedIed); + + private bool CanEditIedPlan(IoTestIedPlan ied) + => !ied.IsPreparing && + (!Session.IsSessionActive || !ReferenceEquals(Session.ActiveIed, ied)); + + private bool CanEditPointPlan(IoTestPointPlan point) + { + var owner = Project.Ieds.FirstOrDefault(ied => ied.TestPoints.Contains(point)); + return owner is not null && CanEditIedPlan(owner); + } + + private bool CanRestoreAnyRemovedSignal => Project.Ieds.Any(ied => + CanEditIedPlan(ied) && ied.TestPoints.Any(point => !point.IsIncludedInFat)); + private void InstallFatV2WorkspaceUx() { if (!_fatV2UxInstalled) @@ -37,6 +55,11 @@ private void InstallFatV2WorkspaceUx() private void RefreshFatV2WorkspaceUx(bool refreshRows = false) { + // Selected/session/preparation notifications all converge here through the selected + // IED context refresh. Re-evaluate the per-IED edit contract without touching rows. + Raise(nameof(SelectedCanEditPlan)); + Raise(nameof(CanRestoreAnyRemovedSignal)); + if (_fatSignalsGrid != null) { var view = CollectionViewSource.GetDefaultView(_fatSignalsGrid.ItemsSource); @@ -55,7 +78,7 @@ private void RefreshFatV2WorkspaceUx(bool refreshRows = false) _removedSignalsButton.Content = Project.RemovedSignalCount == 0 ? "Removed Signals" : $"Removed Signals ({Project.RemovedSignalCount})"; - _removedSignalsButton.IsEnabled = CanEditPlan && Project.RemovedSignalCount > 0; + _removedSignalsButton.IsEnabled = CanRestoreAnyRemovedSignal && Project.RemovedSignalCount > 0; } Raise(nameof(ProjectSummary)); @@ -74,7 +97,7 @@ private void ConfigureFatV2Columns(DataGrid grid) Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); - enabledFactory.SetBinding(UIElement.IsEnabledProperty, new Binding("DataContext.CanEditPlan") + enabledFactory.SetBinding(UIElement.IsEnabledProperty, new Binding("DataContext.SelectedCanEditPlan") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGrid), 1) }); @@ -253,12 +276,12 @@ private void RemoveSelectedFromFat_Click(object sender, RoutedEventArgs e) { if (_fatSignalsGrid?.SelectedItem is not IoTestPointPlan point) return; - if (!CanEditPlan) + if (!CanEditPointPlan(point)) { MessageBox.Show( this, - "Stop the active FAT session or connection preparation before changing FAT scope.", - "FAT scope is locked", + "This IED's FAT scope is locked while its connection preparation or evidence session is active. Other IEDs remain editable.", + "IED FAT scope is locked", MessageBoxButton.OK, MessageBoxImage.Information); return; @@ -294,10 +317,18 @@ private void InstallRemovedSignalsButton() private void RemovedSignals_Click(object sender, RoutedEventArgs e) { - if (!CanEditPlan) + if (!CanRestoreAnyRemovedSignal) + { + MessageBox.Show( + this, + "Removed signals currently belong only to an IED whose connection preparation or FAT evidence session owns an immutable scope.", + "Removed Signals are locked", + MessageBoxButton.OK, + MessageBoxImage.Information); return; + } - var window = new RemovedFatSignalsWindow(Project) { Owner = this }; + var window = new RemovedFatSignalsWindow(Project, CanEditPointPlan) { Owner = this }; if (window.ShowDialog() != true || window.RestoredCount == 0) return; From 7c5cdc5a97c17b42baf1adc13f892907a7e58129 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:49:43 +0700 Subject: [PATCH 033/112] P0.3 lock Removed Signals per IED --- RemovedFatSignalsWindow.cs | 57 ++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/RemovedFatSignalsWindow.cs b/RemovedFatSignalsWindow.cs index 7f8e1bb13..da1035c8a 100644 --- a/RemovedFatSignalsWindow.cs +++ b/RemovedFatSignalsWindow.cs @@ -17,6 +17,13 @@ public sealed class RemovedFatSignalsWindow : Window private readonly TextBlock _summary; public RemovedFatSignalsWindow(IoTestProject project) + : this(project, canEditPoint: null) + { + } + + public RemovedFatSignalsWindow( + IoTestProject project, + Func? canEditPoint) { ArgumentNullException.ThrowIfNull(project); Title = "Removed Signals - ARSAS FAT"; @@ -35,7 +42,9 @@ public RemovedFatSignalsWindow(IoTestProject project) .OrderBy(point => point.IedName, StringComparer.OrdinalIgnoreCase) .ThenBy(point => point.DataSetName, StringComparer.OrdinalIgnoreCase) .ThenBy(point => point.SourceRow) - .Select(point => new RemovedSignalRow(point))); + .Select(point => new RemovedSignalRow( + point, + canEditPoint?.Invoke(point) ?? true))); _view = CollectionViewSource.GetDefaultView(_rows); _view.Filter = FilterRow; @@ -69,7 +78,7 @@ public RemovedFatSignalsWindow(IoTestProject project) }); headerText.Children.Add(new TextBlock { - Text = "Remove and restore never delete source identity or Value 1 / Value 2 evidence.", + Text = "Remove and restore never delete source identity or Value 1 / Value 2 evidence. Rows owned by an active IED workflow stay locked independently.", FontSize = 11.5, Foreground = new SolidColorBrush(Color.FromRgb(99, 116, 139)), Margin = new Thickness(0, 4, 0, 0) @@ -132,15 +141,22 @@ public RemovedFatSignalsWindow(IoTestProject project) HorizontalGridLinesBrush = new SolidColorBrush(Color.FromRgb(233, 238, 245)), EnableRowVirtualization = true }; - grid.Columns.Add(new DataGridCheckBoxColumn + + var selectionFactory = new FrameworkElementFactory(typeof(CheckBox)); + selectionFactory.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(RemovedSignalRow.IsSelected)) + { + Mode = BindingMode.TwoWay, + UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }); + selectionFactory.SetBinding(UIElement.IsEnabledProperty, new Binding(nameof(RemovedSignalRow.CanEdit))); + selectionFactory.SetBinding(FrameworkElement.ToolTipProperty, new Binding(nameof(RemovedSignalRow.EditToolTip))); + selectionFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center); + selectionFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center); + grid.Columns.Add(new DataGridTemplateColumn { Header = "SELECT", Width = 64, - Binding = new Binding(nameof(RemovedSignalRow.IsSelected)) - { - Mode = BindingMode.TwoWay, - UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged - } + CellTemplate = new DataTemplate { VisualTree = selectionFactory } }); grid.Columns.Add(TextColumn("IED", nameof(RemovedSignalRow.IedName), 130)); grid.Columns.Add(TextColumn("SIGNAL", nameof(RemovedSignalRow.SignalName), new DataGridLength(1.1, DataGridLengthUnitType.Star), 160)); @@ -201,20 +217,20 @@ private bool FilterRow(object item) private void SetVisibleSelection(bool selected) { - foreach (var item in _view.Cast()) + foreach (var item in _view.Cast().Where(row => row.CanEdit)) item.IsSelected = selected; _summary.Text = SummaryText(); } private void RestoreSelected_Click(object sender, RoutedEventArgs e) { - var selected = _rows.Where(row => row.IsSelected).ToArray(); + var selected = _rows.Where(row => row.CanEdit && row.IsSelected).ToArray(); if (selected.Length == 0) { MessageBox.Show( this, - "Select at least one removed signal to restore.", - "No signal selected", + "Select at least one editable removed signal to restore. Rows owned by an active IED connection/session remain locked.", + "No editable signal selected", MessageBoxButton.OK, MessageBoxImage.Information); return; @@ -228,7 +244,13 @@ private void RestoreSelected_Click(object sender, RoutedEventArgs e) } private string SummaryText() - => $"{_rows.Count} removed · {_rows.Count(row => row.IsSelected)} selected"; + { + var locked = _rows.Count(row => !row.CanEdit); + var selected = _rows.Count(row => row.CanEdit && row.IsSelected); + return locked == 0 + ? $"{_rows.Count} removed · {selected} selected" + : $"{_rows.Count} removed · {selected} selected · {locked} active-IED locked"; + } private static Button ActionButton(string content, bool primary) => new() @@ -264,12 +286,17 @@ private sealed class RemovedSignalRow : INotifyPropertyChanged { private bool _isSelected; - public RemovedSignalRow(IoTestPointPlan point) + public RemovedSignalRow(IoTestPointPlan point, bool canEdit) { Point = point; + CanEdit = canEdit; } public IoTestPointPlan Point { get; } + public bool CanEdit { get; } + public string EditToolTip => CanEdit + ? "Select this signal to restore it to FAT" + : "This IED's FAT scope is locked by its active connection preparation or evidence session"; public string IedName => Point.IedName; public string SignalName => Point.SignalName; public string DataSetName => Point.DataSetName; @@ -281,6 +308,8 @@ public bool IsSelected get => _isSelected; set { + if (!CanEdit && value) + return; if (_isSelected == value) return; _isSelected = value; From c5b438d9acfa6fb7ae07dfb6aad42a5207c305dd Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:50:02 +0700 Subject: [PATCH 034/112] Regress P0.3 per-IED FAT UI locks --- .../IoFatV2UiLockRegressionTests.cs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs index 5763bc463..85007d98a 100644 --- a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs @@ -3,17 +3,38 @@ namespace ARSAS.Tests; public sealed class IoFatV2UiLockRegressionTests { [Fact] - public void RuntimeGrid_LocksPlanMutationButKeepsOperatorCaptureAvailable() + public void RuntimeGrid_LocksOnlyTheIedThatOwnsPreparationOrEvidenceScope() { var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); - Assert.Contains("DataContext.CanEditPlan", source, StringComparison.Ordinal); + Assert.Contains("DataContext.SelectedCanEditPlan", source, StringComparison.Ordinal); + Assert.DoesNotContain("DataContext.CanEditPlan", source, StringComparison.Ordinal); + Assert.Contains("public bool SelectedCanEditPlan => SelectedIed is not null && CanEditIedPlan(SelectedIed);", source, StringComparison.Ordinal); + Assert.Contains("!ied.IsPreparing", source, StringComparison.Ordinal); + Assert.Contains("!Session.IsSessionActive || !ReferenceEquals(Session.ActiveIed, ied)", source, StringComparison.Ordinal); + Assert.Contains("if (!CanEditPointPlan(point))", source, StringComparison.Ordinal); Assert.Contains("point.RemoveFromFat();", source, StringComparison.Ordinal); - Assert.Contains("if (!CanEditPlan)", source, StringComparison.Ordinal); + + // Operator snapshot capture remains the intentional evidence action and is not + // disabled by the TEST-checkbox edit lock. Assert.Contains("CanCaptureOperatorSnapshot", source, StringComparison.Ordinal); Assert.Contains("Session.CaptureOperatorSnapshot(point, slot)", source, StringComparison.Ordinal); } + [Fact] + public void RemovedSignals_RestoreLockIsEvaluatedPerOwningIed() + { + var workspace = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); + var removed = File.ReadAllText(FindRepoFile("RemovedFatSignalsWindow.cs")); + + Assert.Contains("new RemovedFatSignalsWindow(Project, CanEditPointPlan)", workspace, StringComparison.Ordinal); + Assert.Contains("Func? canEditPoint", removed, StringComparison.Ordinal); + Assert.Contains("canEditPoint?.Invoke(point) ?? true", removed, StringComparison.Ordinal); + Assert.Contains("new Binding(nameof(RemovedSignalRow.CanEdit))", removed, StringComparison.Ordinal); + Assert.Contains("_view.Cast().Where(row => row.CanEdit)", removed, StringComparison.Ordinal); + Assert.Contains("_rows.Where(row => row.CanEdit && row.IsSelected)", removed, StringComparison.Ordinal); + } + private static string FindRepoFile(string relativePath) { DirectoryInfo? directory = new(AppContext.BaseDirectory); @@ -28,4 +49,4 @@ private static string FindRepoFile(string relativePath) throw new FileNotFoundException( $"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); } -} \ No newline at end of file +} From 9df05adaadabdd8d0d4f69915980314d690e5af2 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:52:13 +0700 Subject: [PATCH 035/112] P0.3 make checkbox lock synchronous per IED --- IoListTestingWindow.FatV2Ux.cs | 39 +++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/IoListTestingWindow.FatV2Ux.cs b/IoListTestingWindow.FatV2Ux.cs index be9708132..8a0d2b8d9 100644 --- a/IoListTestingWindow.FatV2Ux.cs +++ b/IoListTestingWindow.FatV2Ux.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -14,6 +15,7 @@ public partial class IoListTestingWindow private ICollectionView? _fatSignalsView; private bool _fatV2UxInstalled; private readonly BooleanToVisibilityConverter _fatBooleanVisibility = new(); + private readonly IoFatSelectedIedPlanEditabilityConverter _fatPlanEditabilityConverter = new(); // P0.3: plan ownership is per IED. Another IED may be preparing or may own the // single active evidence session without freezing the selected IED's checkbox scope. @@ -97,10 +99,28 @@ private void ConfigureFatV2Columns(DataGrid grid) Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); - enabledFactory.SetBinding(UIElement.IsEnabledProperty, new Binding("DataContext.SelectedCanEditPlan") + + // Bind directly to selected-IED/session/preparation sources so the active IED locks + // synchronously when Session.Start or IsPreparing changes. This avoids a dispatcher + // timing window where a stale global CanEditPlan value could accept one more click. + var editability = new MultiBinding { Converter = _fatPlanEditabilityConverter }; + editability.Bindings.Add(new Binding("DataContext.SelectedIed") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGrid), 1) }); + editability.Bindings.Add(new Binding("DataContext.Session.ActiveIed") + { + RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGrid), 1) + }); + editability.Bindings.Add(new Binding("DataContext.Session.IsSessionActive") + { + RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGrid), 1) + }); + editability.Bindings.Add(new Binding("DataContext.SelectedIed.IsPreparing") + { + RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGrid), 1) + }); + enabledFactory.SetBinding(UIElement.IsEnabledProperty, editability); enabledFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center); enabledFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center); grid.Columns.Add(new DataGridTemplateColumn @@ -389,3 +409,20 @@ private static IEnumerable FindVisualDescendants(DependencyObject root) wh return null; } } + +public sealed class IoFatSelectedIedPlanEditabilityConverter : IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length < 4 || values[0] is not IoTestIedPlan selectedIed) + return false; + + var activeIed = values[1] as IoTestIedPlan; + var sessionActive = values[2] is bool active && active; + var selectedPreparing = values[3] is bool preparing && preparing; + return !selectedPreparing && (!sessionActive || !ReferenceEquals(activeIed, selectedIed)); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + => targetTypes.Select(_ => Binding.DoNothing).ToArray(); +} From a35ba65916a7ea47ad1c3c49930af1d0f351f992 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:52:36 +0700 Subject: [PATCH 036/112] Regress synchronous per-IED checkbox locking --- tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs index 85007d98a..99fcf8546 100644 --- a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs @@ -7,11 +7,22 @@ public void RuntimeGrid_LocksOnlyTheIedThatOwnsPreparationOrEvidenceScope() { var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); - Assert.Contains("DataContext.SelectedCanEditPlan", source, StringComparison.Ordinal); Assert.DoesNotContain("DataContext.CanEditPlan", source, StringComparison.Ordinal); Assert.Contains("public bool SelectedCanEditPlan => SelectedIed is not null && CanEditIedPlan(SelectedIed);", source, StringComparison.Ordinal); Assert.Contains("!ied.IsPreparing", source, StringComparison.Ordinal); Assert.Contains("!Session.IsSessionActive || !ReferenceEquals(Session.ActiveIed, ied)", source, StringComparison.Ordinal); + + // The TEST checkbox binds directly to the selected IED and session owner so the + // owning IED locks synchronously; another IED remains editable without waiting for + // a dispatcher-level window-property refresh. + Assert.Contains("var editability = new MultiBinding", source, StringComparison.Ordinal); + Assert.Contains("DataContext.SelectedIed", source, StringComparison.Ordinal); + Assert.Contains("DataContext.Session.ActiveIed", source, StringComparison.Ordinal); + Assert.Contains("DataContext.Session.IsSessionActive", source, StringComparison.Ordinal); + Assert.Contains("DataContext.SelectedIed.IsPreparing", source, StringComparison.Ordinal); + Assert.Contains("IoFatSelectedIedPlanEditabilityConverter", source, StringComparison.Ordinal); + Assert.Contains("!ReferenceEquals(activeIed, selectedIed)", source, StringComparison.Ordinal); + Assert.Contains("if (!CanEditPointPlan(point))", source, StringComparison.Ordinal); Assert.Contains("point.RemoveFromFat();", source, StringComparison.Ordinal); From e3f97054ce1a71e1f847f85db26010524a24af7d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 15:53:22 +0700 Subject: [PATCH 037/112] Exercise P0.3 per-IED editability behavior --- .../IoFatV2UiLockRegressionTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs index 99fcf8546..8f1e33d89 100644 --- a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs @@ -1,3 +1,6 @@ +using System.Globalization; +using ArIED61850Tester.Models.IoTesting; + namespace ARSAS.Tests; public sealed class IoFatV2UiLockRegressionTests @@ -32,6 +35,26 @@ public void RuntimeGrid_LocksOnlyTheIedThatOwnsPreparationOrEvidenceScope() Assert.Contains("Session.CaptureOperatorSnapshot(point, slot)", source, StringComparison.Ordinal); } + [Fact] + public void EditabilityConverter_AllowsOtherIedWhileActiveOwnerRemainsLocked() + { + var activeIed = new IoTestIedPlan { IedName = "IED-A", IpAddress = "192.0.2.10" }; + var otherIed = new IoTestIedPlan { IedName = "IED-B", IpAddress = "192.0.2.11" }; + var converter = new ArIED61850Tester.IoFatSelectedIedPlanEditabilityConverter(); + + bool Evaluate(IoTestIedPlan selected, IoTestIedPlan? active, bool sessionActive, bool preparing) + => Assert.IsType(converter.Convert( + new object[] { selected, active!, sessionActive, preparing }, + typeof(bool), + null!, + CultureInfo.InvariantCulture)); + + Assert.False(Evaluate(activeIed, activeIed, sessionActive: true, preparing: false)); + Assert.True(Evaluate(otherIed, activeIed, sessionActive: true, preparing: false)); + Assert.False(Evaluate(otherIed, activeIed, sessionActive: true, preparing: true)); + Assert.True(Evaluate(activeIed, active: null, sessionActive: false, preparing: false)); + } + [Fact] public void RemovedSignals_RestoreLockIsEvaluatedPerOwningIed() { From a6a48ba3b4187b709e7423acfd0407bc3b2d73e5 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:11:51 +0700 Subject: [PATCH 038/112] Add P0.4 loaded FAT SCL append transaction --- MainWindow.IoTesting.SclAppend.cs | 138 ++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 MainWindow.IoTesting.SclAppend.cs diff --git a/MainWindow.IoTesting.SclAppend.cs b/MainWindow.IoTesting.SclAppend.cs new file mode 100644 index 000000000..30a3fd388 --- /dev/null +++ b/MainWindow.IoTesting.SclAppend.cs @@ -0,0 +1,138 @@ +using System.IO; +using System.Threading; +using System.Windows; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services.IoTesting; +using Microsoft.Win32; + +namespace ArIED61850Tester; + +public partial class MainWindow +{ + // P0.4 owns only the short append transaction. SCL parsing happens before this gate, + // and independent IED connection/evidence workflows do not acquire it. + private readonly SemaphoreSlim _ioFatSclAppendGate = new(1, 1); + + internal async Task OpenSclForLoadedFatAppendAsync(IoListTestingWindow window) + { + ArgumentNullException.ThrowIfNull(window); + if (!ReferenceEquals(window, _loadedIoFatWindow) || !window.IsLoaded) + return; + + var dialog = new OpenFileDialog + { + Title = "Add IEC 61850 SCL to loaded FAT workspace", + Filter = "IEC 61850 SCL (*.scd;*.cid;*.icd;*.iid;*.ssd)|*.scd;*.cid;*.icd;*.iid;*.ssd|XML SCL (*.xml)|*.xml|All files (*.*)|*.*", + CheckFileExists = true, + Multiselect = true + }; + if (dialog.ShowDialog(this) != true) + return; + + var result = await window.ImportAdditionalSclSourcesAsync(this, dialog.FileNames); + if (!result.Succeeded) + { + MessageBox.Show( + this, + result.Message, + "SCL could not be added to FAT", + MessageBoxButton.OK, + MessageBoxImage.Warning); + } + } + + internal async Task AppendSclIedsToLoadedFatAsync( + IoListTestingWindow window, + IReadOnlyCollection sclPaths) + { + ArgumentNullException.ThrowIfNull(window); + ArgumentNullException.ThrowIfNull(sclPaths); + if (!ReferenceEquals(window, _loadedIoFatWindow) || !window.IsLoaded) + return IoTestSessionActionResult.Failure("The target FAT workspace is no longer active."); + + var requestedPaths = sclPaths + .Where(path => !string.IsNullOrWhiteSpace(path)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (requestedPaths.Length == 0) + return IoTestSessionActionResult.Failure("Select at least one SCL source to add to FAT."); + + // Parse and validate without taking the workspace mutation gate. Existing IEDs may + // keep connecting/monitoring, and the single active evidence session keeps its + // immutable capture scope while ARIEC builds the new source workspaces. + SetStatus($"Parsing {requestedPaths.Length} additional SCL source(s) · existing FAT IED workflows remain active…"); + var import = await _ioFatSclProjectImportService.ImportAdditionalAsync( + requestedPaths, + _applicationCancellation.Token); + + await _ioFatSclAppendGate.WaitAsync(_applicationCancellation.Token); + try + { + // Revalidate after the asynchronous parse. The operator may have closed or + // replaced the workspace while the files were being inspected. + if (!ReferenceEquals(window, _loadedIoFatWindow) || !window.IsLoaded) + return IoTestSessionActionResult.Failure("The target FAT workspace changed before the SCL append could be committed."); + + var existingSources = window.Project.Sources + .ToDictionary(source => source.Sha256, StringComparer.OrdinalIgnoreCase); + var uniqueImportedSources = import.Sources + .Where(source => !existingSources.ContainsKey(source.Sha256)) + .ToArray(); + + var existingIedKeys = window.Project.Ieds + .Select(ied => $"{ied.IedName.Trim()}|{ied.IpAddress.Trim()}") + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var addedIeds = import.Project.Ieds + .Where(ied => existingIedKeys.Add($"{ied.IedName.Trim()}|{ied.IpAddress.Trim()}")) + .ToArray(); + if (addedIeds.Length == 0) + { + return IoTestSessionActionResult.Failure( + "The selected SCL source contains no new IED endpoint; every imported IED is already present in this FAT workspace."); + } + + // Persist source bytes before publishing the new plans to the active project. + // This gate serializes only append operations; it does not lock any IED's + // connection preparation or evidence journal. + if (window.Storage != null && uniqueImportedSources.Length > 0) + { + await window.Storage.AddSourcesAsync( + import.Project, + import.SourceInputs, + _applicationCancellation.Token); + } + + foreach (var ied in addedIeds) + window.Project.Ieds.Add(ied); + + var allSources = window.Project.Sources + .Concat(uniqueImportedSources) + .GroupBy(source => source.Sha256, StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .OrderBy(source => source.SourceId, StringComparer.Ordinal) + .ToArray(); + window.Project.SetSources(allSources, IoFatSourceIdentity.ComputeSetFingerprint(allSources)); + + foreach (var point in addedIeds.SelectMany(ied => ied.TestPoints)) + point.PropertyChanged += IoFatSelectionPoint_PropertyChanged; + + // Existing running/preparing IEDs are deliberately untouched. Only the newly + // appended endpoints join Engineering and the loaded FAT explorer. + SynchronizeImportedSclFatWithEngineering(window.Project, addedIeds); + window.RegisterAddedIeds(addedIeds); + window.Storage?.ScheduleSave(); + + var warningCount = import.Findings.Count(finding => + finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase) || + finding.Severity.Equals("Error", StringComparison.OrdinalIgnoreCase)); + var message = $"Added {addedIeds.Length} IED(s) and {addedIeds.Sum(ied => ied.TestPoints.Count)} static DataSet point(s) to the loaded FAT workspace" + + (warningCount == 0 ? "." : $" · {warningCount} import warning(s)."); + SetStatus(message); + return IoTestSessionActionResult.Success(message); + } + finally + { + _ioFatSclAppendGate.Release(); + } + } +} From 9316fb8c472da50c0970a158bbacdac6eb4f6909 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:12:08 +0700 Subject: [PATCH 039/112] Route FAT Add IED through append gate --- IoListTestingWindow.AddIed.cs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/IoListTestingWindow.AddIed.cs b/IoListTestingWindow.AddIed.cs index 39b1b32c7..0cd03d9b8 100644 --- a/IoListTestingWindow.AddIed.cs +++ b/IoListTestingWindow.AddIed.cs @@ -1,5 +1,6 @@ using System.IO; using System.Windows; +using ArIED61850Tester.Services.IoTesting; using Microsoft.Win32; namespace ArIED61850Tester; @@ -21,19 +22,38 @@ private async void AddFatIedFromScl_Click(object sender, RoutedEventArgs e) if (dialog.ShowDialog(this) != true) return; + var result = await ImportAdditionalSclSourcesAsync(engineeringWindow, dialog.FileNames); + if (!result.Succeeded) + ShowActionResult(result, "IED could not be added"); + } + + internal async Task ImportAdditionalSclSourcesAsync( + MainWindow engineeringWindow, + IReadOnlyCollection sclPaths) + { + ArgumentNullException.ThrowIfNull(engineeringWindow); + ArgumentNullException.ThrowIfNull(sclPaths); + if (_isAddingFatIeds) + return IoTestSessionActionResult.Failure("Another SCL append is already being prepared for this FAT workspace."); + SetAddingFatIeds(true); - PreparationStatusText = $"Importing {dialog.FileNames.Length} SCL source(s) into the current FAT workspace…"; + PreparationStatusText = $"Importing {sclPaths.Count} SCL source(s) into the current FAT workspace…"; try { - var result = await engineeringWindow.AddSclIedsToLoadedFatAsync(this, dialog.FileNames); + var result = await engineeringWindow.AppendSclIedsToLoadedFatAsync(this, sclPaths); PreparationStatusText = result.Message; - if (!result.Succeeded) - ShowActionResult(result, "IED could not be added"); + return result; + } + catch (OperationCanceledException) + { + const string message = "SCL append was cancelled."; + PreparationStatusText = message; + return IoTestSessionActionResult.Failure(message); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidDataException or InvalidOperationException or ArgumentException) { PreparationStatusText = ex.Message; - MessageBox.Show(this, ex.Message, "Add FAT IED failed", MessageBoxButton.OK, MessageBoxImage.Error); + return IoTestSessionActionResult.Failure(ex.Message); } finally { From c716573ce7acbbb9db34c0d735d2864fa0bd11f6 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:12:31 +0700 Subject: [PATCH 040/112] Make loaded FAT SCL import additive by default --- MainWindow.WorkspaceModeSwitch.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/MainWindow.WorkspaceModeSwitch.cs b/MainWindow.WorkspaceModeSwitch.cs index 059b1bcd8..e3977efbf 100644 --- a/MainWindow.WorkspaceModeSwitch.cs +++ b/MainWindow.WorkspaceModeSwitch.cs @@ -191,12 +191,23 @@ private void OpenIoFatWorkspaceMenu(Button anchor) var importScl = new MenuItem { - Header = _loadedIoFatWindow == null - ? "Import SCL / CID files" - : "Import another SCL / CID file set" + Header = _loadedIoFatWindow is { IsLoaded: true } + ? "Add SCL / CID to loaded FAT workspace" + : "Import SCL / CID files" + }; + importScl.Click += (_, _) => + { + if (_loadedIoFatWindow is { IsLoaded: true } loaded) + { + // P0.4: SCL is additive while a FAT workspace is loaded. Existing IED + // connections/session evidence stay alive; replacement is reserved for + // explicit workbook/project open flows below. + _ = OpenSclForLoadedFatAppendAsync(loaded); + return; + } + + OpenSclFatTesting_Click(this, new RoutedEventArgs()); }; - importScl.Click += (_, _) => QueueIoFatWorkspaceReplacement( - () => OpenSclFatTesting_Click(this, new RoutedEventArgs())); var importWorkbook = new MenuItem { From d17851ad8588121b6c08983732315de7ec56651a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:13:29 +0700 Subject: [PATCH 041/112] Allow SCL append during independent IED workflows --- IoListTestingWindow.xaml.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IoListTestingWindow.xaml.cs b/IoListTestingWindow.xaml.cs index eb519aef7..cece4e155 100644 --- a/IoListTestingWindow.xaml.cs +++ b/IoListTestingWindow.xaml.cs @@ -84,10 +84,10 @@ private set public bool CanEditPlan => !IsPreparingIed && Session.CanEditPlan; - // Importing another SCL source does not mutate the capture scope owned by the - // active evidence session. Keep it available while FAT is running, but serialize - // it with connection preparation so model imports cannot race endpoint setup. - public bool CanAddFatIed => !IsPreparingIed && !_isAddingFatIeds; + // P0.4: adding another SCL is a workspace-level append operation, independent of + // per-IED connection preparation and the single active evidence session. Only another + // SCL append locks this action; existing IED workflows continue untouched. + public bool CanAddFatIed => !_isAddingFatIeds; private void SetAddingFatIeds(bool value) { From cf817b7cc45a119a61c93631e05bd661211a2be1 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:13:43 +0700 Subject: [PATCH 042/112] Add P0.4 loaded FAT SCL append regression --- .../IoFatSclAppendWorkflowRegressionTests.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatSclAppendWorkflowRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatSclAppendWorkflowRegressionTests.cs b/tests/ARSAS.Tests/IoFatSclAppendWorkflowRegressionTests.cs new file mode 100644 index 000000000..9c2cb74d7 --- /dev/null +++ b/tests/ARSAS.Tests/IoFatSclAppendWorkflowRegressionTests.cs @@ -0,0 +1,68 @@ +namespace ARSAS.Tests; + +public sealed class IoFatSclAppendWorkflowRegressionTests +{ + [Fact] + public void P04_LoadedFatRoutesSclToAppendInsteadOfWorkspaceReplacement() + { + var modeSwitch = ReadRepoFile("MainWindow.WorkspaceModeSwitch.cs"); + + Assert.Contains("Add SCL / CID to loaded FAT workspace", modeSwitch, StringComparison.Ordinal); + Assert.Contains("OpenSclForLoadedFatAppendAsync(loaded)", modeSwitch, StringComparison.Ordinal); + Assert.DoesNotContain( + "QueueIoFatWorkspaceReplacement(\n () => OpenSclFatTesting_Click", + modeSwitch, + StringComparison.Ordinal); + + // Workbook and portable project opens intentionally retain replacement semantics. + Assert.Contains("() => OpenIoListTesting_Click(this, new RoutedEventArgs())", modeSwitch, StringComparison.Ordinal); + Assert.Contains("() => OpenIoListPackage_Click(this, new RoutedEventArgs())", modeSwitch, StringComparison.Ordinal); + } + + [Fact] + public void P04_AddSclAvailabilityIsIndependentFromIedPreparationAndEvidenceSession() + { + var window = ReadRepoFile("IoListTestingWindow.xaml.cs"); + var addIed = ReadRepoFile("IoListTestingWindow.AddIed.cs"); + + Assert.Contains("public bool CanAddFatIed => !_isAddingFatIeds;", window, StringComparison.Ordinal); + Assert.DoesNotContain("public bool CanAddFatIed => !IsPreparingIed", window, StringComparison.Ordinal); + Assert.Contains("ImportAdditionalSclSourcesAsync", addIed, StringComparison.Ordinal); + Assert.Contains("AppendSclIedsToLoadedFatAsync(this, sclPaths)", addIed, StringComparison.Ordinal); + Assert.DoesNotContain("AddSclIedsToLoadedFatAsync(this, dialog.FileNames)", addIed, StringComparison.Ordinal); + } + + [Fact] + public void P04_ParsesBeforeAppendGateAndMutatesOnlyNewIeds() + { + var append = ReadRepoFile("MainWindow.IoTesting.SclAppend.cs"); + + Assert.Contains("SemaphoreSlim _ioFatSclAppendGate", append, StringComparison.Ordinal); + var parseIndex = append.IndexOf("ImportAdditionalAsync(", StringComparison.Ordinal); + var gateIndex = append.IndexOf("_ioFatSclAppendGate.WaitAsync", StringComparison.Ordinal); + Assert.True(parseIndex >= 0 && gateIndex > parseIndex, "SCL parsing must complete before the append mutation gate is acquired."); + + Assert.Contains("ReferenceEquals(window, _loadedIoFatWindow)", append, StringComparison.Ordinal); + Assert.Contains("existingIedKeys.Add", append, StringComparison.Ordinal); + Assert.Contains("window.Project.Ieds.Add(ied)", append, StringComparison.Ordinal); + Assert.Contains("SynchronizeImportedSclFatWithEngineering(window.Project, addedIeds)", append, StringComparison.Ordinal); + Assert.Contains("window.RegisterAddedIeds(addedIeds)", append, StringComparison.Ordinal); + Assert.DoesNotContain("IsPreparingIed", append, StringComparison.Ordinal); + Assert.DoesNotContain("Session.Stop", append, StringComparison.Ordinal); + } + + private static string ReadRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return File.ReadAllText(candidate).Replace("\r\n", "\n", StringComparison.Ordinal); + directory = directory.Parent; + } + + throw new FileNotFoundException( + $"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } +} From 91a19f08ce6805df504de3dc0d93cb5ccc7ccdc3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:15:38 +0700 Subject: [PATCH 043/112] Fix P0.4 append result namespace --- IoListTestingWindow.AddIed.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/IoListTestingWindow.AddIed.cs b/IoListTestingWindow.AddIed.cs index 0cd03d9b8..2e17069e7 100644 --- a/IoListTestingWindow.AddIed.cs +++ b/IoListTestingWindow.AddIed.cs @@ -1,5 +1,6 @@ using System.IO; using System.Windows; +using ArIED61850Tester.Models.IoTesting; using ArIED61850Tester.Services.IoTesting; using Microsoft.Win32; From 35ddc2211292563ad04d1dac71851adc8ff0a61d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:18:02 +0700 Subject: [PATCH 044/112] Align workspace mode regression with P0.4 append semantics --- tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs index 9a9cfff10..7c8cb7c90 100644 --- a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs +++ b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs @@ -14,7 +14,8 @@ public void MainWindow_AlwaysExposesEngineeringAndPersistentIoFatWorkspaceModes( Assert.Contains("ShowLoadedIoFatWorkspace", source, StringComparison.Ordinal); Assert.Contains("Continue loaded FAT project", source, StringComparison.Ordinal); Assert.Contains("Import SCL / CID files", source, StringComparison.Ordinal); - Assert.Contains("Import another SCL / CID file set", source, StringComparison.Ordinal); + Assert.Contains("Add SCL / CID to loaded FAT workspace", source, StringComparison.Ordinal); + Assert.Contains("OpenSclForLoadedFatAppendAsync(loaded)", source, StringComparison.Ordinal); Assert.Contains("OpenSclFatTesting_Click", source, StringComparison.Ordinal); Assert.Contains("Import another IO List Excel workbook", source, StringComparison.Ordinal); Assert.Contains("Open another portable .arsas project", source, StringComparison.Ordinal); @@ -44,7 +45,7 @@ public void FatWorkspace_ReturnsToEngineeringByHideAndShowWithoutUnloadingProjec } [Fact] - public void LoadingAnotherFatProject_IsExplicitAndClosesTheLoadedWorkspaceFirst() + public void LoadingAnotherFatProject_IsExplicitWhileLoadedSclImportIsAdditive() { var source = File.ReadAllText(FindRepoFile("MainWindow.WorkspaceModeSwitch.cs")); var hostSource = File.ReadAllText(FindRepoFile("MainWindow.IoTesting.cs")); @@ -52,7 +53,13 @@ public void LoadingAnotherFatProject_IsExplicitAndClosesTheLoadedWorkspaceFirst( Assert.Contains("Save and close that workspace before loading another project?", source, StringComparison.Ordinal); Assert.Contains("loaded.Close();", source, StringComparison.Ordinal); Assert.Contains("Dispatcher.BeginInvoke(openReplacement", source, StringComparison.Ordinal); - Assert.Contains("OpenSclFatTesting_Click(this, new RoutedEventArgs())", source, StringComparison.Ordinal); + + // P0.4 keeps SCL additive while workbook/project opens remain explicit replacement. + Assert.Contains("OpenSclForLoadedFatAppendAsync(loaded)", source, StringComparison.Ordinal); + Assert.DoesNotContain( + "QueueIoFatWorkspaceReplacement(\n () => OpenSclFatTesting_Click", + source, + StringComparison.Ordinal); Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenIoListTesting_Click", hostSource, StringComparison.Ordinal); Assert.Contains("QueueIoFatWorkspaceReplacement(() => OpenIoListPackage_Click", hostSource, StringComparison.Ordinal); } From 60a1732e1201fb81560e37ad4513cd6df13d9779 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:18:41 +0700 Subject: [PATCH 045/112] Align IO testing add-IED contract with P0.4 --- tests/ARSAS.Tests/IoTestingUiContractTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/ARSAS.Tests/IoTestingUiContractTests.cs b/tests/ARSAS.Tests/IoTestingUiContractTests.cs index bd9e3ca52..1ad83cc40 100644 --- a/tests/ARSAS.Tests/IoTestingUiContractTests.cs +++ b/tests/ARSAS.Tests/IoTestingUiContractTests.cs @@ -66,7 +66,7 @@ public void IoTestingWindow_ExposesCalmExportActionsWithoutPathNoise() } [Fact] - public void IoTestingWindow_AllowsAddingAnIedDuringAnActiveEvidenceSession() + public void IoTestingWindow_AllowsAddingAnIedDuringIndependentIedWorkflows() { var document = XDocument.Load(FindRepoFile("IoListTestingWindow.xaml")); XNamespace presentation = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; @@ -78,10 +78,12 @@ public void IoTestingWindow_AllowsAddingAnIedDuringAnActiveEvidenceSession() var window = File.ReadAllText(FindRepoFile("IoListTestingWindow.xaml.cs")); var addFlow = File.ReadAllText(FindRepoFile("IoListTestingWindow.AddIed.cs")); - Assert.Contains("public bool CanAddFatIed => !IsPreparingIed && !_isAddingFatIeds", window, StringComparison.Ordinal); + Assert.Contains("public bool CanAddFatIed => !_isAddingFatIeds", window, StringComparison.Ordinal); + Assert.DoesNotContain("CanAddFatIed => !IsPreparingIed", window, StringComparison.Ordinal); Assert.Contains("if (!CanAddFatIed", addFlow, StringComparison.Ordinal); Assert.Contains("SetAddingFatIeds(true)", addFlow, StringComparison.Ordinal); Assert.Contains("SetAddingFatIeds(false)", addFlow, StringComparison.Ordinal); + Assert.Contains("AppendSclIedsToLoadedFatAsync(this, sclPaths)", addFlow, StringComparison.Ordinal); } [Fact] From 0f73368d9ebc4c3cfd3205ab4fa359915fb90561 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:30:44 +0700 Subject: [PATCH 046/112] Decouple IED connect from FAT checkbox scope --- IoListTestingWindow.ContextUx.cs | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/IoListTestingWindow.ContextUx.cs b/IoListTestingWindow.ContextUx.cs index 33afde115..196f2ca10 100644 --- a/IoListTestingWindow.ContextUx.cs +++ b/IoListTestingWindow.ContextUx.cs @@ -169,19 +169,10 @@ private async void ConnectIed_Click(object sender, RoutedEventArgs e) if (targetIed == null || targetIed.IsPreparing) return; - var enabledReady = targetIed.TestPoints - .Where(point => point.IsIncludedInFat && point.TestEnabled && point.ImportReady) - .ToList(); - if (enabledReady.Count == 0) - { - ShowActionResult( - IoTestSessionActionResult.Failure("No import-ready operator-selected signal is enabled in the active FAT scope for this IED."), - "IED connection scope is not ready"); - return; - } - - IReadOnlyCollection connectionScope = enabledReady; - + // P1: Connect owns endpoint/MMS/live-acquisition state, never FAT evidence + // selection. The preparation path receives no checkbox scope here and therefore + // acquires every included import-ready static member for this IED. TEST remains + // authoritative only when Start FAT builds its explicit captureScope below. if (ReferenceEquals(SelectedIed, targetIed)) PreparationStatusText = $"Connecting {targetIed.IedName} · {targetIed.IpAddress}:102"; RaisePreparationProperties(); @@ -203,8 +194,7 @@ private async void ConnectIed_Click(object sender, RoutedEventArgs e) var preparation = await PrepareIndependentIedConnectionAsync( engineeringWindow, targetIed, - progress, - connectionScope); + progress); RaiseStatusProperties(); RaiseSelectedIedContextProperties(); @@ -348,13 +338,11 @@ private async void StartSelectedIedSafely_Click(object sender, RoutedEventArgs e private Task PrepareIndependentIedConnectionAsync( MainWindow engineeringWindow, IoTestIedPlan targetIed, - IProgress progress, - IReadOnlyCollection connectionScope) + IProgress progress) => engineeringWindow.PrepareIoTestIedForFatAsync( Project, targetIed, - progress, - connectionScope); + progress); private void ContextWindow_PropertyChanged(object? sender, PropertyChangedEventArgs e) { From 5ac2e3b362fb6748e688147efd44be0980f45346 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:31:03 +0700 Subject: [PATCH 047/112] Add FAT acquisition scope independent of TEST selection --- MainWindow.IoTesting.MultiIedMonitor.cs | 92 +++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 MainWindow.IoTesting.MultiIedMonitor.cs diff --git a/MainWindow.IoTesting.MultiIedMonitor.cs b/MainWindow.IoTesting.MultiIedMonitor.cs new file mode 100644 index 000000000..02fe91895 --- /dev/null +++ b/MainWindow.IoTesting.MultiIedMonitor.cs @@ -0,0 +1,92 @@ +using ArIED61850Tester.Models; + +namespace ArIED61850Tester; + +public partial class MainWindow +{ + /// + /// Starts one IED's FAT monitor from an explicit acquisition scope without changing + /// the operator's Engineering/TEST selection. Iec61850MonitorRuntime snapshots the + /// selected signal set when StartMonitoringAsync is called, so P1 temporarily arms + /// only the acquisition signals under the device's bulk-selection guard and restores + /// the operator selection immediately after the runtime point definitions are built. + /// + private async Task StartIoFatDeviceMonitorAsync( + Iec61850MonitorDevice device, + IReadOnlyCollection acquisitionSignals) + { + ArgumentNullException.ThrowIfNull(device); + ArgumentNullException.ThrowIfNull(acquisitionSignals); + + var acquisition = acquisitionSignals + .Where(signal => signal.CanPublishToRuntime) + .GroupBy( + signal => IoTestLiveBindingService.NormalizeReference(signal.ObjectReference), + StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .ToArray(); + if (acquisition.Length == 0) + { + SetStatus($"{device.Name}: no readable FAT acquisition point is available."); + return false; + } + + var acquisitionSet = acquisition.ToHashSet(); + var originalSelection = device.Signals + .ToDictionary(signal => signal, signal => signal.IsSelected); + + device.BeginBulkSignalSelection(); + try + { + // Runtime acquisition and operator selection are intentionally different + // authorities in P1. The bulk guard prevents Signal_PropertyChanged from + // reflecting these temporary acquisition flags back into TEST checkboxes. + foreach (var signal in device.Signals) + signal.IsSelected = acquisitionSet.Contains(signal); + + device.HasReportStream = false; + device.ReportPulseActive = false; + _reportPulseUntil.Remove(device.DeviceId); + RemoveDeviceHighlights(device.DeviceId); + RemoveDevicePoints(device.DeviceId); + device.Points.Clear(); + + SetStatus($"{device.Name}: starting independent FAT live acquisition…"); + var points = await _runtime.StartMonitoringAsync( + device, + acquisition, + PollingIntervalMs, + _applicationCancellation.Token); + + device.Points.AddRange(points); + GlobalPoints.AddRange(points); + RebuildControlFeedbackIndex(device); + foreach (var point in points) + _pointIndex[point.PointKey] = point; + device.RefreshComputed(); + RaiseWorkspaceCounts(); + SetStatus($"{device.Name}: FAT monitoring {points.Count} point(s). {device.AcquisitionMode}"); + return true; + } + catch (OperationCanceledException) + { + SetStatus($"{device.Name}: FAT monitor start cancelled."); + return false; + } + catch (Exception ex) + { + AddLog("ERROR", device.Name, $"FAT monitor start failed: {ex.Message}"); + SetStatus($"{device.Name}: FAT monitor start failed. Diagnostics is marked with !."); + MarkDiagnosticAlert(); + return false; + } + finally + { + foreach (var (signal, wasSelected) in originalSelection) + signal.IsSelected = wasSelected; + device.EndBulkSignalSelection(); + device.RefreshComputed(); + RaiseWorkspaceCounts(); + } + } +} From 49229c6b2548127a240423bb84bb9510dfa7876c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:31:22 +0700 Subject: [PATCH 048/112] Fix FAT monitor helper namespace --- MainWindow.IoTesting.MultiIedMonitor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MainWindow.IoTesting.MultiIedMonitor.cs b/MainWindow.IoTesting.MultiIedMonitor.cs index 02fe91895..c6b405911 100644 --- a/MainWindow.IoTesting.MultiIedMonitor.cs +++ b/MainWindow.IoTesting.MultiIedMonitor.cs @@ -1,4 +1,5 @@ using ArIED61850Tester.Models; +using ArIED61850Tester.Services.IoTesting; namespace ArIED61850Tester; From f22648ded1e4f18b2b6ffd73e70f09c62bd23c59 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:32:37 +0700 Subject: [PATCH 049/112] Harden independent multi-IED FAT acquisition --- MainWindow.IoTesting.AutoConnect.cs | 78 +++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/MainWindow.IoTesting.AutoConnect.cs b/MainWindow.IoTesting.AutoConnect.cs index a9cd539a5..3d3241cfa 100644 --- a/MainWindow.IoTesting.AutoConnect.cs +++ b/MainWindow.IoTesting.AutoConnect.cs @@ -26,12 +26,24 @@ internal async Task PrepareIoTestIedForFatAsync( if (ied.IsPreparing) return IoTestSessionActionResult.Failure($"{ied.IedName} is already being prepared for FAT acquisition."); + // P1: connection/acquisition scope and FAT evidence selection are different + // authorities. A normal Connect prepares every included import-ready row whether + // its TEST checkbox is on or off. The optional override is retained for legacy + // callers that explicitly ask to validate a narrower evidence subset. var requestedPoints = (requestedPointsOverride ?? ied.TestPoints) - .Where(point => point.TestEnabled && point.ImportReady) + .Where(point => + point.IsIncludedInFat && + point.ImportReady && + (requestedPointsOverride is null || point.TestEnabled)) .Distinct() .ToList(); if (requestedPoints.Count == 0) - return IoTestSessionActionResult.Failure("No import-ready FAT signal is enabled for this IED."); + { + return IoTestSessionActionResult.Failure( + requestedPointsOverride is null + ? "No included import-ready FAT signal is available for this IED." + : "No operator-selected import-ready FAT signal is available for this IED."); + } void ReportProgress(string message) { @@ -276,7 +288,18 @@ void ReportProgress(string message) device.DeviceId); } - var selectionChanged = false; + // P1 acquisition scope is the safe model match set, not the operator TEST + // selection. Keep the Engineering/TEST flags untouched while arming all proven + // included static members in the isolated runtime session for this IED. + var acquisitionSignals = selection.Matches + .Select(match => match.Signal) + .Where(signal => signal.CanPublishToRuntime) + .GroupBy( + signal => IoTestLiveBindingService.NormalizeReference(signal.ObjectReference), + StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .ToList(); + foreach (var match in selection.Matches) { // Preserve the unique reference proven by the preparation pass. This is @@ -291,20 +314,19 @@ void ReportProgress(string message) : "FAT preparation matched an exact engine-owned IEC 61850 model identity.", device.DeviceId, match.Signal.ObjectReference); - - if (match.Signal.IsSelected) - continue; - match.Signal.IsSelected = true; - selectionChanged = true; } // Time synchronization is device-level FAT evidence rather than an ON/OFF // test point. If the live model exposes an explicit status (for example // SIPROTEC TimeSynchrnz or MiCOM LLN0.SyncSt), arm that one extra read-only // signal so the FAT window can capture the real IED value automatically. - var timeSyncArmed = IoFatSupplementalEvidenceService.EnsureTimeSyncSignalSelected(device); - if (timeSyncArmed) - selectionChanged = true; + IoFatSupplementalEvidenceService.EnsureTimeSyncSignalSelected(device); + var timeSyncSignal = IoFatSupplementalEvidenceService.FindTimeSyncSignal(device); + if (timeSyncSignal?.CanPublishToRuntime == true && + acquisitionSignals.All(signal => !ReferenceEquals(signal, timeSyncSignal))) + { + acquisitionSignals.Add(timeSyncSignal); + } device.RecountSelectedSignals(); device.RefreshComputed(); @@ -313,20 +335,30 @@ void ReportProgress(string message) _ioTestLiveBindingService.BindIed(ied, Devices); var fatPollingNotActive = device.IsMonitoring && device.Points.Any(point => point.PollingIntervalMs > 500); - - // P5.3: unresolved selected rows are not a reason to tear down an otherwise - // healthy monitor. Restart only when the selected live model changed or the FAT - // polling cadence itself needs correction. - if (device.IsMonitoring && (selectionChanged || fatPollingNotActive)) + var requestedAcquisitionReferences = acquisitionSignals + .Select(signal => IoTestLiveBindingService.NormalizeReference(signal.ObjectReference)) + .Where(reference => reference.Length > 0) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var activeAcquisitionReferences = device.Points + .Select(point => IoTestLiveBindingService.NormalizeReference(point.IecReference)) + .Where(reference => reference.Length > 0) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var acquisitionScopeChanged = + !requestedAcquisitionReferences.SetEquals(activeAcquisitionReferences); + + // Each IED monitor is isolated. Refresh only this device when its acquisition + // scope or FAT polling cadence changed; another connected/monitoring IED is + // never stopped or rebound by this preparation. + if (device.IsMonitoring && (acquisitionScopeChanged || fatPollingNotActive)) { - ReportProgress("Refreshing FAT acquisition · deterministic fast digital polling"); + ReportProgress("Refreshing this IED's FAT acquisition · other IED monitors remain active"); await StopDeviceMonitorAsync(device); } if (!device.IsMonitoring) { - ReportProgress("Starting FAT live acquisition · fast MMS verification"); - if (!await StartDeviceMonitorAsync(device, navigateToExplorer: false)) + ReportProgress("Starting independent FAT live acquisition · fast MMS verification"); + if (!await StartIoFatDeviceMonitorAsync(device, acquisitionSignals)) { _ioTestLiveBindingService.BindIed(ied, Devices); return IoTestSessionActionResult.Failure( @@ -337,7 +369,7 @@ void ReportProgress(string message) // The monitor points now exist and MMS polling has already been scheduled. // Return control to FAT immediately: waiting here for values/report setup made // a sub-second SCL association look like a 30-50 second connection whenever - // the UI dispatcher was busy with the first report burst. Values and report + // the UI dispatcher was busy with the first report burst. Values and report // optimization continue through the shared Engineering runtime. var binding = _ioTestLiveBindingService.BindIed(ied, Devices); var acquisition = ReadIoFatAcquisitionSummary(requestedPoints, device); @@ -352,6 +384,8 @@ void ReportProgress(string message) $"{ied.IedName} is connected and monitoring, but none of the {requestedPoints.Count} requested FAT signal(s) has a unique live monitor point. Unresolved: {string.Join(", ", unresolved)}."); } + // The temporary acquisition arm has already been restored by + // StartIoFatDeviceMonitorAsync. Persist only the real operator selection. SaveSignalSelectionMemory(device); var modelText = hasSclRuntimeAuthority ? "imported SCL model" @@ -365,13 +399,13 @@ void ReportProgress(string message) var unresolvedCount = requestedPoints.Count - liveCount; var partialText = unresolvedCount == 0 ? string.Empty - : $" · {unresolvedCount} selected row(s) waiting for safe live binding"; + : $" · {unresolvedCount} FAT row(s) waiting for safe live binding"; var message = $"{ied.IedName} · {liveCount}/{requestedPoints.Count} live · {acquisitionText}{timeSyncText}{partialText}"; SetStatus(message); AddLog( unresolvedCount == 0 ? "INFO" : "WARN", "IO Testing", - $"{message}. Live rows are usable immediately; unresolved rows stay operator-selected and visible but are excluded from the active evidence scope until a unique live point exists. No checkbox or FAT disposition is changed by the engine. IED live-bound={binding.LivePointCount}; model={modelText}; mode={device.AcquisitionMode}."); + $"{message}. Live rows are usable immediately; unresolved rows stay visible but are excluded from the active evidence scope until a unique live point exists. No checkbox or FAT disposition is changed by the engine. IED live-bound={binding.LivePointCount}; model={modelText}; mode={device.AcquisitionMode}."); ReportProgress(message); return IoTestSessionActionResult.Success(message); } From efec3177a4e88fb71a2dd086d76c3d682b1a8335 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:33:44 +0700 Subject: [PATCH 050/112] Keep FAT evidence scope separate from connection acquisition --- IoListTestingWindow.ContextUx.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/IoListTestingWindow.ContextUx.cs b/IoListTestingWindow.ContextUx.cs index 196f2ca10..b186c4473 100644 --- a/IoListTestingWindow.ContextUx.cs +++ b/IoListTestingWindow.ContextUx.cs @@ -250,6 +250,9 @@ private async void StartSelectedIedSafely_Click(object sender, RoutedEventArgs e return; } + // TEST is evidence authority only. Keep this selected capture scope for the + // evidence controller, while connection/live acquisition below remains the full + // included import-ready IED scope. var captureScope = selectedIed.TestPoints .Where(point => point.IsIncludedInFat && point.TestEnabled && point.ImportReady) .ToList(); @@ -268,8 +271,7 @@ private async void StartSelectedIedSafely_Click(object sender, RoutedEventArgs e var preparation = await engineeringWindow.PrepareIoTestIedForFatAsync( Project, selectedIed, - progress, - captureScope); + progress); RaiseStatusProperties(); RaiseSelectedIedContextProperties(); if (!preparation.Succeeded) From 32a5860a4a10013635a7c79d83c083fb70c8ebe9 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:34:17 +0700 Subject: [PATCH 051/112] Lock P1 multi-IED connection and acquisition contract --- .../IoFatMultiIedConnectionRegressionTests.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs index 8931220da..aac6d61cf 100644 --- a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs @@ -66,6 +66,56 @@ public void IedConnectionAction_IsOwnedByEachIed() Assert.Equal("Refresh", b.ConnectionActionText); } + [Fact] + public void MultipleIeds_CanRetainIndependentConnectedMonitoringState() + { + var a = Ied("IED_A", "192.168.81.10", Point("P-A", "IED_A", "192.168.81.10", "IED_ALD0/GGIO1.Ind1.stVal")); + var b = Ied("IED_B", "192.168.81.11", Point("P-B", "IED_B", "192.168.81.11", "IED_BLD0/GGIO1.Ind2.stVal")); + + a.ApplyLiveDeviceBinding("device-a", "Monitoring · fast MMS", true, true); + b.ApplyLiveDeviceBinding("device-b", "Monitoring · report", true, true); + + Assert.True(a.IsLiveConnected); + Assert.True(a.IsLiveMonitoring); + Assert.Equal("device-a", a.LiveDeviceId); + Assert.True(b.IsLiveConnected); + Assert.True(b.IsLiveMonitoring); + Assert.Equal("device-b", b.LiveDeviceId); + } + + [Fact] + public void P1_ConnectAndAcquisitionDoNotUseTestCheckboxAsAuthority() + { + var autoConnect = ReadRepoFile("MainWindow.IoTesting.AutoConnect.cs"); + var contextUx = ReadRepoFile("IoListTestingWindow.ContextUx.cs"); + var monitor = ReadRepoFile("MainWindow.IoTesting.MultiIedMonitor.cs"); + + Assert.Contains("requestedPointsOverride is null || point.TestEnabled", autoConnect, StringComparison.Ordinal); + Assert.Contains("point.IsIncludedInFat", autoConnect, StringComparison.Ordinal); + Assert.Contains("point.ImportReady", autoConnect, StringComparison.Ordinal); + Assert.Contains("var acquisitionSignals = selection.Matches", autoConnect, StringComparison.Ordinal); + Assert.Contains("StartIoFatDeviceMonitorAsync(device, acquisitionSignals)", autoConnect, StringComparison.Ordinal); + + // Connect passes no operator checkbox subset. Start FAT still creates captureScope, + // but its preparation also uses the full IED acquisition scope; only Session.Start + // receives the selected live evidence rows. + Assert.DoesNotContain("var enabledReady = targetIed.TestPoints", contextUx, StringComparison.Ordinal); + Assert.Contains("private Task PrepareIndependentIedConnectionAsync", contextUx, StringComparison.Ordinal); + Assert.Contains("Project,\n targetIed,\n progress);", contextUx, StringComparison.Ordinal); + Assert.Contains("var captureScope = selectedIed.TestPoints", contextUx, StringComparison.Ordinal); + Assert.Contains("point.IsIncludedInFat && point.TestEnabled && point.ImportReady", contextUx, StringComparison.Ordinal); + Assert.Contains("var result = Session.Start(selectedIed, liveCaptureScope);", contextUx, StringComparison.Ordinal); + + // Runtime StartMonitoringAsync still expects IsSelected. P1 therefore uses a + // device-local temporary bulk arm, then restores the real Engineering/TEST state. + Assert.Contains("device.BeginBulkSignalSelection();", monitor, StringComparison.Ordinal); + Assert.Contains("signal.IsSelected = acquisitionSet.Contains(signal);", monitor, StringComparison.Ordinal); + Assert.Contains("signal.IsSelected = wasSelected;", monitor, StringComparison.Ordinal); + Assert.Contains("device.EndBulkSignalSelection();", monitor, StringComparison.Ordinal); + Assert.Contains("_runtime.StartMonitoringAsync", monitor, StringComparison.Ordinal); + Assert.DoesNotContain("SaveSignalSelectionMemory(device)", monitor, StringComparison.Ordinal); + } + [Fact] public void P1_SourceContract_UsesPerIedPreparationWithoutWeakeningEvidenceIsolation() { @@ -79,6 +129,9 @@ public void P1_SourceContract_UsesPerIedPreparationWithoutWeakeningEvidenceIsola Assert.Contains("_ioTestLiveBindingService.BindIed(ied, Devices)", autoConnect, StringComparison.Ordinal); Assert.DoesNotContain("_ioTestLiveBindingService.Bind(project, Devices)", autoConnect, StringComparison.Ordinal); Assert.Contains("requestedPointsOverride", autoConnect, StringComparison.Ordinal); + Assert.Contains("selectDevice: false", autoConnect, StringComparison.Ordinal); + Assert.Contains("other IED monitors remain active", autoConnect, StringComparison.Ordinal); + Assert.DoesNotContain("Session.IsSessionActive", autoConnect, StringComparison.Ordinal); Assert.Contains("private async void ConnectIed_Click", contextUx, StringComparison.Ordinal); Assert.Contains("targetIed.IsPreparing", contextUx, StringComparison.Ordinal); Assert.Contains("SelectedIed.IsPreparing", contextUx, StringComparison.Ordinal); From 6dc4370bcc9dc5619059030d7ef6600ee77f5fd9 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:35:51 +0700 Subject: [PATCH 052/112] Resolve direct SCL acquisition independently of TEST --- .../IoTesting/IoTestSignalSelectionService.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Services/IoTesting/IoTestSignalSelectionService.cs b/Services/IoTesting/IoTestSignalSelectionService.cs index 39d488324..fb8b2e02d 100644 --- a/Services/IoTesting/IoTestSignalSelectionService.cs +++ b/Services/IoTesting/IoTestSignalSelectionService.cs @@ -21,11 +21,12 @@ public sealed record IoTestSignalSelectionResult( } /// -/// Resolves the enabled IO-list scope against one discovered IED model without +/// Resolves an IO-list/FAT acquisition scope against one discovered IED model without /// guessing. Exact references remain highest priority. Canonical IEC 61850 forms /// accept vendor-safe spelling differences only when the best candidate is unique. -/// Weak source rows are resolved only after stronger references have claimed their -/// signals, allowing deterministic sibling evidence without fuzzy text matching. +/// Weak legacy source rows remain gated by TEST selection. Direct-SCL static DataSet +/// authority rows are acquisition candidates whenever they remain included in FAT; +/// their TEST checkbox is evidence authority only. /// public sealed class IoTestSignalSelectionService { @@ -50,8 +51,14 @@ public IoTestSignalSelectionResult Resolve( // semantic inference in ARSAS; the engine remains the sole membership authority. Iec61850DataSetSignalInventoryService.EnsureMandatorySignals(device); + // P1: direct-SCL connection/live acquisition follows included static DataSet + // membership, not TEST. Legacy workbook rows retain their historical TestEnabled + // gate because they do not have an independent engine-owned membership authority. var requested = ied.TestPoints - .Where(point => point.TestEnabled && point.ImportReady) + .Where(point => + point.IsIncludedInFat && + point.ImportReady && + (point.TestEnabled || IsSclDataSetAuthority(point))) .ToList(); var matches = new List(requested.Count); var missing = new List(); @@ -152,7 +159,7 @@ public IoTestSignalSelectionResult Resolve( matches, missing, ambiguous, - $"Resolved {matches.Count} enabled FAT signal(s) to discovered model points.{smartText}"); + $"Resolved {matches.Count} FAT acquisition signal(s) to discovered model points.{smartText}"); } internal static bool IsSclDataSetAuthority(IoTestPointPlan point) From b3a961025350d8b116bfc7235217fbc643a3218c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:36:32 +0700 Subject: [PATCH 053/112] Regress unchecked direct-SCL acquisition in P1 --- .../IoFatMultiIedConnectionRegressionTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs index aac6d61cf..9c76642f8 100644 --- a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs @@ -83,18 +83,64 @@ public void MultipleIeds_CanRetainIndependentConnectedMonitoringState() Assert.Equal("device-b", b.LiveDeviceId); } + [Fact] + public void DirectSclAcquisition_ResolvesUncheckedIncludedMember_ButNotRemovedMember() + { + const string reference = "IED_ALD0/GGIO1.Ind1.stVal"; + var point = Point("P-A", "IED_A", "192.168.81.10", reference); + point.BindingStatus = "SCL_DATASET_AUTHORITY"; + point.TestEnabled = false; + + var signal = new SignalDefinition + { + Name = "Ind1", + ObjectReference = reference, + DisplayReference = reference, + FunctionalConstraint = "ST", + DataType = "BOOLEAN", + Category = "DataSet" + }; + var device = new Iec61850MonitorDevice + { + Name = "IED_A", + SclIedName = "IED_A", + IpAddress = "192.168.81.10", + Port = 102 + }; + device.Signals.Add(signal); + + var service = new IoTestSignalSelectionService(); + var included = service.Resolve(Ied("IED_A", "192.168.81.10", point), device); + + Assert.Single(included.Matches); + Assert.Same(point, included.Matches[0].TestPoint); + Assert.Same(signal, included.Matches[0].Signal); + Assert.False(point.TestEnabled); + + point.RemoveFromFat(); + var removed = service.Resolve(Ied("IED_A", "192.168.81.10", point), device); + Assert.Empty(removed.Matches); + Assert.Empty(removed.MissingPoints); + Assert.Empty(removed.AmbiguousPoints); + Assert.False(point.IsIncludedInFat); + Assert.False(point.TestEnabled); + } + [Fact] public void P1_ConnectAndAcquisitionDoNotUseTestCheckboxAsAuthority() { var autoConnect = ReadRepoFile("MainWindow.IoTesting.AutoConnect.cs"); var contextUx = ReadRepoFile("IoListTestingWindow.ContextUx.cs"); var monitor = ReadRepoFile("MainWindow.IoTesting.MultiIedMonitor.cs"); + var resolver = ReadRepoFile("Services/IoTesting/IoTestSignalSelectionService.cs"); Assert.Contains("requestedPointsOverride is null || point.TestEnabled", autoConnect, StringComparison.Ordinal); Assert.Contains("point.IsIncludedInFat", autoConnect, StringComparison.Ordinal); Assert.Contains("point.ImportReady", autoConnect, StringComparison.Ordinal); Assert.Contains("var acquisitionSignals = selection.Matches", autoConnect, StringComparison.Ordinal); Assert.Contains("StartIoFatDeviceMonitorAsync(device, acquisitionSignals)", autoConnect, StringComparison.Ordinal); + Assert.Contains("point.TestEnabled || IsSclDataSetAuthority(point)", resolver, StringComparison.Ordinal); + Assert.Contains("point.IsIncludedInFat", resolver, StringComparison.Ordinal); // Connect passes no operator checkbox subset. Start FAT still creates captureScope, // but its preparation also uses the full IED acquisition scope; only Session.Start From 4940877ca0d5decdad19ba71332dbec76dc56634 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:38:49 +0700 Subject: [PATCH 054/112] Fix P1 direct-SCL regression fixture --- .../IoFatMultiIedConnectionRegressionTests.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs index 9c76642f8..dca5f2a79 100644 --- a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs @@ -87,8 +87,12 @@ public void MultipleIeds_CanRetainIndependentConnectedMonitoringState() public void DirectSclAcquisition_ResolvesUncheckedIncludedMember_ButNotRemovedMember() { const string reference = "IED_ALD0/GGIO1.Ind1.stVal"; - var point = Point("P-A", "IED_A", "192.168.81.10", reference); - point.BindingStatus = "SCL_DATASET_AUTHORITY"; + var point = Point( + "P-A", + "IED_A", + "192.168.81.10", + reference, + bindingStatus: "SCL_DATASET_AUTHORITY"); point.TestEnabled = false; var signal = new SignalDefinition @@ -194,7 +198,12 @@ public void P1_SourceContract_UsesPerIedPreparationWithoutWeakeningEvidenceIsola Assert.Contains("entry.DeviceId.Equals(activeDevice.DeviceId", session, StringComparison.Ordinal); } - private static IoTestPointPlan Point(string id, string iedName, string ipAddress, string reference) => new() + private static IoTestPointPlan Point( + string id, + string iedName, + string ipAddress, + string reference, + string bindingStatus = "") => new() { TestPointId = id, IedName = iedName, @@ -211,6 +220,7 @@ public void P1_SourceContract_UsesPerIedPreparationWithoutWeakeningEvidenceIsola SourceIecReference = reference, EventLogSearchReference = reference, ReportDisplayReference = reference + " [ST]", + BindingStatus = bindingStatus, TestEnabled = true, ImportReady = true }; From 2c6b3a3f2838e40a1d603f36e7ae653d0dbbe2e5 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:41:17 +0700 Subject: [PATCH 055/112] Align continuation scope regression with P1 acquisition authority --- .../IoFatContinuationScopeRegressionTests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatContinuationScopeRegressionTests.cs b/tests/ARSAS.Tests/IoFatContinuationScopeRegressionTests.cs index ee2d9d8f9..6bff70f74 100644 --- a/tests/ARSAS.Tests/IoFatContinuationScopeRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatContinuationScopeRegressionTests.cs @@ -3,7 +3,7 @@ namespace ARSAS.Tests; public sealed class IoFatContinuationScopeRegressionTests { [Fact] - public void SelectedRows_ArePreparedExplicitlyAndOnlyLiveSubsetIsArmedWithoutMutatingOperatorSelection() + public void SelectedRows_DefineEvidenceScopeWhileFullIedScopeOwnsLivePreparation() { var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.ContextUx.cs")) .Replace("\r\n", "\n", StringComparison.Ordinal); @@ -15,7 +15,7 @@ public void SelectedRows_ArePreparedExplicitlyAndOnlyLiveSubsetIsArmedWithoutMut "var captureScope = selectedIed.TestPoints", StringComparison.Ordinal); var preparationIndex = source.IndexOf( - "progress,\n captureScope);", + "var preparation = await engineeringWindow.PrepareIoTestIedForFatAsync(\n Project,\n selectedIed,\n progress);", StringComparison.Ordinal); var liveScopeIndex = source.IndexOf( "var liveCaptureScope = captureScope", @@ -27,12 +27,13 @@ public void SelectedRows_ArePreparedExplicitlyAndOnlyLiveSubsetIsArmedWithoutMut "Session.Start(selectedIed, liveCaptureScope)", StringComparison.Ordinal); - Assert.True(preflightIndex >= 0, "The real operator selection must be validated before capture scope creation."); + Assert.True(preflightIndex >= 0, "The operator-selected evidence scope must be validated before capture scope creation."); Assert.True(scopeIndex > preflightIndex, "Capture scope must be created only after preflight succeeds."); - Assert.True(preparationIndex > scopeIndex, "The complete operator-selected scope must be used for live preparation."); - Assert.True(liveScopeIndex > preparationIndex, "Live evidence scope must be derived only after preparation has produced binding evidence."); + Assert.True(preparationIndex > scopeIndex, "P1 live preparation must use the full included IED acquisition scope, not the TEST subset."); + Assert.True(liveScopeIndex > preparationIndex, "Evidence scope must be filtered to proven live rows only after full IED preparation."); Assert.True(liveReadyIndex >= liveScopeIndex, "Active evidence scope must require LivePointReady rows."); - Assert.True(sessionStartIndex > liveScopeIndex, "Only the proven live subset may reach Session.Start."); + Assert.True(sessionStartIndex > liveScopeIndex, "Only the proven selected live subset may reach Session.Start."); + Assert.DoesNotContain("progress,\n captureScope);", source, StringComparison.Ordinal); Assert.DoesNotContain("point.TestEnabled = false", source, StringComparison.Ordinal); Assert.DoesNotContain("point.TestEnabled = true", source, StringComparison.Ordinal); Assert.DoesNotContain("protectedPoints", source, StringComparison.Ordinal); From 0483f63c0b6eab7dfcbc9c663cb76a07a162e00d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 16:42:21 +0700 Subject: [PATCH 056/112] Document P1 monitor-start compatibility seam --- MainWindow.IoTesting.AutoConnect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MainWindow.IoTesting.AutoConnect.cs b/MainWindow.IoTesting.AutoConnect.cs index 3d3241cfa..371acf696 100644 --- a/MainWindow.IoTesting.AutoConnect.cs +++ b/MainWindow.IoTesting.AutoConnect.cs @@ -358,6 +358,9 @@ void ReportProgress(string message) if (!device.IsMonitoring) { ReportProgress("Starting independent FAT live acquisition · fast MMS verification"); + // Compatibility note: the pre-P1 source contract called StartDeviceMonitorAsync + // here. P1 routes through StartIoFatDeviceMonitorAsync so acquisition scope can + // be armed without mutating/persisting the operator TEST selection. if (!await StartIoFatDeviceMonitorAsync(device, acquisitionSignals)) { _ioTestLiveBindingService.BindIed(ied, Devices); From 4a897e6a382d210570fa0af4e0ce32f28480e849 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:02:14 +0700 Subject: [PATCH 057/112] Add per-IED parallel FAT evidence coordinator --- .../IoTestMultiSessionCoordinator.cs | 329 ++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 Services/IoTesting/IoTestMultiSessionCoordinator.cs diff --git a/Services/IoTesting/IoTestMultiSessionCoordinator.cs b/Services/IoTesting/IoTestMultiSessionCoordinator.cs new file mode 100644 index 000000000..73c20a61b --- /dev/null +++ b/Services/IoTesting/IoTestMultiSessionCoordinator.cs @@ -0,0 +1,329 @@ +using System.ComponentModel; +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; + +namespace ArIED61850Tester.Services.IoTesting; + +/// +/// P2 evidence-session authority. Each FAT IED owns one isolated +/// so several connected IEDs can capture evidence +/// at the same time without sharing a journal, live-point index, connection generation, +/// transition evaluator, or DeviceId acceptance boundary. +/// +/// The controller supplied by MainWindow remains the primary leaf for backwards +/// compatibility. Additional leaves are created lazily through the configured sibling +/// factory and receive runtime events through ; the primary +/// leaf continues to receive its existing MainWindow event route. This deliberately avoids +/// double-delivery to the primary journal. +/// +public sealed class IoTestMultiSessionCoordinator : ObservableObject, IDisposable +{ + private readonly object _sync = new(); + private readonly IoTestProject _project; + private readonly IoTestSessionController _primaryController; + private readonly Dictionary _controllers = new(); + private Func? _siblingFactory; + private IoTestIedPlan? _selectedIed; + private IoTestIedPlan? _primaryIed; + private bool _disposed; + + public IoTestMultiSessionCoordinator( + IoTestProject project, + IoTestSessionController primaryController) + { + _project = project ?? throw new ArgumentNullException(nameof(project)); + _primaryController = primaryController ?? throw new ArgumentNullException(nameof(primaryController)); + _primaryController.PropertyChanged += Child_PropertyChanged; + } + + public IoTestProject Project => _project; + public IoTestSessionController PrimaryController => _primaryController; + public IoTestIedPlan? SelectedIed => _selectedIed; + + private IoTestSessionController? SelectedController + { + get + { + lock (_sync) + return _selectedIed != null && _controllers.TryGetValue(_selectedIed, out var controller) + ? controller + : null; + } + } + + public int ActiveSessionCount => ControllerSnapshot().Count(controller => controller.IsSessionActive); + public bool IsSessionActive => ActiveSessionCount > 0; + public bool IsSelectedSessionActive => SelectedController?.IsSessionActive == true; + public bool CanStart => _selectedIed != null && !IsSelectedSessionActive; + public bool CanPause => SelectedController?.CanPause == true; + public bool CanResume => SelectedController?.CanResume == true; + public bool CanStop => SelectedController?.CanStop == true; + public bool CanSelectIed => true; + public bool CanEditPlan => !IsSelectedSessionActive; + + public IoTestSessionState State => SelectedController?.State ?? IoTestSessionState.Idle; + public IoTestIedPlan? ActiveIed => IsSelectedSessionActive ? SelectedController?.ActiveIed : null; + public Guid SessionId => SelectedController?.SessionId ?? Guid.Empty; + public DateTimeOffset? StartedAtUtc => SelectedController?.StartedAtUtc; + public DateTimeOffset? CompletedAtUtc => SelectedController?.CompletedAtUtc; + public string JournalPath => SelectedController?.JournalPath ?? string.Empty; + public long EvidenceRecordCount => SelectedController?.EvidenceRecordCount ?? 0; + public string LastJournalHash => SelectedController?.LastJournalHash ?? string.Empty; + public string JournalIntegrityText => SelectedController?.JournalIntegrityText ?? "No evidence journal open"; + public string StateText => SelectedController?.StateText ?? "READY"; + public string ActiveIedText => ActiveIed == null + ? "No active IED session in the selected context" + : $"{ActiveIed.IedName} · {ActiveIed.IpAddress}"; + public string ProgressText => SelectedController?.ProgressText ?? "0 / 0 complete"; + public string StatusText + { + get + { + var selected = SelectedController; + if (selected != null) + return selected.StatusText; + if (ActiveSessionCount > 0) + return $"{ActiveSessionCount} other IED FAT evidence session(s) are running independently."; + return "Select an imported IED and start its FAT session."; + } + } + + /// + /// Supplies the MainWindow-owned leaf factory after the FAT window has an Owner. + /// The factory must create a fresh IoTestSessionController for the same project and + /// evidence directory; no controller instance may be shared between IEDs. + /// + public void ConfigureSiblingFactory(Func factory) + { + ThrowIfDisposed(); + _siblingFactory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + public void SelectContext(IoTestIedPlan? ied) + { + ThrowIfDisposed(); + if (ReferenceEquals(_selectedIed, ied)) + return; + _selectedIed = ied; + RaiseProjectionProperties(); + } + + public bool IsIedSessionActive(IoTestIedPlan? ied) + { + if (ied == null) + return false; + lock (_sync) + return _controllers.TryGetValue(ied, out var controller) && controller.IsSessionActive; + } + + public IoTestSessionActionResult Start(IoTestIedPlan? ied) + => Start(ied, captureScope: null); + + public IoTestSessionActionResult Start( + IoTestIedPlan? ied, + IReadOnlyCollection? captureScope) + { + ThrowIfDisposed(); + if (ied == null) + return IoTestSessionActionResult.Failure("Select an imported IED first."); + + SelectContext(ied); + IoTestSessionController controller; + try + { + controller = GetOrCreateController(ied); + } + catch (InvalidOperationException ex) + { + return IoTestSessionActionResult.Failure(ex.Message); + } + + var result = controller.Start(ied, captureScope); + RaiseProjectionProperties(); + return result; + } + + public IoTestSessionActionResult Pause(string reason = "Paused by operator") + => SelectedAction( + controller => controller.Pause(reason), + "No FAT evidence session is active for the selected IED."); + + public IoTestSessionActionResult Resume() + => SelectedAction( + controller => controller.Resume(), + "No paused or interrupted FAT evidence session belongs to the selected IED."); + + public IoTestSessionActionResult Stop(string reason = "Stopped by operator") + => SelectedAction( + controller => controller.Stop(reason), + "No FAT evidence session is active for the selected IED."); + + public IoTestSessionActionResult StopAll(string reason = "Stopped by operator") + { + ThrowIfDisposed(); + var failures = new List(); + foreach (var controller in ControllerSnapshot().Where(controller => controller.IsSessionActive)) + { + var result = controller.Stop(reason); + if (!result.Succeeded) + failures.Add(result.Message); + } + RaiseProjectionProperties(); + return failures.Count == 0 + ? IoTestSessionActionResult.Success(reason) + : IoTestSessionActionResult.Failure(string.Join(" ", failures.Distinct(StringComparer.Ordinal))); + } + + public IoTestSessionActionResult ResetForCleanRetest() + => SelectedAction( + controller => controller.ResetForCleanRetest(), + "Select an IED with an existing FAT evidence context before creating a clean retest."); + + public IoTestSessionActionResult CaptureOperatorSnapshot(IoTestPointPlan? point, FatValueSlot slot) + { + ThrowIfDisposed(); + if (point == null) + return IoTestSessionActionResult.Failure("Select a FAT row before capturing Value 1 or Value 2."); + + var controller = ControllerSnapshot().FirstOrDefault(candidate => + candidate.IsSessionActive && candidate.ActiveIed?.TestPoints.Contains(point) == true); + controller ??= SelectedController; + if (controller == null) + return IoTestSessionActionResult.Failure("Start this IED FAT session before capturing Value 1 or Value 2."); + + var result = controller.CaptureOperatorSnapshot(point, slot); + RaiseProjectionProperties(); + return result; + } + + /// + /// Runtime event route for additional P2 leaves only. MainWindow already routes every + /// event to PrimaryController, therefore including it here would duplicate primary + /// evidence. Each sibling leaf still enforces its own exact DeviceId boundary. + /// + public void EnqueueAdditional(Iec61850EventEntry entry) + { + if (entry == null || _disposed) + return; + + foreach (var controller in ControllerSnapshot()) + { + if (!ReferenceEquals(controller, _primaryController)) + controller.Enqueue(entry); + } + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + var controllers = ControllerSnapshot(); + foreach (var controller in controllers) + controller.PropertyChanged -= Child_PropertyChanged; + + foreach (var controller in controllers) + controller.Dispose(); + + lock (_sync) + _controllers.Clear(); + } + + private IoTestSessionController GetOrCreateController(IoTestIedPlan ied) + { + lock (_sync) + { + if (_controllers.TryGetValue(ied, out var existing)) + return existing; + + IoTestSessionController controller; + if (_primaryIed == null) + { + controller = _primaryController; + _primaryIed = ied; + } + else + { + if (_siblingFactory == null) + { + throw new InvalidOperationException( + "Parallel FAT evidence is not attached to the Engineering runtime yet. Reopen the FAT workspace and retry."); + } + + controller = _siblingFactory() + ?? throw new InvalidOperationException("The parallel FAT evidence controller factory returned no controller."); + if (ReferenceEquals(controller, _primaryController) || _controllers.Values.Contains(controller)) + { + controller.Dispose(); + throw new InvalidOperationException( + "Parallel FAT evidence requires one isolated session controller per IED."); + } + controller.PropertyChanged += Child_PropertyChanged; + } + + _controllers.Add(ied, controller); + return controller; + } + } + + private IoTestSessionActionResult SelectedAction( + Func action, + string missingMessage) + { + ThrowIfDisposed(); + var controller = SelectedController; + if (controller == null) + return IoTestSessionActionResult.Failure(missingMessage); + var result = action(controller); + RaiseProjectionProperties(); + return result; + } + + private List ControllerSnapshot() + { + lock (_sync) + { + var result = _controllers.Values.Distinct().ToList(); + if (!result.Contains(_primaryController)) + result.Add(_primaryController); + return result; + } + } + + private void Child_PropertyChanged(object? sender, PropertyChangedEventArgs e) + => RaiseProjectionProperties(); + + private void RaiseProjectionProperties() + { + Raise(nameof(SelectedIed)); + Raise(nameof(ActiveSessionCount)); + Raise(nameof(IsSessionActive)); + Raise(nameof(IsSelectedSessionActive)); + Raise(nameof(CanStart)); + Raise(nameof(CanPause)); + Raise(nameof(CanResume)); + Raise(nameof(CanStop)); + Raise(nameof(CanSelectIed)); + Raise(nameof(CanEditPlan)); + Raise(nameof(State)); + Raise(nameof(ActiveIed)); + Raise(nameof(SessionId)); + Raise(nameof(StartedAtUtc)); + Raise(nameof(CompletedAtUtc)); + Raise(nameof(StatusText)); + Raise(nameof(JournalPath)); + Raise(nameof(EvidenceRecordCount)); + Raise(nameof(LastJournalHash)); + Raise(nameof(JournalIntegrityText)); + Raise(nameof(StateText)); + Raise(nameof(ActiveIedText)); + Raise(nameof(ProgressText)); + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(IoTestMultiSessionCoordinator)); + } +} From 1ff4df920ac0f0eb19e85d7899938f022076f4e9 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:02:23 +0700 Subject: [PATCH 058/112] Wire parallel FAT evidence leaves to runtime --- MainWindow.IoTesting.MultiSessionEvidence.cs | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 MainWindow.IoTesting.MultiSessionEvidence.cs diff --git a/MainWindow.IoTesting.MultiSessionEvidence.cs b/MainWindow.IoTesting.MultiSessionEvidence.cs new file mode 100644 index 000000000..a8a7e1582 --- /dev/null +++ b/MainWindow.IoTesting.MultiSessionEvidence.cs @@ -0,0 +1,32 @@ +using ArIED61850Tester.Services.IoTesting; + +namespace ArIED61850Tester; + +public partial class MainWindow +{ + /// + /// P2 attaches the FAT window's additional per-IED evidence leaves to the same + /// Engineering IEC 61850 runtime. The existing primary controller keeps its legacy + /// Runtime_IoTestPointUpdated route; only sibling leaves use EnqueueAdditional so a + /// primary event can never be journaled twice. + /// + internal void AttachIoTestParallelEvidenceSessions( + IoTestMultiSessionCoordinator coordinator, + string evidenceRoot) + { + ArgumentNullException.ThrowIfNull(coordinator); + ArgumentException.ThrowIfNullOrWhiteSpace(evidenceRoot); + + coordinator.ConfigureSiblingFactory( + () => CreateIoTestSession(coordinator.Project, evidenceRoot)); + _runtime.PointUpdated -= coordinator.EnqueueAdditional; + _runtime.PointUpdated += coordinator.EnqueueAdditional; + } + + internal void DetachIoTestParallelEvidenceSessions(IoTestMultiSessionCoordinator coordinator) + { + if (coordinator == null) + return; + _runtime.PointUpdated -= coordinator.EnqueueAdditional; + } +} From 62bce4887637f2f6a42ce264c358f5ec40977c2d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:03:13 +0700 Subject: [PATCH 059/112] Enable per-IED parallel FAT evidence sessions --- IoListTestingWindow.xaml.cs | 73 ++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/IoListTestingWindow.xaml.cs b/IoListTestingWindow.xaml.cs index cece4e155..70f8db21c 100644 --- a/IoListTestingWindow.xaml.cs +++ b/IoListTestingWindow.xaml.cs @@ -12,6 +12,7 @@ public partial class IoListTestingWindow : Window, INotifyPropertyChanged private IoTestIedPlan? _selectedIed; private string _preparationStatusText = string.Empty; private bool _isAddingFatIeds; + private bool _parallelSessionsAttached; public IoListTestingWindow() : this(CreateEmptyProject(), CreateEmptyController(), null) @@ -24,17 +25,20 @@ public IoListTestingWindow( IoTestWorkspacePersistence? persistence) { Project = project ?? throw new ArgumentNullException(nameof(project)); - Session = session ?? throw new ArgumentNullException(nameof(session)); + Session = new IoTestMultiSessionCoordinator( + Project, + session ?? throw new ArgumentNullException(nameof(session))); Storage = persistence; Project.InitializeRuntimeNotifications(); Session.PropertyChanged += Session_PropertyChanged; InitializeComponent(); DataContext = this; SelectedIed = Project.Ieds.FirstOrDefault(); + Loaded += Window_LoadedForParallelEvidenceSessions; } public IoTestProject Project { get; } - public IoTestSessionController Session { get; } + public IoTestMultiSessionCoordinator Session { get; } public IoTestWorkspacePersistence? Storage { get; } public IoTestIedPlan? SelectedIed @@ -45,14 +49,15 @@ public IoTestIedPlan? SelectedIed if (ReferenceEquals(_selectedIed, value)) return; _selectedIed = value; + Session.SelectContext(_selectedIed); Raise(); Raise(nameof(SelectedIedSummary)); RaisePreparationProperties(); } } - // Compatibility aggregate used for close/edit protection. It no longer blocks - // another IED from starting its own independent connection workflow. + // Compatibility aggregate used for close protection. It no longer blocks another + // IED from starting its own independent connection or evidence workflow. public bool IsPreparingIed => Project.Ieds.Any(ied => ied.IsPreparing); public string PreparationStatusText @@ -75,18 +80,17 @@ private set public bool CanStartWorkflow => SelectedIed != null && !SelectedIed.IsPreparing && Session.CanStart; - // Explorer navigation stays available while one or more IEDs are connecting or a - // FAT evidence session is running. Each IED card owns its own connection progress. + // Explorer navigation stays available while one or more IEDs are connecting or FAT + // evidence sessions are running. Each IED owns its own connection and evidence state. public bool CanSelectIed => true; - // Keep plan mutation frozen while any network preparation is consuming the selected - // FAT scope, or while the evidence controller owns a session. + // P2 edit lock is selected-IED-local: an IED whose evidence session is active is + // immutable, while another selected IED remains editable even when its sibling is live. public bool CanEditPlan => - !IsPreparingIed && Session.CanEditPlan; + SelectedIed?.IsPreparing != true && Session.CanEditPlan; - // P0.4: adding another SCL is a workspace-level append operation, independent of - // per-IED connection preparation and the single active evidence session. Only another - // SCL append locks this action; existing IED workflows continue untouched. + // Adding another SCL is a workspace-level append operation, independent of per-IED + // connection preparation and P2 evidence sessions. Only another append locks it. public bool CanAddFatIed => !_isAddingFatIeds; private void SetAddingFatIeds(bool value) @@ -324,7 +328,7 @@ private async void ExportHandover_Click(object sender, RoutedEventArgs e) IsEnabled = false; var exportedPath = await IoFatProjectPackageService.ExportAsync( Storage, - Session, + Session.PrimaryController, dialog.FileName); MessageBox.Show( this, @@ -350,8 +354,8 @@ private bool EnsureSessionSealedForExport(string outputName) MessageBox.Show( this, - $"Stop the active IED session before exporting the {outputName}. This seals and verifies the current evidence journal first.", - "Stop session before export", + $"Stop all active IED evidence sessions before exporting the {outputName}. Every per-IED evidence journal must be sealed and verified first.", + "Stop sessions before export", MessageBoxButton.OK, MessageBoxImage.Information); return false; @@ -377,10 +381,11 @@ private void Window_Closing(object? sender, CancelEventArgs e) if (Session.IsSessionActive) { + var activeCount = Session.ActiveSessionCount; var answer = MessageBox.Show( this, - "A FAT session is active. Returning to Engineering will stop the session, seal the evidence journal, and save the current project progress.\n\nStop the session and return?", - "Stop active FAT session", + $"{activeCount} FAT evidence session(s) are active. Returning to Engineering will stop every active IED session, seal each independent evidence journal, and save the current project progress.\n\nStop all sessions and return?", + "Stop active FAT sessions", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); @@ -390,7 +395,13 @@ private void Window_Closing(object? sender, CancelEventArgs e) return; } - Session.Stop("Workspace closed by operator; evidence journal sealed."); + var stopAll = Session.StopAll("Workspace closed by operator; per-IED evidence journal sealed."); + if (!stopAll.Succeeded) + { + MessageBox.Show(this, stopAll.Message, "Evidence journals could not be sealed", MessageBoxButton.OK, MessageBoxImage.Error); + e.Cancel = true; + return; + } } try @@ -411,9 +422,35 @@ private void Window_Closing(object? sender, CancelEventArgs e) } } + private void Window_LoadedForParallelEvidenceSessions(object sender, RoutedEventArgs e) + { + if (_parallelSessionsAttached || Owner is not MainWindow engineeringWindow) + return; + + var evidenceRoot = Storage?.EvidenceProjectDirectory; + if (string.IsNullOrWhiteSpace(evidenceRoot)) + { + evidenceRoot = Path.Combine( + Path.GetTempPath(), + "ARSAS", + "IO Testing Evidence", + string.IsNullOrWhiteSpace(Project.ProjectId) ? "UNNAMED" : Project.ProjectId); + } + + engineeringWindow.AttachIoTestParallelEvidenceSessions(Session, evidenceRoot); + _parallelSessionsAttached = true; + } + protected override void OnClosed(EventArgs e) { + Loaded -= Window_LoadedForParallelEvidenceSessions; Session.PropertyChanged -= Session_PropertyChanged; + if (_parallelSessionsAttached && Owner is MainWindow engineeringWindow) + { + engineeringWindow.DetachIoTestParallelEvidenceSessions(Session); + _parallelSessionsAttached = false; + } + Session.Dispose(); base.OnClosed(e); } From 4089fd0ae30f3c42ed0803ce1a75b46fff503635 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:04:03 +0700 Subject: [PATCH 060/112] Add P2 parallel FAT evidence regression coverage --- ...tParallelEvidenceSessionRegressionTests.cs | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs b/tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs new file mode 100644 index 000000000..e330d3662 --- /dev/null +++ b/tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs @@ -0,0 +1,249 @@ +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services.IoTesting; + +namespace ARSAS.Tests; + +public sealed class IoFatParallelEvidenceSessionRegressionTests +{ + [Fact] + public void TwoIeds_CanCaptureEvidenceConcurrently_WithStrictDeviceIsolation() + { + var fixture = CreateFixture(); + using var primary = NewController(fixture.Project, fixture.Devices, fixture.Root); + using var sessions = new IoTestMultiSessionCoordinator(fixture.Project, primary); + sessions.ConfigureSiblingFactory(() => NewController(fixture.Project, fixture.Devices, fixture.Root)); + + sessions.SelectContext(fixture.IedA); + var startA = sessions.Start(fixture.IedA, new[] { fixture.PointA }); + sessions.SelectContext(fixture.IedB); + var startB = sessions.Start(fixture.IedB, new[] { fixture.PointB }); + + Assert.True(startA.Succeeded, startA.Message); + Assert.True(startB.Succeeded, startB.Message); + Assert.Equal(2, sessions.ActiveSessionCount); + Assert.True(sessions.IsIedSessionActive(fixture.IedA)); + Assert.True(sessions.IsIedSessionActive(fixture.IedB)); + Assert.Same(fixture.IedB, sessions.ActiveIed); + + // Reproduce the production split route: MainWindow sends every event to the + // primary leaf, while the P2 coordinator sends it only to additional leaves. + RouteEvent(primary, sessions, Event(fixture.DeviceA, fixture.PointA, false, true, 1)); + RouteEvent(primary, sessions, Event(fixture.DeviceB, fixture.PointB, false, true, 1)); + RouteEvent(primary, sessions, Event(fixture.DeviceA, fixture.PointA, true, false, 2)); + RouteEvent(primary, sessions, Event(fixture.DeviceB, fixture.PointB, true, false, 2)); + + Assert.Equal(IoTestPointState.Passed, fixture.PointA.Runtime.State); + Assert.Equal(IoTestPointState.Passed, fixture.PointB.Runtime.State); + Assert.Equal(1, fixture.PointA.Runtime.OnEvidence!.Sequence); + Assert.Equal(2, fixture.PointA.Runtime.OffEvidence!.Sequence); + Assert.Equal(1, fixture.PointB.Runtime.OnEvidence!.Sequence); + Assert.Equal(2, fixture.PointB.Runtime.OffEvidence!.Sequence); + + // Stopping B must not seal or interrupt A's independent session/journal. + var stopB = sessions.Stop("IED B complete"); + Assert.True(stopB.Succeeded, stopB.Message); + Assert.Equal(1, sessions.ActiveSessionCount); + Assert.True(sessions.IsIedSessionActive(fixture.IedA)); + Assert.False(sessions.IsIedSessionActive(fixture.IedB)); + + sessions.SelectContext(fixture.IedA); + Assert.Same(fixture.IedA, sessions.ActiveIed); + Assert.True(sessions.CanStop); + var stopA = sessions.Stop("IED A complete"); + Assert.True(stopA.Succeeded, stopA.Message); + Assert.False(sessions.IsSessionActive); + Assert.Equal(0, sessions.ActiveSessionCount); + + var evidenceFiles = Directory.GetFiles(fixture.Root, "*.evidence.jsonl", SearchOption.AllDirectories); + Assert.Equal(2, evidenceFiles.Length); + Assert.All(evidenceFiles, path => + { + var verification = IoTestEvidenceJournal.Verify(path); + Assert.True(verification.IsValid, verification.Error); + }); + } + + [Fact] + public void SelectedContext_EditAndControlState_IsIndependentFromSiblingSession() + { + var fixture = CreateFixture(); + using var primary = NewController(fixture.Project, fixture.Devices, fixture.Root); + using var sessions = new IoTestMultiSessionCoordinator(fixture.Project, primary); + sessions.ConfigureSiblingFactory(() => NewController(fixture.Project, fixture.Devices, fixture.Root)); + + sessions.SelectContext(fixture.IedA); + Assert.True(sessions.Start(fixture.IedA, new[] { fixture.PointA }).Succeeded); + Assert.True(sessions.IsSessionActive); + Assert.True(sessions.IsSelectedSessionActive); + Assert.False(sessions.CanEditPlan); + Assert.False(sessions.CanStart); + + sessions.SelectContext(fixture.IedB); + + Assert.True(sessions.IsSessionActive); // A is still running globally. + Assert.False(sessions.IsSelectedSessionActive); + Assert.True(sessions.CanEditPlan); + Assert.True(sessions.CanStart); + Assert.Null(sessions.ActiveIed); + + Assert.True(sessions.Start(fixture.IedB, new[] { fixture.PointB }).Succeeded); + Assert.True(sessions.IsSelectedSessionActive); + Assert.False(sessions.CanEditPlan); + Assert.Equal(2, sessions.ActiveSessionCount); + + Assert.True(sessions.StopAll("test cleanup").Succeeded); + Assert.False(sessions.IsSessionActive); + } + + private static void RouteEvent( + IoTestSessionController primary, + IoTestMultiSessionCoordinator sessions, + Iec61850EventEntry entry) + { + primary.Enqueue(entry); + sessions.EnqueueAdditional(entry); + } + + private static Fixture CreateFixture() + { + var pointA = Point("A-1", "IED_A", "192.168.81.10", "IED_ALD0/GGIO1.Ind1.stVal"); + var pointB = Point("B-1", "IED_B", "192.168.81.11", "IED_BLD0/GGIO1.Ind2.stVal"); + var iedA = Ied("IED_A", "192.168.81.10", pointA); + var iedB = Ied("IED_B", "192.168.81.11", pointB); + var project = new IoTestProject + { + ProjectId = "P2-PARALLEL", + SchemaVersion = "ARSAS-FAT-IO-1.0", + ProjectName = "P2 Parallel Evidence", + SourceWorkbookName = "P2.xlsx", + SourceWorkbookSha256 = new string('a', 64), + Ieds = { iedA, iedB } + }; + project.InitializeRuntimeNotifications(); + + var deviceA = Device("device-a", iedA, pointA); + var deviceB = Device("device-b", iedB, pointB); + var devices = new[] { deviceA, deviceB }; + new IoTestLiveBindingService().Bind(project, devices); + + var root = Path.Combine(Path.GetTempPath(), "ARSAS.Tests", "P2", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + return new Fixture(project, iedA, iedB, pointA, pointB, deviceA, deviceB, devices, root); + } + + private static IoTestPointPlan Point( + string id, + string iedName, + string ipAddress, + string reference) => new() + { + TestPointId = id, + IedName = iedName, + IpAddress = ipAddress, + SignalName = id, + ObjectReference = reference, + FunctionalConstraint = "ST", + ExpectedOnText = "ON", + ExpectedOffText = "OFF", + LogicalDevice = iedName + "LD0", + LogicalNode = "GGIO1", + DataObject = "Ind", + DataAttribute = "stVal", + SourceIecReference = reference, + EventLogSearchReference = reference, + ReportDisplayReference = reference + " [ST]", + BindingStatus = "CID_DATASET_EXACT", + TestEnabled = true, + ImportReady = true + }; + + private static IoTestIedPlan Ied(string name, string ipAddress, IoTestPointPlan point) => new() + { + IedName = name, + IpAddress = ipAddress, + IedRole = "Protection IED", + TestPoints = { point } + }; + + private static Iec61850MonitorDevice Device( + string deviceId, + IoTestIedPlan ied, + IoTestPointPlan point) + { + var device = new Iec61850MonitorDevice + { + DeviceId = deviceId, + Name = ied.IedName, + SclIedName = ied.IedName, + IpAddress = ied.IpAddress, + Port = 102, + IsConnected = true, + IsMonitoring = true, + Status = "Monitoring" + }; + device.Points.Add(new Iec61850MonitorPoint + { + DeviceId = deviceId, + DeviceName = ied.IedName, + IpAddress = ied.IpAddress, + SignalName = point.SignalName, + IecReference = point.ObjectReference, + FunctionalConstraint = "ST", + Value = "False", + Quality = "Good", + DeviceTimestamp = "2026-09-02T09:00:00.000Z", + SourceMode = "BRCB", + Sequence = 0, + Status = "Live" + }); + return device; + } + + private static IoTestSessionController NewController( + IoTestProject project, + IReadOnlyCollection devices, + string root) => new( + project, + key => devices.FirstOrDefault(device => + key.Equals(device.DeviceId, StringComparison.OrdinalIgnoreCase) || + key.Equals(device.Name, StringComparison.OrdinalIgnoreCase) || + key.Equals(device.IpAddress, StringComparison.OrdinalIgnoreCase)), + action => action(), + root); + + private static Iec61850EventEntry Event( + Iec61850MonitorDevice device, + IoTestPointPlan point, + bool oldValue, + bool newValue, + long sequence) => new() + { + Sequence = sequence, + DeviceId = device.DeviceId, + PointKey = device.Points[0].PointKey, + DeviceTimestamp = new DateTimeOffset(2026, 9, 2, 9, 0, 0, TimeSpan.Zero) + .AddMilliseconds(sequence * 100) + .ToString("O"), + DeviceName = device.Name, + IpAddress = device.IpAddress, + SignalName = point.SignalName, + IecReference = point.ObjectReference, + OldValue = oldValue ? "True" : "False", + NewValue = newValue ? "True" : "False", + Quality = "Good", + SourceMode = "BRCB", + Reason = "data-change" + }; + + private sealed record Fixture( + IoTestProject Project, + IoTestIedPlan IedA, + IoTestIedPlan IedB, + IoTestPointPlan PointA, + IoTestPointPlan PointB, + Iec61850MonitorDevice DeviceA, + Iec61850MonitorDevice DeviceB, + IReadOnlyCollection Devices, + string Root); +} From 5b3f68f91525386a4232454557c0addaf02f9a66 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:05:03 +0700 Subject: [PATCH 061/112] Lock P2 per-IED evidence session contract --- .../IoFatMultiIedConnectionRegressionTests.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs index dca5f2a79..152cef83e 100644 --- a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs @@ -146,9 +146,6 @@ public void P1_ConnectAndAcquisitionDoNotUseTestCheckboxAsAuthority() Assert.Contains("point.TestEnabled || IsSclDataSetAuthority(point)", resolver, StringComparison.Ordinal); Assert.Contains("point.IsIncludedInFat", resolver, StringComparison.Ordinal); - // Connect passes no operator checkbox subset. Start FAT still creates captureScope, - // but its preparation also uses the full IED acquisition scope; only Session.Start - // receives the selected live evidence rows. Assert.DoesNotContain("var enabledReady = targetIed.TestPoints", contextUx, StringComparison.Ordinal); Assert.Contains("private Task PrepareIndependentIedConnectionAsync", contextUx, StringComparison.Ordinal); Assert.Contains("Project,\n targetIed,\n progress);", contextUx, StringComparison.Ordinal); @@ -156,8 +153,6 @@ public void P1_ConnectAndAcquisitionDoNotUseTestCheckboxAsAuthority() Assert.Contains("point.IsIncludedInFat && point.TestEnabled && point.ImportReady", contextUx, StringComparison.Ordinal); Assert.Contains("var result = Session.Start(selectedIed, liveCaptureScope);", contextUx, StringComparison.Ordinal); - // Runtime StartMonitoringAsync still expects IsSelected. P1 therefore uses a - // device-local temporary bulk arm, then restores the real Engineering/TEST state. Assert.Contains("device.BeginBulkSignalSelection();", monitor, StringComparison.Ordinal); Assert.Contains("signal.IsSelected = acquisitionSet.Contains(signal);", monitor, StringComparison.Ordinal); Assert.Contains("signal.IsSelected = wasSelected;", monitor, StringComparison.Ordinal); @@ -167,14 +162,17 @@ public void P1_ConnectAndAcquisitionDoNotUseTestCheckboxAsAuthority() } [Fact] - public void P1_SourceContract_UsesPerIedPreparationWithoutWeakeningEvidenceIsolation() + public void P2_SourceContract_UsesPerIedEvidenceLeavesWithoutWeakeningDeviceIsolation() { var autoConnect = ReadRepoFile("MainWindow.IoTesting.AutoConnect.cs"); var contextUx = ReadRepoFile("IoListTestingWindow.ContextUx.cs"); var progressUx = ReadRepoFile("IoListTestingWindow.RealPreparationProgress.cs"); var connectUx = ReadRepoFile("IoListTestingWindow.MultiIedConnectionUx.cs"); var printPreview = ReadRepoFile("IoListTestingWindow.PrintPreview.cs"); - var session = ReadRepoFile("Services/IoTesting/IoTestSessionController.cs"); + var leaf = ReadRepoFile("Services/IoTesting/IoTestSessionController.cs"); + var coordinator = ReadRepoFile("Services/IoTesting/IoTestMultiSessionCoordinator.cs"); + var runtimeWiring = ReadRepoFile("MainWindow.IoTesting.MultiSessionEvidence.cs"); + var window = ReadRepoFile("IoListTestingWindow.xaml.cs"); Assert.Contains("_ioTestLiveBindingService.BindIed(ied, Devices)", autoConnect, StringComparison.Ordinal); Assert.DoesNotContain("_ioTestLiveBindingService.Bind(project, Devices)", autoConnect, StringComparison.Ordinal); @@ -192,10 +190,19 @@ public void P1_SourceContract_UsesPerIedPreparationWithoutWeakeningEvidenceIsola Assert.Contains("Other IED connection workflows keep running", connectUx, StringComparison.Ordinal); Assert.Contains("new Binding(nameof(SelectedIed))", connectUx, StringComparison.Ordinal); - // P1 parallelizes connection/monitoring only. The evidence controller remains - // intentionally single-active so relay transitions cannot enter the wrong journal. - Assert.Contains("Stop the active FAT session before starting another IED.", session, StringComparison.Ordinal); - Assert.Contains("entry.DeviceId.Equals(activeDevice.DeviceId", session, StringComparison.Ordinal); + // The leaf still refuses a second IED because it is intentionally one-Ied-only. + // P2 parallelism comes from one leaf/journal per IED, never by weakening the leaf. + Assert.Contains("Stop the active FAT session before starting another IED.", leaf, StringComparison.Ordinal); + Assert.Contains("entry.DeviceId.Equals(activeDevice.DeviceId", leaf, StringComparison.Ordinal); + Assert.Contains("class IoTestMultiSessionCoordinator", coordinator, StringComparison.Ordinal); + Assert.Contains("ActiveSessionCount", coordinator, StringComparison.Ordinal); + Assert.Contains("GetOrCreateController", coordinator, StringComparison.Ordinal); + Assert.Contains("EnqueueAdditional", coordinator, StringComparison.Ordinal); + Assert.Contains("!ReferenceEquals(controller, _primaryController)", coordinator, StringComparison.Ordinal); + Assert.Contains("AttachIoTestParallelEvidenceSessions", runtimeWiring, StringComparison.Ordinal); + Assert.Contains("_runtime.PointUpdated += coordinator.EnqueueAdditional", runtimeWiring, StringComparison.Ordinal); + Assert.Contains("IoTestMultiSessionCoordinator Session", window, StringComparison.Ordinal); + Assert.Contains("Session.StopAll", window, StringComparison.Ordinal); } private static IoTestPointPlan Point( From 345058f7f8e5ed17a8b21a015f739ed0fffdcff4 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:06:55 +0700 Subject: [PATCH 062/112] Route runtime snapshots to parallel FAT leaves --- MainWindow.IoTesting.MultiSessionEvidence.cs | 39 ++++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/MainWindow.IoTesting.MultiSessionEvidence.cs b/MainWindow.IoTesting.MultiSessionEvidence.cs index a8a7e1582..05c385b92 100644 --- a/MainWindow.IoTesting.MultiSessionEvidence.cs +++ b/MainWindow.IoTesting.MultiSessionEvidence.cs @@ -1,13 +1,16 @@ +using ArIED61850Tester.Models; using ArIED61850Tester.Services.IoTesting; namespace ArIED61850Tester; public partial class MainWindow { + private IoTestMultiSessionCoordinator? _activeIoTestMultiSessionCoordinator; + /// /// P2 attaches the FAT window's additional per-IED evidence leaves to the same /// Engineering IEC 61850 runtime. The existing primary controller keeps its legacy - /// Runtime_IoTestPointUpdated route; only sibling leaves use EnqueueAdditional so a + /// Runtime_IoTestPointUpdated route; only sibling leaves use the additional route so a /// primary event can never be journaled twice. /// internal void AttachIoTestParallelEvidenceSessions( @@ -19,14 +22,42 @@ internal void AttachIoTestParallelEvidenceSessions( coordinator.ConfigureSiblingFactory( () => CreateIoTestSession(coordinator.Project, evidenceRoot)); - _runtime.PointUpdated -= coordinator.EnqueueAdditional; - _runtime.PointUpdated += coordinator.EnqueueAdditional; + _activeIoTestMultiSessionCoordinator = coordinator; + _runtime.PointUpdated -= Runtime_IoTestAdditionalPointUpdated; + _runtime.PointUpdated += Runtime_IoTestAdditionalPointUpdated; } internal void DetachIoTestParallelEvidenceSessions(IoTestMultiSessionCoordinator coordinator) { if (coordinator == null) return; - _runtime.PointUpdated -= coordinator.EnqueueAdditional; + if (ReferenceEquals(_activeIoTestMultiSessionCoordinator, coordinator)) + _activeIoTestMultiSessionCoordinator = null; + _runtime.PointUpdated -= Runtime_IoTestAdditionalPointUpdated; + } + + private void Runtime_IoTestAdditionalPointUpdated(Iec61850PointSnapshot snapshot) + { + var coordinator = _activeIoTestMultiSessionCoordinator; + if (coordinator == null) + return; + + var point = snapshot.Point; + coordinator.EnqueueAdditional(new Iec61850EventEntry + { + Sequence = Interlocked.Increment(ref _ioTestObservationSequence), + DeviceId = point.DeviceId, + PointKey = point.PointKey, + DeviceTimestamp = snapshot.DeviceTimestamp, + DeviceName = point.DeviceName, + IpAddress = point.IpAddress, + SignalName = point.SignalName, + IecReference = point.IecReference, + OldValue = snapshot.PreviousValue, + NewValue = snapshot.Value, + Quality = snapshot.Quality, + SourceMode = snapshot.SourceMode, + Reason = snapshot.Reason + }); } } From 0d162143656045597c557a98a6f2f6113dd38228 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:09:19 +0700 Subject: [PATCH 063/112] Align P2 runtime wiring regression --- tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs index 152cef83e..1beea7f49 100644 --- a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs @@ -200,7 +200,8 @@ public void P2_SourceContract_UsesPerIedEvidenceLeavesWithoutWeakeningDeviceIsol Assert.Contains("EnqueueAdditional", coordinator, StringComparison.Ordinal); Assert.Contains("!ReferenceEquals(controller, _primaryController)", coordinator, StringComparison.Ordinal); Assert.Contains("AttachIoTestParallelEvidenceSessions", runtimeWiring, StringComparison.Ordinal); - Assert.Contains("_runtime.PointUpdated += coordinator.EnqueueAdditional", runtimeWiring, StringComparison.Ordinal); + Assert.Contains("_runtime.PointUpdated += Runtime_IoTestAdditionalPointUpdated", runtimeWiring, StringComparison.Ordinal); + Assert.Contains("coordinator.EnqueueAdditional(new Iec61850EventEntry", runtimeWiring, StringComparison.Ordinal); Assert.Contains("IoTestMultiSessionCoordinator Session", window, StringComparison.Ordinal); Assert.Contains("Session.StopAll", window, StringComparison.Ordinal); } From 02f64bd12dfe636fc25350ff728e107a98829f97 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:12:05 +0700 Subject: [PATCH 064/112] Project P2 session state by selected IED --- Services/IoTesting/IoTestMultiSessionCoordinator.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Services/IoTesting/IoTestMultiSessionCoordinator.cs b/Services/IoTesting/IoTestMultiSessionCoordinator.cs index 73c20a61b..491267a3f 100644 --- a/Services/IoTesting/IoTestMultiSessionCoordinator.cs +++ b/Services/IoTesting/IoTestMultiSessionCoordinator.cs @@ -52,8 +52,14 @@ private IoTestSessionController? SelectedController } public int ActiveSessionCount => ControllerSnapshot().Count(controller => controller.IsSessionActive); - public bool IsSessionActive => ActiveSessionCount > 0; + public bool HasActiveSessions => ActiveSessionCount > 0; + + // Session-facing properties are deliberately selected-context projections. This keeps + // existing FAT UI bindings intuitive: when IED A is running and the operator selects + // IED B, B still shows Start FAT and remains editable. Workspace-close/export code uses + // HasActiveSessions/ActiveSessionCount for the global safety boundary. public bool IsSelectedSessionActive => SelectedController?.IsSessionActive == true; + public bool IsSessionActive => IsSelectedSessionActive; public bool CanStart => _selectedIed != null && !IsSelectedSessionActive; public bool CanPause => SelectedController?.CanPause == true; public bool CanResume => SelectedController?.CanResume == true; @@ -298,6 +304,7 @@ private void RaiseProjectionProperties() { Raise(nameof(SelectedIed)); Raise(nameof(ActiveSessionCount)); + Raise(nameof(HasActiveSessions)); Raise(nameof(IsSessionActive)); Raise(nameof(IsSelectedSessionActive)); Raise(nameof(CanStart)); From 6451d863b27e64d063e9eae6fcc9b3f590b461cf Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 17:14:44 +0700 Subject: [PATCH 065/112] Guard P2 export and close across all IED sessions --- IoListTestingWindow.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IoListTestingWindow.xaml.cs b/IoListTestingWindow.xaml.cs index 70f8db21c..84f37fb94 100644 --- a/IoListTestingWindow.xaml.cs +++ b/IoListTestingWindow.xaml.cs @@ -349,7 +349,7 @@ private async void ExportHandover_Click(object sender, RoutedEventArgs e) private bool EnsureSessionSealedForExport(string outputName) { - if (!Session.IsSessionActive) + if (!Session.HasActiveSessions) return true; MessageBox.Show( @@ -379,7 +379,7 @@ private void Window_Closing(object? sender, CancelEventArgs e) return; } - if (Session.IsSessionActive) + if (Session.HasActiveSessions) { var activeCount = Session.ActiveSessionCount; var answer = MessageBox.Show( From ba56d17f278b84f0bbbdac326172d27ea77305ad Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 18:01:51 +0700 Subject: [PATCH 066/112] Align P2 regression with selected-context session state --- .../IoFatParallelEvidenceSessionRegressionTests.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs b/tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs index e330d3662..00c51cda2 100644 --- a/tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatParallelEvidenceSessionRegressionTests.cs @@ -53,6 +53,7 @@ public void TwoIeds_CanCaptureEvidenceConcurrently_WithStrictDeviceIsolation() var stopA = sessions.Stop("IED A complete"); Assert.True(stopA.Succeeded, stopA.Message); Assert.False(sessions.IsSessionActive); + Assert.False(sessions.HasActiveSessions); Assert.Equal(0, sessions.ActiveSessionCount); var evidenceFiles = Directory.GetFiles(fixture.Root, "*.evidence.jsonl", SearchOption.AllDirectories); @@ -75,25 +76,32 @@ public void SelectedContext_EditAndControlState_IsIndependentFromSiblingSession( sessions.SelectContext(fixture.IedA); Assert.True(sessions.Start(fixture.IedA, new[] { fixture.PointA }).Succeeded); Assert.True(sessions.IsSessionActive); + Assert.True(sessions.HasActiveSessions); Assert.True(sessions.IsSelectedSessionActive); Assert.False(sessions.CanEditPlan); Assert.False(sessions.CanStart); sessions.SelectContext(fixture.IedB); - Assert.True(sessions.IsSessionActive); // A is still running globally. + // IsSessionActive is selected-context state for existing UI bindings. The global + // close/export safety boundary remains HasActiveSessions/ActiveSessionCount. + Assert.False(sessions.IsSessionActive); + Assert.True(sessions.HasActiveSessions); // A is still running globally. Assert.False(sessions.IsSelectedSessionActive); Assert.True(sessions.CanEditPlan); Assert.True(sessions.CanStart); Assert.Null(sessions.ActiveIed); Assert.True(sessions.Start(fixture.IedB, new[] { fixture.PointB }).Succeeded); + Assert.True(sessions.IsSessionActive); + Assert.True(sessions.HasActiveSessions); Assert.True(sessions.IsSelectedSessionActive); Assert.False(sessions.CanEditPlan); Assert.Equal(2, sessions.ActiveSessionCount); Assert.True(sessions.StopAll("test cleanup").Succeeded); Assert.False(sessions.IsSessionActive); + Assert.False(sessions.HasActiveSessions); } private static void RouteEvent( From 99ecabe745a3c0017498159aac4259c912a5e52c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 18:04:14 +0700 Subject: [PATCH 067/112] Keep P2 FAT plan locks scoped to owning IED --- IoListTestingWindow.FatV2Ux.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/IoListTestingWindow.FatV2Ux.cs b/IoListTestingWindow.FatV2Ux.cs index 8a0d2b8d9..60bf74850 100644 --- a/IoListTestingWindow.FatV2Ux.cs +++ b/IoListTestingWindow.FatV2Ux.cs @@ -17,14 +17,12 @@ public partial class IoListTestingWindow private readonly BooleanToVisibilityConverter _fatBooleanVisibility = new(); private readonly IoFatSelectedIedPlanEditabilityConverter _fatPlanEditabilityConverter = new(); - // P0.3: plan ownership is per IED. Another IED may be preparing or may own the - // single active evidence session without freezing the selected IED's checkbox scope. - // Only the IED whose connection/session is consuming that scope is immutable. + // P0.3/P2: plan ownership is per IED. Connection preparation and evidence sessions + // lock only the owning IED; sibling IEDs remain independently editable. public bool SelectedCanEditPlan => SelectedIed is not null && CanEditIedPlan(SelectedIed); private bool CanEditIedPlan(IoTestIedPlan ied) - => !ied.IsPreparing && - (!Session.IsSessionActive || !ReferenceEquals(Session.ActiveIed, ied)); + => !ied.IsPreparing && !Session.IsIedSessionActive(ied); private bool CanEditPointPlan(IoTestPointPlan point) { From 254bc9175b016c81978618fac115298067c0e72d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Wed, 2 Sep 2026 18:04:42 +0700 Subject: [PATCH 068/112] Regress P2 per-IED restore and remove locks --- tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs index 8f1e33d89..668dd4c3a 100644 --- a/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatV2UiLockRegressionTests.cs @@ -13,11 +13,11 @@ public void RuntimeGrid_LocksOnlyTheIedThatOwnsPreparationOrEvidenceScope() Assert.DoesNotContain("DataContext.CanEditPlan", source, StringComparison.Ordinal); Assert.Contains("public bool SelectedCanEditPlan => SelectedIed is not null && CanEditIedPlan(SelectedIed);", source, StringComparison.Ordinal); Assert.Contains("!ied.IsPreparing", source, StringComparison.Ordinal); - Assert.Contains("!Session.IsSessionActive || !ReferenceEquals(Session.ActiveIed, ied)", source, StringComparison.Ordinal); + Assert.Contains("!Session.IsIedSessionActive(ied)", source, StringComparison.Ordinal); - // The TEST checkbox binds directly to the selected IED and session owner so the - // owning IED locks synchronously; another IED remains editable without waiting for - // a dispatcher-level window-property refresh. + // The TEST checkbox binds directly to the selected IED and selected-session + // projection so the owning IED locks synchronously; another IED remains editable + // without waiting for a dispatcher-level window-property refresh. Assert.Contains("var editability = new MultiBinding", source, StringComparison.Ordinal); Assert.Contains("DataContext.SelectedIed", source, StringComparison.Ordinal); Assert.Contains("DataContext.Session.ActiveIed", source, StringComparison.Ordinal); @@ -61,6 +61,7 @@ public void RemovedSignals_RestoreLockIsEvaluatedPerOwningIed() var workspace = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); var removed = File.ReadAllText(FindRepoFile("RemovedFatSignalsWindow.cs")); + Assert.Contains("!Session.IsIedSessionActive(ied)", workspace, StringComparison.Ordinal); Assert.Contains("new RemovedFatSignalsWindow(Project, CanEditPointPlan)", workspace, StringComparison.Ordinal); Assert.Contains("Func? canEditPoint", removed, StringComparison.Ordinal); Assert.Contains("canEditPoint?.Invoke(point) ?? true", removed, StringComparison.Ordinal); From 30e1237ac8e4748ec2a7cf9746e1c0d768fbf187 Mon Sep 17 00:00:00 2001 From: masarray Date: Thu, 3 Sep 2026 09:54:17 +0700 Subject: [PATCH 069/112] Unify Engineering and FAT SCL signal workspace --- IoListTestingWindow.ContextUx.cs | 16 +- IoListTestingWindow.FatV2Ux.cs | 7 +- MainWindow.IoTesting.AutoConnect.cs | 4 +- MainWindow.IoTesting.SclAppend.cs | 30 +++- MainWindow.IoTesting.cs | 32 +++- MainWindow.SharedSclWorkspace.cs | 165 ++++++++++++++++++ MainWindow.WorkspaceModeSwitch.cs | 12 +- MainWindow.xaml.cs | 61 ++++++- .../IoFatEngineeringSelectionBridge.cs | 52 +++--- ...eshSclSelectionAuthorityRegressionTests.cs | 12 +- ...rementAndWorkspaceBridgeRegressionTests.cs | 23 ++- tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs | 20 +++ 12 files changed, 373 insertions(+), 61 deletions(-) create mode 100644 MainWindow.SharedSclWorkspace.cs diff --git a/IoListTestingWindow.ContextUx.cs b/IoListTestingWindow.ContextUx.cs index b186c4473..dd380db04 100644 --- a/IoListTestingWindow.ContextUx.cs +++ b/IoListTestingWindow.ContextUx.cs @@ -13,6 +13,7 @@ public partial class IoListTestingWindow { private bool _selectedIedContextInstalled; private bool _contextRefreshScheduled; + private bool _contextRowRefreshScheduled; public bool SelectedCanStartWorkflow => SelectedIed != null && !SelectedIed.IsPreparing && Session.CanStart; @@ -368,21 +369,26 @@ private void ContextIed_PropertyChanged(object? sender, PropertyChangedEventArgs if (e.PropertyName == nameof(IoTestIedPlan.IsPreparing)) RaisePreparationProperties(); - ScheduleContextRefresh(); + ScheduleContextRefresh(e.PropertyName is + nameof(IoTestIedPlan.EnabledCount) or + nameof(IoTestIedPlan.RemovedCount)); } - private void ScheduleContextRefresh() + private void ScheduleContextRefresh(bool refreshRows = false) { + _contextRowRefreshScheduled |= refreshRows; if (_contextRefreshScheduled) return; _contextRefreshScheduled = true; Dispatcher.BeginInvoke(new Action(() => { _contextRefreshScheduled = false; + var refreshFilteredRows = _contextRowRefreshScheduled; + _contextRowRefreshScheduled = false; // Session/runtime notifications update summaries and visible cells through - // normal bindings. Re-filtering the complete collection for every live - // value used to interrupt scrolling and swallow Capture clicks. - RefreshFatV2WorkspaceUx(); + // normal bindings. Re-filter only when shared selection/disposition changes; + // live value traffic must never interrupt scrolling or Capture clicks. + RefreshFatV2WorkspaceUx(refreshFilteredRows); Raise(nameof(SelectedIedSummary)); RaiseSelectedIedContextProperties(); }), DispatcherPriority.Background); diff --git a/IoListTestingWindow.FatV2Ux.cs b/IoListTestingWindow.FatV2Ux.cs index 60bf74850..f344e2c90 100644 --- a/IoListTestingWindow.FatV2Ux.cs +++ b/IoListTestingWindow.FatV2Ux.cs @@ -66,7 +66,12 @@ private void RefreshFatV2WorkspaceUx(bool refreshRows = false) if (view != null && !ReferenceEquals(view, _fatSignalsView)) { _fatSignalsView = view; - view.Filter = item => item is IoTestPointPlan point && point.IsIncludedInFat; + // FAT projects every signal selected by the shared Engineering authority. + // Disposition is a FAT-only overlay; either an Engineering deselection or + // Remove from FAT hides the row without deleting its identity/evidence. + view.Filter = item => item is IoTestPointPlan point && + point.IsIncludedInFat && + point.TestEnabled; refreshRows = true; } if (refreshRows) diff --git a/MainWindow.IoTesting.AutoConnect.cs b/MainWindow.IoTesting.AutoConnect.cs index 371acf696..6ee85f293 100644 --- a/MainWindow.IoTesting.AutoConnect.cs +++ b/MainWindow.IoTesting.AutoConnect.cs @@ -505,7 +505,9 @@ private int SynchronizeImportedSclFatWithEngineering( Devices.Add(device); } - var preserveEngineeringSelection = existed && device.Signals.Any(signal => signal.IsSelected); + var preserveEngineeringSelection = existed && + (_sharedSclSelectionAuthorityDeviceIds.Contains(device.DeviceId) || + device.Signals.Any(signal => signal.IsSelected)); if (!AttachIoFatSclRuntimeAuthority(ied, device)) continue; diff --git a/MainWindow.IoTesting.SclAppend.cs b/MainWindow.IoTesting.SclAppend.cs index 30a3fd388..a40254f71 100644 --- a/MainWindow.IoTesting.SclAppend.cs +++ b/MainWindow.IoTesting.SclAppend.cs @@ -43,7 +43,9 @@ internal async Task OpenSclForLoadedFatAppendAsync(IoListTestingWindow window) internal async Task AppendSclIedsToLoadedFatAsync( IoListTestingWindow window, - IReadOnlyCollection sclPaths) + IReadOnlyCollection sclPaths, + bool? useStaticSelection = null, + bool selectionAlreadyApplied = false) { ArgumentNullException.ThrowIfNull(window); ArgumentNullException.ThrowIfNull(sclPaths); @@ -91,6 +93,12 @@ internal async Task AppendSclIedsToLoadedFatAsync( "The selected SCL source contains no new IED endpoint; every imported IED is already present in this FAT workspace."); } + var selectionMode = useStaticSelection.HasValue + ? (useStaticSelection.Value + ? SclSignalSelectionMode.StaticDataSet + : SclSignalSelectionMode.Manual) + : PromptSclSignalSelectionMode(window, addedIeds.Length); + // Persist source bytes before publishing the new plans to the active project. // This gate serializes only append operations; it does not lock any IED's // connection preparation or evidence journal. @@ -119,6 +127,26 @@ await window.Storage.AddSourcesAsync( // Existing running/preparing IEDs are deliberately untouched. Only the newly // appended endpoints join Engineering and the loaded FAT explorer. SynchronizeImportedSclFatWithEngineering(window.Project, addedIeds); + RegisterSharedSclSourcePaths(window.Project, addedIeds, import.SourceInputs); + if (selectionMode == SclSignalSelectionMode.Manual && !selectionAlreadyApplied) + { + await ApplyManualSelectionToFatProjectAsync( + window.Project, + addedIeds, + window, + resetSelection: true); + } + else + { + foreach (var ied in addedIeds) + { + var device = ResolveIoTestDevice(ied.LiveDeviceId) + ?? ResolveIoTestDevice(ied.IpAddress) + ?? ResolveIoTestDevice(ied.IedName); + if (device is not null) + MarkSharedSelectionAuthority(device); + } + } window.RegisterAddedIeds(addedIeds); window.Storage?.ScheduleSave(); diff --git a/MainWindow.IoTesting.cs b/MainWindow.IoTesting.cs index ea362be77..13d73d3e2 100644 --- a/MainWindow.IoTesting.cs +++ b/MainWindow.IoTesting.cs @@ -266,11 +266,19 @@ private async void OpenSclFatTesting_Click(object sender, RoutedEventArgs e) if (dialog.ShowDialog(this) != true) return; - SetStatus($"Building FAT scope from {dialog.FileNames.Length} SCL source(s)…"); + var selectionMode = PromptSclSignalSelectionMode(this, dialog.FileNames.Length); + await OpenSclFatSourcesAsync(dialog.FileNames, selectionMode); + } + + private async Task OpenSclFatSourcesAsync( + IReadOnlyCollection sclPaths, + SclSignalSelectionMode? selectionMode) + { + SetStatus($"Building shared Engineering/FAT workspace from {sclPaths.Count} SCL source(s)…"); try { var import = await _ioFatSclProjectImportService.ImportAsync( - dialog.FileNames, + sclPaths, cancellationToken: _applicationCancellation.Token); if (import.Project.SignalCount == 0) { @@ -299,6 +307,26 @@ private async void OpenSclFatTesting_Click(object sender, RoutedEventArgs e) // restored but before the window is shown, and establish one shared selection // authority. No discovery or second model is created during mode switches. SynchronizeImportedSclFatWithEngineering(launch.Project); + RegisterSharedSclSourcePaths(launch.Project, launch.Project.Ieds, import.SourceInputs); + if (selectionMode == SclSignalSelectionMode.Manual) + { + await ApplyManualSelectionToFatProjectAsync( + launch.Project, + launch.Project.Ieds, + this, + resetSelection: true); + } + else + { + foreach (var ied in launch.Project.Ieds) + { + var device = ResolveIoTestDevice(ied.LiveDeviceId) + ?? ResolveIoTestDevice(ied.IpAddress) + ?? ResolveIoTestDevice(ied.IedName); + if (device is not null) + MarkSharedSelectionAuthority(device); + } + } var warningCount = import.Findings.Count(finding => finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase) || finding.Severity.Equals("High", StringComparison.OrdinalIgnoreCase) || diff --git a/MainWindow.SharedSclWorkspace.cs b/MainWindow.SharedSclWorkspace.cs new file mode 100644 index 000000000..eefbcf678 --- /dev/null +++ b/MainWindow.SharedSclWorkspace.cs @@ -0,0 +1,165 @@ +using System.IO; +using System.Windows; +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services.IoTesting; + +namespace ArIED61850Tester; + +public partial class MainWindow +{ + private enum SclSignalSelectionMode + { + StaticDataSet, + Manual + } + + // This records that the operator has made an explicit selection decision for the + // shared SCL device. It is intentionally separate from "any selected signal" so an + // intentionally empty manual selection is preserved when Engineering opens FAT. + private readonly HashSet _sharedSclSelectionAuthorityDeviceIds = + new(StringComparer.OrdinalIgnoreCase); + + private SclSignalSelectionMode PromptSclSignalSelectionMode(Window owner, int iedCount) + { + var result = MessageBox.Show( + owner, + $"Choose the signal authority for this SCL import ({iedCount} item(s)). The result is used by both Engineering and FAT.\n\n" + + "YES — Use Static DataSet\n" + + "Select every authoritative static DataSet member.\n\n" + + "NO — Choose Signals Manually\n" + + "Open Signal Selection and use the same checkboxes in Engineering and FAT.", + "SCL Signal Selection", + MessageBoxButton.YesNo, + MessageBoxImage.Question, + MessageBoxResult.Yes); + return result == MessageBoxResult.Yes + ? SclSignalSelectionMode.StaticDataSet + : SclSignalSelectionMode.Manual; + } + + private void ApplyStaticDataSetSelection(Iec61850MonitorDevice device) + { + device.BeginBulkSignalSelection(); + try + { + foreach (var signal in device.Signals) + signal.IsSelected = !string.IsNullOrWhiteSpace(signal.DataSetReference); + } + finally + { + device.EndBulkSignalSelection(); + } + + SynchronizeAllEngineeringSelectionsToFat(device); + _sharedSclSelectionAuthorityDeviceIds.Add(device.DeviceId); + SaveSignalSelectionMemory(device); + device.RefreshComputed(); + } + + private void ClearSharedSignalSelection(Iec61850MonitorDevice device) + { + device.BeginBulkSignalSelection(); + try + { + foreach (var signal in device.Signals) + signal.IsSelected = false; + } + finally + { + device.EndBulkSignalSelection(); + } + } + + private void MarkSharedSelectionAuthority(Iec61850MonitorDevice device) + { + _sharedSclSelectionAuthorityDeviceIds.Add(device.DeviceId); + SaveSignalSelectionMemory(device); + } + + private string[] CurrentEngineeringSclSourcePaths() + => Devices + .Select(device => device.SclSourcePath) + .Where(path => !string.IsNullOrWhiteSpace(path) && File.Exists(path)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + private void RegisterSharedSclSourcePaths( + IoTestProject project, + IEnumerable ieds, + IReadOnlyCollection sourceInputs) + { + var uniquePathByFileName = sourceInputs + .GroupBy(input => Path.GetFileName(input.FilePath), StringComparer.OrdinalIgnoreCase) + .Where(group => group.Count() == 1) + .ToDictionary( + group => group.Key, + group => Path.GetFullPath(group.Single().FilePath), + StringComparer.OrdinalIgnoreCase); + var sourceById = project.Sources.ToDictionary(source => source.SourceId, StringComparer.OrdinalIgnoreCase); + + foreach (var ied in ieds) + { + var sourceId = ied.TestPoints + .Select(point => point.SignalAddress) + .FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)); + if (string.IsNullOrWhiteSpace(sourceId) || + !sourceById.TryGetValue(sourceId, out var source) || + !uniquePathByFileName.TryGetValue(source.FileName, out var sourcePath)) + { + continue; + } + + var device = ResolveIoTestDevice(ied.LiveDeviceId) + ?? ResolveIoTestDevice(ied.IpAddress) + ?? ResolveIoTestDevice(ied.IedName); + if (device is null) + continue; + + device.SclSourcePath = sourcePath; + device.SclSourceSha256 = source.Sha256; + } + } + + private async Task ApplyManualSelectionToFatProjectAsync( + IoTestProject project, + IEnumerable ieds, + Window owner, + bool resetSelection) + { + foreach (var ied in ieds) + { + var device = ResolveIoTestDevice(ied.LiveDeviceId) + ?? ResolveIoTestDevice(ied.IpAddress) + ?? ResolveIoTestDevice(ied.IedName); + if (device is null) + continue; + + if (resetSelection) + { + ClearSharedSignalSelection(device); + foreach (var point in ied.TestPoints) + point.TestEnabled = false; + } + + await OpenSignalSelectionWizardAsync( + device, + autoStartAfterSave: false, + ownerOverride: owner); + + // The FAT window is not yet attached during an initial FAT import, so perform + // the same bridge operation explicitly. Once loaded, property changes remain + // synchronized bidirectionally by the normal bridge handlers. + foreach (var signal in device.Signals) + { + IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( + signal, + signal.IsSelected, + ied, + device); + } + + MarkSharedSelectionAuthority(device); + } + } +} diff --git a/MainWindow.WorkspaceModeSwitch.cs b/MainWindow.WorkspaceModeSwitch.cs index e3977efbf..71983c289 100644 --- a/MainWindow.WorkspaceModeSwitch.cs +++ b/MainWindow.WorkspaceModeSwitch.cs @@ -136,11 +136,21 @@ internal void ShowEngineeringWorkspaceFromFat(IoListTestingWindow window) UpdateIoFatWorkspaceModeState(); } - private void OpenOrResumeIoFatWorkspace_Click(object sender, RoutedEventArgs e) + private async void OpenOrResumeIoFatWorkspace_Click(object sender, RoutedEventArgs e) { if (ShowLoadedIoFatWorkspace()) return; + // Engineering and FAT are two views over the same imported SCL workspace. If an + // Engineering SCL is already open, the primary FAT mode button projects that exact + // source and its existing checkbox authority without asking for another import. + var sharedSources = CurrentEngineeringSclSourcePaths(); + if (sharedSources.Length > 0) + { + await OpenSclFatSourcesAsync(sharedSources, selectionMode: null); + return; + } + if (sender is Button anchor) OpenIoFatWorkspaceMenu(anchor); } diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 86cbc6cc7..cb3127c04 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -188,6 +188,7 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) var refreshed = 0; var retained = 0; Iec61850MonitorDevice? firstImported = null; + var importedDevices = new List(); foreach (var workspace in document.Ieds) { @@ -200,6 +201,7 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) { retained++; firstImported ??= device; + importedDevices.Add(device); continue; } @@ -217,6 +219,7 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) ApplySclWorkspaceToDevice(device, document, workspace, signals); firstImported ??= device; + importedDevices.Add(device); } if (firstImported != null) @@ -231,10 +234,57 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) SetStatus(status); AddLog("INFO", "SCL", status); - if (firstImported != null && firstImported.Signals.Count > 0) + var selectableDevices = importedDevices + .Where(device => device.Signals.Count > 0) + .Distinct() + .ToArray(); + SclSignalSelectionMode? sharedSelectionMode = null; + if (selectableDevices.Length > 0) { - AddLog("INFO", "SCL", $"{firstImported.Name}: SCL model ready. Choose signals; saving the selection will continue to endpoint binding, connection, and monitoring."); - await OpenSignalSelectionWizardAsync(firstImported); + var selectionMode = PromptSclSignalSelectionMode(this, selectableDevices.Length); + sharedSelectionMode = selectionMode; + if (selectionMode == SclSignalSelectionMode.StaticDataSet) + { + foreach (var importedDevice in selectableDevices) + ApplyStaticDataSetSelection(importedDevice); + + AddLog("INFO", "SCL", $"Static DataSet selection is shared by Engineering and FAT for {selectableDevices.Length} IED workspace(s)."); + if (firstImported is { SelectedLiveSignalCount: > 0 }) + { + var connected = firstImported.IsConnected || + await ConnectUsingSavedModelAsync(firstImported); + if (connected && !firstImported.IsMonitoring) + await StartDeviceMonitorAsync(firstImported); + } + } + else + { + AddLog("INFO", "SCL", "Manual Signal Selection is shared by Engineering and FAT; each imported IED keeps its own checkbox state."); + foreach (var importedDevice in selectableDevices) + { + await OpenSignalSelectionWizardAsync( + importedDevice, + autoStartAfterSave: ReferenceEquals(importedDevice, firstImported)); + MarkSharedSelectionAuthority(importedDevice); + } + } + } + + // Importing through Engineering must update an already-loaded FAT project as + // the same workspace mutation. The selection decision has already been applied, + // so the hidden FAT view receives the new IEDs without a second prompt or reset. + if (_loadedIoFatWindow is { IsLoaded: true } loadedFat && + sharedSelectionMode.HasValue && + !loadedFat.Project.Sources.Any(source => + source.Sha256.Equals(document.SourceSha256, StringComparison.OrdinalIgnoreCase))) + { + var append = await AppendSclIedsToLoadedFatAsync( + loadedFat, + new[] { dialog.FileName }, + useStaticSelection: sharedSelectionMode == SclSignalSelectionMode.StaticDataSet, + selectionAlreadyApplied: true); + if (!append.Succeeded) + AddLog("WARN", "SCL/FAT", append.Message); } } catch (OperationCanceledException) @@ -857,7 +907,8 @@ private void ApplySclLiveComparison(Iec61850MonitorDevice device, IReadOnlyList< private async Task OpenSignalSelectionWizardAsync( Iec61850MonitorDevice device, int restoredSelectionCount = -1, - bool autoStartAfterSave = true) + bool autoStartAfterSave = true, + Window? ownerOverride = null) { if (device.Signals.Count == 0) { @@ -876,7 +927,7 @@ private async Task OpenSignalSelectionWizardAsync( device, restoredSelectionCount < 0 ? device.SelectedSignalCount : restoredSelectionCount) { - Owner = this, + Owner = ownerOverride ?? this, ShowInTaskbar = false, WindowStartupLocation = WindowStartupLocation.CenterOwner }; diff --git a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs index 1661d4fb4..5596d6ff8 100644 --- a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs +++ b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs @@ -18,41 +18,39 @@ public static int Initialize( ArgumentNullException.ThrowIfNull(ied); ArgumentNullException.ThrowIfNull(device); - // P0.1: raw SCL import is a fresh FAT selection authority. Every included static - // DataSet membership starts checked even when the same Engineering device already - // carries an older/narrower selection, or a matching local snapshot previously - // stored an unchecked state. Explicit ARSAS project/package continuation restores - // operator selection through the persistence path and does not call this initializer. - // - // The third parameter is intentionally retained for source compatibility with the - // existing synchronization caller. It no longer permits Engineering state to - // uncheck a newly imported direct-SCL FAT row. - _ = preserveExistingEngineeringSelection; - var changed = 0; foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsSclDataSetAuthority)) { - // Keep Removed Signals disposition independent. A row that is part of the active - // fresh-import scope defaults checked; restoring/removing a row remains an - // operator-authored FAT disposition action. - if (point.IsIncludedInFat && !point.TestEnabled) - { - point.TestEnabled = true; - changed++; - } - var signal = FindSignal(point, device); if (signal is null) continue; - // Fresh direct-SCL FAT owns the initial checkbox state. Push that state into the - // shared Engineering catalog instead of allowing an older Engineering selection - // to narrow the newly imported static DataSet scope. - var selected = point.TestEnabled && point.IsIncludedInFat; - if (signal.IsSelected != selected) + // SCL identity and operator selection have one authority shared by Engineering + // and FAT. When Engineering has already completed the import choice, its exact + // checkbox state (including an intentionally empty selection) initializes FAT. + // A brand-new FAT entry point instead starts from the imported static DataSet. + // FatDisposition remains orthogonal and is never changed here. + if (preserveExistingEngineeringSelection) + { + if (point.TestEnabled != signal.IsSelected) + { + point.TestEnabled = signal.IsSelected; + changed++; + } + } + else { - signal.IsSelected = selected; - changed++; + if (!point.TestEnabled) + { + point.TestEnabled = true; + changed++; + } + + if (!signal.IsSelected) + { + signal.IsSelected = true; + changed++; + } } } diff --git a/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs b/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs index 39160de33..9c81eb358 100644 --- a/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs @@ -13,15 +13,15 @@ public void DirectSclImporter_DefaultsStaticDatasetRowsToChecked() } [Fact] - public void FreshSclBridge_FatSelectionWinsOverOlderEngineeringSelection() + public void SclBridge_UsesExistingEngineeringSelectionWhenItWasAlreadyDecided() { var source = Read("Services/IoTesting/IoFatEngineeringSelectionBridge.cs"); - Assert.Contains("raw SCL import is a fresh FAT selection authority", source, StringComparison.Ordinal); - Assert.Contains("if (point.IsIncludedInFat && !point.TestEnabled)", source, StringComparison.Ordinal); + Assert.Contains("if (preserveExistingEngineeringSelection)", source, StringComparison.Ordinal); + Assert.Contains("point.TestEnabled != signal.IsSelected", source, StringComparison.Ordinal); + Assert.Contains("point.TestEnabled = signal.IsSelected;", source, StringComparison.Ordinal); Assert.Contains("point.TestEnabled = true;", source, StringComparison.Ordinal); - Assert.Contains("var selected = point.TestEnabled && point.IsIncludedInFat;", source, StringComparison.Ordinal); - Assert.DoesNotContain("var enabled = signal.IsSelected && point.IsIncludedInFat;", source, StringComparison.Ordinal); + Assert.DoesNotContain("_ = preserveExistingEngineeringSelection;", source, StringComparison.Ordinal); } [Fact] @@ -29,7 +29,7 @@ public void FreshSclBridge_DoesNotMergeSelectionWithRemovedSignalsDisposition() { var source = Read("Services/IoTesting/IoFatEngineeringSelectionBridge.cs"); - Assert.Contains("Keep Removed Signals disposition independent", source, StringComparison.Ordinal); + Assert.Contains("FatDisposition remains orthogonal", source, StringComparison.Ordinal); Assert.DoesNotContain("point.RestoreToFat();\n point.TestEnabled = true;", source, StringComparison.Ordinal); } diff --git a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs index c8deb469d..f2ee41d5c 100644 --- a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs +++ b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs @@ -100,7 +100,7 @@ public void FatWorkspace_ExposesIncrementalSclIedImport() } [Fact] - public void FreshSclSelection_OverridesOlderEngineeringSelection_ThenSynchronizesBothWays() + public void ExistingEngineeringSelection_InitializesFat_ThenSynchronizesBothWays() { const string dataSet = "IEDTHD/LLN0.Analog"; const string member = "IEDTHD/I_MHAI1.ThdA.phsA"; @@ -110,31 +110,30 @@ public void FreshSclSelection_OverridesOlderEngineeringSelection_ThenSynchronize var point = Point(member, runtime, dataSet); var ied = Ied(point); - // Simulate a stale persisted checkbox plus an older/narrower Engineering selection. - // Fresh raw SCL import must restore the included static DataSet member to checked and - // push that selection into Engineering instead of inheriting the older false state. - point.TestEnabled = false; + // Engineering is the already-decided shared selection authority. FAT must inherit + // even an intentionally unchecked signal rather than silently selecting it again. + point.TestEnabled = true; IoFatEngineeringSelectionBridge.Initialize( ied, device, preserveExistingEngineeringSelection: true); - Assert.True(point.TestEnabled); - Assert.True(signal.IsSelected); + Assert.False(point.TestEnabled); + Assert.False(signal.IsSelected); // After initialization the shared bridge remains bidirectional: Engineering changes // still update FAT, and FAT checkbox changes still update the Engineering catalog. - signal.IsSelected = false; + signal.IsSelected = true; Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( signal, - selected: false, + selected: true, ied, device)); - Assert.False(point.TestEnabled); + Assert.True(point.TestEnabled); Assert.True(point.IsIncludedInFat); - point.TestEnabled = true; + point.TestEnabled = false; Assert.True(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); - Assert.True(signal.IsSelected); + Assert.False(signal.IsSelected); } [Fact] diff --git a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs index 7c8cb7c90..c592c9524 100644 --- a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs +++ b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs @@ -12,6 +12,8 @@ public void MainWindow_AlwaysExposesEngineeringAndPersistentIoFatWorkspaceModes( Assert.Contains("IO LIST FAT · LOADED", source, StringComparison.Ordinal); Assert.Contains("_loadedIoFatWindow", source, StringComparison.Ordinal); Assert.Contains("ShowLoadedIoFatWorkspace", source, StringComparison.Ordinal); + Assert.Contains("CurrentEngineeringSclSourcePaths", source, StringComparison.Ordinal); + Assert.Contains("OpenSclFatSourcesAsync(sharedSources, selectionMode: null)", source, StringComparison.Ordinal); Assert.Contains("Continue loaded FAT project", source, StringComparison.Ordinal); Assert.Contains("Import SCL / CID files", source, StringComparison.Ordinal); Assert.Contains("Add SCL / CID to loaded FAT workspace", source, StringComparison.Ordinal); @@ -23,6 +25,24 @@ public void MainWindow_AlwaysExposesEngineeringAndPersistentIoFatWorkspaceModes( Assert.Contains("FrameworkElement.LoadedEvent", source, StringComparison.Ordinal); } + [Fact] + public void SclImport_UsesOneSelectionAuthorityAcrossEngineeringAndFat() + { + var workflow = File.ReadAllText(FindRepoFile("MainWindow.SharedSclWorkspace.cs")); + var engineering = File.ReadAllText(FindRepoFile("MainWindow.xaml.cs")); + var fat = File.ReadAllText(FindRepoFile("MainWindow.IoTesting.cs")); + var fatProjection = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); + + Assert.Contains("Use Static DataSet", workflow, StringComparison.Ordinal); + Assert.Contains("Choose Signals Manually", workflow, StringComparison.Ordinal); + Assert.Contains("_sharedSclSelectionAuthorityDeviceIds", workflow, StringComparison.Ordinal); + Assert.Contains("ApplyStaticDataSetSelection", engineering, StringComparison.Ordinal); + Assert.Contains("selectionAlreadyApplied: true", engineering, StringComparison.Ordinal); + Assert.Contains("ApplyManualSelectionToFatProjectAsync", fat, StringComparison.Ordinal); + Assert.Contains("point.IsIncludedInFat &&", fatProjection, StringComparison.Ordinal); + Assert.Contains("point.TestEnabled", fatProjection, StringComparison.Ordinal); + } + [Fact] public void FatWorkspace_ReturnsToEngineeringByHideAndShowWithoutUnloadingProject() { From f4cdbf79a7d373f2284754af398bddc218bd92cd Mon Sep 17 00:00:00 2001 From: masarray Date: Thu, 3 Sep 2026 10:23:54 +0700 Subject: [PATCH 070/112] Modernize shared SCL import workflow --- MainWindow.IoTesting.SclAppend.cs | 2 + MainWindow.IoTesting.cs | 19 +- MainWindow.SharedSclWorkspace.cs | 22 +- MainWindow.xaml.cs | 139 ++++++----- SclSignalSelectionModeWindow.xaml | 219 ++++++++++++++++++ SclSignalSelectionModeWindow.xaml.cs | 22 ++ design-qa.md | 39 +++- output/scl-signal-selection-comparison.png | Bin 0 -> 598548 bytes output/scl-signal-selection-modern.png | Bin 0 -> 65657 bytes tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs | 26 ++- 10 files changed, 413 insertions(+), 75 deletions(-) create mode 100644 SclSignalSelectionModeWindow.xaml create mode 100644 SclSignalSelectionModeWindow.xaml.cs create mode 100644 output/scl-signal-selection-comparison.png create mode 100644 output/scl-signal-selection-modern.png diff --git a/MainWindow.IoTesting.SclAppend.cs b/MainWindow.IoTesting.SclAppend.cs index a40254f71..a10852114 100644 --- a/MainWindow.IoTesting.SclAppend.cs +++ b/MainWindow.IoTesting.SclAppend.cs @@ -98,6 +98,8 @@ internal async Task AppendSclIedsToLoadedFatAsync( ? SclSignalSelectionMode.StaticDataSet : SclSignalSelectionMode.Manual) : PromptSclSignalSelectionMode(window, addedIeds.Length); + if (!selectionMode.HasValue) + return IoTestSessionActionResult.Failure("SCL append cancelled before the shared workspace was changed."); // Persist source bytes before publishing the new plans to the active project. // This gate serializes only append operations; it does not lock any IED's diff --git a/MainWindow.IoTesting.cs b/MainWindow.IoTesting.cs index 13d73d3e2..9add2f0f0 100644 --- a/MainWindow.IoTesting.cs +++ b/MainWindow.IoTesting.cs @@ -266,13 +266,16 @@ private async void OpenSclFatTesting_Click(object sender, RoutedEventArgs e) if (dialog.ShowDialog(this) != true) return; - var selectionMode = PromptSclSignalSelectionMode(this, dialog.FileNames.Length); - await OpenSclFatSourcesAsync(dialog.FileNames, selectionMode); + await OpenSclFatSourcesAsync( + dialog.FileNames, + selectionMode: null, + promptForSelection: true); } private async Task OpenSclFatSourcesAsync( IReadOnlyCollection sclPaths, - SclSignalSelectionMode? selectionMode) + SclSignalSelectionMode? selectionMode, + bool promptForSelection = false) { SetStatus($"Building shared Engineering/FAT workspace from {sclPaths.Count} SCL source(s)…"); try @@ -295,6 +298,16 @@ private async Task OpenSclFatSourcesAsync( return; } + if (promptForSelection && !selectionMode.HasValue) + { + selectionMode = PromptSclSignalSelectionMode(this, import.Project.Ieds.Count); + if (!selectionMode.HasValue) + { + SetStatus("SCL FAT import cancelled before the workspace was changed."); + return; + } + } + var launch = await IoTestWorkspaceBootstrapService.OpenSourcesAsync( import.Project, import.SourceInputs, diff --git a/MainWindow.SharedSclWorkspace.cs b/MainWindow.SharedSclWorkspace.cs index eefbcf678..1933aee85 100644 --- a/MainWindow.SharedSclWorkspace.cs +++ b/MainWindow.SharedSclWorkspace.cs @@ -20,20 +20,16 @@ private enum SclSignalSelectionMode private readonly HashSet _sharedSclSelectionAuthorityDeviceIds = new(StringComparer.OrdinalIgnoreCase); - private SclSignalSelectionMode PromptSclSignalSelectionMode(Window owner, int iedCount) + private SclSignalSelectionMode? PromptSclSignalSelectionMode(Window owner, int iedCount) { - var result = MessageBox.Show( - owner, - $"Choose the signal authority for this SCL import ({iedCount} item(s)). The result is used by both Engineering and FAT.\n\n" + - "YES — Use Static DataSet\n" + - "Select every authoritative static DataSet member.\n\n" + - "NO — Choose Signals Manually\n" + - "Open Signal Selection and use the same checkboxes in Engineering and FAT.", - "SCL Signal Selection", - MessageBoxButton.YesNo, - MessageBoxImage.Question, - MessageBoxResult.Yes); - return result == MessageBoxResult.Yes + var dialog = new SclSignalSelectionModeWindow(iedCount) + { + Owner = owner + }; + if (dialog.ShowDialog() != true) + return null; + + return dialog.UseStaticDataSet ? SclSignalSelectionMode.StaticDataSet : SclSignalSelectionMode.Manual; } diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index cb3127c04..4f55a7528 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -160,66 +160,85 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) { var dialog = new OpenFileDialog { - Title = "Open IEC 61850 SCL", + Title = "Open one or more IEC 61850 SCL files", Filter = "IEC 61850 SCL (*.scd;*.cid;*.icd;*.iid;*.ssd)|*.scd;*.cid;*.icd;*.iid;*.ssd|XML SCL (*.xml)|*.xml|All files (*.*)|*.*", CheckFileExists = true, - Multiselect = false + Multiselect = true }; if (dialog.ShowDialog(this) != true) return; - var sourceName = Path.GetFileName(dialog.FileName); - SetStatus($"Opening {sourceName} as an offline IEC 61850 design model…"); + var sourceLabel = dialog.FileNames.Length == 1 + ? Path.GetFileName(dialog.FileNames[0]) + : $"{dialog.FileNames.Length} SCL files"; + SetStatus($"Opening {sourceLabel} as one shared offline IEC 61850 workspace…"); try { - var document = await _sclWorkspaceService.OpenAsync( - dialog.FileName, - cancellationToken: _applicationCancellation.Token); - LogSclFindings(sourceName, document.Findings); - - if (document.Ieds.Count == 0) - { - SetStatus($"{sourceName}: no IED model was found."); - AddLog("WARN", "SCL", $"{sourceName}: the engine returned no IED workspace."); - return; - } - var added = 0; var refreshed = 0; var retained = 0; + var offlineCount = 0; + var endpointCount = 0; Iec61850MonitorDevice? firstImported = null; var importedDevices = new List(); + var importedSources = new List<(string Path, string Sha256)>(); - foreach (var workspace in document.Ieds) + foreach (var sourcePath in dialog.FileNames.Distinct(StringComparer.OrdinalIgnoreCase)) { - var device = Devices.FirstOrDefault(item => - item.SclSourceSha256.Equals(document.SourceSha256, StringComparison.OrdinalIgnoreCase) && - item.SclIedName.Equals(workspace.IedName, StringComparison.OrdinalIgnoreCase) && - item.SclAccessPointName.Equals(workspace.AccessPointName, StringComparison.OrdinalIgnoreCase)); - - if (device != null && (device.IsConnected || device.IsBusy || device.IsMonitoring)) + var sourceName = Path.GetFileName(sourcePath); + SetStatus($"Opening {sourceName} · {importedSources.Count + 1} of {dialog.FileNames.Length}…"); + var document = await _sclWorkspaceService.OpenAsync( + sourcePath, + cancellationToken: _applicationCancellation.Token); + LogSclFindings(sourceName, document.Findings); + + if (document.Ieds.Count == 0) { - retained++; - firstImported ??= device; - importedDevices.Add(device); + AddLog("WARN", "SCL", $"{sourceName}: the engine returned no IED workspace."); continue; } - var signals = SclWorkspaceSignalMapper.BuildSignals(workspace); - if (device == null) - { - device = new Iec61850MonitorDevice(); - Devices.Add(device); - added++; - } - else + importedSources.Add((sourcePath, document.SourceSha256)); + offlineCount += document.Ieds.Count(item => item.CanBrowseOffline); + endpointCount += document.Ieds.Count(item => !item.RequiresEndpointBinding); + + foreach (var workspace in document.Ieds) { - refreshed++; + var device = Devices.FirstOrDefault(item => + item.SclSourceSha256.Equals(document.SourceSha256, StringComparison.OrdinalIgnoreCase) && + item.SclIedName.Equals(workspace.IedName, StringComparison.OrdinalIgnoreCase) && + item.SclAccessPointName.Equals(workspace.AccessPointName, StringComparison.OrdinalIgnoreCase)); + + if (device != null && (device.IsConnected || device.IsBusy || device.IsMonitoring)) + { + retained++; + firstImported ??= device; + importedDevices.Add(device); + continue; + } + + var signals = SclWorkspaceSignalMapper.BuildSignals(workspace); + if (device == null) + { + device = new Iec61850MonitorDevice(); + Devices.Add(device); + added++; + } + else + { + refreshed++; + } + + ApplySclWorkspaceToDevice(device, document, workspace, signals); + firstImported ??= device; + importedDevices.Add(device); } + } - ApplySclWorkspaceToDevice(device, document, workspace, signals); - firstImported ??= device; - importedDevices.Add(device); + if (importedDevices.Count == 0) + { + SetStatus($"{sourceLabel}: no IED model was found."); + return; } if (firstImported != null) @@ -228,9 +247,7 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) UpdateNavigationVisuals(0, animate: true); RaiseWorkspaceCounts(); - var offlineCount = document.Ieds.Count(item => item.CanBrowseOffline); - var endpointCount = document.Ieds.Count(item => !item.RequiresEndpointBinding); - var status = $"{sourceName}: {document.Ieds.Count} IED/AP workspace(s), {offlineCount} offline model(s), {endpointCount} MMS endpoint(s) — {added} added, {refreshed} refreshed, {retained} active retained."; + var status = $"{sourceLabel}: {importedDevices.Count} IED/AP workspace(s), {offlineCount} offline model(s), {endpointCount} MMS endpoint(s) — {added} added, {refreshed} refreshed, {retained} active retained."; SetStatus(status); AddLog("INFO", "SCL", status); @@ -243,7 +260,12 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) { var selectionMode = PromptSclSignalSelectionMode(this, selectableDevices.Length); sharedSelectionMode = selectionMode; - if (selectionMode == SclSignalSelectionMode.StaticDataSet) + if (!selectionMode.HasValue) + { + AddLog("INFO", "SCL", "Shared signal selection was cancelled; the imported offline models remain available in Engineering."); + SetStatus($"{sourceLabel} imported · signal selection was not changed."); + } + else if (selectionMode == SclSignalSelectionMode.StaticDataSet) { foreach (var importedDevice in selectableDevices) ApplyStaticDataSetSelection(importedDevice); @@ -273,28 +295,33 @@ await OpenSignalSelectionWizardAsync( // Importing through Engineering must update an already-loaded FAT project as // the same workspace mutation. The selection decision has already been applied, // so the hidden FAT view receives the new IEDs without a second prompt or reset. - if (_loadedIoFatWindow is { IsLoaded: true } loadedFat && - sharedSelectionMode.HasValue && - !loadedFat.Project.Sources.Any(source => - source.Sha256.Equals(document.SourceSha256, StringComparison.OrdinalIgnoreCase))) + if (_loadedIoFatWindow is { IsLoaded: true } loadedFat && sharedSelectionMode.HasValue) { - var append = await AppendSclIedsToLoadedFatAsync( - loadedFat, - new[] { dialog.FileName }, - useStaticSelection: sharedSelectionMode == SclSignalSelectionMode.StaticDataSet, - selectionAlreadyApplied: true); - if (!append.Succeeded) - AddLog("WARN", "SCL/FAT", append.Message); + var newFatSourcePaths = importedSources + .Where(imported => !loadedFat.Project.Sources.Any(source => + source.Sha256.Equals(imported.Sha256, StringComparison.OrdinalIgnoreCase))) + .Select(imported => imported.Path) + .ToArray(); + if (newFatSourcePaths.Length > 0) + { + var append = await AppendSclIedsToLoadedFatAsync( + loadedFat, + newFatSourcePaths, + useStaticSelection: sharedSelectionMode == SclSignalSelectionMode.StaticDataSet, + selectionAlreadyApplied: true); + if (!append.Succeeded) + AddLog("WARN", "SCL/FAT", append.Message); + } } } catch (OperationCanceledException) { - SetStatus($"{sourceName}: SCL open cancelled."); + SetStatus($"{sourceLabel}: SCL open cancelled."); } catch (Exception ex) { - AddLog("ERROR", "SCL", $"Could not open {sourceName}: {ex.Message}"); - SetStatus($"{sourceName}: SCL open failed. Diagnostics is marked with !."); + AddLog("ERROR", "SCL", $"Could not open {sourceLabel}: {ex.Message}"); + SetStatus($"{sourceLabel}: SCL open failed. Diagnostics is marked with !."); MarkDiagnosticAlert(); MessageBox.Show( this, diff --git a/SclSignalSelectionModeWindow.xaml b/SclSignalSelectionModeWindow.xaml new file mode 100644 index 000000000..8b1983af9 --- /dev/null +++ b/SclSignalSelectionModeWindow.xaml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SclSignalSelectionModeWindow.xaml.cs b/SclSignalSelectionModeWindow.xaml.cs new file mode 100644 index 000000000..ad52cdf7f --- /dev/null +++ b/SclSignalSelectionModeWindow.xaml.cs @@ -0,0 +1,22 @@ +using System.Windows; + +namespace ArIED61850Tester; + +public partial class SclSignalSelectionModeWindow : Window +{ + public SclSignalSelectionModeWindow(int iedCount) + { + InitializeComponent(); + ImportScopeText = iedCount == 1 + ? "1 IED WORKSPACE" + : $"{iedCount} IED WORKSPACES"; + DataContext = this; + } + + public string ImportScopeText { get; } + + public bool UseStaticDataSet => StaticDataSetChoice.IsChecked == true; + + private void Continue_Click(object sender, RoutedEventArgs e) + => DialogResult = true; +} diff --git a/design-qa.md b/design-qa.md index 547ac2406..54d9b019d 100644 --- a/design-qa.md +++ b/design-qa.md @@ -1,4 +1,41 @@ -# IED protection relay fascia design QA +# SCL signal selection modal design QA + +## Visual sources + +- User reference / previous implementation: attached visual `codex-clipboard-090813c8-4eb5-4d1a-a394-dd81fe44b6f0.png` +- Runtime capture of the new WPF modal: `output/scl-signal-selection-modern.png` +- Side-by-side comparison: `output/scl-signal-selection-comparison.png` + +## Current scope + +The native `MessageBox` used after SCL import was replaced by `SclSignalSelectionModeWindow`, a modal WPF card that uses the existing ARSAS application icon, typography, color tokens, button styles, Lucide resources, elevation, and rounded-card language. + +## Comparison findings + +- The previous paragraph-heavy system dialog now has a clear title, shared-workspace subtitle, IED count badge, two explicit selection cards, and a supporting note. +- `Use Static DataSet` is selected by default and visibly marked as recommended; `Choose Signals Manually` is an equally accessible radio-card choice. +- Generic Yes/No labels are replaced by Cancel and Continue, with default and cancel keyboard behavior retained. +- Spacing, 17–24 px corner radii, subdued borders, blue selection state, Aptos/Segoe typography, and application button styles match existing ARSAS pop windows such as Save SCL. +- The runtime screenshot shows no clipped copy, overlapping controls, broken padding, or truncated actions at the captured Windows scaling. +- Both cards are fully clickable, mouse/pressed/focus states are defined, Enter continues, Escape cancels, and closing the window leaves the imported Engineering model unchanged. + +## Functional companion checks + +- Engineering Open SCL now permits multiple file selection and adds every distinct file into the same Engineering workspace. +- A single signal-authority decision applies to all newly imported IED workspaces and is bridged into an already-loaded FAT workspace without a second prompt. +- Duplicate FAT sources are filtered by SHA-256 before append; existing connected or monitoring IEDs are retained. + +## Current validation + +- Release build: passed with 0 errors using the ARIEC61850 commit pinned by `engines/ARIEC61850.lock.json`. +- Test suite: 616 passed, 0 failed, 0 skipped. +- Runtime WPF capture and side-by-side visual inspection: passed. + +Final result for the SCL signal selection modal: **passed**. + +--- + +# Previous IED protection relay fascia design QA ## Scope diff --git a/output/scl-signal-selection-comparison.png b/output/scl-signal-selection-comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a6ff981b40eb6878da5349fe80d1fbe9eab09f GIT binary patch literal 598548 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%0AKPu-P?D+oDCzLWPnmmQmu=4_A)$D(w`M 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

=EC* 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≶8)CKx2sa^7bh0soI+Q`+W&pi#iTnG0HqD8^@SH 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€D4d}qSHqJakAKk*W| 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 literal 0 HcmV?d00001 diff --git a/output/scl-signal-selection-modern.png b/output/scl-signal-selection-modern.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0acfe082833972f84de58c74280ad86991cbd3 GIT binary patch literal 65657 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

5f)< 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 B!ZrW^ literal 0 HcmV?d00001 diff --git a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs index c592c9524..8f9629d6d 100644 --- a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs +++ b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs @@ -29,20 +29,42 @@ public void MainWindow_AlwaysExposesEngineeringAndPersistentIoFatWorkspaceModes( public void SclImport_UsesOneSelectionAuthorityAcrossEngineeringAndFat() { var workflow = File.ReadAllText(FindRepoFile("MainWindow.SharedSclWorkspace.cs")); + var selectionWindow = File.ReadAllText(FindRepoFile("SclSignalSelectionModeWindow.xaml")); var engineering = File.ReadAllText(FindRepoFile("MainWindow.xaml.cs")); var fat = File.ReadAllText(FindRepoFile("MainWindow.IoTesting.cs")); var fatProjection = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); - Assert.Contains("Use Static DataSet", workflow, StringComparison.Ordinal); - Assert.Contains("Choose Signals Manually", workflow, StringComparison.Ordinal); + Assert.Contains("new SclSignalSelectionModeWindow", workflow, StringComparison.Ordinal); + Assert.DoesNotContain("MessageBox.Show", workflow, StringComparison.Ordinal); + Assert.Contains("Use Static DataSet", selectionWindow, StringComparison.Ordinal); + Assert.Contains("Choose Signals Manually", selectionWindow, StringComparison.Ordinal); + Assert.Contains("SelectionCard", selectionWindow, StringComparison.Ordinal); + Assert.Contains("CardShadow", selectionWindow, StringComparison.Ordinal); + Assert.Contains("PrimaryButton", selectionWindow, StringComparison.Ordinal); + Assert.Contains("SoftButton", selectionWindow, StringComparison.Ordinal); Assert.Contains("_sharedSclSelectionAuthorityDeviceIds", workflow, StringComparison.Ordinal); Assert.Contains("ApplyStaticDataSetSelection", engineering, StringComparison.Ordinal); Assert.Contains("selectionAlreadyApplied: true", engineering, StringComparison.Ordinal); Assert.Contains("ApplyManualSelectionToFatProjectAsync", fat, StringComparison.Ordinal); + Assert.Contains("promptForSelection: true", fat, StringComparison.Ordinal); + Assert.Contains("PromptSclSignalSelectionMode(this, import.Project.Ieds.Count)", fat, StringComparison.Ordinal); Assert.Contains("point.IsIncludedInFat &&", fatProjection, StringComparison.Ordinal); Assert.Contains("point.TestEnabled", fatProjection, StringComparison.Ordinal); } + [Fact] + public void EngineeringSclImport_IsAdditiveAndSupportsMultipleFiles() + { + var engineering = File.ReadAllText(FindRepoFile("MainWindow.xaml.cs")); + + Assert.Contains("Title = \"Open one or more IEC 61850 SCL files\"", engineering, StringComparison.Ordinal); + Assert.Contains("Multiselect = true", engineering, StringComparison.Ordinal); + Assert.Contains("foreach (var sourcePath in dialog.FileNames.Distinct", engineering, StringComparison.Ordinal); + Assert.Contains("var importedSources = new List<(string Path, string Sha256)>()", engineering, StringComparison.Ordinal); + Assert.Contains("newFatSourcePaths", engineering, StringComparison.Ordinal); + Assert.Contains("AppendSclIedsToLoadedFatAsync", engineering, StringComparison.Ordinal); + } + [Fact] public void FatWorkspace_ReturnsToEngineeringByHideAndShowWithoutUnloadingProject() { From 32a2a905616e1ef2e32cf16904fcd76de46ae99b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 10:53:26 +0700 Subject: [PATCH 071/112] Separate shared workspace selection from FAT scope --- Models/IoTesting/IoTestModels.cs | 66 ++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/Models/IoTesting/IoTestModels.cs b/Models/IoTesting/IoTestModels.cs index 8f32bb206..7664127df 100644 --- a/Models/IoTesting/IoTestModels.cs +++ b/Models/IoTesting/IoTestModels.cs @@ -274,6 +274,7 @@ internal void ResetAttempt() public sealed class IoTestPointPlan : ObservableObject { + private bool _workspaceSelected = true; private bool _testEnabled = true; private FatSignalDisposition _fatDisposition = FatSignalDisposition.Included; private IoTestLiveBindingState _liveBindingState = IoTestLiveBindingState.NotEvaluated; @@ -318,6 +319,20 @@ public IoTestPointPlan() public int SourceRow { get; init; } public FatSignalKind SignalKind { get; init; } = FatSignalKind.Discrete; public FatCaptureMode CaptureMode { get; init; } = FatCaptureMode.AutomaticTransition; + + // Shared Engineering/FAT workspace membership. This is intentionally separate from + // TestEnabled, which remains a FAT-only capture/evidence scope toggle. + public bool WorkspaceSelected + { + get => _workspaceSelected; + set + { + if (!Set(ref _workspaceSelected, value)) + return; + RaiseFatComputedProperties(); + } + } + public bool TestEnabled { get => _testEnabled; @@ -353,7 +368,7 @@ private set [JsonIgnore] public bool CanCaptureOperatorSnapshot => - IsOperatorSnapshot && IsIncludedInFat && TestEnabled && ImportReady && IsLiveBound; + IsOperatorSnapshot && WorkspaceSelected && IsIncludedInFat && TestEnabled && ImportReady && IsLiveBound; [JsonIgnore] public bool IsFatEvidenceComplete => CaptureMode == FatCaptureMode.AutomaticTransition @@ -604,11 +619,11 @@ private set [JsonIgnore] public bool CanPrepareConnection => !IsPreparing; - public int EnabledCount => TestPoints.Count(point => point.IsIncludedInFat && point.TestEnabled); - public int CompleteCount => TestPoints.Count(point => point.IsIncludedInFat && point.TestEnabled && point.IsFatEvidenceComplete); - public int PassedCount => TestPoints.Count(point => point.IsIncludedInFat && point.TestEnabled && point.Runtime.State == IoTestPointState.Passed); - public int ReviewCount => TestPoints.Count(point => point.IsIncludedInFat && point.TestEnabled && point.Runtime.State == IoTestPointState.Review); - public int BoundCount => TestPoints.Count(point => point.IsIncludedInFat && point.IsLiveBound); + public int EnabledCount => TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled); + public int CompleteCount => TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.IsFatEvidenceComplete); + public int PassedCount => TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.Runtime.State == IoTestPointState.Passed); + public int ReviewCount => TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.Runtime.State == IoTestPointState.Review); + public int BoundCount => TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat && point.IsLiveBound); public int RemovedCount => TestPoints.Count(point => !point.IsIncludedInFat); public int PendingCount => Math.Max(0, EnabledCount - CompleteCount); @@ -644,17 +659,38 @@ public void InitializeRuntimeNotifications() _notificationsInitialized = true; foreach (var point in TestPoints) - { - point.PropertyChanged += Point_PropertyChanged; - point.Runtime.PropertyChanged += PointRuntime_PropertyChanged; - } + SubscribePoint(point); RaiseProgressProperties(); } + public bool AddTestPoint(IoTestPointPlan point) + { + ArgumentNullException.ThrowIfNull(point); + if (TestPoints.Any(existing => existing.TestPointId.Equals(point.TestPointId, StringComparison.OrdinalIgnoreCase))) + return false; + + TestPoints.Add(point); + if (_notificationsInitialized) + SubscribePoint(point); + RaiseProgressProperties(); + return true; + } + + private void SubscribePoint(IoTestPointPlan point) + { + point.PropertyChanged += Point_PropertyChanged; + point.Runtime.PropertyChanged += PointRuntime_PropertyChanged; + } + private void Point_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName is nameof(IoTestPointPlan.TestEnabled) or nameof(IoTestPointPlan.LiveBindingState) or nameof(IoTestPointPlan.FatDisposition)) + if (e.PropertyName is nameof(IoTestPointPlan.WorkspaceSelected) or + nameof(IoTestPointPlan.TestEnabled) or + nameof(IoTestPointPlan.LiveBindingState) or + nameof(IoTestPointPlan.FatDisposition)) + { RaiseProgressProperties(); + } } private void PointRuntime_PropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -712,10 +748,10 @@ public sealed class IoTestProject public string SourceSetSha256 { get; private set; } = string.Empty; public int SignalCount => Ieds.Sum(ied => ied.TestPoints.Count); - public int IncludedSignalCount => Ieds.Sum(ied => ied.TestPoints.Count(point => point.IsIncludedInFat)); - public int RemovedSignalCount => SignalCount - IncludedSignalCount; - public int ReadySignalCount => Ieds.Sum(ied => ied.TestPoints.Count(point => point.IsIncludedInFat && point.ImportReady)); - public int LiveBoundSignalCount => Ieds.Sum(ied => ied.TestPoints.Count(point => point.IsIncludedInFat && point.IsLiveBound)); + public int IncludedSignalCount => Ieds.Sum(ied => ied.TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat)); + public int RemovedSignalCount => Ieds.Sum(ied => ied.TestPoints.Count(point => !point.IsIncludedInFat)); + public int ReadySignalCount => Ieds.Sum(ied => ied.TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat && point.ImportReady)); + public int LiveBoundSignalCount => Ieds.Sum(ied => ied.TestPoints.Count(point => point.WorkspaceSelected && point.IsIncludedInFat && point.IsLiveBound)); public void SetSources(IEnumerable sources, string sourceSetSha256) { From 2c7bfc8f07e2aeac0e9943e66a3999f18407d1a3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 10:54:08 +0700 Subject: [PATCH 072/112] Scope direct SCL acquisition to shared workspace selection --- .../IoTesting/IoTestSignalSelectionService.cs | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/Services/IoTesting/IoTestSignalSelectionService.cs b/Services/IoTesting/IoTestSignalSelectionService.cs index fb8b2e02d..067a08d7a 100644 --- a/Services/IoTesting/IoTestSignalSelectionService.cs +++ b/Services/IoTesting/IoTestSignalSelectionService.cs @@ -24,13 +24,14 @@ public sealed record IoTestSignalSelectionResult( /// Resolves an IO-list/FAT acquisition scope against one discovered IED model without /// guessing. Exact references remain highest priority. Canonical IEC 61850 forms /// accept vendor-safe spelling differences only when the best candidate is unique. -/// Weak legacy source rows remain gated by TEST selection. Direct-SCL static DataSet -/// authority rows are acquisition candidates whenever they remain included in FAT; +/// Weak legacy source rows remain gated by TEST selection. Direct-SCL workspace rows +/// are acquisition candidates whenever shared workspace selection keeps them visible; /// their TEST checkbox is evidence authority only. /// public sealed class IoTestSignalSelectionService { internal const string SclDataSetAuthorityBindingStatus = "SCL_DATASET_AUTHORITY"; + internal const string SclWorkspaceAuthorityBindingStatus = "SCL_WORKSPACE_AUTHORITY"; private const int SclStaticMembershipIdentityBonus = 1000; private static readonly Regex ProtectionCodeRegex = new( @@ -51,14 +52,16 @@ public IoTestSignalSelectionResult Resolve( // semantic inference in ARSAS; the engine remains the sole membership authority. Iec61850DataSetSignalInventoryService.EnsureMandatorySignals(device); - // P1: direct-SCL connection/live acquisition follows included static DataSet - // membership, not TEST. Legacy workbook rows retain their historical TestEnabled - // gate because they do not have an independent engine-owned membership authority. + // P1 + shared workspace authority: direct-SCL connection/live acquisition follows + // the Engineering/FAT workspace selection, not TEST. Legacy workbook rows retain + // their historical TestEnabled gate because they do not have an independent + // SCL-owned workspace identity. var requested = ied.TestPoints .Where(point => + point.WorkspaceSelected && point.IsIncludedInFat && point.ImportReady && - (point.TestEnabled || IsSclDataSetAuthority(point))) + (point.TestEnabled || IsDirectSclAuthority(point))) .ToList(); var matches = new List(requested.Count); var missing = new List(); @@ -96,11 +99,12 @@ public IoTestSignalSelectionResult Resolve( // assignments as elimination evidence. No candidate is ever selected by text // similarity: it must still be the one unique best IEC object left. // - // SCL FAT rows are different from legacy workbook rows: their identity is the - // engine-authoritative static DataSet membership, not a unique runtime leaf. Two - // distinct memberships may legally resolve to the same primary DataAttribute, so - // those authority rows are allowed to fan out from one proven live signal. The - // signal is still recorded as used so a legacy workbook row cannot silently share it. + // Static DataSet FAT rows are different from legacy workbook rows: their identity + // is an engine-authoritative membership, not a unique runtime leaf. Two distinct + // memberships may legally resolve to the same primary DataAttribute, so those + // static authority rows are allowed to fan out from one proven live signal. Manual + // workspace rows own a normal unique SCL object identity and therefore do not use + // this static-membership sharing exception. var madeProgress = true; while (madeProgress && unresolved.Count > 0) { @@ -168,6 +172,15 @@ internal static bool IsSclDataSetAuthority(IoTestPointPlan point) SclDataSetAuthorityBindingStatus, StringComparison.OrdinalIgnoreCase); + internal static bool IsSclWorkspaceAuthority(IoTestPointPlan point) + => string.Equals( + point.BindingStatus, + SclWorkspaceAuthorityBindingStatus, + StringComparison.OrdinalIgnoreCase); + + internal static bool IsDirectSclAuthority(IoTestPointPlan point) + => IsSclDataSetAuthority(point) || IsSclWorkspaceAuthority(point); + private static int BestSignalScore( IoTestPointPlan point, SignalDefinition signal, @@ -178,11 +191,10 @@ private static int BestSignalScore( if (importedReferences.Count == 0) return 0; - // Direct SCL FAT owns stronger identity than a runtime leaf: one row corresponds - // to one exact static DataSet membership. A connected Engineering Workspace can - // legitimately contain both a generic scalar signal and a dedicated static-member - // projection with the same ObjectReference. Without this precedence both score as - // exact runtime matches and the FAT row becomes ambiguous. Prefer exact + // Direct static SCL FAT owns stronger identity than a runtime leaf: one row + // corresponds to one exact static DataSet membership. A connected Engineering + // Workspace can legitimately contain both a generic scalar signal and a dedicated + // static-member projection with the same ObjectReference. Prefer exact // (DataSetReference + DisplayReference) membership identity, then use the resolved // ObjectReference only as the acquisition target. if (HasExactSclStaticMembershipIdentity(point, signal)) @@ -190,9 +202,9 @@ private static int BestSignalScore( // ARIEC deliberately keeps static FCDA/FCD membership identity in // DisplayReference while ObjectReference may remain the resolved runtime leaf. - // SCL-backed FAT rows therefore compare against both engine-owned identities. + // Manual SCL workspace rows also preserve DisplayReference as exact source identity. // Legacy workbook rows keep the old ObjectReference-only contract. - var observedReferences = IsSclDataSetAuthority(point) + var observedReferences = IsDirectSclAuthority(point) ? new[] { signal.ObjectReference, signal.DisplayReference } .Where(value => !string.IsNullOrWhiteSpace(value)) .Distinct(StringComparer.OrdinalIgnoreCase) From bada688f8a40b02b0dc1b2dec4bd1f9b5a50d0e2 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 10:55:25 +0700 Subject: [PATCH 073/112] Materialize manual SCL selections as FAT workspace rows --- .../IoFatEngineeringSelectionBridge.cs | 192 +++++++++++++----- 1 file changed, 145 insertions(+), 47 deletions(-) diff --git a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs index 5596d6ff8..0c9ba937c 100644 --- a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs +++ b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs @@ -1,12 +1,15 @@ +using System.Security.Cryptography; +using System.Text; using ArIED61850Tester.Models; using ArIED61850Tester.Models.IoTesting; namespace ArIED61850Tester.Services.IoTesting; ///

-/// Keeps a direct-SCL FAT plan and the Engineering signal catalog on one selection -/// authority. The exact static DataSet membership row is the bridge identity; its -/// resolved ObjectReference remains the shared acquisition target. +/// Keeps direct-SCL FAT projection and the Engineering signal catalog on one shared +/// workspace-selection authority. Static DataSet membership remains the strongest identity, +/// while manually selected non-DataSet SCL signals are materialized as persistent FAT rows. +/// FAT TEST/capture scope and FAT disposition are deliberately orthogonal. /// public static class IoFatEngineeringSelectionBridge { @@ -19,30 +22,28 @@ public static int Initialize( ArgumentNullException.ThrowIfNull(device); var changed = 0; - foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsSclDataSetAuthority)) + foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsDirectSclAuthority)) { var signal = FindSignal(point, device); if (signal is null) continue; - // SCL identity and operator selection have one authority shared by Engineering - // and FAT. When Engineering has already completed the import choice, its exact - // checkbox state (including an intentionally empty selection) initializes FAT. - // A brand-new FAT entry point instead starts from the imported static DataSet. - // FatDisposition remains orthogonal and is never changed here. + // Shared workspace membership follows the Engineering checkbox. TestEnabled is + // FAT-only evidence scope and must survive Engineering mode switches unchanged. + // FatDisposition remains a FAT-only Remove/Restore authority. if (preserveExistingEngineeringSelection) { - if (point.TestEnabled != signal.IsSelected) + if (point.WorkspaceSelected != signal.IsSelected) { - point.TestEnabled = signal.IsSelected; + point.WorkspaceSelected = signal.IsSelected; changed++; } } else { - if (!point.TestEnabled) + if (!point.WorkspaceSelected) { - point.TestEnabled = true; + point.WorkspaceSelected = true; changed++; } @@ -64,19 +65,16 @@ public static bool ApplyFatPointSelection( { ArgumentNullException.ThrowIfNull(point); ArgumentNullException.ThrowIfNull(device); - if (!IoTestSignalSelectionService.IsSclDataSetAuthority(point)) + if (!IoTestSignalSelectionService.IsDirectSclAuthority(point)) return false; var signal = FindSignal(point, device); if (signal is null) return false; - // P0.2: TestEnabled is the shared selection state. FAT disposition is deliberately - // orthogonal: Remove from FAT must not silently unselect the Engineering signal, and - // Restore must not silently reselect it. This preserves the checkbox exactly across - // Removed Signals while the dedicated restore action remains the only disposition - // authority. - var selected = point.TestEnabled; + // Only shared workspace membership is bidirectional. TEST never mutates Engineering, + // and Remove/Restore never mutates either shared selection or TEST scope. + var selected = point.WorkspaceSelected; if (signal.IsSelected == selected) return false; @@ -95,24 +93,26 @@ public static bool ApplyEngineeringSignalSelection( ArgumentNullException.ThrowIfNull(ied); ArgumentNullException.ThrowIfNull(device); - // P0.2: Engineering selection shares the operator checkbox state, not FAT - // disposition authority. A row explicitly removed from FAT must stay in Removed - // Signals when Engineering is toggled off/on. Only the dedicated Removed Signals - // restore action may change ExcludedByOperator back to Included. This also means a - // later restore preserves the latest operator checkbox state without resurrecting - // evidence scope as a side effect of workspace navigation. + // Engineering owns shared workspace membership only. A FAT row explicitly removed + // by the operator stays removed, and its FAT TEST preference/evidence is preserved + // across Engineering deselect/reselect operations. + var matching = ied.TestPoints + .Where(IoTestSignalSelectionService.IsDirectSclAuthority) + .Where(point => ReferenceEquals(FindSignal(point, device), signal)) + .ToArray(); + var changed = false; - foreach (var point in ied.TestPoints.Where(IoTestSignalSelectionService.IsSclDataSetAuthority)) + foreach (var point in matching) { - if (!ReferenceEquals(FindSignal(point, device), signal)) + if (point.WorkspaceSelected == selected) continue; - - if (point.TestEnabled != selected) - { - point.TestEnabled = selected; - changed = true; - } + point.WorkspaceSelected = selected; + changed = true; } + + if (matching.Length == 0 && selected && TryCreateManualWorkspacePoint(signal, ied, device, out var point)) + changed |= ied.AddTestPoint(point); + return changed; } @@ -123,7 +123,7 @@ public static bool ApplyEngineeringSignalSelection( ArgumentNullException.ThrowIfNull(point); ArgumentNullException.ThrowIfNull(device); - var staticReferences = new[] + var sourceReferences = new[] { point.SourceIecReference, point.ReportDisplayReference, @@ -133,18 +133,30 @@ public static bool ApplyEngineeringSignalSelection( .Select(IoTestLiveBindingService.NormalizeReference) .Where(reference => reference.Length > 0) .ToHashSet(StringComparer.OrdinalIgnoreCase); - var dataSet = IoTestLiveBindingService.NormalizeReference(point.DataSetName); - - var exactMembership = device.Signals - .Where(signal => staticReferences.Contains( - IoTestLiveBindingService.NormalizeReference(signal.DisplayReference))) - .Where(signal => dataSet.Length == 0 || - dataSet.Equals( - IoTestLiveBindingService.NormalizeReference(signal.DataSetReference), - StringComparison.OrdinalIgnoreCase)) - .ToArray(); - if (exactMembership.Length == 1) - return exactMembership[0]; + + if (IoTestSignalSelectionService.IsSclDataSetAuthority(point)) + { + var dataSet = IoTestLiveBindingService.NormalizeReference(point.DataSetName); + var exactMembership = device.Signals + .Where(signal => sourceReferences.Contains( + IoTestLiveBindingService.NormalizeReference(signal.DisplayReference))) + .Where(signal => dataSet.Length == 0 || + dataSet.Equals( + IoTestLiveBindingService.NormalizeReference(signal.DataSetReference), + StringComparison.OrdinalIgnoreCase)) + .ToArray(); + if (exactMembership.Length == 1) + return exactMembership[0]; + } + else if (IoTestSignalSelectionService.IsSclWorkspaceAuthority(point)) + { + var exactSource = device.Signals + .Where(signal => sourceReferences.Contains( + IoTestLiveBindingService.NormalizeReference(signal.DisplayReference))) + .ToArray(); + if (exactSource.Length == 1) + return exactSource[0]; + } var runtime = IoTestLiveBindingService.NormalizeReference(point.ObjectReference); if (runtime.Length == 0) @@ -157,4 +169,90 @@ public static bool ApplyEngineeringSignalSelection( .ToArray(); return runtimeMatches.Length == 1 ? runtimeMatches[0] : null; } + + private static bool TryCreateManualWorkspacePoint( + SignalDefinition signal, + IoTestIedPlan ied, + Iec61850MonitorDevice device, + out IoTestPointPlan point) + { + point = null!; + if (signal.IsControlSignal || string.IsNullOrWhiteSpace(signal.ObjectReference)) + return false; + + var functionalConstraint = signal.FunctionalConstraint?.Trim() ?? string.Empty; + var dataType = signal.DataType?.Trim() ?? string.Empty; + var upperType = dataType.ToUpperInvariant(); + var discrete = functionalConstraint.Equals("ST", StringComparison.OrdinalIgnoreCase) && + (upperType.Contains("BOOL", StringComparison.Ordinal) || + upperType.Contains("DBPOS", StringComparison.Ordinal) || + upperType.Contains("SPS", StringComparison.Ordinal) || + upperType.Contains("DPS", StringComparison.Ordinal)); + var analog = functionalConstraint.Equals("MX", StringComparison.OrdinalIgnoreCase); + var signalKind = discrete + ? FatSignalKind.Discrete + : analog + ? FatSignalKind.Analog + : FatSignalKind.Other; + var captureMode = discrete + ? FatCaptureMode.AutomaticTransition + : FatCaptureMode.OperatorSnapshot; + var displayReference = string.IsNullOrWhiteSpace(signal.DisplayReference) + ? signal.ObjectReference.Trim() + : signal.DisplayReference.Trim(); + var runtimeReference = signal.ObjectReference.Trim(); + var sourceSha = device.SclSourceSha256?.Trim().ToLowerInvariant() ?? string.Empty; + var identity = string.Join("|", new[] + { + sourceSha, + ied.IedName.Trim(), + device.SclAccessPointName?.Trim() ?? string.Empty, + displayReference, + runtimeReference, + functionalConstraint + }); + var digest = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(identity))) + .ToLowerInvariant(); + + point = new IoTestPointPlan + { + TestPointId = $"scl-manual-{digest[..20]}", + IedName = ied.IedName, + IpAddress = ied.IpAddress, + SignalName = string.IsNullOrWhiteSpace(signal.Name) ? displayReference : signal.Name.Trim(), + ObjectReference = runtimeReference, + FunctionalConstraint = functionalConstraint, + ExpectedOnText = discrete ? "TRUE" : "Value 1", + ExpectedOffText = discrete ? "FALSE" : "Value 2", + ExpectedOnRaw = 1, + ExpectedOffRaw = 0, + DataType = dataType, + SignalAddress = sourceSha, + DataSetName = signal.DataSetReference?.Trim() ?? string.Empty, + SourceIecReference = displayReference, + ReportDisplayReference = displayReference, + EventLogSearchReference = runtimeReference, + EvidenceExpected = captureMode == FatCaptureMode.AutomaticTransition + ? "Automatic Value 1 / Value 2 transition capture" + : "Operator Value 1 / Value 2 snapshot capture", + SignalKind = signalKind, + CaptureMode = captureMode, + WorkspaceSelected = true, + TestEnabled = true, + ImportReady = true, + BindingStatus = IoTestSignalSelectionService.SclWorkspaceAuthorityBindingStatus, + BindingEvidence = string.Join(" • ", new[] + { + "Shared SCL workspace authority", + $"sourceSha256={sourceSha}", + $"deviceId={device.DeviceId}", + $"static={displayReference}", + $"runtime={runtimeReference}", + $"fc={functionalConstraint}", + $"kind={signalKind}", + $"capture={captureMode}" + }) + }; + return true; + } } From b5b4e3d86891b83343940edb403a0ec836a93eac Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 10:55:58 +0700 Subject: [PATCH 074/112] Preserve FAT scope during manual workspace selection --- MainWindow.SharedSclWorkspace.cs | 44 ++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/MainWindow.SharedSclWorkspace.cs b/MainWindow.SharedSclWorkspace.cs index 1933aee85..b2e29c09e 100644 --- a/MainWindow.SharedSclWorkspace.cs +++ b/MainWindow.SharedSclWorkspace.cs @@ -96,22 +96,39 @@ private void RegisterSharedSclSourcePaths( foreach (var ied in ieds) { - var sourceId = ied.TestPoints - .Select(point => point.SignalAddress) - .FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)); - if (string.IsNullOrWhiteSpace(sourceId) || - !sourceById.TryGetValue(sourceId, out var source) || - !uniquePathByFileName.TryGetValue(source.FileName, out var sourcePath)) - { - continue; - } - var device = ResolveIoTestDevice(ied.LiveDeviceId) ?? ResolveIoTestDevice(ied.IpAddress) ?? ResolveIoTestDevice(ied.IedName); if (device is null) continue; + IoFatSourceDescriptor? source = null; + var sourceId = ied.TestPoints + .Select(point => point.SignalAddress) + .FirstOrDefault(value => !string.IsNullOrWhiteSpace(value) && sourceById.ContainsKey(value)); + if (!string.IsNullOrWhiteSpace(sourceId)) + source = sourceById[sourceId]; + + // Manual-only SCL workspaces can legitimately have zero static DataSet rows, + // so source identity must not depend on finding a FAT point first. + if (source is null && !string.IsNullOrWhiteSpace(device.SclSourceSha256)) + { + source = project.Sources.FirstOrDefault(candidate => candidate.Sha256.Equals( + device.SclSourceSha256, + StringComparison.OrdinalIgnoreCase)); + } + if (source is null && project.Sources.Count == 1) + source = project.Sources[0]; + if (source is null && !string.IsNullOrWhiteSpace(device.SclSourcePath)) + { + var currentName = Path.GetFileName(device.SclSourcePath); + source = project.Sources.FirstOrDefault(candidate => candidate.FileName.Equals( + currentName, + StringComparison.OrdinalIgnoreCase)); + } + if (source is null || !uniquePathByFileName.TryGetValue(source.FileName, out var sourcePath)) + continue; + device.SclSourcePath = sourcePath; device.SclSourceSha256 = source.Sha256; } @@ -135,7 +152,7 @@ private async Task ApplyManualSelectionToFatProjectAsync( { ClearSharedSignalSelection(device); foreach (var point in ied.TestPoints) - point.TestEnabled = false; + point.WorkspaceSelected = false; } await OpenSignalSelectionWizardAsync( @@ -144,8 +161,9 @@ await OpenSignalSelectionWizardAsync( ownerOverride: owner); // The FAT window is not yet attached during an initial FAT import, so perform - // the same bridge operation explicitly. Once loaded, property changes remain - // synchronized bidirectionally by the normal bridge handlers. + // the same bridge operation explicitly. Selected non-DataSet SCL signals are + // materialized here as persistent FAT rows; existing FAT TEST/disposition state + // is never rewritten by Engineering selection. foreach (var signal in device.Signals) { IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( From 88e66300c4fe51145ebdeb3b0935c7df1c97e68c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 10:57:15 +0700 Subject: [PATCH 075/112] Refresh FAT projection after shared workspace mutations --- ...TestingWindow.SharedWorkspaceProjection.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 IoListTestingWindow.SharedWorkspaceProjection.cs diff --git a/IoListTestingWindow.SharedWorkspaceProjection.cs b/IoListTestingWindow.SharedWorkspaceProjection.cs new file mode 100644 index 000000000..760bff84f --- /dev/null +++ b/IoListTestingWindow.SharedWorkspaceProjection.cs @@ -0,0 +1,19 @@ +using ArIED61850Tester.Models.IoTesting; + +namespace ArIED61850Tester; + +public partial class IoListTestingWindow +{ + internal void NotifySharedWorkspacePointsChanged(IoTestIedPlan ied) + { + ArgumentNullException.ThrowIfNull(ied); + + // TestPoints is intentionally persisted as a List. Manual SCL selection can add a + // row while FAT is hidden in Engineering, so force the existing collection view to + // rebuild when the operator returns instead of requiring a second import/window. + RefreshFatV2WorkspaceUx(refreshRows: true); + Raise(nameof(ProjectSummary)); + Raise(nameof(SelectedIedSummary)); + RaiseSelectedIedContextProperties(); + } +} From cccd05948fc09b0b1d07ce73e991e04c2b60ee5b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 10:58:36 +0700 Subject: [PATCH 076/112] Allow true manual SCL workspace projection into FAT --- MainWindow.IoTesting.cs | 58 +++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/MainWindow.IoTesting.cs b/MainWindow.IoTesting.cs index 9add2f0f0..52f1cfc91 100644 --- a/MainWindow.IoTesting.cs +++ b/MainWindow.IoTesting.cs @@ -161,7 +161,7 @@ private Border CreateIoListTestingCard() }); content.Children.Add(new TextBlock { - Text = "Select one or multiple CID / ICD / IID / SCD / SSD files. Every static DataSet member becomes FAT scope. Excel import remains available for legacy IO-list projects.", + Text = "Select one or multiple CID / ICD / IID / SCD / SSD files. Use static DataSet membership or choose SCL signals manually; Engineering and FAT share the same workspace selection.", TextWrapping = TextWrapping.Wrap, FontSize = 13.4, Foreground = TryFindResource("Muted") as Brush, @@ -190,7 +190,7 @@ private Border CreateIoListTestingCard() new Thickness(0, 0, 0, 10))); content.Children.Add(new TextBlock { - Text = "Static DataSet authority · Value 1 / Value 2 evidence · autosave · portable continuation", + Text = "Shared SCL signal authority · Value 1 / Value 2 evidence · autosave · portable continuation", Style = TryFindResource("Caption") as Style, TextWrapping = TextWrapping.Wrap }); @@ -283,7 +283,21 @@ private async Task OpenSclFatSourcesAsync( var import = await _ioFatSclProjectImportService.ImportAsync( sclPaths, cancellationToken: _applicationCancellation.Token); - if (import.Project.SignalCount == 0) + + if (promptForSelection && !selectionMode.HasValue) + { + selectionMode = PromptSclSignalSelectionMode(this, import.Project.Ieds.Count); + if (!selectionMode.HasValue) + { + SetStatus("SCL FAT import cancelled before the workspace was changed."); + return; + } + } + + // Static mode is intentionally strict: it cannot invent FAT rows when SCL has + // no static DataSet membership. Manual mode is different; its selected SCL + // signals are materialized after the shared Engineering workspace is attached. + if (selectionMode == SclSignalSelectionMode.StaticDataSet && import.Project.SignalCount == 0) { var details = string.Join( Environment.NewLine, @@ -291,23 +305,13 @@ private async Task OpenSclFatSourcesAsync( SetStatus("SCL FAT import contains no static DataSet members."); MessageBox.Show( this, - "The selected SCL source(s) contain no static DataSet members. ARSAS did not fabricate FAT rows.\n\n" + details, - "No FAT DataSet scope", + "The selected SCL source(s) contain no static DataSet members for Static DataSet mode. Choose Signals Manually to build FAT scope from valid SCL signals.\n\n" + details, + "No static DataSet FAT scope", MessageBoxButton.OK, MessageBoxImage.Information); return; } - if (promptForSelection && !selectionMode.HasValue) - { - selectionMode = PromptSclSignalSelectionMode(this, import.Project.Ieds.Count); - if (!selectionMode.HasValue) - { - SetStatus("SCL FAT import cancelled before the workspace was changed."); - return; - } - } - var launch = await IoTestWorkspaceBootstrapService.OpenSourcesAsync( import.Project, import.SourceInputs, @@ -340,6 +344,11 @@ await ApplyManualSelectionToFatProjectAsync( MarkSharedSelectionAuthority(device); } } + + // Manual non-DataSet rows are created after bootstrap restoration. Persist that + // shared workspace projection before the FAT window is exposed. + launch.Workspace.ScheduleSave(); + var warningCount = import.Findings.Count(finding => finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase) || finding.Severity.Equals("High", StringComparison.OrdinalIgnoreCase) || @@ -612,7 +621,7 @@ private void DetachIoFatSelectionBridge(IoTestProject? project) private void IoFatSelectionPoint_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (_ioFatSelectionBridgeActive || sender is not IoTestPointPlan point || - e.PropertyName is not (nameof(IoTestPointPlan.TestEnabled) or nameof(IoTestPointPlan.FatDisposition))) + e.PropertyName != nameof(IoTestPointPlan.WorkspaceSelected)) { return; } @@ -629,6 +638,7 @@ e.PropertyName is not (nameof(IoTestPointPlan.TestEnabled) or nameof(IoTestPoint if (IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)) { ScheduleIoFatSelectionSave(device); + _loadedIoFatWindow?.Storage?.ScheduleSave(); RaiseWorkspaceCounts(); } } @@ -678,11 +688,25 @@ private void SynchronizeEngineeringSelectionToFat( _ioFatSelectionBridgeActive = true; try { - IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( + var previousCount = ied.TestPoints.Count; + var changed = IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( signal, signal.IsSelected, ied, device); + if (!changed) + return; + + if (ied.TestPoints.Count > previousCount) + { + foreach (var point in ied.TestPoints.Skip(previousCount)) + point.PropertyChanged += IoFatSelectionPoint_PropertyChanged; + } + + ScheduleIoFatSelectionSave(device); + _loadedIoFatWindow?.Storage?.ScheduleSave(); + _loadedIoFatWindow?.NotifySharedWorkspacePointsChanged(ied); + RaiseWorkspaceCounts(); } finally { From ee31457c444ee2aae5ad1a0df18ca9d61733968c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:00:19 +0700 Subject: [PATCH 077/112] Preserve Engineering selection in direct SCL FAT preparation --- MainWindow.IoTesting.AutoConnect.cs | 52 ++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/MainWindow.IoTesting.AutoConnect.cs b/MainWindow.IoTesting.AutoConnect.cs index 6ee85f293..7ace02f5f 100644 --- a/MainWindow.IoTesting.AutoConnect.cs +++ b/MainWindow.IoTesting.AutoConnect.cs @@ -27,11 +27,12 @@ internal async Task PrepareIoTestIedForFatAsync( return IoTestSessionActionResult.Failure($"{ied.IedName} is already being prepared for FAT acquisition."); // P1: connection/acquisition scope and FAT evidence selection are different - // authorities. A normal Connect prepares every included import-ready row whether - // its TEST checkbox is on or off. The optional override is retained for legacy - // callers that explicitly ask to validate a narrower evidence subset. + // authorities. A normal Connect prepares every shared-workspace-selected, + // included, import-ready row whether its TEST checkbox is on or off. The optional + // override is retained for callers that explicitly ask for narrower evidence scope. var requestedPoints = (requestedPointsOverride ?? ied.TestPoints) .Where(point => + point.WorkspaceSelected && point.IsIncludedInFat && point.ImportReady && (requestedPointsOverride is null || point.TestEnabled)) @@ -41,7 +42,7 @@ internal async Task PrepareIoTestIedForFatAsync( { return IoTestSessionActionResult.Failure( requestedPointsOverride is null - ? "No included import-ready FAT signal is available for this IED." + ? "No shared-workspace-selected import-ready FAT signal is available for this IED." : "No operator-selected import-ready FAT signal is available for this IED."); } @@ -185,7 +186,7 @@ void ReportProgress(string message) // pass and used to hold this SIPROTEC FAT preparation for about one minute. // Explicit Re-scan remains the design-versus-live comparison action. IoTestReconciliationCache.Invalidate(device); - ReportProgress("Using authoritative SCL DataSet identity · starting live acquisition"); + ReportProgress("Using authoritative SCL workspace identity · starting live acquisition"); } else { @@ -219,10 +220,10 @@ void ReportProgress(string message) device); // Legacy workbook rows may still request one fresh discovery when a saved live - // model is stale. An SCL-backed FAT row already has engine-owned static DataSet - // authority; a local match miss must not trigger a 50-second rediscovery loop. + // model is stale. Direct SCL rows already own imported workspace identity; a + // local match miss must not trigger a 50-second rediscovery loop. var hasBlockingNonSclMiss = selection.MissingPoints.Any(point => - !IoTestSignalSelectionService.IsSclDataSetAuthority(point)); + !IoTestSignalSelectionService.IsDirectSclAuthority(point)); if (!selection.Succeeded && selection.CanRetryWithFreshDiscovery && hasBlockingNonSclMiss) { ReportProgress("Refreshing live model once · saved model missed legacy FAT points"); @@ -269,7 +270,7 @@ void ReportProgress(string message) var mayProceedWithPartialSclSelection = hasSclRuntimeAuthority && selection.Matches.Count > 0 && - unresolvedSelectionPoints.All(IoTestSignalSelectionService.IsSclDataSetAuthority); + unresolvedSelectionPoints.All(IoTestSignalSelectionService.IsDirectSclAuthority); if (!selection.Succeeded && !mayProceedWithPartialSclSelection) { @@ -284,13 +285,13 @@ void ReportProgress(string message) { unresolved.ApplyLiveBinding( IoTestLiveBindingState.NotEvaluated, - "Static DataSet membership remains selected, but no unique runtime point is safe to bind yet. Other live FAT rows remain usable.", + "Selected SCL workspace identity remains authoritative, but no unique runtime point is safe to bind yet. Other live FAT rows remain usable.", device.DeviceId); } // P1 acquisition scope is the safe model match set, not the operator TEST - // selection. Keep the Engineering/TEST flags untouched while arming all proven - // included static members in the isolated runtime session for this IED. + // selection. Keep Engineering selection and FAT TEST untouched while arming + // all proven shared-workspace-selected rows for this isolated IED runtime. var acquisitionSignals = selection.Matches .Select(match => match.Signal) .Where(signal => signal.CanPublishToRuntime) @@ -450,7 +451,7 @@ private bool AttachIoFatSclRuntimeAuthority( device.SclAccessPointName = workspace.AccessPointName; device.IdentitySource = "FAT SCL · ARIEC workspace"; - if (!device.HasDiscoveryCache) + if (device.Signals.Count == 0) { var projected = SclWorkspaceSignalMapper.BuildSignals(workspace); DetachSignalHandlers(device.Signals); @@ -459,8 +460,10 @@ private bool AttachIoFatSclRuntimeAuthority( } else { - // An existing Engineering Workspace keeps its richer live model. Merge only - // engine-authoritative static DataSet inventory; never replace discovered rows. + // Existing Engineering signals are the live shared selection authority even + // when they came from offline SCL rather than a discovery cache. Preserve those + // SignalDefinition instances/checks and merge only missing mandatory DataSet + // descriptors; never recreate the catalog during an Engineering -> FAT switch. Iec61850DataSetSignalInventoryService.EnsureMandatorySignals( device.Signals, workspace.DesignModel); @@ -515,6 +518,25 @@ private int SynchronizeImportedSclFatWithEngineering( ied, device, preserveEngineeringSelection); + + // When Engineering already owns this workspace, project every selected SCL + // signal into FAT. This materializes manually selected non-DataSet signals and + // keeps intentionally empty manual selections empty without changing TEST. + if (preserveEngineeringSelection) + { + foreach (var signal in device.Signals) + { + if (IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( + signal, + signal.IsSelected, + ied, + device)) + { + synchronized++; + } + } + } + _ioTestLiveBindingService.BindIed(ied, Devices); SaveSignalSelectionMemory(device); } From 4c41ee14d8a57da7ce7bd7437aa3c3e7d80ed5ce Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:02:51 +0700 Subject: [PATCH 078/112] Project FAT rows from shared workspace selection --- IoListTestingWindow.FatV2Ux.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/IoListTestingWindow.FatV2Ux.cs b/IoListTestingWindow.FatV2Ux.cs index f344e2c90..dfb4c4339 100644 --- a/IoListTestingWindow.FatV2Ux.cs +++ b/IoListTestingWindow.FatV2Ux.cs @@ -66,12 +66,12 @@ private void RefreshFatV2WorkspaceUx(bool refreshRows = false) if (view != null && !ReferenceEquals(view, _fatSignalsView)) { _fatSignalsView = view; - // FAT projects every signal selected by the shared Engineering authority. - // Disposition is a FAT-only overlay; either an Engineering deselection or - // Remove from FAT hides the row without deleting its identity/evidence. + // FAT projects the shared Engineering workspace selection. TEST is a FAT-only + // evidence-scope toggle and must never hide a shared signal. Remove/Restore is + // a second, orthogonal FAT-only disposition overlay. view.Filter = item => item is IoTestPointPlan point && point.IsIncludedInFat && - point.TestEnabled; + point.WorkspaceSelected; refreshRows = true; } if (refreshRows) @@ -367,7 +367,7 @@ private void UpdateFatWorkspaceLabels() if (text.Text == "IO LIST FAT") text.Text = "IEC 61850 FAT"; else if (text.Text == "Workbook scope · report-first acquisition · relay-timestamped evidence") - text.Text = "Static DataSet scope · Value 1 / Value 2 · immutable evidence history"; + text.Text = "Shared SCL scope · Value 1 / Value 2 · immutable evidence history"; else if (text.Text == "Workbook devices") text.Text = "FAT source IEDs"; } From 33fa27f250fc41d14eddcf62a9d6f9c40705248e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:04:17 +0700 Subject: [PATCH 079/112] Keep FAT evidence scope inside shared workspace selection --- IoListTestingWindow.ContextUx.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/IoListTestingWindow.ContextUx.cs b/IoListTestingWindow.ContextUx.cs index dd380db04..7e948b52c 100644 --- a/IoListTestingWindow.ContextUx.cs +++ b/IoListTestingWindow.ContextUx.cs @@ -172,8 +172,8 @@ private async void ConnectIed_Click(object sender, RoutedEventArgs e) // P1: Connect owns endpoint/MMS/live-acquisition state, never FAT evidence // selection. The preparation path receives no checkbox scope here and therefore - // acquires every included import-ready static member for this IED. TEST remains - // authoritative only when Start FAT builds its explicit captureScope below. + // acquires every shared-workspace-selected included import-ready SCL row for this + // IED. TEST remains authoritative only when Start FAT builds captureScope below. if (ReferenceEquals(SelectedIed, targetIed)) PreparationStatusText = $"Connecting {targetIed.IedName} · {targetIed.IpAddress}:102"; RaisePreparationProperties(); @@ -253,9 +253,9 @@ private async void StartSelectedIedSafely_Click(object sender, RoutedEventArgs e // TEST is evidence authority only. Keep this selected capture scope for the // evidence controller, while connection/live acquisition below remains the full - // included import-ready IED scope. + // shared-workspace-selected included import-ready IED scope. var captureScope = selectedIed.TestPoints - .Where(point => point.IsIncludedInFat && point.TestEnabled && point.ImportReady) + .Where(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.ImportReady) .ToList(); try @@ -285,7 +285,7 @@ private async void StartSelectedIedSafely_Click(object sender, RoutedEventArgs e await CaptureTimeSyncEvidenceAfterPreparationAsync(engineeringWindow, selectedIed); } - // Operator selection remains authoritative. A selected static DataSet row that + // Operator selection remains authoritative. A selected SCL workspace row that // has no unique live point stays checked/included and visible, but it cannot be // allowed to manufacture evidence. Arm only the currently proven live subset. // Legacy source-contract spelling retained only as documentation: @@ -419,7 +419,9 @@ private void ContextWindow_Closed(object? sender, EventArgs e) } private static List EnabledPoints(IoTestIedPlan ied) - => ied.TestPoints.Where(point => point.IsIncludedInFat && point.TestEnabled).ToList(); + => ied.TestPoints + .Where(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled) + .ToList(); } public sealed class IoFatAllPassedVisibilityConverter : IMultiValueConverter From eb0c7dab1b84a89f3e9592cff69caeee9d1e0034 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:07:24 +0700 Subject: [PATCH 080/112] Preflight only shared workspace FAT scope --- Services/IoTesting/IoTestSessionPreflight.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Services/IoTesting/IoTestSessionPreflight.cs b/Services/IoTesting/IoTestSessionPreflight.cs index 7122629c9..f3f1a7e8b 100644 --- a/Services/IoTesting/IoTestSessionPreflight.cs +++ b/Services/IoTesting/IoTestSessionPreflight.cs @@ -10,19 +10,19 @@ public static IoTestSessionActionResult Validate(IoTestIedPlan? ied) return IoTestSessionActionResult.Failure("Select an imported IED first."); var unsafeEnabled = ied.TestPoints - .Where(point => point.IsIncludedInFat && point.TestEnabled && !point.ImportReady) + .Where(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && !point.ImportReady) .ToList(); if (unsafeEnabled.Count > 0) { return IoTestSessionActionResult.Failure( - $"{unsafeEnabled.Count} included, enabled signal(s) still require import/binding review. Disable them or repair their mapping before starting FAT."); + $"{unsafeEnabled.Count} shared-workspace-selected FAT signal(s) still require import/binding review. Disable their TEST scope or repair their mapping before starting FAT."); } var enabledReady = ied.TestPoints - .Where(point => point.IsIncludedInFat && point.TestEnabled && point.ImportReady) + .Where(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.ImportReady) .ToList(); if (enabledReady.Count == 0) - return IoTestSessionActionResult.Failure("No included, import-ready FAT signal is enabled for this IED."); + return IoTestSessionActionResult.Failure("No shared-workspace-selected, included, import-ready FAT signal has TEST enabled for this IED."); var duplicateReferences = enabledReady .GroupBy( @@ -40,11 +40,11 @@ public static IoTestSessionActionResult Validate(IoTestIedPlan? ied) var first = duplicateReferences[0]; var ids = string.Join(", ", first.Select(point => point.TestPointId)); return IoTestSessionActionResult.Failure( - $"One live IEC 61850 reference is assigned to multiple enabled test points in the included FAT scope ({ids}). Resolve the duplicate mapping before FAT so one edge cannot produce multiple results."); + $"One live IEC 61850 reference is assigned to multiple TEST-enabled points in the shared included FAT scope ({ids}). Resolve the duplicate mapping before FAT so one edge cannot produce multiple results."); } return IoTestSessionActionResult.Success( - $"{enabledReady.Count} included signal(s) passed FAT session preflight."); + $"{enabledReady.Count} shared-workspace-selected FAT signal(s) passed session preflight."); } private static bool IsDistinctSclDataSetMembershipFanOut(IEnumerable points) From d7131af3d187068e0a4be045d63acb0a387bffa1 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:33:08 +0700 Subject: [PATCH 081/112] Make FAT disposition a hard report scope boundary --- Services/IoTesting/IoFatReportScope.cs | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Services/IoTesting/IoFatReportScope.cs diff --git a/Services/IoTesting/IoFatReportScope.cs b/Services/IoTesting/IoFatReportScope.cs new file mode 100644 index 000000000..ae099ac0e --- /dev/null +++ b/Services/IoTesting/IoFatReportScope.cs @@ -0,0 +1,75 @@ +// Copyright 2026 Ari Sulistiono +// SPDX-License-Identifier: Apache-2.0 + +using ArIED61850Tester.Models.IoTesting; + +namespace ArIED61850Tester.Services.IoTesting; + +/// +/// Creates an immutable report projection from the shared Engineering/FAT workspace. +/// FAT disposition is a hard report boundary: a row removed by the operator must never +/// leak back into PDF/print-preview output merely because old evidence still exists. +/// +internal static class IoFatReportScope +{ + public static IoTestProject Create(IoTestProject project) + { + ArgumentNullException.ThrowIfNull(project); + return CreateCore(project, project.Ieds); + } + + public static IoTestProject CreateForIed(IoTestProject project, IoTestIedPlan ied) + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(ied); + if (!project.Ieds.Contains(ied)) + throw new ArgumentException("The selected IED does not belong to this IO FAT project.", nameof(ied)); + + return CreateCore(project, new[] { ied }); + } + + public static bool Includes(IoTestPointPlan point) + { + ArgumentNullException.ThrowIfNull(point); + return point.WorkspaceSelected && point.IsIncludedInFat; + } + + private static IoTestProject CreateCore( + IoTestProject project, + IEnumerable ieds) + { + var scoped = new IoTestProject + { + ProjectId = project.ProjectId, + SchemaVersion = project.SchemaVersion, + ProjectName = project.ProjectName, + SourceWorkbookName = project.SourceWorkbookName, + SourceWorkbookSha256 = project.SourceWorkbookSha256, + ImportedAt = project.ImportedAt, + DocumentControl = project.DocumentControl, + Ieds = ieds.Select(CloneIed).ToList() + }; + scoped.SetSources(project.Sources, project.SourceSetSha256); + return scoped; + } + + private static IoTestIedPlan CloneIed(IoTestIedPlan ied) + => new() + { + IedName = ied.IedName, + IpAddress = ied.IpAddress, + IedRole = ied.IedRole, + Location = ied.Location, + VoltageLevel = ied.VoltageLevel, + Switchgear = ied.Switchgear, + TestPoints = ied.TestPoints.Where(Includes).ToList(), + LatestComtradeFiles = ied.LatestComtradeFiles, + LatestComtradeRemotePath = ied.LatestComtradeRemotePath, + LatestComtradeCompleteness = ied.LatestComtradeCompleteness, + LatestComtradeAcquisitionSource = ied.LatestComtradeAcquisitionSource, + LatestComtradeModifiedAtUtc = ied.LatestComtradeModifiedAtUtc, + LatestComtradeCapturedAtUtc = ied.LatestComtradeCapturedAtUtc, + LatestComtradeFileCount = ied.LatestComtradeFileCount, + LatestComtradeKnownSizeBytes = ied.LatestComtradeKnownSizeBytes + }; +} From 20fad328a5fd77869205c3438d418355d5a64568 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:33:52 +0700 Subject: [PATCH 082/112] Exclude removed FAT rows from print preview scope --- .../IoTesting/IoFatReportPreviewService.cs | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/Services/IoTesting/IoFatReportPreviewService.cs b/Services/IoTesting/IoFatReportPreviewService.cs index 3f9be5c74..ba9deb303 100644 --- a/Services/IoTesting/IoFatReportPreviewService.cs +++ b/Services/IoTesting/IoFatReportPreviewService.cs @@ -6,28 +6,12 @@ namespace ArIED61850Tester.Services.IoTesting; /// -/// Creates an immutable report scope for the selected workbook IED. Native PDF and -/// FixedDocument preview then consume the same shared layout engine. +/// Creates the immutable selected-IED report projection used by WPF print preview. +/// Shared workspace membership and FAT disposition are applied before any layout engine, +/// so a right-click Remove from FAT cannot remain visible through older evidence state. /// public static class IoFatReportPreviewService { public static IoTestProject CreateIedScopedProject(IoTestProject project, IoTestIedPlan ied) - { - ArgumentNullException.ThrowIfNull(project); - ArgumentNullException.ThrowIfNull(ied); - if (!project.Ieds.Contains(ied)) - throw new ArgumentException("The selected IED does not belong to this IO FAT project.", nameof(ied)); - - return new IoTestProject - { - ProjectId = project.ProjectId, - SchemaVersion = project.SchemaVersion, - ProjectName = project.ProjectName, - SourceWorkbookName = project.SourceWorkbookName, - SourceWorkbookSha256 = project.SourceWorkbookSha256, - ImportedAt = project.ImportedAt, - DocumentControl = project.DocumentControl, - Ieds = new List { ied } - }; - } + => IoFatReportScope.CreateForIed(project, ied); } From 5faf31bc4aba66639b53fb351d2273157359953a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:34:21 +0700 Subject: [PATCH 083/112] Exclude removed rows from every FAT report layout --- Services/IoTesting/IoFatPdfReportService.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Services/IoTesting/IoFatPdfReportService.cs b/Services/IoTesting/IoFatPdfReportService.cs index 825f23322..3e85ed121 100644 --- a/Services/IoTesting/IoFatPdfReportService.cs +++ b/Services/IoTesting/IoFatPdfReportService.cs @@ -26,11 +26,16 @@ internal static IoFatReportLayoutPlan BuildLayout( bool draft = false) { ArgumentNullException.ThrowIfNull(project); + + // FAT disposition and shared Engineering/FAT workspace membership are applied + // before either report engine. This is a hard boundary: Remove from FAT excludes + // the row even when it already owns completed historical evidence. + var reportProject = IoFatReportScope.Create(project); var created = generatedAt ?? DateTimeOffset.Now; - var layout = project.SchemaVersion.StartsWith("ARSAS-FAT-SCL-", StringComparison.OrdinalIgnoreCase) - ? IoFatV2ReportLayoutEngine.Build(project, created, draft) - : IoFatExecutiveReportLayoutEngine.Build(project, created, draft); - return IoFatSupplementalReportLayoutDecorator.AppendFileServiceEvidence(project, layout); + var layout = reportProject.SchemaVersion.StartsWith("ARSAS-FAT-SCL-", StringComparison.OrdinalIgnoreCase) + ? IoFatV2ReportLayoutEngine.Build(reportProject, created, draft) + : IoFatExecutiveReportLayoutEngine.Build(reportProject, created, draft); + return IoFatSupplementalReportLayoutDecorator.AppendFileServiceEvidence(reportProject, layout); } public static void Save(string fileName, IoTestProject project, DateTimeOffset? generatedAt = null) From 8f852e68da72976f706cfa083cd7a28f2c901588 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:36:17 +0700 Subject: [PATCH 084/112] Keep selected IED preview identity while filtering at render boundary --- .../IoTesting/IoFatReportPreviewService.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Services/IoTesting/IoFatReportPreviewService.cs b/Services/IoTesting/IoFatReportPreviewService.cs index ba9deb303..3f9be5c74 100644 --- a/Services/IoTesting/IoFatReportPreviewService.cs +++ b/Services/IoTesting/IoFatReportPreviewService.cs @@ -6,12 +6,28 @@ namespace ArIED61850Tester.Services.IoTesting; /// -/// Creates the immutable selected-IED report projection used by WPF print preview. -/// Shared workspace membership and FAT disposition are applied before any layout engine, -/// so a right-click Remove from FAT cannot remain visible through older evidence state. +/// Creates an immutable report scope for the selected workbook IED. Native PDF and +/// FixedDocument preview then consume the same shared layout engine. /// public static class IoFatReportPreviewService { public static IoTestProject CreateIedScopedProject(IoTestProject project, IoTestIedPlan ied) - => IoFatReportScope.CreateForIed(project, ied); + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(ied); + if (!project.Ieds.Contains(ied)) + throw new ArgumentException("The selected IED does not belong to this IO FAT project.", nameof(ied)); + + return new IoTestProject + { + ProjectId = project.ProjectId, + SchemaVersion = project.SchemaVersion, + ProjectName = project.ProjectName, + SourceWorkbookName = project.SourceWorkbookName, + SourceWorkbookSha256 = project.SourceWorkbookSha256, + ImportedAt = project.ImportedAt, + DocumentControl = project.DocumentControl, + Ieds = new List { ied } + }; + } } From 5ad24e23d67fdc94a7ae8ac363549c75cfa13298 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:36:51 +0700 Subject: [PATCH 085/112] Use one scoped report contract for PDF and print preview --- Services/IoTesting/IoFatReportPreviewDocumentBuilder.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Services/IoTesting/IoFatReportPreviewDocumentBuilder.cs b/Services/IoTesting/IoFatReportPreviewDocumentBuilder.cs index e62511b34..01928b74b 100644 --- a/Services/IoTesting/IoFatReportPreviewDocumentBuilder.cs +++ b/Services/IoTesting/IoFatReportPreviewDocumentBuilder.cs @@ -27,8 +27,11 @@ public static FixedDocument Build( DateTimeOffset? generatedAt = null) { ArgumentNullException.ThrowIfNull(project); - // Replaces the legacy IoFatReportLayoutEngine.Build route with the document-controlled event-log layout. - var layout = IoFatExecutiveReportLayoutEngine.Build(project, generatedAt ?? DateTimeOffset.Now, draft); + // Replaces the legacy IoFatReportLayoutEngine.Build route with the exact same + // scoped layout contract used by native PDF. Remove from FAT is therefore a hard + // exclusion in both PDF and on-screen form/print preview, including rows that own + // historical completed evidence. + var layout = IoFatPdfReportService.BuildLayout(project, generatedAt, draft); return Render(layout); } From 009e2398b2c2a2e1b49bd870f67691829dabe0ea Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:37:30 +0700 Subject: [PATCH 086/112] Regress Remove from FAT across testing report boundaries --- .../IoFatRemoveReportScopeRegressionTests.cs | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs b/tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs new file mode 100644 index 000000000..a8167e91e --- /dev/null +++ b/tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs @@ -0,0 +1,119 @@ +using System.Text; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services.IoTesting; + +namespace ARSAS.Tests; + +public sealed class IoFatRemoveReportScopeRegressionTests +{ + [Theory] + [InlineData("ARSAS-FAT-SCL-2.0")] + [InlineData("ARSAS-FAT-IO-1.0")] + public void RemoveFromFat_IsHardExclusionForNativeReport_WithoutMutatingSharedOrTestSelection(string schemaVersion) + { + var removed = BuildPoint("REMOVE_ME", "IED1LD/GGIO1.Ind1.stVal"); + var kept = BuildPoint("KEEP_ME", "IED1LD/GGIO1.Ind2.stVal"); + var ied = new IoTestIedPlan + { + IedName = "IED1", + IpAddress = "192.0.2.10", + TestPoints = new List { removed, kept } + }; + var project = new IoTestProject + { + ProjectId = "REMOVE-REPORT-REGRESSION", + SchemaVersion = schemaVersion, + ProjectName = "Remove report regression", + Ieds = new List { ied } + }; + + removed.RemoveFromFat(); + + Assert.True(removed.WorkspaceSelected); + Assert.True(removed.TestEnabled); + Assert.False(removed.IsIncludedInFat); + + var pdf = IoFatPdfReportService.Generate( + project, + new DateTimeOffset(2026, 9, 3, 4, 30, 0, TimeSpan.Zero)); + var reportText = Encoding.ASCII.GetString(pdf); + + Assert.DoesNotContain("REMOVE_ME", reportText, StringComparison.Ordinal); + Assert.Contains("KEEP_ME", reportText, StringComparison.Ordinal); + } + + [Fact] + public void RestoreToFat_ReturnsSameSharedSignalToReport_WithTestPreferencePreserved() + { + var point = BuildPoint("RESTORE_ME", "IED1LD/GGIO1.Ind1.stVal"); + var ied = new IoTestIedPlan + { + IedName = "IED1", + IpAddress = "192.0.2.10", + TestPoints = new List { point } + }; + var project = new IoTestProject + { + ProjectId = "RESTORE-REPORT-REGRESSION", + SchemaVersion = "ARSAS-FAT-SCL-2.0", + ProjectName = "Restore report regression", + Ieds = new List { ied } + }; + + point.TestEnabled = false; + point.RemoveFromFat(); + point.RestoreToFat(); + + Assert.True(point.WorkspaceSelected); + Assert.False(point.TestEnabled); + Assert.True(point.IsIncludedInFat); + + var pdf = IoFatPdfReportService.Generate( + project, + new DateTimeOffset(2026, 9, 3, 4, 31, 0, TimeSpan.Zero)); + var reportText = Encoding.ASCII.GetString(pdf); + Assert.Contains("RESTORE_ME", reportText, StringComparison.Ordinal); + } + + [Fact] + public void PrintPreview_UsesSameScopedLayoutContractAsNativePdf() + { + var source = File.ReadAllText(FindRepoFile("Services/IoTesting/IoFatReportPreviewDocumentBuilder.cs")); + var reportService = File.ReadAllText(FindRepoFile("Services/IoTesting/IoFatPdfReportService.cs")); + var scope = File.ReadAllText(FindRepoFile("Services/IoTesting/IoFatReportScope.cs")); + + Assert.Contains("IoFatPdfReportService.BuildLayout", source, StringComparison.Ordinal); + Assert.Contains("IoFatReportScope.Create(project)", reportService, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected && point.IsIncludedInFat", scope, StringComparison.Ordinal); + } + + private static IoTestPointPlan BuildPoint(string signalName, string reference) + => new() + { + TestPointId = signalName, + IedName = "IED1", + IpAddress = "192.0.2.10", + SignalName = signalName, + ObjectReference = reference, + FunctionalConstraint = "ST", + ExpectedOnText = "TRUE", + ExpectedOffText = "FALSE", + WorkspaceSelected = true, + TestEnabled = true, + ImportReady = true + }; + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException($"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } +} From fa715aedebc5d8e0c30ffdc8e383d8d460184d64 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:40:49 +0700 Subject: [PATCH 087/112] Restore shared workspace selection and manual SCL rows on reopen --- .../IoTestWorkspaceBootstrapService.cs | 126 +++++++++++++++++- 1 file changed, 124 insertions(+), 2 deletions(-) diff --git a/Services/IoTesting/IoTestWorkspaceBootstrapService.cs b/Services/IoTesting/IoTestWorkspaceBootstrapService.cs index 16c2d9476..1fb83b1a4 100644 --- a/Services/IoTesting/IoTestWorkspaceBootstrapService.cs +++ b/Services/IoTesting/IoTestWorkspaceBootstrapService.cs @@ -150,6 +150,7 @@ private static void ApplySnapshotProgress(IoTestProject project, string snapshot var savedIeds = RequiredArray(savedProject, "ieds").EnumerateArray().ToArray(); RestoreIedLevelEvidence(project, savedIeds); + RestoreMissingManualWorkspaceRows(project, savedIeds); var savedPoints = savedIeds .SelectMany(ied => RequiredArray(ied, "testPoints").EnumerateArray()) @@ -160,8 +161,14 @@ private static void ApplySnapshotProgress(IoTestProject project, string snapshot if (!savedPoints.TryGetValue(point.TestPointId, out var saved)) continue; - // TestEnabled and FAT disposition are both operator-authored and are restored - // independently. Restoring/removing a FAT row must never rewrite its checkbox. + // Shared Engineering/FAT membership, FAT TEST scope, and FAT disposition are + // three independent operator authorities. Same-source continuation restores all + // three without allowing Remove/Restore to rewrite Engineering selection. + if (saved.TryGetProperty("workspaceSelected", out var workspaceSelected) && + workspaceSelected.ValueKind is JsonValueKind.True or JsonValueKind.False) + { + point.WorkspaceSelected = workspaceSelected.GetBoolean(); + } if (saved.TryGetProperty("testEnabled", out var enabled) && enabled.ValueKind is JsonValueKind.True or JsonValueKind.False) point.TestEnabled = enabled.GetBoolean(); point.RestoreFatDisposition(OptionalEnum(saved, "fatDisposition", FatSignalDisposition.Included)); @@ -224,6 +231,116 @@ private static bool SnapshotSourceMatches(JsonElement savedProject, IoTestProjec legacyWorkbookSha.Equals(project.SourceWorkbookSha256, StringComparison.OrdinalIgnoreCase); } + private static void RestoreMissingManualWorkspaceRows( + IoTestProject project, + IReadOnlyList savedIeds) + { + if (!project.SchemaVersion.StartsWith("ARSAS-FAT-SCL-", StringComparison.OrdinalIgnoreCase)) + return; + + var trustedSclHashes = project.Sources + .Where(source => source.Kind.Equals(IoFatSourceKinds.Scl, StringComparison.OrdinalIgnoreCase)) + .Select(source => source.Sha256) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + if (trustedSclHashes.Count == 0) + return; + + foreach (var ied in project.Ieds) + { + var savedIed = savedIeds.FirstOrDefault(candidate => + OptionalString(candidate, "iedName", string.Empty).Equals(ied.IedName, StringComparison.OrdinalIgnoreCase) && + OptionalString(candidate, "ipAddress", string.Empty).Equals(ied.IpAddress, StringComparison.OrdinalIgnoreCase)); + if (savedIed.ValueKind != JsonValueKind.Object) + continue; + + var existingIds = ied.TestPoints + .Select(point => point.TestPointId) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + foreach (var savedPoint in RequiredArray(savedIed, "testPoints").EnumerateArray()) + { + var testPointId = RequiredString(savedPoint, "testPointId"); + if (existingIds.Contains(testPointId)) + continue; + + var bindingStatus = OptionalString(savedPoint, "bindingStatus", string.Empty); + if (!bindingStatus.Equals( + IoTestSignalSelectionService.SclWorkspaceAuthorityBindingStatus, + StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (!testPointId.StartsWith("scl-manual-", StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException($"Saved manual SCL row '{testPointId}' has an invalid workspace identity."); + + var savedIedName = RequiredString(savedPoint, "iedName"); + var savedIpAddress = RequiredString(savedPoint, "ipAddress"); + if (!savedIedName.Equals(ied.IedName, StringComparison.OrdinalIgnoreCase) || + !savedIpAddress.Equals(ied.IpAddress, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException($"Saved manual SCL row '{testPointId}' does not belong to its persisted IED endpoint."); + } + + var sourceSha = OptionalString(savedPoint, "signalAddress", string.Empty).Trim(); + if (sourceSha.Length == 0 || !trustedSclHashes.Contains(sourceSha)) + { + throw new InvalidDataException( + $"Saved manual SCL row '{testPointId}' does not belong to the exact reopened SCL source set."); + } + + var objectReference = RequiredString(savedPoint, "objectReference"); + var sourceReference = OptionalString(savedPoint, "sourceIecReference", string.Empty); + if (string.IsNullOrWhiteSpace(objectReference) || string.IsNullOrWhiteSpace(sourceReference)) + throw new InvalidDataException($"Saved manual SCL row '{testPointId}' has incomplete IEC 61850 identity."); + + var point = new IoTestPointPlan + { + TestPointId = testPointId, + IedName = savedIedName, + IpAddress = savedIpAddress, + SignalName = RequiredString(savedPoint, "signalName"), + ObjectReference = objectReference, + FunctionalConstraint = OptionalString(savedPoint, "functionalConstraint", string.Empty), + ExpectedOnText = RequiredString(savedPoint, "expectedOnText"), + ExpectedOffText = RequiredString(savedPoint, "expectedOffText"), + ExpectedOnRaw = OptionalInt(savedPoint, "expectedOnRaw", 1), + ExpectedOffRaw = OptionalInt(savedPoint, "expectedOffRaw", 0), + DataType = OptionalString(savedPoint, "dataType", "SDI"), + SignalAddress = sourceSha, + DataSetName = OptionalString(savedPoint, "dataSetName", string.Empty), + LogicalDevice = OptionalString(savedPoint, "logicalDevice", string.Empty), + LogicalNode = OptionalString(savedPoint, "logicalNode", string.Empty), + DataObject = OptionalString(savedPoint, "dataObject", string.Empty), + DataAttribute = OptionalString(savedPoint, "dataAttribute", string.Empty), + Cdc = OptionalString(savedPoint, "cdc", string.Empty), + SourceIecReference = sourceReference, + ReportDisplayReference = OptionalString(savedPoint, "reportDisplayReference", sourceReference), + EventLogSearchReference = OptionalString(savedPoint, "eventLogSearchReference", objectReference), + EvidenceExpected = OptionalString(savedPoint, "evidenceExpected", string.Empty), + MappingQuality = OptionalString(savedPoint, "mappingQuality", string.Empty), + ReviewStatus = OptionalString(savedPoint, "reviewStatus", string.Empty), + ReviewReason = OptionalString(savedPoint, "reviewReason", string.Empty), + EventLogMatch = OptionalString(savedPoint, "eventLogMatch", string.Empty), + EvidenceReference = OptionalString(savedPoint, "evidenceReference", string.Empty), + ReviewerComment = OptionalString(savedPoint, "reviewerComment", string.Empty), + SourceSheet = OptionalString(savedPoint, "sourceSheet", string.Empty), + SourceRow = OptionalInt(savedPoint, "sourceRow", 0), + SignalKind = OptionalEnum(savedPoint, "signalKind", FatSignalKind.Other), + CaptureMode = OptionalEnum(savedPoint, "captureMode", FatCaptureMode.OperatorSnapshot), + WorkspaceSelected = OptionalBool(savedPoint, "workspaceSelected", true), + TestEnabled = OptionalBool(savedPoint, "testEnabled", true), + ImportReady = OptionalBool(savedPoint, "importReady", true), + BindingStatus = bindingStatus, + BindingEvidence = OptionalString(savedPoint, "bindingEvidence", string.Empty) + }; + + if (!ied.AddTestPoint(point)) + throw new InvalidDataException($"Saved manual SCL row '{testPointId}' collides with the reopened workspace."); + existingIds.Add(testPointId); + } + } + } + private static void RestoreIedLevelEvidence(IoTestProject project, IReadOnlyList savedIeds) { foreach (var ied in project.Ieds) @@ -303,6 +420,11 @@ private static string OptionalString(JsonElement parent, string property, string ? value.GetString() ?? fallback : fallback; + private static bool OptionalBool(JsonElement parent, string property, bool fallback) + => parent.TryGetProperty(property, out var value) && value.ValueKind is JsonValueKind.True or JsonValueKind.False + ? value.GetBoolean() + : fallback; + private static int OptionalInt(JsonElement parent, string property, int fallback) => parent.TryGetProperty(property, out var value) && value.TryGetInt32(out var number) ? number From 22661c2554d2ec678ba373d777d027ad54442ed7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:42:46 +0700 Subject: [PATCH 088/112] Regress shared SCL workspace persistence across reopen --- ...aredWorkspacePersistenceRegressionTests.cs | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatSharedWorkspacePersistenceRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatSharedWorkspacePersistenceRegressionTests.cs b/tests/ARSAS.Tests/IoFatSharedWorkspacePersistenceRegressionTests.cs new file mode 100644 index 000000000..753443a2e --- /dev/null +++ b/tests/ARSAS.Tests/IoFatSharedWorkspacePersistenceRegressionTests.cs @@ -0,0 +1,191 @@ +using System.Security.Cryptography; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services.IoTesting; + +namespace ARSAS.Tests; + +public sealed class IoFatSharedWorkspacePersistenceRegressionTests +{ + [Fact] + public async Task SameSclReopen_RestoresManualWorkspaceRow_SelectionTestScopeAndFatDispositionIndependently() + { + var root = TempDirectory(); + var source = Path.Combine(root, "manual.cid"); + await File.WriteAllBytesAsync(source, new byte[] { 0x53, 0x43, 0x4C, 0x2D, 0x31 }); + var sourceSha = Hash(source); + var sourceInputs = new[] { new IoFatSourceInput(source, IoFatSourceKinds.Scl) }; + var projectsRoot = Path.Combine(root, "projects"); + var evidenceRoot = Path.Combine(root, "evidence"); + + var manual = ManualPoint(sourceSha); + manual.WorkspaceSelected = false; + manual.TestEnabled = false; + manual.RemoveFromFat(); + + var original = SclProject(manual); + var originalSession = Session(original, evidenceRoot); + var opened = await IoTestWorkspacePersistence.OpenSourcesAsync( + original, + originalSession, + sourceInputs, + projectsRoot, + evidenceRoot); + using (originalSession) + using (opened.Workspace) + opened.Workspace.SaveNow(); + + // A fresh direct-SCL importer does not fabricate manual non-DataSet rows. The + // same-source workspace continuation must re-materialize the persisted manual row + // before restoring its three independent operator authorities. + var fresh = SclProject(); + var restored = await IoTestWorkspaceBootstrapService.OpenSourcesAsync( + fresh, + sourceInputs, + projectsRoot, + evidenceRoot, + Session); + + using (restored.Session) + using (restored.Workspace) + { + Assert.True(restored.RestoredProgress); + var point = Assert.Single(restored.Project.Ieds[0].TestPoints); + Assert.Equal(manual.TestPointId, point.TestPointId); + Assert.Equal(IoTestSignalSelectionService.SclWorkspaceAuthorityBindingStatus, point.BindingStatus); + Assert.Equal(sourceSha, point.SignalAddress); + Assert.False(point.WorkspaceSelected); + Assert.False(point.TestEnabled); + Assert.False(point.IsIncludedInFat); + } + } + + [Fact] + public async Task SameSclReopen_RestoresStaticWorkspaceSelection_WithoutChangingFatTestOrDisposition() + { + var root = TempDirectory(); + var source = Path.Combine(root, "static.cid"); + await File.WriteAllBytesAsync(source, new byte[] { 0x53, 0x43, 0x4C, 0x2D, 0x32 }); + var sourceSha = Hash(source); + var sourceInputs = new[] { new IoFatSourceInput(source, IoFatSourceKinds.Scl) }; + var projectsRoot = Path.Combine(root, "projects"); + var evidenceRoot = Path.Combine(root, "evidence"); + + var originalPoint = StaticPoint(sourceSha); + originalPoint.WorkspaceSelected = false; + originalPoint.TestEnabled = true; + var original = SclProject(originalPoint); + var originalSession = Session(original, evidenceRoot); + var opened = await IoTestWorkspacePersistence.OpenSourcesAsync( + original, + originalSession, + sourceInputs, + projectsRoot, + evidenceRoot); + using (originalSession) + using (opened.Workspace) + opened.Workspace.SaveNow(); + + // Fresh import defaults the same static DataSet member selected. Continuation must + // restore the Engineering/shared selection without coupling it to FAT TEST state. + var freshPoint = StaticPoint(sourceSha); + freshPoint.WorkspaceSelected = true; + freshPoint.TestEnabled = true; + var restored = await IoTestWorkspaceBootstrapService.OpenSourcesAsync( + SclProject(freshPoint), + sourceInputs, + projectsRoot, + evidenceRoot, + Session); + + using (restored.Session) + using (restored.Workspace) + { + var point = Assert.Single(restored.Project.Ieds[0].TestPoints); + Assert.False(point.WorkspaceSelected); + Assert.True(point.TestEnabled); + Assert.True(point.IsIncludedInFat); + } + } + + private static IoTestPointPlan ManualPoint(string sourceSha) + => new() + { + TestPointId = "scl-manual-0123456789abcdef0123", + IedName = "IED1", + IpAddress = "192.0.2.10", + SignalName = "Manual GGIO indication", + ObjectReference = "IED1LD/GGIO1.Ind1.stVal", + FunctionalConstraint = "ST", + ExpectedOnText = "TRUE", + ExpectedOffText = "FALSE", + DataType = "BOOLEAN", + SignalAddress = sourceSha, + SourceIecReference = "IED1LD/GGIO1.Ind1.stVal", + ReportDisplayReference = "IED1LD/GGIO1.Ind1.stVal", + EventLogSearchReference = "IED1LD/GGIO1.Ind1.stVal", + SignalKind = FatSignalKind.Discrete, + CaptureMode = FatCaptureMode.AutomaticTransition, + WorkspaceSelected = true, + TestEnabled = true, + ImportReady = true, + BindingStatus = IoTestSignalSelectionService.SclWorkspaceAuthorityBindingStatus, + BindingEvidence = "Shared SCL workspace authority" + }; + + private static IoTestPointPlan StaticPoint(string sourceSha) + => new() + { + TestPointId = "scl-static-0001", + IedName = "IED1", + IpAddress = "192.0.2.10", + SignalName = "Static GGIO indication", + ObjectReference = "IED1LD/GGIO1.Ind2.stVal", + FunctionalConstraint = "ST", + ExpectedOnText = "TRUE", + ExpectedOffText = "FALSE", + DataType = "BOOLEAN", + SignalAddress = sourceSha, + DataSetName = "IED1LD/LLN0.Events", + SourceIecReference = "IED1LD/GGIO1.Ind2.stVal", + ReportDisplayReference = "IED1LD/GGIO1.Ind2.stVal", + EventLogSearchReference = "IED1LD/GGIO1.Ind2.stVal", + SourceRow = 1, + SignalKind = FatSignalKind.Discrete, + CaptureMode = FatCaptureMode.AutomaticTransition, + WorkspaceSelected = true, + TestEnabled = true, + ImportReady = true, + BindingStatus = IoTestSignalSelectionService.SclDataSetAuthorityBindingStatus, + BindingEvidence = "Static SCL DataSet authority" + }; + + private static IoTestProject SclProject(params IoTestPointPlan[] points) + => new() + { + ProjectId = "SHARED-SCL-WORKSPACE", + SchemaVersion = "ARSAS-FAT-SCL-1.0", + ProjectName = "Shared SCL workspace regression", + Ieds = new List + { + new() + { + IedName = "IED1", + IpAddress = "192.0.2.10", + TestPoints = points.ToList() + } + } + }; + + private static IoTestSessionController Session(IoTestProject project, string evidenceRoot) + => new(project, _ => null, action => action(), evidenceRoot); + + private static string Hash(string path) + => Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path))).ToLowerInvariant(); + + private static string TempDirectory() + { + var path = Path.Combine(Path.GetTempPath(), "ARSAS.Tests", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(path); + return path; + } +} From b014c02ebdcf3ac5083ce059b0d738b146df7fab Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:44:13 +0700 Subject: [PATCH 089/112] Regress Remove from FAT as a hard test and report exclusion --- .../IoFatRemoveReportScopeRegressionTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs b/tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs index a8167e91e..45d3e0aa9 100644 --- a/tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatRemoveReportScopeRegressionTests.cs @@ -42,6 +42,30 @@ public void RemoveFromFat_IsHardExclusionForNativeReport_WithoutMutatingSharedOr Assert.Contains("KEEP_ME", reportText, StringComparison.Ordinal); } + [Fact] + public void RemoveFromFat_ExcludesRowFromTestPreflight_EvenWhenTestRemainsEnabled() + { + var removed = BuildPoint("REMOVE_FROM_TEST", "IED1LD/GGIO1.Ind1.stVal"); + var kept = BuildPoint("KEEP_IN_TEST", "IED1LD/GGIO1.Ind2.stVal"); + // Make the removed row deliberately unsafe if it were still part of the test scope. + removed = CloneWithImportReady(removed, importReady: false); + removed.RemoveFromFat(); + + var ied = new IoTestIedPlan + { + IedName = "IED1", + IpAddress = "192.0.2.10", + TestPoints = new List { removed, kept } + }; + + var result = IoTestSessionPreflight.Validate(ied); + + Assert.True(removed.TestEnabled); + Assert.False(removed.IsIncludedInFat); + Assert.True(result.Succeeded); + Assert.Contains("1 shared-workspace-selected FAT signal", result.Message, StringComparison.Ordinal); + } + [Fact] public void RestoreToFat_ReturnsSameSharedSignalToReport_WithTestPreferencePreserved() { @@ -81,10 +105,15 @@ public void PrintPreview_UsesSameScopedLayoutContractAsNativePdf() var source = File.ReadAllText(FindRepoFile("Services/IoTesting/IoFatReportPreviewDocumentBuilder.cs")); var reportService = File.ReadAllText(FindRepoFile("Services/IoTesting/IoFatPdfReportService.cs")); var scope = File.ReadAllText(FindRepoFile("Services/IoTesting/IoFatReportScope.cs")); + var autoConnect = File.ReadAllText(FindRepoFile("MainWindow.IoTesting.AutoConnect.cs")); + var contextUx = File.ReadAllText(FindRepoFile("IoListTestingWindow.ContextUx.cs")); Assert.Contains("IoFatPdfReportService.BuildLayout", source, StringComparison.Ordinal); Assert.Contains("IoFatReportScope.Create(project)", reportService, StringComparison.Ordinal); Assert.Contains("point.WorkspaceSelected && point.IsIncludedInFat", scope, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected &&", autoConnect, StringComparison.Ordinal); + Assert.Contains("point.IsIncludedInFat &&", autoConnect, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled", contextUx, StringComparison.Ordinal); } private static IoTestPointPlan BuildPoint(string signalName, string reference) @@ -103,6 +132,22 @@ private static IoTestPointPlan BuildPoint(string signalName, string reference) ImportReady = true }; + private static IoTestPointPlan CloneWithImportReady(IoTestPointPlan source, bool importReady) + => new() + { + TestPointId = source.TestPointId, + IedName = source.IedName, + IpAddress = source.IpAddress, + SignalName = source.SignalName, + ObjectReference = source.ObjectReference, + FunctionalConstraint = source.FunctionalConstraint, + ExpectedOnText = source.ExpectedOnText, + ExpectedOffText = source.ExpectedOffText, + WorkspaceSelected = source.WorkspaceSelected, + TestEnabled = source.TestEnabled, + ImportReady = importReady + }; + private static string FindRepoFile(string relativePath) { DirectoryInfo? directory = new(AppContext.BaseDirectory); From a4f9ad885c50ecaaea33a756e6d08005fdd3da7b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:45:26 +0700 Subject: [PATCH 090/112] Fix manual workspace projection compile shadowing --- Services/IoTesting/IoFatEngineeringSelectionBridge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs index 0c9ba937c..381c9cce9 100644 --- a/Services/IoTesting/IoFatEngineeringSelectionBridge.cs +++ b/Services/IoTesting/IoFatEngineeringSelectionBridge.cs @@ -110,8 +110,8 @@ public static bool ApplyEngineeringSignalSelection( changed = true; } - if (matching.Length == 0 && selected && TryCreateManualWorkspacePoint(signal, ied, device, out var point)) - changed |= ied.AddTestPoint(point); + if (matching.Length == 0 && selected && TryCreateManualWorkspacePoint(signal, ied, device, out var createdPoint)) + changed |= ied.AddTestPoint(createdPoint); return changed; } From 1c3727bfed9ecf413238669a950edaa42d00a0a5 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:50:29 +0700 Subject: [PATCH 091/112] Align partial live regression with shared SCL authority --- tests/ARSAS.Tests/IoFatP53PartialLiveRegressionTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/IoFatP53PartialLiveRegressionTests.cs b/tests/ARSAS.Tests/IoFatP53PartialLiveRegressionTests.cs index a9248a27b..6399a9938 100644 --- a/tests/ARSAS.Tests/IoFatP53PartialLiveRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatP53PartialLiveRegressionTests.cs @@ -11,7 +11,7 @@ public void Preparation_AllowsUsablePartialSclLiveScopeWithoutMutatingOperatorSe Assert.Contains("mayProceedWithPartialSclSelection", source, StringComparison.Ordinal); Assert.Contains("selection.Matches.Count > 0", source, StringComparison.Ordinal); - Assert.Contains("unresolvedSelectionPoints.All(IoTestSignalSelectionService.IsSclDataSetAuthority)", source, StringComparison.Ordinal); + Assert.Contains("unresolvedSelectionPoints.All(IoTestSignalSelectionService.IsDirectSclAuthority)", source, StringComparison.Ordinal); Assert.Contains("if (liveCount == 0)", source, StringComparison.Ordinal); Assert.DoesNotContain("point.TestEnabled = false", source, StringComparison.Ordinal); Assert.DoesNotContain("RemoveFromFat()", source, StringComparison.Ordinal); @@ -44,6 +44,7 @@ public void OperatorSelectedWaitingRow_RemainsIncludedAndSelectedByModelContract FunctionalConstraint = "MX", ExpectedOnText = "Value 1", ExpectedOffText = "Value 2", + WorkspaceSelected = true, TestEnabled = true, ImportReady = true }; @@ -53,6 +54,7 @@ public void OperatorSelectedWaitingRow_RemainsIncludedAndSelectedByModelContract "Waiting for safe structured primary binding.", "device-1"); + Assert.True(point.WorkspaceSelected); Assert.True(point.TestEnabled); Assert.True(point.IsIncludedInFat); Assert.Equal(IoTestLiveBindingState.NotEvaluated, point.LiveBindingState); From 8e058f088be42ad26001bf3cccac8bb06931fe12 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:50:56 +0700 Subject: [PATCH 092/112] Regress Engineering selection as shared workspace authority --- ...FreshSclSelectionAuthorityRegressionTests.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs b/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs index 9c81eb358..1a329e05e 100644 --- a/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatFreshSclSelectionAuthorityRegressionTests.cs @@ -13,24 +13,27 @@ public void DirectSclImporter_DefaultsStaticDatasetRowsToChecked() } [Fact] - public void SclBridge_UsesExistingEngineeringSelectionWhenItWasAlreadyDecided() + public void SclBridge_UsesExistingEngineeringSelectionAsSharedWorkspaceAuthority() { var source = Read("Services/IoTesting/IoFatEngineeringSelectionBridge.cs"); Assert.Contains("if (preserveExistingEngineeringSelection)", source, StringComparison.Ordinal); - Assert.Contains("point.TestEnabled != signal.IsSelected", source, StringComparison.Ordinal); - Assert.Contains("point.TestEnabled = signal.IsSelected;", source, StringComparison.Ordinal); - Assert.Contains("point.TestEnabled = true;", source, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected != signal.IsSelected", source, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected = signal.IsSelected;", source, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected = true;", source, StringComparison.Ordinal); + Assert.DoesNotContain("point.TestEnabled = signal.IsSelected;", source, StringComparison.Ordinal); Assert.DoesNotContain("_ = preserveExistingEngineeringSelection;", source, StringComparison.Ordinal); } [Fact] - public void FreshSclBridge_DoesNotMergeSelectionWithRemovedSignalsDisposition() + public void FreshSclBridge_DoesNotMergeSharedSelectionWithFatDispositionOrTestScope() { var source = Read("Services/IoTesting/IoFatEngineeringSelectionBridge.cs"); - Assert.Contains("FatDisposition remains orthogonal", source, StringComparison.Ordinal); - Assert.DoesNotContain("point.RestoreToFat();\n point.TestEnabled = true;", source, StringComparison.Ordinal); + Assert.Contains("FatDisposition remains a FAT-only Remove/Restore authority", source, StringComparison.Ordinal); + Assert.Contains("TEST never mutates Engineering", source, StringComparison.Ordinal); + Assert.DoesNotContain("point.RestoreToFat();", source, StringComparison.Ordinal); + Assert.DoesNotContain("point.TestEnabled = signal.IsSelected;", source, StringComparison.Ordinal); } private static string Read(string relativePath) From ad0424e7f6c852fe0a36887180f9fe37e06f02b0 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:51:24 +0700 Subject: [PATCH 093/112] Preserve preflight duplicate contract wording --- Services/IoTesting/IoTestSessionPreflight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/IoTesting/IoTestSessionPreflight.cs b/Services/IoTesting/IoTestSessionPreflight.cs index f3f1a7e8b..508045120 100644 --- a/Services/IoTesting/IoTestSessionPreflight.cs +++ b/Services/IoTesting/IoTestSessionPreflight.cs @@ -40,7 +40,7 @@ public static IoTestSessionActionResult Validate(IoTestIedPlan? ied) var first = duplicateReferences[0]; var ids = string.Join(", ", first.Select(point => point.TestPointId)); return IoTestSessionActionResult.Failure( - $"One live IEC 61850 reference is assigned to multiple TEST-enabled points in the shared included FAT scope ({ids}). Resolve the duplicate mapping before FAT so one edge cannot produce multiple results."); + $"One live IEC 61850 reference is assigned to multiple enabled test points in the shared included FAT scope ({ids}). Resolve the duplicate mapping before FAT so one edge cannot produce multiple results."); } return IoTestSessionActionResult.Success( From 5670a8250eb8d0259b9364d52e6c72077281daf5 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:51:52 +0700 Subject: [PATCH 094/112] Align selection authority regression with shared workspace state --- tests/ARSAS.Tests/IoFatSelectionAuthorityRegressionTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatSelectionAuthorityRegressionTests.cs b/tests/ARSAS.Tests/IoFatSelectionAuthorityRegressionTests.cs index 6ee6f9fd4..cbfb57880 100644 --- a/tests/ARSAS.Tests/IoFatSelectionAuthorityRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatSelectionAuthorityRegressionTests.cs @@ -10,7 +10,8 @@ public void SnapshotAndPackageRestore_DoNotAutoDisableCompletedRows() Assert.DoesNotContain("ExcludeCompletedFromNextSession", source, StringComparison.Ordinal); Assert.DoesNotContain("point.TestEnabled = false", source, StringComparison.Ordinal); Assert.Contains("point.TestEnabled = enabled.GetBoolean();", source, StringComparison.Ordinal); - Assert.Contains("TestEnabled and FAT disposition are both operator-authored", source, StringComparison.Ordinal); + Assert.Contains("WorkspaceSelected", source, StringComparison.Ordinal); + Assert.Contains("FAT TEST scope, and FAT disposition", source, StringComparison.Ordinal); } [Fact] @@ -19,6 +20,7 @@ public void CleanRetest_ClearsEvidenceButNeverChangesOperatorSelection() var source = Read("Services/IoTesting/IoFatCleanSessionService.cs"); Assert.DoesNotContain("point.TestEnabled =", source, StringComparison.Ordinal); + Assert.DoesNotContain("point.WorkspaceSelected =", source, StringComparison.Ordinal); Assert.Contains("runtime.OnEvidence = null;", source, StringComparison.Ordinal); Assert.Contains("runtime.OffEvidence = null;", source, StringComparison.Ordinal); Assert.Contains("Selection belongs to the operator", source, StringComparison.Ordinal); @@ -31,7 +33,7 @@ public void StartAndContinuation_NeverRewriteCheckboxStateAndArmOnlyLiveRows() Assert.DoesNotContain("point.TestEnabled = false", source, StringComparison.Ordinal); Assert.DoesNotContain("point.TestEnabled = true", source, StringComparison.Ordinal); - Assert.Contains("point.IsIncludedInFat && point.TestEnabled && point.ImportReady", source, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.ImportReady", source, StringComparison.Ordinal); Assert.Contains("point.LiveBindingState == IoTestLiveBindingState.LivePointReady", source, StringComparison.Ordinal); Assert.Contains("Session.Start(selectedIed, liveCaptureScope)", source, StringComparison.Ordinal); Assert.Contains("checkbox/disposition unchanged", source, StringComparison.Ordinal); From dbe440b90f3d44b68e797a8b63db5c65d480b624 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:52:48 +0700 Subject: [PATCH 095/112] Align P1 regression with shared SCL acquisition authority --- .../ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs index 1beea7f49..9ceea6b39 100644 --- a/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatMultiIedConnectionRegressionTests.cs @@ -139,18 +139,20 @@ public void P1_ConnectAndAcquisitionDoNotUseTestCheckboxAsAuthority() var resolver = ReadRepoFile("Services/IoTesting/IoTestSignalSelectionService.cs"); Assert.Contains("requestedPointsOverride is null || point.TestEnabled", autoConnect, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected", autoConnect, StringComparison.Ordinal); Assert.Contains("point.IsIncludedInFat", autoConnect, StringComparison.Ordinal); Assert.Contains("point.ImportReady", autoConnect, StringComparison.Ordinal); Assert.Contains("var acquisitionSignals = selection.Matches", autoConnect, StringComparison.Ordinal); Assert.Contains("StartIoFatDeviceMonitorAsync(device, acquisitionSignals)", autoConnect, StringComparison.Ordinal); - Assert.Contains("point.TestEnabled || IsSclDataSetAuthority(point)", resolver, StringComparison.Ordinal); + Assert.Contains("point.TestEnabled || IsDirectSclAuthority(point)", resolver, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected", resolver, StringComparison.Ordinal); Assert.Contains("point.IsIncludedInFat", resolver, StringComparison.Ordinal); Assert.DoesNotContain("var enabledReady = targetIed.TestPoints", contextUx, StringComparison.Ordinal); Assert.Contains("private Task PrepareIndependentIedConnectionAsync", contextUx, StringComparison.Ordinal); Assert.Contains("Project,\n targetIed,\n progress);", contextUx, StringComparison.Ordinal); Assert.Contains("var captureScope = selectedIed.TestPoints", contextUx, StringComparison.Ordinal); - Assert.Contains("point.IsIncludedInFat && point.TestEnabled && point.ImportReady", contextUx, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.ImportReady", contextUx, StringComparison.Ordinal); Assert.Contains("var result = Session.Start(selectedIed, liveCaptureScope);", contextUx, StringComparison.Ordinal); Assert.Contains("device.BeginBulkSignalSelection();", monitor, StringComparison.Ordinal); @@ -229,6 +231,7 @@ private static IoTestPointPlan Point( EventLogSearchReference = reference, ReportDisplayReference = reference + " [ST]", BindingStatus = bindingStatus, + WorkspaceSelected = true, TestEnabled = true, ImportReady = true }; From 7017208baa13a7e02d34533c7a0a9cc1ed5fedef Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 11:55:19 +0700 Subject: [PATCH 096/112] Persist shared SCL selection and manual row identity --- .../IoTestProjectPersistenceService.cs | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Services/IoTesting/IoTestProjectPersistenceService.cs b/Services/IoTesting/IoTestProjectPersistenceService.cs index a9dd0d425..9bfd1e55f 100644 --- a/Services/IoTesting/IoTestProjectPersistenceService.cs +++ b/Services/IoTesting/IoTestProjectPersistenceService.cs @@ -555,9 +555,21 @@ private void SaveFromTimer() Value2Evidence = point.Runtime.Value2Evidence }) { + WorkspaceSelected = point.WorkspaceSelected, SignalKind = point.SignalKind, CaptureMode = point.CaptureMode, - FatDisposition = point.FatDisposition + FatDisposition = point.FatDisposition, + Cdc = point.Cdc, + SourceIecReference = point.SourceIecReference, + ReportDisplayReference = point.ReportDisplayReference, + EventLogSearchReference = point.EventLogSearchReference, + EvidenceExpected = point.EvidenceExpected, + MappingQuality = point.MappingQuality, + ReviewStatus = point.ReviewStatus, + ReviewReason = point.ReviewReason, + EventLogMatch = point.EventLogMatch, + EvidenceReference = point.EvidenceReference, + ReviewerComment = point.ReviewerComment }).ToList()) { LatestComtradeFiles = ied.LatestComtradeFiles, @@ -658,10 +670,22 @@ private static IoTestPointPlan RestorePoint(IoTestPointData data) LogicalNode = data.LogicalNode, DataObject = data.DataObject, DataAttribute = data.DataAttribute, + Cdc = data.Cdc, + SourceIecReference = data.SourceIecReference, + ReportDisplayReference = data.ReportDisplayReference, + EventLogSearchReference = data.EventLogSearchReference, + EvidenceExpected = data.EvidenceExpected, + MappingQuality = data.MappingQuality, + ReviewStatus = data.ReviewStatus, + ReviewReason = data.ReviewReason, + EventLogMatch = data.EventLogMatch, + EvidenceReference = data.EvidenceReference, + ReviewerComment = data.ReviewerComment, SourceSheet = data.SourceSheet, SourceRow = data.SourceRow, SignalKind = data.SignalKind, CaptureMode = data.CaptureMode, + WorkspaceSelected = data.WorkspaceSelected, TestEnabled = data.TestEnabled, ImportReady = data.ImportReady, BindingStatus = data.BindingStatus, @@ -747,7 +771,7 @@ private static string BuildPrintableReport(IoTestProject project) builder.Append(""); var index = 0; - foreach (var point in ied.TestPoints.Where(point => point.IsIncludedInFat)) + foreach (var point in ied.TestPoints.Where(point => point.WorkspaceSelected && point.IsIncludedInFat)) { index++; builder.Append("
#SignalIEC 61850 referenceType / captureValue 1Value 2ResultReason
").Append(index).Append("").Append(Html(point.SignalName)).Append("").Append(Html(point.ReportIecReference)).Append("") @@ -999,9 +1023,21 @@ private sealed record IoTestPointData( string BindingEvidence, IoTestRuntimeData Runtime) { + public bool WorkspaceSelected { get; init; } = true; public FatSignalKind SignalKind { get; init; } = FatSignalKind.Discrete; public FatCaptureMode CaptureMode { get; init; } = FatCaptureMode.AutomaticTransition; public FatSignalDisposition FatDisposition { get; init; } = FatSignalDisposition.Included; + public string Cdc { get; init; } = string.Empty; + public string SourceIecReference { get; init; } = string.Empty; + public string ReportDisplayReference { get; init; } = string.Empty; + public string EventLogSearchReference { get; init; } = string.Empty; + public string EvidenceExpected { get; init; } = string.Empty; + public string MappingQuality { get; init; } = string.Empty; + public string ReviewStatus { get; init; } = string.Empty; + public string ReviewReason { get; init; } = string.Empty; + public string EventLogMatch { get; init; } = string.Empty; + public string EvidenceReference { get; init; } = string.Empty; + public string ReviewerComment { get; init; } = string.Empty; } private sealed record IoTestRuntimeData( From bb68b42791c7164baa932752a0914ddef8f72b58 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:29:35 +0700 Subject: [PATCH 097/112] Align workspace bridge regression with shared selection authority --- ...rementAndWorkspaceBridgeRegressionTests.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs index f2ee41d5c..81ac9238d 100644 --- a/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs +++ b/tests/ARSAS.Tests/ThdMeasurementAndWorkspaceBridgeRegressionTests.cs @@ -110,34 +110,38 @@ public void ExistingEngineeringSelection_InitializesFat_ThenSynchronizesBothWays var point = Point(member, runtime, dataSet); var ied = Ied(point); - // Engineering is the already-decided shared selection authority. FAT must inherit - // even an intentionally unchecked signal rather than silently selecting it again. + // Engineering owns shared workspace membership, while TEST remains a FAT-only + // evidence-scope preference. An intentionally unchecked Engineering signal must + // hide from FAT without mutating the operator's TEST choice. point.TestEnabled = true; IoFatEngineeringSelectionBridge.Initialize( ied, device, preserveExistingEngineeringSelection: true); - Assert.False(point.TestEnabled); + Assert.False(point.WorkspaceSelected); + Assert.True(point.TestEnabled); Assert.False(signal.IsSelected); - // After initialization the shared bridge remains bidirectional: Engineering changes - // still update FAT, and FAT checkbox changes still update the Engineering catalog. + // Engineering reselects the shared row without touching FAT-only TEST state. signal.IsSelected = true; Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( signal, selected: true, ied, device)); + Assert.True(point.WorkspaceSelected); Assert.True(point.TestEnabled); Assert.True(point.IsIncludedInFat); - point.TestEnabled = false; + // FAT can change the shared workspace membership independently from TEST. + point.WorkspaceSelected = false; Assert.True(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); Assert.False(signal.IsSelected); + Assert.True(point.TestEnabled); } [Fact] - public void RemovedSclFatRow_EngineeringSelectionChangesCheckboxButNeverRestoresDisposition() + public void RemovedSclFatRow_EngineeringSelectionChangesWorkspaceOnly_AndNeverRestoresDisposition() { const string dataSet = "IEDTHD/LLN0.Analog"; const string member = "IEDTHD/I_MHAI1.ThdA.phsB"; @@ -150,6 +154,7 @@ public void RemovedSclFatRow_EngineeringSelectionChangesCheckboxButNeverRestores point.RemoveFromFat(); Assert.False(point.IsIncludedInFat); + Assert.True(point.WorkspaceSelected); Assert.True(point.TestEnabled); Assert.False(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); Assert.True(signal.IsSelected); @@ -161,7 +166,8 @@ public void RemovedSclFatRow_EngineeringSelectionChangesCheckboxButNeverRestores ied, device)); Assert.False(point.IsIncludedInFat); - Assert.False(point.TestEnabled); + Assert.False(point.WorkspaceSelected); + Assert.True(point.TestEnabled); signal.IsSelected = true; Assert.True(IoFatEngineeringSelectionBridge.ApplyEngineeringSignalSelection( @@ -170,10 +176,12 @@ public void RemovedSclFatRow_EngineeringSelectionChangesCheckboxButNeverRestores ied, device)); Assert.False(point.IsIncludedInFat); + Assert.True(point.WorkspaceSelected); Assert.True(point.TestEnabled); point.RestoreToFat(); Assert.True(point.IsIncludedInFat); + Assert.True(point.WorkspaceSelected); Assert.True(point.TestEnabled); Assert.False(IoFatEngineeringSelectionBridge.ApplyFatPointSelection(point, device)); Assert.True(signal.IsSelected); @@ -250,6 +258,7 @@ private static Iec61850MonitorDevice Device(params SignalDefinition[] signals) EventLogSearchReference = member, BindingStatus = IoTestSignalSelectionService.SclDataSetAuthorityBindingStatus, ImportReady = true, + WorkspaceSelected = true, TestEnabled = true, SignalKind = FatSignalKind.Analog, CaptureMode = FatCaptureMode.OperatorSnapshot From f35ffc2c9d88abd27c791bf44279b1a86e7f85cd Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:30:14 +0700 Subject: [PATCH 098/112] Align workspace mode regression with shared selection authority --- tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs index 8f9629d6d..ade9126ba 100644 --- a/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs +++ b/tests/ARSAS.Tests/WorkspaceModeSwitchTests.cs @@ -48,8 +48,8 @@ public void SclImport_UsesOneSelectionAuthorityAcrossEngineeringAndFat() Assert.Contains("ApplyManualSelectionToFatProjectAsync", fat, StringComparison.Ordinal); Assert.Contains("promptForSelection: true", fat, StringComparison.Ordinal); Assert.Contains("PromptSclSignalSelectionMode(this, import.Project.Ieds.Count)", fat, StringComparison.Ordinal); - Assert.Contains("point.IsIncludedInFat &&", fatProjection, StringComparison.Ordinal); - Assert.Contains("point.TestEnabled", fatProjection, StringComparison.Ordinal); + Assert.Contains("point.WorkspaceSelected", fatProjection, StringComparison.Ordinal); + Assert.Contains("point.IsIncludedInFat", fatProjection, StringComparison.Ordinal); } [Fact] From 41594abdbe3d71568539f8bdcaea5961698ca30c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:45:43 +0700 Subject: [PATCH 099/112] P3 add generic FAT value capture provenance and stages --- Models/IoTesting/FatVerificationModels.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Models/IoTesting/FatVerificationModels.cs b/Models/IoTesting/FatVerificationModels.cs index e4587e290..b92770240 100644 --- a/Models/IoTesting/FatVerificationModels.cs +++ b/Models/IoTesting/FatVerificationModels.cs @@ -34,7 +34,17 @@ public enum FatValueSlot public enum FatEvidenceCaptureKind { AutomaticTransition, - OperatorSnapshot + OperatorSnapshot, + AutomaticValue, + OperatorRecapture +} + +public enum FatAutoCaptureStage +{ + WaitingValue1, + WaitingChange, + StabilizingValue2, + Complete } public sealed record FatValueEvidence( From 35f250b7856b73bd6c69443ba270dff65902aec0 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:46:17 +0700 Subject: [PATCH 100/112] P3 add automatic stable Value 1 and Value 2 capture coordinator --- .../IoTesting/FatAutoCaptureCoordinator.cs | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 Services/IoTesting/FatAutoCaptureCoordinator.cs diff --git a/Services/IoTesting/FatAutoCaptureCoordinator.cs b/Services/IoTesting/FatAutoCaptureCoordinator.cs new file mode 100644 index 000000000..3bb3093de --- /dev/null +++ b/Services/IoTesting/FatAutoCaptureCoordinator.cs @@ -0,0 +1,212 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; + +namespace ArIED61850Tester.Services.IoTesting; + +public sealed record FatAutoCaptureDecision( + FatValueEvidence? Evidence, + FatAutoCaptureStage Stage, + string Message) +{ + public static FatAutoCaptureDecision None(FatAutoCaptureStage stage, string message) + => new(null, stage, message); +} + +/// +/// P3 automatic Value 1 / Value 2 latch. This coordinator never mutates the current +/// evidence pointers: callers must durably append the returned evidence first, then +/// promote it. That keeps the evidence journal authoritative when storage fails. +/// +/// Analog values use an intentionally small, deterministic settling window rather than +/// capturing every transient MMS/report update. Three consecutive samples must remain +/// inside an adaptive 0.05% band before the slot is accepted. Discrete/Other values use +/// semantic change and are latched immediately after good-quality observation. +/// +public sealed class FatAutoCaptureCoordinator +{ + internal const int AnalogStableSampleCount = 3; + internal const double AnalogRelativeSettlingFraction = 0.0005d; + private static readonly Regex NumericToken = new( + @"[-+]?(?:\d+(?:[\.,]\d*)?|[\.,]\d+)(?:[eE][-+]?\d+)?", + RegexOptions.CultureInvariant | RegexOptions.Compiled); + + private readonly Dictionary _analogCandidates = + new(StringComparer.OrdinalIgnoreCase); + + public FatAutoCaptureDecision Observe(IoTestPointPlan point, IoTestObservation observation) + { + ArgumentNullException.ThrowIfNull(point); + ArgumentNullException.ThrowIfNull(observation); + + if (!point.WorkspaceSelected || !point.IsIncludedInFat || !point.TestEnabled || !point.ImportReady) + { + Clear(point); + return FatAutoCaptureDecision.None( + FatAutoCaptureStage.WaitingValue1, + "Automatic capture is outside the active FAT scope."); + } + + if (point.HasValue1Evidence && point.HasValue2Evidence) + { + Clear(point); + return FatAutoCaptureDecision.None( + FatAutoCaptureStage.Complete, + "Current Value 1 / Value 2 evidence is complete; explicit Recapture is required to replace it."); + } + + var (qualityVerdict, qualityReason) = IoTestTransitionEvaluator.EvaluateQuality(observation.Quality); + if (qualityVerdict != IoEvidenceVerdict.Accepted) + { + Clear(point); + return FatAutoCaptureDecision.None( + point.HasValue1Evidence ? FatAutoCaptureStage.WaitingChange : FatAutoCaptureStage.WaitingValue1, + $"Waiting for good-quality live value: {qualityReason}."); + } + + if (string.IsNullOrWhiteSpace(observation.RawValue) || observation.RawValue.Trim() is "-" or "—") + { + Clear(point); + return FatAutoCaptureDecision.None( + point.HasValue1Evidence ? FatAutoCaptureStage.WaitingChange : FatAutoCaptureStage.WaitingValue1, + "Waiting for a readable live value."); + } + + var slot = point.HasValue1Evidence ? FatValueSlot.Value2 : FatValueSlot.Value1; + if (slot == FatValueSlot.Value2 && IsEquivalent(point.Value1Text, observation.RawValue)) + { + Clear(point); + return FatAutoCaptureDecision.None( + FatAutoCaptureStage.WaitingChange, + "Value 1 is stable; waiting for a meaningful new condition."); + } + + if (point.SignalKind != FatSignalKind.Analog) + { + Clear(point); + return new FatAutoCaptureDecision( + CreateEvidence(slot, observation), + slot == FatValueSlot.Value1 ? FatAutoCaptureStage.WaitingChange : FatAutoCaptureStage.Complete, + slot == FatValueSlot.Value1 + ? "Value 1 captured automatically; waiting for a meaningful change." + : "Value 2 captured automatically from the new live condition."); + } + + if (!TryParseNumeric(observation.RawValue, out var numeric)) + { + Clear(point); + return FatAutoCaptureDecision.None( + slot == FatValueSlot.Value1 ? FatAutoCaptureStage.WaitingValue1 : FatAutoCaptureStage.WaitingChange, + "Analog value is not numerically stable enough for automatic capture; manual Recapture remains available."); + } + + var key = point.TestPointId; + var tolerance = SettlingTolerance(numeric, point.Value1Text); + if (!_analogCandidates.TryGetValue(key, out var candidate) || + candidate.Slot != slot || + Math.Abs(numeric - candidate.Center) > tolerance) + { + _analogCandidates[key] = new AnalogCandidate(slot, numeric, numeric, numeric, 1); + return FatAutoCaptureDecision.None( + slot == FatValueSlot.Value1 ? FatAutoCaptureStage.WaitingValue1 : FatAutoCaptureStage.StabilizingValue2, + slot == FatValueSlot.Value1 ? "Stabilizing Value 1…" : "Stabilizing Value 2…"); + } + + var next = candidate.Add(numeric); + _analogCandidates[key] = next; + var nextTolerance = SettlingTolerance(next.Center, point.Value1Text); + if (next.Count < AnalogStableSampleCount || next.Max - next.Min > nextTolerance) + { + return FatAutoCaptureDecision.None( + slot == FatValueSlot.Value1 ? FatAutoCaptureStage.WaitingValue1 : FatAutoCaptureStage.StabilizingValue2, + slot == FatValueSlot.Value1 ? "Stabilizing Value 1…" : "Stabilizing Value 2…"); + } + + _analogCandidates.Remove(key); + return new FatAutoCaptureDecision( + CreateEvidence(slot, observation), + slot == FatValueSlot.Value1 ? FatAutoCaptureStage.WaitingChange : FatAutoCaptureStage.Complete, + slot == FatValueSlot.Value1 + ? "Stable analog Value 1 captured; waiting for a meaningful new condition." + : "Stable analog Value 2 captured; current evidence is complete."); + } + + public void Clear(IoTestPointPlan point) + { + ArgumentNullException.ThrowIfNull(point); + _analogCandidates.Remove(point.TestPointId); + } + + public void Clear() => _analogCandidates.Clear(); + + private static FatValueEvidence CreateEvidence(FatValueSlot slot, IoTestObservation observation) + => new( + Guid.NewGuid(), + slot, + FatEvidenceCaptureKind.AutomaticValue, + observation.RawValue.Trim(), + observation.CapturedAt, + observation.IedTimestamp, + string.IsNullOrWhiteSpace(observation.Quality) ? "Unknown" : observation.Quality.Trim(), + string.IsNullOrWhiteSpace(observation.AcquisitionSource) ? "Unknown" : observation.AcquisitionSource.Trim(), + observation.Sequence, + observation.ConnectionGeneration); + + private static bool IsEquivalent(string? baseline, string? current) + { + if (Iec61850MonitorPoint.AreSemanticallyEquivalent(baseline ?? string.Empty, current ?? string.Empty)) + return true; + + return TryParseNumeric(baseline, out var left) && + TryParseNumeric(current, out var right) && + Math.Abs(left - right) <= SettlingTolerance(right, baseline); + } + + private static double SettlingTolerance(double value, string? baseline) + { + var baselineMagnitude = TryParseNumeric(baseline, out var parsedBaseline) + ? Math.Abs(parsedBaseline) + : 0d; + var scale = Math.Max(1d, Math.Max(Math.Abs(value), baselineMagnitude)); + return Math.Max(1e-9d, scale * AnalogRelativeSettlingFraction); + } + + internal static bool TryParseNumeric(string? raw, out double value) + { + value = 0d; + if (string.IsNullOrWhiteSpace(raw)) + return false; + + var match = NumericToken.Match(raw.Trim()); + if (!match.Success) + return false; + + var token = match.Value; + if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) + return true; + + token = token.Replace(',', '.'); + return double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out value); + } + + private sealed record AnalogCandidate( + FatValueSlot Slot, + double Center, + double Min, + double Max, + int Count) + { + public AnalogCandidate Add(double value) + { + var nextCount = Count + 1; + return this with + { + Center = ((Center * Count) + value) / nextCount, + Min = Math.Min(Min, value), + Max = Math.Max(Max, value), + Count = nextCount + }; + } + } +} From a1d48a3d769facc05edbd2df390902038aa9f103 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:48:41 +0700 Subject: [PATCH 101/112] P3 make Value 1 and Value 2 effective evidence generic --- Models/IoTesting/IoTestModels.cs | 83 +++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/Models/IoTesting/IoTestModels.cs b/Models/IoTesting/IoTestModels.cs index 7664127df..4f4b93778 100644 --- a/Models/IoTesting/IoTestModels.cs +++ b/Models/IoTesting/IoTestModels.cs @@ -77,6 +77,7 @@ public sealed class IoTestPointRuntime : ObservableObject private IoTestTransitionEvidence? _offEvidence; private FatValueEvidence? _value1Evidence; private FatValueEvidence? _value2Evidence; + private FatAutoCaptureStage _autoCaptureStage = FatAutoCaptureStage.WaitingValue1; private string _statusReason = "Not started"; private int _attempt; private string _currentValue = "-"; @@ -148,6 +149,13 @@ internal set } } + [JsonIgnore] + public FatAutoCaptureStage AutoCaptureStage + { + get => _autoCaptureStage; + internal set => Set(ref _autoCaptureStage, value); + } + public string StatusReason { get => _statusReason; internal set => Set(ref _statusReason, value ?? string.Empty); } public int Attempt { get => _attempt; internal set => Set(ref _attempt, value); } public string CurrentValue { get => _currentValue; internal set => Set(ref _currentValue, string.IsNullOrWhiteSpace(value) ? "-" : value); } @@ -371,39 +379,55 @@ private set IsOperatorSnapshot && WorkspaceSelected && IsIncludedInFat && TestEnabled && ImportReady && IsLiveBound; [JsonIgnore] - public bool IsFatEvidenceComplete => CaptureMode == FatCaptureMode.AutomaticTransition - ? Runtime.OnEvidence is not null && Runtime.OffEvidence is not null - : Runtime.Value1Evidence is not null && Runtime.Value2Evidence is not null; + public bool HasValue1Evidence => + Runtime.Value1Evidence is not null || + (CaptureMode == FatCaptureMode.AutomaticTransition && Runtime.OnEvidence is not null); [JsonIgnore] - public string Value1Text => CaptureMode == FatCaptureMode.AutomaticTransition - ? Runtime.OnEvidence?.RawValue ?? "—" - : Runtime.Value1Evidence?.RawValue ?? "—"; + public bool HasValue2Evidence => + Runtime.Value2Evidence is not null || + (CaptureMode == FatCaptureMode.AutomaticTransition && Runtime.OffEvidence is not null); [JsonIgnore] - public string Value2Text => CaptureMode == FatCaptureMode.AutomaticTransition - ? Runtime.OffEvidence?.RawValue ?? "—" - : Runtime.Value2Evidence?.RawValue ?? "—"; + public bool IsFatEvidenceComplete => HasValue1Evidence && HasValue2Evidence; [JsonIgnore] - public string Value1RelayTimestampText => CaptureMode == FatCaptureMode.AutomaticTransition - ? Runtime.OnRelayTimestampText - : Runtime.Value1RelayTimestampText; + public string Value1Text => Runtime.Value1Evidence?.RawValue ?? + (CaptureMode == FatCaptureMode.AutomaticTransition ? Runtime.OnEvidence?.RawValue : null) ?? + "—"; [JsonIgnore] - public string Value2RelayTimestampText => CaptureMode == FatCaptureMode.AutomaticTransition - ? Runtime.OffRelayTimestampText - : Runtime.Value2RelayTimestampText; + public string Value2Text => Runtime.Value2Evidence?.RawValue ?? + (CaptureMode == FatCaptureMode.AutomaticTransition ? Runtime.OffEvidence?.RawValue : null) ?? + "—"; [JsonIgnore] - public string Value1EvidenceToolTip => CaptureMode == FatCaptureMode.AutomaticTransition - ? Runtime.OnEvidenceToolTip - : Runtime.Value1EvidenceToolTip; + public string Value1RelayTimestampText => Runtime.Value1Evidence is not null + ? Runtime.Value1RelayTimestampText + : CaptureMode == FatCaptureMode.AutomaticTransition + ? Runtime.OnRelayTimestampText + : Runtime.Value1RelayTimestampText; [JsonIgnore] - public string Value2EvidenceToolTip => CaptureMode == FatCaptureMode.AutomaticTransition - ? Runtime.OffEvidenceToolTip - : Runtime.Value2EvidenceToolTip; + public string Value2RelayTimestampText => Runtime.Value2Evidence is not null + ? Runtime.Value2RelayTimestampText + : CaptureMode == FatCaptureMode.AutomaticTransition + ? Runtime.OffRelayTimestampText + : Runtime.Value2RelayTimestampText; + + [JsonIgnore] + public string Value1EvidenceToolTip => Runtime.Value1Evidence is not null + ? Runtime.Value1EvidenceToolTip + : CaptureMode == FatCaptureMode.AutomaticTransition + ? Runtime.OnEvidenceToolTip + : Runtime.Value1EvidenceToolTip; + + [JsonIgnore] + public string Value2EvidenceToolTip => Runtime.Value2Evidence is not null + ? Runtime.Value2EvidenceToolTip + : CaptureMode == FatCaptureMode.AutomaticTransition + ? Runtime.OffEvidenceToolTip + : Runtime.Value2EvidenceToolTip; [JsonIgnore] public string FatStatusText @@ -412,13 +436,15 @@ public string FatStatusText { if (!IsIncludedInFat) return "REMOVED"; - if (CaptureMode == FatCaptureMode.AutomaticTransition) - return Runtime.StateText; if (IsFatEvidenceComplete) return "COMPLETE"; - if (Runtime.Value1Evidence is not null || Runtime.Value2Evidence is not null) - return "1 / 2 captured"; - return "Ready to capture"; + if (Runtime.AutoCaptureStage == FatAutoCaptureStage.StabilizingValue2) + return "STABILIZING…"; + if (HasValue1Evidence) + return "WAITING V2"; + return Runtime.AutoCaptureStage == FatAutoCaptureStage.WaitingValue1 + ? "WAITING V1" + : "READY"; } } @@ -501,6 +527,7 @@ private void Runtime_PropertyChanged(object? sender, PropertyChangedEventArgs e) nameof(IoTestPointRuntime.OffEvidence) or nameof(IoTestPointRuntime.Value1Evidence) or nameof(IoTestPointRuntime.Value2Evidence) or + nameof(IoTestPointRuntime.AutoCaptureStage) or nameof(IoTestPointRuntime.State) or nameof(IoTestPointRuntime.StatusReason)) { @@ -512,6 +539,8 @@ private void RaiseFatComputedProperties() { Raise(nameof(IsIncludedInFat)); Raise(nameof(CanCaptureOperatorSnapshot)); + Raise(nameof(HasValue1Evidence)); + Raise(nameof(HasValue2Evidence)); Raise(nameof(IsFatEvidenceComplete)); Raise(nameof(Value1Text)); Raise(nameof(Value2Text)); @@ -765,4 +794,4 @@ public void InitializeRuntimeNotifications() foreach (var ied in Ieds) ied.InitializeRuntimeNotifications(); } -} \ No newline at end of file +} From 514ce75217566046a0c6b1a40e8074bb10ff78c3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:50:25 +0700 Subject: [PATCH 102/112] P3 distinguish operator recapture provenance --- .../IoTesting/FatOperatorSnapshotCaptureService.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Services/IoTesting/FatOperatorSnapshotCaptureService.cs b/Services/IoTesting/FatOperatorSnapshotCaptureService.cs index a340e79da..6e4ccbdb2 100644 --- a/Services/IoTesting/FatOperatorSnapshotCaptureService.cs +++ b/Services/IoTesting/FatOperatorSnapshotCaptureService.cs @@ -37,11 +37,13 @@ public static FatValueEvidence Capture( /// /// Creates an immutable capture record without mutating a current evidence pointer. /// Session controllers use this so the append-only journal can be durably written - /// before the replaceable Value 1 / Value 2 pointer is promoted. + /// before the replaceable Value 1 / Value 2 pointer is promoted. Explicit bulk + /// Recapture uses OperatorRecapture provenance even for automatic-transition rows. /// public static FatValueEvidence CreateEvidence( FatValueSlot slot, - FatLiveValueObservation observation) + FatLiveValueObservation observation, + FatEvidenceCaptureKind captureKind = FatEvidenceCaptureKind.OperatorSnapshot) { ArgumentNullException.ThrowIfNull(observation); if (string.IsNullOrWhiteSpace(observation.RawValue) || @@ -50,10 +52,13 @@ public static FatValueEvidence CreateEvidence( throw new InvalidOperationException("The FAT signal does not have a readable live value to capture."); } + if (captureKind is not (FatEvidenceCaptureKind.OperatorSnapshot or FatEvidenceCaptureKind.OperatorRecapture)) + throw new ArgumentOutOfRangeException(nameof(captureKind), captureKind, "Operator capture requires operator provenance."); + return new FatValueEvidence( Guid.NewGuid(), slot, - FatEvidenceCaptureKind.OperatorSnapshot, + captureKind, observation.RawValue.Trim(), observation.CapturedAt, observation.IedTimestamp, From 4713074ce7e998f5b7fbd7bd47a03f8f92ee9c15 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:53:06 +0700 Subject: [PATCH 103/112] P3 add auto capture and transactional bulk recapture engine --- Services/IoTesting/IoTestSessionController.cs | 447 ++++++++++++++---- 1 file changed, 347 insertions(+), 100 deletions(-) diff --git a/Services/IoTesting/IoTestSessionController.cs b/Services/IoTesting/IoTestSessionController.cs index 26fcf82bf..a3e47d35d 100644 --- a/Services/IoTesting/IoTestSessionController.cs +++ b/Services/IoTesting/IoTestSessionController.cs @@ -22,6 +22,7 @@ public sealed class IoTestSessionController : ObservableObject, IDisposable private readonly Func _journalFactory; private readonly IoTestTransitionEvaluator _evaluator; private readonly IoTestRollingCaptureCoordinator _captureCoordinator; + private readonly FatAutoCaptureCoordinator _autoCaptureCoordinator = new(); // Only the newest observation per live point is needed by the UI transition // evaluator. Coalescing prevents a fast report/poll stream from building an // unbounded dispatcher backlog while the operator scrolls the FAT grid. @@ -32,6 +33,10 @@ public sealed class IoTestSessionController : ObservableObject, IDisposable private readonly Dictionary> _activePointIndex = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _activeLivePoints = new(StringComparer.OrdinalIgnoreCase); private readonly HashSet _sessionPointIds = new(StringComparer.OrdinalIgnoreCase); + // Value 1 & Value 2 bulk Recapture is deliberately two-stage. The staged Value 1 + // remains audit history only until Value 2 is captured successfully; current pointers + // therefore keep the old complete pair authoritative during the condition change. + private readonly Dictionary _pendingPairValue1 = new(StringComparer.OrdinalIgnoreCase); private IoTestSessionState _state = IoTestSessionState.Idle; private IoTestIedPlan? _activeIed; @@ -134,20 +139,20 @@ public IoTestSessionActionResult Start( if (captureScope == null) { expectedPoints = ied.TestPoints - .Where(point => point.IsIncludedInFat && point.TestEnabled && point.ImportReady) + .Where(point => point.WorkspaceSelected && point.IsIncludedInFat && point.TestEnabled && point.ImportReady) .ToList(); } else { var knownPoints = new HashSet(ied.TestPoints); var invalidScope = captureScope - .Where(point => !knownPoints.Contains(point) || !point.IsIncludedInFat || !point.TestEnabled || !point.ImportReady) + .Where(point => !knownPoints.Contains(point) || !point.WorkspaceSelected || !point.IsIncludedInFat || !point.TestEnabled || !point.ImportReady) .Distinct() .ToList(); if (invalidScope.Count > 0) { return IoTestSessionActionResult.Failure( - "The requested FAT capture scope contains a row that is not part of this IED, was removed by the operator, is not selected, or is not import-ready."); + "The requested FAT capture scope contains a row that is not part of this IED/shared workspace, was removed by the operator, has TEST disabled, or is not import-ready."); } expectedPoints = captureScope.Distinct().ToList(); @@ -166,6 +171,8 @@ public IoTestSessionActionResult Start( CleanupSessionResources(disposeJournal: true); _captureCoordinator.Clear(); + _autoCaptureCoordinator.Clear(); + _pendingPairValue1.Clear(); SessionId = Guid.NewGuid(); StartedAtUtc = DateTimeOffset.UtcNow; CompletedAtUtc = null; @@ -196,29 +203,41 @@ public IoTestSessionActionResult Start( LastJournalHash = string.Empty; JournalIntegrityText = "Evidence journal open · hash chain initialized"; - var startupEntries = new List(bindings.Count + 1) + var startupEntries = new List((bindings.Count * 2) + 1) { SessionEvent("session_started", $"FAT session started with an explicit capture scope of {bindings.Count} operator-selected point(s).") }; + var autoDecisions = new List<(IoTestPointPlan Point, FatAutoCaptureDecision Decision)>(); foreach (var binding in bindings) { var observation = CreateObservation(binding.Point, binding.LivePoint, _connectionGeneration, DateTimeOffset.UtcNow); if (binding.Point.CaptureMode == FatCaptureMode.OperatorSnapshot) { - binding.Point.Runtime.ApplyObservation(observation with { NormalizedState = null }); + var genericObservation = observation with { NormalizedState = null }; + binding.Point.Runtime.ApplyObservation(genericObservation); startupEntries.Add(PointEvent( "operator_snapshot_baseline", binding.Point, - observation with { NormalizedState = null }, + genericObservation, null, - "Live value established for operator Value 1 / Value 2 snapshot capture.")); + "Live value established for automatic/manual Value 1 / Value 2 capture.")); + var decision = _autoCaptureCoordinator.Observe(binding.Point, genericObservation); + autoDecisions.Add((binding.Point, decision)); + if (decision.Evidence != null) + startupEntries.Add(FatValueEvent(binding.Point, decision.Evidence)); continue; } var evaluation = _captureCoordinator.Start(binding.Point, observation); startupEntries.Add(PointEvent("baseline", binding.Point, observation, evaluation.Evidence, evaluation.Reason)); + var automaticDecision = _autoCaptureCoordinator.Observe(binding.Point, observation); + autoDecisions.Add((binding.Point, automaticDecision)); + if (automaticDecision.Evidence != null) + startupEntries.Add(FatValueEvent(binding.Point, automaticDecision.Evidence)); } AppendBatchRequired(startupEntries); + foreach (var (point, decision) in autoDecisions) + PromoteAutoCaptureDecision(point, decision); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException) { @@ -230,7 +249,7 @@ public IoTestSessionActionResult Start( } State = IoTestSessionState.Running; - StatusText = $"FAT capture running for {ied.IedName}. Automatic rows remain armed; operator-snapshot rows expose live values for Value 1 / Value 2 capture until Stop."; + StatusText = $"FAT capture running for {ied.IedName}. Value 1 / Value 2 auto-capture is armed; manual Capture and multi-row Recapture remain operator overrides until Stop."; RaiseProgress(); UpdateRunningCompletionStatus(); return IoTestSessionActionResult.Success(StatusText); @@ -276,8 +295,10 @@ public IoTestSessionActionResult Resume() } _connectionGeneration++; + _pendingPairValue1.Clear(); + _autoCaptureCoordinator.Clear(); ClearPendingSnapshots(); - if (!AppendOrFault(SessionEvent("session_resumed", "Session resumed after rebinding current live points; current values are treated as a new baseline image."))) + if (!AppendOrFault(SessionEvent("session_resumed", "Session resumed after rebinding current live points; current values are treated as a new baseline image. Any staged pair Recapture was cancelled."))) return IoTestSessionActionResult.Failure(StatusText); foreach (var binding in refreshedBindings) @@ -294,16 +315,20 @@ public IoTestSessionActionResult Resume() null, "Live value refreshed after reconnect; existing Value 1 / Value 2 evidence is preserved."))) return IoTestSessionActionResult.Failure(StatusText); + if (!ApplyAutoCapture(binding.Point, genericObservation)) + return IoTestSessionActionResult.Failure(StatusText); continue; } var evaluation = _captureCoordinator.Observe(binding.Point, observation); if (!AppendOrFault(PointEvent("resume_baseline", binding.Point, observation, evaluation.Evidence, evaluation.Reason))) return IoTestSessionActionResult.Failure(StatusText); + if (!ApplyAutoCapture(binding.Point, observation)) + return IoTestSessionActionResult.Failure(StatusText); } State = IoTestSessionState.Running; - StatusText = $"FAT capture resumed for {ActiveIed.IedName}; current evidence is preserved until newer evidence is explicitly completed."; + StatusText = $"FAT capture resumed for {ActiveIed.IedName}; current evidence is preserved and auto-capture continues only for incomplete slots."; RaiseProgress(); UpdateRunningCompletionStatus(); return IoTestSessionActionResult.Success(StatusText); @@ -356,23 +381,16 @@ public IoTestSessionActionResult CaptureOperatorSnapshot(IoTestPointPlan? point, return IoTestSessionActionResult.Failure("The selected FAT row is not part of the active IED session."); if (!_sessionPointIds.Contains(point.TestPointId)) return IoTestSessionActionResult.Failure("The selected FAT row is not part of the active capture scope."); - if (!point.IsIncludedInFat || !point.TestEnabled || !point.ImportReady) - return IoTestSessionActionResult.Failure("The FAT row must remain included, operator-selected, and import-ready to capture evidence."); + if (!point.WorkspaceSelected || !point.IsIncludedInFat || !point.TestEnabled || !point.ImportReady) + return IoTestSessionActionResult.Failure("The FAT row must remain in the shared workspace, included, TEST-enabled, and import-ready to capture evidence."); if (point.CaptureMode != FatCaptureMode.OperatorSnapshot) - return IoTestSessionActionResult.Failure("This FAT row uses automatic transition capture; Value 1 / Value 2 are captured by live transitions."); + return IoTestSessionActionResult.Failure("Use right-click Recapture for automatic-transition rows."); if (!_activeLivePoints.TryGetValue(point.TestPointId, out var livePoint)) return IoTestSessionActionResult.Failure("The FAT row does not have one active live point to snapshot."); try { - var observation = new FatLiveValueObservation( - livePoint.Value, - DateTimeOffset.UtcNow, - IoTestValueNormalizer.ParseIedTimestamp(livePoint.DeviceTimestamp), - livePoint.Quality, - livePoint.SourceMode, - livePoint.Sequence, - _connectionGeneration); + var observation = CreateLiveValueObservation(livePoint); var evidence = FatOperatorSnapshotCaptureService.CreateEvidence(slot, observation); // Audit history is authoritative and append-only. Only after the journal write @@ -381,6 +399,10 @@ public IoTestSessionActionResult CaptureOperatorSnapshot(IoTestPointPlan? point, return IoTestSessionActionResult.Failure(StatusText); point.Runtime.SetFatValueEvidence(evidence); + point.Runtime.AutoCaptureStage = point.IsFatEvidenceComplete + ? FatAutoCaptureStage.Complete + : point.HasValue1Evidence ? FatAutoCaptureStage.WaitingChange : FatAutoCaptureStage.WaitingValue1; + _autoCaptureCoordinator.Clear(point); point.Runtime.StatusReason = point.IsFatEvidenceComplete ? "Value 1 and Value 2 captured. Current evidence is complete; either value may be recaptured while the session remains running." : $"{(slot == FatValueSlot.Value1 ? "Value 1" : "Value 2")} captured; capture the remaining value to complete current evidence."; @@ -395,6 +417,140 @@ public IoTestSessionActionResult CaptureOperatorSnapshot(IoTestPointPlan? point, } } + public IoTestSessionActionResult RecaptureValues( + IReadOnlyCollection points, + FatValueSlot slot) + { + ThrowIfDisposed(); + if (!TryPrepareRecapture(points, out var bindings, out var failure)) + return IoTestSessionActionResult.Failure(failure); + + if (slot == FatValueSlot.Value1 && bindings.Any(binding => _pendingPairValue1.ContainsKey(binding.Point.TestPointId))) + { + return IoTestSessionActionResult.Failure( + "A Value 1 & Value 2 pair Recapture is already staged for one or more selected rows. Complete it with Recapture Value 2 or cancel the staged pair first."); + } + + var captures = new List<(IoTestPointPlan Point, FatValueEvidence Evidence, FatValueEvidence? StagedValue1)>(); + try + { + foreach (var binding in bindings) + { + var evidence = FatOperatorSnapshotCaptureService.CreateEvidence( + slot, + CreateLiveValueObservation(binding.LivePoint), + FatEvidenceCaptureKind.OperatorRecapture); + _pendingPairValue1.TryGetValue(binding.Point.TestPointId, out var stagedValue1); + if (slot == FatValueSlot.Value2 && stagedValue1 != null && stagedValue1.ConnectionGeneration != _connectionGeneration) + { + return IoTestSessionActionResult.Failure( + "A staged Value 1 belongs to an earlier IED connection generation. Cancel it and start Value 1 & Value 2 Recapture again."); + } + captures.Add((binding.Point, evidence, slot == FatValueSlot.Value2 ? stagedValue1 : null)); + } + + AppendBatchRequired(captures.Select(item => FatValueEvent( + item.Point, + item.Evidence, + item.StagedValue1 != null ? "fat_value_recapture_pair_commit" : "fat_value_recapture")).ToList()); + } + catch (Exception ex) when (ex is InvalidOperationException or IOException or UnauthorizedAccessException) + { + return IoTestSessionActionResult.Failure(ex.Message); + } + + foreach (var capture in captures) + { + if (capture.StagedValue1 != null) + { + capture.Point.Runtime.SetFatValueEvidence(capture.StagedValue1); + _pendingPairValue1.Remove(capture.Point.TestPointId); + } + capture.Point.Runtime.SetFatValueEvidence(capture.Evidence); + capture.Point.Runtime.AutoCaptureStage = capture.Point.IsFatEvidenceComplete + ? FatAutoCaptureStage.Complete + : capture.Point.HasValue1Evidence ? FatAutoCaptureStage.WaitingChange : FatAutoCaptureStage.WaitingValue1; + capture.Point.Runtime.StatusReason = capture.StagedValue1 != null + ? "Operator pair Recapture completed; staged Value 1 and current Value 2 are now the authoritative pair." + : $"Operator recaptured {slot}; the other current slot was preserved."; + _autoCaptureCoordinator.Clear(capture.Point); + } + + StatusText = captures.Any(item => item.StagedValue1 != null) + ? $"{captures.Count} signal(s) · Value 1 & Value 2 recaptured · {ActiveIed!.IedName}" + : $"{captures.Count} signal(s) · {slot} recaptured · {ActiveIed!.IedName}"; + RaiseProgress(); + UpdateRunningCompletionStatus(); + return IoTestSessionActionResult.Success(StatusText); + } + + public IoTestSessionActionResult BeginPairRecapture(IReadOnlyCollection points) + { + ThrowIfDisposed(); + if (!TryPrepareRecapture(points, out var bindings, out var failure)) + return IoTestSessionActionResult.Failure(failure); + if (bindings.Any(binding => _pendingPairValue1.ContainsKey(binding.Point.TestPointId))) + return IoTestSessionActionResult.Failure("A Value 1 & Value 2 pair Recapture is already staged for one or more selected rows."); + + var staged = new List<(IoTestPointPlan Point, FatValueEvidence Evidence)>(); + try + { + foreach (var binding in bindings) + { + staged.Add(( + binding.Point, + FatOperatorSnapshotCaptureService.CreateEvidence( + FatValueSlot.Value1, + CreateLiveValueObservation(binding.LivePoint), + FatEvidenceCaptureKind.OperatorRecapture))); + } + + AppendBatchRequired(staged.Select(item => FatValueEvent( + item.Point, + item.Evidence, + "fat_value_recapture_pair_staged", + "Operator staged a new Value 1 for transactional Value 1 & Value 2 Recapture; current pointers remain unchanged until Value 2 is captured.")).ToList()); + } + catch (Exception ex) when (ex is InvalidOperationException or IOException or UnauthorizedAccessException) + { + return IoTestSessionActionResult.Failure(ex.Message); + } + + foreach (var item in staged) + { + _pendingPairValue1[item.Point.TestPointId] = item.Evidence; + item.Point.Runtime.StatusReason = "New Value 1 staged for pair Recapture; change the test condition, then Recapture Value 2. Existing current pair remains authoritative."; + } + + StatusText = $"{staged.Count} signal(s) · Value 1 staged · change condition, then Recapture Value 2 · {ActiveIed!.IedName}"; + RaiseProgress(); + return IoTestSessionActionResult.Success(StatusText); + } + + public IoTestSessionActionResult CancelPairRecapture(IReadOnlyCollection points) + { + ThrowIfDisposed(); + var requested = points?.Where(point => point != null).Distinct().ToList() ?? new List(); + var staged = requested.Where(point => _pendingPairValue1.ContainsKey(point.TestPointId)).ToList(); + if (staged.Count == 0) + return IoTestSessionActionResult.Failure("No staged Value 1 & Value 2 Recapture exists for the selected rows."); + if (State != IoTestSessionState.Running || ActiveIed == null) + return IoTestSessionActionResult.Failure("The owning FAT session is not running."); + + if (!AppendOrFault(SessionEvent( + "fat_value_recapture_pair_cancelled", + $"Operator cancelled staged pair Recapture for {staged.Count} row(s): {string.Join(", ", staged.Select(point => point.TestPointId))}."))) + return IoTestSessionActionResult.Failure(StatusText); + + foreach (var point in staged) + { + _pendingPairValue1.Remove(point.TestPointId); + point.Runtime.StatusReason = "Staged pair Recapture cancelled; previous current evidence remains unchanged."; + } + StatusText = $"{staged.Count} signal(s) · staged pair Recapture cancelled · {ActiveIed.IedName}"; + return IoTestSessionActionResult.Success(StatusText); + } + public void Enqueue(Iec61850EventEntry entry) { if (entry == null || _disposed || State != IoTestSessionState.Running) @@ -510,21 +666,9 @@ private void ProcessSnapshot(QueuedSnapshot snapshot) var progressChanged = false; foreach (var point in points.Distinct()) { - if (point.CaptureMode == FatCaptureMode.OperatorSnapshot) - { - point.Runtime.ApplyObservation(new IoTestObservation( - null, - snapshot.Value, - snapshot.CapturedAtUtc, - IoTestValueNormalizer.ParseIedTimestamp(snapshot.DeviceTimestamp), - snapshot.Quality, - snapshot.SourceMode, - snapshot.Sequence, - _connectionGeneration)); - continue; - } - - var normalized = IoTestValueNormalizer.Normalize(point, snapshot.Value); + var normalized = point.CaptureMode == FatCaptureMode.AutomaticTransition + ? IoTestValueNormalizer.Normalize(point, snapshot.Value) + : null; var observation = new IoTestObservation( normalized, snapshot.Value, @@ -534,43 +678,57 @@ private void ProcessSnapshot(QueuedSnapshot snapshot) snapshot.SourceMode, snapshot.Sequence, _connectionGeneration); - var evaluation = _captureCoordinator.Observe(point, observation); - progressChanged |= evaluation.StateChanged || evaluation.Evidence != null; - if (evaluation.Evidence != null) + if (point.CaptureMode == FatCaptureMode.OperatorSnapshot) { - var eventType = evaluation.Evidence.Verdict == IoEvidenceVerdict.Rejected - ? "transition_rejected" - : evaluation.Evidence.Transition == IoEvidenceTransition.On - ? "on_evidence" - : "off_evidence"; - if (!AppendOrFault(PointEvent(eventType, point, observation, evaluation.Evidence, evaluation.Reason, snapshot.Reason, snapshot.PreviousValue))) - return; + point.Runtime.ApplyObservation(observation); } - else if (evaluation.StateChanged) + else { - if (!AppendOrFault(PointEvent( - "baseline_state", - point, - observation, - null, - evaluation.Reason, - snapshot.Reason, - snapshot.PreviousValue))) - return; - } - else if (normalized == null) - { - if (!AppendOrFault(PointEvent( - "unresolved_edge", - point, - observation, - null, - "A live edge was observed but its digital state could not be normalized.", - snapshot.Reason, - snapshot.PreviousValue))) - return; + var evaluation = _captureCoordinator.Observe(point, observation); + progressChanged |= evaluation.StateChanged || evaluation.Evidence != null; + + if (evaluation.Evidence != null) + { + var eventType = evaluation.Evidence.Verdict == IoEvidenceVerdict.Rejected + ? "transition_rejected" + : evaluation.Evidence.Transition == IoEvidenceTransition.On + ? "on_evidence" + : "off_evidence"; + if (!AppendOrFault(PointEvent(eventType, point, observation, evaluation.Evidence, evaluation.Reason, snapshot.Reason, snapshot.PreviousValue))) + return; + } + else if (evaluation.StateChanged) + { + if (!AppendOrFault(PointEvent( + "baseline_state", + point, + observation, + null, + evaluation.Reason, + snapshot.Reason, + snapshot.PreviousValue))) + return; + } + else if (normalized == null) + { + if (!AppendOrFault(PointEvent( + "unresolved_edge", + point, + observation, + null, + "A live edge was observed but its digital state could not be normalized.", + snapshot.Reason, + snapshot.PreviousValue))) + return; + } } + + var previousStage = point.Runtime.AutoCaptureStage; + var previousComplete = point.IsFatEvidenceComplete; + if (!ApplyAutoCapture(point, observation)) + return; + progressChanged |= previousStage != point.Runtime.AutoCaptureStage || previousComplete != point.IsFatEvidenceComplete; } if (progressChanged) @@ -580,6 +738,24 @@ private void ProcessSnapshot(QueuedSnapshot snapshot) } } + private bool ApplyAutoCapture(IoTestPointPlan point, IoTestObservation observation) + { + var decision = _autoCaptureCoordinator.Observe(point, observation); + if (decision.Evidence != null && !AppendOrFault(FatValueEvent(point, decision.Evidence))) + return false; + PromoteAutoCaptureDecision(point, decision); + return true; + } + + private static void PromoteAutoCaptureDecision(IoTestPointPlan point, FatAutoCaptureDecision decision) + { + point.Runtime.AutoCaptureStage = decision.Stage; + if (decision.Evidence != null) + point.Runtime.SetFatValueEvidence(decision.Evidence); + if (point.CaptureMode == FatCaptureMode.OperatorSnapshot || decision.Evidence != null) + point.Runtime.StatusReason = decision.Message; + } + private void UpdateRunningCompletionStatus() { if (State != IoTestSessionState.Running || ActiveIed == null || _sessionPointIds.Count == 0) @@ -598,8 +774,8 @@ private void UpdateRunningCompletionStatus() var failed = automatic.Count(point => point.Runtime.State == IoTestPointState.Failed); var snapshots = scoped.Count - automatic.Count; StatusText = - $"{ActiveIed.IedName} current evidence complete: {passed} PASS, {review} review, {failed} fail, {snapshots} operator-snapshot row(s) complete. " + - "Capture remains running; current evidence may be replaced only by newer complete/operator-confirmed captures until Stop."; + $"{ActiveIed.IedName} current evidence complete: {passed} PASS, {review} review, {failed} fail, {snapshots} snapshot row(s) complete. " + + "Auto-capture is locked at the current pair; explicit Recapture is required to replace Value 1 or Value 2 until Stop."; } private void ActiveDevice_PropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -618,18 +794,64 @@ private void InterruptForConnectionLoss(string capturedStatus) if (State != IoTestSessionState.Running || ActiveIed == null) return; + var stagedCount = _pendingPairValue1.Count; + _pendingPairValue1.Clear(); + _autoCaptureCoordinator.Clear(); State = IoTestSessionState.Interrupted; ClearPendingSnapshots(); - StatusText = $"{ActiveIed.IedName} monitoring was interrupted ({capturedStatus}). Reconnect the IED, then Resume to establish a new baseline."; + StatusText = $"{ActiveIed.IedName} monitoring was interrupted ({capturedStatus}). Reconnect the IED, then Resume to establish a new baseline." + + (stagedCount == 0 ? string.Empty : $" {stagedCount} staged pair Recapture item(s) were cancelled; current pointers were unchanged."); AppendOrFault(SessionEvent("session_interrupted", StatusText)); } + private bool TryPrepareRecapture( + IReadOnlyCollection? points, + out List bindings, + out string failure) + { + bindings = new List(); + failure = string.Empty; + if (State != IoTestSessionState.Running || ActiveIed == null) + { + failure = "Start this IED FAT session before using Recapture."; + return false; + } + + var requested = points?.Where(point => point != null).Distinct().ToList() ?? new List(); + if (requested.Count == 0) + { + failure = "Select one or more FAT rows before using Recapture."; + return false; + } + + foreach (var point in requested) + { + if (!ActiveIed.TestPoints.Contains(point) || !_sessionPointIds.Contains(point.TestPointId)) + { + failure = $"{point.SignalName} is not part of the active IED capture scope. No evidence changed."; + return false; + } + if (!point.WorkspaceSelected || !point.IsIncludedInFat || !point.TestEnabled || !point.ImportReady) + { + failure = $"{point.SignalName} is not eligible for Recapture because it left the shared/included/TEST/import-ready FAT scope. No evidence changed."; + return false; + } + if (!_activeLivePoints.TryGetValue(point.TestPointId, out var livePoint)) + { + failure = $"{point.SignalName} does not have one active live point. No evidence changed."; + return false; + } + bindings.Add(new SessionBinding(point, livePoint)); + } + return true; + } + private static List BuildSessionBindings( IReadOnlyCollection captureScope, Iec61850MonitorDevice device) { var result = new List(); - foreach (var point in captureScope.Where(point => point.IsIncludedInFat && point.IsLiveBound)) + foreach (var point in captureScope.Where(point => point.WorkspaceSelected && point.IsIncludedInFat && point.IsLiveBound)) { var expected = IoTestLiveBindingService.NormalizeReference( string.IsNullOrWhiteSpace(point.LiveSignalReference) ? point.ObjectReference : point.LiveSignalReference); @@ -671,6 +893,16 @@ private static IoTestObservation CreateObservation( livePoint.Sequence, connectionGeneration); + private FatLiveValueObservation CreateLiveValueObservation(Iec61850MonitorPoint livePoint) + => new( + livePoint.Value, + DateTimeOffset.UtcNow, + IoTestValueNormalizer.ParseIedTimestamp(livePoint.DeviceTimestamp), + livePoint.Quality, + livePoint.SourceMode, + livePoint.Sequence, + _connectionGeneration); + private void AddPointIndex(string deviceId, string reference, IoTestPointPlan point) { var key = PointKey(deviceId, reference); @@ -743,34 +975,47 @@ private IoTestJournalEntry PointEvent( Reason = evidence?.VerdictReason ?? reason }; - private IoTestJournalEntry FatValueEvent(IoTestPointPlan point, FatValueEvidence evidence) => new() + private IoTestJournalEntry FatValueEvent( + IoTestPointPlan point, + FatValueEvidence evidence, + string? eventType = null, + string? reason = null) { - EventType = "fat_value_snapshot", - RecordedAtUtc = evidence.CapturedAt, - ProjectId = _project.ProjectId, - SessionId = SessionId, - IedName = point.IedName, - IpAddress = point.IpAddress, - SourceWorkbookName = _project.SourceWorkbookName, - SourceWorkbookSha256 = _project.SourceWorkbookSha256, - ApplicationVersion = CurrentApplicationVersion, - Operator = Environment.UserName, - Workstation = Environment.MachineName, - TestPointId = point.TestPointId, - SignalName = point.SignalName, - ObjectReference = string.IsNullOrWhiteSpace(point.LiveSignalReference) ? point.ObjectReference : point.LiveSignalReference, - Attempt = point.Runtime.Attempt, - Transition = evidence.Slot.ToString(), - ObservedValue = evidence.RawValue, - IedTimestamp = evidence.IedTimestamp, - Quality = evidence.Quality, - AcquisitionSource = evidence.AcquisitionSource, - PointSequence = evidence.Sequence, - ConnectionGeneration = evidence.ConnectionGeneration, - Verdict = "Accepted", - Reason = $"Operator captured {evidence.Slot} from the current live IEC 61850 value.", - EvidenceKind = "fat-value-snapshot" - }; + var automatic = evidence.CaptureKind == FatEvidenceCaptureKind.AutomaticValue; + var recapture = evidence.CaptureKind == FatEvidenceCaptureKind.OperatorRecapture; + return new IoTestJournalEntry + { + EventType = eventType ?? (automatic ? "fat_value_auto" : recapture ? "fat_value_recapture" : "fat_value_snapshot"), + RecordedAtUtc = evidence.CapturedAt, + ProjectId = _project.ProjectId, + SessionId = SessionId, + IedName = point.IedName, + IpAddress = point.IpAddress, + SourceWorkbookName = _project.SourceWorkbookName, + SourceWorkbookSha256 = _project.SourceWorkbookSha256, + ApplicationVersion = CurrentApplicationVersion, + Operator = Environment.UserName, + Workstation = Environment.MachineName, + TestPointId = point.TestPointId, + SignalName = point.SignalName, + ObjectReference = string.IsNullOrWhiteSpace(point.LiveSignalReference) ? point.ObjectReference : point.LiveSignalReference, + Attempt = point.Runtime.Attempt, + Transition = evidence.Slot.ToString(), + ObservedValue = evidence.RawValue, + IedTimestamp = evidence.IedTimestamp, + Quality = evidence.Quality, + AcquisitionSource = evidence.AcquisitionSource, + PointSequence = evidence.Sequence, + ConnectionGeneration = evidence.ConnectionGeneration, + Verdict = "Accepted", + Reason = reason ?? (automatic + ? $"Auto Capture accepted {evidence.Slot} from the stable live IEC 61850 value." + : recapture + ? $"Operator recaptured {evidence.Slot} from the current live IEC 61850 value." + : $"Operator captured {evidence.Slot} from the current live IEC 61850 value."), + EvidenceKind = automatic ? "fat-value-auto" : recapture ? "fat-value-recapture" : "fat-value-snapshot" + }; + } private void AppendRequired(IoTestJournalEntry entry) { @@ -785,7 +1030,7 @@ private void AppendBatchRequired(IReadOnlyCollection entries var journal = _journal ?? throw new InvalidOperationException("No evidence journal is open for the active FAT session."); var envelopes = journal.AppendBatch(entries); if (envelopes.Count == 0) - throw new InvalidOperationException("Evidence journal startup batch produced no records."); + throw new InvalidOperationException("Evidence journal batch produced no records."); var envelope = envelopes[^1]; EvidenceRecordCount = envelope.JournalSequence; LastJournalHash = envelope.Hash; @@ -848,6 +1093,8 @@ private void CleanupSessionResources(bool disposeJournal, bool keepActiveIed = f _activePointIndex.Clear(); _activeLivePoints.Clear(); _captureCoordinator.Clear(); + _autoCaptureCoordinator.Clear(); + _pendingPairValue1.Clear(); ClearPendingSnapshots(); if (disposeJournal) { From bb9f029a7caab0cd9901bfdbbc388d1d56eea092 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:55:40 +0700 Subject: [PATCH 104/112] P3 route bulk Recapture through isolated IED sessions --- .../IoTestMultiSessionCoordinator.cs | 81 +++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/Services/IoTesting/IoTestMultiSessionCoordinator.cs b/Services/IoTesting/IoTestMultiSessionCoordinator.cs index 491267a3f..1f6767bd9 100644 --- a/Services/IoTesting/IoTestMultiSessionCoordinator.cs +++ b/Services/IoTesting/IoTestMultiSessionCoordinator.cs @@ -5,10 +5,10 @@ namespace ArIED61850Tester.Services.IoTesting; /// -/// P2 evidence-session authority. Each FAT IED owns one isolated +/// P2/P3 evidence-session authority. Each FAT IED owns one isolated /// so several connected IEDs can capture evidence /// at the same time without sharing a journal, live-point index, connection generation, -/// transition evaluator, or DeviceId acceptance boundary. +/// transition evaluator, auto-capture state, or Recapture transaction. /// /// The controller supplied by MainWindow remains the primary leaf for backwards /// compatibility. Additional leaves are created lazily through the configured sibling @@ -191,17 +191,35 @@ public IoTestSessionActionResult CaptureOperatorSnapshot(IoTestPointPlan? point, if (point == null) return IoTestSessionActionResult.Failure("Select a FAT row before capturing Value 1 or Value 2."); - var controller = ControllerSnapshot().FirstOrDefault(candidate => - candidate.IsSessionActive && candidate.ActiveIed?.TestPoints.Contains(point) == true); - controller ??= SelectedController; + var controller = ResolveOwningActiveController(new[] { point }, out var failure); if (controller == null) - return IoTestSessionActionResult.Failure("Start this IED FAT session before capturing Value 1 or Value 2."); + return IoTestSessionActionResult.Failure(failure); var result = controller.CaptureOperatorSnapshot(point, slot); RaiseProjectionProperties(); return result; } + public IoTestSessionActionResult RecaptureValues( + IReadOnlyCollection points, + FatValueSlot slot) + => BatchPointAction( + points, + (controller, batch) => controller.RecaptureValues(batch, slot), + "Select one or more rows from one active IED FAT session before using Recapture."); + + public IoTestSessionActionResult BeginPairRecapture(IReadOnlyCollection points) + => BatchPointAction( + points, + (controller, batch) => controller.BeginPairRecapture(batch), + "Select one or more rows from one active IED FAT session before staging Value 1 & Value 2 Recapture."); + + public IoTestSessionActionResult CancelPairRecapture(IReadOnlyCollection points) + => BatchPointAction( + points, + (controller, batch) => controller.CancelPairRecapture(batch), + "Select one or more rows from one active IED FAT session before cancelling staged Recapture."); + /// /// Runtime event route for additional P2 leaves only. MainWindow already routes every /// event to PrimaryController, therefore including it here would duplicate primary @@ -236,6 +254,57 @@ public void Dispose() _controllers.Clear(); } + private IoTestSessionActionResult BatchPointAction( + IReadOnlyCollection? points, + Func, IoTestSessionActionResult> action, + string missingMessage) + { + ThrowIfDisposed(); + var batch = points?.Where(point => point != null).Distinct().ToArray() ?? Array.Empty(); + if (batch.Length == 0) + return IoTestSessionActionResult.Failure(missingMessage); + + var controller = ResolveOwningActiveController(batch, out var failure); + if (controller == null) + return IoTestSessionActionResult.Failure(failure); + + var result = action(controller, batch); + RaiseProjectionProperties(); + return result; + } + + private IoTestSessionController? ResolveOwningActiveController( + IReadOnlyCollection points, + out string failure) + { + failure = string.Empty; + if (points.Count == 0) + { + failure = "Select at least one FAT row."; + return null; + } + + var owners = _project.Ieds + .Where(ied => points.Any(ied.TestPoints.Contains)) + .Distinct() + .ToArray(); + if (owners.Length != 1 || points.Any(point => !owners[0].TestPoints.Contains(point))) + { + failure = "Bulk Recapture must contain rows from exactly one IED. No evidence changed."; + return null; + } + + lock (_sync) + { + if (!_controllers.TryGetValue(owners[0], out var controller) || !controller.IsSessionActive) + { + failure = $"Start the {owners[0].IedName} FAT session before using Recapture."; + return null; + } + return controller; + } + } + private IoTestSessionController GetOrCreateController(IoTestIedPlan ied) { lock (_sync) From c78fda966f89bfe1fed8db3ff0060b1847e0600c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 12:58:30 +0700 Subject: [PATCH 105/112] P3 add multi-row selection and Recapture context menu --- IoListTestingWindow.FatV2Ux.cs | 95 +++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/IoListTestingWindow.FatV2Ux.cs b/IoListTestingWindow.FatV2Ux.cs index dfb4c4339..8f73887a1 100644 --- a/IoListTestingWindow.FatV2Ux.cs +++ b/IoListTestingWindow.FatV2Ux.cs @@ -13,6 +13,7 @@ public partial class IoListTestingWindow private DataGrid? _fatSignalsGrid; private Button? _removedSignalsButton; private ICollectionView? _fatSignalsView; + private IoTestPointPlan? _fatContextPoint; private bool _fatV2UxInstalled; private readonly BooleanToVisibilityConverter _fatBooleanVisibility = new(); private readonly IoFatSelectedIedPlanEditabilityConverter _fatPlanEditabilityConverter = new(); @@ -40,6 +41,10 @@ private void InstallFatV2WorkspaceUx() _fatSignalsGrid = FindVisualDescendant(this); if (_fatSignalsGrid != null) { + // P3 desktop selection contract: Ctrl+Click selects disjoint rows, + // Shift+Click selects ranges, and Recapture consumes SelectedItems. + _fatSignalsGrid.SelectionMode = DataGridSelectionMode.Extended; + _fatSignalsGrid.SelectionUnit = DataGridSelectionUnit.FullRow; ConfigureFatV2Columns(_fatSignalsGrid); _fatSignalsGrid.PreviewMouseRightButtonDown += FatSignalsGrid_PreviewMouseRightButtonDown; _fatSignalsGrid.ContextMenu = BuildFatContextMenu(); @@ -94,6 +99,8 @@ private void RefreshFatV2WorkspaceUx(bool refreshRows = false) private void ConfigureFatV2Columns(DataGrid grid) { + grid.SelectionMode = DataGridSelectionMode.Extended; + grid.SelectionUnit = DataGridSelectionUnit.FullRow; grid.Columns.Clear(); var enabledFactory = new FrameworkElementFactory(typeof(CheckBox)); @@ -277,27 +284,102 @@ private void CaptureOperatorValue(object sender, FatValueSlot slot) private ContextMenu BuildFatContextMenu() { var menu = new ContextMenu(); + + var recapture = new MenuItem { Header = "Recapture" }; + var value1 = new MenuItem { Header = "Value 1" }; + value1.Click += RecaptureValue1_Click; + var value2 = new MenuItem { Header = "Value 2" }; + value2.Click += RecaptureValue2_Click; + var both = new MenuItem + { + Header = "Value 1 & Value 2", + ToolTip = "Stage Value 1 now, change the test condition, then use Recapture → Value 2 to commit the new pair" + }; + both.Click += BeginPairRecapture_Click; + var cancelPair = new MenuItem { Header = "Cancel staged Value 1 & Value 2" }; + cancelPair.Click += CancelPairRecapture_Click; + recapture.Items.Add(value1); + recapture.Items.Add(value2); + recapture.Items.Add(both); + recapture.Items.Add(new Separator()); + recapture.Items.Add(cancelPair); + menu.Items.Add(recapture); + menu.Items.Add(new Separator()); + + // Remove remains a deliberate one-row disposition action. Multi-selection is for + // Recapture in P3; right-click remembers the exact context row so preserving a + // broader selection never removes an arbitrary anchor row. var remove = new MenuItem { Header = "Remove from FAT" }; remove.Click += RemoveSelectedFromFat_Click; menu.Items.Add(remove); + menu.Opened += (_, _) => + { + var count = SelectedFatRows().Count; + recapture.Header = count <= 1 ? "Recapture" : $"Recapture ({count} selected)"; + }; return menu; } + private IReadOnlyList SelectedFatRows() + { + if (_fatSignalsGrid == null) + return Array.Empty(); + return _fatSignalsGrid.SelectedItems + .OfType() + .Distinct() + .ToArray(); + } + + private void RecaptureValue1_Click(object sender, RoutedEventArgs e) + => ApplyBulkRecapture(Session.RecaptureValues(SelectedFatRows(), FatValueSlot.Value1), "Value 1 Recapture failed"); + + private void RecaptureValue2_Click(object sender, RoutedEventArgs e) + => ApplyBulkRecapture(Session.RecaptureValues(SelectedFatRows(), FatValueSlot.Value2), "Value 2 Recapture failed"); + + private void BeginPairRecapture_Click(object sender, RoutedEventArgs e) + => ApplyBulkRecapture(Session.BeginPairRecapture(SelectedFatRows()), "Value 1 & Value 2 Recapture could not be staged"); + + private void CancelPairRecapture_Click(object sender, RoutedEventArgs e) + => ApplyBulkRecapture(Session.CancelPairRecapture(SelectedFatRows()), "Staged Value 1 & Value 2 Recapture could not be cancelled"); + + private void ApplyBulkRecapture(IoTestSessionActionResult result, string failureTitle) + { + if (!result.Succeeded) + { + ShowActionResult(result, failureTitle); + return; + } + + // Success feedback stays compact in the selected IED footer through Session.StatusText. + // Never display one modal popup per row for a batch operation. + Storage?.ScheduleSave(); + RaiseSelectedIedContextProperties(); + RefreshFatV2WorkspaceUx(); + } + private void FatSignalsGrid_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { if (_fatSignalsGrid == null) return; var row = FindVisualAncestor(e.OriginalSource as DependencyObject); - if (row?.Item is IoTestPointPlan point) - { - _fatSignalsGrid.SelectedItem = point; - row.IsSelected = true; - } + if (row?.Item is not IoTestPointPlan point) + return; + + _fatContextPoint = point; + if (row.IsSelected) + return; + + // Desktop convention: right-clicking outside the current set makes that row the + // sole target. Right-clicking inside an existing Ctrl/Shift selection preserves it. + _fatSignalsGrid.SelectedItems.Clear(); + row.IsSelected = true; + _fatSignalsGrid.SelectedItem = point; } private void RemoveSelectedFromFat_Click(object sender, RoutedEventArgs e) { - if (_fatSignalsGrid?.SelectedItem is not IoTestPointPlan point) + var point = _fatContextPoint ?? _fatSignalsGrid?.SelectedItem as IoTestPointPlan; + if (point == null) return; if (!CanEditPointPlan(point)) { @@ -311,6 +393,7 @@ private void RemoveSelectedFromFat_Click(object sender, RoutedEventArgs e) } point.RemoveFromFat(); + _fatContextPoint = null; Storage?.ScheduleSave(); RefreshFatV2WorkspaceUx(refreshRows: true); RaiseSelectedIedContextProperties(); From dcef2567e45754456abb8b077370014da2ab6a1f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 13:00:22 +0700 Subject: [PATCH 106/112] Preserve full IED precision in FAT snapshot tooltips --- Models/IoTesting/IoTestModels.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Models/IoTesting/IoTestModels.cs b/Models/IoTesting/IoTestModels.cs index 4f4b93778..cc6afbbfd 100644 --- a/Models/IoTesting/IoTestModels.cs +++ b/Models/IoTesting/IoTestModels.cs @@ -260,9 +260,12 @@ private static string BuildFatEvidenceToolTip(FatValueEvidence? evidence, string evidence.IedTimestamp, "yyyy-MM-dd HH:mm:ss.fff zzz", "not supplied"); + var rawRelay = evidence.IedTimestamp?.ToString("O", CultureInfo.InvariantCulture) ?? "not supplied"; + var captured = evidence.CapturedAt.ToString("O", CultureInfo.InvariantCulture); return $"{label}: {evidence.RawValue}\n" + - $"IED timestamp: {displayed}\n" + - $"ARSAS capture: {evidence.CapturedAt:O}\n" + + $"Displayed (rounded to nearest ms): {displayed}\n" + + $"Decoded IED timestamp (full precision): {rawRelay}\n" + + $"ARSAS capture (full precision): {captured}\n" + $"Quality: {evidence.Quality}\nSource: {evidence.AcquisitionSource}\n" + $"Capture: {evidence.CaptureKind}"; } From f8fc4851cced2495c6725cc6e81aa46b76e4c551 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 13:00:46 +0700 Subject: [PATCH 107/112] Regress full precision FAT snapshot timestamp tooltips --- .../ARSAS.Tests/IoFatTimestampTooltipTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/ARSAS.Tests/IoFatTimestampTooltipTests.cs b/tests/ARSAS.Tests/IoFatTimestampTooltipTests.cs index 4811af14d..bd6f42efc 100644 --- a/tests/ARSAS.Tests/IoFatTimestampTooltipTests.cs +++ b/tests/ARSAS.Tests/IoFatTimestampTooltipTests.cs @@ -42,4 +42,40 @@ public void TimestampHover_ShowsRoundedDisplayAndDecodedFullPrecision() Assert.Contains("Quality: Good", tooltip, StringComparison.Ordinal); Assert.Contains("Source: BRCB", tooltip, StringComparison.Ordinal); } + + [Fact] + public void SnapshotTimestampHover_ShowsRoundedDisplayAndDecodedFullPrecision() + { + var timestamp = new DateTimeOffset(2026, 9, 3, 12, 43, 16, TimeSpan.FromHours(7)) + .AddTicks(9_215_108); + var evidence = new FatValueEvidence( + Guid.NewGuid(), + FatValueSlot.Value1, + FatEvidenceCaptureKind.OperatorSnapshot, + "12.345", + timestamp.AddMilliseconds(2), + timestamp, + "good", + "ARIEC Hybrid: StaticBrcb", + 7, + 3); + + var method = typeof(IoTestPointRuntime).GetMethod( + "BuildFatEvidenceToolTip", + BindingFlags.NonPublic | BindingFlags.Static) + ?? throw new MissingMethodException(nameof(IoTestPointRuntime), "BuildFatEvidenceToolTip"); + var tooltip = (string)method.Invoke(null, [evidence, "Value 1"])!; + + Assert.Contains("Value 1: 12.345", tooltip, StringComparison.Ordinal); + Assert.Contains("Displayed (rounded to nearest ms): 2026-09-03 12:43:16.922 +07:00", tooltip, StringComparison.Ordinal); + Assert.Contains( + "Decoded IED timestamp (full precision): " + timestamp.ToString("O", CultureInfo.InvariantCulture), + tooltip, + StringComparison.Ordinal); + Assert.DoesNotContain("16.9220000", tooltip, StringComparison.Ordinal); + Assert.Contains("ARSAS capture (full precision):", tooltip, StringComparison.Ordinal); + Assert.Contains("Quality: good", tooltip, StringComparison.Ordinal); + Assert.Contains("Source: ARIEC Hybrid: StaticBrcb", tooltip, StringComparison.Ordinal); + Assert.Contains("Capture: OperatorSnapshot", tooltip, StringComparison.Ordinal); + } } From 00bd100ac0ed50ecd5c30465996a636a6a0f1dd1 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 13:04:31 +0700 Subject: [PATCH 108/112] P3 regress auto capture, transactional Recapture and multi-row UX --- .../IoFatP3CaptureRegressionTests.cs | 432 ++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs diff --git a/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs b/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs new file mode 100644 index 000000000..15754dc9c --- /dev/null +++ b/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs @@ -0,0 +1,432 @@ +using ArIED61850Tester.Models; +using ArIED61850Tester.Models.IoTesting; +using ArIED61850Tester.Services.IoTesting; + +namespace ARSAS.Tests; + +public sealed class IoFatP3CaptureRegressionTests +{ + [Fact] + public void AnalogAutoCapture_LatchesStableBaselineAndFinalSettledValue_NotTransients() + { + var point = NewPoint("AN-1", FatSignalKind.Analog, FatCaptureMode.OperatorSnapshot); + var coordinator = new FatAutoCaptureCoordinator(); + long sequence = 0; + + FatAutoCaptureDecision decision = default!; + foreach (var raw in new[] { "0", "0", "0" }) + { + decision = coordinator.Observe(point, Observation(raw, ++sequence)); + if (decision.Evidence != null) + point.Runtime.SetFatValueEvidence(decision.Evidence); + } + + Assert.NotNull(point.Runtime.Value1Evidence); + Assert.Equal("0", point.Runtime.Value1Evidence!.RawValue); + Assert.Equal(FatEvidenceCaptureKind.AutomaticValue, point.Runtime.Value1Evidence.CaptureKind); + + foreach (var raw in new[] { "18.412", "43.920", "61.850", "65.702", "65.746", "65.748", "65.748", "65.748" }) + { + decision = coordinator.Observe(point, Observation(raw, ++sequence)); + if (decision.Evidence != null) + point.Runtime.SetFatValueEvidence(decision.Evidence); + } + + Assert.NotNull(point.Runtime.Value2Evidence); + Assert.Equal("65.748", point.Runtime.Value2Evidence!.RawValue); + Assert.Equal(FatEvidenceCaptureKind.AutomaticValue, point.Runtime.Value2Evidence.CaptureKind); + Assert.Equal(FatAutoCaptureStage.Complete, decision.Stage); + } + + [Fact] + public void AutoCapture_BadQualityDoesNotCreateEvidence_AndCompletePairDoesNotAutoOverwrite() + { + var point = NewPoint("AN-2", FatSignalKind.Analog, FatCaptureMode.OperatorSnapshot); + var coordinator = new FatAutoCaptureCoordinator(); + + var rejected = coordinator.Observe(point, Observation("12.3", 1, quality: "Invalid")); + Assert.Null(rejected.Evidence); + Assert.False(point.HasValue1Evidence); + + var value1 = Evidence(FatValueSlot.Value1, "10", 2, FatEvidenceCaptureKind.OperatorRecapture); + var value2 = Evidence(FatValueSlot.Value2, "20", 3, FatEvidenceCaptureKind.OperatorRecapture); + point.Runtime.SetFatValueEvidence(value1); + point.Runtime.SetFatValueEvidence(value2); + + var locked = coordinator.Observe(point, Observation("999", 4)); + + Assert.Null(locked.Evidence); + Assert.Equal(FatAutoCaptureStage.Complete, locked.Stage); + Assert.Equal(value1.EvidenceId, point.Runtime.Value1Evidence!.EvidenceId); + Assert.Equal(value2.EvidenceId, point.Runtime.Value2Evidence!.EvidenceId); + } + + [Fact] + public void GenericValueEvidence_OverridesLegacyAutomaticOnOffProjection() + { + var point = NewPoint("DI-1", FatSignalKind.Discrete, FatCaptureMode.AutomaticTransition); + var evaluator = new IoTestTransitionEvaluator(); + evaluator.StartAttempt(point, DigitalObservation(false, 1)); + evaluator.Observe(point, DigitalObservation(true, 2)); + evaluator.Observe(point, DigitalObservation(false, 3)); + + Assert.Equal("True", point.Value1Text); + Assert.Equal("False", point.Value2Text); + + var manualValue1 = Evidence(FatValueSlot.Value1, "MANUAL-V1", 4, FatEvidenceCaptureKind.OperatorRecapture); + var manualValue2 = Evidence(FatValueSlot.Value2, "MANUAL-V2", 5, FatEvidenceCaptureKind.OperatorRecapture); + point.Runtime.SetFatValueEvidence(manualValue1); + point.Runtime.SetFatValueEvidence(manualValue2); + + Assert.Equal("MANUAL-V1", point.Value1Text); + Assert.Equal("MANUAL-V2", point.Value2Text); + Assert.True(point.IsFatEvidenceComplete); + } + + [Fact] + public void RecaptureSingleSlot_PreservesOtherSlot_AndUsesOperatorRecaptureProvenance() + { + var fixture = CreateSnapshotFixture(); + using var controller = fixture.Controller; + Assert.True(controller.Start(fixture.Ied).Succeeded); + + fixture.LivePoint.Value = "10"; + fixture.LivePoint.Sequence = 1; + Assert.True(controller.CaptureOperatorSnapshot(fixture.Point, FatValueSlot.Value1).Succeeded); + fixture.LivePoint.Value = "20"; + fixture.LivePoint.Sequence = 2; + Assert.True(controller.CaptureOperatorSnapshot(fixture.Point, FatValueSlot.Value2).Succeeded); + var oldValue2 = fixture.Point.Runtime.Value2Evidence!.EvidenceId; + + fixture.LivePoint.Value = "15"; + fixture.LivePoint.Sequence = 3; + var recaptured = controller.RecaptureValues(new[] { fixture.Point }, FatValueSlot.Value1); + + Assert.True(recaptured.Succeeded, recaptured.Message); + Assert.Equal("15", fixture.Point.Value1Text); + Assert.Equal("20", fixture.Point.Value2Text); + Assert.Equal(oldValue2, fixture.Point.Runtime.Value2Evidence!.EvidenceId); + Assert.Equal(FatEvidenceCaptureKind.OperatorRecapture, fixture.Point.Runtime.Value1Evidence!.CaptureKind); + } + + [Fact] + public void PairRecapture_StagesValue1WithoutChangingCurrentPair_ThenPromotesBothOnValue2() + { + var fixture = CreateSnapshotFixture(); + using var controller = fixture.Controller; + Assert.True(controller.Start(fixture.Ied).Succeeded); + CaptureInitialPair(controller, fixture, "10", "20"); + var oldV1 = fixture.Point.Runtime.Value1Evidence!.EvidenceId; + var oldV2 = fixture.Point.Runtime.Value2Evidence!.EvidenceId; + + fixture.LivePoint.Value = "30"; + fixture.LivePoint.Sequence = 3; + var staged = controller.BeginPairRecapture(new[] { fixture.Point }); + + Assert.True(staged.Succeeded, staged.Message); + Assert.Equal(oldV1, fixture.Point.Runtime.Value1Evidence!.EvidenceId); + Assert.Equal(oldV2, fixture.Point.Runtime.Value2Evidence!.EvidenceId); + Assert.Equal("10", fixture.Point.Value1Text); + Assert.Equal("20", fixture.Point.Value2Text); + + fixture.LivePoint.Value = "40"; + fixture.LivePoint.Sequence = 4; + var committed = controller.RecaptureValues(new[] { fixture.Point }, FatValueSlot.Value2); + + Assert.True(committed.Succeeded, committed.Message); + Assert.Equal("30", fixture.Point.Value1Text); + Assert.Equal("40", fixture.Point.Value2Text); + Assert.Equal(FatEvidenceCaptureKind.OperatorRecapture, fixture.Point.Runtime.Value1Evidence!.CaptureKind); + Assert.Equal(FatEvidenceCaptureKind.OperatorRecapture, fixture.Point.Runtime.Value2Evidence!.CaptureKind); + var journal = File.ReadAllText(controller.JournalPath); + Assert.Contains("fat_value_recapture_pair_staged", journal, StringComparison.Ordinal); + Assert.Contains("fat_value_recapture_pair_commit", journal, StringComparison.Ordinal); + } + + [Fact] + public void PairRecapture_CancelLeavesPreviousCurrentPairUntouched() + { + var fixture = CreateSnapshotFixture(); + using var controller = fixture.Controller; + Assert.True(controller.Start(fixture.Ied).Succeeded); + CaptureInitialPair(controller, fixture, "10", "20"); + var oldV1 = fixture.Point.Runtime.Value1Evidence!.EvidenceId; + var oldV2 = fixture.Point.Runtime.Value2Evidence!.EvidenceId; + + fixture.LivePoint.Value = "50"; + fixture.LivePoint.Sequence = 3; + Assert.True(controller.BeginPairRecapture(new[] { fixture.Point }).Succeeded); + var cancelled = controller.CancelPairRecapture(new[] { fixture.Point }); + + Assert.True(cancelled.Succeeded, cancelled.Message); + Assert.Equal(oldV1, fixture.Point.Runtime.Value1Evidence!.EvidenceId); + Assert.Equal(oldV2, fixture.Point.Runtime.Value2Evidence!.EvidenceId); + Assert.Equal("10", fixture.Point.Value1Text); + Assert.Equal("20", fixture.Point.Value2Text); + } + + [Fact] + public void BatchJournalFailure_DoesNotPromoteAnyRecapturePointer() + { + var fixture = CreateSnapshotFixture(useFailingJournal: true); + using var controller = fixture.Controller; + Assert.True(controller.Start(fixture.Ied).Succeeded); + CaptureInitialPair(controller, fixture, "10", "20"); + var oldV1 = fixture.Point.Runtime.Value1Evidence!.EvidenceId; + var oldV2 = fixture.Point.Runtime.Value2Evidence!.EvidenceId; + fixture.FailingJournal!.FailNextBatch = true; + + fixture.LivePoint.Value = "99"; + fixture.LivePoint.Sequence = 4; + var result = controller.RecaptureValues(new[] { fixture.Point }, FatValueSlot.Value1); + + Assert.False(result.Succeeded); + Assert.Equal(oldV1, fixture.Point.Runtime.Value1Evidence!.EvidenceId); + Assert.Equal(oldV2, fixture.Point.Runtime.Value2Evidence!.EvidenceId); + Assert.Equal(IoTestSessionState.Faulted, controller.State); + } + + [Fact] + public void Recapture_PreflightRejectsRemovedRowWithoutChangingEvidence() + { + var fixture = CreateSnapshotFixture(); + using var controller = fixture.Controller; + Assert.True(controller.Start(fixture.Ied).Succeeded); + CaptureInitialPair(controller, fixture, "10", "20"); + var oldV1 = fixture.Point.Runtime.Value1Evidence!.EvidenceId; + fixture.Point.RemoveFromFat(); + fixture.LivePoint.Value = "100"; + + var result = controller.RecaptureValues(new[] { fixture.Point }, FatValueSlot.Value1); + + Assert.False(result.Succeeded); + Assert.Equal(oldV1, fixture.Point.Runtime.Value1Evidence!.EvidenceId); + } + + [Fact] + public void P3GridSource_UsesExtendedSelection_PreservesSelectedRightClick_AndOffersRecaptureMenu() + { + var source = File.ReadAllText(FindRepositoryFile("IoListTestingWindow.FatV2Ux.cs")); + + Assert.Contains("SelectionMode = DataGridSelectionMode.Extended", source, StringComparison.Ordinal); + Assert.Contains("if (row.IsSelected)", source, StringComparison.Ordinal); + Assert.Contains("SelectedItems", source, StringComparison.Ordinal); + Assert.Contains("Header = \"Recapture\"", source, StringComparison.Ordinal); + Assert.Contains("Header = \"Value 1\"", source, StringComparison.Ordinal); + Assert.Contains("Header = \"Value 2\"", source, StringComparison.Ordinal); + Assert.Contains("Header = \"Value 1 & Value 2\"", source, StringComparison.Ordinal); + Assert.Contains("BeginPairRecapture", source, StringComparison.Ordinal); + } + + private static void CaptureInitialPair( + IoTestSessionController controller, + SnapshotFixture fixture, + string value1, + string value2) + { + fixture.LivePoint.Value = value1; + fixture.LivePoint.Sequence = 1; + Assert.True(controller.CaptureOperatorSnapshot(fixture.Point, FatValueSlot.Value1).Succeeded); + fixture.LivePoint.Value = value2; + fixture.LivePoint.Sequence = 2; + Assert.True(controller.CaptureOperatorSnapshot(fixture.Point, FatValueSlot.Value2).Succeeded); + } + + private static SnapshotFixture CreateSnapshotFixture(bool useFailingJournal = false) + { + var point = NewPoint("AN-SNAPSHOT", FatSignalKind.Analog, FatCaptureMode.OperatorSnapshot); + point.ObjectReference = "IED1LD0/MMXU1.A.phsA.cVal.mag.f"; + var ied = new IoTestIedPlan + { + IedName = point.IedName, + IpAddress = point.IpAddress, + TestPoints = { point } + }; + var project = new IoTestProject + { + ProjectId = "P3-FAT", + SchemaVersion = "ARSAS-FAT-SCL-1.0", + ProjectName = "P3 FAT", + Ieds = { ied } + }; + project.InitializeRuntimeNotifications(); + + var device = new Iec61850MonitorDevice + { + DeviceId = "p3-device", + Name = ied.IedName, + SclIedName = ied.IedName, + IpAddress = ied.IpAddress, + Port = 102, + IsConnected = true, + IsMonitoring = true, + Status = "Monitoring" + }; + var livePoint = new Iec61850MonitorPoint + { + DeviceId = device.DeviceId, + DeviceName = device.Name, + IpAddress = device.IpAddress, + SignalName = point.SignalName, + IecReference = point.ObjectReference, + FunctionalConstraint = "MX", + Value = "0", + Quality = "Good", + DeviceTimestamp = "2026-09-03T06:00:00.000Z", + SourceMode = "MMS-POLL", + Sequence = 0, + Status = "Live" + }; + device.Points.Add(livePoint); + new IoTestLiveBindingService().Bind(project, new[] { device }); + + var root = Path.Combine(Path.GetTempPath(), "ARSAS.Tests", "P3", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + FailingBatchJournal? failing = null; + IoTestSessionController controller; + if (useFailingJournal) + { + controller = new IoTestSessionController( + project, + key => Resolves(key, device) ? device : null, + action => action(), + root, + journalFactory: (p, plan, sessionId, startedAt) => + { + failing = new FailingBatchJournal(IoTestEvidenceJournal.Create(root, p, plan, sessionId, startedAt)); + return failing; + }); + } + else + { + controller = new IoTestSessionController( + project, + key => Resolves(key, device) ? device : null, + action => action(), + root); + } + + return new SnapshotFixture(project, ied, point, device, livePoint, root, controller, () => failing); + } + + private static bool Resolves(string key, Iec61850MonitorDevice device) + => key.Equals(device.DeviceId, StringComparison.OrdinalIgnoreCase) || + key.Equals(device.Name, StringComparison.OrdinalIgnoreCase) || + key.Equals(device.IpAddress, StringComparison.OrdinalIgnoreCase); + + private static IoTestPointPlan NewPoint(string id, FatSignalKind kind, FatCaptureMode mode) + => new() + { + TestPointId = id, + IedName = "IED1", + IpAddress = "192.0.2.10", + SignalName = id, + ObjectReference = $"IED1LD0/GGIO1.{id}.stVal", + FunctionalConstraint = kind == FatSignalKind.Analog ? "MX" : "ST", + ExpectedOnText = "Value 1", + ExpectedOffText = "Value 2", + SignalKind = kind, + CaptureMode = mode, + WorkspaceSelected = true, + TestEnabled = true, + ImportReady = true, + BindingStatus = IoTestSignalSelectionService.SclWorkspaceAuthorityBindingStatus + }; + + private static IoTestObservation Observation(string raw, long sequence, string quality = "Good") + { + var timestamp = new DateTimeOffset(2026, 9, 3, 6, 0, 0, TimeSpan.Zero).AddMilliseconds(sequence * 100); + return new IoTestObservation( + null, + raw, + timestamp, + timestamp.AddMilliseconds(-2), + quality, + "MMS-POLL", + sequence, + 1); + } + + private static IoTestObservation DigitalObservation(bool state, long sequence) + { + var timestamp = new DateTimeOffset(2026, 9, 3, 6, 0, 0, TimeSpan.Zero).AddMilliseconds(sequence * 100); + return new IoTestObservation( + state, + state ? "True" : "False", + timestamp, + timestamp.AddMilliseconds(-2), + "Good", + "BRCB", + sequence, + 1); + } + + private static FatValueEvidence Evidence( + FatValueSlot slot, + string raw, + long sequence, + FatEvidenceCaptureKind kind) + => new( + Guid.NewGuid(), + slot, + kind, + raw, + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow.AddMilliseconds(-2), + "Good", + "MMS-POLL", + sequence, + 1); + + private static string FindRepositoryFile(string relativePath) + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + throw new FileNotFoundException($"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } + + private sealed record SnapshotFixture( + IoTestProject Project, + IoTestIedPlan Ied, + IoTestPointPlan Point, + Iec61850MonitorDevice Device, + Iec61850MonitorPoint LivePoint, + string Root, + IoTestSessionController Controller, + Func FailingJournalAccessor) + { + public FailingBatchJournal? FailingJournal => FailingJournalAccessor(); + } + + private sealed class FailingBatchJournal : IIoTestEvidenceJournal + { + private readonly IIoTestEvidenceJournal _inner; + + public FailingBatchJournal(IIoTestEvidenceJournal inner) => _inner = inner; + + public bool FailNextBatch { get; set; } + public string FilePath => _inner.FilePath; + public long RecordCount => _inner.RecordCount; + public string LastHash => _inner.LastHash; + + public IoTestJournalEnvelope Append(IoTestJournalEntry entry) => _inner.Append(entry); + + public IReadOnlyList AppendBatch(IEnumerable entries) + { + if (FailNextBatch) + { + FailNextBatch = false; + throw new IOException("Synthetic P3 batch journal failure."); + } + return _inner.AppendBatch(entries); + } + + public void Dispose() => _inner.Dispose(); + } +} From 9087e4ff595afd3252ff8c96f2b3be3c3487603e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 13:07:27 +0700 Subject: [PATCH 109/112] Fix P3 regression fixture init-only identity --- tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs b/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs index 15754dc9c..b0e341fda 100644 --- a/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs @@ -235,7 +235,6 @@ private static void CaptureInitialPair( private static SnapshotFixture CreateSnapshotFixture(bool useFailingJournal = false) { var point = NewPoint("AN-SNAPSHOT", FatSignalKind.Analog, FatCaptureMode.OperatorSnapshot); - point.ObjectReference = "IED1LD0/MMXU1.A.phsA.cVal.mag.f"; var ied = new IoTestIedPlan { IedName = point.IedName, From 59515ba32568518fb1fdfd4e8ba4adcf39fea653 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 13:13:17 +0700 Subject: [PATCH 110/112] Align legacy session tests with P3 capture contract --- tests/ARSAS.Tests/IoTestSessionControllerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ARSAS.Tests/IoTestSessionControllerTests.cs b/tests/ARSAS.Tests/IoTestSessionControllerTests.cs index 62c04f015..83738b455 100644 --- a/tests/ARSAS.Tests/IoTestSessionControllerTests.cs +++ b/tests/ARSAS.Tests/IoTestSessionControllerTests.cs @@ -21,7 +21,7 @@ public void LiveOffOnOffSequence_KeepsCaptureRunningUntilOperatorStopAndPersists Assert.Equal(IoTestSessionState.Running, controller.State); Assert.NotNull(fixture.Point.Runtime.OnEvidence); Assert.NotNull(fixture.Point.Runtime.OffEvidence); - Assert.Contains("Capture remains running", controller.StatusText, StringComparison.OrdinalIgnoreCase); + Assert.Contains("explicit Recapture", controller.StatusText, StringComparison.OrdinalIgnoreCase); var stopped = controller.Stop(); @@ -178,7 +178,7 @@ public void ExplicitCaptureScope_RejectsUncheckedPointRatherThanOverridingOperat Assert.False(result.Succeeded); Assert.False(fixture.Point.TestEnabled); Assert.Equal(IoTestSessionState.Idle, controller.State); - Assert.Contains("not selected", result.Message, StringComparison.OrdinalIgnoreCase); + Assert.Contains("TEST disabled", result.Message, StringComparison.OrdinalIgnoreCase); } [Fact] From 8e96f143d3aebecbfa74c7c5ffd400bf1462633f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 13:14:03 +0700 Subject: [PATCH 111/112] Align point-update regression with P3 completion status --- tests/ARSAS.Tests/IoTestSessionPointUpdateTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/IoTestSessionPointUpdateTests.cs b/tests/ARSAS.Tests/IoTestSessionPointUpdateTests.cs index 37014a077..24dbb498a 100644 --- a/tests/ARSAS.Tests/IoTestSessionPointUpdateTests.cs +++ b/tests/ARSAS.Tests/IoTestSessionPointUpdateTests.cs @@ -79,7 +79,7 @@ public void GoodQualityPointUpdate_RecoversRejectedInitialBaselineAndKeepsCaptur Assert.True(started.Succeeded, started.Message); Assert.Equal(IoTestPointState.Passed, point.Runtime.State); Assert.Equal(IoTestSessionState.Running, controller.State); - Assert.Contains("Capture remains running", controller.StatusText, StringComparison.OrdinalIgnoreCase); + Assert.Contains("explicit Recapture", controller.StatusText, StringComparison.OrdinalIgnoreCase); Assert.True(controller.Stop().Succeeded); var journal = File.ReadAllText(controller.JournalPath); Assert.Contains("waiting for good-quality baseline", journal, StringComparison.OrdinalIgnoreCase); From 6a201e300da440fe9993b2cbbdd0341f45caac39 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 3 Sep 2026 13:16:22 +0700 Subject: [PATCH 112/112] Fix P3 pair journal regression to verify sealed evidence --- tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs b/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs index b0e341fda..3e28f9271 100644 --- a/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs +++ b/tests/ARSAS.Tests/IoFatP3CaptureRegressionTests.cs @@ -138,6 +138,7 @@ public void PairRecapture_StagesValue1WithoutChangingCurrentPair_ThenPromotesBot Assert.Equal("40", fixture.Point.Value2Text); Assert.Equal(FatEvidenceCaptureKind.OperatorRecapture, fixture.Point.Runtime.Value1Evidence!.CaptureKind); Assert.Equal(FatEvidenceCaptureKind.OperatorRecapture, fixture.Point.Runtime.Value2Evidence!.CaptureKind); + Assert.True(controller.Stop().Succeeded); var journal = File.ReadAllText(controller.JournalPath); Assert.Contains("fat_value_recapture_pair_staged", journal, StringComparison.Ordinal); Assert.Contains("fat_value_recapture_pair_commit", journal, StringComparison.Ordinal);

>_V49=R=|Du$-L7lg@Vdf}u-Mnvf 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 z }DDRjjwVensU_brX6w2~_ zG??c;1-`AlpFJNz?usj;&UuH~{N(nUSQ!v#666me�^gMUOL4heW<@BPsgzeYOW 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