Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
510b108
fix(identity): bind trusted SCL match to selected access point in con…
masarray Oct 19, 2026
86715fb
build(identity): pin tested engine P0-A exact SHA for isolated consum…
masarray Oct 19, 2026
2e8f6d2
test(identity): verify five-LD SIEBCU consumer promotion and reject w…
masarray Oct 19, 2026
b2584c8
build(identity): preserve historical physical baseline pin and declar…
masarray Oct 19, 2026
0e3eba9
test(identity): preserve historical trial ancestry while admitting ex…
masarray Oct 19, 2026
10485e7
test(identity): preserve historical trial ancestry while admitting ex…
masarray Oct 19, 2026
c326c50
test(identity): preserve historical trial ancestry while admitting ex…
masarray Oct 19, 2026
29e3211
test(identity): exercise historical R10 trial fixture independently f…
masarray Oct 19, 2026
509cbb0
test(identity): add ancestry-bound code-only trial policy without wea…
masarray Oct 19, 2026
9caeb69
ci(identity): admit only ancestry-proven P0-A trial while keeping phy…
masarray Oct 19, 2026
14b9dd3
ci(identity): admit only ancestry-proven P0-A trial while keeping phy…
masarray Oct 19, 2026
201b3e5
ci(identity): admit only ancestry-proven P0-A trial while keeping phy…
masarray Oct 19, 2026
6315228
test(identity): execute exact P0-A policy helper inside isolated hist…
masarray Oct 19, 2026
f97db9a
ci(identity): respect mainline workflow root and executable sandbox t…
masarray Oct 19, 2026
e098d62
ci(identity): authorize exact ancestry-bound P0-A pin in shared immut…
masarray Oct 19, 2026
5f9f7c3
test(identity): assert immutable C264 PR ancestry separately from cur…
masarray Oct 19, 2026
5c9f273
test(identity): assert immutable C264 PR ancestry separately from cur…
masarray Oct 19, 2026
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
4 changes: 3 additions & 1 deletion .github/workflows/interoperability-reference-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ jobs:
. .\ARSAS\scripts\test-m217k-typed-status-trial.ps1
$coreRoutingTrialValid = Test-P109FCoreRoutingTrial -Lock $lock
$m217KTrialValid = Test-M217KTypedStatusTrial -Lock $lock
. .\ARSAS\scripts\test-p0-a-identity-trial.ps1
$p0AIdentityTrialValid = Test-P0AIdentityTrial -Lock $lock
$trial = $lock.smartInteroperabilityCodeTrial
$serverAt = $lock.sclServerAtTrial
$originalTrial = $lock.commit -eq $trial.exactCommit -and
Expand Down Expand Up @@ -135,7 +137,7 @@ jobs:
if ($trial.status -ne 'code-verified-not-physical' -or
[int]$trial.issue -ne 144 -or
[int]$trial.integrationPullRequest -ne 148 -or
-not ($originalTrial -or $serverAtTrial -or $datasetTrial -or $p10Trial -or $coreRoutingTrialValid -or $m217KTrialValid) -or
-not ($originalTrial -or $serverAtTrial -or $datasetTrial -or $p10Trial -or $coreRoutingTrialValid -or $m217KTrialValid -or $p0AIdentityTrialValid) -or
-not [bool]$trial.fullDiscoveryFallbackForbidden -or
-not [bool]$trial.vendorBranchingForbidden -or
-not [bool]$trial.physicalPromotionRequired) {
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/scl-interoperability-r7.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jobs:
. .\ARSAS\scripts\test-m217k-typed-status-trial.ps1
$coreRoutingTrialValid = Test-P109FCoreRoutingTrial -Lock $lock
$m217KTrialValid = Test-M217KTypedStatusTrial -Lock $lock
. .\ARSAS\scripts\test-p0-a-identity-trial.ps1
$p0AIdentityTrialValid = Test-P0AIdentityTrial -Lock $lock
$trial = $lock.smartInteroperabilityCodeTrial
if ($null -ne $trial -and $lock.commit -eq $trial.exactCommit) {
if ([int]$lock.sourcePullRequest -ne 148 -or
Expand Down Expand Up @@ -150,7 +152,7 @@ jobs:
}
Write-Host 'P10 trusted-SCL identity trial accepted for code CI ONLY; R7/R10 physical gate unchanged.'
}
elseif ($m217KTrialValid) {
elseif ($m217KTrialValid -or $p0AIdentityTrialValid) {
if ($env:GITHUB_BASE_REF -eq 'main' -or $env:GITHUB_REF_NAME -eq 'main') {
throw 'M2.17-K is not physically accepted and cannot run as a mainline promotion.'
}
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/smart-discovery-mainline-readiness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ jobs:
. .\scripts\test-m217k-typed-status-trial.ps1
$coreRoutingTrialValid = Test-P109FCoreRoutingTrial -Lock $lock
$m217KTrialValid = Test-M217KTypedStatusTrial -Lock $lock
. .\scripts\test-p0-a-identity-trial.ps1
$p0AIdentityTrialValid = Test-P0AIdentityTrial -Lock $lock
$trial = $lock.smartInteroperabilityCodeTrial
$serverAt = $lock.sclServerAtTrial
$closure = $lock.datasetFirstDiscoveryTrial
Expand Down Expand Up @@ -164,7 +166,7 @@ jobs:
$lock.commit -eq $p10.exactCommit -and
[int]$lock.sourcePullRequest -eq 161

if ($originalTrial -or $serverAtTrial -or $datasetTrial -or $p10Trial -or $coreRoutingTrialValid -or $m217KTrialValid) {
if ($originalTrial -or $serverAtTrial -or $datasetTrial -or $p10Trial -or $coreRoutingTrialValid -or $m217KTrialValid -or $p0AIdentityTrialValid) {
if (-not $historicalTrialValid -or
$lock.ref -ne 'main') {
throw 'Stacked trial identity or ancestor authority is inconsistent.'
Expand Down
6 changes: 4 additions & 2 deletions Services/TrustedSclIdentityAuthority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public static class TrustedSclIdentityAuthority
source,
liveModel.LogicalDevices.Select(ld => ld.MmsDomain),
device.IpAddress,
device.SclIedName);
device.SclIedName,
device.SclAccessPointName);
}

/// <summary>
Expand Down Expand Up @@ -87,7 +88,8 @@ public static class TrustedSclIdentityAuthority
continue;

var identity = TrustedSclIedIdentityMatcher.TryMatch(
document, domains, device.IpAddress, candidate.IedName);
document, domains, device.IpAddress, candidate.IedName,
candidate.AccessPointName);
if (identity is null ||
identity.Source != "TrustedSclExactDomainMatch" ||
identity.Confidence != LiveIedDiscoveryConfidenceLevel.High)
Expand Down
22 changes: 18 additions & 4 deletions engines/ARIEC61850.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"schemaVersion": 1,
"repository": "masarray/ARIEC61850",
"ref": "main",
"commit": "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da",
"sourcePullRequest": 173,
"purpose": "M2.17-K isolated C264 acceptance trial: ARIEC typed ST enumerated Mod/Beh/Health/PhyHealth Report FCD projection with exact ST/quality/timestamp checks, preserving P10.9V control and fail-closed unknowns. ARIEC #173 Windows engine CI passed; integrated consumer CI/physical C264 replay pending. Do not promote or merge into main without acceptance.",
"commit": "ccf31f60095ea65f9095514f0fa0102060962049",
"sourcePullRequest": 177,
"purpose": "P0-A trial of generic exact-prefix identity, provisional MMS-only confidence, case-sensitive LD domains and endpoint/AP-bound trusted SCL reconciliation. Engine exact-SHA CI passed; consumer exact-SHA Windows CI and physical qualification required; no mainline promotion.",
"previousTrialPin": {
"commit": "4467124775d8d9d76f3db194f9fbfd97144767a8",
"sourcePullRequest": 134,
"purpose": "Previous P0-5b/P0-5c field-capture engine pin retained as the physical R5 baseline before canonical communication evidence, logical RCB SCL projection, and round-trip association validation were added."
"purpose": "Historical P0-5b/P0-5c field-capture engine baseline required by immutable golden evidence and smart-discovery capture gates."
},
"priorGoldenWirePin": {
"commit": "0023ef9a4373855497464ed3979e359c4041c95d",
Expand Down Expand Up @@ -270,5 +270,19 @@
"physicalQualificationRequired": true,
"mainlinePromotionForbidden": true,
"cyclicMmsProcessPollingForbidden": true
},
"p0AIdentityTrial": {
"status": "code-verified-engine-consumer-trial-not-physical",
"enginePullRequest": 177,
"consumerPullRequest": 625,
"baseEngineCommit": "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da",
"exactCommit": "ccf31f60095ea65f9095514f0fa0102060962049",
"engineCiRun": 39228439974,
"fullDomainSetRequired": true,
"sclSha256Required": true,
"endpointAndAccessPointRequired": true,
"provisionalHeuristicOnly": true,
"physicalQualificationRequired": true,
"mainlinePromotionForbidden": true
}
}
5 changes: 4 additions & 1 deletion scripts/resolve-ariec-lock.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,16 @@ if ($null -ne $lock.sclAssociationInteroperability) {
. (Join-Path $PSScriptRoot 'test-m217k-typed-status-trial.ps1')
$coreRoutingTrialValid = Test-P109FCoreRoutingTrial -Lock $lock
$m217KTrialValid = Test-M217KTypedStatusTrial -Lock $lock
. (Join-Path $PSScriptRoot 'test-p0-a-identity-trial.ps1')
$p0AIdentityTrialValid = Test-P0AIdentityTrial -Lock $lock
$acceptedTrial = (
($trialValid -and [string]$lock.commit -ceq [string]$trial.exactCommit -and [int]$lock.sourcePullRequest -eq 148) -or
($serverAtValid -and [string]$lock.commit -ceq [string]$serverAt.exactCommit -and [int]$lock.sourcePullRequest -eq 153) -or
($closureValid -and [string]$lock.commit -ceq [string]$closure.exactCommit -and [int]$lock.sourcePullRequest -eq 157) -or
($p10Valid -and [string]$lock.commit -ceq [string]$p10.exactCommit -and [int]$lock.sourcePullRequest -eq 161) -or
($p10Valid -and $coreRoutingTrialValid) -or
$m217KTrialValid
$m217KTrialValid -or
$p0AIdentityTrialValid
)
if (-not $historicalAnchorValid -or -not $acceptedTrial) {
throw 'SCL association physical-tested engine head differs from integration lock.'
Expand Down
34 changes: 34 additions & 0 deletions scripts/test-p0-a-identity-trial.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# P0-A identity is a code-only descendant of the immutable M2.17-K engine.
# This verifier deliberately never changes historical physical acceptance.
function Test-P0AIdentityTrial {
param([Parameter(Mandatory = $true)]$Lock)

$trial = $Lock.p0AIdentityTrial
if ($null -eq $trial) { return $false }

. (Join-Path $PSScriptRoot 'test-m217k-typed-status-trial.ps1')
$parent = $Lock | ConvertTo-Json -Depth 100 | ConvertFrom-Json
$parent.commit = '51c2eefd61d6bcbe1116fda6b781b74bb9cf06da'
$parent.sourcePullRequest = 173
$parent.purpose = 'M2.17-K isolated C264 acceptance trial'
if (-not (Test-M217KTypedStatusTrial -Lock $parent)) { return $false }

return (
[string]$Lock.repository -ceq 'masarray/ARIEC61850' -and
[string]$Lock.ref -ceq 'main' -and
[string]$Lock.commit -ceq 'ccf31f60095ea65f9095514f0fa0102060962049' -and
[int]$Lock.sourcePullRequest -eq 177 -and
[string]$trial.status -ceq 'code-verified-engine-consumer-trial-not-physical' -and
[int]$trial.enginePullRequest -eq 177 -and
[int]$trial.consumerPullRequest -eq 625 -and
[string]$trial.baseEngineCommit -ceq '51c2eefd61d6bcbe1116fda6b781b74bb9cf06da' -and
[string]$trial.exactCommit -ceq $Lock.commit -and
[long]$trial.engineCiRun -eq 39228439974 -and
[bool]$trial.fullDomainSetRequired -and
[bool]$trial.sclSha256Required -and
[bool]$trial.endpointAndAccessPointRequired -and
[bool]$trial.provisionalHeuristicOnly -and
[bool]$trial.physicalQualificationRequired -and
[bool]$trial.mainlinePromotionForbidden
)
}
11 changes: 7 additions & 4 deletions tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -474,9 +474,12 @@ public void EngineLock_PreservesPhysicalSclRepairBaselineAcrossCodeVerifiedInter
typedStatusTrial.GetProperty("previousConsumerPin").GetString());
Assert.Equal("51c2eefd61d6bcbe1116fda6b781b74bb9cf06da",
typedStatusTrial.GetProperty("exactCommit").GetString());
var identityTrial = root.GetProperty("p0AIdentityTrial");
Assert.Equal(typedStatusTrial.GetProperty("exactCommit").GetString(),
identityTrial.GetProperty("baseEngineCommit").GetString());
Assert.Equal(identityTrial.GetProperty("exactCommit").GetString(),
root.GetProperty("commit").GetString());
Assert.Equal(173, root.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(177, root.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(618, typedStatusTrial.GetProperty("consumerPullRequest").GetInt32());
Assert.True(typedStatusTrial.GetProperty("strictFcAndTupleEvidenceRequired").GetBoolean());
Assert.True(typedStatusTrial.GetProperty("physicalQualificationRequired").GetBoolean());
Expand Down Expand Up @@ -518,9 +521,9 @@ public void EngineLock_PreservesPhysicalSclRepairBaselineAcrossCodeVerifiedInter
Assert.True(zivTrial.GetProperty("mainlinePromotionForbidden").GetBoolean());
Assert.True(zivTrial.GetProperty("fullDiscoveryFallbackForbidden").GetBoolean());
Assert.True(zivTrial.GetProperty("cyclicMmsProcessPollingForbidden").GetBoolean());
// The current C264 report trial is PR #173, while the previous
// P10.9V control authority remains the unchanged PR #169.
Assert.Equal(173, root.GetProperty("sourcePullRequest").GetInt32());
// Historical C264 remains PR #173; current P0-A identity trial is #177.
Assert.Equal(173, typedStatusTrial.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(177, root.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(169, typedBscTrial.GetProperty("enginePullRequest").GetInt32());
Assert.True(genericTrial.GetProperty("typedSemanticGateRequired").GetBoolean());
Assert.True(genericTrial.GetProperty("associationScopedInventoryRequired").GetBoolean());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ public void TestProductUsesExplicitlyApprovedEngineCommitNotPreviouslyPinnedDll(
using var lockDocument = JsonDocument.Parse(File.ReadAllText(
Path.Combine(root!.FullName, "engines", "ARIEC61850.lock.json")));
var lockRoot = lockDocument.RootElement;
Assert.Equal(ExpectedEngineSha,
var identityTrial = lockRoot.GetProperty("p0AIdentityTrial");
Assert.Equal(ExpectedEngineSha, identityTrial.GetProperty("baseEngineCommit").GetString());
Assert.Equal(identityTrial.GetProperty("exactCommit").GetString(),
lockRoot.GetProperty("commit").GetString());
Assert.Equal(173, lockRoot.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(177, lockRoot.GetProperty("sourcePullRequest").GetInt32());
var trial = lockRoot.GetProperty("m217KTypedStatusReportTrial");
Assert.Equal("036e2c00d83a0815b6160147b14ea05975057965",
trial.GetProperty("previousConsumerPin").GetString());
Expand Down
13 changes: 13 additions & 0 deletions tests/ARSAS.Tests/P10MainlinePolicyExecutionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,22 @@ public void Exact_CI_Gate_Fails_Closed_Without_False_Physical_Promotion(
// must execute the same immutable-trial gate, not a missing script.
File.Copy(Path.Combine(root, "scripts", "test-m217k-typed-status-trial.ps1"),
Path.Combine(folder, "scripts", "test-m217k-typed-status-trial.ps1"));
File.Copy(Path.Combine(root, "scripts", "test-p0-a-identity-trial.ps1"),
Path.Combine(folder, "scripts", "test-p0-a-identity-trial.ps1"));
var lockDoc = JsonNode.Parse(File.ReadAllText(Path.Combine(root,
"engines", "ARIEC61850.lock.json")))!.AsObject();

// Historical R10 gate tests its own already-qualified M2.17-K
// baseline. The P0-A child is a distinct non-physical trial, not
// retroactive evidence that R10's historical pin changed.
if (scenario == "p10-stacked-valid" ||
scenario.StartsWith("m217k-", StringComparison.Ordinal))
{
lockDoc["commit"] = "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da";
lockDoc["sourcePullRequest"] = 173;
lockDoc["purpose"] = "M2.17-K isolated C264 acceptance trial";
}

string baseRef = "fix/512-goose-event-header-alignment";
string sourceRef = "512/merge";
switch (scenario)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,14 @@ public void TrialLock_PreservesHistoricalPhysicalAuthorityAndExplicitlyRequiresP
typedStatusTrial.GetProperty("previousConsumerPin").GetString());
Assert.Equal("51c2eefd61d6bcbe1116fda6b781b74bb9cf06da",
typedStatusTrial.GetProperty("exactCommit").GetString());
var identityTrial = root.GetProperty("p0AIdentityTrial");
Assert.Equal(typedStatusTrial.GetProperty("exactCommit").GetString(),
identityTrial.GetProperty("baseEngineCommit").GetString());
Assert.Equal(identityTrial.GetProperty("exactCommit").GetString(),
root.GetProperty("commit").GetString());
Assert.Equal(173, root.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(177, root.GetProperty("sourcePullRequest").GetInt32());
Assert.True(identityTrial.GetProperty("physicalQualificationRequired").GetBoolean());
Assert.True(identityTrial.GetProperty("mainlinePromotionForbidden").GetBoolean());
Assert.Equal(618, typedStatusTrial.GetProperty("consumerPullRequest").GetInt32());
Assert.True(typedStatusTrial.GetProperty("strictFcAndTupleEvidenceRequired").GetBoolean());
Assert.True(typedStatusTrial.GetProperty("physicalQualificationRequired").GetBoolean());
Expand Down Expand Up @@ -166,9 +171,9 @@ public void TrialLock_PreservesHistoricalPhysicalAuthorityAndExplicitlyRequiresP
Assert.True(zivTrial.GetProperty("mainlinePromotionForbidden").GetBoolean());
Assert.True(zivTrial.GetProperty("fullDiscoveryFallbackForbidden").GetBoolean());
Assert.True(zivTrial.GetProperty("cyclicMmsProcessPollingForbidden").GetBoolean());
// The current C264 report trial is PR #173, while the previous
// P10.9V control authority remains the unchanged PR #169.
Assert.Equal(173, root.GetProperty("sourcePullRequest").GetInt32());
// Historical C264 remains PR #173; current P0-A identity trial is #177.
Assert.Equal(173, typedStatusTrial.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(177, root.GetProperty("sourcePullRequest").GetInt32());
Assert.Equal(169, typedBscTrial.GetProperty("enginePullRequest").GetInt32());
Assert.Equal(540, genericTrial.GetProperty("issue").GetInt32());
Assert.Equal(38231327892L, genericTrial.GetProperty("engineCiRun").GetInt64());
Expand Down
34 changes: 34 additions & 0 deletions tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,40 @@ public async Task GE_F650_SHA_Pinned_Engineering_Source_Promotes_IED_And_LD_With
Assert.Equal("F650",Assert.Single(projected.LogicalDevices).Instance);
}


[Fact]
public async Task Five_Ld_SIEBCU_Identity_Is_Verified_Only_By_Matching_Pinned_Scl_Endpoint_And_Ap()
{
using var temp=TempScl.Create("SIEBCU","CTRL","192.0.2.20");
var path=temp.Device.SclSourcePath;
var xml=File.ReadAllText(path);
xml=xml.Replace("</Server>", """
<LDevice inst="MEAS"/><LDevice inst="PROT"/><LDevice inst="UD1"/><LDevice inst="UD2"/></Server>
""", StringComparison.Ordinal);
File.WriteAllText(path,xml);
temp.Device.SclSourceSha256=Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path)));
var domains=new[] {"SIEBCUCTRL","SIEBCUMEAS","SIEBCUPROT","SIEBCUUD1","SIEBCUUD2"};
var model=new LiveIedModelDiscoveryDocument
{
Host="192.0.2.20",
LogicalDevices=domains.Select(x=>new LiveIedLogicalDeviceModel{MmsDomain=x,Inst=x}).ToArray()
};
var verified=await TrustedSclIdentityAuthority.TryMatchAsync(temp.Device,model);
Assert.NotNull(verified);
Assert.Equal("SIEBCU",verified.IedName);
Assert.Equal(LiveIedDiscoveryConfidenceLevel.High,verified.Confidence);
Assert.Equal(5,verified.LogicalDeviceAliases.Count);
var projected=TrustedSclIdentityAuthority.ToConsumerIdentity(verified,domains);
Assert.Equal("SIEBCU",projected.IedName);
Assert.Equal("UD2",projected.LogicalDevices.Single(x=>x.Domain=="SIEBCUUD2").Instance);

temp.Device.SclAccessPointName="WRONG_AP";
Assert.Null(await TrustedSclIdentityAuthority.TryMatchAsync(temp.Device,model));
temp.Device.SclAccessPointName="S1";
temp.Device.IpAddress="192.0.2.21";
Assert.Null(await TrustedSclIdentityAuthority.TryMatchAsync(temp.Device,model));
}

[Fact]
public async Task Changed_SCL_SHA_Must_Fail_Closed_And_Never_Rename()
{
Expand Down
Loading