Repository navigation
682 lines (633 loc) · 37.1 KB
/
Copy pathbuild.yml
File metadata and controls
682 lines (633 loc) · 37.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
name: Build ARSAS
on:
push:
branches: [ main ]
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
build-windows:
name: Build, test, validate and package Windows application
runs-on: windows-latest
steps:
- name: Checkout exact triggering ARSAS revision
uses: actions/checkout@v7
with:
path: ArIED61850Tester
ref: ${{ github.sha }}
persist-credentials: false
fetch-depth: 1
- name: Verify tested application revision
shell: powershell
run: |
$actual = (git -C .\ArIED61850Tester rev-parse HEAD).Trim().ToLowerInvariant()
$expected = $env:GITHUB_SHA.Trim().ToLowerInvariant()
if ($actual -ne $expected) {
throw "CI source revision mismatch. Expected $expected, checked out $actual."
}
"ARSAS_SOURCE_COMMIT=$actual" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "Testing and packaging exact ARSAS source $actual"
- name: Resolve immutable engine and native bridge locks
shell: powershell
run: |
$lockPath = ".\ArIED61850Tester\engines\ARIEC61850.lock.json"
if (!(Test-Path $lockPath)) {
throw "ARIEC61850 lock file was not found."
}
$lock = Get-Content $lockPath -Raw | ConvertFrom-Json
if ($lock.repository -notmatch '^[^/]+/[^/]+$' -or
$lock.ref -ne 'main' -or
$lock.commit -notmatch '^[0-9a-f]{40}$') {
throw "ARIEC61850 lock metadata is invalid."
}
$ardirecPath = ".\ArIED61850Tester\engines\ARDIREC.lock.json"
if (!(Test-Path $ardirecPath)) {
throw "ArdIrec lock file was not found."
}
$ardirec = Get-Content $ardirecPath -Raw | ConvertFrom-Json
if ($ardirec.schema -ne 3 -or
$ardirec.repository -notmatch '^[^/]+/[^/]+$' -or
$ardirec.ref -notmatch '^[A-Za-z0-9._/-]+$' -or
$ardirec.commit -notmatch '^[0-9a-f]{40}$' -or
$ardirec.bridge.abi -ne 1 -or
$ardirec.bridge.mode -ne 'native-only' -or
$ardirec.bridge.relativeLibrary -ne 'Tools/ArdIrec/ardirec_bridge.dll') {
throw "ArdIrec native bridge lock metadata is invalid."
}
"ARIEC61850_REPOSITORY=$($lock.repository)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"ARIEC61850_REF=$($lock.ref)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"ARIEC61850_COMMIT=$($lock.commit)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"ARDIREC_REPOSITORY=$($ardirec.repository)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"ARDIREC_REF=$($ardirec.ref)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"ARDIREC_COMMIT=$($ardirec.commit)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Verify source, website and license boundaries
shell: powershell
run: .\ArIED61850Tester\scripts\verify-source-clean.ps1
- name: Verify source-clean rejection fixtures
shell: powershell
run: .\ArIED61850Tester\scripts\test-source-clean-guard.ps1
- name: Validate Windows packaging PowerShell syntax
shell: powershell
run: |
$scripts = @(
'.\ArIED61850Tester\scripts\build-ardirec-bridge.ps1',
'.\ArIED61850Tester\scripts\publish-windows-portable.ps1',
'.\ArIED61850Tester\scripts\build-windows-installer.ps1'
)
foreach ($script in $scripts) {
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
$script, [ref]$tokens, [ref]$errors) | Out-Null
if ($errors.Count -ne 0) {
$messages = ($errors | ForEach-Object Message) -join '; '
throw "PowerShell syntax validation failed for ${script}: $messages"
}
}
- name: Set up Python for read-only maintenance inventory
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Test and capture workflow-script consumer inventory
shell: powershell
run: |
python .\ArIED61850Tester\scripts\test-maintenance-dependencies.py
if ($LASTEXITCODE -ne 0) { throw "Maintenance inventory tests failed." }
python .\ArIED61850Tester\scripts\test-verify-p10-ge-field-scl.py
if ($LASTEXITCODE -ne 0) { throw "P10 GE trusted-IID SCL field gate fixture regressions failed." }
python .\ArIED61850Tester\scripts\test-compare-p10-brcb-sessions.py
if ($LASTEXITCODE -ne 0) { throw "P10.8D exact-slot BRCB comparator regressions failed." }
python .\ArIED61850Tester\scripts\audit-maintenance-dependencies.py --root .\ArIED61850Tester --output "$env:RUNNER_TEMP\arsas-maintenance-dependencies.json"
if ($LASTEXITCODE -ne 0) { throw "Maintenance inventory generation failed." }
- name: Upload maintenance dependency inventory
uses: actions/upload-artifact@v7
with:
name: ARSAS-maintenance-dependency-inventory
path: ${{ runner.temp }}/arsas-maintenance-dependencies.json
if-no-files-found: error
- name: Verify premium UX, GOOSE, SMV, SAS and release invariants
shell: powershell
run: |
$app = Get-Content .\ArIED61850Tester\App.xaml -Raw
$main = Get-Content .\ArIED61850Tester\MainWindow.xaml -Raw
$code = Get-Content .\ArIED61850Tester\MainWindow.xaml.cs -Raw
$save = Get-Content .\ArIED61850Tester\SaveSclWindow.xaml -Raw
$saveCode = Get-Content .\ArIED61850Tester\SaveSclWindow.xaml.cs -Raw
$project = Get-Content .\ArIED61850Tester\ArIED61850Tester.csproj -Raw
$versionProps = Get-Content .\ArIED61850Tester\Directory.Build.props -Raw
$versionFile = (Get-Content .\ArIED61850Tester\VERSION -Raw).Trim()
$publish = Get-Content .\ArIED61850Tester\scripts\publish-windows-portable.ps1 -Raw
$manifest = Get-Content .\ArIED61850Tester\app.manifest -Raw
$sasPolicy = Get-Content .\ArIED61850Tester\Services\SasOperationalSignalPolicy.cs -Raw
$sasUi = Get-Content .\ArIED61850Tester\SasOperationalUiPolicy.cs -Raw
$sclMapper = Get-Content .\ArIED61850Tester\Services\SclWorkspaceSignalMapper.cs -Raw
$gooseUi = Get-Content .\ArIED61850Tester\MainWindow.GooseSubscriber.cs -Raw
$gooseRuntime = Get-Content .\ArIED61850Tester\Services\GooseSubscriberRuntime.cs -Raw
$gooseModels = Get-Content .\ArIED61850Tester\Models\GooseSubscriberModels.cs -Raw
$smvUi = Get-Content .\ArIED61850Tester\SmvViewerWindow.xaml -Raw
$smvCode = Get-Content .\ArIED61850Tester\SmvViewerWindow.xaml.cs -Raw
$smvHardening = Get-Content .\ArIED61850Tester\SmvViewerWindow.P0Hardening.cs -Raw
$smvRuntime = Get-Content .\ArIED61850Tester\Services\SmvSnapshotCaptureService.cs -Raw
$smvSafety = Get-Content .\ArIED61850Tester\Services\SmvSnapshotSafetyAssessment.cs -Raw
$smvSelection = Get-Content .\ArIED61850Tester\Services\SmvSnapshotSelectionIdentity.cs -Raw
$tests = Get-Content .\ArIED61850Tester\tests\ARSAS.Tests\SmvSnapshotSafetyTests.cs -Raw
if ($save -notmatch 'x:Name="EditionIndicator"' -or
$save -notmatch 'Content="Edition 2"' -or
$save -notmatch 'Content="Edition 1"' -or
$save -match '<ComboBox' -or
$save -match 'Schema V3\.1' -or
$save -match 'Schema V1\.6') {
throw "Save SCL segmented Edition 2 / Edition 1 selector regressed."
}
$itemStyleMatch = [regex]::Match(
$main,
'<ListBox\.ItemContainerStyle>(?<body>.*?)</ListBox\.ItemContainerStyle>',
[System.Text.RegularExpressions.RegexOptions]::Singleline)
if (-not $itemStyleMatch.Success) {
throw "IED card item-container style was not found."
}
$itemStyle = $itemStyleMatch.Groups['body'].Value
if ($itemStyle -match 'ScaleTransform' -or $itemStyle -match 'RenderTransform') {
throw "IED card interaction must not resize or transform the card."
}
if ($main -notmatch 'Content="\{Binding ControlPendingConfirmationLabel\}"' -or
$main -match 'Content="Confirm Open"' -or
$main -match 'Content="Confirm Close"' -or
$main -notmatch 'ScrollViewer.HorizontalScrollBarVisibility="Disabled"' -or
$main -notmatch 'CanUserResizeColumns="False"') {
throw "Atomic Confirm or Command Panel overflow protection regressed."
}
if ($main -notmatch 'WorkflowNavShell" Width="760" Height="56"' -or
$main -notmatch 'x:Name="NavGooseButton"' -or
$main -notmatch '<TabItem Header="GOOSE Subscriber">' -or
$main -notmatch 'ItemsSource="\{Binding SelectedGooseStream.Leaves\}"' -or
$main -notmatch 'Header="confRev"' -or
$main -notmatch 'Header="Flags"' -or
$main -notmatch 'Header="Diagnostics" Binding="\{Binding DiagnosticsSummary\}"' -or
$main -notmatch '<ColumnDefinition Width="56"/>' -or
$main -notmatch '<Viewbox Width="50" Height="50" Stretch="Uniform"' -or
$main -notmatch 'x:Name="MonitorStateBadge"' -or
$main -match '<Control\.Effect><DropShadowEffect BlurRadius="5" ShadowDepth="1" Opacity="0\.22" Color="#26313B"/></Control\.Effect>' -or
$main -notmatch 'LucideSquare.*Stroke="\{StaticResource Warning\}"' -or
$main -notmatch 'LucideX.*Stroke="\{StaticResource Danger\}"' -or
$app -notmatch 'x:Key="CommandOpenGradient"' -or
$app -notmatch 'x:Key="CommandCloseGradient"' -or
$app -notmatch 'x:Key="CommandActionButtonBase"' -or
$app -notmatch '<Setter Property="Foreground" Value="White"/>' -or
$app -notmatch 'x:Key="IedIconButton"[\s\S]*?<Setter Property="Background" Value="Transparent"/>' -or
$app -notmatch 'x:Key="IedIconButton"[\s\S]*?<Setter Property="BorderBrush" Value="Transparent"/>') {
throw "Direct navbar, IED-card action, relay-icon, command-action, or GOOSE-tab visual invariants regressed."
}
if ($gooseRuntime -notmatch 'NpcapProcessBusFrameSource' -or
$gooseRuntime -notmatch 'ProcessBusStreamMonitor' -or
$gooseRuntime -notmatch 'GooseFrameParser.TryParseEthernetFrame' -or
$gooseRuntime -notmatch 'ether proto 0x88b8' -or
$gooseUi -notmatch 'BuildGooseBindingCatalog' -or
$gooseUi -notmatch 'SclWorkspace' -or
$gooseUi -notmatch 'LiveDiscoveryModel' -or
$gooseUi -notmatch 'Select\(\(entry, position\)' -or
$gooseUi -notmatch 'Select\(\(member, position\)' -or
$gooseUi -notmatch 'FirstNonEmpty' -or
$gooseUi -notmatch 'GOOSE APPID differs from model' -or
$gooseUi -notmatch 'GOOSE confRev differs from model' -or
$gooseUi -match 'signal\?\.Category' -or
$gooseModels -notmatch 'class GooseLeafValueRow' -or
$gooseModels -notmatch 'public int Order' -or
$gooseModels -notmatch 'FlagsText' -or
$project -notmatch 'ArIec61850NpcapProject' -or
$project -notmatch 'AR.Iec61850.Transports.Npcap') {
throw "SCL/discovery-aware GOOSE Subscriber runtime, canonical leaf order, or semantic metadata binding regressed."
}
if ($smvUi -notmatch 'Content="Capture 2 cycles"' -or
$smvUi -notmatch 'x:Name="StreamGrid"' -or
$smvUi -notmatch 'ElementName=CaptureButton' -or
$smvUi -notmatch 'P0StreamGrid_SelectionChanged' -or
$smvUi -notmatch 'SnapshotContinuityEvidenceText' -or
$smvUi -notmatch 'x:Name="WaveformCanvas"' -or
$smvCode -notmatch 'SmvSnapshotCaptureService' -or
$smvCode -notmatch 'smpCnt' -or
$smvCode -notmatch 'Raw lanes remain semantically unresolved' -or
$smvHardening -notmatch 'SmvSnapshotSafetyAssessment.IsCleanProof' -or
$smvHardening -notmatch 'SmvSnapshotSelectionIdentity' -or
$smvHardening -notmatch 'publisher restart' -or
$smvSafety -notmatch 'RestartTransitions > 0' -or
$smvSelection -notmatch 'public sealed record SmvSnapshotSelectionIdentity' -or
$tests -notmatch 'AnyCounterDiscontinuity_BlocksCleanProof' -or
$tests -notmatch 'SelectionIdentity_RejectsDifferentStream' -or
$smvRuntime -notmatch 'SampledValuesFrameParser.TryParseEthernetFrame' -or
$smvRuntime -notmatch 'SvGenericAsduInspector' -or
$smvRuntime -notmatch 'SvSampleCounterTracker' -or
$smvRuntime -notmatch 'SvTimebaseResolver' -or
$smvRuntime -notmatch 'ether proto 0x88ba') {
throw "Bounded two-cycle SMV P0 safety, selection lock, raw waveform, timebase, or continuity evidence regressed."
}
if ($sasPolicy -notmatch 'public static bool IsVisible' -or
$sasPolicy -notmatch 'IsOperationalProtectionLeaf' -or
$sasPolicy -notmatch 'IsFundamentalMeasurement' -or
$sasPolicy -notmatch 'IsUnprovenSyntheticCandidate' -or
$sasPolicy -notmatch 'dataObject is "mod" or "beh"') {
throw "Typed SAS operational signal policy is incomplete."
}
if ($sasUi -notmatch 'ModuleInitializer' -or
$sasUi -notmatch 'ApplyBallisticNavigation' -or
$sasUi -notmatch 'legacyPill.Visibility = Visibility.Collapsed' -or
$sasUi -notmatch 'shell.Width = 760' -or
$sasUi -notmatch 'shell.Height = 56' -or
$sasUi -notmatch 'NavGooseButton' -or
$sasUi -notmatch '#D8E2F0' -or
$sasUi -notmatch 'SasOperationalSignalPolicy.IsVisible' -or
$sclMapper -notmatch 'SasOperationalSignalPolicy.IsVisible') {
throw "Operator signal pruning or ballistic navigation policy is not installed."
}
[xml]$projectXml = $project
[xml]$versionXml = $versionProps
$releaseVersion = [string]$versionXml.Project.PropertyGroup.Version
$assemblyVersion = [string]$versionXml.Project.PropertyGroup.AssemblyVersion
$fileVersion = [string]$versionXml.Project.PropertyGroup.FileVersion
$projectVersion = [string]$projectXml.Project.PropertyGroup.Version
if ($releaseVersion -notmatch '^\d+\.\d+\.\d+$' -or
$assemblyVersion -ne "$releaseVersion.0" -or
$fileVersion -ne "$releaseVersion.0" -or
$projectVersion -ne $releaseVersion -or
$versionFile -ne $releaseVersion -or
$publish -notmatch 'Directory.Build.props' -or
$publish -notmatch 'PublishSingleFile' -or
$publish -notmatch 'IncludeNativeLibrariesForSelfExtract=true' -or
$publish -notmatch 'IncludeAllContentForSelfExtract=true' -or
$publish -notmatch 'PublishTrimmed=false' -or
$publish -notmatch 'portable.exe' -or
$manifest -notmatch 'requestedExecutionLevel level="asInvoker"') {
throw "Release metadata is not aligned to canonical version $releaseVersion."
}
"ARSAS_VERSION=$releaseVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Upload source snapshot
uses: actions/upload-artifact@v7
with:
name: ARSAS-source-snapshot
path: |
ArIED61850Tester/App.xaml
ArIED61850Tester/MainWindow.xaml
ArIED61850Tester/MainWindow.xaml.cs
ArIED61850Tester/MainWindow.CommandPanelUx.cs
ArIED61850Tester/MainWindow.ControlDiagnostics.cs
ArIED61850Tester/MainWindow.GooseSubscriber.cs
ArIED61850Tester/SmvViewerWindow.xaml
ArIED61850Tester/SmvViewerWindow.xaml.cs
ArIED61850Tester/SmvViewerWindow.P0Hardening.cs
ArIED61850Tester/SaveSclWindow.xaml
ArIED61850Tester/SaveSclWindow.xaml.cs
ArIED61850Tester/GridUxBehavior.cs
ArIED61850Tester/Directory.Build.props
ArIED61850Tester/VERSION
ArIED61850Tester/engines
ArIED61850Tester/Models
ArIED61850Tester/Services
ArIED61850Tester/tests
ArIED61850Tester/docs
ArIED61850Tester/landing
- name: Checkout immutable ARIEC61850 engine revision
shell: powershell
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 checkout --quiet --detach $env:ARIEC61850_COMMIT
$actual = (git -C .\ARIEC61850 rev-parse HEAD).Trim()
if ($actual -ne $env:ARIEC61850_COMMIT) {
throw "ARIEC61850 pin mismatch. Expected $env:ARIEC61850_COMMIT, got $actual."
}
- name: Checkout immutable ArdIrec native bridge revision
shell: powershell
run: |
git clone --quiet --filter=blob:none --no-checkout "https://github.com/$env:ARDIREC_REPOSITORY.git" ArdIrec
if ($LASTEXITCODE -ne 0) { throw "Could not clone pinned ArdIrec repository." }
git -C .\ArdIrec fetch --quiet --depth 1 origin $env:ARDIREC_COMMIT
if ($LASTEXITCODE -ne 0) { throw "Could not fetch pinned ArdIrec revision." }
git -C .\ArdIrec checkout --quiet --detach $env:ARDIREC_COMMIT
if ($LASTEXITCODE -ne 0) { throw "Could not checkout pinned ArdIrec revision." }
$actual = (git -C .\ArdIrec rev-parse HEAD).Trim().ToLowerInvariant()
if ($actual -ne $env:ARDIREC_COMMIT) {
throw "ArdIrec pin mismatch. Expected $env:ARDIREC_COMMIT, got $actual."
}
- name: Verify required ARIEC61850 APIs
shell: powershell
run: |
$control = ".\ARIEC61850\src\AR.Iec61850\Control\Iec61850ControlService.cs"
$models = ".\ARIEC61850\src\AR.Iec61850\Control\Iec61850ControlModels.cs"
$workspace = ".\ARIEC61850\src\AR.Iec61850\Scl\Workspace\SclWorkspaceService.cs"
$workspaceModels = ".\ARIEC61850\src\AR.Iec61850\Scl\Workspace\SclWorkspaceModels.cs"
$sclModels = ".\ARIEC61850\src\AR.Iec61850\Scl\SclModels.cs"
$sclExporter = ".\ARIEC61850\src\AR.Iec61850\Scl\Export\LiveIedSclExporter.cs"
$schemaProfiles = ".\ARIEC61850\src\AR.Iec61850\Scl\Export\SclSchemaProfiles.cs"
$interoperableConverter = ".\ARIEC61850\src\AR.Iec61850\Scl\Export\InteroperableSclConverter.cs"
$gooseParser = ".\ARIEC61850\src\AR.Iec61850\Goose\GooseFrameParser.cs"
$gooseDecoded = ".\ARIEC61850\src\AR.Iec61850\Goose\GooseDecodedValue.cs"
$processBusMonitor = ".\ARIEC61850\src\AR.Iec61850\Monitoring\ProcessBusStreamMonitor.cs"
$svParser = ".\ARIEC61850\src\AR.Iec61850\SampledValues\SampledValuesFrameParser.cs"
$svInspector = ".\ARIEC61850\src\AR.Iec61850\SampledValues\Analysis\SvGenericAsduInspector.cs"
$svCounter = ".\ARIEC61850\src\AR.Iec61850\SampledValues\Measurements\SvSampleCounterTracker.cs"
$svTimebase = ".\ARIEC61850\src\AR.Iec61850\SampledValues\Measurements\SvTimebaseResolver.cs"
$npcapSource = ".\ARIEC61850\src\AR.Iec61850.Transports.Npcap\NpcapProcessBusFrameSource.cs"
$npcapProject = ".\ARIEC61850\src\AR.Iec61850.Transports.Npcap\AR.Iec61850.Transports.Npcap.csproj"
if (!(Test-Path $control) -or !(Test-Path $models) -or
!(Select-String -Path $models -Pattern "interface IIec61850ControlService" -Quiet) -or
!(Select-String -Path $models -Pattern "CommandTerminationReceived" -Quiet)) {
throw "Required ARIEC61850 Smart Control contract was not found."
}
if (!(Test-Path $workspace) -or !(Test-Path $workspaceModels) -or
!(Select-String -Path $workspace -Pattern "CompareLive" -Quiet)) {
throw "Required ARIEC61850 SCL Workspace API was not found."
}
if (!(Test-Path $sclModels) -or
!(Select-String -Path $sclModels -Pattern "SclDataSetBindingStatus" -Quiet) -or
!(Select-String -Path $sclModels -Pattern "class SclDataSetEntry" -Quiet) -or
!(Select-String -Path $sclModels -Pattern "public int Index" -Quiet)) {
throw "Typed SCL DataSet binding and ordered-entry contracts were not found."
}
if (!(Test-Path $sclExporter) -or
!(Select-String -Path $sclExporter -Pattern "WriteFiles" -Quiet) -or
!(Test-Path $interoperableConverter) -or
!(Select-String -Path $interoperableConverter -Pattern "WriteFiles" -Quiet) -or
!(Test-Path $schemaProfiles) -or
!(Select-String -Path $schemaProfiles -Pattern "Edition2V31" -Quiet) -or
!(Select-String -Path $schemaProfiles -Pattern "Edition1V16" -Quiet)) {
throw "Required ARIEC61850 interoperable SCL export APIs were not found."
}
if (!(Test-Path $gooseParser) -or
!(Test-Path $gooseDecoded) -or
!(Test-Path $processBusMonitor) -or
!(Test-Path $svParser) -or
!(Test-Path $svInspector) -or
!(Test-Path $svCounter) -or
!(Test-Path $svTimebase) -or
!(Test-Path $npcapSource) -or
!(Test-Path $npcapProject) -or
!(Select-String -Path $gooseParser -Pattern "TryParseEthernetFrame" -Quiet) -or
!(Select-String -Path $gooseDecoded -Pattern "SignalReference" -Quiet) -or
!(Select-String -Path $processBusMonitor -Pattern "GooseValues" -Quiet) -or
!(Select-String -Path $processBusMonitor -Pattern "TimeAllowedToLiveMilliseconds" -Quiet) -or
!(Select-String -Path $svParser -Pattern "TryParseEthernetFrame" -Quiet) -or
!(Select-String -Path $svInspector -Pattern "SvGenericPayloadInspector" -Quiet) -or
!(Select-String -Path $svCounter -Pattern "SvSampleCounterTransitionKind" -Quiet) -or
!(Select-String -Path $svTimebase -Pattern "TrustedNominalFrequencyHz" -Quiet) -or
!(Select-String -Path $npcapSource -Pattern "CaptureAsync" -Quiet)) {
throw "Required ARIEC61850 live process-bus, SV parser/inspection/timebase/counter, or Npcap APIs were not found."
}
- name: Setup .NET 8
uses: actions/setup-dotnet@v6
with:
dotnet-version: 8.0.x
- name: Restore solution
run: dotnet restore .\ArIED61850Tester\ArIED61850Tester.sln
- name: Build solution
run: dotnet build .\ArIED61850Tester\ArIED61850Tester.sln -c Release --no-restore
- name: Run pinned engine P10 identity and canonical SCL regression
run: dotnet test .\ARIEC61850\tests\AR.Iec61850.Tests\AR.Iec61850.Tests.csproj -c Release --filter "FullyQualifiedName~TrustedSclIedIdentityMatcherTests|FullyQualifiedName~CanonicalLiveIedSclExporterTests" --logger "trx;LogFileName=engine-p10-identity.trx" --results-directory .\ArIED61850Tester\EngineTestResults
- name: Run ARSAS application regression tests
run: dotnet test .\ArIED61850Tester\tests\ARSAS.Tests\ARSAS.Tests.csproj -c Release --no-build --no-restore --logger "trx;LogFileName=arsas-tests.trx" --results-directory .\ArIED61850Tester\TestResults
- name: Write exact-SHA canonical test attestation
shell: powershell
run: |
python .\ArIED61850Tester\scripts\write-ci-canonical-proof.py `
--app-dir .\ArIED61850Tester `
--engine-dir .\ARIEC61850 `
--test-results-dir .\ArIED61850Tester\TestResults `
--source-sha $env:GITHUB_SHA `
--engine-sha $env:ARIEC61850_COMMIT `
--run-id $env:GITHUB_RUN_ID `
--attempt $env:GITHUB_RUN_ATTEMPT `
--event-name $env:GITHUB_EVENT_NAME
if ($LASTEXITCODE -ne 0) {
throw 'Canonical exact-SHA full-regression attestation failed.'
}
- name: Upload test evidence
if: always()
uses: actions/upload-artifact@v7
with:
name: ARSAS-test-evidence
path: |
ArIED61850Tester\TestResults\*.trx
ArIED61850Tester\TestResults\ci-canonical-authority.json
if-no-files-found: error
- name: Build and test exact pinned ArdIrec native bridge once
shell: powershell
run: |
$bridgeDir = Join-Path $env:RUNNER_TEMP 'arsas-canonical-ardirec-bridge'
$bridgeBuild = Join-Path $env:RUNNER_TEMP 'arsas-canonical-ardirec-build'
$ctestJunit = Join-Path $env:RUNNER_TEMP 'arsas-canonical-native-ctest.xml'
$bridge = [string](
.\ArIED61850Tester\scripts\build-ardirec-bridge.ps1 `
-ArdIrecSource "$env:GITHUB_WORKSPACE\ArdIrec" `
-OutputDirectory $bridgeDir `
-BuildDirectory $bridgeBuild `
-CTestJunitPath $ctestJunit |
Select-Object -Last 1
)
$bridge = $bridge.Trim()
if (-not (Test-Path $bridge -PathType Leaf)) {
throw "Canonical ArdIrec bridge build did not produce a DLL: $bridge"
}
$ctestProof = Join-Path $env:RUNNER_TEMP 'ci-native-ctest-authority.json'
python .\ArIED61850Tester\scripts\write-ci-native-ctest-proof.py `
--junit $ctestJunit `
--bridge $bridge `
--source-sha $env:GITHUB_SHA `
--engine-sha $env:ARIEC61850_COMMIT `
--ardirec-sha $env:ARDIREC_COMMIT `
--run-id $env:GITHUB_RUN_ID `
--attempt $env:GITHUB_RUN_ATTEMPT `
--event $env:GITHUB_EVENT_NAME `
--output $ctestProof
if ($LASTEXITCODE -ne 0) { throw "Canonical native CTest proof sealing failed." }
if ($env:GITHUB_EVENT_NAME -eq 'pull_request') {
$shadowDir = '.\ArIED61850Tester\TestResults\native-shadow'
New-Item -ItemType Directory -Path $shadowDir -Force | Out-Null
Copy-Item $ctestJunit (Join-Path $shadowDir 'ctest.xml') -Force
Copy-Item $ctestProof (Join-Path $shadowDir 'ci-native-ctest-authority.json') -Force
}
"NATIVE_CTEST_JUNIT=$ctestJunit" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append
"NATIVE_CTEST_PROOF=$ctestProof" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append
"ARDIREC_BRIDGE_PATH=$bridge" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append
"ARDIREC_BRIDGE_SHA256=$((Get-FileHash $bridge -Algorithm SHA256).Hash.ToLowerInvariant())" |
Out-File $env:GITHUB_ENV -Encoding utf8 -Append
- name: Upload canonical native CTest shadow evidence
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v7
with:
name: ARSAS-native-ctest-shadow
path: ArIED61850Tester/TestResults/native-shadow/
if-no-files-found: error
retention-days: 14
- name: Publish canonical installer-input folder
shell: powershell
run: |
.\ArIED61850Tester\scripts\publish-windows-portable.ps1 `
-Version $env:ARSAS_VERSION `
-Runtime win-x64 `
-SingleFile $false `
-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" `
-ArdIrecBridgePath $env:ARDIREC_BRIDGE_PATH
if ($LASTEXITCODE -ne 0) { throw "Canonical installer-input publish failed." }
$publish = ".\ArIED61850Tester\dist\ARSAS-$env:ARSAS_VERSION-win-x64"
$ardirecDir = Join-Path $publish 'Tools\ArdIrec'
New-Item -ItemType Directory -Force $ardirecDir | Out-Null
$license = ".\ArdIrec\LICENSE"
if (Test-Path $license -PathType Leaf) {
Copy-Item $license (Join-Path $ardirecDir 'LICENSE.txt') -Force
}
$packagedBridge = Join-Path $ardirecDir 'ardirec_bridge.dll'
if (-not (Test-Path $packagedBridge -PathType Leaf)) {
throw "Canonical installer-input folder is missing the native bridge."
}
$packagedHash = (Get-FileHash $packagedBridge -Algorithm SHA256).Hash.ToLowerInvariant()
if ($packagedHash -ne $env:ARDIREC_BRIDGE_SHA256) {
throw "Installer-input bridge bytes differ from the tested canonical bridge."
}
- name: Exercise exact packaged native bridge through managed tests
shell: powershell
run: |
$env:ARSAS_ARDIREC_BRIDGE_PATH = "$env:GITHUB_WORKSPACE\ArIED61850Tester\dist\ARSAS-$env:ARSAS_VERSION-win-x64\Tools\ArdIrec\ardirec_bridge.dll"
$env:ARSAS_NATIVE_COMTRADE_TEST_CFG = "$env:GITHUB_WORKSPACE\ArdIrec\tests\data\minimal_1999.cfg"
$env:ARSAS_NATIVE_LOCUS_TEST_CFG = "$env:GITHUB_WORKSPACE\ArdIrec\tests\data\distance_p1.cfg"
foreach ($required in @(
$env:ARSAS_ARDIREC_BRIDGE_PATH,
$env:ARSAS_NATIVE_COMTRADE_TEST_CFG,
$env:ARSAS_NATIVE_LOCUS_TEST_CFG
)) {
if (-not (Test-Path $required -PathType Leaf)) {
throw "Canonical native bridge verification prerequisite is missing: $required"
}
}
dotnet test .\ArIED61850Tester\tests\ARSAS.Tests\ARSAS.Tests.csproj `
-c Release --no-build --no-restore `
--filter "FullyQualifiedName~ArdIrecNativeBridgeIntegrationTests|FullyQualifiedName~ArdIrecLocusNativeSessionIntegrationTests"
if ($LASTEXITCODE -ne 0) {
throw "Canonical packaged ArdIrec bridge failed managed integration tests."
}
- name: Publish real portable single EXE from the same native bridge
shell: powershell
run: |
.\ArIED61850Tester\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" `
-ArdIrecBridgePath $env:ARDIREC_BRIDGE_PATH
if ($LASTEXITCODE -ne 0) { throw "Canonical portable publish failed." }
- name: Smoke-test real portable single EXE
shell: powershell
run: |
$exe = ".\ArIED61850Tester\dist\ARSAS-$env:ARSAS_VERSION-win-x64-portable.exe"
if (-not (Test-Path $exe -PathType Leaf)) { throw "Portable single EXE was not produced: $exe" }
$env:DOTNET_BUNDLE_EXTRACT_BASE_DIR = Join-Path $env:RUNNER_TEMP "ARSAS-bundle-cache"
$diagnostic = Join-Path ([System.IO.Path]::GetTempPath()) "ARSAS-portable-smoke-error.txt"
Remove-Item $diagnostic -Force -ErrorAction SilentlyContinue
$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 single EXE smoke test timed out."
}
if ($process.ExitCode -ne 0) {
if (Test-Path $diagnostic) { Get-Content $diagnostic | Write-Host }
throw "Portable single EXE smoke test failed with exit code $($process.ExitCode)."
}
- name: Stage package verification runtime and fixtures
shell: powershell
run: |
$verification = ".\ArIED61850Tester\TestResults\windows-package-verification"
if (Test-Path $verification) { Remove-Item $verification -Recurse -Force }
$tests = Join-Path $verification 'ARSAS.Tests'
$fixtures = Join-Path $verification 'fixtures'
$native = Join-Path $verification 'native'
New-Item -ItemType Directory -Force -Path $tests, $fixtures, $native | Out-Null
$stagedCtest = Join-Path $native 'ctest.xml'
$stagedNativeProof = Join-Path $native 'ci-native-ctest-authority.json'
Copy-Item $env:NATIVE_CTEST_JUNIT $stagedCtest -Force
Copy-Item $env:NATIVE_CTEST_PROOF $stagedNativeProof -Force
python .\ArIED61850Tester\scripts\write-ci-native-ctest-proof.py `
--junit $stagedCtest `
--bridge $env:ARDIREC_BRIDGE_PATH `
--source-sha $env:GITHUB_SHA `
--engine-sha $env:ARIEC61850_COMMIT `
--ardirec-sha $env:ARDIREC_COMMIT `
--run-id $env:GITHUB_RUN_ID `
--attempt $env:GITHUB_RUN_ATTEMPT `
--event $env:GITHUB_EVENT_NAME `
--output $stagedNativeProof `
--verify-only
if ($LASTEXITCODE -ne 0) { throw 'Staged native CTest proof verification failed.' }
$testOutput = ".\ArIED61850Tester\tests\ARSAS.Tests\bin\Release\net8.0-windows"
if (-not (Test-Path (Join-Path $testOutput 'ARSAS.Tests.dll') -PathType Leaf)) {
throw "Canonical prebuilt ARSAS.Tests runtime is missing."
}
Copy-Item (Join-Path $testOutput '*') $tests -Recurse -Force
foreach ($stem in @('minimal_1999', 'distance_p1')) {
$matches = @(Get-ChildItem ".\ArdIrec\tests\data\$stem.*" -File -ErrorAction SilentlyContinue)
if (-not ($matches | Where-Object Extension -eq '.cfg')) {
throw "Pinned ArdIrec fixture is missing: $stem.cfg"
}
foreach ($file in $matches) {
Copy-Item $file.FullName (Join-Path $fixtures $file.Name) -Force
}
}
foreach ($extension in @('cfg', 'dat')) {
$releaseFixture = ".\ArIED61850Tester\tests\fixtures\comtrade\p1-release-smoke.$extension"
if (-not (Test-Path $releaseFixture -PathType Leaf)) {
throw "Canonical release native fixture is missing: $releaseFixture"
}
Copy-Item $releaseFixture (Join-Path $fixtures "p1-release-smoke.$extension") -Force
}
- name: Seal exact tested Windows package payload
shell: powershell
run: |
$artifactRoot = ".\ArIED61850Tester\TestResults\windows-package-artifact"
python .\ArIED61850Tester\scripts\write-ci-package-manifest.py `
--artifact-root $artifactRoot `
--portable ".\ArIED61850Tester\dist\ARSAS-$env:ARSAS_VERSION-win-x64-portable.exe" `
--portable-identity ".\ArIED61850Tester\dist\ARSAS-$env:ARSAS_VERSION-win-x64-portable-build-identity.json" `
--installer-input ".\ArIED61850Tester\dist\ARSAS-$env:ARSAS_VERSION-win-x64" `
--verification-dir ".\ArIED61850Tester\TestResults\windows-package-verification" `
--evidence-dir ".\ArIED61850Tester\TestResults" `
--source-sha $env:GITHUB_SHA `
--engine-sha $env:ARIEC61850_COMMIT `
--ardirec-sha $env:ARDIREC_COMMIT `
--version $env:ARSAS_VERSION `
--runtime win-x64 `
--run-id $env:GITHUB_RUN_ID `
--attempt $env:GITHUB_RUN_ATTEMPT `
--event-name $env:GITHUB_EVENT_NAME
if ($LASTEXITCODE -ne 0) { throw "Canonical Windows package sealing failed." }
$manifest = Get-Content (Join-Path $artifactRoot 'ci-windows-package-authority.json') -Raw | ConvertFrom-Json
if ($manifest.sourceSha -ne $env:GITHUB_SHA -or
$manifest.engineSha -ne $env:ARIEC61850_COMMIT -or
$manifest.ardirecSha -ne $env:ARDIREC_COMMIT -or
-not [bool]$manifest.fullRegressionPassed -or
-not [bool]$manifest.nativeBridgeIntegrationPassed -or
-not [bool]$manifest.portableSmokePassed -or
[bool]$manifest.installerBuilt -or
[bool]$manifest.releasePromotionAuthority -or
$manifest.nativeBridge.sha256 -ne $env:ARDIREC_BRIDGE_SHA256 -or
-not $manifest.portableIdentity -or
$manifest.portableIdentity.path -notmatch 'portable-build-identity\.json$') {
throw "Canonical Windows package manifest does not match the tested build authority."
}
- name: Upload sealed Windows package input
uses: actions/upload-artifact@v7
with:
name: ARSAS-windows-package-input
path: ArIED61850Tester\TestResults\windows-package-artifact
if-no-files-found: error
retention-days: 30
- name: Upload portable single EXE
uses: actions/upload-artifact@v7
with:
name: ARSAS-win-x64-portable-single-exe
path: |
ArIED61850Tester\dist\ARSAS-*-win-x64-portable.exe
ArIED61850Tester\dist\ARSAS-*-win-x64-portable-build-identity.json
if-no-files-found: error