Repository navigation
CI · pull_request · fix/960-961-holdings-dca #1266
Workflow file for this run
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 | |
| # The Actions list titles every run with the COMMIT MESSAGE, so four different workflows | |
| # triggered by one push read as four identical entries -- which is how a merge came to look | |
| # like a job running four times. `run-name` replaces that title with what the run IS. | |
| # | |
| # Deliberately NOT `github.event.head_commit.message`: this repository writes long, | |
| # multi-paragraph commit messages, the title would swallow the list, and GitHub already | |
| # shows the commit beside every run. The workflow's identity is the part that was missing. | |
| run-name: "CI · ${{ github.event_name }} · ${{ github.head_ref || github.ref_name }}" | |
| # ON EVERY MERGE, and on demand. A push to `main` is what a merge looks like from Actions' side, | |
| # so every merged PR now re-runs lint, the suite, and the build-identity check against the tree | |
| # that actually resulted from the merge. | |
| # | |
| # That last phrase is the point, and it is why this is NOT the redundant run it was once called. | |
| # A `pull_request` run tests the PR head; a `push` run tests `main` after the merge landed. Those | |
| # differ whenever two independently-green branches conflict semantically -- each passes alone, the | |
| # merge of them does not. Only the post-merge run can see that. | |
| # | |
| # `pull_request` is here for a specific, load-bearing reason: it is what makes this job report a | |
| # status named `test` on a PR, and the `main` ruleset REQUIRES that context before a merge is | |
| # allowed (issue #236). The two are a matched pair and must be changed together -- removing this | |
| # trigger while the ruleset still requires `test` would leave every PR waiting forever on a check | |
| # that can never report, which is precisely the broken state #236 was filed to fix. That is not a | |
| # hypothetical: the ruleset previously required "Lint, Unit tests, Build" and "Integration Tests", | |
| # names no job here has ever produced. | |
| # | |
| # The context string is `test` because the job below is `test:` and declares no `name:`. Renaming | |
| # that job, or giving it a `name:`, silently changes the context and breaks the ruleset the same | |
| # way. If you rename it, update the ruleset in the same change. | |
| # | |
| # So the two triggers do different jobs: `pull_request` GATES the merge, `push` VERIFIES the result | |
| # of it. `release.yml` remains the gate that matters for money -- it re-runs `ruff` + `mypy` + `pytest` | |
| # itself before it will build an artifact, so a red `main` cannot become a wheel, a tag, or a | |
| # release asset even if both of these were removed. | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| # `github.ref` is `refs/pull/<n>/merge` for PR runs, `refs/heads/main` for the post-merge push, and | |
| # `refs/heads/<branch>` for a dispatch -- distinct keys, so a PR run never cancels the post-merge | |
| # run. Pushes to the same PR do cancel each other, and back-to-back merges cancel the older `main` | |
| # run -- correct in both cases: only the newest result matters. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| # ONE leg, because the floor and the development interpreter are now the same version. | |
| # | |
| # This was two legs -- "3.11" and "3.14" -- the two ends of the `>=3.11` floor #283 chose. | |
| # With `requires-python = ">=3.14"` there is no span left to test across: the floor leg and | |
| # the development leg would be the same interpreter, and a matrix of one is a matrix that | |
| # costs a second checkout to prove nothing. | |
| # | |
| # It stays a matrix rather than a bare `python-version:` deliberately. When a second | |
| # supported version arrives -- 3.15, or a floor drop -- adding it here is one list entry, | |
| # and the required status context stays `test` either way, because the matrix fans the | |
| # context out without renaming it. Collapsing to a scalar would make that a workflow edit. | |
| # | |
| # "3.14" is named by major.minor only; `.python-version` pins the exact patch (3.14.4) for | |
| # local work, and "whatever .python-version says" is not addressable per-leg. | |
| strategy: | |
| matrix: | |
| python: ["3.14"] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 # tags -- tests/test_python_floor.py reads the latest release's | |
| # requires-python from its tag (#557: it must never be main's floor) | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install "${{ matrix.python }}" | |
| - name: Sync dependencies | |
| run: uv sync --all-extras --dev --python "${{ matrix.python }}" | |
| - name: Lint | |
| run: uv run --python "${{ matrix.python }}" ruff check keel tests packages | |
| # WHOLE TREE, unlike `ruff check` above, and that difference is deliberate. `ruff check` | |
| # is scoped to the three trees whose lint debt has been paid; formatting has no such debt | |
| # after #783's one-shot pass, so scoping this would just re-open the drift in `docs/` and | |
| # `scripts/` that the pass exists to close. | |
| # | |
| # It runs as its own step so a formatting failure reads as one. Folded into the lint step, | |
| # `ruff check` would mask it on any run where both are red. | |
| - name: Format | |
| run: uv run --python "${{ matrix.python }}" ruff format --check . | |
| # A STEP in this job, deliberately not a job of its own. The `main` ruleset requires the | |
| # status context `test`, which this job produces; a separate `typecheck:` job would report | |
| # a context nothing requires, so a red mypy would not block a merge -- the same failure | |
| # shape the header above describes for a renamed job. As a step it inherits that gate. | |
| # | |
| # Paths come from `[tool.mypy]`'s `files` in pyproject.toml and are deliberately NOT | |
| # repeated here: `keel.*` came off the `ignore_errors` list in #266, and the point of this | |
| # step is that it cannot drift back silently. A package moving in or out of the checked | |
| # set is then one edit there, not one edit mirrored across two workflows. | |
| - name: Type-check | |
| run: uv run --python "${{ matrix.python }}" mypy | |
| - name: Test | |
| run: uv run --python "${{ matrix.python }}" pytest -q | |
| # A build that cannot identify itself must not reach a release. This also catches an | |
| # import-time break in the CLI, which `pytest` alone would not surface as sharply. | |
| - name: Build identity | |
| run: uv run --python "${{ matrix.python }}" keel --version |