pytest-obligation 0.8.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 (376) hide show
  1. pytest_obligation-0.8.0/.agents/plugins/marketplace.json +20 -0
  2. pytest_obligation-0.8.0/.claude/skills/find-upstream-weaknesses/SKILL.md +91 -0
  3. pytest_obligation-0.8.0/.claude-plugin/marketplace.json +12 -0
  4. pytest_obligation-0.8.0/.gitattributes +1 -0
  5. pytest_obligation-0.8.0/.github/workflows/ci.yml +470 -0
  6. pytest_obligation-0.8.0/.github/workflows/pr-title.yml +34 -0
  7. pytest_obligation-0.8.0/.github/workflows/release.yml +193 -0
  8. pytest_obligation-0.8.0/.gitignore +12 -0
  9. pytest_obligation-0.8.0/.python-version +1 -0
  10. pytest_obligation-0.8.0/.release-please-manifest.json +1 -0
  11. pytest_obligation-0.8.0/ADOPTING.md +285 -0
  12. pytest_obligation-0.8.0/ARCHITECTURE.md +159 -0
  13. pytest_obligation-0.8.0/CHANGELOG.md +148 -0
  14. pytest_obligation-0.8.0/CONTRIBUTING.md +63 -0
  15. pytest_obligation-0.8.0/LICENSE +202 -0
  16. pytest_obligation-0.8.0/PKG-INFO +227 -0
  17. pytest_obligation-0.8.0/README.md +172 -0
  18. pytest_obligation-0.8.0/RELEASING.md +75 -0
  19. pytest_obligation-0.8.0/check/action.yml +88 -0
  20. pytest_obligation-0.8.0/demos/README.md +164 -0
  21. pytest_obligation-0.8.0/demos/__init__.py +0 -0
  22. pytest_obligation-0.8.0/demos/cached_database.py +73 -0
  23. pytest_obligation-0.8.0/demos/dbos_transactional_outbox/__init__.py +0 -0
  24. pytest_obligation-0.8.0/demos/dbos_transactional_outbox/conftest.py +4 -0
  25. pytest_obligation-0.8.0/demos/dbos_transactional_outbox/pyproject.toml +9 -0
  26. pytest_obligation-0.8.0/demos/dbos_transactional_outbox/run_demo_process.py +56 -0
  27. pytest_obligation-0.8.0/demos/dbos_transactional_outbox/test_handoff_inventory.py +24 -0
  28. pytest_obligation-0.8.0/demos/dbos_transactional_outbox/test_transactional_outbox.py +408 -0
  29. pytest_obligation-0.8.0/demos/fetch_upstream.py +46 -0
  30. pytest_obligation-0.8.0/demos/procrastinate_demo_django/__init__.py +0 -0
  31. pytest_obligation-0.8.0/demos/procrastinate_demo_django/conftest.py +6 -0
  32. pytest_obligation-0.8.0/demos/procrastinate_demo_django/pyproject.toml +5 -0
  33. pytest_obligation-0.8.0/demos/procrastinate_demo_django/settings.py +16 -0
  34. pytest_obligation-0.8.0/demos/procrastinate_demo_django/test_demo_django.py +417 -0
  35. pytest_obligation-0.8.0/demos/procrastinate_demo_django/test_handoff_inventory.py +20 -0
  36. pytest_obligation-0.8.0/demos/saleor_checkout/conftest.py +26 -0
  37. pytest_obligation-0.8.0/demos/saleor_checkout/prepare_db.py +57 -0
  38. pytest_obligation-0.8.0/demos/saleor_checkout/pyproject.toml +153 -0
  39. pytest_obligation-0.8.0/demos/saleor_checkout/run.sh +50 -0
  40. pytest_obligation-0.8.0/demos/saleor_checkout/test_handoff_inventory.py +23 -0
  41. pytest_obligation-0.8.0/demos/saleor_checkout/test_order_confirmation.py +775 -0
  42. pytest_obligation-0.8.0/demos/saleor_checkout/test_payment_evidence.py +285 -0
  43. pytest_obligation-0.8.0/demos/test_readme_gif.py +92 -0
  44. pytest_obligation-0.8.0/demos/throwaway_postgres.sh +16 -0
  45. pytest_obligation-0.8.0/demos/wagtail_tasks/__init__.py +0 -0
  46. pytest_obligation-0.8.0/demos/wagtail_tasks/cdn.py +15 -0
  47. pytest_obligation-0.8.0/demos/wagtail_tasks/conftest.py +19 -0
  48. pytest_obligation-0.8.0/demos/wagtail_tasks/pyproject.toml +20 -0
  49. pytest_obligation-0.8.0/demos/wagtail_tasks/settings.py +35 -0
  50. pytest_obligation-0.8.0/demos/wagtail_tasks/test_feature_detection.py +90 -0
  51. pytest_obligation-0.8.0/demos/wagtail_tasks/test_wagtail_tasks.py +557 -0
  52. pytest_obligation-0.8.0/docs/adopter-capabilities.md +64 -0
  53. pytest_obligation-0.8.0/docs/aiokafka.md +48 -0
  54. pytest_obligation-0.8.0/docs/async-integrations-plan.md +15 -0
  55. pytest_obligation-0.8.0/docs/coverage.md +38 -0
  56. pytest_obligation-0.8.0/docs/demo.tape +77 -0
  57. pytest_obligation-0.8.0/docs/demo_env.sh +10 -0
  58. pytest_obligation-0.8.0/docs/false-greens.md +107 -0
  59. pytest_obligation-0.8.0/docs/how-it-works.md +94 -0
  60. pytest_obligation-0.8.0/docs/integrations.md +32 -0
  61. pytest_obligation-0.8.0/docs/interleavings.md +129 -0
  62. pytest_obligation-0.8.0/docs/jepsen-analogy.md +80 -0
  63. pytest_obligation-0.8.0/docs/mongodb.md +39 -0
  64. pytest_obligation-0.8.0/docs/package-migration.md +46 -0
  65. pytest_obligation-0.8.0/docs/plans/backend-conformance-port.md +34 -0
  66. pytest_obligation-0.8.0/docs/plans/deep-adopter-coverage.md +26 -0
  67. pytest_obligation-0.8.0/docs/plans/generated-capability-adoption.md +18 -0
  68. pytest_obligation-0.8.0/docs/prefect.md +44 -0
  69. pytest_obligation-0.8.0/docs/pytest-plugin.md +103 -0
  70. pytest_obligation-0.8.0/docs/upstream-playbook.md +209 -0
  71. pytest_obligation-0.8.0/docs/upstream-templates/README.md +17 -0
  72. pytest_obligation-0.8.0/docs/upstream-templates/eli5-issue.md +23 -0
  73. pytest_obligation-0.8.0/docs/upstream-templates/eli5-pr.md +26 -0
  74. pytest_obligation-0.8.0/docs/upstream-templates/issue.md +40 -0
  75. pytest_obligation-0.8.0/docs/upstream-templates/map-the-handoffs.md +35 -0
  76. pytest_obligation-0.8.0/docs/upstream-templates/pr.md +59 -0
  77. pytest_obligation-0.8.0/docs/what-a-green-result-means.md +286 -0
  78. pytest_obligation-0.8.0/docs/work-in-progress/tickets/canonical-obligation-api-test-plan.md +16 -0
  79. pytest_obligation-0.8.0/examples/adopter/README.md +65 -0
  80. pytest_obligation-0.8.0/examples/adopter/__init__.py +1 -0
  81. pytest_obligation-0.8.0/examples/adopter/adopter_app/__init__.py +0 -0
  82. pytest_obligation-0.8.0/examples/adopter/adopter_app/catalog.py +119 -0
  83. pytest_obligation-0.8.0/examples/adopter/adopter_app/summaries.py +26 -0
  84. pytest_obligation-0.8.0/examples/adopter/pyproject.toml +25 -0
  85. pytest_obligation-0.8.0/examples/adopter/test_bindings.py +380 -0
  86. pytest_obligation-0.8.0/examples/adopter/tests/__init__.py +0 -0
  87. pytest_obligation-0.8.0/examples/adopter/tests/conftest.py +4 -0
  88. pytest_obligation-0.8.0/examples/adopter/tests/test_catalog.py +12 -0
  89. pytest_obligation-0.8.0/examples/adopter/tests/test_catalog_failures.py +70 -0
  90. pytest_obligation-0.8.0/examples/adopter/tests/test_summaries_exemption.py +22 -0
  91. pytest_obligation-0.8.0/examples/adopter/uv.lock +350 -0
  92. pytest_obligation-0.8.0/plugins/pytest-obligation/.claude-plugin/plugin.json +10 -0
  93. pytest_obligation-0.8.0/plugins/pytest-obligation/.codex-plugin/plugin.json +30 -0
  94. pytest_obligation-0.8.0/plugins/pytest-obligation/README.md +14 -0
  95. pytest_obligation-0.8.0/plugins/pytest-obligation/assets/icon.svg +6 -0
  96. pytest_obligation-0.8.0/plugins/pytest-obligation/skills/probe-upstream-due-work/SKILL.md +18 -0
  97. pytest_obligation-0.8.0/plugins/pytest-obligation/skills/prove-due-work/SKILL.md +28 -0
  98. pytest_obligation-0.8.0/plugins/pytest-obligation/skills/prove-due-work/references/integrations.md +16 -0
  99. pytest_obligation-0.8.0/pyproject.toml +120 -0
  100. pytest_obligation-0.8.0/release-please-config.json +26 -0
  101. pytest_obligation-0.8.0/scripts/check_adoption_report.py +63 -0
  102. pytest_obligation-0.8.0/scripts/check_core_is_framework_free.py +35 -0
  103. pytest_obligation-0.8.0/shims/due_work_harness/__init__.py +70 -0
  104. pytest_obligation-0.8.0/shims/due_work_harness/__init__.pyi +2 -0
  105. pytest_obligation-0.8.0/shims/due_work_harness/binding.pyi +1 -0
  106. pytest_obligation-0.8.0/shims/due_work_harness/coherence.pyi +1 -0
  107. pytest_obligation-0.8.0/shims/due_work_harness/contract.pyi +1 -0
  108. pytest_obligation-0.8.0/shims/due_work_harness/coverage/__init__.pyi +1 -0
  109. pytest_obligation-0.8.0/shims/due_work_harness/coverage/cli.pyi +1 -0
  110. pytest_obligation-0.8.0/shims/due_work_harness/coverage/config.pyi +1 -0
  111. pytest_obligation-0.8.0/shims/due_work_harness/coverage/declarations.pyi +1 -0
  112. pytest_obligation-0.8.0/shims/due_work_harness/coverage/handoffs.pyi +1 -0
  113. pytest_obligation-0.8.0/shims/due_work_harness/coverage/project.pyi +1 -0
  114. pytest_obligation-0.8.0/shims/due_work_harness/coverage/report.pyi +1 -0
  115. pytest_obligation-0.8.0/shims/due_work_harness/coverage/scan.pyi +1 -0
  116. pytest_obligation-0.8.0/shims/due_work_harness/coverage/sites.pyi +1 -0
  117. pytest_obligation-0.8.0/shims/due_work_harness/crash_histories.pyi +1 -0
  118. pytest_obligation-0.8.0/shims/due_work_harness/evidence/__init__.pyi +1 -0
  119. pytest_obligation-0.8.0/shims/due_work_harness/evidence/observation.pyi +1 -0
  120. pytest_obligation-0.8.0/shims/due_work_harness/evidence/observation_report.pyi +1 -0
  121. pytest_obligation-0.8.0/shims/due_work_harness/exemptions.pyi +1 -0
  122. pytest_obligation-0.8.0/shims/due_work_harness/faults.pyi +1 -0
  123. pytest_obligation-0.8.0/shims/due_work_harness/gap_probes.pyi +1 -0
  124. pytest_obligation-0.8.0/shims/due_work_harness/helpers.pyi +1 -0
  125. pytest_obligation-0.8.0/shims/due_work_harness/host.pyi +1 -0
  126. pytest_obligation-0.8.0/shims/due_work_harness/integrations/__init__.pyi +1 -0
  127. pytest_obligation-0.8.0/shims/due_work_harness/integrations/aiokafka.pyi +1 -0
  128. pytest_obligation-0.8.0/shims/due_work_harness/integrations/celery.pyi +1 -0
  129. pytest_obligation-0.8.0/shims/due_work_harness/integrations/celery_worker.pyi +1 -0
  130. pytest_obligation-0.8.0/shims/due_work_harness/integrations/clocks.pyi +1 -0
  131. pytest_obligation-0.8.0/shims/due_work_harness/integrations/dbos.pyi +1 -0
  132. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/__init__.pyi +1 -0
  133. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/admission.pyi +1 -0
  134. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/callbacks.pyi +1 -0
  135. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/commands.pyi +1 -0
  136. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/commits.pyi +1 -0
  137. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/lifecycle_references.pyi +1 -0
  138. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/lifecycle_states.pyi +1 -0
  139. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/lock_order.pyi +1 -0
  140. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/receivers.pyi +1 -0
  141. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/references.pyi +1 -0
  142. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/selection.pyi +1 -0
  143. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django/writes.pyi +1 -0
  144. pytest_obligation-0.8.0/shims/due_work_harness/integrations/django_tasks.pyi +1 -0
  145. pytest_obligation-0.8.0/shims/due_work_harness/integrations/mongodb.pyi +1 -0
  146. pytest_obligation-0.8.0/shims/due_work_harness/integrations/postgres_plans.pyi +1 -0
  147. pytest_obligation-0.8.0/shims/due_work_harness/integrations/prefect.pyi +1 -0
  148. pytest_obligation-0.8.0/shims/due_work_harness/integrations/procrastinate.pyi +1 -0
  149. pytest_obligation-0.8.0/shims/due_work_harness/integrations/redis.pyi +1 -0
  150. pytest_obligation-0.8.0/shims/due_work_harness/integrations/rq.pyi +1 -0
  151. pytest_obligation-0.8.0/shims/due_work_harness/integrations/task_queues.pyi +1 -0
  152. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/__init__.pyi +1 -0
  153. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/adapters/__init__.pyi +1 -0
  154. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/adapters/integration.pyi +1 -0
  155. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/bindings.pyi +1 -0
  156. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/engine/__init__.pyi +1 -0
  157. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/engine/catalog.pyi +1 -0
  158. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/engine/causality.pyi +1 -0
  159. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/engine/provider.pyi +1 -0
  160. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/engine/runner.pyi +1 -0
  161. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/exploration/__init__.pyi +1 -0
  162. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/exploration/hypothesis.pyi +1 -0
  163. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/model.pyi +1 -0
  164. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/ports.pyi +1 -0
  165. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/testing/__init__.pyi +1 -0
  166. pytest_obligation-0.8.0/shims/due_work_harness/interleavings/testing/reference.pyi +1 -0
  167. pytest_obligation-0.8.0/shims/due_work_harness/models.pyi +1 -0
  168. pytest_obligation-0.8.0/shims/due_work_harness/process_histories.pyi +1 -0
  169. pytest_obligation-0.8.0/shims/due_work_harness/profiles/__init__.pyi +1 -0
  170. pytest_obligation-0.8.0/shims/due_work_harness/profiles/automatic_recovery.pyi +1 -0
  171. pytest_obligation-0.8.0/shims/due_work_harness/profiles/bounded_ownership.pyi +1 -0
  172. pytest_obligation-0.8.0/shims/due_work_harness/profiles/catalog.pyi +1 -0
  173. pytest_obligation-0.8.0/shims/due_work_harness/profiles/coverage.pyi +1 -0
  174. pytest_obligation-0.8.0/shims/due_work_harness/profiles/crash_ambiguity.pyi +1 -0
  175. pytest_obligation-0.8.0/shims/due_work_harness/profiles/durable_retention.pyi +1 -0
  176. pytest_obligation-0.8.0/shims/due_work_harness/profiles/eventual_convergence.pyi +1 -0
  177. pytest_obligation-0.8.0/shims/due_work_harness/profiles/fact_derived_obligations.pyi +1 -0
  178. pytest_obligation-0.8.0/shims/due_work_harness/profiles/gated_execution.pyi +1 -0
  179. pytest_obligation-0.8.0/shims/due_work_harness/profiles/harmless_replay.pyi +1 -0
  180. pytest_obligation-0.8.0/shims/due_work_harness/profiles/indivisible_admission.pyi +1 -0
  181. pytest_obligation-0.8.0/shims/due_work_harness/profiles/job_retry_limits.pyi +1 -0
  182. pytest_obligation-0.8.0/shims/due_work_harness/profiles/reporting.pyi +1 -0
  183. pytest_obligation-0.8.0/shims/due_work_harness/py.typed +1 -0
  184. pytest_obligation-0.8.0/shims/due_work_harness/pytest_plugin.pyi +1 -0
  185. pytest_obligation-0.8.0/shims/due_work_harness/recording.pyi +1 -0
  186. pytest_obligation-0.8.0/shims/due_work_harness/references/__init__.pyi +1 -0
  187. pytest_obligation-0.8.0/shims/due_work_harness/references/eligibility.pyi +1 -0
  188. pytest_obligation-0.8.0/shims/due_work_harness/references/in_memory.pyi +1 -0
  189. pytest_obligation-0.8.0/shims/due_work_harness/references/in_memory_handoffs.pyi +1 -0
  190. pytest_obligation-0.8.0/shims/due_work_harness/worker_death.pyi +1 -0
  191. pytest_obligation-0.8.0/src/pytest_obligation/__init__.py +245 -0
  192. pytest_obligation-0.8.0/src/pytest_obligation/binding.py +884 -0
  193. pytest_obligation-0.8.0/src/pytest_obligation/coherence.py +118 -0
  194. pytest_obligation-0.8.0/src/pytest_obligation/contract.py +1850 -0
  195. pytest_obligation-0.8.0/src/pytest_obligation/coverage/__init__.py +55 -0
  196. pytest_obligation-0.8.0/src/pytest_obligation/coverage/cli.py +145 -0
  197. pytest_obligation-0.8.0/src/pytest_obligation/coverage/config.py +103 -0
  198. pytest_obligation-0.8.0/src/pytest_obligation/coverage/declarations.py +262 -0
  199. pytest_obligation-0.8.0/src/pytest_obligation/coverage/handoffs.py +467 -0
  200. pytest_obligation-0.8.0/src/pytest_obligation/coverage/project.py +261 -0
  201. pytest_obligation-0.8.0/src/pytest_obligation/coverage/report.py +45 -0
  202. pytest_obligation-0.8.0/src/pytest_obligation/coverage/scan.py +136 -0
  203. pytest_obligation-0.8.0/src/pytest_obligation/coverage/sites.py +152 -0
  204. pytest_obligation-0.8.0/src/pytest_obligation/crash_histories.py +948 -0
  205. pytest_obligation-0.8.0/src/pytest_obligation/evidence/__init__.py +13 -0
  206. pytest_obligation-0.8.0/src/pytest_obligation/evidence/observation.py +130 -0
  207. pytest_obligation-0.8.0/src/pytest_obligation/evidence/observation_report.py +76 -0
  208. pytest_obligation-0.8.0/src/pytest_obligation/exemptions.py +87 -0
  209. pytest_obligation-0.8.0/src/pytest_obligation/faults.py +41 -0
  210. pytest_obligation-0.8.0/src/pytest_obligation/gap_probes.py +314 -0
  211. pytest_obligation-0.8.0/src/pytest_obligation/helpers.py +204 -0
  212. pytest_obligation-0.8.0/src/pytest_obligation/host.py +338 -0
  213. pytest_obligation-0.8.0/src/pytest_obligation/integrations/__init__.py +0 -0
  214. pytest_obligation-0.8.0/src/pytest_obligation/integrations/aiokafka.py +104 -0
  215. pytest_obligation-0.8.0/src/pytest_obligation/integrations/celery.py +218 -0
  216. pytest_obligation-0.8.0/src/pytest_obligation/integrations/celery_worker.py +336 -0
  217. pytest_obligation-0.8.0/src/pytest_obligation/integrations/clocks.py +21 -0
  218. pytest_obligation-0.8.0/src/pytest_obligation/integrations/dbos.py +94 -0
  219. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/__init__.py +142 -0
  220. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/admission.py +35 -0
  221. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/callbacks.py +56 -0
  222. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/commands.py +74 -0
  223. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/commits.py +102 -0
  224. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/lifecycle_references.py +565 -0
  225. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/lifecycle_states.py +630 -0
  226. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/lock_order.py +428 -0
  227. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/receivers.py +47 -0
  228. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/references.py +35 -0
  229. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/selection.py +242 -0
  230. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django/writes.py +186 -0
  231. pytest_obligation-0.8.0/src/pytest_obligation/integrations/django_tasks.py +257 -0
  232. pytest_obligation-0.8.0/src/pytest_obligation/integrations/mongodb.py +179 -0
  233. pytest_obligation-0.8.0/src/pytest_obligation/integrations/postgres_plans.py +343 -0
  234. pytest_obligation-0.8.0/src/pytest_obligation/integrations/prefect.py +86 -0
  235. pytest_obligation-0.8.0/src/pytest_obligation/integrations/procrastinate.py +308 -0
  236. pytest_obligation-0.8.0/src/pytest_obligation/integrations/redis.py +217 -0
  237. pytest_obligation-0.8.0/src/pytest_obligation/integrations/rq.py +455 -0
  238. pytest_obligation-0.8.0/src/pytest_obligation/integrations/task_queues.py +124 -0
  239. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/__init__.py +42 -0
  240. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/adapters/__init__.py +1 -0
  241. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/adapters/integration.py +99 -0
  242. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/bindings.py +393 -0
  243. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/engine/__init__.py +1 -0
  244. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/engine/catalog.py +292 -0
  245. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/engine/causality.py +33 -0
  246. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/engine/provider.py +99 -0
  247. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/engine/runner.py +326 -0
  248. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/exploration/__init__.py +1 -0
  249. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/exploration/hypothesis.py +99 -0
  250. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/model.py +152 -0
  251. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/ports.py +34 -0
  252. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/testing/__init__.py +1 -0
  253. pytest_obligation-0.8.0/src/pytest_obligation/interleavings/testing/reference.py +102 -0
  254. pytest_obligation-0.8.0/src/pytest_obligation/models.py +90 -0
  255. pytest_obligation-0.8.0/src/pytest_obligation/process_histories.py +207 -0
  256. pytest_obligation-0.8.0/src/pytest_obligation/profiles/__init__.py +6 -0
  257. pytest_obligation-0.8.0/src/pytest_obligation/profiles/automatic_recovery.py +1414 -0
  258. pytest_obligation-0.8.0/src/pytest_obligation/profiles/bounded_ownership.py +680 -0
  259. pytest_obligation-0.8.0/src/pytest_obligation/profiles/catalog.py +51 -0
  260. pytest_obligation-0.8.0/src/pytest_obligation/profiles/coverage.py +77 -0
  261. pytest_obligation-0.8.0/src/pytest_obligation/profiles/crash_ambiguity.py +291 -0
  262. pytest_obligation-0.8.0/src/pytest_obligation/profiles/durable_retention.py +119 -0
  263. pytest_obligation-0.8.0/src/pytest_obligation/profiles/eventual_convergence.py +308 -0
  264. pytest_obligation-0.8.0/src/pytest_obligation/profiles/fact_derived_obligations.py +389 -0
  265. pytest_obligation-0.8.0/src/pytest_obligation/profiles/gated_execution.py +389 -0
  266. pytest_obligation-0.8.0/src/pytest_obligation/profiles/harmless_replay.py +154 -0
  267. pytest_obligation-0.8.0/src/pytest_obligation/profiles/indivisible_admission.py +122 -0
  268. pytest_obligation-0.8.0/src/pytest_obligation/profiles/job_retry_limits.py +219 -0
  269. pytest_obligation-0.8.0/src/pytest_obligation/profiles/reporting.py +162 -0
  270. pytest_obligation-0.8.0/src/pytest_obligation/py.typed +0 -0
  271. pytest_obligation-0.8.0/src/pytest_obligation/pytest_plugin.py +245 -0
  272. pytest_obligation-0.8.0/src/pytest_obligation/recording.py +130 -0
  273. pytest_obligation-0.8.0/src/pytest_obligation/references/__init__.py +19 -0
  274. pytest_obligation-0.8.0/src/pytest_obligation/references/eligibility.py +222 -0
  275. pytest_obligation-0.8.0/src/pytest_obligation/references/in_memory.py +632 -0
  276. pytest_obligation-0.8.0/src/pytest_obligation/references/in_memory_handoffs.py +527 -0
  277. pytest_obligation-0.8.0/src/pytest_obligation/worker_death.py +65 -0
  278. pytest_obligation-0.8.0/test/action.yml +63 -0
  279. pytest_obligation-0.8.0/tests/__init__.py +0 -0
  280. pytest_obligation-0.8.0/tests/aiokafka/test_offsets.py +202 -0
  281. pytest_obligation-0.8.0/tests/celery_worker/__init__.py +0 -0
  282. pytest_obligation-0.8.0/tests/celery_worker/app.py +33 -0
  283. pytest_obligation-0.8.0/tests/celery_worker/test_catalog.py +48 -0
  284. pytest_obligation-0.8.0/tests/celery_worker/test_celery_task_bindings.py +61 -0
  285. pytest_obligation-0.8.0/tests/celery_worker/test_celery_worker_contract.py +118 -0
  286. pytest_obligation-0.8.0/tests/celery_worker/test_celery_worker_platform.py +16 -0
  287. pytest_obligation-0.8.0/tests/core/__init__.py +0 -0
  288. pytest_obligation-0.8.0/tests/core/automatic_recovery/__init__.py +0 -0
  289. pytest_obligation-0.8.0/tests/core/automatic_recovery/test_plan_verdict.py +498 -0
  290. pytest_obligation-0.8.0/tests/core/automatic_recovery/test_sweep.py +1193 -0
  291. pytest_obligation-0.8.0/tests/core/conftest.py +23 -0
  292. pytest_obligation-0.8.0/tests/core/contract/__init__.py +0 -0
  293. pytest_obligation-0.8.0/tests/core/contract/child_suites.py +64 -0
  294. pytest_obligation-0.8.0/tests/core/contract/conftest.py +25 -0
  295. pytest_obligation-0.8.0/tests/core/contract/declarations.py +122 -0
  296. pytest_obligation-0.8.0/tests/core/contract/reference_contract_cases.py +57 -0
  297. pytest_obligation-0.8.0/tests/core/contract/test_binding.py +53 -0
  298. pytest_obligation-0.8.0/tests/core/contract/test_contract.py +1525 -0
  299. pytest_obligation-0.8.0/tests/core/contract/test_declared_findings_session.py +39 -0
  300. pytest_obligation-0.8.0/tests/core/contract/test_gap_probes.py +95 -0
  301. pytest_obligation-0.8.0/tests/core/contract/test_generated_cases_are_marked.py +22 -0
  302. pytest_obligation-0.8.0/tests/core/contract/test_inversion_tripwire.py +433 -0
  303. pytest_obligation-0.8.0/tests/core/contract/test_record_findings_session.py +35 -0
  304. pytest_obligation-0.8.0/tests/core/contract/test_safety_contract.py +188 -0
  305. pytest_obligation-0.8.0/tests/core/coverage/__init__.py +0 -0
  306. pytest_obligation-0.8.0/tests/core/coverage/builders.py +32 -0
  307. pytest_obligation-0.8.0/tests/core/coverage/conftest.py +5 -0
  308. pytest_obligation-0.8.0/tests/core/coverage/test_cli.py +105 -0
  309. pytest_obligation-0.8.0/tests/core/coverage/test_due_work_summary.py +77 -0
  310. pytest_obligation-0.8.0/tests/core/coverage/test_due_work_verify.py +147 -0
  311. pytest_obligation-0.8.0/tests/core/coverage/test_exempt_due_work_suite.py +91 -0
  312. pytest_obligation-0.8.0/tests/core/coverage/test_scan_finds_sites.py +471 -0
  313. pytest_obligation-0.8.0/tests/core/coverage/test_scan_requires_one_disposition.py +354 -0
  314. pytest_obligation-0.8.0/tests/core/coverage/test_scan_sites_in_transaction.py +151 -0
  315. pytest_obligation-0.8.0/tests/core/evidence/__init__.py +0 -0
  316. pytest_obligation-0.8.0/tests/core/evidence/test_observation.py +62 -0
  317. pytest_obligation-0.8.0/tests/core/profiles/__init__.py +0 -0
  318. pytest_obligation-0.8.0/tests/core/profiles/test_bounded_ownership.py +565 -0
  319. pytest_obligation-0.8.0/tests/core/profiles/test_crash_ambiguity.py +268 -0
  320. pytest_obligation-0.8.0/tests/core/profiles/test_durable_retention.py +61 -0
  321. pytest_obligation-0.8.0/tests/core/profiles/test_eventual_convergence.py +164 -0
  322. pytest_obligation-0.8.0/tests/core/profiles/test_execution_eligibility.py +272 -0
  323. pytest_obligation-0.8.0/tests/core/profiles/test_fact_derived_obligations.py +390 -0
  324. pytest_obligation-0.8.0/tests/core/safety/__init__.py +0 -0
  325. pytest_obligation-0.8.0/tests/core/safety/test_bounded_retry.py +170 -0
  326. pytest_obligation-0.8.0/tests/core/safety/test_replay_safe_execution.py +109 -0
  327. pytest_obligation-0.8.0/tests/core/test_adoption_report_guard.py +43 -0
  328. pytest_obligation-0.8.0/tests/core/test_compatibility.py +99 -0
  329. pytest_obligation-0.8.0/tests/core/test_crash_histories.py +267 -0
  330. pytest_obligation-0.8.0/tests/core/test_external_call_seams.py +333 -0
  331. pytest_obligation-0.8.0/tests/core/test_indivisible_admission.py +119 -0
  332. pytest_obligation-0.8.0/tests/core/test_interleaving_suites.py +90 -0
  333. pytest_obligation-0.8.0/tests/core/test_interleavings.py +537 -0
  334. pytest_obligation-0.8.0/tests/core/test_models.py +28 -0
  335. pytest_obligation-0.8.0/tests/core/test_package_identity.py +25 -0
  336. pytest_obligation-0.8.0/tests/core/test_process_histories.py +139 -0
  337. pytest_obligation-0.8.0/tests/core/test_profile_catalog.py +125 -0
  338. pytest_obligation-0.8.0/tests/core/test_profile_coverage.py +120 -0
  339. pytest_obligation-0.8.0/tests/core/test_recording.py +65 -0
  340. pytest_obligation-0.8.0/tests/django/__init__.py +0 -0
  341. pytest_obligation-0.8.0/tests/django/automatic_recovery/__init__.py +0 -0
  342. pytest_obligation-0.8.0/tests/django/automatic_recovery/test_selection_inspector.py +483 -0
  343. pytest_obligation-0.8.0/tests/django/automatic_recovery/test_terminal_states.py +536 -0
  344. pytest_obligation-0.8.0/tests/django/conftest.py +30 -0
  345. pytest_obligation-0.8.0/tests/django/contract/__init__.py +0 -0
  346. pytest_obligation-0.8.0/tests/django/contract/test_contract_suites.py +147 -0
  347. pytest_obligation-0.8.0/tests/django/settings.py +18 -0
  348. pytest_obligation-0.8.0/tests/django/test_admission.py +63 -0
  349. pytest_obligation-0.8.0/tests/django/test_claim_race.py +156 -0
  350. pytest_obligation-0.8.0/tests/django/test_commit_observer.py +241 -0
  351. pytest_obligation-0.8.0/tests/django/test_crash_histories.py +255 -0
  352. pytest_obligation-0.8.0/tests/django/test_lock_order.py +355 -0
  353. pytest_obligation-0.8.0/tests/django/test_management_command.py +76 -0
  354. pytest_obligation-0.8.0/tests/django/test_observed_selection.py +71 -0
  355. pytest_obligation-0.8.0/tests/django/test_postgresql_required.py +15 -0
  356. pytest_obligation-0.8.0/tests/django/test_publication_breaker.py +66 -0
  357. pytest_obligation-0.8.0/tests/mongodb/test_mongodb.py +181 -0
  358. pytest_obligation-0.8.0/tests/prefect/test_catalog.py +42 -0
  359. pytest_obligation-0.8.0/tests/prefect/test_prefect.py +153 -0
  360. pytest_obligation-0.8.0/tests/redis_databases.py +47 -0
  361. pytest_obligation-0.8.0/tests/rq/__init__.py +0 -0
  362. pytest_obligation-0.8.0/tests/rq/conftest.py +44 -0
  363. pytest_obligation-0.8.0/tests/rq/connection.py +6 -0
  364. pytest_obligation-0.8.0/tests/rq/jobs.py +42 -0
  365. pytest_obligation-0.8.0/tests/rq/test_catalog.py +28 -0
  366. pytest_obligation-0.8.0/tests/rq/test_redis_commits.py +104 -0
  367. pytest_obligation-0.8.0/tests/rq/test_rq_worker_contract.py +172 -0
  368. pytest_obligation-0.8.0/tests_support/catalog_executor.py +26 -0
  369. pytest_obligation-0.8.0/tests_support/mongodb_app.py +18 -0
  370. pytest_obligation-0.8.0/tests_support/prefect_app.py +17 -0
  371. pytest_obligation-0.8.0/tests_support/sample_production/__init__.py +11 -0
  372. pytest_obligation-0.8.0/tests_support/sample_production/admission.py +64 -0
  373. pytest_obligation-0.8.0/tests_support/sample_production/cache.py +11 -0
  374. pytest_obligation-0.8.0/tests_support/sample_production/feed.py +25 -0
  375. pytest_obligation-0.8.0/tests_support/sample_production/tasks.py +9 -0
  376. pytest_obligation-0.8.0/uv.lock +4381 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "pytest-obligation",
3
+ "interface": {
4
+ "displayName": "pytest-obligation"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "pytest-obligation",
9
+ "source": {
10
+ "source": "local",
11
+ "path": "../../plugins/pytest-obligation"
12
+ },
13
+ "policy": {
14
+ "installation": "AVAILABLE",
15
+ "authentication": "ON_INSTALL"
16
+ },
17
+ "category": "Developer Tools"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: find-upstream-weaknesses
3
+ description: Use to probe a real open-source project (an application first, or a framework) with due-work-harness, find background work that can be lost, repeated or misrecorded, and disclose it upstream as issues plus a PR that carries the harness contract. Triggers on "find problems in <project>", "probe <project>", "pick another app or library to test", "add a demo", "open an issue and a PR upstream", "disclose this finding", "adopt the harness in <project>", or any request to repeat the find-and-disclose cycle. Not for adopting the harness in the user's own repository (see ADOPTING.md).
4
+ ---
5
+
6
+ # Find weaknesses in a real project, then disclose them
7
+
8
+ The full method is [docs/upstream-playbook.md](../../../docs/upstream-playbook.md); read the step you are
9
+ on when you reach it. The copy-and-fill templates are in
10
+ [docs/upstream-templates/](../../../docs/upstream-templates/). This file is the runbook: what to do, in
11
+ order, and the rules that are not negotiable.
12
+
13
+ The installable plugin's [probe-upstream-due-work](../../../plugins/pytest-obligation/skills/probe-upstream-due-work/SKILL.md)
14
+ skill is the portable route for people working outside this repository. This repository-local skill
15
+ retains the maintainer-specific release and disclosure workflow below; neither skill replaces
16
+ gigaverse-backend's own due-work runtime and conformance instructions.
17
+
18
+ ## Rules that do not bend
19
+
20
+ - **Expose, don't fix.** Deliver a test that fails for a stated reason and a contract that finds it. Never
21
+ propose or write the fix in an issue or PR.
22
+ - **Every adoption PR MUST carry `ObligationContract` and a collected
23
+ `@due_work_contract_suite(CONTRACT)` class.** Follow the
24
+ [required adoption shape](../../../ADOPTING.md#required-adoption-shape): declare
25
+ all A–J profiles for the current source (or the installed release's matching catalog), put histories in the contract, execute the generated
26
+ cases, and link the declaration, suite and `--due-work-summary` report. Ordinary
27
+ tests and direct crash-helper calls are supporting evidence, never a substitute.
28
+ The decorator generates pytest cases, not Python files; use `--junitxml` when a
29
+ generated result file is needed and retain it as a CI artifact.
30
+ - **Claim only what you ran.** A sentence in a report is either measured or marked as code reading. Agents'
31
+ findings (including your own from an earlier turn) are hypotheses until step 5.
32
+ - **Posting is outward-facing.** Issues, PRs, comments and releases go out under the user's account.
33
+ Do it only with the user's authorization for this project in this session; if unsure, ask once, with
34
+ the draft. Follow the target's own contribution rules over these: some ask for a human-written PR
35
+ description or forbid AI-authored contributions, and then the user decides.
36
+ - **Every comment you post opens with the self-identification line**
37
+ `> I'm {model}, AI can make mistakes. {a fresh 3-5 word joke}` (no joke on incident threads).
38
+ - **Every probe strengthens the harness.** A gap the probe hit becomes a general capability in the harness,
39
+ released before the upstream PRs pin it.
40
+
41
+ ## The cycle
42
+
43
+ Work in a scratch directory, not in a checkout that matters. Track the steps as tasks.
44
+
45
+ 1. **Choose.** Prefer an application whose failure a customer or money feels; take a framework when apps
46
+ depend on it and nobody has probed it. Count handoffs with `uv run due-work-harness check` (or the
47
+ `scan` API), require that the project already recovers some of them, that its own tests run on this
48
+ machine (Python floor, database, system packages), and that outside contributions are accepted. Read its
49
+ CONTRIBUTING, templates and AI policy now. Search its tracker for prior reports. If two targets fit,
50
+ pick one and say why; ask only if the choice needs the user's judgement (policy, risk to a relationship).
51
+ 2. **Set up.** Fork and clone; put Postgres/Redis in containers the way their CI does; **run their
52
+ unmodified suite first** so a red baseline is known before it is blamed on you.
53
+ 3. **Map.** `due-work-harness sites` and `due-work-harness in-transaction` list the candidates first
54
+ (a throwaway `[tool.due-work-harness]` table in the fork is enough). Then run the prompt in
55
+ [map-the-handoffs.md](../../../docs/upstream-templates/map-the-handoffs.md) through an agent (fill the
56
+ placeholders from the scan). Keep the result as ranked hypotheses.
57
+ 4. **Adopt in the fork.** A branch such as `test/<what-breaks>`, a `tests/due_work/` directory named the way
58
+ their runner collects it, a host (`django_host`, `redis_host`, or a new one), bindings that call the
59
+ real code (a string command that reaches no project code is refused: pass the command class), a
60
+ disposition for every profile with a reason, and one history per user-facing action whose observation
61
+ includes what users and external systems saw (not something the project's own cleanup deletes). A
62
+ management-command recovery is `management_command(name, after=...)`. Use `--due-work-summary` to read
63
+ the run.
64
+ 5. **Confirm each finding.** Reproduce the smallest version with a script that uses the project's own API
65
+ and no harness. Test the neighbouring claim before you make it, check server or library versions, and
66
+ search the tracker again. Drop what you cannot reproduce or what the project defends.
67
+ 6. **Declare and mark.** `Findings(delivered, {label: outcome})` on each history (generate it with
68
+ `--due-work-record-findings`, read it, then paste), `handoff_gaps` with the
69
+ issue link, an `expectedFailure` plain test that fails for the stated reason (remove the marker once and
70
+ read the failure), and the *this is where the magic happens* comment above each decorated class.
71
+ 7. **Improve the harness.** If the probe needed something the harness lacks, or you wrote a helper twice,
72
+ build it in the harness on its own branch (test it, in CI, on the stack it failed on), then run a DRY
73
+ round from demo to application to integration to core. Release it (see RELEASING.md) and raise the pins.
74
+ 8. **Disclose.** One issue per finding (template `issue.md`), then one PR (`pr.md`) that links the magic
75
+ point at the pushed commit and shows the `--due-work-summary` capture, then the plain-language comments
76
+ with ASCII art (`eli5-issue.md`, `eli5-pr.md`) on each. Fill their template and policy first.
77
+ 9. **Keep it green.** Watch CI. The usual causes are in the playbook's table: lockfile, an environment that
78
+ does not install the harness, a version-dependent table, a bot commit (merge, never rebase), workflows
79
+ waiting for approval, a `main` that is already red (prove it, say so, leave their test alone).
80
+ 10. **Record.** Update the demo and README tables if the project is a shipped demo, and keep the scratch
81
+ material until the threads close.
82
+
83
+ ## Before you say it is done
84
+
85
+ - [ ] their own suite ran green here first; the baseline is known
86
+ - [ ] each finding has an independent reproduction, and only measured claims are in the text
87
+ - [ ] `ObligationContract`, its decorated collected class, `Findings`, gaps, plain test, and the magic comment are in the pushed commit
88
+ - [ ] generated node IDs were collected and executed; the PR links the declaration, class, and summary/artifact, with gaps counted separately
89
+ - [ ] the harness gap (if any) is fixed, tested in CI on that stack, released, and the pins raised
90
+ - [ ] issues, PR and plain-language comments are posted with the self-identification line, no fix offered
91
+ - [ ] CI is green, or its red is explained with evidence
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "pytest-obligation",
3
+ "description": "Generate ObligationContract tests and investigate lost or duplicate work in Python projects.",
4
+ "owner": {"name": "Gigaverse"},
5
+ "plugins": [
6
+ {
7
+ "name": "pytest-obligation",
8
+ "source": "./plugins/pytest-obligation",
9
+ "description": "Generate ObligationContract tests in your app or verify due-work bugs in open-source projects."
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,470 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ # Every job installs from uv.lock with --locked: a lock that no longer matches
16
+ # pyproject.toml fails CI instead of silently resolving something new.
17
+
18
+ jobs:
19
+ lint:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v7
23
+ - uses: astral-sh/setup-uv@v7
24
+ with:
25
+ enable-cache: true
26
+ - run: uv sync --locked --only-group lint
27
+ - run: uv run --no-sync ruff check
28
+ - run: uv run --no-sync ruff format --check
29
+ - name: actionlint
30
+ run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.7 -color
31
+
32
+ typecheck:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v7
36
+ - uses: astral-sh/setup-uv@v7
37
+ with:
38
+ enable-cache: true
39
+ # The demos type-check against the upstream code they run (Saleor's in its own job).
40
+ - run: uv run --no-project python demos/fetch_upstream.py procrastinate dbos-demo-apps
41
+ - run: uv sync --locked --all-extras --group demos
42
+ - run: uv pip install -e demos/.upstream/procrastinate
43
+ - run: uv run --no-sync pyrefly check
44
+
45
+ core:
46
+ # The core must work with no framework installed: the package, pytest and pytest-xdist only.
47
+ runs-on: ubuntu-latest
48
+ strategy:
49
+ fail-fast: false
50
+ matrix:
51
+ python: ["3.11", "3.12", "3.13", "3.14"]
52
+ steps:
53
+ - uses: actions/checkout@v7
54
+ - uses: astral-sh/setup-uv@v7
55
+ with:
56
+ enable-cache: true
57
+ python-version: ${{ matrix.python }}
58
+ # xdist is no framework: it lets the verify option's self-tests split a session across workers.
59
+ - run: uv sync --python ${{ matrix.python }} --locked --no-default-groups --group xdist
60
+ - run: uv run --python ${{ matrix.python }} --no-sync python scripts/check_core_is_framework_free.py
61
+ - run: uv run --python ${{ matrix.python }} --no-sync python -m compileall -q src/pytest_obligation
62
+ - run: uv run --python ${{ matrix.python }} --no-sync pytest tests/core
63
+
64
+ minimum-models:
65
+ # Exercise nested generic declarations on the supported minimum, without Hypothesis or frameworks.
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@v7
69
+ - uses: astral-sh/setup-uv@v7
70
+ with:
71
+ enable-cache: true
72
+ python-version: "3.12"
73
+ - run: uv sync --locked --no-default-groups --group xdist
74
+ # This lane deliberately checks the declared lower bound, not uv.lock's current release.
75
+ - run: uv pip install 'pydantic==2.11.0'
76
+ - run: uv run --no-sync python scripts/check_core_is_framework_free.py
77
+ - run: uv run --no-sync pytest tests/core
78
+ - run: uv run --no-sync pytest examples/adopter/tests --due-work-require-assessed
79
+
80
+ exploration:
81
+ runs-on: ubuntu-latest
82
+ steps:
83
+ - uses: actions/checkout@v7
84
+ - uses: astral-sh/setup-uv@v7
85
+ with:
86
+ enable-cache: true
87
+ - run: uv sync --locked --no-default-groups --extra exploration --group xdist
88
+ - run: uv run --no-sync pytest tests/core
89
+
90
+ django:
91
+ runs-on: ubuntu-latest
92
+ services:
93
+ postgres:
94
+ image: postgres:16
95
+ env:
96
+ POSTGRES_PASSWORD: postgres
97
+ ports: ["5432:5432"]
98
+ options: >-
99
+ --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10
100
+ env:
101
+ PGHOST: localhost
102
+ PGUSER: postgres
103
+ PGPASSWORD: postgres
104
+ PGPORT: "5432"
105
+ PGDATABASE: pytest_obligation
106
+ steps:
107
+ - uses: actions/checkout@v7
108
+ - uses: astral-sh/setup-uv@v7
109
+ with:
110
+ enable-cache: true
111
+ # pytest-django gives each xdist worker its own test database.
112
+ - run: uv sync --locked --no-default-groups --extra django --extra celery --group xdist
113
+ - run: uv run --no-sync pytest tests/django --ds=tests.django.settings -n 4
114
+
115
+ # The same suite on psycopg2, which Django uses when psycopg 3 is not installed: pretix and many older
116
+ # projects run on it, and the host's autocommit-write probe once assumed psycopg 3.
117
+ django-psycopg2:
118
+ runs-on: ubuntu-latest
119
+ services:
120
+ postgres:
121
+ image: postgres:16
122
+ env:
123
+ POSTGRES_PASSWORD: postgres
124
+ ports: ["5432:5432"]
125
+ options: >-
126
+ --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10
127
+ env:
128
+ PGHOST: localhost
129
+ PGUSER: postgres
130
+ PGPASSWORD: postgres
131
+ PGPORT: "5432"
132
+ PGDATABASE: due_work_harness_psycopg2
133
+ steps:
134
+ - uses: actions/checkout@v7
135
+ - uses: astral-sh/setup-uv@v7
136
+ with:
137
+ enable-cache: true
138
+ - run: uv sync --locked --no-default-groups --extra django --extra celery --group xdist
139
+ - run: uv pip uninstall psycopg psycopg-binary && uv pip install psycopg2-binary
140
+ - run: uv run --no-sync pytest tests/django --ds=tests.django.settings -n 4
141
+
142
+ # Redis-backed queues, and Celery's real worker as a child process on a Redis broker.
143
+ redis:
144
+ runs-on: ubuntu-latest
145
+ services:
146
+ redis:
147
+ image: redis:7
148
+ ports: ["6379:6379"]
149
+ options: >-
150
+ --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 10
151
+ env:
152
+ REDIS_URL: redis://localhost:6379/14
153
+ steps:
154
+ - uses: actions/checkout@v7
155
+ - uses: astral-sh/setup-uv@v7
156
+ with:
157
+ enable-cache: true
158
+ - run: uv sync --locked --no-default-groups --extra rq --extra celery --extra exploration --group xdist
159
+ # Each xdist worker takes its own four Redis databases (tests/redis_databases.py), so cases that
160
+ # empty theirs cannot wipe another worker's; four workers use all 16 of the service's databases.
161
+ - run: uv run --no-sync pytest tests/rq tests/celery_worker -p no:django -rxX -n 4 --durations=10 --due-work-explore=smoke --due-work-require-assessed --due-work-profile-report=profiles-redis.json
162
+
163
+ - name: Require the promised adopters and executed capabilities
164
+ run: |
165
+ uv run --no-sync python scripts/check_adoption_report.py profiles-redis.json --contract "catalog on RQ" --contract "catalog through Celery" --verified E G H I
166
+ uv run --no-sync python scripts/check_adoption_report.py profiles-redis.json --contract "rq reference jobs" --verified G J --exercised H B
167
+ - name: Keep executed profile evidence
168
+ if: always()
169
+ uses: actions/upload-artifact@v4
170
+ with:
171
+ name: profiles-redis
172
+ path: |
173
+ profiles-*.json
174
+ demos/**/profiles-*.json
175
+ if-no-files-found: error
176
+
177
+ mongodb:
178
+ runs-on: ubuntu-latest
179
+ env:
180
+ MONGODB_URI: mongodb://localhost:27017/?directConnection=true
181
+ steps:
182
+ - uses: actions/checkout@v7
183
+ - uses: astral-sh/setup-uv@v7
184
+ with:
185
+ enable-cache: true
186
+ - run: uv sync --locked --no-default-groups --extra mongodb
187
+ # Transactions need a replica set on this disposable runner.
188
+ - run: |
189
+ docker run -d --name due-work-mongodb -p 27017:27017 mongo:7 --replSet dwh --bind_ip_all
190
+ for attempt in $(seq 1 30); do
191
+ if docker exec due-work-mongodb mongosh --quiet --eval 'rs.initiate({_id:"dwh",members:[{_id:0,host:"localhost:27017"}]})'; then break; fi
192
+ if [ "$attempt" -eq 30 ]; then exit 1; fi
193
+ sleep 1
194
+ done
195
+ - run: uv run --no-sync pytest tests/mongodb -q
196
+
197
+ aiokafka:
198
+ runs-on: ubuntu-latest
199
+ services:
200
+ kafka:
201
+ image: apache/kafka:3.9.1
202
+ ports: ["9092:9092"]
203
+ env:
204
+ KAFKA_NODE_ID: "1"
205
+ KAFKA_PROCESS_ROLES: broker,controller
206
+ KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
207
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
208
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
209
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
210
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
211
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
212
+ KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0"
213
+ options: >-
214
+ --health-cmd "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list"
215
+ --health-interval 5s --health-timeout 10s --health-retries 12
216
+ env:
217
+ KAFKA_BOOTSTRAP_SERVERS: localhost:9092
218
+ steps:
219
+ - uses: actions/checkout@v7
220
+ - uses: astral-sh/setup-uv@v7
221
+ with:
222
+ enable-cache: true
223
+ - run: uv sync --locked --no-default-groups --extra aiokafka
224
+ - run: uv run --no-sync pytest tests/aiokafka -q
225
+
226
+ prefect:
227
+ runs-on: ubuntu-latest
228
+ steps:
229
+ - uses: actions/checkout@v7
230
+ - uses: astral-sh/setup-uv@v7
231
+ with:
232
+ enable-cache: true
233
+ - run: uv sync --locked --no-default-groups --extra prefect --extra exploration
234
+ - run: uv run --no-sync pytest tests/prefect -q --due-work-explore=smoke --due-work-require-assessed --due-work-profile-report=profiles-prefect.json
235
+
236
+ - name: Require the promised adopters and executed capabilities
237
+ run: |
238
+ uv run --no-sync python scripts/check_adoption_report.py profiles-prefect.json --contract "catalog through Prefect engine" --verified E G H I
239
+ - name: Keep executed profile evidence
240
+ if: always()
241
+ uses: actions/upload-artifact@v4
242
+ with:
243
+ name: profiles-prefect
244
+ path: |
245
+ profiles-*.json
246
+ demos/**/profiles-*.json
247
+ if-no-files-found: error
248
+
249
+ demos:
250
+ runs-on: ubuntu-latest
251
+ services:
252
+ postgres:
253
+ image: postgres:16
254
+ env:
255
+ POSTGRES_PASSWORD: postgres
256
+ ports: ["5432:5432"]
257
+ options: >-
258
+ --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10
259
+ env:
260
+ PGHOST: localhost
261
+ PGUSER: postgres
262
+ PGPASSWORD: postgres
263
+ PGPORT: "5432"
264
+ steps:
265
+ - uses: actions/checkout@v7
266
+ with:
267
+ # The check action builds the harness from this checkout; its version comes from git tags.
268
+ fetch-depth: 0
269
+ - uses: astral-sh/setup-uv@v7
270
+ with:
271
+ enable-cache: true
272
+ # Saleor too: the README's demo GIF shows the coverage scan of its checkout. Wagtail's source is
273
+ # what the Wagtail demo's coverage check reads; the demo itself runs the wheel from PyPI.
274
+ - run: uv run --no-project python demos/fetch_upstream.py procrastinate dbos-demo-apps saleor wagtail
275
+ - run: uv sync --locked --no-default-groups --extra django --group demos
276
+ # procrastinate's demos ship only in its source tree, pinned by fetch_upstream.py.
277
+ - run: uv pip install -e demos/.upstream/procrastinate
278
+ - name: Postgres without durability
279
+ run: demos/throwaway_postgres.sh
280
+ # The durations are the evidence for the next change to this job's speed.
281
+ - name: procrastinate demo_django
282
+ env:
283
+ PGDATABASE: procrastinate_demo
284
+ run: >-
285
+ uv run --no-sync pytest demos/procrastinate_demo_django --ds=demos.procrastinate_demo_django.settings
286
+ -rxX --durations=15 --due-work-require-assessed --due-work-profile-report=profiles-procrastinate.json
287
+ - name: DBOS transactional-outbox
288
+ run: uv run --no-sync pytest demos/dbos_transactional_outbox -p no:django -rxX --durations=15 --due-work-require-assessed --due-work-profile-report=profiles-dbos.json
289
+ # Migrating Wagtail's test database is most of its run, and depends only on the pinned packages
290
+ # (uv.lock) and the demo's settings: restored from the cached dump when the key matches.
291
+ - uses: actions/cache@v4
292
+ with:
293
+ path: demos/.ci-cache/wagtail_tasks.dump
294
+ key: wagtail-test-db-pg16-${{ hashFiles('uv.lock', 'demos/wagtail_tasks/settings.py', 'demos/cached_database.py') }}
295
+ - name: Wagtail's test database, migrated once
296
+ # As pytest-django creates a test database: every app's migrations, syncdb for the rest, no checks.
297
+ run: >-
298
+ uv run --no-sync python demos/cached_database.py test_wagtail_tasks --dump demos/.ci-cache/wagtail_tasks.dump
299
+ -- uv run --no-sync python -m django migrate --settings=demos.wagtail_tasks.settings
300
+ --run-syncdb --skip-checks --no-input --verbosity 0
301
+ # Wagtail 8.0 on django-tasks-db, both from PyPI; the demo's own directory is pytest's rootdir.
302
+ - name: Wagtail on django-tasks-db
303
+ working-directory: demos/wagtail_tasks
304
+ env:
305
+ PGDATABASE: wagtail_tasks
306
+ run: uv run --no-sync --project ../.. pytest --reuse-db --due-work-verify --durations=15 --due-work-require-assessed --due-work-profile-report=profiles-wagtail.json
307
+ - name: the README's demo GIF prints what the harness prints
308
+ run: uv run --no-sync pytest demos/test_readme_gif.py -p no:django --durations=15
309
+ # Each demo is an adopter: its handoffs are checked the way an adopter's CI checks them.
310
+ - name: procrastinate demo_django, every handoff accounted for
311
+ uses: ./check
312
+ with:
313
+ working-directory: demos/procrastinate_demo_django
314
+ package: ${{ github.workspace }}
315
+ - name: DBOS transactional-outbox, every handoff accounted for
316
+ uses: ./check
317
+ with:
318
+ working-directory: demos/dbos_transactional_outbox
319
+ package: ${{ github.workspace }}
320
+ - name: Wagtail on django-tasks-db, every handoff accounted for
321
+ uses: ./check
322
+ with:
323
+ working-directory: demos/wagtail_tasks
324
+ package: ${{ github.workspace }}
325
+
326
+ - name: Require the promised adopters and executed capabilities
327
+ run: |
328
+ uv run --no-sync python scripts/check_adoption_report.py profiles-procrastinate.json --contract "procrastinate demo_django: create book" --contract "procrastinate demo_django: create book, with its fixes" --exercised I
329
+ uv run --no-sync python scripts/check_adoption_report.py profiles-dbos.json --contract "DBOS transactional-outbox: place order" --verified I --exercised H J
330
+ uv run --no-sync python scripts/check_adoption_report.py demos/wagtail_tasks/profiles-wagtail.json --contract "wagtail: image and document lifecycle" --contract "wagtail: publishing a page behind a CDN" --exercised I --verified H J
331
+ uv run --no-sync python scripts/check_adoption_report.py demos/wagtail_tasks/profiles-wagtail.json --contract "wagtail: image and document lifecycle" --exercised E
332
+ - name: Keep executed profile evidence
333
+ if: always()
334
+ uses: actions/upload-artifact@v4
335
+ with:
336
+ name: profiles-demos
337
+ path: |
338
+ profiles-*.json
339
+ demos/**/profiles-*.json
340
+ if-no-files-found: error
341
+
342
+ saleor:
343
+ # Saleor's checkout, unmodified, in Saleor's own environment built from its uv.lock.
344
+ runs-on: ubuntu-latest
345
+ services:
346
+ postgres:
347
+ image: postgres:16
348
+ env:
349
+ POSTGRES_PASSWORD: postgres
350
+ ports: ["5432:5432"]
351
+ options: >-
352
+ --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10
353
+ env:
354
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/saleor
355
+ # Absolute: run.sh works in the demo directory.
356
+ SALEOR_DATABASE_DUMP: ${{ github.workspace }}/demos/.ci-cache/saleor.dump
357
+ steps:
358
+ - uses: actions/checkout@v7
359
+ with:
360
+ # The check action builds the harness from this checkout; its version comes from git tags.
361
+ fetch-depth: 0
362
+ - uses: astral-sh/setup-uv@v7
363
+ with:
364
+ enable-cache: true
365
+ # Saleor's pin lives in fetch_upstream.py; its lock is fetched after this step.
366
+ cache-dependency-glob: |
367
+ uv.lock
368
+ demos/fetch_upstream.py
369
+ - run: uv run --no-project python demos/fetch_upstream.py saleor
370
+ - name: Type-check the demo against Saleor
371
+ run: demos/saleor_checkout/run.sh --typecheck
372
+ - name: Saleor, every handoff accounted for
373
+ uses: ./check
374
+ with:
375
+ working-directory: demos/saleor_checkout
376
+ package: ${{ github.workspace }}
377
+ - name: Postgres without durability
378
+ run: demos/throwaway_postgres.sh
379
+ # Saleor's migrations depend only on its pin (in fetch_upstream.py): their result is cached as a
380
+ # dump, restored in seconds instead of migrating for two minutes.
381
+ - uses: actions/cache@v4
382
+ with:
383
+ path: ${{ env.SALEOR_DATABASE_DUMP }}
384
+ key: saleor-test-db-pg16-${{ hashFiles('demos/fetch_upstream.py', 'demos/saleor_checkout/prepare_db.py', 'demos/cached_database.py') }}
385
+ # One xdist worker per runner CPU. Four workers each migrating their own database took
386
+ # 193 s apiece; migrating once and cloning a copy per worker costs one migration.
387
+ - name: Saleor's test databases, migrated once
388
+ run: demos/saleor_checkout/run.sh --prepare-db 4
389
+ # The durations are the evidence for the next change to this job's speed. --dist worksteal was
390
+ # tried: pytest took 75 s and 80 s, inside the 66 to 91 s this step takes with the default on the
391
+ # same code, as runners vary. No gain was measurable, so the default stays.
392
+ - name: Saleor checkout
393
+ run: demos/saleor_checkout/run.sh -rxX --due-work-explore=smoke --due-work-verify -n 4 --reuse-db --durations=15 --due-work-require-assessed --due-work-profile-report=profiles-saleor.json
394
+
395
+ - name: Require the promised adopters and executed capabilities
396
+ run: |
397
+ demos/.upstream/saleor/.venv/bin/python scripts/check_adoption_report.py demos/saleor_checkout/profiles-saleor.json --contract "saleor checkout" --contract "saleor checkout, Transactions API with automatic completion" --exercised H I
398
+ demos/.upstream/saleor/.venv/bin/python scripts/check_adoption_report.py demos/saleor_checkout/profiles-saleor.json --contract "saleor payment event reports" --verified E --exercised I
399
+ - name: Keep executed profile evidence
400
+ if: always()
401
+ uses: actions/upload-artifact@v4
402
+ with:
403
+ name: profiles-saleor
404
+ path: |
405
+ profiles-*.json
406
+ demos/**/profiles-*.json
407
+ if-no-files-found: error
408
+
409
+ build:
410
+ # The published wheel alone: it installs without any framework, imports, and ships py.typed.
411
+ runs-on: ubuntu-latest
412
+ steps:
413
+ - uses: actions/checkout@v7
414
+ with:
415
+ # The version is derived from tags, so build with the history that has them.
416
+ fetch-depth: 0
417
+ - uses: astral-sh/setup-uv@v7
418
+ with:
419
+ enable-cache: true
420
+ - run: uv build
421
+ - run: unzip -l dist/*.whl | grep -q "pytest_obligation/py.typed"
422
+ - run: unzip -l dist/*.whl | grep -q "due_work_harness/py.typed"
423
+ - run: uv venv /tmp/wheel && uv pip install --python /tmp/wheel dist/*.whl
424
+ - run: /tmp/wheel/bin/python scripts/check_core_is_framework_free.py
425
+ - name: Verify old and new adopters against the installed wheel
426
+ run: /tmp/wheel/bin/python -m pytest tests/core/test_compatibility.py tests/core/test_package_identity.py -q
427
+
428
+ actions:
429
+ # The published GitHub Actions, run against the example adopter the way a project would use them.
430
+ runs-on: ubuntu-latest
431
+ steps:
432
+ - uses: actions/checkout@v7
433
+ with:
434
+ fetch-depth: 0
435
+ - name: check passes on a project whose every handoff is accounted for
436
+ uses: ./check
437
+ with:
438
+ working-directory: examples/adopter
439
+ package: ${{ github.workspace }}
440
+ - name: test runs the project's generated suites without Hypothesis
441
+ uses: ./test
442
+ with:
443
+ working-directory: examples/adopter
444
+ pytest-args: --due-work-require-assessed
445
+ - name: Generated histories also support optional search
446
+ uses: ./test
447
+ with:
448
+ working-directory: examples/adopter
449
+ sync-args: --extra exploration
450
+ pytest-args: --due-work-explore=smoke --due-work-require-assessed --due-work-profile-report=profiles-example.json
451
+ - name: Replay and sensitivity controls
452
+ working-directory: examples/adopter
453
+ run: uv run --no-sync pytest tests/test_catalog_failures.py
454
+ - name: Require example capabilities
455
+ working-directory: examples/adopter
456
+ run: uv run --no-sync python ../../scripts/check_adoption_report.py profiles-example.json --contract "example catalog" --verified E G H I
457
+ - name: Keep example profile evidence
458
+ if: always()
459
+ uses: actions/upload-artifact@v4
460
+ with:
461
+ name: profiles-example
462
+ path: examples/adopter/profiles-example.json
463
+ if-no-files-found: error
464
+ - name: check fails once a handoff loses its disposition
465
+ run: |
466
+ rm examples/adopter/tests/test_summaries_exemption.py
467
+ if uvx --from "$GITHUB_WORKSPACE" due-work-harness check --root examples/adopter; then
468
+ echo "::error::the check passed a project with an unaccounted handoff" >&2
469
+ exit 1
470
+ fi
@@ -0,0 +1,34 @@
1
+ name: PR title
2
+
3
+ # Pull requests are squash-merged with their title as the commit message, and
4
+ # release-please reads those commits to choose the next version. The title must
5
+ # therefore be a Conventional Commit: see RELEASING.md.
6
+
7
+ on:
8
+ pull_request_target:
9
+ types: [opened, edited, synchronize, reopened]
10
+
11
+ permissions:
12
+ pull-requests: read
13
+
14
+ jobs:
15
+ conventional-commit:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ # Reads only the title through the API; it never checks out the pull request's code.
19
+ - uses: amannn/action-semantic-pull-request@v5
20
+ env:
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22
+ with:
23
+ types: |
24
+ feat
25
+ fix
26
+ perf
27
+ revert
28
+ docs
29
+ refactor
30
+ test
31
+ build
32
+ ci
33
+ chore
34
+ requireScope: false