decision_agent 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +313 -8
  3. data/bin/decision_agent +104 -0
  4. data/lib/decision_agent/agent.rb +72 -1
  5. data/lib/decision_agent/context.rb +1 -0
  6. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  7. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  8. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  9. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  10. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  11. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  12. data/lib/decision_agent/decision.rb +102 -2
  13. data/lib/decision_agent/dmn/adapter.rb +135 -0
  14. data/lib/decision_agent/dmn/cache.rb +306 -0
  15. data/lib/decision_agent/dmn/decision_graph.rb +327 -0
  16. data/lib/decision_agent/dmn/decision_tree.rb +192 -0
  17. data/lib/decision_agent/dmn/errors.rb +30 -0
  18. data/lib/decision_agent/dmn/exporter.rb +217 -0
  19. data/lib/decision_agent/dmn/feel/evaluator.rb +819 -0
  20. data/lib/decision_agent/dmn/feel/functions.rb +420 -0
  21. data/lib/decision_agent/dmn/feel/parser.rb +349 -0
  22. data/lib/decision_agent/dmn/feel/simple_parser.rb +276 -0
  23. data/lib/decision_agent/dmn/feel/transformer.rb +372 -0
  24. data/lib/decision_agent/dmn/feel/types.rb +276 -0
  25. data/lib/decision_agent/dmn/importer.rb +77 -0
  26. data/lib/decision_agent/dmn/model.rb +197 -0
  27. data/lib/decision_agent/dmn/parser.rb +191 -0
  28. data/lib/decision_agent/dmn/testing.rb +333 -0
  29. data/lib/decision_agent/dmn/validator.rb +315 -0
  30. data/lib/decision_agent/dmn/versioning.rb +229 -0
  31. data/lib/decision_agent/dmn/visualizer.rb +513 -0
  32. data/lib/decision_agent/dsl/condition_evaluator.rb +984 -838
  33. data/lib/decision_agent/dsl/schema_validator.rb +53 -14
  34. data/lib/decision_agent/evaluators/dmn_evaluator.rb +308 -0
  35. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  36. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  37. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  38. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  39. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  40. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  41. data/lib/decision_agent/simulation/errors.rb +18 -0
  42. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  43. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  44. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  45. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  46. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  47. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  48. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  49. data/lib/decision_agent/simulation.rb +17 -0
  50. data/lib/decision_agent/version.rb +1 -1
  51. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  52. data/lib/decision_agent/web/dmn_editor.rb +426 -0
  53. data/lib/decision_agent/web/public/app.js +119 -0
  54. data/lib/decision_agent/web/public/dmn-editor.css +596 -0
  55. data/lib/decision_agent/web/public/dmn-editor.html +250 -0
  56. data/lib/decision_agent/web/public/dmn-editor.js +553 -0
  57. data/lib/decision_agent/web/public/index.html +52 -0
  58. data/lib/decision_agent/web/public/simulation.html +130 -0
  59. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  60. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  61. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  62. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  63. data/lib/decision_agent/web/public/styles.css +86 -0
  64. data/lib/decision_agent/web/server.rb +1059 -23
  65. data/lib/decision_agent.rb +60 -2
  66. metadata +105 -61
  67. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  68. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  69. data/spec/ab_testing/ab_test_spec.rb +0 -270
  70. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -481
  71. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  72. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  73. data/spec/activerecord_thread_safety_spec.rb +0 -553
  74. data/spec/advanced_operators_spec.rb +0 -3150
  75. data/spec/agent_spec.rb +0 -289
  76. data/spec/api_contract_spec.rb +0 -430
  77. data/spec/audit_adapters_spec.rb +0 -92
  78. data/spec/auth/access_audit_logger_spec.rb +0 -394
  79. data/spec/auth/authenticator_spec.rb +0 -112
  80. data/spec/auth/password_reset_spec.rb +0 -294
  81. data/spec/auth/permission_checker_spec.rb +0 -207
  82. data/spec/auth/permission_spec.rb +0 -73
  83. data/spec/auth/rbac_adapter_spec.rb +0 -550
  84. data/spec/auth/rbac_config_spec.rb +0 -82
  85. data/spec/auth/role_spec.rb +0 -51
  86. data/spec/auth/session_manager_spec.rb +0 -172
  87. data/spec/auth/session_spec.rb +0 -112
  88. data/spec/auth/user_spec.rb +0 -130
  89. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  90. data/spec/context_spec.rb +0 -127
  91. data/spec/decision_agent_spec.rb +0 -96
  92. data/spec/decision_spec.rb +0 -423
  93. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  94. data/spec/dsl_validation_spec.rb +0 -648
  95. data/spec/edge_cases_spec.rb +0 -353
  96. data/spec/evaluation_spec.rb +0 -364
  97. data/spec/evaluation_validator_spec.rb +0 -165
  98. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  99. data/spec/examples.txt +0 -1633
  100. data/spec/issue_verification_spec.rb +0 -759
  101. data/spec/json_rule_evaluator_spec.rb +0 -587
  102. data/spec/monitoring/alert_manager_spec.rb +0 -378
  103. data/spec/monitoring/metrics_collector_spec.rb +0 -499
  104. data/spec/monitoring/monitored_agent_spec.rb +0 -222
  105. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  106. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  107. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  108. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  109. data/spec/performance_optimizations_spec.rb +0 -486
  110. data/spec/replay_edge_cases_spec.rb +0 -699
  111. data/spec/replay_spec.rb +0 -210
  112. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  113. data/spec/scoring_spec.rb +0 -225
  114. data/spec/spec_helper.rb +0 -60
  115. data/spec/testing/batch_test_importer_spec.rb +0 -693
  116. data/spec/testing/batch_test_runner_spec.rb +0 -307
  117. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  118. data/spec/testing/test_result_comparator_spec.rb +0 -392
  119. data/spec/testing/test_scenario_spec.rb +0 -113
  120. data/spec/thread_safety_spec.rb +0 -482
  121. data/spec/thread_safety_spec.rb.broken +0 -878
  122. data/spec/versioning/adapter_spec.rb +0 -156
  123. data/spec/versioning_spec.rb +0 -1030
  124. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  125. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  126. data/spec/web_ui_rack_spec.rb +0 -1840
@@ -1,172 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::SessionManager do
4
- let(:manager) { DecisionAgent::Auth::SessionManager.new }
5
-
6
- describe "#initialize" do
7
- it "initializes with empty sessions" do
8
- expect(manager.count).to eq(0)
9
- end
10
- end
11
-
12
- describe "#create_session" do
13
- it "creates a new session" do
14
- session = manager.create_session("user123")
15
- expect(session).to be_a(DecisionAgent::Auth::Session)
16
- expect(session.user_id).to eq("user123")
17
- end
18
-
19
- it "stores the session" do
20
- session = manager.create_session("user123")
21
- retrieved = manager.get_session(session.token)
22
- expect(retrieved).to eq(session)
23
- end
24
-
25
- it "accepts custom expiration time" do
26
- session = manager.create_session("user123", expires_in: 7200)
27
- expect(session.expires_at).to be > Time.now.utc + 7100
28
- end
29
- end
30
-
31
- describe "#get_session" do
32
- it "returns session for valid token" do
33
- session = manager.create_session("user123")
34
- retrieved = manager.get_session(session.token)
35
- expect(retrieved).to eq(session)
36
- end
37
-
38
- it "returns nil for invalid token" do
39
- expect(manager.get_session("invalid_token")).to be_nil
40
- end
41
-
42
- it "returns nil for expired session" do
43
- session = manager.create_session("user123", expires_in: -1)
44
- expect(manager.get_session(session.token)).to be_nil
45
- end
46
- end
47
-
48
- describe "#delete_session" do
49
- it "deletes a session" do
50
- session = manager.create_session("user123")
51
- manager.delete_session(session.token)
52
- expect(manager.get_session(session.token)).to be_nil
53
- end
54
-
55
- it "does not raise error for non-existent session" do
56
- expect { manager.delete_session("nonexistent") }.not_to raise_error
57
- end
58
- end
59
-
60
- describe "#delete_user_sessions" do
61
- it "deletes all sessions for a user" do
62
- session1 = manager.create_session("user123")
63
- session2 = manager.create_session("user123")
64
- session3 = manager.create_session("user456")
65
-
66
- manager.delete_user_sessions("user123")
67
-
68
- expect(manager.get_session(session1.token)).to be_nil
69
- expect(manager.get_session(session2.token)).to be_nil
70
- expect(manager.get_session(session3.token)).to eq(session3) # Other user's session still exists
71
- end
72
-
73
- it "does not raise error for user with no sessions" do
74
- expect { manager.delete_user_sessions("nonexistent") }.not_to raise_error
75
- end
76
- end
77
-
78
- describe "#cleanup_expired_sessions" do
79
- it "removes expired sessions" do
80
- # Create expired session
81
- expired_session = manager.create_session("user123", expires_in: -1)
82
- # Create valid session
83
- valid_session = manager.create_session("user456", expires_in: 3600)
84
-
85
- expect(manager.count).to eq(2)
86
-
87
- # Force cleanup by setting last_cleanup far in the past and creating another session
88
- manager.instance_variable_set(:@last_cleanup, Time.now - 400) # More than 300 seconds
89
- manager.create_session("user789", expires_in: 3600)
90
-
91
- # Expired session should be cleaned up
92
- expect(manager.get_session(expired_session.token)).to be_nil
93
- expect(manager.get_session(valid_session.token)).to eq(valid_session)
94
- end
95
-
96
- it "only runs cleanup after cleanup_interval" do
97
- manager = DecisionAgent::Auth::SessionManager.new
98
-
99
- # Create an expired session
100
- expired_session = manager.create_session("user123", expires_in: -1)
101
- expect(manager.count).to eq(1)
102
-
103
- # The cleanup_expired_sessions is called during create_session, but it checks the interval
104
- # Let's test by manually setting last_cleanup to far in the past
105
- manager.instance_variable_set(:@last_cleanup, Time.now - 400) # More than 300 seconds
106
- manager.create_session("user456", expires_in: 3600)
107
-
108
- # The expired session should be cleaned up now
109
- expect(manager.get_session(expired_session.token)).to be_nil
110
- end
111
- end
112
-
113
- describe "#count" do
114
- it "returns zero initially" do
115
- expect(manager.count).to eq(0)
116
- end
117
-
118
- it "returns correct count after creating sessions" do
119
- manager.create_session("user123")
120
- expect(manager.count).to eq(1)
121
-
122
- manager.create_session("user123")
123
- expect(manager.count).to eq(2)
124
-
125
- manager.create_session("user456")
126
- expect(manager.count).to eq(3)
127
- end
128
-
129
- it "reflects deletions" do
130
- session1 = manager.create_session("user123")
131
- manager.create_session("user123")
132
- expect(manager.count).to eq(2)
133
-
134
- manager.delete_session(session1.token)
135
- expect(manager.count).to eq(1)
136
-
137
- manager.delete_user_sessions("user123")
138
- expect(manager.count).to eq(0)
139
- end
140
-
141
- it "does not count expired sessions" do
142
- manager.create_session("user123", expires_in: -1)
143
- # Expired sessions are not counted when retrieved, but are still in storage until cleanup
144
- # So count will include them until cleanup runs
145
- expect(manager.count).to eq(1)
146
-
147
- # After cleanup
148
- manager.instance_variable_set(:@last_cleanup, Time.now - 400)
149
- manager.create_session("user456", expires_in: 3600)
150
- # Now expired session should be cleaned up
151
- expect(manager.count).to eq(1)
152
- end
153
- end
154
-
155
- describe "thread safety" do
156
- it "handles concurrent access safely" do
157
- threads = []
158
- 10.times do
159
- threads << Thread.new do
160
- 10.times do
161
- session = manager.create_session("user#{rand(100)}")
162
- manager.get_session(session.token)
163
- manager.count
164
- end
165
- end
166
- end
167
-
168
- threads.each(&:join)
169
- expect(manager.count).to eq(100)
170
- end
171
- end
172
- end
@@ -1,112 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::Session do
4
- describe "#initialize" do
5
- it "creates a session with user_id" do
6
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
7
- expect(session.user_id).to eq("user123")
8
- expect(session.token).to be_a(String)
9
- expect(session.token.length).to eq(64) # 32 bytes hex = 64 chars
10
- end
11
-
12
- it "sets expiration time" do
13
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 1800)
14
- expect(session.expires_at).to be > Time.now.utc
15
- expect(session.expires_at).to be <= Time.now.utc + 1801
16
- end
17
-
18
- it "uses default expiration of 3600 seconds" do
19
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
20
- expected_expiry = session.created_at + 3600
21
- expect(session.expires_at).to be_within(1).of(expected_expiry)
22
- end
23
- end
24
-
25
- describe "#expired?" do
26
- it "returns false for valid session" do
27
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
28
- expect(session.expired?).to be false
29
- end
30
-
31
- it "returns true for expired session" do
32
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: -1)
33
- expect(session.expired?).to be true
34
- end
35
- end
36
-
37
- describe "#valid?" do
38
- it "returns true for non-expired session" do
39
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
40
- expect(session.valid?).to be true
41
- end
42
-
43
- it "returns false for expired session" do
44
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: -1)
45
- expect(session.valid?).to be false
46
- end
47
-
48
- it "returns false when expired? returns true" do
49
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: -1)
50
- expect(session.expired?).to be true
51
- expect(session.valid?).to be false
52
- end
53
-
54
- it "returns true when expired? returns false" do
55
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
56
- expect(session.expired?).to be false
57
- expect(session.valid?).to be true
58
- end
59
- end
60
-
61
- describe "#to_h" do
62
- it "returns hash with all session attributes" do
63
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 3600)
64
- hash = session.to_h
65
-
66
- expect(hash).to be_a(Hash)
67
- expect(hash[:token]).to eq(session.token)
68
- expect(hash[:user_id]).to eq("user123")
69
- expect(hash[:created_at]).to be_a(String)
70
- expect(hash[:expires_at]).to be_a(String)
71
- end
72
-
73
- it "serializes timestamps as ISO8601 strings" do
74
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
75
- hash = session.to_h
76
-
77
- expect { Time.iso8601(hash[:created_at]) }.not_to raise_error
78
- expect { Time.iso8601(hash[:expires_at]) }.not_to raise_error
79
- end
80
-
81
- it "includes correct user_id" do
82
- session = DecisionAgent::Auth::Session.new(user_id: "user456")
83
- hash = session.to_h
84
- expect(hash[:user_id]).to eq("user456")
85
- end
86
-
87
- it "includes correct token" do
88
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
89
- hash = session.to_h
90
- expect(hash[:token]).to eq(session.token)
91
- end
92
- end
93
-
94
- describe "#created_at" do
95
- it "is set to current UTC time" do
96
- before = Time.now.utc
97
- session = DecisionAgent::Auth::Session.new(user_id: "user123")
98
- after = Time.now.utc
99
-
100
- expect(session.created_at).to be >= before
101
- expect(session.created_at).to be <= after
102
- end
103
- end
104
-
105
- describe "#expires_at" do
106
- it "is set based on expires_in parameter" do
107
- session = DecisionAgent::Auth::Session.new(user_id: "user123", expires_in: 7200)
108
- expected = session.created_at + 7200
109
- expect(session.expires_at).to be_within(1).of(expected)
110
- end
111
- end
112
- end
@@ -1,130 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe DecisionAgent::Auth::User do
4
- describe "#initialize" do
5
- it "creates a user with email and password" do
6
- user = DecisionAgent::Auth::User.new(
7
- email: "test@example.com",
8
- password: "password123"
9
- )
10
-
11
- expect(user.email).to eq("test@example.com")
12
- expect(user.id).to be_a(String)
13
- expect(user.active).to be true
14
- expect(user.roles).to eq([])
15
- end
16
-
17
- it "creates a user with roles" do
18
- user = DecisionAgent::Auth::User.new(
19
- email: "admin@example.com",
20
- password: "password123",
21
- roles: %i[admin editor]
22
- )
23
-
24
- expect(user.roles).to include(:admin, :editor)
25
- end
26
-
27
- it "raises error if neither password nor password_hash provided" do
28
- expect do
29
- DecisionAgent::Auth::User.new(email: "test@example.com")
30
- end.to raise_error(ArgumentError, /password/)
31
- end
32
- end
33
-
34
- describe "#authenticate" do
35
- it "returns true for correct password" do
36
- user = DecisionAgent::Auth::User.new(
37
- email: "test@example.com",
38
- password: "password123"
39
- )
40
-
41
- expect(user.authenticate("password123")).to be true
42
- end
43
-
44
- it "returns false for incorrect password" do
45
- user = DecisionAgent::Auth::User.new(
46
- email: "test@example.com",
47
- password: "password123"
48
- )
49
-
50
- expect(user.authenticate("wrongpassword")).to be false
51
- end
52
-
53
- it "returns false for inactive user" do
54
- user = DecisionAgent::Auth::User.new(
55
- email: "test@example.com",
56
- password: "password123",
57
- active: false
58
- )
59
-
60
- expect(user.authenticate("password123")).to be false
61
- end
62
- end
63
-
64
- describe "#assign_role" do
65
- it "adds a role to the user" do
66
- user = DecisionAgent::Auth::User.new(
67
- email: "test@example.com",
68
- password: "password123"
69
- )
70
-
71
- user.assign_role(:editor)
72
- expect(user.roles).to include(:editor)
73
- end
74
-
75
- it "does not add duplicate roles" do
76
- user = DecisionAgent::Auth::User.new(
77
- email: "test@example.com",
78
- password: "password123"
79
- )
80
-
81
- user.assign_role(:editor)
82
- user.assign_role(:editor)
83
-
84
- expect(user.roles.count(:editor)).to eq(1)
85
- end
86
- end
87
-
88
- describe "#remove_role" do
89
- it "removes a role from the user" do
90
- user = DecisionAgent::Auth::User.new(
91
- email: "test@example.com",
92
- password: "password123",
93
- roles: %i[editor viewer]
94
- )
95
-
96
- user.remove_role(:editor)
97
- expect(user.roles).not_to include(:editor)
98
- expect(user.roles).to include(:viewer)
99
- end
100
- end
101
-
102
- describe "#has_role?" do
103
- it "returns true if user has the role" do
104
- user = DecisionAgent::Auth::User.new(
105
- email: "test@example.com",
106
- password: "password123",
107
- roles: [:editor]
108
- )
109
-
110
- expect(user.has_role?(:editor)).to be true
111
- expect(user.has_role?(:admin)).to be false
112
- end
113
- end
114
-
115
- describe "#to_h" do
116
- it "returns a hash representation of the user" do
117
- user = DecisionAgent::Auth::User.new(
118
- email: "test@example.com",
119
- password: "password123",
120
- roles: [:editor]
121
- )
122
-
123
- hash = user.to_h
124
- expect(hash[:email]).to eq("test@example.com")
125
- expect(hash[:roles]).to eq(["editor"])
126
- expect(hash[:active]).to be true
127
- expect(hash[:id]).to be_a(String)
128
- end
129
- end
130
- end