Skip to content

Commit 9bba2f5

Browse files
shanselmanCopilot
andcommitted
feat(setup): enforce validated gateway releases
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 862c995 commit 9bba2f5

32 files changed

Lines changed: 2485 additions & 326 deletions

.github/workflows/ci.yml

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,48 +42,6 @@ jobs:
4242
with:
4343
fetch-depth: 0
4444

45-
- name: Detect gateway LKG drift from npm latest
46-
id: gateway_lkg_drift
47-
continue-on-error: true
48-
shell: pwsh
49-
run: |
50-
$sourcePath = "src/OpenClaw.SetupEngine/GatewayLkgVersion.cs"
51-
$source = Get-Content -LiteralPath $sourcePath -Raw
52-
$match = [regex]::Match($source, 'LkgVersion\s*=\s*"([^"]+)"')
53-
if (-not $match.Success) {
54-
throw "Unable to parse LKG version from $sourcePath"
55-
}
56-
57-
$pinned = $match.Groups[1].Value
58-
$latest = (Invoke-RestMethod -Uri "https://registry.npmjs.org/openclaw/latest" -TimeoutSec 30).version
59-
if ([string]::IsNullOrWhiteSpace($latest)) {
60-
throw "Unable to resolve npm latest version for openclaw."
61-
}
62-
if ($latest -notmatch '^[0-9]+\.[0-9]+\.[0-9]+([.\-+][A-Za-z0-9.\-]+)*$') {
63-
throw "Resolved npm latest version has unexpected format: $latest"
64-
}
65-
66-
"pinned=$pinned" >> $env:GITHUB_OUTPUT
67-
"latest=$latest" >> $env:GITHUB_OUTPUT
68-
69-
if ($pinned -ne $latest) {
70-
"drifted=true" >> $env:GITHUB_OUTPUT
71-
Write-Host "::warning::Gateway LKG drift detected: pinned $pinned, npm latest $latest. Run gateway-lkg-update workflow to refresh the standing draft PR."
72-
Write-Error "Gateway LKG drift detected (pinned $pinned, npm latest $latest)."
73-
"### :warning: Gateway LKG drift detected" >> $env:GITHUB_STEP_SUMMARY
74-
"" >> $env:GITHUB_STEP_SUMMARY
75-
"- Pinned LKG: $pinned" >> $env:GITHUB_STEP_SUMMARY
76-
"- npm latest: $latest" >> $env:GITHUB_STEP_SUMMARY
77-
"- Action: run `gateway-lkg-update` to refresh the standing draft PR." >> $env:GITHUB_STEP_SUMMARY
78-
exit 1
79-
} else {
80-
"drifted=false" >> $env:GITHUB_OUTPUT
81-
Write-Host "Gateway LKG is current ($pinned)."
82-
"### Gateway LKG is current" >> $env:GITHUB_STEP_SUMMARY
83-
"" >> $env:GITHUB_STEP_SUMMARY
84-
"- Pinned LKG: $pinned" >> $env:GITHUB_STEP_SUMMARY
85-
}
86-
8745
- name: Setup .NET 10
8846
uses: actions/setup-dotnet@v6
8947
with:

.github/workflows/gateway-lkg-update.yml

Lines changed: 0 additions & 141 deletions
This file was deleted.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
name: Gateway Release Candidate
2+
3+
on:
4+
schedule:
5+
- cron: '23 7 * * *'
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: Exact official Gateway version, or empty to inspect npm latest and extended-stable
10+
required: false
11+
type: string
12+
13+
permissions:
14+
contents: write
15+
pull-requests: write
16+
17+
concurrency:
18+
group: gateway-release-candidate
19+
cancel-in-progress: false
20+
21+
jobs:
22+
discover:
23+
runs-on: windows-latest
24+
env:
25+
BRANCH_NAME: automation/gateway-release-candidate
26+
GH_TOKEN: ${{ github.token }}
27+
GITHUB_TOKEN: ${{ github.token }}
28+
REQUESTED_VERSION: ${{ inputs.version }}
29+
steps:
30+
- uses: actions/checkout@v7
31+
with:
32+
fetch-depth: 0
33+
34+
- name: Resolve candidate
35+
id: candidate
36+
shell: pwsh
37+
run: |
38+
$requested = ([string]$env:REQUESTED_VERSION).Trim()
39+
if ($requested) {
40+
$candidates = @($requested)
41+
} else {
42+
$tags = Invoke-RestMethod -Uri "https://registry.npmjs.org/-/package/openclaw/dist-tags" -TimeoutSec 30
43+
$candidates = @(
44+
@([string]$tags.latest, [string]$tags.'extended-stable') |
45+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
46+
Select-Object -Unique
47+
)
48+
}
49+
50+
$policy = Get-Content .\src\OpenClaw.SetupEngine\GatewayReleasePolicy.cs -Raw
51+
$recommendedMatch = [regex]::Match(
52+
$policy,
53+
'RecommendedVersion\s*=\s*"(?<version>[^"]+)"')
54+
if (-not $recommendedMatch.Success) {
55+
throw "Could not read GatewayReleasePolicy.RecommendedVersion."
56+
}
57+
$recommended = $recommendedMatch.Groups['version'].Value
58+
$embeddedVersions = @(
59+
[regex]::Matches(
60+
$policy,
61+
'"(?<version>\d{4}\.\d{1,2}\.\d+(?:-\d+)?)"') |
62+
ForEach-Object { $_.Groups['version'].Value } |
63+
Select-Object -Unique
64+
)
65+
66+
$passing = @()
67+
for ($index = 0; $index -lt $candidates.Count; $index++) {
68+
$version = $candidates[$index]
69+
$evidencePath = "gateway-candidate-evidence-$index.json"
70+
try {
71+
.\scripts\Test-GatewayReleaseCandidate.ps1 `
72+
-Version $version `
73+
-SummaryPath $evidencePath
74+
$passing += [pscustomobject]@{
75+
Version = $version
76+
EvidencePath = $evidencePath
77+
Embedded = $embeddedVersions -contains $version
78+
}
79+
} catch {
80+
Write-Warning "Gateway $version did not pass static candidate evidence: $($_.Exception.Message)"
81+
}
82+
}
83+
if ($passing.Count -eq 0) {
84+
throw "No discovered Gateway release passed the static evidence gate."
85+
}
86+
87+
$selected = @($passing | Where-Object { -not $_.Embedded }) |
88+
Select-Object -First 1
89+
if ($null -eq $selected) {
90+
$selected = @($passing | Where-Object { $_.Version -eq $recommended }) |
91+
Select-Object -First 1
92+
}
93+
if ($null -eq $selected) {
94+
$selected = $passing[0]
95+
}
96+
97+
Copy-Item -LiteralPath $selected.EvidencePath `
98+
-Destination gateway-candidate-evidence.json -Force
99+
for ($index = 0; $index -lt $candidates.Count; $index++) {
100+
Remove-Item -LiteralPath "gateway-candidate-evidence-$index.json" `
101+
-Force -ErrorAction SilentlyContinue
102+
}
103+
$candidate = $selected.Version
104+
105+
"version=$candidate" >> $env:GITHUB_OUTPUT
106+
"already_embedded=$($selected.Embedded.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
107+
108+
- name: Prepare evidence-only candidate branch
109+
if: steps.candidate.outputs.already_embedded != 'true'
110+
shell: pwsh
111+
env:
112+
CANDIDATE_VERSION: ${{ steps.candidate.outputs.version }}
113+
run: |
114+
$version = $env:CANDIDATE_VERSION
115+
git fetch origin main
116+
git checkout -B $env:BRANCH_NAME origin/main
117+
$evidence = Get-Content gateway-candidate-evidence.json -Raw | ConvertFrom-Json
118+
@"
119+
# Gateway release candidate
120+
121+
- Candidate: ``$version``
122+
- Protocol generation: ``$($evidence.protocolGeneration)``
123+
- npm integrity: ``$($evidence.npmIntegrity)``
124+
- npm integrity verified: ``$($evidence.npmIntegrityVerified)``
125+
- npm signature count: ``$($evidence.npmSignatureCount)``
126+
- npm signatures verified: ``$($evidence.npmSignatureVerified)``
127+
- npm SLSA provenance verified: ``$($evidence.npmProvenance)``
128+
- GitHub stable release: ``$($evidence.githubStableRelease)``
129+
- Stable release manifest and soak: ``$($evidence.stableReleaseManifest)``
130+
- GitHub verified tag: ``$($evidence.githubVerifiedTag)``
131+
- Signed extended-stable tag: ``$($evidence.extendedStableTag)``
132+
133+
This is discovery evidence only. It does not promote the candidate.
134+
Promotion requires exact-version Windows setup, pairing, reconnect,
135+
recovery, and representative Gateway-to-node invocation proof.
136+
"@ | Set-Content -LiteralPath docs/gateway-release-candidate.md -Encoding utf8
137+
git add docs/gateway-release-candidate.md
138+
if (git diff --cached --quiet) {
139+
exit 0
140+
}
141+
git config user.name "github-actions[bot]"
142+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
143+
git commit -m "chore(setup): validate gateway candidate $version"
144+
git push --force-with-lease origin $env:BRANCH_NAME
145+
146+
- name: Create or update standing draft PR
147+
if: steps.candidate.outputs.already_embedded != 'true'
148+
shell: pwsh
149+
env:
150+
CANDIDATE_VERSION: ${{ steps.candidate.outputs.version }}
151+
run: |
152+
$version = $env:CANDIDATE_VERSION
153+
$title = "chore(setup): validate gateway candidate $version"
154+
$body = @"
155+
## Candidate discovery
156+
157+
This PR records upstream stable-release evidence for exact Gateway
158+
candidate ``$version``. It intentionally does not update
159+
``GatewayReleasePolicy.RecommendedVersion``.
160+
161+
## Promotion gate
162+
163+
- [ ] Exact package installs on a clean Windows WSL setup
164+
- [ ] CLI and ``hello-ok.server.version`` equal ``$version``
165+
- [ ] Negotiated Gateway protocol is v4
166+
- [ ] Operator and Windows node pairing pass
167+
- [ ] Tray restart/reconnect passes
168+
- [ ] Recovery shards pass
169+
- [ ] Representative Gateway-to-node invocation passes
170+
- [ ] Current-head proof artifacts are linked
171+
172+
Generate candidate-only E2E input with:
173+
174+
``.\scripts\Test-GatewayReleaseCandidate.ps1 -Version $version -SummaryPath gateway-candidate-evidence.json``
175+
176+
Then set ``OPENCLAW_E2E_GATEWAY_VERSION=$version`` and
177+
``OPENCLAW_E2E_GATEWAY_EVIDENCE=<absolute evidence path>`` for the
178+
setup/connect and recovery shards. Normal product setup never accepts
179+
this external candidate evidence.
180+
"@
181+
$number = gh pr list --state open --head $env:BRANCH_NAME --json number --jq '.[0].number // empty'
182+
if ($number) {
183+
gh pr edit $number --title $title --body $body
184+
} else {
185+
gh pr create --draft --base main --head $env:BRANCH_NAME --title $title --body $body
186+
}
187+
188+
- name: Upload candidate evidence
189+
if: always()
190+
uses: actions/upload-artifact@v7
191+
with:
192+
name: gateway-candidate-evidence
193+
path: gateway-candidate-evidence.json
194+
if-no-files-found: warn

0 commit comments

Comments
 (0)