fix(deploy): upgrade every keel distribution, and verify all of them - #243
Merged
Conversation
The documented upgrade installed `keel_trader` alone. Its workspace siblings were required without a version, so an already-installed older `keel-core` satisfied `keel-core` and was never touched: `~/keel` ran `keel-trader 0.5.7` against `keel-core 0.5.5` across two releases. `keel --version` reported the new number throughout, because it reports that one distribution and nothing else -- a verification step structurally blind to the failure it is trusted to rule out. Three changes, each of which fixes it alone: - README: name all four production wheels by path. A wheel path is a direct requirement, installed whatever is already there. Not `Release/*.whl`: the release ships every workspace wheel, including the dev-only fake venue and Robinhood's Ed25519 stack. - Pins: every intra-workspace dependency is `==` the workspace version, verified to land in the built wheel's `Requires-Dist`. `tests/test_packaging.py` fails the build if a bump leaves one behind. - `keel versions`: prints the build identity plus every keel distribution in the venv and exits non-zero when they disagree, or when `keel-broker-fake` is present in a release build. `--version` now warns and points at it.
Merged
eaitbrahim
added a commit
that referenced
this pull request
Aug 11, 2026
Patch bump: packaging and documentation only, no behaviour change. 0.6.0's deploy exposed that the documented install upgraded `keel_trader` alone and silently left `keel-core` / `keel-broker-*` behind -- the real deployment was found running keel-trader 0.5.7 against keel-core 0.5.5, across two releases, because the dependencies were unpinned and `keel --version` reports only the keel-trader distribution and so could not see it. #243 pinned every intra-workspace dependency to the exact version, added `keel versions` (which exits 1 on a partial install), and corrected the README. This is the first release whose wheels CARRY those pins, so it is also the first one whose install is self-correcting: `Requires-Dist: keel-core==0.6.1` forces the sibling upgrade regardless of how the wheel is installed. `tests/test_packaging.py`, added by the same PR, is what kept this bump honest -- bumping the root version alone failed it twice before the pins were updated. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Found while deploying v0.6.0. The
~/keelvenv, immediately before the install:The documented step installs
keel_traderonly. Its siblings are required with no version, sothe
keel-core 0.5.5already on disk satisfiedkeel-coreand was never touched. The engine rannew code against old libraries for two releases.
The verification step could not see it. The README calls
keel --version"the check thatmatters"; it reports the
keel-traderdistribution's version and nothing else, so it printed0.6.0whilekeel-coresat at0.5.5. A check blind to the failure mode is worse than none,because it is trusted.
Reproduced end to end from the real wheels (0.5.5 set built from
2a58ffbwith the versionsrewritten, 0.6.0 set built from
2a58ffbunchanged), running exactly what the README said:1. The install command
A wheel path is a direct requirement — that exact file is installed regardless of what is
already there — so naming all four is what actually moves them.
--find-links Releasestays, nowresolving the pinned siblings locally rather than from PyPI where they do not exist. Installing
by path rather than by bare name is unchanged and still explained:
keelon PyPI is anunrelated project.
Deliberately not
Release/*.whl, the obvious route.uv build --all-packagesbuilds everyworkspace member and
gh release create dist/*publishes all of them, so the glob installs twodistributions production must not have. Measured:
The four named wheels are
keel-trader's whole dependency closure — nothing more, nothing less.2. Pins — decided yes, with wheel-metadata evidence
Every intra-workspace dependency is now
==the workspace version, in the root and in all fourpackage
pyproject.tomls.[tool.uv.sources] workspace = truegoverns resolution during development and says nothing aboutpublished metadata, so I built and read the wheel rather than assuming. Before:
After:
The constraint lands, and the pin alone fixes the bug for anyone who ignores the README — the
old command against the new metadata, from the same 0.5.5 starting state:
Why pin, given the README fix already works. The README binds only the person reading it. The
pin binds pip. These are not third-party dependencies with independent release cycles — they are
cut from this repo, at one version, in one
uv build --all-packages, so==states a fact ratherthan guessing at compatibility, and there is no version skew it would wrongly forbid. The cost is
that a bump must move the pins; the bump commit already edits
versionin all five files, so thisis incremental work on a commit that exists rather than a new obligation, and
tests/test_packaging.pyfails the build if a pin is left behind — the failure mode of a forgottenpin is otherwise silent (the wheel still builds and still installs, it just stops forcing the
upgrade).
>=was rejected: it forbids an older sibling but not a mixed install, and mixed isthe thing that happened.
uv.lockis unchanged — workspace sources already resolved to the members.3. A verification step that can fail
New
keel versions, registered inkeel/cli.py. It prints the same build-identity line, thenevery
keel-*distribution in the running interpreter's environment, and exits non-zero when theydisagree. No config, no database, no network, so a non-zero exit is unambiguous.
Healthy — the deployment after the new install command:
Mismatched — same venv,
keel-coreforced back to 0.5.5 with--no-deps(the pin has to bebypassed on purpose now):
keel --versionon that same venv now says so too, on stderr, leaving its stdout string exactlywhat it was (the release workflow greps it):
It ships in the wheel, not in
scripts/.scripts/is operator tooling and is not packaged;a deployment is a
.venvbeside aRelease/directory with no checkout of this repo, so a scriptthere could not be run without first fetching it. The check has to travel inside the artifact it
is checking. The rules live in
keel.version.InstallReportas a pure value, tested withoutinstalling anything; the command is rendering and an exit code. The release workflow now runs
keel versionsagainst the wheels it just built, so a release cannot publish a set that disagreeswith itself.
keel-broker-fakeIt should not be there and the check now says so: a release build with
keel-broker-fakeinstalled exits 1 with
uv pip uninstall --python .venv keel-broker-fake. A checkout does notfail — that is exactly where the fake belongs, and a check that cried wolf on every developer's
machine would be ignored by the time it mattered.
tests/test_packaging.pyalso asserts nothingin the workspace can pull it in as a runtime dependency. The dev group is untouched.
It is inert today (nothing calls
load_broker()), but it registers afakevenue underkeel.brokers, and "inert" is a property of this release rather than of the package. The READMEand the RELEASING asset table now say not to install it, which is also the reason the install
command names wheels instead of globbing.
Gates
Baseline on
origin/main, re-measured in this worktree, was 2669 passed / 1 skipped; +27 tests,same single expected skip.
No version bump here — that stays a separate reviewed change.
docs/experiments/untouched.