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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '095f5b6604126a4288548c648efbfb3f88cc94bf2a51ba84b21f422286c9c0f3'
4
- data.tar.gz: 0f5475b14f32d3f7524ba63d21152bda4d088312dc1b154a6c15dd73f8e038e0
3
+ metadata.gz: c6c760652b22f7cf3e4524aec1757f1fba1fc21c206c960ad3836eed18279f13
4
+ data.tar.gz: e0c2f21dfac36d7a4ce7935d6ea38375265c64e15eec4c276c514a1bf759dd6e
5
5
  SHA512:
6
- metadata.gz: 60f10c33779fac9cf26ed698a704b91e4ff65ca200d76870fa593a9074506b10a7791a471ab5cbb0fcdc9603145dc965131508619f0f1356f163cdaf069f49c9
7
- data.tar.gz: d60e5eaf6d7350a55047594481c00360fa188e66db11e84cf863c7c734d1210c777e7fb358a689470d84256dd225b22376deb50648443dd0257a5041816086c1
6
+ metadata.gz: d9a402f62fa86a4cf83d727f048b6de8a8d67ab317cbc3dd2d681cd537a2a1a68a1bbe77b2f34a4c6f4a805babace2e7c6d449435d8fef9831dff884152b441a
7
+ data.tar.gz: 2b00ae31f1e89caa62c004c6e649296e1c6a193faf90ded7ef2fd6e930b3c5547113d028349de2b6904bfb9e2371fd37335251b6391bac4315cda1ca6407c4e8
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/decision_agent.svg)](https://badge.fury.io/rb/decision_agent)
4
4
  [![CI](https://github.com/samaswin/decision_agent/actions/workflows/ci.yml/badge.svg)](https://github.com/samaswin/decision_agent/actions/workflows/ci.yml)
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
6
- [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7.0-red.svg)](https://www.ruby-lang.org)
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.0.0-red.svg)](https://www.ruby-lang.org)
7
7
 
8
8
  A production-grade, deterministic, explainable, and auditable decision engine for Ruby.
9
9
 
@@ -14,8 +14,8 @@ A production-grade, deterministic, explainable, and auditable decision engine fo
14
14
  DecisionAgent is designed for applications that require **deterministic, explainable, and auditable** decision-making:
15
15
 
16
16
  - ✅ **Deterministic** - Same input always produces same output
17
- - ✅ **Explainable** - Every decision includes human-readable reasoning
18
- - ✅ **Auditable** - Reproduce any historical decision exactly
17
+ - ✅ **Explainable** - Every decision includes human-readable reasoning and machine-readable condition traces
18
+ - ✅ **Auditable** - Reproduce any historical decision exactly with complete explainability
19
19
  - ✅ **Framework-agnostic** - Pure Ruby, works anywhere
20
20
  - ✅ **Production-ready** - Comprehensive testing ([Coverage Report](coverage.md)), error handling, and versioning
21
21
 
@@ -55,9 +55,12 @@ agent = DecisionAgent::Agent.new(evaluators: [evaluator])
55
55
  # Make decision
56
56
  result = agent.decide(context: { amount: 1500 })
57
57
 
58
- puts result.decision # => "approve"
59
- puts result.confidence # => 0.9
60
- puts result.explanations # => ["High value transaction"]
58
+ puts result.decision # => "approve"
59
+ puts result.confidence # => 0.9
60
+ puts result.explanations # => ["High value transaction"]
61
+ puts result.because # => ["amount > 1000"]
62
+ puts result.failed_conditions # => []
63
+ puts result.explainability # => { decision: "approve", because: [...], failed_conditions: [] }
61
64
  ```
62
65
 
63
66
  See [Code Examples](docs/CODE_EXAMPLES.md) for more comprehensive examples.
@@ -72,23 +75,48 @@ See [Code Examples](docs/CODE_EXAMPLES.md) for more comprehensive examples.
72
75
 
73
76
  ### Auditability & Compliance
74
77
  - **Complete Audit Trails** - Every decision fully logged
78
+ - **Explainability Layer** - Machine-readable condition traces for every decision
79
+ - `result.because` - Conditions that led to the decision
80
+ - `result.failed_conditions` - Conditions that failed
81
+ - `result.explainability` - Complete machine-readable explainability data
75
82
  - **Deterministic Replay** - Reproduce historical decisions exactly
76
83
  - **RFC 8785 Canonical JSON** - Industry-standard deterministic hashing
77
84
  - **Compliance Ready** - HIPAA, SOX, regulatory compliance support
78
85
 
86
+ ### Testing & Simulation
87
+ - **Simulation & What-If Analysis** - Test rule changes before deployment
88
+ - **Historical Replay / Backtesting** - Replay past decisions with new rules (CSV, JSON, database import)
89
+ - **What-If Analysis** - Simulate scenarios and sensitivity analysis with decision boundary visualization
90
+ - **Impact Analysis** - Quantify rule change effects (decision distribution, confidence shifts, performance impact)
91
+ - **Shadow Testing** - Compare new rules against production without affecting outcomes
92
+ - **Monte Carlo Simulation** - Model probabilistic inputs and understand decision outcome probabilities
93
+ - **Batch Testing** - Test rules against large datasets with CSV/Excel import, coverage analysis, and resume capability
94
+ - **A/B Testing** - Champion/Challenger testing with statistical significance analysis
95
+
96
+ ### Security & Access Control
97
+ - **Role-Based Access Control (RBAC)** - Enterprise-grade authentication and authorization
98
+ - Built-in user/role system with bcrypt password hashing
99
+ - Configurable adapters for Devise, CanCanCan, Pundit, or custom auth systems
100
+ - 5 default roles (Admin, Editor, Viewer, Auditor, Approver) with 7 permissions
101
+ - Password reset functionality with secure token management
102
+ - Comprehensive access audit logging for compliance
103
+ - Web UI integration with login and user management pages
104
+
79
105
  ### Developer Experience
80
106
  - **Pluggable Architecture** - Custom evaluators, scoring, audit adapters
81
- - **Framework Agnostic** - Works with Rails, Sinatra, or standalone
107
+ - **Framework Agnostic** - Works with Rails, Rack, or standalone
82
108
  - **JSON Rule DSL** - Non-technical users can write rules
83
109
  - **DMN 1.3 Support** - Industry-standard Decision Model and Notation with full FEEL expression language
84
110
  - **Visual Rule Builder** - Web UI for rule management and DMN modeler
111
+ - **CLI Tools** - Command-line interface for DMN import/export and web server
85
112
 
86
113
  ### Production Features
87
114
  - **Real-time Monitoring** - Live dashboard with WebSocket updates
115
+ - **Persistent Monitoring** - Database storage for long-term analytics (PostgreSQL, MySQL, SQLite)
88
116
  - **Prometheus Export** - Industry-standard metrics format
89
117
  - **Intelligent Alerting** - Anomaly detection with customizable rules
90
118
  - **Grafana Integration** - Pre-built dashboards and alert rules
91
- - **Version Control** - Full rule version control and rollback
119
+ - **Version Control** - Full rule version control, rollback, and history ([Versioning Guide](docs/VERSIONING.md))
92
120
  - **Thread-Safe** - Safe for multi-threaded servers and background jobs
93
121
  - **High Performance** - 10,000+ decisions/second, ~0.1ms latency
94
122
 
@@ -102,6 +130,8 @@ decision_agent web
102
130
 
103
131
  Open [http://localhost:4567](http://localhost:4567) in your browser.
104
132
 
133
+ The Web UI includes a **DMN visual modeler** at `/dmn/editor` for building and editing decision tables.
134
+
105
135
  ### Integration
106
136
 
107
137
  **Rails:**
@@ -112,7 +142,7 @@ Rails.application.routes.draw do
112
142
  end
113
143
  ```
114
144
 
115
- **Rack/Sinatra:**
145
+ **Rack:**
116
146
  ```ruby
117
147
  require 'decision_agent/web/server'
118
148
  map '/decision_agent' do
@@ -120,7 +150,7 @@ map '/decision_agent' do
120
150
  end
121
151
  ```
122
152
 
123
- See [Web UI Integration Guide](docs/WEB_UI_RAILS_INTEGRATION.md) for detailed setup.
153
+ See [Web UI Integration Guide](docs/WEB_UI_INTEGRATION.md) for detailed setup with Rails, Sinatra, Hanami, and other frameworks.
124
154
 
125
155
  ## DMN (Decision Model and Notation) Support
126
156
 
@@ -175,11 +205,159 @@ Open [http://localhost:4568](http://localhost:4568) for the monitoring dashboard
175
205
 
176
206
  **Features:**
177
207
  - Real-time dashboard with WebSocket updates
208
+ - **Persistent Storage** - Database storage for long-term analytics (PostgreSQL, MySQL, SQLite)
178
209
  - Prometheus metrics export
179
210
  - Intelligent alerting with anomaly detection
180
211
  - Grafana integration with pre-built dashboards
181
212
 
182
- See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complete documentation.
213
+ See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) and [Persistent Monitoring Guide](docs/PERSISTENT_MONITORING.md) for complete documentation.
214
+
215
+ ## Simulation & What-If Analysis
216
+
217
+ DecisionAgent provides comprehensive simulation capabilities to test rule changes before deployment:
218
+
219
+ ```ruby
220
+ require 'decision_agent/simulation/replay_engine'
221
+ require 'decision_agent/simulation/what_if_analyzer'
222
+ require 'decision_agent/simulation/impact_analyzer'
223
+
224
+ # Replay historical decisions with new rules
225
+ replay_engine = DecisionAgent::Simulation::ReplayEngine.new(
226
+ agent: agent,
227
+ version_manager: version_manager
228
+ )
229
+ results = replay_engine.replay(historical_data: "decisions.csv")
230
+
231
+ # What-if analysis (scenarios = array of context hashes)
232
+ whatif = DecisionAgent::Simulation::WhatIfAnalyzer.new(agent: agent)
233
+ analysis = whatif.analyze(
234
+ scenarios: [
235
+ { credit_score: 700, amount: 50000 },
236
+ { credit_score: 750, amount: 50000 },
237
+ { credit_score: 650, amount: 50000 }
238
+ ]
239
+ )
240
+
241
+ # Impact analysis (compare two rule versions on test data)
242
+ impact = DecisionAgent::Simulation::ImpactAnalyzer.new(version_manager: version_manager)
243
+ comparison = impact.analyze(
244
+ baseline_version: baseline_version_id,
245
+ proposed_version: proposed_version_id,
246
+ test_data: test_contexts
247
+ )
248
+ ```
249
+
250
+ **Features:**
251
+ - **Historical Replay** - Replay past decisions with CSV/JSON/database import
252
+ - **What-If Analysis** - Scenario simulation with decision boundary visualization
253
+ - **Impact Analysis** - Quantify rule change effects (decisions, confidence, performance)
254
+ - **Shadow Testing** - Test new rules in production without affecting outcomes
255
+ - **Monte Carlo Simulation** - Probabilistic decision modeling
256
+ - **Web UI** - Complete simulation dashboard at `/simulation`
257
+
258
+ See [Simulation Guide](docs/SIMULATION.md) for complete documentation and [Simulation Example](examples/simulation_example.rb) for working examples.
259
+
260
+ ## Role-Based Access Control (RBAC)
261
+
262
+ Enterprise-grade authentication and authorization system:
263
+
264
+ ```ruby
265
+ require 'decision_agent'
266
+
267
+ # Configure RBAC (works with any auth system)
268
+ DecisionAgent.configure_rbac(:devise_cancan, ability_class: Ability)
269
+
270
+ # Or use built-in RBAC
271
+ authenticator = DecisionAgent::Auth::Authenticator.new
272
+ admin = authenticator.create_user(
273
+ email: "admin@example.com",
274
+ password: "secure_password",
275
+ roles: [:admin]
276
+ )
277
+
278
+ session = authenticator.login("admin@example.com", "secure_password")
279
+
280
+ # Permission checks
281
+ checker = DecisionAgent.permission_checker
282
+ checker.can?(admin, :write) # => true
283
+ checker.can?(admin, :approve) # => true
284
+ ```
285
+
286
+ **Features:**
287
+ - **Built-in User System** - User management with bcrypt password hashing
288
+ - **5 Default Roles** - Admin, Editor, Viewer, Auditor, Approver
289
+ - **Configurable Adapters** - Devise, CanCanCan, Pundit, or custom
290
+ - **Password Reset** - Secure token-based password reset
291
+ - **Access Audit Logging** - Comprehensive audit trail for compliance
292
+ - **Web UI Integration** - Login page and user management interface
293
+
294
+ See [RBAC Configuration Guide](docs/RBAC_CONFIGURATION.md) for complete documentation and [RBAC Examples](examples/rbac_configuration_examples.rb) for integration examples.
295
+
296
+ ## Batch Testing
297
+
298
+ Test rules against large datasets with comprehensive analysis:
299
+
300
+ ```ruby
301
+ require 'decision_agent/testing/batch_test_runner'
302
+ require 'decision_agent/testing/batch_test_importer'
303
+
304
+ runner = DecisionAgent::Testing::BatchTestRunner.new(agent)
305
+
306
+ # Import from CSV or Excel (context columns default to all except id/expected_*)
307
+ importer = DecisionAgent::Testing::BatchTestImporter.new
308
+ scenarios = importer.import_csv("test_data.csv")
309
+
310
+ # Run batch test
311
+ results = runner.run(scenarios)
312
+
313
+ stats = runner.statistics
314
+ puts "Total: #{stats[:total]}"
315
+ puts "Passed: #{stats[:successful]}"
316
+ puts "Failed: #{stats[:failed]}"
317
+ puts "Success rate: #{(stats[:success_rate] * 100).round(2)}%"
318
+ ```
319
+
320
+ **Features:**
321
+ - **CSV/Excel Import** - Import test scenarios from files
322
+ - **Database Import** - Load test data from databases
323
+ - **Coverage Analysis** - Identify untested rule combinations
324
+ - **Resume Capability** - Continue interrupted tests from checkpoint
325
+ - **Progress Tracking** - Real-time progress updates for large imports
326
+ - **Web UI** - Complete batch testing interface with file upload
327
+
328
+ See [Batch Testing Guide](docs/BATCH_TESTING.md) for complete documentation.
329
+
330
+ ## A/B Testing
331
+
332
+ Compare rule versions with statistical analysis:
333
+
334
+ ```ruby
335
+ require 'decision_agent/ab_testing/ab_test_manager'
336
+
337
+ ab_manager = DecisionAgent::ABTesting::ABTestManager.new(version_manager: version_manager)
338
+
339
+ test = ab_manager.create_test(
340
+ name: "loan_approval_v2",
341
+ champion_version_id: champion_version_id,
342
+ challenger_version_id: challenger_version_id,
343
+ traffic_split: { champion: 90, challenger: 10 }
344
+ )
345
+
346
+ # Assign variant per request, then record decisions; when done, get results
347
+ assignment = ab_manager.assign_variant(test_id: test.id, user_id: "user_123")
348
+ # ... run agent with assignment[:version_id], then:
349
+ ab_manager.record_decision(assignment_id: assignment[:assignment_id], decision: "approve", confidence: 0.9)
350
+
351
+ results = ab_manager.get_results(test.id)
352
+ ```
353
+
354
+ **Features:**
355
+ - **Champion/Challenger Testing** - Compare baseline vs proposed rules
356
+ - **Statistical Significance** - P-value calculation and confidence intervals
357
+ - **Traffic Splitting** - Configurable split ratios
358
+ - **Decision Distribution Comparison** - Visualize differences in outcomes
359
+
360
+ See [A/B Testing Guide](docs/AB_TESTING.md) for complete documentation.
183
361
 
184
362
  ## When to Use DecisionAgent
185
363
 
@@ -204,6 +382,7 @@ See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complet
204
382
  - [Examples Directory](examples/README.md) - Working examples with explanations
205
383
 
206
384
  ### Core Features
385
+ - [Explainability Layer](docs/EXPLAINABILITY.md) - Machine-readable decision explanations with condition-level tracing
207
386
  - [Advanced Operators](docs/ADVANCED_OPERATORS.md) - String, numeric, date/time, collection, and geospatial operators
208
387
  - [DMN Guide](docs/DMN_GUIDE.md) - Complete DMN 1.3 support guide
209
388
  - [DMN API Reference](docs/DMN_API.md) - DMN API documentation
@@ -211,16 +390,25 @@ See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complet
211
390
  - [DMN Migration Guide](docs/DMN_MIGRATION_GUIDE.md) - Migrating from JSON to DMN
212
391
  - [DMN Best Practices](docs/DMN_BEST_PRACTICES.md) - DMN modeling best practices
213
392
  - [Versioning System](docs/VERSIONING.md) - Version control for rules
393
+ - [Simulation & What-If Analysis](docs/SIMULATION.md) - Historical replay, what-if analysis, impact analysis, and shadow testing
214
394
  - [A/B Testing](docs/AB_TESTING.md) - Compare rule versions with statistical analysis
395
+ - [Batch Testing](docs/BATCH_TESTING.md) - Test rules against large datasets with CSV/Excel import
396
+ - [RBAC Configuration](docs/RBAC_CONFIGURATION.md) - Role-based access control setup and integration
397
+ - [RBAC Quick Reference](docs/RBAC_QUICK_REFERENCE.md) - Quick reference for RBAC configuration
215
398
  - [Web UI](docs/WEB_UI.md) - Visual rule builder
216
399
  - [Web UI Setup](docs/WEB_UI_SETUP.md) - Setup guide
217
- - [Web UI Rails Integration](docs/WEB_UI_RAILS_INTEGRATION.md) - Mount in Rails/Rack apps
400
+ - [Web UI Integration](docs/WEB_UI_INTEGRATION.md) - Mount in Rails, Sinatra, Hanami, and other Rack frameworks
218
401
  - [Monitoring & Analytics](docs/MONITORING_AND_ANALYTICS.md) - Real-time monitoring, metrics, and alerting
219
402
  - [Monitoring Architecture](docs/MONITORING_ARCHITECTURE.md) - System architecture and design
403
+ - [Persistent Monitoring](docs/PERSISTENT_MONITORING.md) - Database storage for long-term analytics
220
404
 
221
405
  ### Performance & Thread-Safety
222
406
  - [Performance & Thread-Safety Summary](docs/PERFORMANCE_AND_THREAD_SAFETY.md) - Benchmarks and production readiness
223
407
  - [Thread-Safety Implementation](docs/THREAD_SAFETY.md) - Technical implementation guide
408
+ - [Benchmarks](benchmarks/README.md) - Comprehensive benchmark suite and performance testing
409
+
410
+ ### Development
411
+ - [Development Setup](docs/DEVELOPMENT_SETUP.md) - Development environment setup, testing, and tools
224
412
 
225
413
  ### Reference
226
414
  - [API Contract](docs/API_CONTRACT.md) - Full API reference
@@ -244,12 +432,44 @@ All data structures are deeply frozen to prevent mutation, ensuring safe concurr
244
432
 
245
433
  See [Thread-Safety Guide](docs/THREAD_SAFETY.md) and [Performance Analysis](docs/PERFORMANCE_AND_THREAD_SAFETY.md) for details.
246
434
 
435
+ **Run Benchmarks:**
436
+ ```bash
437
+ # Run all benchmarks (single Ruby version)
438
+ rake benchmark:all
439
+
440
+ # Run specific benchmarks
441
+ rake benchmark:basic # Basic decision performance
442
+ rake benchmark:threads # Thread-safety and scalability
443
+ rake benchmark:regression # Compare against baseline
444
+
445
+ # Run benchmarks across all Ruby versions (3.0.7, 3.1.6, 3.2.5, 3.3.5)
446
+ ./scripts/benchmark_all_ruby_versions.sh
447
+
448
+ # See [Benchmarks Guide](benchmarks/README.md) for complete documentation
449
+ ```
450
+
451
+ ### Latest Benchmark Results
452
+
453
+ Run `rake benchmark:regression` to generate results for your environment. Example (Ruby 3.3, typical hardware):
454
+
455
+ | Metric | Typical range |
456
+ |--------|----------------|
457
+ | Basic throughput | ~9,000+ decisions/sec |
458
+ | Basic latency | ~0.1 ms |
459
+ | Multi-threaded (50 threads) | ~8,500+ decisions/sec |
460
+
461
+ > 💡 **Note:** See [Benchmarks Guide](benchmarks/README.md) and run `rake benchmark:all` or `rake benchmark:regression` for current numbers.
247
462
  ## Contributing
248
463
 
249
464
  1. Fork the repository
250
465
  2. Create a feature branch
251
- 3. Add tests (maintain 90%+ coverage)
252
- 4. Submit a pull request
466
+ 3. Set up development environment (see [Development Setup](docs/DEVELOPMENT_SETUP.md))
467
+ 4. Add tests (maintain 90%+ coverage)
468
+ 5. Run tests across all Ruby versions: `./scripts/test_all_ruby_versions.sh`
469
+ 6. Run benchmarks across all Ruby versions: `./scripts/benchmark_all_ruby_versions.sh`
470
+ 7. Submit a pull request
471
+
472
+ See [Development Setup Guide](docs/DEVELOPMENT_SETUP.md) for detailed setup instructions, testing workflows, and development best practices.
253
473
 
254
474
  ## Support
255
475
 
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
1
5
  module DecisionAgent
2
6
  module ABTesting
3
7
  # Represents an A/B test configuration for comparing rule versions
@@ -41,7 +45,7 @@ module DecisionAgent
41
45
 
42
46
  if user_id
43
47
  # Consistent hashing: same user always gets same variant
44
- hash_value = Digest::SHA256.hexdigest("#{@id}:#{user_id}").to_i(16)
48
+ hash_value = OpenSSL::Digest::SHA256.hexdigest("#{@id}:#{user_id}").to_i(16)
45
49
  percentage = hash_value % 100
46
50
  else
47
51
  # Random assignment
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module ABTesting
3
5
  # Tracks individual assignments of users/requests to A/B test variants
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "monitor"
2
4
 
3
5
  module DecisionAgent
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module ABTesting
3
5
  # Agent wrapper that adds A/B testing capabilities to the standard Agent
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record"
2
4
 
3
5
  module DecisionAgent
@@ -86,19 +88,6 @@ module DecisionAgent
86
88
  end
87
89
  # rubocop:enable Naming/PredicateMethod
88
90
 
89
- # Get statistics from database
90
- def get_test_statistics(test_id)
91
- assignments = ::ABTestAssignmentModel.where(ab_test_id: test_id)
92
-
93
- {
94
- total_assignments: assignments.count,
95
- champion_count: assignments.where(variant: "champion").count,
96
- challenger_count: assignments.where(variant: "challenger").count,
97
- with_decisions: assignments.where.not(decision_result: nil).count,
98
- avg_confidence: assignments.where.not(confidence: nil).average(:confidence)&.to_f
99
- }
100
- end
101
-
102
91
  private
103
92
 
104
93
  def to_ab_test(record)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module ABTesting
3
5
  module Storage
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "monitor"
2
4
 
3
5
  module DecisionAgent
@@ -1,11 +1,30 @@
1
- require "digest"
1
+ # frozen_string_literal: true
2
+
2
3
  require "json"
3
4
  require "json/canonicalization"
5
+ require "openssl"
4
6
 
5
7
  module DecisionAgent
8
+ # Agent runs multiple evaluators over a context, scores their evaluations,
9
+ # and returns a single {Decision} with the chosen outcome and confidence.
6
10
  class Agent
7
11
  attr_reader :evaluators, :scoring_strategy, :audit_adapter
8
12
 
13
+ # Thread-safe cache for deterministic hash computation
14
+ # This significantly improves performance when the same context/evaluations
15
+ # are processed multiple times (common in benchmarks and high-throughput scenarios)
16
+ @hash_cache = {}
17
+ @hash_cache_mutex = Mutex.new
18
+ @hash_cache_max_size = 1000 # Limit cache size to prevent memory bloat
19
+
20
+ class << self
21
+ attr_reader :hash_cache, :hash_cache_mutex, :hash_cache_max_size
22
+ end
23
+
24
+ # @param evaluators [Array<#evaluate>] Objects that respond to #evaluate(context, feedback:)
25
+ # @param scoring_strategy [Scoring::Base, nil] Strategy to score evaluations (default: WeightedAverage)
26
+ # @param audit_adapter [Audit::Base, nil] Adapter for recording decisions (default: NullAdapter)
27
+ # @param validate_evaluations [Boolean, nil] If true, validate evaluations; nil = validate unless production
9
28
  def initialize(evaluators:, scoring_strategy: nil, audit_adapter: nil, validate_evaluations: nil)
10
29
  @evaluators = Array(evaluators)
11
30
  @scoring_strategy = scoring_strategy || Scoring::WeightedAverage.new
@@ -19,6 +38,12 @@ module DecisionAgent
19
38
  @evaluators.freeze
20
39
  end
21
40
 
41
+ # Runs all evaluators on the context, scores results, and returns a single decision.
42
+ #
43
+ # @param context [Context, Hash] Input data; converted to {Context} if a Hash
44
+ # @param feedback [Hash] Optional feedback passed to each evaluator
45
+ # @return [Decision] The chosen decision with confidence, explanations, and audit payload
46
+ # @raise [NoEvaluationsError] when no evaluator returns a valid evaluation
22
47
  def decide(context:, feedback: {})
23
48
  ctx = context.is_a?(Context) ? context : Context.new(context)
24
49
 
@@ -76,7 +101,8 @@ module DecisionAgent
76
101
  def collect_evaluations(context, feedback)
77
102
  @evaluators.map do |evaluator|
78
103
  evaluator.evaluate(context, feedback: feedback)
79
- rescue StandardError
104
+ rescue StandardError => e
105
+ warn "[DecisionAgent] Evaluator #{evaluator.class} failed: #{e.message}"
80
106
  nil
81
107
  end.compact
82
108
  end
@@ -89,20 +115,20 @@ module DecisionAgent
89
115
  explanations << "Decision: #{final_decision} (confidence: #{confidence.round(2)})"
90
116
 
91
117
  if matching_evals.size == 1
92
- eval = matching_evals.first
93
- explanations << "#{eval.evaluator_name}: #{eval.reason} (weight: #{eval.weight})"
118
+ evaluation = matching_evals.first
119
+ explanations << "#{evaluation.evaluator_name}: #{evaluation.reason} (weight: #{evaluation.weight})"
94
120
  elsif matching_evals.size > 1
95
121
  explanations << "Based on #{matching_evals.size} evaluators:"
96
- matching_evals.each do |eval|
97
- explanations << " - #{eval.evaluator_name}: #{eval.reason} (weight: #{eval.weight})"
122
+ matching_evals.each do |evaluation|
123
+ explanations << " - #{evaluation.evaluator_name}: #{evaluation.reason} (weight: #{evaluation.weight})"
98
124
  end
99
125
  end
100
126
 
101
127
  conflicting_evals = evaluations.reject { |e| e.decision == final_decision }
102
128
  if conflicting_evals.any?
103
129
  explanations << "Conflicting evaluations resolved by #{@scoring_strategy.class.name.split('::').last}:"
104
- conflicting_evals.each do |eval|
105
- explanations << " - #{eval.evaluator_name}: suggested '#{eval.decision}' (weight: #{eval.weight})"
130
+ conflicting_evals.each do |evaluation|
131
+ explanations << " - #{evaluation.evaluator_name}: suggested '#{evaluation.decision}' (weight: #{evaluation.weight})"
106
132
  end
107
133
  end
108
134
 
@@ -129,8 +155,51 @@ module DecisionAgent
129
155
 
130
156
  def compute_deterministic_hash(payload)
131
157
  hashable = payload.slice(:context, :evaluations, :decision, :confidence, :scoring_strategy)
158
+
159
+ # Use fast hash (MD5) as cache key to avoid expensive canonicalization on cache hits
160
+ # The cache key doesn't need perfect determinism, just good enough to catch duplicates
161
+ # Use OpenSSL::Digest to avoid "Digest::Base cannot be directly inherited" on some Ruby/digest setups
162
+ json_str = hashable.to_json
163
+ fast_key = OpenSSL::Digest::MD5.hexdigest(json_str)
164
+
165
+ # Fast path: check cache without lock first (unsafe read, but acceptable for cache)
166
+ cached_hash = lookup_cached_hash(fast_key)
167
+ return cached_hash if cached_hash
168
+
169
+ # Cache miss - compute canonical JSON and hash
170
+ computed_hash = compute_canonical_hash(hashable)
171
+
172
+ # Store in cache (thread-safe, with size limit)
173
+ cache_hash(fast_key, computed_hash)
174
+
175
+ computed_hash
176
+ end
177
+
178
+ def lookup_cached_hash(fast_key)
179
+ self.class.hash_cache[fast_key]
180
+ end
181
+
182
+ def compute_canonical_hash(hashable)
132
183
  canonical = canonical_json(hashable)
133
- Digest::SHA256.hexdigest(canonical)
184
+ OpenSSL::Digest::SHA256.hexdigest(canonical)
185
+ end
186
+
187
+ def cache_hash(fast_key, computed_hash)
188
+ self.class.hash_cache_mutex.synchronize do
189
+ # Double-check after acquiring lock (another thread may have added it)
190
+ return self.class.hash_cache[fast_key] if self.class.hash_cache[fast_key]
191
+
192
+ evict_cache_if_needed
193
+ self.class.hash_cache[fast_key] = computed_hash
194
+ end
195
+ end
196
+
197
+ def evict_cache_if_needed
198
+ return unless self.class.hash_cache.size >= self.class.hash_cache_max_size
199
+
200
+ # Remove oldest 10% of entries (simple FIFO eviction)
201
+ keys_to_remove = self.class.hash_cache.keys.first(self.class.hash_cache_max_size / 10)
202
+ keys_to_remove.each { |key| self.class.hash_cache.delete(key) }
134
203
  end
135
204
 
136
205
  # Uses RFC 8785 (JSON Canonicalization Scheme) for deterministic JSON serialization
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Audit
3
5
  class Adapter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "logger"
2
4
  require "json"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Audit
3
5
  class NullAdapter < Adapter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require_relative "../audit/adapter"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Auth
3
5
  class Authenticator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Auth
3
5
  class PasswordResetManager
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "securerandom"
2
4
 
3
5
  module DecisionAgent
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Auth
3
5
  class Permission
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Auth
3
5
  class PermissionChecker
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Auth
3
5
  # Base adapter interface for RBAC integration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Auth
3
5
  # Configuration class for RBAC adapter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DecisionAgent
2
4
  module Auth
3
5
  class Role