Skip to content

CI never resolves dependencies at their declared lower bounds, so stale floors ship unnoticed #107

Description

@lesnik512

What

No repo in the org ever installs its dependencies at the lower bound of what it declares. Every gate — PR checks and the daily scheduled check alike — resolves highest. A floor that is a lie is therefore invisible until a user hits it.

24 repos have a _checks.yml. None of them has a lower-bound job.

Why now — three instances in one day, 2026-09-20

  1. faststream-outbox declared faststream>=0.7.1 while the code needed 0.7.6. import faststream_outbox failed outright on 0.7.5 with TypeError: TestOutboxBroker.__init_subclass__() takes no keyword arguments. Caught only because someone asked whether the work was backward compatible; fixed in chore(deps): adapt to faststream 0.7.6 and raise its floor faststream-outbox#182.
  2. faststream-redis-timers and faststream-concurrent-aiokafka were both capped <0.7.6 and then re-floored within the hour (redis-timers#84 → Relative links in README.md 404 on the PyPI package page #85, aiokafka#80 → docs: stop asserting the PR-body shape in AGENTS.md #79). Floor-range edits made under time pressure, with nothing verifying either end.
  3. faststream-outbox declares asyncpg>=0.29 and claims support for Python 3.11–3.14. asyncpg 0.29.0 ships wheels for cp38–cp312 only, but declares requires-python >=3.8.0, so on 3.13+ uv falls through to the sdist and the build fails. The first release with cp313 wheels is 0.30.0. That floor is unsatisfiable on half the supported Python range, today, and nothing reports it.

Item 3 was found by running the proposed job by hand. It is not hypothetical.

Proposal

A lowest job in _checks.yml, alongside the existing matrix. The naive form does not work — see below — so the shape that does:

uv venv
uv pip install --resolution lowest-direct ".[all]"   # runtime deps pinned to their declared floors
uv pip install pytest pytest-asyncio pytest-cov      # test tooling at current resolution
uv run --no-sync pytest --no-cov

Verified on faststream-outbox at Python 3.11:

alembic            1.13.0
asyncpg            0.29.0
fastapi            0.95.0
faststream         0.7.6
opentelemetry-api  1.20.0
prometheus-client  0.19.0
sqlalchemy         2.0.0
typing-extensions  4.12.0
-> import OK

Every runtime dependency sits on its declared floor while the test tooling stays current.

Why not just set UV_RESOLUTION=lowest-direct on the existing job

Because lowest-direct applies to every direct dependency, dev groups included, and all 24 repos declare dev/lint dependencies with no version specifier at all:

repo unfloored dev/lint deps
that-depends 12
httpware, modern-di-faststream 10
modern-di, modern-di-grpc 9
db-retry, modern-di-aiohttp, modern-di-fastapi, modern-di-starlette, modern-di-typer, semvertag 8
the remaining 13 5–7 each

pytest with no floor resolves to 2.0.0, which fails to build, and the job dies before it tests anything. Same for asgi-lifespan (0.0.1) and friends. Splitting the install in two sidesteps this entirely and keeps the job about what it is actually for: the runtime contract we publish.

Adding floors across every dev group would also work, but it is 24 repos of churn to fix a problem the two-step install does not have.

Caveats worth deciding on

  • Python version. The job has to pick one. Old floors frequently predate the newest interpreters (item 3 above), so running it on the lowest supported Python is the honest choice — a floor only has to be installable somewhere in the declared range, and pinning the job to 3.14 would report failures that are not really floor lies.
  • Repos with service dependencies (faststream-outbox, faststream-redis-timers, faststream-concurrent-aiokafka, db-retry) need their Postgres/Redis/redpanda service block on this job too, or the job runs unit tests only. Unit-only still catches the import-time failures, which is what items 1 and 3 were.
  • Rollout. Worth doing on one repo first and reading the result before the other 23. faststream-outbox is the obvious candidate: it is the repo where two of the three instances were found, and item 3 means it will fail immediately with something real.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageMaintainer needs to evaluate this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions