phronomy 0.26.0 → 0.27.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/README.md +4 -0
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -22
- data/benchmark/bench_context_assembler.rb +4 -5
- data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
- data/docs/architecture/agent-context.md +4 -2
- data/docs/architecture/agent-transition-ownership.md +89 -0
- data/docs/architecture/before-llm-input.md +6 -0
- data/docs/architecture/context-management.md +30 -0
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-metadata-and-values.md +80 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +8 -2
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +27 -4
- data/docs/architecture/refactoring-closure.md +334 -0
- data/docs/architecture/remaining-refactoring-plan.md +374 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/tool-schema-recording-gap.md +50 -0
- data/docs/architecture/tracing.md +1 -1
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +209 -0
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
- data/docs/decisions/032-storage-backend-composition.md +80 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
- data/docs/decisions/036-context-contract-ownership.md +86 -0
- data/docs/decisions/037-common-definition-ownership.md +62 -0
- data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
- data/docs/decisions/040-configuration-default-composition.md +94 -0
- data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
- data/docs/decisions/042-feature-owned-execution-state.md +106 -0
- data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
- data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
- data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
- data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
- data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
- data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
- data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
- data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
- data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
- data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
- data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
- data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
- data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
- data/docs/decisions/058-neutral-storage-primitives.md +78 -0
- data/docs/decisions/README.md +35 -6
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
- data/docs/features.md +9 -3
- data/docs/getting-started.md +4 -4
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
- data/docs/migrations/handoff-runner-multi-agent.md +35 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/persistence-backends.md +162 -204
- data/docs/runtime-and-concurrency.md +117 -32
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/agent/async_event_api.rb +2 -2
- data/lib/phronomy/agent/base.rb +51 -241
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context/capability/base.rb +67 -26
- data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
- data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
- data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
- data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
- data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
- data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
- data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
- data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
- data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
- data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
- data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +19 -95
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +9 -10
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
- data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +20 -71
- data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
- data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
- data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
- data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
- data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
- data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
- data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
- data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
- data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
- data/lib/phronomy/agent/persistence/codec.rb +358 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
- data/lib/phronomy/agent/persistence/queries.rb +61 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
- data/lib/phronomy/agent/persistence/watermark.rb +27 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +32 -37
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
- data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
- data/lib/phronomy/agent/selection/candidate.rb +1 -1
- data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
- data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
- data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
- data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
- data/lib/phronomy/common/configuration_error.rb +7 -0
- data/lib/phronomy/common/error.rb +5 -0
- data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
- data/lib/phronomy/common/values/serializable.rb +32 -0
- data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
- data/lib/phronomy/configuration/global_configuration.rb +26 -0
- data/lib/phronomy/content_store/storage_schema.rb +11 -0
- data/lib/phronomy/content_store/stored_contents.rb +43 -0
- data/lib/phronomy/engine/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → engine/blocking.rb} +1 -1
- data/lib/phronomy/engine/cancellation_error.rb +7 -0
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +4 -0
- data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
- data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/engine/event_loop.rb +158 -624
- data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
- data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
- data/lib/phronomy/engine/execution_receiver.rb +65 -0
- data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
- data/lib/phronomy/engine/fsm_protocol.rb +14 -0
- data/lib/phronomy/engine/fsm_session.rb +35 -28
- data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
- data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
- data/lib/phronomy/engine/runtime.rb +80 -84
- data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/engine/task_result.rb +4 -0
- data/lib/phronomy/engine/timeout_error.rb +7 -0
- data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
- data/lib/phronomy/generation/generator_verifier.rb +118 -0
- data/lib/phronomy/generation/low_confidence_error.rb +14 -0
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
- data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
- data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
- data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
- data/lib/phronomy/llm_contract/transport_error.rb +7 -0
- data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +5 -5
- data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
- data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
- data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
- data/lib/phronomy/persistence/api/persistence.rb +140 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
- data/lib/phronomy/persistence_composition/repositories.rb +77 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/storage/backend.rb +101 -0
- data/lib/phronomy/storage/backends/in_memory.rb +157 -0
- data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
- data/lib/phronomy/storage/blobs.rb +31 -0
- data/lib/phronomy/storage/condition.rb +25 -0
- data/lib/phronomy/storage/condition_failed_error.rb +16 -0
- data/lib/phronomy/storage/conflict_error.rb +9 -0
- data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
- data/lib/phronomy/storage/entry.rb +33 -0
- data/lib/phronomy/storage/guard_ref.rb +13 -0
- data/lib/phronomy/storage/not_found_error.rb +9 -0
- data/lib/phronomy/storage/record_codec.rb +177 -0
- data/lib/phronomy/storage/records.rb +61 -0
- data/lib/phronomy/storage/resource.rb +126 -0
- data/lib/phronomy/storage/scope.rb +25 -0
- data/lib/phronomy/storage/serialization_error.rb +9 -0
- data/lib/phronomy/storage/streams.rb +44 -0
- data/lib/phronomy/storage/transaction_error.rb +10 -0
- data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
- data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
- data/lib/phronomy/storage/validation.rb +53 -0
- data/lib/phronomy/storage/view.rb +131 -0
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/contract/tool_error.rb +7 -0
- data/lib/phronomy/tools/agent.rb +1 -1
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +122 -71
- data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
- data/lib/phronomy/workflow/phase_machine_builder.rb +2 -2
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
- data/lib/phronomy.rb +52 -94
- data/scripts/api_snapshot.rb +1 -1
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/phronomy/agent.rbs +0 -1
- data/sig/phronomy/execution_receiver.rbs +34 -0
- data/sig/phronomy/handoff.rbs +4 -2
- data/sig/phronomy/multi_agent.rbs +18 -0
- data/sig/phronomy/persistence.rbs +7 -91
- data/sig/phronomy/storage.rbs +174 -0
- data/sig/phronomy/tool.rbs +10 -1
- metadata +230 -99
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_executor.rb +0 -55
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
- data/lib/phronomy/persistence/durable_codec.rb +0 -706
- data/lib/phronomy/persistence/in_memory.rb +0 -690
- data/lib/phronomy/persistence/repository_facades.rb +0 -535
- data/lib/phronomy/persistence.rb +0 -276
- data/lib/phronomy/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/workflow_recovery.rb +0 -123
- /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
- /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
- /data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
- /data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +0 -0
- /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
- /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Workflow-owned segment admission and routing, mutated only on EventLoop.
|
|
5
|
+
# @api private
|
|
6
|
+
class WorkflowExecutionRegistry < Phronomy::ExecutionReceiver
|
|
7
|
+
# Workflow-owned process-local Workflow execution-segment admission. The
|
|
8
|
+
# owner token is a Runtime coordination capability, not a domain identity or
|
|
9
|
+
# routing identity. fsm_session_id is bound only after durable load/hydration
|
|
10
|
+
# completes and a concrete FSMSession has been constructed.
|
|
11
|
+
WorkflowAdmission = Data.define(
|
|
12
|
+
:workflow_instance_id, :owner_token, :fsm_session_id, :state
|
|
13
|
+
)
|
|
14
|
+
private_constant :WorkflowAdmission
|
|
15
|
+
|
|
16
|
+
def initialize(event_loop:)
|
|
17
|
+
super
|
|
18
|
+
@workflow_admissions = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def post(command, admission: false, completion: nil)
|
|
22
|
+
post_message(command, admission: admission, completion: completion)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def deliver(command)
|
|
26
|
+
assert_event_loop_thread!
|
|
27
|
+
command.runner.deliver_on_event_loop(command)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def idle?
|
|
31
|
+
!workflow_admission_transition_in_progress_locked?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def session_retired(fsm_session_id, reason:)
|
|
35
|
+
assert_event_loop_thread!
|
|
36
|
+
mark_workflow_recovery_required_for_session(fsm_session_id) if reason == :recovery_required
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def shutdown(error:)
|
|
40
|
+
synchronize { @workflow_admissions.clear }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Reserves one logical Workflow execution segment before durable load or
|
|
44
|
+
# hydration. The owner token is independent from any later FSMSession id.
|
|
45
|
+
# @api private
|
|
46
|
+
def admit_workflow(workflow_instance_id, owner_token:)
|
|
47
|
+
assert_event_loop_thread!
|
|
48
|
+
key = workflow_instance_id.to_s
|
|
49
|
+
raise ArgumentError, "workflow_instance_id must not be empty" if key.empty?
|
|
50
|
+
raise ArgumentError, "owner_token is required" unless owner_token
|
|
51
|
+
|
|
52
|
+
admit do
|
|
53
|
+
if @workflow_admissions.key?(key)
|
|
54
|
+
raise Phronomy::Error,
|
|
55
|
+
"Workflow instance #{key.inspect} already has a live execution segment"
|
|
56
|
+
end
|
|
57
|
+
@workflow_admissions[key] = WorkflowAdmission.new(
|
|
58
|
+
workflow_instance_id: key.freeze,
|
|
59
|
+
owner_token: owner_token,
|
|
60
|
+
fsm_session_id: nil,
|
|
61
|
+
state: :admitting
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Binds the concrete routing identity after admission and durable hydration.
|
|
68
|
+
# @api private
|
|
69
|
+
def bind_workflow_session(workflow_instance_id, owner_token:, fsm_session_id:)
|
|
70
|
+
assert_event_loop_thread!
|
|
71
|
+
key = workflow_instance_id.to_s
|
|
72
|
+
fsm_key = fsm_session_id.to_s
|
|
73
|
+
raise ArgumentError, "fsm_session_id must not be empty" if fsm_key.empty?
|
|
74
|
+
|
|
75
|
+
synchronize do
|
|
76
|
+
current = @workflow_admissions.fetch(key) do
|
|
77
|
+
raise Phronomy::Error, "Workflow instance #{key.inspect} has no Runtime admission"
|
|
78
|
+
end
|
|
79
|
+
unless current.owner_token.equal?(owner_token) && current.fsm_session_id.nil?
|
|
80
|
+
raise Phronomy::Error, "stale Workflow admission bind for #{key.inspect}"
|
|
81
|
+
end
|
|
82
|
+
@workflow_admissions[key] = WorkflowAdmission.new(
|
|
83
|
+
workflow_instance_id: current.workflow_instance_id,
|
|
84
|
+
owner_token: current.owner_token,
|
|
85
|
+
fsm_session_id: fsm_key.freeze,
|
|
86
|
+
state: :executing
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @api private
|
|
93
|
+
def mark_workflow_admission(workflow_instance_id, owner_token:, state:)
|
|
94
|
+
assert_event_loop_thread!
|
|
95
|
+
key = workflow_instance_id.to_s
|
|
96
|
+
next_state = state.to_sym
|
|
97
|
+
unless %i[executing persisting_terminal recovery_required].include?(next_state)
|
|
98
|
+
raise ArgumentError, "unsupported Workflow admission state: #{next_state.inspect}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
synchronize do
|
|
102
|
+
current = @workflow_admissions.fetch(key) do
|
|
103
|
+
raise Phronomy::Error, "Workflow instance #{key.inspect} has no Runtime admission"
|
|
104
|
+
end
|
|
105
|
+
unless current.owner_token.equal?(owner_token)
|
|
106
|
+
raise Phronomy::Error, "stale Workflow admission update for #{key.inspect}"
|
|
107
|
+
end
|
|
108
|
+
@workflow_admissions[key] = WorkflowAdmission.new(
|
|
109
|
+
workflow_instance_id: current.workflow_instance_id,
|
|
110
|
+
owner_token: current.owner_token,
|
|
111
|
+
fsm_session_id: current.fsm_session_id,
|
|
112
|
+
state: next_state
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Owner-aware release. A competing or stale attempt cannot release the
|
|
119
|
+
# current Workflow execution segment.
|
|
120
|
+
# @api private
|
|
121
|
+
def release_workflow(workflow_instance_id, owner_token:)
|
|
122
|
+
assert_event_loop_thread!
|
|
123
|
+
key = workflow_instance_id.to_s
|
|
124
|
+
synchronize do
|
|
125
|
+
current = @workflow_admissions[key]
|
|
126
|
+
next false unless current&.owner_token&.equal?(owner_token)
|
|
127
|
+
|
|
128
|
+
@workflow_admissions.delete(key)
|
|
129
|
+
true
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Read-only diagnostics used by internal tests and routing assertions.
|
|
134
|
+
def workflow_admission_owner(workflow_instance_id)
|
|
135
|
+
synchronize do
|
|
136
|
+
@workflow_admissions[workflow_instance_id.to_s]&.owner_token
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def workflow_admission_fsm_session_id(workflow_instance_id)
|
|
141
|
+
synchronize do
|
|
142
|
+
@workflow_admissions[workflow_instance_id.to_s]&.fsm_session_id
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def workflow_admission_state(workflow_instance_id)
|
|
147
|
+
synchronize do
|
|
148
|
+
@workflow_admissions[workflow_instance_id.to_s]&.state
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def post_to_workflow(workflow_instance_id:, event:, payload: nil)
|
|
153
|
+
@event_loop.__route_execution_event(self) do
|
|
154
|
+
admission = @workflow_admissions[workflow_instance_id.to_s]
|
|
155
|
+
next unless admission&.state == :executing && admission.fsm_session_id
|
|
156
|
+
|
|
157
|
+
Phronomy::Event.new(
|
|
158
|
+
type: event.to_sym, target_id: admission.fsm_session_id, payload: payload
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def mark_workflow_recovery_required_for_session(fsm_session_id)
|
|
166
|
+
synchronize do
|
|
167
|
+
key, admission = @workflow_admissions.find do |_workflow_instance_id, candidate|
|
|
168
|
+
candidate.fsm_session_id == fsm_session_id.to_s
|
|
169
|
+
end
|
|
170
|
+
return false unless admission
|
|
171
|
+
|
|
172
|
+
@workflow_admissions[key] = WorkflowAdmission.new(
|
|
173
|
+
workflow_instance_id: admission.workflow_instance_id,
|
|
174
|
+
owner_token: admission.owner_token,
|
|
175
|
+
fsm_session_id: nil,
|
|
176
|
+
state: :recovery_required
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
true
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def workflow_admission_transition_in_progress_locked?
|
|
183
|
+
@workflow_admissions.values.any? do |admission|
|
|
184
|
+
%i[admitting executing persisting_terminal].include?(admission.state)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../engine/concurrency/worker_input_restricted"
|
|
4
|
+
|
|
3
5
|
require "securerandom"
|
|
4
6
|
|
|
5
7
|
module Phronomy
|
|
@@ -16,9 +18,9 @@ module Phronomy
|
|
|
16
18
|
# to the same FSMSession before that session becomes halted/completed.
|
|
17
19
|
# @api private
|
|
18
20
|
class WorkflowRunner
|
|
19
|
-
include Phronomy::
|
|
21
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
include Phronomy::Runnable
|
|
22
24
|
|
|
23
25
|
Execution = Data.define(
|
|
24
26
|
:context,
|
|
@@ -138,7 +140,7 @@ module Phronomy
|
|
|
138
140
|
end
|
|
139
141
|
|
|
140
142
|
# Posts an application-defined event to the currently live Workflow owner.
|
|
141
|
-
#
|
|
143
|
+
# WorkflowExecutionRegistry resolves workflow_instance_id to the current concrete
|
|
142
144
|
# fsm_session_id. The opaque admission owner token is deliberately not a
|
|
143
145
|
# routing identity and is not exposed here.
|
|
144
146
|
def signal(workflow_instance_id:, event:, payload: nil)
|
|
@@ -151,7 +153,10 @@ module Phronomy
|
|
|
151
153
|
"Valid events: #{@external_events.keys.inspect}"
|
|
152
154
|
end
|
|
153
155
|
|
|
154
|
-
Phronomy::Runtime.instance
|
|
156
|
+
registry = Phronomy::WorkflowExecutionRegistry.existing_for(Phronomy::Runtime.instance)
|
|
157
|
+
return false unless registry
|
|
158
|
+
|
|
159
|
+
registry.post_to_workflow(
|
|
155
160
|
workflow_instance_id: workflow_instance_id,
|
|
156
161
|
event: event_name,
|
|
157
162
|
payload: payload
|
|
@@ -277,7 +282,7 @@ module Phronomy
|
|
|
277
282
|
runtime = Phronomy::Runtime.instance
|
|
278
283
|
event_loop = runtime.event_loop
|
|
279
284
|
admitted = false
|
|
280
|
-
event_loop.admit_workflow(
|
|
285
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).admit_workflow(
|
|
281
286
|
request.workflow_instance_id,
|
|
282
287
|
owner_token: request.owner_token
|
|
283
288
|
)
|
|
@@ -318,7 +323,7 @@ module Phronomy
|
|
|
318
323
|
)
|
|
319
324
|
rescue => error
|
|
320
325
|
if admitted
|
|
321
|
-
event_loop.release_workflow(
|
|
326
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
|
|
322
327
|
request.workflow_instance_id,
|
|
323
328
|
owner_token: request.owner_token
|
|
324
329
|
)
|
|
@@ -330,7 +335,7 @@ module Phronomy
|
|
|
330
335
|
runtime = Phronomy::Runtime.instance
|
|
331
336
|
event_loop = runtime.event_loop
|
|
332
337
|
admitted = false
|
|
333
|
-
event_loop.admit_workflow(
|
|
338
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).admit_workflow(
|
|
334
339
|
request.workflow_instance_id,
|
|
335
340
|
owner_token: request.owner_token
|
|
336
341
|
)
|
|
@@ -366,7 +371,7 @@ module Phronomy
|
|
|
366
371
|
)
|
|
367
372
|
rescue => error
|
|
368
373
|
if admitted
|
|
369
|
-
event_loop.release_workflow(
|
|
374
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
|
|
370
375
|
request.workflow_instance_id,
|
|
371
376
|
owner_token: request.owner_token
|
|
372
377
|
)
|
|
@@ -503,7 +508,7 @@ module Phronomy
|
|
|
503
508
|
local_snapshot = normalize_snapshot(snapshot_for(state))
|
|
504
509
|
return durable_revision if normalized_durable == local_snapshot
|
|
505
510
|
|
|
506
|
-
raise Phronomy::
|
|
511
|
+
raise Phronomy::Storage::ConflictError,
|
|
507
512
|
"Workflow state changed since the supplied halted context for " \
|
|
508
513
|
"workflow_instance_id #{state.workflow_instance_id.inspect}; explicit reload/reconciliation is required"
|
|
509
514
|
end
|
|
@@ -576,7 +581,7 @@ module Phronomy
|
|
|
576
581
|
resume_phase: resume_phase,
|
|
577
582
|
stable_observer: stable_observer
|
|
578
583
|
)
|
|
579
|
-
event_loop.bind_workflow_session(
|
|
584
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).bind_workflow_session(
|
|
580
585
|
execution.workflow_instance_id,
|
|
581
586
|
owner_token: execution.owner_token,
|
|
582
587
|
fsm_session_id: session.id
|
|
@@ -591,11 +596,12 @@ module Phronomy
|
|
|
591
596
|
error: error
|
|
592
597
|
)
|
|
593
598
|
end
|
|
594
|
-
event_loop.register(session, completion: source_task
|
|
599
|
+
event_loop.register(session, completion: source_task,
|
|
600
|
+
receiver: Phronomy::WorkflowExecutionRegistry.for(event_loop))
|
|
595
601
|
result_task
|
|
596
602
|
rescue => error
|
|
597
603
|
if event_loop&.current?
|
|
598
|
-
event_loop
|
|
604
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
|
|
599
605
|
execution.workflow_instance_id,
|
|
600
606
|
owner_token: execution.owner_token
|
|
601
607
|
)
|
|
@@ -608,7 +614,7 @@ module Phronomy
|
|
|
608
614
|
def finalize_execution(execution:, result_task:, result:, error:)
|
|
609
615
|
event_loop = Phronomy::Runtime.instance.event_loop
|
|
610
616
|
assert_event_loop!(event_loop)
|
|
611
|
-
event_loop.release_workflow(
|
|
617
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
|
|
612
618
|
execution.workflow_instance_id,
|
|
613
619
|
owner_token: execution.owner_token
|
|
614
620
|
)
|
|
@@ -624,73 +630,117 @@ module Phronomy
|
|
|
624
630
|
runtime = Phronomy::Runtime.instance
|
|
625
631
|
event_loop = runtime.event_loop
|
|
626
632
|
assert_event_loop!(event_loop)
|
|
627
|
-
event_loop.mark_workflow_admission(
|
|
633
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).mark_workflow_admission(
|
|
628
634
|
execution.workflow_instance_id,
|
|
629
635
|
owner_token: execution.owner_token,
|
|
630
636
|
state: :persisting_terminal
|
|
631
637
|
)
|
|
632
638
|
|
|
633
|
-
operation = WorkflowTerminalPersistenceCommand.new(
|
|
639
|
+
operation = Phronomy::WorkflowRunner::WorkflowTerminalPersistenceCommand.new(
|
|
634
640
|
repository: execution.repository,
|
|
635
641
|
workflow_instance_id: execution.workflow_instance_id.to_s.freeze,
|
|
636
642
|
expected_revision: execution.expected_revision,
|
|
637
643
|
snapshot: deep_immutable_copy(snapshot_for(context))
|
|
638
644
|
)
|
|
645
|
+
|
|
639
646
|
task = runtime.offload.submit(on_full: :raise) do
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
647
|
+
persist_terminal_snapshot(operation)
|
|
648
|
+
end
|
|
649
|
+
task.on_complete do |result, operation_error|
|
|
650
|
+
deliver_terminal_persistence_result(execution, event_sink, result, operation_error)
|
|
651
|
+
end
|
|
652
|
+
terminal_type
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def persist_terminal_snapshot(operation)
|
|
656
|
+
revision = operation.repository.save(
|
|
657
|
+
operation.workflow_instance_id,
|
|
658
|
+
expected_revision: operation.expected_revision,
|
|
659
|
+
snapshot: operation.snapshot
|
|
660
|
+
)
|
|
661
|
+
Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
|
|
662
|
+
outcome: :success,
|
|
663
|
+
revision: revision,
|
|
664
|
+
error: nil
|
|
665
|
+
)
|
|
666
|
+
rescue Phronomy::Storage::ConflictError,
|
|
667
|
+
Phronomy::Storage::NotFoundError,
|
|
668
|
+
Phronomy::Storage::SerializationError,
|
|
669
|
+
Phronomy::Storage::UnsupportedBackendError => error
|
|
670
|
+
Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
|
|
671
|
+
outcome: :known_failure,
|
|
672
|
+
revision: nil,
|
|
673
|
+
error: error
|
|
674
|
+
)
|
|
675
|
+
rescue => error
|
|
676
|
+
reconcile_workflow_terminal_f1(operation, error)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
def deliver_terminal_persistence_result(execution, event_sink, result, operation_error)
|
|
680
|
+
delivery = if operation_error
|
|
681
|
+
Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
|
|
682
|
+
outcome: :outcome_unknown,
|
|
683
|
+
revision: nil,
|
|
684
|
+
error: operation_error
|
|
644
685
|
)
|
|
645
|
-
|
|
686
|
+
else
|
|
687
|
+
result
|
|
688
|
+
end
|
|
689
|
+
accepted = event_sink.post(:workflow_terminal_persistence_result, delivery)
|
|
690
|
+
unless accepted
|
|
691
|
+
Phronomy.configuration.logger&.warn(
|
|
692
|
+
"[Phronomy] EventLoop rejected Workflow terminal persistence result " \
|
|
693
|
+
"for #{execution.workflow_instance_id.inspect}"
|
|
694
|
+
)
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def reconcile_workflow_terminal_f1(operation, original_error)
|
|
699
|
+
record = operation.repository.load(operation.workflow_instance_id)
|
|
700
|
+
case Phronomy::Recovery.compare_revisioned_snapshot(
|
|
701
|
+
record: record,
|
|
702
|
+
expected_pre_revision: operation.expected_revision,
|
|
703
|
+
intended_snapshot: operation.snapshot
|
|
704
|
+
)
|
|
705
|
+
when :post_state
|
|
706
|
+
revision = Phronomy::Recovery.fetch_value(record, :revision)
|
|
707
|
+
Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
|
|
646
708
|
outcome: :success,
|
|
647
709
|
revision: revision,
|
|
648
710
|
error: nil
|
|
649
711
|
)
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
WorkflowTerminalPersistenceResult.new(
|
|
712
|
+
when :pre_state
|
|
713
|
+
# The uncertain save is now known not to have committed. Do not blindly
|
|
714
|
+
# retry it here; surface a known failure through the ordinary terminal
|
|
715
|
+
# barrier semantics.
|
|
716
|
+
Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
|
|
655
717
|
outcome: :known_failure,
|
|
656
718
|
revision: nil,
|
|
657
|
-
error:
|
|
719
|
+
error: original_error
|
|
658
720
|
)
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
721
|
+
else
|
|
722
|
+
conflict = Phronomy::Storage::ConflictError.new(
|
|
723
|
+
"Workflow terminal Persistence outcome conflicts with both expected " \
|
|
724
|
+
"pre-state and intended post-state for #{operation.workflow_instance_id.inspect}"
|
|
725
|
+
)
|
|
726
|
+
Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
|
|
664
727
|
outcome: :outcome_unknown,
|
|
665
728
|
revision: nil,
|
|
666
|
-
error:
|
|
729
|
+
error: conflict
|
|
667
730
|
)
|
|
668
731
|
end
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
)
|
|
676
|
-
else
|
|
677
|
-
result
|
|
678
|
-
end
|
|
679
|
-
accepted = event_sink.post(:workflow_terminal_persistence_result, delivery)
|
|
680
|
-
unless accepted
|
|
681
|
-
Phronomy.configuration.logger&.warn(
|
|
682
|
-
"[Phronomy] EventLoop rejected Workflow terminal persistence result " \
|
|
683
|
-
"for #{execution.workflow_instance_id.inspect}"
|
|
684
|
-
)
|
|
685
|
-
end
|
|
686
|
-
end
|
|
687
|
-
terminal_type
|
|
732
|
+
rescue => reconciliation_error
|
|
733
|
+
Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
|
|
734
|
+
outcome: :outcome_unknown,
|
|
735
|
+
revision: nil,
|
|
736
|
+
error: reconciliation_error
|
|
737
|
+
)
|
|
688
738
|
end
|
|
689
739
|
|
|
690
740
|
def release_and_fail(request, error)
|
|
691
741
|
event_loop = Phronomy::Runtime.instance.event_loop
|
|
692
742
|
assert_event_loop!(event_loop)
|
|
693
|
-
event_loop.release_workflow(
|
|
743
|
+
Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
|
|
694
744
|
request.workflow_instance_id,
|
|
695
745
|
owner_token: request.owner_token
|
|
696
746
|
)
|
|
@@ -711,17 +761,6 @@ module Phronomy
|
|
|
711
761
|
resume_phase: nil,
|
|
712
762
|
stable_observer: nil
|
|
713
763
|
)
|
|
714
|
-
terminal_barrier = if execution.repository && execution.persist
|
|
715
|
-
->(terminal_type:, context:, event_sink:) {
|
|
716
|
-
begin_terminal_persistence_on_event_loop(
|
|
717
|
-
execution,
|
|
718
|
-
terminal_type: terminal_type,
|
|
719
|
-
context: context,
|
|
720
|
-
event_sink: event_sink
|
|
721
|
-
)
|
|
722
|
-
}
|
|
723
|
-
end
|
|
724
|
-
|
|
725
764
|
Phronomy::FSMSession.new(
|
|
726
765
|
context: execution.context,
|
|
727
766
|
context_metadata: {
|
|
@@ -739,7 +778,22 @@ module Phronomy
|
|
|
739
778
|
resume_event: resume_event,
|
|
740
779
|
resume_phase: resume_phase,
|
|
741
780
|
stable_observer: stable_observer,
|
|
742
|
-
|
|
781
|
+
terminal_policy: build_terminal_policy(execution)
|
|
782
|
+
)
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
def build_terminal_policy(execution)
|
|
786
|
+
return unless execution.repository && execution.persist
|
|
787
|
+
|
|
788
|
+
Phronomy::WorkflowTerminalPolicy.new(
|
|
789
|
+
persist: ->(terminal_type:, context:, event_sink:) {
|
|
790
|
+
begin_terminal_persistence_on_event_loop(
|
|
791
|
+
execution,
|
|
792
|
+
terminal_type: terminal_type,
|
|
793
|
+
context: context,
|
|
794
|
+
event_sink: event_sink
|
|
795
|
+
)
|
|
796
|
+
}
|
|
743
797
|
)
|
|
744
798
|
end
|
|
745
799
|
|
|
@@ -766,13 +820,10 @@ module Phronomy
|
|
|
766
820
|
end
|
|
767
821
|
|
|
768
822
|
def post_control(runtime, command)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
payload: {command: command}
|
|
774
|
-
)
|
|
775
|
-
)
|
|
823
|
+
admission = command.is_a?(StartCommand) || command.is_a?(ResumeCommand)
|
|
824
|
+
completion = admission ? command.result_task : command.request.result_task
|
|
825
|
+
Phronomy::WorkflowExecutionRegistry.for(runtime.event_loop).post(command,
|
|
826
|
+
admission: admission, completion: completion)
|
|
776
827
|
end
|
|
777
828
|
|
|
778
829
|
def runtime_rejected_error(action)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Interprets durable Workflow results for the shared FSM terminal gate.
|
|
5
|
+
# The session owns pending state; Runner owns persistence and reconciliation.
|
|
6
|
+
# @api private
|
|
7
|
+
class WorkflowTerminalPolicy
|
|
8
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
9
|
+
|
|
10
|
+
def initialize(persist:)
|
|
11
|
+
@persist = persist
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start(terminal_type:, context:, event_sink:)
|
|
15
|
+
@persist.call(terminal_type: terminal_type, context: context, event_sink: event_sink)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def handles?(event)
|
|
19
|
+
event.type == :workflow_terminal_persistence_result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def decision_for(event)
|
|
23
|
+
result = event.payload
|
|
24
|
+
case result.outcome
|
|
25
|
+
when :success
|
|
26
|
+
FSMProtocol::TerminalDecision.new(action: :complete, error: nil)
|
|
27
|
+
when :known_failure
|
|
28
|
+
FSMProtocol::TerminalDecision.new(
|
|
29
|
+
action: :fail,
|
|
30
|
+
error: result.error || Phronomy::Error.new("Workflow terminal persistence failed")
|
|
31
|
+
)
|
|
32
|
+
when :outcome_unknown
|
|
33
|
+
FSMProtocol::TerminalDecision.new(action: :retire, error: result.error)
|
|
34
|
+
else
|
|
35
|
+
raise Phronomy::Error,
|
|
36
|
+
"unknown Workflow terminal persistence outcome: #{result.outcome.inspect}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|