-
Notifications
You must be signed in to change notification settings - Fork 1
289 lines (276 loc) · 12.4 KB
/
Copy pathci.yml
File metadata and controls
289 lines (276 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# Boatstack-owned control plane.
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
concurrency:
group: boatstack-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
flow-sdk:
name: flow-sdk
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.sum
- name: Build TypeScript frontends
run: npm ci && npm run build:flow-sdk
- name: Prove frontend canonical equivalence
working-directory: boatstack
env:
BOATSTACK_REQUIRE_FLOW_FRONTEND: '1'
run: go test ./controlprogram -run TestTypeScriptDSLAndRawIRHaveOneCanonicalFingerprint
component:
name: component-${{ matrix.name }}
strategy:
fail-fast: false
matrix:
include:
- name: kernel-mechanism
packages: ./kernel
- name: control-program-compiler
packages: ./controlprogram ./delivery ./core ./flow/softwaredelivery
- name: standard-flow
packages: ./flow/standard ./internal/softwaredelivery/protocol
- name: extension-conformance
packages: ./extension/... ./distribution
- name: surface-parity
packages: ./internal/softwaredelivery/surfaces ./sdk ./cmd/boatstack-helper
- name: plant-integration
packages: ./internal/softwaredelivery/plant ./internal/softwaredelivery/effects ./internal/softwaredelivery/engine
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.sum
- name: Test ${{ matrix.name }}
working-directory: boatstack
run: go test ${{ matrix.packages }}
# Unix runs the full suite serially (~1-2 min) and is the unsharded correctness
# reference. Windows is sharded in `test-windows` (see below).
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.sum
- name: Show toolchain
run: go version
working-directory: boatstack
- name: Test runtime with race detector
if: matrix.os == 'ubuntu-latest'
run: go test -race ./...
working-directory: boatstack
- name: Test runtime
if: matrix.os == 'macos-latest'
run: go test ./...
working-directory: boatstack
- name: Build helper
run: go build ./cmd/boatstack-helper
working-directory: boatstack
- name: Validate Bash installer
shell: bash
run: bash -n install.sh
race-critical:
name: race-critical
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.sum
- name: Test concurrency-bearing packages
working-directory: boatstack
run: go test -race ./kernel ./internal/softwaredelivery/effects
# Windows `go test` is dominated by per-process spawn latency (each test spawns
# several `git` processes; the suite is ~330 tests run serially), so the full
# suite takes ~15 min on Windows vs ~1 min on Unix. In-process t.Parallel() is
# unsafe (the package swaps ~14 global function-seams), so we shard across
# processes: N runners, each running a disjoint, balanced subset. Shard
# assignment is an LPT makespan-minimization controller — see
# .github/scripts/ci_shard.py. Target: slowest shard < 5 min.
test-windows:
strategy:
fail-fast: false
matrix:
shard: [0, 1, 2, 3, 4, 5]
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
# Windows `go test`/`go build` is dominated by Microsoft Defender scanning
# the many small files the Go toolchain emits during compile/link. Excluding
# the Go caches, the workspace, and go.exe is a major wall-clock lever;
# sharding on top of this is what gets the suite under 5 min.
- name: Exclude Go caches from Microsoft Defender (Windows)
shell: pwsh
run: |
$targets = @(
"$env:LOCALAPPDATA\go-build", # GOCACHE (build cache)
"$env:USERPROFILE\go", # GOPATH incl. pkg\mod (GOMODCACHE)
$env:GITHUB_WORKSPACE, # sources + compiled test binaries
$env:RUNNER_TEMP
) | Where-Object { $_ -and $_.Trim() -ne '' } | Select-Object -Unique
foreach ($t in $targets) {
try {
Add-MpPreference -ExclusionPath $t -ErrorAction Stop
Write-Host "Defender exclusion added: $t"
} catch {
Write-Host "::warning::Defender exclusion failed for $t : $($_.Exception.Message)"
}
}
try { Add-MpPreference -ExclusionProcess 'go.exe' -ErrorAction Stop } catch {}
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.sum
- name: Show toolchain
run: go version
working-directory: boatstack
# Enumerate tests, pick this shard's balanced subset, and run only those.
# `go test -list` and `go test -run` share the warm GOCACHE, so the second
# compile is a cache hit. An empty shard is a clean skip — never
# `go test -run ''`, which would run the whole suite.
- name: Test shard ${{ matrix.shard }}
shell: bash
working-directory: boatstack
run: |
regex=$(go test -list '^Test' ./... | python "${{ github.workspace }}/.github/scripts/ci_shard.py" --total 6 --index ${{ matrix.shard }})
if [ -z "$regex" ]; then
echo "Shard ${{ matrix.shard }} is empty; nothing to run."
exit 0
fi
echo "Shard ${{ matrix.shard }} selects $(( $(grep -o '|' <<<"$regex" | wc -l) + 1 )) tests"
go test -run "$regex" ./...
# Windows-only, non-test validation runs once (on shard 0), not per shard.
- name: Build helper
if: matrix.shard == '0'
working-directory: boatstack
run: go build ./cmd/boatstack-helper
- name: Validate PowerShell installer
if: matrix.shard == '0'
shell: pwsh
run: |
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path install.ps1), [ref]$tokens, [ref]$errors) > $null
if ($errors.Count -gt 0) {
$errors | ForEach-Object { Write-Error $_ }
exit 1
}
- name: Prove PowerShell install and hydration boundaries
if: matrix.shard == '0'
shell: pwsh
run: |
$root = Join-Path $env:RUNNER_TEMP ("boatstack-installer-" + [guid]::NewGuid().ToString("N"))
$helper = (Resolve-Path boatstack/boatstack-helper.exe).Path
$version = (& $helper version).Trim()
$digest = (Get-FileHash -Algorithm SHA256 -LiteralPath $helper).Hash.ToLowerInvariant()
try {
foreach ($mode in @("hydrate", "install")) {
$repository = Join-Path $root "$mode-repository"
New-Item -ItemType Directory -Force -Path $repository | Out-Null
& git -C $repository init --initial-branch=main | Out-Null
& git -C $repository config user.email "installer-contract@example.invalid"
& git -C $repository config user.name "Installer Contract"
Set-Content -LiteralPath (Join-Path $repository "README.md") -Value "fixture"
& git -C $repository add README.md
& git -C $repository commit -m "Initialize fixture" | Out-Null
if ($mode -eq "hydrate") {
& git -C $repository checkout --detach | Out-Null
}
$runtimeHome = Join-Path $root "$mode-home"
$installDir = Join-Path $root "$mode-bin"
$launcher = Join-Path $installDir "boatstack.exe"
if ($mode -eq "hydrate") {
New-Item -ItemType Directory -Force -Path $installDir | Out-Null
[System.IO.File]::WriteAllBytes($launcher, [byte[]](1, 2, 3, 4))
$launcherBefore = (Get-FileHash -Algorithm SHA256 -LiteralPath $launcher).Hash
}
$env:BOATSTACK_REPO = $repository
$env:BOATSTACK_HOME = $runtimeHome
$env:BOATSTACK_INSTALL_DIR = $installDir
$env:BOATSTACK_MODE = $mode
$env:BOATSTACK_VERSION = $version
$env:BOATSTACK_BINARY = $helper
$env:BOATSTACK_BINARY_SHA256 = $digest
$env:BOATSTACK_EXPECTED_RUNTIME_SHA256 = $digest
& ./install.ps1
if ($LASTEXITCODE -ne 0) { throw "$mode installer failed" }
$runtime = Join-Path $runtimeHome "runtimes\$version-$digest\boatstack-runtime.exe"
if (-not (Test-Path -LiteralPath $runtime -PathType Leaf)) { throw "$mode did not stage the runtime" }
if (-not (Test-Path -LiteralPath $launcher -PathType Leaf)) { throw "$mode did not stage the launcher" }
if ($mode -eq "hydrate") {
if ((Get-FileHash -Algorithm SHA256 -LiteralPath $launcher).Hash -ne $launcherBefore) { throw "hydrate replaced the shared launcher" }
if (Test-Path -LiteralPath (Join-Path $repository ".boatstack")) { throw "hydrate changed repository state" }
if (Test-Path -LiteralPath (Join-Path $repository ".git\boatstack")) { throw "hydrate changed controller state" }
if (& git -C $repository status --porcelain) { throw "hydrate changed tracked repository files" }
} elseif (-not (Test-Path -LiteralPath (Join-Path $repository ".boatstack\runtime.json") -PathType Leaf)) {
throw "install did not initialize the repository runtime pin"
}
}
$missingEvidenceRepository = Join-Path $root "missing-evidence-repository"
New-Item -ItemType Directory -Force -Path $missingEvidenceRepository | Out-Null
& git -C $missingEvidenceRepository init --initial-branch=main | Out-Null
$missingEvidenceHome = Join-Path $root "missing-evidence-home"
$missingEvidenceBin = Join-Path $root "missing-evidence-bin"
$env:BOATSTACK_REPO = $missingEvidenceRepository
$env:BOATSTACK_HOME = $missingEvidenceHome
$env:BOATSTACK_INSTALL_DIR = $missingEvidenceBin
$env:BOATSTACK_MODE = "hydrate"
$env:BOATSTACK_VERSION = "latest"
Remove-Item Env:BOATSTACK_EXPECTED_RUNTIME_SHA256 -ErrorAction SilentlyContinue
try {
& ./install.ps1
throw "hydrate without exact evidence succeeded"
} catch {
if ($_ -notmatch "BOATSTACK_RUNTIME_PIN_INVALID") { throw }
}
if (Test-Path -LiteralPath $missingEvidenceHome) { throw "invalid hydrate changed runtime storage" }
if (Test-Path -LiteralPath $missingEvidenceBin) { throw "invalid hydrate changed launcher storage" }
} finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}
repository-contract:
name: repository-contract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.sum
- name: Require an append-only release message
if: github.event_name == 'pull_request'
run: >-
python .github/scripts/release_notes.py check-policy
--repo .
--base "${{ github.event.pull_request.base.sha }}"
--head "${{ github.event.pull_request.head.sha }}"
- name: Verify repository contract
env:
PYTHONUTF8: "1"
run: python -m unittest discover -s .github/tests -p 'test_*.py' -v