Skip to content

Commit b993267

Browse files
ian-floresCopilot
andauthored
ci: make smoke gates fail when in-scope suites are skipped (#530)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 7663a3a commit b993267

5 files changed

Lines changed: 79 additions & 29 deletions

File tree

.github/workflows/connect-smoke.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,16 @@ jobs:
312312
echo "Version-matrix job failed; cannot confirm smoke scope"
313313
exit 1
314314
fi
315+
# If Connect smoke was in scope for this PR (paths matched) it MUST
316+
# have succeeded — a skip here means it was gated out (missing license,
317+
# excluded actor, or self-skip) rather than legitimately not relevant,
318+
# so treat "in scope but not success" as a hard gate failure.
319+
if [ "${{ needs.changes.outputs.relevant }}" = "true" ] && [ "${{ needs.connect-smoke.result }}" != "success" ]; then
320+
echo "Connect smoke was in scope for this PR but did not succeed (result=${{ needs.connect-smoke.result }})"
321+
exit 1
322+
fi
315323
if [ "${{ needs.connect-smoke.result }}" = "failure" ] || [ "${{ needs.connect-smoke.result }}" = "cancelled" ]; then
316324
echo "Connect smoke tests failed or were cancelled"
317325
exit 1
318326
fi
319-
echo "Connect smoke tests passed or were skipped"
327+
echo "Connect smoke tests passed or were legitimately out of scope"

.github/workflows/mock-idp-e2e.yml

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,6 @@ on:
3030
- 'Dockerfile'
3131
- '.github/workflows/mock-idp-e2e.yml'
3232
pull_request:
33-
paths:
34-
- 'src/vip/auth.py'
35-
- 'src/vip/idp.py'
36-
- 'src/vip/totp.py'
37-
- 'src/vip/cli.py'
38-
- 'src/vip_tests/conftest.py'
39-
- 'src/vip_tests/connect/test_auth.py'
40-
- 'src/vip_tests/connect/test_auth.feature'
41-
- 'src/vip_tests/workbench/test_auth.py'
42-
- 'src/vip_tests/workbench/test_auth.feature'
43-
- 'docker/keycloak/**'
44-
- 'docker/connect/**'
45-
- 'docker/workbench/**'
46-
- 'docker/tls/**'
47-
- 'compose.mock-idp.yml'
48-
- 'Dockerfile'
49-
- '.github/workflows/mock-idp-e2e.yml'
5033
schedule:
5134
- cron: "30 5 * * *" # daily at 5:30am UTC
5235
workflow_dispatch:
@@ -59,6 +42,39 @@ concurrency:
5942
cancel-in-progress: true
6043

6144
jobs:
45+
changes:
46+
runs-on: ubuntu-latest
47+
permissions:
48+
contents: read
49+
pull-requests: read
50+
outputs:
51+
relevant: ${{ steps.filter.outputs.relevant }}
52+
steps:
53+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
54+
with:
55+
persist-credentials: false
56+
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
57+
id: filter
58+
with:
59+
filters: |
60+
relevant:
61+
- 'src/vip/auth.py'
62+
- 'src/vip/idp.py'
63+
- 'src/vip/totp.py'
64+
- 'src/vip/cli.py'
65+
- 'src/vip_tests/conftest.py'
66+
- 'src/vip_tests/connect/test_auth.py'
67+
- 'src/vip_tests/connect/test_auth.feature'
68+
- 'src/vip_tests/workbench/test_auth.py'
69+
- 'src/vip_tests/workbench/test_auth.feature'
70+
- 'docker/keycloak/**'
71+
- 'docker/connect/**'
72+
- 'docker/workbench/**'
73+
- 'docker/tls/**'
74+
- 'compose.mock-idp.yml'
75+
- 'Dockerfile'
76+
- '.github/workflows/mock-idp-e2e.yml'
77+
6278
# Emit the Workbench version list for the E2E job. PR/push runs test only the
6379
# latest release (single required check, unchanged). Scheduled/manual runs fan
6480
# out across the Workbench support window (current + 2 back) — same list as
@@ -80,7 +96,11 @@ jobs:
8096
fi
8197
8298
mock-idp-e2e:
83-
needs: [set-matrix]
99+
needs: [set-matrix, changes]
100+
if: >-
101+
needs.changes.outputs.relevant == 'true'
102+
|| github.event_name == 'schedule'
103+
|| github.event_name == 'workflow_dispatch'
84104
# Keep the original job name on the release (PR/push) leg so the PR-time
85105
# check name is unchanged; suffix only the pinned scheduled/manual legs.
86106
# `!=` first so the release leg yields '' rather than tripping the falsy-`||` trap.
@@ -227,19 +247,29 @@ jobs:
227247
status:
228248
name: Mock-IdP E2E Status
229249
if: always()
230-
needs: [set-matrix, mock-idp-e2e]
250+
needs: [changes, set-matrix, mock-idp-e2e]
231251
runs-on: ubuntu-latest
232252
steps:
233253
- name: Check result
234254
run: |
255+
if [ "${{ needs.changes.result }}" = "failure" ] || [ "${{ needs.changes.result }}" = "cancelled" ]; then
256+
echo "Change-detection job failed; cannot confirm E2E scope"
257+
exit 1
258+
fi
235259
# A failed set-matrix skips (not fails) mock-idp-e2e, which the
236260
# check below would otherwise read as a clean skip.
237261
if [ "${{ needs.set-matrix.result }}" = "failure" ] || [ "${{ needs.set-matrix.result }}" = "cancelled" ]; then
238262
echo "Version-matrix job failed; cannot confirm E2E scope"
239263
exit 1
240264
fi
265+
# If auth paths changed, the E2E suite was in scope and MUST have
266+
# succeeded — a skip means it was gated out, not legitimately absent.
267+
if [ "${{ needs.changes.outputs.relevant }}" = "true" ] && [ "${{ needs.mock-idp-e2e.result }}" != "success" ]; then
268+
echo "Mock-IdP E2E was in scope but did not succeed (result=${{ needs.mock-idp-e2e.result }})"
269+
exit 1
270+
fi
241271
if [ "${{ needs.mock-idp-e2e.result }}" = "failure" ] || [ "${{ needs.mock-idp-e2e.result }}" = "cancelled" ]; then
242272
echo "Mock-IdP E2E tests failed or were cancelled"
243273
exit 1
244274
fi
245-
echo "Mock-IdP E2E tests passed or were skipped"
275+
echo "Mock-IdP E2E tests passed or were legitimately out of scope"

.github/workflows/packagemanager-smoke.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,19 @@ jobs:
9494
with:
9595
persist-credentials: false
9696

97-
# Skip the entire job if the license secret is not configured
97+
# The job only runs when PM smoke is in scope (changed paths) or on a
98+
# scheduled/manual run -- all cases where the suite is meant to execute.
99+
# A missing license secret therefore means the required gate cannot be
100+
# satisfied, so fail loudly instead of silently skipping every test step
101+
# (which would otherwise report a green required check with zero coverage).
98102
- name: Check for RSPM_LICENSE
99103
id: license
100104
run: |
101105
if [ -z "${{ secrets.RSPM_LICENSE }}" ]; then
102-
echo "RSPM_LICENSE secret not configured — skipping PM smoke tests"
103-
echo "available=false" >> "$GITHUB_OUTPUT"
104-
else
105-
echo "available=true" >> "$GITHUB_OUTPUT"
106+
echo "::error::RSPM_LICENSE secret is not configured; Package Manager smoke cannot run. Set the RSPM_LICENSE repository secret."
107+
exit 1
106108
fi
109+
echo "available=true" >> "$GITHUB_OUTPUT"
107110
108111
# Start Package Manager in Docker
109112
- name: Start Package Manager
@@ -358,8 +361,12 @@ jobs:
358361
echo "Version-matrix job failed; cannot confirm smoke scope"
359362
exit 1
360363
fi
364+
if [ "${{ needs.changes.outputs.relevant }}" = "true" ] && [ "${{ needs.packagemanager-smoke.result }}" != "success" ]; then
365+
echo "Package Manager smoke was in scope but did not succeed (result=${{ needs.packagemanager-smoke.result }}); is RSPM_LICENSE configured?"
366+
exit 1
367+
fi
361368
if [ "${{ needs.packagemanager-smoke.result }}" = "failure" ] || [ "${{ needs.packagemanager-smoke.result }}" = "cancelled" ]; then
362369
echo "Package Manager smoke tests failed or were cancelled"
363370
exit 1
364371
fi
365-
echo "Package Manager smoke tests passed or were skipped"
372+
echo "Package Manager smoke tests passed or were legitimately out of scope"

.github/workflows/workbench-smoke.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,12 @@ jobs:
329329
echo "Version-matrix job failed; cannot confirm smoke scope"
330330
exit 1
331331
fi
332+
if [ "${{ needs.changes.outputs.relevant }}" = "true" ] && [ "${{ needs.workbench-smoke.result }}" != "success" ]; then
333+
echo "Workbench smoke was in scope for this PR but did not succeed (result=${{ needs.workbench-smoke.result }})"
334+
exit 1
335+
fi
332336
if [ "${{ needs.workbench-smoke.result }}" = "failure" ] || [ "${{ needs.workbench-smoke.result }}" = "cancelled" ]; then
333337
echo "Workbench smoke tests failed or were cancelled"
334338
exit 1
335339
fi
336-
echo "Workbench smoke tests passed or were skipped"
340+
echo "Workbench smoke tests passed or were legitimately out of scope"

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ The report lives in `report/` and reads `report/results.json` (written by pytest
229229
- **`ci.yml`** -- on every PR/push: ruff lint/format (pinned to 0.15.0), mypy type-check, zizmor actions-lint, a runtime dependency audit, and selftests (Ubuntu + macOS, Python 3.10 and 3.12). A `changes` path-filter gates the expensive jobs, while `Lint & Format` and `Selftests Status` always run as required checks. Uses uv cache.
230230
- **`preview.yml`** -- runs selftests, renders Quarto report, publishes PR preview to gh-pages via `rossjrw/pr-preview-action@v1`. Uses uv and Quarto caches.
231231
- **`pr-title.yml`** -- validates PR titles follow conventional commit format. Squash merges use the PR title as the commit message.
232-
- **Smoke workflows** (`connect-smoke.yml`, `workbench-smoke.yml`, `packagemanager-smoke.yml`, `mock-idp-e2e.yml`) -- run the product suites against real containers. On PR/push each tests a single latest version (change-gated, no new required checks); on `schedule` (nightly, staggered hourly) and `workflow_dispatch` a `set-matrix` job fans each out across the product version support window (current + 2 back). Bump the pinned tags in each workflow's `set-matrix` step when a new product release ships.
232+
- **Smoke workflows** (`connect-smoke.yml`, `workbench-smoke.yml`, `packagemanager-smoke.yml`, `mock-idp-e2e.yml`) -- run the product suites against real containers. On PR/push each tests a single latest version (change-gated via a `changes` paths-filter job); on `schedule` (nightly, staggered hourly) and `workflow_dispatch` a `set-matrix` job fans each out across the product version support window (current + 2 back). Bump the pinned tags in each workflow's `set-matrix` step when a new product release ships. Each workflow's `*-status` aggregation job is scope-aware: it passes when the suite was legitimately out of scope (the PR's paths didn't match) but **fails** when the suite was in scope (`changes.relevant == 'true'`) yet did not succeed -- so a path-gated skip, an excluded actor, or a missing license secret can no longer report a green required check without the suite having run. The Connect, Workbench, and Package Manager `*-status` jobs are required merge checks.
233+
`mock-idp-e2e.yml` is structured the same way so `Mock-IdP E2E Status` can be promoted to a required check via a separate admin action.
233234
- **`add-to-team-project.yml`** -- when a `team: connect`, `team: workbench`, or `team: package manager` label is added to an issue, adds it to that product team's org-level GitHub project board. Ported from rstudio/helm. Requires the cross-org `POSIT_PLATFORM_CLIENT_ID`/`POSIT_PLATFORM_PEM` app secrets.
234235
- **`weekly-summary.yml`** -- Mondays (and on demand via `workflow_dispatch`) gathers the week's merged PRs, has Claude pick the highlights via Bedrock, and posts a Slack summary; `pull_request` runs are a dry run that builds and logs the payload without posting. Requires the `SLACK_WEBHOOK_VIP_WEEKLY_SUMMARY` secret and permission to assume the `claude-code-gha` AWS role.
235236

0 commit comments

Comments
 (0)