smith-agents 0.4.1 → 0.4.3

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -3
  3. data/README.md +69 -3
  4. data/UPSTREAM_PROPOSAL.md +19 -0
  5. data/docs/PATTERNS.md +85 -6
  6. data/lib/smith/agent/lifecycle.rb +11 -7
  7. data/lib/smith/agent/registry/introspection.rb +27 -0
  8. data/lib/smith/agent/registry.rb +22 -17
  9. data/lib/smith/agent/registry_binding.rb +43 -0
  10. data/lib/smith/agent.rb +3 -4
  11. data/lib/smith/doctor/checks/models_registry.rb +25 -14
  12. data/lib/smith/doctor/checks/persistence_capabilities.rb +37 -29
  13. data/lib/smith/version.rb +1 -1
  14. data/lib/smith/workflow/agent_result.rb +26 -0
  15. data/lib/smith/workflow/branch_env.rb +24 -0
  16. data/lib/smith/workflow/budget_integration.rb +27 -5
  17. data/lib/smith/workflow/deterministic_execution.rb +1 -1
  18. data/lib/smith/workflow/deterministic_step.rb +32 -4
  19. data/lib/smith/workflow/durability.rb +38 -12
  20. data/lib/smith/workflow/evaluator_optimizer.rb +18 -10
  21. data/lib/smith/workflow/event_integration.rb +3 -3
  22. data/lib/smith/workflow/execution.rb +8 -1
  23. data/lib/smith/workflow/fanout_execution.rb +121 -0
  24. data/lib/smith/workflow/graph/contract_helpers.rb +65 -0
  25. data/lib/smith/workflow/graph/fanout_contract.rb +140 -0
  26. data/lib/smith/workflow/graph/nested_readiness_diagnostics.rb +98 -0
  27. data/lib/smith/workflow/graph/optimization_contract.rb +96 -0
  28. data/lib/smith/workflow/graph/orchestration_contract.rb +83 -0
  29. data/lib/smith/workflow/graph/runtime_binding_diagnostic_builder.rb +124 -0
  30. data/lib/smith/workflow/graph/runtime_binding_diagnostics.rb +114 -0
  31. data/lib/smith/workflow/graph/runtime_readiness.rb +63 -0
  32. data/lib/smith/workflow/graph/runtime_readiness_metrics.rb +87 -0
  33. data/lib/smith/workflow/graph/runtime_readiness_report.rb +65 -0
  34. data/lib/smith/workflow/graph/targets.rb +5 -0
  35. data/lib/smith/workflow/graph/transition_diagnostics.rb +7 -0
  36. data/lib/smith/workflow/graph/transition_snapshot.rb +80 -18
  37. data/lib/smith/workflow/graph.rb +16 -1
  38. data/lib/smith/workflow/graph_dsl.rb +4 -0
  39. data/lib/smith/workflow/guardrail_integration.rb +45 -10
  40. data/lib/smith/workflow/nested_execution.rb +5 -4
  41. data/lib/smith/workflow/optimization_state.rb +13 -0
  42. data/lib/smith/workflow/orchestration_state.rb +13 -0
  43. data/lib/smith/workflow/orchestrator_worker.rb +5 -16
  44. data/lib/smith/workflow/parallel/cancellation.rb +9 -0
  45. data/lib/smith/workflow/parallel/cancellation_signal.rb +21 -0
  46. data/lib/smith/workflow/parallel.rb +12 -16
  47. data/lib/smith/workflow/parallel_execution.rb +5 -1
  48. data/lib/smith/workflow/persistence.rb +37 -6
  49. data/lib/smith/workflow/retry_execution.rb +52 -0
  50. data/lib/smith/workflow/router.rb +15 -4
  51. data/lib/smith/workflow/run_result.rb +54 -0
  52. data/lib/smith/workflow/transition.rb +260 -24
  53. data/lib/smith/workflow/usage_entry.rb +30 -0
  54. data/lib/smith/workflow/worker_execution.rb +13 -0
  55. data/lib/smith/workflow.rb +40 -136
  56. data/lib/smith.rb +12 -0
  57. metadata +24 -1
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-initializer"
4
+
5
+ module Smith
6
+ class Workflow
7
+ class Graph
8
+ class RuntimeBindingDiagnosticBuilder
9
+ extend Dry::Initializer
10
+
11
+ param :graph
12
+ option :binding
13
+
14
+ def to_diagnostic
15
+ registry_binding = Agent::Registry.binding_for(agent)
16
+ return unresolved_agent_diagnostic unless registry_binding
17
+
18
+ if registry_binding.fetch(:agent_class).nil?
19
+ return uninspectable_agent_diagnostic(registry_binding) if registry_binding.fetch(:call)
20
+
21
+ return invalid_agent_diagnostic(registry_binding.fetch(:raw_binding))
22
+ end
23
+
24
+ agent_class = registry_binding.fetch(:agent_class)
25
+ return if agent_class.model_configured?
26
+ return required_model_diagnostic if model_required?
27
+
28
+ model_warning
29
+ end
30
+
31
+ private
32
+
33
+ def unresolved_agent_diagnostic
34
+ Diagnostic.new(
35
+ severity: :error,
36
+ code: :unresolved_agent_binding,
37
+ transition: transition.name,
38
+ target: agent,
39
+ message: "Transition #{ref(transition.name)} references unregistered #{role} #{ref(agent)}.",
40
+ suggestion: "Load and register agent #{ref(agent)} before running workflow #{workflow_label}."
41
+ )
42
+ end
43
+
44
+ def invalid_agent_diagnostic(raw_binding)
45
+ Diagnostic.new(
46
+ severity: :error,
47
+ code: :invalid_agent_binding,
48
+ transition: transition.name,
49
+ target: agent,
50
+ message: "Transition #{ref(transition.name)} references #{role} #{ref(agent)}, " \
51
+ "but the registry binding is not a Smith::Agent subclass.",
52
+ suggestion: "Register #{ref(agent)} as a Smith::Agent subclass instead of " \
53
+ "#{raw_binding.class}."
54
+ )
55
+ end
56
+
57
+ def uninspectable_agent_diagnostic(registry_binding)
58
+ Diagnostic.new(
59
+ severity: model_required? ? :error : :warning,
60
+ code: :uninspectable_agent_binding,
61
+ transition: transition.name,
62
+ target: agent,
63
+ message: "Transition #{ref(transition.name)} references #{role} #{ref(agent)}, " \
64
+ "but the registry binding is lazy and cannot be inspected without resolving it.",
65
+ suggestion: "Register #{ref(registry_binding.fetch(:key))} as a concrete Smith::Agent subclass " \
66
+ "before relying on static runtime readiness."
67
+ )
68
+ end
69
+
70
+ def required_model_diagnostic
71
+ Diagnostic.new(
72
+ severity: :error,
73
+ code: :agent_without_required_model,
74
+ transition: transition.name,
75
+ target: agent,
76
+ message: "Transition #{ref(transition.name)} references #{role} #{ref(agent)}, " \
77
+ "but that role requires model output at runtime.",
78
+ suggestion: "Configure a model for agent #{ref(agent)} or move this transition to a " \
79
+ "deterministic/nested workflow pattern."
80
+ )
81
+ end
82
+
83
+ def model_warning
84
+ Diagnostic.new(
85
+ severity: :warning,
86
+ code: :agent_without_model,
87
+ transition: transition.name,
88
+ target: agent,
89
+ message: "Transition #{ref(transition.name)} references #{role} #{ref(agent)}, " \
90
+ "but the registered agent has no model configured.",
91
+ suggestion: "Configure a model for agent #{ref(agent)} if this transition should call a provider."
92
+ )
93
+ end
94
+
95
+ def agent
96
+ binding.fetch(:agent)
97
+ end
98
+
99
+ def transition
100
+ binding.fetch(:transition)
101
+ end
102
+
103
+ def role
104
+ binding.fetch(:role)
105
+ end
106
+
107
+ def model_required?
108
+ binding.fetch(:requires_model)
109
+ end
110
+
111
+ def ref(value)
112
+ Reference.format(value)
113
+ end
114
+
115
+ def workflow_label
116
+ name = graph.workflow_class.name
117
+ return name if name && !name.empty?
118
+
119
+ graph.workflow_class.inspect
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-initializer"
4
+
5
+ module Smith
6
+ class Workflow
7
+ class Graph
8
+ class RuntimeBindingDiagnostics
9
+ extend Dry::Initializer
10
+
11
+ param :graph
12
+
13
+ def to_a
14
+ agent_bindings.filter_map do |binding|
15
+ RuntimeBindingDiagnosticBuilder.new(graph, binding: binding).to_diagnostic
16
+ end
17
+ end
18
+
19
+ def agent_bindings
20
+ @agent_bindings ||= graph.transitions.values.flat_map do |transition|
21
+ bindings_for_transition(transition)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def bindings_for_transition(transition)
28
+ [
29
+ primary_agent_binding(transition),
30
+ *optimizer_agent_bindings(transition),
31
+ *orchestrator_agent_bindings(transition),
32
+ *fanout_agent_bindings(transition)
33
+ ].compact
34
+ end
35
+
36
+ def primary_agent_binding(transition)
37
+ return unless transition.agent_name
38
+
39
+ {
40
+ transition: transition,
41
+ role: agent_role_for(transition),
42
+ agent: transition.agent_name,
43
+ requires_model: transition.routed?
44
+ }
45
+ end
46
+
47
+ def optimizer_agent_bindings(transition)
48
+ config = transition.optimization_config
49
+ return [] unless config
50
+
51
+ [
52
+ {
53
+ transition: transition,
54
+ role: "optimizer generator",
55
+ agent: config.fetch(:generator),
56
+ requires_model: true
57
+ },
58
+ {
59
+ transition: transition,
60
+ role: "optimizer evaluator",
61
+ agent: config.fetch(:evaluator),
62
+ requires_model: true
63
+ }
64
+ ]
65
+ end
66
+
67
+ def orchestrator_agent_bindings(transition)
68
+ config = transition.orchestrator_config
69
+ return [] unless config
70
+
71
+ [
72
+ {
73
+ transition: transition,
74
+ role: "orchestrator",
75
+ agent: config.fetch(:orchestrator),
76
+ requires_model: true
77
+ },
78
+ {
79
+ transition: transition,
80
+ role: "worker",
81
+ agent: config.fetch(:worker),
82
+ requires_model: true
83
+ }
84
+ ]
85
+ end
86
+
87
+ def fanout_agent_bindings(transition)
88
+ branches = transition.fanout_config&.fetch(:branches, nil)
89
+ return [] unless branches
90
+
91
+ branches.map do |branch, agent|
92
+ {
93
+ transition: transition,
94
+ role: "fan-out branch #{ref(branch)}",
95
+ agent: agent,
96
+ requires_model: false
97
+ }
98
+ end
99
+ end
100
+
101
+ def agent_role_for(transition)
102
+ return "router agent" if transition.routed?
103
+ return "parallel agent" if transition.parallel?
104
+
105
+ "agent"
106
+ end
107
+
108
+ def ref(value)
109
+ Reference.format(value)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-initializer"
4
+
5
+ module Smith
6
+ class Workflow
7
+ class Graph
8
+ class RuntimeReadiness
9
+ extend Dry::Initializer
10
+
11
+ param :graph
12
+ option :visited, default: proc {}
13
+
14
+ def report
15
+ RuntimeReadinessReport.new(
16
+ workflow_class: workflow_label(graph.workflow_class),
17
+ topology_report: topology_report,
18
+ runtime_diagnostics: runtime_diagnostics,
19
+ metrics: runtime_metrics
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def topology_report
26
+ @topology_report ||= graph.validate
27
+ end
28
+
29
+ def runtime_diagnostics
30
+ @runtime_diagnostics ||= [
31
+ *binding_diagnostics.to_a,
32
+ *nested_diagnostics.to_a
33
+ ]
34
+ end
35
+
36
+ def binding_diagnostics
37
+ @binding_diagnostics ||= RuntimeBindingDiagnostics.new(graph)
38
+ end
39
+
40
+ def nested_diagnostics
41
+ @nested_diagnostics ||= NestedReadinessDiagnostics.new(graph, visited: visited)
42
+ end
43
+
44
+ def runtime_metrics
45
+ RuntimeReadinessMetrics.new(
46
+ graph,
47
+ topology_report: topology_report,
48
+ runtime_diagnostics: runtime_diagnostics,
49
+ agent_bindings: binding_diagnostics.agent_bindings,
50
+ nested_reports: nested_diagnostics.child_reports
51
+ ).to_h
52
+ end
53
+
54
+ def workflow_label(workflow_class)
55
+ name = workflow_class.name
56
+ return name if name && !name.empty?
57
+
58
+ workflow_class.inspect
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-initializer"
4
+
5
+ module Smith
6
+ class Workflow
7
+ class Graph
8
+ class RuntimeReadinessMetrics
9
+ extend Dry::Initializer
10
+
11
+ param :graph
12
+ option :topology_report
13
+ option :runtime_diagnostics
14
+ option :agent_bindings
15
+ option :nested_reports, default: proc { [] }
16
+
17
+ def to_h
18
+ core_metrics.merge(
19
+ binding_metrics,
20
+ nested_workflow_metrics,
21
+ fanout_metrics
22
+ )
23
+ end
24
+
25
+ private
26
+
27
+ def core_metrics
28
+ {
29
+ topology_status: topology_report.status,
30
+ unresolved_agent_bindings_count: diagnostic_count(:unresolved_agent_binding),
31
+ invalid_agent_bindings_count: diagnostic_count(:invalid_agent_binding),
32
+ uninspectable_agent_bindings_count: diagnostic_count(:uninspectable_agent_binding),
33
+ modelless_agent_bindings_count: diagnostic_count(:agent_without_model),
34
+ required_model_missing_count: diagnostic_count(:agent_without_required_model)
35
+ }
36
+ end
37
+
38
+ def binding_metrics
39
+ {
40
+ direct_agent_bindings_count: agent_bindings.length,
41
+ agent_bindings_count: agent_bindings.length + nested_metric_sum(:agent_bindings_count)
42
+ }
43
+ end
44
+
45
+ def nested_workflow_metrics
46
+ {
47
+ direct_nested_workflow_count: direct_nested_workflow_count,
48
+ nested_workflow_count: direct_nested_workflow_count + nested_metric_sum(:nested_workflow_count)
49
+ }
50
+ end
51
+
52
+ def fanout_metrics
53
+ {
54
+ direct_fanout_groups_count: fanout_transitions.length,
55
+ fanout_groups_count: fanout_transitions.length + nested_metric_sum(:fanout_groups_count),
56
+ direct_fanout_branches_count: fanout_branch_count,
57
+ fanout_branches_count: fanout_branch_count + nested_metric_sum(:fanout_branches_count)
58
+ }
59
+ end
60
+
61
+ def nested_metric_sum(key)
62
+ nested_reports.sum { |report| report.metrics.fetch(key, 0) }
63
+ end
64
+
65
+ def diagnostic_count(code)
66
+ runtime_diagnostics.count do |diagnostic|
67
+ diagnostic.code.to_s.end_with?(code.to_s)
68
+ end
69
+ end
70
+
71
+ def fanout_branch_count
72
+ fanout_transitions.sum do |transition|
73
+ transition.fanout_config.fetch(:branches).length
74
+ end
75
+ end
76
+
77
+ def fanout_transitions
78
+ @fanout_transitions ||= graph.transitions.values.select(&:fanout?)
79
+ end
80
+
81
+ def direct_nested_workflow_count
82
+ graph.transitions.values.count(&:nested?)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-initializer"
4
+
5
+ module Smith
6
+ class Workflow
7
+ class Graph
8
+ class RuntimeReadinessReport
9
+ extend Dry::Initializer
10
+
11
+ option :workflow_class
12
+ option :topology_report
13
+ option :runtime_diagnostics
14
+ option :metrics
15
+
16
+ def ready?
17
+ errors.empty?
18
+ end
19
+ alias valid? ready?
20
+
21
+ def status
22
+ return :not_ready if errors.any?
23
+ return :warning if warnings.any?
24
+
25
+ :ready
26
+ end
27
+
28
+ def topology_status
29
+ topology_report.status
30
+ end
31
+
32
+ def diagnostics
33
+ topology_report.diagnostics + runtime_diagnostics
34
+ end
35
+
36
+ def errors
37
+ diagnostics.select { |diagnostic| diagnostic.severity == :error }
38
+ end
39
+
40
+ def warnings
41
+ diagnostics.select { |diagnostic| diagnostic.severity == :warning }
42
+ end
43
+
44
+ def suggestions
45
+ diagnostics.map(&:suggestion).compact.uniq
46
+ end
47
+
48
+ def to_h
49
+ {
50
+ status: status,
51
+ workflow_class: workflow_class,
52
+ topology_status: topology_status,
53
+ ready: ready?,
54
+ diagnostics: diagnostics.map(&:to_h),
55
+ runtime_diagnostics: runtime_diagnostics.map(&:to_h),
56
+ topology_diagnostics: topology_report.diagnostics.map(&:to_h),
57
+ suggestions: suggestions,
58
+ metrics: metrics,
59
+ graph: topology_report.to_h
60
+ }
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -21,6 +21,7 @@ module Smith
21
21
  def names
22
22
  names = [transition.success_transition, transition.failure_transition]
23
23
  names.concat(router_names)
24
+ names.concat(deterministic_route_names)
24
25
  names.compact.uniq
25
26
  end
26
27
 
@@ -32,6 +33,10 @@ module Smith
32
33
  transition.router_config.fetch(:fallback)
33
34
  ]
34
35
  end
36
+
37
+ def deterministic_route_names
38
+ transition.deterministic_routes || []
39
+ end
35
40
  end
36
41
  end
37
42
  end
@@ -16,6 +16,7 @@ module Smith
16
16
  transition_target_diagnostic(transition, :success_transition, transition.success_transition),
17
17
  transition_target_diagnostic(transition, :failure_transition, transition.failure_transition),
18
18
  *router_target_diagnostics(transition),
19
+ *deterministic_route_diagnostics(transition),
19
20
  *target_state_mismatch_diagnostics(transition)
20
21
  ].compact
21
22
  end
@@ -45,6 +46,12 @@ module Smith
45
46
  end
46
47
  end
47
48
 
49
+ def deterministic_route_diagnostics(transition)
50
+ Array(transition.deterministic_routes).filter_map do |target|
51
+ transition_target_diagnostic(transition, :deterministic_route, target)
52
+ end
53
+ end
54
+
48
55
  def target_state_mismatch_diagnostics(transition)
49
56
  Targets.for(transition).filter_map do |target_name|
50
57
  target = graph.transitions[target_name]
@@ -1,31 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dry-initializer"
4
+
5
+ require_relative "fanout_contract"
6
+ require_relative "optimization_contract"
7
+ require_relative "orchestration_contract"
8
+
3
9
  module Smith
4
10
  class Workflow
5
11
  class Graph
6
12
  class TransitionSnapshot
13
+ extend Dry::Initializer
14
+
7
15
  KINDS = [
8
16
  %i[deterministic deterministic?],
9
17
  %i[router routed?],
10
18
  %i[nested_workflow nested?],
11
19
  %i[optimizer optimized?],
12
20
  %i[orchestrator orchestrated?],
21
+ %i[fanout fanout?],
13
22
  %i[parallel parallel?]
14
23
  ].freeze
15
24
 
16
- attr_reader :name, :from, :to, :kind, :success_transition, :failure_transition, :routes, :fallback
25
+ option :name
26
+ option :from
27
+ option :to
28
+ option :kind
29
+ option :success_transition, default: proc {}
30
+ option :failure_transition, default: proc {}
31
+ option :routes, default: proc {}
32
+ option :fallback, default: proc {}
33
+ option :deterministic_routes, default: proc {}
34
+ option :fanout_branches, default: proc {}
35
+ option :fanout, default: proc {}
36
+ option :optimization, default: proc {}
37
+ option :orchestration, default: proc {}
38
+ option :retry_policy, default: proc {}
17
39
 
18
- def self.from_transition(transition)
19
- new(
40
+ def self.from_transition(transition, workflow_class: nil)
41
+ new(**attributes_for(transition, workflow_class: workflow_class))
42
+ end
43
+
44
+ def self.attributes_for(transition, workflow_class:)
45
+ {
20
46
  name: transition.name,
21
47
  from: transition.from,
22
48
  to: transition.to,
23
- kind: kind_for(transition),
49
+ kind: kind_for(transition)
50
+ }.merge(routing_attributes(transition), contract_attributes(transition, workflow_class: workflow_class))
51
+ end
52
+
53
+ def self.routing_attributes(transition)
54
+ {
24
55
  success_transition: transition.success_transition,
25
56
  failure_transition: transition.failure_transition,
26
57
  routes: transition.router_config&.fetch(:routes, nil),
27
- fallback: transition.router_config&.fetch(:fallback, nil)
28
- )
58
+ fallback: transition.router_config&.fetch(:fallback, nil),
59
+ deterministic_routes: transition.deterministic_routes,
60
+ fanout_branches: transition.fanout_config&.fetch(:branches, nil)
61
+ }
62
+ end
63
+
64
+ def self.contract_attributes(transition, workflow_class:)
65
+ {
66
+ fanout: fanout_for(transition, workflow_class: workflow_class),
67
+ optimization: optimization_for(transition, workflow_class: workflow_class),
68
+ orchestration: orchestration_for(transition, workflow_class: workflow_class),
69
+ retry_policy: retry_policy_for(transition)
70
+ }
71
+ end
72
+
73
+ def self.fanout_for(transition, workflow_class:)
74
+ FanoutContract.from_transition(transition, workflow_class: workflow_class)
75
+ end
76
+
77
+ def self.optimization_for(transition, workflow_class:)
78
+ OptimizationContract.from_transition(transition, workflow_class: workflow_class)
79
+ end
80
+
81
+ def self.orchestration_for(transition, workflow_class:)
82
+ OrchestrationContract.from_transition(transition, workflow_class: workflow_class)
83
+ end
84
+
85
+ def self.retry_policy_for(transition)
86
+ config = transition.retry_config
87
+ return unless config
88
+
89
+ {
90
+ attempts: config.fetch(:attempts),
91
+ error_classes: config.fetch(:error_classes).map(&:name),
92
+ backoff: config.fetch(:backoff),
93
+ max_delay: config[:max_delay],
94
+ jitter: config.fetch(:jitter)
95
+ }.compact
29
96
  end
30
97
 
31
98
  def self.kind_for(transition)
@@ -36,17 +103,6 @@ module Smith
36
103
  :noop
37
104
  end
38
105
 
39
- def initialize(**attributes)
40
- @name = attributes.fetch(:name)
41
- @from = attributes.fetch(:from)
42
- @to = attributes.fetch(:to)
43
- @kind = attributes.fetch(:kind)
44
- @success_transition = attributes[:success_transition]
45
- @failure_transition = attributes[:failure_transition]
46
- @routes = attributes[:routes]
47
- @fallback = attributes[:fallback]
48
- end
49
-
50
106
  def to_h
51
107
  {
52
108
  name: name,
@@ -56,7 +112,13 @@ module Smith
56
112
  success_transition: success_transition,
57
113
  failure_transition: failure_transition,
58
114
  routes: routes,
59
- fallback: fallback
115
+ fallback: fallback,
116
+ deterministic_routes: deterministic_routes,
117
+ fanout_branches: fanout_branches,
118
+ fanout: fanout,
119
+ optimization: optimization,
120
+ orchestration: orchestration,
121
+ retry_policy: retry_policy
60
122
  }.compact
61
123
  end
62
124
  end
@@ -16,8 +16,14 @@ module Smith
16
16
  Validator.new(self).report
17
17
  end
18
18
 
19
+ def runtime_readiness(visited: nil)
20
+ RuntimeReadiness.new(self, visited: visited).report
21
+ end
22
+
19
23
  def transition_snapshots
20
- transitions.values.map { |transition| TransitionSnapshot.from_transition(transition) }
24
+ transitions.values.map do |transition|
25
+ TransitionSnapshot.from_transition(transition, workflow_class: workflow_class)
26
+ end
21
27
  end
22
28
  end
23
29
  end
@@ -29,8 +35,17 @@ require_relative "graph/state_diagnostics"
29
35
  require_relative "graph/reachability"
30
36
  require_relative "graph/reachability_diagnostics"
31
37
  require_relative "graph/metrics"
38
+ require_relative "graph/nested_readiness_diagnostics"
32
39
  require_relative "graph/report"
40
+ require_relative "graph/runtime_binding_diagnostic_builder"
41
+ require_relative "graph/runtime_binding_diagnostics"
42
+ require_relative "graph/runtime_readiness_report"
43
+ require_relative "graph/runtime_readiness_metrics"
33
44
  require_relative "graph/targets"
45
+ require_relative "graph/fanout_contract"
46
+ require_relative "graph/optimization_contract"
47
+ require_relative "graph/orchestration_contract"
34
48
  require_relative "graph/transition_snapshot"
35
49
  require_relative "graph/transition_diagnostics"
36
50
  require_relative "graph/validator"
51
+ require_relative "graph/runtime_readiness"
@@ -14,5 +14,9 @@ module Smith
14
14
  def self.validate_graph
15
15
  graph.validate
16
16
  end
17
+
18
+ def self.runtime_readiness
19
+ graph.runtime_readiness
20
+ end
17
21
  end
18
22
  end