From c0dfe5cd20b353daf2d83baf890099cd056f10fc Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Tue, 18 Aug 2026 14:58:38 +0700 Subject: [PATCH 1/5] P6.2: pin dynamic NVL probation engine --- engines/ARIEC61850.lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engines/ARIEC61850.lock.json b/engines/ARIEC61850.lock.json index 7edc3332..c5d8c75c 100644 --- a/engines/ARIEC61850.lock.json +++ b/engines/ARIEC61850.lock.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "repository": "masarray/ARIEC61850", "ref": "main", - "commit": "1d052c3919ca1fdf043747d858dab9b6c384e827", - "sourcePullRequest": 87, - "purpose": "Pins the ARIEC61850 engine used by ARSAS. 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 resolved runtime primary leaves from the 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 so OptFlds/inclusion/reason fields can never leak into process values, PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling while preventing quality/timestamp descriptors from hijacking the primary-value fallback, PR #85 evaluates negotiated MMS write/DefineNamedVariableList/DeleteNamedVariableList support plus fresh per-RCB DatSet/RptEna/TrgOps/OptFlds/GI/IntgPd/reservation/Owner evidence before automatic dynamic reporting, PR #86 requires explicit per-signal dynamic-attempt/skip evidence before final polling while returning runtime dynamic-attempt failure reasons and best-effort rollback evidence for failed temporary DataSet/RCB activation, and PR #87 restores baseline-safe static precedence by keeping freshly verified populated static RCBs visible to the stable planner while capability qualification remains an additional gate only for empty RCB slots that require dynamic DataSet/RCB mutation." + "commit": "4df84d4e2a7c824e4115c6182ef5d4dd7872d5ae", + "sourcePullRequest": 88, + "purpose": "Pins the ARIEC61850 engine used by ARSAS. 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 resolved runtime primary leaves from the 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 so OptFlds/inclusion/reason fields can never leak into process values, PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling while preventing quality/timestamp descriptors from hijacking the primary-value fallback, PR #85 evaluates negotiated MMS write/DefineNamedVariableList/DeleteNamedVariableList support plus fresh per-RCB DatSet/RptEna/TrgOps/OptFlds/GI/IntgPd/reservation/Owner evidence before automatic dynamic reporting, PR #86 requires explicit per-signal dynamic-attempt/skip evidence before final polling while returning runtime dynamic-attempt failure reasons and best-effort rollback evidence for failed temporary DataSet/RCB activation, PR #87 restores baseline-safe static precedence by keeping freshly verified populated static RCBs visible to the stable planner while capability qualification remains an additional gate only for empty RCB slots that require dynamic DataSet/RCB mutation, and PR #88 adds a single-member DefineNamedVariableList/GetNamedVariableListAttributes/DeleteNamedVariableList probation before any dynamic RCB mutation while preserving exact invokeID, request/response BER, routing, member, DataSet, and association-state evidence for field root-cause analysis." } From 2606d684125b38e84750aadf4849b955012c314e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Tue, 18 Aug 2026 14:59:02 +0700 Subject: [PATCH 2/5] P6.2: guard exact NVL probe evidence through ARSAS --- .../P62DynamicNvlProbeRegressionTests.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/ARSAS.Tests/P62DynamicNvlProbeRegressionTests.cs diff --git a/tests/ARSAS.Tests/P62DynamicNvlProbeRegressionTests.cs b/tests/ARSAS.Tests/P62DynamicNvlProbeRegressionTests.cs new file mode 100644 index 00000000..7b158a99 --- /dev/null +++ b/tests/ARSAS.Tests/P62DynamicNvlProbeRegressionTests.cs @@ -0,0 +1,70 @@ +using AR.Iec61850.Mms; + +namespace ARSAS.Tests; + +public sealed class P62DynamicNvlProbeRegressionTests +{ + [Fact] + public void PinnedEngine_ExposesExactSingleMemberProbeEvidenceContract() + { + var evidence = new MmsDynamicDataSetProbeServiceEvidence + { + Service = "DefineNamedVariableList", + Attempted = true, + IsSuccess = false, + InvokeId = 41, + DataSetReference = "IEDLD0/LLN0.AR_HYB_01", + MemberReference = "IEDLD0/GGIO1$ST$Ind1$stVal", + RequestHex = "AA BB CC", + ResponseHex = string.Empty, + StateBefore = MmsAssociationState.MmsInitiated, + StateAfter = MmsAssociationState.MmsInitiateFailed, + ReceiveRoutingSummary = "transport closed", + Message = "probe transport fault" + }; + + Assert.True(evidence.Attempted); + Assert.Equal(41, evidence.InvokeId); + Assert.Equal("IEDLD0/LLN0.AR_HYB_01", evidence.DataSetReference); + Assert.Equal("IEDLD0/GGIO1$ST$Ind1$stVal", evidence.MemberReference); + Assert.Equal(MmsAssociationState.MmsInitiated, evidence.StateBefore); + Assert.Equal(MmsAssociationState.MmsInitiateFailed, evidence.StateAfter); + Assert.Contains("DefineNamedVariableList", evidence.Summary, StringComparison.Ordinal); + Assert.Contains("IEDLD0/LLN0.AR_HYB_01", evidence.Summary, StringComparison.Ordinal); + } + + [Theory] + [InlineData(MmsReportActivationFailureReason.DynamicDataSetProbeDefineFailed)] + [InlineData(MmsReportActivationFailureReason.DynamicDataSetProbeVerificationFailed)] + [InlineData(MmsReportActivationFailureReason.DynamicDataSetProbeDeleteFailed)] + public void PinnedEngine_ExposesProbeFailureStages(MmsReportActivationFailureReason reason) + { + Assert.Contains("Probe", reason.ToString(), StringComparison.Ordinal); + } + + [Fact] + public void RuntimeDiagnostics_SurfaceProbeEvidenceWithoutAddingAnotherLiveGrid() + { + var runtime = ReadRepoFile("Services/Iec61850MonitorRuntime.cs"); + var diagnostics = ReadRepoFile("MainWindow.HybridAcquisitionDiagnostics.cs"); + + Assert.Contains("result.Warnings.Take(3)", runtime, StringComparison.Ordinal); + Assert.Contains("Report plan unavailable", runtime, StringComparison.Ordinal); + Assert.DoesNotContain("DispatcherTimer", diagnostics, StringComparison.Ordinal); + Assert.DoesNotContain("DataGrid", diagnostics, 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(relativePath); + } +} From 0b939c68eb902ada90081a65449283c4f81d6812 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Tue, 18 Aug 2026 15:02:20 +0700 Subject: [PATCH 3/5] P6.2: pin fail-closed NVL probe engine --- 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 c5d8c75c..fd6ca881 100644 --- a/engines/ARIEC61850.lock.json +++ b/engines/ARIEC61850.lock.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "repository": "masarray/ARIEC61850", "ref": "main", - "commit": "4df84d4e2a7c824e4115c6182ef5d4dd7872d5ae", + "commit": "d4612fb7fd5d499a70df17110f6191c452340e0c", "sourcePullRequest": 88, - "purpose": "Pins the ARIEC61850 engine used by ARSAS. 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 resolved runtime primary leaves from the 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 so OptFlds/inclusion/reason fields can never leak into process values, PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling while preventing quality/timestamp descriptors from hijacking the primary-value fallback, PR #85 evaluates negotiated MMS write/DefineNamedVariableList/DeleteNamedVariableList support plus fresh per-RCB DatSet/RptEna/TrgOps/OptFlds/GI/IntgPd/reservation/Owner evidence before automatic dynamic reporting, PR #86 requires explicit per-signal dynamic-attempt/skip evidence before final polling while returning runtime dynamic-attempt failure reasons and best-effort rollback evidence for failed temporary DataSet/RCB activation, PR #87 restores baseline-safe static precedence by keeping freshly verified populated static RCBs visible to the stable planner while capability qualification remains an additional gate only for empty RCB slots that require dynamic DataSet/RCB mutation, and PR #88 adds a single-member DefineNamedVariableList/GetNamedVariableListAttributes/DeleteNamedVariableList probation before any dynamic RCB mutation while preserving exact invokeID, request/response BER, routing, member, DataSet, and association-state evidence for field root-cause analysis." + "purpose": "Pins the ARIEC61850 engine used by ARSAS. 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 resolved runtime primary leaves from the 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 so OptFlds/inclusion/reason fields can never leak into process values, PR #84 routes exact PrimaryValue residuals through dynamic reporting before MMS polling while preventing quality/timestamp descriptors from hijacking the primary-value fallback, PR #85 evaluates negotiated MMS write/DefineNamedVariableList/DeleteNamedVariableList support plus fresh per-RCB DatSet/RptEna/TrgOps/OptFlds/GI/IntgPd/reservation/Owner evidence before automatic dynamic reporting, PR #86 requires explicit per-signal dynamic-attempt/skip evidence before final polling while returning runtime dynamic-attempt failure reasons and best-effort rollback evidence for failed temporary DataSet/RCB activation, PR #87 restores baseline-safe static precedence by keeping freshly verified populated static RCBs visible to the stable planner while capability qualification remains an additional gate only for empty RCB slots that require dynamic DataSet/RCB mutation, and PR #88 adds a fail-closed single-member DefineNamedVariableList/GetNamedVariableListAttributes/DeleteNamedVariableList probation before any dynamic RCB mutation while preserving exact invokeID, request/response BER, routing, member, DataSet, association-state, and cleanup evidence for field root-cause analysis." } From f2f1a8def034eb8a96a8f6c3db5c4324e34c5a1f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Tue, 18 Aug 2026 15:05:11 +0700 Subject: [PATCH 4/5] P6.2: advance capability provenance guard to NVL probe engine --- ...portAssociationCapabilityP3RegressionTests.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/ARSAS.Tests/HybridReportAssociationCapabilityP3RegressionTests.cs b/tests/ARSAS.Tests/HybridReportAssociationCapabilityP3RegressionTests.cs index a5704e78..8e45e112 100644 --- a/tests/ARSAS.Tests/HybridReportAssociationCapabilityP3RegressionTests.cs +++ b/tests/ARSAS.Tests/HybridReportAssociationCapabilityP3RegressionTests.cs @@ -18,18 +18,20 @@ public void HybridPlanning_UsesAssociationCapabilityForInitialPlanAndFreshRevali } [Fact] - public void EngineLock_PinsP61StaticPreservingAttemptAwareEngine() + public void EngineLock_PinsP62ProbeEngineWhilePreservingP61StaticSafetyHistory() { var source = Read("engines/ARIEC61850.lock.json"); - Assert.Contains("1d052c3919ca1fdf043747d858dab9b6c384e827", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("\"sourcePullRequest\": 87", source, StringComparison.Ordinal); + Assert.Contains("d4612fb7fd5d499a70df17110f6191c452340e0c", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("\"sourcePullRequest\": 88", source, StringComparison.Ordinal); + Assert.Contains("PR #87", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("baseline-safe static precedence", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("DefineNamedVariableList", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("GetNamedVariableListAttributes", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("DeleteNamedVariableList", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("dynamic-attempt", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("rollback", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("baseline-safe static precedence", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("dynamic DataSet/RCB mutation", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("single-member", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("before any dynamic RCB mutation", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("cleanup evidence", source, StringComparison.OrdinalIgnoreCase); } private static int Count(string source, string value) From 3aab0e5f355c69e471b456b87c0abc6ce039e6a5 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Tue, 18 Aug 2026 15:05:38 +0700 Subject: [PATCH 5/5] P6.2: preserve report history while pinning probe engine --- ...OfflineDataSetSignalSelectionRegressionTests.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/ARSAS.Tests/OfflineDataSetSignalSelectionRegressionTests.cs b/tests/ARSAS.Tests/OfflineDataSetSignalSelectionRegressionTests.cs index dcfeb889..599ed46d 100644 --- a/tests/ARSAS.Tests/OfflineDataSetSignalSelectionRegressionTests.cs +++ b/tests/ARSAS.Tests/OfflineDataSetSignalSelectionRegressionTests.cs @@ -69,25 +69,29 @@ public void SignalSelectionRecovery_RunsAgainAfterLegacyConstructorDisplayPrepar } [Fact] - public void EngineLock_PreservesReportProjectionHistoryAndPinsP61StaticSafetyEngine() + public void EngineLock_PreservesReportProjectionHistoryAndPinsP62ProbeEngine() { var source = File.ReadAllText(FindRepoFile("engines/ARIEC61850.lock.json")); using var document = JsonDocument.Parse(source); var root = document.RootElement; - Assert.Equal("1d052c3919ca1fdf043747d858dab9b6c384e827", root.GetProperty("commit").GetString()); - Assert.Equal(87, root.GetProperty("sourcePullRequest").GetInt32()); + Assert.Equal("d4612fb7fd5d499a70df17110f6191c452340e0c", root.GetProperty("commit").GetString()); + Assert.Equal(88, root.GetProperty("sourcePullRequest").GetInt32()); Assert.Contains("one descriptor per static DataSet member", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("generic Boolean status structures", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("DataRef-enabled InformationReport ordering", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("zero OptFlds", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("quarantining unmapped canonical report metadata", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("dynamic reporting before MMS polling", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("DefineNamedVariableList", source, StringComparison.OrdinalIgnoreCase); - Assert.Contains("TrgOps", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("dynamic-attempt", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("rollback", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("PR #87", source, StringComparison.OrdinalIgnoreCase); Assert.Contains("baseline-safe static precedence", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("DefineNamedVariableList", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("GetNamedVariableListAttributes", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("DeleteNamedVariableList", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("single-member", source, StringComparison.OrdinalIgnoreCase); + Assert.Contains("cleanup evidence", source, StringComparison.OrdinalIgnoreCase); } [Fact]