decision_agent 0.1.1 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +234 -919
- data/bin/decision_agent +5 -5
- data/lib/decision_agent/agent.rb +19 -26
- data/lib/decision_agent/audit/null_adapter.rb +1 -2
- data/lib/decision_agent/decision.rb +3 -1
- data/lib/decision_agent/dsl/condition_evaluator.rb +4 -3
- data/lib/decision_agent/dsl/rule_parser.rb +4 -6
- data/lib/decision_agent/dsl/schema_validator.rb +27 -31
- data/lib/decision_agent/errors.rb +21 -6
- data/lib/decision_agent/evaluation.rb +3 -1
- data/lib/decision_agent/evaluation_validator.rb +78 -0
- data/lib/decision_agent/evaluators/json_rule_evaluator.rb +26 -0
- data/lib/decision_agent/evaluators/static_evaluator.rb +2 -6
- data/lib/decision_agent/monitoring/alert_manager.rb +282 -0
- data/lib/decision_agent/monitoring/dashboard/public/dashboard.css +381 -0
- data/lib/decision_agent/monitoring/dashboard/public/dashboard.js +471 -0
- data/lib/decision_agent/monitoring/dashboard/public/index.html +161 -0
- data/lib/decision_agent/monitoring/dashboard_server.rb +340 -0
- data/lib/decision_agent/monitoring/metrics_collector.rb +278 -0
- data/lib/decision_agent/monitoring/monitored_agent.rb +71 -0
- data/lib/decision_agent/monitoring/prometheus_exporter.rb +247 -0
- data/lib/decision_agent/replay/replay.rb +12 -22
- data/lib/decision_agent/scoring/base.rb +1 -1
- data/lib/decision_agent/scoring/consensus.rb +5 -5
- data/lib/decision_agent/scoring/weighted_average.rb +1 -1
- data/lib/decision_agent/version.rb +1 -1
- data/lib/decision_agent/versioning/activerecord_adapter.rb +141 -0
- data/lib/decision_agent/versioning/adapter.rb +100 -0
- data/lib/decision_agent/versioning/file_storage_adapter.rb +290 -0
- data/lib/decision_agent/versioning/version_manager.rb +127 -0
- data/lib/decision_agent/web/public/app.js +318 -0
- data/lib/decision_agent/web/public/index.html +56 -1
- data/lib/decision_agent/web/public/styles.css +219 -0
- data/lib/decision_agent/web/server.rb +169 -9
- data/lib/decision_agent.rb +11 -0
- data/lib/generators/decision_agent/install/install_generator.rb +40 -0
- data/lib/generators/decision_agent/install/templates/README +47 -0
- data/lib/generators/decision_agent/install/templates/migration.rb +37 -0
- data/lib/generators/decision_agent/install/templates/rule.rb +30 -0
- data/lib/generators/decision_agent/install/templates/rule_version.rb +66 -0
- data/spec/activerecord_thread_safety_spec.rb +553 -0
- data/spec/agent_spec.rb +13 -13
- data/spec/api_contract_spec.rb +16 -16
- data/spec/audit_adapters_spec.rb +3 -3
- data/spec/comprehensive_edge_cases_spec.rb +86 -86
- data/spec/dsl_validation_spec.rb +83 -83
- data/spec/edge_cases_spec.rb +23 -23
- data/spec/examples/feedback_aware_evaluator_spec.rb +7 -7
- data/spec/examples.txt +548 -0
- data/spec/issue_verification_spec.rb +685 -0
- data/spec/json_rule_evaluator_spec.rb +15 -15
- data/spec/monitoring/alert_manager_spec.rb +378 -0
- data/spec/monitoring/metrics_collector_spec.rb +281 -0
- data/spec/monitoring/monitored_agent_spec.rb +222 -0
- data/spec/monitoring/prometheus_exporter_spec.rb +242 -0
- data/spec/replay_edge_cases_spec.rb +58 -58
- data/spec/replay_spec.rb +11 -11
- data/spec/rfc8785_canonicalization_spec.rb +215 -0
- data/spec/scoring_spec.rb +1 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/thread_safety_spec.rb +482 -0
- data/spec/thread_safety_spec.rb.broken +878 -0
- data/spec/versioning_spec.rb +777 -0
- data/spec/web_ui_rack_spec.rb +135 -0
- metadata +84 -11
data/spec/dsl_validation_spec.rb
CHANGED
|
@@ -4,15 +4,15 @@ RSpec.describe "DSL Validation" do
|
|
|
4
4
|
describe DecisionAgent::Dsl::SchemaValidator do
|
|
5
5
|
describe "root structure validation" do
|
|
6
6
|
it "rejects non-hash input" do
|
|
7
|
-
expect
|
|
7
|
+
expect do
|
|
8
8
|
DecisionAgent::Dsl::SchemaValidator.validate!([1, 2, 3])
|
|
9
|
-
|
|
9
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Root element must be a hash/)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
it "rejects string input" do
|
|
13
|
-
expect
|
|
13
|
+
expect do
|
|
14
14
|
DecisionAgent::Dsl::SchemaValidator.validate!("not a hash")
|
|
15
|
-
|
|
15
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Root element must be a hash/)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
it "accepts valid hash input" do
|
|
@@ -21,9 +21,9 @@ RSpec.describe "DSL Validation" do
|
|
|
21
21
|
"rules" => []
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
expect
|
|
24
|
+
expect do
|
|
25
25
|
DecisionAgent::Dsl::SchemaValidator.validate!(valid_rules)
|
|
26
|
-
|
|
26
|
+
end.not_to raise_error
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -33,9 +33,9 @@ RSpec.describe "DSL Validation" do
|
|
|
33
33
|
"rules" => []
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
expect
|
|
36
|
+
expect do
|
|
37
37
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
38
|
-
|
|
38
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Missing required field 'version'/)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
it "accepts version as symbol key" do
|
|
@@ -44,9 +44,9 @@ RSpec.describe "DSL Validation" do
|
|
|
44
44
|
rules: []
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
expect
|
|
47
|
+
expect do
|
|
48
48
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
49
|
-
|
|
49
|
+
end.not_to raise_error
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -56,9 +56,9 @@ RSpec.describe "DSL Validation" do
|
|
|
56
56
|
"version" => "1.0"
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
expect
|
|
59
|
+
expect do
|
|
60
60
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
61
|
-
|
|
61
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Missing required field 'rules'/)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
it "rejects non-array rules" do
|
|
@@ -67,9 +67,9 @@ RSpec.describe "DSL Validation" do
|
|
|
67
67
|
"rules" => "not an array"
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
expect
|
|
70
|
+
expect do
|
|
71
71
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
72
|
-
|
|
72
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /must be an array/)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
it "accepts empty rules array" do
|
|
@@ -78,9 +78,9 @@ RSpec.describe "DSL Validation" do
|
|
|
78
78
|
"rules" => []
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
expect
|
|
81
|
+
expect do
|
|
82
82
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
83
|
-
|
|
83
|
+
end.not_to raise_error
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -91,9 +91,9 @@ RSpec.describe "DSL Validation" do
|
|
|
91
91
|
"rules" => ["not a hash"]
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
expect
|
|
94
|
+
expect do
|
|
95
95
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
96
|
-
|
|
96
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /rules\[0\].*must be a hash/)
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
it "requires rule id" do
|
|
@@ -107,9 +107,9 @@ RSpec.describe "DSL Validation" do
|
|
|
107
107
|
]
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
expect
|
|
110
|
+
expect do
|
|
111
111
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
112
|
-
|
|
112
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Missing required field 'id'/)
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
it "requires rule if clause" do
|
|
@@ -123,9 +123,9 @@ RSpec.describe "DSL Validation" do
|
|
|
123
123
|
]
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
expect
|
|
126
|
+
expect do
|
|
127
127
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
128
|
-
|
|
128
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Missing required field 'if'/)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
it "requires rule then clause" do
|
|
@@ -139,9 +139,9 @@ RSpec.describe "DSL Validation" do
|
|
|
139
139
|
]
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
expect
|
|
142
|
+
expect do
|
|
143
143
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
144
|
-
|
|
144
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Missing required field 'then'/)
|
|
145
145
|
end
|
|
146
146
|
end
|
|
147
147
|
|
|
@@ -158,9 +158,9 @@ RSpec.describe "DSL Validation" do
|
|
|
158
158
|
]
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
expect
|
|
161
|
+
expect do
|
|
162
162
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
163
|
-
|
|
163
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Condition must have one of: 'field', 'all', or 'any'/)
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
it "rejects non-hash condition" do
|
|
@@ -175,9 +175,9 @@ RSpec.describe "DSL Validation" do
|
|
|
175
175
|
]
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
expect
|
|
178
|
+
expect do
|
|
179
179
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
180
|
-
|
|
180
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Condition must be a hash/)
|
|
181
181
|
end
|
|
182
182
|
end
|
|
183
183
|
|
|
@@ -194,9 +194,9 @@ RSpec.describe "DSL Validation" do
|
|
|
194
194
|
]
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
expect
|
|
197
|
+
expect do
|
|
198
198
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
199
|
-
|
|
199
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError) do |error|
|
|
200
200
|
expect(error.message).to match(/Condition must have one of: 'field', 'all', or 'any'/)
|
|
201
201
|
end
|
|
202
202
|
end
|
|
@@ -213,9 +213,9 @@ RSpec.describe "DSL Validation" do
|
|
|
213
213
|
]
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
expect
|
|
216
|
+
expect do
|
|
217
217
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
218
|
-
|
|
218
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /missing 'op'/)
|
|
219
219
|
end
|
|
220
220
|
|
|
221
221
|
it "validates operator is supported" do
|
|
@@ -230,9 +230,9 @@ RSpec.describe "DSL Validation" do
|
|
|
230
230
|
]
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
expect
|
|
233
|
+
expect do
|
|
234
234
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
235
|
-
|
|
235
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError) do |error|
|
|
236
236
|
expect(error.message).to include("Unsupported operator 'invalid_op'")
|
|
237
237
|
expect(error.message).to include("eq, neq, gt, gte, lt, lte, in, present, blank")
|
|
238
238
|
end
|
|
@@ -250,9 +250,9 @@ RSpec.describe "DSL Validation" do
|
|
|
250
250
|
]
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
expect
|
|
253
|
+
expect do
|
|
254
254
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
255
|
-
|
|
255
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /missing 'value' key/)
|
|
256
256
|
end
|
|
257
257
|
|
|
258
258
|
it "allows missing value for present operator" do
|
|
@@ -267,9 +267,9 @@ RSpec.describe "DSL Validation" do
|
|
|
267
267
|
]
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
expect
|
|
270
|
+
expect do
|
|
271
271
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
272
|
-
|
|
272
|
+
end.not_to raise_error
|
|
273
273
|
end
|
|
274
274
|
|
|
275
275
|
it "allows missing value for blank operator" do
|
|
@@ -284,9 +284,9 @@ RSpec.describe "DSL Validation" do
|
|
|
284
284
|
]
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
expect
|
|
287
|
+
expect do
|
|
288
288
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
289
|
-
|
|
289
|
+
end.not_to raise_error
|
|
290
290
|
end
|
|
291
291
|
|
|
292
292
|
it "rejects empty field path" do
|
|
@@ -301,9 +301,9 @@ RSpec.describe "DSL Validation" do
|
|
|
301
301
|
]
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
expect
|
|
304
|
+
expect do
|
|
305
305
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
306
|
-
|
|
306
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Field path cannot be empty/)
|
|
307
307
|
end
|
|
308
308
|
|
|
309
309
|
it "rejects invalid dot-notation" do
|
|
@@ -318,9 +318,9 @@ RSpec.describe "DSL Validation" do
|
|
|
318
318
|
]
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
expect
|
|
321
|
+
expect do
|
|
322
322
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
323
|
-
|
|
323
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /cannot have empty segments/)
|
|
324
324
|
end
|
|
325
325
|
|
|
326
326
|
it "accepts valid dot-notation" do
|
|
@@ -335,9 +335,9 @@ RSpec.describe "DSL Validation" do
|
|
|
335
335
|
]
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
expect
|
|
338
|
+
expect do
|
|
339
339
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
340
|
-
|
|
340
|
+
end.not_to raise_error
|
|
341
341
|
end
|
|
342
342
|
end
|
|
343
343
|
|
|
@@ -354,9 +354,9 @@ RSpec.describe "DSL Validation" do
|
|
|
354
354
|
]
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
expect
|
|
357
|
+
expect do
|
|
358
358
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
359
|
-
|
|
359
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /'all' condition must contain an array/)
|
|
360
360
|
end
|
|
361
361
|
|
|
362
362
|
it "requires array for any condition" do
|
|
@@ -371,9 +371,9 @@ RSpec.describe "DSL Validation" do
|
|
|
371
371
|
]
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
-
expect
|
|
374
|
+
expect do
|
|
375
375
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
376
|
-
|
|
376
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /'any' condition must contain an array/)
|
|
377
377
|
end
|
|
378
378
|
|
|
379
379
|
it "validates nested conditions in all" do
|
|
@@ -392,9 +392,9 @@ RSpec.describe "DSL Validation" do
|
|
|
392
392
|
]
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
expect
|
|
395
|
+
expect do
|
|
396
396
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
397
|
-
|
|
397
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Unsupported operator/)
|
|
398
398
|
end
|
|
399
399
|
|
|
400
400
|
it "validates nested conditions in any" do
|
|
@@ -405,7 +405,7 @@ RSpec.describe "DSL Validation" do
|
|
|
405
405
|
"id" => "rule_1",
|
|
406
406
|
"if" => {
|
|
407
407
|
"any" => [
|
|
408
|
-
{ "field" => "priority" }
|
|
408
|
+
{ "field" => "priority" } # Missing op
|
|
409
409
|
]
|
|
410
410
|
},
|
|
411
411
|
"then" => { "decision" => "escalate" }
|
|
@@ -413,9 +413,9 @@ RSpec.describe "DSL Validation" do
|
|
|
413
413
|
]
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
expect
|
|
416
|
+
expect do
|
|
417
417
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
418
|
-
|
|
418
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /missing 'op'/)
|
|
419
419
|
end
|
|
420
420
|
end
|
|
421
421
|
|
|
@@ -432,9 +432,9 @@ RSpec.describe "DSL Validation" do
|
|
|
432
432
|
]
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
expect
|
|
435
|
+
expect do
|
|
436
436
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
437
|
-
|
|
437
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /then.*Must be a hash/)
|
|
438
438
|
end
|
|
439
439
|
|
|
440
440
|
it "requires decision field in then clause" do
|
|
@@ -449,9 +449,9 @@ RSpec.describe "DSL Validation" do
|
|
|
449
449
|
]
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
expect
|
|
452
|
+
expect do
|
|
453
453
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
454
|
-
|
|
454
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Missing required field 'decision'/)
|
|
455
455
|
end
|
|
456
456
|
|
|
457
457
|
it "validates weight is numeric" do
|
|
@@ -466,9 +466,9 @@ RSpec.describe "DSL Validation" do
|
|
|
466
466
|
]
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
-
expect
|
|
469
|
+
expect do
|
|
470
470
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
471
|
-
|
|
471
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /weight.*Must be a number/)
|
|
472
472
|
end
|
|
473
473
|
|
|
474
474
|
it "validates weight is between 0 and 1" do
|
|
@@ -483,9 +483,9 @@ RSpec.describe "DSL Validation" do
|
|
|
483
483
|
]
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
expect
|
|
486
|
+
expect do
|
|
487
487
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
488
|
-
|
|
488
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /weight.*between 0.0 and 1.0/)
|
|
489
489
|
end
|
|
490
490
|
|
|
491
491
|
it "validates reason is a string" do
|
|
@@ -500,9 +500,9 @@ RSpec.describe "DSL Validation" do
|
|
|
500
500
|
]
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
-
expect
|
|
503
|
+
expect do
|
|
504
504
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
505
|
-
|
|
505
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /reason.*Must be a string/)
|
|
506
506
|
end
|
|
507
507
|
|
|
508
508
|
it "accepts valid then clause with all fields" do
|
|
@@ -521,9 +521,9 @@ RSpec.describe "DSL Validation" do
|
|
|
521
521
|
]
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
expect
|
|
524
|
+
expect do
|
|
525
525
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
526
|
-
|
|
526
|
+
end.not_to raise_error
|
|
527
527
|
end
|
|
528
528
|
end
|
|
529
529
|
|
|
@@ -533,16 +533,16 @@ RSpec.describe "DSL Validation" do
|
|
|
533
533
|
"version" => "1.0",
|
|
534
534
|
"rules" => [
|
|
535
535
|
{
|
|
536
|
-
"id" => "rule_1"
|
|
536
|
+
"id" => "rule_1"
|
|
537
537
|
# Missing if clause
|
|
538
538
|
# Missing then clause
|
|
539
539
|
}
|
|
540
540
|
]
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
-
expect
|
|
543
|
+
expect do
|
|
544
544
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
545
|
-
|
|
545
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError) do |error|
|
|
546
546
|
expect(error.message).to include("1.")
|
|
547
547
|
expect(error.message).to include("2.")
|
|
548
548
|
expect(error.message).to match(/validation failed with 2 errors/)
|
|
@@ -561,9 +561,9 @@ RSpec.describe "DSL Validation" do
|
|
|
561
561
|
]
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
-
expect
|
|
564
|
+
expect do
|
|
565
565
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
566
|
-
|
|
566
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError) do |error|
|
|
567
567
|
expect(error.message).to include("rules[0].if")
|
|
568
568
|
expect(error.message).to include("Supported operators:")
|
|
569
569
|
end
|
|
@@ -592,9 +592,9 @@ RSpec.describe "DSL Validation" do
|
|
|
592
592
|
]
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
-
expect
|
|
595
|
+
expect do
|
|
596
596
|
DecisionAgent::Dsl::SchemaValidator.validate!(rules)
|
|
597
|
-
|
|
597
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError) do |error|
|
|
598
598
|
expect(error.message).to include("rules[0].if.all[0].any[1]")
|
|
599
599
|
expect(error.message).to include("Unsupported operator")
|
|
600
600
|
end
|
|
@@ -606,17 +606,17 @@ RSpec.describe "DSL Validation" do
|
|
|
606
606
|
it "uses SchemaValidator for validation" do
|
|
607
607
|
invalid_json = '{"version": "1.0", "rules": "not an array"}'
|
|
608
608
|
|
|
609
|
-
expect
|
|
609
|
+
expect do
|
|
610
610
|
DecisionAgent::Dsl::RuleParser.parse(invalid_json)
|
|
611
|
-
|
|
611
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /must be an array/)
|
|
612
612
|
end
|
|
613
613
|
|
|
614
614
|
it "provides helpful error for malformed JSON" do
|
|
615
615
|
malformed_json = '{"version": "1.0", "rules": [,,,]}'
|
|
616
616
|
|
|
617
|
-
expect
|
|
617
|
+
expect do
|
|
618
618
|
DecisionAgent::Dsl::RuleParser.parse(malformed_json)
|
|
619
|
-
|
|
619
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError) do |error|
|
|
620
620
|
expect(error.message).to include("Invalid JSON syntax")
|
|
621
621
|
expect(error.message).to include("Common issues")
|
|
622
622
|
end
|
|
@@ -634,15 +634,15 @@ RSpec.describe "DSL Validation" do
|
|
|
634
634
|
]
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
-
expect
|
|
637
|
+
expect do
|
|
638
638
|
DecisionAgent::Dsl::RuleParser.parse(rules_hash)
|
|
639
|
-
|
|
639
|
+
end.not_to raise_error
|
|
640
640
|
end
|
|
641
641
|
|
|
642
642
|
it "rejects invalid input types" do
|
|
643
|
-
expect
|
|
644
|
-
DecisionAgent::Dsl::RuleParser.parse(
|
|
645
|
-
|
|
643
|
+
expect do
|
|
644
|
+
DecisionAgent::Dsl::RuleParser.parse(12_345)
|
|
645
|
+
end.to raise_error(DecisionAgent::InvalidRuleDslError, /Expected JSON string or Hash/)
|
|
646
646
|
end
|
|
647
647
|
end
|
|
648
648
|
end
|
data/spec/edge_cases_spec.rb
CHANGED
|
@@ -47,7 +47,7 @@ RSpec.describe "Edge Cases" do
|
|
|
47
47
|
|
|
48
48
|
describe "confidence edge cases" do
|
|
49
49
|
it "raises error when confidence exceeds 1.0" do
|
|
50
|
-
expect
|
|
50
|
+
expect do
|
|
51
51
|
DecisionAgent::Decision.new(
|
|
52
52
|
decision: "test",
|
|
53
53
|
confidence: 1.5,
|
|
@@ -55,11 +55,11 @@ RSpec.describe "Edge Cases" do
|
|
|
55
55
|
evaluations: [],
|
|
56
56
|
audit_payload: {}
|
|
57
57
|
)
|
|
58
|
-
|
|
58
|
+
end.to raise_error(DecisionAgent::InvalidConfidenceError)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
it "raises error when confidence is negative" do
|
|
62
|
-
expect
|
|
62
|
+
expect do
|
|
63
63
|
DecisionAgent::Decision.new(
|
|
64
64
|
decision: "test",
|
|
65
65
|
confidence: -0.1,
|
|
@@ -67,7 +67,7 @@ RSpec.describe "Edge Cases" do
|
|
|
67
67
|
evaluations: [],
|
|
68
68
|
audit_payload: {}
|
|
69
69
|
)
|
|
70
|
-
|
|
70
|
+
end.to raise_error(DecisionAgent::InvalidConfidenceError)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
it "accepts confidence at boundary values" do
|
|
@@ -94,25 +94,25 @@ RSpec.describe "Edge Cases" do
|
|
|
94
94
|
|
|
95
95
|
describe "weight edge cases" do
|
|
96
96
|
it "raises error when weight exceeds 1.0" do
|
|
97
|
-
expect
|
|
97
|
+
expect do
|
|
98
98
|
DecisionAgent::Evaluation.new(
|
|
99
99
|
decision: "test",
|
|
100
100
|
weight: 1.5,
|
|
101
101
|
reason: "test",
|
|
102
102
|
evaluator_name: "test"
|
|
103
103
|
)
|
|
104
|
-
|
|
104
|
+
end.to raise_error(DecisionAgent::InvalidWeightError)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
it "raises error when weight is negative" do
|
|
108
|
-
expect
|
|
108
|
+
expect do
|
|
109
109
|
DecisionAgent::Evaluation.new(
|
|
110
110
|
decision: "test",
|
|
111
111
|
weight: -0.1,
|
|
112
112
|
reason: "test",
|
|
113
113
|
evaluator_name: "test"
|
|
114
114
|
)
|
|
115
|
-
|
|
115
|
+
end.to raise_error(DecisionAgent::InvalidWeightError)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
it "accepts weight at boundary values" do
|
|
@@ -206,9 +206,9 @@ RSpec.describe "Edge Cases" do
|
|
|
206
206
|
it "freezes context data to prevent modification" do
|
|
207
207
|
context = DecisionAgent::Context.new({ user: "alice" })
|
|
208
208
|
|
|
209
|
-
expect
|
|
209
|
+
expect do
|
|
210
210
|
context.to_h[:user] = "bob"
|
|
211
|
-
|
|
211
|
+
end.to raise_error(FrozenError)
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
it "freezes evaluation fields" do
|
|
@@ -313,16 +313,16 @@ RSpec.describe "Edge Cases" do
|
|
|
313
313
|
|
|
314
314
|
evaluator = DecisionAgent::Evaluators::JsonRuleEvaluator.new(rules_json: rules)
|
|
315
315
|
context = DecisionAgent::Context.new({
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
316
|
+
a: {
|
|
317
|
+
b: {
|
|
318
|
+
c: {
|
|
319
|
+
d: {
|
|
320
|
+
e: "deep"
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
326
|
|
|
327
327
|
evaluation = evaluator.evaluate(context)
|
|
328
328
|
|
|
@@ -334,7 +334,7 @@ RSpec.describe "Edge Cases" do
|
|
|
334
334
|
describe "audit adapter errors" do
|
|
335
335
|
it "propagates errors from audit adapter" do
|
|
336
336
|
failing_adapter = Class.new(DecisionAgent::Audit::Adapter) do
|
|
337
|
-
def record(
|
|
337
|
+
def record(_decision, _context)
|
|
338
338
|
raise StandardError, "Audit failed"
|
|
339
339
|
end
|
|
340
340
|
end
|
|
@@ -345,9 +345,9 @@ RSpec.describe "Edge Cases" do
|
|
|
345
345
|
audit_adapter: failing_adapter.new
|
|
346
346
|
)
|
|
347
347
|
|
|
348
|
-
expect
|
|
348
|
+
expect do
|
|
349
349
|
agent.decide(context: {})
|
|
350
|
-
|
|
350
|
+
end.to raise_error(StandardError, "Audit failed")
|
|
351
351
|
end
|
|
352
352
|
end
|
|
353
353
|
end
|
|
@@ -152,7 +152,7 @@ RSpec.describe Examples::FeedbackAwareEvaluator do
|
|
|
152
152
|
it "clamps weight to minimum 0.0" do
|
|
153
153
|
result = evaluator.evaluate(
|
|
154
154
|
context,
|
|
155
|
-
feedback: { past_accuracy: -1.0 }
|
|
155
|
+
feedback: { past_accuracy: -1.0 } # Invalid, but should be handled
|
|
156
156
|
)
|
|
157
157
|
|
|
158
158
|
expect(result.weight).to be >= 0.0
|
|
@@ -161,7 +161,7 @@ RSpec.describe Examples::FeedbackAwareEvaluator do
|
|
|
161
161
|
it "clamps weight to maximum 1.0" do
|
|
162
162
|
result = evaluator.evaluate(
|
|
163
163
|
context,
|
|
164
|
-
feedback: { past_accuracy: 2.0 }
|
|
164
|
+
feedback: { past_accuracy: 2.0 } # Would produce 1.6, should clamp to 1.0
|
|
165
165
|
)
|
|
166
166
|
|
|
167
167
|
expect(result.weight).to be <= 1.0
|
|
@@ -254,7 +254,7 @@ RSpec.describe Examples::FeedbackAwareEvaluator do
|
|
|
254
254
|
|
|
255
255
|
result = high_weight_evaluator.evaluate(
|
|
256
256
|
context,
|
|
257
|
-
feedback: { source: "expert_review" }
|
|
257
|
+
feedback: { source: "expert_review" } # 0.9 * 1.2 = 1.08, should clamp to 1.0
|
|
258
258
|
)
|
|
259
259
|
|
|
260
260
|
expect(result.weight).to eq(1.0)
|
|
@@ -330,7 +330,7 @@ RSpec.describe Examples::FeedbackAwareEvaluator do
|
|
|
330
330
|
)
|
|
331
331
|
|
|
332
332
|
expect(result.decision).to eq("approve")
|
|
333
|
-
expect(result.confidence).to eq(1.0)
|
|
333
|
+
expect(result.confidence).to eq(1.0) # Single evaluator normalized to 1.0
|
|
334
334
|
end
|
|
335
335
|
|
|
336
336
|
it "respects feedback in agent context" do
|
|
@@ -357,7 +357,7 @@ RSpec.describe Examples::FeedbackAwareEvaluator do
|
|
|
357
357
|
|
|
358
358
|
result = agent.decide(
|
|
359
359
|
context: {},
|
|
360
|
-
feedback: { past_accuracy: 0.5 }
|
|
360
|
+
feedback: { past_accuracy: 0.5 } # Reduces feedback_eval weight to 0.4
|
|
361
361
|
)
|
|
362
362
|
|
|
363
363
|
expect(result.decision).to eq("approve")
|
|
@@ -385,7 +385,7 @@ RSpec.describe Examples::FeedbackAwareEvaluator do
|
|
|
385
385
|
|
|
386
386
|
result_with_feedback = agent.decide(
|
|
387
387
|
context: {},
|
|
388
|
-
feedback: { past_accuracy: 0.5 }
|
|
388
|
+
feedback: { past_accuracy: 0.5 } # Reduces feedback_eval to 0.4
|
|
389
389
|
)
|
|
390
390
|
# MaxWeight still picks static_eval (0.9 > 0.4)
|
|
391
391
|
expect(result_with_feedback.decision).to eq("reject")
|
|
@@ -399,7 +399,7 @@ RSpec.describe Examples::FeedbackAwareEvaluator do
|
|
|
399
399
|
it "handles feedback with string keys" do
|
|
400
400
|
result = evaluator.evaluate(
|
|
401
401
|
context,
|
|
402
|
-
feedback: { "override" => "reject" }
|
|
402
|
+
feedback: { "override" => "reject" } # String key instead of symbol
|
|
403
403
|
)
|
|
404
404
|
|
|
405
405
|
# Should not match because code expects symbols
|