Skip to content

✅ test(ci): correct the env bounding claim and close… #842

✅ test(ci): correct the env bounding claim and close…

✅ test(ci): correct the env bounding claim and close… #842

Workflow file for this run

# Tier 0 — mandatory Kind smoke on every non-docs PR and push to main.
# Required check name for branch protection: kind-smoke
# See docs/development/coding-standards.md and docs/development/testing.md.
#
# CI-DOCSGATE-01: `pull_request` is deliberately unfiltered. `kind-smoke` is a required context
# in the protect-main ruleset, and a workflow skipped by `paths-ignore` never reports its
# contexts at all — not even as skipped — so every docs-only PR sat at BLOCKED until someone
# bypassed the ruleset. The `changes` job below makes the docs-only decision instead, the real
# cluster work lives in `kind-smoke-run`, and the `kind-smoke` job is a report that reaches a
# conclusion on every PR without burning a kind cluster on a docs change. `paths-ignore` stays
# on `push`, where no required context is at stake. Locked by hack/test/ci_docs_gate_test.sh.
name: E2E smoke
on:
push:
branches: [main]
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
- "CHANGELOG.md"
- "CONTRIBUTING.md"
- "LICENSE"
- ".github/ISSUE_TEMPLATE/**"
pull_request:
permissions:
contents: read
concurrency:
group: e2e-smoke-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
# HY-06: never cancel in-flight runs on main (would leave CI-UNKNOWN); PR runs still supersede.
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
# CI-DOCSGATE-01: the per-PR verdict `kind-smoke-run` hangs off. `code=true` means the change
# touches something other than documentation. Fail-safe in one direction only: anything this
# job cannot classify (a non-PR event, a missing or unreachable SHA, an empty diff) yields
# `true`, so the worst case is a wasted kind run rather than a required context reported green
# without having run. This step body is byte-identical to the one in
# .github/workflows/ci.yaml, and hack/test/ci_docs_gate_test.sh compares them: two copies that
# drift are two different answers to "is this docs-only?", and `test` and `kind-smoke` would
# then disagree about the same PR.
changes:
name: changes (e2e-smoke)
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
# `fetch-depth: 0` is required, not defensive: BASE_SHA is the base branch tip at event
# time and can be an arbitrary ancestor, and a shallow clone has neither it nor a
# merge base, so `git diff` would fail. That fails CLOSED (code=true), which is safe but
# would silently run full CI on every PR and destroy the point of this job. `filter:
# blob:none` keeps that full commit history while skipping the file contents -- a
# `--name-only --no-renames` diff compares tree entries by object id and never reads a
# blob -- so this costs a blobless clone, not a full one.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
filter: blob:none
persist-credentials: false
- id: filter
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
# The documentation path set. Mirrors the `paths-ignore` list kept on `push` above.
docs_only_path() {
case "$1" in
docs/* | mkdocs.yml | README.md | CHANGELOG.md | CONTRIBUTING.md | LICENSE | .github/ISSUE_TEMPLATE/*)
return 0
;;
*)
return 1
;;
esac
}
emit() {
echo "code=$1" >>"${GITHUB_OUTPUT}"
echo "changed-code=$1"
}
if [ "${EVENT_NAME}" != "pull_request" ]; then
emit true
exit 0
fi
if [ -z "${BASE_SHA}" ] || [ -z "${HEAD_SHA}" ]; then
emit true
exit 0
fi
# `--no-renames` is load-bearing, not tidiness. Rename detection is ON by default
# (diff.renames since git 2.9) and `git diff --name-only` then prints ONLY the
# DESTINATION of a detected rename. Without it, `git mv internal/x.go docs/x.go`
# reports the single path `docs/x.go`, this job answers "documentation-only", and
# both reporters report green for a change that deleted a Go file from the tree.
# With `--no-renames` the same diff reports both paths, and the loop below sees the
# source. Locked by hack/test/ci_docs_gate_test.sh (rename-into-docs case + mutant).
# `core.quotepath` is left at its default, so a path containing non-ASCII, a newline or
# a tab comes back C-quoted and matches no documentation pattern below -- that change
# gets full CI. Wasteful, but it is the safe direction, so it is left alone.
if ! changed="$(git diff --name-only --no-renames "${BASE_SHA}" "${HEAD_SHA}" 2>/dev/null)"; then
emit true
exit 0
fi
if [ -z "${changed}" ]; then
emit true
exit 0
fi
while IFS= read -r file; do
[ -n "${file}" ] || continue
if ! docs_only_path "${file}"; then
emit true
exit 0
fi
done <<<"${changed}"
emit false
# CI-DOCSGATE-01: the REAL Tier-0 smoke. Deliberately NOT named `kind-smoke` — the required
# context of that name is produced by the reporting job at the bottom of this file, which
# reports on every PR. A job that legitimately skips must never carry a required context name.
kind-smoke-run:
name: kind-smoke-run
needs: [changes]
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/kind-e2e-setup
with:
scenario-script: hack/kind/e2e/smoke.sh
wait-timeout: 300s
# L4 for the pipeline CLI (ADR-0801, P-008). The CLI is standalone — it collects via kubeconfig
# without the operator — so it runs as its own isolated-cluster scenario rather than bolting onto
# the operator smoke. run-mode repo_root sets REPO_ROOT so the scenario can `task build:cli`.
pipeline-cli-smoke:
name: pipeline-cli-smoke
needs: [changes]
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/kind-e2e-setup
with:
scenario-script: hack/kind/e2e/pipeline-cli-smoke.sh
run-mode: repo_root
wait-timeout: 300s
# DEMO-03 — canonical Git-only hero demo path (Forgejo in kind).
# Separate from required Tier-0 kind-smoke (branch protection lists kind-smoke only).
# Uses a noop setup so kind-e2e-setup installs kind/helm without creating kollect-e2e;
# smoke.sh owns kollect-hero (up → assert → down).
hero-demo-smoke:
name: hero-demo-smoke
needs: [changes]
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Hero smoke contract (no kind)
run: bash hack/test/demo_03_hero_smoke_test.sh
- uses: ./.github/actions/kind-e2e-setup
with:
setup-script: hack/demo/hero/ci-noop-setup.sh
scenario-script: hack/demo/hero/smoke.sh
wait-timeout: 300s
# CI-DOCSGATE-01: the required context `kind-smoke` (protect-main lists it by exact string).
#
# A REPORT and nothing else, so the context reaches a conclusion on EVERY pull request —
# including a documentation-only one, where `kind-smoke-run` is skipped and no kind cluster is
# created. `if: always()` is what makes that work: without it a skipped worker would skip this
# job too and the context would vanish from the PR, which is the defect this lane removed.
#
# It cannot stand in for the real smoke. Whenever the change touches anything outside the
# documentation path set it fails unless `kind-smoke-run` actually SUCCEEDED — a skipped,
# failed or cancelled worker reds the context. The comparison is `!= "false"`, not
# `== "true"`, so an empty or corrupted verdict from `changes` also demands a real success.
# hack/test/ci_docs_gate_test.sh executes this exact body against that truth table.
kind-smoke:
name: kind-smoke
needs: [changes, kind-smoke-run]
if: always()
runs-on: ubuntu-latest
steps:
- name: Report the required kind-smoke context
shell: bash
env:
CODE: ${{ needs.changes.outputs.code }}
RESULT: ${{ needs.kind-smoke-run.result }}
run: |
set -euo pipefail
if [ "${CODE}" != "false" ]; then
if [ "${RESULT}" != "success" ]; then
echo "::error::the kind-smoke-run job must succeed on a change that touches non-documentation paths (changes.code='${CODE}', kind-smoke-run='${RESULT}')"
exit 1
fi
echo "code change: kind-smoke-run succeeded"
exit 0
fi
case "${RESULT}" in
failure | cancelled)
echo "::error::documentation-only change, but kind-smoke-run reported '${RESULT}'"
exit 1
;;
esac
echo "documentation-only change: kind-smoke-run '${RESULT}', required context reported without creating a kind cluster"