Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions engines/ARIEC61850.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "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 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."
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
70 changes: 70 additions & 0 deletions tests/ARSAS.Tests/P62DynamicNvlProbeRegressionTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading