lex-agentic-social 0.1.19 → 0.1.21

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: 49f5148e440e4e58060bf6ff47c9dc8d31bfde5092ab17b7ad078fb95344e711
4
- data.tar.gz: 191fddf9d835866196a20ed7efafcbfb4ef34729d7d5b585f7d68a95dc225b00
3
+ metadata.gz: 813631a90b8945655f76aaca3c5b8ba2bbb8b09a60126da891c1d3c6353e47a9
4
+ data.tar.gz: cc24309702a44a0a1cd7866d8b6632cce6551949079d646e6bb697f4bcd284a9
5
5
  SHA512:
6
- metadata.gz: e052b2d246149565565a6f0a7654bb7d7bf689bc149c2bb17b22fdcc59c15339178805127b1a07e8a602a3d9fd1fcebb3e5b3a1bacef01622f6f4ac934be120f
7
- data.tar.gz: 92f66448450959900ac87054fc9a5b4e9e442b3217f52d35e7b8a6e17c377317932c6f92ed129d64c82c177b101d7c95d30ce0a7e52a818f4d74c49edc87a695
6
+ metadata.gz: db8bafe82e2b2c3e57af811c93ca2cadbd5593d98c95c7deaef69764b55cc35ab8fd54ff3218c3ffce5bd310a002d20ec9f698058a9e90c876eb259cc347894b
7
+ data.tar.gz: 3e461b5ca49eb3d7ff2ad53c4da56c89ec9ed51f74a0cf37c13479a4a17ec57ee5c09e6a9025c07fdb7ddd744e1876ed435e8cba155cc2af47a04bb998b2e62e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.21] - 2026-07-16
4
+ ### Added
5
+ - `CalibrationStore#record_advisory` gains `applied:` kwarg to store which behavioral synapses were applied alongside the advisory.
6
+ - `CalibrationStore#evaluate_reaction` now returns `reaction_score` (Float 0.0–1.0) and `applied` hash in its result hash, enabling legion-gaia's H2 synapse grading path.
7
+ - `Calibration` runner `record_advisory_meta` accepts and passes through the `applied:` kwarg.
8
+ - `Calibration` runner `update_calibration` passes through `reaction_score` and `applied` from the store result.
9
+
10
+ ## [0.1.20] - 2026-07-16
11
+ ### Added
12
+ - `CalibrationRegistry`: per-identity `CalibrationStore` instances keyed by identity string, fixing the grading-contamination bug where one interlocutor's reactions moved another's calibration weights.
13
+ - `CalibrationRegistry#erase_partner!(identity:)`: removes all calibration data for a given identity, leaving others untouched.
14
+ - `CalibrationRegistry#known_identities`: returns all registered identity keys.
15
+ - `CalibrationRegistry#to_apollo_entries_for(identity:)`: returns Apollo persistence entries scoped to one identity.
16
+ - `TrustMap` gains `partner_identity:` keyword on `get`, `get_or_create`, and `record_interaction` — entries are keyed as `(partner_identity, agent_id, domain)` for H7 scoping.
17
+ - `TrustMap#erase_partner!(identity:)`: removes all trust entries scoped to the given partner identity and marks the map dirty.
18
+
3
19
  ## [0.1.19] - 2026-06-01
4
20
  ### Fixed
5
21
  - Calibration LLM preference extraction now uses `Legion::LLM.chat` with explicit system/user messages instead of legacy `Legion::LLM.ask`.
@@ -25,7 +25,7 @@ module Legion
25
25
  { agents_updated: agents.size, models: agents.map { |id| attachment_store.get(id)&.to_h } }
26
26
  end
27
27
 
28
- def reflect_on_bonds(_tick_results: {}, _bond_summary: {}, **)
28
+ def reflect_on_bonds(**)
29
29
  store = apollo_local_store
30
30
  return { success: false, error: :no_store } unless store
31
31
 
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Social
7
+ module Calibration
8
+ module Helpers
9
+ class CalibrationRegistry
10
+ def initialize
11
+ @stores = {}
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ def for(identity:)
16
+ @mutex.synchronize { @stores[identity.to_s] ||= CalibrationStore.new }
17
+ end
18
+
19
+ def erase_partner!(identity:)
20
+ @mutex.synchronize { @stores.delete(identity.to_s) }
21
+ true
22
+ end
23
+
24
+ def known_identities
25
+ @mutex.synchronize { @stores.keys.dup }
26
+ end
27
+
28
+ def to_apollo_entries_for(identity:)
29
+ store = @mutex.synchronize { @stores[identity.to_s] }
30
+ return [] unless store
31
+
32
+ store.to_apollo_entries
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -28,10 +28,11 @@ module Legion
28
28
  self
29
29
  end
30
30
 
31
- def record_advisory(advisory_id:, advisory_types:)
31
+ def record_advisory(advisory_id:, advisory_types:, applied: nil)
32
32
  @last_advisory_meta = {
33
33
  advisory_id: advisory_id,
34
34
  advisory_types: Array(advisory_types).map(&:to_s),
35
+ applied: applied,
35
36
  timestamp: Time.now.utc
36
37
  }
37
38
  end
@@ -45,6 +46,7 @@ module Legion
45
46
 
46
47
  reaction_score = compute_reaction_score(observation)
47
48
  confidence = compute_confidence(observation)
49
+ applied_from_meta = @last_advisory_meta[:applied]
48
50
 
49
51
  deltas = @last_advisory_meta[:advisory_types].map do |type|
50
52
  apply_delta(type: type, reaction_score: reaction_score, confidence: confidence)
@@ -52,7 +54,7 @@ module Legion
52
54
 
53
55
  @last_advisory_meta = nil
54
56
  @dirty = true
55
- { success: true, deltas: deltas }
57
+ { success: true, deltas: deltas, reaction_score: reaction_score, applied: applied_from_meta }
56
58
  end
57
59
 
58
60
  def calibration_weights
@@ -21,11 +21,12 @@ module Legion
21
21
  result = calibration_store.evaluate_reaction(observation: observation)
22
22
  return { success: true, skipped: :no_advisory } unless result
23
23
 
24
- { success: true, deltas: result[:deltas], weights: calibration_store.calibration_weights }
24
+ { success: true, deltas: result[:deltas], weights: calibration_store.calibration_weights,
25
+ reaction_score: result[:reaction_score], applied: result[:applied] }
25
26
  end
26
27
 
27
- def record_advisory_meta(advisory_id:, advisory_types:, **)
28
- calibration_store.record_advisory(advisory_id: advisory_id, advisory_types: advisory_types)
28
+ def record_advisory_meta(advisory_id:, advisory_types:, applied: nil, **)
29
+ calibration_store.record_advisory(advisory_id: advisory_id, advisory_types: advisory_types, applied: applied)
29
30
  { success: true }
30
31
  end
31
32
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'calibration/helpers/constants'
4
4
  require_relative 'calibration/helpers/calibration_store'
5
+ require_relative 'calibration/helpers/calibration_registry'
5
6
  require_relative 'calibration/runners/calibration'
6
7
  require_relative 'calibration/client'
7
8
 
@@ -10,7 +10,7 @@ module Legion
10
10
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
11
11
  Legion::Extensions::Helpers.const_defined?(:Lex, false)
12
12
 
13
- def check_consent(domain:, _action_type: :general, **)
13
+ def check_consent(domain:, **)
14
14
  tier = consent_map.get_tier(domain)
15
15
  log.debug "[consent] check: domain=#{domain} tier=#{tier} allowed=#{tier == :autonomous}"
16
16
 
@@ -61,7 +61,7 @@ module Legion
61
61
  validate_action(layer: :agent_validation, action: action, _context: context)
62
62
  end
63
63
 
64
- def validate_action(layer:, action: nil, _context: {}, **)
64
+ def validate_action(layer:, action: nil, **)
65
65
  return { error: :invalid_layer } unless Helpers::Layers.valid_layer?(layer)
66
66
 
67
67
  log.info "[governance] validating action=#{action} layer=#{layer}"
@@ -10,7 +10,7 @@ module Legion
10
10
  attr_reader :entries
11
11
 
12
12
  def initialize
13
- @entries = {} # key: "agent_id:domain"
13
+ @entries = {} # key: "partner_identity|agent_id:domain"
14
14
  @dirty = false
15
15
  end
16
16
 
@@ -23,16 +23,16 @@ module Legion
23
23
  self
24
24
  end
25
25
 
26
- def get(agent_id, domain: :general)
27
- @entries[key(agent_id, domain)]
26
+ def get(agent_id, domain: :general, partner_identity: nil)
27
+ @entries[key(agent_id, domain, partner_identity)]
28
28
  end
29
29
 
30
- def get_or_create(agent_id, domain: :general)
31
- @entries[key(agent_id, domain)] ||= TrustModel.new_trust_entry(agent_id: agent_id, domain: domain)
30
+ def get_or_create(agent_id, domain: :general, partner_identity: nil)
31
+ @entries[key(agent_id, domain, partner_identity)] ||= TrustModel.new_trust_entry(agent_id: agent_id, domain: domain)
32
32
  end
33
33
 
34
- def record_interaction(agent_id, positive:, domain: :general)
35
- entry = get_or_create(agent_id, domain: domain)
34
+ def record_interaction(agent_id, positive:, domain: :general, partner_identity: nil)
35
+ entry = get_or_create(agent_id, domain: domain, partner_identity: partner_identity)
36
36
  entry[:interaction_count] += 1
37
37
  entry[:last_interaction] = Time.now.utc
38
38
 
@@ -53,6 +53,14 @@ module Legion
53
53
  entry
54
54
  end
55
55
 
56
+ def erase_partner!(identity:)
57
+ prefix = "#{identity}|"
58
+ removed = @entries.keys.select { |k| k.start_with?(prefix) }
59
+ removed.each { |k| @entries.delete(k) }
60
+ @dirty = true if removed.any?
61
+ removed.size
62
+ end
63
+
56
64
  def reinforce_dimension(agent_id, dimension:, domain: :general, amount: TrustModel::TRUST_REINFORCEMENT)
57
65
  entry = get_or_create(agent_id, domain: domain)
58
66
  return unless TrustModel::TRUST_DIMENSIONS.include?(dimension)
@@ -121,8 +129,9 @@ module Legion
121
129
 
122
130
  private
123
131
 
124
- def key(agent_id, domain)
125
- "#{agent_id}:#{domain}"
132
+ def key(agent_id, domain, partner_identity = nil)
133
+ base = "#{agent_id}:#{domain}"
134
+ partner_identity ? "#{partner_identity}|#{base}" : base
126
135
  end
127
136
 
128
137
  def build_apollo_tags(agent_id, domain)
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Social
7
- VERSION = '0.1.19'
7
+ VERSION = '0.1.21'
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Helpers::CalibrationRegistry do
6
+ subject(:registry) { described_class.new }
7
+
8
+ let(:identity_x) { 'user:alice' }
9
+ let(:identity_y) { 'user:bob' }
10
+
11
+ describe '#for(identity:)' do
12
+ it 'returns a CalibrationStore instance' do
13
+ expect(registry.for(identity: identity_x)).to be_a(
14
+ Legion::Extensions::Agentic::Social::Calibration::Helpers::CalibrationStore
15
+ )
16
+ end
17
+
18
+ it 'returns the same instance on repeated calls for the same identity' do
19
+ first = registry.for(identity: identity_x)
20
+ second = registry.for(identity: identity_x)
21
+ expect(first).to equal(second)
22
+ end
23
+
24
+ it 'returns independent instances per identity' do
25
+ x = registry.for(identity: identity_x)
26
+ y = registry.for(identity: identity_y)
27
+ expect(x).not_to equal(y)
28
+ end
29
+
30
+ it 'X reads never see Y data — weights are independent' do
31
+ store_x = registry.for(identity: identity_x)
32
+ store_x.record_advisory(advisory_id: 'ax', advisory_types: [:tone_adjustment])
33
+ store_x.evaluate_reaction(observation: { content: 'thanks, perfect!', direct_address: true, content_length: 20 })
34
+
35
+ store_y = registry.for(identity: identity_y)
36
+ expect(store_y.weights['tone_adjustment']).to eq(0.5)
37
+ end
38
+
39
+ it 'Y negative reaction never moves X calibration weights (contamination guard)' do
40
+ store_x = registry.for(identity: identity_x)
41
+ store_x.record_advisory(advisory_id: 'ax', advisory_types: [:tone_adjustment])
42
+
43
+ store_y = registry.for(identity: identity_y)
44
+ store_y.record_advisory(advisory_id: 'ay', advisory_types: [:tone_adjustment])
45
+ store_y.evaluate_reaction(observation: { content: 'no, wrong', content_length: 5 })
46
+
47
+ # X advisory meta should still be present (Y's reaction didn't consume X's advisory)
48
+ result = store_x.evaluate_reaction(observation: { content: 'hello' })
49
+ expect(result).not_to be_nil
50
+ end
51
+ end
52
+
53
+ describe '#erase_partner!(identity:)' do
54
+ before do
55
+ store = registry.for(identity: identity_x)
56
+ store.record_advisory(advisory_id: 'ax', advisory_types: [:tone_adjustment])
57
+ store.evaluate_reaction(observation: { content: 'thanks' })
58
+
59
+ registry.for(identity: identity_y)
60
+ end
61
+
62
+ it 'removes the store for the given identity' do
63
+ registry.erase_partner!(identity: identity_x)
64
+ # A new store is created — weights reset to neutral
65
+ new_store = registry.for(identity: identity_x)
66
+ expect(new_store.weights['tone_adjustment']).to eq(0.5)
67
+ expect(new_store.history).to be_empty
68
+ end
69
+
70
+ it 'leaves other identities untouched' do
71
+ store_y = registry.for(identity: identity_y)
72
+ original_y = store_y.object_id
73
+ registry.erase_partner!(identity: identity_x)
74
+ expect(registry.for(identity: identity_y).object_id).to eq(original_y)
75
+ end
76
+
77
+ it 'returns true' do
78
+ expect(registry.erase_partner!(identity: identity_x)).to be true
79
+ end
80
+
81
+ it 'returns true even when identity does not exist' do
82
+ expect(registry.erase_partner!(identity: 'unknown')).to be true
83
+ end
84
+ end
85
+
86
+ describe '#known_identities' do
87
+ it 'returns empty array when no identities registered' do
88
+ expect(registry.known_identities).to be_empty
89
+ end
90
+
91
+ it 'returns registered identities' do
92
+ registry.for(identity: identity_x)
93
+ registry.for(identity: identity_y)
94
+ expect(registry.known_identities).to contain_exactly(identity_x, identity_y)
95
+ end
96
+
97
+ it 'does not include erased identities' do
98
+ registry.for(identity: identity_x)
99
+ registry.erase_partner!(identity: identity_x)
100
+ expect(registry.known_identities).not_to include(identity_x)
101
+ end
102
+ end
103
+
104
+ describe 'boot-order / idempotent registration' do
105
+ it 're-registering an already-registered identity is a no-op (no duplicate store)' do
106
+ first = registry.for(identity: identity_x)
107
+ second = registry.for(identity: identity_x)
108
+ expect(first).to equal(second)
109
+ end
110
+ end
111
+
112
+ describe 'fail-closed guard' do
113
+ it 'to_apollo_entries_for returns empty array when identity not registered' do
114
+ expect(registry.to_apollo_entries_for(identity: 'unregistered')).to eq([])
115
+ end
116
+
117
+ it 'to_apollo_entries_for returns entries for a registered identity' do
118
+ registry.for(identity: identity_x)
119
+ entries = registry.to_apollo_entries_for(identity: identity_x)
120
+ expect(entries).to be_an(Array)
121
+ expect(entries).not_to be_empty
122
+ end
123
+ end
124
+ end
@@ -48,6 +48,19 @@ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Helpers::Calibr
48
48
  expect(result).not_to be_nil
49
49
  expect(result[:deltas].size).to eq(2)
50
50
  end
51
+
52
+ it 'stores applied kwarg when provided' do
53
+ applied_data = { synapse_ids: [1, 2, 3], strategy: 'tone' }
54
+ store.record_advisory(advisory_id: 'abc', advisory_types: [:tone_adjustment], applied: applied_data)
55
+ result = store.evaluate_reaction(observation: { content: 'thanks' })
56
+ expect(result[:applied]).to eq(applied_data)
57
+ end
58
+
59
+ it 'stores nil applied when not provided' do
60
+ store.record_advisory(advisory_id: 'abc', advisory_types: [:tone_adjustment])
61
+ result = store.evaluate_reaction(observation: { content: 'thanks' })
62
+ expect(result[:applied]).to be_nil
63
+ end
51
64
  end
52
65
 
53
66
  describe '#evaluate_reaction' do
@@ -90,6 +103,40 @@ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Helpers::Calibr
90
103
  store.evaluate_reaction(observation: { content: 'thanks' })
91
104
  expect(store.history['tone_adjustment']).not_to be_empty
92
105
  end
106
+
107
+ it 'returns reaction_score as a float between 0.0 and 1.0' do
108
+ result = store.evaluate_reaction(observation: { content: 'thanks' })
109
+ expect(result[:reaction_score]).to be_a(Float)
110
+ expect(result[:reaction_score]).to be_between(0.0, 1.0)
111
+ end
112
+
113
+ it 'returns applied from recorded advisory' do
114
+ store.mark_clean!
115
+ applied_data = { synapse_ids: [10, 20] }
116
+ store.record_advisory(advisory_id: 'test2', advisory_types: [:tone_adjustment], applied: applied_data)
117
+ result = store.evaluate_reaction(observation: { content: 'ok' })
118
+ expect(result[:applied]).to eq(applied_data)
119
+ end
120
+ end
121
+
122
+ context 'reaction_score signal ranges' do
123
+ it 'returns reaction_score >= 0.6 for positive feedback' do
124
+ store.record_advisory(advisory_id: 'pos', advisory_types: [:tone_adjustment])
125
+ result = store.evaluate_reaction(observation: { content: 'thanks, perfect!', direct_address: true, content_length: 50 })
126
+ expect(result[:reaction_score]).to be >= 0.6
127
+ end
128
+
129
+ it 'returns reaction_score <= 0.4 for negative feedback' do
130
+ store.record_advisory(advisory_id: 'neg', advisory_types: [:tone_adjustment])
131
+ result = store.evaluate_reaction(observation: { content: 'no, wrong, stop', content_length: 5 })
132
+ expect(result[:reaction_score]).to be <= 0.4
133
+ end
134
+
135
+ it 'returns reaction_score between 0.4 and 0.6 for neutral feedback' do
136
+ store.record_advisory(advisory_id: 'neu', advisory_types: [:tone_adjustment])
137
+ result = store.evaluate_reaction(observation: { content: 'I see', content_length: 10 })
138
+ expect(result[:reaction_score]).to be_between(0.4, 0.6)
139
+ end
93
140
  end
94
141
  end
95
142
 
@@ -22,6 +22,20 @@ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Runners::Calibr
22
22
  expect(result[:success]).to be true
23
23
  expect(result[:deltas]).not_to be_empty
24
24
  end
25
+
26
+ it 'returns reaction_score in result when advisory recorded' do
27
+ client.record_advisory_meta(advisory_id: 'test', advisory_types: [:tone_adjustment])
28
+ result = client.update_calibration(observation: { content: 'thanks', direct_address: true, content_length: 20 })
29
+ expect(result).to have_key(:reaction_score)
30
+ expect(result[:reaction_score]).to be_a(Float)
31
+ end
32
+
33
+ it 'returns applied in result when advisory recorded with applied kwarg' do
34
+ applied_data = { synapse_ids: [5, 6] }
35
+ client.record_advisory_meta(advisory_id: 'test', advisory_types: [:tone_adjustment], applied: applied_data)
36
+ result = client.update_calibration(observation: { content: 'thanks', direct_address: true, content_length: 20 })
37
+ expect(result[:applied]).to eq(applied_data)
38
+ end
25
39
  end
26
40
 
27
41
  describe '#record_advisory_meta' do
@@ -29,6 +43,15 @@ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Runners::Calibr
29
43
  result = client.record_advisory_meta(advisory_id: 'abc', advisory_types: [:partner_hint])
30
44
  expect(result[:success]).to be true
31
45
  end
46
+
47
+ it 'accepts and passes applied kwarg' do
48
+ applied_data = { synapse_ids: [1, 2], strategy: 'verbosity' }
49
+ result = client.record_advisory_meta(advisory_id: 'abc', advisory_types: [:partner_hint], applied: applied_data)
50
+ expect(result[:success]).to be true
51
+ # Verify the applied data flows through to evaluate_reaction
52
+ eval_result = client.update_calibration(observation: { content: 'thanks', content_length: 10 })
53
+ expect(eval_result[:applied]).to eq(applied_data)
54
+ end
32
55
  end
33
56
 
34
57
  describe '#detect_explicit_feedback' do
@@ -296,4 +296,70 @@ RSpec.describe Legion::Extensions::Agentic::Social::Trust::Helpers::TrustMap do
296
296
  expect(map.count).to eq(3)
297
297
  end
298
298
  end
299
+
300
+ describe 'partner_identity keying (H0)' do
301
+ let(:partner_x) { 'user:alice' }
302
+ let(:partner_y) { 'user:bob' }
303
+
304
+ it 'entries scoped to partner_x are not visible under partner_y' do
305
+ map.record_interaction(agent_id, positive: true, partner_identity: partner_x)
306
+ expect(map.get(agent_id, partner_identity: partner_y)).to be_nil
307
+ end
308
+
309
+ it 'independent entries exist per partner_identity' do
310
+ map.record_interaction(agent_id, positive: true, partner_identity: partner_x)
311
+ map.record_interaction(agent_id, positive: false, partner_identity: partner_y)
312
+ expect(map.get(agent_id, partner_identity: partner_x)[:composite]).to be >
313
+ map.get(agent_id, partner_identity: partner_y)[:composite]
314
+ end
315
+
316
+ it 'entries keyed without partner_identity are independent from partner-scoped entries' do
317
+ map.record_interaction(agent_id, positive: true)
318
+ map.record_interaction(agent_id, positive: false, partner_identity: partner_x)
319
+ expect(map.get(agent_id)[:composite]).not_to eq(map.get(agent_id, partner_identity: partner_x)[:composite])
320
+ end
321
+
322
+ it 'count includes all partner-scoped entries' do
323
+ map.get_or_create(agent_id, partner_identity: partner_x)
324
+ map.get_or_create(agent_id, partner_identity: partner_y)
325
+ expect(map.count).to eq(2)
326
+ end
327
+ end
328
+
329
+ describe '#erase_partner!(identity:)' do
330
+ let(:partner_x) { 'user:alice' }
331
+ let(:partner_y) { 'user:bob' }
332
+
333
+ before do
334
+ map.record_interaction(agent_id, positive: true, partner_identity: partner_x)
335
+ map.record_interaction(agent_id, positive: true, partner_identity: partner_y)
336
+ map.record_interaction('other-agent', positive: true, partner_identity: partner_x)
337
+ end
338
+
339
+ it 'removes all entries for the given partner_identity' do
340
+ map.erase_partner!(identity: partner_x)
341
+ expect(map.get(agent_id, partner_identity: partner_x)).to be_nil
342
+ expect(map.get('other-agent', partner_identity: partner_x)).to be_nil
343
+ end
344
+
345
+ it 'leaves entries for other partner identities untouched' do
346
+ map.erase_partner!(identity: partner_x)
347
+ expect(map.get(agent_id, partner_identity: partner_y)).not_to be_nil
348
+ end
349
+
350
+ it 'marks the map as dirty' do
351
+ map.mark_clean!
352
+ map.erase_partner!(identity: partner_x)
353
+ expect(map).to be_dirty
354
+ end
355
+
356
+ it 'returns the count of removed entries' do
357
+ count = map.erase_partner!(identity: partner_x)
358
+ expect(count).to eq(2)
359
+ end
360
+
361
+ it 'returns 0 when no entries exist for that identity' do
362
+ expect(map.erase_partner!(identity: 'unknown')).to eq(0)
363
+ end
364
+ end
299
365
  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.19
4
+ version: 0.1.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -192,6 +192,7 @@ files:
192
192
  - lib/legion/extensions/agentic/social/attachment/version.rb
193
193
  - lib/legion/extensions/agentic/social/calibration.rb
194
194
  - lib/legion/extensions/agentic/social/calibration/client.rb
195
+ - lib/legion/extensions/agentic/social/calibration/helpers/calibration_registry.rb
195
196
  - lib/legion/extensions/agentic/social/calibration/helpers/calibration_store.rb
196
197
  - lib/legion/extensions/agentic/social/calibration/helpers/constants.rb
197
198
  - lib/legion/extensions/agentic/social/calibration/runners/calibration.rb
@@ -335,6 +336,7 @@ files:
335
336
  - spec/legion/extensions/agentic/social/attachment/helpers/attachment_store_spec.rb
336
337
  - spec/legion/extensions/agentic/social/attachment/helpers/constants_spec.rb
337
338
  - spec/legion/extensions/agentic/social/attachment/runners/attachment_spec.rb
339
+ - spec/legion/extensions/agentic/social/calibration/helpers/calibration_registry_spec.rb
338
340
  - spec/legion/extensions/agentic/social/calibration/helpers/calibration_store_spec.rb
339
341
  - spec/legion/extensions/agentic/social/calibration/helpers/constants_spec.rb
340
342
  - spec/legion/extensions/agentic/social/calibration/runners/calibration_spec.rb