Skip to content

deps: bump mpmath from 1.3.0 to 1.4.1 - #332

Merged
cdeust merged 3 commits into
mainfrom
dependabot/pip/mpmath-1.4.1
Aug 10, 2026
Merged

deps: bump mpmath from 1.3.0 to 1.4.1#332
cdeust merged 3 commits into
mainfrom
dependabot/pip/mpmath-1.4.1

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 1, 2026

Copy link
Copy Markdown
Contributor

Bumps mpmath from 1.3.0 to 1.4.1.

Release notes

Sourced from mpmath's releases.

1.4.1

Bug fixes:

  • Fix test_hexadecimal_with_libc_bulk(), see #1049 (Doug Torrance)
  • Keep available deprecated aliases for mpc/mpf_log() (Sergey B Kirpichev)
  • Use version_file option of setuptools-scm to keep version info, see #1048 (Sergey B Kirpichev)
  • Add workaround for test on s390x, see #1061 (Sergey B Kirpichev)

Full Changelog: mpmath/mpmath@1.4.0...1.4.1

1.4.0

Features:

  • Support underscores as digit separators per PEP 515, see #661 (Sergey B Kirpichev)
  • Add rationals converter for mpf's, see #666 (Sergey B Kirpichev)
  • Rewrite bernpoly/eulerpoly to avoid dependency on bernoulli(1) convention, see #700 (Sergey B Kirpichev)
  • Support base kwarg for from_str(), see #703 (Jonathan Warner, Sergey B Kirpichev)
  • Support randmatrix() for mp.iv and mp contexts, see #527 (Maximilian Gaukler)
  • Added rank() function for matrices, see #610 (Jan-Philipp Hoffmann)
  • Add plus flag to select the B_1 sign convention for bernoulli/bernfrac, see #724 (Jeremy Tan Jie Rui, Sergey B Kirpichev)
  • Add mpf.as_integer_ratio() method, support construction of mpf from Decimal objects, see #731 (Sergey B Kirpichev)
  • Expose lower/upper_gamma functions, see #740 (Sergey B Kirpichev)
  • Support mpc initialization from string, see #743 (Sergey B Kirpichev)
  • Support asinh/acosh/atanh in the fp context, see #750 (Sergey B Kirpichev)
  • Support binary/octal/hexadecimal string output, see #711 (Jonathan Warner, Sergey B Kirpichev)
  • Support pickling for matrices and mpi, see #761 (Sergey B Kirpichev)
  • Support matrix.__array__() dunder method, see #767 (Sergey B Kirpichev)
  • Support more number syntaxes, see #778 (Sergey B Kirpichev)
  • Run mpmath as a module for interactive work, see #773, #923, #931, #936, #939 and #954 (Sergey B Kirpichev)
  • Add signed option to to_man_exp(), see #783 (Sergey B Kirpichev)
  • Add fp.hypot, see #798 (Sergey B Kirpichev)
  • Support inf/nan's in ctx.almosteq(), #802 (Sergey B Kirpichev)
  • Implement mpf.__format__(), see #819, #831, #850, #859, #857, #862, #881, #944 and #966 (Javier Garcia, Sergey B Kirpichev)
  • Support conversion from scalar ndarray's, see #821 (Sergey B Kirpichev)
  • Support rounding modes in mpf.__format__, see #823, #831, #834 and #969 (Javier Garcia, Sergey B Kirpichev)
  • Support '%' presentation type for mpf, see #847 (Sergey B Kirpichev)
  • Support gmpy2-like rounding modes in to_str(), see #830 (Javier Garcia)
  • Implement 'a'/'A' formating types for mpf.__format__, see #841 and #870 (Sergey B Kirpichev)
  • Add mpc.__format__(), see #855 (Sergey B Kirpichev)
  • Now mpf.__round__() returns mpf, see #826 and #966 (Sergey B Kirpichev)
  • Support 'b' (binary) format type for mpf/mpc, see #867 (Sergey B Kirpichev)
  • Implement mpf.__floordiv__() and mpf.__divmod__(), see #873 (Sergey B Kirpichev)
  • Add parameters for MPContext constructor, see #876 and #963 (Sergey B Kirpichev)
  • Add MPFR-compatible aliases for rounding modes, see #892 (Sergey B Kirpichev)
  • Support negative indexes in matrix, see #897 (Riccardo Orsi)
  • Better introspection support for decorated functions, see #900 (Sergey B Kirpichev)
  • Add moving sofa demo, see #924 (Sergey B Kirpichev)
  • Support spherical Bessel functions (jn/yn), #935 (Sergey B Kirpichev)
  • Add pretty_dps context property to control number of printed digits, see #933 (Sergey B Kirpichev)

... (truncated)

Changelog

Sourced from mpmath's changelog.

--1.5.0-- Released TBD

Features:

  • Support special numbers in mpf_frexp() like math.frexp(), see #1081 (Sergey B Kirpichev)
  • Add ModAB rootfinding algorithm, see #1093 (Ayush Baranwal)
  • Add Brent root-finding algorithm, see #1103 (Ayush Baranwal)
  • Correct integral path of the lerchphi() to use Laplace transform integral, see #1109 (Sergey B Kirpichev)

Compatibility:

  • Drop support for CPython 3.9, see #1058 (Sergey B Kirpichev)
  • Remove deprecated math2 and rational modules, see #1057 (Sergey B Kirpichev)
  • Remove deprecated mp.mpnumeric alias, see #1057 (Sergey B Kirpichev)
  • Remove deprecated bitcount(), fp.is_special() and to/from_pickable() functions, see #1057 (Sergey B Kirpichev)
  • Drop DeprecationWarning for force_type kwarg for matrix(), see #1057 (Sergey B Kirpichev)
  • Use signed=True per default in to_man_exp(), see #1057 (Sergey B Kirpichev)
  • Use asc=True per default for polynomial functions, see #1057 (Sergey B Kirpichev)
  • Restrict libmp exports to public API, see #1089 (Sergey B Kirpichev)

Bug fixes:

  • Fix test_hexadecimal_with_libc_bulk(), see #1049 (Doug Torrance)
  • Keep available deprecated aliases for mpc/mpf_log() (Sergey B Kirpichev)
  • Use version_file option of setuptools-scm to keep version info, see #1048 (Sergey B Kirpichev)
  • Add workaround for test on s390x, see #1061 (Sergey B Kirpichev)
  • Fix signature of root(), see #1072 (Sergey B Kirpichev)
  • Speedup removal trailing zeros in _normalize/from_man_exp(), see #1074 (Fredrik Johansson and Sergey B Kirpichev)
  • Improve documentation about rounding in the mp context, see #1079 (Sergey B Kirpichev)
  • Correct to_float() conversion for double-rounding cases (e.g. subnormals), see #1082 (Sergey B Kirpichev)
  • Fix qr_solve() failure on well-conditioned matrices with zero pivot, see #1083 (Jam Balaya)
  • Clarify to_float() docstring, see #1087 (Sergey B Kirpichev)
  • Add extra precision for summation in mpf_hypot(), see #1088 (Sergey B Kirpichev)
  • Fix typo and function names for sin/cospi(), see #1091 (Sergey B Kirpichev)
  • Raise ValueError when same sign at interval boundaries in bisection rootfinding algorithm, see #1092 (Ayush Baranwal)
  • Correct interval update for Ridder's method, see #1096 (Sergey B Kirpichev)
  • Set dynamic maxsteps value for the bisect method, see

... (truncated)

Commits
  • c1131e2 Merge pull request #1053 from skirpichev/release-1.4.1
  • d3d3eb9 Update CHANGES
  • 629daff Add a workaround for test on s390x
  • 96272f3 Revert "Add backport action"
  • e129480 Test frozen package version (#1055)
  • 1ef88b4 Amend docstring for round_digits()
  • 650520b Use version_file option of setuptools-scm to keep version info
  • 501af4a Keep forever deprecated aliases for mpc/mpf_log()
  • 6887127 Set argtypes for sprintf from ctypes
  • f3e9cf0 Update CHANGES (free-threading support)
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file python Pull requests that update python code labels Aug 1, 2026
@cdeust
cdeust force-pushed the dependabot/pip/mpmath-1.4.1 branch from dc8cdad to 8a7ddbc Compare August 10, 2026 06:35
cdeust added a commit that referenced this pull request Aug 10, 2026
…migration

Two follow-ups the CI run on PR #331 caught immediately, both fixed at
the source rather than baselined/skipped:

- tests_py/infrastructure/test_stdio_late_response.py's test method
  exceeded the 40-line/method cap after the first extraction pass
  (the extracted helper itself was still over). Split further: a
  _RaceFixtures dataclass + _build_race_fixtures() groups the six-value
  setup into one call, leaving _drive_late_request_scenario() and the
  test method both under the cap.
- pyproject.toml gained exceptiongroup>=1.2.0 in a later edit than the
  `uv lock` run in the prior commit — CI's `uv lock --check` correctly
  caught the resulting drift. Re-locked (uv.lock unchanged in content,
  since exceptiongroup was already pinned transitively at the same
  version — only the marker condition and provenance changed) and
  regenerated the 5 requirements/*.txt files whose extras include the
  dev group.

Also adds .craftsmanship.conf (same fix as PR #332/#416, not yet on
this branch since it was cut before those merged): the local
zetetic-marketplace pre-commit hook has no auto-generated-file
detection and flags requirements/*.txt against the generic §4.1
500-line cap. Scoped skip for requirements/, matching the documented
auto-generated exception. Distinct from this repo's own CI gate
(scripts/check_craftsmanship.py), unaffected by this file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cdeust

cdeust commented Aug 10, 2026

Copy link
Copy Markdown
Owner

ZETETIC-REVIEW: REQUEST_CHANGES

Reviewed at head 8a7ddbc1977263590b22f464406c025c39d050d2. No benchmark/full suite executed per instruction — reviewed the diff plus independently verified the upstream compatibility claim.

Move 0 — ledger / seen-defect check

No unmapped paths, no un-issued "pre-existing/unrelated/out of scope" dismissals in the diff or commit message. Passes.

Stakes

Medium-High: touches 13 install sites (CI workflows, composite action, 3 Dockerfiles, ClusterFuzzLite) and a dependency-resolution override that changes what --require-hashes actually verifies repo-wide. Full review applied.

1. Is the mpmath override justified?

Partially, and the PR's framing overstates the case. I re-derived this independently rather than trusting the PR body, per the review's own instruction.

  • Confirmed via uv.lock: neither sympy nor mpmath is imported anywhere in this codebase (grep -rln "import sympy\|import mpmath" → empty). Both arrive transitively, exclusively through torch. This part of the PR's claim is correct and lowers the blast radius considerably — the override does not put Cortex's own numerical code on an unverified mpmath, only torch's internal (mostly symbolic, rarely evalf-heavy) use of sympy.
  • The bound is not "precautionary" in the sense the PR comment claims ("no known issue"). I read sympy's own issue tracker (mpmath 1.4.0 cannot be used sympy/sympy#29231) and the maintainer's (oscarbenjamin) direct testimony: the cap exists because sympy's evalf() genuinely depends on mpmath.libmp internals that mpmath 1.4.0 deprecated (mpf_log, direct _mpf_ tuple access, to/from_pickable, private mpq, removed sage backend). He reproduces it: sympy.log(2).evalf() under mpmath 1.4.0 emits DeprecationWarning: mpf_log is deprecated, use mpf_ln — a real, currently-observed incompatibility, just non-fatal today because mpmath 1.4.1 kept the deprecated alias (confirmed in its own changelog: "Keep available deprecated aliases for mpc/mpf_log()"). An mpmath 1.4.0 alpha previously broke import sympy outright (AttributeError: module 'mpmath' has no attribute 'rational' sympy/sympy#26273) — this is exactly why the cap exists, and it is not hypothetical.
  • So: "imports and evaluates correctly" (the PR's verification) is true and reproducible, but "precautionary bound, not a real incompatibility" is not an accurate characterization of what sympy's maintainers say about their own cap. Please correct the pyproject.toml comment to say the bound protects a currently-deprecated-but-not-yet-removed API surface, not a nonexistent one.

Blocking, correctness/robustness, not merely wording: override-dependencies = ["mpmath>=1.4.1"] is open-ended above. uv override-dependencies replaces the constraint the resolver uses for every requirer — it doesn't add a ceiling, it removes sympy's <1.4 ceiling entirely with no replacement ceiling. sympy's own maintainer states in the same thread that the next sympy release will cap at mpmath < 1.5, and that an mpmath prerelease once broke import sympy completely. A routine uv lock --upgrade (not even --upgrade-package mpmath) at any point in the future can silently pick up mpmath 1.5.0+, at which point this override — designed to fix one dependabot bump — reintroduces exactly the class of breakage this PR set out to avoid, and does so silently since --require-hashes no longer re-validates sympy's declared bound at any of the 13 install sites this PR just changed. That is an internal contradiction with the PR's own stated goal (reproducible, verified installs).

Required change: narrow the override to what was actually verified — either an exact pin (mpmath==1.4.1) or a bounded range matching the compatibility window sympy's maintainers describe (mpmath>=1.4.1,<1.5). Either is a one-line change.

2. --no-deps across 13 sites

Justified and narrower in effect than it first appears. requirements/*.txt are uv export-derived, hash-pinned, complete closures of uv.lock (verified by reading scripts/generate_pip_constraints.py — it exports the full resolved graph, not just direct deps). Once every transitive dependency's hash is already pinned in the file, pip's metadata-based re-derivation is redundant by construction: it can only ever reject a graph uv already fully resolved, which is exactly the failure mode this PR hit. --no-deps here means "trust the closure I was handed as complete," which is true for this file.

The residual risk is narrow and already implicitly covered by the existing --check step: if generate_pip_constraints.py or uv.lock ever produced an incomplete export (missing a transitive dependency rather than a version conflict), --no-deps would let that through silently as a ModuleNotFoundError at import time rather than an install-time failure — a shift in when the error surfaces, not a new failure mode --require-hashes was ever protecting against for this generated-file class. No action required; call this out for future reviewers rather than blocking on it.

3. Lockfile as source of truth / reproducibility

Confirmed: uv.lock diff is mpmath-only (4 lines), all 8 requirements/*.txt regenerated consistently (3 lines each, mpmath hash + version), .craftsmanship.conf change is a narrowly-scoped, well-justified addition of the documented §4.1 auto-generated-file exception for requirements/. --require-hashes still runs at every site; only the re-derivation from declared metadata is skipped, and only for the already-resolved closure files. Reproducibility is preserved.

Verdict rationale

Everything except the override's open upper bound is sound, sourced, and narrowly scoped. The unbounded override-dependencies is the one change that both contradicts the PR's own stated verification (which only checked 1.4.1, not "anything ≥1.4.1") and reopens the exact risk class (sympy/mpmath prerelease breakage) the PR's own cited evidence documents. Bound it and this is a clean approve.

Required to unblock

  • pyproject.toml: change override-dependencies = ["mpmath>=1.4.1"] to a bounded form (mpmath==1.4.1 or mpmath>=1.4.1,<1.5).
  • Correct the adjacent comment's "precautionary... not a real incompatibility" framing to reflect sympy's maintainer's documented reasoning (deprecated-API dependency, not a phantom bound).

🤖 Reviewed by code-reviewer (Sonnet 5), no benchmark run per instruction.

cdeust added a commit that referenced this pull request Aug 10, 2026
Review finding on PR #332: the prior comment called sympy 1.14.0's
`mpmath<1.4` bound precautionary, on the strength of one working
import+evalf call. The sympy maintainer's own issue tracker
(sympy/sympy#29231) says otherwise: sympy 1.14.0 has a real, active
dependency on a deprecated mpmath API (`mpf_log`), and an mpmath 1.4.0
ALPHA once broke `import sympy` outright before that got fixed. A
sample of one code path working is not proof of general compatibility
across sympy's much larger surface -- routing around a maintainer's
bound requires knowing what it protects, not just testing that it
doesn't (yet) bite the one call site checked.

The practical decision is unchanged: this repo never imports sympy or
mpmath directly (both arrive transitively through torch), so the
deprecated-API surface sympy still touches is never exercised here,
and the override remains safe to take. But it needed a ceiling:
`override-dependencies = ["mpmath>=1.4.1"]` had no upper bound, so a
routine `uv lock --upgrade` (not `--upgrade-package mpmath`) could pull
a version sympy's own maintainer has already flagged as unsupported --
they state the next sympy release (1.15) will cap at `mpmath<1.5`
specifically because of the alpha-0 breakage. Matched that ceiling:
`mpmath>=1.4.1,<1.5`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cdeust

cdeust commented Aug 10, 2026

Copy link
Copy Markdown
Owner

ZETETIC-REVIEW: APPROVE

Summary

Second-round review of dec4dec0. The first round's blocking finding is fixed: the pyproject.toml comment no longer calls sympy's mpmath<1.4 bound "precautionary" — it now correctly states, sourced, that sympy 1.14.0 has a real active dependency on a deprecated mpmath API (mpf_log) and that an mpmath 1.4.0 alpha once broke import sympy outright. The override now carries an upper bound (mpmath>=1.4.1,<1.5) instead of the previous open-ended floor.

Verification performed this round (all against primary sources, no benchmark/test run)

  • mpmath 1.4.0 cannot be used sympy/sympy#29231, read directly via gh api: every quoted string in the new pyproject.toml comment is a verbatim (or clearly-bracketed) match to oscarbenjamin's actual comments — "sympy 1.14.0 uses API that is deprecated in mpmath 1.4.0" (mpf_log), "mpmath 1.4.0 alpha 0 completely broke sympy to the extent that import sympy failed", and "I would say that the new release should cap mpmath < 1.5". The [sympy 1.15] bracket the comment adds is itself independently confirmed by a second comment from the same maintainer in the same thread: "There is still the question of what to use for the cap in sympy 1.15. I think it should be mpmath < 1.5." Not a fabricated attribution.
  • PyPI JSON API for sympy: latest is still 1.14.0, requires_dist still mpmath<1.4,>=1.1.0 — matches the comment.
  • Upper bound is structurally effective, not just documented: read the actual file at the PR head (git blob for 9c2e72a8...pyproject.toml, lines 201–246) — override-dependencies = ["mpmath>=1.4.1,<1.5"] sits correctly under [tool.uv], single entry, no duplicate/conflicting override elsewhere in the file. uv.lock's [manifest] overrides line mirrors it. This is a real PEP 508 range on the override itself (not a comment-only promise) — uv lock --upgrade cannot resolve past 1.5 without also editing this line.
  • Hash integrity: fetched mpmath==1.4.1's actual PyPI digests (pypi.org/pypi/mpmath/1.4.1/json) — the wheel and sdist sha256 hashes in every regenerated requirements/*.txt match exactly.
  • --no-deps mechanics (the second half of this PR, unchanged from round 1 and not in question this round): correctly scoped — added only to pip install --require-hashes -r <hash-pinned file> call sites, consistent with the existing rationale for the project's own -e . install.

Rules compliance (coding-standards.md)

Rule Status Evidence Action
§8 Source discipline pass pyproject.toml:220-241 — 3 # source: citations, all independently verified above, quotes accurate none
§9 anti-pattern (invented constants) pass upper bound <1.5 is not invented — traced to maintainer's own stated ceiling for sympy 1.15 none
§4 size limits n/a no function/class touched
§2 layer boundaries n/a build/CI/dependency files only

Stakes

Medium — CI/build infrastructure and a resolver override affecting every install path in the repo (not High: no auth/billing/crypto/schema/concurrency surface). Full Move 1/3/6-equivalent care applied via source verification, matching the CORRECTION → verify-before-accept escalation this round required.

Issues

Blocking

None.

Non-blocking

None — the round-1 finding (unbounded override + unverified "precautionary" characterization) is fully resolved and independently confirmed against the primary source, not just re-asserted.

Verdict

APPROVE.

cdeust added a commit that referenced this pull request Aug 10, 2026
Review finding on PR #332: the prior comment called sympy 1.14.0's
`mpmath<1.4` bound precautionary, on the strength of one working
import+evalf call. The sympy maintainer's own issue tracker
(sympy/sympy#29231) says otherwise: sympy 1.14.0 has a real, active
dependency on a deprecated mpmath API (`mpf_log`), and an mpmath 1.4.0
ALPHA once broke `import sympy` outright before that got fixed. A
sample of one code path working is not proof of general compatibility
across sympy's much larger surface -- routing around a maintainer's
bound requires knowing what it protects, not just testing that it
doesn't (yet) bite the one call site checked.

The practical decision is unchanged: this repo never imports sympy or
mpmath directly (both arrive transitively through torch), so the
deprecated-API surface sympy still touches is never exercised here,
and the override remains safe to take. But it needed a ceiling:
`override-dependencies = ["mpmath>=1.4.1"]` had no upper bound, so a
routine `uv lock --upgrade` (not `--upgrade-package mpmath`) could pull
a version sympy's own maintainer has already flagged as unsupported --
they state the next sympy release (1.15) will cap at `mpmath<1.5`
specifically because of the alpha-0 breakage. Matched that ceiling:
`mpmath>=1.4.1,<1.5`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cdeust
cdeust force-pushed the dependabot/pip/mpmath-1.4.1 branch from dec4dec to 56bae9e Compare August 10, 2026 10:34
@cdeust

cdeust commented Aug 10, 2026

Copy link
Copy Markdown
Owner

ZETETIC-REVIEW: APPROVE

Scope of this round

The head this reviews is 56bae9e3. The previous APPROVE on this PR was rendered against dec4dec0, a commit that no longer exists — a force-push rewrote the branch's history to resolve a conflict with an advanced main. That prior verdict is invalidated by construction (a verdict targets a diff, and that diff is gone), so this round re-verifies branch integrity from scratch rather than trusting the rewrite's own description of itself. The prior review's substance (sympy maintainer citation, <1.5 attribution to sympy 1.15, --no-deps validity across the thirteen install sites) is not re-litigated here — only what the rewrite could have damaged.

1. What did the rewrite carry forward, intact?

  • pyproject.toml: override-dependencies = ["mpmath>=1.4.1,<1.5"] — present, upper bound intact.
  • uv.lock: overrides = [{ name = "mpmath", specifier = ">=1.4.1,<1.5" }] — mirrored correctly, not just the pyproject side.
  • The sympy/sympy#29231 citation block in pyproject.toml, including the "sympy 1.15" attribution — present and unchanged.

Checked in the actual checked-out worktree at 56bae9e3, not the diff text alone (a conflict-resolution that silently took "their" side on this hunk wouldn't necessarily show as a diff-against-main anomaly if main itself never touched these lines — so the worktree file content was read directly).

2. What did the rewrite reintroduce or drop?

git merge-base HEAD origin/main resolves to e88e4e21 — which is exactly origin/main's current HEAD (the commit that merged #416, pydantic-core). Zero commits exist on main that are missing from this branch's ancestry. That's the direct answer to "did the rewrite drop something upstream had already fixed": it can't have, because the branch is fully caught up — not partially rebased, not missing any of the PRs you named (pydantic-core dependabot.yml entry verified present at lines 87–126 with the exact table from #416; no gate-reference or module-split regression visible).

gh pr diff 332 against origin/main touches only the mpmath-related surface: pyproject.toml, uv.lock, 8 requirements/*.txt, 5 CI/Docker --no-deps install-flag sites, .craftsmanship.conf, .clusterfuzzlite/build.sh. Nothing outside that footprint changed — no requirements/*.txt reverted to a prior state, no dependabot/gate entry reappeared from an earlier point in history.

3. Do the hash pins still hold?

mpmath 1.4.1's two hashes (dc4f0ea2..., efd6d1b7...) verified directly against PyPI's JSON API — exact match to both requirement-file entries. Checked all 8 requirements/*.txt files that carry mpmath: every one shows exactly the mpmath line changed (surrounding more-itertools/narwhals context untouched), confirming this was a targeted --upgrade-package mpmath regeneration, not a blanket relock that could have silently moved other pins.

4. Business gate

python3 scripts/check_craftsmanship.py --base origin/main on 56bae9e3Craftsmanship gate: OK. No override needed.

Verdict

The rewrite lost nothing and reintroduced nothing. merge-base(HEAD, origin/main) == origin/main is the load-bearing check here — the branch isn't merely "not obviously broken," it is fully rebased on the newest main with nothing outstanding. Combined with the intact upper bound, matching hash pins, and a green craftsmanship gate on the actual head, this is mergeable as-is.

cdeust added a commit that referenced this pull request Aug 10, 2026
…migration

Two follow-ups the CI run on PR #331 caught immediately, both fixed at
the source rather than baselined/skipped:

- tests_py/infrastructure/test_stdio_late_response.py's test method
  exceeded the 40-line/method cap after the first extraction pass
  (the extracted helper itself was still over). Split further: a
  _RaceFixtures dataclass + _build_race_fixtures() groups the six-value
  setup into one call, leaving _drive_late_request_scenario() and the
  test method both under the cap.
- pyproject.toml gained exceptiongroup>=1.2.0 in a later edit than the
  `uv lock` run in the prior commit — CI's `uv lock --check` correctly
  caught the resulting drift. Re-locked (uv.lock unchanged in content,
  since exceptiongroup was already pinned transitively at the same
  version — only the marker condition and provenance changed) and
  regenerated the 5 requirements/*.txt files whose extras include the
  dev group.

Also adds .craftsmanship.conf (same fix as PR #332/#416, not yet on
this branch since it was cut before those merged): the local
zetetic-marketplace pre-commit hook has no auto-generated-file
detection and flags requirements/*.txt against the generic §4.1
500-line cap. Scoped skip for requirements/, matching the documented
auto-generated exception. Distinct from this repo's own CI gate
(scripts/check_craftsmanship.py), unaffected by this file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cdeust added a commit that referenced this pull request Aug 10, 2026
* feat(mcp2): migrate off fastmcp onto the native mcp 2.0.0 SDK server

mcp 2.0.0 folds FastMCP's decorator API directly into the SDK
(mcp.server.mcpserver.MCPServer, the documented successor to
fastmcp.FastMCP) and fastmcp-slim (latest, 3.4.5) still declares
mcp<2.0, so the two cannot coexist in one lock. Adopting MCPServer
resolves the conflict and removes ~24 transitive packages from the
lock (keyring, secretstorage, jeepney, jaraco-*, openapi-pydantic,
websockets, watchfiles, email-validator, ...).

Mechanical surface (7 tool_registry_*.py files, mcp_progress.py,
mcp_prompts.py, tool_error_handler.py, handlers/_tool_meta.py):
FastMCP -> MCPServer in type annotations and imports; Context/ToolError
imports moved to mcp.server.mcpserver / mcp.server.mcpserver.exceptions.

Two architectural pieces mcp 2.0.0 has no drop-in equivalent for,
each resolved by direct empirical verification against the installed
package (not assumed from type signatures):

1. tool_profile_middleware.py — FastMCP's 4-hook Middleware
   (on_list_tools/on_call_tool/on_list_prompts/on_get_prompt) becomes
   ONE mcp.server.context.ServerMiddleware.__call__(ctx, call_next),
   dispatching on ctx.method. Rejecting a tools/call by raising before
   call_next (the naive port) loses the classified message — it skips
   MCPServer._handle_call_tool's own isError=True conversion and
   surfaces as a bare "MCPError: Internal server error". Fixed by
   constructing and returning a CallToolResult(is_error=True, ...)
   directly instead — the graceful, model-visible shape the original
   design intended (tool_error_handler.py's whole idiom). prompts/get
   has no in-band error shape, so that path still raises, but as
   mcp.shared.exceptions.MCPError specifically (the one class the
   dispatcher's error mapper recognizes and preserves the message for).
   Registered via MCPServer(middleware=[...]) at construction — no
   post-construction add_middleware() exists anymore.

2. stdio_transport.py (295 lines, the FastMCP-race workaround) —
   DELETED, not ported. Reproduced the exact scenario from its own
   removed regression test (deterministic anyio.Event synchronization,
   no sleeps) directly against bare mcp==2.0.0 in an isolated venv:
   the rewritten dispatcher (mcp.shared.jsonrpc_dispatcher
   .JSONRPCDispatcher.run) explicitly wraps the write-stream close
   OUTSIDE the task-group join, so a request still in flight at EOF
   always gets an explicit answer now -- the original silent-drop
   defect is fixed upstream. What is NOT reproduced: in-flight work is
   cancelled on EOF (not drained to completion), so the answer is a
   graceful CONNECTION_CLOSED error, not the handler's real result --
   a narrower but still-correct guarantee (no silent drop, ever) this
   session initially over-claimed as full parity before checking the
   actual response payload, not just its presence. Replaced with
   test_stdio_late_response.py, a positive regression pin for the
   verified (not assumed) contract. __main__.py's main() is now
   anyio.run(mcp.run_stdio_async) -- the SDK also owns lifespan entry
   internally now, and there is no more banner/PyPI-update-check
   ceremony to preserve (mcp 2.0.0 does neither).

Two further defects found only by full empirical round-trips, not by
reading signatures:

- mcp 2.0.0's MCPServer.tool() has no output_schema parameter at all --
  structured output is derived exclusively from the wrapped function's
  return TYPE ANNOTATION. Every tool_registry_*.py inner function
  returned bare `-> dict`, which mcp 2.0.0 does not structure (no
  Tool.output_schema, no structuredContent) -- a universal regression
  across all 43 tools, not just the 9 with a hand-authored outputSchema.
  Fixed at the root: `-> dict` -> `-> dict[str, Any]` (mechanical,
  43 occurrences) restores auto-derived structuredContent; a new
  apply_output_schemas() (handlers/_tool_meta.py) then overrides the
  auto-derived generic schema with each handler's hand-authored
  outputSchema by assigning tool.output_schema directly on the
  persistent internal Tool object (a cached_property, overridable via
  normal instance-__dict__ precedence) -- verified this persists across
  every subsequent tools/list call, unlike mutating the per-call
  wire-level mcp.types.Tool the old apply_param_docs() read from
  (also fixed to read the same persistent internal registry).
- mcp 2.0.0's func_metadata no longer parses Google-style docstring
  Args: sections for prompt-argument descriptions (verified: no such
  logic anywhere in mcp.server.mcpserver.utilities.func_metadata).
  mcp_prompts.py's three prompt functions now declare descriptions via
  Annotated[str, Field(description=...)] instead.

Test suite: 7046 of 7046 non-PG-gated tests pass (verified in an
isolated venv with the full stack including flashrank installed);
264 skip (PostgreSQL-gated, expected without a live PG). mcp.Client's
in-process transport wraps server errors in ExceptionGroup (anyio
task-group teardown) where fastmcp.Client raised directly -- test
assertions updated to unwrap; tests_py/test_mcp_prompts.py's helper
needs ExceptionGroup/BaseExceptionGroup, 3.11+ builtins, so
exceptiongroup>=1.2.0 (already a transitive anyio dependency) is now
declared directly in the dev extras rather than relied on by luck.

uv.lock + requirements/*.txt regenerated via `uv lock` +
scripts/generate_pip_constraints.py. .craftsmanship-baseline.json
surgically pruned (3 entries) for the deleted stdio_transport.py test
files only -- not regenerated wholesale, to avoid grandfathering
unrelated pre-existing debt the full-tree --write-baseline scan would
otherwise have swept in from files this change never touches.

Builds on investigation from an abandoned 2026-08-01 session (commits
ba0821b8/86158b0d on the local feat/mcp-2.0 branch, never merged) that
correctly identified both hard problems but did not verify its own
tool_profile_middleware.py port against a real mcp.Client round-trip
(it raises MCPError for tools/call too, which IS recognized and DOES
preserve the message -- but changes tools/call rejections from a
graceful isError=True result to a protocol-level error, a real,
unverified behavior change) and did not reach stdio_transport.py at
all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(mcp2): keep the craftsmanship gate and lockfile in sync with the migration

Two follow-ups the CI run on PR #331 caught immediately, both fixed at
the source rather than baselined/skipped:

- tests_py/infrastructure/test_stdio_late_response.py's test method
  exceeded the 40-line/method cap after the first extraction pass
  (the extracted helper itself was still over). Split further: a
  _RaceFixtures dataclass + _build_race_fixtures() groups the six-value
  setup into one call, leaving _drive_late_request_scenario() and the
  test method both under the cap.
- pyproject.toml gained exceptiongroup>=1.2.0 in a later edit than the
  `uv lock` run in the prior commit — CI's `uv lock --check` correctly
  caught the resulting drift. Re-locked (uv.lock unchanged in content,
  since exceptiongroup was already pinned transitively at the same
  version — only the marker condition and provenance changed) and
  regenerated the 5 requirements/*.txt files whose extras include the
  dev group.

Also adds .craftsmanship.conf (same fix as PR #332/#416, not yet on
this branch since it was cut before those merged): the local
zetetic-marketplace pre-commit hook has no auto-generated-file
detection and flags requirements/*.txt against the generic §4.1
500-line cap. Scoped skip for requirements/, matching the documented
auto-generated exception. Distinct from this repo's own CI gate
(scripts/check_craftsmanship.py), unaffected by this file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(mcp2): update the remaining hand-maintained fastmcp references

Real bugs surfaced by CI running against the mcp 2.0.0 migration, not
speculative cleanup:

- scripts/launcher_pins.py: BASE_PACKAGES still pinned fastmcp==3.4.5
  -- the plugin-bootstrap package list has no resolver available to
  catch drift itself, which is exactly why
  tests_py/scripts/test_launcher_pins_match_lock.py exists (compares
  this list against requirements/setup.txt on every PR) and is what
  caught this: `_BASE_PACKAGES disagrees with setup.txt`, failing
  every Test (Python 3.1x) leg on PR #331. Fixed to mcp==2.0.0.
- scripts/setup.py: install_deps()'s hand-written package list had the
  same fastmcp>=2.0.0 entry, unguarded by a test (no equivalent to the
  launcher_pins check exists for this file). Fixed to mcp>=2.0.0 --
  otherwise a fresh `setup.sh` run would install a package this repo
  no longer imports.
- scripts/verify_mcp_hosts.py: refactored main() (Fowler 2018 Ch. 6,
  Extract Function) into _build_parser/_resolved_command/_case_command/
  _run_one_case/_run_all_cases while touching this file for the
  PYTHONPATH fix -- the previous single function was already at the
  file's craftsmanship baseline (76 lines, nesting depth 5, both over
  CLAUDE.md's 40-line/3-level caps) and this PR's own edit was the
  trigger to fix it rather than add to it further. Both baseline
  entries pruned since neither violation reproduces anymore.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(mcp2): shrink verify_mcp_hosts.py functions back under the size caps

The prior commit's refactor and PYTHONPATH-fix comment pushed two
functions over CLAUDE.md's 40-line/method cap again (caught by CI's
Craftsmanship Gate on push, not locally -- the pre-commit hook and the
CI gate agree, this was a same-session miss, not a policy gap):

- _environment(): the PYTHONPATH rationale moved from a 17-line inline
  comment to the module docstring's new "Environment isolation"
  section, leaving a two-line pointer at the call site.
- _build_parser(): split into _add_selection_arguments (which host
  identities/profiles a run exercises) and _add_runtime_arguments (how
  each case is driven) -- the same split the module's own two concerns
  already implied.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(mcp2): drain before signalling shutdown in the stdio host harness

PR #331's red CI was not a Cortex regression and not a race the mcp 2.0.0
SDK gets wrong: it was our harness asking for a guarantee the MCP protocol
never grants.

Model. Order the events of one batch: arrive(r) -> accept(r) -> begin(r)
-> produce(r) -> enqueue(r) -> emit(r), against EOF -> readclose ->
cancel -> join -> wclose. Serial reads over rendezvous streams force
accept(r) -> EOF -> cancel for every request in the batch, and enqueue(r)
-> emit(r) holds because stdout_writer lives outside the dispatcher's task
group. Nothing orders enqueue(r) before cancel. The guarantee that is
therefore nowhere established -- and that the harness assumed -- is the
ANSWER OBLIGATION FOR AN ACCEPTED REQUEST: for every r the dispatcher takes
off the read stream, exactly one frame carrying r's id reaches the wire.

Mechanism, read from the pinned wheel (sha256 1cb4c75d...49d6, the hash
uv.lock pins), not from its docs: JSONRPCDispatcher._handle_request sets
answer_write_started on the line BEFORE awaiting the response write, and
the shutdown arm declines to send CONNECTION_CLOSED whenever that flag is
set ("prefer possibly-zero answers over possibly-two"). A write cancelled
at MemoryObjectSendStream.send's entry checkpoint provably never delivered,
yet counts as possibly sent -- so the request settles with no frame at all.
Instrumented trace: begin(5) write_result-enter(5)
send-cancelled-at-entry-checkpoint(5,CancelledError), then silence. This
corrects the prior hypothesis (a handler task cancelled before its first
scheduling turn); the handler ran to completion.

Verdict. The SDK promises nothing here, and MCP 2025-06-18 §Lifecycle >
Shutdown > stdio makes closing stdin the shutdown signal with no drain
phase defined, so an accepted request is owed nothing once EOF lands. The
defect is in the client: subprocess.run(input=...) signals shutdown before
reading a single response. Reproduced against a BARE mcp 2.0.0 server with
zero Cortex code -- ids 4 and 5 of a six-frame batch got no frame, 5/5 runs;
the same batch drained first, 5/5 runs, loses nothing.

Change. scripts/mcp_host_client.py owns the exchange and keeps stdin open
until every expected id has arrived, closing it only then; synchronisation
is by event (a response line, or stdout EOF), never by elapsed time -- the
caller's --timeout survives only as a watchdog that kills a wedged child
and never decides a verdict. stderr goes to a file, not an undrained pipe.
verify_mcp_hosts.py drops to 276 lines, so its baselined file-size entry is
pruned. mcp_server/__main__.py's main() comment claimed mcp 2.0.0 had the
drain invariant built in; it does not, and the comment now says why the
workaround is still not restored (a drain in our transport would let a
wedged handler hold shutdown hostage, and real hosts never need it).

Tests. test_stdio_eof_drain.py forces both orderings deterministically over
one in-memory pair -- park the response write, then EOF: no frame; drain,
then EOF: every real result. test_mcp_host_client.py pins that stdin is
provably still open at each response read. test_stdio_late_response.py's
docstring generalised one green interleaving into "no silent drop, ever";
that inference is retracted, with the scope it actually covers spelled out.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(mcp2): follow the harness split through its callers, and type the gate

Two CI failures on the previous commit, both real:

- tests_py/scripts/test_verify_mcp_hosts.py still imported `_environment`
  and `_responses` from scripts/verify_mcp_hosts.py, which no longer owns
  them. Retargeted at scripts/mcp_host_client.py; `_responses(stdout: str)`
  became `absorb(line, responses)` because the exchange reads one line at a
  time -- it must decide the batch is answered before it may close stdin,
  so it cannot wait for the whole stream. Added the case that change
  introduces: a blank line (the trailing newline whole-stream `splitlines()`
  never yielded) is not a malformed frame.
- The sibling import is now `scripts.mcp_host_client` under one canonical
  name, with the repo root put on sys.path when this module runs as a
  script. A bare `import mcp_host_client` would give the executed script and
  an importing test two distinct module objects -- and two distinct
  `ContractError` classes, so `pytest.raises` would miss the real one. That
  dual-identity trap is already documented in
  tests_py/scripts/_craftsmanship_support.py for its own siblings.

Also fixed, pre-existing on this branch and blocking the Type Check gate:
mcp_server/tool_profile_middleware.py read `params["name"]` as `Any | None`
and handed it to `allows()`/`is_available()`, both typed `str`. `_requested_name`
now collapses a missing or non-string name to `""` -- which no profile lists,
so both gates refuse it exactly as they already refused an unknown name.
Behaviour is unchanged on every input; only the type is now honest.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(mcp2): pin the drain rule's degenerate cases

The exchange's termination argument has to hold at the corners, not only on
the happy batch: a server that answers nothing (EOF before any frame) must
report nothing and close, never block; a frame arriving after the last
awaited id must still be parsed and contract-checked rather than discarded
by the loop's exit; and ids are awaited as a SET, since mcp 2.0.0 runs
handlers concurrently and does not answer in request order.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: cdeust <cdeust@icloud.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
cdeust added a commit that referenced this pull request Aug 10, 2026
Review finding on PR #332: the prior comment called sympy 1.14.0's
`mpmath<1.4` bound precautionary, on the strength of one working
import+evalf call. The sympy maintainer's own issue tracker
(sympy/sympy#29231) says otherwise: sympy 1.14.0 has a real, active
dependency on a deprecated mpmath API (`mpf_log`), and an mpmath 1.4.0
ALPHA once broke `import sympy` outright before that got fixed. A
sample of one code path working is not proof of general compatibility
across sympy's much larger surface -- routing around a maintainer's
bound requires knowing what it protects, not just testing that it
doesn't (yet) bite the one call site checked.

The practical decision is unchanged: this repo never imports sympy or
mpmath directly (both arrive transitively through torch), so the
deprecated-API surface sympy still touches is never exercised here,
and the override remains safe to take. But it needed a ceiling:
`override-dependencies = ["mpmath>=1.4.1"]` had no upper bound, so a
routine `uv lock --upgrade` (not `--upgrade-package mpmath`) could pull
a version sympy's own maintainer has already flagged as unsupported --
they state the next sympy release (1.15) will cap at `mpmath<1.5`
specifically because of the alpha-0 breakage. Matched that ceiling:
`mpmath>=1.4.1,<1.5`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cdeust
cdeust force-pushed the dependabot/pip/mpmath-1.4.1 branch from 56bae9e to c85fd5f Compare August 10, 2026 15:47
cdeust and others added 3 commits August 10, 2026 19:01
… overrides

sympy 1.14.0 (latest) declares `mpmath<1.4,>=1.1.0` — a precautionary
upstream bound, not a real incompatibility: mpmath 1.4.1 imports and
evaluates correctly under sympy 1.14.0 (verified in an isolated venv,
`sympy.sqrt(8).evalf()` -> 2.82842712474619). Added `[tool.uv]
override-dependencies` in pyproject.toml so uv's resolver reaches the
newer release instead of staying frozen behind it.

That alone did not turn CI green. uv's override only affects uv's own
resolution; the exported `requirements/*.txt` files carry no way to
represent it, so `pip install --require-hashes -r <file>` re-derives
dependency satisfaction from each package's own metadata and rejects
mpmath 1.4.1 against sympy's declared `mpmath<1.4` bound — reproduced
locally: the same file installs cleanly with `--no-deps` added, since
these hash-pinned exports are already uv's complete, resolved closure
and pip has no re-resolution left to do. Added `--no-deps` to every
`pip install --require-hashes -r requirements/*.txt` call site (CI
workflows, the shared test-suite composite action, all three
Dockerfiles, and the ClusterFuzzLite build script) — the same pairing
the local editable install already used, and for the same reason.

uv.lock and requirements/*.txt regenerated via
`uv lock --upgrade-package mpmath` + `scripts/generate_pip_constraints.py`;
diff is mpmath-only. Verified: repo's craftsmanship gate, ruff
check/format, constraint-generator `--check`, and the
constraint/parity/typecheck-env test suites all pass locally.

Also adds .craftsmanship.conf: the local zetetic-marketplace pre-commit
hook (craftsmanship-checker.sh, distinct from this repo's own CI gate)
has no auto-generated-file detection, so it flagged requirements/*.txt
against the generic §4.1 500-line cap. Scoped skip for requirements/,
matching the documented auto-generated exception.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review finding on PR #332: the prior comment called sympy 1.14.0's
`mpmath<1.4` bound precautionary, on the strength of one working
import+evalf call. The sympy maintainer's own issue tracker
(sympy/sympy#29231) says otherwise: sympy 1.14.0 has a real, active
dependency on a deprecated mpmath API (`mpf_log`), and an mpmath 1.4.0
ALPHA once broke `import sympy` outright before that got fixed. A
sample of one code path working is not proof of general compatibility
across sympy's much larger surface -- routing around a maintainer's
bound requires knowing what it protects, not just testing that it
doesn't (yet) bite the one call site checked.

The practical decision is unchanged: this repo never imports sympy or
mpmath directly (both arrive transitively through torch), so the
deprecated-API surface sympy still touches is never exercised here,
and the override remains safe to take. But it needed a ceiling:
`override-dependencies = ["mpmath>=1.4.1"]` had no upper bound, so a
routine `uv lock --upgrade` (not `--upgrade-package mpmath`) could pull
a version sympy's own maintainer has already flagged as unsupported --
they state the next sympy release (1.15) will cap at `mpmath<1.5`
specifically because of the alpha-0 breakage. Matched that ceiling:
`mpmath>=1.4.1,<1.5`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous push shipped placeholder (--ours) conflict resolutions
for the 9 requirements/*.txt files from an earlier rebase, never
replaced by the actual regeneration against the merged uv.lock before
commit — so mpmath silently reverted to 1.3.0 in the exports while
uv.lock correctly carried 1.4.1. python scripts/generate_pip_constraints.py
--check now passes; mpmath 1.4.1's two hashes re-verified against PyPI.

Co-Authored-By: Claude <noreply@anthropic.com>
@cdeust
cdeust force-pushed the dependabot/pip/mpmath-1.4.1 branch from c85fd5f to 0e1dbe1 Compare August 10, 2026 17:04
@cdeust

cdeust commented Aug 10, 2026

Copy link
Copy Markdown
Owner

ZETETIC-REVIEW: APPROVE

Fresh verdict on head 0e1dbe1c. Any earlier approval on this PR predated three pushes and did not speak for this diff.

mpmath 1.3.0 → 1.4.1, plus the change that made it mergeable.

The recurring Lint failure was not a formatting problem. The first diagnosis — ruff format — was wrong, and reached by analogy with #415 rather than by reading the job. The real cause: nine requirements/*.txt exports disagreed with uv.lock. The bump's resolution had regenerated them in the working tree and never staged them, so CI installed from files describing a resolution that no longer existed. Fixing the export files, not the formatter, is what closed it.

A second trap was caught on the way: a local run went green against a stale ruff 0.15.20 living in an unrelated virtualenv, while CI pins 0.16.0. A false green from the wrong binary is indistinguishable from a real one — the run that matters is the pinned one.

Green: 23 pass, 0 fail, 2 skipping — including the Lint job that failed on the three previous heads, which is what makes this a discriminating signal rather than a quiet one.

Scope check: a dependency bump carries a dependency bump. The Docker Smoke stdin-close race found while triaging this batch was deliberately kept out and is being handled on its own contract.

@cdeust
cdeust merged commit bad7b98 into main Aug 10, 2026
25 checks passed
@cdeust
cdeust deleted the dependabot/pip/mpmath-1.4.1 branch August 10, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant