Flips ADR-0001's decision-8 amendment to accepted #28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| gate: | |
| name: Full quality gate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # No service containers. Every function in this package is pure over a | |
| # decoded map, so the suite needs nothing but the BEAM; a database or a | |
| # browser here would be a sign that something moved in that should not | |
| # have. | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Single source of truth for the toolchain: a version duplicated into | |
| # this file drifts the first time mise.toml moves. mise itself is not | |
| # used to provision CI - it would build Erlang from source. | |
| - name: Read the toolchain out of mise.toml | |
| id: toolchain | |
| run: | | |
| set -euo pipefail | |
| erlang="$(sed -n 's/^erlang *= *"\(.*\)".*$/\1/p' mise.toml | head -1)" | |
| elixir="$(sed -n 's/^elixir *= *"\(.*\)".*$/\1/p' mise.toml | head -1)" | |
| test -n "$erlang" || { echo "no erlang version in mise.toml" >&2; exit 1; } | |
| test -n "$elixir" || { echo "no elixir version in mise.toml" >&2; exit 1; } | |
| echo "erlang=$erlang" >> "$GITHUB_OUTPUT" | |
| echo "elixir=$elixir" >> "$GITHUB_OUTPUT" | |
| - uses: erlef/setup-beam@v1 | |
| with: | |
| otp-version: ${{ steps.toolchain.outputs.erlang }} | |
| elixir-version: ${{ steps.toolchain.outputs.elixir }} | |
| # _build carries the Dialyzer PLT | |
| # (_build/dev/dialyxir_erlang-*_elixir-*_deps-dev.plt), so this cache is | |
| # the PLT cache too. The key is derived from mix.lock, which is the whole | |
| # pin: every dependency here is a Hex package, so nothing authenticates | |
| # to anything and the default GITHUB_TOKEN with the read-only `contents` | |
| # permission above is all this job needs. | |
| # | |
| # Known tradeoff, the same one the satellites accept: a warm _build | |
| # means the compile stage is incremental, so warnings_as_errors only | |
| # sees files this push actually recompiles. Deleting the cache is how to | |
| # get a from-scratch compile when one is wanted. | |
| - name: Cache deps and build | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| deps | |
| _build | |
| key: mix-${{ runner.os }}-otp${{ steps.toolchain.outputs.erlang }}-ex${{ steps.toolchain.outputs.elixir }}-${{ hashFiles('mix.lock') }} | |
| restore-keys: | | |
| mix-${{ runner.os }}-otp${{ steps.toolchain.outputs.erlang }}-ex${{ steps.toolchain.outputs.elixir }}- | |
| - name: Fetch dependencies | |
| run: mix deps.get | |
| # The gate is the whole job. It formats, compiles with warnings as | |
| # errors, runs credo --strict, dialyzer, the deps audit, and the full | |
| # suite with coverage, printing detail under any failing stage - so a | |
| # red run is diagnosable from this log alone. Three stages report as | |
| # skipped and always will: doctor, gettext, and sobelow, none installed | |
| # here (they are the manifest's not_applicable_skips). | |
| # | |
| # Same reasoning as the toolchain step, applied to the gate itself: the | |
| # command is read out of .claude/wurk.json rather than written here, so | |
| # the gate CI runs and the gate an agent must have green before | |
| # committing are one definition with one place to change it. It is read | |
| # and executed inside the step rather than interpolated into `run:`, so | |
| # a repository file never reaches the shell as expression text. | |
| - name: Full quality gate | |
| run: | | |
| set -euo pipefail | |
| mapfile -t cmd < <(jq -er '.gate.full[]' .claude/wurk.json) | |
| test "${#cmd[@]}" -gt 0 || { echo "gate.full is empty" >&2; exit 1; } | |
| echo "gate command (.claude/wurk.json gate.full): ${cmd[*]}" | |
| "${cmd[@]}" |