smith-agents 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +83 -0
  4. data/lib/smith/agent.rb +13 -0
  5. data/lib/smith/errors.rb +4 -0
  6. data/lib/smith/version.rb +2 -2
  7. data/lib/smith/workflow/composite/branch.rb +93 -0
  8. data/lib/smith/workflow/composite/branch_budget_contract.rb +41 -0
  9. data/lib/smith/workflow/composite/branch_contract.rb +102 -0
  10. data/lib/smith/workflow/composite/branch_execution.rb +124 -0
  11. data/lib/smith/workflow/composite/branch_failure.rb +91 -0
  12. data/lib/smith/workflow/composite/branch_outcome.rb +108 -0
  13. data/lib/smith/workflow/composite/budget_allocator.rb +78 -0
  14. data/lib/smith/workflow/composite/budget_math.rb +51 -0
  15. data/lib/smith/workflow/composite/contract.rb +100 -0
  16. data/lib/smith/workflow/composite/effects.rb +140 -0
  17. data/lib/smith/workflow/composite/effects_application.rb +36 -0
  18. data/lib/smith/workflow/composite/effects_baseline.rb +36 -0
  19. data/lib/smith/workflow/composite/effects_preflight.rb +102 -0
  20. data/lib/smith/workflow/composite/encoded_value_budget.rb +36 -0
  21. data/lib/smith/workflow/composite/enums.rb +27 -0
  22. data/lib/smith/workflow/composite/error.rb +54 -0
  23. data/lib/smith/workflow/composite/error_evidence.rb +61 -0
  24. data/lib/smith/workflow/composite/execution_contract.rb +73 -0
  25. data/lib/smith/workflow/composite/fanout_branch_contract.rb +61 -0
  26. data/lib/smith/workflow/composite/input.rb +53 -0
  27. data/lib/smith/workflow/composite/outcome_accumulator.rb +131 -0
  28. data/lib/smith/workflow/composite/outcome_set.rb +23 -0
  29. data/lib/smith/workflow/composite/payload.rb +128 -0
  30. data/lib/smith/workflow/composite/payload_digest.rb +28 -0
  31. data/lib/smith/workflow/composite/plan.rb +130 -0
  32. data/lib/smith/workflow/composite/plan_integrity.rb +52 -0
  33. data/lib/smith/workflow/composite/planner.rb +53 -0
  34. data/lib/smith/workflow/composite/preparation.rb +27 -0
  35. data/lib/smith/workflow/composite/reducer.rb +133 -0
  36. data/lib/smith/workflow/composite/reduction.rb +31 -0
  37. data/lib/smith/workflow/composite/value_budget.rb +90 -0
  38. data/lib/smith/workflow/composite_branch_execution_authorization.rb +52 -0
  39. data/lib/smith/workflow/deadline_enforcement.rb +22 -5
  40. data/lib/smith/workflow/execution.rb +16 -16
  41. data/lib/smith/workflow/fanout_execution.rb +19 -18
  42. data/lib/smith/workflow/message_value_normalizer.rb +11 -10
  43. data/lib/smith/workflow/parallel_execution.rb +41 -13
  44. data/lib/smith/workflow/prepared_branch_execution.rb +31 -0
  45. data/lib/smith/workflow/prepared_step_execution_authorization.rb +14 -29
  46. data/lib/smith/workflow/prepared_step_execution_scope.rb +81 -15
  47. data/lib/smith/workflow/process_local.rb +33 -0
  48. data/lib/smith/workflow/split_step_persistence/composite_branch_authorization.rb +99 -0
  49. data/lib/smith/workflow/split_step_persistence/composite_branch_effects.rb +41 -0
  50. data/lib/smith/workflow/split_step_persistence/composite_branch_execution.rb +95 -0
  51. data/lib/smith/workflow/split_step_persistence/composite_branch_outcome.rb +36 -0
  52. data/lib/smith/workflow/split_step_persistence/composite_execution.rb +39 -0
  53. data/lib/smith/workflow/split_step_persistence/composite_preparation.rb +57 -0
  54. data/lib/smith/workflow/split_step_persistence/composite_reduction_execution.rb +122 -0
  55. data/lib/smith/workflow/split_step_persistence/execution.rb +7 -53
  56. data/lib/smith/workflow/split_step_persistence/execution_authorization.rb +41 -21
  57. data/lib/smith/workflow/split_step_persistence/execution_authorization_issuance.rb +57 -0
  58. data/lib/smith/workflow/split_step_persistence/execution_binding_collector.rb +5 -0
  59. data/lib/smith/workflow/split_step_persistence/execution_binding_snapshot.rb +17 -3
  60. data/lib/smith/workflow/split_step_persistence/execution_lifecycle.rb +96 -0
  61. data/lib/smith/workflow/split_step_persistence/execution_verification.rb +17 -5
  62. data/lib/smith/workflow/split_step_persistence/preparation_claim.rb +8 -0
  63. data/lib/smith/workflow/split_step_persistence/subclass_boundary.rb +21 -5
  64. data/lib/smith/workflow/split_step_persistence.rb +14 -0
  65. data/lib/smith/workflow/step_context.rb +46 -0
  66. data/lib/smith/workflow/thread_context_snapshot.rb +103 -0
  67. data/lib/smith/workflow/transition.rb +16 -1
  68. data/lib/smith/workflow.rb +21 -1
  69. data/lib/smith.rb +3 -0
  70. metadata +46 -1
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../composite/contract"
4
+ require_relative "../composite_branch_execution_authorization"
5
+ require_relative "execution_binding_snapshot"
6
+
7
+ module Smith
8
+ class Workflow
9
+ module SplitStepPersistence
10
+ module CompositeBranchAuthorization
11
+ include Composite::Contract
12
+
13
+ private
14
+
15
+ def authorize_prepared_composite_branch_execution!(execution:, input:)
16
+ authorization = nil
17
+ handed_off = false
18
+ authorization = CompositeBranchAuthorization
19
+ .instance_method(:interrupt_safe_composite_branch_authorization)
20
+ .bind_call(self, execution, input)
21
+ handed_off = true
22
+ authorization
23
+ ensure
24
+ unless handed_off
25
+ ExecutionLifecycle
26
+ .instance_method(:release_failed_execution_authorization!)
27
+ .bind_call(self, authorization)
28
+ end
29
+ end
30
+
31
+ def with_composite_branch_authorization(execution, input)
32
+ Thread.handle_interrupt(Object => :never) do
33
+ authorization = interrupt_safe_composite_branch_authorization(execution, input)
34
+ begin
35
+ Thread.handle_interrupt(Object => :immediate) { yield(authorization) }
36
+ ensure
37
+ ExecutionLifecycle
38
+ .instance_method(:release_failed_execution_authorization!)
39
+ .bind_call(self, authorization)
40
+ end
41
+ end
42
+ end
43
+
44
+ def interrupt_safe_composite_branch_authorization(execution, input)
45
+ Thread.handle_interrupt(Object => :never) do
46
+ Composite::Contract
47
+ .instance_method(:validate_composite_branch_payload_types!)
48
+ .bind_call(self, execution, input)
49
+ verification_token = ExecutionAuthorization
50
+ .instance_method(:claim_framework_execution_verification!)
51
+ .bind_call(self)
52
+ begin
53
+ CompositeBranchAuthorization
54
+ .instance_method(:authorize_claimed_composite_branch_execution!)
55
+ .bind_call(self, verification_token, execution, input)
56
+ ensure
57
+ ExecutionAuthorization
58
+ .instance_method(:restore_framework_execution_verification!)
59
+ .bind_call(self, verification_token)
60
+ end
61
+ end
62
+ end
63
+
64
+ def authorize_claimed_composite_branch_execution!(verification_token, execution, input)
65
+ ExecutionVerification
66
+ .instance_method(:verify_claimed_split_step_execution!)
67
+ .bind_call(self, verification_token)
68
+ authorization = CompositeBranchAuthorization
69
+ .instance_method(:build_composite_branch_execution_authorization)
70
+ .bind_call(self, execution, input)
71
+ ExecutionAuthorization
72
+ .instance_method(:activate_split_step_execution_authorization!)
73
+ .bind_call(self, authorization, verification_token)
74
+ Composite::Contract
75
+ .instance_method(:validate_composite_branch_execution!)
76
+ .bind_call(self, authorization, execution, input)
77
+ authorization
78
+ end
79
+
80
+ def build_composite_branch_execution_authorization(execution, input)
81
+ role = execution.kind == :parallel ? :agent : :fanout_agent
82
+ bindings = ExecutionBindingSnapshot.capture_agent(
83
+ @split_step_transition,
84
+ workflow_class: self.class,
85
+ name: execution.branch.agent,
86
+ role:
87
+ )
88
+ CompositeBranchExecutionAuthorization.new(
89
+ execution:,
90
+ input:,
91
+ prepared_step: @split_step_prepared_descriptor,
92
+ dispatch_claim: @split_step_dispatch_descriptor,
93
+ execution_bindings: bindings
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../composite/effects"
4
+
5
+ module Smith
6
+ class Workflow
7
+ module SplitStepPersistence
8
+ module CompositeBranchEffects
9
+ private
10
+
11
+ def composite_effect_offsets
12
+ usage = @usage_mutex.synchronize { @usage_entries.length }
13
+ tools = @tool_results_mutex.synchronize { @tool_results.length }
14
+ [usage, tools]
15
+ end
16
+
17
+ def capture_composite_branch_effects(usage_offset, tool_offset, branch)
18
+ usage = @usage_mutex.synchronize do
19
+ @usage_entries.drop(usage_offset).map { composite_usage_entry(_1, branch) }
20
+ end
21
+ tools = @tool_results_mutex.synchronize { @tool_results.drop(tool_offset) }
22
+ consumed = @ledger ? @ledger.consumed : {}
23
+ [Composite::Effects.new(usage_entries: usage, tool_results: tools, budget_consumed: consumed), nil]
24
+ rescue WorkflowError, ArgumentError => e
25
+ safe = Composite::Effects.new(usage_entries: usage || [], tool_results: [], budget_consumed: consumed || {})
26
+ [safe, e]
27
+ end
28
+
29
+ def composite_usage_entry(entry, branch)
30
+ attributes = entry.to_h
31
+ recorded_agent = attributes[:agent_name]
32
+ if recorded_agent && recorded_agent.to_s != branch.agent
33
+ raise WorkflowError, "composite usage entry does not match the executed branch agent"
34
+ end
35
+
36
+ attributes.merge(agent_name: branch.agent)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../composite/contract"
4
+ require_relative "composite_branch_effects"
5
+ require_relative "composite_branch_outcome"
6
+
7
+ module Smith
8
+ class Workflow
9
+ module SplitStepPersistence
10
+ module CompositeBranchExecution
11
+ include Composite::Contract
12
+ include CompositeBranchEffects
13
+ include CompositeBranchOutcome
14
+
15
+ private
16
+
17
+ def execute_authorized_composite_branch!(authorization, execution:, input:)
18
+ authorization = validate_composite_authorization!(authorization)
19
+ perform_authorized_prepared_step_execution!(authorization) do
20
+ validate_composite_branch_execution!(authorization, execution, input)
21
+ [nil, execute_composite_branch(authorization, execution, input)]
22
+ end
23
+ end
24
+
25
+ def execute_composite_branch(authorization, execution, input)
26
+ branch = execution.branch
27
+ previous_ledger = @ledger
28
+ previous_execution_namespace = @execution_namespace
29
+ @ledger = composite_branch_ledger(branch)
30
+ @execution_namespace = execution.execution_namespace
31
+ offsets = composite_effect_offsets
32
+ output, error = capture_composite_branch do
33
+ dispatch_composite_branch(authorization, execution, input)
34
+ end
35
+ effects, effect_error = capture_composite_branch_effects(*offsets, branch)
36
+ composite_branch_outcome(execution, output, error || effect_error, effects)
37
+ ensure
38
+ @ledger = previous_ledger
39
+ @execution_namespace = previous_execution_namespace
40
+ end
41
+
42
+ def capture_composite_branch(&block)
43
+ output = within_raw_step_context { with_scoped_artifacts(&block) }
44
+ [output, nil]
45
+ rescue StandardError => e
46
+ [nil, e]
47
+ end
48
+
49
+ def dispatch_composite_branch(authorization, execution, input)
50
+ branch = execution.branch
51
+ transition = @split_step_transition
52
+ agent_role = execution.kind == :parallel ? :agent : :fanout_agent
53
+ agent_class = captured_agent(authorization, transition, branch.agent, agent_role)
54
+ apply_tool_guardrails(agent_class) if execution.kind == :parallel
55
+ environment = composite_branch_environment(execution, input, agent_class, transition)
56
+ execute_composite_branch_kind(execution, transition, agent_class, environment)
57
+ end
58
+
59
+ def execute_composite_branch_kind(execution, transition, agent_class, environment)
60
+ branch = execution.branch
61
+ signal = Parallel::CancellationSignal.new
62
+ return run_branch(transition, branch.ordinal, environment, @ledger, signal) if execution.kind == :parallel
63
+
64
+ branch_key, agent = fetch_composite_fanout_branch(transition, branch.key)
65
+ run_fanout_branch(branch_key, agent, agent_class, environment, signal)
66
+ end
67
+
68
+ def composite_branch_environment(execution, input, agent_class, transition)
69
+ branch = execution.branch
70
+ budget = branch.budget.transform_keys(&:to_sym)
71
+ branch_key = fetch_composite_fanout_branch(transition, branch.key).first unless execution.kind == :parallel
72
+ estimates = execution.kind == :parallel ? budget : { branch_key => budget }
73
+ BranchEnv.new(
74
+ prepared_input: input.agent_messages,
75
+ guardrail_sources: Tool.current_guardrails,
76
+ scoped_store: propagate_scoped_artifacts,
77
+ branch_estimates: estimates,
78
+ deadline: wall_clock_deadline,
79
+ agent_class: execution.kind == :parallel ? agent_class : nil
80
+ )
81
+ end
82
+
83
+ def composite_branch_ledger(branch)
84
+ return if branch.budget.empty?
85
+
86
+ Budget::Ledger.new(limits: branch.budget.transform_keys(&:to_sym))
87
+ end
88
+
89
+ def fetch_composite_fanout_branch(transition, branch_key)
90
+ Transition.instance_method(:fetch_fanout_branch!).bind_call(transition, branch_key)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../composite/branch_outcome"
4
+ require_relative "../composite/error_evidence"
5
+
6
+ module Smith
7
+ class Workflow
8
+ module SplitStepPersistence
9
+ module CompositeBranchOutcome
10
+ private
11
+
12
+ def composite_branch_outcome(execution, output, error, effects)
13
+ return failed_composite_branch_outcome(execution, error, effects) if error
14
+
15
+ Composite::BranchOutcome.succeeded(
16
+ plan_digest: execution.plan_digest,
17
+ branch: execution.branch,
18
+ output: output.fetch(:output),
19
+ effects:
20
+ )
21
+ rescue WorkflowError, ArgumentError => e
22
+ failed_composite_branch_outcome(execution, e, effects)
23
+ end
24
+
25
+ def failed_composite_branch_outcome(execution, error, effects)
26
+ Composite::BranchOutcome.failed(
27
+ plan_digest: execution.plan_digest,
28
+ branch: execution.branch,
29
+ error: Composite::ErrorEvidence.call(error),
30
+ effects:
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ module SplitStepPersistence
6
+ module CompositeExecution
7
+ def prepare_composite_step!
8
+ ExecutionAuthorizationIssuance
9
+ .instance_method(:with_prepared_step_execution_authorization)
10
+ .bind_call(self) do |authorization|
11
+ CompositePreparation
12
+ .instance_method(:prepare_authorized_composite_step!)
13
+ .bind_call(self, authorization)
14
+ end
15
+ end
16
+
17
+ def execute_prepared_composite_branch!(execution:, input:)
18
+ CompositeBranchAuthorization
19
+ .instance_method(:with_composite_branch_authorization)
20
+ .bind_call(self, execution, input) do |authorization|
21
+ CompositeBranchExecution
22
+ .instance_method(:execute_authorized_composite_branch!)
23
+ .bind_call(self, authorization, execution:, input:)
24
+ end
25
+ end
26
+
27
+ def reduce_prepared_composite_step!(plan:, input:, outcomes:, primary_failure: nil)
28
+ ExecutionAuthorizationIssuance
29
+ .instance_method(:with_prepared_step_execution_authorization)
30
+ .bind_call(self) do |authorization|
31
+ CompositeReductionExecution
32
+ .instance_method(:reduce_authorized_composite_step!)
33
+ .bind_call(self, authorization, plan:, input:, outcomes:, primary_failure:)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../composite/contract"
4
+ require_relative "../composite/input"
5
+ require_relative "../composite/planner"
6
+ require_relative "../composite/preparation"
7
+
8
+ module Smith
9
+ class Workflow
10
+ module SplitStepPersistence
11
+ module CompositePreparation
12
+ include Composite::Contract
13
+
14
+ private
15
+
16
+ def prepare_authorized_composite_step!(authorization)
17
+ authorization = validate_composite_authorization!(authorization)
18
+ transition = @split_step_transition
19
+ specs = composite_branch_specs(authorization, transition)
20
+ input = prepare_composite_input(authorization, transition, specs)
21
+ plan = Composite::Planner.new(
22
+ dispatch: authorization.dispatch_claim,
23
+ kind: composite_kind(transition),
24
+ transition: transition.name,
25
+ from: transition.from,
26
+ execution_namespace: execution_namespace,
27
+ branch_specs: specs,
28
+ input_digest: input.digest,
29
+ budget_state_digest: composite_budget_state_digest
30
+ ).call
31
+ Composite::Preparation.new(plan:, input:)
32
+ end
33
+
34
+ def prepare_composite_input(authorization, transition, specs)
35
+ ThreadContextSnapshot.new.around do
36
+ run_composite_input_guardrails(authorization, transition, specs)
37
+ agent_messages = build_session&.prepare!
38
+ Composite::Input.build(agent_messages:, session_messages: snapshot_session_messages)
39
+ end
40
+ end
41
+
42
+ def run_composite_input_guardrails(authorization, transition, specs)
43
+ if transition.parallel?
44
+ agent = specs.first.fetch(:agent)
45
+ run_input_guardrails(captured_agent(authorization, transition, agent, :agent))
46
+ else
47
+ run_workflow_input_guardrails
48
+ specs.each do |spec|
49
+ agent = spec.fetch(:agent)
50
+ run_agent_input_guardrails(captured_agent(authorization, transition, agent, :fanout_agent))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../composite/branch_failure"
4
+ require_relative "../composite/contract"
5
+ require_relative "../composite/effects_baseline"
6
+ require_relative "../composite/effects_preflight"
7
+ require_relative "../composite/reducer"
8
+
9
+ module Smith
10
+ class Workflow
11
+ module SplitStepPersistence
12
+ module CompositeReductionExecution
13
+ include Composite::Contract
14
+
15
+ private
16
+
17
+ def reduce_authorized_composite_step!(authorization, plan:, input:, outcomes:, primary_failure: nil)
18
+ authorization = validate_composite_authorization!(authorization)
19
+ validate_composite_reduction_plan!(authorization, plan, input)
20
+ reduction = Composite::Reducer.new(plan:, outcomes:, primary_failure:).call
21
+ effects_application = prepare_composite_effects(reduction.effects)
22
+ @composite_plan = plan
23
+ @composite_input = input
24
+ @composite_reduction = reduction
25
+ @composite_effects_application = effects_application
26
+ @execution_namespace = plan.execution_namespace
27
+ Execution.instance_method(:execute_authorized_prepared_step!).bind_call(self, authorization)
28
+ ensure
29
+ @composite_plan = nil
30
+ @composite_input = nil
31
+ @composite_reduction = nil
32
+ @composite_effects_application = nil
33
+ end
34
+
35
+ def apply_composite_reduction!(transition)
36
+ reduction = @composite_reduction
37
+ apply_composite_effects!(@composite_effects_application)
38
+ restore_composite_session!
39
+ if reduction.failed?
40
+ raise Composite::BranchFailure.new(
41
+ branch_key: reduction.failed_branch_key,
42
+ error: reduction.error
43
+ )
44
+ end
45
+
46
+ validate_composite_output!(transition, reduction.output)
47
+ reduction.output
48
+ end
49
+
50
+ def restore_composite_session!
51
+ @session_messages = snapshot_value(@composite_input.session_messages)
52
+ @last_prepared_input = snapshot_value(@composite_input.agent_messages)
53
+ end
54
+
55
+ def prepare_composite_effects(effects)
56
+ Composite::EffectsPreflight.new(
57
+ effects:,
58
+ baseline: composite_effect_baseline,
59
+ snapshotter: method(:snapshot_value)
60
+ ).call
61
+ end
62
+
63
+ def apply_composite_effects!(application)
64
+ Thread.handle_interrupt(Object => :never) do
65
+ @usage_mutex.synchronize do
66
+ @tool_results_mutex.synchronize do
67
+ validate_composite_effect_baseline!(application.baseline)
68
+ @usage_entries = application.usage_entries.dup
69
+ @total_tokens = application.total_tokens
70
+ @total_cost = application.total_cost
71
+ @tool_results = application.tool_results.dup
72
+ @ledger = application.ledger
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def composite_effect_baseline
79
+ @usage_mutex.synchronize do
80
+ @tool_results_mutex.synchronize do
81
+ build_composite_effect_baseline
82
+ end
83
+ end
84
+ end
85
+
86
+ def build_composite_effect_baseline
87
+ ledger = @ledger
88
+ Composite::EffectsBaseline.new(
89
+ usage_entries: @usage_entries.dup,
90
+ tool_results: snapshot_value(@tool_results),
91
+ total_tokens: @total_tokens,
92
+ total_cost: @total_cost,
93
+ ledger:,
94
+ budget_consumed: ledger ? ledger.consumed : {}
95
+ )
96
+ end
97
+
98
+ def validate_composite_effect_baseline!(expected)
99
+ current = build_composite_effect_baseline
100
+ return if current == expected
101
+
102
+ raise WorkflowError, "composite effect baseline changed before application"
103
+ end
104
+
105
+ def validate_composite_output!(transition, output)
106
+ if @composite_plan.kind == :parallel
107
+ agent = captured_agent(
108
+ @split_step_active_execution_authorization,
109
+ transition,
110
+ @composite_plan.branches.first.agent,
111
+ :agent
112
+ )
113
+ validate_data_volume!(output, agent)
114
+ run_output_guardrails(output, agent)
115
+ else
116
+ run_workflow_output_guardrails(output)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -5,28 +5,19 @@ module Smith
5
5
  module SplitStepPersistence
6
6
  module Execution
7
7
  def execute_prepared_step!
8
- authorization = ExecutionAuthorization
9
- .instance_method(:authorize_prepared_step_execution!)
10
- .bind_call(self)
11
- result = Execution.instance_method(:execute_authorized_prepared_step!).bind_call(self, authorization)
8
+ result = ExecutionAuthorizationIssuance
9
+ .instance_method(:with_prepared_step_execution_authorization)
10
+ .bind_call(self) do |authorization|
11
+ Execution.instance_method(:execute_authorized_prepared_step!).bind_call(self, authorization)
12
+ end
12
13
  result.step_snapshot
13
14
  end
14
15
 
15
16
  def execute_authorized_prepared_step!(authorization)
16
- authorization = validate_split_step_execution_authorization!(authorization)
17
- step = nil
18
- result = nil
19
- execution_thread = Thread.current
20
- execution_active = false
21
- begin
22
- activate_split_step_execution!(authorization, execution_thread)
23
- execution_active = true
17
+ perform_authorized_prepared_step_execution!(authorization) do |execution_thread|
24
18
  step = execute_claimed_split_step_transition!
25
- result = consume_split_step_execution_result!(execution_thread)
26
- ensure
27
- finish_split_step_execution!(step, authorization, execution_thread) if execution_active
19
+ [step, consume_split_step_execution_result!(execution_thread)]
28
20
  end
29
- result
30
21
  end
31
22
 
32
23
  def prepared_persisted_step?
@@ -38,26 +29,6 @@ module Smith
38
29
 
39
30
  private
40
31
 
41
- def activate_split_step_execution!(authorization, execution_thread)
42
- @split_step_mutex.synchronize do
43
- unless active_split_step_execution_authorization?(authorization)
44
- raise WorkflowError, "the prepared-step execution authorization is no longer active"
45
- end
46
-
47
- ensure_split_step_definition_current!
48
- ensure_prepared_split_step_transition_matches!
49
- PreparedStepExecutionAuthorization.instance_method(:activate_execution!)
50
- .bind_call(authorization, execution_thread)
51
-
52
- @split_step_active_execution_authorization = authorization
53
- @split_step_execution_result = nil
54
- @split_step_phase = :executing
55
- clear_split_step_execution_authorization!
56
- @split_step_execution_thread = execution_thread
57
- @split_step_advance_permit = true
58
- end
59
- end
60
-
61
32
  def restore_unverified_execution!(verification_token)
62
33
  @split_step_mutex.synchronize do
63
34
  next unless active_split_step_execution_verification?(verification_token)
@@ -75,23 +46,6 @@ module Smith
75
46
  raise WorkflowError, "prepared execution did not return the claimed transition"
76
47
  end
77
48
 
78
- def finish_split_step_execution!(step, authorization, execution_thread)
79
- @split_step_mutex.synchronize do
80
- next unless @split_step_phase == :executing &&
81
- @split_step_execution_thread.equal?(execution_thread)
82
-
83
- @split_step_execution_thread = nil
84
- @split_step_advance_permit = false
85
- @split_step_execution_previous_phase = nil
86
- @split_step_active_execution_authorization = nil
87
- @split_step_execution_result = nil
88
- @split_step_phase = step ? :executed : :attempted
89
- end
90
- ensure
91
- PreparedStepExecutionAuthorization.instance_method(:close_execution!)
92
- .bind_call(authorization, execution_thread)
93
- end
94
-
95
49
  def resolve_split_step_advance_transition
96
50
  @split_step_mutex.synchronize do
97
51
  unless @split_step_phase == :executing && @split_step_execution_thread.equal?(Thread.current)
@@ -4,36 +4,56 @@ module Smith
4
4
  class Workflow
5
5
  module SplitStepPersistence
6
6
  module ExecutionAuthorization
7
- def authorize_prepared_step_execution!
8
- verification_token = claim_split_step_execution_verification!
9
- authorized = false
10
- verify_claimed_split_step_execution!(verification_token)
11
- authorization = build_split_step_execution_authorization
12
- activate_split_step_execution_authorization!(authorization, verification_token)
13
- authorized = true
14
- authorization
15
- ensure
16
- restore_unverified_execution!(verification_token) if verification_token && !authorized
17
- end
18
-
19
7
  def release_prepared_step_execution!(authorization)
20
8
  authorization = validate_split_step_execution_authorization!(authorization)
21
- @split_step_mutex.synchronize do
22
- unless active_split_step_execution_authorization?(authorization)
23
- raise WorkflowError, "the prepared-step execution authorization is no longer active"
24
- end
9
+ Thread.handle_interrupt(Object => :never) do
10
+ @split_step_mutex.synchronize do
11
+ unless active_split_step_execution_authorization?(authorization)
12
+ raise WorkflowError, "the prepared-step execution authorization is no longer active"
13
+ end
25
14
 
26
- @split_step_phase = @split_step_execution_previous_phase
27
- clear_split_step_execution_authorization!
15
+ @split_step_phase = @split_step_execution_previous_phase
16
+ clear_split_step_execution_authorization!
17
+ end
18
+ PreparedStepExecutionAuthorization
19
+ .instance_method(:close_execution!)
20
+ .bind_call(authorization)
28
21
  end
29
- PreparedStepExecutionAuthorization
30
- .instance_method(:close_execution!)
31
- .bind_call(authorization)
32
22
  self
33
23
  end
34
24
 
35
25
  private
36
26
 
27
+ def authorize_claimed_prepared_step_execution!(verification_token)
28
+ ExecutionVerification
29
+ .instance_method(:verify_claimed_split_step_execution!)
30
+ .bind_call(self, verification_token)
31
+ authorization = ExecutionAuthorization
32
+ .instance_method(:build_split_step_execution_authorization)
33
+ .bind_call(self)
34
+ ExecutionAuthorization
35
+ .instance_method(:activate_split_step_execution_authorization!)
36
+ .bind_call(self, authorization, verification_token)
37
+ authorization
38
+ end
39
+
40
+ def claim_framework_execution_verification!
41
+ ExecutionVerification
42
+ .instance_method(:claim_split_step_execution_verification!)
43
+ .bind_call(self)
44
+ end
45
+
46
+ def restore_framework_execution_verification!(verification_token)
47
+ return unless verification_token
48
+
49
+ active = ExecutionAuthorization
50
+ .instance_method(:active_split_step_execution_verification?)
51
+ .bind_call(self, verification_token)
52
+ return unless active
53
+
54
+ Execution.instance_method(:restore_unverified_execution!).bind_call(self, verification_token)
55
+ end
56
+
37
57
  def build_split_step_execution_authorization
38
58
  PreparedStepExecutionAuthorization.new(
39
59
  prepared_step: @split_step_prepared_descriptor,