From b88ca0f52eb22c5d86ba96e3b911ed1bce126167 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 12:26:05 +0700 Subject: [PATCH 1/7] chore(release): retire historical v1.6.38 workflow build-v1.6.38-golden-installer.yml --- .../build-v1.6.38-golden-installer.yml | 254 ------------------ 1 file changed, 254 deletions(-) delete mode 100644 .github/workflows/build-v1.6.38-golden-installer.yml diff --git a/.github/workflows/build-v1.6.38-golden-installer.yml b/.github/workflows/build-v1.6.38-golden-installer.yml deleted file mode 100644 index 5e30d82b4..000000000 --- a/.github/workflows/build-v1.6.38-golden-installer.yml +++ /dev/null @@ -1,254 +0,0 @@ -name: Build ARSAS v1.6.38 golden installer candidate - -on: - pull_request: - paths: - - ".github/workflows/build-v1.6.38-golden-installer.yml" - - ".release/recover-v1.6.38-golden.json" - - "installer/ArIED61850.iss" - push: - branches: [ main ] - paths: - - ".github/workflows/build-v1.6.38-golden-installer.yml" - - ".release/recover-v1.6.38-golden.json" - - "installer/ArIED61850.iss" - workflow_dispatch: - -permissions: - actions: read - contents: read - -concurrency: - group: build-v1.6.38-golden-installer-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - build: - name: Package exact golden runtime and verify installed payload - runs-on: windows-latest - timeout-minutes: 30 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Read exact golden runtime authority - shell: pwsh - run: | - $request = Get-Content ".\.release\recover-v1.6.38-golden.json" -Raw | ConvertFrom-Json - $expected = [ordered]@{ - tag = "v1.6.38" - actionsArtifactId = 10549589733 - artifactName = "ARSAS-r7-scl-interoperability-win-x64" - goldenZipSha256 = "d68b9e89487cfd71bf92ac181e5abed35106ea65776a6005eaa863eb6cdc0068" - goldenExeSha256 = "555c3d91dbda85cb1b3de453266b33dd26a95cd06fe6805ccb973ed62d19487a" - goldenExePath = "dist/ARSAS-1.6.37-win-x64-portable.exe" - applicationCommit = "eb8eb13d491f9aa265205852b8a4bab07af440ff" - engineCommit = "9935d6902d786cc69b299260fe36b835944d5e81" - } - - foreach ($key in $expected.Keys) { - $actual = [string]$request.$key - $wanted = [string]$expected[$key] - if ($actual -ne $wanted) { - throw "Golden installer authority mismatch for $key. Expected '$wanted', got '$actual'." - } - } - - "RELEASE_TAG=$($request.tag)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "RELEASE_VERSION=1.6.38" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "ARTIFACT_ID=$($request.actionsArtifactId)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "ARTIFACT_NAME=$($request.artifactName)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "GOLDEN_ZIP_SHA=$($request.goldenZipSha256)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "GOLDEN_EXE_SHA=$($request.goldenExeSha256)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "GOLDEN_EXE_PATH=$($request.goldenExePath)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "RUNTIME_APP_COMMIT=$($request.applicationCommit)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "RUNTIME_ENGINE_COMMIT=$($request.engineCommit)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Download exact retained golden artifact - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - $root = Join-Path $env:RUNNER_TEMP "arsas-golden-installer" - $zip = Join-Path $root "golden-artifact.zip" - New-Item -ItemType Directory -Path $root -Force | Out-Null - - $headers = @{ - Authorization = "Bearer $env:GH_TOKEN" - Accept = "application/vnd.github+json" - "X-GitHub-Api-Version" = "2022-11-28" - } - $artifactUrl = "https://api.github.com/repos/$env:GITHUB_REPOSITORY/actions/artifacts/$env:ARTIFACT_ID/zip" - Invoke-WebRequest -Uri $artifactUrl -Headers $headers -OutFile $zip -MaximumRedirection 5 - if (-not (Test-Path $zip -PathType Leaf)) { - throw "Failed to download exact retained golden artifact." - } - - $zipSha = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLowerInvariant() - if ($zipSha -ne $env:GOLDEN_ZIP_SHA) { - throw "Golden artifact ZIP hash mismatch. Expected $env:GOLDEN_ZIP_SHA got $zipSha." - } - - $extract = Join-Path $root "extracted" - Expand-Archive -Path $zip -DestinationPath $extract -Force - $sourceExe = Join-Path $extract ($env:GOLDEN_EXE_PATH -replace "/", "\") - if (-not (Test-Path $sourceExe -PathType Leaf)) { - throw "Golden runtime executable missing: $sourceExe" - } - - $exeSha = (Get-FileHash $sourceExe -Algorithm SHA256).Hash.ToLowerInvariant() - if ($exeSha -ne $env:GOLDEN_EXE_SHA) { - throw "Golden runtime hash mismatch. Expected $env:GOLDEN_EXE_SHA got $exeSha." - } - - $manifest = Join-Path $extract "dist\R7-SCL-INTEROP-BUILD.txt" - if (-not (Test-Path $manifest -PathType Leaf)) { throw "Golden manifest missing." } - $manifestText = Get-Content $manifest -Raw - if ($manifestText -notmatch [regex]::Escape("ARSAS commit: $env:RUNTIME_APP_COMMIT") -or - $manifestText -notmatch [regex]::Escape("ARIEC61850 commit: $env:RUNTIME_ENGINE_COMMIT")) { - throw "Golden manifest commit authority mismatch." - } - - "GOLDEN_ROOT=$root" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "GOLDEN_SOURCE_EXE=$sourceExe" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Stage installer source from exact golden EXE only - shell: pwsh - run: | - $sourceDir = Join-Path $env:RUNNER_TEMP "arsas-golden-installer-source" - if (Test-Path $sourceDir) { Remove-Item $sourceDir -Recurse -Force } - New-Item -ItemType Directory -Path $sourceDir -Force | Out-Null - - $stagedExe = Join-Path $sourceDir "ARSAS.exe" - Copy-Item $env:GOLDEN_SOURCE_EXE $stagedExe -Force - $stagedSha = (Get-FileHash $stagedExe -Algorithm SHA256).Hash.ToLowerInvariant() - if ($stagedSha -ne $env:GOLDEN_EXE_SHA) { - throw "Staged installer payload changed bytes. Expected $env:GOLDEN_EXE_SHA got $stagedSha." - } - - $files = @(Get-ChildItem $sourceDir -File -Recurse) - if ($files.Count -ne 1 -or $files[0].Name -ne "ARSAS.exe") { - throw "Installer staging must contain exactly one runtime payload: ARSAS.exe." - } - - "INSTALLER_SOURCE_DIR=$sourceDir" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Install Inno Setup compiler - shell: pwsh - run: choco install innosetup --no-progress --yes - - - name: Compile installer around exact golden runtime - shell: pwsh - run: | - $iscc = (Get-Command ISCC.exe -ErrorAction SilentlyContinue).Source - if ([string]::IsNullOrWhiteSpace($iscc)) { - $iscc = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" - } - if (-not (Test-Path $iscc -PathType Leaf)) { - throw "Inno Setup compiler not found." - } - - $output = Join-Path $env:GITHUB_WORKSPACE "dist" - New-Item -ItemType Directory -Path $output -Force | Out-Null - $definition = Join-Path $env:GITHUB_WORKSPACE "installer\ArIED61850.iss" - $arguments = @( - "/DAppVersion=$env:RELEASE_VERSION", - "/DAppVersionNumeric=1.6.38.0", - "/DSourceDir=$env:INSTALLER_SOURCE_DIR", - "/DOutputDir=$output", - "/DOutputBaseFilename=ARSAS-Windows-x64-Setup", - $definition - ) - & $iscc @arguments - if ($LASTEXITCODE -ne 0) { - throw "Inno Setup compilation failed with exit code $LASTEXITCODE." - } - - $setup = Join-Path $output "ARSAS-Windows-x64-Setup.exe" - if (-not (Test-Path $setup -PathType Leaf)) { throw "Installer was not produced." } - $setupSha = (Get-FileHash $setup -Algorithm SHA256).Hash.ToLowerInvariant() - "SETUP_PATH=$setup" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "SETUP_SHA=$setupSha" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - Write-Host "Installer SHA256: $setupSha" - - - name: Silent install and prove installed runtime is byte-identical - shell: pwsh - run: | - $installRoot = Join-Path $env:RUNNER_TEMP "arsas-v1.6.38-installed" - if (Test-Path $installRoot) { Remove-Item $installRoot -Recurse -Force } - - $install = Start-Process -FilePath $env:SETUP_PATH -ArgumentList @( - "/VERYSILENT", - "/SUPPRESSMSGBOXES", - "/NORESTART", - "/SP-", - "/CURRENTUSER", - "/DIR=$installRoot" - ) -Wait -PassThru - if ($install.ExitCode -ne 0) { throw "Silent installer failed with exit code $($install.ExitCode)." } - - $installedExe = Join-Path $installRoot "ARSAS.exe" - if (-not (Test-Path $installedExe -PathType Leaf)) { - throw "Installed golden runtime was not found: $installedExe" - } - $installedSha = (Get-FileHash $installedExe -Algorithm SHA256).Hash.ToLowerInvariant() - if ($installedSha -ne $env:GOLDEN_EXE_SHA) { - throw "Installed runtime is not byte-identical to golden EXE. Expected $env:GOLDEN_EXE_SHA got $installedSha." - } - - $env:DOTNET_BUNDLE_EXTRACT_BASE_DIR = Join-Path $env:RUNNER_TEMP "arsas-v1.6.38-installed-bundle-cache" - $smoke = Start-Process -FilePath $installedExe -ArgumentList @("--portable-smoke-test") -Wait -PassThru - if ($smoke.ExitCode -ne 0) { - throw "Installed golden runtime smoke test failed with exit code $($smoke.ExitCode)." - } - - $uninstaller = Join-Path $installRoot "unins000.exe" - if (-not (Test-Path $uninstaller -PathType Leaf)) { throw "Uninstaller was not created." } - $uninstall = Start-Process -FilePath $uninstaller -ArgumentList @( - "/VERYSILENT", - "/SUPPRESSMSGBOXES", - "/NORESTART" - ) -Wait -PassThru - if ($uninstall.ExitCode -ne 0) { throw "Silent uninstall failed with exit code $($uninstall.ExitCode)." } - - - name: Write installer candidate evidence - shell: pwsh - run: | - $evidencePath = Join-Path $env:GITHUB_WORKSPACE "dist\ARSAS-v1.6.38-golden-installer-evidence.json" - $evidence = [ordered]@{ - schemaVersion = 1 - product = "ARSAS" - releaseTag = $env:RELEASE_TAG - installerVersion = $env:RELEASE_VERSION - runtimeRebuilt = $false - sourceArtifact = [ordered]@{ - id = [int64]$env:ARTIFACT_ID - name = $env:ARTIFACT_NAME - zipSha256 = $env:GOLDEN_ZIP_SHA - } - runtime = [ordered]@{ - applicationCommit = $env:RUNTIME_APP_COMMIT - engineCommit = $env:RUNTIME_ENGINE_COMMIT - portableSha256 = $env:GOLDEN_EXE_SHA - installedFileName = "ARSAS.exe" - installedSha256 = $env:GOLDEN_EXE_SHA - } - installer = [ordered]@{ - name = "ARSAS-Windows-x64-Setup.exe" - sha256 = $env:SETUP_SHA - packaging = "Inno Setup container over exact golden single-file runtime" - } - } - $evidence | ConvertTo-Json -Depth 6 | Set-Content $evidencePath -Encoding utf8 - Get-Content $evidencePath | Write-Host - - - name: Upload tested installer candidate - uses: actions/upload-artifact@v4 - with: - name: ARSAS-v1.6.38-golden-installer-candidate - retention-days: 90 - if-no-files-found: error - path: | - dist/ARSAS-Windows-x64-Setup.exe - dist/ARSAS-v1.6.38-golden-installer-evidence.json From 78cfad54cc9d49d0a941d6173f51411f26cffa4f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 12:26:08 +0700 Subject: [PATCH 2/7] chore(release): retire historical v1.6.38 workflow publish-v1.6.38-golden-installer.yml --- .../publish-v1.6.38-golden-installer.yml | 475 ------------------ 1 file changed, 475 deletions(-) delete mode 100644 .github/workflows/publish-v1.6.38-golden-installer.yml diff --git a/.github/workflows/publish-v1.6.38-golden-installer.yml b/.github/workflows/publish-v1.6.38-golden-installer.yml deleted file mode 100644 index 595fe1b5c..000000000 --- a/.github/workflows/publish-v1.6.38-golden-installer.yml +++ /dev/null @@ -1,475 +0,0 @@ -name: Publish ARSAS v1.6.38 golden installer - -on: - pull_request: - paths: - - ".release/recover-v1.6.38-golden-installer.json" - - ".github/workflows/publish-v1.6.38-golden-installer.yml" - push: - branches: [ main ] - paths: - - ".release/recover-v1.6.38-golden-installer.json" - - ".github/workflows/publish-v1.6.38-golden-installer.yml" - workflow_dispatch: - -permissions: - actions: read - contents: write - -concurrency: - group: publish-v1.6.38-golden-installer-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: false - -jobs: - verify-publish: - name: Verify tested installer artifact and publish exact stable setup - runs-on: windows-latest - timeout-minutes: 30 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Read pinned tested installer authority - shell: pwsh - run: | - $pin = Get-Content ".\.release\recover-v1.6.38-golden-installer.json" -Raw | ConvertFrom-Json - $expected = [ordered]@{ - tag = "v1.6.38" - installerVersion = "1.6.38" - testedInstallerArtifactId = "10586413305" - testedInstallerArtifactName = "ARSAS-v1.6.38-golden-installer-candidate" - testedInstallerArtifactZipSha256 = "ec3dc83b74d421aa779d9c94fc5a1f4c219e135ad3d9893f9e87f5ad2bc15660" - installerName = "ARSAS-Windows-x64-Setup.exe" - installerSha256 = "198dfea5b6d7b58c4c1006fd15c6fbfef1f0885d84924b17c04e0f6cb4cfff30" - installerSizeBytes = "73461055" - installerEvidenceName = "ARSAS-v1.6.38-golden-installer-evidence.json" - installerEvidenceSha256 = "e1ad23a6ee128efe5b07eaf20266978dda89c43ee45b2ec3e41e679b2cda2e80" - goldenRuntimeSha256 = "555c3d91dbda85cb1b3de453266b33dd26a95cd06fe6805ccb973ed62d19487a" - goldenRuntimeSizeBytes = "78383243" - runtimeApplicationCommit = "eb8eb13d491f9aa265205852b8a4bab07af440ff" - runtimeEngineCommit = "9935d6902d786cc69b299260fe36b835944d5e81" - sourceGoldenArtifactId = "10549589733" - sourceGoldenArtifactZipSha256 = "d68b9e89487cfd71bf92ac181e5abed35106ea65776a6005eaa863eb6cdc0068" - runtimeRebuilt = "False" - } - foreach ($key in $expected.Keys) { - $actual = [string]$pin.$key - if ($actual -ne [string]$expected[$key]) { - throw "Pinned installer authority mismatch for $key. Expected '$($expected[$key])', got '$actual'." - } - } - - "RELEASE_TAG=$($pin.tag)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "RELEASE_VERSION=$($pin.installerVersion)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_ARTIFACT_ID=$($pin.testedInstallerArtifactId)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_ARTIFACT_NAME=$($pin.testedInstallerArtifactName)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_ARTIFACT_ZIP_SHA=$($pin.testedInstallerArtifactZipSha256)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_NAME=$($pin.installerName)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_SHA=$($pin.installerSha256)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_SIZE=$($pin.installerSizeBytes)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_EVIDENCE_NAME=$($pin.installerEvidenceName)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "INSTALLER_EVIDENCE_SHA=$($pin.installerEvidenceSha256)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "GOLDEN_EXE_SHA=$($pin.goldenRuntimeSha256)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "GOLDEN_EXE_SIZE=$($pin.goldenRuntimeSizeBytes)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "RUNTIME_APP_COMMIT=$($pin.runtimeApplicationCommit)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "RUNTIME_ENGINE_COMMIT=$($pin.runtimeEngineCommit)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "SOURCE_GOLDEN_ARTIFACT_ID=$($pin.sourceGoldenArtifactId)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "SOURCE_GOLDEN_ZIP_SHA=$($pin.sourceGoldenArtifactZipSha256)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Download exact tested installer artifact - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - $root = Join-Path $env:RUNNER_TEMP "arsas-tested-installer-promotion" - $zip = Join-Path $root "tested-installer-artifact.zip" - New-Item -ItemType Directory -Path $root -Force | Out-Null - - $headers = @{ - Authorization = "Bearer $env:GH_TOKEN" - Accept = "application/vnd.github+json" - "X-GitHub-Api-Version" = "2022-11-28" - } - $artifactUrl = "https://api.github.com/repos/$env:GITHUB_REPOSITORY/actions/artifacts/$env:INSTALLER_ARTIFACT_ID/zip" - Invoke-WebRequest -Uri $artifactUrl -Headers $headers -OutFile $zip -MaximumRedirection 5 - - $zipSha = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLowerInvariant() - if ($zipSha -ne $env:INSTALLER_ARTIFACT_ZIP_SHA) { - throw "Tested installer artifact ZIP hash mismatch." - } - - $extract = Join-Path $root "extracted" - Expand-Archive -Path $zip -DestinationPath $extract -Force - $files = @(Get-ChildItem $extract -File -Recurse) - $names = @($files | ForEach-Object Name | Sort-Object) - $expectedNames = @($env:INSTALLER_EVIDENCE_NAME, $env:INSTALLER_NAME) | Sort-Object - if (($names -join "|") -ne ($expectedNames -join "|")) { - throw "Tested installer artifact inventory mismatch: $($names -join ', ')." - } - - $setup = Join-Path $extract $env:INSTALLER_NAME - $evidencePath = Join-Path $extract $env:INSTALLER_EVIDENCE_NAME - $setupSha = (Get-FileHash $setup -Algorithm SHA256).Hash.ToLowerInvariant() - $setupSize = (Get-Item $setup).Length - $evidenceSha = (Get-FileHash $evidencePath -Algorithm SHA256).Hash.ToLowerInvariant() - if ($setupSha -ne $env:INSTALLER_SHA -or [string]$setupSize -ne [string]$env:INSTALLER_SIZE) { - throw "Tested installer binary mismatch." - } - if ($evidenceSha -ne $env:INSTALLER_EVIDENCE_SHA) { - throw "Tested installer evidence hash mismatch." - } - - $evidence = Get-Content $evidencePath -Raw | ConvertFrom-Json - if ($evidence.schemaVersion -ne 1 -or - $evidence.product -ne "ARSAS" -or - $evidence.releaseTag -ne $env:RELEASE_TAG -or - $evidence.installerVersion -ne $env:RELEASE_VERSION -or - [bool]$evidence.runtimeRebuilt -or - [string]$evidence.sourceArtifact.id -ne $env:SOURCE_GOLDEN_ARTIFACT_ID -or - $evidence.sourceArtifact.zipSha256 -ne $env:SOURCE_GOLDEN_ZIP_SHA -or - $evidence.runtime.applicationCommit -ne $env:RUNTIME_APP_COMMIT -or - $evidence.runtime.engineCommit -ne $env:RUNTIME_ENGINE_COMMIT -or - $evidence.runtime.portableSha256 -ne $env:GOLDEN_EXE_SHA -or - $evidence.runtime.installedSha256 -ne $env:GOLDEN_EXE_SHA -or - $evidence.installer.sha256 -ne $env:INSTALLER_SHA) { - throw "Tested installer evidence does not match pinned golden authority." - } - - "PROMOTION_ROOT=$root" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "TESTED_SETUP_PATH=$setup" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Confirm current public portable remains golden - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - $dir = Join-Path $env:PROMOTION_ROOT "public-before" - New-Item -ItemType Directory -Path $dir -Force | Out-Null - gh release download $env:RELEASE_TAG --repo $env:GITHUB_REPOSITORY --dir $dir --pattern "ARSAS-Windows-x64-Portable.exe" --clobber - if ($LASTEXITCODE -ne 0) { throw "Failed to download current public portable." } - - $portable = Join-Path $dir "ARSAS-Windows-x64-Portable.exe" - $sha = (Get-FileHash $portable -Algorithm SHA256).Hash.ToLowerInvariant() - $size = (Get-Item $portable).Length - if ($sha -ne $env:GOLDEN_EXE_SHA -or [string]$size -ne [string]$env:GOLDEN_EXE_SIZE) { - throw "Current public portable is no longer the golden runtime." - } - "PUBLIC_PORTABLE_PATH=$portable" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Reinstall tested candidate and prove golden payload again - shell: pwsh - run: | - $installRoot = Join-Path $env:RUNNER_TEMP "arsas-tested-installer-installed" - if (Test-Path $installRoot) { Remove-Item $installRoot -Recurse -Force } - - $install = Start-Process -FilePath $env:TESTED_SETUP_PATH -ArgumentList @( - "/VERYSILENT", - "/SUPPRESSMSGBOXES", - "/NORESTART", - "/SP-", - "/CURRENTUSER", - "/DIR=$installRoot" - ) -Wait -PassThru - if ($install.ExitCode -ne 0) { throw "Tested installer failed." } - - $installed = Join-Path $installRoot "ARSAS.exe" - if (-not (Test-Path $installed -PathType Leaf)) { throw "Installed runtime missing." } - $installedSha = (Get-FileHash $installed -Algorithm SHA256).Hash.ToLowerInvariant() - if ($installedSha -ne $env:GOLDEN_EXE_SHA) { - throw "Installed runtime differs from golden runtime." - } - - $env:DOTNET_BUNDLE_EXTRACT_BASE_DIR = Join-Path $env:RUNNER_TEMP "arsas-tested-installer-bundle-cache" - $smoke = Start-Process -FilePath $installed -ArgumentList @("--portable-smoke-test") -Wait -PassThru - if ($smoke.ExitCode -ne 0) { throw "Installed golden runtime smoke test failed." } - - $uninstaller = Join-Path $installRoot "unins000.exe" - if (-not (Test-Path $uninstaller -PathType Leaf)) { throw "Uninstaller missing." } - $uninstall = Start-Process -FilePath $uninstaller -ArgumentList @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART") -Wait -PassThru - if ($uninstall.ExitCode -ne 0) { throw "Silent uninstall failed." } - - - name: Stage exact release metadata - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - $publish = Join-Path $env:PROMOTION_ROOT "publish" - New-Item -ItemType Directory -Path $publish -Force | Out-Null - Copy-Item $env:TESTED_SETUP_PATH (Join-Path $publish $env:INSTALLER_NAME) -Force - - $checksums = Join-Path $publish "ARSAS-Windows-x64-SHA256SUMS.txt" - @( - "$env:INSTALLER_SHA $env:INSTALLER_NAME", - "$env:GOLDEN_EXE_SHA ARSAS-Windows-x64-Portable.exe" - ) | Set-Content $checksums -Encoding ascii - - $release = (gh api "repos/$env:GITHUB_REPOSITORY/releases/tags/$env:RELEASE_TAG") | ConvertFrom-Json - if ($LASTEXITCODE -ne 0 -or $release.draft -or $release.prerelease) { - throw "Stable release metadata could not be resolved." - } - - $provenancePath = Join-Path $publish "ARSAS-Windows-x64-PROVENANCE.json" - $provenance = [ordered]@{ - schemaVersion = 4 - product = "ARSAS" - releaseTag = $env:RELEASE_TAG - releaseRecovery = $true - runtimeRebuilt = $false - runtimeAuthority = [ordered]@{ - applicationCommit = $env:RUNTIME_APP_COMMIT - engineCommit = $env:RUNTIME_ENGINE_COMMIT - portableSha256 = $env:GOLDEN_EXE_SHA - portableSizeBytes = [int64]$env:GOLDEN_EXE_SIZE - sourceArtifactId = [int64]$env:SOURCE_GOLDEN_ARTIFACT_ID - sourceArtifactZipSha256 = $env:SOURCE_GOLDEN_ZIP_SHA - } - installer = [ordered]@{ - name = $env:INSTALLER_NAME - sha256 = $env:INSTALLER_SHA - sizeBytes = [int64]$env:INSTALLER_SIZE - testedArtifactId = [int64]$env:INSTALLER_ARTIFACT_ID - testedArtifactName = $env:INSTALLER_ARTIFACT_NAME - testedArtifactZipSha256 = $env:INSTALLER_ARTIFACT_ZIP_SHA - installedRuntimeSha256 = $env:GOLDEN_EXE_SHA - runtimeRebuilt = $false - } - releaseTagReferenceObservedAtPublication = [string]$release.target_commitish - note = "The installer packages the exact field-verified golden single-file runtime without rebuilding it. Git tag history was not rewritten." - } - $provenance | ConvertTo-Json -Depth 8 | Set-Content $provenancePath -Encoding utf8 - - $notesPath = Join-Path $publish "release-notes.md" - @( - "# ARSAS 1.6.38", - "", - "This stable release uses the exact field-verified golden runtime for both Portable and Installer delivery.", - "", - "## Runtime authority", - "", - "- Portable Windows x64 is the exact retained golden runtime.", - "- The Windows installer packages that same runtime without rebuilding application code.", - "- Silent-install verification proves installed ARSAS.exe is byte-identical to the golden portable runtime.", - "- The installer and portable hashes are published together in SHA-256 checksums.", - "- Git tag history was not rewritten.", - "", - "## Included assets", - "", - "- ARSAS-Windows-x64-Setup.exe", - "- ARSAS-Windows-x64-Portable.exe", - "- ARSAS-Windows-x64-SHA256SUMS.txt", - "- ARSAS-Windows-x64-PROVENANCE.json" - ) | Set-Content $notesPath -Encoding utf8 - - "PUBLISH_DIR=$publish" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "CHECKSUM_PATH=$checksums" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "PROVENANCE_PATH=$provenancePath" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "RELEASE_NOTES_PATH=$notesPath" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Publish exact tested installer to v1.6.38 - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - $assets = @( - (Join-Path $env:PUBLISH_DIR $env:INSTALLER_NAME), - $env:CHECKSUM_PATH, - $env:PROVENANCE_PATH - ) - gh release upload $env:RELEASE_TAG @assets --repo $env:GITHUB_REPOSITORY --clobber - if ($LASTEXITCODE -ne 0) { throw "Failed to publish exact tested installer assets." } - - gh release edit $env:RELEASE_TAG --repo $env:GITHUB_REPOSITORY --title "ARSAS 1.6.38" --notes-file $env:RELEASE_NOTES_PATH --draft=false --prerelease=false --latest - if ($LASTEXITCODE -ne 0) { throw "Failed to update recovered stable release metadata." } - - - name: Verify public installer and portable bytes after publication - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - $verify = Join-Path $env:PROMOTION_ROOT "public-after" - New-Item -ItemType Directory -Path $verify -Force | Out-Null - gh release download $env:RELEASE_TAG --repo $env:GITHUB_REPOSITORY --dir $verify --pattern "ARSAS-Windows-x64-Setup.exe" --pattern "ARSAS-Windows-x64-Portable.exe" --pattern "ARSAS-Windows-x64-SHA256SUMS.txt" --pattern "ARSAS-Windows-x64-PROVENANCE.json" --clobber - if ($LASTEXITCODE -ne 0) { throw "Failed to download published release assets." } - - $setup = Join-Path $verify "ARSAS-Windows-x64-Setup.exe" - $portable = Join-Path $verify "ARSAS-Windows-x64-Portable.exe" - if ((Get-FileHash $setup -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:INSTALLER_SHA) { - throw "Published installer SHA-256 mismatch." - } - if ((Get-FileHash $portable -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:GOLDEN_EXE_SHA) { - throw "Published portable SHA-256 mismatch." - } - - $release = (gh api "repos/$env:GITHUB_REPOSITORY/releases/tags/$env:RELEASE_TAG") | ConvertFrom-Json - $names = @($release.assets | ForEach-Object name | Sort-Object) - $expected = @("ARSAS-Windows-x64-Portable.exe", "ARSAS-Windows-x64-PROVENANCE.json", "ARSAS-Windows-x64-SHA256SUMS.txt", "ARSAS-Windows-x64-Setup.exe") | Sort-Object - if (($names -join "|") -ne ($expected -join "|")) { - throw "Published release inventory mismatch: $($names -join ', ')." - } - $latest = (gh api "repos/$env:GITHUB_REPOSITORY/releases/latest" --jq ".tag_name").Trim() - if ($latest -ne $env:RELEASE_TAG) { throw "v1.6.38 is no longer latest stable release." } - - "VERIFIED_PUBLIC_CHECKSUMS=$(Join-Path $verify 'ARSAS-Windows-x64-SHA256SUMS.txt')" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - "VERIFIED_PUBLIC_PROVENANCE=$(Join-Path $verify 'ARSAS-Windows-x64-PROVENANCE.json')" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Synchronize repository release evidence after successful publication - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - run: | - $release = (gh api "repos/$env:GITHUB_REPOSITORY/releases/tags/$env:RELEASE_TAG") | ConvertFrom-Json - $checksumsSize = (Get-Item $env:VERIFIED_PUBLIC_CHECKSUMS).Length - $provenanceSize = (Get-Item $env:VERIFIED_PUBLIC_PROVENANCE).Length - $provenanceSha = (Get-FileHash $env:VERIFIED_PUBLIC_PROVENANCE -Algorithm SHA256).Hash.ToLowerInvariant() - $stableRoot = "https://github.com/$env:GITHUB_REPOSITORY/releases/latest/download" - - $record = [ordered]@{ - version = $env:RELEASE_VERSION - tag = $env:RELEASE_TAG - channel = "stable" - publishedAtUtc = [string]$release.published_at - sourceCommit = [string]$release.target_commitish - releaseRecovery = $true - runtimeAuthority = [ordered]@{ - applicationCommit = $env:RUNTIME_APP_COMMIT - engineCommit = $env:RUNTIME_ENGINE_COMMIT - runtimeRebuilt = $false - } - engine = [ordered]@{ - repository = "masarray/ARIEC61850" - commit = $env:RUNTIME_ENGINE_COMMIT - } - installer = [ordered]@{ - name = "ARSAS-Windows-x64-Setup.exe" - url = "$stableRoot/ARSAS-Windows-x64-Setup.exe" - sha256 = $env:INSTALLER_SHA - sizeBytes = [int64]$env:INSTALLER_SIZE - } - portable = [ordered]@{ - name = "ARSAS-Windows-x64-Portable.exe" - url = "$stableRoot/ARSAS-Windows-x64-Portable.exe" - sha256 = $env:GOLDEN_EXE_SHA - sizeBytes = [int64]$env:GOLDEN_EXE_SIZE - } - checksums = [ordered]@{ - name = "ARSAS-Windows-x64-SHA256SUMS.txt" - url = "$stableRoot/ARSAS-Windows-x64-SHA256SUMS.txt" - sizeBytes = $checksumsSize - } - provenance = [ordered]@{ - name = "ARSAS-Windows-x64-PROVENANCE.json" - url = "$stableRoot/ARSAS-Windows-x64-PROVENANCE.json" - sha256 = $provenanceSha - sizeBytes = $provenanceSize - } - codeSigning = [ordered]@{ - status = "unsigned" - platform = "Authenticode" - detail = "The current public Windows installer and portable binary do not carry a commercial Authenticode publisher signature. Verify the published SHA-256 value before use. SmartScreen warnings are therefore possible." - } - } - $record | ConvertTo-Json -Depth 10 | Set-Content ".\.release\published.json" -Encoding utf8 - - $landing = [ordered]@{ - schemaVersion = 1 - product = "ARSAS" - version = $env:RELEASE_VERSION - tag = $env:RELEASE_TAG - channel = "stable" - publishedAtUtc = [string]$release.published_at - sourceCommit = [string]$release.target_commitish - releaseUrl = [string]$release.html_url - releaseRecovery = $true - runtimeAuthority = $record.runtimeAuthority - installer = $record.installer - portable = $record.portable - checksums = $record.checksums - codeSigning = $record.codeSigning - } - $landing | ConvertTo-Json -Depth 10 | Set-Content ".\landing\latest.json" -Encoding utf8 - - $notes = Get-Content ".\landing\release-notes.json" -Raw | ConvertFrom-Json - $notes.summary = "ARSAS 1.6.38 is the recovered stable Windows release using the exact field-verified golden runtime for both Portable and Installer delivery." - $notes.summaryId = "ARSAS 1.6.38 adalah stable release Windows hasil recovery yang memakai runtime golden terverifikasi di lapangan untuk Portable dan Installer." - $notes.highlights = @( - "Polish WPF typography with embedded Inter and smooth rendering", - "convergence(discovery/scl): reference-parity discovery and usable SCL", - "fix(ci): restore R10 post-merge release gates", - "Portable and Installer now use the same exact field-verified golden runtime; installer packaging does not rebuild application code." - ) - $notes.highlightsId = @( - "Perubahan rilis: Polish WPF typography with embedded Inter and smooth rendering", - "Perubahan rilis: convergence(discovery/scl): reference-parity discovery and usable SCL", - "Perubahan rilis: fix(ci): restore R10 post-merge release gates", - "Portable dan Installer sekarang memakai runtime golden terverifikasi yang sama persis; packaging installer tidak membangun ulang kode aplikasi." - ) - $notes.improvements = @( - "Stable release identity, package size and SHA-256 are sourced from verified release evidence.", - "The installer packages the exact tested portable runtime and verifies the installed ARSAS.exe SHA-256 before publication.", - "Installer and portable download URLs use the latest stable GitHub Release authority.", - "Release publication remains fail-closed if any tested artifact, installed runtime or public download hash diverges." - ) - $notes.improvementsId = @( - "Identitas stable release, ukuran paket, dan SHA-256 diambil dari evidence release terverifikasi.", - "Installer memaketkan runtime portable yang sudah diuji persis dan memverifikasi SHA-256 ARSAS.exe hasil instalasi sebelum publikasi.", - "URL installer dan portable memakai authority GitHub Release stabil terbaru.", - "Publikasi release bersifat fail-closed jika artifact teruji, runtime hasil instalasi, atau hash download publik berbeda." - ) - $notes.codeSigning.status = "unsigned" - $notes.codeSigning.label = "Not Authenticode-signed" - $notes.codeSigning.labelId = "Belum ditandatangani dengan Authenticode" - $notes.codeSigning.detail = "The current public Windows installer and portable binary do not carry a commercial Authenticode publisher signature. Verify the published SHA-256 value before use. SmartScreen warnings are therefore possible." - $notes.codeSigning.detailId = "Installer Windows dan portable EXE publik saat ini belum memiliki commercial Authenticode publisher signature. Verifikasi nilai SHA-256 yang dipublikasikan sebelum digunakan. Peringatan SmartScreen masih mungkin muncul." - $notes.screenshot.caption = "ARSAS 1.6.38 stable Windows release with the recovered field-verified golden runtime in both Installer and Portable delivery." - $notes.screenshot.captionId = "Stable release Windows ARSAS 1.6.38 dengan runtime golden hasil recovery yang terverifikasi di lapangan pada Installer dan Portable." - $notes | ConvertTo-Json -Depth 10 | Set-Content ".\landing\release-notes.json" -Encoding utf8 - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add .release/published.json landing/latest.json landing/release-notes.json - git diff --cached --quiet - if ($LASTEXITCODE -ne 0) { - git commit -m "docs: synchronize recovered ARSAS 1.6.38 installer evidence" - git fetch origin main - git rebase origin/main - git push origin HEAD:main - if ($LASTEXITCODE -ne 0) { throw "Failed to publish synchronized installer release evidence." } - } - - - name: Stage installer promotion audit bundle - if: always() - shell: pwsh - run: | - $audit = Join-Path $env:GITHUB_WORKSPACE "_promotion-audit" - New-Item -ItemType Directory -Path $audit -Force | Out-Null - Copy-Item ".\.release\recover-v1.6.38-golden-installer.json" (Join-Path $audit "recover-v1.6.38-golden-installer.json") -Force - - if (-not [string]::IsNullOrWhiteSpace($env:PROMOTION_ROOT)) { - $candidateEvidence = Join-Path $env:PROMOTION_ROOT "extracted\ARSAS-v1.6.38-golden-installer-evidence.json" - if (Test-Path $candidateEvidence -PathType Leaf) { - Copy-Item $candidateEvidence (Join-Path $audit "ARSAS-v1.6.38-golden-installer-evidence.json") -Force - } - } - - if (-not [string]::IsNullOrWhiteSpace($env:PUBLISH_DIR)) { - foreach ($name in @("ARSAS-Windows-x64-SHA256SUMS.txt", "ARSAS-Windows-x64-PROVENANCE.json", "release-notes.md")) { - $source = Join-Path $env:PUBLISH_DIR $name - if (Test-Path $source -PathType Leaf) { - Copy-Item $source (Join-Path $audit $name) -Force - } - } - } - - - name: Upload installer promotion audit bundle - if: always() - uses: actions/upload-artifact@v4 - with: - name: ARSAS-v1.6.38-golden-installer-promotion-evidence - retention-days: 90 - if-no-files-found: warn - path: _promotion-audit From 1df25f012308179a9feee6e1510a668614dd09d6 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 12:26:10 +0700 Subject: [PATCH 3/7] chore(release): retire historical v1.6.38 workflow recover-v1.6.38-golden.yml --- .github/workflows/recover-v1.6.38-golden.yml | 246 ------------------- 1 file changed, 246 deletions(-) delete mode 100644 .github/workflows/recover-v1.6.38-golden.yml diff --git a/.github/workflows/recover-v1.6.38-golden.yml b/.github/workflows/recover-v1.6.38-golden.yml deleted file mode 100644 index aa18b5a0b..000000000 --- a/.github/workflows/recover-v1.6.38-golden.yml +++ /dev/null @@ -1,246 +0,0 @@ -name: Recover ARSAS v1.6.38 golden runtime - -on: - push: - branches: [ main ] - paths: - - ".release/recover-v1.6.38-golden.json" - - ".github/workflows/recover-v1.6.38-golden.yml" - workflow_dispatch: - -permissions: - actions: read - contents: write - -concurrency: - group: recover-v1.6.38-golden-runtime - cancel-in-progress: false - -jobs: - recover: - name: Verify exact golden artifact and recover stable release - runs-on: ubuntu-latest - steps: - - name: Checkout recovery request - uses: actions/checkout@v4 - - - name: Read fail-closed recovery request - id: request - shell: bash - run: | - set -euo pipefail - python - <<'PY' >> "$GITHUB_OUTPUT" - import json - from pathlib import Path - - p = Path(".release/recover-v1.6.38-golden.json") - value = json.loads(p.read_text()) - expected = { - "schemaVersion": 1, - "tag": "v1.6.38", - "actionsArtifactId": 10549589733, - "artifactName": "ARSAS-r7-scl-interoperability-win-x64", - "goldenZipSha256": "d68b9e89487cfd71bf92ac181e5abed35106ea65776a6005eaa863eb6cdc0068", - "goldenExeSha256": "555c3d91dbda85cb1b3de453266b33dd26a95cd06fe6805ccb973ed62d19487a", - "goldenExePath": "dist/ARSAS-1.6.37-win-x64-portable.exe", - "applicationCommit": "eb8eb13d491f9aa265205852b8a4bab07af440ff", - "engineCommit": "9935d6902d786cc69b299260fe36b835944d5e81", - } - for key, expected_value in expected.items(): - actual = value.get(key) - if actual != expected_value: - raise SystemExit(f"Recovery request mismatch for {key}: {actual!r} != {expected_value!r}") - print(f"tag={value['tag']}") - print(f"artifact_id={value['actionsArtifactId']}") - print(f"artifact_name={value['artifactName']}") - print(f"zip_sha={value['goldenZipSha256']}") - print(f"exe_sha={value['goldenExeSha256']}") - print(f"exe_path={value['goldenExePath']}") - print(f"app_commit={value['applicationCommit']}") - print(f"engine_commit={value['engineCommit']}") - PY - - - name: Snapshot current public release before mutation - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.request.outputs.tag }} - shell: bash - run: | - set -euo pipefail - mkdir -p recovery/backup - gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json tagName,name,isDraft,isPrerelease,body,assets > recovery/backup/release-before.json - gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir recovery/backup/assets || true - - - name: Download exact retained Actions artifact - env: - GH_TOKEN: ${{ github.token }} - ARTIFACT_ID: ${{ steps.request.outputs.artifact_id }} - shell: bash - run: | - set -euo pipefail - mkdir -p recovery/golden - gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$ARTIFACT_ID/zip" > recovery/golden/artifact.zip - - - name: Verify golden ZIP, binary and provenance fingerprints - env: - ZIP_SHA: ${{ steps.request.outputs.zip_sha }} - EXE_SHA: ${{ steps.request.outputs.exe_sha }} - EXE_PATH: ${{ steps.request.outputs.exe_path }} - APP_COMMIT: ${{ steps.request.outputs.app_commit }} - ENGINE_COMMIT: ${{ steps.request.outputs.engine_commit }} - shell: bash - run: | - set -euo pipefail - actual_zip="$(sha256sum recovery/golden/artifact.zip | awk '{print $1}')" - test "$actual_zip" = "$ZIP_SHA" - - mkdir -p recovery/golden/extracted - unzip -q recovery/golden/artifact.zip -d recovery/golden/extracted - source_exe="recovery/golden/extracted/$EXE_PATH" - test -s "$source_exe" - - actual_exe="$(sha256sum "$source_exe" | awk '{print $1}')" - test "$actual_exe" = "$EXE_SHA" - - manifest="recovery/golden/extracted/dist/R7-SCL-INTEROP-BUILD.txt" - test -s "$manifest" - grep -Fq "ARSAS commit: $APP_COMMIT" "$manifest" - grep -Fq "ARIEC61850 commit: $ENGINE_COMMIT" "$manifest" - grep -Fq "ARSAS R7 SCL interoperability field-test build" "$manifest" - - - name: Stage recovered public assets - env: - GH_TOKEN: ${{ github.token }} - EXE_PATH: ${{ steps.request.outputs.exe_path }} - EXE_SHA: ${{ steps.request.outputs.exe_sha }} - APP_COMMIT: ${{ steps.request.outputs.app_commit }} - ENGINE_COMMIT: ${{ steps.request.outputs.engine_commit }} - ARTIFACT_ID: ${{ steps.request.outputs.artifact_id }} - ARTIFACT_NAME: ${{ steps.request.outputs.artifact_name }} - ZIP_SHA: ${{ steps.request.outputs.zip_sha }} - TAG: ${{ steps.request.outputs.tag }} - shell: bash - run: | - set -euo pipefail - mkdir -p recovery/publish - cp "recovery/golden/extracted/$EXE_PATH" recovery/publish/ARSAS-Windows-x64-Portable.exe - - printf '%s %s\n' "$EXE_SHA" "ARSAS-Windows-x64-Portable.exe" > recovery/publish/ARSAS-Windows-x64-SHA256SUMS.txt - - tag_target="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --jq .object.sha 2>/dev/null || true)" - if [ -z "$tag_target" ]; then - tag_target="$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$TAG" --jq .target_commitish)" - fi - - python - < recovery/publish/ARSAS-Windows-x64-PROVENANCE.json - import json - print(json.dumps({ - "schemaVersion": 3, - "product": "ARSAS", - "releaseTag": "$TAG", - "releaseRecovery": True, - "runtimeApplicationCommit": "$APP_COMMIT", - "runtimeEngineCommit": "$ENGINE_COMMIT", - "sourceArtifact": { - "kind": "github-actions-artifact", - "id": int("$ARTIFACT_ID"), - "name": "$ARTIFACT_NAME", - "zipSha256": "$ZIP_SHA" - }, - "portable": { - "name": "ARSAS-Windows-x64-Portable.exe", - "sha256": "$EXE_SHA" - }, - "releaseTagReferenceObservedAtRecovery": "$tag_target", - "note": "Recovered from the exact retained field-verified runtime artifact. The tag history was not rewritten. Installer and stale supply-chain assets were intentionally removed because they did not represent this exact runtime." - }, indent=2)) - PY - - cat > recovery/publish/release-notes.md <<'EOF' - # ARSAS 1.6.38 - - This stable download was recovered to the exact field-verified runtime artifact after a release-build routing regression was identified. - - ## Recovery status - - - Portable Windows x64 is the exact retained golden runtime artifact. - - The published binary is verified by SHA-256 before and after release upload. - - The previous installer was removed because it was built through a different runtime path. - - Previous SBOM/provenance assets were removed because they described the replaced binaries. - - Git tag history was not rewritten. - - Source-line recovery remains a separate change and must pass fresh physical verification before replacing this binary authority. - - ## Included assets - - - ARSAS-Windows-x64-Portable.exe - - ARSAS-Windows-x64-SHA256SUMS.txt - - ARSAS-Windows-x64-PROVENANCE.json - EOF - - - name: Replace stale v1.6.38 assets with exact golden runtime - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.request.outputs.tag }} - shell: bash - run: | - set -euo pipefail - - for asset in ARSAS-Windows-x64-Portable.exe ARSAS-Windows-x64-Setup.exe ARSAS-Windows-x64-SHA256SUMS.txt ARSAS-Windows-x64-SBOM.spdx.json ARSAS-Windows-x64-PROVENANCE.json; do - if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq ".assets[].name" | grep -Fxq "$asset"; then - gh release delete-asset "$TAG" "$asset" --repo "$GITHUB_REPOSITORY" --yes - fi - done - - gh release upload "$TAG" recovery/publish/ARSAS-Windows-x64-Portable.exe recovery/publish/ARSAS-Windows-x64-SHA256SUMS.txt recovery/publish/ARSAS-Windows-x64-PROVENANCE.json --repo "$GITHUB_REPOSITORY" - - gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --title "ARSAS 1.6.38" --notes-file recovery/publish/release-notes.md --draft=false --prerelease=false --latest - - - name: Verify published bytes and release inventory - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.request.outputs.tag }} - EXE_SHA: ${{ steps.request.outputs.exe_sha }} - shell: bash - run: | - set -euo pipefail - mkdir -p recovery/verify - gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern ARSAS-Windows-x64-Portable.exe --dir recovery/verify - actual="$(sha256sum recovery/verify/ARSAS-Windows-x64-Portable.exe | awk '{print $1}')" - test "$actual" = "$EXE_SHA" - - gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json tagName,name,isDraft,isPrerelease,body,assets > recovery/release-after.json - - python - <<'PY' - import json - from pathlib import Path - value = json.loads(Path("recovery/release-after.json").read_text()) - names = {x["name"] for x in value.get("assets", [])} - expected = { - "ARSAS-Windows-x64-Portable.exe", - "ARSAS-Windows-x64-SHA256SUMS.txt", - "ARSAS-Windows-x64-PROVENANCE.json", - } - if names != expected: - raise SystemExit(f"Unexpected release inventory: {sorted(names)}") - if value.get("isDraft") or value.get("isPrerelease"): - raise SystemExit("Recovered release is unexpectedly draft or prerelease") - PY - - latest_tag="$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq .tag_name)" - test "$latest_tag" = "$TAG" - - - name: Preserve recovery audit bundle - if: always() - uses: actions/upload-artifact@v4 - with: - name: ARSAS-v1.6.38-golden-release-recovery-evidence - retention-days: 90 - if-no-files-found: warn - path: | - recovery/backup/release-before.json - recovery/backup/assets - recovery/golden/extracted/dist/R7-SCL-INTEROP-BUILD.txt - recovery/publish/ARSAS-Windows-x64-SHA256SUMS.txt - recovery/publish/ARSAS-Windows-x64-PROVENANCE.json - recovery/publish/release-notes.md - recovery/release-after.json From 904d272f5944b8fac2efb1a6dd94433191a71ed7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 12:26:26 +0700 Subject: [PATCH 4/7] fix(release): make supply-chain backfill additive rather than clobbering stable SBOM --- .github/workflows/release-supply-chain.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-supply-chain.yml b/.github/workflows/release-supply-chain.yml index c3a022574..f015c822e 100644 --- a/.github/workflows/release-supply-chain.yml +++ b/.github/workflows/release-supply-chain.yml @@ -115,7 +115,12 @@ jobs: GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ steps.release.outputs.tag }} shell: bash - run: gh release upload "$RELEASE_TAG" _supply/ARSAS-Windows-x64-SBOM.spdx.json --repo "$GITHUB_REPOSITORY" --clobber + run: # Backfill is additive only. Published stable assets are immutable, even for a manual rerun. + if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' | grep -Fxq 'ARSAS-Windows-x64-SBOM.spdx.json'; then + echo "Stable SBOM already exists for $RELEASE_TAG; refusing to replace published bytes." >&2 + exit 1 + fi + gh release upload "$RELEASE_TAG" _supply/ARSAS-Windows-x64-SBOM.spdx.json --repo "$GITHUB_REPOSITORY" - name: Attest installer artifact digest uses: actions/attest@v4 From 90b6aa312ce597161b450119eaa69241c784874f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 12:26:40 +0700 Subject: [PATCH 5/7] fix(ci): restore valid shell block for fail-closed SBOM backfill --- .github/workflows/release-supply-chain.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-supply-chain.yml b/.github/workflows/release-supply-chain.yml index f015c822e..fdcc2a0c2 100644 --- a/.github/workflows/release-supply-chain.yml +++ b/.github/workflows/release-supply-chain.yml @@ -115,12 +115,14 @@ jobs: GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ steps.release.outputs.tag }} shell: bash - run: # Backfill is additive only. Published stable assets are immutable, even for a manual rerun. - if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' | grep -Fxq 'ARSAS-Windows-x64-SBOM.spdx.json'; then - echo "Stable SBOM already exists for $RELEASE_TAG; refusing to replace published bytes." >&2 - exit 1 - fi - gh release upload "$RELEASE_TAG" _supply/ARSAS-Windows-x64-SBOM.spdx.json --repo "$GITHUB_REPOSITORY" + run: | + set -euo pipefail + # Backfill is additive only. Published stable assets are immutable, even for a manual rerun. + if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' | grep -Fxq 'ARSAS-Windows-x64-SBOM.spdx.json'; then + echo "Stable SBOM already exists for $RELEASE_TAG; refusing to replace published bytes." >&2 + exit 1 + fi + gh release upload "$RELEASE_TAG" _supply/ARSAS-Windows-x64-SBOM.spdx.json --repo "$GITHUB_REPOSITORY" - name: Attest installer artifact digest uses: actions/attest@v4 From ed7556f5c887c0ca966443949b463ab1614a3186 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 12:27:01 +0700 Subject: [PATCH 6/7] docs(release): document single stable publisher and immutable historical provenance --- docs/WINDOWS_RELEASES.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/WINDOWS_RELEASES.md b/docs/WINDOWS_RELEASES.md index c9d2315fb..7fe9c2bb8 100644 --- a/docs/WINDOWS_RELEASES.md +++ b/docs/WINDOWS_RELEASES.md @@ -24,10 +24,16 @@ The release workflow: 9. compiles the Windows installer and performs silent install/uninstall smoke validation; 10. creates SHA-256 checksums, SPDX 2.3 SBOM, and provenance evidence; 11. creates GitHub artifact attestations for the public Windows binaries; -12. creates or updates the stable GitHub Release and uploads the public assets. +12. creates a new stable GitHub Release with the public assets; existing tags/assets are immutable and must not be overwritten. The workflow explicitly rejects legacy `ardirec.exe` and Qt runtime files from official packaging. ARSAS uses the pinned in-process `ardirec_bridge.dll` contract instead. +## Historical publication workflows + +The v1.6.38 golden-installer build/publisher and golden-runtime recovery workflows were one-off recovery mechanisms, not the ongoing release authority. Their tracked workflow definitions have been retired from the current tree because they could replace old published assets or incorrectly mark an older release as latest. Their immutable history and `.release/recover-v1.6.38-golden*.json` evidence remain available for audit; retirement does not rewrite published history or change the v1.6.40 binary. + +The ongoing publisher is `.github/workflows/release-windows.yml`, governed by the reviewed `.release/windows.json` request and pinned app/engine/bridge source. The alternative verified-artifact publisher `.github/workflows/publish-verified-release.yml` refuses to overwrite an existing tag. Manual supply-chain backfill is additive only and refuses replacement of an existing published SBOM. Publication metadata and the website must follow the verified release rather than become a separate publication authority. + ## Public assets A successful stable release publishes these stable asset names: @@ -44,7 +50,7 @@ Versioned build artifacts may also exist inside the workflow run, but public doc For a controlled manual run, use **Actions → Release ARSAS Windows packages → Run workflow**. Supply a semantic version matching the checked-out ARSAS metadata and choose whether the workflow should publish a GitHub Release. -A manual run with publication disabled is useful for packaging verification, but it is **not** a public stable release and must not be used to invent `landing/latest.json` evidence. +A manual run with publication disabled is useful for packaging verification, but it is **not** a public stable release and must not be used to invent `landing/latest.json` evidence. An existing tag can be verified, but it must not be republished with different bytes or a different source; use a new version for changed packages. ## Installer behavior @@ -83,7 +89,7 @@ Prerequisites: Use the repository packaging scripts rather than hand-assembling a release folder. For example: ```powershell -.\scripts\publish-windows-portable.ps1 -Version 1.6.37 +.\scripts\publish-windows-portable.ps1 -Version 1.6.40 .\scripts\build-windows-installer.ps1 -Version 1.6.37 -Runtime win-x64 ``` From 2663dc8ba0bbd577f6e4118a08f645d0ec95da1b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 24 Sep 2026 12:27:22 +0700 Subject: [PATCH 7/7] test(release): lock retired legacy workflows and evidence preservation --- .../LegacyReleaseWorkflowRetirementTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/ARSAS.Tests/LegacyReleaseWorkflowRetirementTests.cs diff --git a/tests/ARSAS.Tests/LegacyReleaseWorkflowRetirementTests.cs b/tests/ARSAS.Tests/LegacyReleaseWorkflowRetirementTests.cs new file mode 100644 index 000000000..05ebfbf83 --- /dev/null +++ b/tests/ARSAS.Tests/LegacyReleaseWorkflowRetirementTests.cs @@ -0,0 +1,52 @@ +namespace ARSAS.Tests; + +/// +/// Prevents one-off historical recovery jobs from regaining release-write authority. +/// Their evidence JSON is retained; published v1.6.40 assets are never rewritten. +/// +public sealed class LegacyReleaseWorkflowRetirementTests +{ + [Theory] + [InlineData(".github/workflows/build-v1.6.38-golden-installer.yml")] + [InlineData(".github/workflows/publish-v1.6.38-golden-installer.yml")] + [InlineData(".github/workflows/recover-v1.6.38-golden.yml")] + public void OneOffHistoricalWorkflow_IsNotAnActiveActionsEntrypoint(string relativePath) + => Assert.False(File.Exists(RepositoryPath(relativePath)), relativePath); + + [Theory] + [InlineData(".release/recover-v1.6.38-golden.json")] + [InlineData(".release/recover-v1.6.38-golden-installer.json")] + [InlineData("evidence/v1.6.39-physical-rejection.json")] + [InlineData("evidence/v1.6.40-installed-release-field-verification.json")] + public void HistoricalAcceptanceOrRejectionEvidence_IsPreserved(string relativePath) + => Assert.True(File.Exists(RepositoryPath(relativePath)), relativePath); + + [Fact] + public void ActiveReleasePublishers_DoNotClobberPublishedAssets() + { + var canonical = File.ReadAllText(RepositoryPath(".github/workflows/release-windows.yml")); + var verified = File.ReadAllText(RepositoryPath(".github/workflows/publish-verified-release.yml")); + var backfill = File.ReadAllText(RepositoryPath(".github/workflows/release-supply-chain.yml")); + + Assert.Contains("RELEASE_ALREADY_PUBLISHED=true", canonical, StringComparison.Ordinal); + Assert.DoesNotContain("gh release upload", canonical, StringComparison.Ordinal); + Assert.Contains("Refusing to overwrite existing stable release", verified, StringComparison.Ordinal); + Assert.DoesNotContain("gh release upload", verified, StringComparison.Ordinal); + Assert.Contains("refusing to replace published bytes", backfill, StringComparison.Ordinal); + Assert.DoesNotContain("gh release delete-asset", backfill, StringComparison.Ordinal); + Assert.DoesNotContain("--clobber", backfill, StringComparison.Ordinal); + } + + private static string RepositoryPath(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + if (File.Exists(Path.Combine(directory.FullName, "ArIED61850Tester.sln"))) + return Path.Combine(directory.FullName, relativePath); + directory = directory.Parent; + } + + throw new DirectoryNotFoundException("Could not locate ARSAS repository root."); + } +}