From 45189fc8bb2c9eda08d01b1fec219f362e1b2bf3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:03:10 +0700 Subject: [PATCH 01/12] test(identity): consumer enforces verified AP and five-domain IED projection --- .../TrustedSclIdentityConsumerTests.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs b/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs index f2476d095..4c775c7ae 100644 --- a/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs +++ b/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs @@ -44,6 +44,33 @@ public async Task Missing_Trusted_SCL_And_Wrong_MMS_Endpoint_Does_Not_Guess() temp.Device,Model("BCUGEF650"))); } + + [Fact] + public async Task Scl_Identity_Requires_The_Selected_AccessPoint() + { + using var temp=TempScl.Create("STATIONBCU","CTRL","192.0.2.20"); + temp.Device.SclAccessPointName="OTHER_AP"; + Assert.Null(await TrustedSclIdentityAuthority.TryMatchAsync( + temp.Device,Model("STATIONBCUCTRL"))); + } + + [Fact] + public async Task Five_Live_Domains_With_Verified_Scl_Preserve_Exact_Ied_And_Original_Mms_Refs() + { + using var temp=TempScl.Create("SIEBCU","CTRL","192.0.2.20", + "MEAS","PROT","UD1","UD2"); + var domains=new[] {"SIEBCUCTRL","SIEBCUMEAS","SIEBCUPROT","SIEBCUUD1","SIEBCUUD2"}; + var model=Model(domains); + var verified=await TrustedSclIdentityAuthority.TryMatchAsync(temp.Device,model); + Assert.NotNull(verified); + Assert.Equal("SIEBCU",verified.IedName); + Assert.Equal(LiveIedDiscoveryConfidenceLevel.High,verified.Confidence); + var consumer=TrustedSclIdentityAuthority.ToConsumerIdentity(verified,domains); + Assert.Equal("SIEBCU",consumer.IedName); + Assert.Equal(domains,consumer.LogicalDevices.Select(d=>d.Domain).ToArray()); + Assert.Equal("UD2",consumer.LogicalDevices.Last().Instance); + } + [Fact] public void Canonical_Save_And_RCB_Export_Must_Consume_Verified_Identity() { @@ -60,10 +87,10 @@ public void Canonical_Save_And_RCB_Export_Must_Consume_Verified_Identity() Assert.Contains("TrustedSclIdentityAuthority.ToConsumerIdentity",runtime); } - private static LiveIedModelDiscoveryDocument Model(string domain) => new() + private static LiveIedModelDiscoveryDocument Model(params string[] domains) => new() { Host="192.16.1.33",IedName="IED_192_16_1_33", - LogicalDevices=[new LiveIedLogicalDeviceModel{MmsDomain=domain,Inst=domain}] + LogicalDevices=domains.Select(domain=>new LiveIedLogicalDeviceModel{MmsDomain=domain,Inst=domain}).ToArray() }; private sealed class TempScl : IDisposable @@ -74,15 +101,17 @@ private TempScl(string folder,Iec61850MonitorDevice device) { _folder=folder;Device=device; } - public static TempScl Create(string ied,string ld,string ip) + public static TempScl Create(string ied,string ld,string ip,params string[] otherLdInstances) { var dir=Path.Combine(Path.GetTempPath(),"arsas-ied-p10-"+Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); var path=Path.Combine(dir,"engineering.iid"); + var remaining=string.Concat(otherLdInstances.Select(inst=> + $"")); var xml=$"""

{ip}

- + {remaining}
"""; File.WriteAllText(path,xml); From d1625fb6b99e8b25b02bcebe8f44b557a58d2e62 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:03:36 +0700 Subject: [PATCH 02/12] test(identity): use valid C# raw interpolated XML fixture --- tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs b/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs index 4c775c7ae..10ed1f84b 100644 --- a/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs +++ b/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs @@ -107,7 +107,7 @@ public static TempScl Create(string ied,string ld,string ip,params string[] othe Directory.CreateDirectory(dir); var path=Path.Combine(dir,"engineering.iid"); var remaining=string.Concat(otherLdInstances.Select(inst=> - $"")); + $"""""")); var xml=$"""

{ip}

From 0c931e987729f822265e908bb27c6c9e5b047ad8 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:03:58 +0700 Subject: [PATCH 03/12] fix(identity): verify selected SCL access point before consumer authority promotion --- Services/TrustedSclIdentityAuthority.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Services/TrustedSclIdentityAuthority.cs b/Services/TrustedSclIdentityAuthority.cs index 2e894f5c0..a5795c14a 100644 --- a/Services/TrustedSclIdentityAuthority.cs +++ b/Services/TrustedSclIdentityAuthority.cs @@ -29,7 +29,8 @@ public static class TrustedSclIdentityAuthority source, liveModel.LogicalDevices.Select(ld => ld.MmsDomain), device.IpAddress, - device.SclIedName); + device.SclIedName, + device.SclAccessPointName); } /// @@ -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) From afc0d4589339d9642adae7cc9e9338718b26914f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:07:41 +0700 Subject: [PATCH 04/12] build(identity): pin P0-A trial engine exact SHA on isolated consumer branch --- engines/ARIEC61850.lock.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/engines/ARIEC61850.lock.json b/engines/ARIEC61850.lock.json index 8cc894215..bdde399cc 100644 --- a/engines/ARIEC61850.lock.json +++ b/engines/ARIEC61850.lock.json @@ -2,9 +2,9 @@ "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": "a662834be11227b1872df011551029fbc24e9e81", + "sourcePullRequest": 177, + "purpose": "P0-A isolated generically inferred/provisional IED identity and SHA-pinned SCL exact AP/endpoint/domain verification; engine #177 exact-SHA Windows verification required. No changes to report/control, no physical field acceptance.", "previousTrialPin": { "commit": "4467124775d8d9d76f3db194f9fbfd97144767a8", "sourcePullRequest": 134, @@ -270,5 +270,10 @@ "physicalQualificationRequired": true, "mainlinePromotionForbidden": true, "cyclicMmsProcessPollingForbidden": true + }, + "previousIdentityTrialPin": { + "commit": "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da", + "sourcePullRequest": 173, + "purpose": "M2.17-K typed ST report baseline retained for independent identity trial and rollback." } } From 68e607aa895e9047a850b30fe1042ef76f75609e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:16:41 +0700 Subject: [PATCH 05/12] build(identity): record exact P0-A code-only trial ancestry and physical guard --- engines/ARIEC61850.lock.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/engines/ARIEC61850.lock.json b/engines/ARIEC61850.lock.json index bdde399cc..3e4fc588a 100644 --- a/engines/ARIEC61850.lock.json +++ b/engines/ARIEC61850.lock.json @@ -2,9 +2,9 @@ "schemaVersion": 1, "repository": "masarray/ARIEC61850", "ref": "main", - "commit": "a662834be11227b1872df011551029fbc24e9e81", + "commit": "ccf31f60095ea65f9095514f0fa0102060962049", "sourcePullRequest": 177, - "purpose": "P0-A isolated generically inferred/provisional IED identity and SHA-pinned SCL exact AP/endpoint/domain verification; engine #177 exact-SHA Windows verification required. No changes to report/control, no physical field acceptance.", + "purpose": "P0-A identity code-only trial: exact case-sensitive MMS domain identity, provisional heuristics, SHA-pinned SCL AP/endpoint/domain evidence. Engine #177 Windows CI pending final verification; no physical promotion.", "previousTrialPin": { "commit": "4467124775d8d9d76f3db194f9fbfd97144767a8", "sourcePullRequest": 134, @@ -274,6 +274,20 @@ "previousIdentityTrialPin": { "commit": "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da", "sourcePullRequest": 173, - "purpose": "M2.17-K typed ST report baseline retained for independent identity trial and rollback." + "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." + }, + "p0AIdentityTrial": { + "status": "code-trial-not-physical", + "baseEngineCommit": "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da", + "exactCommit": "ccf31f60095ea65f9095514f0fa0102060962049", + "enginePullRequest": 177, + "consumerPullRequest": 624, + "engineCiRun": 39228439974, + "exactDomainCaseRequired": true, + "sclShaPinnedRequired": true, + "exactEndpointAndAccessPointRequired": true, + "discoveryWritesForbidden": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true } } From c5565ccb770df377608ec8adb7da32a162563093 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:17:09 +0700 Subject: [PATCH 06/12] ci(identity): allow exact ancestry-verified P0-A trial without granting physical promotion --- scripts/test-m217k-typed-status-trial.ps1 | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/scripts/test-m217k-typed-status-trial.ps1 b/scripts/test-m217k-typed-status-trial.ps1 index ecf716301..a9683cc1a 100644 --- a/scripts/test-m217k-typed-status-trial.ps1 +++ b/scripts/test-m217k-typed-status-trial.ps1 @@ -10,6 +10,42 @@ function Test-M217KTypedStatusTrial { $previous = '036e2c00d83a0815b6160147b14ea05975057965' $candidate = '51c2eefd61d6bcbe1116fda6b781b74bb9cf06da' + # P0-A is a descendant of the exact M2.17-K code trial, never a replacement + # for the historical physical authority. Re-run the original unmodified + # M2.17-K guard against its preserved pin before admitting one exact child. + if ([int]$Lock.sourcePullRequest -eq 177) { + $identity = $Lock.p0AIdentityTrial + $saved = $Lock.previousIdentityTrialPin + if ($null -eq $identity -or $null -eq $saved) { return $false } + $historicalView = $Lock | Select-Object * + $historicalView.commit = $candidate + $historicalView.sourcePullRequest = 173 + $historicalView.purpose = [string]$saved.purpose + $parentValid = Test-M217KTypedStatusTrial -Lock $historicalView + return ( + $parentValid -and + [string]$Lock.repository -ceq 'masarray/ARIEC61850' -and + [string]$Lock.ref -ceq 'main' -and + [string]$saved.commit -ceq $candidate -and + [int]$saved.sourcePullRequest -eq 173 -and + [string]$saved.purpose -match '^M2.17-K isolated C264 acceptance trial' -and + [string]$identity.baseEngineCommit -ceq $candidate -and + [string]$identity.exactCommit -ceq 'ccf31f60095ea65f9095514f0fa0102060962049' -and + [string]$Lock.commit -ceq [string]$identity.exactCommit -and + [string]$Lock.purpose -match '^P0-A identity code-only trial' -and + [int]$identity.enginePullRequest -eq 177 -and + [int]$identity.consumerPullRequest -eq 624 -and + [long]$identity.engineCiRun -eq 39228439974 -and + [string]$identity.status -ceq 'code-trial-not-physical' -and + [bool]$identity.exactDomainCaseRequired -and + [bool]$identity.sclShaPinnedRequired -and + [bool]$identity.exactEndpointAndAccessPointRequired -and + [bool]$identity.discoveryWritesForbidden -and + [bool]$identity.physicalQualificationRequired -and + [bool]$identity.mainlinePromotionForbidden + ) + } + # Audit the COMPLETE previously accepted P10.9V code-only ancestry, # including physical R10 history, routing, subscriber integrity, canonical # ingress, ZIV BSC, commissioning and typed control gates. A new head From 99ca775fcc29d0fc332e71361d24ef7ac9c1ce73 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:17:50 +0700 Subject: [PATCH 07/12] test(identity): enforce exact P0-A trial lineage while preserving historic engine authority --- ...martInteroperabilityTrialGuardRegressionTests.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/ARSAS.Tests/SmartInteroperabilityTrialGuardRegressionTests.cs b/tests/ARSAS.Tests/SmartInteroperabilityTrialGuardRegressionTests.cs index 0225ca225..1148031ab 100644 --- a/tests/ARSAS.Tests/SmartInteroperabilityTrialGuardRegressionTests.cs +++ b/tests/ARSAS.Tests/SmartInteroperabilityTrialGuardRegressionTests.cs @@ -118,9 +118,14 @@ public void TrialLock_PreservesHistoricalPhysicalAuthorityAndExplicitlyRequiresP typedStatusTrial.GetProperty("previousConsumerPin").GetString()); Assert.Equal("51c2eefd61d6bcbe1116fda6b781b74bb9cf06da", typedStatusTrial.GetProperty("exactCommit").GetString()); - Assert.Equal(typedStatusTrial.GetProperty("exactCommit").GetString(), - root.GetProperty("commit").GetString()); - Assert.Equal(173, root.GetProperty("sourcePullRequest").GetInt32()); + var identityTrial = root.GetProperty("p0AIdentityTrial"); + Assert.Equal(typedStatusTrial.GetProperty("exactCommit").GetString(), identityTrial.GetProperty("baseEngineCommit").GetString()); + Assert.Equal(typedStatusTrial.GetProperty("exactCommit").GetString(), root.GetProperty("previousIdentityTrialPin").GetProperty("commit").GetString()); + Assert.Equal(identityTrial.GetProperty("exactCommit").GetString(), root.GetProperty("commit").GetString()); + Assert.Equal("code-trial-not-physical", identityTrial.GetProperty("status").GetString()); + Assert.True(identityTrial.GetProperty("physicalQualificationRequired").GetBoolean()); + Assert.True(identityTrial.GetProperty("mainlinePromotionForbidden").GetBoolean()); + 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()); @@ -168,7 +173,7 @@ public void TrialLock_PreservesHistoricalPhysicalAuthorityAndExplicitlyRequiresP 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()); + 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()); From 19e03f5211cfec837db9c4debf15c11554fe9a8d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:17:54 +0700 Subject: [PATCH 08/12] test(identity): enforce exact P0-A trial lineage while preserving historic engine authority --- .../CanonicalLiveSclExportRegressionTests.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs b/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs index e3351a288..c518cac56 100644 --- a/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs +++ b/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs @@ -474,9 +474,14 @@ public void EngineLock_PreservesPhysicalSclRepairBaselineAcrossCodeVerifiedInter typedStatusTrial.GetProperty("previousConsumerPin").GetString()); Assert.Equal("51c2eefd61d6bcbe1116fda6b781b74bb9cf06da", typedStatusTrial.GetProperty("exactCommit").GetString()); - Assert.Equal(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("code-trial-not-physical", identityTrial.GetProperty("status").GetString()); + Assert.True(identityTrial.GetProperty("physicalQualificationRequired").GetBoolean()); + Assert.True(identityTrial.GetProperty("mainlinePromotionForbidden").GetBoolean()); + 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()); @@ -520,7 +525,7 @@ public void EngineLock_PreservesPhysicalSclRepairBaselineAcrossCodeVerifiedInter 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()); + 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()); From a928966a23627d341be15d00d6365a69c09af88a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:17:57 +0700 Subject: [PATCH 09/12] test(identity): enforce exact P0-A trial lineage while preserving historic engine authority --- .../M217KC264TypedStatusReportIntegrationTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/M217KC264TypedStatusReportIntegrationTests.cs b/tests/ARSAS.Tests/M217KC264TypedStatusReportIntegrationTests.cs index 431c34b23..04d31d84e 100644 --- a/tests/ARSAS.Tests/M217KC264TypedStatusReportIntegrationTests.cs +++ b/tests/ARSAS.Tests/M217KC264TypedStatusReportIntegrationTests.cs @@ -68,9 +68,13 @@ public void TestProductUsesExplicitlyApprovedEngineCommitNotPreviouslyPinnedDll( using var lockDocument = JsonDocument.Parse(File.ReadAllText( Path.Combine(root!.FullName, "engines", "ARIEC61850.lock.json"))); var lockRoot = lockDocument.RootElement; + var identityTrial = lockRoot.GetProperty("p0AIdentityTrial"); + Assert.Equal(ExpectedEngineSha, identityTrial.GetProperty("baseEngineCommit").GetString()); Assert.Equal(ExpectedEngineSha, + lockRoot.GetProperty("previousIdentityTrialPin").GetProperty("commit").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()); From 11a2e81229d73ef6502d81c51a6176122b724501 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:18:01 +0700 Subject: [PATCH 10/12] test(identity): enforce exact P0-A trial lineage while preserving historic engine authority --- .../P10MainlinePolicyExecutionTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/ARSAS.Tests/P10MainlinePolicyExecutionTests.cs b/tests/ARSAS.Tests/P10MainlinePolicyExecutionTests.cs index e4f1db0b9..dc8fef921 100644 --- a/tests/ARSAS.Tests/P10MainlinePolicyExecutionTests.cs +++ b/tests/ARSAS.Tests/P10MainlinePolicyExecutionTests.cs @@ -73,6 +73,11 @@ public sealed class P10MainlinePolicyExecutionTests [InlineData("p10-cyclic-fallback-denied", false)] [InlineData("p10-history-tampered-denied", false)] [InlineData("p10-dataset-lineage-tampered-denied", false)] + [InlineData("p0a-wrong-exact-sha-denied", false)] + [InlineData("p0a-wrong-parent-denied", false)] + [InlineData("p0a-wrong-ci-denied", false)] + [InlineData("p0a-wrong-pr-denied", false)] + [InlineData("p0a-physical-promote-denied", false)] [InlineData("m217k-exact-sha-denied", false)] [InlineData("m217k-engine-ci-denied", false)] [InlineData("m217k-previous-pin-denied", false)] @@ -124,6 +129,16 @@ public void Exact_CI_Gate_Fails_Closed_Without_False_Physical_Promotion( switch (scenario) { case "p10-pr-main-denied": baseRef = "main"; break; + case "p0a-wrong-exact-sha-denied": + lockDoc["p0AIdentityTrial"]!["exactCommit"] = new string('0',40); break; + case "p0a-wrong-parent-denied": + lockDoc["p0AIdentityTrial"]!["baseEngineCommit"] = new string('0',40); break; + case "p0a-wrong-ci-denied": + lockDoc["p0AIdentityTrial"]!["engineCiRun"] = 0; break; + case "p0a-wrong-pr-denied": + lockDoc["p0AIdentityTrial"]!["enginePullRequest"] = 0; break; + case "p0a-physical-promote-denied": + lockDoc["p0AIdentityTrial"]!["mainlinePromotionForbidden"] = false; break; case "m217k-exact-sha-denied": lockDoc["m217KTypedStatusReportTrial"]!["exactCommit"] = new string('0', 40); break; case "m217k-engine-ci-denied": From 8425f1537f561186a013c03822ffdb812b9ff745 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:27:37 +0700 Subject: [PATCH 11/12] test(identity): preserve two exact case-distinct MMS domains in ARSAS --- .../ARSAS.Tests/TrustedSclIdentityConsumerTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs b/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs index 10ed1f84b..03f59af0c 100644 --- a/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs +++ b/tests/ARSAS.Tests/TrustedSclIdentityConsumerTests.cs @@ -71,6 +71,20 @@ public async Task Five_Live_Domains_With_Verified_Scl_Preserve_Exact_Ied_And_Ori Assert.Equal("UD2",consumer.LogicalDevices.Last().Instance); } + + [Fact] + public async Task Verified_Identity_Preserves_Case_Distinct_Raw_Mms_Domains_In_Consumer() + { + using var temp=TempScl.Create("STATIONBCU","CTRL","192.0.2.20","ctrl"); + var domains=new[] {"STATIONBCUCTRL","STATIONBCUctrl"}; + var verified=await TrustedSclIdentityAuthority.TryMatchAsync(temp.Device,Model(domains)); + Assert.NotNull(verified); + var projected=TrustedSclIdentityAuthority.ToConsumerIdentity(verified,domains); + Assert.Equal(domains,projected.LogicalDevices.Select(x=>x.Domain).ToArray()); + Assert.Equal(new[] {"CTRL","ctrl"}, + projected.LogicalDevices.Select(x=>x.Instance).ToArray()); + } + [Fact] public void Canonical_Save_And_RCB_Export_Must_Consume_Verified_Identity() { From 77d72550c8763bfc487a56dbad0b3a9118186632 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 19 Oct 2026 16:27:42 +0700 Subject: [PATCH 12/12] fix(identity): use exact MMS domain equality in consumer projected LD set --- Services/TrustedSclIdentityAuthority.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/TrustedSclIdentityAuthority.cs b/Services/TrustedSclIdentityAuthority.cs index a5795c14a..6d7bd4726 100644 --- a/Services/TrustedSclIdentityAuthority.cs +++ b/Services/TrustedSclIdentityAuthority.cs @@ -124,7 +124,7 @@ public static Iec61850DeviceIdentity ToConsumerIdentity( { IedName=verified.IedName, Source=$"ARIEC61850 {verified.Source} ({verified.Confidence})", - LogicalDevices=domains.Distinct(StringComparer.OrdinalIgnoreCase).Select(domain => + LogicalDevices=domains.Distinct(StringComparer.Ordinal).Select(domain => new Iec61850LogicalDeviceIdentity { Domain=domain,