ci: add pyrefly and move coverage reporting to Codecov - #113
Merged
Conversation
pyrefly joins mypy and pyright as a third strict type checker, in the quality matrix and in the pre-commit hooks. Three independent implementations of the same type system disagree in the corners, and the corners are exactly where a signature-preserving decorator lives. The package is clean under pyrefly's strict preset; missing-override-decorator is the one error kind turned off, since typing.override is 3.12+ and the core carries no typing_extensions dependency to backport it. Coverage reporting moves to Codecov. The coverage job now writes coverage.xml plus a JUnit report and uploads both, so a PR shows the per-file coverage diff and the failing tests themselves instead of a single total. The 100% gate is unchanged and still enforced by pytest (fail_under); the Codecov statuses in .github/codecov.yml only mirror it. This replaces py-cov-action and its badge branch, so the job drops contents: write and pull-requests: write.
Contributor
ty was never wired into CI or the pre-commit hooks — pyrefly is the third checker that actually runs, so the dependency has no reader. Codecov stayed silent on this PR: `require_changes: true` suppresses the comment when coverage does not move, and at a flat 100% it never moves.
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
5 tasks
bagowix
added a commit
that referenced
this pull request
Aug 1, 2026
## Summary The `RuleBasedStateMachine` from #106 (#112) shrank a counterexample on `main`. It reproduces locally but not in CI — hypothesis found it on a seed CI has not drawn yet, so today it is a latent flake rather than a red build: ``` config: minimum_number_of_calls=1, failure_rate_threshold=1.0, permitted_calls_in_half_open=1, max_concurrent_probes=1 admit → settle(FAILURE) # CLOSED → OPEN advance(1.0) # the open wait elapses admit → settle(FAILURE) # HALF_OPEN probe fails → OPEN ``` At the last step the machine still holds `_probes_admitted == 1` while the model predicted `0`: the model forgets the probe round on *every* transition, and the machine clears it on entry to `HALF_OPEN` (`_to_half_open`) and `CLOSED` (`_close`), not on `_open()`. **The machine is right.** The counters are read only by `_admits_probe`, which is reachable only in `HALF_OPEN`, and `HALF_OPEN` is only ever entered through `_to_half_open()` — which resets them. Adding a reset to `_open()` would be defensive code written for a test, not for a caller. What the model asserted there was an internal detail the contract does not promise. So `probe_budget_holds` now compares the machine's counters against the model only in `HALF_OPEN`, where the budget is spent and the contract defines it; the cap assertions (`max_concurrent_probes`, `permitted_calls_in_half_open`) stay unconditional. No production code changes. ## Test plan - [x] `uv run pytest tests/test_state_machine_model.py` passes, plus 6 explicit `--hypothesis-seed` runs - [x] **Mutation check — the invariant still bites.** With the `self._probes_in_flight -= 1` line deleted from `StateMachine._record_probe` (a leaked probe slot), the scoped invariant fails within one run at the same assertion. Scoping removed the false alarm, not the sensitivity. - [x] The shrunk sequence is pinned as `test__probe_budget__failed_probe_reopens__next_round_starts_full`, as `tests/CLAUDE.md` requires — it asserts what *is* promised: the next probe round starts from a full budget. - [x] `uv run pytest --cov` — 549 passed, 100.00% - [x] `uv run prek run --all-files` clean ## Related issues Follow-up to #106 / #112. Independent of #113.
7 tasks
bagowix
added a commit
that referenced
this pull request
Aug 3, 2026
## Summary Prepare the `2.1.4` patch release. - bump the package version from `2.1.3` to `2.1.4` - move the current changelog entries from `[Unreleased]` into `2.1.4` - update changelog comparison links - update the release version in the comparison page - regenerate `docs/llms-full.txt` Everything in `[Unreleased]` is infrastructure, documentation and bug fixes — the correctness docs page, the OpenSSF badge, `griffe check`, mutation testing, pyrefly, the Codecov move, the supply-chain hardening, plus the coordinator lane `task_done()` fix and `EventListener` failure isolation. No public symbol was added or changed, so this is a patch, not a minor. ### Why now The `v2.1.4` tag was pushed while `interlock/version.py` still read `2.1.3`, so the release workflow built `2.1.3` artifacts and PyPI rejected the upload — that filename already exists and PyPI never allows reuse. The tag has been deleted locally and on the remote; it needs to be re-created on the merge commit of this PR so the build picks up the bumped version. ### Note on `docs/llms-full.txt` The regenerated file carries one change that is not from the version bump: #87 changed the migration-guide anchors in `docs/migration.md` but did not regenerate `llms-full.txt`, so the committed copy had drifted. Running `scripts/build_llms_full.py` brings it back in sync. `docs/comparison.md` keeps its "as of July 2026" label: that date records when the *other* libraries' versions were checked, and only interlock's own cell moves — the same way the 2.1.3 release handled it. ### The griffe gate had to be fixed first `griffe check` failed this PR on `Attribute value was changed: '2.1.3' -> '2.1.4'`. The job landed in #118 and this is the first release since, so it had never run against a version bump — it would have failed every release from now on, or forced a misleading `breaking-change` label onto each one. The `-f github` run still annotates the diff; the gate now reads a `oneline` run with that single finding filtered out. The filter matches only that attribute, in that file, for that one check, so it fails closed: if griffe rewords the message it stops matching and the job fails. Verified both ways locally — the bump alone passes, and a removed public export (`sync_timeout`) still fails it. ## Checklist - [x] Test suite passes with 100% coverage (583 passed, 2 skipped) - [x] `uv run ruff format --check` and `uv run ruff check` pass - [x] `uv run mypy`, `uv run pyright` and `uv run pyrefly check` pass - [x] Documentation updated (`docs/`) - [x] `CHANGELOG.md` `[Unreleased]` updated - [x] Release artifacts build successfully (`interlock_cb-2.1.4`, sdist + wheel) - [x] Commits follow Conventional Commits ## Related issues - Includes changes from #83, #97, #102, #103, #104, #105, #107, #113, #87
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two CI changes, both about making an existing guarantee visible instead of asserted.
pyrefly joins mypy and pyright as a third strict type checker — in the
qualitymatrix and in the pre-commit hooks. Three independent implementationsof the same type system disagree in the corners, and the corners are exactly
where a signature-preserving
ParamSpecdecorator lives; a check the other twomiss should fail before a release, not in a user's editor.
The package is clean under pyrefly's
strictpreset as it stands — no sourcechanges were needed.
missing-override-decoratoris the one error kind turnedoff (
[tool.pyrefly.errors]):typing.overrideis 3.12+ and the core carries notyping_extensionsdependency to backport it, so the transport overrides inintegrations/httpx2.pyandintegrations/requests.pycannot be decorated while3.11 is supported. The 4 existing
# type: ignorecomments are honoured assuppressions, not re-reported.
Coverage reporting moves to Codecov. The
coveragejob now writescoverage.xmlplus a JUnit report and uploads both, so a PR shows the per-filecoverage diff and the failing tests themselves rather than one total. The 100%
gate is unchanged and still enforced by pytest (
fail_underinpyproject.toml); the statuses in.github/codecov.ymlmirror it (project andpatch at 100%). This replaces
py-cov-action/python-coverage-comment-actionandthe badge branch it maintained — the job no longer needs
contents: writeorpull-requests: write, so every job inci.ymlnow runs oncontents: read.secrets.CODECOV_TOKENis configured in the repository.Test plan
uv run pyrefly check— 0 errors (4 suppressed)uv run prek run --all-files— all hooks pass, including the newpyreflyhookuv run pytest --cov --cov-report=xml --junitxml=junit.xml -o junit_family=legacy— 548 passed, 100.00% coverage, both reports well-formed XML
ruff format --check,ruff check,mypy,pyrightunchanged and greenPyreflystep passes on 3.11–3.14 and 3.14t, and bothCodecov uploads succeed (
fail_ci_if_error: true, so a silent failure turns the job red)Follow-ups (not in this PR)
python-coverage-comment-action-databranch is now unused and can be deleted.tyhas been an unused dev dependency since before this change; left alone here.Related issues
None — CI maintenance.