Skip to content

Dependencies

Ownership

Four native pip-compile inputs own the exact direct tool dependencies. Root pyproject.toml owns empty runtime metadata and the isolated resolver bootstrap pins:

Audience Direct input Generated lock Purpose
Quality requirements-dev.in requirements-dev.txt Ruff, mypy, Bandit, and dependency audit
Tests requirements-test.in requirements-test.txt pytest, asyncio support, and branch coverage
Packaging requirements-package.in requirements-package.txt standard wheel and sdist construction only
Documentation requirements-docs.in requirements-docs.txt strict MkDocs site build

Every lock is generated by pip-compile, carries exact versions and SHA-256 hashes, and installs with wheel-only hash enforcement followed by pip check. Direct dependencies do not overlap between audiences. Installed PEP 561 smoke uses mypy from the quality environment; the packaging environment therefore contains no type checker or standalone executable builder.

The maintainer audiences are not published as package extras. There is no runtime input or lock because the library has no runtime dependencies. The build-system setuptools range describes backend capability; the exact backend used by project builds is owned by requirements-package.in. CI's distribution job combines only the packaging tools needed to construct archives with the quality audience's mypy for an installed-package typing check; publication downloads the already gated archives and installs no build tools.

There is no third-party product-license inventory because the runtime dependency set is empty and none of the maintainer tool audiences is redistributed in the wheel or sdist. Distribution verification instead checks that the project's MIT license is present and exact. Adding any runtime or redistributed third-party dependency also requires adding an owned license inventory before release.

The test-only SSH server image is separate from Python tool audiences and is never published or included in the wheel or sdist. Its owning tests_acceptance/server/Containerfile pins the base image by digest and Debian packages through a dated signed archive snapshot. Update those pins deliberately and run make test-acceptance and make ci; do not install or version-match an SSH server on the client host. make acceptance-image rebuilds from that narrow context and keeps its image ID under the selected machine's environment root. This includes the image-owned tini init process; engine injection of a host catatonit or docker-init binary is disabled, not added as a host prerequisite. For live tests, Podman runs rootless with its host pasta helper from the passt OS package, using a direct per-container network rather than a shared bridge. Docker remains supported through its native network stack. This host engine prerequisite adds no dependency to the library or its Python locks.

Reviewed update loop

  1. Change exact direct pins in the appropriate requirements-*.in file. Update overlapping resolver bootstrap pins in pyproject.toml in the same change.
  2. Run make lock, or make refresh-dependencies for a deliberate graph-wide refresh.
  3. Review every transitive pin and hash change.
  4. Run make freeze-check, make lock-platform-check, make dependency-snapshot, and make audit.
  5. Review make audit-raw and make outdated.

Generated lock files are not edited manually. The isolated resolver bootstrap is exactly pinned in the standard [dependency-groups].resolver-bootstrap group, which is excluded from built package metadata. The Makefile asks the dependency-policy helper for the complete closure instead of duplicating it, installs that closure with --no-deps, and then runs pip check. Every bootstrap package that also occurs in an audience lock has the same version. Bootstrap export validates the source pins without consulting existing locks; otherwise an overlapping update such as build would prevent creating the resolver needed to regenerate those locks. Both lock-generation Make targets finish by validating the complete new graph, including bootstrap overlaps. Do not bypass Make, temporarily revert pins, or hand-edit locks to perform an update. The current resolver pair, pip==26.2.1 and pip-tools==7.6.1, matches Dependabot's Python helper pins reviewed on 2026-09-08 at dependabot/dependabot-core revision bddccfc0419e89a6ca0d33f80a1fb0b2882ef467. Future resolver migrations require reviewing that pair and reproducing all four locks. Freeze checking seeds temporary outputs with the committed locks so available transitive updates do not turn validation into a graph refresh. It compares regenerated content while excluding only pip-compile's interpreter-specific header.

Dependabot discovers each .in/.txt pair as a native pip-compile manifest and groups Python updates into one PR. pyproject.toml is excluded from its pip manifests so the plain requirements updater cannot own generated locks. The isolated resolver group remains manually reviewed, validated, and audited.

The audit covers all four locks and the exact resolver bootstrap. Findings are accepted only when they exactly equal .github/dependency-audit-exceptions.json; both new findings and stale exceptions fail. Dependency submission derives all four persistent manifests offline from validated locks; the transient resolver group is validated and audited but is not submitted as a fifth lock. The dependency contract contains the evidence mapping.