decision_agent 0.1.4 → 0.1.6
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 +84 -233
- data/lib/decision_agent/ab_testing/ab_testing_agent.rb +46 -10
- data/lib/decision_agent/agent.rb +5 -3
- data/lib/decision_agent/auth/access_audit_logger.rb +122 -0
- data/lib/decision_agent/auth/authenticator.rb +127 -0
- data/lib/decision_agent/auth/password_reset_manager.rb +57 -0
- data/lib/decision_agent/auth/password_reset_token.rb +33 -0
- data/lib/decision_agent/auth/permission.rb +29 -0
- data/lib/decision_agent/auth/permission_checker.rb +43 -0
- data/lib/decision_agent/auth/rbac_adapter.rb +278 -0
- data/lib/decision_agent/auth/rbac_config.rb +51 -0
- data/lib/decision_agent/auth/role.rb +56 -0
- data/lib/decision_agent/auth/session.rb +33 -0
- data/lib/decision_agent/auth/session_manager.rb +57 -0
- data/lib/decision_agent/auth/user.rb +70 -0
- data/lib/decision_agent/context.rb +24 -4
- data/lib/decision_agent/decision.rb +10 -3
- data/lib/decision_agent/dsl/condition_evaluator.rb +378 -1
- data/lib/decision_agent/dsl/schema_validator.rb +8 -1
- data/lib/decision_agent/errors.rb +38 -0
- data/lib/decision_agent/evaluation.rb +10 -3
- data/lib/decision_agent/evaluation_validator.rb +8 -13
- data/lib/decision_agent/monitoring/dashboard_server.rb +1 -0
- data/lib/decision_agent/monitoring/metrics_collector.rb +17 -5
- data/lib/decision_agent/testing/batch_test_importer.rb +373 -0
- data/lib/decision_agent/testing/batch_test_runner.rb +244 -0
- data/lib/decision_agent/testing/test_coverage_analyzer.rb +191 -0
- data/lib/decision_agent/testing/test_result_comparator.rb +235 -0
- data/lib/decision_agent/testing/test_scenario.rb +42 -0
- data/lib/decision_agent/version.rb +10 -1
- data/lib/decision_agent/versioning/activerecord_adapter.rb +1 -1
- data/lib/decision_agent/versioning/file_storage_adapter.rb +96 -28
- data/lib/decision_agent/web/middleware/auth_middleware.rb +45 -0
- data/lib/decision_agent/web/middleware/permission_middleware.rb +94 -0
- data/lib/decision_agent/web/public/app.js +184 -29
- data/lib/decision_agent/web/public/batch_testing.html +640 -0
- data/lib/decision_agent/web/public/index.html +37 -9
- data/lib/decision_agent/web/public/login.html +298 -0
- data/lib/decision_agent/web/public/users.html +679 -0
- data/lib/decision_agent/web/server.rb +873 -7
- data/lib/decision_agent.rb +52 -0
- data/lib/generators/decision_agent/install/templates/rule_version.rb +1 -1
- data/spec/ab_testing/ab_test_assignment_spec.rb +253 -0
- data/spec/ab_testing/ab_test_manager_spec.rb +282 -0
- data/spec/ab_testing/ab_testing_agent_spec.rb +481 -0
- data/spec/ab_testing/storage/adapter_spec.rb +64 -0
- data/spec/ab_testing/storage/memory_adapter_spec.rb +485 -0
- data/spec/advanced_operators_spec.rb +1003 -0
- data/spec/agent_spec.rb +40 -0
- data/spec/audit_adapters_spec.rb +18 -0
- data/spec/auth/access_audit_logger_spec.rb +394 -0
- data/spec/auth/authenticator_spec.rb +112 -0
- data/spec/auth/password_reset_spec.rb +294 -0
- data/spec/auth/permission_checker_spec.rb +207 -0
- data/spec/auth/permission_spec.rb +73 -0
- data/spec/auth/rbac_adapter_spec.rb +550 -0
- data/spec/auth/rbac_config_spec.rb +82 -0
- data/spec/auth/role_spec.rb +51 -0
- data/spec/auth/session_manager_spec.rb +172 -0
- data/spec/auth/session_spec.rb +112 -0
- data/spec/auth/user_spec.rb +130 -0
- data/spec/context_spec.rb +43 -0
- data/spec/decision_agent_spec.rb +96 -0
- data/spec/decision_spec.rb +423 -0
- data/spec/dsl/condition_evaluator_spec.rb +774 -0
- data/spec/evaluation_spec.rb +364 -0
- data/spec/evaluation_validator_spec.rb +165 -0
- data/spec/examples.txt +1542 -612
- data/spec/monitoring/metrics_collector_spec.rb +220 -2
- data/spec/monitoring/storage/activerecord_adapter_spec.rb +153 -1
- data/spec/monitoring/storage/base_adapter_spec.rb +61 -0
- data/spec/performance_optimizations_spec.rb +486 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/testing/batch_test_importer_spec.rb +693 -0
- data/spec/testing/batch_test_runner_spec.rb +307 -0
- data/spec/testing/test_coverage_analyzer_spec.rb +292 -0
- data/spec/testing/test_result_comparator_spec.rb +392 -0
- data/spec/testing/test_scenario_spec.rb +113 -0
- data/spec/versioning/adapter_spec.rb +156 -0
- data/spec/versioning_spec.rb +253 -0
- data/spec/web/middleware/auth_middleware_spec.rb +133 -0
- data/spec/web/middleware/permission_middleware_spec.rb +247 -0
- data/spec/web_ui_rack_spec.rb +1705 -0
- metadata +99 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6adecd350c20f336995ee4954e26f9be65dee45fb9d31838be996eda5928d3d
|
|
4
|
+
data.tar.gz: cb55474c53415d83a4d8a66598b8a4fcfd526472c5419612d9491df4ea02832f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f2f14271db7d5ba25d3e3604396c221cce07ed62824025d1686c4950a06cf2f064cd5498053aa49f613d09e8a0da62fccdc880f293209cb6e30dfe3119fe567c
|
|
7
|
+
data.tar.gz: b32ddb86d61e4588d4db68e6a4f441b2a35688918ddde831ef6b47a19782dfd36f68931d1cd9e1885f59fae1f63af96f683401c6f382e119c7fb23477940075c
|
data/README.md
CHANGED
|
@@ -11,11 +11,13 @@ A production-grade, deterministic, explainable, and auditable decision engine fo
|
|
|
11
11
|
|
|
12
12
|
## Why DecisionAgent?
|
|
13
13
|
|
|
14
|
+
DecisionAgent is designed for applications that require **deterministic, explainable, and auditable** decision-making:
|
|
15
|
+
|
|
14
16
|
- ✅ **Deterministic** - Same input always produces same output
|
|
15
17
|
- ✅ **Explainable** - Every decision includes human-readable reasoning
|
|
16
18
|
- ✅ **Auditable** - Reproduce any historical decision exactly
|
|
17
19
|
- ✅ **Framework-agnostic** - Pure Ruby, works anywhere
|
|
18
|
-
- ✅ **Production-ready** - Comprehensive testing, error handling, and versioning
|
|
20
|
+
- ✅ **Production-ready** - Comprehensive testing ([Coverage Report](coverage.md)), error handling, and versioning
|
|
19
21
|
|
|
20
22
|
## Installation
|
|
21
23
|
|
|
@@ -24,6 +26,7 @@ gem install decision_agent
|
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
Or add to your Gemfile:
|
|
29
|
+
|
|
27
30
|
```ruby
|
|
28
31
|
gem 'decision_agent'
|
|
29
32
|
```
|
|
@@ -57,11 +60,38 @@ puts result.confidence # => 0.9
|
|
|
57
60
|
puts result.explanations # => ["High value transaction"]
|
|
58
61
|
```
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
See [Code Examples](docs/CODE_EXAMPLES.md) for more comprehensive examples.
|
|
64
|
+
|
|
65
|
+
## Key Features
|
|
66
|
+
|
|
67
|
+
### Decision Making
|
|
68
|
+
- **Multiple Evaluators** - Combine rule-based, ML, and custom logic
|
|
69
|
+
- **Conflict Resolution** - Weighted average, consensus, threshold, max weight
|
|
70
|
+
- **Rich Context** - Nested data, dot notation, flexible operators
|
|
71
|
+
- **Advanced Operators** - String, numeric, date/time, collection, and geospatial operators
|
|
72
|
+
|
|
73
|
+
### Auditability & Compliance
|
|
74
|
+
- **Complete Audit Trails** - Every decision fully logged
|
|
75
|
+
- **Deterministic Replay** - Reproduce historical decisions exactly
|
|
76
|
+
- **RFC 8785 Canonical JSON** - Industry-standard deterministic hashing
|
|
77
|
+
- **Compliance Ready** - HIPAA, SOX, regulatory compliance support
|
|
78
|
+
|
|
79
|
+
### Developer Experience
|
|
80
|
+
- **Pluggable Architecture** - Custom evaluators, scoring, audit adapters
|
|
81
|
+
- **Framework Agnostic** - Works with Rails, Sinatra, or standalone
|
|
82
|
+
- **JSON Rule DSL** - Non-technical users can write rules
|
|
83
|
+
- **Visual Rule Builder** - Web UI for rule management
|
|
61
84
|
|
|
62
|
-
|
|
85
|
+
### Production Features
|
|
86
|
+
- **Real-time Monitoring** - Live dashboard with WebSocket updates
|
|
87
|
+
- **Prometheus Export** - Industry-standard metrics format
|
|
88
|
+
- **Intelligent Alerting** - Anomaly detection with customizable rules
|
|
89
|
+
- **Grafana Integration** - Pre-built dashboards and alert rules
|
|
90
|
+
- **Version Control** - Full rule version control and rollback
|
|
91
|
+
- **Thread-Safe** - Safe for multi-threaded servers and background jobs
|
|
92
|
+
- **High Performance** - 10,000+ decisions/second, ~0.1ms latency
|
|
63
93
|
|
|
64
|
-
|
|
94
|
+
## Web UI - Visual Rule Builder
|
|
65
95
|
|
|
66
96
|
Launch the visual rule builder:
|
|
67
97
|
|
|
@@ -71,246 +101,50 @@ decision_agent web
|
|
|
71
101
|
|
|
72
102
|
Open [http://localhost:4567](http://localhost:4567) in your browser.
|
|
73
103
|
|
|
74
|
-
###
|
|
75
|
-
|
|
76
|
-
Add to your `config/routes.rb`:
|
|
104
|
+
### Integration
|
|
77
105
|
|
|
106
|
+
**Rails:**
|
|
78
107
|
```ruby
|
|
79
108
|
require 'decision_agent/web/server'
|
|
80
|
-
|
|
81
109
|
Rails.application.routes.draw do
|
|
82
|
-
# Mount DecisionAgent Web UI
|
|
83
|
-
mount DecisionAgent::Web::Server, at: '/decision_agent'
|
|
84
|
-
end
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Then visit `http://localhost:3000/decision_agent` in your browser.
|
|
88
|
-
|
|
89
|
-
**With Authentication:**
|
|
90
|
-
|
|
91
|
-
```ruby
|
|
92
|
-
authenticate :user, ->(user) { user.admin? } do
|
|
93
110
|
mount DecisionAgent::Web::Server, at: '/decision_agent'
|
|
94
111
|
end
|
|
95
112
|
```
|
|
96
113
|
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
**Rack/Sinatra:**
|
|
99
115
|
```ruby
|
|
100
|
-
# config.ru
|
|
101
116
|
require 'decision_agent/web/server'
|
|
102
|
-
|
|
103
117
|
map '/decision_agent' do
|
|
104
118
|
run DecisionAgent::Web::Server
|
|
105
119
|
end
|
|
106
120
|
```
|
|
107
121
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
See [Web UI Rails Integration Guide](wiki/WEB_UI_RAILS_INTEGRATION.md) for detailed setup instructions.
|
|
122
|
+
See [Web UI Integration Guide](docs/WEB_UI_RAILS_INTEGRATION.md) for detailed setup.
|
|
111
123
|
|
|
112
124
|
## Monitoring & Analytics
|
|
113
125
|
|
|
114
126
|
Real-time monitoring, metrics, and alerting for production environments.
|
|
115
127
|
|
|
116
|
-
### Quick Start
|
|
117
|
-
|
|
118
128
|
```ruby
|
|
119
129
|
require 'decision_agent/monitoring/metrics_collector'
|
|
120
130
|
require 'decision_agent/monitoring/dashboard_server'
|
|
121
131
|
|
|
122
|
-
# Initialize metrics collection
|
|
123
132
|
collector = DecisionAgent::Monitoring::MetricsCollector.new(window_size: 3600)
|
|
124
|
-
|
|
125
|
-
# Start real-time dashboard
|
|
126
133
|
DecisionAgent::Monitoring::DashboardServer.start!(
|
|
127
134
|
port: 4568,
|
|
128
135
|
metrics_collector: collector
|
|
129
136
|
)
|
|
130
|
-
|
|
131
|
-
# Record decisions
|
|
132
|
-
agent = DecisionAgent::Agent.new(evaluators: [evaluator])
|
|
133
|
-
result = agent.decide(context: { amount: 1500 })
|
|
134
|
-
collector.record_decision(result, context, duration_ms: 25.5)
|
|
135
137
|
```
|
|
136
138
|
|
|
137
139
|
Open [http://localhost:4568](http://localhost:4568) for the monitoring dashboard.
|
|
138
140
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
- **Grafana Integration** - Pre-built dashboards and alert rules
|
|
145
|
-
- **Custom KPIs** - Track business-specific metrics
|
|
146
|
-
- **Thread-Safe** - Production-ready performance
|
|
147
|
-
|
|
148
|
-
### Prometheus & Grafana
|
|
149
|
-
|
|
150
|
-
```yaml
|
|
151
|
-
# prometheus.yml
|
|
152
|
-
scrape_configs:
|
|
153
|
-
- job_name: 'decision_agent'
|
|
154
|
-
static_configs:
|
|
155
|
-
- targets: ['localhost:4568']
|
|
156
|
-
metrics_path: '/metrics'
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Import the pre-built Grafana dashboard from [grafana/decision_agent_dashboard.json](grafana/decision_agent_dashboard.json).
|
|
160
|
-
|
|
161
|
-
### Alert Management
|
|
162
|
-
|
|
163
|
-
```ruby
|
|
164
|
-
alert_manager = DecisionAgent::Monitoring::AlertManager.new(
|
|
165
|
-
metrics_collector: collector
|
|
166
|
-
)
|
|
141
|
+
**Features:**
|
|
142
|
+
- Real-time dashboard with WebSocket updates
|
|
143
|
+
- Prometheus metrics export
|
|
144
|
+
- Intelligent alerting with anomaly detection
|
|
145
|
+
- Grafana integration with pre-built dashboards
|
|
167
146
|
|
|
168
|
-
|
|
169
|
-
alert_manager.add_rule(
|
|
170
|
-
name: 'High Error Rate',
|
|
171
|
-
condition: AlertManager.high_error_rate(threshold: 0.1),
|
|
172
|
-
severity: :critical
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
# Register alert handlers
|
|
176
|
-
alert_manager.add_handler do |alert|
|
|
177
|
-
SlackNotifier.notify("🚨 #{alert[:message]}")
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Start monitoring
|
|
181
|
-
alert_manager.start_monitoring(interval: 60)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
See [Monitoring & Analytics Guide](wiki/MONITORING_AND_ANALYTICS.md) for complete documentation.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
## Key Features
|
|
188
|
-
|
|
189
|
-
### Decision Making
|
|
190
|
-
- **Multiple Evaluators** - Combine rule-based, ML, and custom logic
|
|
191
|
-
- **Conflict Resolution** - Weighted average, consensus, threshold, max weight
|
|
192
|
-
- **Rich Context** - Nested data, dot notation, flexible operators
|
|
193
|
-
|
|
194
|
-
### Auditability
|
|
195
|
-
- **Complete Audit Trails** - Every decision fully logged
|
|
196
|
-
- **Deterministic Replay** - Reproduce historical decisions exactly
|
|
197
|
-
- **Compliance Ready** - HIPAA, SOX, regulatory compliance support
|
|
198
|
-
|
|
199
|
-
### Flexibility
|
|
200
|
-
- **Pluggable Architecture** - Custom evaluators, scoring, audit adapters
|
|
201
|
-
- **Framework Agnostic** - Works with Rails, Sinatra, or standalone
|
|
202
|
-
- **JSON Rule DSL** - Non-technical users can write rules
|
|
203
|
-
- **Visual Rule Builder** - Web UI for rule management
|
|
204
|
-
|
|
205
|
-
### Monitoring & Observability
|
|
206
|
-
- **Real-time Metrics** - Live dashboard with WebSocket updates (<1 second latency)
|
|
207
|
-
- **Prometheus Export** - Industry-standard metrics format at `/metrics` endpoint
|
|
208
|
-
- **Intelligent Alerting** - Anomaly detection with customizable rules and severity levels
|
|
209
|
-
- **Grafana Integration** - Pre-built dashboards and alert configurations in `grafana/` directory
|
|
210
|
-
- **Custom KPIs** - Track business-specific metrics with thread-safe operations
|
|
211
|
-
- **MonitoredAgent** - Drop-in replacement that auto-records all metrics
|
|
212
|
-
- **AlertManager** - Built-in anomaly detection (error rates, latency spikes, low confidence)
|
|
213
|
-
|
|
214
|
-
### Production Ready
|
|
215
|
-
- **Comprehensive Testing** - 90%+ code coverage
|
|
216
|
-
- **Error Handling** - Clear, actionable error messages
|
|
217
|
-
- **Versioning** - Full rule version control and rollback
|
|
218
|
-
- **Performance** - Fast, zero external dependencies
|
|
219
|
-
- **Thread-Safe** - Safe for multi-threaded servers and background jobs
|
|
220
|
-
|
|
221
|
-
## Examples
|
|
222
|
-
|
|
223
|
-
```ruby
|
|
224
|
-
# Multiple evaluators with conflict resolution
|
|
225
|
-
agent = DecisionAgent::Agent.new(
|
|
226
|
-
evaluators: [rule_evaluator, ml_evaluator],
|
|
227
|
-
scoring_strategy: DecisionAgent::Scoring::Consensus.new(minimum_agreement: 0.7),
|
|
228
|
-
audit_adapter: DecisionAgent::Audit::LoggerAdapter.new
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
# Complex rules with nested conditions
|
|
232
|
-
rules = {
|
|
233
|
-
version: "1.0",
|
|
234
|
-
ruleset: "fraud_detection",
|
|
235
|
-
rules: [{
|
|
236
|
-
id: "suspicious_activity",
|
|
237
|
-
if: {
|
|
238
|
-
all: [
|
|
239
|
-
{ field: "amount", op: "gt", value: 10000 },
|
|
240
|
-
{ any: [
|
|
241
|
-
{ field: "user.country", op: "in", value: ["XX", "YY"] },
|
|
242
|
-
{ field: "velocity", op: "gt", value: 5 }
|
|
243
|
-
]}
|
|
244
|
-
]
|
|
245
|
-
},
|
|
246
|
-
then: { decision: "flag_for_review", weight: 0.95, reason: "Suspicious patterns detected" }
|
|
247
|
-
}]
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
See [examples/](examples/) for complete working examples.
|
|
252
|
-
|
|
253
|
-
## Thread-Safety Guarantees
|
|
254
|
-
|
|
255
|
-
DecisionAgent is designed to be **thread-safe and FAST** for use in multi-threaded environments:
|
|
256
|
-
|
|
257
|
-
### Performance
|
|
258
|
-
- **10,000+ decisions/second** throughput
|
|
259
|
-
- **~0.1ms average latency** per decision
|
|
260
|
-
- **Zero performance overhead** from thread-safety
|
|
261
|
-
- **Linear scalability** with thread count
|
|
262
|
-
|
|
263
|
-
### Safe Concurrent Usage
|
|
264
|
-
- **Agent instances** can be shared across threads safely
|
|
265
|
-
- **Evaluators** are immutable after initialization
|
|
266
|
-
- **Decisions and Evaluations** are deeply frozen
|
|
267
|
-
- **File storage** uses mutex-protected operations
|
|
268
|
-
|
|
269
|
-
### Best Practices
|
|
270
|
-
```ruby
|
|
271
|
-
# Safe: Reuse agent instance across threads
|
|
272
|
-
agent = DecisionAgent::Agent.new(evaluators: [evaluator])
|
|
273
|
-
|
|
274
|
-
Thread.new { agent.decide(context: { user_id: 1 }) }
|
|
275
|
-
Thread.new { agent.decide(context: { user_id: 2 }) }
|
|
276
|
-
|
|
277
|
-
# Safe: Share evaluators across agent instances
|
|
278
|
-
evaluator = DecisionAgent::Evaluators::JsonRuleEvaluator.new(rules_json: rules)
|
|
279
|
-
agent1 = DecisionAgent::Agent.new(evaluators: [evaluator])
|
|
280
|
-
agent2 = DecisionAgent::Agent.new(evaluators: [evaluator])
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### What's Frozen
|
|
284
|
-
All data structures are deeply frozen to prevent mutation:
|
|
285
|
-
- Decision objects (decision, confidence, explanations, evaluations)
|
|
286
|
-
- Evaluation objects (decision, weight, reason, metadata)
|
|
287
|
-
- Context data
|
|
288
|
-
- Rule definitions in evaluators
|
|
289
|
-
|
|
290
|
-
This ensures safe concurrent access without race conditions.
|
|
291
|
-
|
|
292
|
-
### RFC 8785 Canonical JSON
|
|
293
|
-
DecisionAgent uses **RFC 8785 (JSON Canonicalization Scheme)** for deterministic audit hashing:
|
|
294
|
-
|
|
295
|
-
- **Industry Standard** - Official IETF specification for canonical JSON
|
|
296
|
-
- **Cryptographically Sound** - Ensures deterministic hashing of decision payloads
|
|
297
|
-
- **Reproducible** - Same decision always produces same audit hash
|
|
298
|
-
- **Interoperable** - Compatible with other systems using RFC 8785
|
|
299
|
-
|
|
300
|
-
Every decision includes a deterministic SHA-256 hash in the audit payload, enabling:
|
|
301
|
-
- Tamper detection in audit logs
|
|
302
|
-
- Exact replay verification
|
|
303
|
-
- Regulatory compliance documentation
|
|
304
|
-
|
|
305
|
-
Learn more: [RFC 8785 Specification](https://datatracker.ietf.org/doc/html/rfc8785)
|
|
306
|
-
|
|
307
|
-
### Performance Benchmark
|
|
308
|
-
Run the included benchmark to verify zero overhead:
|
|
309
|
-
```bash
|
|
310
|
-
ruby examples/thread_safe_performance.rb
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
See [THREAD_SAFETY.md](wiki/THREAD_SAFETY.md) for detailed implementation guide and [PERFORMANCE_AND_THREAD_SAFETY.md](wiki/PERFORMANCE_AND_THREAD_SAFETY.md) for detailed performance analysis.
|
|
147
|
+
See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complete documentation.
|
|
314
148
|
|
|
315
149
|
## When to Use DecisionAgent
|
|
316
150
|
|
|
@@ -328,31 +162,48 @@ See [THREAD_SAFETY.md](wiki/THREAD_SAFETY.md) for detailed implementation guide
|
|
|
328
162
|
|
|
329
163
|
## Documentation
|
|
330
164
|
|
|
331
|
-
|
|
165
|
+
### Getting Started
|
|
332
166
|
- [Installation](#installation)
|
|
333
167
|
- [Quick Start](#quick-start)
|
|
334
|
-
- [Examples](
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
- [
|
|
339
|
-
- [
|
|
340
|
-
- [
|
|
341
|
-
- [
|
|
342
|
-
- [
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
- [
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
- [
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
- [
|
|
168
|
+
- [Code Examples](docs/CODE_EXAMPLES.md) - Comprehensive code snippets
|
|
169
|
+
- [Examples Directory](examples/README.md) - Working examples with explanations
|
|
170
|
+
|
|
171
|
+
### Core Features
|
|
172
|
+
- [Advanced Operators](docs/ADVANCED_OPERATORS.md) - String, numeric, date/time, collection, and geospatial operators
|
|
173
|
+
- [Versioning System](docs/VERSIONING.md) - Version control for rules
|
|
174
|
+
- [A/B Testing](docs/AB_TESTING.md) - Compare rule versions with statistical analysis
|
|
175
|
+
- [Web UI](docs/WEB_UI.md) - Visual rule builder
|
|
176
|
+
- [Web UI Setup](docs/WEB_UI_SETUP.md) - Setup guide
|
|
177
|
+
- [Web UI Rails Integration](docs/WEB_UI_RAILS_INTEGRATION.md) - Mount in Rails/Rack apps
|
|
178
|
+
- [Monitoring & Analytics](docs/MONITORING_AND_ANALYTICS.md) - Real-time monitoring, metrics, and alerting
|
|
179
|
+
- [Monitoring Architecture](docs/MONITORING_ARCHITECTURE.md) - System architecture and design
|
|
180
|
+
|
|
181
|
+
### Performance & Thread-Safety
|
|
182
|
+
- [Performance & Thread-Safety Summary](docs/PERFORMANCE_AND_THREAD_SAFETY.md) - Benchmarks and production readiness
|
|
183
|
+
- [Thread-Safety Implementation](docs/THREAD_SAFETY.md) - Technical implementation guide
|
|
184
|
+
|
|
185
|
+
### Reference
|
|
186
|
+
- [API Contract](docs/API_CONTRACT.md) - Full API reference
|
|
187
|
+
- [Changelog](docs/CHANGELOG.md) - Version history
|
|
188
|
+
- [Code Coverage Report](coverage.md) - Test coverage statistics
|
|
189
|
+
|
|
190
|
+
### More Resources
|
|
191
|
+
- [Documentation Home](docs/README.md) - Documentation index
|
|
354
192
|
- [GitHub Issues](https://github.com/samaswin87/decision_agent/issues) - Report bugs or request features
|
|
355
193
|
|
|
194
|
+
## Thread-Safety & Performance
|
|
195
|
+
|
|
196
|
+
DecisionAgent is designed to be **thread-safe and FAST** for use in multi-threaded environments:
|
|
197
|
+
|
|
198
|
+
- **10,000+ decisions/second** throughput
|
|
199
|
+
- **~0.1ms average latency** per decision
|
|
200
|
+
- **Zero performance overhead** from thread-safety
|
|
201
|
+
- **Linear scalability** with thread count
|
|
202
|
+
|
|
203
|
+
All data structures are deeply frozen to prevent mutation, ensuring safe concurrent access without race conditions.
|
|
204
|
+
|
|
205
|
+
See [Thread-Safety Guide](docs/THREAD_SAFETY.md) and [Performance Analysis](docs/PERFORMANCE_AND_THREAD_SAFETY.md) for details.
|
|
206
|
+
|
|
356
207
|
## Contributing
|
|
357
208
|
|
|
358
209
|
1. Fork the repository
|
|
@@ -363,8 +214,8 @@ See [THREAD_SAFETY.md](wiki/THREAD_SAFETY.md) for detailed implementation guide
|
|
|
363
214
|
## Support
|
|
364
215
|
|
|
365
216
|
- **Issues**: [GitHub Issues](https://github.com/samaswin87/decision_agent/issues)
|
|
366
|
-
- **Documentation**: [
|
|
367
|
-
- **Examples**: [
|
|
217
|
+
- **Documentation**: [Documentation](docs/README.md)
|
|
218
|
+
- **Examples**: [Examples Directory](examples/README.md)
|
|
368
219
|
|
|
369
220
|
## License
|
|
370
221
|
|
|
@@ -10,18 +10,23 @@ module DecisionAgent
|
|
|
10
10
|
# @param evaluators [Array] Base evaluators (can be overridden by versioned rules)
|
|
11
11
|
# @param scoring_strategy [Scoring::Base] Scoring strategy
|
|
12
12
|
# @param audit_adapter [Audit::Adapter] Audit adapter
|
|
13
|
+
# @param cache_agents [Boolean] Whether to cache agents by version_id (default: true)
|
|
13
14
|
def initialize(
|
|
14
15
|
ab_test_manager:,
|
|
15
16
|
version_manager: nil,
|
|
16
17
|
evaluators: [],
|
|
17
18
|
scoring_strategy: nil,
|
|
18
|
-
audit_adapter: nil
|
|
19
|
+
audit_adapter: nil,
|
|
20
|
+
cache_agents: true
|
|
19
21
|
)
|
|
20
22
|
@ab_test_manager = ab_test_manager
|
|
21
23
|
@version_manager = version_manager || ab_test_manager.version_manager
|
|
22
24
|
@base_evaluators = evaluators
|
|
23
25
|
@scoring_strategy = scoring_strategy
|
|
24
26
|
@audit_adapter = audit_adapter
|
|
27
|
+
@cache_agents = cache_agents
|
|
28
|
+
@agent_cache = {} # Cache agents by version_id
|
|
29
|
+
@agent_cache_mutex = Mutex.new
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
# Make a decision with A/B testing support
|
|
@@ -64,21 +69,29 @@ module DecisionAgent
|
|
|
64
69
|
@ab_test_manager.active_tests
|
|
65
70
|
end
|
|
66
71
|
|
|
72
|
+
# Clear the agent cache (useful for testing or when versions are updated)
|
|
73
|
+
def clear_agent_cache!
|
|
74
|
+
@agent_cache_mutex.synchronize { @agent_cache.clear }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get cache statistics
|
|
78
|
+
def cache_stats
|
|
79
|
+
@agent_cache_mutex.synchronize do
|
|
80
|
+
{
|
|
81
|
+
cached_agents: @agent_cache.size,
|
|
82
|
+
version_ids: @agent_cache.keys
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
67
87
|
private
|
|
68
88
|
|
|
69
89
|
def decide_with_ab_test(context, feedback, ab_test_id, user_id)
|
|
70
90
|
# Assign variant
|
|
71
91
|
assignment = @ab_test_manager.assign_variant(test_id: ab_test_id, user_id: user_id)
|
|
72
92
|
|
|
73
|
-
# Get
|
|
74
|
-
|
|
75
|
-
raise VersionNotFoundError, "Version not found: #{assignment[:version_id]}" unless version
|
|
76
|
-
|
|
77
|
-
# Build evaluators from the versioned rule content
|
|
78
|
-
evaluators = build_evaluators_from_version(version)
|
|
79
|
-
|
|
80
|
-
# Create agent with version-specific evaluators
|
|
81
|
-
agent = build_agent(evaluators)
|
|
93
|
+
# Get or build cached agent for this version
|
|
94
|
+
agent = get_or_build_agent_for_version(assignment[:version_id])
|
|
82
95
|
|
|
83
96
|
# Make decision
|
|
84
97
|
decision = agent.decide(context: context, feedback: feedback)
|
|
@@ -105,6 +118,29 @@ module DecisionAgent
|
|
|
105
118
|
}
|
|
106
119
|
end
|
|
107
120
|
|
|
121
|
+
# Get or build agent for a specific version (with caching)
|
|
122
|
+
def get_or_build_agent_for_version(version_id)
|
|
123
|
+
return build_agent_for_version(version_id) unless @cache_agents
|
|
124
|
+
|
|
125
|
+
# Check cache first (fast path without lock for reads)
|
|
126
|
+
cached = @agent_cache[version_id]
|
|
127
|
+
return cached if cached
|
|
128
|
+
|
|
129
|
+
# Cache miss - acquire lock and build
|
|
130
|
+
@agent_cache_mutex.synchronize do
|
|
131
|
+
# Double-check after acquiring lock (another thread may have built it)
|
|
132
|
+
@agent_cache[version_id] ||= build_agent_for_version(version_id)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def build_agent_for_version(version_id)
|
|
137
|
+
version = @version_manager.get_version(version_id: version_id)
|
|
138
|
+
raise VersionNotFoundError, "Version not found: #{version_id}" unless version
|
|
139
|
+
|
|
140
|
+
evaluators = build_evaluators_from_version(version)
|
|
141
|
+
build_agent(evaluators)
|
|
142
|
+
end
|
|
143
|
+
|
|
108
144
|
def build_agent(evaluators)
|
|
109
145
|
Agent.new(
|
|
110
146
|
evaluators: evaluators.empty? ? @base_evaluators : evaluators,
|
data/lib/decision_agent/agent.rb
CHANGED
|
@@ -6,10 +6,12 @@ module DecisionAgent
|
|
|
6
6
|
class Agent
|
|
7
7
|
attr_reader :evaluators, :scoring_strategy, :audit_adapter
|
|
8
8
|
|
|
9
|
-
def initialize(evaluators:, scoring_strategy: nil, audit_adapter: nil)
|
|
9
|
+
def initialize(evaluators:, scoring_strategy: nil, audit_adapter: nil, validate_evaluations: nil)
|
|
10
10
|
@evaluators = Array(evaluators)
|
|
11
11
|
@scoring_strategy = scoring_strategy || Scoring::WeightedAverage.new
|
|
12
12
|
@audit_adapter = audit_adapter || Audit::NullAdapter.new
|
|
13
|
+
# Default to validating in development, skip in production for performance
|
|
14
|
+
@validate_evaluations = validate_evaluations.nil? ? (ENV["RAILS_ENV"] != "production") : validate_evaluations
|
|
13
15
|
|
|
14
16
|
validate_configuration!
|
|
15
17
|
|
|
@@ -24,8 +26,8 @@ module DecisionAgent
|
|
|
24
26
|
|
|
25
27
|
raise NoEvaluationsError if evaluations.empty?
|
|
26
28
|
|
|
27
|
-
# Validate all evaluations for correctness and thread-safety
|
|
28
|
-
EvaluationValidator.validate_all!(evaluations)
|
|
29
|
+
# Validate all evaluations for correctness and thread-safety (optional for performance)
|
|
30
|
+
EvaluationValidator.validate_all!(evaluations) if @validate_evaluations
|
|
29
31
|
|
|
30
32
|
scored_result = @scoring_strategy.score(evaluations)
|
|
31
33
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "../audit/adapter"
|
|
3
|
+
|
|
4
|
+
module DecisionAgent
|
|
5
|
+
module Auth
|
|
6
|
+
class AccessAuditLogger
|
|
7
|
+
attr_reader :adapter
|
|
8
|
+
|
|
9
|
+
def initialize(adapter: nil)
|
|
10
|
+
@adapter = adapter || Audit::InMemoryAccessAdapter.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def log_authentication(event_type, user_id:, email: nil, success: true, reason: nil)
|
|
14
|
+
log_entry = {
|
|
15
|
+
event_type: event_type.to_s,
|
|
16
|
+
user_id: user_id,
|
|
17
|
+
email: email,
|
|
18
|
+
success: success,
|
|
19
|
+
reason: reason,
|
|
20
|
+
timestamp: Time.now.utc.iso8601,
|
|
21
|
+
ip_address: nil # Can be set by middleware
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@adapter.record_access(log_entry)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def log_permission_check(user_id:, permission:, resource_type: nil, resource_id: nil, granted: true)
|
|
28
|
+
log_entry = {
|
|
29
|
+
event_type: "permission_check",
|
|
30
|
+
user_id: user_id,
|
|
31
|
+
permission: permission.to_s,
|
|
32
|
+
resource_type: resource_type,
|
|
33
|
+
resource_id: resource_id,
|
|
34
|
+
granted: granted,
|
|
35
|
+
timestamp: Time.now.utc.iso8601
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@adapter.record_access(log_entry)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def log_access(user_id:, action:, resource_type: nil, resource_id: nil, success: true)
|
|
42
|
+
log_entry = {
|
|
43
|
+
event_type: "access",
|
|
44
|
+
user_id: user_id,
|
|
45
|
+
action: action.to_s,
|
|
46
|
+
resource_type: resource_type,
|
|
47
|
+
resource_id: resource_id,
|
|
48
|
+
success: success,
|
|
49
|
+
timestamp: Time.now.utc.iso8601
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@adapter.record_access(log_entry)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def query(filters = {})
|
|
56
|
+
@adapter.query_access_logs(filters)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
module Audit
|
|
62
|
+
class AccessAdapter < Adapter
|
|
63
|
+
def record_access(log_entry)
|
|
64
|
+
raise NotImplementedError, "Subclasses must implement #record_access"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def query_access_logs(filters = {})
|
|
68
|
+
raise NotImplementedError, "Subclasses must implement #query_access_logs"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class InMemoryAccessAdapter < AccessAdapter
|
|
73
|
+
def initialize
|
|
74
|
+
super
|
|
75
|
+
@logs = []
|
|
76
|
+
@mutex = Mutex.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def record_access(log_entry)
|
|
80
|
+
@mutex.synchronize do
|
|
81
|
+
@logs << log_entry.dup
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def query_access_logs(filters = {})
|
|
86
|
+
@mutex.synchronize do
|
|
87
|
+
results = @logs.dup
|
|
88
|
+
|
|
89
|
+
results.select! { |log| log[:user_id] == filters[:user_id] } if filters[:user_id]
|
|
90
|
+
|
|
91
|
+
results.select! { |log| log[:event_type] == filters[:event_type].to_s } if filters[:event_type]
|
|
92
|
+
|
|
93
|
+
if filters[:start_time]
|
|
94
|
+
start_time = filters[:start_time].is_a?(String) ? Time.parse(filters[:start_time]) : filters[:start_time]
|
|
95
|
+
results.select! { |log| Time.parse(log[:timestamp]) >= start_time }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if filters[:end_time]
|
|
99
|
+
end_time = filters[:end_time].is_a?(String) ? Time.parse(filters[:end_time]) : filters[:end_time]
|
|
100
|
+
results.select! { |log| Time.parse(log[:timestamp]) <= end_time }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
results = results.last(filters[:limit]) if filters[:limit]
|
|
104
|
+
|
|
105
|
+
results.reverse # Most recent first
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def all_logs
|
|
110
|
+
@mutex.synchronize do
|
|
111
|
+
@logs.dup
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def clear
|
|
116
|
+
@mutex.synchronize do
|
|
117
|
+
@logs.clear
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|