Skip to content

packaging: every extra but mcp is unbounded, and the lockfile hides the next 2.0 #103

Description

@Shashankss1205

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.

Activity

Shashankss1205 commented on Sep 26, 2026

@Shashankss1205
CollaboratorAuthor

Closed by #129, with #116 having covered the first half.

Verified by running the job rather than trusting the if: condition. upstream-drift is gated to schedule/workflow_dispatch, so it is skipped on pull requests and would have merged never having executed. Dispatched on the branch, it re-resolved every range from scratch and moved a lot — 1,926 lines of uv.lock, including two major bumps:

Updated websockets v15.0.1 -> v16.1.1
Updated xxhash      v3.8.1  -> v4.0.1
...
ok: 132 modules imported from wheel 0.1.8

Suite green and the wheel walk clean against those. So as of today the package survives the newest release of every extra — which is the thing this issue said nothing could tell you.

What now exists

Acceptance criterion
resolve ignoring uv.lock, newest release of every extra ✅ uv lock --upgrade (#116)
on a schedule ✅ Mondays 06:00 UTC (#116)
imports every subpackage from the built wheel ✅ #129
reports where a maintainer sees it ✅ opens/updates an issue (#116)
without blocking unrelated pull requests ✅ gated to schedule/workflow_dispatch

The walk now lives in scripts/wheel_import_walk.py, shared by build (against what uv.lock pins) and upstream-drift (against what upstream has since released), rather than as two copies of a heredoc.

It caught something on its first run, which is the best argument for it: pointed at the wheel then in dist/, it reported grapharc.examples.plan_research missing — correct, that wheel predated #115.

Still open, on purpose: the ceilings

The eight unbounded extras are unchanged. This issue argues a bound should be "a ceiling with a reason attached, not a reflex", and adding eight at once is the reflex — it would also refuse users upgrades that are fine, as websockets 16 and xxhash 4 just demonstrated. Detection is closed; if you want a pinning policy, that is worth its own issue with the reason recorded per pin, the way #101's mcp pin documents porting off FastMCP.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions