Skip to content
Closed
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
179 changes: 179 additions & 0 deletions .github/workflows/scl-export-only-semantic-candidate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: SCL Export-Only Semantic Candidate

on:
pull_request:
paths:
- "engines/ARIEC61850.lock.json"
- "evidence/scl-export-only-semantic-candidate.json"
- "tests/ARSAS.Tests/P07ReleaseCandidateLockRegressionTests.cs"
- "tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs"
- "tests/ARSAS.Tests/SclExportOnlySemanticIsolationRegressionTests.cs"
- ".github/workflows/scl-export-only-semantic-candidate.yml"
workflow_dispatch:

concurrency:
group: scl-export-only-semantic-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build-export-only-candidate:
runs-on: windows-latest
steps:
- name: Checkout exact ARSAS candidate
shell: pwsh
env:
ARSAS_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
git clone --quiet --filter=blob:none --no-checkout "https://github.com/$env:GITHUB_REPOSITORY.git" ARSAS
git -C .\ARSAS fetch --quiet --depth 1 origin $env:ARSAS_SOURCE_SHA
git -C .\ARSAS checkout --quiet --detach $env:ARSAS_SOURCE_SHA
$actual = (git -C .\ARSAS rev-parse HEAD).Trim().ToLowerInvariant()
if ($actual -ne $env:ARSAS_SOURCE_SHA.ToLowerInvariant()) { throw "ARSAS SHA mismatch." }
"ARSAS_COMMIT=$actual" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append

- name: Validate candidate contract
shell: pwsh
run: |
$baseline = Get-Content .\ARSAS\evidence\p0.7-release-candidate-lock.json -Raw | ConvertFrom-Json
$candidate = Get-Content .\ARSAS\evidence\scl-export-only-semantic-candidate.json -Raw | ConvertFrom-Json
$lock = Get-Content .\ARSAS\engines\ARIEC61850.lock.json -Raw | ConvertFrom-Json

if ($baseline.physicalReference.engineBaseline -ne '648124097621046f5f127ceb1cf853fea54db730' -or
[int]$baseline.acceptedRuntime.staticMembers -ne 58 -or
[int]$baseline.acceptedRuntime.selectedLiveRows -ne 58 -or
[int]$baseline.acceptedRuntime.analogRows -ne 22 -or
[int]$baseline.acceptedRuntime.digitalRows -ne 36 -or
[int]$baseline.acceptedRuntime.reportBackedRows -ne 58 -or
[int]$baseline.acceptedRuntime.cyclicMmsProcessPolling -ne 0) {
throw 'Frozen P0.7 baseline changed.'
}

if ($candidate.contractId -ne 'SCL-EXPORT-ONLY-SEMANTIC-P0' -or
[int]$candidate.replacementCandidate.enginePullRequest -ne 140 -or
$candidate.replacementCandidate.engineBase -ne '648124097621046f5f127ceb1cf853fea54db730' -or
$candidate.replacementCandidate.engineCommit -ne $lock.commit -or
-not [bool]$candidate.replacementCandidate.discoveryRuntimeSourceMustMatchPhysicalBaseline -or
[bool]$candidate.replacementCandidate.liveDiscoveryModelMutationAllowed -or
[bool]$candidate.replacementCandidate.acquisitionBehaviorChangeAllowed) {
throw 'Export-only semantic candidate contract invalid.'
}

if ([int]$lock.sourcePullRequest -ne 140) { throw 'Only engine PR #140 is permitted.' }
"ARIEC61850_REPOSITORY=$($lock.repository)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append
"ARIEC61850_COMMIT=$($lock.commit)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append
$version = (Get-Content .\ARSAS\VERSION -Raw).Trim()
"ARSAS_VERSION=$version" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append

- name: Checkout exact engine candidate and baseline
shell: pwsh
run: |
git clone --quiet --filter=blob:none --no-checkout "https://github.com/$env:ARIEC61850_REPOSITORY.git" ARIEC61850
git -C .\ARIEC61850 fetch --quiet --depth 1 origin $env:ARIEC61850_COMMIT
git -C .\ARIEC61850 fetch --quiet --depth 1 origin 648124097621046f5f127ceb1cf853fea54db730
git -C .\ARIEC61850 checkout --quiet --detach $env:ARIEC61850_COMMIT

- name: Prove engine is export-only
shell: pwsh
run: |
$base = '648124097621046f5f127ceb1cf853fea54db730'
$head = $env:ARIEC61850_COMMIT
$changed = @(git -C .\ARIEC61850 diff --name-only $base $head)
$expected = @(
'src/AR.Iec61850/Scl/Export/LiveIedSclExporter.cs',
'tests/AR.Iec61850.Tests/Scl/LiveIedSclExportOnlySemanticAuthorityTests.cs',
'src/AR.Iec61850/Discovery/Iec61850StandardModelRegistry.cs'
)
$unexpected = @($changed | Where-Object { $_ -notin $expected })
$missing = @($expected | Where-Object { $_ -notin $changed })
if ($unexpected.Count -gt 0 -or $missing.Count -gt 0 -or $changed.Count -ne 3) {
$changed | ForEach-Object { Write-Host $_ }
throw 'Engine candidate is not export-only plus the approved comment cleanup.'
}

$registryDiff = @(git -C .\ARIEC61850 diff --unified=0 $base $head -- src/AR.Iec61850/Discovery/Iec61850StandardModelRegistry.cs)
$registryChangedLines = @($registryDiff | Where-Object {
$_ -match '^[+-]' -and $_ -notmatch '^(---|\+\+\+)'
})
$registryCodeDelta = @($registryChangedLines | Where-Object {
$_ -notmatch '^[+-]\s*//'
})
if ($registryCodeDelta.Count -ne 0) {
$registryCodeDelta | ForEach-Object { Write-Host $_ }
throw 'StandardModelRegistry contains executable code drift; only source-clean comment wording is allowed.'
}

git -C .\ARIEC61850 diff --exit-code $base $head -- src/AR.Iec61850/Mms src/AR.Iec61850/Osi src/AR.Iec61850/Acse
if ($LASTEXITCODE -ne 0) { throw 'Wire/MMS source differs from physical baseline.' }

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Build and test engine
shell: pwsh
run: |
dotnet restore .\ARIEC61850\ARIEC61850.sln
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet build .\ARIEC61850\ARIEC61850.sln -c Release --no-restore
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet test .\ARIEC61850\ARIEC61850.sln -c Release --no-build --no-restore --logger "trx;LogFileName=engine-export-only.trx" --results-directory .\ARSAS\TestResults
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Build and test ARSAS
shell: pwsh
run: |
dotnet restore .\ARSAS\ArIED61850Tester.sln
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet build .\ARSAS\ArIED61850Tester.sln -c Release --no-restore
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet test .\ARSAS\tests\ARSAS.Tests\ARSAS.Tests.csproj -c Release --no-build --no-restore --logger "trx;LogFileName=arsas-export-only.trx" --results-directory .\ARSAS\TestResults
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Publish portable candidate
shell: pwsh
run: |
.\ARSAS\scripts\publish-windows-portable.ps1 `
-Version $env:ARSAS_VERSION `
-Runtime win-x64 `
-SingleFile $true `
-SelfContained $true `
-EngineProject "$env:GITHUB_WORKSPACE\ARIEC61850\src\AR.Iec61850\AR.Iec61850.csproj" `
-NpcapProject "$env:GITHUB_WORKSPACE\ARIEC61850\src\AR.Iec61850.Transports.Npcap\AR.Iec61850.Transports.Npcap.csproj"

- name: Smoke test and manifest
shell: pwsh
run: |
$exe = ".\ARSAS\dist\ARSAS-$env:ARSAS_VERSION-win-x64-portable.exe"
if (!(Test-Path $exe -PathType Leaf)) { throw "Portable EXE missing." }
$env:DOTNET_BUNDLE_EXTRACT_BASE_DIR = Join-Path $env:RUNNER_TEMP 'ARSAS-export-only-cache'
$process = Start-Process -FilePath $exe -ArgumentList @('--portable-smoke-test') -PassThru
if (-not $process.WaitForExit(30000)) {
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
throw 'Portable EXE smoke test timed out.'
}
if ($process.ExitCode -ne 0) { throw "Portable smoke test failed." }

@(
'ARSAS SCL export-only semantic physical-retest candidate',
"ARSAS commit: $env:ARSAS_COMMIT",
"Engine candidate: $env:ARIEC61850_COMMIT",
'Physical engine baseline: 648124097621046f5f127ceb1cf853fea54db730',
'Engine executable diff is LiveIedSclExporter.cs only; one regression test plus two source-clean comment lines are the only additional changes.',
'Discovery executable logic plus MMS/Osi/Acse source matches the physical baseline; the registry delta is comment-only.',
'Acceptance: discovery path/speed baseline, 58/58, analog 22, digital 36, report-backed 58, actual InformationReport, polling 0.',
'SCL target: CBClsCounter live runtime unchanged; saved/reopened SCL INS/INT32.'
) | Set-Content .\ARSAS\dist\SCL-EXPORT-ONLY-CANDIDATE.txt -Encoding utf8

- name: Upload candidate
uses: actions/upload-artifact@v4
with:
name: ARSAS-scl-export-only-semantic-win-x64
path: |
ARSAS\dist\ARSAS-*-win-x64-portable.exe
ARSAS\dist\SCL-EXPORT-ONLY-CANDIDATE.txt
ARSAS\evidence\p0.7-release-candidate-lock.json
ARSAS\evidence\scl-export-only-semantic-candidate.json
ARSAS\TestResults\*.trx
if-no-files-found: error
retention-days: 14
6 changes: 3 additions & 3 deletions engines/ARIEC61850.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"schemaVersion": 1,
"repository": "masarray/ARIEC61850",
"ref": "main",
"commit": "648124097621046f5f127ceb1cf853fea54db730",
"sourcePullRequest": 135,
"purpose": "R7 physical SCL repair pin on the complete PR #134 smart-discovery performance head. Accepted remote AP-title/AE/PSEL/SSEL remain decoded from the exact association request bytes accepted by the IED and TSEL remains bound to the accepted COTP destination selector. Physical AA1E1F06R4 evidence additionally proved that indexed RCB siblings may differ in writable/current BufTm, IntgPd, TrgOps and OptFlds while still representing one logical SCL ReportControl; engine head 648124097621046f5f127ceb1cf853fea54db730 preserves DataSet/ConfRev/domain/LN/buffered identity while allowing that runtime-mutable setting drift. ARSAS consumer-side reconnect must reproduce the proven native calling identity byte-for-byte and canonical save uses full-model SCL with prefixed-LN identity, CDC-aware WYE/DEL/SEQ SDO and FC ownership, plus exact standard TCTR/TVTR/LTIM/EEName/MltLev CDC authority and Edition-2-only LTRK service-tracking CDCs with Edition-1 schema downgrade protection; physical R8 reuse evidence additionally requires exact MMS TypeSpecification declaration order through canonical model/SCL reload, treats SG/SE as setting data rather than control blocks, and maps MHAI THD phase groups as WYE/CMV; safety is enforced by bounded FC-read policy rather than deleting discovered model leaves. Production promotion remains fail-closed and still requires fresh physical authority. R9 reuse lock additionally preserves rptID-backed preallocated singleton RCB indexing, case-distinct canonical instance values, and compile-safe logical RCB projection; unresolved 46 trusted-SCL projection errors remain a fail-open diagnostic gap but a fail-closed promotion gap until physical evidence reaches zero. P1 trusted-SCL projection repair removes cross-DO positional dependence for multi-DO CF structures: SCL LNodeType order is not treated as MMS FC-structure order, so CF hydration is split into exact DO-scoped structured Reads and remains batched/bounded. P0 structural discovery remains unchanged. P2 makes instance-value identity exact-case end-to-end: ARSAS trusted-SCL caching uses StringComparer.Ordinal and reports projectedUniqueValues/cacheLoss; engine TypeSpecification member resolution and canonical DO/DA instance-value targeting are case-sensitive so legal paths such as tracking t/T cannot collapse or cross-resolve. R10 physical acceptance passed on AA1E1F06R4: Ed2 and Ed1 both reached projectionErrors=0 and cacheLoss=0, all planned reads succeeded, 58/58 runtime points were report-backed, and actual InformationReport traffic was observed. The exact tested commit 9935d6902d786cc69b299260fe36b835944d5e81 and merged main commit 648124097621046f5f127ceb1cf853fea54db730 have the identical source tree 1cf7e08f333f24994625e8fe8416dbd0a16195b1.",
"commit": "32fae3a56b1e21874895c7e781923f324cd1e54e",
"sourcePullRequest": 140,
"purpose": "R7 physical SCL repair pin on the complete PR #134 smart-discovery performance head. Accepted remote AP-title/AE/PSEL/SSEL remain decoded from the exact association request bytes accepted by the IED and TSEL remains bound to the accepted COTP destination selector. Physical AA1E1F06R4 evidence additionally proved that indexed RCB siblings may differ in writable/current BufTm, IntgPd, TrgOps and OptFlds while still representing one logical SCL ReportControl; engine head 648124097621046f5f127ceb1cf853fea54db730 preserves DataSet/ConfRev/domain/LN/buffered identity while allowing that runtime-mutable setting drift. ARSAS consumer-side reconnect must reproduce the proven native calling identity byte-for-byte and canonical save uses full-model SCL with prefixed-LN identity, CDC-aware WYE/DEL/SEQ SDO and FC ownership, plus exact standard TCTR/TVTR/LTIM/EEName/MltLev CDC authority and Edition-2-only LTRK service-tracking CDCs with Edition-1 schema downgrade protection; physical R8 reuse evidence additionally requires exact MMS TypeSpecification declaration order through canonical model/SCL reload, treats SG/SE as setting data rather than control blocks, and maps MHAI THD phase groups as WYE/CMV; safety is enforced by bounded FC-read policy rather than deleting discovered model leaves. Production promotion remains fail-closed and still requires fresh physical authority. R9 reuse lock additionally preserves rptID-backed preallocated singleton RCB indexing, case-distinct canonical instance values, and compile-safe logical RCB projection; unresolved 46 trusted-SCL projection errors remain a fail-open diagnostic gap but a fail-closed promotion gap until physical evidence reaches zero. P1 trusted-SCL projection repair removes cross-DO positional dependence for multi-DO CF structures: SCL LNodeType order is not treated as MMS FC-structure order, so CF hydration is split into exact DO-scoped structured Reads and remains batched/bounded. P0 structural discovery remains unchanged. P2 makes instance-value identity exact-case end-to-end: ARSAS trusted-SCL caching uses StringComparer.Ordinal and reports projectedUniqueValues/cacheLoss; engine TypeSpecification member resolution and canonical DO/DA instance-value targeting are case-sensitive so legal paths such as tracking t/T cannot collapse or cross-resolve. R10 physical acceptance passed on AA1E1F06R4: Ed2 and Ed1 both reached projectionErrors=0 and cacheLoss=0, all planned reads succeeded, 58/58 runtime points were report-backed, and actual InformationReport traffic was observed. The exact tested commit 9935d6902d786cc69b299260fe36b835944d5e81 and merged main commit 648124097621046f5f127ceb1cf853fea54db730 have the identical source tree 1cf7e08f333f24994625e8fe8416dbd0a16195b1. SCL export-only semantic candidate PR #140 is pinned only on an isolated ARSAS candidate branch. It starts from the exact physical engine baseline 648124097621046f5f127ceb1cf853fea54db730 and changes no Discovery/MMS/reporting/runtime source; only LiveIedSclExporter plus export-only regression tests differ. Exact live stVal TypeSpecification may refine generic SPS/INS/ENS only while writing SCL; the live discovery model bound to runtime/reporting remains unchanged. PR #139 was physically rejected and closed after AA1E1F06R4 showed slow discovery and BIT STRING/Boolean report rejection with only 23 report-backed rows. The only additional Discovery-tree delta is two source-clean wording comments in Iec61850StandardModelRegistry.cs; executable registry logic is unchanged.",
"previousTrialPin": {
"commit": "4467124775d8d9d76f3db194f9fbfd97144767a8",
"sourcePullRequest": 134,
Expand Down
66 changes: 66 additions & 0 deletions evidence/scl-export-only-semantic-candidate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"schemaVersion": 1,
"contractId": "SCL-EXPORT-ONLY-SEMANTIC-P0",
"status": "ci-candidate-physical-retest-pending",
"physicalBaseline": {
"arsasHead": "0d0b9204d6637e3d62e2eee94000386ae43cd0e9",
"arsasLockHead": "6d6c5f6f022b66d93141ace361ed88d4a47ecbb9",
"engineCommit": "648124097621046f5f127ceb1cf853fea54db730",
"relay": "AA1E1F06R4",
"acceptedRuntime": {
"staticMembers": 58,
"liveRows": 58,
"analogRows": 22,
"digitalRows": 36,
"reportBackedRows": 58,
"cyclicMmsProcessPolling": 0
}
},
"rejectedCandidate": {
"enginePullRequest": 139,
"engineCommit": "090d81944791be5b690d24342f9262495eda1a09",
"arsasPullRequest": 342,
"arsasCommit": "32dc8542914d069e8fa49fe6ea6574fdaf950564",
"physicalObserved": {
"discoveryRegression": true,
"reportBackedRowsObserved": 23,
"repeatedBooleanBitStringRejections": true
},
"disposition": "physically-rejected-closed"
},
"replacementCandidate": {
"enginePullRequest": 140,
"engineCommit": "32fae3a56b1e21874895c7e781923f324cd1e54e",
"engineBase": "648124097621046f5f127ceb1cf853fea54db730",
"allowedEngineDiff": [
"src/AR.Iec61850/Scl/Export/LiveIedSclExporter.cs",
"tests/AR.Iec61850.Tests/Scl/LiveIedSclExportOnlySemanticAuthorityTests.cs",
"src/AR.Iec61850/Discovery/Iec61850StandardModelRegistry.cs"
],
"discoveryRuntimeSourceMustMatchPhysicalBaseline": true,
"liveDiscoveryModelMutationAllowed": false,
"acquisitionBehaviorChangeAllowed": false,
"commentOnlyDiscoveryDelta": "Iec61850StandardModelRegistry.cs has only two comment wording replacements required by source-clean; executable registry logic is unchanged."
},
"semanticTarget": {
"object": "CBClsCounter",
"liveRuntimeCdcRemains": "SPS",
"exportedSclCdc": "INS",
"exportedStValBType": "INT32",
"reopenedSclCdc": "INS",
"reopenedStValBType": "INT32"
},
"physicalAcceptance": {
"discoveryMustMatchAcceptedBaseline": true,
"staticMembers": 58,
"liveRows": 58,
"analogRows": 22,
"digitalRows": 36,
"reportBackedRows": 58,
"actualInformationReportRequired": true,
"cyclicMmsProcessPolling": 0,
"saveSclWhileMonitoringMustRemainNonDisruptive": true,
"reopenedSclLiveRows": 58,
"noBooleanBitStringRejectionRegression": true
}
}
Loading
Loading