decision_agent 0.3.0 → 1.1.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.
Files changed (220) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +234 -14
  3. data/lib/decision_agent/ab_testing/ab_test.rb +5 -1
  4. data/lib/decision_agent/ab_testing/ab_test_assignment.rb +2 -0
  5. data/lib/decision_agent/ab_testing/ab_test_manager.rb +2 -0
  6. data/lib/decision_agent/ab_testing/ab_testing_agent.rb +2 -0
  7. data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +2 -13
  8. data/lib/decision_agent/ab_testing/storage/adapter.rb +2 -0
  9. data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +2 -0
  10. data/lib/decision_agent/agent.rb +78 -9
  11. data/lib/decision_agent/audit/adapter.rb +2 -0
  12. data/lib/decision_agent/audit/logger_adapter.rb +2 -0
  13. data/lib/decision_agent/audit/null_adapter.rb +2 -0
  14. data/lib/decision_agent/auth/access_audit_logger.rb +2 -0
  15. data/lib/decision_agent/auth/authenticator.rb +2 -0
  16. data/lib/decision_agent/auth/password_reset_manager.rb +2 -0
  17. data/lib/decision_agent/auth/password_reset_token.rb +2 -0
  18. data/lib/decision_agent/auth/permission.rb +2 -0
  19. data/lib/decision_agent/auth/permission_checker.rb +2 -0
  20. data/lib/decision_agent/auth/rbac_adapter.rb +2 -0
  21. data/lib/decision_agent/auth/rbac_config.rb +2 -0
  22. data/lib/decision_agent/auth/role.rb +2 -0
  23. data/lib/decision_agent/auth/session.rb +2 -0
  24. data/lib/decision_agent/auth/session_manager.rb +2 -0
  25. data/lib/decision_agent/auth/user.rb +2 -0
  26. data/lib/decision_agent/context.rb +14 -0
  27. data/lib/decision_agent/decision.rb +113 -4
  28. data/lib/decision_agent/dmn/adapter.rb +2 -0
  29. data/lib/decision_agent/dmn/cache.rb +2 -2
  30. data/lib/decision_agent/dmn/decision_graph.rb +7 -7
  31. data/lib/decision_agent/dmn/decision_tree.rb +16 -8
  32. data/lib/decision_agent/dmn/errors.rb +2 -0
  33. data/lib/decision_agent/dmn/exporter.rb +2 -0
  34. data/lib/decision_agent/dmn/feel/evaluator.rb +130 -114
  35. data/lib/decision_agent/dmn/feel/functions.rb +2 -0
  36. data/lib/decision_agent/dmn/feel/parser.rb +2 -0
  37. data/lib/decision_agent/dmn/feel/simple_parser.rb +98 -77
  38. data/lib/decision_agent/dmn/feel/transformer.rb +56 -102
  39. data/lib/decision_agent/dmn/feel/types.rb +2 -0
  40. data/lib/decision_agent/dmn/importer.rb +2 -0
  41. data/lib/decision_agent/dmn/model.rb +2 -4
  42. data/lib/decision_agent/dmn/parser.rb +2 -0
  43. data/lib/decision_agent/dmn/testing.rb +3 -2
  44. data/lib/decision_agent/dmn/validator.rb +5 -3
  45. data/lib/decision_agent/dmn/visualizer.rb +7 -6
  46. data/lib/decision_agent/dsl/condition_evaluator.rb +242 -1375
  47. data/lib/decision_agent/dsl/helpers/cache_helpers.rb +82 -0
  48. data/lib/decision_agent/dsl/helpers/comparison_helpers.rb +98 -0
  49. data/lib/decision_agent/dsl/helpers/date_helpers.rb +91 -0
  50. data/lib/decision_agent/dsl/helpers/geospatial_helpers.rb +85 -0
  51. data/lib/decision_agent/dsl/helpers/operator_evaluation_helpers.rb +160 -0
  52. data/lib/decision_agent/dsl/helpers/parameter_parsing_helpers.rb +206 -0
  53. data/lib/decision_agent/dsl/helpers/template_helpers.rb +39 -0
  54. data/lib/decision_agent/dsl/helpers/utility_helpers.rb +45 -0
  55. data/lib/decision_agent/dsl/operators/base.rb +70 -0
  56. data/lib/decision_agent/dsl/operators/basic_comparison_operators.rb +80 -0
  57. data/lib/decision_agent/dsl/operators/collection_operators.rb +60 -0
  58. data/lib/decision_agent/dsl/operators/date_arithmetic_operators.rb +206 -0
  59. data/lib/decision_agent/dsl/operators/date_time_operators.rb +47 -0
  60. data/lib/decision_agent/dsl/operators/duration_operators.rb +149 -0
  61. data/lib/decision_agent/dsl/operators/financial_operators.rb +237 -0
  62. data/lib/decision_agent/dsl/operators/geospatial_operators.rb +106 -0
  63. data/lib/decision_agent/dsl/operators/mathematical_operators.rb +234 -0
  64. data/lib/decision_agent/dsl/operators/moving_window_operators.rb +135 -0
  65. data/lib/decision_agent/dsl/operators/numeric_operators.rb +120 -0
  66. data/lib/decision_agent/dsl/operators/rate_operators.rb +65 -0
  67. data/lib/decision_agent/dsl/operators/statistical_aggregations.rb +187 -0
  68. data/lib/decision_agent/dsl/operators/string_aggregations.rb +84 -0
  69. data/lib/decision_agent/dsl/operators/string_operators.rb +72 -0
  70. data/lib/decision_agent/dsl/operators/time_component_operators.rb +72 -0
  71. data/lib/decision_agent/dsl/rule_parser.rb +2 -0
  72. data/lib/decision_agent/dsl/schema_validator.rb +37 -14
  73. data/lib/decision_agent/errors.rb +2 -0
  74. data/lib/decision_agent/evaluation.rb +14 -2
  75. data/lib/decision_agent/evaluators/base.rb +2 -0
  76. data/lib/decision_agent/evaluators/dmn_evaluator.rb +108 -19
  77. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +56 -11
  78. data/lib/decision_agent/evaluators/static_evaluator.rb +2 -0
  79. data/lib/decision_agent/explainability/condition_trace.rb +85 -0
  80. data/lib/decision_agent/explainability/explainability_result.rb +50 -0
  81. data/lib/decision_agent/explainability/rule_trace.rb +41 -0
  82. data/lib/decision_agent/explainability/trace_collector.rb +26 -0
  83. data/lib/decision_agent/monitoring/alert_manager.rb +7 -16
  84. data/lib/decision_agent/monitoring/dashboard_server.rb +383 -250
  85. data/lib/decision_agent/monitoring/metrics_collector.rb +2 -0
  86. data/lib/decision_agent/monitoring/monitored_agent.rb +2 -0
  87. data/lib/decision_agent/monitoring/prometheus_exporter.rb +3 -1
  88. data/lib/decision_agent/replay/replay.rb +4 -1
  89. data/lib/decision_agent/scoring/base.rb +2 -0
  90. data/lib/decision_agent/scoring/consensus.rb +2 -0
  91. data/lib/decision_agent/scoring/max_weight.rb +2 -0
  92. data/lib/decision_agent/scoring/threshold.rb +2 -0
  93. data/lib/decision_agent/scoring/weighted_average.rb +2 -0
  94. data/lib/decision_agent/simulation/errors.rb +20 -0
  95. data/lib/decision_agent/simulation/impact_analyzer.rb +500 -0
  96. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +638 -0
  97. data/lib/decision_agent/simulation/replay_engine.rb +488 -0
  98. data/lib/decision_agent/simulation/scenario_engine.rb +320 -0
  99. data/lib/decision_agent/simulation/scenario_library.rb +165 -0
  100. data/lib/decision_agent/simulation/shadow_test_engine.rb +274 -0
  101. data/lib/decision_agent/simulation/what_if_analyzer.rb +1008 -0
  102. data/lib/decision_agent/simulation.rb +19 -0
  103. data/lib/decision_agent/testing/batch_test_importer.rb +6 -2
  104. data/lib/decision_agent/testing/batch_test_runner.rb +5 -2
  105. data/lib/decision_agent/testing/test_coverage_analyzer.rb +2 -0
  106. data/lib/decision_agent/testing/test_result_comparator.rb +2 -0
  107. data/lib/decision_agent/testing/test_scenario.rb +2 -0
  108. data/lib/decision_agent/version.rb +3 -1
  109. data/lib/decision_agent/versioning/activerecord_adapter.rb +108 -43
  110. data/lib/decision_agent/versioning/adapter.rb +9 -0
  111. data/lib/decision_agent/versioning/file_storage_adapter.rb +19 -6
  112. data/lib/decision_agent/versioning/version_manager.rb +9 -0
  113. data/lib/decision_agent/web/dmn_editor/serialization.rb +74 -0
  114. data/lib/decision_agent/web/dmn_editor/xml_builder.rb +107 -0
  115. data/lib/decision_agent/web/dmn_editor.rb +8 -67
  116. data/lib/decision_agent/web/middleware/auth_middleware.rb +2 -0
  117. data/lib/decision_agent/web/middleware/permission_middleware.rb +3 -1
  118. data/lib/decision_agent/web/public/app.js +186 -26
  119. data/lib/decision_agent/web/public/batch_testing.html +80 -6
  120. data/lib/decision_agent/web/public/dmn-editor.html +2 -2
  121. data/lib/decision_agent/web/public/dmn-editor.js +74 -8
  122. data/lib/decision_agent/web/public/index.html +69 -3
  123. data/lib/decision_agent/web/public/login.html +1 -1
  124. data/lib/decision_agent/web/public/sample_batch.csv +11 -0
  125. data/lib/decision_agent/web/public/sample_impact.csv +11 -0
  126. data/lib/decision_agent/web/public/sample_replay.csv +11 -0
  127. data/lib/decision_agent/web/public/sample_rules.json +118 -0
  128. data/lib/decision_agent/web/public/sample_shadow.csv +11 -0
  129. data/lib/decision_agent/web/public/sample_whatif.csv +11 -0
  130. data/lib/decision_agent/web/public/simulation.html +146 -0
  131. data/lib/decision_agent/web/public/simulation_impact.html +495 -0
  132. data/lib/decision_agent/web/public/simulation_replay.html +547 -0
  133. data/lib/decision_agent/web/public/simulation_shadow.html +561 -0
  134. data/lib/decision_agent/web/public/simulation_whatif.html +549 -0
  135. data/lib/decision_agent/web/public/styles.css +65 -0
  136. data/lib/decision_agent/web/public/users.html +1 -1
  137. data/lib/decision_agent/web/rack_helpers.rb +106 -0
  138. data/lib/decision_agent/web/rack_request_helpers.rb +196 -0
  139. data/lib/decision_agent/web/server.rb +2126 -1374
  140. data/lib/decision_agent.rb +19 -1
  141. data/lib/generators/decision_agent/install/install_generator.rb +2 -0
  142. data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +2 -0
  143. data/lib/generators/decision_agent/install/templates/ab_test_model.rb +2 -0
  144. data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +2 -0
  145. data/lib/generators/decision_agent/install/templates/migration.rb +2 -0
  146. data/lib/generators/decision_agent/install/templates/rule.rb +2 -0
  147. data/lib/generators/decision_agent/install/templates/rule_version.rb +2 -0
  148. metadata +103 -89
  149. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  150. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  151. data/spec/ab_testing/ab_test_spec.rb +0 -270
  152. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
  153. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  154. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  155. data/spec/activerecord_thread_safety_spec.rb +0 -553
  156. data/spec/advanced_operators_spec.rb +0 -3150
  157. data/spec/agent_spec.rb +0 -289
  158. data/spec/api_contract_spec.rb +0 -430
  159. data/spec/audit_adapters_spec.rb +0 -92
  160. data/spec/auth/access_audit_logger_spec.rb +0 -394
  161. data/spec/auth/authenticator_spec.rb +0 -112
  162. data/spec/auth/password_reset_spec.rb +0 -294
  163. data/spec/auth/permission_checker_spec.rb +0 -207
  164. data/spec/auth/permission_spec.rb +0 -73
  165. data/spec/auth/rbac_adapter_spec.rb +0 -778
  166. data/spec/auth/rbac_config_spec.rb +0 -82
  167. data/spec/auth/role_spec.rb +0 -51
  168. data/spec/auth/session_manager_spec.rb +0 -172
  169. data/spec/auth/session_spec.rb +0 -112
  170. data/spec/auth/user_spec.rb +0 -130
  171. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  172. data/spec/context_spec.rb +0 -127
  173. data/spec/decision_agent_spec.rb +0 -96
  174. data/spec/decision_spec.rb +0 -423
  175. data/spec/dmn/decision_graph_spec.rb +0 -282
  176. data/spec/dmn/decision_tree_spec.rb +0 -203
  177. data/spec/dmn/feel/errors_spec.rb +0 -18
  178. data/spec/dmn/feel/functions_spec.rb +0 -400
  179. data/spec/dmn/feel/simple_parser_spec.rb +0 -274
  180. data/spec/dmn/feel/types_spec.rb +0 -176
  181. data/spec/dmn/feel_parser_spec.rb +0 -489
  182. data/spec/dmn/hit_policy_spec.rb +0 -202
  183. data/spec/dmn/integration_spec.rb +0 -226
  184. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  185. data/spec/dsl_validation_spec.rb +0 -648
  186. data/spec/edge_cases_spec.rb +0 -353
  187. data/spec/evaluation_spec.rb +0 -364
  188. data/spec/evaluation_validator_spec.rb +0 -165
  189. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  190. data/spec/examples.txt +0 -1909
  191. data/spec/fixtures/dmn/complex_decision.dmn +0 -81
  192. data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
  193. data/spec/fixtures/dmn/simple_decision.dmn +0 -40
  194. data/spec/issue_verification_spec.rb +0 -759
  195. data/spec/json_rule_evaluator_spec.rb +0 -587
  196. data/spec/monitoring/alert_manager_spec.rb +0 -378
  197. data/spec/monitoring/metrics_collector_spec.rb +0 -501
  198. data/spec/monitoring/monitored_agent_spec.rb +0 -225
  199. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  200. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  201. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  202. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  203. data/spec/performance_optimizations_spec.rb +0 -493
  204. data/spec/replay_edge_cases_spec.rb +0 -699
  205. data/spec/replay_spec.rb +0 -210
  206. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  207. data/spec/scoring_spec.rb +0 -225
  208. data/spec/spec_helper.rb +0 -60
  209. data/spec/testing/batch_test_importer_spec.rb +0 -693
  210. data/spec/testing/batch_test_runner_spec.rb +0 -307
  211. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  212. data/spec/testing/test_result_comparator_spec.rb +0 -392
  213. data/spec/testing/test_scenario_spec.rb +0 -113
  214. data/spec/thread_safety_spec.rb +0 -490
  215. data/spec/thread_safety_spec.rb.broken +0 -878
  216. data/spec/versioning/adapter_spec.rb +0 -156
  217. data/spec/versioning_spec.rb +0 -1030
  218. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  219. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  220. data/spec/web_ui_rack_spec.rb +0 -2134
data/spec/agent_spec.rb DELETED
@@ -1,289 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Agent do
4
- describe "#initialize" do
5
- it "requires at least one evaluator" do
6
- expect do
7
- DecisionAgent::Agent.new(evaluators: [])
8
- end.to raise_error(DecisionAgent::InvalidConfigurationError, /at least one evaluator/i)
9
- end
10
-
11
- it "validates evaluators respond to #evaluate" do
12
- invalid_evaluator = Object.new
13
-
14
- expect do
15
- DecisionAgent::Agent.new(evaluators: [invalid_evaluator])
16
- end.to raise_error(DecisionAgent::InvalidEvaluatorError)
17
- end
18
-
19
- it "validates scoring strategy responds to #score" do
20
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
21
- invalid_strategy = Object.new
22
-
23
- expect do
24
- DecisionAgent::Agent.new(
25
- evaluators: [evaluator],
26
- scoring_strategy: invalid_strategy
27
- )
28
- end.to raise_error(DecisionAgent::InvalidScoringStrategyError)
29
- end
30
-
31
- it "validates audit adapter responds to #record" do
32
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
33
- invalid_adapter = Object.new
34
-
35
- expect do
36
- DecisionAgent::Agent.new(
37
- evaluators: [evaluator],
38
- audit_adapter: invalid_adapter
39
- )
40
- end.to raise_error(DecisionAgent::InvalidAuditAdapterError)
41
- end
42
-
43
- it "uses defaults when optional parameters are omitted" do
44
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
45
-
46
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
47
-
48
- expect(agent.scoring_strategy).to be_a(DecisionAgent::Scoring::WeightedAverage)
49
- expect(agent.audit_adapter).to be_a(DecisionAgent::Audit::NullAdapter)
50
- end
51
-
52
- it "enables validation by default in non-production environments" do
53
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
54
- original_env = ENV.fetch("RAILS_ENV", nil)
55
- ENV["RAILS_ENV"] = "development"
56
-
57
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
58
- # Validation should be enabled (we can't directly test this, but we can test behavior)
59
- # If validation is enabled, invalid evaluations would raise errors
60
- expect(agent).to be_a(DecisionAgent::Agent)
61
-
62
- ENV["RAILS_ENV"] = original_env
63
- end
64
-
65
- it "disables validation in production by default" do
66
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
67
- original_env = ENV.fetch("RAILS_ENV", nil)
68
- ENV["RAILS_ENV"] = "production"
69
-
70
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
71
- expect(agent).to be_a(DecisionAgent::Agent)
72
-
73
- ENV["RAILS_ENV"] = original_env
74
- end
75
-
76
- it "allows explicit validation control" do
77
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
78
-
79
- agent_with_validation = DecisionAgent::Agent.new(
80
- evaluators: [evaluator],
81
- validate_evaluations: true
82
- )
83
- expect(agent_with_validation).to be_a(DecisionAgent::Agent)
84
-
85
- agent_without_validation = DecisionAgent::Agent.new(
86
- evaluators: [evaluator],
87
- validate_evaluations: false
88
- )
89
- expect(agent_without_validation).to be_a(DecisionAgent::Agent)
90
- end
91
- end
92
-
93
- describe "#decide" do
94
- it "returns a Decision object with all required fields" do
95
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(
96
- decision: "approve",
97
- weight: 0.8,
98
- reason: "Test approval"
99
- )
100
-
101
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
102
-
103
- result = agent.decide(context: { user: "test" })
104
-
105
- expect(result).to be_a(DecisionAgent::Decision)
106
- expect(result.decision).to eq("approve")
107
- expect(result.confidence).to be_between(0.0, 1.0)
108
- expect(result.explanations).to be_an(Array)
109
- expect(result.evaluations).to be_an(Array)
110
- expect(result.audit_payload).to be_a(Hash)
111
- end
112
-
113
- it "accepts Context object or Hash for context parameter" do
114
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
115
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
116
-
117
- result1 = agent.decide(context: { user: "test" })
118
- result2 = agent.decide(context: DecisionAgent::Context.new({ user: "test" }))
119
-
120
- expect(result1.decision).to eq(result2.decision)
121
- end
122
-
123
- it "raises NoEvaluationsError when no evaluators return decisions" do
124
- failing_evaluator = Class.new(DecisionAgent::Evaluators::Base) do
125
- def evaluate(_context, feedback: {})
126
- nil
127
- end
128
- end
129
-
130
- agent = DecisionAgent::Agent.new(evaluators: [failing_evaluator.new])
131
-
132
- expect do
133
- agent.decide(context: {})
134
- end.to raise_error(DecisionAgent::NoEvaluationsError)
135
- end
136
-
137
- it "includes feedback in evaluation" do
138
- feedback_evaluator = Class.new(DecisionAgent::Evaluators::Base) do
139
- def evaluate(_context, feedback: {})
140
- decision = feedback[:override] ? "reject" : "approve"
141
- DecisionAgent::Evaluation.new(
142
- decision: decision,
143
- weight: 1.0,
144
- reason: "Feedback-based",
145
- evaluator_name: "FeedbackEvaluator"
146
- )
147
- end
148
- end
149
-
150
- agent = DecisionAgent::Agent.new(evaluators: [feedback_evaluator.new])
151
-
152
- result1 = agent.decide(context: {}, feedback: {})
153
- result2 = agent.decide(context: {}, feedback: { override: true })
154
-
155
- expect(result1.decision).to eq("approve")
156
- expect(result2.decision).to eq("reject")
157
- end
158
-
159
- it "records decision via audit adapter" do
160
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
161
-
162
- audit_adapter = Class.new(DecisionAgent::Audit::Adapter) do
163
- attr_reader :recorded_decision, :recorded_context
164
-
165
- def record(decision, context)
166
- @recorded_decision = decision
167
- @recorded_context = context
168
- end
169
- end.new
170
-
171
- agent = DecisionAgent::Agent.new(
172
- evaluators: [evaluator],
173
- audit_adapter: audit_adapter
174
- )
175
-
176
- result = agent.decide(context: { user: "test" })
177
-
178
- expect(audit_adapter.recorded_decision).to eq(result)
179
- expect(audit_adapter.recorded_context.to_h).to eq({ user: "test" })
180
- end
181
-
182
- it "includes deterministic hash in audit payload" do
183
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve", weight: 0.8)
184
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
185
-
186
- result1 = agent.decide(context: { user: "test" })
187
- result2 = agent.decide(context: { user: "test" })
188
-
189
- expect(result1.audit_payload[:deterministic_hash]).to be_a(String)
190
- expect(result1.audit_payload[:deterministic_hash]).to eq(result2.audit_payload[:deterministic_hash])
191
- end
192
-
193
- it "produces different hashes for different contexts" do
194
- evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
195
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
196
-
197
- result1 = agent.decide(context: { user: "alice" })
198
- result2 = agent.decide(context: { user: "bob" })
199
-
200
- expect(result1.audit_payload[:deterministic_hash]).not_to eq(result2.audit_payload[:deterministic_hash])
201
- end
202
- end
203
-
204
- describe "conflict resolution" do
205
- it "resolves conflicting evaluations using scoring strategy" do
206
- evaluator1 = DecisionAgent::Evaluators::StaticEvaluator.new(
207
- decision: "approve",
208
- weight: 0.6,
209
- name: "Evaluator1"
210
- )
211
- evaluator2 = DecisionAgent::Evaluators::StaticEvaluator.new(
212
- decision: "reject",
213
- weight: 0.9,
214
- name: "Evaluator2"
215
- )
216
-
217
- agent = DecisionAgent::Agent.new(
218
- evaluators: [evaluator1, evaluator2],
219
- scoring_strategy: DecisionAgent::Scoring::MaxWeight.new
220
- )
221
-
222
- result = agent.decide(context: {})
223
-
224
- expect(result.decision).to eq("reject")
225
- expect(result.explanations.join(" ")).to include("Conflicting evaluations")
226
- end
227
-
228
- it "includes conflicting evaluations in explanations" do
229
- evaluator1 = DecisionAgent::Evaluators::StaticEvaluator.new(
230
- decision: "approve",
231
- weight: 0.4,
232
- name: "Evaluator1"
233
- )
234
- evaluator2 = DecisionAgent::Evaluators::StaticEvaluator.new(
235
- decision: "reject",
236
- weight: 0.7,
237
- name: "Evaluator2"
238
- )
239
-
240
- agent = DecisionAgent::Agent.new(evaluators: [evaluator1, evaluator2])
241
-
242
- result = agent.decide(context: {})
243
-
244
- explanations_text = result.explanations.join(" ")
245
- expect(explanations_text).to include("Evaluator1")
246
- expect(explanations_text).to include("Evaluator2")
247
- end
248
- end
249
-
250
- describe "multiple evaluators agreeing" do
251
- it "combines evaluations when all agree" do
252
- evaluator1 = DecisionAgent::Evaluators::StaticEvaluator.new(
253
- decision: "approve",
254
- weight: 0.6,
255
- name: "Evaluator1"
256
- )
257
- evaluator2 = DecisionAgent::Evaluators::StaticEvaluator.new(
258
- decision: "approve",
259
- weight: 0.8,
260
- name: "Evaluator2"
261
- )
262
-
263
- agent = DecisionAgent::Agent.new(evaluators: [evaluator1, evaluator2])
264
-
265
- result = agent.decide(context: {})
266
-
267
- expect(result.decision).to eq("approve")
268
- expect(result.confidence).to be > 0.5
269
- end
270
- end
271
-
272
- describe "graceful error handling" do
273
- it "ignores evaluators that raise errors" do
274
- good_evaluator = DecisionAgent::Evaluators::StaticEvaluator.new(decision: "approve")
275
-
276
- bad_evaluator = Class.new(DecisionAgent::Evaluators::Base) do
277
- def evaluate(_context, feedback: {})
278
- raise StandardError, "Intentional error"
279
- end
280
- end
281
-
282
- agent = DecisionAgent::Agent.new(evaluators: [bad_evaluator.new, good_evaluator])
283
-
284
- result = agent.decide(context: {})
285
-
286
- expect(result.decision).to eq("approve")
287
- end
288
- end
289
- end