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.
- {openfactory-0.2.0/openfactory.egg-info → openfactory-0.2.1}/PKG-INFO +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/README.md +2 -2
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/__init__.py +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/base.py +2 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/catalog.py +431 -6
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/base.py +234 -15
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/claude_code.py +37 -10
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/codex.py +34 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/kimi.py +22 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/opencode.py +21 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/roles.py +6 -2
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/techlead.py +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/columns.py +46 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_db.py +56 -3
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/credential/registry.py +29 -3
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/azure_devops.py +159 -17
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/base.py +18 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/github.py +75 -22
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/local.py +223 -65
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/claude_code.py +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/harness.py +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/base.py +11 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/worktree.py +59 -2
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/azure_devops.py +27 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/github.py +26 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/jira.py +11 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/local.py +29 -0
- openfactory-0.2.1/openfactory/adapters/tracker/parse.py +437 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/after_merge.py +30 -0
- openfactory-0.2.1/openfactory/agent_answer.py +199 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/app.py +266 -73
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/metrics_view.py +24 -4
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/panel.html +366 -24
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/box_prove.py +350 -15
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/cli.py +372 -40
- openfactory-0.2.1/openfactory/contracts/checks.py +254 -0
- openfactory-0.2.1/openfactory/contracts/item_space.py +86 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/ticket.py +10 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/doctor.py +60 -24
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/factory.py +70 -3
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/floor/reading.py +124 -10
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/base.py +14 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/local.py +70 -4
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/people.py +48 -15
- openfactory-0.2.1/openfactory/observability/job_record.py +96 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/query.py +12 -3
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/registry.py +8 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/sqlite_metrics.py +44 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/spend.py +9 -15
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/machine.py +172 -22
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/engineering.md +17 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/authoring.py +47 -5
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/brownfield.py +23 -3
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/case.py +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/channel.py +22 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/confirm.py +20 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/intents.py +26 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/module.py +154 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/queue.py +1 -9
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/staging.py +4 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/triage.py +12 -16
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/voice.py +258 -1
- openfactory-0.2.1/openfactory/runtime/host.py +299 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/activities.py +87 -28
- openfactory-0.2.1/openfactory/runtime/temporal/connection.py +163 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/schedule.py +33 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/view.py +294 -4
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/worker.py +2 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/workflow.py +84 -9
- {openfactory-0.2.0 → openfactory-0.2.1/openfactory.egg-info}/PKG-INFO +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/SOURCES.txt +31 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/pyproject.toml +1 -1
- openfactory-0.2.1/tests/test_a_card_is_named_in_the_forge_only_where_the_forge_owns_it.py +303 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_credential_is_scoped.py +1 -1
- openfactory-0.2.1/tests/test_a_failing_check_is_read_for_what_it_is.py +780 -0
- openfactory-0.2.1/tests/test_a_forge_that_brings_its_own_credential_is_not_reported_as_having_none.py +262 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_frame_does_not_erase_what_it_omits.py +176 -18
- openfactory-0.2.1/tests/test_a_fresh_install_reads_an_empty_store.py +93 -0
- openfactory-0.2.1/tests/test_a_job_branches_from_the_base_the_forge_holds.py +341 -0
- openfactory-0.2.1/tests/test_a_manifest_can_be_proven_before_it_is_merged.py +369 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_person_is_invited_not_signed_up.py +9 -4
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_poller_that_is_on_is_not_a_poller_that_ticks.py +15 -4
- openfactory-0.2.1/tests/test_a_promotion_the_box_cannot_run_is_refused_before_the_first_card.py +168 -0
- openfactory-0.2.1/tests/test_a_proposal_whose_base_moved_still_lands_on_the_local_forge.py +303 -0
- openfactory-0.2.1/tests/test_a_pull_request_is_read_and_merged_in_its_own_repository.py +374 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_resume_re_enters_the_merge.py +5 -3
- openfactory-0.2.1/tests/test_a_scenario_is_one_criterion.py +296 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_ticket_written_in_portuguese.py +1 -0
- openfactory-0.2.1/tests/test_a_trust_file_nobody_reads_is_a_green_proof.py +192 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_box_prove.py +100 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_brownfield_wired.py +3 -2
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_card_maintenance.py +209 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_card_maintenance_channel.py +74 -4
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_deploy_watch_workflow.py +5 -3
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_generates_the_deployment.py +53 -6
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_gesture_exists_in_one_language_only.py +8 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_unbounded_growth.py +48 -6
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_brownfield.py +38 -0
- openfactory-0.2.1/tests/test_product_init_writes_a_file_its_own_check_accepts.py +219 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_simulation.py +2 -2
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_triage.py +48 -17
- openfactory-0.2.1/tests/test_provenance_in_the_brief.py +381 -0
- openfactory-0.2.1/tests/test_something_finally_asks_the_agent.py +530 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_temporal_workflow.py +12 -6
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_action_layer.py +15 -0
- openfactory-0.2.1/tests/test_the_attended_driver_records_what_it_spent.py +124 -0
- openfactory-0.2.1/tests/test_the_board_is_a_page_on_the_panel.py +872 -0
- openfactory-0.2.1/tests/test_the_board_keeps_your_place.py +228 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_button_without_a_gate.py +4 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_cli_runs_in_the_deployments_box.py +30 -7
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_cloud_is_a_directory_delete.py +15 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_docs_do_not_drift.py +88 -0
- openfactory-0.2.1/tests/test_the_engine_answers_or_says_so.py +211 -0
- openfactory-0.2.1/tests/test_the_engine_starts_only_beside_the_library_its_worker_runs_on.py +184 -0
- openfactory-0.2.1/tests/test_the_extra_ca_file_is_always_a_valid_pem.py +182 -0
- openfactory-0.2.1/tests/test_the_floor_is_not_re_read_on_every_frame.py +506 -0
- openfactory-0.2.1/tests/test_the_gate_holds_when_the_store_cannot_be_read.py +527 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_builds_the_commands_it_says_it_does.py +260 -6
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_merge_is_the_end_of_the_road.py +5 -3
- openfactory-0.2.1/tests/test_the_panel_holds_one_engine_client.py +499 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_whether_work_is_picked_up.py +13 -2
- openfactory-0.2.1/tests/test_the_poller_has_a_lever.py +219 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_role_lives_outside_slack.py +108 -2
- openfactory-0.2.1/tests/test_the_proof_on_the_host.py +471 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_pull_request_lives_in_the_file.py +1 -1
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_queue_proposes_in_batches.py +1 -1
- openfactory-0.2.1/tests/test_the_refusal_reads_the_file_it_would_write.py +126 -0
- openfactory-0.2.1/tests/test_the_set_ends_together.py +282 -0
- openfactory-0.2.1/tests/test_the_shell_we_ship_runs_on_both_seds.py +76 -0
- openfactory-0.2.1/tests/test_the_suite_runs_where_a_maintainer_sits.py +204 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_trajectory_reaches_the_record.py +12 -6
- openfactory-0.2.0/openfactory/adapters/tracker/parse.py +0 -231
- openfactory-0.2.0/openfactory/runtime/host.py +0 -91
- openfactory-0.2.0/openfactory/runtime/temporal/connection.py +0 -89
- openfactory-0.2.0/tests/test_the_board_is_a_page_on_the_panel.py +0 -293
- openfactory-0.2.0/tests/test_the_proof_on_the_host.py +0 -223
- {openfactory-0.2.0 → openfactory-0.2.1}/LICENSE +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/NOTICE +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/actions/floor_intents.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/routes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/session_store.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/stream.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/agent/token_pool.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/azure_devops.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/azure_devops.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/base.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/factory.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/jira.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board/local.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/base.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/local.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/board_setup/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/base.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/panel.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/channel/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/credential/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/azure_pipelines.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/base.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/github_actions.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/none.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/environment/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/forge/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/github_app.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/base.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/panel.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/notify/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/reviewer/base.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/container.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/sandbox/timeouts.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/base.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/github_board_setup.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/github_project.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/adapters/tracker/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/api/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/approvals.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/cli_refusals.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/conformance/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/conformance/adapters.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/bot.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/commands.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/decision.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/manifest.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/product.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/profile.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/project.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/refs.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/review.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/run.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/contracts/state.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/credentials.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/doors.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/environ.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/floor/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/floor/ladder.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/oidc.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/identity/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/bundle.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/check.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/contracts.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/experiment.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/gaps.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/gate.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/gather.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/generator.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/inventory.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/okf.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/pipeline.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/render.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/service.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/knowledge/staleness.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/language/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/language/assent.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/loader.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/ledger.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/messages.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/recall.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/store.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/memory/transcript.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/namespace.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/events.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/metrics.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/observability/trajectory.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/concepts.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/context.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/cover.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/deployment.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/firstrun.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/history.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/infer.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/live_ci.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/onboard.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/propose_manifest.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/questions.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/readiness.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/onboarding/renew.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/ops/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/ops/impediment.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/context.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/errors.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/merge_policy.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/promotion.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/risk.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/orchestrator/validation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/floor.yaml +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/profiles/prototype.yaml +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/profiles/regulated.yaml +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/requirements/0000-template.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/coordinator.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/executor.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/planner.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/product.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/recovery.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/sizer.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/roles/techlead.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/org_defaults/tdd.md +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/own_work.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/paths.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/plugins.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/authz.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/census.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/conformance.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/floor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/presets.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/profiles.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/protected.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/policy/test_work.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/preflight.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/node.yaml +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/python.yaml +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/security-oss.yaml +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/presets/terraform.yaml +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/asked.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/board.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/config.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/conversation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/corpus.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/domain.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/facts.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/followup.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/loader.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/needs_action.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/onboard.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/reading.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/release.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/requester.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/role.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/product/workspace.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/review/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/review/verdict.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/boxed_job.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/card_repo.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/repo_cache.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/io.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/poller.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/temporal/starter.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/runtime/toolbox.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/scheduler.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/semver.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/classify.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/conversation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/diagnosis.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/memory.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/pack.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/voice.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/techlead/watch.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/testing/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/testing/local_flow.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/__init__.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/bounded.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/causes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory/util/scratch.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/dependency_links.txt +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/entry_points.txt +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/requires.txt +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/openfactory.egg-info/top_level.txt +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/setup.cfg +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_banner_line_does_not_kill_the_review.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_budget_nothing_spends_is_not_a_defect.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_card_says_who_asked_for_it.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_case_does_not_outlive_its_test.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_concept_is_earned_not_declared.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_context_repository_is_creatable_on_azure_too.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_count_is_a_number_not_a_sentence.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_credential_is_asked_of_its_own_axis.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_credential_only_goes_to_its_own_host.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_decision_is_written_down.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_declared_environment_is_observed_or_named.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_delivered_ticket_is_not_rerun.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_disabled_project_does_not_look_armed.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_double_without_intake_answers_every_turn.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_failed_board_read_is_not_a_wrong_root.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_failing_setup_is_not_silent.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_file_nothing_describes_is_the_least_safe_to_change.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_fingerprint_gains_a_reader.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_free_deployment_can_read_its_logs.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_free_deployment_resumes_its_own_sessions.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_fresh_install_registers_no_projects.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_gate_is_not_a_stall.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_gate_that_could_not_run.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_gate_that_holds_says_so_where_the_person_decides.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_green_deploy_asks_somebody_to_look.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_green_environment_is_green_for_THIS_change.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_hostile_value_stays_data.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_human_gate_is_not_a_review.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_jira_project_is_a_board.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_killed_mutation_run_cannot_hide.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_local_deployment_names_no_vendor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_manifest_that_loads_is_not_a_manifest_that_is_right.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_message_never_claims_an_action_nobody_takes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_message_only_teaches_a_verb_the_channel_parses.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_model_nobody_can_run_is_not_a_setting.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_named_check_is_not_dropped.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_negation_the_bridge_carries_is_refused.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_pass_in_flight_is_not_a_gate.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_pass_that_changed_nothing.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_path_nothing_declares_is_not_the_freest.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_path_registers_itself.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_paused_factory_says_so.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_personal_accounts_board_is_readable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_personal_boards_columns_can_be_read.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_preexisting_context_repo_is_declarable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_probe_set_is_pinned_whole.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_project_can_change_the_language_it_speaks_first.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_project_declares_what_it_is.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_project_is_not_registered_as_the_wrong_vendor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_proof_that_was_not_recorded_says_so.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_proposal_can_carry_what_to_change.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_proposed_command_can_run_where_the_box_runs_it.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_pull_that_changes_nothing_is_impossible_to_miss.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_question_reaches_the_person_who_asked.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_range_is_a_citation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_rebuild_is_not_a_change.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_ref_that_is_not_a_number.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_rejected_review_can_be_asked_again.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_remedy_names_a_document_that_exists.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_repaired_card_is_announced_on_any_tracker.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_repository_on_the_former_name_is_refused_at_every_door.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_retired_pipeline_is_not_a_gate.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_review_belongs_to_the_code_it_read.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_scratch_directory_is_the_only_thing_we_delete.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_skip_is_something_that_happened.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_staged_decision_survives_a_refresh.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_stranger_can_add_a_role.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_stranger_can_add_an_adapter.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_suite_that_stopped_collecting_is_not_a_green_suite.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_ticket_ref_is_never_typed_as_a_number.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_typed_instruction_reaches_the_same_row_as_the_button.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_vendor_error_code_is_not_a_status.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_verb_says_what_to_put_in_it.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_wait_can_say_until_when.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_wedged_job_has_an_exit.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_wedged_job_is_seen.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_write_that_did_not_land_is_not_counted.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_a_yes_is_an_answer.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_accept_flows_end_to_end.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_acceptance_loop.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_accepting_starts_the_work.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_acknowledgement.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_adapter_conformance_suite.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_advisory_findings_survive_save.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_harness.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_resume_c2.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_roles.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_stream.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_agent_token_pool.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_already_asked_is_answered_from_the_board_not_the_transcript.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_advisory_gate_cannot_hold_a_pickup.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_advisory_gate_reports_and_never_blocks.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_environment_written_before_this_change_still_resolves_the_old_path.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_intake_is_a_typed_case.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_open_question_can_be_retired.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_open_question_is_an_offer_not_a_toll.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_unread_review_is_not_a_rejection.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_an_unreadable_store_is_not_an_empty_one.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_api.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_board_is_vendor_neutral.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_board_read_cost.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_bot_push.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_box_carries_the_provider_kind.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_breakdown_reuses_what_exists.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_c18_has_an_azure_expression.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_c18_reaches_every_entry_point.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_causes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_ci_runs_what_we_run.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_client_board_is_not_a_test_bench.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_commit_workflow_strip.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_compose_forwards_the_whole_env_file.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_confirmation_by_click.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_confirmation_is_understood.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_context.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_conversation_worlds.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_corpus_health_and_board_placement.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_create_repository_survives_an_app_token.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_decision.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_decision_loop.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_diff_hygiene.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_doctor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_doctor_checks_the_product_link.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_env_apply_answers_in_the_readers_language.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_env_apply_takes_a_path_like_env_read.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_env_proposes_before_it_verifies.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_event_sinks.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_every_FROM_the_release_builds_resolves_without_the_local_daemon.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_every_image_the_compose_file_names_is_one_the_release_builds.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_every_mutation_plan_can_run.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_factory_asks_for_help.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_filed_work_is_reachable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_first_real_conversation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_forge_ci.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_forge_deploy.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_forge_idempotency.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_foundation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_gh_is_authenticated.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_handoff.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_asks_for_one_machine.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_speaks_to_the_operator_not_to_us.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_init_that_acts.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_internal_imports_resolve.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_judge_verdict_parsing.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_kernel_names_no_vendor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_build_reaches_a_factory_cloned_project.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_derived_not_cached.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_experiment.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_knowledge_pipeline.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_local_flow.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_loops_are_reachable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_manifest_version.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_memory_ledger.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_memory_recall_eval.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_memory_store.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_merge_policy.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_metrics.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_card_id_reaches_a_stranger.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_checkout_assumes_a_branch_name.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_module_is_ever_called_like_a_function.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_plumbing_reaches_the_client.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_no_silent_failures.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_non_numeric_refs_reach_the_api.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_nothing_speaks_before_it_asks_the_language.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_observability_registries.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboard_builds_the_context_too.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboard_proposes_a_measured_setup.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_a_context_that_already_exists.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_backfills_the_context.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_creates_the_context_repository.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_onboarding_firstrun.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_one_process_one_installation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_one_question_decides_where_the_factory_runs.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_opencode_harness.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_orphaned_cards_are_repointed.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_panel_approval_store.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_panel_reads_events_without_aws.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_park_alert.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_paths_are_traversal_safe.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_permission_is_an_order_in_every_language.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_ports_are_serialisable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight_json_is_the_document_the_agent_lane_reads.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight_names_a_remedy_for_every_thing_it_refuses.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_preflight_says_which_machine_it_measured.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_authoring.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_board_reads.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_channel.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_config.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_corpus.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_followup.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_followup_e2e.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_intents.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_loader.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_module.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_needs_action.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_role.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_sees_the_code.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_sweep.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_view_is_stable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_voice.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_product_workspace.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_project_add_exposes_language.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_project_add_speaks_azure_devops.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_promotion.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_provider_seams.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_public_core_names_no_forge.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_recent_runs_outlives_the_engine.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_registry_is_runtime_state.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_repo_cache_credentials.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_repo_cache_lifecycle.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_requirement_can_be_dropped.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_requirement_lifecycle.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sandbox_registry.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_scheduler.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_schedules_are_reachable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_somebody_is_asked_to_look_at_what_shipped.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sqlite_metrics.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sso_is_a_row_the_core_ships.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sweep_client_surface.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_sweep_records_only_what_posted.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_classify.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_memory.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_self_heal.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_techlead_watch.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_telemetry_reads_from_the_configured_sink.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_temporal_call_arity.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_temporal_view.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_acceptance_map_reaches_a_person.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_add_on_packages_install.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_board.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_coordinates_have_one_home.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_credential_outlives_one_token.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_forge.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_pipelines.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ado_tracker.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_alert_names_its_ticket_on_any_tracker.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_announcement_carries_the_verdict.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_app_key_reaches_every_entry_point.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_app_permission_table_has_one_home.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_areas_that_change_are_the_ones_that_matter.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfill_is_metered.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfill_names_the_real_reason.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfill_reads_every_source.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_backfills_questions_can_be_finished.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_base_branch_is_asked_not_assumed.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_bench_is_not_the_case.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_lives_in_one_file.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_names_its_own_pickup_column.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_port_takes_any_providers_ref.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_board_says_where_it_lives.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_can_obtain_the_repository.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_can_reach_the_pr_branch.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_claims_only_what_it_does.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_clones_from_the_forge_it_was_told.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_does_not_run_the_clients_shell_profile.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_env_is_a_named_seam.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_image_resolves_in_one_place.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_image_the_worker_spawns_is_one_the_install_pulls.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_program_can_be_launched.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_proof_is_watchable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_says_where_the_harness_is.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_box_transmits_while_it_runs.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_boxed_job_entrypoint.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_branch_carries_the_products_name.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_card_carries_ONE_acceptance_section.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_card_moves_even_when_the_link_cannot_be_built.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_card_says_what_the_floor_says.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_chat_has_its_own_fences.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_chat_is_a_directory_delete.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_chat_thread_survives_a_refresh.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ci_is_read_more_than_once.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_cli_refuses_in_sentences.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_client_never_reads_an_emoji.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_client_releases_production.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_columns_and_the_identity_are_the_platforms.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_compose_file_can_be_installed_and_built_from_one_file.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_confirmation_executor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_context_proposal_is_reachable.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_context_repository_can_be_created.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_context_repository_exists_on_this_runtime.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_addon_ledger.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_boundary.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_does_not_need_a_cloud.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_core_is_a_core.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_credential_scan_reads_a_delimiter.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_default_language_is_the_products.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_deployment_asks_the_rows_for_their_variables.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_diff_decides_scope_and_what_validates.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_distribution_ships_what_it_tells_you_to_copy.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_docs_name_no_vendor_as_the_core.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_docs_say_which_promise.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doctor_says_where_project_less_speech_goes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doctor_survives_its_own_probes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doctor_tells_the_truth_about_this_machine.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documentation_has_one_front_door.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documented_default_is_the_real_one.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documents_describe_the_product_this_tree_is.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_documents_name_the_repository_and_keep_their_accents.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_doors_derive_from_the_registries.py +0 -0
- {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
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_engine_dying_under_a_job_is_not_a_mystery.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_environment_carries_the_products_name.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_environment_round_is_a_terminal_verb.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_event_axis_is_reached.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_examples_a_reader_copies_load_on_the_core.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_extensibility_doc_names_the_real_group.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_factory_asks_before_it_starts.py +0 -0
- {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
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_factory_runs_on_one_machine.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_factory_runs_without_a_cloud.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_facts_are_files_the_techlead_can_open.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_facts_stay_files_for_every_harness.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_first_hour_fails_by_name.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_first_round_is_reachable.py +0 -0
- {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
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_and_the_inbox_agree.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_is_a_deployment_default_not_a_transcription.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_is_a_platform_capability.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_floor_is_enforced_where_the_money_is.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_forge_can_read_a_foreign_repository.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_forge_credential_is_per_project.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_forge_opens_a_pr_in_another_repository.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_form_asks_what_the_address_needs.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_gate_checks_the_shell_script_a_stranger_runs_first.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_gate_shows_what_the_review_found.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_generated_environment_names_a_work_directory_that_needs_no_root.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_harness_endpoint_is_the_clients.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_harness_is_watched_while_it_runs.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_harness_pulse.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_history_says_where_the_work_is.py +0 -0
- {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
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_knows_exactly_two_facts_the_package_does_not.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_never_calls_sudo.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_installer_pins_a_version_and_never_installs_a_floating_tag.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_inventory_counts_what_it_could_not_place.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_journal_is_where_the_deployment_says.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_journal_records_how_a_job_ENDED.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_judging_roles_can_reach_their_model.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_language_reaches_every_unprompted_surface.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_lifecycle_names_no_provider.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_loop_tells_the_truth.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_manifest_can_be_proposed_server_side.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_manifest_reference_is_complete.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_map_does_not_wait_for_a_merge.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_map_is_published_beside_the_concepts.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_map_says_what_it_does_not_cover.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_merge_re_authors_what_it_invalidated.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_model_is_a_per_project_choice.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_model_is_set_from_the_cli.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_mutation_runner_is_not_decoration.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_namespace_is_the_products_name.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_net_that_protects_the_client.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_number_told_is_the_number_written.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_one_machine_deployment_runs_unattended.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_open_distribution_has_a_voice.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_operator_hears_the_projects_language.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_operator_path_names_what_the_code_mints.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_oss_distribution.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_is_a_channel.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_is_pushed_to.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_names_no_vendor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_reads_as_a_control_room.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_a_pass_is_running.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_where_to_change_things.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_says_which_harness.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_speaks_one_language.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_panel_touches_no_cloud_locally.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_platform_reads_a_repository_and_proposes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_carries_no_ones_past.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_carries_no_owners_name.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_conversation_is_core.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_opens_a_card_as_described.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_orders_the_backlog_from_chat.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_reads_an_intake_and_teaches.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owner_writes_the_backlog_order.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_owners_facts_are_files.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_path_is_not_github_by_construction.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_product_role_speaks_the_clients_language.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_project_remembers_what_everyone_said.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_promotion_tail_speaks.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_proof_gates_each_repository.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_proof_gates_pickup.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_proposal_covers_the_floor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_public_cut_is_written_down.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_public_documents_serve_a_stranger.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_pull_request_says_what_the_card_says.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_reading_is_bound_against_the_bundle_that_exists.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_release_assembles_what_the_installer_downloads.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_remedy_a_refusal_hands_you_can_be_followed.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_renewal_keeps_the_inventory_current.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_repository_moves_to_the_ticket.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_review_findings_stay_fixed.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_reviewer_can_actually_run.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_role_does_not_claim_blindness.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_roles_can_reach_the_bundle.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_roles_run_on_the_local_rows.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_second_deployment_is_not_the_first.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_second_yes_is_the_requesters.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_security_policy_promises_what_the_scrub_does.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_sizer_names_the_area_it_expects_to_touch.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_socket_is_real_and_scoped.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_split_says_which_card_to_drag.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_starter_template_passes_its_own_floor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_station_strip_resets_on_a_re_run.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_stock_box_runs_what_the_proposer_proposes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_structural_documents_speak_the_projects_portuguese.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_suite_cannot_reach_a_live_engine.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_suite_cannot_reach_production.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_answers_without_a_channel.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_can_see_the_change_it_judges.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_can_see_what_it_reasons_about.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_does_not_cry_wolf.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_reads_through_the_ports.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_remembers_the_conversation.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_techlead_sees_a_jira_floor.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_ticket_url_is_the_trackers_own.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_toolbox.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_tracker_has_a_read_side.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_tracker_reads_what_a_person_wrote.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_trajectory_is_a_measurement.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_two_halves_agree_on_which_code_they_run.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_vendors_credential_is_found_by_default.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_verifier_is_not_editable_by_what_it_verifies.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_walk_respects_what_the_repository_ignores.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_web_conversation_is_directed_at_the_person_in_it.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_web_offers_the_room_and_the_private_chat.py +0 -0
- {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
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_wheel_ships_what_the_platform_needs.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_the_word_list_is_a_shortcut_not_a_gate.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_three_commands_one_verdict.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_token_provider.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_tracker_board_writes.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_tracker_is_vendor_neutral.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_transcript_memory.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_two_projects_do_not_share_a_box.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_voice_vocabulary.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_walking_skeleton.py +0 -0
- {openfactory-0.2.0 → openfactory-0.2.1}/tests/test_what_git_refuses_the_person_reads.py +0 -0
- {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.
|
|
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
|
|
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
|
|
|
@@ -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
|
-
|
|
3137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
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,
|