trustcheck 2.2.0__tar.gz → 2.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/ci.yml +11 -4
  2. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/publish.yml +81 -3
  3. {trustcheck-2.2.0 → trustcheck-2.2.1}/MANIFEST.in +1 -0
  4. {trustcheck-2.2.0/src/trustcheck.egg-info → trustcheck-2.2.1}/PKG-INFO +16 -15
  5. {trustcheck-2.2.0 → trustcheck-2.2.1}/README.md +15 -14
  6. trustcheck-2.2.1/scripts/export_homebrew_tap.py +307 -0
  7. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/_version.py +2 -2
  8. {trustcheck-2.2.0 → trustcheck-2.2.1/src/trustcheck.egg-info}/PKG-INFO +16 -15
  9. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck.egg-info/SOURCES.txt +2 -0
  10. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_ci_workflow.py +5 -0
  11. trustcheck-2.2.1/tests/test_homebrew_tap_export.py +134 -0
  12. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_release_readiness.py +17 -0
  13. {trustcheck-2.2.0 → trustcheck-2.2.1}/.dockerignore +0 -0
  14. {trustcheck-2.2.0 → trustcheck-2.2.1}/.gitattributes +0 -0
  15. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/CODEOWNERS +0 -0
  16. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/ISSUE_TEMPLATE/general.yml +0 -0
  17. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/dependabot.yml +0 -0
  18. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/trustcheck-action-fail-policy.json +0 -0
  19. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/acceptance-matrix.yml +0 -0
  20. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/action-integration.yml +0 -0
  21. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/bandit.yml +0 -0
  22. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/benchmarks.yml +0 -0
  23. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/binary-security.yml +0 -0
  24. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/codeql.yml +0 -0
  25. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/docs.yml +0 -0
  26. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/fuzz.yml +0 -0
  27. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/live-integration.yml +0 -0
  28. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/mutation.yml +0 -0
  29. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/plagiarism-scan.yml +0 -0
  30. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/post-release-parity.yml +0 -0
  31. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/sarif-integration.yml +0 -0
  32. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/semgrep.yml +0 -0
  33. {trustcheck-2.2.0 → trustcheck-2.2.1}/.github/workflows/source-build.yml +0 -0
  34. {trustcheck-2.2.0 → trustcheck-2.2.1}/.gitignore +0 -0
  35. {trustcheck-2.2.0 → trustcheck-2.2.1}/.pre-commit-hooks.yaml +0 -0
  36. {trustcheck-2.2.0 → trustcheck-2.2.1}/CHANGELOG.md +0 -0
  37. {trustcheck-2.2.0 → trustcheck-2.2.1}/CONTRIBUTING.md +0 -0
  38. {trustcheck-2.2.0 → trustcheck-2.2.1}/Dockerfile +0 -0
  39. {trustcheck-2.2.0 → trustcheck-2.2.1}/LICENSE +0 -0
  40. {trustcheck-2.2.0 → trustcheck-2.2.1}/SECURITY.md +0 -0
  41. {trustcheck-2.2.0 → trustcheck-2.2.1}/action.yml +0 -0
  42. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/README.md +0 -0
  43. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/benchmark_against_pip_audit.py +0 -0
  44. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/corpus.json +0 -0
  45. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/malicious-calibration.json +0 -0
  46. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/pdm.lock +0 -0
  47. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/poetry.lock +0 -0
  48. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/pylock.toml +0 -0
  49. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-hashed.txt +0 -0
  50. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-main.txt +0 -0
  51. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-malformed.txt +0 -0
  52. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-markers-extras.txt +0 -0
  53. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-private-index.txt +0 -0
  54. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-profiles.txt +0 -0
  55. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-resolution.txt +0 -0
  56. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements-vcs-editable.txt +0 -0
  57. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/requirements.txt +0 -0
  58. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/truth-public-key.pem +0 -0
  59. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/truth.json +0 -0
  60. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/truth.json.sig +0 -0
  61. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/corpus/uv.lock +0 -0
  62. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/measure_command.py +0 -0
  63. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/results/benchmark-public-key.pem +0 -0
  64. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/results/latest.json +0 -0
  65. {trustcheck-2.2.0 → trustcheck-2.2.1}/benchmarks/results/latest.json.sig +0 -0
  66. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/assets/images/logo-bg-less.png +0 -0
  67. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/assets/images/logo.png +0 -0
  68. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/assets/javascripts/disable-search-shortcut.js +0 -0
  69. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/changelog.md +0 -0
  70. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/cli/configuration.md +0 -0
  71. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/cli/exit-codes.md +0 -0
  72. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/cli/index.md +0 -0
  73. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/cli/policies.md +0 -0
  74. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/getting-started/installation.md +0 -0
  75. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/getting-started/quickstart.md +0 -0
  76. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/guides/ci-integration.md +0 -0
  77. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/index.md +0 -0
  78. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/benchmarks.md +0 -0
  79. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/compatibility.md +0 -0
  80. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/industry-formats.md +0 -0
  81. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/json-contract.md +0 -0
  82. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/malicious-package-detection.md +0 -0
  83. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/performance-extensibility.md +0 -0
  84. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/python-api.md +0 -0
  85. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/recommendations.md +0 -0
  86. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/remediation.md +0 -0
  87. {trustcheck-2.2.0 → trustcheck-2.2.1}/docs/reference/trust-model.md +0 -0
  88. {trustcheck-2.2.0 → trustcheck-2.2.1}/fuzz/README.md +0 -0
  89. {trustcheck-2.2.0 → trustcheck-2.2.1}/fuzz/fuzz_artifacts.py +0 -0
  90. {trustcheck-2.2.0 → trustcheck-2.2.1}/fuzz/fuzz_exports.py +0 -0
  91. {trustcheck-2.2.0 → trustcheck-2.2.1}/fuzz/fuzz_indexes.py +0 -0
  92. {trustcheck-2.2.0 → trustcheck-2.2.1}/fuzz/fuzz_lockfiles.py +0 -0
  93. {trustcheck-2.2.0 → trustcheck-2.2.1}/fuzz/fuzz_provenance.py +0 -0
  94. {trustcheck-2.2.0 → trustcheck-2.2.1}/fuzz/fuzz_requirements.py +0 -0
  95. {trustcheck-2.2.0 → trustcheck-2.2.1}/mkdocs.yml +0 -0
  96. {trustcheck-2.2.0 → trustcheck-2.2.1}/pyproject.toml +0 -0
  97. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/action.lock +0 -0
  98. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/ci.in +0 -0
  99. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/ci.lock +0 -0
  100. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/fuzz.in +0 -0
  101. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/fuzz.lock +0 -0
  102. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/runtime.in +0 -0
  103. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/runtime.lock +0 -0
  104. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/semgrep.in +0 -0
  105. {trustcheck-2.2.0 → trustcheck-2.2.1}/requirements/semgrep.lock +0 -0
  106. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/acceptance_matrix.py +0 -0
  107. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/benchmark_signature.py +0 -0
  108. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/build_msix_layout.py +0 -0
  109. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/build_standalone.py +0 -0
  110. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/check_mutation_score.py +0 -0
  111. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/dependency_bounds.py +0 -0
  112. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/github_plagiarism_scan.py +0 -0
  113. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/smoke_test_distribution.py +0 -0
  114. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/trustcheck_binary.py +0 -0
  115. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/update_benchmark_table.py +0 -0
  116. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/update_coverage_badge.py +0 -0
  117. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/validate_sarif.py +0 -0
  118. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/verify_release_channels.py +0 -0
  119. {trustcheck-2.2.0 → trustcheck-2.2.1}/scripts/verify_release_version.py +0 -0
  120. {trustcheck-2.2.0 → trustcheck-2.2.1}/setup.cfg +0 -0
  121. {trustcheck-2.2.0 → trustcheck-2.2.1}/snap/README.md +0 -0
  122. {trustcheck-2.2.0 → trustcheck-2.2.1}/snap/gui/icon.png +0 -0
  123. {trustcheck-2.2.0 → trustcheck-2.2.1}/snap/snapcraft.yaml +0 -0
  124. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/__init__.py +0 -0
  125. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/__main__.py +0 -0
  126. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/_resolver_guard.py +0 -0
  127. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/advisories.py +0 -0
  128. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/artifacts.py +0 -0
  129. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/attestations.py +0 -0
  130. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cache.py +0 -0
  131. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli.py +0 -0
  132. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/__init__.py +0 -0
  133. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/context.py +0 -0
  134. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/diff.py +0 -0
  135. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/doctor.py +0 -0
  136. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/environment.py +0 -0
  137. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/impact.py +0 -0
  138. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/inspect.py +0 -0
  139. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/install.py +0 -0
  140. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/manifest.py +0 -0
  141. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_commands/scan.py +0 -0
  142. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_models.py +0 -0
  143. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_render.py +0 -0
  144. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_runtime.py +0 -0
  145. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/cli_targets.py +0 -0
  146. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/contract.py +0 -0
  147. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/diff.py +0 -0
  148. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/doctor.py +0 -0
  149. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/dynamic.py +0 -0
  150. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/export_models.py +0 -0
  151. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/export_xml.py +0 -0
  152. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/exports.py +0 -0
  153. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/github_action.py +0 -0
  154. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/impact.py +0 -0
  155. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/indexes.py +0 -0
  156. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/lockfiles.py +0 -0
  157. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/malicious.py +0 -0
  158. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/manifest.py +0 -0
  159. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/models.py +0 -0
  160. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/plugins.py +0 -0
  161. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/policy.py +0 -0
  162. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/pre_commit.py +0 -0
  163. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/provenance.py +0 -0
  164. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/py.typed +0 -0
  165. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/pypi.py +0 -0
  166. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/remediation.py +0 -0
  167. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/remediation_models.py +0 -0
  168. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/remediation_render.py +0 -0
  169. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/resolver.py +0 -0
  170. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/resume.py +0 -0
  171. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/schemas.py +0 -0
  172. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/service.py +0 -0
  173. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/service_state.py +0 -0
  174. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/service_urls.py +0 -0
  175. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/snapshots.py +0 -0
  176. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck/workspace.py +0 -0
  177. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck.egg-info/dependency_links.txt +0 -0
  178. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck.egg-info/entry_points.txt +0 -0
  179. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck.egg-info/requires.txt +0 -0
  180. {trustcheck-2.2.0 → trustcheck-2.2.1}/src/trustcheck.egg-info/top_level.txt +0 -0
  181. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/bad-scan.toml +0 -0
  182. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/cache/5e491d79f8ba9e36d864ae50c690989677616cd509e5b99abb9272c8ad976435.json +0 -0
  183. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/config_non_object.json +0 -0
  184. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/empty-scan.toml +0 -0
  185. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/empty-scan.txt +0 -0
  186. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/invalid-scan.txt +0 -0
  187. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/policy_non_object.json +0 -0
  188. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/scan-poetry.toml +0 -0
  189. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/_tmp/scan-project.toml +0 -0
  190. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/fixtures/client_config.json +0 -0
  191. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/fixtures/policy_require_expected_repo.json +0 -0
  192. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/fixtures/requirements-vulnerable.txt +0 -0
  193. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/snapshots/contract_schema.json +0 -0
  194. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/snapshots/report_minimal.json +0 -0
  195. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/snapshots/report_verified.json +0 -0
  196. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_advisories.py +0 -0
  197. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_artifacts.py +0 -0
  198. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_attestations.py +0 -0
  199. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_benchmark_results.py +0 -0
  200. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_binary_security_workflow.py +0 -0
  201. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_cli.py +0 -0
  202. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_contract.py +0 -0
  203. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_dependency_bounds.py +0 -0
  204. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_diff.py +0 -0
  205. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_docker_workflows.py +0 -0
  206. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_doctor.py +0 -0
  207. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_dynamic.py +0 -0
  208. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_edge_cases.py +0 -0
  209. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_exports.py +0 -0
  210. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_github_action.py +0 -0
  211. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_impact.py +0 -0
  212. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_indexes.py +0 -0
  213. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_install_command.py +0 -0
  214. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_integration_live.py +0 -0
  215. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_lockfiles.py +0 -0
  216. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_malicious.py +0 -0
  217. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_manifest.py +0 -0
  218. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_msix_packaging.py +0 -0
  219. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_mutation_score.py +0 -0
  220. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_performance_extensibility.py +0 -0
  221. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_plagiarism_scan.py +0 -0
  222. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_plugin_security.py +0 -0
  223. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_pre_commit.py +0 -0
  224. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_property_invariants.py +0 -0
  225. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_provenance.py +0 -0
  226. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_public_api.py +0 -0
  227. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_pypi.py +0 -0
  228. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_release_channels.py +0 -0
  229. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_release_executable.py +0 -0
  230. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_release_version.py +0 -0
  231. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_remediation.py +0 -0
  232. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_resolver.py +0 -0
  233. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_resolver_guard.py +0 -0
  234. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_sarif_validation.py +0 -0
  235. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_scan_profiles.py +0 -0
  236. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_service.py +0 -0
  237. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_snap_packaging.py +0 -0
  238. {trustcheck-2.2.0 → trustcheck-2.2.1}/tests/test_workspace.py +0 -0
@@ -379,13 +379,20 @@ jobs:
379
379
  GH_TOKEN: ${{ github.token }}
380
380
  run: |
381
381
  publish_root="$(mktemp -d)"
382
- cp coverage-artifacts/coverage-badge/coverage.svg "$publish_root/coverage.svg"
383
382
  git -C "$publish_root" init
384
383
  git -C "$publish_root" config user.name "github-actions[bot]"
385
384
  git -C "$publish_root" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
386
- git -C "$publish_root" add coverage.svg
387
- git -C "$publish_root" commit -m "Update coverage badge for ${GITHUB_SHA}"
388
- git -C "$publish_root" branch -M coverage-badge
389
385
  git -C "$publish_root" remote add origin \
390
386
  "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
387
+ git -C "$publish_root" fetch --depth=1 origin coverage-badge
388
+ if ! git -C "$publish_root" show FETCH_HEAD:PyPI.png > "$publish_root/PyPI.png"; then
389
+ printf '%s\n' \
390
+ "PyPI.png is missing from coverage-badge; refusing to replace the asset branch" \
391
+ "without it." >&2
392
+ exit 1
393
+ fi
394
+ cp coverage-artifacts/coverage-badge/coverage.svg "$publish_root/coverage.svg"
395
+ git -C "$publish_root" add PyPI.png coverage.svg
396
+ git -C "$publish_root" commit -m "Update coverage badge for ${GITHUB_SHA}"
397
+ git -C "$publish_root" branch -M coverage-badge
391
398
  git -C "$publish_root" push --force origin coverage-badge
@@ -837,6 +837,84 @@ jobs:
837
837
  packages-dir: dist/packages
838
838
  verbose: true
839
839
 
840
+ publish-homebrew-tap:
841
+ needs: publish-pypi
842
+ runs-on: ubuntu-latest
843
+
844
+ steps:
845
+ - name: Check out tagged commit
846
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
847
+ with:
848
+ ref: ${{ github.sha }}
849
+ persist-credentials: false
850
+
851
+ - name: Set up Python
852
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
853
+ with:
854
+ python-version: "3.12"
855
+
856
+ - name: Download build artifacts
857
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
858
+ with:
859
+ name: dist-${{ github.sha }}
860
+ path: dist
861
+
862
+ - name: Export Homebrew tap pins
863
+ run: |
864
+ python scripts/export_homebrew_tap.py \
865
+ --runtime-lock requirements/runtime.lock \
866
+ --build-lock requirements/action.lock \
867
+ --checksums dist/SHA256SUMS.txt \
868
+ --output-dir homebrew-tap-export/trustcheck \
869
+ --tag "$GITHUB_REF_NAME" \
870
+ --source-repository "$GITHUB_REPOSITORY" \
871
+ --source-commit "$GITHUB_SHA" \
872
+ --extra-package setuptools-scm \
873
+ --extra-package wheel
874
+
875
+ - name: Require Homebrew tap token
876
+ env:
877
+ HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN || secrets.RELEASE_TOKEN }}
878
+ run: |
879
+ if [ -z "$HOMEBREW_TAP_TOKEN" ]; then
880
+ echo "Set HOMEBREW_TAP_TOKEN or RELEASE_TOKEN with contents:write access to Halfblood-Prince/homebrew-tap." >&2
881
+ exit 1
882
+ fi
883
+
884
+ - name: Check out Homebrew tap
885
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
886
+ with:
887
+ repository: Halfblood-Prince/homebrew-tap
888
+ ref: main
889
+ token: ${{ secrets.HOMEBREW_TAP_TOKEN || secrets.RELEASE_TOKEN }}
890
+ path: homebrew-tap
891
+ persist-credentials: true
892
+
893
+ - name: Commit Homebrew tap pins
894
+ env:
895
+ RELEASE_TAG: ${{ github.ref_name }}
896
+ run: |
897
+ mkdir -p homebrew-tap/trustcheck
898
+ cp homebrew-tap-export/trustcheck/runtime.lock homebrew-tap/trustcheck/runtime.lock
899
+ cp homebrew-tap-export/trustcheck/resources.rb homebrew-tap/trustcheck/resources.rb
900
+ cp homebrew-tap-export/trustcheck/release.json homebrew-tap/trustcheck/release.json
901
+
902
+ git -C homebrew-tap config user.name "github-actions[bot]"
903
+ git -C homebrew-tap config user.email "41898282+github-actions[bot]@users.noreply.github.com"
904
+ git -C homebrew-tap add trustcheck
905
+ if git -C homebrew-tap diff --cached --quiet; then
906
+ echo "No Homebrew tap pin changes to commit."
907
+ exit 0
908
+ fi
909
+ git -C homebrew-tap commit -m "Update trustcheck Homebrew pins for ${RELEASE_TAG}"
910
+ git -C homebrew-tap push
911
+ {
912
+ echo "## Homebrew tap"
913
+ echo
914
+ echo "- Updated \`Halfblood-Prince/homebrew-tap\` trustcheck pin export for \`${RELEASE_TAG}\`."
915
+ echo "- Wrote \`trustcheck/runtime.lock\`, \`trustcheck/resources.rb\`, and \`trustcheck/release.json\`."
916
+ } >> "$GITHUB_STEP_SUMMARY"
917
+
840
918
  publish-docker:
841
919
  needs:
842
920
  - coverage-build
@@ -935,9 +1013,9 @@ jobs:
935
1013
  append_body: true
936
1014
  body: |
937
1015
  Published from immutable commit `${{ github.sha }}`.
938
- The release workflow publishes PyPI, GitHub Action, Snap Store, and
939
- GHCR Docker distributions after shared tag verification, QA, matrix,
940
- and coverage builds.
1016
+ The release workflow publishes PyPI, GitHub Action, Snap Store,
1017
+ Homebrew tap pins, and GHCR Docker distributions after shared tag
1018
+ verification, QA, matrix, and coverage builds.
941
1019
 
942
1020
  Release artifacts:
943
1021
  - `dist/*`
@@ -1,2 +1,3 @@
1
1
  include scripts/verify_release_version.py
2
2
  include scripts/verify_release_channels.py
3
+ include scripts/export_homebrew_tap.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustcheck
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Package trust and provenance verification for PyPI consumers.
5
5
  License-Expression: LicenseRef-Trustcheck-Personal-Use
6
6
  Project-URL: Repository, https://github.com/Halfblood-Prince/trustcheck
@@ -63,6 +63,7 @@ Dynamic: license-file
63
63
  [![TrustCheck Package Scanner](https://img.shields.io/badge/GitHub%20Action-TrustCheck%20Package%20Scanner-blue?logo=githubactions&logoColor=white)](https://github.com/marketplace/actions/trustcheck-package-scanner)
64
64
 
65
65
  [![Get it from the Snap Store](https://snapcraft.io/en/dark/install.svg)](https://snapcraft.io/trustcheck)
66
+ <a href="https://pypi.org/project/trustcheck/"><img src="https://raw.githubusercontent.com/Halfblood-Prince/trustcheck/coverage-badge/PyPI.png" alt="Get it from PyPI" height="55"></a>
66
67
 
67
68
  `trustcheck` is a Python package and CLI for evaluating the trust posture of PyPI releases before they are installed, promoted, or approved.
68
69
 
@@ -81,6 +82,20 @@ Packages that publish no provenance are treated as needing review rather than as
81
82
  | `manifest` | lock approved trust evidence |
82
83
  | `impact` | prioritize by observed usage |
83
84
 
85
+ <!-- trustcheck-benchmark:start -->
86
+ ## Latest benchmark
87
+
88
+ Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
89
+
90
+ | Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
91
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
92
+ | trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
93
+ | pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
94
+
95
+ Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
96
+ Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
97
+ <!-- trustcheck-benchmark:end -->
98
+
84
99
  ## What it checks
85
100
 
86
101
  For a selected package version, `trustcheck` can:
@@ -141,20 +156,6 @@ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
141
156
  are retained as workflow artifacts by
142
157
  [Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
143
158
 
144
- <!-- trustcheck-benchmark:start -->
145
- ## Latest benchmark
146
-
147
- Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
148
-
149
- | Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
150
- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
151
- | trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
152
- | pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
153
-
154
- Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
155
- Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
156
- <!-- trustcheck-benchmark:end -->
157
-
158
159
  ## Installation
159
160
 
160
161
  Install from PyPI:
@@ -22,6 +22,7 @@
22
22
  [![TrustCheck Package Scanner](https://img.shields.io/badge/GitHub%20Action-TrustCheck%20Package%20Scanner-blue?logo=githubactions&logoColor=white)](https://github.com/marketplace/actions/trustcheck-package-scanner)
23
23
 
24
24
  [![Get it from the Snap Store](https://snapcraft.io/en/dark/install.svg)](https://snapcraft.io/trustcheck)
25
+ <a href="https://pypi.org/project/trustcheck/"><img src="https://raw.githubusercontent.com/Halfblood-Prince/trustcheck/coverage-badge/PyPI.png" alt="Get it from PyPI" height="55"></a>
25
26
 
26
27
  `trustcheck` is a Python package and CLI for evaluating the trust posture of PyPI releases before they are installed, promoted, or approved.
27
28
 
@@ -40,6 +41,20 @@ Packages that publish no provenance are treated as needing review rather than as
40
41
  | `manifest` | lock approved trust evidence |
41
42
  | `impact` | prioritize by observed usage |
42
43
 
44
+ <!-- trustcheck-benchmark:start -->
45
+ ## Latest benchmark
46
+
47
+ Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
48
+
49
+ | Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
50
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
51
+ | trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
52
+ | pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
53
+
54
+ Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
55
+ Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
56
+ <!-- trustcheck-benchmark:end -->
57
+
43
58
  ## What it checks
44
59
 
45
60
  For a selected package version, `trustcheck` can:
@@ -100,20 +115,6 @@ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
100
115
  are retained as workflow artifacts by
101
116
  [Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
102
117
 
103
- <!-- trustcheck-benchmark:start -->
104
- ## Latest benchmark
105
-
106
- Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
107
-
108
- | Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
109
- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
110
- | trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
111
- | pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
112
-
113
- Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
114
- Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
115
- <!-- trustcheck-benchmark:end -->
116
-
117
118
  ## Installation
118
119
 
119
120
  Install from PyPI:
@@ -0,0 +1,307 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import re
6
+ import shutil
7
+ import time
8
+ from collections.abc import Callable, Mapping, Sequence
9
+ from dataclasses import asdict, dataclass
10
+ from pathlib import Path
11
+ from urllib.parse import quote
12
+
13
+ import urllib3
14
+
15
+ PACKAGE_LINE = re.compile(
16
+ r"^(?P<name>[A-Za-z0-9_.-]+)==(?P<version>[^\\\s;]+)(?:\s*;[^\\]+)?(?:\s*\\)?$"
17
+ )
18
+ HASH_LINE = re.compile(r"--hash=sha256:(?P<hash>[0-9a-f]{64})")
19
+
20
+
21
+ FetchJson = Callable[[str, str], Mapping[str, object]]
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class LockedPackage:
26
+ name: str
27
+ version: str
28
+ hashes: frozenset[str]
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class Distribution:
33
+ filename: str
34
+ url: str
35
+ sha256: str
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class ResourcePin:
40
+ name: str
41
+ version: str
42
+ filename: str
43
+ url: str
44
+ sha256: str
45
+
46
+
47
+ def canonical_name(name: str) -> str:
48
+ return re.sub(r"[-_.]+", "-", name).lower()
49
+
50
+
51
+ def parse_lockfile(path: Path) -> dict[str, LockedPackage]:
52
+ packages: dict[str, LockedPackage] = {}
53
+ current_name: str | None = None
54
+ current_version: str | None = None
55
+ current_hashes: set[str] = set()
56
+
57
+ def flush_current() -> None:
58
+ nonlocal current_name, current_version, current_hashes
59
+ if current_name is None or current_version is None:
60
+ return
61
+ if not current_hashes:
62
+ raise ValueError(f"{path}: {current_name}=={current_version} has no hashes")
63
+ packages[canonical_name(current_name)] = LockedPackage(
64
+ name=current_name,
65
+ version=current_version,
66
+ hashes=frozenset(current_hashes),
67
+ )
68
+ current_name = None
69
+ current_version = None
70
+ current_hashes = set()
71
+
72
+ for raw_line in path.read_text(encoding="utf-8").splitlines():
73
+ line = raw_line.strip()
74
+ package_match = PACKAGE_LINE.match(line)
75
+ if package_match is not None:
76
+ flush_current()
77
+ current_name = package_match.group("name")
78
+ current_version = package_match.group("version")
79
+ continue
80
+ if current_name is None:
81
+ continue
82
+ for hash_match in HASH_LINE.finditer(line):
83
+ current_hashes.add(hash_match.group("hash"))
84
+ flush_current()
85
+ if not packages:
86
+ raise ValueError(f"{path}: no pinned packages found")
87
+ return packages
88
+
89
+
90
+ def parse_checksums(path: Path) -> dict[str, str]:
91
+ checksums: dict[str, str] = {}
92
+ for line in path.read_text(encoding="utf-8").splitlines():
93
+ stripped = line.strip()
94
+ if not stripped:
95
+ continue
96
+ digest, filename = stripped.split(maxsplit=1)
97
+ checksums[Path(filename).name] = digest.lower()
98
+ if not checksums:
99
+ raise ValueError(f"{path}: no checksums found")
100
+ return checksums
101
+
102
+
103
+ def read_pypi_json(project: str, version: str) -> Mapping[str, object]:
104
+ encoded_project = quote(project, safe="")
105
+ encoded_version = quote(version, safe="")
106
+ url = f"https://pypi.org/pypi/{encoded_project}/{encoded_version}/json"
107
+ pool = urllib3.PoolManager()
108
+ last_error: Exception | None = None
109
+ for attempt in range(1, 7):
110
+ try:
111
+ response = pool.request(
112
+ "GET",
113
+ url,
114
+ headers={
115
+ "Accept": "application/json",
116
+ "User-Agent": "trustcheck-homebrew-tap-exporter",
117
+ },
118
+ retries=False,
119
+ timeout=urllib3.Timeout(total=30),
120
+ )
121
+ if response.status >= 400:
122
+ raise ValueError(
123
+ f"{project}=={version}: PyPI returned HTTP {response.status}"
124
+ )
125
+ payload = json.loads(response.data)
126
+ if not isinstance(payload, dict):
127
+ raise ValueError(f"{project}=={version}: PyPI returned non-object JSON")
128
+ return payload
129
+ except (urllib3.exceptions.HTTPError, ValueError) as exc:
130
+ last_error = exc
131
+ if attempt < 6:
132
+ time.sleep(10)
133
+ raise ValueError(f"Unable to read PyPI metadata for {project}=={version}") from last_error
134
+
135
+
136
+ def fetch_sdist(
137
+ project: str,
138
+ version: str,
139
+ fetch_json: FetchJson = read_pypi_json,
140
+ ) -> Distribution:
141
+ payload = fetch_json(project, version)
142
+ urls = payload.get("urls")
143
+ if not isinstance(urls, list):
144
+ raise ValueError(f"{project}=={version}: PyPI metadata has no urls array")
145
+
146
+ candidates: list[Distribution] = []
147
+ for item in urls:
148
+ if not isinstance(item, Mapping) or item.get("packagetype") != "sdist":
149
+ continue
150
+ digests = item.get("digests")
151
+ filename = item.get("filename")
152
+ url = item.get("url")
153
+ if not isinstance(digests, Mapping):
154
+ continue
155
+ sha256 = digests.get("sha256")
156
+ if (
157
+ isinstance(filename, str)
158
+ and isinstance(url, str)
159
+ and isinstance(sha256, str)
160
+ and re.fullmatch(r"[0-9a-f]{64}", sha256)
161
+ ):
162
+ candidates.append(Distribution(filename=filename, url=url, sha256=sha256))
163
+ if not candidates:
164
+ raise ValueError(f"{project}=={version}: no PyPI sdist found")
165
+ return sorted(
166
+ candidates,
167
+ key=lambda item: (not item.filename.endswith(".tar.gz"), item.filename),
168
+ )[0]
169
+
170
+
171
+ def render_homebrew_resources(resources: Sequence[ResourcePin]) -> str:
172
+ lines = [
173
+ "# This file is generated by scripts/export_homebrew_tap.py.",
174
+ "# Do not edit manually.",
175
+ "",
176
+ ]
177
+ for resource in resources:
178
+ lines.extend(
179
+ [
180
+ f'resource "{resource.name}" do',
181
+ f' url "{resource.url}"',
182
+ f' sha256 "{resource.sha256}"',
183
+ "end",
184
+ "",
185
+ ]
186
+ )
187
+ return "\n".join(lines)
188
+
189
+
190
+ def export_homebrew_tap(
191
+ *,
192
+ runtime_lock: Path,
193
+ build_lock: Path | None,
194
+ checksums: Path,
195
+ output_dir: Path,
196
+ tag: str,
197
+ source_repository: str,
198
+ source_commit: str,
199
+ package_name: str = "trustcheck",
200
+ extra_packages: Sequence[str] = (),
201
+ fetch_json: FetchJson = read_pypi_json,
202
+ ) -> None:
203
+ version = tag.removeprefix("v")
204
+ runtime_packages = parse_lockfile(runtime_lock)
205
+ build_packages = parse_lockfile(build_lock) if build_lock is not None else {}
206
+ selected_packages = dict(runtime_packages)
207
+
208
+ for package in extra_packages:
209
+ key = canonical_name(package)
210
+ if key in selected_packages:
211
+ continue
212
+ try:
213
+ selected_packages[key] = build_packages[key]
214
+ except KeyError as exc:
215
+ raise ValueError(
216
+ f"extra package {package!r} is not pinned in {build_lock}"
217
+ ) from exc
218
+
219
+ release_sdist = fetch_sdist(package_name, version, fetch_json)
220
+ release_checksums = parse_checksums(checksums)
221
+ expected_release_hash = release_checksums.get(release_sdist.filename)
222
+ if expected_release_hash != release_sdist.sha256:
223
+ raise ValueError(
224
+ f"{release_sdist.filename} PyPI sha256 does not match {checksums}: "
225
+ f"{release_sdist.sha256} != {expected_release_hash}"
226
+ )
227
+
228
+ resources: list[ResourcePin] = []
229
+ for package in sorted(
230
+ selected_packages.values(),
231
+ key=lambda item: canonical_name(item.name),
232
+ ):
233
+ sdist = fetch_sdist(package.name, package.version, fetch_json)
234
+ if sdist.sha256 not in package.hashes:
235
+ raise ValueError(
236
+ f"{package.name}=={package.version} sdist hash is not pinned in lockfile: "
237
+ f"{sdist.sha256}"
238
+ )
239
+ resources.append(
240
+ ResourcePin(
241
+ name=canonical_name(package.name),
242
+ version=package.version,
243
+ filename=sdist.filename,
244
+ url=sdist.url,
245
+ sha256=sdist.sha256,
246
+ )
247
+ )
248
+
249
+ output_dir.mkdir(parents=True, exist_ok=True)
250
+ shutil.copyfile(runtime_lock, output_dir / "runtime.lock")
251
+ (output_dir / "resources.rb").write_text(
252
+ render_homebrew_resources(resources),
253
+ encoding="utf-8",
254
+ )
255
+ (output_dir / "release.json").write_text(
256
+ json.dumps(
257
+ {
258
+ "generated_by": "scripts/export_homebrew_tap.py",
259
+ "package": {
260
+ "name": package_name,
261
+ "version": version,
262
+ "tag": tag,
263
+ "source_repository": source_repository,
264
+ "source_commit": source_commit,
265
+ "sdist": asdict(release_sdist),
266
+ },
267
+ "resources": [asdict(resource) for resource in resources],
268
+ },
269
+ indent=2,
270
+ sort_keys=True,
271
+ )
272
+ + "\n",
273
+ encoding="utf-8",
274
+ )
275
+
276
+
277
+ def main(argv: Sequence[str] | None = None) -> int:
278
+ parser = argparse.ArgumentParser(
279
+ description="Export trustcheck release pins for the Homebrew tap."
280
+ )
281
+ parser.add_argument("--runtime-lock", type=Path, required=True)
282
+ parser.add_argument("--build-lock", type=Path)
283
+ parser.add_argument("--checksums", type=Path, required=True)
284
+ parser.add_argument("--output-dir", type=Path, required=True)
285
+ parser.add_argument("--tag", required=True)
286
+ parser.add_argument("--source-repository", required=True)
287
+ parser.add_argument("--source-commit", required=True)
288
+ parser.add_argument("--package-name", default="trustcheck")
289
+ parser.add_argument("--extra-package", action="append", default=[])
290
+ args = parser.parse_args(argv)
291
+
292
+ export_homebrew_tap(
293
+ runtime_lock=args.runtime_lock,
294
+ build_lock=args.build_lock,
295
+ checksums=args.checksums,
296
+ output_dir=args.output_dir,
297
+ tag=args.tag,
298
+ source_repository=args.source_repository,
299
+ source_commit=args.source_commit,
300
+ package_name=args.package_name,
301
+ extra_packages=args.extra_package,
302
+ )
303
+ return 0
304
+
305
+
306
+ if __name__ == "__main__":
307
+ raise SystemExit(main())
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = 'v2.2.0'
22
- __version_tuple__ = version_tuple = (2, 2, 0)
21
+ __version__ = version = 'v2.2.1'
22
+ __version_tuple__ = version_tuple = (2, 2, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustcheck
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Package trust and provenance verification for PyPI consumers.
5
5
  License-Expression: LicenseRef-Trustcheck-Personal-Use
6
6
  Project-URL: Repository, https://github.com/Halfblood-Prince/trustcheck
@@ -63,6 +63,7 @@ Dynamic: license-file
63
63
  [![TrustCheck Package Scanner](https://img.shields.io/badge/GitHub%20Action-TrustCheck%20Package%20Scanner-blue?logo=githubactions&logoColor=white)](https://github.com/marketplace/actions/trustcheck-package-scanner)
64
64
 
65
65
  [![Get it from the Snap Store](https://snapcraft.io/en/dark/install.svg)](https://snapcraft.io/trustcheck)
66
+ <a href="https://pypi.org/project/trustcheck/"><img src="https://raw.githubusercontent.com/Halfblood-Prince/trustcheck/coverage-badge/PyPI.png" alt="Get it from PyPI" height="55"></a>
66
67
 
67
68
  `trustcheck` is a Python package and CLI for evaluating the trust posture of PyPI releases before they are installed, promoted, or approved.
68
69
 
@@ -81,6 +82,20 @@ Packages that publish no provenance are treated as needing review rather than as
81
82
  | `manifest` | lock approved trust evidence |
82
83
  | `impact` | prioritize by observed usage |
83
84
 
85
+ <!-- trustcheck-benchmark:start -->
86
+ ## Latest benchmark
87
+
88
+ Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
89
+
90
+ | Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
91
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
92
+ | trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
93
+ | pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
94
+
95
+ Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
96
+ Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
97
+ <!-- trustcheck-benchmark:end -->
98
+
84
99
  ## What it checks
85
100
 
86
101
  For a selected package version, `trustcheck` can:
@@ -141,20 +156,6 @@ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
141
156
  are retained as workflow artifacts by
142
157
  [Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
143
158
 
144
- <!-- trustcheck-benchmark:start -->
145
- ## Latest benchmark
146
-
147
- Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
148
-
149
- | Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
150
- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
151
- | trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
152
- | pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
153
-
154
- Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
155
- Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
156
- <!-- trustcheck-benchmark:end -->
157
-
158
159
  ## Installation
159
160
 
160
161
  Install from PyPI:
@@ -101,6 +101,7 @@ scripts/build_msix_layout.py
101
101
  scripts/build_standalone.py
102
102
  scripts/check_mutation_score.py
103
103
  scripts/dependency_bounds.py
104
+ scripts/export_homebrew_tap.py
104
105
  scripts/github_plagiarism_scan.py
105
106
  scripts/smoke_test_distribution.py
106
107
  scripts/trustcheck_binary.py
@@ -188,6 +189,7 @@ tests/test_dynamic.py
188
189
  tests/test_edge_cases.py
189
190
  tests/test_exports.py
190
191
  tests/test_github_action.py
192
+ tests/test_homebrew_tap_export.py
191
193
  tests/test_impact.py
192
194
  tests/test_indexes.py
193
195
  tests/test_install_command.py
@@ -137,6 +137,11 @@ class CoverageBadgeWorkflowTests(unittest.TestCase):
137
137
  self.assertIn("github.event_name == 'push'", workflow)
138
138
  self.assertIn("group: coverage-badge", workflow)
139
139
  self.assertIn("cancel-in-progress: true", workflow)
140
+ self.assertIn("git -C \"$publish_root\" fetch --depth=1 origin coverage-badge", workflow)
141
+ self.assertIn("show FETCH_HEAD:PyPI.png", workflow)
142
+ self.assertIn("refusing to replace the asset branch", workflow)
143
+ self.assertIn("\"without it.\"", workflow)
144
+ self.assertIn("git -C \"$publish_root\" add PyPI.png coverage.svg", workflow)
140
145
  self.assertIn("git -C \"$publish_root\" push --force origin coverage-badge", workflow)
141
146
  self.assertNotIn("git diff --exit-code -- docs/assets/images/coverage.svg", workflow)
142
147