Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,18 @@ and ARM64 separately.
- Keep x64 and ARM64 behavior synchronized across the workflow matrix, scripts,
project runtime identifiers, manifest content, payload metadata, and signing
validation.
- Official releases combine the x64 and ARM64 packages into one signed
`.msixbundle` while retaining signed standalone packages for explicit
architecture-specific deployment. Compose the bundle before signing; bundle
signing recursively covers its contained packages.
- Metadata files are part of the release trust chain, not incidental build
output. Changes to their fields must be coordinated across payload creation,
MSIX creation, signing validation, workflow artifacts, and tests.
- Keep the workflow's manual `openclaw_ref` default and automatic
`env.OPENCLAW_REF` fallback identical. Official-release changes also update
the reviewed immutable commit in `release-policy.json`.
the reviewed immutable commit and stable or correction tag in
`release-policy.json`. The tag determines the four-part MSIX identity
version and the permanent GitHub Release tag.
- The launcher is NativeAOT. `dotnet build` and the xUnit suite exercise a JIT
build, so run the NativeAOT publish path when changing reflection, interop,
or trimming-sensitive code.
Expand Down
216 changes: 212 additions & 4 deletions .github/workflows/gateway-msix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ jobs:
run: >
.\scripts\Test-WorkflowPackageVersion.Tests.ps1

- name: Test MSIX bundle build
shell: pwsh
run: >
.\scripts\Test-Build-MSIXBundle.Tests.ps1

- name: Test Git hooks
shell: pwsh
run: >
Expand Down Expand Up @@ -259,10 +264,20 @@ jobs:

- name: Compose unsigned MSIX
shell: pwsh
env:
SIGNING_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode || 'unsigned' }}
run: |
$versionParameters = @{
RunNumber = '${{ github.run_number }}'
RunAttempt = '${{ github.run_attempt }}'
}
if ($env:SIGNING_MODE -eq 'official') {
$policy = Get-Content -LiteralPath .\release-policy.json -Raw |
ConvertFrom-Json
$versionParameters.ReleaseVersion = [string]$policy.packageVersion
}
$packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 `
-RunNumber '${{ github.run_number }}' `
-RunAttempt '${{ github.run_attempt }}'
@versionParameters

.\scripts\Build-MSIX.ps1 `
-PayloadDirectory '${{ runner.temp }}\openclaw-payload' `
Expand Down Expand Up @@ -330,6 +345,59 @@ jobs:
if-no-files-found: error
retention-days: 7

build-msix-bundle:
name: Build unsigned multi-architecture Gateway MSIX bundle
needs: build-msix
runs-on: windows-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Download unsigned x64 package
uses: actions/download-artifact@v8
with:
name: openclaw-gateway-msix-unsigned-x64
path: artifacts\x64

- name: Download unsigned ARM64 package
uses: actions/download-artifact@v8
with:
name: openclaw-gateway-msix-unsigned-arm64
path: artifacts\arm64

- name: Compose unsigned multi-architecture MSIX bundle
shell: pwsh
env:
SIGNING_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode || 'unsigned' }}
run: |
$versionParameters = @{
RunNumber = '${{ github.run_number }}'
RunAttempt = '${{ github.run_attempt }}'
}
if ($env:SIGNING_MODE -eq 'official') {
$policy = Get-Content -LiteralPath .\release-policy.json -Raw |
ConvertFrom-Json
$versionParameters.ReleaseVersion = [string]$policy.packageVersion
}
$packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 `
@versionParameters

.\scripts\Build-MSIXBundle.ps1 `
-X64Package artifacts\x64\OpenClawGateway-x64.msix `
-Arm64Package artifacts\arm64\OpenClawGateway-arm64.msix `
-PackageVersion $packageVersion `
-OutputPath artifacts\bundle\OpenClawGateway.msixbundle

- name: Upload unsigned multi-architecture MSIX bundle
uses: actions/upload-artifact@v7
with:
name: openclaw-gateway-msix-unsigned-bundle
path: artifacts\bundle\OpenClawGateway.msixbundle
if-no-files-found: error
retention-days: 7

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' }}
Expand All @@ -346,8 +414,14 @@ jobs:
authorize-signing:
name: Authorize official Gateway MSIX signing
if: ${{ github.event_name == 'workflow_dispatch' && inputs.signing_mode == 'official' && github.ref == 'refs/heads/main' }}
needs: build-msix
needs:
- build-msix
- build-msix-bundle
runs-on: windows-latest
outputs:
package_version: ${{ steps.release.outputs.package_version }}
release_tag: ${{ steps.release.outputs.release_tag }}
release_version: ${{ steps.release.outputs.release_version }}
permissions:
actions: read
contents: read
Expand All @@ -369,6 +443,28 @@ jobs:
name: openclaw-gateway-msix-unsigned-arm64
path: artifacts\arm64

- name: Download unsigned multi-architecture bundle
uses: actions/download-artifact@v8
with:
name: openclaw-gateway-msix-unsigned-bundle
path: artifacts\bundle

- name: Resolve official release metadata
id: release
shell: pwsh
run: |
$policy = Get-Content -LiteralPath .\release-policy.json -Raw |
ConvertFrom-Json
$releaseTag = ([string]$policy.releaseTag).Trim()
$packageVersion = .\scripts\Get-WorkflowPackageVersion.ps1 `
-RunNumber '${{ github.run_number }}' `
-RunAttempt '${{ github.run_attempt }}' `
-ReleaseVersion ([string]$policy.packageVersion)

"package_version=$packageVersion" >> $env:GITHUB_OUTPUT
"release_tag=$releaseTag" >> $env:GITHUB_OUTPUT
"release_version=$($releaseTag.Substring(1))" >> $env:GITHUB_OUTPUT

- name: Enforce official signing policy
shell: pwsh
env:
Expand All @@ -378,6 +474,7 @@ jobs:
.\scripts\Test-SigningInputs.ps1 `
-ArtifactsDirectory artifacts `
-PolicyPath .\release-policy.json `
-BundlePath artifacts\bundle\OpenClawGateway.msixbundle `
-RequestedRef $env:OPENCLAW_REF `
-PackagingCommit $env:PACKAGING_COMMIT

Expand All @@ -386,6 +483,7 @@ jobs:
if: ${{ needs.authorize-signing.result == 'success' }}
needs:
- build-msix
- build-msix-bundle
- authorize-signing
runs-on: windows-latest
environment: release-signing
Expand All @@ -406,14 +504,20 @@ jobs:
name: openclaw-gateway-msix-unsigned-arm64
path: artifacts\arm64

- name: Download unsigned multi-architecture bundle
uses: actions/download-artifact@v8
with:
name: openclaw-gateway-msix-unsigned-bundle
path: artifacts\bundle

- name: Azure login
uses: azure/login@v3
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

- name: Sign MSIX packages
- name: Sign standalone MSIX packages
uses: azure/artifact-signing-action@v2
with:
endpoint: https://eus.codesigning.azure.net/
Expand All @@ -427,6 +531,17 @@ jobs:
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256

- name: Sign multi-architecture MSIX bundle
uses: azure/artifact-signing-action@v2
with:
endpoint: https://eus.codesigning.azure.net/
signing-account-name: openclaw
certificate-profile-name: openclaw
files: ${{ github.workspace }}\artifacts\bundle\OpenClawGateway.msixbundle
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256

- name: Verify signatures and refresh metadata
shell: pwsh
run: |
Expand Down Expand Up @@ -460,6 +575,24 @@ jobs:
Set-Content -LiteralPath $metadataPath -Encoding utf8
}

$bundle = Get-Item `
-LiteralPath artifacts\bundle\OpenClawGateway.msixbundle
$bundleSignature = Get-AuthenticodeSignature `
-LiteralPath $bundle.FullName
if ($bundleSignature.Status -ne 'Valid') {
throw "$($bundle.Name) signature status was $($bundleSignature.Status)."
}
if (-not [string]::Equals(
$bundleSignature.SignerCertificate.Subject,
$expectedSubject,
[StringComparison]::OrdinalIgnoreCase
)) {
throw (
"$($bundle.Name) signer was unexpected: " +
$bundleSignature.SignerCertificate.Subject
)
}

- name: Upload signed x64 MSIX
uses: actions/upload-artifact@v7
with:
Expand All @@ -475,3 +608,78 @@ jobs:
path: artifacts\arm64\
if-no-files-found: error
retention-days: 7

- name: Upload signed multi-architecture MSIX bundle
uses: actions/upload-artifact@v7
with:
name: openclaw-gateway-msix-bundle
path: artifacts\bundle\OpenClawGateway.msixbundle
if-no-files-found: error
retention-days: 7

publish-release:
name: Publish signed Gateway MSIX release
if: ${{ needs.sign-msix.result == 'success' }}
needs:
- authorize-signing
- sign-msix
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download signed x64 package
uses: actions/download-artifact@v8
with:
name: openclaw-gateway-msix-x64
path: signed/x64

- name: Download signed ARM64 package
uses: actions/download-artifact@v8
with:
name: openclaw-gateway-msix-arm64
path: signed/arm64

- name: Download signed multi-architecture bundle
uses: actions/download-artifact@v8
with:
name: openclaw-gateway-msix-bundle
path: signed/bundle

- name: Stage versioned release assets
shell: bash
env:
RELEASE_VERSION: ${{ needs.authorize-signing.outputs.release_version }}
run: |
set -euo pipefail
mkdir release-assets
cp signed/x64/OpenClawGateway-x64.msix \
"release-assets/OpenClawGateway-${RELEASE_VERSION}-x64.msix"
cp signed/arm64/OpenClawGateway-arm64.msix \
"release-assets/OpenClawGateway-${RELEASE_VERSION}-arm64.msix"
cp signed/bundle/OpenClawGateway.msixbundle \
"release-assets/OpenClawGateway-${RELEASE_VERSION}.msixbundle"

- name: Create permanent GitHub release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.authorize-signing.outputs.release_tag }}
target_commitish: ${{ github.sha }}
name: OpenClaw Gateway MSIX ${{ needs.authorize-signing.outputs.release_tag }}
generate_release_notes: true
prerelease: false
make_latest: true
overwrite_files: false
fail_on_unmatched_files: true
files: |
release-assets/*.msix
release-assets/*.msixbundle
body: |
Packages OpenClaw Gateway `${{ needs.authorize-signing.outputs.release_tag }}`
from [`openclaw/openclaw@${{ inputs.openclaw_ref }}`](https://github.com/openclaw/openclaw/commit/${{ inputs.openclaw_ref }}).

### Downloads
- **Recommended:** `OpenClawGateway-${{ needs.authorize-signing.outputs.release_version }}.msixbundle`
- **x64:** `OpenClawGateway-${{ needs.authorize-signing.outputs.release_version }}-x64.msix`
- **ARM64:** `OpenClawGateway-${{ needs.authorize-signing.outputs.release_version }}-arm64.msix`

The packages are signed by OpenClaw Foundation through Azure Artifact Signing.
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,15 @@ 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 approved for
official signing. Updating that policy requires a reviewed repository change.
Official signing runs only from `main` and verifies the workflow input, both
`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
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
every file against the embedded application inventory before requesting Azure
credentials.
every file against the embedded application inventory. It also byte-compares
the bundle's embedded packages with those authorized standalone packages before
requesting Azure credentials.

## Build and test

Expand Down Expand Up @@ -162,14 +165,33 @@ validation. Manual runs support three signing modes:
temporary self-signed certificate plus the public `.cer` needed for local
installation;
- `official` requires the approved immutable commit from
`release-policy.json` and may run only from `main`.
`release-policy.json`, may run only from `main`, and publishes the signed
packages as permanent assets on a GitHub Release named by the policy.

Official signing uses the protected `release-signing` environment, Azure OIDC,
and the existing OpenClaw Artifact Signing account and certificate profile.
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
`OpenClawGateway-<version>.msixbundle` as the recommended download, plus signed
`OpenClawGateway-<version>-x64.msix` and
`OpenClawGateway-<version>-arm64.msix` packages for architecture-specific
deployment. The duplicate GitHub Actions artifacts remain short-lived transport
and diagnostic copies.

An `.msixbundle` is a single installable container for the x64 and ARM64 MSIX
packages; Windows selects the package appropriate for the device. An
`.appinstaller` file is separate update-channel metadata rather than an
alternative package format. This repository does not publish one yet, so GitHub
Release installs do not opt devices into automatic update checks.

### Official signing setup

The `release-signing` GitHub environment must define these environment
Expand Down
4 changes: 3 additions & 1 deletion release-policy.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"repository": "https://github.com/openclaw/openclaw",
"releaseTag": "v2026.8.2",
"releaseTag": "v0.0.0.0",
"packageVersion": "0.0.0.0",
"payloadPackageVersion": "2026.8.2",
"approvedCommit": "0965053fe6b9341776df147a6934b7485c60b5ca",
"publisher": "CN=OpenClaw Foundation, O=OpenClaw Foundation, L=Mill Valley, S=California, C=US"
}
Loading