Repository navigation
452 lines (392 loc) · 15.1 KB
/
Copy pathpr_validation.yml
File metadata and controls
452 lines (392 loc) · 15.1 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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
name: pr_validation
on:
# Merge queues test a synthetic merge group rather than the pull request ref.
# Required checks must subscribe to this event or queued PRs will never merge.
merge_group:
types:
- checks_requested
push:
branches:
- master
- dev
- main
pull_request:
branches:
- master
- dev
- main
- feature/*
permissions:
contents: read
# One run per ref (or per PR). A newer run cancels the older one in the same
# group, so overlapping dev pushes do not pile jobs onto the runner pool.
concurrency:
group: pr_validation-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test-${{matrix.os}}
# The 3-OS matrix is the PR gate. A post-merge push runs Post-merge Verify
# instead, so the expensive cross-platform legs stay off the merge path.
if: github.event_name != 'push'
runs-on: ${{matrix.os}}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
rid: linux-x64
- os: windows-latest
rid: win-x64
- os: macos-26
rid: osx-arm64
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
lfs: true
fetch-depth: 0
- name: "Install .NET SDK"
uses: actions/setup-dotnet@v6
with:
global-json-file: "./global.json"
- name: "Restore .NET tools"
run: dotnet tool restore
- name: "Validate release notes parser"
shell: pwsh
run: ./scripts/smoke/release-notes-smoke.ps1
- name: "Update release notes"
shell: pwsh
run: |
./build.ps1
- name: "dotnet build"
run: dotnet build -c Release
# .NET Framework tests can't run reliably on Linux, so we only do .NET 8
- name: "dotnet test"
shell: bash
# blame-hang aborts + writes a Sequence file (naming the in-flight test) and a full process
# dump if a test stalls (no test activity) for 300s, so a hang fails fast (~minutes, not the
# 30-min job cap) and is diagnosable from the CI log + the uploaded dump instead of a silent
# timeout. The full dump carries every thread's stack — needed to confirm the suspected
# macOS/ARM64 weak-memory-ordering hang in the TUI view-models.
run: dotnet test -c Release --filter "Category!=NativeShell" --blame-hang-timeout 300s --blame-hang-dump-type full --results-directory ./TestResults
- name: "dotnet test (native shell, serial)"
if: runner.os == 'Windows'
shell: bash
# The native PowerShell test needs an isolated Windows host.
run: dotnet test -c Release --no-build --filter "Category=NativeShell" --blame-hang-timeout 300s --blame-hang-dump-type full --results-directory ./TestResults
# Diagnostic: surface the blame Sequence file (names the stuck test) in the CI log so a
# platform-specific hang can be pinpointed even without downloading the dump artifact.
- name: "Show hang sequence (if a test hung)"
if: always()
shell: bash
run: |
seq=$(find ./TestResults -name '*Sequence*.xml' 2>/dev/null || true)
if [ -n "$seq" ]; then
for f in $seq; do echo "===== HANG SEQUENCE: $f ====="; cat "$f"; echo; done
else
echo "No blame Sequence file — no test hang detected."
fi
# The VSTest adapter shows only "Test Assembly Cleanup Failure" for the temp-leak gate.
# The gate also writes its report next to the test assembly. Print it here.
- name: "Show test temp leaks (if a test leaked)"
if: always()
shell: bash
run: |
reports=$(find ./src ./tests -name 'test-temp-leaks.txt' 2>/dev/null || true)
if [ -n "$reports" ]; then
for f in $reports; do echo "===== TEST TEMP LEAKS: $f ====="; cat "$f"; echo; done
else
echo "No test temp leak report."
fi
# Upload the hang dump + sequence file so a dedicated agent can open the dump (dotnet-dump
# analyze / lldb) and read the stalled thread stacks. Per-OS name; only the macOS run is
# expected to produce one today.
- name: "Upload hang dump"
if: always()
uses: actions/upload-artifact@v7
with:
name: test-hang-dump-${{ matrix.os }}
path: ./TestResults
if-no-files-found: ignore
retention-days: 14
- name: "Publish CLI (single-file, self-contained)"
if: runner.os != 'Windows'
run: >
dotnet publish src/Netclaw.Cli/Netclaw.Cli.csproj
-c Release
-r ${{ matrix.rid }}
--self-contained true
/p:PublishSingleFile=true
/p:EnableCompressionInSingleFile=true
/p:IncludeNativeLibrariesForSelfExtract=true
-o ./publish/cli
- name: "Package and stage CLI binary (simulate release install)"
if: runner.os != 'Windows'
shell: bash
run: |
mkdir -p ./archives ./install-test
tar czf ./archives/netclaw-test.tar.gz -C ./publish/cli netclaw
tar xzf ./archives/netclaw-test.tar.gz -C ./install-test
- name: "CLI smoke tests"
if: runner.os != 'Windows'
shell: bash
run: bash scripts/smoke/cli-smoke.sh
env:
NETCLAW_CLI: ./install-test/netclaw
mutation-gates:
# One job definition for every focused Stryker gate. Each matrix group shares
# one checkout, SDK install, and tool restore. The session readiness gate
# has its own group because the existing groups run near the Windows test.
name: Mutation Gates (${{ matrix.group }})
if: github.event_name != 'push'
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- group: shell-command-analysis-a
gates: shell-command-analysis-a
- group: shell-command-analysis-b
gates: shell-command-analysis-b
- group: shell-assignment-and-paths
gates: shell-assignment path-access
- group: config-read-and-admission
gates: shell-config-read tool-authorizer-order mcp-artifact-admission
- group: verb-chain
gates: exact-verb-chain
- group: reminder
gates: reminder-execution literal-twin stream-commit-gate shutdown-notice-identity
- group: authorization-and-directory
gates: tool-authorization approval-directory
- group: shell-consent
gates: shell-once-identity compound-redirect thinking-settings
- group: guard-and-authority
gates: skill-manage-guard no-program input-one-authority skill-remove
- group: stop-proof
gates: stop-proof secrets-plaintext-leaf local-daemon-address
- group: daemon-log-retention
gates: daemon-log-retention config-startup-reconciliation log-redaction-registration
- group: session-reliability
gates: tool-batch-readiness headless-stop-rule open-child-runs local-control-proof stopped-turn-continuation
- group: child-owner-and-report
gates: child-owner-and-report chat-close-dispatch-expiry
- group: loopback-host-origin
gates: loopback-host-origin curation-same-name
- group: child-consent
gates: child-consent text-answer-guard child-resume-gate
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: "Install .NET SDK"
uses: actions/setup-dotnet@v6
with:
global-json-file: "./global.json"
- name: "Restore .NET tools"
run: dotnet tool restore
# Run every gate in the group, even after a failure, so one run reports
# all failed gates. The step fails if any gate fails.
- name: "Run mutation gates"
env:
GATES: ${{ matrix.gates }}
run: |
failed=()
for gate in $GATES; do
echo "::group::$gate"
if "./scripts/run-${gate}-mutations.sh"; then
echo "- \`$gate\`: passed" >> "$GITHUB_STEP_SUMMARY"
else
echo "- \`$gate\`: FAILED" >> "$GITHUB_STEP_SUMMARY"
failed+=("$gate")
fi
echo "::endgroup::"
done
if (( ${#failed[@]} > 0 )); then
echo "::error::Mutation gates failed: ${failed[*]}"
exit 1
fi
- name: "Upload mutation reports"
if: always()
uses: actions/upload-artifact@v7
with:
name: mutation-reports-${{ matrix.group }}
path: ./artifacts/stryker
if-no-files-found: ignore
retention-days: 14
copyright-headers:
name: Copyright Headers
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: "Verify copyright headers"
shell: pwsh
run: ./scripts/Add-FileHeaders.ps1 -Verify
no-bom:
name: No UTF-8 BOM
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: "Verify no .cs file starts with a UTF-8 BOM"
shell: bash
run: bash scripts/check-no-bom.sh
slopwatch:
name: Slopwatch
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: "Install .NET SDK"
uses: actions/setup-dotnet@v6
with:
global-json-file: "./global.json"
- name: "Restore .NET tools"
run: dotnet tool restore
- name: "slopwatch analyze"
run: dotnet slopwatch analyze
install-smoke:
name: Install Script Smoke-${{ matrix.os }}
if: github.event_name != 'push'
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-26, windows-latest]
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: "Install shell integration test dependencies"
if: runner.os == 'Linux'
run: bash scripts/smoke/install-shell-test-dependencies.sh
- name: "install.sh smoke test"
if: runner.os != 'Windows'
shell: bash
run: bash scripts/smoke/install-smoke.sh
- name: "install.ps1 smoke test"
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/smoke/install-smoke.ps1
- name: "install.ps1 Windows PowerShell 5.1 smoke test"
if: runner.os == 'Windows'
shell: powershell
run: ./scripts/smoke/install-smoke.ps1
eval-oracles:
name: Eval Oracle Tests
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
- name: Install eval assertion dependencies
run: sudo apt-get update && sudo apt-get install -y jq sqlite3
- name: Check eval assertions and fixture protocol
run: |
bash -n evals/run-evals.sh
bash -n evals/run-background-evals.sh
bash -n evals/cycle_evals.sh
python3 -m unittest discover -s evals -p 'test_*.py' -v
approval-outcome-direction:
name: Approval Outcome Direction
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# The check reads the review snapshot at the base revision with git show,
# so it needs the base history.
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: "Self-tests"
run: python3 -m unittest discover -s scripts/tests -p 'test_*.py' -v
# An allowed case must stay allowed, and a denied case must stay denied.
# See TOOLING.md "Outcome Direction Check".
- name: "Check approval outcome direction"
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE_REF: ${{ github.base_ref }}
MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
run: |
case "$EVENT_NAME" in
pull_request)
python3 scripts/check-approval-outcome-direction.py --base-ref "origin/$PR_BASE_REF" ;;
merge_group)
python3 scripts/check-approval-outcome-direction.py --base-rev "$MERGE_GROUP_BASE_SHA" ;;
push)
if [[ "$PUSH_BEFORE_SHA" =~ ^0+$ ]]; then
echo "New branch push: compare with the merge base of origin/dev."
python3 scripts/check-approval-outcome-direction.py --base-ref origin/dev
elif ! git merge-base --is-ancestor "$PUSH_BEFORE_SHA" HEAD 2>/dev/null; then
# A force push (for example, GitHub rebasing a stacked PR) replaces
# the history. The previous head is then not in this clone.
echo "Force push: $PUSH_BEFORE_SHA is not an ancestor of HEAD. Compare with the merge base of origin/dev."
python3 scripts/check-approval-outcome-direction.py --base-ref origin/dev
else
python3 scripts/check-approval-outcome-direction.py --base-rev "$PUSH_BEFORE_SHA"
fi ;;
*)
echo "Unsupported event: $EVENT_NAME" >&2
exit 2 ;;
esac
semver-conformance:
name: SemVer Conformance
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 1
# The C# side (SemVerConformanceTests) runs in the Test job; this asserts the
# release-manifest generator's precedence key agrees with the same shared fixture,
# so the two implementations can't silently drift.
- name: "Generator precedence key matches the shared fixture"
run: python3 feeds/scripts/semver_key.py --check feeds/scripts/semver-order.txt
post-merge-verify:
# An admin can bypass review and push straight to dev. The PR gate does not
# run for such a commit, so confirm the merged tree with a build and the
# unit tests on ubuntu. The PR run remains the full cross-platform gate.
name: Post-merge Verify
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
lfs: true
fetch-depth: 0
- name: "Install .NET SDK"
uses: actions/setup-dotnet@v6
with:
global-json-file: "./global.json"
- name: "Restore .NET tools"
run: dotnet tool restore
- name: "Validate release notes parser"
shell: pwsh
run: ./scripts/smoke/release-notes-smoke.ps1
- name: "Update release notes"
shell: pwsh
run: ./build.ps1
- name: "dotnet build"
run: dotnet build -c Release
- name: "dotnet test"
shell: bash
run: dotnet test -c Release --no-build --filter "Category!=NativeShell" --blame-hang-timeout 300s --blame-hang-dump-type full --results-directory ./TestResults