You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Split out of #107. That issue is about a CI job that resolves dependencies downward and runs the suite. This one is a static check that needs no resolver, no interpreter matrix and no test run — and it catches a defect the audit found in four repos.
The defect
A dependency declared as a bare name has no lower bound, so the declared range admits every version ever published. It reads as deliberate in a dependency list and is only visible when something resolves it downward.
It finds every instance at once. A resolver stops at the first wall, so the lower-bound audit reported one broken dependency per repo and stopped. The static scan shows db-retry has three unbounded dependencies and semvertag has five, not the one each that the audit surfaced. Fixing what the audit reported would leave most of them in place.
It is cheap. No venv, no matrix, no network. It is a parse of pyproject.toml and a regex. It can run in the existing lint-ci recipe and add no measurable time.
It prevents rather than detects.#107 tells you a floor is wrong after someone wrote it. This refuses the commit that omits one.
Scope decision needed
The published dependencies are clearly in scope: they are the contract users install against, and all four cases above are real defects.
Against: they are never published, a stale dev floor hurts nobody downstream, and 182 bounds is real churn to write and then to maintain.
My read: enforce on published dependencies now, and leave dev groups out until someone actually wants the one-step form in #107. Worth an explicit decision either way rather than letting the check's scope drift.
Implementation
No mechanism exists today for a check that spans repos — each repo carries its own copy of _checks.yml, and .github has no workflow that reaches into the others. Two options that fit the org as it is:
A small CLI, the way eof-fixer already works.modern-python already ships a single-purpose lint tool that every repo calls from lint-ci. A second one in that mould would be consistent, versioned, and fixable in one place.
A test per repo, the way tests/test_adr_citations.py and tests/test_invariant_census.py already assert repo-shape invariants. Cheaper to land, but it is 24 copies of the same twenty lines.
(1) is the better fit if this is worth doing at all; (2) is the faster way to find out whether it is.
Split out of #107. That issue is about a CI job that resolves dependencies downward and runs the suite. This one is a static check that needs no resolver, no interpreter matrix and no test run — and it catches a defect the audit found in four repos.
The defect
A dependency declared as a bare name has no lower bound, so the declared range admits every version ever published. It reads as deliberate in a dependency list and is only visible when something resolves it downward.
From modern-python/db-retry#52 and modern-python/semvertag#78:
Both fail to build, so those packages do not install at their own declared minimum, on any supported Python.
Current state, all 24 repos
Published dependencies —
[project] dependenciesand[project.optional-dependencies], excluding self-referential extras:typer,rich,semver,pydantic-settings,httpx2tenacity,sqlalchemy[asyncio],asyncpgpathspec[fastapi] fastapi,[faststream] faststream11 declarations across 4 repos. The other 20 repos are clean here.
Dev and lint groups: 182 bare entries across all 24 repos —
pytest,ruff,ty,eof-fixerand friends, almost none of which carry a bound anywhere.Why this is worth separating from #107
It finds every instance at once. A resolver stops at the first wall, so the lower-bound audit reported one broken dependency per repo and stopped. The static scan shows
db-retryhas three unbounded dependencies andsemvertaghas five, not the one each that the audit surfaced. Fixing what the audit reported would leave most of them in place.It is cheap. No venv, no matrix, no network. It is a parse of
pyproject.tomland a regex. It can run in the existinglint-cirecipe and add no measurable time.It prevents rather than detects. #107 tells you a floor is wrong after someone wrote it. This refuses the commit that omits one.
Scope decision needed
The published dependencies are clearly in scope: they are the contract users install against, and all four cases above are real defects.
The 182 dev/lint entries are a policy call:
UV_RESOLUTION=lowest-directcannot be used at all while they exist — it resolvespytestto 2.0.0 and dies at the build step. This is exactly why CI never resolves dependencies at their declared lower bounds, so stale floors ship unnoticed #107's proposal had to use a two-step install instead. Bounding them would make the simpler form usable.My read: enforce on published dependencies now, and leave dev groups out until someone actually wants the one-step form in #107. Worth an explicit decision either way rather than letting the check's scope drift.
Implementation
No mechanism exists today for a check that spans repos — each repo carries its own copy of
_checks.yml, and.githubhas no workflow that reaches into the others. Two options that fit the org as it is:eof-fixeralready works.modern-pythonalready ships a single-purpose lint tool that every repo calls fromlint-ci. A second one in that mould would be consistent, versioned, and fixable in one place.tests/test_adr_citations.pyandtests/test_invariant_census.pyalready assert repo-shape invariants. Cheaper to land, but it is 24 copies of the same twenty lines.(1) is the better fit if this is worth doing at all; (2) is the faster way to find out whether it is.
Related