lex-agentic-social 0.1.6 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e4e4cf74f2655992c34c4945a8da0de92c597fc5371ff6fa595e583a4cdd88a
4
- data.tar.gz: b0696e9bc1f7b990fb8e3a153fd22755edf34e18a6fdce7ea7efe23eccaa5afb
3
+ metadata.gz: 2a637b1280b8b3159e22384a36f90577406b8f3531189100d5d379cadd2dc00b
4
+ data.tar.gz: 16d221421b1212af948a0afffa6bb54e149087b13b4d473f1dcfe4cdd459ba27
5
5
  SHA512:
6
- metadata.gz: 362432beb980f6e62f4097db1c37f444929ce2edd84439daee258de6e15dae346b5c33b09e3bfab19b2ca5d38ecd75b8f65269099247586750c329e05f5e7728
7
- data.tar.gz: 1309465c2e0655fe62b68b3bfb58cf4f07eb9bbd808215927eb64c240dd4a16575117eb1e3cf6c757d7cd40e586bf3416ce4ddc3ba7feb040d8b2589f6f34c9a
6
+ metadata.gz: 94e0f0c04272c236d18011f3b8bb63798f06be346335bb0dcebac5472439f927817465d2f07bffa5c0633302c0d3bdb8b8a64bb7c30b3d7633a6b38e8cb52029
7
+ data.tar.gz: 5fbd869c1827ee00431b24d9ac317d001dfd4af19926a241f70f06cccad44656467585b6adea2499df1938524151dc096be4ceac3a784c34d52d8461fc744920
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.7] - 2026-03-31
4
+
5
+ ### Changed
6
+ - Migrate `TrustMap` persistence from Data::Local SQLite to Apollo Local (`to_apollo_entries`, `from_apollo`)
7
+ - Add dirty tracking (`dirty?`, `mark_clean!`) to `TrustMap` matching SocialGraph/MentalStateTracker pattern
8
+ - Tag schema: `['trust', 'trust_entry', '<agent_id>', '<domain>']` with optional `'partner'` tag via BondRegistry
9
+ - Remove Data::Local migration registration from trust entry point (migration file retained for existing installs)
10
+ - Add one-time `scripts/migrate_trust_to_apollo.rb` for legacy SQLite data migration
11
+
3
12
  ## [0.1.6] - 2026-03-31
4
13
 
5
14
  ### Added
@@ -11,7 +11,16 @@ module Legion
11
11
 
12
12
  def initialize
13
13
  @entries = {} # key: "agent_id:domain"
14
- load_from_local
14
+ @dirty = false
15
+ end
16
+
17
+ def dirty?
18
+ @dirty
19
+ end
20
+
21
+ def mark_clean!
22
+ @dirty = false
23
+ self
15
24
  end
16
25
 
17
26
  def get(agent_id, domain: :general)
@@ -40,6 +49,7 @@ module Legion
40
49
  end
41
50
 
42
51
  entry[:composite] = TrustModel.composite_score(entry[:dimensions])
52
+ @dirty = true
43
53
  entry
44
54
  end
45
55
 
@@ -49,6 +59,7 @@ module Legion
49
59
 
50
60
  entry[:dimensions][dimension] = TrustModel.clamp(entry[:dimensions][dimension] + amount)
51
61
  entry[:composite] = TrustModel.composite_score(entry[:dimensions])
62
+ @dirty = true
52
63
  end
53
64
 
54
65
  def decay_all
@@ -61,6 +72,7 @@ module Legion
61
72
  entry[:composite] = TrustModel.composite_score(entry[:dimensions])
62
73
  decayed += 1
63
74
  end
75
+ @dirty = true if decayed.positive?
64
76
  decayed
65
77
  end
66
78
 
@@ -78,72 +90,33 @@ module Legion
78
90
  @entries.size
79
91
  end
80
92
 
81
- def save_to_local
82
- return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
83
-
84
- dataset = Legion::Data::Local.connection[:trust_entries]
85
-
86
- @entries.each_value do |entry|
87
- row = {
88
- agent_id: entry[:agent_id].to_s,
89
- domain: entry[:domain].to_s,
90
- reliability: entry[:dimensions][:reliability],
91
- competence: entry[:dimensions][:competence],
92
- integrity: entry[:dimensions][:integrity],
93
- benevolence: entry[:dimensions][:benevolence],
94
- composite: entry[:composite],
95
- interaction_count: entry[:interaction_count],
96
- positive_count: entry[:positive_count],
97
- negative_count: entry[:negative_count],
98
- last_interaction: entry[:last_interaction],
99
- created_at: entry[:created_at]
100
- }
101
- existing = dataset.where(agent_id: row[:agent_id], domain: row[:domain]).first
102
- if existing
103
- dataset.where(agent_id: row[:agent_id], domain: row[:domain])
104
- .update(row.except(:agent_id, :domain))
105
- else
106
- dataset.insert(row)
107
- end
93
+ def to_apollo_entries
94
+ @entries.map do |_key, entry|
95
+ tags = build_apollo_tags(entry[:agent_id], entry[:domain])
96
+ content = Legion::JSON.dump({
97
+ agent_id: entry[:agent_id].to_s,
98
+ domain: entry[:domain].to_s,
99
+ dimensions: entry[:dimensions],
100
+ composite: entry[:composite],
101
+ interaction_count: entry[:interaction_count],
102
+ positive_count: entry[:positive_count],
103
+ negative_count: entry[:negative_count],
104
+ last_interaction: entry[:last_interaction]&.iso8601,
105
+ created_at: entry[:created_at]&.iso8601
106
+ })
107
+ { content: content, tags: tags }
108
108
  end
109
+ end
109
110
 
110
- # Remove DB rows for entries no longer in memory
111
- memory_pairs = @entries.values.map { |e| [e[:agent_id].to_s, e[:domain].to_s] }
112
- dataset.each do |row|
113
- pair = [row[:agent_id], row[:domain]]
114
- dataset.where(agent_id: pair[0], domain: pair[1]).delete unless memory_pairs.include?(pair)
115
- end
116
- rescue StandardError => e
117
- Legion::Logging.warn "[trust] save_to_local failed: #{e.message}"
118
- end
119
-
120
- def load_from_local
121
- return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
122
-
123
- Legion::Data::Local.connection[:trust_entries].each do |row|
124
- agent_id = row[:agent_id]
125
- domain_str = row[:domain]
126
- domain_val = domain_str.to_sym
127
- entry_key = "#{agent_id}:#{domain_str}"
128
- @entries[entry_key] = {
129
- agent_id: agent_id,
130
- domain: domain_val,
131
- dimensions: {
132
- reliability: row[:reliability].to_f,
133
- competence: row[:competence].to_f,
134
- integrity: row[:integrity].to_f,
135
- benevolence: row[:benevolence].to_f
136
- },
137
- composite: row[:composite].to_f,
138
- interaction_count: row[:interaction_count].to_i,
139
- positive_count: row[:positive_count].to_i,
140
- negative_count: row[:negative_count].to_i,
141
- last_interaction: row[:last_interaction],
142
- created_at: row[:created_at]
143
- }
144
- end
111
+ def from_apollo(store:)
112
+ result = store.query(text: 'trust trust_entry', tags: %w[trust trust_entry])
113
+ return false unless result[:success] && result[:results]&.any?
114
+
115
+ result[:results].each { |entry| restore_from_entry(entry) }
116
+ true
145
117
  rescue StandardError => e
146
- Legion::Logging.warn "[trust] load_from_local failed: #{e.message}"
118
+ Legion::Logging.warn("[trust_map] from_apollo error: #{e.message}")
119
+ false
147
120
  end
148
121
 
149
122
  private
@@ -151,6 +124,63 @@ module Legion
151
124
  def key(agent_id, domain)
152
125
  "#{agent_id}:#{domain}"
153
126
  end
127
+
128
+ def build_apollo_tags(agent_id, domain)
129
+ tags = ['trust', 'trust_entry', agent_id.to_s, domain.to_s]
130
+ tags << 'partner' if defined?(Legion::Gaia::BondRegistry) && partner_agent?(agent_id)
131
+ tags
132
+ end
133
+
134
+ def partner_agent?(agent_id)
135
+ Legion::Gaia::BondRegistry.partner?(agent_id.to_s)
136
+ rescue StandardError => e
137
+ Legion::Logging.debug("[trust_map] BondRegistry check failed: #{e.message}")
138
+ false
139
+ end
140
+
141
+ def restore_from_entry(entry)
142
+ data = Legion::JSON.parse(entry[:content])
143
+ agent_id = flex(data, 'agent_id')
144
+ return unless agent_id
145
+
146
+ domain_val = (flex(data, 'domain') || 'general').to_sym
147
+ dims = restore_dimensions(flex(data, 'dimensions') || {})
148
+
149
+ @entries[key(agent_id, domain_val)] = {
150
+ agent_id: agent_id,
151
+ domain: domain_val,
152
+ dimensions: dims,
153
+ composite: (flex(data, 'composite') || TrustModel::NEUTRAL_TRUST).to_f,
154
+ interaction_count: (flex(data, 'interaction_count') || 0).to_i,
155
+ positive_count: (flex(data, 'positive_count') || 0).to_i,
156
+ negative_count: (flex(data, 'negative_count') || 0).to_i,
157
+ last_interaction: parse_time(flex(data, 'last_interaction')),
158
+ created_at: parse_time(flex(data, 'created_at')) || Time.now.utc
159
+ }
160
+ rescue StandardError => e
161
+ Legion::Logging.debug("[trust_map] restore entry failed: #{e.message}")
162
+ nil
163
+ end
164
+
165
+ def flex(hash, string_key)
166
+ hash[string_key] || hash[string_key.to_sym]
167
+ end
168
+
169
+ def restore_dimensions(dims)
170
+ TrustModel::TRUST_DIMENSIONS.to_h do |dim|
171
+ [dim, (flex(dims, dim.to_s) || TrustModel::NEUTRAL_TRUST).to_f]
172
+ end
173
+ end
174
+
175
+ def parse_time(value)
176
+ return nil if value.nil?
177
+ return value if value.is_a?(Time)
178
+
179
+ Time.parse(value.to_s)
180
+ rescue ArgumentError => e
181
+ Legion::Logging.debug("[trust_map] parse_time failed: #{e.message}")
182
+ nil
183
+ end
154
184
  end
155
185
  end
156
186
  end
@@ -14,12 +14,5 @@ module Legion
14
14
  end
15
15
  end
16
16
  end
17
-
18
- if defined?(Legion::Data::Local)
19
- Legion::Data::Local.register_migrations(
20
- name: :trust,
21
- path: File.join(__dir__, 'trust', 'local_migrations')
22
- )
23
- end
24
17
  end
25
18
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Social
7
- VERSION = '0.1.6'
7
+ VERSION = '0.1.7'
8
8
  end
9
9
  end
10
10
  end
@@ -1,204 +1,200 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
- require 'sequel'
5
- require 'tmpdir'
6
-
7
- RSpec.describe 'lex-trust local SQLite persistence' do
8
- let(:db_path) { File.join(Dir.tmpdir, "trust_test_#{Process.pid}_#{rand(9999)}.db") }
9
- let(:db) { Sequel.sqlite(db_path) }
10
-
11
- before do
12
- db.create_table(:trust_entries) do
13
- primary_key :id
14
- String :agent_id, null: false
15
- String :domain, null: false
16
- Float :reliability, default: 0.3
17
- Float :competence, default: 0.3
18
- Float :integrity, default: 0.3
19
- Float :benevolence, default: 0.3
20
- Float :composite, default: 0.3
21
- Integer :interaction_count, default: 0
22
- Integer :positive_count, default: 0
23
- Integer :negative_count, default: 0
24
- DateTime :last_interaction
25
- DateTime :created_at, null: false
26
- unique %i[agent_id domain]
27
- index [:agent_id]
28
- end
29
-
30
- stub_const('Legion::Data::Local', Module.new do
31
- def self.connected?
32
- true
33
- end
34
4
 
35
- def self.connection
36
- @_connection
37
- end
5
+ RSpec.describe 'lex-trust Apollo Local persistence' do
6
+ subject(:map) { described_class.new }
38
7
 
39
- def self._set_connection(conn)
40
- @_connection = conn
41
- end
42
- end)
8
+ let(:described_class) { Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap }
43
9
 
44
- Legion::Data::Local._set_connection(db)
45
- end
46
-
47
- after do
48
- db.disconnect
49
- FileUtils.rm_f(db_path)
50
- end
10
+ describe '#dirty?' do
11
+ it 'starts clean' do
12
+ expect(map.dirty?).to be false
13
+ end
51
14
 
52
- describe 'save_to_local' do
53
- it 'writes an entry to the database' do
54
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
15
+ it 'becomes dirty after record_interaction' do
55
16
  map.record_interaction('agent-001', positive: true)
56
- map.save_to_local
57
-
58
- row = db[:trust_entries].where(agent_id: 'agent-001', domain: 'general').first
59
- expect(row).not_to be_nil
60
- expect(row[:agent_id]).to eq('agent-001')
61
- expect(row[:domain]).to eq('general')
62
- expect(row[:positive_count]).to eq(1)
63
- expect(row[:interaction_count]).to eq(1)
17
+ expect(map.dirty?).to be true
64
18
  end
65
19
 
66
- it 'updates an existing row on second save' do
67
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
68
- map.record_interaction('agent-001', positive: true)
69
- map.save_to_local
20
+ it 'becomes dirty after reinforce_dimension' do
21
+ map.get_or_create('agent-001')
22
+ map.reinforce_dimension('agent-001', dimension: :competence)
23
+ expect(map.dirty?).to be true
24
+ end
70
25
 
71
- map.record_interaction('agent-001', positive: false)
72
- map.save_to_local
26
+ it 'becomes dirty after decay_all with entries' do
27
+ map.get_or_create('agent-001')
28
+ map.decay_all
29
+ expect(map.dirty?).to be true
30
+ end
73
31
 
74
- rows = db[:trust_entries].where(agent_id: 'agent-001', domain: 'general').all
75
- expect(rows.size).to eq(1)
76
- expect(rows.first[:negative_count]).to eq(1)
77
- expect(rows.first[:interaction_count]).to eq(2)
32
+ it 'stays clean after decay_all with no entries' do
33
+ map.decay_all
34
+ expect(map.dirty?).to be false
78
35
  end
79
36
 
80
- it 'persists composite score' do
81
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
37
+ it 'becomes clean after mark_clean!' do
82
38
  map.record_interaction('agent-001', positive: true)
83
- map.save_to_local
39
+ map.mark_clean!
40
+ expect(map.dirty?).to be false
41
+ end
42
+ end
84
43
 
85
- row = db[:trust_entries].where(agent_id: 'agent-001', domain: 'general').first
86
- expect(row[:composite]).to be > 0.3
44
+ describe '#mark_clean!' do
45
+ it 'returns self' do
46
+ expect(map.mark_clean!).to eq(map)
87
47
  end
88
48
 
89
- it 'persists all four dimension scores' do
90
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
49
+ it 'resets dirty flag' do
91
50
  map.record_interaction('agent-001', positive: true)
92
- map.save_to_local
51
+ map.mark_clean!
52
+ expect(map.dirty?).to be false
53
+ end
54
+ end
93
55
 
94
- row = db[:trust_entries].where(agent_id: 'agent-001', domain: 'general').first
95
- expect(row[:reliability]).to be > 0.3
96
- expect(row[:competence]).to be > 0.3
97
- expect(row[:integrity]).to be > 0.3
98
- expect(row[:benevolence]).to be > 0.3
56
+ describe '#to_apollo_entries' do
57
+ it 'returns empty array when no entries' do
58
+ expect(map.to_apollo_entries).to eq([])
99
59
  end
100
60
 
101
- it 'persists entries for multiple agents' do
102
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
61
+ it 'returns one entry per trust record' do
103
62
  map.record_interaction('agent-a', positive: true)
104
63
  map.record_interaction('agent-b', positive: false)
105
- map.save_to_local
106
-
107
- expect(db[:trust_entries].count).to eq(2)
64
+ expect(map.to_apollo_entries.size).to eq(2)
108
65
  end
109
66
 
110
- it 'persists entries for multiple domains of the same agent' do
111
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
67
+ it 'returns separate entries for different domains of the same agent' do
112
68
  map.record_interaction('agent-001', positive: true, domain: :code)
113
69
  map.record_interaction('agent-001', positive: false, domain: :ops)
114
- map.save_to_local
70
+ expect(map.to_apollo_entries.size).to eq(2)
71
+ end
72
+
73
+ it 'entry content is a JSON string with agent_id' do
74
+ map.record_interaction('agent-001', positive: true)
75
+ entry = map.to_apollo_entries.first
76
+ parsed = JSON.parse(entry[:content])
77
+ expect(parsed).to have_key('agent_id')
78
+ expect(parsed['agent_id']).to eq('agent-001')
79
+ end
80
+
81
+ it 'entry content includes domain' do
82
+ map.record_interaction('agent-001', positive: true, domain: :code)
83
+ entry = map.to_apollo_entries.first
84
+ parsed = JSON.parse(entry[:content])
85
+ expect(parsed['domain']).to eq('code')
86
+ end
87
+
88
+ it 'entry content includes all four dimensions' do
89
+ map.record_interaction('agent-001', positive: true)
90
+ entry = map.to_apollo_entries.first
91
+ parsed = JSON.parse(entry[:content])
92
+ expect(parsed['dimensions']).to have_key('reliability')
93
+ expect(parsed['dimensions']).to have_key('competence')
94
+ expect(parsed['dimensions']).to have_key('integrity')
95
+ expect(parsed['dimensions']).to have_key('benevolence')
96
+ end
115
97
 
116
- expect(db[:trust_entries].where(agent_id: 'agent-001').count).to eq(2)
98
+ it 'entry content includes composite score' do
99
+ map.record_interaction('agent-001', positive: true)
100
+ entry = map.to_apollo_entries.first
101
+ parsed = JSON.parse(entry[:content])
102
+ expect(parsed['composite']).to be > 0.3
117
103
  end
118
104
 
119
- it 'removes DB rows for entries removed from memory' do
120
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
121
- map.get_or_create('agent-a')
122
- map.get_or_create('agent-b')
123
- map.save_to_local
105
+ it 'entry content includes interaction counts' do
106
+ map.record_interaction('agent-001', positive: true)
107
+ entry = map.to_apollo_entries.first
108
+ parsed = JSON.parse(entry[:content])
109
+ expect(parsed['interaction_count']).to eq(1)
110
+ expect(parsed['positive_count']).to eq(1)
111
+ expect(parsed['negative_count']).to eq(0)
112
+ end
124
113
 
125
- expect(db[:trust_entries].count).to eq(2)
114
+ it 'entry tags include trust, trust_entry, agent_id, and domain' do
115
+ map.record_interaction('agent-001', positive: true, domain: :code)
116
+ entry = map.to_apollo_entries.first
117
+ expect(entry[:tags]).to include('trust', 'trust_entry', 'agent-001', 'code')
118
+ end
126
119
 
127
- # Simulate removal: build new map with only agent-a, then save
128
- map2 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
129
- map2.save_to_local # loaded agent-a and agent-b from DB, both still in memory
130
- expect(db[:trust_entries].count).to eq(2) # nothing deleted yet
120
+ it 'entry tags include partner when BondRegistry reports partner' do
121
+ bond_registry = Module.new do
122
+ def self.partner?(agent_id)
123
+ agent_id == 'agent-001'
124
+ end
125
+ end
126
+ stub_const('Legion::Gaia::BondRegistry', bond_registry)
131
127
 
132
- # Remove agent-b from memory then save
133
- map2.entries.delete_if { |k, _| k.start_with?('agent-b') }
134
- map2.save_to_local
128
+ map.record_interaction('agent-001', positive: true)
129
+ entry = map.to_apollo_entries.first
130
+ expect(entry[:tags]).to include('partner')
131
+ end
135
132
 
136
- expect(db[:trust_entries].count).to eq(1)
137
- expect(db[:trust_entries].first[:agent_id]).to eq('agent-a')
133
+ it 'entry tags exclude partner for non-partner agents' do
134
+ bond_registry = Module.new do
135
+ def self.partner?(_agent_id)
136
+ false
137
+ end
138
+ end
139
+ stub_const('Legion::Gaia::BondRegistry', bond_registry)
140
+
141
+ map.record_interaction('agent-001', positive: true)
142
+ entry = map.to_apollo_entries.first
143
+ expect(entry[:tags]).not_to include('partner')
138
144
  end
139
145
  end
140
146
 
141
- describe 'load_from_local' do
142
- it 'restores entries from the database on initialize' do
143
- db[:trust_entries].insert(
144
- agent_id: 'agent-001',
145
- domain: 'general',
146
- reliability: 0.45,
147
- competence: 0.45,
148
- integrity: 0.45,
149
- benevolence: 0.45,
150
- composite: 0.45,
151
- interaction_count: 3,
152
- positive_count: 3,
153
- negative_count: 0,
154
- created_at: Time.now.utc
155
- )
147
+ describe '#from_apollo' do
148
+ let(:mock_store) do
149
+ double('ApolloLocal').tap do |store|
150
+ allow(store).to receive(:query).and_return({ success: false, results: [] })
151
+ end
152
+ end
156
153
 
157
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
158
- entry = map.get('agent-001')
159
- expect(entry).not_to be_nil
160
- expect(entry[:composite]).to be_within(0.0001).of(0.45)
161
- expect(entry[:interaction_count]).to eq(3)
162
- expect(entry[:positive_count]).to eq(3)
154
+ it 'returns false when store query fails or returns no results' do
155
+ result = map.from_apollo(store: mock_store)
156
+ expect(result).to be false
163
157
  end
164
158
 
165
- it 'restores domain as a symbol' do
166
- db[:trust_entries].insert(
167
- agent_id: 'agent-001',
168
- domain: 'code',
169
- reliability: 0.3,
170
- competence: 0.3,
171
- integrity: 0.3,
172
- benevolence: 0.3,
173
- composite: 0.3,
174
- interaction_count: 0,
175
- positive_count: 0,
176
- negative_count: 0,
177
- created_at: Time.now.utc
159
+ it 'populates entries from stored JSON' do
160
+ content = JSON.dump({
161
+ agent_id: 'agent-001', domain: 'general',
162
+ dimensions: { reliability: 0.6, competence: 0.5, integrity: 0.4, benevolence: 0.3 },
163
+ composite: 0.45, interaction_count: 3, positive_count: 3, negative_count: 0,
164
+ last_interaction: Time.now.utc.iso8601, created_at: Time.now.utc.iso8601
165
+ })
166
+ allow(mock_store).to receive(:query).and_return(
167
+ { success: true, results: [{ content: content, tags: %w[trust trust_entry agent-001 general] }] }
178
168
  )
169
+ map.from_apollo(store: mock_store)
170
+ expect(map.get('agent-001')).not_to be_nil
171
+ end
179
172
 
180
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
173
+ it 'restores domain as a symbol' do
174
+ content = JSON.dump({
175
+ agent_id: 'agent-001', domain: 'code',
176
+ dimensions: { reliability: 0.3, competence: 0.3, integrity: 0.3, benevolence: 0.3 },
177
+ composite: 0.3, interaction_count: 0, positive_count: 0, negative_count: 0
178
+ })
179
+ allow(mock_store).to receive(:query).and_return(
180
+ { success: true, results: [{ content: content, tags: %w[trust trust_entry agent-001 code] }] }
181
+ )
182
+ map.from_apollo(store: mock_store)
181
183
  entry = map.get('agent-001', domain: :code)
182
184
  expect(entry).not_to be_nil
183
185
  expect(entry[:domain]).to eq(:code)
184
186
  end
185
187
 
186
188
  it 'restores all four dimension values' do
187
- db[:trust_entries].insert(
188
- agent_id: 'agent-001',
189
- domain: 'general',
190
- reliability: 0.6,
191
- competence: 0.5,
192
- integrity: 0.4,
193
- benevolence: 0.3,
194
- composite: 0.45,
195
- interaction_count: 0,
196
- positive_count: 0,
197
- negative_count: 0,
198
- created_at: Time.now.utc
189
+ content = JSON.dump({
190
+ agent_id: 'agent-001', domain: 'general',
191
+ dimensions: { reliability: 0.6, competence: 0.5, integrity: 0.4, benevolence: 0.3 },
192
+ composite: 0.45, interaction_count: 0, positive_count: 0, negative_count: 0
193
+ })
194
+ allow(mock_store).to receive(:query).and_return(
195
+ { success: true, results: [{ content: content, tags: %w[trust trust_entry agent-001 general] }] }
199
196
  )
200
-
201
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
197
+ map.from_apollo(store: mock_store)
202
198
  entry = map.get('agent-001')
203
199
  expect(entry[:dimensions][:reliability]).to be_within(0.0001).of(0.6)
204
200
  expect(entry[:dimensions][:competence]).to be_within(0.0001).of(0.5)
@@ -207,64 +203,98 @@ RSpec.describe 'lex-trust local SQLite persistence' do
207
203
  end
208
204
 
209
205
  it 'restores multiple entries' do
210
- db[:trust_entries].insert(
211
- agent_id: 'agent-a', domain: 'general',
212
- reliability: 0.3, competence: 0.3, integrity: 0.3, benevolence: 0.3,
213
- composite: 0.3, interaction_count: 0, positive_count: 0, negative_count: 0,
214
- created_at: Time.now.utc
206
+ content_a = JSON.dump({
207
+ agent_id: 'agent-a', domain: 'general',
208
+ dimensions: { reliability: 0.3, competence: 0.3, integrity: 0.3, benevolence: 0.3 },
209
+ composite: 0.3, interaction_count: 0, positive_count: 0, negative_count: 0
210
+ })
211
+ content_b = JSON.dump({
212
+ agent_id: 'agent-b', domain: 'ops',
213
+ dimensions: { reliability: 0.3, competence: 0.3, integrity: 0.3, benevolence: 0.3 },
214
+ composite: 0.3, interaction_count: 0, positive_count: 0, negative_count: 0
215
+ })
216
+ allow(mock_store).to receive(:query).and_return(
217
+ { success: true, results: [
218
+ { content: content_a, tags: %w[trust trust_entry agent-a general] },
219
+ { content: content_b, tags: %w[trust trust_entry agent-b ops] }
220
+ ] }
215
221
  )
216
- db[:trust_entries].insert(
217
- agent_id: 'agent-b', domain: 'ops',
218
- reliability: 0.3, competence: 0.3, integrity: 0.3, benevolence: 0.3,
219
- composite: 0.3, interaction_count: 0, positive_count: 0, negative_count: 0,
220
- created_at: Time.now.utc
221
- )
222
-
223
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
222
+ map.from_apollo(store: mock_store)
224
223
  expect(map.count).to eq(2)
225
224
  end
226
225
 
227
- it 'allows normal operations on restored entries' do
228
- db[:trust_entries].insert(
229
- agent_id: 'agent-001',
230
- domain: 'general',
231
- reliability: 0.4,
232
- competence: 0.4,
233
- integrity: 0.4,
234
- benevolence: 0.4,
235
- composite: 0.4,
236
- interaction_count: 2,
237
- positive_count: 2,
238
- negative_count: 0,
239
- created_at: Time.now.utc
226
+ it 'restores interaction counts' do
227
+ content = JSON.dump({
228
+ agent_id: 'agent-001', domain: 'general',
229
+ dimensions: { reliability: 0.45, competence: 0.45, integrity: 0.45, benevolence: 0.45 },
230
+ composite: 0.45, interaction_count: 5, positive_count: 3, negative_count: 2
231
+ })
232
+ allow(mock_store).to receive(:query).and_return(
233
+ { success: true, results: [{ content: content, tags: %w[trust trust_entry agent-001 general] }] }
240
234
  )
235
+ map.from_apollo(store: mock_store)
236
+ entry = map.get('agent-001')
237
+ expect(entry[:interaction_count]).to eq(5)
238
+ expect(entry[:positive_count]).to eq(3)
239
+ expect(entry[:negative_count]).to eq(2)
240
+ end
241
241
 
242
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
242
+ it 'allows normal operations on restored entries' do
243
+ content = JSON.dump({
244
+ agent_id: 'agent-001', domain: 'general',
245
+ dimensions: { reliability: 0.4, competence: 0.4, integrity: 0.4, benevolence: 0.4 },
246
+ composite: 0.4, interaction_count: 2, positive_count: 2, negative_count: 0
247
+ })
248
+ allow(mock_store).to receive(:query).and_return(
249
+ { success: true, results: [{ content: content, tags: %w[trust trust_entry agent-001 general] }] }
250
+ )
251
+ map.from_apollo(store: mock_store)
243
252
  map.record_interaction('agent-001', positive: true)
244
253
  entry = map.get('agent-001')
245
254
  expect(entry[:interaction_count]).to eq(3)
246
255
  expect(entry[:positive_count]).to eq(3)
247
256
  end
257
+
258
+ it 'trusted_agents works correctly after hydration' do
259
+ content = JSON.dump({
260
+ agent_id: 'agent-001', domain: 'general',
261
+ dimensions: { reliability: 0.6, competence: 0.6, integrity: 0.6, benevolence: 0.6 },
262
+ composite: 0.6, interaction_count: 5, positive_count: 5, negative_count: 0
263
+ })
264
+ allow(mock_store).to receive(:query).and_return(
265
+ { success: true, results: [{ content: content, tags: %w[trust trust_entry agent-001 general] }] }
266
+ )
267
+ map.from_apollo(store: mock_store)
268
+ trusted = map.trusted_agents
269
+ expect(trusted).not_to be_empty
270
+ expect(trusted.first[:agent_id]).to eq('agent-001')
271
+ end
248
272
  end
249
273
 
250
- describe 'round-trip persistence' do
251
- it 'saves and restores trust score across instances' do
252
- map1 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
253
- 5.times { map1.record_interaction('agent-001', positive: true) }
254
- composite_before = map1.get('agent-001')[:composite]
255
- map1.save_to_local
274
+ describe 'round-trip via to_apollo_entries / from_apollo' do
275
+ let(:mock_store) { double('ApolloLocal') }
276
+
277
+ it 'preserves trust scores across serialize/deserialize' do
278
+ 5.times { map.record_interaction('agent-001', positive: true) }
279
+ composite_before = map.get('agent-001')[:composite]
280
+
281
+ entries = map.to_apollo_entries
282
+ allow(mock_store).to receive(:query).and_return({ success: true, results: entries })
256
283
 
257
- map2 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
284
+ map2 = described_class.new
285
+ map2.from_apollo(store: mock_store)
258
286
  expect(map2.get('agent-001')[:composite]).to be_within(0.0001).of(composite_before)
259
287
  end
260
288
 
261
289
  it 'round-trips interaction counts accurately' do
262
- map1 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
263
- 3.times { map1.record_interaction('agent-001', positive: true) }
264
- 2.times { map1.record_interaction('agent-001', positive: false) }
265
- map1.save_to_local
290
+ 3.times { map.record_interaction('agent-001', positive: true) }
291
+ 2.times { map.record_interaction('agent-001', positive: false) }
266
292
 
267
- map2 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
293
+ entries = map.to_apollo_entries
294
+ allow(mock_store).to receive(:query).and_return({ success: true, results: entries })
295
+
296
+ map2 = described_class.new
297
+ map2.from_apollo(store: mock_store)
268
298
  entry = map2.get('agent-001')
269
299
  expect(entry[:interaction_count]).to eq(5)
270
300
  expect(entry[:positive_count]).to eq(3)
@@ -272,88 +302,45 @@ RSpec.describe 'lex-trust local SQLite persistence' do
272
302
  end
273
303
 
274
304
  it 'round-trips multiple agents independently' do
275
- map1 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
276
- 5.times { map1.record_interaction('agent-high', positive: true) }
277
- map1.record_interaction('agent-low', positive: false)
278
- map1.save_to_local
305
+ 5.times { map.record_interaction('agent-high', positive: true) }
306
+ map.record_interaction('agent-low', positive: false)
307
+
308
+ entries = map.to_apollo_entries
309
+ allow(mock_store).to receive(:query).and_return({ success: true, results: entries })
279
310
 
280
- map2 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
311
+ map2 = described_class.new
312
+ map2.from_apollo(store: mock_store)
281
313
  expect(map2.get('agent-high')[:composite]).to be > map2.get('agent-low')[:composite]
282
314
  end
283
315
 
284
316
  it 'round-trips domain-scoped entries independently' do
285
- map1 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
286
- 5.times { map1.record_interaction('agent-001', positive: true, domain: :code) }
287
- map1.record_interaction('agent-001', positive: false, domain: :ops)
288
- map1.save_to_local
317
+ 5.times { map.record_interaction('agent-001', positive: true, domain: :code) }
318
+ map.record_interaction('agent-001', positive: false, domain: :ops)
289
319
 
290
- map2 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
320
+ entries = map.to_apollo_entries
321
+ allow(mock_store).to receive(:query).and_return({ success: true, results: entries })
322
+
323
+ map2 = described_class.new
324
+ map2.from_apollo(store: mock_store)
291
325
  code_entry = map2.get('agent-001', domain: :code)
292
326
  ops_entry = map2.get('agent-001', domain: :ops)
293
327
  expect(code_entry[:composite]).to be > ops_entry[:composite]
294
328
  end
295
329
 
296
- it 'trusted_agents works correctly after round-trip' do
297
- map1 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
298
- 5.times { map1.record_interaction('agent-001', positive: true) }
299
- map1.save_to_local
300
-
301
- map2 = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
302
- trusted = map2.trusted_agents
303
- expect(trusted).not_to be_empty
304
- expect(trusted.first[:agent_id]).to eq('agent-001')
305
- end
306
- end
307
-
308
- describe 'graceful no-op when Legion::Data::Local is unavailable' do
309
- before do
310
- hide_const('Legion::Data::Local')
311
- end
312
-
313
- it 'initialize completes without raising' do
314
- expect { Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new }.not_to raise_error
315
- end
316
-
317
- it 'save_to_local does nothing without raising' do
318
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
319
- map.record_interaction('agent-001', positive: true)
320
- expect { map.save_to_local }.not_to raise_error
321
- end
322
-
323
- it 'starts with empty in-memory state' do
324
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
325
- expect(map.count).to eq(0)
326
- end
327
-
328
- it 'operates normally in memory when DB unavailable' do
329
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
330
+ it 'round-trips all four dimension values' do
330
331
  map.record_interaction('agent-001', positive: true)
331
- expect(map.get('agent-001')[:positive_count]).to eq(1)
332
- end
333
- end
334
-
335
- describe 'graceful no-op when Legion::Data::Local is defined but not connected' do
336
- before do
337
- stub_const('Legion::Data::Local', Module.new do
338
- def self.connected?
339
- false
340
- end
341
- end)
342
- end
343
-
344
- it 'initialize completes without raising' do
345
- expect { Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new }.not_to raise_error
346
- end
332
+ map.reinforce_dimension('agent-001', dimension: :competence, amount: 0.2)
347
333
 
348
- it 'save_to_local does nothing without raising' do
349
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
350
- map.record_interaction('agent-001', positive: true)
351
- expect { map.save_to_local }.not_to raise_error
352
- end
334
+ entries = map.to_apollo_entries
335
+ allow(mock_store).to receive(:query).and_return({ success: true, results: entries })
353
336
 
354
- it 'starts with empty in-memory state' do
355
- map = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap.new
356
- expect(map.count).to eq(0)
337
+ map2 = described_class.new
338
+ map2.from_apollo(store: mock_store)
339
+ original = map.get('agent-001')[:dimensions]
340
+ restored = map2.get('agent-001')[:dimensions]
341
+ Legion::Extensions::Agentic::Social::Trust::Helpers::TrustModel::TRUST_DIMENSIONS.each do |dim|
342
+ expect(restored[dim]).to be_within(0.0001).of(original[dim])
343
+ end
357
344
  end
358
345
  end
359
346
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-social
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity