pytest-selective 0.4.2__tar.gz → 1.0.0__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 (187) hide show
  1. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/test-lanes.json +28 -2
  2. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/ci.yml +45 -8
  3. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/public-actions.yml +5 -1
  4. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/release.yml +4 -0
  5. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.gitignore +3 -0
  6. pytest_selective-1.0.0/.release-please-manifest.json +3 -0
  7. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/AGENTS.md +5 -0
  8. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/CHANGELOG.md +15 -0
  9. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/CONTRIBUTING.md +34 -2
  10. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/PKG-INFO +37 -2
  11. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/README.md +36 -1
  12. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/RELEASING.md +23 -1
  13. pytest_selective-1.0.0/contracts/selection/README.md +33 -0
  14. pytest_selective-1.0.0/contracts/selection/conftest.py +297 -0
  15. pytest_selective-1.0.0/contracts/selection/quint-connect.json +107 -0
  16. pytest_selective-1.0.0/contracts/selection/quint.ini +8 -0
  17. pytest_selective-1.0.0/contracts/selection/selection.qnt +336 -0
  18. pytest_selective-1.0.0/contracts/selection/selection_driver.py +112 -0
  19. pytest_selective-1.0.0/contracts/selection/test_selection_contract.py +4 -0
  20. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/ci-toolkit.md +5 -1
  21. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/ci.md +12 -1
  22. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/configuration.md +1 -1
  23. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorials/adoption-checklist.md +5 -0
  24. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorials/skip-unrelated-ci.md +33 -0
  25. pytest_selective-1.0.0/docs/work-in-progress/tickets/graph-based-inputs-plan.md +58 -0
  26. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/pyproject.toml +3 -1
  27. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/skills/pytest-selective/SKILL.md +8 -0
  28. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/skills/pytest-selective/references/adoption.md +3 -0
  29. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/ci.py +9 -4
  30. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_graph.py +116 -3
  31. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_models.py +3 -0
  32. pytest_selective-1.0.0/tests/test_release_workflow.py +36 -0
  33. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_repository_ci.py +90 -29
  34. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_ci.py +53 -0
  35. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_graph.py +69 -0
  36. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/uv.lock +18 -2
  37. pytest_selective-0.4.2/.release-please-manifest.json +0 -3
  38. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.dry-lint/baseline.json +0 -0
  39. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.dry-lint/progress.json +0 -0
  40. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.dry-lint.toml +0 -0
  41. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.gitattributes +0 -0
  42. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  43. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  44. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/dependabot.yml +0 -0
  45. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/docs.yml +0 -0
  46. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/dry-lint-audit.yml +0 -0
  47. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/dry-lint-judge.yml +0 -0
  48. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/dry-lint.yml +0 -0
  49. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/observe-ci.yml +0 -0
  50. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/pr-title.yml +0 -0
  51. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/release-please.yml +0 -0
  52. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/windows-performance.yml +0 -0
  53. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.pre-commit-config.yaml +0 -0
  54. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.readthedocs.yaml +0 -0
  55. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/CLAUDE.md +0 -0
  56. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/LICENSE +0 -0
  57. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/PROVENANCE.md +0 -0
  58. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/action.yml +0 -0
  59. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/README.md +0 -0
  60. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/plan-lanes/action.yml +0 -0
  61. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/pytest-expansion/action.yml +0 -0
  62. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/test-impact/action.yml +0 -0
  63. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/agentsync.toml +0 -0
  64. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/README.md +0 -0
  65. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/actions.md +0 -0
  66. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/agents.md +0 -0
  67. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/banner.png +0 -0
  68. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/banner.svg +0 -0
  69. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/brand.json +0 -0
  70. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/icon.png +0 -0
  71. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/logo.svg +0 -0
  72. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/raster-sources.json +0 -0
  73. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/social-preview.png +0 -0
  74. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/social-preview.svg +0 -0
  75. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
  76. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
  77. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
  78. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
  79. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
  80. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
  81. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-obligation.json +0 -0
  82. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-routing.md +0 -0
  83. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
  84. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22.md +0 -0
  85. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
  86. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
  87. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
  88. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
  89. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
  90. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
  91. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
  92. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
  93. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
  94. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/windows-runtime.md +0 -0
  95. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/comparison-pytest-impacted.md +0 -0
  96. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/evidence.md +0 -0
  97. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/local-development.md +0 -0
  98. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/pytest-hygiene.md +0 -0
  99. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/selection-health.md +0 -0
  100. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorial.md +0 -0
  101. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorials/sidecar-dependencies.md +0 -0
  102. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
  103. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
  104. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
  105. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
  106. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
  107. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
  108. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
  109. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
  110. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/assets/routing-comparison.svg +0 -0
  111. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/check_site.py +0 -0
  112. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/ecosystem.md +0 -0
  113. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/hooks.py +0 -0
  114. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/index.md +0 -0
  115. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/overrides/main.html +0 -0
  116. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/publishing.md +0 -0
  117. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/requirements.in +0 -0
  118. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/requirements.txt +0 -0
  119. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/mkdocs.yml +0 -0
  120. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/release-please-config.json +0 -0
  121. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/benchmark_graph.py +0 -0
  122. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/benchmark_parsing.py +0 -0
  123. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/benchmark_rust.py +0 -0
  124. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/check_docs.py +0 -0
  125. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/check_metadata.py +0 -0
  126. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/ci_file_config.py +0 -0
  127. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/compare_impacted.py +0 -0
  128. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/measure_ci_runs.py +0 -0
  129. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/public_actions.py +0 -0
  130. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/render_brand.py +0 -0
  131. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/run_ci_tests.py +0 -0
  132. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/smoke_distribution.py +0 -0
  133. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/tutorial.py +0 -0
  134. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/skills/pytest-selective/agents/openai.yaml +0 -0
  135. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/__init__.py +0 -0
  136. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/__main__.py +0 -0
  137. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/ast_imports.py +0 -0
  138. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/checkpoint.py +0 -0
  139. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/git_operations.py +0 -0
  140. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/hygiene.py +0 -0
  141. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_audit.py +0 -0
  142. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_audit_plugin.py +0 -0
  143. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_cli.py +0 -0
  144. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_declarations.py +0 -0
  145. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_explanation.py +0 -0
  146. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_first_party.py +0 -0
  147. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_name_scope.py +0 -0
  148. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_selection.py +0 -0
  149. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/input_policy.py +0 -0
  150. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/local.py +0 -0
  151. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/parse_batch.py +0 -0
  152. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/plugin.py +0 -0
  153. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/py.typed +0 -0
  154. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/pytest_expansion.py +0 -0
  155. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/pytest_plugins.py +0 -0
  156. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/pytest_sharding.py +0 -0
  157. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/test_inventory.py +0 -0
  158. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/typecheck_scope.py +0 -0
  159. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/working_tree.py +0 -0
  160. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/__init__.py +0 -0
  161. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_checkpoint_policy.py +0 -0
  162. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_ci_admission.py +0 -0
  163. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_ci_measurements.py +0 -0
  164. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_ci_test_execution.py +0 -0
  165. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_distribution_metadata.py +0 -0
  166. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_hygiene.py +0 -0
  167. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_public_actions.py +0 -0
  168. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_pytest_expansion.py +0 -0
  169. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_repository_hygiene.py +0 -0
  170. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
  171. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_tutorial.py +0 -0
  172. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/__init__.py +0 -0
  173. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/git_repo.py +0 -0
  174. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_ast_imports.py +0 -0
  175. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_git_repo.py +0 -0
  176. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_global_inputs.py +0 -0
  177. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_audit.py +0 -0
  178. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_explanation.py +0 -0
  179. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_first_party.py +0 -0
  180. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_name_scope.py +0 -0
  181. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_parse_batch.py +0 -0
  182. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_portability.py +0 -0
  183. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_pytest_plugin.py +0 -0
  184. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_pytest_plugins.py +0 -0
  185. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_pytest_sharding.py +0 -0
  186. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_typecheck_scope.py +0 -0
  187. {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_working_tree.py +0 -0
@@ -7,6 +7,11 @@
7
7
  "test_roots": [
8
8
  "tests"
9
9
  ],
10
+ "global_inputs": [
11
+ "pyproject.toml",
12
+ "uv.lock",
13
+ ".python-version"
14
+ ],
10
15
  "pytest_policy": {
11
16
  "pyproject.toml": "7a78d68735c0e3b091962244620d3a17557951123c645970638f15d3a737d987"
12
17
  }
@@ -20,6 +25,27 @@
20
25
  "src/**",
21
26
  "tests/**"
22
27
  ]
28
+ },
29
+ "contracts": {
30
+ "roots": [
31
+ "contracts"
32
+ ],
33
+ "inputs": [
34
+ "src/**",
35
+ "contracts/**",
36
+ "tests/testing/git_repo.py"
37
+ ]
38
+ },
39
+ "package": {
40
+ "inputs": [
41
+ "src/**",
42
+ "pyproject.toml",
43
+ "uv.lock",
44
+ "README.md",
45
+ "LICENSE",
46
+ "scripts/check_metadata.py",
47
+ "scripts/smoke_distribution.py"
48
+ ]
23
49
  }
24
50
  },
25
51
  "global_inputs": [
@@ -33,9 +59,9 @@
33
59
  ".github/test-lanes.json",
34
60
  ".github/workflows/ci.yml",
35
61
  ".github/workflows/release.yml",
36
- "actions/**",
62
+ "actions/*/*",
63
+ "actions/*.py",
37
64
  "action.yml",
38
- "examples/**",
39
65
  ".github/workflows/public-actions.yml"
40
66
  ]
41
67
  }
@@ -6,6 +6,8 @@ on:
6
6
  types: [opened, synchronize, reopened]
7
7
  workflow_dispatch:
8
8
  workflow_call:
9
+ schedule:
10
+ - cron: '13 3 * * 1'
9
11
  permissions:
10
12
  contents: read
11
13
  concurrency:
@@ -92,13 +94,12 @@ jobs:
92
94
  with:
93
95
  junit-pattern: test-timings.xml
94
96
  artifact-name: test-timings-${{ matrix.python }}-${{ matrix.os }}
95
- - run: uv run --locked python scripts/tutorial.py --output tutorial-report.json
97
+ # tests/test_tutorial.py owns the end-to-end tutorial and follows the file plan.
96
98
  - uses: actions/upload-artifact@v7
99
+ if: ${{ hashFiles('coverage.xml') != '' }}
97
100
  with:
98
101
  name: evidence-${{ matrix.os }}-${{ matrix.python }}
99
- path: |
100
- coverage.xml
101
- tutorial-report.json
102
+ path: coverage.xml
102
103
  pytest-minimum:
103
104
  needs: select-tests
104
105
  if: >-
@@ -119,8 +120,10 @@ jobs:
119
120
  CI_TEST_FILES: ${{ needs.select-tests.outputs.files }}
120
121
  distributions:
121
122
  needs: select-tests
122
- # README changes still need build/metadata checks; planner failure must not skip them.
123
- if: ${{ !cancelled() }}
123
+ # The package lane includes the embedded README and LICENSE; planner failure must not skip it.
124
+ if: >-
125
+ ${{ always() && !cancelled() && (needs.select-tests.result != 'success' ||
126
+ fromJSON(needs.select-tests.outputs.lanes).package) }}
124
127
  runs-on: ubuntu-latest
125
128
  steps:
126
129
  - uses: actions/checkout@v7
@@ -141,9 +144,43 @@ jobs:
141
144
  path: dist/*
142
145
  if-no-files-found: error
143
146
 
147
+ selection-contract:
148
+ name: Selection contract (Quint)
149
+ needs: select-tests
150
+ permissions:
151
+ contents: read
152
+ # The contract report's pull-request comment.
153
+ pull-requests: write
154
+ if: >-
155
+ ${{ always() && !cancelled() && (needs.select-tests.result != 'success' ||
156
+ fromJSON(needs.select-tests.outputs.lanes).contracts) }}
157
+ runs-on: ubuntu-latest
158
+ steps:
159
+ - uses: actions/checkout@v7
160
+ - uses: gigaverse-app/ci-esta/actions/setup-cached-uv@v1
161
+ with:
162
+ python-version: '3.14'
163
+ # pytest-quint-connect installs its locked Quint with npm on first use.
164
+ - uses: actions/setup-node@v7
165
+ with:
166
+ node-version: '24'
167
+ - name: Check the generated driver against the model
168
+ run: uv run --locked --group quint pytest-quint-connect codegen --check contracts/selection/quint-connect.json
169
+ - name: Generate histories from the model and replay them against the planner
170
+ run: uv run --locked --group quint pytest -c contracts/selection/quint.ini contracts/selection --quint-require-complete -q
171
+ # Match the README with the same locked renderer that produced these receipts.
172
+ # A presentation update to the floating report action must not break an immutable release.
173
+ - name: Check the README against the locked contract renderer
174
+ run: uv run --locked --group quint pytest-quint-connect report '**/.artifacts/quint-connect/results.json' --readme README.md --check
175
+ # The current action owns the sticky and artifacts, independently of README formatting.
176
+ - uses: gigaverse-app/pytest-quint-connect/report@v1
177
+ if: ${{ always() }}
178
+ with:
179
+ readme-mode: 'off'
180
+
144
181
  validation-gate:
145
182
  name: Validation gate
146
- needs: [select-tests, quality, tests, pytest-minimum, distributions]
183
+ needs: [select-tests, quality, tests, pytest-minimum, distributions, selection-contract]
147
184
  if: ${{ !cancelled() }}
148
185
  runs-on: ubuntu-latest
149
186
  steps:
@@ -151,4 +188,4 @@ jobs:
151
188
  with:
152
189
  needs: ${{ toJSON(needs) }}
153
190
  lanes: ${{ needs.select-tests.outputs.lanes || '{}' }}
154
- lane-jobs: '{"tests":["tests"],"pytest-minimum":["tests"]}'
191
+ lane-jobs: '{"tests":["tests"],"pytest-minimum":["tests"],"distributions":["package"],"selection-contract":["contracts"]}'
@@ -4,6 +4,8 @@ on:
4
4
  push:
5
5
  branches: [main]
6
6
  workflow_dispatch:
7
+ schedule:
8
+ - cron: '33 3 * * 1'
7
9
  permissions:
8
10
  contents: read
9
11
  jobs:
@@ -67,7 +69,9 @@ jobs:
67
69
  run: |
68
70
  python - <<'PY'
69
71
  import json, os
70
- assert json.loads(os.environ['LANES']) == {'tests': True}
72
+ # A full checkpoint admits every configured lane.
73
+ configured = json.load(open('.github/test-lanes.json', encoding='utf-8'))['lanes']
74
+ assert json.loads(os.environ['LANES']) == dict.fromkeys(configured, True)
71
75
  PY
72
76
  - name: Require successful planning after fallback execution
73
77
  if: ${{ !cancelled() && needs.select-tests.result != 'success' }}
@@ -21,6 +21,10 @@ jobs:
21
21
  uv run --no-project python -c 'import os, pathlib, tomllib; version = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"]; assert os.environ["RELEASE_REF"] == f"refs/tags/v{version}", "Run publishing from the version-matching v tag"'
22
22
  checks:
23
23
  needs: validate-tag
24
+ # Reusable CI's Quint report requests this grant, even on a tag with no PR.
25
+ permissions:
26
+ contents: read
27
+ pull-requests: write
24
28
  uses: ./.github/workflows/ci.yml
25
29
  publish:
26
30
  needs: checks
@@ -18,3 +18,6 @@ tutorial-report.json
18
18
 
19
19
  # Local judge credentials
20
20
  .env
21
+
22
+ # pytest-quint-connect receipts, traces and logs
23
+ .artifacts/
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.0.0"
3
+ }
@@ -11,6 +11,11 @@ on pytest-selective itself.
11
11
  - Reproduce selection bugs using real temporary Git repositories and existing
12
12
  `tests/testing/git_repo.py` helpers. Cover base and head trees, deleted inputs,
13
13
  explicit declarations and the relevant fallback. Do not mock away selection.
14
+ - The planner's import, conftest, declaration and content-read closure, its parse-error
15
+ stop, its selection against older bases and its graph cache's transparency are also a
16
+ Quint contract in
17
+ `contracts/selection`. After changing selection, run it (CONTRIBUTING.md, *The selection
18
+ contract*); extend the model rather than weakening it when the promise changes.
14
19
  - Planning supports committed revisions and explicit working-tree snapshots. Preserve full execution on
15
20
  operational uncertainty and diagnostics for invalid declarations. An empty
16
21
  selection is not an empty pytest argument list.
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.3...v1.0.0) (2026-11-08)
4
+
5
+
6
+ ### Bug fixes
7
+
8
+ * **ci:** streamline dogfood checks and prepare the 1.0 release ([#97](https://github.com/gigaverse-app/pytest-selective/issues/97)) ([5c79edc](https://github.com/gigaverse-app/pytest-selective/commit/5c79edcc2e234ccd90902ef001d2852cf7ecfdbc))
9
+ * **selection:** follow star imports to the submodules a package lists in __all__ ([#94](https://github.com/gigaverse-app/pytest-selective/issues/94)) ([ea02fbf](https://github.com/gigaverse-app/pytest-selective/commit/ea02fbfe85c2d5b6e8685a4b159215d421f78653))
10
+
11
+ ## [0.4.3](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.2...v0.4.3) (2026-11-08)
12
+
13
+
14
+ ### Bug fixes
15
+
16
+ * **lanes:** select lanes through the graph and match ** like gitignore ([#90](https://github.com/gigaverse-app/pytest-selective/issues/90)) ([107b021](https://github.com/gigaverse-app/pytest-selective/commit/107b0212ed741ac72c36a39cc864906eca75d1ab))
17
+
3
18
  ## [0.4.2](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.1...v0.4.2) (2026-11-07)
4
19
 
5
20
 
@@ -32,6 +32,31 @@ Changes to collection policy, runtime auditing, or the composite action need tes
32
32
  at that boundary. Update the tutorial when the public command contract changes.
33
33
  Keep performance claims tied to reproducible measurements and their limitations.
34
34
 
35
+ ### The selection contract
36
+
37
+ [contracts/selection](contracts/selection/README.md) states the planner's core
38
+ promise as a [Quint](https://quint.sh) model and checks it with
39
+ [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect):
40
+ generated histories of real commits, each selected by `affected()` and compared
41
+ with the model. It runs as its own CI lane, outside the xdist suite, and needs
42
+ Node.js 22+ for Quint:
43
+
44
+ ```bash
45
+ uv run --group quint pytest -c contracts/selection/quint.ini contracts/selection --quint-require-complete
46
+ ```
47
+
48
+ When selection semantics change on purpose, change `selection.qnt` with them and
49
+ regenerate the driver (`pytest-quint-connect codegen contracts/selection/quint-connect.json`).
50
+ Never drop a witness, mutant or observed field to make it pass.
51
+
52
+ CI reports each run in one pull-request comment and checks the README's contract block
53
+ against it. After a change to the contract's cases, refresh the block from a complete
54
+ local run and commit it:
55
+
56
+ ```bash
57
+ uv run --group quint pytest-quint-connect report '**/.artifacts/quint-connect/results.json' --readme README.md
58
+ ```
59
+
35
60
  ### Shared implementation owners
36
61
 
37
62
  | Contract | Owner |
@@ -72,8 +97,9 @@ limitations. The pull request template provides a short checklist. See
72
97
  ## CI lane selection
73
98
 
74
99
  This repository uses its own `plan-lanes` action. Documentation-only PRs keep
75
- quality checks (including Markdown links) and distribution build/metadata checks,
76
- while skipping the six-platform pytest matrix and minimum-pytest lane. Within an
100
+ quality checks (including Markdown links) and the documentation build, while
101
+ skipping every test lane and the distribution build. A README or LICENSE change
102
+ also runs the `package` lane, because both distributions embed them. Within an
77
103
  admitted lane, the repository's public file-selection action selects affected
78
104
  test files once; the platform matrix and minimum-pytest job execute that same
79
105
  plan. CI-esta receives both lane and file explanations, learns JUnit timings,
@@ -171,6 +197,12 @@ history or failed planning must execute the full affected lanes and fail the
171
197
  required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
172
198
  remain complete checkpoints.
173
199
 
200
+ CI and public-action smoke run full checkpoints every Monday. Ordinary runs keep
201
+ the shared affected-file plan across all six OS/Python combinations and minimum
202
+ pytest. `tests/test_tutorial.py` owns the end-to-end tutorial execution and follows
203
+ that plan; there is no second tutorial invocation in each matrix job. JUnit retains
204
+ its test result and timing, and full plans also upload whole-suite coverage.
205
+
174
206
  ## Duplication
175
207
 
176
208
  `uv run pre-commit install` installs both the staged dry-lint check and the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pytest-selective
3
- Version: 0.4.2
3
+ Version: 1.0.0
4
4
  Summary: Explainable Python test impact analysis: select affected pytest files from Git changes
5
5
  Project-URL: Homepage, https://github.com/gigaverse-app/pytest-selective
6
6
  Project-URL: Documentation, https://pytest-selective.readthedocs.io/en/latest/
@@ -233,7 +233,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
233
233
  | --- | --- |
234
234
  | A transitive dependency | Import chains and package initialization |
235
235
  | A deleted or renamed module | Dependencies in both the base and head trees |
236
- | Fixtures or startup code | Ancestor conftests and configured global modules |
236
+ | Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
237
237
  | Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
238
238
  | A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
239
239
  | A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
@@ -262,6 +262,41 @@ and evidence checks.
262
262
 
263
263
  [Read the evidence, safety boundaries, and adoption checklist](https://github.com/gigaverse-app/pytest-selective/blob/main/docs/evidence.md).
264
264
 
265
+ ### Selection checked against a model
266
+
267
+ The planner's core promise is also a [Quint](https://quint.sh) model. CI replays generated
268
+ histories of real commits through the planner and requires every deliberately broken
269
+ planner to be caught ([selection contract](https://github.com/gigaverse-app/pytest-selective/blob/main/contracts/selection/README.md)). The block below
270
+ is the latest complete run, checked by
271
+ [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) on every
272
+ change that runs the contract.
273
+
274
+ <!-- pytest-quint-connect:start -->
275
+ <!-- pytest-quint-connect:contracts selection -->
276
+ <a href="https://github.com/gigaverse-app/pytest-quint-connect#impact"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&amp;name=gigaverse-app%2Fpytest-selective&amp;theme=dark"><img alt="What pytest-quint-connect&#x27;s contracts caught in gigaverse-app/pytest-selective&#x27;s pull requests" src="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&amp;name=gigaverse-app%2Fpytest-selective" width="100%"></picture></a>
277
+
278
+ <a href="https://github.com/gigaverse-app/pytest-quint-connect" title="Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug"><img alt="Quint contracts: 1 contract passed · 44 histories · 10/10 planted bugs caught" src="https://img.shields.io/badge/Quint_contracts-1_contract_passed_%C2%B7_44_histories_%C2%B7_10%2F10_planted_bugs_caught-brightgreen"></a>
279
+
280
+ | | Contract | Cases passed | [Planted bugs caught](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") |
281
+ | :-: | :-- | --: | --: |
282
+ | ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>44 histories (20 witnesses, 24 samples, seed 11) · every action covered · Quint 0.33.1</sub> | 55/55 | 10/10 |
283
+
284
+ **[Planted bugs](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") this contract catches**
285
+
286
+ - `head-edges-only`: Plans with the head tree's edges only, so a deleted module's former importers are not selected. Caught by `witness-deletedModuleSelectsFormerImportersTest` at state 1, after `remove(file='beta', target='')`.
287
+ - `direct-importers-only`: Follows only files that import a changed file directly, not their importers. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
288
+ - `global-inputs-ignored`: Treats requirements.txt like any unreferenced file instead of selecting every test. Caught by `witness-globalInputSelectsEveryTestTest` at state 1, after `touchGlobal(file='', target='')`.
289
+ - `content-follows-imports`: Reads a kind: content declaration as an import, so it also follows the read file's imports. Caught by `witness-contentReadIsNotTransitiveTest` at state 2, after `edit(file='beta', target='')`.
290
+ - `conftest-scope-collapsed`: Scopes every conftest to the top test directory, so tests/sub/conftest.py reaches every test. Caught by `witness-nestedConftestSelectsItsSubtreeTest` at state 1, after `edit(file='subconftest', target='')`.
291
+ - `parse-errors-ignored`: Treats an unparseable file as importing nothing instead of stopping with an error. Caught by `witness-unparseableSourceIsAnErrorTest` at state 1, after `breakSyntax(file='beta', target='')`.
292
+ - `assumes-consecutive-base`: Plans every run against the head it planned last, which is right only between neighbouring commits. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
293
+ - `cache-key-omits-test-roots`: Leaves test_roots out of the cached graph's key, so a graph built for the old roots is reused after they change. Caught by `witness-narrowedTestRootsReplanFromScratchTest` at state 2, after `toggleConfig(file='narrow', target='')`.
294
+ - `parse-cache-keyed-by-path`: Caches parsed files by path instead of content, so an edited file keeps its old imports. Caught by `witness-importDeclarationFollowsImportsTest` at state 3, after `edit(file='beta', target='')`.
295
+ - `cache-checksum-ignored`: Trusts a stored graph without checking its checksum, so a damaged entry is used as if it were valid. Caught by `witness-damagedCacheIsRebuiltTest` at state 2, after `corrupt(file='', target='')`.
296
+
297
+ <sub>Generated by [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) from contract receipts; [what a passing run proves](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/limits.md).</sub>
298
+ <!-- pytest-quint-connect:end -->
299
+
265
300
  ## Choose the right tool
266
301
 
267
302
  [pytest-impacted](https://github.com/promptromp/pytest-impacted) offers a pytest-native
@@ -202,7 +202,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
202
202
  | --- | --- |
203
203
  | A transitive dependency | Import chains and package initialization |
204
204
  | A deleted or renamed module | Dependencies in both the base and head trees |
205
- | Fixtures or startup code | Ancestor conftests and configured global modules |
205
+ | Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
206
206
  | Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
207
207
  | A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
208
208
  | A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
@@ -231,6 +231,41 @@ and evidence checks.
231
231
 
232
232
  [Read the evidence, safety boundaries, and adoption checklist](https://github.com/gigaverse-app/pytest-selective/blob/main/docs/evidence.md).
233
233
 
234
+ ### Selection checked against a model
235
+
236
+ The planner's core promise is also a [Quint](https://quint.sh) model. CI replays generated
237
+ histories of real commits through the planner and requires every deliberately broken
238
+ planner to be caught ([selection contract](https://github.com/gigaverse-app/pytest-selective/blob/main/contracts/selection/README.md)). The block below
239
+ is the latest complete run, checked by
240
+ [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) on every
241
+ change that runs the contract.
242
+
243
+ <!-- pytest-quint-connect:start -->
244
+ <!-- pytest-quint-connect:contracts selection -->
245
+ <a href="https://github.com/gigaverse-app/pytest-quint-connect#impact"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&amp;name=gigaverse-app%2Fpytest-selective&amp;theme=dark"><img alt="What pytest-quint-connect&#x27;s contracts caught in gigaverse-app/pytest-selective&#x27;s pull requests" src="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&amp;name=gigaverse-app%2Fpytest-selective" width="100%"></picture></a>
246
+
247
+ <a href="https://github.com/gigaverse-app/pytest-quint-connect" title="Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug"><img alt="Quint contracts: 1 contract passed · 44 histories · 10/10 planted bugs caught" src="https://img.shields.io/badge/Quint_contracts-1_contract_passed_%C2%B7_44_histories_%C2%B7_10%2F10_planted_bugs_caught-brightgreen"></a>
248
+
249
+ | | Contract | Cases passed | [Planted bugs caught](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") |
250
+ | :-: | :-- | --: | --: |
251
+ | ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>44 histories (20 witnesses, 24 samples, seed 11) · every action covered · Quint 0.33.1</sub> | 55/55 | 10/10 |
252
+
253
+ **[Planted bugs](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") this contract catches**
254
+
255
+ - `head-edges-only`: Plans with the head tree's edges only, so a deleted module's former importers are not selected. Caught by `witness-deletedModuleSelectsFormerImportersTest` at state 1, after `remove(file='beta', target='')`.
256
+ - `direct-importers-only`: Follows only files that import a changed file directly, not their importers. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
257
+ - `global-inputs-ignored`: Treats requirements.txt like any unreferenced file instead of selecting every test. Caught by `witness-globalInputSelectsEveryTestTest` at state 1, after `touchGlobal(file='', target='')`.
258
+ - `content-follows-imports`: Reads a kind: content declaration as an import, so it also follows the read file's imports. Caught by `witness-contentReadIsNotTransitiveTest` at state 2, after `edit(file='beta', target='')`.
259
+ - `conftest-scope-collapsed`: Scopes every conftest to the top test directory, so tests/sub/conftest.py reaches every test. Caught by `witness-nestedConftestSelectsItsSubtreeTest` at state 1, after `edit(file='subconftest', target='')`.
260
+ - `parse-errors-ignored`: Treats an unparseable file as importing nothing instead of stopping with an error. Caught by `witness-unparseableSourceIsAnErrorTest` at state 1, after `breakSyntax(file='beta', target='')`.
261
+ - `assumes-consecutive-base`: Plans every run against the head it planned last, which is right only between neighbouring commits. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
262
+ - `cache-key-omits-test-roots`: Leaves test_roots out of the cached graph's key, so a graph built for the old roots is reused after they change. Caught by `witness-narrowedTestRootsReplanFromScratchTest` at state 2, after `toggleConfig(file='narrow', target='')`.
263
+ - `parse-cache-keyed-by-path`: Caches parsed files by path instead of content, so an edited file keeps its old imports. Caught by `witness-importDeclarationFollowsImportsTest` at state 3, after `edit(file='beta', target='')`.
264
+ - `cache-checksum-ignored`: Trusts a stored graph without checking its checksum, so a damaged entry is used as if it were valid. Caught by `witness-damagedCacheIsRebuiltTest` at state 2, after `corrupt(file='', target='')`.
265
+
266
+ <sub>Generated by [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) from contract receipts; [what a passing run proves](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/limits.md).</sub>
267
+ <!-- pytest-quint-connect:end -->
268
+
234
269
  ## Choose the right tool
235
270
 
236
271
  [pytest-impacted](https://github.com/promptromp/pytest-impacted) offers a pytest-native
@@ -48,9 +48,31 @@ version: publishing Python `0.x.y` does not prevent updating Actions `v1`.
48
48
  Consumers deliberately use `actions/NAME@v1` as a moving target. Change the
49
49
  Actions major only when deliberately establishing a new Actions API line.
50
50
  Package requirements use their own compatible ranges, such as
51
- `pytest-selective>=0.2,<0.3`; lockfiles retain resolved versions and artifact
51
+ `pytest-selective>=1.0,<2`; lockfiles retain resolved versions and artifact
52
52
  hashes. Prereleases do not advance the Actions tag.
53
53
 
54
+ ## Transition to Python 1.x
55
+
56
+ Request the first stable release with a one-time `Release-As: 1.0.0` footer in
57
+ the merged commit. Preserve that footer in the squash message. Release Please
58
+ then updates `pyproject.toml`, `uv.lock`, the manifest and changelog together in
59
+ its normal release PR. Do not keep a permanent `release-as` override, which
60
+ would interfere with subsequent automatic version bumps.
61
+
62
+ The package's stable-version transition keeps the current CLI, imports and
63
+ Actions inputs. `v1.0.0` is the immutable package tag; `v1` remains the moving
64
+ Actions compatibility tag and advances only after successful PyPI publication.
65
+ Existing `0.4.x` pins remain installable. Consumers that want the new package
66
+ must allow 1.x in their dependency range; CI-esta accepts both the existing
67
+ published 0.4.2 and 1.x while retaining its tested lockfile.
68
+
69
+ Before merging the 1.0 release PR, verify the Publish caller grants all
70
+ permissions requested by reusable CI. In particular, the Quint report requests
71
+ `pull-requests: write`; GitHub checks that grant even on release tags. Keep
72
+ OIDC permission confined to the separate `pypi` upload job. The 0.4.3 GitHub
73
+ release failed at workflow startup, before PyPI upload or movement of `v1`;
74
+ leave that immutable tag intact and publish the corrected pipeline as 1.0.0.
75
+
54
76
  For an Actions-only publication, advance `v1` to the exact reviewed main commit
55
77
  after its complete CI passes. This updates the Git tag without creating a GitHub
56
78
  release or invoking PyPI publication. Do not move immutable package release tags.
@@ -0,0 +1,33 @@
1
+ # Selection contract
2
+
3
+ pytest-selective's central promise, as a [Quint](https://quint.sh) model. After a commit,
4
+ the selected tests are exactly the tests that reach a changed file, in the base or the head
5
+ tree, through imports, an ancestor `conftest.py` or a `# gv:test-dependency:` comment
6
+ (import kind, so it follows the declared file's imports), plus the readers of a changed
7
+ file's contents (a `kind: content` sidecar, which does not follow imports). A change to a
8
+ global input selects every test. Unparseable Python in either tree is an error. The planner's
9
+ graph cache is not part of the promise: no configuration edit, cache restart or damaged cache
10
+ entry may change an answer.
11
+
12
+ [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) generates
13
+ histories from [selection.qnt](selection.qnt). [conftest.py](conftest.py) replays each step
14
+ as a real Git commit in a small fixture repository, then runs `affected()` twice through one
15
+ `GraphCache`: against the previous commit, and against the history's first commit, as a pull
16
+ request's base and head. Configuration edits, cache restarts and damaged cache entries are
17
+ followed by a re-plan of the last commit through the same cache. The plugin compares every
18
+ selection with the model's.
19
+
20
+ | Case | What it checks |
21
+ | --- | --- |
22
+ | 20 witnesses | transitive edits; a deleted module's former importers; declared reads, added and removed; root and nested conftests; a global input; an added import; a deleted test; a test-root helper module; content reads that do not follow imports; import declarations that do; unparseable source; a reverted import that is no change against the first commit; selection across several commits; narrowed `test_roots` after a cached plan; an extra global input; a damaged cache; a restarted cache |
23
+ | 24 samples | seeded random histories of up to 8 operations mixing every action |
24
+ | 10 mutants | the planner with base-tree edges dropped, only direct importers followed, global inputs ignored, content reads followed like imports, conftest scope collapsed to the test root, parse errors ignored, every base assumed to be the last planned head, `test_roots` left out of the graph cache key, parsed files cached by path, and cache checksums ignored; each must be caught |
25
+ | coverage | every action reached |
26
+
27
+ ```bash
28
+ uv run --group quint pytest -c quint.ini . --quint-require-complete # from this directory
29
+ ```
30
+
31
+ The [manifest](quint-connect.json) lists what the model leaves out: first-party pins,
32
+ name-scoped modules, deferred strings, Django URLconf policy and plugin-registering test
33
+ modules have their own tests in `tests/`.