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
24 changes: 24 additions & 0 deletions .github/msix-version-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"schemaVersion": 1,
"lastAllocated": {
"2026.9.4": 400
},
"evidence": {
"2026.9.4": {
"storePackageVersion": "2026.9.400.0",
"workflowRun": "https://github.com/natalie-aguinaldo/openclaw-windows-node/actions/runs/35303248183",
"workflowHeadCommit": "01f2bf7ef407f8b9125f40bf1a4638c0361818c2",
"packageSourceCommit": "ded1d4aed4d1a69859a817dc57087f2c6142deab",
"artifacts": {
"x64": {
"artifactId": 10531666502,
"packageSha256": "4612ff57a12489584a853a5eed38f687d6a8ac2f89836eb74c4eab8b1514d4db"
},
"arm64": {
"artifactId": 10531117192,
"packageSha256": "febd4c4db99d153eea85c97000dd3583364ce671c3e8598bd7bf972a89cc788d"
}
}
}
}
}
100 changes: 90 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ jobs:
shell: pwsh
run: ./scripts/test-msix-ci-artifacts.ps1

- name: Validate MSIX version allocation
shell: pwsh
run: ./scripts/test-msix-versioning.ps1

- name: Validate MSIX preview source selection
shell: pwsh
run: ./scripts/test-msix-preview-source-version.ps1

- name: Validate Store MSIX alpha release assets
shell: pwsh
run: ./scripts/test-msix-alpha-release.ps1
Expand Down Expand Up @@ -163,6 +171,8 @@ jobs:
isPrerelease: ${{ steps.release_version.outputs.isPrerelease }}
isStableCorrection: ${{ steps.release_version.outputs.isStableCorrection }}
isMsixAlpha: ${{ steps.release_version.outputs.isMsixAlpha }}
msixVersionInfo: ${{ steps.msix_preview.outputs.versionInfo }}
msixSourceVersion: ${{ steps.msix_preview.outputs.sourceVersion }}
steps:
- uses: actions/checkout@v7
with:
Expand Down Expand Up @@ -224,6 +234,51 @@ jobs:
$isMsixAlpha = $isPrerelease -and ($env:GITHUB_REF -cmatch '^refs/tags/v(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)-alpha\.(?:0|[1-9]\d*)$')
"isMsixAlpha=$($isMsixAlpha.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT

- name: Resolve MSIX preview version
id: msix_preview
if: ${{ !(github.repository == 'openclaw/openclaw-windows-node' && startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')) }}
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
$sourceVersion = .\scripts\Get-OpenClawMsixPreviewSourceVersion.ps1 `
-GitHubToken $env:GH_TOKEN
$info = .\scripts\Resolve-MsixPackageVersion.ps1 `
-SourceVersion $sourceVersion -SourceCommit $env:GITHUB_SHA `
-SourceRef $env:GITHUB_REF -Repository openclaw/openclaw-windows-node `
-GitHubToken $env:GH_TOKEN
"sourceVersion=$sourceVersion" >> $env:GITHUB_OUTPUT
"versionInfo=$($info | ConvertTo-Json -Depth 4 -Compress)" >> $env:GITHUB_OUTPUT

reserve-msix-version:
name: Reserve official MSIX version
needs: [change-classification, metadata]
if: ${{ !cancelled() && needs.metadata.result == 'success' && github.repository == 'openclaw/openclaw-windows-node' && startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
runs-on: windows-latest
permissions:
contents: write
outputs:
versionInfo: ${{ steps.reserve.outputs.versionInfo }}
sourceVersion: ${{ steps.reserve.outputs.sourceVersion }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false

- name: Reserve or reuse release package version
id: reserve
shell: pwsh
env:
SOURCE_VERSION: ${{ needs.metadata.outputs.semVer }}
GH_TOKEN: ${{ github.token }}
run: |
$info = .\scripts\Resolve-MsixPackageVersion.ps1 `
-SourceVersion $env:SOURCE_VERSION -SourceCommit $env:GITHUB_SHA `
-SourceRef $env:GITHUB_REF -Repository $env:GITHUB_REPOSITORY -Reserve
"sourceVersion=$($info.sourceVersion)" >> $env:GITHUB_OUTPUT
"versionInfo=$($info | ConvertTo-Json -Depth 4 -Compress)" >> $env:GITHUB_OUTPUT

core-tests:
name: Core and CLI tests
needs: [change-classification, fast-validation]
Expand Down Expand Up @@ -820,13 +875,15 @@ jobs:

build-msix:
name: MSIX artifacts (${{ matrix.architecture }})
needs: [change-classification, metadata]
if: ${{ !cancelled() && needs.change-classification.result == 'success' && needs.metadata.result == 'success' && (needs.change-classification.outputs.x64_release == 'true' || needs.change-classification.outputs.arm64_release == 'true') }}
needs: [change-classification, metadata, reserve-msix-version]
if: ${{ !cancelled() && needs.change-classification.result == 'success' && needs.metadata.result == 'success' && (needs.reserve-msix-version.result == 'success' || needs.reserve-msix-version.result == 'skipped') && (needs.change-classification.outputs.x64_release == 'true' || needs.change-classification.outputs.arm64_release == 'true') }}
# Unsigned Store packages may publish to alpha releases only. Dev packages stay workflow-only.
runs-on: ${{ matrix.architecture == 'arm64' && 'windows-11-arm' || 'windows-latest' }}
env:
OPENCLAW_BUILD_VERSION: ${{ needs.metadata.outputs.semVer }}
DEV_MSIX_REVISION: ${{ github.run_number }}
MSIX_VERSION_INFO: ${{ needs.reserve-msix-version.outputs.versionInfo || needs.metadata.outputs.msixVersionInfo }}
MSIX_SOURCE_VERSION: ${{ needs.reserve-msix-version.outputs.sourceVersion || needs.metadata.outputs.msixSourceVersion }}
strategy:
fail-fast: false
matrix:
Expand All @@ -850,9 +907,23 @@ jobs:
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: nuget-${{ runner.os }}-

- name: Validate shared MSIX version allocation
shell: pwsh
run: |
if ([string]::IsNullOrWhiteSpace($env:MSIX_VERSION_INFO)) {
throw 'Missing MSIX version allocation; refusing to build with a fallback version.'
}
. .\scripts\MsixVersioning.ps1
$info = Assert-MsixVersionInfo -VersionInfo ($env:MSIX_VERSION_INFO | ConvertFrom-Json) `
-SourceCommit $env:GITHUB_SHA -SourceVersion $env:MSIX_SOURCE_VERSION
$info | ConvertTo-Json -Depth 4 |
Set-Content -LiteralPath "$env:RUNNER_TEMP\openclaw-msix-version.json" -Encoding utf8

- name: Build and validate unsigned Store MSIX
shell: pwsh
run: .\scripts\Build-StoreMsix.ps1 -Architecture ${{ matrix.architecture }}
run: >
.\scripts\Build-StoreMsix.ps1 -Architecture ${{ matrix.architecture }}
-VersionInfoPath "$env:RUNNER_TEMP\openclaw-msix-version.json"

- name: Upload unsigned Store submission artifact
uses: actions/upload-artifact@v7
Expand All @@ -869,20 +940,23 @@ jobs:

- name: Build signed Dev MSIX
shell: pwsh
run: >
.\build.ps1 -Project WinUI -Configuration Release -Msix Dev
-MsixRevision $env:DEV_MSIX_REVISION
-MsixOutputDirectory "$env:RUNNER_TEMP\openclaw-dev-appx"
run: |
$info = Get-Content -LiteralPath "$env:RUNNER_TEMP\openclaw-msix-version.json" -Raw | ConvertFrom-Json
.\build.ps1 -Project WinUI -Configuration Release -Msix Dev `
-MsixRevision $env:DEV_MSIX_REVISION -MsixBaseVersion $info.packageBaseVersion `
-MsixOutputDirectory "$env:RUNNER_TEMP\openclaw-dev-appx"

- name: Validate and stage Dev tester artifact
shell: pwsh
run: |
$info = Get-Content -LiteralPath "$env:RUNNER_TEMP\openclaw-msix-version.json" -Raw | ConvertFrom-Json
$thumbprint = (Get-Content "$env:LOCALAPPDATA\OpenClawDevelopment\MSIX\dev-msix-thumbprint.txt" -Raw).Trim()
.\scripts\Export-DevMsixArtifact.ps1 `
-Architecture ${{ matrix.architecture }} `
-PackageDirectory "$env:RUNNER_TEMP\openclaw-dev-appx" `
-ExpectedRevision $env:DEV_MSIX_REVISION `
-ExpectedVersion $env:OPENCLAW_BUILD_VERSION `
-ExpectedVersion $info.packageBaseVersion `
-VersionInfoPath "$env:RUNNER_TEMP\openclaw-msix-version.json" `
-CertificateThumbprint $thumbprint `
-OutputDirectory "artifacts\msix-dev\${{ matrix.architecture }}"

Expand Down Expand Up @@ -964,8 +1038,8 @@ jobs:
"CI Gate passed $validatedMode validation." >> $env:GITHUB_STEP_SUMMARY

release:
needs: [change-classification, metadata, build-x64, build-arm64, ci-gate]
if: startsWith(github.ref, 'refs/tags/v') && needs.ci-gate.result == 'success' && needs.change-classification.outputs.full == 'true' && needs.metadata.result == 'success' && needs.build-x64.result == 'success' && needs.build-arm64.result == 'success' && !cancelled()
needs: [change-classification, metadata, reserve-msix-version, build-x64, build-arm64, ci-gate]
if: startsWith(github.ref, 'refs/tags/v') && needs.ci-gate.result == 'success' && needs.change-classification.outputs.full == 'true' && needs.metadata.result == 'success' && needs.reserve-msix-version.result == 'success' && needs.build-x64.result == 'success' && needs.build-arm64.result == 'success' && !cancelled()
runs-on: windows-latest
environment: release-signing
permissions:
Expand Down Expand Up @@ -1181,11 +1255,17 @@ jobs:
shell: pwsh
env:
RELEASE_VERSION: ${{ needs.metadata.outputs.semVer }}
MSIX_VERSION_INFO: ${{ needs.reserve-msix-version.outputs.versionInfo }}
run: |
if ([string]::IsNullOrWhiteSpace($env:MSIX_VERSION_INFO)) {
throw 'Missing official MSIX reservation.'
}
$env:MSIX_VERSION_INFO | Set-Content -LiteralPath "$env:RUNNER_TEMP\openclaw-msix-version.json" -Encoding utf8
$assets = .\scripts\Stage-StoreMsixReleaseAssets.ps1 `
-ArtifactDirectory 'artifacts\msix-alpha' `
-OutputDirectory 'msix-alpha-release' `
-Version $env:RELEASE_VERSION `
-VersionInfoPath "$env:RUNNER_TEMP\openclaw-msix-version.json" `
-ExpectedSourceCommit $env:GITHUB_SHA
@('files<<MSIX_ALPHA_FILES'; $assets.Files; 'MSIX_ALPHA_FILES') >> $env:GITHUB_OUTPUT
@('notes<<MSIX_ALPHA_NOTES'; $assets.Notes; 'MSIX_ALPHA_NOTES') >> $env:GITHUB_OUTPUT
Expand Down
87 changes: 80 additions & 7 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,15 +356,88 @@ CI passes `-MsixRevision $env:GITHUB_RUN_NUMBER` to the existing
`build.ps1 -Project WinUI -Configuration Release -Msix Dev` path, with a fresh
`-MsixOutputDirectory`. Explicit revisions must be 1-65535; overflow fails
instead of wrapping. Omitting these options preserves local build behavior.
The same run's reruns keep the same version, not a new upgrade. Version
ordering is not guaranteed across forks, branches, local builds, or decreasing
base versions. Do not uninstall/downgrade an existing Dev package just to
Tagged-release reruns reuse their reserved package base. PR/main previews use
the latest published stable Windows release line from the canonical upstream
repository and do not consume numbers. For example, while Latest is
`v2026.9.4`, previews use the `2026.9.4` allocation range and currently produce
Store `2026.9.402.0`; a future official `v2026.9.5` release still starts in the
`500-599` range. A preview candidate can advance after another official release
reserves a number. Version ordering is not guaranteed across forks, branches,
local builds, or decreasing base versions.
Do not uninstall/downgrade an existing Dev package just to
resolve a version conflict without considering its settings and data.
When `-MsixBaseVersion` is omitted, local `-Msix Dev` builds compare the
application-derived base with the installed Dev package and reuse the installed
three-part base when it is higher. The fourth component still increments from
the installed revision. This keeps ordinary local builds upgrade-compatible
after installing an encoded CI Dev package without changing GitVersion.

CI allocates a separate MSIX base without changing the application's GitVersion,
assembly metadata, EXE/ZIP versions, or GitHub release tags. For app `X.Y.Z`,
the third package component starts at `Z * 100` and advances within that patch's
100-number range. For example:

| App release line | MSIX Store versions |
|---|---|
| `2026.9.4`, including its alpha/correction tags | `2026.9.400.0` through `2026.9.499.0` |
| `2026.9.5`, including its alpha/correction tags | `2026.9.500.0` through `2026.9.599.0` |
| `2026.10.1`, including its alpha/correction tags | `2026.10.100.0` through `2026.10.199.0` |

The already-used `2026.9.400.0` is recorded with its workflow and artifact
provenance in `.github/msix-version-baseline.json`. The canonical ledger also
contains reservation `msix-package/2026.9.4/401`, so the next unreserved
`2026.9.4` packaging candidate is `2026.9.402.0`. Correction suffixes do not
occupy their own digit.

All tags on the same app patch share the counter, including revisions 10, 11,
and onward. Exhaustion fails rather than entering the next patch's range.
Every component must fit `uint16`; patch 655 has only the remaining 65500-65535
slots, and larger patches cannot be encoded.

Only `push` or `workflow_dispatch` builds of `v*` tags in the upstream repository
reserve versions. A separate write-scoped job allocates once before the
architecture matrix. PRs, ordinary main builds, and fork builds resolve Latest
from `openclaw/openclaw-windows-node`, then read the next candidate from that
same canonical reservation ledger. They are marked `preview` in
`msixVersionAllocation` in each metadata sidecar and are not official release
or Store-submission versions.
Both Store architectures share the reserved base and end in `.0`. Both Dev
architectures use the same base with the CI run number as the final component.

The allocator records reservations as append-only annotated Git tags under
`msix-package/<app-base>/<package-counter>`. Concurrent claims use atomic
create-ref requests; reruns of the same source tag/commit reuse their record.
Failed builds keep their reservations, so numbers are never recycled.
Do not delete, move, or repurpose reservation tags, including during alpha
release cleanup. The repository's active `Protect MSIX package reservations`
ruleset blocks deletion and non-fast-forward updates under this namespace while
permitting the official workflow to create refs. Preserve that ruleset as part
of the release contract.
See [MSIX version allocation](docs/RELEASING.md#msix-version-allocation).

For an encoded local preview, save the readonly resolver result outside tracked
source, then pass it through the validated builder:

```powershell
$info = .\scripts\Resolve-MsixPackageVersion.ps1 `
-SourceVersion (.\scripts\Get-OpenClawMsixPreviewSourceVersion.ps1) `
-SourceCommit (git rev-parse HEAD) `
-SourceRef "refs/heads/$(git branch --show-current)" `
-Repository openclaw/openclaw-windows-node
$info | ConvertTo-Json -Depth 4 |
Set-Content "$env:TEMP\openclaw-msix-preview.json" -Encoding utf8
.\scripts\Build-StoreMsix.ps1 -Architecture x64 `
-VersionInfoPath "$env:TEMP\openclaw-msix-preview.json"
.\build.ps1 -Project WinUI -Configuration Release -Msix Dev `
-MsixBaseVersion $info.packageBaseVersion
```

The Store version stays `X.Y.Z.0`. Prerelease and stable-correction suffixes
can therefore produce the same Store version; CI artifacts do not promise
unique Store submissions for every tag. Store submission version allocation
must be resolved before distribution is enabled in #1375.
`VersionInfoPath` validates the source commit and expected actual package
version before writing metadata. `MsixBaseVersion` changes only the package
manifest base; it does not override the app's assembly versions.
Ordinary local builds that omit these options preserve their previous
unallocated version calculation. Store distribution remains gated by #1375;
this allocator does not submit packages to Partner Center.

Canonical `vX.Y.Z-alpha.N` releases also attach the **unsigned Store** MSIX
files and architecture-specific metadata, for manual upload to Partner Center.
Expand Down
Loading
Loading