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
2 changes: 1 addition & 1 deletion .github/coverage.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Coverage configuration for the one coverage-measured leg of unit-tests.yml
# (`test`, Python 3.15), passed with `--rcfile`. Coverage reads a single config
# file, so this repeats pyproject.toml's [tool.coverage.*] settings and adds the
# four below on top. tests/project/test_coverage_rcfile.py asserts every
# settings below on top. tests/project/test_coverage_rcfile.py asserts every
# pyproject setting is repeated here unchanged.
#
# parallel -- each process writes its own .coverage.* file, merged afterwards
Expand Down
6 changes: 3 additions & 3 deletions .github/python-versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ latest = "3.15"
# no wheel, conda build or required context. The weekly job has no source for
# either, so it keeps these lines as written, after the other versions of their
# table. Promoting one once its leg is green is a hand edit, which
# util/python_versions.py refuses until it has those three.
# util/python_versions.py refuses until it has all of them.
[versions.graalpy]
"graalpy-25.4" = { tier = "experimental" }

# The pins kept off the tiers, each with its reason in the comment above it.
# tests/project/test_python_versions_1660.py fails on a pin not listed here, on
# an entry that matches no pin, and on an entry without a comment.

# The parity matrix's one supported leg saves the apt cache. Both legs install
# one set under one key, and a second saver fails with `Unable to reserve
# Only the parity matrix's supported leg saves the apt cache. Every leg installs
# one set under one key, and another saver fails with `Unable to reserve
# cache`.
[[exemptions]]
workflow = "unit-tests.yml"
Expand Down
45 changes: 21 additions & 24 deletions .github/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,31 @@
# the type a pull request announces in its title cannot reach this file.
#
# Merged pull requests carry a type label, set by hand, which is what makes a
# category per label possible -- an earlier survey of 40 pull requests,
# mostly untyped, supported only a bare Dependencies/catch-all split.
#
# Sampling 334 merged pull requests (`gh pr list --state merged --limit 400`)
# gives the frequency below; zero carry no type label:
#
# fix 124, dependencies 97, breaking 50, ci 41, test 31, docs 28, feat 12,
# release 10, chore 9, perf 7, const 2, refactor 2
# category per label possible -- an earlier survey of pull requests, mostly
# untyped, supported only a bare Dependencies/catch-all split. The order below
# was derived from a sample of merged pull requests (`gh pr list --state
# merged`), every one of which carried a type label, `fix` most often.
#
# GitHub files a pull request under the *first* category whose `labels` it
# matches, so category order is what resolves the 69 pull requests that carry
# matches, so category order is what resolves the pull requests that carry
# more than one type label. This order is derived from those overlaps, traced
# against the actual pull requests, not guessed:
#
# * `breaking` is first, full stop. It co-occurs with `fix` (39x, +5 more with
# `test` too), `feat` (3x), `ci`+`release` (2x, +1 more with `fix` too) and
# `refactor` (1x) -- e.g. #25 "New distribution [0.14.0]" carries
# * `breaking` is first, full stop. It co-occurs with `fix` (most often, at
# times with `test` too), `feat`, `ci`+`release` (at times with `fix` too)
# and `refactor` -- e.g. #25 "New distribution [0.14.0]" carries
# breaking+ci+fix+release. Any other position buries a breaking change
# under "Fixed" or "Release", which is the one mistake this file exists to
# under "Fixes" or "Release", which is the one mistake this file exists to
# avoid.
# * `release` is second. Its overlaps with `ci` (3x) and with `ci`+
# `dependencies` (1x) are all old "New distribution [x]" pull requests
# * `release` is second. Its overlaps with `ci` and with `ci`+
# `dependencies` are all old "New distribution [x]" pull requests
# (#9, #11, #21, #22) -- a version bump that happens to also touch a
# workflow file or a lockfile is still, in substance, a release. `release`
# also wins its one overlap with `fix` (#14, "New distribution [0.13.3]").
# * `feat` and `fix` follow, in that order by convention: neither overlaps
# with anything ranked above, so nothing in the data forces this half.
# `refactor` comes next; its only overlap is with `breaking`, above.
# also wins its overlap with `fix` (#14, "New distribution [0.13.3]").
# * `feat` and `fix` follow, in that order by convention: they never overlap
# each other, so nothing in the data forces this half.
# `refactor` comes next; in the sample its only overlap was with
# `breaking`, above.
# * `ci` beats `perf` (#727, subject `ci(unit-tests):`, labelled ci+perf),
# `const` (#12 "Update Makefile", labelled ci+const) and `dependencies`
# (#41 "Config file for pyup.io", labelled ci+dependencies) -- in each case
Expand All @@ -53,22 +50,22 @@
# against).
# * `dependencies` sits second-to-last rather than first -- see below.
#
# `dependencies` used to be the first category, and is 97 of the 334 sampled
# pull requests, all Dependabot noise. Moving it down is a judgement call, not
# a data requirement: nothing in the overlaps above forces its position
# `dependencies` used to be the first category, and its pull requests are
# almost all bot noise, from pyup and Dependabot. Moving it down is a
# judgement call, not a data requirement: nothing in the overlaps above forces its position
# relative to anything except `release` and `ci`, both of which already rank
# ahead of it regardless of where it sits. The case for moving it here: a
# future pull request that mixes a real change with a `dependencies` label
# (say, a fix prompted by a dependency bump) surfaces under that real category
# instead of being swallowed by bot noise -- which is also, concretely, what
# keeps #22 ("New distribution [0.13.3.post2]", ci+dependencies+release) filed
# under Release rather than Dependencies. The case for leaving it first, as
# before: it keeps the highest-volume, lowest-interest category out of the way
# before: it keeps a high-volume, low-interest category out of the way
# of everything else on a first read. This file takes the former; flip its
# position in the list below if that judgement is wrong.
#
# `*` catches whatever matches none of the categories above -- nothing in the
# sample, since every pull request in it carries a type label, but it stays
# sample, since every pull request in it carried a type label, but it stays
# last regardless so an unlabelled future pull request still lands somewhere
# instead of silently vanishing from the notes.
#
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-timing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ on:

# Read-only: the runs, jobs and artifacts need `actions: read`, the workflow
# files at each run's sha need `contents: read`. GITHUB_TOKEN allows 1,000 API
# requests an hour per repository; a default run uses about 690.
# requests an hour per repository; a default run was estimated at about 690.
permissions:
actions: read
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Coverage Comment
# unit-tests.yml as a pull-request comment. It runs no tests. The comment cannot
# be posted from unit-tests.yml itself: that file is also a reusable workflow,
# and a called workflow can only narrow the token its caller passes, never widen
# it. All four callers grant `contents: read`, so asking for
# it. Every caller grants `contents: read`, so asking for
# `pull-requests: write` there would break every one of them, the release gate
# included.
#
Expand Down
54 changes: 27 additions & 27 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ concurrency:
cancel-in-progress: false

# Every job below with an effect outside this run declares a deployment
# ``environment:``, but only one of them -- ``github-release`` -- carries a
# required reviewer (#887). Four jobs still get four environments rather than
# ``environment:``, but only ``github-release`` carries a required reviewer
# (#887). Each such job still gets an environment of its own rather than
# one shared ``release``, for credential and blast-radius separation, not for
# approval count:
#
Expand All @@ -50,7 +50,7 @@ concurrency:
# published. See :file:`docs/source/contributing/releasing.rst` for the full pipeline and the
# reasoning behind one approval being enough.
#
# The other three environments keep their names -- ``pypi``'s matters because
# The other environments keep their names -- ``pypi``'s matters because
# PyPI trusted publishing matches its OIDC claim against it -- and keep their
# secret scoping, they just hold no required reviewer of their own. Do
# not add one back to any of them without re-examining the ``needs:`` chain: a
Expand All @@ -60,7 +60,7 @@ concurrency:
# ``tests/project/test_release_gates.py``'s ``TestGatedJobsDependOnGithub``
# asserts that every gated job's ``needs:`` closure includes ``github``, which
# is the checkable half of this paragraph -- reverting the line above to
# ``needs: [ version_check ]`` fails only that one test.
# ``needs: [ version_check ]`` fails only that test.
#
# One environment per distinct credential and blast radius regardless of who
# gates it: a single ``release`` approved once would release PyPI and Anaconda
Expand All @@ -75,7 +75,7 @@ concurrency:
# whereas ``tag`` spends a long-lived PAT and a deploy key to write to the
# default branch.
#
# Two things about this that are not self-enforcing, and #641 is the first of them:
# Some things about this are not self-enforcing, and #641 is the first of them:
#
# * An ``environment:`` naming an environment that does not exist is created
# implicitly, with no protection rules, and the job proceeds unapproved. So
Expand Down Expand Up @@ -129,7 +129,7 @@ concurrency:
# pair does not. A cancelled ``tag`` would otherwise send ``conda`` into its
# check that ``conda-<version>+0`` names this run's commit, for a tag ``tag``
# never pushed, trading a clean skip for a failed step. ``version_check`` is held
# to a stricter bar still: all three require ``needs.version_check.result ==
# to a stricter bar still: all of them require ``needs.version_check.result ==
# 'success'`` with no ``|| 'skipped'``, since it produces the evidence the gates
# below read, and a skipped or cancelled ``version_check`` leaves them nothing
# to decide on. Either spelling bypasses the default ``success()`` GitHub
Expand All @@ -148,9 +148,9 @@ concurrency:
# that the gate -- a ~20-minute full-suite run -- is spent only when something
# is going to be published. Every successful Vendor Update triggers this
# workflow, and most of them leave ``__version__`` alone, so the gate used to
# run on commits Unit Tests had already passed only for all four jobs below to
# run on commits Unit Tests had already passed only for every job below to
# skip (eight times on 2026-10-05 alone). ``unit-tests`` runs when the ref
# is a ``v*`` tag or *any* of the four evidence outputs is not ``'true'``,
# is a ``v*`` tag or *any* of the evidence outputs is not ``'true'``,
# which covers every case in which ``github``, ``tag``, ``pypi`` or ``conda``
# could run: each of them runs only on a ``v*`` ref or on its own evidence
# reading ``'false'``. ``!= 'true'`` rather than ``== 'false'`` is deliberate --
Expand Down Expand Up @@ -191,11 +191,11 @@ concurrency:
# and runs ``tag``'s scripts against a scratch repository.
#
# #1748: so a version tagged at another commit would fail every later run for
# it: ``v1.5.0b12`` names ``2025da5e5`` with 1 of its 10 Anaconda files, and
# each push-raised run spent the test gate and 10 ``conda`` legs on refusals.
# it: ``v1.5.0b12`` names ``2025da5e5`` with only part of its Anaconda files,
# and each push-raised run spent the test gate and every ``conda`` leg on refusals.
# ``version_check``'s ``owner`` step resolves the ``v*`` tag as the #1634 check
# does, and on a ref that is not a ``v*`` tag, ``PCAPKIT_RELEASED_ELSEWHERE``
# reading ``'true'`` skips the gate and all four publishers -- ``github``
# reading ``'true'`` skips the gate and every publisher -- ``github``
# mostly skips there already, the tag existing. ``release_status`` then posts
# a notice when that release is complete and a warning naming what it lacks
# when not, and stays green. A ``v*`` push still meets the #1634 check, and
Expand All @@ -209,7 +209,7 @@ jobs:
# ``success``, and Create Release took it for a real one (Vendor Update
# 38650191283, a second Create Release run for ``0cdd3cdd1``). Measured: that
# run's jobs read ``success``/``skipped``/``skipped``, and 38655782606, whose
# three jobs all skipped, concluded ``skipped``; ``verdict`` has to run to
# jobs all skipped, concluded ``skipped``; ``verdict`` has to run to
# decide, so Vendor Update could not conclude otherwise without a failed or
# cancelled run. This job reads that run's ``vendor-update`` job instead, with
# "List jobs for a workflow run", whose ``filter`` defaults to ``latest``,
Expand Down Expand Up @@ -269,7 +269,7 @@ jobs:
# The outermost gate, moved here from ``unit-tests`` by #1052: a
# ``workflow_run`` trigger proceeds only when the Vendor Update run that
# triggered it concluded ``success``, and a tag push always proceeds. This
# job is read-only -- it installs the package and reads three public
# job is read-only -- it installs the package and reads public
# endpoints -- so running it ahead of the test gate risks nothing.
#
# #1634: a ``v*`` push by the release App's bot skips too. That is the tag
Expand Down Expand Up @@ -303,7 +303,7 @@ jobs:

# #1660: .github/python-versions.toml, read with the runner's own python3:
# `latest` for this job and the ``pypi`` and ``conda`` matrices, from this
# run's commit, and the tags the two completeness checks below count until
# run's commit, and the tags the completeness checks below count until
# the version is tagged.
- name: Read the Python versions
id: python-versions
Expand Down Expand Up @@ -337,15 +337,15 @@ jobs:
# above legitimately uses since the ``v*`` tag *is* its own artefact. It is
# the wrong question for ``tag``, ``pypi`` and ``conda``, whose artefacts
# are a different tag, PyPI, and Anaconda respectively -- a half-failed
# release leaves the ``v*`` tag behind while those three are still
# release leaves the ``v*`` tag behind while those are still
# incomplete, and gating them on it is what made every one of them skip
# silently. Each gets its own evidence step below instead.
- uses: mukunku/tag-exists-action@v1.7.0
id: check_conda_tag
with:
tag: "conda-${{ steps.get_version.outputs.PCAPKIT_VERSION }}+0"

# #1660: the two checks below count what the version was released with.
# #1660: the checks below count what the version was released with.
# Once its ``v*`` tag exists, that is the tier file at the tag's commit,
# read by that commit's own util/python_versions.py, not this run's file:
# a version added to the supported tier since was never built for it, and
Expand Down Expand Up @@ -404,7 +404,7 @@ jobs:
#
# The per-version endpoint's own ``releases`` mapping was empty on every
# request checked while writing this, including against a version that
# genuinely has all 8 files -- so this reads ``urls``, the field the
# genuinely has every file -- so this reads ``urls``, the field the
# per-version endpoint actually populates for the requested version.
# ``/pypi/pypcapkit/json`` (no version) does populate ``releases``, but at
# ~950KB against this endpoint's ~15KB, for one release's worth of data.
Expand Down Expand Up @@ -573,7 +573,7 @@ jobs:
# effect, and a job that calls a reusable workflow may not declare one anyway.
#
# #1052: runs only when a release will actually happen -- a ``v*`` tag push,
# or any of the four targets still incomplete by its own evidence. See the
# or any target still incomplete by its own evidence. See the
# note above ``jobs:`` for why ``!= 'true'`` and why a skip here can never
# let a publisher through. #1748: nor on a version tagged at another commit,
# unless this is a ``v*`` push, on the same clause as ``tag``, ``pypi`` and
Expand Down Expand Up @@ -603,7 +603,7 @@ jobs:
# ``github.sha`` (#1634) -- that every job below then verifies before
# publishing under it. This is the release's only approval (#887) --
# see the note above ``jobs:``. Still gated on ``PCAPKIT_TAG_EXISTS``,
# unlike the three jobs below it: the tag that output answers for is this
# unlike the publishing jobs below it: the tag that output answers for is this
# job's own artefact, not a proxy for someone else's -- see the #888 note
# above ``jobs:``. ``unit-tests`` is listed because the gate follows
# ``version_check`` (#1052); this ``if:`` calls no status-check function,
Expand All @@ -613,8 +613,8 @@ jobs:
# #1748: a tag naming another commit is a tag that exists, so
# ``PCAPKIT_TAG_EXISTS`` already skips this job off a ``v*`` ref -- unless
# the tag was pushed between ``check_tag`` and ``owner``, which read it
# separately. The same clause as the other three covers that too, so the
# gate runs whenever any of the four would.
# separately. The same clause as the other publishers' covers that too, so the
# gate runs whenever any publisher would.
environment: github-release
permissions:
contents: write
Expand Down Expand Up @@ -830,7 +830,7 @@ jobs:
fetch-depth: 0

# Everything after this step writes through the deploy key loaded above.
# Kept to these lines alone: the project test runs the two steps below
# Kept to these lines alone: the project test runs the steps below
# against a scratch repository, standing in for this one.
- name: Configure git for the deploy key
run: |
Expand Down Expand Up @@ -1103,7 +1103,7 @@ jobs:
# byte-identical -- but only an asset in state ``uploaded``: after a 502,
# Upload a release asset "may leave an empty asset with a state of
# starter", which ``--clobber`` deletes before uploading the file again.
# Two failures the action did not have: no release fails ``gh release
# Failures the action did not have: no release fails ``gh release
# view`` with "release not found", where the action created one with the
# default branch as its target, and a pattern matching nothing is an
# ``::error``, where it only warned. ``conda`` runs the same script.
Expand Down Expand Up @@ -1466,7 +1466,7 @@ jobs:
packages: ${{ steps.conda_build.outputs.packages }}
labels: ${{ needs.version_check.outputs.PCAPKIT_CONDA_LABEL }}

# ``pypi``'s upload script, for the same two reasons (#1634, #1640). Not
# ``pypi``'s upload script, for the same reasons (#1634, #1640). Not
# gated on ``check_leg`` as the Anaconda upload is: a leg whose Anaconda
# upload landed and whose GitHub upload then failed would get no GitHub
# asset on any re-run. Skipping a file the release already has is what
Expand Down Expand Up @@ -1644,11 +1644,11 @@ jobs:
# and skips with work outstanding, every publisher skips behind it
# and the same reconciliation reaches the loud #888 branch instead.
#
# Reconcile each of the four release jobs against its OWN evidence,
# Reconcile each release job against its OWN evidence,
# not against each other. A self-heal is a MIXED outcome by design
# -- github/tag skip because their artefacts already existed going
# in, pypi/conda run and succeed because theirs did not -- and
# demanding uniformity across all four (either all skipped, or all
# demanding uniformity across all of them (either all skipped, or all
# succeeded, as an earlier revision of this job did) reads that
# mix as the #888 shape itself, firing the error below on the run
# that just finished the release. "Reconciled" means: this target
Expand Down Expand Up @@ -1695,7 +1695,7 @@ jobs:
fi

# Every target reconciled, but at least one of them actually ran --
# either a fresh full release (all four `success`) or a self-heal
# either a fresh full release (every one `success`) or a self-heal
# (a mix of `success` and legitimately-`skipped`). Both are the
# release working as intended, so both are quiet.
if [ "$all_reconciled" = "true" ]; then
Expand Down
Loading
Loading