diff --git a/.github/workflows/gateway-msix.yml b/.github/workflows/gateway-msix.yml index 624919d0..63f9cb20 100644 --- a/.github/workflows/gateway-msix.yml +++ b/.github/workflows/gateway-msix.yml @@ -10,7 +10,7 @@ on: openclaw_ref: description: openclaw/openclaw tag, branch, or commit to package required: true - default: 0965053fe6b9341776df147a6934b7485c60b5ca + default: 3a9d69db306cd7f081e06254cb89c4bcc14a7107 type: string signing_mode: description: Package signing mode @@ -31,7 +31,7 @@ permissions: pull-requests: read env: - OPENCLAW_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.openclaw_ref || '0965053fe6b9341776df147a6934b7485c60b5ca' }} + OPENCLAW_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.openclaw_ref || '3a9d69db306cd7f081e06254cb89c4bcc14a7107' }} PACKAGING_ROOT: . jobs: @@ -40,6 +40,7 @@ jobs: runs-on: ubuntu-latest outputs: packaging: ${{ steps.filter.outputs.packaging }} + versioning: ${{ steps.filter.outputs.versioning }} steps: - uses: actions/checkout@v6 @@ -51,6 +52,7 @@ jobs: run: | if ($env:GITHUB_EVENT_NAME -ne 'pull_request') { 'packaging=true' >> $env:GITHUB_OUTPUT + 'versioning=false' >> $env:GITHUB_OUTPUT return } @@ -68,6 +70,25 @@ jobs: -FileListPath $fileListPath "packaging=$packaging" >> $env:GITHUB_OUTPUT + $pages = Get-Content -LiteralPath $fileListPath -Raw | + ConvertFrom-Json + $versioningPaths = @( + 'release-policy.json' + 'scripts/Get-MSIXReleaseIdentity.ps1' + 'scripts/Test-MSIXReleaseIdentity.Tests.ps1' + 'scripts/Test-MSIXUpgrade.ps1' + 'scripts/msix-upgrade-baselines.json' + ) + $versioning = 'false' + foreach ($page in @($pages)) { + foreach ($file in @($page)) { + if ([string]$file.filename -in $versioningPaths) { + $versioning = 'true' + } + } + } + "versioning=$versioning" >> $env:GITHUB_OUTPUT + test-host: name: Test Gateway MSIX host runs-on: windows-latest @@ -164,6 +185,11 @@ jobs: run: > .\scripts\Test-Deploy-LocalPackage.Tests.ps1 + - name: Test MSIX release identity + shell: pwsh + run: > + .\scripts\Test-MSIXReleaseIdentity.Tests.ps1 + - name: Test MSIX bundle build shell: pwsh run: > @@ -328,6 +354,7 @@ jobs: build-msix: name: Build unsigned ${{ matrix.architecture }} Gateway MSIX needs: + - changes - test-host - build-package runs-on: windows-latest @@ -423,15 +450,22 @@ jobs: shell: pwsh env: SIGNING_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode || 'unsigned' }} + VERSIONING_CHANGE: ${{ needs.changes.outputs.versioning }} run: | $versionParameters = @{ RunNumber = '${{ github.run_number }}' RunAttempt = '${{ github.run_attempt }}' } - if ($env:SIGNING_MODE -eq 'official') { + if ( + $env:SIGNING_MODE -eq 'official' -or + $env:VERSIONING_CHANGE -eq 'true' + ) { $policy = Get-Content -LiteralPath .\release-policy.json -Raw | ConvertFrom-Json - $versionParameters.ReleaseVersion = [string]$policy.packageVersion + $identity = .\scripts\Get-MSIXReleaseIdentity.ps1 ` + -GatewayTag ([string]$policy.gatewayTag) ` + -MSIXRevision ([int]$policy.msixRevision) + $versionParameters.ReleaseVersion = $identity.PackageVersion } $packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 ` @versionParameters @@ -504,7 +538,9 @@ jobs: build-msix-bundle: name: Build unsigned multi-architecture Gateway MSIX bundle - needs: build-msix + needs: + - changes + - build-msix runs-on: windows-latest steps: - name: Check out repository @@ -528,15 +564,22 @@ jobs: shell: pwsh env: SIGNING_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode || 'unsigned' }} + VERSIONING_CHANGE: ${{ needs.changes.outputs.versioning }} run: | $versionParameters = @{ RunNumber = '${{ github.run_number }}' RunAttempt = '${{ github.run_attempt }}' } - if ($env:SIGNING_MODE -eq 'official') { + if ( + $env:SIGNING_MODE -eq 'official' -or + $env:VERSIONING_CHANGE -eq 'true' + ) { $policy = Get-Content -LiteralPath .\release-policy.json -Raw | ConvertFrom-Json - $versionParameters.ReleaseVersion = [string]$policy.packageVersion + $identity = .\scripts\Get-MSIXReleaseIdentity.ps1 ` + -GatewayTag ([string]$policy.gatewayTag) ` + -MSIXRevision ([int]$policy.msixRevision) + $versionParameters.ReleaseVersion = $identity.PackageVersion } $packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 ` @versionParameters @@ -555,6 +598,86 @@ jobs: if-no-files-found: error retention-days: 7 + test-msix-upgrades: + name: Test proof-release MSIX upgrades + if: ${{ github.event_name == 'pull_request' && needs.changes.outputs.versioning == 'true' }} + needs: + - changes + - build-msix + - build-msix-bundle + runs-on: windows-latest + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Download unsigned x64 candidate + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-unsigned-x64 + path: artifacts\x64 + + - name: Download unsigned bundle candidate + uses: actions/download-artifact@v8 + with: + name: openclaw-gateway-msix-unsigned-bundle + path: artifacts\bundle + + - name: Apply temporary test signature + shell: pwsh + run: | + .\scripts\Sign-TestMSIX.ps1 ` + -ArtifactsDirectory artifacts ` + -OutputDirectory test-signed + + - name: Download hash-pinned proof releases + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $baselines = Get-Content ` + -LiteralPath .\scripts\msix-upgrade-baselines.json ` + -Raw | + ConvertFrom-Json + New-Item -Path baselines -ItemType Directory -Force | Out-Null + foreach ($baseline in $baselines.baselines) { + & gh release download ([string]$baseline.releaseTag) ` + --repo $env:GITHUB_REPOSITORY ` + --pattern ([string]$baseline.assetName) ` + --dir baselines + if ($LASTEXITCODE -ne 0) { + throw "Unable to download $($baseline.releaseTag) upgrade baseline." + } + } + + - name: Test installed-package upgrades and retained LocalState + shell: pwsh + run: | + $policy = Get-Content -LiteralPath .\release-policy.json -Raw | + ConvertFrom-Json + $identity = .\scripts\Get-MSIXReleaseIdentity.ps1 ` + -GatewayTag ([string]$policy.gatewayTag) ` + -MSIXRevision ([int]$policy.msixRevision) + .\scripts\Test-MSIXUpgrade.ps1 ` + -BaselinesPath .\scripts\msix-upgrade-baselines.json ` + -BaselinesDirectory baselines ` + -CandidatePackagePath test-signed\x64\OpenClawGateway-x64.msix ` + -CandidateBundlePath test-signed\bundle\OpenClawGateway.msixbundle ` + -CandidateCertificatePath test-signed\x64\OpenClawGateway-test-signing.cer ` + -ExpectedCandidateVersion $identity.PackageVersion ` + -EvidencePath evidence\msix-upgrade-evidence.json + + - name: Upload upgrade evidence + uses: actions/upload-artifact@v7 + with: + name: openclaw-gateway-msix-upgrade-evidence + path: evidence\msix-upgrade-evidence.json + if-no-files-found: error + retention-days: 90 + reject-untrusted-official-signing: name: Reject official signing outside main if: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode == 'official' && github.ref != 'refs/heads/main' }} @@ -612,15 +735,17 @@ jobs: run: | $policy = Get-Content -LiteralPath .\release-policy.json -Raw | ConvertFrom-Json - $releaseTag = ([string]$policy.releaseTag).Trim() + $identity = .\scripts\Get-MSIXReleaseIdentity.ps1 ` + -GatewayTag ([string]$policy.gatewayTag) ` + -MSIXRevision ([int]$policy.msixRevision) $packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 ` -RunNumber '${{ github.run_number }}' ` -RunAttempt '${{ github.run_attempt }}' ` - -ReleaseVersion ([string]$policy.packageVersion) + -ReleaseVersion $identity.PackageVersion "package_version=$packageVersion" >> $env:GITHUB_OUTPUT - "release_tag=$releaseTag" >> $env:GITHUB_OUTPUT - "release_version=$($releaseTag.Substring(1))" >> $env:GITHUB_OUTPUT + "release_tag=$($identity.ReleaseTag)" >> $env:GITHUB_OUTPUT + "release_version=$($identity.ReleaseVersion)" >> $env:GITHUB_OUTPUT - name: Enforce official signing policy shell: pwsh @@ -851,6 +976,7 @@ jobs: - build-msix - test-sign-msix - build-msix-bundle + - test-msix-upgrades - reject-untrusted-official-signing - authorize-signing - sign-msix diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87afd115..e28271a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,7 @@ or package version logic: .\scripts\Test-PackagingRelevance.Tests.ps1 .\scripts\Test-OpenClawCacheKey.Tests.ps1 .\scripts\Test-OpenClawPackage.Tests.ps1 +.\scripts\Test-MSIXReleaseIdentity.Tests.ps1 .\scripts\Test-WorkflowPackageVersion.Tests.ps1 .\scripts\Test-GitHooks.Tests.ps1 ``` @@ -183,6 +184,19 @@ bypassable, and required CI checks remain authoritative. - Metadata files are part of the release trust chain. Coordinate changes across payload creation, MSIX creation, signing validation, workflow artifacts, and tests. +- Keep official release identity derived from `gatewayTag` and `msixRevision`. + The unsuffixed Gateway tag owns revision block `1000-1999`; correction tags + `-2` through `-64` own their corresponding 1,000-number blocks. Use revision + `0` for the first MSIX of a Gateway tag and increment only for packaging-only + rebuilds of that exact tag. Do not assign package versions or release tags by + hand. +- Treat published proof releases in `scripts/msix-upgrade-baselines.json` as + immutable transition fixtures. Keep their release asset names and SHA-256 + digests pinned. Version-policy changes must pass the installed-package + upgrade job from every standalone and bundle baseline, retain package + LocalState, and prove both candidate delivery formats install fresh. Run the + harness only on an isolated clean Windows account; it refuses pre-existing + OpenClaw Gateway registrations and cleans up only its own installation. - Use source-generated `System.Text.Json` metadata through `OpenClawJsonContext`. The launcher is NativeAOT and must not introduce reflection-based serialization. diff --git a/README.md b/README.md index 1a519484..d73701b2 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,9 @@ The payload artifact records the requested ref and resolved upstream commit in OpenClaw commit, while embedded `payload-files.json` records every packaged application file's path, length, and SHA-256. -`release-policy.json` records the immutable OpenClaw commit and payload version -approved for official signing, plus the independent MSIX package version and -release tag. Updating that +`release-policy.json` records the immutable OpenClaw commit and Gateway tag +approved for official signing, plus an independent MSIX packaging revision. +Updating that policy requires a reviewed repository change. Official signing runs only from `main` and verifies the workflow input, policy-approved package version, both architecture metadata files, both MSIX hashes, the embedded manifests, and @@ -200,8 +200,8 @@ they do not represent the default-disabled state of a normal install. Full selected-theme cohesion requires the generic plugin-frame theme forwarding merged by [`openclaw/openclaw#145409`](https://github.com/openclaw/openclaw/pull/145409). -The current workflow remains on the release-approved OpenClaw baseline -`0965053fe6b9341776df147a6934b7485c60b5ca` while this plugin is disabled by +The current workflow remains on the release-approved OpenClaw `v2026.9.4` +baseline (`3a9d69db306cd7f081e06254cb89c4bcc14a7107`) while this plugin is disabled by default. That baseline packages and inspects the plugin safely but does not forward selected Control UI themes into plugin frames. The future launcher enablement change must also advance and qualify the runtime to the merged theme @@ -292,23 +292,66 @@ Test-signing private keys are generated only on the temporary GitHub runner and are deleted before artifacts are uploaded. No signing secret or private key is stored in the repository. -Official releases use the independent four-part numeric `packageVersion` and -`releaseTag` from `release-policy.json`. The initial signing proof uses package -version `0.0.0.0` and tag `v0.0.0.0`; a later policy change can establish the -long-term Gateway-to-MSIX version mapping. The workflow creates the tag in this -repository and a GitHub Release with generated release notes. Each release -contains a signed, multi-architecture +Official releases derive their GitHub tag and four-part numeric MSIX identity +from `gatewayTag` and `msixRevision` in `release-policy.json`. The GitHub tag is +`-msix.`. The MSIX identity is +`year.month.patch.(gateway-release-sequence * 1000 + msix-revision)`. + +| Gateway tag | MSIX revision | GitHub release tag | MSIX version | +|---|---:|---|---| +| `v2026.7.1` | `0` | `v2026.7.1-msix.0` | `2026.7.1.1000` | +| `v2026.7.1-2` | `0` | `v2026.7.1-2-msix.0` | `2026.7.1.2000` | +| `v2026.7.1-2` | `1` | `v2026.7.1-2-msix.1` | `2026.7.1.2001` | +| `v2026.7.2` | `0` | `v2026.7.2-msix.0` | `2026.7.2.1000` | + +The unsuffixed Gateway tag is release sequence `1`; correction suffixes `-2` +through `-64` use their numeric suffix as the sequence. A `-1` suffix is +rejected because it would collide with the unsuffixed tag. Set `msixRevision` +from `0` through `999`, starting at `0` for each Gateway tag and incrementing it +only when that exact Gateway tag is repackaged. Each Gateway release therefore +owns a deterministic 1,000-number block, and an MSIX-only rebuild cannot shift +the version assigned to a later Gateway correction or patch. + +To prepare an official release, update these policy inputs together in a +reviewed pull request: + +1. `gatewayTag` to the stable upstream Gateway tag; +2. `approvedCommit` to the immutable commit resolved from that tag; +3. `payloadPackageVersion` to the version reported by the pinned payload; +4. `msixRevision` to `0`, or increment it for a packaging-only rebuild of the + same Gateway tag; +5. the workflow's `openclaw_ref` default and non-manual fallback to the same + `approvedCommit`. + +After that pull request merges, manually run **Build OpenClaw Gateway MSIX** on +`main` with `openclaw_ref` set to the approved commit and `signing_mode` set to +`official`. The workflow derives the package version and release tag, creates +the tag in this repository, and publishes a GitHub Release with generated +release notes. Each release contains a signed, multi-architecture `OpenClawGateway-.msixbundle` as the recommended download, plus signed `OpenClawGateway--x64.msix` and `OpenClawGateway--arm64.msix` packages for architecture-specific deployment. The duplicate GitHub Actions artifacts remain short-lived transport and diagnostic copies. -For the all-zero proof only, MakeAppx assigns the outer bundle identity its -date/time-based version because it does not preserve `0.0.0.0` as a bundle -version. The two embedded architecture packages retain identity version -`0.0.0.0`; signing authorization verifies those versions and byte-compares both -embedded packages with the approved standalone inputs. +Microsoft Store submissions reserve the fourth version component as zero, so +Store publication will need its own version policy when it is introduced. + +The signed `v0.0.0.0` and `v0.0.0.1` proof releases are not production version +identities, but they are retained as transition baselines. Pull requests that +change release versioning download the hash-pinned standalone x64 and +recommended `.msixbundle` assets, install each one on a clean GitHub-hosted +Windows runner, upgrade it in place through the same delivery format, and +verify that the package family remains stable and a LocalState marker is +retained. The gate also proves fresh installation of both the standalone and +bundle candidates. It refuses to run when an OpenClaw Gateway package is +already registered and removes only packages installed by that test +invocation. It temporarily trusts the ephemeral test-signing certificate in +the local-machine Trusted People store, as required by Windows deployment, and +removes that certificate in `finally`. The resulting JSON evidence is retained +as a workflow artifact for 90 days. Future versioning schemes must keep this +transition gate green or explicitly document and obtain approval for a +breaking reset. An `.msixbundle` is a single installable container for the x64 and ARM64 MSIX packages; Windows selects the package appropriate for the device. An diff --git a/release-policy.json b/release-policy.json index 33c1adb7..12ad1d78 100644 --- a/release-policy.json +++ b/release-policy.json @@ -1,8 +1,8 @@ { "repository": "https://github.com/openclaw/openclaw", - "releaseTag": "v0.0.0.1", - "packageVersion": "0.0.0.1", - "payloadPackageVersion": "2026.8.2", - "approvedCommit": "0965053fe6b9341776df147a6934b7485c60b5ca", + "gatewayTag": "v2026.9.4", + "msixRevision": 0, + "payloadPackageVersion": "2026.9.4", + "approvedCommit": "3a9d69db306cd7f081e06254cb89c4bcc14a7107", "publisher": "CN=OpenClaw Foundation, O=OpenClaw Foundation, L=Mill Valley, S=California, C=US" } diff --git a/scripts/Build-Payload.ps1 b/scripts/Build-Payload.ps1 index 7d8166d4..1e768ebb 100644 --- a/scripts/Build-Payload.ps1 +++ b/scripts/Build-Payload.ps1 @@ -247,13 +247,22 @@ try { -LiteralPath $validationConfigPath ` -Raw | ConvertFrom-Json - $validationEntryNames = @( - $validationConfig.plugins.entries.PSObject.Properties.Name + $explicitlyEnabledEntryNames = @( + foreach ( + $entry in + $validationConfig.plugins.entries.PSObject.Properties + ) { + if ( + $entry.Value.PSObject.Properties.Name -contains 'enabled' -and + $entry.Value.enabled -eq $true + ) { + $entry.Name + } + } ) if ( - $validationEntryNames.Count -ne 1 -or - $validationEntryNames[0] -ne 'gateway-isolation' -or - $validationConfig.plugins.entries.'gateway-isolation'.enabled -ne $true + $explicitlyEnabledEntryNames.Count -ne 1 -or + $explicitlyEnabledEntryNames[0] -ne 'gateway-isolation' ) { throw ( 'The isolated validation configuration must explicitly enable ' + diff --git a/scripts/Get-MSIXReleaseIdentity.ps1 b/scripts/Get-MSIXReleaseIdentity.ps1 new file mode 100644 index 00000000..3730313a --- /dev/null +++ b/scripts/Get-MSIXReleaseIdentity.ps1 @@ -0,0 +1,68 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$GatewayTag, + + [Parameter(Mandatory)] + [int]$MSIXRevision +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$match = [regex]::Match( + $GatewayTag.Trim(), + '^v(?\d{4})\.(?\d{1,2})\.(?\d{1,5})(?:-(?\d+))?$' +) +if (-not $match.Success) { + throw ( + "GatewayTag '$GatewayTag' must be a stable OpenClaw release tag " + + 'such as v2026.9.4 or v2026.7.1-2.' + ) +} + +[int]$year = $match.Groups['year'].Value +[int]$month = $match.Groups['month'].Value +[int]$patch = $match.Groups['patch'].Value +[int]$releaseSequence = if ($match.Groups['correction'].Success) { + [int]$correction = $match.Groups['correction'].Value + if ($correction -lt 2 -or $correction -gt 64) { + throw 'The Gateway correction suffix must be between 2 and 64.' + } + $correction +} +else { + 1 +} + +if ($year -lt 1 -or $year -gt 9999) { + throw 'The Gateway release year must be between 1 and 9999.' +} +if ($month -lt 1 -or $month -gt 12) { + throw 'The Gateway release month must be between 1 and 12.' +} +if ($patch -lt 0 -or $patch -gt 65534) { + throw 'The Gateway patch must be between 0 and 65534.' +} +if ($MSIXRevision -lt 0 -or $MSIXRevision -gt 999) { + throw 'MSIXRevision must be between 0 and 999.' +} + +# Give every Gateway release sequence 1,000 deterministic MSIX revision slots. The +# unsuffixed tag is sequence 1 and correction tags use their numeric suffix. This +# prevents an MSIX-only rebuild from consuming the number assigned to a later +# Gateway correction. +$packageRevision = ($releaseSequence * 1000) + $MSIXRevision +if ($packageRevision -gt 65534) { + throw 'The combined Gateway release sequence and MSIXRevision must not exceed 65534.' +} +$packageVersion = "$year.$month.$patch.$packageRevision" +$releaseTag = "$($GatewayTag.Trim())-msix.$MSIXRevision" + +[pscustomobject]@{ + GatewayTag = $GatewayTag.Trim() + MSIXRevision = $MSIXRevision + PackageVersion = $packageVersion + ReleaseTag = $releaseTag + ReleaseVersion = $releaseTag.Substring(1) +} diff --git a/scripts/Sign-TestMSIX.ps1 b/scripts/Sign-TestMSIX.ps1 index fa1d19f9..5f3ac672 100644 --- a/scripts/Sign-TestMSIX.ps1 +++ b/scripts/Sign-TestMSIX.ps1 @@ -39,11 +39,13 @@ $architectureDirectories = @( } } ) -if ($architectureDirectories.Count -eq 0) { +$bundleDirectory = Join-Path $resolvedArtifactsDirectory 'bundle' +$hasBundleDirectory = Test-Path -LiteralPath $bundleDirectory -PathType Container +if ($architectureDirectories.Count -eq 0 -and -not $hasBundleDirectory) { throw ( - "No architecture directories were found under " + + "No signable package directories were found under " + "'$resolvedArtifactsDirectory'. Expected at least one of: " + - ($architectures -join ', ') + '.' + ($architectures -join ', ') + ', bundle.' ) } @@ -176,6 +178,52 @@ try { ) ` -Encoding utf8 } + + if ($hasBundleDirectory) { + $sourceBundles = @( + Get-ChildItem ` + -LiteralPath $bundleDirectory ` + -Filter '*.msixbundle' ` + -File + ) + if ($sourceBundles.Count -ne 1) { + throw ( + "Expected one unsigned MSIX bundle in '$bundleDirectory'; " + + "found $($sourceBundles.Count)." + ) + } + + $destinationDirectory = Join-Path $OutputDirectory 'bundle' + New-Item ` + -Path $destinationDirectory ` + -ItemType Directory ` + -Force | + Out-Null + $signedBundlePath = Join-Path ` + $destinationDirectory ` + $sourceBundles[0].Name + Copy-Item ` + -LiteralPath $sourceBundles[0].FullName ` + -Destination $signedBundlePath ` + -Force + + & $signtool.FullName sign ` + /fd SHA256 ` + /f $temporaryPfx ` + /p $passwordText ` + $signedBundlePath + if ($LASTEXITCODE -ne 0) { + throw "Test signing failed for the bundle with exit code $LASTEXITCODE." + } + + $signature = Get-AuthenticodeSignature -LiteralPath $signedBundlePath + if ( + $null -eq $signature.SignerCertificate -or + $signature.SignerCertificate.Thumbprint -ne $certificate.Thumbprint + ) { + throw 'The bundle test signature was not applied.' + } + } } finally { Remove-Item -LiteralPath $temporaryPfx -Force -ErrorAction SilentlyContinue diff --git a/scripts/Test-GatewayIsolationPlugin.Tests.ps1 b/scripts/Test-GatewayIsolationPlugin.Tests.ps1 index fe985925..31690c6b 100644 --- a/scripts/Test-GatewayIsolationPlugin.Tests.ps1 +++ b/scripts/Test-GatewayIsolationPlugin.Tests.ps1 @@ -89,6 +89,20 @@ if (args[0] === "plugins" && args[1] === "enable") { entries: { "gateway-isolation": { enabled: true + }, + anthropic: { + config: { + sessionCatalog: { + enabled: false + } + } + }, + codex: { + config: { + sessionCatalog: { + enabled: false + } + } } } } diff --git a/scripts/Test-MSIXReleaseIdentity.Tests.ps1 b/scripts/Test-MSIXReleaseIdentity.Tests.ps1 new file mode 100644 index 00000000..99c2be10 --- /dev/null +++ b/scripts/Test-MSIXReleaseIdentity.Tests.ps1 @@ -0,0 +1,159 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$scriptPath = Join-Path $PSScriptRoot 'Get-MSIXReleaseIdentity.ps1' +$workflowVersionScriptPath = Join-Path $PSScriptRoot 'Get-WorkflowPackageVersion.ps1' + +function Assert-Identity { + param( + [Parameter(Mandatory)] + [string]$GatewayTag, + + [Parameter(Mandatory)] + [int]$MSIXRevision, + + [Parameter(Mandatory)] + [string]$PackageVersion, + + [Parameter(Mandatory)] + [string]$ReleaseTag + ) + + $identity = & $scriptPath ` + -GatewayTag $GatewayTag ` + -MSIXRevision $MSIXRevision + if ($identity.PackageVersion -ne $PackageVersion) { + throw ( + "Expected $GatewayTag revision $MSIXRevision to produce " + + "$PackageVersion; received $($identity.PackageVersion)." + ) + } + if ($identity.ReleaseTag -ne $ReleaseTag) { + throw ( + "Expected $GatewayTag revision $MSIXRevision to produce " + + "$ReleaseTag; received $($identity.ReleaseTag)." + ) + } + if ($identity.ReleaseVersion -ne $ReleaseTag.Substring(1)) { + throw 'ReleaseVersion did not match the release tag without its v prefix.' + } +} + +function Assert-Fails { + param( + [Parameter(Mandatory)] + [scriptblock]$Action, + + [Parameter(Mandatory)] + [string]$MessagePattern + ) + + try { + & $Action + } + catch { + if ($_.Exception.Message -notmatch $MessagePattern) { + throw ( + "Expected failure matching '$MessagePattern'; received: " + + $_.Exception.Message + ) + } + return + } + + throw "Expected failure matching '$MessagePattern', but the action succeeded." +} + +Assert-Identity ` + -GatewayTag 'v2026.9.4' ` + -MSIXRevision 0 ` + -PackageVersion '2026.9.4.1000' ` + -ReleaseTag 'v2026.9.4-msix.0' +Assert-Identity ` + -GatewayTag 'v2026.7.12' ` + -MSIXRevision 0 ` + -PackageVersion '2026.7.12.1000' ` + -ReleaseTag 'v2026.7.12-msix.0' +Assert-Identity ` + -GatewayTag 'v2026.7.2' ` + -MSIXRevision 0 ` + -PackageVersion '2026.7.2.1000' ` + -ReleaseTag 'v2026.7.2-msix.0' +Assert-Identity ` + -GatewayTag 'v2026.7.1' ` + -MSIXRevision 1 ` + -PackageVersion '2026.7.1.1001' ` + -ReleaseTag 'v2026.7.1-msix.1' +Assert-Identity ` + -GatewayTag 'v2026.7.1-2' ` + -MSIXRevision 0 ` + -PackageVersion '2026.7.1.2000' ` + -ReleaseTag 'v2026.7.1-2-msix.0' +Assert-Identity ` + -GatewayTag 'v2026.7.1-2' ` + -MSIXRevision 1 ` + -PackageVersion '2026.7.1.2001' ` + -ReleaseTag 'v2026.7.1-2-msix.1' + +$gatewayCorrection = & $scriptPath ` + -GatewayTag 'v2026.7.1-2' ` + -MSIXRevision 0 +$packagingCorrection = & $scriptPath ` + -GatewayTag 'v2026.7.1-2' ` + -MSIXRevision 1 +$nextGatewayCorrection = & $scriptPath ` + -GatewayTag 'v2026.7.1-3' ` + -MSIXRevision 0 +$nextGatewayPatch = & $scriptPath ` + -GatewayTag 'v2026.7.2' ` + -MSIXRevision 0 +if ( + [version]$packagingCorrection.PackageVersion -le + [version]$gatewayCorrection.PackageVersion -or + [version]$nextGatewayCorrection.PackageVersion -le + [version]$packagingCorrection.PackageVersion -or + [version]$nextGatewayPatch.PackageVersion -le + [version]$nextGatewayCorrection.PackageVersion +) { + throw 'Derived MSIX versions do not preserve release ordering.' +} + +Assert-Fails -MessagePattern 'stable OpenClaw release tag' -Action { + & $scriptPath -GatewayTag 'v2026.9.4-beta.1' -MSIXRevision 0 +} +Assert-Fails -MessagePattern 'month must be between 1 and 12' -Action { + & $scriptPath -GatewayTag 'v2026.13.1' -MSIXRevision 0 +} +Assert-Fails -MessagePattern 'patch must be between 0 and 65534' -Action { + & $scriptPath -GatewayTag 'v2026.9.65535' -MSIXRevision 0 +} +Assert-Fails -MessagePattern 'correction suffix must be between 2 and 64' -Action { + & $scriptPath -GatewayTag 'v2026.9.4-1' -MSIXRevision 0 +} +Assert-Fails -MessagePattern 'correction suffix must be between 2 and 64' -Action { + & $scriptPath -GatewayTag 'v2026.9.4-65' -MSIXRevision 0 +} +Assert-Fails -MessagePattern 'MSIXRevision must be between 0 and 999' -Action { + & $scriptPath -GatewayTag 'v2026.9.4' -MSIXRevision 1000 +} +Assert-Identity ` + -GatewayTag 'v2026.9.4-64' ` + -MSIXRevision 999 ` + -PackageVersion '2026.9.4.64999' ` + -ReleaseTag 'v2026.9.4-64-msix.999' + +$maximumIdentity = & $scriptPath ` + -GatewayTag 'v2026.9.4-64' ` + -MSIXRevision 999 +$validatedMaximumVersion = & $workflowVersionScriptPath ` + -RunNumber 1 ` + -RunAttempt 1 ` + -ReleaseVersion $maximumIdentity.PackageVersion +if ($validatedMaximumVersion -ne '2026.9.4.64999') { + throw 'The maximum derived identity did not pass workflow validation.' +} + +Write-Host 'MSIX release-identity tests passed.' diff --git a/scripts/Test-MSIXUpgrade.ps1 b/scripts/Test-MSIXUpgrade.ps1 new file mode 100644 index 00000000..077f595c --- /dev/null +++ b/scripts/Test-MSIXUpgrade.ps1 @@ -0,0 +1,352 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$BaselinesPath, + + [Parameter(Mandatory)] + [string]$BaselinesDirectory, + + [Parameter(Mandatory)] + [string]$CandidatePackagePath, + + [Parameter(Mandatory)] + [string]$CandidateBundlePath, + + [Parameter(Mandatory)] + [string]$CandidateCertificatePath, + + [Parameter(Mandatory)] + [string]$ExpectedCandidateVersion, + + [Parameter(Mandatory)] + [string]$EvidencePath +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +if (-not $IsWindows) { + throw 'MSIX upgrade validation requires Windows.' +} + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +function Read-MSIXIdentity { + param([Parameter(Mandatory)][string]$Path) + + $archive = [IO.Compression.ZipFile]::OpenRead( + (Resolve-Path -LiteralPath $Path).Path + ) + try { + $isBundle = [IO.Path]::GetExtension($Path) -ieq '.msixbundle' + $manifestPath = if ($isBundle) { + 'AppxMetadata/AppxBundleManifest.xml' + } + else { + 'AppxManifest.xml' + } + $entry = $archive.GetEntry($manifestPath) + if ($null -eq $entry) { + throw "Package '$Path' does not contain $manifestPath." + } + $reader = [IO.StreamReader]::new($entry.Open()) + try { + [xml]$manifest = $reader.ReadToEnd() + } + finally { + $reader.Dispose() + } + if ($isBundle) { + $identity = $manifest.Bundle.Identity + $x64Package = @($manifest.Bundle.Packages.Package) | + Where-Object { [string]$_.Architecture -ceq 'x64' } | + Select-Object -First 1 + if ($null -eq $x64Package) { + throw "MSIX bundle '$Path' does not contain an x64 package." + } + return [pscustomobject]@{ + Name = [string]$identity.Name + Publisher = [string]$identity.Publisher + Architecture = 'x64' + Version = [string]$x64Package.Version + BundleVersion = [string]$identity.Version + DeliveryType = 'bundle' + } + } + + $identity = $manifest.Package.Identity + [pscustomobject]@{ + Name = [string]$identity.Name + Publisher = [string]$identity.Publisher + Architecture = [string]$identity.ProcessorArchitecture + Version = [string]$identity.Version + BundleVersion = $null + DeliveryType = 'standalone' + } + } + finally { + $archive.Dispose() + } +} + +function Get-GatewayPackages { + @(Get-AppxPackage -Name 'OpenClaw.Gateway' -ErrorAction SilentlyContinue) +} + +$testOwnsPackage = $false +$testPackageFamilyName = $null + +function Remove-TestPackage { + if (-not $script:testOwnsPackage) { + return + } + + $packages = @(Get-GatewayPackages) + if ($packages.Count -gt 1) { + throw 'More than one OpenClaw.Gateway registration exists during cleanup.' + } + if ($packages.Count -eq 1) { + if ( + $null -ne $script:testPackageFamilyName -and + [string]$packages[0].PackageFamilyName -cne + $script:testPackageFamilyName + ) { + throw 'Refusing to remove an OpenClaw package not owned by this test.' + } + Remove-AppxPackage ` + -Package $packages[0].PackageFullName ` + -ErrorAction Stop + } + + $script:testOwnsPackage = $false + $script:testPackageFamilyName = $null +} + +function Install-TestPackage { + param([Parameter(Mandatory)][string]$Path) + + if (@(Get-GatewayPackages).Count -ne 0) { + throw 'Refusing to install over an OpenClaw package not owned by this test.' + } + # The clean-machine guard above establishes ownership before installation, + # allowing finally cleanup even if installation only partially succeeds. + $script:testOwnsPackage = $true + Add-AppxPackage -Path $Path -ErrorAction Stop + $packages = @(Get-GatewayPackages) + if ($packages.Count -ne 1) { + throw 'Windows did not create exactly one OpenClaw.Gateway registration.' + } + $script:testPackageFamilyName = [string]$packages[0].PackageFamilyName + $packages[0] +} + +$resolvedBaselinesPath = (Resolve-Path -LiteralPath $BaselinesPath).Path +$resolvedBaselinesDirectory = ( + Resolve-Path -LiteralPath $BaselinesDirectory +).Path +$resolvedCandidatePath = ( + Resolve-Path -LiteralPath $CandidatePackagePath +).Path +$resolvedCandidateBundlePath = ( + Resolve-Path -LiteralPath $CandidateBundlePath +).Path +$resolvedCertificatePath = ( + Resolve-Path -LiteralPath $CandidateCertificatePath +).Path +$candidateIdentity = Read-MSIXIdentity -Path $resolvedCandidatePath +$candidateBundleIdentity = Read-MSIXIdentity -Path $resolvedCandidateBundlePath +if ( + $candidateIdentity.Name -cne 'OpenClaw.Gateway' -or + $candidateIdentity.Architecture -cne 'x64' -or + $candidateIdentity.Version -cne $ExpectedCandidateVersion +) { + throw 'The candidate MSIX identity is unexpected.' +} +if ( + $candidateBundleIdentity.Name -cne $candidateIdentity.Name -or + $candidateBundleIdentity.Publisher -cne $candidateIdentity.Publisher -or + $candidateBundleIdentity.Architecture -cne 'x64' -or + $candidateBundleIdentity.Version -cne $ExpectedCandidateVersion -or + $candidateBundleIdentity.DeliveryType -cne 'bundle' +) { + throw 'The candidate MSIX bundle identity is unexpected.' +} + +$policy = Get-Content ` + -LiteralPath (Join-Path (Split-Path $PSScriptRoot -Parent) 'release-policy.json') ` + -Raw | + ConvertFrom-Json +if ($candidateIdentity.Publisher -cne [string]$policy.publisher) { + throw 'The candidate MSIX publisher does not match release policy.' +} + +$baselineManifest = Get-Content -LiteralPath $resolvedBaselinesPath -Raw | + ConvertFrom-Json +if ($baselineManifest.baselines.Count -ne 4) { + throw 'Upgrade validation requires standalone and bundle proof-release baselines.' +} +if (@(Get-GatewayPackages).Count -ne 0) { + throw ( + 'Refusing to run MSIX upgrade validation while OpenClaw.Gateway is ' + + 'already installed. Use an isolated clean test account.' + ) +} + +$certificate = Import-Certificate ` + -FilePath $resolvedCertificatePath ` + -CertStoreLocation 'Cert:\LocalMachine\TrustedPeople' +$results = [Collections.Generic.List[object]]::new() +$freshInstall = $null + +try { + foreach ($baseline in $baselineManifest.baselines) { + Remove-TestPackage + $baselinePath = Join-Path ` + $resolvedBaselinesDirectory ` + ([string]$baseline.assetName) + if (-not (Test-Path -LiteralPath $baselinePath -PathType Leaf)) { + throw "Missing upgrade baseline '$($baseline.assetName)'." + } + $actualHash = ( + Get-FileHash -LiteralPath $baselinePath -Algorithm SHA256 + ).Hash.ToLowerInvariant() + if ($actualHash -cne [string]$baseline.sha256) { + throw "Upgrade baseline '$($baseline.assetName)' failed hash validation." + } + + $deliveryType = [string]$baseline.deliveryType + if ($deliveryType -notin @('standalone', 'bundle')) { + throw "Unknown delivery type '$deliveryType'." + } + $baselineIdentity = Read-MSIXIdentity -Path $baselinePath + if ( + $baselineIdentity.Name -cne $candidateIdentity.Name -or + $baselineIdentity.Publisher -cne $candidateIdentity.Publisher -or + $baselineIdentity.Architecture -cne 'x64' -or + $baselineIdentity.Version -cne [string]$baseline.packageVersion -or + $baselineIdentity.DeliveryType -cne $deliveryType + ) { + throw "Upgrade baseline '$($baseline.assetName)' has an unexpected identity." + } + if ( + [version]$candidateIdentity.Version -le + [version]$baselineIdentity.Version + ) { + throw 'The candidate MSIX must be newer than every upgrade baseline.' + } + + $installedBaseline = Install-TestPackage -Path $baselinePath + if ( + $null -eq $installedBaseline -or + [string]$installedBaseline.Version -cne $baselineIdentity.Version -or + [string]$installedBaseline.Status -cne 'Ok' + ) { + throw "Windows did not install '$($baseline.assetName)' successfully." + } + + $localState = Join-Path ` + $env:LOCALAPPDATA ` + "Packages\$($installedBaseline.PackageFamilyName)\LocalState" + New-Item -Path $localState -ItemType Directory -Force | Out-Null + $markerPath = Join-Path $localState 'msix-upgrade-proof.txt' + $marker = "upgrade-from-$($baseline.packageVersion)" + Set-Content -LiteralPath $markerPath -Value $marker -Encoding utf8 + + $candidatePath = if ($deliveryType -ceq 'bundle') { + $resolvedCandidateBundlePath + } + else { + $resolvedCandidatePath + } + Add-AppxPackage ` + -Path $candidatePath ` + -ForceApplicationShutdown ` + -ErrorAction Stop + $installedCandidate = Get-AppxPackage -Name $candidateIdentity.Name + if ( + $null -eq $installedCandidate -or + [string]$installedCandidate.Version -cne $candidateIdentity.Version -or + [string]$installedCandidate.Status -cne 'Ok' + ) { + throw "Windows did not upgrade from '$($baseline.assetName)'." + } + $candidateLocalState = Join-Path ` + $env:LOCALAPPDATA ` + "Packages\$($installedCandidate.PackageFamilyName)\LocalState" + $retainedMarkerPath = Join-Path ` + $candidateLocalState ` + 'msix-upgrade-proof.txt' + if ( + $installedCandidate.PackageFamilyName -cne + $installedBaseline.PackageFamilyName -or + -not (Test-Path -LiteralPath $retainedMarkerPath -PathType Leaf) -or + (Get-Content -LiteralPath $retainedMarkerPath -Raw).Trim() -cne $marker + ) { + throw "LocalState was not retained across the $($baseline.packageVersion) upgrade." + } + + $results.Add([pscustomobject]@{ + baselineRelease = [string]$baseline.releaseTag + deliveryType = $deliveryType + baselineVersion = $baselineIdentity.Version + candidateVersion = $candidateIdentity.Version + packageFamilyName = [string]$installedCandidate.PackageFamilyName + status = [string]$installedCandidate.Status + localStateRetained = $true + }) + } + + $freshInstalls = [Collections.Generic.List[object]]::new() + foreach ($candidate in @( + [pscustomobject]@{ + deliveryType = 'standalone' + path = $resolvedCandidatePath + }, + [pscustomobject]@{ + deliveryType = 'bundle' + path = $resolvedCandidateBundlePath + } + )) { + Remove-TestPackage + $installedFresh = Install-TestPackage -Path $candidate.path + if ( + $null -eq $installedFresh -or + [string]$installedFresh.Version -cne $candidateIdentity.Version -or + [string]$installedFresh.Status -cne 'Ok' + ) { + throw "Windows did not accept a fresh $($candidate.deliveryType) installation." + } + $freshInstalls.Add([pscustomobject]@{ + deliveryType = $candidate.deliveryType + candidateVersion = $candidateIdentity.Version + packageFamilyName = [string]$installedFresh.PackageFamilyName + status = [string]$installedFresh.Status + }) + } + $freshInstall = $freshInstalls +} +finally { + Remove-TestPackage + if ($null -ne $certificate) { + Remove-Item ` + -LiteralPath "Cert:\LocalMachine\TrustedPeople\$($certificate.Thumbprint)" ` + -Force ` + -ErrorAction SilentlyContinue + } +} + +$evidenceDirectory = Split-Path -Parent $EvidencePath +if (-not [string]::IsNullOrWhiteSpace($evidenceDirectory)) { + New-Item -Path $evidenceDirectory -ItemType Directory -Force | Out-Null +} +[pscustomobject]@{ + testedAt = (Get-Date).ToUniversalTime().ToString('o') + runner = [Environment]::OSVersion.VersionString + candidateVersion = $candidateIdentity.Version + freshInstall = $freshInstall + transitions = $results +} | + ConvertTo-Json -Depth 4 | + Set-Content -LiteralPath $EvidencePath -Encoding utf8 + +Write-Host "MSIX upgrade compatibility passed for $($results.Count) proof-release paths." diff --git a/scripts/Test-Sign-TestMSIX.Tests.ps1 b/scripts/Test-Sign-TestMSIX.Tests.ps1 index 88338e1c..e305019c 100644 --- a/scripts/Test-Sign-TestMSIX.Tests.ps1 +++ b/scripts/Test-Sign-TestMSIX.Tests.ps1 @@ -35,8 +35,8 @@ try { if ($exitCode -eq 0) { throw 'Signing unexpectedly succeeded without an architecture directory.' } - if ($message -notmatch 'No architecture directories were found') { - throw "Signing failure did not identify the missing architecture directories. Output: $message" + if ($message -notmatch 'No signable package directories were found') { + throw "Signing failure did not identify the missing package directories. Output: $message" } if (Test-Path -LiteralPath $outputDirectory) { throw 'Signing created an output directory despite finding no architecture directory.' diff --git a/scripts/Test-SigningInputs.Tests.ps1 b/scripts/Test-SigningInputs.Tests.ps1 index 003c6bb4..b6bd73b2 100644 --- a/scripts/Test-SigningInputs.Tests.ps1 +++ b/scripts/Test-SigningInputs.Tests.ps1 @@ -8,12 +8,12 @@ $repositoryRoot = Split-Path $PSScriptRoot -Parent $policyPath = Join-Path $repositoryRoot 'release-policy.json' $policy = Get-Content -LiteralPath $policyPath -Raw | ConvertFrom-Json $approvedCommit = [string]$policy.approvedCommit -$approvedPackageVersion = & ( - Join-Path $PSScriptRoot 'Get-WorkflowPackageVersion.ps1' +$releaseIdentity = & ( + Join-Path $PSScriptRoot 'Get-MSIXReleaseIdentity.ps1' ) ` - -RunNumber 1 ` - -RunAttempt 1 ` - -ReleaseVersion ([string]$policy.packageVersion) + -GatewayTag ([string]$policy.gatewayTag) ` + -MSIXRevision ([int]$policy.msixRevision) +$approvedPackageVersion = $releaseIdentity.PackageVersion $approvedPayloadVersion = [string]$policy.payloadPackageVersion $packagingCommit = '1111111111111111111111111111111111111111' $testRoot = Join-Path $env:TEMP ( @@ -233,7 +233,7 @@ function New-TestBundle { $Root ` 'x64\OpenClawGateway-x64.msix'), - [string]$BundleVersion = '2026.912.815.0' + [string]$BundleVersion = $approvedPackageVersion ) $bundleDirectory = Join-Path $Root 'bundle' @@ -386,6 +386,14 @@ try { -MessagePattern 'Node.js runtime versions do not match' ` -Action { Invoke-PolicyValidation -Root $testRoot } + Reset-TestArtifacts + New-TestBundle -Root $testRoot -BundleVersion '2026.8.2001.0' + Assert-Fails ` + -MessagePattern 'bundle manifest identity is unexpected' ` + -Action { + Invoke-PolicyValidation -Root $testRoot -PreserveBundle + } + Reset-TestArtifacts Assert-Fails ` -MessagePattern 'approved immutable OpenClaw commit' ` @@ -413,7 +421,7 @@ try { New-TestArtifact ` -Root $testRoot ` -Architecture x64 ` - -PayloadPackageVersion '2026.9.4' + -PayloadPackageVersion '2026.9.3' New-TestArtifact -Root $testRoot -Architecture arm64 Assert-Fails ` -MessagePattern 'metadata is not eligible' ` diff --git a/scripts/Test-SigningInputs.ps1 b/scripts/Test-SigningInputs.ps1 index 14c6171b..8811f68f 100644 --- a/scripts/Test-SigningInputs.ps1 +++ b/scripts/Test-SigningInputs.ps1 @@ -153,22 +153,22 @@ $policy = Get-Content -LiteralPath $resolvedPolicyPath -Raw | if ( $policy.repository -ne 'https://github.com/openclaw/openclaw' -or - [string]::IsNullOrWhiteSpace([string]$policy.releaseTag) -or - $policy.packageVersion -notmatch '^\d+\.\d+\.\d+\.\d+$' -or - $policy.releaseTag -ne "v$($policy.packageVersion)" -or + [string]::IsNullOrWhiteSpace([string]$policy.gatewayTag) -or + $policy.msixRevision -isnot [int64] -or [string]::IsNullOrWhiteSpace([string]$policy.payloadPackageVersion) -or + $policy.gatewayTag -ne "v$($policy.payloadPackageVersion)" -or $policy.approvedCommit -notmatch '^[0-9a-fA-F]{40}$' -or [string]::IsNullOrWhiteSpace([string]$policy.publisher) ) { throw 'The Gateway MSIX release policy is invalid.' } -$approvedPackageVersion = & ( - Join-Path $PSScriptRoot 'Get-WorkflowPackageVersion.ps1' +$releaseIdentity = & ( + Join-Path $PSScriptRoot 'Get-MSIXReleaseIdentity.ps1' ) ` - -RunNumber 1 ` - -RunAttempt 1 ` - -ReleaseVersion ([string]$policy.packageVersion) + -GatewayTag ([string]$policy.gatewayTag) ` + -MSIXRevision ([int]$policy.msixRevision) +$approvedPackageVersion = $releaseIdentity.PackageVersion $approvedPayloadVersion = [string]$policy.payloadPackageVersion $approvedCommit = ([string]$policy.approvedCommit).ToLowerInvariant() $normalizedRequestedRef = $RequestedRef.Trim().ToLowerInvariant() @@ -446,7 +446,8 @@ try { $null -eq $bundleIdentity -or $bundleIdentity.Name -ne 'OpenClaw.Gateway' -or $bundleIdentity.Publisher -ne $policy.publisher -or - -not $bundleVersionIsValid + -not $bundleVersionIsValid -or + $bundleVersion -ne $approvedPackageVersion ) { throw 'The MSIX bundle manifest identity is unexpected.' } diff --git a/scripts/Test-WorkflowSigningConfiguration.ps1 b/scripts/Test-WorkflowSigningConfiguration.ps1 index 31c73bdf..aae8fced 100644 --- a/scripts/Test-WorkflowSigningConfiguration.ps1 +++ b/scripts/Test-WorkflowSigningConfiguration.ps1 @@ -43,6 +43,15 @@ $requiredFragments = @( 'uses: azure/artifact-signing-action@v2' 'name: Compose unsigned multi-architecture MSIX bundle' 'name: Upload unsigned multi-architecture MSIX bundle' + 'name: Test proof-release MSIX upgrades' + "needs.changes.outputs.versioning == 'true'" + 'scripts/Test-MSIXReleaseIdentity.Tests.ps1' + '.\scripts\msix-upgrade-baselines.json' + '.\scripts\Test-MSIXUpgrade.ps1' + 'name: Download unsigned bundle candidate' + '-CandidateBundlePath test-signed\bundle\OpenClawGateway.msixbundle' + 'openclaw-gateway-msix-upgrade-evidence' + 'retention-days: 90' '-BundlePath artifacts\bundle\OpenClawGateway.msixbundle' 'files-folder-recurse: true' 'files: ${{ github.workspace }}\artifacts\bundle\OpenClawGateway.msixbundle' @@ -51,6 +60,7 @@ $requiredFragments = @( 'signing-account-name: openclaw' 'certificate-profile-name: openclaw' 'name: Publish signed Gateway MSIX release' + '.\scripts\Get-MSIXReleaseIdentity.ps1' 'contents: write' 'uses: softprops/action-gh-release@v3' 'tag_name: ${{ needs.authorize-signing.outputs.release_tag }}' diff --git a/scripts/msix-upgrade-baselines.json b/scripts/msix-upgrade-baselines.json new file mode 100644 index 00000000..2b50caa9 --- /dev/null +++ b/scripts/msix-upgrade-baselines.json @@ -0,0 +1,32 @@ +{ + "baselines": [ + { + "releaseTag": "v0.0.0.0", + "deliveryType": "standalone", + "assetName": "OpenClawGateway-0.0.0.0-x64.msix", + "packageVersion": "0.0.0.0", + "sha256": "3f288e267de01f8f4e25897a6c5bb3db9faaa09253360fb83e2247dcfffaf76b" + }, + { + "releaseTag": "v0.0.0.1", + "deliveryType": "standalone", + "assetName": "OpenClawGateway-0.0.0.1-x64.msix", + "packageVersion": "0.0.0.1", + "sha256": "098eae798413f3b831126d9806c0cb47695280cc9b2b4d52696bfde075aa1747" + }, + { + "releaseTag": "v0.0.0.0", + "deliveryType": "bundle", + "assetName": "OpenClawGateway-0.0.0.0.msixbundle", + "packageVersion": "0.0.0.0", + "sha256": "6dba82cc6cc0bf475463368be9d3899dff31addb840663d66587ad1267f3bb6e" + }, + { + "releaseTag": "v0.0.0.1", + "deliveryType": "bundle", + "assetName": "OpenClawGateway-0.0.0.1.msixbundle", + "packageVersion": "0.0.0.1", + "sha256": "48a65dd7bdc515acac96f73129aed9a343604bbfb3ceed8be4e5bd5367fd85c4" + } + ] +}