decision_agent 0.2.0 → 1.0.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +313 -8
  3. data/bin/decision_agent +104 -0
  4. data/lib/decision_agent/agent.rb +72 -1
  5. data/lib/decision_agent/context.rb +1 -0
  6. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  7. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  8. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  9. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  10. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  11. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  12. data/lib/decision_agent/decision.rb +102 -2
  13. data/lib/decision_agent/dmn/adapter.rb +135 -0
  14. data/lib/decision_agent/dmn/cache.rb +306 -0
  15. data/lib/decision_agent/dmn/decision_graph.rb +327 -0
  16. data/lib/decision_agent/dmn/decision_tree.rb +192 -0
  17. data/lib/decision_agent/dmn/errors.rb +30 -0
  18. data/lib/decision_agent/dmn/exporter.rb +217 -0
  19. data/lib/decision_agent/dmn/feel/evaluator.rb +819 -0
  20. data/lib/decision_agent/dmn/feel/functions.rb +420 -0
  21. data/lib/decision_agent/dmn/feel/parser.rb +349 -0
  22. data/lib/decision_agent/dmn/feel/simple_parser.rb +276 -0
  23. data/lib/decision_agent/dmn/feel/transformer.rb +372 -0
  24. data/lib/decision_agent/dmn/feel/types.rb +276 -0
  25. data/lib/decision_agent/dmn/importer.rb +77 -0
  26. data/lib/decision_agent/dmn/model.rb +197 -0
  27. data/lib/decision_agent/dmn/parser.rb +191 -0
  28. data/lib/decision_agent/dmn/testing.rb +333 -0
  29. data/lib/decision_agent/dmn/validator.rb +315 -0
  30. data/lib/decision_agent/dmn/versioning.rb +229 -0
  31. data/lib/decision_agent/dmn/visualizer.rb +513 -0
  32. data/lib/decision_agent/dsl/condition_evaluator.rb +984 -838
  33. data/lib/decision_agent/dsl/schema_validator.rb +53 -14
  34. data/lib/decision_agent/evaluators/dmn_evaluator.rb +308 -0
  35. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  36. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  37. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  38. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  39. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  40. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  41. data/lib/decision_agent/simulation/errors.rb +18 -0
  42. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  43. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  44. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  45. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  46. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  47. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  48. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  49. data/lib/decision_agent/simulation.rb +17 -0
  50. data/lib/decision_agent/version.rb +1 -1
  51. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  52. data/lib/decision_agent/web/dmn_editor.rb +426 -0
  53. data/lib/decision_agent/web/public/app.js +119 -0
  54. data/lib/decision_agent/web/public/dmn-editor.css +596 -0
  55. data/lib/decision_agent/web/public/dmn-editor.html +250 -0
  56. data/lib/decision_agent/web/public/dmn-editor.js +553 -0
  57. data/lib/decision_agent/web/public/index.html +52 -0
  58. data/lib/decision_agent/web/public/simulation.html +130 -0
  59. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  60. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  61. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  62. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  63. data/lib/decision_agent/web/public/styles.css +86 -0
  64. data/lib/decision_agent/web/server.rb +1059 -23
  65. data/lib/decision_agent.rb +60 -2
  66. metadata +105 -61
  67. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  68. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  69. data/spec/ab_testing/ab_test_spec.rb +0 -270
  70. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -481
  71. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  72. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  73. data/spec/activerecord_thread_safety_spec.rb +0 -553
  74. data/spec/advanced_operators_spec.rb +0 -3150
  75. data/spec/agent_spec.rb +0 -289
  76. data/spec/api_contract_spec.rb +0 -430
  77. data/spec/audit_adapters_spec.rb +0 -92
  78. data/spec/auth/access_audit_logger_spec.rb +0 -394
  79. data/spec/auth/authenticator_spec.rb +0 -112
  80. data/spec/auth/password_reset_spec.rb +0 -294
  81. data/spec/auth/permission_checker_spec.rb +0 -207
  82. data/spec/auth/permission_spec.rb +0 -73
  83. data/spec/auth/rbac_adapter_spec.rb +0 -550
  84. data/spec/auth/rbac_config_spec.rb +0 -82
  85. data/spec/auth/role_spec.rb +0 -51
  86. data/spec/auth/session_manager_spec.rb +0 -172
  87. data/spec/auth/session_spec.rb +0 -112
  88. data/spec/auth/user_spec.rb +0 -130
  89. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  90. data/spec/context_spec.rb +0 -127
  91. data/spec/decision_agent_spec.rb +0 -96
  92. data/spec/decision_spec.rb +0 -423
  93. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  94. data/spec/dsl_validation_spec.rb +0 -648
  95. data/spec/edge_cases_spec.rb +0 -353
  96. data/spec/evaluation_spec.rb +0 -364
  97. data/spec/evaluation_validator_spec.rb +0 -165
  98. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  99. data/spec/examples.txt +0 -1633
  100. data/spec/issue_verification_spec.rb +0 -759
  101. data/spec/json_rule_evaluator_spec.rb +0 -587
  102. data/spec/monitoring/alert_manager_spec.rb +0 -378
  103. data/spec/monitoring/metrics_collector_spec.rb +0 -499
  104. data/spec/monitoring/monitored_agent_spec.rb +0 -222
  105. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  106. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  107. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  108. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  109. data/spec/performance_optimizations_spec.rb +0 -486
  110. data/spec/replay_edge_cases_spec.rb +0 -699
  111. data/spec/replay_spec.rb +0 -210
  112. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  113. data/spec/scoring_spec.rb +0 -225
  114. data/spec/spec_helper.rb +0 -60
  115. data/spec/testing/batch_test_importer_spec.rb +0 -693
  116. data/spec/testing/batch_test_runner_spec.rb +0 -307
  117. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  118. data/spec/testing/test_result_comparator_spec.rb +0 -392
  119. data/spec/testing/test_scenario_spec.rb +0 -113
  120. data/spec/thread_safety_spec.rb +0 -482
  121. data/spec/thread_safety_spec.rb.broken +0 -878
  122. data/spec/versioning/adapter_spec.rb +0 -156
  123. data/spec/versioning_spec.rb +0 -1030
  124. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  125. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  126. data/spec/web_ui_rack_spec.rb +0 -1840
@@ -1,270 +0,0 @@
1
- require "spec_helper"
2
- require "decision_agent/ab_testing/ab_test"
3
-
4
- RSpec.describe DecisionAgent::ABTesting::ABTest do
5
- describe "#initialize" do
6
- it "creates a valid A/B test with default values" do
7
- test = described_class.new(
8
- name: "Test A vs B",
9
- champion_version_id: "v1",
10
- challenger_version_id: "v2"
11
- )
12
-
13
- expect(test.name).to eq("Test A vs B")
14
- expect(test.champion_version_id).to eq("v1")
15
- expect(test.challenger_version_id).to eq("v2")
16
- expect(test.traffic_split).to eq({ champion: 90, challenger: 10 })
17
- expect(test.status).to eq("scheduled")
18
- end
19
-
20
- it "accepts custom traffic split as hash" do
21
- test = described_class.new(
22
- name: "Custom Split",
23
- champion_version_id: "v1",
24
- challenger_version_id: "v2",
25
- traffic_split: { champion: 70, challenger: 30 }
26
- )
27
-
28
- expect(test.traffic_split).to eq({ champion: 70, challenger: 30 })
29
- end
30
-
31
- it "accepts custom traffic split as array" do
32
- test = described_class.new(
33
- name: "Array Split",
34
- champion_version_id: "v1",
35
- challenger_version_id: "v2",
36
- traffic_split: [80, 20]
37
- )
38
-
39
- expect(test.traffic_split).to eq({ champion: 80, challenger: 20 })
40
- end
41
-
42
- it "raises error if traffic split doesn't sum to 100" do
43
- expect do
44
- described_class.new(
45
- name: "Bad Split",
46
- champion_version_id: "v1",
47
- challenger_version_id: "v2",
48
- traffic_split: { champion: 60, challenger: 30 }
49
- )
50
- end.to raise_error(DecisionAgent::ValidationError, /must sum to 100/)
51
- end
52
-
53
- it "raises error if champion and challenger are the same" do
54
- expect do
55
- described_class.new(
56
- name: "Same Versions",
57
- champion_version_id: "v1",
58
- challenger_version_id: "v1"
59
- )
60
- end.to raise_error(DecisionAgent::ValidationError, /must be different/)
61
- end
62
-
63
- it "raises error if name is empty" do
64
- expect do
65
- described_class.new(
66
- name: "",
67
- champion_version_id: "v1",
68
- challenger_version_id: "v2"
69
- )
70
- end.to raise_error(DecisionAgent::ValidationError, /name is required/)
71
- end
72
- end
73
-
74
- describe "#assign_variant" do
75
- let(:test) do
76
- described_class.new(
77
- name: "Test",
78
- champion_version_id: "v1",
79
- challenger_version_id: "v2",
80
- traffic_split: { champion: 90, challenger: 10 },
81
- status: "running",
82
- id: 123
83
- )
84
- end
85
-
86
- it "assigns champion or challenger based on traffic split" do
87
- assignments = 1000.times.map { test.assign_variant }
88
- champion_count = assignments.count { |v| v == :champion }
89
- challenger_count = assignments.count { |v| v == :challenger }
90
-
91
- # With 90/10 split, expect roughly 900/100
92
- expect(champion_count).to be_between(850, 950)
93
- expect(challenger_count).to be_between(50, 150)
94
- end
95
-
96
- it "assigns same variant to same user consistently" do
97
- user_id = "user_123"
98
- variants = 10.times.map { test.assign_variant(user_id: user_id) }
99
-
100
- expect(variants.uniq.size).to eq(1)
101
- end
102
-
103
- it "assigns different users to different variants based on split" do
104
- assignments = 1000.times.map { |i| test.assign_variant(user_id: "user_#{i}") }
105
- champion_count = assignments.count { |v| v == :champion }
106
- challenger_count = assignments.count { |v| v == :challenger }
107
-
108
- expect(champion_count).to be_between(850, 950)
109
- expect(challenger_count).to be_between(50, 150)
110
- end
111
-
112
- it "raises error if test is not running" do
113
- test = described_class.new(
114
- name: "Not Running",
115
- champion_version_id: "v1",
116
- challenger_version_id: "v2",
117
- status: "completed"
118
- )
119
-
120
- expect do
121
- test.assign_variant
122
- end.to raise_error(DecisionAgent::ABTesting::TestNotRunningError)
123
- end
124
- end
125
-
126
- describe "#version_for_variant" do
127
- let(:test) do
128
- described_class.new(
129
- name: "Test",
130
- champion_version_id: "champion_v1",
131
- challenger_version_id: "challenger_v2"
132
- )
133
- end
134
-
135
- it "returns champion version ID for :champion variant" do
136
- expect(test.version_for_variant(:champion)).to eq("champion_v1")
137
- end
138
-
139
- it "returns challenger version ID for :challenger variant" do
140
- expect(test.version_for_variant(:challenger)).to eq("challenger_v2")
141
- end
142
-
143
- it "raises error for invalid variant" do
144
- expect do
145
- test.version_for_variant(:invalid)
146
- end.to raise_error(ArgumentError, /Invalid variant/)
147
- end
148
- end
149
-
150
- describe "#running?" do
151
- it "returns true when status is running and within date range" do
152
- test = described_class.new(
153
- name: "Test",
154
- champion_version_id: "v1",
155
- challenger_version_id: "v2",
156
- status: "running",
157
- start_date: Time.now.utc - 3600,
158
- end_date: Time.now.utc + 3600
159
- )
160
-
161
- expect(test.running?).to be true
162
- end
163
-
164
- it "returns false when status is not running" do
165
- test = described_class.new(
166
- name: "Test",
167
- champion_version_id: "v1",
168
- challenger_version_id: "v2",
169
- status: "completed"
170
- )
171
-
172
- expect(test.running?).to be false
173
- end
174
-
175
- it "returns false when start date is in future" do
176
- test = described_class.new(
177
- name: "Test",
178
- champion_version_id: "v1",
179
- challenger_version_id: "v2",
180
- status: "running",
181
- start_date: Time.now.utc + 3600
182
- )
183
-
184
- expect(test.running?).to be false
185
- end
186
-
187
- it "returns false when end date has passed" do
188
- test = described_class.new(
189
- name: "Test",
190
- champion_version_id: "v1",
191
- challenger_version_id: "v2",
192
- status: "running",
193
- start_date: Time.now.utc - 7200,
194
- end_date: Time.now.utc - 3600
195
- )
196
-
197
- expect(test.running?).to be false
198
- end
199
- end
200
-
201
- describe "status transitions" do
202
- it "can start a scheduled test" do
203
- test = described_class.new(
204
- name: "Test",
205
- champion_version_id: "v1",
206
- challenger_version_id: "v2",
207
- status: "scheduled"
208
- )
209
-
210
- expect { test.start! }.not_to raise_error
211
- expect(test.status).to eq("running")
212
- end
213
-
214
- it "can complete a running test" do
215
- test = described_class.new(
216
- name: "Test",
217
- champion_version_id: "v1",
218
- challenger_version_id: "v2",
219
- status: "running"
220
- )
221
-
222
- expect { test.complete! }.not_to raise_error
223
- expect(test.status).to eq("completed")
224
- end
225
-
226
- it "can cancel a test" do
227
- test = described_class.new(
228
- name: "Test",
229
- champion_version_id: "v1",
230
- challenger_version_id: "v2",
231
- status: "running"
232
- )
233
-
234
- expect { test.cancel! }.not_to raise_error
235
- expect(test.status).to eq("cancelled")
236
- end
237
-
238
- it "raises error when trying invalid status transition" do
239
- test = described_class.new(
240
- name: "Test",
241
- champion_version_id: "v1",
242
- challenger_version_id: "v2",
243
- status: "completed"
244
- )
245
-
246
- expect do
247
- test.start!
248
- end.to raise_error(DecisionAgent::ABTesting::InvalidStatusTransitionError)
249
- end
250
- end
251
-
252
- describe "#to_h" do
253
- it "returns hash representation" do
254
- test = described_class.new(
255
- name: "Test",
256
- champion_version_id: "v1",
257
- challenger_version_id: "v2",
258
- id: 123
259
- )
260
-
261
- hash = test.to_h
262
-
263
- expect(hash[:id]).to eq(123)
264
- expect(hash[:name]).to eq("Test")
265
- expect(hash[:champion_version_id]).to eq("v1")
266
- expect(hash[:challenger_version_id]).to eq("v2")
267
- expect(hash[:status]).to eq("scheduled")
268
- end
269
- end
270
- end