lex-trust 0.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.
- checksums.yaml +7 -0
- data/Gemfile +12 -0
- data/lex-trust.gemspec +30 -0
- data/lib/legion/extensions/trust/actors/decay.rb +41 -0
- data/lib/legion/extensions/trust/client.rb +23 -0
- data/lib/legion/extensions/trust/helpers/trust_map.rb +156 -0
- data/lib/legion/extensions/trust/helpers/trust_model.rb +48 -0
- data/lib/legion/extensions/trust/local_migrations/20260316000020_create_trust_entries.rb +23 -0
- data/lib/legion/extensions/trust/runners/trust.rb +76 -0
- data/lib/legion/extensions/trust/version.rb +9 -0
- data/lib/legion/extensions/trust.rb +21 -0
- data/spec/legion/extensions/trust/actors/decay_spec.rb +62 -0
- data/spec/legion/extensions/trust/client_spec.rb +17 -0
- data/spec/legion/extensions/trust/helpers/trust_map_spec.rb +299 -0
- data/spec/legion/extensions/trust/helpers/trust_model_spec.rb +179 -0
- data/spec/legion/extensions/trust/runners/trust_spec.rb +84 -0
- data/spec/local_persistence_spec.rb +359 -0
- data/spec/spec_helper.rb +20 -0
- metadata +91 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Trust::Helpers::TrustMap do
|
|
6
|
+
subject(:map) { described_class.new }
|
|
7
|
+
|
|
8
|
+
let(:agent_id) { 'agent-001' }
|
|
9
|
+
|
|
10
|
+
describe '#initialize' do
|
|
11
|
+
it 'starts with an empty entries hash' do
|
|
12
|
+
expect(map.entries).to eq({})
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#get' do
|
|
17
|
+
it 'returns nil for an unknown agent' do
|
|
18
|
+
expect(map.get(agent_id)).to be_nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'returns nil for a known agent in a different domain' do
|
|
22
|
+
map.get_or_create(agent_id, domain: :code)
|
|
23
|
+
expect(map.get(agent_id, domain: :ops)).to be_nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'returns the entry after it has been created' do
|
|
27
|
+
map.get_or_create(agent_id)
|
|
28
|
+
expect(map.get(agent_id)).not_to be_nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'retrieves an entry for the correct domain' do
|
|
32
|
+
map.get_or_create(agent_id, domain: :code)
|
|
33
|
+
entry = map.get(agent_id, domain: :code)
|
|
34
|
+
expect(entry[:domain]).to eq(:code)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'defaults domain to :general' do
|
|
38
|
+
map.get_or_create(agent_id)
|
|
39
|
+
entry = map.get(agent_id)
|
|
40
|
+
expect(entry[:domain]).to eq(:general)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe '#get_or_create' do
|
|
45
|
+
it 'creates a new entry for a new agent' do
|
|
46
|
+
entry = map.get_or_create(agent_id)
|
|
47
|
+
expect(entry).not_to be_nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'returns the same entry on subsequent calls' do
|
|
51
|
+
first = map.get_or_create(agent_id)
|
|
52
|
+
second = map.get_or_create(agent_id)
|
|
53
|
+
expect(first).to equal(second)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'initializes composite to NEUTRAL_TRUST' do
|
|
57
|
+
entry = map.get_or_create(agent_id)
|
|
58
|
+
expect(entry[:composite]).to eq(Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'creates independent entries per domain' do
|
|
62
|
+
general = map.get_or_create(agent_id, domain: :general)
|
|
63
|
+
code = map.get_or_create(agent_id, domain: :code)
|
|
64
|
+
expect(general).not_to equal(code)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'creates independent entries per agent' do
|
|
68
|
+
a = map.get_or_create('agent-a')
|
|
69
|
+
b = map.get_or_create('agent-b')
|
|
70
|
+
expect(a).not_to equal(b)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe '#record_interaction' do
|
|
75
|
+
context 'with a positive interaction' do
|
|
76
|
+
it 'increments interaction_count by 1' do
|
|
77
|
+
map.record_interaction(agent_id, positive: true)
|
|
78
|
+
expect(map.get(agent_id)[:interaction_count]).to eq(1)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'increments positive_count by 1' do
|
|
82
|
+
map.record_interaction(agent_id, positive: true)
|
|
83
|
+
expect(map.get(agent_id)[:positive_count]).to eq(1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'does not increment negative_count' do
|
|
87
|
+
map.record_interaction(agent_id, positive: true)
|
|
88
|
+
expect(map.get(agent_id)[:negative_count]).to eq(0)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'increases all dimension values above NEUTRAL_TRUST' do
|
|
92
|
+
map.record_interaction(agent_id, positive: true)
|
|
93
|
+
entry = map.get(agent_id)
|
|
94
|
+
Legion::Extensions::Trust::Helpers::TrustModel::TRUST_DIMENSIONS.each do |dim|
|
|
95
|
+
expect(entry[:dimensions][dim]).to be > Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'increases composite above NEUTRAL_TRUST' do
|
|
100
|
+
map.record_interaction(agent_id, positive: true)
|
|
101
|
+
expect(map.get(agent_id)[:composite]).to be > Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'sets last_interaction to a recent time' do
|
|
105
|
+
before = Time.now.utc
|
|
106
|
+
map.record_interaction(agent_id, positive: true)
|
|
107
|
+
expect(map.get(agent_id)[:last_interaction]).to be >= before
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
context 'with a negative interaction' do
|
|
112
|
+
it 'increments interaction_count by 1' do
|
|
113
|
+
map.record_interaction(agent_id, positive: false)
|
|
114
|
+
expect(map.get(agent_id)[:interaction_count]).to eq(1)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'increments negative_count by 1' do
|
|
118
|
+
map.record_interaction(agent_id, positive: false)
|
|
119
|
+
expect(map.get(agent_id)[:negative_count]).to eq(1)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'does not increment positive_count' do
|
|
123
|
+
map.record_interaction(agent_id, positive: false)
|
|
124
|
+
expect(map.get(agent_id)[:positive_count]).to eq(0)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'decreases composite below NEUTRAL_TRUST' do
|
|
128
|
+
map.record_interaction(agent_id, positive: false)
|
|
129
|
+
expect(map.get(agent_id)[:composite]).to be < Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'applies the asymmetric penalty (penalty > reinforcement)' do
|
|
133
|
+
map.record_interaction(agent_id, positive: true)
|
|
134
|
+
positive_composite = map.get(agent_id)[:composite]
|
|
135
|
+
map2 = described_class.new
|
|
136
|
+
map2.record_interaction(agent_id, positive: false)
|
|
137
|
+
negative_composite = map2.get(agent_id)[:composite]
|
|
138
|
+
delta_positive = positive_composite - Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST
|
|
139
|
+
delta_negative = Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST - negative_composite
|
|
140
|
+
expect(delta_negative).to be > delta_positive
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'creates the entry if it does not exist' do
|
|
145
|
+
map.record_interaction('new-agent', positive: true)
|
|
146
|
+
expect(map.get('new-agent')).not_to be_nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'tracks trust separately per domain' do
|
|
150
|
+
map.record_interaction(agent_id, positive: true, domain: :code)
|
|
151
|
+
map.record_interaction(agent_id, positive: false, domain: :ops)
|
|
152
|
+
expect(map.get(agent_id, domain: :code)[:composite]).to be > map.get(agent_id, domain: :ops)[:composite]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe '#reinforce_dimension' do
|
|
157
|
+
it 'increases the targeted dimension' do
|
|
158
|
+
map.get_or_create(agent_id)
|
|
159
|
+
map.reinforce_dimension(agent_id, dimension: :competence, amount: 0.2)
|
|
160
|
+
entry = map.get(agent_id)
|
|
161
|
+
expect(entry[:dimensions][:competence]).to be > Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it 'does not change non-targeted dimensions' do
|
|
165
|
+
map.get_or_create(agent_id)
|
|
166
|
+
map.reinforce_dimension(agent_id, dimension: :competence, amount: 0.1)
|
|
167
|
+
entry = map.get(agent_id)
|
|
168
|
+
expect(entry[:dimensions][:reliability]).to eq(Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'updates composite after reinforcement' do
|
|
172
|
+
map.get_or_create(agent_id)
|
|
173
|
+
original_composite = map.get(agent_id)[:composite]
|
|
174
|
+
map.reinforce_dimension(agent_id, dimension: :competence, amount: 0.2)
|
|
175
|
+
expect(map.get(agent_id)[:composite]).to be > original_composite
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'ignores an invalid dimension' do
|
|
179
|
+
map.get_or_create(agent_id)
|
|
180
|
+
original = map.get(agent_id)[:composite]
|
|
181
|
+
map.reinforce_dimension(agent_id, dimension: :invalid_dim)
|
|
182
|
+
expect(map.get(agent_id)[:composite]).to eq(original)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'clamps dimension to 1.0 maximum' do
|
|
186
|
+
map.get_or_create(agent_id)
|
|
187
|
+
map.reinforce_dimension(agent_id, dimension: :reliability, amount: 5.0)
|
|
188
|
+
expect(map.get(agent_id)[:dimensions][:reliability]).to eq(1.0)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
describe '#decay_all' do
|
|
193
|
+
it 'returns 0 when no entries exist' do
|
|
194
|
+
expect(map.decay_all).to eq(0)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'returns the number of entries decayed' do
|
|
198
|
+
map.get_or_create('agent-a')
|
|
199
|
+
map.get_or_create('agent-b')
|
|
200
|
+
expect(map.decay_all).to eq(2)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'reduces each dimension by TRUST_DECAY_RATE' do
|
|
204
|
+
map.get_or_create(agent_id)
|
|
205
|
+
map.decay_all
|
|
206
|
+
entry = map.get(agent_id)
|
|
207
|
+
Legion::Extensions::Trust::Helpers::TrustModel::TRUST_DIMENSIONS.each do |dim|
|
|
208
|
+
expected = Legion::Extensions::Trust::Helpers::TrustModel::NEUTRAL_TRUST -
|
|
209
|
+
Legion::Extensions::Trust::Helpers::TrustModel::TRUST_DECAY_RATE
|
|
210
|
+
expect(entry[:dimensions][dim]).to be_within(0.0001).of(expected)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'floors dimensions at 0.0 when trust is already near-zero' do
|
|
215
|
+
map.get_or_create(agent_id)
|
|
216
|
+
200.times { map.decay_all }
|
|
217
|
+
entry = map.get(agent_id)
|
|
218
|
+
Legion::Extensions::Trust::Helpers::TrustModel::TRUST_DIMENSIONS.each do |dim|
|
|
219
|
+
expect(entry[:dimensions][dim]).to eq(0.0)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'recomputes composite after decay' do
|
|
224
|
+
map.get_or_create(agent_id)
|
|
225
|
+
before = map.get(agent_id)[:composite]
|
|
226
|
+
map.decay_all
|
|
227
|
+
expect(map.get(agent_id)[:composite]).to be < before
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
describe '#trusted_agents' do
|
|
232
|
+
it 'returns empty array when no entries exist' do
|
|
233
|
+
expect(map.trusted_agents).to be_empty
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it 'returns entries with composite >= min_trust' do
|
|
237
|
+
5.times { map.record_interaction(agent_id, positive: true) }
|
|
238
|
+
result = map.trusted_agents
|
|
239
|
+
expect(result).not_to be_empty
|
|
240
|
+
result.each do |entry|
|
|
241
|
+
expect(entry[:composite]).to be >= Legion::Extensions::Trust::Helpers::TrustModel::TRUST_CONSIDER_THRESHOLD
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'excludes entries below min_trust' do
|
|
246
|
+
map.record_interaction(agent_id, positive: false)
|
|
247
|
+
expect(map.trusted_agents).to be_empty
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'filters by domain' do
|
|
251
|
+
5.times { map.record_interaction(agent_id, positive: true, domain: :code) }
|
|
252
|
+
map.record_interaction('other-agent', positive: true, domain: :ops)
|
|
253
|
+
result = map.trusted_agents(domain: :code)
|
|
254
|
+
expect(result.all? { |e| e[:domain] == :code }).to be true
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it 'sorts by composite descending' do
|
|
258
|
+
5.times { map.record_interaction('agent-high', positive: true) }
|
|
259
|
+
3.times { map.record_interaction('agent-low', positive: true) }
|
|
260
|
+
result = map.trusted_agents
|
|
261
|
+
composites = result.map { |e| e[:composite] }
|
|
262
|
+
expect(composites).to eq(composites.sort.reverse)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it 'accepts a custom min_trust threshold' do
|
|
266
|
+
5.times { map.record_interaction(agent_id, positive: true) }
|
|
267
|
+
strict_result = map.trusted_agents(min_trust: 0.9)
|
|
268
|
+
loose_result = map.trusted_agents(min_trust: 0.3)
|
|
269
|
+
expect(strict_result.size).to be <= loose_result.size
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
describe '#delegatable_agents' do
|
|
274
|
+
it 'requires TRUST_DELEGATE_THRESHOLD (higher than trusted_agents default)' do
|
|
275
|
+
5.times { map.record_interaction(agent_id, positive: true) }
|
|
276
|
+
trusted = map.trusted_agents.size
|
|
277
|
+
delegatable = map.delegatable_agents.size
|
|
278
|
+
expect(delegatable).to be <= trusted
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it 'returns empty array when no entries meet the delegation threshold' do
|
|
282
|
+
map.record_interaction(agent_id, positive: true)
|
|
283
|
+
expect(map.delegatable_agents).to be_empty
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
describe '#count' do
|
|
288
|
+
it 'returns 0 for an empty map' do
|
|
289
|
+
expect(map.count).to eq(0)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it 'counts total entries across all agents and domains' do
|
|
293
|
+
map.get_or_create('a', domain: :general)
|
|
294
|
+
map.get_or_create('a', domain: :code)
|
|
295
|
+
map.get_or_create('b', domain: :general)
|
|
296
|
+
expect(map.count).to eq(3)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Trust::Helpers::TrustModel do
|
|
6
|
+
describe 'TRUST_DIMENSIONS' do
|
|
7
|
+
it 'is a frozen array of symbols' do
|
|
8
|
+
expect(described_class::TRUST_DIMENSIONS).to be_a(Array)
|
|
9
|
+
expect(described_class::TRUST_DIMENSIONS).to be_frozen
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'contains exactly four dimensions' do
|
|
13
|
+
expect(described_class::TRUST_DIMENSIONS.size).to eq(4)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'includes reliability' do
|
|
17
|
+
expect(described_class::TRUST_DIMENSIONS).to include(:reliability)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'includes competence' do
|
|
21
|
+
expect(described_class::TRUST_DIMENSIONS).to include(:competence)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'includes integrity' do
|
|
25
|
+
expect(described_class::TRUST_DIMENSIONS).to include(:integrity)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'includes benevolence' do
|
|
29
|
+
expect(described_class::TRUST_DIMENSIONS).to include(:benevolence)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe 'threshold constants' do
|
|
34
|
+
it 'TRUST_CONSIDER_THRESHOLD is 0.3' do
|
|
35
|
+
expect(described_class::TRUST_CONSIDER_THRESHOLD).to eq(0.3)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'TRUST_DELEGATE_THRESHOLD is 0.7' do
|
|
39
|
+
expect(described_class::TRUST_DELEGATE_THRESHOLD).to eq(0.7)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'delegate threshold is higher than consider threshold' do
|
|
43
|
+
expect(described_class::TRUST_DELEGATE_THRESHOLD).to be > described_class::TRUST_CONSIDER_THRESHOLD
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'TRUST_DECAY_RATE is 0.005' do
|
|
47
|
+
expect(described_class::TRUST_DECAY_RATE).to eq(0.005)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'TRUST_REINFORCEMENT is 0.05' do
|
|
51
|
+
expect(described_class::TRUST_REINFORCEMENT).to eq(0.05)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'TRUST_PENALTY is 0.15' do
|
|
55
|
+
expect(described_class::TRUST_PENALTY).to eq(0.15)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'penalty is asymmetrically larger than reinforcement' do
|
|
59
|
+
expect(described_class::TRUST_PENALTY).to be > described_class::TRUST_REINFORCEMENT
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'NEUTRAL_TRUST is 0.3' do
|
|
63
|
+
expect(described_class::NEUTRAL_TRUST).to eq(0.3)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'NEUTRAL_TRUST equals TRUST_CONSIDER_THRESHOLD (new agents are borderline trustworthy)' do
|
|
67
|
+
expect(described_class::NEUTRAL_TRUST).to eq(described_class::TRUST_CONSIDER_THRESHOLD)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '.new_trust_entry' do
|
|
72
|
+
let(:entry) { described_class.new_trust_entry(agent_id: 'agent-42') }
|
|
73
|
+
|
|
74
|
+
it 'sets agent_id' do
|
|
75
|
+
expect(entry[:agent_id]).to eq('agent-42')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'defaults domain to :general' do
|
|
79
|
+
expect(entry[:domain]).to eq(:general)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'respects a custom domain' do
|
|
83
|
+
custom = described_class.new_trust_entry(agent_id: 'agent-1', domain: :code)
|
|
84
|
+
expect(custom[:domain]).to eq(:code)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'initializes all four dimensions to NEUTRAL_TRUST' do
|
|
88
|
+
described_class::TRUST_DIMENSIONS.each do |dim|
|
|
89
|
+
expect(entry[:dimensions][dim]).to eq(described_class::NEUTRAL_TRUST)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'sets composite to NEUTRAL_TRUST' do
|
|
94
|
+
expect(entry[:composite]).to eq(described_class::NEUTRAL_TRUST)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'starts with zero interaction_count' do
|
|
98
|
+
expect(entry[:interaction_count]).to eq(0)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'starts with zero positive_count' do
|
|
102
|
+
expect(entry[:positive_count]).to eq(0)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'starts with zero negative_count' do
|
|
106
|
+
expect(entry[:negative_count]).to eq(0)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'sets last_interaction to nil' do
|
|
110
|
+
expect(entry[:last_interaction]).to be_nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'sets created_at to a recent UTC time' do
|
|
114
|
+
before = Time.now.utc
|
|
115
|
+
e = described_class.new_trust_entry(agent_id: 'x')
|
|
116
|
+
expect(e[:created_at]).to be >= before
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'returns a different hash for each call' do
|
|
120
|
+
a = described_class.new_trust_entry(agent_id: 'a')
|
|
121
|
+
b = described_class.new_trust_entry(agent_id: 'b')
|
|
122
|
+
expect(a).not_to equal(b)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe '.composite_score' do
|
|
127
|
+
it 'returns the arithmetic mean of dimension values' do
|
|
128
|
+
dims = { reliability: 0.8, competence: 0.6, integrity: 0.4, benevolence: 0.2 }
|
|
129
|
+
expect(described_class.composite_score(dims)).to be_within(0.0001).of(0.5)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'returns 1.0 when all dimensions are 1.0' do
|
|
133
|
+
dims = described_class::TRUST_DIMENSIONS.to_h { |d| [d, 1.0] }
|
|
134
|
+
expect(described_class.composite_score(dims)).to eq(1.0)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'returns 0.0 when all dimensions are 0.0' do
|
|
138
|
+
dims = described_class::TRUST_DIMENSIONS.to_h { |d| [d, 0.0] }
|
|
139
|
+
expect(described_class.composite_score(dims)).to eq(0.0)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'returns NEUTRAL_TRUST for freshly initialized dimensions' do
|
|
143
|
+
dims = described_class::TRUST_DIMENSIONS.to_h { |d| [d, described_class::NEUTRAL_TRUST] }
|
|
144
|
+
expect(described_class.composite_score(dims)).to eq(described_class::NEUTRAL_TRUST)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'returns 0.0 for an empty dimensions hash' do
|
|
148
|
+
expect(described_class.composite_score({})).to eq(0.0)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe '.clamp' do
|
|
153
|
+
it 'returns the value when within range' do
|
|
154
|
+
expect(described_class.clamp(0.5)).to eq(0.5)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'clamps to 0.0 when value is negative' do
|
|
158
|
+
expect(described_class.clamp(-0.1)).to eq(0.0)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'clamps to 1.0 when value exceeds 1.0' do
|
|
162
|
+
expect(described_class.clamp(1.5)).to eq(1.0)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'returns exactly 0.0 at the lower boundary' do
|
|
166
|
+
expect(described_class.clamp(0.0)).to eq(0.0)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'returns exactly 1.0 at the upper boundary' do
|
|
170
|
+
expect(described_class.clamp(1.0)).to eq(1.0)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'accepts custom min/max bounds' do
|
|
174
|
+
expect(described_class.clamp(5.0, 0.0, 10.0)).to eq(5.0)
|
|
175
|
+
expect(described_class.clamp(15.0, 0.0, 10.0)).to eq(10.0)
|
|
176
|
+
expect(described_class.clamp(-1.0, 0.0, 10.0)).to eq(0.0)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/trust/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Trust::Runners::Trust do
|
|
6
|
+
let(:client) { Legion::Extensions::Trust::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#get_trust' do
|
|
9
|
+
it 'returns found: false for unknown agent' do
|
|
10
|
+
result = client.get_trust(agent_id: 'unknown')
|
|
11
|
+
expect(result[:found]).to be false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#record_trust_interaction' do
|
|
16
|
+
it 'creates entry and records positive interaction' do
|
|
17
|
+
result = client.record_trust_interaction(agent_id: 'agent-1', positive: true)
|
|
18
|
+
expect(result[:positive]).to be true
|
|
19
|
+
expect(result[:composite]).to be > 0.3 # above neutral
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'creates entry and records negative interaction' do
|
|
23
|
+
result = client.record_trust_interaction(agent_id: 'agent-1', positive: false)
|
|
24
|
+
expect(result[:composite]).to be < 0.3 # below neutral
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'asymmetric: penalty > reinforcement' do
|
|
28
|
+
client.record_trust_interaction(agent_id: 'agent-1', positive: true)
|
|
29
|
+
client.get_trust(agent_id: 'agent-1')[:trust][:composite]
|
|
30
|
+
|
|
31
|
+
client.record_trust_interaction(agent_id: 'agent-1', positive: false)
|
|
32
|
+
after_negative = client.get_trust(agent_id: 'agent-1')[:trust][:composite]
|
|
33
|
+
|
|
34
|
+
# Net of one positive + one negative should be below starting point
|
|
35
|
+
expect(after_negative).to be < 0.3
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'tracks per domain' do
|
|
39
|
+
client.record_trust_interaction(agent_id: 'agent-1', domain: :code, positive: true)
|
|
40
|
+
client.record_trust_interaction(agent_id: 'agent-1', domain: :ops, positive: false)
|
|
41
|
+
|
|
42
|
+
code_trust = client.get_trust(agent_id: 'agent-1', domain: :code)
|
|
43
|
+
ops_trust = client.get_trust(agent_id: 'agent-1', domain: :ops)
|
|
44
|
+
|
|
45
|
+
expect(code_trust[:trust][:composite]).to be > ops_trust[:trust][:composite]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe '#reinforce_trust_dimension' do
|
|
50
|
+
it 'reinforces specific dimension' do
|
|
51
|
+
client.record_trust_interaction(agent_id: 'agent-1', positive: true)
|
|
52
|
+
client.reinforce_trust_dimension(agent_id: 'agent-1', dimension: :competence, amount: 0.2)
|
|
53
|
+
entry = client.get_trust(agent_id: 'agent-1')[:trust]
|
|
54
|
+
expect(entry[:dimensions][:competence]).to be > entry[:dimensions][:reliability]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#decay_trust' do
|
|
59
|
+
it 'decays all entries' do
|
|
60
|
+
client.record_trust_interaction(agent_id: 'agent-1', positive: true)
|
|
61
|
+
before = client.get_trust(agent_id: 'agent-1')[:trust][:composite]
|
|
62
|
+
client.decay_trust
|
|
63
|
+
after = client.get_trust(agent_id: 'agent-1')[:trust][:composite]
|
|
64
|
+
expect(after).to be < before
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#trusted_agents' do
|
|
69
|
+
it 'returns agents above threshold' do
|
|
70
|
+
5.times { client.record_trust_interaction(agent_id: 'agent-1', positive: true) }
|
|
71
|
+
result = client.trusted_agents
|
|
72
|
+
expect(result[:count]).to eq(1)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe '#delegatable_agents' do
|
|
77
|
+
it 'requires higher trust for delegation' do
|
|
78
|
+
3.times { client.record_trust_interaction(agent_id: 'agent-1', positive: true) }
|
|
79
|
+
trusted = client.trusted_agents
|
|
80
|
+
delegatable = client.delegatable_agents
|
|
81
|
+
expect(delegatable[:count]).to be <= trusted[:count]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|