Skip to content

build: prepare the 0.2.0 release to pypi - #27

Merged
CameronBrooks11 merged 10 commits into
mainfrom
build/release-0.2.0
Oct 21, 2026
Merged

CameronBrooks11 merged 10 commits into
mainfrom
build/release-0.2.0

Conversation

@CameronBrooks11

@CameronBrooks11 CameronBrooks11 commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Prepares kindkit's first PyPI release, 0.2.0. Nothing is tagged or published by this PR: publication happens only when a v0.2.0 tag is pushed after merge.

What changes

  • .github/workflows/release.yml (new), modelled on rowspec's. On a v* tag:

    1. gate: just check, just test, just mutants (the same gates check.yml runs on a PR), and kind calls kind.yml on the toy tree exactly as check.yml does.
    2. build (needs both): uv build, then refuses
      • a tag that disagrees with the built version,
      • a wheel carrying anything outside kindkit/ and its .dist-info, or missing kindkit/py.typed,
      • an sdist whose shipped test suite (uv run --exact --locked pytest) or tools/validate_case_tree.py tests/fixtures/kv fails when run from the unpacked sdist,
      • a wheel whose kindkit.__version__ disagrees with its metadata, that imports a non-stdlib module, that resolves kindkit from the checkout instead of the wheel, or whose runner cannot both pass the toy kind (Good) and fail its negative control (Raw).
    3. publish (needs build; if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')): environment: pypi, id-token: write, pypa/gh-action-pypi-publish at the same full SHA rowspec pins (v1.14.2).

    Every action is pinned by commit SHA, with the same SHAs rowspec and check.yml use. workflow_dispatch is a dry run on any ref, a tag included. Every gate and the build run, and publish is skipped because the event is not push. A guard on the ref alone would not be enough: gh workflow run release.yml --ref v0.2.0 sets github.ref to refs/tags/v0.2.0, which the pypi environment admits. rowspec's workflow has no if: on publish at all. rowspec's release workflow creates no GitHub Release, so this one doesn't either. Its action-pin and published-action jobs have no counterpart here, because kindkit ships no action.yml.

  • sdist ships case-tree/ (with its CC0 LICENSE). tools/validate_case_tree.py and tools/mutation_gate.py read case-tree/expect.schema.json, and the sdist shipped tools/ without it. Before this change, from an unpacked sdist, python3 -I tools/validate_case_tree.py tests/fixtures/kv printed error: cannot read schema '.../case-tree/expect.schema.json' and exited 2. It now prints 0 invalid case(s) across 6 case(s) in 1 tree(s) and exits 0.

  • sdist ships uv.lock and .pre-commit-config.yaml. tests/test_tooling.py reads both to check the ruff hook against the locked ruff, and from the unpacked sdist it failed with FileNotFoundError at tests/test_tooling.py:22. Both files are already public in this repository and name only public sources (pypi.org, and the GitHub repositories of the pre-commit hooks). From the unpacked sdist, uv run --exact --locked pytest -q now gives 220 passed, the same count as the checkout, with none skipped. No test needs a git checkout: the git tests build their own repositories under tmp_path.

  • Version 0.2.0 in pyproject.toml, kindkit.__version__ and uv.lock's own entry (via uv lock).

  • README status line: "not on PyPI" becomes "published on PyPI as kindkit from v* tags, starting at 0.2.0; install with pip install kindkit". The "Using the workflow" section now says 0.2.0 is the first release that has the report keys v0.1.0 lacks. The claim that rowspec pins kindkit to a commit still holds: git -C rowspec show origin/main:pyproject.toml | grep kindkit shows kindkit @ git+...@8e21b558....

Changes since v0.1.0

kindkit has no CHANGELOG. From git log --oneline v0.1.0..origin/main:

Evidence

Claim Command Result
Lint and format clean just check exit 0
Tests pass just test 220 passed, exit 0
Mutation gate just mutants 148 mutations, 148 caught, 0 survived, 0 broken, exit 0
Build succeeds uv build kindkit-0.2.0.tar.gz, kindkit-0.2.0-py3-none-any.whl
Wheel holds only the package and licences zipfile.ZipFile(whl).namelist() kindkit/{__init__,cli,gitmerge,mutation,runner}.py, kindkit/py.typed, kindkit-0.2.0.dist-info/{METADATA,WHEEL,RECORD}, licenses/LICENSE, licenses/LICENSES/{Apache-2.0,CC-BY-4.0,CC0-1.0,MIT}.txt
Metadata METADATA Name: kindkit, Version: 0.2.0, License-Expression: MIT
Wheel installs and imports python3 -m venv v && v/bin/pip install kindkit-0.2.0-py3-none-any.whl && v/bin/python -I -c 'import kindkit; print(kindkit.__version__)' 0.2.0, from the venv's site-packages
The workflow's build steps pass on this tree each run: block of the build job, extracted with PyYAML and run with bash -e in a copy of the checkout tag check (GITHUB_REF_NAME=v0.2.0) exit 0; wheel contents exit 0; smoke exit 0, printing 0 failure(s) for Good and 2 failure(s) for Raw
Each build guard can fail the same steps after breaking the thing each one checks, with the input hashed before and after so a mutation that does not apply reports BROKEN tag v0.2.1: exit 1. __version__ = "0.0.0", wheel tests/__init__.py, wheel without py.typed, python -P dropped, Raw swapped for Alt, import yaml in runner.py: all caught (exit 1). Controls exit 0
The sdist step can fail kk28-sdist-sweep.sh: the step's run: block extracted with PyYAML, run on a copy of the checkout, then rerun with each new include entry deleted, hashing pyproject.toml before and after each control exit 0 (220 passed, 0 invalid case(s) across 6 case(s) in 1 tree(s)). /case-tree removed (d3e04a28e132 -> 73c2cbdeefdf): exit 1, FileNotFoundError: .../case-tree/expect.schema.json. /uv.lock removed (-> c9f49de5ef26): exit 2, Unable to find lockfile at uv.lock, but --locked was provided. /.pre-commit-config.yaml removed (-> 2f7e849b0096): exit 1, FileNotFoundError at tests/test_tooling.py:22
Extra release time the step above, timed locally the suite runs in 11–34s depending on machine load (220 passed in 11.06s and in 33.24s). In CI this is an estimate, not a measurement: roughly that plus an environment sync. The step cannot run until the workflow is on main
sdist contents tar -tzf kindkit-0.2.0.tar.gz 59 entries, including case-tree/{CASE-TREE.md,LICENSE,expect.schema.json}, uv.lock and .pre-commit-config.yaml
No effect on required checks check_required_checks.py --load on a --public-only snapshot with this release.yml added to kindkit's workflows 0 finding(s), 0 not evaluated, exit 0
The audit sees this file the same snapshot with push: stripped of its tags: filter DUPLICATE kindkit: ... release.yml: job 'kind' ... also produces 'kind / conformance' on push to other branches, exit 1

The python -P guard caught a real defect while this was being written. A script read from stdin puts the working directory on sys.path, so without -P the smoke test imported kindkit from the checkout and not from the wheel.

Not covered here

  • The publish job and the kind call from a tag ref have not run. A workflow_dispatch on main after merge exercises everything except publish.
  • check.yml is unchanged. This workflow adds no pull_request trigger and pushes only on tags.

pyproject.toml, kindkit.__version__ and the lockfile's own entry all
move to 0.2.0, the version the first PyPI release will carry.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
A v* tag runs the same gates check.yml runs on a pull request, plus
the reusable workflow on the toy tree, then builds the sdist and wheel.
The build refuses a tag that disagrees with the built version, a wheel
carrying anything outside kindkit/ and its dist-info, and a wheel whose
__version__ disagrees with its metadata, that imports a non-stdlib
module, or whose runner cannot both pass the toy kind and fail its
negative control. Only then does the publish job, in the pypi
environment, upload through PyPI's trusted publisher.

workflow_dispatch is a dry run: publish is skipped off a tag. The push
trigger is tags only, so no job here produces a required check on a
pull request head.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
The status line said not on PyPI. It now names the package, the
install command and the release that first carries the report and
gate-report contract the reusable workflow needs.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
A workflow_dispatch run against a tag ref sets github.ref to
refs/tags/v*, which the pypi environment admits, so a guard on the ref
alone let a dispatch publish. The publish job now also requires
github.event_name == 'push', which makes a dispatch a dry run on any
ref.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
tools/validate_case_tree.py and tools/mutation_gate.py both read
case-tree/expect.schema.json, and the sdist shipped tools/ without it:
from an unpacked sdist the validator exited 2, unable to read the
schema. The convention, its schema and their CC0 LICENSE now ship.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
The build job unpacks the built sdist into a temporary directory and
runs tools/validate_case_tree.py from it, so an include list that
drops a file the shipped tools read fails the release instead of the
person who unpacks it. With /case-tree removed from the include the
step exits 2, unable to read the schema.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
The sentence naming 0.2.0 as the first release with the report keys
ran to 104 columns; it now wraps like the rest of the paragraph.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
tests/test_tooling.py checks that the ruff hook in
.pre-commit-config.yaml runs the ruff uv.lock resolves, and the sdist
shipped the test without either file, so it failed from an unpacked
sdist with FileNotFoundError. Both files only name public package and
hook sources. From the unpacked sdist the full suite now passes.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
The sdist step now runs the shipped suite with uv run --exact --locked
from the unpacked sdist before the validator, so an include list that
drops a file the tests read fails the release. Removing /case-tree,
/uv.lock or /.pre-commit-config.yaml from the include each turns the
step red.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
PYTHONPYCACHEPREFIX mirrors the source's absolute path, and a recursive
`**` glob skips dot-directories, so the precondition assert failed for
anyone whose temporary directory sits under one, such as ~/.cache.
Match hidden directories too. The suite now ships in the sdist, where
that is likelier.

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
@CameronBrooks11
CameronBrooks11 merged commit 4764461 into main Oct 21, 2026
5 checks passed
@CameronBrooks11
CameronBrooks11 deleted the build/release-0.2.0 branch October 21, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant