-
Notifications
You must be signed in to change notification settings - Fork 13
packaging: every extra but mcp is unbounded, and the lockfile hides the next 2.0 #103
Description
Split out of #101, which fixed one instance of this and named the general case as separate work.
What happens
Every optional dependency except mcp is declared with a lower bound and no ceiling:
langchain-openai>=0.2 # openrouter, openai, novita, ollama fastapi>=0.115 # server uvicorn>=0.32 # server real-ladybug>=0.15.3 # ladybug anthropic>=0.40 # api slack-bolt>=1.20 # slack opentelemetry-api>=1.27 # otel opentelemetry-sdk>=1.27 opentelemetry-exporter-otlp-proto-http>=1.27
Each is one major release away from the failure #101 documented: mcp>=1.2 resolved to 2.0.0 for anyone installing fresh, mcp.server.fastmcp no longer existed there, and grapharc[mcp] was broken on arrival.
Why nothing catches it
uv.lock pins the working versions, so the dev environment and every local pytest resolve to what already works. Only a resolver starting from pyproject.toml — that is, a real user running pip install grapharc[...] — sees the new major. The gap is structural, not an oversight: the lockfile's whole job is to keep development reproducible, and that is exactly what hides this.
CI's clean-environment wheel check is the one job that caught #101, and it caught it only because the broken import made a subpackage unwalkable. An extra whose SDK changed shape without breaking import would sail through.
What to consider
A scheduled job (weekly, plus workflow_dispatch) that resolves and installs from pyproject.toml with --upgrade rather than from the lockfile, then imports every subpackage the way ci.yml's build job already does. It must not gate pull requests — an upstream release is not a reason to redden someone's unrelated PR — so it should open or update an issue on failure instead.
Worth deciding alongside it: whether new extras get a ceiling by default. #101's pin is documented as deliberate (raising it means porting off FastMCP), which is the right shape for a bound — a ceiling with a reason attached, not a reflex.
Acceptance criteria
A resolve that ignores uv.lock and takes the newest release of every extra runs on a schedule, imports every subpackage from the built wheel, and reports a failure somewhere a maintainer will see it without it blocking unrelated pull requests.