Skip to content

Commit 20dee29

Browse files
hyperpolymathclaude
andcommitted
feat(chapel-ci): always-on aggregator gate (chapel-ci-gate)
The six chapel-ci jobs are path-filtered (chapel/**, Cargo files, etc.) so they don't run on every PR. Path-filtered required status checks leave "expected but not reported" on unrelated PRs, blocking them indefinitely once the gates are in the Base ruleset. Replace that with a single always-on aggregator job (chapel-ci-gate): * Drop path filter from `on:` so the workflow triggers on every PR. * New `detect-relevant-changes` job inspects the diff and outputs `relevant=true|false` based on the same path list that used to live in the on-trigger. * Each of the six gates gains `needs: detect-relevant-changes` + `if: needs.detect-relevant-changes.outputs.relevant == 'true'`. * `chapel-ci-gate` job needs all six + detect, `if: always()`. Reports SUCCESS immediately if no chapel-relevant paths changed; otherwise inspects each dependent job's result and fails on any non-success. Next step (separate, post-merge): update the Base ruleset to require ONLY `chapel-ci-gate` instead of the six individual jobs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4b96c26 commit 20dee29

1 file changed

Lines changed: 120 additions & 42 deletions

File tree

.github/workflows/chapel-ci.yml

Lines changed: 120 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,39 @@
44
#
55
# The Rust binary stands alone (USB-stick-portable, single-machine). chapel/ is
66
# a detachable multi-machine harness on top. This workflow exercises the harness
7-
# and the Rust↔Chapel contract surface. Path triggers are scoped so a pure-Rust
8-
# PR that doesn't touch chapel/ or src/main.rs leaves these jobs unrun, and
9-
# removing chapel/ entirely leaves the Rust CI path (rust-ci.yml) green.
7+
# and the Rust↔Chapel contract surface.
108
#
11-
# Six strict jobs (no continue-on-error):
9+
# **Why an aggregator gate?** The 6 gate jobs are path-filtered (they only do
10+
# real work when chapel/** or the Rust contract files change). But path-filtered
11+
# workflows that don't trigger leave required status checks "expected but not
12+
# reported" — which blocks unrelated PRs from merging when the gates are in
13+
# the Base ruleset. Solution: a single `chapel-ci-gate` job that ALWAYS runs
14+
# and aggregates. The ruleset requires only the gate. The gate reports:
15+
# - SUCCESS immediately if no chapel-relevant paths changed.
16+
# - SUCCESS if all 6 underlying jobs succeeded on a relevant change.
17+
# - FAILURE if any underlying job failed.
18+
#
19+
# Six strict jobs (no continue-on-error anywhere):
1220
# 1. chapel-parse-check — chpl --parse-only on every module
13-
# 2. chapel-build — just chapel-build-ci (no toolbox)
21+
# 2. chapel-build — chpl build of mass-panic + smoke (no toolbox)
1422
# 3. chapel-smoke — chapel/smoke/two_repo_smoke (Chapel data flow)
15-
# 4. chapel-e2e — mass-panic end-to-end (-nl 1) on a synthetic
16-
# 2-repo manifest. True -nl 2 requires CHPL_COMM=gasnet
17-
# which the stock .deb doesn't ship; tracked for Wave 2.
18-
# 5. chapel-cli-contract — assert panic-attack describe-contract matches fixture
19-
# 6. chapel-rust-diff — rayon assemblyline vs Chapel single-locale aggregates
23+
# 4. chapel-e2e — mass-panic -nl 1 on a synthetic 2-repo manifest
24+
# True -nl 2 requires CHPL_COMM=gasnet which the
25+
# stock .deb doesn't ship; tracked for Wave 2.
26+
# 5. chapel-cli-contract — panic-attack describe-contract vs expected fixture
27+
# 6. chapel-rust-diff — rayon assemblyline vs Chapel single-locale parity
28+
#
29+
# Plus the always-on aggregator: `chapel-ci-gate`.
2030
#
2131
# Wave 2 hardening tracker: SHA-pin the Chapel 2.8.0 .deb download. Today the
22-
# workflow trusts the HTTPS endpoint at chapel-lang/chapel releases. Acceptable
23-
# for the harness scaffold; harden before promoting Chapel to a production gate.
32+
# workflow trusts the HTTPS endpoint at chapel-lang/chapel releases.
2433

2534
name: chapel-ci
2635

2736
on:
2837
push:
2938
branches: [main]
30-
paths:
31-
- 'chapel/**'
32-
- 'Justfile'
33-
- '.github/workflows/chapel-ci.yml'
34-
- 'src/main.rs'
35-
- 'src/types.rs'
36-
- 'Cargo.toml'
37-
- 'Cargo.lock'
3839
pull_request:
39-
paths:
40-
- 'chapel/**'
41-
- 'Justfile'
42-
- '.github/workflows/chapel-ci.yml'
43-
- 'src/main.rs'
44-
- 'src/types.rs'
45-
- 'Cargo.toml'
46-
- 'Cargo.lock'
4740

4841
permissions:
4942
contents: read
@@ -57,16 +50,48 @@ env:
5750
CHAPEL_DEB_URL: "https://github.com/chapel-lang/chapel/releases/download/2.8.0/chapel-2.8.0-1.ubuntu22.amd64.deb"
5851

5952
jobs:
53+
detect-relevant-changes:
54+
name: detect-relevant-changes
55+
runs-on: ubuntu-22.04
56+
outputs:
57+
relevant: ${{ steps.f.outputs.relevant }}
58+
steps:
59+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
60+
with:
61+
fetch-depth: 2
62+
- id: f
63+
name: Detect chapel-relevant paths
64+
run: |
65+
set -euo pipefail
66+
BASE="${{ github.event.pull_request.base.sha || github.event.before }}"
67+
if [[ -z "$BASE" || "$BASE" == "0000000000000000000000000000000000000000" ]]; then
68+
# First push or detached state — be safe and run the full gate.
69+
echo "relevant=true" >> "$GITHUB_OUTPUT"
70+
echo "detect: BASE missing/zero — treating as relevant"
71+
exit 0
72+
fi
73+
git fetch origin "$BASE" --depth=1 2>/dev/null || true
74+
CHANGED=$(git diff --name-only "$BASE" HEAD || true)
75+
echo "Changed files:"
76+
echo "$CHANGED"
77+
if echo "$CHANGED" | grep -qE '^(chapel/|Justfile$|\.github/workflows/chapel-ci\.yml$|src/main\.rs$|src/types\.rs$|Cargo\.toml$|Cargo\.lock$)'; then
78+
echo "relevant=true" >> "$GITHUB_OUTPUT"
79+
echo "detect: chapel-relevant paths changed — running gates"
80+
else
81+
echo "relevant=false" >> "$GITHUB_OUTPUT"
82+
echo "detect: no chapel-relevant paths — gates skipped via if-guard"
83+
fi
84+
6085
chapel-parse-check:
6186
name: chapel-parse-check
87+
needs: detect-relevant-changes
88+
if: needs.detect-relevant-changes.outputs.relevant == 'true'
6289
runs-on: ubuntu-22.04
6390
steps:
6491
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
6592
- name: Install Chapel ${{ env.CHAPEL_VERSION }}
6693
run: |
6794
set -euo pipefail
68-
# Chapel .deb's apt dep: libunwind-dev. Install first so dpkg
69-
# doesn't bail out on unmet dependencies.
7095
sudo apt-get update -qq
7196
sudo apt-get install -y libunwind-dev
7297
curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}"
@@ -81,15 +106,14 @@ jobs:
81106
82107
chapel-build:
83108
name: chapel-build
84-
needs: chapel-parse-check
109+
needs: [detect-relevant-changes, chapel-parse-check]
110+
if: needs.detect-relevant-changes.outputs.relevant == 'true'
85111
runs-on: ubuntu-22.04
86112
steps:
87113
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
88114
- name: Install Chapel ${{ env.CHAPEL_VERSION }}
89115
run: |
90116
set -euo pipefail
91-
# Chapel .deb's apt dep: libunwind-dev. Install first so dpkg
92-
# doesn't bail out on unmet dependencies.
93117
sudo apt-get update -qq
94118
sudo apt-get install -y libunwind-dev
95119
curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}"
@@ -112,15 +136,14 @@ jobs:
112136

113137
chapel-smoke:
114138
name: chapel-smoke
115-
needs: chapel-build
139+
needs: [detect-relevant-changes, chapel-build]
140+
if: needs.detect-relevant-changes.outputs.relevant == 'true'
116141
runs-on: ubuntu-22.04
117142
steps:
118143
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
119144
- name: Install Chapel ${{ env.CHAPEL_VERSION }}
120145
run: |
121146
set -euo pipefail
122-
# Chapel .deb's apt dep: libunwind-dev. Install first so dpkg
123-
# doesn't bail out on unmet dependencies.
124147
sudo apt-get update -qq
125148
sudo apt-get install -y libunwind-dev
126149
curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}"
@@ -137,15 +160,14 @@ jobs:
137160

138161
chapel-e2e:
139162
name: chapel-e2e
140-
needs: chapel-build
163+
needs: [detect-relevant-changes, chapel-build]
164+
if: needs.detect-relevant-changes.outputs.relevant == 'true'
141165
runs-on: ubuntu-22.04
142166
steps:
143167
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
144168
- name: Install Chapel ${{ env.CHAPEL_VERSION }}
145169
run: |
146170
set -euo pipefail
147-
# Chapel .deb's apt dep: libunwind-dev. Install first so dpkg
148-
# doesn't bail out on unmet dependencies.
149171
sudo apt-get update -qq
150172
sudo apt-get install -y libunwind-dev
151173
curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}"
@@ -174,6 +196,8 @@ jobs:
174196
175197
chapel-cli-contract:
176198
name: chapel-cli-contract
199+
needs: detect-relevant-changes
200+
if: needs.detect-relevant-changes.outputs.relevant == 'true'
177201
runs-on: ubuntu-22.04
178202
steps:
179203
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -192,7 +216,8 @@ jobs:
192216

193217
chapel-rust-diff:
194218
name: chapel-rust-diff
195-
needs: chapel-build
219+
needs: [detect-relevant-changes, chapel-build]
220+
if: needs.detect-relevant-changes.outputs.relevant == 'true'
196221
runs-on: ubuntu-22.04
197222
steps:
198223
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -207,8 +232,6 @@ jobs:
207232
- name: Install Chapel ${{ env.CHAPEL_VERSION }}
208233
run: |
209234
set -euo pipefail
210-
# Chapel .deb's apt dep: libunwind-dev. Install first so dpkg
211-
# doesn't bail out on unmet dependencies.
212235
sudo apt-get update -qq
213236
sudo apt-get install -y libunwind-dev
214237
curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}"
@@ -224,3 +247,58 @@ jobs:
224247
run: cargo build --release --locked
225248
- name: rayon vs Chapel single-locale aggregate parity
226249
run: ./chapel/tests/rayon_vs_chapel_diff.sh
250+
251+
# Always-on aggregator. This is the ONLY job listed in the Base ruleset's
252+
# required_status_checks rule. If detect-relevant-changes determined nothing
253+
# in this PR touches Chapel-relevant paths, the gate passes immediately
254+
# (the six per-task jobs above skip via their `if:` guard). If a relevant
255+
# change is present, the gate inspects each job's result and only passes
256+
# when ALL six succeeded.
257+
chapel-ci-gate:
258+
name: chapel-ci-gate
259+
needs:
260+
- detect-relevant-changes
261+
- chapel-parse-check
262+
- chapel-build
263+
- chapel-smoke
264+
- chapel-e2e
265+
- chapel-cli-contract
266+
- chapel-rust-diff
267+
if: always()
268+
runs-on: ubuntu-22.04
269+
steps:
270+
- name: Aggregate chapel-ci results
271+
env:
272+
RELEVANT: ${{ needs.detect-relevant-changes.outputs.relevant }}
273+
R_PARSE: ${{ needs.chapel-parse-check.result }}
274+
R_BUILD: ${{ needs.chapel-build.result }}
275+
R_SMOKE: ${{ needs.chapel-smoke.result }}
276+
R_E2E: ${{ needs.chapel-e2e.result }}
277+
R_CLI: ${{ needs.chapel-cli-contract.result }}
278+
R_DIFF: ${{ needs.chapel-rust-diff.result }}
279+
run: |
280+
set -euo pipefail
281+
echo "detect-relevant-changes.outputs.relevant=$RELEVANT"
282+
printf 'parse-check=%s\nbuild=%s\nsmoke=%s\ne2e=%s\ncli-contract=%s\nrust-diff=%s\n' \
283+
"$R_PARSE" "$R_BUILD" "$R_SMOKE" "$R_E2E" "$R_CLI" "$R_DIFF"
284+
if [[ "$RELEVANT" != "true" ]]; then
285+
echo "chapel-ci-gate: SKIP (no chapel-relevant paths changed) → PASS"
286+
exit 0
287+
fi
288+
# If detect itself failed, we never confirmed relevance — fail safe.
289+
if [[ "${{ needs.detect-relevant-changes.result }}" != "success" ]]; then
290+
echo "chapel-ci-gate: detect-relevant-changes did not succeed → FAIL"
291+
exit 1
292+
fi
293+
fail=0
294+
for r in "$R_PARSE" "$R_BUILD" "$R_SMOKE" "$R_E2E" "$R_CLI" "$R_DIFF"; do
295+
case "$r" in
296+
success) ;;
297+
*) fail=$((fail + 1)) ;;
298+
esac
299+
done
300+
if [[ $fail -gt 0 ]]; then
301+
echo "chapel-ci-gate: $fail dependent job(s) did not succeed → FAIL"
302+
exit 1
303+
fi
304+
echo "chapel-ci-gate: all six gates green → PASS"

0 commit comments

Comments
 (0)