Skip to content

Commit 364b075

Browse files
authored
fix(ci): preserve required PR evidence (#1426)
* fix(ci): preserve required PR evidence * style(ci): format workflow policy test * fix(ci): validate cancelled benchmark dependencies * fix(ci): cancel superseded pull request runs * fix(ci): fail closed on cancelled aggregate jobs * fix(benchmarks): normalize cancelled validation inputs * style(ci): format cancellation policy assertions
1 parent 4bf70a3 commit 364b075

3 files changed

Lines changed: 42 additions & 13 deletions

File tree

.github/workflows/benchmarks.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ on:
1414

1515
concurrency:
1616
group: benchmarks-${{ github.workflow }}-${{ github.ref }}
17+
# A newer PR commit supersedes stale evidence; Benchmark Validation still
18+
# runs after dependency cancellation and rejects missing evidence.
1719
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
1820

1921
permissions:
@@ -317,6 +319,9 @@ jobs:
317319

318320
validation:
319321
name: Benchmark Validation
322+
# This required aggregate must run after cancellation so the validator can
323+
# turn cancelled dependencies into a failing check instead of a skipped,
324+
# branch-protection-passing job.
320325
if: ${{ always() }}
321326
needs: [plan, static, contracts, host_validation, inventory, oracle]
322327
runs-on: ubuntu-latest
@@ -370,11 +375,11 @@ jobs:
370375
--execution-sha "${{ github.sha }}" \
371376
--receipt-root "${{ runner.temp }}/benchmark-validation/host" \
372377
--timings "${{ runner.temp }}/benchmark-validation/timing/benchmark-test-durations.json" \
373-
--lane "static:$PLAN_CHECK:$STATIC_RESULT" \
374-
--lane "contracts:$RECORD_SCHEMA_FLAG:$CONTRACTS_RESULT" \
375-
--lane "host-validation:$HOST_VALIDATION_FLAG:$HOST_VALIDATION_RESULT" \
376-
--lane "inventory:$INVENTORY_FLAG:$INVENTORY_RESULT" \
377-
--lane "oracle:$ORACLE_FLAG:$ORACLE_RESULT"
378+
--lane "static:${PLAN_CHECK:-false}:${STATIC_RESULT:-cancelled}" \
379+
--lane "contracts:${RECORD_SCHEMA_FLAG:-false}:${CONTRACTS_RESULT:-cancelled}" \
380+
--lane "host-validation:${HOST_VALIDATION_FLAG:-false}:${HOST_VALIDATION_RESULT:-cancelled}" \
381+
--lane "inventory:${INVENTORY_FLAG:-false}:${INVENTORY_RESULT:-cancelled}" \
382+
--lane "oracle:${ORACLE_FLAG:-false}:${ORACLE_RESULT:-cancelled}"
378383
379384
timings:
380385
name: Publish Benchmark Timings

.github/workflows/ci.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ on:
1212

1313
concurrency:
1414
group: ci-${{ github.workflow }}-${{ github.ref }}
15+
# A newer PR commit supersedes stale evidence; the required aggregate must
16+
# still fail closed if cancellation reaches its dependencies.
1517
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
1618

1719
permissions:
@@ -213,7 +215,7 @@ jobs:
213215

214216
coverage:
215217
name: Coverage
216-
if: ${{ always() && !cancelled() }}
218+
if: ${{ always() }}
217219
needs: [python, boundaries, subprocess_coverage]
218220
runs-on: ubuntu-latest
219221
timeout-minutes: 10
@@ -296,7 +298,7 @@ jobs:
296298

297299
required:
298300
name: required
299-
if: ${{ always() && !cancelled() }}
301+
if: ${{ always() }}
300302
needs: [static, python, boundaries, wheel, coverage, lean]
301303
runs-on: ubuntu-latest
302304
timeout-minutes: 5
@@ -319,7 +321,7 @@ jobs:
319321
320322
python-test:
321323
name: Python Tests
322-
if: ${{ always() && !cancelled() }}
324+
if: ${{ always() }}
323325
needs: [required]
324326
runs-on: ubuntu-latest
325327
timeout-minutes: 5
@@ -331,7 +333,7 @@ jobs:
331333

332334
lean-test:
333335
name: Lean Tests
334-
if: ${{ always() && !cancelled() }}
336+
if: ${{ always() }}
335337
needs: [lean]
336338
runs-on: ubuntu-latest
337339
timeout-minutes: 5
@@ -343,7 +345,7 @@ jobs:
343345

344346
deployment-test:
345347
name: Deployment Tests
346-
if: ${{ always() && !cancelled() }}
348+
if: ${{ always() }}
347349
needs: [static]
348350
runs-on: ubuntu-latest
349351
timeout-minutes: 5

tests/unit/tooling/test_ci_execution_policy.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,14 +286,15 @@ def test_plan_receipt_digests_are_rendered_as_markdown_code() -> None:
286286
assert "Plan receipt: \\\\`$(python" not in workflow
287287

288288

289-
def test_required_ci_gates_fail_closed_without_extending_cancelled_runs() -> None:
289+
def test_required_ci_gates_fail_closed_after_cancellation() -> None:
290290
workflow = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
291291
required = workflow.split(" required:", 1)[1].split(" python-test:", 1)[0]
292292
aggregate_tail = workflow.split(" required:", 1)[1]
293293

294294
assert "treating gate as non-failure" not in workflow
295-
assert "if: ${{ always() }}" not in aggregate_tail
296-
assert aggregate_tail.count("if: ${{ always() && !cancelled() }}") == 4
295+
assert aggregate_tail.count("if: ${{ always() }}") == 4
296+
assert "if: ${{ always() && !cancelled() }}" not in aggregate_tail
297+
assert "if: ${{ always() }}" in required
297298
assert "name: required" in workflow
298299
assert "name: Python Tests" in workflow
299300
assert "name: Lean Tests" in workflow
@@ -307,6 +308,27 @@ def test_required_ci_gates_fail_closed_without_extending_cancelled_runs() -> Non
307308
assert "JACOBIAN_LEAN_REQUIRED" in lean_job
308309

309310

311+
def test_required_pr_workflows_cancel_stale_evidence() -> None:
312+
ci = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
313+
benchmarks = (ROOT / ".github/workflows/benchmarks.yml").read_text(encoding="utf-8")
314+
315+
expected_concurrency = (
316+
"cancel-in-progress: ${{ github.event_name == 'pull_request' }}"
317+
)
318+
assert expected_concurrency in ci
319+
assert expected_concurrency in benchmarks
320+
validation = benchmarks.split(" validation:", 1)[1].split(" timings:", 1)[0]
321+
assert "if: ${{ always() }}" in validation
322+
assert "if: ${{ always() && !cancelled() }}" not in validation
323+
assert (
324+
'--lane "static:${PLAN_CHECK:-false}:${STATIC_RESULT:-cancelled}"' in validation
325+
)
326+
assert (
327+
'--lane "oracle:${ORACLE_FLAG:-false}:${ORACLE_RESULT:-cancelled}"'
328+
in validation
329+
)
330+
331+
310332
def test_subprocess_coverage_is_owned_by_one_focused_worker_lane() -> None:
311333
workflow = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
312334
pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")

0 commit comments

Comments
 (0)