Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 54 additions & 26 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
# CI for the organization's community-health repository.
# DO NOT EDIT MANUALLY.
# This workflow is generic — no project-specific content — and is destined to
# become part of the canonical baseline limen distributes.
#
# The canonical shape — GitHub's own checkout pinned by SHA, aqua for the
# pinned toolchain, and `just` recipes identical to the ones a laptop runs —
# but on a single linux leg. ci.yaml is seeded once and is the project's own
# from there (see book/mandatory-files.md), and this repository ships no code:
# nothing here is platform-dependent, so the five-runner matrix the code
# repositories need would buy nothing but minutes.
# Design: minimal GitHub glue around the same tooling every developer runs
# locally. The only marketplace action is GitHub's own checkout, pinned by
# commit SHA (a tag can be moved to malicious code; a SHA cannot). Everything
# else is pinned, checksum-verified shell: aqua installs the repo's tools at
# the versions aqua.yaml pins, and `just` runs the exact recipes a laptop
# runs — CI green means the same thing as local green, by construction.
# Deliberately absent: runner egress filtering — the available options are
# third-party actions, which this workflow avoids on principle.
name: ci

on:
push:
branches: [main]
pull_request:

# No default token permissions: each job states what it needs.
# No default token permissions: each job states what it needs. This workflow
# only ever reads the repository — it cannot write code, releases, or
# packages even if a step is compromised.
permissions: {}

# A superseded run (new push to the same branch/PR) is cancelled, not raced.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# Every run step is bash on every runner: without this, windows defaults to
# PowerShell, which never sees the git-bash environment setup-aqua prepares
# (the whole matrix is designed to run the recipes under git-bash). Explicit
# bash also means `-eo pipefail` everywhere, which the implicit linux/macos
# default lacks.
defaults:
run:
shell: bash
Expand All @@ -30,11 +41,24 @@ jobs:
strategy:
fail-fast: false
matrix:
# Single leg, but kept as a matrix so the check-run name stays
# `verify (ubuntu-24.04)` — the shape the canonical limen:main ruleset
# names in its required status checks.
# Pinned images, not -latest: a runner bump is a reviewed change.
# macOS is not redundant: its /bin/bash is 3.2, the portability floor
# the shared recipes target. arm64 (ubuntu-24.04-arm, windows-11-arm)
# and windows cover the rest of the supported release matrix — both
# windows legs run the recipes under git-bash (native arm64 tools
# where upstream ships them, Prism-emulated amd64 via aqua's
# windows_arm_emulation where it does not).
# These names are NOT the ruleset's required contexts — the `gate` job
# below is, precisely so this list can change without touching any
# repository's branch protection.
#
# Trimmed from the canonical matrix to a single linux leg: this is the
# organization's community-health repository and ships no code, so
# nothing here is platform-dependent — the five-runner matrix the code
# repositories need would buy nothing but minutes.
os: [ubuntu-24.04]
runs-on: ${{ matrix.os }}
# One linux leg linting a handful of files: nowhere near the canonical 45.
timeout-minutes: 15
permissions:
contents: read
Expand All @@ -44,44 +68,48 @@ jobs:
# The token is not left behind in .git/config: nothing in this
# workflow talks to GitHub after checkout.
persist-credentials: false
# Full history: `just do lint commits` validates the commit range
# against the PR's base branch, which a shallow clone lacks.
# Full history and refs: `just do lint commits` validates the commit
# range against the PR's base branch, which a shallow clone lacks.
fetch-depth: 0

- name: Install aqua (pinned, checksum-verified)
uses: ./.github/actions/setup-aqua

- name: Install pinned tools
# Not optional: setup-aqua only installs aqua itself and puts its bin
# directory on PATH — that directory is EMPTY until this runs. The
# shims created here are what make `just` (and everything the recipes
# call) resolve at all. Link-only: each tool downloads lazily on first
# use and is verified against the committed aqua-checksums.json then,
# so a job only pays for the tools its recipes actually run.
# Link-only: the shims download each tool lazily on first use (and it
# is verified against the committed aqua-checksums.json then), so a
# job only ever pays for the tools its recipes actually run.
run: aqua install --only-link

- name: Lint
run: just lint

- name: Test
# A no-op today (the Justfile's `test` recipe is empty) — kept so the
# shape matches the canonical ci.yaml and tests run the day there are any.
run: just test

# The single required status check, identical to the canonical ci.yaml's.
# This repository runs one leg where the code repositories run five — which
# is exactly why the ruleset names this job and not the matrix.
# The ONE required status check (see defaultRequiredChecks in
# internal/github/audit.go). Branch protection names contexts as strings, so
# requiring the matrix legs directly would bake this workflow's runner list
# into every repository's ruleset — and a project whose matrix differs then
# waits forever on checks that never report. This job collapses the matrix,
# whatever its shape, to a single stable name: change the legs above freely,
# the ruleset never moves.
gate:
needs: [verify]
# always(), with the result asserted: without it a failed dependency SKIPS
# this job, and a skipped required check does not block a merge.
# always(), and the result asserted explicitly. Without always() a failed
# or cancelled dependency SKIPS this job instead of failing it, and a
# skipped required check does not block a merge — branch protection that
# quietly stops protecting. `needs.verify.result` is success only when
# every matrix leg succeeded.
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions: {}
steps:
- name: Every verify leg succeeded
env:
# Via env, never interpolated into the script: the shell sees data,
# not something the expression layer can rewrite into code.
RESULT: ${{ needs.verify.result }}
run: |
printf 'verify: %s\n' "$RESULT"
Expand Down
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ test:
# Shell plus the aqua-pinned yq: no unpinned interpreter, and shellcheck lints
# the script like any other we ship.
issue-forms:
@.github/workflows/validate-issue-forms.sh
@.github/scripts/validate-issue-forms.sh
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ formatted, and `do lint links` (lychee) proves the links resolve — but neither
that a well-formed YAML file is a *valid issue form*. GitHub fails that silently: it does
not reject the file, it just refuses to render the form. Since these templates are the
org-wide fallback, a broken one breaks issue creation in every repository that has none of
its own. `.github/workflows/validate-issue-forms.sh` checks them against the form schema —
its own. `.github/scripts/validate-issue-forms.sh` checks them against the form schema —
shell plus the aqua-pinned `yq`, so it introduces no interpreter the toolchain does not
already pin, and `do lint shell` lints it like any other script we ship.

Expand Down