From cd30be05f65bea21cf551de63b2b4e1d72e98d4e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 13:01:20 +0700 Subject: [PATCH 01/10] fix(source-clean): remove whole-file exemptions and exercise the strict gate --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3183adef..e537c2703 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,6 +51,10 @@ jobs: 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: Verify premium UX, GOOSE, SMV, SAS and release invariants shell: powershell run: | From c1dabb9e59f85fd28f6e86f1ef924200ea79ec61 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 13:01:24 +0700 Subject: [PATCH 02/10] fix(source-clean): remove whole-file exemptions and exercise the strict gate --- scripts/verify-source-clean.ps1 | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/scripts/verify-source-clean.ps1 b/scripts/verify-source-clean.ps1 index 3c0fd91e3..6cb2fee20 100644 --- a/scripts/verify-source-clean.ps1 +++ b/scripts/verify-source-clean.ps1 @@ -12,10 +12,17 @@ publish or repeat unrelated product and company names. #> [CmdletBinding()] -param() +param( + [string]$RepositoryRoot, + [switch]$ScanOnly +) $ErrorActionPreference = "Stop" -$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$RepoRoot = if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) { + (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +} else { + (Resolve-Path -LiteralPath $RepositoryRoot).Path +} $ForbiddenFilePatterns = @( "LICENSE-APACHE-2.0", @@ -56,16 +63,8 @@ $TextExtensions = @( ".props", ".targets", ".sln", ".slnx", ".txt" ) -# These are first-party convergence authorities. They intentionally contain the -# external interoperability label so the acceptance contract remains discoverable. -$ApprovedConvergenceIdentifierPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) -@( - ".github/workflows/smart-discovery-post-merge-production.yml", - ".github/workflows/smart-discovery-mainline-readiness.yml", - ".github/workflows/scl-interoperability-r7.yml", - "tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs" -) | ForEach-Object { [void]$ApprovedConvergenceIdentifierPaths.Add($_) } - +# No tracked path receives a whole-file external-identifier exemption. Historical +# comparison evidence is linked by immutable commit rather than copied into active files. $Problems = New-Object System.Collections.Generic.List[string] function Normalize-RelativePath { @@ -135,8 +134,7 @@ foreach ($relative in (Get-TrackedRelativePaths)) { } } - $identifierScanExempt = $ApprovedConvergenceIdentifierPaths.Contains($relative) - if (-not $identifierScanExempt -and (Test-ContainsForbiddenIdentifier $relative)) { + if (Test-ContainsForbiddenIdentifier $relative) { $Problems.Add("Forbidden external identifier in path: $relative") } @@ -144,7 +142,7 @@ foreach ($relative in (Get-TrackedRelativePaths)) { if ($TextExtensions -notcontains [IO.Path]::GetExtension($relative).ToLowerInvariant()) { continue } $content = Get-Content -LiteralPath $fullPath -Raw -ErrorAction SilentlyContinue - if (-not $identifierScanExempt -and (Test-ContainsForbiddenIdentifier $content)) { + if (Test-ContainsForbiddenIdentifier $content) { $Problems.Add("Forbidden external identifier in text: $relative") } @@ -162,7 +160,9 @@ if ($Problems.Count -gt 0) { throw "ARSAS source tree failed clean-room validation with $($Problems.Count) problem(s)." } -& (Join-Path $PSScriptRoot "verify-fault-record-bindings.ps1") -& (Join-Path $PSScriptRoot "verify-auto-update.ps1") +if (-not $ScanOnly) { + & (Join-Path $PSScriptRoot "verify-fault-record-bindings.ps1") + & (Join-Path $PSScriptRoot "verify-auto-update.ps1") +} -Write-Host "All Git-tracked ARSAS content passed source, website, external-IP, current-license, binding, and updater checks." -ForegroundColor Green +Write-Host "All Git-tracked ARSAS content passed source and external-identifier checks." -ForegroundColor Green From ec5a94b687f9da5e3fac7a06ee2f7e10996dbffd Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 13:01:28 +0700 Subject: [PATCH 03/10] fix(source-clean): remove whole-file exemptions and exercise the strict gate --- .../CanonicalLiveSclExportRegressionTests.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs b/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs index b5d7c170d..745cf9c02 100644 --- a/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs +++ b/tests/ARSAS.Tests/CanonicalLiveSclExportRegressionTests.cs @@ -385,15 +385,18 @@ public void InteroperabilityReferenceContract_PhysicalRetestPassedAndMergeReady( } [Fact] - public void SourceClean_DoesNotExemptNeutralReferenceEvidence() + public void SourceClean_GuardsEveryTrackedFileWithoutWholeFileExceptions() { var source = File.ReadAllText(FindRepoFile("scripts/verify-source-clean.ps1")); - - Assert.Contains("$ApprovedConvergenceIdentifierPaths", source, StringComparison.Ordinal); - Assert.DoesNotContain("docs/INTEROPERABILITY_REFERENCE_CONTRACT.md", source, StringComparison.Ordinal); - Assert.DoesNotContain("evidence/interoperability-reference-target.json", source, StringComparison.Ordinal); - Assert.Contains("CanonicalLiveSclExportRegressionTests.cs", source, StringComparison.Ordinal); - Assert.Contains("identifierScanExempt", source, StringComparison.Ordinal); + var build = File.ReadAllText(FindRepoFile(".github/workflows/build.yml")); + + Assert.DoesNotContain("$ApprovedConvergenceIdentifierPaths", source, StringComparison.Ordinal); + Assert.DoesNotContain("identifierScanExempt", source, StringComparison.Ordinal); + Assert.Contains("if (Test-ContainsForbiddenIdentifier $relative)", source, StringComparison.Ordinal); + Assert.Contains("if (Test-ContainsForbiddenIdentifier $content)", source, StringComparison.Ordinal); + Assert.Contains("test-source-clean-guard.ps1", build, StringComparison.Ordinal); + Assert.Contains("RepositoryRoot", source, StringComparison.Ordinal); + Assert.Contains("ScanOnly", source, StringComparison.Ordinal); } From 6229e275d82af6cc9219ceb86356a7c0a21faa85 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 13:01:32 +0700 Subject: [PATCH 04/10] test(source-clean): add end-to-end negative fixtures for every tracked surface --- scripts/test-source-clean-guard.ps1 | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 scripts/test-source-clean-guard.ps1 diff --git a/scripts/test-source-clean-guard.ps1 b/scripts/test-source-clean-guard.ps1 new file mode 100644 index 000000000..1b93f4a7a --- /dev/null +++ b/scripts/test-source-clean-guard.ps1 @@ -0,0 +1,61 @@ +# Copyright 2026 Ari Sulistiono +# SPDX-License-Identifier: GPL-3.0-or-later +<# +Tests the complete source-clean scanner against temporary Git-tracked fixtures. +Construct the known test identifier from code points so this test file itself +does not need an exemption from the same clean-room gate. +#> +[CmdletBinding()] +param() + +$ErrorActionPreference = "Stop" +$scanner = Join-Path $PSScriptRoot "verify-source-clean.ps1" +$identifier = -join (@(73, 69, 68, 83, 99, 111, 117, 116) | ForEach-Object { [char]$_ }) +$cases = @( + @{ Path = "Services/Fixture.cs"; Text = "public sealed class ${identifier}Fixture {}"; Expected = "text" }, + @{ Path = "docs/reference.md"; Text = "# $identifier"; Expected = "text" }, + @{ Path = "evidence/fixture.json"; Text = "{`"reference`": `"$identifier`"}"; Expected = "text" }, + @{ Path = ".github/workflows/smart-discovery-post-merge-production.yml"; Text = "name: $identifier"; Expected = "text" }, + @{ Path = "tests/ARSAS.Tests/SyntheticFixture.cs"; Text = "// $identifier"; Expected = "text" }, + @{ Path = "docs/${identifier}-fixture.md"; Text = "# independently generated fixture"; Expected = "path" } +) + +function Invoke-Case { + param( + [Parameter(Mandatory=$true)][string]$RelativePath, + [Parameter(Mandatory=$true)][string]$Content, + [Parameter(Mandatory=$true)][bool]$MustReject, + [string]$Expected = "text" + ) + $root = Join-Path ([IO.Path]::GetTempPath()) ("arsas-clean-room-" + [guid]::NewGuid().ToString("N")) + New-Item -ItemType Directory -Path $root -Force | Out-Null + try { + & git -C $root init --quiet + if ($LASTEXITCODE -ne 0) { throw "Fixture Git initialization failed." } + $file = Join-Path $root ($RelativePath.Replace('/', [IO.Path]::DirectorySeparatorChar)) + New-Item -ItemType Directory -Path (Split-Path $file) -Force | Out-Null + [IO.File]::WriteAllText($file, $Content, [Text.UTF8Encoding]::new($false)) + & git -C $root add --all + if ($LASTEXITCODE -ne 0) { throw "Fixture Git staging failed." } + + $output = (& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $scanner -RepositoryRoot $root -ScanOnly 2>&1 | Out-String) + $exitCode = $LASTEXITCODE + if ($MustReject) { + if ($exitCode -eq 0 -or $output -notmatch ("Forbidden external identifier in " + $Expected)) { + throw "Source-clean unexpectedly accepted a forbidden $Expected fixture: $RelativePath; exit=$exitCode; output=$output" + } + } + elseif ($exitCode -ne 0) { + throw "Source-clean rejected a neutral fixture: $RelativePath; exit=$exitCode; output=$output" + } + } + finally { + Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue + } +} + +foreach ($case in $cases) { + Invoke-Case -RelativePath $case.Path -Content $case.Text -MustReject $true -Expected $case.Expected +} +Invoke-Case -RelativePath "docs/synthetic-reference.md" -Content "# ARSAS independent IEC 61850 synthetic evidence" -MustReject $false +Write-Host "Source-clean negative and positive fixture tests PASS." -ForegroundColor Green From 50c73d6b15e025f3da4b791459724e729a111375 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 21:01:36 +0700 Subject: [PATCH 05/10] fix(source-clean): detect prohibited identifiers embedded in symbols --- scripts/verify-source-clean.ps1 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/verify-source-clean.ps1 b/scripts/verify-source-clean.ps1 index 6cb2fee20..2b0cb2aca 100644 --- a/scripts/verify-source-clean.ps1 +++ b/scripts/verify-source-clean.ps1 @@ -92,6 +92,21 @@ function Test-ContainsForbiddenIdentifier { $words = @([regex]::Matches($Text.ToLowerInvariant(), '[a-z0-9]+') | ForEach-Object { $_.Value }) for ($index = 0; $index -lt $words.Count; $index++) { + $word = $words[$index] + + # Detect identifiers embedded in source/path tokens such as TypeNameSuffix. + # This closes the common case where a prohibited product name is attached + # to a class, fixture, job, or filename rather than separated by punctuation. + foreach ($length in $CandidateLengths) { + if ($word.Length -lt $length) { continue } + for ($offset = 0; $offset -le ($word.Length - $length); $offset++) { + $fragment = $word.Substring($offset, $length) + if ($ForbiddenIdentifierHashes.Contains((Get-Sha256Hex $fragment))) { + return $true + } + } + } + $candidate = "" for ($count = 1; $count -le 4 -and ($index + $count - 1) -lt $words.Count; $count++) { $candidate += $words[$index + $count - 1] From 642d20af90e7add3ace3a92e8b0f49e403c205c2 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 21:04:43 +0700 Subject: [PATCH 06/10] test(source-clean): require fully scoped identifier scanning --- .../InteroperabilityReferenceEvidenceRegressionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs b/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs index 58443173f..5965fa8c1 100644 --- a/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs +++ b/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs @@ -55,7 +55,7 @@ public void ActiveReferenceAndOriginalHistoricalProvenance_AreBothDiscoverable() Assert.Contains("externalReferenceCapture", workflow, StringComparison.Ordinal); Assert.Contains("evidence/interoperability-reference-target.json", workflow, StringComparison.Ordinal); Assert.Contains("evidence/interoperability-reference-target.json", documentation, StringComparison.Ordinal); - Assert.Contains("ApprovedConvergenceIdentifierPaths", sourceClean, StringComparison.Ordinal); + Assert.DoesNotContain("ApprovedConvergenceIdentifierPaths", sourceClean, StringComparison.Ordinal);\n Assert.Contains("No tracked path receives a whole-file external-identifier exemption", sourceClean, StringComparison.Ordinal); } private static string FindRepositoryFile(string path) From 52889b9285f9231419eb7b85d45561d7e30cedcb Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 21:09:56 +0700 Subject: [PATCH 07/10] fix(test): repair source-clean invariant assertion formatting From d74ffa3f4bc57fb6023242a52697764a11f14498 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 21:13:13 +0700 Subject: [PATCH 08/10] fix(test): use real newline in source-clean invariant assertions --- .../InteroperabilityReferenceEvidenceRegressionTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs b/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs index 5965fa8c1..35698c5b5 100644 --- a/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs +++ b/tests/ARSAS.Tests/InteroperabilityReferenceEvidenceRegressionTests.cs @@ -55,7 +55,8 @@ public void ActiveReferenceAndOriginalHistoricalProvenance_AreBothDiscoverable() Assert.Contains("externalReferenceCapture", workflow, StringComparison.Ordinal); Assert.Contains("evidence/interoperability-reference-target.json", workflow, StringComparison.Ordinal); Assert.Contains("evidence/interoperability-reference-target.json", documentation, StringComparison.Ordinal); - Assert.DoesNotContain("ApprovedConvergenceIdentifierPaths", sourceClean, StringComparison.Ordinal);\n Assert.Contains("No tracked path receives a whole-file external-identifier exemption", sourceClean, StringComparison.Ordinal); + Assert.DoesNotContain("ApprovedConvergenceIdentifierPaths", sourceClean, StringComparison.Ordinal); + Assert.Contains("No tracked path receives a whole-file external-identifier exemption", sourceClean, StringComparison.Ordinal); } private static string FindRepositoryFile(string path) From 4500edfb4a3b3d42fe100ea4d456387d55620fb1 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Fri, 25 Sep 2026 09:12:28 +0700 Subject: [PATCH 09/10] fix(ci): capture expected source-clean failures without native stderr termination --- scripts/test-source-clean-guard.ps1 | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/scripts/test-source-clean-guard.ps1 b/scripts/test-source-clean-guard.ps1 index 1b93f4a7a..ed4e98f5c 100644 --- a/scripts/test-source-clean-guard.ps1 +++ b/scripts/test-source-clean-guard.ps1 @@ -38,8 +38,31 @@ function Invoke-Case { & git -C $root add --all if ($LASTEXITCODE -ne 0) { throw "Fixture Git staging failed." } - $output = (& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $scanner -RepositoryRoot $root -ScanOnly 2>&1 | Out-String) - $exitCode = $LASTEXITCODE + $startInfo = [System.Diagnostics.ProcessStartInfo]::new() + $startInfo.FileName = "powershell.exe" + $startInfo.UseShellExecute = $false + $startInfo.CreateNoWindow = $true + $startInfo.RedirectStandardOutput = $true + $startInfo.RedirectStandardError = $true + + $quotedScanner = '"' + $scanner.Replace('"', '\"') + '"' + $quotedRoot = '"' + $root.Replace('"', '\"') + '"' + $startInfo.Arguments = "-NoProfile -ExecutionPolicy Bypass -File $quotedScanner -RepositoryRoot $quotedRoot -ScanOnly" + + $process = [System.Diagnostics.Process]::new() + $process.StartInfo = $startInfo + if (-not $process.Start()) { + throw "Source-clean fixture scanner failed to start." + } + + $stdoutTask = $process.StandardOutput.ReadToEndAsync() + $stderrTask = $process.StandardError.ReadToEndAsync() + $process.WaitForExit() + $stdout = $stdoutTask.GetAwaiter().GetResult() + $stderr = $stderrTask.GetAwaiter().GetResult() + $exitCode = $process.ExitCode + $output = @($stdout, $stderr) -join [Environment]::NewLine + $process.Dispose() if ($MustReject) { if ($exitCode -eq 0 -or $output -notmatch ("Forbidden external identifier in " + $Expected)) { throw "Source-clean unexpectedly accepted a forbidden $Expected fixture: $RelativePath; exit=$exitCode; output=$output" From 72b07f8df154dea4062f628c14f7f3d66f964111 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Fri, 25 Sep 2026 09:16:04 +0700 Subject: [PATCH 10/10] perf(ci): cache clean-room identifier fingerprints --- scripts/verify-source-clean.ps1 | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/scripts/verify-source-clean.ps1 b/scripts/verify-source-clean.ps1 index 2b0cb2aca..f03c91ad5 100644 --- a/scripts/verify-source-clean.ps1 +++ b/scripts/verify-source-clean.ps1 @@ -72,17 +72,22 @@ function Normalize-RelativePath { return $Path.Replace('\', '/').TrimStart('/') } -function Get-Sha256Hex { +$Sha256 = [System.Security.Cryptography.SHA256]::Create() +$IdentifierCandidateCache = [System.Collections.Generic.Dictionary[string,bool]]::new([System.StringComparer]::Ordinal) + +function Test-ForbiddenIdentifierCandidate { param([Parameter(Mandatory=$true)][string]$Value) - $algorithm = [System.Security.Cryptography.SHA256]::Create() - try { - $bytes = [System.Text.Encoding]::UTF8.GetBytes($Value) - return -join ($algorithm.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") }) - } - finally { - $algorithm.Dispose() + if (-not $CandidateLengths.Contains($Value.Length)) { return $false } + if ($IdentifierCandidateCache.ContainsKey($Value)) { + return $IdentifierCandidateCache[$Value] } + + $bytes = [System.Text.Encoding]::UTF8.GetBytes($Value) + $hash = -join ($Sha256.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") }) + $isForbidden = $ForbiddenIdentifierHashes.Contains($hash) + $IdentifierCandidateCache[$Value] = $isForbidden + return $isForbidden } function Test-ContainsForbiddenIdentifier { @@ -101,7 +106,7 @@ function Test-ContainsForbiddenIdentifier { if ($word.Length -lt $length) { continue } for ($offset = 0; $offset -le ($word.Length - $length); $offset++) { $fragment = $word.Substring($offset, $length) - if ($ForbiddenIdentifierHashes.Contains((Get-Sha256Hex $fragment))) { + if (Test-ForbiddenIdentifierCandidate $fragment) { return $true } } @@ -111,7 +116,7 @@ function Test-ContainsForbiddenIdentifier { for ($count = 1; $count -le 4 -and ($index + $count - 1) -lt $words.Count; $count++) { $candidate += $words[$index + $count - 1] if ($candidate.Length -gt 22) { break } - if ($CandidateLengths.Contains($candidate.Length) -and $ForbiddenIdentifierHashes.Contains((Get-Sha256Hex $candidate))) { + if (Test-ForbiddenIdentifierCandidate $candidate) { return $true } } @@ -180,4 +185,5 @@ if (-not $ScanOnly) { & (Join-Path $PSScriptRoot "verify-auto-update.ps1") } +$Sha256.Dispose() Write-Host "All Git-tracked ARSAS content passed source and external-identifier checks." -ForegroundColor Green