openfactory 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (783) hide show
  1. {openfactory-0.2.0/openfactory.egg-info → openfactory-0.2.1}/PKG-INFO +1 -1
  2. {openfactory-0.2.0 → openfactory-0.2.1}/README.md +2 -2
  3. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/__init__.py +1 -1
  4. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/base.py +2 -0
  5. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/catalog.py +431 -6
  6. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/base.py +234 -15
  7. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/claude_code.py +37 -10
  8. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/codex.py +34 -1
  9. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/kimi.py +22 -1
  10. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/opencode.py +21 -1
  11. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/roles.py +6 -2
  12. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/techlead.py +1 -1
  13. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/columns.py +46 -0
  14. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_db.py +56 -3
  15. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/credential/registry.py +29 -3
  16. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/azure_devops.py +159 -17
  17. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/base.py +18 -1
  18. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/github.py +75 -22
  19. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/local.py +223 -65
  20. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/claude_code.py +1 -1
  21. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/harness.py +1 -1
  22. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/base.py +11 -0
  23. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/worktree.py +59 -2
  24. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/azure_devops.py +27 -0
  25. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/github.py +26 -0
  26. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/jira.py +11 -0
  27. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/local.py +29 -0
  28. openfactory-0.2.1/openfactory/adapters/tracker/parse.py +437 -0
  29. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/after_merge.py +30 -0
  30. openfactory-0.2.1/openfactory/agent_answer.py +199 -0
  31. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/app.py +266 -73
  32. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/metrics_view.py +24 -4
  33. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/panel.html +366 -24
  34. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/box_prove.py +350 -15
  35. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/cli.py +372 -40
  36. openfactory-0.2.1/openfactory/contracts/checks.py +254 -0
  37. openfactory-0.2.1/openfactory/contracts/item_space.py +86 -0
  38. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/ticket.py +10 -0
  39. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/doctor.py +60 -24
  40. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/factory.py +70 -3
  41. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/floor/reading.py +124 -10
  42. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/base.py +14 -1
  43. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/local.py +70 -4
  44. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/people.py +48 -15
  45. openfactory-0.2.1/openfactory/observability/job_record.py +96 -0
  46. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/query.py +12 -3
  47. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/registry.py +8 -1
  48. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/sqlite_metrics.py +44 -1
  49. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/spend.py +9 -15
  50. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/machine.py +172 -22
  51. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/engineering.md +17 -0
  52. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/authoring.py +47 -5
  53. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/brownfield.py +23 -3
  54. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/case.py +1 -1
  55. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/channel.py +22 -0
  56. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/confirm.py +20 -0
  57. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/intents.py +26 -0
  58. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/module.py +154 -1
  59. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/queue.py +1 -9
  60. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/staging.py +4 -1
  61. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/triage.py +12 -16
  62. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/voice.py +258 -1
  63. openfactory-0.2.1/openfactory/runtime/host.py +299 -0
  64. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/activities.py +87 -28
  65. openfactory-0.2.1/openfactory/runtime/temporal/connection.py +163 -0
  66. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/schedule.py +33 -0
  67. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/view.py +294 -4
  68. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/worker.py +2 -1
  69. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/workflow.py +84 -9
  70. {openfactory-0.2.0 → openfactory-0.2.1/openfactory.egg-info}/PKG-INFO +1 -1
  71. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/SOURCES.txt +31 -0
  72. {openfactory-0.2.0 → openfactory-0.2.1}/pyproject.toml +1 -1
  73. openfactory-0.2.1/tests/test_a_card_is_named_in_the_forge_only_where_the_forge_owns_it.py +303 -0
  74. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_credential_is_scoped.py +1 -1
  75. openfactory-0.2.1/tests/test_a_failing_check_is_read_for_what_it_is.py +780 -0
  76. openfactory-0.2.1/tests/test_a_forge_that_brings_its_own_credential_is_not_reported_as_having_none.py +262 -0
  77. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_frame_does_not_erase_what_it_omits.py +176 -18
  78. openfactory-0.2.1/tests/test_a_fresh_install_reads_an_empty_store.py +93 -0
  79. openfactory-0.2.1/tests/test_a_job_branches_from_the_base_the_forge_holds.py +341 -0
  80. openfactory-0.2.1/tests/test_a_manifest_can_be_proven_before_it_is_merged.py +369 -0
  81. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_person_is_invited_not_signed_up.py +9 -4
  82. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_poller_that_is_on_is_not_a_poller_that_ticks.py +15 -4
  83. openfactory-0.2.1/tests/test_a_promotion_the_box_cannot_run_is_refused_before_the_first_card.py +168 -0
  84. openfactory-0.2.1/tests/test_a_proposal_whose_base_moved_still_lands_on_the_local_forge.py +303 -0
  85. openfactory-0.2.1/tests/test_a_pull_request_is_read_and_merged_in_its_own_repository.py +374 -0
  86. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_resume_re_enters_the_merge.py +5 -3
  87. openfactory-0.2.1/tests/test_a_scenario_is_one_criterion.py +296 -0
  88. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_ticket_written_in_portuguese.py +1 -0
  89. openfactory-0.2.1/tests/test_a_trust_file_nobody_reads_is_a_green_proof.py +192 -0
  90. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_box_prove.py +100 -0
  91. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_brownfield_wired.py +3 -2
  92. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_card_maintenance.py +209 -1
  93. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_card_maintenance_channel.py +74 -4
  94. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_deploy_watch_workflow.py +5 -3
  95. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_generates_the_deployment.py +53 -6
  96. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_gesture_exists_in_one_language_only.py +8 -0
  97. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_unbounded_growth.py +48 -6
  98. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_brownfield.py +38 -0
  99. openfactory-0.2.1/tests/test_product_init_writes_a_file_its_own_check_accepts.py +219 -0
  100. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_simulation.py +2 -2
  101. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_triage.py +48 -17
  102. openfactory-0.2.1/tests/test_provenance_in_the_brief.py +381 -0
  103. openfactory-0.2.1/tests/test_something_finally_asks_the_agent.py +530 -0
  104. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_temporal_workflow.py +12 -6
  105. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_action_layer.py +15 -0
  106. openfactory-0.2.1/tests/test_the_attended_driver_records_what_it_spent.py +124 -0
  107. openfactory-0.2.1/tests/test_the_board_is_a_page_on_the_panel.py +872 -0
  108. openfactory-0.2.1/tests/test_the_board_keeps_your_place.py +228 -0
  109. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_button_without_a_gate.py +4 -0
  110. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_cli_runs_in_the_deployments_box.py +30 -7
  111. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_cloud_is_a_directory_delete.py +15 -1
  112. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_docs_do_not_drift.py +88 -0
  113. openfactory-0.2.1/tests/test_the_engine_answers_or_says_so.py +211 -0
  114. openfactory-0.2.1/tests/test_the_engine_starts_only_beside_the_library_its_worker_runs_on.py +184 -0
  115. openfactory-0.2.1/tests/test_the_extra_ca_file_is_always_a_valid_pem.py +182 -0
  116. openfactory-0.2.1/tests/test_the_floor_is_not_re_read_on_every_frame.py +506 -0
  117. openfactory-0.2.1/tests/test_the_gate_holds_when_the_store_cannot_be_read.py +527 -0
  118. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_builds_the_commands_it_says_it_does.py +260 -6
  119. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_merge_is_the_end_of_the_road.py +5 -3
  120. openfactory-0.2.1/tests/test_the_panel_holds_one_engine_client.py +499 -0
  121. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_whether_work_is_picked_up.py +13 -2
  122. openfactory-0.2.1/tests/test_the_poller_has_a_lever.py +219 -0
  123. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_role_lives_outside_slack.py +108 -2
  124. openfactory-0.2.1/tests/test_the_proof_on_the_host.py +471 -0
  125. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_pull_request_lives_in_the_file.py +1 -1
  126. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_queue_proposes_in_batches.py +1 -1
  127. openfactory-0.2.1/tests/test_the_refusal_reads_the_file_it_would_write.py +126 -0
  128. openfactory-0.2.1/tests/test_the_set_ends_together.py +282 -0
  129. openfactory-0.2.1/tests/test_the_shell_we_ship_runs_on_both_seds.py +76 -0
  130. openfactory-0.2.1/tests/test_the_suite_runs_where_a_maintainer_sits.py +204 -0
  131. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_trajectory_reaches_the_record.py +12 -6
  132. openfactory-0.2.0/openfactory/adapters/tracker/parse.py +0 -231
  133. openfactory-0.2.0/openfactory/runtime/host.py +0 -91
  134. openfactory-0.2.0/openfactory/runtime/temporal/connection.py +0 -89
  135. openfactory-0.2.0/tests/test_the_board_is_a_page_on_the_panel.py +0 -293
  136. openfactory-0.2.0/tests/test_the_proof_on_the_host.py +0 -223
  137. {openfactory-0.2.0 → openfactory-0.2.1}/LICENSE +0 -0
  138. {openfactory-0.2.0 → openfactory-0.2.1}/NOTICE +0 -0
  139. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/__init__.py +0 -0
  140. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/floor_intents.py +0 -0
  141. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/__init__.py +0 -0
  142. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/__init__.py +0 -0
  143. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/registry.py +0 -0
  144. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/routes.py +0 -0
  145. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/session_store.py +0 -0
  146. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/stream.py +0 -0
  147. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/token_pool.py +0 -0
  148. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/azure_devops.py +0 -0
  149. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/__init__.py +0 -0
  150. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/azure_devops.py +0 -0
  151. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/base.py +0 -0
  152. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/factory.py +0 -0
  153. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/jira.py +0 -0
  154. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/local.py +0 -0
  155. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/__init__.py +0 -0
  156. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/base.py +0 -0
  157. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/local.py +0 -0
  158. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/registry.py +0 -0
  159. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/__init__.py +0 -0
  160. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/base.py +0 -0
  161. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/panel.py +0 -0
  162. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/registry.py +0 -0
  163. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/credential/__init__.py +0 -0
  164. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/__init__.py +0 -0
  165. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/azure_pipelines.py +0 -0
  166. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/base.py +0 -0
  167. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/github_actions.py +0 -0
  168. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/none.py +0 -0
  169. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/registry.py +0 -0
  170. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/__init__.py +0 -0
  171. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/registry.py +0 -0
  172. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/github_app.py +0 -0
  173. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/__init__.py +0 -0
  174. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/base.py +0 -0
  175. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/panel.py +0 -0
  176. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/registry.py +0 -0
  177. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/__init__.py +0 -0
  178. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/base.py +0 -0
  179. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/__init__.py +0 -0
  180. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/container.py +0 -0
  181. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/registry.py +0 -0
  182. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/timeouts.py +0 -0
  183. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/__init__.py +0 -0
  184. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/base.py +0 -0
  185. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/github_board_setup.py +0 -0
  186. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/github_project.py +0 -0
  187. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/registry.py +0 -0
  188. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/__init__.py +0 -0
  189. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/approvals.py +0 -0
  190. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/cli_refusals.py +0 -0
  191. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/conformance/__init__.py +0 -0
  192. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/conformance/adapters.py +0 -0
  193. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/__init__.py +0 -0
  194. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/bot.py +0 -0
  195. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/commands.py +0 -0
  196. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/decision.py +0 -0
  197. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/manifest.py +0 -0
  198. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/product.py +0 -0
  199. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/profile.py +0 -0
  200. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/project.py +0 -0
  201. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/refs.py +0 -0
  202. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/review.py +0 -0
  203. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/run.py +0 -0
  204. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/state.py +0 -0
  205. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/credentials.py +0 -0
  206. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/doors.py +0 -0
  207. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/environ.py +0 -0
  208. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/floor/__init__.py +0 -0
  209. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/floor/ladder.py +0 -0
  210. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/__init__.py +0 -0
  211. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/oidc.py +0 -0
  212. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/registry.py +0 -0
  213. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/__init__.py +0 -0
  214. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/bundle.py +0 -0
  215. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/check.py +0 -0
  216. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/contracts.py +0 -0
  217. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/experiment.py +0 -0
  218. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/gaps.py +0 -0
  219. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/gate.py +0 -0
  220. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/gather.py +0 -0
  221. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/generator.py +0 -0
  222. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/inventory.py +0 -0
  223. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/okf.py +0 -0
  224. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/pipeline.py +0 -0
  225. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/render.py +0 -0
  226. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/service.py +0 -0
  227. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/staleness.py +0 -0
  228. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/language/__init__.py +0 -0
  229. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/language/assent.py +0 -0
  230. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/loader.py +0 -0
  231. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/__init__.py +0 -0
  232. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/ledger.py +0 -0
  233. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/messages.py +0 -0
  234. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/recall.py +0 -0
  235. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/store.py +0 -0
  236. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/transcript.py +0 -0
  237. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/namespace.py +0 -0
  238. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/__init__.py +0 -0
  239. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/events.py +0 -0
  240. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/metrics.py +0 -0
  241. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/trajectory.py +0 -0
  242. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/__init__.py +0 -0
  243. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/concepts.py +0 -0
  244. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/context.py +0 -0
  245. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/cover.py +0 -0
  246. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/deployment.py +0 -0
  247. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/firstrun.py +0 -0
  248. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/history.py +0 -0
  249. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/infer.py +0 -0
  250. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/live_ci.py +0 -0
  251. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/onboard.py +0 -0
  252. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/propose_manifest.py +0 -0
  253. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/questions.py +0 -0
  254. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/readiness.py +0 -0
  255. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/renew.py +0 -0
  256. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/ops/__init__.py +0 -0
  257. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/ops/impediment.py +0 -0
  258. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/__init__.py +0 -0
  259. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/context.py +0 -0
  260. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/errors.py +0 -0
  261. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/merge_policy.py +0 -0
  262. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/promotion.py +0 -0
  263. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/risk.py +0 -0
  264. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/validation.py +0 -0
  265. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/floor.yaml +0 -0
  266. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/profiles/prototype.yaml +0 -0
  267. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/profiles/regulated.yaml +0 -0
  268. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/requirements/0000-template.md +0 -0
  269. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/coordinator.md +0 -0
  270. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/executor.md +0 -0
  271. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/planner.md +0 -0
  272. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/product.md +0 -0
  273. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/recovery.md +0 -0
  274. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/sizer.md +0 -0
  275. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/techlead.md +0 -0
  276. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/tdd.md +0 -0
  277. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/own_work.py +0 -0
  278. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/paths.py +0 -0
  279. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/plugins.py +0 -0
  280. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/__init__.py +0 -0
  281. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/authz.py +0 -0
  282. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/census.py +0 -0
  283. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/conformance.py +0 -0
  284. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/floor.py +0 -0
  285. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/presets.py +0 -0
  286. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/profiles.py +0 -0
  287. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/protected.py +0 -0
  288. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/test_work.py +0 -0
  289. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/preflight.py +0 -0
  290. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/node.yaml +0 -0
  291. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/python.yaml +0 -0
  292. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/security-oss.yaml +0 -0
  293. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/terraform.yaml +0 -0
  294. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/__init__.py +0 -0
  295. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/asked.py +0 -0
  296. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/board.py +0 -0
  297. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/config.py +0 -0
  298. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/conversation.py +0 -0
  299. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/corpus.py +0 -0
  300. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/domain.py +0 -0
  301. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/facts.py +0 -0
  302. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/followup.py +0 -0
  303. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/loader.py +0 -0
  304. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/needs_action.py +0 -0
  305. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/onboard.py +0 -0
  306. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/reading.py +0 -0
  307. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/release.py +0 -0
  308. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/requester.py +0 -0
  309. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/role.py +0 -0
  310. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/workspace.py +0 -0
  311. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/registry.py +0 -0
  312. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/review/__init__.py +0 -0
  313. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/review/verdict.py +0 -0
  314. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/__init__.py +0 -0
  315. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/boxed_job.py +0 -0
  316. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/card_repo.py +0 -0
  317. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/repo_cache.py +0 -0
  318. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/__init__.py +0 -0
  319. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/io.py +0 -0
  320. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/poller.py +0 -0
  321. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/starter.py +0 -0
  322. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/toolbox.py +0 -0
  323. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/scheduler.py +0 -0
  324. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/semver.py +0 -0
  325. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/__init__.py +0 -0
  326. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/classify.py +0 -0
  327. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/conversation.py +0 -0
  328. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/diagnosis.py +0 -0
  329. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/memory.py +0 -0
  330. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/pack.py +0 -0
  331. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/voice.py +0 -0
  332. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/watch.py +0 -0
  333. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/testing/__init__.py +0 -0
  334. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/testing/local_flow.py +0 -0
  335. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/__init__.py +0 -0
  336. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/bounded.py +0 -0
  337. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/causes.py +0 -0
  338. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/scratch.py +0 -0
  339. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/dependency_links.txt +0 -0
  340. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/entry_points.txt +0 -0
  341. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/requires.txt +0 -0
  342. {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/top_level.txt +0 -0
  343. {openfactory-0.2.0 → openfactory-0.2.1}/setup.cfg +0 -0
  344. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_banner_line_does_not_kill_the_review.py +0 -0
  345. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_budget_nothing_spends_is_not_a_defect.py +0 -0
  346. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_card_says_who_asked_for_it.py +0 -0
  347. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_case_does_not_outlive_its_test.py +0 -0
  348. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_concept_is_earned_not_declared.py +0 -0
  349. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_context_repository_is_creatable_on_azure_too.py +0 -0
  350. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_count_is_a_number_not_a_sentence.py +0 -0
  351. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_credential_is_asked_of_its_own_axis.py +0 -0
  352. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_credential_only_goes_to_its_own_host.py +0 -0
  353. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_decision_is_written_down.py +0 -0
  354. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_declared_environment_is_observed_or_named.py +0 -0
  355. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_delivered_ticket_is_not_rerun.py +0 -0
  356. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_disabled_project_does_not_look_armed.py +0 -0
  357. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_double_without_intake_answers_every_turn.py +0 -0
  358. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_failed_board_read_is_not_a_wrong_root.py +0 -0
  359. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_failing_setup_is_not_silent.py +0 -0
  360. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_file_nothing_describes_is_the_least_safe_to_change.py +0 -0
  361. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_fingerprint_gains_a_reader.py +0 -0
  362. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_free_deployment_can_read_its_logs.py +0 -0
  363. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_free_deployment_resumes_its_own_sessions.py +0 -0
  364. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_fresh_install_registers_no_projects.py +0 -0
  365. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_gate_is_not_a_stall.py +0 -0
  366. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_gate_that_could_not_run.py +0 -0
  367. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_gate_that_holds_says_so_where_the_person_decides.py +0 -0
  368. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_green_deploy_asks_somebody_to_look.py +0 -0
  369. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_green_environment_is_green_for_THIS_change.py +0 -0
  370. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_hostile_value_stays_data.py +0 -0
  371. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_human_gate_is_not_a_review.py +0 -0
  372. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_jira_project_is_a_board.py +0 -0
  373. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_killed_mutation_run_cannot_hide.py +0 -0
  374. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_local_deployment_names_no_vendor.py +0 -0
  375. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_manifest_that_loads_is_not_a_manifest_that_is_right.py +0 -0
  376. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_message_never_claims_an_action_nobody_takes.py +0 -0
  377. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_message_only_teaches_a_verb_the_channel_parses.py +0 -0
  378. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_model_nobody_can_run_is_not_a_setting.py +0 -0
  379. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_named_check_is_not_dropped.py +0 -0
  380. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_negation_the_bridge_carries_is_refused.py +0 -0
  381. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_pass_in_flight_is_not_a_gate.py +0 -0
  382. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_pass_that_changed_nothing.py +0 -0
  383. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_path_nothing_declares_is_not_the_freest.py +0 -0
  384. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_path_registers_itself.py +0 -0
  385. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_paused_factory_says_so.py +0 -0
  386. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_personal_accounts_board_is_readable.py +0 -0
  387. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_personal_boards_columns_can_be_read.py +0 -0
  388. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_preexisting_context_repo_is_declarable.py +0 -0
  389. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_probe_set_is_pinned_whole.py +0 -0
  390. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_project_can_change_the_language_it_speaks_first.py +0 -0
  391. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_project_declares_what_it_is.py +0 -0
  392. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_project_is_not_registered_as_the_wrong_vendor.py +0 -0
  393. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_proof_that_was_not_recorded_says_so.py +0 -0
  394. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_proposal_can_carry_what_to_change.py +0 -0
  395. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_proposed_command_can_run_where_the_box_runs_it.py +0 -0
  396. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_pull_that_changes_nothing_is_impossible_to_miss.py +0 -0
  397. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_question_reaches_the_person_who_asked.py +0 -0
  398. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_range_is_a_citation.py +0 -0
  399. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_rebuild_is_not_a_change.py +0 -0
  400. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_ref_that_is_not_a_number.py +0 -0
  401. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_rejected_review_can_be_asked_again.py +0 -0
  402. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_remedy_names_a_document_that_exists.py +0 -0
  403. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_repaired_card_is_announced_on_any_tracker.py +0 -0
  404. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_repository_on_the_former_name_is_refused_at_every_door.py +0 -0
  405. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_retired_pipeline_is_not_a_gate.py +0 -0
  406. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_review_belongs_to_the_code_it_read.py +0 -0
  407. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_scratch_directory_is_the_only_thing_we_delete.py +0 -0
  408. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_skip_is_something_that_happened.py +0 -0
  409. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_staged_decision_survives_a_refresh.py +0 -0
  410. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_stranger_can_add_a_role.py +0 -0
  411. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_stranger_can_add_an_adapter.py +0 -0
  412. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_suite_that_stopped_collecting_is_not_a_green_suite.py +0 -0
  413. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_ticket_ref_is_never_typed_as_a_number.py +0 -0
  414. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_typed_instruction_reaches_the_same_row_as_the_button.py +0 -0
  415. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_vendor_error_code_is_not_a_status.py +0 -0
  416. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_verb_says_what_to_put_in_it.py +0 -0
  417. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_wait_can_say_until_when.py +0 -0
  418. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_wedged_job_has_an_exit.py +0 -0
  419. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_wedged_job_is_seen.py +0 -0
  420. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_write_that_did_not_land_is_not_counted.py +0 -0
  421. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_yes_is_an_answer.py +0 -0
  422. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_accept_flows_end_to_end.py +0 -0
  423. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_acceptance_loop.py +0 -0
  424. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_accepting_starts_the_work.py +0 -0
  425. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_acknowledgement.py +0 -0
  426. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_adapter_conformance_suite.py +0 -0
  427. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_advisory_findings_survive_save.py +0 -0
  428. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_harness.py +0 -0
  429. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_resume_c2.py +0 -0
  430. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_roles.py +0 -0
  431. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_stream.py +0 -0
  432. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_token_pool.py +0 -0
  433. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_already_asked_is_answered_from_the_board_not_the_transcript.py +0 -0
  434. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_advisory_gate_cannot_hold_a_pickup.py +0 -0
  435. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_advisory_gate_reports_and_never_blocks.py +0 -0
  436. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_environment_written_before_this_change_still_resolves_the_old_path.py +0 -0
  437. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_intake_is_a_typed_case.py +0 -0
  438. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_open_question_can_be_retired.py +0 -0
  439. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_open_question_is_an_offer_not_a_toll.py +0 -0
  440. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_unread_review_is_not_a_rejection.py +0 -0
  441. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_unreadable_store_is_not_an_empty_one.py +0 -0
  442. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_api.py +0 -0
  443. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_board_is_vendor_neutral.py +0 -0
  444. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_board_read_cost.py +0 -0
  445. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_bot_push.py +0 -0
  446. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_box_carries_the_provider_kind.py +0 -0
  447. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_breakdown_reuses_what_exists.py +0 -0
  448. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_c18_has_an_azure_expression.py +0 -0
  449. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_c18_reaches_every_entry_point.py +0 -0
  450. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_causes.py +0 -0
  451. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_ci_runs_what_we_run.py +0 -0
  452. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_client_board_is_not_a_test_bench.py +0 -0
  453. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_commit_workflow_strip.py +0 -0
  454. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_compose_forwards_the_whole_env_file.py +0 -0
  455. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_confirmation_by_click.py +0 -0
  456. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_confirmation_is_understood.py +0 -0
  457. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_context.py +0 -0
  458. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_conversation_worlds.py +0 -0
  459. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_corpus_health_and_board_placement.py +0 -0
  460. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_create_repository_survives_an_app_token.py +0 -0
  461. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_decision.py +0 -0
  462. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_decision_loop.py +0 -0
  463. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_diff_hygiene.py +0 -0
  464. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_doctor.py +0 -0
  465. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_doctor_checks_the_product_link.py +0 -0
  466. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_env_apply_answers_in_the_readers_language.py +0 -0
  467. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_env_apply_takes_a_path_like_env_read.py +0 -0
  468. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_env_proposes_before_it_verifies.py +0 -0
  469. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_event_sinks.py +0 -0
  470. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_every_FROM_the_release_builds_resolves_without_the_local_daemon.py +0 -0
  471. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_every_image_the_compose_file_names_is_one_the_release_builds.py +0 -0
  472. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_every_mutation_plan_can_run.py +0 -0
  473. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_factory_asks_for_help.py +0 -0
  474. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_filed_work_is_reachable.py +0 -0
  475. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_first_real_conversation.py +0 -0
  476. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_forge_ci.py +0 -0
  477. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_forge_deploy.py +0 -0
  478. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_forge_idempotency.py +0 -0
  479. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_foundation.py +0 -0
  480. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_gh_is_authenticated.py +0 -0
  481. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_handoff.py +0 -0
  482. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_asks_for_one_machine.py +0 -0
  483. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_speaks_to_the_operator_not_to_us.py +0 -0
  484. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_that_acts.py +0 -0
  485. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_internal_imports_resolve.py +0 -0
  486. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_judge_verdict_parsing.py +0 -0
  487. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_kernel_names_no_vendor.py +0 -0
  488. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge.py +0 -0
  489. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_build_reaches_a_factory_cloned_project.py +0 -0
  490. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_derived_not_cached.py +0 -0
  491. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_experiment.py +0 -0
  492. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_pipeline.py +0 -0
  493. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_local_flow.py +0 -0
  494. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_loops_are_reachable.py +0 -0
  495. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_manifest_version.py +0 -0
  496. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_memory_ledger.py +0 -0
  497. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_memory_recall_eval.py +0 -0
  498. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_memory_store.py +0 -0
  499. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_merge_policy.py +0 -0
  500. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_metrics.py +0 -0
  501. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_card_id_reaches_a_stranger.py +0 -0
  502. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_checkout_assumes_a_branch_name.py +0 -0
  503. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_module_is_ever_called_like_a_function.py +0 -0
  504. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_plumbing_reaches_the_client.py +0 -0
  505. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_silent_failures.py +0 -0
  506. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_non_numeric_refs_reach_the_api.py +0 -0
  507. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_nothing_speaks_before_it_asks_the_language.py +0 -0
  508. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_observability_registries.py +0 -0
  509. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboard_builds_the_context_too.py +0 -0
  510. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboard_proposes_a_measured_setup.py +0 -0
  511. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_a_context_that_already_exists.py +0 -0
  512. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_backfills_the_context.py +0 -0
  513. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_creates_the_context_repository.py +0 -0
  514. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_firstrun.py +0 -0
  515. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_one_process_one_installation.py +0 -0
  516. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_one_question_decides_where_the_factory_runs.py +0 -0
  517. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_opencode_harness.py +0 -0
  518. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_orphaned_cards_are_repointed.py +0 -0
  519. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_panel_approval_store.py +0 -0
  520. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_panel_reads_events_without_aws.py +0 -0
  521. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_park_alert.py +0 -0
  522. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_paths_are_traversal_safe.py +0 -0
  523. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_permission_is_an_order_in_every_language.py +0 -0
  524. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_ports_are_serialisable.py +0 -0
  525. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight.py +0 -0
  526. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight_json_is_the_document_the_agent_lane_reads.py +0 -0
  527. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight_names_a_remedy_for_every_thing_it_refuses.py +0 -0
  528. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight_says_which_machine_it_measured.py +0 -0
  529. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_authoring.py +0 -0
  530. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_board_reads.py +0 -0
  531. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_channel.py +0 -0
  532. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_config.py +0 -0
  533. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_corpus.py +0 -0
  534. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_followup.py +0 -0
  535. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_followup_e2e.py +0 -0
  536. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_intents.py +0 -0
  537. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_loader.py +0 -0
  538. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_module.py +0 -0
  539. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_needs_action.py +0 -0
  540. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_role.py +0 -0
  541. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_sees_the_code.py +0 -0
  542. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_sweep.py +0 -0
  543. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_view_is_stable.py +0 -0
  544. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_voice.py +0 -0
  545. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_workspace.py +0 -0
  546. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_project_add_exposes_language.py +0 -0
  547. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_project_add_speaks_azure_devops.py +0 -0
  548. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_promotion.py +0 -0
  549. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_provider_seams.py +0 -0
  550. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_public_core_names_no_forge.py +0 -0
  551. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_recent_runs_outlives_the_engine.py +0 -0
  552. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_registry_is_runtime_state.py +0 -0
  553. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_repo_cache_credentials.py +0 -0
  554. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_repo_cache_lifecycle.py +0 -0
  555. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_requirement_can_be_dropped.py +0 -0
  556. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_requirement_lifecycle.py +0 -0
  557. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sandbox_registry.py +0 -0
  558. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_scheduler.py +0 -0
  559. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_schedules_are_reachable.py +0 -0
  560. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_somebody_is_asked_to_look_at_what_shipped.py +0 -0
  561. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sqlite_metrics.py +0 -0
  562. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sso_is_a_row_the_core_ships.py +0 -0
  563. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sweep_client_surface.py +0 -0
  564. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sweep_records_only_what_posted.py +0 -0
  565. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_classify.py +0 -0
  566. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_memory.py +0 -0
  567. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_self_heal.py +0 -0
  568. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_watch.py +0 -0
  569. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_telemetry_reads_from_the_configured_sink.py +0 -0
  570. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_temporal_call_arity.py +0 -0
  571. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_temporal_view.py +0 -0
  572. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_acceptance_map_reaches_a_person.py +0 -0
  573. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_add_on_packages_install.py +0 -0
  574. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_board.py +0 -0
  575. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_coordinates_have_one_home.py +0 -0
  576. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_credential_outlives_one_token.py +0 -0
  577. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_forge.py +0 -0
  578. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_pipelines.py +0 -0
  579. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_tracker.py +0 -0
  580. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_alert_names_its_ticket_on_any_tracker.py +0 -0
  581. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_announcement_carries_the_verdict.py +0 -0
  582. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_app_key_reaches_every_entry_point.py +0 -0
  583. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_app_permission_table_has_one_home.py +0 -0
  584. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_areas_that_change_are_the_ones_that_matter.py +0 -0
  585. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfill_is_metered.py +0 -0
  586. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfill_names_the_real_reason.py +0 -0
  587. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfill_reads_every_source.py +0 -0
  588. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfills_questions_can_be_finished.py +0 -0
  589. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_base_branch_is_asked_not_assumed.py +0 -0
  590. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_bench_is_not_the_case.py +0 -0
  591. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_lives_in_one_file.py +0 -0
  592. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_names_its_own_pickup_column.py +0 -0
  593. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_port_takes_any_providers_ref.py +0 -0
  594. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_says_where_it_lives.py +0 -0
  595. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_can_obtain_the_repository.py +0 -0
  596. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_can_reach_the_pr_branch.py +0 -0
  597. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_claims_only_what_it_does.py +0 -0
  598. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_clones_from_the_forge_it_was_told.py +0 -0
  599. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_does_not_run_the_clients_shell_profile.py +0 -0
  600. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_env_is_a_named_seam.py +0 -0
  601. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_image_resolves_in_one_place.py +0 -0
  602. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_image_the_worker_spawns_is_one_the_install_pulls.py +0 -0
  603. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_program_can_be_launched.py +0 -0
  604. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_proof_is_watchable.py +0 -0
  605. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_says_where_the_harness_is.py +0 -0
  606. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_transmits_while_it_runs.py +0 -0
  607. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_boxed_job_entrypoint.py +0 -0
  608. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_branch_carries_the_products_name.py +0 -0
  609. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_card_carries_ONE_acceptance_section.py +0 -0
  610. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_card_moves_even_when_the_link_cannot_be_built.py +0 -0
  611. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_card_says_what_the_floor_says.py +0 -0
  612. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_chat_has_its_own_fences.py +0 -0
  613. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_chat_is_a_directory_delete.py +0 -0
  614. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_chat_thread_survives_a_refresh.py +0 -0
  615. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ci_is_read_more_than_once.py +0 -0
  616. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_cli_refuses_in_sentences.py +0 -0
  617. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_client_never_reads_an_emoji.py +0 -0
  618. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_client_releases_production.py +0 -0
  619. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_columns_and_the_identity_are_the_platforms.py +0 -0
  620. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_compose_file_can_be_installed_and_built_from_one_file.py +0 -0
  621. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_confirmation_executor.py +0 -0
  622. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_context_proposal_is_reachable.py +0 -0
  623. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_context_repository_can_be_created.py +0 -0
  624. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_context_repository_exists_on_this_runtime.py +0 -0
  625. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_addon_ledger.py +0 -0
  626. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_boundary.py +0 -0
  627. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_does_not_need_a_cloud.py +0 -0
  628. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_is_a_core.py +0 -0
  629. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_credential_scan_reads_a_delimiter.py +0 -0
  630. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_default_language_is_the_products.py +0 -0
  631. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_deployment_asks_the_rows_for_their_variables.py +0 -0
  632. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_diff_decides_scope_and_what_validates.py +0 -0
  633. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_distribution_ships_what_it_tells_you_to_copy.py +0 -0
  634. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_docs_name_no_vendor_as_the_core.py +0 -0
  635. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_docs_say_which_promise.py +0 -0
  636. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doctor_says_where_project_less_speech_goes.py +0 -0
  637. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doctor_survives_its_own_probes.py +0 -0
  638. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doctor_tells_the_truth_about_this_machine.py +0 -0
  639. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documentation_has_one_front_door.py +0 -0
  640. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documented_default_is_the_real_one.py +0 -0
  641. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documents_describe_the_product_this_tree_is.py +0 -0
  642. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documents_name_the_repository_and_keep_their_accents.py +0 -0
  643. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doors_derive_from_the_registries.py +0 -0
  644. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_end_to_end_install_is_a_script_the_suite_can_run.py +0 -0
  645. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_engine_dying_under_a_job_is_not_a_mystery.py +0 -0
  646. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_environment_carries_the_products_name.py +0 -0
  647. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_environment_round_is_a_terminal_verb.py +0 -0
  648. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_event_axis_is_reached.py +0 -0
  649. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_examples_a_reader_copies_load_on_the_core.py +0 -0
  650. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_extensibility_doc_names_the_real_group.py +0 -0
  651. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_factory_asks_before_it_starts.py +0 -0
  652. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_factory_authors_what_it_does_not_know_before_it_changes_it.py +0 -0
  653. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_factory_runs_on_one_machine.py +0 -0
  654. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_factory_runs_without_a_cloud.py +0 -0
  655. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_facts_are_files_the_techlead_can_open.py +0 -0
  656. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_facts_stay_files_for_every_harness.py +0 -0
  657. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_first_hour_fails_by_name.py +0 -0
  658. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_first_round_is_reachable.py +0 -0
  659. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_first_yes_writes_and_the_second_yes_is_on_the_ticket.py +0 -0
  660. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_and_the_inbox_agree.py +0 -0
  661. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_is_a_deployment_default_not_a_transcription.py +0 -0
  662. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_is_a_platform_capability.py +0 -0
  663. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_is_enforced_where_the_money_is.py +0 -0
  664. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_forge_can_read_a_foreign_repository.py +0 -0
  665. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_forge_credential_is_per_project.py +0 -0
  666. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_forge_opens_a_pr_in_another_repository.py +0 -0
  667. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_form_asks_what_the_address_needs.py +0 -0
  668. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_gate_checks_the_shell_script_a_stranger_runs_first.py +0 -0
  669. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_gate_shows_what_the_review_found.py +0 -0
  670. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_generated_environment_names_a_work_directory_that_needs_no_root.py +0 -0
  671. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_harness_endpoint_is_the_clients.py +0 -0
  672. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_harness_is_watched_while_it_runs.py +0 -0
  673. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_harness_pulse.py +0 -0
  674. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_history_says_where_the_work_is.py +0 -0
  675. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_install_line_the_readme_prints_is_the_url_the_release_publishes_to.py +0 -0
  676. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_knows_exactly_two_facts_the_package_does_not.py +0 -0
  677. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_never_calls_sudo.py +0 -0
  678. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_pins_a_version_and_never_installs_a_floating_tag.py +0 -0
  679. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_inventory_counts_what_it_could_not_place.py +0 -0
  680. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_journal_is_where_the_deployment_says.py +0 -0
  681. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_journal_records_how_a_job_ENDED.py +0 -0
  682. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_judging_roles_can_reach_their_model.py +0 -0
  683. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_language_reaches_every_unprompted_surface.py +0 -0
  684. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_lifecycle_names_no_provider.py +0 -0
  685. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_loop_tells_the_truth.py +0 -0
  686. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_manifest_can_be_proposed_server_side.py +0 -0
  687. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_manifest_reference_is_complete.py +0 -0
  688. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_map_does_not_wait_for_a_merge.py +0 -0
  689. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_map_is_published_beside_the_concepts.py +0 -0
  690. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_map_says_what_it_does_not_cover.py +0 -0
  691. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_merge_re_authors_what_it_invalidated.py +0 -0
  692. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_model_is_a_per_project_choice.py +0 -0
  693. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_model_is_set_from_the_cli.py +0 -0
  694. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_mutation_runner_is_not_decoration.py +0 -0
  695. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_namespace_is_the_products_name.py +0 -0
  696. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_net_that_protects_the_client.py +0 -0
  697. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_number_told_is_the_number_written.py +0 -0
  698. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_one_machine_deployment_runs_unattended.py +0 -0
  699. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_open_distribution_has_a_voice.py +0 -0
  700. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_operator_hears_the_projects_language.py +0 -0
  701. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_operator_path_names_what_the_code_mints.py +0 -0
  702. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_oss_distribution.py +0 -0
  703. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_is_a_channel.py +0 -0
  704. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_is_pushed_to.py +0 -0
  705. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_names_no_vendor.py +0 -0
  706. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_reads_as_a_control_room.py +0 -0
  707. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_a_pass_is_running.py +0 -0
  708. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_where_to_change_things.py +0 -0
  709. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_which_harness.py +0 -0
  710. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_speaks_one_language.py +0 -0
  711. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_touches_no_cloud_locally.py +0 -0
  712. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_platform_reads_a_repository_and_proposes.py +0 -0
  713. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_carries_no_ones_past.py +0 -0
  714. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_carries_no_owners_name.py +0 -0
  715. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_conversation_is_core.py +0 -0
  716. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_opens_a_card_as_described.py +0 -0
  717. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_orders_the_backlog_from_chat.py +0 -0
  718. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_reads_an_intake_and_teaches.py +0 -0
  719. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_writes_the_backlog_order.py +0 -0
  720. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owners_facts_are_files.py +0 -0
  721. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_path_is_not_github_by_construction.py +0 -0
  722. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_role_speaks_the_clients_language.py +0 -0
  723. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_project_remembers_what_everyone_said.py +0 -0
  724. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_promotion_tail_speaks.py +0 -0
  725. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_proof_gates_each_repository.py +0 -0
  726. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_proof_gates_pickup.py +0 -0
  727. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_proposal_covers_the_floor.py +0 -0
  728. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_public_cut_is_written_down.py +0 -0
  729. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_public_documents_serve_a_stranger.py +0 -0
  730. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_pull_request_says_what_the_card_says.py +0 -0
  731. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_reading_is_bound_against_the_bundle_that_exists.py +0 -0
  732. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_release_assembles_what_the_installer_downloads.py +0 -0
  733. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_remedy_a_refusal_hands_you_can_be_followed.py +0 -0
  734. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_renewal_keeps_the_inventory_current.py +0 -0
  735. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_repository_moves_to_the_ticket.py +0 -0
  736. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_review_findings_stay_fixed.py +0 -0
  737. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_reviewer_can_actually_run.py +0 -0
  738. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_role_does_not_claim_blindness.py +0 -0
  739. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_roles_can_reach_the_bundle.py +0 -0
  740. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_roles_run_on_the_local_rows.py +0 -0
  741. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_second_deployment_is_not_the_first.py +0 -0
  742. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_second_yes_is_the_requesters.py +0 -0
  743. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_security_policy_promises_what_the_scrub_does.py +0 -0
  744. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_sizer_names_the_area_it_expects_to_touch.py +0 -0
  745. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_socket_is_real_and_scoped.py +0 -0
  746. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_split_says_which_card_to_drag.py +0 -0
  747. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_starter_template_passes_its_own_floor.py +0 -0
  748. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_station_strip_resets_on_a_re_run.py +0 -0
  749. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_stock_box_runs_what_the_proposer_proposes.py +0 -0
  750. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_structural_documents_speak_the_projects_portuguese.py +0 -0
  751. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_suite_cannot_reach_a_live_engine.py +0 -0
  752. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_suite_cannot_reach_production.py +0 -0
  753. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_answers_without_a_channel.py +0 -0
  754. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_can_see_the_change_it_judges.py +0 -0
  755. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_can_see_what_it_reasons_about.py +0 -0
  756. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_does_not_cry_wolf.py +0 -0
  757. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_reads_through_the_ports.py +0 -0
  758. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_remembers_the_conversation.py +0 -0
  759. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_sees_a_jira_floor.py +0 -0
  760. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ticket_url_is_the_trackers_own.py +0 -0
  761. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_toolbox.py +0 -0
  762. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_tracker_has_a_read_side.py +0 -0
  763. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_tracker_reads_what_a_person_wrote.py +0 -0
  764. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_trajectory_is_a_measurement.py +0 -0
  765. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_two_halves_agree_on_which_code_they_run.py +0 -0
  766. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_vendors_credential_is_found_by_default.py +0 -0
  767. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_verifier_is_not_editable_by_what_it_verifies.py +0 -0
  768. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_walk_respects_what_the_repository_ignores.py +0 -0
  769. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_web_conversation_is_directed_at_the_person_in_it.py +0 -0
  770. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_web_offers_the_room_and_the_private_chat.py +0 -0
  771. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_wheel_is_published_under_the_name_the_docs_tell_you_to_install.py +0 -0
  772. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_wheel_ships_what_the_platform_needs.py +0 -0
  773. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_word_list_is_a_shortcut_not_a_gate.py +0 -0
  774. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_three_commands_one_verdict.py +0 -0
  775. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_token_provider.py +0 -0
  776. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_tracker_board_writes.py +0 -0
  777. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_tracker_is_vendor_neutral.py +0 -0
  778. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_transcript_memory.py +0 -0
  779. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_two_projects_do_not_share_a_box.py +0 -0
  780. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_voice_vocabulary.py +0 -0
  781. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_walking_skeleton.py +0 -0
  782. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_what_git_refuses_the_person_reads.py +0 -0
  783. {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_who_is_asking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openfactory
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A framework to run software tickets autonomously with coding agents — policies authorize actions, humans evaluate results.
5
5
  Author: The OpenFactory Authors
6
6
  License-Expression: Apache-2.0
@@ -42,7 +42,7 @@ Prerequisites: git, Python 3.12+, and a coding agent on your PATH already signed
42
42
  ```bash
43
43
  git clone https://github.com/Open-Factory-Digital/openfactory-core.git && cd openfactory-core
44
44
  python3 -m venv .venv && source .venv/bin/activate # 3.12+; see docs/ONBOARDING.md §0
45
- pip install -e .
45
+ pip install -e '.[runtime]' # the durable engine's worker runs on the extra
46
46
 
47
47
  openfactory init # press Enter twice: your code and your tickets live HERE
48
48
  openfactory project init myapp ~/code/myapp # a path registers as itself — no owner, no board
@@ -57,7 +57,7 @@ openfactory act card_move -p myapp -i 1 -P column=TO-DO
57
57
  openfactory poll myapp # one card, all the way to Done
58
58
  ```
59
59
 
60
- `openfactory panel` serves the same thing in a browser at http://localhost:8787 — the Board, the
60
+ `openfactory serve` serves the same thing in a browser at http://localhost:8787 — the Board, the
61
61
  job, the pull request. The whole door is **[docs/setup/one-machine.md](docs/setup/one-machine.md)**,
62
62
  including what happens to your working tree when a merge lands.
63
63
 
@@ -4,4 +4,4 @@ Policies authorize actions; humans evaluate results. See
4
4
  docs/adr/0001-foundational-conceptual-model.md for the conceptual model.
5
5
  """
6
6
 
7
- __version__ = "0.2.0"
7
+ __version__ = "0.2.1"
@@ -230,6 +230,8 @@ PARAMS: dict[str, str] = {
230
230
  "term": "the word or phrase being defined, as the business says it",
231
231
  "body": "what it means, in the business's own words",
232
232
  "restated": "the broken promise, restated as what should happen and what happens instead",
233
+ "text": ("what the card should say instead — it replaces what was asked, or what is "
234
+ "happening, on a card the product role opened"),
233
235
  "title": "what the card is called, in the person's own words — short",
234
236
  "column": "the column's name as it is written on the board, e.g. `TO-DO`",
235
237
  "violates": "the id of the requirement this breaks, if one is known",
@@ -2988,6 +2988,32 @@ async def _product_close_card(*, project: str, number: str, by: Actor, in_favour
2988
2988
  return _write_outcome(result, did=f"closed #{ref}", project=proj.name)
2989
2989
 
2990
2990
 
2991
+ async def _product_correct_card(*, project: str, number: str, by: Actor, text: str = "",
2992
+ title: str = "", yes: object = False) -> Outcome:
2993
+ """Correct a card the product role opened from a request or a defect — its text or its title.
2994
+
2995
+ THE PRODUCT OWNER'S HAND ON THEIR OWN CARD (#156). The board refuses to change such a card
2996
+ (#150), and this row is what the refusal points at. It spends no agent pass, so it runs here
2997
+ like `product_close_card`."""
2998
+ import asyncio
2999
+
3000
+ module, proj, bad = _product_module(project, by=by)
3001
+ if bad:
3002
+ return bad
3003
+ ref = str(number or "").strip().lstrip("#")
3004
+ if not ref:
3005
+ return refused(INVALID, "say which card to correct.")
3006
+ if not _said_yes(yes):
3007
+ return refused(INVALID, f"nothing was corrected: #{ref} stays as it is without `yes`.")
3008
+ wanted_text, wanted_title = (text or "").strip(), (title or "").strip()
3009
+ if not wanted_text and not wanted_title:
3010
+ return refused(INVALID, f"say what #{ref} should say — a correction with neither a text "
3011
+ f"nor a title changes nothing.")
3012
+ result = await asyncio.to_thread(
3013
+ lambda: module.correct_card(ref, actor=by.id, text=wanted_text, title=wanted_title))
3014
+ return _write_outcome(result, did=f"corrected #{ref}", project=proj.name)
3015
+
3016
+
2991
3017
  async def _product_record_decision(*, project: str, number: str, decision: str, by: Actor,
2992
3018
  yes: object = False) -> Outcome:
2993
3019
  """Write a decision taken AFTER the acceptance into the requirement's own register.
@@ -3096,6 +3122,56 @@ def _board_pair(project: str):
3096
3122
  return proj, build_tracker(proj, token=token), build_board(proj, token=token), None
3097
3123
 
3098
3124
 
3125
+ def _pickup_says(tracker, ref: str) -> str | None:
3126
+ """The spec gate's verdict on a card AS THE TRACKER NOW HOLDS IT — `""` when pickup would take
3127
+ it, the refusal when it would not, None when the card could not be read back.
3128
+
3129
+ Read back rather than parsed from what was sent, because that is what pickup reads: a hosted row
3130
+ converts and sanitises a body on the way in, and the verdict on the text somebody typed is not
3131
+ the verdict on the card."""
3132
+ from openfactory.orchestrator.machine import spec_verdict
3133
+
3134
+ try:
3135
+ return spec_verdict(tracker.get_ticket(ref))
3136
+ except Exception as exc: # noqa: BLE001 — the write landed; no verdict is not a failure
3137
+ log.warning("OPENFACTORY_CARD_VERDICT_UNREAD card=%s: the card was written but could not "
3138
+ "be read back to ask the spec gate, so no verdict is reported — %s", ref, exc)
3139
+ return None
3140
+
3141
+
3142
+ def _as_pickup_would(message: str, verdict: str | None) -> tuple[str, dict]:
3143
+ """A write's line, with what pickup would say added when it would refuse the card (#150)."""
3144
+ if verdict is None:
3145
+ return message, {}
3146
+ if verdict:
3147
+ return f"{message}. As written, pickup would refuse it: {verdict}", {"refusal": verdict}
3148
+ return message, {"refusal": ""}
3149
+
3150
+
3151
+ async def _card_check(*, project: str, by: Actor, title: str = "", body: str = "") -> Outcome:
3152
+ """What pickup would say about a card before it is saved — the spec gate, run on a draft.
3153
+
3154
+ WRITES NOTHING, and is a row anyway (ADR-0039): the page that writes a card asks it on every
3155
+ pause in typing, and a verdict computed by the page would be the second rule this exists to
3156
+ avoid. The answer also carries the draft as the parser reads it, which is how the page learns
3157
+ whether its form can hold a card's whole body before it offers the form for editing it."""
3158
+ from openfactory.adapters.tracker.parse import form_fields, parse_ticket_body
3159
+ from openfactory.orchestrator.machine import spec_verdict
3160
+
3161
+ proj, bad = _project(project)
3162
+ if bad:
3163
+ return bad
3164
+ repo = getattr(getattr(proj, "tracker", None), "repo", "") or proj.name
3165
+ draft = parse_ticket_body(id="draft", title=(title or "").strip(), body=body or "", repo=repo)
3166
+ verdict = spec_verdict(draft)
3167
+ count = len(draft.acceptance_criteria)
3168
+ said = verdict or (f"pickup would take this card as written — {count} "
3169
+ f"{'criterion' if count == 1 else 'criteria'}")
3170
+ return done(said, project=proj.name, refusal=verdict,
3171
+ criteria=[c.text for c in draft.acceptance_criteria],
3172
+ fields=form_fields(body or ""))
3173
+
3174
+
3099
3175
  async def _card_create(*, project: str, title: str, by: Actor, body: str = "",
3100
3176
  column: str = "") -> Outcome:
3101
3177
  """Open a card on this project's board."""
@@ -3133,8 +3209,9 @@ async def _card_create(*, project: str, title: str, by: Actor, body: str = "",
3133
3209
  ref = await asyncio.to_thread(_open)
3134
3210
  except Exception as exc: # noqa: BLE001 — a board that refused is an outcome, not a traceback
3135
3211
  return refused(UNAVAILABLE, f"the card was not opened: {exc}")
3136
- return done(f"opened {ref} on {proj.name}'s board ({by})",
3137
- project=proj.name, issue=str(ref), url=tracker.ticket_url(ref))
3212
+ said, gate = _as_pickup_would(f"opened {ref} on {proj.name}'s board ({by})",
3213
+ await asyncio.to_thread(_pickup_says, tracker, ref))
3214
+ return done(said, project=proj.name, issue=str(ref), url=tracker.ticket_url(ref), **gate)
3138
3215
 
3139
3216
 
3140
3217
  async def _card_move(*, project: str, issue: str, column: str, by: Actor) -> Outcome:
@@ -3162,6 +3239,291 @@ async def _card_move(*, project: str, issue: str, column: str, by: Actor) -> Out
3162
3239
  project=proj.name, issue=str(issue), column=wanted)
3163
3240
 
3164
3241
 
3242
+ def _stage_refusal(proj, board, issue: str, *, act: str = "edit") -> str:
3243
+ """Why this card may not be edited (or closed), or `""` when it may (#150).
3244
+
3245
+ ONLY BEFORE THE FACTORY HAS IT. An agent works from the text it read at pickup, so an edit
3246
+ afterwards moves the target under it with nobody seeing — the card is the factory's from that
3247
+ moment, and a correction is a comment. `todo` and `backlog` are the operator's columns; the
3248
+ other four are where `set_state` put the card, which is the platform's own record that it was
3249
+ taken up (`adapters/board/columns.py`).
3250
+
3251
+ FIVE ANSWERS, AND ONLY ONE OF THEM IS SILENCE. A project with no board cannot have moved the
3252
+ card, an unreadable board cannot say, a card nobody put on the board has not moved, a column
3253
+ this platform does not map cannot be judged, and a column it does map answers outright. The
3254
+ two "cannot tell" cases REFUSE: letting an edit through on a card that may already be running
3255
+ is the failure this gate exists to prevent, and it is the direction it must not fail in.
3256
+
3257
+ CLOSING ASKS THE SAME QUESTION (review of #153). A card closed from the board while a job works
3258
+ on it leaves the board with the job still running and nothing telling it, and a reopen then
3259
+ puts it in Backlog as though nobody were on it. `act="close"` gets the same gate and says what
3260
+ to do instead: stop the job first."""
3261
+ from openfactory.adapters.board.columns import has_started, key_for
3262
+ from openfactory.contracts.refs import canonical_ref
3263
+
3264
+ if board is None:
3265
+ return "" # tickets only, no columns at all: nothing can have moved it
3266
+ where = board.columns()
3267
+ if where is None:
3268
+ return (f"{proj.name}'s board could not be read, so there is no way to tell whether the "
3269
+ f"factory has already taken {issue} up. Nothing was changed — try again, and if "
3270
+ f"the board stays unreadable, say it on the card instead.")
3271
+ column = where.get(canonical_ref(issue)) or where.get(str(issue))
3272
+ if not column:
3273
+ return "" # not on the board: nobody has moved it anywhere
3274
+ key = key_for(column, renamed=(getattr(proj.tracker, "options", None) or {}).get("columns"))
3275
+ if not key:
3276
+ return (f"{issue} is in {column!r}, which is not a column this platform maps, so it cannot "
3277
+ f"tell whether the factory has taken the card up. Map it with the project's "
3278
+ f"tracker option `columns`, or say what you wanted to change on the card itself.")
3279
+ if has_started(key) and act == "close":
3280
+ return (f"{issue} is in {column!r} — the factory has taken it up, and a job may be working "
3281
+ f"on it right now. Closing it would take it off the board with that job still "
3282
+ f"running and nothing telling it. Stop the job first (`stop`), or say it on the "
3283
+ f"card.")
3284
+ if has_started(key):
3285
+ return (f"{issue} is in {column!r} — the factory has taken it up, and it works from the "
3286
+ f"text it read at pickup. Editing it now would move the target with nobody "
3287
+ f"seeing. Say it on the card instead: a comment is what the factory reads next.")
3288
+ return ""
3289
+
3290
+
3291
+ #: How each kind of card the product role opens is named in a refusal, and where the change is
3292
+ #: asked for instead.
3293
+ _OPENED_FROM = {
3294
+ "requirement": ("from a requirement",
3295
+ " It changes the requirement first, and then realigns this card to it."),
3296
+ "request": ("from a request somebody made in the conversation",
3297
+ " It can correct the text there: “corrige o #N: …”."),
3298
+ "defect": ("from a reported defect", " It can correct the text there: “corrige o #N: …”."),
3299
+ }
3300
+
3301
+
3302
+ def _product_owned_refusal(tracker, issue: str, *, act: str) -> str:
3303
+ """Why this card may not be `act`-ed from the board, or `""` when it may (#150).
3304
+
3305
+ A CARD THE PRODUCT ROLE OPENED IS THE PRODUCT OWNER'S, in every column. Written on the board it
3306
+ is correctable until pickup (`_stage_refusal`); opened from a requirement, a request or a defect
3307
+ it is what somebody asked for, and only they change it — through the product role, which keeps
3308
+ the requirement and the card saying the same thing. Decided on #150 in place of "record the edit
3309
+ and ask again": the second yes accepts the requirement, so a yes on an edited card would accept
3310
+ a text no requirement holds.
3311
+
3312
+ A CARD THAT CANNOT BE READ REFUSES. Whether the product role opened it is the question, and
3313
+ letting the act through on a card that may be somebody's promise is the direction this must not
3314
+ fail in — the same choice `_stage_refusal` makes about a board it cannot read."""
3315
+ from openfactory.product.authoring import filed_by_the_product_role
3316
+
3317
+ try:
3318
+ ticket = tracker.get_ticket(issue)
3319
+ except Exception as exc: # noqa: BLE001 — an unreadable card is an answer, and it refuses
3320
+ log.warning("OPENFACTORY_CARD_OWNER_UNREAD card=%s: could not read the card to tell who "
3321
+ "opened it, so the board's %s is refused — %s", issue, act, exc)
3322
+ return (f"{issue} could not be read, so there is no way to tell whether the product role "
3323
+ f"opened it — and a card it opened only the product owner may change. Nothing was "
3324
+ f"changed; try again.")
3325
+ kind = filed_by_the_product_role(getattr(ticket, "raw", "") or "")
3326
+ if not kind:
3327
+ return ""
3328
+ source, then = _OPENED_FROM[kind]
3329
+ return (f"{issue} was opened by the product role {source}, so only the product owner {act} it. "
3330
+ f"Nothing was changed. Ask for it in the conversation with the product role.{then}")
3331
+
3332
+
3333
+ async def _card_edit(*, project: str, issue: str, by: Actor, title: str = "",
3334
+ body: str = "") -> Outcome:
3335
+ """Correct a card's title or description, before the factory has taken it up."""
3336
+ import asyncio
3337
+
3338
+ proj, tracker, board, bad = _board_pair(project)
3339
+ if bad:
3340
+ return bad
3341
+ wanted_title, wanted_body = (title or "").strip(), (body or "").strip()
3342
+ if not wanted_title and not wanted_body:
3343
+ return refused(INVALID, "say what to change — an edit carrying neither a title nor a "
3344
+ "description changes nothing.")
3345
+
3346
+ # ASKED OF THE ROW, NOT OF THE PORT (#150). Renaming is not something every tracker can be
3347
+ # made to do, and putting it on `TrackerAdapter` would fail every adapter a stranger already
3348
+ # shipped — measured: it also made `check_tracker` report the missing method INSTEAD of the
3349
+ # read-side findings it exists for. `say` on this axis is reached the same way.
3350
+ # WHOSE CARD AND WHICH STAGE FIRST, then whether this tracker can rename: a card the product
3351
+ # role opened is refused for that, which is the reason a person can act on, rather than for a
3352
+ # capability that would not have mattered (review of #153).
3353
+ refusal = (await asyncio.to_thread(_product_owned_refusal, tracker, issue, act="changes")
3354
+ or await asyncio.to_thread(lambda: _stage_refusal(proj, board, issue)))
3355
+ if refusal:
3356
+ return refused(CONFLICT, refusal)
3357
+
3358
+ rename = getattr(tracker, "update_title", None)
3359
+ if wanted_title and rename is None:
3360
+ return refused(INVALID,
3361
+ f"{proj.name}'s tracker cannot rename a card from here — it can only change "
3362
+ f"the description. Rename it in the tracker's own screen, or send this "
3363
+ f"edit without a title.")
3364
+
3365
+ def _write() -> tuple[list[str], str, bool]:
3366
+ """`(what changed, why the rest did not, whether the note recording it was left)`.
3367
+
3368
+ EACH WRITE IS ITS OWN OUTCOME (review of #153). The rename, the body and the note are
3369
+ separate tracker calls — two `gh` invocations on GitHub — and one `except` around all three
3370
+ answered "nothing was changed" over a card already renamed, with no note saying so; a retry
3371
+ then found the title matching and the rename was never recorded at all. What landed is
3372
+ reported and noted whatever failed after it."""
3373
+ from openfactory.adapters.tracker.parse import changed_sections
3374
+ from openfactory.product.voice import card_edit_note
3375
+
3376
+ # ONLY WHAT MOVED IS WRITTEN, AND NAMED (#150). The panel's form sends the title and the
3377
+ # whole body on every save, so writing what was sent recorded every save as a rewrite of
3378
+ # both. The card as it stands is read first; a title that is the same is not renamed, a
3379
+ # body that says the same thing is not rewritten, and the note lists the sections that did
3380
+ # change. A save that changes nothing writes nothing and leaves no note.
3381
+ current = tracker.get_ticket(issue)
3382
+ changed: list[str] = []
3383
+ failure = ""
3384
+ try:
3385
+ if wanted_title and wanted_title != (current.title or "").strip():
3386
+ rename(issue, wanted_title)
3387
+ changed.append("title")
3388
+ sections = changed_sections(getattr(current, "raw", "") or "", wanted_body) \
3389
+ if wanted_body else []
3390
+ if sections:
3391
+ tracker.update_body(issue, wanted_body)
3392
+ changed += sections
3393
+ except Exception as exc: # noqa: BLE001 — what landed before it is still reported
3394
+ failure = str(exc) or type(exc).__name__
3395
+ if not changed:
3396
+ return changed, failure, False
3397
+ # EVERY EDIT LEAVES A RECORD, in the platform's own voice rather than the person's: this
3398
+ # is a note ABOUT what somebody did, not something they said, and the tech-lead reads the
3399
+ # thread. `update_body` is a separate port method precisely so that rewriting somebody
3400
+ # else's text is visible, and a silent rewrite would take that back.
3401
+ #
3402
+ # AND IN THE PROJECT'S LANGUAGE, asked of the catalogue rather than written here (#160):
3403
+ # a sentence composed at a call site is how an English-configured client received
3404
+ # Portuguese and a Portuguese-configured one received English, from code sitting beside a
3405
+ # working per-language catalogue.
3406
+ try:
3407
+ tracker.comment(issue, card_edit_note(who=str(by), parts=changed,
3408
+ language=getattr(proj, "language", None)))
3409
+ except Exception as exc: # noqa: BLE001 — the edit landed; only its record did not
3410
+ log.warning("OPENFACTORY_CARD_EDIT_UNNOTED card=%s: %s changed and the note recording "
3411
+ "it could not be left — %s", issue, ",".join(changed), exc)
3412
+ return changed, failure, False
3413
+ return changed, failure, True
3414
+
3415
+ from openfactory.product.voice import card_edit_parts
3416
+
3417
+ try:
3418
+ changed, failure, noted = await asyncio.to_thread(_write)
3419
+ except Exception as exc: # noqa: BLE001 — the card could not be read, so nothing was written
3420
+ return refused(UNAVAILABLE, f"nothing was changed on {issue}: {exc}")
3421
+ what = card_edit_parts(changed, language="en")
3422
+ if failure and not changed:
3423
+ return refused(UNAVAILABLE, f"nothing was changed on {issue}: {failure}")
3424
+ if failure:
3425
+ record = ("the thread records what did" if noted
3426
+ else "and nothing on the thread records it")
3427
+ return refused(UNAVAILABLE, f"only {what} of {issue} changed — the rest of the edit did "
3428
+ f"not: {failure}. {record[0].upper()}{record[1:]}.",
3429
+ project=proj.name, issue=str(issue), changed=",".join(changed))
3430
+ unnoted = "" if noted else ", but the note recording it could not be left on the card"
3431
+ line = (f"edited {what} of {issue} ({by}){unnoted}" if changed else
3432
+ f"nothing to change on {issue} — its title and description already say what was sent "
3433
+ f"({by})")
3434
+ said, gate = _as_pickup_would(line, await asyncio.to_thread(_pickup_says, tracker, issue))
3435
+ return done(said, # the operator's line
3436
+ project=proj.name, issue=str(issue), changed=",".join(changed), **gate)
3437
+
3438
+
3439
+ async def _card_close(*, project: str, issue: str, by: Actor, reason: str = "") -> Outcome:
3440
+ """Take a card off the board, with a reason — the operator's own 'this should not be here'."""
3441
+ import asyncio
3442
+
3443
+ proj, tracker, board, bad = _board_pair(project)
3444
+ if bad:
3445
+ return bad
3446
+ # A REASON OF ONLY SPACES IS NO REASON. `required` makes `perform` refuse a reason that is
3447
+ # missing or exactly `""` — equality, not emptiness — so `" "` reached this row and closed the
3448
+ # card with a note ending in a space. This check was once removed as dead, on a surviving
3449
+ # mutation row; the row survived because nothing drove a whitespace-only reason (review of
3450
+ # #153), which is a weak guard, not dead code.
3451
+ said = (reason or "").strip()
3452
+ if not said:
3453
+ return refused(INVALID, "say why the card is being closed — the reason is what the next "
3454
+ "reader of the card has, and one made only of spaces says nothing.")
3455
+ refusal = (await asyncio.to_thread(_product_owned_refusal, tracker, issue, act="closes")
3456
+ or await asyncio.to_thread(lambda: _stage_refusal(proj, board, issue, act="close")))
3457
+ if refusal:
3458
+ return refused(CONFLICT, refusal)
3459
+
3460
+ def _close() -> None:
3461
+ from openfactory.product.voice import card_close_note
3462
+
3463
+ # NOT DELIVERED, AND THAT IS THE WHOLE POINT OF THE ARGUMENT. An operator closing a card
3464
+ # from the board means "this should not be on my board", never "this shipped" — and
3465
+ # `triage.Ticket.delivered` reads exactly this word. Eleven cards closed as duplicates
3466
+ # once came back downstream as completed work.
3467
+ note = card_close_note(who=str(by), reason=said,
3468
+ language=getattr(proj, "language", None))
3469
+ try:
3470
+ tracker.close_ticket(issue, note, delivered=False)
3471
+ except TypeError:
3472
+ # A row whose `close_ticket` takes only the port's two arguments (jira) is called with
3473
+ # exactly those, as `_card_create` does for `create_ticket`.
3474
+ tracker.close_ticket(issue, note)
3475
+
3476
+ try:
3477
+ await asyncio.to_thread(_close)
3478
+ except Exception as exc: # noqa: BLE001 — see `_card_create`
3479
+ return refused(UNAVAILABLE, f"{issue} is still open: {exc}")
3480
+ return done(f"closed {issue} ({by}) — it is off the board, not deleted, and its thread is "
3481
+ f"intact", project=proj.name, issue=str(issue))
3482
+
3483
+
3484
+ async def _card_reopen(*, project: str, issue: str, by: Actor) -> Outcome:
3485
+ """Put a closed card back — the undo a close on the only surface an operator has must have."""
3486
+ import asyncio
3487
+
3488
+ proj, tracker, _board, bad = _board_pair(project)
3489
+ if bad:
3490
+ return bad
3491
+ owned = await asyncio.to_thread(_product_owned_refusal, tracker, issue, act="reopens")
3492
+ if owned:
3493
+ return refused(CONFLICT, owned)
3494
+
3495
+ from openfactory.product.voice import card_reopen_note
3496
+
3497
+ # NAMED, NOT `getattr`-ed, AND ONLY THE LOOKUP IS GUARDED. `reopen_ticket` is not on the tracker
3498
+ # port (#150: putting it there made `check_tracker` report the missing method instead of the
3499
+ # read-side findings it exists for), but `test_the_action_layer.py` asks every action row for a
3500
+ # marker the catalog USES — and a string inside `getattr` is not a name. The `AttributeError`
3501
+ # branch once wrapped the reopen AND its note, so an `AttributeError` from the note described a
3502
+ # card that had been reopened as one this tracker cannot reopen (review of #153).
3503
+ try:
3504
+ reopen = tracker.reopen_ticket
3505
+ except AttributeError:
3506
+ return refused(INVALID,
3507
+ f"{proj.name}'s tracker cannot reopen a card from here. Reopen it in the "
3508
+ f"tracker's own screen — the card, its thread and its number are all still "
3509
+ f"there, because closing never deleted anything.")
3510
+ try:
3511
+ await asyncio.to_thread(reopen, issue)
3512
+ except Exception as exc: # noqa: BLE001 — see `_card_create`
3513
+ return refused(UNAVAILABLE, f"{issue} is still closed: {exc}")
3514
+ reopened = f"reopened {issue} ({by}) — it is back on the board for somebody to pick up"
3515
+ # TWO WRITES, TWO OUTCOMES: the card IS open, whatever happens to the note that says who did it.
3516
+ try:
3517
+ await asyncio.to_thread(tracker.comment, issue, card_reopen_note(
3518
+ who=str(by), language=getattr(proj, "language", None)))
3519
+ except Exception as exc: # noqa: BLE001 — the reopen landed; only its record did not
3520
+ log.warning("OPENFACTORY_CARD_REOPEN_UNNOTED card=%s: reopened, and the note saying who "
3521
+ "reopened it could not be left — %s", issue, exc)
3522
+ return done(f"{reopened}, but the note saying who reopened it could not be left on the "
3523
+ f"card", project=proj.name, issue=str(issue))
3524
+ return done(reopened, project=proj.name, issue=str(issue))
3525
+
3526
+
3165
3527
  async def _pr_merge(*, project: str, pr: str, by: Actor) -> Outcome:
3166
3528
  """Land a pull request NO DURABLE JOB IS WAITING ON — the local forge's own fast-forward.
3167
3529
 
@@ -3202,7 +3564,12 @@ async def _pr_merge(*, project: str, pr: str, by: Actor) -> Outcome:
3202
3564
  forge.merge_pr(pr=pr)
3203
3565
  except Exception as exc: # noqa: BLE001 — the refusal IS the answer, in git's own words
3204
3566
  return refused(CONFLICT, str(exc)[:2000])
3205
- return done(f"{pr} merged — the base moved, and nothing else was touched.",
3567
+ # WHAT THE SENTENCE CLAIMS IS WHAT IS TRUE ON EVERY REPOSITORY. It said "nothing else was
3568
+ # touched" until #142: on the installation's bare context repository a proposal whose base moved
3569
+ # is rebased before the fast-forward, so its branch WAS touched, and `merge_pr` returns nothing
3570
+ # this line could tell the two cases apart by.
3571
+ return done(f"{pr} merged — its commits are on the base now, fast-forwarded, "
3572
+ "with no merge commit.",
3206
3573
  project=found.name, pr=pr, by=str(by))
3207
3574
 
3208
3575
 
@@ -4402,9 +4769,18 @@ async def _people_invite(*, person: str, by: Actor, display: str = "",
4402
4769
  if why:
4403
4770
  return refused(UNAVAILABLE, why)
4404
4771
  scoped = str(product or "").strip().lower() in ("1", "true", "yes", "y", "product")
4405
- got = _people.PeopleStore().invite(str(person or ""), display=str(display or ""),
4406
- groups=(PRODUCT_GROUP,) if scoped else (),
4407
- by=(by.id or "").strip())
4772
+ from openfactory.observability.query import StoreUnreadable
4773
+
4774
+ try:
4775
+ got = _people.PeopleStore().invite(str(person or ""), display=str(display or ""),
4776
+ groups=(PRODUCT_GROUP,) if scoped else (),
4777
+ by=(by.id or "").strip())
4778
+ except StoreUnreadable as exc:
4779
+ # AN INVITATION IS CHECKED AGAINST WHO IS ALREADY REGISTERED, and a store that cannot be
4780
+ # read cannot say: the old empty answer minted a second link for somebody who already
4781
+ # has an account. UNAVAILABLE and not INVALID — nothing about the request is wrong.
4782
+ return refused(UNAVAILABLE, f"the people store cannot be read, so no invitation was "
4783
+ f"issued — {exc}")
4408
4784
  if isinstance(got, str):
4409
4785
  return refused(INVALID, got)
4410
4786
  token, invitation = got
@@ -4470,6 +4846,43 @@ CATALOG: dict[str, ActionSpec] = {
4470
4846
  run=_card_comment,
4471
4847
  required=("project", "issue", "message"),
4472
4848
  ),
4849
+ ActionSpec(
4850
+ name="card_check",
4851
+ summary="say what pickup would make of a card before it is saved — writes nothing",
4852
+ run=_card_check,
4853
+ required=("project",),
4854
+ optional=("title", "body"),
4855
+ choose_when="while a card is being written or corrected: it runs the same spec gate a "
4856
+ "job runs at pickup, on the draft, so a refusal arrives while the card can "
4857
+ "still be fixed rather than after somebody queued it",
4858
+ ),
4859
+ ActionSpec(
4860
+ name="card_edit",
4861
+ summary="correct a card's title or description, before the factory takes it up",
4862
+ run=_card_edit,
4863
+ required=("project", "issue"),
4864
+ optional=("title", "body"),
4865
+ choose_when="when a card is wrong and no job has it yet — the spec gate refused it, or "
4866
+ "the person who filed it left something out. Once the factory has taken "
4867
+ "the card up this refuses, and a comment is the way to reach the job",
4868
+ ),
4869
+ ActionSpec(
4870
+ name="card_close",
4871
+ summary="take a card off the board with a reason — closed, never deleted",
4872
+ run=_card_close,
4873
+ required=("project", "issue", "reason"),
4874
+ choose_when="when a card should not be on the board at all: a duplicate, a request "
4875
+ "withdrawn, a plan abandoned. It records that the work did NOT happen, so "
4876
+ "nothing downstream reads it as delivered",
4877
+ ),
4878
+ ActionSpec(
4879
+ name="card_reopen",
4880
+ summary="put a closed card back on the board",
4881
+ run=_card_reopen,
4882
+ required=("project", "issue"),
4883
+ choose_when="when a card was closed by mistake, or the thing came back. Nothing is "
4884
+ "deleted here, so the card, its thread and its number are all still there",
4885
+ ),
4473
4886
  ActionSpec(
4474
4887
  name="pr_merge",
4475
4888
  summary="land a pull request no job is waiting on — this forge's own fast-forward",
@@ -4725,6 +5138,18 @@ CATALOG: dict[str, ActionSpec] = {
4725
5138
  required=("project", "number"),
4726
5139
  optional=("in_favour_of", "reason", "yes"),
4727
5140
  ),
5141
+ ActionSpec(
5142
+ name="product_correct_card",
5143
+ scope=PRODUCT,
5144
+ summary="correct the text or title of a card the product role opened from a request "
5145
+ "or a defect",
5146
+ run=_product_correct_card,
5147
+ required=("project", "number"),
5148
+ optional=("text", "title", "yes"),
5149
+ choose_when="when a card the product role opened from a request or a defect says the "
5150
+ "wrong thing and the factory has not taken it up — the board refuses to "
5151
+ "edit those cards, and this is the product owner's way to change one",
5152
+ ),
4728
5153
  ActionSpec(
4729
5154
  name="product_align_card",
4730
5155
  scope=PRODUCT,