Repository navigation
build(deps): set runtime floors to the oldest tested versions so pip stops resolving 0.15.0 - #1092
Open
pengfei-threemoonslab wants to merge 3 commits into
Open
pengfei-threemoonslab wants to merge 3 commits into
pengfei-threemoonslab wants to merge 3 commits into
Conversation
… reports a dependency below its floor The runtime floors had tracked the newest upstream release, so pip install agents-shipgate beside an agent project's ordinary pins quietly installed an old release: 0.15.0 with cryptography==46.0.7 or packaging==24.2, and 0.11.0 beside crewai==1.15.27 (pydantic<2.13). - pyproject.toml: cryptography>=44.0.3, packaging>=24.2, pydantic>=2.12.5,<3, pyyaml>=6.0.2,<7, ruamel.yaml>=0.18.10,<1, typer>=0.20.0,<1. The cryptography and packaging caps are dropped. typer is 0.20.0, not 0.16.0: the root command loader reads Typer.suggest_commands, added in 0.20.0. The full suite passes at exactly these floors (schema-generator and wall-clock failures aside, see the PR). - scripts/dependency_floors.py: `coinstall` (pip --dry-run --report against a metadata-only wheel of this tree, per scenario), `compile` (uv pip compile --resolution lowest-direct, failing unless every runtime dependency lands exactly on its floor) and `installed`. - ci.yml: a dependency-floors job runs all three, installs the lowest-direct closure with hashes, and runs the dependency-heavy tests, every sample golden and doctor there. - dependabot.yml: versioning-strategy increase-if-necessary, so updates move the locks and never a floor. lockfile-only would stop Dependabot proposing the constraints/*.txt updates at all, because it reads them as requirement files. - CONTRIBUTING.md says when a floor may move. - doctor --json: mismatches[] gains dependency_below_declared_floor (error) and, for a source checkout, dependency_differs_from_lock (warning), read from importlib.metadata against the running build's declarations. - constraints/dev.txt: recompiled with uv 0.13.1; only its declaration block changed. Closes #1028
…15-minute cap The first run took 1m44s: 19 s for the three pip dry runs and 55 s for the 698 tests. The 20-minute cap had been sized for a cold download cache that GitHub's runners do not have.
Contributor
Author
🤖 Engineering review — implementer self-reviewI reviewed Found and fixed before opening
Left for the owner
|
Contributor
Author
|
🤖 Engineering review — independent review, cycle 1 Reviewed head: What I checked
Owner decisions (protected paths)
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #1028. Rank 9 of the engineering-review order in #1060.
The runtime floors had tracked the newest upstream release, so
pip install agents-shipgatebeside an agent project's ordinary pins quietly installed 0.15.0 (cryptography==46.0.7,packaging==24.2) or failed or fell back further (CrewAI 1.15,pydantic<2.13). Reproduced before the change: withcryptography==46.0.7, pip resolvesagents-shipgate 0.15.0; withcrewai==1.15.27, it resolves0.11.0after five minutes of backtracking.pyproject.toml):cryptography>=44.0.3,packaging>=24.2,pydantic>=2.12.5,<3,pyyaml>=6.0.2,<7,ruamel.yaml>=0.18.10,<1,typer>=0.20.0,<1. Thecryptography<51andpackaging<27caps are dropped. Each floor was confirmed by the full suite at that exact combination, not only by the CI subset (see Verification).app.suggest_commands, which Typer added in 0.20.0; on 0.16.0–0.19.xagents-shipgate --helpexits 1 withAttributeError: 'Typer' object has no attribute 'suggest_commands', and every grouped subcommand fails the same way. The verifier's subset did not reach it.scripts/dependency_floors.py(new):coinstallruns onepip install --dry-run --reportper scenario (cryptography==46.0.7,packaging==24.2,crewai==1.15.27) against a metadata-only wheel of this tree and fails unless pip keeps both this version and the pin;compilerunsuv pip compile --resolution lowest-direct --generate-hashesover the runtime requirements plus the test tools at theirconstraints/dev.txtversions, and fails unless every runtime dependency lands exactly on its floor (naming whatever raised it);installedre-checks the interpreter.dependency-floorsjob (new, runs on every PR): installs the pinneduvand the scripts' ownpackagingin a separate hash-checked venv, runscoinstall, compiles the lowest-direct closure, installs it--require-hashesandpip checks it, runs the dependency-heavy tests andregenerate_goldens.py --checkthere, and runs./shipgate doctorto confirm no dependency is below its floor.versioning-strategy: increase-if-necessaryon the pip block. See "Deviation" below for why not the literallockfile-only.doctor --json: two newmismatches[]codes from the existing_mismatchesengine (no new block, no field changes):dependency_below_declared_floor(error, with a reinstallcommand) and, for a source checkout,dependency_differs_from_lock(warning). Declarations come from the running build (the checkout'spyproject.toml, or the installed wheel'sRequires-Dist); versions fromimportlib.metadata, read at call time so tests patch that module. The module stays standard-library only (the launcher imports it to explain import failures), so the version comparison is a conservative PEP 440 release-segment comparison that never reports what it cannot read.docs/diagnostics.md,AGENTS.md(+ regeneratedllms-full.txt), CHANGELOG### Packaging.Deviation from the acceptance wording:
increase-if-necessary, notlockfile-onlyThe issue offers either; the AC says "a lockfile-only strategy". I read dependabot-core's pip updater before choosing (
python/lib/dependabot/python/update_checker.rb,update_checker/requirements_updater.rb,updater/.../create_security_update_pull_request.rb):lockfile-only,requirements_unlocked_or_can_be?is false, so the update runs with no requirement unlocked. Dependabot readsconstraints/*.txtas requirement files (the:requirementsresolver; they have no pip-compile header), whose==pins are the requirement. Nothing can move: weekly lock PRs stop, and a security update ends asupdate_not_possible.increase-if-necessary, apyproject.tomlrange that already admits the new release is left unchanged (update_pyproject_version_if_needed), while a==pin inconstraints/is rewritten. That is "updates move locks, not floors" — the intent of the AC.library? ? widen : increase, wherelibrary?compares PyPI's summary for agents-shipgate withpyproject.toml's description. Any description edit between releases switched Dependabot to raising floors, which is how Build(deps-dev): Update mcp requirement from <3,>=2.1.1 to >=2.2.0,<3 #705 moved themcpfloor.tests/test_dependency_floors.py::test_dependabot_moves_the_locks_and_never_a_floorpins the setting. Side effect worth knowing: the harness'sclaude-agent-sdk>=0.2.136,<0.3floor-bump PRs (always closed: #1012, #982, #957…) stop; a release outside<0.3still opens one.Type
doctor --jsongains twomismatches[].codevalues)Protected paths
This touches protected paths and needs owner review before merge:
pyproject.toml— runtime floors and caps.constraints/dev.txt— recompiled because its declaration block records the runtime ranges (uv 0.13.1, after build(deps): refresh dev, build-backend and publish locks; uv 0.13.1, pydantic 2.14.1 #1088 merged)..github/dependabot.yml—versioning-strategy..github/workflows/ci.yml— newdependency-floorsjob.CONTRIBUTING.md— when floors may move; the Dependabot procedure.Acceptance criteria
pip install --dry-run --reportcheck resolves the current release withcryptography==46.0.7, withpackaging==24.2, and alongsidecrewai==1.15.27.python scripts/dependency_floors.py coinstallprintsOKfor all three on this tree (exit 0, run with pip 26.0.1 and pip 25.0.1). Onorigin/main'spyproject.tomlthe same command fails all three withResolutionImpossible(exit 1). Tests:tests/test_dependency_floors.py::test_pip_installs_this_release_beside_the_pins_agent_projects_carryruns the real check (slow, so nightly and in release verification; it skips when pypi.org is unreachable). The offlinetest_the_floors_admit_each_scenarios_direct_pins[*]andtest_the_floors_admit_crewais_pydantic_rangefail on the old floors.dependency-floorsinci.yml. I ran each step locally in fresh venvs.compileput all six at exactly their floors (uv 0.13.1); the hashed install andpip checkpassed; the 14 test files passed (75 s on 10 cores, about 290 s of test time);regenerate_goldens.py --checkchanged 0 of 24 artifacts;doctorreported onlydependency_differs_from_lock. In CI (run 39145905447, headf88fd4e) the job passed in 1m44s: threeOKdry runs in 19 s, six floors resolved exactly,pip checkclean, 698 passed and 2 skipped in 55 s, and 0 of 24 goldens changed.dependabot.ymluses a lockfile-only strategy and CONTRIBUTING states when floors may move.increase-if-necessary, so updates move only the locks; see the deviation above.test_dependabot_moves_the_locks_and_never_a_floorfails on the old config. CONTRIBUTING § Changing dependencies → Runtime floors.doctor --jsonreports a mismatch when a dependency is below its floor (unit test with patchedimportlib.metadata).tests/test_environment.py::test_doctor_reads_installed_versions_through_importlib_metadatapatchesimportlib.metadata.versionand runsenvironment_reporton this checkout, andtest_a_built_distribution_is_judged_by_its_own_requires_distalso patchesimportlib.metadata.requires. With the one line that adds the dependency mismatches reverted, 11 of the new tests fail. On the maintainer's launcher venv,./shipgate doctor --jsonused to reportmismatches: []. It now reportsdependency_differs_from_lockfor cryptography 46.0.7, packaging 26.2, pydantic 2.13.4 and typer 0.27.0. Against the old floors, the first two would bedependency_below_declared_floor.scripts/verify_dependency_lock.pyand theexecutable_pinparity claims still pass.verify_dependency_lock.pyexit 0.tests/test_distribution_surface_parity.pypasses. Theconstraints/dev.txtrecompile changed only its declaration block: no pin moved, no marker was dropped, and noname[extra]==pin appeared.Owner decisions
cryptographyfloor now admits releases with advisories. Every release before 50.0.0 has at least one (PyPI/OSV), and Add trusted authorization for exact coding-agent pushes #281 had raised the floor "past vulnerable wheels". I read each advisory that applies to 44.0.3–49.x: PKCS#7 decryption (GHSA-g6cj), X.509 path building (GHSA-jwv3), DNS name constraints (GHSA-m2h6, GHSA-m959), non-contiguous buffers (GHSA-p423), SECT-curve subgroups (GHSA-r6ph), and the bundled-OpenSSL advisory of 2026-06-09 (GHSA-537c: PKCS7/CMS/QUIC/OCSP/AES-OCB…). None reaches the one call Agents Shipgate makes,Ed25519PublicKey.from_public_bytes(...).verify(...)on bytes. A fresh install still resolves the newest release. CONTRIBUTING now states the rule: a vulnerability moves a floor only when it is reachable. If you would rather not admit those releases, the lowest floor the acceptance criteria allow is46.0.7.dependency-floorsbecome a required check? I recommend not yet. It resolves from the live index on purpose (lowest-direct means "the newest of everything else"), so a third-party release can turn it red on an unrelated PR. Leave it advisory for a few weeks of green runs. It runs as one job with the usualtimeout-minutes: 15, alongside the shards, and does not touch their budget.slowco-installation test now runs in release verification, which uses-m "not perf", so the release gate reaches PyPI for that one test. It skips when the index is unreachable. If you want the release gate fully offline, deselect it there.pydantic<3,pyyaml<7,ruamel.yaml<1,typer<1, where the next major is the dependency's own declared break. Caps dropped:cryptography<51,packaging<27. Both publish majors routinely, and either cap would repeat the 0.15.0 fallback on their next release.tests/test_dependency_floors.py::CAPPEDrecords the reasons.click8.0 undertyper0.20, are proven by neither this job nor the locked suite.Authorship
Verification
Locked environment: a fresh venv from
constraints/dev.txtandconstraints/build-backend.txtwith--require-hashes, pluspip install -e . --no-deps --no-build-isolation, on Python 3.12.13.python -m ruff check .python scripts/verify_dependency_lock.pypython scripts/generate_schemas.py --checkpython scripts/regenerate_goldens.py --checkpytest tests/test_dependency_floors.py tests/test_environment.py tests/test_packaging.py tests/test_distribution_surface_parity.py tests/test_action_metadata.py tests/test_slow_tests_workflow.py tests/test_shard_partition.py tests/test_public_surface_contract.py tests/test_workflow_step_action_references.py tests/test_actions_token_scope.py tests/test_release_pipeline.py tests/test_agent_instructions_renderers.pypytest -n auto -m "not perf" --ignore=tests/test_adapter_static_only.pytest_check_unmodelled_host_config_keys.py[local_settings_enabled_plugins-*]global-gitignore noise. All six CI shards pass.pytest tests/test_adapter_static_only.pypytest tests/test_latency_budget.py -m perfAt the floors (every runtime dependency at exactly its floor, the rest of the dev closure installed):
pytest -n auto -m "not perf" --ignore=tests/test_adapter_static_only.pygave 23,247 passed and 6 failed. None of the six is a floor defect:tests/test_schema_roundtrip.py: pydantic 2.12.5 rendersCapabilityDiffRowwithout theadditionalProperties: falsethat 2.14 adds (build(deps): refresh dev, build-backend and publish locks; uv 0.13.1, pydantic 2.14.1 #1088). That is the generator of the committed docs, not what the engine emits or validates. The floors job leavesgenerate_schemas.py --checkout and says why.test_check_unmodelled_host_config_keys.py[local_settings_*]: the known local-only global-gitignore noise.test_release_advisory_pipeline.py::test_an_exercised_wheel_is_bound_by_bytes_and_never_called_qualified: a wall-clock flake under load. Its_wheelhelper writes zip entries with the current time, so two wheels built in different seconds differ. It passes alone. This is pre-existing and unrelated to this PR.An earlier full run at the issue's suggested
typer==0.16.0failed 80 tests by 26% before I stopped it. That is what moved the floor to 0.20.0.coinstalltook 46 s with a warm pip cache and 15 min on a slow local connection with a cold cache. In CI it took 19 s, so the job keeps the usual 15-minute cap.Release-readiness notes
scripts/dependency_floors.pyin CI and by oneslowtest that skips when PyPI is unreachable)docs/checks.md(none changed)STABILITY.md(no report or schema change; two additivedoctormismatch codes, documented indocs/diagnostics.md)🤖 Generated with Claude Code